Added back several extensions
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Another Perspective</name>
|
||||
<id>fablabchemnitz.de.another_perspective</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">another_perspective.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
@ -0,0 +1,312 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Copyright (C) 2017 Corentin Brulé
|
||||
|
||||
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/>.
|
||||
|
||||
Special thanks and orignal copyrigths : Aaron Spike (2005) and Timo Kähkönen (2012)
|
||||
"""
|
||||
|
||||
import inkex
|
||||
import re
|
||||
from lxml import etree
|
||||
from inkex.transforms import Transform
|
||||
from inkex.paths import Path, CubicSuperPath
|
||||
|
||||
__version__ = '0.1'
|
||||
|
||||
debug=False
|
||||
|
||||
def distort_path(path_str,source,destination):
|
||||
path_arr = path_string_to_array(path_str)
|
||||
|
||||
subpath_type=""
|
||||
is_num =""
|
||||
xy_counter =""
|
||||
xy=""
|
||||
path_arr2=[]
|
||||
subpath_type_upper=""
|
||||
point=""
|
||||
i=0
|
||||
for i in range(len(path_arr)):
|
||||
patt1 = r"[mzlhvcsqta]"
|
||||
|
||||
curr = path_arr[i]
|
||||
if re.match(patt1,curr,flags=re.I):
|
||||
xy_counter = -1
|
||||
subpath_type = curr
|
||||
subpath_type_upper = subpath_type.upper()
|
||||
is_num = False
|
||||
path_arr2.append(curr)
|
||||
else :
|
||||
is_num = True
|
||||
curr = float(curr)
|
||||
|
||||
if xy_counter%2 == 0:
|
||||
xy="x"
|
||||
else:
|
||||
xy="y"
|
||||
|
||||
if is_num :
|
||||
if xy=="y" :
|
||||
point = transferPoint(float(path_arr[i-1]),curr,source,destination)
|
||||
path_arr2.append(point["x"])
|
||||
path_arr2.append(point["y"])
|
||||
|
||||
xy_counter+=1
|
||||
|
||||
path_str = path_array_to_string(path_arr2)
|
||||
return path_str
|
||||
|
||||
|
||||
def path_array_to_string(path_arr):
|
||||
|
||||
path_str=str(path_arr)
|
||||
|
||||
path_str=path_str.replace(r"([0-9]),([-0-9])", "$1 $2")
|
||||
path_str=path_str.replace(r"([0-9]),([-0-9])", "$1 $2")
|
||||
path_str=path_str.replace(",", "")
|
||||
path_str=path_str.replace("[", "").replace("]","")
|
||||
path_str=path_str.replace("'", "")
|
||||
|
||||
return path_str
|
||||
|
||||
def path_string_to_array(path_str):
|
||||
|
||||
patt1=r"[mzlhvcsqta]|-?[0-9.]+" #gi
|
||||
#path_arr=path_str.match(patt1) #array de résultats
|
||||
path_arr = re.findall(patt1,path_str,flags=re.I)
|
||||
|
||||
patt1=r"[mzlhvcsqta]" #i
|
||||
i = 0
|
||||
for i in range(len(path_arr)):
|
||||
if re.match(path_arr[i],patt1,flags=re.I) == -1:
|
||||
path_arr[i] = float(path_arr[i])
|
||||
|
||||
return path_arr
|
||||
|
||||
'''
|
||||
def isPermissible(p):
|
||||
p0 = {x:c0.attr("cx"),y:c0.attr("cy")}
|
||||
p1 = {x:c1.attr("cx"),y:c1.attr("cy")}
|
||||
p2 = {x:c2.attr("cx"),y:c2.attr("cy")}
|
||||
p3 = {x:c3.attr("cx"),y:c3.attr("cy")}
|
||||
a0 = angle(p3, p0, p1)
|
||||
a1 = angle(p0, p1, p2)
|
||||
a2 = angle(p1, p2, p3)
|
||||
a3 = angle(p2, p3, p0)
|
||||
if not (a0 > 0 and a0 < 180) or not (a1 > 0 and a1 < 180) or not(a2 > 0 and a2 < 180) or not(a3 > 0 and a3 < 180) :
|
||||
return False
|
||||
else :
|
||||
return True
|
||||
}
|
||||
|
||||
def angle(c, b, a):
|
||||
ab = {x: b.x - a.x, y: b.y - a.y }
|
||||
cb = {x: b.x - c.x, y: b.y - c.y }
|
||||
dot = (ab.x * cb.x + ab.y * cb.y)
|
||||
cross = (ab.x * cb.y - ab.y * cb.x)
|
||||
alpha = Math.atan2(cross, dot)
|
||||
return alpha * 180 / PI
|
||||
}
|
||||
'''
|
||||
|
||||
def transferPoint (xI, yI, source, destination):
|
||||
|
||||
ADDING = 0.001 # to avoid dividing by zero
|
||||
|
||||
xA = source[0]["x"]
|
||||
yA = source[0]["y"]
|
||||
|
||||
xC = source[2]["x"]
|
||||
yC = source[2]["y"]
|
||||
|
||||
xAu = destination[0]["x"]
|
||||
yAu = destination[0]["y"]
|
||||
|
||||
xBu = destination[1]["x"]
|
||||
yBu = destination[1]["y"]
|
||||
|
||||
xCu = destination[2]["x"]
|
||||
yCu = destination[2]["y"]
|
||||
|
||||
xDu = destination[3]["x"]
|
||||
yDu = destination[3]["y"]
|
||||
|
||||
# Calcultations
|
||||
if xBu==xCu :
|
||||
xCu+=ADDING
|
||||
if xAu==xDu :
|
||||
xDu+=ADDING
|
||||
if xAu==xBu :
|
||||
xBu+=ADDING
|
||||
if xDu==xCu :
|
||||
xCu+=ADDING
|
||||
kBC = float(yBu-yCu)/float(xBu-xCu)
|
||||
kAD = float(yAu-yDu)/float(xAu-xDu)
|
||||
kAB = float(yAu-yBu)/float(xAu-xBu)
|
||||
kDC = float(yDu-yCu)/float(xDu-xCu)
|
||||
|
||||
if kBC==kAD :
|
||||
kAD += ADDING
|
||||
xE = float(kBC*xBu - kAD*xAu + yAu - yBu) / float(kBC-kAD)
|
||||
yE = kBC*(xE - xBu) + yBu
|
||||
|
||||
if kAB==kDC :
|
||||
kDC += ADDING
|
||||
xF = float(kAB*xBu - kDC*xCu + yCu - yBu) / float(kAB-kDC)
|
||||
yF = kAB*(xF - xBu) + yBu
|
||||
|
||||
if xE==xF :
|
||||
xF += ADDING
|
||||
kEF = float(yE-yF) / float(xE-xF)
|
||||
|
||||
if kEF==kAB:
|
||||
kAB += ADDING
|
||||
xG = float(kEF*xDu - kAB*xAu + yAu - yDu) / float(kEF-kAB)
|
||||
yG = kEF*(xG - xDu) + yDu
|
||||
|
||||
if kEF==kBC :
|
||||
kBC+=ADDING
|
||||
xH = float(kEF*xDu - kBC*xBu + yBu - yDu) / float(kEF-kBC)
|
||||
yH = kEF*(xH - xDu) + yDu
|
||||
|
||||
rG = float(yC-yI)/float(yC-yA)
|
||||
rH = float(xI-xA)/float(xC-xA)
|
||||
|
||||
xJ = (xG-xDu)*rG + xDu
|
||||
yJ = (yG-yDu)*rG + yDu
|
||||
|
||||
xK = (xH-xDu)*rH + xDu
|
||||
yK = (yH-yDu)*rH + yDu
|
||||
|
||||
if xF==xJ:
|
||||
xJ+=ADDING
|
||||
if xE==xK:
|
||||
xK+=ADDING
|
||||
kJF = float(yF-yJ) / float(xF-xJ)
|
||||
kKE = float(yE-yK) / float(xE-xK)
|
||||
|
||||
xKE = ""
|
||||
if kJF==kKE:
|
||||
kKE += ADDING
|
||||
xIu = float(kJF*xF - kKE*xE + yE - yF) / float(kJF-kKE)
|
||||
yIu = kJF * (xIu - xJ) + yJ
|
||||
|
||||
b = {"x":xIu,"y":yIu}
|
||||
b["x"] = round(b["x"])
|
||||
b["y"] = round(b["y"])
|
||||
return b
|
||||
|
||||
|
||||
def projection(path_object,coords):
|
||||
|
||||
pp_object = Path(path_object).to_arrays()
|
||||
|
||||
bounds = Path(path_object).bounding_box()
|
||||
|
||||
# Make array of coordinates, every array member represent corner of text path
|
||||
source = [
|
||||
{"x":bounds.left,"y":bounds.top},
|
||||
{"x":bounds.right,"y":bounds.top},
|
||||
{"x":bounds.right,"y":bounds.bottom},
|
||||
{"x":bounds.left,"y":bounds.bottom}
|
||||
]
|
||||
|
||||
destination=[
|
||||
{"x":coords[0][0],"y":coords[0][1]},
|
||||
{"x":coords[1][0],"y":coords[1][1]},
|
||||
{"x":coords[2][0],"y":coords[2][1]},
|
||||
{"x":coords[3][0],"y":coords[3][1]}
|
||||
]
|
||||
|
||||
path_destination = distort_path(path_object,source,destination)
|
||||
|
||||
return path_destination
|
||||
'''
|
||||
def complex2tulpe(complexNb):
|
||||
return (complexNb.real,complexNb.imag)
|
||||
'''
|
||||
class AnotherPerspective(inkex.EffectExtension):
|
||||
|
||||
def envelope2coords(self, path_envelope):
|
||||
pp_envelope = CubicSuperPath(path_envelope)
|
||||
if len(pp_envelope[0]) < 4:
|
||||
inkex.errormsg("The selected envelope (your second path) does not contain enough nodes. Check to have at least 4 nodes.")
|
||||
exit()
|
||||
|
||||
c0 = pp_envelope[0][0][0]
|
||||
c1 = pp_envelope[0][1][0]
|
||||
c2 = pp_envelope[0][2][0]
|
||||
c3 = pp_envelope[0][3][0]
|
||||
# inkex.debug(str(c0)+" "+str(c1)+" "+str(c2)+" "+str(c3))
|
||||
return [c0, c1, c2, c3]
|
||||
|
||||
def effect(self):
|
||||
if len(self.options.ids) < 2:
|
||||
inkex.errormsg("This extension requires two selected paths.")
|
||||
exit()
|
||||
|
||||
obj = self.svg.selected[self.options.ids[0]]
|
||||
envelope = self.svg.selected[self.options.ids[1]]
|
||||
|
||||
if obj.get(inkex.addNS('type','sodipodi')):
|
||||
inkex.errormsg("The first selected object is of type '%s'.\nTry using the procedure Path->Object to Path." % obj.get(inkex.addNS('type','sodipodi')))
|
||||
exit()
|
||||
|
||||
if obj.tag == inkex.addNS('path','svg') or obj.tag == inkex.addNS('g','svg'):
|
||||
if envelope.tag == inkex.addNS('path','svg'):
|
||||
mat = envelope.transform @ Transform([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])
|
||||
path = CubicSuperPath(envelope.get('d'))
|
||||
Path(path).transform(mat)
|
||||
absolute_envelope_path = envelope.get('d')
|
||||
# inkex.debug(absolute_envelope_path)
|
||||
coords_to_project = self.envelope2coords(absolute_envelope_path)
|
||||
|
||||
if obj.tag == inkex.addNS('path','svg'):
|
||||
mat = obj.transform @ Transform([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])
|
||||
absolute_d = str(Path(obj.get('d')))
|
||||
path = CubicSuperPath(absolute_d)
|
||||
Path(path).transform(mat)
|
||||
absolute_object_path = str(path)
|
||||
# inkex.debug(absolute_object_path)
|
||||
|
||||
elif obj.tag == inkex.addNS('g','svg'):
|
||||
absolute_object_path=""
|
||||
for p in obj.iterfind(".//{http://www.w3.org/2000/svg}path"):
|
||||
|
||||
absolute_d = str(Path(p.get('d')))
|
||||
mat = p.transform * Transform([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])
|
||||
path = CubicSuperPath(absolute_d)
|
||||
Path(path).transform(mat)
|
||||
absolute_object_path += str(Path(path))
|
||||
# inkex.debug(absolute_object_path)
|
||||
|
||||
new_path = projection(absolute_object_path,coords_to_project)
|
||||
attributes = {'d':new_path, 'style':str(obj.style)}
|
||||
new_element = etree.SubElement(self.svg.get_current_layer(),inkex.addNS('path','svg'),attributes)
|
||||
|
||||
else:
|
||||
if envelope.tag == inkex.addNS('g','svg'):
|
||||
inkex.errormsg("The second selected object is a group, not a path.\nTry using the procedure Object->Ungroup.")
|
||||
else:
|
||||
inkex.errormsg("The second selected object is not a path.\nTry using the procedure Path->Object to Path.")
|
||||
exit()
|
||||
else:
|
||||
inkex.errormsg("The first selected object is not a path.\nTry using the procedure Path->Object to Path.")
|
||||
exit()
|
||||
|
||||
if __name__ == '__main__':
|
||||
AnotherPerspective().run()
|
21
extensions/fablabchemnitz/another_perspective/meta.json
Normal file
@ -0,0 +1,21 @@
|
||||
[
|
||||
{
|
||||
"name": "Another Perspective",
|
||||
"id": "fablabchemnitz.de.another_perspective",
|
||||
"path": "another_perspective",
|
||||
"dependent_extensions": null,
|
||||
"original_name": "AnotherPerspective",
|
||||
"original_id": "macrico.anotherperspective",
|
||||
"license": "GNU GPL v3",
|
||||
"license_url": "https://github.com/CorentinBrule/inkscape_another_perspective_extension/blob/master/anotherperspective.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/another_perspective",
|
||||
"fork_url": "https://github.com/CorentinBrule/inkscape_another_perspective_extension",
|
||||
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Another+Perspective",
|
||||
"inkscape_gallery_url": null,
|
||||
"main_authors": [
|
||||
"github.com/CorentinBrule",
|
||||
"github.com/eridur-de"
|
||||
]
|
||||
}
|
||||
]
|
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Bobbin Lace - Circular Ground from Template</name>
|
||||
<id>fablabchemnitz.de.bobbinlace.circular_ground_from_template</id>
|
||||
<label appearance="header">Wrap lace pattern found in template file around a circle.</label>
|
||||
<label>Note: Drawing can become quite large when "Number of copies around circle" is small or "Diameter" of inside circle is large.</label>
|
||||
<param name="file" type="path" gui-text="Template file name (full path):" mode="file" filetypes="txt">./templates/</param>
|
||||
<label appearance="header">Grid description</label>
|
||||
<hbox indent="1">
|
||||
<param name="angle" type="float" precision="1" min="30" max="89" gui-text="Angle (degrees):">45.0</param>
|
||||
</hbox>
|
||||
<hbox indent="1">
|
||||
<param name="cols" type="int" min="3" max="1000" gui-text="Number of copies around circle:">30</param>
|
||||
</hbox>
|
||||
<label appearance="header">Patch description</label>
|
||||
<hbox indent="1">
|
||||
<param name="diameter" type="float" precision="2" min="0.1" max="1000" gui-text="Inner circle diameter:">50</param>
|
||||
<param name="diamunits" gui-text=" " type="optiongroup" appearance="combo">
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="in">in</option>
|
||||
<option value="px">px</option>
|
||||
<option value="pt">pt</option>
|
||||
</param>
|
||||
</hbox>
|
||||
<hbox indent="1">
|
||||
<param name="rows" type="int" min="1" max="1000" gui-text="Number of circles:">3</param>
|
||||
</hbox>
|
||||
<label appearance="header">Line Appearance</label>
|
||||
<hbox indent="1">
|
||||
<param name="linewidth" type="float" precision="2" min="0.01" max="1000" gui-text="Width:">1</param>
|
||||
<param name="lineunits" gui-text=" " type="optiongroup" appearance="combo">
|
||||
<option value="px">px</option>
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="in">in</option>
|
||||
<option value="pt">pt</option>
|
||||
</param>
|
||||
</hbox>
|
||||
<hbox indent="1">
|
||||
<param name="linecolor" type="color" appearance="colorbutton" gui-text="Color:">255</param>
|
||||
</hbox>
|
||||
<effect needs-live-preview="true">
|
||||
<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">circular_ground_from_template.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
@ -0,0 +1,434 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (c) 2017, Ben Connors
|
||||
# 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.
|
||||
#
|
||||
# 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 OWNER 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.
|
||||
|
||||
import os
|
||||
from math import sin, cos, acos, tan, radians, pi, sqrt, ceil, floor
|
||||
import inkex
|
||||
from lxml import etree
|
||||
|
||||
__author__ = 'Ben Connors'
|
||||
__credits__ = ['Ben Connors', 'Veronika Irvine', 'Jo Pol', 'Mark Shafer']
|
||||
__license__ = 'Simplified BSD'
|
||||
|
||||
class Vector:
|
||||
def __repr__(self):
|
||||
return 'Vector(%.4f, %.4f)' % (self.dx,self.dy)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.dx,self.dy))
|
||||
|
||||
def rotate(self,theta):
|
||||
""" Rotate counterclockwise by theta."""
|
||||
return self.mag*Vector(cos(self.theta+theta),
|
||||
sin(self.theta+theta),
|
||||
_theta=self.theta+theta)
|
||||
|
||||
def __mul__(self,other):
|
||||
return Vector(self.dx*other,self.dy*other,_theta=self.theta)
|
||||
|
||||
def __rmul__(self,other):
|
||||
return self*other
|
||||
|
||||
def __init__(self,dx,dy,_theta=None):
|
||||
""" Create a vector with the specified components.
|
||||
_theta should NOT be passed in normal use - this value is passed by
|
||||
vector functions where the angle of the new vector is known in order
|
||||
to eliminate that calculation.
|
||||
"""
|
||||
self.dx = float(dx)
|
||||
self.dy = float(dy)
|
||||
self.mag = sqrt(dx**2 + dy**2)
|
||||
self.tuple = (dx,dy)
|
||||
|
||||
## Angle to positive X axis
|
||||
if _theta == None:
|
||||
_theta = acos(self.dx/self.mag)
|
||||
self.theta = 2*pi-_theta if self.dy < 0 else _theta
|
||||
else:
|
||||
self.theta = _theta
|
||||
|
||||
|
||||
class CircularGroundFromTemplate(inkex.EffectExtension):
|
||||
|
||||
def unitToUu(self,param):
|
||||
""" Convert units.
|
||||
Converts a number in some units into the units used internally by
|
||||
Inkscape.
|
||||
|
||||
param is a string representing a number with units attached. An
|
||||
example would be '3.8mm'. Any units supported by Inkscape
|
||||
are supported by this function.
|
||||
|
||||
This wrapper function catches changes made to the location
|
||||
of the function between Inkscape versions.
|
||||
"""
|
||||
try:
|
||||
return self.svg.unittouu(param)
|
||||
except:
|
||||
return inkex.unittouu(param)
|
||||
|
||||
def loadFile(self):
|
||||
""" Load the specification for the unit cell from the file given.
|
||||
Note that the specification should be in the following format:
|
||||
TYPE ROWS COLS
|
||||
[x1,y1,x2,y2,x3,y3] [x4,y4,x5 ...
|
||||
|
||||
And so on. The TYPE is always CHECKER and is ignored by this program.
|
||||
ROWS specifies the height of the unit cell (i.e. max_y - min_y)
|
||||
and COLS specifies the same for the width (i.e. max_x - min_x).
|
||||
Note that this is not enforced when drawing the unit cell - points
|
||||
may be outside this range. These values are used to determine the
|
||||
distance between unit cells (i.e. unit cells may overlap).
|
||||
"""
|
||||
# Ensure that file exists and has the proper extension
|
||||
if not self.options.file:
|
||||
inkex.errormsg('You must specify a template file.')
|
||||
exit()
|
||||
self.options.file = self.options.file.strip()
|
||||
if self.options.file == '':
|
||||
inkex.errormsg('You must specify a template file.')
|
||||
exit()
|
||||
if not os.path.isfile(self.options.file):
|
||||
inkex.errormsg('You have not specified a valid path for the template file.\n\nYour entry: '+self.options.file)
|
||||
exit()
|
||||
extension = os.path.splitext(self.options.file)[1]
|
||||
if extension != '.txt':
|
||||
inkex.errormsg('The file name must end with .txt.\n\nYour entry: '+self.options.file)
|
||||
exit()
|
||||
|
||||
data = []
|
||||
rows, cols = -1, -1
|
||||
with open(self.options.file,'r') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
## If rows is not a positive integer, we're on the first line
|
||||
if rows == -1:
|
||||
tmp = line.split('\t')
|
||||
_type,cols,rows = tmp[0],int(tmp[1]),int(tmp[2])
|
||||
else:
|
||||
data.append([])
|
||||
for cell in line[1:-1].split(']\t['):
|
||||
cell = cell.strip()
|
||||
## The pattern must be rotated 90 degrees clockwise. It's
|
||||
## simplest to just do that here
|
||||
tmp = [float(n) for n in cell.split(',')]
|
||||
data[-1].append([a for b in zip([rows-i for i in tmp[1::2]],[cols-i for i in tmp[::2]]) for a in b])
|
||||
return {'type': _type, 'rows': rows, 'cols': cols, 'data' : data}
|
||||
|
||||
def line(self,points):
|
||||
"""
|
||||
Draw a line from point at (x1, y1) to point at (x2, y2).
|
||||
Style of line is hard coded and specified by 's'.
|
||||
"""
|
||||
# define the motions
|
||||
path = ('M%.4f,%.4fL' % tuple(points[0][:2])) + 'L'.join([('%f,%f' % tuple(a[:2])) for a in points[1:]])
|
||||
|
||||
# define the stroke style
|
||||
s = {'stroke-linejoin': 'miter',
|
||||
'stroke-width': self.options.linewidth,
|
||||
'stroke-opacity': '1.0',
|
||||
'fill-opacity': '1.0',
|
||||
'stroke': self.options.linecolor,
|
||||
'stroke-linecap': 'butt',
|
||||
'stroke-linejoin': 'miter',
|
||||
'fill': 'none'
|
||||
}
|
||||
|
||||
|
||||
## Attributes for new element
|
||||
attribs = {'style':str(inkex.Style(s)),
|
||||
'd' : path}
|
||||
|
||||
## Add new element
|
||||
etree.SubElement(self.svg.get_current_layer(), inkex.addNS('path', 'svg'), attribs)
|
||||
|
||||
def baseVectors(self,segments):
|
||||
""" Create vectors for all vertices on the specified polygon."""
|
||||
## Start at 12 o'clock
|
||||
theta = pi/2
|
||||
## Move clockwise
|
||||
dtheta = -2*pi/segments
|
||||
|
||||
vector = Vector(0,self.options.diameter/2)
|
||||
vectors = [vector]
|
||||
for i in range(1,segments):
|
||||
vector = vector.rotate(dtheta)
|
||||
vectors.append(vector)
|
||||
return vectors
|
||||
|
||||
def fuzzyEquality(self,a,b):
|
||||
return (a-b <= 1e-8)
|
||||
|
||||
def circleWrap(self,points,segments):
|
||||
""" Wrap a grid around the origin.
|
||||
<<points>> is a list of 2- or 3-tuples.
|
||||
In the case of 3-tuples, they should be laid out like: (x,y,name)
|
||||
Whereas 2-tuples should eliminate the name portion.
|
||||
Only one format may be passed; they may not be mixed.
|
||||
x- and y- values are rounded to the nearest integer.
|
||||
If more precision is desired, scale up the points before calling this function.
|
||||
x-values should be within [0,segments)
|
||||
Values not within range will be moved within range.
|
||||
y-values must be greater than 0
|
||||
An error will be raised if a y-value is less than 0.
|
||||
The 'name' portion is not touched by this function; it is merely
|
||||
passed along. This may be used to identify points or groups of points.
|
||||
<<radius>> is the inside radius (i.e. distance to origin from a point with
|
||||
a y-value of 0).
|
||||
<<segments>> is the number of segments (sides) of the polygon.
|
||||
<<angle>> is the angle of the diagonal of the square approximation. It must be
|
||||
somewhere on (0,pi/2).
|
||||
"""
|
||||
angle = self.options.angle
|
||||
if angle <= 0 or angle >= pi/2:
|
||||
raise ValueError('Angle must be in (0,pi/2)')
|
||||
|
||||
vectors = self.baseVectors(segments)
|
||||
theta = 2*pi/segments
|
||||
|
||||
"""
|
||||
Determine the coefficient to multiply the vectors by in order to deal
|
||||
with a higher x-value.
|
||||
With R being the large radius (radius to next y-value) and r being the
|
||||
small radius (radius to current y-value):
|
||||
|
||||
a^2 = r^2 (1 - cos(theta)) ## Cosine law
|
||||
b^2 = R^2 (1 - cos(theta))
|
||||
|
||||
To get the most square-like trapezoid:
|
||||
R - r = 0.5(a+b)
|
||||
|
||||
Subbing in the equations for b^2 and a^2 yields the following lines.
|
||||
"""
|
||||
C = sqrt(2*(1-cos(theta)))
|
||||
val = 2*tan(pi/2-angle)
|
||||
coeff = (val+C)/(val-C)
|
||||
diff = coeff-1
|
||||
|
||||
## Sort points in order of increasing y-value.
|
||||
named = False
|
||||
if len(points[0]) == 3:
|
||||
named = True
|
||||
points = [(x,y,name) for x,y,name in sorted(points,key=lambda a: a[1])]
|
||||
else:
|
||||
points = [(x,y,None) for x,y in sorted(points,key=lambda a: a[1])]
|
||||
|
||||
done = []
|
||||
cur_y = 0
|
||||
for point in points:
|
||||
x,y,name = point
|
||||
|
||||
## Check constraints
|
||||
if y < cur_y:
|
||||
raise ValueError('Invalid point (%d,%d)' % (x,y))
|
||||
elif y >= cur_y+1:
|
||||
## Multiply vectors accordingly
|
||||
delta = floor(y-cur_y)
|
||||
vectors = [(coeff**delta)*v for v in vectors]
|
||||
cur_y = floor(y)
|
||||
|
||||
## Wrap x-value to lie in the proper place
|
||||
## lazy
|
||||
while x < 0:
|
||||
x += segments
|
||||
while x >= segments:
|
||||
x -= segments
|
||||
|
||||
if self.fuzzyEquality(y,int(y)) and self.fuzzyEquality(x,int(x)):
|
||||
x = int(x)
|
||||
## Can do it the quick way
|
||||
wx,wy = vectors[x].tuple
|
||||
else:
|
||||
## Use vector rotation
|
||||
## Determine nearest vector (counterclockwise)
|
||||
pointer = vectors[floor(x)]
|
||||
## Scale x and y to be within (0,1)
|
||||
x -= int(x)
|
||||
y -= int(y)
|
||||
c = C*x ## This value is used a lot, cache it
|
||||
## Now the angle of rotation must be determined using cosine law
|
||||
factor = 1
|
||||
if not self.fuzzyEquality(x,0):
|
||||
## x isn't equal to 0, must rotate vector
|
||||
n2 = 1+c**2-2*c*cos((pi-theta)/2)
|
||||
factor = sqrt(n2)
|
||||
phi = acos((n2+1-c**2)/(2*factor))
|
||||
pointer = pointer.rotate(-phi)
|
||||
## Correct vector magnitude
|
||||
pointer = (1+y*diff)*factor*pointer
|
||||
wx,wy = pointer.tuple
|
||||
if named:
|
||||
done.append((wx,wy,name))
|
||||
else:
|
||||
done.append((wx,wy))
|
||||
return done
|
||||
|
||||
def createGround(self,unit,rows,cols,scale=1):
|
||||
""" Return a lace ground.
|
||||
|
||||
This function returns a list of points and corresponding lines that may
|
||||
be transformed or passed to a drawing function (such as draw_image) in
|
||||
order to draw a lace ground.
|
||||
|
||||
unit is the pattern for the lace ground, in the format returned by
|
||||
loadFile.
|
||||
|
||||
rows and cols are integers and represent the number of horizontal repeats
|
||||
and vertical repeats of the pattern, respectively.
|
||||
|
||||
scale is an optional value that can be used to scale the pattern before it
|
||||
is repeated. Note that this comes with some constraints - the
|
||||
template's rows and cols after scaling (i.e. unit['rows']*scale) must
|
||||
be an integer. For example, a template with 4 rows and 4 cols before
|
||||
scaling may be scaled by any integer value above 1 and select values
|
||||
between 1 and 0 (namely 0.25,0.5,0.75). A scale value of 'True' may be
|
||||
passed if each repeat of the template should fit within a 1x1 square.
|
||||
"""
|
||||
data = unit['data']
|
||||
unit_rows = unit['rows']
|
||||
unit_cols = unit['cols']
|
||||
if scale <= 0:
|
||||
raise ValueError('Scale must be greater than zero')
|
||||
elif scale != 1:
|
||||
## The user wants to scale the template
|
||||
_data = []
|
||||
for row in data:
|
||||
_row = []
|
||||
for c in row:
|
||||
if scale == True:
|
||||
_row.append([i for n in zip([a/unit_cols for a in c[::2]],[a/unit_rows for a in c[1::2]]) for i in n])
|
||||
else:
|
||||
_row.append([a*scale for a in c])
|
||||
_data.append(_row)
|
||||
data = _data
|
||||
unit_rows *= scale
|
||||
unit_cols *= scale
|
||||
## Catching invalid input
|
||||
if not self.fuzzyEquality(unit_rows,int(unit_rows)):
|
||||
raise ValueError('Scale factor must result in an integer value for template rows')
|
||||
if not self.fuzzyEquality(unit_cols,int(unit_cols)):
|
||||
raise ValueError('Scale factor must result in an integer value for template cols')
|
||||
unit_rows = int(unit_rows)
|
||||
unit_cols = int(unit_cols)
|
||||
line_num = 0
|
||||
points = []
|
||||
for c in range(cols):
|
||||
## Do each column first
|
||||
x = c*unit_cols
|
||||
for r in range(rows):
|
||||
y = r*unit_rows
|
||||
for row in data:
|
||||
for x1,y1,x2,y2,x3,y3 in row:
|
||||
## In order to draw lines in the correct order, an extra
|
||||
## point must be added
|
||||
p1 = (x+x1,y+y1,'%09da,1'%line_num)
|
||||
p2 = (x+x2,y+y2,'%09da,2'%line_num)
|
||||
p1a = (x+x1,y+y1,'%09db,1'%line_num)
|
||||
p3 = (x+x3,y+y3,'%09db,3'%line_num)
|
||||
points.extend([p1,p2,p1a,p3])
|
||||
line_num += 1
|
||||
return points
|
||||
|
||||
def draw(self, points,line=lambda a: None):
|
||||
""" Draw the image.
|
||||
points - a list of points, as returned by createGround.
|
||||
line - a function that draws a line connecting all points in the passed list in order.
|
||||
"""
|
||||
groups = {}
|
||||
## This loop scales points, sorts them into groups, and gets image parameters
|
||||
xs = []
|
||||
ys = []
|
||||
for x,y,n in points:
|
||||
xs.append(x)
|
||||
ys.append(y)
|
||||
sn = n.split(',',1)
|
||||
ident = 0
|
||||
if len(sn) == 2:
|
||||
ident = int(sn[1])
|
||||
n = sn[0]
|
||||
if n not in groups:
|
||||
groups[n] = []
|
||||
groups[n].append((x,y,ident))
|
||||
max_x = max(xs)
|
||||
min_x = min(xs)
|
||||
max_y = max(ys)
|
||||
min_y = min(ys)
|
||||
## Sort all groups to draw lines in order
|
||||
for group in groups:
|
||||
groups[group].sort(key=lambda a:a[2])
|
||||
## Sort all groups to draw groups in order
|
||||
groups = sorted([(name,pts) for name,pts in groups.items()],key=lambda a:a[0])
|
||||
## Draw lines
|
||||
for name,pts in groups:
|
||||
_pts = []
|
||||
for p in pts:
|
||||
_pts.append([p[0]-min_x,p[1]-min_y])
|
||||
self.line(_pts)
|
||||
|
||||
def add_arguments(self, pars):
|
||||
pars.add_argument('--file')
|
||||
pars.add_argument('--angle', type=int)
|
||||
pars.add_argument('--cols', type=int)
|
||||
pars.add_argument('--diameter', type=float)
|
||||
pars.add_argument('--diamunits')
|
||||
pars.add_argument('--rows', type=int)
|
||||
pars.add_argument('--linewidth', type=float)
|
||||
pars.add_argument('--lineunits')
|
||||
pars.add_argument('--linecolor', type=inkex.Color)
|
||||
|
||||
def effect(self):
|
||||
## Load the file
|
||||
unit = self.loadFile()
|
||||
self.options.linecolor = self.options.linecolor.to_rgb()
|
||||
|
||||
## Change the input to universal units
|
||||
self.options.diameter = self.unitToUu(str(self.options.diameter)+self.options.diamunits)
|
||||
self.options.linewidth = self.unitToUu(str(self.options.linewidth)+self.options.lineunits)
|
||||
|
||||
## Convert the angle
|
||||
self.options.angle = radians(self.options.angle)
|
||||
|
||||
## Ensure no y-values are below 0
|
||||
min_y = min([b for a in [i[1::2] for row in unit['data'] for i in row] for b in a])
|
||||
if min_y < 0:
|
||||
data = []
|
||||
for row in unit['data']:
|
||||
_row = []
|
||||
for c in row:
|
||||
_row.append([a for b in zip(c[::2],[i-min_y for i in c[1::2]]) for a in b])
|
||||
data.append(_row)
|
||||
unit['data'] = data
|
||||
|
||||
## Create the ground coordinates
|
||||
points = self.createGround(unit,self.options.rows,self.options.cols)
|
||||
|
||||
## Wrap it around a polygon
|
||||
points = self.circleWrap(points,self.options.cols*unit['cols'])
|
||||
|
||||
## Draw everything
|
||||
self.draw(points,line=lambda a: self.line(a))
|
||||
|
||||
if __name__ == '__main__':
|
||||
CircularGroundFromTemplate().run()
|
@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Bobbin Lace - Ground From Template</name>
|
||||
<id>fablabchemnitz.de.bobbinlace.ground_from_template</id>
|
||||
<label appearance="header">Fill a rectangular patch with a lace ground pattern from selected template file.</label>
|
||||
<param name="file" type="path" gui-text="Template file name (full path):" mode="file" filetypes="txt">./templates/</param>
|
||||
<label appearance="header">Grid description</label>
|
||||
<hbox indent="1">
|
||||
<param name="angle" type="float" precision="1" min="30" max="89" gui-text="Angle (degrees):">45.0</param>
|
||||
</hbox>
|
||||
<hbox indent="1">
|
||||
<param name="distance" type="float" precision="2" min="0.01" max="1000.0" gui-text="Distance between footside pins:">5.0</param>
|
||||
<param name="pinunits" gui-text=" " type="optiongroup" appearance="combo">
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="in">in</option>
|
||||
<option value="px">px</option>
|
||||
<option value="pt">pt</option>
|
||||
</param>
|
||||
</hbox>
|
||||
<label appearance="header">Patch description</label>
|
||||
<hbox indent="1">
|
||||
<param name="width" type="float" precision="2" min="0.1" max="1000" gui-text="Width:">50</param>
|
||||
<param name="patchunits" gui-text=" " type="optiongroup" appearance="combo">
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="in">in</option>
|
||||
<option value="px">px</option>
|
||||
<option value="pt">pt</option>
|
||||
</param>
|
||||
</hbox>
|
||||
<hbox indent="1">
|
||||
<param name="height" type="float" precision="2" min="0.1" max="1000" gui-text="Height:">50</param>
|
||||
</hbox>
|
||||
<label appearance="header">Line Appearance</label>
|
||||
<hbox indent="1">
|
||||
<param name="linewidth" type="float" precision="2" min="0.01" max="1000" gui-text="Width:">1</param>
|
||||
<param name="lineunits" gui-text=" " type="optiongroup" appearance="combo">
|
||||
<option value="px">px</option>
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="in">in</option>
|
||||
<option value="pt">pt</option>
|
||||
</param>
|
||||
</hbox>
|
||||
<hbox indent="1">
|
||||
<param name="linecolor" type="color" appearance="colorbutton" gui-text="Color:">255</param>
|
||||
</hbox>
|
||||
<effect needs-live-preview="true">
|
||||
<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">ground_from_template.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
176
extensions/fablabchemnitz/bobbinlace/ground_from_template.py
Normal file
@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (c) 2017, Veronika Irvine
|
||||
# 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.
|
||||
#
|
||||
# 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 OWNER 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.
|
||||
|
||||
import os
|
||||
from math import sin, cos, radians, ceil
|
||||
from lxml import etree
|
||||
import inkex
|
||||
|
||||
__author__ = 'Veronika Irvine'
|
||||
__credits__ = ['Ben Connors', 'Veronika Irvine', 'Mark Shafer']
|
||||
__license__ = 'Simplified BSD'
|
||||
|
||||
class GroundFromTemplate(inkex.EffectExtension):
|
||||
|
||||
def loadFile(self):
|
||||
# Ensure that file exists and has the proper extension
|
||||
if not self.options.file:
|
||||
inkex.errormsg('You must specify a template file.')
|
||||
exit()
|
||||
self.options.file = self.options.file.strip()
|
||||
if self.options.file == '':
|
||||
inkex.errormsg('You must specify a template file.')
|
||||
exit()
|
||||
if not os.path.isfile(self.options.file):
|
||||
inkex.errormsg('You have not specified a valid path for the template file.\n\nYour entry: '+self.options.file)
|
||||
exit()
|
||||
extension = os.path.splitext(self.options.file)[1]
|
||||
if extension != '.txt':
|
||||
inkex.errormsg('The file name must end with .txt.\n\nYour entry: '+self.options.file)
|
||||
exit()
|
||||
|
||||
data = []
|
||||
rowCount = 0
|
||||
colCount = 0
|
||||
with open(self.options.file,'r') as f:
|
||||
first = True
|
||||
for line in f:
|
||||
if first:
|
||||
# first line of file gives row count and column count
|
||||
first = False
|
||||
line = line.strip()
|
||||
temp = line.split('\t')
|
||||
type = temp[0]
|
||||
rowCount = int(temp[1])
|
||||
colCount = int(temp[-1])
|
||||
|
||||
else:
|
||||
line = line.strip()
|
||||
line = line.lstrip('[')
|
||||
line = line.rstrip(']')
|
||||
rowData = line.split(']\t[')
|
||||
data.append([])
|
||||
for cell in rowData:
|
||||
cell = cell.strip()
|
||||
data[-1].append([float(num) for num in cell.split(',')])
|
||||
|
||||
return {'type':type, 'rowCount':rowCount, 'colCount':colCount, 'data':data}
|
||||
|
||||
def line(self, x1, y1, x2, y2):
|
||||
"""
|
||||
Draw a line from point at (x1, y1) to point at (x2, y2).
|
||||
Style of line is hard coded and specified by 's'.
|
||||
"""
|
||||
# define the motions
|
||||
path = 'M %s,%s L %s,%s' %(x1,y1,x2,y2)
|
||||
|
||||
# define the stroke style
|
||||
s = {'stroke-linejoin': 'miter',
|
||||
'stroke-width': self.options.linewidth,
|
||||
'stroke-opacity': '1.0',
|
||||
'fill-opacity': '1.0',
|
||||
'stroke': self.options.linecolor,
|
||||
'stroke-linecap': 'butt',
|
||||
'stroke-linejoin': 'miter',
|
||||
'fill': 'none'
|
||||
}
|
||||
|
||||
# create attributes from style and path
|
||||
attribs = {'style':str(inkex.Style(s)), 'd':path}
|
||||
|
||||
# insert path object into current layer
|
||||
etree.SubElement(self.svg.get_current_layer(), inkex.addNS('path', 'svg'), attribs)
|
||||
|
||||
def draw(self, data, rowCount, colCount):
|
||||
a = self.options.distance
|
||||
theta = self.options.angle
|
||||
deltaX = a*sin(theta)
|
||||
deltaY = a*cos(theta)
|
||||
maxRows = ceil(self.options.height / deltaY)
|
||||
maxCols = ceil(self.options.width / deltaX)
|
||||
|
||||
x = 0.0
|
||||
y = 0.0
|
||||
repeatY = 0
|
||||
repeatX = 0
|
||||
|
||||
while repeatY * rowCount < maxRows:
|
||||
x = 0.0
|
||||
repeatX = 0
|
||||
|
||||
while repeatX * colCount < maxCols:
|
||||
|
||||
for row in data:
|
||||
for coords in row:
|
||||
x1 = x + coords[0]*deltaX
|
||||
y1 = y + coords[1]*deltaY
|
||||
x2 = x + coords[2]*deltaX
|
||||
y2 = y + coords[3]*deltaY
|
||||
x3 = x + coords[4]*deltaX
|
||||
y3 = y + coords[5]*deltaY
|
||||
|
||||
self.line(x1,y1,x2,y2)
|
||||
self.line(x1,y1,x3,y3)
|
||||
|
||||
repeatX += 1
|
||||
x += deltaX * colCount
|
||||
|
||||
repeatY += 1
|
||||
y += deltaY * rowCount
|
||||
|
||||
def add_arguments(self, pars):
|
||||
pars.add_argument('-f', '--file', help='File containing lace ground description')
|
||||
pars.add_argument('--angle', type=float)
|
||||
pars.add_argument('--distance', type=float)
|
||||
pars.add_argument('--pinunits')
|
||||
pars.add_argument('--width', type=float)
|
||||
pars.add_argument('--patchunits')
|
||||
pars.add_argument('--height', type=float)
|
||||
pars.add_argument('--linewidth', type=float)
|
||||
pars.add_argument('--lineunits')
|
||||
pars.add_argument('--linecolor', type=inkex.Color)
|
||||
|
||||
def effect(self):
|
||||
result = self.loadFile()
|
||||
|
||||
# Convert input to universal units
|
||||
self.options.width = self.svg.unittouu(str(self.options.width)+self.options.patchunits)
|
||||
self.options.height = self.svg.unittouu(str(self.options.height)+self.options.patchunits)
|
||||
self.options.linewidth = self.svg.unittouu(str(self.options.linewidth)+self.options.lineunits)
|
||||
self.options.distance = self.svg.unittouu(str(self.options.distance)+self.options.pinunits)
|
||||
|
||||
# Users expect distance to be the vertical distance between footside pins
|
||||
# (vertical distance between every other row) but in the script we use it
|
||||
# as the diagonal distance between grid points
|
||||
# therefore convert distance based on the angle chosen
|
||||
self.options.angle = radians(self.options.angle)
|
||||
self.options.distance = self.options.distance/(2.0*cos(self.options.angle))
|
||||
|
||||
# Draw a ground based on file description and user inputs
|
||||
self.options.linecolor = self.options.linecolor.to_rgb()
|
||||
# For now, assume style is Checker but could change in future
|
||||
self.draw(result['data'],result['rowCount'],result['colCount'])
|
||||
|
||||
if __name__ == '__main__':
|
||||
GroundFromTemplate().run()
|
24
extensions/fablabchemnitz/bobbinlace/meta.json
Normal file
@ -0,0 +1,24 @@
|
||||
[
|
||||
{
|
||||
"name": "<various>",
|
||||
"id": "fablabchemnitz.de.bobbinlace.<various>",
|
||||
"path": "bobbinlace",
|
||||
"dependent_extensions": null,
|
||||
"original_name": "<various>",
|
||||
"original_id": "tesselace.<various>",
|
||||
"license": "GNU GPL v3 / Simplified BSD License",
|
||||
"license_url": "https://d-bl.github.io/inkscape-bobbinlace/",
|
||||
"comment": "Mixed licenses",
|
||||
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/bobbinlace",
|
||||
"fork_url": "https://github.com/d-bl/inkscape-bobbinlace",
|
||||
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Bobbin+Lace",
|
||||
"inkscape_gallery_url": null,
|
||||
"main_authors": [
|
||||
"github.com/d-bl",
|
||||
"github.com/veronika",
|
||||
"github.com/Neon22",
|
||||
"github.com/jo-pol",
|
||||
"github.com/eridur-de"
|
||||
]
|
||||
}
|
||||
]
|
71
extensions/fablabchemnitz/bobbinlace/polar_grid.inx
Normal file
@ -0,0 +1,71 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Bobbin Lace - Polar Grid</name>
|
||||
<id>fablabchemnitz.de.bobbinlace.polar_grid</id>
|
||||
<label>Creates a printable polar grid of dots with a constant number of dots per circle and the distance between circles changing at the same speed as the distance between the dots on a circle.</label>
|
||||
<label appearance="header">Grid style</label>
|
||||
<hbox indent="1">
|
||||
<param name="angle" type="float" precision="2" min="15" max="80" gui-text="Grid angle (degrees):">45.0</param>
|
||||
</hbox>
|
||||
<hbox indent="1">
|
||||
<param name="dots" type="int" min="4" max="400" gui-text="Number of dots per circle:">180</param>
|
||||
</hbox>
|
||||
<hbox indent="1">
|
||||
<param name="variant" type="optiongroup" appearance="combo" gui-text="Pattern:">
|
||||
<option value="">diamond</option>
|
||||
<option value="rectangle">rectangle</option>
|
||||
<option value="hexagon1">hexagon (30°)</option>
|
||||
<option value="hexagon2">hexagon (60°, /3)</option>
|
||||
<option value="hexagon3">hexagon + triangle (30-45-60°, /2)</option>
|
||||
<option value="hexagon4">hexagon + diamond (30°)</option>
|
||||
<option value="hexagon5">hexagon + diamond (60°, /2)</option>
|
||||
<option value="snow2">snow, hexagon (60°, /6)</option>
|
||||
<option value="snow1">snow, hexagon + diamond (60°, /8)</option>
|
||||
</param>
|
||||
</hbox>
|
||||
<label appearance="header">Grid size</label>
|
||||
<hbox indent="1">
|
||||
<param name="outerDiameter" type="float" precision="2" min="0.5" max="500" gui-text="Outside diameter:">160</param>
|
||||
<param name="circleDiameterUnits" gui-text=" " type="optiongroup" appearance="combo">
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="in">in</option>
|
||||
<option value="px">px</option>
|
||||
<option value="pt">pt</option>
|
||||
</param>
|
||||
</hbox>
|
||||
<hbox indent="1">
|
||||
<param name="innerDiameter" type="float" precision="2" min="0.5" max="500" gui-text="Inside diameter:">100</param>
|
||||
</hbox>
|
||||
<hbox indent="1">
|
||||
<param name="alignment" type="optiongroup" appearance="combo" gui-text="Align to:">
|
||||
<option value="outside">outside circle</option>
|
||||
<option value="inside">inside circle</option>
|
||||
</param>
|
||||
</hbox>
|
||||
<label appearance="header">Dot properties</label>
|
||||
<hbox indent="1">
|
||||
<param name="size" type="float" precision="2" min="0.001" max="10" gui-text="Diameter:">0.5</param>
|
||||
<param name="dotUnits" gui-text=" " type="optiongroup" appearance="combo">
|
||||
<option value="px">px</option>
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="in">in</option>
|
||||
<option value="pt">pt</option>
|
||||
</param>
|
||||
</hbox>
|
||||
<hbox indent="1">
|
||||
<param name="fill" type="color" appearance="colorbutton" gui-text="Color:">255</param>
|
||||
</hbox>
|
||||
<effect needs-live-preview="true">
|
||||
<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">polar_grid.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
184
extensions/fablabchemnitz/bobbinlace/polar_grid.py
Normal file
@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright 2015 Jo Pol
|
||||
# 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/.
|
||||
|
||||
from __future__ import division
|
||||
from math import pi, sin, cos, tan, radians
|
||||
from lxml import etree
|
||||
|
||||
# We will use the inkex module with the predefined
|
||||
# Effect base class.
|
||||
import inkex
|
||||
|
||||
__author__ = 'Jo Pol'
|
||||
__credits__ = ['Veronika Irvine','Jo Pol','Mark Shafer']
|
||||
__license__ = 'GPLv3'
|
||||
|
||||
class PolarGrid(inkex.EffectExtension):
|
||||
|
||||
def add_arguments(self, pars):
|
||||
pars.add_argument('-a', '--angle', type=float, default=45, help='grid angle (degrees)')
|
||||
pars.add_argument('-d', '--dots', type=int, default=180, help='number of dots on a circle')
|
||||
pars.add_argument('-o', '--outerDiameter', type=float, default=160, help='outer diameter (mm)')
|
||||
pars.add_argument('-i', '--innerDiameter', type=float, default=100, help='minimum inner diameter (mm)')
|
||||
pars.add_argument('-f', '--fill', type=inkex.Color, default='-6711040', help='dot color')
|
||||
pars.add_argument('-A', '--alignment', default='outside', help='exact diameter on [inside|outside]')
|
||||
pars.add_argument('-s', '--size', type=float, default=0.5, help='dot diameter (mm)')
|
||||
pars.add_argument('-v', '--variant', default='', help='omit rows to get [|rectangle|hexagon1]')
|
||||
pars.add_argument('-cu', '--circleDiameterUnits', default = 'mm', help = 'Circle diameter is measured in these units')
|
||||
pars.add_argument('-du', '--dotUnits', default = 'px', help = 'Dot diameter is measured in these unites')
|
||||
|
||||
def group(self, diameter):
|
||||
"""
|
||||
Create a group labeled with the diameter
|
||||
"""
|
||||
label = 'diameter: {0:.2f} mm'.format(diameter)
|
||||
attribs = {inkex.addNS('label', 'inkscape'):label}
|
||||
return etree.SubElement(self.gridContainer, inkex.addNS('g', 'svg'), attribs)
|
||||
|
||||
def dots(self, diameter, circleNr, group):
|
||||
"""
|
||||
Draw dots on a grid circle
|
||||
"""
|
||||
offset = (circleNr % 2) * 0.5
|
||||
for dotNr in range (0, self.options.dots):
|
||||
a = (dotNr + offset) * self.alpha
|
||||
x = (diameter / 2.0) * cos(a)
|
||||
y = (diameter / 2.0) * sin(a)
|
||||
attribs = {'style':self.dotStyle, 'cx':str(x * self.circleScale), 'cy':str(y * self.circleScale), 'r':self.dotR}
|
||||
etree.SubElement(group, inkex.addNS('circle', 'svg'), attribs)
|
||||
|
||||
def iterate(self, diameter, circleNr):
|
||||
"""
|
||||
Create a group with a ring of dots.
|
||||
Returns half of the arc length between the dots
|
||||
which becomes the distance to the next ring.
|
||||
"""
|
||||
group = self.group(diameter)
|
||||
self.dots(diameter, circleNr, group)
|
||||
self.generatedCircles.append(group)
|
||||
return diameter * self.change
|
||||
|
||||
def generate(self):
|
||||
"""
|
||||
Generate rings with dots, either inside out or outside in
|
||||
"""
|
||||
circleNr = 0
|
||||
flag_error = False
|
||||
minimum = 2 * self.options.size * self.options.dots /pi
|
||||
if minimum < self.options.innerDiameter:
|
||||
minimum = self.options.innerDiameter
|
||||
else:
|
||||
flag_error = True
|
||||
if self.options.alignment == 'outside':
|
||||
diameter = self.options.outerDiameter
|
||||
while diameter > minimum:
|
||||
diameter -= self.iterate(diameter, circleNr)
|
||||
circleNr += 1
|
||||
else:
|
||||
diameter = minimum
|
||||
while diameter < self.options.outerDiameter:
|
||||
diameter += self.iterate(diameter, circleNr)
|
||||
circleNr += 1
|
||||
# Display message
|
||||
if flag_error:
|
||||
# Leave message on top
|
||||
font_height = 8
|
||||
text_style = { 'font-size': str(font_height),
|
||||
'font-family': 'sans-serif',
|
||||
'text-anchor': 'middle',
|
||||
'text-align': 'center',
|
||||
'fill': '#000000' }
|
||||
text_atts = {'style':str(inkex.Style(text_style)),
|
||||
'x': '0', 'y': '0'}
|
||||
text = etree.SubElement(self.gridContainer, 'text', text_atts)
|
||||
text.text = "Dots overlap. inner changed to %4.1f" % (minimum)
|
||||
|
||||
def removeGroups(self, start, increment):
|
||||
"""
|
||||
Remove complete rings with dots
|
||||
"""
|
||||
for i in range(start, len(self.generatedCircles), increment):
|
||||
self.svg.get_current_layer().remove(self.generatedCircles[i])
|
||||
|
||||
def removeDots(self, i, offset, step):
|
||||
"""
|
||||
Remove dots from one circle
|
||||
"""
|
||||
group = self.generatedCircles[i]
|
||||
dots = list(group)
|
||||
start = len(dots) - 1 - offset
|
||||
for j in range(start, -1, 0-step):
|
||||
group.remove(dots[j])
|
||||
|
||||
def computations(self, angle):
|
||||
self.alpha = radians(360.0 / self.options.dots)
|
||||
correction = pi / (4 * self.options.dots)
|
||||
correction *= tan(angle*0.93)
|
||||
self.change = tan(angle - correction) * pi / self.options.dots
|
||||
|
||||
def effect(self):
|
||||
"""
|
||||
Effect behaviour.
|
||||
Overrides base class' method and draws something.
|
||||
"""
|
||||
|
||||
# constants
|
||||
self.dotStyle = str(inkex.Style({'fill': self.options.fill.to_rgb(),'stroke':'none'}))
|
||||
self.dotScale = self.svg.unittouu("1" + self.options.dotUnits)
|
||||
self.dotR = str(self.options.size * (self.dotScale/2))
|
||||
self.circleScale = self.svg.unittouu("1" + self.options.circleDiameterUnits)
|
||||
self.computations(radians(self.options.angle))
|
||||
|
||||
# processing variables
|
||||
self.generatedCircles = []
|
||||
self.gridContainer = self.svg.get_current_layer()
|
||||
|
||||
self.generate()
|
||||
|
||||
if self.options.variant == 'rectangle':
|
||||
self.removeGroups(1, 2)
|
||||
elif self.options.variant == 'hexagon1':
|
||||
self.removeGroups(0, 3)
|
||||
elif self.options.variant == 'hexagon2' or self.options.variant == 'snow2':
|
||||
for i in range(0, len(self.generatedCircles), 1):
|
||||
self.removeDots(i, (((i%2)+1)*2)%3, 3)
|
||||
elif self.options.variant == 'hexagon3':
|
||||
for i in range(0, len(self.generatedCircles), 2):
|
||||
self.removeDots(i, (i//2+1)%2, 2)
|
||||
elif self.options.variant == 'hexagon4':
|
||||
self.removeGroups(0, 4)
|
||||
elif self.options.variant == 'hexagon5' or self.options.variant == 'snow1':
|
||||
for i in range(0, len(self.generatedCircles), 2):
|
||||
self.removeDots(i, 1, 2)
|
||||
|
||||
self.dotStyle = str(inkex.Style({'fill': 'none','stroke':self.options.fill.to_rgb(),'stroke-width':0.7}))
|
||||
self.dotR = str((((self.options.innerDiameter * pi) / self.options.dots) / 2) * self.dotScale)
|
||||
self.generatedCircles = []
|
||||
if self.options.variant == 'snow2':
|
||||
self.options.dots = self.options.dots // 3
|
||||
self.computations(radians(self.options.angle))
|
||||
self.generate()
|
||||
elif self.options.variant == 'snow1':
|
||||
self.generate()
|
||||
self.removeGroups(1, 2)
|
||||
for i in range(0, len(self.generatedCircles), 2):
|
||||
self.removeDots(i, i%4, 2)
|
||||
for i in range(0, len(self.generatedCircles), 2):
|
||||
self.removeDots(i, (i+1)%2, 2)
|
||||
for i in range(2, len(self.generatedCircles), 4):
|
||||
self.removeDots(i, 0, self.options.dots)
|
||||
|
||||
if __name__ == '__main__':
|
||||
PolarGrid().run()
|
60
extensions/fablabchemnitz/bobbinlace/regular_grid.inx
Normal file
@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Bobbin Lace - Regular Grid</name>
|
||||
<id>fablabchemnitz.de.bobbinlace.regular_grid</id>
|
||||
<label appearance="header">Creates a grid of dots of specified angle.</label>
|
||||
<spacer/>
|
||||
<label appearance="header">Grid description</label>
|
||||
<hbox indent="1">
|
||||
<param name="angle" type="float" precision="1" min="30" max="89" gui-text="Angle (degrees):">45.0</param>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<param name="distance" type="float" precision="2" min="0.01" max="1000" gui-text="Distance between footside pins:">5.0</param>
|
||||
<param name="pinunits" gui-text=" " type="optiongroup" appearance="combo">
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="in">in</option>
|
||||
<option value="px">px</option>
|
||||
<option value="pt">pt</option>
|
||||
</param>
|
||||
</hbox>
|
||||
<label appearance="header">Patch description</label>
|
||||
<hbox indent="1">
|
||||
<param name="width" type="float" precision="2" min="0.1" max="1000" gui-text="Width:">50</param>
|
||||
<param name="patchunits" gui-text=" " type="optiongroup" appearance="combo">
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="in">in</option>
|
||||
<option value="px">px</option>
|
||||
<option value="pt">pt</option>
|
||||
</param>
|
||||
</hbox>
|
||||
<hbox indent="1">
|
||||
<param name="height" type="float" precision="2" min="0.1" max="1000" gui-text="Height:">50</param>
|
||||
</hbox>
|
||||
<label appearance="header">Dot Appearance</label>
|
||||
<hbox indent="1">
|
||||
<param name="dotwidth" type="float" precision="2" min="0.01" max="1000" gui-text="Diameter:">2</param>
|
||||
<param name="dotunits" gui-text=" " type="optiongroup" appearance="combo">
|
||||
<option value="px">px</option>
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="in">in</option>
|
||||
<option value="pt">pt</option>
|
||||
</param>
|
||||
</hbox>
|
||||
<hbox indent="1">
|
||||
<param name="dotcolor" type="color" appearance="colorbutton" gui-text="Color:">255</param>
|
||||
</hbox>
|
||||
<effect needs-live-preview="true">
|
||||
<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">regular_grid.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
107
extensions/fablabchemnitz/bobbinlace/regular_grid.py
Normal file
@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (c) 2017, Veronika Irvine
|
||||
# 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.
|
||||
#
|
||||
# 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 OWNER 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.
|
||||
|
||||
from math import sin, cos, radians, ceil
|
||||
import inkex
|
||||
from lxml import etree
|
||||
|
||||
__author__ = 'Veronika Irvine'
|
||||
__credits__ = ['Ben Connors', 'Veronika Irvine', 'Mark Shafer']
|
||||
__license__ = 'Simplified BSD'
|
||||
|
||||
class RegularGrid(inkex.EffectExtension):
|
||||
def circle(self, x, y, r, fill):
|
||||
# define the stroke style
|
||||
s = {'fill': fill}
|
||||
|
||||
# create attributes from style and define path
|
||||
attribs = {'style':str(inkex.Style(s)),
|
||||
'cx':str(x),
|
||||
'cy':str(y),
|
||||
'r':str(r)}
|
||||
|
||||
# insert path object into current layer
|
||||
etree.SubElement(self.svg.get_current_layer(), inkex.addNS('circle', 'svg'), attribs)
|
||||
|
||||
def drawDot(self, x, y):
|
||||
self.circle(x, y, self.options.dotwidth, self.options.dotcolor)
|
||||
|
||||
def draw(self):
|
||||
|
||||
a = self.options.distance
|
||||
theta = self.options.angle
|
||||
|
||||
hgrid = a*sin(theta);
|
||||
vgrid = a*cos(theta)
|
||||
rows = int(ceil(self.options.height / vgrid))
|
||||
cols = int(ceil(self.options.width / hgrid))
|
||||
y = 0.0
|
||||
|
||||
for r in range(rows):
|
||||
x = 0.0
|
||||
if (r % 2 == 1):
|
||||
x += hgrid
|
||||
|
||||
for c in range(ceil(cols/2)):
|
||||
self.drawDot(x, y)
|
||||
x += 2.0*hgrid;
|
||||
|
||||
y += vgrid;
|
||||
|
||||
def add_arguments(self, pars):
|
||||
pars.add_argument('--angle', type=float)
|
||||
pars.add_argument('--distance', type=float)
|
||||
pars.add_argument('--pinunits')
|
||||
pars.add_argument('--width', type=float)
|
||||
pars.add_argument('--patchunits')
|
||||
pars.add_argument('--height', type=float)
|
||||
pars.add_argument('--dotwidth', type=float)
|
||||
pars.add_argument('--dotunits')
|
||||
pars.add_argument('--dotcolor', type=inkex.Color)
|
||||
|
||||
def effect(self):
|
||||
"""
|
||||
Effect behaviour.
|
||||
Overrides base class' method and draws something.
|
||||
"""
|
||||
# Convert user input to universal units
|
||||
self.options.width = self.svg.unittouu(str(self.options.width)+self.options.patchunits)
|
||||
self.options.height = self.svg.unittouu(str(self.options.height)+self.options.patchunits)
|
||||
self.options.distance = self.svg.unittouu(str(self.options.distance)+self.options.pinunits)
|
||||
# Convert from diameter to radius
|
||||
self.options.dotwidth = self.svg.unittouu(str(self.options.dotwidth)+self.options.dotunits)/2
|
||||
# Users expect distance to be the vertical distance between footside pins
|
||||
# (vertical distance between every other row) but in the script we use it
|
||||
# as as diagonal distance between grid points
|
||||
# therefore convert distance based on the angle chosen
|
||||
self.options.angle = radians(self.options.angle)
|
||||
self.options.distance = self.options.distance/(2.0*cos(self.options.angle))
|
||||
|
||||
# Draw a grid of dots based on user inputs
|
||||
self.options.dotcolor = self.options.dotcolor.to_rgb()
|
||||
self.draw()
|
||||
|
||||
if __name__ == '__main__':
|
||||
RegularGrid().run()
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/1x1_1.png
Normal file
After Width: | Height: | Size: 2.8 KiB |
2
extensions/fablabchemnitz/bobbinlace/templates/1x1_1.txt
Normal file
@ -0,0 +1,2 @@
|
||||
CHECKER 1 1
|
||||
[0,0,1,0,-1,1]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/2x1_2.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
3
extensions/fablabchemnitz/bobbinlace/templates/2x1_2.txt
Normal file
@ -0,0 +1,3 @@
|
||||
CHECKER 2 1
|
||||
[0,0,1,0,0,1]
|
||||
[0,1,-1,1,0,2]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/2x2_2.png
Normal file
After Width: | Height: | Size: 6.7 KiB |
3
extensions/fablabchemnitz/bobbinlace/templates/2x2_2.txt
Normal file
@ -0,0 +1,3 @@
|
||||
CHECKER 2 2
|
||||
[0,0,1,0,-1,0] [1,0,0,1,2,1]
|
||||
[0,1,1,1,-1,1] [1,1,0,2,2,2]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/2x2_5.png
Normal file
After Width: | Height: | Size: 5.7 KiB |
3
extensions/fablabchemnitz/bobbinlace/templates/2x2_5.txt
Normal file
@ -0,0 +1,3 @@
|
||||
CHECKER 2 2
|
||||
[0,0,1,0,-1,1] [1,0,2,0,1,1]
|
||||
[1,1,0,2,1,2]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/2x4_1.png
Normal file
After Width: | Height: | Size: 11 KiB |
3
extensions/fablabchemnitz/bobbinlace/templates/2x4_1.txt
Normal file
@ -0,0 +1,3 @@
|
||||
CHECKER 2 4
|
||||
[0,0,1,0,-1,0] [1,0,2,0,0,1] [2,0,3,0,1,1] [3,0,2,1,4,1]
|
||||
[0,1,1,1,-1,1] [1,1,2,1,0,2] [2,1,3,1,1,2] [3,1,2,2,4,2]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/2x4_10.png
Normal file
After Width: | Height: | Size: 9.1 KiB |
@ -0,0 +1,3 @@
|
||||
CHECKER 2 4
|
||||
[0,0,1,0,-1,1] [1,0,2,0,1,1] [2,0,1,1,3,1]
|
||||
[1,1,0,2,1,2] [3,1,2,2,4,2]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/2x4_11.png
Normal file
After Width: | Height: | Size: 10 KiB |
@ -0,0 +1,3 @@
|
||||
CHECKER 2 4
|
||||
[0,0,1,0,-1,1] [1,0,2,0,1,1] [2,0,2,1,3,1]
|
||||
[1,1,0,2,1,2] [2,1,1,1,2,2] [3,1,2,1,4,2]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/2x4_4.png
Normal file
After Width: | Height: | Size: 12 KiB |
3
extensions/fablabchemnitz/bobbinlace/templates/2x4_4.txt
Normal file
@ -0,0 +1,3 @@
|
||||
CHECKER 2 4
|
||||
[0,0,1,0,-1,0] [1,0,2,0,0,1] [2,0,1,1,3,1] [3,0,2,0,4,1]
|
||||
[0,1,1,1,-1,1] [1,1,2,1,0,2] [2,1,1,2,3,2] [3,1,2,1,4,2]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/2x4_7.png
Normal file
After Width: | Height: | Size: 9.4 KiB |
3
extensions/fablabchemnitz/bobbinlace/templates/2x4_7.txt
Normal file
@ -0,0 +1,3 @@
|
||||
CHECKER 2 4
|
||||
[0,0,1,0,-1,0] [1,0,2,0,1,1] [2,0,1,1,3,1] [3,0,2,0,3,1]
|
||||
[1,1,0,2,1,2] [3,1,3,2,4,2]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/2x4_8.png
Normal file
After Width: | Height: | Size: 9.3 KiB |
3
extensions/fablabchemnitz/bobbinlace/templates/2x4_8.txt
Normal file
@ -0,0 +1,3 @@
|
||||
CHECKER 2 4
|
||||
[0,0,1,0,-1,1] [1,0,2,0,1,1] [2,0,3,0,2,1] [3,0,4,0,3,1]
|
||||
[1,1,0,2,1,2] [2,1,1,1,2,2] [3,1,2,1,3,2]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/3x3_1.png
Normal file
After Width: | Height: | Size: 15 KiB |
4
extensions/fablabchemnitz/bobbinlace/templates/3x3_1.txt
Normal file
@ -0,0 +1,4 @@
|
||||
CHECKER 3 3
|
||||
[0,0,1,0,-1,0] [1,0,2,0,0,1] [2,0,1,1,3,1]
|
||||
[0,1,1,1,-1,1] [1,1,2,1,0,2] [2,1,1,2,3,2]
|
||||
[0,2,1,2,-1,2] [1,2,2,2,0,3] [2,2,1,3,3,3]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/3x3_3.png
Normal file
After Width: | Height: | Size: 8.8 KiB |
3
extensions/fablabchemnitz/bobbinlace/templates/3x3_3.txt
Normal file
@ -0,0 +1,3 @@
|
||||
CHECKER 3 3
|
||||
[0,0,1,0,0,2] [1,0,2,0,1,2] [2,0,3,0,2,2]
|
||||
[0,2,-1,2,0,3] [1,2,0,2,1,3] [2,2,1,2,2,3]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_10.png
Normal file
After Width: | Height: | Size: 20 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,-1,0] [1,0,2,0,0,1] [2,0,2,1,3,1] [3,0,2,0,3,1]
|
||||
[0,1,1,1,-1,2] [1,1,0,2,2,2] [2,1,1,1,2,2] [3,1,4,1,2,1]
|
||||
[0,2,-1,3,0,3] [2,2,3,2,1,3] [3,2,4,2,2,3]
|
||||
[0,3,1,3,0,4] [1,3,2,3,0,4] [2,3,3,3,1,4] [3,3,4,3,3,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_100.png
Normal file
After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,-1,0] [1,0,2,0,1,1] [2,0,1,1,3,1] [3,0,2,0,3,1]
|
||||
[1,1,0,2,1,2] [3,1,2,2,3,2]
|
||||
[0,2,1,2,0,4] [1,2,2,2,1,3] [2,2,3,2,1,3] [3,2,4,2,3,4]
|
||||
[1,3,0,4,1,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_101.png
Normal file
After Width: | Height: | Size: 16 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,-1,0] [1,0,2,0,1,1] [2,0,1,1,3,1] [3,0,2,0,3,1]
|
||||
[1,1,0,2,2,2] [3,1,2,2,3,2]
|
||||
[0,2,0,4,1,3] [2,2,3,2,1,3] [3,2,4,2,3,4]
|
||||
[1,3,0,4,1,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_102.png
Normal file
After Width: | Height: | Size: 16 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,-1,0] [1,0,2,0,1,1] [2,0,1,1,3,1] [3,0,2,0,3,1]
|
||||
[1,1,1,2,2,2] [3,1,2,2,3,2]
|
||||
[0,2,0,4,1,3] [1,2,0,2,1,3] [2,2,3,2,1,2] [3,2,4,2,3,4]
|
||||
[1,3,0,4,1,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_103.png
Normal file
After Width: | Height: | Size: 16 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,-1,0] [1,0,2,0,1,1] [2,0,1,1,2,2] [3,0,2,0,3,2]
|
||||
[1,1,0,2,2,2]
|
||||
[0,2,0,4,1,3] [2,2,3,2,1,3] [3,2,4,2,3,4]
|
||||
[1,3,0,4,1,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_104.png
Normal file
After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,-1,0] [1,0,2,0,1,1] [2,0,1,1,2,2] [3,0,2,0,3,2]
|
||||
[1,1,0,2,1,2]
|
||||
[0,2,1,2,0,4] [1,2,2,2,1,3] [2,2,3,2,1,3] [3,2,4,2,3,4]
|
||||
[1,3,0,4,1,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_105.png
Normal file
After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,-1,0] [1,0,2,0,1,1] [2,0,1,1,2,2] [3,0,2,0,3,2]
|
||||
[1,1,1,2,2,2]
|
||||
[0,2,0,4,1,3] [1,2,0,2,1,3] [2,2,3,2,1,2] [3,2,4,2,3,4]
|
||||
[1,3,0,4,1,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_106.png
Normal file
After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,-1,0] [1,0,2,0,1,2] [2,0,2,2,3,1] [3,0,2,0,3,1]
|
||||
[3,1,2,2,3,2]
|
||||
[0,2,0,4,1,3] [1,2,0,2,1,3] [2,2,3,2,1,2] [3,2,4,2,3,4]
|
||||
[1,3,0,4,1,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_107.png
Normal file
After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,-1,1] [1,0,2,0,1,1] [2,0,1,1,3,1]
|
||||
[1,1,0,2,1,2] [3,1,2,2,4,2]
|
||||
[0,2,1,2,0,4] [1,2,2,2,1,3] [2,2,1,3,2,4]
|
||||
[1,3,0,4,1,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_108.png
Normal file
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,-1,1] [1,0,2,0,1,1] [2,0,1,1,3,1]
|
||||
[1,1,0,2,2,2] [3,1,2,2,4,2]
|
||||
[0,2,0,4,1,3] [2,2,1,3,2,4]
|
||||
[1,3,0,4,1,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_109.png
Normal file
After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,-1,1] [1,0,2,0,1,1] [2,0,1,1,3,1]
|
||||
[1,1,1,2,2,2] [3,1,2,2,4,2]
|
||||
[0,2,0,4,1,3] [1,2,0,2,1,3] [2,2,1,2,2,4]
|
||||
[1,3,0,4,1,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_11.png
Normal file
After Width: | Height: | Size: 20 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,-1,0] [1,0,2,0,0,1] [2,0,2,1,3,1] [3,0,2,0,3,1]
|
||||
[0,1,1,1,-1,2] [1,1,1,2,2,2] [2,1,1,1,2,2] [3,1,4,1,2,1]
|
||||
[0,2,-1,3,0,3] [1,2,0,2,1,3] [2,2,3,2,1,2] [3,2,4,2,2,3]
|
||||
[0,3,1,3,0,4] [1,3,2,3,0,4] [2,3,3,3,1,4] [3,3,4,3,3,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_110.png
Normal file
After Width: | Height: | Size: 16 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,-1,1] [1,0,2,0,1,1] [2,0,2,1,3,1]
|
||||
[1,1,0,2,1,2] [2,1,1,1,2,2] [3,1,2,1,4,2]
|
||||
[0,2,1,2,0,4] [1,2,2,2,1,3] [2,2,1,3,2,4]
|
||||
[1,3,0,4,1,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_111.png
Normal file
After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,-1,1] [1,0,2,0,1,1] [2,0,2,1,3,1]
|
||||
[1,1,0,2,2,2] [2,1,1,1,2,2] [3,1,2,1,4,2]
|
||||
[0,2,0,4,1,3] [2,2,1,3,2,4]
|
||||
[1,3,0,4,1,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_112.png
Normal file
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,-1,1] [1,0,2,0,1,2] [2,0,2,2,3,1]
|
||||
[3,1,2,2,4,2]
|
||||
[0,2,0,4,1,3] [1,2,0,2,1,3] [2,2,1,2,2,4]
|
||||
[1,3,0,4,1,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_113.png
Normal file
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,-1,1] [1,0,2,0,1,2] [2,0,3,0,2,2] [3,0,4,0,3,1]
|
||||
[3,1,3,2,4,2]
|
||||
[0,2,-1,2,1,3] [1,2,0,2,1,3] [2,2,1,2,2,4] [3,2,2,2,3,4]
|
||||
[1,3,0,4,1,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_114.png
Normal file
After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,0,1] [1,0,2,0,1,1] [2,0,1,1,3,1]
|
||||
[0,1,-1,1,0,2] [1,1,0,1,2,2] [3,1,2,2,4,2]
|
||||
[0,2,0,4,1,3] [2,2,1,3,2,4]
|
||||
[1,3,0,4,1,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_115.png
Normal file
After Width: | Height: | Size: 16 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,0,1] [1,0,2,0,1,1] [2,0,2,1,3,1]
|
||||
[0,1,-1,1,0,2] [1,1,0,1,1,2] [2,1,1,1,2,2] [3,1,2,1,4,2]
|
||||
[0,2,1,2,0,4] [1,2,2,2,1,3] [2,2,1,3,2,4]
|
||||
[1,3,0,4,1,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_116.png
Normal file
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,0,1] [1,0,2,0,1,1] [2,0,2,1,3,1]
|
||||
[0,1,-1,1,0,2] [1,1,0,1,2,2] [2,1,1,1,2,2] [3,1,2,1,4,2]
|
||||
[0,2,0,4,1,3] [2,2,1,3,2,4]
|
||||
[1,3,0,4,1,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_117.png
Normal file
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,1,1] [1,0,2,0,1,1] [2,0,3,0,3,1] [3,0,4,0,3,1]
|
||||
[1,1,0,2,1,2] [3,1,2,2,3,2]
|
||||
[0,2,-1,2,1,3] [1,2,0,2,1,3] [2,2,1,2,2,4] [3,2,2,2,3,4]
|
||||
[1,3,0,4,1,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_118.png
Normal file
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,1,1] [1,0,2,0,1,1] [2,0,3,0,2,2] [3,0,4,0,3,2]
|
||||
[1,1,0,2,1,2]
|
||||
[0,2,-1,2,1,3] [1,2,0,2,1,3] [2,2,1,2,2,4] [3,2,2,2,3,4]
|
||||
[1,3,0,4,1,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_119.png
Normal file
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,0,2] [1,0,2,0,1,1] [2,0,3,0,1,1] [3,0,4,0,3,2]
|
||||
[1,1,1,2,2,2]
|
||||
[0,2,-1,2,1,3] [1,2,0,2,1,3] [2,2,1,2,2,4] [3,2,2,2,3,4]
|
||||
[1,3,0,4,1,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_12.png
Normal file
After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,-1,0] [1,0,0,1,2,1] [2,0,3,0,1,0] [3,0,2,1,4,1]
|
||||
[0,1,1,1,-1,1] [1,1,0,2,2,2] [2,1,3,1,1,1] [3,1,2,2,4,2]
|
||||
[0,2,-1,3,0,3] [2,2,1,3,2,3]
|
||||
[0,3,1,3,0,4] [1,3,2,3,0,4] [2,3,3,3,2,4] [3,3,4,3,2,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_120.png
Normal file
After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,0,2] [1,0,2,0,1,1] [2,0,3,0,1,1] [3,0,4,0,3,2]
|
||||
[1,1,0,2,2,2]
|
||||
[0,2,-1,2,1,3] [2,2,1,3,2,4] [3,2,2,2,3,4]
|
||||
[1,3,0,4,1,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_121.png
Normal file
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,0,2] [1,0,2,0,1,2] [2,0,3,0,3,1] [3,0,4,0,3,1]
|
||||
[3,1,2,2,3,2]
|
||||
[0,2,-1,2,1,3] [1,2,0,2,1,3] [2,2,1,2,2,4] [3,2,2,2,3,4]
|
||||
[1,3,0,4,1,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_122.png
Normal file
After Width: | Height: | Size: 13 KiB |
@ -0,0 +1,4 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,0,2] [1,0,2,0,1,2] [2,0,3,0,2,2] [3,0,4,0,3,2]
|
||||
[0,2,-1,2,1,3] [1,2,0,2,1,3] [2,2,1,2,2,4] [3,2,2,2,3,4]
|
||||
[1,3,0,4,1,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_124.png
Normal file
After Width: | Height: | Size: 13 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,-1,1] [1,0,2,0,1,1] [2,0,3,0,1,1] [3,0,4,0,3,1]
|
||||
[1,1,0,2,2,2] [3,1,2,2,4,2]
|
||||
[0,2,-1,3,1,3] [2,2,1,3,3,3]
|
||||
[1,3,0,4,1,4] [3,3,2,4,3,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_126.png
Normal file
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,-1,1] [1,0,2,0,1,1] [2,0,3,0,1,1] [3,0,4,0,3,1]
|
||||
[1,1,0,2,1,3] [3,1,3,3,4,2]
|
||||
[0,2,-1,3,1,3]
|
||||
[1,3,0,4,1,4] [3,3,2,4,3,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_127.png
Normal file
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,-1,1] [1,0,2,0,1,2] [2,0,3,0,2,2] [3,0,4,0,3,1]
|
||||
[3,1,3,2,4,2]
|
||||
[0,2,-1,2,1,3] [1,2,0,2,1,3] [2,2,1,2,3,3] [3,2,2,2,3,3]
|
||||
[1,3,0,4,1,4] [3,3,2,4,3,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_128.png
Normal file
After Width: | Height: | Size: 15 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,-1,1] [1,0,2,0,1,1] [2,0,3,0,2,1] [3,0,4,0,3,1]
|
||||
[1,1,0,2,1,2] [2,1,1,1,2,2] [3,1,2,1,3,2]
|
||||
[0,2,1,2,-1,3] [1,2,2,2,1,3] [2,2,3,2,1,3] [3,2,4,2,3,3]
|
||||
[1,3,0,4,1,4] [3,3,2,4,3,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_129.png
Normal file
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,0,1] [1,0,2,0,1,1] [2,0,3,0,2,1] [3,0,4,0,3,1]
|
||||
[0,1,1,1,0,2] [1,1,2,1,1,2] [2,1,3,1,2,2] [3,1,4,1,3,2]
|
||||
[0,2,-1,2,-1,3] [1,2,0,2,1,3] [2,2,1,2,1,3] [3,2,2,2,3,3]
|
||||
[1,3,0,4,1,4] [3,3,2,4,3,4]
|
BIN
extensions/fablabchemnitz/bobbinlace/templates/4x4_13.png
Normal file
After Width: | Height: | Size: 18 KiB |
@ -0,0 +1,5 @@
|
||||
CHECKER 4 4
|
||||
[0,0,1,0,-1,0] [1,0,0,1,2,1] [2,0,3,0,1,0] [3,0,2,1,4,1]
|
||||
[0,1,1,1,-1,1] [1,1,0,2,2,2] [2,1,3,1,1,1] [3,1,3,2,4,2]
|
||||
[0,2,-1,2,0,3] [2,2,1,3,2,3] [3,2,2,2,3,3]
|
||||
[0,3,1,3,0,4] [1,3,2,3,0,4] [2,3,3,3,2,4] [3,3,4,3,2,4]
|