Merge branch 'master' of lastycah.fablabchemnitz.de:MarioVoigt/mightyscape-1.X

This commit is contained in:
leyghisbb 2020-12-12 23:05:29 +01:00
commit ef06056348
29 changed files with 2425 additions and 608 deletions

View File

@ -1,4 +0,0 @@
#!/bin/sh
KABEJA_HOME=`dirname $0`
java -jar $KABEJA_HOME/kabeja-dxf2svg.jar "$1"

View File

@ -1,3 +0,0 @@
@echo off
set KABEJA_HOME=%0\..
java -jar %KABEJA_HOME%\kabeja-dxf2svg.jar %1

View File

@ -1,22 +0,0 @@
<inkscape-extension>
<name>Kabeja DXF Input</name>
<id>org.kabeja.inkscape.import.dxf</id>
<dependency type="extension">org.inkscape.input.svg</dependency>
<param name="description" type="description">Choose the layout for the import.</param>
<param name="bounds-rule" gui-text="Layout" type="enum">
<item>Modelspace</item>
<item>Modelspace-Limits</item>
<item>Paperspace</item>
<item>Paperspace-Limits</item>
</param>
<input>
<extension>.dxf</extension>
<mimetype>image/vnd.dxf</mimetype>
<filetypename>Kabeja-AutoCAD DXF (*.dxf)</filetypename>
<filetypetooltip>Import AutoCAD's Document Exchange Format</filetypetooltip>
<output_extension>org.inkscape.output.svg</output_extension>
</input>
<script>
<command reldir="extensions" >kabeja</command>
</script>
</inkscape-extension>

View File

@ -1,4 +0,0 @@
#!/bin/sh
KABEJA_HOME=`dirname $0`
java -jar $KABEJA_HOME/kabeja-dxf2svg.jar "$1"

View File

@ -1,3 +0,0 @@
@echo off
set KABEJA_HOME=%0\..
java -jar %KABEJA_HOME%\kabeja-dxf2svg.jar %1

View File

