different large refactorings (subdirectores, removed obsolete stuff) and
bug fixes
This commit is contained in:
@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Contour Scanner</name>
|
||||
<id>fablabchemnitz.de.contour_scanner</id>
|
||||
<param name="main_tabs" type="notebook">
|
||||
<page name="tab_active" gui-text="Active">
|
||||
<param name="desc" type="description">This tool helps you to find nasty contours which might bug you and prevent your work from being ready for production. You can find the complete documentation at the Wiki space of https://fablabchemnitz.de</param>
|
||||
<param name="help_general" type="description" appearance="header">General</param>
|
||||
<param name="breakapart" type="bool" gui-text="Break apart contours">false</param>
|
||||
<param name="removefillsetstroke" type="bool" gui-text="Remove fill and define stroke">false</param>
|
||||
<param name="strokewidth" min="0.0" max="10000.0" gui-text="Stroke width (px)" type="float">1.0</param>
|
||||
<param name="help_highlight" type="description" appearance="header">Highlight paths</param>
|
||||
<param name="highlight_opened" type="bool" gui-text="Highlight opened contours">true</param>
|
||||
<param name="color_opened" type="color" appearance="colorbutton" gui-text="Color opened contours">4012452351</param>
|
||||
<param name="highlight_closed" type="bool" gui-text="Highlight closed contours">true</param>
|
||||
<param name="color_closed" type="color" appearance="colorbutton" gui-text="Color closed contours">2330080511</param>
|
||||
<param name="highlight_selfintersecting" type="bool" gui-text="Highlight self-intersecting contours">true</param>
|
||||
<param name="color_selfintersecting" type="color" appearance="colorbutton" gui-text="Color self-intersecting contours">1923076095</param>
|
||||
<param name="highlight_intersectionpoints" type="bool" gui-text="Highlight self-intersecting points">true</param>
|
||||
<param name="color_intersectionpoints" type="color" appearance="colorbutton" gui-text="Color self-intersecting points">4239343359</param>
|
||||
<param name="dotsize" type="int" min="0" max="10000" gui-text="Dot size (px) for self-intersecting points">10</param>
|
||||
<param name="help_remove" type="description" appearance="header">Remove paths</param>
|
||||
<param name="remove_opened" type="bool" gui-text="Remove opened contours">false</param>
|
||||
<param name="remove_closed" type="bool" gui-text="Remove closed contours">false</param>
|
||||
<param name="remove_selfintersecting" type="bool" gui-text="Remove self-intersecting contours">false</param>
|
||||
</page>
|
||||
<page name="tab_info" gui-text="License/Version">
|
||||
<param name="desc1" type="description">Written by Mario Voigt (Stadtfabrikanten e.V. / FabLab Chemnitz) (https://gitea.fablabchemnitz.de)</param>
|
||||
<param name="desc2" type="description">Last update: 09.08.2020</param>
|
||||
<param name="desc3" type="description">This piece of software is part of the MightyScape for InkScape 1.0/1.1dev Extension Collection</param>
|
||||
<param name="desc4" type="description" appearance="header">you found a bug or got some fresh code? Just report to mario.voigt@stadtfabrikanten.org. Thanks!</param>
|
||||
</page>
|
||||
</param>
|
||||
<effect>
|
||||
<object-type>path</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz">
|
||||
<submenu name="Nesting/Cut Optimization"/>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">fablabchemnitz_contour_scanner.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
@ -0,0 +1,203 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Extension for InkScape 1.0
|
||||
Features
|
||||
- helps to find contours which are closed or not. Good for repairing contours, closing contours,...
|
||||
- works for paths which are packed into groups or groups of groups. #
|
||||
- can break contours apart like in "Path -> Break Apart"
|
||||
- implements Bentley-Ottmann algorithm from https://github.com/ideasman42/isect_segments-bentley_ottmann to scan for self-intersecting paths. You might get "assert(event.in_sweep == False) AssertionError". Don't know how to fix rgis
|
||||
- colorized paths respective to their type
|
||||
- can add dots to intersection points you'd like to fix
|
||||
|
||||
Author: Mario Voigt / FabLab Chemnitz
|
||||
Mail: mario.voigt@stadtfabrikanten.org
|
||||
Date: 09.08.2020
|
||||
Last patch: 19.08.2020
|
||||
License: GNU GPL v3
|
||||
"""
|
||||
|
||||
from math import *
|
||||
import inkex
|
||||
from inkex.paths import Path, CubicSuperPath
|
||||
from inkex import Style, Color, Circle
|
||||
from lxml import etree
|
||||
import fablabchemnitz_poly_point_isect
|
||||
import copy
|
||||
|
||||
def adjustStyle(self, node):
|
||||
if node.attrib.has_key('style'):
|
||||
style = node.get('style')
|
||||
if style:
|
||||
declarations = style.split(';')
|
||||
for i,decl in enumerate(declarations):
|
||||
parts = decl.split(':', 2)
|
||||
if len(parts) == 2:
|
||||
(prop, val) = parts
|
||||
prop = prop.strip().lower()
|
||||
if prop == 'stroke-width':
|
||||
declarations[i] = prop + ':' + str(self.svg.unittouu(str(self.options.strokewidth) +"px"))
|
||||
if prop == 'fill':
|
||||
declarations[i] = prop + ':none'
|
||||
node.set('style', ';'.join(declarations) + ';stroke:#000000;stroke-opacity:1.0')
|
||||
|
||||
class ContourScanner(inkex.Effect):
|
||||
|
||||
def __init__(self):
|
||||
inkex.Effect.__init__(self)
|
||||
self.arg_parser.add_argument("--breakapart", type=inkex.Boolean, default=False, help="Break apart contours")
|
||||
self.arg_parser.add_argument("--removefillsetstroke", type=inkex.Boolean, default=False, help="Remove fill and define stroke")
|
||||
self.arg_parser.add_argument("--strokewidth", type=float, default=1.0, help="Stroke width (px)")
|
||||
self.arg_parser.add_argument("--highlight_opened", type=inkex.Boolean, default=True, help="Highlight opened contours")
|
||||
self.arg_parser.add_argument("--color_opened", type=Color, default='4012452351', help="Color opened contours")
|
||||
self.arg_parser.add_argument("--highlight_closed", type=inkex.Boolean, default=True, help="Highlight closed contours")
|
||||
self.arg_parser.add_argument("--color_closed", type=Color, default='2330080511', help="Color closed contours")
|
||||
self.arg_parser.add_argument("--highlight_selfintersecting", type=inkex.Boolean, default=True, help="Highlight self-intersecting contours")
|
||||
self.arg_parser.add_argument("--highlight_intersectionpoints", type=inkex.Boolean, default=True, help="Highlight self-intersecting points")
|
||||
self.arg_parser.add_argument("--color_selfintersecting", type=Color, default='1923076095', help="Color closed contours")
|
||||
self.arg_parser.add_argument("--color_intersectionpoints", type=Color, default='4239343359', help="Color closed contours")
|
||||
self.arg_parser.add_argument("--dotsize", type=int, default=10, help="Dot size (px) for self-intersecting points")
|
||||
self.arg_parser.add_argument("--remove_opened", type=inkex.Boolean, default=False, help="Remove opened contours")
|
||||
self.arg_parser.add_argument("--remove_closed", type=inkex.Boolean, default=False, help="Remove closed contours")
|
||||
self.arg_parser.add_argument("--remove_selfintersecting", type=inkex.Boolean, default=False, help="Remove self-intersecting contours")
|
||||
self.arg_parser.add_argument("--main_tabs")
|
||||
|
||||
#split combined contours into single contours if enabled - this is exactly the same as "Path -> Break Apart"
|
||||
def breakContours(self, node):
|
||||
replacedNodes = []
|
||||
if node.tag == inkex.addNS('path','svg'):
|
||||
parent = node.getparent()
|
||||
idx = parent.index(node)
|
||||
idSuffix = 0
|
||||
raw = Path(node.get("d")).to_arrays()
|
||||
subpaths, prev = [], 0
|
||||
for i in range(len(raw)): # Breaks compound paths into simple paths
|
||||
if raw[i][0] == 'M' and i != 0:
|
||||
subpaths.append(raw[prev:i])
|
||||
prev = i
|
||||
subpaths.append(raw[prev:])
|
||||
for subpath in subpaths:
|
||||
replacedNode = copy.copy(node)
|
||||
oldId = replacedNode.get('id')
|
||||
|
||||
replacedNode.set('d', CubicSuperPath(subpath))
|
||||
replacedNode.set('id', oldId + str(idSuffix).zfill(5))
|
||||
parent.insert(idx, replacedNode)
|
||||
idSuffix += 1
|
||||
replacedNodes.append(replacedNode)
|
||||
parent.remove(node)
|
||||
for child in node:
|
||||
self.breakContours(child)
|
||||
return replacedNodes
|
||||
|
||||
def scanContours(self, node):
|
||||
if node.tag == inkex.addNS('path','svg'):
|
||||
if self.options.removefillsetstroke:
|
||||
adjustStyle(self, node)
|
||||
|
||||
dot_group = node.getparent().add(inkex.Group())
|
||||
|
||||
raw = (Path(node.get('d')).to_arrays())
|
||||
subpaths, prev = [], 0
|
||||
for i in range(len(raw)): # Breaks compound paths into simple paths
|
||||
if raw[i][0] == 'M' and i != 0:
|
||||
subpaths.append(raw[prev:i])
|
||||
prev = i
|
||||
subpaths.append(raw[prev:])
|
||||
|
||||
for simpath in subpaths:
|
||||
if len(simpath) > 0:
|
||||
closed = False
|
||||
if simpath[-1][0] == 'Z':
|
||||
closed = True
|
||||
|
||||
if not closed:
|
||||
if self.options.highlight_opened:
|
||||
style = {'stroke-linejoin': 'miter', 'stroke-width': str(self.svg.unittouu(str(self.options.strokewidth) +"px")),
|
||||
'stroke-opacity': '1.0', 'fill-opacity': '1.0',
|
||||
'stroke': self.options.color_opened, 'stroke-linecap': 'butt', 'fill': 'none'}
|
||||
node.attrib['style'] = Style(style).to_str()
|
||||
if self.options.remove_opened:
|
||||
try:
|
||||
node.getparent().remove(node)
|
||||
except AttributeError:
|
||||
pass #we ignore that parent can be None
|
||||
if closed:
|
||||
if self.options.highlight_closed:
|
||||
style = {'stroke-linejoin': 'miter', 'stroke-width': str(self.svg.unittouu(str(self.options.strokewidth) +"px")),
|
||||
'stroke-opacity': '1.0', 'fill-opacity': '1.0',
|
||||
'stroke': self.options.color_closed, 'stroke-linecap': 'butt', 'fill': 'none'}
|
||||
node.attrib['style'] = Style(style).to_str()
|
||||
if self.options.remove_closed:
|
||||
try:
|
||||
node.getparent().remove(node)
|
||||
except AttributeError:
|
||||
pass #we ignore that parent can be None
|
||||
|
||||
for simpath in subpaths:
|
||||
closed = False
|
||||
if simpath[-1][0] == 'Z':
|
||||
closed = True
|
||||
if simpath[-2][0] == 'L': simpath[-1][1] = simpath[0][1]
|
||||
else: simpath.pop()
|
||||
nodes = []
|
||||
for i in range(len(simpath)):
|
||||
if simpath[i][0] == 'V': # vertical and horizontal lines only have one point in args, but 2 are required
|
||||
simpath[i][0]='L' #overwrite V with regular L command
|
||||
add=simpath[i-1][1][0] #read the X value from previous segment
|
||||
simpath[i][1].append(simpath[i][1][0]) #add the second (missing) argument by taking argument from previous segment
|
||||
simpath[i][1][0]=add #replace with recent X after Y was appended
|
||||
if simpath[i][0] == 'H': # vertical and horizontal lines only have one point in args, but 2 are required
|
||||
simpath[i][0]='L' #overwrite H with regular L command
|
||||
simpath[i][1].append(simpath[i-1][1][1]) #add the second (missing) argument by taking argument from previous segment
|
||||
nodes.append(simpath[i][1][-2:])
|
||||
|
||||
#if one of the options is activated we also check for self-intersecting
|
||||
if self.options.highlight_selfintersecting or self.options.highlight_intersectionpoints:
|
||||
try:
|
||||
if len(nodes) > 0: #try to find self-intersecting /overlapping polygons
|
||||
isect = fablabchemnitz_poly_point_isect.isect_polygon(nodes) #TODO: FIND OUT HOW TO HANDLE OPEN CONTOURS TO OMIT VIRTUALLY CROSSING LINES (WHICH DO NOT INTERSECT)
|
||||
if len(isect) > 0:
|
||||
#make dot markings at the intersection points
|
||||
if self.options.highlight_intersectionpoints:
|
||||
for xy in isect:
|
||||
#Add a dot label for this path element
|
||||
style = inkex.Style({'stroke': 'none', 'fill': self.options.color_intersectionpoints})
|
||||
circle = dot_group.add(Circle(cx=str(xy[0]), cy=str(xy[1]), r=str(self.svg.unittouu(str(self.options.dotsize/2) + "px"))))
|
||||
circle.style = style
|
||||
|
||||
if self.options.highlight_selfintersecting:
|
||||
style = {'stroke-linejoin': 'miter', 'stroke-width': str(self.svg.unittouu(str(self.options.strokewidth) +"px")),
|
||||
'stroke-opacity': '1.0', 'fill-opacity': '1.0',
|
||||
'stroke': self.options.color_selfintersecting, 'stroke-linecap': 'butt', 'fill': 'none'}
|
||||
node.attrib['style'] = Style(style).to_str()
|
||||
if self.options.remove_selfintersecting:
|
||||
if node.getparent() is not None: #might be already been deleted by previously checked settings so check again
|
||||
node.getparent().remove(node)
|
||||
except Exception as e: # we skip AssertionError
|
||||
#inkex.utils.debug("Accuracy Error. Try to reduce the precision of the paths using the extension called Rounder to cutoff unrequired decimals.")
|
||||
print(str(e))
|
||||
for child in node:
|
||||
self.scanContours(child)
|
||||
|
||||
def effect(self):
|
||||
if self.options.breakapart:
|
||||
if len(self.svg.selected) == 0:
|
||||
self.breakContours(self.document.getroot())
|
||||
self.scanContours(self.document.getroot())
|
||||
else:
|
||||
newContourSet = []
|
||||
for id, item in self.svg.selected.items():
|
||||
newContourSet.append(self.breakContours(item))
|
||||
for newContours in newContourSet:
|
||||
for newContour in newContours:
|
||||
self.scanContours(newContour)
|
||||
else:
|
||||
if len(self.svg.selected) == 0:
|
||||
self.scanContours(self.document.getroot())
|
||||
else:
|
||||
for id, item in self.svg.selected.items():
|
||||
self.scanContours(item)
|
||||
|
||||
if __name__ == '__main__':
|
||||
ContourScanner().run()
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user