reverted some issue in contour scanner, cleaned some code

This commit is contained in:
Mario Voigt 2021-06-05 19:42:33 +02:00
parent eb23a4100f
commit 592f24aa9c
2 changed files with 126 additions and 122 deletions

View File

@ -59,11 +59,6 @@
<page name="tab_style" gui-text="Style"> <page name="tab_style" gui-text="Style">
<hbox> <hbox>
<vbox> <vbox>
<label appearance="header">General Style</label>
<param name="strokewidth" min="0.0" max="10000.0" gui-text="Stroke width (px)" gui-description="Applies For sub split lines and trimmed lines" type="float">1.0</param>
<param name="dotsize_intersections" type="int" min="0" max="10000" gui-text="Dot size (px)" gui-description="For self-intersecting and global intersection points">30</param>
<param name="removefillsetstroke" type="bool" gui-text="Remove fill and define stroke" gui-description="Modifies original path style">false</param>
<param name="apply_original_style" type="bool" gui-text="Original style for trimmed lines" gui-description="Apply original path style to trimmed lines.">true</param>
<label appearance="header">Scanning Colors</label> <label appearance="header">Scanning Colors</label>
<param name="color_subsplit" type="color" appearance="colorbutton" gui-text="sub split lines">1630897151</param> <param name="color_subsplit" type="color" appearance="colorbutton" gui-text="sub split lines">1630897151</param>
<param name="color_relative" type="color" appearance="colorbutton" gui-text="relative cmd paths">3419879935</param> <param name="color_relative" type="color" appearance="colorbutton" gui-text="relative cmd paths">3419879935</param>
@ -83,6 +78,13 @@
<param name="color_trimmed" type="color" appearance="colorbutton" gui-text="trimmed lines">3227634687</param> <param name="color_trimmed" type="color" appearance="colorbutton" gui-text="trimmed lines">3227634687</param>
<param name="color_combined" type="color" appearance="colorbutton" gui-text="non-intersected lines" gui-description="Colorize non-trimmed lines differently than the trimmed ones. Does not apply if 'Original style for trimmed lines' is enabled">1923076095</param> <param name="color_combined" type="color" appearance="colorbutton" gui-text="non-intersected lines" gui-description="Colorize non-trimmed lines differently than the trimmed ones. Does not apply if 'Original style for trimmed lines' is enabled">1923076095</param>
<param name="color_nonintersected" type="color" appearance="colorbutton" gui-text="non-intersected paths" gui-description="Colorize the complete path in case it does not contain any trim. Does not apply if 'Original style for trimmed lines' is enabled">3045284607</param> <param name="color_nonintersected" type="color" appearance="colorbutton" gui-text="non-intersected paths" gui-description="Colorize the complete path in case it does not contain any trim. Does not apply if 'Original style for trimmed lines' is enabled">3045284607</param>
<label appearance="header">General Style</label>
<param name="strokewidth" min="0.0" max="10000.0" precision="3" gui-text="Stroke width (px)" gui-description="Applies For sub split lines and trimmed lines" type="float">1.0</param>
<param name="dotsize_intersections" type="int" min="0" max="10000" gui-text="Dot size (px)" gui-description="For self-intersecting and global intersection points">30</param>
<param name="removefillsetstroke" type="bool" gui-text="Remove fill and define stroke" gui-description="Modifies original path style">false</param>
<param name="apply_style_to_subsplits" type="bool" gui-text="Highlighting styles for sub split lines" gui-description="Apply highlighting styles to sub split lines.">true</param>
<param name="apply_style_to_trimmed" type="bool" gui-text="Original style for trimmed lines" gui-description="Apply original path style to trimmed lines.">true</param>
</vbox> </vbox>
</hbox> </hbox>
</page> </page>

View File

