fixed and new options for contour scanner

This commit is contained in:
Mario Voigt 2021-06-04 07:06:13 +02:00
parent 7c2de7d7e3
commit 7d863c7040
2 changed files with 128 additions and 70 deletions

View File

@ -6,7 +6,7 @@
<page name="tab_settings" gui-text="Settings">
<label appearance="header">General</label>
<param name="show_debug" type="bool" gui-text="Show debug infos">false</param>
<param name="break_apart" type="bool" gui-text="Break apart input" gui-description="Break apart input paths into sub paths. Modifies original paths">false</param>
<param name="break_apart" type="bool" gui-text="Break apart input" gui-description="Break apart input paths into sub paths. Modifies original paths: converts to absolute paths and might create additional new path elements.">false</param>
<param name="handle_groups" type="bool" gui-text="Handle groups" gui-description="Also looks for paths in groups which are in the current selection. Note: The generated results have a different structure (less granularity due to grouping) than directly selected paths. The colorization for non-intersected paths will be different too.">false</param>
<param name="path_types" type="optiongroup" appearance="combo" gui-text="Scanning selection" gui-description="Process open paths by other open paths, closed paths by other closed paths, or all paths by all other paths">
<option value="both">all:all paths</option>
@ -17,26 +17,32 @@
<param name="flatness" type="float" min="0.001" max="99999.000" precision="3" gui-text="Flatness (tolerance)" gui-description="Minimum flatness = 0.001. The smaller the value the more fine segments you will get (quantization). Large values might destroy the line continuity.">0.100</param>
<param name="decimals" type="int" min="0" max="16" gui-text="Decimals" gui-description="Accuracy for sub split lines / lines trimmed by shapely (default: 3)">3</param>
<param name="snap_tolerance" type="float" min="0.01" max="10.0" gui-text="Snap tolerance" gui-description="Snap tolerance for intersection points on paths (default: 0.1)">0.1</param>
<param name="draw_subsplit" type="bool" gui-text="Draw sub split lines (for debugging purposes)" gui-description="Draws polylines. Will be automatically enabled if any highlighting below is activated.">false</param>
<param name="draw_subsplit" type="bool" gui-text="Draw sub split lines (for debugging purposes)" gui-description="Draws polylines. Will be automatically enabled if any highlighting is activated.">false</param>
</page>
<page name="tab_scanning" gui-text="Scanning and Trimming">
<hbox>
<vbox>
<label appearance="header">Removing</label>
<param name="remove_polylines" type="bool" gui-text="Remove original polylines paths">false</param>
<param name="remove_beziers" type="bool" gui-text="Remove original beziers paths">false</param>
<param name="remove_opened" type="bool" gui-text="Remove original opened paths">false</param>
<param name="remove_closed" type="bool" gui-text="Remove original closed paths">false</param>
<label appearance="header">Removing Original Paths</label>
<param name="remove_relative" type="bool" gui-text="relative cmd">false</param>
<param name="remove_absolute" type="bool" gui-text="absolute cmd">false</param>
<param name="remove_mixed" type="bool" gui-text="mixed cmd" gui-description="combined relative and absolute">false</param>
<param name="remove_polylines" type="bool" gui-text="polylines">false</param>
<param name="remove_beziers" type="bool" gui-text="beziers">false</param>
<param name="remove_opened" type="bool" gui-text="opened">false</param>
<param name="remove_closed" type="bool" gui-text="closed">false</param>
<param name="remove_self_intersecting" type="bool" gui-text="Remove original self-intersecting paths">false</param>
<separator/>
<label appearance="header">Highlighting</label>
<param name="highlight_polylines" type="bool" gui-text="Highlight polyline paths">false</param>
<param name="highlight_beziers" type="bool" gui-text="Highlight bezier paths">false</param>
<param name="highlight_opened" type="bool" gui-text="Highlight opened paths">false</param>
<param name="highlight_closed" type="bool" gui-text="Highlight closed paths">false</param>
<param name="highlight_self_intersecting" type="bool" gui-text="Highlight self-intersecting paths" gui-description="Requires to draw sub split lines. Will override highlighting colors for open and closed paths (if those options are enabled)">false</param>
<param name="visualize_self_intersections" type="bool" gui-text="Visualize self-intersecting path points">false</param>
<param name="visualize_global_intersections" type="bool" gui-text="Visualize global intersection points" gui-description="Will also contain self-intersecting points!">false</param>
<param name="highlight_relative" type="bool" gui-text="relative cmd paths">false</param>
<param name="highlight_absolute" type="bool" gui-text="absolute cmd paths">false</param>
<param name="highlight_mixed" type="bool" gui-text="mixed cmd paths" gui-description="combined relative and absolute">false</param>
<param name="highlight_polylines" type="bool" gui-text="polyline paths">false</param>
<param name="highlight_beziers" type="bool" gui-text="bezier paths">false</param>
<param name="highlight_opened" type="bool" gui-text="opened paths">false</param>
<param name="highlight_closed" type="bool" gui-text="closed paths">false</param>
<param name="highlight_self_intersecting" type="bool" gui-text="self-intersecting paths" gui-description="Requires to draw sub split lines. Will override highlighting colors for open and closed paths (if those options are enabled)">false</param>
<param name="visualize_self_intersections" type="bool" gui-text="self-intersecting path points">false</param>
<param name="visualize_global_intersections" type="bool" gui-text="global intersection points" gui-description="Will also contain self-intersecting points!">false</param>
</vbox>
<separator/>
<vbox>
@ -59,21 +65,24 @@
<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>
<param name="color_polyline" type="color" appearance="colorbutton" gui-text="Color for polyline paths">4289703935</param>
<param name="color_bezier" type="color" appearance="colorbutton" gui-text="Color for bezier paths">258744063</param>
<param name="color_opened" type="color" appearance="colorbutton" gui-text="Color for opened paths">4012452351</param>
<param name="color_closed" type="color" appearance="colorbutton" gui-text="Color for closed paths">2330080511</param>
<param name="color_self_intersecting_paths" type="color" appearance="colorbutton" gui-text="Color for self-intersecting contours">2593756927</param>
<param name="color_subsplit" type="color" appearance="colorbutton" gui-text="Color for sub split lines">1630897151</param>
<param name="color_self_intersections" type="color" appearance="colorbutton" gui-text="Color for self-intersecting line points">6320383</param>
<param name="color_global_intersections" type="color" appearance="colorbutton" gui-text="Color for global intersection points">4239343359</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_absolute" type="color" appearance="colorbutton" gui-text="absolute cmd paths">1592519679</param>
<param name="color_mixed" type="color" appearance="colorbutton" gui-text="mixed cmd paths" gui-description="combined relative and absolute">3351636735</param>
<param name="color_polyline" type="color" appearance="colorbutton" gui-text="polyline paths">4289703935</param>
<param name="color_bezier" type="color" appearance="colorbutton" gui-text="bezier paths">258744063</param>
<param name="color_opened" type="color" appearance="colorbutton" gui-text="opened paths">4012452351</param>
<param name="color_closed" type="color" appearance="colorbutton" gui-text="closed paths">2330080511</param>
<param name="color_self_intersecting_paths" type="color" appearance="colorbutton" gui-text="self-intersecting contours">2593756927</param>
<param name="color_self_intersections" type="color" appearance="colorbutton" gui-text="self-intersecting line points">6320383</param>
<param name="color_global_intersections" type="color" appearance="colorbutton" gui-text="global intersection points">4239343359</param>
</vbox>
<separator/>
<vbox>
<label appearance="header">Trimming Colors</label>
<param name="color_trimmed" type="color" appearance="colorbutton" gui-text="Color for trimmed lines">3227634687</param>
<param name="color_combined" type="color" appearance="colorbutton" gui-text="Color for 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="Color for 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_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_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>
</vbox>
</hbox>
</page>

