diff --git a/extensions/fablabchemnitz_bouwkamp_code.inx b/extensions/fablabchemnitz_bouwkamp_code.inx
index d84a9f5f..2f883864 100644
--- a/extensions/fablabchemnitz_bouwkamp_code.inx
+++ b/extensions/fablabchemnitz_bouwkamp_code.inx
@@ -26,4 +26,4 @@ You can paste Bouwkamp codes with or without various formatting characters (like
-
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz_destructiveclip.inx b/extensions/fablabchemnitz_destructiveclip.inx
new file mode 100644
index 00000000..3cc31c0d
--- /dev/null
+++ b/extensions/fablabchemnitz_destructiveclip.inx
@@ -0,0 +1,17 @@
+
+
+ <_name>Destructive Clip
+ fablabchemnitz.de.destructiveclip
+
+ path
+
+
+
+
+
+ "Destructively" clip selected paths using the topmost as clipping path
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz_destructiveclip.py b/extensions/fablabchemnitz_destructiveclip.py
new file mode 100644
index 00000000..bcc8507a
--- /dev/null
+++ b/extensions/fablabchemnitz_destructiveclip.py
@@ -0,0 +1,186 @@
+#!/usr/bin/env python3
+'''
+ ---DESTRUCTIVE Clip---
+ An Inkscape Extension which works like Object|Clip|Set except that the paths clipped are actually *modified*
+ Thus the clipping is included when exported, for example as a DXF file.
+ Select two or more *paths* then choose Extensions|Modify path|Destructive clip. The topmost path will be used to clip the others.
+ Notes:-
+ * Curves in paths are not supported (use Flatten Beziers).
+ * Non-path objects in the selection will be ignored. Use Object|Ungroup.
+ * Paths entirely outside the clipping path will remain untouched (rather than modifying them to an empty path)
+ * Complex paths may take a while (there seems to be no way too show progress)
+ * Yes, using MBR's to do gross clipping might make it faster
+ * No, Python is not my first language (C/C++ is)
+
+ Mark Wilson Feb 2016
+
+ ----
+
+ Edits by Windell H. Oskay, www.evilmadscientit.com, August 2020
+ Update calls to Inkscape 1.0 extension API to avoid deprecation warnings
+ Minimal standardization of python whitespace
+ Handle some errors more gracefully
+
+'''
+
+import inkex
+import sys
+
+
+class DestructiveClip(inkex.Effect):
+
+ def __init__(self):
+ self.tolerance = 0.0001 # arbitrary fudge factor
+ inkex.Effect.__init__(self)
+ self.error_messages = []
+
+ self.curve_error = 'Unable to parse path.\nConsider removing curves '
+ self.curve_error += 'with Extensions > Modify Path > Flatten Beziers...'
+
+ def approxEqual(self, a, b):
+ # compare with tiny tolerance
+ return abs(a-b) <= self.tolerance
+
+ def midPoint(self, line):
+ # midPoint of line
+ return [(line[0][0] + line[1][0])/2, (line[0][1] + line[1][1])/2]
+
+ def maxX(self, lineSegments):
+ # return max X coord of lineSegments
+ maxx = 0.0
+ for line in lineSegments:
+ maxx = max(maxx, line[0][0])
+ maxx = max(maxx, line[1][0])
+ return maxx
+
+ def simplepathToLineSegments(self, path):
+ # takes a simplepath and converts to line *segments*, for simplicity.
+ # Thus [MoveTo P0, LineTo P1, LineTo P2] becomes [[P0-P1],[P1,P2]]
+ # only handles, Move, Line and Close.
+ # The simplepath library has already simplified things, normalized relative commands, etc
+ lineSegments = first = prev = this = []
+ errors = set([]) # Similar errors will be stored only once
+ for cmd in path:
+ this = cmd[1]
+ if cmd[0] == 'M': # moveto
+ if first == []:
+ first = this
+ elif cmd[0] == 'L': # lineto
+ lineSegments.append([prev, this])
+ elif cmd[0] == 'Z': # close
+ lineSegments.append([prev, first])
+ first = []
+ elif cmd[0] == 'C':
+ # https://developer.mozilla.org/en/docs/Web/SVG/Tutorial/Paths
+ lineSegments.append([prev, [this[4], this[5]]])
+ errors.add("Curve node detected (svg type C), this node will be handled as a regular node")
+ else:
+ errors.add("Invalid node type detected: {}. This script only handle type M, L, Z".format(cmd[0]))
+ prev = this
+ return (lineSegments, errors)
+
+ def linesgmentsToSimplePath(self, lineSegments):
+ # reverses simplepathToLines - converts line segments to Move/Line-to's
+ path = []
+ end = None
+ for line in lineSegments:
+ start = line[0]
+ if end is None:
+ path.append(['M', start]) # start with a move
+ elif not (self.approxEqual(end[0], start[0]) and self.approxEqual(end[1], start[1])):
+ path.append(['M', start]) # only move if previous end not within tolerance of this start
+ end = line[1]
+ path.append(['L', end])
+ return path
+
+ def lineIntersection(self, L1From, L1To, L2From, L2To):
+ # returns as [x, y] the intersection of the line L1From-L1To and L2From-L2To, or None
+ # http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
+
+ try:
+ dL1 = [L1To[0] - L1From[0], L1To[1] - L1From[1]]
+ dL2 = [L2To[0] - L2From[0], L2To[1] - L2From[1]]
+ except IndexError:
+ inkex.errormsg(self.curve_error)
+ sys.exit()
+
+ denominator = -dL2[0]*dL1[1] + dL1[0]*dL2[1]
+ if not self.approxEqual(denominator, 0.0):
+ s = (-dL1[1]*(L1From[0] - L2From[0]) + dL1[0]*(L1From[1] - L2From[1]))/denominator
+ t = (+dL2[0]*(L1From[1] - L2From[1]) - dL2[1]*(L1From[0] - L2From[0]))/denominator
+ if s >= 0.0 and s <= 1.0 and t >= 0.0 and t <= 1.0:
+ return [L1From[0] + (t * dL1[0]), L1From[1] + (t * dL1[1])]
+ else:
+ return None
+
+ def insideRegion(self, point, lineSegments, lineSegmentsMaxX):
+ # returns true if point is inside the region defined by lineSegments. lineSegmentsMaxX is the maximum X extent
+ ray = [point, [lineSegmentsMaxX*2.0, point[1]]] # hz line to right of point, extending well outside MBR
+ crossings = 0
+ for line in lineSegments:
+ if not self.lineIntersection(line[0], line[1], ray[0], ray[1]) is None:
+ crossings += 1
+ return (crossings % 2) == 1 # odd number of crossings means inside
+
+ def cullSegmentedLine(self, segmentedLine, lineSegments, lineSegmentsMaxX):
+ # returns just the segments in segmentedLine which are inside lineSegments
+ culled = []
+ for segment in segmentedLine:
+ if self.insideRegion(self.midPoint(segment), lineSegments, lineSegmentsMaxX):
+ culled.append(segment)
+ return culled
+
+ def clipLine(self, line, lineSegments):
+ # returns line split where-ever lines in lineSegments cross it
+ linesWrite = [line]
+ for segment in lineSegments:
+ linesRead = linesWrite
+ linesWrite = []
+ for line in linesRead:
+ intersect = self.lineIntersection(line[0], line[1], segment[0], segment[1])
+ if intersect is None:
+ linesWrite.append(line)
+ else: # split
+ linesWrite.append([line[0], intersect])
+ linesWrite.append([intersect, line[1]])
+ return linesWrite
+
+ def clipLineSegments(self, lineSegmentsToClip, clippingLineSegments):
+ # return the lines in lineSegmentsToClip clipped by the lines in clippingLineSegments
+ clippedLines = []
+ for lineToClip in lineSegmentsToClip:
+ clippedLines.extend(self.cullSegmentedLine(self.clipLine(lineToClip, clippingLineSegments), clippingLineSegments, self.maxX(clippingLineSegments)))
+ return clippedLines
+
+ def effect(self):
+ clippingLineSegments = None
+ pathTag = inkex.addNS('path', 'svg')
+ groupTag = inkex.addNS('g', 'svg')
+ self.error_messages = []
+ for id in self.options.ids: # the selection, top-down
+ node = self.svg.selected[id]
+ if node.tag == pathTag:
+ if clippingLineSegments is None: # first path is the clipper
+ (clippingLineSegments, errors) = self.simplepathToLineSegments(node.path.to_arrays())
+ self.error_messages.extend(['{}: {}'.format(id, err) for err in errors])
+ else:
+ # do all the work!
+ segmentsToClip, errors = self.simplepathToLineSegments(node.path.to_arrays())
+ self.error_messages.extend(['{}: {}'.format(id, err) for err in errors])
+ clippedSegments = self.clipLineSegments(segmentsToClip, clippingLineSegments)
+ if len(clippedSegments) != 0:
+ path = str(inkex.Path(self.linesgmentsToSimplePath(clippedSegments)))
+ node.set('d', path)
+ else:
+ # don't put back an empty path(?) could perhaps put move, move?
+ inkex.errormsg('Object {} clipped to nothing, will not be updated.'.format(node.get('id')))
+ elif node.tag == groupTag: # we don't look inside groups for paths
+ inkex.errormsg('Group object {} will be ignored. Please ungroup before running the script.'.format(id))
+ else: # something else
+ inkex.errormsg('Object {} is not of type path ({}), and will be ignored. Current type "{}".'.format(id, pathTag, node.tag))
+
+ for error in self.error_messages:
+ inkex.errormsg(error)
+
+if __name__ == '__main__':
+ DestructiveClip().run()
\ No newline at end of file