@ -1,22 +0,0 @@
<inkscape-extension>
<name>Kabeja DXF Input</name>
<id>org.kabeja.inkscape.import.dxf</id>
<dependency type="extension">org.inkscape.input.svg</dependency>
<param name="description" type="description">Choose the layout for the import.</param>
<param name="bounds-rule" gui-text="Layout" type="enum">
<item>Modelspace</item>
<item>Modelspace-Limits</item>
<item>Paperspace</item>
<item>Paperspace-Limits</item>
</param>
<input>
<extension>.dxf</extension>
<mimetype>image/vnd.dxf</mimetype>
<filetypename>Kabeja-AutoCAD DXF (*.dxf)</filetypename>
<filetypetooltip>Import AutoCAD's Document Exchange Format</filetypetooltip>
<output_extension>org.inkscape.output.svg</output_extension>
</input>
<script>
<command reldir="extensions" >kabeja</command>
</script>
</inkscape-extension>

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape">
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Ids To Text</name>
<id>org.inkscape.render.ids_to_text</id>
<id>fablabchemnitz.de.ids_to_text</id>
<param name="fontsize" type="int" min="1" max="1000" gui-text="Font size (px):">10</param>
<param name="color" type="color" appearance="colorbutton" gui-text="Color (in hex)">255</param>
<param name="font" type="string" gui-text="Font">Roboto</param>

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>IFS Fractals</name>
<id>fablabchemnitz.de.ifs_fractals</id>
<param name="tab" type="notebook">
<page name="Options" gui-text="Options">
<param name="iter" type="int" min="0" max="100" gui-text="Number of iterations:">3</param>
<label appearance="header">Transform Matrices</label>
<param name="tab" type="notebook">
<page name="0" gui-text="1">
<param name="xform0" type="bool" gui-text="Enabled:">true</param>
<param name="A0" type="float" min="-10000" max="10000" precision="3" gui-text="A:">0.5</param>
<param name="B0" type="float" min="-10000" max="10000" precision="3" gui-text="B:">0</param>
<param name="C0" type="float" min="-10000" max="10000" precision="3" gui-text="C:">0</param>
<param name="D0" type="float" min="-10000" max="10000" precision="3" gui-text="D:">0.5</param>
<param name="E0" type="float" min="-10000" max="10000" precision="3" gui-text="E:">0</param>
<param name="F0" type="float" min="-10000" max="10000" precision="3" gui-text="F:">0</param>
</page>
<page name="1" gui-text="2">
<param name="xform1" type="bool" gui-text="Enabled:">false</param>
<param name="A1" type="float" min="-10000" max="10000" precision="3" gui-text="A:">0.5</param>
<param name="B1" type="float" min="-10000" max="10000" precision="3" gui-text="B:">0</param>
<param name="C1" type="float" min="-10000" max="10000" precision="3" gui-text="C:">0</param>
<param name="D1" type="float" min="-10000" max="10000" precision="3" gui-text="D:">0.5</param>
<param name="E1" type="float" min="-10000" max="10000" precision="3" gui-text="E:">1</param>
<param name="F1" type="float" min="-10000" max="10000" precision="3" gui-text="F:">0</param>
</page>
<page name="2" gui-text="3">
<param name="xform2" type="bool" gui-text="Enabled:">false</param>
<param name="A2" type="float" min="-10000" max="10000" precision="3" gui-text="A:">0.5</param>
<param name="B2" type="float" min="-10000" max="10000" precision="3" gui-text="B:">0</param>
<param name="C2" type="float" min="-10000" max="10000" precision="3" gui-text="C:">0</param>
<param name="D2" type="float" min="-10000" max="10000" precision="3" gui-text="D:">0.5</param>
<param name="E2" type="float" min="-10000" max="10000" precision="3" gui-text="E:">0.5</param>
<param name="F2" type="float" min="-10000" max="10000" precision="3" gui-text="F:">1</param>
</page>
<page name="3" gui-text="4">
<param name="xform3" type="bool" gui-text="Enabled:">false</param>
<param name="A3" type="float" min="-10000" max="10000" precision="3" gui-text="A:">0.5</param>
<param name="B3" type="float" min="-10000" max="10000" precision="3" gui-text="B:">0</param>
<param name="C3" type="float" min="-10000" max="10000" precision="3" gui-text="C:">0</param>
<param name="D3" type="float" min="-10000" max="10000" precision="3" gui-text="D:">0.5</param>
<param name="E3" type="float" min="-10000" max="10000" precision="3" gui-text="E:">0</param>
<param name="F3" type="float" min="-10000" max="10000" precision="3" gui-text="F:">0</param>
</page>
<page name="4" gui-text="5">
<param name="xform4" type="bool" gui-text="Enabled:">false</param>
<param name="A4" type="float" min="-10000" max="10000" precision="3" gui-text="A:">0.5</param>
<param name="B4" type="float" min="-10000" max="10000" precision="3" gui-text="B:">0</param>
<param name="C4" type="float" min="-10000" max="10000" precision="3" gui-text="C:">0</param>
<param name="D4" type="float" min="-10000" max="10000" precision="3" gui-text="D:">0.5</param>
<param name="E4" type="float" min="-10000" max="10000" precision="3" gui-text="E:">0</param>
<param name="F4" type="float" min="-10000" max="10000" precision="3" gui-text="F:">0</param>
</page>
</param>
</page>
<page name="Help" gui-text="Help">
<label>
This performs an Iterated Function System by repeating one or more duplicate-and-transform operations on the selected objects.
</label>
<label>
The transformations are specified using matrices, in the same form as the transformation dialog, each of which should be contractive (i.e., shrinking).
</label>
<label>
For example, if you set N transforms, it will make N duplicates and transform each in the first iteration, and then N^2 duplicates of those, and so on, for a total of (N^(I+1)-1)/(N-1) duplicates.
</label>
</page>
</param>
<effect>
<object-type>all</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Shape/Pattern from Generator"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">ifs_fractals.py</command>
</script>
</inkscape-extension>

View File

@ -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()

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>InkPACKING</name>
<id>fablabchemnitz.de.inkpacking</id>
<param name="pages" type="notebook">

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Longest Continuous Path</name>
<id>fablabchemnitz.de.optimize_paths</id>
<param name="tolerance" type="float" min="0.0" max="100.0" gui-text="Merge Tolerance:">0.10</param>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Maze</name>
<id>fablabchemnitz.de.maze</id>
<param name="verti" type="int" min="2" max="1000" gui-text="Height">20</param>
<param name="horiz" type="int" min="2" max="1000" gui-text="Length">20</param>
<param name="size" type="float" max="100.0" gui-text="Cell Size">10.0</param>
<param name="width" type="float" gui-text="Line Width">1.0</param>
<param name="algo" type="optiongroup" appearance="minimal" gui-text="Algorithm">
<option value="kruskal">Kruskal</option>
<option value="recursive_backtrack">Recursive Backtrack</option>
<option value="empty">Empty</option>
<option value="full">Full</option>
</param>
<param name="use2" type="description" xml:space="preserve">This script will generate a maze according to a certain algorithm.</param>
<effect>
<object-type>path</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Shape/Pattern from Generator" />
</submenu>
</effects-menu>
</effect>
<script>
<command reldir="inx" interpreter="python">laby.py</command>
</script>
</inkscape-extension>

