diff --git a/extensions/fablabchemnitz/dxf2papercraft/kabeja/inkscape-extension/kabeja b/extensions/fablabchemnitz/dxf2papercraft/kabeja/inkscape-extension/kabeja
deleted file mode 100644
index 35d1ce43..00000000
--- a/extensions/fablabchemnitz/dxf2papercraft/kabeja/inkscape-extension/kabeja
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/sh
-KABEJA_HOME=`dirname $0`
-java -jar $KABEJA_HOME/kabeja-dxf2svg.jar "$1"
-
diff --git a/extensions/fablabchemnitz/dxf2papercraft/kabeja/inkscape-extension/kabeja-dxf2svg.jar b/extensions/fablabchemnitz/dxf2papercraft/kabeja/inkscape-extension/kabeja-dxf2svg.jar
deleted file mode 100644
index 1a587b3b..00000000
Binary files a/extensions/fablabchemnitz/dxf2papercraft/kabeja/inkscape-extension/kabeja-dxf2svg.jar and /dev/null differ
diff --git a/extensions/fablabchemnitz/dxf2papercraft/kabeja/inkscape-extension/kabeja.bat b/extensions/fablabchemnitz/dxf2papercraft/kabeja/inkscape-extension/kabeja.bat
deleted file mode 100644
index 7beb5973..00000000
--- a/extensions/fablabchemnitz/dxf2papercraft/kabeja/inkscape-extension/kabeja.bat
+++ /dev/null
@@ -1,3 +0,0 @@
-@echo off
-set KABEJA_HOME=%0\..
-java -jar %KABEJA_HOME%\kabeja-dxf2svg.jar %1
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/dxf2papercraft/kabeja/inkscape-extension/kabeja.inx b/extensions/fablabchemnitz/dxf2papercraft/kabeja/inkscape-extension/kabeja.inx
deleted file mode 100644
index 2afafb9d..00000000
--- a/extensions/fablabchemnitz/dxf2papercraft/kabeja/inkscape-extension/kabeja.inx
+++ /dev/null
@@ -1,22 +0,0 @@
-
- Kabeja DXF Input
- org.kabeja.inkscape.import.dxf
- org.inkscape.input.svg
- Choose the layout for the import.
-
- - Modelspace
- - Modelspace-Limits
- - Paperspace
- - Paperspace-Limits
-
-
- .dxf
- image/vnd.dxf
- Kabeja-AutoCAD DXF (*.dxf)
- Import AutoCAD's Document Exchange Format
- org.inkscape.output.svg
-
-
-
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/dxfdwgimporter/kabeja/inkscape-extension/kabeja b/extensions/fablabchemnitz/dxfdwgimporter/kabeja/inkscape-extension/kabeja
deleted file mode 100644
index 35d1ce43..00000000
--- a/extensions/fablabchemnitz/dxfdwgimporter/kabeja/inkscape-extension/kabeja
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/bin/sh
-KABEJA_HOME=`dirname $0`
-java -jar $KABEJA_HOME/kabeja-dxf2svg.jar "$1"
-
diff --git a/extensions/fablabchemnitz/dxfdwgimporter/kabeja/inkscape-extension/kabeja-dxf2svg.jar b/extensions/fablabchemnitz/dxfdwgimporter/kabeja/inkscape-extension/kabeja-dxf2svg.jar
deleted file mode 100644
index 1a587b3b..00000000
Binary files a/extensions/fablabchemnitz/dxfdwgimporter/kabeja/inkscape-extension/kabeja-dxf2svg.jar and /dev/null differ
diff --git a/extensions/fablabchemnitz/dxfdwgimporter/kabeja/inkscape-extension/kabeja.bat b/extensions/fablabchemnitz/dxfdwgimporter/kabeja/inkscape-extension/kabeja.bat
deleted file mode 100644
index 7beb5973..00000000
--- a/extensions/fablabchemnitz/dxfdwgimporter/kabeja/inkscape-extension/kabeja.bat
+++ /dev/null
@@ -1,3 +0,0 @@
-@echo off
-set KABEJA_HOME=%0\..
-java -jar %KABEJA_HOME%\kabeja-dxf2svg.jar %1
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/dxfdwgimporter/kabeja/inkscape-extension/kabeja.inx b/extensions/fablabchemnitz/dxfdwgimporter/kabeja/inkscape-extension/kabeja.inx
deleted file mode 100644
index 2afafb9d..00000000
--- a/extensions/fablabchemnitz/dxfdwgimporter/kabeja/inkscape-extension/kabeja.inx
+++ /dev/null
@@ -1,22 +0,0 @@
-
- Kabeja DXF Input
- org.kabeja.inkscape.import.dxf
- org.inkscape.input.svg
- Choose the layout for the import.
-
- - Modelspace
- - Modelspace-Limits
- - Paperspace
- - Paperspace-Limits
-
-
- .dxf
- image/vnd.dxf
- Kabeja-AutoCAD DXF (*.dxf)
- Import AutoCAD's Document Exchange Format
- org.inkscape.output.svg
-
-
-
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/ids_to_text.inx b/extensions/fablabchemnitz/ids_to_text.inx
index 380177f1..a6bb13c3 100644
--- a/extensions/fablabchemnitz/ids_to_text.inx
+++ b/extensions/fablabchemnitz/ids_to_text.inx
@@ -1,7 +1,7 @@
-
+
Ids To Text
- org.inkscape.render.ids_to_text
+ fablabchemnitz.de.ids_to_text
10
255
Roboto
diff --git a/extensions/fablabchemnitz/ifs_fractals.inx b/extensions/fablabchemnitz/ifs_fractals.inx
new file mode 100644
index 00000000..9788f349
--- /dev/null
+++ b/extensions/fablabchemnitz/ifs_fractals.inx
@@ -0,0 +1,80 @@
+
+
+ IFS Fractals
+ fablabchemnitz.de.ifs_fractals
+
+
+ 3
+
+
+
+ true
+ 0.5
+ 0
+ 0
+ 0.5
+ 0
+ 0
+
+
+ false
+ 0.5
+ 0
+ 0
+ 0.5
+ 1
+ 0
+
+
+ false
+ 0.5
+ 0
+ 0
+ 0.5
+ 0.5
+ 1
+
+
+ false
+ 0.5
+ 0
+ 0
+ 0.5
+ 0
+ 0
+
+
+ false
+ 0.5
+ 0
+ 0
+ 0.5
+ 0
+ 0
+
+
+
+
+
+
+
+
+
+
+ all
+
+
+
+
+
+
+
+
diff --git a/extensions/fablabchemnitz/ifs_fractals.py b/extensions/fablabchemnitz/ifs_fractals.py
new file mode 100644
index 00000000..ee245f13
--- /dev/null
+++ b/extensions/fablabchemnitz/ifs_fractals.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python3
+# coding=utf-8
+#
+# Copyright (C) 2020 Dylan Simon, dylan@dylex.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.
+#
+"""
+Perform fixed-depth IFS repeated duplicate-and-transform.
+"""
+
+import inkex
+
+class IFS(inkex.EffectExtension):
+ NXFORM = 5
+ XFORM_PARAMS = list("ABCDEF")
+
+ def add_arguments(self, pars):
+ pars.add_argument("--tab")
+ pars.add_argument("--iter", type=int, default=3, help="number of iterations")
+ for i in range(self.NXFORM):
+ pars.add_argument("--xform%d"%i, type=inkex.Boolean, default=False, help="enable transformation %d"%i)
+ for p in self.XFORM_PARAMS:
+ pars.add_argument("--%s%d"%(p,i), type=float, help="transformation matrix %d %s"%(i,p))
+
+ def effect(self):
+ xforms = []
+ for i in range(self.NXFORM):
+ if getattr(self.options,'xform%d'%i):
+ t = [getattr(self.options,"%s%d"%(p,i)) for p in self.XFORM_PARAMS]
+ xforms.append(inkex.Transform(t))
+
+ if not xforms:
+ inkex.errormsg(_('There are no transforms to apply'))
+ return False
+
+ if not self.svg.selected:
+ inkex.errormsg(_('There is no selection to duplicate'))
+ return False
+
+ nodes = self.svg.selected.values()
+ grp = inkex.Group('IFS')
+ layer = self.svg.get_current_layer().add(grp)
+
+ for i in range(self.options.iter):
+ n = []
+ for node in nodes:
+ for x in xforms:
+ d = node.copy()
+ d.transform = x * d.transform
+ n.append(d)
+ g = inkex.Group('IFS iter %d'%i)
+ g.add(*n)
+ grp.add(g)
+ nodes = n
+
+ return True
+
+if __name__ == '__main__':
+ IFS().run()
diff --git a/extensions/fablabchemnitz/inkpacking.inx b/extensions/fablabchemnitz/inkpacking.inx
index 2751d9c5..ce9d3bd6 100644
--- a/extensions/fablabchemnitz/inkpacking.inx
+++ b/extensions/fablabchemnitz/inkpacking.inx
@@ -1,5 +1,5 @@
-
+
InkPACKING
fablabchemnitz.de.inkpacking
diff --git a/extensions/fablabchemnitz/longest_continuous_paths.inx b/extensions/fablabchemnitz/longest_continuous_paths.inx
index 358bb847..5cf26568 100644
--- a/extensions/fablabchemnitz/longest_continuous_paths.inx
+++ b/extensions/fablabchemnitz/longest_continuous_paths.inx
@@ -1,5 +1,5 @@
-
+
Longest Continuous Path
fablabchemnitz.de.optimize_paths
0.10
diff --git a/extensions/fablabchemnitz/maze/laby.inx b/extensions/fablabchemnitz/maze/laby.inx
new file mode 100644
index 00000000..534a3e1e
--- /dev/null
+++ b/extensions/fablabchemnitz/maze/laby.inx
@@ -0,0 +1,27 @@
+
+
+ Maze
+ fablabchemnitz.de.maze
+ 20
+ 20
+ 10.0
+ 1.0
+
+
+
+
+
+
+ This script will generate a maze according to a certain algorithm.
+
+ path
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/maze/laby.py b/extensions/fablabchemnitz/maze/laby.py
new file mode 100644
index 00000000..ff55275b
--- /dev/null
+++ b/extensions/fablabchemnitz/maze/laby.py
@@ -0,0 +1,70 @@
+#! /usr/bin/env python3
+
+# this extension is under licence CC-by-sa @ Tiemen DUVILLARD 2020
+# for all questions, comments, bugs: duvillard.tiemen@gmail.com
+
+import inkex
+from lxml import etree
+
+# my maze module
+from maze import *
+
+
+def points_to_svgd(p, close=False):
+ """ convert list of points (x,y) pairs
+ into a SVG path list
+ """
+ f = p[0]
+ p = p[1:]
+ svgd = 'M%.4f,%.4f' % f
+ for x in p:
+ svgd += 'L%.4f,%.4f' % x
+ if close:
+ svgd += 'z'
+ return svgd
+
+
+class Recursive(inkex.Effect):
+
+ def __init__(self):
+ " define how the options are mapped from the inx file "
+ inkex.Effect.__init__(self) # initialize the super class
+
+ self.arg_parser.add_argument("--verti", type=int, default=20, help="Height")
+ self.arg_parser.add_argument("--horiz", type=int, default=20, help="Length")
+ self.arg_parser.add_argument("--size", type=float, default=10.0, help="Cell Size")
+ self.arg_parser.add_argument("--algo", default=1, help="Algorithm")
+ self.arg_parser.add_argument("--width", type=float, default=10.0, help="Line width")
+
+ def effect(self):
+ # basic style
+ style = { 'stroke': "black", "fill":"none", 'stroke-width': self.options.width }
+ # my group of paths
+ topgroup = etree.SubElement(self.svg.get_current_layer(), 'g' )
+
+ lc = self.options.size
+ X = self.options.verti
+ Y = self.options.horiz
+
+ L = Maze(X,Y,self.options.algo)
+
+ for i,j,door in L.verticalDoors():
+ if door:
+ path = points_to_svgd([(lc*(j+1), lc*(i)), (lc*(j+1), lc*(i+1))])
+ mypath_attribs = { 'style': str(inkex.Style(style)), 'd': path }
+ squiggle = etree.SubElement(topgroup, inkex.addNS('path','svg'), mypath_attribs )
+
+ for i,j,door in L.horizontalDoors():
+ if door:
+ path = points_to_svgd([(lc*(j), lc*(i+1)), (lc*(j+1), lc*(i+1))])
+ mypath_attribs = { 'style': str(inkex.Style(style)), 'd': path }
+ squiggle = etree.SubElement(topgroup, inkex.addNS('path','svg'), mypath_attribs )
+
+
+ path = points_to_svgd([(0,0),(0,lc*Y),(lc*X,lc*Y),(lc*X,0)], True)
+ mypath_attribs = { 'style': str(inkex.Style(style)), 'd': path }
+ squiggle = etree.SubElement(topgroup, inkex.addNS('path','svg'), mypath_attribs )
+
+
+if __name__ == '__main__':
+ Recursive().run()
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/maze/maze.py b/extensions/fablabchemnitz/maze/maze.py
new file mode 100644
index 00000000..ecd896bc
--- /dev/null
+++ b/extensions/fablabchemnitz/maze/maze.py
@@ -0,0 +1,537 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# this module is under licence CC-by-sa @ Tiemen DUVILLARD 2020
+# for all questions, comments, bugs: duvillard.tiemen@gmail.com
+
+
+from random import choice, randrange
+
+# Representation of maze :
+# A labyrinth is a set of 2 door panels.
+# For a maze 5*5 :
+# Theory : ## Example :
+# L = [[[a, b, c, d], # vertical doors ## L = [[[1, 1, 1, 1], # vertical doors
+# [e, f, g, h], ## [1, 0, 1, 0],
+# [i, j, k, l], ## [0, 1, 0, 1],
+# [m, n, o, p], ## [0, 0, 0, 0],
+# [q, r, s, t]], ## [0, 1, 0, 0]],
+# [[A, B, C, D, E], # horizontal doors ## [[0, 0, 0, 0, 0], # horizontal doors
+# [F, G, H, I, J], ## [0, 0, 0, 0, 1],
+# [K, L, M, N, O], ## [0, 1, 1, 1, 0],
+# [P, Q, R, S, T]]] ## [0, 1, 0, 1, 1]]]
+# ##
+# ==>> ## ==>>
+# X 0 1 2 3 4 ## X 0 1 2 3 4
+# Y ┌───┬───┬───┬───┬───┐ ## Y ┌───┬───┬───┬───┬───┐
+# 0 │ ∙ a ∙ b ∙ c ∙ d ∙ │ ## 0 │ ∙ │ ∙ │ ∙ │ ∙ │ ∙ │
+# ├─A─┼─B─┼─C─┼─D─┼─E─┤ ## │ │ │ │ │ │
+# 1 │ ∙ e ∙ f ∙ g ∙ h ∙ │ ## 1 │ ∙ │ ∙ ∙ │ ∙ ∙ │
+# ├─F─┼─G─┼─H─┼─I─┼─J─┤ ## │ │ │ │ ┌───┤
+# 2 │ ∙ i ∙ j ∙ k ∙ l ∙ │ ## 2 │ ∙ ∙ │ ∙ ∙ │ ∙ │
+# ├─K─┼─L─┼─M─┼─N─┼─O─┤ ## │ ╶───┴───────┘ │
+# 3 │ ∙ m ∙ n ∙ o ∙ p ∙ │ ## 3 │ ∙ ∙ ∙ ∙ ∙ │
+# ├─P─┼─Q─┼─R─┼─S─┼─T─┤ ## │ ╶───┐ ╶───────│
+# 4 │ ∙ q ∙ r ∙ s ∙ t ∙ │ ## 4 │ ∙ ∙ │ ∙ ∙ ∙ │
+# └───┴───┴───┴───┴───┘ ## └───────┴───────────┘
+
+def kruskal(x, y):
+ global ID
+ ID = 0
+
+ class case:
+ """Little class for union-find"""
+ def __init__(self,x,y):
+ self.x = x
+ self.y = y
+ self.is_repr = True
+ self.repr = None
+ global ID
+ self.ID = ID
+ ID += 1
+
+ def represent(self):
+ if self.is_repr:
+ return self
+ else:
+ L = [self.repr]
+ while not L[0].is_repr:
+ L[0] = L[0].repr
+ return L[0]
+
+ def __eq__(self,other):
+ return self.represent().ID == other.represent().ID
+
+ def union(self,other):
+ a = self.represent()
+ b = other.represent()
+ if not (a == b):
+ b.is_repr = False
+ b.repr = a
+
+ # set of doors
+ doors = []
+ # verticals doors result
+ verti = []
+ # horizontals doors result
+ horiz = []
+ # set of cases
+ cases = []
+ ## initialize vertical doors
+ for j in range(y):
+ l = []
+ for i in range(x-1):
+ l.append(1)
+ doors.append([i,j,'v'])
+ verti.append(l)
+ ## initialize horizontal doors
+ for j in range(y-1):
+ l = []
+ for i in range(x):
+ l.append(1)
+ doors.append([i,j,'h'])
+ horiz.append(l)
+
+ ## initialize cases
+ for i in range(y):
+ l = []
+ for j in range(x):
+ l.append(case(x,y))
+ cases.append(l)
+
+ cpt = x*y -1 # nb of openings in perfect maze
+ while cpt > 0:
+ # I choose a door
+ idoor = randrange(len(doors))
+ dx, dy, dd = doors[idoor]
+ cx1, cy1 = dx, dy
+ if dd == 'h' : cx2, cy2 = cx1, cy1+1
+ else : cx2, cy2 = cx1+1, cy1
+
+ C1 = cases[cy1][cx1]
+ C2 = cases[cy2][cx2]
+ # if the 2 cases separate by my door are not in same set
+ if not C1 == C2:
+ C1.union(C2)
+ cpt -= 1
+ # i modify my result
+ if dd == "v": verti[dy][dx] = 0
+ else: horiz[dy][dx] = 0
+ # i delete the door
+ del doors[idoor]
+
+ return verti,horiz
+
+def recursive_backtrack(x, y):
+ # Initialisation of my variables
+ labyrinthe = []
+ for i in range(y): labyrinthe.append([0] * x)
+ horiz = []
+ for i in range(y-1): horiz.append([1] * x)
+ verti = []
+ for i in range(y): verti.append([1] * (x-1))
+
+ # I choose a random start
+ X_pos = randrange(x)
+ Y_pos = randrange(y)
+ labyrinthe[Y_pos][X_pos] = 1
+ historique = [[X_pos,Y_pos]]
+
+ # I explore a tree with deep parcours
+ while len(historique) != 0:
+ X = historique[-1][0]
+ Y = historique[-1][1]
+
+ possibilite = []
+ if (Y-1 >= 0) and (labyrinthe[Y-1][X] == 0): possibilite.append(0)
+ if (X+1 < x) and (labyrinthe[Y][X+1] == 0): possibilite.append(1)
+ if (Y+1 < y) and (labyrinthe[Y+1][X] == 0): possibilite.append(2)
+ if (X-1 >= 0) and (labyrinthe[Y][X-1] == 0): possibilite.append(3)
+
+ if len(possibilite) == 0:
+ del historique[-1]
+ else:
+ d = choice(possibilite)
+ if d == 0:
+ X1,Y1 = X, Y-1
+ horiz[Y-1][X] = 0
+ if d == 1:
+ X1,Y1 = X+1, Y
+ verti[Y][X] = 0
+ if d == 2:
+ X1,Y1 = X, Y+1
+ horiz[Y][X] = 0
+ if d == 3:
+ X1,Y1 = X-1, Y
+ verti[Y][X-1] = 0
+ labyrinthe[Y1][X1] = 1
+ historique.append([X1,Y1])
+
+ return verti, horiz
+
+# a empty maze
+def empty(x, y):
+ verti = []
+ for i in range(y):
+ verti.append([0] * (x-1))
+
+ horiz = []
+ for i in range(y-1):
+ horiz.append([0] * x)
+ return verti, horiz
+
+# a full maze
+def full(x, y):
+ verti = []
+ for i in range(y):
+ verti.append([1] * (x-1))
+
+ horiz = []
+ for i in range(y-1):
+ horiz.append([1] * x)
+ return verti, horiz
+
+
+
+class Maze:
+ """This Class define a Maze object"""
+ def __init__(self, X=5, Y=5, algorithm="empty"):
+ """Use : m = Maze(X:int,Y:int, algorithm:string)"""
+ self.X = X # width
+ self.Y = Y # height
+ self.verti = [] # table of vertical doors
+ self.horiz = [] # table of horizontal doors
+
+
+ if algorithm in ("kruskal","K") :
+ self.verti, self.horiz = kruskal(self.X,self.Y)
+ self.doors = self.nbDoors()
+ self.algorithm = "kruskal"
+ self.perfect = True
+
+ elif algorithm in ("recursive_backtrack","RB") :
+ self.verti, self.horiz = recursive_backtrack(self.X,self.Y)
+ self.doors = self.nbDoors()
+ self.algorithm = "recursive_backtrack"
+ self.perfect = True
+
+ elif algorithm in ("empty","E") :
+ self.verti, self.horiz = empty(self.X,self.Y)
+ self.doors = self.nbDoors()
+ self.algorithm = "empty"
+ self.perfect = False
+
+ elif algorithm in ("full","F") :
+ self.verti, self.horiz = full(self.X,self.Y)
+ self.doors = self.nbDoors()
+ self.algorithm = "full"
+ self.perfect = False
+
+ else :
+ raise("Algorithm not recognized. Algorithm must be in ['kruskal','recursive_backtrack','empty','full']")
+
+ def __str__(self):
+ """Return information about maze"""
+ s = "Size of maze : {}*{}\nGenerate by : {}\nNb of doors : {}\nPerfect : {}"
+ s = s.format(self.X,self.Y,self.algorithm,self.doors,self.perfect)
+ return s
+
+ def nbDoors(self):
+ """Return number of doors in my maze. If maze is perfect, it equals to (self.X-1)*(self.Y-1)"""
+ S = 0
+ for k in self.verti: S += sum(k)
+ for k in self.horiz: S += sum(k)
+ return S
+
+ def verticalDoors(self):
+ """Iterate on vertical doors"""
+ for i in range(len(self.verti)):
+ for j in range(len(self.verti[i])):
+ yield i,j, bool(self.verti[i][j])
+
+ def horizontalDoors(self):
+ """Iterate on horizontal doors"""
+ for i in range(len(self.horiz)):
+ for j in range(len(self.horiz[i])):
+ yield i,j, bool(self.horiz[i][j])
+
+ def canMove(self, x, y, direction) :
+ """return if i can move in a direction since (x,y) """
+ if direction in ["down","d",0] : # down
+ if y == self.Y-1 :return False
+ return not self.horiz[y][x]
+ if direction in ["up","u",2] : # up
+ if y == 0 : return False
+ return not self.horiz[y-1][x]
+ if direction in ["left","l",3] : # left
+ if x == 0 : return False
+ return not self.verti[y][x-1]
+ if direction in ["right","r",1] : # right
+ if x == self.X-1 : return False
+ return not self.verti[y][x]
+ return False
+
+ def move(self, x, y, direction) :
+ """return new cord after one move in direction"""
+ if direction in ["down","d",0] : # down
+ return x,y+1
+ if direction in ["up","u",2] : # up
+ return x,y-1
+ if direction in ["left","l",3] : # left
+ return x-1,y
+ if direction in ["right","r",1] : # right
+ return x+1,y
+ return x,y
+
+ def toSquare(self):
+ """return another representation of maze, a List of List of case in {0,1} """
+ RET = []
+ for i in range(self.Y*2+1):
+ l = []
+ for j in range(self.X*2+1):
+ if i == 0 or i == self.Y*2 or j == 0 or j == self.X*2 :
+ l.append(1)
+ else :
+ if (i%2==0 and j%2==0) :
+ l.append(1)
+ elif (i%2==1 and j%2==1):
+ l.append(0)
+ elif (i%2==0 and j%2==1):
+ l.append(self.horiz[(i-1)//2][(j-1)//2])
+ elif (i%2==1 and j%2==0):
+ l.append(self.verti[(i-1)//2][(j-1)//2])
+ RET.append(l)
+ return RET
+
+ def solve(self, xa, ya, xb, yb):
+ """return a list of direction for link (xa,ya) -> (xb,yb). Null"""
+ Sol = []
+ Pile = [(xa,ya)]
+ laby = []
+ for i in range(self.Y):
+ laby.append(['new'] * self.X)
+
+ while Pile[-1] != (xb,yb) and len(Pile) != 0:
+ pos = Pile[-1]
+ x,y = pos[0],pos[1]
+ if laby[y][x] == 'new':
+ possibilite = []
+ for d in ['down',"up","left","right"]:
+ if self.canMove(pos[0],pos[1],d) :
+ nx,ny = self.move(pos[0],pos[1],d)
+ if laby[ny][nx] == 'new' :
+ possibilite.append(d)
+
+ if len(possibilite) == 0:
+ laby[y][x] = "old"
+ else :
+ laby[y][x] = possibilite
+ elif laby[y][x] == "old":
+ del Pile[-1]
+ del Sol[-1]
+ elif type(laby[y][x]) == list:
+ if len(laby[y][x]) == 0:
+ laby[y][x] = "old"
+ else:
+ ichoix = randrange(len(laby[y][x]))
+ choix = laby[y][x][ichoix]
+ Pile.append(self.move(x,y,choix))
+ Sol.append(choix)
+ del laby[y][x][ichoix]
+
+ return Sol
+
+ def save(self):
+ """Return a string contain's all information. it can be save in file, print, etc.."""
+ s = "{};{};{};{};{};{};{}".format(self.X, self.Y, self.doors, self.algorithm, self.perfect, self.verti, self.horiz)
+ return s
+
+ def load(self, st):
+ """Load a Maze since a string of format save"""
+ cont = st.split(";")
+ self.X = int(cont[0])
+ self.Y = int(cont[1])
+ self.doors = int(cont[2])
+ self.algorithm = cont[3]
+ self.perfect = bool(cont[4])
+ self.verti = cont[5].split("], [")
+ for i in range(len(self.verti)):
+ self.verti[i] = self.verti[i].split(',')
+ for j in range(len(self.verti[i])):
+ self.verti[i][j] = self.verti[i][j].replace("[","")
+ self.verti[i][j] = self.verti[i][j].replace("]","")
+ self.verti[i][j] = self.verti[i][j].replace(" ","")
+ self.verti[i][j] = int(self.verti[i][j])
+
+ self.horiz = cont[6].split("], [")
+ for i in range(len(self.horiz)):
+ self.horiz[i] = self.horiz[i].split(',')
+ for j in range(len(self.horiz[i])):
+ self.horiz[i][j] = self.horiz[i][j].replace("[","")
+ self.horiz[i][j] = self.horiz[i][j].replace("]","")
+ self.horiz[i][j] = self.horiz[i][j].replace(" ","")
+ self.horiz[i][j] = int(self.horiz[i][j])
+
+ def toTxt(self, centre=False, coord=False, basic= False):
+ """return a txt representation of maze in ASCII art"""
+ if type(centre) == bool:
+ if centre : centre = "*"
+ else : centre = " "
+ else:
+ centre = centre[0]
+
+ if basic :
+ if coord:
+ R = " X"
+ for i in range(self.X):
+ R += " {} ".format(i%10)
+ R += "\n"
+ R += "Y +"
+ r = "0 |"
+ else:
+ R = "+"
+ r = "|"
+ for j in range(self.X-1):
+ if self.verti[0][j]:
+ R += "---+"
+ r += " {} |".format(centre)
+ else:
+ R += "----"
+ r += " {} ".format(centre)
+ R += "---+\n"
+ r += " {} |\n".format(centre)
+ R += r
+ for i in range(self.Y-1):
+ if coord:
+ if self.horiz[i][0]:
+ l1 = " +"
+ else:
+ l1 = " |"
+ l2 = "{} |".format((i+1)%10)
+ else:
+ if self.horiz[i][0]:
+ l1 = "+"
+ else:
+ l1 = "|"
+ l2 = "|"
+ for j in range(self.X-1):
+ inter = "+"
+ if self.horiz[i][j] and self.horiz[i][j+1] and not self.verti[i][j] and not self.verti[i+1][j] : inter = "-"
+ if not self.horiz[i][j] and not self.horiz[i][j+1] and self.verti[i][j] and self.verti[i+1][j] : inter = "|"
+ if not self.horiz[i][j] and not self.horiz[i][j+1] and not self.verti[i][j] and not self.verti[i+1][j] : inter = " "
+
+ if self.horiz[i][j]:
+ l1 += "---"+inter
+ else:
+ l1 += " "+inter
+
+ if self.verti[i+1][j]:
+ l2 += " {} |".format(centre)
+ else:
+ l2 += " {} ".format(centre)
+ if self.horiz[i][-1]:
+ l1 += "---+"
+ else:
+ l1 += " |"
+ l2 += " {} |".format(centre)
+
+ R += l1 + "\n"
+ R += l2 + "\n"
+ if coord:
+ r = " +"
+ else:
+ r = "+"
+ for j in range(self.X-1):
+ if self.verti[-1][j]:
+ r += "---+"
+ else:
+ r += "----"
+ r += "---+"
+ R += r
+ return R
+
+ else :
+ if coord:
+ R = " X"
+ for i in range(self.X):
+ R += " {} ".format(i%10)
+ R += "\n"
+ R += "Y ┌"
+ r = "0 │"
+ else:
+ R = "┌"
+ r = "│"
+ for j in range(self.X-1):
+ if self.verti[0][j]:
+ R += "───┬"
+ r += " {} │".format(centre)
+ else:
+ R += "────"
+ r += " {} ".format(centre)
+ R += "───┐\n"
+ r += " {} │\n".format(centre)
+ R += r
+ for i in range(self.Y-1):
+ if coord:
+ if self.horiz[i][0]:
+ l1 = " ├"
+ else:
+ l1 = " │"
+ l2 = "{} │".format((i+1)%10)
+ else:
+ if self.horiz[i][0]:
+ l1 = "├"
+ else:
+ l1 = "│"
+ l2 = "│"
+ for j in range(self.X-1):
+ # 4 door
+ if self.horiz[i][j] and self.horiz[i][j+1] and self.verti[i][j] and self.verti[i+1][j] : inter = "┼"
+ # 3 door
+ if self.horiz[i][j] and self.horiz[i][j+1] and self.verti[i][j] and not self.verti[i+1][j] : inter = "┴"
+ if self.horiz[i][j] and self.horiz[i][j+1] and not self.verti[i][j] and self.verti[i+1][j] : inter = "┬"
+ if self.horiz[i][j] and not self.horiz[i][j+1] and self.verti[i][j] and self.verti[i+1][j] : inter = "┤"
+ if not self.horiz[i][j] and self.horiz[i][j+1] and self.verti[i][j] and self.verti[i+1][j] : inter = "├"
+ # 2 door
+ if self.horiz[i][j] and self.horiz[i][j+1] and not self.verti[i][j] and not self.verti[i+1][j] : inter = "─"
+ if self.horiz[i][j] and not self.horiz[i][j+1] and self.verti[i][j] and not self.verti[i+1][j] : inter = "┘"
+ if not self.horiz[i][j] and self.horiz[i][j+1] and self.verti[i][j] and not self.verti[i+1][j] : inter = "└"
+ if self.horiz[i][j] and not self.horiz[i][j+1] and not self.verti[i][j] and self.verti[i+1][j] : inter = "┐"
+ if not self.horiz[i][j] and self.horiz[i][j+1] and not self.verti[i][j] and self.verti[i+1][j] : inter = "┌"
+ if not self.horiz[i][j] and not self.horiz[i][j+1] and self.verti[i][j] and self.verti[i+1][j] : inter = "│"
+ # 1 door
+ if not self.horiz[i][j] and not self.horiz[i][j+1] and not self.verti[i][j] and self.verti[i+1][j] : inter = "╷"
+ if not self.horiz[i][j] and not self.horiz[i][j+1] and self.verti[i][j] and not self.verti[i+1][j] : inter = "╵"
+ if not self.horiz[i][j] and self.horiz[i][j+1] and not self.verti[i][j] and not self.verti[i+1][j] : inter = "╶"
+ if self.horiz[i][j] and not self.horiz[i][j+1] and not self.verti[i][j] and not self.verti[i+1][j] : inter = "╴"
+ # 0 door
+ if not self.horiz[i][j] and not self.horiz[i][j+1] and not self.verti[i][j] and not self.verti[i+1][j] : inter = " "
+ if self.horiz[i][j]:
+ l1 += "───"+inter
+ else:
+ l1 += " "+inter
+
+ if self.verti[i+1][j]:
+ l2 += " {} │".format(centre)
+ else:
+ l2 += " {} ".format(centre)
+ if self.horiz[i][-1]:
+ l1 += "───┤"
+ else:
+ l1 += " │"
+ l2 += " {} │".format(centre)
+
+ R += l1 + "\n"
+ R += l2 + "\n"
+ if coord:
+ r = " └"
+ else:
+ r = "└"
+ for j in range(self.X-1):
+ if self.verti[-1][j]:
+ r += "───┴"
+ else:
+ r += "────"
+ r += "───┘"
+ R += r
+ return R
diff --git a/extensions/fablabchemnitz/papercraft_unfold/papercraft_unfold.inx b/extensions/fablabchemnitz/papercraft_unfold/papercraft_unfold.inx
index 157c0308..bebcea00 100644
--- a/extensions/fablabchemnitz/papercraft_unfold/papercraft_unfold.inx
+++ b/extensions/fablabchemnitz/papercraft_unfold/papercraft_unfold.inx
@@ -35,7 +35,7 @@
false
1.0
-
+
@@ -53,4 +53,4 @@
-
\ No newline at end of file
+
diff --git a/extensions/fablabchemnitz/removeDuplicateNodes.inx b/extensions/fablabchemnitz/removeDuplicateNodes.inx
new file mode 100644
index 00000000..6179261a
--- /dev/null
+++ b/extensions/fablabchemnitz/removeDuplicateNodes.inx
@@ -0,0 +1,22 @@
+
+
+ Purge Duplicate Path Nodes
+ fablabchemnitz.de.purge_duplicate_path_nodes
+ Remove duplicate nodes from selected paths.
+ false
+ 0.01
+ false
+ 0.01
+ Unit as defined in document (File->Document Properties).
+
+ path
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/removeDuplicateNodes.py b/extensions/fablabchemnitz/removeDuplicateNodes.py
new file mode 100644
index 00000000..3653474b
--- /dev/null
+++ b/extensions/fablabchemnitz/removeDuplicateNodes.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python
+# coding=utf-8
+#
+# Copyright (C) 2020 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.
+"""
+Remove duplicate nodes or join nodes with distance less than specified.
+Optionally join start node with end node of each subpath if distance less than specified.
+"""
+
+import inkex
+from inkex import bezier, PathElement, CubicSuperPath
+
+class removeDuplicateNodes(inkex.EffectExtension):
+
+ def add_arguments(self, pars):
+ pars.add_argument("--minlength", default="0")
+ pars.add_argument("--minUse", type=inkex.Boolean, default=False)
+ pars.add_argument("--maxdist", default="0")
+ pars.add_argument("--joinEnd", type=inkex.Boolean, default=False)
+
+ """Remove duplicate nodes"""
+ def effect(self):
+ for id, elem in self.svg.selected.items():
+ minlength=float(self.options.minlength)
+ maxdist=float(self.options.maxdist)
+ if self.options.minUse == False:
+ minlength=0
+ if self.options.joinEnd == False:
+ maxdist=-1
+
+ #register which subpaths are closed
+ dList=str(elem.path).upper().split(' M')
+ closed=[""]
+ l=0
+ for sub in dList:
+ if dList[l].find("Z") > -1:
+ closed.append(" Z ")
+ else:
+ closed.append("")
+ l+=1
+ closed.pop(0)
+
+ new = []
+ s=0
+ for sub in elem.path.to_superpath():
+ new.append([sub[0]])
+ i = 1
+ while i <= len(sub) - 1:
+ length = bezier.cspseglength(new[-1][-1], sub[i]) #curve length
+ if length >= minlength:
+ new[-1].append(sub[i])
+ else:
+ #average last node xy with this node xy and set this further node to last
+ new[-1][-1][1][0]= 0.5*(new[-1][-1][1][0]+sub[i][1][0])
+ new[-1][-1][1][1]= 0.5*(new[-1][-1][1][1]+sub[i][1][1])
+ new[-1][-1][-1]=sub[i][-1]
+ i += 1
+
+ if maxdist > -1:
+ #calculate distance between first and last node
+ #if <= maxdist set closed[i] to "Z "
+ last=new[-1][-1]
+ length = bezier.cspseglength(new[-1][-1], sub[0])
+ if length <= maxdist:
+ newStartEnd=[0.5*(new[-1][-1][-1][0]+new[0][0][0][0]),0.5*(new[-1][-1][-1][1]+new[0][0][0][1])]
+ new[0][0][0]=newStartEnd
+ new[0][0][1]=newStartEnd
+ new[-1][-1][1]=newStartEnd
+ new[-1][-1][2]=newStartEnd
+ closed[s]=" Z "
+ s+=1
+
+ elem.path = CubicSuperPath(new).to_path(curves_only=True)
+
+ #reset z to the originally closed paths (z lost in cubicsuperpath)
+ temppath=str(elem.path.to_absolute()).split('M ')
+ temppath.pop(0)
+ newPath=''
+ l=0
+ for sub in temppath:
+ newPath=newPath+'M '+temppath[l]+closed[l]
+ l+=1
+ elem.path=newPath
+
+if __name__ == '__main__':
+ removeDuplicateNodes().run()
+
diff --git a/extensions/fablabchemnitz/render_scale.inx b/extensions/fablabchemnitz/render_scale.inx
index 5e4be0ae..7b09f905 100644
--- a/extensions/fablabchemnitz/render_scale.inx
+++ b/extensions/fablabchemnitz/render_scale.inx
@@ -1,5 +1,5 @@
-
+
Vertical / Horizontal Scale
fablabchemnitz.de.render_scale
diff --git a/extensions/fablabchemnitz/shapes/arakne_xy.py b/extensions/fablabchemnitz/shapes/arakne_xy.py
new file mode 100644
index 00000000..e0ba99cd
--- /dev/null
+++ b/extensions/fablabchemnitz/shapes/arakne_xy.py
@@ -0,0 +1,382 @@
+#!/usr/bin/env python3
+'''
+Copyright (C) 2015 Paco Garcia, www.arakne.es
+
+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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+'''
+import os, sys, tempfile, webbrowser, math, inkex, simplestyle, simpletransform
+from lxml import etree
+
+def info(s, newLine="\n"):
+ sys.stderr.write(s)
+ #sys.stderr.write(s.encode("UTF-8"))
+ sys.stderr.write(newLine)
+
+def tern(condition,val1,val2):
+ return val1 if condition else val2
+
+def _rads(n):
+ return math.radians(n)
+
+def pow2(n):
+ return math.pow(n, 2)
+
+# calcula la hipotenusa dados los catetos
+def triHipo(catA, catB):
+ return math.sqrt(pow2(catA) + pow2(catB))
+
+# calcula el cateto dada la hipotenusa y el otro cateto
+def triCat(Hipo, catA):
+ return math.sqrt(pow2(Hipo) - pow2(catA))
+
+class XY:
+ """A class for work with 2d points"""
+ def __init__(self, *args, **kwargs):
+ self.co=[0.0,0.0]
+ lArgs=len(args)
+ if lArgs>0:
+ if lArgs==1:
+ if type(args[0])==XY:
+ self.co=args[0].co
+ else:
+ self.co=[args[0],args[0]]
+ if lArgs>1:
+ self.co=[args[0],args[1]]
+ def __add__(self,xy):
+ self.co = [self.co[0] + xy.co[0],self.co[1] + xy.co[1]]
+ return self
+ def __sub__(self,xy):
+ self.co=[self.co[0] - xy.co[0], self.co[1] - xy.co[1]]
+ return self
+ def __eq__(self, xy):
+ return (self.co[0] == xy.x and self.co[1] == xy.y)
+
+ def sub(self,xy):
+ self.__sub__(xy)
+ return self
+ def mul(self,xy):
+ if type(xy)==XY:
+ co=[self.co[0] * xy.co[0],self.co[1] * xy.co[1]]
+ else:
+ co=[self.co[0] * xy,self.co[1] * xy]
+ self.co = co
+ return self
+
+ def div(self,xy):
+ if type(xy)==XY:
+ co=[self.co[0] / xy.co[0], self.co[1] / xy.co[1]]
+ else:
+ co=[self.co[0] / xy, self.co[1] / xy]
+ self.co = co
+ return self
+
+ def vlength(self):
+ #return math.sqrt((self.co[0]*self.co[0])+(self.co[1]*self.co[1]))
+ return triHipo(self.co[0], self.co[1])
+
+ def rot(self,ang):
+ x,y,sa,ca= (self.co[0], self.co[1], math.sin(ang), math.cos(ang))
+ self.co=[ca * x - sa * y, sa * x + ca * y]
+ return self
+
+ def Rot(self,p,r):
+ self.co=[math.cos(r)*p, math.sin(r)*p]
+ return self
+
+ def rotate(self,rot,cX=0.0,cY=0.0):
+ cosRot = math.cos(rot)
+ px = cX + (self.x-cX) * cosRot - (self.y-cY) * math.sin(rot)
+ py = cY + (self.x-cX) * math.sin(rot) + (self.y-cY) * cosRot
+ self.co = [px,py]
+ return self
+
+ def rotateD(self,rot,cX=0.0,cY=0.0):
+ self.rotate(_rads(rot),cX,cY)
+ return self
+
+ def VDist(self,V2):
+ tmp = XY(self.co[0],self.co[1])
+ tmp = tmp.sub(V2)
+ return tmp.vlength()
+
+ def st(self):
+ return str(self.co[0])+','+str(self.co[1])
+
+ @property
+ def x(self):
+ return self.co[0]
+
+ @property
+ def sx(self):
+ return str(self.co[0])
+
+ @property
+ def y(self):
+ return self.co[1]
+
+ @property
+ def sy(self):
+ return str(self.co[1])
+
+ def hipo(self,xy):
+ #return math.sqrt(math.pow(self.x-xy.x,2) + math.pow(self.y-xy.y,2) )
+ return triHipo(self.x-xy.x, self.y-xy.y)
+ def angBetween2Lines(self,p1,p2): # pC punto comun
+ return math.atan2(self.y - p1.y, self.x - p1.x) - math.atan2(self.y - p2.y, self.x - p2.x)
+ def getAngle(self,b):
+ return math.atan2(b.y - self.y, b.x - self.x)
+ def getAngleD(self,b):
+ return math.degrees(math.atan2(b.y - self.y, b.x - self.x))
+ # translada un punto hacia otro
+ def atPercent(self, p2, percent):
+ self.co = [(p2.x - self.x) * percent + self.x,(p2.y-self.y) * percent + self.y]
+ return self
+
+ def atMid(self, p2):
+ return self.atPercent(p2,0.5)
+
+# ________________________________________________________________
+# ________________________________________________________________
+# ________________________________________________________________
+class bezpnt(object):
+ def __init__(self,pfixed=None,pprev=None,pnext=None):
+ if isinstance(pfixed, list):
+ self.fixed = XY(pfixed[0],pfixed[1])
+ else:
+ self.fixed = pfixed
+ if isinstance(pprev, list):
+ self.prev = XY(pprev[0],pprev[1])
+ else:
+ self.prev = pprev
+ if isinstance(pnext, list):
+ self.next = XY(pnext[0],pnext[1])
+ else:
+ self.next = pnext
+ return
+
+ def translate(self,x,y):
+ self.fixed + XY(x,y)
+ if self.prev!=None:self.prev + XY(x,y)
+ if self.next!=None:self.next + XY(x,y)
+ return self
+
+ def scale(self,x=1.0,y=1.0):
+ self.fixed.scale(x,y)
+ if self.prev!=None:self.prev.scale(x,y)
+ if self.next!=None:self.next.scale(x,y)
+ return self
+
+ def rotate(self,rot,cX=0.0,cY=0.0):
+ self.fixed.rotate(rot,cX,cY)
+ if self.prev!=None:self.prev.rotate(rot,cX,cY)
+ if self.next!=None:self.next.rotate(rot,cX,cY)
+ return sel
+
+ def skew(self,rotx,roty,cX=0.0,cY=0.0):
+ self.fixed.skew(rotx,roty,cX,cY)
+ if self.prev!=None:self.prev.skew(rotx,roty,cX,cY)
+ if self.next!=None:self.next.skew(rotx,roty,cX,cY)
+ return self
+ def copy(self,bez2):
+ try:
+ self.fixed=XY().copy(bez2.fixed)
+ self.prev = None if bez2.prev == None else XY().copy(bez2.prev)
+ self.next = None if bez2.next == None else XY().copy(bez2.next)
+ except Exception:
+ gimp.message(str(Exception))
+ return self
+ def arrXY(self):
+ pts=[]
+ if self.prev == None:
+ pts+=self.fixed.arrXY(1)
+ else:
+ pts+=self.prev.arrXY(1)
+ pts+=self.fixed.arrXY(1)
+ if self.next==None:
+ pts+=self.fixed.arrXY(1)
+ else:
+ pts+=self.next.arrXY(1)
+ return pts
+ def Prev(self):
+ p = self.prev
+ if p==None: p=self.fixed
+ return p
+ def Next(self):
+ p = self.next
+ if p==None: p=self.fixed
+ return p
+ def Fixed(self):
+ return self.fixed
+ def flip(self):
+ p=self.prev
+ n=self.next
+ self.prev=n
+ self.next=p
+
+def createSmallArcBez(r, a1, a2,rot):
+ a = (a2 - a1) * 0.5
+ p4 = XY(r * math.cos(a), r * math.sin(a))
+ p1 = XY(p4.x, -p4.y)
+ k = 0.5522847498
+ f = k * math.tan(a)
+ p2 = XY(p1.x + f * p4.y, p1.y + f * p4.x)
+ p3 = XY(p2.x,-p2.y)
+ ar = a + a1
+ P1 = XY(r * math.cos(a1), r * math.sin(a1)).rotate(rot)
+ P2 = XY(p2.x, p2.y).rotate(ar).rotate(rot)
+ P3 = XY(p3.x, p3.y).rotate(ar).rotate(rot)
+ P4 = XY(r * math.cos(a2),r * math.sin(a2)).rotate(rot)
+ B1=bezpnt(P1,None,P2)
+ B2=bezpnt(P4,P3)
+ return [B1,B2]
+
+def createArcBez(rad, sAng, eAng):
+ EPSILON = 0.0000000001
+ bezs =[]
+ if eAng < sAng:
+ eAng += 360.0
+ sAng = _rads(sAng)
+ eAng = _rads(eAng)
+ rot = sAng
+ sAng = _rads(0)
+ eAng = eAng - rot
+ pi2 = math.pi * 2
+ sAng, eAng = (sAng % pi2, eAng % pi2)
+ pi_2 = math.pi * 0.5
+ sign = 1 if (sAng < eAng) else -1
+ a1 = sAng
+ totAng = min(math.pi * 2, abs(eAng - sAng))
+ while (totAng > EPSILON):
+ a2 = a1 + sign * min(totAng, pi_2)
+ bezs.extend(createSmallArcBez(rad, a1, a2,rot))
+ totAng = totAng - abs(a2 - a1)
+ a1 = a2
+ return bezs
+
+def bezs2XYList(arc1, transform = None):
+ pnts=[]
+ bezs=[]
+ for aa in arc1:
+ if aa.prev is not None:
+ bezs.append(XY(aa.prev))
+ bezs.append(XY(aa.fixed))
+
+ if aa.next is not None:
+ bezs.append(XY(aa.next))
+
+ for i in range(len(bezs)):
+ v = bezs[i]
+ if transform:
+ v = v + transform
+ if i == 0:
+ pnts.append(v)
+ else:
+ v2=pnts[-1]
+ if (v2.x != v.x or v2.y != v.y):
+ pnts.append(XY(v))
+ a=len(pnts)
+ return pnts
+
+def XYList(lst, rot = 0.0, add = None):
+ verts=[]
+ for nn in range(len(lst)):
+ v = lst[nn]
+ if rot != 0.0: v = v.rotate(rot)
+ if add: v = v + add
+ verts.append([v.x,v.y])
+ return verts
+
+def XYListSt(lst, rot = 0.0, add = None):
+ D2 = ""
+ for nn in range(len(lst)):
+ v = lst[nn]
+ if rot != 0.0: v = v.rotate(rot)
+ if add: v = v + add
+ D2 += "%s%s " % (tern(nn==1,"C",""), v.st())
+ return D2
+
+# circle by quadrants, A: 0>90, B: 90>180, C: 180>270, D: 270>360
+def circQ(p,r,abcd="ABCD",inverse=0,xtra=None):
+ aa = r * 0.551915024494
+ parts={
+ 'A':[XY(0,-r),XY(aa,-r), XY(r, -aa),XY(r,0)],
+ 'B':[XY(r,0), XY(r, aa), XY(aa, r),XY(0,r)],
+ 'C':[XY(0,r), XY(-aa,r), XY(-r, aa),XY(-r,0)],
+ 'D':[XY(-r,0),XY(-r,-aa),XY(-aa,-r),XY(0,-r)]}
+ pA = [XY(p)+N for N in parts[abcd[0]]]
+ for aa in abcd[1:]:
+ pA = pA + [XY(p)+N for N in parts[aa][1:]]
+ if inverse==1: pA.reverse()
+ listA = XYList(pA)
+ if xtra:
+ for n in xtra:
+ listA[n].extend(xtra[n])
+ return listA
+
+def circleInCircle(c1,r1,c2,r2):
+ return tern((r1 > (c1.hipo(c2) + r2)),True,False)
+
+def polar2cartesian(cX, cY, rad, ang):
+ return XY(cX + (rad * math.cos(ang)), cY + (rad * math.sin(ang)))
+
+def setArc(x, y, rad, ang1, ang2, first):
+ start = polar2cartesian(x, y, rad, ang2)
+ end = polar2cartesian(x, y, rad, ang1)
+ arcSweep = "0" if (ang2 - ang1 <= 180) else "1"
+ d = " A%f,%f 0 %s 0 %s" % (rad, rad, arcSweep, end.st())
+ if first == 1:
+ d = "M" + start.st() + " " + d
+ return d
+
+def setArcs(x, y, rad, ang1, ang2, first):
+ if ang2 < ang1:
+ m = setArc(x, y, rad, 0, _rads(ang2), first)
+ m = m +" "+ setArc(x, y, rad, _rads(ang1), _rads(360), 0)
+ else:
+ m = setArc(x, y, rad, _rads(ang1), _rads(ang2), first)
+ return m
+
+def addChild(padre, type, props):
+ hijo = etree.SubElement(padre, inkex.addNS(type,'svg'))
+ for n in props:
+ hijo.set(n,props[n])
+ return hijo
+
+def svgCircle(padre, r, cx, cy):
+ return addChild(padre,'circle' ,{'r':str(r), 'cx': str(cx), 'cy': str(cy)})
+
+def circleInscribedInTri(p1,p2,p3):
+ d1, d2, d3 = [n.vlength() for n in [XY(p3)-p2, XY(p1)-p3, XY(p2)-p1]]
+ p = d1 + d2 + d3
+ centro = XY( (p1.x*d1 + p2.x*d2 + p3.x*d3) / p, (p1.y*d1 + p2.y*d2 + p3.y*d3) / p)
+ p = p / 2.0
+ radius = math.sqrt(p * (p - d1) * (p - d2) * (p - d3))/p
+ return (radius, centro.x, centro.y)
+
+def TriInscribedInCircle(p1,p2,p3):
+ x12, x13, x31, x21 = (p1.x - p2.x, p1.x - p3.x, p3.x - p1.x, p2.x - p1.x)
+ y12, y13, y31, y21 = (p1.y - p2.y, p1.y - p3.y, p3.y - p1.y, p2.y - p1.y)
+ sx13 = pow2(p1.x) - pow2(p3.x)
+ sy13 = pow2(p1.y) - pow2(p3.y)
+ sx21 = pow2(p2.x) - pow2(p1.x)
+ sy21 = pow2(p2.y) - pow2(p1.y)
+ f = (sx13*x12 + sy13*x12 + sx21*x13 + sy21*x13) / (2 * (y31*x12 - y21*x13))
+ g = (sx13*y12 + sy13*y12 + sx21*y13 + sy21*y13) / (2 * (x31*y12 - x21*y13))
+ c = - pow2(p1.x) - pow2(p1.y) - 2*g*p1.x - 2*f*p1.y
+ r = math.sqrt(pow2(-g) + pow2(-f) - c)
+ return (r, -g, -f)
+
+# 243
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/shapes/shapes.inx b/extensions/fablabchemnitz/shapes/shapes.inx
new file mode 100644
index 00000000..f664e836
--- /dev/null
+++ b/extensions/fablabchemnitz/shapes/shapes.inx
@@ -0,0 +1,197 @@
+
+
+ Shapes
+ fablabchemnitz.de.shapes
+ Create shapes using the bounding box or the node position of the selected objects
+
+
+
+
+
+
+
+ - Chamfer
+ - Rect inside
+ - Round inside
+ - Star
+ - Crossed corners quads
+ - Crossed corners tris
+ - Crossed corners round
+ - Pillow
+ - Rect spiral
+
+
+
+
+
+ false
+ 20
+
+
+
+
+
+
+
+ - Rombus
+ - Cross
+ - Star
+ - Pillow
+
+
+
+
+
+ 20
+
+
+
+
+
+
+
+ - Triangle
+ - Rectangle
+ - Square
+ - Rounded
+ - Wave
+
+ 2.0
+ 0.0
+ 0.0
+
+
+
+ - Outside
+ - Inside
+ - Alternate
+
+
+ - Predefined
+ - None
+ - Outside
+ - Inside
+ - Alternate
+
+
+ - Predefined
+ - None
+ - Outside
+ - Inside
+ - Alternate
+
+
+ - Predefined
+ - None
+ - Outside
+ - Inside
+ - Alternate
+
+
+ - Predefined
+ - None
+ - Outside
+ - Inside
+ - Alternate
+
+
+
+
+
+
+ - Isosceles
+ - Equilateral
+ - Rectangle
+ - From 3 nodes: Inscribed triangle
+ - From 3 nodes: Inscribed circle
+ - From 3 nodes: Bounding circle
+
+ false
+ false
+
+
+
+ - Filled
+ - Stick
+
+ 20.0
+ 40.0
+ 10.0
+
+
+
+
+
+ - Rect
+ - Blob
+ - Oval
+
+ 0.0
+
+
+
+
+ - Square
+ - Circle
+ - Numerate
+ - Nodes coordinates
+ - Object
+
+
+
+
+ 3
+
+
+
+
+
+ - Center
+ - Left
+ - Right
+
+
+ - Center
+ - Bottom
+ - Top
+
+
+
+ 0.00
+ false
+
+ 10
+ 6
+
+
+
+
+ 0.0
+
+
+
+ - px
+ - pt
+ - in
+ - cm
+ - mm
+
+
+
+
+ false
+ false
+
+ false
+
+ all
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/shapes/shapes.py b/extensions/fablabchemnitz/shapes/shapes.py
new file mode 100644
index 00000000..299faf5d
--- /dev/null
+++ b/extensions/fablabchemnitz/shapes/shapes.py
@@ -0,0 +1,766 @@
+#!/usr/bin/env python
+'''
+shapes_1.py
+
+Copyright (C) 2015 - 2020 Paco Garcia, www.arakne.es
+
+2017_07_30: added crossed corners
+ copy class of original object if exists
+2017_08_09: rombus moved to From corners tab
+2017_08_17: join circles not need boolen operations now
+ join circles added Oval
+2017_08_25: fixed error in objects without style
+
+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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+-----------------------
+'''
+import locale, os, sys, tempfile, webbrowser, math, simplepath
+from lxml import etree
+
+try:
+ from subprocess import Popen, PIPE
+ bsubprocess = True
+except:
+ bsubprocess = False
+
+import inkex
+from inkex.transforms import BoundingBox
+
+from arakne_xy import *
+
+defStyle = [['stroke-width','0.5'],['fill','#f0ff00'],['stroke','#ff0000']]
+
+locale.setlocale(locale.LC_ALL, '')
+
+# ####################################################3
+
+def calcCircle(pt1, pt2, pt3):
+ D_a = XY(pt2)-pt1
+ D_b = XY(pt3)-pt2
+ m_C = XY()
+ Min = 0.000000001
+
+ m_dRadius= 0
+
+ if (abs(D_a.x) <= Min and abs(D_b.y) <= Min):
+ m_C= XY(0.5*(pt2.x + pt3.x), 0.5*(pt1.y + pt2.y))
+ m_dRadius= vlength(m_C,pt1) # calc. radius
+
+ aSlope = D_a.y / D_a.x
+
+ if D_b.x == 0:
+ bSlope = D_b.y
+ else:
+ bSlope = D_b.y / D_b.x
+ if (abs(aSlope-bSlope) <= Min): # checking if given points are colinear.
+ return [-1,-1,-1]
+ # calc center
+ m_Cx= (aSlope * bSlope * (pt1.y - pt3.y) + bSlope * ( pt1.x + pt2.x ) - aSlope * ( pt2.x + pt3.x ) )/( 2 * ( bSlope - aSlope) )
+ m_Cy = -1*(m_Cx - (pt1.x + pt2.x) / 2 ) / aSlope + (pt1.y + pt2.y)/2
+ v1 = XY(m_Cx,m_Cy).VDist(pt2)
+ return {'c':XY(m_Cx,m_Cy),'r':v1}
+
+# ####################################################3
+
+class Shapes(inkex.Effect):
+ def addOpt(self, name, Type=str, Default=""):
+ self.arg_parser.add_argument("--" + name, action="store", type=Type, dest=name, default=Default, help="")
+
+ def __init__(self):
+ inkex.Effect.__init__(self)
+ #sOP = self.OptionParser
+ sOP = self.arg_parser
+ for n in ["tab","chamfertype","midtype","objid","tab_from_bb"] : self.addOpt(n)
+ for n in ["size","midsize","incdec","spikesep","spikeheight","joinradius","objsize","reducey"] : self.addOpt(n, float, 0.0)
+ for n in ["arrowWidth","fntsize"] : self.addOpt(n, float, 10.0)
+ for n in ["tritype", "spikestype","spikesdir","spikesdirt","spikesdirr","spikesdirb","spikesdirl","unit"] : self.addOpt(n)
+ self.addOpt("spikesize", float, "2.0")
+ self.addOpt("arrowtype")
+ self.addOpt("headWidth",float,"20.0")
+ self.addOpt("headHeight",float,"40.0")
+ for n in ["squareselection", "trihside","trivside","copyfill", "fromCornersInv", "deleteorigin"] : self.addOpt(n, inkex.Boolean, "false")
+ sOP.add_argument("--joincirctype", action="store", type=str, dest="joincirctype", default="", help="" )
+
+ # para from nodes
+ for n in ["obj","posh","posv"] : self.addOpt(n)
+ sOP.add_argument("--maxdecimals", action="store", type=int, dest="maxdecimals", default="6", help="" )
+
+ for n in ["ordery", "rotpath"] : self.addOpt(n, inkex.Boolean, "false")
+
+ def getU(self, val):
+ return self.svg.unittouu(str(val)+self.options.unit)
+
+ def addEle(self, ele, parent, props):
+ #elem = inkex.etree.SubElement(parent, ele)
+ elem = etree.SubElement(parent, ele)
+ for n in props: elem.set(n,props[n])
+ return elem
+
+ def chStyles(self,node,sty):
+ style = dict(inkex.Style.parse_str(node.get('style')))
+ for m in list(style):
+ sys.stderr.write(m)
+ for n in sty:
+ if n[0] in style: style.pop(n[0], None)
+ if n[1]!="": style[n[0]]=n[1]
+ node.set('style',inkex.Style(style))
+
+ # def unit2uu(self, val):
+ # if hasattr(self,"unittouu") is True:
+ # return self.svg.unittouu(val)
+ # else:
+ # return inkex.unittouu(val)
+
+ # def u2uu(self,value):
+ # if hasattr(inkex, 'unittouu'):
+ # v = inkex.unittouu(value)
+ # else:
+ # v = self.unittouu(value)
+ # return v
+
+ def limits(self, node):
+ s = node.bounding_box()
+ incdec = self.getU(self.options.incdec)
+ l,r,t,b,an,al = (s.left-incdec, s.right+incdec, s.top-incdec, s.bottom+incdec, s.width+incdec*2, s.height+incdec*2)
+ return (l,r,t,b,an,al)
+
+ def copyProp(self, orig, dest, prop):
+ if orig.get(prop):
+ dest.set(prop, orig.get(prop))
+
+ def estilo(self, nn, orig, style=defStyle):
+ if self.options.copyfill:
+ self.copyProp(orig, nn, 'style')
+ self.copyProp(orig, nn, 'class')
+ else:
+ self.chStyles(nn,style)
+
+ def circleABCD(self,p,r,abcd="ABCD",inverse=False,xtra=None):
+ aa = r * 0.551915024494
+ parts={
+ 'A':[XY(0,-r),XY(aa,-r), XY(r, -aa),XY(r,0)],
+ 'B':[XY(r,0), XY(r, aa), XY(aa, r),XY(0,r)],
+ 'C':[XY(0,r), XY(-aa,r), XY(-r, aa),XY(-r,0)],
+ 'D':[XY(-r,0),XY(-r,-aa),XY(-aa,-r),XY(0,-r)]}
+ pA = [XY(p)+N for N in parts[abcd[0]]]
+ for aa in abcd[1:]:
+ pA = pA + [XY(p)+N for N in parts[aa][1:]]
+ if inverse==True: pA.reverse()
+ listA = XYList(pA)
+ if xtra:
+ for n in xtra:
+ listA[n].extend(xtra[n])
+ return listA
+
+ # def getMed(self, id):
+ # #query inkscape about the bounding box of obj
+ # q = {'width':0,'height':0}
+ # file = self.args[-1]
+ # scale = self.unittouu('1px') # convert to document units
+ # for query in q.keys():
+ # ss = 'inkscape --query-%s --query-id=%s "%s"' % (query,id,file)
+ # if bsubprocess:
+ # info('bsubprocess')
+ # p = Popen(ss, shell=True, stdout=PIPE, stderr=PIPE)
+ # rc = p.wait()
+ # aaa = p.stdout.read()
+ # q[query] = aaa
+ # err = p.stderr.read()
+ # else:
+ # f,err = os.popen3(ss)[1:]
+ # q[query] = scale * float(f.read())
+ # f.close()
+ # err.close()
+ # return q
+
+ def pillows(self, type, a, node, l, t, r, b, cX, cY):
+ pts = []
+ if type=="pillowrect": cnrs=[XY(l,t), XY(r,t), XY(r,b), XY(l,b)]
+ if type=="pillowrombus": cnrs=[XY(l,t+cY), XY(l+cX,t), XY(r,t+cY), XY(r-cX,b)]
+ aa = a
+ for n in range(0,len(cnrs)-1):
+ pts.append(cnrs[n])
+ pts.append(XY(cnrs[n]).atMid(cnrs[n+1]) + XY(0,aa).rot(cnrs[n].getAngle(cnrs[n+1])))
+ n=len(cnrs)-1
+ pts.append(cnrs[n])
+
+ pM = XY(cnrs[n]).atMid(cnrs[0]) + XY(0,aa).rot(cnrs[n].getAngle(cnrs[0]))
+ pts.append(pM)
+
+ s = ''
+
+ for n in range(0,int(len(pts)/2)-1):
+ nnA = calcCircle(pts[n*2], pts[n*2+1], pts[n*2 + 2])
+ s = s + ' ' + setArc(nnA['c'].x, nnA['c'].y, nnA['r'], nnA['c'].getAngle(pts[n*2+2]), nnA['c'].getAngle(pts[n*2]), 1 if n==0 else 0)
+ n = len(pts)
+ nnA = calcCircle(pts[n-2], pts[n-1], pts[0])
+ s = s + ' ' + setArc(nnA['c'].x, nnA['c'].y, nnA['r'], nnA['c'].getAngle(pts[0]), nnA['c'].getAngle(pts[n-2]), 0)
+ shp = addChild(node.getparent(), 'path',{'d': s+" Z"})
+ self.estilo(shp,node)
+
+ def draw(self, node, sh='rombus'):
+ if (node.tag == inkex.addNS('text','svg')):
+ return
+ sO = self.options
+ l, r, t, b, an, al = self.limits(node)
+ sqSel = sO.squareselection
+ tInv = sO.fromCornersInv
+ copyfill = sO.copyfill
+ deleteorigin = sO.deleteorigin
+
+ side = min(al,an)
+ if sqSel:
+ incx=(an-side)/2.0
+ l,r,an =(l+incx,r-incx,side)
+ incy=(al-side)/2.0
+ t +=incy
+ b -=incy
+ al = side
+ cX, cY = (an/2.0,al/2.0)
+ sub_bb = sO.tab_from_bb
+ pp = node.getparent()
+ varBez = 0.551915024494
+
+ a = self.getU(sO.size) if sub_bb=="chamfer" else self.getU(sO.midsize)
+
+ a_2, a2 = (a / 2.0,a * 2.0)
+ dS = "m %sz"
+ pnts = [[l+cX,t],[cX,cY],[-cX,cY],[-cX,-cY]]
+ aa = a * varBez
+ chtype = sO.chamfertype
+ midtype = sO.midtype
+
+ an2, al2 = ((an-a)/2.0,(al-a)/2.0)
+ tritype = sO.tritype
+ if sh == 'bbox':
+ if midtype=="rombus" and a>0: pnts=[[l+cX - a_2,t],[a,0],[an2,al2],[0,a],[-an2,al2],[-a,0],[-an2,-al2],[0,-a]]
+ if (sub_bb=='mid'):
+ if midtype=="chamfer":
+ if tInv==False:
+ pnts=[[l+a,t],[an - a2,0],[a,a],[0,al-a2],[-a,a],[-(an - a2),0],[-a,-a],[0,-(al-a2)]]
+ else:
+ pnts=[[l,t],[a,0],[-a,a],[an-a,0," z m"],[a,0],[0,a],[a,al," z m"],[0,-a],[-a,a],[-an+a,0," z m"],[-a,-a],[0,a]]
+ if midtype=="cross":
+ pnts=[[l+an2,t],[a,0],[0,al2],[an2,0],[0,a],[-an2,0],[0,al2],[-a,0],[0,-al2],[-an2,0],[0,-a],[an2,0]]
+ if midtype=="starcenter":
+ pnts=[[l+cX,t],[a_2,al2], [an2,a_2], [-an2,a_2],[-a_2,al2],[-a_2,-al2],[-an2,-a_2],[an2,-a_2]]
+
+ if midtype=="pillowrombus":
+ self.pillows(midtype, a, node, l, t, r, b, cX, cY)
+ pnts = []
+ if deleteorigin: node.getparent().remove(node)
+
+ if (sub_bb=='chamfer'):
+ if chtype=="chamfer":
+ if tInv==False:
+ pnts=[[l+a,t],[an - a2,0],[a,a],[0,al-a2],[-a,a],[-(an - a2),0],[-a,-a],[0,-(al-a2)]]
+ else:
+ pnts=[[l,t],[a,0],[-a,a],[an-a,0," z m"],[a,0],[0,a],[a,al," z m"],[0,-a],[-a,a],[-an+a,0," z m"],[-a,-a],[0,a]]
+ if chtype=="round":
+ if tInv==False:
+ pnts = circQ(XY(l,t),a,"B",0,{1:"C"}) + circQ(XY(l,b),a,"A",0,{0:"L",1:"C"}) + circQ(XY(r,b),a,"D",0,{0:"L",1:"C"}) + circQ(XY(r,t),a,"C",0,{0:"L",1:"C"})
+ else:
+ pnts=[[l,t],[a,0],[0,aa,"c "],[-aa,a],[-a,a],[an-a,0,"z m "],[a,0],[0,a],[-aa,0," c"],[-a,-aa],[-a,-a],
+ [a,al-a,"z m "],[0,a],[-a,0],[0,-aa,"c "],[aa,-a],[a,-a],[-an,0,"z m "],[0,a],[a,0],[0,-aa,"c "],[-aa,-a],[-a,-a]]
+ if chtype=="roundinv":
+ pnts=[[l,t],[a,0],[0,aa,"c "],[-aa,a],[-a,a],[an-a,0,"z m "],[a,0],[0,a],[-aa,0," c"],[-a,-aa],[-a,-a],
+ [a,al-a,"z m "],[0,a],[-a,0],[0,-aa,"c "],[aa,-a],[a,-a],[-an,0,"z m "],[0,a],[a,0],[0,-aa,"c "],[-aa,-a],[-a,-a]]
+ if chtype=="rect":
+ pnts=[[l+a,t],[an - a2,0],[0,a],[a,0],[0,al-a2],[-a,0],[0,a],[-(an-a2),0],[0,-a],[-a,0],[0,-(al-a2)],[a,0]]
+ if chtype=="starcorners":
+ pnts=[[l,t],[cX,al2],[cX,-al2],[-an2,cY],[an2,cY],[-cX,-al2],[-cX,al2],[an2,-cY]]
+ if chtype=="crosscornersquad":
+ pnts=[[l-a,t],[0,-a],[a,0],[0,al+a*2],[-a,0],[0,-a],[an+a*2,0],[0,a],[-a,0],[0,-al-a*2],[a,0],[0,a]]
+ if chtype=="crosscornerstri":
+ pnts=[[l-a,t], [a,-a], [0,al+a*2], [-a,-a], [an+a*2,0], [-a,a], [0,-al-a*2],[a,a]]
+ if chtype=="crosscornersround":
+ dS = "M %sZ"
+ aa2 = a_2 * varBez
+ p1 = circQ(XY(r + a_2, t - a_2),a_2,"DAB",1)
+ p2 = circQ(XY(r + a_2, b + a_2),a_2,"ABC",1)
+ p3 = circQ(XY(l - a_2, b + a_2),a_2,"BCD",1)
+ p4 = circQ(XY(l - a_2, t - a_2),a_2,"CDA",1)
+ pnts = p1 + [[r,t],[r,b+a_2-aa2]] + p2 + [[r+a_2-aa2,b],[l-a_2+aa2,b]] + p3 + [[l,b+a_2-aa],[l,t-a_2+aa]] + p4
+ pnts[1].append(" C")
+
+ if chtype=="pillowrect":
+ pts = []
+ self.pillows(chtype, a, node, l, t, r, b, cX, cY)
+ pnts = []
+ dS = "M %sZ"
+ if deleteorigin: node.getparent().remove(node)
+ if chtype == "spiralrect":
+ An, Al = (an, al)
+ pnts = [[l,t], [An,0], [0,Al], [-An,0], [0,-Al+a]]
+ An = An - a
+ Al = Al - a*2
+ tot = min(An//a,Al//a) // 2 + 1
+
+ for n in range(0,int(tot)):
+ pnts.append([An,0])
+ An = An-a
+ if Al>a:
+ pnts.append([0,Al])
+ Al=Al-a
+ else:
+ break
+ if An>a:
+ pnts.extend([[-An,0]])
+ An = An-a
+ else:
+ break
+ if Al>0:
+ pnts.extend([[0, -Al]])
+ Al=Al-a
+ else:
+ break
+
+ # ________________
+ # ______________ |
+ # | __________ | |
+ # | |____________| |
+ # |________________|
+
+ defStyle = [['stroke-width','2.5'],['fill','none'],['stroke','#ff0000']]
+ dS = "m %s"
+
+ if (sub_bb=='spikes'): pnts = self.triSpikes(sO, an, al, l, t)
+
+ if sub_bb=='arrow':
+ pnts = self.drawArrow(sO, an, al, l, t)
+ if sO.arrowtype=="arrowstick":
+ dS = "m %s"
+
+ if sub_bb=='triangles':
+ trihside, trivside=(sO.trihside, sO.trivside)
+ if tritype=="isosceles": pnts=[[l+cX,t],[cX,al],[-an,0]]
+ if tritype=="equi":
+ sqrt3 = 1.7320508075
+ height = sqrt3/2 * side
+ tcx, tcy = ((an - side)/2.0, (al - height)/2.0)
+ pnts=[[cX+l,t+tcy],[an/2.0-tcx,height],[-side,0]]
+ if tritype=="rect":
+ x1 = l + tern(not trivside and trihside,an,0)
+ x2 = tern(not trivside and trihside,0,an)
+ x3 = tern(trivside and trihside,0,-an)
+ pnts=[[x1,t], [x2,tern(not trivside,al,0)], [x3,tern(not trivside,0,al)]]
+ # #######################################
+ if tritype=="circi" or tritype=="circe" or tritype=="trii":
+ # get verts
+ pnts = []
+ if node.get('d'):
+ p = node.path.to_superpath().to_path().to_arrays()
+ vs=[]
+ for cmd, params in p:
+ if cmd != 'Z' and cmd != 'z':
+ vs.append(XY(params[-2],params[-1]))
+ if len(vs)>2:
+ if tritype == "trii":
+ p1 = XY(vs[0]) + (XY(vs[1])-vs[0]).div(2)
+ p2 = XY(vs[1]) + (XY(vs[2])-vs[1]).div(2)
+ p3 = XY(vs[2]) + (XY(vs[0])-vs[2]).div(2)
+ pnts = [p3.co,(p1-p3).co,((p2-p1)-p3).co]
+ if tritype == "circi" or tritype == "circe":
+ if tritype == "circi":
+ rad, px, py = circleInscribedInTri(vs[0], vs[1], vs[2])
+ if tritype == "circe":
+ rad, px, py = TriInscribedInCircle(vs[0], vs[1], vs[2])
+ nn = svgCircle(node.getparent(), rad, px, py)
+ self.estilo(nn,node)
+ pnts=[]
+ if deleteorigin: node.getparent().remove(node)
+ if sh=='nodes':
+ # get verts
+ obj, posh, posv, objS, oY =(sO.obj, int(sO.posh), int(sO.posv), sO.objsize, sO.ordery)
+ reducey = sO.reducey
+ o2 = objS/2
+ pnts = []
+ orderY = []
+ if node.get('d'):
+ p = node.path.to_arrays()
+ vs = []
+ minY, maxY, prevX, prevY = (100000.0, -100000.0, 0, 0)
+ for cmd, params in p:
+ if cmd != 'Z' and cmd != 'z':
+ posY = prevY
+ posX = prevX
+ posY = params[-1]
+ if cmd in ['h','H']:
+ posY = prevY
+ posX = params[-1]
+ elif cmd not in ['v','V','h','H']:
+ posX = params[-2]
+ vs.append(XY(posX, posY))
+ prevX, prevY, minY, maxY = (posX, posY, min(posY, minY), max(posY, maxY))
+ objs = []
+ dist = maxY - minY
+ grp = addChild(node.getparent(), 'g',{})
+ self.copyProp(node, grp, 'transform')
+ self.estilo(grp,node)
+
+ if obj == "obj":
+ oi = sO.objid
+ este = self.svg.getElementById('%s' % oi)
+ if este == None:
+ obj='c'
+ else:
+ l1, r1, t1, b1, an1, al1 = self.limits(este)
+ w2, h2 = (an1/2 , al1/2)
+ for n in range(0,len(vs)):
+ if obj == "number":
+ nn = self.addTxt(grp, str(vs[n].x), str(vs[n].y), str(n))
+ if obj=="obj":
+ if este != None:
+ reduce = (100 - (reducey * ((maxY - vs[n].y) / dist)))/100
+ px = str(vs[n].x / reduce - l1 - w2 + w2 * posh)
+ py = str(vs[n].y / reduce - t1 - h2 + h2 * posv)
+ nn = addChild(grp,'use',{inkex.addNS('href',"xlink"):"#"+oi,'x':px,'y':py, "transform":"scale(%f)" % (reduce)})
+ else:
+ obj='c'
+ reduce = (o2 / 100 * reducey) * (maxY - vs[n].y) / dist
+ O2 = o2 - reduce
+ if obj=="c":
+ pxy = vs[n] + XY(posh, posv).mul(O2)
+ nn = svgCircle(grp, O2, pxy.x, pxy.y)
+ if obj=="s":
+ pxy = vs[n] - XY(O2) + XY(O2 * posh, O2 * posv)
+ nn = addChild(grp,'rect',{'height':str(O2*2), 'width':str(O2*2), 'x':str(pxy.x), 'y':str(pxy.y)})
+ if obj=="number":
+ nn = self.addTxt(grp, str(vs[n].x), str(vs[n].y), str(n))
+ if obj=="coords":
+ maxDec=sO.maxdecimals
+ nn = self.addTxt(grp, str(vs[n].x), str(vs[n].y), str(round(vs[n].x, maxDec)) + "," + str(round(vs[n].y, maxDec)))
+ orderY.append([vs[n].y,nn])
+ #self.estilo(nn,node)
+ if sO.ordery:
+ def myFunc(e):
+ return e[0]
+ orderY.sort(key=myFunc)
+ for item in orderY:
+ grp.append( item[1])
+ if deleteorigin: node.getparent().remove(node)
+ # ##############################3
+
+ d = ""
+ if len(pnts)>0:
+ for n in pnts:
+ ss = "" if len(n)<3 else n[2]
+ d += "%s%s,%s " % (ss, str(n[0]),str(n[1]))
+ nn = self.addEle('path',pp, {'d':dS % (d)})
+ self.estilo(nn,node)
+ if deleteorigin: node.getparent().remove(node)
+
+ def makeRel(self,arr):
+ b = arr[:]
+ for n in range(1,len(arr)):
+ s = b[n]
+ for i in range(0,n):
+ s = s - arr[i]
+ b[n] = s
+ return b
+
+ def circle(self,p,r):
+ varBez = 0.551915024494
+ dS = "m %s"
+ aa = r * varBez
+ d=""
+ pnts=[[p.x - r,p.y],[0,aa,"c "],[r - aa,r],[r,r],[aa,0,"c "],[r,-r+aa],[r,-r],[0,-aa,"c "],[-r+aa,-r],[-r,-r],[-aa,0,"c "],[-r,r-aa],[-r, r]]
+ for n in pnts:
+ ss = "" if len(n)<3 else n[2]
+ d += "%s%s,%s " % (ss, str(n[0]),str(n[1]))
+ return d
+
+ def addTxt(self, node, x, y, text, dy = 0):
+ new2 = self.addEle(inkex.addNS('text','svg'), node,{'x':str(x),'y':str(y)})
+ new = etree.SubElement(new2, inkex.addNS('tspan','svg'), {inkex.addNS('role','sodipodi'): 'line'})
+ new.set('style','text-align:center; vertical-align:bottom; font-size:%s; fill-opacity:1.0; stroke:none; font-weight:normal; font-style:normal; fill:#000000' % self.options.fntsize)
+ new.set('dy', str(dy))
+ new.text = str(text)
+ return new2
+
+ def circsCone(self, sels, sh='rombus'):
+ sO = self.options
+ copyfill = sO.copyfill
+ deleteorigin = sO.deleteorigin
+ joincirctype = sO.joincirctype
+ r2 = sO.joinradius
+ cssEmpty = [['stroke-width','0.5'],['fill','none'],['stroke','#ff0000']]
+
+ strEmpty = 'stroke-width:0.02; fill:none; stroke:#000000; stroke-dasharray:0.5,0.2; stroke-dashoffset:0;'
+
+ for nodos in range(len(sels)-1):
+ node = sels[nodos]
+ node2 = sels[nodos+1]
+ lA, rA, tA, bA, anA, alA = self.limits(node)
+ lB, rB, tB, bB, anB, alB = self.limits(node2)
+ rA, cY = (anA/2.0,alA/2.0)
+ rB, cY2 = (anB/2.0,alB/2.0)
+
+ PtA = XY(lA + rA, tA + cY)
+ PtB = XY(lB + rB, tB + cY2)
+
+ if (circleInCircle(PtA,rA,PtB,rB) or circleInCircle(PtB,rB,PtA,rA)):
+ pass
+ else:
+ pp = node.getparent()
+ rotAB = XY(PtB).getAngle(PtA)
+ dist = PtA.hipo(PtB)
+ if joincirctype=='trapecio':
+ # alineamos las esferas en Y
+ rDif = rA - rB
+ Axis = XY(-rDif,0)
+ #D2 = math.sqrt((dist*dist) - (rDif*rDif)) / dist
+ D2 = triCat(dist, rDif) / dist
+ P1 = XY(Axis).mul(rA / dist)
+ P2 = XY(-dist,0) + XY(Axis).mul(rB / dist)
+ r = P1.VDist(P2)
+ Rot1 = XY(P2.x,rB * D2).getAngleD(XY(P2.x + r, rA * D2))
+ aBez=createArcBez(rA,-90 -Rot1, -270 + Rot1)
+ curva1a = bezs2XYList(aBez)
+ d = XYListSt(curva1a, rotAB, PtA)
+
+ pnts2 = bezs2XYList(createArcBez(rB, 90 + Rot1, 270 - Rot1),XY(-dist,0))
+ d2 = XYListSt(pnts2, rotAB, PtA)
+ nn = self.addEle('path',pp, {'d':"M%s L%sZ" % (d,d2)})
+ self.estilo(nn,node)
+ # ################## B L O B ##############
+ if joincirctype=='blob':
+ if ((r2==0) and (dist<(rA+rB))):
+ r2 = dist - rB
+ if (r2 > 0):
+ rad1 = rA + r2
+ rad2 = rB + r2
+ a = ( pow2(dist) - pow2(rB+r2) + pow2(rA+r2))/(dist*2)
+ else:
+ r2 = dist - rA - rB
+ rad1 = dist - rB
+ rad2 = dist - rA
+ a = (pow2(dist-rB) - pow2(dist-rA) + pow2(dist))/(dist*2);
+ # alineamos las esferas en Y
+
+ rt = math.atan2(PtB.y - PtA.y, PtB.x - PtA.x)
+ # # distancia del centro 1 a la interseccion de los circulos
+ x = (dist * dist - rad2 * rad2 + rad1 * rad1) / (dist*2)
+ if (rad1 * rad1 - x * x) > 0 :
+ #catB = math.sqrt(rad1 * rad1 - x * x)
+ catB = triCat(rad1, x)
+
+ rt = math.degrees(XY(0,0).getAngle(XY(-x, -catB)))
+ rt2 = math.degrees(XY(0,0).getAngle(XY(-(dist - x), -catB)))
+
+ curva1 = bezs2XYList(createArcBez(rA, rt, -rt))
+ curva1.reverse()
+ curva2 = bezs2XYList(createArcBez(r2, -180 + rt, -rt2),XY(-x, -catB))
+ curva3 = bezs2XYList(createArcBez(rB, rt2+180,180-rt2),XY(-dist, 0))
+ curva3.reverse()
+ curva4 = bezs2XYList(createArcBez(r2, rt2, 180 - rt),XY(-x, catB))
+
+ curva1= curva1+curva2[1:]+curva3[1:]+curva4[1:]
+ sCurva1 = XYListSt(curva1, rotAB, PtA)
+
+ nn = self.addEle('path',pp,{'d':"M %s" % (sCurva1)})
+ self.estilo(nn,node)
+# ################################################
+# ################## O V A L #####################
+# ################################################
+ if joincirctype=='oval':
+ minR2 = dist + min(rA,rB)
+ if r2 < minR2:
+ r2 = minR2
+ info('Changed radius to '+str(minR2))
+ rad1 = r2 - rA
+ rad2 = r2 - rB
+ a = ( pow2(dist) - pow2(rB+r2) + pow2(rA+r2))/(dist*2)
+
+ rt = math.atan2(PtB.y - PtA.y, PtB.x - PtA.x)
+ D = dist #XY(PtA).sub(PtB).vlength() # distancia entre los centros
+ # distancia del centro 1 a la interseccion de los circulos
+ x = (D*D - rad2 * rad2 + rad1 * rad1) / (D*2)
+ # catB = math.sqrt(rad1 * rad1 - x * x)
+ catB = triCat(rad1, x)
+
+ rotAB=XY(PtB).getAngle(PtA)
+ rot1 = math.degrees(XY(0,0).getAngle(XY(-x,-catB))) + 180.0
+ curva1 = bezs2XYList(createArcBez(rA, -rot1, rot1))
+ curva1.reverse()
+ #rot2 = math.degrees(XY(-dist,0).getAngle(XY(-x,-catB))) +180.0
+ rot2 = XY(-dist,0).getAngleD(XY(-x,-catB)) +180.0
+ curva2 = bezs2XYList(createArcBez(r2, -rot2,-rot1),XY(-x,catB))
+ curva2.reverse()
+ curva3 = bezs2XYList(createArcBez(rB, rot2,-rot2),XY(-dist,0))
+ curva3.reverse()
+ curva4 = bezs2XYList(createArcBez(r2, rot1,rot2),XY(-x,-catB))
+ curva4.reverse()
+ curva1= curva1+curva2[1:]+curva3[1:]+curva4[1:] #+curva3[1:]+curva4[1:]
+ sCurva1 = XYListSt(curva1, rotAB, PtA)
+ # curva1
+ nn = self.addEle('path',pp,{'d':"M %sZ" % (sCurva1),'style':'stroke-width:0.02;fill:#cc0000;stroke:#000000;'})
+ self.estilo(nn,node)
+ if deleteorigin: node.getparent().remove(node)
+
+ def drawArrow(self, sO, an, al, l, t):
+ arrowType = sO.arrowtype
+ headH, headW, arrowW = (self.getU(sO.headHeight), self.getU(sO.headWidth), self.getU(sO.arrowWidth))
+ hw2=headW/2.0
+ cX = an/2.0
+ if arrowType=="arrowfilled":
+ pnts=[[l+cX,t],[hw2,headH],[-(headW-arrowW)/2.0,0],[0,al-headH],[-arrowW,0],[0,-(al-headH)],[-(headW-arrowW)/2.0,0]]
+ else:
+ #dS = "m %s"
+ pnts=[[l+cX,t],[0,al],[-hw2,-al+headH,"m "],[hw2,-headH],[hw2,headH]]
+ return pnts
+
+ def triSpikes(self, sO, an, al, l, t):
+ spktype, spikesdir, sh, ssep = (sO.spikestype, sO.spikesdir, sO.spikeheight, self.getU(sO.spikesep))
+ ss = self.getU(sO.spikesize)
+ anX, anY = (int( (an + ssep) / (ss * 2 + ssep)), int( (al+ssep) / (ss * 2 + ssep)))
+ iniX, iniY = (((an+ssep) - (anX * (ss * 2 + ssep))) / 2.0, ((al+ssep) - (anY * (ss * 2 + ssep))) / 2.0)
+ if spktype=="trirect" or spktype=="squ":
+ anX, anY = ( int((an + ssep) / (ss + ssep)), int((al + ssep) / (ss + ssep)) )
+ iniX, iniY = (((an + ssep) - (anX * (ss + ssep))) / 2.0, ((al + ssep) - (anY * (ss + ssep))) / 2.0)
+ dir = 1
+ pnts = [[l,t],[iniX,0]]
+
+ if spikesdir=='ins': dir = -1.0
+ #if spktype=="tri" or spktype=="trirect" or spktype=="squ":
+ if spktype in ["tri", "trirect", "squ"]:
+ sDir = sO.spikesdirt # --------------------------------TOP---------------------
+ if sDir=='pre': sDir = spikesdir
+ if sDir=='non':
+ pnts = [[l,t],[an,0]]
+ else :
+ dirT = 1
+ if sDir=='ins': dirT = -1.0
+ for n in range(anX):
+ if sDir=='alt' : dirT = 1 if n % 2 == 1 else -1
+ if spktype=="tri": pnts.extend([[ss,-sh*dirT],[ss,sh*dirT]])
+ if spktype=="trirect": pnts.extend([[0,-sh*dirT],[ss,sh*dirT]])
+ if spktype=="squ": pnts.extend([[0,-sh*dirT],[ss,0],[0,sh*dirT]])
+ if ssep != 0 and n < (anX-1): pnts.append([ssep,0])
+ pnts.append([iniX,0])
+ sDir = sO.spikesdirr # ---------------------------------RIGHT-------------------
+ if sDir == 'pre' : sDir = spikesdir
+ if sDir == 'non' :
+ pnts.append([0,al])
+ else :
+ pnts.append([0,iniY])
+ dirR = -1.0 if sDir=='ins' else 1.0
+ for n in range(anY):
+ if sDir == 'alt' : dirR = 1 if n % 2 == 1 else -1
+ if spktype=="tri": pnts.extend([[sh*dirR,ss],[-sh * dirR,ss]])
+ if spktype=="trirect": pnts.extend([[sh*dirR,0],[-sh * dirR,ss]])
+ if spktype=="squ": pnts.extend([[sh*dirR,0],[0,ss],[-sh * dirR,0]])
+ if ssep != 0 and n < (anY-1): pnts.append([0, ssep])
+ pnts.append([0,iniY])
+ sDir = sO.spikesdirb # -------------------------------BOTTOM--------------------
+ if sDir == 'pre' : sDir = spikesdir
+ if sDir == 'non' :
+ pnts.append([-an,0])
+ else :
+ pnts.append([-iniX,0])
+ dirB = -1.0 if sDir=='ins' else 1.0
+ for n in range(anX):
+ if sDir == 'alt' : dirB = 1 if n % 2 == 1 else -1
+ if spktype=="tri": pnts.extend([[-ss,sh*dirB],[-ss,-sh*dirB]])
+ if spktype=="trirect": pnts.extend([[0,sh*dirB],[-ss,-sh*dirB]])
+ if spktype=="squ": pnts.extend([[0,sh*dirB],[-ss,0],[0,-sh*dirB]])
+ if ssep != 0 and n < (anX-1): pnts.append([-ssep,0])
+ pnts.append([-iniX,0])
+ sDir = sO.spikesdirl # --------------------------------------LEFT---------------
+ if sDir == 'pre' : sDir = spikesdir
+ if sDir != 'non' :
+ pnts.append([0,-iniY])
+ dirL = -1.0 if sDir=='ins' else 1.0
+ #sDir = sO.spikesdir
+ for n in range(anY):
+ if sDir == 'alt' : dirL = 1 if n % 2 == 1 else -1
+ #pnts.extend([[-sh*dirL,-ss],[sh*dirL,-ss]])
+ if spktype=="tri": pnts.extend([[-sh*dirL,-ss],[sh*dirL,-ss]])
+ if spktype=="trirect": pnts.extend([[-sh*dirL,0], [sh*dirL,-ss]])
+ if spktype=="squ": pnts.extend([[-sh*dirL,0], [0,-ss],[sh * dirL,0]])
+ if ssep != 0 and n < (anY-1): pnts.append([0, -ssep])
+ ###########################################
+ varBez = 0.551915024494
+ if spktype in ["rnd", "wav"]:
+ dif, difh, dBez, dBezh = (ss-(ss*varBez), sh-(sh*varBez), ss*varBez, sh*varBez)
+ sDir = sO.spikesdirt # --------------------------------TOP---------------------
+ if sDir=='pre': sDir = spikesdir
+ if sDir=='non':
+ pnts = [[l,t],[an,0],[0,iniY]]
+ else :
+ dirT = -1.0 if sDir=='ins' else 1.0
+ for n in range(anX):
+ if sDir=='alt' : dirT = 1 if n % 2 == 1 else -1
+ if spktype == "rnd": pnts.extend([[0,-dBezh*dirT," c"],[dif,-sh*dirT],[ss,-sh*dirT],[dBez,0], [ss,difh*dirT],[ss,sh*dirT]]) #fijo
+ if spktype == "wav": pnts.extend([[0,-dBezh*dirT," c"],[dif,-sh*dirT],[ss,-sh*dirT],[0,dBezh*dirT],[dBez,sh*dirT],[ss,sh*dirT]]) #fijo
+ if ssep!=0 and n < (anX-1): pnts.append([ssep,0,' l'])
+ pnts.extend([[iniX,0,' l'],[0,iniY]])
+ sDir = sO.spikesdirr # ---------------------------------RIGHT-------------------
+ if sDir == 'pre' : sDir = spikesdir
+ if sDir == 'non' :
+ pnts.extend([[0,al - iniY],[-iniX,0]])
+ else :
+ dirR = -1.0 if sDir=='ins' else 1.0
+ for n in range(anY):
+ if sDir == 'alt' : dirR = 1 if n % 2 == 1 else -1
+ if spktype == "rnd": pnts.extend([[dBezh*dirR,0," c"],[sh*dirR,dif],[sh*dirR,ss], [0,dBez] ,[-difh*dirR,ss], [-sh*dirR,ss]]) #fijo
+ if spktype == "wav": pnts.extend([[dBezh*dirR,0," c"],[sh*dirR,dif],[sh*dirR,ss], [-dBezh*dirR,0],[-sh*dirR,dBez], [-sh*dirR,ss]]) #fijo
+ if ssep!=0 and n < (anY-1): pnts.append([0, ssep,' l'])
+ pnts.extend([[0,iniY,' l'],[-iniX,0]])
+ sDir = sO.spikesdirb # -------------------------------BOTTOM--------------------
+ if sDir == 'pre' : sDir = spikesdir
+ if sDir == 'non' :
+ pnts.extend([[-an + iniX,0],[0,-iniY]])
+ else :
+ dirB = -1.0 if sDir=='ins' else 1.0
+ for n in range(anX):
+ if sDir == 'alt' : dirB = 1 if n % 2 == 1 else -1
+ if spktype == "rnd": pnts.extend([[0,dBezh*dirB," c"],[-dif,sh*dirB],[-ss,sh*dirB],[-dBez,0],[-ss,-difh * dirB],[-ss,-sh * dirB]]) #fijo
+ if spktype == "wav": pnts.extend([[0,dBezh*dirB," c"],[-dif,sh*dirB],[-ss,sh*dirB],[0,-dBezh*dirB],[-dif, -sh*dirB],[-ss,-sh * dirB]]) #fijo
+ if ssep!=0 and n < (anX-1): pnts.append([-ssep,0,' l'])
+ pnts.extend([[-iniX,0,' l'],[0,-iniY]])
+ sDir = sO.spikesdirl # --------------------------------------LEFT---------------
+ if sDir == 'pre' : sDir = spikesdir
+ if sDir != 'non' :
+ dirL = -1.0 if sDir=='ins' else 1.0
+ for n in range(anY):
+ if sDir == 'alt' : dirL = 1 if n % 2 == 1 else -1
+ if spktype=="rnd": pnts.extend([[-dBezh*dirL,0," c"],[-sh*dirL,-dif],[-sh*dirL,-ss],[0,-dBez],[difh * dirL,-ss],[sh * dirL,-ss]]) #fijo
+ if spktype=="wav": pnts.extend([[-dBezh*dirL,0," c"],[-sh*dirL,-dif],[-sh*dirL,-ss],[dBezh*dirL,0],[sh*dirL,-dBez],[sh * dirL,-ss]]) #fijo
+ if ssep!=0 and n < (anY-1): pnts.append([0, -ssep,' l'])
+ return pnts # 805
+
+ def draw_shapes(self):
+ sels = []
+ for id, node in self.svg.selected.items(): sels.append(node)
+ tab = str(self.options.tab)
+ if tab != 'extra':
+ for node in sels:
+ self.draw(node, tab)
+ else:
+ Type = str(self.options.joincirctype)
+ if len(sels)<2:
+ inkex.errormsg('Select at least two objects')
+ else:
+ self.circsCone(sels, Type)
+
+ def loc_str(self, str):
+ return locale.format("%.f", float(str), 0)
+
+ def effect(self):
+ slices = self.draw_shapes()
+
+if __name__ == "__main__":
+ Shapes().run()
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/snap_objects.inx b/extensions/fablabchemnitz/snap_objects.inx
new file mode 100644
index 00000000..b1854565
--- /dev/null
+++ b/extensions/fablabchemnitz/snap_objects.inx
@@ -0,0 +1,21 @@
+
+
+ Snap Object Points
+ fablabchemnitz.de.snap_objects
+ 25
+ true
+ true
+ false
+
+
+ path
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/snap_objects.py b/extensions/fablabchemnitz/snap_objects.py
new file mode 100755
index 00000000..de931bc7
--- /dev/null
+++ b/extensions/fablabchemnitz/snap_objects.py
@@ -0,0 +1,137 @@
+#!/usr/bin/env python3
+
+'''
+Copyright (C) 2020 Scott Pakin, scott-ink@pakin.org
+
+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 3 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.
+
+'''
+
+from collections import defaultdict
+import inkex
+from inkex.paths import Arc, Curve, Horz, Line, Move, Quadratic, Smooth, TepidQuadratic, Vert, ZoneClose
+
+
+class SnapObjects(inkex.Effect):
+ "Snap the points on multiple paths towards each other."
+
+ def __init__(self):
+ inkex.Effect.__init__(self)
+ self.arg_parser.add_argument('--max_dist', type=float, default=25.0, help='Maximum distance to be considered a "nearby" point')
+ self.arg_parser.add_argument('--controls', type=inkex.Boolean, default=True, help='Snap control points')
+ self.arg_parser.add_argument('--ends', type=inkex.Boolean, default=True, help='Snap endpoints')
+ self.arg_parser.add_argument('--first_only', type=inkex.Boolean, default=True, help='Modify only the first selected path')
+
+ def _bin_points(self):
+ "Associate each path ID with a list of control points and a list of endpoints."
+ cpoints = defaultdict(list)
+ epoints = defaultdict(list)
+ for node in self.svg.selection.filter(inkex.PathElement).values():
+ for cmd in node.path.to_absolute().proxy_iterator():
+ pid = node.get_id()
+ cpoints[pid].extend(cmd.control_points)
+ epoints[pid].append(cmd.end_point)
+ return cpoints, epoints
+
+ def _find_nearest(self, pid, x0, y0, other_points):
+ '''Find the nearest neighbor to a given point, and return the midpoint
+ of the given point and its neighbor.'''
+ max_dist2 = self.options.max_dist**2 # Work with squares instead of wasting time on square roots.
+ bx, by = x0, y0 # Best new point
+ best_dist2 = max_dist2 # Minimal distance observed from (x0, y0)
+ for k, pts in other_points.items():
+ if k == pid:
+ continue # Don't compare to our own points.
+ for vec in pts:
+ x1, y1 = vec.x, vec.y
+ dist2 = (x1 - x0)**2 + (y1 - y0)**2 # Squared distance
+ if dist2 > best_dist2:
+ continue # Not the nearest point
+ best_dist2 = dist2
+ bx, by = x1, y1
+ return (x0 + bx)/2, (y0 + by)/2
+
+ def _simplify_paths(self):
+ 'Make all commands absolute, and replace Vert and Horz commands with Line.'
+ for node in self.svg.selection.filter(inkex.PathElement).values():
+ path = node.path.to_absolute()
+ new_path = []
+ prev = inkex.Vector2d()
+ prev_prev = inkex.Vector2d()
+ first = inkex.Vector2d()
+ for i, cmd in enumerate(path):
+ if i == 0:
+ first = cmd.end_point(first, prev)
+ prev, prev_prev = first, first
+ if isinstance(cmd, Vert):
+ cmd = cmd.to_line(prev)
+ elif isinstance(cmd, Horz):
+ cmd = cmd.to_line(prev)
+ new_path.append(cmd)
+ if isinstance(cmd, (Curve, Quadratic, Smooth, TepidQuadratic)):
+ prev_prev = list(cmd.control_points(first, prev, prev_prev))[-2]
+ prev = cmd.end_point(first, prev)
+ node.path = new_path
+
+ def effect(self):
+ """Snap control points to other objects' control points and endpoints
+ to other objects' endpoints."""
+ # This function uses an O(n^2) algorithm, which shouldn't be too slow
+ # for typical point counts.
+ #
+ # As a preprocessing step, we first simplify the paths to reduce the
+ # number of special cases we'll need to deal with. Then, we associate
+ # each path with all of its control points and endpoints.
+ if len(self.svg.selection.filter(inkex.PathElement)) < 2:
+ raise inkex.utils.AbortExtension(_('Snap Object Points requires that at least two paths be selected.'))
+ self._simplify_paths()
+ cpoints, epoints = self._bin_points()
+
+
+
+ # Process in turn each command on each path.
+ for node in self.svg.selection.filter(inkex.PathElement).values():
+ pid = node.get_id()
+ path = node.path
+ new_path = []
+ for cmd in path:
+ args = cmd.args
+ new_args = list(args)
+ na = len(args)
+ if isinstance(cmd, (Curve, Line, Move, Quadratic, Smooth, TepidQuadratic)):
+ # Zero or more control points followed by an endpoint.
+ if self.options.controls:
+ for i in range(0, na - 2, 2):
+ new_args[i], new_args[i + 1] = self._find_nearest(pid, args[i], args[i + 1], cpoints)
+ if self.options.ends:
+ new_args[na - 2], new_args[na - 1] = self._find_nearest(pid, args[na - 2], args[na - 1], epoints)
+ elif isinstance(cmd, ZoneClose):
+ # No arguments at all.
+ pass
+ elif isinstance(cmd, Arc):
+ # Non-coordinates followed by an endpoint.
+ if self.options.ends:
+ new_args[na - 2], new_args[na - 1] = self._find_nearest(pid, args[na - 2], args[na - 1], epoints)
+ else:
+ # Unexpected command.
+ inkex.errormsg(_('Unexpected path command "%s"' % cmd.name))
+ new_path.append(cmd.__class__(*new_args))
+ node.path = new_path
+ if self.options.first_only:
+ break
+
+if __name__ == '__main__':
+ SnapObjects().run()
diff --git a/extensions/fablabchemnitz/styles_to_layers.py b/extensions/fablabchemnitz/styles_to_layers.py
index 1e1ca84d..bddda33d 100644
--- a/extensions/fablabchemnitz/styles_to_layers.py
+++ b/extensions/fablabchemnitz/styles_to_layers.py
@@ -157,8 +157,11 @@ class StylesToLayers(inkex.Effect):
contentlength = 0 #some counter to track if there are layers inside or if it is just a list with empty children
for layerNode in layerNodeList:
- layerNode[0].append(layerNode[2]) #append element to created layer
- if layerNode[1] is not None: contentlength += 1 #for each found layer we increment +1
+ try: #some nasty workaround. Make better code
+ layerNode[0].append(layerNode[2]) #append element to created layer
+ if layerNode[1] is not None: contentlength += 1 #for each found layer we increment +1
+ except:
+ continue
# Additionally if threshold was defined re-arrange the previously created layers by putting them into sub layers
if self.options.subdividethreshold > 1 and contentlength > 0: #check if we need to subdivide and if there are items we could rearrange into sub layers
diff --git a/extensions/fablabchemnitz/tool_covers.inx b/extensions/fablabchemnitz/tool_covers.inx
deleted file mode 100644
index 0da2b1b3..00000000
--- a/extensions/fablabchemnitz/tool_covers.inx
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
- Tool Covers
- fablabchemnitz.de.tool_covers
-
-
- base
- 20
- 40
- 40
- 20
- band
- 15
- 30
- fastener
- 10
- 10
- margin and needle hole
- 3
- 3
-
-
- band
- 0.7
- needle hole
- 1
- 1
- 1
- true
-
-
- PiersCover Inkscape extension
-
-Version 0.92
-
-by Yoichi Tanibayashi
-
-https://ytani01.github.io/PliersCover/
-
-
-
-
- all
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/tool_covers.py b/extensions/fablabchemnitz/tool_covers.py
deleted file mode 100644
index 5b305042..00000000
--- a/extensions/fablabchemnitz/tool_covers.py
+++ /dev/null
@@ -1,487 +0,0 @@
-#!/usr/bin/env python3
-#
-# (c) 2020 Yoichi Tanibayashi
-#
-import inkex
-from lxml import etree
-import math
-
-class Point(object):
- def __init__(self, x, y):
- self.x = x
- self.y = y
-
- def distance(self, c):
- return math.sqrt((c.x - self.x) ** 2 + (c.y - self.y) ** 2)
-
- def rotate(self, rad):
- new_x = math.cos(rad) * self.x - math.sin(rad) * self.y
- new_y = math.sin(rad) * self.x + math.cos(rad) * self.y
- self.x = new_x
- self.y = new_y
- return self
-
- def mirror(self):
- self.x = -self.x
- return self
-
-
-class Vpoint(Point):
- '''
- A point with (x, y) coordinates and direction (rad)
-
- rad: Direction (true up: 0, right: math.pi / 2, ...)
- '''
- def __init__(self, x, y, rad=0):
- super(Vpoint, self).__init__(x, y)
- self.rad = rad
-
- def rotate(self, rad):
- super(Vpoint, self).rotate(rad)
- self.rad += rad
- return self
-
- def mirror(self):
- super(Vpoint, self).mirror()
- self.rad = -self.rad
- return self
-
-
-class SvgObj(object):
- DEF_COLOR = '#00FF00'
- DEF_STROKE_WIDTH = 0.2
- DEF_STROKE_DASHARRAY = 'none'
-
- def __init__(self, parent):
- self.parent = parent
- self.type = None
- self.attr = {}
-
- def draw(self, color=DEF_COLOR,
- stroke_width=DEF_STROKE_WIDTH,
- stroke_dasharray=DEF_STROKE_DASHARRAY):
-
- self.attr['style'] = str(inkex.Style({
- 'stroke': str(color),
- 'stroke-width': str(stroke_width),
- 'stroke-dasharray': str(stroke_dasharray),
- 'fill': 'none'}))
- return etree.SubElement(self.parent,
- inkex.addNS(self.type, 'svg'),
- self.attr)
-
-
-class SvgCircle(SvgObj):
- DEF_COLOR = '#FF0000'
- DEF_STROKE_WIDTH = 0.2
- DEF_STROKE_DASHARRAY = 'none'
-
- def __init__(self, parent, r):
- super(SvgCircle, self).__init__(parent)
- self.r = r
- self.type = 'circle'
-
- def draw(self, point,
- color=DEF_COLOR,
- stroke_width=DEF_STROKE_WIDTH,
- stroke_dasharray=DEF_STROKE_DASHARRAY):
- self.attr['cx'] = str(point.x)
- self.attr['cy'] = str(point.y)
- self.attr['r'] = str(self.r)
-
- return super(SvgCircle, self).draw(color,
- stroke_width, stroke_dasharray)
-
-
-class SvgPath(SvgObj):
- DEF_COLOR = '#0000FF'
- DEF_STROKE_WIDTH = 0.2
- DEF_STROKE_DASHARRAY = 'none'
-
- def __init__(self, parent, points):
- super(SvgPath, self).__init__(parent)
- self.points = points
- self.type = 'path'
-
- def create_svg_d(self, origin_vpoint, points):
- '''
- to be override
-
- This is sample code.
- '''
- svg_d = ''
- for i, p in enumerate(points):
- (x1, y1) = (p.x + origin_vpoint.x, p.y + origin_vpoint.y)
- if i == 0:
- svg_d = 'M %f,%f' % (x1, y1)
- else:
- svg_d += ' L %f,%f' % (x1, y1)
- return svg_d
-
- def rotate(self, rad):
- for p in self.points:
- p.rotate(rad)
- return self
-
- def mirror(self):
- for p in self.points:
- p.mirror()
- return self
-
- def draw(self, origin,
- color=DEF_COLOR, stroke_width=DEF_STROKE_WIDTH,
- stroke_dasharray=DEF_STROKE_DASHARRAY):
-
- self.rotate(origin.rad)
-
- svg_d = self.create_svg_d(origin, self.points)
- # inkex.errormsg('svg_d=%s' % svg_d)
- # inkex.errormsg('svg_d=%s' % str(Path( svg_d )))
-
- self.attr['d'] = svg_d
- return super(SvgPath, self).draw(color, stroke_width, stroke_dasharray)
-
-
-class SvgLine(SvgPath):
- # exactly same as SvgPath
- pass
-
-
-class SvgPolygon(SvgPath):
- def create_svg_d(self, origin, points):
- svg_d = super(SvgPolygon, self).create_svg_d(origin, points)
- svg_d += ' Z'
- return svg_d
-
-
-class SvgPart1Outline(SvgPolygon):
- def __init__(self, parent, points, bw_bf):
- super(SvgPart1Outline, self).__init__(parent, points)
- self.bw_bf = bw_bf
-
- def create_svg_d(self, origin, points, bw_bf=1):
- for i, p in enumerate(points):
- (x1, y1) = (p.x + origin.x, p.y + origin.y)
- if i == 0:
- d = 'M %f,%f' % (x1, y1)
- elif i == 7:
- d += ' L %f,%f' % (x1, y1)
- x2 = x1
- y2 = y1 + self.bw_bf
- elif i == 8:
- d += ' C %f,%f %f,%f %f,%f' % (x2, y2, x1, y2, x1, y1)
- else:
- d += ' L %f,%f' % (x1, y1)
-
- d += ' Z'
- return d
-
-
-class SvgNeedleHole(SvgPolygon):
- def __init__(self, parent, w, h, tf):
- '''
- w: width
- h: height
- tf: tilt factor
- '''
- self.w = w
- self.h = h
- self.tf = tf
-
- self.gen_points(self.w, self.h, self.tf)
- super(SvgNeedleHole, self).__init__(parent, self.points)
-
- def gen_points(self, w, h, tf):
- self.points = []
- self.points.append(Point(-w / 2, h * tf))
- self.points.append(Point( w / 2, h * (1 - tf)))
- self.points.append(Point( w / 2, -h * tf))
- self.points.append(Point(-w / 2, -h * (1 - tf)))
-
-
-class Part1(object):
- def __init__(self, parent,
- w1, w2, h1, h2, bw, bl, bf, dia1, d1, d2,
- needle_w, needle_h, needle_tf, needle_corner_rotation):
- self.parent = parent
- self.w1 = w1
- self.w2 = w2
- self.h1 = h1
- self.h2 = h2
- self.bw = bw
- self.bl = bl
- self.bf = bf
- self.dia1 = dia1
- self.d1 = d1
- self.d2 = d2
- self.needle_w = needle_w
- self.needle_h = needle_h
- self.needle_tf = needle_tf
- self.needle_corner_rotation = needle_corner_rotation
-
- # Group Creation
- attr = {inkex.addNS('label', 'inkscape'): 'Part1'}
- self.g = etree.SubElement(self.parent, 'g', attr)
-
- # drawing
- self.points_outline = self.create_points_outline()
- self.svg_outline = SvgPart1Outline(self.g, self.points_outline,
- (self.bw * self.bf))
- self.svg_hole = SvgCircle(self.g, self.dia1 / 2)
-
- self.vpoints_needle = self.create_needle_vpoints()
- self.svgs_needle_hole = []
- for v in self.vpoints_needle:
- svg_nh = SvgNeedleHole(self.g,
- self.needle_w,
- self.needle_h,
- self.needle_tf)
- self.svgs_needle_hole.append((svg_nh, v))
-
- def create_points_outline(self):
- #Generate the coordinates of the outer frame
-
- points = []
- (x0, y0) = (-(self.w2 / 2), 0)
-
- (x, y) = (x0, y0 + self.h1 + self.h2)
- points.append(Point(x, y))
-
- y = y0 + self.h1
- points.append(Point(x, y))
-
- x = -(self.w1 / 2)
- y = y0
- points.append(Point(x, y))
-
- x = self.w1 / 2
- points.append(Point(x, y))
-
- x = self.w2 / 2
- y += self.h1
- points.append(Point(x, y))
-
- y += self.h2
- points.append(Point(x, y))
-
- x = self.bw / 2
- points.append(Point(x, y))
-
- y += self.bl - self.bw / 2
- points.append(Point(x, y))
-
- x = -(self.bw / 2)
- points.append(Point(x, y))
-
- y = y0 + self.h1 + self.h2
- points.append(Point(x, y))
-
- return points
-
- def create_needle_vpoints(self):
- '''
- 針穴の点と方向を生成
- '''
- rad1 = math.atan((self.w2 - self.w1) / (2 * self.h1))
- rad1a = (math.pi - rad1) / 2
- a1 = self.d1 / math.tan(rad1a)
-
- rad2 = (math.pi / 2) - rad1
- rad2a = (math.pi - rad2) / 2
- a2 = self.d1 / math.tan(rad2a)
-
- #
- # summit
- #
- vpoints1 = []
- for i, p in enumerate(self.points_outline):
- (nx, ny) = (p.x, p.y)
- if i == 0:
- nx += self.d1
- ny -= self.d1 * 1.5
- vpoints1.append(Vpoint(nx, ny, 0))
- if i == 1:
- nx += self.d1
- ny += a1
- vpoints1.append(Vpoint(nx, ny, rad1))
- if i == 2:
- nx += a2
- ny += self.d1
- vpoints1.append(Vpoint(nx, ny, math.pi / 2))
- if i == 3:
- nx -= a2
- ny += self.d1
- vpoints1.append(Vpoint(nx, ny, (math.pi / 2) + rad2))
- if i == 4:
- nx -= self.d1
- ny += a1
- vpoints1.append(Vpoint(nx, ny, math.pi))
- if i == 5:
- nx -= self.d1
- ny -= self.d1 * 1.5
- vpoints1.append(Vpoint(nx, ny, math.pi))
- if i > 5:
- break
-
- # Generate a point that completes a vertex
- vpoints2 = []
- for i in range(len(vpoints1)-1):
- d = vpoints1[i].distance(vpoints1[i+1])
- n = int(abs(round(d / self.d2)))
- for p in self.split_vpoints(vpoints1[i], vpoints1[i+1], n):
- vpoints2.append(p)
-
- vpoints2.insert(0, vpoints1[0])
- return vpoints2
-
- def split_vpoints(self, v1, v2, n):
- #v1, v2 Generate a list by dividing the space between the two into n pieces
-
- if n == 0:
- return [v1]
- (dx, dy) = ((v2.x - v1.x) / n, (v2.y - v1.y) / n)
-
- v = []
- for i in range(n):
- v.append(Vpoint(v1.x + dx * (i + 1),
- v1.y + dy * (i + 1),
- v1.rad))
- if self.needle_corner_rotation:
- v[-1].rad = (v1.rad + v2.rad) / 2
- return v
-
- def draw(self, origin):
- origin_base = Vpoint(origin.x + self.w2 / 2,
- origin.y,
- origin.rad)
- self.svg_outline.draw(origin_base, color='#0000FF')
-
- x = origin.x + self.w2 / 2
- y = origin.y + self.h1 + self.h2 + self.bl - self.bw / 2
- origin_hole = Point(x, y)
- self.svg_hole.draw(origin_hole, color='#FF0000')
-
- for (svg_nh, p) in self.svgs_needle_hole:
- origin_nh = Vpoint(origin.x + p.x + self.w2 / 2,
- origin.y + p.y,
- p.rad)
- svg_nh.draw(origin_nh, color='#FF0000')
-
-
-class Part2(object):
- def __init__(self, parent, part1, dia2):
- self.parent = parent
- self.part1 = part1
- self.dia2 = dia2
-
- # Group Creation
- attr = {inkex.addNS('label', 'inkscape'): 'Part2'}
- self.g = etree.SubElement(self.parent, 'g', attr)
-
- # outer frame > Mirroring the points_outline in Part1, and use the first six points.
- self.points_outline = []
- for i in range(6):
- self.points_outline.append(self.part1.points_outline[i].mirror())
-
- self.svg_outline = SvgPolygon(self.g, self.points_outline)
-
- # clasp
- self.svg_hole = SvgCircle(self.g, self.dia2 / 2)
-
- # pinhole -> Mirroring the vpoints_needle in Part1
- self.svgs_needle_hole = []
- for v in self.part1.vpoints_needle:
- v.mirror()
- # Mirror also SvgNeedleHole
- svg_nh = SvgNeedleHole(self.g,
- self.part1.needle_w,
- self.part1.needle_h,
- self.part1.needle_tf)
- svg_nh.mirror()
- self.svgs_needle_hole.append((svg_nh, v))
-
- def draw(self, origin):
- origin_base = Vpoint(origin.x + self.part1.w2 / 2,
- origin.y, origin.rad)
- self.svg_outline.draw(origin_base, color='#0000FF')
-
- x = origin.x + self.part1.w2 / 2
- y = origin.y + self.part1.h1 + self.part1.h2
- y -= (self.svg_hole.r + self.part1.d1)
- origin_hole = Vpoint(x, y, origin.rad)
- self.svg_hole.draw(origin_hole, color='#FF0000')
-
- for (svg_nh, p) in self.svgs_needle_hole:
- origin_nh = Vpoint(origin.x + p.x + self.part1.w2 / 2,
- origin.y + p.y,
- p.rad)
- svg_nh.draw(origin_nh, color='#FF0000')
-
-
-class PliersCover(inkex.Effect):
- DEF_OFFSET_X = 20
- DEF_OFFSET_Y = 20
-
- def __init__(self):
- inkex.Effect.__init__(self)
- self.arg_parser.add_argument("--tabs")
- self.arg_parser.add_argument("--w1", type=float)
- self.arg_parser.add_argument("--w2", type=float)
- self.arg_parser.add_argument("--h1", type=float)
- self.arg_parser.add_argument("--h2", type=float)
- self.arg_parser.add_argument("--bw", type=float)
- self.arg_parser.add_argument("--bl", type=float)
- self.arg_parser.add_argument("--bf", type=float)
- self.arg_parser.add_argument("--dia1", type=float)
- self.arg_parser.add_argument("--dia2", type=float)
- self.arg_parser.add_argument("--d1", type=float)
- self.arg_parser.add_argument("--d2", type=float)
- self.arg_parser.add_argument("--needle_w", type=float)
- self.arg_parser.add_argument("--needle_h", type=float)
- self.arg_parser.add_argument("--needle_tf", type=float)
- self.arg_parser.add_argument("--needle_corner_rotation", type=inkex.Boolean, default=True)
-
- def effect(self):
- # inkex.errormsg('view_center=%s' % str(self.view_center))
- # inkex.errormsg('selected=%s' % str(self.selected))
-
- # parameters
- opt = self.options
-
- #
- # error check
- #
- if opt.w1 >= opt.w2:
- msg = "Error: w1(%d) > w2(%d) !" % (opt.w1, opt.w2)
- inkex.errormsg(msg)
- return
-
- if opt.dia1 >= opt.bw:
- msg = "Error: dia1(%d) >= bw(%d) !" % (opt.dia1, opt.bw)
- inkex.errormsg(msg)
- return
-
- #
- # draw
- #
- origin_vpoint = Vpoint(self.DEF_OFFSET_X, self.DEF_OFFSET_Y)
-
- # Group Creation
- attr = {inkex.addNS('label', 'inkscape'): 'PliersCover'}
- self.g = etree.SubElement(self.svg.get_current_layer(), 'g', attr)
-
- part1 = Part1(self.g,
- opt.w1, opt.w2, opt.h1, opt.h2,
- opt.bw, opt.bl, opt.bf, opt.dia1,
- opt.d1, opt.d2,
- opt.needle_w, opt.needle_h, opt.needle_tf,
- opt.needle_corner_rotation)
- part1.draw(origin_vpoint)
-
- origin_vpoint.x += opt.w2 + 10
-
- part2 = Part2(self.g, part1, opt.dia2)
- part2.draw(origin_vpoint)
-
-if __name__ == '__main__':
- PliersCover().run()
\ No newline at end of file
diff --git a/extensions/fablabchemnitz/triangulation.inx b/extensions/fablabchemnitz/triangulation.inx
index 392479e6..974c7d89 100644
--- a/extensions/fablabchemnitz/triangulation.inx
+++ b/extensions/fablabchemnitz/triangulation.inx
@@ -1,5 +1,5 @@
-
+
Image Triangulation
fablabchemnitz.de.triangulation