diff --git a/extensions/fablabchemnitz/contour_scanner_and_trimmer/contour_scanner_and_trimmer.inx b/extensions/fablabchemnitz/contour_scanner_and_trimmer/contour_scanner_and_trimmer.inx
index d3cd23ea..02846a58 100644
--- a/extensions/fablabchemnitz/contour_scanner_and_trimmer/contour_scanner_and_trimmer.inx
+++ b/extensions/fablabchemnitz/contour_scanner_and_trimmer/contour_scanner_and_trimmer.inx
@@ -59,11 +59,6 @@
-
- 1.0
- 30
- false
- true
1630897151
3419879935
@@ -83,6 +78,13 @@
3227634687
1923076095
3045284607
+
+ 1.0
+ 30
+ false
+ true
+ true
+
diff --git a/extensions/fablabchemnitz/contour_scanner_and_trimmer/contour_scanner_and_trimmer.py b/extensions/fablabchemnitz/contour_scanner_and_trimmer/contour_scanner_and_trimmer.py
index 4914c17b..f4cb4574 100644
--- a/extensions/fablabchemnitz/contour_scanner_and_trimmer/contour_scanner_and_trimmer.py
+++ b/extensions/fablabchemnitz/contour_scanner_and_trimmer/contour_scanner_and_trimmer.py
@@ -19,6 +19,11 @@ Extension for InkScape 1.0+
- duplicates in split bezier
- ...
- refactor subSplitData stuff by some clean mapping/structure instead having a lot of useless arays
+ - maybe option: convert abs path to rel path
+ - maybe option: convert rel path to abs path
+ replacedelement.path = replacedelement.path.to_absolute().to_superpath().to_path()
+ - maybe option: break apart while keeping relative/absolute commands (more complex and not sure if we have a great advantage having this)
+
- important to notice
- this algorithm might be really slow. Reduce flattening quality to speed up
@@ -47,7 +52,7 @@ Extension for InkScape 1.0+
Author: Mario Voigt / FabLab Chemnitz
Mail: mario.voigt@stadtfabrikanten.org
Date: 09.08.2020 (extension originally called "Contour Scanner")
-Last patch: 04.06.2021
+Last patch: 05.06.2021
License: GNU GPL v3
'''
@@ -85,19 +90,24 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
parent = element.getparent()
idx = parent.index(element)
idSuffix = 0
- raw = str(element.path).split()
- subPaths, prev = [], 0
+ #raw = str(element.path).split()
+ raw = element.path.to_arrays()
+ subPaths = []
+ prev = 0
for i in range(len(raw)): # Breaks compound paths into simple paths
- if raw[i][0].upper() == 'M' and i != 0:
- subPaths.append(raw[prev:i])
+ #if raw[i][0].upper() == 'M' and i != 0:
+ if raw[i][0] == 'M' and i != 0:
+ subPath = raw[prev:i]
+ subPaths.append(Path(subPath))
prev = i
- subPaths.append(raw[prev:])
- for subpath in subPaths:
+ subPaths.append(Path(raw[prev:])) #finally add the last path
+
+ for subPath in subPaths:
replacedelement = copy.copy(element)
oldId = replacedelement.get('id')
- csp = CubicSuperPath(Path(" ".join(subpath)))
- if len(subpath) > 1 and csp[0][0] != csp[0][1]: #avoids pointy paths like M "31.4794 57.6024 Z"
- replacedelement.set('d', " ".join(subpath))
+ csp = CubicSuperPath(subPath)
+ if len(subPath) > 1 and csp[0][0] != csp[0][1]: #avoids pointy paths like M "31.4794 57.6024 Z"
+ replacedelement.path = subPath
replacedelement.set('id', oldId + str(idSuffix))
parent.insert(idx, replacedelement)
idSuffix += 1
@@ -146,6 +156,7 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
return pathElements
+
def findGroup(self, groupId):
''' check if a group with a given id exists or not. Returns None if not found, else returns the group element '''
groups = self.document.xpath('//svg:g', namespaces=inkex.NSS)
@@ -155,8 +166,6 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
return group
return None
- #function to refine the style of the lines
-
def adjustStyle(self, element):
''' Replace some style attributes of the given element '''
@@ -230,7 +239,7 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
def buildTrimLineGroups(self, allSubSplitData, subSplitIndex, globalIntersectionPoints,
- snap_tolerance, apply_original_style):
+ snap_tolerance, apply_style_to_trimmed):
''' make a group containing trimmed lines'''
#Check if we should skip or process the path anyway
@@ -279,7 +288,7 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
trimLine.path = [['M', [x[0],y[0]]], ['L', [x[1],y[1]]]]
if trimGroupParentTransform is not None:
trimLine.path = trimLine.path.transform(-trimGroupParentTransform)
- if apply_original_style is False:
+ if apply_style_to_trimmed is False:
trimLine.style = trimLineStyle
else:
trimLine.style = allSubSplitData[2][subSplitIndex]
@@ -306,7 +315,7 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
trimGroup.delete()
- def combine_nonintersects(self, allTrimGroups, apply_original_style):
+ def combine_nonintersects(self, allTrimGroups, apply_style_to_trimmed):
'''
combine and chain all non intersected sub split lines which were trimmed at intersection points before.
- At first we sort out all lines by their id:
@@ -349,7 +358,7 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
self.msg("trim group {} has {} combinable segments:".format(trimGroup.get('id'), len(newPathData)))
self.msg("{}".format(newPathData))
combinedPath.path = Path(newPathData)
- if apply_original_style is False:
+ if apply_style_to_trimmed is False:
combinedPath.style = trimNonIntersectedStyle
if totalIntersectionsAtPath == 0:
combinedPath.style = nonTrimLineStyle
@@ -448,7 +457,8 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
pars.add_argument("--dotsize_intersections", type=int, default=30, help="Dot size (px) for self-intersecting and global intersection points")
pars.add_argument("--removefillsetstroke", type=inkex.Boolean, default=False, help="Remove fill and define stroke for original paths")
pars.add_argument("--bezier_trimming", type=inkex.Boolean, default=False, help="If true we try to use the calculated t parameters from intersection points to receive splitted bezier curves")
- pars.add_argument("--apply_original_style", type=inkex.Boolean, default=True, help="Apply original path style to trimmed lines")
+ pars.add_argument("--apply_style_to_subsplits", type=inkex.Boolean, default=True, help="Apply highlighting styles to sub split lines.")
+ pars.add_argument("--apply_style_to_trimmed", type=inkex.Boolean, default=True, help="Apply original path style to trimmed lines")
#Style - Scanning Colors
pars.add_argument("--color_subsplit", type=Color, default='1630897151', help="sub split lines")
@@ -470,32 +480,12 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
def effect(self):
+
so = self.options
- #some dependent configuration for drawing modes
- if \
- so.highlight_relative is True or \
- so.highlight_absolute is True or \
- so.highlight_mixed is True or \
- so.highlight_beziers is True or \
- so.highlight_polylines is True or \
- so.highlight_opened is True or \
- so.highlight_closed is True or \
- so.highlight_self_intersecting is True:
- so.draw_subsplit = True
- if so.draw_subsplit is False:
- so.highlight_relative = False
- so.highlight_absolute = False
- so.highlight_mixed = False
- so.highlight_beziers = False
- so.highlight_polylines = False
- so.highlight_open = False
- so.highlight_closed = False
- so.highlight_self_intersecting = False
-
if so.break_apart is True and so.show_debug is True:
- self.msg("Warning: 'Break apart input' setting is enabled. Cannot check for relative, absolute or mixed paths!")
-
+ self.msg("Warning: 'Break apart input' setting is enabled. Cannot check accordingly for relative, absolute or mixed paths for breaked elements (they are always absolute)!")
+
#some constant stuff / styles
relativePathStyle = {'stroke': str(so.color_relative), 'fill': 'none', 'stroke-width': so.strokewidth}
absolutePathStyle = {'stroke': str(so.color_absolute), 'fill': 'none', 'stroke-width': so.strokewidth}
@@ -509,7 +499,7 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
#get all paths which are within selection or in document and generate sub split lines
pathElements = self.getPathElements()
-
+
allSubSplitLines = []
allSubSplitIds = []
allSubSplitStyles = []
@@ -526,47 +516,25 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
allSubSplitData.append(allSubSplitIsClosed) #column 5
for pathElement in pathElements:
+ originalPathId = pathElement.attrib["id"]
path = pathElement.path.transform(pathElement.composed_transform())
#path = pathElement.path
'''
- Some original path checkings for analysis/highlighting purposes
- Note: highlighting open/closed/self-intersecting contours does work best if you break apart
- combined paths before.
+ check for relative or absolute paths
'''
- isClosed = False
- path_arrays = path.to_arrays()
- if path_arrays[-1][0] == 'Z' or \
- (path_arrays[-1][0] == 'L' and path_arrays[0][1] == path_arrays[-1][1]) or \
- (path_arrays[-1][0] == 'C' and path_arrays[0][1] == [path_arrays[-1][1][-2], path_arrays[-1][1][-1]]) \
- : #if first is last point the path is also closed. The "Z" command is not required
- isClosed = True
-
- #Check if we should delete the path or not
- if so.remove_opened is True and isClosed is False:
- pathElement.delete()
- continue #skip this loop iteration
- if so.remove_closed is True and isClosed is True:
- pathElement.delete()
- continue #skip this loop iteration
-
- #check for relative or absolute paths. Does not work if break apart is enabled
isRelative = False
isAbsolute = False
isMixed = False
relCmds = ['m', 'l', 'h', 'v', 'c', 's', 'q', 't', 'a', 'z']
- if any(relCmd in pathElement.attrib['d'] for relCmd in relCmds):
+ if any(relCmd in str(path) for relCmd in relCmds):
isRelative = True
- if any(relCmd.upper() in pathElement.attrib['d'] for relCmd in relCmds):
+ if any(relCmd.upper() in str(path) for relCmd in relCmds):
isAbsolute = True
if isRelative is True and isAbsolute is True:
isMixed = True
isRelative = False
isAbsolute = False
- #self.msg("isRelative = {}".format(isRelative))
- #self.msg("isAbsolute = {}".format(isAbsolute))
- #self.msg("isMixed = {}".format(isMixed))
-
if so.remove_absolute is True and isAbsolute is True:
pathElement.delete()
continue #skip this loop iteration
@@ -577,17 +545,43 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
pathElement.delete()
continue #skip this loop iteration
- #adjust the style of original paths if desired. Has influence to the finally trimmed lines style results too!
- if so.removefillsetstroke:
- self.adjustStyle(pathElement)
-
- originalPathId = pathElement.attrib["id"]
+ '''
+ check for bezier or polyline paths
+ '''
+ isBezier = False
+ if 'c' in str(path) or 'C' in str(path):
+ isBezier = True
+ if so.show_debug is True:
+ self.msg("sub path in {} is bezier: {}".format(originalPathId, isBezier))
+ if so.remove_beziers is True and isBezier is True:
+ pathElement.delete()
+ continue #skip this loop iteration
+ if so.remove_polylines is True and isBezier is False:
+ pathElement.delete()
+ continue #skip this loop iteration
+
+ '''
+ check for closed or open paths
+ '''
+ isClosed = False
+ raw = path.to_arrays()
+ if raw[-1][0] == 'Z' or \
+ (raw[-1][0] == 'L' and raw[0][1] == raw[-1][1]) or \
+ (raw[-1][0] == 'C' and raw[0][1] == [raw[-1][1][-2], raw[-1][1][-1]]) \
+ : #if first is last point the path is also closed. The "Z" command is not required
+ isClosed = True
+ if so.remove_opened is True and isClosed is False:
+ pathElement.delete()
+ continue #skip this loop iteration
+ if so.remove_closed is True and isClosed is True:
+ pathElement.delete()
+ continue #skip this loop iteration
+
if so.draw_subsplit is True:
subSplitTrimLineGroup = pathElement.getparent().add(inkex.Group(id="{}-{}".format(idPrefix, pathElement.attrib["id"])))
#get all sub paths for the path of the element
- raw = path.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:
@@ -596,22 +590,7 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
subPaths.append(raw[prev:])
#now loop through all sub paths (and flatten if desired) to build up single lines
- for subPath in subPaths:
- #set to True if the sub path is a bezier, else we assume it only has straight lines inside
- isBezier = False
- if 'C' in str(subPath):
- isBezier = True
- if so.show_debug is True:
- self.msg("sub path in {} is bezier: {}".format(originalPathId, isBezier))
-
- deleteFlag = False
- if so.remove_beziers is True and isBezier is True:
- deleteFlag = True
- if so.remove_polylines is True and isBezier is False:
- deleteFlag = True
-
- #self.msg("sub path in {} = {}".format(element.get('id'), subPath))
- #flatten the subpath if wanted
+ for subPath in subPaths:
subPathData = CubicSuperPath(subPath)
#flatten bezier curves. If it was already a straight line do nothing! Otherwise we would split straight lines into a lot more straight lines
@@ -664,16 +643,7 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
subSplitOriginalPathIds.append(originalPathId) #some dirty flag we need
subSplitIsClosed.append(isClosed) #some dirty flag we need
- if deleteFlag is True:
- pathElement.delete()
- continue #skip the subPath loop
-
- if so.draw_subsplit is True:
- #check for open/closed again (at first we checked for the combined path. Now we can do for each sub path too!
- #subPathIsClosed = False
- #if subSplitLines[0][0] == subSplitLines[-1][1]:
- # subPathIsClosed = True
-
+ if so.draw_subsplit is True and so.apply_style_to_subsplits is True:
for subSplitLine in subSplitTrimLineGroup:
if subSplitLine.attrib['isRelative'] == 'True':
if so.highlight_relative is True:
@@ -741,6 +711,37 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
allSubSplitOriginalPathIds.extend(subSplitOriginalPathIds)
allSubSplitIsClosed.extend(subSplitIsClosed)
+ #adjust the style of original paths if desired. Has influence to the finally trimmed lines style results too!
+ if so.removefillsetstroke is True:
+ self.adjustStyle(pathElement)
+
+ #apply styles to original paths
+ if isRelative is True:
+ if so.highlight_relative is True:
+ pathElement.style = relativePathStyle
+
+ if isAbsolute is True:
+ if so.highlight_absolute is True:
+ pathElement.style = absolutePathStyle
+
+ if isMixed is True:
+ if so.highlight_mixed is True:
+ pathElement.style = mixedPathStyle
+
+ if isBezier is True:
+ if so.highlight_beziers is True:
+ pathElement.style = bezierPathStyle
+ else:
+ if so.highlight_polylines is True:
+ pathElement.style = polylinePathStyle
+
+ if isClosed is True:
+ if so.highlight_closed is True:
+ pathElement.style = closedPathStyle
+ else:
+ if so.highlight_opened is True:
+ pathElement.style = openPathStyle
+
if so.draw_subsplit is True:
if subSplitTrimLineGroup is not None: #might get deleted before so we need to check this first
subSplitTrimLineGroup = reversed(subSplitTrimLineGroup) #reverse the order to match the original path segment placing
@@ -751,23 +752,24 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
'''
now we intersect the sub split lines to find the global intersection points using Bentley-Ottmann algorithm (contains self-intersections too!)
'''
- try:
- globalIntersectionPoints = MultiPoint(isect_segments(allSubSplitData[0], validate=True))
- if so.show_debug is True:
- self.msg("global intersection points count: {}".format(len(globalIntersectionPoints)))
- if len(globalIntersectionPoints) > 0:
- if so.visualize_global_intersections is True:
- self.visualize_global_intersections(globalIntersectionPoints)
-
- '''
- now we trim the sub split lines at all calculated intersection points.
- We do this path by path to keep the logic between original paths, sub split lines and the final output
- '''
- if so.draw_trimmed is True:
+ if so.draw_trimmed is True:
+ try:
+ globalIntersectionPoints = MultiPoint(isect_segments(allSubSplitData[0], validate=True))
+ if so.show_debug is True:
+ self.msg("global intersection points count: {}".format(len(globalIntersectionPoints)))
+ if len(globalIntersectionPoints) > 0:
+ if so.visualize_global_intersections is True:
+ self.visualize_global_intersections(globalIntersectionPoints)
+
+ '''
+ now we trim the sub split lines at all calculated intersection points.
+ We do this path by path to keep the logic between original paths, sub split lines and the final output
+ '''
+
allTrimGroups = [] #container to collect all trim groups for later on processing
for subSplitIndex in range(len(allSubSplitData[0])):
trimGroup = self.buildTrimLineGroups(allSubSplitData, subSplitIndex,
- globalIntersectionPoints, so.snap_tolerance, so.apply_original_style)
+ globalIntersectionPoints, so.snap_tolerance, so.apply_style_to_trimmed)
if trimGroup is not None:
if trimGroup not in allTrimGroups:
allTrimGroups.append(trimGroup)
@@ -783,15 +785,15 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
if so.remove_duplicates is True: self.remove_duplicates(allTrimGroups, so.reverse_removal_order)
#glue together all non-intersected sub split lines to larger path structures again (cleaning up).
- if so.combine_nonintersects is True: self. combine_nonintersects(allTrimGroups, so.apply_original_style)
+ if so.combine_nonintersects is True: self. combine_nonintersects(allTrimGroups, so.apply_style_to_trimmed)
#clean original paths if selected. This option is explicitely independent from remove_open, remove_closed
if so.keep_original_after_trim is False:
for pathElement in pathElements:
pathElement.delete()
- except AssertionError as e:
- self.msg("Error calculating global intersections.\n\
+ except AssertionError as e:
+ self.msg("Error calculating global intersections.\n\
See https://github.com/ideasman42/isect_segments-bentley_ottmann.\n\n\
You can try to fix this by:\n\
- reduce or raise the 'decimals' setting (default is 3 but try to set to 6 for example)\n\
@@ -799,4 +801,4 @@ You can try to fix this by:\n\
return
if __name__ == '__main__':
- ContourScannerAndTrimmer().run()
+ ContourScannerAndTrimmer().run()
\ No newline at end of file