View File

@ -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()

View File

@ -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

View File

@ -35,7 +35,7 @@
<param name="xz_mirror" type="bool" gui-text="XZ-Mirror">false</param>
<param name="scale" type="float" min="0.0001" max="10000.0" precision="4" gui-text="Scale">1.0</param>
</page>
<page name="meshfixing" gui-text="About">
<page name="about" gui-text="About">
<label>Papercraft Unfold - by Mario Voigt / Stadtfabrikanten e.V. (2020)</label>
<label appearance="url">https://fablabchemnitz.de</label>
<label>License: GNU GPL v3</label>
@ -53,4 +53,4 @@
<script>
<command location="inx" interpreter="python">papercraft_unfold.py</command>
</script>
</inkscape-extension>
</inkscape-extension>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Purge Duplicate Path Nodes</name>
<id>fablabchemnitz.de.purge_duplicate_path_nodes</id>
<param name="help" type="description">Remove duplicate nodes from selected paths.</param>
<param name="minUse" type="boolean" gui-text="Also remove segments with length less than specified length">false</param>
<param name="minlength" indent="6" type="float" precision="1" min="0" max="9999" gui-text="Minimum segment length">0.01</param>
<param name="joinEnd" type="boolean" gui-text="Join end nodes of each subpath if distance less or equal to">false</param>
<param name="maxdist" indent="6" type="float" precision="1" min="0" max="9999" gui-text="Max opening">0.01</param>
<param name="unitinfo" type="description">Unit as defined in document (File->Document Properties).</param>
<effect>
<object-type>path</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Nesting/Cut Optimization"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">removeDuplicateNodes.py</command>
</script>
</inkscape-extension>

View File

@ -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()

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension ns="http://www.inkscape.org/namespace/inkscape/extension">
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Vertical / Horizontal Scale</name>
<id>fablabchemnitz.de.render_scale</id>
<param name="tab" type="notebook">

View File

@ -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

View File