@ -19,6 +19,11 @@ Extension for InkScape 1.0+
- duplicates in split bezier - duplicates in split bezier
- ... - ...
- refactor subSplitData stuff by some clean mapping/structure instead having a lot of useless arays - 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 - important to notice
- this algorithm might be really slow. Reduce flattening quality to speed up - 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 Author: Mario Voigt / FabLab Chemnitz
Mail: mario.voigt@stadtfabrikanten.org Mail: mario.voigt@stadtfabrikanten.org
Date: 09.08.2020 (extension originally called "Contour Scanner") Date: 09.08.2020 (extension originally called "Contour Scanner")
Last patch: 04.06.2021 Last patch: 05.06.2021
License: GNU GPL v3 License: GNU GPL v3
''' '''
@ -85,19 +90,24 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
parent = element.getparent() parent = element.getparent()
idx = parent.index(element) idx = parent.index(element)
idSuffix = 0 idSuffix = 0
raw = str(element.path).split() #raw = str(element.path).split()
subPaths, prev = [], 0 raw = element.path.to_arrays()
subPaths = []
prev = 0
for i in range(len(raw)): # Breaks compound paths into simple paths for i in range(len(raw)): # Breaks compound paths into simple paths
if raw[i][0].upper() == 'M' and i != 0: #if raw[i][0].upper() == 'M' and i != 0:
subPaths.append(raw[prev:i]) if raw[i][0] == 'M' and i != 0:
subPath = raw[prev:i]
subPaths.append(Path(subPath))
prev = i prev = i
subPaths.append(raw[prev:]) subPaths.append(Path(raw[prev:])) #finally add the last path
for subpath in subPaths:
for subPath in subPaths:
replacedelement = copy.copy(element) replacedelement = copy.copy(element)
oldId = replacedelement.get('id') oldId = replacedelement.get('id')
csp = CubicSuperPath(Path(" ".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" 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)) replacedelement.path = subPath
replacedelement.set('id', oldId + str(idSuffix)) replacedelement.set('id', oldId + str(idSuffix))
parent.insert(idx, replacedelement) parent.insert(idx, replacedelement)
idSuffix += 1 idSuffix += 1
@ -146,6 +156,7 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
return pathElements return pathElements
def findGroup(self, groupId): 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 ''' ''' 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) groups = self.document.xpath('//svg:g', namespaces=inkex.NSS)
@ -155,8 +166,6 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
return group return group
return None return None
#function to refine the style of the lines
def adjustStyle(self, element): def adjustStyle(self, element):
''' Replace some style attributes of the given element ''' ''' Replace some style attributes of the given element '''
@ -230,7 +239,7 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
def buildTrimLineGroups(self, allSubSplitData, subSplitIndex, globalIntersectionPoints, def buildTrimLineGroups(self, allSubSplitData, subSplitIndex, globalIntersectionPoints,
snap_tolerance, apply_original_style): snap_tolerance, apply_style_to_trimmed):
''' make a group containing trimmed lines''' ''' make a group containing trimmed lines'''
#Check if we should skip or process the path anyway #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]]]] trimLine.path = [['M', [x[0],y[0]]], ['L', [x[1],y[1]]]]
if trimGroupParentTransform is not None: if trimGroupParentTransform is not None:
trimLine.path = trimLine.path.transform(-trimGroupParentTransform) trimLine.path = trimLine.path.transform(-trimGroupParentTransform)
if apply_original_style is False: if apply_style_to_trimmed is False:
trimLine.style = trimLineStyle trimLine.style = trimLineStyle
else: else:
trimLine.style = allSubSplitData[2][subSplitIndex] trimLine.style = allSubSplitData[2][subSplitIndex]
@ -306,7 +315,7 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
trimGroup.delete() 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. 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: - 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("trim group {} has {} combinable segments:".format(trimGroup.get('id'), len(newPathData)))
self.msg("{}".format(newPathData)) self.msg("{}".format(newPathData))
combinedPath.path = Path(newPathData) combinedPath.path = Path(newPathData)
if apply_original_style is False: if apply_style_to_trimmed is False:
combinedPath.style = trimNonIntersectedStyle combinedPath.style = trimNonIntersectedStyle
if totalIntersectionsAtPath == 0: if totalIntersectionsAtPath == 0:
combinedPath.style = nonTrimLineStyle 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("--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("--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("--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 #Style - Scanning Colors
pars.add_argument("--color_subsplit", type=Color, default='1630897151', help="sub split lines") pars.add_argument("--color_subsplit", type=Color, default='1630897151', help="sub split lines")
@ -470,31 +480,11 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
def effect(self): def effect(self):
so = self.options 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: 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 #some constant stuff / styles
relativePathStyle = {'stroke': str(so.color_relative), 'fill': 'none', 'stroke-width': so.strokewidth} relativePathStyle = {'stroke': str(so.color_relative), 'fill': 'none', 'stroke-width': so.strokewidth}
@ -526,47 +516,25 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
allSubSplitData.append(allSubSplitIsClosed) #column 5 allSubSplitData.append(allSubSplitIsClosed) #column 5
for pathElement in pathElements: for pathElement in pathElements:
originalPathId = pathElement.attrib["id"]
path = pathElement.path.transform(pathElement.composed_transform()) path = pathElement.path.transform(pathElement.composed_transform())
#path = pathElement.path #path = pathElement.path
''' '''
Some original path checkings for analysis/highlighting purposes check for relative or absolute paths
Note: highlighting open/closed/self-intersecting contours does work best if you break apart
combined paths before.
''' '''
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 isRelative = False
isAbsolute = False isAbsolute = False
isMixed = False isMixed = False
relCmds = ['m', 'l', 'h', 'v', 'c', 's', 'q', 't', 'a', 'z'] 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 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 isAbsolute = True
if isRelative is True and isAbsolute is True: if isRelative is True and isAbsolute is True:
isMixed = True isMixed = True
isRelative = False isRelative = False
isAbsolute = 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: if so.remove_absolute is True and isAbsolute is True:
pathElement.delete() pathElement.delete()
continue #skip this loop iteration continue #skip this loop iteration
@ -577,17 +545,43 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
pathElement.delete() pathElement.delete()
continue #skip this loop iteration 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: check for bezier or polyline paths
self.adjustStyle(pathElement) '''
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
originalPathId = pathElement.attrib["id"]
'''
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: if so.draw_subsplit is True:
subSplitTrimLineGroup = pathElement.getparent().add(inkex.Group(id="{}-{}".format(idPrefix, pathElement.attrib["id"]))) subSplitTrimLineGroup = pathElement.getparent().add(inkex.Group(id="{}-{}".format(idPrefix, pathElement.attrib["id"])))
#get all sub paths for the path of the element #get all sub paths for the path of the element
raw = path.to_arrays()
subPaths, prev = [], 0 subPaths, prev = [], 0
for i in range(len(raw)): # Breaks compound paths into simple paths for i in range(len(raw)): # Breaks compound paths into simple paths
if raw[i][0] == 'M' and i != 0: if raw[i][0] == 'M' and i != 0:
@ -597,21 +591,6 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
#now loop through all sub paths (and flatten if desired) to build up single lines #now loop through all sub paths (and flatten if desired) to build up single lines
for subPath in subPaths: 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
subPathData = CubicSuperPath(subPath) 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 #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 subSplitOriginalPathIds.append(originalPathId) #some dirty flag we need
subSplitIsClosed.append(isClosed) #some dirty flag we need subSplitIsClosed.append(isClosed) #some dirty flag we need
if deleteFlag is True: if so.draw_subsplit is True and so.apply_style_to_subsplits 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 <maybe> combined path. Now we can do for each sub path too!
#subPathIsClosed = False
#if subSplitLines[0][0] == subSplitLines[-1][1]:
# subPathIsClosed = True
for subSplitLine in subSplitTrimLineGroup: for subSplitLine in subSplitTrimLineGroup:
if subSplitLine.attrib['isRelative'] == 'True': if subSplitLine.attrib['isRelative'] == 'True':
if so.highlight_relative is True: if so.highlight_relative is True:
@ -741,6 +711,37 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
allSubSplitOriginalPathIds.extend(subSplitOriginalPathIds) allSubSplitOriginalPathIds.extend(subSplitOriginalPathIds)
allSubSplitIsClosed.extend(subSplitIsClosed) 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 so.draw_subsplit is True:
if subSplitTrimLineGroup is not None: #might get deleted before so we need to check this first 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 subSplitTrimLineGroup = reversed(subSplitTrimLineGroup) #reverse the order to match the original path segment placing
@ -751,6 +752,7 @@ 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!) now we intersect the sub split lines to find the global intersection points using Bentley-Ottmann algorithm (contains self-intersections too!)
''' '''
if so.draw_trimmed is True:
try: try:
globalIntersectionPoints = MultiPoint(isect_segments(allSubSplitData[0], validate=True)) globalIntersectionPoints = MultiPoint(isect_segments(allSubSplitData[0], validate=True))
if so.show_debug is True: if so.show_debug is True:
@ -763,11 +765,11 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
now we trim the sub split lines at all calculated intersection points. 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 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:
allTrimGroups = [] #container to collect all trim groups for later on processing allTrimGroups = [] #container to collect all trim groups for later on processing
for subSplitIndex in range(len(allSubSplitData[0])): for subSplitIndex in range(len(allSubSplitData[0])):
trimGroup = self.buildTrimLineGroups(allSubSplitData, subSplitIndex, 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 is not None:
if trimGroup not in allTrimGroups: if trimGroup not in allTrimGroups:
allTrimGroups.append(trimGroup) allTrimGroups.append(trimGroup)
@ -783,7 +785,7 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
if so.remove_duplicates is True: self.remove_duplicates(allTrimGroups, so.reverse_removal_order) 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). #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 #clean original paths if selected. This option is explicitely independent from remove_open, remove_closed
if so.keep_original_after_trim is False: if so.keep_original_after_trim is False: