diff --git a/extensions/fablabchemnitz/contourscanner/contour_scanner.inx b/extensions/fablabchemnitz/contourscanner/contour_scanner.inx
deleted file mode 100644
index 616962f8..00000000
--- a/extensions/fablabchemnitz/contourscanner/contour_scanner.inx
+++ /dev/null
@@ -1,79 +0,0 @@
-
-
- Contour Scanner
- fablabchemnitz.de.contour_scanner
-
-
-
-
-
- true
- 4012452351
- true
- 2330080511
- true
- 1923076095
- true
- 4239343359
- 10
- true
- true
-
-
-
-
- false
- false
- false
-
-
- false
- false
- 1.0
- false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ../000_about_fablabchemnitz.svg
-
-
-
- path
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/contourscanner/contour_scanner.py b/extensions/fablabchemnitz/contourscanner/contour_scanner.py
deleted file mode 100644
index f39c9793..00000000
--- a/extensions/fablabchemnitz/contourscanner/contour_scanner.py
+++ /dev/null
@@ -1,295 +0,0 @@
-#!/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.05.2021
-License: GNU GPL v3
-
-ToDo:
-- add option to replace last segment of closed paths by 'Z' or 'z' in case the first and last segment touch each other (coincident point)
-"""
-
-import sys
-from math import *
-from lxml import etree
-import poly_point_isect
-import copy
-import inkex
-from inkex.paths import Path, CubicSuperPath
-from inkex import Style, Color, Circle
-
-class ContourScanner(inkex.EffectExtension):
-
- def add_arguments(self, pars):
- pars.add_argument("--main_tabs")
- pars.add_argument("--breakapart", type=inkex.Boolean, default=False, help="Break apart selection into single contours")
- pars.add_argument("--removefillsetstroke", type=inkex.Boolean, default=False, help="Remove fill and define stroke")
- pars.add_argument("--strokewidth", type=float, default=1.0, help="Stroke width (px)")
- pars.add_argument("--highlight_opened", type=inkex.Boolean, default=True, help="Highlight opened contours")
- pars.add_argument("--color_opened", type=Color, default='4012452351', help="Color opened contours")
- pars.add_argument("--highlight_closed", type=inkex.Boolean, default=True, help="Highlight closed contours")
- pars.add_argument("--color_closed", type=Color, default='2330080511', help="Color closed contours")
- pars.add_argument("--highlight_selfintersecting", type=inkex.Boolean, default=True, help="Highlight self-intersecting contours")
- pars.add_argument("--highlight_intersectionpoints", type=inkex.Boolean, default=True, help="Highlight self-intersecting points")
- pars.add_argument("--color_selfintersecting", type=Color, default='1923076095', help="Color closed contours")
- pars.add_argument("--color_intersectionpoints", type=Color, default='4239343359', help="self-intersecting points")
- pars.add_argument("--addlines", type=inkex.Boolean, default=True, help="Add closing lines for self-crossing contours")
- pars.add_argument("--polypaths", type=inkex.Boolean, default=True, help="Add polypath outline for self-crossing contours")
- pars.add_argument("--dotsize", type=int, default=10, help="Dot size (px) for self-intersecting points")
- pars.add_argument("--remove_opened", type=inkex.Boolean, default=False, help="Remove opened contours")
- pars.add_argument("--remove_closed", type=inkex.Boolean, default=False, help="Remove closed contours")
- pars.add_argument("--remove_selfintersecting", type=inkex.Boolean, default=False, help="Remove self-intersecting contours")
- pars.add_argument("--show_debug", type=inkex.Boolean, default=False, help="Show debug info")
-
- #function to refine the style of the lines
- 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')
- else:
- node.set('style', 'stroke:#000000;stroke-opacity:1.0')
-
-
- #get polyline from path
- def getPolyline(self, node):
- if node.tag == inkex.addNS('path','svg'):
- polypath = []
- i = 0
- for x, y in node.path.end_points:
- if i == 0:
- polypath.append(['M', [x,y]])
- else:
- polypath.append(['L', [x,y]])
- if i == 1 and polypath[len(polypath)-2][1] == polypath[len(polypath)-1][1]:
- polypath.pop(len(polypath)-1) #special handling for the second point after M command
- elif polypath[len(polypath)-2] == polypath[len(polypath)-1]: #get the previous point
- polypath.pop(len(polypath)-1)
- i += 1
- return Path(polypath)
-
-
- #split combined contours into single contours if enabled - this is exactly the same as "Path -> Break Apart"
- replacedNodes = []
- def breakContours(self, node): #this does the same as "CTRL + SHIFT + K"
- if node.tag == inkex.addNS('path','svg'):
- parent = node.getparent()
- idx = parent.index(node)
- idSuffix = 0
- #raw = Path(node.get("d")).to_arrays()
- #raw = node.path.transform(node.composed_transform()).to_superpath()
- raw = node.path.transform(node.composed_transform()).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
- self.replacedNodes.append(replacedNode)
- node.delete()
- for child in node:
- self.breakContours(child)
-
- def scanContours(self, node):
- if node.tag == inkex.addNS('path','svg'):
- if self.options.removefillsetstroke:
- self.adjustStyle(node)
-
- intersectionGroup = node.getparent().add(inkex.Group())
-
- #raw = (Path(node.get('d')).to_arrays())
- raw = node.path.transform(node.composed_transform()).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:
-
- closed = False
- if simpath[-1][0] == 'Z' or \
- (simpath[-1][0] == 'L' and simpath[0][1] == simpath[-1][1]) or \
- (simpath[-1][0] == 'C' and simpath[0][1] == [simpath[-1][1][-2], simpath[-1][1][-1]]) : #if first is last point the path is also closed. The "Z" command is not required
- closed = True
-
- if simpath[-2][0] == 'L': simpath[-1][1] = simpath[0][1]
- else: simpath.pop()
- points = []
- 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
- points.append(simpath[i][1][-2:])
- if points[0] == points[-1]: #if first is last point the path is also closed. The "Z" command is not required
- closed = True
-
- if closed == False:
- 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.delete()
- except AttributeError:
- pass #we ignore that parent can be None
- if closed == True:
- 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.delete()
- except AttributeError:
- pass #we ignore that parent can be None
-
- #if one of the options is activated we also check for self-intersecting
- if self.options.highlight_selfintersecting or self.options.highlight_intersectionpoints:
-
- #Style definitions
- closingLineStyle = 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_intersectionpoints, 'stroke-linecap': 'butt', 'fill': 'none'}).to_str()
-
- intersectionPointStyle = Style({'stroke': 'none', 'fill': self.options.color_intersectionpoints}).to_str()
-
- intersectionStyle = 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'}).to_str()
-
- try:
- if len(points) > 2: #try to find self-intersecting /overlapping polygons. We need at least 3 points to detect for intersections (only possible if first points matched last point)
- isect = poly_point_isect.isect_polygon(points, validate=True)
- if len(isect) > 0:
- if closed == False and self.options.addlines == True: #if contour is open and we found intersection points those points might be not relevant
- closingLine = intersectionGroup.add(inkex.PathElement())
- closingLine.set('id', self.svg.get_unique_id('closingline-'))
- closingLine.path = [
- ['M', [points[0][0],points[0][1]]],
- ['L', [points[-1][0],points[-1][1]]],
- ['Z', []]
- ]
- closingLine.attrib['style'] = closingLineStyle
-
- #draw polylines if option is enabled
- if self.options.polypaths == True:
- polyNode = intersectionGroup.add(inkex.PathElement())
- polyNode.set('id', self.svg.get_unique_id('polypath-'))
- polyNode.set('d', str(self.getPolyline(node)))
- polyNode.attrib['style'] = closingLineStyle
-
- #make dot markings at the intersection points
- if self.options.highlight_intersectionpoints:
- for xy in isect:
- #Add a dot label for this path element
- intersectionPoint = intersectionGroup.add(Circle(cx=str(xy[0]), cy=str(xy[1]), r=str(self.svg.unittouu(str(self.options.dotsize/2) + "px"))))
- intersectionPoint.set('id', self.svg.get_unique_id('intersectionpoint-'))
- intersectionPoint.style = intersectionPointStyle
-
- if self.options.highlight_selfintersecting:
- node.attrib['style'] = intersectionStyle
- if self.options.remove_selfintersecting:
- if node.getparent() is not None: #might be already been deleted by previously checked settings so check again
- node.delete()
-
- #draw intersections segment lines - useless at the moment. We could use this information to cut the original polyline to get a new curve path which included the intersection points
- #isectSegs = poly_point_isect.isect_polygon_include_segments(points)
- #for seg in isectSegs:
- # isectSegsPath = []
- # isecX = seg[0][0] #the intersection point - X
- # isecY = seg[0][1] #the intersection point - Y
- # isecSeg1X = seg[1][0][0][0] #the first intersection point segment - X
- # isecSeg1Y = seg[1][0][0][1] #the first intersection point segment - Y
- # isecSeg2X = seg[1][1][0][0] #the second intersection point segment - X
- # isecSeg2Y = seg[1][1][0][1] #the second intersection point segment - Y
- # isectSegsPath.append(['L', [isecSeg2X, isecSeg2Y]])
- # isectSegsPath.append(['L', [isecX, isecY]])
- # isectSegsPath.append(['L', [isecSeg1X, isecSeg1Y]])
- # #fix the really first point. Has to be an 'M' command instead of 'L'
- # isectSegsPath[0][0] = 'M'
- # polySegsNode = intersectionGroup.add(inkex.PathElement())
- # polySegsNode.set('id', self.svg.get_unique_id('intersectsegments-'))
- # polySegsNode.set('d', str(Path(isectSegsPath)))
- # polySegsNode.attrib['style'] = closingLineStyle
-
- except AssertionError as e: # we skip AssertionError
- if self.options.show_debug is True:
- inkex.utils.debug("AssertionError at " + node.get('id'))
- continue
- except IndexError as i: # we skip IndexError
- if self.options.show_debug is True:
- inkex.utils.debug("IndexError at " + node.get('id'))
- continue
- #if the intersectionGroup was created but nothing attached we delete it again to prevent messing the SVG XML tree
- if len(intersectionGroup.getchildren()) == 0:
- intersectionGroupParent = intersectionGroup.getparent()
- if intersectionGroupParent is not None:
- intersectionGroup.delete()
- #put the node into the intersectionGroup to bundle the path with it's error markers. If removal is selected we need to avoid intersectionGroup.insert(), because it will break the removal
- elif self.options.remove_selfintersecting == False:
- intersectionGroup.insert(0, node)
- children = node.getchildren()
- if children is not None:
- for child in children:
- self.scanContours(child)
-
- def effect(self):
- if self.options.breakapart is True:
- if len(self.svg.selected) == 0:
- self.breakContours(self.document.getroot())
- self.scanContours(self.document.getroot())
- else:
- newContourSet = []
- for element in self.svg.selected.values():
- self.breakContours(element)
- for newContours in self.replacedNodes:
- self.scanContours(newContours)
- else:
- if len(self.svg.selected) == 0:
- self.scanContours(self.document.getroot())
- else:
- for element in self.svg.selected.values():
- self.scanContours(element)
-
-if __name__ == '__main__':
- ContourScanner().run()
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/contourscannerandtrimmer/contour_scanner_and_trimmer.inx b/extensions/fablabchemnitz/contourscannerandtrimmer/contour_scanner_and_trimmer.inx
new file mode 100644
index 00000000..7d644c5b
--- /dev/null
+++ b/extensions/fablabchemnitz/contourscannerandtrimmer/contour_scanner_and_trimmer.inx
@@ -0,0 +1,125 @@
+
+
+ Contour Scanner And Trimmer
+ fablabchemnitz.de.contour_scanner_and_trimmer
+
+
+
+ false
+ false
+ false
+ true
+ 0.100
+ 3
+ 0.1
+ false
+
+
+
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+ false
+ true
+ true
+ false
+ true
+ false
+
+
+
+
+
+
+
+ 1.0
+ 30
+ false
+ true
+
+ 4012452351
+ 2330080511
+ 2593756927
+ 1630897151
+ 6320383
+ 4239343359
+
+
+
+
+ 3227634687
+ 1923076095
+ 3045284607
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ../000_about_fablabchemnitz.svg
+
+
+
+ all
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/contourscannerandtrimmer/contour_scanner_and_trimmer.py b/extensions/fablabchemnitz/contourscannerandtrimmer/contour_scanner_and_trimmer.py
new file mode 100644
index 00000000..6003c436
--- /dev/null
+++ b/extensions/fablabchemnitz/contourscannerandtrimmer/contour_scanner_and_trimmer.py
@@ -0,0 +1,701 @@
+#!/usr/bin/env python3
+
+'''
+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)
+ - replace trimmed paths by bezier paths (calculating lengths and required t parameter)
+ - find more duplicates
+ - overlapping lines in sub splits
+ - overlapping in original selection
+ - duplicates in original selection
+ - duplicates in split bezier
+ - ...
+
+- important to notice
+ - this algorithm might be really slow. Reduce flattening quality to speed up
+ - the code quality is horrible. We need a lot of asserts and functions to structure that stuff
+ - try to adjust snap tolerance and flatness in case of errors, like
+ poly_point_isect.py: "KeyError: 'Event(0x21412ce81c0, s0=(47.16, 179.1),
+ s1=(47.17, 178.21), p=(47.16, 179.1), type=2, slope=-88.9999999999531)'"
+ - this extension does not check for strange paths. Please ensure that your path 'd'
+ data is valid (no pointy paths, no duplicates, etc.)
+ - we do not use shapely to look for intersections by cutting each line against
+ 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)
+
+- things to look at more closely:
+ - https://gis.stackexchange.com/questions/203048/split-lines-at-points-using-shapely
+ - https://stackoverflow.com/questions/34754777/shapely-split-linestrings-at-intersections-with-other-linestrings
+ - There are floating point precision errors when finding a point on a line. Use the distance with an appropriate threshold instead.
+ - line.within(point) # False
+ - line.distance(point) # 7.765244949417793e-11
+ - line.distance(point) < 1e-8 # True
+ - https://bezier.readthedocs.io/en/stable/python/reference/bezier.hazmat.clipping.html / https://github.com/dhermes/bezier
+ - De Casteljau Algorithm
+
+Author: Mario Voigt / FabLab Chemnitz
+Mail: mario.voigt@stadtfabrikanten.org
+Date: 09.08.2020 (extension originally called "Contour Scanner")
+Last patch: 01.06.2021
+License: GNU GPL v3
+'''
+
+import sys
+import os
+import copy
+from lxml import etree
+import poly_point_isect
+from poly_point_isect import isect_segments
+import inkex
+from inkex import transforms, bezier, PathElement, Color, Circle
+from inkex.bezier import csplength
+from inkex.paths import Path, CubicSuperPath
+from shapely.geometry import LineString, Point, MultiPoint
+from shapely.ops import snap, split
+from shapely import speedups
+if speedups.available:
+ speedups.enable()
+
+
+idPrefix = "subsplit"
+intersectedVerb = "-intersected-"
+
+class ContourScannerAndTrimmer(inkex.EffectExtension):
+
+ def breakContours(self, element, breakelements = None):
+ ''' this does the same as "CTRL + SHIFT + K" '''
+ if breakelements == None:
+ breakelements = []
+ if element.tag == inkex.addNS('path','svg'):
+ parent = element.getparent()
+ idx = parent.index(element)
+ idSuffix = 0
+ raw = element.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:
+ subPaths.append(raw[prev:i])
+ prev = i
+ subPaths.append(raw[prev:])
+ for subpath in subPaths:
+ replacedelement = copy.copy(element)
+ oldId = replacedelement.get('id')
+ replacedelement.set('d', CubicSuperPath(subpath))
+ replacedelement.set('id', oldId + str(idSuffix).zfill(5))
+ parent.insert(idx, replacedelement)
+ idSuffix += 1
+ breakelements.append(replacedelement)
+ parent.remove(element)
+ for child in element.getchildren():
+ self.breakContours(child, breakelements)
+ return breakelements
+
+
+ def getChildPaths(self, element, elements = None):
+ ''' a function to get child paths from elements (used by "handling groups" option) '''
+ if elements == None:
+ elements = []
+ if element.tag == inkex.addNS('path','svg'):
+ elements.append(element)
+ for child in element.getchildren():
+ self.getChildPaths(child, elements)
+ return elements
+
+
+ def getPathElements(self):
+ ''' get all path elements, either from selection or from whole document. Uses options '''
+ pathElements = []
+ if len(self.svg.selected) == 0: #if nothing selected we search for the complete document
+ pathElements = self.document.xpath('//svg:path', namespaces=inkex.NSS)
+ else: # or get selected paths (and children) and convert them to shapely LineString objects
+ if self.options.handle_groups is False:
+ pathElements = list(self.svg.selection.filter(PathElement).values())
+ else:
+ for element in self.svg.selection.values():
+ pathElements = self.getChildPaths(element, pathElements)
+
+ if len(pathElements) == 0:
+ self.msg('Selection appears to be empty or does not contain any valid svg:path nodes. Try to cast your objects to paths using CTRL + SHIFT + C or strokes to paths using CTRL + ALT + C')
+ return
+ return pathElements
+
+ if self.options.break_apart is True:
+ breakApartElements = None
+ for pathElement in pathElements:
+ breakApartElements = self.breakContours(pathElement, breakApartElements)
+ pathElements = breakApartElements
+
+ if self.options.show_debug is True:
+ self.msg("total processing paths count: {}".format(len(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)
+ for group in groups:
+ #self.msg(str(layer.get('inkscape:label')) + " == " + layerName)
+ if group.get('id') == groupId:
+ return group
+ return None
+
+ #function to refine the style of the lines
+
+
+ def adjustStyle(self, element):
+ ''' Replace some style attributes of the given element '''
+ if element.attrib.has_key('style'):
+ style = element.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'
+ element.set('style', ';'.join(declarations) + ';stroke:#000000;stroke-opacity:1.0')
+ else:
+ element.set('style', 'stroke:#000000;stroke-opacity:1.0')
+
+
+ def lineFromSegments(self, segs, i, decimals):
+ '''builds a straight line for the segment i and the next segment i+2. Returns both point XY coordinates'''
+ pseudoPath = Path(segs[i:i+2]).to_arrays()
+ x1 = round(pseudoPath[0][1][-2], decimals)
+ y1 = round(pseudoPath[0][1][-1], decimals)
+ if pseudoPath[1][0] == 'Z': #some crappy code when the path is closed
+ pseudoPathEnd = Path(segs[0:2]).to_arrays()
+ x2 = round(pseudoPathEnd[0][1][-2], decimals)
+ y2 = round(pseudoPathEnd[0][1][-1], decimals)
+ else:
+ x2 = round(pseudoPath[1][1][-2], decimals)
+ y2 = round(pseudoPath[1][1][-1], decimals)
+ return x1, y1, x2, y2
+
+
+ def visualize_self_intersections(self, pathElement, selfIntersectionPoints):
+ ''' Draw some circles at given point coordinates (data from array)'''
+ selfIntersectionGroup = pathElement.getparent().add(inkex.Group(id="selfIntersectionPoints-{}".format(pathElement.attrib["id"])))
+ selfIntersectionPointStyle = {'stroke': 'none', 'fill': self.options.color_self_intersections}
+ for selfIntersectionPoint in selfIntersectionPoints:
+ cx = selfIntersectionPoint[0]
+ cy = selfIntersectionPoint[1]
+ selfIntersectionPointCircle = Circle(cx=str(cx),
+ cy=str(cy),
+ r=str(self.svg.unittouu(str(self.options.dotsize_intersections / 2) + "px"))
+ )
+
+ if pathElement.getparent() != self.svg.root:
+ selfIntersectionPointCircle.transform = -pathElement.getparent().composed_transform()
+ selfIntersectionPointCircle.set('id', self.svg.get_unique_id('selfIntersectionPoint-'))
+ selfIntersectionPointCircle.style = selfIntersectionPointStyle
+ selfIntersectionGroup.add(selfIntersectionPointCircle)
+
+
+ def visualize_global_intersections(self, globalIntersectionPoints):
+ ''' Draw some circles at given point coordinates (data from array)'''
+ if len(globalIntersectionPoints) > 0: #only create a group and add stuff if there are some elements to work on
+ globalIntersectionGroup = self.svg.root.add(inkex.Group(id="globalIntersectionPoints"))
+ globalIntersectionPointStyle = {'stroke': 'none', 'fill': self.options.color_global_intersections}
+ for globalIntersectionPoint in globalIntersectionPoints:
+ cx = globalIntersectionPoint.coords[0][0]
+ cy = globalIntersectionPoint.coords[0][1]
+ globalIntersectionPointCircle = Circle(cx=str(cx),
+ cy=str(cy),
+ r=str(self.svg.unittouu(str(self.options.dotsize_intersections / 2) + "px"))
+ )
+ globalIntersectionPointCircle.set('id', self.svg.get_unique_id('globalIntersectionPoint-'))
+ globalIntersectionPointCircle.style = globalIntersectionPointStyle
+ globalIntersectionGroup.add(globalIntersectionPointCircle)
+
+
+ def buildTrimLineGroups(self, allSubSplitData, subSplitIndex, globalIntersectionPoints,
+ trimLineIndex, snap_tolerance, apply_original_style):
+ ''' make a group containing trimmed lines'''
+
+ trimLineStyle = {'stroke': str(self.options.color_trimmed), 'fill': 'none', 'stroke-width': self.options.strokewidth}
+
+ linesWithSnappedIntersectionPoints = snap(LineString(allSubSplitData[0][subSplitIndex]), globalIntersectionPoints, snap_tolerance)
+ trimGroupId = 'shapely-' + allSubSplitData[1][subSplitIndex].split("_")[0] #spit at "_" (_ from subSplitId)
+ trimGroupParentId = allSubSplitData[1][subSplitIndex].split(idPrefix+"-")[1].split("_")[0]
+ trimGroupParent = self.svg.getElementById(trimGroupParentId)
+ trimGroupParentTransform = trimGroupParent.composed_transform()
+ trimGroup = self.findGroup(trimGroupId)
+ if trimGroup is None:
+ trimGroup = trimGroupParent.getparent().add(inkex.Group(id=trimGroupId))
+
+ #apply isBezier and original path id information to group (required for bezier splitting the original path at the end)
+ trimGroup.attrib['isBezier'] = str(allSubSplitData[3][subSplitIndex])
+ trimGroup.attrib['originalId'] = allSubSplitData[4][subSplitIndex]
+
+ #split all lines against all other lines using the intersection points
+ trimLines = split(linesWithSnappedIntersectionPoints, globalIntersectionPoints)
+
+ splitAt = [] #if the sub split line was split by an intersecting line we receive two trim lines with same assigned original path id!
+ prevLine = None
+ for j in range(len(trimLines)):
+ trimLineId = trimGroupId + "-" + str(trimLineIndex)
+ splitAt.append(trimGroupId)
+ if splitAt.count(trimGroupId) > 1: #we detected a lines with intersection on
+ trimLineId = trimLineId + self.svg.get_unique_id(intersectedVerb)
+ '''
+ so the previous lines was an intersection lines too. so we change the id to include the intersected verb
+ (left side and right side of cut) - note: updating element
+ id sometimes seems not to work if the id was used before in Inkscape
+ '''
+ prevLine.attrib['id'] = trimGroupId + "-" + str(trimLineIndex) + self.svg.get_unique_id(intersectedVerb)
+ prevLine.attrib['intersected'] = 'True' #some dirty flag we need
+ prevLine = trimLine = inkex.PathElement(id=trimLineId)
+ #if so.show_debug is True:
+ # self.msg(prevLine.attrib['id'])
+ # self.msg(trimLineId)
+ x, y = trimLines[j].coords.xy
+ 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:
+ trimLine.style = trimLineStyle
+ else:
+ trimLine.style = allSubSplitData[2][subSplitIndex]
+ trimGroup.add(trimLine)
+ return trimGroup
+
+
+ def remove_duplicates(self, allTrimGroups, reverse_removal_order):
+ ''' find duplicate lines in a given array [] of groups '''
+ totalTrimPaths = []
+ if reverse_removal_order is True:
+ allTrimGroups = allTrimGroups[::-1]
+ for trimGroup in allTrimGroups:
+ for element in trimGroup:
+ if element.path not in totalTrimPaths:
+ totalTrimPaths.append(element.path)
+ else:
+ element.delete()
+ if len(trimGroup) == 0:
+ trimGroup.delete()
+
+
+ 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.
+
+ '''
+
+ nonTrimLineStyle = {'stroke': str(self.options.color_nonintersected), 'fill': 'none', 'stroke-width': self.options.strokewidth}
+ trimNonIntersectedStyle = {'stroke': str(self.options.color_combined), 'fill': 'none', 'stroke-width': self.options.strokewidth}
+
+ for trimGroup in allTrimGroups:
+ totalIntersectionsAtPath = 0
+ combinedPath = None
+ combinedPathData = Path()
+ if self.options.show_debug is True:
+ self.msg("trim group {} has {} paths".format(trimGroup.get('id'), len(trimGroup)))
+ for pElement in trimGroup:
+ pId = pElement.get('id')
+ #if self.options.show_debug is True:
+ # self.msg("trim paths id {}".format(pId))
+ if intersectedVerb not in pId:
+ if combinedPath is None:
+ combinedPath = pElement
+ combinedPathData = pElement.path
+ else:
+ combinedPathData += pElement.path
+ pElement.delete()
+ else:
+ totalIntersectionsAtPath += 1
+ if len(combinedPathData) > 0:
+ segData = combinedPathData.to_arrays()
+ newPathData = []
+ newPathData.append(segData[0])
+ for z in range(1, len(segData)): #skip first because we add it statically
+ if segData[z][1] != segData[z-1][1]:
+ newPathData.append(segData[z])
+ if self.options.show_debug is True:
+ 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:
+ combinedPath.style = trimNonIntersectedStyle
+ if totalIntersectionsAtPath == 0:
+ combinedPath.style = nonTrimLineStyle
+ else: #the group might consist of intersections only. than we have length of 0
+ if self.options.show_debug is True:
+ self.msg("trim group {} has no combinable segments (contains only intersected trim lines)".format(trimGroup.get('id')))
+
+
+ def trim_bezier(self, allTrimGroups):
+ '''
+ trim bezier path by checking the lengths and calculating global t parameter from the trimmed sub split lines groups
+ This function does not work yet.
+ '''
+ for trimGroup in allTrimGroups:
+ if trimGroup.attrib.has_key('isBezier') and trimGroup.attrib['isBezier'] == "True":
+ globalTParameters = []
+ if self.options.show_debug is True:
+ self.msg("{}: count of trim lines = {}".format(trimGroup.get('id'), len(trimGroup)))
+ totalLength = 0
+ for trimLine in trimGroup:
+ ignore, lineLength = csplength(CubicSuperPath(trimLine.get('d')))
+ totalLength += lineLength
+ if self.options.show_debug is True:
+ self.msg("total length = {}".format(totalLength))
+ chainLength = 0
+ for trimLine in trimGroup:
+ ignore, lineLength = csplength(CubicSuperPath(trimLine.get('d')))
+ chainLength += lineLength
+ if trimLine.attrib.has_key('intersected') or trimLine == trimGroup[-1]: #we may not used intersectedVerb because this was used for the affected left as well as the right side of the splitting. This would result in one "intersection" too much.
+ globalTParameter = chainLength / totalLength
+ globalTParameters.append(globalTParameter)
+ if self.options.show_debug is True:
+ self.msg("chain piece length = {}".format(chainLength))
+ self.msg("t parameter = {}".format(globalTParameter))
+ chainLength = 0
+ if self.options.show_debug is True:
+ self.msg("Trimming the original bezier path {} at global t parameters: {}".format(trimGroup.attrib['originalId'], globalTParameters))
+ for globalTParameter in globalTParameters:
+ csp = CubicSuperPath(self.svg.getElementById(trimGroup.attrib['originalId']))
+ '''
+ Sadly, those calculated global t parameters are useless for splitting because we cannot split the complete curve at a t parameter
+ Instead we only can split a bezier by getting to commands which build up a bezier path segment.
+ - we need to find those parts (segment pairs) of the original path first where the sub split line intersection occurs
+ - then we need to calculate the t parameter
+ - then we split the bezier part (consisting of two commands) and check the new intersection point.
+ It should match the sub split lines intersection point.
+ If they do not match we need to adjust the t parameter or loop to previous or next bezier command to find intersection
+ '''
+
+
+ def add_arguments(self, pars):
+ pars.add_argument("--tab")
+
+ #Settings - General
+ pars.add_argument("--show_debug", type=inkex.Boolean, default=False, help="Show debug infos")
+ pars.add_argument("--path_types", default="closed_paths", help="Apply for closed paths, open paths or both")
+ pars.add_argument("--break_apart", type=inkex.Boolean, default=False, help="Break apart input paths into sub paths")
+ pars.add_argument("--handle_groups", type=inkex.Boolean, default=False, help="Also looks for paths in groups which are in the current selection")
+ pars.add_argument("--flattenbezier", type=inkex.Boolean, default=True, help="Flatten bezier curves to polylines")
+ pars.add_argument("--flatness", type=float, default=0.1, help="Minimum flatness = 0.001. The smaller the value the more fine segments you will get (quantization). Large values might destroy the line continuity.")
+ 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
+ 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")
+ pars.add_argument("--highlight_opened", type=inkex.Boolean, default=False, help="Highlight opened contours")
+ pars.add_argument("--highlight_closed", type=inkex.Boolean, default=False, help="Highlight closed contours")
+ pars.add_argument("--highlight_self_intersecting", type=inkex.Boolean, default=False, help="Highlight self-intersecting contours")
+ pars.add_argument("--draw_subsplit", type=inkex.Boolean, default=False, help="Draw sub split lines (polylines)")
+ pars.add_argument("--visualize_self_intersections", type=inkex.Boolean, default=False, help="Visualize self-intersecting path points")
+ pars.add_argument("--visualize_global_intersections", type=inkex.Boolean, default=False, help="Visualize global intersection points")
+
+ #Settings - Trimming
+ pars.add_argument("--draw_trimmed", type=inkex.Boolean, default=False, help="Draw trimmed lines")
+ pars.add_argument("--combine_nonintersects", type=inkex.Boolean, default=True, help="Combine non-intersected lines")
+ pars.add_argument("--remove_duplicates", type=inkex.Boolean, default=True, help="Remove duplicate trim lines")
+ pars.add_argument("--reverse_removal_order", type=inkex.Boolean, default=False, help="Reverses the order of removal. Relevant for keeping certain styles of elements")
+ pars.add_argument("--keep_original_after_trim", type=inkex.Boolean, default=False, help="Keep original paths after trimming")
+
+ #Style - General Style
+ pars.add_argument("--strokewidth", type=float, default=1.0, help="Stroke width (px)")
+ 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")
+
+ #Style - Scanning Colors
+ pars.add_argument("--color_opened", type=Color, default='4012452351', help="Color for opened contours")
+ pars.add_argument("--color_closed", type=Color, default='2330080511', help="Color for closed contours")
+ 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")
+
+ #Style - Trimming Color
+ pars.add_argument("--color_trimmed", type=Color, default='1923076095', help="Color for trimmed lines")
+ pars.add_argument("--color_combined", type=Color, default='3227634687', help="Color for non-intersected lines")
+ pars.add_argument("--color_nonintersected", type=Color, default='3045284607', help="Color for non-intersected paths")
+
+
+ def effect(self):
+
+ so = self.options
+
+ #warn if there is nothing to visualize
+ if \
+ so.keep_original_after_trim is False and \
+ so.remove_opened is True and \
+ so.remove_closed is True and \
+ so.visualize_self_intersections is False and \
+ so.visualize_global_intersections is False and \
+ so.draw_subsplit is False and \
+ so.draw_trimmed is False:
+ self.msg("Nothing to draw. Select at least one visualization option.")
+ return
+
+ #some dependent configuration for drawing modes
+ if \
+ 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_open = False
+ so.highlight_closed = False
+ so.highlight_self_intersecting = False
+
+ #some constant stuff / styles
+ keepOpenPathStyle = {'stroke': str(so.color_opened), 'fill': 'none', 'stroke-width': so.strokewidth}
+ keepClosedPathStyle = {'stroke': str(so.color_closed), 'fill': 'none', 'stroke-width': so.strokewidth}
+ keepSelfIntersectingPathStyle = {'stroke': str(so.color_self_intersecting_paths), 'fill': 'none', 'stroke-width': so.strokewidth}
+ subSplitLineStyle = {'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
+ '''
+
+ pathElements = self.getPathElements()
+
+ allSubSplitLines = []
+ allSubSplitIds = []
+ allSubSplitStyles = []
+ allSubSplitIsBezier = []
+ allSubSplitOriginalPathIds = []
+
+ allSubSplitData = [] #an array of sub split lines and it's belonging sub path id
+ allSubSplitData.append(allSubSplitLines) #column 0
+ allSubSplitData.append(allSubSplitIds) #column 1
+ allSubSplitData.append(allSubSplitStyles) #column 2
+ allSubSplitData.append(allSubSplitIsBezier) #column 3
+ allSubSplitData.append(allSubSplitOriginalPathIds) #column 4
+
+ 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
+ combined paths before.
+ '''
+ pathIsClosed = 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
+ pathIsClosed = True
+
+ #Check if we should delete the path or not
+ if so.remove_opened is True and pathIsClosed is False:
+ pathElement.delete()
+ continue #skip this loop iteration
+ if so.remove_closed is True and pathIsClosed is True:
+ pathElement.delete()
+ continue #skip this loop iteration
+
+ #Check if we should skip or process the path anyway
+ if so.path_types == 'open_paths' and pathIsClosed is True: continue #skip this loop iteration
+ elif so.path_types == 'closed_paths' and pathIsClosed is False: continue #skip this loop iteration
+ elif so.path_types == 'both': pass
+
+ #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"]
+
+ 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:
+ subPaths.append(raw[prev:i])
+ prev = i
+ 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))
+
+ #self.msg("sub path in {} = {}".format(element.get('id'), subPath))
+ #flatten the subpath if wanted
+ 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
+ if so.flattenbezier is True and isBezier is True:
+ bezier.cspsubdiv(subPathData, so.flatness) #modifies the path
+ flattenedpath = []
+ for seg in subPathData:
+ first = True
+ for csp in seg:
+ cmd = 'L'
+ if first:
+ cmd = 'M'
+ first = False
+ flattenedpath.append([cmd, [csp[1][0], csp[1][1]]])
+ #self.msg("flattened path = " + str(flattenedpath))
+ segs = list(CubicSuperPath(flattenedpath).to_segments())
+ else:
+ segs = list(subPathData.to_segments())
+ #segs = segs[::-1] #reverse the segments
+
+ #build polylines from segment data
+ subSplitLines = []
+ subSplitIds = []
+ subSplitStyles = []
+ subSplitIsBezier = []
+ subSplitOriginalPathIds = []
+ for i in range(len(segs) - 1): #we could do the same routine to build up polylines using "for x, y in node.path.end_points". See "number nodes" extension
+ x1, y1, x2, y2 = self.lineFromSegments(segs, i, so.decimals)
+ #self.msg("(y1 = {},y2 = {},x1 = {},x2 = {})".format(x1, y1, x2, y2))
+ subSplitId = "{}-{}_{}".format(idPrefix, originalPathId, i)
+ if so.draw_subsplit is True:
+ line = inkex.PathElement(id=subSplitId)
+ #apply line path with composed negative transform from parent element
+ line.path = [['M', [x1, y1]], ['L', [x2, y2]]]
+ if pathElement.getparent() != self.svg.root:
+ line.path = line.path.transform(-pathElement.getparent().composed_transform())
+ line.style = subSplitLineStyle
+ subSplitTrimLineGroup.add(line)
+
+ subSplitLines.append([(x1, y1), (x2, y2)])
+ subSplitIds.append(subSplitId)
+ subSplitStyles.append(pathElement.style)
+ subSplitIsBezier.append(isBezier) #some dirty flag we need
+ subSplitOriginalPathIds.append(originalPathId) #some dirty flag we need
+
+ 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
+ for subSplitLine in subSplitTrimLineGroup:
+ if subPathIsClosed is True:
+ if so.highlight_closed is True:
+ subSplitLine.style = keepClosedPathStyle
+ else:
+ if so.highlight_opened is True:
+ subSplitLine.style = keepOpenPathStyle
+
+ #check for self intersections
+ selfIntersectionPoints = isect_segments(subSplitLines, validate=True)
+ if len(selfIntersectionPoints) > 0:
+ if so.show_debug is True:
+ self.msg("{} in {} intersects itself with {} intersections!".format(subSplitId, originalPathId, len(selfIntersectionPoints)))
+ if so.draw_subsplit is True:
+ if so.highlight_self_intersecting is True:
+ for subSplitLine in subSplitTrimLineGroup:
+ subSplitLine.style = keepSelfIntersectingPathStyle #adjusts line color
+ #delete cosmetic sub split lines if desired
+ if so.remove_self_intersecting:
+ subSplitTrimLineGroup.delete()
+ if so.visualize_self_intersections is True: #draw points (circles)
+ self.visualize_self_intersections(pathElement, selfIntersectionPoints)
+
+ #and also delete non-cosmetics
+ if so.remove_self_intersecting:
+ #if we also want to avoid processing them for trimming
+ subSplitLines = None
+ subSplitIds = None
+ subSplitStyles = None
+ subSplitIsBezier = None
+ subSplitOriginalPathIds = None
+ pathElement.delete() #and finally delete the orginal path
+
+ #extend the complete collection
+ if subSplitLines != None and \
+ subSplitIds != None and \
+ subSplitStyles != None and \
+ subSplitIsBezier != None and \
+ allSubSplitOriginalPathIds != None:
+ allSubSplitStyles.extend(subSplitStyles)
+ allSubSplitLines.extend(subSplitLines)
+ allSubSplitIds.extend(subSplitIds)
+ allSubSplitIsBezier.extend(subSplitIsBezier)
+ allSubSplitOriginalPathIds.extend(subSplitOriginalPathIds)
+
+ 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
+
+ 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!)
+ '''
+ 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)
+
+ ''' 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
+ '''
+ if so.draw_trimmed is True:
+ allTrimGroups = [] #container to collect all trim groups for later on processing
+ trimLineIndex = 1
+ for subSplitIndex in range(len(allSubSplitData[0])):
+ trimGroup = self.buildTrimLineGroups(allSubSplitData, subSplitIndex,
+ globalIntersectionPoints, trimLineIndex, so.snap_tolerance, so.apply_original_style)
+ if trimGroup not in allTrimGroups:
+ allTrimGroups.append(trimGroup)
+ trimLineIndex += 1
+
+ if so.show_debug is True: self.msg("trim groups count: {}".format(len(allTrimGroups)))
+ if len(allTrimGroups) == 0:
+ self.msg("You selected to draw trimmed lines but no intersections could be calculated.")
+
+ #trim beziers - not working yet
+ if so.bezier_trimming is True: self.trim_bezier(allTrimGroups)
+
+ #check for duplicate trim lines and delete them if desired
+ 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)
+
+ #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\
+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\
+- reduce or raise the 'flatness' setting (if quantization option is used at all; default is 0.100).")
+ return
+
+if __name__ == '__main__':
+ ContourScannerAndTrimmer().run()
diff --git a/extensions/fablabchemnitz/contourscanner/poly_point_isect.py b/extensions/fablabchemnitz/contourscannerandtrimmer/poly_point_isect.py
similarity index 100%
rename from extensions/fablabchemnitz/contourscanner/poly_point_isect.py
rename to extensions/fablabchemnitz/contourscannerandtrimmer/poly_point_isect.py