Convert tab indents to spaces.

pull/97/head
Ryan Jarvis 2018-03-29 21:45:11 -07:00
rodzic e98ed3a91e
commit e5c5477253
17 zmienionych plików z 5785 dodań i 5786 usunięć

Wyświetl plik

@ -43,29 +43,29 @@ LINE_SKIP = 6 # Baseline skip between lines of text
# be used to deal with a face being removed from hersheydata.py
map_our_names_to_hersheydata = {
'astrology': 'astrology',
'cursive': 'cursive',
'cyrillic': 'cyrillic',
'futural': 'futural',
'futuram': 'futuram',
'gothiceng': 'gothiceng',
'gothicger': 'gothicger',
'gothicita': 'gothicita',
'greek': 'greek',
'japanese': 'japanese',
'markers': 'markers',
'mathlow': 'mathlow',
'mathupp': 'mathupp',
'meteorology': 'meteorology',
'music': 'music',
'scriptc': 'scriptc',
'scripts': 'scripts',
'symbolic': 'symbolic',
'timesg': 'timesg',
'timesi': 'timesi',
'timesib': 'timesib',
'timesr': 'timesr',
'timesrb': 'timesrb'}
'astrology': 'astrology',
'cursive': 'cursive',
'cyrillic': 'cyrillic',
'futural': 'futural',
'futuram': 'futuram',
'gothiceng': 'gothiceng',
'gothicger': 'gothicger',
'gothicita': 'gothicita',
'greek': 'greek',
'japanese': 'japanese',
'markers': 'markers',
'mathlow': 'mathlow',
'mathupp': 'mathupp',
'meteorology': 'meteorology',
'music': 'music',
'scriptc': 'scriptc',
'scripts': 'scripts',
'symbolic': 'symbolic',
'timesg': 'timesg',
'timesi': 'timesi',
'timesib': 'timesib',
'timesr': 'timesr',
'timesrb': 'timesrb'}
# The following two routines are lifted with impunity from Windell H. Oskay's
@ -73,198 +73,198 @@ map_our_names_to_hersheydata = {
# Copyright 2011, Windell H. Oskay, www.evilmadscientist.com
def draw_svg_text(char, face, offset, vertoffset, parent):
style = {'stroke': '#000000', 'fill': 'none'}
pathString = face[char]
splitString = pathString.split()
midpoint = offset - int(splitString[0])
i = pathString.find("M")
if i >= 0:
pathString = pathString[i:] # portion after first move
trans = 'translate(' + str(midpoint) + ',' + str(vertoffset) + ')'
text_attribs = {'style': simplestyle.formatStyle(style), 'd': pathString, 'transform': trans}
inkex.etree.SubElement(parent, inkex.addNS('path', 'svg'), text_attribs)
return midpoint + int(splitString[1]) # new offset value
style = {'stroke': '#000000', 'fill': 'none'}
pathString = face[char]
splitString = pathString.split()
midpoint = offset - int(splitString[0])
i = pathString.find("M")
if i >= 0:
pathString = pathString[i:] # portion after first move
trans = 'translate(' + str(midpoint) + ',' + str(vertoffset) + ')'
text_attribs = {'style': simplestyle.formatStyle(style), 'd': pathString, 'transform': trans}
inkex.etree.SubElement(parent, inkex.addNS('path', 'svg'), text_attribs)
return midpoint + int(splitString[1]) # new offset value
def renderText(parent, w, y, text, typeface):
"""
Render a string of text starting from the point (w, y) and using
the supplied typeface data.
"""
"""
Render a string of text starting from the point (w, y) and using
the supplied typeface data.
"""
if (text is None) or (text == ''):
return
if (text is None) or (text == ''):
return
spacing = 3 # spacing between letters
letterVals = [ord(q) - 32 for q in text]
spacing = 3 # spacing between letters
letterVals = [ord(q) - 32 for q in text]
for q in letterVals:
if (q < 0) or (q > 95):
w += 2 * spacing
else:
w = draw_svg_text(q, typeface, w, y, parent)
for q in letterVals:
if (q < 0) or (q > 95):
w += 2 * spacing
else:
w = draw_svg_text(q, typeface, w, y, parent)
return w
return w
def renderLine(parent, x, y, line, typeface1, typeface2):
"""
Render a single line of text:
+ The text runs horizontally from left to right starting at the point (x,y)
+ The first character of the line is written using "typeface1"
+ The remainder of the line of text is written using "typeface2"
"""
Render a single line of text:
+ The text runs horizontally from left to right starting at the point (x,y)
+ The first character of the line is written using "typeface1"
+ The remainder of the line of text is written using "typeface2"
The entire line is stored as individual paths which are child elements
of the SVG element "parent". The text in typeface2 (line[1:]) is also
placed in it's own subgroup. The leading character is not placed in
that subgroup. The reasoning is that the user may want to pick out
the first character of each line of text and put them in another layer
for plotting in a different color.
"""
The entire line is stored as individual paths which are child elements
of the SVG element "parent". The text in typeface2 (line[1:]) is also
placed in it's own subgroup. The leading character is not placed in
that subgroup. The reasoning is that the user may want to pick out
the first character of each line of text and put them in another layer
for plotting in a different color.
"""
# Return now if there's nothing to do
if (line is None) or (line == ''):
return
# Return now if there's nothing to do
if (line is None) or (line == ''):
return
# Render the first character
x = renderText(parent, x, y, [line[0]], typeface1)
# Render the first character
x = renderText(parent, x, y, [line[0]], typeface1)
# Render the rest of the line
line = line[1:]
if line == '':
return
g = inkex.etree.SubElement(parent, 'g')
renderText(g, x, y, line, typeface2)
# Render the rest of the line
line = line[1:]
if line == '':
return
g = inkex.etree.SubElement(parent, 'g')
renderText(g, x, y, line, typeface2)
class AcrosticText(inkex.Effect):
def __init__(self):
def __init__(self):
inkex.Effect.__init__(self)
self.OptionParser.add_option("--tab", # NOTE: value is not used.
action="store", type="string", dest="tab", default="splash",
help="The active tab when Apply was pressed")
self.OptionParser.add_option("--line01", action="store",
type="string", dest="line1", default="")
self.OptionParser.add_option("--line02", action="store",
type="string", dest="line2", default="")
self.OptionParser.add_option("--line03", action="store",
type="string", dest="line3", default="")
self.OptionParser.add_option("--line04", action="store",
type="string", dest="line4", default="")
self.OptionParser.add_option("--line05", action="store",
type="string", dest="line5", default="")
self.OptionParser.add_option("--line06", action="store",
type="string", dest="line6", default="")
self.OptionParser.add_option("--line07", action="store",
type="string", dest="line7", default="")
self.OptionParser.add_option("--line08", action="store",
type="string", dest="line8", default="")
self.OptionParser.add_option("--line09", action="store",
type="string", dest="line9", default="")
self.OptionParser.add_option("--line10", action="store",
type="string", dest="line10", default="")
self.OptionParser.add_option("--line11", action="store",
type="string", dest="line11", default="")
self.OptionParser.add_option("--line12", action="store",
type="string", dest="line12", default="")
self.OptionParser.add_option("--face1",
action="store", type="string", dest="face1", default="scriptc",
help="Leading font typeface")
self.OptionParser.add_option("--face2", action="store",
type="string", dest="face2", default="scripts",
help="Secondary typeface")
self.OptionParser.add_option("--flip", action="store", type="inkbool",
dest="flip", default=False,
help="Flip the text for plotting with the egg's bottom at the egg motor")
self.OptionParser.add_option("--stretch",
action="store", type="inkbool", dest="stretch", default=True,
help="Stretch the text horizontally to account for egg distortions")
inkex.Effect.__init__(self)
self.OptionParser.add_option("--tab", # NOTE: value is not used.
action="store", type="string", dest="tab", default="splash",
help="The active tab when Apply was pressed")
self.OptionParser.add_option("--line01", action="store",
type="string", dest="line1", default="")
self.OptionParser.add_option("--line02", action="store",
type="string", dest="line2", default="")
self.OptionParser.add_option("--line03", action="store",
type="string", dest="line3", default="")
self.OptionParser.add_option("--line04", action="store",
type="string", dest="line4", default="")
self.OptionParser.add_option("--line05", action="store",
type="string", dest="line5", default="")
self.OptionParser.add_option("--line06", action="store",
type="string", dest="line6", default="")
self.OptionParser.add_option("--line07", action="store",
type="string", dest="line7", default="")
self.OptionParser.add_option("--line08", action="store",
type="string", dest="line8", default="")
self.OptionParser.add_option("--line09", action="store",
type="string", dest="line9", default="")
self.OptionParser.add_option("--line10", action="store",
type="string", dest="line10", default="")
self.OptionParser.add_option("--line11", action="store",
type="string", dest="line11", default="")
self.OptionParser.add_option("--line12", action="store",
type="string", dest="line12", default="")
self.OptionParser.add_option("--face1",
action="store", type="string", dest="face1", default="scriptc",
help="Leading font typeface")
self.OptionParser.add_option("--face2", action="store",
type="string", dest="face2", default="scripts",
help="Secondary typeface")
self.OptionParser.add_option("--flip", action="store", type="inkbool",
dest="flip", default=False,
help="Flip the text for plotting with the egg's bottom at the egg motor")
self.OptionParser.add_option("--stretch",
action="store", type="inkbool", dest="stretch", default=True,
help="Stretch the text horizontally to account for egg distortions")
def effect(self):
def effect(self):
# Process the lines, ignoring leading or trailing blank lines
# and collapsing multiple internal runs of blank lines into a
# single blank line.
lines = []
prior_empty = False
for i in range(1, 13):
line = eval('self.options.line' + str(i)).strip()
if line == '':
if len(lines) != 0:
prior_empty = True
else:
if prior_empty:
lines.append('')
prior_empty = False
lines.append(line)
# Process the lines, ignoring leading or trailing blank lines
# and collapsing multiple internal runs of blank lines into a
# single blank line.
lines = []
prior_empty = False
for i in range(1, 13):
line = eval('self.options.line' + str(i)).strip()
if line == '':
if len(lines) != 0:
prior_empty = True
else:
if prior_empty:
lines.append('')
prior_empty = False
lines.append(line)
# Return now if there are no lines to print
line_count = len(lines)
if line_count == 0:
return
# Return now if there are no lines to print
line_count = len(lines)
if line_count == 0:
return
# Determine how much vertical room we need for our text
h = line_count * MAX_H + (line_count - 1) * LINE_SKIP
# Determine how much vertical room we need for our text
h = line_count * MAX_H + (line_count - 1) * LINE_SKIP
svg = self.document.getroot()
doc_height = self.unittouu(svg.attrib['height'])
if doc_height <= 0:
doc_height = HEIGHT
doc_width = self.unittouu(svg.attrib['width'])
if doc_width <= 0:
doc_width = WIDTH
svg = self.document.getroot()
doc_height = self.unittouu(svg.attrib['height'])
if doc_height <= 0:
doc_height = HEIGHT
doc_width = self.unittouu(svg.attrib['width'])
if doc_width <= 0:
doc_width = WIDTH
# Scale to doc_height pixels high
scale_y = float(doc_height) / float(h)
if self.options.stretch:
scale_x = scale_y * 1.5
else:
scale_x = scale_y
# Scale to doc_height pixels high
scale_y = float(doc_height) / float(h)
if self.options.stretch:
scale_x = scale_y * 1.5
else:
scale_x = scale_y
# Determine where to position the text
# We do not bother centering the text horizontally
# to do that we would need to pre-render the text to determine
# the length of the longest line. That's too much bother so
# we just skip that potential nice-to-have.
x = float(doc_width) / (2.0 * scale_x)
y = float(MAX_H) / scale_y
# Determine where to position the text
# We do not bother centering the text horizontally
# to do that we would need to pre-render the text to determine
# the length of the longest line. That's too much bother so
# we just skip that potential nice-to-have.
x = float(doc_width) / (2.0 * scale_x)
y = float(MAX_H) / scale_y
# Get the two type faces
name1 = self.options.face1
if name1 in map_our_names_to_hersheydata:
name1 = map_our_names_to_hersheydata[name1]
face1 = eval('hersheydata.' + name1)
name2 = self.options.face2
if name2 in map_our_names_to_hersheydata:
name2 = map_our_names_to_hersheydata[name2]
face2 = eval('hersheydata.' + name2)
# Get the two type faces
name1 = self.options.face1
if name1 in map_our_names_to_hersheydata:
name1 = map_our_names_to_hersheydata[name1]
face1 = eval('hersheydata.' + name1)
name2 = self.options.face2
if name2 in map_our_names_to_hersheydata:
name2 = map_our_names_to_hersheydata[name2]
face2 = eval('hersheydata.' + name2)
# Create the group which will contain all of the text
# We DO NOT make this a child of the current layer as that
# would subject us to any transforms it might have. That's
# an issue even in the simple case of someone opening a default
# document and then changing its dimensions to 3200 x 800:
# Inkscape imposes a transform in that situation. While that
# transform is no big deal, it's another complication in trying
# to just make the resulting text look right (right size, right
# approximate position, etc.).
# Create the group which will contain all of the text
# We DO NOT make this a child of the current layer as that
# would subject us to any transforms it might have. That's
# an issue even in the simple case of someone opening a default
# document and then changing its dimensions to 3200 x 800:
# Inkscape imposes a transform in that situation. While that
# transform is no big deal, it's another complication in trying
# to just make the resulting text look right (right size, right
# approximate position, etc.).
if self.options.flip:
attribs = {'transform': 'matrix(-%f,0,0,-%f,%d,%d)' % (scale_x, scale_y, doc_width, doc_height)}
else:
attribs = {'transform': 'scale(%f,%f)' % (scale_x, scale_y)}
container = inkex.etree.SubElement(self.document.getroot(), 'g', attribs)
if self.options.flip:
attribs = {'transform': 'matrix(-%f,0,0,-%f,%d,%d)' % (scale_x, scale_y, doc_width, doc_height)}
else:
attribs = {'transform': 'scale(%f,%f)' % (scale_x, scale_y)}
container = inkex.etree.SubElement(self.document.getroot(), 'g', attribs)
# Finally, we render each line of text
for i in range(0, len(lines)):
if lines[i] != '':
g = inkex.etree.SubElement(container, 'g')
renderLine(g, x, y, lines[i], face1, face2)
y += MAX_H + LINE_SKIP
# Finally, we render each line of text
for i in range(0, len(lines)):
if lines[i] != '':
g = inkex.etree.SubElement(container, 'g')
renderLine(g, x, y, lines[i], face1, face2)
y += MAX_H + LINE_SKIP
if __name__ == '__main__':
e = AcrosticText()
e.affect()
e = AcrosticText()
e.affect()

Wyświetl plik

@ -34,84 +34,84 @@ import simplestyle
class EggBot_PostProcessTraceBitmap(inkex.Effect):
def __init__(self):
inkex.Effect.__init__(self)
self.OptionParser.add_option(
"--outlineRegions", action="store", dest="outlineRegions",
type="inkbool", default=True,
help="Outline the regions with a stroked line of the same color as the region itself")
self.OptionParser.add_option(
"--fillRegions", action="store", dest="fillRegions",
type="inkbool", default=True,
help="Fill regions with color")
self.OptionParser.add_option(
"--removeImage", action="store", dest="removeImage",
type="inkbool", default=True,
help="Remove the traced bitmap image from the drawing")
def __init__(self):
inkex.Effect.__init__(self)
self.OptionParser.add_option(
"--outlineRegions", action="store", dest="outlineRegions",
type="inkbool", default=True,
help="Outline the regions with a stroked line of the same color as the region itself")
self.OptionParser.add_option(
"--fillRegions", action="store", dest="fillRegions",
type="inkbool", default=True,
help="Fill regions with color")
self.OptionParser.add_option(
"--removeImage", action="store", dest="removeImage",
type="inkbool", default=True,
help="Remove the traced bitmap image from the drawing")
def effect(self):
def effect(self):
root = self.document.getroot()
root = self.document.getroot()
count = 0
for path in self.document.xpath('//svg:path', namespaces=inkex.NSS):
count = 0
for path in self.document.xpath('//svg:path', namespaces=inkex.NSS):
# Default settings for now
stroke, fill, color = ('none', 'none', 'unknown')
# Default settings for now
stroke, fill, color = ('none', 'none', 'unknown')
# Get the paths style attribute
style = simplestyle.parseStyle(path.get('style', ''))
# Obtain the fill color from the path's style attribute
if 'fill' in style:
color = style['fill']
if self.options.fillRegions:
fill = color
if self.options.outlineRegions:
stroke = color
# Get the paths style attribute
style = simplestyle.parseStyle(path.get('style', ''))
# Obtain the fill color from the path's style attribute
if 'fill' in style:
color = style['fill']
if self.options.fillRegions:
fill = color
if self.options.outlineRegions:
stroke = color
# Now add or change the fill color in the path's style
style['fill'] = fill
# Now add or change the fill color in the path's style
style['fill'] = fill
# Add or change the stroke behavior in the path's style
style['stroke'] = stroke
# Add or change the stroke behavior in the path's style
style['stroke'] = stroke
# And change the style attribute for the path
path.set('style', simplestyle.formatStyle(style))
# And change the style attribute for the path
path.set('style', simplestyle.formatStyle(style))
# Create a group <g> element under the document root
layer = inkex.etree.SubElement(root, inkex.addNS('g', 'svg'))
# Create a group <g> element under the document root
layer = inkex.etree.SubElement(root, inkex.addNS('g', 'svg'))
# Add Inkscape layer attributes to this new group
count += 1
layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
layer.set(inkex.addNS('label', 'inkscape'), '%d - %s' % (count, color))
# Add Inkscape layer attributes to this new group
count += 1
layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
layer.set(inkex.addNS('label', 'inkscape'), '%d - %s' % (count, color))
# Now move this path from where it was to being a child
# of this new group/layer we just made
layer.append(path)
# Now move this path from where it was to being a child
# of this new group/layer we just made
layer.append(path)
# Remove any image
# For color scans, Trace Bitmap seems to put the
# image in the same layer & group as the traced regions.
# BUT, for gray scans, it seems to leave the image by
# itself as a child of the root document
# Remove any image
# For color scans, Trace Bitmap seems to put the
# image in the same layer & group as the traced regions.
# BUT, for gray scans, it seems to leave the image by
# itself as a child of the root document
if self.options.removeImage:
for node in self.document.xpath('//svg:image', namespaces=inkex.NSS):
parent = node.getparent()
if (parent.tag == 'svg') or \
(parent.tag == inkex.addNS('svg', 'svg')):
parent.remove(node)
else:
gparent = parent.getparent()
try:
gparent.remove(parent)
except:
parent.remove(node)
if self.options.removeImage:
for node in self.document.xpath('//svg:image', namespaces=inkex.NSS):
parent = node.getparent()
if (parent.tag == 'svg') or \
(parent.tag == inkex.addNS('svg', 'svg')):
parent.remove(node)
else:
gparent = parent.getparent()
try:
gparent.remove(parent)
except:
parent.remove(node)
inkex.errormsg(gettext.gettext('Finished. Created %d layers') % count)
inkex.errormsg(gettext.gettext('Finished. Created %d layers') % count)
if __name__ == '__main__':
e = EggBot_PostProcessTraceBitmap()
e.affect()
e = EggBot_PostProcessTraceBitmap()
e.affect()

Wyświetl plik

@ -37,336 +37,336 @@ VERSION = 1
def parseDesc(str):
"""
Create a dictionary from string description
"""
"""
Create a dictionary from string description
"""
if str is None:
return {}
else:
return dict([tok.split(':') for tok in str.split(';') if len(tok)])
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
"""
"""
Format an inline name1:value1;name2:value2;... style attribute value
from a dictionary
"""
return ';'.join([atr + ':' + str(val) for atr, val in d.iteritems()])
return ';'.join([atr + ':' + str(val) for atr, val in d.iteritems()])
def drawSine(cycles=8, rn=0, rm=0, nPoints=50, offset=[0, 0],
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'
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.
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.
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.
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.
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.
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.
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'.
"""
fun
May be either 'sine' or 'lace'.
"""
"""
A complicated way of plotting y = sin(x)
"""
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.
"""
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.
"""
bounded = False
bounded = False
if (not (bound1 is None)) and (not (bound2 is None)) and \
(len(bound1) > 0) and (len(bound2) > 0):
if (not (bound1 is None)) and (not (bound2 is None)) and \
(len(bound1) > 0) and (len(bound2) > 0):
func = parseDesc(bound1)
if len(func) == 0:
return None, None
m1 = int(func['rm'])
if m1 == 0:
xMin1 = float(0)
else:
xMin1 = 2 * pi * float(func['rn']) / float(m1)
xMax1 = xMin1 + 2 * pi * float(func['cycles'])
yMin1 = float(-1)
yMax1 = float(+1)
yScale1 = float(func['height']) / (yMax1 - yMin1)
yOffset1 = float(func['y'])
Y1s = lambda s: yOffset1 - yScale1 * sin(xMin1 + (xMax1 - xMin1) * s)
func = parseDesc(bound1)
if len(func) == 0:
return None, None
m1 = int(func['rm'])
if m1 == 0:
xMin1 = float(0)
else:
xMin1 = 2 * pi * float(func['rn']) / float(m1)
xMax1 = xMin1 + 2 * pi * float(func['cycles'])
yMin1 = float(-1)
yMax1 = float(+1)
yScale1 = float(func['height']) / (yMax1 - yMin1)
yOffset1 = float(func['y'])
Y1s = lambda s: yOffset1 - yScale1 * sin(xMin1 + (xMax1 - xMin1) * s)
func = parseDesc(bound2)
if len(func) == 0:
return None, None
m2 = int(func['rm'])
if m2 == 0:
xMin2 = float(0)
else:
xMin2 = 2 * pi * float(func['rn']) / float(m2)
xMax2 = xMin2 + 2 * pi * float(func['cycles'])
yMin2 = float(-1)
yMax2 = float(+1)
yScale2 = float(func['height']) / (yMax2 - yMin2)
yOffset2 = float(func['y'])
Y2s = lambda s: yOffset2 - yScale2 * sin(xMin2 + (xMax2 - xMin2) * s)
func = parseDesc(bound2)
if len(func) == 0:
return None, None
m2 = int(func['rm'])
if m2 == 0:
xMin2 = float(0)
else:
xMin2 = 2 * pi * float(func['rn']) / float(m2)
xMax2 = xMin2 + 2 * pi * float(func['cycles'])
yMin2 = float(-1)
yMax2 = float(+1)
yScale2 = float(func['height']) / (yMax2 - yMin2)
yOffset2 = float(func['y'])
Y2s = lambda s: yOffset2 - yScale2 * sin(xMin2 + (xMax2 - xMin2) * s)
bounded = True
bounded = True
rescale = float(rescale)
xOffset = float(offset[0])
yOffset = float(offset[1])
rescale = float(rescale)
xOffset = float(offset[0])
yOffset = float(offset[1])
# Each cycle is 2pi
n, m = int(0), int(0)
r = 2 * pi * float(cycles)
if (int(rm) == 0) or (int(rn) == 0):
xMin = float(0)
else:
xMin = 2 * pi * float(rn) / float(rm)
xMax = xMin + r
xScale = float(width) / r # width / ( xMax - xMin )
# Each cycle is 2pi
n, m = int(0), int(0)
r = 2 * pi * float(cycles)
if (int(rm) == 0) or (int(rn) == 0):
xMin = float(0)
else:
xMin = 2 * pi * float(rn) / float(rm)
xMax = xMin + r
xScale = float(width) / r # width / ( xMax - xMin )
yMin = float(-1)
yMax = float(+1)
yScale = float(height) / (yMax - yMin)
yMin = float(-1)
yMax = float(+1)
yScale = float(height) / (yMax - yMin)
# Our parametric equations which map the results to our drawing window
# Note the "-yScale" that's because in SVG, the y-axis runs "backwards"
if (fun is None) or (fun == ''):
fun = 'sine'
fun = fun.lower()
if fun == 'sine':
Xs = lambda s: xOffset + xScale * (xMax - xMin) * s
Ys = lambda s: yOffset - yScale * sin(xMin + (xMax - xMin) * s)
dYdXs = lambda s: -yScale * cos(xMin + (xMax - xMin) * s) / xScale
elif fun == 'lace':
Xs = lambda s: xOffset + xScale * ((xMax - xMin) * s + 2 * sin(2 * s * (xMax - xMin) + pi))
dXs = lambda s: xScale * (xMax - xMin) * (1.0 + 4.0 * cos(2 * s * (xMax - xMin) + pi))
Ys = lambda s: yOffset - yScale * sin(xMin + (xMax - xMin) * s)
dYs = lambda s: -yScale * cos(xMin + (xMax - xMin) * s) * (xMax - xMin)
dYdXs = lambda s: dYs(s) / dXs(s)
else:
inkex.errormsg('Unknown function %s specified' % fun)
return
# Our parametric equations which map the results to our drawing window
# Note the "-yScale" that's because in SVG, the y-axis runs "backwards"
if (fun is None) or (fun == ''):
fun = 'sine'
fun = fun.lower()
if fun == 'sine':
Xs = lambda s: xOffset + xScale * (xMax - xMin) * s
Ys = lambda s: yOffset - yScale * sin(xMin + (xMax - xMin) * s)
dYdXs = lambda s: -yScale * cos(xMin + (xMax - xMin) * s) / xScale
elif fun == 'lace':
Xs = lambda s: xOffset + xScale * ((xMax - xMin) * s + 2 * sin(2 * s * (xMax - xMin) + pi))
dXs = lambda s: xScale * (xMax - xMin) * (1.0 + 4.0 * cos(2 * s * (xMax - xMin) + pi))
Ys = lambda s: yOffset - yScale * sin(xMin + (xMax - xMin) * s)
dYs = lambda s: -yScale * cos(xMin + (xMax - xMin) * s) * (xMax - xMin)
dYdXs = lambda s: dYs(s) / dXs(s)
else:
inkex.errormsg('Unknown function %s specified' % fun)
return
# Derivatives: remember the chain rule....
# dXs = lambda s: xScale * ( xMax - xMin )
# dYs = lambda s: -yScale * cos( xMin + ( xMax - xMin ) * s ) * ( xMax - xMin )
# Derivatives: remember the chain rule....
# dXs = lambda s: xScale * ( xMax - xMin )
# dYs = lambda s: -yScale * cos( xMin + ( xMax - xMin ) * s ) * ( xMax - xMin )
# xThird is 1/3 the step size
nPoints = int(nPoints)
# xThird is 1/3 the step size
nPoints = int(nPoints)
# xThird is 1/3 the step size; note that Xs(1) - Xs(0) = xScale * ( xMax - xMin )
xThird = (Xs(float(1)) - Xs(float(0))) / float(3 * (nPoints - 1))
# xThird is 1/3 the step size; note that Xs(1) - Xs(0) = xScale * ( xMax - xMin )
xThird = (Xs(float(1)) - Xs(float(0))) / float(3 * (nPoints - 1))
if bounded:
yUpper = Y2s(float(0))
yLower = Y1s(float(0))
yOffset = 0.5 * (yUpper + yLower)
yUpper = yOffset + rescale * (yUpper - yOffset)
yLower = yOffset + rescale * (yLower - yOffset)
yScale = (yUpper - yLower) / (yMax - yMin)
if bounded:
yUpper = Y2s(float(0))
yLower = Y1s(float(0))
yOffset = 0.5 * (yUpper + yLower)
yUpper = yOffset + rescale * (yUpper - yOffset)
yLower = yOffset + rescale * (yLower - yOffset)
yScale = (yUpper - yLower) / (yMax - yMin)
x1 = Xs(float(0))
y1 = Ys(float(0))
dx1 = float(1)
dy1 = dYdXs(float(0))
x1 = Xs(float(0))
y1 = Ys(float(0))
dx1 = float(1)
dy1 = dYdXs(float(0))
# Starting point in the path is ( x, sin(x) )
pathData = []
pathData.append(['M ', [x1, y1]])
# Starting point in the path is ( x, sin(x) )
pathData = []
pathData.append(['M ', [x1, y1]])
for i in range(1, nPoints):
for i in range(1, nPoints):
s = float(i) / float(nPoints - 1)
if bounded:
yUpper = Y2s(s)
yLower = Y1s(s)
yOffset = 0.5 * (yUpper + yLower)
yUpper = yOffset + rescale * (yUpper - yOffset)
yLower = yOffset + rescale * (yLower - yOffset)
yScale = (yUpper - yLower) / (yMax - yMin)
s = float(i) / float(nPoints - 1)
if bounded:
yUpper = Y2s(s)
yLower = Y1s(s)
yOffset = 0.5 * (yUpper + yLower)
yUpper = yOffset + rescale * (yUpper - yOffset)
yLower = yOffset + rescale * (yLower - yOffset)
yScale = (yUpper - yLower) / (yMax - yMin)
x2 = Xs(s)
y2 = Ys(s)
dx2 = float(1)
dy2 = dYdXs(s)
if dy2 > float(10):
dy2 = float(10)
elif dy2 < float(-10):
dy2 = float(-10)
x2 = Xs(s)
y2 = Ys(s)
dx2 = float(1)
dy2 = dYdXs(s)
if dy2 > float(10):
dy2 = float(10)
elif dy2 < float(-10):
dy2 = float(-10)
# Add another segment to the plot
if spline:
pathData.append([' C ',
[x1 + (dx1 * xThird),
y1 + (dy1 * xThird),
x2 - (dx2 * xThird),
y2 - (dy2 * xThird),
x2, y2]])
else:
pathData.append([' L ', [x1, y1]])
pathData.append([' L ', [x2, y2]])
x1 = x2
y1 = y2
dx1 = dx2
dy1 = dy2
# Add another segment to the plot
if spline:
pathData.append([' C ',
[x1 + (dx1 * xThird),
y1 + (dy1 * xThird),
x2 - (dx2 * xThird),
y2 - (dy2 * xThird),
x2, y2]])
else:
pathData.append([' L ', [x1, y1]])
pathData.append([' L ', [x2, y2]])
x1 = x2
y1 = y2
dx1 = dx2
dy1 = dy2
pathDesc = \
'version:%d;style:linear;function:sin(x);' % VERSION + \
'cycles:%f;rn:%d;rm:%d;points:%d' % (cycles, rn, rm, nPoints) + \
';width:%d;height:%d;x:%d;y:%d' % (width, height, offset[0], offset[1])
pathDesc = \
'version:%d;style:linear;function:sin(x);' % VERSION + \
'cycles:%f;rn:%d;rm:%d;points:%d' % (cycles, rn, rm, nPoints) + \
';width:%d;height:%d;x:%d;y:%d' % (width, height, offset[0], offset[1])
return pathData, pathDesc
return pathData, pathDesc
class SpiroSine(inkex.Effect):
nsURI = 'http://sample.com/ns'
nsPrefix = 'doof'
nsURI = 'http://sample.com/ns'
nsPrefix = 'doof'
def __init__(self):
def __init__(self):
inkex.Effect.__init__(self)
inkex.Effect.__init__(self)
self.OptionParser.add_option("--tab", # NOTE: value is not used.
action="store", type="string",
dest="tab", default="splash",
help="The active tab when Apply was pressed")
self.OptionParser.add_option("--tab", # NOTE: value is not used.
action="store", type="string",
dest="tab", default="splash",
help="The active tab when Apply was pressed")
self.OptionParser.add_option('--fCycles', dest='fCycles',
type='float', default=float(10), action='store',
help='Number of cycles (periods)')
self.OptionParser.add_option('--fCycles', dest='fCycles',
type='float', default=float(10), action='store',
help='Number of cycles (periods)')
self.OptionParser.add_option('--nrN', dest='nrN',
type='int', default=int(0), action='store',
help='Start x at 2 * pi * n / m')
self.OptionParser.add_option('--nrN', dest='nrN',
type='int', default=int(0), action='store',
help='Start x at 2 * pi * n / m')
self.OptionParser.add_option('--nrM', dest='nrM',
type='int', default=int(0), action='store',
help='Start x at 2 * pi * n / m')
self.OptionParser.add_option('--nrM', dest='nrM',
type='int', default=int(0), action='store',
help='Start x at 2 * pi * n / m')
self.OptionParser.add_option('--fRecess', dest='fRecess',
type='float', default=float(2), action='store',
help='Recede from envelope by factor')
self.OptionParser.add_option('--fRecess', dest='fRecess',
type='float', default=float(2), action='store',
help='Recede from envelope by factor')
self.OptionParser.add_option("--nSamples", dest="nSamples",
type="int", default=float(50), action="store",
help="Number of points to sample")
self.OptionParser.add_option("--nSamples", dest="nSamples",
type="int", default=float(50), action="store",
help="Number of points to sample")
self.OptionParser.add_option("--nWidth", dest="nWidth",
type="int", default=int(3200), action="store",
help="Width in pixels")
self.OptionParser.add_option("--nWidth", dest="nWidth",
type="int", default=int(3200), action="store",
help="Width in pixels")
self.OptionParser.add_option("--nHeight", dest="nHeight",
type="int", default=int(100), action="store",
help="Height in pixels")
self.OptionParser.add_option("--nHeight", dest="nHeight",
type="int", default=int(100), action="store",
help="Height in pixels")
self.OptionParser.add_option("--nOffsetX", dest="nOffsetX",
type="int", default=int(0), action="store",
help="Starting x coordinate (pixels)")
self.OptionParser.add_option("--nOffsetX", dest="nOffsetX",
type="int", default=int(0), action="store",
help="Starting x coordinate (pixels)")
self.OptionParser.add_option("--nOffsetY", dest="nOffsetY",
type="int", default=int(400), action="store",
help="Starting y coordinate (pixels)")
self.OptionParser.add_option("--nOffsetY", dest="nOffsetY",
type="int", default=int(400), action="store",
help="Starting y coordinate (pixels)")
self.OptionParser.add_option('--bLace', dest='bLace',
type='inkbool', default=False, action='store',
help='Lace')
self.OptionParser.add_option('--bLace', dest='bLace',
type='inkbool', default=False, action='store',
help='Lace')
self.OptionParser.add_option('--bSpline', dest='bSpline',
type='inkbool', default=True, action='store',
help='Spline')
self.OptionParser.add_option('--bSpline', dest='bSpline',
type='inkbool', default=True, action='store',
help='Spline')
self.recess = float(0.95)
self.recess = float(0.95)
def effect(self):
def effect(self):
inkex.NSS[self.nsPrefix] = self.nsURI
inkex.NSS[self.nsPrefix] = self.nsURI
if self.options.bLace:
func = 'lace'
else:
func = 'sine'
if self.options.bLace:
func = 'lace'
else:
func = 'sine'
fRecess = float(1)
if self.options.fRecess > 0.0:
fRecess = 1.0 - self.options.fRecess / float(100)
if fRecess <= 0.0:
fRecess = float(0)
fRecess = float(1)
if self.options.fRecess > 0.0:
fRecess = 1.0 - self.options.fRecess / float(100)
if fRecess <= 0.0:
fRecess = float(0)
if self.options.ids:
if len(self.options.ids) == 2:
attr = self.selected[self.options.ids[0]].attrib
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,
fRecess,
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)
if self.options.ids:
if len(self.options.ids) == 2:
attr = self.selected[self.options.ids[0]].attrib
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,
fRecess,
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,
fRecess,
None, None, func, self.options.bSpline)
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,
fRecess,
None, None, func, self.options.bSpline)
style = {'stroke': 'black', 'stroke-width': '1', 'fill': 'none'}
path_attrs = {
'style': simplestyle.formatStyle(style),
'd': simplepath.formatPath(path_data),
inkex.addNS('desc', self.nsPrefix): path_desc}
newpath = inkex.etree.SubElement(self.document.getroot(),
inkex.addNS('path', 'svg '), path_attrs, nsmap=inkex.NSS)
style = {'stroke': 'black', 'stroke-width': '1', 'fill': 'none'}
path_attrs = {
'style': simplestyle.formatStyle(style),
'd': simplepath.formatPath(path_data),
inkex.addNS('desc', self.nsPrefix): path_desc}
newpath = inkex.etree.SubElement(self.document.getroot(),
inkex.addNS('path', 'svg '), path_attrs, nsmap=inkex.NSS)
if __name__ == '__main__':
e = SpiroSine()
e.affect()
e = SpiroSine()
e.affect()

Wyświetl plik

@ -82,29 +82,29 @@ import simplestyle
# be used to deal with a face being removed from hersheydata.py
map_our_names_to_hersheydata = {
'astrology': 'astrology',
'cursive': 'cursive',
'cyrillic': 'cyrillic',
'futural': 'futural',
'futuram': 'futuram',
'gothiceng': 'gothiceng',
'gothicger': 'gothicger',
'gothicita': 'gothicita',
'greek': 'greek',
'japanese': 'japanese',
'markers': 'markers',
'mathlow': 'mathlow',
'mathupp': 'mathupp',
'meteorology': 'meteorology',
'music': 'music',
'scriptc': 'scriptc',
'scripts': 'scripts',
'symbolic': 'symbolic',
'timesg': 'timesg',
'timesi': 'timesi',
'timesib': 'timesib',
'timesr': 'timesr',
'timesrb': 'timesrb'}
'astrology': 'astrology',
'cursive': 'cursive',
'cyrillic': 'cyrillic',
'futural': 'futural',
'futuram': 'futuram',
'gothiceng': 'gothiceng',
'gothicger': 'gothicger',
'gothicita': 'gothicita',
'greek': 'greek',
'japanese': 'japanese',
'markers': 'markers',
'mathlow': 'mathlow',
'mathupp': 'mathupp',
'meteorology': 'meteorology',
'music': 'music',
'scriptc': 'scriptc',
'scripts': 'scripts',
'symbolic': 'symbolic',
'timesg': 'timesg',
'timesi': 'timesi',
'timesib': 'timesib',
'timesr': 'timesr',
'timesrb': 'timesrb'}
# The following two routines are lifted with impunity from Windell H. Oskay's
@ -112,39 +112,39 @@ map_our_names_to_hersheydata = {
# Copyright 2011, Windell H. Oskay, www.evilmadscientist.com
def draw_svg_text(char, face, offset, vertoffset, parent):
style = {'stroke': '#000000', 'fill': 'none', 'stroke-linecap': 'round', 'stroke-linejoin': 'round'}
# Apply rounding to ends so that user gets best impression of final printed text appearance.
pathString = face[char]
splitString = pathString.split()
midpoint = offset - float(splitString[0])
i = pathString.find("M")
if i >= 0:
pathString = pathString[i:] # portion after first move
trans = 'translate(' + str(midpoint) + ',' + str(vertoffset) + ')'
text_attribs = {'style': simplestyle.formatStyle(style), 'd': pathString, 'transform': trans}
inkex.etree.SubElement(parent, inkex.addNS('path', 'svg'), text_attribs)
return midpoint + float(splitString[1]) # new offset value
style = {'stroke': '#000000', 'fill': 'none', 'stroke-linecap': 'round', 'stroke-linejoin': 'round'}
# Apply rounding to ends so that user gets best impression of final printed text appearance.
pathString = face[char]
splitString = pathString.split()
midpoint = offset - float(splitString[0])
i = pathString.find("M")
if i >= 0:
pathString = pathString[i:] # portion after first move
trans = 'translate(' + str(midpoint) + ',' + str(vertoffset) + ')'
text_attribs = {'style': simplestyle.formatStyle(style), 'd': pathString, 'transform': trans}
inkex.etree.SubElement(parent, inkex.addNS('path', 'svg'), text_attribs)
return midpoint + float(splitString[1]) # new offset value
def renderText(parent, markup):
# Embed text in group to make manipulation easier:
g_attribs = {inkex.addNS('label', 'inkscape'): 'Hershey Text'}
g = inkex.etree.SubElement(parent, 'g', g_attribs)
# Embed text in group to make manipulation easier:
g_attribs = {inkex.addNS('label', 'inkscape'): 'Hershey Text'}
g = inkex.etree.SubElement(parent, 'g', g_attribs)
w = 0 # Initial spacing offset
spacing = 3 # spacing between letters
w = 0 # Initial spacing offset
spacing = 3 # spacing between letters
for Face, Text in markup:
if Face in map_our_names_to_hersheydata:
Face = map_our_names_to_hersheydata[Face]
font = eval('hersheydata.' + Face)
letterVals = [ord(q) - 32 for q in Text]
for q in letterVals:
if (q < 0) or (q > 95):
w += 2 * spacing
else:
w = draw_svg_text(q, font, w, 0, g)
return g, w
for Face, Text in markup:
if Face in map_our_names_to_hersheydata:
Face = map_our_names_to_hersheydata[Face]
font = eval('hersheydata.' + Face)
letterVals = [ord(q) - 32 for q in Text]
for q in letterVals:
if (q < 0) or (q > 95):
w += 2 * spacing
else:
w = draw_svg_text(q, font, w, 0, g)
return g, w
# The generic font "families" we support
@ -152,9 +152,9 @@ generic_families = ('sans', 'script', 'times')
# Convert "family-name" + "bold-0-or-1" + "italic-0-or-1" to a typeface name
family_to_font = {
'sans00': 'futural', 'sans10': 'futuram', 'sans01': 'futural', 'sans11': 'futuram',
'times00': 'timesr', 'times10': 'timesrb', 'times01': 'timesi', 'times11': 'timesib',
'script00': 'scripts', 'script10': 'scriptc', 'script01': 'scripts', 'script11': 'scriptc'}
'sans00': 'futural', 'sans10': 'futuram', 'sans01': 'futural', 'sans11': 'futuram',
'times00': 'timesr', 'times10': 'timesrb', 'times01': 'timesi', 'times11': 'timesib',
'script00': 'scripts', 'script10': 'scriptc', 'script01': 'scripts', 'script11': 'scriptc'}
emphasis_is_bold = {'sans': True, 'times': False, 'script': True}
# Short list of entity references
@ -162,282 +162,282 @@ entity_refs = {'&lt;': '<', '&gt;': '>', '&amp;': '&', '&quot;': '"', '&apos': "
def normalize_possible_EMS_string(tag):
# Normalizes tag name by removing any spaces
sNormalizedTag = tag.replace(' ', '')
return sNormalizedTag
# Normalizes tag name by removing any spaces
sNormalizedTag = tag.replace(' ', '')
return sNormalizedTag
def is_valid_EMS_name(tag):
# returns true if family is one of the "EMS" faces in hersheydata.py
# else false
sNormalizedTag = normalize_possible_EMS_string(tag)
bRetVal = False # default assumption
try:
fontgroup = hersheydata.group_allfonts
except:
# User probably has old version of hersheydata.py
pass
else:
for f in fontgroup:
if f[0] == sNormalizedTag:
bRetVal = True
break
# returns true if family is one of the "EMS" faces in hersheydata.py
# else false
sNormalizedTag = normalize_possible_EMS_string(tag)
bRetVal = False # default assumption
try:
fontgroup = hersheydata.group_allfonts
except:
# User probably has old version of hersheydata.py
pass
else:
for f in fontgroup:
if f[0] == sNormalizedTag:
bRetVal = True
break
return bRetVal
return bRetVal
def pickFace(family, bold=False, italics=False, emphasis=False):
if (family is None) or (family == ''):
return None
if (family is None) or (family == ''):
return None
b = '0'
i = '0'
b = '0'
i = '0'
# If using a generic font family, then determine how to map <em>
if emphasis and (family in generic_families):
if emphasis_is_bold[family]:
bold = True
else:
italics = True
# If using a generic font family, then determine how to map <em>
if emphasis and (family in generic_families):
if emphasis_is_bold[family]:
bold = True
else:
italics = True
if bold:
b = '1'
if bold:
b = '1'
if italics:
i = '1'
if italics:
i = '1'
if (family + b + i) in family_to_font:
return family_to_font[family + b + i]
if (family + b + i) in family_to_font:
return family_to_font[family + b + i]
return family
return family
def processMarkup(text, family='sans'):
if text is None:
text = ''
if text is None:
text = ''
# By default we assume 'sans'
if (family is None) or (family == ''):
family = 'sans'
family_default = family
face_stack = [family]
# By default we assume 'sans'
if (family is None) or (family == ''):
family = 'sans'
family_default = family
face_stack = [family]
# Bold and italics off
bold = False
emphasis = False
italic = False
# Bold and italics off
bold = False
emphasis = False
italic = False
# Set the current typeface
face = pickFace(family, bold, italic, emphasis)
# Set the current typeface
face = pickFace(family, bold, italic, emphasis)
# And the result of markup processing so far
markup = []
# And the result of markup processing so far
markup = []
# We keep a queue / list of the open markup tags
# When a tag is closed, we expect it to be well nested. To enforce
# that expectation, we make sure that we are closing the most recently
# opened tag. While this may seem overly picky, it's easier than worrying
# issues like, "Does closing a typeface imply implicitly closing <b> or <it>?"
# And, "Does starting a new typeface imply closing the prior selected face?"
tags_used = []
# We keep a queue / list of the open markup tags
# When a tag is closed, we expect it to be well nested. To enforce
# that expectation, we make sure that we are closing the most recently
# opened tag. While this may seem overly picky, it's easier than worrying
# issues like, "Does closing a typeface imply implicitly closing <b> or <it>?"
# And, "Does starting a new typeface imply closing the prior selected face?"
tags_used = []
outstr = ''
i = 0
while i < len(text):
# An entity reference?
if text[i] == '&':
j = text.find(';', i + 1)
if j != -1:
eref = text[i:j + 1]
if eref in entity_refs:
outstr += entity_refs[eref]
i = j + 1
else:
inkex.errormsg('Ignoring the unrecognized entity reference %s.' % eref)
outstr += eref
i = j + 1
else:
inkex.errormsg('An unescaped "&" was encountered; please replace it with "&amp;".')
break
outstr = ''
i = 0
while i < len(text):
# An entity reference?
if text[i] == '&':
j = text.find(';', i + 1)
if j != -1:
eref = text[i:j + 1]
if eref in entity_refs:
outstr += entity_refs[eref]
i = j + 1
else:
inkex.errormsg('Ignoring the unrecognized entity reference %s.' % eref)
outstr += eref
i = j + 1
else:
inkex.errormsg('An unescaped "&" was encountered; please replace it with "&amp;".')
break
# Start of a tag (start-tag or end-tag? self-closing tags not supported)
elif text[i] == '<':
j = text.find('>', i + 1)
if (j != -1) and (j > (i + 1)):
# Start of a tag (start-tag or end-tag? self-closing tags not supported)
elif text[i] == '<':
j = text.find('>', i + 1)
if (j != -1) and (j > (i + 1)):
tag = text[i + 1:j]
i = j + 1
tag = text[i + 1:j]
i = j + 1
if tag[0] == '/':
# This is an end-tag (closing tag)
close = True
tag = tag[1:]
if tag[0] == '/':
# This is an end-tag (closing tag)
close = True
tag = tag[1:]
# Ensure that the most recently opened tag is that which we are closing here
# We'll pop the most recent tag from the queue of opened tags and see if
# it matches
if len(tags_used) == 0:
inkex.errormsg('The ending tag </%s> appeared before any start tag <%s>.' % (tag, tag))
break
else:
old_tag = tags_used.pop()
if old_tag != tag:
inkex.errormsg('The ending tag </%s> does not appear to be correctly nested; it tried to close the tag <%s>. Sorry, but all tags must be properly nested.' % (tag, old_tag))
break
else:
# Start tag (opening tag)
# Push it onto the queue of opened tags
close = False
tags_used.append(tag)
# Ensure that the most recently opened tag is that which we are closing here
# We'll pop the most recent tag from the queue of opened tags and see if
# it matches
if len(tags_used) == 0:
inkex.errormsg('The ending tag </%s> appeared before any start tag <%s>.' % (tag, tag))
break
else:
old_tag = tags_used.pop()
if old_tag != tag:
inkex.errormsg('The ending tag </%s> does not appear to be correctly nested; it tried to close the tag <%s>. Sorry, but all tags must be properly nested.' % (tag, old_tag))
break
else:
# Start tag (opening tag)
# Push it onto the queue of opened tags
close = False
tags_used.append(tag)
if (tag == 'b') or (tag == 'strong'):
if bold == close:
# Push prior string and font onto the stack
if outstr != '':
markup.append([face, outstr])
outstr = ''
if (tag == 'b') or (tag == 'strong'):
if bold == close:
# Push prior string and font onto the stack
if outstr != '':
markup.append([face, outstr])
outstr = ''
# Start a new boldface string
bold = not bold
face = pickFace(family, bold, italic, emphasis)
# Start a new boldface string
bold = not bold
face = pickFace(family, bold, italic, emphasis)
elif tag == 'i':
if italic == close:
# Push the prior string and font unto the stack
if outstr != '':
markup.append([face, outstr])
outstr = ''
elif tag == 'i':
if italic == close:
# Push the prior string and font unto the stack
if outstr != '':
markup.append([face, outstr])
outstr = ''
# Start a new italicized string
italic = not italic
face = pickFace(family, bold, italic, emphasis)
# Start a new italicized string
italic = not italic
face = pickFace(family, bold, italic, emphasis)
elif tag == 'em':
if emphasis == close:
# Push the prior string and font unto the stack
if outstr != '':
markup.append([face, outstr])
outstr = ''
elif tag == 'em':
if emphasis == close:
# Push the prior string and font unto the stack
if outstr != '':
markup.append([face, outstr])
outstr = ''
# Start a new italicized string
emphasis = not emphasis
face = pickFace(family, bold, italic, emphasis)
# Start a new italicized string
emphasis = not emphasis
face = pickFace(family, bold, italic, emphasis)
else:
bValidEMSName = is_valid_EMS_name(tag)
if bValidEMSName:
tag = normalize_possible_EMS_string(tag)
if (
(tag not in generic_families)
and (tag not in map_our_names_to_hersheydata)
and (not bValidEMSName)
):
if close:
inkex.errormsg('Ignoring the unrecognized tag </%s>.' % tag)
else:
inkex.errormsg('Ignoring the unrecognized tag <%s>.' % tag)
else:
if outstr != '':
markup.append([face, outstr])
outstr = ''
if not close:
family = tag
face_stack.append(family)
else:
if len(face_stack) > 0:
# Current face on the stack should be the one we just closed
face_stack.pop()
if len(face_stack) > 0:
family = face_stack[len(face_stack) - 1]
else:
family = default_family
else:
family = default_family
face = pickFace(family, bold, italic, emphasis)
else:
inkex.errormsg('Ignoring unescaped "<"')
outstr += '<'
i += 1
else:
outstr += text[i]
i += 1
else:
bValidEMSName = is_valid_EMS_name(tag)
if bValidEMSName:
tag = normalize_possible_EMS_string(tag)
if (
(tag not in generic_families)
and (tag not in map_our_names_to_hersheydata)
and (not bValidEMSName)
):
if close:
inkex.errormsg('Ignoring the unrecognized tag </%s>.' % tag)
else:
inkex.errormsg('Ignoring the unrecognized tag <%s>.' % tag)
else:
if outstr != '':
markup.append([face, outstr])
outstr = ''
if not close:
family = tag
face_stack.append(family)
else:
if len(face_stack) > 0:
# Current face on the stack should be the one we just closed
face_stack.pop()
if len(face_stack) > 0:
family = face_stack[len(face_stack) - 1]
else:
family = default_family
else:
family = default_family
face = pickFace(family, bold, italic, emphasis)
else:
inkex.errormsg('Ignoring unescaped "<"')
outstr += '<'
i += 1
else:
outstr += text[i]
i += 1
# We won't worry about unclosed tags -- we're not trying to be an XML or XHTML parser
# We won't worry about unclosed tags -- we're not trying to be an XML or XHTML parser
# See if there was a hard error
if i < len(text):
return None
# See if there was a hard error
if i < len(text):
return None
# And push the last text into the list of processed markup
if outstr != '':
markup.append([face, outstr])
# And push the last text into the list of processed markup
if outstr != '':
markup.append([face, outstr])
return markup
return markup
class SpiralText(inkex.Effect):
def __init__(self):
inkex.Effect.__init__(self)
self.OptionParser.add_option("--tab", # NOTE: value is not used.
action="store", type="string",
dest="tab", default="splash",
help="The active tab when Apply was pressed")
self.OptionParser.add_option("--text",
action="store", type="string",
dest="text", default="Hershey Text for Inkscape",
help="The input text to render")
self.OptionParser.add_option("--fontfamily",
action="store", type="string",
dest="fontfamily", default="sans",
help="The selected font face when Apply was pressed")
self.OptionParser.add_option("--wrap",
action="store", type="float",
dest="wrap", default=float(10),
help="Number of times to wrap the text around the egg")
self.OptionParser.add_option("--flip",
action="store", type="inkbool",
dest="flip", default=False,
help="Flip the text for plotting with the egg's bottom at the egg motor")
self.OptionParser.add_option("--stretch",
action="store", type="inkbool",
dest="stretch", default=True,
help="Stretch the text horizontally to account for egg distortions")
def __init__(self):
inkex.Effect.__init__(self)
self.OptionParser.add_option("--tab", # NOTE: value is not used.
action="store", type="string",
dest="tab", default="splash",
help="The active tab when Apply was pressed")
self.OptionParser.add_option("--text",
action="store", type="string",
dest="text", default="Hershey Text for Inkscape",
help="The input text to render")
self.OptionParser.add_option("--fontfamily",
action="store", type="string",
dest="fontfamily", default="sans",
help="The selected font face when Apply was pressed")
self.OptionParser.add_option("--wrap",
action="store", type="float",
dest="wrap", default=float(10),
help="Number of times to wrap the text around the egg")
self.OptionParser.add_option("--flip",
action="store", type="inkbool",
dest="flip", default=False,
help="Flip the text for plotting with the egg's bottom at the egg motor")
self.OptionParser.add_option("--stretch",
action="store", type="inkbool",
dest="stretch", default=True,
help="Stretch the text horizontally to account for egg distortions")
def effect(self):
def effect(self):
markup = processMarkup(self.options.text, self.options.fontfamily)
g, w = renderText(self.current_layer, markup)
markup = processMarkup(self.options.text, self.options.fontfamily)
g, w = renderText(self.current_layer, markup)
# Now to wrap the text N times around the egg, we need to scale it to have
# length 3200 * N. It's current width is w so the scale factor is (3200 * N) / w.
# Now to wrap the text N times around the egg, we need to scale it to have
# length 3200 * N. It's current width is w so the scale factor is (3200 * N) / w.
scale_x = float(3200 * self.options.wrap) / float(w)
scale_y = scale_x
if self.options.stretch:
scale_y = scale_y * 2.0 / 3.0
scale_x = float(3200 * self.options.wrap) / float(w)
scale_y = scale_x
if self.options.stretch:
scale_y = scale_y * 2.0 / 3.0
# In planning the scaling, we'd like to know the height of our line of text.
# Rather than computing its bounding box, we'll just use the height of the
# parens from the Simplex Roman font. And, we could compute that but we'll
# just use our prior knowledge of it being 32.
# In planning the scaling, we'd like to know the height of our line of text.
# Rather than computing its bounding box, we'll just use the height of the
# parens from the Simplex Roman font. And, we could compute that but we'll
# just use our prior knowledge of it being 32.
h = float(32.0)
h = float(32.0)
# And the angular tilt will be arcsine( height / (3200 * fWrap) )
svg = self.document.getroot()
height = float(self.unittouu(svg.attrib['height'])) - h * scale_y
angle = (float(180) / math.pi) * math.asin(height / float(3200 * self.options.wrap))
# And the angular tilt will be arcsine( height / (3200 * fWrap) )
svg = self.document.getroot()
height = float(self.unittouu(svg.attrib['height'])) - h * scale_y
angle = (float(180) / math.pi) * math.asin(height / float(3200 * self.options.wrap))
if self.options.flip:
angle += float(180.0)
t = 'translate(%f,%f) rotate(%f,%f,0) scale(%f,%f)' % (-w * scale_x, h * scale_y, angle,
w * scale_x, scale_x, scale_y)
else:
t = 'translate(0,%f) rotate(%f,0,0) scale(%f,%f)' % (h, angle, scale_x, scale_y)
g.set('transform', t)
if self.options.flip:
angle += float(180.0)
t = 'translate(%f,%f) rotate(%f,%f,0) scale(%f,%f)' % (-w * scale_x, h * scale_y, angle,
w * scale_x, scale_x, scale_y)
else:
t = 'translate(0,%f) rotate(%f,0,0) scale(%f,%f)' % (h, angle, scale_x, scale_y)
g.set('transform', t)
if __name__ == '__main__':
e = SpiralText()
e.affect()
e = SpiralText()
e.affect()

Wyświetl plik

@ -42,501 +42,501 @@ from simpletransform import applyTransformToPath, composeTransform, parseTransfo
def subdivideCubicPath(sp, flat, i=1):
"""
[ Lifted from eggbot.py with impunity ]
"""
[ Lifted from eggbot.py with impunity ]
Break up a bezier curve into smaller curves, each of which
is approximately a straight line within a given tolerance
(the "smoothness" defined by [flat]).
Break up a bezier curve into smaller curves, each of which
is approximately a straight line within a given tolerance
(the "smoothness" defined by [flat]).
This is a modified version of cspsubdiv.cspsubdiv(): rewritten
because recursion-depth errors on complicated line segments
could occur with cspsubdiv.cspsubdiv().
"""
This is a modified version of cspsubdiv.cspsubdiv(): rewritten
because recursion-depth errors on complicated line segments
could occur with cspsubdiv.cspsubdiv().
"""
while True:
while True:
if i >= len(sp):
return
while True:
while True:
if i >= len(sp):
return
p0 = sp[i - 1][1]
p1 = sp[i - 1][2]
p2 = sp[i][0]
p3 = sp[i][1]
p0 = sp[i - 1][1]
p1 = sp[i - 1][2]
p2 = sp[i][0]
p3 = sp[i][1]
b = (p0, p1, p2, p3)
b = (p0, p1, p2, p3)
if cspsubdiv.maxdist(b) > flat:
break
if cspsubdiv.maxdist(b) > flat:
break
i += 1
i += 1
one, two = bezmisc.beziersplitatt(b, 0.5)
sp[i - 1][2] = one[1]
sp[i][0] = two[2]
p = [one[2], one[3], two[1]]
sp[i:1] = [p]
one, two = bezmisc.beziersplitatt(b, 0.5)
sp[i - 1][2] = one[1]
sp[i][0] = two[2]
p = [one[2], one[3], two[1]]
sp[i:1] = [p]
def distanceSquared(P1, P2):
"""
Pythagorean distance formula WITHOUT the square root. Since
we just want to know if the distance is less than some fixed
fudge factor, we can just square the fudge factor once and run
with it rather than compute square roots over and over.
"""
"""
Pythagorean distance formula WITHOUT the square root. Since
we just want to know if the distance is less than some fixed
fudge factor, we can just square the fudge factor once and run
with it rather than compute square roots over and over.
"""
dx = P2[0] - P1[0]
dy = P2[1] - P1[1]
dx = P2[0] - P1[0]
dy = P2[1] - P1[1]
return dx * dx + dy * dy
return dx * dx + dy * dy
class Twist(inkex.Effect):
def __init__(self):
inkex.Effect.__init__(self)
self.OptionParser.add_option(
"--nSteps", action="store", type="int",
dest="nSteps", default=8,
help="Number of iterations to take")
self.OptionParser.add_option(
"--fRatio", action="store", type="float",
dest="fRatio", default=float(0.2),
help="Some ratio")
"""
Store each path in an associative array (dictionary) indexed
by the lxml.etree pointer for the SVG document element
containing the path. Looking up the path in the dictionary
yields a list of lists. Each of these lists is a subpath
# of the path. E.g., for the SVG path
<path d="M 10,10 l 0,5 l 5,0 l 0,-5 Z M 30,30 L 30,60"/>
we'd have two subpaths which will be reduced to absolute
coordinates.
subpath_1 = [ [10, 10], [10, 15], [15, 15], [15, 10], [10,10] ]
subpath_2 = [ [30, 30], [30, 60] ]
self.paths[<node pointer>] = [ subpath_1, subpath_2 ]
All of the paths and their subpaths could be drawn as follows:
for path in self.paths:
for subpath in self.paths[path]:
first = True
for vertex in subpath:
if first:
moveto( vertex[0], vertex[1] )
first = False
else:
lineto( vertex[0], vertex[1] )
NOTE: drawing all the paths like the above would not in general
give the correct rendering of the document UNLESS path transforms
were also tracked and applied.
"""
self.paths = {}
self.paths_clone_transform = {}
def addPathVertices(self, path, node=None, transform=None, cloneTransform=None):
"""
Decompose the path data from an SVG element into individual
subpaths, each subpath consisting of absolute move to and line
to coordinates. Place these coordinates into a list of polygon
vertices.
"""
if (not path) or (len(path) == 0):
# Nothing to do
return
# parsePath() may raise an exception. This is okay
sp = simplepath.parsePath(path)
if (not sp) or (len(sp) == 0):
# Path must have been devoid of any real content
return
# Get a cubic super path
p = cubicsuperpath.CubicSuperPath(sp)
if (not p) or (len(p) == 0):
# Probably never happens, but...
return
# if transform:
# simpletransform.applyTransformToPath( transform, p )
# Now traverse the cubic super path
subpath_list = []
subpath_vertices = []
for sp in p:
if len(subpath_vertices):
# There's a prior subpath: see if it is closed and should be saved
if distanceSquared(subpath_vertices[0], subpath_vertices[-1]) < 1:
# Keep the prior subpath: it appears to be a closed path
subpath_list.append(subpath_vertices)
subpath_vertices = []
subdivideCubicPath(sp, float(0.2))
for csp in sp:
# Add this vertex to the list of vetices
subpath_vertices.append(csp[1])
# Handle final subpath
if len(subpath_vertices):
if distanceSquared(subpath_vertices[0], subpath_vertices[-1]) < 1:
# Path appears to be closed so let's keep it
subpath_list.append(subpath_vertices)
# Empty path?
if len(subpath_list) == 0:
return
# Store the list of subpaths in a dictionary keyed off of the path's node pointer
self.paths[node] = subpath_list
self.paths_clone_transform[node] = cloneTransform
def recursivelyTraverseSvg(self, aNodeList,
matCurrent=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
parent_visibility='visible', cloneTransform=None):
"""
[ This too is largely lifted from eggbot.py ]
Recursively walk the SVG document, building polygon vertex lists
for each graphical element we support.
Rendered SVG elements:
<circle>, <ellipse>, <line>, <path>, <polygon>, <polyline>, <rect>
Supported SVG elements:
<group>, <use>
Ignored SVG elements:
<defs>, <eggbot>, <metadata>, <namedview>, <pattern>,
processing directives
All other SVG elements trigger an error (including <text>)
"""
for node in aNodeList:
# Ignore invisible nodes
v = node.get('visibility', parent_visibility)
if v == 'inherit':
v = parent_visibility
if v == 'hidden' or v == 'collapse':
pass
# First apply the current matrix transform to this node's tranform
matNew = composeTransform(matCurrent, parseTransform(node.get("transform")))
if node.tag == inkex.addNS('g', 'svg') or node.tag == 'g':
self.recursivelyTraverseSvg(node, matNew, parent_visibility=v)
elif node.tag == inkex.addNS('use', 'svg') or node.tag == 'use':
# A <use> element refers to another SVG element via an xlink:href="#blah"
# attribute. We will handle the element by doing an XPath search through
# the document, looking for the element with the matching id="blah"
# attribute. We then recursively process that element after applying
# any necessary (x,y) translation.
#
# Notes:
# 1. We ignore the height and width attributes as they do not apply to
# path-like elements, and
# 2. Even if the use element has visibility="hidden", SVG still calls
# for processing the referenced element. The referenced element is
# hidden only if its visibility is "inherit" or "hidden".
refid = node.get(inkex.addNS('href', 'xlink'))
if not refid:
pass
# [1:] to ignore leading '#' in reference
path = '//*[@id="%s"]' % refid[1:]
refnode = node.xpath(path)
if refnode:
x = float(node.get('x', '0'))
y = float(node.get('y', '0'))
# Note: the transform has already been applied
if (x != 0) or (y != 0):
matNew2 = composeTransform(matNew, parseTransform('translate(%f,%f)' % (x, y)))
else:
matNew2 = matNew
v = node.get('visibility', v)
self.recursivelyTraverseSvg(refnode, matNew2,
parent_visibility=v, cloneTransform=node.get('transform'))
elif node.tag == inkex.addNS('path', 'svg'):
path_data = node.get('d')
if path_data:
self.addPathVertices(path_data, node, matNew, cloneTransform)
elif node.tag == inkex.addNS('rect', 'svg') or node.tag == 'rect':
# Manually transform
#
# <rect x="X" y="Y" width="W" height="H"/>
#
# into
#
# <path d="MX,Y lW,0 l0,H l-W,0 z"/>
#
# I.e., explicitly draw three sides of the rectangle and the
# fourth side implicitly
# Create a path with the outline of the rectangle
x = float(node.get('x'))
y = float(node.get('y'))
if (not x) or (not y):
pass
w = float(node.get('width', '0'))
h = float(node.get('height', '0'))
a = []
a.append(['M ', [x, y]])
a.append([' l ', [w, 0]])
a.append([' l ', [0, h]])
a.append([' l ', [-w, 0]])
a.append([' Z', []])
self.addPathVertices(simplepath.formatPath(a), node, matNew, cloneTransform)
elif node.tag == inkex.addNS('line', 'svg') or node.tag == 'line':
# Convert
#
# <line x1="X1" y1="Y1" x2="X2" y2="Y2/>
#
# to
#
# <path d="MX1,Y1 LX2,Y2"/>
x1 = float(node.get('x1'))
y1 = float(node.get('y1'))
x2 = float(node.get('x2'))
y2 = float(node.get('y2'))
if (not x1) or (not y1) or (not x2) or (not y2):
pass
a = []
a.append(['M ', [x1, y1]])
a.append([' L ', [x2, y2]])
self.addPathVertices(simplepath.formatPath(a), node, matNew, cloneTransform)
elif node.tag == inkex.addNS('polyline', 'svg') or node.tag == 'polyline':
# Convert
#
# <polyline points="x1,y1 x2,y2 x3,y3 [...]"/>
#
# to
#
# <path d="Mx1,y1 Lx2,y2 Lx3,y3 [...]"/>
#
# Note: we ignore polylines with no points
pl = node.get('points', '').strip()
if pl == '':
pass
pa = pl.split()
d = "".join(["M " + pa[i] if i == 0 else " L " + pa[i] for i in range(0, len(pa))])
self.addPathVertices(d, node, matNew, cloneTransform)
elif node.tag == inkex.addNS('polygon', 'svg') or node.tag == 'polygon':
# Convert
#
# <polygon points="x1,y1 x2,y2 x3,y3 [...]"/>
#
# to
#
# <path d="Mx1,y1 Lx2,y2 Lx3,y3 [...] Z"/>
#
# Note: we ignore polygons with no points
pl = node.get('points', '').strip()
if pl == '':
pass
pa = pl.split()
d = "".join(["M " + pa[i] if i == 0 else " L " + pa[i] for i in range(0, len(pa))])
d += " Z"
self.addPathVertices(d, node, matNew, cloneTransform)
elif node.tag == inkex.addNS('ellipse', 'svg') or \
node.tag == 'ellipse' or \
node.tag == inkex.addNS('circle', 'svg') or \
node.tag == 'circle':
# Convert circles and ellipses to a path with two 180 degree arcs.
# In general (an ellipse), we convert
#
# <ellipse rx="RX" ry="RY" cx="X" cy="Y"/>
#
# to
#
# <path d="MX1,CY A RX,RY 0 1 0 X2,CY A RX,RY 0 1 0 X1,CY"/>
#
# where
#
# X1 = CX - RX
# X2 = CX + RX
#
# Note: ellipses or circles with a radius attribute of value 0 are ignored
if node.tag == inkex.addNS('ellipse', 'svg') or node.tag == 'ellipse':
rx = float(node.get('rx', '0'))
ry = float(node.get('ry', '0'))
else:
rx = float(node.get('r', '0'))
ry = rx
if rx == 0 or ry == 0:
pass
cx = float(node.get('cx', '0'))
cy = float(node.get('cy', '0'))
x1 = cx - rx
x2 = cx + rx
d = 'M %f,%f ' % (x1, cy) + \
'A %f,%f ' % (rx, ry) + \
'0 1 0 %f,%f ' % (x2, cy) + \
'A %f,%f ' % (rx, ry) + \
'0 1 0 %f,%f' % (x1, cy)
self.addPathVertices(d, node, matNew, cloneTransform)
elif node.tag == inkex.addNS('pattern', 'svg') or node.tag == 'pattern':
pass
elif node.tag == inkex.addNS('metadata', 'svg') or node.tag == 'metadata':
pass
elif node.tag == inkex.addNS('defs', 'svg') or node.tag == 'defs':
pass
elif node.tag == inkex.addNS('namedview', 'sodipodi') or node.tag == 'namedview':
pass
elif node.tag == inkex.addNS('eggbot', 'svg') or node.tag == 'eggbot':
pass
elif node.tag == inkex.addNS('text', 'svg') or node.tag == 'text':
inkex.errormsg('Warning: unable to draw text, please convert it to a path first.')
pass
elif not isinstance(node.tag, basestring):
pass
else:
inkex.errormsg('Warning: unable to draw object <%s>, please convert it to a path first.' % node.tag)
pass
def joinWithNode(self, node, path, makeGroup=False, cloneTransform=None):
"""
Generate a SVG <path> element containing the path data "path".
Then put this new <path> element into a <group> with the supplied
node. This means making a new <group> element and making the
node a child of it with the new <path> as a sibling.
"""
if (not path) or (len(path) == 0):
return
if makeGroup:
# Make a new SVG <group> element whose parent is the parent of node
parent = node.getparent()
# was: if not parent:
if parent is None:
parent = self.document.getroot()
g = inkex.etree.SubElement(parent, inkex.addNS('g', 'svg'))
# Move node to be a child of this new <g> element
g.append(node)
# Promote the node's transform to the new parent group
# This way, it will apply to the original paths and the
# "twisted" paths
transform = node.get('transform')
if transform:
g.set('transform', transform)
del node.attrib['transform']
else:
g = node.getparent()
# Now make a <path> element which contains the twist & is a child
# of the new <g> element
style = {'stroke': '#000000', 'fill': 'none', 'stroke-width': '1'}
line_attribs = {'style': simplestyle.formatStyle(style), 'd': path}
if (cloneTransform is not None) and (cloneTransform != ''):
line_attribs['transform'] = cloneTransform
inkex.etree.SubElement(g, inkex.addNS('path', 'svg'), line_attribs)
def twist(self, ratio):
if not self.paths:
return
# Now iterate over all of the polygons
for path in self.paths:
for subpath in self.paths[path]:
for i in range(0, len(subpath) - 1):
x = subpath[i][0] + ratio * (subpath[i + 1][0] - subpath[i][0])
y = subpath[i][1] + ratio * (subpath[i + 1][1] - subpath[i][1])
subpath[i] = [x, y]
subpath[-1] = subpath[0]
def __init__(self):
inkex.Effect.__init__(self)
self.OptionParser.add_option(
"--nSteps", action="store", type="int",
dest="nSteps", default=8,
help="Number of iterations to take")
self.OptionParser.add_option(
"--fRatio", action="store", type="float",
dest="fRatio", default=float(0.2),
help="Some ratio")
"""
Store each path in an associative array (dictionary) indexed
by the lxml.etree pointer for the SVG document element
containing the path. Looking up the path in the dictionary
yields a list of lists. Each of these lists is a subpath
# of the path. E.g., for the SVG path
<path d="M 10,10 l 0,5 l 5,0 l 0,-5 Z M 30,30 L 30,60"/>
we'd have two subpaths which will be reduced to absolute
coordinates.
subpath_1 = [ [10, 10], [10, 15], [15, 15], [15, 10], [10,10] ]
subpath_2 = [ [30, 30], [30, 60] ]
self.paths[<node pointer>] = [ subpath_1, subpath_2 ]
All of the paths and their subpaths could be drawn as follows:
for path in self.paths:
for subpath in self.paths[path]:
first = True
for vertex in subpath:
if first:
moveto( vertex[0], vertex[1] )
first = False
else:
lineto( vertex[0], vertex[1] )
NOTE: drawing all the paths like the above would not in general
give the correct rendering of the document UNLESS path transforms
were also tracked and applied.
"""
self.paths = {}
self.paths_clone_transform = {}
def addPathVertices(self, path, node=None, transform=None, cloneTransform=None):
"""
Decompose the path data from an SVG element into individual
subpaths, each subpath consisting of absolute move to and line
to coordinates. Place these coordinates into a list of polygon
vertices.
"""
if (not path) or (len(path) == 0):
# Nothing to do
return
# parsePath() may raise an exception. This is okay
sp = simplepath.parsePath(path)
if (not sp) or (len(sp) == 0):
# Path must have been devoid of any real content
return
# Get a cubic super path
p = cubicsuperpath.CubicSuperPath(sp)
if (not p) or (len(p) == 0):
# Probably never happens, but...
return
# if transform:
# simpletransform.applyTransformToPath( transform, p )
# Now traverse the cubic super path
subpath_list = []
subpath_vertices = []
for sp in p:
if len(subpath_vertices):
# There's a prior subpath: see if it is closed and should be saved
if distanceSquared(subpath_vertices[0], subpath_vertices[-1]) < 1:
# Keep the prior subpath: it appears to be a closed path
subpath_list.append(subpath_vertices)
subpath_vertices = []
subdivideCubicPath(sp, float(0.2))
for csp in sp:
# Add this vertex to the list of vetices
subpath_vertices.append(csp[1])
# Handle final subpath
if len(subpath_vertices):
if distanceSquared(subpath_vertices[0], subpath_vertices[-1]) < 1:
# Path appears to be closed so let's keep it
subpath_list.append(subpath_vertices)
# Empty path?
if len(subpath_list) == 0:
return
# Store the list of subpaths in a dictionary keyed off of the path's node pointer
self.paths[node] = subpath_list
self.paths_clone_transform[node] = cloneTransform
def recursivelyTraverseSvg(self, aNodeList,
matCurrent=[[1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
parent_visibility='visible', cloneTransform=None):
"""
[ This too is largely lifted from eggbot.py ]
Recursively walk the SVG document, building polygon vertex lists
for each graphical element we support.
Rendered SVG elements:
<circle>, <ellipse>, <line>, <path>, <polygon>, <polyline>, <rect>
Supported SVG elements:
<group>, <use>
Ignored SVG elements:
<defs>, <eggbot>, <metadata>, <namedview>, <pattern>,
processing directives
All other SVG elements trigger an error (including <text>)
"""
for node in aNodeList:
# Ignore invisible nodes
v = node.get('visibility', parent_visibility)
if v == 'inherit':
v = parent_visibility
if v == 'hidden' or v == 'collapse':
pass
# First apply the current matrix transform to this node's tranform
matNew = composeTransform(matCurrent, parseTransform(node.get("transform")))
if node.tag == inkex.addNS('g', 'svg') or node.tag == 'g':
self.recursivelyTraverseSvg(node, matNew, parent_visibility=v)
elif node.tag == inkex.addNS('use', 'svg') or node.tag == 'use':
# A <use> element refers to another SVG element via an xlink:href="#blah"
# attribute. We will handle the element by doing an XPath search through
# the document, looking for the element with the matching id="blah"
# attribute. We then recursively process that element after applying
# any necessary (x,y) translation.
#
# Notes:
# 1. We ignore the height and width attributes as they do not apply to
# path-like elements, and
# 2. Even if the use element has visibility="hidden", SVG still calls
# for processing the referenced element. The referenced element is
# hidden only if its visibility is "inherit" or "hidden".
refid = node.get(inkex.addNS('href', 'xlink'))
if not refid:
pass
# [1:] to ignore leading '#' in reference
path = '//*[@id="%s"]' % refid[1:]
refnode = node.xpath(path)
if refnode:
x = float(node.get('x', '0'))
y = float(node.get('y', '0'))
# Note: the transform has already been applied
if (x != 0) or (y != 0):
matNew2 = composeTransform(matNew, parseTransform('translate(%f,%f)' % (x, y)))
else:
matNew2 = matNew
v = node.get('visibility', v)
self.recursivelyTraverseSvg(refnode, matNew2,
parent_visibility=v, cloneTransform=node.get('transform'))
elif node.tag == inkex.addNS('path', 'svg'):
path_data = node.get('d')
if path_data:
self.addPathVertices(path_data, node, matNew, cloneTransform)
elif node.tag == inkex.addNS('rect', 'svg') or node.tag == 'rect':
# Manually transform
#
# <rect x="X" y="Y" width="W" height="H"/>
#
# into
#
# <path d="MX,Y lW,0 l0,H l-W,0 z"/>
#
# I.e., explicitly draw three sides of the rectangle and the
# fourth side implicitly
# Create a path with the outline of the rectangle
x = float(node.get('x'))
y = float(node.get('y'))
if (not x) or (not y):
pass
w = float(node.get('width', '0'))
h = float(node.get('height', '0'))
a = []
a.append(['M ', [x, y]])
a.append([' l ', [w, 0]])
a.append([' l ', [0, h]])
a.append([' l ', [-w, 0]])
a.append([' Z', []])
self.addPathVertices(simplepath.formatPath(a), node, matNew, cloneTransform)
elif node.tag == inkex.addNS('line', 'svg') or node.tag == 'line':
# Convert
#
# <line x1="X1" y1="Y1" x2="X2" y2="Y2/>
#
# to
#
# <path d="MX1,Y1 LX2,Y2"/>
x1 = float(node.get('x1'))
y1 = float(node.get('y1'))
x2 = float(node.get('x2'))
y2 = float(node.get('y2'))
if (not x1) or (not y1) or (not x2) or (not y2):
pass
a = []
a.append(['M ', [x1, y1]])
a.append([' L ', [x2, y2]])
self.addPathVertices(simplepath.formatPath(a), node, matNew, cloneTransform)
elif node.tag == inkex.addNS('polyline', 'svg') or node.tag == 'polyline':
# Convert
#
# <polyline points="x1,y1 x2,y2 x3,y3 [...]"/>
#
# to
#
# <path d="Mx1,y1 Lx2,y2 Lx3,y3 [...]"/>
#
# Note: we ignore polylines with no points
pl = node.get('points', '').strip()
if pl == '':
pass
pa = pl.split()
d = "".join(["M " + pa[i] if i == 0 else " L " + pa[i] for i in range(0, len(pa))])
self.addPathVertices(d, node, matNew, cloneTransform)
elif node.tag == inkex.addNS('polygon', 'svg') or node.tag == 'polygon':
# Convert
#
# <polygon points="x1,y1 x2,y2 x3,y3 [...]"/>
#
# to
#
# <path d="Mx1,y1 Lx2,y2 Lx3,y3 [...] Z"/>
#
# Note: we ignore polygons with no points
pl = node.get('points', '').strip()
if pl == '':
pass
pa = pl.split()
d = "".join(["M " + pa[i] if i == 0 else " L " + pa[i] for i in range(0, len(pa))])
d += " Z"
self.addPathVertices(d, node, matNew, cloneTransform)
elif node.tag == inkex.addNS('ellipse', 'svg') or \
node.tag == 'ellipse' or \
node.tag == inkex.addNS('circle', 'svg') or \
node.tag == 'circle':
# Convert circles and ellipses to a path with two 180 degree arcs.
# In general (an ellipse), we convert
#
# <ellipse rx="RX" ry="RY" cx="X" cy="Y"/>
#
# to
#
# <path d="MX1,CY A RX,RY 0 1 0 X2,CY A RX,RY 0 1 0 X1,CY"/>
#
# where
#
# X1 = CX - RX
# X2 = CX + RX
#
# Note: ellipses or circles with a radius attribute of value 0 are ignored
if node.tag == inkex.addNS('ellipse', 'svg') or node.tag == 'ellipse':
rx = float(node.get('rx', '0'))
ry = float(node.get('ry', '0'))
else:
rx = float(node.get('r', '0'))
ry = rx
if rx == 0 or ry == 0:
pass
cx = float(node.get('cx', '0'))
cy = float(node.get('cy', '0'))
x1 = cx - rx
x2 = cx + rx
d = 'M %f,%f ' % (x1, cy) + \
'A %f,%f ' % (rx, ry) + \
'0 1 0 %f,%f ' % (x2, cy) + \
'A %f,%f ' % (rx, ry) + \
'0 1 0 %f,%f' % (x1, cy)
self.addPathVertices(d, node, matNew, cloneTransform)
elif node.tag == inkex.addNS('pattern', 'svg') or node.tag == 'pattern':
pass
elif node.tag == inkex.addNS('metadata', 'svg') or node.tag == 'metadata':
pass
elif node.tag == inkex.addNS('defs', 'svg') or node.tag == 'defs':
pass
elif node.tag == inkex.addNS('namedview', 'sodipodi') or node.tag == 'namedview':
pass
elif node.tag == inkex.addNS('eggbot', 'svg') or node.tag == 'eggbot':
pass
elif node.tag == inkex.addNS('text', 'svg') or node.tag == 'text':
inkex.errormsg('Warning: unable to draw text, please convert it to a path first.')
pass
elif not isinstance(node.tag, basestring):
pass
else:
inkex.errormsg('Warning: unable to draw object <%s>, please convert it to a path first.' % node.tag)
pass
def joinWithNode(self, node, path, makeGroup=False, cloneTransform=None):
"""
Generate a SVG <path> element containing the path data "path".
Then put this new <path> element into a <group> with the supplied
node. This means making a new <group> element and making the
node a child of it with the new <path> as a sibling.
"""
if (not path) or (len(path) == 0):
return
if makeGroup:
# Make a new SVG <group> element whose parent is the parent of node
parent = node.getparent()
# was: if not parent:
if parent is None:
parent = self.document.getroot()
g = inkex.etree.SubElement(parent, inkex.addNS('g', 'svg'))
# Move node to be a child of this new <g> element
g.append(node)
# Promote the node's transform to the new parent group
# This way, it will apply to the original paths and the
# "twisted" paths
transform = node.get('transform')
if transform:
g.set('transform', transform)
del node.attrib['transform']
else:
g = node.getparent()
# Now make a <path> element which contains the twist & is a child
# of the new <g> element
style = {'stroke': '#000000', 'fill': 'none', 'stroke-width': '1'}
line_attribs = {'style': simplestyle.formatStyle(style), 'd': path}
if (cloneTransform is not None) and (cloneTransform != ''):
line_attribs['transform'] = cloneTransform
inkex.etree.SubElement(g, inkex.addNS('path', 'svg'), line_attribs)
def twist(self, ratio):
if not self.paths:
return
# Now iterate over all of the polygons
for path in self.paths:
for subpath in self.paths[path]:
for i in range(0, len(subpath) - 1):
x = subpath[i][0] + ratio * (subpath[i + 1][0] - subpath[i][0])
y = subpath[i][1] + ratio * (subpath[i + 1][1] - subpath[i][1])
subpath[i] = [x, y]
subpath[-1] = subpath[0]
def draw(self, makeGroup=False):
def draw(self, makeGroup=False):
"""
Draw the edges of the current list of vertices
"""
"""
Draw the edges of the current list of vertices
"""
if not self.paths:
return
if not self.paths:
return
# Now iterate over all of the polygons
for path in self.paths:
for subpath in self.paths[path]:
pdata = ''
for vertex in subpath:
if pdata == '':
pdata = 'M %f,%f' % (vertex[0], vertex[1])
else:
pdata += ' L %f,%f' % (vertex[0], vertex[1])
self.joinWithNode(path, pdata, makeGroup, self.paths_clone_transform[path])
# Now iterate over all of the polygons
for path in self.paths:
for subpath in self.paths[path]:
pdata = ''
for vertex in subpath:
if pdata == '':
pdata = 'M %f,%f' % (vertex[0], vertex[1])
else:
pdata += ' L %f,%f' % (vertex[0], vertex[1])
self.joinWithNode(path, pdata, makeGroup, self.paths_clone_transform[path])
def effect(self):
def effect(self):
# Build a list of the vertices for the document's graphical elements
if self.options.ids:
# Traverse the selected objects
for id in self.options.ids:
self.recursivelyTraverseSvg([self.selected[id]])
else:
# Traverse the entire document
self.recursivelyTraverseSvg(self.document.getroot())
# Now iterate over the vertices N times
for n in range(0, self.options.nSteps):
self.twist(self.options.fRatio)
self.draw(n == 0)
# Build a list of the vertices for the document's graphical elements
if self.options.ids:
# Traverse the selected objects
for id in self.options.ids:
self.recursivelyTraverseSvg([self.selected[id]])
else:
# Traverse the entire document
self.recursivelyTraverseSvg(self.document.getroot())
# Now iterate over the vertices N times
for n in range(0, self.options.nSteps):
self.twist(self.options.fRatio)
self.draw(n == 0)
if __name__ == '__main__':
e = Twist()
e.affect()
e = Twist()
e.affect()

Plik diff jest za duży Load Diff

Wyświetl plik

@ -22,14 +22,14 @@ The STEP SCALING FACTOR below can be used to work with motor-driver combinations
For 1600 steps/rev, use:
STEP_SCALE = 4
STEP_SCALE = 4
For NORMAL USE, with overall 3200 steps/rev, use the default value:
STEP_SCALE = 2
STEP_SCALE = 2
For 6400 steps/rev, use:
STEP_SCALE = 1
STEP_SCALE = 1
Other _integer_ scaling values can be used as well, with similar scaling.
"""

Wyświetl plik

@ -21,35 +21,35 @@ import inkex
class PresetHatch(inkex.Effect):
def __init__(self):
inkex.Effect.__init__(self)
self.OptionParser.add_option("--title")
def __init__(self):
inkex.Effect.__init__(self)
self.OptionParser.add_option("--title")
def effect(self):
self.svgDefRead = False
self.svg = self.document.getroot()
self.recursiveDefDataScan(self.svg)
def effect(self):
self.svgDefRead = False
self.svg = self.document.getroot()
self.recursiveDefDataScan(self.svg)
def recursiveDefDataScan(self, aNodeList):
for node in aNodeList:
if node.tag == inkex.addNS('defs', 'svg') or node.tag == 'defs':
self.recursiveDefDataScan(node)
elif node.tag == inkex.addNS('path-effect', 'inkscape'):
if node.get('effect') == 'rough_hatches':
node.set('dist_rdm', '0;1')
node.set('growth', str(0))
# node.set( 'do_bend', 'false' )
node.set('bottom_edge_variation', '0;1')
node.set('top_edge_variation', '0;1')
node.set('bottom_tgt_variation', '0;1')
node.set('top_tgt_variation', '0;1')
node.set('scale_bf', str(2))
node.set('scale_bb', str(2))
node.set('scale_tf', str(2))
node.set('scale_tb', str(2))
node.set('top_smth_variation', '0;1')
node.set('bottom_smth_variation', '0;1')
node.set('fat_output', 'false')
def recursiveDefDataScan(self, aNodeList):
for node in aNodeList:
if node.tag == inkex.addNS('defs', 'svg') or node.tag == 'defs':
self.recursiveDefDataScan(node)
elif node.tag == inkex.addNS('path-effect', 'inkscape'):
if node.get('effect') == 'rough_hatches':
node.set('dist_rdm', '0;1')
node.set('growth', str(0))
# node.set( 'do_bend', 'false' )
node.set('bottom_edge_variation', '0;1')
node.set('top_edge_variation', '0;1')
node.set('bottom_tgt_variation', '0;1')
node.set('top_tgt_variation', '0;1')
node.set('scale_bf', str(2))
node.set('scale_bb', str(2))
node.set('scale_tf', str(2))
node.set('scale_tb', str(2))
node.set('top_smth_variation', '0;1')
node.set('bottom_smth_variation', '0;1')
node.set('fat_output', 'false')
e = PresetHatch()

Wyświetl plik

@ -31,146 +31,146 @@ import simpletransform
def dist(x0, y0, x1, y1):
return math.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2)
return math.sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2)
def find_ordering_naive(objlist):
"""
Takes a list of (id, (startX, startY, endX, endY)), and finds the best ordering.
Doesn't handle anything fancy, like reversing the ordering, but it's useful for now.
Returns a list of JUST THE IDs, in a better order, as well as the original and optimized
"air distance" which is just the distance traveled in the air. Perhaps we want to make
these comparison distances into something more relevant such as degrees traveled?
"""
"""
Takes a list of (id, (startX, startY, endX, endY)), and finds the best ordering.
Doesn't handle anything fancy, like reversing the ordering, but it's useful for now.
Returns a list of JUST THE IDs, in a better order, as well as the original and optimized
"air distance" which is just the distance traveled in the air. Perhaps we want to make
these comparison distances into something more relevant such as degrees traveled?
"""
# let's figure out the default in-air length, so we know how much we improved
air_length_default = 0
try:
oldx = objlist[0][1][2]
oldy = objlist[0][1][3]
except:
inkex.errormsg(gettext.gettext(str(objlist[0])))
sys.exit(1)
for id, coords in objlist[1:]:
air_length_default += dist(oldx, oldy, coords[0], coords[1])
oldx = coords[2]
oldy = coords[3]
# fid.write("Default air distance: %d\n" % air_length_default)
# let's figure out the default in-air length, so we know how much we improved
air_length_default = 0
try:
oldx = objlist[0][1][2]
oldy = objlist[0][1][3]
except:
inkex.errormsg(gettext.gettext(str(objlist[0])))
sys.exit(1)
for id, coords in objlist[1:]:
air_length_default += dist(oldx, oldy, coords[0], coords[1])
oldx = coords[2]
oldy = coords[3]
# fid.write("Default air distance: %d\n" % air_length_default)
air_length_ordered = 0
# for now, start with a random one:
sort_list = []
random_index = random.randint(0, len(objlist) - 1)
sort_list.append(objlist[random_index])
objlist.remove(objlist[random_index])
air_length_ordered = 0
# for now, start with a random one:
sort_list = []
random_index = random.randint(0, len(objlist) - 1)
sort_list.append(objlist[random_index])
objlist.remove(objlist[random_index])
# for now, do this in the most naive way:
# for the previous end point, iterate over each remaining path and pick the closest starting point
while len(objlist) > 0:
min_distance = 100000000 # TODO put something else here better?
for path in objlist:
# instead of having a prevX, prevY, we just look at the last item in sort_list
this_distance = dist(sort_list[-1][1][2], sort_list[-1][1][3], path[1][0], path[1][1])
# this is such a common thing to do, you'd think there would be a name for it...
if this_distance < min_distance:
min_distance = this_distance
min_path = path
air_length_ordered += min_distance
sort_list.append(min_path)
objlist.remove(min_path)
# for now, do this in the most naive way:
# for the previous end point, iterate over each remaining path and pick the closest starting point
while len(objlist) > 0:
min_distance = 100000000 # TODO put something else here better?
for path in objlist:
# instead of having a prevX, prevY, we just look at the last item in sort_list
this_distance = dist(sort_list[-1][1][2], sort_list[-1][1][3], path[1][0], path[1][1])
# this is such a common thing to do, you'd think there would be a name for it...
if this_distance < min_distance:
min_distance = this_distance
min_path = path
air_length_ordered += min_distance
sort_list.append(min_path)
objlist.remove(min_path)
# fid.write("optimized air distance: %d\n" % air_length_ordered)
# fid.write("optimized air distance: %d\n" % air_length_ordered)
# remove the extraneous info from the list order
sort_order = [id for id, coords in sort_list]
return sort_order, air_length_default, air_length_ordered
# remove the extraneous info from the list order
sort_order = [id for id, coords in sort_list]
return sort_order, air_length_default, air_length_ordered
def conv(x, y, trans_matrix=None):
"""
not used currently, but can be used to apply a translation matrix to an (x, y) pair
I'm sure there is a better way to do this using simpletransform or it's ilk
"""
"""
not used currently, but can be used to apply a translation matrix to an (x, y) pair
I'm sure there is a better way to do this using simpletransform or it's ilk
"""
if trans_matrix:
xt = trans_matrix[0][0] * x + trans_matrix[0][1] * y + trans_matrix[0][2]
yt = trans_matrix[1][0] * x + trans_matrix[1][1] * y + trans_matrix[1][2]
return xt, yt
else:
return x, y
if trans_matrix:
xt = trans_matrix[0][0] * x + trans_matrix[0][1] * y + trans_matrix[0][2]
yt = trans_matrix[1][0] * x + trans_matrix[1][1] * y + trans_matrix[1][2]
return xt, yt
else:
return x, y
class EggBotReorderPaths(inkex.Effect):
def __init__(self):
inkex.Effect.__init__(self)
def __init__(self):
inkex.Effect.__init__(self)
# self.OptionParser.add_option( '-r', '--reverse', action='store', type="inkbool",
# dest="reverse", default=True, help="Enable 'reverse path direction' optimizations" )
# self.OptionParser.add_option( '-w', '--wrap', action='store', type="inkbool",
# dest="wrap", default=True, help="Enable 'wrap egg axis' optimizations" )
# self.OptionParser.add_option( '-r', '--reverse', action='store', type="inkbool",
# dest="reverse", default=True, help="Enable 'reverse path direction' optimizations" )
# self.OptionParser.add_option( '-w', '--wrap', action='store', type="inkbool",
# dest="wrap", default=True, help="Enable 'wrap egg axis' optimizations" )
def get_start_end(self, node, transform):
"""Given a node, return the start and end points"""
d = node.get('d')
sp = simplepath.parsePath(d)
def get_start_end(self, node, transform):
"""Given a node, return the start and end points"""
d = node.get('d')
sp = simplepath.parsePath(d)
# simplepath converts coordinates to absolute and cleans them up, but
# these are still some big assumptions here, are they always valid? TODO
startX = sp[0][1][0]
startY = sp[0][1][1]
if sp[-1][0] == 'Z':
# go back to start
endX = startX
endY = startY
else:
endX = sp[-1][1][-2]
endY = sp[-1][1][-1]
# simplepath converts coordinates to absolute and cleans them up, but
# these are still some big assumptions here, are they always valid? TODO
startX = sp[0][1][0]
startY = sp[0][1][1]
if sp[-1][0] == 'Z':
# go back to start
endX = startX
endY = startY
else:
endX = sp[-1][1][-2]
endY = sp[-1][1][-1]
sx, sy = conv(startX, startY, transform)
ex, ey = conv(endX, endY, transform)
return sx, sy, ex, ey
sx, sy = conv(startX, startY, transform)
ex, ey = conv(endX, endY, transform)
return sx, sy, ex, ey
def effect(self):
"""This is the main entry point"""
def effect(self):
"""This is the main entry point"""
# based partially on the restack.py extension
if len(self.selected) > 0:
svg = self.document.getroot()
# based partially on the restack.py extension
if len(self.selected) > 0:
svg = self.document.getroot()
# TODO check for non-path elements?
# TODO it seems like the order of selection is not consistent
# TODO check for non-path elements?
# TODO it seems like the order of selection is not consistent
# fid = open("/home/matthew/debug.txt", "w")
# fid = open("/home/matthew/debug.txt", "w")
# for each selected item - TODO make this be all objects, everywhere
# I can think of two options:
# 1. Iterate over all paths in root, then iterate over all layers, and their paths
# 2. Some magic with xpath? (would this limit us to specific node types?)
# for each selected item - TODO make this be all objects, everywhere
# I can think of two options:
# 1. Iterate over all paths in root, then iterate over all layers, and their paths
# 2. Some magic with xpath? (would this limit us to specific node types?)
objlist = []
for id, node in self.selected.iteritems():
transform = node.get('transform')
if transform:
transform = simpletransform.parseTransform(transform)
objlist = []
for id, node in self.selected.iteritems():
transform = node.get('transform')
if transform:
transform = simpletransform.parseTransform(transform)
item = (id, self.get_start_end(node, transform))
objlist.append(item)
item = (id, self.get_start_end(node, transform))
objlist.append(item)
# sort / order the objects
sort_order, air_distance_default, air_distance_ordered = find_ordering_naive(objlist)
# sort / order the objects
sort_order, air_distance_default, air_distance_ordered = find_ordering_naive(objlist)
for id in sort_order:
# There's some good magic here, that you can use an
# object id to index into self.selected. Brilliant!
self.current_layer.append(self.selected[id])
for id in sort_order:
# There's some good magic here, that you can use an
# object id to index into self.selected. Brilliant!
self.current_layer.append(self.selected[id])
# fid.close()
# fid.close()
if air_distance_default > 0: # don't divide by zero. :P
improvement_pct = 100 * ((air_distance_default - air_distance_ordered) / air_distance_default)
inkex.errormsg(gettext.gettext("Selected paths have been reordered and optimized for quicker EggBot plotting.\n\nOriginal air-distance: %d\nOptimized air-distance: %d\nDistance reduced by: %1.2d%%\n\nHave a nice day!" % (air_distance_default, air_distance_ordered, improvement_pct)))
else:
inkex.errormsg(gettext.gettext("Unable to start. Please select multiple distinct paths. :)"))
if air_distance_default > 0: # don't divide by zero. :P
improvement_pct = 100 * ((air_distance_default - air_distance_ordered) / air_distance_default)
inkex.errormsg(gettext.gettext("Selected paths have been reordered and optimized for quicker EggBot plotting.\n\nOriginal air-distance: %d\nOptimized air-distance: %d\nDistance reduced by: %1.2d%%\n\nHave a nice day!" % (air_distance_default, air_distance_ordered, improvement_pct)))
else:
inkex.errormsg(gettext.gettext("Unable to start. Please select multiple distinct paths. :)"))
e = EggBotReorderPaths()

