Added back several extensions

This commit is contained in:
Mario Voigt 2022-10-03 02:49:07 +02:00
parent ed65606b93
commit a1f6bea8de
570 changed files with 15115 additions and 2 deletions

View File

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

View File

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

View 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"
]
}
]

View File

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

View File

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

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

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

View 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"
]
}
]

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

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

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

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,2 @@
CHECKER 1 1
[0,0,1,0,-1,1]

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -0,0 +1,3 @@
CHECKER 2 1
[0,0,1,0,0,1]
[0,1,-1,1,0,2]

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View 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]

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

View 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]

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View 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]

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View 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]

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View 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]

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

View 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]

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View 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]

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

View 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]

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

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

Some files were not shown because too many files have changed in this diff Show More