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