Wyświetl plik

@ -6,38 +6,38 @@ USB_DEVICE_TREE = '/sys/bus/usb/devices'
def findEiBotBoards():
"""Find only those USB devices that declare themselves to be EiBotBoards"""
"""Find only those USB devices that declare themselves to be EiBotBoards"""
# find all USB devices whose product name is 'EiBotBoard'
with os.popen('fgrep -l EiBotBoard %s/*/product' % USB_DEVICE_TREE) as pipe:
for path in [os.path.split(path)[0] for path in pipe.readlines()]:
device = os.path.split(path)[1]
# find all USB devices whose product name is 'EiBotBoard'
with os.popen('fgrep -l EiBotBoard %s/*/product' % USB_DEVICE_TREE) as pipe:
for path in [os.path.split(path)[0] for path in pipe.readlines()]:
device = os.path.split(path)[1]
# for each device endpoint ...
for dir in os.listdir(path):
if dir.startswith(device):
# for each device endpoint ...
for dir in os.listdir(path):
if dir.startswith(device):
# find the endpoint that supports tty access
ttydir = os.path.join(USB_DEVICE_TREE, device, dir, 'tty')
if os.path.exists(ttydir):
# find the endpoint that supports tty access
ttydir = os.path.join(USB_DEVICE_TREE, device, dir, 'tty')
if os.path.exists(ttydir):
# And emit each (the) interface name
for ttyname in os.listdir(ttydir):
yield os.path.join(DEV_TREE, ttyname)
# And emit each (the) interface name
for ttyname in os.listdir(ttydir):
yield os.path.join(DEV_TREE, ttyname)
def findPorts():
for device in os.listdir(DEV_TREE):
if not device.startswith('ttyACM'):
continue
yield os.path.join(DEV_TREE, device)
for device in os.listdir(DEV_TREE):
if not device.startswith('ttyACM'):
continue
yield os.path.join(DEV_TREE, device)
if __name__ == '__main__':
print("Looking for EiBotBoards")
for port in findEiBotBoards():
print(" ", port)
print("Looking for EiBotBoards")
for port in findEiBotBoards():
print(" ", port)
print("Looking for COM ports")
for port in findPorts():
print(" ", port)
print("Looking for COM ports")
for port in findPorts():
print(" ", port)