@ -0,0 +1,197 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Shapes</name>
<id>fablabchemnitz.de.shapes</id>
<param name="help-ren" type="description" xml:space="preserve">Create shapes using the bounding box or the node position of the selected objects</param>
<param name="tab" type="notebook">
<page name="bbox" gui-text="From bounding box">
<param type="notebook" name="tab_from_bb">
<page name="chamfer" gui-text="From corners">
<!--image>shapes-squared-plugin.svg</image-->
<hbox>
<param type="optiongroup" name="chamfertype" gui-text="Type:">
<item value="chamfer">Chamfer</item>
<item value="rect">Rect inside</item>
<item value="round">Round inside</item>
<item value="starcorners">Star</item>
<item value="crosscornersquad">Crossed corners quads</item>
<item value="crosscornerstri">Crossed corners tris</item>
<item value="crosscornersround">Crossed corners round</item>
<item value="pillowrect">Pillow</item>
<item value="spiralrect">Rect spiral</item>
</param>
<spacer />
<spacer />
<spacer />
<vbox>
<param type="boolean" name="fromCornersInv" gui-text="Inverse (for chamfer and round)">false</param>
<param type="float" name="size" min="0.0" max="1000.0" gui-text="Size:">20</param>
</vbox>
</hbox>
</page>
<page name="mid" gui-text="From the middle">
<!--image>shapes-squared-plugin.svg</image-->
<hbox>
<param type="optiongroup" name="midtype" gui-text="Type:">
<item value="rombus">Rombus</item>
<item value="cross">Cross</item>
<item value="starcenter">Star</item>
<item value="pillowrombus">Pillow</item>
</param>
<spacer />
<spacer />
<spacer />
<vbox>
<param type="float" name="midsize" min="0.0" max="1000.0" gui-text="Size:">20</param>
</vbox>
</hbox>
</page>
<page name="spikes" gui-text="Spikes">
<hbox>
<vbox>
<param name="spikestype" type="enum" appearance="minimal" gui-text="Shape:">
<item value="tri">Triangle</item>
<item value="trirect">Rectangle</item>
<item value="squ">Square</item>
<item value="rnd">Rounded</item>
<item value="wav">Wave</item>
</param>
<param name="spikesize" type="float" min="0.1" max="1000.0" gui-text="Size:">2.0</param>
<param name="spikesep" type="float" min="-1000.0" max="1000.0" gui-text="Distance:">0.0</param>
<param name="spikeheight" type="float" min="-1000.0" max="1000.0" gui-text="Height:">0.0</param>
</vbox>
<vbox>
<param name="spikesdir" type="enum" appearance="minimal" gui-text="Direction:" indent="1">
<item value="out">Outside</item>
<item value="ins">Inside</item>
<item value="alt">Alternate</item>
</param>
<param name="spikesdirt" type="enum" appearance="minimal" gui-text="Top:" indent="1">
<item value="pre">Predefined</item>
<item value="non">None</item>
<item value="out">Outside</item>
<item value="ins">Inside</item>
<item value="alt">Alternate</item>
</param>
<param name="spikesdirr" type="enum" appearance="minimal" gui-text="Right:" indent="1">
<item value="pre">Predefined</item>
<item value="non">None</item>
<item value="out">Outside</item>
<item value="ins">Inside</item>
<item value="alt">Alternate</item>
</param>
<param name="spikesdirb" type="enum" appearance="minimal" gui-text="Bottom:" indent="1">
<item value="pre">Predefined</item>
<item value="non">None</item>
<item value="out">Outside</item>
<item value="ins">Inside</item>
<item value="alt">Alternate</item>
</param>
<param name="spikesdirl" type="enum" appearance="minimal" gui-text="Left:" indent="1">
<item value="pre">Predefined</item>
<item value="non">None</item>
<item value="out">Outside</item>
<item value="ins">Inside</item>
<item value="alt">Alternate</item>
</param>
</vbox>
</hbox>
</page>
<page name="triangles" gui-text="Triangles">
<param name="tritype" type="optiongroup" gui-text="Triangle type:">
<item value="isosceles">Isosceles</item>
<item value="equi">Equilateral</item>
<item value="rect">Rectangle</item>
<item value="trii">From 3 nodes: Inscribed triangle</item>
<item value="circi">From 3 nodes: Inscribed circle</item>
<item value="circe">From 3 nodes: Bounding circle</item>
</param>
<param name="trihside" type="boolean" gui-text="Right side aligned">false</param>
<param name="trivside" type="boolean" gui-text="Top side aligned">false</param>
</page>
<page name="arrow" gui-text="Arrows">
<param name="arrowtype" type="optiongroup" gui-text="Arrow type:">
<item value="arrowfilled">Filled</item>
<item value="arrowstick">Stick</item>
</param>
<param name="headWidth" type="float" min="0.1" max="1000.0" gui-text="Head width:">20.0</param>
<param name="headHeight" type="float" min="0.1" max="1000.0" gui-text="Head height:">40.0</param>
<param name="arrowWidth" type="float" min="0.1" max="1000.0" gui-text="Tail width:">10.0</param>
</page>
</param>
</page>
<page name="extra" gui-text="Join circles">
<param name="joincirctype" type="enum" gui-text="Function:">
<item value="trapecio">Rect</item>
<item value="blob">Blob</item>
<item value="oval">Oval</item>
</param>
<param name="joinradius" type="float" min="0.0" max="1000.0" gui-text="Join radius:">0.0</param>
</page>
<page name="nodes" gui-text="From nodes">
<hbox>
<param name="obj" type="enum" gui-text="Add:">
<item value="s">Square</item>
<item value="c">Circle</item>
<item value="number">Numerate</item>
<item value="coords">Nodes coordinates</item>
<item value="obj">Object</item>
</param>
<spacer />
<spacer />
<spacer />
<param name="objsize" type="float" min="0.01" max="1000.0" gui-text="Diameter/Side size:">3</param>
</hbox>
<param name="objid" type="string" gui-text="Object ID"></param>
<hbox>
<label appearance="header">Position - </label>
<param name="posh" type="enum" gui-text="Horizontal:">
<item value="0">Center</item>
<item value="1">Left</item>
<item value="-1">Right</item>
</param>
<param name="posv" type="enum" gui-text="Vertical:">
<item value="0">Center</item>
<item value="-1">Bottom</item>
<item value="1">Top</item>
</param>
</hbox>
<hbox>
<param name="reducey" type="float" min="0.00" max="1000.0" gui-text="Reduce size in Y axis (%):">0.00</param>
<param name="ordery" type="boolean" gui-text="Z-index by Y position">false</param>
</hbox>
<param name="fntsize" type="float" min="0.1" max="100.0" gui-text="Font size (for numerate/coordinates):">10</param>
<param name="maxdecimals" type="int" min="1" max="6" gui-text="Number of decimals (for coordinates):">6</param>
</page>
</param>
<hbox>
<vbox>
<param name="incdec" type="float" min="-1000.0" max="1000.0" gui-text="Increase/decrease size:">0.0</param>
</vbox>
<vbox>
<param name="unit" gui-text="Unit for values:" type="enum" appearance="minimal">
<item value="px">px</item>
<item value="pt">pt</item>
<item value="in">in</item>
<item value="cm">cm</item>
<item value="mm">mm</item>
</param>
</vbox>
</hbox>
<hbox>
<param name="squareselection" type="boolean" gui-text="Make the result object square">false</param>
<param name="copyfill" type="boolean" gui-text="Copy fill from selected">false</param>
</hbox>
<param name="deleteorigin" type="boolean" gui-text="Delete origin object">false</param>
<effect needs-live-preview="true">
<object-type>all</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Shape/Pattern from existing Path(s)" />
</submenu>
</effects-menu>
</effect>
<script>
<command reldir="inx" interpreter="python">shapes.py</command>
</script>
</inkscape-extension>

