diff --git a/extensions/fablabchemnitz/remove_duplicate_lines/remove_duplicate_lines.inx b/extensions/fablabchemnitz/remove_duplicate_lines/remove_duplicate_lines.inx
new file mode 100644
index 00000000..2b86b73b
--- /dev/null
+++ b/extensions/fablabchemnitz/remove_duplicate_lines/remove_duplicate_lines.inx
@@ -0,0 +1,35 @@
+
+
+ Remove Duplicate Lines
+ fablabchemnitz.de.remove_duplicate_lines
+
+
+
+ false
+
+ false
+ 0.01
+
+ false
+
+
+
+
+
+
+ all
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/remove_duplicate_lines/remove_duplicate_lines.py b/extensions/fablabchemnitz/remove_duplicate_lines/remove_duplicate_lines.py
new file mode 100644
index 00000000..082fa23d
--- /dev/null
+++ b/extensions/fablabchemnitz/remove_duplicate_lines/remove_duplicate_lines.py
@@ -0,0 +1,361 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2021 Ellen Wasboe, ellen@wasbo.net
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+"""s
+Remove duplicate lines by comparing cubic bezier control points after converting to cubic super path.
+Optionally include searching for overlaps within the same path (which might cause trouble if the tolerance is too high and small neighbour segments are regarded as a match.
+Optionally add a tolerance for the comparison.
+Optionally interpolate the four control points of the remaining and the removed segment.
+"""
+
+import inkex
+from inkex import bezier, PathElement, CubicSuperPath, Transform
+import numpy as np
+from tkinter import messagebox
+
+class removeDuplicateLines(inkex.EffectExtension):
+
+ def add_arguments(self, pars):
+ pars.add_argument("--tab", default="options")
+ pars.add_argument("--tolerance", default="0")
+ pars.add_argument("--minUse", type=inkex.Boolean, default=False)
+ pars.add_argument("--selfPath", type=inkex.Boolean, default=False)
+ pars.add_argument("--interp", type=inkex.Boolean, default=False)
+
+ """Remove duplicate lines"""
+ def effect(self):
+ tolerance=float(self.options.tolerance)
+ if self.options.minUse == False:
+ tolerance=0
+
+ coords=[]#one segmentx8 subarray for each path and subpath (paths and subpaths treated equally)
+ pathNo=[]
+ subPathNo=[]
+ cPathNo=[]#counting alle paths and subpaths equally
+ removeSegmentPath=[]
+ removeSegmentSubPath=[]
+ removeSegment_cPath=[]
+ removeSegment=[]
+ matchSegmentPath=[]
+ matchSegmentSubPath=[]
+ matchSegment_cPath=[]
+ matchSegment=[]
+ matchSegmentRev=[]
+
+ if not self.svg.selected:
+ raise inkex.AbortExtension("Please select an object.")
+ nFailed=0
+ nInkEffect=0
+ p=0
+ c=0
+ idsNotPath=[]
+ for id, elem in self.svg.selection.id_dict().items():
+ thisIsPath=True
+ if elem.get('d')==None:
+ thisIsPath=False
+ nFailed+=1
+ idsNotPath.append(id)
+ if elem.get('inkscape:path-effect') != None:
+ thisIsPath=False
+ nInkEffect+=1
+ idsNotPath.append(id)
+
+ if thisIsPath:
+ #apply transformation matrix if present
+ csp = CubicSuperPath(elem.get('d'))
+ elem.path=elem.path.to_absolute()
+ transformMat = Transform(elem.get('transform'))
+ cpsTransf=csp.transform(transformMat)
+ elem.path = cpsTransf.to_path(curves_only=True)
+ pp=elem.path
+
+ s=0
+ #create matrix with segment coordinates p1x p1y c1x c1y c2x c2y p2x p2y
+ for sub in pp.to_superpath():
+ coordsThis=np.zeros((len(sub)-1,8))
+
+ i=0
+ while i <= len(sub) - 2:
+ coordsThis[i][0]=sub[i][1][0]
+ coordsThis[i][1]=sub[i][1][1]
+ coordsThis[i][2]=sub[i][2][0]
+ coordsThis[i][3]=sub[i][2][1]
+ coordsThis[i][4]=sub[i+1][0][0]
+ coordsThis[i][5]=sub[i+1][0][1]
+ coordsThis[i][6]=sub[i+1][1][0]
+ coordsThis[i][7]=sub[i+1][1][1]
+
+ i+=1
+
+ coords.append(coordsThis)
+ pathNo.append(p)
+ subPathNo.append(s)
+ cPathNo.append(c)
+ c+=1
+ s+=1
+ p+=1
+ if nFailed > 0:
+ messagebox.showwarning('Warning',str(nFailed)+' selected elements did not have a path. Groups, shapeelements and text will be ignored.')
+
+ if nInkEffect > 0:
+ messagebox.showwarning('Warning',str(nInkEffect)+' selected elements have an inkscape:path-effect applied. These elements will be ignored to avoid confusing results. Apply Paths->Object to path (Shift+Ctrl+C) and retry .')
+
+ origCoords=[]
+ for item in coords: origCoords.append(np.copy(item))#make a real copy (not a reference that changes with the original
+ #search for overlapping or close segments
+ #for each segment find if difference of any x or y is less than tolerance - if so - calculate 2d-distance and find if all 4 less than tolerance
+ #repeat with reversed segment
+ #if match found set match coordinates to -1000 to mark this to be removed and being ignored later on
+ i=0
+ while i <= len(coords)-1:#each path or subpath
+ j=0
+ while j<=len(coords[i][:,0])-1:#each segment j of path i
+ k=0
+ while k<=len(coords)-1:#search all other subpaths
+ evalPath=True
+ if k == i and self.options.selfPath == False:#do not test path against itself
+ evalPath=False
+ if evalPath:
+ segmentCoords=np.array(coords[i][j,:])
+ if segmentCoords[0] != -1000 and segmentCoords[1] != -1000:
+ searchCoords=np.array(coords[k])
+ if k==i:
+ searchCoords[j,:]=-2000#avoid comparing segment with itself
+ subtr=np.abs(searchCoords-segmentCoords)
+ maxval=subtr.max(1)
+ lessTol=np.argwhere(maxval 0:#proceed to calculate 2d distance where both x and y distance is less than tolerance
+ c=0
+ while c < len(lessTol):
+ dists=np.zeros(4)
+ dists[0]=np.sqrt(np.add(np.power(subtr[lessTol[c,0]][0],2),np.power(subtr[lessTol[c,0]][1],2)))
+ dists[1]=np.sqrt(np.add(np.power(subtr[lessTol[c,0]][2],2),np.power(subtr[lessTol[c,0]][3],2)))
+ dists[2]=np.sqrt(np.add(np.power(subtr[lessTol[c,0]][4],2),np.power(subtr[lessTol[c,0]][5],2)))
+ dists[3]=np.sqrt(np.add(np.power(subtr[lessTol[c,0]][6],2),np.power(subtr[lessTol[c,0]][7],2)))
+ if dists.max() < tolerance:
+ matchThis=True
+ finalK=k
+ lesstolc=lessTol[c]
+ c+=1
+ if matchThis == False:#try reversed
+ segmentCoordsRev=[segmentCoords[6], segmentCoords[7],segmentCoords[4],segmentCoords[5],segmentCoords[2],segmentCoords[3],segmentCoords[0],segmentCoords[1]]
+ subtr=np.abs(searchCoords-segmentCoordsRev)
+ maxval=subtr.max(1)
+ lessTol=np.argwhere(maxval 0:#proceed to calculate 2d distance where both x and y distance is less than tolerance
+ c=0
+ while c < len(lessTol):
+ dists=np.zeros(4)
+ dists[0]=np.sqrt(np.add(np.power(subtr[lessTol[c,0]][0],2),np.power(subtr[lessTol[c,0]][1],2)))
+ dists[1]=np.sqrt(np.add(np.power(subtr[lessTol[c,0]][2],2),np.power(subtr[lessTol[c,0]][3],2)))
+ dists[2]=np.sqrt(np.add(np.power(subtr[lessTol[c,0]][4],2),np.power(subtr[lessTol[c,0]][5],2)))
+ dists[3]=np.sqrt(np.add(np.power(subtr[lessTol[c,0]][6],2),np.power(subtr[lessTol[c,0]][7],2)))
+ if dists.max() < tolerance:
+ matchThis=True
+ matchThisRev=True
+ finalK=k
+ lesstolc=lessTol[c]
+ c+=1
+
+ if matchThis:
+ coords[finalK][lesstolc,:]=-1000
+ removeSegmentPath.append(pathNo[finalK])
+ removeSegmentSubPath.append(subPathNo[finalK])
+ removeSegment_cPath.append(cPathNo[finalK])
+ removeSegment.append(lesstolc)
+ matchSegmentPath.append(pathNo[i])
+ matchSegmentSubPath.append(subPathNo[i])
+ matchSegment_cPath.append(cPathNo[i])
+ matchSegment.append(j)
+ matchSegmentRev.append(matchThisRev)
+
+ k+=1
+ j+=1
+ i+=1
+
+ #(interpolate remaining and) remove segments with a match
+ if len(removeSegmentPath) > 0:
+ removeSegmentPath=np.array(removeSegmentPath)
+ removeSegmentSubPath=np.array(removeSegmentSubPath)
+ removeSegment_cPath=np.array(removeSegment_cPath)
+ removeSegment=np.array(removeSegment)
+ matchSegmentPath=np.array(matchSegmentPath)
+ matchSegment_cPath=np.array(matchSegment_cPath)
+ matchSegmentSubPath=np.array(matchSegmentSubPath)
+ matchSegment=np.array(matchSegment)
+ matchSegmentRev=np.array(matchSegmentRev)
+
+ #first interpolate remaining segment
+ if self.options.interp:
+ idx=np.argsort(matchSegmentPath)
+ matchSegmentPath=matchSegmentPath[idx]
+ matchSegment_cPath=matchSegment_cPath[idx]
+ matchSegmentSubPath=matchSegmentSubPath[idx]
+ matchSegment=matchSegment[idx]
+ matchSegmentRev=matchSegmentRev[idx]
+ remSegmentPath=removeSegmentPath[idx]
+ remSegment_cPath=removeSegment_cPath[idx]
+ remSegment=removeSegment[idx]
+
+ i=0
+ for id, elem in self.svg.selection.id_dict().items():#each path
+ if not id in idsNotPath:
+ if i in matchSegmentPath:
+ idxi=np.argwhere(matchSegmentPath==i)
+ idxi=idxi.reshape(-1)
+ icMatch=matchSegment_cPath[idxi]
+ iSegMatch=matchSegment[idxi]
+ iSegMatchRev=matchSegmentRev[idxi]
+ iSubMatch=matchSegmentSubPath[idxi]
+ iSegRem=remSegment[idxi]
+ icRem=remSegment_cPath[idxi]
+ iPathRem=remSegmentPath[idxi]
+ new=[]
+ j=0
+ for sub in elem.path.to_superpath():#each subpath
+ idxj=np.argwhere(iSubMatch==j)
+ idxj=idxj.reshape(-1)
+ this_cMatch=icMatch[idxj]
+ thisSegMatch=iSegMatch[idxj]
+ thisSegMatchRev=iSegMatchRev[idxj]
+ thisSegRem=iSegRem[idxj].reshape(-1)
+ this_cRem=icRem[idxj]
+ thisPathRem=iPathRem[idxj]
+ k=0
+ while k 0:
+ idx=idx.reshape(1,-1)
+ idx=idx[0]
+ new=[]
+ j=0
+ for sub in elem.path.to_superpath():#each subpath
+ thisSegRem=removeSegment[idx]
+ keepLast=False if len(sub)-2 in thisSegRem else True
+ keepNext2Last=False if len(sub)-3 in thisSegRem else True
+ thisSubPath=removeSegmentSubPath[idx]
+ idx2=np.argwhere(removeSegmentSubPath[idx]==j)
+ if len(idx2) > 0:
+ idx2=idx2.reshape(1,-1)
+ idx2=idx2[0]
+ thisSegRem=thisSegRem[idx2]
+ if len(thisSegRem) < len(sub)-1:#if any segment to be kept
+ #find first segment
+ k=0
+ if 0 in thisSegRem:#remove first segment
+ proceed=True
+ while proceed:
+ if k+1 in thisSegRem:
+ k+=1
+ else:
+ proceed=False
+ k+=1
+ new.append([sub[k]])
+ if sub[k+1]!=new[-1][-1]:#avoid duplicated nodes
+ new[-1].append(sub[k+1])
+ new[-1][-1][0]=new[-1][-1][1]
+ else:
+ new.append([sub[0]])
+ if sub[1]!=new[-1][-1]:#avoid duplicated nodes
+ new[-1].append(sub[1])
+ k+=1
+
+ #rest of segments
+ while k 0:
+ if len(new[-1])==1: new.pop()
+ else:
+ new.append(sub)#add as is
+
+ j+=1
+
+ elem.path = CubicSuperPath(new).to_path(curves_only=True)
+ i+=1
+
+if __name__ == '__main__':
+ removeDuplicateLines().run()
\ No newline at end of file