View File

@ -4,7 +4,14 @@
Extension for InkScape 1.0+
- WARNING: HORRIBLY SLOW CODE. PLEASE HELP TO MAKE IT USEFUL FOR LARGE AMOUNT OF PATHS
- add options:
- find line parts which are included in other lines and perform intersections/splittings (overlapping colinear lines)
- efficiently find overlapping colinear lines by checking their slope/gradient
- get all lines and sort by slope; kick out all slopes which are unique. We only want re-occuring slopes
- intersects() is equivalent to the OR-ing of contains(), crosses(), equals(), touches(), and within().
So there might be some cases where two lines intersect eachother without crossing,
in particular when one line contains another or when two lines are equals.
- crosses() returns True if the dimension of the intersection is less than the dimension of the one or the other.
So if two lines overlap, they won't be considered as "crossing". intersection() will return a geometric object.
- replace trimmed paths by bezier paths (calculating lengths and required t parameter)
- find more duplicates
- overlapping lines in sub splits
@ -25,6 +32,7 @@ Extension for InkScape 1.0+
each other line (line1.intersection(line2) using two for-loops) because this
kind of logic is really really slow for huge amount. You could use that only
for ~50-100 elements. So we use special algorihm (Bentley-Ottmann)
- Cool tool to visualize sweep line algorithm Bentley-Ottmann: https://bl.ocks.org/1wheel/464141fe9b940153e636
- things to look at more closely:
- https://gis.stackexchange.com/questions/203048/split-lines-at-points-using-shapely
@ -39,24 +47,9 @@ 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: 01.06.2021
Last patch: 04.06.2021
License: GNU GPL v3
efficiently find overlapping lines
- loope durch alle straihgt lines und berechne deren steigung -< einsortieren der linien nach steigung
- wenn die steigung noch nicht erfasst wurde, dann adde die line in eine collection aller in frage kommenden linien
- loope durch die vorfilterung und check für shapely
intersects() is equivalent to the OR-ing of contains(), crosses(), equals(), touches(), and within().
So there might be some cases where two lines intersect eachother without crossing,
in particular when one line contains another or when two lines are equals.
More specifically:
crosses() returns True [...] if the dimension of the intersection is less than the dimension of the one or the other.
So if two lines overlap, they won't be considered as "crossing".
intersection() will return a geometric object.
'''
import sys
@ -306,13 +299,12 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
def combine_nonintersects(self, allTrimGroups, apply_original_style):
'''
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:
- if the lines id contains intersectedVerb, we ignore it
- we combine all lines which do not contain intersectedVerb
- Then we loop through that combined structure and chain their segments which touch each other
Changes the style according to user setting.
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:
- if the lines id contains intersectedVerb, we ignore it
- we combine all lines which do not contain intersectedVerb
- Then we loop through that combined structure and chain their segments which touch each other
Changes the style according to user setting.
'''
nonTrimLineStyle = {'stroke': str(self.options.color_nonintersected), 'fill': 'none', 'stroke-width': self.options.strokewidth}
@ -412,12 +404,20 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
pars.add_argument("--decimals", type=int, default=3, help="Accuracy for sub split lines / lines trimmed by shapely")
pars.add_argument("--snap_tolerance", type=float, default=0.1, help="Snap tolerance for intersection points")
#Settings - Scanning
#Scanning - Removing
pars.add_argument("--remove_relative", type=inkex.Boolean, default=False, help="Remove original relative cmd paths")
pars.add_argument("--remove_absolute", type=inkex.Boolean, default=False, help="Remove original absolute cmd paths")
pars.add_argument("--remove_mixed", type=inkex.Boolean, default=False, help="Remove original mixed cmd (relative + absolute) paths")
pars.add_argument("--remove_polylines", type=inkex.Boolean, default=False, help="Remove original polyline paths")
pars.add_argument("--remove_beziers", type=inkex.Boolean, default=False, help="Remove original bezier paths")
pars.add_argument("--remove_opened", type=inkex.Boolean, default=False, help="Remove original opened paths")
pars.add_argument("--remove_closed", type=inkex.Boolean, default=False, help="Remove original closed paths")
pars.add_argument("--remove_self_intersecting", type=inkex.Boolean, default=False, help="Remove original self-intersecting paths")
#Scanning - Highlighting
pars.add_argument("--highlight_relative", type=inkex.Boolean, default=False, help="Highlight relative cmd paths")
pars.add_argument("--highlight_absolute", type=inkex.Boolean, default=False, help="Highlight absolute cmd paths")
pars.add_argument("--highlight_mixed", type=inkex.Boolean, default=False, help="Highlight mixed cmd (relative + absolute) paths")
pars.add_argument("--highlight_polylines", type=inkex.Boolean, default=False, help="Highlight polyline paths")
pars.add_argument("--highlight_beziers", type=inkex.Boolean, default=False, help="Highlight bezier paths")
pars.add_argument("--highlight_opened", type=inkex.Boolean, default=False, help="Highlight opened paths")
@ -442,12 +442,15 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
pars.add_argument("--apply_original_style", 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="Color for sub split lines")
pars.add_argument("--color_relative", type=Color, default='3419879935', help="Color for relative cmd paths")
pars.add_argument("--color_absolute", type=Color, default='1592519679', help="Color for absolute cmd paths")
pars.add_argument("--color_mixed", type=Color, default='3351636735', help="Color for mixed cmd (relative + absolute) paths")
pars.add_argument("--color_polyline", type=Color, default='4289703935', help="Color for polyline paths")
pars.add_argument("--color_bezier", type=Color, default='258744063', help="Color for bezier paths")
pars.add_argument("--color_opened", type=Color, default='4012452351', help="Color for opened paths")
pars.add_argument("--color_closed", type=Color, default='2330080511', help="Color for closed paths")
pars.add_argument("--color_self_intersecting_paths", type=Color, default='2593756927', help="Color for self-intersecting contours")
pars.add_argument("--color_subsplit", type=Color, default='1630897151', help="Color for sub split lines")
pars.add_argument("--color_self_intersections", type=Color, default='6320383', help="Color for self-intersecting line points")
pars.add_argument("--color_global_intersections", type=Color, default='4239343359', help="Color for global intersection points")
@ -458,11 +461,13 @@ 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 \
@ -470,13 +475,22 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
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!")
#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}
mixedPathStyle = {'stroke': str(so.color_mixed), 'fill': 'none', 'stroke-width': so.strokewidth}
polylinePathStyle = {'stroke': str(so.color_polyline), 'fill': 'none', 'stroke-width': so.strokewidth}
bezierPathStyle = {'stroke': str(so.color_bezier), 'fill': 'none', 'stroke-width': so.strokewidth}
openPathStyle = {'stroke': str(so.color_opened), 'fill': 'none', 'stroke-width': so.strokewidth}
@ -484,14 +498,7 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
selfIntersectingPathStyle = {'stroke': str(so.color_self_intersecting_paths), 'fill': 'none', 'stroke-width': so.strokewidth}
basicSubSplitLineStyle = {'stroke': str(so.color_subsplit), 'fill': 'none', 'stroke-width': so.strokewidth}
''' 1 //
get all paths which are within selection or in document and generate sub split lines
If flatten is enabled, we do the best approximation into a set of fine line segments.
To quickly find all intersections we use Bentley-Ottmann algorithm.
To use it we have to split all paths into subpaths and each sub path's will puzzled into single straight lines
Cool tool to visualize: https://bl.ocks.org/1wheel/464141fe9b940153e636
'''
#get all paths which are within selection or in document and generate sub split lines
pathElements = self.getPathElements()
allSubSplitLines = []
@ -510,7 +517,7 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
for pathElement in pathElements:
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
@ -537,6 +544,33 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
elif so.path_types == 'closed_paths' and isClosed is False: continue #skip this loop iteration
elif so.path_types == 'both': pass
#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):
isRelative = True
if any(relCmd.upper() in pathElement.attrib['d'] 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
if so.remove_relative is True and isRelative is True:
pathElement.delete()
continue #skip this loop iteration
if so.remove_mixed is True and isMixed is True:
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)
@ -609,8 +643,11 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
if pathElement.getparent() != self.svg.root:
line.path = line.path.transform(-pathElement.getparent().composed_transform())
line.style = basicSubSplitLineStyle
line.attrib['isBezier'] = str(isBezier)
line.attrib['isClosed'] = str(isClosed)
line.attrib['isRelative'] = str(isRelative)
line.attrib['isAbsolute'] = str(isAbsolute)
line.attrib['isMixed'] = str(isMixed)
line.attrib['isBezier'] = str(isBezier)
line.attrib['isClosed'] = str(isClosed)
subSplitTrimLineGroup.add(line)
subSplitLines.append([(x1, y1), (x2, y2)])
@ -630,6 +667,18 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
# subPathIsClosed = True
for subSplitLine in subSplitTrimLineGroup:
if subSplitLine.attrib['isRelative'] == 'True':
if so.highlight_relative is True:
subSplitLine.style = relativePathStyle
if subSplitLine.attrib['isAbsolute'] == 'True':
if so.highlight_absolute is True:
subSplitLine.style = absolutePathStyle
if subSplitLine.attrib['isMixed'] == 'True':
if so.highlight_mixed is True:
subSplitLine.style = mixedPathStyle
if subSplitLine.attrib['isBezier'] == 'True':
if so.highlight_beziers is True:
subSplitLine.style = bezierPathStyle
@ -643,9 +692,9 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
subSplitLine.style = closedPathStyle
else:
if so.highlight_opened is True:
subSplitLine.style = openPathStyle
subSplitLine.style = openPathStyle
#check for self intersections
#check for self intersections using Bentley-Ottmann algorithm.
selfIntersectionPoints = isect_segments(subSplitLines, validate=True)
if len(selfIntersectionPoints) > 0:
if so.show_debug is True:
@ -689,8 +738,8 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
if so.show_debug is True:
self.msg("sub split line count: {}".format(len(allSubSplitLines)))
''' 2 //
now we intersect the sub split lines to find the global intersection points (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!)
'''
try:
globalIntersectionPoints = MultiPoint(isect_segments(allSubSplitData[0], validate=True))
@ -700,9 +749,9 @@ class ContourScannerAndTrimmer(inkex.EffectExtension):
if so.visualize_global_intersections is True:
self.visualize_global_intersections(globalIntersectionPoints)
''' 3 //
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
'''
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:
allTrimGroups = [] #container to collect all trim groups for later on processing