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 bdca404f..8ccb52a1 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
@@ -6,7 +6,7 @@
false
- false
+ false
false
@@ -17,26 +17,32 @@
0.100
3
0.1
- false
+ false
-
- false
- false
- false
- false
+
+ false
+ false
+ false
+ false
+ false
+ false
+ false
false
- false
- false
- false
- false
- false
- false
- false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
@@ -59,21 +65,24 @@
false
true
- 4289703935
- 258744063
- 4012452351
- 2330080511
- 2593756927
- 1630897151
- 6320383
- 4239343359
+ 1630897151
+ 3419879935
+ 1592519679
+ 3351636735
+ 4289703935
+ 258744063
+ 4012452351
+ 2330080511
+ 2593756927
+ 6320383
+ 4239343359
- 3227634687
- 1923076095
- 3045284607
+ 3227634687
+ 1923076095
+ 3045284607
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 180e5137..aee3b1f2 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
@@ -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