Another set of reintegrated extensions
This commit is contained in:
parent
a38a160484
commit
7cab1a92ea
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Apply Transformations</name>
|
||||
<id>fablabchemnitz.de.apply_transformations</id>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz">
|
||||
<submenu name="Transformations"/>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">apply_transformations.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# License: GPL2
|
||||
# Copyright Mark "Klowner" Riedesel
|
||||
# https://github.com/Klowner/inkscape-applytransforms
|
||||
#
|
||||
import copy
|
||||
import math
|
||||
from lxml import etree
|
||||
import re
|
||||
import inkex
|
||||
from inkex.paths import CubicSuperPath, Path
|
||||
from inkex.transforms import Transform
|
||||
from inkex.styles import Style
|
||||
|
||||
NULL_TRANSFORM = Transform([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]])
|
||||
|
||||
|
||||
class ApplyTransformations(inkex.EffectExtension):
|
||||
|
||||
def effect(self):
|
||||
if self.svg.selected:
|
||||
for id, shape in self.svg.selected.items():
|
||||
self.recursiveFuseTransform(shape)
|
||||
else:
|
||||
self.recursiveFuseTransform(self.document.getroot())
|
||||
|
||||
@staticmethod
|
||||
def objectToPath(element):
|
||||
if element.tag == inkex.addNS('g', 'svg'):
|
||||
return element
|
||||
|
||||
if element.tag == inkex.addNS('path', 'svg') or element.tag == 'path':
|
||||
for attName in element.attrib.keys():
|
||||
if ("sodipodi" in attName) or ("inkscape" in attName):
|
||||
del element.attrib[attName]
|
||||
return element
|
||||
|
||||
return element
|
||||
|
||||
def scaleStrokeWidth(self, element, transf):
|
||||
if 'style' in element.attrib:
|
||||
style = element.attrib.get('style')
|
||||
style = dict(Style.parse_str(style))
|
||||
update = False
|
||||
if 'stroke-width' in style:
|
||||
try:
|
||||
stroke_width = self.svg.unittouu(style.get('stroke-width')) / self.svg.unittouu("1px")
|
||||
stroke_width *= math.sqrt(abs(transf.a * transf.d - transf.b * transf.c))
|
||||
style['stroke-width'] = str(stroke_width)
|
||||
update = True
|
||||
except AttributeError as e:
|
||||
pass
|
||||
|
||||
if update:
|
||||
element.attrib['style'] = Style(style).to_str()
|
||||
|
||||
def recursiveFuseTransform(self, element, transf=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]):
|
||||
|
||||
transf = Transform(transf) @ Transform(element.get("transform", None)) #a, b, c, d = linear transformations / e, f = translations
|
||||
|
||||
if 'transform' in element.attrib:
|
||||
del element.attrib['transform']
|
||||
|
||||
element = ApplyTransformations.objectToPath(element)
|
||||
|
||||
if transf == NULL_TRANSFORM:
|
||||
# Don't do anything if there is effectively no transform applied
|
||||
# reduces alerts for unsupported elements
|
||||
pass
|
||||
elif 'd' in element.attrib:
|
||||
d = element.get('d')
|
||||
p = CubicSuperPath(d)
|
||||
p = Path(p).to_absolute().transform(transf, True)
|
||||
element.set('d', str(Path(CubicSuperPath(p).to_path())))
|
||||
|
||||
self.scaleStrokeWidth(element, transf)
|
||||
|
||||
elif element.tag in [inkex.addNS('polygon', 'svg'),
|
||||
inkex.addNS('polyline', 'svg')]:
|
||||
points = element.get('points')
|
||||
points = points.strip().split(' ')
|
||||
for k, p in enumerate(points):
|
||||
if ',' in p:
|
||||
p = p.split(',')
|
||||
p = [float(p[0]), float(p[1])]
|
||||
p = transf.apply_to_point(p)
|
||||
p = [str(p[0]), str(p[1])]
|
||||
p = ','.join(p)
|
||||
points[k] = p
|
||||
points = ' '.join(points)
|
||||
element.set('points', points)
|
||||
|
||||
self.scaleStrokeWidth(element, transf)
|
||||
|
||||
elif element.tag in [inkex.addNS("ellipse", "svg"), inkex.addNS("circle", "svg")]:
|
||||
|
||||
def isequal(a, b):
|
||||
return abs(a - b) <= transf.absolute_tolerance
|
||||
|
||||
if element.TAG == "ellipse":
|
||||
rx = float(element.get("rx"))
|
||||
ry = float(element.get("ry"))
|
||||
else:
|
||||
rx = float(element.get("r"))
|
||||
ry = rx
|
||||
|
||||
cx = float(element.get("cx"))
|
||||
cy = float(element.get("cy"))
|
||||
sqxy1 = (cx - rx, cy - ry)
|
||||
sqxy2 = (cx + rx, cy - ry)
|
||||
sqxy3 = (cx + rx, cy + ry)
|
||||
newxy1 = transf.apply_to_point(sqxy1)
|
||||
newxy2 = transf.apply_to_point(sqxy2)
|
||||
newxy3 = transf.apply_to_point(sqxy3)
|
||||
|
||||
element.set("cx", (newxy1[0] + newxy3[0]) / 2)
|
||||
element.set("cy", (newxy1[1] + newxy3[1]) / 2)
|
||||
edgex = math.sqrt(
|
||||
abs(newxy1[0] - newxy2[0]) ** 2 + abs(newxy1[1] - newxy2[1]) ** 2
|
||||
)
|
||||
edgey = math.sqrt(
|
||||
abs(newxy2[0] - newxy3[0]) ** 2 + abs(newxy2[1] - newxy3[1]) ** 2
|
||||
)
|
||||
|
||||
if not isequal(edgex, edgey) and (
|
||||
element.TAG == "circle"
|
||||
or not isequal(newxy2[0], newxy3[0])
|
||||
or not isequal(newxy1[1], newxy2[1])
|
||||
):
|
||||
inkex.utils.errormsg(
|
||||
"Warning: Shape %s (%s) is approximate only, try Object to path first for better results"
|
||||
% (element.TAG, element.get("id"))
|
||||
)
|
||||
|
||||
if element.TAG == "ellipse":
|
||||
element.set("rx", edgex / 2)
|
||||
element.set("ry", edgey / 2)
|
||||
else:
|
||||
element.set("r", edgex / 2)
|
||||
|
||||
# this is unstable at the moment
|
||||
elif element.tag == inkex.addNS("use", "svg"):
|
||||
href = None
|
||||
old_href_key = '{http://www.w3.org/1999/xlink}href'
|
||||
new_href_key = 'href'
|
||||
if element.attrib.has_key(old_href_key) is True: # {http://www.w3.org/1999/xlink}href (which gets displayed as 'xlink:href') attribute is deprecated. the newer attribute is just 'href'
|
||||
href = element.attrib.get(old_href_key)
|
||||
#element.attrib.pop(old_href_key)
|
||||
if element.attrib.has_key(new_href_key) is True:
|
||||
href = element.attrib.get(new_href_key) #we might overwrite the previous deprecated xlink:href but it's okay
|
||||
#element.attrib.pop(new_href_key)
|
||||
|
||||
#get the linked object from href attribute
|
||||
linkedObject = self.document.getroot().xpath("//*[@id = '%s']" % href.lstrip('#')) #we must remove hashtag symbol
|
||||
linkedObjectCopy = copy.copy(linkedObject[0])
|
||||
objectType = linkedObject[0].tag
|
||||
|
||||
if objectType == inkex.addNS("image", "svg"):
|
||||
mask = None #image might have an alpha channel
|
||||
new_mask_id = self.svg.get_unique_id("mask")
|
||||
newMask = None
|
||||
if element.attrib.has_key('mask') is True:
|
||||
mask = element.attrib.get('mask')
|
||||
#element.attrib.pop('mask')
|
||||
|
||||
#get the linked mask from mask attribute. We remove the old and create a new
|
||||
if mask is not None:
|
||||
linkedMask = self.document.getroot().xpath("//*[@id = '%s']" % mask.lstrip('url(#').rstrip(')')) #we must remove hashtag symbol
|
||||
linkedMask[0].delete()
|
||||
maskAttributes = {'id': new_mask_id}
|
||||
newMask = etree.SubElement(self.document.getroot(), inkex.addNS('mask', 'svg'), maskAttributes)
|
||||
|
||||
width = float(linkedObjectCopy.get('width')) * transf.a
|
||||
height = float(linkedObjectCopy.get('height')) * transf.d
|
||||
linkedObjectCopy.set('width', '{:1.6f}'.format(width))
|
||||
linkedObjectCopy.set('height', '{:1.6f}'.format(height))
|
||||
linkedObjectCopy.set('x', '{:1.6f}'.format(transf.e))
|
||||
linkedObjectCopy.set('y', '{:1.6f}'.format(transf.f))
|
||||
if newMask is not None:
|
||||
linkedObjectCopy.set('mask', 'url(#' + new_mask_id + ')')
|
||||
maskRectAttributes = {'x': '{:1.6f}'.format(transf.e), 'y': '{:1.6f}'.format(transf.f), 'width': '{:1.6f}'.format(width), 'height': '{:1.6f}'.format(height), 'style':'fill:#ffffff;'}
|
||||
maskRect = etree.SubElement(newMask, inkex.addNS('rect', 'svg'), maskRectAttributes)
|
||||
self.document.getroot().append(linkedObjectCopy) #for each svg:use we append a copy to the document root
|
||||
element.delete() #then we remove the use object
|
||||
else:
|
||||
#self.recursiveFuseTransform(linkedObjectCopy, transf)
|
||||
self.recursiveFuseTransform(element.unlink(), transf)
|
||||
|
||||
elif element.tag in [inkex.addNS('rect', 'svg'),
|
||||
inkex.addNS('text', 'svg'),
|
||||
inkex.addNS('image', 'svg')]:
|
||||
element.attrib['transform'] = str(transf)
|
||||
inkex.utils.errormsg(
|
||||
"Shape %s (%s) not yet supported. Not all transforms will be applied. Try Object to path first"
|
||||
% (element.TAG, element.get("id"))
|
||||
)
|
||||
else:
|
||||
# e.g. <g style="...">
|
||||
self.scaleStrokeWidth(element, transf)
|
||||
|
||||
for child in element.getchildren():
|
||||
self.recursiveFuseTransform(child, transf)
|
||||
|
||||
if __name__ == '__main__':
|
||||
ApplyTransformations().run()
|
21
extensions/fablabchemnitz/apply_transformations/meta.json
Normal file
21
extensions/fablabchemnitz/apply_transformations/meta.json
Normal file
@ -0,0 +1,21 @@
|
||||
[
|
||||
{
|
||||
"name": "Apply Transformations",
|
||||
"id": "fablabchemnitz.de.apply_transformations",
|
||||
"path": "apply_transformations",
|
||||
"dependent_extensions": null,
|
||||
"original_name": "Apply Transform",
|
||||
"original_id": "com.klowner.filter.apply_transform",
|
||||
"license": "GNU GPL v2",
|
||||
"license_url": "https://github.com/Klowner/inkscape-applytransforms/blob/master/LICENSE.txt",
|
||||
"comment": "",
|
||||
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/apply_transformations",
|
||||
"fork_url": "https://github.com/Klowner/inkscape-applytransforms",
|
||||
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Apply+Transformations",
|
||||
"inkscape_gallery_url": null,
|
||||
"main_authors": [
|
||||
"github.com/Klowner",
|
||||
"github.com/eridur-de"
|
||||
]
|
||||
}
|
||||
]
|
@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Box Maker - T-Slot</name>
|
||||
<id>fablabchemnitz.de.box_maker_t_slot</id>
|
||||
<param name="unit" gui-text="Unit" type="optiongroup" appearance="combo">
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="in">in</option>
|
||||
</param>
|
||||
<param name="inside" type="optiongroup" appearance="combo" gui-text="Box Dimensions">
|
||||
<option value="1">Inside</option>
|
||||
<option value="0">Outside</option>
|
||||
</param>
|
||||
<param name="length" type="float" precision="3" min="0.0" max="10000.0" gui-text="Length">80.0</param>
|
||||
<param name="width" type="float" precision="3" min="0.0" max="10000.0" gui-text="Width">80.0</param>
|
||||
<param name="depth" type="float" precision="3" min="0.0" max="10000.0" gui-text="Height">80.0</param>
|
||||
<param name="tab" type="float" precision="2" min="0.0" max="10000.0" gui-text="Minimum/Prefered Tab Width">10.0</param>
|
||||
<param name="equal" type="optiongroup" appearance="combo" gui-text="Tab Width">
|
||||
<option value="0">Fixed</option>
|
||||
<option value="1">Proportional</option>
|
||||
</param>
|
||||
<param name="thickness" type="float" precision="2" min="0.0" max="10000.0" gui-text="Material Thickness">6.0</param>
|
||||
<param name="kerf" type="float" precision="3" min="0.0" max="10000.0" gui-text="Kerf (cut width)">0.0</param>
|
||||
<param name="clearance" type="float" precision="3" min="0.0" max="10000.0" gui-text="Clearance">0.05</param>
|
||||
<param name="style" gui-text="Layout/Style" type="optiongroup" appearance="combo">
|
||||
<option value="1">Diagramatic</option>
|
||||
<option value="2">3 piece</option>
|
||||
<option value="3">Inline(compact)</option>
|
||||
<option value="4">Diag Alternate Tabs</option>
|
||||
</param>
|
||||
<param name="spacing" type="float" precision="2" min="0.0" max="10000.0" gui-text="Space Between Parts">5.0</param>
|
||||
<param name="screw_length" type="float" precision="2" min="0.0" max="10000.0" gui-text="Screw Length">12</param>
|
||||
<param name="screw_diameter" type="float" precision="2" min="0.0" max="10000.0" gui-text="Screw Diameter">3</param>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz Boxes/Papercraft">
|
||||
<submenu name="Finger-jointed/Tabbed Boxes"/>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">box_maker_t_slot.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
321
extensions/fablabchemnitz/box_maker_t_slot/box_maker_t_slot.py
Normal file
321
extensions/fablabchemnitz/box_maker_t_slot/box_maker_t_slot.py
Normal file
@ -0,0 +1,321 @@
|
||||
#! /usr/bin/env python3
|
||||
'''
|
||||
Generates Inkscape SVG file containing box components needed to
|
||||
laser cut a tabbed construction box taking kerf and clearance into account
|
||||
|
||||
Copyright (C) 2011 elliot white elliot@twot.eu
|
||||
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/>.
|
||||
'''
|
||||
__version__ = "0.8" ### please report bugs, suggestions etc to bugs@twot.eu ###
|
||||
|
||||
from ink_helper import *
|
||||
from lxml import etree
|
||||
|
||||
def drill(center, diameter, n_pt):
|
||||
from math import sin, cos, pi
|
||||
center = Vec2(center)
|
||||
radius = diameter / 2.
|
||||
out = Vec2([1, 0])
|
||||
up = Vec2([0, 1])
|
||||
path = Path([center + out * radius])
|
||||
dtheta = (2 * pi) / n_pt
|
||||
for i in range(n_pt + 1):
|
||||
path.append(center + out * radius * cos(i * dtheta) + up * radius * sin(i * dtheta))
|
||||
return path
|
||||
|
||||
def t_slot(center, orient, screw_diameter, nut_diameter):
|
||||
'''
|
||||
make one t-slot starting
|
||||
__
|
||||
| |
|
||||
-----------+ +-----+ ------
|
||||
| ^
|
||||
x center | screw_diameter x----------------------> orient
|
||||
| v
|
||||
-----------+ +-----+ ------
|
||||
| |
|
||||
--
|
||||
'''
|
||||
orient = Vec2(orient)
|
||||
out = orient / orient.norm()
|
||||
up = Vec2([out[1], -out[0]])
|
||||
center = Vec2(center)
|
||||
screw_r = screw_diameter / 2.
|
||||
nut_r = nut_diameter / 2.
|
||||
nut_w = screw_diameter
|
||||
path = Path([center + up * screw_r])
|
||||
path.append_from_last(orient)
|
||||
path.append_from_last(up * (nut_r - screw_r))
|
||||
path.append_from_last(out * (nut_w))
|
||||
path.append_from_last(-up * (nut_r - screw_r))
|
||||
path.append_from_last(out * (screw_length - thickness - orient.norm() - nut_w))
|
||||
path.append_from_last(-up * screw_r)
|
||||
path.extend(path.reflect(center, up).reverse())
|
||||
return path
|
||||
|
||||
def t_slots(rx, ry, sox, soy, eox, eoy, tabVec, length, dirx, diry, isTab, do_holes):
|
||||
# root startOffset endOffset tabVec length direction isTab
|
||||
|
||||
divs=int(length/nomTab) # divisions
|
||||
if not divs%2: divs-=1 # make divs odd
|
||||
divs=float(divs)
|
||||
tabs=(divs-1)/2 # tabs for side
|
||||
|
||||
if equalTabs:
|
||||
gapWidth=tabWidth=length/divs
|
||||
else:
|
||||
tabWidth=nomTab
|
||||
gapWidth=(length-tabs*nomTab)/(divs-tabs)
|
||||
|
||||
if isTab: # kerf correction
|
||||
gapWidth-=correction
|
||||
tabWidth+=correction
|
||||
first=correction/2
|
||||
else:
|
||||
gapWidth+=correction
|
||||
tabWidth-=correction
|
||||
first=-correction/2
|
||||
|
||||
s=[]
|
||||
firstVec=0; secondVec=tabVec
|
||||
dirxN=0 if dirx else 1 # used to select operation on x or y
|
||||
diryN=0 if diry else 1
|
||||
(Vx,Vy)=(rx+sox*thickness,ry+soy*thickness)
|
||||
nut_diameter = 2 * screw_diameter
|
||||
|
||||
step = Vec2([dirx * (tabWidth + gapWidth + firstVec * 2), diry * (tabWidth + gapWidth + firstVec * 2)])
|
||||
orient = Vec2([-diry * (screw_length - thickness - screw_diameter), dirx * (screw_length - thickness - screw_diameter)])
|
||||
center = Vec2(Vx + dirx * (gapWidth + tabWidth/2.), Vy + diry * (gapWidth + tabWidth/2.)) + (orient / orient.norm()) * thickness
|
||||
slot = t_slot(center, orient, screw_diameter, nut_diameter)
|
||||
hole = drill(center - (orient / orient.norm()) * (thickness * 1.5 + spacing), screw_diameter, 360)
|
||||
|
||||
slots = []
|
||||
holes = []
|
||||
for i in range(0, int(divs / 2), 1):
|
||||
slots.append(slot.translate(step * i))
|
||||
if do_holes:
|
||||
holes.append(hole.translate(step * i))
|
||||
holes.append(hole.translate(step * i - orient / orient.norm() * (Z - thickness) ))
|
||||
|
||||
out = [s.drawXY() for s in slots]
|
||||
out.extend([h.drawXY() for h in holes])
|
||||
return out
|
||||
|
||||
def side(rx, ry, sox, soy, eox, eoy, tabVec, length, dirx, diry, isTab):
|
||||
# root startOffset endOffset tabVec length direction isTab
|
||||
|
||||
divs=int(length/nomTab) # divisions
|
||||
if not divs%2: divs-=1 # make divs odd
|
||||
divs=float(divs)
|
||||
tabs=(divs-1)/2 # tabs for side
|
||||
|
||||
if equalTabs:
|
||||
gapWidth=tabWidth=length/divs
|
||||
else:
|
||||
tabWidth=nomTab
|
||||
gapWidth=(length-tabs*nomTab)/(divs-tabs)
|
||||
|
||||
if isTab: # kerf correction
|
||||
gapWidth-=correction
|
||||
tabWidth+=correction
|
||||
first=correction/2
|
||||
else:
|
||||
gapWidth+=correction
|
||||
tabWidth-=correction
|
||||
first=-correction/2
|
||||
|
||||
firstVec=0; secondVec=tabVec
|
||||
dirxN=0 if dirx else 1 # used to select operation on x or y
|
||||
diryN=0 if diry else 1
|
||||
(Vx,Vy)=(rx+sox*thickness,ry+soy*thickness)
|
||||
s='M '+str(Vx)+','+str(Vy)+' '
|
||||
|
||||
if dirxN: Vy=ry # set correct line start
|
||||
if diryN: Vx=rx
|
||||
|
||||
# generate line as tab or hole using:
|
||||
# last co-ord:Vx,Vy ; tab dir:tabVec ; direction:dirx,diry ; thickness:thickness
|
||||
# divisions:divs ; gap width:gapWidth ; tab width:tabWidth
|
||||
|
||||
for n in range(1,int(divs)):
|
||||
if n%2:
|
||||
Vx=Vx+dirx*gapWidth+dirxN*firstVec+first*dirx
|
||||
Vy=Vy+diry*gapWidth+diryN*firstVec+first*diry
|
||||
s+='L '+str(Vx)+','+str(Vy)+' '
|
||||
Vx=Vx+dirxN*secondVec
|
||||
Vy=Vy+diryN*secondVec
|
||||
s+='L '+str(Vx)+','+str(Vy)+' '
|
||||
else:
|
||||
Vx=Vx+dirx*tabWidth+dirxN*firstVec
|
||||
Vy=Vy+diry*tabWidth+diryN*firstVec
|
||||
s+='L '+str(Vx)+','+str(Vy)+' '
|
||||
Vx=Vx+dirxN*secondVec
|
||||
Vy=Vy+diryN*secondVec
|
||||
s+='L '+str(Vx)+','+str(Vy)+' '
|
||||
|
||||
(secondVec,firstVec)=(-secondVec,-firstVec) # swap tab direction
|
||||
first=0
|
||||
s+='L '+str(rx+eox*thickness+dirx*length)+','+str(ry+eoy*thickness+diry*length)+' '
|
||||
return s
|
||||
|
||||
|
||||
class TSlotBoxMaker(inkex.Effect):
|
||||
|
||||
def add_arguments(self, pars):
|
||||
pars.add_argument('--unit', default='mm',help='Measure Units')
|
||||
pars.add_argument('--inside',type=int, default=0, help='Int/Ext Dimension')
|
||||
pars.add_argument('--length',type=float, default=100, help='Length of Box')
|
||||
pars.add_argument('--width',type=float, default=100, help='Width of Box')
|
||||
pars.add_argument('--depth',type=float, default=100, help='Height of Box')
|
||||
pars.add_argument('--tab',type=float, default=25, help='Nominal Tab Width')
|
||||
pars.add_argument('--equal',type=int, default=0, help='Equal/Prop Tabs')
|
||||
pars.add_argument('--thickness',type=float, default=10, help='Thickness of Material')
|
||||
pars.add_argument('--kerf',type=float, default=0.5, help='Kerf (width) of cut')
|
||||
pars.add_argument('--clearance',type=float, default=0.01, help='Clearance of joints')
|
||||
pars.add_argument('--style',type=int, default=25, help='Layout/Style')
|
||||
pars.add_argument('--spacing',type=float, default=25, help='Part Spacing')
|
||||
pars.add_argument('--screw_length',type=float, default=25, help='Screw Length')
|
||||
pars.add_argument('--screw_diameter',type=float, default=25, help='Screw Diameter')
|
||||
|
||||
def effect(self):
|
||||
global parent,nomTab,equalTabs,thickness,correction, screw_length, screw_diameter, spacing, Z
|
||||
|
||||
# Get access to main SVG document element and get its dimensions.
|
||||
svg = self.document.getroot()
|
||||
|
||||
# Get the attibutes:
|
||||
widthDoc = self.svg.unittouu(svg.get('width'))
|
||||
heightDoc = self.svg.unittouu(svg.get('height'))
|
||||
|
||||
# Create a new layer.
|
||||
layer = etree.SubElement(svg, 'g')
|
||||
layer.set(inkex.addNS('label', 'inkscape'), 'T-Slot Box')
|
||||
layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
|
||||
|
||||
parent=self.svg.get_current_layer()
|
||||
|
||||
# Get script's option values.
|
||||
unit=self.options.unit
|
||||
inside=self.options.inside
|
||||
X = self.svg.unittouu( str(self.options.length) + unit )
|
||||
Y = self.svg.unittouu( str(self.options.width) + unit )
|
||||
Z = self.svg.unittouu( str(self.options.depth) + unit )
|
||||
thickness = self.svg.unittouu( str(self.options.thickness) + unit )
|
||||
nomTab = self.svg.unittouu( str(self.options.tab) + unit )
|
||||
equalTabs=self.options.equal
|
||||
kerf = self.svg.unittouu( str(self.options.kerf) + unit )
|
||||
clearance = self.svg.unittouu( str(self.options.clearance) + unit )
|
||||
layout=self.options.style
|
||||
spacing = self.svg.unittouu( str(self.options.spacing) + unit )
|
||||
screw_length = self.svg.unittouu( str(self.options.screw_length) + unit )
|
||||
screw_diameter = self.svg.unittouu( str(self.options.screw_diameter) + unit )
|
||||
|
||||
if inside: # if inside dimension selected correct values to outside dimension
|
||||
X+=thickness*2
|
||||
Y+=thickness*2
|
||||
Z+=thickness*2
|
||||
|
||||
correction=kerf-clearance
|
||||
|
||||
# check input values mainly to avoid python errors
|
||||
# TODO restrict values to *correct* solutions
|
||||
error=0
|
||||
|
||||
if min(X,Y,Z)==0:
|
||||
inkex.errormsg(_('Error: Dimensions must be non zero'))
|
||||
error=1
|
||||
if max(X,Y,Z)>max(widthDoc,heightDoc)*10: # crude test
|
||||
inkex.errormsg(_('Error: Dimensions Too Large'))
|
||||
error=1
|
||||
if min(X,Y,Z)<3*nomTab:
|
||||
inkex.errormsg(_('Error: Tab size too large'))
|
||||
error=1
|
||||
if nomTab<thickness:
|
||||
inkex.errormsg(_('Error: Tab size too small'))
|
||||
error=1
|
||||
if thickness==0:
|
||||
inkex.errormsg(_('Error: Thickness is zero'))
|
||||
error=1
|
||||
if thickness>min(X,Y,Z)/3: # crude test
|
||||
inkex.errormsg(_('Error: Material too thick'))
|
||||
error=1
|
||||
if correction>min(X,Y,Z)/3: # crude test
|
||||
inkex.errormsg(_('Error: Kerf/Clearence too large'))
|
||||
error=1
|
||||
if spacing>max(X,Y,Z)*10: # crude test
|
||||
inkex.errormsg(_('Error: Spacing too large'))
|
||||
error=1
|
||||
if spacing<kerf:
|
||||
inkex.errormsg(_('Error: Spacing too small'))
|
||||
error=1
|
||||
|
||||
if error: exit()
|
||||
|
||||
# layout format:(rootx),(rooty),Xlength,Ylength,tabInfo
|
||||
# root= (spacing,X,Y,Z) * values in tuple
|
||||
# tabInfo= <abcd> 0=holes 1=tabs
|
||||
if layout==1: # Diagramatic Layout
|
||||
pieces=[[(2,0,0,1),(3,0,1,1),X,Z,0b1010, False, False],
|
||||
[(1,0,0,0),(2,0,0,1),Z,Y,0b1111, False, False],
|
||||
[(2,0,0,1),(2,0,0,1),X,Y,0b0000, True, True],
|
||||
[(3,1,0,1),(2,0,0,1),Z,Y,0b1111, False, False],
|
||||
[(4,1,0,2),(2,0,0,1),X,Y,0b0000, True, False],
|
||||
[(2,0,0,1),(1,0,0,0),X,Z,0b1010, False, False]]
|
||||
elif layout==2: # 3 Piece Layout
|
||||
pieces=[[(2,0,0,1),(2,0,1,0),X,Z,0b1010, False, False],
|
||||
[(1,0,0,0),(1,0,0,0),Z,Y,0b1111, False, False],
|
||||
[(2,0,0,1),(1,0,0,0),X,Y,0b0000, False, False]]
|
||||
elif layout==3: # Inline(compact) Layout
|
||||
pieces=[[(1,0,0,0),(1,0,0,0),X,Y,0b0000, False, False],
|
||||
[(2,1,0,0),(1,0,0,0),X,Y,0b0000, False, False],
|
||||
[(3,2,0,0),(1,0,0,0),Z,Y,0b0101, False, False],
|
||||
[(4,2,0,1),(1,0,0,0),Z,Y,0b0101, False, False],
|
||||
[(5,2,0,2),(1,0,0,0),X,Z,0b1111, False, False],
|
||||
[(6,3,0,2),(1,0,0,0),X,Z,0b1111, False, False]]
|
||||
elif layout==4: # Diagramatic Layout with Alternate Tab Arrangement
|
||||
pieces=[[(2,0,0,1),(3,0,1,1),X,Z,0b1001, False, False],
|
||||
[(1,0,0,0),(2,0,0,1),Z,Y,0b1100, False, False],
|
||||
[(2,0,0,1),(2,0,0,1),X,Y,0b1100, False, False],
|
||||
[(3,1,0,1),(2,0,0,1),Z,Y,0b0110, False, False],
|
||||
[(4,1,0,2),(2,0,0,1),X,Y,0b0110, False, False],
|
||||
[(2,0,0,1),(1,0,0,0),X,Z,0b1100, False, False]]
|
||||
|
||||
for piece in pieces: # generate and draw each piece of the box
|
||||
(xs,xx,xy,xz)=piece[0]
|
||||
(ys,yx,yy,yz)=piece[1]
|
||||
x=xs*spacing+xx*X+xy*Y+xz*Z # root x co-ord for piece
|
||||
y=ys*spacing+yx*X+yy*Y+yz*Z # root y co-ord for piece
|
||||
dx=piece[2]
|
||||
dy=piece[3]
|
||||
tabs=piece[4]
|
||||
slots = piece[5]
|
||||
holes = piece[6]
|
||||
a=tabs>>3&1; b=tabs>>2&1; c=tabs>>1&1; d=tabs&1 # extract tab status for each side
|
||||
# generate and draw the sides of each piece
|
||||
drawS(side(x , y , d, a, -b, a, -thickness if a else thickness, dx, 1, 0, a), layer) # side a
|
||||
drawS(side(x+dx, y , -b, a, -b, -c, thickness if b else -thickness, dy, 0, 1, b), layer) # side b
|
||||
drawS(side(x+dx, y+dy, -b, -c, d, -c, thickness if c else -thickness, dx, -1, 0, c), layer) # side c
|
||||
drawS(side(x , y+dy, d, -c, d, a, -thickness if d else thickness, dy, 0, -1, d), layer) # side d
|
||||
|
||||
# side((rx,ry),(sox,soy),(eox,eoy),tabVec,length,(dirx,diry),isTab):
|
||||
# root startOffset endOffset tabVec length direction isTab
|
||||
|
||||
if slots:
|
||||
[drawS(slot, layer) for slot in t_slots(x , y , d, a, -b, a, -thickness if a else thickness, dx, 1, 0, a, holes)] # slot a
|
||||
[drawS(slot, layer) for slot in t_slots(x+dx, y , -b, a, -b, -c, thickness if b else -thickness, dy, 0, 1, b, holes)] # slot b
|
||||
[drawS(slot, layer) for slot in t_slots(x+dx, y+dy , -b, -c, d, -c, thickness if c else -thickness, dx, -1, 0, c, holes)] # slot c
|
||||
[drawS(slot, layer) for slot in t_slots(x , y+dy , d, -c, d, a, -thickness if d else thickness, dy, 0, -1, d, holes)] # slot d
|
||||
|
||||
# Create effect instance and apply it.
|
||||
TSlotBoxMaker().run()
|
161
extensions/fablabchemnitz/box_maker_t_slot/ink_helper.py
Normal file
161
extensions/fablabchemnitz/box_maker_t_slot/ink_helper.py
Normal file
@ -0,0 +1,161 @@
|
||||
import math
|
||||
import inkex
|
||||
from lxml import etree
|
||||
|
||||
def drawS(XYstring, parent): # Draw lines from a list
|
||||
name='part'
|
||||
style = { 'stroke': '#000000', 'stroke-width':'0.26458333', 'fill': 'none' }
|
||||
drw = {'style':str(inkex.Style(style)),inkex.addNS('label','inkscape'):name,'d':XYstring}
|
||||
etree.SubElement(parent, inkex.addNS('path','svg'), drw )
|
||||
return
|
||||
|
||||
class Vec2:
|
||||
|
||||
def __init__(self, x, y=None):
|
||||
if y is None:
|
||||
y = x[1]
|
||||
x = x[0]
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
def norm(self):
|
||||
return math.sqrt(self.x ** 2 + self.y ** 2)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
return [self.x, self.y][idx]
|
||||
|
||||
def __neg__(self):
|
||||
return Vec2(-self.x, -self.y)
|
||||
|
||||
def __add__(self, other):
|
||||
return Vec2(self.x + other[0], self.y + other[1])
|
||||
|
||||
def __sub__(self, other):
|
||||
return self + [-other[0], -other[1]]
|
||||
|
||||
def __mul__(self, scalar):
|
||||
return Vec2(self.x * scalar, self.y * scalar)
|
||||
|
||||
def __truediv__(self, scalar):
|
||||
return Vec2(self.x / scalar, self.y / scalar)
|
||||
|
||||
def dot(self, other):
|
||||
return self.x * other[0] + self.y * other[1]
|
||||
|
||||
def inner(self, other):
|
||||
return self.dot(other)
|
||||
|
||||
def outer(self, other):
|
||||
return [[self[0] * other[0], self[0] * other[1]],
|
||||
[self[1] * other[0], self[1] * other[1]]]
|
||||
|
||||
def __repr__(self):
|
||||
return 'Vec2(%s, %s)' % (self.x, self.y)
|
||||
|
||||
def toXY(self):
|
||||
return '%s,%s ' % (self.x, self.y)
|
||||
|
||||
def mat_x_vec(mat, vec):
|
||||
return Vec2(vec.dot(mat[0]), vec.dot(mat[1]))
|
||||
|
||||
def sign(x):
|
||||
return 1 if x > 0 else -1
|
||||
|
||||
class Path:
|
||||
'''
|
||||
a list of Vec2 points
|
||||
'''
|
||||
def __init__(self, path=()):
|
||||
self.path = [Vec2(p) for p in path]
|
||||
|
||||
def append(self, point):
|
||||
self.path.append(Vec2(point))
|
||||
|
||||
def rotate(self, center, angle):
|
||||
'''
|
||||
angle in degrees
|
||||
'''
|
||||
from math import cos, sin
|
||||
angle *= math.pi / 180.
|
||||
R = [[cos(angle), -sin(angle)],
|
||||
[sin(angle), cos(angle)]]
|
||||
out = [mat_x_vec(R, p - center) + center for p in self.path]
|
||||
return Path(out)
|
||||
|
||||
def translate(self, vec):
|
||||
return Path([p + vec for p in self.path])
|
||||
|
||||
def append_from_last(self, v):
|
||||
self.path.append(self.path[-1] + v)
|
||||
|
||||
def extend(self, points):
|
||||
self.path.extend(points)
|
||||
|
||||
def __getitem__(self, idx):
|
||||
return self.path[idx]
|
||||
|
||||
def reflect(self, center, orient):
|
||||
out = self.translate(-center)
|
||||
R = Vec2(orient).outer(orient)
|
||||
R = [[1 - 2 * R[0][0], 2 * R[0][1]],
|
||||
[2 * R[1][0], 1 - 2 * R[1][1]]]
|
||||
out = Path([mat_x_vec(R, p) for p in out])
|
||||
out = out.translate(center)
|
||||
return out
|
||||
|
||||
def reverse(self):
|
||||
return Path(self.path[::-1])
|
||||
|
||||
def drawXY(self):
|
||||
XYstring = 'M ' + 'L '.join([p.toXY() for p in self.path])
|
||||
return XYstring
|
||||
|
||||
def plot(self, lt='-'):
|
||||
from pylab import plot
|
||||
xs = [l.x for l in self.path]
|
||||
ys = [l.y for l in self.path]
|
||||
plot(xs, ys, lt)
|
||||
|
||||
|
||||
def Vec2__test__():
|
||||
v1 = Vec2(1, 1)
|
||||
assert abs(v1.norm() - math.sqrt(2)) < 1e-8
|
||||
assert abs(-v1[0] + 1) < 1e-8
|
||||
assert abs((v1 + v1)[0] - 2) < 1e-8
|
||||
assert abs((v1 - v1)[0] - 0) < 1e-8
|
||||
assert abs((v1 + [1, 2]).x - 2) < 1e-8
|
||||
assert abs((v1 - [1, 2]).x - 0) < 1e-8
|
||||
assert (v1.dot(v1) - v1.norm() ** 2) < 1e-8
|
||||
Vec2__test__()
|
||||
|
||||
if __name__ == '__main__':
|
||||
from pylab import plot, figure, clf, show, axis
|
||||
from numpy import array
|
||||
mm = 1.
|
||||
center = [0, 30 * mm]
|
||||
orient = [12. * mm, 0]
|
||||
screw_diameter = 3 * mm
|
||||
nut_diameter = 5.5 * mm
|
||||
nut_w = 2.5 * mm
|
||||
screw_length = 16 * mm
|
||||
thickness = 6 * mm
|
||||
|
||||
orient = Vec2(orient)
|
||||
out = orient / math.sqrt(orient[0] ** 2 + orient[1] ** 2)
|
||||
up = Vec2([-out[1], out[0]])
|
||||
center = Vec2(center)
|
||||
screw_r = screw_diameter / 2.
|
||||
nut_r = nut_diameter / 2.
|
||||
path = Path([center + up * screw_r])
|
||||
path.append_from_last(orient)
|
||||
path.append_from_last(up * (nut_r - screw_r))
|
||||
path.append_from_last(out * (nut_w))
|
||||
path.append_from_last(-up * (nut_r - screw_r))
|
||||
path.append_from_last(out * (screw_length - thickness))
|
||||
path.append_from_last(-up * screw_r)
|
||||
rest = path.reflect(center, up).reverse()
|
||||
path.extend(rest)
|
||||
path.plot()
|
||||
rest.plot('o-')
|
||||
axis('equal')
|
||||
show()
|
21
extensions/fablabchemnitz/box_maker_t_slot/meta.json
Normal file
21
extensions/fablabchemnitz/box_maker_t_slot/meta.json
Normal file
@ -0,0 +1,21 @@
|
||||
[
|
||||
{
|
||||
"name": "Box Maker - T-Slot",
|
||||
"id": "fablabchemnitz.de.box_maker_t_slot",
|
||||
"path": "box_maker_t_slot",
|
||||
"dependent_extensions": null,
|
||||
"original_name": "T-Slot Box Maker",
|
||||
"original_id": "eu.twot.render.my_boxmaker",
|
||||
"license": "GNU GPL v3",
|
||||
"license_url": "https://github.com/kchimbo/inkscape_tslot_boxmaker/blob/master/LICENSE.md",
|
||||
"comment": "",
|
||||
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/box_maker_t_slot",
|
||||
"fork_url": "https://github.com/kchimbo/inkscape_tslot_boxmaker",
|
||||
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Box+Maker+-+T-Slot",
|
||||
"inkscape_gallery_url": null,
|
||||
"main_authors": [
|
||||
"github.com/kchimbo",
|
||||
"github.com/eridur-de"
|
||||
]
|
||||
}
|
||||
]
|
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Colorize Path Lengths/Slants</name>
|
||||
<id>fablabchemnitz.de.colorize_path_lengths</id>
|
||||
<param name="selection" type="optiongroup" appearance="combo" gui-text="Selection: ">
|
||||
<option value="path_lengthselection">Length selection</option>
|
||||
<option value="path_slantselection">Slant selection</option>
|
||||
</param>
|
||||
<label>Unit for lengths is px</label>
|
||||
<param name="len1" type="float" gui-text="Len1: red <" min="1" max="100">12</param>
|
||||
<param name="len2" type="float" gui-text="Len2: green < =" min="1" max="100">25</param>
|
||||
<param name="len3" type="float" gui-text="Len3: greenyellow < =" min="1" max="100">40</param>
|
||||
<param name="len4" type="float" gui-text="Len4: skyblue < =" min="1" max="100">60</param>
|
||||
<param name="len5" type="float" gui-text="Len5: blue >" min="1" max="100">60</param>
|
||||
<param name="hor" type="float" gui-text="hor: red < (H/W)" min="0.01" max="100">0.1</param>
|
||||
<param name="ver" type="float" gui-text="ver: blue >" min="1" max="100">10</param>
|
||||
<effect>
|
||||
<object-type>path</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz">
|
||||
<submenu name="Dimensioning/Measuring"/>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">colorize_path_lengths.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python3
|
||||
'''
|
||||
pathselection.py
|
||||
|
||||
Sunabe kazumichi 2009/9/29
|
||||
http://dp48069596.lolipop.jp/
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
'''
|
||||
import inkex, locale
|
||||
import math
|
||||
from lxml import etree
|
||||
from inkex import paths
|
||||
from inkex import bezier
|
||||
from inkex import PathElement
|
||||
|
||||
# Set current system locale
|
||||
locale.setlocale(locale.LC_ALL, '')
|
||||
|
||||
def cspseglength(sp1,sp2, tolerance = 0.001):
|
||||
bez = (sp1[1][:],sp1[2][:],sp2[0][:],sp2[1][:])
|
||||
return bezier.bezierlength(bez, tolerance)
|
||||
def csplength(csp):
|
||||
lengths = []
|
||||
for sp in csp:
|
||||
lengths.append([])
|
||||
for i in range(1,len(sp)):
|
||||
l = cspseglength(sp[i-1],sp[i])
|
||||
lengths[-1].append(l)
|
||||
return lengths
|
||||
|
||||
def roughBBox(path):
|
||||
xmin,xMax,ymin,yMax=path[0][0][0],path[0][0][0],path[0][0][1],path[0][0][1]
|
||||
for pathcomp in path:
|
||||
for pt in pathcomp:
|
||||
xmin=min(xmin,pt[0])
|
||||
xMax=max(xMax,pt[0])
|
||||
ymin=min(ymin,pt[1])
|
||||
yMax=max(yMax,pt[1])
|
||||
if xMax-xmin==0:
|
||||
tn=0
|
||||
else :
|
||||
tn=(yMax-ymin)/(xMax-xmin)
|
||||
return tn
|
||||
|
||||
class ColorizePathLengths(inkex.EffectExtension):
|
||||
|
||||
def add_arguments(self, pars):
|
||||
pars.add_argument("-s", "--selection", default=True, help="select path with length or slant")
|
||||
pars.add_argument("-1", "--len1", type=float, default=12)
|
||||
pars.add_argument("-2", "--len2", type=float, default=25)
|
||||
pars.add_argument("-3", "--len3", type=float, default=40)
|
||||
pars.add_argument("-4", "--len4", type=float, default=60)
|
||||
pars.add_argument("-5", "--len5", type=float, default=60)
|
||||
pars.add_argument("-6", "--hor", type=float, default=0.2)
|
||||
pars.add_argument("-7", "--ver", type=float, default=10)
|
||||
|
||||
def effect(self):
|
||||
# loop over all selected paths
|
||||
if self.options.selection=="path_lengthselection":
|
||||
if len(self.svg.selected) > 0:
|
||||
for node in self.svg.selection.filter(PathElement).values():
|
||||
if node.tag == inkex.addNS('path','svg'):
|
||||
l1,l2,l3,l4,l5=[],[],[],[],[]
|
||||
p = paths.CubicSuperPath(inkex.paths.Path(node.get('d')))
|
||||
slengths = csplength(p)
|
||||
b = [slengths, p]
|
||||
|
||||
# path length select
|
||||
for x in range(0, len(slengths)):
|
||||
if sum(b[0][x]) < self.options.len1:
|
||||
l1.append(b[1][x])
|
||||
if self.options.len2 > sum(b[0][x]) >= self.options.len1 :
|
||||
l2.append(b[1][x])
|
||||
if self.options.len3 > sum(b[0][x]) >= self.options.len2 :
|
||||
l3.append(b[1][x])
|
||||
if self.options.len4 > sum(b[0][x]) >= self.options.len3 :
|
||||
l4.append(b[1][x])
|
||||
if sum(b[0][x]) >= self.options.len4 :
|
||||
l5.append(b[1][x])
|
||||
|
||||
# make path
|
||||
lensel = [l1, l2, l3, l4, l5]
|
||||
strlen = ['#FF0001', '#00FF02', '#AAFF03', '#87CEE4', '#000FF5']
|
||||
for i, x in zip(strlen, lensel):
|
||||
s = {'stroke-linejoin': 'miter', 'stroke-width': '0.5px',
|
||||
'stroke-opacity': '1.0', 'fill-opacity': '1.0',
|
||||
'stroke': i, 'stroke-linecap': 'butt', 'fill': 'none'}
|
||||
attribs={'style':str(inkex.Style(s)),'d':str(paths.Path(paths.CubicSuperPath(x).to_path().to_arrays()))}
|
||||
etree.SubElement(node.getparent(),inkex.addNS('path','svg'),attribs)
|
||||
else:
|
||||
self.msg('Please select some paths first.')
|
||||
return
|
||||
|
||||
if self.options.selection=="path_slantselection":
|
||||
if len(self.svg.selected) > 0:
|
||||
for node in self.svg.selection.filter(PathElement).values():
|
||||
if node.tag == inkex.addNS('path','svg'):
|
||||
hor1,ver2,slan3=[],[],[]
|
||||
p = paths.CubicSuperPath(inkex.paths.Path(node.get('d')))
|
||||
|
||||
# path slant select
|
||||
for i,x in enumerate(p):
|
||||
tn=roughBBox(x)
|
||||
if tn<self.options.hor:
|
||||
hor1.append(p[i])
|
||||
elif tn>self.options.ver:
|
||||
ver2.append(p[i])
|
||||
else:
|
||||
slan3.append(p[i])
|
||||
|
||||
# make path
|
||||
slnsel = [hor1, ver2, slan3]
|
||||
strsln = ['#FF0001', '#00FF02', '#000FF5']
|
||||
for i, x in zip(strsln, slnsel):
|
||||
s = {'stroke-linejoin': 'miter', 'stroke-width': '0.5px',
|
||||
'stroke-opacity': '1.0', 'fill-opacity': '1.0',
|
||||
'stroke': i, 'stroke-linecap': 'butt', 'fill': 'none'}
|
||||
attribs={'style':str(inkex.Style(s)),'d':str(paths.Path(paths.CubicSuperPath(x).to_path().to_arrays()))}
|
||||
etree.SubElement(node.getparent(), inkex.addNS('path','svg'), attribs)
|
||||
else:
|
||||
self.msg('Please select some paths first.')
|
||||
return
|
||||
|
||||
if __name__ == '__main__':
|
||||
ColorizePathLengths().run()
|
21
extensions/fablabchemnitz/colorize_path_lengths/meta.json
Normal file
21
extensions/fablabchemnitz/colorize_path_lengths/meta.json
Normal file
@ -0,0 +1,21 @@
|
||||
[
|
||||
{
|
||||
"name": "Colorize Path Lengths/Slants",
|
||||
"id": "fablabchemnitz.de.colorize_path_lengths",
|
||||
"path": "colorize_path_lengths",
|
||||
"dependent_extensions": null,
|
||||
"original_name": "<unknown>",
|
||||
"original_id": "<unknown>",
|
||||
"license": "GNU GPL v2",
|
||||
"license_url": "http://dp48069596.lolipop.jp/sd/scripts/script_inkscape/pathselection.zip",
|
||||
"comment": "ported to Inkscape v1 manually by Mario Voigt",
|
||||
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/colorize_path_lengths",
|
||||
"fork_url": "http://dp48069596.lolipop.jp/sd/scripts/script_inkscape/pathselection.zip",
|
||||
"documentation_url": "https://stadtfabrikanten.org/pages/viewpage.action?pageId=55019108",
|
||||
"inkscape_gallery_url": null,
|
||||
"main_authors": [
|
||||
"Sunabe Kazumichi",
|
||||
"github.com/eridur-de"
|
||||
]
|
||||
}
|
||||
]
|
57
extensions/fablabchemnitz/cutcraft/cutcraft/README.md
Normal file
57
extensions/fablabchemnitz/cutcraft/cutcraft/README.md
Normal file
@ -0,0 +1,57 @@
|
||||
# cut-craft
|
||||
|
||||
|
||||
## Python package
|
||||
|
||||
Note that this package is written for Python 3, however requires Python 2 compatibility for Inkscape integration.
|
||||
|
||||
The `cutcraft` package contains components in the following categories:
|
||||
|
||||
| Folder | Description |
|
||||
| --------- | ---------------------------------------------------- |
|
||||
| core | Core components (point, line etc). |
|
||||
| platforms | Platforms used to construct shapes (circular etc). |
|
||||
| shapes | Fundamental 3D shapes (cylinder, cone, sphere etc). |
|
||||
| supports | Vertical supports to hold the shape levels apart. |
|
||||
|
||||
|
||||
## Core
|
||||
|
||||
| Module | Description |
|
||||
| --------- | --------------------------------------------------------------------------- |
|
||||
| point | A 2D point with `x` and `y` coordinates. |
|
||||
| rectangle | Two `point`s defining topleft and bottom right for a rectangle. |
|
||||
| trace | An ordered collection of `point`s. |
|
||||
| part | A collection of one or more `trace`s. |
|
||||
| line | A type of `trace` with two `point`s defining the start and end of the line. |
|
||||
| circle | A type of `trace` with `point`s defining a circle. |
|
||||
| neopixel | A type of `trace` with the `point`s defining a cutout suitable to fit a variety of [NeoPixels](https://www.adafruit.com/category/168). |
|
||||
|
||||
|
||||
## Shapes
|
||||
|
||||
| Module | Description |
|
||||
| -------- | -------------------------------------- |
|
||||
| shape | The core 3D functionality for a shape. |
|
||||
| cone | A cone `shape`. |
|
||||
| cylinder | A cylinder `shape`. |
|
||||
| sphere | A 3D spherical `shape`. |
|
||||
|
||||
> Note that the fundamental `shape`s listed above can be used flexibly considering the number of `circle` segments can be specified. For example a `cone` with 4 segments becomes a **pyramid**, and a `cylinder` with 4 segments becomes a **cube**.
|
||||
|
||||
|
||||
## Supports
|
||||
|
||||
| Module | Description |
|
||||
| -------- | --------------------------------------------------- |
|
||||
| support | The core support structure functionality. |
|
||||
| pier | A pier like `support` to hold `shape` levels apart. |
|
||||
| face | A solid face to `support` `shape` levels. |
|
||||
|
||||
|
||||
## Python 2 vs 3 Compatibility
|
||||
|
||||
The initial aim was to develop only for Python 3, however [Inkscape](https://inkscape.org) currently uses Python 2 as the default interpreter for extensions. As a result, the following should be noted while reviewing the code:
|
||||
|
||||
1) The calls to `super()` are written in a way that works with both versions of Python.
|
||||
2) The `math.isclose()` function is not available in Python 2 so a local version has been created in [util.py](util.py).
|
17
extensions/fablabchemnitz/cutcraft/cutcraft/__init__.py
Normal file
17
extensions/fablabchemnitz/cutcraft/cutcraft/__init__.py
Normal file
@ -0,0 +1,17 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
27
extensions/fablabchemnitz/cutcraft/cutcraft/core/__init__.py
Normal file
27
extensions/fablabchemnitz/cutcraft/cutcraft/core/__init__.py
Normal file
@ -0,0 +1,27 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .point import Point
|
||||
from .line import Line
|
||||
from .rectangle import Rectangle
|
||||
from .trace import Trace
|
||||
from .circle import Circle
|
||||
from .part import Part
|
||||
from .neopixel import NeoPixel
|
||||
from .fingerjoint import FingerJoint
|
||||
|
||||
__all__ = ["Point", "Line", "Rectangle", "Trace", "Circle", "Part", "NeoPixel", "FingerJoint"]
|
131
extensions/fablabchemnitz/cutcraft/cutcraft/core/circle.py
Normal file
131
extensions/fablabchemnitz/cutcraft/cutcraft/core/circle.py
Normal file
@ -0,0 +1,131 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .point import Point
|
||||
from .line import Line
|
||||
from .trace import Trace
|
||||
from ..util import isclose
|
||||
from math import pi, sin, cos, asin
|
||||
|
||||
class Circle(Trace):
|
||||
def __init__(self, radius, segments, cuts, cutdepth=0.0, start=0.0, end=pi*2.0, rotation=0.0,
|
||||
origin=Point(0.0, 0.0), thickness=0.0, kerf=0.0):
|
||||
super(Circle, self).__init__()
|
||||
self.thickness = thickness
|
||||
self.kerf = kerf
|
||||
|
||||
partial = True if start != 0.0 or end != pi*2.0 else False
|
||||
|
||||
if cuts==0:
|
||||
c = 0.0
|
||||
else:
|
||||
if self.thickness <= 0.0:
|
||||
raise ValueError("cutcraft.circle: parameter 'thickness' not set when 'cuts' greater than zero.")
|
||||
if cutdepth <= 0.0:
|
||||
raise ValueError("cutcraft.circle: parameter 'cutdepth' not set when 'cuts' greater than zero.")
|
||||
c = asin(self.thickness/2/radius)
|
||||
if partial:
|
||||
angles = [[rotation+start+(end-start)/segments*seg, 'SEG'] for seg in range(segments+1)] + \
|
||||
[[rotation+start+(end-start)/(cuts+1)*cut-c, '<CUT'] for cut in range(1, cuts+1)] + \
|
||||
[[rotation+start+(end-start)/(cuts+1)*cut+c, 'CUT>'] for cut in range(1, cuts+1)]
|
||||
else:
|
||||
angles = [[rotation+end/segments*seg, 'SEG'] for seg in range(segments)] + \
|
||||
[[rotation+end/cuts*cut-c, '<CUT'] for cut in range(cuts)] + \
|
||||
[[rotation+end/cuts*cut+c, 'CUT>'] for cut in range(cuts)]
|
||||
angles = sorted(angles)
|
||||
if angles[0][1] == 'CUT>':
|
||||
angles = angles[1:] + [angles[0]]
|
||||
|
||||
for i, angle in enumerate(angles):
|
||||
angle.append(self._cnext(angles, i, 'SEG'))
|
||||
angle.append(self._cprev(angles, i, 'SEG'))
|
||||
angle.append(self._cnext(angles, i, 'CUT>') if angle[1]=='<CUT' else None)
|
||||
angle.append(self._cprev(angles, i, '<CUT') if angle[1]=='CUT>' else None)
|
||||
|
||||
for i, angle in enumerate(angles):
|
||||
if angle[1] == 'SEG':
|
||||
angle.append([self._pos(angle[0], radius)])
|
||||
|
||||
for i, angle in enumerate(angles):
|
||||
if angle[1] != 'SEG':
|
||||
mult = -1 if angle[1] == '<CUT' else 1
|
||||
a = angle[0] - mult*c
|
||||
a2 = a + mult*pi/2
|
||||
# Line from previous to next segment point.
|
||||
line1 = Line(angles[angle[2]][6][0], angles[angle[3]][6][0])
|
||||
# Line from origin offset by thickness.
|
||||
p1 = self._pos(a2, self.thickness/2)
|
||||
p2 = p1 + self._pos(a, radius)
|
||||
line2 = Line(p1, p2)
|
||||
pintersect = line1.intersection(line2)
|
||||
pinset = pintersect + self._pos(a, -cutdepth)
|
||||
if angle[1] == '<CUT':
|
||||
angle.append([pintersect, pinset])
|
||||
else:
|
||||
angle.append([pinset, pintersect])
|
||||
d1 = pinset.distance(Point(0.0,0.0))
|
||||
d2 = angles[angle[5]][6][1].distance(Point(0.0,0.0))
|
||||
|
||||
if d1<d2:
|
||||
angles[angle[5]][6][1] = pinset - self._pos(a2, self.thickness)
|
||||
elif d2<d1:
|
||||
angle[6][0] = angles[angle[5]][6][1] + self._pos(a2, self.thickness)
|
||||
pass
|
||||
|
||||
incut = False
|
||||
for i, angle in enumerate(angles):
|
||||
atype = angle[1]
|
||||
|
||||
if atype=='<CUT':
|
||||
incut = True
|
||||
elif atype=='CUT>':
|
||||
incut = False
|
||||
|
||||
if atype != 'SEG' or (atype == 'SEG' and not incut):
|
||||
for pos in angle[6]:
|
||||
x = origin.x + pos.x
|
||||
y = origin.y + pos.y
|
||||
if len(self.x)==0 or not (isclose(x, self.x[-1]) and isclose(y, self.y[-1])):
|
||||
self.x.append(x)
|
||||
self.y.append(y)
|
||||
|
||||
return
|
||||
|
||||
def _cnext(self, angles, i, item):
|
||||
if i>=len(angles):
|
||||
i=-1
|
||||
for j, angle in enumerate(angles[i+1:]):
|
||||
if angle[1] == item:
|
||||
return i+1+j
|
||||
for j, angle in enumerate(angles):
|
||||
if angle[1] == item:
|
||||
return j
|
||||
return None
|
||||
|
||||
def _cprev(self, angles, i, item):
|
||||
if i<=0:
|
||||
i=len(angles)
|
||||
for j, angle in enumerate(angles[:i][::-1]):
|
||||
if angle[1] == item:
|
||||
return i-j-1
|
||||
for j, angle in enumerate(angles[::-1]):
|
||||
if angle[1] == item:
|
||||
return j
|
||||
return None
|
||||
|
||||
def _pos(self, angle, radius):
|
||||
return Point(sin(angle)*radius, cos(angle)*radius)
|
@ -0,0 +1,40 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from math import floor
|
||||
|
||||
class FingerJoint(object):
|
||||
def __init__(self, length, fingerwidth, style, thickness=0.0):
|
||||
super(FingerJoint, self).__init__()
|
||||
|
||||
self.thickness = thickness
|
||||
|
||||
if style in ('depth','height'):
|
||||
self.fingers = [0.0] + \
|
||||
[pos + fingerwidth*2.0 for pos in self._fingers(length-fingerwidth*4.0, fingerwidth)] + \
|
||||
[length]
|
||||
elif style=='width':
|
||||
self.fingers = [pos + thickness for pos in self._fingers(length-thickness*2.0, fingerwidth)]
|
||||
else:
|
||||
raise ValueError("cutcraft.core.fingerjoin: invalid value of '{}' for parameter 'style'.".format(style))
|
||||
|
||||
return
|
||||
|
||||
def _fingers(self, length, fingerwidth):
|
||||
count = int(floor(length / fingerwidth))
|
||||
count = count-1 if count%2==0 else count
|
||||
return [length/count*c for c in range(count+1)]
|
83
extensions/fablabchemnitz/cutcraft/cutcraft/core/line.py
Normal file
83
extensions/fablabchemnitz/cutcraft/cutcraft/core/line.py
Normal file
@ -0,0 +1,83 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .point import Point
|
||||
from math import sqrt
|
||||
|
||||
def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
|
||||
# Required as Inkscape does not include math.isclose().
|
||||
return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
|
||||
|
||||
class Line(object):
|
||||
""" Line class defined by start and end Points. """
|
||||
def __init__(self, point1, point2):
|
||||
self.pts = (point1, point2)
|
||||
self.x = [point1.x, point2.x]
|
||||
self.y = [point1.y, point2.y]
|
||||
return
|
||||
|
||||
def _line(self):
|
||||
# Convert line segment into a line equation (infinite length).
|
||||
p1 = self.pts[0]
|
||||
p2 = self.pts[1]
|
||||
A = (p1.y - p2.y)
|
||||
B = (p2.x - p1.x)
|
||||
C = (p1.x*p2.y - p2.x*p1.y)
|
||||
return A, B, -C
|
||||
|
||||
def intersection(self, other):
|
||||
# Find the intersection of the lines (infinite length - not segments)
|
||||
L1 = self._line()
|
||||
L2 = other._line()
|
||||
D = L1[0] * L2[1] - L1[1] * L2[0]
|
||||
Dx = L1[2] * L2[1] - L1[1] * L2[2]
|
||||
Dy = L1[0] * L2[2] - L1[2] * L2[0]
|
||||
if D != 0:
|
||||
x = Dx / D
|
||||
y = Dy / D
|
||||
return Point(x, y)
|
||||
else:
|
||||
return None
|
||||
|
||||
def normal(self):
|
||||
# Return the unit normal
|
||||
dx = self.x[1] - self.x[0]
|
||||
dy = self.y[1] - self.y[0]
|
||||
|
||||
d = sqrt(dx*dx + dy*dy)
|
||||
if d == 0:
|
||||
raise ValueError("cutcraft.line: parameter 'normal' could not be calculated. Check your entered parameters.")
|
||||
return dx/d, -dy/d
|
||||
|
||||
def addkerf(self, kerf):
|
||||
nx, ny = self.normal()
|
||||
offset = Point(ny*kerf, nx*kerf)
|
||||
self.pts = (self.pts[0] + offset, self.pts[1] + offset)
|
||||
self.x = [self.pts[0].x, self.pts[1].x]
|
||||
self.y = [self.pts[0].y, self.pts[1].y]
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.pts == other.pts)
|
||||
|
||||
def __ne__(self, other):
|
||||
return (self.pts != other.pts)
|
||||
|
||||
def __repr__(self):
|
||||
return "Line(" + repr(self.pts[0]) + ", " + repr(self.pts[1]) + ")"
|
||||
|
||||
def __str__(self):
|
||||
return "(" + str(self.pts[0]) + ", " + str(self.pts[1]) + ")"
|
65
extensions/fablabchemnitz/cutcraft/cutcraft/core/neopixel.py
Normal file
65
extensions/fablabchemnitz/cutcraft/cutcraft/core/neopixel.py
Normal file
@ -0,0 +1,65 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .point import Point
|
||||
from .trace import Trace
|
||||
from .part import Part
|
||||
from math import pi, sin, cos, sqrt
|
||||
|
||||
class NeoPixel(Part):
|
||||
rings = [[1, 0.0], [6, 16.0/2.0], [12, 30.0/2.0]]
|
||||
size = 5.5
|
||||
|
||||
""" Line class defined by start and end Points. """
|
||||
def __init__(self, style='rings', origin=Point(0.0, 0.0), scale=1.0, rotate=0.0):
|
||||
super(NeoPixel, self).__init__()
|
||||
self.scale = scale
|
||||
|
||||
if style=='rings':
|
||||
for ring in self.rings:
|
||||
pixels = ring[0]
|
||||
radius = ring[1] * self.scale
|
||||
for pixel in range(pixels):
|
||||
a = rotate + pi*2 * pixel / pixels
|
||||
seg = self._pixel(origin + Point(sin(a) * radius, cos(a) * radius),
|
||||
pi/4 + a)
|
||||
self += seg
|
||||
elif style=='strip':
|
||||
xo = origin.x
|
||||
yo = origin.y
|
||||
xsize = 25.4*2.0*self.scale
|
||||
size = self.size*self.scale
|
||||
seg = Trace() + \
|
||||
Point(xo-xsize/2.0, yo+size/2.0) + \
|
||||
Point(xo-xsize/2.0, yo-size/2.0) + \
|
||||
Point(xo+xsize/2.0, yo-size/2.0) + \
|
||||
Point(xo+xsize/2.0, yo+size/2.0)
|
||||
seg.close()
|
||||
self += seg
|
||||
return
|
||||
|
||||
def _pixel(self, position, rotation):
|
||||
seg = Trace()
|
||||
xo = position.x
|
||||
yo = position.y
|
||||
size = sqrt(2.0*(self.size*self.scale)**2)
|
||||
for corner in range(4):
|
||||
# Points added in counterclockwise direction as this is an inner cut.
|
||||
a = rotation-2.0*pi*corner/4.0
|
||||
seg += Point(xo + sin(a) * size/2.0, yo + cos(a) * size/2.0)
|
||||
seg.close()
|
||||
return seg
|
91
extensions/fablabchemnitz/cutcraft/cutcraft/core/part.py
Normal file
91
extensions/fablabchemnitz/cutcraft/cutcraft/core/part.py
Normal file
@ -0,0 +1,91 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from copy import deepcopy
|
||||
from .point import Point
|
||||
from .rectangle import Rectangle
|
||||
from .trace import Trace
|
||||
|
||||
class Part(object):
|
||||
""" List of traces that make up a part. """
|
||||
def __init__(self):
|
||||
self.traces = []
|
||||
return
|
||||
|
||||
def close(self):
|
||||
""" Close each traces back to their start. """
|
||||
for trace in self.traces:
|
||||
trace.close()
|
||||
return
|
||||
|
||||
def applykerf(self, kerf):
|
||||
""" Apply an offset to allow for the kerf when cutting. """
|
||||
for trace in self.traces:
|
||||
trace.applykerf(kerf)
|
||||
return
|
||||
|
||||
def svg(self):
|
||||
# Generate SVG string for this part.
|
||||
return " ".join([trace.svg() for trace in self.traces])
|
||||
|
||||
def bbox(self):
|
||||
bboxes = [trace.bbox() for trace in self.traces]
|
||||
x = [p1.x for p1, p2 in bboxes] + [p2.x for p1, p2 in bboxes]
|
||||
y = [p1.y for p1, p2 in bboxes] + [p2.y for p1, p2 in bboxes]
|
||||
return Rectangle(Point(min(x), min(y)), Point(max(x), max(y)))
|
||||
|
||||
def area(self):
|
||||
bbox = self.bbox()
|
||||
return bbox.area()
|
||||
|
||||
def size(self):
|
||||
bbox = self.bbox()
|
||||
return bbox.size()
|
||||
|
||||
def __add__(self, other):
|
||||
p = Part()
|
||||
if isinstance(other, Part):
|
||||
p.traces = self.traces + deepcopy(other.traces)
|
||||
elif isinstance(other, Trace):
|
||||
p.traces = deepcopy(self.traces)
|
||||
p.traces.append(other)
|
||||
elif isinstance(other, Point):
|
||||
p.traces = self.traces
|
||||
for trace in p.traces:
|
||||
trace.offset(other)
|
||||
else:
|
||||
raise RuntimeError("Can only add a Part, Trace or Point to an existing Part.")
|
||||
return p
|
||||
|
||||
def __iadd__(self, other):
|
||||
if isinstance(other, Part):
|
||||
self.traces.extend(other.traces)
|
||||
elif isinstance(other, Trace):
|
||||
self.traces.append(deepcopy(other))
|
||||
elif isinstance(other, Point):
|
||||
for trace in self.traces:
|
||||
trace.offset(other)
|
||||
else:
|
||||
raise RuntimeError("Can only add a Part, Trace or Point to an existing Part.")
|
||||
return self
|
||||
|
||||
def __repr__(self):
|
||||
return "Part" + str(self)
|
||||
|
||||
def __str__(self):
|
||||
l = len(self.traces)
|
||||
return "(" + str(l) + " trace" + ("s" if l>1 else "") + ")"
|
64
extensions/fablabchemnitz/cutcraft/cutcraft/core/point.py
Normal file
64
extensions/fablabchemnitz/cutcraft/cutcraft/core/point.py
Normal file
@ -0,0 +1,64 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from math import sqrt
|
||||
|
||||
class Point(object):
|
||||
""" Point (x,y) class suppporting addition for offsets. """
|
||||
def __init__(self, x, y):
|
||||
self.x = x
|
||||
self.y = y
|
||||
|
||||
def distance(self, other):
|
||||
""" Distance between two points. """
|
||||
x = self.x - other.x
|
||||
y = self.y - other.y
|
||||
return sqrt(x*x+y*y)
|
||||
|
||||
def tup(self):
|
||||
return (self.x, self.y)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.x == other.x and self.y == other.y)
|
||||
|
||||
def __ne__(self, other):
|
||||
return (self.x != other.x or self.y != other.y)
|
||||
|
||||
def __add__(self, other):
|
||||
return Point(self.x + other.x, self.y + other.y)
|
||||
|
||||
def __iadd__(self, other):
|
||||
self.x += other.x
|
||||
self.y += other.y
|
||||
return self
|
||||
|
||||
def __sub__(self, other):
|
||||
return Point(self.x - other.x, self.y - other.y)
|
||||
|
||||
def __isub__(self, other):
|
||||
self.x -= other.x
|
||||
self.y -= other.y
|
||||
return self
|
||||
|
||||
def __neg__(self):
|
||||
return Point(-self.x, -self.y)
|
||||
|
||||
def __repr__(self):
|
||||
return "Point(" + str(self.x) + ", " + str(self.y) + ")"
|
||||
|
||||
def __str__(self):
|
||||
return "(" + str(self.x) + ", " + str(self.y) + ")"
|
@ -0,0 +1,60 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from math import ceil, floor
|
||||
from .point import Point
|
||||
|
||||
class Rectangle(object):
|
||||
""" Rectangle class defined by top-left and bottom-right Points. """
|
||||
def __init__(self, point1, point2):
|
||||
# Correct the input points in case they are not topleft, bottomright as expected.
|
||||
self.topleft = Point(min(point1.x, point2.x), min(point1.y, point2.y))
|
||||
self.bottomright = Point(max(point1.x, point2.x), max(point1.y, point2.y))
|
||||
return
|
||||
|
||||
def size(self):
|
||||
# Calculate the size as: width, height.
|
||||
return self.bottomright.x-self.topleft.x, self.bottomright.y-self.topleft.y
|
||||
|
||||
def area(self):
|
||||
width, height = self.size()
|
||||
return width*height
|
||||
|
||||
def expanded(self):
|
||||
# Expand the current Rectangle out to integer boundary.
|
||||
return Rectangle(Point(floor(self.topleft.x), floor(self.topleft.y)),
|
||||
Point(ceil(self.bottomright.x), ceil(self.bottomright.y)))
|
||||
|
||||
def svg(self):
|
||||
# Generate SVG string for this rectangle.
|
||||
ptx = [self.topleft.x, self.bottomright.x, self.bottomright.x, self.topleft.x]
|
||||
pty = [self.topleft.y, self.topleft.y, self.bottomright.y, self.bottomright.y]
|
||||
return "M {} {} ".format(ptx[0], pty[0]) + \
|
||||
" ".join(["L {} {}".format(x, y) for x, y in zip(ptx[1:], pty[1:])]) + \
|
||||
" L {} {}".format(ptx[0], pty[0])
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.topleft == other.topleft and self.bottomright == other.bottomright )
|
||||
|
||||
def __ne__(self, other):
|
||||
return (self.topleft != other.topleft or self.bottomright != other.bottomright)
|
||||
|
||||
def __repr__(self):
|
||||
return "Rectangle(" + repr(self.topleft) + ", " + repr(self.bottomright) + ")"
|
||||
|
||||
def __str__(self):
|
||||
return "(" + str(self.topleft) + ", " + str(self.bottomright) + ")"
|
149
extensions/fablabchemnitz/cutcraft/cutcraft/core/trace.py
Normal file
149
extensions/fablabchemnitz/cutcraft/cutcraft/core/trace.py
Normal file
@ -0,0 +1,149 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .point import Point
|
||||
from .line import Line
|
||||
from ..util import isclose, iscloselist
|
||||
|
||||
class Trace(object):
|
||||
""" List of coordinates that make a boundary. """
|
||||
def __init__(self, x=None, y=None):
|
||||
self.x = [] if x is None else x
|
||||
self.y = [] if y is None else y
|
||||
self.closed = False
|
||||
return
|
||||
|
||||
def close(self):
|
||||
""" Close the trace back to the start. """
|
||||
if not self.closed:
|
||||
if isclose(self.x[0], self.x[-1]) and isclose(self.y[0], self.y[-1]):
|
||||
# Start and end should be the same.
|
||||
self.x[-1] = self.x[0]
|
||||
self.y[-1] = self.y[0]
|
||||
else:
|
||||
# Add new end point to close the loop.
|
||||
self.x.append(self.x[0])
|
||||
self.y.append(self.y[0])
|
||||
self.closed = True
|
||||
return
|
||||
|
||||
def applykerf(self, kerf):
|
||||
""" Apply an offset to allow for the kerf when cutting. """
|
||||
self.close()
|
||||
|
||||
# Convert the points to lines.
|
||||
lines = [Line(Point(x1, y1), Point(x2, y2)) for x1, y1, x2, y2 in
|
||||
zip(self.x[:-1], self.y[:-1], self.x[1:], self.y[1:])]
|
||||
|
||||
# Add the kerf to the lines.
|
||||
for line in lines:
|
||||
line.addkerf(kerf)
|
||||
|
||||
# Extract the line intersections as the new points.
|
||||
pts = [line1.intersection(line2) for line1, line2 in zip(lines, lines[-1:] + lines[:-1])]
|
||||
self.clear()
|
||||
self.x += [pt.x for pt in pts]
|
||||
self.y += [pt.y for pt in pts]
|
||||
self.x += self.x[:1]
|
||||
self.y += self.y[:1]
|
||||
return
|
||||
|
||||
def offset(self, pt):
|
||||
""" Move a trace by an x/y offset. """
|
||||
self.x = [x + pt.x for x in self.x]
|
||||
self.y = [y + pt.y for y in self.y]
|
||||
|
||||
def clear(self):
|
||||
self.x = []
|
||||
self.y = []
|
||||
|
||||
def svg(self):
|
||||
# Generate SVG string for this trace.
|
||||
if len(self.x)<2:
|
||||
return ""
|
||||
return "M {} {} ".format(self.x[0], self.y[0]) + \
|
||||
" ".join(["L {} {}".format(x, y) for x, y in zip(self.x[1:], self.y[1:])])
|
||||
|
||||
def bbox(self):
|
||||
return Point(min(self.x), min(self.y)), Point(max(self.x), max(self.y))
|
||||
|
||||
def __len__(self):
|
||||
return len(self.x)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (iscloselist(self.x,other.x) and iscloselist(self.y, other.y))
|
||||
|
||||
def __ne__(self, other):
|
||||
return (not iscloselist(self.x, other.x) or not iscloselist(self.y, other.y))
|
||||
|
||||
def __add__(self, other):
|
||||
new = Trace()
|
||||
if isinstance(other, Point):
|
||||
new.x = self.x + [other.x]
|
||||
new.y = self.y + [other.y]
|
||||
elif isinstance(other, Trace):
|
||||
new.x = self.x + other.x
|
||||
new.y = self.y + other.y
|
||||
else:
|
||||
raise RuntimeError("Can only add a Trace or Point to an existing Trace.")
|
||||
return new
|
||||
|
||||
def __iadd__(self, other):
|
||||
if isinstance(other, Point):
|
||||
self.x.append(other.x)
|
||||
self.y.append(other.y)
|
||||
elif isinstance(other, Trace):
|
||||
self.x += other.x
|
||||
self.y += other.y
|
||||
else:
|
||||
raise RuntimeError("Can only add a Trace or Point to an existing Trace.")
|
||||
return self
|
||||
|
||||
def __repr__(self):
|
||||
return "Trace(" + str(self.x) + ", " + str(self.y) + ")"
|
||||
|
||||
def __str__(self):
|
||||
return "(" + str(self.x) + ", " + str(self.y) + ")"
|
||||
|
||||
def __getitem__(self, key):
|
||||
""" Used to override the slice functionality (eg: reversing). """
|
||||
new = Trace()
|
||||
new.x = self.x[key]
|
||||
new.y = self.y[key]
|
||||
return new
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
""" Used to override the slice functionality. """
|
||||
if isinstance(value, Point):
|
||||
self.x[key] = value.x
|
||||
self.y[key] = value.y
|
||||
else:
|
||||
raise RuntimeError("Can only update a single item in an existing Trace.")
|
||||
return self
|
||||
|
||||
def __delitem__(self, key):
|
||||
""" Used to override the slice functionality (eg: reversing). """
|
||||
del self.x[key]
|
||||
del self.y[key]
|
||||
return self
|
||||
|
||||
def __reversed__(self):
|
||||
""" Used to override the slice functionality (eg: reversing). """
|
||||
new = Trace()
|
||||
new.x = list(reversed(self.x))
|
||||
new.y = list(reversed(self.y))
|
||||
return new
|
@ -0,0 +1,22 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .platform import Platform
|
||||
from .circular import Circular
|
||||
from .rollerframe import RollerFrame
|
||||
|
||||
__all__ = ["Platform", "Circular", "RollerFrame"]
|
@ -0,0 +1,39 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ..core.point import Point
|
||||
from ..core.part import Part
|
||||
from ..core.circle import Circle
|
||||
from .platform import Platform
|
||||
from math import pi
|
||||
|
||||
class Circular(Platform):
|
||||
""" Circular Platform. """
|
||||
def __init__(self, radius, inradius, segments, cuts, cutdepth, start=0.0, end=pi*2, rotation=0.0,
|
||||
origin=Point(0.0, 0.0), thickness=0.0):
|
||||
super(Circular, self).__init__(thickness)
|
||||
self.radius = radius
|
||||
self.inradius = inradius
|
||||
self.segments = segments
|
||||
outer = Circle(self.radius, segments, cuts, cutdepth=cutdepth, start=start, end=end,
|
||||
rotation=rotation, origin=origin, thickness=thickness)
|
||||
outer.close()
|
||||
inner = Circle(self.inradius, segments, 0, start=start, end=end,
|
||||
rotation=rotation, origin=origin, thickness=thickness)
|
||||
inner.close()
|
||||
self.traces.append(outer)
|
||||
self.traces.append(reversed(inner))
|
@ -0,0 +1,24 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ..core.part import Part
|
||||
|
||||
class Platform(Part):
|
||||
def __init__(self, thickness):
|
||||
super(Platform, self).__init__()
|
||||
self.thickness = thickness
|
||||
return
|
@ -0,0 +1,378 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ..core.point import Point
|
||||
from ..core.part import Part
|
||||
from ..core.trace import Trace
|
||||
from ..core.circle import Circle
|
||||
from ..core.fingerjoint import FingerJoint
|
||||
from ..core.neopixel import NeoPixel
|
||||
from .platform import Platform
|
||||
from ..util import intersection
|
||||
from math import pi, sqrt, asin, atan
|
||||
|
||||
#import inkex
|
||||
|
||||
class RollerFrame(Platform):
|
||||
""" RollerBot Platform. """
|
||||
def __init__(self, supwidth, wheelradius, upperradius, lowerradius,
|
||||
facesize, barsize, primarygapwidth, secondarygapwidth,
|
||||
scale, part_id, thickness=0.0):
|
||||
super(RollerFrame, self).__init__(thickness)
|
||||
self.supwidth = supwidth
|
||||
self.barsize = barsize
|
||||
|
||||
cutdepth = supwidth / 3.0
|
||||
barradius = sqrt(2.0*(barsize/2.0)**2)
|
||||
|
||||
facewidth = primarygapwidth*2.0 + thickness*3.0
|
||||
faceheight = facesize + thickness
|
||||
fjoint = FingerJoint(faceheight, thickness*2.0, 'height', thickness=thickness) # Face
|
||||
bjoint = FingerJoint(faceheight, thickness*2.0, 'depth', thickness=thickness) # Base
|
||||
wjoint = FingerJoint(facewidth, thickness*2.0, 'width', thickness=thickness) # Length
|
||||
|
||||
if part_id<5:
|
||||
# The circular segments for the main body structure.
|
||||
|
||||
# Outer section.
|
||||
x = barsize/2.0
|
||||
y = sqrt(lowerradius**2 - x**2)
|
||||
a = atan(x/y)
|
||||
outer = Circle(upperradius, 72, 5, cutdepth=cutdepth, start=0.0, end=pi, thickness=thickness) + \
|
||||
Point(0.0, -upperradius + cutdepth) + \
|
||||
Point(-thickness, -upperradius + cutdepth) + \
|
||||
Point(-thickness, -upperradius) + \
|
||||
Point(-barsize/2.0, -upperradius) + \
|
||||
Circle(lowerradius, 72, 4, cutdepth=cutdepth, start=pi+a, end=pi*2-a, thickness=thickness) + \
|
||||
Point(-barsize/2.0, upperradius) + \
|
||||
Point(-thickness, upperradius) + \
|
||||
Point(-thickness, upperradius - cutdepth) + \
|
||||
Point(0.0, upperradius - cutdepth)
|
||||
outer.close()
|
||||
self.traces.append(outer)
|
||||
|
||||
if part_id in (0,4):
|
||||
# Central Motor Position.
|
||||
inner = Trace() + \
|
||||
Point(-barsize/2.0, -barsize/2.0) + \
|
||||
Point(-barsize/2.0, barsize/2.0) + \
|
||||
Point(-barsize/6.0, barsize/2.0) + \
|
||||
Point(-barsize/6.0, barsize/2.0-thickness/2.0) + \
|
||||
Point(barsize/6.0, barsize/2.0-thickness/2.0) + \
|
||||
Point(barsize/6.0, barsize/2.0) + \
|
||||
Point(barsize/2.0, barsize/2.0) + \
|
||||
Point(barsize/2.0, -barsize/2.0) + \
|
||||
Point(barsize/6.0, -barsize/2.0) + \
|
||||
Point(barsize/6.0, -barsize/2.0+thickness/2.0) + \
|
||||
Point(-barsize/6.0, -barsize/2.0+thickness/2.0) + \
|
||||
Point(-barsize/6.0, -barsize/2.0)
|
||||
inner.close()
|
||||
self.traces.append(reversed(inner))
|
||||
|
||||
# Outer parts are complete, inner parts have cutouts.
|
||||
if part_id in (1,2,3):
|
||||
# Central Motor Position and Bar.
|
||||
inner = Trace() + \
|
||||
Point(-barsize/2.0*1.3, -barsize/2.0) + \
|
||||
Point(-barsize/2.0*1.3, -barsize/2.0*0.55) + \
|
||||
Point(-barsize/2.0, -barsize/2.0*0.55) + \
|
||||
Point(-barsize/2.0, barsize/2.0*0.55) + \
|
||||
Point(-barsize/2.0*1.3, barsize/2.0*0.55) + \
|
||||
Point(-barsize/2.0*1.3, barsize/2.0) + \
|
||||
Point(barsize/2.0, barsize/2.0) + \
|
||||
Point(barsize/2.0, barsize/10.0) + \
|
||||
Point(barsize/2.0*1.2, barsize/20.0) + \
|
||||
Point(barsize/2.0*1.2, -barsize/20.0) + \
|
||||
Point(barsize/2.0, -barsize/10.0) + \
|
||||
Point(barsize/2.0, -barsize/2.0)
|
||||
inner.close()
|
||||
self.traces.append(reversed(inner))
|
||||
|
||||
# Upper segment cut-outs.
|
||||
x = supwidth/2.0
|
||||
y = sqrt((upperradius-supwidth)**2 - x**2)
|
||||
a_outer = atan(x/y)
|
||||
y = sqrt((barradius+supwidth)**2 - x**2)
|
||||
a_inner = atan(x/y)
|
||||
|
||||
inner = self._segment(upperradius-supwidth, barradius+supwidth,
|
||||
0, 1, cutdepth, 0.0, a_outer, a_inner)
|
||||
self.traces.append(reversed(inner))
|
||||
|
||||
fa = (pi/2.0 - self._faceangle(facesize, upperradius)) / 2.0
|
||||
(fx, fy) = intersection(upperradius, angle=fa)
|
||||
if 0:
|
||||
inner = Trace() + \
|
||||
Point(fx, -fy) + \
|
||||
Point(fy, -fy) + \
|
||||
Point(fy, -fx)
|
||||
inner.close()
|
||||
self.traces.append(reversed(inner))
|
||||
|
||||
oy = fy-thickness*2.0
|
||||
(ox, oa) = intersection(upperradius, y=oy)
|
||||
if 0:
|
||||
inner = Trace() + \
|
||||
Point(ox, -oy) + \
|
||||
Point(oy, -oy) + \
|
||||
Point(oy, -ox)
|
||||
inner.close()
|
||||
self.traces.append(reversed(inner))
|
||||
|
||||
iy = oy
|
||||
(ix, ia) = intersection(upperradius-supwidth, y=iy)
|
||||
|
||||
if part_id==2:
|
||||
inner = Circle(upperradius-supwidth, 18, 0, cutdepth=cutdepth,
|
||||
start=pi/3+a_outer, end=pi-a_outer,
|
||||
thickness=self.thickness) + \
|
||||
reversed(Circle(barradius+supwidth, 18, 0, cutdepth=cutdepth,
|
||||
start=pi/3+a_inner, end=pi-a_inner,
|
||||
thickness=self.thickness))
|
||||
inner.close()
|
||||
self.traces.append(reversed(inner))
|
||||
|
||||
# Temporary cut to remove where the face will be installed.
|
||||
oy = fy-thickness
|
||||
(ox, oa) = intersection(upperradius, y=oy)
|
||||
inner = Trace() + \
|
||||
Point(ox, -ox) + \
|
||||
Point(oy, -ox) + \
|
||||
Point(oy, -oy) + \
|
||||
Point(ox, -oy)
|
||||
inner.close()
|
||||
self.traces.append(reversed(inner))
|
||||
|
||||
else:
|
||||
inner = Circle(upperradius-supwidth, 18, 0, cutdepth=cutdepth,
|
||||
start=pi/3*1+a_outer, end=pi/2+ia,
|
||||
thickness=self.thickness) + \
|
||||
reversed(Circle(barradius+supwidth, 18, 0, cutdepth=cutdepth,
|
||||
start=pi/3*1+a_inner, end=pi/3*2-a_inner,
|
||||
thickness=self.thickness))
|
||||
inner.close()
|
||||
self.traces.append(reversed(inner))
|
||||
|
||||
ia = pi/2 - ia
|
||||
(ix, iy) = intersection(upperradius-supwidth, angle=ia)
|
||||
|
||||
inner = Circle(upperradius-supwidth, 18, 0, cutdepth=cutdepth,
|
||||
start=pi/2+ia, end=pi/3*3-a_outer,
|
||||
thickness=self.thickness) + \
|
||||
reversed(Circle(barradius+supwidth, 18, 0, cutdepth=cutdepth,
|
||||
start=pi/3*2+a_inner, end=pi/3*3-a_inner,
|
||||
thickness=self.thickness)) + \
|
||||
Point(ix, -ix)
|
||||
inner.close()
|
||||
self.traces.append(reversed(inner))
|
||||
|
||||
# Face and base cutout slots.
|
||||
cy = fy-thickness
|
||||
for (x1, x2) in zip(fjoint.fingers[1::2],fjoint.fingers[2::2]):
|
||||
inner = Trace() + \
|
||||
Point(cy+x1, -cy) + \
|
||||
Point(cy+x2, -cy) + \
|
||||
Point(cy+x2, -cy-thickness) + \
|
||||
Point(cy+x1, -cy-thickness)
|
||||
inner.close()
|
||||
self.traces.append(reversed(inner))
|
||||
for (y1, y2) in zip(bjoint.fingers[1::2],bjoint.fingers[2::2]):
|
||||
inner = Trace() + \
|
||||
Point(cy, -cy-y2) + \
|
||||
Point(cy, -cy-y1) + \
|
||||
Point(cy+thickness, -cy-y1) + \
|
||||
Point(cy+thickness, -cy-y2)
|
||||
inner.close()
|
||||
self.traces.append(reversed(inner))
|
||||
|
||||
if 0:
|
||||
if part_id==2:
|
||||
for seg in range(2):
|
||||
segnext = seg*2+1
|
||||
inner = self._segment(upperradius-supwidth, barradius+supwidth,
|
||||
seg, segnext, cutdepth, 0.0, a_outer, a_inner)
|
||||
self.traces.append(reversed(inner))
|
||||
else:
|
||||
for seg in range(3):
|
||||
segnext = seg+1
|
||||
inner = self._segment(upperradius-supwidth, barradius+supwidth,
|
||||
seg, segnext, cutdepth, 0.0, a_outer, a_inner)
|
||||
self.traces.append(reversed(inner))
|
||||
|
||||
# Lower segment cut-outs.
|
||||
x = supwidth/2.0
|
||||
y = sqrt((lowerradius-supwidth)**2 - x**2)
|
||||
a_outer = atan(x/y)
|
||||
y = sqrt((barradius+supwidth)**2 - x**2)
|
||||
a_inner = atan(x/y)
|
||||
|
||||
for seg in range(3):
|
||||
segnext = seg+1
|
||||
inner = self._segment(lowerradius-supwidth, barradius+supwidth,
|
||||
seg, segnext, cutdepth, pi, a_outer, a_inner)
|
||||
self.traces.append(reversed(inner))
|
||||
|
||||
if part_id in (1,2,3):
|
||||
r_mid = barradius+supwidth + ((upperradius-supwidth) - (barradius+supwidth))/2.0
|
||||
self._slot(barsize/2.0 + supwidth/2.0, cutdepth*1.5, cutdepth)
|
||||
self._slot(barsize/2.0 + supwidth/2.0, -cutdepth*1.5, cutdepth)
|
||||
self._slot(barsize/2.0 + supwidth/2.0, r_mid+cutdepth*1.5, cutdepth)
|
||||
self._slot(barsize/2.0 + supwidth/2.0, r_mid-cutdepth*1.5, cutdepth)
|
||||
|
||||
elif part_id in (5,6):
|
||||
# The board supports.
|
||||
x = primarygapwidth
|
||||
y = ((upperradius-supwidth) + (barradius+supwidth))/2.0 + supwidth*2.0
|
||||
t = Trace() + \
|
||||
Point(0.0, 0.0) + \
|
||||
Point(0.0, supwidth-cutdepth*2.0) + \
|
||||
Point(-cutdepth, supwidth-cutdepth*2.0) + \
|
||||
Point(-cutdepth, supwidth-cutdepth*1.0) + \
|
||||
Point(0.0, supwidth-cutdepth*1.0) + \
|
||||
Point(0.0, supwidth*2.0-cutdepth*2.0) + \
|
||||
Point(-cutdepth, supwidth*2.0-cutdepth*2.0) + \
|
||||
Point(-cutdepth, supwidth*2.0-cutdepth*1.0) + \
|
||||
Point(0.0, supwidth*2.0-cutdepth*1.0) + \
|
||||
Point(0.0, y-supwidth*2.0+cutdepth*1.0) + \
|
||||
Point(-cutdepth, y-supwidth*2.0+cutdepth*1.0) + \
|
||||
Point(-cutdepth, y-supwidth*2.0+cutdepth*2.0) + \
|
||||
Point(0.0, y-supwidth*2.0+cutdepth*2.0) + \
|
||||
Point(0.0, y-supwidth+cutdepth*1.0) + \
|
||||
Point(-cutdepth, y-supwidth+cutdepth*1.0) + \
|
||||
Point(-cutdepth, y-supwidth+cutdepth*2.0) + \
|
||||
Point(0.0, y-supwidth+cutdepth*2.0) + \
|
||||
Point(0.0, y) + \
|
||||
Point(x, y) + \
|
||||
Point(x, y-supwidth-cutdepth*1.0) + \
|
||||
Point(x+cutdepth, y-supwidth-cutdepth*1.0) + \
|
||||
Point(x+cutdepth, y-supwidth-cutdepth*2.0) + \
|
||||
Point(x, y-supwidth-cutdepth*2.0) + \
|
||||
Point(x, supwidth-cutdepth*1.0) + \
|
||||
Point(x+cutdepth, supwidth-cutdepth*1.0) + \
|
||||
Point(x+cutdepth, supwidth-cutdepth*2.0) + \
|
||||
Point(x, supwidth-cutdepth*2.0) + \
|
||||
Point(x, 0.0)
|
||||
t.close()
|
||||
self.traces.append(t)
|
||||
|
||||
elif part_id==7:
|
||||
# The face components.
|
||||
t = Trace(x=[thickness], y=[thickness])
|
||||
for i, pos in enumerate(fjoint.fingers[1:-1]):
|
||||
if i%2==0:
|
||||
t += Point(pos, thickness)
|
||||
t += Point(pos, 0.0)
|
||||
else:
|
||||
t += Point(pos, 0.0)
|
||||
t += Point(pos, thickness)
|
||||
t += Point(faceheight, thickness)
|
||||
t += Point(faceheight, facewidth-thickness)
|
||||
for i, pos in enumerate(reversed(fjoint.fingers[1:-1])):
|
||||
if i%2==0:
|
||||
t += Point(pos, facewidth-thickness)
|
||||
t += Point(pos, facewidth)
|
||||
else:
|
||||
t += Point(pos, facewidth)
|
||||
t += Point(pos, facewidth-thickness)
|
||||
t += Point(thickness, facewidth-thickness)
|
||||
for i, pos in enumerate(reversed(wjoint.fingers[1:-1])):
|
||||
if i%2==0:
|
||||
t += Point(thickness, pos)
|
||||
t += Point(0.0, pos)
|
||||
else:
|
||||
t += Point(0.0, pos)
|
||||
t += Point(thickness, pos)
|
||||
t.close()
|
||||
self.traces.append(t)
|
||||
|
||||
elif part_id==8:
|
||||
t = Trace(x=[thickness], y=[0.0])
|
||||
t += Point(facewidth-thickness, 0.0)
|
||||
for i, pos in enumerate(bjoint.fingers[1:-1]):
|
||||
if i%2==0:
|
||||
t += Point(facewidth-thickness, pos)
|
||||
t += Point(facewidth, pos)
|
||||
else:
|
||||
t += Point(facewidth, pos)
|
||||
t += Point(facewidth-thickness, pos)
|
||||
t += Point(facewidth-thickness, faceheight)
|
||||
for i, pos in enumerate(reversed(wjoint.fingers[1:-1])):
|
||||
if i%2==0:
|
||||
t += Point(pos, faceheight)
|
||||
t += Point(pos, faceheight-thickness)
|
||||
else:
|
||||
t += Point(pos, faceheight-thickness)
|
||||
t += Point(pos, faceheight)
|
||||
t += Point(thickness, faceheight)
|
||||
for i, pos in enumerate(reversed(bjoint.fingers[1:-1])):
|
||||
if i%2==0:
|
||||
t += Point(thickness, pos)
|
||||
t += Point(0.0, pos)
|
||||
else:
|
||||
t += Point(0.0, pos)
|
||||
t += Point(thickness, pos)
|
||||
t.close()
|
||||
self.traces.append(t)
|
||||
|
||||
for eye in range(2):
|
||||
np = NeoPixel(style='rings', origin=Point(facewidth/4.0*(1+eye*2), faceheight/2.5), scale=scale, rotate=pi/2)
|
||||
self.traces.extend(np.traces)
|
||||
np = NeoPixel(style='strip', origin=Point(facewidth/2.0, faceheight*0.80), scale=scale)
|
||||
self.traces.extend(np.traces)
|
||||
|
||||
# Camera
|
||||
csize = 8.5*scale
|
||||
t = Trace() + \
|
||||
Point(facewidth/2.0 - csize/2.0, faceheight/2.5 - csize/2.0) + \
|
||||
Point(facewidth/2.0 + csize/2.0, faceheight/2.5 - csize/2.0) + \
|
||||
Point(facewidth/2.0 + csize/2.0, faceheight/2.5 + csize/2.0) + \
|
||||
Point(facewidth/2.0 - csize/2.0, faceheight/2.5 + csize/2.0)
|
||||
t.close()
|
||||
self.traces.append(t)
|
||||
|
||||
def _faceangle(self, size, radius):
|
||||
# Returns total angle required for a face.
|
||||
o = sqrt(2.0*(size**2))*0.5
|
||||
h = radius
|
||||
return 2.0*asin(o/h)
|
||||
|
||||
def _segment(self, r_outer, r_inner, seg, segnext, cutdepth, a_offset, a_outer, a_inner):
|
||||
# Create an inner segment cutout.
|
||||
t = Circle(r_outer, 18, 0, cutdepth=cutdepth,
|
||||
start=a_offset+pi/3*seg+a_outer, end=a_offset+pi/3*segnext-a_outer,
|
||||
thickness=self.thickness) + \
|
||||
reversed(Circle(r_inner, 18, 0, cutdepth=cutdepth,
|
||||
start=a_offset+pi/3*seg+a_inner, end=a_offset+pi/3*segnext-a_inner,
|
||||
thickness=self.thickness))
|
||||
if a_offset == 0.0 and seg == 0:
|
||||
r_mid = r_inner + (r_outer - r_inner)/2.0
|
||||
t += Trace() + \
|
||||
Point(self.supwidth / 2.0, r_mid - self.supwidth) + \
|
||||
Point(self.barsize/2.0 + self.supwidth, r_mid - self.supwidth) + \
|
||||
Point(self.barsize/2.0 + self.supwidth, r_mid + self.supwidth) + \
|
||||
Point(self.supwidth / 2.0, r_mid + self.supwidth)
|
||||
t.close()
|
||||
return t
|
||||
|
||||
def _slot(self, x, y, cutdepth):
|
||||
slot = Trace() + \
|
||||
Point(x-self.thickness/2.0, y+cutdepth/2.0) + \
|
||||
Point(x+self.thickness/2.0, y+cutdepth/2.0) + \
|
||||
Point(x+self.thickness/2.0, y-cutdepth/2.0) + \
|
||||
Point(x-self.thickness/2.0, y-cutdepth/2.0)
|
||||
slot.close()
|
||||
self.traces.append(reversed(slot))
|
@ -0,0 +1,25 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .shape import Shape
|
||||
from .box import Box
|
||||
from .cone import Cone
|
||||
from .cylinder import Cylinder
|
||||
from .sphere import Sphere
|
||||
from .rollerbot import RollerBot
|
||||
|
||||
__all__ = ["Shape", "Box", "Cone", "Cylinder", "Sphere", "RollerBot"]
|
161
extensions/fablabchemnitz/cutcraft/cutcraft/shapes/box.py
Normal file
161
extensions/fablabchemnitz/cutcraft/cutcraft/shapes/box.py
Normal file
@ -0,0 +1,161 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ..core.part import Part
|
||||
from ..core.point import Point
|
||||
from ..core.trace import Trace
|
||||
from ..core.fingerjoint import FingerJoint
|
||||
from .shape import Shape
|
||||
|
||||
class Box(Shape):
|
||||
""" List of segments that make up a part. """
|
||||
def __init__(self, width, depth, height, thickness, kerf, top=True, bottom=True,
|
||||
left=True, right=True, front=True, back=True):
|
||||
super(Box, self).__init__(thickness, kerf)
|
||||
|
||||
self.width = width
|
||||
self.depth = depth
|
||||
self.height = height
|
||||
|
||||
self.faces = []
|
||||
self.parts = []
|
||||
|
||||
for face in range(6):
|
||||
p = self._face(face, width, depth, height, thickness, top, bottom, left, right, front, back)
|
||||
self.faces.append((p, face))
|
||||
self.parts.append((p, face))
|
||||
|
||||
if kerf:
|
||||
for part, _ in self.parts:
|
||||
part.applykerf(kerf)
|
||||
|
||||
def _face(self, face, width, depth, height, thickness, top, bottom, left, right, front, back):
|
||||
faces = (top, bottom, left, right, front, back)
|
||||
|
||||
# Check if the requested face is active for this box.
|
||||
if faces[face] == False:
|
||||
return None
|
||||
|
||||
wjoint = FingerJoint(width, thickness*2.0, 'width', thickness=thickness)
|
||||
djoint = FingerJoint(depth, thickness*2.0, 'depth', thickness=thickness)
|
||||
hjoint = FingerJoint(height, thickness*2.0, 'height', thickness=thickness)
|
||||
|
||||
if face in (0, 1):
|
||||
t = Trace(x=[thickness], y=[thickness])
|
||||
for i, pos in enumerate(djoint.fingers[1:-1]):
|
||||
if i%2==0:
|
||||
t += Point(pos, thickness)
|
||||
t += Point(pos, 0.0)
|
||||
else:
|
||||
t += Point(pos, 0.0)
|
||||
t += Point(pos, thickness)
|
||||
t += Point(depth-thickness, thickness)
|
||||
for i, pos in enumerate(wjoint.fingers[1:-1]):
|
||||
if i%2==0:
|
||||
t += Point(depth-thickness, pos)
|
||||
t += Point(depth, pos)
|
||||
else:
|
||||
t += Point(depth, pos)
|
||||
t += Point(depth-thickness, pos)
|
||||
t += Point(depth-thickness, width-thickness)
|
||||
for i, pos in enumerate(reversed(djoint.fingers[1:-1])):
|
||||
if i%2==0:
|
||||
t += Point(pos, width-thickness)
|
||||
t += Point(pos, width)
|
||||
else:
|
||||
t += Point(pos, width)
|
||||
t += Point(pos, width-thickness)
|
||||
t += Point(thickness, width-thickness)
|
||||
for i, pos in enumerate(reversed(wjoint.fingers[1:-1])):
|
||||
if i%2==0:
|
||||
t += Point(thickness, pos)
|
||||
t += Point(0.0, pos)
|
||||
else:
|
||||
t += Point(0.0, pos)
|
||||
t += Point(thickness, pos)
|
||||
elif face in (2, 3):
|
||||
t = Trace(x=[0.0], y=[0.0])
|
||||
for i, pos in enumerate(djoint.fingers[1:-1]):
|
||||
if i%2==0:
|
||||
t += Point(pos, 0.0)
|
||||
t += Point(pos, thickness)
|
||||
else:
|
||||
t += Point(pos, thickness)
|
||||
t += Point(pos, 0.0)
|
||||
t += Point(depth, 0.0)
|
||||
for i, pos in enumerate(hjoint.fingers[1:-1]):
|
||||
if i%2==0:
|
||||
t += Point(depth, pos)
|
||||
t += Point(depth-thickness, pos)
|
||||
else:
|
||||
t += Point(depth-thickness, pos)
|
||||
t += Point(depth, pos)
|
||||
t += Point(depth, height)
|
||||
for i, pos in enumerate(reversed(djoint.fingers[1:-1])):
|
||||
if i%2==0:
|
||||
t += Point(pos, height)
|
||||
t += Point(pos, height-thickness)
|
||||
else:
|
||||
t += Point(pos, height-thickness)
|
||||
t += Point(pos, height)
|
||||
t += Point(0.0, height)
|
||||
for i, pos in enumerate(reversed(hjoint.fingers[1:-1])):
|
||||
if i%2==0:
|
||||
t += Point(0.0, pos)
|
||||
t += Point(thickness, pos)
|
||||
else:
|
||||
t += Point(thickness, pos)
|
||||
t += Point(0.0, pos)
|
||||
pass
|
||||
elif face in (4, 5):
|
||||
t = Trace(x=[thickness], y=[0.0])
|
||||
for i, pos in enumerate(wjoint.fingers[1:-1]):
|
||||
if i%2==0:
|
||||
t += Point(pos, 0.0)
|
||||
t += Point(pos, thickness)
|
||||
else:
|
||||
t += Point(pos, thickness)
|
||||
t += Point(pos, 0.0)
|
||||
t += Point(width-thickness, 0.0)
|
||||
for i, pos in enumerate(hjoint.fingers[1:-1]):
|
||||
if i%2==0:
|
||||
t += Point(width-thickness, pos)
|
||||
t += Point(width, pos)
|
||||
else:
|
||||
t += Point(width, pos)
|
||||
t += Point(width-thickness, pos)
|
||||
t += Point(width-thickness, height)
|
||||
for i, pos in enumerate(reversed(wjoint.fingers[1:-1])):
|
||||
if i%2==0:
|
||||
t += Point(pos, height)
|
||||
t += Point(pos, height-thickness)
|
||||
else:
|
||||
t += Point(pos, height-thickness)
|
||||
t += Point(pos, height)
|
||||
t += Point(thickness, height)
|
||||
for i, pos in enumerate(reversed(hjoint.fingers[1:-1])):
|
||||
if i%2==0:
|
||||
t += Point(thickness, pos)
|
||||
t += Point(0.0, pos)
|
||||
else:
|
||||
t += Point(0.0, pos)
|
||||
t += Point(thickness, pos)
|
||||
pass
|
||||
|
||||
t.close()
|
||||
|
||||
return Part() + t
|
34
extensions/fablabchemnitz/cutcraft/cutcraft/shapes/cone.py
Normal file
34
extensions/fablabchemnitz/cutcraft/cutcraft/shapes/cone.py
Normal file
@ -0,0 +1,34 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ..core.part import Part
|
||||
from ..platforms.circular import Circular
|
||||
from .shape import Shape
|
||||
|
||||
class Cone(Shape):
|
||||
""" List of segments that make up a part. """
|
||||
def __init__(self, height, radius1, radius2, segments, cuts, cutdepth, platforms,
|
||||
thickness, kerf):
|
||||
super(Cone, self).__init__(thickness, kerf)
|
||||
|
||||
# levels = [p/(platforms-1)*height for p in range(platforms)]
|
||||
# radii = [radius1 + (radius2-radius1)*p/(platforms-1) for p in range(platforms)]
|
||||
|
||||
# for level, radius in zip(levels, radii):
|
||||
# p = cc.Part()
|
||||
# p += cp.Circular(radius, segments, cuts, cutdepth, thickness=thickness, kerf=kerf).part
|
||||
# self.parts.append((p, level))
|
@ -0,0 +1,47 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ..core.part import Part
|
||||
from ..platforms.circular import Circular
|
||||
from ..supports.pier import Pier
|
||||
from .shape import Shape
|
||||
|
||||
class Cylinder(Shape):
|
||||
""" List of segments that make up a part. """
|
||||
def __init__(self, height, radius, inradius, segments, cuts, cutdepth, supwidth, platforms,
|
||||
thickness, kerf):
|
||||
super(Cylinder, self).__init__(thickness, kerf)
|
||||
|
||||
self.platforms = []
|
||||
self.piers = []
|
||||
|
||||
# List of vertical positions for the platforms
|
||||
levels = [float(p)/float(platforms-1)*(height-thickness) for p in range(platforms)]
|
||||
|
||||
for level in levels:
|
||||
p = Circular(radius, inradius, segments, cuts, cutdepth, thickness=thickness)
|
||||
self.platforms.append((p, level))
|
||||
self.parts.append((p, level))
|
||||
|
||||
for _ in range(cuts):
|
||||
p = Pier(height, supwidth, supwidth-cutdepth, [(level, 0.0) for level in levels], thickness=thickness)
|
||||
self.piers.append((p, None))
|
||||
self.parts.append((p, None))
|
||||
|
||||
if kerf:
|
||||
for part, _ in self.parts:
|
||||
part.applykerf(kerf)
|
@ -0,0 +1,64 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ..core.part import Part
|
||||
from ..platforms.rollerframe import RollerFrame
|
||||
from ..supports.pier import Pier
|
||||
from .shape import Shape
|
||||
|
||||
class RollerBot(Shape):
|
||||
""" List of segments that make up a part. """
|
||||
def __init__(self, width, supwidth, wheelradius, upperradius, lowerradius,
|
||||
facesize, barsize, primarygapwidth, secondarygapwidth, scale,
|
||||
thickness, kerf):
|
||||
super(RollerBot, self).__init__(thickness, kerf)
|
||||
|
||||
self.platforms = []
|
||||
self.piers = []
|
||||
|
||||
cutdepth = supwidth / 3.0
|
||||
|
||||
for level in range(9):
|
||||
# for level in range(7):
|
||||
p = RollerFrame(supwidth, wheelradius, upperradius, lowerradius,
|
||||
facesize, barsize, primarygapwidth, secondarygapwidth,
|
||||
scale, level, thickness=thickness)
|
||||
self.platforms.append((p, 0.0))
|
||||
self.parts.append((p, 0.0))
|
||||
|
||||
levels = [0.0, secondarygapwidth+thickness,
|
||||
secondarygapwidth+primarygapwidth+thickness*2.0,
|
||||
secondarygapwidth+primarygapwidth*2.0+thickness*3.0,
|
||||
secondarygapwidth*2.0+primarygapwidth*2.0+thickness*4.0 ]
|
||||
height = secondarygapwidth*2.0+primarygapwidth*2.0+thickness*5.0
|
||||
|
||||
for _ in range(9):
|
||||
p = Pier(height, supwidth, supwidth-cutdepth, [(level, 0.0) for level in levels], thickness=thickness)
|
||||
self.piers.append((p, None))
|
||||
self.parts.append((p, None))
|
||||
|
||||
levels = [0.0, secondarygapwidth+thickness ]
|
||||
height = secondarygapwidth+thickness*2.0
|
||||
|
||||
for _ in range(4):
|
||||
p = Pier(height, supwidth, supwidth-cutdepth, [(level, 0.0) for level in levels], thickness=thickness)
|
||||
self.piers.append((p, None))
|
||||
self.parts.append((p, None))
|
||||
|
||||
if kerf:
|
||||
for part, _ in self.parts:
|
||||
part.applykerf(kerf)
|
27
extensions/fablabchemnitz/cutcraft/cutcraft/shapes/shape.py
Normal file
27
extensions/fablabchemnitz/cutcraft/cutcraft/shapes/shape.py
Normal file
@ -0,0 +1,27 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
class Shape(object):
|
||||
def __init__(self, thickness, kerf):
|
||||
self.thickness = thickness
|
||||
self.kerf = kerf
|
||||
self.parts = []
|
||||
return
|
||||
|
||||
def close(self):
|
||||
for part in self.parts:
|
||||
part[0].close()
|
25
extensions/fablabchemnitz/cutcraft/cutcraft/shapes/sphere.py
Normal file
25
extensions/fablabchemnitz/cutcraft/cutcraft/shapes/sphere.py
Normal file
@ -0,0 +1,25 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ..core.part import Part
|
||||
from ..platforms.circular import Circular
|
||||
from .shape import Shape
|
||||
|
||||
class Sphere(Shape):
|
||||
""" List of segments that make up a part. """
|
||||
def __init__(self):
|
||||
super(Sphere, self).__init__()
|
@ -0,0 +1,21 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .support import Support
|
||||
from .pier import Pier
|
||||
|
||||
__all__ = ["Support", "Pier"]
|
84
extensions/fablabchemnitz/cutcraft/cutcraft/supports/pier.py
Normal file
84
extensions/fablabchemnitz/cutcraft/cutcraft/supports/pier.py
Normal file
@ -0,0 +1,84 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ..core.trace import Trace
|
||||
from .support import Support
|
||||
from ..util import isclose
|
||||
|
||||
class Pier(Support):
|
||||
""" List of segments that make up a part. """
|
||||
def __init__(self, height, depth, cutdepth, levels, thickness):
|
||||
super(Pier, self).__init__(height, thickness)
|
||||
|
||||
self.depth = depth
|
||||
self.cutdepth = cutdepth
|
||||
|
||||
# The 'levels' list is defined as follows:
|
||||
# [(height1, x-offset1), (height2, x-offset2), ...]
|
||||
# where the values are:
|
||||
# height<n>: The height of the bottom of the platform (0.0=at base level, height-thickness=at top level).
|
||||
# x-offset<n>: The distance from the core axis for this level. Used to create slopes and curves.
|
||||
|
||||
ys, xs = zip(*sorted(levels))
|
||||
if any([(h2) - (h1) < 3*thickness for h1, h2
|
||||
in zip(ys[:-1], ys[1:])]):
|
||||
raise RuntimeError("Pier levels are too close. Try decreasing the number of levels.")
|
||||
|
||||
self.topcut = isclose(ys[-1], height-thickness)
|
||||
self.bottomcut = isclose(ys[0], 0.0)
|
||||
self.vertical = all([isclose(x1, x2) for x1, x2 in zip(xs[:-1], xs[1:])])
|
||||
|
||||
# Starting at the bottom inner point, trace up the uncut side.
|
||||
if self.vertical:
|
||||
tx = [0.0, 0.0]
|
||||
ty = [0.0, height]
|
||||
else:
|
||||
tx = list(xs)
|
||||
ty = list(ys[:-1]) + [height]
|
||||
|
||||
# Add the top points.
|
||||
xtop = xs[0]
|
||||
xbottom = xs[-1]
|
||||
if self.topcut:
|
||||
tx.extend([xtop+depth-cutdepth, xtop+depth-cutdepth, xtop+depth])
|
||||
ty.extend([height, height-thickness, height-thickness])
|
||||
else:
|
||||
tx.extend([xtop+depth])
|
||||
ty.extend([height])
|
||||
|
||||
if self.topcut and self.bottomcut:
|
||||
xs = xs[1:-1]
|
||||
ys = ys[1:-1]
|
||||
elif self.topcut:
|
||||
xs = xs[:-1]
|
||||
ys = ys[:-1]
|
||||
elif self.bottomcut:
|
||||
xs = xs[1:]
|
||||
ys = ys[1:]
|
||||
|
||||
for y, x in zip(reversed(ys), reversed(xs)):
|
||||
tx.extend([x+depth, x+depth-cutdepth, x+depth-cutdepth, x+depth])
|
||||
ty.extend([y+thickness, y+thickness, y, y])
|
||||
|
||||
if self.bottomcut:
|
||||
tx.extend([xbottom+depth, xbottom+depth-cutdepth, xbottom+depth-cutdepth, xbottom])
|
||||
ty.extend([thickness, thickness, 0.0, 0.0])
|
||||
else:
|
||||
tx.extend([xbottom+depth, xbottom])
|
||||
ty.extend([0.0, 0.0])
|
||||
|
||||
self.traces.append(Trace(x=tx, y=ty))
|
@ -0,0 +1,25 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from ..core.part import Part
|
||||
|
||||
class Support(Part):
|
||||
""" General support. """
|
||||
def __init__(self, height, thickness):
|
||||
super(Support, self).__init__()
|
||||
self.height = height
|
||||
self.thickness = thickness
|
40
extensions/fablabchemnitz/cutcraft/cutcraft/util.py
Normal file
40
extensions/fablabchemnitz/cutcraft/cutcraft/util.py
Normal file
@ -0,0 +1,40 @@
|
||||
# Copyright (C) 2018 Michael Matthews
|
||||
#
|
||||
# This file is part of CutCraft.
|
||||
#
|
||||
# CutCraft 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.
|
||||
#
|
||||
# CutCraft 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 CutCraft. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from math import pi, atan2, cos, sin, sqrt
|
||||
|
||||
def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
|
||||
# Required as Inkscape uses old version of Python that does not include math.isclose().
|
||||
return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
|
||||
|
||||
def iscloselist(a, b):
|
||||
# Functionality of isclose() for lists of values.
|
||||
return all([isclose(aval, bval) for aval, bval in zip(a, b)])
|
||||
|
||||
def intersection(radius, angle=None, x=None, y=None):
|
||||
# With a circle of a given radius determine the intercepts for an angle, x or y coordinate.
|
||||
if angle:
|
||||
# Returns (x,y) tuple of intercept.
|
||||
return (cos(angle)*radius, sin(angle)*radius)
|
||||
elif x:
|
||||
y = sqrt((radius)**2 - x**2)
|
||||
return (y, atan2(y, x))
|
||||
elif y:
|
||||
x = sqrt((radius)**2 - y**2)
|
||||
return (x, atan2(y, x))
|
||||
else:
|
||||
raise ValueError("Invalid values passed to intersection().")
|
32
extensions/fablabchemnitz/cutcraft/cutcraftbox.inx
Normal file
32
extensions/fablabchemnitz/cutcraft/cutcraftbox.inx
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Cut-Craft Box</name>
|
||||
<id>fablabchemnitz.de.cutcraft.box</id>
|
||||
<param name="unit" gui-text="Measurement Units" gui-description="Unit of measurement for all subsequent values entered in this dialog" type="optiongroup" appearance="combo">
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="in">in</option>
|
||||
</param>
|
||||
<separator/>
|
||||
<param name="width" type="float" min="10.0" max="1000.0" precision="3" gui-text="Box Width" gui-description="Cylinder Width">60.0</param>
|
||||
<param name="depth" type="float" min="10.0" max="1000.0" precision="3" gui-text="Box Depth" gui-description="Cylinder Depth">30.0</param>
|
||||
<param name="height" type="float" min="10.0" max="1000.0" precision="3" gui-text="Box Height" gui-description="Cylinder Height">30.0</param>
|
||||
<separator/>
|
||||
<param name="thickness" type="float" min="0.1" max="1000.0" precision="3" gui-text="Material Thickness" gui-description="Thickness of the material">5.0</param>
|
||||
<param name="kerf" type="float" min="0.0" max="1000.0" precision="3" gui-text="Laser Cutter Kerf" gui-description="Laser Cutter Kerf (tolerance). Varies based on cutter and material thickness">0.01</param>
|
||||
<param name="linethickness" gui-text="Line Thickness" gui-description="Thickness of the cutting line on the display" type="optiongroup" appearance="combo">
|
||||
<option value="1px">1 pixel</option>
|
||||
<option value="0.002in">hairline</option>
|
||||
</param>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz Boxes/Papercraft">
|
||||
<submenu name="Finger-jointed/Tabbed Boxes" />
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">cutcraftbox.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
27
extensions/fablabchemnitz/cutcraft/cutcraftbox.py
Normal file
27
extensions/fablabchemnitz/cutcraft/cutcraftbox.py
Normal file
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import inkex
|
||||
from cutcraftshape import CutCraftShape
|
||||
import cutcraft.platforms as cp
|
||||
from cutcraft.shapes import Box
|
||||
|
||||
class CutCraftBox(CutCraftShape):
|
||||
def __init__(self):
|
||||
CutCraftShape.__init__(self)
|
||||
self.arg_parser.add_argument("--width", type=float, default=6.0, help="Box Width")
|
||||
self.arg_parser.add_argument("--depth", type=float, default=6.0, help="Box Depth")
|
||||
self.arg_parser.add_argument("--height", type=float, default=60.0, help="Box height")
|
||||
|
||||
def effect(self):
|
||||
CutCraftShape.effect(self)
|
||||
|
||||
width = self.svg.unittouu( str(self.options.width) + self.unit )
|
||||
depth = self.svg.unittouu( str(self.options.depth) + self.unit )
|
||||
height = self.svg.unittouu( str(self.options.height) + self.unit )
|
||||
|
||||
shape = Box(width, depth, height, self.thickness, self.kerf)
|
||||
|
||||
self.pack(shape)
|
||||
|
||||
if __name__ == '__main__':
|
||||
CutCraftBox().run()
|
36
extensions/fablabchemnitz/cutcraft/cutcraftcylinder.inx
Normal file
36
extensions/fablabchemnitz/cutcraft/cutcraftcylinder.inx
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Cut-Craft Cylinder</name>
|
||||
<id>fablabchemnitz.de.cutcraft.cylinder</id>
|
||||
<param name="unit" gui-text="Measurement Units" gui-description="Unit of measurement for all subsequent values entered in this dialog." type="optiongroup" appearance="combo">
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="in">in</option>
|
||||
</param>
|
||||
<separator />
|
||||
<param name="height" type="float" min="10.0" max="1000.0" precision="3" gui-text="Height" gui-description="Cylinder Height">60.0</param>
|
||||
<param name="outer" type="float" min="0.1" max="1000.0" precision="3" gui-text="Outer diameter" gui-description="Outside diameter of the Cylinder">60.0</param>
|
||||
<param name="inner" type="float" min="0.1" max="1000.0" precision="3" gui-text="Inner diameter" gui-description="Inside diameter of the Cylinder">30.0</param>
|
||||
<param name="vertices" type="int" min="3" max="180" gui-text="Number of Vertices (3..180)" gui-description="Number of vertices for the Cylinder (3 = Triangle, 4 = Square, ... 90 = Circular)">3</param>
|
||||
<param name="levels" type="int" min="2" max="100" gui-text="Number of Levels (2..100)" gui-description="Number of horizontal circular platforms">2</param>
|
||||
<param name="supports" type="int" min="3" max="18" gui-text="Number of Supports (3..18)" gui-description="Number of vertical supports holding the cylinder together">3</param>
|
||||
<param name="supwidth" type="float" min="0.1" max="1000.0" gui-text="Support Width" gui-description="Width of the vertical supports holding the cylinder together">6.0</param>
|
||||
<separator />
|
||||
<param name="thickness" type="float" min="0.1" max="1000.0" precision="3" gui-text="Material Thickness" gui-description="Thickness of the material">5.0</param>
|
||||
<param name="kerf" type="float" min="0.0" max="1000.0" precision="3" gui-text="Laser Cutter Kerf" gui-description="Laser Cutter Kerf (tolerance). Varies based on cutter and material thickness">0.01</param>
|
||||
<param name="linethickness" gui-text="Line Thickness" gui-description="Thickness of the cutting line on the display" type="optiongroup" appearance="combo">
|
||||
<option value="1px">1 pixel</option>
|
||||
<option value="0.002in">hairline</option>
|
||||
</param>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz Boxes/Papercraft">
|
||||
<submenu name="Finger-jointed/Tabbed Boxes" />
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">cutcraftcylinder.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
40
extensions/fablabchemnitz/cutcraft/cutcraftcylinder.py
Normal file
40
extensions/fablabchemnitz/cutcraft/cutcraftcylinder.py
Normal file
@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import inkex
|
||||
from cutcraftshape import CutCraftShape
|
||||
import cutcraft.platforms as cp
|
||||
from cutcraft.shapes import Cylinder
|
||||
|
||||
class CutCraftCylinder(CutCraftShape):
|
||||
def __init__(self):
|
||||
CutCraftShape.__init__(self)
|
||||
self.arg_parser.add_argument("--vertices", type=int, default=3, help="Number of vertices")
|
||||
self.arg_parser.add_argument("--levels", type=int, default=3, help="Number of levels")
|
||||
self.arg_parser.add_argument("--supports", type=int, default=3, help="Number of supports")
|
||||
self.arg_parser.add_argument("--supwidth", type=float, default=6.0, help="Support Width")
|
||||
self.arg_parser.add_argument("--height", type=float, default=60.0, help="Cylinder height")
|
||||
self.arg_parser.add_argument("--outer", type=float, default=60.0, help="Diameter of cylinder")
|
||||
self.arg_parser.add_argument("--inner", type=float, default=30.0, help="Diameter of central hole - 0.0 for no hole")
|
||||
|
||||
def effect(self):
|
||||
CutCraftShape.effect(self)
|
||||
|
||||
vertices = self.options.vertices
|
||||
levels = self.options.levels
|
||||
supports = self.options.supports
|
||||
supwidth = self.svg.unittouu( str(self.options.supwidth) + self.unit )
|
||||
height = self.svg.unittouu( str(self.options.height) + self.unit )
|
||||
outer = self.svg.unittouu( str(self.options.outer) + self.unit )
|
||||
inner = self.svg.unittouu( str(self.options.inner) + self.unit )
|
||||
|
||||
if outer<=inner:
|
||||
self._error("ERROR: Outer diameter must be greater than inner diameter.")
|
||||
exit()
|
||||
|
||||
shape = Cylinder(height, outer/2.0, inner/2.0, vertices, supports, supwidth/2.0, supwidth, levels,
|
||||
self.thickness, self.kerf)
|
||||
|
||||
self.pack(shape)
|
||||
|
||||
if __name__ == '__main__':
|
||||
CutCraftCylinder().run()
|
30
extensions/fablabchemnitz/cutcraft/cutcraftrollerbot.inx
Normal file
30
extensions/fablabchemnitz/cutcraft/cutcraftrollerbot.inx
Normal file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Cut-Craft RollerBot</name>
|
||||
<id>fablabchemnitz.de.cutcraft.rollerbot</id>
|
||||
<param name="unit" gui-text="Measurement Units" gui-description="Unit of measurement for all subsequent values entered in this dialog" type="optiongroup" appearance="combo">
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="in">in</option>
|
||||
</param>
|
||||
<separator/>
|
||||
<param name="supwidth" type="float" min="0.1" max="1000.0" gui-text="Support Width" gui-description="Width of the supports holding the robot together">12.0</param>
|
||||
<separator/>
|
||||
<param name="thickness" type="float" min="0.1" max="1000.0" precision="3" gui-text="Material Thickness" gui-description="Thickness of the material">5.0</param>
|
||||
<param name="kerf" type="float" min="0.0" max="1000.0" precision="3" gui-text="Laser Cutter Kerf" gui-description="Laser Cutter Kerf (tolerance). Varies based on cutter and material thickness">0.01</param>
|
||||
<param name="linethickness" gui-text="Line Thickness" type="optiongroup" appearance="combo">
|
||||
<option value="1px">1 pixel</option>
|
||||
<option value="0.002in">hairline</option>
|
||||
</param>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz Boxes/Papercraft">
|
||||
<submenu name="Finger-jointed/Tabbed Boxes" />
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">cutcraftrollerbot.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
37
extensions/fablabchemnitz/cutcraft/cutcraftrollerbot.py
Normal file
37
extensions/fablabchemnitz/cutcraft/cutcraftrollerbot.py
Normal file
@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import inkex
|
||||
from cutcraftshape import CutCraftShape
|
||||
import cutcraft.platforms as cp
|
||||
from cutcraft.shapes import RollerBot
|
||||
|
||||
class CutCraftRollerBot(CutCraftShape):
|
||||
def __init__(self):
|
||||
CutCraftShape.__init__(self)
|
||||
self.arg_parser.add_argument("--supwidth", type=float, default=6.0, help="Support Width")
|
||||
|
||||
def effect(self):
|
||||
CutCraftShape.effect(self)
|
||||
|
||||
supwidth = self.svg.unittouu( str(self.options.supwidth) + self.unit )
|
||||
|
||||
# Constants in the current RollerBot design.
|
||||
wheelradius = self.svg.unittouu( str(100.0) + "mm" )
|
||||
upperradius = self.svg.unittouu( str(92.0) + "mm" )
|
||||
lowerradius = self.svg.unittouu( str(82.0) + "mm" )
|
||||
facesize = self.svg.unittouu( str(50.0) + "mm" )
|
||||
barsize = self.svg.unittouu( str(25.4) + "mm" )
|
||||
scale = self.svg.unittouu( str(1.0) + "mm" )
|
||||
|
||||
primarygapwidth = self.svg.unittouu( str(70.0) + "mm" ) # Must be greater than width of Raspberry PI / Arduino.
|
||||
secondarygapwidth = self.svg.unittouu( str(25.0) + "mm" )
|
||||
width = primarygapwidth*2.0 + secondarygapwidth*2.0 + self.thickness*5.0
|
||||
|
||||
shape = RollerBot(width, supwidth, wheelradius, upperradius, lowerradius,
|
||||
facesize, barsize, primarygapwidth, secondarygapwidth, scale,
|
||||
self.thickness, self.kerf)
|
||||
|
||||
self.pack(shape)
|
||||
|
||||
if __name__ == '__main__':
|
||||
CutCraftRollerBot().run()
|
119
extensions/fablabchemnitz/cutcraft/cutcraftshape.py
Normal file
119
extensions/fablabchemnitz/cutcraft/cutcraftshape.py
Normal file
@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import gettext
|
||||
import inkex
|
||||
from math import floor
|
||||
from cutcraft.core import Point, Rectangle
|
||||
from lxml import etree
|
||||
|
||||
#TODOS
|
||||
'''
|
||||
since InkScape 1.0 / Python 3 adjustments are required to fix "TypeError: '<' not supported between instances of 'Pier' and 'Pier'". A __lt__ method has to be implemented
|
||||
"for this reasion items = sorted([(p[0].area(),p[0]) for p in shape.parts], reverse=True)" was commented out
|
||||
'''
|
||||
|
||||
class CutCraftNode(object):
|
||||
def __init__(self, rect):
|
||||
self.children = []
|
||||
self.rectangle = rect
|
||||
self.part = None
|
||||
|
||||
def insert(self, part, shape):
|
||||
if len(self.children)>0:
|
||||
node = self.children[0].insert(part, shape)
|
||||
if node is not None:
|
||||
return node
|
||||
else:
|
||||
return self.children[1].insert(part, shape)
|
||||
|
||||
if self.part is not None:
|
||||
return None
|
||||
|
||||
pwidth, pheight = part.bbox().expanded().size()
|
||||
nwidth, nheight = self.rectangle.expanded().size()
|
||||
|
||||
if pwidth>nwidth or pheight>nheight:
|
||||
# Too small.
|
||||
return None
|
||||
if pwidth==nwidth and pheight==nheight:
|
||||
# This node fits.
|
||||
self.part = part
|
||||
return self
|
||||
|
||||
nleft, ntop = self.rectangle.expanded().topleft.tup()
|
||||
nright, nbottom = self.rectangle.expanded().bottomright.tup()
|
||||
|
||||
if nwidth - pwidth > nheight - pheight:
|
||||
r1 = Rectangle(Point(nleft, ntop),
|
||||
Point(nleft+pwidth, nbottom))
|
||||
r2 = Rectangle(Point(nleft+pwidth+1.0, ntop),
|
||||
Point(nright, nbottom))
|
||||
else:
|
||||
r1 = Rectangle(Point(nleft, ntop),
|
||||
Point(nright, ntop+pheight))
|
||||
r2 = Rectangle(Point(nleft, ntop+pheight+1.0),
|
||||
Point(nright, nbottom))
|
||||
|
||||
self.children = [CutCraftNode(r1), CutCraftNode(r2)]
|
||||
|
||||
return self.children[0].insert(part, shape)
|
||||
|
||||
class CutCraftShape(inkex.EffectExtension):
|
||||
|
||||
def add_arguments(self, pars):
|
||||
pars.add_argument("--active-tab", default="Options", help="The tab selected when OK was pressed")
|
||||
pars.add_argument("--unit", default="mm", help="unit of measure for circular pitch and center diameter")
|
||||
pars.add_argument("--thickness", type=float, default=20.0, help="Material Thickness")
|
||||
pars.add_argument("--kerf", type=float, default=20.0, help="Laser Cutter Kerf")
|
||||
pars.add_argument("--linethickness", default="1px", help="Line Thickness")
|
||||
|
||||
def effect(self):
|
||||
self.unit = self.options.unit
|
||||
self.thickness = self.svg.unittouu( str(self.options.thickness) + self.unit)
|
||||
self.kerf = self.svg.unittouu( str(self.options.kerf) + self.unit)
|
||||
self.linethickness = self.svg.unittouu(self.options.linethickness)
|
||||
|
||||
svg = self.document.getroot()
|
||||
self.docwidth = self.svg.unittouu(svg.get('width'))
|
||||
self.docheight = self.svg.unittouu(svg.get('height'))
|
||||
|
||||
self.parent=self.svg.get_current_layer()
|
||||
|
||||
layer = etree.SubElement(svg, 'g')
|
||||
layer.set(inkex.addNS('label', 'inkscape'), 'newlayer')
|
||||
layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
|
||||
|
||||
def _debug(self, string):
|
||||
inkex.debug( gettext.gettext(str(string)) )
|
||||
|
||||
def _error(self, string):
|
||||
inkex.errormsg( gettext.gettext(str(string)) )
|
||||
|
||||
def pack(self, shape):
|
||||
# Pack the individual parts onto the current canvas.
|
||||
line_style = { 'stroke': '#000000',
|
||||
'stroke-width': str(self.linethickness),
|
||||
'fill': 'none' }
|
||||
|
||||
#items = sorted([(p[0].area(),p[0]) for p in shape.parts], reverse=True)
|
||||
items = [(p[0].area(),p[0]) for p in shape.parts]
|
||||
#for p in shape.parts:
|
||||
# inkex.utils.debug(p[0])
|
||||
|
||||
rootnode = CutCraftNode(Rectangle(Point(0.0, 0.0), Point(floor(self.docwidth), floor(self.docheight))))
|
||||
|
||||
for i, (_, part) in enumerate(items):
|
||||
node = rootnode.insert(part, self)
|
||||
if node is None:
|
||||
self._error("ERROR: Cannot fit parts onto canvas.\n" +
|
||||
"Try a larger canvas and then manually arrange if required.")
|
||||
exit()
|
||||
|
||||
bbox = part.bbox().expanded()
|
||||
part += -bbox.topleft
|
||||
part += node.rectangle.topleft
|
||||
|
||||
line_attribs = { 'style' : str(inkex.Style(line_style)),
|
||||
inkex.addNS('label','inkscape') : 'Test ' + str(i),
|
||||
'd' : part.svg() }
|
||||
_ = etree.SubElement(self.parent, inkex.addNS('path','svg'), line_attribs)
|
25
extensions/fablabchemnitz/cutcraft/meta.json
Normal file
25
extensions/fablabchemnitz/cutcraft/meta.json
Normal file
@ -0,0 +1,25 @@
|
||||
[
|
||||
{
|
||||
"name": "<various>",
|
||||
"id": "fablabchemnitz.de.cutcraft.<various>",
|
||||
"path": "cutcraft",
|
||||
"dependent_extensions": null,
|
||||
"original_name": "<various>",
|
||||
"original_id": "cutcraft.<various>",
|
||||
"license": "GNU GPL v3",
|
||||
"license_url": "https://github.com/m-matthews/cut-craft/blob/master/LICENSE",
|
||||
"comment": "",
|
||||
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/cutcraft",
|
||||
"fork_url": "https://github.com/m-matthews/cut-craft",
|
||||
"documentation_url": [
|
||||
"https://stadtfabrikanten.org/display/IFM/Cut-Craft+Boxes",
|
||||
"https://stadtfabrikanten.org/display/IFM/Cut-Craft+Cylinder",
|
||||
"https://stadtfabrikanten.org/display/IFM/Cut-Craft+RollerBot"
|
||||
],
|
||||
"inkscape_gallery_url": null,
|
||||
"main_authors": [
|
||||
"github.com/m-matthews",
|
||||
"github.com/eridur-de"
|
||||
]
|
||||
}
|
||||
]
|
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension
|
||||
xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Delaunay Triangulation</name>
|
||||
<id>fablabchemnitz.de.delaunay_triangulation</id>
|
||||
<param name="tab" type="notebook">
|
||||
<page name="Options" gui-text="Options">
|
||||
<param name="joggling" type="bool" gui-text="Support concavity and holes">false</param>
|
||||
<param name="furthest" type="bool" gui-text="Use furthest-site triangulation">false</param>
|
||||
<param name="elt_type" type="optiongroup" appearance="combo" gui-text="Object type to generate">
|
||||
<option value="poly">Triangles</option>
|
||||
<option value="line">Individual lines</option>
|
||||
</param>
|
||||
<spacer/>
|
||||
<separator/>
|
||||
<spacer/>
|
||||
<hbox>
|
||||
<param name="fill_type" type="optiongroup" appearance="combo" gui-text="Fill color source">
|
||||
<option value="first_sel">Same as first object selected</option>
|
||||
<option value="last_sel">Same as last object selected</option>
|
||||
<option value="random">Random</option>
|
||||
<option value="specified">Explicitly specified</option>
|
||||
</param>
|
||||
<param name="fill_color" type="color" appearance="colorbutton" gui-text=" " gui-description="Specific fill color">-1</param>
|
||||
</hbox>
|
||||
<hbox>
|
||||
<param name="stroke_type" type="optiongroup" appearance="combo" gui-text="Stroke color source">
|
||||
<option value="first_sel">Same as first object selected</option>
|
||||
<option value="last_sel">Same as last object selected</option>
|
||||
<option value="random">Random</option>
|
||||
<option value="specified">Explicitly specified</option>
|
||||
</param>
|
||||
<param name="stroke_color" type="color" appearance="colorbutton" gui-text=" " gui-description="Specific stroke color">255</param>
|
||||
</hbox>
|
||||
</page>
|
||||
<page name="Advanced" gui-text="Advanced">
|
||||
<param name="qhull" type="string" gui-text="qhull options">Qbb Qc Qz Q12</param>
|
||||
<label>If "Support concavity" is enabled on the Options tab, "QJ" will be prepended to the qhull options listed above. The default options are "Qbb Qc Qz Q12". The following website describes the available options.</label>
|
||||
<label appearance="url">http://www.qhull.org/html/qhull.htm#options</label>
|
||||
</page>
|
||||
<page name="Help" gui-text="Help">
|
||||
<label>This effect uses the Delaunay triangulation algorithm to create triangles from all of the points found in the selected objects.</label>
|
||||
</page>
|
||||
</param>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz">
|
||||
<submenu name="Shape/Pattern from existing Path(s)"/>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">delaunay_triangulation.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
@ -0,0 +1,197 @@
|
||||
#! /usr/bin/python3
|
||||
|
||||
'''
|
||||
Copyright (C) 2020 Scott Pakin, scott-ink@pakin.org
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
02110-1301, USA.
|
||||
|
||||
'''
|
||||
|
||||
import inkex
|
||||
import numpy as np
|
||||
import random
|
||||
import sys
|
||||
from inkex import Group, Line, Polygon, TextElement
|
||||
from inkex.styles import Style
|
||||
from inkex.transforms import Vector2d
|
||||
from scipy.spatial import Delaunay
|
||||
|
||||
class DelaunayTriangulation(inkex.EffectExtension):
|
||||
'Overlay selected objects with triangles.'
|
||||
|
||||
def add_arguments(self, pars):
|
||||
pars.add_argument('--tab', help='The selected UI tab when OK was pressed')
|
||||
pars.add_argument('--joggling', type=inkex.Boolean, default=False, help='Use joggled input instead of merged facets')
|
||||
pars.add_argument('--furthest', type=inkex.Boolean, default=False, help='Furthest-site Delaunay triangulation')
|
||||
pars.add_argument('--elt_type', default='poly', help='Element type to generate ("poly" or "line")')
|
||||
pars.add_argument('--qhull', help='Triangulation options to pass to qhull')
|
||||
pars.add_argument('--fill_type', help='How to fill generated polygons')
|
||||
pars.add_argument('--fill_color', type=inkex.Color, help='Fill color to use with a fill type of "specified"')
|
||||
pars.add_argument('--stroke_type', help='How to stroke generated polygons')
|
||||
pars.add_argument('--stroke_color', type=inkex.Color, help='Stroke color to use with a stroke type of "specified"')
|
||||
|
||||
def _path_points(self, elt):
|
||||
'Return a list of all points on a path (endpoints, not control points).'
|
||||
pts = set()
|
||||
first = None
|
||||
prev = Vector2d()
|
||||
for cmd in elt.path.to_absolute():
|
||||
if first is None:
|
||||
first = cmd.end_point(first, prev)
|
||||
ep = cmd.end_point(first, prev)
|
||||
pts.add((ep.x, ep.y))
|
||||
prev = ep
|
||||
return pts
|
||||
|
||||
def _create_styles(self, n):
|
||||
'Return a style to use for the generated objects.'
|
||||
# Use either the first or the last element's stroke for line caps,
|
||||
# stroke widths, etc.
|
||||
fstyle = self.svg.selection.first().style
|
||||
lstyle = self.svg.selection[-1].style
|
||||
if self.options.stroke_type == 'last_sel':
|
||||
style = Style(lstyle)
|
||||
else:
|
||||
style = Style(fstyle)
|
||||
|
||||
# Apply the specified fill color.
|
||||
if self.options.fill_type == 'first_sel':
|
||||
fcolor = fstyle.get_color('fill')
|
||||
style.set_color(fcolor, 'fill')
|
||||
elif self.options.fill_type == 'last_sel':
|
||||
fcolor = lstyle.get_color('fill')
|
||||
style.set_color(fcolor, 'fill')
|
||||
elif self.options.fill_type == 'specified':
|
||||
style.set_color(self.options.fill_color, 'fill')
|
||||
elif self.options.fill_type == 'random':
|
||||
pass # Handled below
|
||||
else:
|
||||
sys.exit(inkex.utils.errormsg(_('Internal error: Unrecognized fill type "%s".')) % self.options.fill_type)
|
||||
|
||||
# Apply the specified stroke color.
|
||||
if self.options.stroke_type == 'first_sel':
|
||||
scolor = fstyle.get_color('stroke')
|
||||
style.set_color(scolor, 'stroke')
|
||||
elif self.options.stroke_type == 'last_sel':
|
||||
scolor = lstyle.get_color('stroke')
|
||||
style.set_color(scolor, 'stroke')
|
||||
elif self.options.stroke_type == 'specified':
|
||||
style.set_color(self.options.stroke_color, 'stroke')
|
||||
elif self.options.stroke_type == 'random':
|
||||
pass # Handled below
|
||||
else:
|
||||
sys.exit(inkex.utils.errormsg(_('Internal error: Unrecognized stroke type "%s".')) % self.options.stroke_type)
|
||||
|
||||
# Produce n copies of the style.
|
||||
styles = [Style(style) for i in range(n)]
|
||||
if self.options.fill_type == 'random':
|
||||
for s in styles:
|
||||
r = random.randint(0, 255)
|
||||
g = random.randint(0, 255)
|
||||
b = random.randint(0, 255)
|
||||
s.set_color('#%02x%02x%02x' % (r, g, b), 'fill')
|
||||
s['fill-opacity'] = 255
|
||||
if self.options.stroke_type == 'random':
|
||||
for s in styles:
|
||||
r = random.randint(0, 255)
|
||||
g = random.randint(0, 255)
|
||||
b = random.randint(0, 255)
|
||||
s.set_color('#%02x%02x%02x' % (r, g, b), 'stroke')
|
||||
s['stroke-opacity'] = 255
|
||||
|
||||
# Return the list of styles.
|
||||
return [str(s) for s in styles]
|
||||
|
||||
def _create_polygons(self, triangles):
|
||||
'Render triangles as SVG polygons.'
|
||||
styles = self._create_styles(len(triangles))
|
||||
group = self.svg.get_current_layer().add(Group())
|
||||
for tri, style in zip(triangles, styles):
|
||||
tri_str = ' '.join(['%.10g %.10g' % (pt[0], pt[1]) for pt in tri])
|
||||
poly = Polygon()
|
||||
poly.set('points', tri_str)
|
||||
poly.style = style
|
||||
group.add(poly)
|
||||
|
||||
def _create_lines(self, triangles):
|
||||
'Render triangles as individual SVG lines.'
|
||||
# First, find all unique lines.
|
||||
lines = set()
|
||||
for tri in triangles:
|
||||
if len(tri) != 3:
|
||||
sys.exit(inkex.utils.errormsg(_('Internal error: Encountered a non-triangle.')))
|
||||
for i, j in [(0, 1), (0, 2), (1, 2)]:
|
||||
xy1 = tuple(tri[i])
|
||||
xy2 = tuple(tri[j])
|
||||
if xy1 < xy2:
|
||||
lines.update([(xy1, xy2)])
|
||||
else:
|
||||
lines.update([(xy2, xy1)])
|
||||
|
||||
# Then, create SVG line elements.
|
||||
styles = self._create_styles(len(lines))
|
||||
group = self.svg.get_current_layer().add(Group())
|
||||
for ([(x1, y1), (x2, y2)], style) in zip(lines, styles):
|
||||
line = Line()
|
||||
line.set('x1', x1)
|
||||
line.set('y1', y1)
|
||||
line.set('x2', x2)
|
||||
line.set('y2', y2)
|
||||
line.style = style
|
||||
group.add(line)
|
||||
|
||||
def effect(self):
|
||||
'Triangulate a set of objects.'
|
||||
|
||||
# Complain if the selection is empty.
|
||||
if len(self.svg.selection) == 0:
|
||||
return inkex.utils.errormsg(_('Please select at least one object.'))
|
||||
|
||||
# Acquire a set of all points from all selected objects.
|
||||
all_points = set()
|
||||
warned_text = False
|
||||
for obj in self.svg.selection.values():
|
||||
if isinstance(obj, TextElement) and not warned_text:
|
||||
sys.stderr.write('Warning: Text elements are not currently supported. Ignoring all text in the selection.\n')
|
||||
warned_text = True
|
||||
all_points.update(self._path_points(obj.to_path_element()))
|
||||
|
||||
# Use SciPy to perform the Delaunay triangulation.
|
||||
pts = np.array(list(all_points))
|
||||
if len(pts) == 0:
|
||||
return inkex.utils.errormsg(_('No points were found.'))
|
||||
qopts = self.options.qhull
|
||||
if self.options.joggling:
|
||||
qopts = 'QJ ' + qopts
|
||||
simplices = Delaunay(pts, furthest_site=self.options.furthest, qhull_options=qopts).simplices
|
||||
|
||||
# Create either triangles or lines, as request. Either option uses the
|
||||
# style of the first object in the selection.
|
||||
triangles = []
|
||||
for s in simplices:
|
||||
try:
|
||||
triangles.append(pts[s])
|
||||
except IndexError:
|
||||
pass
|
||||
if self.options.elt_type == 'poly':
|
||||
self._create_polygons(triangles)
|
||||
elif self.options.elt_type == 'line':
|
||||
self._create_lines(triangles)
|
||||
else:
|
||||
return inkex.utils.errormsg(_('Internal error: unexpected element type "%s".') % self.options.elt_type)
|
||||
|
||||
if __name__ == '__main__':
|
||||
DelaunayTriangulation().run()
|
21
extensions/fablabchemnitz/delaunay_triangulation/meta.json
Normal file
21
extensions/fablabchemnitz/delaunay_triangulation/meta.json
Normal file
@ -0,0 +1,21 @@
|
||||
[
|
||||
{
|
||||
"name": "Delaunay Triangulation",
|
||||
"id": "fablabchemnitz.de.delaunay_triangulation",
|
||||
"path": "delaunay_triangulation",
|
||||
"dependent_extensions": null,
|
||||
"original_name": "Delaunay Triangulation",
|
||||
"original_id": "org.pakin.filter.delaunay",
|
||||
"license": "GNU GPL v3",
|
||||
"license_url": "https://inkscape.org/~pakin/%E2%98%85delaunay-triangulation",
|
||||
"comment": "",
|
||||
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/delaunay_triangulation",
|
||||
"fork_url": "https://inkscape.org/~pakin/%E2%98%85delaunay-triangulation",
|
||||
"documentation_url": "",
|
||||
"inkscape_gallery_url": null,
|
||||
"main_authors": [
|
||||
"inkscape.org/pakin",
|
||||
"github.com/eridur-de"
|
||||
]
|
||||
}
|
||||
]
|
@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Draw Directions / Travel Moves</name>
|
||||
<id>fablabchemnitz.de.draw_directions_tavel_moves</id>
|
||||
<param name="nb_main" type="notebook">
|
||||
<page name="tab_draw" gui-text="Draw">
|
||||
<param name="order" type="optiongroup" appearance="combo" gui-text="Order">
|
||||
<option value="separate_groups">To separate groups</option>
|
||||
<option value="separate_layers">To separate layers</option>
|
||||
<option value="element_index">At element's index</option>
|
||||
</param>
|
||||
<separator/>
|
||||
<param name="draw_dots" type="bool" gui-text="Draw dots" gui-description="Start and end point of opened (red + blue) and closed paths (green + yellow)">true</param>
|
||||
<param name="dotsize" type="int" min="1" max="9999" gui-text="Dot size (px)">10</param>
|
||||
<separator/>
|
||||
<param name="draw_travel_moves" type="bool" gui-text="Draw travel moves">false</param>
|
||||
<param name="ignore_colors" type="bool" gui-text="Ignore stroke colors" gui-description="If enabled we connect all lines by order, ignoring the stroke color (normally we have one travel line group per color)">false</param>
|
||||
<param name="dashed_style" type="bool" gui-text="Dashed style">true</param>
|
||||
<param name="arrow_style" type="bool" gui-text="Arrow style">true</param>
|
||||
<param name="arrow_size" type="float" min="0.1" max="10.0" precision="2" gui-text="Arrow size (scale)">1.0</param>
|
||||
<separator/>
|
||||
<param name="debug" type="bool" gui-text="Print debug info">false</param>
|
||||
</page>
|
||||
<page name="tab_remove" gui-text="Remove">
|
||||
<label>If this page is selected, you can remove all old travel lines/groups Just press 'apply'.</label>
|
||||
</page>
|
||||
<page name="tab_about" gui-text="About">
|
||||
<label appearance="header">Draw Directions / Travel Moves</label>
|
||||
<label>Draw travel lines and/or dots to mark start and end points of paths and to show the line order.</label>
|
||||
<label>2021 - 2022 / written by Mario Voigt (Stadtfabrikanten e.V. / FabLab Chemnitz)</label>
|
||||
<spacer/>
|
||||
<label appearance="header">Online Documentation</label>
|
||||
<label appearance="url">https://y.stadtfabrikanten.org/drawdirectionstravelmoves</label>
|
||||
<spacer/>
|
||||
<label appearance="header">Contributing</label>
|
||||
<label appearance="url">https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.X</label>
|
||||
<label appearance="url">mailto:mario.voigt@stadtfabrikanten.org</label>
|
||||
<spacer/>
|
||||
<label appearance="header">MightyScape Extension Collection</label>
|
||||
<label>This piece of software is part of the MightyScape for Inkscape Extension Collection and is licensed under GNU GPL v3</label>
|
||||
<label appearance="url">https://y.stadtfabrikanten.org/mightyscape-overview</label>
|
||||
</page>
|
||||
<page name="tab_donate" gui-text="Donate">
|
||||
<label appearance="header">Coffee + Pizza</label>
|
||||
<label>We are the Stadtfabrikanten, running the FabLab Chemnitz since 2016. A FabLab is an open workshop that gives people access to machines and digital tools like 3D printers, laser cutters and CNC milling machines.</label>
|
||||
<spacer/>
|
||||
<label>You like our work and want to support us? You can donate to our non-profit organization by different ways:</label>
|
||||
<label appearance="url">https://y.stadtfabrikanten.org/donate</label>
|
||||
<spacer/>
|
||||
<label>Thanks for using our extension and helping us!</label>
|
||||
<image>../000_about_fablabchemnitz.svg</image>
|
||||
</page>
|
||||
</param>
|
||||
<effect>
|
||||
<object-type>path</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz">
|
||||
<submenu name="Shape/Pattern from existing Path(s)"/>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">draw_directions_tavel_moves.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
@ -0,0 +1,306 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from lxml import etree
|
||||
import itertools
|
||||
import inkex
|
||||
from inkex import Circle, Vector2d
|
||||
from inkex.paths import Path
|
||||
from inkex.bezier import csplength
|
||||
from debugpy.common.timestamp import current
|
||||
|
||||
'''
|
||||
ToDos
|
||||
- draw numbers for each travel lines next to the line
|
||||
'''
|
||||
|
||||
class DrawDirectionsTravelMoves(inkex.EffectExtension):
|
||||
|
||||
|
||||
def drawCircle(self, group, color, point):
|
||||
style = inkex.Style({'stroke': 'none', 'fill': color})
|
||||
startCircle = group.add(Circle(cx=str(point[0]), cy=str(point[1]), r=str(self.svg.unittouu(str(so.dotsize/2) + "px"))))
|
||||
startCircle.style = style
|
||||
|
||||
|
||||
def find_group(self, groupId):
|
||||
''' check if a group with a given id exists or not. Returns None if not found, else returns the group element '''
|
||||
groups = self.document.xpath('//svg:g', namespaces=inkex.NSS)
|
||||
for group in groups:
|
||||
#inkex.utils.debug(str(layer.get('inkscape:label')) + " == " + layerName)
|
||||
if group.get('id') == groupId:
|
||||
return group
|
||||
return None
|
||||
|
||||
|
||||
def createTravelMarker(self, markerId):
|
||||
#add new marker to defs (or overwrite if existent)
|
||||
defs = self.svg.defs
|
||||
for defi in defs:
|
||||
if defi.tag == "{http://www.w3.org/2000/svg}marker" and defi.get('id') == markerId: #delete
|
||||
defi.delete()
|
||||
marker = inkex.Marker(id=markerId)
|
||||
marker.set("inkscape:isstock", "true")
|
||||
marker.set("inkscape:stockid", markerId)
|
||||
marker.set("orient", "auto")
|
||||
marker.set("refY", "0.0")
|
||||
marker.set("refX", "0.0")
|
||||
marker.set("style", "overflow:visible;")
|
||||
|
||||
markerPath = inkex.PathElement(id=self.svg.get_unique_id('markerId-'))
|
||||
markerPath.style = {"fill-rule": "evenodd", "fill": "context-stroke", "stroke-width": str(self.svg.unittouu('1px'))}
|
||||
markerPath.transform = "scale(1.0,1.0) rotate(0) translate(-6.0,0)"
|
||||
arrowHeight = 6.0
|
||||
markerPath.attrib["transform"] = "scale({},{}) rotate(0) translate(-{},0)".format(so.arrow_size, so.arrow_size, arrowHeight)
|
||||
markerPath.path = "M {},0.0 L -3.0,5.0 L -3.0,-5.0 L {},0.0 z".format(arrowHeight, arrowHeight)
|
||||
|
||||
marker.append(markerPath)
|
||||
defs.append(marker) #do not append before letting contain it one path. Otherwise Inkscape is going to crash immediately
|
||||
|
||||
|
||||
def add_arguments(self, pars):
|
||||
pars.add_argument("--nb_main")
|
||||
pars.add_argument("--order", default="separate_groups")
|
||||
pars.add_argument("--draw_dots", type=inkex.Boolean, default=True, help="Start and end point of opened (red + blue) and closed paths (green + yellow)")
|
||||
pars.add_argument("--dotsize", type=int, default=10, help="Dot size (px)")
|
||||
pars.add_argument("--draw_travel_moves", type=inkex.Boolean, default=False)
|
||||
pars.add_argument("--ignore_colors", type=inkex.Boolean, default=False, help="If enabled we connect all lines by order, ignoring the stroke color (normally we have one travel line group per color)")
|
||||
pars.add_argument("--dashed_style", type=inkex.Boolean, default=True)
|
||||
pars.add_argument("--arrow_style", type=inkex.Boolean, default=True)
|
||||
pars.add_argument("--arrow_size", type=float, default=True)
|
||||
pars.add_argument("--debug", type=inkex.Boolean, default=False)
|
||||
|
||||
def effect(self):
|
||||
global so
|
||||
so = self.options
|
||||
dotPrefix = "dots-"
|
||||
lineSuffix = "-travelLine"
|
||||
groupPrefix = "travelLines-"
|
||||
ignoreWord = "ignore"
|
||||
|
||||
if so.nb_main == "tab_remove":
|
||||
#remove dots
|
||||
dots = self.document.xpath("//svg:g[starts-with(@id, '" + dotPrefix + "')]", namespaces=inkex.NSS)
|
||||
for dot in dots:
|
||||
dot.delete()
|
||||
|
||||
#remove travel lines
|
||||
travelLines = self.document.xpath("//svg:path[contains(@id, '" + lineSuffix + "')]", namespaces=inkex.NSS)
|
||||
for travelLine in travelLines:
|
||||
travelLine.delete()
|
||||
|
||||
#remove travel groups/layers
|
||||
travelGroups = self.document.xpath("//svg:g[starts-with(@id, '" + groupPrefix + "')]", namespaces=inkex.NSS)
|
||||
for travelGroup in travelGroups:
|
||||
if len(travelGroup) == 0:
|
||||
travelGroup.delete()
|
||||
return
|
||||
|
||||
#else ...
|
||||
|
||||
selectedPaths = [] #total list of elements to parse
|
||||
|
||||
def parseChildren(element):
|
||||
if isinstance(element, inkex.PathElement) and element not in selectedPaths and lineSuffix not in element.get('id') and not isinstance(element.getparent(), inkex.Marker):
|
||||
selectedPaths.append(element)
|
||||
children = element.getchildren()
|
||||
if children is not None:
|
||||
for child in children:
|
||||
if isinstance(child, inkex.PathElement) and child not in selectedPaths and lineSuffix not in element.get('id') and not isinstance(element.getparent(), inkex.Marker):
|
||||
selectedPaths.append(child)
|
||||
parseChildren(child) #go deeper and deeper
|
||||
|
||||
#check if we have selectedPaths elements or if we should parse the whole document instead
|
||||
if len(self.svg.selected) == 0:
|
||||
for element in self.document.getroot().iter(tag=etree.Element):
|
||||
if isinstance(element, inkex.PathElement) and element != self.document.getroot() and lineSuffix not in element.get('id') and not isinstance(element.getparent(), inkex.Marker):
|
||||
selectedPaths.append(element)
|
||||
else:
|
||||
for element in self.svg.selected.values():
|
||||
parseChildren(element)
|
||||
|
||||
dotGroup = self.svg.add(inkex.Group(id=self.svg.get_unique_id(dotPrefix)))
|
||||
if so.order == "separate_layers":
|
||||
dotGroup.set("inkscape:groupmode", "layer")
|
||||
dotGroup.set("inkscape:label", dotPrefix + "layer")
|
||||
if so.order == "separate_groups":
|
||||
dotGroup.pop("inkscape:groupmode")
|
||||
dotGroup.pop("inkscape:label")
|
||||
if dotGroup.style.get('display') is not None:
|
||||
dotGroup.style.pop("display") #if the group previously has been a layer (which was hidden), a display:none will be added. we don't want that
|
||||
|
||||
if so.arrow_style is True:
|
||||
markerId = "travel_move_arrow"
|
||||
self.createTravelMarker(markerId)
|
||||
|
||||
#collect all different stroke colors to distinguish by groups
|
||||
strokeColors = []
|
||||
strokeColorsAndCounts = {}
|
||||
'''
|
||||
the container for all paths we will loop on. Index:
|
||||
0 = element
|
||||
1 = start point
|
||||
2 = end point
|
||||
'''
|
||||
startEndPath = []
|
||||
|
||||
for element in selectedPaths:
|
||||
strokeColor = element.style.get('stroke')
|
||||
if strokeColor is None or strokeColor == "none":
|
||||
strokeColor = "none"
|
||||
if so.ignore_colors is True:
|
||||
strokeColor = ignoreWord
|
||||
strokeColors.append(strokeColor)
|
||||
groupName = groupPrefix + strokeColor
|
||||
travelGroup = self.find_group(groupName)
|
||||
if travelGroup is None:
|
||||
travelGroup = inkex.Group(id=groupName)
|
||||
self.document.getroot().append(travelGroup)
|
||||
else: #exists
|
||||
self.document.getroot().append(travelGroup)
|
||||
for child in travelGroup:
|
||||
child.delete()
|
||||
if so.order == "separate_layers":
|
||||
travelGroup.set("inkscape:groupmode", "layer")
|
||||
travelGroup.set("inkscape:label", groupName + "-layer")
|
||||
if so.order == "separate_groups":
|
||||
travelGroup.pop("inkscape:groupmode")
|
||||
travelGroup.pop("inkscape:label")
|
||||
if travelGroup.style.get('display') is not None:
|
||||
travelGroup.style.pop("display")
|
||||
|
||||
|
||||
p = element.path.transform(element.composed_transform())
|
||||
points = list(p.end_points)
|
||||
start = points[0]
|
||||
end = points[len(points) - 1]
|
||||
|
||||
startEndPath.append([element, start, end])
|
||||
|
||||
if so.draw_dots is True:
|
||||
if start[0] == end[0] and start[1] == end[1]:
|
||||
self.drawCircle(dotGroup, '#00FF00', start)
|
||||
self.drawCircle(dotGroup, '#FFFF00', points[1]) #draw one point which gives direction of the path
|
||||
else: #open contour with start and end point
|
||||
self.drawCircle(dotGroup, '#FF0000', start)
|
||||
self.drawCircle( dotGroup, '#0000FF', end)
|
||||
|
||||
for strokeColor in strokeColors:
|
||||
if strokeColor in strokeColorsAndCounts:
|
||||
strokeColorsAndCounts[strokeColor] = strokeColorsAndCounts[strokeColor] + 1
|
||||
else:
|
||||
strokeColorsAndCounts[strokeColor] = 1
|
||||
|
||||
|
||||
if so.draw_travel_moves is True:
|
||||
for sc in strokeColorsAndCounts: #loop through all unique stroke colors
|
||||
colorCount = strokeColorsAndCounts[sc] #the total color count per stroke color
|
||||
ran = len(startEndPath) + 1 #one more because the last travel moves goes back to 0,0 (so for 3 elements (1,2,3) the range is 0 to 3 -> makes 4)
|
||||
firstOccurence = True
|
||||
createdMoves = 0
|
||||
for i in range(0, ran): #loop through the item selection. nested loop. so we loop over alle elements again for each color
|
||||
if i < ran - 1:
|
||||
elementStroke = startEndPath[i][0].style.get('stroke')
|
||||
currentElement = startEndPath[i][0]
|
||||
idx = currentElement.getparent().index(currentElement)
|
||||
travelLineId = currentElement.get('id') + lineSuffix + ("-begin" if firstOccurence is True else "")
|
||||
if i == ran or createdMoves == colorCount:
|
||||
elementStroke = startEndPath[i-1][0].style.get('stroke')
|
||||
currentElement = startEndPath[i-1][0]
|
||||
idx = currentElement.getparent().index(currentElement) + 1
|
||||
travelLineId = currentElement.get('id') + lineSuffix + "-end"
|
||||
|
||||
if i < ran - 2:
|
||||
nextElement = startEndPath[i+1][0]
|
||||
elif i < ran - 1:
|
||||
nextElement = startEndPath[i][0]
|
||||
else:
|
||||
nextElement = None
|
||||
|
||||
if elementStroke is None or elementStroke == "none":
|
||||
elementStroke = "none"
|
||||
|
||||
if so.debug is True: inkex.utils.debug("current stroke color {}, element stroke color{}".format(sc, elementStroke))
|
||||
if sc == elementStroke or sc == ignoreWord:
|
||||
if firstOccurence is True:
|
||||
travelStart = Vector2d(0,0)
|
||||
firstOccurence = False
|
||||
else:
|
||||
if i <= ran - 1:
|
||||
travelStart = startEndPath[i-1][2] #end point from this path
|
||||
|
||||
if so.debug is True: inkex.utils.debug("travelStart={}".format(travelStart))
|
||||
if i < ran - 1:
|
||||
travelEnd = startEndPath[i][1]
|
||||
if createdMoves == colorCount:
|
||||
travelEnd = Vector2d(0,0)
|
||||
|
||||
if so.debug is True: inkex.utils.debug("travelEnd={}".format(travelEnd))
|
||||
|
||||
if so.debug is True:
|
||||
if i < ran - 1:
|
||||
inkex.utils.debug("segment={},{}".format(startEndPath[i][2], startEndPath[i][1]))
|
||||
if i == ran - 1:
|
||||
inkex.utils.debug("segment={},{}".format(startEndPath[i-1][1], travelEnd))
|
||||
|
||||
travelLine = inkex.PathElement(id=travelLineId)
|
||||
#if some objects are at svg:svg level this may cause errors
|
||||
#if element.getparent() != self.document.getroot():
|
||||
# travelLine.transform = element.getparent().composed_transform()
|
||||
travelLine.style = {'stroke': ("#000000" if so.ignore_colors is True else sc), 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px')), 'marker-end': 'url(#marker1426)'}
|
||||
if so.dashed_style is True:
|
||||
travelLine.style['stroke-dasharray'] = '1,1'
|
||||
travelLine.style['stroke-dashoffset'] = '0'
|
||||
if so.arrow_style is True:
|
||||
travelLine.style["marker-end"] = "url(#{})".format(markerId)
|
||||
|
||||
#this is a really dirty block of code
|
||||
if so.order == "element_index":
|
||||
#adding the lines at element index requires to apply transformations for start point and end point (in case they are in different groups)
|
||||
pseudo1 = inkex.PathElement()
|
||||
pseudo1.set('d', "M{:0.6f},{:0.6f}".format(travelStart[0],travelStart[1]))
|
||||
pseudo2 = inkex.PathElement()
|
||||
pseudo2.set('d', "M{:0.6f},{:0.6f}".format(travelEnd[0],travelEnd[1]))
|
||||
if nextElement is not None:
|
||||
if currentElement.getparent() == nextElement.getparent():
|
||||
pseudo1.path = pseudo1.path.transform(-currentElement.composed_transform()).to_superpath()
|
||||
pseudo2.path = pseudo2.path.transform(-nextElement.composed_transform()).to_superpath()
|
||||
else:
|
||||
pseudo1.path = pseudo1.path.transform(-currentElement.composed_transform()).to_superpath()
|
||||
pseudo2.path = pseudo2.path.transform(-currentElement.composed_transform()).to_superpath()
|
||||
else:
|
||||
pseudo1.path = pseudo1.path.transform(-currentElement.composed_transform()).to_superpath()
|
||||
pseudo2.path = pseudo2.path.transform(-currentElement.composed_transform()).to_superpath()
|
||||
travelLine.path = pseudo1.path + pseudo2.get('d').replace("M", "L")
|
||||
if so.debug is True: self.msg("travelLine={}".format(travelLine.path))
|
||||
|
||||
#check the length. if zero we do not add
|
||||
slengths, stotal = csplength(travelLine.path.transform(currentElement.composed_transform()).to_superpath()) #get segment lengths and total length of path in document's internal unit
|
||||
if stotal > 0:
|
||||
#finally add the line
|
||||
currentElement.getparent().insert(idx, travelLine)
|
||||
else:
|
||||
if so.debug is True: inkex.utils.debug("Line has length of zero")
|
||||
else:
|
||||
travelLine.set('d', "M{:0.6f},{:0.6f} L{:0.6f},{:0.6f}".format(travelStart[0],travelStart[1],travelEnd[0],travelEnd[1]))
|
||||
#check the length. if zero we do not add
|
||||
slengths, stotal = csplength(travelLine.path.transform(currentElement.composed_transform()).to_superpath()) #get segment lengths and total length of path in document's internal unit
|
||||
if stotal > 0:
|
||||
#finally add the line
|
||||
self.find_group(groupPrefix + sc).add(travelLine)
|
||||
else:
|
||||
if so.debug is True: inkex.utils.debug("Line has length of zero")
|
||||
|
||||
createdMoves += 1 #each time we created a move we count up. we want to compare against the total count of that color
|
||||
if so.debug is True: inkex.utils.debug("createdMoves={}".format(createdMoves))
|
||||
if so.debug is True: inkex.utils.debug("-"*40)
|
||||
|
||||
#cleanup empty groups
|
||||
if len(dotGroup) == 0:
|
||||
dotGroup.delete()
|
||||
travelGroups = self.document.xpath("//svg:g[starts-with(@id, 'travelLines-')]", namespaces=inkex.NSS)
|
||||
for travelGroup in travelGroups:
|
||||
if len(travelGroup) == 0:
|
||||
travelGroup.delete()
|
||||
|
||||
if __name__ == '__main__':
|
||||
DrawDirectionsTravelMoves().run()
|
@ -0,0 +1,20 @@
|
||||
[
|
||||
{
|
||||
"name": "Draw Directions / Travel Moves",
|
||||
"id": "fablabchemnitz.de.draw_directions_travel_moces",
|
||||
"path": "draw_directions",
|
||||
"dependent_extensions": null,
|
||||
"original_name": "Draw Directions / Travel Moves",
|
||||
"original_id": "fablabchemnitz.de.draw_directions_travel_moces",
|
||||
"license": "GNU GPL v3",
|
||||
"license_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/LICENSE",
|
||||
"comment": "Written by Mario Voigt",
|
||||
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/draw_directions",
|
||||
"fork_url": null,
|
||||
"documentation_url": "https://stadtfabrikanten.org/pages/viewpage.action?pageId=120524682",
|
||||
"inkscape_gallery_url": null,
|
||||
"main_authors": [
|
||||
"github.com/eridur-de"
|
||||
]
|
||||
}
|
||||
]
|
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Exponential Distort</name>
|
||||
<id>fablabchemnitz.de.exponential_distort</id>
|
||||
<param name="exponent" type="float" min="0.01" max="100" precision="2" gui-text="Exponent:">1.33</param>
|
||||
<param name="padding_perc" type="float" min="0" max="500" precision="0" gui-text="Padding [%]:">0</param>
|
||||
<label>Apply padding to soften the effect. Without padding the exponential curve runs from 0 to 1. With e.g. 100% padding it runs from 0.5 to 1.</label>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz">
|
||||
<submenu name="Transformations" />
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">exponential_distort.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
import math
|
||||
import inkex
|
||||
from inkex.paths import CubicSuperPath
|
||||
|
||||
class ExponentialDistort(inkex.EffectExtension):
|
||||
|
||||
def add_arguments(self, pars):
|
||||
#pars.add_argument('-a', '--axis', default='x', help='distortion axis. Valid values are "x", "y", or "xy". Default is "x"')
|
||||
pars.add_argument('-x', '--exponent', type=float, default=1.3, help='distortion factor. 1=no distortion, default 1.3')
|
||||
pars.add_argument('-p', '--padding_perc', type=float, default=0, help='pad at origin. Padding 100% runs the exponential curve through [0.5 .. 1.0] -- default 0% runs through [0.0 .. 1.0]')
|
||||
|
||||
def x_exp(self, bbox, x):
|
||||
""" reference implementation ignoring padding. unused. """
|
||||
xmin = bbox[0] # maps to 0
|
||||
xmax = bbox[1] # maps to 1
|
||||
w = xmax-xmin # maps to 1
|
||||
# convert world to math coordinates
|
||||
xm = (x-xmin)/w
|
||||
# apply function with properties f(1.0) == 1.0 and f(0.0) == 0.0
|
||||
xm = xm**self.options.exponent # oh, parabola or logarithm?
|
||||
# convert back from math to world coordinates.
|
||||
return x*w + xmin
|
||||
|
||||
def x_exp_p(self, bbox, x):
|
||||
""" parabola mapping with padding
|
||||
CAUTION: the properties f(1.0) == 1.0 and f(0.0) == 0.0
|
||||
do not really hold, as our x does not run the full range [0.0 .. 1.0]
|
||||
FIXME: if you expect some c**xm here, instead of xm**c, think about c==1 ...
|
||||
"""
|
||||
xmin = bbox[0] # maps to 0 when padding=0,
|
||||
xmax = bbox[1] # maps to 1
|
||||
xzero = xmin - (xmax-xmin)*self.options.padding_perc*0.01 # maps to 0, after applying padding
|
||||
w = xmax - xzero
|
||||
w = w * (1+self.options.padding_perc*0.01)
|
||||
# convert world to math coordinates
|
||||
xm = (x-xzero)/w
|
||||
# apply function with properties f(1.0) == 1.0 and f(0.0) == 0.0
|
||||
xm = xm**self.options.exponent # oh, parabola or logarithm?
|
||||
return xm
|
||||
|
||||
def x_exp_p_inplace(self, bbox, xm):
|
||||
""" back from mat to world coordinates, retaining xmin and xmax
|
||||
|
||||
Algorithm: (pre)compute a linear mapping function by explicitly
|
||||
running x_exp_p for the two points xmin and xmax.
|
||||
Then use the resulting linear function to map back any xm into world coordinates x.
|
||||
|
||||
An obvious speedup by factor 3 is waiting for you here.
|
||||
"""
|
||||
|
||||
xmin = bbox[0]
|
||||
xmax = bbox[1]
|
||||
## assert that xmin maps to xmin and xmax maps to xmax, whatever x_exp_p() does to us.
|
||||
f_xmin = self.x_exp_p(bbox, xmin)
|
||||
f_xmax = self.x_exp_p(bbox, xmax)
|
||||
f_x = self.x_exp_p(bbox, xm)
|
||||
x = (f_x - f_xmin) * (xmax-xmin) / (f_xmax-f_xmin) + xmin
|
||||
return x
|
||||
|
||||
def computeBBox(self, pts):
|
||||
""" 'improved' version of simplepath.computeBBox, this one includes b-spline handles."""
|
||||
xmin = None
|
||||
xmax = None
|
||||
ymin = None
|
||||
ymax = None
|
||||
for p in pts:
|
||||
for pp in p:
|
||||
for ppp in pp:
|
||||
if xmin is None: xmin = ppp[0]
|
||||
if xmax is None: xmax = ppp[0]
|
||||
if ymin is None: ymin = ppp[1]
|
||||
if ymax is None: ymax = ppp[1]
|
||||
|
||||
if xmin > ppp[0]: xmin = ppp[0]
|
||||
if xmax < ppp[0]: xmax = ppp[0]
|
||||
if ymin > ppp[1]: ymin = ppp[1]
|
||||
if ymax < ppp[1]: ymax = ppp[1]
|
||||
return (xmin, xmax, ymin, ymax)
|
||||
|
||||
def effect(self):
|
||||
|
||||
if len(self.svg.selected) == 0:
|
||||
inkex.errormsg("Please select an object to perform the " +
|
||||
"exponential-distort transformation on.")
|
||||
return
|
||||
|
||||
for id, node in self.svg.selected.items():
|
||||
type = node.get("{http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}type", "path")
|
||||
if node.tag != '{http://www.w3.org/2000/svg}path' or type != 'path':
|
||||
inkex.errormsg(node.tag + " is not a path. Type="+type+". Please use 'Path->Object to Path' first.")
|
||||
else:
|
||||
pts = CubicSuperPath(node.get('d'))
|
||||
bbox = self.computeBBox(pts)
|
||||
## bbox (60.0, 160.0, 77.0, 197.0)
|
||||
## pts [[[[60.0, 77.0], [60.0, 77.0], [60.0, 77.0]], [[60.0, 197.0], [60.0, 197.0], [60.0, 197.0]], [[70.0, 197.0], ...
|
||||
for p in pts:
|
||||
for pp in p:
|
||||
for ppp in pp:
|
||||
ppp[0] = self.x_exp_p_inplace(bbox, ppp[0])
|
||||
|
||||
node.set('d', str(pts))
|
||||
|
||||
if __name__ == '__main__':
|
||||
ExponentialDistort().run()
|
21
extensions/fablabchemnitz/exponential_distort/meta.json
Normal file
21
extensions/fablabchemnitz/exponential_distort/meta.json
Normal file
@ -0,0 +1,21 @@
|
||||
[
|
||||
{
|
||||
"name": "Exponential Distort",
|
||||
"id": "fablabchemnitz.de.exponential_distort",
|
||||
"path": "exponential_distort",
|
||||
"dependent_extensions": null,
|
||||
"original_name": "Exponential Distort",
|
||||
"original_id": "com.github.jnweiger.inkscape.exponential.distort",
|
||||
"license": "MIT License",
|
||||
"license_url": "https://github.com/jnweiger/inkscape-exponential-distort/blob/master/LICENSE",
|
||||
"comment": "ported to Inkscape v1 by Mario Voigt",
|
||||
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/exponential_distort",
|
||||
"fork_url": "https://github.com/jnweiger/inkscape-exponential-distort",
|
||||
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Exponential+Distort",
|
||||
"inkscape_gallery_url": null,
|
||||
"main_authors": [
|
||||
"github.com/jnweiger",
|
||||
"github.com/eridur-de"
|
||||
]
|
||||
}
|
||||
]
|
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Fill Rectangle With Circles</name>
|
||||
<id>fablabchemnitz.fill_rectangle_with_circles</id>
|
||||
<param name="radius" type="float" min="0.01" max="10000" gui-text="Radius to enter">3.0</param>
|
||||
<param name="margin" type="float" min="0.01" max="10000" gui-text="Margin between the edge of the rectangles and the circles">10.0</param>
|
||||
<param name="space" type="float" min="0.01" max="10000" gui-text="Spacing between circles">30.0</param>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz">
|
||||
<submenu name="Shape/Pattern from existing Object(s)"/>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">fill_rectangle_with_circles.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Program allowing the addition of small grey dots in rectangles created using Inkscape.
|
||||
|
||||
# Thomas Guzik, thomas.guzik@laposte.net
|
||||
# Leo 130 contact@avilab.fr
|
||||
# Corentin Bettiol - corentin-bettiol@hotmail.fr
|
||||
|
||||
# -Creative Commons License
|
||||
# -This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
|
||||
# -http://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
import inkex
|
||||
from lxml import etree
|
||||
|
||||
def recup(selection, attrib):
|
||||
l = []
|
||||
for i in selection:
|
||||
selec = i
|
||||
valr = selec.get(attrib)
|
||||
l.append(valr)
|
||||
return l
|
||||
|
||||
def generCircle(y, x, r):
|
||||
circle = etree.Element('{http://www.w3.org/2000/svg}circle')
|
||||
circle.set('cy',str(y))
|
||||
circle.set('cx',str(x))
|
||||
circle.set('r',str(r))
|
||||
circle.set('fill','#000000')
|
||||
circle.set('stroke','#000000')
|
||||
circle.set('stroke-width','0')
|
||||
return circle
|
||||
|
||||
def toFloat(l):
|
||||
for i in range(len(l)):
|
||||
l[i] = float(l[i])
|
||||
return l
|
||||
|
||||
class FillRectangleWithCircle(inkex.EffectExtension):
|
||||
|
||||
def add_arguments(self, pars):
|
||||
pars.add_argument('--radius', type = float, default = 3.0, help = 'Radius to enter')
|
||||
pars.add_argument('--margin', type = float, default = 10.0, help = 'Margin between the edge of the rectangles and the circles')
|
||||
pars.add_argument('--space', type = float, default = 30.0, help = 'Spacing between circles')
|
||||
|
||||
def effect(self):
|
||||
# svg = self.svg.document.getroot()
|
||||
# layer = etree.SubElement(svg, 'g')
|
||||
# layer.set(inkex.addNS('label', 'inkscape'), 'Layer')
|
||||
# layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
|
||||
# Should we add the circles on a different layer sheet?
|
||||
|
||||
radius = self.options.radius
|
||||
margin = self.options.margin
|
||||
space = self.options.space
|
||||
|
||||
if str(list(self.svg.selected.values())[0]) == 'rect':
|
||||
selection = (self.svg.selected).values()
|
||||
|
||||
y,x,height,width = [], [], [], []
|
||||
|
||||
if (len(selection))>0:
|
||||
y = toFloat(recup(selection,'y'))
|
||||
x = toFloat(recup(selection,'x'))
|
||||
height = toFloat(recup(selection,'height'))
|
||||
width = toFloat(recup(selection,'width'))
|
||||
|
||||
for i in range(len(selection)):
|
||||
xC = x[i] + margin
|
||||
yC = y[i] + margin
|
||||
|
||||
while xC < (x[i] + width[i] - margin):
|
||||
while yC < (y[i] + height[i] - margin):
|
||||
self.svg.get_current_layer().append(generCircle(yC,xC,radius))
|
||||
yC += (space + radius)
|
||||
|
||||
xC += space + radius
|
||||
yC = y[i] + margin
|
||||
else:
|
||||
inkex.utils.debug("No rectangle(s) have been selected.")
|
||||
|
||||
if __name__ == '__main__':
|
||||
FillRectangleWithCircle().run()
|
@ -0,0 +1,21 @@
|
||||
[
|
||||
{
|
||||
"name": "Fill Rectangle With Circles",
|
||||
"id": "fablabchemnitz.de.fill_rectangle_with_circles",
|
||||
"path": "fill_rectangle_with_circles",
|
||||
"dependent_extensions": null,
|
||||
"original_name": "Remplir de cercles",
|
||||
"original_id": "org.ekips.filter.fill_circle",
|
||||
"license": "GNU GPL v3",
|
||||
"license_url": "https://github.com/thomas-guzik/inkscape-extension-fillsquarewithcircles/blob/master/LICENSE",
|
||||
"comment": "",
|
||||
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/fill_rectangle_with_circles",
|
||||
"fork_url": "https://github.com/thomas-guzik/inkscape-extension-fillsquarewithcircles",
|
||||
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Fill+Rectangle+With+Circles",
|
||||
"inkscape_gallery_url": null,
|
||||
"main_authors": [
|
||||
"github.com/thomas-guzik",
|
||||
"github.com/eridur-de"
|
||||
]
|
||||
}
|
||||
]
|
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Fillet And Chamfer (Replaced by LPE)</name>
|
||||
<id>fablabchemnitz.de.fillet_and_chamfer</id>
|
||||
<param name="fillet_type" type="optiongroup" appearance="combo" gui-text="Fillet or Chamfer:">
|
||||
<option value="fillet">Fillet</option>
|
||||
<option value="chamfer">Chamfer</option>
|
||||
</param>
|
||||
<param name="radius" type="float" min="0.0" max="1000.0" gui-text="Radius:">5.0</param>
|
||||
<param name="unit" gui-text="Unit" type="optiongroup" appearance="combo">
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="in">in</option>
|
||||
<option value="px">px</option>
|
||||
</param>
|
||||
<param name="remove" type="bool" gui-text="Remove control object">false</param>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz">
|
||||
<submenu name="Modify existing Path(s)"/>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">fillet_and_chamfer.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
@ -0,0 +1,293 @@
|
||||
#!/usr/bin/env python3
|
||||
'''
|
||||
Copyright (C) 2018 Tao Wei taowei@buffalo.edu
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
'''
|
||||
import inkex
|
||||
import math
|
||||
import svgpathtools
|
||||
from lxml import etree
|
||||
|
||||
KAPPA = 4/3. * (math.sqrt(2)-1)
|
||||
|
||||
def cround(cnumber, ndigits):
|
||||
return round(cnumber.real, ndigits) + round(cnumber.imag, ndigits)*1j
|
||||
|
||||
def round_seg(seg, ndigits):
|
||||
seg.start = cround(seg.start, ndigits)
|
||||
seg.end = cround(seg.end, ndigits)
|
||||
return seg
|
||||
|
||||
def round_path(p, ndigits=6):
|
||||
"""fix for precision issue"""
|
||||
for seg in p:
|
||||
round_seg(seg, ndigits)
|
||||
return p
|
||||
|
||||
def remove_zero_length_segments(p, eps=1e-6):
|
||||
"z will add a zero length line segment"
|
||||
return svgpathtools.Path(*filter(lambda seg: seg.length() > eps, p))
|
||||
|
||||
def iscontinuous(p):
|
||||
for seg1, seg2 in zip(p[:-1], p[1:]):
|
||||
if abs(seg1.end-seg2.start) >= 1e-6:
|
||||
return False
|
||||
return True
|
||||
|
||||
def isclosedac(p):
|
||||
return abs(p.start-p.end) < 1e-6
|
||||
|
||||
def isclosed(p):
|
||||
assert iscontinuous(p)
|
||||
return isclosedac(p)
|
||||
|
||||
from svgpathtools.path import Line, CubicBezier, QuadraticBezier, Arc
|
||||
|
||||
def d_str(self, useSandT=False, use_closed_attrib=False, rel=False):
|
||||
"""Returns a path d-string for the path object.
|
||||
For an explanation of useSandT and use_closed_attrib, see the
|
||||
compatibility notes in the README."""
|
||||
|
||||
if use_closed_attrib:
|
||||
self_closed = self.iscontinuous() and self.isclosed()
|
||||
if self_closed:
|
||||
segments = self[:-1]
|
||||
else:
|
||||
segments = self[:]
|
||||
else:
|
||||
self_closed = False
|
||||
segments = self[:]
|
||||
|
||||
current_pos = None
|
||||
parts = []
|
||||
previous_segment = None
|
||||
end = self[-1].end
|
||||
|
||||
for segment in segments:
|
||||
seg_start = segment.start
|
||||
# If the start of this segment does not coincide with the end of
|
||||
# the last segment or if this segment is actually the close point
|
||||
# of a closed path, then we should start a new subpath here.
|
||||
if current_pos != seg_start or \
|
||||
(self_closed and seg_start == end and use_closed_attrib):
|
||||
if rel:
|
||||
_seg_start = seg_start - current_pos if current_pos is not None else seg_start
|
||||
else:
|
||||
_seg_start = seg_start
|
||||
parts.append('M {},{}'.format(_seg_start.real, _seg_start.imag))
|
||||
|
||||
if isinstance(segment, Line):
|
||||
if rel:
|
||||
_seg_end = segment.end - seg_start
|
||||
else:
|
||||
_seg_end = segment.end
|
||||
parts.append('L {},{}'.format(_seg_end.real, _seg_end.imag))
|
||||
elif isinstance(segment, CubicBezier):
|
||||
if useSandT and segment.is_smooth_from(previous_segment,
|
||||
warning_on=False):
|
||||
if rel:
|
||||
_seg_control2 = segment.control2 - seg_start
|
||||
_seg_end = segment.end - seg_start
|
||||
else:
|
||||
_seg_control2 = segment.control2
|
||||
_seg_end = segment.end
|
||||
args = (_seg_control2.real, _seg_control2.imag,
|
||||
_seg_end.real, _seg_end.imag)
|
||||
parts.append('S {},{} {},{}'.format(*args))
|
||||
else:
|
||||
if rel:
|
||||
_seg_control1 = segment.control1 - seg_start
|
||||
_seg_control2 = segment.control2 - seg_start
|
||||
_seg_end = segment.end - seg_start
|
||||
else:
|
||||
_seg_control1 = segment.control1
|
||||
_seg_control2 = segment.control2
|
||||
_seg_end = segment.end
|
||||
args = (_seg_control1.real, _seg_control1.imag,
|
||||
_seg_control2.real, _seg_control2.imag,
|
||||
_seg_end.real, _seg_end.imag)
|
||||
parts.append('C {},{} {},{} {},{}'.format(*args))
|
||||
elif isinstance(segment, QuadraticBezier):
|
||||
if useSandT and segment.is_smooth_from(previous_segment,
|
||||
warning_on=False):
|
||||
if rel:
|
||||
_seg_end = segment.end - seg_start
|
||||
else:
|
||||
_seg_end = segment.end
|
||||
args = _seg_end.real, _seg_end.imag
|
||||
parts.append('T {},{}'.format(*args))
|
||||
else:
|
||||
if rel:
|
||||
_seg_control = segment.control - seg_start
|
||||
_seg_end = segment.end - seg_start
|
||||
else:
|
||||
_seg_control = segment.control
|
||||
_seg_end = segment.end
|
||||
args = (_seg_control.real, _seg_control.imag,
|
||||
_seg_end.real, _seg_end.imag)
|
||||
parts.append('Q {},{} {},{}'.format(*args))
|
||||
|
||||
elif isinstance(segment, Arc):
|
||||
if rel:
|
||||
_seg_end = segment.end - seg_start
|
||||
else:
|
||||
_seg_end = segment.end
|
||||
args = (segment.radius.real, segment.radius.imag,
|
||||
segment.rotation,int(segment.large_arc),
|
||||
int(segment.sweep),_seg_end.real, _seg_end.imag)
|
||||
parts.append('A {},{} {} {:d},{:d} {},{}'.format(*args))
|
||||
current_pos = segment.end
|
||||
previous_segment = segment
|
||||
|
||||
if self_closed:
|
||||
parts.append('Z')
|
||||
|
||||
s = ' '.join(parts)
|
||||
return s if not rel else s.lower()
|
||||
|
||||
class FilletAndChamfer(inkex.EffectExtension):
|
||||
|
||||
def add_arguments(self, pars):
|
||||
pars.add_argument("-t", "--fillet_type", default="fillet", help="Selects whether using fillet or chamfer")
|
||||
pars.add_argument("-R", "--radius", type=float, default=60.0, help="The radius")
|
||||
pars.add_argument('--unit', default='px', help='units of measurement')
|
||||
pars.add_argument("--remove", type=inkex.Boolean, default=False, help="If True, control object will be removed")
|
||||
|
||||
def addEle(self, ele, parent, props):
|
||||
# https://inkscape.org/~pacogarcia/%E2%98%85new-version-of-shapes-extension
|
||||
elem = etree.SubElement(parent, ele)
|
||||
for n in props: elem.set(n,props[n])
|
||||
return elem
|
||||
|
||||
def circle(self, c, r):
|
||||
return svgpathtools.parse_path("m %f,%f a %f,%f 0 0 1 -%f,%f %f,%f 0 0 1 -%f,-%f %f,%f 0 0 1 %f,-%f %f,%f 0 0 1 %f,%f z" % tuple((c.real+r, c.imag) + (r,)*16))
|
||||
|
||||
def _calc_fillet_for_joint(self, p, i):
|
||||
seg1 = p[(i) % len(p)]
|
||||
seg2 = p[(i+1) % len(p)]
|
||||
|
||||
ori_p = svgpathtools.Path(seg1, seg2)
|
||||
new_p = svgpathtools.Path()
|
||||
|
||||
# ignore the node if G1 continuity
|
||||
tg1 = seg1.unit_tangent(1.0)
|
||||
tg2 = seg2.unit_tangent(0.0)
|
||||
cosA = abs(tg1.real * tg2.real + tg1.imag * tg2.imag)
|
||||
if abs(cosA - 1.0) < 1e-6:
|
||||
new_p.append(seg1.cropped(self._prev_t, 1.0))
|
||||
self._prev_t = 0.0
|
||||
if self._very_first_t is None:
|
||||
self._very_first_t = 1.0
|
||||
if not isclosedac(p) and i == len(p) - 2:
|
||||
new_p.append(seg2.cropped(0.0, 1.0)) # add last segment if not closed
|
||||
else:
|
||||
cir = self.circle(seg1.end, self.options.radius)
|
||||
# new_p.extend(cir)
|
||||
|
||||
intersects = ori_p.intersect(cir)
|
||||
if len(intersects) != 2:
|
||||
inkex.errormsg("Some fillet or chamfer may not be drawn: %d intersections!" % len(intersects))
|
||||
new_p.append(seg1.cropped(self._prev_t, 1.0))
|
||||
self._prev_t = 0.0
|
||||
if self._very_first_t is None:
|
||||
self._very_first_t = 1.0
|
||||
if not isclosedac(p) and i == len(p) - 2:
|
||||
new_p.append(seg2.cropped(0.0, 1.0)) # add last segment if not closed
|
||||
else:
|
||||
cb = []; segs = []; ts = []
|
||||
for (T1, seg1, t1), (T2, seg2, t2) in intersects:
|
||||
c1 = seg1.point(t1)
|
||||
tg1 = seg1.unit_tangent(t1) * (self.options.radius * KAPPA)
|
||||
cb.extend([c1, tg1])
|
||||
segs.append(seg1); ts.append(t1)
|
||||
|
||||
# cir1 = self.circle(c1, self.options.radius * KAPPA)
|
||||
# new_p.extend(cir1)
|
||||
# new_p.append(svgpathtools.Line(c1, c1+tg1))
|
||||
|
||||
assert len(cb) == 4
|
||||
new_p.append(segs[0].cropped(self._prev_t, ts[0]))
|
||||
if self.options.fillet_type == 'fillet':
|
||||
fillet = svgpathtools.CubicBezier(cb[0], cb[0]+cb[1], cb[2]-cb[3], cb[2])
|
||||
else:
|
||||
fillet = svgpathtools.Line(cb[0], cb[2])
|
||||
new_p.append(fillet)
|
||||
self._prev_t = ts[1]
|
||||
if self._very_first_t is None:
|
||||
self._very_first_t = ts[0]
|
||||
|
||||
if isclosedac(p) and i == len(p) - 1:
|
||||
new_p.append(segs[1].cropped(ts[1], self._very_first_t)) # update first segment if closed
|
||||
elif not isclosedac(p) and i == len(p) - 2:
|
||||
new_p.append(segs[1].cropped(ts[1], 1.0)) # add last segment if not closed
|
||||
|
||||
# # fix for the first segment
|
||||
# if p.isclosed():
|
||||
# new_p[0] = p[0].cropped(ts[1], self._very_first_t)
|
||||
|
||||
# new_p.append(segs[0].cropped(ts[0], 1.0))
|
||||
# new_p.append(segs[1].cropped(0.0, ts[1]))
|
||||
# if self.options.fillet_type == 'fillet':
|
||||
# fillet = svgpathtools.CubicBezier(cb[0], cb[0]+cb[1], cb[2]-cb[3], cb[2])
|
||||
# else:
|
||||
# fillet = svgpathtools.Line(cb[0], cb[2])
|
||||
# new_p.append(fillet.reversed())
|
||||
|
||||
return new_p
|
||||
|
||||
def add_fillet_to_path(self, d):
|
||||
p = svgpathtools.parse_path(d)
|
||||
p = remove_zero_length_segments(p) # for z, a zero length line segment is possibly added
|
||||
|
||||
if len(p) <= 1:
|
||||
return d
|
||||
|
||||
new_p = svgpathtools.Path()
|
||||
self._prev_t = 0 # used as cache
|
||||
self._very_first_t = None # update first segment if closed
|
||||
if isclosedac(p):
|
||||
for i in range(len(p)):
|
||||
new_p.extend(self._calc_fillet_for_joint(p, i))
|
||||
if not isclosedac(new_p):
|
||||
del new_p[0] # remove first segment if closed
|
||||
else:
|
||||
for i in range(len(p)-1):
|
||||
new_p.extend(self._calc_fillet_for_joint(p, i))
|
||||
new_p = round_path(new_p, 6)
|
||||
# inkex.errormsg(d_str(new_p, use_closed_attrib=True, rel=True))
|
||||
return d_str(new_p, use_closed_attrib=True, rel=True)
|
||||
|
||||
def effect(self):
|
||||
self.options.radius = self.svg.unittouu(str(self.options.radius) + self.options.unit)
|
||||
|
||||
if self.options.radius == 0:
|
||||
return
|
||||
|
||||
for id, node in self.svg.selected.items():
|
||||
_shape = etree.QName(node.tag).localname
|
||||
if _shape != "path":
|
||||
inkex.errormsg("Fillet and chamfer only operates on path: %s is %s" % (id, _shape))
|
||||
else:
|
||||
# inkex.errormsg(etree.tostring(node))
|
||||
attrib = {k:v for k,v in node.attrib.items()}
|
||||
attrib['d'] = self.add_fillet_to_path(attrib['d'])
|
||||
self.addEle(inkex.addNS('path','svg'), node.getparent(), attrib)
|
||||
|
||||
if self.options.remove:
|
||||
node.delete()
|
||||
|
||||
if __name__ == '__main__':
|
||||
FilletAndChamfer().run()
|
21
extensions/fablabchemnitz/fillet_and_chamfer/meta.json
Normal file
21
extensions/fablabchemnitz/fillet_and_chamfer/meta.json
Normal file
@ -0,0 +1,21 @@
|
||||
[
|
||||
{
|
||||
"name": "Fillet And Chamfer (Replaced by LPE)",
|
||||
"id": "fablabchemnitz.de.fillet_and_chamfer",
|
||||
"path": "fillet_and_chamfer",
|
||||
"dependent_extensions": null,
|
||||
"original_name": "Fillet and Chamfer",
|
||||
"original_id": "org.ekips.filter.filletchamfer",
|
||||
"license": "GNU GPL v2",
|
||||
"license_url": "https://github.com/taoari/inkscape-filletandchamfer/blob/master/filletchamfer.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/fillet_and_chamfer",
|
||||
"fork_url": "https://github.com/taoari/inkscape-filletandchamfer",
|
||||
"documentation_url": "https://stadtfabrikanten.org/pages/viewpage.action?pageId=55019889",
|
||||
"inkscape_gallery_url": null,
|
||||
"main_authors": [
|
||||
"github.com/taoari",
|
||||
"github.com/eridur-de"
|
||||
]
|
||||
}
|
||||
]
|
17
extensions/fablabchemnitz/group_to_layer/group_to_layer.inx
Normal file
17
extensions/fablabchemnitz/group_to_layer/group_to_layer.inx
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Group To Layer</name>
|
||||
<id>fablabchemnitz.de.group_to_layer</id>
|
||||
<param name="depth" type="int" gui-text="Max nested group depth">1</param>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz">
|
||||
<submenu name="Groups and Layers"/>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">group_to_layer.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
52
extensions/fablabchemnitz/group_to_layer/group_to_layer.py
Normal file
52
extensions/fablabchemnitz/group_to_layer/group_to_layer.py
Normal file
@ -0,0 +1,52 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2012 Stuart Pernsteiner
|
||||
# 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 AUTHOR "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 AUTHOR 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 inkex
|
||||
|
||||
class GroupToLayer(inkex.EffectExtension):
|
||||
|
||||
def add_arguments(self, pars):
|
||||
pars.add_argument('-d', '--depth', type = int, default = 1, help = 'Convert nested group up to DEPTH layers deep')
|
||||
|
||||
def effect(self):
|
||||
depth = self.options.depth
|
||||
self.tag_g = inkex.addNS('g', 'svg')
|
||||
if len(self.svg.selected) > 0:
|
||||
for node in self.svg.selected.values():
|
||||
self.convert_group(node, depth)
|
||||
else:
|
||||
inkex.errormsg('Please select some objects first.')
|
||||
return
|
||||
|
||||
def convert_group(self, node, depth):
|
||||
if depth <= 0:
|
||||
return
|
||||
if node.tag != self.tag_g:
|
||||
return
|
||||
node.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
|
||||
for child in node:
|
||||
self.convert_group(child, depth - 1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
GroupToLayer().run()
|
21
extensions/fablabchemnitz/group_to_layer/meta.json
Normal file
21
extensions/fablabchemnitz/group_to_layer/meta.json
Normal file
@ -0,0 +1,21 @@
|
||||
[
|
||||
{
|
||||
"name": "Group To Layer",
|
||||
"id": "fablabchemnitz.de.group_to_layer",
|
||||
"path": "group_to_layer",
|
||||
"dependent_extensions": null,
|
||||
"original_name": "Group to Layer",
|
||||
"original_id": "org.pernsteiner.inkscape.group_to_layer",
|
||||
"license": "BSD-2-Clause License",
|
||||
"license_url": "http://www.pernsteiner.org/inkscape/group_to_layer/inkscape-group_to_layer-0.1.0.zip",
|
||||
"comment": "",
|
||||
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/group_to_layer",
|
||||
"fork_url": "http://www.pernsteiner.org/inkscape/group_to_layer/",
|
||||
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Group+To+Layer",
|
||||
"inkscape_gallery_url": null,
|
||||
"main_authors": [
|
||||
"pernsteiner.org/Stuart Pernsteiner",
|
||||
"github.com/eridur-de"
|
||||
]
|
||||
}
|
||||
]
|
21
extensions/fablabchemnitz/perspective_grid/meta.json
Normal file
21
extensions/fablabchemnitz/perspective_grid/meta.json
Normal file
@ -0,0 +1,21 @@
|
||||
[
|
||||
{
|
||||
"name": "Perspective Grid",
|
||||
"id": "fablabchemnitz.de.perspective_grid",
|
||||
"path": "perspective_grid",
|
||||
"dependent_extensions": null,
|
||||
"original_name": "Perspective Grid",
|
||||
"original_id": "grid.perspective",
|
||||
"license": "GNU GPL v2",
|
||||
"license_url": "https://github.com/cds4/inkscape-grids/blob/master/grid_perspect2.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/perspective_grid",
|
||||
"fork_url": "https://github.com/cds4/inkscape-grids",
|
||||
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Perspective+Grid",
|
||||
"inkscape_gallery_url": null,
|
||||
"main_authors": [
|
||||
"github.com/cds4",
|
||||
"github.com/eridur-de"
|
||||
]
|
||||
}
|
||||
]
|
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Perspective Grid</name>
|
||||
<id>fablabchemnitz.de.perspective_grid</id>
|
||||
<param name="size_unit" type="optiongroup" appearance="combo" gui-text="Geometry units">
|
||||
<option value="px">px</option>
|
||||
<option value="pt">pt</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="mm">mm</option>
|
||||
<option value="in">in</option>
|
||||
</param>
|
||||
<param name="width" type="int" min="1" max="1000" gui-text="Width of window">500</param>
|
||||
<param name="height" type="int" min="1" max="1000" gui-text="Height of window">300</param>
|
||||
<param name="horizon" type="float" min="-1000" max="1000" gui-text="Horizon y coordinate">150</param>
|
||||
<param name="left_x" type="float" min="-1000" max="1000" gui-text="Left perspective point">-100.0</param>
|
||||
<param name="right_x" type="float" min="-1000" max="1000" gui-text="Right perspective point">600</param>
|
||||
<param name="p_divs" type="int" min="1" max="1000" gui-text="Perspective angle divisions">10</param>
|
||||
<param name="border_th" type="float" min="0" max="1000" gui-text="Border Thickness [px]">3</param>
|
||||
<param name="div_th" type="float" min="0" max="1000" gui-text="Major grid division Thickness [px]">2</param>
|
||||
<param name="div_color" type="color" appearance="colorbutton" gui-text="Grid color">255</param>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz">
|
||||
<submenu name="Grids/Guides"/>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">perspective_grid.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
279
extensions/fablabchemnitz/perspective_grid/perspective_grid.py
Normal file
279
extensions/fablabchemnitz/perspective_grid/perspective_grid.py
Normal file
@ -0,0 +1,279 @@
|
||||
#!/usr/bin/env python3
|
||||
'''
|
||||
Copyright (C) 2013 Carl Sorensen carl.d.sorensen@gmail.com
|
||||
Derived from grid_cartesian.py copyright (C) 2007 John Beard john.j.beard@gmail.com
|
||||
|
||||
|
||||
##This extension allows you to draw a two-point perspective grid in Inkscape.
|
||||
##There is a wide range of options including subdivision, subsubdivions
|
||||
##and angles of the triangular axes.
|
||||
##Custom line widths are also possible.
|
||||
##All elements are grouped with similar elements (eg all x-subdivs)
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
'''
|
||||
|
||||
import inkex
|
||||
from math import *
|
||||
from lxml import etree
|
||||
|
||||
def draw_SVG_line(x1, y1, x2, y2, width, stroke, name, parent):
|
||||
style = { 'stroke': stroke, 'stroke-width':"{:0.6f}".format(width), 'fill': 'none' }
|
||||
line_attribs = {'style':str(inkex.Style(style)),
|
||||
inkex.addNS('label','inkscape'):name,
|
||||
'd':'M '+str(x1)+','+str(y1)+' L '+str(x2)+','+str(y2)}
|
||||
etree.SubElement(parent, inkex.addNS('path','svg'), line_attribs )
|
||||
|
||||
def draw_SVG_rect(x,y,w,h, width, stroke, fill, name, parent):
|
||||
style = { 'stroke': stroke, 'stroke-width':str(width), 'fill':fill}
|
||||
rect_attribs = {'style':str(inkex.Style(style)),
|
||||
inkex.addNS('label','inkscape'):name,
|
||||
'x':str(x), 'y':str(y), 'width':str(w), 'height':str(h)}
|
||||
etree.SubElement(parent, inkex.addNS('rect','svg'), rect_attribs )
|
||||
|
||||
def colorString(pickerColor):
|
||||
longcolor = int(pickerColor)
|
||||
if longcolor < 0:
|
||||
longcolor = longcolor & 0xFFFFFFFF
|
||||
return '#' + format(longcolor >> 8, '06X')
|
||||
|
||||
class PerspectiveGrid(inkex.EffectExtension):
|
||||
|
||||
def add_arguments(self, pars):
|
||||
pars.add_argument("--size_unit", default="", help="Units for geometry")
|
||||
pars.add_argument("--width", type=int, default=500, help="Width of grid window")
|
||||
pars.add_argument("--height", type=int, default=300, help="Height of grid window")
|
||||
pars.add_argument("--p_divs", type=int, default=10, help="Number of divisions in perspective angle")
|
||||
pars.add_argument("--horizon", type=float, default=150, help="Y coordinate of horizon")
|
||||
pars.add_argument("--left_x", type=float, default=-250, help="X coordinate of left perspective point")
|
||||
pars.add_argument("--right_x", type=float, default=750, help="X coordinate of right perspective point")
|
||||
pars.add_argument("--div_th", type=float, default=2, help="Grid division line thickness (px)")
|
||||
pars.add_argument("--border_th", type=float, default=3, help="Border Line thickness (px)")
|
||||
pars.add_argument("--div_color", type=int, help="Grid color")
|
||||
|
||||
def EdgePoints(self,x0, y0, theta):
|
||||
# find the intersection points of the line with the extended
|
||||
# grid bounding box.
|
||||
# Note that y is positive DOWN, not up
|
||||
# Grid bounding box goes from (0,0) to (self.xmax, self.ymax)
|
||||
theta_r = radians(theta)
|
||||
if theta_r == 0:
|
||||
return [[0,y0],[self.xmax,y0],
|
||||
[-100, self.ymax], [self.xmax+100,0]]
|
||||
r_bot = (self.ymax-y0)/sin(theta_r)
|
||||
r_top = -y0/sin(theta_r)
|
||||
r_left = -x0/cos(theta_r)
|
||||
r_right = (self.xmax-x0)/cos(theta_r)
|
||||
return [[0,y0+r_left*sin(theta_r)], # left
|
||||
[self.xmax, y0+r_right*sin(theta_r)], # right
|
||||
[x0+r_bot*cos(theta_r), self.ymax], #bottom
|
||||
[x0+r_top*cos(theta_r), 0]] #top
|
||||
|
||||
def trimmed_coords(self, x1, y1, theta):
|
||||
#find the start and end coordinates for a grid line
|
||||
#starting at (x1, y1) with an angle of theta
|
||||
border_points = self.EdgePoints(x1, y1, theta)
|
||||
left = 0
|
||||
right = 1
|
||||
top = 3
|
||||
bottom = 2
|
||||
x=0
|
||||
y=1
|
||||
if theta > 0:
|
||||
if border_points[left][y] < 0:
|
||||
start_x = border_points[top][x]
|
||||
start_y = border_points[top][y]
|
||||
else:
|
||||
start_x = border_points[left][x]
|
||||
start_y = border_points[left][y]
|
||||
if border_points[right][y] > self.ymax:
|
||||
end_x = border_points[bottom][x]
|
||||
end_y = border_points[bottom][y]
|
||||
else:
|
||||
end_x = border_points[right][x]
|
||||
end_y = border_points[right][y]
|
||||
else:
|
||||
if border_points[left][y] > self.ymax:
|
||||
start_x = border_points[bottom][x]
|
||||
start_y = border_points[bottom][y]
|
||||
else:
|
||||
start_x = border_points[left][x]
|
||||
start_y = border_points[left][y]
|
||||
if border_points[right][y] < 0:
|
||||
end_x = border_points[top][x]
|
||||
end_y = border_points[top][y]
|
||||
else:
|
||||
end_x = border_points[right][x]
|
||||
end_y = border_points[right][y]
|
||||
return [[start_x,start_y],[end_x, end_y]]
|
||||
|
||||
def drawAngledGridLine (self, x1, y1, theta, thickness, color,
|
||||
label, groupName):
|
||||
end_points = self.trimmed_coords(x1, y1, theta)
|
||||
x_start = end_points[0][0]
|
||||
y_start = end_points[0][1]
|
||||
x_end = end_points[1][0]
|
||||
y_end = end_points[1][1]
|
||||
|
||||
if (x_start >= 0 and x_start <= self.xmax and
|
||||
y_start >= 0 and y_start <= self.ymax and
|
||||
x_end >= 0 and x_end <= self.xmax and
|
||||
y_end >= 0 and y_end <= self.ymax):
|
||||
draw_SVG_line(x_start, y_start,
|
||||
x_end, y_end,
|
||||
thickness, colorString(color), label, groupName)
|
||||
|
||||
def perspective_intersection(self, left_theta, right_theta):
|
||||
if right_theta == 0 or left_theta == 0 or left_theta == right_theta:
|
||||
return -100 # outside of bounding box
|
||||
try:
|
||||
r=(self.right_x - self.left_x)/(sin(right_theta)/tan(left_theta)-cos(right_theta))
|
||||
y_int = self.horizon + r*sin(right_theta)
|
||||
if y_int < 0 or y_int > self.ymax :
|
||||
return -100 #above or below bounding box
|
||||
return self.right_x + r*cos(right_theta)
|
||||
except ZeroDivisionError:
|
||||
inkex.errormsg("Perspective angle divisions resulted in division by zero. Please adjust the values for perspective points and/or angle divisions.")
|
||||
exit()
|
||||
|
||||
def effect(self):
|
||||
|
||||
#find the pixel dimensions of the overall grid
|
||||
self.ymax = self.svg.unittouu(str(self.options.height)+self.options.size_unit)
|
||||
self.xmax = self.svg.unittouu(str(self.options.width)+self.options.size_unit)
|
||||
self.horizon = self.svg.unittouu(str(self.options.horizon)+self.options.size_unit)
|
||||
self.left_x = self.svg.unittouu(str(self.options.left_x)+self.options.size_unit)
|
||||
self.right_x = self.svg.unittouu(str(self.options.right_x)+self.options.size_unit)
|
||||
|
||||
# Overwrite thickness values to use px unit properly
|
||||
self.options.div_th = self.svg.unittouu(str(self.options.div_th) + "px")
|
||||
self.options.border_th = self.svg.unittouu(str(self.options.border_th) + "px")
|
||||
|
||||
# Embed grid in group
|
||||
#Put in in the centre of the current view
|
||||
t = 'translate(' + str( self.svg.namedview.center[0]- self.xmax/2.0) + ',' + \
|
||||
str( self.svg.namedview.center[1]- self.ymax/2.0) + ')'
|
||||
g_attribs = {inkex.addNS('label','inkscape'):'Grid_Perspective:Size' + \
|
||||
str( self.xmax)+'x'+str(self.ymax) +
|
||||
':Horizon'+str(self.horizon) +
|
||||
':LeftX'+str(self.left_x) +
|
||||
':RightX'+str(self.right_x),
|
||||
'transform':t }
|
||||
grid = etree.SubElement(self.svg.get_current_layer(), 'g', g_attribs)
|
||||
|
||||
#Group for vertical gridlines
|
||||
g_attribs = {inkex.addNS('label','inkscape'):'VerticalGridlines'}
|
||||
gv = etree.SubElement(grid, 'g', g_attribs)
|
||||
|
||||
#Group for left point gridlines
|
||||
g_attribs = {inkex.addNS('label','inkscape'):'LeftPointGridlines'}
|
||||
glp = etree.SubElement(grid, 'g', g_attribs)
|
||||
|
||||
#Group for right point gridlines
|
||||
g_attribs = {inkex.addNS('label','inkscape'):'RightPointGridlines'}
|
||||
grp = etree.SubElement(grid, 'g', g_attribs)
|
||||
|
||||
draw_SVG_rect(0, 0, self.xmax, self.ymax, self.options.border_th,
|
||||
colorString(self.options.div_color), 'none',
|
||||
'Border', grid) #border rectangle
|
||||
|
||||
|
||||
# Calculate the extreme angles for the left and right points
|
||||
try:
|
||||
if self.horizon < 0 :
|
||||
left_theta_min = atan((self.horizon-0)/(0-self.right_x))
|
||||
left_theta_max = atan((self.ymax - self.horizon)/
|
||||
(0-self.left_x))
|
||||
right_theta_min = atan((0-self.horizon)/
|
||||
(self.left_x-self.xmax))
|
||||
right_theta_max = atan((self.horizon - self.ymax)/
|
||||
(self.right_x - self.xmax ))
|
||||
elif self.horizon < self.ymax :
|
||||
left_theta_min = atan((self.horizon-0)/(self.left_x-0))
|
||||
left_theta_max = atan((self.ymax - self.horizon)/
|
||||
(0-self.left_x))
|
||||
right_theta_min = atan((self.horizon-0)/
|
||||
(self.right_x-self.xmax))
|
||||
right_theta_max = atan((self.horizon - self.ymax)/
|
||||
(self.right_x - self.xmax ))
|
||||
else:
|
||||
left_theta_min = atan((self.horizon-0)/(self.left_x-0))
|
||||
left_theta_max = atan((self.ymax - self.horizon)/
|
||||
(0-self.right_x))
|
||||
right_theta_min = atan((self.horizon-0)/
|
||||
(self.right_x-self.xmax))
|
||||
right_theta_max = atan((self.horizon - self.ymax)/
|
||||
(self.left_x - self.xmax ))
|
||||
except ZeroDivisionError:
|
||||
inkex.errormsg("Division by zero error. Please adjust the values accordingly.")
|
||||
exit()
|
||||
left_dtheta = (left_theta_max - left_theta_min)/float(self.options.p_divs)
|
||||
right_dtheta = (right_theta_max - right_theta_min)/float(self.options.p_divs)
|
||||
mid_index = self.options.p_divs/2
|
||||
left_mid_theta = left_theta_min + mid_index * left_dtheta
|
||||
right_mid_theta = right_theta_min + mid_index * right_dtheta
|
||||
|
||||
|
||||
#DO THE PERSPECTIVE DIVISONS========================================
|
||||
for i in range(0,self.options.p_divs+1):
|
||||
left_theta = left_theta_min + i * left_dtheta
|
||||
right_theta = right_theta_min + i * right_dtheta
|
||||
self.drawAngledGridLine(self.left_x, self.horizon,
|
||||
degrees(left_theta),
|
||||
self.options.div_th,
|
||||
self.options.div_color,
|
||||
'LeftDivPersp'+str(i),
|
||||
glp)
|
||||
self.drawAngledGridLine(self.right_x, self.horizon,
|
||||
degrees(right_theta),
|
||||
self.options.div_th,
|
||||
self.options.div_color,
|
||||
'RightDivPersp'+str(i),
|
||||
grp)
|
||||
intersection = self.perspective_intersection(left_theta,
|
||||
right_theta_max - i * right_dtheta)
|
||||
if intersection > 0 and intersection < self.xmax:
|
||||
draw_SVG_line(intersection, 0,
|
||||
intersection, self.ymax,
|
||||
self.options.div_th,
|
||||
colorString(self.options.div_color),
|
||||
'VerticalDiv'+str(i), gv)
|
||||
comment = """
|
||||
intersection = self.perspective_intersection(left_theta, right_mid_theta)
|
||||
if intersection > 0 and intersection < self.xmax:
|
||||
draw_SVG_line(intersection, 0,
|
||||
intersection, self.ymax,
|
||||
self.options.div_th,
|
||||
colorString(self.options.div_color),
|
||||
'VerticalDiv'+str(i), gv)
|
||||
intersection = self.perspective_intersection(left_theta, right_theta)
|
||||
if intersection > 0 and intersection < self.xmax:
|
||||
draw_SVG_line(intersection, 0,
|
||||
intersection, self.ymax,
|
||||
self.options.div_th,
|
||||
colorString(self.options.div_color),
|
||||
'VerticalDiv'+str(i), gv)
|
||||
"""
|
||||
|
||||
intersection = self.perspective_intersection(left_mid_theta, right_mid_theta)
|
||||
if intersection > 0 and intersection < self.xmax:
|
||||
draw_SVG_line(intersection, 0,
|
||||
intersection, self.ymax,
|
||||
self.options.div_th,
|
||||
colorString(self.options.div_color),
|
||||
'VerticalDiv'+str(i), gv)
|
||||
|
||||
if __name__ == '__main__':
|
||||
PerspectiveGrid().run()
|
21
extensions/fablabchemnitz/rounder/meta.json
Normal file
21
extensions/fablabchemnitz/rounder/meta.json
Normal file
@ -0,0 +1,21 @@
|
||||
[
|
||||
{
|
||||
"name": "Rounder",
|
||||
"id": "fablabchemnitz.de.rounder",
|
||||
"path": "rounder",
|
||||
"dependent_extensions": null,
|
||||
"original_name": "Rounder",
|
||||
"original_id": "jtx.rounder",
|
||||
"license": "GNU GPL v2",
|
||||
"license_url": "https://inkscape.org/~jabiertxof/%E2%98%85rounder-04",
|
||||
"comment": "bufgixed and ported to Inkscape v1 manually by Mario Voigt",
|
||||
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/rounder",
|
||||
"fork_url": "https://inkscape.org/~jabiertxof/%E2%98%85rounder-04",
|
||||
"documentation_url": "",
|
||||
"inkscape_gallery_url": null,
|
||||
"main_authors": [
|
||||
"inkscape.org/jabiertxof",
|
||||
"github.com/eridur-de"
|
||||
]
|
||||
}
|
||||
]
|
30
extensions/fablabchemnitz/rounder/rounder.inx
Normal file
30
extensions/fablabchemnitz/rounder/rounder.inx
Normal file
@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Rounder</name>
|
||||
<id>fablabchemnitz.de.rounder</id>
|
||||
<param name="precision" type="int" min="0" max="20" gui-text="Rounding precision">2</param>
|
||||
<param name="paths" type="bool" gui-text="Round nodes">true</param>
|
||||
<param name="ctrl" type="bool" gui-text="Round handles">false</param>
|
||||
<param name="along" type="bool" gui-text="Move handles following node movement">true</param>
|
||||
<param name="half" type="bool" gui-text="Allow round to half if nearest">false</param>
|
||||
<param name="strokewidth" type="bool" gui-text="Round stroke width">false</param>
|
||||
<param name="widthheight" type="bool" gui-text="Round width and height">false</param>
|
||||
<param name="position" type="bool" gui-text="Round position X and Y">false</param>
|
||||
<param name="opacity" type="bool" gui-text="Round global opacity">false</param>
|
||||
<param name="strokeopacity" type="bool" gui-text="Round stroke opacity">false</param>
|
||||
<param name="fillopacity" type="bool" gui-text="Round fill opacity">false</param>
|
||||
<spacer/>
|
||||
<label>Please note: Rounder only applies to svg:path elements. You cannot use it for rectangle, circle, ellipsis, arc, polygon, line, polyline, etc.</label>
|
||||
<label appearance="url">https://y.stadtfabrikanten.org/rounder</label>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz">
|
||||
<submenu name="Modify existing Path(s)"/>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">rounder.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
165
extensions/fablabchemnitz/rounder/rounder.py
Normal file
165
extensions/fablabchemnitz/rounder/rounder.py
Normal file
@ -0,0 +1,165 @@
|
||||
#! /usr/bin/python
|
||||
'''
|
||||
Rounder 0.4
|
||||
Based in deprecated "Path Rounder 0.2"
|
||||
Based in radiusrand script from Aaron Spike and make it by Jabier Arraiza,
|
||||
jabier.arraiza@marker.es
|
||||
Copyright (C) 2005 Aaron Spike, aaron@ekips.org
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
'''
|
||||
import random
|
||||
import math
|
||||
import inkex
|
||||
from inkex.paths import Path, CubicSuperPath
|
||||
import re
|
||||
|
||||
class Rounder(inkex.EffectExtension):
|
||||
|
||||
def add_arguments(self, pars):
|
||||
pars.add_argument("--precision", type=int, default=3, help="Precision")
|
||||
pars.add_argument("--ctrl", type=inkex.Boolean, default = False, help="Round element handles")
|
||||
pars.add_argument("--along", type=inkex.Boolean, default = True, help="Move handles following element movement")
|
||||
pars.add_argument("--half", type=inkex.Boolean, default = False, help="Allow round to half if nearest")
|
||||
pars.add_argument("--paths", type=inkex.Boolean, default = True, help="Affect to paths")
|
||||
pars.add_argument("--widthheight", type=inkex.Boolean, default = False, help="Affect to width and height of objects")
|
||||
pars.add_argument("--position", type=inkex.Boolean, default = False, help="Affect to position of objects")
|
||||
pars.add_argument("--strokewidth", type=inkex.Boolean, default = False, help="Affect to stroke width of objects")
|
||||
pars.add_argument("--opacity", type=inkex.Boolean, default = False, help="Affect to global opacity of objects")
|
||||
pars.add_argument("--strokeopacity", type=inkex.Boolean, default = False, help="Affect to stroke opcacity of objects")
|
||||
pars.add_argument("--fillopacity", type=inkex.Boolean, default = False, help="Affect to fill opcacity of objects")
|
||||
|
||||
def roundFloat(self, n):
|
||||
if self.options.half:
|
||||
if self.options.precision == 0:
|
||||
return str(round(n * 2) / 2)
|
||||
else:
|
||||
return str(round(n * (self.options.precision) * 10 * 2) / ((self.options.precision) * 10 * 2))
|
||||
else:
|
||||
return str(round(n, self.options.precision))
|
||||
|
||||
def roundit(self, p):
|
||||
x = self.roundFloat(p[0])
|
||||
y = self.roundFloat(p[1])
|
||||
return [float(x) - p[0], float(y) - p[1]]
|
||||
|
||||
def path_round_it(self,element):
|
||||
if element.tag == inkex.addNS('path','svg'):
|
||||
d = element.get('d')
|
||||
p = CubicSuperPath(d)
|
||||
for subpath in p:
|
||||
for csp in subpath:
|
||||
delta = self.roundit(csp[1])
|
||||
if self.options.along:
|
||||
csp[0][0]+=delta[0]
|
||||
csp[0][1]+=delta[1]
|
||||
csp[1][0]+=delta[0]
|
||||
csp[1][1]+=delta[1]
|
||||
if self.options.along:
|
||||
csp[2][0]+=delta[0]
|
||||
csp[2][1]+=delta[1]
|
||||
if self.options.ctrl:
|
||||
delta = self.roundit(csp[0])
|
||||
csp[0][0]+=delta[0]
|
||||
csp[0][1]+=delta[1]
|
||||
delta = self.roundit(csp[2])
|
||||
csp[2][0]+=delta[0]
|
||||
csp[2][1]+=delta[1]
|
||||
element.set('d',str(Path(p)))
|
||||
elif element.tag == inkex.addNS('g','svg'):
|
||||
for e in element:
|
||||
self.path_round_it(e)
|
||||
|
||||
def roundStroke(self,matchobj):
|
||||
return 'stroke-width:' + self.roundFloat(float( re.sub(r'[a-zA-Z]', "", matchobj.group(1)))) + matchobj.group(2);
|
||||
|
||||
def roundOpacity(self,matchobj):
|
||||
return matchobj.group(1) + matchobj.group(2) + self.roundFloat(float( matchobj.group(3))) + matchobj.group(4);
|
||||
|
||||
def roundWHXY(self,matchobj):
|
||||
return matchobj.group(1) + self.roundFloat(float( matchobj.group(2))) + matchobj.group(3);
|
||||
|
||||
|
||||
def stroke_round_it(self, element):
|
||||
if element.tag == inkex.addNS('g','svg'):
|
||||
for e in element:
|
||||
self.stroke_round_it(e)
|
||||
else:
|
||||
style = element.get('style')
|
||||
if style:
|
||||
style = re.sub('stroke-width:(.*?)(;|$)',self.roundStroke, style)
|
||||
element.set('style',style)
|
||||
def opacity_round_it(self, element, typeOpacity):
|
||||
if element.tag == inkex.addNS('g','svg'):
|
||||
for e in element:
|
||||
self.opacity_round_it(e, typeOpacity)
|
||||
else:
|
||||
style = element.get('style')
|
||||
if style:
|
||||
style = re.sub('(^|;)(' + typeOpacity + ':)(.*?)(;|$)',self.roundOpacity, style)
|
||||
element.set('style',style)
|
||||
|
||||
def widthheight_round_it(self, element):
|
||||
if element.tag == inkex.addNS('g','svg'):
|
||||
for e in element:
|
||||
self.widthheight_round_it(e)
|
||||
else:
|
||||
width = element.get('width')
|
||||
if width:
|
||||
width = re.sub('^(\-|)([0-9]+\.[0-9]+)(.*?)$',self.roundWHXY, width)
|
||||
element.set('width',width)
|
||||
height = element.get('height')
|
||||
if height:
|
||||
height = re.sub('^(\-|)([0-9]+\.[0-9]+)(.*?)$',self.roundWHXY, height)
|
||||
element.set('height',height)
|
||||
|
||||
def position_round_it(self, element):
|
||||
if element.tag == inkex.addNS('g','svg'):
|
||||
for e in element:
|
||||
self.position_round_it(e)
|
||||
else:
|
||||
x = element.get('x')
|
||||
if x:
|
||||
x = re.sub('^(\-|)([0-9]+\.[0-9]+)(.*?)$',self.roundWHXY, x)
|
||||
element.set('x', x)
|
||||
y = element.get('y')
|
||||
if y:
|
||||
y = re.sub('^(\-|)([0-9]+\.[0-9]+)(.*?)$',self.roundWHXY, y)
|
||||
element.set('y', y)
|
||||
|
||||
def effect(self):
|
||||
|
||||
if len(self.svg.selected) > 0:
|
||||
for element in self.svg.selection.values():
|
||||
if self.options.paths:
|
||||
self.path_round_it(element)
|
||||
if self.options.strokewidth:
|
||||
self.stroke_round_it(element)
|
||||
if self.options.widthheight:
|
||||
self.widthheight_round_it(element)
|
||||
if self.options.position:
|
||||
self.position_round_it(element)
|
||||
if self.options.opacity:
|
||||
self.opacity_round_it(element, "opacity")
|
||||
if self.options.strokeopacity:
|
||||
self.opacity_round_it(element, "stroke-opacity")
|
||||
if self.options.fillopacity:
|
||||
self.opacity_round_it(element, "fill-opacity")
|
||||
else:
|
||||
self.msg('Please select some paths first.')
|
||||
return
|
||||
|
||||
if __name__ == '__main__':
|
||||
Rounder().run()
|
21
extensions/fablabchemnitz/set_view_box/meta.json
Normal file
21
extensions/fablabchemnitz/set_view_box/meta.json
Normal file
@ -0,0 +1,21 @@
|
||||
[
|
||||
{
|
||||
"name": "Set View Box (Replaced by CTRL + SHIFT + R)",
|
||||
"id": "fablabchemnitz.de.set_view_box",
|
||||
"path": "set_view_box",
|
||||
"dependent_extensions": null,
|
||||
"original_name": "Set viewBox",
|
||||
"original_id": "org.pernsteiner.inkscape.viewbox",
|
||||
"license": "BSD-2-Clause License",
|
||||
"license_url": "http://www.pernsteiner.org/inkscape/viewbox/inkscape-viewbox-0.1.0.zip",
|
||||
"comment": "ported to Inkscape v1 manually by Mario Voigt",
|
||||
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/set_view_box",
|
||||
"fork_url": "http://www.pernsteiner.org/inkscape/viewbox",
|
||||
"documentation_url": "https://stadtfabrikanten.org/pages/viewpage.action?pageId=74645779",
|
||||
"inkscape_gallery_url": null,
|
||||
"main_authors": [
|
||||
"pernsteiger.org/Stuart Pernsteiner",
|
||||
"github.com/eridur-de"
|
||||
]
|
||||
}
|
||||
]
|
16
extensions/fablabchemnitz/set_view_box/set_view_box.inx
Normal file
16
extensions/fablabchemnitz/set_view_box/set_view_box.inx
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Set View Box</name>
|
||||
<id>fablabchemnitz.de.set_view_box</id>
|
||||
<effect>
|
||||
<object-type>rect</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz">
|
||||
<submenu name="Transformations"/>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">set_view_box.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
71
extensions/fablabchemnitz/set_view_box/set_view_box.py
Normal file
71
extensions/fablabchemnitz/set_view_box/set_view_box.py
Normal file
@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright (c) 2012 Stuart Pernsteiner
|
||||
# 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 AUTHOR "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 AUTHOR 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 sys
|
||||
import inkex
|
||||
from inkex import Transform
|
||||
|
||||
class SetViewBox(inkex.EffectExtension):
|
||||
|
||||
def effect(self):
|
||||
if len(self.svg.selected) != 1:
|
||||
sys.exit("Error: You must select exactly one rectangle")
|
||||
if list(self.svg.selected.items())[0][1].tag != inkex.addNS('rect','svg'):
|
||||
sys.exit("Error: You must select exactly one rectangle")
|
||||
|
||||
sel = None
|
||||
for pathId in self.svg.selected:
|
||||
sel = self.svg.selected[pathId]
|
||||
|
||||
mat = [[1,0,0],[0,1,0]]
|
||||
cur = sel
|
||||
while cur is not None:
|
||||
curMat = Transform(cur.get('transform'))
|
||||
mat = Transform(curMat) @ Transform(mat)
|
||||
cur = cur.getparent()
|
||||
|
||||
[x,y,w,h] = map(lambda attr: float(sel.get(attr)),
|
||||
['x','y','width','height'])
|
||||
|
||||
(x1,y1) = transformPoint(mat, (x,y))
|
||||
(x2,y2) = transformPoint(mat, (x+w,y+h))
|
||||
|
||||
ww = x2-x1
|
||||
hh = y2-y1
|
||||
|
||||
format_units = inkex.units.parse_unit(self.svg.get('width'))[1] #get the "Format:" unit at "Display tab"
|
||||
|
||||
root = self.svg.getElement('//svg:svg');
|
||||
root.set('viewBox', '%f %f %f %f' % (x1,y1,ww,hh))
|
||||
root.set('width', str(ww) + format_units)
|
||||
root.set('height', str(hh) + format_units)
|
||||
|
||||
def transformPoint(mat, pt):
|
||||
pt2 = [pt[0], pt[1]]
|
||||
Transform(mat).apply_to_point(pt2)
|
||||
return (pt2[0], pt2[1])
|
||||
|
||||
if __name__ == '__main__':
|
||||
SetViewBox().run()
|
22
extensions/fablabchemnitz/shirt_waist/meta.json
Normal file
22
extensions/fablabchemnitz/shirt_waist/meta.json
Normal file
@ -0,0 +1,22 @@
|
||||
[
|
||||
{
|
||||
"name": "Shirt Waist (Sara May Allington)",
|
||||
"id": "fablabchemnitz.de.shirt_waist",
|
||||
"path": "shirt_waist",
|
||||
"dependent_extensions": null,
|
||||
"original_name": "unknown",
|
||||
"original_id": "unknown",
|
||||
"license": "GNU GPL v2 / CC BY-NC 3.0",
|
||||
"license_url": "https://docs.google.com/file/d/0BxsnjDIHW4yvZUl5SUxLTHZHems/edit",
|
||||
"comment": "ported to Inkscape v1 by Mario Voigt",
|
||||
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/shirt_waist",
|
||||
"fork_url": "http://www.taumeta.org/?page_id=247",
|
||||
"documentation_url": "https://stadtfabrikanten.org/pages/viewpage.action?pageId=55018402",
|
||||
"inkscape_gallery_url": null,
|
||||
"main_authors": [
|
||||
"taumeta.org/Susan Spencer",
|
||||
"taumeta.org/Steve Conklin",
|
||||
"github.com/eridur-de"
|
||||
]
|
||||
}
|
||||
]
|
820
extensions/fablabchemnitz/shirt_waist/sewing_patterns.py
Normal file
820
extensions/fablabchemnitz/shirt_waist/sewing_patterns.py
Normal file
@ -0,0 +1,820 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# sewing_patterns.py
|
||||
# Inkscape extension-Effects-Sewing Patterns
|
||||
# Copyright (C) 2010, 2011, 2012 Susan Spencer, Steve Conklin < www.taumeta.org >
|
||||
#
|
||||
# This program is free software:you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version. Attribution must be given in
|
||||
# all derived works.
|
||||
#
|
||||
# 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/ >
|
||||
|
||||
import subprocess, math, inkex, gettext
|
||||
from lxml import etree
|
||||
|
||||
def debug(errmsg, file = 'bell'):
|
||||
#audio files directory: /usr/share/sounds/ubuntu/stereo
|
||||
sounds(file)
|
||||
inkex.errormsg(gettext.gettext(errmsg))
|
||||
|
||||
def sounds(file):
|
||||
subprocess.call(['/usr/bin/canberra-gtk-play', '--id', file ])
|
||||
|
||||
#---
|
||||
class Point():
|
||||
'''Python object class to create variables that represent a named Cartesian point.
|
||||
Accepts id, x, y. Returns object with .id, .x, .y attributes.
|
||||
Example: a5 = Point('a5', 10.0, 15.32) returns variable a5 where a5.id = 'a5', a5.x = 10.0, a5.y = 15.32xt
|
||||
If called with no parameters the defaults are id = '', x = 0, y = 0
|
||||
The python object's id attribute enables it to be represented on the canvas as an svg object with the same id.
|
||||
'''
|
||||
def __init__(self, id = '', x = 0.0, y = 0.0): #if no parameters are passed in then the default values id = '', x = 0, y = 0 are used
|
||||
self.id = id
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.y = y
|
||||
|
||||
def patternPointXY(parent, id, x, y):
|
||||
'''Accepts parent, id, x, y. Returns object of class Point. Calls addPoint() & addText() to create a pattern point on canvas.'''
|
||||
# create python variable
|
||||
pnt = Point(id, x, y)
|
||||
# create svg circle red 5px radius
|
||||
addCircle(parent, id, x, y, radius = 5, fill = 'red', stroke = 'red', stroke_width = '1', reference = 'true')
|
||||
#draw label 8px right and 8px above circle
|
||||
addText(parent, id + '_text', x + 8, y-8, id, fontsize = '30', textalign = 'left', textanchor = 'start', reference = 'true') #the id is used for two things here: the text object's id and the text object's content.
|
||||
return pnt # return python variable for use in remainder of pattern
|
||||
|
||||
def patternPoint(parent, id, pnt):
|
||||
"""Wrapper for patternPointXY. Accepts a Point object instead of X & Y values."""
|
||||
return patternPointXY(parent, id, pnt.x, pnt.y)
|
||||
|
||||
def controlPointXY(parent, id, x, y):
|
||||
'''Accepts parent, id, x, y. Returns object of class Point. Calls addPoint() & addText() to create a pattern point with label on canvas.'''
|
||||
# create python variable
|
||||
pnt = Point(id, x, y)
|
||||
# create unfilled grey circle 5px radius
|
||||
addCircle(parent, id, x, y, radius = 5, fill = 'none', stroke = 'gray', stroke_width = '1', reference = 'true')
|
||||
#draw label 8px right and 8px above circle
|
||||
addText(parent, id + '_text', x + 8, y-8, id, fontsize = '30', textalign = 'left', textanchor = 'start', reference = 'true') #the id is used twice: the text object id and the text object content.
|
||||
return pnt # return python variable for use in remainder of pattern
|
||||
|
||||
def controlPoint(parent, id, pnt):
|
||||
"""Wrapper for controlPointXY. Accepts a Point object instead of X & Y values."""
|
||||
return controlPointXY(parent, id, pnt.x, pnt.y)
|
||||
|
||||
def pointList( * args):
|
||||
"""Accepts list of args. Returns array of args."""
|
||||
list = []
|
||||
for arg in args:
|
||||
list.append(arg)
|
||||
return list
|
||||
|
||||
#---tests for position---
|
||||
def isRight(pnt1, pnt2):
|
||||
'''returns 1 if pnt2 is to the right of pnt1'''
|
||||
right = 0
|
||||
if pnt2.x > pnt1.x:
|
||||
right = 1
|
||||
return right
|
||||
|
||||
def isLeft(pnt1, pnt2):
|
||||
'''returns 1 if pnt2 is to the left of pnt1'''
|
||||
left = 0
|
||||
if pnt2.x < pnt1.x:
|
||||
left = 1
|
||||
return left
|
||||
|
||||
def isAbove(pnt1, pnt2):
|
||||
'''returns 1 if pnt2 is above pnt1'''
|
||||
up = 0
|
||||
if pnt2.y < pnt1.y:
|
||||
up = 1
|
||||
return up
|
||||
|
||||
def isBelow(pnt1, pnt2):
|
||||
'''returns 1 if pnt2 is below pnt1'''
|
||||
down = 0
|
||||
if pnt2.y > pnt1.y:
|
||||
down = 1
|
||||
return down
|
||||
|
||||
def lowest(pnts):
|
||||
"""Accepts array pnts[]. Returns lowest point in array."""
|
||||
low = Point('', pnts[0].x, pnts[0].y)
|
||||
for item in pnts:
|
||||
if isBelow(low, item): #if item is below current low
|
||||
updatePoint('', low, item)
|
||||
return low
|
||||
|
||||
def highest(pnts):
|
||||
"""Accepts array pnts[]. Returns highest point in array."""
|
||||
high = Point('', pnts[0].x, pnts[0].y)
|
||||
for item in pnts:
|
||||
if isAbove(high, item): #if item is above current high
|
||||
updatePoint(high, item)
|
||||
return high
|
||||
|
||||
def leftmost(pnts):
|
||||
"""Accepts array pnts[]. Returns leftmost point in array."""
|
||||
left = Point('', pnts[0].x, pnts[0].y)
|
||||
for item in pnts:
|
||||
if isLeft(left, item):
|
||||
updatePoint(left, item)
|
||||
return left
|
||||
|
||||
def rightmost(pnts):
|
||||
"""Accepts array pnts[]. Returns rightmost point in array."""
|
||||
right = Point('', pnts[0].x, pnts[0].y)
|
||||
for item in pnts:
|
||||
if isRight(right, item):
|
||||
updatePoint(right, item)
|
||||
return right
|
||||
|
||||
#---functions to calculate points. These functions do not create SVG objects---
|
||||
|
||||
def updatePoint(p1, p2):
|
||||
'''Accepts p1 and p2 of class Point. Updates p1 with x & y values from p2'''
|
||||
p1.x = p2.x
|
||||
p1.y = p2.y
|
||||
return
|
||||
|
||||
def right(p1, n):
|
||||
'''Accepts point p1 and float n. Returns (x,y) to the right of p1 at (p1.x + n, p1.y)'''
|
||||
return Point('', p1.x + n, p1.y)
|
||||
|
||||
def left(p1, n):
|
||||
'''Accepts point p1 and float n. Returns p2 to the left of p1 at (p1.x-n, p1.y)'''
|
||||
return Point('', p1.x-n, p1.y)
|
||||
|
||||
def up(p1, n):
|
||||
'''Accepts point p1 and float n. Returns p2 above p1 at (p1.x, p1.y-n)'''
|
||||
return Point('', p1.x, p1.y-n)
|
||||
|
||||
def down(p1, n):
|
||||
'''Accepts point p1 and float n. Returns p2 below p1 at (p1.x, p1.y + n)'''
|
||||
return Point('', p1.x, p1.y + n)
|
||||
|
||||
def symmetric(p1, p2, type = 'vertical'):
|
||||
"""
|
||||
Accepts p1 and p2 of class Point, and optional type is either 'vertical' or 'horizontal with default 'vertical'.
|
||||
Returns p3 of class Point as "mirror image" of p1 relative to p2
|
||||
If type == 'vertical': pnt is on opposite side of vertical line x = p2.x from p1
|
||||
If type == 'horizontal': pnt is on opposite side of horizontal line y = p2.y from p1
|
||||
"""
|
||||
p3 = Point()
|
||||
dx = p2.x - p1.x
|
||||
dy = p2.y - p1.y
|
||||
if (type == 'vertical'):
|
||||
p3.x = p2.x + dx
|
||||
p3.y = p1.y
|
||||
elif (type == 'horizontal'):
|
||||
p3.x = p1.x
|
||||
p3.y = p2.y + dy
|
||||
return p3
|
||||
|
||||
def polar(p1, length, angle):
|
||||
'''
|
||||
Adapted from http://www.teacherschoice.com.au/maths_library/coordinates/polar_-_rectangular_conversion.htm
|
||||
Accepts p1 as type Point, length as float, angle as float. angle is in radians
|
||||
Returns p2 as type Point, calculated at length and angle from p1,
|
||||
Angles start at position 3:00 and move clockwise due to y increasing downwards on Cairo Canvas
|
||||
'''
|
||||
id = ''
|
||||
r = length
|
||||
x = p1.x + (r * math.cos(angle))
|
||||
y = p1.y + (r * math.sin(angle))
|
||||
p2 = Point(id, x, y)
|
||||
return p2
|
||||
|
||||
def midPoint(p1, p2, n = 0.5):
|
||||
'''Accepts points p1 & p2, and n where 0 < n < 1. Returns point p3 as midpoint b/w p1 & p2'''
|
||||
p3 = Point('', (p1.x + p2.x) * n, (p1.y + p2.y) * n)
|
||||
return p3
|
||||
|
||||
|
||||
#---measurements
|
||||
def distance(p1, p2):
|
||||
'''Accepts two points p1 & p2. Returns the distance between p1 & p2.'''
|
||||
return ( ((p2.x-p1.x) ** 2) + ((p2.y-p1.y) ** 2) ) ** 0.5
|
||||
|
||||
def angleOfDegree(degree):
|
||||
'''Accepts degree. Returns radians.'''
|
||||
return degree * math.pi/180.0
|
||||
|
||||
def angleOfLine(p1, p2):
|
||||
""" Accepts points p1 & p2. Returns the angle of the vector between them. Uses atan2."""
|
||||
return math.atan2(p2.y-p1.y, p2.x-p1.x)
|
||||
|
||||
def angleOfVector(p1, v, p2):
|
||||
'''Accepts three points o1, v, and p2. Returns radians of the angle formed between the three points.'''
|
||||
return abs(angleOfLine(v, p1)-angleOfLine(v, p2))
|
||||
|
||||
def slopeOfLine(p1, p2):
|
||||
""" Accepts two point objects and returns the slope """
|
||||
if ((p2.x-p1.x) != 0):
|
||||
m = (p2.y-p1.y)/(p2.x-p1.x)
|
||||
else:
|
||||
#TODO: better error handling here
|
||||
debug('Vertical Line in slopeOfLine')
|
||||
m = None
|
||||
return m
|
||||
|
||||
def slopeOfAngle(radians):
|
||||
'''
|
||||
Accepts angle (radians)
|
||||
Returns the slope as tangent radians
|
||||
'''
|
||||
#get tangent of radians
|
||||
return math.tan(radians)
|
||||
|
||||
#---intersections & extensions
|
||||
|
||||
def extendLine(p1, p2, length, rotation=0):
|
||||
"""
|
||||
Accepts two directed points of a line, and a length to extend the line
|
||||
Finds point along line at length from p2 in direction p1->p2
|
||||
"""
|
||||
return onLineAtLength(p2, p1, -length)
|
||||
|
||||
def onLineAtLength(p1, p2, length, rotation=0):
|
||||
"""
|
||||
Accepts points p1 and p2, distance, and an optional rotation angle.
|
||||
Returns coordinate pair on the line at length measured from p1 towards p2
|
||||
If length is negative, will return a coordinate pair at length measured
|
||||
from p1 in opposite direction from p2.
|
||||
The result is optionally rotated about the first point by the rotation angle in degrees
|
||||
"""
|
||||
lineangle = angleOfLine(p1, p2)
|
||||
angle = lineangle + rotation * (math.pi/180)
|
||||
x = (length * math.cos(angle)) + p1.x
|
||||
y = (length * math.sin(angle)) + p1.y
|
||||
return Point('', x, y)
|
||||
|
||||
def onLineAtX(p1, p2, x):
|
||||
#on line p1-p2, given x find y
|
||||
if (p1.x == p2.x):# vertical line
|
||||
raise ValueError('Points form a vertical line, infinite answers possible')
|
||||
return None
|
||||
else:
|
||||
m = (p2.y - p1.y)/(p2.x - p1.x)
|
||||
b = p2.y - (m * p2.x)
|
||||
return Point('', x, (m * x) + b)
|
||||
|
||||
def onLineAtY(p1, p2, y):
|
||||
#on line p1-p2, find x given y
|
||||
if (p1.y == p2.y): #if horizontal line
|
||||
raise ValueError('Points form a horizontal line, infinite answers possible')
|
||||
return None
|
||||
elif (p1.x == p2.x): # if vertical line
|
||||
return Point('', p1.x, y)
|
||||
else:
|
||||
m = (p1.y - p2.y)/(p1.x - p2.x)
|
||||
b = p2.y - (m * p2.x)
|
||||
return Point('', (y - b)/m, y)
|
||||
|
||||
def intersectLines(p1, p2, p3, p4):
|
||||
"""
|
||||
Find intersection between two lines. Accepts p1, p2, p3, p4 as class Point. Returns p5 as class Point
|
||||
Intersection does not have to be within the supplied line segments
|
||||
"""
|
||||
x, y = 0.0, 0.0
|
||||
if (p1.x == p2.x): #if 1st line vertical, use slope of 2nd line
|
||||
x = p1.x
|
||||
m2 = slopeOfLine(p3, p4)
|
||||
b2 = p3.y-m2 * p3.x
|
||||
y = m2 * x + b2
|
||||
elif (p3.x == p4.x): #if 2nd line vertical, use slope of 1st line
|
||||
x = p3.x
|
||||
m1 = slopeOfLine(p1, p2)
|
||||
b1 = p1.y-m1 * p1.x
|
||||
y = m1 * x + b1
|
||||
else: #otherwise use ratio of difference between points
|
||||
m1 = (p2.y-p1.y)/(p2.x-p1.x)
|
||||
m2 = (p4.y-p3.y)/(p4.x-p3.x)
|
||||
b1 = p1.y-m1 * p1.x
|
||||
b2 = p3.y-m2 * p3.x
|
||||
#if (abs(b1-b2) < 0.01) and (m1 == m2):
|
||||
#x = p1.x
|
||||
#else:
|
||||
#x = (b2-b1)/(m1-m2)
|
||||
if (m1 == m2):
|
||||
#TODO: better error handling here
|
||||
debug(' ** ** * Parallel lines in intersectLines ** ** * ')
|
||||
else:
|
||||
x = (b2-b1)/(m1-m2)
|
||||
y = (m1 * x) + b1 # arbitrary choice, could have used m2 & b2
|
||||
p5 = Point("", x, y)
|
||||
return p5
|
||||
|
||||
def intersectLineRay(P1, P2, R1, angle):
|
||||
'''
|
||||
Accepts two points defining a line, and a point and angle defining a ray.
|
||||
Returns point where they intersect.
|
||||
'''
|
||||
#define a line R1-R2 by finding point R2 along ray 25 pixels (arbitary) from R1
|
||||
R2 = polar(R1, 1 * 25, angle)
|
||||
pnt = intersectLines(P1, P2, R1, R2)
|
||||
return pnt
|
||||
|
||||
def onRayAtX(P, angle, x):
|
||||
'''
|
||||
Accepts point P and angle of line.
|
||||
Returns point along ray at x
|
||||
'''
|
||||
#convert degrees to slope
|
||||
m = slopeOfAngle(angle)
|
||||
#solve for y
|
||||
#(P.y - y)/(P.x - x) = m
|
||||
y = P.y - m * (P.x - x)
|
||||
return Point('', x, y)
|
||||
|
||||
def onRayAtY(P, angle, y):
|
||||
'''
|
||||
Accepts point P and angle of line.
|
||||
Returns point along ray at y
|
||||
'''
|
||||
#convert degrees to slope
|
||||
m = slopeOfAngle(angle)
|
||||
#solve for x
|
||||
#(P.y - y)/(P.x - x) = m
|
||||
x = P.x - (P.y - y)/m
|
||||
return Point('', x, y)
|
||||
|
||||
def intersectCircles(C1, r1, C2, r2):
|
||||
"""
|
||||
Accepts C1, r1, C2, r2 where C1 & C2 are center points of each circle, and r1 & r2 are the radius of each circle.
|
||||
Returns an array of points of intersection.
|
||||
"""
|
||||
x0, y0 = C1.x, C1.y
|
||||
x1, y1 = C2.x, C2.y
|
||||
d = distance(C1, C2) # distance b/w circle centers
|
||||
dx, dy = (x1-x0), (y1-y0) # negate y b/c canvas increases top to bottom
|
||||
pnts = []
|
||||
if (d == 0):
|
||||
#intersections = 0
|
||||
#TODO: better error handling here
|
||||
debug('center of both circles are the same in intersectCircles()')
|
||||
debug('C1 = ', C1.x, C1.y, 'radius1 = ', r1)
|
||||
debug('C2 = ', C2.x, C2.y, 'radius1 = ', r2)
|
||||
return
|
||||
elif (d < abs(r1-r2)):
|
||||
#intersections = 0
|
||||
#TODO: better error handling here
|
||||
debug('one circle is within the other in intersectCircles()')
|
||||
debug('d = ', d)
|
||||
debug('r1 - r2 = ', (r1-r2))
|
||||
debug('d < abs(r1 - r2) ?', (d < abs(r1-r2)))
|
||||
debug('C1 = ', C1.x, C1.y, 'radius1 = ', r1)
|
||||
debug('C2 = ', C2.x, C2.y, 'radius1 = ', r2)
|
||||
return
|
||||
elif (d > (r1 + r2)):
|
||||
#intersections = 0
|
||||
#TODO: better error handling here
|
||||
debug('circles do not intersect in intersectCircles()')
|
||||
debug('d = ', d)
|
||||
debug('r1 + r2 = ', (r1 + r2))
|
||||
debug('d > abs(r1 + r2) ?', (d > abs(r1 + r2)))
|
||||
debug('C1 = ', C1.x, C1.y, 'radius1 = ', r1)
|
||||
debug('C2 = ', C2.x, C2.y, 'radius1 = ', r2)
|
||||
# TODO:possible kluge -check if this is acceptable using a small margin of error between r1 & r2 (0.5 * CM)?:
|
||||
#r2 = d-r1
|
||||
return
|
||||
else:
|
||||
#intersections = 2 or intersections = 1
|
||||
a = ((r1 * r1)-(r2 * r2) + (d * d))/(2.0 * d)
|
||||
x2 = x0 + (dx * a/d)
|
||||
y2 = y0 + (dy * a/d)
|
||||
h = math.sqrt((r1 * r1)-(a * a))
|
||||
rx = -dy * (h/d)
|
||||
ry = dx * (h/d)
|
||||
X1 = x2 + rx
|
||||
Y1 = y2 + ry
|
||||
X2 = x2-rx
|
||||
Y2 = y2-ry
|
||||
pnts.append(Point("", X1, Y1))
|
||||
pnts.append(Point("", X2, Y2))
|
||||
return pnts
|
||||
|
||||
def onCircleAtX(C, r, x):
|
||||
"""
|
||||
Finds points on circle where p.x=x
|
||||
Accepts C as an object of class Point or xy coords for the center of the circle,
|
||||
r as the radius, and x to find the points on the circle
|
||||
Returns an array P
|
||||
Based on paulbourke.net/geometry/sphereline/sphere_line_intersection.py
|
||||
"""
|
||||
#print 'C =', C.x, C.y
|
||||
#print 'r =', r
|
||||
#print 'x =', x
|
||||
P = []
|
||||
if abs(x - C.x) > r:
|
||||
print('abs(x - C.x) > r ...', abs(x - C.x), ' > ', r)
|
||||
print('x is outside radius of circle in intersections.onCircleAtX()')
|
||||
else:
|
||||
translated_x = x - C.x # center of translated circle is (0, 0) as translated_x is the difference b/w C.x & x
|
||||
translated_y1 = abs(math.sqrt(r**2 - translated_x**2))
|
||||
translated_y2 = -(translated_y1)
|
||||
y1 = translated_y1 + C.y # translate back to C.y
|
||||
y2 = translated_y2 + C.y # translate back to C.y
|
||||
P.append(Point('', x, y1))
|
||||
P.append(Point('', x, y2))
|
||||
return P
|
||||
|
||||
def onCircleAtY(C, r, y):
|
||||
"""
|
||||
Finds points one or two points on circle where P.y=y
|
||||
Accepts C of class Point or coords as circle center, r of type float as radius, and y of type float)
|
||||
Returns an array P
|
||||
Based on paulbourke.net/geometry/sphereline/sphere_line_intersection.py
|
||||
"""
|
||||
#print('C =', C.x, C.y)
|
||||
#print('r =', r)
|
||||
#print('x = ', y))
|
||||
P = []
|
||||
if abs(y - C.y) > r:
|
||||
print('abs(y - C.y) > r ...', abs(y - C.y), ' > ', r)
|
||||
print('y is outside radius in onCircleAtY() -- no intersection')
|
||||
else:
|
||||
translated_y = y - C.y
|
||||
translated_x1 = abs(math.sqrt(r**2 - translated_y**2))
|
||||
translated_x2 = -translated_x1
|
||||
x1 = translated_x1 + C.x
|
||||
x2 = translated_x2 + C.x
|
||||
P.append(Point('', x1, y))
|
||||
P.append(Point('', x2, y))
|
||||
return P
|
||||
|
||||
def intersectLineCircle(P1, P2, C, r):
|
||||
"""
|
||||
Finds intersection of a line segment and a circle.
|
||||
Accepts circle center point object C, radius r, and two line point objects P1 & P2
|
||||
Returns an array P with up to two coordinate pairs as P.intersections P[0] & P[1]
|
||||
Based on paulbourke.net/geometry/sphereline/sphere_line_intersection.py
|
||||
"""
|
||||
|
||||
#print('C =', C.x, C.y)
|
||||
#print('P1 =', P1.x, P1.y)
|
||||
#print('P2 =', P2.x, P2.y)
|
||||
#print('r =', r, 'pts', ', ', r / CM, 'cm')
|
||||
|
||||
p1, p2 = Point('', '', ''), Point('', '', '')
|
||||
P = []
|
||||
|
||||
if P1.x == P2.x: #vertical line
|
||||
if abs(P1.x - C.x) > r:
|
||||
print('no intersections for vertical line P1', P1.name, P1.x, P1.y, ', P2', P2.name, P2.x, P2.y, ', and Circle', C.name, C.x, C.y, ', radius', r)
|
||||
return None
|
||||
else:
|
||||
#print('Vertical line')
|
||||
p1.x = P1.x
|
||||
p2.x = P1.x
|
||||
p1.y = C.y + sqrt(r**2 - (P1.x - C.x)**2)
|
||||
p2.y = C.y - sqrt(r**2 - (P1.x - C.x)**2)
|
||||
elif P1.y == P2.y: #horizontal line
|
||||
if abs(P1.y-C.y) > r:
|
||||
print('no intersections for horizontal line P1', P1.name, P1.x, P1.y, ', P2', P2.name, P2.x, P2.y, ', and Circle', C.name, C.x, C.y, ', radius', r)
|
||||
return None
|
||||
else:
|
||||
#print('Horizontal line')
|
||||
p1.y = P1.y
|
||||
p2.y = P1.y
|
||||
p1.x = C.x + sqrt(r**2 - (P1.y - C.y)**2)
|
||||
p2.x = C.x - sqrt(r**2 - (P1.y - C.y)**2)
|
||||
else:
|
||||
a = (P2.x - P1.x)**2 + (P2.y - P1.y)**2
|
||||
b = 2.0 * ((P2.x - P1.x) * (P1.x - C.x)) + ((P2.y - P1.y) * (P1.y - C.y))
|
||||
c = C.x**2 + C.y**2 + P1.x**2 + P1.y**2 - (2.0 * (C.x * P1.x + C.y * P1.y)) - r**2
|
||||
i = b**2 - 4.0 * a * c
|
||||
if i < 0.0:
|
||||
print('no intersections b/w line', P1.name, P1.x, P1.y, '--', P2.name, P2.x, P2.y, 'and Circle', C.name, C.x, C.y, 'with radius', r)
|
||||
return None
|
||||
elif i == 0.0:
|
||||
# one intersection
|
||||
#print('one intersection')
|
||||
mu = -b/(2.0 * a)
|
||||
p1.x, p1.y = P1.x + mu * (P2.x - P1.x), P1.y + mu * (P2.y - P1.y)
|
||||
elif i > 0.0:
|
||||
# two intersections
|
||||
#print('two intersections')
|
||||
# first intersection
|
||||
mu1 = (-b + math.sqrt(i)) / (2.0*a)
|
||||
p1.x, p1.y = P1.x + mu1 * (P2.x - P1.x), P1.y + mu1 * (P2.y - P1.y)
|
||||
# second intersection
|
||||
mu2 = (-b - math.sqrt(i)) / (2.0*a)
|
||||
p2.x, p2.y = P1.x + mu2 * (P2.x - P1.x), P1.y + mu2 * (P2.y - P1.y)
|
||||
P.append(p1)
|
||||
P.append(p2)
|
||||
return P
|
||||
|
||||
def intersectChordCircle(C, r, P, chord_length):
|
||||
''' Accepts center of circle, radius of circle, a point on the circle, and chord length.
|
||||
Returns an array of two points on the circle at chord_length distance away from original point'''
|
||||
d = chord_length
|
||||
# point on circle given chordlength & starting point = 2 * asin(d/2r)
|
||||
d_div_2r = d/(2.0 * r)
|
||||
angle = 2 * asin(d_div_2r)
|
||||
pnts = []
|
||||
pnts.append(polar(C, r, angle))
|
||||
pnts.append(polar(C, r, - angle))
|
||||
return pnts
|
||||
|
||||
def intersectLineCurve(P1, P2, curve, n = 100):
|
||||
'''
|
||||
Accepts two points of a line P1 & P2, and an array of connected bezier curves [P11, C11, C12, P12, C21, C22, P22, C31, C32, P32, ...]
|
||||
Returns an array points_found[] of point objects where line intersected with the curve, and tangents_found[] of tangent angle at that point
|
||||
'''
|
||||
# get polar equation for line for P1-P2
|
||||
# point furthest away from 1st point in curve[] is the fixed point & sets the direction of the angle towards the curve
|
||||
#if distance(P1, curve[0]) > = distance(P2, curve[0] ):
|
||||
# fixed_pnt = P1
|
||||
# angle = angleOfLine(P1, P2)
|
||||
#else:
|
||||
# fixed_pnt = P2
|
||||
# angle = angleOfLine(P2, P1)
|
||||
#debug('intersectLineCurve...')
|
||||
#debug('....P1 = ' + P1.id + ' ' + str(P1.x) + ', ' + str(P1.y))
|
||||
#debug('....P2 = ' + P2.id + ' ' + str(P2.x) + ', ' + str(P2.y))
|
||||
#for pnt in curve:
|
||||
#debug( '....curve = ' + pnt.id + ' ' + str(pnt.x) + ', ' + str(pnt.y))
|
||||
fixed_pnt = P1
|
||||
angle = angleOfLine(P1, P2)
|
||||
intersections = 0
|
||||
points_found = []
|
||||
tangents_found = []
|
||||
pnt = Point()
|
||||
j = 0
|
||||
while j <= len(curve) -4: # for each bezier curve in curveArray
|
||||
intersection_estimate = intersectLines(P1, P2, curve[j], curve[j + 3]) # is there an intersection?
|
||||
if intersection_estimate != None or intersection_estimate != '':
|
||||
interpolatedPoints = interpolateCurve(curve[j], curve[j + 1], curve[j + 2], curve[j + 3], n) #interpolate this bezier curve, n = 100
|
||||
k = 0
|
||||
while k < len(interpolatedPoints)-1:
|
||||
length = distance(fixed_pnt, interpolatedPoints[k])
|
||||
pnt_on_line = polar(fixed_pnt, length, angle)
|
||||
range = distance(interpolatedPoints[k], interpolatedPoints[k + 1]) # TODO:improve margin of error
|
||||
length = distance(pnt_on_line, interpolatedPoints[k])
|
||||
#debug(str(k) + 'pntOnCurve = ' + \
|
||||
# str(interpolatedPoints[k].x) + ', ' + str(interpolatedPoints[k].y) + \
|
||||
# 'intersectLineAtLength = ' + str(pnt_on_line.x) + ', ' + str( pnt_on_line.y)\
|
||||
# + 'length = ' + str(length) + 'range = ' + str(range))
|
||||
if ( length <= range):
|
||||
#debug('its close enough!')
|
||||
if k > 1:
|
||||
if (interpolatedPoints[k-1] not in points_found) and (interpolatedPoints[k-2] not in points_found):
|
||||
points_found.append(interpolatedPoints[k])
|
||||
tangents_found.append(angleOfLine(interpolatedPoints[k-1], interpolatedPoints[k + 1]))
|
||||
intersections += 1
|
||||
elif k == 1:
|
||||
if (curve[0] not in intersections):
|
||||
points_found.append(interpolatedPoints[1])
|
||||
tangents_found.append(angleOfLine(curve[0], interpolatedPoints[2]))
|
||||
intersections += 1
|
||||
else:
|
||||
intersections.append(curve[0])
|
||||
tangents_found.append(angleOfLine(curve[0], curve[1]))
|
||||
k += 1
|
||||
j += 3 # skip j up to P3 of the current curve to be used as P0 start of next curve
|
||||
if intersections == 0:
|
||||
#TODO: better error handling here
|
||||
debug('no intersections found in intersectLineCurve(' + P1.id + ', ' + P2.id + ' and curve')
|
||||
#return points_found, tangents_found
|
||||
return points_found
|
||||
|
||||
def interpolateCurve(P0, C1, C2, P1, t = 100):
|
||||
'''
|
||||
Accepts curve points P0, C1, C2, P1 & number of interpolations t
|
||||
Returns array of interpolated points of class Point
|
||||
Adapted from http://www.planetclegg.com/projects/WarpingTextToSplines.htm
|
||||
'''
|
||||
# calculate coefficients for two knot points P0 & P1 ; C1 & C2 are the controlpoints.
|
||||
# x coefficients
|
||||
A = P1.x-(3 * C2.x) + (3 * C1.x)-P0.x
|
||||
B = (3 * C2.x)-(6 * C1.x) + (3 * P0.x)
|
||||
C = (3 * C1.x)-(3 * P0.x)
|
||||
D = P0.x
|
||||
# y coefficients
|
||||
E = P1.y-(3 * C2.y) + (3 * C1.y)-P0.y
|
||||
F = (3 * C2.y)-(6 * C1.y) + (3 * P0.y)
|
||||
G = (3 * C1.y)-(3 * P0.y)
|
||||
H = P0.y
|
||||
# calculate interpolated points
|
||||
interpolatedPoints = []
|
||||
maxPoint = float(t)
|
||||
i = 0
|
||||
while ( i <= t):
|
||||
j = i/maxPoint # j can't be an integer, i/t is an integer..always 0.
|
||||
x = A * (j ** 3) + B * (j ** 2) + C * j + D
|
||||
y = E * (j ** 3) + F * (j ** 2) + G * j + H
|
||||
interpolatedPoints.append(Point('', x, y))
|
||||
i += 1
|
||||
return interpolatedPoints
|
||||
|
||||
#---rotations
|
||||
def slashAndSpread(pivot, angle, *args):
|
||||
"""
|
||||
Accepts pivot point, angle of rotation, and the points to be rotated.
|
||||
Accepts positive & negative angles.
|
||||
"""
|
||||
if (angle == 0.0):
|
||||
print('Angle = 0 -- Slash and Spread not possible')
|
||||
else:
|
||||
list = []
|
||||
for arg in args:
|
||||
list.append(arg)
|
||||
i = 0
|
||||
for pnt in list:
|
||||
length = distance(pivot, pnt)
|
||||
rotated_pnt = polar(pivot, length, angleOfLine(pivot, pnt) + angle) # if angle > 0 spread clockwise. if angle < 0 spread counterclockwise
|
||||
updatePoint(pnt, rotated_pnt)
|
||||
return
|
||||
|
||||
#---darts
|
||||
def foldDart(dart, inside_pnt, seam_allowance):
|
||||
'''
|
||||
Accepts dart, and the nearest point in the direction dart will be folded
|
||||
Returns dart.m, dart.oc, dart.ic, dart.angle
|
||||
dart.m = middle dart leg at seamline (to be included in seamline path)
|
||||
dart.oc = inside dart leg at cuttingline (to be included in dartline path)
|
||||
dart.oc = outside dart leg at cuttingline (to be included in dartline path)
|
||||
'''
|
||||
mid_pnt = midPoint(dart.i, dart.o)
|
||||
dart_length = distance(dart, dart.i)
|
||||
i_angle = angleOfLine(dart, dart.i)
|
||||
c_angle = angleOfLine(dart, inside_pnt)
|
||||
dart_angle = abs(angleOfVector(dart.i, dart, dart.o))
|
||||
dart_half_angle = dart_angle/2.0
|
||||
|
||||
#determine which direction the dart will be folded
|
||||
#if ((dart.i.x > dart.x) and (dart.i.y > dart.y)) or ((dart.i.x < dart.x) and (dart.i.y > dart.y)):
|
||||
#x & y vectors not the same sign
|
||||
#dart_half_angle = -dart_half_angle
|
||||
if i_angle > c_angle:
|
||||
dart_half_angle = -dart_half_angle
|
||||
elif dart_angle < c_angle:
|
||||
#dart straddles 0 angle
|
||||
dart_half_angle = -dart_half_angle
|
||||
|
||||
fold_angle = i_angle + dart_half_angle
|
||||
fold_pnt = intersectLineRay(dart.i, inside_pnt, dart, fold_angle)
|
||||
dart.m = onLineAtLength(dart, mid_pnt, distance(dart, fold_pnt)) #dart midpoint at seamline
|
||||
dart.oc = polar(dart, distance(dart, dart.o) + seam_allowance, angleOfLine(dart, dart.o)) #dart outside leg at cuttingline
|
||||
dart.ic = extendLine(dart, dart.i, seam_allowance) #dart inside leg at cuttingline
|
||||
#create or update dart.angles
|
||||
dart.angle = angleOfVector(dart.i, dart, dart.o)
|
||||
return
|
||||
|
||||
#---base, pattern & patternpiece groups
|
||||
def base(parent, id):
|
||||
'''Create a base group to contain all patterns, parent should be the document'''
|
||||
newBase = addGroup(parent, id)
|
||||
newBase.set(inkex.addNS('label', 'inkscape'), id)
|
||||
newBase.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
|
||||
return newBase
|
||||
|
||||
def pattern(parent, id):
|
||||
'''Create a pattern group to hold a single pattern, parent should be the base group'''
|
||||
newPattern = addGroup(parent, id)
|
||||
newPattern.set(inkex.addNS('label', 'inkscape'), id)
|
||||
newPattern.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
|
||||
return newPattern
|
||||
|
||||
def patternPiece(parent, id, name, fabric = 2, interfacing = 0, lining = 0):
|
||||
'''Create a pattern piece group to hold a single pattern piece, parent should be a pattern group'''
|
||||
newPatternPiece = addGroup(parent, id)
|
||||
newPatternPiece.set(inkex.addNS('label', 'inkscape'), name)
|
||||
newPatternPiece.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
|
||||
return newPatternPiece
|
||||
|
||||
#---svg
|
||||
|
||||
def addCircle(parent, id, x, y, radius = 5, fill = 'red', stroke = 'red', stroke_width = '1', reference = 'false'):
|
||||
'''create & write a circle to canvas & set it's attributes'''
|
||||
circ = etree.SubElement(parent, inkex.addNS('circle', 'svg'))
|
||||
circ.set('id', id)
|
||||
circ.set('cx', str(x))
|
||||
circ.set('cy', str(y))
|
||||
circ.set('r', str(radius))
|
||||
circ.set('fill', fill)
|
||||
circ.set('stroke', stroke)
|
||||
circ.set('stroke-width', stroke_width)
|
||||
if reference == 'true':
|
||||
circ.attrib['reference'] = 'true'
|
||||
return
|
||||
|
||||
def addSquare(parent, id, w, h, x, y, reference='false'):
|
||||
# create & write a square element, set its attributes
|
||||
square = etree.SubElement(parent, inkex.addNS('rect', 'svg'))
|
||||
square.set('id', id)
|
||||
square.set('width', str(w))
|
||||
square.set('height', str(h))
|
||||
square.set('x', str(x))
|
||||
square.set('y', str(y))
|
||||
square.set('stroke', 'none')
|
||||
square.set('fill', '#000000')
|
||||
square.set('stroke-width', '1')
|
||||
if (reference == 'true'):
|
||||
square.attrib['reference'] = 'true'
|
||||
return
|
||||
|
||||
def addText(parent, id, x, y, text, fontsize = '12', textalign = 'left', textanchor = 'start', reference = 'false'):
|
||||
'''Create a text element, set its attributes, then write to canvas.
|
||||
The text element is different than other elements -- > Set attributes first then write to canvas using append method.
|
||||
There is no etree.SubElement() method for creating a text element & placing it into the document in one step.
|
||||
Use inkex's etree.Element() method to create an unattached text svg object,
|
||||
then use a document object's append() method to place it on the document canvas'''
|
||||
#create a text element with inkex's Element()
|
||||
txt = etree.Element(inkex.addNS('text', 'svg'))
|
||||
#set attributes of the text element
|
||||
txt.set('id', id)
|
||||
txt.set('x', str(x))
|
||||
txt.set('y', str(y))
|
||||
txt.text = text
|
||||
style = {'text-align':textalign, 'text-anchor':textanchor, 'font-size':fontsize}
|
||||
txt.set('style', str(inkex.Style(style)))
|
||||
if reference == 'true':
|
||||
txt.attrib['reference'] = 'true'
|
||||
#txt.setAttribute('reference', 'true') #alternative syntax
|
||||
#add to canvas in the parent group
|
||||
parent.append(txt)
|
||||
return
|
||||
|
||||
def addLayer(parent, id):
|
||||
'''Create & write an inkscape group-layer to canvas'''
|
||||
new_layer = etree.SubElement(parent, 'g')
|
||||
new_layer.set('id', id)
|
||||
new_layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
|
||||
new_layer.set(inkex.addNS('label', 'inkscape'), '%s layer' % (id))
|
||||
return new_layer
|
||||
|
||||
def addGroup(parent, id):
|
||||
'''Create & write an svg group to canvas'''
|
||||
new_layer = etree.SubElement(parent, 'g')
|
||||
new_layer.set('id', id)
|
||||
return new_layer
|
||||
|
||||
def addPath(parent, id, path_str, path_type):
|
||||
'''Accepts parent, id, path string, path type. Creates attribute dictionary. Creates & writes path.'''
|
||||
reference = 'false'
|
||||
if path_type == 'seamline':
|
||||
path_style = {'stroke':'green', 'stroke-width':'4', 'fill':'none', 'opacity':'1', 'stroke-dasharray':'15, 5', 'stroke-dashoffset':'0'}
|
||||
elif path_type == 'cuttingline':
|
||||
path_style = {'stroke':'green', 'stroke-width':'4', 'fill':'none', 'opacity':'1'}
|
||||
elif path_type == 'gridline':
|
||||
path_style = {'stroke':'gray', 'stroke-width':'4', 'fill':'none', 'opacity':'1', 'stroke-dasharray':'6, 6', 'stroke-dashoffset':'0'}
|
||||
reference = 'true'
|
||||
elif path_type == 'dartline':
|
||||
path_style = {'stroke':'gray', 'stroke-width':'4', 'fill':'none', 'opacity':'1'}
|
||||
elif path_type == 'grainline':
|
||||
path_style = {'stroke':'DimGray', 'stroke-width':'3', 'fill':'none', 'opacity':'1', \
|
||||
'marker-start':'url(#ArrowStart)', 'marker-end':'url(#ArrowEnd)'}
|
||||
elif path_type == 'slashline':
|
||||
path_style = {'stroke':'green', 'stroke-width':'4', 'fill':'none', 'opacity':'1'}
|
||||
svg_path = etree.SubElement(parent, inkex.addNS('path', 'svg'))
|
||||
svg_path.set('id', id)
|
||||
svg_path.set('d', path_str)
|
||||
svg_path.set('style', str(inkex.Style(path_style)))
|
||||
if reference == 'true':
|
||||
svg_path.attrib['reference'] = 'true'
|
||||
return svg_path
|
||||
|
||||
def formatPath( * args):
|
||||
"""
|
||||
Accepts a series of pseudo svg path arguments 'M', 'L', 'C' , and point objects.
|
||||
Returns path_string which is a string formatted for use as the 'd' path attribute in an svg object.
|
||||
"""
|
||||
tokens = [] # initialize an empty array
|
||||
# put all the parameters in * args into the array
|
||||
for arg in args:
|
||||
tokens.append(arg)
|
||||
com = ', '
|
||||
path_string = ''
|
||||
i = 0
|
||||
while (i < len(tokens)):
|
||||
cmd = tokens[i]
|
||||
if (cmd == 'M') or (cmd == 'L'):
|
||||
path_string += " %s %g %g" % (cmd, tokens[i + 1].x, tokens[i + 1].y)
|
||||
i = i + 2
|
||||
elif (cmd == 'C'):
|
||||
path_string += " %s %g %g %g %g %g %g" % (cmd, tokens[i + 1].x, \
|
||||
tokens[i + 1].y, tokens[i + 2].x, tokens[i + 2].y, tokens[i + 3].x, tokens[i + 3].y)
|
||||
i = i + 4
|
||||
return path_string
|
||||
|
||||
def addDefs(doc):
|
||||
'''Add defs group with markers to the document'''
|
||||
defs = etree.SubElement(doc, inkex.addNS('defs', 'svg'))
|
||||
#add start arrow
|
||||
marker = etree.SubElement(defs, 'marker', {'id':'ArrowStart', 'orient':'auto', 'refX':'0.0', 'refY':'0.0', 'style':'overflow:visible'})
|
||||
etree.SubElement(marker, 'path', {'d':'M 0, 0 L 0, 5 L -20, 0 L 0, -5 z', 'style':'fill:DimGray; stroke:DimGray; stroke-width:0.5'})
|
||||
#add end arrow
|
||||
marker = etree.SubElement(defs, 'marker', {'id':'ArrowEnd', 'orient':'auto', 'refX':'0.0', 'refY':'0.0', 'style':'overflow:visible'})
|
||||
etree.SubElement(marker, 'path', {'d':'M 0, 0 L 0, 5 L 20, 0 L 0, -5 z', 'style':'fill:DimGray; stroke:DimGray; stroke-width:0.5'})
|
33
extensions/fablabchemnitz/shirt_waist/shirt_waist.inx
Normal file
33
extensions/fablabchemnitz/shirt_waist/shirt_waist.inx
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Shirt Waist (Sara May Allington)</name>
|
||||
<id>fablabchemnitz.de.shirt_waist</id>
|
||||
<param name="m_unit" type="optiongroup" appearance="combo" gui-text="Select measurement: ">
|
||||
<option value="Inches">Inches</option>
|
||||
<option value="Centimeters">Centimeters</option>
|
||||
</param>
|
||||
<param name="m_front_waist_length" type="float" min="1.0" max="100.0" gui-text="Front Waist Length">15.0</param>
|
||||
<param name="m_back_waist_length" type="float" min="1.0" max="100.0" gui-text="Back Waist Length">15.5</param>
|
||||
<param name="m_neck_circumference" type="float" min="1.0" max="100.0" gui-text="Neck Circumference">13.5</param>
|
||||
<param name="m_bust_circumference" type="float" min="1.0" max="100.0" gui-text="Bust Circumference">39.0</param>
|
||||
<param name="m_waist_circumference" type="float" min="1.0" max="100.0" gui-text="Waist Circumference">25.0</param>
|
||||
<param name="m_armscye_circumference" type="float" min="1.0" max="100.0" gui-text="Armscye Circumference">15.0</param>
|
||||
<param name="m_across_back" type="float" min="1.0" max="100.0" gui-text="Across Back">13.5</param>
|
||||
<param name="m_side" type="float" min="1.0" max="100.0" gui-text="Side">7.75</param>
|
||||
<param name="m_upper_front_height" type="float" min="1.0" max="100.0" gui-text="Upper Front Height">10.75</param>
|
||||
<param name="m_overarm_length" type="float" min="1.0" max="100.0" gui-text="Overarm Length">20.00</param>
|
||||
<param name="m_elbow_height" type="float" min="1.0" max="100.0" gui-text="Elbow height - from wrist to elbow">9.50</param>
|
||||
<param name="m_elbow_circumference" type="float" min="1.0" max="100.0" gui-text="Elbow Circumference">12.50</param>
|
||||
<param name="m_hand_circumference" type="float" min="1.0" max="100.0" gui-text="Hand Circumference">8.00</param>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz">
|
||||
<submenu name="Dimensioning/Measuring"/>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">shirt_waist.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
459
extensions/fablabchemnitz/shirt_waist/shirt_waist.py
Normal file
459
extensions/fablabchemnitz/shirt_waist/shirt_waist.py
Normal file
@ -0,0 +1,459 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# shirt_waist_allington.py
|
||||
# Inkscape extension-Effects-Sewing Patterns-Shirt Waist Allington
|
||||
# Copyright (C) 2010, 2011, 2012 Susan Spencer, Steve Conklin <www.taumeta.org>
|
||||
|
||||
'''
|
||||
Licensing paragraph:
|
||||
|
||||
1. CODE LICENSE: GPL 2.0+
|
||||
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
|
||||
|
||||
2. PATTERN LICENSE: CC BY-NC 3.0
|
||||
The output of this code is a pattern and is considered a
|
||||
visual artwork. The pattern is licensed under
|
||||
Attribution-NonCommercial 3.0 (CC BY-NC 3.0)
|
||||
<http://creativecommons.org/licenses/by-nc/3.0/>
|
||||
Items made from the pattern may be sold;
|
||||
the pattern may not be sold.
|
||||
|
||||
End of Licensing paragraph.
|
||||
'''
|
||||
|
||||
import math, inkex
|
||||
from sewing_patterns import *
|
||||
|
||||
class ShirtWaist(inkex.EffectExtension):
|
||||
|
||||
def add_arguments(self, pars):
|
||||
pars.add_argument('--m_unit', default = 'Inches', help = 'Centimeters or Inches?')
|
||||
pars.add_argument('--m_front_waist_length', type = float, default = '15.0', help = 'Front Waist Length')
|
||||
pars.add_argument('--m_back_waist_length', type = float, default = '15.5', help = 'Back Waist Length')
|
||||
pars.add_argument('--m_neck_circumference', type = float, default = '13.5', help = 'Neck Circumference')
|
||||
pars.add_argument('--m_bust_circumference', type = float, default = '39.0', help = 'Bust Circumference')
|
||||
pars.add_argument('--m_waist_circumference', type = float, default = '25.0', help = 'Waist Circumference')
|
||||
pars.add_argument('--m_armscye_circumference', type = float, default = '15.0', help = 'Armscye circumference')
|
||||
pars.add_argument('--m_across_back', type = float, default = '13.5', help = 'Across Back')
|
||||
pars.add_argument('--m_shoulder', type = float, default = '6.5', help = 'Shoulder')
|
||||
pars.add_argument('--m_side', type = float, default = '7.75', help = 'Side')
|
||||
pars.add_argument('--m_upper_front_height', type = float, default = '10.75', help = 'Upper Front Height')
|
||||
pars.add_argument('--m_overarm_length', type = float, default = '20.00', help = 'Overarm Length')
|
||||
pars.add_argument('--m_elbow_height', type = float, default = '9.50', help = 'Elbow Height - from wrist to elbow')
|
||||
pars.add_argument('--m_elbow_circumference', type = float, default = '12.50', help = 'Elbow Circumference - arm bent')
|
||||
pars.add_argument('--m_hand_circumference', type = float, default = '8.00', help = 'Hand Circumference')
|
||||
|
||||
def effect(self):
|
||||
|
||||
def printPoint(pnt):
|
||||
debug(' %s = %f, %f')%pnt.id, pnt.x, pnt.y
|
||||
|
||||
INCH_to_PX = 96.0 #inkscape 1.0 uses 96 pixels per 1 inch
|
||||
CM_to_INCH = 1/2.54
|
||||
CM_to_PX = CM_to_INCH*INCH_to_PX
|
||||
CM = CM_to_PX # CM - shorthand when using centimeters
|
||||
IN = INCH_to_PX # IN - shorthand when using inches
|
||||
|
||||
#all measurements must be converted to px
|
||||
munit = self.options.m_unit
|
||||
if munit == 'Centimeters':
|
||||
MEASUREMENT_CONVERSION = CM
|
||||
else:
|
||||
MEASUREMENT_CONVERSION = IN
|
||||
|
||||
#convert measurements
|
||||
front_waist_length = self.options.m_front_waist_length * MEASUREMENT_CONVERSION
|
||||
neck_circumference = self.options.m_neck_circumference * MEASUREMENT_CONVERSION
|
||||
bust_circumference = self.options.m_bust_circumference * MEASUREMENT_CONVERSION
|
||||
waist_circumference = self.options.m_waist_circumference * MEASUREMENT_CONVERSION
|
||||
armscye_circumference = self.options.m_armscye_circumference * MEASUREMENT_CONVERSION
|
||||
across_back = self.options.m_across_back * MEASUREMENT_CONVERSION
|
||||
shoulder = self.options.m_shoulder * MEASUREMENT_CONVERSION
|
||||
side = self.options.m_side * MEASUREMENT_CONVERSION
|
||||
upper_front_height = self.options.m_upper_front_height * MEASUREMENT_CONVERSION
|
||||
overarm_length = self.options.m_overarm_length * MEASUREMENT_CONVERSION
|
||||
elbow_height = self.options.m_elbow_height * MEASUREMENT_CONVERSION
|
||||
elbow_circumference = self.options.m_elbow_circumference * MEASUREMENT_CONVERSION
|
||||
hand_circumference = self.options.m_hand_circumference * MEASUREMENT_CONVERSION
|
||||
|
||||
#constants
|
||||
ANGLE90 = angleOfDegree(90)
|
||||
ANGLE180 = angleOfDegree(180)
|
||||
SEAM_ALLOWANCE = (5/8.0)*IN
|
||||
BORDER = 1*IN
|
||||
NOTE_HEIGHT = 1*IN
|
||||
|
||||
#get the document, set initial width & height
|
||||
doc = self.document.getroot() # self.document is the canvas seen in Inkscape
|
||||
#add defs group with markers to document
|
||||
addDefs(doc)
|
||||
|
||||
#width_orig = inkex.unittouu(doc.get('width'))
|
||||
#height_orig = inkex.unittouu(doc.get('height'))
|
||||
#doc_width = 4*BORDER+4*SEAM_ALLOWANCE + bust_circumference/2.0
|
||||
#doc_height = 2*BORDER+3*SEAM_ALLOWANCE+(upper_front_height+side)
|
||||
#doc.set('width', str(doc_width)) #temporary document width, doc is resized near end of pattern file
|
||||
#doc.set('height', str(doc_height)) #temporary document height, doc is resized near end of pattern file
|
||||
|
||||
|
||||
#create a base group in the document to hold all patterns
|
||||
pattern_base = base(doc, 'pattern_base')
|
||||
#create a pattern group for each pattern, put pattern group in base group - there can be multiple patterns
|
||||
bodice = pattern(pattern_base, 'bodice')
|
||||
# create a group for each pattern piece, put pattern piece group in pattern group
|
||||
A = patternPiece(bodice, 'A', 'bodice_front', fabric = 2, interfacing = 0, lining = 0)
|
||||
B = patternPiece(bodice, 'B', 'bodice_back', fabric = 2, interfacing = 0, lining = 0)
|
||||
C = patternPiece(bodice, 'C', 'bodice_sleeve', fabric = 2, interfacing = 0, lining = 0)
|
||||
D = patternPiece(bodice, 'D', 'bodice_cuff', fabric = 2, interfacing = 0, lining = 0)
|
||||
|
||||
#pattern notes
|
||||
notes = []
|
||||
notes.append('Define Seam Allowances: Select File/Inkscape Preferences/Steps and set Outset to 56.25px (5/8" seam allowance)')
|
||||
notes.append('Create Seam Allowances: Press CTRL-F, type "cuttingline" in the ID field, click the Find button, press CTRL-)')
|
||||
notes.append('Remove Points & Gridlines: Press CTRL-F, type "reference" in the Attribute field, click Find button, press DELETE')
|
||||
notes.append('Print: Save as a PDF file, open PDF with PDF viewer (Adobe, Evince, Okular, xPDF), print from Print Preview option')
|
||||
|
||||
#pattern points
|
||||
b1 = patternPointXY(B, 'b1', 0, 0) #B
|
||||
b2 = patternPoint(B, 'b2', down(b1, front_waist_length)) #A
|
||||
b3 = patternPoint(B, 'b3', up(b2, side)) #C
|
||||
a1 = patternPoint(A, 'a1', left(b3, bust_circumference/2.0)) #D
|
||||
b4 = patternPoint(B, 'b4', left(b3, across_back/2.0)) #E
|
||||
b5 = patternPoint(B, 'b5', up(b4, armscye_circumference/3.0)) #F
|
||||
b6 = patternPoint(B, 'b6', up(b1, 0.5*IN)) #G
|
||||
b7 = patternPoint(B, 'b7', left(b6, 1.5*IN)) #H
|
||||
b8 = patternPoint(B, 'b8', onLineAtLength(b5, b7, -0.5*IN)) #I
|
||||
a2 = patternPoint(A, 'a2', left(b4, armscye_circumference/4.0)) #J
|
||||
a3 = patternPoint(A, 'a3', midPoint(a2, b4)) #K
|
||||
a4 = patternPoint(A, 'a4', up(a2, 2.5*IN)) #L
|
||||
a5 = patternPoint(A, 'a5', up(b5, 1.5*IN)) #M
|
||||
a6 = patternPoint(A, 'a6', left(a5, 2*IN)) #N
|
||||
a7 = patternPoint(A, 'a7', left(a6, distance(b7, b8))) #O
|
||||
a8 = patternPointXY(A, 'a8', a7.x, b3.y - (upper_front_height - distance(b1, b7))) #P
|
||||
a9 = patternPoint(A, 'a9', down(a8, neck_circumference/4.0)) #Q
|
||||
a10 = patternPoint(A, 'a10', up(a9, 0.5*IN)) #R
|
||||
a11 = patternPoint(A, 'a11', left(a10, (neck_circumference/6.0)+0.25*IN )) #S
|
||||
b9 = patternPoint(B, 'b9', midPoint(a3, b4)) #T on back bodice B
|
||||
a12 = patternPoint(A, 'a12', b9) #T on front bodice A
|
||||
b10 = patternPoint(B, 'b10', down(b9, side)) #U
|
||||
b11 = patternPoint(B , 'b11', right(b10, 1*IN)) #V
|
||||
a13 = patternPoint(A, 'a13', left(b10, 1*IN)) #W
|
||||
a14 = patternPoint(A, 'a14', onLineAtLength(a11, a1, front_waist_length)) #X
|
||||
a15 = patternPoint(A, 'a15', down(a8, distance(a8, a14))) #Y - new point at front waist
|
||||
b12 = patternPoint(B, 'b12', up(b4, distance(b5, b4)/3.0)) #Z - new point at back armscye
|
||||
#temporary armscye curve from a3 to b12 to find top point of side seam
|
||||
length = distance(a3, b12)/3.0
|
||||
temp_b12_c1 = right(a3, length) #don't create an svg controlpoint circle for this point
|
||||
temp_b12_c2 = down(b12, length) #or for this point
|
||||
#find top point of side seam with intersection of side and armscye curve, save to two points a16 and b13
|
||||
curve1 = pointList(a3, temp_b12_c1, temp_b12_c2, b12)
|
||||
intersections = intersectLineCurve(b10, b9, curve1) #this line is directional from b10 to b9
|
||||
b13 = patternPoint(B, 'b13', intersections[0]) # AA on bodice back B -use 1st intersection found, in this case there's only one intersection
|
||||
a16 = patternPoint(A, 'a16', b13) #AA on bodice back A
|
||||
|
||||
#front control points - path runs counterclockwise from front neck center a11
|
||||
#front neck control points from a8 to a11
|
||||
length = distance(a8, a11)/3.0
|
||||
a11.c2 = controlPoint(A, 'a11.c2', right(a11, 1.5*length))
|
||||
a11.c1 = controlPoint(A, 'a11.c1', polar(a8, length, angleOfLine(a8, a11.c2)))
|
||||
#front waist control points from a14 to a15
|
||||
length = distance(a14, a15)/3.0
|
||||
a15.c1 = controlPoint(A, 'a15.c1', polar(a14, length, angleOfLine(a14, a11)+ANGLE90)) #control handle line is perpendicular to line a14-a11
|
||||
a15.c2 = controlPoint(A, 'a15.c2', left(a15, length))
|
||||
#front waist control points from a15 to a13
|
||||
length = distance(a15, a13)/3.0
|
||||
a13.c1 = controlPoint(A, 'a13.c1', right(a15, 1.5*length))
|
||||
a13.c2 = controlPoint(A, 'a13.c2', polar(a13, length, angleOfLine(a13, a13.c1))) #second control aimed at first control point
|
||||
#front side control points from a13 to a12
|
||||
length = distance(a13, a12)/3.0
|
||||
a12.c1 = controlPoint(A, 'a12.c1', up(a13, length))
|
||||
a12.c2 = controlPoint(A, 'a12.c2', down(a12, length))
|
||||
#front armscye control points from a16 to a3 to a4 to 16
|
||||
length1 = distance(a16, a3)/3.0
|
||||
length2 = distance(a3, a4)/3.0
|
||||
length3 = distance(a4, a6)/3.0
|
||||
angle1 = angleOfLine(a16, a3)
|
||||
angle2 = ANGLE180
|
||||
angle3 = (angle1+angle2)/2.0
|
||||
a3.c1 = controlPoint(A, 'a3.c1', polar(a16, length1, angle1))
|
||||
a3.c2 = controlPoint(A, 'a3.c2', polar(a3, length1, angle3-ANGLE180))
|
||||
a4.c1 = controlPoint(A, 'a4.c1', polar(a3, length2, angle3))
|
||||
angle4 = angleOfLine(a3, a6)
|
||||
angle5 = angleOfLine(a4, a6)
|
||||
angle6 = (angle4+angle5)/2.0
|
||||
a4.c2 = controlPoint(A, 'a4.c2', polar(a4, 1.5*length2, angle6-ANGLE180))
|
||||
a6.c1 = controlPoint(A, 'a6.c1', polar(a4, length3, angle6))
|
||||
a6.c2 = controlPoint(A, 'a6.c2', polar(a6, length3/2.0, angleOfLine(a8, a6)+ANGLE90))
|
||||
|
||||
#back control points - path runs clockwise from back nape b1
|
||||
#back neck control points from b7 to b1
|
||||
length = distance(b7, b1)/3.0
|
||||
b1.c1 = controlPoint(B, 'b1.c1', down(b7, length/2.0)) #short control point handle
|
||||
b1.c2 = controlPoint(B, 'b1.c2', left(b1, length*2)) #long control point handle
|
||||
#back side control points from b11 to b9
|
||||
length = distance(b11, b9)/3.0
|
||||
b9.c1 = controlPoint(B, 'b9.c1', up(b11, length))
|
||||
b9.c2 = controlPoint(B, 'b9.c2', down(b9, length))
|
||||
#back armscye points from b13 to b12 to b8
|
||||
length1 = distance(b13, b12)/3.0
|
||||
length2 = distance(b12, b8)/3.0
|
||||
angle1 = angleOfLine(b13, b8)
|
||||
b12.c1 = controlPoint(B, 'b12.c1', polar(b13, length1, angleOfLine(a3.c1, a16)))
|
||||
b12.c2 = controlPoint(B, 'b12.c2', polar(b12, length1, angle1-ANGLE180))
|
||||
b8.c1 = controlPoint(B, 'b8.c1', polar(b12, length2, angle1))
|
||||
b8.c2 = controlPoint(B, 'b8.c2', polar(b8, length2/2.0, angleOfLine(b7, b8)-ANGLE90))
|
||||
|
||||
#sleeve C
|
||||
c1 = patternPointXY(C, 'c1', 0.0, 0.0) #A
|
||||
c2 = patternPoint(C, 'c2', down(c1, overarm_length)) #B
|
||||
c3 = patternPoint(C, 'c3', up(c2, elbow_height)) #C
|
||||
c4 = patternPoint(C, 'c4', right(c2, 1*IN)) #D
|
||||
c5 = patternPoint(C, 'c5', right(c3, 0.5*IN)) #E
|
||||
c6 = patternPoint(C, 'c6', left(c1, 1*IN)) #F
|
||||
c7 = patternPoint(C, 'c7', right(c4, 1*IN)) #G
|
||||
c8 = patternPoint(C, 'c8', right(c7, hand_circumference+2*IN)) #H
|
||||
c9 = patternPoint(C, 'c9', right(c8, 1*IN)) #I
|
||||
c10 = patternPoint(C, 'c10', right(c5, 1*IN) )#J
|
||||
c11 = patternPoint(C, 'c11', right(c10, elbow_circumference)) #K
|
||||
c12 = patternPoint(C, 'c12', right(c11, 0.5*IN)) #L
|
||||
c13 = patternPoint(C, 'c13', right(c1, armscye_circumference)) #M
|
||||
c14 = patternPoint(C, 'c14', right(c13, 2*IN)) #N
|
||||
c15 = patternPoint(C, 'c15', up(c1, 2.5*IN)) #O
|
||||
c16 = patternPoint(C, 'c16', right(c1, 1.5*IN)) #P
|
||||
c17 = patternPoint(C, 'c17', left(c13, 3*IN)) #Q
|
||||
c18 = patternPointXY(C, 'c18', c16.x, c15.y) #R
|
||||
c19 = patternPointXY(C, 'c19', c17.x, c15.y) #S
|
||||
c20 = patternPoint(C, 'c20', midPoint(c16, c17)) #T
|
||||
c21 = patternPoint(C, 'c21', up(c20, distance(c20, c18))) #U - above T
|
||||
c22 = patternPoint(C, 'c22', down(midPoint(c7, c8), 0.75*IN)) #V - was U
|
||||
c23 = patternPoint(C, 'c23', right(c4, distance(c4, c8)*3/5.0)) #W
|
||||
c24 = patternPoint(C, 'c24', up(c23, distance(c4, c3)/3.0)) #X - was V
|
||||
c25 = patternPoint(C, 'c25', down(c23, 0.75*IN)) #Y - new point
|
||||
# sleeve C control points
|
||||
# sleevecap c6 to c18 to c21 to c19 to c13 to c14
|
||||
length1 = distance(c6, c18)/3.0
|
||||
length2 = distance(c18, c21)/3.0
|
||||
c21.c2 = controlPoint(C, 'c21.c2', left(c21, length2))
|
||||
c21.c1 = controlPoint(C, 'c21.c1', polar(c18, length2, angleOfLine(c18, c21.c2)))
|
||||
angle = angleOfLine(c6, c18)+angleOfVector(c18, c6, c1)/2.0
|
||||
c18.c1 = controlPoint(C, 'c18.c1', polar(c6, length1, angle))
|
||||
c18.c2 = controlPoint(C, 'c18.c2', polar(c18, length1, angleOfLine(c21.c1, c18)))
|
||||
length1 = distance(c21, c19)/3.0
|
||||
length2 = distance(c19, c13)/3.0
|
||||
length3 = distance(c13, c14)/3.0
|
||||
c19.c1 = controlPoint(C, 'c19.c1', right(c21, length1))
|
||||
c19.c2 = controlPoint(C, 'c19.c2', polar(c19, length1, angleOfLine(c19, c19.c1)))
|
||||
c13.c1 = controlPoint(C, 'c13.c1', polar(c19, length2, angleOfLine(c19.c2, c19)))
|
||||
angle1 = angleOfLine(c13.c1, c13)/2.0
|
||||
c13.c2 = controlPoint(C, 'c13.c2', polar(c13, length2, angle1+ANGLE180))
|
||||
c14.c1 = controlPoint(C, 'c14.c1', polar(c13, length3, angle1))
|
||||
c14.c2 = controlPoint(C, 'c14.c2', polar(c14, length3, angleOfLine(c18.c1, c6)))
|
||||
# c14 to c12
|
||||
length = distance(c14, c12)/3.0
|
||||
c12.c2 = controlPoint(C, 'c12.c2', polar(c12, length, angleOfLine(c9, c12)))
|
||||
c12.c1 = controlPoint(C, 'c12.c1', polar(c14, length, angleOfLine(c14, c12.c2)))
|
||||
# c9 to c25
|
||||
length = distance(c9, c25)/3.0
|
||||
c25.c2 = controlPoint(C, 'c25.c2', right(c25, length))
|
||||
c25.c1 = controlPoint(C, 'c25.c1', polar(c9, length, angleOfLine(c9, c25.c2)))
|
||||
#c22 to c4
|
||||
length = distance(c22, c4)/3.0
|
||||
c4.c1 = controlPoint(C, 'c4.c1', left(c22, length))
|
||||
c4.c2 = controlPoint(C, 'c4.c2', polar(c4, length, angleOfLine(c4, c4.c1)))
|
||||
#c5 to c6
|
||||
length = distance(c5, c6)/3.0
|
||||
c6.c1 = controlPoint(C, 'c6.c1', polar(c5, length, angleOfLine(c4, c5)))
|
||||
c6.c2 = controlPoint(C, 'c6.c2', polar(c6, length, angleOfLine(c6, c6.c1)))
|
||||
|
||||
|
||||
#cuff D
|
||||
d1 = patternPointXY(D, 'd1', 0, 0)
|
||||
d2 = patternPoint(D, 'd2', right(d1, hand_circumference+2*IN))
|
||||
d3 = patternPoint(D, 'd3', down(d2, 3*IN))
|
||||
d4 = patternPoint(D, 'd4', up(d3, 0.75*IN))
|
||||
d5 = patternPoint(D, 'd5', left(d3, 1*IN))
|
||||
d6 = patternPoint(D, 'd6', down(d1, 3*IN))
|
||||
d7 = patternPoint(D, 'd7', right(d6, 1*IN))
|
||||
d8 = patternPoint(D, 'd8', up(d6, 0.75*IN))
|
||||
length1 = 0.7*distance(d1, d6)
|
||||
length2 = 0.75*IN
|
||||
d9 = patternPointXY(D, 'd9', d1.x+0.5*IN, d1.y+length1)
|
||||
d10 = patternPoint(D, 'd10', right(d9, length2))
|
||||
d11 = patternPointXY(D, 'd11', d2.x-0.5*IN, d2.y+length1)
|
||||
d12 = patternPoint(D, 'd12', left(d11, length2))
|
||||
#cuff D control points
|
||||
length = distance(d4, d5)/3.0
|
||||
d5.c1 = controlPoint(D, 'd5.c1', down(d4, length))
|
||||
d5.c2 = controlPoint(D, 'd5.c2', right(d5, length))
|
||||
d8.c1 = controlPoint(D, 'd8.c1', left(d7, length))
|
||||
d8.c2 = controlPoint(D, 'd8.c2', down(d8, length))
|
||||
|
||||
# all points are defined, now create paths with them...
|
||||
# pattern marks, labels, grainlines, seamlines, cuttinglines, darts, etc.
|
||||
|
||||
#bodice front A
|
||||
#letter
|
||||
pnt1 = Point('', a8.x, a6.c1.y)
|
||||
addText(A, 'A_letter', pnt1.x, pnt1.y, 'A', fontsize = '72')
|
||||
#label
|
||||
pnt2 = down(pnt1, 0.5*IN)
|
||||
addText(A, 'A_label', pnt2.x, pnt2.y, 'Bodice Front', fontsize = '48')
|
||||
#label
|
||||
pnt3 = down(pnt2, 0.5*IN)
|
||||
addText(A, 'A_fabric', pnt3.x, pnt3.y, 'Cut 2 of fabric', fontsize = '38')
|
||||
#grainline points
|
||||
aG1 = down(a11, front_waist_length/3.0)
|
||||
aG2 = polar(aG1, front_waist_length/2.0, angleOfLine(a11, a14))
|
||||
path_str = formatPath('M', aG1, 'L', aG2)
|
||||
A_grainline = addPath(A, 'A_grainline', path_str, 'grainline')
|
||||
# gridline - helpful for troubleshooting during design phase
|
||||
path_str = formatPath('M', a1, 'L', a3, 'M', a4, 'L', a2, 'M', a8, 'L', a15, 'M', a11, 'L', a10, 'M', a7, 'L', a5)
|
||||
A_gridline = addPath(A, 'A_gridline', path_str, 'gridline')
|
||||
#seamline & cuttingline
|
||||
path_str = formatPath('M', a11, 'L', a14, 'C', a15.c1, a15.c2, a15, 'C', a13.c1, a13.c2, a13, 'C', a12.c1, a12.c2, a12)
|
||||
path_str = path_str+formatPath('L', a16, 'C', a3.c1, a3.c2, a3, 'C', a4.c1, a4.c2, a4, 'C', a6.c1, a6.c2, a6, 'L', a8, 'C', a11.c1, a11.c2, a11)
|
||||
A_seamline = addPath(A, 'A_seamline', path_str, 'seamline')
|
||||
A_cuttingline = addPath(A, 'A_cuttingline', path_str, 'cuttingline')
|
||||
|
||||
#bodice back B
|
||||
#letter
|
||||
pnt1 = Point('', b8.x*2/3.0, b8.c2.y)
|
||||
addText(B, 'B_letter', pnt1.x, pnt1.y, 'B', fontsize = '72') #
|
||||
#label
|
||||
pnt2 = down(pnt1, 0.5*IN)
|
||||
addText(B, 'B_name', pnt2.x, pnt2.y, 'Bodice Back', fontsize = '48')
|
||||
#label
|
||||
pnt3 = down(pnt2, 0.5*IN)
|
||||
addText(B, 'B_fabric', pnt3.x, pnt3.y, 'Cut 2 of fabric', fontsize = '38')
|
||||
#grainline points
|
||||
bG1 = down(b7, front_waist_length/3.0)
|
||||
bG2 = down(bG1, front_waist_length/2.0)
|
||||
path_str = formatPath('M', bG1, 'L', bG2)
|
||||
B_grainline = addPath(B, 'B_grainline', path_str, 'grainline')
|
||||
# gridline
|
||||
path_str = formatPath('M', b1, 'L', b2, 'M', b11, 'L', b9, 'M', b9, 'L', b10, 'M', b7, 'L', b6, 'L', b1, 'M', b11, 'L', b10)
|
||||
B_gridline = addPath(B, 'B_gridline', path_str, 'gridline')
|
||||
#seamline & cuttingline
|
||||
path_str = formatPath('M', b1, 'L', b2, 'L', b11, 'C', b9.c1, b9.c2, b9, 'L', b13, 'C', b12.c1, b12.c2, b12, 'C', b8.c1, b8.c2, b8, 'L', b7, 'C', b1.c1, b1.c2, b1)
|
||||
B_seamline = addPath(B, 'B_seamline', path_str, 'seamline')
|
||||
B_cuttingline = addPath(B, 'B_cuttingline', path_str, 'cuttingline')
|
||||
|
||||
#bodice sleeve C
|
||||
#letter
|
||||
pnt1 = Point('', c19.c1.x, c12.c1.y)
|
||||
addText(C, 'C_letter', pnt1.x, pnt1.y, 'C', fontsize = '72') #
|
||||
#label
|
||||
pnt2 = down(pnt1, 0.5*IN)
|
||||
addText(C, 'C_name', pnt2.x, pnt2.y, 'Bodice Sleeve', fontsize = '48')
|
||||
#label
|
||||
pnt3 = down(pnt2, 0.5*IN)
|
||||
addText(C, 'C_fabric', pnt3.x, pnt3.y, 'Cut 2 of fabric', fontsize = '38')
|
||||
#grainline points
|
||||
cG1 = c20
|
||||
cG2 = down(cG1, overarm_length/2.0)
|
||||
path_str = formatPath('M', cG1, 'L', cG2)
|
||||
C_grainline = addPath(C, 'C_grainline', path_str, 'grainline')
|
||||
# gridline
|
||||
path_str = formatPath('M', c15, 'L', c2, 'M', c15, 'L', c19, 'M', c2, 'L', c9, 'M', c3, 'L', c12, 'M', c6, 'L', c14, 'M', c18, 'L', c16, 'M', c19, 'L', c17)
|
||||
C_gridline = addPath(C, 'C_gridline', path_str, 'gridline')
|
||||
# slashline
|
||||
path_str = formatPath('M', c24, 'L', c25)
|
||||
C_slashline = addPath(C, 'C_slashline', path_str, 'slashline')
|
||||
#seamline & cuttingline
|
||||
path_str = formatPath('M', c6, 'C', c18.c1, c18.c2, c18, 'C', c21.c1, c21.c2, c21, 'C', c19.c1, c19.c2, c19, 'C', c13.c1, c13.c2, c13, 'C', c14.c1, c14.c2, c14)
|
||||
path_str += formatPath('C', c12.c1, c12.c2, c12, 'L', c9, 'C', c25.c1, c25.c2, c25, 'L', c22, 'C', c4.c1, c4.c2, c4, 'L', c5, 'C', c6.c1, c6.c2, c6)
|
||||
C_seamline = addPath(C, 'C_seamline', path_str, 'seamline')
|
||||
C_cuttingline = addPath(C, 'C_cuttingline', path_str, 'cuttingline')
|
||||
|
||||
#bodice cuff D
|
||||
#letter
|
||||
pnt1 = Point('', d7.x, d6.y/4.0)
|
||||
addText(D, 'D_letter', pnt1.x, pnt1.y, 'D', fontsize = '38') #
|
||||
#label
|
||||
pnt2 = right(pnt1, 1*IN)
|
||||
addText(D, 'C_name', pnt2.x, pnt2.y, 'Bodice Sleeve Cuff', fontsize = '30')
|
||||
#label
|
||||
pnt3 = right(pnt2, 4*IN)
|
||||
addText(D, 'C_fabric', pnt3.x, pnt3.y, 'Cut 2 of fabric', fontsize = '24')
|
||||
pnt3 = down(pnt3, 0.3*IN)
|
||||
addText(D, 'C_finterfacing', pnt3.x, pnt3.y, 'Cut 2 of interfacing', fontsize = '24')
|
||||
#grainline points
|
||||
pnt1 = midPoint(d1, d6)
|
||||
dG1 = right(pnt1, distance(d1, d2)/4.0)
|
||||
dG2 = right(dG1, distance(d1, d2)/2.0)
|
||||
path_str = formatPath('M', dG1, 'L', dG2)
|
||||
D_grainline = addPath(D, 'D_grainline', path_str, 'grainline')
|
||||
# gridline
|
||||
path_str = formatPath('M', d1, 'L', d2, 'L', d4, 'L', d5, 'L', d7, 'L', d8, 'L', d1)
|
||||
D_gridline = addPath(D, 'D_gridline', path_str, 'gridline')
|
||||
# slashline
|
||||
path_str = formatPath('M', d9, 'L', d10, 'M', d11, 'L', d12)
|
||||
D_slashline = addPath(D, 'D_slashline', path_str, 'slashline')
|
||||
#seamline & cuttingline
|
||||
path_str = formatPath('M', d1, 'L', d2, 'L', d4, 'C', d5.c1, d5.c2, d5, 'L', d7, 'C', d8.c1, d8.c2, d8, 'L', d1)
|
||||
D_seamline = addPath(D, 'D_seamline', path_str, 'seamline')
|
||||
D_cuttingline = addPath(D, 'D_cuttingline', path_str, 'cuttingline')
|
||||
|
||||
#layout patterns on document in rows
|
||||
dx = BORDER+SEAM_ALLOWANCE #left border, allow width for seam allowance
|
||||
dy = BORDER+NOTE_HEIGHT+2*SEAM_ALLOWANCE # print pattern under the note header, allow height for seam allowance plus extra space
|
||||
pattern_buffer = 3*SEAM_ALLOWANCE #between any two patterns need 2 seam allowances plus additional space
|
||||
# first row
|
||||
pattern_offset = dx
|
||||
row_offset = dy
|
||||
#layout bodice front A
|
||||
adx = pattern_offset-a14.x #left border offset dx, translate leftmost A point a14 to this offset
|
||||
ady = row_offset-a8.y #upper height offset dy, translate highest A point a8
|
||||
A.set('transform', 'translate('+str(adx)+' '+str(ady)+')')
|
||||
pattern_offset = adx+a12.x+pattern_buffer
|
||||
#layout bodice front B
|
||||
bdx = pattern_offset-b9.x #translate leftmost B point
|
||||
bdy = row_offset-b6.y #translate highest B point
|
||||
B.set('transform', 'translate('+str(bdx)+' '+str(bdy)+')')
|
||||
|
||||
#2nd row
|
||||
pattern_offset = dx
|
||||
row_offset = ady+a15.y+pattern_offset # row_offset + lowest point from previous row, plus pattern_offset
|
||||
#layout sleeve C
|
||||
cdx = pattern_offset-c6.x
|
||||
cdy = row_offset-c21.y
|
||||
C.set('transform', 'translate('+str(cdx)+' '+str(cdy)+')')
|
||||
pattern_offset = cdx+c14.x+pattern_buffer
|
||||
#layout cuff D
|
||||
ddx = pattern_offset-d1.x
|
||||
ddy = row_offset-d1.y
|
||||
D.set('transform', 'translate('+str(ddx)+' '+str(ddy)+')')
|
||||
#3rd row, use this to calculate document height
|
||||
row_offset = cdy+c25.y
|
||||
|
||||
#resize document to fit pattern piece layout
|
||||
width = ddx+d2.x # use pattern piece that appears farthest to the right in Inkscape canvas
|
||||
doc_width = width+2*SEAM_ALLOWANCE+2*BORDER
|
||||
doc_height = row_offset+SEAM_ALLOWANCE+BORDER
|
||||
root = self.svg.getElement('//svg:svg');
|
||||
root.set('viewBox', '%f %f %f %f' % (0,0,doc_width,doc_height))
|
||||
root.set('width', str(doc_width))
|
||||
root.set('height', str(doc_height))
|
||||
|
||||
#Place notes on document after pattern pieces are transformed so that notes are centered on correct width
|
||||
x = doc_width/2.0
|
||||
y = BORDER
|
||||
i = 0
|
||||
for item in notes:
|
||||
addText(bodice, 'note'+str(i), x, y, item, fontsize = '28', textalign = 'center', textanchor = 'middle', reference = 'false')
|
||||
y = y+0.33*IN
|
||||
|
||||
if __name__ == '__main__':
|
||||
ShirtWaist().run()
|
21
extensions/fablabchemnitz/show_path_coordinates/meta.json
Normal file
21
extensions/fablabchemnitz/show_path_coordinates/meta.json
Normal file
@ -0,0 +1,21 @@
|
||||
[
|
||||
{
|
||||
"name": "Show Path Coordinates",
|
||||
"id": "fablabchemnitz.de.show_path_coordinates",
|
||||
"path": "show_path_coordinates",
|
||||
"dependent_extensions": null,
|
||||
"original_name": "Export XY",
|
||||
"original_id": "org.simarilius.filter.ExportXY",
|
||||
"license": "GNU GPL v3",
|
||||
"license_url": "https://github.com/jwcliff/Inkscape_Exportxy/blob/master/LICENSE",
|
||||
"comment": "ported to Inkscape v1 by Mario Voigt",
|
||||
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/show_path_coordinates",
|
||||
"fork_url": "https://github.com/jwcliff/Inkscape_Exportxy",
|
||||
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Show+Path+Coordinates",
|
||||
"inkscape_gallery_url": null,
|
||||
"main_authors": [
|
||||
"github.com/jwcliff",
|
||||
"github.com/eridur-de"
|
||||
]
|
||||
}
|
||||
]
|
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Show Path Coordinates</name>
|
||||
<id>fablabchemnitz.de.show_path_coordinates</id>
|
||||
<effect needs-live-preview="false">
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz">
|
||||
<submenu name="Dimensioning/Measuring"/>
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">show_path_coordinates.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# curve xy co-ordinate export
|
||||
# Authors:
|
||||
# Jean Moreno <jean.moreno.fr@gmail.com>
|
||||
# John Cliff <john.cliff@gmail.com>
|
||||
# Neon22 <https://github.com/Neon22?tab=repositories>
|
||||
# Jens N. Lallensack <jens.lallensack@gmail.com>
|
||||
# Mario Voigt <mario.voigt@stadtfabrikanten.org>
|
||||
#
|
||||
# Copyright (C) 2011 Jean Moreno
|
||||
# Copyright (C) 2011 John Cliff
|
||||
# Copyright (C) 2011 Neon22
|
||||
# Copyright (C) 2019 Jens N. Lallensack
|
||||
# Copyright (C) 2021 Mario Voigt
|
||||
|
||||
# Released under GNU GPL v3, see https://www.gnu.org/licenses/gpl-3.0.en.html for details.
|
||||
#
|
||||
import inkex
|
||||
import sys
|
||||
from inkex.paths import CubicSuperPath
|
||||
from inkex import transforms
|
||||
|
||||
class ShowPathCoordinates(inkex.EffectExtension):
|
||||
|
||||
def effect(self):
|
||||
if len(self.svg.selected) > 0:
|
||||
output_all = output_nodes = ""
|
||||
for node in self.svg.selection.filter(inkex.PathElement):
|
||||
node.apply_transform()
|
||||
p = CubicSuperPath(node.get('d'))
|
||||
for subpath in p:
|
||||
for csp in subpath:
|
||||
output_nodes += str(csp[1][0]) + "\t" + str(csp[1][1]) + "\n"
|
||||
output_nodes += "\n"
|
||||
sys.stderr.write(output_nodes.strip())
|
||||
else:
|
||||
inkex.errormsg('Please select some paths first.')
|
||||
return
|
||||
|
||||
if __name__ == '__main__':
|
||||
ShowPathCoordinates().run()
|
21
extensions/fablabchemnitz/simple_frame/meta.json
Normal file
21
extensions/fablabchemnitz/simple_frame/meta.json
Normal file
@ -0,0 +1,21 @@
|
||||
[
|
||||
{
|
||||
"name": "Simple Frame",
|
||||
"id": "fablabchemnitz.de.simple_frame",
|
||||
"path": "simple_frame",
|
||||
"dependent_extensions": null,
|
||||
"original_name": "Simple Frame",
|
||||
"original_id": "org.inkscape.estucheria.caja4p",
|
||||
"license": "GNU GPL v3",
|
||||
"license_url": "https://github.com/redentis/inkscape-extensions/blob/main/papercraft_frame.py",
|
||||
"comment": "",
|
||||
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/simple_frame",
|
||||
"fork_url": "https://github.com/redentis/inkscape-extensions",
|
||||
"documentation_url": "",
|
||||
"inkscape_gallery_url": null,
|
||||
"main_authors": [
|
||||
"github.com/redentis",
|
||||
"github.com/eridur-de"
|
||||
]
|
||||
}
|
||||
]
|
23
extensions/fablabchemnitz/simple_frame/simple_frame.inx
Normal file
23
extensions/fablabchemnitz/simple_frame/simple_frame.inx
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Simple Frame</name>
|
||||
<id>fablabchemnitz.de.simple_frame</id>
|
||||
<param name="unit" gui-text="Unit:" type="optiongroup" appearance="combo">
|
||||
<option value="mm">mm</option>
|
||||
</param>
|
||||
<param name="width" type="float" min="0.1" max="300.0" gui-text="Inner width:">100.0</param>
|
||||
<param name="height" type="float" min="0.1" max="300.0" gui-text="Inner height:">150.0</param>
|
||||
<param name="depth" type="float" min="0.1" max="300.0" gui-text="Depth">10.0</param>
|
||||
<param name="border" type="float" min="0.1" max="300.0" gui-text="Frame border width">20.0</param>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz Boxes/Papercraft">
|
||||
<submenu name="Paper/Cardboard Boxes" />
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">simple_frame.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
272
extensions/fablabchemnitz/simple_frame/simple_frame.py
Normal file
272
extensions/fablabchemnitz/simple_frame/simple_frame.py
Normal file
@ -0,0 +1,272 @@
|
||||
#! /usr/bin/env python3
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
__version__ = "0.1"
|
||||
|
||||
import inkex
|
||||
import math
|
||||
|
||||
class GenerateFrame(inkex.EffectExtension):
|
||||
|
||||
def add_arguments(self, pars):
|
||||
pars.add_argument("--width", type=float, default=100.0, help="Inner width")
|
||||
pars.add_argument("--height", type=float, default=150.0, help="Inner height")
|
||||
pars.add_argument("--depth", type=float, default=10.0, help="Frame depth")
|
||||
pars.add_argument("--border", type=float, default=20.0, help="Frame border width")
|
||||
pars.add_argument("--unit", default="mm", help="Unit of measure")
|
||||
|
||||
def effect(self):
|
||||
center_x = self.svg.unittouu(self.document.getroot().get('width'))/2
|
||||
center_y = self.svg.unittouu(self.document.getroot().get('height'))/2
|
||||
|
||||
_width = self.svg.unittouu(str(self.options.width) + self.options.unit)
|
||||
_height = self.svg.unittouu(str(self.options.height) + self.options.unit)
|
||||
_depth = self.svg.unittouu(str(self.options.depth) + self.options.unit)
|
||||
|
||||
_border = self.svg.unittouu(str(self.options.border) + self.options.unit)
|
||||
_border_hyp = math.sqrt(2 * _border * _border)
|
||||
|
||||
id_frame = self.svg.get_unique_id('papercraft-frame')
|
||||
group = self.svg.get_current_layer().add(inkex.Group(id=id_frame))
|
||||
id_score = self.svg.get_unique_id('papercraft-scores')
|
||||
score_group = group.add(inkex.Group(id=id_score))
|
||||
cut_line = {'stroke': '#FF0000', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||
safe_line = {'stroke': '#0000FF', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px'))}
|
||||
valley_score_line = {'stroke': '#00FF00', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px')), 'stroke-dasharray': '1.05999995,0.52999997,0.26499999,0.52999997'}
|
||||
mountain_score_line = {'stroke': '#00FF00', 'fill': 'none', 'stroke-width': str(self.svg.unittouu('1px')), 'stroke-dasharray': '5,5'}
|
||||
|
||||
# line.path --> M = absolute coordinates
|
||||
# line.path --> l = draws a line from the current point to the specified relative coordinates
|
||||
# line.path --> c = draws a beizer curve from the current point to the specified coordinates
|
||||
# line.path --> q = draw an arc from the current point to the specified coordinates using a point as reference
|
||||
# line.path --> Z = close path
|
||||
|
||||
# outer profile (cut)
|
||||
line = group.add(inkex.PathElement(id=id_frame + '-outer-profile'))
|
||||
line.path = [
|
||||
# top-left
|
||||
['M', [0, 2 * (_border+_depth)]],
|
||||
['l', [_border+_depth+_border,0]],
|
||||
['l', [0,-_depth]],
|
||||
['l', [_depth,0]],
|
||||
['l', [_border,-_border]],
|
||||
['l', [0,-_depth]],
|
||||
['l', [-_border,-_border]],
|
||||
['l', [_width+2*_border,0]],
|
||||
|
||||
# top-right
|
||||
['l', [-_border,_border]],
|
||||
['l', [0,_depth]],
|
||||
['l', [_border,_border]],
|
||||
['l', [_depth,0]],
|
||||
['l', [0,_depth]],
|
||||
['l', [_border+_depth+_border,0]],
|
||||
['l', [0,_height+2*_border]],
|
||||
|
||||
# bottom-right
|
||||
['l', [-(_border+_depth+_border),0]],
|
||||
['l', [0,_depth]],
|
||||
['l', [-_depth,0]],
|
||||
['l', [-_border,_border]],
|
||||
['l', [0,_depth]],
|
||||
['l', [_border,_border]],
|
||||
['l', [-(_width+2*_border),0]],
|
||||
|
||||
# bottom-left
|
||||
['l', [_border,-_border]],
|
||||
['l', [0,-_depth]],
|
||||
['l', [-_border,-_border]],
|
||||
['l', [-_depth,0]],
|
||||
['l', [0,-_depth]],
|
||||
['l', [-(_border+_depth+_border),0]],
|
||||
|
||||
['Z', []]
|
||||
]
|
||||
line.style = cut_line
|
||||
|
||||
line = group.add(inkex.PathElement(id=id_frame + '-inner-profile'))
|
||||
line.path = [
|
||||
['M', [2*_depth+3*_border, 2*_depth+3*_border]],
|
||||
['l', [_width,0]],
|
||||
['l', [0,_height]],
|
||||
['l', [-_width,0]],
|
||||
['Z', []]
|
||||
]
|
||||
line.style = safe_line
|
||||
|
||||
# score lines -- vertical
|
||||
_top_edge = 2 *_border+2*_depth
|
||||
line = score_group.add(inkex.PathElement(id=id_score + '-score-1'))
|
||||
line.path = [
|
||||
['M', [_border, _top_edge]],
|
||||
['l', [0, _height+2*_border]],
|
||||
]
|
||||
line.style = valley_score_line
|
||||
|
||||
line = score_group.add(inkex.PathElement(id=id_score + '-score-2'))
|
||||
line.path = [
|
||||
['M', [_border+_depth, _top_edge]],
|
||||
['l', [0, _height+2*_border]],
|
||||
]
|
||||
line.style = valley_score_line
|
||||
|
||||
line = score_group.add(inkex.PathElement(id=id_score + '-score-3'))
|
||||
line.path = [
|
||||
['M', [_border+_depth+_border, _top_edge]],
|
||||
['l', [0, _height+2*_border]],
|
||||
]
|
||||
line.style = valley_score_line
|
||||
|
||||
line = score_group.add(inkex.PathElement(id=id_score + '-score-4'))
|
||||
line.path = [
|
||||
['M', [2*(_border+_depth), _top_edge-_depth]],
|
||||
['l', [0, _height+2*_border+2*_depth]],
|
||||
]
|
||||
line.style = valley_score_line
|
||||
|
||||
_right_side = _width+4*_border+2*_depth
|
||||
|
||||
line = score_group.add(inkex.PathElement(id=id_score + '-score-5'))
|
||||
line.path = [
|
||||
['M', [_right_side, _top_edge-_depth]],
|
||||
['l', [0, _height+2*_border+2*_depth]],
|
||||
]
|
||||
line.style = valley_score_line
|
||||
|
||||
line = score_group.add(inkex.PathElement(id=id_score + '-score-6'))
|
||||
line.path = [
|
||||
['M', [_right_side+_depth, _top_edge]],
|
||||
['l', [0, _height+2*_border]],
|
||||
]
|
||||
line.style = valley_score_line
|
||||
|
||||
line = score_group.add(inkex.PathElement(id=id_score + '-score-7'))
|
||||
line.path = [
|
||||
['M', [_right_side+_border+_depth, _top_edge]],
|
||||
['l', [0, _height+2*_border]],
|
||||
]
|
||||
line.style = valley_score_line
|
||||
|
||||
line = score_group.add(inkex.PathElement(id=id_score + '-score-8'))
|
||||
line.path = [
|
||||
['M', [_right_side+_border+_depth+_depth, _top_edge]],
|
||||
['l', [0, _height+2*_border]],
|
||||
]
|
||||
line.style = valley_score_line
|
||||
|
||||
# corners
|
||||
_o1_x = 2*_border + _depth
|
||||
_o1_y = _o1_x + _depth
|
||||
|
||||
line = score_group.add(inkex.PathElement(id=id_score + '-score-22'))
|
||||
line.path = [
|
||||
['M', [_o1_x+_depth, _o1_y]],
|
||||
['l', [-_depth,-_depth]],
|
||||
]
|
||||
line.style = mountain_score_line
|
||||
|
||||
_o2_x = _o1_x + _width + 2*_border + 2*_depth
|
||||
_o2_y = _o1_y
|
||||
|
||||
line = score_group.add(inkex.PathElement(id=id_score + '-score-24'))
|
||||
line.path = [
|
||||
['M', [_o2_x-_depth, _o2_y]],
|
||||
['l', [_depth,-_depth]],
|
||||
]
|
||||
line.style = mountain_score_line
|
||||
|
||||
_o3_x = _o1_x
|
||||
_o3_y = _o1_y + _height + 2*_border
|
||||
|
||||
line = score_group.add(inkex.PathElement(id=id_score + '-score-26'))
|
||||
line.path = [
|
||||
['M', [_o3_x + _depth, _o3_y]],
|
||||
['l', [-_depth,_depth]],
|
||||
]
|
||||
line.style = mountain_score_line
|
||||
|
||||
_o4_x = _o2_x
|
||||
_o4_y = _o3_y
|
||||
|
||||
line = score_group.add(inkex.PathElement(id=id_score + '-score-28'))
|
||||
line.path = [
|
||||
['M', [_o4_x - _depth, _o4_y]],
|
||||
['l', [_depth,_depth]],
|
||||
]
|
||||
line.style = mountain_score_line
|
||||
|
||||
# horizontals
|
||||
line = score_group.add(inkex.PathElement(id=id_score + '-score-31'))
|
||||
line.path = [
|
||||
['M', [2*_border+_depth, 2*_border+2*_depth]],
|
||||
['l', [_width+2*_border+2*_depth,0]],
|
||||
]
|
||||
line.style = valley_score_line
|
||||
|
||||
line = score_group.add(inkex.PathElement(id=id_score + '-score-32'))
|
||||
line.path = [
|
||||
['M', [2*_border+2*_depth, 2*_border+_depth]],
|
||||
['l', [_width+2*_border,0]],
|
||||
]
|
||||
line.style = valley_score_line
|
||||
|
||||
line = score_group.add(inkex.PathElement(id=id_score + '-score-33'))
|
||||
line.path = [
|
||||
['M', [3*_border+2*_depth, _border+_depth]],
|
||||
['l', [_width,0]],
|
||||
]
|
||||
line.style = valley_score_line
|
||||
|
||||
line = score_group.add(inkex.PathElement(id=id_score + '-score-34'))
|
||||
line.path = [
|
||||
['M', [3*_border+2*_depth, _border]],
|
||||
['l', [_width,0]],
|
||||
]
|
||||
line.style = valley_score_line
|
||||
|
||||
_bottom = _height + 4*_border+2*_depth
|
||||
|
||||
line = score_group.add(inkex.PathElement(id=id_score + '-score-35'))
|
||||
line.path = [
|
||||
['M', [2*_border+_depth, _bottom]],
|
||||
['l', [_width+2*_border+2*_depth,0]],
|
||||
]
|
||||
line.style = valley_score_line
|
||||
|
||||
line = score_group.add(inkex.PathElement(id=id_score + '-score-36'))
|
||||
line.path = [
|
||||
['M', [2*_border+2*_depth, _bottom+_depth]],
|
||||
['l', [_width+2*_border,0]],
|
||||
]
|
||||
line.style = valley_score_line
|
||||
|
||||
line = score_group.add(inkex.PathElement(id=id_score + '-score-37'))
|
||||
line.path = [
|
||||
['M', [3*_border+2*_depth, _bottom+_border+_depth]],
|
||||
['l', [_width,0]],
|
||||
]
|
||||
line.style = valley_score_line
|
||||
|
||||
line = score_group.add(inkex.PathElement(id=id_score + '-score-38'))
|
||||
line.path = [
|
||||
['M', [3*_border+2*_depth, _bottom+_border+2*_depth]],
|
||||
['l', [_width,0]],
|
||||
]
|
||||
line.style = valley_score_line
|
||||
|
||||
if __name__ == '__main__':
|
||||
GenerateFrame().run()
|
@ -7,7 +7,7 @@
|
||||
<option value="Hypotrochoid">Hypotrochoid</option>
|
||||
</param>
|
||||
<label appearance="header">Curve parameters</label>
|
||||
<param name="radius_R" type="int" min="0" max="1000" gui-text="Fixed circle radius (R):">10</param>
|
||||
<param name="radius_R" type="int" min="1" max="1000" gui-text="Fixed circle radius (R):">10</param>
|
||||
<param name="radius_r" type="int" min="-1000" max="1000" gui-text="Rolling circle radius (r):">5</param>
|
||||
<param name="pencil_distance" type="int" min="-1000" max="1000" gui-text="Pencil distance¹ (d):">2</param>
|
||||
<label>¹ use d=r for Epi/Hypocycloid.</label>
|
||||
|
21
extensions/fablabchemnitz/triangle/meta.json
Normal file
21
extensions/fablabchemnitz/triangle/meta.json
Normal file
@ -0,0 +1,21 @@
|
||||
[
|
||||
{
|
||||
"name": "Triangle",
|
||||
"id": "fablabchemnitz.de.triangle",
|
||||
"path": "triangle",
|
||||
"dependent_extensions": null,
|
||||
"original_name": "Triangle",
|
||||
"original_id": "math.triangle",
|
||||
"license": "GNU GPL v2",
|
||||
"license_url": "https://gitlab.com/inkscape/extensions/-/blob/master/LICENSE.txt",
|
||||
"comment": "",
|
||||
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/triangle",
|
||||
"fork_url": "https://gitlab.com/inkscape/extensions/",
|
||||
"documentation_url": "https://stadtfabrikanten.org/display/IFM/Triangle",
|
||||
"inkscape_gallery_url": null,
|
||||
"main_authors": [
|
||||
"John Beard:john.j.beard@gmail.com",
|
||||
"github.com/eridur-de"
|
||||
]
|
||||
}
|
||||
]
|
37
extensions/fablabchemnitz/triangle/triangle.inx
Normal file
37
extensions/fablabchemnitz/triangle/triangle.inx
Normal file
@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Triangle</name>
|
||||
<id>fablabchemnitz.de.triangle</id>
|
||||
<param name="unit" type="optiongroup" appearance="combo" gui-text="Units:">
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="px">px</option>
|
||||
<option value="pt">pt</option>
|
||||
<option value="pc">pc</option>
|
||||
<option value="in">in</option>
|
||||
</param>
|
||||
<param name="s_a" type="float" min="0.01" max="10000" gui-text="Side Length a:">100.0</param>
|
||||
<param name="s_b" type="float" min="0.01" max="10000" gui-text="Side Length b:">100.0</param>
|
||||
<param name="s_c" type="float" min="0.01" max="10000" gui-text="Side Length c:">100.0</param>
|
||||
<param name="a_a" type="float" min="0" max="180" gui-text="Angle a (deg):">60</param>
|
||||
<param name="a_b" type="float" min="0" max="180" gui-text="Angle b (deg):">30</param>
|
||||
<param name="a_c" type="float" min="0" max="180" gui-text="Angle c (deg):">90</param>
|
||||
<param name="mode" type="optiongroup" appearance="combo" gui-text="Mode:">
|
||||
<option value="3_sides">From Three Sides</option>
|
||||
<option value="s_ab_a_c">From Sides a, b and Angle c</option>
|
||||
<option value="s_ab_a_a">From Sides a, b and Angle a</option>
|
||||
<option value="s_a_a_ab">From Side a and Angles a, b</option>
|
||||
<option value="s_c_a_ab">From Side c and Angles a, b</option>
|
||||
</param>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz">
|
||||
<submenu name="Shape/Pattern from Generator" />
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">triangle.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
188
extensions/fablabchemnitz/triangle/triangle.py
Normal file
188
extensions/fablabchemnitz/triangle/triangle.py
Normal file
@ -0,0 +1,188 @@
|
||||
#! /usr/bin/python3
|
||||
#
|
||||
# Copyright (C) 2007 John Beard john.j.beard@gmail.com
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
"""
|
||||
This extension allows you to draw a triangle given certain information
|
||||
about side length or angles.
|
||||
|
||||
Measurements of the triangle
|
||||
|
||||
C(x_c,y_c)
|
||||
/`__
|
||||
/ a_c``--__
|
||||
/ ``--__ s_a
|
||||
s_b / ``--__
|
||||
/a_a a_b`--__
|
||||
/--------------------------------``B(x_b, y_b)
|
||||
A(x_a,y_a) s_b
|
||||
"""
|
||||
|
||||
import sys
|
||||
from math import acos, asin, cos, pi, sin, sqrt
|
||||
|
||||
import inkex
|
||||
|
||||
X, Y = range(2)
|
||||
|
||||
def draw_SVG_tri(point1, point2, point3, offset, width, name, parent):
|
||||
style = {'stroke': '#000000', 'stroke-width': str(width), 'fill': 'none'}
|
||||
elem = parent.add(inkex.PathElement())
|
||||
elem.update(**{
|
||||
'style': style,
|
||||
'inkscape:label': name,
|
||||
'd': 'M ' + str(point1[X] + offset[X]) + ',' + str(point1[Y] + offset[Y]) +
|
||||
' L ' + str(point2[X] + offset[X]) + ',' + str(point2[Y] + offset[Y]) +
|
||||
' L ' + str(point3[X] + offset[X]) + ',' + str(point3[Y] + offset[Y]) +
|
||||
' L ' + str(point1[X] + offset[X]) + ',' + str(point1[Y] + offset[Y]) + ' z'})
|
||||
return elem
|
||||
|
||||
|
||||
def angle_from_3_sides(a, b, c): # return the angle opposite side c
|
||||
cosx = (a * a + b * b - c * c) / (2 * a * b) # use the cosine rule
|
||||
return acos(cosx)
|
||||
|
||||
|
||||
def third_side_from_enclosed_angle(s_a, s_b, a_c): # return the side opposite a_c
|
||||
c_squared = s_a * s_a + s_b * s_b - 2 * s_a * s_b * cos(a_c)
|
||||
if c_squared > 0:
|
||||
return sqrt(c_squared)
|
||||
else:
|
||||
return 0 # means we have an invalid or degenerate triangle (zero is caught at the drawing stage)
|
||||
|
||||
|
||||
def pt_on_circ(radius, angle): # return the x,y coordinate of the polar coordinate
|
||||
x = radius * cos(angle)
|
||||
y = radius * sin(angle)
|
||||
return [x, y]
|
||||
|
||||
|
||||
def v_add(point1, point2): # add an offset to coordinates
|
||||
return [point1[X] + point2[X], point1[Y] + point2[Y]]
|
||||
|
||||
|
||||
def is_valid_tri_from_sides(a, b, c): # check whether triangle with sides a,b,c is valid
|
||||
return (a + b) > c and (a + c) > b and (b + c) > a and a > 0 and b > 0 and c > 0 # two sides must always be greater than the third
|
||||
# no zero-length sides, no degenerate case
|
||||
|
||||
|
||||
def draw_tri_from_3_sides(s_a, s_b, s_c, offset, width, parent): # draw a triangle from three sides (with a given offset
|
||||
if is_valid_tri_from_sides(s_a, s_b, s_c):
|
||||
a_b = angle_from_3_sides(s_a, s_c, s_b)
|
||||
|
||||
a = (0, 0) # a is the origin
|
||||
b = v_add(a, (s_c, 0)) # point B is horizontal from the origin
|
||||
c = v_add(b, pt_on_circ(s_a, pi - a_b)) # get point c
|
||||
c[1] = -c[1]
|
||||
|
||||
offx = max(b[0], c[0]) / 2 # b or c could be the furthest right
|
||||
offy = c[1] / 2 # c is the highest point
|
||||
offset = (offset[0] - offx, offset[1] - offy) # add the centre of the triangle to the offset
|
||||
|
||||
draw_SVG_tri(a, b, c, offset, width, 'Triangle', parent)
|
||||
else:
|
||||
inkex.errormsg('Invalid Triangle Specifications.')
|
||||
|
||||
|
||||
class Triangle(inkex.EffectExtension):
|
||||
def add_arguments(self, pars):
|
||||
pars.add_argument("--unit", default="mm", help="Units")
|
||||
pars.add_argument("--s_a", type=float, default=100.0, help="Side Length a")
|
||||
pars.add_argument("--s_b", type=float, default=100.0, help="Side Length b")
|
||||
pars.add_argument("--s_c", type=float, default=100.0, help="Side Length c")
|
||||
pars.add_argument("--a_a", type=float, default=60.0, help="Angle a")
|
||||
pars.add_argument("--a_b", type=float, default=30.0, help="Angle b")
|
||||
pars.add_argument("--a_c", type=float, default=90.0, help="Angle c")
|
||||
pars.add_argument("--mode", default='3_sides', help="Side Length c")
|
||||
|
||||
def effect(self):
|
||||
tri = self.svg.get_current_layer()
|
||||
offset = self.svg.namedview.center
|
||||
self.options.s_a = self.svg.unittouu(str(self.options.s_a) + self.options.unit)
|
||||
self.options.s_b = self.svg.unittouu(str(self.options.s_b) + self.options.unit)
|
||||
self.options.s_c = self.svg.unittouu(str(self.options.s_c) + self.options.unit)
|
||||
stroke_width = self.svg.unittouu('1px')
|
||||
|
||||
if self.options.mode == '3_sides':
|
||||
s_a = self.options.s_a
|
||||
s_b = self.options.s_b
|
||||
s_c = self.options.s_c
|
||||
draw_tri_from_3_sides(s_a, s_b, s_c, offset, stroke_width, tri)
|
||||
|
||||
elif self.options.mode == 's_ab_a_c':
|
||||
s_a = self.options.s_a
|
||||
s_b = self.options.s_b
|
||||
a_c = self.options.a_c * pi / 180 # in rad
|
||||
|
||||
s_c = third_side_from_enclosed_angle(s_a, s_b, a_c)
|
||||
draw_tri_from_3_sides(s_a, s_b, s_c, offset, stroke_width, tri)
|
||||
|
||||
elif self.options.mode == 's_ab_a_a':
|
||||
s_a = self.options.s_a
|
||||
s_b = self.options.s_b
|
||||
a_a = self.options.a_a * pi / 180 # in rad
|
||||
|
||||
if (a_a < pi / 2.0) and (s_a < s_b) and (s_a > s_b * sin(a_a)): # this is an ambiguous case
|
||||
ambiguous = True # we will give both answers
|
||||
else:
|
||||
ambiguous = False
|
||||
|
||||
sin_a_b = s_b * sin(a_a) / s_a
|
||||
|
||||
if (sin_a_b <= 1) and (sin_a_b >= -1): # check the solution is possible
|
||||
a_b = asin(sin_a_b) # acute solution
|
||||
a_c = pi - a_a - a_b
|
||||
error = False
|
||||
else:
|
||||
sys.stderr.write('Error:Invalid Triangle Specifications.\n') # signal an error
|
||||
error = True
|
||||
|
||||
if not error and (a_b < pi) and (a_c < pi): # check that the solution is valid, if so draw acute solution
|
||||
s_c = third_side_from_enclosed_angle(s_a, s_b, a_c)
|
||||
draw_tri_from_3_sides(s_a, s_b, s_c, offset, stroke_width, tri)
|
||||
|
||||
if not error and ((a_b > pi) or (a_c > pi) or ambiguous): # we want the obtuse solution
|
||||
a_b = pi - a_b
|
||||
a_c = pi - a_a - a_b
|
||||
s_c = third_side_from_enclosed_angle(s_a, s_b, a_c)
|
||||
draw_tri_from_3_sides(s_a, s_b, s_c, offset, stroke_width, tri)
|
||||
|
||||
elif self.options.mode == 's_a_a_ab':
|
||||
s_a = self.options.s_a
|
||||
a_a = self.options.a_a * pi / 180 # in rad
|
||||
a_b = self.options.a_b * pi / 180 # in rad
|
||||
|
||||
a_c = pi - a_a - a_b
|
||||
s_b = s_a * sin(a_b) / sin(a_a)
|
||||
s_c = s_a * sin(a_c) / sin(a_a)
|
||||
|
||||
draw_tri_from_3_sides(s_a, s_b, s_c, offset, stroke_width, tri)
|
||||
|
||||
elif self.options.mode == 's_c_a_ab':
|
||||
s_c = self.options.s_c
|
||||
a_a = self.options.a_a * pi / 180 # in rad
|
||||
a_b = self.options.a_b * pi / 180 # in rad
|
||||
|
||||
a_c = pi - a_a - a_b
|
||||
s_a = s_c * sin(a_a) / sin(a_c)
|
||||
s_b = s_c * sin(a_b) / sin(a_c)
|
||||
|
||||
draw_tri_from_3_sides(s_a, s_b, s_c, offset, stroke_width, tri)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
Triangle().run()
|
@ -0,0 +1,21 @@
|
||||
[
|
||||
{
|
||||
"name": "Vertical / Horizontal Scale",
|
||||
"id": "fablabchemnitz.de.vertical_horizontal_scale",
|
||||
"path": "vertical_horizontal_scale",
|
||||
"dependent_extensions": null,
|
||||
"original_name": "Scale",
|
||||
"original_id": "org.inkscape.render.render_scale",
|
||||
"license": "GNU GPL v2",
|
||||
"license_url": "https://github.com/brathering82/inkscape/blob/master/LICENSE",
|
||||
"comment": "ported to Inkscape v1 by Mario Voigt",
|
||||
"source_url": "https://gitea.fablabchemnitz.de/FabLab_Chemnitz/mightyscape-1.2/src/branch/master/extensions/fablabchemnitz/vertical_horizontal_scale",
|
||||
"fork_url": "https://github.com/brathering82/inkscape",
|
||||
"documentation_url": "https://stadtfabrikanten.org/pages/viewpage.action?pageId=55018500",
|
||||
"inkscape_gallery_url": null,
|
||||
"main_authors": [
|
||||
"github.com/brathering82",
|
||||
"github.com/eridur-de"
|
||||
]
|
||||
}
|
||||
]
|
@ -0,0 +1,81 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<name>Vertical / Horizontal Scale</name>
|
||||
<id>fablabchemnitz.de.vertical_horizontal_scale</id>
|
||||
<param name="tab" type="notebook">
|
||||
<page name="global" gui-text="Shape">
|
||||
<label appearance="header">Global</label>
|
||||
<param name="type" type="optiongroup" appearance="combo" gui-text="Type:">
|
||||
<option value="straight">Straight</option>
|
||||
<option value="circular">Circular</option>
|
||||
</param>
|
||||
<param name="unit" type="optiongroup" appearance="combo" gui-text="Unit:">
|
||||
<option value="mm">mm</option>
|
||||
<option value="cm">cm</option>
|
||||
<option value="in">inch</option>
|
||||
<option value="px">pixel</option>
|
||||
<option value="pt">point</option>
|
||||
</param>
|
||||
<param name="useref" type="bool" gui-text="Origin from bounding box center">false</param>
|
||||
<param name="insidetf" type="bool" gui-text="Swap inside out">false</param>
|
||||
<label appearance="header">Straight</label>
|
||||
<param name="rotate" type="optiongroup" appearance="combo" gui-text="Orientation:">
|
||||
<option value="0">Vertical</option>
|
||||
<option value="90">Horizontal</option>
|
||||
</param>
|
||||
<label appearance="header">Circular</label>
|
||||
<param name="radius" type="float" gui-text="Radius (units):" min="0.000000001" max="32000">50.0</param>
|
||||
<label>0 deg is at top. Positive values are clockwise.</label>
|
||||
<param name="scaleradbegin" type="float" gui-text="Start (deg):" min="-360.0" max="360.0">0</param>
|
||||
<param name="scaleradcount" type="float" gui-text="Count (deg):" min="-360.0" max="360.0">90</param>
|
||||
<param name="radmark" type="bool" gui-text="Mark origin">true</param>
|
||||
</page>
|
||||
<page name="labelopt" gui-text="Labels">
|
||||
<param name="drawalllabels" type="bool" gui-text="Draw all labels">true</param>
|
||||
<label appearance="header">Numbers</label>
|
||||
<param name="scalefrom" type="int" gui-text="Number from:" min="-32000" max="32000">0</param>
|
||||
<param name="scaleto" type="int" gui-text="Number to:" min="-32000" max="32000">40</param>
|
||||
<param name="mathexpression" type="string" gui-text="Math expression (number = 'n'):"/>
|
||||
<param name="reverse" type="bool" gui-text="Reverse order">false</param>
|
||||
<label appearance="header">Format</label>
|
||||
<param name="fontsize" type="float" gui-text="Fontsize (units):" min="1" max="32000">3</param>
|
||||
<param name="suffix" type="string" gui-text="Label suffix:"/>
|
||||
<param name="ishorizontal" type="bool" gui-text="Horizontal labels (circular only)">false</param>
|
||||
<param name="fliplabel" type="bool" gui-text="Flip orientation">false</param>
|
||||
<label appearance="header">Offset (relative to label orientation):</label>
|
||||
<param name="labeloffseth" type="float" gui-text="Horizontal (units):" min="-32000" max="32000">0</param>
|
||||
<param name="labeloffsetv" type="float" gui-text="Vertical (units):" min="-32000" max="32000">0</param>
|
||||
</page>
|
||||
<page name="lineopt" gui-text="Lines">
|
||||
<label appearance="header">All lines</label>
|
||||
<param name="units_per_line" type="float" gui-text="Units per line (straight only):" min="0.00001" max="99999.9000">1.00000</param>
|
||||
<label appearance="header">Perpendicular line</label>
|
||||
<param name="perpline" type="bool" gui-text="Draw perpendicular line">false</param>
|
||||
<param name="perplinestrokewidth" type="float" gui-text="Stroke width (units):" min="0.001" max="32000">0.2</param>
|
||||
<param name="perplineoffset" type="float" gui-text="Offset (units):" min="-32000" max="32000">0</param>
|
||||
<label appearance="header">Label line</label>
|
||||
<param name="labellinelength" type="float" gui-text="Length (units):" min="1" max="32000">5.0</param>
|
||||
<param name="labellinestrokewidth" type="float" gui-text="Stroke width (units):" min="0.001" max="32000">0.4</param>
|
||||
<param name="mark0" type="int" gui-text="Draw every x lines (label number based on this):" min="0" max="32000">10</param>
|
||||
<label appearance="header">Long line</label>
|
||||
<param name="mark1wid" type="int" gui-text="Length (percentage of label line length):" min="0" max="200">85</param>
|
||||
<param name="longlinestrokewidth" type="float" gui-text="Stroke width (units):" min="0.001" max="32000">0.2</param>
|
||||
<param name="mark1" type="int" gui-text="Draw every x lines:" min="0" max="32000">5</param>
|
||||
<label appearance="header">Short line</label>
|
||||
<param name="mark2wid" type="int" gui-text="Length (percentage of label line length):" min="0" max="200">60</param>
|
||||
<param name="shortlinestrokewidth" type="float" gui-text="Stroke width (units):" min="0.001" max="32000">0.2</param>
|
||||
<param name="mark2" type="int" gui-text="Draw every x lines:" min="0" max="32000">1</param>
|
||||
</page>
|
||||
</param>
|
||||
<effect>
|
||||
<object-type>all</object-type>
|
||||
<effects-menu>
|
||||
<submenu name="FabLab Chemnitz Shape Generators">
|
||||
<submenu name="Scales" />
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command location="inx" interpreter="python">vertical_horizontal_scale.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
@ -0,0 +1,542 @@
|
||||
#!/usr/bin/env python3
|
||||
'''
|
||||
Copyright (C)
|
||||
2009 Sascha Poczihoski, sascha@junktech.de
|
||||
Original author.
|
||||
|
||||
2013 Roger Jeurissen, roger@acfd-consultancy.nl
|
||||
Added dangling labels and inside/outside scale features.
|
||||
|
||||
2015 Paul Rogalinski, pulsar@codewut.de
|
||||
Adapted Inkscape 0.91 API changes.
|
||||
|
||||
2015 Bit Barrel Media, bitbarrelmedia -at- gmail dot com
|
||||
-Changed UI and added the following features:
|
||||
Label offset. This will move the labels side to side and up/down.
|
||||
Option to use the center of a bounding box as the drawing reference.
|
||||
Ability to set line stroke width.
|
||||
Option to add a perpendicular line.
|
||||
Mathematical expression for the number format. For example, to divide the label number by 2, use "n/2".
|
||||
"Draw all labels" checkbox.
|
||||
Option to flip the label orientation.
|
||||
Support for "Draw every x lines" = 0 in order to remove lines.
|
||||
|
||||
|
||||
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
|
||||
|
||||
TODO
|
||||
- fix bug: special chars in suffix
|
||||
|
||||
#for debugging:
|
||||
message = "Debug: " + str(i) + "\n"
|
||||
inkex.utils.debug(message)
|
||||
'''
|
||||
|
||||
import math
|
||||
import inkex
|
||||
from lxml import etree
|
||||
|
||||
class VerticalHorizontalScale(inkex.EffectExtension):
|
||||
|
||||
|
||||
def add_arguments(self, pars):
|
||||
# Define string option "--what" with "-w" shortcut and default value "World".
|
||||
pars.add_argument('-f', '--scalefrom', type = int, default = '0', help = 'Number from...')
|
||||
pars.add_argument('-t', '--scaleto', type = int,default = '20', help = 'Number to...')
|
||||
pars.add_argument('--mathexpression', default = '', help = 'Math expression')
|
||||
pars.add_argument('-c', '--reverse', default = 'false', help = 'Reverse order:')
|
||||
pars.add_argument('-p', '--type', default = 'false', help = 'Type:')
|
||||
pars.add_argument('--radius', type = float, default = '100', help = 'Radius')
|
||||
pars.add_argument('--scaleradcount', type = float, default = '90', help = 'Circular count')
|
||||
pars.add_argument('--scaleradbegin', type = float, default = '0', help = 'Circular begin')
|
||||
pars.add_argument('--radmark', default = 'True', help = 'Mark origin')
|
||||
pars.add_argument('--insidetf', type = inkex.Boolean, default = 'False', help = 'Swap inside out')
|
||||
pars.add_argument('--ishorizontal', default = 'False', help = 'Horizontal labels')
|
||||
pars.add_argument('--rotate', default = '0', help = 'Rotate:')
|
||||
pars.add_argument('-b', '--units_per_line', type = float, default = '100', help = 'Units per line')
|
||||
pars.add_argument('-g', '--labellinelength', type = float, default = '100', help = 'Label line - Length')
|
||||
pars.add_argument('-s', '--fontsize', type = float, default = '3', help = 'Font Size:')
|
||||
pars.add_argument('-i', '--suffix', default = '', help = 'Suffix:')
|
||||
pars.add_argument('--drawalllabels', type = inkex.Boolean, default = 'True', help = 'Draw all labels')
|
||||
pars.add_argument('--fliplabel', type = inkex.Boolean, default = 'False', help = 'Flip orientation')
|
||||
pars.add_argument('--labellinestrokewidth', type = float, default = '0.4', help = 'Label line - Stroke width')
|
||||
pars.add_argument('--longlinestrokewidth', type = float, default = '0.2', help = 'Long line - Stroke width')
|
||||
pars.add_argument('--shortlinestrokewidth', type = float, default = '0.2', help = 'Short line - Stroke width')
|
||||
pars.add_argument('--perplinestrokewidth', type = float, default = '0.2', help = 'Perpendicular line - Stroke width')
|
||||
|
||||
# label offset
|
||||
pars.add_argument('-x', '--labeloffseth', type = float, default = '0', help = 'Label offset h:')
|
||||
pars.add_argument('-y', '--labeloffsetv', type = float, default = '-3.5', help = 'Label offset v:')
|
||||
|
||||
# line spacing
|
||||
pars.add_argument('-m', '--mark0', type = int, default = '10', help = 'Label line - Draw every x lines:')
|
||||
pars.add_argument('-n', '--mark1', type = int, default = '5', help = 'Long line - Draw every x lines')
|
||||
pars.add_argument('-o', '--mark2', type = int, default = '1', help = 'Short line - Draw every x lines')
|
||||
|
||||
# line length
|
||||
pars.add_argument('-w', '--mark1wid', type = int, default = '75', help = 'Long line: - Length (units): (\%):')
|
||||
pars.add_argument('-v', '--mark2wid', type = int, help = 'Short line: - Length (units): (\%):')
|
||||
pars.add_argument('-u', '--unit', default = 'mm', help = 'Unit:')
|
||||
pars.add_argument('--useref', type = inkex.Boolean, default = False, help = 'Reference is bounding box center')
|
||||
pars.add_argument('--tab', default = 'global', help = '')
|
||||
pars.add_argument("--perpline", type=inkex.Boolean, default=True, help="Perpendicular line")
|
||||
pars.add_argument('--perplineoffset', type = float, default = '0', help = 'Offset')
|
||||
|
||||
|
||||
def addLabel(self, n, x, y, group, fontsize, phi = 0.0):
|
||||
mathexpression = self.options.mathexpression
|
||||
fliplabel = self.options.fliplabel
|
||||
drawalllabels = self.options.drawalllabels
|
||||
labeloffseth = self.options.labeloffseth
|
||||
labeloffsetv = self.options.labeloffsetv
|
||||
scaletype = self.options.type
|
||||
insidetf = self.options.insidetf
|
||||
rotate = self.options.rotate
|
||||
unit = self.options.unit
|
||||
|
||||
fontsize = self.svg.unittouu(str(fontsize)+unit)
|
||||
labeloffseth = self.svg.unittouu(str(labeloffseth)+unit)
|
||||
labeloffsetv = self.svg.unittouu(str(labeloffsetv)+unit)
|
||||
|
||||
#swapped and horizontal
|
||||
if scaletype == 'straight' and insidetf and rotate=='90':
|
||||
labeloffsetv *= -1
|
||||
|
||||
#swapped and vertical
|
||||
if scaletype == 'straight' and insidetf and rotate=='0':
|
||||
labeloffseth *= -1
|
||||
|
||||
if drawalllabels==True:
|
||||
if fliplabel==True:
|
||||
phi += 180
|
||||
|
||||
if scaletype == 'straight':
|
||||
x = float(x) + labeloffseth
|
||||
y = float(y) - labeloffsetv
|
||||
|
||||
res = self.options.units_per_line
|
||||
pos = n*res + fontsize/2
|
||||
suffix = self.options.suffix
|
||||
text = etree.SubElement(group, inkex.addNS('text','svg'))
|
||||
|
||||
number = n;
|
||||
try:
|
||||
number = eval(mathexpression)
|
||||
except (ValueError, SyntaxError, NameError):
|
||||
pass
|
||||
|
||||
text.text = str(number)+suffix
|
||||
cosphi=math.cos(math.radians(phi))
|
||||
sinphi=math.sin(math.radians(phi))
|
||||
a1 = str(cosphi)
|
||||
a2 = str(-sinphi)
|
||||
a3 = str(sinphi)
|
||||
a4 = str(cosphi)
|
||||
a5 = str((1-cosphi)*x-sinphi*y)
|
||||
a6 = str(sinphi*x+(1-cosphi)*y)
|
||||
fs = str(fontsize)
|
||||
style = {'text-align' : 'center', 'text-anchor': 'middle', 'font-size': fs}
|
||||
text.set('style', str(inkex.Style(style)))
|
||||
text.set('transform', 'matrix({0},{1},{2},{3},{4},{5})'.format(a1,a2,a3,a4,a5,a6))
|
||||
text.set('x', str(float(x)))
|
||||
text.set('y', str(float(y)))
|
||||
group.append(text)
|
||||
|
||||
|
||||
def addLine(self, i, scalefrom, scaleto, group, grpLabel, type=2):
|
||||
reverse = self.options.reverse
|
||||
rotate = self.options.rotate
|
||||
unit = self.options.unit
|
||||
fontsize = self.options.fontsize
|
||||
res = self.options.units_per_line
|
||||
labellinestrokewidth = self.options.labellinestrokewidth
|
||||
longlinestrokewidth = self.options.longlinestrokewidth
|
||||
shortlinestrokewidth = self.options.shortlinestrokewidth
|
||||
insidetf = self.options.insidetf
|
||||
|
||||
perplinestrokewidth = self.options.perplinestrokewidth
|
||||
perplineoffset = self.options.perplineoffset
|
||||
|
||||
factor = 1
|
||||
if insidetf==True:
|
||||
factor = -1
|
||||
|
||||
#vertical
|
||||
if rotate=='0':
|
||||
res *= -1
|
||||
|
||||
label = False
|
||||
if reverse=='true':
|
||||
# Count absolute i for labeling
|
||||
counter = 0
|
||||
for n in range(scalefrom, i):
|
||||
counter += 1
|
||||
n = scaleto-counter-1
|
||||
else:
|
||||
n = i
|
||||
|
||||
#label line
|
||||
if type==0:
|
||||
name = 'label line'
|
||||
stroke = self.svg.unittouu(str(labellinestrokewidth)+unit)
|
||||
line_style = { 'stroke': 'black', 'stroke-width': stroke }
|
||||
x1 = 0
|
||||
y1 = i*res
|
||||
x2 = self.options.labellinelength*factor
|
||||
y2 = i*res
|
||||
|
||||
label = True
|
||||
|
||||
#long line
|
||||
if type==1:
|
||||
name = 'long line'
|
||||
stroke = self.svg.unittouu(str(longlinestrokewidth)+unit)
|
||||
line_style = { 'stroke': 'black', 'stroke-width': stroke }
|
||||
x1 = 0
|
||||
y1 = i*res
|
||||
x2 = self.options.labellinelength*0.01*self.options.mark1wid*factor
|
||||
y2 = i*res
|
||||
|
||||
#short line
|
||||
name = 'short line'
|
||||
if type==2:
|
||||
stroke = self.svg.unittouu(str(shortlinestrokewidth)+unit)
|
||||
line_style = { 'stroke': 'black', 'stroke-width': stroke }
|
||||
x1 = 0
|
||||
y1 = i*res
|
||||
x2 = self.options.labellinelength*0.01*self.options.mark2wid*factor
|
||||
y2 = i*res
|
||||
|
||||
#perpendicular line
|
||||
if type==3:
|
||||
name = 'perpendicular line'
|
||||
stroke = self.svg.unittouu(str(perplinestrokewidth)+unit)
|
||||
line_style = { 'stroke': 'black', 'stroke-width': stroke }
|
||||
|
||||
#if stroke is in px, use this logic:
|
||||
# unitfactor = self.svg.unittouu(str(1)+unit)
|
||||
# strokeoffset = (labellinestrokewidth / 2) / unitfactor
|
||||
|
||||
#if stroke is in units, use this logic:
|
||||
strokeoffset = (labellinestrokewidth / 2)
|
||||
|
||||
x1 = perplineoffset
|
||||
x2 = perplineoffset
|
||||
|
||||
#horizontal
|
||||
if rotate=='90':
|
||||
y2 = ((scaleto-1)*res) + strokeoffset
|
||||
y1 = -strokeoffset
|
||||
|
||||
#vertical
|
||||
else:
|
||||
y2 = ((scaleto-1)*res) - strokeoffset
|
||||
y1 = strokeoffset
|
||||
|
||||
|
||||
x1 = str(self.svg.unittouu(str(x1)+unit) )
|
||||
y1 = str(self.svg.unittouu(str(y1)+unit) )
|
||||
x2 = str(self.svg.unittouu(str(x2)+unit) )
|
||||
y2 = str(self.svg.unittouu(str(y2)+unit) )
|
||||
|
||||
#horizontal
|
||||
if rotate=='90':
|
||||
tx = x1
|
||||
x1 = y1
|
||||
y1 = tx
|
||||
|
||||
tx = x2
|
||||
x2 = y2
|
||||
y2 = tx
|
||||
|
||||
if label==True:
|
||||
self.addLabel(n , x2, y2, grpLabel, fontsize)
|
||||
|
||||
line_attribs = {'style' : str(inkex.Style(line_style)), inkex.addNS('label','inkscape') : name, 'd' : 'M '+x1+','+y1+' L '+x2+','+y2}
|
||||
line = etree.SubElement(group, inkex.addNS('path','svg'), line_attribs )
|
||||
|
||||
|
||||
def addLineRad(self, i, scalefrom, scaleto, group, grpLabel, type=2, ishorizontal=True):
|
||||
height = self.options.labellinelength
|
||||
reverse = self.options.reverse
|
||||
radbegin = self.options.scaleradbegin
|
||||
radcount = self.options.scaleradcount
|
||||
unit = self.options.unit
|
||||
fontsize = self.options.fontsize
|
||||
radius = self.options.radius
|
||||
labeloffseth = self.options.labeloffseth
|
||||
labeloffsetv = self.options.labeloffsetv
|
||||
insidetf = self.options.insidetf
|
||||
labellinestrokewidth = self.options.labellinestrokewidth
|
||||
longlinestrokewidth = self.options.longlinestrokewidth
|
||||
shortlinestrokewidth = self.options.shortlinestrokewidth
|
||||
perplinestrokewidth = self.options.perplinestrokewidth
|
||||
perplineoffset = self.options.perplineoffset
|
||||
|
||||
label = False
|
||||
|
||||
labeloffsetv *= -1
|
||||
|
||||
# Count absolute count for evaluation of increment
|
||||
count = 0
|
||||
for n in range(scalefrom, scaleto):
|
||||
count += 1
|
||||
countstatus = 0
|
||||
for n in range(scalefrom, i):
|
||||
countstatus += 1
|
||||
|
||||
if reverse=='true':
|
||||
counter = 0
|
||||
for n in range(scalefrom, i):
|
||||
counter += 1
|
||||
n = scaleto-counter-1
|
||||
else:
|
||||
n = i
|
||||
inc = radcount / (count-1)
|
||||
irad = countstatus*inc
|
||||
irad = -1 * (radbegin+irad+180)
|
||||
|
||||
dangle = 0
|
||||
if ishorizontal=='false':
|
||||
dangle = 1
|
||||
|
||||
inside = -1
|
||||
if insidetf==True:
|
||||
inside = 1
|
||||
|
||||
#label line
|
||||
if type==0:
|
||||
name = 'label line'
|
||||
stroke = self.svg.unittouu(str(labellinestrokewidth)+unit)
|
||||
line_style = { 'stroke': 'black', 'stroke-width': stroke }
|
||||
x1 = math.sin(math.radians(irad))*radius
|
||||
y1 = math.cos(math.radians(irad))*radius
|
||||
x2 = math.sin(math.radians(irad))*(radius-inside*height)
|
||||
y2 = math.cos(math.radians(irad))*(radius-inside*height)
|
||||
|
||||
label = True
|
||||
|
||||
#long line
|
||||
if type==1:
|
||||
name = 'long line'
|
||||
stroke = self.svg.unittouu(str(longlinestrokewidth)+unit)
|
||||
line_style = { 'stroke': 'black', 'stroke-width': stroke }
|
||||
x1 = math.sin(math.radians(irad))*radius
|
||||
y1 = math.cos(math.radians(irad))*radius
|
||||
x2 = math.sin(math.radians(irad))*(radius-inside*height*self.options.mark1wid*0.01)
|
||||
y2 = math.cos(math.radians(irad))*(radius-inside*height*self.options.mark1wid*0.01)
|
||||
|
||||
#short line
|
||||
if type==2:
|
||||
name = 'short line'
|
||||
stroke = self.svg.unittouu(str(shortlinestrokewidth)+unit)
|
||||
line_style = { 'stroke': 'black', 'stroke-width': stroke }
|
||||
x1 = math.sin(math.radians(irad))*radius
|
||||
y1 = math.cos(math.radians(irad))*radius
|
||||
x2 = math.sin(math.radians(irad))*(radius-inside*height*self.options.mark2wid*0.01)
|
||||
y2 = math.cos(math.radians(irad))*(radius-inside*height*self.options.mark2wid*0.01)
|
||||
|
||||
#perpendicular line
|
||||
if type==3:
|
||||
name = 'perpendicular line'
|
||||
stroke = self.svg.unittouu(str(perplinestrokewidth)+unit)
|
||||
line_style = {'stroke': 'black', 'stroke-width' : stroke, 'fill': 'none'}
|
||||
|
||||
rx = self.svg.unittouu(str(radius+perplineoffset)+unit)
|
||||
ry = rx
|
||||
|
||||
#if stroke is in px, use this logic:
|
||||
#unitfactor = self.svg.unittouu(str(1)+unit)
|
||||
#strokeoffset = math.atan(((labellinestrokewidth / 2) / unitfactor) / radius)
|
||||
|
||||
#if stroke is in units, use this logic:
|
||||
strokeoffset = math.atan((labellinestrokewidth / 2) / radius)
|
||||
|
||||
start = math.radians(radbegin + 270) - strokeoffset
|
||||
end = math.radians(radbegin+radcount + 270) + strokeoffset
|
||||
|
||||
if radcount != 360:
|
||||
line_attribs = {'style':str(inkex.Style(line_style)),
|
||||
inkex.addNS('label','inkscape') :name,
|
||||
inkex.addNS('cx','sodipodi') :str(0),
|
||||
inkex.addNS('cy','sodipodi') :str(0),
|
||||
inkex.addNS('rx','sodipodi') :str(rx),
|
||||
inkex.addNS('ry','sodipodi') :str(ry),
|
||||
inkex.addNS('start','sodipodi') :str(start),
|
||||
inkex.addNS('end','sodipodi') :str(end),
|
||||
inkex.addNS('open','sodipodi') :'true', #all ellipse sectors we will draw are open
|
||||
inkex.addNS('type','sodipodi') :'arc',}
|
||||
else:
|
||||
line_attribs = {'style':str(inkex.Style(line_style)),
|
||||
inkex.addNS('label','inkscape') :name,
|
||||
inkex.addNS('cx','sodipodi') :str(0),
|
||||
inkex.addNS('cy','sodipodi') :str(0),
|
||||
inkex.addNS('rx','sodipodi') :str(rx),
|
||||
inkex.addNS('ry','sodipodi') :str(ry),
|
||||
inkex.addNS('open','sodipodi') :'true', #all ellipse sectors we will draw are open
|
||||
inkex.addNS('type','sodipodi') :'arc',}
|
||||
|
||||
if type!=3:
|
||||
# use user unit
|
||||
x1 = self.svg.unittouu(str(x1)+unit)
|
||||
y1 = self.svg.unittouu(str(y1)+unit)
|
||||
x2 = self.svg.unittouu(str(x2)+unit)
|
||||
y2 = self.svg.unittouu(str(y2)+unit)
|
||||
|
||||
if label==True :
|
||||
|
||||
#if the circle count is 360 degrees, do not draw the last label because it will overwrite the first.
|
||||
if not (radcount==360 and n==360):
|
||||
x2label = math.sin(math.radians(irad + labeloffseth))*(radius-inside*(-labeloffsetv+height*self.options.mark2wid*0.01))
|
||||
y2label = math.cos(math.radians(irad + labeloffseth))*(radius-inside*(-labeloffsetv+height*self.options.mark2wid*0.01))
|
||||
x2label = self.svg.unittouu(str(x2label)+unit)
|
||||
y2label = self.svg.unittouu(str(y2label)+unit)
|
||||
self.addLabel(n , x2label, y2label, grpLabel, fontsize,dangle*(irad+labeloffseth))
|
||||
|
||||
line_attribs = {'style' : str(inkex.Style(line_style)), inkex.addNS('label','inkscape') : name, 'd' : 'M '+str(x1)+','+str(y1)+' L '+str(x2)+','+str(y2)}
|
||||
|
||||
line = etree.SubElement(group, inkex.addNS('path','svg'), line_attribs )
|
||||
|
||||
|
||||
def skipfunc(self, i, markArray, groups):
|
||||
|
||||
skip = True
|
||||
group = groups[0]
|
||||
type = 0
|
||||
|
||||
if markArray[0] != 0:
|
||||
if (i % markArray[0])==0:
|
||||
type = 0 # the labeled line
|
||||
group = groups[0]
|
||||
skip = False
|
||||
|
||||
if markArray[1] != 0 and skip==1:
|
||||
if (i % markArray[1])==0:
|
||||
type = 1 # the long line
|
||||
group = groups[1]
|
||||
skip = False
|
||||
|
||||
if markArray[2] != 0 and skip==1:
|
||||
if (i % markArray[2])==0:
|
||||
type = 2 # the short line
|
||||
group = groups[2]
|
||||
skip = False
|
||||
|
||||
return (skip, group, type)
|
||||
|
||||
|
||||
|
||||
def effect(self):
|
||||
scalefrom = self.options.scalefrom
|
||||
scaleto = self.options.scaleto
|
||||
|
||||
scaleGroup = self.svg.get_current_layer().add(inkex.Group())
|
||||
groups = [None, None, None, None]
|
||||
markArray = [self.options.mark0, self.options.mark1, self.options.mark2]
|
||||
|
||||
# Get access to main SVG document element and get its dimensions.
|
||||
svg = self.document.getroot()
|
||||
|
||||
# Again, there are two ways to get the attributes:
|
||||
width = self.svg.unittouu(svg.get('width'))
|
||||
height = self.svg.unittouu(svg.get('height'))
|
||||
|
||||
centre = self.svg.namedview.center #Put in in the centre of the current view
|
||||
|
||||
if self.options.useref is True:
|
||||
self.bbox = sum([node.bounding_box() for node in self.svg.selected.values() ])
|
||||
try:
|
||||
test = self.bbox[0]
|
||||
except TypeError:
|
||||
pass
|
||||
else:
|
||||
half = (self.bbox[1] - self.bbox[0]) / 2
|
||||
x = self.bbox[0] + half
|
||||
|
||||
half = (self.bbox[3] - self.bbox[2]) / 2
|
||||
y = self.bbox[2] + half
|
||||
centre = (x, y)
|
||||
|
||||
grp_transform = 'translate(' + str(centre) + ')'
|
||||
|
||||
grp_name = 'Label line'
|
||||
grp_attribs = {inkex.addNS('label','inkscape'):grp_name, 'transform':grp_transform }
|
||||
groups[0] = etree.SubElement(self.svg.get_current_layer(), 'g', grp_attribs)
|
||||
|
||||
if self.options.mark1 > 0:
|
||||
grp_name = 'Long line'
|
||||
grp_attribs = {inkex.addNS('label','inkscape'):grp_name, 'transform':grp_transform }
|
||||
groups[1] = etree.SubElement(self.svg.get_current_layer(), 'g', grp_attribs)
|
||||
|
||||
if self.options.mark2 > 0:
|
||||
grp_name = 'Short line'
|
||||
grp_attribs = {inkex.addNS('label','inkscape'):grp_name, 'transform':grp_transform }
|
||||
groups[2] = etree.SubElement(self.svg.get_current_layer(), 'g', grp_attribs)
|
||||
|
||||
if self.options.drawalllabels is True:
|
||||
grp_name = 'Labels'
|
||||
grp_attribs = {inkex.addNS('label','inkscape'):grp_name, 'transform':grp_transform }
|
||||
groups[3] = etree.SubElement(self.svg.get_current_layer(), 'g', grp_attribs)
|
||||
|
||||
# to allow positive to negative counts
|
||||
if scalefrom < scaleto:
|
||||
scaleto += 1
|
||||
else:
|
||||
temp = scaleto
|
||||
scaleto = scalefrom+1
|
||||
scalefrom = temp
|
||||
|
||||
if self.options.type == 'straight':
|
||||
|
||||
for i in range(scalefrom, scaleto):
|
||||
skip, group, type = self.skipfunc(i, markArray, groups)
|
||||
if skip==False:
|
||||
self.addLine(i, scalefrom, scaleto, group, groups[3], type) # addLabel is called from inside
|
||||
|
||||
#add the perpendicular line
|
||||
if self.options.perpline is True:
|
||||
self.addLine(0, scalefrom, scaleto, groups[0], groups[3], 3)
|
||||
|
||||
elif self.options.type == 'circular':
|
||||
|
||||
for i in range(scalefrom, scaleto):
|
||||
skip, group, type = self.skipfunc(i, markArray, groups)
|
||||
if skip==False:
|
||||
self.addLineRad(i, scalefrom, scaleto, group, groups[3], type, self.options.ishorizontal) # addLabel is called from inside
|
||||
|
||||
#add the perpendicular (circular) line
|
||||
if self.options.perpline is True:
|
||||
self.addLineRad(0, scalefrom, scaleto, groups[0], groups[3], 3, self.options.ishorizontal)
|
||||
|
||||
if self.options.radmark=='true':
|
||||
|
||||
grp_name = 'Radial center'
|
||||
grp_attribs = {inkex.addNS('label','inkscape'):grp_name, 'transform':grp_transform }
|
||||
grpRadMark = etree.SubElement(self.svg.get_current_layer(), 'g', grp_attribs)
|
||||
|
||||
line_style = { 'stroke': 'black', 'stroke-width': '1' }
|
||||
line_attribs = {'style' : str(inkex.Style(line_style)), inkex.addNS('label','inkscape') : 'name', 'd' : 'M '+str(-10)+','+str(-10)+' L '+str(10)+','+str(10)}
|
||||
line = etree.SubElement(grpRadMark, inkex.addNS('path','svg'), line_attribs )
|
||||
|
||||
line_attribs = {'style' : str(inkex.Style(line_style)), inkex.addNS('label','inkscape') : 'name', 'd' : 'M '+str(-10)+','+str(10)+' L '+str(10)+','+str(-10)}
|
||||
line = etree.SubElement(grpRadMark, inkex.addNS('path','svg'), line_attribs )
|
||||
|
||||
#add all sub groups into a top one
|
||||
for group in groups:
|
||||
if group is not None:
|
||||
scaleGroup.append(group)
|
||||
|
||||
if __name__ == '__main__':
|
||||
VerticalHorizontalScale().run()
|
Loading…
Reference in New Issue
Block a user