More adds and fixes

This commit is contained in:
2022-11-05 10:41:20 +01:00
parent 3df556e4fa
commit fed542ba37
42 changed files with 5790 additions and 21 deletions

View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Lasercut Jigsaw</name>
<id>fablabchemnitz.de.lasercut_jigsaw</id>
<param name="tab" type="notebook">
<page name="Style" gui-text="Style">
<label>The Jigsaw lines color does not apply if 'Create separated pieces' option is enabled.</label>
<param name="color_border" type="color" appearance="colorbutton" gui-text="Border color">4278190335</param>
<param name="color_jigsaw" type="color" appearance="colorbutton" gui-text="Jigsaw lines color">65535</param>
</page>
<page name="Dimensions" gui-text="Dimensions">
<label>Define the Jigsaw size and grid size.</label>
<param name="sizetype" gui-text="Width/Height for ..." type="optiongroup" appearance="radio">
<option value="boxsize">box size</option>
<option value="partsize">part size</option>
</param>
<param name="width" type="float" min="0.1" max="1000.0" precision="2" gui-text="Width">100.0</param>
<param name="height" type="float" min="0.1" max="1000.0" precision="2" gui-text="Height">80.0</param>
<param name="innerradius" type="float" min="0.0" max="500.0" precision="2" gui-text="Corner radius" gui-description="0 implies square corners">5.0</param>
<param name="units" gui-text="Units" type="optiongroup" appearance="combo" gui-description="The unit of the box dimensions">
<option value="px">px</option>
<option value="pt">pt</option>
<option value="in">in</option>
<option value="cm">cm</option>
<option value="mm">mm</option>
</param>
<param name="border" type="bool" gui-text="Outer Border" gui-description="Add Outer Surround">false</param>
<param name="borderwidth" type="float" min="0.0" max="500.0" precision="2" gui-text="Border width" gui-description="Size of external surrounding border.">20.0</param>
<param name="outerradius" type="float" min="0.0" max="500.0" precision="2" gui-text="Border radius" gui-description="0 implies square corners">5.0</param>
<param name="pack" type="optiongroup" appearance="combo" gui-text="Pack Location" gui-description="Where to place backing piece on page">
<option value="Right">Right</option>
<option value="Below">Below</option>
<option value="Separate">Separate</option>
</param>
<param name="pieces_W" type="int" min="2" max="199" gui-text="How many pieces across (cols)">5</param>
<param name="pieces_H" type="int" min="2" max="199" gui-text="How many pieces down (rows)">4</param>
</page>
<page name="Notches" gui-text="Notches">
<label>The interlocking pieces can be shaped here. Also the random nature of the layout.</label>
<param name="notch_percent" type="float" min="0.0" max="1.0" precision="2" appearance="full" gui-text="Notch relative size" gui-description="Notch relative size. 0.15 to 0.50 is good">0.5</param>
<param name="rand" type="float" min="0.0" max="1.0" precision="2" appearance="full" gui-text="Grid Randomisation" gui-description="Amount to perturb the basic piece grid.">0.4</param>
<param name="smooth_edges" type="bool" gui-text="Some edges can be smooth" gui-description="Allow pieces with smooth edges.">false</param>
<param name="noknob_frequency" type="float" min="0.0" max="100.0" appearance="full" precision="2" gui-text="percentage of smooth edges">10</param>
<param name="use_seed" type="bool" gui-text="Random jigsaw" gui-description="Use the kerf value as the drawn line width">true</param>
<param name="seed" type="int" min="0" max="99999999" gui-text="or Jigsaw pattern (seed)" gui-description="Random seed for repeatability">12345</param>
<param name="pieces" type="bool" gui-text="Create separated pieces">false</param>
<param name="shift" type="float" min="0.0" max="100.0" precision="2" gui-text="Shifting for each piece (%)">0</param>
</page>
<page name="Usage" gui-text="Usage">
<label xml:space="preserve">Jigsaw lines are single for minimal laser cutting.
(The pieces are not discrete shapes.)
The outer edge can be a rectangle or have rounded corners.
A Surrounding border can be added to frame the jigsaw.
Notch size is related to the averaged Jigsaw piece size.
Randomization creates irregularity for unique pieces.
Adjust Notch size and Randomization to avoid overlapping lines:
- High values of randomization will cause overlapping lines
on small notches.
- Highly unbalanced grids (E.g. 9x2 with 0.5 notches) will
create overlapping lines.
</label>
</page>
</param>
<effect>
<object-type>all</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz Shape Generators">
<submenu name="Puzzles/Mazes/Nests" />
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">lasercut_jigsaw.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,507 @@
#!/usr/bin/env python3
'''
Copyright (C) 2011 Mark Schafer <neon.mark (a) gmail dot com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'''
# Build a Jigsaw puzzle for Lasercutting.
# User defines:
# - dimensions,
# - number of pieces in X and Y,
# - notch size,
# - random amount of perturbation for uniqueness,
# - border and rounding for border and inner corners
# - random or random seed for repeats
### 0.1 make basic jigsaw for lasercut - March 2011
### 0.2 add random seed so repeatable, add pieces for manual booleans - May 2011
### 0.3 add some no-knob edges - June 2019
### Todo
# add option to cut pieces:
# - taking two rows(cols) at a time - reverse the second one and concat on end - add z to close
# - taking a row and a col - do intersect = piece.
__version__ = "0.3"
import sys, math, random, copy
from lxml import etree
import inkex
from inkex import Path, CubicSuperPath, Color
from inkex.command import inkscape
def dirtyFormat(path):
return str(path).replace('[','').replace(']','').replace(',','').replace('\'','')
def randomize(x_y, radius, norm=True, absolute=False):
""" return x,y moved by a random amount inside a radius.
use uniform distribution unless
- norm = True - then use a normal distribution
If absolute is true - ensure random is only added to x,y """
# if norm:
# r = abs(random.normalvariate(0.0,0.5*radius))
# else:
# r = random.uniform(0.0,radius)
x, y = x_y
a = random.uniform(0.0,2*math.pi)
x += math.cos(a)*radius
y += math.sin(a)*radius
if absolute:
x = abs(x)
y = abs(y)
return [x, y]
def add_rounded_rectangle(startx, starty, radius, width, height, style, name, parent, mask=False):
line_path = [['M', [startx, starty+radius]]]
if radius > 0.0: # rounded corners
line_path.append(['c', [0, -radius/2, radius/2, -radius, radius, -radius]])
if mask == "Below":
line_path.append(['m', [width-2*radius, 0,]])
else:
line_path.append(['c', [radius/2, 0, width-2*radius-radius/2, 0, width-2*radius,0 ]]) # top line
line_path.append(['c', [radius/2, 0, radius, radius/2, radius, radius]])
line_path.append(['c', [0, radius/2, 0, height-2*radius-radius/2, 0, height-2*radius]]) # RHS line
line_path.append(['c', [0, radius/2, -radius/2, radius, -radius, radius]])
line_path.append(['c', [-radius/2,0, -width+2*radius+radius/2,0, -width+2*radius,0]]) # bottom line
line_path.append(['c', [-radius/2, 0, -radius, -radius/2, -radius, -radius]])
if mask == "Right":
line_path.append(['m', [0, height]])
else:
line_path.append(['c', [0, -radius/2, 0, -height+2*radius+radius/2, 0, -height+2*radius]]) # LHS line
else: # square corners
if mask == "Below":
line_path.append(['m', [width, 0]])
line_path.append(['l', [0, height, -width, 0, 0, -height]])
elif mask == "Right":
line_path.append(['l', [width, 0, 0, height, -width, 0,]])
else: # separate
line_path.append(['l', [width, 0, 0, height, -width, 0, 0, -height]])
#
#sys.stderr.write("%s\n"% line_path)
attribs = {'style':str(inkex.Style(style)), inkex.addNS('label','inkscape'):name, 'd':dirtyFormat(line_path)}
#sys.stderr.write("%s\n"% attribs)
etree.SubElement(parent, inkex.addNS('path','svg'), attribs )
###----------------------
### all for intersection from http://www.kevlindev.com/gui/index.htm
def get_derivative(polynomial):
deriv = []
for i in range(len(polynomial)):
deriv.append(i* polynomial[i])
return deriv
class LasercutJigsaw(inkex.EffectExtension):
def add_arguments(self, pars):
# General settings
pars.add_argument("--tab")
#Style
pars.add_argument("--color_border", type=Color, default='4278190335', help="Border color")
pars.add_argument("--color_jigsaw", type=Color, default='65535', help="Jigsaw lines color")
#Dimensions
pars.add_argument("--sizetype", default="boxsize")
pars.add_argument("--width", type=float, default=50.0)
pars.add_argument("--height", type=float, default=30.0)
pars.add_argument("--innerradius", type=float, default=5.0, help="0 implies square corners")
pars.add_argument("--units", default="cm", help="The unit of the box dimensions")
pars.add_argument("--border", type=inkex.Boolean, default=False, help="Add Outer Surround")
pars.add_argument("--borderwidth", type=float, default=10.0, help="Size of external surrounding border.")
pars.add_argument("--outerradius", type=float, default=5.0, help="0 implies square corners")
pars.add_argument("--pack", default="Below", help="Where to place backing piece on page")
pars.add_argument("--pieces_W", type=int, default=11, help="How many pieces across")
pars.add_argument("--pieces_H", type=int, default=11, help="How many pieces down")
#Notches
pars.add_argument("--notch_percent", type=float, default=0.0, help="Notch relative size. 0 to 1. 0.15 is good")
pars.add_argument("--rand", type=float, default=0.1, help="Amount to perturb the basic piece grid.")
pars.add_argument("--noknob_frequency", type=float, default=10, help="Percentage of smooth-sided edges.")
pars.add_argument("--smooth_edges", type=inkex.Boolean, default=False, help="Allow pieces with smooth edges.")
pars.add_argument("--use_seed", type=inkex.Boolean, default=False, help="Use the kerf value as the drawn line width")
pars.add_argument("--seed", type=int, default=12345, help="Random seed for repeatability")
pars.add_argument("--pieces", type=inkex.Boolean, default=False, help="Create separated pieces")
pars.add_argument("--shift", type=float, default=0.0, help="Shifting for each piece (%)")
def add_jigsaw_horiz_line(self, startx, starty, stepx, steps, width, style, name, parent):
""" complex version All C smooth
- get ctrl pt offset and use on both sides of each node (negate for smooth)"""
line_path = []
# starts with an M - then C with first point same as M = smooth (also last point still in C but doubled)
line_path.append(['M', [startx, starty]])
clist = [startx, starty] # duplicate 1st point so its smooth
for i in range(1,steps+1):
flip = 1
if random.uniform(0.0,1.0) < 0.5:
flip = -1
do_smooth = False
if self.smooth_edges:
if random.uniform(0.0,100.0) < self.noknob_frequency:
do_smooth = True
if do_smooth:
pt1 = randomize((startx+i*stepx/2+stepx/2*(i-1), starty), self.random_radius/3, True)
rand1 = randomize((0, 0), self.random_radius/4, True, True)
# up to pt1
ctrl1 = (-self.notch_step*1.5, self.notch_step*1.5)
clist.extend([pt1[0]+ctrl1[0]-rand1[0], pt1[1]-ctrl1[1]*flip+rand1[1]*flip])
clist.extend(pt1)
# last ctrl point for next step
clist.extend([pt1[0]-ctrl1[0]+rand1[0], pt1[1]+ctrl1[1]*flip-rand1[1]*flip])
else:
pt1 = randomize((startx-self.notch_step+i*stepx/2+stepx/2*(i-1), starty+self.notch_step/4*flip), self.random_radius/3, True)
pt2 = randomize((startx-self.notch_step+i*stepx/2+stepx/2*(i-1), starty-self.notch_step*flip), self.random_radius/3, True)
# pt3 is foor tip of the notch - required ?
pt4 = randomize((startx+self.notch_step+i*stepx/2+stepx/2*(i-1), starty-self.notch_step*flip), self.random_radius/3, True) #mirror of 2
pt5 = randomize((startx+self.notch_step+i*stepx/2+stepx/2*(i-1), starty+self.notch_step/4*flip), self.random_radius/3, True) # mirror of pt1
# Create random local value for x,y of handle - then reflect to enforce smoothness
rand1 = randomize((0, 0), self.random_radius/4, True, True)
rand2 = randomize((0, 0), self.random_radius/4, True, True)
rand4 = randomize((0, 0), self.random_radius/4, True, True)
rand5 = randomize((0, 0), self.random_radius/4, True, True)
# up to pt1
#ctrl1_2 = (startx+i*stepx/2+(i-1)*stepx/2, starty-self.notch_step/3)
ctrl1 = (self.notch_step/1.2, -self.notch_step/3)
clist.extend([pt1[0]-ctrl1[0]-rand1[0], pt1[1]-ctrl1[1]*flip+rand1[1]*flip])
clist.extend(pt1)
# up to pt2
clist.extend([pt1[0]+ctrl1[0]+rand1[0], pt1[1]+ctrl1[1]*flip-rand1[1]*flip])
ctrl2 = (0, -self.notch_step/1.2)
clist.extend([pt2[0]+ctrl2[0]-rand2[0], pt2[1]-ctrl2[1]*flip+rand2[1]*flip])
clist.extend(pt2)
# up to pt4
clist.extend([pt2[0]-ctrl2[0]+rand2[0], pt2[1]+ctrl2[1]*flip-rand2[1]*flip])
ctrl4 = (0, self.notch_step/1.2)
clist.extend([pt4[0]+ctrl4[0]-rand4[0], pt4[1]-ctrl4[1]*flip+rand4[1]*flip])
clist.extend(pt4)
# up to pt5
clist.extend([pt4[0]-ctrl4[0]+rand4[0], pt4[1]+ctrl4[1]*flip-rand4[1]*flip])
ctrl5 = (self.notch_step/1.2, self.notch_step/3)
clist.extend([pt5[0]-ctrl5[0]+rand5[0], pt5[1]-ctrl5[1]*flip-rand5[1]*flip])
clist.extend(pt5)
# last ctrl point for next step
clist.extend([pt5[0]+ctrl5[0]-rand5[0], pt5[1]+ctrl5[1]*flip+rand5[1]*flip])
#
clist.extend([width, starty, width, starty]) # doubled up at end for smooth curve
line_path.append(['C',clist])
borderLineStyle = str(inkex.Style(style))
attribs = { 'style':borderLineStyle, 'id':name, 'd':dirtyFormat(line_path)}
etree.SubElement(parent, inkex.addNS('path','svg'), attribs )
def create_horiz_blocks(self, group, gridy, style):
path = lastpath = 0
blocks = []
count = 0
for node in gridy.iterchildren():
if node.tag == inkex.addNS('path','svg'): # which they ALL should because we just made them
path = CubicSuperPath(node.get('d')) # turn it into a global C style SVG path
#sys.stderr.write("count: %d\n"% count)
if count == 0: # first one so use the top border
spath = node.get('d') # work on string instead of cubicpath
lastpoint = spath.split()[-2:]
lastx = float(lastpoint[0])
lasty = float(lastpoint[1])
#sys.stderr.write("lastpoint: %s\n"% lastpoint)
spath += ' %f %f %f %f %f %f' % (lastx,lasty-self.inner_radius, lastx,1.5*self.inner_radius, lastx,self.inner_radius)
spath += ' %f %f %f %f %f %f' % (self.width,self.inner_radius/2, self.width-self.inner_radius/2,0, self.width-self.inner_radius,0)
spath += ' %f %f %f %f %f %f' % (self.width-self.inner_radius/2,0, 1.5*self.inner_radius,0, self.inner_radius, 0)
spath += ' %f %f %f %f %f %f' % (self.inner_radius/2, 0, 0,self.inner_radius/2, 0,self.inner_radius)
spath += 'z'
#sys.stderr.write("spath: %s\n"% spath)
#
name = "RowPieces_%d" % (count)
attribs = { 'style':style, 'id':name, 'd':spath }
n = etree.SubElement(group, inkex.addNS('path','svg'), attribs )
blocks.append(n) # for direct traversal later
else: # internal line - concat a reversed version with the last one
thispath = copy.deepcopy(path)
for i in range(len(thispath[0])): # reverse the internal C pairs
thispath[0][i].reverse()
thispath[0].reverse() # reverse the entire line
lastpath[0].extend(thispath[0]) # append
name = "RowPieces_%d" % (count)
attribs = { 'style':style, 'id':name, 'd':dirtyFormat(lastpath) }
n = etree.SubElement(group, inkex.addNS('path','svg'), attribs )
blocks.append(n) # for direct traversal later
n.set('d', n.get('d')+'z') # close it
#
count += 1
lastpath = path
# do the last row
spath = node.get('d') # work on string instead of cubicpath
lastpoint = spath.split()[-2:]
lastx = float(lastpoint[0])
lasty = float(lastpoint[1])
#sys.stderr.write("lastpoint: %s\n"% lastpoint)
spath += ' %f %f %f %f %f %f' % (lastx,lasty+self.inner_radius, lastx,self.height-1.5*self.inner_radius, lastx,self.height-self.inner_radius)
spath += ' %f %f %f %f %f %f' % (self.width,self.height-self.inner_radius/2, self.width-self.inner_radius/2,self.height, self.width-self.inner_radius,self.height)
spath += ' %f %f %f %f %f %f' % (self.width-self.inner_radius/2,self.height, 1.5*self.inner_radius,self.height, self.inner_radius, self.height)
spath += ' %f %f %f %f %f %f' % (self.inner_radius/2, self.height, 0,self.height-self.inner_radius/2, 0,self.height-self.inner_radius)
spath += 'z'
#
name = "RowPieces_%d" % (count)
attribs = { 'style':style, 'id':name, 'd':spath }
n = etree.SubElement(group, inkex.addNS('path','svg'), attribs )
blocks.append(n) # for direct traversal later
#
return(blocks)
def create_vert_blocks(self, group, gridx, style):
path = lastpath = 0
blocks = []
count = 0
for node in gridx.iterchildren():
if node.tag == inkex.addNS('path','svg'): # which they ALL should because we just made them
path = CubicSuperPath(node.get('d')) # turn it into a global C style SVG path
#sys.stderr.write("count: %d\n"% count)
if count == 0: # first one so use the right border
spath = node.get('d') # work on string instead of cubicpath
lastpoint = spath.split()[-2:]
lastx = float(lastpoint[0])
lasty = float(lastpoint[1])
#sys.stderr.write("lastpoint: %s\n"% lastpoint)
spath += ' %f %f %f %f %f %f' % (lastx+self.inner_radius/2,lasty, self.width-1.5*self.inner_radius,lasty, self.width-self.inner_radius, lasty)
spath += ' %f %f %f %f %f %f' % (self.width-self.inner_radius/2,lasty, self.width,self.height-self.inner_radius/2, self.width,self.height-self.inner_radius)
spath += ' %f %f %f %f %f %f' % (self.width,self.height-1.5*self.inner_radius, self.width, 1.5*self.inner_radius, self.width,self.inner_radius)
spath += ' %f %f %f %f %f %f' % (self.width,self.inner_radius/2, self.width-self.inner_radius/2,0, self.width-self.inner_radius,0)
spath += 'z'
#sys.stderr.write("spath: %s\n"% spath)
#
name = "ColPieces_%d" % (count)
attribs = { 'style':style, 'id':name, 'd':spath }
n = etree.SubElement(group, inkex.addNS('path','svg'), attribs )
blocks.append(n) # for direct traversal later
else: # internal line - concat a reversed version with the last one
thispath = copy.deepcopy(path)
for i in range(len(thispath[0])): # reverse the internal C pairs
thispath[0][i].reverse()
thispath[0].reverse() # reverse the entire line
lastpath[0].extend(thispath[0]) # append
name = "ColPieces_%d" % (count)
attribs = { 'style':style, 'id':name, 'd':dirtyFormat(lastpath) }
n = etree.SubElement(group, inkex.addNS('path','svg'), attribs )
blocks.append(n) # for direct traversal later
n.set('d', n.get('d')+'z') # close it
#
count += 1
lastpath = path
# do the last one (LHS)
spath = node.get('d') # work on string instead of cubicpath
lastpoint = spath.split()[-2:]
lastx = float(lastpoint[0])
lasty = float(lastpoint[1])
#sys.stderr.write("lastpoint: %s\n"% lastpoint)
spath += ' %f %f %f %f %f %f' % (lastx-self.inner_radius,lasty, 1.5*self.inner_radius, lasty, self.inner_radius,lasty)
spath += ' %f %f %f %f %f %f' % (self.inner_radius/2,lasty, 0,lasty-self.inner_radius/2, 0,lasty-self.inner_radius)
spath += ' %f %f %f %f %f %f' % (0,lasty-1.5*self.inner_radius, 0,1.5*self.inner_radius, 0,self.inner_radius)
spath += ' %f %f %f %f %f %f' % (self.inner_radius/2,0, self.inner_radius,0, 1.5*self.inner_radius, 0)
spath += 'z'
#
name = "ColPieces_%d" % (count)
attribs = { 'style':style, 'id':name, 'd':spath }
n = etree.SubElement(group, inkex.addNS('path','svg'), attribs )
blocks.append(n) # for direct traversal later
#
return(blocks)
def create_pieces(self, jigsaw, gridx, gridy):
""" Loop through each row """
# Treat outer edge carefully as border runs around. So special code the edges
# Internal lines should be in pairs - with second line reversed and appended to first. Close with a 'z'
# Create new group
g_attribs = {inkex.addNS('label','inkscape'):'JigsawPieces:X' + \
str( self.pieces_W )+':Y'+str( self.pieces_H ) }
jigsaw_pieces = etree.SubElement(jigsaw, 'g', g_attribs)
jigsaw_pieces_id = self.svg.get_unique_id("pieces-")
jigsaw_pieces.attrib['id'] = jigsaw_pieces_id
borderLineStyle = str(inkex.Style(self.borderLineStyle))
xblocks = self.create_horiz_blocks(jigsaw_pieces, gridy, borderLineStyle)
yblocks = self.create_vert_blocks(jigsaw_pieces, gridx, borderLineStyle)
# for each xblock intersect it with each Y block
puzzlePartNo = 1
allPathPairsToIntersect = []
allPathsToDelete = []
for x in range(len(xblocks)):
for y in range(len(yblocks)):
allPathPairsToIntersect.append([copy.copy(xblocks[x]), copy.copy(yblocks[y])])
allPathsToDelete.append(xblocks[x])
allPathsToDelete.append(yblocks[y])
for pair in allPathPairsToIntersect:
pair[0].attrib['id'] = str(puzzlePartNo) + "_X"
pair[1].attrib['id'] = str(puzzlePartNo) + "_Y"
xId = pair[0].get('id')
yId = pair[1].get('id')
#self.msg("intersecting {} with {}".format(xId, yId))
puzzlePartNo += 1
jigsaw_pieces.append(pair[0])
jigsaw_pieces.append(pair[1])
for pathToDelete in allPathsToDelete:
pathToDelete.delete()
actions_list = []
for pair in allPathPairsToIntersect:
actions_list.append("select-by-id:{}".format(pair[0].attrib['id']))
actions_list.append("select-by-id:{}".format(pair[1].attrib['id']))
actions_list.append("path-intersection")
actions_list.append("select-clear")
#self.msg(actions_list)
#workaround to fix it (we use export to tempfile instead processing and saving again)
tempfile = self.options.input_file + "-intersected.svg"
with open(tempfile, 'wb') as fp:
fp.write(self.svg.tostring())
actions_list.append("export-type:svg")
actions_list.append("export-filename:{}".format(tempfile))
actions_list.append("export-do")
actions = ";".join(actions_list)
#self.msg(actions)
cli_output = inkscape(tempfile, actions=actions) #process recent file
if len(cli_output) > 0:
self.msg("Inkscape returned the following output when trying to run the file export; the file export may still have worked:")
self.msg(cli_output)
# replace current document with content of temp copy file
self.document = inkex.load_svg(tempfile)
# update self.svg
self.svg = self.document.getroot()
row = 1
col = 1
offsetW = self.options.shift / 100 * (self.options.width / self.options.pieces_W)
offsetH = self.options.shift / 100 * (self.options.height / self.options.pieces_H)
for jigsaw_piece in self.svg.getElementById(jigsaw_pieces_id).getchildren():
jigsaw_piece.apply_transform()
jigsaw_piece.set('transform', 'translate(%f,%f)' % (-col * offsetH, 0))
jigsaw_piece.apply_transform()
jigsaw_piece.set('transform', 'translate(%f,%f)' % (0, row * offsetW))
jigsaw_piece.apply_transform()
currentPiece = int(jigsaw_piece.get('id').split('_')[0])
#self.msg("piece {} zeile {} Spalte {}".format(currentPiece, row, col))
if currentPiece % self.options.pieces_W == 0:
row += 1
col -= self.options.pieces_W
col += 1
return jigsaw_pieces_id
def effect(self):
# internal useful variables
self.stroke_width =str(self.svg.unittouu("1px")) # default for visiblity
self.borderLineStyle = {'stroke': self.options.color_border, 'fill': 'none', 'stroke-width': self.stroke_width,
'stroke-linecap': 'butt', 'stroke-linejoin': 'miter'}
self.jigsawLineStyle = {'stroke': self.options.color_jigsaw, 'fill': 'none', 'stroke-width': self.stroke_width,
'stroke-linecap': 'butt', 'stroke-linejoin': 'miter'}
# document dimensions (for centering)
docW = self.svg.unittouu(self.document.getroot().get('width'))
docH = self.svg.unittouu(self.document.getroot().get('height'))
# extract fields from UI
self.width = self.svg.unittouu( str(self.options.width) + self.options.units )
self.height = self.svg.unittouu( str(self.options.height) + self.options.units )
self.pieces_W = self.options.pieces_W
self.pieces_H = self.options.pieces_H
if self.options.sizetype == "partsize":
self.width = self.width * self.pieces_W
self.height = self.height * self.pieces_H
average_block = (self.width/self.pieces_W + self.height/self.pieces_H) / 2
self.notch_step = average_block * self.options.notch_percent / 3 # 3 = a useful notch size factor
self.smooth_edges = self.options.smooth_edges
self.noknob_frequency = self.options.noknob_frequency
self.random_radius = self.options.rand * average_block / 5 # 5 = a useful range factor
self.inner_radius = self.options.innerradius
if self.inner_radius < 0.01: self.inner_radius = 0.0 # snap to 0 for UI error when setting spinner to 0.0
self.border = self.options.border
self.borderwidth = self.options.borderwidth
self.outer_radius = self.options.outerradius
if self.outer_radius < 0.01: self.outer_radius = 0.0 # snap to 0 for UI error when setting spinner to 0.0
self.pack = self.options.pack
# pieces
self.pieces = self.options.pieces
# random function
if not self.options.use_seed:
random.seed(self.options.seed)
#
# set up the main object in the current layer - group gridlines
g_attribs = {inkex.addNS('label','inkscape'):'Jigsaw:X' + str(self.pieces_W )+':Y'+str(self.pieces_H) + "={}Pcs)".format(self.pieces_W * self.pieces_H)}
jigsaw_group = etree.SubElement(self.svg.get_current_layer(), 'g', g_attribs)
#Group for X grid
g_attribs = {inkex.addNS('label','inkscape'):'X_Gridlines'}
gridx = etree.SubElement(jigsaw_group, 'g', g_attribs)
#Group for Y grid
g_attribs = {inkex.addNS('label','inkscape'):'Y_Gridlines'}
gridy = etree.SubElement(jigsaw_group, 'g', g_attribs)
# Draw the Border
add_rounded_rectangle(0,0, self.inner_radius, self.width, self.height, self.borderLineStyle, 'innerborder', jigsaw_group)
# Do the Border
if self.border:
add_rounded_rectangle(-self.borderwidth,-self.borderwidth, self.outer_radius, self.borderwidth*2+self.width,
self.borderwidth*2+self.height, self.borderLineStyle, 'outerborder', jigsaw_group)
# make a second copy below the jigsaw for the cutout BG
if self.pack == "Below":
add_rounded_rectangle(-self.borderwidth,self.borderwidth+ self.height, self.outer_radius, self.borderwidth*2+self.width,
self.borderwidth*2+self.height, self.borderLineStyle, 'BG', jigsaw_group, self.pack)
elif self.pack == "Right":
add_rounded_rectangle(self.width+self.borderwidth,-self.borderwidth, self.outer_radius, self.borderwidth*2+self.width,
self.borderwidth*2+self.height, self.borderLineStyle, 'BG', jigsaw_group, self.pack)
else: # Separate
add_rounded_rectangle(self.width+self.borderwidth*2,-self.borderwidth, self.outer_radius, self.borderwidth*2+self.width,
self.borderwidth*2+self.height, self.borderLineStyle, 'BG', jigsaw_group)
# Step through the Grid
Xstep = self.width / (self.pieces_W)
Ystep = self.height / (self.pieces_H)
# Draw Horizontal lines on Y step with Xstep notches
for i in range(1, self.pieces_H):
self.add_jigsaw_horiz_line(0, Ystep*i, Xstep, self.pieces_W, self.width, self.jigsawLineStyle, 'YDiv'+str(i), gridy)
# Draw Vertical lines on X step with Ystep notches
for i in range(1, self.pieces_W):
self.add_jigsaw_horiz_line(0, Xstep*i, Ystep, self.pieces_H, self.height, self.jigsawLineStyle, 'XDiv'+str(i), gridx)
# Rotate lines into pos
# actualy transform can have multiple transforms in it e.g. 'translate(10,10) rotate(10)'
for node in gridx.iterchildren():
if node.tag == inkex.addNS('path','svg'):
node.set('transform', 'translate(%f,%f) rotate(90)' % (self.width, 0))
node.apply_transform()
# center the jigsaw
jigsaw_group.set('transform', 'translate(%f,%f)' % ( (docW-self.width)/2, (docH-self.height)/2 ) )
#inkex.utils.debug("Your puzzle consists out of {} pieces.".format(self.pieces_W * self.pieces_H))
# pieces
if self.pieces:
gridx.delete() #delete the previous x generated stuff because we have single pieces instead!
gridy.delete() #delete the previous y generated stuff because we have single pieces instead!
jigsaw_group.getchildren()[0].delete() #delete inner border
jigsaw_pieces_id = self.create_pieces(jigsaw_group, gridx,gridy)
for jigsaw_piece in self.svg.getElementById(jigsaw_pieces_id).getchildren():
jigsaw_piece.attrib['id'] = jigsaw_pieces_id + "_" + jigsaw_piece.attrib['id']
if __name__ == '__main__':
LasercutJigsaw().run()

View File

@ -0,0 +1,25 @@
[
{
"name": "Lasercut Jigsaw",
"id": "fablabchemnitz.de.lasercut_jigsaw",
"path": "lasercut_jigsaw",
"dependent_extensions": null,
"original_name": "Lasercut Jigsaw",
"original_id": "org.inkscape.LasercutJigsa",
"license": "GNU GPL v2",
"license_url": "https://github.com/Neon22/inkscape-jigsaw/blob/master/LICENSE",
"comment": "",
"source_url": "https://stadtfabrikanten.org/display/IFM/Lasercut+Jigsaw",
"fork_url": "https://github.com/Neon22/inkscape-jigsaw",
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Lasercut+Jigsaw",
"inkscape_gallery_url": null,
"main_authors": [
"github.com/Neon22",
"github.com/jonadem",
"github.com/speleo3",
"github.com/LynNor1",
"github.com/roeschter",
"github.com/vmario89"
]
}
]