Initial commit. First part of extensions. More are coming back again
soon.
110
extensions/fablabchemnitz/000_about_fablabchemnitz.svg
Normal file
After Width: | Height: | Size: 29 KiB |
10
extensions/fablabchemnitz/000_update-json.sh
Executable file
@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
clear
|
||||||
|
|
||||||
|
for folder in */ ; do
|
||||||
|
if [[ -f "${folder}/meta.json" ]]; then
|
||||||
|
sed -i 's/github.com\/vmario89/github.com\/eridur-de/g' ${folder}/meta.json
|
||||||
|
sed -i 's/mightyscape-1.X/mightyscape-1.2/g' ${folder}/meta.json
|
||||||
|
|
||||||
|
fi
|
||||||
|
done
|
114
extensions/fablabchemnitz/000_validate.sh
Executable file
@ -0,0 +1,114 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
clear
|
||||||
|
|
||||||
|
|
||||||
|
echo "--> Validating inx files with xmllint. Only errors are printed to console"
|
||||||
|
for folder in */ ; do
|
||||||
|
xmllint --noout --relaxng ./inkscape.extension.rng ${folder}*.inx > /dev/null 2>> 000_xmllint.out
|
||||||
|
done
|
||||||
|
grep -v "validates\|warning: failed to load external entity" 000_xmllint.out; rm 000_xmllint.out
|
||||||
|
|
||||||
|
|
||||||
|
#complete set of meta information
|
||||||
|
AGGLOMERATED_JSON=""
|
||||||
|
for folder in */ ; do
|
||||||
|
if [[ ! -f "${folder}meta.json" ]]; then
|
||||||
|
echo "meta.json missing for ${folder}"
|
||||||
|
else
|
||||||
|
JSON_OKAY=$(jq -e . ${folder}meta.json)
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo ${AGGLOMERATED_JSON} > /tmp/prevJson
|
||||||
|
AGGLOMERATED_JSON=$(jq -s ".[0] + .[1]" /tmp/prevJson ${folder}meta.json)
|
||||||
|
else
|
||||||
|
echo Format error in ${folder}meta.json
|
||||||
|
fi
|
||||||
|
#DEBUG
|
||||||
|
#cat ${folder}meta.json | jq
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
#print overall json
|
||||||
|
#echo $AGGLOMERATED_JSON | jq
|
||||||
|
|
||||||
|
|
||||||
|
echo "--> Show unique license kinds used:"
|
||||||
|
echo $AGGLOMERATED_JSON | jq -r '.[]|{license}|.[]' | sort | uniq -c
|
||||||
|
|
||||||
|
|
||||||
|
echo "--> show unique list of involved contributors (thanks/credits):"
|
||||||
|
#echo $AGGLOMERATED_JSON | jq -r '.[]|{main_authors}|.[]|.[]' | sort | uniq -c
|
||||||
|
echo $AGGLOMERATED_JSON | jq -r '.[]|{main_authors}|.[]|.[]' | sort | uniq
|
||||||
|
|
||||||
|
|
||||||
|
#show extensions which are in gallery
|
||||||
|
GALLERY_EXTENSIONS=$(echo $AGGLOMERATED_JSON | jq -r '.[]|{inkscape_gallery_url}|.[]' | sort | grep -v "null")
|
||||||
|
for GALLERY_EXTENSION in ${GALLERY_EXTENSIONS}; do
|
||||||
|
EXTENSION=$(echo ${AGGLOMERATED_JSON} | jq -r '.[]|select(.inkscape_gallery_url=="'$GALLERY_EXTENSION'")|{name}|.[]')
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "--> Count of inx files:"
|
||||||
|
INX=$(find ./ -type f -name "*.inx" | wc -l)
|
||||||
|
echo INX: $INX
|
||||||
|
|
||||||
|
|
||||||
|
echo "--> Count of extension folders:"
|
||||||
|
FOLDERS=$(ls -d */ | wc -l)
|
||||||
|
echo FOLDERS: $FOLDERS
|
||||||
|
|
||||||
|
|
||||||
|
README="../../README.md"
|
||||||
|
#replace values in README.md
|
||||||
|
sed -i 's/\*\*.* extension folders\*\*/\*\*'${FOLDERS}' extension folders\*\*/g' ${README}
|
||||||
|
sed -i 's/\*\* with .* \.inx files\*\*/\*\* with \*\*'${INX}' \.inx files\*\*/g' ${README}
|
||||||
|
|
||||||
|
|
||||||
|
echo "Removing unrequired pyc cache files"
|
||||||
|
find . -type d -name "__pycache__" -exec rm -rf {} \; > /dev/null
|
||||||
|
|
||||||
|
|
||||||
|
read -p "Update zip files for zipmirror?" -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Building extension zip files for zipmirror"
|
||||||
|
TARGETDIR="../../../mightyscape-1.2-zipmirror"
|
||||||
|
mkdir -p $TARGETDIR > /dev/null 2>&1
|
||||||
|
|
||||||
|
for EXTENSION in */; do
|
||||||
|
EXTENSION="${EXTENSION%/}" #strip trailing slash
|
||||||
|
EXTRA=""
|
||||||
|
DEPS=$(jq -r 'try .[]|.dependent_extensions|.[]' ${EXTENSION}/meta.json)
|
||||||
|
DEPS=$(echo $DEPS|tr -d '\n')
|
||||||
|
#if dependencies are not empty, then ...
|
||||||
|
if [[ ! -z $DEPS ]]; then
|
||||||
|
EXTRA="$DEPS"
|
||||||
|
fi
|
||||||
|
ZIPFILE=$TARGETDIR/$EXTENSION.zip
|
||||||
|
echo "--> creating/updating $ZIPFILE"
|
||||||
|
zip -ru $ZIPFILE $EXTENSION/ 000_about_fablabchemnitz.svg $EXTRA > /dev/null 2>&1
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
read -p "Build local gallery extension zip files?" -n 1 -r
|
||||||
|
echo
|
||||||
|
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||||
|
echo "Building Inkscape gallery extension zip files"
|
||||||
|
TARGETDIR="../000_Inkscape_Gallery"
|
||||||
|
mkdir -p $TARGETDIR > /dev/null 2>&1
|
||||||
|
|
||||||
|
#show extensions which are in gallery
|
||||||
|
GALLERY_EXTENSIONS=$(echo $AGGLOMERATED_JSON | jq -r '.[]|{inkscape_gallery_url}|.[]' | sort | grep -v "null")
|
||||||
|
for GALLERY_EXTENSION in ${GALLERY_EXTENSIONS}; do
|
||||||
|
EXTENSION="$(echo ${AGGLOMERATED_JSON} | jq -r '.[]|select(.inkscape_gallery_url=="'$GALLERY_EXTENSION'")|{path}|.[]')"
|
||||||
|
EXTRA=""
|
||||||
|
DEPS=$(jq -r 'try .[]|.dependent_extensions|.[]' ${EXTENSION}/meta.json)
|
||||||
|
DEPS=$(echo $DEPS|tr -d '\n')
|
||||||
|
#if dependencies are not empty, then ...
|
||||||
|
if [[ ! -z $DEPS ]]; then
|
||||||
|
EXTRA="$DEPS"
|
||||||
|
fi
|
||||||
|
ZIPFILE=$TARGETDIR/$EXTENSION.zip
|
||||||
|
rm $ZIPFILE > /dev/null 2>&1
|
||||||
|
echo "--> creating/updating $ZIPFILE"
|
||||||
|
zip -ru $ZIPFILE $EXTENSION/ 000_about_fablabchemnitz.svg $EXTRA > /dev/null 2>&1
|
||||||
|
done
|
||||||
|
fi
|
24
extensions/fablabchemnitz/affine_spirals/affine_spirals.inx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Affine Spirals</name>
|
||||||
|
<id>fablabchemnitz.de.affine_spirals</id>
|
||||||
|
<param name="active-tab" type="notebook">
|
||||||
|
<page name="title" gui-text="Settings">
|
||||||
|
<param name="num_lines" type="int" min="1" max="100" gui-text="Depth">3</param>
|
||||||
|
<param name="num_petals" type="int" min="2" max="100" gui-text="petals">2</param>
|
||||||
|
<param name="shrink_ratio" type="float" min="0.01" max="0.99" precision="2" gui-text="shrink factor">0.8</param>
|
||||||
|
<param name="noses" type="bool" gui-text="Enabled noses">true</param>
|
||||||
|
</page>
|
||||||
|
</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz Shape Generators">
|
||||||
|
<submenu name="Puzzles/Mazes/Nests"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">affine_spirals.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
100
extensions/fablabchemnitz/affine_spirals/affine_spirals.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
from lxml import etree
|
||||||
|
from math import cos, sin, pi, exp
|
||||||
|
__version__ = '0.1'
|
||||||
|
|
||||||
|
def line(npts=40, x0=0, y0=0, delta=.5, sgn=1):
|
||||||
|
#returns a list of points on a line (y = +/- x + c) starting at x0,y0
|
||||||
|
return [ (x0 + delta*t, y0 + sgn*delta*t) for t in range(npts)]
|
||||||
|
|
||||||
|
def ff(v, ww=.25, ds=.4):
|
||||||
|
#covering map from R^2 ro punctured plane
|
||||||
|
x,y = v
|
||||||
|
r,u = exp(-ds*x), cos(pi*ww*y) + 1J*sin(pi*ww*y)
|
||||||
|
return r*u
|
||||||
|
|
||||||
|
def mk_plugs(pts, noses):
|
||||||
|
#returns a list of complex representing a plug type segment
|
||||||
|
segs = [fit_plug(end_pts, noses) for end_pts in zip(pts,pts[1:]) ]
|
||||||
|
tmp = []
|
||||||
|
for seg in segs:
|
||||||
|
tmp.extend(seg)
|
||||||
|
return tmp
|
||||||
|
|
||||||
|
def fit_plug(ss, noses):
|
||||||
|
a,b = ss
|
||||||
|
rot = complex(b-a)
|
||||||
|
pts = [0,.45,.4 + .15*1J, .6 + .15*1J, .55, 1]
|
||||||
|
if noses is True:
|
||||||
|
return [rot*z + a for z in pts]
|
||||||
|
else:
|
||||||
|
return ss
|
||||||
|
|
||||||
|
def pts2curve(cplxs):
|
||||||
|
'''makes a polyline path element from a list of complex
|
||||||
|
'''
|
||||||
|
def cplx2pt(z):
|
||||||
|
return (z.real,z.imag)
|
||||||
|
|
||||||
|
scale = 200
|
||||||
|
data = [cplx2pt( scale*z ) for z in cplxs ]
|
||||||
|
pth = [ '%.2f, %.2f '%z for z in data]
|
||||||
|
return 'M '+ ''.join(pth)
|
||||||
|
|
||||||
|
class AffineSpirals(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("--num_lines", type=int, default=3)
|
||||||
|
pars.add_argument("--num_petals", type=int, default=3)
|
||||||
|
pars.add_argument("--shrink_ratio", type=float, default=3)
|
||||||
|
pars.add_argument("--active-tab")
|
||||||
|
pars.add_argument("--noses", type=inkex.Boolean, default=True)
|
||||||
|
|
||||||
|
def calc_unit_factor(self):
|
||||||
|
unit_factor = self.svg.unittouu(str(1.0) + self.options.units)
|
||||||
|
return unit_factor
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
path_stroke = '#DD0000' # take color from tab3
|
||||||
|
path_fill = 'none' # no fill - just a line
|
||||||
|
path_stroke_width = 1. # can also be in form '0.6mm'
|
||||||
|
page_id = self.options.active_tab # sometimes wrong the very first time
|
||||||
|
|
||||||
|
styles = [ {'stroke': path_stroke , 'fill': 'none', 'stroke-width': path_stroke_width},
|
||||||
|
{'stroke': 'none', 'fill': '#FFFF00', 'stroke-width': 0}]
|
||||||
|
|
||||||
|
styles = [str(inkex.Style(x)) for x in styles]
|
||||||
|
|
||||||
|
# This finds center of current view in inkscape
|
||||||
|
t = 'translate(%s,%s)' % (self.svg.namedview.center[0], self.svg.namedview.center[1])
|
||||||
|
|
||||||
|
# Make a nice useful name
|
||||||
|
g_attribs = {inkex.addNS('label','inkscape'): 'koch',
|
||||||
|
inkex.addNS('transform-center-x','inkscape'): str(0),
|
||||||
|
inkex.addNS('transform-center-y','inkscape'): str(0),
|
||||||
|
'transform': t,
|
||||||
|
'style' : styles[1],
|
||||||
|
'info':'N: '+str(self.options.num_lines) }
|
||||||
|
topgroup = etree.SubElement(self.svg.get_current_layer(), 'g', g_attribs)
|
||||||
|
|
||||||
|
NN = 2*self.options.num_lines
|
||||||
|
NP = self.options.num_petals
|
||||||
|
SF = 2*self.options.shrink_ratio
|
||||||
|
|
||||||
|
payload = []
|
||||||
|
for y in range(-NP,NP):
|
||||||
|
mpts = [ff(z,ww=1./NP, ds=SF) for z in line(npts=NN, y0=y)]
|
||||||
|
payload.append(mk_plugs(mpts, self.options.noses))
|
||||||
|
mpts = [ff(z,ww=1./NP, ds=SF) for z in line(npts=NN, y0=y,sgn=-1 )]
|
||||||
|
payload.append(mk_plugs(mpts, self.options.noses))
|
||||||
|
|
||||||
|
payload = [pts2curve(cc) for cc in payload]
|
||||||
|
payload = ' '.join(payload)
|
||||||
|
|
||||||
|
curve_attribs = { 'style': styles[0], 'd': payload}
|
||||||
|
etree.SubElement(topgroup, inkex.addNS('path','svg'), curve_attribs)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
AffineSpirals().run()
|
21
extensions/fablabchemnitz/affine_spirals/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Affine Spirals",
|
||||||
|
"id": "fablabchemnitz.de.affine_spirals",
|
||||||
|
"path": "affine_spirals",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "Affine Torus 0.0",
|
||||||
|
"original_id": "githubacct.uniqueid.affine_torus",
|
||||||
|
"license": "MIT License",
|
||||||
|
"license_url": "https://github.com/macbuse/Affine-spirals/blob/master/LICENSE",
|
||||||
|
"comment": "",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/affine_spirals",
|
||||||
|
"fork_url": "https://github.com/macbuse/Affine-spirals",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Affine+Spirals",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"github.com/macbuse",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
194
extensions/fablabchemnitz/apollonian_gasket/apollon.py
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Generate Apollonian Gaskets -- the math part.
|
||||||
|
|
||||||
|
# Copyright (c) 2014 Ludger Sandig
|
||||||
|
# This file is part of apollon.
|
||||||
|
|
||||||
|
# Apollon 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.
|
||||||
|
|
||||||
|
# Apollon 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 Apollon. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from cmath import *
|
||||||
|
import random
|
||||||
|
|
||||||
|
class Circle(object):
|
||||||
|
"""
|
||||||
|
A circle represented by center point as complex number and radius.
|
||||||
|
"""
|
||||||
|
def __init__ ( self, mx, my, r ):
|
||||||
|
"""
|
||||||
|
@param mx: x center coordinate
|
||||||
|
@type mx: int or float
|
||||||
|
@param my: y center coordinate
|
||||||
|
@type my: int or float
|
||||||
|
@param r: radius
|
||||||
|
@type r: int or float
|
||||||
|
"""
|
||||||
|
self.r = r
|
||||||
|
self.m = (mx +my*1j)
|
||||||
|
|
||||||
|
def __repr__ ( self ):
|
||||||
|
"""
|
||||||
|
Pretty printing
|
||||||
|
"""
|
||||||
|
return "Circle( self, %s, %s, %s )" % (self.m.real, self.m.imag, self.r)
|
||||||
|
|
||||||
|
def __str__ ( self ):
|
||||||
|
"""
|
||||||
|
Pretty printing
|
||||||
|
"""
|
||||||
|
return "Circle x:%.3f y:%.3f r:%.3f [cur:%.3f]" % (self.m.real, self.m.imag, self.r.real, self.curvature().real)
|
||||||
|
|
||||||
|
def curvature (self):
|
||||||
|
"""
|
||||||
|
Get circle's curvature.
|
||||||
|
@rtype: float
|
||||||
|
@return: Curvature of the circle.
|
||||||
|
"""
|
||||||
|
return 1/self.r
|
||||||
|
|
||||||
|
def outerTangentCircle( circle1, circle2, circle3 ):
|
||||||
|
"""
|
||||||
|
Takes three externally tangent circles and calculates the fourth one enclosing them.
|
||||||
|
@param circle1: first circle
|
||||||
|
@param circle2: second circle
|
||||||
|
@param circle3: third circle
|
||||||
|
@type circle1: L{Circle}
|
||||||
|
@type circle2: L{Circle}
|
||||||
|
@type circle3: L{Circle}
|
||||||
|
@return: The enclosing circle
|
||||||
|
@rtype: L{Circle}
|
||||||
|
"""
|
||||||
|
cur1 = circle1.curvature()
|
||||||
|
cur2 = circle2.curvature()
|
||||||
|
cur3 = circle3.curvature()
|
||||||
|
m1 = circle1.m
|
||||||
|
m2 = circle2.m
|
||||||
|
m3 = circle3.m
|
||||||
|
cur4 = -2 * sqrt( cur1*cur2 + cur2*cur3 + cur1 * cur3 ) + cur1 + cur2 + cur3
|
||||||
|
m4 = ( -2 * sqrt( cur1*m1*cur2*m2 + cur2*m2*cur3*m3 + cur1*m1*cur3*m3 ) + cur1*m1 + cur2*m2 + cur3*m3 ) / cur4
|
||||||
|
circle4 = Circle( m4.real, m4.imag, 1/cur4 )
|
||||||
|
|
||||||
|
return circle4
|
||||||
|
|
||||||
|
|
||||||
|
def tangentCirclesFromRadii( r2, r3, r4 ):
|
||||||
|
"""
|
||||||
|
Takes three radii and calculates the corresponding externally
|
||||||
|
tangent circles as well as a fourth one enclosing them. The enclosing
|
||||||
|
circle is the first one.
|
||||||
|
|
||||||
|
@param r2, r3, r4: Radii of the circles to calculate
|
||||||
|
@type r2: int or float
|
||||||
|
@type r3: int or float
|
||||||
|
@type r4: int or float
|
||||||
|
@return: The four circles, where the first one is the enclosing one.
|
||||||
|
@rtype: (L{Circle}, L{Circle}, L{Circle}, L{Circle})
|
||||||
|
"""
|
||||||
|
circle2 = Circle( 0, 0, r2 )
|
||||||
|
circle3 = Circle( r2 + r3, 0, r3 )
|
||||||
|
m4x = (r2*r2 + r2*r4 + r2*r3 - r3*r4) / (r2 + r3)
|
||||||
|
m4y = sqrt( (r2 + r4) * (r2 + r4) - m4x*m4x )
|
||||||
|
circle4 = Circle( m4x, m4y, r4 )
|
||||||
|
circle1 = outerTangentCircle( circle2, circle3, circle4 )
|
||||||
|
return ( circle1, circle2, circle3, circle4 )
|
||||||
|
|
||||||
|
def secondSolution( fixed, c1, c2, c3 ):
|
||||||
|
"""
|
||||||
|
If given four tangent circles, calculate the other one that is tangent
|
||||||
|
to the last three.
|
||||||
|
|
||||||
|
@param fixed: The fixed circle touches the other three, but not
|
||||||
|
the one to be calculated.
|
||||||
|
|
||||||
|
@param c1, c2, c3: Three circles to which the other tangent circle
|
||||||
|
is to be calculated.
|
||||||
|
|
||||||
|
@type fixed: L{Circle}
|
||||||
|
@type c1: L{Circle}
|
||||||
|
@type c2: L{Circle}
|
||||||
|
@type c3: L{Circle}
|
||||||
|
@return: The circle.
|
||||||
|
@rtype: L{Circle}
|
||||||
|
"""
|
||||||
|
|
||||||
|
curf = fixed.curvature()
|
||||||
|
cur1 = c1.curvature()
|
||||||
|
cur2 = c2.curvature()
|
||||||
|
cur3 = c3.curvature()
|
||||||
|
|
||||||
|
curn = 2 * (cur1 + cur2 + cur3) - curf
|
||||||
|
mn = (2 * (cur1*c1.m + cur2*c2.m + cur3*c3.m) - curf*fixed.m ) / curn
|
||||||
|
return Circle( mn.real, mn.imag, 1/curn )
|
||||||
|
|
||||||
|
class ApollonianGasket(object):
|
||||||
|
"""
|
||||||
|
Container for an Apollonian Gasket.
|
||||||
|
"""
|
||||||
|
def __init__(self, c1, c2, c3):
|
||||||
|
"""
|
||||||
|
Creates a basic apollonian Gasket with four circles.
|
||||||
|
|
||||||
|
@param c1, c2, c3: The curvatures of the three inner circles of the
|
||||||
|
starting set (i.e. depth 0 of the recursion). The fourth,
|
||||||
|
enclosing circle will be calculated from them.
|
||||||
|
@type c1: int or float
|
||||||
|
@type c2: int or float
|
||||||
|
@type c3: int or float
|
||||||
|
"""
|
||||||
|
self.start = tangentCirclesFromRadii( 1/c1, 1/c2, 1/c3 )
|
||||||
|
self.genCircles = list(self.start)
|
||||||
|
|
||||||
|
def recurse(self, circles, depth, maxDepth):
|
||||||
|
"""Recursively calculate the smaller circles of the AG up to the
|
||||||
|
given depth. Note that for depth n we get 2*3^{n+1} circles.
|
||||||
|
|
||||||
|
@param maxDepth: Maximal depth of the recursion.
|
||||||
|
@type maxDepth: int
|
||||||
|
|
||||||
|
@param circles: 4-Tuple of circles for which the second
|
||||||
|
solutions are calculated
|
||||||
|
@type circles: (L{Circle}, L{Circle}, L{Circle}, L{Circle})
|
||||||
|
|
||||||
|
@param depth: Current depth
|
||||||
|
@type depth: int
|
||||||
|
"""
|
||||||
|
if( depth == maxDepth ):
|
||||||
|
return
|
||||||
|
(c1, c2, c3, c4) = circles
|
||||||
|
if( depth == 0 ):
|
||||||
|
# First recursive step, this is the only time we need to
|
||||||
|
# calculate 4 new circles.
|
||||||
|
del self.genCircles[4:]
|
||||||
|
cspecial = secondSolution( c1, c2, c3, c4 )
|
||||||
|
self.genCircles.append( cspecial )
|
||||||
|
self.recurse( (cspecial, c2, c3, c4), 1, maxDepth )
|
||||||
|
|
||||||
|
cn2 = secondSolution( c2, c1, c3, c4 )
|
||||||
|
self.genCircles.append( cn2 )
|
||||||
|
cn3 = secondSolution( c3, c1, c2, c4 )
|
||||||
|
self.genCircles.append( cn3 )
|
||||||
|
cn4 = secondSolution( c4, c1, c2, c3 )
|
||||||
|
self.genCircles.append( cn4 )
|
||||||
|
|
||||||
|
self.recurse( (cn2, c1, c3, c4), depth+1, maxDepth )
|
||||||
|
self.recurse( (cn3, c1, c2, c4), depth+1, maxDepth )
|
||||||
|
self.recurse( (cn4, c1, c2, c3), depth+1, maxDepth )
|
||||||
|
|
||||||
|
def generate(self, depth):
|
||||||
|
"""
|
||||||
|
Wrapper for the recurse function. Generate the AG,
|
||||||
|
@param depth: Recursion depth of the Gasket
|
||||||
|
@type depth: int
|
||||||
|
"""
|
||||||
|
self.recurse(self.start, 0, depth)
|
||||||
|
|
@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Apollonian Gasket</name>
|
||||||
|
<id>fablabchemnitz.de.apollonian_gasket</id>
|
||||||
|
<param name="active_tab" type="notebook">
|
||||||
|
<page name="settings" gui-text="Settings">
|
||||||
|
<param name="depth" type="int" min="2" max="10" gui-text="Depth" gui-description="Warning: high values might calculate really long!">3</param>
|
||||||
|
<param name="c1" type="float" min="0.1" max="10.0" precision="2" gui-text="c1">2.0</param>
|
||||||
|
<param name="c2" type="float" min="0.1" max="10.0" precision="2" gui-text="c2">3.0</param>
|
||||||
|
<param name="c3" type="float" min="0.1" max="10.0" precision="2" gui-text="c3">3.0</param>
|
||||||
|
<param name="shrink" type="bool" gui-text="shrink circles for cutting">true</param>
|
||||||
|
<param name="as_paths" type="bool" gui-text="draw svg:path instead svg:circle elements">true</param>
|
||||||
|
|
||||||
|
</page>
|
||||||
|
<page name="Usage" gui-text="Usage">
|
||||||
|
<label xml:space="preserve">Make an apollonian gasket:
|
||||||
|
Depth = depth in search tree
|
||||||
|
|
||||||
|
c1,c2,c3 = curvatures of first 3 osculating circles</label>
|
||||||
|
<label appearance="url">https://en.wikipedia.org/wiki/Apollonian_gasket</label>
|
||||||
|
</page>
|
||||||
|
</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz Shape Generators">
|
||||||
|
<submenu name="Puzzles/Mazes/Nests"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">apollonian_gasket.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
101
extensions/fablabchemnitz/apollonian_gasket/apollonian_gasket.py
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
import apolloniangasket_func
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
__version__ = '0.0'
|
||||||
|
|
||||||
|
def cplxs2pts(zs):
|
||||||
|
tt = []
|
||||||
|
for z in zs:
|
||||||
|
tt.extend([z.real,z.imag])
|
||||||
|
return tt
|
||||||
|
|
||||||
|
def draw_SVG_circle(parent, r, cx, cy, name):
|
||||||
|
" structure an SVG circle entity under parent "
|
||||||
|
circ_attribs = { 'cx': str(cx), 'cy': str(cy),
|
||||||
|
'r': str(r),
|
||||||
|
inkex.addNS('label','inkscape'): name}
|
||||||
|
circle = etree.SubElement(parent, inkex.addNS('circle','svg'), circ_attribs)
|
||||||
|
|
||||||
|
def draw_SVG_circleAsPath(parent, r, cx, cy, name):
|
||||||
|
circ_attribs = {
|
||||||
|
"d": "M {:0.6f}, {:0.6f} a {:0.6f},{:0.6f} 0 1,0 {:0.6f},0 a {:0.6f},{:0.6f} 0 1,0 {:0.6f},0".format(
|
||||||
|
cx - r, cy, r, r, 2*r, r, r, -2*r),
|
||||||
|
inkex.addNS('label','inkscape'): name}
|
||||||
|
circle = etree.SubElement(parent, inkex.addNS('path','svg'), circ_attribs)
|
||||||
|
|
||||||
|
class Gasket(inkex.EffectExtension): # choose a better name
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("--active_tab")
|
||||||
|
pars.add_argument("--depth",type=int, default=3)
|
||||||
|
pars.add_argument("--c1", type=float, default=2.0)
|
||||||
|
pars.add_argument("--c2", type=float, default=3.0)
|
||||||
|
pars.add_argument("--c3", type=float, default=3.0)
|
||||||
|
pars.add_argument("--shrink", type=inkex.Boolean, default=True)
|
||||||
|
pars.add_argument("--as_paths", type=inkex.Boolean, default=True)
|
||||||
|
|
||||||
|
def calc_unit_factor(self):
|
||||||
|
unit_factor = self.svg.unittouu(str(1.0) + self.options.units)
|
||||||
|
return unit_factor
|
||||||
|
|
||||||
|
### -------------------------------------------------------------------
|
||||||
|
### Main function and is called when the extension is run.
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
|
||||||
|
#set up path styles
|
||||||
|
path_stroke = '#DD0000' # take color from tab3
|
||||||
|
path_fill = 'none' # no fill - just a line
|
||||||
|
path_stroke_width = self.svg.unittouu(str(0.1) + "mm")
|
||||||
|
page_id = self.options.active_tab # sometimes wrong the very first time
|
||||||
|
|
||||||
|
style_curve = { 'stroke': path_stroke,
|
||||||
|
'fill': 'none',
|
||||||
|
'stroke-width': path_stroke_width }
|
||||||
|
|
||||||
|
# This finds center of current view in inkscape
|
||||||
|
t = 'translate(%s,%s)' % (self.svg.namedview.center[0], self.svg.namedview.center[1])
|
||||||
|
|
||||||
|
# add a group to the document's current layer
|
||||||
|
#all the circles inherit style from this group
|
||||||
|
g_attribs = { inkex.addNS('label','inkscape'): 'zengon' + "_%d"%(self.options.depth),
|
||||||
|
inkex.addNS('transform-center-x','inkscape'): str(0),
|
||||||
|
inkex.addNS('transform-center-y','inkscape'): str(0),
|
||||||
|
'transform': t,
|
||||||
|
'style' : str(inkex.Style((style_curve))),
|
||||||
|
'info':'N: '}
|
||||||
|
topgroup = etree.SubElement(self.svg.get_current_layer(), 'g', g_attribs)
|
||||||
|
|
||||||
|
circles = apolloniangasket_func.main(c1=self.options.c1,
|
||||||
|
c2=self.options.c2,
|
||||||
|
c3=self.options.c3,
|
||||||
|
depth=self.options.depth)
|
||||||
|
|
||||||
|
#shrink the circles so they don't touch
|
||||||
|
#useful for laser cutting
|
||||||
|
|
||||||
|
if self.options.shrink:
|
||||||
|
circles = circles[1:]
|
||||||
|
for cc in circles:
|
||||||
|
cc.r = abs(cc.r)
|
||||||
|
if cc.r >.5:
|
||||||
|
cc.r -= .1
|
||||||
|
else:
|
||||||
|
cc.r *= .9
|
||||||
|
|
||||||
|
scale_factor = 200
|
||||||
|
for c in circles:
|
||||||
|
cx, cy, r = c.m.real, c.m.imag, abs(c.r)
|
||||||
|
|
||||||
|
#rescale and add circle to document
|
||||||
|
cx, cy, r = scale_factor * cx , scale_factor * cy, scale_factor * r
|
||||||
|
if self.options.as_paths is False:
|
||||||
|
draw_SVG_circle(topgroup, r, cx, cy, 'apollian')
|
||||||
|
else:
|
||||||
|
draw_SVG_circleAsPath(topgroup, r, cx, cy, 'apollian')
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
Gasket().run()
|
@ -0,0 +1,112 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Command line program to create svg apollonian circles
|
||||||
|
|
||||||
|
# Copyright (c) 2014 Ludger Sandig
|
||||||
|
# This file is part of apollon.
|
||||||
|
|
||||||
|
# Apollon 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.
|
||||||
|
|
||||||
|
# Apollon 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 Apollon. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import math
|
||||||
|
from apollon import ApollonianGasket
|
||||||
|
|
||||||
|
def ag_to_svg(circles, colors, tresh=0.00005):
|
||||||
|
"""
|
||||||
|
Convert a list of circles to svg, optionally color them.
|
||||||
|
@param circles: A list of L{Circle}s
|
||||||
|
@param colors: A L{ColorMap} object
|
||||||
|
@param tresh: Only circles with a radius greater than the product of tresh and maximal radius are saved
|
||||||
|
"""
|
||||||
|
svg = []
|
||||||
|
|
||||||
|
tresh = .000005
|
||||||
|
print ('>>', tresh)
|
||||||
|
|
||||||
|
# Find the biggest circle, which hopefully is the enclosing one
|
||||||
|
# and has a negative radius because of this. Note that this does
|
||||||
|
# not have to be the case if we picked an unlucky set of radii at
|
||||||
|
# the start. If that was the case, we're screwed now.
|
||||||
|
|
||||||
|
big = min(circles, key=lambda c: c.r.real)
|
||||||
|
|
||||||
|
# Move biggest circle to front so it gets drawn first
|
||||||
|
circles.remove(big)
|
||||||
|
circles.insert(0, big)
|
||||||
|
|
||||||
|
if big.r.real < 0:
|
||||||
|
# Bounding box from biggest circle, lower left corner and two
|
||||||
|
# times the radius as width
|
||||||
|
corner = big.m - ( abs(big.r) + abs(big.r) * 1j )
|
||||||
|
vbwidth = abs(big.r)*2
|
||||||
|
width = 500 # Hardcoded!
|
||||||
|
|
||||||
|
# Line width independent of circle size
|
||||||
|
lw = (vbwidth/width)
|
||||||
|
|
||||||
|
svg.append('<svg xmlns="http://www.w3.org/2000/svg" width="%f" height="%f" viewBox="%f %f %f %f">\n' % (width, width, corner.real, corner.imag, vbwidth, vbwidth))
|
||||||
|
|
||||||
|
# Keep stroke width relative
|
||||||
|
svg.append('<g stroke-width="%f">\n' % lw)
|
||||||
|
|
||||||
|
# Iterate through circle list, circles with radius<radmin
|
||||||
|
# will not be saved because they are too small for printing.
|
||||||
|
radmin = tresh * abs(big.r)
|
||||||
|
print(radmin)
|
||||||
|
|
||||||
|
for c in circles:
|
||||||
|
if abs(c.r) > radmin:
|
||||||
|
fill = colors.color_for(abs(c.r))
|
||||||
|
svg.append(( '<circle cx="%f" cy="%f" r="%f" fill="%s" stroke="black"/>\n' % (c.m.real, c.m.imag, abs(c.r), fill)))
|
||||||
|
|
||||||
|
svg.append('</g>\n')
|
||||||
|
svg.append('</svg>\n')
|
||||||
|
|
||||||
|
return ''.join(svg)
|
||||||
|
|
||||||
|
def impossible_combination(c1, c2, c3):
|
||||||
|
# If any curvatures x, y, z satisfy the equation
|
||||||
|
# x = 2*sqrt(y*z) + y + z
|
||||||
|
# then no fourth enclosing circle can be genereated, because it
|
||||||
|
# would be a line.
|
||||||
|
# We need to see for c1, c2, c3 if they could be "x".
|
||||||
|
|
||||||
|
impossible = False
|
||||||
|
|
||||||
|
sets = [(c1,c2,c3), (c2,c3,c1), (c3,c1,c2)]
|
||||||
|
|
||||||
|
for (x, y, z) in sets:
|
||||||
|
if x == 2*math.sqrt(y*z) + y + z:
|
||||||
|
impossible = True
|
||||||
|
|
||||||
|
return impossible
|
||||||
|
|
||||||
|
def main(c1=3.,c2=2.,c3=2.,depth=5):
|
||||||
|
# Sanity checks
|
||||||
|
for c in [c1, c2,c3]:
|
||||||
|
if c == 0:
|
||||||
|
print("Error: curvature or radius can't be 0")
|
||||||
|
exit(1)
|
||||||
|
if impossible_combination(c1, c2, c3):
|
||||||
|
print("Error: no apollonian gasket possible for these curvatures")
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
ag = ApollonianGasket(c1, c2, c3)
|
||||||
|
|
||||||
|
ag.generate(depth)
|
||||||
|
|
||||||
|
# Get smallest and biggest radius
|
||||||
|
smallest = abs(min(ag.genCircles, key=lambda c: abs(c.r.real)).r.real)
|
||||||
|
biggest = abs(max(ag.genCircles, key=lambda c: abs(c.r.real)).r.real)
|
||||||
|
|
||||||
|
return ag.genCircles
|
21
extensions/fablabchemnitz/apollonian_gasket/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Apollonian Gasket",
|
||||||
|
"id": "fablabchemnitz.de.apollonian_gasket",
|
||||||
|
"path": "apollonian_gasket",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "Apollonian Gasket 0.0",
|
||||||
|
"original_id": "githubacct.uniqueid.apollonian",
|
||||||
|
"license": "MIT License",
|
||||||
|
"license_url": "https://github.com/macbuse/Apollonian/blob/master/License.md",
|
||||||
|
"comment": "",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/apollonian_gasket",
|
||||||
|
"fork_url": "https://github.com/macbuse/Apollonian",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Apollonian+Gasket",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"github.com/macbuse",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,55 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Archimedes Spiral</name>
|
||||||
|
<id>fablabchemnitz.de.archimedes_spiral</id>
|
||||||
|
<param name="tab" type="notebook">
|
||||||
|
<page name="tab_settings" gui-text="Settings">
|
||||||
|
<label appearance="header">R = r + aθ</label>
|
||||||
|
<param name="r" type="int" min="0" max="1000000" gui-text="r (mm)">50</param>
|
||||||
|
<param name="a" type="float" min="0" max="1000000" gui-text="a">3</param>
|
||||||
|
<param name="step" type="int" min="1" max="300" gui-text="Step" gui-description="The higher the value the better the accuracy. If you set the step value really low the resulting length will not be precise and the curve is maybe not drawn at all">50</param>
|
||||||
|
<param name="trl" type="optiongroup" appearance="combo" gui-text="Turn direction">
|
||||||
|
<option value="0">Left</option>
|
||||||
|
<option value="1">Right</option>
|
||||||
|
</param>
|
||||||
|
<param name="length" type="float" min="0" max="1000000" gui-text="Length (mm)">0</param>
|
||||||
|
<param name="turns" type="int" min="1" max="1000000" gui-text="Turns" gui-description="Works only if you set 'length (mm)' to 0.0">5</param>
|
||||||
|
</page>
|
||||||
|
<page name="tab_about" gui-text="About">
|
||||||
|
<label appearance="header">Archimedes Spiral</label>
|
||||||
|
<label>2020 - 2021 / written by Mario Voigt (Stadtfabrikanten e.V. / FabLab Chemnitz)</label>
|
||||||
|
<spacer/>
|
||||||
|
<label appearance="header">Online Documentation</label>
|
||||||
|
<label appearance="url">https://y.stadtfabrikanten.org/archimedesspiral</label>
|
||||||
|
<spacer/>
|
||||||
|
<label appearance="header">Contributing</label>
|
||||||
|
<label appearance="url">https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X</label>
|
||||||
|
<label appearance="url">mailto:mario.voigt@stadtfabrikanten.org</label>
|
||||||
|
<spacer/>
|
||||||
|
<label appearance="header">MightyScape Extension Collection</label>
|
||||||
|
<label>This piece of software is part of the MightyScape for Inkscape Extension Collection and is licensed under GNU GPL v3</label>
|
||||||
|
<label appearance="url">https://y.stadtfabrikanten.org/mightyscape-overview</label>
|
||||||
|
</page>
|
||||||
|
<page name="tab_donate" gui-text="Donate">
|
||||||
|
<label appearance="header">Coffee + Pizza</label>
|
||||||
|
<label>We are the Stadtfabrikanten, running the FabLab Chemnitz since 2016. A FabLab is an open workshop that gives people access to machines and digital tools like 3D printers, laser cutters and CNC milling machines.</label>
|
||||||
|
<spacer/>
|
||||||
|
<label>You like our work and want to support us? You can donate to our non-profit organization by different ways:</label>
|
||||||
|
<label appearance="url">https://y.stadtfabrikanten.org/donate</label>
|
||||||
|
<spacer/>
|
||||||
|
<label>Thanks for using our extension and helping us!</label>
|
||||||
|
<image>../000_about_fablabchemnitz.svg</image>
|
||||||
|
</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">archimedes_spiral.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
@ -0,0 +1,89 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Copyright (C) 2017 Panagiotis Loukas
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WAphiANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WAphiANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
Version 0.2
|
||||||
|
This script was written by Panagiotis Loukas to make spiral easy.
|
||||||
|
It simply,
|
||||||
|
|
||||||
|
Have fun :)
|
||||||
|
PS.
|
||||||
|
Written on Arch.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
from lxml import etree
|
||||||
|
from math import cos, sin, pi, log, sqrt
|
||||||
|
|
||||||
|
class Archimedes(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument('--tab')
|
||||||
|
pars.add_argument('--r', type = int, default = '50')
|
||||||
|
pars.add_argument('--a', type = float, default = '3')
|
||||||
|
pars.add_argument('--step', type = int, default = '50')
|
||||||
|
pars.add_argument('--trl', default = '1')
|
||||||
|
pars.add_argument('--turns', type = float, default = '5')
|
||||||
|
pars.add_argument('--length', type = float, default = '500')
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
th = pi / 3
|
||||||
|
a = self.options.a
|
||||||
|
r = self.options.r
|
||||||
|
|
||||||
|
length = self.options.length
|
||||||
|
if length > 0:
|
||||||
|
turns = self.angle(a, r, length, th) / (2 * pi)
|
||||||
|
else:
|
||||||
|
turns = self.options.turns
|
||||||
|
|
||||||
|
if self.options.trl == '1':
|
||||||
|
step = -self.options.step
|
||||||
|
else:
|
||||||
|
step = self.options.step
|
||||||
|
|
||||||
|
layer = etree.SubElement(self.document.getroot(),'g')
|
||||||
|
path = etree.Element(inkex.addNS('path','svg'))
|
||||||
|
path.set('d', self.built(r, step, a, turns))
|
||||||
|
path.set('style',"fill:none;stroke:#000000;stroke-width:1px;stroke-opacity:1")
|
||||||
|
layer.append(path)
|
||||||
|
|
||||||
|
def built(self, r0, st = 4, a = 4, k = 1, th = 0):
|
||||||
|
step = 2 * pi / st
|
||||||
|
r = r0
|
||||||
|
s = "M " + str(r * cos(th)) + ", " + str(-r * sin(th))
|
||||||
|
for i in range(0, int(k * (abs(st)))):
|
||||||
|
prin = th + i * step
|
||||||
|
meta = th + (i + 1) * step
|
||||||
|
rp = r0 + abs(a * prin)# instead we put the absolute value the spiral will drift inwards
|
||||||
|
rm = r0 + abs(a * meta)# at the absolute price closes outwards
|
||||||
|
|
||||||
|
s += "a " + str(rm) + "," + str(rm) + " 0 0," + self.options.trl + " " + str(-rp * cos(prin) + rm * cos(meta)) + "," + str(rp * sin(prin) -rm * sin(meta))
|
||||||
|
return s
|
||||||
|
|
||||||
|
# see https://mathepedia.de/Archimedische_Spirale.html for formula of total arc length
|
||||||
|
def spirallength(self, a, r0, th):
|
||||||
|
phi = (r0 / a) + th
|
||||||
|
phi_sqrt = sqrt(phi ** 2 + 1)
|
||||||
|
return (a / 2) * (phi * phi_sqrt + log(phi + phi_sqrt))
|
||||||
|
|
||||||
|
def ds(self, a, r0, th):
|
||||||
|
return self.spirallength(a, r0, th) - self.spirallength(a, r0, 0)
|
||||||
|
|
||||||
|
def angle(self, a, r0, length, th):
|
||||||
|
i = 0.0
|
||||||
|
while (True):
|
||||||
|
ls=self.ds(a, r0, i)
|
||||||
|
if length - ls > 100:
|
||||||
|
i += 0.01
|
||||||
|
elif length - ls > 10: i += 0.001
|
||||||
|
elif length - ls > 1: i += 0.0001
|
||||||
|
elif length - ls > 0.1: i += 0.00001
|
||||||
|
elif length - ls > 0.01: i += 0.000001
|
||||||
|
else: break
|
||||||
|
return i
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
Archimedes().run()
|
21
extensions/fablabchemnitz/archimedes_spiral/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Archimedes Spiral",
|
||||||
|
"id": "fablabchemnitz.de.archimedes_spiral",
|
||||||
|
"path": "archimedes_spiral",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "speira",
|
||||||
|
"original_id": "org.ekips.filter.s2",
|
||||||
|
"license": "GNU GPL v2",
|
||||||
|
"license_url": "https://inkscape.org/de/~panos/%E2%98%85speira?c=16537",
|
||||||
|
"comment": "",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/archimedes_spiral",
|
||||||
|
"fork_url": "https://inkscape.org/de/~panos/%E2%98%85speira?c=16537",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Archimedes+Spiral",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"inkscape.org/panos",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Barrel Distortion</name>
|
||||||
|
<id>fablabchemnitz.de.barrel_distorsion</id>
|
||||||
|
<param name="lambda_coef" type="float" min="-1000.0" max="0.0" precision="2" gui-text="Lambda parameter">-1.0</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Transformations"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">barrel_distorsion.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
103
extensions/fablabchemnitz/barrel_distorsion/barrel_distorsion.py
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import math
|
||||||
|
import re
|
||||||
|
import inkex
|
||||||
|
from inkex import bezier
|
||||||
|
from inkex.paths import Path, CubicSuperPath
|
||||||
|
|
||||||
|
class BarrelDistorsion(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("--lambda_coef", type=float, default=-5.0, help="command line help")
|
||||||
|
|
||||||
|
def distort_coordinates(self, x, y):
|
||||||
|
"""Method applies barrel distorsion to given points with distorsion center in center of image, selected to
|
||||||
|
|
||||||
|
Args:
|
||||||
|
x (float): X coordinate of given point
|
||||||
|
y (float): Y coordinate of given point
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple(float, float): Tuple with X,Y distorted coordinates of given point
|
||||||
|
"""
|
||||||
|
x_u = (x - self.x_c) / (self.width + self.height)
|
||||||
|
y_u = (y - self.y_c) / (self.width + self.height)
|
||||||
|
x_d = x_u / 2 / (self.q * y_u**2 + x_u**2 * self.q) * (1 - math.sqrt(1 - 4 * self.q * y_u**2 - 4 * x_u**2 * self.q))
|
||||||
|
y_d = y_u / 2 / (self.q * y_u**2 + x_u**2 * self.q) * (1 - math.sqrt(1 - 4 * self.q * y_u**2 - 4 * x_u**2 * self.q))
|
||||||
|
x_d *= self.width + self.height
|
||||||
|
y_d *= self.width + self.height
|
||||||
|
x_d += self.x_c
|
||||||
|
y_d += self.y_c
|
||||||
|
return x_d, y_d
|
||||||
|
|
||||||
|
def split_into_nodes(self, nodes_number=1000):
|
||||||
|
for id, node in self.svg.selected.items():
|
||||||
|
if node.tag == inkex.addNS('path', 'svg'):
|
||||||
|
p = CubicSuperPath(node.get('d'))
|
||||||
|
new = []
|
||||||
|
for sub in p:
|
||||||
|
new.append([sub[0][:]])
|
||||||
|
i = 1
|
||||||
|
while i <= len(sub) - 1:
|
||||||
|
length = bezier.cspseglength(
|
||||||
|
new[-1][-1], sub[i])
|
||||||
|
|
||||||
|
splits = nodes_number
|
||||||
|
for s in range(int(splits), 1, -1):
|
||||||
|
new[-1][-1], next, sub[
|
||||||
|
i] = bezier.cspbezsplitatlength(
|
||||||
|
new[-1][-1], sub[i], 1.0 / s)
|
||||||
|
new[-1].append(next[:])
|
||||||
|
new[-1].append(sub[i])
|
||||||
|
i += 1
|
||||||
|
node.set('d', str(CubicSuperPath(new)))
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
if re.match(r'g\d+',
|
||||||
|
list(self.svg.selected.items())[0][0]) is not None:
|
||||||
|
raise SystemExit(
|
||||||
|
"You are trying to distort group of objects.\n This extension works only with path objects due to Inkscape API restrictions.\n Ungroup your objects and try again."
|
||||||
|
)
|
||||||
|
self.split_into_nodes()
|
||||||
|
self.q = self.options.lambda_coef
|
||||||
|
if self.q == 0.0:
|
||||||
|
inkex.errormsg("Invalid lambda coefficient. May not be exactly zero.")
|
||||||
|
return
|
||||||
|
nodes = []
|
||||||
|
for id, node in self.svg.selected.items():
|
||||||
|
if node.tag == inkex.addNS('path', 'svg'):
|
||||||
|
path = Path(node.get('d')).to_arrays()
|
||||||
|
nodes += path
|
||||||
|
if len(nodes) == 0:
|
||||||
|
inkex.utils.debug("Selection is invalid. Please change selection to paths only.")
|
||||||
|
exit(1)
|
||||||
|
nodes_filtered = [x for x in nodes if x[0] != 'Z']
|
||||||
|
x_coordinates = [x[-1][-2] for x in nodes_filtered]
|
||||||
|
y_coordinates = [y[-1][-1] for y in nodes_filtered]
|
||||||
|
self.width = max(x_coordinates) - min(x_coordinates)
|
||||||
|
self.height = max(y_coordinates) - min(y_coordinates)
|
||||||
|
self.x_c = sum(x_coordinates) / len(x_coordinates)
|
||||||
|
self.y_c = sum(y_coordinates) / len(y_coordinates)
|
||||||
|
for id, node in self.svg.selected.items():
|
||||||
|
if node.tag == inkex.addNS('path', 'svg'):
|
||||||
|
path = Path(node.get('d')).to_arrays()
|
||||||
|
distorted = []
|
||||||
|
first = True
|
||||||
|
for cmd, params in path:
|
||||||
|
if cmd != 'Z':
|
||||||
|
if first == True:
|
||||||
|
x = params[-2]
|
||||||
|
y = params[-1]
|
||||||
|
distorted.append(
|
||||||
|
['M',
|
||||||
|
list(self.distort_coordinates(x, y))])
|
||||||
|
first = False
|
||||||
|
else:
|
||||||
|
x = params[-2]
|
||||||
|
y = params[-1]
|
||||||
|
distorted.append(
|
||||||
|
['L', self.distort_coordinates(x, y)])
|
||||||
|
node.set('d', str(Path(distorted)))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
BarrelDistorsion().run()
|
21
extensions/fablabchemnitz/barrel_distorsion/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Barrel Distortion",
|
||||||
|
"id": "fablabchemnitz.de.barrel_distorsion",
|
||||||
|
"path": "barrel_distorsion",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "Distortion",
|
||||||
|
"original_id": "org.pub.dIstorsion",
|
||||||
|
"license": "MIT License",
|
||||||
|
"license_url": "https://github.com/ucuapps/InkscapeBarrelDistortion/blob/master/LICENSE",
|
||||||
|
"comment": "",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/barrel_distorsion",
|
||||||
|
"fork_url": "https://github.com/ucuapps/InkscapeBarrelDistortion",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Barrel+Distortion",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"github.com/ucuapps",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Bezier Envelope</name>
|
||||||
|
<id>fablabchemnitz.de.bezier_envelope</id>
|
||||||
|
<effect>
|
||||||
|
<object-type>path</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Transformations"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">bezier_envelope.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
409
extensions/fablabchemnitz/bezier_envelope/bezier_envelope.py
Normal file
@ -0,0 +1,409 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
'''
|
||||||
|
Bezier Envelope extension for Inkscape
|
||||||
|
Copyright (C) 2009 Gerrit Karius
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
About the Bezier Envelope extension:
|
||||||
|
|
||||||
|
This extension implements Bezier enveloping.
|
||||||
|
It takes an arbitrary path (the "letter") and a 4-sided path (the "envelope") as input.
|
||||||
|
The envelope must be 4 segments long. Unless the letter is to be rotated or flipped,
|
||||||
|
the envelope should begin at the upper left corner and be drawn clockwise.
|
||||||
|
The extension then attempts to squeeze the letter into the envelope
|
||||||
|
by rearranging all anchor and handle points of the letter's path.
|
||||||
|
|
||||||
|
In order to do this, the bounding box of the letter is used.
|
||||||
|
All anchor and bezier handle points get new x and y coordinates between 0% and 100%
|
||||||
|
according to their place inside the bounding box.
|
||||||
|
The 4 sides of the envelope are then interpreted as deformed axes.
|
||||||
|
Points at 0% or 100% could be placed along these axes, but because most points
|
||||||
|
are somewhere inside the bounding box, some tweening of the axes must be done.
|
||||||
|
|
||||||
|
The function mapPointsToMorph does the tweening.
|
||||||
|
Say, some point is at x=30%, y=40%.
|
||||||
|
For the tweening, the function tweenCubic first calculates a straight tween
|
||||||
|
of the y axis at the x percentage of 30%.
|
||||||
|
This tween axis now floats somewhere between the y axis keys at the x percentage,
|
||||||
|
but is not necessarily inside the envelope, because the x axes are not straight.
|
||||||
|
Now, the end points on the two x axes at 30% are calculated. The function match()
|
||||||
|
takes these points and calculates a "stretch" transform which maps the two anchor
|
||||||
|
points of the y axis tween to the two points on the x axes by rotating the tween and
|
||||||
|
stretching it along its endpoints. This transform is then applied to the handle points,
|
||||||
|
to get the entire tweened y axis to its x tweened position.
|
||||||
|
Last, the point at the y percentage 40% of this y axis tween is calculated.
|
||||||
|
That is the final point of the enveloped letter.
|
||||||
|
|
||||||
|
Finally, after all of the letter's points have been recalculated in this manner,
|
||||||
|
the resulting path is taken and replaces the letter's original path.
|
||||||
|
|
||||||
|
TODO:
|
||||||
|
* Currently, both letter and envelope must be paths to work.
|
||||||
|
-> Arbitrary other shapes like circles and rectangles should be interpreted as paths.
|
||||||
|
* It should be possible to select several letters, and squeeze them into one envelope as a group.
|
||||||
|
* It should be possible to insert a clone of the letter, instead of replacing it.
|
||||||
|
* This program was originally written in Java. Maybe for some code, Python shortcuts can be used.
|
||||||
|
|
||||||
|
I hope the comments are not too verbose. Enjoy!
|
||||||
|
|
||||||
|
'''
|
||||||
|
import inkex
|
||||||
|
from inkex import Transform
|
||||||
|
from inkex.paths import Path
|
||||||
|
import math
|
||||||
|
import sys
|
||||||
|
import ffgeom
|
||||||
|
|
||||||
|
|
||||||
|
class BezierEnvelope(inkex.EffectExtension):
|
||||||
|
|
||||||
|
segmentTypes = ["move","line","quad","cubic","close"]
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
if len(self.options.ids) < 2:
|
||||||
|
inkex.errormsg("Two paths must be selected. The 1st is the letter, the 2nd is the envelope and must have 4 sides.")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
letterElement = self.svg.selected[self.options.ids[0]]
|
||||||
|
envelopeElement = self.svg.selected[self.options.ids[1]]
|
||||||
|
|
||||||
|
if letterElement.get('inkscape:original-d') or envelopeElement.get('inkscape:original-d'):
|
||||||
|
inkex.errormsg("One or both selected paths have attribute 'inkscape:original-d' which points to Live Path Effects (LPE). Please convert to regular path.")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
if letterElement.tag != inkex.addNS('path','svg') or envelopeElement.tag != inkex.addNS('path','svg'):
|
||||||
|
inkex.errormsg("Both letter and envelope must be SVG paths.")
|
||||||
|
exit()
|
||||||
|
|
||||||
|
axes = extractMorphAxes(Path( envelopeElement.get('d') ).to_arrays())
|
||||||
|
if axes is None:
|
||||||
|
inkex.errormsg("No axes found on envelope.")
|
||||||
|
exit()
|
||||||
|
axisCount = len(axes)
|
||||||
|
if axisCount < 4:
|
||||||
|
inkex.errormsg("The envelope path has less than 4 segments.")
|
||||||
|
exit()
|
||||||
|
for i in range( 0, 4 ):
|
||||||
|
if axes[i] is None:
|
||||||
|
inkex.errormsg("axis[%i] is None. Check if envelope has at least 4 nodes (closed path) or 5 nodes (open path)." % i)
|
||||||
|
exit()
|
||||||
|
# morph the enveloped element according to the axes
|
||||||
|
morph_element( letterElement, envelopeElement, axes );
|
||||||
|
|
||||||
|
|
||||||
|
def morph_element( letterElement, envelopeElement, axes ):
|
||||||
|
path = Path( letterElement.get('d') ).to_arrays()
|
||||||
|
morphedPath = morphPath( path, axes )
|
||||||
|
letterElement.set("d", str(Path(morphedPath)))
|
||||||
|
|
||||||
|
|
||||||
|
# Morphs a path into a new path, according to cubic curved bounding axes.
|
||||||
|
def morphPath(path, axes):
|
||||||
|
bounds = [y for x in list(Path(path).bounding_box()) for y in list(x)]
|
||||||
|
assert len(bounds) == 4
|
||||||
|
new_path = []
|
||||||
|
current = [ 0.0, 0.0 ]
|
||||||
|
start = [ 0.0, 0.0 ]
|
||||||
|
|
||||||
|
for cmd, params in path:
|
||||||
|
segmentType = cmd
|
||||||
|
points = params
|
||||||
|
if segmentType == "M":
|
||||||
|
start[0] = points[0]
|
||||||
|
start[1] = points[1]
|
||||||
|
segmentType = convertSegmentToCubic( current, segmentType, points, start )
|
||||||
|
percentages = [0.0]*len(points)
|
||||||
|
morphed = [0.0]*len(points)
|
||||||
|
numPts = getNumPts( segmentType )
|
||||||
|
normalizePoints( bounds, points, percentages, numPts )
|
||||||
|
mapPointsToMorph( axes, percentages, morphed, numPts )
|
||||||
|
addSegment( new_path, segmentType, morphed )
|
||||||
|
if len(points) >= 2:
|
||||||
|
current[0] = points[ len(points)-2 ]
|
||||||
|
current[1] = points[ len(points)-1 ]
|
||||||
|
return new_path
|
||||||
|
|
||||||
|
|
||||||
|
def getNumPts( segmentType ):
|
||||||
|
if segmentType == "M":
|
||||||
|
return 1
|
||||||
|
if segmentType == "L":
|
||||||
|
return 1
|
||||||
|
if segmentType == "Q":
|
||||||
|
return 2
|
||||||
|
if segmentType == "C":
|
||||||
|
return 3
|
||||||
|
if segmentType == "Z":
|
||||||
|
return 0
|
||||||
|
return -1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def addSegment( path, segmentType, points ):
|
||||||
|
path.append([segmentType,points])
|
||||||
|
|
||||||
|
|
||||||
|
# Converts visible path segments (Z,L,Q) into absolute cubic segments (C).
|
||||||
|
def convertSegmentToCubic( current, segmentType, points, start ):
|
||||||
|
if segmentType == "H":
|
||||||
|
# print(current, points, start)
|
||||||
|
assert len(points) == 1
|
||||||
|
points.insert(0, current[0])
|
||||||
|
# points[0] += current[0]
|
||||||
|
# print(segmentType, current, points, start)
|
||||||
|
return convertSegmentToCubic(current, "L", points, start)
|
||||||
|
elif segmentType == "V":
|
||||||
|
# print(points)
|
||||||
|
assert len(points) == 1
|
||||||
|
points.append(current[1])
|
||||||
|
# points[1] += current[1]
|
||||||
|
# print(segmentType, current, points, start)
|
||||||
|
return convertSegmentToCubic(current, "L", points, start)
|
||||||
|
if segmentType == "M":
|
||||||
|
return "M";
|
||||||
|
if segmentType == "C":
|
||||||
|
return "C";
|
||||||
|
elif segmentType == "Z":
|
||||||
|
for i in range(0,6):
|
||||||
|
points.append(0.0)
|
||||||
|
points[4] = start[0]
|
||||||
|
points[5] = start[1]
|
||||||
|
thirdX = (points[4] - current[0]) / 3.0
|
||||||
|
thirdY = (points[5] - current[1]) / 3.0
|
||||||
|
points[2] = points[4]-thirdX
|
||||||
|
points[3] = points[5]-thirdY
|
||||||
|
points[0] = current[0]+thirdX
|
||||||
|
points[1] = current[1]+thirdY
|
||||||
|
return "C"
|
||||||
|
elif segmentType == "L":
|
||||||
|
for i in range(0,4):
|
||||||
|
points.append(0.0)
|
||||||
|
points[4] = points[0]
|
||||||
|
points[5] = points[1]
|
||||||
|
thirdX = (points[4] - current[0]) / 3.0
|
||||||
|
thirdY = (points[5] - current[1]) / 3.0
|
||||||
|
points[2] = points[4]-thirdX
|
||||||
|
points[3] = points[5]-thirdY
|
||||||
|
points[0] = current[0]+thirdX
|
||||||
|
points[1] = current[1]+thirdY
|
||||||
|
return "C"
|
||||||
|
elif segmentType == "Q":
|
||||||
|
for i in range(0,2):
|
||||||
|
points.append(0.0)
|
||||||
|
firstThirdX = (points[0] - current[0]) * 2.0 / 3.0
|
||||||
|
firstThirdY = (points[1] - current[1]) * 2.0 / 3.0
|
||||||
|
secondThirdX = (points[2] - points[0]) * 2.0 / 3.0
|
||||||
|
secondThirdY = (points[3] - points[1]) * 2.0 / 3.0
|
||||||
|
points[4] = points[2]
|
||||||
|
points[5] = points[3]
|
||||||
|
points[0] = current[0] + firstThirdX
|
||||||
|
points[1] = current[1] + firstThirdY
|
||||||
|
points[2] = points[2] - secondThirdX
|
||||||
|
points[3] = points[3] - secondThirdY
|
||||||
|
return "C"
|
||||||
|
elif segmentType == "A":
|
||||||
|
inkex.errormsg("Sorry, arcs are not supported in envelope or letter path!")
|
||||||
|
exit()
|
||||||
|
else:
|
||||||
|
inkex.errormsg("unsupported segment type: %s" % (segmentType))
|
||||||
|
return segmentType
|
||||||
|
|
||||||
|
|
||||||
|
# Normalizes the points of a path segment, so that they are expressed as percentage coordinates
|
||||||
|
# relative to the bounding box axes of the total shape.
|
||||||
|
# @param bounds The bounding box of the shape.
|
||||||
|
# @param points The points of the segment.
|
||||||
|
# @param percentages The returned points in normalized percentage form.
|
||||||
|
# @param numPts
|
||||||
|
def normalizePoints( bounds, points, percentages, numPts ):
|
||||||
|
# bounds has structure xmin,xMax,ymin,yMax
|
||||||
|
xmin,xMax,ymin,yMax = bounds
|
||||||
|
for i in range( 0, numPts ):
|
||||||
|
x = i*2
|
||||||
|
y = i*2+1
|
||||||
|
percentages[x] = (points[x] - xmin) / (xMax-xmin)
|
||||||
|
percentages[y] = (points[y] - ymin) / (yMax-ymin)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Extracts 4 axes from a path. It is assumed that the path starts with a move, followed by 4 cubic paths.
|
||||||
|
# The extraction reverses the last 2 axes, so that they run in parallel with the first 2.
|
||||||
|
# @param path The path that is formed by the axes.
|
||||||
|
# @return The definition points of the 4 cubic path axes as float arrays, bundled in another array.
|
||||||
|
def extractMorphAxes( path ):
|
||||||
|
points = []
|
||||||
|
current = [ 0.0, 0.0 ]
|
||||||
|
start = [ 0.0, 0.0 ]
|
||||||
|
# the curved axis definitions go in here
|
||||||
|
axes = [None]*4
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
for cmd, params in path:
|
||||||
|
points = params
|
||||||
|
cmd = convertSegmentToCubic( current, cmd, points, start )
|
||||||
|
|
||||||
|
if cmd is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if cmd == "A":
|
||||||
|
inkex.errormsg("Sorry, arcs are not supported in envelope or letter path!")
|
||||||
|
return None
|
||||||
|
|
||||||
|
elif cmd == "M":
|
||||||
|
current[0] = points[0]
|
||||||
|
current[1] = points[1]
|
||||||
|
start[0] = points[0]
|
||||||
|
start[1] = points[1]
|
||||||
|
|
||||||
|
elif cmd == "C":
|
||||||
|
|
||||||
|
# 1st cubic becomes x axis 0
|
||||||
|
# 2nd cubic becomes y axis 1
|
||||||
|
# 3rd cubic becomes x axis 2 and is reversed
|
||||||
|
# 4th cubic becomes y axis 3 and is reversed
|
||||||
|
if i % 2 == 0:
|
||||||
|
index = i
|
||||||
|
else:
|
||||||
|
index = 4-i
|
||||||
|
if( i < 2 ):
|
||||||
|
# axes 1 and 2
|
||||||
|
axes[index] = [ current[0], current[1], points[0], points[1], points[2], points[3], points[4], points[5] ]
|
||||||
|
elif( i < 4 ):
|
||||||
|
# axes 3 and 4
|
||||||
|
axes[index] = [ points[4], points[5], points[2], points[3], points[0], points[1], current[0], current[1] ]
|
||||||
|
else:
|
||||||
|
# more than 4 axes - hopefully it was an unnecessary trailing Z
|
||||||
|
{}
|
||||||
|
current[0] = points[4]
|
||||||
|
current[1] = points[5]
|
||||||
|
i = i + 1
|
||||||
|
elif cmd == "Z":
|
||||||
|
#do nothing
|
||||||
|
{}
|
||||||
|
else:
|
||||||
|
inkex.errormsg("Unsupported segment type in envelope path: %s" % cmd)
|
||||||
|
return None
|
||||||
|
|
||||||
|
return axes
|
||||||
|
|
||||||
|
|
||||||
|
# Projects points in percentage coordinates into a morphed coordinate system that is framed
|
||||||
|
# by 2 x cubic curves (along the x axis) and 2 y cubic curves (along the y axis).
|
||||||
|
# @param axes The x and y axes of the envelope.
|
||||||
|
# @param percentage The current segment of the letter in normalized percentage form.
|
||||||
|
# @param morphed The array to hold the returned morphed path.
|
||||||
|
# @param numPts The number of points to be transformed.
|
||||||
|
def mapPointsToMorph( axes, percentage, morphed, numPts ):
|
||||||
|
# rename the axes for legibility
|
||||||
|
yCubic0 = axes[1]
|
||||||
|
yCubic1 = axes[3]
|
||||||
|
xCubic0 = axes[0]
|
||||||
|
xCubic1 = axes[2]
|
||||||
|
# morph each point
|
||||||
|
for i in range( 0, numPts ):
|
||||||
|
x = i*2
|
||||||
|
y = i*2+1
|
||||||
|
# tween between the morphed y axes according to the x percentage
|
||||||
|
tweenedY = tweenCubic( yCubic0, yCubic1, percentage[x] )
|
||||||
|
# get 2 points on the morphed x axes
|
||||||
|
xSpot0 = pointOnCubic( xCubic0, percentage[x] )
|
||||||
|
xSpot1 = pointOnCubic( xCubic1, percentage[x] )
|
||||||
|
# create a transform that stretches the y axis tween between these 2 points
|
||||||
|
yAnchor0 = [ tweenedY[0], tweenedY[1] ]
|
||||||
|
yAnchor1 = [ tweenedY[6], tweenedY[7] ]
|
||||||
|
xTransform = match( yAnchor0, yAnchor1, xSpot0, xSpot1 )
|
||||||
|
# map the y axis tween to the 2 points by applying the stretch transform
|
||||||
|
for j in range(0,4):
|
||||||
|
x2 = j*2
|
||||||
|
y2 = j*2+1
|
||||||
|
pointOnY = [tweenedY[x2],tweenedY[y2]]
|
||||||
|
Transform(xTransform).apply_to_point(pointOnY)
|
||||||
|
tweenedY[x2] = pointOnY[0]
|
||||||
|
tweenedY[y2] = pointOnY[1]
|
||||||
|
# get the point on the tweened and transformed y axis according to the y percentage
|
||||||
|
morphedPoint = pointOnCubic( tweenedY, percentage[y] )
|
||||||
|
morphed[x] = morphedPoint[0]
|
||||||
|
morphed[y] = morphedPoint[1]
|
||||||
|
|
||||||
|
# Calculates the point on a cubic bezier curve at the given percentage.
|
||||||
|
def pointOnCubic( c, t ):
|
||||||
|
point = [0.0,0.0]
|
||||||
|
_t_2 = t*t
|
||||||
|
_t_3 = _t_2*t
|
||||||
|
_1_t = 1-t
|
||||||
|
_1_t_2 = _1_t*_1_t
|
||||||
|
_1_t_3 = _1_t_2*_1_t
|
||||||
|
|
||||||
|
for i in range( 0, 2 ):
|
||||||
|
point[i] = c[i]*_1_t_3 + 3*c[2+i]*_1_t_2*t + 3*c[4+i]*_1_t*_t_2 + c[6+i]*_t_3
|
||||||
|
return point
|
||||||
|
|
||||||
|
# Tweens 2 bezier curves in a straightforward way,
|
||||||
|
# i.e. each of the points on the curve is tweened along a straight line
|
||||||
|
# between the respective point on key1 and key2.
|
||||||
|
def tweenCubic( key1, key2, percentage ):
|
||||||
|
tween = [0.0]*len(key1)
|
||||||
|
for i in range ( 0, len(key1) ):
|
||||||
|
tween[i] = key1[i] + percentage * (key2[i] - key1[i])
|
||||||
|
return tween
|
||||||
|
|
||||||
|
# Calculates a transform that matches 2 points to 2 anchors
|
||||||
|
# by rotating and scaling (up or down) along the axis that is formed by
|
||||||
|
# a line between the two points.
|
||||||
|
def match( p1, p2, a1, a2 ):
|
||||||
|
x = 0
|
||||||
|
y = 1
|
||||||
|
# distances
|
||||||
|
dp = [ p2[x]-p1[x], p2[y]-p1[y] ]
|
||||||
|
da = [ a2[x]-a1[x], a2[y]-a1[y] ]
|
||||||
|
# angles
|
||||||
|
angle_p = math.atan2( dp[x], dp[y] )
|
||||||
|
angle_a = math.atan2( da[x], da[y] )
|
||||||
|
# radians
|
||||||
|
#rp = math.sqrt( dp[x]*dp[x] + dp[y]*dp[y] )
|
||||||
|
#ra = math.sqrt( da[x]*da[x] + da[y]*da[y] )
|
||||||
|
rp = math.hypot( dp[x], dp[y] )
|
||||||
|
ra = math.hypot( da[x], da[y] )
|
||||||
|
# scale
|
||||||
|
scale = ra / rp
|
||||||
|
# transforms in the order they are applied
|
||||||
|
t1 = Transform( "translate(%f,%f)"%(-p1[x],-p1[y]) ).matrix
|
||||||
|
#t2 = Transform( "rotate(%f)"%(-angle_p) ).matrix
|
||||||
|
#t3 = Transform( "scale(%f,%f)"%(scale,scale) ).matrix
|
||||||
|
#t4 = Transform( "rotate(%f)"%angle_a ).matrix
|
||||||
|
t2 = rotateTransform(-angle_p)
|
||||||
|
t3 = scale_transform( scale, scale )
|
||||||
|
t4 = rotateTransform( angle_a )
|
||||||
|
t5 = Transform( "translate(%f,%f)"%(a1[x],a1[y]) ).matrix
|
||||||
|
# transforms in the order they are multiplied
|
||||||
|
t = t5
|
||||||
|
t = Transform(t) @ Transform(t4)
|
||||||
|
t = Transform(t) @ Transform(t3)
|
||||||
|
t = Transform(t) @ Transform(t2)
|
||||||
|
t = Transform(t) @ Transform(t1)
|
||||||
|
# return the combined transform
|
||||||
|
return t
|
||||||
|
|
||||||
|
|
||||||
|
def rotateTransform( a ):
|
||||||
|
return [[math.cos(a),-math.sin(a),0],[math.sin(a),math.cos(a),0]]
|
||||||
|
|
||||||
|
def scale_transform( sx, sy ):
|
||||||
|
return [[sx,0,0],[0,sy,0]]
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
BezierEnvelope().run()
|
21
extensions/fablabchemnitz/bezier_envelope/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Bezier Envelope",
|
||||||
|
"id": "fablabchemnitz.de.bezier_envelope",
|
||||||
|
"path": "bezier_envelope",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "Bezier Envelope",
|
||||||
|
"original_id": "de.gerrit_karius.envelope",
|
||||||
|
"license": "GNU GPL v2",
|
||||||
|
"license_url": "https://github.com/shlomif/Bezier-Envelope-for-Inkscape/blob/master/LICENSE",
|
||||||
|
"comment": "",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/bezier_envelope",
|
||||||
|
"fork_url": "https://github.com/shlomif/Bezier-Envelope-for-Inkscape",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Bezier+Envelope",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"github.com/shlomif",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
32
extensions/fablabchemnitz/blobs/blobs.inx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Blobs Texture</name>
|
||||||
|
<id>fablabchemnitz.de.blobs</id>
|
||||||
|
<param type="notebook" name="Nmain">
|
||||||
|
<page name="top" gui-text="All">
|
||||||
|
<label>Fills a box with blobs.</label>
|
||||||
|
<param max="256" name="nb" type="int" gui-text="How many blobs?">10</param>
|
||||||
|
<param name="pgsizep" type="bool" gui-text="Default rectangle to page size?">true</param>
|
||||||
|
<param max="10000" name="rx" type="int" gui-text="Work area x">1000</param>
|
||||||
|
<param max="10000" name="ry" type="int" gui-text="Work area y">1000</param>
|
||||||
|
</page>
|
||||||
|
<page name="bottom" gui-text="Each">
|
||||||
|
<label>Each blob.</label>
|
||||||
|
<param max="256" name="num" type="int" gui-text="Interior points">10</param>
|
||||||
|
<param max="10000." name="sz" type="float" gui-text="Size of a blob">50.</param>
|
||||||
|
<param max="1.0" name="cave" type="float" gui-text="Concavity">0.</param>
|
||||||
|
<param max="10.0" name="blunt" type="float" gui-text="Corner bluntness">0.3</param>
|
||||||
|
</page>
|
||||||
|
</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz Shape Generators">
|
||||||
|
<submenu name="Streaks And Blobs" />
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">blobs.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
169
extensions/fablabchemnitz/blobs/blobs.py
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# These two lines are only needed if you don't put the script directly into
|
||||||
|
# the installation directory
|
||||||
|
import math
|
||||||
|
import inkex
|
||||||
|
import random
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
class Blobs(inkex.EffectExtension):
|
||||||
|
"""
|
||||||
|
Creates a random blob from a convex hull over n points.
|
||||||
|
The expected degree of the polygon is sqrt(n). The corners
|
||||||
|
are blunted by the blunt parameter. 0 means sharp. 1 will
|
||||||
|
result in loopy splines.
|
||||||
|
"""
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("--pgsizep", type=inkex.Boolean, default=True, help="Default rectangle to page size?")
|
||||||
|
pars.add_argument('--num', type = int, default = 25, help = 'Number of random points to start with')
|
||||||
|
pars.add_argument('--blunt', type = float, default = 0.3, help = 'Bluntness of corners. Should be < 1')
|
||||||
|
pars.add_argument('--cave', type = float, default = 0.0, help = 'Concavity. Less blobby and more splatty')
|
||||||
|
pars.add_argument('--rx', type = int, default = 1000, help = 'Size of work area x')
|
||||||
|
pars.add_argument('--ry', type = int, default = 1000, help = 'Size of work area y')
|
||||||
|
pars.add_argument('--sz', type = float, default = 50., help = 'Size of a blob')
|
||||||
|
pars.add_argument('--nb', type = int, default = 10, help = 'Total number of blobs')
|
||||||
|
pars.add_argument("--Nmain", default='top', help="Active tab.")
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
global cave
|
||||||
|
if self.options.pgsizep:
|
||||||
|
svg = self.document.getroot()
|
||||||
|
rx = int(self.svg.unittouu(svg.get('width')))
|
||||||
|
ry = int(self.svg.unittouu(svg.attrib['height']))
|
||||||
|
else:
|
||||||
|
rx = self.options.rx
|
||||||
|
ry = self.options.ry
|
||||||
|
blunt = self.options.blunt
|
||||||
|
cave = self.options.cave
|
||||||
|
sz = self.options.sz
|
||||||
|
nb = self.options.nb
|
||||||
|
num = self.options.num
|
||||||
|
|
||||||
|
# Get access to main SVG document element and get its dimensions.
|
||||||
|
svg = self.document.getroot()
|
||||||
|
|
||||||
|
# Create a new layer.
|
||||||
|
layer = etree.SubElement(svg, 'g')
|
||||||
|
layer.set(inkex.addNS('label', 'inkscape'), 'Blob Layer')
|
||||||
|
layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
|
||||||
|
|
||||||
|
ctrs = [(random.randrange(rx) , random.randrange(ry))
|
||||||
|
for i in range(nb) ]
|
||||||
|
for ctr in ctrs :
|
||||||
|
points = [(random.gauss(ctr[0], sz) , random.gauss(ctr[1], sz))
|
||||||
|
for i in range(num) ]
|
||||||
|
|
||||||
|
px = hull(points)
|
||||||
|
pts = [points[px[i]] for i in range(len(px))]
|
||||||
|
|
||||||
|
# Create path element
|
||||||
|
path = etree.Element(inkex.addNS('path','svg'))
|
||||||
|
path.set('style', str(inkex.Style({'fill':'#000000'})))
|
||||||
|
pathstring = 'M ' + str(pts[0][0]) + ' ' + str(pts[0][1]) + ' '
|
||||||
|
|
||||||
|
for j in range(len(pts)):
|
||||||
|
k = (j+1) % len(pts)
|
||||||
|
kk = (j+2) % len(pts)
|
||||||
|
if j==0 :
|
||||||
|
(lasth, h1) = sHandles(pts[-1], pts[0], pts[1], blunt)
|
||||||
|
(h2, hnext) = sHandles(pts[j], pts[k], pts[kk], blunt)
|
||||||
|
pathstring += "C %f %f %f %f %f %f " % (h1[0], h1[1],
|
||||||
|
h2[0], h2[1],
|
||||||
|
pts[k][0], pts[k][1])
|
||||||
|
h1 = hnext
|
||||||
|
|
||||||
|
pathstring += 'Z'
|
||||||
|
path.set('d', pathstring)
|
||||||
|
layer.append(path)
|
||||||
|
|
||||||
|
def sHandles(pre, pt, post, blunt):
|
||||||
|
'''I'm proud of this cute little construction for the
|
||||||
|
spline handles. No doubt someone has thought of it before
|
||||||
|
but, if not, its name is ACHC Andrew's Cute Handle
|
||||||
|
Construction. Note: no trig function calls.'''
|
||||||
|
try :
|
||||||
|
slope = (post[1] - pt[1]) / (post[0] - pt[0])
|
||||||
|
except ZeroDivisionError :
|
||||||
|
slope = math.copysign(1E30 , post[1] - pt[1])
|
||||||
|
lenpre = distance(pre, pt)
|
||||||
|
lenpost = distance(pt, post)
|
||||||
|
lenr = lenpre**2 / lenpost
|
||||||
|
locx = math.copysign(lenr / math.sqrt(1. + slope**2) , post[0] - pt[0])
|
||||||
|
mark = (pre[0] - locx , pre[1] - locx*slope)
|
||||||
|
try :
|
||||||
|
markslope = (pt[1] - mark[1]) / (pt[0] - mark[0])
|
||||||
|
except ZeroDivisionError :
|
||||||
|
markslope = math.copysign(1E30 , pt[1] - mark[1])
|
||||||
|
prex = math.copysign(lenpre / math.sqrt(1. + markslope**2) ,
|
||||||
|
pt[0] - mark[0])
|
||||||
|
hpre = (pt[0] - prex*blunt , pt[1] - prex*markslope*blunt)
|
||||||
|
postx = prex*lenpost/lenpre
|
||||||
|
hpost = (pt[0] + postx*blunt , pt[1] + postx*markslope*blunt)
|
||||||
|
return (hpre, hpost)
|
||||||
|
|
||||||
|
"""Blunt=0.3 makes pleasingly round, mostly convex blobs. 0.4 makes them more
|
||||||
|
concave. 0.6 - 1.0 they're getting more and more pointy. 2.0 - 10. and they
|
||||||
|
grow appendages like hot-air balloons. 0.1 makes the corners pretty sharp.
|
||||||
|
0.0 and it's down to the convex hulls that are the basis of the blobs, that
|
||||||
|
is, polygons"""
|
||||||
|
|
||||||
|
def distance(a, b) :
|
||||||
|
return math.sqrt((a[0] - b[0])**2 + (a[1] - b[1])**2 )
|
||||||
|
|
||||||
|
def hull(arg):
|
||||||
|
"""Convex hull by Graham scan."""
|
||||||
|
xarr, yarr = zip(* arg)
|
||||||
|
ymin = min(yarr)
|
||||||
|
ind = findall(yarr, lambda y: y == ymin)
|
||||||
|
if len(ind) > 1 :
|
||||||
|
xshort = [xarr[j] for j in ind]
|
||||||
|
xmin = min(xshort)
|
||||||
|
j = ind[xshort.index(xmin)]
|
||||||
|
ind = j
|
||||||
|
else :
|
||||||
|
ind = ind[0]
|
||||||
|
all = list(range(len(xarr)))
|
||||||
|
del all[ind]
|
||||||
|
all.sort(key=lambda i : (xarr[i] - xarr[ind]) /
|
||||||
|
math.sqrt((xarr[i] - xarr[ind])**2 + (yarr[i] - yarr[ind])**2),
|
||||||
|
reverse=True)
|
||||||
|
if len(all) < 3 :
|
||||||
|
all.insert(0, ind)
|
||||||
|
return all
|
||||||
|
ans = [ind]
|
||||||
|
for i in all :
|
||||||
|
if len(ans) == 1 :
|
||||||
|
ans.append(i)
|
||||||
|
else :
|
||||||
|
while rightTurn(ans[-2], ans[-1], i, arg) :
|
||||||
|
ans.pop()
|
||||||
|
ans.append(i)
|
||||||
|
|
||||||
|
return ans
|
||||||
|
|
||||||
|
def rightTurn(j, k, l, arg) :
|
||||||
|
'''Cross product: Ax*By - Ay*Bx = Cz '''
|
||||||
|
ax = (arg[k][0] - arg[j][0])
|
||||||
|
by = (arg[l][1] - arg[k][1])
|
||||||
|
ay = (arg[k][1] - arg[j][1])
|
||||||
|
bx = (arg[l][0] - arg[k][0])
|
||||||
|
p = ax*by - ay*bx
|
||||||
|
dot = ax*bx + ay*by
|
||||||
|
cos = dot / math.sqrt((ax**2 + ay**2) * (bx**2 + by**2))
|
||||||
|
crt = 1 - cave*2
|
||||||
|
|
||||||
|
if p <= 0 :
|
||||||
|
return cos < crt #We forgive right turns based on /cave/
|
||||||
|
else :
|
||||||
|
return False
|
||||||
|
|
||||||
|
def findall(a, f):
|
||||||
|
r = []
|
||||||
|
for x, j in zip(a, range(len(a))) :
|
||||||
|
if f(x) :
|
||||||
|
r.append(j)
|
||||||
|
return r
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
Blobs().run()
|
21
extensions/fablabchemnitz/blobs/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Blobs Texture",
|
||||||
|
"id": "fablabchemnitz.de.blobs",
|
||||||
|
"path": "blobs",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "Blobs texture",
|
||||||
|
"original_id": "org.ekips.filter.blobsTexture",
|
||||||
|
"license": "GNU GPL v2",
|
||||||
|
"license_url": "https://inkscape.org/~kurn/%E2%98%85blobs",
|
||||||
|
"comment": "",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/blobs",
|
||||||
|
"fork_url": "https://inkscape.org/~kurn/%E2%98%85blobs",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Blobs+Texture",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"inkscape.org/kurn",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Blueprint Maker</name>
|
||||||
|
<id>fablabchemnitz.de.blueprint_maker</id>
|
||||||
|
<param name="palette" type="optiongroup" appearance="combo" gui-text="Palette:">
|
||||||
|
<option value="blueprint">Blueprint</option>
|
||||||
|
<option value="black">Screen</option>
|
||||||
|
<option value="white">Paper</option>
|
||||||
|
<option value="laser">Laser</option>
|
||||||
|
</param>
|
||||||
|
<param name="stroke_width" precision="4" type="float" min="0.001" max="10.000" gui-text="Line thickness:">1.000</param>
|
||||||
|
<param name="stroke_units" type="optiongroup" appearance="combo" gui-text="Line units:">
|
||||||
|
<option value="millimeters">mm</option>
|
||||||
|
<option value="centimeters">cm</option>
|
||||||
|
<option value="points">pt</option>
|
||||||
|
<option value="pixels">px</option>
|
||||||
|
</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Modify existing Path(s)"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">blueprint_maker.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
117
extensions/fablabchemnitz/blueprint_maker/blueprint_maker.py
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
import copy
|
||||||
|
|
||||||
|
class bluePrintMakerData():
|
||||||
|
def __init__(self,effect):
|
||||||
|
self.effect=effect
|
||||||
|
self.stroke_units=effect.options.stroke_units
|
||||||
|
self.unit_factor=1.0
|
||||||
|
self.set_units()
|
||||||
|
self.stroke_width=effect.options.stroke_width*self.unit_factor
|
||||||
|
self.palette=effect.options.palette
|
||||||
|
self.background_color=None
|
||||||
|
self.stroke_color=None
|
||||||
|
self.set_colors()
|
||||||
|
self.selected_nodes=[]
|
||||||
|
if len(effect.options.ids)==0:
|
||||||
|
self.selected_nodes=[effect.svg.getElementById(x) for x in effect.svg.get_ids()]
|
||||||
|
self.selected_nodes=[node for node in self.selected_nodes if effect.is_geometry(node)]
|
||||||
|
else:
|
||||||
|
self.selected_nodes=[y for x,y in effect.svg.selected.items()]
|
||||||
|
self.selected_nodes=[node for node in self.selected_nodes if effect.is_geometry(node,shapes=['path','g','rect','ellipse','perspective'])]
|
||||||
|
self.allowed_ids=[]
|
||||||
|
self.allowed_nodes=[]
|
||||||
|
self.set_objects()
|
||||||
|
|
||||||
|
def set_units(self):
|
||||||
|
if self.stroke_units=='millimeters':
|
||||||
|
self.unit_factor=3.543
|
||||||
|
if self.stroke_units=='centimeters':
|
||||||
|
self.unit_factor=35.433
|
||||||
|
if self.stroke_units=='points':
|
||||||
|
self.unit_factor=1.25
|
||||||
|
if self.stroke_units=='pixels':
|
||||||
|
self.unit_factor=1.0
|
||||||
|
|
||||||
|
def set_colors(self):
|
||||||
|
if self.palette=='blueprint':
|
||||||
|
self.background_color='#006fde'
|
||||||
|
self.stroke_color='#ffffff'
|
||||||
|
if self.palette=='black':
|
||||||
|
self.background_color='#000000'
|
||||||
|
self.stroke_color='#ffffff'
|
||||||
|
if self.palette=='white':
|
||||||
|
self.background_color='#ffffff'
|
||||||
|
self.stroke_color='#000000'
|
||||||
|
if self.palette=='laser':
|
||||||
|
self.background_color='#ffffff'
|
||||||
|
self.stroke_color='#ff0000'
|
||||||
|
|
||||||
|
def set_objects(self):
|
||||||
|
for current_id in self.effect.svg.get_ids():
|
||||||
|
node=self.effect.svg.getElementById(current_id)
|
||||||
|
if self.effect.is_geometry(node):
|
||||||
|
self.allowed_ids.append(current_id)
|
||||||
|
self.allowed_nodes.append(node)
|
||||||
|
|
||||||
|
class BluePrintMaker(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
inkex.Effect.__init__(self)
|
||||||
|
self.arg_parser.add_argument('-p', '--palette', help='Choose the colors...')
|
||||||
|
self.arg_parser.add_argument('-s', '--stroke_width', type=float, help='Stroke size...')
|
||||||
|
self.arg_parser.add_argument('-u', '--stroke_units', help='Choose the units...')
|
||||||
|
self.data=None
|
||||||
|
|
||||||
|
def is_a_group(self, node):
|
||||||
|
data=False
|
||||||
|
if node.tag==inkex.addNS('g','svg'):
|
||||||
|
data=True
|
||||||
|
return data
|
||||||
|
|
||||||
|
def is_geometry(self, node, shapes=['path','rect','ellipse','perspective']):
|
||||||
|
data=False
|
||||||
|
for s in shapes:
|
||||||
|
if node.tag==inkex.addNS(s,'svg'):
|
||||||
|
data=True
|
||||||
|
return data
|
||||||
|
|
||||||
|
def change_page_settings(self):
|
||||||
|
namedview=self.svg.namedview
|
||||||
|
namedview.set('pagecolor',self.data.background_color)
|
||||||
|
namedview.set(inkex.addNS('pageopacity', 'inkscape'),'1')
|
||||||
|
namedview.set(inkex.addNS('pageshadow', 'inkscape'),'0')
|
||||||
|
namedview.set('bordercolor',self.data.stroke_color)
|
||||||
|
namedview.set('borderopacity','1')
|
||||||
|
return None
|
||||||
|
|
||||||
|
def change_this_object(self,node):
|
||||||
|
styles=dict(inkex.Style.parse_str(node.get('style')))
|
||||||
|
styles_copy=copy.deepcopy(styles)
|
||||||
|
styles_copy['stroke']=self.data.stroke_color
|
||||||
|
styles_copy['stroke-width']=self.data.stroke_width
|
||||||
|
styles_copy['stroke-opacity']='1'
|
||||||
|
styles_copy['fill']='none'
|
||||||
|
styles_copy['fill-opacity']='1'
|
||||||
|
styles_copy['opacity']='1'
|
||||||
|
node.set('style',str(inkex.Style(styles_copy)))
|
||||||
|
return None
|
||||||
|
|
||||||
|
def iterate_on_objects(self,node=None):
|
||||||
|
if self.is_geometry(node):
|
||||||
|
self.change_this_object(node)
|
||||||
|
if self.is_a_group(node):
|
||||||
|
for current_node in list(node):
|
||||||
|
self.iterate_on_objects(current_node)
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
self.data=bluePrintMakerData(self)
|
||||||
|
self.change_page_settings()
|
||||||
|
for node in self.data.selected_nodes:
|
||||||
|
self.iterate_on_objects(node)
|
||||||
|
return None
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
BluePrintMaker().run()
|
21
extensions/fablabchemnitz/blueprint_maker/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Blueprint Maker",
|
||||||
|
"id": "fablabchemnitz.de.blueprint_maker",
|
||||||
|
"path": "blueprint_maker",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "Blueprint Maker",
|
||||||
|
"original_id": "org.inkscape.blueprint-maker",
|
||||||
|
"license": "GNU AGPL v3",
|
||||||
|
"license_url": "https://framagit.org/SxncR0OR/inkscape-extension-blueprint/-/blob/master/LICENSE.md",
|
||||||
|
"comment": "",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/blueprint_maker",
|
||||||
|
"fork_url": "https://framagit.org/SxncR0OR/inkscape-extension-blueprint",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Blueprint+Maker",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"framagit.org/SxncR0OR",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
32
extensions/fablabchemnitz/bounding_box/bounding_box.inx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Bounding Box</name>
|
||||||
|
<id>fablabchemnitz.de.bounding_box</id>
|
||||||
|
<label>Draws bounding boxes around selected objects, useful for debugging. Author: Pawel Mosakowski. Modded by Mario Voigt.</label>
|
||||||
|
<param name="unit" gui-text="Unit" type="optiongroup" appearance="combo" gui-description="The unit applies to interval and thresholds">
|
||||||
|
<option value="mm">mm</option>
|
||||||
|
<option value="cm">cm</option>
|
||||||
|
<option value="px">px</option>
|
||||||
|
<option value="pt">pt</option>
|
||||||
|
<option value="pc">pc</option>
|
||||||
|
<option value="in">in</option>
|
||||||
|
</param>
|
||||||
|
<param name="offset" min="-10000.000" max="10000.000" precision="3" type="float" gui-text="Offset from object (all directions)">0.000</param>
|
||||||
|
<param name="corner_radius" type="float" min="0.000" precision="3" max="10000.000" gui-text="Corner radius" gui-description="Only applies for box type">0.000</param>
|
||||||
|
<separator/>
|
||||||
|
<param name="box" type="bool" gui-text="Draw boxes">true</param>
|
||||||
|
<param name="circle" type="bool" gui-text="Draw circles">false</param>
|
||||||
|
<separator/>
|
||||||
|
<param name="split" type="bool" gui-text="Handle selection as group">true</param>
|
||||||
|
<effect>
|
||||||
|
<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 location="inx" interpreter="python">bounding_box.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
64
extensions/fablabchemnitz/bounding_box/bounding_box.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
import math
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
class BoundingBox(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument('--offset', type=float, default=0.0, help='Offset from object (all directions)')
|
||||||
|
pars.add_argument('--unit', default="mm")
|
||||||
|
pars.add_argument('--box', type=inkex.Boolean, default=0.0, help='Draw boxes')
|
||||||
|
pars.add_argument('--corner_radius', type=float, default=0.0, help='Corner radius')
|
||||||
|
pars.add_argument('--circle', type=inkex.Boolean, default=0.0, help='Draw circles')
|
||||||
|
pars.add_argument('--split', type = inkex.Boolean, default = True, help = 'Handle selection as group')
|
||||||
|
|
||||||
|
def drawBBox(self, bbox):
|
||||||
|
so = self.options
|
||||||
|
offset = self.svg.unittouu(str(so.offset) + so.unit)
|
||||||
|
if self.options.box:
|
||||||
|
attribs = {
|
||||||
|
'style' : str(inkex.Style({'stroke':'#ff0000','stroke-width':str(self.svg.unittouu("1px")),'fill':'none'})),
|
||||||
|
'x' : str(bbox.left - offset),
|
||||||
|
'y' : str(bbox.top - offset),
|
||||||
|
'width' : str(bbox.width + 2 * offset),
|
||||||
|
'height': str(bbox.height + 2 * offset),
|
||||||
|
'ry' : str(self.options.corner_radius),
|
||||||
|
'rx' : str(self.options.corner_radius)
|
||||||
|
}
|
||||||
|
etree.SubElement(self.svg.get_current_layer(), inkex.addNS('rect','svg'), attribs)
|
||||||
|
|
||||||
|
if self.options.circle:
|
||||||
|
attribs = {
|
||||||
|
'style': str(inkex.Style({'stroke':'#ff0000','stroke-width':str(self.svg.unittouu("1px")),'fill':'none'})),
|
||||||
|
'cx' : str(bbox.center_x),
|
||||||
|
'cy' : str(bbox.center_y),
|
||||||
|
#'r' : str(bbox.width / 2 + offset),
|
||||||
|
'r' : str(math.sqrt((bbox.width + 2 * offset)* (bbox.width + 2 * offset) + (bbox.height + 2 * self.options.offset) * (bbox.height + 2 * self.options.offset)) / 2),
|
||||||
|
}
|
||||||
|
etree.SubElement(self.svg.get_current_layer(), inkex.addNS('circle','svg'), attribs)
|
||||||
|
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
|
||||||
|
scale_factor = self.svg.unittouu("1px")
|
||||||
|
|
||||||
|
if len(self.svg.selected) > 0:
|
||||||
|
if self.options.split is False:
|
||||||
|
for element in self.svg.selected.values():
|
||||||
|
self.drawBBox(element.bounding_box())
|
||||||
|
else: #combined bbox
|
||||||
|
#self.drawBBox(self.svg.get_selected_bbox()) #works for InkScape (1:1.0+devel+202008292235+eff2292935) @ Linux and for Windows (but with deprecation)
|
||||||
|
#self.drawBBox(self.svg.selection.bounding_box()) #works for InkScape 1.1dev (9b1fc87, 2020-08-27)) @ Windows
|
||||||
|
bbox = inkex.BoundingBox()
|
||||||
|
for element in self.svg.selected.values():
|
||||||
|
if isinstance (element, inkex.ShapeElement) and element.tag != inkex.addNS('use','svg') and element.get('inkscape:groupmode') != 'layer': #bbox fails for svg:use elements and layers:
|
||||||
|
bbox += element.bounding_box()
|
||||||
|
self.drawBBox(bbox)
|
||||||
|
else:
|
||||||
|
inkex.errormsg('Please select some objects first.')
|
||||||
|
return
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
BoundingBox().run()
|
20
extensions/fablabchemnitz/bounding_box/meta.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Bounding Box",
|
||||||
|
"id": "fablabchemnitz.de.bounding_box",
|
||||||
|
"path": "bounding_box",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "Bounding Box",
|
||||||
|
"original_id": "fablabchemnitz.de.bounding_box",
|
||||||
|
"license": "GNU GPL v3",
|
||||||
|
"license_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/LICENSE",
|
||||||
|
"comment": "",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/bounding_box",
|
||||||
|
"fork_url": null,
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Bounding+Box",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
28
extensions/fablabchemnitz/bouwkamp_code/bouwkamp_code.inx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Bouwkamp Code</name>
|
||||||
|
<id>fablabchemnitz.de.bouwkamp</id>
|
||||||
|
<param name="tab" type="notebook">
|
||||||
|
<page name="settings" gui-text="Settings">
|
||||||
|
<param name="bouwkamp_code" type="string" gui-text="Bouwkamp code:">21, 112, 112, [50, 35, 27], [8, 19], [15, 17, 11], [6, 24], [29, 25, 9, 2], [7, 18], [16], [42], [4, 37], [33]</param>
|
||||||
|
<param name="wrap_in_group" type="bool" gui-text="Wrap in group">true</param>
|
||||||
|
</page>
|
||||||
|
<page name="help" gui-text="Help">
|
||||||
|
<label xml:space="preserve">This Inkscape extension allows you to generate squared squares and squared rectangles from Bouwkamp codes and table codes.
|
||||||
|
|
||||||
|
You can paste Bouwkamp codes with or without various formatting characters (like brackets) and convert them to the corresponding squares.
|
||||||
|
</label>
|
||||||
|
</page>
|
||||||
|
</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz Shape Generators">
|
||||||
|
<submenu name="Puzzles/Mazes/Nests"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">bouwkamp_code.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
163
extensions/fablabchemnitz/bouwkamp_code/bouwkamp_code.py
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
'''
|
||||||
|
BSD 3-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2019, Pascal Wagler
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
|
This Inkscape extension allows you to generate squared squares and squared rectangles from
|
||||||
|
Bouwkamp codes and table codes.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import re
|
||||||
|
import inkex
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
class BouwkampCode(inkex.EffectExtension):
|
||||||
|
"""
|
||||||
|
This Inkscape extension allows you to generate squared squares and squared rectangles from
|
||||||
|
Bouwkamp codes and table codes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument('--tab')
|
||||||
|
pars.add_argument('--bouwkamp_code', default='21, 112, 112, [50, 35, 27], [8, 19], [15, 17, 11], [6, 24], [29, 25, 9, 2], [7, 18], [16], [42], [4, 37], [33]', help='The Bouwkamp code.'
|
||||||
|
)
|
||||||
|
pars.add_argument('--wrap_in_group', type=inkex.Boolean, default=True, help='Should the generated items be wrapped inside a group.'
|
||||||
|
)
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
# compute center of the view
|
||||||
|
center = self.svg.namedview.center
|
||||||
|
|
||||||
|
# create the group that holds all the elements
|
||||||
|
container = self.svg.get_current_layer()
|
||||||
|
if self.options.wrap_in_group:
|
||||||
|
group_attributes = {
|
||||||
|
inkex.addNS('label', 'inkscape'): 'BouwkampSquares',
|
||||||
|
'transform': 'translate' + str(center)
|
||||||
|
}
|
||||||
|
group = etree.SubElement(self.svg.get_current_layer(), 'g', group_attributes)
|
||||||
|
container = group
|
||||||
|
|
||||||
|
# parse the bouwkamp code string as a list
|
||||||
|
bouwkamp_code = self.parse_bouwkamp_code_from_string(self.options.bouwkamp_code)
|
||||||
|
|
||||||
|
# show an error message and exit if the bouwkamp code is invalid
|
||||||
|
try:
|
||||||
|
self.exception_on_invalid_bouwkamp_code(bouwkamp_code)
|
||||||
|
except ValueError as exception:
|
||||||
|
inkex.errormsg(str(exception))
|
||||||
|
return
|
||||||
|
|
||||||
|
# draw the bouwkamp code
|
||||||
|
self.draw_bouwkamp_code(container, center, bouwkamp_code)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def exception_on_invalid_bouwkamp_code(bouwkamp_code):
|
||||||
|
"""
|
||||||
|
Raises a ValueError if the passed list is not a valid Bouwkamp code.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not bouwkamp_code: #len(bouwkamp_code) == 0
|
||||||
|
raise ValueError('Error: Invalid Bouwkamp code.\n\nThe Bouwkamp code is emtpy. ' +
|
||||||
|
'Please specify a valid Bouwkamp code.')
|
||||||
|
|
||||||
|
if len(bouwkamp_code) - 3 != bouwkamp_code[0]:
|
||||||
|
raise ValueError('Error: Invalid Bouwkamp code.\n\nThe Bouwkamp code has the wrong ' +
|
||||||
|
'length. The first number needs to specify how many squares ' +
|
||||||
|
'should be drawn.')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_bouwkamp_code_from_string(bouwkamp_code_string):
|
||||||
|
"""
|
||||||
|
Converts a Bouwkamp code string into a list of integers. Any parentheses, commas and
|
||||||
|
spaces are stripped. Extended Bouwkamp codes are not supported.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# replace every character (except numbers) with a space
|
||||||
|
text = re.sub('[^0-9]', ' ', bouwkamp_code_string)
|
||||||
|
# collapse all spaces to just one space
|
||||||
|
text = re.sub(' {1,}', ' ', text).strip()
|
||||||
|
# split the string into small strings and convert them to integers
|
||||||
|
numbers = [int(x) for x in text.split(" ")]
|
||||||
|
|
||||||
|
return numbers
|
||||||
|
|
||||||
|
def draw_bouwkamp_code(self, parent, center, bouwkamp_code):
|
||||||
|
"""
|
||||||
|
Draws the passed Bouwkamp code (a list of integers) with rectangles.
|
||||||
|
"""
|
||||||
|
|
||||||
|
order = bouwkamp_code[0]
|
||||||
|
width = bouwkamp_code[1]
|
||||||
|
# height = bouwkamp_code[2]
|
||||||
|
code = bouwkamp_code[3:] # cut the first three elements away
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
helper = [0] * 900
|
||||||
|
|
||||||
|
for rectangle in range(0, order):
|
||||||
|
i = 0
|
||||||
|
for j in range(1, width):
|
||||||
|
if helper[j] < helper[i]:
|
||||||
|
i = j
|
||||||
|
|
||||||
|
position = (i, helper[i])
|
||||||
|
dimension = (code[rectangle], code[rectangle])
|
||||||
|
self.draw_rectangle(position, dimension, parent, center)
|
||||||
|
|
||||||
|
for j in range(0, code[rectangle]):
|
||||||
|
helper[i+j] += code[rectangle]
|
||||||
|
|
||||||
|
def draw_rectangle(self, position, dimension, parent, center):
|
||||||
|
rectangle_style = {
|
||||||
|
'opacity': '1',
|
||||||
|
'stroke': '#000000',
|
||||||
|
'stroke-width': str(self.svg.unittouu('2px')),
|
||||||
|
'fill': '#FFFFFF'
|
||||||
|
}
|
||||||
|
|
||||||
|
transform = ""
|
||||||
|
if not self.options.wrap_in_group:
|
||||||
|
transform = 'translate' + str(center)
|
||||||
|
|
||||||
|
rectangle_attributes = {
|
||||||
|
'transform': transform,
|
||||||
|
'style': str(inkex.Style(rectangle_style)),
|
||||||
|
inkex.addNS('label', 'inkscape'): "Rectangle "+str(dimension[0]),
|
||||||
|
'x': str(position[0]),
|
||||||
|
'y': str(position[1]),
|
||||||
|
'width': str(dimension[0]),
|
||||||
|
'height': str(dimension[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
etree.SubElement(parent, inkex.addNS('rect', 'svg'), rectangle_attributes)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
BouwkampCode().run()
|
21
extensions/fablabchemnitz/bouwkamp_code/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Bouwkamp Code",
|
||||||
|
"id": "fablabchemnitz.de.bouwkamp_code",
|
||||||
|
"path": "boukamp_code",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "Bouwkamp code",
|
||||||
|
"original_id": "de.pascalwagler.inkscape.bouwkamp",
|
||||||
|
"license": "BSD-3-Clause License",
|
||||||
|
"license_url": "https://github.com/Wandmalfarbe/bouwkamp-code-generator/blob/master/LICENSE",
|
||||||
|
"comment": "ported to Inkscape v1 by Mario Voigt",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/boukamp_code",
|
||||||
|
"fork_url": "https://github.com/Wandmalfarbe/bouwkamp-code-generator",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Bouwkamp+Code",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"github.com/Wandmalfarbe",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
25
extensions/fablabchemnitz/braille_l18n/braille_l18n.inx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Convert To Localized Braille</name>
|
||||||
|
<id>fablabchemnitz.de.braille_l18n</id>
|
||||||
|
<param name="locale" gui-text="Alphabet:" type="optiongroup" appearance="combo">
|
||||||
|
<option value="en">English (ASCII)</option>
|
||||||
|
<option value="es">Spanish</option>
|
||||||
|
<option value="fr">French</option>
|
||||||
|
<option value="de">German</option>
|
||||||
|
<option value="gl">Galician</option>
|
||||||
|
<option value="eu">Basque</option>
|
||||||
|
<option value="ca">Catalan/Valencian</option>
|
||||||
|
</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Text" />
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">braille_l18n.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
504
extensions/fablabchemnitz/braille_l18n/braille_l18n.py
Normal file
@ -0,0 +1,504 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
|
||||||
|
# ---------------------------------
|
||||||
|
|
||||||
|
# UTILITIES
|
||||||
|
|
||||||
|
# Common standards
|
||||||
|
|
||||||
|
UPPERCASE_PREFIXES = {
|
||||||
|
chr(15): 0x2828, # uppercase prefix: https://codepoints.net/U+000F
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
LOUIS_BRAILLE_NUMBERS_PREFIX = 0x283c # Louis Braille's numbers prefix
|
||||||
|
LOUIS_BRAILLE_NUMBERS = { # Louis Braille's original numbers codification
|
||||||
|
"0": 0x281a,
|
||||||
|
"1": 0x2801,
|
||||||
|
"2": 0x2803,
|
||||||
|
"3": 0x2809,
|
||||||
|
"4": 0x2819,
|
||||||
|
"5": 0x2811,
|
||||||
|
"6": 0x280B,
|
||||||
|
"7": 0x281b,
|
||||||
|
"8": 0x2813,
|
||||||
|
"9": 0x280a,
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------
|
||||||
|
|
||||||
|
# English based locales
|
||||||
|
|
||||||
|
EN_ASCII = " A1B'K2L@CIF/MSP\"E3H9O6R^DJG>NTQ,*5<-U8V.%[$+X!&;:4\\0Z7(_?W]#Y)="
|
||||||
|
|
||||||
|
# Spanish based locales
|
||||||
|
|
||||||
|
ES_LETTERS = {
|
||||||
|
"A": 0x2801,
|
||||||
|
"B": 0x2803,
|
||||||
|
"C": 0x2809,
|
||||||
|
"D": 0x2819,
|
||||||
|
"E": 0x2811,
|
||||||
|
"F": 0x280B,
|
||||||
|
"G": 0x281b,
|
||||||
|
"H": 0x2813,
|
||||||
|
"I": 0x280a,
|
||||||
|
"J": 0x281a,
|
||||||
|
"K": 0x2805,
|
||||||
|
"L": 0x2807,
|
||||||
|
"M": 0x280d,
|
||||||
|
"N": 0x281d,
|
||||||
|
"Ñ": 0x283b,
|
||||||
|
"O": 0x2815,
|
||||||
|
"P": 0x280f,
|
||||||
|
"Q": 0x281f,
|
||||||
|
"R": 0x2817,
|
||||||
|
"S": 0x280e,
|
||||||
|
"T": 0x281e,
|
||||||
|
"U": 0x2825,
|
||||||
|
"V": 0x2827,
|
||||||
|
"W": 0x283a,
|
||||||
|
"X": 0x282d,
|
||||||
|
"Y": 0x283d,
|
||||||
|
"Z": 0x2835,
|
||||||
|
}
|
||||||
|
|
||||||
|
ES_SIGNS = {
|
||||||
|
" ": 0x2800, # braille space
|
||||||
|
"ª": 0x2801, # ordinal (feminine) -> same as A
|
||||||
|
"º": 0x2815, # ordinal (masculine) -> same as O
|
||||||
|
"&": 0x282f,
|
||||||
|
".": 0x2804,
|
||||||
|
",": 0x2802,
|
||||||
|
":": 0x2812,
|
||||||
|
";": 0x2806,
|
||||||
|
"¿": 0x2822,
|
||||||
|
"?": 0x2822,
|
||||||
|
"¡": 0x2816,
|
||||||
|
"!": 0x2816,
|
||||||
|
'"': 0x2826,
|
||||||
|
"(": 0x2823,
|
||||||
|
")": 0x281c,
|
||||||
|
# "[": 0x2837, collides with "Á" (Spanish and Catalan)
|
||||||
|
# "]": 0x283e, collides with "Ú" (Spanish and Catalan)
|
||||||
|
"*": 0x2814,
|
||||||
|
|
||||||
|
# math
|
||||||
|
"-": 0x2824,
|
||||||
|
"=": 0x2836,
|
||||||
|
"×": 0x2826, # multiplication
|
||||||
|
"÷": 0x2832, # division
|
||||||
|
"+": 0x2816,
|
||||||
|
"@": 0x2810,
|
||||||
|
}
|
||||||
|
|
||||||
|
ES_ACCENT_MARKS = {
|
||||||
|
"Á": 0x2837,
|
||||||
|
"É": 0x282e,
|
||||||
|
"Í": 0x280c,
|
||||||
|
"Ó": 0x282c,
|
||||||
|
"Ú": 0x283e,
|
||||||
|
"Ü": 0x2833,
|
||||||
|
}
|
||||||
|
|
||||||
|
ES_COMBINATIONS = {
|
||||||
|
# signs
|
||||||
|
"%": (0x2838, 0x2834),
|
||||||
|
"‰": (0x2838, 0x2834, 0x2834), # per mile
|
||||||
|
"/": (0x2820, 0x2802),
|
||||||
|
"\\": (0x2810, 0x2804),
|
||||||
|
"<": (0x2810, 0x2805),
|
||||||
|
">": (0x2828, 0x2802),
|
||||||
|
"|": (0x2838, 0x2807),
|
||||||
|
"{": (0x2810, 0x2807),
|
||||||
|
"}": (0x2838, 0x2802),
|
||||||
|
"–": (0x2824, 0x2824), # two different unicode dashes
|
||||||
|
"—": (0x2824, 0x2824),
|
||||||
|
"…": (0x2804, 0x2804, 0x2804),
|
||||||
|
|
||||||
|
# legal
|
||||||
|
"©": (0x2823, 0x2828, 0x2809, 0x281c), # copyright
|
||||||
|
"®": (0x2823, 0x2828, 0x2817, 0x281c), # registered
|
||||||
|
"℗": (0x2823, 0x2828, 0x280f, 0x281c),
|
||||||
|
"🄯": (0x2823, 0x2828, 0x2807, 0x281c),
|
||||||
|
|
||||||
|
# currencies
|
||||||
|
"€": (0x2838, 0x2811),
|
||||||
|
"$": (0x2838, 0x280e),
|
||||||
|
"¢": (0x2818, 0x2809),
|
||||||
|
"£": (0x2810, 0x282e),
|
||||||
|
"¥": (0x2838, 0x283d),
|
||||||
|
"¥": (0x2838, 0x283d),
|
||||||
|
}
|
||||||
|
|
||||||
|
CA_ACCENT_MARKS = {
|
||||||
|
"É": 0x283f,
|
||||||
|
"Í": 0x280c,
|
||||||
|
"Ó": 0x282a,
|
||||||
|
"Ú": 0x283e,
|
||||||
|
"À": 0x2837,
|
||||||
|
"È": 0x282e,
|
||||||
|
"Ò": 0x282c,
|
||||||
|
"Ï": 0x283b,
|
||||||
|
"Ü": 0x2833,
|
||||||
|
"Ç": 0x282f,
|
||||||
|
}
|
||||||
|
|
||||||
|
# French based locales
|
||||||
|
|
||||||
|
FR_LETTERS = {
|
||||||
|
"A": 0x2801,
|
||||||
|
"B": 0x2803,
|
||||||
|
"C": 0x2809,
|
||||||
|
"D": 0x2819,
|
||||||
|
"E": 0x2811,
|
||||||
|
"F": 0x280b,
|
||||||
|
"G": 0x281b,
|
||||||
|
"H": 0x2813,
|
||||||
|
"I": 0x280a,
|
||||||
|
"J": 0x281a,
|
||||||
|
"K": 0x2805,
|
||||||
|
"L": 0x2807,
|
||||||
|
"M": 0x280d,
|
||||||
|
"N": 0x281d,
|
||||||
|
"O": 0x2815,
|
||||||
|
"P": 0x280f,
|
||||||
|
"Q": 0x281f,
|
||||||
|
"R": 0x2817,
|
||||||
|
"S": 0x280e,
|
||||||
|
"T": 0x281e,
|
||||||
|
"U": 0x2825,
|
||||||
|
"V": 0x2827,
|
||||||
|
"W": 0x283a,
|
||||||
|
"X": 0x282d,
|
||||||
|
"Y": 0x283d,
|
||||||
|
"Z": 0x2835,
|
||||||
|
}
|
||||||
|
|
||||||
|
FR_ACCENT_MARKS = {
|
||||||
|
"É": 0x283f,
|
||||||
|
"À": 0x2837,
|
||||||
|
"È": 0x282e,
|
||||||
|
"Ù": 0x283e,
|
||||||
|
"Â": 0x2821,
|
||||||
|
"Ê": 0x2823,
|
||||||
|
"Î": 0x2829,
|
||||||
|
"Ô": 0x2839,
|
||||||
|
"Û": 0x2831,
|
||||||
|
"Ë": 0x282b,
|
||||||
|
"Ï": 0x283b,
|
||||||
|
"Ü": 0x2833,
|
||||||
|
"Ç": 0x282f,
|
||||||
|
"Œ": 0x282a, # oe ligature
|
||||||
|
}
|
||||||
|
|
||||||
|
FR_SIGNS = {
|
||||||
|
" ": 0x2800, # braille space
|
||||||
|
",": 0x2802,
|
||||||
|
";": 0x2806,
|
||||||
|
":": 0x2812,
|
||||||
|
".": 0x2832,
|
||||||
|
"?": 0x2822,
|
||||||
|
"!": 0x2816,
|
||||||
|
"«": 0x2836,
|
||||||
|
"»": 0x2836,
|
||||||
|
"“": 0x2836,
|
||||||
|
"”": 0x2836,
|
||||||
|
'"': 0x2836,
|
||||||
|
"‘": 0x2836,
|
||||||
|
"’": 0x2836,
|
||||||
|
"(": 0x2826,
|
||||||
|
")": 0x2834,
|
||||||
|
"'": 0x2804,
|
||||||
|
"'": 0x2804,
|
||||||
|
"/": 0x280c,
|
||||||
|
"@": 0x281c,
|
||||||
|
"^": 0x2808, # elevation exponent
|
||||||
|
"-": 0x2824,
|
||||||
|
"+": 0x2816,
|
||||||
|
"×": 0x2814, # multiplication
|
||||||
|
"÷": 0x2812, # division
|
||||||
|
"=": 0x2836,
|
||||||
|
}
|
||||||
|
|
||||||
|
FR_COMBINATIONS = {
|
||||||
|
"↔": (0x282a, 0x2812, 0x2815), # bidirectional arrow
|
||||||
|
"←": (0x282a, 0x2812, 0x2812), # left arrow
|
||||||
|
"→": (0x2812, 0x2812, 0x2815), # right arrow
|
||||||
|
"…": (0x2832, 0x2832, 0x2832), # unicode ellipsis
|
||||||
|
"–": (0x2824, 0x2824),
|
||||||
|
"—": (0x2824, 0x2824),
|
||||||
|
"_": (0x2810, 0x2824),
|
||||||
|
"[": (0x2818, 0x2826),
|
||||||
|
"]": (0x2834, 0x2803),
|
||||||
|
"°": (0x2810, 0x2815), # degrees
|
||||||
|
"§": (0x2810, 0x280f), # paragraph/section symbol
|
||||||
|
"&": (0x2810, 0x283f),
|
||||||
|
"\\": (0x2810, 0x280c),
|
||||||
|
"#": (0x2810, 0x283c),
|
||||||
|
"{": (0x2820, 0x2820, 0x2826),
|
||||||
|
"}": (0x2834, 0x2804, 0x2804),
|
||||||
|
|
||||||
|
# math
|
||||||
|
"µ": (0x2818, 0x280d), # micron
|
||||||
|
"π": (0x2818, 0x280f),
|
||||||
|
"≤": (0x2818, 0x2823),
|
||||||
|
"≥": (0x2818, 0x281c),
|
||||||
|
"<": (0x2810, 0x2823),
|
||||||
|
">": (0x2810, 0x281c),
|
||||||
|
"~": (0x2810, 0x2822),
|
||||||
|
"*": (0x2810, 0x2814),
|
||||||
|
"%": (0x2810, 0x282c),
|
||||||
|
"‰": (0x2810, 0x282c, 0x282c), # per mile
|
||||||
|
|
||||||
|
# legal
|
||||||
|
"©": (0x2810, 0x2809), # copyright
|
||||||
|
"®": (0x2810, 0x2817), # registered
|
||||||
|
"™": (0x2810, 0x281e), # trademark
|
||||||
|
|
||||||
|
# currencies
|
||||||
|
"¢": (0x2818, 0x2809),
|
||||||
|
"€": (0x2818, 0x2811),
|
||||||
|
"£": (0x2818, 0x2807),
|
||||||
|
"$": (0x2818, 0x280e),
|
||||||
|
"¥": (0x2818, 0x283d),
|
||||||
|
"¥": (0x2818, 0x283d),
|
||||||
|
}
|
||||||
|
|
||||||
|
# German based locales
|
||||||
|
|
||||||
|
DE_ACCENT_MARKS = {
|
||||||
|
"Ä": 0x281c,
|
||||||
|
"Ö": 0x282a,
|
||||||
|
"Ü": 0x2833,
|
||||||
|
}
|
||||||
|
|
||||||
|
DE_SIGNS = {
|
||||||
|
" ": 0x2800, # braille space
|
||||||
|
",": 0x2802,
|
||||||
|
";": 0x2806,
|
||||||
|
":": 0x2812,
|
||||||
|
"?": 0x2822,
|
||||||
|
"!": 0x2816,
|
||||||
|
"„": 0x2826,
|
||||||
|
"“": 0x2834,
|
||||||
|
"§": 0x282c,
|
||||||
|
".": 0x2804,
|
||||||
|
"–": 0x2824,
|
||||||
|
"‚": 0x2820,
|
||||||
|
}
|
||||||
|
|
||||||
|
DE_COMBINATIONS = {
|
||||||
|
# signs
|
||||||
|
"ß": (0x282e,), # converted to 'SS' if uppercased, so defined in combinations
|
||||||
|
"|": (0x2810, 0x2824),
|
||||||
|
"[": (0x2818, 0x2837),
|
||||||
|
"]": (0x2818, 0x283e),
|
||||||
|
"/": (0x2818, 0x280c),
|
||||||
|
"`": (0x2820, 0x2826),
|
||||||
|
"´": (0x2820, 0x2834),
|
||||||
|
"/": (0x2810, 0x2802),
|
||||||
|
"&": (0x2810, 0x2825),
|
||||||
|
"*": (0x2820, 0x2814),
|
||||||
|
"→": (0x2812, 0x2812, 0x2815),
|
||||||
|
"←": (0x282a, 0x2812, 0x2812),
|
||||||
|
"↔": (0x282a, 0x2812, 0x2812, 0x2815),
|
||||||
|
"%": (0x283c, 0x281a, 0x2834),
|
||||||
|
"‰": (0x283c, 0x281a, 0x2834, 0x2834),
|
||||||
|
"°": (0x2808, 0x2834),
|
||||||
|
"′": (0x2808, 0x2814),
|
||||||
|
"″": (0x2808, 0x2814, 0x2814),
|
||||||
|
"@": (0x2808, 0x281c),
|
||||||
|
"_": (0x2808, 0x2838),
|
||||||
|
"#": (0x2808, 0x283c),
|
||||||
|
|
||||||
|
# currencies
|
||||||
|
"€": (0x2808, 0x2811),
|
||||||
|
"$": (0x2808, 0x280e),
|
||||||
|
"¢": (0x2808, 0x2809),
|
||||||
|
"£": (0x2808, 0x2807),
|
||||||
|
|
||||||
|
# legal
|
||||||
|
"©": (0x2836, 0x2818, 0x2809, 0x2836),
|
||||||
|
"®": (0x2836, 0x2818, 0x2817, 0x2836),
|
||||||
|
}
|
||||||
|
|
||||||
|
# END: UTILITIES
|
||||||
|
|
||||||
|
# ---------------------------------
|
||||||
|
|
||||||
|
# LOCALE FUNCTIONS
|
||||||
|
|
||||||
|
def en_char_map(char):
|
||||||
|
"""English chars mapper.
|
||||||
|
|
||||||
|
Source: https://en.wikipedia.org/wiki/Braille_ASCII#Braille_ASCII_values
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
mapint = EN_ASCII.index(char.upper())
|
||||||
|
except ValueError:
|
||||||
|
return char
|
||||||
|
return chr(mapint + 0x2800)
|
||||||
|
|
||||||
|
def numbers_singleuppers_combinations_factory(
|
||||||
|
numbers_map,
|
||||||
|
singleuppers_map,
|
||||||
|
combinations_map, # also individual characters that are modified if uppercased
|
||||||
|
number_prefix,
|
||||||
|
uppercase_prefix,
|
||||||
|
):
|
||||||
|
"""Wrapper for various character mappers implementations."""
|
||||||
|
def char_mapper(char):
|
||||||
|
if char.isnumeric():
|
||||||
|
# numeric prefix + number
|
||||||
|
return "".join([chr(number_prefix), chr(numbers_map[char])])
|
||||||
|
try:
|
||||||
|
bcharint = singleuppers_map[char.upper()]
|
||||||
|
except KeyError:
|
||||||
|
try:
|
||||||
|
# combinations
|
||||||
|
return "".join([chr(num) for num in combinations_map[char]])
|
||||||
|
except KeyError:
|
||||||
|
return char
|
||||||
|
else:
|
||||||
|
# if uppercase, add uppercase prefix before letter
|
||||||
|
if char.isupper():
|
||||||
|
return "".join([chr(uppercase_prefix), chr(bcharint)])
|
||||||
|
return chr(bcharint)
|
||||||
|
return char_mapper
|
||||||
|
|
||||||
|
def es_char_map_loader():
|
||||||
|
"""Spanish/Galician chars mappers.
|
||||||
|
|
||||||
|
Source: https://sid.usal.es/idocs/F8/FDO12069/signografiabasica.pdf
|
||||||
|
"""
|
||||||
|
return numbers_singleuppers_combinations_factory(
|
||||||
|
LOUIS_BRAILLE_NUMBERS,
|
||||||
|
{
|
||||||
|
**ES_LETTERS,
|
||||||
|
**ES_ACCENT_MARKS,
|
||||||
|
**ES_SIGNS,
|
||||||
|
**UPPERCASE_PREFIXES,
|
||||||
|
},
|
||||||
|
ES_COMBINATIONS,
|
||||||
|
0x283c,
|
||||||
|
0x2828,
|
||||||
|
)
|
||||||
|
|
||||||
|
def eu_char_map_loader():
|
||||||
|
"""Euskera chars mapper.
|
||||||
|
|
||||||
|
Uses the sample implementation as Spanish but without accent marks.
|
||||||
|
|
||||||
|
Source: https://sid.usal.es/idocs/F8/FDO12069/signografiabasica.pdf
|
||||||
|
"""
|
||||||
|
return numbers_singleuppers_combinations_factory(
|
||||||
|
LOUIS_BRAILLE_NUMBERS,
|
||||||
|
{
|
||||||
|
**ES_LETTERS,
|
||||||
|
**ES_SIGNS,
|
||||||
|
**UPPERCASE_PREFIXES,
|
||||||
|
},
|
||||||
|
ES_COMBINATIONS,
|
||||||
|
0x283c,
|
||||||
|
0x2828,
|
||||||
|
)
|
||||||
|
|
||||||
|
def ca_char_map_loader():
|
||||||
|
"""Catalan/Valencian chars mappers. Uses the same implementation as
|
||||||
|
Spanish but different accent marks.
|
||||||
|
|
||||||
|
Source: https://sid.usal.es/idocs/F8/FDO12069/signografiabasica.pdf
|
||||||
|
"""
|
||||||
|
return numbers_singleuppers_combinations_factory(
|
||||||
|
LOUIS_BRAILLE_NUMBERS,
|
||||||
|
{
|
||||||
|
**ES_LETTERS,
|
||||||
|
**CA_ACCENT_MARKS,
|
||||||
|
**ES_SIGNS,
|
||||||
|
**UPPERCASE_PREFIXES,
|
||||||
|
},
|
||||||
|
ES_COMBINATIONS,
|
||||||
|
0x283c,
|
||||||
|
0x2828,
|
||||||
|
)
|
||||||
|
|
||||||
|
def fr_char_map_loader():
|
||||||
|
"""French chars mapper.
|
||||||
|
|
||||||
|
Source: https://sid.usal.es/idocs/F8/FDO12069/signografiabasica.pdf
|
||||||
|
"""
|
||||||
|
return numbers_singleuppers_combinations_factory(
|
||||||
|
LOUIS_BRAILLE_NUMBERS,
|
||||||
|
{
|
||||||
|
**FR_LETTERS,
|
||||||
|
**FR_ACCENT_MARKS,
|
||||||
|
**FR_SIGNS,
|
||||||
|
**UPPERCASE_PREFIXES,
|
||||||
|
},
|
||||||
|
FR_COMBINATIONS,
|
||||||
|
0x283c,
|
||||||
|
0x2828,
|
||||||
|
)
|
||||||
|
|
||||||
|
def de_char_map_loader():
|
||||||
|
"""German chars mapper.
|
||||||
|
|
||||||
|
- For letters, uses the same dictionary as French implementation.
|
||||||
|
|
||||||
|
Source: http://bskdl.org/textschrift.html
|
||||||
|
"""
|
||||||
|
return numbers_singleuppers_combinations_factory(
|
||||||
|
LOUIS_BRAILLE_NUMBERS,
|
||||||
|
{
|
||||||
|
**FR_LETTERS, # Same as French implementation
|
||||||
|
**DE_ACCENT_MARKS,
|
||||||
|
**DE_SIGNS,
|
||||||
|
**UPPERCASE_PREFIXES,
|
||||||
|
},
|
||||||
|
DE_COMBINATIONS,
|
||||||
|
0x283c,
|
||||||
|
0x2828,
|
||||||
|
)
|
||||||
|
|
||||||
|
# END: LOCALE FUNCTIONS
|
||||||
|
|
||||||
|
LOCALE_CHARMAPS = {
|
||||||
|
"en": en_char_map, # English
|
||||||
|
"es": es_char_map_loader, # Spanish
|
||||||
|
"fr": fr_char_map_loader, # French
|
||||||
|
"de": de_char_map_loader, # German
|
||||||
|
"gl": es_char_map_loader, # Galician
|
||||||
|
"eu": eu_char_map_loader, # Euskera
|
||||||
|
"ca": ca_char_map_loader, # Catalan/Valencian
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------
|
||||||
|
|
||||||
|
# EXTENSION
|
||||||
|
|
||||||
|
class BrailleL18n(inkex.TextExtension):
|
||||||
|
"""Convert to Braille giving a localized map of replacements."""
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument(
|
||||||
|
"-l", "--locale", type=str, dest="locale", default="en",
|
||||||
|
choices=LOCALE_CHARMAPS.keys(),
|
||||||
|
help="Locale to use converting to Braille.",
|
||||||
|
)
|
||||||
|
|
||||||
|
def process_chardata(self, text):
|
||||||
|
"""Replaceable chardata method for processing the text."""
|
||||||
|
chars_mapper = LOCALE_CHARMAPS[self.options.locale]
|
||||||
|
|
||||||
|
# `chars_mapper` could be a function loader or a characters mapper
|
||||||
|
# itself, so check if the characters mapper is loaded and load it
|
||||||
|
# if is created from a factory
|
||||||
|
if "loader" in chars_mapper.__name__:
|
||||||
|
chars_mapper = chars_mapper()
|
||||||
|
|
||||||
|
return ''.join(map(chars_mapper, text))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
BrailleL18n().run()
|
21
extensions/fablabchemnitz/braille_l18n/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Convert To Localized Braille",
|
||||||
|
"id": "fablabchemnitz.de.braille_l18n",
|
||||||
|
"path": "braille_l18n",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "Convert to localized Braille",
|
||||||
|
"original_id": "org.inkscape.text.braille-l18n",
|
||||||
|
"license": "BSD-3-Clause License",
|
||||||
|
"license_url": "https://github.com/mondeja/inkscape-braille-l18n-ext/blob/master/LICENSE",
|
||||||
|
"comment": "",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/braille-l18n",
|
||||||
|
"fork_url": "https://github.com/mondeja/inkscape-braille-l18n-ext",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Convert+To+Localized+Braille",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"github.com/mondeja",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
29
extensions/fablabchemnitz/can_generator/can_generator.inx
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Can Generator</name>
|
||||||
|
<id>fablabchemnitz.de.can_generator</id>
|
||||||
|
<param name="active-tab" type="notebook">
|
||||||
|
<page name="config" gui-text="Configuration">
|
||||||
|
<param name="diameter" type="int" min="3" max="1200" gui-text="Diameter of the can in mm">30</param>
|
||||||
|
<param name="overhang" type="int" min="0" max="1200" gui-text="Overhang of the can in mm">5</param>
|
||||||
|
<param name="height" type="int" min="3" max="1000" gui-text="Height of the can in mm">50</param>
|
||||||
|
<param name="angle" type="float" min="8" max="22.5" gui-text="Angle of the segments ( number of segments divisible by 2!)">22.5</param>
|
||||||
|
<param name="material" type="float" min="0.2" max="5" gui-text="Material thickness in mm">3.6</param>
|
||||||
|
<param name="bottom" type="bool" gui-text="Top and bottom?">false</param>
|
||||||
|
</page>
|
||||||
|
<page name="hilfe" gui-text="Hilfe">
|
||||||
|
<label xml:space="preserve">The idea of the can generator is to cut plywood so deep that it can be bent without breaking. From the bent wood the side of the can is formed. The lid is not completely round, but cut in segments. The number of segments corresponds to the cuts in the side of the can. For this purpose, the wood must be cut across the grain direction. The angle determines the number of segments. For example, 22.5 degrees gives 16 segments. Less should not be less.</label>
|
||||||
|
</page>
|
||||||
|
</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz Boxes/Papercraft">
|
||||||
|
<submenu name="Finger-jointed/Tabbed Boxes" />
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">can_generator.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
371
extensions/fablabchemnitz/can_generator/can_generator.py
Normal file
@ -0,0 +1,371 @@
|
|||||||
|
#! /usr/bin/env python3
|
||||||
|
'''
|
||||||
|
@author: mini@revollo.de
|
||||||
|
member of the erfindergarden
|
||||||
|
|
||||||
|
Inkscape Erweiterung - Dosen Generator
|
||||||
|
13.01.2019
|
||||||
|
|
||||||
|
Danke an neon22 https://github.com/Neon22/inkscape_extension_template
|
||||||
|
Nach seiner Anleitung konnte ich dieses Programm erstellen.
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
from lxml import etree
|
||||||
|
from math import *
|
||||||
|
|
||||||
|
__version__ = '0.2'
|
||||||
|
|
||||||
|
def points_to_svgd(p, close = True):
|
||||||
|
""" convert list of points (x,y) pairs
|
||||||
|
into a closed SVG path list
|
||||||
|
"""
|
||||||
|
f = p[0]
|
||||||
|
p = p[1:]
|
||||||
|
svgd = 'M %.2f,%.2f ' % f
|
||||||
|
for x in p:
|
||||||
|
svgd += ' %.4f,%.4f' % x
|
||||||
|
if close:
|
||||||
|
svgd += 'z'
|
||||||
|
return svgd
|
||||||
|
|
||||||
|
def punkte_erstellen(punkte, x, y):
|
||||||
|
###Schreibt die aktuellen Koordinaten in die Punkteliste
|
||||||
|
punkte.append((x, y))
|
||||||
|
|
||||||
|
class CanGenerator(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("--height", type=int, default = 50, help="Höhe der Dose")
|
||||||
|
pars.add_argument("--overhang", type=int, default = 40, help="Überstand des Deckels")
|
||||||
|
pars.add_argument("--diameter", type=int, default = 40, help="diameter der Dose")
|
||||||
|
pars.add_argument("--angle", type=float, default = 22.5, help="angle der segments")
|
||||||
|
pars.add_argument("--material", type=float, default = 3.6, help="Materialstärke")
|
||||||
|
pars.add_argument("--bottom", type=inkex.Boolean, default = False, help="Deckel und bottom?")
|
||||||
|
pars.add_argument("--active-tab", default='title', help="Active tab.")
|
||||||
|
|
||||||
|
self.deckel_punkte = []
|
||||||
|
self.deckel_pfad = []
|
||||||
|
self.seite_punkte = []
|
||||||
|
self.seite_pfad = []
|
||||||
|
self.ausschnitt_punkte = []
|
||||||
|
self.ausschnitt_pfad = []
|
||||||
|
self.ausschnitt_nummer = 0
|
||||||
|
self.einschnitt_punkte = []
|
||||||
|
self.einschnitt_pfad = []
|
||||||
|
self.einschnitt_nummer = 0
|
||||||
|
self.einschnitt_breite = 0.2 #Abstand zwischen den beiden Einschnittlinien
|
||||||
|
|
||||||
|
|
||||||
|
def pfad_erstellen(self, pfad, punkte):
|
||||||
|
# Die gesammelten x und y Koordinaten der Punkte werden in Pfade (d) umgewandelt.
|
||||||
|
|
||||||
|
pfad.append(points_to_svgd(punkte))
|
||||||
|
del punkte[:]
|
||||||
|
|
||||||
|
def pfade_schreiben(self):
|
||||||
|
###Schreibt alle Pfade nacheinander in die Szene
|
||||||
|
|
||||||
|
path_stroke = '#101010' # Farbe für die Dose
|
||||||
|
path_fill = 'none' # keine Füllung, nur eine Linie
|
||||||
|
path_stroke_width = '0.4' # can also be in form '0.6mm'
|
||||||
|
|
||||||
|
for nummer in range(len(self.pfade)):
|
||||||
|
# define style using basic dictionary
|
||||||
|
pfad_attribute = {'id': "pfad%d"%nummer, 'stroke': path_stroke,
|
||||||
|
'fill': path_fill, 'stroke-width': path_stroke_width,
|
||||||
|
'd': self.pfade[nummer]}
|
||||||
|
# add path to scene
|
||||||
|
pfad = etree.SubElement(self.topgroup, inkex.addNS('path','svg'), pfad_attribute )
|
||||||
|
|
||||||
|
def deckel_erstellen(self):
|
||||||
|
###Erstellt alle Punkte für den Aussenkreis des Deckels.
|
||||||
|
angle = self.angle
|
||||||
|
|
||||||
|
segments = int(360 / angle)
|
||||||
|
for segment_nr in range(segments + 1):
|
||||||
|
#y berechnen = Gegenkathete
|
||||||
|
y = sin(radians(angle)) * self.radius_mit_overhang * -1
|
||||||
|
#Innenangle berechnen
|
||||||
|
beta = 180 - 90 - angle
|
||||||
|
#Ankathete berechnen
|
||||||
|
b = sin(radians(beta)) * self.radius_mit_overhang
|
||||||
|
#x berechnen
|
||||||
|
x = self.radius_mit_overhang - b - self.overhang
|
||||||
|
punkte_erstellen(self.deckel_punkte, x, y)
|
||||||
|
angle += self.angle
|
||||||
|
self.deckel_schreiben()
|
||||||
|
|
||||||
|
def deckel_schreiben(self):
|
||||||
|
###Schreibt den Deckel ohne Ausschnitte in die Szene
|
||||||
|
|
||||||
|
path_stroke = '#0000ff' # Farbe für den Rand
|
||||||
|
path_fill = 'none' # keine Füllung, nur eine Linie
|
||||||
|
path_stroke_width = '0.6' # can also be in form '0.6mm'
|
||||||
|
#Punkte zu Pfad umwandeln
|
||||||
|
self.deckel_pfad = points_to_svgd(self.deckel_punkte, False)
|
||||||
|
|
||||||
|
# define style using basic dictionary
|
||||||
|
deckel_attribute = {'id': "rand", 'stroke': path_stroke, 'fill': path_fill,
|
||||||
|
'stroke-width': path_stroke_width , 'x': '0', 'y': '0', 'd': self.deckel_pfad}
|
||||||
|
# add path to scene
|
||||||
|
deckel = etree.SubElement(self.topgroup, inkex.addNS('path','svg'), deckel_attribute )
|
||||||
|
|
||||||
|
def ausschnitt_erstellen(self):
|
||||||
|
###Erstellt alle Punkte für den Aussenkreis des Deckels.
|
||||||
|
|
||||||
|
angle = self.angle
|
||||||
|
|
||||||
|
for segment_nr in range(self.segments):
|
||||||
|
|
||||||
|
###Punkt 1 wird berechnet
|
||||||
|
#y berechnen = Gegenkathete
|
||||||
|
y = sin(radians(angle)) * self.radius * -1
|
||||||
|
#Innenangle berechnen
|
||||||
|
beta = 180 - 90 - angle
|
||||||
|
#Ankathete berechnen
|
||||||
|
b = sin(radians(beta)) * self.radius
|
||||||
|
#x berechnen
|
||||||
|
x = self.radius - b
|
||||||
|
punkte_erstellen(self.ausschnitt_punkte, x, y)
|
||||||
|
angle += self.angle
|
||||||
|
|
||||||
|
###Punkt 2 wird berechnet
|
||||||
|
#y berechnen = Gegenkathete
|
||||||
|
y = sin(radians(angle)) * self.radius * -1
|
||||||
|
#Innenangle berechnen
|
||||||
|
beta = 180 - 90 - angle
|
||||||
|
#Ankathete berechnen
|
||||||
|
b = sin(radians(beta)) * self.radius
|
||||||
|
#x berechnen
|
||||||
|
x = self.radius - b
|
||||||
|
|
||||||
|
punkte_erstellen(self.ausschnitt_punkte, x, y)
|
||||||
|
|
||||||
|
###Punkt 3 wird berechnet
|
||||||
|
|
||||||
|
alpha = angle - (self.angle / 2)
|
||||||
|
beta = 180 - 90 - alpha
|
||||||
|
y += sin(radians(alpha)) * self.material
|
||||||
|
x += sin(radians(beta)) * self.material
|
||||||
|
angle += self.angle
|
||||||
|
punkte_erstellen(self.ausschnitt_punkte, x, y)
|
||||||
|
|
||||||
|
### Punkt 4 wird berechnet
|
||||||
|
alpha = 180 - alpha
|
||||||
|
beta = 180 - 90 - alpha
|
||||||
|
x -= sin(radians(alpha)) * self.ausschnitt_breite
|
||||||
|
y -= sin(radians(beta)) * self.ausschnitt_breite
|
||||||
|
punkte_erstellen(self.ausschnitt_punkte, x, y)
|
||||||
|
#
|
||||||
|
self.ausschnitt_schreiben()
|
||||||
|
del self.ausschnitt_punkte[:]
|
||||||
|
|
||||||
|
def ausschnitt_schreiben(self):
|
||||||
|
###Schreibt den Ausschnitte in die Szene
|
||||||
|
|
||||||
|
path_stroke = '#ff0000' # Farbe für den Rand
|
||||||
|
path_fill = 'none' # keine Füllung, nur eine Linie
|
||||||
|
path_stroke_width = '0.6' # can also be in form '0.6mm'
|
||||||
|
#Punkte zu Pfad umwandeln
|
||||||
|
self.ausschnitt_pfad = points_to_svgd(self.ausschnitt_punkte, True)
|
||||||
|
|
||||||
|
# define style using basic dictionary
|
||||||
|
ausschnitt_attribute = {'id': "ausschnitt_%s"%self.ausschnitt_nummer, 'stroke': path_stroke, 'fill': path_fill,
|
||||||
|
'stroke-width': path_stroke_width , 'x': '0', 'y': '0', 'd': self.ausschnitt_pfad}
|
||||||
|
self.ausschnitt_nummer += 1
|
||||||
|
# add path to scene
|
||||||
|
ausschnitt = etree.SubElement(self.topgroup, inkex.addNS('path','svg'), ausschnitt_attribute )
|
||||||
|
|
||||||
|
|
||||||
|
def seite_erstellen(self):
|
||||||
|
###Erstellt die Seite der Dose mit den Zinken und den Einschnitten###
|
||||||
|
|
||||||
|
x = 0
|
||||||
|
y = self.radius_mit_overhang + 10
|
||||||
|
punkte_erstellen(self.seite_punkte, x, y)
|
||||||
|
for item in range(int(self.segments / 2)):
|
||||||
|
y -= self.material
|
||||||
|
punkte_erstellen(self.seite_punkte, x, y)
|
||||||
|
x += self.ausschnitt_breite
|
||||||
|
punkte_erstellen(self.seite_punkte, x, y)
|
||||||
|
y += self.material
|
||||||
|
punkte_erstellen(self.seite_punkte, x, y)
|
||||||
|
x += self.ausschnitt_breite
|
||||||
|
punkte_erstellen(self.seite_punkte, x, y)
|
||||||
|
if self.bottom == False:
|
||||||
|
y += self.height - self.material
|
||||||
|
punkte_erstellen(self.seite_punkte, x, y)
|
||||||
|
x -= self.segments * self.ausschnitt_breite
|
||||||
|
punkte_erstellen(self.seite_punkte, x, y)
|
||||||
|
y -= self.height - self.material
|
||||||
|
punkte_erstellen(self.seite_punkte, x, y)
|
||||||
|
else:
|
||||||
|
y += self.height - self.material - self.material
|
||||||
|
punkte_erstellen(self.seite_punkte, x, y)
|
||||||
|
for item in range(int(self.segments / 2)):
|
||||||
|
x -= self.ausschnitt_breite
|
||||||
|
punkte_erstellen(self.seite_punkte, x, y)
|
||||||
|
y += self.material
|
||||||
|
punkte_erstellen(self.seite_punkte, x, y)
|
||||||
|
x -= self.ausschnitt_breite
|
||||||
|
punkte_erstellen(self.seite_punkte, x, y)
|
||||||
|
y -= self.material
|
||||||
|
punkte_erstellen(self.seite_punkte, x, y)
|
||||||
|
y -= self.height
|
||||||
|
punkte_erstellen(self.seite_punkte, x, y)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
self.seite_schreiben()
|
||||||
|
|
||||||
|
def seite_schreiben(self):
|
||||||
|
###Schreibt die Seite in die Szene
|
||||||
|
|
||||||
|
path_stroke = '#0000ff' # Farbe für den Rand
|
||||||
|
path_fill = 'none' # keine Füllung, nur eine Linie
|
||||||
|
path_stroke_width = '0.6' # can also be in form '0.6mm'
|
||||||
|
#Punkte zu Pfad umwandeln
|
||||||
|
self.seite_pfad = points_to_svgd(self.seite_punkte, True)
|
||||||
|
|
||||||
|
# define style using basic dictionary
|
||||||
|
seite_attribute = {'id': "seite", 'stroke': path_stroke, 'fill': path_fill,
|
||||||
|
'stroke-width': path_stroke_width , 'x': '0', 'y': '0', 'd': self.seite_pfad}
|
||||||
|
# add path to scene
|
||||||
|
seite = etree.SubElement(self.topgroup, inkex.addNS('path','svg'), seite_attribute )
|
||||||
|
|
||||||
|
def einschnitte_erstellen(self):
|
||||||
|
###Erstellt die Einschnitte in die Seite
|
||||||
|
|
||||||
|
x = self.einschnitt_breite / -2
|
||||||
|
y = self.radius_mit_overhang + 10 - self.material
|
||||||
|
|
||||||
|
for segment_nr in range(self.segments - 1):
|
||||||
|
|
||||||
|
###Punkt 1 wird berechnet
|
||||||
|
x += self.ausschnitt_breite
|
||||||
|
punkte_erstellen(self.einschnitt_punkte, x, y)
|
||||||
|
###Punkt 2 wird berechnet
|
||||||
|
x += self.einschnitt_breite
|
||||||
|
punkte_erstellen(self.einschnitt_punkte, x, y)
|
||||||
|
###Punkt 3 wird berechnet
|
||||||
|
y += self.height
|
||||||
|
punkte_erstellen(self.einschnitt_punkte, x, y)
|
||||||
|
### Punkt 4 wird berechnet
|
||||||
|
x -= self.einschnitt_breite
|
||||||
|
punkte_erstellen(self.einschnitt_punkte, x, y)
|
||||||
|
y -= self.height
|
||||||
|
|
||||||
|
self.einschnitte_schreiben()
|
||||||
|
del self.einschnitt_punkte[:]
|
||||||
|
|
||||||
|
def einschnitte_schreiben(self):
|
||||||
|
###Schreibt die Einschnitte in die Seite
|
||||||
|
|
||||||
|
path_stroke = '#00ff00' # Farbe für die Einschnitte
|
||||||
|
path_fill = 'none' # keine Füllung, nur eine Linie
|
||||||
|
path_stroke_width = '0.6' # can also be in form '0.6mm'
|
||||||
|
#Punkte zu Pfad umwandeln
|
||||||
|
self.einschnitt_pfad = points_to_svgd(self.einschnitt_punkte, True)
|
||||||
|
|
||||||
|
# define style using basic dictionary
|
||||||
|
einschnitt_attribute = {'id': "einschnitt_%s"%self.einschnitt_nummer, 'stroke': path_stroke, 'fill': path_fill,
|
||||||
|
'stroke-width': path_stroke_width , 'x': '0', 'y': '0', 'd': self.einschnitt_pfad}
|
||||||
|
self.einschnitt_nummer += 1
|
||||||
|
# add path to scene
|
||||||
|
einschnitt = etree.SubElement(self.undergroup, inkex.addNS('path','svg'), einschnitt_attribute )
|
||||||
|
|
||||||
|
### -------------------------------------------------------------------
|
||||||
|
### This is your main function and is called when the extension is run.
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
###Hauptprogramm
|
||||||
|
|
||||||
|
|
||||||
|
# holt die Parameter aus Inkscape
|
||||||
|
self.height = self.options.height
|
||||||
|
self.diameter = self.options.diameter
|
||||||
|
self.overhang = self.options.overhang
|
||||||
|
self.radius = self.diameter / 2
|
||||||
|
self.radius_mit_overhang = self.radius + self.overhang
|
||||||
|
self.angle = self.options.angle
|
||||||
|
self.bottom = self.options.bottom
|
||||||
|
self.material = self.options.material
|
||||||
|
self.segments = int(360 / self.angle)
|
||||||
|
#Ausschnittbreite errechnen
|
||||||
|
y = sin(radians(self.angle)) * self.radius
|
||||||
|
beta = 180 - 90 - self.angle
|
||||||
|
b = sin(radians(beta)) * self.radius
|
||||||
|
x = self.radius - b
|
||||||
|
self.ausschnitt_breite = sqrt((x * x) + (y * y))
|
||||||
|
# what page are we on
|
||||||
|
page_id = self.options.active_tab # sometimes wrong the very first time
|
||||||
|
|
||||||
|
#Eigenschaften der SVG auslesen und die Größe der Dose anpassen
|
||||||
|
svg = self.document.getroot()
|
||||||
|
#viewbox_size = '0 0 ' + str(self.breite) + ' ' + str(self.height)
|
||||||
|
#svg.set('viewBox', viewbox_size)
|
||||||
|
#svg.set('height', str(self.height))
|
||||||
|
#svg.set('width', str(self.breite))
|
||||||
|
|
||||||
|
# Embed the path in a group to make animation easier:
|
||||||
|
# Be sure to examine the internal structure by looking in the xml editor inside inkscape
|
||||||
|
|
||||||
|
# Make a nice useful name
|
||||||
|
g_attribs = { inkex.addNS('label','inkscape'): 'dosen-gruppe', 'id': "dose",}
|
||||||
|
# add the group to the document's current layer
|
||||||
|
self.topgroup = etree.SubElement(self.svg.get_current_layer(), 'g', g_attribs )
|
||||||
|
# Create SVG Path under this top level group
|
||||||
|
# Make a nice useful name
|
||||||
|
g_attribs = { inkex.addNS('label','inkscape'): 'einschnitt-gruppe', 'id': "einschnitte",}
|
||||||
|
# add the group to the document's current layer
|
||||||
|
self.undergroup = etree.SubElement(self.svg.get_current_layer(), 'g', g_attribs )
|
||||||
|
# Create SVG Path under this top level group
|
||||||
|
|
||||||
|
self.deckel_erstellen()
|
||||||
|
self.ausschnitt_erstellen()
|
||||||
|
self.seite_erstellen()
|
||||||
|
self.einschnitte_erstellen()
|
||||||
|
|
||||||
|
|
||||||
|
# Make a nice useful name
|
||||||
|
text_g_attribs = { inkex.addNS('label','inkscape'): 'dosen-gruppe', 'id': "Branding",}
|
||||||
|
# add the group to the document's current layer
|
||||||
|
textgroup = etree.SubElement(self.svg.get_current_layer(), 'g', text_g_attribs )
|
||||||
|
|
||||||
|
line_style = {'font-size': '10px', 'font-style':'normal', 'font-weight': 'normal',
|
||||||
|
'fill': '#ff0000', 'font-family': 'Consolas',
|
||||||
|
'text-anchor': 'start'}
|
||||||
|
branding_line_attribs = {inkex.addNS('label','inkscape'): 'branding-text',
|
||||||
|
'id': 'front text',
|
||||||
|
'style': str(inkex.Style(line_style)),
|
||||||
|
'x': str(0),
|
||||||
|
'y': str(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
branding_line = etree.SubElement(textgroup, inkex.addNS('text','svg'), branding_line_attribs)
|
||||||
|
branding_line.text = 'dosen-generator by mini revollo member of the erfindergarden'
|
||||||
|
|
||||||
|
# Make a nice useful name
|
||||||
|
einschnitt_text_g_attribs = { inkex.addNS('label','inkscape'): 'einschnitt-gruppe', 'id': "Einschnitte_Text",}
|
||||||
|
# add the group to the document's current layer
|
||||||
|
textgroup = etree.SubElement(self.svg.get_current_layer(), 'g', einschnitt_text_g_attribs )
|
||||||
|
|
||||||
|
line_style = {'font-size': '5px', 'font-style':'normal', 'font-weight': 'normal',
|
||||||
|
'fill': '#00ff00', 'font-family': 'Consolas',
|
||||||
|
'text-anchor': 'start'}
|
||||||
|
einschnitt_line_attribs = {inkex.addNS('label','inkscape'): 'Einschnitte_text',
|
||||||
|
'id': 'front text',
|
||||||
|
'style': str(inkex.Style(line_style)),
|
||||||
|
'x': str(0),
|
||||||
|
'y': str(self.radius_mit_overhang + self.height / 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
branding_line = etree.SubElement(textgroup, inkex.addNS('text','svg'), einschnitt_line_attribs)
|
||||||
|
branding_line.text = 'Die Einschnitte nur zu 70 Prozent in das Material lasern'
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
CanGenerator().run()
|
21
extensions/fablabchemnitz/can_generator/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Can Generator",
|
||||||
|
"id": "fablabchemnitz.de.can_generator",
|
||||||
|
"path": "can_generator",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "Dosengenerator",
|
||||||
|
"original_id": "githubacct.uniqueid.Dosengenerator",
|
||||||
|
"license": "GNU GPL v3",
|
||||||
|
"license_url": "https://github.com/minirevollo/Inkscape-Dosen-Generator/blob/master/LICENSE",
|
||||||
|
"comment": "ported to Inkscape v1 by Mario Voigt",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/can_generator",
|
||||||
|
"fork_url": "https://github.com/minirevollo/Inkscape-Dosen-Generator",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Can+Generator",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"github.com/minirevollo",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Cards Layout Guides</name>
|
||||||
|
<id>fablabchemnitz.de.card_layout_guides</id>
|
||||||
|
<param name="layout" type="optiongroup" appearance="combo" gui-text="Layout Type">
|
||||||
|
<option value="SIMPLE">Simple Grid</option>
|
||||||
|
<option value="FOLDING">Folding Grid</option>
|
||||||
|
</param>
|
||||||
|
<param name="card_width" type="float" precision="1" min="5" max="1000" gui-text="Card width (mm)">60</param>
|
||||||
|
<param name="card_height" type="float" precision="1" min="5" max="1000" gui-text="Card height (mm)">30</param>
|
||||||
|
<param name="orientation" type="optiongroup" appearance="combo" gui-text="Card Orientation">
|
||||||
|
<option value="HORIZONTAL">Horizontal</option>
|
||||||
|
<option value="VERTICAL">Vertical</option>
|
||||||
|
</param>
|
||||||
|
<param name="card_margin" type="float" precision="1" min="0" max="200" gui-text="Card margin(mm)">2</param>
|
||||||
|
<param name="bleed_margin" type="float" precision="1" min="0" max="200" gui-text="Bleed margin(mm)">2</param>
|
||||||
|
<param name="page_margin" type="float" precision="1" min="0" max="200" gui-text="Page margin(mm)">15</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Grids/Guides"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">card_layout_guides.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
@ -0,0 +1,480 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
|
||||||
|
# Copyright 2016 Luke Phillips (lukerazor@hotmail.com)
|
||||||
|
# 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 St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
# Extension dirs
|
||||||
|
# linux:~/.config/inkscape/extensions
|
||||||
|
# windows: [Drive]:\Program Files\Inkscape\share\extensions
|
||||||
|
|
||||||
|
from lxml import etree
|
||||||
|
import inkex
|
||||||
|
import copy
|
||||||
|
import math
|
||||||
|
|
||||||
|
FOLD_GAP = 5
|
||||||
|
CROP_GAP = 2
|
||||||
|
CROP_LENGTH = 3
|
||||||
|
|
||||||
|
inkex.NSS[u'cs'] = u'http://www.razorfoss.org/cardlayoutguides/'
|
||||||
|
|
||||||
|
def PrintDebug(string):
|
||||||
|
inkex.utils.debug( _(str(string)) )
|
||||||
|
|
||||||
|
def RoundAndDeduplicatePoints(points):
|
||||||
|
return sorted(list(set(map(lambda x: round(x, 3), points))))
|
||||||
|
|
||||||
|
class Point():
|
||||||
|
def __init__(self, x, y):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
|
||||||
|
def rotate(self, angle, origin):
|
||||||
|
"""
|
||||||
|
Rotate a point counterclockwise by a given angle around a given origin.
|
||||||
|
|
||||||
|
The angle should be given in degrees.
|
||||||
|
"""
|
||||||
|
rads = math.radians(angle)
|
||||||
|
newX = origin.x + math.cos(rads) * (self.x - origin.x) - math.sin(rads) * (self.y - origin.y)
|
||||||
|
newY = origin.y + math.sin(rads) * (self.x - origin.x) + math.cos(rads) * (self.y - origin.y)
|
||||||
|
|
||||||
|
return Point(newX, newY)
|
||||||
|
|
||||||
|
def add(self, point):
|
||||||
|
return Point(self.x + point.x, self.y + point.y)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parsePoint(pointString):
|
||||||
|
x, y = map(lambda v: float(v), pointString.split(","))
|
||||||
|
return Point(x, y)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse(pointString, orientationString=None):
|
||||||
|
p1 = Point.parsePoint(pointString)
|
||||||
|
p = Point(p1.x, p1.y)
|
||||||
|
|
||||||
|
if orientationString != None:
|
||||||
|
po = Point.parsePoint(orientationString)
|
||||||
|
p = p1.add(po.rotate(270, Point(0, 0)))
|
||||||
|
|
||||||
|
return p
|
||||||
|
|
||||||
|
class LineGeneratorBase(object):
|
||||||
|
def __init__(self, card_width, card_height, card_margin, bleed_margin, pageWidth, pageHeight, page_margin, unitConverterFunc):
|
||||||
|
self.UnitConverterFunc = unitConverterFunc
|
||||||
|
self.card_width = card_width
|
||||||
|
self.card_height = card_height
|
||||||
|
self.card_margin = card_margin
|
||||||
|
self.bleed_margin = bleed_margin
|
||||||
|
|
||||||
|
self.PageWidth = pageWidth
|
||||||
|
self.PageHeight = pageHeight
|
||||||
|
self.page_margin = page_margin
|
||||||
|
|
||||||
|
self.ContainerWidth = -1
|
||||||
|
self.ContainerHeight = -1
|
||||||
|
|
||||||
|
self.GuideOffsetsWithFold = [
|
||||||
|
0,
|
||||||
|
self.bleed_margin, self.card_margin, self.card_height - 2*self.card_margin, self.card_margin, self.bleed_margin,
|
||||||
|
2*FOLD_GAP,
|
||||||
|
self.bleed_margin, self.card_margin, self.card_height - 2*self.card_margin, self.card_margin, self.bleed_margin
|
||||||
|
]
|
||||||
|
|
||||||
|
self.GuideOffsetsNoFold = [
|
||||||
|
0,
|
||||||
|
self.bleed_margin, self.card_margin,
|
||||||
|
self.card_width - 2*self.card_margin,
|
||||||
|
self.card_margin, self.bleed_margin
|
||||||
|
]
|
||||||
|
|
||||||
|
def CalcPageLeftMargin(self):
|
||||||
|
return (self.PageWidth - self.ContentWidth) / 2.0
|
||||||
|
|
||||||
|
def CalcPageBottomMargin(self):
|
||||||
|
return (self.PageHeight - self.ContentHeight) / 2.0
|
||||||
|
|
||||||
|
def DrawGuide(self, xmlParent, xpos, ypos):
|
||||||
|
posString = "{},{}".format(xpos, ypos)
|
||||||
|
attribs = {'position': posString, 'orientation': posString}
|
||||||
|
|
||||||
|
etree.SubElement(xmlParent, inkex.addNS('guide',"sodipodi"), attribs)
|
||||||
|
|
||||||
|
def ConvertPoint(self, p):
|
||||||
|
# convert point into svg approriate values, including catering for inkscapes "alternative" axis sytem ie 0, 0 is bottom left not top left
|
||||||
|
newX = self.UnitConverterFunc("{}mm".format(p.x))
|
||||||
|
newY = self.PageHeight - self.UnitConverterFunc("{}mm".format(p.y))
|
||||||
|
|
||||||
|
return Point(newX, newY)
|
||||||
|
|
||||||
|
def DrawLine(self, xmlParent, p1, p2):
|
||||||
|
cp1 = self.ConvertPoint(p1)
|
||||||
|
cp2 = self.ConvertPoint(p2)
|
||||||
|
|
||||||
|
pathStr = "M {},{} {}, {}".format(cp1.x, cp1.y, cp2.x, cp2.y)
|
||||||
|
style = {'stroke': '#000000', 'stroke-width': self.UnitConverterFunc('0.25mm'), 'fill': 'none'}
|
||||||
|
attribs = {'style': str(inkex.Style(style)), 'd': pathStr}
|
||||||
|
|
||||||
|
etree.SubElement(xmlParent, inkex.addNS('path','svg'), attribs )
|
||||||
|
|
||||||
|
def DrawVerticleGuides(self, xmlParent, positions, gap):
|
||||||
|
curPos = self.CalcPageLeftMargin()
|
||||||
|
lastPos = -1
|
||||||
|
while curPos + self.ContainerWidth <= self.PageWidth - self.page_margin:
|
||||||
|
for offset in positions:
|
||||||
|
curPos += offset
|
||||||
|
if curPos != lastPos: # don't double draw
|
||||||
|
self.DrawGuide(xmlParent, curPos, 0)
|
||||||
|
|
||||||
|
lastPos = curPos
|
||||||
|
|
||||||
|
curPos += gap
|
||||||
|
|
||||||
|
def DrawHorizontalGuides(self, xmlParent, positions, gap):
|
||||||
|
curPos = self.CalcPageBottomMargin()
|
||||||
|
lastPos = -1
|
||||||
|
while curPos + self.ContainerHeight <= self.PageHeight - self.page_margin:
|
||||||
|
for offset in positions:
|
||||||
|
curPos += offset
|
||||||
|
if curPos != lastPos: # don't double draw
|
||||||
|
self.DrawGuide(xmlParent, 0, curPos)
|
||||||
|
|
||||||
|
lastPos = curPos
|
||||||
|
|
||||||
|
curPos += gap
|
||||||
|
|
||||||
|
def GenerateFoldLines(self, xmlParent):
|
||||||
|
lines = self.GetFoldLinePositions()
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
self.DrawLine(xmlParent, line[0], line[1])
|
||||||
|
|
||||||
|
def GenerateCropMarks(self, xmlParent):
|
||||||
|
lines = self.GetCropMarkLines()
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
self.DrawLine(xmlParent, line[0], line[1])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def CreateLineGenerator(layout, orientation, card_width, card_height, card_margin, bleed_margin, pageWidth, pageHeight, page_margin, unitConverterFunc):
|
||||||
|
if layout == "SIMPLE":
|
||||||
|
return SimpleGridLineGenerator(orientation, card_width, card_height, card_margin, bleed_margin, pageWidth, pageHeight, page_margin, unitConverterFunc)
|
||||||
|
|
||||||
|
if orientation == "HORIZONTAL":
|
||||||
|
return LineGeneratorForHorizontalCards(card_width, card_height, card_margin, bleed_margin, pageWidth, pageHeight, page_margin, unitConverterFunc)
|
||||||
|
|
||||||
|
return LineGeneratorForVerticalCards(card_width, card_height, card_margin, bleed_margin, pageWidth, pageHeight, page_margin, unitConverterFunc)
|
||||||
|
|
||||||
|
class SimpleGridLineGenerator(LineGeneratorBase):
|
||||||
|
def __init__(self, orientation, card_width, card_height, card_margin, bleed_margin, pageWidth, pageHeight, page_margin, unitConverterFunc):
|
||||||
|
if orientation == "HORIZONTAL":
|
||||||
|
super(SimpleGridLineGenerator, self).__init__(card_height, card_width, card_margin, bleed_margin, pageWidth, pageHeight, page_margin, unitConverterFunc)
|
||||||
|
else:
|
||||||
|
super(SimpleGridLineGenerator, self).__init__(card_width, card_height, card_margin, bleed_margin, pageWidth, pageHeight, page_margin, unitConverterFunc)
|
||||||
|
|
||||||
|
self.ContainerWidth = self.card_width + 2 * bleed_margin
|
||||||
|
self.ContainerHeight = self.card_height + 2 * bleed_margin
|
||||||
|
|
||||||
|
# num across
|
||||||
|
# num down
|
||||||
|
self.NumContainersAcross = int((self.PageWidth - 2*self.page_margin) // self.ContainerWidth) # round down division
|
||||||
|
self.NumContainersDown = int((self.PageHeight - 2*self.page_margin) // self.ContainerHeight) # round down division
|
||||||
|
|
||||||
|
# content sizes
|
||||||
|
self.ContentWidth = self.NumContainersAcross * self.ContainerWidth
|
||||||
|
self.ContentHeight = self.NumContainersDown * self.ContainerHeight
|
||||||
|
|
||||||
|
def GenerateGuides(self, xmlParent):
|
||||||
|
horizontalOffsets = self.GuideOffsetsNoFold = [
|
||||||
|
0,
|
||||||
|
self.bleed_margin, self.card_margin,
|
||||||
|
self.card_width - 2*self.card_margin,
|
||||||
|
self.card_margin, self.bleed_margin
|
||||||
|
]
|
||||||
|
|
||||||
|
verticalOffsets = self.GuideOffsetsNoFold = [
|
||||||
|
0,
|
||||||
|
self.bleed_margin, self.card_margin,
|
||||||
|
self.card_height - 2*self.card_margin,
|
||||||
|
self.card_margin, self.bleed_margin
|
||||||
|
]
|
||||||
|
|
||||||
|
self.DrawVerticleGuides(xmlParent, horizontalOffsets, 0)
|
||||||
|
self.DrawHorizontalGuides(xmlParent, verticalOffsets, 0)
|
||||||
|
|
||||||
|
def GetFoldLinePositions(self):
|
||||||
|
return [] # no fold lines in simple grid
|
||||||
|
|
||||||
|
def GetCropMarkLines(self):
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
leftMargin = self.CalcPageLeftMargin()
|
||||||
|
bottomMargin = self.CalcPageBottomMargin()
|
||||||
|
|
||||||
|
#determine all horizontal crop marks, duplicates possible
|
||||||
|
# figure out the ypos
|
||||||
|
horizontal_ypos = []
|
||||||
|
for idx in range(self.NumContainersDown):
|
||||||
|
bottomY = self.bleed_margin
|
||||||
|
topY = bottomY + self.card_height
|
||||||
|
containerOffset = bottomMargin + idx*self.ContainerHeight
|
||||||
|
|
||||||
|
horizontal_ypos.append(containerOffset + bottomY)
|
||||||
|
horizontal_ypos.append(containerOffset + topY)
|
||||||
|
|
||||||
|
horizontal_ypos = RoundAndDeduplicatePoints(horizontal_ypos) # remove duplicate positions
|
||||||
|
|
||||||
|
horizontal_xpos = [leftMargin - CROP_GAP, self.PageWidth - leftMargin + CROP_GAP + CROP_LENGTH]
|
||||||
|
for xpos in horizontal_xpos:
|
||||||
|
for ypos in horizontal_ypos:
|
||||||
|
lines.append([
|
||||||
|
Point(xpos - CROP_LENGTH, ypos),
|
||||||
|
Point(xpos, ypos)
|
||||||
|
])
|
||||||
|
|
||||||
|
#determine all vertical crop marks, duplicates possible
|
||||||
|
# figure out the xpos
|
||||||
|
vertical_xpos = []
|
||||||
|
for idx in range(self.NumContainersAcross):
|
||||||
|
leftX = self.bleed_margin
|
||||||
|
rightX = leftX + self.card_width
|
||||||
|
containerOffset = leftMargin + idx*self.ContainerWidth
|
||||||
|
|
||||||
|
vertical_xpos.append(containerOffset + leftX)
|
||||||
|
vertical_xpos.append(containerOffset + rightX)
|
||||||
|
|
||||||
|
vertical_xpos = RoundAndDeduplicatePoints(vertical_xpos) # remove duplicate positions
|
||||||
|
|
||||||
|
vertical_ypos = [bottomMargin - CROP_GAP, self.PageHeight - bottomMargin + CROP_GAP + CROP_LENGTH]
|
||||||
|
for xpos in vertical_xpos:
|
||||||
|
for ypos in vertical_ypos:
|
||||||
|
lines.append([
|
||||||
|
Point(xpos, ypos),
|
||||||
|
Point(xpos, ypos - CROP_LENGTH)
|
||||||
|
])
|
||||||
|
|
||||||
|
return lines
|
||||||
|
|
||||||
|
class LineGeneratorForVerticalCards(LineGeneratorBase):
|
||||||
|
def __init__(self, card_width, card_height, card_margin, bleed_margin, pageWidth, pageHeight, page_margin, unitConverterFunc):
|
||||||
|
super(LineGeneratorForVerticalCards, self).__init__(card_width, card_height, card_margin, bleed_margin, pageWidth, pageHeight, page_margin, unitConverterFunc)
|
||||||
|
|
||||||
|
self.ContainerWidth = card_width + 2*bleed_margin
|
||||||
|
self.ContainerHeight = 2*(card_height + 2*bleed_margin + FOLD_GAP)
|
||||||
|
|
||||||
|
# num across
|
||||||
|
self.NumContainersAcross = int((self.PageWidth - 2*self.page_margin) // self.ContainerWidth) # round down division
|
||||||
|
|
||||||
|
# num down
|
||||||
|
contentHeight = lambda n: n * self.ContainerHeight + (n - 1)*(2*FOLD_GAP)
|
||||||
|
workingHeight = self.PageHeight - 2*self.page_margin
|
||||||
|
|
||||||
|
self.NumContainersDown = int(workingHeight // self.ContainerHeight) # round down division for nominal value
|
||||||
|
if contentHeight(self.NumContainersDown) > workingHeight:
|
||||||
|
self.NumContainersDown -= 1
|
||||||
|
|
||||||
|
# content sizes
|
||||||
|
self.ContentWidth = self.NumContainersAcross * self.ContainerWidth
|
||||||
|
self.ContentHeight = contentHeight(self.NumContainersDown)
|
||||||
|
|
||||||
|
def GenerateGuides(self, xmlParent):
|
||||||
|
self.DrawVerticleGuides(xmlParent, self.GuideOffsetsNoFold, 0)
|
||||||
|
self.DrawHorizontalGuides(xmlParent, self.GuideOffsetsWithFold, 2*FOLD_GAP)
|
||||||
|
|
||||||
|
def GetFoldLinePositions(self):
|
||||||
|
lines = []
|
||||||
|
leftMargin = self.CalcPageLeftMargin()
|
||||||
|
|
||||||
|
for idx in range(self.NumContainersDown):
|
||||||
|
foldY = self.CalcPageBottomMargin() + idx*(self.ContainerHeight + 2*FOLD_GAP) + self.ContainerHeight/2
|
||||||
|
lines.append([Point(leftMargin, foldY), Point(self.PageWidth - leftMargin, foldY)])
|
||||||
|
|
||||||
|
return lines
|
||||||
|
|
||||||
|
def GetCropMarkLines(self):
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
leftMargin = self.CalcPageLeftMargin()
|
||||||
|
bottomMargin = self.CalcPageBottomMargin()
|
||||||
|
vertical_ypos = []
|
||||||
|
# determine all of the hornzontal facing crop marks, no duplicates possible
|
||||||
|
for idx in range(self.NumContainersDown):
|
||||||
|
bottomY = self.bleed_margin
|
||||||
|
topY = bottomY + self.card_height
|
||||||
|
containerOffset = bottomMargin + idx*(self.ContainerHeight + 2*FOLD_GAP)
|
||||||
|
vertical_ypos += [
|
||||||
|
containerOffset - CROP_GAP,
|
||||||
|
containerOffset + 2*self.bleed_margin + 2*self.card_margin + self.card_height + CROP_GAP + CROP_LENGTH
|
||||||
|
] # stash for later
|
||||||
|
|
||||||
|
for ypos in [containerOffset + bottomY, containerOffset + topY]:
|
||||||
|
for xpos in [leftMargin - CROP_GAP, self.PageWidth - leftMargin + CROP_GAP + CROP_LENGTH]:
|
||||||
|
lines.append([
|
||||||
|
Point(xpos, ypos),
|
||||||
|
Point(xpos - CROP_LENGTH, ypos)
|
||||||
|
])
|
||||||
|
|
||||||
|
#determine all vertical crop marks, duplicates possible
|
||||||
|
# figure out the xpos
|
||||||
|
vertical_xpos = []
|
||||||
|
for idx in range(self.NumContainersAcross):
|
||||||
|
leftX = self.bleed_margin
|
||||||
|
rightX = leftX + self.card_width
|
||||||
|
containerOffset = leftMargin + idx*self.ContainerWidth
|
||||||
|
|
||||||
|
vertical_xpos.append(containerOffset + leftX)
|
||||||
|
vertical_xpos.append(containerOffset + rightX)
|
||||||
|
|
||||||
|
vertical_xpos = list(set(vertical_xpos)) # remove duplicate positions
|
||||||
|
|
||||||
|
for xpos in vertical_xpos:
|
||||||
|
for ypos in vertical_ypos:
|
||||||
|
lines.append([
|
||||||
|
Point(xpos, ypos),
|
||||||
|
Point(xpos, ypos - CROP_LENGTH)
|
||||||
|
])
|
||||||
|
|
||||||
|
return lines
|
||||||
|
|
||||||
|
class LineGeneratorForHorizontalCards(LineGeneratorBase):
|
||||||
|
def __init__(self, card_width, card_height, card_margin, bleed_margin, pageWidth, pageHeight, page_margin, unitConverterFunc):
|
||||||
|
super(LineGeneratorForHorizontalCards, self).__init__(card_width, card_height, card_margin, bleed_margin, pageWidth, pageHeight, page_margin, unitConverterFunc)
|
||||||
|
|
||||||
|
self.ContainerWidth = 2*(card_height + 2 * bleed_margin + FOLD_GAP)
|
||||||
|
self.ContainerHeight = card_width + 2 * bleed_margin
|
||||||
|
|
||||||
|
# num across
|
||||||
|
contentWidth = lambda n: n * self.ContainerWidth + (n - 1)*(2*FOLD_GAP)
|
||||||
|
workingWidth = self.PageWidth - 2*self.page_margin
|
||||||
|
|
||||||
|
self.NumContainersAcross = int(workingWidth // self.ContainerWidth) # round down division for nominal value
|
||||||
|
if contentWidth(self.NumContainersAcross) > workingWidth:
|
||||||
|
self.NumContainersAcross -= 1
|
||||||
|
|
||||||
|
# num down
|
||||||
|
self.NumContainersDown = int((self.PageHeight - 2*self.page_margin) // self.ContainerHeight) # round down division
|
||||||
|
|
||||||
|
# content sizes
|
||||||
|
self.ContentWidth = contentWidth(self.NumContainersAcross)
|
||||||
|
self.ContentHeight = self.NumContainersDown * self.ContainerHeight
|
||||||
|
|
||||||
|
def GenerateGuides(self, xmlParent):
|
||||||
|
self.DrawVerticleGuides(xmlParent, self.GuideOffsetsWithFold, 2*FOLD_GAP)
|
||||||
|
self.DrawHorizontalGuides(xmlParent, self.GuideOffsetsNoFold, 0)
|
||||||
|
|
||||||
|
def GetFoldLinePositions(self):
|
||||||
|
lines = []
|
||||||
|
bottomMargin = self.CalcPageBottomMargin()
|
||||||
|
|
||||||
|
for idx in range(self.NumContainersAcross):
|
||||||
|
foldX = self.CalcPageLeftMargin() + idx*(self.ContainerWidth + 2*FOLD_GAP) + self.ContainerWidth/2
|
||||||
|
lines.append([Point(foldX, bottomMargin), Point(foldX, self.PageHeight - bottomMargin)])
|
||||||
|
|
||||||
|
return lines
|
||||||
|
|
||||||
|
def GetCropMarkLines(self):
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
leftMargin = self.CalcPageLeftMargin()
|
||||||
|
bottomMargin = self.CalcPageBottomMargin()
|
||||||
|
horizontal_xpos = []
|
||||||
|
|
||||||
|
# determine all of the vertical facing crop marks, no duplicates possible
|
||||||
|
for idx in range(self.NumContainersAcross):
|
||||||
|
leftX = self.bleed_margin
|
||||||
|
rightX = leftX + self.card_height
|
||||||
|
containerOffset = leftMargin + idx*(self.ContainerWidth + 2*FOLD_GAP)
|
||||||
|
horizontal_xpos += [
|
||||||
|
containerOffset - CROP_GAP,
|
||||||
|
containerOffset + 2*self.bleed_margin + 2*self.card_margin + self.card_height + CROP_GAP + CROP_LENGTH
|
||||||
|
] # stash for later
|
||||||
|
|
||||||
|
for xpos in [containerOffset + leftX, containerOffset + rightX]:
|
||||||
|
for ypos in [bottomMargin - CROP_GAP, self.PageHeight - bottomMargin + CROP_GAP + CROP_LENGTH]:
|
||||||
|
lines.append([
|
||||||
|
Point(xpos, ypos),
|
||||||
|
Point(xpos, ypos - CROP_LENGTH)
|
||||||
|
])
|
||||||
|
|
||||||
|
#determine all horizontal crop marks, duplicates possible
|
||||||
|
# figure out the xpos
|
||||||
|
horizontal_ypos = []
|
||||||
|
for idx in range(self.NumContainersDown):
|
||||||
|
bottomY = self.bleed_margin
|
||||||
|
topY = bottomY + self.card_width
|
||||||
|
containerOffset = bottomMargin + idx*self.ContainerHeight
|
||||||
|
|
||||||
|
horizontal_ypos.append(containerOffset + bottomY)
|
||||||
|
horizontal_ypos.append(containerOffset + topY)
|
||||||
|
|
||||||
|
horizontal_ypos = RoundAndDeduplicatePoints(horizontal_ypos) # remove duplicate positions
|
||||||
|
|
||||||
|
for ypos in horizontal_ypos:
|
||||||
|
for xpos in horizontal_xpos:
|
||||||
|
lines.append([
|
||||||
|
Point(xpos, ypos),
|
||||||
|
Point(xpos - CROP_LENGTH, ypos)
|
||||||
|
])
|
||||||
|
|
||||||
|
return lines
|
||||||
|
|
||||||
|
class CardLayoutGuides(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument('-l', '--layout')
|
||||||
|
pars.add_argument('-w', '--card_width', type = float)
|
||||||
|
pars.add_argument('-d', '--card_height', type = float)
|
||||||
|
pars.add_argument('-o', '--orientation')
|
||||||
|
pars.add_argument('-c', '--card_margin', type = float)
|
||||||
|
pars.add_argument('-b', '--bleed_margin', type = float)
|
||||||
|
pars.add_argument('-p', '--page_margin', type = float)
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
# find dimensions of page
|
||||||
|
pageWidth = inkex.units.convert_unit(self.svg.viewport_width, "mm", "px")
|
||||||
|
pageHeight = inkex.units.convert_unit(self.svg.viewport_height, "mm", "px")
|
||||||
|
|
||||||
|
opt = self.options
|
||||||
|
|
||||||
|
guideParent = self.document.xpath('//sodipodi:namedview',namespaces=inkex.NSS)[0]
|
||||||
|
|
||||||
|
### GUIDES
|
||||||
|
# remove all the existing guides
|
||||||
|
[node.delete() for node in self.document.xpath('//sodipodi:guide',namespaces=inkex.NSS)]
|
||||||
|
|
||||||
|
# create the generator object
|
||||||
|
gen = LineGeneratorBase.CreateLineGenerator(opt.layout, opt.orientation, opt.card_width, opt.card_height, opt.card_margin, opt.bleed_margin, pageWidth, pageHeight, opt.page_margin, self.svg.unittouu)
|
||||||
|
|
||||||
|
gen.GenerateGuides(guideParent)
|
||||||
|
|
||||||
|
### FOLD LINES
|
||||||
|
# remove any existing 'Crop marks' layer
|
||||||
|
[node.delete() for node in self.document.xpath("//svg:g[@inkscape:label='Crop Marks']",namespaces=inkex.NSS)]
|
||||||
|
|
||||||
|
svg = self.document.xpath('//svg:svg', namespaces=inkex.NSS)[0]
|
||||||
|
layer = etree.SubElement(svg, inkex.addNS('g',"svg"), {})
|
||||||
|
layer.set(inkex.addNS('label', 'inkscape'), "Crop Marks")
|
||||||
|
layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
|
||||||
|
#layer.set(inkex.addNS('insensitive', 'sodipodi'), 'true')
|
||||||
|
|
||||||
|
gen.GenerateFoldLines(layer)
|
||||||
|
|
||||||
|
### CROP MARKS
|
||||||
|
gen.GenerateCropMarks(layer)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
CardLayoutGuides().run()
|
21
extensions/fablabchemnitz/card_layout_guides/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Card Layout Guides",
|
||||||
|
"id": "fablabchemnitz.de.card_layout_guides",
|
||||||
|
"path": "card_layout_guides",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "Cards",
|
||||||
|
"original_id": "phillips.effect.cardlayoutguides",
|
||||||
|
"license": "GNU GPL v2",
|
||||||
|
"license_url": "https://sourceforge.net/p/razorfoss/svn/HEAD/tree/trunk/Inkscape/LayoutGuides/CardLayoutGuides.py",
|
||||||
|
"comment": "",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/card_layout_guides",
|
||||||
|
"fork_url": "https://sourceforge.net/p/razorfoss/svn/HEAD/tree/trunk/Inkscape/LayoutGuides/",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Card+Layout+Guides",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"Luke Phillips:lukerazor@hotmail.com",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
37
extensions/fablabchemnitz/checkerboard/checkerboard.inx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Checkerboard</name>
|
||||||
|
<id>fablabchemnitz.de.checkerboard</id>
|
||||||
|
<param name="tab" type="notebook">
|
||||||
|
<page name="params" gui-text="Params">
|
||||||
|
<param name="size" type="string" gui-text="Cell size" gui-description="Enter value + unit, e.g. px, pt, mm, cm, in">50px</param>
|
||||||
|
<param name="rows" type="int" min="2" max="1000" gui-text="Rows">10</param>
|
||||||
|
<param name="cols" type="int" min="2" max="1000" gui-text="Columns">10</param>
|
||||||
|
<param name="layer" type="bool" gui-text="Create in current layer">true</param>
|
||||||
|
</page>
|
||||||
|
<page name="color1" gui-text="1st color">
|
||||||
|
<param name="color1" type="color" gui-text="Color 1">4286282751</param>
|
||||||
|
</page>
|
||||||
|
<page name="color2" gui-text="2nd color">
|
||||||
|
<param name="color2" type="color" gui-text="Color 2">8092671</param>
|
||||||
|
</page>
|
||||||
|
<page name="help" gui-text="Help">
|
||||||
|
<label>1. On the Params tab, choose the cell size (size of the constituent squares), number of rows and columns, and whether to add the checkerboard to the current layer (if unchecked, the checkerboard will be added to the root layer)</label>
|
||||||
|
<label>2. On the 1st color and 2nd color tabs, select the colors for the two sets of squares</label>
|
||||||
|
<label>3. Click Apply</label>
|
||||||
|
<label>More information and source code:</label>
|
||||||
|
<label appearance="url">https://github.com/jeffkayser/inkscape-checkerboard</label>
|
||||||
|
</page>
|
||||||
|
</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Grids/Guides"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">checkerboard.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
93
extensions/fablabchemnitz/checkerboard/checkerboard.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
Inkscape extension to create checkerboard patterns
|
||||||
|
|
||||||
|
|
||||||
|
Copyright (C) 2011 Jeff Kayser
|
||||||
|
|
||||||
|
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 inkex
|
||||||
|
from lxml import etree
|
||||||
|
import re
|
||||||
|
from inkex import Color
|
||||||
|
|
||||||
|
def rgbToHex(pickerColor):
|
||||||
|
longcolor = int(pickerColor)
|
||||||
|
if longcolor < 0:
|
||||||
|
longcolor = longcolor & 0xFFFFFFFF
|
||||||
|
return '#' + format(longcolor >> 8, '06X')
|
||||||
|
|
||||||
|
def draw_square(x, y, w, h, color, parent, id_=None):
|
||||||
|
"""Draw a w*h square at (x, y) having color color
|
||||||
|
"""
|
||||||
|
if len(color) == 4:
|
||||||
|
opacity=color[3]
|
||||||
|
else:
|
||||||
|
opacity=1.0
|
||||||
|
|
||||||
|
style = {'stroke': 'none', 'stroke-width': '1', 'fill': rgbToHex(color), 'fill-opacity': opacity}
|
||||||
|
attribs = {'style': str(inkex.Style(style)), 'height': str(h), 'width': str(w), 'x': str(x), 'y': str(y)}
|
||||||
|
if id_ is not None:
|
||||||
|
attribs.update({'id': id_})
|
||||||
|
obj = etree.SubElement(parent, inkex.addNS('rect', 'svg'), attribs)
|
||||||
|
|
||||||
|
def draw_grid(x, y, rows, cols, size, color1, color2, parent):
|
||||||
|
"""Draw a rows*cols checkboard grid at (x, y) with square size of size*size,
|
||||||
|
with squares having alternating colors color1 and color2
|
||||||
|
"""
|
||||||
|
# Group like-colors
|
||||||
|
group1 = etree.SubElement(parent, 'g', {'id': 'diagonal1'})
|
||||||
|
group2 = etree.SubElement(parent, 'g', {'id': 'diagonal2'})
|
||||||
|
for row in range(int(rows)):
|
||||||
|
for col in range(int(cols)):
|
||||||
|
alternate = (col + row) % 2 == 0
|
||||||
|
color = color1 if alternate else color2
|
||||||
|
group = group1 if alternate else group2
|
||||||
|
id_ = 'cell{0}x{1}'.format(col, row)
|
||||||
|
draw_square(x + col * size, y + row * size, size, size, color, group, id_)
|
||||||
|
|
||||||
|
class Checkerboard(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("--tab")
|
||||||
|
pars.add_argument("--color1", type=Color, default=4286282751)
|
||||||
|
pars.add_argument("--color2", type=Color, default=8092671)
|
||||||
|
pars.add_argument("--size")
|
||||||
|
pars.add_argument("--rows", type=int)
|
||||||
|
pars.add_argument("--cols", type=int)
|
||||||
|
pars.add_argument("--layer", type=inkex.Boolean)
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
if self.svg.get_current_layer() is not None:
|
||||||
|
group = etree.SubElement(self.svg.get_current_layer(), 'g', {'id': 'checkerboard'})
|
||||||
|
else:
|
||||||
|
parent = self.document.getroot()
|
||||||
|
group = etree.SubElement(parent, 'g', {'id': 'checkerboard'})
|
||||||
|
|
||||||
|
rows = self.options.rows
|
||||||
|
cols = self.options.cols
|
||||||
|
# Convert to pixels
|
||||||
|
size = self.svg.unittouu(self.options.size)
|
||||||
|
color1 = self.options.color1
|
||||||
|
color2 = self.options.color2
|
||||||
|
# Center checkerboard within visible viewport
|
||||||
|
x, y = self.svg.namedview.center[0] - cols * size / 2, self.svg.namedview.center[1] - rows * size / 2
|
||||||
|
draw_grid(x, y, rows, cols, size, color1, color2, group)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
Checkerboard().run()
|
21
extensions/fablabchemnitz/checkerboard/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Checkerboard",
|
||||||
|
"id": "fablabchemnitz.de.checkerboard",
|
||||||
|
"path": "checkerboard",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "Checkerboard",
|
||||||
|
"original_id": "org.jeffkayser.checkerboard",
|
||||||
|
"license": "GNU GPL v2",
|
||||||
|
"license_url": "https://github.com/jeffkayser/inkscape-checkerboard/blob/master/LICENSE",
|
||||||
|
"comment": "ported to Inkscape v1 by Mario Voigt",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/checkerboard",
|
||||||
|
"fork_url": "https://github.com/jeffkayser/inkscape-checkerboard",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Checkerboard",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"github.com/jeffkayser",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Circle Tangents (Replaced by Snap Objects Feature)</name>
|
||||||
|
<id>fablabchemnitz.de.circle_tangents</id>
|
||||||
|
<param name="position" type="optiongroup" appearance="combo" gui-text="Outer or inner?">
|
||||||
|
<option value="inner">Inner</option>
|
||||||
|
<option value="outer">Outer</option>
|
||||||
|
</param>
|
||||||
|
<param name="selector" type="optiongroup" appearance="combo" gui-text="Which tangents?">
|
||||||
|
<option value="first">First</option>
|
||||||
|
<option value="second">Second</option>
|
||||||
|
<option value="both">Both</option>
|
||||||
|
</param>
|
||||||
|
<param name="use_style_from_first" type="bool" gui-text="Use style from first selected">true</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Paths - Join/Order"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">circle_tangents.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
230
extensions/fablabchemnitz/circle_tangents/circle_tangents.py
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
'''
|
||||||
|
Copyright (C) 2012 Rhys Owen, rhysun@gmail.com
|
||||||
|
|
||||||
|
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, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
A
|
||||||
|
|\
|
||||||
|
| \
|
||||||
|
| \
|
||||||
|
| \
|
||||||
|
| \
|
||||||
|
b| \h
|
||||||
|
| \
|
||||||
|
| \
|
||||||
|
|_ \
|
||||||
|
|_|_______\
|
||||||
|
C a B
|
||||||
|
'''
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
from inkex.paths import Path
|
||||||
|
from inkex import Transform
|
||||||
|
from math import *
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
def poltocar(r, rad, negx=False, negy=False):
|
||||||
|
# converts polar coords to cartesian
|
||||||
|
x = r * cos(rad)
|
||||||
|
y = r * sin(rad)
|
||||||
|
if negx and not negy:
|
||||||
|
return [-x, y]
|
||||||
|
elif not negx and negy:
|
||||||
|
return [x, -y]
|
||||||
|
elif not negx and not negy:
|
||||||
|
return [-x, -y]
|
||||||
|
else:
|
||||||
|
return [x, y]
|
||||||
|
|
||||||
|
def deuclid(x1, y1, x2, y2):
|
||||||
|
# euclidean distance between two cartesian coords
|
||||||
|
squarex = (x1 - x2)**2
|
||||||
|
squarey = (y1 - y2)**2
|
||||||
|
d = sqrt(squarex + squarey)
|
||||||
|
return d
|
||||||
|
|
||||||
|
def getAngle(b, h):
|
||||||
|
angle = asin(b / h)
|
||||||
|
return angle
|
||||||
|
|
||||||
|
def aLength(b, h):
|
||||||
|
a = sqrt(h**2-b**2)
|
||||||
|
return a
|
||||||
|
|
||||||
|
|
||||||
|
def getPathData(obj):
|
||||||
|
if obj.get("d"):# If the circle has been converted to a path object
|
||||||
|
d = obj.get("d")
|
||||||
|
p = Path(d)
|
||||||
|
if obj.get("transform"):
|
||||||
|
trans = Transform(obj.get("transform"))
|
||||||
|
scalex = trans[0][0]
|
||||||
|
scaley = trans[1][1]
|
||||||
|
data = {'rx' : p[1][1][0]*scalex,
|
||||||
|
'ry' : p[1][1][1]*scaley,
|
||||||
|
'x' : (trans[0][0]*p[0][1][0])+(trans[0][1]*p[0][1][1])+trans[0][2]-(p[1][1][0]*scalex),
|
||||||
|
'y' : (trans[1][0]*p[0][1][0])+(trans[1][1]*p[0][1][1])+trans[1][2]}
|
||||||
|
else:
|
||||||
|
data = {'rx': p[1][1][0],
|
||||||
|
'ry': p[1][1][1],
|
||||||
|
'x' : p[0][1][0]-p[1][1][0],
|
||||||
|
'y' : p[0][1][1]}
|
||||||
|
elif obj.get("r"):# For a pure circle object
|
||||||
|
r = obj.get("r")
|
||||||
|
cx = obj.get("cx")
|
||||||
|
cy = obj.get("cy")
|
||||||
|
data = {'rx' : float(r),
|
||||||
|
'ry' : float(r),
|
||||||
|
'x' : float(cx),
|
||||||
|
'y' : float(cy)}
|
||||||
|
elif obj.get("rx"):# For ellipses
|
||||||
|
rx = obj.get("rx")
|
||||||
|
ry = obj.get("ry")
|
||||||
|
cx = obj.get("cx")
|
||||||
|
cy = obj.get("cy")
|
||||||
|
data = {'rx' : float(rx),
|
||||||
|
'ry' : float(ry),
|
||||||
|
'x' : float(cx),
|
||||||
|
'y' : float(cy)}
|
||||||
|
else:
|
||||||
|
stockErrorMsg("4")
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
class CircleTangents(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("--position", default="inner", help="Choose either inner or outer tangent lines")
|
||||||
|
pars.add_argument("--selector", default="both", help="Choose which tangents you want to get")
|
||||||
|
pars.add_argument("--use_style_from_first", type=inkex.Boolean, default=False, help="Use style from first selected")
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
if len(self.options.ids) != 2:
|
||||||
|
inkex.errormsg("Please select exactly two circles and try again!")
|
||||||
|
return
|
||||||
|
|
||||||
|
c1object = self.svg.selected[self.options.ids[0]]
|
||||||
|
c2object = self.svg.selected[self.options.ids[1]]
|
||||||
|
|
||||||
|
if c1object.tag != inkex.addNS('circle','svg') or c2object.tag != inkex.addNS('circle','svg'):
|
||||||
|
self.msg("One or both objects are not svg:circle elements!")
|
||||||
|
return
|
||||||
|
|
||||||
|
c1 = getPathData(c1object)
|
||||||
|
c2 = getPathData(c2object)
|
||||||
|
|
||||||
|
# Create a third 'virtual' circle
|
||||||
|
if c1['rx'] <= c2['rx']:
|
||||||
|
c3x = c2['x']
|
||||||
|
c3y = c2['y']
|
||||||
|
if self.options.position == "outer":
|
||||||
|
c3r = c2['rx'] - c1['rx']
|
||||||
|
else:
|
||||||
|
c3r = c2['rx'] + c1['rx']
|
||||||
|
cyfA = [c1['x'], c1['y']]
|
||||||
|
cyfB = [c2['x'], c2['y']]
|
||||||
|
elif c1['rx'] > c2['rx']:
|
||||||
|
c3x = c1['x']
|
||||||
|
c3y = c1['y']
|
||||||
|
if self.options.position == "outer":
|
||||||
|
c3r = c1['rx'] - c2['rx']
|
||||||
|
else:
|
||||||
|
c3r = c1['rx'] + c2['rx']
|
||||||
|
cyfA = [c2['x'], c2['y']]
|
||||||
|
cyfB = [c1['x'], c1['y']]
|
||||||
|
|
||||||
|
# Test whether the circles are actually circles!
|
||||||
|
if c1['rx'] != c1['ry'] or c2['rx'] != c2['ry']:
|
||||||
|
inkex.errormsg("One or both objects may be elliptical. Ensure you have circles!")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Hypotenus of the triangle - Euclidean distance between c1 x, y and c2 x, y.
|
||||||
|
h = deuclid(c1['x'], c1['y'], c2['x'], c2['y'])
|
||||||
|
b = c3r
|
||||||
|
B = None
|
||||||
|
try:
|
||||||
|
B = getAngle(b, h)
|
||||||
|
except ValueError as e:
|
||||||
|
if self.options.position == "inner":
|
||||||
|
inkex.errormsg("Error calculating angle. Maybe your circles are overlapping each other")
|
||||||
|
else:
|
||||||
|
inkex.errormsg("Error calculating angle.")
|
||||||
|
return
|
||||||
|
a = aLength(b, h)
|
||||||
|
# Angle of hypotenuse to x-axis
|
||||||
|
E = getAngle(max(c1['y'], c2['y']) - min(c1['y'], c2['y']), h)
|
||||||
|
|
||||||
|
# To test if the smallest circle is lower than the other
|
||||||
|
if cyfB[1] <= cyfA[1]:
|
||||||
|
negx = False
|
||||||
|
else:
|
||||||
|
negx = True
|
||||||
|
|
||||||
|
# To test if it's the smallest circle to the right of the other
|
||||||
|
if cyfB[0] <= cyfA[0]:
|
||||||
|
negy = False
|
||||||
|
else:
|
||||||
|
negy = True
|
||||||
|
|
||||||
|
angleTop = -B+E
|
||||||
|
angleBottom = B+E
|
||||||
|
if self.options.position == "outer":# External
|
||||||
|
perpTop = -(pi/2)
|
||||||
|
perpBottom = pi/2
|
||||||
|
else:# Internal
|
||||||
|
perpTop = pi/2
|
||||||
|
perpBottom = -(pi/2)
|
||||||
|
|
||||||
|
# Top coordinates of the top line
|
||||||
|
cyfC = poltocar(a, angleTop, negx, negy)
|
||||||
|
# Information for converting top 90grade coordinates
|
||||||
|
conversionTop = poltocar(min(c1['rx'], c2['rx']), perpTop+angleTop, negx, negy)#1.5707964 1.57079632679
|
||||||
|
|
||||||
|
# Bottom line coordinates
|
||||||
|
cyfD = poltocar(a, angleBottom, negx, negy)
|
||||||
|
# Information for converting the bottom 90 degree coordinates
|
||||||
|
conversionBottom = poltocar(min(c1['rx'], c2['rx']), perpBottom+angleBottom, negx, negy)
|
||||||
|
|
||||||
|
# Draw a line
|
||||||
|
llx1 = cyfA[0]
|
||||||
|
lly1 = cyfA[1]
|
||||||
|
if self.options.use_style_from_first is True:
|
||||||
|
llsteil = (c1object.get("style")) #note: if the selected objects do not contain a stroke width the tangents will be invisible!
|
||||||
|
else:
|
||||||
|
llsteil = "stroke:#000000; stroke-width:1px; fill:none;"
|
||||||
|
|
||||||
|
# Line 1
|
||||||
|
if self.options.selector == "first" or self.options.selector == "both":
|
||||||
|
ll1x2 = cyfC[0]
|
||||||
|
ll1y2 = cyfC[1]
|
||||||
|
parent = c1object.getparent()
|
||||||
|
attribsLine1 = {'style':llsteil,
|
||||||
|
inkex.addNS('label','inkscape'):"line1",
|
||||||
|
'd':'m '+str(llx1+conversionTop[0])+','+str(lly1+conversionTop[1])+' l '+str(ll1x2)+','+str(ll1y2)}
|
||||||
|
elfen1 = etree.SubElement(parent, inkex.addNS('path','svg'), attribsLine1 )
|
||||||
|
|
||||||
|
#Line 2
|
||||||
|
if self.options.selector == "second" or self.options.selector == "both":
|
||||||
|
ll2x2 = cyfD[0]
|
||||||
|
ll2y2 = cyfD[1]
|
||||||
|
parent = c1object.getparent()
|
||||||
|
attribsLine1 = {'style':llsteil,
|
||||||
|
inkex.addNS('label','inkscape'):"line2",
|
||||||
|
'd':'m '+str(llx1+conversionBottom[0])+','+str(lly1+conversionBottom[1])+' l '+str(ll2x2)+','+str(ll2y2)}
|
||||||
|
etree.SubElement(parent, inkex.addNS('path','svg'), attribsLine1 )
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
CircleTangents().run()
|
21
extensions/fablabchemnitz/circle_tangents/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Circle Tangents (Replaced by Snap Objects Feature)",
|
||||||
|
"id": "fablabchemnitz.de.circle_tangents",
|
||||||
|
"path": "circle_tangents",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "Tangent",
|
||||||
|
"original_id": "org.inkscape.tangent",
|
||||||
|
"license": "GNU GPL v3",
|
||||||
|
"license_url": "https://github.com/Rhysun/inkTan/blob/master/tangent.py",
|
||||||
|
"comment": "ported to Inkscape v1 manually by Mario Voigt",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/circle_tangents",
|
||||||
|
"fork_url": "https://github.com/Rhysun/inkTan",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/pages/viewpage.action?pageId=74645771",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"github.com/Rhysun",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Clones In Perspective</name>
|
||||||
|
<id>fablabchemnitz.de.clones_in_perspective</id>
|
||||||
|
<param max="256" name="num" type="int" gui-text="How many?">5</param>
|
||||||
|
<param max="0.9999" precision="4" name="ratio" type="float" gui-text="relative size">0.8</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Shape/Pattern from existing Object(s)"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">clones_in_perspective.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
import math
|
||||||
|
import inkex
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
class clonesInPerspective(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument('--num', type = int, default = 5, help = 'Drag out center of rotation before calling')
|
||||||
|
pars.add_argument('--ratio', type = float, default = 0.9, help = 'Ratio of size of nearest neighbor to first. Must be < 1')
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
if len(self.svg.selected) != 1:
|
||||||
|
inkex.errormsg("Select exactly 1 thing. If necessary, group your items before.")
|
||||||
|
sys.exit(1)
|
||||||
|
xpath = list(self.svg.selected.items())[0][0]
|
||||||
|
sel = self.svg.selected[xpath]
|
||||||
|
id = sel.get('id')
|
||||||
|
|
||||||
|
try :
|
||||||
|
tx = sel.attrib[inkex.addNS('transform-center-x','inkscape')]
|
||||||
|
except KeyError:
|
||||||
|
tx = '0.'
|
||||||
|
try :
|
||||||
|
ty = sel.attrib[inkex.addNS('transform-center-y','inkscape')]
|
||||||
|
except KeyError:
|
||||||
|
ty = '0.'
|
||||||
|
|
||||||
|
if float(tx) == 0. and float(ty) == 0. :
|
||||||
|
inkex.errormsg("Center of rotation ist at the center of object. Please move the center first.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
bbox = sel.bounding_box()
|
||||||
|
width = bbox.height
|
||||||
|
height = bbox.width
|
||||||
|
cx = float(bbox.left) + 0.5 * width #Find center of selected object
|
||||||
|
cy = float(bbox.top) + 0.5 * height #Find center of selected object
|
||||||
|
tx = float(tx)
|
||||||
|
ty = float(ty)
|
||||||
|
crat = 1.0
|
||||||
|
otx = tx
|
||||||
|
oty = ty
|
||||||
|
|
||||||
|
parent = sel.getparent()
|
||||||
|
j = parent.index(sel)
|
||||||
|
for i in range(self.options.num) :
|
||||||
|
crat *= self.options.ratio
|
||||||
|
tx *= self.options.ratio
|
||||||
|
ty *= self.options.ratio
|
||||||
|
att = {
|
||||||
|
"id" : self.svg.get_unique_id("clone" + id),
|
||||||
|
inkex.addNS('href','xlink') : "#" + id,
|
||||||
|
inkex.addNS('transform-center-x','inkscape') : str(tx),
|
||||||
|
inkex.addNS('transform-center-y','inkscape') : str(ty),
|
||||||
|
'transform' : ("matrix(%f,0,0,%f,%f,%f)" % (crat, crat,(1. - crat)*(cx + otx), (1. - crat)*(cy - oty))),
|
||||||
|
"width" : "100%",
|
||||||
|
"height" : "100%",
|
||||||
|
}
|
||||||
|
parent.insert(j, etree.Element('use', att))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
clonesInPerspective().run()
|
21
extensions/fablabchemnitz/clones_in_perspective/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Clones In Perspective",
|
||||||
|
"id": "fablabchemnitz.de.clones_in_perspective",
|
||||||
|
"path": "clones_in_perspective",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "Clones in Perspective",
|
||||||
|
"original_id": "org.ekips.filter.clonesPerspective",
|
||||||
|
"license": "GNU GPL v2",
|
||||||
|
"license_url": "https://inkscape.org/de/~kurn/%E2%98%85clonesp",
|
||||||
|
"comment": "ported to Inkscape v1 manually by Mario Voigt",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/clones_in_perspective",
|
||||||
|
"fork_url": "https://inkscape.org/de/~kurn/%E2%98%85clonesp",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Clones+In+Perspective",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"inkscape.org/kurn",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
16
extensions/fablabchemnitz/close_paths/close_paths.inx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Close Paths</name>
|
||||||
|
<id>fablabchemnitz.de.close_paths</id>
|
||||||
|
<effect needs-live-preview="false">
|
||||||
|
<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">close_paths.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
43
extensions/fablabchemnitz/close_paths/close_paths.py
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Copyright (C) 2009 Nick Drobchenko, nick@cnc-club.ru
|
||||||
|
based on gcode.py (C) 2007 hugomatic...
|
||||||
|
based on addnodes.py (C) 2005,2007 Aaron Spike, aaron@ekips.org
|
||||||
|
based on dots.py (C) 2005 Aaron Spike, aaron@ekips.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 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
|
||||||
|
"""
|
||||||
|
|
||||||
|
#
|
||||||
|
# This extension close paths by adding 'z' before each 'm' if it is not the first 'm' and it is not prepended by 'z' already.
|
||||||
|
#
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
import re
|
||||||
|
from inkex.paths import Path
|
||||||
|
|
||||||
|
class ClosePaths(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
for id, node in self.svg.selected.items():
|
||||||
|
if node.tag == inkex.addNS('path','svg'):
|
||||||
|
d = node.get('d')
|
||||||
|
d = str(Path((Path(d).to_arrays())))
|
||||||
|
d = re.sub(r'(?i)(m[^mz]+)',r'\1 Z ',d)
|
||||||
|
d = re.sub(r'(?i)\s*z\s*z\s*',r' Z ',d)
|
||||||
|
node.set('d', d)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
ClosePaths().run()
|
21
extensions/fablabchemnitz/close_paths/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Close Paths",
|
||||||
|
"id": "fablabchemnitz.de.close_paths",
|
||||||
|
"path": "close_paths",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "Close curves",
|
||||||
|
"original_id": "ru.cnc-club.filter.close_curves",
|
||||||
|
"license": "GNU GPL v2",
|
||||||
|
"license_url": "https://www.cnc-club.ru/forum/download/file.php?id=94&sid=63473c1c91035bea64a8986cd1ab292c",
|
||||||
|
"comment": "ported to Inkscape v1 by Mario Voigt; See https://www.cnc-club.ru/forum/viewtopic.php?f=15&t=37&p=92",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/close_paths",
|
||||||
|
"fork_url": "https://www.cnc-club.ru/forum/download/file.php?id=94&sid=63473c1c91035bea64a8986cd1ab292c",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Close+Paths",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"Nick Drobchenko:nick@cnc-club.ru",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Convert To Polylines</name>
|
||||||
|
<id>fablabchemnitz.de.convert_to_polylines</id>
|
||||||
|
<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">convert_to_polylines.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""
|
||||||
|
Extension for InkScape 1.0
|
||||||
|
|
||||||
|
Converts curves to polylines - a quick and dirty helper for a lot of elements. Basically the same functionality can be done with default UI featureset but with a lot more mouse clicks
|
||||||
|
|
||||||
|
Author: Mario Voigt / FabLab Chemnitz
|
||||||
|
Mail: mario.voigt@stadtfabrikanten.org
|
||||||
|
Date: 05.09.2020
|
||||||
|
Last patch: 05.09.2020
|
||||||
|
License: GNU GPL v3
|
||||||
|
"""
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
from inkex.paths import Path
|
||||||
|
|
||||||
|
class ConvertToPolylines(inkex.EffectExtension):
|
||||||
|
|
||||||
|
#convert a path (curve) to a polyline and remove dangling/duplicate/useless overlapping handles (points)
|
||||||
|
def convertPath(self, node):
|
||||||
|
if node.tag == inkex.addNS('path','svg'):
|
||||||
|
polypath = []
|
||||||
|
i = 0
|
||||||
|
for x, y in node.path.end_points:
|
||||||
|
if i == 0:
|
||||||
|
polypath.append(['M', [x,y]])
|
||||||
|
else:
|
||||||
|
polypath.append(['L', [x,y]])
|
||||||
|
if i == 1 and polypath[len(polypath)-2][1] == polypath[len(polypath)-1][1]:
|
||||||
|
polypath.pop(len(polypath)-1) #special handling for the seconds point after M command
|
||||||
|
elif polypath[len(polypath)-2] == polypath[len(polypath)-1]: #get the previous point
|
||||||
|
polypath.pop(len(polypath)-1)
|
||||||
|
i += 1
|
||||||
|
node.set('d', str(Path(polypath)))
|
||||||
|
children = node.getchildren()
|
||||||
|
if children is not None:
|
||||||
|
for child in children:
|
||||||
|
self.convertPath(child)
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
if len(self.svg.selected) == 0:
|
||||||
|
self.convertPath(self.document.getroot())
|
||||||
|
else:
|
||||||
|
for id, item in self.svg.selected.items():
|
||||||
|
self.convertPath(item)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
ConvertToPolylines().run()
|
20
extensions/fablabchemnitz/convert_to_polylines/meta.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Convert To Polylines",
|
||||||
|
"id": "fablabchemnitz.de.convert_to_polylines",
|
||||||
|
"path": "convert_to_polylines",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "Convert To Polylines",
|
||||||
|
"original_id": "fablabchemnitz.de.convert_to_polylines",
|
||||||
|
"license": "GNU GPL v3",
|
||||||
|
"license_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/LICENSE",
|
||||||
|
"comment": "Written by Mario Voigt",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/convert_to_polylines",
|
||||||
|
"fork_url": null,
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Convert+To+Polylines",
|
||||||
|
"inkscape_gallery_url": "https://inkscape.org/~MarioVoigt/%E2%98%85convert-to-polylines",
|
||||||
|
"main_authors": [
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
16
extensions/fablabchemnitz/convex_hull/convex_hull.inx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Convex Hull</name>
|
||||||
|
<id>fablabchemnitz.de.convex_hull</id>
|
||||||
|
<effect needs-live-preview="false">
|
||||||
|
<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 location="inx" interpreter="python">convex_hull.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
140
extensions/fablabchemnitz/convex_hull/convex_hull.py
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
import sys
|
||||||
|
import numpy as n
|
||||||
|
from inkex import PathElement
|
||||||
|
from inkex.paths import CubicSuperPath, Path
|
||||||
|
from inkex.transforms import Transform
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
link = lambda a,b: n.concatenate((a,b[1:]))
|
||||||
|
edge = lambda a,b: n.concatenate(([a],[b]))
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
Convex Hull routine by Markus Kohler, found at https://gist.github.com/kohlerm/0337f7c64df42f21f96405b3f8e895f2, which itself is an implementation of:
|
||||||
|
https://github.com/flengyel/REST/blob/master/quickhull.py
|
||||||
|
Compute the convex hull of a set of points in the plane
|
||||||
|
Adapted from en.literateprograms.org/Quickhull_(Python,arrays)
|
||||||
|
Content available under MIT/X11 License at
|
||||||
|
http://en.literateprograms.org/LiteratePrograms:Copyrights
|
||||||
|
Copyright (c) 2008 the authors listed in the page history
|
||||||
|
(see http://en.literateprograms.org/index.php?title=Quickhull_(Python,_arrays)&action=history)
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
IN THE SOFTWARE.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def qhull(sample):
|
||||||
|
def dome(sample,base):
|
||||||
|
sys.setrecursionlimit(20000) #dangerous stuff
|
||||||
|
h, t = base
|
||||||
|
dists = n.dot(sample-h, n.dot(((0,-1),(1,0)),(t-h)))
|
||||||
|
outer = n.repeat(sample, dists>0, 0)
|
||||||
|
|
||||||
|
if len(outer):
|
||||||
|
pivot = sample[n.argmax(dists)]
|
||||||
|
return link(dome(outer, edge(h, pivot)),
|
||||||
|
dome(outer, edge(pivot, t)))
|
||||||
|
else:
|
||||||
|
return base
|
||||||
|
if len(sample) > 2:
|
||||||
|
axis = sample[:,0]
|
||||||
|
base = n.take(sample, [n.argmin(axis), n.argmax(axis)], 0)
|
||||||
|
return link(dome(sample, base),
|
||||||
|
dome(sample, base[::-1]))
|
||||||
|
else:
|
||||||
|
return sample
|
||||||
|
|
||||||
|
class ConvexHull(inkex.EffectExtension):
|
||||||
|
def __init__(self):
|
||||||
|
inkex.Effect.__init__(self)
|
||||||
|
self.paths = {}
|
||||||
|
self.paths_clone_transform = {}
|
||||||
|
|
||||||
|
def joinWithElement (self, element, path, makeGroup=False, cloneTransform=None):
|
||||||
|
if (not path) or (len(path) == 0 ):
|
||||||
|
return
|
||||||
|
g = self.document.getroot()
|
||||||
|
# Now make a <path> element which contains the twist & is a child
|
||||||
|
# of the new <g> element
|
||||||
|
style = { 'stroke': '#000000', 'fill': 'none', 'stroke-width': str(self.svg.unittouu("1px")) }
|
||||||
|
line_attribs = { 'style':str(inkex.Style(style)), 'd': path }
|
||||||
|
if (cloneTransform != None ) and (cloneTransform != '' ):
|
||||||
|
line_attribs['transform'] = cloneTransform
|
||||||
|
etree.SubElement(g, inkex.addNS('path', 'svg' ), line_attribs)
|
||||||
|
|
||||||
|
def getControlPoints(self, element, n_array = None):
|
||||||
|
if n_array == None:
|
||||||
|
n_array = []
|
||||||
|
if element.tag == inkex.addNS('path','svg'):
|
||||||
|
#element.apply_transform()
|
||||||
|
|
||||||
|
parent = element.getparent()
|
||||||
|
if parent is not None:
|
||||||
|
csp = Path(element.path.transform(parent.composed_transform())).to_arrays()
|
||||||
|
else:
|
||||||
|
csp = element.path.to_arrays()
|
||||||
|
p = CubicSuperPath(csp)
|
||||||
|
|
||||||
|
for subpath in p: # there may be several paths joined together (e.g. holes)
|
||||||
|
for csp in subpath: # groups of three to handle control points.
|
||||||
|
# just the points no control points (handles)
|
||||||
|
n_array.append(csp[1][0])
|
||||||
|
n_array.append(csp[1][1])
|
||||||
|
for child in element.getchildren():
|
||||||
|
n_array += self.getControlPoints(child, n_array)
|
||||||
|
return n_array
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
|
||||||
|
if len(self.svg.selected) > 0:
|
||||||
|
global output_elements, points
|
||||||
|
|
||||||
|
n_array = []
|
||||||
|
elements = self.svg.selection.filter(PathElement).values()
|
||||||
|
if len(elements) > 0:
|
||||||
|
for element in elements:
|
||||||
|
if element.tag == inkex.addNS('path','svg'):
|
||||||
|
n_array += self.getControlPoints(element, None)
|
||||||
|
|
||||||
|
k = n.asarray(n_array)
|
||||||
|
|
||||||
|
length = int(len(k)/2)
|
||||||
|
c = k.reshape(length,2)
|
||||||
|
|
||||||
|
hull_pts = qhull(c)
|
||||||
|
|
||||||
|
pdata = ''
|
||||||
|
for vertex in hull_pts:
|
||||||
|
if pdata == '':
|
||||||
|
pdata = 'M%f,%f' % (vertex[0], vertex[1] )
|
||||||
|
else:
|
||||||
|
pdata += 'L %f,%f' % (vertex[0], vertex[1] )
|
||||||
|
pdata += ' Z'
|
||||||
|
path = 'polygon'
|
||||||
|
makeGroup = False
|
||||||
|
paths_clone_transform = None
|
||||||
|
self.joinWithElement(path, pdata, makeGroup, paths_clone_transform)
|
||||||
|
else:
|
||||||
|
inkex.errormsg('Selection seems not to contain svg:path elements. Maybe you have selected a group instead?')
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
inkex.errormsg('Please select some paths first.')
|
||||||
|
return
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
ConvexHull().run()
|
22
extensions/fablabchemnitz/convex_hull/meta.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Convex Hull",
|
||||||
|
"id": "fablabchemnitz.de.convex_hull",
|
||||||
|
"path": "convex_hull",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "ConvexHull",
|
||||||
|
"original_id": "org.simarilius.filter.ConvexHull",
|
||||||
|
"license": "MIT License",
|
||||||
|
"license_url": " https://github.com/flengyel/REST/blob/master/quickhull.py",
|
||||||
|
"comment": "",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/convex_hull",
|
||||||
|
"fork_url": "https://gist.github.com/kohlerm/0337f7c64df42f21f96405b3f8e895f2",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Convex+Hull",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"github.com/flengyel",
|
||||||
|
"github.com/kohlerm",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
63
extensions/fablabchemnitz/create_hexmap/create_hexmap.inx
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Create Hexmap</name>
|
||||||
|
<id>fablabchemnitz.de.create_hexmap</id>
|
||||||
|
<param name="tab" type="notebook">
|
||||||
|
<page name="page_1" gui-text="Size">
|
||||||
|
<param name="units" type="optiongroup" appearance="combo" gui-text="Size units">
|
||||||
|
<option value="mm">mm</option>
|
||||||
|
<option value="cm">cm</option>
|
||||||
|
<option value="in">in</option>
|
||||||
|
<option value="pt">pt</option>
|
||||||
|
<option value="px">px</option>
|
||||||
|
</param>
|
||||||
|
<param name="cols" type="int" gui-text="Columns" min="1" max="1000000">10</param>
|
||||||
|
<param name="rows" type="int" gui-text="Rows" min="1" max="1000000">10</param>
|
||||||
|
<param name="hexsize" type="float" min="0" max="9999" gui-text="Hex Size (optional)">0</param>
|
||||||
|
<param name="strokewidth" type="float" min="0.0" max="9999.0" gui-text="Stroke Width">1.0</param>
|
||||||
|
<param name="verticesize" type="float" min="0.0" max="50.0" gui-text="Size of vertices (%)">10.0</param>
|
||||||
|
</page>
|
||||||
|
<page name="page_2" gui-text="Style">
|
||||||
|
<param name="bricks" type="bool" gui-text="Bricks">false</param>
|
||||||
|
<param name="squarebricks" type="bool" gui-text="Force Square Bricks">false</param>
|
||||||
|
<param name="rotate" type="bool" gui-text="Rotate">false</param>
|
||||||
|
<param name="halfhexes" type="bool" gui-text="Half hexes at top and bottom">false</param>
|
||||||
|
<param name="xshift" type="bool" gui-text="Shift grid to side and wrap">false</param>
|
||||||
|
<param name="firstcoldown" type="bool" gui-text="First column half-hex down">false</param>
|
||||||
|
</page>
|
||||||
|
<page name="page_3" gui-text="Coords">
|
||||||
|
<param name="coordseparator" type="string" gui-text="Coordinate Separator">.</param>
|
||||||
|
<param name="coordalphacol" type="bool" gui-text="Column Alpha Coordinates">false</param>
|
||||||
|
<param name="coordrevrow" type="bool" gui-text="Row Coordinates Reversed">false</param>
|
||||||
|
<param name="coordrowfirst" type="bool" gui-text="Row Coordinate First">false</param>
|
||||||
|
<param name="coordzeros" type="bool" gui-text="Zero-Padded Coordinates">true</param>
|
||||||
|
<param name="coordrows" type="int" min="1" max="100" gui-text="Coordinates Every ... Rows">1</param>
|
||||||
|
<param name="coordcolstart" type="int" gui-text="First Col Nr" min="0" max="1000">1</param>
|
||||||
|
<param name="coordrowstart" type="int" gui-text="First Row Nr" min="0" max="1000">1</param>
|
||||||
|
</page>
|
||||||
|
<page name="page_4" gui-text="Layers">
|
||||||
|
<param name="layer_grid" type="bool" gui-text="Grid">true</param>
|
||||||
|
<param name="layer_fill" type="bool" gui-text="Fill (#ffffff)">true</param>
|
||||||
|
<param name="layer_coordinates" type="bool" gui-text="Coordinates">true</param>
|
||||||
|
<param name="layer_centerdots" type="bool" gui-text="Centerdots">true</param>
|
||||||
|
<param name="layer_vertices" type="bool" gui-text="Vertices">false</param>
|
||||||
|
<param name="layer_circles" type="bool" gui-text="Circles">false</param>
|
||||||
|
<param name="layersingroup" type="bool" gui-text="Layers in Group">false</param>
|
||||||
|
</page>
|
||||||
|
<page name="page_5" gui-text="Debug">
|
||||||
|
<param name="generatelog" type="bool" gui-text="Generate log file">false</param>
|
||||||
|
<param name="logfilepath" type="path" gui-text="Log File (optional)" mode="file_new" filetypes="txt">debug.txt</param>
|
||||||
|
</page>
|
||||||
|
</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Grids/Guides"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">create_hexmap.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
420
extensions/fablabchemnitz/create_hexmap/create_hexmap.py
Normal file
@ -0,0 +1,420 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
import sys
|
||||||
|
from inkex import NSS
|
||||||
|
import math
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
class Point:
|
||||||
|
def __init__(self, x, y):
|
||||||
|
self.x = x
|
||||||
|
self.y = y
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '%f,%f' % (self.x, self.y)
|
||||||
|
|
||||||
|
def y_mirror(self, h):
|
||||||
|
return Point(self.x, h - self.y);
|
||||||
|
|
||||||
|
def __sub__(self, other):
|
||||||
|
return Point(self.x - other.x, self.y - other.y)
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
return Point(self.x + other.x, self.y + other.y)
|
||||||
|
|
||||||
|
def __mul__(self, k):
|
||||||
|
return Point(self.x * k, self.y * k)
|
||||||
|
|
||||||
|
def rotated(self, total_width):
|
||||||
|
return Point(self.y, total_width - self.x)
|
||||||
|
|
||||||
|
def nrdigits(f):
|
||||||
|
return int(math.floor(math.log10(f))) + 1
|
||||||
|
|
||||||
|
def alphacol(c):
|
||||||
|
d = c / 26
|
||||||
|
r = c % 26
|
||||||
|
return ('%c' % (r + 65)) * (int(d) + 1)
|
||||||
|
|
||||||
|
def calc_hex_height(hex_width):
|
||||||
|
return 0.25 * hex_width / math.tan(math.pi / 6) * 2
|
||||||
|
|
||||||
|
COORD_SIZE_PART_OF_HEX_HEIGHT = 0.1
|
||||||
|
COORD_YOFFSET_PART = 75
|
||||||
|
CENTERDOT_SIZE_FACTOR = 1.1690625
|
||||||
|
|
||||||
|
class CreateHexmap(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument('--tab')
|
||||||
|
pars.add_argument('--generatelog', type = inkex.Boolean, default = False)
|
||||||
|
pars.add_argument('--logfilepath', default = "debug.txt")
|
||||||
|
pars.add_argument("--units", default='mm', help="Units this dialog is using")
|
||||||
|
pars.add_argument('--cols', type = int, default = '10', help = 'Number of columns.')
|
||||||
|
pars.add_argument('--rows', type = int, default = '10', help = 'Number of columns.')
|
||||||
|
pars.add_argument('--hexsize', type = float, default = 0.0)
|
||||||
|
pars.add_argument('--strokewidth', type = float, default = 1.0)
|
||||||
|
pars.add_argument('--coordrows', type = int, default = '1')
|
||||||
|
pars.add_argument('--coordcolstart', type = int, default = '1')
|
||||||
|
pars.add_argument('--coordrowstart', type = int, default = '1')
|
||||||
|
pars.add_argument('--bricks', type = inkex.Boolean, default = False)
|
||||||
|
pars.add_argument('--squarebricks', type = inkex.Boolean, default = False)
|
||||||
|
pars.add_argument('--rotate', type = inkex.Boolean, default = False)
|
||||||
|
pars.add_argument('--coordseparator', default = '')
|
||||||
|
pars.add_argument('--layersingroup', type = inkex.Boolean, default = False, help = 'Put all layers in a layer group.')
|
||||||
|
pars.add_argument('--coordalphacol', type = inkex.Boolean, default = False, help = 'Reverse row coordinates.')
|
||||||
|
pars.add_argument('--coordrevrow', type = inkex.Boolean, default = False, help = 'Reverse row coordinates.')
|
||||||
|
pars.add_argument('--coordzeros', type = inkex.Boolean, default = True)
|
||||||
|
pars.add_argument('--coordrowfirst', type = inkex.Boolean, default = False, help = 'Reverse row coordinates.')
|
||||||
|
pars.add_argument('--xshift', type = inkex.Boolean, default = False, help = 'Shift grid half hex and wrap.')
|
||||||
|
pars.add_argument('--firstcoldown', type = inkex.Boolean, default = False, help = 'Make first column half-hex down.')
|
||||||
|
pars.add_argument('--halfhexes', type = inkex.Boolean, default = False)
|
||||||
|
pars.add_argument('--verticesize', type = float,default = 1.0)
|
||||||
|
pars.add_argument('--layer_grid', type = inkex.Boolean, default = True)
|
||||||
|
pars.add_argument('--layer_fill', type = inkex.Boolean, default = True)
|
||||||
|
pars.add_argument('--layer_coordinates', type = inkex.Boolean, default = True)
|
||||||
|
pars.add_argument('--layer_centerdots', type = inkex.Boolean, default = True)
|
||||||
|
pars.add_argument('--layer_vertices', type = inkex.Boolean, default = False)
|
||||||
|
pars.add_argument('--layer_circles', type = inkex.Boolean, default = False)
|
||||||
|
|
||||||
|
def createLayer(self, name):
|
||||||
|
layer = etree.Element(inkex.addNS('g', 'svg'))
|
||||||
|
layer.set(inkex.addNS('label', 'inkscape'), name)
|
||||||
|
layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
|
||||||
|
return layer
|
||||||
|
|
||||||
|
def logwrite(self, msg):
|
||||||
|
if self.options.generatelog:
|
||||||
|
log = open(self.options.logfilepath, 'w')
|
||||||
|
log.write(msg)
|
||||||
|
log.close()
|
||||||
|
|
||||||
|
def svg_line(self, p1, p2):
|
||||||
|
line = etree.Element('line')
|
||||||
|
line.set('x1', str(p1.x + self.xoffset))
|
||||||
|
line.set('y1', str(p1.y + self.yoffset))
|
||||||
|
line.set('x2', str(p2.x + self.xoffset))
|
||||||
|
line.set('y2', str(p2.y + self.yoffset))
|
||||||
|
line.set('style', 'stroke:#000000; stroke-width:' + str(self.stroke_width) + ';stroke-linecap:round')
|
||||||
|
return line
|
||||||
|
|
||||||
|
def svg_circle(self, p, radius):
|
||||||
|
circle = etree.Element('circle')
|
||||||
|
circle.set('cx', str(p.x + self.xoffset))
|
||||||
|
circle.set('cy', str(p.y + self.yoffset))
|
||||||
|
circle.set('r', str(radius))
|
||||||
|
circle.set('fill', 'black')
|
||||||
|
return circle
|
||||||
|
|
||||||
|
def svg_polygon(self, points):
|
||||||
|
poly = etree.Element('polygon')
|
||||||
|
pointsdefa = []
|
||||||
|
for p in points:
|
||||||
|
offset_p = Point(p.x + self.xoffset, p.y + self.yoffset)
|
||||||
|
pointsdefa.append(str(offset_p))
|
||||||
|
pointsdef = ' '.join(pointsdefa)
|
||||||
|
poly.set('points', pointsdef)
|
||||||
|
poly.set('style', 'stroke:none;fill:#ffffff;fill-opacity:1;stroke-width:' + str(self.stroke_width) + ';stroke-linecap:round')
|
||||||
|
return poly
|
||||||
|
|
||||||
|
def svg_coord(self, p, col, row, cols, rows, anchor='middle'):
|
||||||
|
if self.coordrevrow:
|
||||||
|
row = rows - row
|
||||||
|
else:
|
||||||
|
row = row + 1
|
||||||
|
if self.coordrevcol:
|
||||||
|
col = cols - col
|
||||||
|
else:
|
||||||
|
col = col + 1
|
||||||
|
row = row + self.options.coordrowstart - 1
|
||||||
|
col = col + self.options.coordcolstart - 1
|
||||||
|
if ((row != 1 and row % self.coordrows != 0)
|
||||||
|
or row < 1 or col < 1):
|
||||||
|
return None
|
||||||
|
|
||||||
|
if self.coordrowfirst:
|
||||||
|
col,row = [row,col]
|
||||||
|
|
||||||
|
if self.coordalphacol:
|
||||||
|
acol = alphacol(col - 1)
|
||||||
|
if self.coordzeros:
|
||||||
|
zrow = str(row).zfill(self.rowdigits)
|
||||||
|
coord = acol + self.coordseparator + zrow
|
||||||
|
else:
|
||||||
|
coord = acol + self.coordseparator + str(row)
|
||||||
|
elif self.coordzeros:
|
||||||
|
zcol = str(col).zfill(self.coldigits)
|
||||||
|
zrow = str(row).zfill(self.rowdigits)
|
||||||
|
coord = zcol + self.coordseparator + zrow
|
||||||
|
else:
|
||||||
|
coord = str(col) + self.coordseparator + str(row)
|
||||||
|
|
||||||
|
self.logwrite(" coord-> '%s'\n" % (coord))
|
||||||
|
text = etree.Element('text')
|
||||||
|
text.set('x', str(p.x + self.xoffset))
|
||||||
|
text.set('y', str(p.y + self.yoffset))
|
||||||
|
style = ('text-align:center;text-anchor:%s;font-size:%fpt'
|
||||||
|
% (anchor, self.coordsize))
|
||||||
|
text.set('style', style)
|
||||||
|
text.text = coord
|
||||||
|
return text
|
||||||
|
|
||||||
|
def add_hexline(self, gridlayer, verticelayer, p1, p2):
|
||||||
|
if gridlayer is not None:
|
||||||
|
gridlayer.append(self.svg_line(p1, p2))
|
||||||
|
if verticelayer is not None:
|
||||||
|
verticelayer.append(self.svg_line(p1, (p2 - p1)
|
||||||
|
* self.verticesize + p1))
|
||||||
|
verticelayer.append(self.svg_line(p2, p2 - (p2 - p1)
|
||||||
|
* self.verticesize))
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
strokewidth = self.options.strokewidth
|
||||||
|
cols = self.options.cols
|
||||||
|
rows = self.options.rows
|
||||||
|
halves = self.options.halfhexes
|
||||||
|
xshift = self.options.xshift
|
||||||
|
firstcoldown = self.options.firstcoldown
|
||||||
|
bricks = self.options.bricks
|
||||||
|
squarebricks = self.options.squarebricks
|
||||||
|
rotate = self.options.rotate
|
||||||
|
layersingroup = self.options.layersingroup
|
||||||
|
|
||||||
|
self.coordseparator = self.options.coordseparator
|
||||||
|
if self.coordseparator == None:
|
||||||
|
self.coordseparator = ''
|
||||||
|
self.coordrevrow = self.options.coordrevrow
|
||||||
|
self.coordrevcol = False
|
||||||
|
self.coordalphacol = self.options.coordalphacol
|
||||||
|
self.coordrows = self.options.coordrows
|
||||||
|
self.coordrowfirst = self.options.coordrowfirst
|
||||||
|
self.coordzeros = self.options.coordzeros
|
||||||
|
|
||||||
|
if rotate:
|
||||||
|
self.coordrowfirst = not self.coordrowfirst
|
||||||
|
self.coordrevcol = not self.coordrevrow
|
||||||
|
self.coordrevrow = False
|
||||||
|
|
||||||
|
self.verticesize = self.options.verticesize / 100.0
|
||||||
|
self.logwrite('verticesize: %f\n' % self.verticesize)
|
||||||
|
if self.verticesize < 0.01 or self.verticesize > 0.5:
|
||||||
|
self.logwrite('verticesize out of range\n')
|
||||||
|
self.verticesize = 0.15
|
||||||
|
|
||||||
|
self.coldigits = nrdigits(cols + self.options.coordcolstart)
|
||||||
|
self.rowdigits = nrdigits(rows + self.options.coordrowstart)
|
||||||
|
if self.coldigits < 2:
|
||||||
|
self.coldigits = 2
|
||||||
|
if self.rowdigits < 2:
|
||||||
|
self.rowdigits = 2
|
||||||
|
if self.coordrowfirst:
|
||||||
|
self.coldigits,self.rowdigits = [self.rowdigits,self.coldigits]
|
||||||
|
|
||||||
|
self.logwrite('cols: %d, rows: %d\n' % (cols, rows))
|
||||||
|
self.logwrite('xshift: %s, halves: %s\n' % (str(xshift), str(halves)))
|
||||||
|
|
||||||
|
svg = self.document.xpath('//svg:svg' , namespaces=NSS)[0]
|
||||||
|
|
||||||
|
self.stroke_width = self.svg.unittouu(str(self.options.strokewidth) + self.options.units)
|
||||||
|
|
||||||
|
width = float(self.svg.unittouu(svg.get('width'))) - self.stroke_width
|
||||||
|
height = float(self.svg.unittouu(svg.get('height'))) - self.stroke_width
|
||||||
|
|
||||||
|
# So I was a bit lazy and only added an offset to all the
|
||||||
|
# svg_* functions to compensate for the stroke width.
|
||||||
|
# There should be a better way.
|
||||||
|
self.xoffset = self.stroke_width * 0.5
|
||||||
|
self.yoffset = self.stroke_width * 0.5
|
||||||
|
|
||||||
|
if self.options.layer_grid:
|
||||||
|
hexgrid = self.createLayer('Hex Grid')
|
||||||
|
else:
|
||||||
|
hexgrid = None
|
||||||
|
if self.options.layer_fill:
|
||||||
|
hexfill = self.createLayer('Hex Fill')
|
||||||
|
else:
|
||||||
|
hexfill = None
|
||||||
|
if self.options.layer_coordinates:
|
||||||
|
hexcoords = self.createLayer('Hex Coordinates')
|
||||||
|
else:
|
||||||
|
hexcoords = None
|
||||||
|
if self.options.layer_centerdots:
|
||||||
|
hexdots = self.createLayer('Hex Centerdots')
|
||||||
|
else:
|
||||||
|
hexdots = None
|
||||||
|
if self.options.layer_vertices:
|
||||||
|
hexvertices = self.createLayer('Hex Vertices')
|
||||||
|
else:
|
||||||
|
hexvertices = None
|
||||||
|
if self.options.layer_circles:
|
||||||
|
hexcircles = self.createLayer('Hex Circles')
|
||||||
|
else:
|
||||||
|
hexcircles = None
|
||||||
|
if hexvertices is not None and hexgrid is not None:
|
||||||
|
hexgrid.set('style', 'display:none')
|
||||||
|
|
||||||
|
self.logwrite('w, h : %f, %f\n' % (width, height))
|
||||||
|
|
||||||
|
if xshift:
|
||||||
|
hex_cols = (cols * 3.0) * 0.25
|
||||||
|
else:
|
||||||
|
hex_cols = (cols * 3.0 + 1.0) * 0.25
|
||||||
|
|
||||||
|
if halves:
|
||||||
|
hex_rows = rows
|
||||||
|
else:
|
||||||
|
hex_rows = rows + 0.5
|
||||||
|
|
||||||
|
hex_width = width / hex_cols
|
||||||
|
|
||||||
|
if self.options.hexsize > 0:
|
||||||
|
hex_width = self.svg.unittouu(str(self.options.hexsize) + self.options.units)
|
||||||
|
hex_height = calc_hex_height(hex_width)
|
||||||
|
|
||||||
|
# square bricks workaround
|
||||||
|
if bricks and squarebricks:
|
||||||
|
hex_height = hex_width
|
||||||
|
hex_width = hex_width / 0.75
|
||||||
|
|
||||||
|
hexes_height = hex_height * hex_rows
|
||||||
|
hexes_width = hex_width * 0.75 * cols + hex_width * 0.25
|
||||||
|
|
||||||
|
self.coordsize = hex_height * COORD_SIZE_PART_OF_HEX_HEIGHT
|
||||||
|
if self.coordsize > 1.0:
|
||||||
|
self.coordsize = round(self.coordsize)
|
||||||
|
self.centerdotsize = self.stroke_width * CENTERDOT_SIZE_FACTOR
|
||||||
|
self.circlesize = hex_height / 2
|
||||||
|
|
||||||
|
self.logwrite('hex_width: %f, hex_height: %f\n' %(hex_width,
|
||||||
|
hex_height))
|
||||||
|
|
||||||
|
# FIXME try to remember what 0.005 is for
|
||||||
|
coord_yoffset = COORD_YOFFSET_PART * hex_height * 0.005
|
||||||
|
|
||||||
|
for col in range(cols + 1):
|
||||||
|
cx = (2.0 + col * 3.0) * 0.25 * hex_width
|
||||||
|
if xshift:
|
||||||
|
cx = cx - hex_width * 0.5
|
||||||
|
coldown = col % 2
|
||||||
|
if firstcoldown:
|
||||||
|
coldown = not coldown
|
||||||
|
for row in range(rows + 1):
|
||||||
|
cy = (0.5 + coldown * 0.5 + row) * hex_height
|
||||||
|
self.logwrite('col: %d, row: %d, c: %f %f\n' % (col, row,
|
||||||
|
cx, cy))
|
||||||
|
c = Point(cx, cy)
|
||||||
|
if rotate:
|
||||||
|
c = c.rotated(hexes_width)
|
||||||
|
if (hexcoords is not None
|
||||||
|
and (col < cols or xshift) and row < rows):
|
||||||
|
cc = c + Point(0, coord_yoffset)
|
||||||
|
anchor = 'middle'
|
||||||
|
if xshift and col == 0:
|
||||||
|
anchor = 'start'
|
||||||
|
elif xshift and col == cols:
|
||||||
|
anchor = 'end'
|
||||||
|
coord = self.svg_coord(cc, col, row, cols, rows, anchor)
|
||||||
|
if coord != None:
|
||||||
|
hexcoords.append(coord)
|
||||||
|
if (hexdots is not None
|
||||||
|
and (col < cols or xshift) and row < rows):
|
||||||
|
cd = self.svg_circle(c, self.centerdotsize)
|
||||||
|
cd.set('id', 'hexcenter_%d_%d'
|
||||||
|
% (col + self.options.coordcolstart,
|
||||||
|
row + self.options.coordrowstart))
|
||||||
|
hexdots.append(cd)
|
||||||
|
#FIXME make half-circles in half hexes
|
||||||
|
if (hexcircles is not None and (col < cols or xshift)
|
||||||
|
and row < rows):
|
||||||
|
el = self.svg_circle(c, self.circlesize)
|
||||||
|
el.set('id', 'hexcircle_%d_%d'
|
||||||
|
% (col + self.options.coordcolstart,
|
||||||
|
row + self.options.coordrowstart))
|
||||||
|
hexcircles.append(el)
|
||||||
|
x = [cx - hex_width * 0.5,
|
||||||
|
cx - hex_width * 0.25,
|
||||||
|
cx + hex_width * 0.25,
|
||||||
|
cx + hex_width * 0.5]
|
||||||
|
y = [cy - hex_height * 0.5,
|
||||||
|
cy,
|
||||||
|
cy + hex_height * 0.5]
|
||||||
|
if bricks and xshift:
|
||||||
|
sys.exit('No support for bricks with x shift.')
|
||||||
|
if xshift and col == 0:
|
||||||
|
x[0] = cx
|
||||||
|
x[1] = cx
|
||||||
|
elif xshift and col == cols:
|
||||||
|
x[2] = cx
|
||||||
|
x[3] = cx
|
||||||
|
if halves and coldown and row == rows-1:
|
||||||
|
y[2] = cy
|
||||||
|
# with bricks pattern, shift some coordinates a bit
|
||||||
|
# to make correct shape
|
||||||
|
if bricks:
|
||||||
|
brick_adjust = hex_width * 0.125
|
||||||
|
else:
|
||||||
|
brick_adjust = 0
|
||||||
|
p = [Point(x[2] + brick_adjust, y[0]),
|
||||||
|
Point(x[3] - brick_adjust, y[1]),
|
||||||
|
Point(x[2] + brick_adjust, y[2]),
|
||||||
|
Point(x[1] - brick_adjust, y[2]),
|
||||||
|
Point(x[0] + brick_adjust, y[1]),
|
||||||
|
Point(x[1] - brick_adjust, y[0])]
|
||||||
|
if rotate:
|
||||||
|
p = [point.rotated(hexes_width) for point in p]
|
||||||
|
if (hexfill is not None
|
||||||
|
and (col < cols or xshift) and row < rows):
|
||||||
|
if row < rows or (halves and coldown):
|
||||||
|
sp = self.svg_polygon(p)
|
||||||
|
if halves and coldown and row == rows - 1:
|
||||||
|
p2 = [x.y_mirror(hexes_height) for x in p]
|
||||||
|
sp = self.svg_polygon(p)
|
||||||
|
sp.set('id', 'hexfill_%d_%d'
|
||||||
|
% (col + self.options.coordcolstart,
|
||||||
|
row + self.options.coordrowstart))
|
||||||
|
hexfill.append(sp)
|
||||||
|
if ((col < cols and (not halves or row < rows
|
||||||
|
or not coldown))
|
||||||
|
or (xshift and col == cols
|
||||||
|
and not (halves and row == rows))):
|
||||||
|
self.add_hexline(hexgrid, hexvertices, p[5], p[0])
|
||||||
|
self.logwrite('line 0-5\n')
|
||||||
|
if row < rows:
|
||||||
|
if ((coldown or row > 0 or col < cols
|
||||||
|
or halves or xshift)
|
||||||
|
and not (xshift and col == 0)):
|
||||||
|
self.add_hexline(hexgrid, hexvertices, p[5], p[4])
|
||||||
|
self.logwrite('line 4-5\n')
|
||||||
|
if not coldown and row == 0 and col < cols:
|
||||||
|
self.add_hexline(hexgrid, hexvertices, p[0], p[1])
|
||||||
|
self.logwrite('line 0-1\n')
|
||||||
|
if not (halves and coldown and row == rows-1):
|
||||||
|
if (not (xshift and col == 0)
|
||||||
|
and not (not xshift and col == cols
|
||||||
|
and row == rows-1 and coldown)):
|
||||||
|
self.add_hexline(hexgrid, hexvertices, p[4], p[3])
|
||||||
|
self.logwrite('line 3-4\n')
|
||||||
|
if coldown and row == rows - 1 and col < cols:
|
||||||
|
self.add_hexline(hexgrid, hexvertices, p[1], p[2])
|
||||||
|
self.logwrite('line 1-2\n')
|
||||||
|
parent = svg
|
||||||
|
if layersingroup:
|
||||||
|
parent = self.createLayer('Hex Map')
|
||||||
|
self.append_if_new_name(svg, parent)
|
||||||
|
self.append_if_new_name(parent, hexfill)
|
||||||
|
self.append_if_new_name(parent, hexcircles)
|
||||||
|
self.append_if_new_name(parent, hexgrid)
|
||||||
|
self.append_if_new_name(parent, hexvertices)
|
||||||
|
self.append_if_new_name(parent, hexcoords)
|
||||||
|
self.append_if_new_name(parent, hexdots)
|
||||||
|
|
||||||
|
def append_if_new_name(self, svg, layer):
|
||||||
|
if layer is not None:
|
||||||
|
name = layer.get(inkex.addNS('label', 'inkscape'))
|
||||||
|
if not name in [c.get(inkex.addNS('label', 'inkscape'), 'name')
|
||||||
|
for c in svg.iterchildren()]:
|
||||||
|
svg.append(layer)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
CreateHexmap().run()
|
23
extensions/fablabchemnitz/create_hexmap/meta.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Create Hexmap",
|
||||||
|
"id": "fablabchemnitz.de.create_hexmap",
|
||||||
|
"path": "create_hexmap",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "Create Hexmap",
|
||||||
|
"original_id": "pelles.effect.hexmap",
|
||||||
|
"license": "GNU GPL v2",
|
||||||
|
"license_url": "https://github.com/lifelike/hexmapextension/blob/master/README.org",
|
||||||
|
"comment": "",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/create_hexmap",
|
||||||
|
"fork_url": "https://github.com/lifelike/hexmapextension",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Create+Hexmap",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"github.com/lifelike",
|
||||||
|
"github.com/pmjdebruijn",
|
||||||
|
"github.com/UnicodingUnicorn",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
22
extensions/fablabchemnitz/eggmazing/eggmazing.inx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Eggmazing</name>
|
||||||
|
<id>fablabchemnitz.de.eggmazing</id>
|
||||||
|
<param name="mazeSize" type="optiongroup" appearance="combo" gui-text="Maze dimensions (w x h):">
|
||||||
|
<option value="SMALL">Small (32 x 6)</option>
|
||||||
|
<option value="MEDIUM">Medium (64 x 12)</option>
|
||||||
|
<option value="LARGE">Large (96 x 18)</option>
|
||||||
|
<option value="XLARGE">Extra large (128 x 24)</option>
|
||||||
|
</param>
|
||||||
|
<effect needs-live-preview="true">
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz Shape Generators">
|
||||||
|
<submenu name="Puzzles/Mazes/Nests" />
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">eggmazing.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
663
extensions/fablabchemnitz/eggmazing/eggmazing.py
Normal file
@ -0,0 +1,663 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
# Draw a cylindrical maze suitable for plotting with the Eggbot
|
||||||
|
# The maze itself is generated using a depth first search (DFS)
|
||||||
|
|
||||||
|
# Written by Daniel C. Newman for the Eggbot Project
|
||||||
|
# Improvements and suggestions by W. Craig Trader
|
||||||
|
# 20 September 2010
|
||||||
|
|
||||||
|
# Update 26 April 2011 by Daniel C. Newman
|
||||||
|
#
|
||||||
|
# 1. Address Issue #40
|
||||||
|
# The extension now draws the maze by columns, going down
|
||||||
|
# one column of cells and then up the next column. By using
|
||||||
|
# this technique, the impact of slippage is largely limited
|
||||||
|
# the the West and East ends of the maze not meeting. Otherwise,
|
||||||
|
# the maze will still look quite well aligned both locally and
|
||||||
|
# globally. Only very gross slippage will impact the local
|
||||||
|
# appearance of the maze.
|
||||||
|
#
|
||||||
|
# Note that this new drawing technique is nearly as fast as
|
||||||
|
# the prior method. The prior method has been preserved and
|
||||||
|
# can be selected by setting self.hpp = True. ("hpp" intended
|
||||||
|
# to mean "high plotting precision".)
|
||||||
|
#
|
||||||
|
# 2. Changed the page dimensions to use a height of 800 rather
|
||||||
|
# than 1000 pixels.
|
||||||
|
#
|
||||||
|
# 3. When drawing the solution layer, draw the ending cell last.
|
||||||
|
# Previously, the starting and ending cells were first drawn,
|
||||||
|
# and then the solution path itself. That caused the pen to
|
||||||
|
# move to the beginning, the end, and then back to the beginning
|
||||||
|
# again to start the solution path. Alternatively, the solution
|
||||||
|
# path might have been drawn from the end to the start. However,
|
||||||
|
# just drawing the ending cell last was easier code-wise.
|
||||||
|
#
|
||||||
|
# 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 sys
|
||||||
|
import array
|
||||||
|
import math
|
||||||
|
import random
|
||||||
|
import inkex
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
# Initialize the pseudo random number generator
|
||||||
|
random.seed()
|
||||||
|
|
||||||
|
PLOT_WIDTH = 3200 # Eggbot plot width in pixels
|
||||||
|
PLOT_HEIGHT = 800 # Eggbot plot height in pixels
|
||||||
|
|
||||||
|
TARGET_WIDTH = 3200 # Desired plot width in pixels
|
||||||
|
TARGET_HEIGHT = 600 # Desired plot height in pixels
|
||||||
|
|
||||||
|
|
||||||
|
def draw_SVG_path(pts, c, t, parent):
|
||||||
|
"""
|
||||||
|
Add a SVG path element to the document
|
||||||
|
We could do this just as easily as a polyline
|
||||||
|
"""
|
||||||
|
if not pts: # Nothing to draw
|
||||||
|
return
|
||||||
|
if isinstance(pts, list):
|
||||||
|
assert len(pts) % 3 == 0, "len(pts) must be a multiple of three"
|
||||||
|
d = "{0} {1:d},{2:d}".format(pts[0], pts[1], pts[2])
|
||||||
|
for i in range(3, len(pts), 3):
|
||||||
|
d += " {0} {1:d},{2:d}".format(pts[i], pts[i + 1], pts[i + 2])
|
||||||
|
elif isinstance(pts, str):
|
||||||
|
d = pts
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
style = {'stroke': c, 'stroke-width': str(t), 'fill': 'none'}
|
||||||
|
line_attribs = {'style': str(inkex.Style(style)), 'd': d}
|
||||||
|
etree.SubElement(parent, inkex.addNS('path', 'svg'), line_attribs)
|
||||||
|
|
||||||
|
|
||||||
|
def draw_SVG_rect(x, y, w, h, c, t, fill, parent):
|
||||||
|
"""
|
||||||
|
Add a SVG rect element to the document
|
||||||
|
"""
|
||||||
|
style = {'stroke': c, 'stroke-width': str(t), 'fill': fill}
|
||||||
|
rect_attribs = {'style': str(inkex.Style(style)),
|
||||||
|
'x': str(x), 'y': str(y),
|
||||||
|
'width': str(w), 'height': str(h)}
|
||||||
|
etree.SubElement(parent, inkex.addNS('rect', 'svg'), rect_attribs)
|
||||||
|
|
||||||
|
|
||||||
|
class Eggmazing(inkex.EffectExtension):
|
||||||
|
"""
|
||||||
|
Each cell in the maze is represented using 9 bits:
|
||||||
|
|
||||||
|
Visited -- When set, indicates that this cell has been visited during
|
||||||
|
construction of the maze
|
||||||
|
|
||||||
|
Border -- Four bits indicating which if any of this cell's walls are
|
||||||
|
part of the maze's boundary (i.e., are unremovable walls)
|
||||||
|
|
||||||
|
Walls -- Four bits indicating which if any of this cell's walls are
|
||||||
|
still standing
|
||||||
|
|
||||||
|
Visited Border Walls
|
||||||
|
x x x x x x x x x
|
||||||
|
W S E N W S E N
|
||||||
|
"""
|
||||||
|
|
||||||
|
_VISITED = 0x0100
|
||||||
|
_NORTH = 0x0001
|
||||||
|
_EAST = 0x0002
|
||||||
|
_SOUTH = 0x0004
|
||||||
|
_WEST = 0x0008
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
|
||||||
|
inkex.Effect.__init__(self)
|
||||||
|
self.arg_parser.add_argument("--tab", default="controls", help="The active tab when Apply was pressed")
|
||||||
|
self.arg_parser.add_argument("--mazeSize", default="MEDIUM", help="Difficulty of maze to build")
|
||||||
|
|
||||||
|
self.hpp = False
|
||||||
|
|
||||||
|
self.w = 0
|
||||||
|
self.h = 0
|
||||||
|
self.solved = 0
|
||||||
|
self.start_x = 0
|
||||||
|
self.start_y = 0
|
||||||
|
self.finish_x = 0
|
||||||
|
self.finish_y = 0
|
||||||
|
self.solution_x = None
|
||||||
|
self.solution_y = None
|
||||||
|
self.cells = None
|
||||||
|
|
||||||
|
# Drawing information
|
||||||
|
self.scale = 25.0
|
||||||
|
self.last_point = None
|
||||||
|
self.path = ''
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
|
||||||
|
# These dimensions are chosen so as to maintain integral dimensions
|
||||||
|
# with a ratio of width to height of TARGET_WIDTH to TARGET_HEIGHT.
|
||||||
|
# Presently that's 3200 to 600 which leads to a ratio of 5 and 1/3.
|
||||||
|
|
||||||
|
if self.options.mazeSize == 'SMALL':
|
||||||
|
self.w = 32
|
||||||
|
self.h = 6
|
||||||
|
elif self.options.mazeSize == 'MEDIUM':
|
||||||
|
self.w = 64
|
||||||
|
self.h = 12
|
||||||
|
elif self.options.mazeSize == 'LARGE':
|
||||||
|
self.w = 96
|
||||||
|
self.h = 18
|
||||||
|
else:
|
||||||
|
self.w = 128
|
||||||
|
self.h = 24
|
||||||
|
|
||||||
|
# The large mazes tend to hit the recursion limit
|
||||||
|
limit = sys.getrecursionlimit()
|
||||||
|
if limit < (4 + self.w * self.h):
|
||||||
|
sys.setrecursionlimit(4 + self.w * self.h)
|
||||||
|
|
||||||
|
maze_size = self.w * self.h
|
||||||
|
self.finish_x = self.w - 1
|
||||||
|
self.finish_y = self.h - 1
|
||||||
|
self.solution_x = array.array('i', range(maze_size))
|
||||||
|
self.solution_y = array.array('i', range(maze_size))
|
||||||
|
self.cells = array.array('H', range(maze_size))
|
||||||
|
|
||||||
|
# Remove any old maze
|
||||||
|
for node in self.document.xpath('//svg:g[@inkscape:label="1 - Maze"]', namespaces=inkex.NSS):
|
||||||
|
parent = node.getparent()
|
||||||
|
parent.remove(node)
|
||||||
|
|
||||||
|
# Remove any old solution
|
||||||
|
for node in self.document.xpath('//svg:g[@inkscape:label="2 - Solution"]', namespaces=inkex.NSS):
|
||||||
|
parent = node.getparent()
|
||||||
|
parent.remove(node)
|
||||||
|
|
||||||
|
# Remove any empty, default "Layer 1"
|
||||||
|
for node in self.document.xpath('//svg:g[@id="layer1"]', namespaces=inkex.NSS):
|
||||||
|
if not node.getchildren():
|
||||||
|
parent = node.getparent()
|
||||||
|
parent.remove(node)
|
||||||
|
|
||||||
|
# Start a new maze
|
||||||
|
self.solved = 0
|
||||||
|
self.start_x = random.randint(0, self.w - 1)
|
||||||
|
self.finish_x = random.randint(0, self.w - 1)
|
||||||
|
|
||||||
|
# Initialize every cell with all four walls up
|
||||||
|
|
||||||
|
for i in range(maze_size):
|
||||||
|
self.cells[i] = Eggmazing._NORTH | Eggmazing._EAST | Eggmazing._SOUTH | Eggmazing._WEST
|
||||||
|
|
||||||
|
# Now set our borders -- borders being walls which cannot be removed.
|
||||||
|
# Since we are a maze on the surface of a cylinder we only have two
|
||||||
|
# edges and hence only two borders. We consider our two edges to run
|
||||||
|
# from WEST to EAST and to be at the NORTH and SOUTH.
|
||||||
|
|
||||||
|
z = (self.h - 1) * self.w
|
||||||
|
for x in range(self.w):
|
||||||
|
self.cells[x] |= Eggmazing._NORTH << 4
|
||||||
|
self.cells[x + z] |= Eggmazing._SOUTH << 4
|
||||||
|
|
||||||
|
# Build the maze
|
||||||
|
self.handle_cell(0, self.start_x, self.start_y)
|
||||||
|
|
||||||
|
# Now that the maze has been built, remove the appropriate walls
|
||||||
|
# associated with the start and finish points of the maze
|
||||||
|
|
||||||
|
# Note: we have to remove these after building the maze. If we
|
||||||
|
# remove them first, then the lack of a border at the start (or
|
||||||
|
# finish) cell will allow the handle_cell() routine to wander
|
||||||
|
# outside of the maze. I.e., handle_cell() doesn't do boundary
|
||||||
|
# checking on the cell cell coordinates it generates. Instead, it
|
||||||
|
# relies upon the presence of borders to prevent it wandering
|
||||||
|
# outside the confines of the maze.
|
||||||
|
|
||||||
|
self.remove_border(self.start_x, self.start_y, Eggmazing._NORTH)
|
||||||
|
self.remove_wall(self.start_x, self.start_y, Eggmazing._NORTH)
|
||||||
|
|
||||||
|
self.remove_border(self.finish_x, self.finish_y, Eggmazing._SOUTH)
|
||||||
|
self.remove_wall(self.finish_x, self.finish_y, Eggmazing._SOUTH)
|
||||||
|
|
||||||
|
# Now draw the maze
|
||||||
|
|
||||||
|
# The following scaling and translations scale the maze's
|
||||||
|
# (width, height) to (TARGET_WIDTH, TARGET_HEIGHT), and translates
|
||||||
|
# the maze so that it centered within a document of dimensions
|
||||||
|
# (width, height) = (PLOT_WIDTH, PLOT_HEIGHT)
|
||||||
|
|
||||||
|
# Note that each cell in the maze is drawn 2 x units wide by
|
||||||
|
# 2 y units high. A width and height of 2 was chosen for
|
||||||
|
# convenience and for allowing easy identification (as the integer 1)
|
||||||
|
# of the centerline along which to draw solution paths. It is the
|
||||||
|
# abstract units which are then mapped to the TARGET_WIDTH eggbot x
|
||||||
|
# pixels by TARGET_HEIGHT eggbot y pixels rectangle.
|
||||||
|
|
||||||
|
scale_x = float(TARGET_WIDTH) / float(2 * self.w)
|
||||||
|
scale_y = float(TARGET_HEIGHT) / float(2 * self.h)
|
||||||
|
translate_x = float(PLOT_WIDTH - TARGET_WIDTH) / 2.0
|
||||||
|
translate_y = float(PLOT_HEIGHT - TARGET_HEIGHT) / 2.0
|
||||||
|
|
||||||
|
# And the SVG transform is thus
|
||||||
|
t = 'translate({0:f},{1:f}) scale({2:f},{3:f})'.format(translate_x, translate_y, scale_x, scale_y)
|
||||||
|
|
||||||
|
# For scaling line thicknesses. We'll typically draw a line of
|
||||||
|
# thickness 1 but will need to make the SVG path have a thickness
|
||||||
|
# of 1 / scale so that after our transforms are applied, the
|
||||||
|
# resulting thickness is the 1 we wanted in the first place.
|
||||||
|
|
||||||
|
if scale_x > scale_y:
|
||||||
|
self.scale = scale_x
|
||||||
|
else:
|
||||||
|
self.scale = scale_y
|
||||||
|
|
||||||
|
self.last_point = None
|
||||||
|
self.path = ''
|
||||||
|
|
||||||
|
if not self.hpp:
|
||||||
|
|
||||||
|
# To draw the walls, we start at the left-most column of cells, draw down drawing
|
||||||
|
# the WEST and NORTH walls and then draw up drawing the EAST and SOUTH walls.
|
||||||
|
# By drawing in this back and forth fashion, we minimize the effect of slippage.
|
||||||
|
|
||||||
|
for x in range(0, self.w, 2):
|
||||||
|
self.draw_vertical(x)
|
||||||
|
|
||||||
|
else:
|
||||||
|
|
||||||
|
# The drawing style of the "high plotting precision" / "faster plotting" mode
|
||||||
|
# is such that it minimizes the number of pen up / pen down operations
|
||||||
|
# but at the expense of requiring higher drawing precision. It's style
|
||||||
|
# of drawing works best when there is very minimal slippage of the egg
|
||||||
|
|
||||||
|
# Draw the horizontal walls
|
||||||
|
|
||||||
|
self.draw_horizontal_hpp(0, Eggmazing._NORTH)
|
||||||
|
for y in range(self.h - 1):
|
||||||
|
self.draw_horizontal_hpp(y, Eggmazing._SOUTH)
|
||||||
|
self.draw_horizontal_hpp(self.h - 1, Eggmazing._SOUTH)
|
||||||
|
|
||||||
|
# Draw the vertical walls
|
||||||
|
|
||||||
|
# Since this is a maze on the surface of a cylinder, we don't need
|
||||||
|
# to draw the vertical walls at the outer edges (x = 0 & x = w - 1)
|
||||||
|
|
||||||
|
for x in range(self.w):
|
||||||
|
self.draw_vertical_hpp(x, Eggmazing._EAST)
|
||||||
|
|
||||||
|
# Maze in layer "1 - Maze"
|
||||||
|
attribs = {
|
||||||
|
inkex.addNS('label', 'inkscape'): '1 - Maze',
|
||||||
|
inkex.addNS('groupmode', 'inkscape'): 'layer',
|
||||||
|
'transform': t}
|
||||||
|
maze_layer = etree.SubElement(self.document.getroot(), 'g', attribs)
|
||||||
|
draw_SVG_path(self.path, "#000000", float(1 / self.scale), maze_layer)
|
||||||
|
|
||||||
|
# Now draw the solution in red in layer "2 - Solution"
|
||||||
|
|
||||||
|
attribs = {
|
||||||
|
inkex.addNS('label', 'inkscape'): '2 - Solution',
|
||||||
|
inkex.addNS('groupmode', 'inkscape'): 'layer',
|
||||||
|
'transform': t}
|
||||||
|
maze_layer = etree.SubElement(self.document.getroot(), 'g', attribs)
|
||||||
|
|
||||||
|
# Mark the starting cell
|
||||||
|
|
||||||
|
draw_SVG_rect(0.25 + 2 * self.start_x, 0.25 + 2 * self.start_y,
|
||||||
|
1.5, 1.5, "#ff0000", 0, "#ff0000", maze_layer)
|
||||||
|
|
||||||
|
# And now generate the solution path itself
|
||||||
|
|
||||||
|
# To minimize the number of plotted paths (and hence pen up / pen
|
||||||
|
# down operations), we generate as few SVG paths as possible.
|
||||||
|
# However, for aesthetic reasons we stop the path and start a new
|
||||||
|
# one when it runs off the edge of the document. We could keep on
|
||||||
|
# drawing as the eggbot will handle that just fine. However, it
|
||||||
|
# doesn't look as good in Inkscape. So, we end the path and start
|
||||||
|
# a new one which is wrapped to the other edge of the document.
|
||||||
|
|
||||||
|
pts = []
|
||||||
|
end_path = False
|
||||||
|
i = 0
|
||||||
|
while i < self.solved:
|
||||||
|
|
||||||
|
x1 = self.solution_x[i]
|
||||||
|
y1 = self.solution_y[i]
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
x2 = self.solution_x[i]
|
||||||
|
y2 = self.solution_y[i]
|
||||||
|
|
||||||
|
if math.fabs(x1 - x2) > 1:
|
||||||
|
|
||||||
|
# We wrapped horizontally...
|
||||||
|
if x1 > x2:
|
||||||
|
x2 = x1 + 1
|
||||||
|
else:
|
||||||
|
x2 = x1 - 1
|
||||||
|
end_path = True
|
||||||
|
|
||||||
|
if i == 1:
|
||||||
|
pts.extend(['M', 2 * x1 + 1, 2 * y1 + 1])
|
||||||
|
pts.extend(['L', 2 * x2 + 1, 2 * y2 + 1])
|
||||||
|
|
||||||
|
if not end_path:
|
||||||
|
continue
|
||||||
|
|
||||||
|
x2 = self.solution_x[i]
|
||||||
|
y2 = self.solution_y[i]
|
||||||
|
pts.extend(['M', 2 * x2 + 1, 2 * y2 + 1])
|
||||||
|
end_path = False
|
||||||
|
|
||||||
|
# Put the solution path into the drawing
|
||||||
|
draw_SVG_path(pts, '#ff0000', float(8 / self.scale), maze_layer)
|
||||||
|
|
||||||
|
# Now mark the ending cell
|
||||||
|
draw_SVG_rect(0.25 + 2 * self.finish_x, 0.25 + 2 * self.finish_y,
|
||||||
|
1.5, 1.5, "#ff0000", 0, "#ff0000", maze_layer)
|
||||||
|
|
||||||
|
# Restore the recursion limit
|
||||||
|
sys.setrecursionlimit(limit)
|
||||||
|
|
||||||
|
# Set some document properties
|
||||||
|
node = self.document.getroot()
|
||||||
|
node.set('width', '3200')
|
||||||
|
node.set('height', '800')
|
||||||
|
|
||||||
|
self.svg.set('viewBox', '0 0 3200 800')
|
||||||
|
|
||||||
|
# The following end up being ignored by Inkscape....
|
||||||
|
node = self.svg.namedview
|
||||||
|
node.set('showborder', 'false')
|
||||||
|
node.set(inkex.addNS('cx', u'inkscape'), '1600')
|
||||||
|
node.set(inkex.addNS('cy', u'inkscape'), '500')
|
||||||
|
node.set(inkex.addNS('showpageshadow', u'inkscape'), 'false')
|
||||||
|
|
||||||
|
# Mark the cell at (x, y) as "visited"
|
||||||
|
def visit(self, x, y):
|
||||||
|
self.cells[y * self.w + x] |= Eggmazing._VISITED
|
||||||
|
|
||||||
|
# Return a non-zero value if the cell at (x, y) has been visited
|
||||||
|
def is_visited(self, x, y):
|
||||||
|
if self.cells[y * self.w + x] & Eggmazing._VISITED:
|
||||||
|
return -1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Return a non-zero value if the cell at (x, y) has a wall
|
||||||
|
# in the direction d
|
||||||
|
def is_wall(self, x, y, d):
|
||||||
|
if self.cells[y * self.w + x] & d:
|
||||||
|
return -1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Remove the wall in the direction d from the cell at (x, y)
|
||||||
|
def remove_wall(self, x, y, d):
|
||||||
|
self.cells[y * self.w + x] &= ~d
|
||||||
|
|
||||||
|
# Return a non-zero value if the cell at (x, y) has a border wall
|
||||||
|
# in the direction d
|
||||||
|
def is_border(self, x, y, d):
|
||||||
|
if self.cells[y * self.w + x] & (d << 4):
|
||||||
|
return -1
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# Remove the border in the direction d from the cell at (x, y)
|
||||||
|
def remove_border(self, x, y, d):
|
||||||
|
self.cells[y * self.w + x] &= ~(d << 4)
|
||||||
|
|
||||||
|
# This is the DFS algorithm which builds the maze. We start at depth 0
|
||||||
|
# at the starting cell (self.start_x, self.start_y). We then walk to a
|
||||||
|
# randomly selected neighboring cell which has not yet been visited (i.e.,
|
||||||
|
# previously walked into). Each step of the walk is a recursive descent
|
||||||
|
# in depth. The solution to the maze comes about when we walk into the
|
||||||
|
# finish cell at (self.finish_x, self.finish_y).
|
||||||
|
#
|
||||||
|
# Each recursive descent finishes when the currently visited cell has no
|
||||||
|
# unvisited neighboring cells.
|
||||||
|
#
|
||||||
|
# Since we don't revisit previously visited cells, each cell is visited
|
||||||
|
# no more than once. As it turns out, each cell is visited, but that's a
|
||||||
|
# little harder to show. Net, net, each cell is visited exactly once.
|
||||||
|
|
||||||
|
def handle_cell(self, depth, x, y):
|
||||||
|
|
||||||
|
# Mark the current cell as visited
|
||||||
|
self.visit(x, y)
|
||||||
|
|
||||||
|
# Save this cell's location in our solution trail / backtrace
|
||||||
|
if not self.solved:
|
||||||
|
|
||||||
|
self.solution_x[depth] = x
|
||||||
|
self.solution_y[depth] = y
|
||||||
|
|
||||||
|
if (x == self.finish_x) and (y == self.finish_y):
|
||||||
|
# Maze has been solved
|
||||||
|
self.solved = depth
|
||||||
|
|
||||||
|
# Shuffle the four compass directions: this is the primary source
|
||||||
|
# of "randomness" in the generated maze. We need to visit each
|
||||||
|
# neighboring cell which has not yet been visited. If we always
|
||||||
|
# did that in the same order, then our mazes would look very regular.
|
||||||
|
# So, we shuffle the list of directions we try in order to find an
|
||||||
|
# unvisited neighbor.
|
||||||
|
|
||||||
|
# HINT: TRY COMMENTING OUT THE shuffle() BELOW AND SEE FOR YOURSELF
|
||||||
|
|
||||||
|
directions = [Eggmazing._NORTH, Eggmazing._SOUTH, Eggmazing._EAST, Eggmazing._WEST]
|
||||||
|
random.shuffle(directions)
|
||||||
|
|
||||||
|
# Now from the cell at (x, y), look to each of the four
|
||||||
|
# directions for unvisited neighboring cells
|
||||||
|
|
||||||
|
for each_direction in directions:
|
||||||
|
|
||||||
|
# If there is a border in direction[i], then don't try
|
||||||
|
# looking for a neighboring cell in that direction. We
|
||||||
|
# Use this check and borders to prevent generating invalid
|
||||||
|
# cell coordinates.
|
||||||
|
|
||||||
|
if self.is_border(x, y, each_direction):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Determine the cell coordinates of a neighboring cell
|
||||||
|
# NOTE: we trust the use of maze borders to prevent us
|
||||||
|
# from generating invalid cell coordinates
|
||||||
|
|
||||||
|
if each_direction == Eggmazing._NORTH:
|
||||||
|
nx = x
|
||||||
|
ny = y - 1
|
||||||
|
opposite_direction = Eggmazing._SOUTH
|
||||||
|
|
||||||
|
elif each_direction == Eggmazing._SOUTH:
|
||||||
|
nx = x
|
||||||
|
ny = y + 1
|
||||||
|
opposite_direction = Eggmazing._NORTH
|
||||||
|
|
||||||
|
elif each_direction == Eggmazing._EAST:
|
||||||
|
nx = x + 1
|
||||||
|
ny = y
|
||||||
|
opposite_direction = Eggmazing._WEST
|
||||||
|
|
||||||
|
else:
|
||||||
|
nx = x - 1
|
||||||
|
ny = y
|
||||||
|
opposite_direction = Eggmazing._EAST
|
||||||
|
|
||||||
|
# Wrap in the horizontal dimension
|
||||||
|
if nx < 0:
|
||||||
|
nx += self.w
|
||||||
|
elif nx >= self.w:
|
||||||
|
nx -= self.w
|
||||||
|
|
||||||
|
# See if this neighboring cell has been visited
|
||||||
|
if self.is_visited(nx, ny):
|
||||||
|
# Neighbor has been visited already
|
||||||
|
continue
|
||||||
|
|
||||||
|
# The neighboring cell has not been visited: remove the wall in
|
||||||
|
# the current cell leading to the neighbor. And, from the
|
||||||
|
# neighbor remove its wall leading to the current cell.
|
||||||
|
|
||||||
|
self.remove_wall(x, y, each_direction)
|
||||||
|
self.remove_wall(nx, ny, opposite_direction)
|
||||||
|
|
||||||
|
# Now recur by "moving" to this unvisited neighboring cell
|
||||||
|
|
||||||
|
self.handle_cell(depth + 1, nx, ny)
|
||||||
|
|
||||||
|
def draw_line(self, x1, y1, x2, y2):
|
||||||
|
|
||||||
|
if self.last_point is not None:
|
||||||
|
if (self.last_point[0] == x1) and (self.last_point[1] == y1):
|
||||||
|
self.path += ' L {0:d},{1:d}'.format(x2, y2)
|
||||||
|
self.last_point = [x2, y2]
|
||||||
|
elif (self.last_point[0] == x2) and (self.last_point[1] == y2):
|
||||||
|
self.path += ' L {0:d},{1:d} L {2:d},{3:d}'.format(x1, y1, x2, y2)
|
||||||
|
# self.last_point unchanged
|
||||||
|
else:
|
||||||
|
self.path += ' M {0:d},{1:d} L {2:d},{3:d}'.format(x1, y1, x2, y2)
|
||||||
|
self.last_point = [x2, y2]
|
||||||
|
else:
|
||||||
|
self.path = 'M {0:d},{1:d} L {2:d},{3:d}'.format(x1, y1, x2, y2)
|
||||||
|
self.last_point = [x2, y2]
|
||||||
|
|
||||||
|
def draw_wall(self, x, y, d, dir_):
|
||||||
|
|
||||||
|
if dir_ > 0:
|
||||||
|
if d == Eggmazing._NORTH:
|
||||||
|
self.draw_line(2 * (x + 1), 2 * y, 2 * x, 2 * y)
|
||||||
|
elif d == Eggmazing._WEST:
|
||||||
|
self.draw_line(2 * x, 2 * y, 2 * x, 2 * (y + 1))
|
||||||
|
elif d == Eggmazing._SOUTH:
|
||||||
|
self.draw_line(2 * (x + 1), 2 * (y + 1), 2 * x, 2 * (y + 1))
|
||||||
|
else: # Eggmazing._EAST
|
||||||
|
self.draw_line(2 * (x + 1), 2 * y, 2 * (x + 1), 2 * (y + 1))
|
||||||
|
else:
|
||||||
|
if d == Eggmazing._NORTH:
|
||||||
|
self.draw_line(2 * x, 2 * y, 2 * (x + 1), 2 * y)
|
||||||
|
elif d == Eggmazing._WEST:
|
||||||
|
self.draw_line(2 * x, 2 * (y + 1), 2 * x, 2 * y)
|
||||||
|
elif d == Eggmazing._SOUTH:
|
||||||
|
self.draw_line(2 * x, 2 * (y + 1), 2 * (x + 1), 2 * (y + 1))
|
||||||
|
else: # Eggmazing._EAST
|
||||||
|
self.draw_line(2 * (x + 1), 2 * (y + 1), 2 * (x + 1), 2 * y)
|
||||||
|
|
||||||
|
# Draw the vertical walls of the maze along the column of cells at
|
||||||
|
# horizontal positions
|
||||||
|
|
||||||
|
def draw_vertical(self, x):
|
||||||
|
|
||||||
|
# Drawing moving downwards from north to south
|
||||||
|
|
||||||
|
if self.is_wall(x, 0, Eggmazing._NORTH):
|
||||||
|
self.draw_wall(x, 0, Eggmazing._NORTH, +1)
|
||||||
|
|
||||||
|
for y in range(self.h):
|
||||||
|
if self.is_wall(x, y, Eggmazing._WEST):
|
||||||
|
self.draw_wall(x, y, Eggmazing._WEST, +1)
|
||||||
|
if self.is_wall(x, y, Eggmazing._SOUTH):
|
||||||
|
self.draw_wall(x, y, Eggmazing._SOUTH, +1)
|
||||||
|
|
||||||
|
# Now, return drawing upwards moving from south to north
|
||||||
|
|
||||||
|
x += 1
|
||||||
|
if x >= self.w:
|
||||||
|
return
|
||||||
|
|
||||||
|
for y in range(self.h - 1, -1, -1):
|
||||||
|
if self.is_wall(x, y, Eggmazing._SOUTH):
|
||||||
|
self.draw_wall(x, y, Eggmazing._SOUTH, -1)
|
||||||
|
if self.is_wall(x, y, Eggmazing._WEST):
|
||||||
|
self.draw_wall(x, y, Eggmazing._WEST, -1)
|
||||||
|
if self.is_wall(x, 0, Eggmazing._NORTH):
|
||||||
|
self.draw_wall(x, 0, Eggmazing._NORTH, -1)
|
||||||
|
|
||||||
|
# Draw the horizontal walls of the maze along the row of
|
||||||
|
# cells at "height" y: "high plotting precision" version
|
||||||
|
|
||||||
|
def draw_horizontal_hpp(self, y, wall):
|
||||||
|
|
||||||
|
# Cater to Python 2.4 and earlier
|
||||||
|
# dy = 0 if wall == Eggmazing._NORTH else 1
|
||||||
|
if wall == Eggmazing._NORTH:
|
||||||
|
dy = 0
|
||||||
|
else:
|
||||||
|
dy = 1
|
||||||
|
|
||||||
|
tracing = False
|
||||||
|
segment = 0
|
||||||
|
for x in range(self.w):
|
||||||
|
|
||||||
|
if self.is_wall(x, y, wall):
|
||||||
|
if not tracing:
|
||||||
|
# Starting a new segment
|
||||||
|
segment = x
|
||||||
|
tracing = True
|
||||||
|
else:
|
||||||
|
if tracing:
|
||||||
|
# Reached the end of a segment
|
||||||
|
self.draw_line(2 * segment, 2 * (y + dy),
|
||||||
|
2 * x, 2 * (y + dy))
|
||||||
|
tracing = False
|
||||||
|
|
||||||
|
if tracing:
|
||||||
|
# Draw the last wall segment
|
||||||
|
self.draw_line(2 * segment, 2 * (y + dy),
|
||||||
|
2 * self.w, 2 * (y + dy))
|
||||||
|
|
||||||
|
# Draw the vertical walls of the maze along the column of cells at
|
||||||
|
# horizontal position x: "high plotting precision" version
|
||||||
|
|
||||||
|
def draw_vertical_hpp(self, x, wall):
|
||||||
|
|
||||||
|
dx = 0 if wall == Eggmazing._WEST else 1
|
||||||
|
|
||||||
|
# We alternate the direction in which we draw each vertical wall.
|
||||||
|
# First, from North to South and then from South to North. This
|
||||||
|
# reduces pen travel on the Eggbot
|
||||||
|
|
||||||
|
if x % 2 == 0: # North-South
|
||||||
|
y_start, y_finis, dy, offset = 0, self.h, 1, 0
|
||||||
|
else: # South-North
|
||||||
|
y_start, y_finis, dy, offset = self.h - 1, -1, -1, 2
|
||||||
|
|
||||||
|
tracing = False
|
||||||
|
segment = y_start
|
||||||
|
for y in range(y_start, y_finis, dy):
|
||||||
|
assert 0 <= y < self.h, "y ({0:d}) is out of range".format(y)
|
||||||
|
if self.is_wall(x, y, wall):
|
||||||
|
if not tracing:
|
||||||
|
# Starting a new segment
|
||||||
|
segment = y
|
||||||
|
tracing = True
|
||||||
|
else:
|
||||||
|
if tracing:
|
||||||
|
# Hit the end of a segment
|
||||||
|
self.draw_line(2 * (x + dx), 2 * segment + offset,
|
||||||
|
2 * (x + dx), 2 * y + offset)
|
||||||
|
tracing = False
|
||||||
|
|
||||||
|
if tracing:
|
||||||
|
# complete the last wall segment
|
||||||
|
self.draw_line(2 * (x + dx), 2 * segment + offset,
|
||||||
|
2 * (x + dx), 2 * y_finis + offset)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
Eggmazing().run()
|
22
extensions/fablabchemnitz/eggmazing/meta.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Eggmazing",
|
||||||
|
"id": "fablabchemnitz.de.eggmazing",
|
||||||
|
"path": "eggmazing",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "Eggmazing",
|
||||||
|
"original_id": "command.eggbot.contributed.eggmazing",
|
||||||
|
"license": "GNU GPL v3",
|
||||||
|
"license_url": "https://github.com/evil-mad/EggBot/blob/master/LICENSE",
|
||||||
|
"comment": "",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/eggmazing",
|
||||||
|
"fork_url": "https://github.com/evil-mad/EggBot/tree/master/inkscape_contributed",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Eggmazing",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"github.com/evil-mad",
|
||||||
|
"github.com/oskay",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Estucheria - Airplane Type Linear Case</name>
|
||||||
|
<id>fablabchemnitz.de.airplanetypelinearcase</id>
|
||||||
|
<image>airplanetypelinearcase.svg</image>
|
||||||
|
<param name="unit" gui-text="Unit:" type="optiongroup" appearance="combo">
|
||||||
|
<option translatable="no" value="mm">mm</option>
|
||||||
|
</param>
|
||||||
|
<param name="width" type="float" min="0.1" max="300.0" gui-text="A - width:">25.0</param>
|
||||||
|
<param name="depth" type="float" min="0.1" max="300.0" gui-text="L - length:">25.0</param>
|
||||||
|
<param name="height" type="float" min="0.1" max="300.0" gui-text="H - height:">25.0</param>
|
||||||
|
<param name="glue_tab" type="float" min="0.1" max="300.0" gui-text="P - Width glue tab">5.0</param>
|
||||||
|
<param name="close_tab" type="float" min="0.1" max="300.0" gui-text="C - Top side sealing flange:">5.0</param>
|
||||||
|
<param name="side_tabs" type="float" min="0.1" max="300.0" gui-text="T - Top side closure tabs:">5.0</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz Boxes/Papercraft">
|
||||||
|
<submenu name="Paper/Cardboard Boxes" />
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">airplanetypelinearcase.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
239
extensions/fablabchemnitz/estucheria/airplanetypelinearcase.py
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
#! /usr/bin/env python3
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Este script dibuja el perfil exterior de corte la caja en un solo
|
||||||
|
# path cerrado y añade despues los otros flejes necesarios con colores
|
||||||
|
# diferentes para identificarlos.
|
||||||
|
# rojo > para cortes y perfil exterior
|
||||||
|
# azul > para hendidos
|
||||||
|
# verde > para taladros
|
||||||
|
# amarillo > medios cortes
|
||||||
|
#
|
||||||
|
# TODO:
|
||||||
|
# agregar opción de dibujo en cm/in
|
||||||
|
# mover dibujo al centro del documento
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
__version__ = "0.2"
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
|
||||||
|
class AirplaneTypeLinearCase(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("--width", type=float, default=25.0, help="Ancho de la caja")
|
||||||
|
pars.add_argument("--height", type=float, default=25.0, help="Alto de la caja")
|
||||||
|
pars.add_argument("--depth", type=float, default=25.0, help="Largo de la caja")
|
||||||
|
pars.add_argument("--glue_tab", type=float, default=5.0, help="Ancho pestaña de engomado")
|
||||||
|
pars.add_argument("--close_tab", type=float, default=5.0, help="Alto pestaña de cierre")
|
||||||
|
pars.add_argument("--side_tabs", type=float, default=5.0, help="Alto pestañas laterales de cierre")
|
||||||
|
pars.add_argument("--unit", default="mm", help="Tipo de unidades")
|
||||||
|
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
centro_ancho_documento = self.svg.unittouu(self.document.getroot().get('width'))/2
|
||||||
|
centro_alto_documento = self.svg.unittouu(self.document.getroot().get('height'))/2
|
||||||
|
|
||||||
|
medida_pestana1=5
|
||||||
|
medida_pestana2=1
|
||||||
|
medida_pestana3=4
|
||||||
|
medida_pestana4=3
|
||||||
|
|
||||||
|
ancho_caja = self.svg.unittouu(str(self.options.width) + self.options.unit)
|
||||||
|
alto_caja = self.svg.unittouu(str(self.options.height) + self.options.unit)
|
||||||
|
largo_caja = self.svg.unittouu(str(self.options.depth) + self.options.unit)
|
||||||
|
ancho_pestana_cola = self.svg.unittouu(str(self.options.glue_tab) + self.options.unit)
|
||||||
|
alto_pestana_cierre = self.svg.unittouu(str(self.options.close_tab) + self.options.unit)
|
||||||
|
alto_pestana = self.svg.unittouu(str(self.options.side_tabs) + self.options.unit)
|
||||||
|
|
||||||
|
if self.options.unit=="cm":
|
||||||
|
medida_pestana1=0.5
|
||||||
|
medida_pestana2=0.1
|
||||||
|
medida_pestana3=0.4
|
||||||
|
medida_pestana3=0.3
|
||||||
|
|
||||||
|
if self.options.unit=='in':
|
||||||
|
medida_pestana1=0.196
|
||||||
|
medida_pestana2=0.039
|
||||||
|
medida_pestana3=0.157
|
||||||
|
medida_pestana3=0.118
|
||||||
|
|
||||||
|
medida1_pestanas_laterales=self.svg.unittouu(str(medida_pestana1) + self.options.unit)
|
||||||
|
medida2_pestanas_laterales=self.svg.unittouu(str(medida_pestana2) + self.options.unit)
|
||||||
|
medida3_pestanas_laterales=self.svg.unittouu(str(medida_pestana3) + self.options.unit)
|
||||||
|
medida4_pestanas_laterales=self.svg.unittouu(str(medida_pestana4) + self.options.unit)
|
||||||
|
|
||||||
|
id_caja = self.svg.get_unique_id('estuche-lineal')
|
||||||
|
group = self.svg.get_current_layer().add(inkex.Group(id=id_caja))
|
||||||
|
estilo_linea_cortes = {'stroke': '#FF0000', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
estilo_linea_hendidos = {'stroke': '#0000FF', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
estilo_linea_medioscortes = {'stroke': '#00FFFF', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
|
||||||
|
# line.path --> M = coordenadas absolutas
|
||||||
|
# line.path --> l = dibuja una linea desde el punto actual a las coordenadas especificadas
|
||||||
|
# line.path --> c = dibuja una curva beizer desde el punto actual a las coordenadas especificadas
|
||||||
|
# line.path --> q = dibuja un arco desde el punto actual a las coordenadas especificadas usando un punto como referencia
|
||||||
|
# line.path --> Z = cierra path
|
||||||
|
|
||||||
|
#Perfil Exterior de la caja
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-exterior'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0, 0]],
|
||||||
|
['l', [0, 0-largo_caja]],
|
||||||
|
['l', [0, 0]],
|
||||||
|
['q', [0,0-alto_pestana_cierre,alto_pestana_cierre, 0-alto_pestana_cierre]],
|
||||||
|
['l', [ancho_caja-(alto_pestana_cierre*2), 0]],
|
||||||
|
['q', [alto_pestana_cierre,0,alto_pestana_cierre,alto_pestana_cierre]],
|
||||||
|
['l', [0, 0]],
|
||||||
|
['l', [0, (largo_caja)]],
|
||||||
|
['l', [medida4_pestanas_laterales, 0-medida4_pestanas_laterales]],
|
||||||
|
['l', [0,0-(alto_pestana-medida4_pestanas_laterales)]],
|
||||||
|
['l', [(largo_caja-medida2_pestanas_laterales-medida3_pestanas_laterales-medida4_pestanas_laterales), 0]],
|
||||||
|
['l', [medida3_pestanas_laterales, (alto_pestana-medida2_pestanas_laterales-medida1_pestanas_laterales)]],
|
||||||
|
['l', [medida2_pestanas_laterales, medida2_pestanas_laterales]],
|
||||||
|
['l', [0, medida1_pestanas_laterales]],
|
||||||
|
['l', [0,0]],
|
||||||
|
['l', [ancho_caja, 0]],
|
||||||
|
['l', [0,0]],
|
||||||
|
['l', [0, 0]],
|
||||||
|
['l', [0, 0-medida1_pestanas_laterales]],
|
||||||
|
['l', [medida2_pestanas_laterales, 0-medida2_pestanas_laterales]],
|
||||||
|
['l', [medida3_pestanas_laterales, 0-(alto_pestana-medida2_pestanas_laterales-medida1_pestanas_laterales)]],
|
||||||
|
['l', [(largo_caja-medida2_pestanas_laterales-medida3_pestanas_laterales-medida4_pestanas_laterales), 0]],
|
||||||
|
['l', [0,alto_pestana-medida4_pestanas_laterales]],
|
||||||
|
['l', [medida4_pestanas_laterales, medida4_pestanas_laterales]],
|
||||||
|
['l', [0, alto_caja]],
|
||||||
|
['l', [0-medida4_pestanas_laterales, medida4_pestanas_laterales]],
|
||||||
|
['l', [0,(alto_pestana-medida4_pestanas_laterales)]],
|
||||||
|
['l', [0-(largo_caja-medida2_pestanas_laterales-medida3_pestanas_laterales-medida4_pestanas_laterales), 0]],
|
||||||
|
['l', [0-(medida3_pestanas_laterales), 0-(alto_pestana-medida2_pestanas_laterales-medida1_pestanas_laterales)]],
|
||||||
|
['l', [0-(medida2_pestanas_laterales), 0-(medida2_pestanas_laterales)]],
|
||||||
|
['l', [0, 0-medida1_pestanas_laterales]],
|
||||||
|
['l', [0, 0]],
|
||||||
|
['l', [0,0]],
|
||||||
|
['l', [0-ancho_caja, 0]],
|
||||||
|
['l', [0,0]],
|
||||||
|
['l', [0, 0]],
|
||||||
|
['l', [0, medida1_pestanas_laterales]],
|
||||||
|
['l', [0-medida2_pestanas_laterales, medida2_pestanas_laterales]],
|
||||||
|
['l', [0-medida3_pestanas_laterales, (alto_pestana-medida2_pestanas_laterales-medida1_pestanas_laterales)]],
|
||||||
|
['l', [0-(largo_caja-medida2_pestanas_laterales-medida3_pestanas_laterales-medida4_pestanas_laterales), 0]],
|
||||||
|
['l', [0,0-(alto_pestana-medida4_pestanas_laterales)]],
|
||||||
|
['l', [0-medida4_pestanas_laterales, 0-medida4_pestanas_laterales]],
|
||||||
|
['l', [0,0]],
|
||||||
|
['l', [0, largo_caja]],
|
||||||
|
['l', [0, 0]],
|
||||||
|
['q', [0,alto_pestana_cierre,0-alto_pestana_cierre, alto_pestana_cierre]],#
|
||||||
|
['l', [0-(ancho_caja-(alto_pestana_cierre*2)), 0]],
|
||||||
|
['q', [0-alto_pestana_cierre,0,0-alto_pestana_cierre,0-alto_pestana_cierre]],
|
||||||
|
['l', [0, 0]],
|
||||||
|
['l', [0, 0-largo_caja]],
|
||||||
|
['l', [0, 0-medida2_pestanas_laterales]],
|
||||||
|
['l', [0-ancho_pestana_cola, 0-(ancho_pestana_cola/2)]],
|
||||||
|
['l', [0, 0-(alto_caja-ancho_pestana_cola-(medida2_pestanas_laterales*2))]],
|
||||||
|
['l', [ancho_pestana_cola, 0-(ancho_pestana_cola/2)]],
|
||||||
|
['Z', []]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_cortes
|
||||||
|
|
||||||
|
#Hendidos
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-1'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0,0]],
|
||||||
|
['l', [0,alto_caja]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-2'))
|
||||||
|
line.path = [
|
||||||
|
['M', [ancho_caja,0]],
|
||||||
|
['l', [0,alto_caja]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-3'))
|
||||||
|
line.path = [
|
||||||
|
['M', [ancho_caja+largo_caja,0]],
|
||||||
|
['l', [0,alto_caja]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-4'))
|
||||||
|
line.path = [
|
||||||
|
['M', [ancho_caja+ancho_caja+largo_caja,0]],
|
||||||
|
['l', [0,alto_caja]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-5'))
|
||||||
|
line.path = [
|
||||||
|
['M', [ancho_caja,0]],
|
||||||
|
['l', [largo_caja,0]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-6'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0,0]],
|
||||||
|
['l', [ancho_caja,0]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-7'))
|
||||||
|
line.path = [
|
||||||
|
['M', [(ancho_caja*2)+largo_caja,0]],
|
||||||
|
['l', [largo_caja,0]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-8'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0,alto_caja]],
|
||||||
|
['l', [ancho_caja,0]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-9'))
|
||||||
|
line.path = [
|
||||||
|
['M', [ancho_caja,alto_caja]],
|
||||||
|
['l', [largo_caja,0]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-10'))
|
||||||
|
line.path = [
|
||||||
|
['M', [(ancho_caja*2)+largo_caja,alto_caja]],
|
||||||
|
['l', [largo_caja,0]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-11'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0,0-(largo_caja)]],
|
||||||
|
['l', [ancho_caja,0]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-12'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0,alto_caja+largo_caja]],
|
||||||
|
['l', [ancho_caja,0]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
AirplaneTypeLinearCase().run()
|
228
extensions/fablabchemnitz/estucheria/airplanetypelinearcase.svg
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="94mm"
|
||||||
|
height="64mm"
|
||||||
|
viewBox="0 0 94.000001 64.000001"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||||
|
sodipodi:docname="LinealAvionLayout.svg">
|
||||||
|
<defs
|
||||||
|
id="defs2">
|
||||||
|
<rect
|
||||||
|
id="rect889"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect889-5" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect902" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect889-2" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect902-7" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect889-53" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect902-3" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect889-6" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect902-4" />
|
||||||
|
<rect
|
||||||
|
id="rect889-2-4"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect979"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
height="211mm"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-height="1018"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:cy="331.71525"
|
||||||
|
inkscape:cx="241.92977"
|
||||||
|
inkscape:zoom="0.98994949"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
borderopacity="1.0"
|
||||||
|
bordercolor="#666666"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
id="base" />
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
transform="translate(0,-232.99994)"
|
||||||
|
id="layer1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
inkscape:label="Capa 1">
|
||||||
|
<text
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,49.95402,191.64277)"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
id="text887"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
x="-68.892474"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">A</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
id="text887-5"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-5);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,71.467503,191.34641)"><tspan
|
||||||
|
x="-68.264088"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">L</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
id="text887-2"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-2);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,79.886969,176.2421)"><tspan
|
||||||
|
x="-68.892474"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">H</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
id="text887-56"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-53);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
transform="matrix(0.27672844,0,0,0.27672844,103.87092,227.43766)"><tspan
|
||||||
|
x="-68.264088"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">T</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
id="text887-9"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-6);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,50.619024,205.89437)"><tspan
|
||||||
|
x="-67.10791"
|
||||||
|
y="214.78815"><tspan
|
||||||
|
style="font-size:6.35px">C</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,31.923261,189.95252)"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-2-4);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
id="text887-2-3"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
x="-68.583793"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">P</tspan></tspan></text>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.41893423,0,0,0.24778878,8.2024521,246.91584)"
|
||||||
|
id="estuche-lineal3266">
|
||||||
|
<path
|
||||||
|
id="estuche-lineal3266-perfil-exterior"
|
||||||
|
d="m 0,0 v -30 0 q 0,-20 20,-20 h 30 q 20,0 20,20 v 0 30 l 3,-3 v -17 h 22 l 4,14 1,1 v 5 0 h 70 v 0 0 -5 l 1,-1 4,-14 h 22 v 17 l 3,3 v 150 l -3,3 v 17 h -22 l -4,-14 -1,-1 v -5 0 0 h -70 v 0 0 5 l -1,1 -4,14 H 73 v -17 l -3,-3 v 0 30 0 q 0,20 -20,20 H 20 Q 0,200 0,180 v 0 -30 -1 l -15,-7.5 V 8.5 L 0,1 Z"
|
||||||
|
style="fill:none;stroke:#ff0000;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-lineal3266-perfil-hendidos-1"
|
||||||
|
d="M 0,0 V 150 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-lineal3266-perfil-hendidos-2"
|
||||||
|
d="M 70,0 V 150 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-lineal3266-perfil-hendidos-3"
|
||||||
|
d="M 100,0 V 150 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-lineal3266-perfil-hendidos-4"
|
||||||
|
d="M 170,0 V 150 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-lineal3266-perfil-hendidos-5"
|
||||||
|
d="m 70,0 h 30 z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-lineal3266-perfil-hendidos-6"
|
||||||
|
d="M 0,0 H 70 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-lineal3266-perfil-hendidos-7"
|
||||||
|
d="m 170,0 h 30 z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-lineal3266-perfil-hendidos-8"
|
||||||
|
d="M 0,150 H 70 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-lineal3266-perfil-hendidos-9"
|
||||||
|
d="m 70,150 h 30 z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-lineal3266-perfil-hendidos-10"
|
||||||
|
d="m 170,150 h 30 z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-lineal3266-perfil-hendidos-11"
|
||||||
|
d="M 0,-30 H 70 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-lineal3266-perfil-hendidos-12"
|
||||||
|
d="M 0,180 H 70 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 8.8 KiB |
26
extensions/fablabchemnitz/estucheria/automaticbottomcase.inx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Estucheria - Automatic Bottom Case</name>
|
||||||
|
<id>fablabchemnitz.de.automaticbottomcase</id>
|
||||||
|
<image>automaticbottomcase.svg</image>
|
||||||
|
<param name="unit" gui-text="Unit:" type="optiongroup" appearance="combo">
|
||||||
|
<option translatable="no" value="mm">mm</option>
|
||||||
|
</param>
|
||||||
|
<param name="width" type="float" min="0.1" max="300.0" gui-text="A - width:">25.0</param>
|
||||||
|
<param name="depth" type="float" min="0.1" max="300.0" gui-text="L - length:">25.0</param>
|
||||||
|
<param name="height" type="float" min="0.1" max="300.0" gui-text="H - height:">25.0</param>
|
||||||
|
<param name="glue_tab" type="float" min="0.1" max="300.0" gui-text="P - Width glue tab">5.0</param>
|
||||||
|
<param name="close_tab" type="float" min="0.1" max="300.0" gui-text="C - Top side sealing flange:">5.0</param>
|
||||||
|
<param name="side_tabs" type="float" min="0.1" max="300.0" gui-text="T - Top side closure tabs:">5.0</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz Boxes/Papercraft">
|
||||||
|
<submenu name="Paper/Cardboard Boxes" />
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">automaticbottomcase.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
233
extensions/fablabchemnitz/estucheria/automaticbottomcase.py
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
#! /usr/bin/env python3
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# This script draws the outer profile of the box cut in a single
|
||||||
|
# closed path and then add the other necessary strips with colours
|
||||||
|
# different to identify them.
|
||||||
|
# red > for cuts and outer profile
|
||||||
|
# blue > for crevices
|
||||||
|
# green > for drills
|
||||||
|
# yellow > half-cut
|
||||||
|
#
|
||||||
|
# EVERYTHING:
|
||||||
|
# add cm/in drawing option
|
||||||
|
# move drawing to the center of the document
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
__version__ = "0.2"
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
|
||||||
|
class AutomaticBottomCase(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("--width", type=float, default=25.0, help="Width of the box")
|
||||||
|
pars.add_argument("--height", type=float, default=25.0, help="Height of the box")
|
||||||
|
pars.add_argument("--depth", type=float, default=25.0, help="Length of the box")
|
||||||
|
pars.add_argument("--glue_tab", type=float, default=5.0, help="Tab width")
|
||||||
|
pars.add_argument("--close_tab", type=float, default=5.0, help="Height sealing flange")
|
||||||
|
pars.add_argument("--side_tabs", type=float, default=5.0, help="Height side sealing flanges")
|
||||||
|
pars.add_argument("--unit", default="mm", help="Type of units")
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
width_center_document = self.svg.unittouu(self.document.getroot().get('width'))/2
|
||||||
|
height_center_document = self.svg.unittouu(self.document.getroot().get('height'))/2
|
||||||
|
|
||||||
|
eyelash_measure1=5
|
||||||
|
eyelash_measure2=1
|
||||||
|
eyelash_measure3=4
|
||||||
|
eyelash_measure4=3
|
||||||
|
|
||||||
|
box_width = self.svg.unittouu(str(self.options.width) + self.options.unit)
|
||||||
|
box_height = self.svg.unittouu(str(self.options.height) + self.options.unit)
|
||||||
|
box_length = self.svg.unittouu(str(self.options.depth) + self.options.unit)
|
||||||
|
glue_width = self.svg.unittouu(str(self.options.glue_tab) + self.options.unit)
|
||||||
|
top_flap_closure = self.svg.unittouu(str(self.options.close_tab) + self.options.unit)
|
||||||
|
top_flap = self.svg.unittouu(str(self.options.side_tabs) + self.options.unit)
|
||||||
|
|
||||||
|
if self.options.unit=="cm":
|
||||||
|
eyelash_measure1=0.5
|
||||||
|
eyelash_measure2=0.1
|
||||||
|
eyelash_measure3=0.4
|
||||||
|
eyelash_measure3=0.3
|
||||||
|
|
||||||
|
if self.options.unit=='in':
|
||||||
|
eyelash_measure1=0.196
|
||||||
|
eyelash_measure2=0.039
|
||||||
|
eyelash_measure3=0.157
|
||||||
|
eyelash_measure3=0.118
|
||||||
|
|
||||||
|
measure1_side_blind=self.svg.unittouu(str(eyelash_measure1) + self.options.unit)
|
||||||
|
measure2_side_blind=self.svg.unittouu(str(eyelash_measure2) + self.options.unit)
|
||||||
|
measure3_side_blind=self.svg.unittouu(str(eyelash_measure3) + self.options.unit)
|
||||||
|
measure4_side_blind=self.svg.unittouu(str(eyelash_measure4) + self.options.unit)
|
||||||
|
|
||||||
|
box_id = self.svg.get_unique_id('automaticbottomcase')
|
||||||
|
group = self.svg.get_current_layer().add(inkex.Group(id=box_id))
|
||||||
|
line_style_cuts = {'stroke': '#FF0000', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
cleft_line_style = {'stroke': '#0000FF', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
line_style_drills = {'stroke': '#00FF00', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
media_line_style = {'stroke': '#00FFFF', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
|
||||||
|
# line.path --> M = absolute coordinates
|
||||||
|
# line.path --> l = draws a line from the current point to the specified coordinates
|
||||||
|
# line.path --> c = draws a beizer curve from the current point to the specified coordinates
|
||||||
|
# line.path --> q = draws an arc from the current point to the specified coordinates using a point as reference
|
||||||
|
# line.path --> Z = close path
|
||||||
|
|
||||||
|
#Outside profile of the box
|
||||||
|
line = group.add(inkex.PathElement(id=box_id + '-profile-outside'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0, 0]],
|
||||||
|
['l', [0, 0-box_length]],
|
||||||
|
['l', [0, 0]],
|
||||||
|
['q', [0,0-top_flap_closure,top_flap_closure, 0-top_flap_closure]],
|
||||||
|
['l', [box_width-(top_flap_closure*2), 0]],
|
||||||
|
['q', [top_flap_closure,0,top_flap_closure,top_flap_closure]],
|
||||||
|
['l', [0, 0]],
|
||||||
|
['l', [0, (box_length)]],
|
||||||
|
['l', [measure4_side_blind, 0-measure4_side_blind]],
|
||||||
|
['l', [0,0-(top_flap-measure4_side_blind)]],
|
||||||
|
['l', [(box_length-measure2_side_blind-measure3_side_blind-measure4_side_blind), 0]],
|
||||||
|
['l', [measure3_side_blind, (top_flap-measure2_side_blind-measure1_side_blind)]],
|
||||||
|
['l', [measure2_side_blind, measure2_side_blind]],
|
||||||
|
['l', [0, measure1_side_blind]],
|
||||||
|
['l', [0,0]],
|
||||||
|
['l', [box_width, 0]],
|
||||||
|
['l', [0,0]],
|
||||||
|
['l', [0, 0]],
|
||||||
|
['l', [0, 0-measure1_side_blind]],
|
||||||
|
['l', [measure2_side_blind, 0-measure2_side_blind]],
|
||||||
|
['l', [measure3_side_blind, 0-(top_flap-measure2_side_blind-measure1_side_blind)]],
|
||||||
|
['l', [(box_length-measure2_side_blind-measure3_side_blind-measure4_side_blind), 0]],
|
||||||
|
['l', [0,top_flap-measure4_side_blind]],
|
||||||
|
['l', [measure4_side_blind, measure4_side_blind]],
|
||||||
|
['l', [0, box_height]],
|
||||||
|
['l', [0-((box_length/2)),box_length/2]],
|
||||||
|
['l', [0-((box_length/2)-(measure2_side_blind)*2),0]],
|
||||||
|
['l', [0-(measure2_side_blind),0-((box_length/2)-measure2_side_blind)]],
|
||||||
|
['l', [0-measure2_side_blind,0-measure2_side_blind]],
|
||||||
|
['l', [0-measure2_side_blind,measure2_side_blind]],
|
||||||
|
['l', [0,box_length*0.7]],
|
||||||
|
['l', [0-(((box_length*0.5)-measure2_side_blind)/2),0]],
|
||||||
|
['l', [0-(((box_length*0.5)-measure2_side_blind)/2),0-((box_length*0.7)-(box_length*0.5))]],
|
||||||
|
['l', [0-((box_width/2)-((box_length*0.5)-measure2_side_blind)),0]],
|
||||||
|
['l', [measure3_side_blind,measure3_side_blind]],
|
||||||
|
['l', [0-((box_length*0.7)-(box_length*0.5))-measure3_side_blind,((box_length*0.7)-(box_length*0.5))-measure3_side_blind]],
|
||||||
|
['l', [0-(((box_width/2)-((box_length*0.5)))+((box_length*0.7)-(box_length*0.5))),0]],
|
||||||
|
['l', [0-measure4_side_blind/2,0-((box_length*0.7))-measure2_side_blind]],
|
||||||
|
['l', [0-(box_length/2),box_length/2]],
|
||||||
|
['l', [0-((box_length/2)-(measure2_side_blind)*2),0]],
|
||||||
|
['l', [0-measure2_side_blind,0-((box_length/2)-measure2_side_blind)]],
|
||||||
|
['l', [0-measure2_side_blind,0-measure2_side_blind]],
|
||||||
|
['l', [0-measure2_side_blind,measure2_side_blind]],
|
||||||
|
['l', [0,box_length*0.7]],
|
||||||
|
['l', [0-(((box_length*0.5)-measure2_side_blind)/2),0]],
|
||||||
|
['l', [0-(((box_length*0.5)-measure2_side_blind)/2),0-((box_length*0.7)-(box_length*0.5))]],
|
||||||
|
['l', [0-((box_width/2)-((box_length*0.5)-measure2_side_blind)),0]],
|
||||||
|
['l', [measure3_side_blind,measure3_side_blind]],
|
||||||
|
['l', [0-((box_length*0.7)-(box_length*0.5))-measure3_side_blind,((box_length*0.7)-(box_length*0.5))-measure3_side_blind]],
|
||||||
|
['l', [0-(((box_width/2)-((box_length*0.5)))+((box_length*0.7)-(box_length*0.5))),0]],
|
||||||
|
['l', [0-measure4_side_blind/2,0-((box_length*0.7))-measure2_side_blind]],
|
||||||
|
['l', [0, 0-measure2_side_blind]],
|
||||||
|
['l', [0-glue_width, 0-(glue_width/2)]],
|
||||||
|
['l', [0, 0-(box_height-glue_width-(measure2_side_blind*2))]],
|
||||||
|
['l', [glue_width, 0-(glue_width/2)]],
|
||||||
|
['Z', []]
|
||||||
|
]
|
||||||
|
line.style = line_style_cuts
|
||||||
|
|
||||||
|
#Hendidos
|
||||||
|
line = group.add(inkex.PathElement(id=box_id + '-profile-splits-1'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0,0]],
|
||||||
|
['l', [0,box_height]]
|
||||||
|
]
|
||||||
|
line.style = cleft_line_style
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=box_id + '-profile-splits-2'))
|
||||||
|
line.path = [
|
||||||
|
['M', [box_width,0]],
|
||||||
|
['l', [0,box_height]]
|
||||||
|
]
|
||||||
|
line.style = cleft_line_style
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=box_id + '-profile-splits-3'))
|
||||||
|
line.path = [
|
||||||
|
['M', [box_width+box_length,0]],
|
||||||
|
['l', [0,box_height]]
|
||||||
|
]
|
||||||
|
line.style = cleft_line_style
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=box_id + '-profile-splits-4'))
|
||||||
|
line.path = [
|
||||||
|
['M', [box_width+box_width+box_length,0]],
|
||||||
|
['l', [0,box_height]]
|
||||||
|
]
|
||||||
|
line.style = cleft_line_style
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=box_id + '-profile-splits-5'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0,0]],
|
||||||
|
['l', [box_width,0]]
|
||||||
|
]
|
||||||
|
line.style = cleft_line_style
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=box_id + '-profile-splits-6'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0,box_height]],
|
||||||
|
['l', [((box_length+box_width)*2),0]]
|
||||||
|
]
|
||||||
|
line.style = cleft_line_style
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=box_id + '-profile-splits-7'))
|
||||||
|
line.path = [
|
||||||
|
['M', [box_width,0]],
|
||||||
|
['l', [box_length,0]]
|
||||||
|
]
|
||||||
|
line.style = cleft_line_style
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=box_id + '-profile-splits-8'))
|
||||||
|
line.path = [
|
||||||
|
['M', [box_length+box_width*2,0]],
|
||||||
|
['l', [box_length,0]]
|
||||||
|
]
|
||||||
|
line.style = cleft_line_style
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=box_id + '-profile-splits-9'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0,0-(box_length)]],
|
||||||
|
['l', [box_width,0]]
|
||||||
|
]
|
||||||
|
line.style = cleft_line_style
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=box_id + '-profile-drill-1'))
|
||||||
|
line.path = [
|
||||||
|
['M', [box_width-measure2_side_blind,box_height+measure2_side_blind]],
|
||||||
|
['l', [0-((box_length*0.5)),(box_length*0.5)]]
|
||||||
|
]
|
||||||
|
line.style = line_style_drills
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=box_id + '-profile-drill-2'))
|
||||||
|
line.path = [
|
||||||
|
['M', [((box_width*2)+box_length)-measure2_side_blind,box_height+measure2_side_blind]],
|
||||||
|
['l', [0-((box_length*0.5)),(box_length*0.5)]]
|
||||||
|
]
|
||||||
|
line.style = line_style_drills
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
AutomaticBottomCase().run()
|
236
extensions/fablabchemnitz/estucheria/automaticbottomcase.svg
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="94mm"
|
||||||
|
height="64mm"
|
||||||
|
viewBox="0 0 94.000001 64.000001"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||||
|
sodipodi:docname="FondoAutomaticoLayout.svg">
|
||||||
|
<defs
|
||||||
|
id="defs2">
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect889" />
|
||||||
|
<rect
|
||||||
|
id="rect889-5"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect902"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect889-2"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect902-7"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect889-53"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect902-3"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect889-6"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect902-4"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect889-2-4" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect979" />
|
||||||
|
<rect
|
||||||
|
id="rect889-2-4-1"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect2863"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="2.8"
|
||||||
|
inkscape:cx="135.62322"
|
||||||
|
inkscape:cy="128.43274"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1018"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
height="211mm" />
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Capa 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-232.99994)">
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
id="text887"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,46.319357,195.18017)"><tspan
|
||||||
|
x="-68.892474"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">A</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,68.201319,194.96547)"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-5);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
id="text887-5"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
x="-68.264088"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">L</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,77.672498,179.97242)"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-2);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
id="text887-2"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
x="-68.892474"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">H</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,112.60673,158.33517)"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-53);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
id="text887-56"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
x="-68.264088"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">T</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
id="text887-2-3"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-2-4);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
transform="matrix(0.28752215,0,0,0.28752215,21.573379,221.63586)"><tspan
|
||||||
|
x="-68.583793"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">P</tspan></tspan></text>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.25345327,0,0,0.23462671,5.0055809,251.88848)"
|
||||||
|
id="estuche-fondoautomatico8066">
|
||||||
|
<path
|
||||||
|
id="estuche-fondoautomatico8066-perfil-exterior"
|
||||||
|
d="m 0,0 v -50.25 h 0.25 q 0,-20 20,-20 h 79.5 q 20,0 20,20 H 120 V -0.5 l 3,-3 v -27 h 41.5 l 4,24 1,1 v 5 0.25 h 120 V 0 h 0.25 v -5 l 1,-1 4,-24 h 41.75 v 27 l 3,3 v 150 l -24.75,25 H 292 l -1,-24 -1,-1 -1,1 v 35 h -12 l -12,-10 h -36 l 4,4 -14,6 h -45 l -4,-35.5 -25,25 h -23 l -1,-24 -1,-1 -1,1 v 35 h -12 l -12,-10 H 59 l 4,4 -14,6 H 4 L 0,151.25 v -1 L -15,142.5 V 10 L 0,2.25 Z"
|
||||||
|
style="fill:none;stroke:#ff0000;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-fondoautomatico8066-perfil-hendidos-1"
|
||||||
|
d="M 0,0 V 150 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-fondoautomatico8066-perfil-hendidos-2"
|
||||||
|
d="M 120,0 V 150 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-fondoautomatico8066-perfil-hendidos-3"
|
||||||
|
d="M 170,0 V 150 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-fondoautomatico8066-perfil-hendidos-4"
|
||||||
|
d="M 290,0 V 150 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-fondoautomatico8066-perfil-hendidos-5"
|
||||||
|
d="M 0,0 H 120 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-fondoautomatico8066-perfil-hendidos-6"
|
||||||
|
d="M 0,150.25 H 339.5 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-fondoautomatico8066-perfil-hendidos-7"
|
||||||
|
d="m 120.25,0.25 h 49.5 z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-fondoautomatico8066-perfil-hendidos-8"
|
||||||
|
d="M 290.5,0.25 H 340 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-fondoautomatico8066-perfil-hendidos-9"
|
||||||
|
d="m 0.25,-50.25 h 119.5 z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-fondoautomatico8066-perfil-taladro-1"
|
||||||
|
d="M 119.25,151.25 94.5,176 Z"
|
||||||
|
style="fill:none;stroke:#00ff00;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-fondoautomatico8066-perfil-taladro-2"
|
||||||
|
d="M 289.5,151.25 264.75,176 Z"
|
||||||
|
style="fill:none;stroke:#00ff00;stroke-width:0.264583" />
|
||||||
|
</g>
|
||||||
|
<text
|
||||||
|
transform="matrix(0.28752215,0,0,0.28752215,38.815037,175.78393)"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-2-4-1);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
id="text887-2-3-2"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
x="-68.892474"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">C</tspan></tspan></text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 9.2 KiB |
23
extensions/fablabchemnitz/estucheria/box4p.inx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Estucheria - 4 Point Base Box</name>
|
||||||
|
<id>fablabchemnitz.de.box4p</id>
|
||||||
|
<image>box4p.svg</image>
|
||||||
|
<param name="unit" gui-text="Unit:" type="optiongroup" appearance="combo">
|
||||||
|
<option translatable="no" value="mm">mm</option>
|
||||||
|
</param>
|
||||||
|
<param name="width" type="float" min="0.1" max="300.0" gui-text="A - width:">25.0</param>
|
||||||
|
<param name="depth" type="float" min="0.1" max="300.0" gui-text="L - length:">25.0</param>
|
||||||
|
<param name="height" type="float" min="0.1" max="300.0" gui-text="H - height:">25.0</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz Boxes/Papercraft">
|
||||||
|
<submenu name="Paper/Cardboard Boxes" />
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">box4p.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
162
extensions/fablabchemnitz/estucheria/box4p.py
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
#! /usr/bin/env python3
|
||||||
|
#
|
||||||
|
# Este script dibuja el perfil exterior de corte la caja en un solo
|
||||||
|
# path cerrado y añade despues los otros flejes necesarios con colores
|
||||||
|
# diferentes para identificarlos.
|
||||||
|
# rojo > para cortes y perfil exterior
|
||||||
|
# azul > para hendidos
|
||||||
|
# verde > para taladros
|
||||||
|
# amarillo > medios cortes
|
||||||
|
#
|
||||||
|
# TODO:
|
||||||
|
# agregar opción de dibujo en cm/in
|
||||||
|
# mover dibujo al centro del documento
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
__version__ = "0.2"
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
|
||||||
|
class Box4P(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("--width", type=float, default=25.0, help="Ancho de la caja")
|
||||||
|
pars.add_argument("--height", type=float, default=25.0, help="Alto de la caja")
|
||||||
|
pars.add_argument("--depth", type=float, default=25.0, help="Largo de la caja")
|
||||||
|
pars.add_argument("--unit", default="mm", help="Tipo de unidades")
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
centro_ancho_documento = self.svg.unittouu(self.document.getroot().get('width'))/2
|
||||||
|
centro_alto_documento = self.svg.unittouu(self.document.getroot().get('height'))/2
|
||||||
|
|
||||||
|
medida_pestana1=2
|
||||||
|
|
||||||
|
ancho_caja = self.svg.unittouu(str(self.options.width) + self.options.unit)
|
||||||
|
alto_caja = self.svg.unittouu(str(self.options.height) + self.options.unit)
|
||||||
|
largo_caja = self.svg.unittouu(str(self.options.depth) + self.options.unit)
|
||||||
|
|
||||||
|
if self.options.unit=="cm":
|
||||||
|
medida_pestana1=0.2
|
||||||
|
|
||||||
|
if self.options.unit=='in':
|
||||||
|
medida_pestana1=0.0787
|
||||||
|
|
||||||
|
medida1_pestanas_laterales=self.svg.unittouu(str(medida_pestana1) + self.options.unit)
|
||||||
|
|
||||||
|
id_caja = self.svg.get_unique_id('bandeja-4P')
|
||||||
|
group = self.svg.get_current_layer().add(inkex.Group(id=id_caja))
|
||||||
|
estilo_linea_cortes = {'stroke': '#FF0000', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
estilo_linea_hendidos = {'stroke': '#0000FF', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
estilo_linea_taladros = {'stroke': '#00FF00', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
estilo_linea_medioscortes = {'stroke': '#00FFFF', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
|
||||||
|
# line.path --> M = coordenadas absolutas
|
||||||
|
# line.path --> l = dibuja una linea desde el punto actual a las coordenadas especificadas
|
||||||
|
# line.path --> c = dibuja una curva beizer desde el punto actual a las coordenadas especificadas
|
||||||
|
# line.path --> q = dibuja un arco desde el punto actual a las coordenadas especificadas usando un punto como referencia
|
||||||
|
# line.path --> Z = cierra path
|
||||||
|
|
||||||
|
#Perfil Exterior de la caja
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-exterior'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0, 0]],
|
||||||
|
['l', [alto_caja,0]],
|
||||||
|
['l', [ancho_caja,0]],
|
||||||
|
['l', [alto_caja,0]],
|
||||||
|
['l', [0,alto_caja-medida1_pestanas_laterales]],
|
||||||
|
['l', [0-(alto_caja-medida1_pestanas_laterales),0]],
|
||||||
|
['l', [0-medida1_pestanas_laterales,medida1_pestanas_laterales]],
|
||||||
|
['l', [alto_caja,0]],
|
||||||
|
['l', [0,largo_caja]],
|
||||||
|
['l', [0-alto_caja,0]],
|
||||||
|
['l', [medida1_pestanas_laterales,medida1_pestanas_laterales]],
|
||||||
|
['l', [alto_caja-medida1_pestanas_laterales,0]],
|
||||||
|
['l', [0,alto_caja-medida1_pestanas_laterales]],
|
||||||
|
['l', [0-(alto_caja),0]],
|
||||||
|
['l', [0-(ancho_caja),0]],
|
||||||
|
['l', [0-(alto_caja),0]],
|
||||||
|
['l', [0,0-(alto_caja-medida1_pestanas_laterales)]],
|
||||||
|
['l', [alto_caja-medida1_pestanas_laterales,0]],
|
||||||
|
['l', [medida1_pestanas_laterales,0-medida1_pestanas_laterales]],
|
||||||
|
['l', [0-(alto_caja),0]],
|
||||||
|
['l', [0,0-largo_caja]],
|
||||||
|
['l', [alto_caja,0]],
|
||||||
|
['l', [0-medida1_pestanas_laterales,0-medida1_pestanas_laterales]],
|
||||||
|
['l', [0-(alto_caja-medida1_pestanas_laterales),0]],
|
||||||
|
['Z', []]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_cortes
|
||||||
|
|
||||||
|
#Hendidos
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-1'))
|
||||||
|
line.path = [
|
||||||
|
['M', [alto_caja,0]],
|
||||||
|
['l', [0,largo_caja+(alto_caja*2)]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-2'))
|
||||||
|
line.path = [
|
||||||
|
['M', [ancho_caja+alto_caja,0]],
|
||||||
|
['l', [0,largo_caja+(alto_caja*2)]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-3'))
|
||||||
|
line.path = [
|
||||||
|
['M', [alto_caja,alto_caja]],
|
||||||
|
['l', [ancho_caja,0]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-4'))
|
||||||
|
line.path = [
|
||||||
|
['M', [alto_caja,alto_caja+largo_caja]],
|
||||||
|
['l', [ancho_caja,0]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-5'))
|
||||||
|
line.path = [
|
||||||
|
['M', [alto_caja,alto_caja]],
|
||||||
|
['l', [0-alto_caja,alto_caja]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-6'))
|
||||||
|
line.path = [
|
||||||
|
['M', [alto_caja+ancho_caja,alto_caja]],
|
||||||
|
['l', [alto_caja,alto_caja]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-7'))
|
||||||
|
line.path = [
|
||||||
|
['M', [alto_caja+ancho_caja,alto_caja+largo_caja]],
|
||||||
|
['l', [alto_caja,0-alto_caja]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-8'))
|
||||||
|
line.path = [
|
||||||
|
['M', [alto_caja,alto_caja+largo_caja]],
|
||||||
|
['l', [0-alto_caja,0-alto_caja]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
Box4P().run()
|
212
extensions/fablabchemnitz/estucheria/box4p.svg
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="94mm"
|
||||||
|
height="64mm"
|
||||||
|
viewBox="0 0 94.000001 64.000001"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||||
|
sodipodi:docname="Fondo4PLayout.svg">
|
||||||
|
<defs
|
||||||
|
id="defs2">
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect889" />
|
||||||
|
<rect
|
||||||
|
id="rect889-5"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect902"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect889-2"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect902-7"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect889-53"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect902-3"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect889-6"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect902-4"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect889-2-4" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect979" />
|
||||||
|
<rect
|
||||||
|
id="rect889-53-2"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect2158"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect889-6-3"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect2161"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="1.4"
|
||||||
|
inkscape:cx="30.346562"
|
||||||
|
inkscape:cy="510.32236"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1018"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
height="211mm" />
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Capa 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-232.99994)">
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
id="text887"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,37.180884,174.77023)"><tspan
|
||||||
|
x="-68.892474"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">A</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,73.057456,197.94617)"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-5);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
id="text887-5"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
x="-68.264088"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">L</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,116.8177,198.07432)"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-2);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
id="text887-2"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
x="-68.892474"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">H</tspan></tspan></text>
|
||||||
|
<g
|
||||||
|
transform="matrix(0,0.16755463,-0.19590377,0,92.695792,234.23686)"
|
||||||
|
id="bandeja-4P4625">
|
||||||
|
<path
|
||||||
|
id="bandeja-4P4625-perfil-exterior"
|
||||||
|
d="m 0,0 h 30 305 30 v 28 h -28 l -2,2 h 30 v 404 h -30 l 2,2 h 28 v 28 H 335 30 0 v -28 h 28 l 2,-2 H 0 V 30 H 30 L 28,28 H 0 Z"
|
||||||
|
style="fill:none;stroke:#ff0000;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="bandeja-4P4625-perfil-hendidos-1"
|
||||||
|
d="M 30,0 V 464 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="bandeja-4P4625-perfil-hendidos-2"
|
||||||
|
d="M 335,0 V 464 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="bandeja-4P4625-perfil-hendidos-3"
|
||||||
|
d="M 30,30 H 335 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="bandeja-4P4625-perfil-hendidos-4"
|
||||||
|
d="M 30,434 H 335 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="bandeja-4P4625-perfil-hendidos-5"
|
||||||
|
d="M 30,30 0,60 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="bandeja-4P4625-perfil-hendidos-6"
|
||||||
|
d="m 335,30 30,30 z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="bandeja-4P4625-perfil-hendidos-7"
|
||||||
|
d="m 335,434 30,-30 z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="bandeja-4P4625-perfil-hendidos-8"
|
||||||
|
d="M 30,434 0,404 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 6.8 KiB |
24
extensions/fablabchemnitz/estucheria/doublerailingcase.inx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Estucheria - Double Railing Case</name>
|
||||||
|
<id>fablabchemnitz.de.doublerailingcase</id>
|
||||||
|
<image>doublerailingcase.svg</image>
|
||||||
|
<param name="unit" gui-text="Unit:" type="optiongroup" appearance="combo">
|
||||||
|
<option translatable="no" value="mm">mm</option>
|
||||||
|
</param>
|
||||||
|
<param name="width" type="float" min="0.1" max="300.0" gui-text="A - width:">25.0</param>
|
||||||
|
<param name="depth" type="float" min="0.1" max="300.0" gui-text="L - length:">25.0</param>
|
||||||
|
<param name="height" type="float" min="0.1" max="300.0" gui-text="H - height:">25.0</param>
|
||||||
|
<param name="glue_tab" type="float" min="0.1" max="300.0" gui-text="P - Width glue tab">5.0</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz Boxes/Papercraft">
|
||||||
|
<submenu name="Paper/Cardboard Boxes" />
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">doublerailingcase.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
225
extensions/fablabchemnitz/estucheria/doublerailingcase.py
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
#! /usr/bin/env python3
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# This script draws the outer profile of the box cut in a single
|
||||||
|
# closed path and then add the other necessary strips with colours
|
||||||
|
# different to identify them.
|
||||||
|
# red > for cuts and outer profile
|
||||||
|
# blue > for crevices
|
||||||
|
# green > for drills
|
||||||
|
# yellow > half-cut
|
||||||
|
#
|
||||||
|
# TODO:
|
||||||
|
# add cm/in drawing option
|
||||||
|
# move drawing to the center of the document
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
__version__ = "0.2"
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
|
||||||
|
class DoubeRailingCase(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("--width", type=float, default=25.0, help="Width of the box")
|
||||||
|
pars.add_argument("--height", type=float, default=25.0, help="Height of the box")
|
||||||
|
pars.add_argument("--depth", type=float, default=25.0, help="Length of the box")
|
||||||
|
pars.add_argument("--glue_tab", type=float, default=5.0, help="Tab width")
|
||||||
|
pars.add_argument("--unit", default="mm", help="Type of units")
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
center_width_document = self.svg.unittouu(self.document.getroot().get('width'))/2
|
||||||
|
center_height_document = self.svg.unittouu(self.document.getroot().get('height'))/2
|
||||||
|
|
||||||
|
box_width = self.svg.unittouu(str(self.options.width) + self.options.unit)
|
||||||
|
box_height = self.svg.unittouu(str(self.options.height) + self.options.unit)
|
||||||
|
box_length = self.svg.unittouu(str(self.options.depth) + self.options.unit)
|
||||||
|
eyelash_width = self.svg.unittouu(str(self.options.glue_tab) + self.options.unit)
|
||||||
|
|
||||||
|
eyelash_measure1=2
|
||||||
|
eyelash_measure2=1
|
||||||
|
eyelash_measure3=5
|
||||||
|
eyelash_measure4=3
|
||||||
|
|
||||||
|
id_box = self.svg.get_unique_id('double-railing-case')
|
||||||
|
group = self.svg.get_current_layer().add(inkex.Group(id=id_box))
|
||||||
|
line_style_cuts = {'stroke': '#FF0000', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
cleft_line_style = {'stroke': '#0000FF', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
line_style_drills = {'stroke': '#00FF00', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
media_line_style = {'stroke': '#00FFFF', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
|
||||||
|
# line.path --> M = absolute coordinates
|
||||||
|
# line.path --> l = draws a line from the current point to the specified coordinates
|
||||||
|
# line.path --> c = draws a beizer curve from the current point to the specified coordinates
|
||||||
|
# line.path --> q = draws an arc from the current point to the specified coordinates using a point as reference
|
||||||
|
# line.path --> Z = close path
|
||||||
|
|
||||||
|
#Outside profile of the box
|
||||||
|
line = group.add(inkex.PathElement(id=id_box + '-profile-outside'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0, 0]],
|
||||||
|
['l', [box_width,0]],
|
||||||
|
['l', [0,0]],
|
||||||
|
['l', [0,eyelash_width]],
|
||||||
|
['l', [eyelash_width,0]],
|
||||||
|
['l', [0-(eyelash_width-eyelash_measure1),box_height-eyelash_measure4]],
|
||||||
|
['l', [0-eyelash_measure1,eyelash_measure2]],
|
||||||
|
['l', [0,eyelash_measure1]],
|
||||||
|
['l', [box_height-eyelash_measure3,eyelash_measure3]],
|
||||||
|
['l', [eyelash_measure3,box_height-eyelash_measure3]],
|
||||||
|
['l', [eyelash_measure1,0]],
|
||||||
|
['l', [eyelash_measure2,eyelash_measure1]],
|
||||||
|
['l', [box_height-eyelash_measure4,eyelash_width-eyelash_measure1]],
|
||||||
|
['l', [0,box_length-(eyelash_width*2)]],
|
||||||
|
['l', [0-(box_height-eyelash_measure4),eyelash_width-eyelash_measure1]],
|
||||||
|
['l', [0-eyelash_measure2,eyelash_measure1]],
|
||||||
|
['l', [0-eyelash_measure1,0]],
|
||||||
|
['l', [0-eyelash_measure3,box_height-eyelash_measure3]],
|
||||||
|
['l', [0-(box_height-eyelash_measure3),eyelash_measure3]],
|
||||||
|
['l', [0,eyelash_measure1]],
|
||||||
|
['l', [eyelash_measure1,eyelash_measure2]],
|
||||||
|
['l', [eyelash_width-eyelash_measure1,box_height-eyelash_measure4]],
|
||||||
|
['l', [0-eyelash_width,0]],
|
||||||
|
['l', [0,0]],
|
||||||
|
['l', [0,eyelash_width]],
|
||||||
|
['l', [0-box_width,0]],
|
||||||
|
['l', [0,0-eyelash_width]],
|
||||||
|
['l', [0,0]],
|
||||||
|
['l', [0-eyelash_width,0]],
|
||||||
|
['l', [eyelash_width-eyelash_measure1,0-(box_height-eyelash_measure4)]],
|
||||||
|
['l', [eyelash_measure1,0-eyelash_measure2]],
|
||||||
|
['l', [0,0-eyelash_measure1]],
|
||||||
|
['l', [0-(box_height-eyelash_measure3),0-eyelash_measure3]],
|
||||||
|
['l', [0-eyelash_measure3,0-(box_height-eyelash_measure3)]],
|
||||||
|
['l', [0-eyelash_measure1,0]],
|
||||||
|
['l', [0-eyelash_measure2,0-eyelash_measure1]],
|
||||||
|
['l', [0-(box_height-eyelash_measure4),0-(eyelash_width-eyelash_measure1)]],
|
||||||
|
['l', [0,0-(box_length-(eyelash_width*2))]],
|
||||||
|
['l', [box_height-eyelash_measure4,0-(eyelash_width-eyelash_measure1),]],
|
||||||
|
['l', [eyelash_measure2,0-eyelash_measure1]],
|
||||||
|
['l', [eyelash_measure1,0]],
|
||||||
|
['l', [eyelash_measure3,0-(box_height-eyelash_measure3)]],
|
||||||
|
['l', [(box_height-eyelash_measure3),0-eyelash_measure3]],
|
||||||
|
['l', [0,0-eyelash_measure1]],
|
||||||
|
['l', [0-eyelash_measure1,0-eyelash_measure2]],
|
||||||
|
['l', [0-(eyelash_width-eyelash_measure1),0-(box_height-eyelash_measure4)]],
|
||||||
|
['l', [eyelash_width,0]],
|
||||||
|
['Z', []]
|
||||||
|
]
|
||||||
|
line.style = line_style_cuts
|
||||||
|
|
||||||
|
#profile splits
|
||||||
|
line = group.add(inkex.PathElement(id=id_box + '-profile-splits-1'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0,eyelash_width]],
|
||||||
|
['l', [box_width,0]]
|
||||||
|
]
|
||||||
|
line.style = cleft_line_style
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_box + '-profile-splits-2'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0,(box_height+eyelash_width)]],
|
||||||
|
['l', [box_width,0]]
|
||||||
|
]
|
||||||
|
line.style = cleft_line_style
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_box + '-profile-splits-3'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0-box_height,((box_height*2)+eyelash_width)]],
|
||||||
|
['l', [box_width+(box_height*2),0]]
|
||||||
|
]
|
||||||
|
line.style = cleft_line_style
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_box + '-profile-splits-4'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0-box_height,(((box_height*2)+eyelash_width)+box_length)]],
|
||||||
|
['l', [box_width+(box_height*2),0]]
|
||||||
|
]
|
||||||
|
line.style = cleft_line_style
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_box + '-profile-splits-5'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0,(box_height*3)+box_length+eyelash_width]],
|
||||||
|
['l', [box_width,0]]
|
||||||
|
]
|
||||||
|
line.style = cleft_line_style
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_box + '-profile-splits-6'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0,(box_height*4)+box_length+eyelash_width]],
|
||||||
|
['l', [box_width,0]]
|
||||||
|
]
|
||||||
|
line.style = cleft_line_style
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_box + '-profile-splits-7'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0,eyelash_width]],
|
||||||
|
['l', [0,box_length+(box_height*4)]]
|
||||||
|
]
|
||||||
|
line.style = cleft_line_style
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_box + '-profile-splits-8'))
|
||||||
|
line.path = [
|
||||||
|
['M', [box_width,eyelash_width]],
|
||||||
|
['l', [0,box_length+(box_height*4)]]
|
||||||
|
]
|
||||||
|
line.style = cleft_line_style
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_box + '-profile-splits-9'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0-box_height,eyelash_width+(box_height*2)]],
|
||||||
|
['l', [0,box_length]]
|
||||||
|
]
|
||||||
|
line.style = cleft_line_style
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_box + '-profile-splits-10'))
|
||||||
|
line.path = [
|
||||||
|
['M', [box_width+box_height,eyelash_width+(box_height*2)]],
|
||||||
|
['l', [0,box_length]]
|
||||||
|
]
|
||||||
|
line.style = cleft_line_style
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_box + '-profile-splits-11'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0,eyelash_width+(box_height*2)]],
|
||||||
|
['l', [0-(box_height-eyelash_measure3),0-(box_height-eyelash_measure3)]]
|
||||||
|
]
|
||||||
|
line.style = cleft_line_style
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_box + '-profile-splits-12'))
|
||||||
|
line.path = [
|
||||||
|
['M', [box_width,eyelash_width+(box_height*2)]],
|
||||||
|
['l', [box_height-eyelash_measure3,0-(box_height-eyelash_measure3)]]
|
||||||
|
]
|
||||||
|
line.style = cleft_line_style
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_box + '-profile-splits-13'))
|
||||||
|
line.path = [
|
||||||
|
['M', [box_width,eyelash_width+(box_height*2)+box_length]],
|
||||||
|
['l', [box_height-eyelash_measure3,box_height-eyelash_measure3]]
|
||||||
|
]
|
||||||
|
line.style = cleft_line_style
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_box + '-profile-splits-14'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0,eyelash_width+(box_height*2)+box_length]],
|
||||||
|
['l', [0-(box_height-eyelash_measure3),box_height-eyelash_measure3]]
|
||||||
|
]
|
||||||
|
line.style = cleft_line_style
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
DoubeRailingCase().run()
|
220
extensions/fablabchemnitz/estucheria/doublerailingcase.svg
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="94mm"
|
||||||
|
height="64mm"
|
||||||
|
viewBox="0 0 94.000001 64.000001"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||||
|
sodipodi:docname="DobleBarandaLayout.svg">
|
||||||
|
<defs
|
||||||
|
id="defs2">
|
||||||
|
<rect
|
||||||
|
id="rect889"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect889-5" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect902" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect889-2" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect902-7" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect889-53" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect902-3" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect889-6" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect902-4" />
|
||||||
|
<rect
|
||||||
|
id="rect889-2-4"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect979"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
height="211mm"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-height="1018"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:cy="108.71689"
|
||||||
|
inkscape:cx="162.05938"
|
||||||
|
inkscape:zoom="1.4"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
borderopacity="1.0"
|
||||||
|
bordercolor="#666666"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
id="base" />
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
transform="translate(0,-232.99994)"
|
||||||
|
id="layer1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
inkscape:label="Capa 1">
|
||||||
|
<text
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,47.465771,176.04713)"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
id="text887"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
x="-68.892474"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">A</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
id="text887-5"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-5);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,74.101888,192.29305)"><tspan
|
||||||
|
x="-68.264088"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">L</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
id="text887-2"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-2);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,101.82825,197.6344)"><tspan
|
||||||
|
x="-68.892474"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">H</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
transform="matrix(0.27672844,0,0,0.27672844,108.8139,222.99361)"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-2-4);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
id="text887-2-3"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
x="-68.583793"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">P</tspan></tspan></text>
|
||||||
|
<g
|
||||||
|
transform="matrix(0,-0.18377696,0.20645318,0,1.7584784,284.73758)"
|
||||||
|
id="estuche-doble-baranda9828">
|
||||||
|
<path
|
||||||
|
id="estuche-doble-baranda9828-perfil-exterior"
|
||||||
|
d="m 0,0 h 210 v 15 h 15 l -13,27 -2,1 v 2 l 25,5 5,25 h 2 l 1,2 27,13 v 260 l -27,13 -1,2 h -2 l -5,25 -25,5 v 2 l 2,1 13,27 h -15 v 15 H 0 v -15 h -15 l 13,-27 2,-1 v -2 l -25,-5 -5,-25 h -2 l -1,-2 -27,-13 V 90 l 27,-13 1,-2 h 2 L -25,50 0,45 V 43 L -2,42 -15,15 H 0 Z"
|
||||||
|
style="fill:none;stroke:#ff0000;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-doble-baranda9828-perfil-hendidos-1"
|
||||||
|
d="M 0,15 H 210 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-doble-baranda9828-perfil-hendidos-2"
|
||||||
|
d="M 0,45 H 210 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-doble-baranda9828-perfil-hendidos-3"
|
||||||
|
d="M -30,75 H 240 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-doble-baranda9828-perfil-hendidos-4"
|
||||||
|
d="M -30,365 H 240 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-doble-baranda9828-perfil-hendidos-5"
|
||||||
|
d="M 0,395 H 210 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-doble-baranda9828-perfil-hendidos-6"
|
||||||
|
d="M 0,425 H 210 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-doble-baranda9828-perfil-hendidos-7"
|
||||||
|
d="M 0,15 V 425 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-doble-baranda9828-perfil-hendidos-8"
|
||||||
|
d="M 210,15 V 425 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-doble-baranda9828-perfil-hendidos-9"
|
||||||
|
d="M -30,75 V 365 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-doble-baranda9828-perfil-hendidos-10"
|
||||||
|
d="M 240,75 V 365 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-doble-baranda9828-perfil-hendidos-11"
|
||||||
|
d="M 0,75 -25,50 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-doble-baranda9828-perfil-hendidos-12"
|
||||||
|
d="M 210,75 235,50 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-doble-baranda9828-perfil-hendidos-13"
|
||||||
|
d="m 210,365 25,25 z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-doble-baranda9828-perfil-hendidos-14"
|
||||||
|
d="m 0,365 -25,25 z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 8.1 KiB |
24
extensions/fablabchemnitz/estucheria/girdle.inx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Estucheria - Girdle</name>
|
||||||
|
<id>fablabchemnitz.de.girdle</id>
|
||||||
|
<image>girdle.svg</image>
|
||||||
|
<param name="unit" gui-text="Unit:" type="optiongroup" appearance="combo">
|
||||||
|
<option translatable="no" value="mm">mm</option>
|
||||||
|
</param>
|
||||||
|
<param name="width" type="float" min="0.1" max="300.0" gui-text="A - width:">25.0</param>
|
||||||
|
<param name="depth" type="float" min="0.1" max="300.0" gui-text="L - length:">25.0</param>
|
||||||
|
<param name="height" type="float" min="0.1" max="300.0" gui-text="H - height:">25.0</param>
|
||||||
|
<param name="glue_tab" type="float" min="0.1" max="300.0" gui-text="P - Width glue tab">5.0</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz Boxes/Papercraft">
|
||||||
|
<submenu name="Paper/Cardboard Boxes" />
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">girdle.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
137
extensions/fablabchemnitz/estucheria/girdle.py
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
#! /usr/bin/env python3
|
||||||
|
#
|
||||||
|
# Este script dibuja el perfil exterior de corte la caja en un solo
|
||||||
|
# path cerrado y añade despues los otros flejes necesarios con colores
|
||||||
|
# diferentes para identificarlos.
|
||||||
|
# rojo > para cortes y perfil exterior
|
||||||
|
# azul > para hendidos
|
||||||
|
# verde > para taladros
|
||||||
|
# amarillo > medios cortes
|
||||||
|
#
|
||||||
|
# TODO:
|
||||||
|
# agregar opción de dibujo en cm/in
|
||||||
|
# mover dibujo al centro del documento
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
__version__ = "0.2"
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
|
||||||
|
class Girdle(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("--width", type=float, default=25.0, help="Ancho de la caja")
|
||||||
|
pars.add_argument("--height", type=float, default=25.0, help="Alto de la caja")
|
||||||
|
pars.add_argument("--depth", type=float, default=25.0, help="Largo de la caja")
|
||||||
|
pars.add_argument("--glue_tab", type=float, default=5.0, help="Ancho pestaña de engomado")
|
||||||
|
pars.add_argument("--unit", default="mm", help="Tipo de unidades")
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
centro_ancho_documento = self.svg.unittouu(self.document.getroot().get('width'))/2
|
||||||
|
centro_alto_documento = self.svg.unittouu(self.document.getroot().get('height'))/2
|
||||||
|
|
||||||
|
medida_pestana1=5
|
||||||
|
medida_pestana2=1
|
||||||
|
medida_pestana3=4
|
||||||
|
medida_pestana4=3
|
||||||
|
|
||||||
|
ancho_caja = self.svg.unittouu(str(self.options.width) + self.options.unit)
|
||||||
|
alto_caja = self.svg.unittouu(str(self.options.height) + self.options.unit)
|
||||||
|
largo_caja = self.svg.unittouu(str(self.options.depth) + self.options.unit)
|
||||||
|
ancho_pestana_cola = self.svg.unittouu(str(self.options.glue_tab) + self.options.unit)
|
||||||
|
|
||||||
|
if self.options.unit=="cm":
|
||||||
|
medida_pestana1=0.5
|
||||||
|
medida_pestana2=0.1
|
||||||
|
medida_pestana3=0.4
|
||||||
|
medida_pestana3=0.3
|
||||||
|
|
||||||
|
if self.options.unit=='in':
|
||||||
|
medida_pestana1=0.196
|
||||||
|
medida_pestana2=0.039
|
||||||
|
medida_pestana3=0.157
|
||||||
|
medida_pestana3=0.118
|
||||||
|
|
||||||
|
medida1_pestanas_laterales=self.svg.unittouu(str(medida_pestana1) + self.options.unit)
|
||||||
|
medida2_pestanas_laterales=self.svg.unittouu(str(medida_pestana2) + self.options.unit)
|
||||||
|
medida3_pestanas_laterales=self.svg.unittouu(str(medida_pestana3) + self.options.unit)
|
||||||
|
medida4_pestanas_laterales=self.svg.unittouu(str(medida_pestana4) + self.options.unit)
|
||||||
|
|
||||||
|
id_caja = self.svg.get_unique_id('estuche-faja')
|
||||||
|
group = self.svg.get_current_layer().add(inkex.Group(id=id_caja))
|
||||||
|
estilo_linea_cortes = {'stroke': '#FF0000', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
estilo_linea_hendidos = {'stroke': '#0000FF', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
estilo_linea_taladros = {'stroke': '#00FF00', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
estilo_linea_medioscortes = {'stroke': '#00FFFF', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
|
||||||
|
# line.path --> M = coordenadas absolutas
|
||||||
|
# line.path --> l = dibuja una linea desde el punto actual a las coordenadas especificadas
|
||||||
|
# line.path --> c = dibuja una curva beizer desde el punto actual a las coordenadas especificadas
|
||||||
|
# line.path --> q = dibuja un arco desde el punto actual a las coordenadas especificadas usando un punto como referencia
|
||||||
|
# line.path --> Z = cierra path
|
||||||
|
|
||||||
|
#Perfil Exterior de la caja
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-exterior'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0, 0]],
|
||||||
|
['l', [ancho_caja,0]],
|
||||||
|
['l', [largo_caja,0]],
|
||||||
|
['l', [ancho_caja,0]],
|
||||||
|
['l', [largo_caja,0]],
|
||||||
|
['l', [0,alto_caja]],
|
||||||
|
['l', [0-(largo_caja),0]],
|
||||||
|
['l', [0-(ancho_caja),0]],
|
||||||
|
['l', [0-largo_caja,0]],
|
||||||
|
['l', [0-ancho_caja,0]],
|
||||||
|
['l', [0-ancho_pestana_cola,0-ancho_pestana_cola]],
|
||||||
|
['l', [0,0-(alto_caja-(ancho_pestana_cola*2))]],
|
||||||
|
['Z', []]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_cortes
|
||||||
|
|
||||||
|
#Hendidos
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-1'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0,0]],
|
||||||
|
['l', [0,alto_caja]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-2'))
|
||||||
|
line.path = [
|
||||||
|
['M', [ancho_caja,0]],
|
||||||
|
['l', [0,alto_caja]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-3'))
|
||||||
|
line.path = [
|
||||||
|
['M', [ancho_caja+largo_caja,0]],
|
||||||
|
['l', [0,alto_caja]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-4'))
|
||||||
|
line.path = [
|
||||||
|
['M', [ancho_caja+ancho_caja+largo_caja,0]],
|
||||||
|
['l', [0,alto_caja]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
Girdle().run()
|
180
extensions/fablabchemnitz/estucheria/girdle.svg
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="94mm"
|
||||||
|
height="64mm"
|
||||||
|
viewBox="0 0 94.000001 64.000001"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||||
|
sodipodi:docname="FajaLayout.svg">
|
||||||
|
<defs
|
||||||
|
id="defs2">
|
||||||
|
<rect
|
||||||
|
id="rect889"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect889-5" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect902" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect889-2" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect902-7" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect889-53" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect902-3" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect889-6" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect902-4" />
|
||||||
|
<rect
|
||||||
|
id="rect889-2-4"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect979"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
height="211mm"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-height="1018"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:cy="59.842544"
|
||||||
|
inkscape:cx="-219.62908"
|
||||||
|
inkscape:zoom="0.98994949"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
borderopacity="1.0"
|
||||||
|
bordercolor="#666666"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
id="base" />
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
transform="translate(0,-232.99994)"
|
||||||
|
id="layer1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
inkscape:label="Capa 1">
|
||||||
|
<text
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,50.709976,203.54903)"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
id="text887"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
x="-68.892474"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">A</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
id="text887-5"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-5);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,70.900538,203.47943)"><tspan
|
||||||
|
x="-68.264088"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">L</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
id="text887-2"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-2);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,78.753039,174.35222)"><tspan
|
||||||
|
x="-68.892474"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">H</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,31.50749,197.62541)"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-2-4);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
id="text887-2-3"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
x="-68.583793"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">P</tspan></tspan></text>
|
||||||
|
<g
|
||||||
|
transform="matrix(0.32727879,0,0,0.40392018,6.8339186,235.01699)"
|
||||||
|
id="estuche-faja5487">
|
||||||
|
<path
|
||||||
|
id="estuche-faja5487-perfil-exterior"
|
||||||
|
d="m 0,0 h 100 30 99.75 29.75 V 150 H 229.75 130 100 0 L -15,135 V 15 Z"
|
||||||
|
style="fill:none;stroke:#ff0000;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-faja5487-perfil-hendidos-1"
|
||||||
|
d="M 0,0 V 150 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-faja5487-perfil-hendidos-2"
|
||||||
|
d="M 100,0 V 150 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-faja5487-perfil-hendidos-3"
|
||||||
|
d="M 129.75,0 V 150 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
<path
|
||||||
|
id="estuche-faja5487-perfil-hendidos-4"
|
||||||
|
d="M 229.5,0 V 150 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.264583" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 6.2 KiB |
26
extensions/fablabchemnitz/estucheria/linearcase.inx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Estucheria - Linear Case</name>
|
||||||
|
<id>fablabchemnitz.de.linearcase</id>
|
||||||
|
<image>linearcase.svg</image>
|
||||||
|
<param name="unit" gui-text="Unit:" type="optiongroup" appearance="combo">
|
||||||
|
<option translatable="no" value="mm">mm</option>
|
||||||
|
</param>
|
||||||
|
<param name="width" type="float" min="0.1" max="300.0" gui-text="A - width:">25.0</param>
|
||||||
|
<param name="depth" type="float" min="0.1" max="300.0" gui-text="L - length:">25.0</param>
|
||||||
|
<param name="height" type="float" min="0.1" max="300.0" gui-text="H - height:">25.0</param>
|
||||||
|
<param name="glue_tab" type="float" min="0.1" max="300.0" gui-text="P - Width glue tab">5.0</param>
|
||||||
|
<param name="close_tab" type="float" min="0.1" max="300.0" gui-text="C - Top side sealing flange:">5.0</param>
|
||||||
|
<param name="side_tabs" type="float" min="0.1" max="300.0" gui-text="T - Top side closure tabs:">5.0</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz Boxes/Papercraft">
|
||||||
|
<submenu name="Paper/Cardboard Boxes" />
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">linearcase.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
239
extensions/fablabchemnitz/estucheria/linearcase.py
Normal file
@ -0,0 +1,239 @@
|
|||||||
|
#! /usr/bin/env python3
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Este script dibuja el perfil exterior de corte la caja en un solo
|
||||||
|
# path cerrado y añade despues los otros flejes necesarios con colores
|
||||||
|
# diferentes para identificarlos.
|
||||||
|
# rojo > para cortes y perfil exterior
|
||||||
|
# azul > para hendidos
|
||||||
|
# verde > para taladros
|
||||||
|
# amarillo > medios cortes
|
||||||
|
#
|
||||||
|
# TODO:
|
||||||
|
# agregar opción de dibujo en cm/in
|
||||||
|
# mover dibujo al centro del documento
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
__version__ = "0.2"
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
|
||||||
|
class LinearCase(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("--width", type=float, default=25.0, help="Ancho de la caja")
|
||||||
|
pars.add_argument("--height", type=float, default=25.0, help="Alto de la caja")
|
||||||
|
pars.add_argument("--depth", type=float, default=25.0, help="Largo de la caja")
|
||||||
|
pars.add_argument("--glue_tab", type=float, default=5.0, help="Ancho pestaña de engomado")
|
||||||
|
pars.add_argument("--close_tab", type=float, default=5.0, help="Alto pestaña de cierre")
|
||||||
|
pars.add_argument("--side_tabs", type=float, default=5.0, help="Alto pestañas laterales de cierre")
|
||||||
|
pars.add_argument("--unit", default="mm", help="Tipo de unidades")
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
centro_ancho_documento = self.svg.unittouu(self.document.getroot().get('width'))/2
|
||||||
|
centro_alto_documento = self.svg.unittouu(self.document.getroot().get('height'))/2
|
||||||
|
|
||||||
|
ancho_caja = self.svg.unittouu(str(self.options.width) + self.options.unit)
|
||||||
|
alto_caja = self.svg.unittouu(str(self.options.height) + self.options.unit)
|
||||||
|
largo_caja = self.svg.unittouu(str(self.options.depth) + self.options.unit)
|
||||||
|
ancho_pestana_cola = self.svg.unittouu(str(self.options.glue_tab) + self.options.unit)
|
||||||
|
alto_pestana_cierre = self.svg.unittouu(str(self.options.close_tab) + self.options.unit)
|
||||||
|
alto_pestana = self.svg.unittouu(str(self.options.side_tabs) + self.options.unit)
|
||||||
|
|
||||||
|
if self.options.unit=="mm":
|
||||||
|
medida_pestana1=5
|
||||||
|
medida_pestana2=1
|
||||||
|
medida_pestana3=4
|
||||||
|
medida_pestana4=3
|
||||||
|
|
||||||
|
if self.options.unit=="cm":
|
||||||
|
medida_pestana1=0.5
|
||||||
|
medida_pestana2=0.1
|
||||||
|
medida_pestana3=0.4
|
||||||
|
medida_pestana4=0.3
|
||||||
|
|
||||||
|
if self.options.unit=='in':
|
||||||
|
medida_pestana1=0.196
|
||||||
|
medida_pestana2=0.039
|
||||||
|
medida_pestana3=0.157
|
||||||
|
medida_pestana4=0.118
|
||||||
|
|
||||||
|
medida1_pestanas_laterales=self.svg.unittouu(str(medida_pestana1) + self.options.unit)
|
||||||
|
medida2_pestanas_laterales=self.svg.unittouu(str(medida_pestana2) + self.options.unit)
|
||||||
|
medida3_pestanas_laterales=self.svg.unittouu(str(medida_pestana3) + self.options.unit)
|
||||||
|
medida4_pestanas_laterales=self.svg.unittouu(str(medida_pestana4) + self.options.unit)
|
||||||
|
|
||||||
|
id_caja = self.svg.get_unique_id('estuche-lineal')
|
||||||
|
group = self.svg.get_current_layer().add(inkex.Group(id=id_caja))
|
||||||
|
estilo_linea_cortes = {'stroke': '#FF0000', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
estilo_linea_hendidos = {'stroke': '#0000FF', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
estilo_linea_medioscortes = {'stroke': '#00FFFF', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
|
||||||
|
# line.path --> M = coordenadas absolutas
|
||||||
|
# line.path --> l = dibuja una linea desde el punto actual a las coordenadas especificadas
|
||||||
|
# line.path --> c = dibuja una curva beizer desde el punto actual a las coordenadas especificadas
|
||||||
|
# line.path --> q = dibuja un arco desde el punto actual a las coordenadas especificadas usando un punto como referencia
|
||||||
|
# line.path --> Z = cierra path
|
||||||
|
|
||||||
|
#Perfil Exterior de la caja
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-exterior'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0, 0]],
|
||||||
|
['l', [ancho_caja, 0]],
|
||||||
|
['l', [0,0]],
|
||||||
|
['l', [0, 0]],
|
||||||
|
['l', [0, 0-medida1_pestanas_laterales]],
|
||||||
|
['l', [medida2_pestanas_laterales, 0-medida2_pestanas_laterales]],
|
||||||
|
['l', [medida3_pestanas_laterales, 0-(alto_pestana-medida2_pestanas_laterales-medida1_pestanas_laterales)]],
|
||||||
|
['l', [(largo_caja-medida2_pestanas_laterales-medida3_pestanas_laterales-medida4_pestanas_laterales), 0]],
|
||||||
|
['l', [0,alto_pestana-medida4_pestanas_laterales]],
|
||||||
|
['l', [medida4_pestanas_laterales, medida4_pestanas_laterales]],
|
||||||
|
['l', [0, 0-largo_caja]],
|
||||||
|
['l', [0, 0]],
|
||||||
|
['q', [0,0-alto_pestana_cierre,alto_pestana_cierre, 0-alto_pestana_cierre]],
|
||||||
|
['l', [ancho_caja-(alto_pestana_cierre*2), 0]],
|
||||||
|
['q', [alto_pestana_cierre,0,alto_pestana_cierre,alto_pestana_cierre]],
|
||||||
|
['l', [0, 0]],
|
||||||
|
['l', [0, (largo_caja)]],
|
||||||
|
['l', [medida4_pestanas_laterales, 0-medida4_pestanas_laterales]],
|
||||||
|
['l', [0,0-(alto_pestana-medida4_pestanas_laterales)]],
|
||||||
|
['l', [(largo_caja-medida2_pestanas_laterales-medida3_pestanas_laterales-medida4_pestanas_laterales), 0]],
|
||||||
|
['l', [medida3_pestanas_laterales, (alto_pestana-medida2_pestanas_laterales-medida1_pestanas_laterales)]],
|
||||||
|
['l', [medida2_pestanas_laterales, medida2_pestanas_laterales]],
|
||||||
|
['l', [0, medida1_pestanas_laterales]],
|
||||||
|
['l', [0,0]],
|
||||||
|
['l', [0, alto_caja]],
|
||||||
|
['l', [0-medida4_pestanas_laterales, medida4_pestanas_laterales]],
|
||||||
|
['l', [0,(alto_pestana-medida4_pestanas_laterales)]],
|
||||||
|
['l', [0-(largo_caja-medida2_pestanas_laterales-medida3_pestanas_laterales-medida4_pestanas_laterales), 0]],
|
||||||
|
['l', [0-(medida3_pestanas_laterales), 0-(alto_pestana-medida2_pestanas_laterales-medida1_pestanas_laterales)]],
|
||||||
|
['l', [0-(medida2_pestanas_laterales), 0-(medida2_pestanas_laterales)]],
|
||||||
|
['l', [0, 0-medida1_pestanas_laterales]],
|
||||||
|
['l', [0, 0]],
|
||||||
|
['l', [0,0]],
|
||||||
|
['l', [0-ancho_caja, 0]],
|
||||||
|
['l', [0,0]],
|
||||||
|
['l', [0, 0]],
|
||||||
|
['l', [0, medida1_pestanas_laterales]],
|
||||||
|
['l', [0-medida2_pestanas_laterales, medida2_pestanas_laterales]],
|
||||||
|
['l', [0-medida3_pestanas_laterales, (alto_pestana-medida2_pestanas_laterales-medida1_pestanas_laterales)]],
|
||||||
|
['l', [0-(largo_caja-medida2_pestanas_laterales-medida3_pestanas_laterales-medida4_pestanas_laterales), 0]],
|
||||||
|
['l', [0,0-(alto_pestana-medida4_pestanas_laterales)]],
|
||||||
|
['l', [0-medida4_pestanas_laterales, 0-medida4_pestanas_laterales]],
|
||||||
|
['l', [0,0]],
|
||||||
|
['l', [0, largo_caja]],
|
||||||
|
['l', [0, 0]],
|
||||||
|
['q', [0,alto_pestana_cierre,0-alto_pestana_cierre, alto_pestana_cierre]],#
|
||||||
|
['l', [0-(ancho_caja-(alto_pestana_cierre*2)), 0]],
|
||||||
|
['q', [0-alto_pestana_cierre,0,0-alto_pestana_cierre,0-alto_pestana_cierre]],
|
||||||
|
['l', [0, 0]],
|
||||||
|
['l', [0, 0-largo_caja]],
|
||||||
|
['l', [0, 0-medida2_pestanas_laterales]],
|
||||||
|
['l', [0-ancho_pestana_cola, 0-(ancho_pestana_cola/2)]],
|
||||||
|
['l', [0, 0-(alto_caja-ancho_pestana_cola-(medida2_pestanas_laterales*2))]],
|
||||||
|
['l', [ancho_pestana_cola, 0-(ancho_pestana_cola/2)]],
|
||||||
|
['Z', []]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_cortes
|
||||||
|
|
||||||
|
#Hendidos
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-1'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0,0]],
|
||||||
|
['l', [0,alto_caja]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-2'))
|
||||||
|
line.path = [
|
||||||
|
['M', [ancho_caja,0]],
|
||||||
|
['l', [0,alto_caja]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-3'))
|
||||||
|
line.path = [
|
||||||
|
['M', [ancho_caja+largo_caja,0]],
|
||||||
|
['l', [0,alto_caja]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-4'))
|
||||||
|
line.path = [
|
||||||
|
['M', [ancho_caja+ancho_caja+largo_caja,0]],
|
||||||
|
['l', [0,alto_caja]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-5'))
|
||||||
|
line.path = [
|
||||||
|
['M', [ancho_caja,0]],
|
||||||
|
['l', [largo_caja,0]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-6'))
|
||||||
|
line.path = [
|
||||||
|
['M', [ancho_caja+largo_caja,0]],
|
||||||
|
['l', [ancho_caja,0]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-7'))
|
||||||
|
line.path = [
|
||||||
|
['M', [(ancho_caja*2)+largo_caja,0]],
|
||||||
|
['l', [largo_caja,0]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-8'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0,alto_caja]],
|
||||||
|
['l', [ancho_caja,0]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-9'))
|
||||||
|
line.path = [
|
||||||
|
['M', [ancho_caja,alto_caja]],
|
||||||
|
['l', [largo_caja,0]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-10'))
|
||||||
|
line.path = [
|
||||||
|
['M', [(ancho_caja*2)+largo_caja,alto_caja]],
|
||||||
|
['l', [largo_caja,0]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-11'))
|
||||||
|
line.path = [
|
||||||
|
['M', [ancho_caja+largo_caja,0-(largo_caja)]],
|
||||||
|
['l', [ancho_caja,0]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-12'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0,alto_caja+largo_caja]],
|
||||||
|
['l', [ancho_caja,0]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
LinearCase().run()
|
217
extensions/fablabchemnitz/estucheria/linearcase.svg
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
width="94mm"
|
||||||
|
height="64mm"
|
||||||
|
viewBox="0 0 94.000001 64.000001"
|
||||||
|
version="1.1"
|
||||||
|
id="svg8"
|
||||||
|
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||||
|
sodipodi:docname="LinealLayout.svg">
|
||||||
|
<defs
|
||||||
|
id="defs2">
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect889" />
|
||||||
|
<rect
|
||||||
|
id="rect889-5"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect902"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect889-2"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect902-7"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect889-53"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect902-3"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect889-6"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect902-4"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect889-2-4" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect979" />
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="0.7"
|
||||||
|
inkscape:cx="242.19298"
|
||||||
|
inkscape:cy="182.95136"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1018"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
height="211mm" />
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Capa 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-232.99994)">
|
||||||
|
<g
|
||||||
|
transform="translate(0.11175705,0.20100678)"
|
||||||
|
id="g2216">
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#ff0000;stroke-width:0.110345"
|
||||||
|
d="M 8.3109493,250.29248 H 37.504577 v -2.08526 l 0.417052,-0.41705 1.251155,-6.25578 h 9.592192 v 7.50693 l 1.251155,1.25116 v -12.51156 c 0,-2.08525 1.04263,-3.12788 3.127889,-3.12788 h 22.93785 c 2.08526,0 3.127889,1.04263 3.127889,3.12788 v 12.51156 l 1.251156,-1.25116 v -7.50693 h 9.592192 l 1.251155,6.25578 0.417052,0.41705 v 2.08526 29.01291 l -1.251156,1.25115 v 7.50693 h -9.592192 l -1.251155,-6.25577 -0.417052,-0.41706 v -2.08525 H 50.016131 v 2.08525 l -0.417051,0.41706 -1.251156,6.25577 h -9.592191 v -7.50693 l -1.251156,-1.25115 v 12.51155 c 0,2.08526 -1.04263,3.12789 -3.127889,3.12789 h -22.93785 c -2.0852592,0 -3.1278887,-1.04263 -3.1278887,-3.12789 v -12.51155 l -6.2557774,-2.91937 v -23.17418 z"
|
||||||
|
id="estuche-lineal2174-perfil-exterior"
|
||||||
|
sodipodi:nodetypes="ccccccccsssscccccccccccccccccccccsssscccc" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.075174"
|
||||||
|
d="m 8.3109493,250.27078 v 29.03461 z"
|
||||||
|
id="estuche-lineal2174-perfil-hendidos-1" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.075174"
|
||||||
|
d="m 37.504577,250.27078 v 29.03461 z"
|
||||||
|
id="estuche-lineal2174-perfil-hendidos-2" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.075174"
|
||||||
|
d="m 50.016131,250.27078 v 29.03461 z"
|
||||||
|
id="estuche-lineal2174-perfil-hendidos-3" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.075174"
|
||||||
|
d="m 79.209759,250.27078 v 29.03461 z"
|
||||||
|
id="estuche-lineal2174-perfil-hendidos-4" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.110345"
|
||||||
|
d="M 37.504577,250.29269 H 91.721314 Z"
|
||||||
|
id="estuche-lineal2174-perfil-hendidos-5" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.110345"
|
||||||
|
d="M 8.3109493,279.30539 H 50.016131 Z"
|
||||||
|
id="estuche-lineal2174-perfil-hendidos-6" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.110345"
|
||||||
|
d="M 79.209759,279.30539 H 91.721314 Z"
|
||||||
|
id="estuche-lineal2174-perfil-hendidos-7" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.110345"
|
||||||
|
d="M 8.3109493,291.81694 H 37.504577 Z"
|
||||||
|
id="estuche-lineal2174-perfil-hendidos-8" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.110345"
|
||||||
|
d="M 50.016131,237.56039 H 79.209759 Z"
|
||||||
|
id="estuche-lineal2174-perfil-hendidos-9" />
|
||||||
|
<text
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,49.653275,186.90606)"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
id="text887"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
x="-68.892474"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">A</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
id="text887-5"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-5);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,70.788782,187.02545)"><tspan
|
||||||
|
x="-68.264088"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">L</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
id="text887-2"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-2);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,79.397236,174.15122)"><tspan
|
||||||
|
x="-68.892474"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">H</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
id="text887-56"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-53);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,112.58601,193.71337)"><tspan
|
||||||
|
x="-68.264088"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">T</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
id="text887-9"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-6);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,49.940303,204.74843)"><tspan
|
||||||
|
x="-67.10791"
|
||||||
|
y="214.78815"><tspan
|
||||||
|
style="font-size:6.35px">C</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,31.773707,184.57322)"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-2-4);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
id="text887-2-3"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
x="-68.583793"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">P</tspan></tspan></text>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 9.1 KiB |
21
extensions/fablabchemnitz/estucheria/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Estucheria - <various>",
|
||||||
|
"id": "fablabchemnitz.de.estucheria.<various>",
|
||||||
|
"path": "estucheria",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "<various>",
|
||||||
|
"original_id": "org.inkscape.estucheria.<various>",
|
||||||
|
"license": "GNU GPL v3",
|
||||||
|
"license_url": "https://gitlab.com/aljurado/packaging-inkscape-extensions/-/blob/master/LICENSE",
|
||||||
|
"comment": "",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/estucheria",
|
||||||
|
"fork_url": "https://gitlab.com/aljurado/packaging-inkscape-extensions",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Estucheria",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"gitlab.com/aljurado",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
26
extensions/fablabchemnitz/estucheria/swissbottomcase.inx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Estucheria - Swiss Bottom Case</name>
|
||||||
|
<id>fablabchemnitz.de.swissbottomcase</id>
|
||||||
|
<image>swissbottomcase.svg</image>
|
||||||
|
<param name="unit" gui-text="Unit:" type="optiongroup" appearance="combo">
|
||||||
|
<option translatable="no" value="mm">mm</option>
|
||||||
|
</param>
|
||||||
|
<param name="width" type="float" min="0.1" max="300.0" gui-text="A - width:">25.0</param>
|
||||||
|
<param name="depth" type="float" min="0.1" max="300.0" gui-text="L - length:">25.0</param>
|
||||||
|
<param name="height" type="float" min="0.1" max="300.0" gui-text="H - height:">25.0</param>
|
||||||
|
<param name="glue_tab" type="float" min="0.1" max="300.0" gui-text="P - Width glue tab">5.0</param>
|
||||||
|
<param name="close_tab" type="float" min="0.1" max="300.0" gui-text="C - Top side sealing flange:">5.0</param>
|
||||||
|
<param name="side_tabs" type="float" min="0.1" max="300.0" gui-text="T - Top side closure tabs:">5.0</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz Boxes/Papercraft">
|
||||||
|
<submenu name="Paper/Cardboard Boxes" />
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">swissbottomcase.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
218
extensions/fablabchemnitz/estucheria/swissbottomcase.py
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
#! /usr/bin/env python3
|
||||||
|
#
|
||||||
|
# Este script dibuja el perfil exterior de corte la caja en un solo
|
||||||
|
# path cerrado y añade despues los otros flejes necesarios con colores
|
||||||
|
# diferentes para identificarlos.
|
||||||
|
# rojo > para cortes y perfil exterior
|
||||||
|
# azul > para hendidos
|
||||||
|
# verde > para taladros
|
||||||
|
# amarillo > medios cortes
|
||||||
|
#
|
||||||
|
# TODO:
|
||||||
|
# agregar opción de dibujo en cm/in
|
||||||
|
# mover dibujo al centro del documento.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
__version__ = "0.2"
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
|
||||||
|
class SwissBottomCase(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("--width", type=float, default=25.0, help="Ancho de la caja")
|
||||||
|
pars.add_argument("--height", type=float, default=25.0, help="Alto de la caja")
|
||||||
|
pars.add_argument("--depth", type=float, default=25.0, help="Largo de la caja")
|
||||||
|
pars.add_argument("--glue_tab", type=float, default=5.0, help="Ancho pestaña de engomado")
|
||||||
|
pars.add_argument("--close_tab", type=float, default=5.0, help="Alto pestaña de cierre")
|
||||||
|
pars.add_argument("--side_tabs", type=float, default=5.0, help="Alto pestañas laterales de cierre")
|
||||||
|
pars.add_argument("--unit", default="mm", help="Tipo de unidades")
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
centro_ancho_documento = self.svg.unittouu(self.document.getroot().get('width'))/2
|
||||||
|
centro_alto_documento = self.svg.unittouu(self.document.getroot().get('height'))/2
|
||||||
|
|
||||||
|
medida_pestana1=5
|
||||||
|
medida_pestana2=1
|
||||||
|
medida_pestana3=4
|
||||||
|
medida_pestana4=3
|
||||||
|
medida_pestana5=10
|
||||||
|
|
||||||
|
ancho_caja = self.svg.unittouu(str(self.options.width) + self.options.unit)
|
||||||
|
alto_caja = self.svg.unittouu(str(self.options.height) + self.options.unit)
|
||||||
|
largo_caja = self.svg.unittouu(str(self.options.depth) + self.options.unit)
|
||||||
|
ancho_pestana_cola = self.svg.unittouu(str(self.options.glue_tab) + self.options.unit)
|
||||||
|
alto_pestana_cierre = self.svg.unittouu(str(self.options.close_tab) + self.options.unit)
|
||||||
|
alto_pestana = self.svg.unittouu(str(self.options.side_tabs) + self.options.unit)
|
||||||
|
|
||||||
|
if self.options.unit=="cm":
|
||||||
|
medida_pestana1=0.5
|
||||||
|
medida_pestana2=0.1
|
||||||
|
medida_pestana3=0.4
|
||||||
|
medida_pestana3=0.3
|
||||||
|
medida_pestana5=1.0
|
||||||
|
|
||||||
|
if self.options.unit=="in":
|
||||||
|
medida_pestana1=0.196
|
||||||
|
medida_pestana2=0.039
|
||||||
|
medida_pestana3=0.157
|
||||||
|
medida_pestana3=0.118
|
||||||
|
medida_pestana5=0.393
|
||||||
|
|
||||||
|
medida1_pestanas_laterales=self.svg.unittouu(str(medida_pestana1) + self.options.unit)
|
||||||
|
medida2_pestanas_laterales=self.svg.unittouu(str(medida_pestana2) + self.options.unit)
|
||||||
|
medida3_pestanas_laterales=self.svg.unittouu(str(medida_pestana3) + self.options.unit)
|
||||||
|
medida4_pestanas_laterales=self.svg.unittouu(str(medida_pestana4) + self.options.unit)
|
||||||
|
medida1_pestanas_cierre=self.svg.unittouu(str(medida_pestana5) + self.options.unit)
|
||||||
|
|
||||||
|
id_caja = self.svg.get_unique_id('estuche-fondosuizo')
|
||||||
|
group = self.svg.get_current_layer().add(inkex.Group(id=id_caja))
|
||||||
|
estilo_linea_cortes = {'stroke': '#FF0000', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
estilo_linea_hendidos = {'stroke': '#0000FF', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
estilo_linea_taladros = {'stroke': '#00FF00', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
estilo_linea_medioscortes = {'stroke': '#00FFFF', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
|
||||||
|
# line.path --> M = coordenadas absolutas
|
||||||
|
# line.path --> l = dibuja una linea desde el punto actual a las coordenadas especificadas
|
||||||
|
# line.path --> c = dibuja una curva beizer desde el punto actual a las coordenadas especificadas
|
||||||
|
# line.path --> q = dibuja un arco desde el punto actual a las coordenadas especificadas usando un punto como referencia
|
||||||
|
# line.path --> Z = cierra path
|
||||||
|
|
||||||
|
#Perfil Exterior de la caja
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-exterior'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0, 0]],
|
||||||
|
['l', [ancho_caja, 0]],
|
||||||
|
['l', [0,0]],
|
||||||
|
['l', [0, 0]],
|
||||||
|
['l', [0, 0-medida1_pestanas_laterales]],
|
||||||
|
['l', [medida2_pestanas_laterales, 0-medida2_pestanas_laterales]],
|
||||||
|
['l', [medida3_pestanas_laterales, 0-(alto_pestana-medida2_pestanas_laterales-medida1_pestanas_laterales)]],
|
||||||
|
['l', [(largo_caja-medida2_pestanas_laterales-medida3_pestanas_laterales-medida4_pestanas_laterales), 0]],
|
||||||
|
['l', [0,alto_pestana-medida4_pestanas_laterales]],
|
||||||
|
['l', [medida4_pestanas_laterales, medida4_pestanas_laterales]],
|
||||||
|
['l', [0, 0-largo_caja]],
|
||||||
|
['l', [0, 0]],
|
||||||
|
['q', [0,0-alto_pestana_cierre,alto_pestana_cierre, 0-alto_pestana_cierre]],
|
||||||
|
['l', [ancho_caja-(alto_pestana_cierre*2), 0]],
|
||||||
|
['q', [alto_pestana_cierre,0,alto_pestana_cierre,alto_pestana_cierre]],
|
||||||
|
['l', [0, 0]],
|
||||||
|
['l', [0, (largo_caja)]],
|
||||||
|
['l', [medida4_pestanas_laterales, 0-medida4_pestanas_laterales]],
|
||||||
|
['l', [0,0-(alto_pestana-medida4_pestanas_laterales)]],
|
||||||
|
['l', [(largo_caja-medida2_pestanas_laterales-medida3_pestanas_laterales-medida4_pestanas_laterales), 0]],
|
||||||
|
['l', [medida3_pestanas_laterales, (alto_pestana-medida2_pestanas_laterales-medida1_pestanas_laterales)]],
|
||||||
|
['l', [medida2_pestanas_laterales, medida2_pestanas_laterales]],
|
||||||
|
['l', [0, medida1_pestanas_laterales]],
|
||||||
|
['l', [0,0]],
|
||||||
|
['l', [0, alto_caja]],
|
||||||
|
['l', [0,(largo_caja*0.5)+medida1_pestanas_cierre]],
|
||||||
|
['l', [0-(largo_caja)*0.5,0]],
|
||||||
|
['l', [0,0-medida1_pestanas_cierre]],
|
||||||
|
['l', [0-(largo_caja)*0.5,0-((largo_caja)*0.5)]],
|
||||||
|
['l', [0,(largo_caja*0.5)+medida1_pestanas_cierre]],
|
||||||
|
['l', [0-(ancho_caja)*0.25,0]],
|
||||||
|
['l', [0, 0-medida1_pestanas_cierre]],
|
||||||
|
['l', [0-(ancho_caja)*0.5,0]],
|
||||||
|
['l', [0, medida1_pestanas_cierre]],
|
||||||
|
['l', [0-(ancho_caja)*0.25,0]],
|
||||||
|
['l', [0,0-((largo_caja*0.5)+medida1_pestanas_cierre)]],
|
||||||
|
['l', [0-(largo_caja)*0.5,((largo_caja*0.5))]],
|
||||||
|
['l', [0,medida1_pestanas_cierre]],
|
||||||
|
['l', [0-(largo_caja)*0.5,0]],
|
||||||
|
['l', [0,0-((largo_caja*0.5)+medida1_pestanas_cierre)]],
|
||||||
|
['l', [0-ancho_caja*0.25,(largo_caja*0.5)]],
|
||||||
|
['l', [0,medida1_pestanas_cierre]],
|
||||||
|
['l', [0-ancho_caja*0.5,0]],
|
||||||
|
['l', [0,0-medida1_pestanas_cierre]],
|
||||||
|
['l', [0-ancho_caja*0.25,0-(largo_caja*0.5)]],
|
||||||
|
['l', [0, 0]],
|
||||||
|
['l', [0, 0]],
|
||||||
|
['l', [0, 0-medida2_pestanas_laterales]],
|
||||||
|
['l', [0-ancho_pestana_cola, 0-(ancho_pestana_cola/2)]],
|
||||||
|
['l', [0, 0-(alto_caja-ancho_pestana_cola-(medida2_pestanas_laterales*2))]],
|
||||||
|
['l', [ancho_pestana_cola, 0-(ancho_pestana_cola/2)]],
|
||||||
|
['Z', []]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_cortes
|
||||||
|
|
||||||
|
#Hendidos
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-1'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0,0]],
|
||||||
|
['l', [0,alto_caja]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-2'))
|
||||||
|
line.path = [
|
||||||
|
['M', [ancho_caja,0]],
|
||||||
|
['l', [0,alto_caja]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-3'))
|
||||||
|
line.path = [
|
||||||
|
['M', [ancho_caja+largo_caja,0]],
|
||||||
|
['l', [0,alto_caja]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-4'))
|
||||||
|
line.path = [
|
||||||
|
['M', [ancho_caja+ancho_caja+largo_caja,0]],
|
||||||
|
['l', [0,alto_caja]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-5'))
|
||||||
|
line.path = [
|
||||||
|
['M', [ancho_caja,0]],
|
||||||
|
['l', [largo_caja,0]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-6'))
|
||||||
|
line.path = [
|
||||||
|
['M', [largo_caja+ancho_caja,0]],
|
||||||
|
['l', [ancho_caja,0]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-7'))
|
||||||
|
line.path = [
|
||||||
|
['M', [(ancho_caja*2)+largo_caja,0]],
|
||||||
|
['l', [largo_caja,0]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-8'))
|
||||||
|
line.path = [
|
||||||
|
['M', [ancho_caja+largo_caja,0-largo_caja]],
|
||||||
|
['l', [ancho_caja,0]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
line = group.add(inkex.PathElement(id=id_caja + '-perfil-hendidos-9'))
|
||||||
|
line.path = [
|
||||||
|
['M', [0,alto_caja]],
|
||||||
|
['l', [(ancho_caja+largo_caja)*2,0]]
|
||||||
|
]
|
||||||
|
line.style = estilo_linea_hendidos
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
SwissBottomCase().run()
|
232
extensions/fablabchemnitz/estucheria/swissbottomcase.svg
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
sodipodi:docname="FondoSuizoLayout.svg"
|
||||||
|
inkscape:version="1.0 (4035a4fb49, 2020-05-01)"
|
||||||
|
id="svg8"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 94.000001 64.000001"
|
||||||
|
height="64mm"
|
||||||
|
width="94mm">
|
||||||
|
<defs
|
||||||
|
id="defs2">
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect889" />
|
||||||
|
<rect
|
||||||
|
id="rect889-5"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect902"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect889-2"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect902-7"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect889-53"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect902-3"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect889-6"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect902-4"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect889-2-4" />
|
||||||
|
<rect
|
||||||
|
x="-76.706352"
|
||||||
|
y="209.06828"
|
||||||
|
width="23.786987"
|
||||||
|
height="31.003265"
|
||||||
|
id="rect979" />
|
||||||
|
<rect
|
||||||
|
id="rect889-53-2"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect2158"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect889-6-3"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
<rect
|
||||||
|
id="rect2161"
|
||||||
|
height="31.003265"
|
||||||
|
width="23.786987"
|
||||||
|
y="209.06828"
|
||||||
|
x="-76.706352" />
|
||||||
|
</defs>
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="1.4"
|
||||||
|
inkscape:cx="49.930469"
|
||||||
|
inkscape:cy="118.23988"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
inkscape:document-rotation="0"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1018"
|
||||||
|
inkscape:window-x="-8"
|
||||||
|
inkscape:window-y="-8"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
height="211mm" />
|
||||||
|
<metadata
|
||||||
|
id="metadata5">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<g
|
||||||
|
inkscape:label="Capa 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-232.99994)">
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
id="text887"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,43.528428,194.28861)"><tspan
|
||||||
|
x="-68.892474"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">A</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,66.364823,194.408)"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-5);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
id="text887-5"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
x="-68.264088"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">L</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
transform="matrix(0.41705182,0,0,0.41705182,78.375063,181.34478)"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-2);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
id="text887-2"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
x="-68.892474"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">H</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
xml:space="preserve"
|
||||||
|
id="text887-2-3"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-2-4);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
transform="matrix(0.30011567,0,0,0.30011567,23.016641,217.4035)"><tspan
|
||||||
|
x="-68.583793"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">P</tspan></tspan></text>
|
||||||
|
<text
|
||||||
|
transform="matrix(0.41705181,0,0,0.41705181,104.8736,154.35938)"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-53-2);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
id="text887-56"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
x="-68.264088"
|
||||||
|
y="219.23689"><tspan
|
||||||
|
style="font-size:11.2889px">T</tspan></tspan></text>
|
||||||
|
<path
|
||||||
|
id="estuche-fondosuizo2763-perfil-exterior"
|
||||||
|
d="M 5.5892335,251.95808 H 31.266128 v -1.14916 l 0.256769,-0.22983 0.770307,-12.41096 h 16.176444 v 13.10045 l 0.770307,0.6895 v -16.08827 q 0,-1.72375 1.925767,-1.72375 h 21.825361 q 1.925767,0 1.925767,1.72375 v 16.08827 l 0.770307,-0.6895 v -13.10045 h 16.176444 l 0.770307,12.41096 0.256768,0.22983 v 1.14916 34.47487 8.04414 h -8.986913 v -2.29833 l -8.986913,-5.74581 v 10.13561 h -6.419224 v -2.29832 H 55.659179 v 2.29832 h -6.419224 v -10.13561 l -8.986913,5.74581 v 2.29833 h -8.986914 v -8.04414 l -6.419223,8.04414 v 2.29832 H 12.008457 v -2.29832 l -6.4192235,-8.04414 -3.8515343,-3.44749 v -29.41855 z"
|
||||||
|
style="fill:none;stroke:#ff0000;stroke-width:0.0642745" />
|
||||||
|
<path
|
||||||
|
id="estuche-fondosuizo2763-perfil-hendidos-1"
|
||||||
|
d="m 5.5892335,251.95808 v 34.47487 z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.0642745" />
|
||||||
|
<path
|
||||||
|
id="estuche-fondosuizo2763-perfil-hendidos-2"
|
||||||
|
d="m 31.266128,251.95808 v 34.47487 z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.0642745" />
|
||||||
|
<path
|
||||||
|
id="estuche-fondosuizo2763-perfil-hendidos-3"
|
||||||
|
d="m 49.239955,251.95808 v 34.47487 z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.0642745" />
|
||||||
|
<path
|
||||||
|
id="estuche-fondosuizo2763-perfil-hendidos-4"
|
||||||
|
d="m 74.91685,251.95808 v 34.47487 z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.0642745" />
|
||||||
|
<path
|
||||||
|
id="estuche-fondosuizo2763-perfil-hendidos-5"
|
||||||
|
d="M 31.266128,251.95808 H 92.890676 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.0642745" />
|
||||||
|
<path
|
||||||
|
id="estuche-fondosuizo2763-perfil-hendidos-6"
|
||||||
|
d="M 5.5892335,286.43295 H 92.890676 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.0642745" />
|
||||||
|
<path
|
||||||
|
id="estuche-fondosuizo2763-perfil-hendidos-7"
|
||||||
|
d="M 74.91685,286.43295 H 92.890676 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.0642745" />
|
||||||
|
<path
|
||||||
|
id="estuche-fondosuizo2763-perfil-hendidos-8"
|
||||||
|
d="M 49.239955,236.92815 H 74.91685 Z"
|
||||||
|
style="fill:none;stroke:#0000ff;stroke-width:0.0642745" />
|
||||||
|
<text
|
||||||
|
transform="matrix(0.41705181,0,0,0.41705181,89.096941,146.87361)"
|
||||||
|
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:3.88056px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold';text-align:center;letter-spacing:0px;word-spacing:0px;white-space:pre;shape-inside:url(#rect889-6-3);fill:#000000;fill-opacity:1;stroke:none;"
|
||||||
|
id="text887-9"
|
||||||
|
xml:space="preserve"><tspan
|
||||||
|
x="-67.10791"
|
||||||
|
y="214.78815"><tspan
|
||||||
|
style="font-size:6.35px">C</tspan></tspan></text>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 9.0 KiB |
@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Fibonacci Pattern</name>
|
||||||
|
<id>fablabchemnitz.de.fibonacci_pattern</id>
|
||||||
|
<param name="FirstRadius" type="int" min="1" max="500" gui-text="First Radius:">2</param>
|
||||||
|
<param name="LastRadius" type="int" min="1" max="500" gui-text="Last Radius:">5</param>
|
||||||
|
<param name="NumberOfNodes" type="int" min="200" max="2000" gui-text="Number of Nodes:">400</param>
|
||||||
|
<param name="SpreadFactor" type="int" min="1" max="100" gui-text="Spread Factor:">20</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">fibonacci_pattern.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
@ -0,0 +1,86 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
'''
|
||||||
|
Copyright (C) 2015-2015 Carlos Mostek carlosmostek@gmail.com
|
||||||
|
|
||||||
|
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 math
|
||||||
|
import inkex
|
||||||
|
from lxml import etree
|
||||||
|
|
||||||
|
# This is basically the draw method from the help guides for inkscape
|
||||||
|
def draw_SVG_ellipse(rx, ry, cx, cy, parent, start_end=(0,2*math.pi),transform='' ):
|
||||||
|
|
||||||
|
style = { 'stroke' : '#000000',
|
||||||
|
'stroke-width' : '1',
|
||||||
|
'fill' : 'none' }
|
||||||
|
ell_attribs = {'style':str(inkex.Style(style)),
|
||||||
|
inkex.addNS('cx','sodipodi') :str(cx),
|
||||||
|
inkex.addNS('cy','sodipodi') :str(cy),
|
||||||
|
inkex.addNS('rx','sodipodi') :str(rx),
|
||||||
|
inkex.addNS('ry','sodipodi') :str(ry),
|
||||||
|
inkex.addNS('start','sodipodi') :str(start_end[0]),
|
||||||
|
inkex.addNS('end','sodipodi') :str(start_end[1]),
|
||||||
|
inkex.addNS('open','sodipodi') :'true', #all ellipse sectors we will draw are open
|
||||||
|
inkex.addNS('type','sodipodi') :'arc',
|
||||||
|
'transform' :transform
|
||||||
|
}
|
||||||
|
ell = etree.SubElement(parent, inkex.addNS('path','svg'), ell_attribs )
|
||||||
|
|
||||||
|
# This is the workhorse, it draws the circle based on which node number
|
||||||
|
def drawKthCircle(k,firstRadius,lastRadius,numNodes,spreadFactor,parent):
|
||||||
|
# Use golden circle phi
|
||||||
|
phi = (math.sqrt(5) - 1)/2
|
||||||
|
|
||||||
|
# Calculate the node radius
|
||||||
|
growth = lastRadius - firstRadius
|
||||||
|
nodeRadius = firstRadius + growth*float(k - 1)/float(numNodes)
|
||||||
|
|
||||||
|
# Calculate X and Y from theta = 2 pi phi k and radius = sqrt(k)
|
||||||
|
r = spreadFactor * math.sqrt(k)
|
||||||
|
theta = 2*math.pi*phi*k
|
||||||
|
|
||||||
|
# use simple trig to get cx and cy
|
||||||
|
x = r * math.cos(theta)
|
||||||
|
y = r * math.sin(theta)
|
||||||
|
|
||||||
|
# Add the px to the size
|
||||||
|
nodeRadiusTxt = "%spx"%nodeRadius
|
||||||
|
|
||||||
|
# Draw the node
|
||||||
|
draw_SVG_ellipse(nodeRadiusTxt,nodeRadiusTxt,x,y,parent)
|
||||||
|
|
||||||
|
|
||||||
|
class FibonacciPattern(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
pars.add_argument("-f", "--FirstRadius", type=int, default="5", help="The radius of the first layer of circles in pixels.")
|
||||||
|
pars.add_argument("-l", "--LastRadius", type=int, default="10", help="The radius of the last layer of circles in pixels.")
|
||||||
|
pars.add_argument("-n", "--NumberOfNodes", type=int, default="5", help="The number of layers in the fibonacci spiral")
|
||||||
|
pars.add_argument("-s", "--SpreadFactor",type=int, default="10", help="This will create a larger spread between the nodes from the center.")
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
# Foreach Node
|
||||||
|
for k in range(1,self.options.NumberOfNodes):
|
||||||
|
# Draw the circle
|
||||||
|
drawKthCircle(k,
|
||||||
|
self.options.FirstRadius,
|
||||||
|
self.options.LastRadius,
|
||||||
|
self.options.NumberOfNodes,
|
||||||
|
self.options.SpreadFactor,
|
||||||
|
self.svg.get_current_layer())
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
FibonacciPattern().run()
|
21
extensions/fablabchemnitz/fibonacci_pattern/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Fibonacci Pattern",
|
||||||
|
"id": "fablabchemnitz.de.fibonacci_pattern",
|
||||||
|
"path": "fibonacci_pattern",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "Beauty Full Day Fibonacci Pattern",
|
||||||
|
"original_id": "com.beautyfullday.inkscape.fibonaccipattern",
|
||||||
|
"license": "GNU GPL v2",
|
||||||
|
"license_url": "https://github.com/mostekcm/beautyfullday_fibonaccipattern/blob/master/render_fibonaccipattern.py",
|
||||||
|
"comment": "ported to Inkscape v1 by Mario Voigt",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/fibonacci_pattern",
|
||||||
|
"fork_url": "https://github.com/mostekcm/beautyfullday_fibonaccipattern",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Fibonacci+Pattern",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"github.com/mostekcm",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
27
extensions/fablabchemnitz/flash_burst/flash_burst.inx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Flash Burst</name>
|
||||||
|
<id>fablabchemnitz.de.flash_burst</id>
|
||||||
|
<param name="r1" type="int" min="1" max="20" gui-text="Radius - inner (min)">10</param>
|
||||||
|
<param name="r2" type="int" min="1" max="20" gui-text="Radius - inner (max)">10</param>
|
||||||
|
<separator />
|
||||||
|
<param name="R1" type="int" min="200" max="500" gui-text="Radius - outer (min)">350</param>
|
||||||
|
<param name="R2" type="int" min="200" max="500" gui-text="Radius - outer (max)">350</param>
|
||||||
|
<separator />
|
||||||
|
<param name="a1" type="int" min="1" max="45" gui-text="Angle (min)">5</param>
|
||||||
|
<param name="a2" type="int" min="1" max="45" gui-text="Angle (max)">10</param>
|
||||||
|
<separator />
|
||||||
|
<param name="ad1" type="int" min="1" max="10" gui-text="Angle delta (min)">3</param>
|
||||||
|
<param name="ad2" type="int" min="1" max="10" gui-text="Angle delta (max)">5</param>
|
||||||
|
<effect>
|
||||||
|
<object-type>all</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz Shape Generators">
|
||||||
|
<submenu name="Streaks And Blobs" />
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">flash_burst.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
105
extensions/fablabchemnitz/flash_burst/flash_burst.py
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# Copyright (C) 2021 Stefan Benediktsson
|
||||||
|
#
|
||||||
|
# GNU Affero General Public License v3.0
|
||||||
|
# Permissions of this strongest copyleft license are
|
||||||
|
# conditioned on making available complete source code
|
||||||
|
# of licensed works and modifications, which include
|
||||||
|
# larger works using a licensed work, under the same
|
||||||
|
# license. Copyright and license notices must be preserved.
|
||||||
|
# Contributors provide an express grant of patent rights.
|
||||||
|
# When a modified version is used to provide a service
|
||||||
|
# over a network, the complete source code of the modified
|
||||||
|
# version must be made available.
|
||||||
|
"""
|
||||||
|
Generate a FlashBurst as SVG.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import random
|
||||||
|
from math import acos, cos, radians, sin, sqrt, tan
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
|
||||||
|
|
||||||
|
def points_to_svgd(p):
|
||||||
|
"""
|
||||||
|
p: list of 2 tuples (x, y coordinates)
|
||||||
|
"""
|
||||||
|
f = p[0]
|
||||||
|
p = p[1:]
|
||||||
|
svgd = 'M{:.3f},{:.3f}'.format(f[0], f[1])
|
||||||
|
for x in p:
|
||||||
|
svgd += 'L{:.3f},{:.3f}'.format(x[0], x[1])
|
||||||
|
svgd +='Z'
|
||||||
|
return svgd
|
||||||
|
|
||||||
|
def checkSize(a, b):
|
||||||
|
if a >= b:
|
||||||
|
return b, a
|
||||||
|
else:
|
||||||
|
return a, b
|
||||||
|
|
||||||
|
class FlashBurst(inkex.GenerateExtension):
|
||||||
|
container_label = 'Rendered FlashBurst'
|
||||||
|
def add_arguments(self, pars):
|
||||||
|
|
||||||
|
pars.add_argument("--r1", type=int, default=20, help="Inner Radius min")
|
||||||
|
pars.add_argument("--r2", type=int, default=20, help="Inner Radius max")
|
||||||
|
pars.add_argument("--R1", type=int, default=350, help="Outer Radius min")
|
||||||
|
pars.add_argument("--R2", type=int, default=350, help="Outer Radius min")
|
||||||
|
pars.add_argument("--a1", type=int, default=5, help="Angle min")
|
||||||
|
pars.add_argument("--a2", type=int, default=7, help="Angle max")
|
||||||
|
pars.add_argument("--ad1", type=int, default=3, help="Angle delta min")
|
||||||
|
pars.add_argument("--ad2", type=int, default=2, help="Angle delta max")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def generate(self):
|
||||||
|
r1 = self.options.r1
|
||||||
|
r2 = self.options.r2
|
||||||
|
R1 = self.options.R1
|
||||||
|
R2 = self.options.R2
|
||||||
|
a1 = self.options.a1
|
||||||
|
a2 = self.options.a2
|
||||||
|
ad1 = self.options.ad1
|
||||||
|
ad2 = self.options.ad2
|
||||||
|
|
||||||
|
# generate points: list of (x, y) pairs
|
||||||
|
|
||||||
|
r1, r2 = checkSize(r1, r2)
|
||||||
|
R1, R2 = checkSize(R1, R2)
|
||||||
|
a1, a2 = checkSize(a1, a2)
|
||||||
|
ad1, ad2 = checkSize(ad1, ad2)
|
||||||
|
|
||||||
|
a = 0
|
||||||
|
oX = 0
|
||||||
|
oY = 0
|
||||||
|
style = {'stroke': '#000000', 'fill': '#000000', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||||
|
|
||||||
|
while a < 360:
|
||||||
|
a = a+random.randint(a1,a2)
|
||||||
|
dI = random.randint(r1,r2)
|
||||||
|
dO = random.randint(R1,R2)
|
||||||
|
ad = random.randint(ad1,ad2)
|
||||||
|
|
||||||
|
x0 = cos(radians(a)) * dI
|
||||||
|
y0 = sin(radians(a)) * dI
|
||||||
|
|
||||||
|
x10 = cos(radians(a-ad/2)) * dO
|
||||||
|
y10 = sin(radians(a-ad/2)) * dO
|
||||||
|
|
||||||
|
x11 = cos(radians(a+ad/2)) * dO
|
||||||
|
y11 = sin(radians(a+ad/2)) * dO
|
||||||
|
|
||||||
|
points = []
|
||||||
|
points.append((x0, y0))
|
||||||
|
points.append((x10,y10))
|
||||||
|
points.append((x11,y11))
|
||||||
|
path = points_to_svgd(points)
|
||||||
|
|
||||||
|
yield inkex.PathElement(style=str(inkex.Style(style)), d=str(path))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
FlashBurst().run()
|
||||||
|
|
21
extensions/fablabchemnitz/flash_burst/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Flash Burst",
|
||||||
|
"id": "fablabchemnitz.de.flash_burst",
|
||||||
|
"path": "flash_burst",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "FlashBurst",
|
||||||
|
"original_id": "hipix.FlashBurst",
|
||||||
|
"license": "GNU AGPL v3",
|
||||||
|
"license_url": "https://github.com/curiousmaster/hipix_inkscape_extensions/blob/main/LICENSE",
|
||||||
|
"comment": "",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/flash_burst",
|
||||||
|
"fork_url": "https://github.com/curiousmaster/hipix_inkscape_extensions",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Flash+Burst",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"github.com/curiousmaster",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
16
extensions/fablabchemnitz/flevobezier/flevobezier.inx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Flevobézier</name>
|
||||||
|
<id>fablabchemnitz.de.flevobezier</id>
|
||||||
|
<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">flevobezier.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
241
extensions/fablabchemnitz/flevobezier/flevobezier.py
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Flevobezier: an Inkscape extension fitting Bezier curves
|
||||||
|
# Parcly Taxel / Jeremy Tan, 2019
|
||||||
|
# https://gitlab.com/parclytaxel
|
||||||
|
|
||||||
|
from __future__ import division
|
||||||
|
from math import *
|
||||||
|
from inkex.bezier import bezierpointatt
|
||||||
|
import inkex
|
||||||
|
import gettext
|
||||||
|
from inkex.paths import Path
|
||||||
|
import sys
|
||||||
|
def pout(t): sys.exit((gettext.gettext(t)))
|
||||||
|
|
||||||
|
class FlevoBezier(inkex.EffectExtension):
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
if len(self.svg.selected) == 0: pout("Please select at least one path.")
|
||||||
|
for obj in self.svg.selected: # The objects are the paths, which may be compound
|
||||||
|
curr = self.svg.selected[obj]
|
||||||
|
raw = Path(curr.get("d")).to_arrays()
|
||||||
|
subpaths, prev = [], 0
|
||||||
|
for i in range(len(raw)): # Breaks compound paths into simple paths
|
||||||
|
if raw[i][0] == 'M' and i != 0:
|
||||||
|
subpaths.append(raw[prev:i])
|
||||||
|
prev = i
|
||||||
|
subpaths.append(raw[prev:])
|
||||||
|
|
||||||
|
output = ""
|
||||||
|
for simpath in subpaths:
|
||||||
|
closed = False
|
||||||
|
if simpath[-1][0] == 'Z':
|
||||||
|
closed = True
|
||||||
|
if simpath[-2][0] == 'L': simpath[-1][1] = simpath[0][1]
|
||||||
|
else: simpath.pop()
|
||||||
|
#nodes = [node(simpath[i][1][-2:]) for i in range(len(simpath))]
|
||||||
|
nodes = []
|
||||||
|
for i in range(len(simpath)):
|
||||||
|
if simpath[i][0] == 'V': # vertical and horizontal lines only have one point in args, but 2 are required
|
||||||
|
#inkex.utils.debug(simpath[i][0])
|
||||||
|
simpath[i][0]='L' #overwrite V with regular L command
|
||||||
|
add=simpath[i-1][1][0] #read the X value from previous segment
|
||||||
|
simpath[i][1].append(simpath[i][1][0]) #add the second (missing) argument by taking argument from previous segment
|
||||||
|
simpath[i][1][0]=add #replace with recent X after Y was appended
|
||||||
|
if simpath[i][0] == 'H': # vertical and horizontal lines only have one point in args, but 2 are required
|
||||||
|
#inkex.utils.debug(simpath[i][0])
|
||||||
|
simpath[i][0]='L' #overwrite H with regular L command
|
||||||
|
simpath[i][1].append(simpath[i-1][1][1]) #add the second (missing) argument by taking argument from previous segment
|
||||||
|
#inkex.utils.debug(simpath[i])
|
||||||
|
nodes.append(node(simpath[i][1][-2:]))
|
||||||
|
output += flevobezier(nodes, closed)
|
||||||
|
curr.set("d", output)
|
||||||
|
|
||||||
|
# The main algorithm! Yay!
|
||||||
|
def flevobezier(points, z):
|
||||||
|
if len(points) < 2: pout("A curve isn't a point, silly!")
|
||||||
|
res = []
|
||||||
|
prevtrail, trail, lead, window = 0, 0, 1, points[:2] # Start with first two points
|
||||||
|
maybeover = False # Over by error followed by over by angle -> backup
|
||||||
|
curcurve = [window[0], slide(window[0], window[1], 1 / 3), slide(window[0], window[1], 2 / 3), window[1]] # Current working curve, always a 4-list
|
||||||
|
while lead + 1 < len(points):
|
||||||
|
lead += 1
|
||||||
|
window = points[trail:lead + 1] # Extend the window one more node
|
||||||
|
v = window[-3] - window[-2]
|
||||||
|
w = window[-1] - window[-2]
|
||||||
|
try:
|
||||||
|
dist(v) / dist(w)
|
||||||
|
except ZeroDivisionError as e:
|
||||||
|
pout("Division by zero. Check if your path contains duplicate handles.")
|
||||||
|
if dotp(v, w) / dist(v) / dist(w) >= 0.5: # 60 degrees or less, over by angle
|
||||||
|
if maybeover: # backup
|
||||||
|
newcurve = stress(points[prevtrail:lead])[0]
|
||||||
|
res[-3:] = newcurve[1:] # replace the last three nodes in res with those of newcurve
|
||||||
|
trail = lead - 1
|
||||||
|
maybeover = False
|
||||||
|
else:
|
||||||
|
if not res: res += curcurve[:1]
|
||||||
|
res += curcurve[1:]
|
||||||
|
prevtrail = trail
|
||||||
|
trail = lead - 1
|
||||||
|
window = points[trail:lead + 1]
|
||||||
|
curcurve = [window[0], slide(window[0], window[1], 1 / 3), slide(window[0], window[1], 2 / 3), window[1]]
|
||||||
|
else: # then see what to do based on how long the window is
|
||||||
|
over = False
|
||||||
|
if len(window) == 3: # Quadratic curve on three nodes stepped to a cubic
|
||||||
|
t = chords(window)[1]
|
||||||
|
qcurve = [window[0], (window[1] - (1 - t) * (1 - t) * window[0] - t * t * window[2]) / (2 * t * (1 - t)), window[2]]
|
||||||
|
newcurve = [qcurve[0], slide(qcurve[0], qcurve[1], 2 / 3), slide(qcurve[1], qcurve[2], 1 / 3), qcurve[2]]
|
||||||
|
elif len(window) == 4: # Cubic curve on four nodes
|
||||||
|
newcurve = cubicfrom4(window)
|
||||||
|
else: # Stress
|
||||||
|
product = stress(window)
|
||||||
|
shortseg = min([dist(window[i], window[i + 1]) for i in range(len(window) - 1)])
|
||||||
|
# Stop condition: maximum error > 1 / 3 * minimum segment length
|
||||||
|
if max(product[1]) > 0.33 * shortseg: over = True
|
||||||
|
else: newcurve = product[0]
|
||||||
|
if over: # Over by error bound
|
||||||
|
maybeover = True
|
||||||
|
if not res: res += curcurve[:1]
|
||||||
|
res += curcurve[1:]
|
||||||
|
prevtrail = trail
|
||||||
|
trail = lead - 1
|
||||||
|
window = points[trail:lead + 1]
|
||||||
|
curcurve = [window[0], slide(window[0], window[1], 1 / 3), slide(window[0], window[1], 2 / 3), window[1]]
|
||||||
|
else: curcurve, maybeover = newcurve, False
|
||||||
|
if maybeover: # When it has reached the end...
|
||||||
|
newcurve = stress(points[prevtrail:lead + 1])[0]
|
||||||
|
res[-3:] = newcurve[1:]
|
||||||
|
else:
|
||||||
|
if not res: res += curcurve[:1]
|
||||||
|
res += curcurve[1:] # If it has reached the end, accept curcurve
|
||||||
|
# Smoothing
|
||||||
|
ouro = res.pop() # Removes the final (redundant) node of closed paths. In the end, does not affect open paths.
|
||||||
|
for t in range(0, len(res), 3):
|
||||||
|
if t != 0 or z: # If not at beginning or if path is closed
|
||||||
|
v = res[t - 1] - res[t] # Previous handle
|
||||||
|
w = res[t + 1] - res[t] # Next handle
|
||||||
|
try:
|
||||||
|
angle = dotp(v, w) / dist(v) / dist(w)
|
||||||
|
if angle <= -0.94: # ~ cos(160 degrees)
|
||||||
|
# Rotate opposing handles and make a straight line.
|
||||||
|
theta = (pi - acos(angle)) / 2 # Angle to rotate by
|
||||||
|
sign = 1 if (dirc(v) > dirc(w)) ^ (abs(dirc(v) - dirc(w)) >= pi) else -1 # Direction to rotate (WTF?)
|
||||||
|
res[t - 1] = res[t] + spin(v, sign * theta)
|
||||||
|
res[t + 1] = res[t] + spin(w, -sign * theta)
|
||||||
|
except ZeroDivisionError:
|
||||||
|
pout("Path has only one point left. Cannot continue")
|
||||||
|
res.append(ouro)
|
||||||
|
# Formatting and final output
|
||||||
|
out = "M " + str(res[0])
|
||||||
|
for c in range(1, len(res), 3): out += " ".join([" C", str(res[c]), str(res[c + 1]), str(res[c + 2])])
|
||||||
|
if z: out += " Z "
|
||||||
|
return out
|
||||||
|
|
||||||
|
'''Helper functions and classes below'''
|
||||||
|
|
||||||
|
# Node object as a helper to simplify code. Calling it point would be SO cliched.
|
||||||
|
class node:
|
||||||
|
def __init__(self, x = None, y = None):
|
||||||
|
if y != None: self.x, self.y = float(x), float(y)
|
||||||
|
elif type(x) == list or type(x) == tuple: self.x, self.y = float(x[0]), float(x[1])
|
||||||
|
else: self.x, self.y = 0.0, 0.0
|
||||||
|
def __str__(self): return str(self.x) + " " + str(self.y)
|
||||||
|
def __repr__(self): return str(self)
|
||||||
|
def __add__(self, pode): return node(self.x + pode.x, self.y + pode.y) # Vector addition
|
||||||
|
def __sub__(self, pode): return node(self.x - pode.x, self.y - pode.y) # and subtraction
|
||||||
|
def __neg__(self): return node(-self.x, -self.y)
|
||||||
|
def __mul__(self, scal): # Multiplication by a scalar
|
||||||
|
if type(scal) == int or type(scal) == float: return node(self.x * scal, self.y * scal)
|
||||||
|
else: return node(self.x * scal.x - self.y * scal.y, self.y * scal.x + self.x * scal.y) # Fallback does complex multiplication
|
||||||
|
def __rmul__(self, scal): return self * scal
|
||||||
|
def __truediv__(self, scal): # Division by a scalar
|
||||||
|
if type(scal) == int or type(scal) == float: return node(self.x / scal, self.y / scal)
|
||||||
|
else:
|
||||||
|
n = scal.x * scal.x + scal.y * scal.y
|
||||||
|
return node(self.x * scal.x + self.y * scal.y, self.y * scal.x - self.x * scal.y) / n # Fallback does complex division
|
||||||
|
|
||||||
|
# Operations on nodes
|
||||||
|
def dist(n0, n1 = None): return hypot(n1.y - n0.y, n1.x - n0.x) if n1 else hypot(n0.y, n0.x) # For these two functions
|
||||||
|
def dirc(n0, n1 = None): return atan2(n1.y - n0.y, n1.x - n0.x) if n1 else atan2(n0.y, n0.x) # n0 is the origin if n1 is present
|
||||||
|
def slide(n0, n1, t): return n0 + t * (n1 - n0)
|
||||||
|
def dotp(n0, n1): return n0.x * n1.x + n0.y * n1.y
|
||||||
|
|
||||||
|
# Operation on vectors: rotation. Positive theta means counterclockwise rotation.
|
||||||
|
def spin(v, theta): return node(v.x * cos(theta) - v.y * sin(theta), v.x * sin(theta) + v.y * cos(theta))
|
||||||
|
|
||||||
|
# Wrapper function for node curves to mesh with bezierpointatt
|
||||||
|
def curveat(curve, t): return node(bezierpointatt(((node.x, node.y) for node in curve), t))
|
||||||
|
|
||||||
|
# This function takes in a list of nodes and returns
|
||||||
|
# a list of numbers between 0 and 1 corresponding to the relative positions
|
||||||
|
# of said nodes (assuming consecutive nodes are linked by straight lines).
|
||||||
|
# The first item is always 0.0 and the last one 1.0.
|
||||||
|
def chords(nodes):
|
||||||
|
lengths = [dist(nodes[i + 1], nodes[i]) for i in range(len(nodes) - 1)]
|
||||||
|
ratios = [0.0] + [sum(lengths[:i + 1]) / sum(lengths) for i in range(len(lengths))]
|
||||||
|
ratios[-1] = 1.0 # Just in case...
|
||||||
|
return ratios
|
||||||
|
|
||||||
|
# Takes a list of four nodes and generates a curve passing through all based on chords().
|
||||||
|
# If lm and mu (the two params for the middle nodes) are not given they are calculated.
|
||||||
|
def cubicfrom4(nodes, p = None, q = None):
|
||||||
|
if p == None or q == None:
|
||||||
|
store = chords(nodes)
|
||||||
|
lm, mu = store[1], store[2] # First one is short for lambda
|
||||||
|
else: lm, mu = p, q
|
||||||
|
a = 3 * (1 - lm) * (1 - lm) * lm
|
||||||
|
b = 3 * (1 - lm) * lm * lm
|
||||||
|
c = 3 * (1 - mu) * (1 - mu) * mu
|
||||||
|
d = 3 * (1 - mu) * mu * mu
|
||||||
|
x = nodes[1] - (1 - lm) ** 3 * nodes[0] - lm ** 3 * nodes[3]
|
||||||
|
y = nodes[2] - (1 - mu) ** 3 * nodes[0] - mu ** 3 * nodes[3]
|
||||||
|
det = a * d - b * c
|
||||||
|
if not det: pout("Singular matrix!")
|
||||||
|
l, m = (d * x - b * y) / det, (a * y - c * x) / det
|
||||||
|
return [nodes[0], l, m, nodes[3]]
|
||||||
|
|
||||||
|
# Stress theory: takes a list of five or more nodes and stresses a curve to fit
|
||||||
|
def stress(string):
|
||||||
|
# Make an initial guess considering the end nodes together with the 2nd/2nd last, 3rd/3rd last, ... nodes.
|
||||||
|
# This is much faster than considering all sets of two interior nodes.
|
||||||
|
callipers, seeds = chords(string), []
|
||||||
|
middle = len(string) // 2
|
||||||
|
for i in range(1, middle):
|
||||||
|
seeds.append(cubicfrom4([string[0], string[i], string[-i - 1], string[-1]], callipers[i], callipers[-i - 1]))
|
||||||
|
a, b = node(), node()
|
||||||
|
for i in range(len(seeds)):
|
||||||
|
a += seeds[i][1]
|
||||||
|
b += seeds[i][2]
|
||||||
|
curve = [string[0], a / len(seeds), b / len(seeds), string[-1]]
|
||||||
|
# Refine by projection and handle shifting
|
||||||
|
for i in range(5):
|
||||||
|
for j in range(middle - 1, 0, -1):
|
||||||
|
delta1, delta2 = project(curve, string[j]), project(curve, string[-j - 1])
|
||||||
|
curve[1] += 2.5 * delta1
|
||||||
|
curve[2] += 2.5 * delta2
|
||||||
|
errors = [dist(project(curve, k)) for k in string]
|
||||||
|
return curve, errors
|
||||||
|
|
||||||
|
# Projection of node onto cubic curve based on public domain code by Mike "Pomax" Kamermans
|
||||||
|
# https://pomax.github.io/bezierinfo/#projections
|
||||||
|
def project(curve, node):
|
||||||
|
samples = 200
|
||||||
|
lookup = [dist(curveat(curve, i / samples), node) for i in range(samples + 1)]
|
||||||
|
mindist = min(lookup)
|
||||||
|
t = lookup.index(mindist) / samples
|
||||||
|
width = 1 / samples # Width of search interval
|
||||||
|
while width > 1.0e-5:
|
||||||
|
left = dist(curveat(curve, max(t - width, 0)), node)
|
||||||
|
right = dist(curveat(curve, min(t + width, 1)), node)
|
||||||
|
if t == 0.0: left = mindist + 1
|
||||||
|
if t == 1.0: right = mindist + 1
|
||||||
|
if left < mindist or right < mindist:
|
||||||
|
mindist = min(left, right)
|
||||||
|
t = max(t - width, 0.0) if left < right else min(t + width, 1.0)
|
||||||
|
else: width /= 2
|
||||||
|
projection = curveat(curve, t)
|
||||||
|
return node - projection
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
FlevoBezier().run()
|
21
extensions/fablabchemnitz/flevobezier/meta.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Flevobézier",
|
||||||
|
"id": "fablabchemnitz.de.flevobezier",
|
||||||
|
"path": "flevobezier",
|
||||||
|
"dependent_extensions": null,
|
||||||
|
"original_name": "Flevobézier",
|
||||||
|
"original_id": "org.parclytaxel.inkscape.flevobezier",
|
||||||
|
"license": "MIT License",
|
||||||
|
"license_url": "https://gitlab.com/parclytaxel/Kinross/-/blob/master/licence",
|
||||||
|
"comment": "",
|
||||||
|
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/flevobezier",
|
||||||
|
"fork_url": "https://gitlab.com/parclytaxel/Kinross",
|
||||||
|
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Flevobezier",
|
||||||
|
"inkscape_gallery_url": null,
|
||||||
|
"main_authors": [
|
||||||
|
"gitlab.com/parclytaxel",
|
||||||
|
"github.com/eridur-de"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
16
extensions/fablabchemnitz/flip/flip.inx
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||||
|
<name>Flip</name>
|
||||||
|
<id>fablabchemnitz.de.flip</id>
|
||||||
|
<effect>
|
||||||
|
<object-type>path</object-type>
|
||||||
|
<effects-menu>
|
||||||
|
<submenu name="FabLab Chemnitz">
|
||||||
|
<submenu name="Transformations"/>
|
||||||
|
</submenu>
|
||||||
|
</effects-menu>
|
||||||
|
</effect>
|
||||||
|
<script>
|
||||||
|
<command location="inx" interpreter="python">flip.py</command>
|
||||||
|
</script>
|
||||||
|
</inkscape-extension>
|
38
extensions/fablabchemnitz/flip/flip.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import math
|
||||||
|
from inkex import EffectExtension, PathElement, transforms as T
|
||||||
|
|
||||||
|
# https://en.wikipedia.org/wiki/Rotations_and_reflections_in_two_dimensions
|
||||||
|
def reflection_matrix(theta):
|
||||||
|
theta2 = 2 * theta
|
||||||
|
return [
|
||||||
|
[math.cos(theta2), math.sin(theta2), 0],
|
||||||
|
[math.sin(theta2), -math.cos(theta2), 0],
|
||||||
|
]
|
||||||
|
|
||||||
|
def svg_matrix_order(mat):
|
||||||
|
((a, c, e), (b, d, f)) = mat
|
||||||
|
return a, b, c, d, e, f
|
||||||
|
|
||||||
|
class Flip(EffectExtension):
|
||||||
|
"""Extension to flip a path about the line from the start to end node"""
|
||||||
|
|
||||||
|
def effect(self):
|
||||||
|
for node in self.svg.selection.filter(PathElement).values():
|
||||||
|
points = list(node.path.end_points)
|
||||||
|
if len(points) < 2 or points[0] == points[-1]:
|
||||||
|
continue
|
||||||
|
start = points[0]
|
||||||
|
end = points[-1]
|
||||||
|
v = end - start
|
||||||
|
theta = math.atan2(v.y, v.x)
|
||||||
|
|
||||||
|
# transforms go in reverse order
|
||||||
|
mat = T.Transform()
|
||||||
|
mat.add_translate(start)
|
||||||
|
mat.add_matrix(*svg_matrix_order(reflection_matrix(theta)))
|
||||||
|
mat.add_translate(-start)
|
||||||
|
|
||||||
|
node.path = node.path.transform(mat)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
Flip().run()
|