Wyświetl plik

@ -32,155 +32,154 @@ FONT_GROUP_V_SPACING = 45
def draw_svg_text(char, face, offset, vertoffset, parent):
style = {'stroke': '#000000', 'fill': 'none'}
pathString = face[char]
splitString = pathString.split()
midpoint = offset - float(splitString[0])
splitpoint = pathString.find("M")
# Space glyphs have just widths with no moves, so their splitpoint is 0
# We only want to generate paths for visible glyphs where splitpoint > 0
if splitpoint > 0:
pathString = pathString[splitpoint:] # portion after first move
trans = 'translate(' + str(midpoint) + ',' + str(vertoffset) + ')'
text_attribs = {'style': simplestyle.formatStyle(style), 'd': pathString, 'transform': trans}
inkex.etree.SubElement(parent, inkex.addNS('path', 'svg'), text_attribs)
return midpoint + float(splitString[1]) # new offset value
style = {'stroke': '#000000', 'fill': 'none'}
pathString = face[char]
splitString = pathString.split()
midpoint = offset - float(splitString[0])
splitpoint = pathString.find("M")
# Space glyphs have just widths with no moves, so their splitpoint is 0
# We only want to generate paths for visible glyphs where splitpoint > 0
if splitpoint > 0:
pathString = pathString[splitpoint:] # portion after first move
trans = 'translate(' + str(midpoint) + ',' + str(vertoffset) + ')'
text_attribs = {'style': simplestyle.formatStyle(style), 'd': pathString, 'transform': trans}
inkex.etree.SubElement(parent, inkex.addNS('path', 'svg'), text_attribs)
return midpoint + float(splitString[1]) # new offset value
def svg_text_width(char, face, offset):
pathString = face[char]
splitString = pathString.split()
midpoint = offset - float(splitString[0])
return midpoint + float(splitString[1]) # new offset value
pathString = face[char]
splitString = pathString.split()
midpoint = offset - float(splitString[0])
return midpoint + float(splitString[1]) # new offset value
class Hershey(inkex.Effect):
def __init__(self):
inkex.Effect.__init__(self)
self.OptionParser.add_option("--tab", # NOTE: value is not used.
action="store", type="string",
dest="tab", default="splash",
help="The active tab when Apply was pressed")
self.OptionParser.add_option("--text",
action="store", type="string",
dest="text", default="Hershey Text for Inkscape",
help="The input text to render")
self.OptionParser.add_option("--action",
action="store", type="string",
dest="action", default="render",
help="The active option when Apply was pressed")
self.OptionParser.add_option("--fontface",
action="store", type="string",
dest="fontface", default="rowmans",
help="The selected font face when Apply was pressed")
def __init__(self):
inkex.Effect.__init__(self)
self.OptionParser.add_option("--tab", # NOTE: value is not used.
action="store", type="string",
dest="tab", default="splash",
help="The active tab when Apply was pressed")
self.OptionParser.add_option("--text",
action="store", type="string",
dest="text", default="Hershey Text for Inkscape",
help="The input text to render")
self.OptionParser.add_option("--action",
action="store", type="string",
dest="action", default="render",
help="The active option when Apply was pressed")
self.OptionParser.add_option("--fontface",
action="store", type="string",
dest="fontface", default="rowmans",
help="The selected font face when Apply was pressed")
def effect(self):
def effect(self):
OutputGenerated = False
OutputGenerated = False
# Embed text in group to make manipulation easier:
g_attribs = {inkex.addNS('label', 'inkscape'): 'Hershey Text'}
g = inkex.etree.SubElement(self.current_layer, 'g', g_attribs)
# Embed text in group to make manipulation easier:
g_attribs = {inkex.addNS('label', 'inkscape'): 'Hershey Text'}
g = inkex.etree.SubElement(self.current_layer, 'g', g_attribs)
scale = self.unittouu('1px') # convert to document units
font = eval('hersheydata.' + str(self.options.fontface))
clearfont = hersheydata.futural
# Baseline: modernized roman simplex from JHF distribution.
scale = self.unittouu('1px') # convert to document units
font = eval('hersheydata.' + str(self.options.fontface))
clearfont = hersheydata.futural
# Baseline: modernized roman simplex from JHF distribution.
w = 0 # Initial spacing offset
v = 0 # Initial vertical offset
spacing = 3 # spacing between letters
w = 0 # Initial spacing offset
v = 0 # Initial vertical offset
spacing = 3 # spacing between letters
if self.options.action == "render":
# evaluate text string
letterVals = [ord(q) - 32 for q in self.options.text]
for q in letterVals:
if (q <= 0) or (q > 95):
w += 2 * spacing
else:
w = draw_svg_text(q, font, w, 0, g)
OutputGenerated = True
elif self.options.action == 'sample':
w, v = self.render_table_of_all_fonts('group_allfonts', g, spacing, clearfont)
OutputGenerated = True
scale *= 0.4 # Typically scales to about A4/US Letter size
elif self.options.action == 'sampleHW':
w, v = self.render_table_of_all_fonts('group_hwfonts', g, spacing, clearfont)
OutputGenerated = True
scale *= 0.5 # Typically scales to about A4/US Letter size
else:
# Generate glyph table
wmax = 0
for p in range(0, 10):
w = 0
v = spacing * (15 * p - 67)
for q in range(0, 10):
r = p * 10 + q
if (r <= 0) or (r > 95):
w += 5 * spacing
else:
w = draw_svg_text(r, clearfont, w, v, g)
w = draw_svg_text(r, font, w, v, g)
w += 5 * spacing
if w > wmax:
wmax = w
w = wmax
OutputGenerated = True
# Translate group to center of view, approximately
t = 'translate(' + str(self.view_center[0] - scale * w / 2) + ',' + str(self.view_center[1] - scale * v / 2) + ')'
if scale != 1:
t += ' scale(' + str(scale) + ')'
g.set('transform', t)
if self.options.action == "render":
# evaluate text string
letterVals = [ord(q) - 32 for q in self.options.text]
for q in letterVals:
if (q <= 0) or (q > 95):
w += 2 * spacing
else:
w = draw_svg_text(q, font, w, 0, g)
OutputGenerated = True
elif self.options.action == 'sample':
w, v = self.render_table_of_all_fonts('group_allfonts', g, spacing, clearfont)
OutputGenerated = True
scale *= 0.4 # Typically scales to about A4/US Letter size
elif self.options.action == 'sampleHW':
w, v = self.render_table_of_all_fonts('group_hwfonts', g, spacing, clearfont)
OutputGenerated = True
scale *= 0.5 # Typically scales to about A4/US Letter size
else:
# Generate glyph table
wmax = 0
for p in range(0, 10):
w = 0
v = spacing * (15 * p - 67)
for q in range(0, 10):
r = p * 10 + q
if (r <= 0) or (r > 95):
w += 5 * spacing
else:
w = draw_svg_text(r, clearfont, w, v, g)
w = draw_svg_text(r, font, w, v, g)
w += 5 * spacing
if w > wmax:
wmax = w
w = wmax
OutputGenerated = True
# Translate group to center of view, approximately
t = 'translate(' + str(self.view_center[0] - scale * w / 2) + ',' + str(self.view_center[1] - scale * v / 2) + ')'
if scale != 1:
t += ' scale(' + str(scale) + ')'
g.set('transform', t)
if not OutputGenerated:
self.current_layer.remove(g) # remove empty group, if no SVG was generated.
if not OutputGenerated:
self.current_layer.remove(g) # remove empty group, if no SVG was generated.
def render_table_of_all_fonts(self, fontgroupname, parent, spacing, clearfont):
v = 0
wmax = 0
wmin = 0
fontgroup = eval('hersheydata.' + fontgroupname)
def render_table_of_all_fonts(self, fontgroupname, parent, spacing, clearfont):
v = 0
wmax = 0
wmin = 0
fontgroup = eval('hersheydata.' + fontgroupname)
# Render list of font names in a vertical column:
nFontIndex = 0
for f in fontgroup:
w = 0
letterVals = [ord(q) - 32 for q in (f[1] + ' -> ')]
# we want to right-justify the clear text, so need to know its width
for q in letterVals:
w = svg_text_width(q, clearfont, w)
# Render list of font names in a vertical column:
nFontIndex = 0
for f in fontgroup:
w = 0
letterVals = [ord(q) - 32 for q in (f[1] + ' -> ')]
# we want to right-justify the clear text, so need to know its width
for q in letterVals:
w = svg_text_width(q, clearfont, w)
w = -w # move the name text left by its width
if w < wmin:
wmin = w
# print the font name
for q in letterVals:
w = draw_svg_text(q, clearfont, w, v, parent)
v += FONT_GROUP_V_SPACING
if w > wmax:
wmax = w
w = -w # move the name text left by its width
if w < wmin:
wmin = w
# print the font name
for q in letterVals:
w = draw_svg_text(q, clearfont, w, v, parent)
v += FONT_GROUP_V_SPACING
if w > wmax:
wmax = w
# Next, we render a second column. The user's text, in each of the different fonts:
v = 0 # back to top line
wmaxname = wmax + 8 # single space width
for f in fontgroup:
w = wmaxname
font = eval('hersheydata.' + f[0])
# evaluate text string
letterVals = [ord(q) - 32 for q in self.options.text]
for q in letterVals:
if (q <= 0) or (q > 95):
w += 2 * spacing
else:
w = draw_svg_text(q, font, w, v, parent)
v += FONT_GROUP_V_SPACING
if w > wmax:
wmax = w
return wmax + wmin, v
# Next, we render a second column. The user's text, in each of the different fonts:
v = 0 # back to top line
wmaxname = wmax + 8 # single space width
for f in fontgroup:
w = wmaxname
font = eval('hersheydata.' + f[0])
# evaluate text string
letterVals = [ord(q) - 32 for q in self.options.text]
for q in letterVals:
if (q <= 0) or (q > 95):
w += 2 * spacing
else:
w = draw_svg_text(q, font, w, v, parent)
v += FONT_GROUP_V_SPACING
if w > wmax:
wmax = w
return wmax + wmin, v
if __name__ == '__main__':
e = Hershey()
e.affect()
e = Hershey()
e.affect()
# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -10,157 +10,157 @@ N_EGG_AXIS_STEPS = 3200 # steps for the egg motor move in one revolution
class EggbotPlotEAGLE:
def __init__(self, parent):
self.parent = parent
self.serialPort = parent.serialPort
def __init__(self, parent):
self.parent = parent
self.serialPort = parent.serialPort
def reset(self):
self.fX = None
self.fY = None
self.fPrevX = None
self.fPrevY = None
self.bPenIsUp = True
self.ptFirst = None
self.bStartingFirstLayer = True
def reset(self):
self.fX = None
self.fY = None
self.fPrevX = None
self.fPrevY = None
self.bPenIsUp = True
self.ptFirst = None
self.bStartingFirstLayer = True
def plot(self):
def plot(self):
if self.serialPort is None or not self.serialPort.isConnected():
return
if self.serialPort is None or not self.serialPort.isConnected():
return
self.reset()
self.initializeServo()
self.reset()
self.initializeServo()
""" Start the plot function on a new thread so the gui still runs """
args = ('filename',)
thread.start_new_thread(self.start, args)
""" Start the plot function on a new thread so the gui still runs """
args = ('filename',)
thread.start_new_thread(self.start, args)
"""
Send the EBB the up, down, and speed settings for the servo.
"""
"""
Send the EBB the up, down, and speed settings for the servo.
"""
def initializeServo(self):
self.serialPort.sendServoUpSetting(self.parent.nPenUpPos)
self.serialPort.sendServoDownSetting(self.parent.nPenDownPos)
self.serialPort.sendServoSpeedSetting(self.parent.nPenRaiseSpeed)
def initializeServo(self):
self.serialPort.sendServoUpSetting(self.parent.nPenUpPos)
self.serialPort.sendServoDownSetting(self.parent.nPenDownPos)
self.serialPort.sendServoSpeedSetting(self.parent.nPenRaiseSpeed)
""" start the recursive calls to plot the svg file, and update the gui when plot is finished """
""" start the recursive calls to plot the svg file, and update the gui when plot is finished """
def start(self, filen):
try:
self.penUp(True) # force a pen up at the start
""" store home """
self.fPrevX = N_EGG_AXIS_STEPS
self.fX = 0
self.fPrevY = N_PEN_AXIS_STEPS / 2
self.ptFirst = (self.fPrevX, self.fPrevY)
def start(self, filen):
try:
self.penUp(True) # force a pen up at the start
""" store home """
self.fPrevX = N_EGG_AXIS_STEPS
self.fX = 0
self.fPrevY = N_PEN_AXIS_STEPS / 2
self.ptFirst = (self.fPrevX, self.fPrevY)
self.plotfile(filen)
self.serialPort.sendPenUp()
self.penUp(True) # force a pen up at the end
self.plotfile(filen)
self.serialPort.sendPenUp()
self.penUp(True) # force a pen up at the end
if self.parent.bReturnToHome and self.ptFirst is not None:
(self.fX, self.fY) = self.ptFirst
self.plotLineAndTime() # return to home line
if self.parent.bReturnToHome and self.ptFirst is not None:
(self.fX, self.fY) = self.ptFirst
self.plotLineAndTime() # return to home line
self.parent.plotHasFinished()
self.parent.plotHasFinished()
except Exception as ex:
self.parent.reportError('Exception during plot: ' + str(ex))
except Exception as ex:
self.parent.reportError('Exception during plot: ' + str(ex))
"""
Plot a simple pen plotter style file containing commands of the form
C<pennumber> ;; select pen
M<x>,<y> ;; Move to x,y with pen up.
D<x>,<y> ;; Move to x,y with pen down.
x and y are in steps. EAGLE (and probably other programs) can be
coerced into generating this format of file.
The main purpose of this program is to convert absolute coordinates
to the relative step counts and timing needed by eggbot.
"""
"""
Plot a simple pen plotter style file containing commands of the form
C<pennumber> ;; select pen
M<x>,<y> ;; Move to x,y with pen up.
D<x>,<y> ;; Move to x,y with pen down.
x and y are in steps. EAGLE (and probably other programs) can be
coerced into generating this format of file.
The main purpose of this program is to convert absolute coordinates
to the relative step counts and timing needed by eggbot.
"""
def plotfile(self, filen):
self.infile = open('/tmp/plot.out', 'r')
for line in self.infile:
""" check if the user has hit the "stop" button """
if self.parent.isStopped():
return
if self.parent.isPaused():
while self.parent.isPaused():
time.sleep(.5) # wait half a second before checking again
def plotfile(self, filen):
self.infile = open('/tmp/plot.out', 'r')
for line in self.infile:
""" check if the user has hit the "stop" button """
if self.parent.isStopped():
return
if self.parent.isPaused():
while self.parent.isPaused():
time.sleep(.5) # wait half a second before checking again
""" make sure the pen is still in the right start before we start """
if self.bPenIsUp:
self.penUp(True)
else:
self.penDown(True)
""" make sure the pen is still in the right start before we start """
if self.bPenIsUp:
self.penUp(True)
else:
self.penDown(True)
# print line[:-1]
if line[0] == 'C':
""" pause between layers, but don't pause before the first layer """
if self.bStartingFirstLayer:
self.bStartingFirstLayer = False
else:
self.parent.pauseForPenChange(line)
# print line[:-1]
if line[0] == 'C':
""" pause between layers, but don't pause before the first layer """
if self.bStartingFirstLayer:
self.bStartingFirstLayer = False
else:
self.parent.pauseForPenChange(line)
elif line[0] == 'M':
self.fX = N_EGG_AXIS_STEPS - int(line.split(',')[0][1:])
self.fY = int(line.split(',')[1])
# print " Move ", self.fX, self.fY
self.penUp()
self.plotLineAndTime()
self.fPrevX = self.fX
self.fPrevY = self.fY
elif line[0] == 'D':
self.fX = N_EGG_AXIS_STEPS - int(line.split(',')[0][1:])
self.fY = int(line.split(',')[1])
# print " Draw ", self.fX, self.fY
self.penDown()
self.plotLineAndTime()
self.fPrevX = self.fX
self.fPrevY = self.fY
else:
# Nothing yet
pass
elif line[0] == 'M':
self.fX = N_EGG_AXIS_STEPS - int(line.split(',')[0][1:])
self.fY = int(line.split(',')[1])
# print " Move ", self.fX, self.fY
self.penUp()
self.plotLineAndTime()
self.fPrevX = self.fX
self.fPrevY = self.fY
elif line[0] == 'D':
self.fX = N_EGG_AXIS_STEPS - int(line.split(',')[0][1:])
self.fY = int(line.split(',')[1])
# print " Draw ", self.fX, self.fY
self.penDown()
self.plotLineAndTime()
self.fPrevX = self.fX
self.fPrevY = self.fY
else:
# Nothing yet
pass
def penUp(self, bForcePenUp=False):
if (not self.bPenIsUp) or bForcePenUp:
self.serialPort.sendPenUp()
self.bPenIsUp = True
self.serialPort.sendPause(self.parent.nPenUpDelay)
def penUp(self, bForcePenUp=False):
if (not self.bPenIsUp) or bForcePenUp:
self.serialPort.sendPenUp()
self.bPenIsUp = True
self.serialPort.sendPause(self.parent.nPenUpDelay)
def penDown(self, bForcePenDown=False):
if self.bPenIsUp or bForcePenDown:
self.serialPort.sendPenDown()
self.bPenIsUp = False
self.serialPort.sendPause(self.parent.nPenDownDelay)
def penDown(self, bForcePenDown=False):
if self.bPenIsUp or bForcePenDown:
self.serialPort.sendPenDown()
self.bPenIsUp = False
self.serialPort.sendPause(self.parent.nPenDownDelay)
"""
Send commands out the com port as a line segment (dx, dy) and a time (ms) the segment
should take to implement
"""
"""
Send commands out the com port as a line segment (dx, dy) and a time (ms) the segment
should take to implement
"""
def plotLineAndTime(self):
def plotLineAndTime(self):
if self.fPrevX is None:
return
if self.fPrevX is None:
return
if self.bPenIsUp:
nSpeed = self.parent.nPenUpSpeed
else:
nSpeed = self.parent.nPenDownSpeed
if self.bPenIsUp:
nSpeed = self.parent.nPenUpSpeed
else:
nSpeed = self.parent.nPenDownSpeed
nDeltaX = int(self.fX) - int(self.fPrevX)
nDeltaY = int(self.fY) - int(self.fPrevY)
nDeltaX = int(self.fX) - int(self.fPrevX)
nDeltaY = int(self.fY) - int(self.fPrevY)
nTime = int(round(1000.0 / nSpeed * distance(nDeltaX, nDeltaY)))
nTime = int(round(1000.0 / nSpeed * distance(nDeltaX, nDeltaY)))
self.serialPort.sendMove(nDeltaX, nDeltaY, nTime)
self.serialPort.sendMove(nDeltaX, nDeltaY, nTime)
""" overload this function since we don't need to access the svg document """
""" overload this function since we don't need to access the svg document """
def uniqueId(self, prefix):
return "1"
def uniqueId(self, prefix):
return "1"
"""
@ -169,4 +169,4 @@ Pythagorean theorem
def distance(x, y):
return sqrt(x * x + y * y)
return sqrt(x * x + y * y)

Wyświetl plik

@ -19,230 +19,230 @@ RUNNING, PAUSED, STOPPED = range(3)
class EggbotGui(wx.Frame):
ID_TIMER = 30 # ms
ID_TIMER = 30 # ms
def __init__(self, docRoot):
def __init__(self, docRoot):
wx.Frame.__init__(self, None, wx.ID_ANY, "Eggbot Plotter")
wx.Frame.__init__(self, None, wx.ID_ANY, "Eggbot Plotter")
self.initializeVariables()
self.CreateStatusBar()
self.menu = EggbotGuiMenu.EggbotGuiMenu(self)
self.serialPort = EggbotSerial.EggbotSerial()
self.EAGLEPlotter = EggbotPlotEAGLE.EggbotPlotEAGLE(self)
self.docRoot = docRoot
self.createButtons()
self.initializeVariables()
self.CreateStatusBar()
self.menu = EggbotGuiMenu.EggbotGuiMenu(self)
self.serialPort = EggbotSerial.EggbotSerial()
self.EAGLEPlotter = EggbotPlotEAGLE.EggbotPlotEAGLE(self)
self.docRoot = docRoot
self.createButtons()
guiRect = self.GetScreenRect()
guiRect = self.GetScreenRect()
self.config = EggbotGuiConfiguration.EggbotGuiConfiguration(self)
self.config.Move(guiRect.GetTopRight())
self.config.Show(True)
self.config = EggbotGuiConfiguration.EggbotGuiConfiguration(self)
self.config.Move(guiRect.GetTopRight())
self.config.Show(True)
configRect = self.config.GetScreenRect()
configRect = self.config.GetScreenRect()
self.manualControl = EggbotGuiManualControl.EggbotGuiManualControl(self)
self.manualControl.Show(True)
self.manualControl.Move(configRect.GetBottomLeft())
self.manualControl = EggbotGuiManualControl.EggbotGuiManualControl(self)
self.manualControl.Show(True)
self.manualControl.Move(configRect.GetBottomLeft())
def createButtons(self):
def createButtons(self):
self.SetAutoLayout(True)
self.SetAutoLayout(True)
panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
panel = wx.Panel(self)
vbox = wx.BoxSizer(wx.VERTICAL)
if self.serialPort.getCurrentPort() is None:
strStatus = "\nNo eggbot connected\n\n"
else:
strStatus = "\nConnected to " + self.serialPort.getCurrentPort() + "\n"
if self.serialPort.getCurrentPort() is None:
strStatus = "\nNo eggbot connected\n\n"
else:
strStatus = "\nConnected to " + self.serialPort.getCurrentPort() + "\n"
strEbbVersion = self.serialPort.getEggbotVersion()
strEbbVersion = self.serialPort.getEggbotVersion()
if strEbbVersion is None:
strEbbVersion = "Eggbot firmware not found"
if strEbbVersion is None:
strEbbVersion = "Eggbot firmware not found"
strStatus += strEbbVersion + "\n"
strStatus += strEbbVersion + "\n"
self.messageText = wx.StaticText(panel, -1, strStatus, style=wx.ALIGN_CENTER)
vbox.Add(self.messageText, flag=wx.ALIGN_CENTER)
self.messageText = wx.StaticText(panel, -1, strStatus, style=wx.ALIGN_CENTER)
vbox.Add(self.messageText, flag=wx.ALIGN_CENTER)
hbox = wx.BoxSizer(wx.HORIZONTAL)
hbox = wx.BoxSizer(wx.HORIZONTAL)
self.startPauseButton = wx.Button(panel, ID_STARTPAUSE_BUTTON, 'Start')
self.Bind(wx.EVT_BUTTON, self.onStartOrPause, self.startPauseButton)
hbox.Add(self.startPauseButton)
self.startPauseButton = wx.Button(panel, ID_STARTPAUSE_BUTTON, 'Start')
self.Bind(wx.EVT_BUTTON, self.onStartOrPause, self.startPauseButton)
hbox.Add(self.startPauseButton)
self.stopButton = wx.Button(panel, ID_STOP_BUTTON, 'Stop')
self.Bind(wx.EVT_BUTTON, self.onStop, self.stopButton)
hbox.Add(self.stopButton)
self.stopButton = wx.Button(panel, ID_STOP_BUTTON, 'Stop')
self.Bind(wx.EVT_BUTTON, self.onStop, self.stopButton)
hbox.Add(self.stopButton)
vbox.Add(hbox, flag=wx.ALIGN_CENTER)
vbox.Add(hbox, flag=wx.ALIGN_CENTER)
panel.SetSizerAndFit(vbox)
self.SetClientSize(panel.GetSize())
self.Show(True)
panel.SetSizerAndFit(vbox)
self.SetClientSize(panel.GetSize())
self.Show(True)
def __del__(self):
if self.serialPort is not None:
self.serialPort.__del__()
def __del__(self):
if self.serialPort is not None:
self.serialPort.__del__()
def onStartOrPause(self, event):
if self.eCurrentState == STOPPED:
""" start plot """
self.eCurrentState = RUNNING
self.startPauseButton.SetLabel('Pause')
self.EAGLEPlotter.plot()
def onStartOrPause(self, event):
if self.eCurrentState == STOPPED:
""" start plot """
self.eCurrentState = RUNNING
self.startPauseButton.SetLabel('Pause')
self.EAGLEPlotter.plot()
elif self.eCurrentState == PAUSED:
""" unpause plot """
self.eCurrentState = RUNNING
self.startPauseButton.SetLabel('Pause')
elif self.eCurrentState == PAUSED:
""" unpause plot """
self.eCurrentState = RUNNING
self.startPauseButton.SetLabel('Pause')
else:
""" pause plot """
self.eCurrentState = PAUSED
self.startPauseButton.SetLabel('Resume')
else:
""" pause plot """
self.eCurrentState = PAUSED
self.startPauseButton.SetLabel('Resume')
def onStop(self, event):
if self.eCurrentState == STOPPED:
pass
else:
self.eCurrentState = STOPPED
self.startPauseButton.SetLabel('Start')
def onStop(self, event):
if self.eCurrentState == STOPPED:
pass
else:
self.eCurrentState = STOPPED
self.startPauseButton.SetLabel('Start')
""" reset variables? """
""" reset variables? """
def isStopped(self):
return self.eCurrentState == STOPPED
def isStopped(self):
return self.eCurrentState == STOPPED
def isPaused(self):
return self.eCurrentState == PAUSED
def isPaused(self):
return self.eCurrentState == PAUSED
def onTimer(self, event):
if event.GetId() == EggbotGui.ID_TIMER:
pass
else:
event.Skip()
def onTimer(self, event):
if event.GetId() == EggbotGui.ID_TIMER:
pass
else:
event.Skip()
def sendServoInitialization(self):
if self.eCurrentState != RUNNING and self.serialPort.isConnected():
self.EAGLEPlotter.initializeServo()
pass
def sendServoInitialization(self):
if self.eCurrentState != RUNNING and self.serialPort.isConnected():
self.EAGLEPlotter.initializeServo()
pass
def sendManualPenUp(self):
if self.eCurrentState != RUNNING and self.serialPort.isConnected():
self.serialPort.sendPenUp()
def sendManualPenUp(self):
if self.eCurrentState != RUNNING and self.serialPort.isConnected():
self.serialPort.sendPenUp()
def sendManualPenDown(self):
if self.eCurrentState != RUNNING and self.serialPort.isConnected():
self.serialPort.sendPenDown()
def sendManualPenDown(self):
if self.eCurrentState != RUNNING and self.serialPort.isConnected():
self.serialPort.sendPenDown()
def sendManualMove(self, nXSteps, nYSteps):
if self.eCurrentState != RUNNING and self.serialPort.isConnected():
self.serialPort.sendMove(nXSteps, nYSteps, 5 * (abs(nXSteps) + abs(nYSteps)))
def sendManualMove(self, nXSteps, nYSteps):
if self.eCurrentState != RUNNING and self.serialPort.isConnected():
self.serialPort.sendMove(nXSteps, nYSteps, 5 * (abs(nXSteps) + abs(nYSteps)))
def reconnectToEggbot(self):
if self.serialPort is not None:
self.serialPort.reconnectToEggbot()
else:
self.serialPort = EggbotSerial.EggbotSerial()
def reconnectToEggbot(self):
if self.serialPort is not None:
self.serialPort.reconnectToEggbot()
else:
self.serialPort = EggbotSerial.EggbotSerial()
def initializeVariables(self):
self.eCurrentState = STOPPED
self.bMotorsEnabled = True
def initializeVariables(self):
self.eCurrentState = STOPPED
self.bMotorsEnabled = True
""" try to load parameters from the file """
if os.path.exists('eggbot.cfg'):
try:
config = ConfigObj(CONFIG_FILE_NAME)
self.nPenDownSpeed = int(config['penDownSpeed'])
self.nPenUpSpeed = int(config['penUpSpeed'])
self.nPenDownDelay = int(config['penDownDelay'])
self.nPenUpDelay = int(config['penUpDelay'])
self.nSmoothness = int(config['smoothness'])
self.bPauseBetweenLayers = (config['pauseBetweenLayers'] == 'True') # simple boolean cast
self.bReturnToHome = (config['returnToHome'] == 'True')
self.bCenterPlot = (config['centerPlot'] == 'True')
self.nPenUpPos = int(config['penUpPos'])
self.nPenDownPos = int(config['penDownPos'])
self.nPenRaiseSpeed = int(config['penRaiseSpeed'])
return
""" try to load parameters from the file """
if os.path.exists('eggbot.cfg'):
try:
config = ConfigObj(CONFIG_FILE_NAME)
self.nPenDownSpeed = int(config['penDownSpeed'])
self.nPenUpSpeed = int(config['penUpSpeed'])
self.nPenDownDelay = int(config['penDownDelay'])
self.nPenUpDelay = int(config['penUpDelay'])
self.nSmoothness = int(config['smoothness'])
self.bPauseBetweenLayers = (config['pauseBetweenLayers'] == 'True') # simple boolean cast
self.bReturnToHome = (config['returnToHome'] == 'True')
self.bCenterPlot = (config['centerPlot'] == 'True')
self.nPenUpPos = int(config['penUpPos'])
self.nPenDownPos = int(config['penDownPos'])
self.nPenRaiseSpeed = int(config['penRaiseSpeed'])
return
except:
pass
except:
pass
""" if no file exists or it has an error, just use the defaults """
self.nPenDownSpeed = 200
self.nPenUpSpeed = 400
self.nPenDownDelay = 4
self.nPenUpDelay = 4
self.nSmoothness = 100 # %
self.bReturnToHome = True
self.bPauseBetweenLayers = True
self.bCenterPlot = False
self.nPenUpPos = 13500
self.nPenDownPos = 11000
self.nPenRaiseSpeed = 150
""" if no file exists or it has an error, just use the defaults """
self.nPenDownSpeed = 200
self.nPenUpSpeed = 400
self.nPenDownDelay = 4
self.nPenUpDelay = 4
self.nSmoothness = 100 # %
self.bReturnToHome = True
self.bPauseBetweenLayers = True
self.bCenterPlot = False
self.nPenUpPos = 13500
self.nPenDownPos = 11000
self.nPenRaiseSpeed = 150
""" store the current settings """
""" store the current settings """
def storeConfiguration(self):
config = ConfigObj(CONFIG_FILE_NAME)
config['penDownSpeed'] = self.nPenDownSpeed
config['penUpSpeed'] = self.nPenUpSpeed
config['penDownDelay'] = self.nPenDownDelay
config['penUpDelay'] = self.nPenUpDelay
config['smoothness'] = self.nSmoothness
config['pauseBetweenLayers'] = self.bPauseBetweenLayers
config['returnToHome'] = self.bReturnToHome
config['centerPlot'] = self.bCenterPlot
config['penUpPos'] = self.nPenUpPos
config['penDownPos'] = self.nPenDownPos
config['penRaiseSpeed'] = self.nPenRaiseSpeed
config.write()
def storeConfiguration(self):
config = ConfigObj(CONFIG_FILE_NAME)
config['penDownSpeed'] = self.nPenDownSpeed
config['penUpSpeed'] = self.nPenUpSpeed
config['penDownDelay'] = self.nPenDownDelay
config['penUpDelay'] = self.nPenUpDelay
config['smoothness'] = self.nSmoothness
config['pauseBetweenLayers'] = self.bPauseBetweenLayers
config['returnToHome'] = self.bReturnToHome
config['centerPlot'] = self.bCenterPlot
config['penUpPos'] = self.nPenUpPos
config['penDownPos'] = self.nPenDownPos
config['penRaiseSpeed'] = self.nPenRaiseSpeed
config.write()
def pauseForPenChange(self, strLayerName):
if strLayerName is None:
strLayerName = ''
def pauseForPenChange(self, strLayerName):
if strLayerName is None:
strLayerName = ''
okDialog = wx.MessageDialog(self, 'About to plot new pen "' + strLayerName + '", would you like ' +
'to change pens? Press okay to continue.', '', wx.OK)
okDialog.ShowModal()
okDialog.Destroy()
okDialog = wx.MessageDialog(self, 'About to plot new pen "' + strLayerName + '", would you like ' +
'to change pens? Press okay to continue.', '', wx.OK)
okDialog.ShowModal()
okDialog.Destroy()
""" end of plot, same as user hitting stop """
""" end of plot, same as user hitting stop """
def plotHasFinished(self):
self.onStop(None)
def plotHasFinished(self):
self.onStop(None)
def toggleDisableMotors(self):
if self.eCurrentState != RUNNING:
if self.bMotorsEnabled:
self.bMotorsEnabled = False
self.serialPort.sendDisableMotors()
else:
self.bMotorsEnabled = True
self.serialPort.sendEnableMotors()
def toggleDisableMotors(self):
if self.eCurrentState != RUNNING:
if self.bMotorsEnabled:
self.bMotorsEnabled = False
self.serialPort.sendDisableMotors()
else:
self.bMotorsEnabled = True
self.serialPort.sendEnableMotors()
def reportError(self, strError):
errDialog = wx.MessageDialog(self, strError + '\n\nContinue?', 'Error', wx.YES_NO)
def reportError(self, strError):
errDialog = wx.MessageDialog(self, strError + '\n\nContinue?', 'Error', wx.YES_NO)
if errDialog.ShowModal() == wx.ID_NO:
self.onStop(None)
if errDialog.ShowModal() == wx.ID_NO:
self.onStop(None)
errDialog.Destroy()
errDialog.Destroy()
if __name__ == '__main__':
class fakeDoc:
def getRoot(self):
return []
class fakeDoc:
def getRoot(self):
return []
fakeCaller = fakeDoc()
fakeCaller.document = fakeDoc()
fakeCaller = fakeDoc()
fakeCaller.document = fakeDoc()
app = wx.PySimpleApp()
frame = EggbotGui(fakeCaller)
app.MainLoop()
app = wx.PySimpleApp()
frame = EggbotGui(fakeCaller)
app.MainLoop()