View File

@ -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()

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Snap Object Points</name>
<id>fablabchemnitz.de.snap_objects</id>
<param name="max_dist" type="float" min="1" max="9999" precision="2" gui-text="Maximum snap distance">25</param>
<param name="controls" type="bool" gui-text="Snap control points">true</param>
<param name="ends" type="bool" gui-text="Snap endpoints">true</param>
<param name="first_only" type="bool" gui-text="Modify only the first selected path">false</param>
<label>This effect snaps points in each selected object to nearby points in other selected objects.</label>
<effect>
<object-type>path</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Modify existing Path(s)"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">snap_objects.py</command>
</script>
</inkscape-extension>

View File

@ -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()

View File

@ -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

View File

@ -1,53 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Tool Covers</name>
<id>fablabchemnitz.de.tool_covers</id>
<param name="tabs" type="notebook">
<page name="parameters" gui-text="Basic parameters">
<param name="title_base" type="description">base</param>
<param name="w1" type="float" min="1" max="200" indent="2" gui-text="Width of tip (w1)[mm]">20</param>
<param name="w2" type="float" min="2" max="200" indent="2" gui-text="Maximum width (w2)[mm]">40</param>
<param name="h1" type="float" min="1" max="200" indent="2" gui-text="Tip length (h1)[mm]">40</param>
<param name="h2" type="float" min="1" max="200" indent="2" gui-text="Length other than tip (h2)[mm]">20</param>
<param name="title_base" type="description">band</param>
<param name="bw" type="float" min="1" max="30" indent="2" gui-text="Band width (bw)[mm]">15</param>
<param name="bl" type="float" min="1" max="90" indent="2" gui-text="Band length (bl)[mm]">30</param>
<param name="title_base" type="description">fastener</param>
<param name="dia1" type="float" min="1" max="50" indent="2" gui-text="Fastener female diameter (dia1)[mm]">10</param>
<param name="dia2" type="float" min="1" max="50" indent="2" gui-text="Fastener male diameter (dia2)[mm]">10</param>
<param name="title_nuishiro" type="description">margin and needle hole</param>
<param name="d1" type="float" min="1" max="5" indent="2" gui-text="Sew it up (d1)[mm]">3</param>
<param name="d2" type="float" min="1" max="10" indent="2" gui-text="Needle hole spacing (d2)[mm]">3</param>
</page>
<page name="details" gui-text="Detailed parameters">
<param name="title_base" type="description">band</param>
<param name="bf" type="float" min="0.1" max="1" indent="2" gui-text="curve coefficient (bf)">0.7</param>
<param name="title_nuishiro" type="description">needle hole</param>
<param name="needle_w" type="float" min="0.5" max="4" indent="2" gui-text="Acupoint width (needle_w)[mm]">1</param>
<param name="needle_h" type="float" min="0.5" max="4" indent="2" gui-text="Height of the needle hole (needle_h)[mm]">1</param>
<param name="needle_tf" type="float" min="-2" max="2" indent="2" gui-text="Needle slope coefficient (needle_tf)">1</param>
<param name="needle_corner_rotation" type="bool" indent="2" gui-text="Angle Tilt Adjustment">true</param>
</page>
<page name="about" gui-text="PliersCover - about">
<param name="version" type="description" xml:space="preserve">PiersCover Inkscape extension
Version 0.92
by Yoichi Tanibayashi
https://ytani01.github.io/PliersCover/
</param>
</page>
</param>
<effect needs-live-preview="true">
<object-type>all</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Shape/Pattern from Generator"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">tool_covers.py</command>
</script>
</inkscape-extension>

View File

@ -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()

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Image Triangulation</name>
<id>fablabchemnitz.de.triangulation</id>
<param name="tab" type="notebook">