Added Sine and Lace, reorganized imports of Twist
This commit is contained in:
parent
7a022d5bca
commit
09cfb9b610
48
extensions/fablabchemnitz_eggbot_sineandlace.inx
Normal file
48
extensions/fablabchemnitz_eggbot_sineandlace.inx
Normal file
@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
|
||||
<_name>Sine and Lace</_name>
|
||||
<id>fablabchemnitz.de.sineandlace</id>
|
||||
<param name="tab" type="notebook">
|
||||
<page name="splash" _gui-text="Sine and Lace">
|
||||
<param name="nWidth" type="int" min="1" max="10000" _gui-text="Width (pixels)">3200</param>
|
||||
<param name="nHeight" type="int" min="1" max="10000" _gui-text="Height (pixels)">100</param>
|
||||
<param name="fCycles" type="float" min="0.0001" max="10000" precision="5" _gui-text="Number of cycles (periods)">10</param>
|
||||
<param name="nrN" type="int" min="-100" max="100" _gui-text="Start angle at 2 pi ( n / m ); n = ">0</param>
|
||||
<param name="nrM" type="int" min="-100" max="100" _gui-text="Start angle at 2 pi ( n / m ); m = ">0</param>
|
||||
<param name="fRecess" type="float" min="0" max="100" precision="5" _gui-text="Recede from envelope by percentage">2</param>
|
||||
<param name="nSamples" type="int" min="2" max="100000" _gui-text="Number of sample points">1000</param>
|
||||
<param name="nOffsetX" type="int" min="-10000" max="10000" _gui-text="Starting x coordinate (pixels)">0</param>
|
||||
<param name="nOffsetY" type="int" min="-10000" max="10000" _gui-text="Starting y coordinate (pixels)">500</param>
|
||||
<param name="bLace" type="boolean" _gui-text="Lace">true</param>
|
||||
<param name="bSpline" type="boolean" _gui-text="Spline">false</param>
|
||||
</page>
|
||||
<page name="info" _gui-text="About...">
|
||||
<_param name="aboutpage" type="description" xml:space="preserve">
|
||||
This extension renders sinusoidal and "lace"
|
||||
patterns whose period is a specified multiple
|
||||
of the document width or any specified width.
|
||||
By selecting two previously drawn patterns,
|
||||
a third pattern may be inscribed within them.
|
||||
Patterns may not be inscribed within an inscribed
|
||||
pattern, however.
|
||||
|
||||
This extension may be found at Thingiverse as
|
||||
Thing #24594.
|
||||
|
||||
Sine and Lace v0.9
|
||||
Dan Newman (dan newman @ mtbaldy us)
|
||||
12 June 2012
|
||||
</_param>
|
||||
</page>
|
||||
</param>
|
||||
<effect>
|
||||
<effects-menu>
|
||||
<submenu _name="FabLab Chemnitz">
|
||||
<submenu _name="Shape/Pattern from Generator" />
|
||||
</submenu>
|
||||
</effects-menu>
|
||||
</effect>
|
||||
<script>
|
||||
<command reldir="extensions" interpreter="python">fablabchemnitz_eggbot_sineandlace.py</command>
|
||||
</script>
|
||||
</inkscape-extension>
|
334
extensions/fablabchemnitz_eggbot_sineandlace.py
Normal file
334
extensions/fablabchemnitz_eggbot_sineandlace.py
Normal file
@ -0,0 +1,334 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# eggbot_sineandlace.py
|
||||
#
|
||||
# Generate sinusoidal and "lace" curves. The curves are described in SVG
|
||||
# along with the data necessary to regenerate them. The same data can be
|
||||
# used to generate new curves which are bounded by a pair of previously
|
||||
# generated curves.
|
||||
|
||||
# Written by Daniel C. Newman for the Eggbot Project
|
||||
# dan newman @ mtbaldy us
|
||||
# Last updated 28 November 2010
|
||||
# 15 October 2010
|
||||
|
||||
# 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
|
||||
|
||||
from math import pi, cos, sin
|
||||
|
||||
from lxml import etree
|
||||
import inkex
|
||||
from inkex.paths import Path
|
||||
|
||||
VERSION = 1
|
||||
|
||||
|
||||
def parseDesc(str):
|
||||
"""
|
||||
Create a dictionary from string description
|
||||
"""
|
||||
|
||||
if str is None:
|
||||
return {}
|
||||
else:
|
||||
return dict([tok.split(':') for tok in str.split(';') if len(tok)])
|
||||
|
||||
|
||||
def formatDesc(d):
|
||||
"""
|
||||
Format an inline name1:value1;name2:value2;... style attribute value
|
||||
from a dictionary
|
||||
"""
|
||||
|
||||
return ';'.join([atr + ':' + str(val) for atr, val in d.iteritems()])
|
||||
|
||||
|
||||
def drawSine(cycles=8, rn=0, rm=0, nPoints=50, offset=None,
|
||||
height=200, width=3200, rescale=0.98, bound1='', bound2='', fun='sine', spline=True):
|
||||
"""
|
||||
cycles
|
||||
Number of periods to plot within the rectangle of width 'width'
|
||||
|
||||
rn, rm
|
||||
Start the function (on the left edge) at the value x = 2 * pi * rn / rm.
|
||||
When rm = 0, function is started at x = 0.
|
||||
|
||||
nPoints
|
||||
The number of points to sample the function at. Since the function is
|
||||
approximated using Bezier cubic splines, this isn't the number of points
|
||||
to plot.
|
||||
|
||||
offset
|
||||
(x, y) coordinate of the lower left corner of the bounding rectangle
|
||||
in which to plot the function.
|
||||
|
||||
height, width
|
||||
The height and width of the rectangle in which to plot the function.
|
||||
Ignored when bounding functions, bound1 and bound2, are supplied.
|
||||
|
||||
rescale
|
||||
Multiplicative Y-scaling factor by which to rescale the plotted function
|
||||
so that it does not fully reach the vertical limits of its bounds. This
|
||||
aids in Eggbot plots by preventing lines from touching and overlapping.
|
||||
|
||||
bound1, bound2
|
||||
Descriptions of upper and lower bounding functions by which to limit the
|
||||
vertical range of the function to be plotted.
|
||||
|
||||
fun
|
||||
May be either 'sine' or 'lace'.
|
||||
"""
|
||||
|
||||
"""
|
||||
A complicated way of plotting y = sin(x)
|
||||
|
||||
Complicated because we wish to generate the sine wave using a
|
||||
parametric representation. For plotting a single sine wave in
|
||||
Cartesian coordinates, this is overkill. However, it's useful
|
||||
for when we want to compress and expand the amplitude of the
|
||||
sine wave in accord with upper and lower boundaries which themselves
|
||||
are functions. By parameterizing everything in sight with the
|
||||
same parameter s and restricting s to the range [0, 1], our life
|
||||
is made much easier.
|
||||
"""
|
||||
if offset is None:
|
||||
offset = [0, 0]
|
||||
|
||||
bounded = False
|
||||
|
||||
if bound1 and bound2:
|
||||
|
||||
func = parseDesc(bound1)
|
||||
if len(func) == 0:
|
||||
return None, None
|
||||
m1 = int(func['rm'])
|
||||
if m1 == 0:
|
||||
x_min1 = 0.0
|
||||
else:
|
||||
x_min1 = 2 * pi * float(func['rn']) / float(m1)
|
||||
x_max1 = x_min1 + 2 * pi * float(func['cycles'])
|
||||
y_min1 = -1.0
|
||||
y_max1 = 1.0
|
||||
y_scale1 = float(func['height']) / (y_max1 - y_min1)
|
||||
y_offset1 = float(func['y'])
|
||||
Y1s = lambda s: y_offset1 - y_scale1 * sin(x_min1 + (x_max1 - x_min1) * s)
|
||||
|
||||
func = parseDesc(bound2)
|
||||
if len(func) == 0:
|
||||
return None, None
|
||||
m2 = int(func['rm'])
|
||||
if m2 == 0:
|
||||
x_min2 = 0.0
|
||||
else:
|
||||
x_min2 = 2 * pi * float(func['rn']) / float(m2)
|
||||
x_max2 = x_min2 + 2 * pi * float(func['cycles'])
|
||||
y_min2 = -1.0
|
||||
y_max2 = 1.0
|
||||
y_scale2 = float(func['height']) / (y_max2 - y_min2)
|
||||
y_offset2 = float(func['y'])
|
||||
Y2s = lambda s: y_offset2 - y_scale2 * sin(x_min2 + (x_max2 - x_min2) * s)
|
||||
|
||||
bounded = True
|
||||
|
||||
rescale = float(rescale)
|
||||
x_offset = float(offset[0])
|
||||
y_offset = float(offset[1])
|
||||
|
||||
# Each cycle is 2pi
|
||||
r = 2 * pi * float(cycles)
|
||||
if (int(rm) == 0) or (int(rn) == 0):
|
||||
x_min = 0.0
|
||||
else:
|
||||
x_min = 2 * pi * float(rn) / float(rm)
|
||||
x_max = x_min + r
|
||||
x_scale = float(width) / r # width / ( x_max - x_min )
|
||||
|
||||
y_min = -1.0
|
||||
y_max = 1.0
|
||||
y_scale = float(height) / (y_max - y_min)
|
||||
|
||||
# Our parametric equations which map the results to our drawing window
|
||||
# Note the "-y_scale" that's because in SVG, the y-axis runs "backwards"
|
||||
if not fun:
|
||||
fun = 'sine'
|
||||
fun = fun.lower()
|
||||
if fun == 'sine':
|
||||
Xs = lambda s: x_offset + x_scale * (x_max - x_min) * s
|
||||
Ys = lambda s: y_offset - y_scale * sin(x_min + (x_max - x_min) * s)
|
||||
dYdXs = lambda s: -y_scale * cos(x_min + (x_max - x_min) * s) / x_scale
|
||||
elif fun == 'lace':
|
||||
Xs = lambda s: x_offset + x_scale * ((x_max - x_min) * s + 2 * sin(2 * s * (x_max - x_min) + pi))
|
||||
dXs = lambda s: x_scale * (x_max - x_min) * (1.0 + 4.0 * cos(2 * s * (x_max - x_min) + pi))
|
||||
Ys = lambda s: y_offset - y_scale * sin(x_min + (x_max - x_min) * s)
|
||||
dYs = lambda s: -y_scale * cos(x_min + (x_max - x_min) * s) * (x_max - x_min)
|
||||
dYdXs = lambda s: dYs(s) / dXs(s)
|
||||
else:
|
||||
inkex.errormsg('Unknown function {0} specified'.format(fun))
|
||||
return
|
||||
|
||||
# Derivatives: remember the chain rule....
|
||||
# dXs = lambda s: x_scale * ( x_max - x_min )
|
||||
# dYs = lambda s: -y_scale * cos( x_min + ( x_max - x_min ) * s ) * ( x_max - x_min )
|
||||
|
||||
# x_third is 1/3 the step size
|
||||
nPoints = int(nPoints)
|
||||
|
||||
# x_third is 1/3 the step size; note that Xs(1) - Xs(0) = x_scale * ( x_max - x_min )
|
||||
x_third = (Xs(1.0) - Xs(0.0)) / float(3 * (nPoints - 1))
|
||||
|
||||
if bounded:
|
||||
y_upper = Y2s(0.0)
|
||||
y_lower = Y1s(0.0)
|
||||
y_offset = 0.5 * (y_upper + y_lower)
|
||||
y_upper = y_offset + rescale * (y_upper - y_offset)
|
||||
y_lower = y_offset + rescale * (y_lower - y_offset)
|
||||
y_scale = (y_upper - y_lower) / (y_max - y_min)
|
||||
|
||||
x1 = Xs(0.0)
|
||||
y1 = Ys(0.0)
|
||||
dx1 = 1.0
|
||||
dy1 = dYdXs(0.0)
|
||||
|
||||
# Starting point in the path is ( x, sin(x) )
|
||||
path_data = []
|
||||
path_data.append(['M', [x1, y1]])
|
||||
|
||||
for i in range(1, nPoints):
|
||||
|
||||
s = float(i) / float(nPoints - 1)
|
||||
if bounded:
|
||||
y_upper = Y2s(s)
|
||||
y_lower = Y1s(s)
|
||||
y_offset = 0.5 * (y_upper + y_lower)
|
||||
y_upper = y_offset + rescale * (y_upper - y_offset)
|
||||
y_lower = y_offset + rescale * (y_lower - y_offset)
|
||||
y_scale = (y_upper - y_lower) / (y_max - y_min)
|
||||
|
||||
x2 = Xs(s)
|
||||
y2 = Ys(s)
|
||||
dx2 = 1.0
|
||||
dy2 = dYdXs(s)
|
||||
if dy2 > 10.0:
|
||||
dy2 = 10.0
|
||||
elif dy2 < -10.0:
|
||||
dy2 = -10.0
|
||||
|
||||
# Add another segment to the plot
|
||||
if spline:
|
||||
path_data.append(['C',
|
||||
[x1 + (dx1 * x_third),
|
||||
y1 + (dy1 * x_third),
|
||||
x2 - (dx2 * x_third),
|
||||
y2 - (dy2 * x_third),
|
||||
x2, y2]])
|
||||
else:
|
||||
path_data.append(['L', [x1, y1]])
|
||||
path_data.append(['L', [x2, y2]])
|
||||
x1 = x2
|
||||
y1 = y2
|
||||
dx1 = dx2
|
||||
dy1 = dy2
|
||||
|
||||
path_desc = \
|
||||
'version:{0:d};style:linear;function:sin(x);'.format(VERSION) + \
|
||||
'cycles:{0:f};rn:{1:d};rm:{2:d};points:{3:d};'.format(cycles, rn, rm, nPoints) + \
|
||||
'width:{0:d};height:{1:d};x:{2:d};y:{3:d}'.format(width, height, offset[0], offset[1])
|
||||
|
||||
return path_data, path_desc
|
||||
|
||||
|
||||
class SpiroSine(inkex.Effect):
|
||||
nsURI = 'http://sample.com/ns'
|
||||
nsPrefix = 'doof'
|
||||
|
||||
def __init__(self):
|
||||
|
||||
inkex.Effect.__init__(self)
|
||||
self.arg_parser.add_argument("--tab", help="The active tab when Apply was pressed")
|
||||
self.arg_parser.add_argument('--fCycles', type=float, default=10.0, help='Number of cycles (periods)')
|
||||
self.arg_parser.add_argument('--nrN', type=int, default=0, help='Start x at 2 * pi * n / m')
|
||||
self.arg_parser.add_argument('--nrM', type=int, default=0, help='Start x at 2 * pi * n / m')
|
||||
self.arg_parser.add_argument('--fRecess', type=float, default=2.0, help='Recede from envelope by factor')
|
||||
self.arg_parser.add_argument("--nSamples", type=int, default=50.0, help="Number of points to sample")
|
||||
self.arg_parser.add_argument("--nWidth", type=int, default=3200, help="Width in pixels")
|
||||
self.arg_parser.add_argument("--nHeight", type=int, default=100, help="Height in pixels")
|
||||
self.arg_parser.add_argument("--nOffsetX", type=int, default=0, help="Starting x coordinate (pixels)")
|
||||
self.arg_parser.add_argument("--nOffsetY", type=int, default=400, help="Starting y coordinate (pixels)")
|
||||
self.arg_parser.add_argument('--bLace', type=inkex.Boolean, default=False, help='Lace')
|
||||
self.arg_parser.add_argument('--bSpline', type=inkex.Boolean, default=True, help='Spline')
|
||||
|
||||
self.recess = 0.95
|
||||
|
||||
def effect(self):
|
||||
|
||||
inkex.NSS[self.nsPrefix] = self.nsURI
|
||||
|
||||
if self.options.bLace:
|
||||
func = 'lace'
|
||||
else:
|
||||
func = 'sine'
|
||||
|
||||
f_recess = 1.0
|
||||
if self.options.fRecess > 0.0:
|
||||
f_recess = 1.0 - self.options.fRecess / 100.0
|
||||
if f_recess <= 0.0:
|
||||
f_recess = 0.0
|
||||
|
||||
if self.options.ids:
|
||||
if len(self.options.ids) == 2:
|
||||
desc1 = self.selected[self.options.ids[0]].get(inkex.addNS('desc', self.nsPrefix))
|
||||
desc2 = self.selected[self.options.ids[1]].get(inkex.addNS('desc', self.nsPrefix))
|
||||
if (not desc1) or (not desc2):
|
||||
inkex.errormsg('Selected objects do not smell right')
|
||||
return
|
||||
path_data, path_desc = drawSine(self.options.fCycles,
|
||||
self.options.nrN,
|
||||
self.options.nrM,
|
||||
self.options.nSamples,
|
||||
[self.options.nOffsetX, self.options.nOffsetY],
|
||||
self.options.nHeight,
|
||||
self.options.nWidth,
|
||||
f_recess,
|
||||
desc1, desc2, func, self.options.bSpline)
|
||||
else:
|
||||
inkex.errormsg('Exactly two objects must be selected')
|
||||
return
|
||||
else:
|
||||
self.document.getroot().set(inkex.addNS(self.nsPrefix, 'xmlns'), self.nsURI)
|
||||
|
||||
path_data, path_desc = drawSine(self.options.fCycles,
|
||||
self.options.nrN,
|
||||
self.options.nrM,
|
||||
self.options.nSamples,
|
||||
[self.options.nOffsetX, self.options.nOffsetY],
|
||||
self.options.nHeight,
|
||||
self.options.nWidth,
|
||||
f_recess,
|
||||
'',
|
||||
'',
|
||||
func,
|
||||
self.options.bSpline)
|
||||
|
||||
style = {'stroke': 'black', 'stroke-width': '1', 'fill': 'none'}
|
||||
path_attrs = {
|
||||
'style': str(inkex.Style(style)),
|
||||
'd': str(Path(path_data)),
|
||||
inkex.addNS('desc', self.nsPrefix): path_desc}
|
||||
newpath = etree.SubElement(self.document.getroot(),
|
||||
inkex.addNS('path', 'svg'), path_attrs, nsmap=inkex.NSS)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
SpiroSine().run()
|
@ -31,11 +31,10 @@
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
from inkex import bezier
|
||||
import cspsubdiv
|
||||
from inkex.paths import Path, CubicSuperPath
|
||||
import inkex
|
||||
from inkex import Transform
|
||||
from inkex import bezier
|
||||
from inkex.paths import Path, CubicSuperPath
|
||||
from lxml import etree
|
||||
|
||||
def subdivideCubicPath(sp, flat, i=1):
|
||||
|
Reference in New Issue
Block a user