hpgl_output.py revision c902a1546edbbe3fa12e1488726dee8718afc317
#!/usr/bin/env python
# coding=utf-8
'''
Copyright (C) 2008 Aaron Spike, aaron@ekips.org
Overcut, Tool Offset, Rotation, Serial Com., Many Bugfixes and Improvements: Sebastian Wüst, sebi@timewaster.de, http://www.timewasters-place.com/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'''
import inkex, simpletransform, cubicsuperpath, simplestyle, sys, math, bezmisc, string, cspsubdiv
class MyEffect(inkex.Effect):
def __init__(self):
inkex.Effect.__init__(self)
self.OptionParser.add_option("-f", "--flatness",
action="store", type="float",
dest="flat", default=1.2,
help="Minimum flatness of the subdivided curves")
self.OptionParser.add_option("-m", "--mirror",
action="store", type="inkbool",
dest="mirror", default="FALSE",
help="Mirror Y-Axis")
self.OptionParser.add_option("-x", "--xOffset",
action="store", type="float",
dest="xOffset", default=0.0,
help="X offset (mm)")
self.OptionParser.add_option("-y", "--yOffset",
action="store", type="float",
dest="yOffset", default=0.0,
help="Y offset (mm)")
self.OptionParser.add_option("-r", "--resolution",
action="store", type="int",
dest="resolution", default=1016,
help="Resolution (dpi)")
self.OptionParser.add_option("-n", "--pen",
action="store", type="int",
dest="pen", default=1,
help="Pen number")
self.OptionParser.add_option("-p", "--plotInvisibleLayers",
action="store", type="inkbool",
dest="plotInvisibleLayers", default="FALSE",
help="Plot invisible layers")
self.OptionParser.add_option("-u", "--useOvercut",
action="store", type="inkbool",
dest="useOvercut", default="TRUE",
help="Use Overcut")
self.OptionParser.add_option("-o", "--overcut",
action="store", type="float",
dest="overcut", default=1.0,
help="Overcut (mm)")
self.OptionParser.add_option("-c", "--correctToolOffset",
action="store", type="inkbool",
dest="correctToolOffset", default="TRUE",
help="Correct tool offset")
self.OptionParser.add_option("-t", "--toolOffset",
action="store", type="float",
dest="toolOffset", default=0.25,
help="Tool offset (mm)")
self.OptionParser.add_option("-w", "--toolOffsetReturn",
action="store", type="float",
dest="toolOffsetReturn", default=2.5,
help="Return Factor")
self.OptionParser.add_option("-a", "--angle",
action="store", type="string",
dest="rotation", default="90",
help="Orientation")
self.OptionParser.add_option("-s", "--sendToPlotter",
action="store", type="inkbool",
dest="sendToPlotter", default="FALSE",
help="Send to Plotter also")
self.OptionParser.add_option("-z", "--port",
action="store", type="string",
dest="port", default="COM1",
help="Serial Port")
self.OptionParser.add_option("-b", "--baudRate",
action="store", type="string",
dest="baudRate", default="9600",
help="Baud Rate")
def effect(self):
# initiate vars
self.xDivergence = 999999999999.0
self.yDivergence = 999999999999.0
scale = float(self.options.resolution) / 90 # dots/inch to dots/pixels
x0 = self.options.xOffset * 3.5433070866 * scale # mm to dots
y0 = self.options.yOffset * 3.5433070866 * scale # mm to dots
self.options.overcut = self.options.overcut * 3.5433070866 * scale # mm to dots
self.options.toolOffset = self.options.toolOffset * 3.5433070866 * scale # mm to dots
self.options.flat = float(self.options.resolution) * self.options.flat / 1000
doc = self.document.getroot()
mirror = 1.0
if self.options.mirror:
mirror = -1.0
# get viewBox parameter to correct scaling
viewBox = doc.get('viewBox')
viewBoxTransformX = 1
viewBoxTransformY = 1
if viewBox:
viewBox = string.split(viewBox, ' ')
if viewBox[2] and viewBox[3]:
viewBoxTransformX = float(inkex.unittouu(doc.get('width'))) / float(viewBox[2])
viewBoxTransformY = float(inkex.unittouu(doc.get('height'))) / float(viewBox[3])
# dryRun to find edges
self.dryRun = True
self.groupmat = [[[scale*viewBoxTransformX, 0.0, 0.0], [0.0, mirror*scale*viewBoxTransformY, 0.0]]]
self.groupmat[0] = simpletransform.composeTransform(self.groupmat[0], simpletransform.parseTransform('rotate(' + self.options.rotation + ')'))
self.vData = [['', -1.0, -1.0], ['', -1.0, -1.0], ['', -1.0, -1.0], ['', -1.0, -1.0]]
self.process_group(doc)
# life run
self.dryRun = False
self.groupmat = [[[scale*viewBoxTransformX, 0.0, -self.xDivergence + x0], [0.0, mirror*scale*viewBoxTransformY, -self.yDivergence + y0]]]
self.groupmat[0] = simpletransform.composeTransform(self.groupmat[0], simpletransform.parseTransform('rotate(' + self.options.rotation + ')'))
self.vData = [['', -1.0, -1.0], ['', -1.0, -1.0], ['', -1.0, -1.0], ['', -1.0, -1.0]]
self.hpgl = 'IN;SP%d;' % self.options.pen
if self.options.sendToPlotter:
try:
import serial
except ImportError, e:
inkex.errormsg('pySerial is not installed.\n\n1. Download pySerial here: http://pypi.python.org/pypi/pyserial\n2. Extract the "serial" folder from the zip to the following folder: "C:\Program Files (x86)\inkscape\python\Lib"\n3. Restart Inkscape.')
self.options.sendToPlotter = False
if self.options.sendToPlotter:
self.S = serial.Serial(port=self.options.port, baudrate=self.options.baudRate, timeout=0.01, writeTimeout=None)
self.S.write('IN;SP%d;' % self.options.pen)
self.process_group(doc)
self.calcOffset('PU', 0, 0)
self.hpgl += 'PU0,0;'
if self.options.sendToPlotter:
self.S.write('PU0,0;')
self.S.read(2)
self.S.close()
def process_group(self, group):
# process groups
style = group.get('style')
if style:
style = simplestyle.parseStyle(style)
if style.has_key('display'):
if style['display'] == 'none':
if not self.options.plotInvisibleLayers:
return
trans = group.get('transform')
if trans:
self.groupmat.append(simpletransform.composeTransform(self.groupmat[-1], simpletransform.parseTransform(trans)))
for node in group:
if node.tag == inkex.addNS('path','svg'):
self.process_path(node, self.groupmat[-1])
if node.tag == inkex.addNS('g','svg'):
self.process_group(node)
if trans:
self.groupmat.pop()
def process_path(self, node, mat):
# process oath
d = node.get('d')
if d:
# transform path
p = cubicsuperpath.parsePath(d)
trans = node.get('transform')
if trans:
mat = simpletransform.composeTransform(mat, simpletransform.parseTransform(trans))
simpletransform.applyTransformToPath(mat, p)
cspsubdiv.cspsubdiv(p, self.options.flat)
# break path into HPGL commands
xPosOld = -1
yPosOld = -1
for sp in p:
cmd = 'PU'
for csp in sp:
xPos = csp[1][0]
yPos = csp[1][1]
if int(xPos) != int(xPosOld) or int(yPos) != int(yPosOld):
self.calcOffset(cmd, xPos, yPos)
cmd = 'PD'
xPosOld = xPos
yPosOld = yPos
# perform overcut
if self.options.useOvercut:
if int(xPos) == int(sp[0][1][0]) and int(yPos) == int(sp[0][1][1]):
for csp in sp:
xPos2 = csp[1][0]
yPos2 = csp[1][1]
if int(xPos) != int(xPos2) or int(yPos) != int(yPos2):
self.calcOffset(cmd, xPos2, yPos2)
if self.options.overcut - self.getLength(xPosOld, yPosOld, xPos2, yPos2) <= 0:
break
xPos = xPos2
yPos = yPos2
def getLength(self, x1, y1, x2, y2, abs = True):
# calc absoulute or relative length of two points
if abs: return math.fabs(math.sqrt((x2 - x1) ** 2.0 + (y2 - y1) ** 2.0))
else: return math.sqrt((x2 - x1) ** 2.0 + (y2 - y1) ** 2.0)
def getAlpha(self, x1, y1, x2, y2, x3, y3):
temp1 = (x1-x2)**2 + (y1-y2)**2 + (x3-x2)**2 + (y3-y2)**2 - (x1-x3)**2 - (y1-y3)**2
temp2 = 2 * math.sqrt((x1-x2)**2 + (y1-y2)**2) * math.sqrt((x3-x2)**2 + (y3-y2)**2)
temp3 = temp1 / temp2
if temp3 < -1:
temp3 = -1
return math.acos(temp3)
def calcOffset(self, cmd, xPos, yPos):
# calculate offset correction (or dont))
if not self.options.correctToolOffset:
self.storeData(cmd, xPos, yPos)
else:
self.vData.pop(0)
self.vData.insert(3, [cmd, xPos, yPos])
if self.vData[2][1] != -1.0:
if self.vData[1][1] != -1.0:
if self.vData[2][0] == 'PD' and self.vData[3][0] == 'PD':
if self.getAlpha(self.vData[1][1], self.vData[1][2], self.vData[2][1], self.vData[2][2], self.vData[3][1], self.vData[3][2]) > 2.748893:
self.storeData(self.vData[2][0], self.vData[2][1], self.vData[2][2])
return
if self.vData[2][0] == 'PD':
self.storeData('PD',
self.changeLengthX(self.vData[1][1], self.vData[1][2], self.vData[2][1], self.vData[2][2], self.options.toolOffset),
self.changeLengthY(self.vData[1][1], self.vData[1][2], self.vData[2][1], self.vData[2][2], self.options.toolOffset))
elif self.vData[0][1] != -1.0:
self.storeData('PU',
self.vData[2][1] - (self.vData[1][1] - self.changeLengthX(self.vData[0][1], self.vData[0][2], self.vData[1][1], self.vData[1][2], self.options.toolOffset)),
self.vData[2][2] - (self.vData[1][2] - self.changeLengthY(self.vData[0][1], self.vData[0][2], self.vData[1][1], self.vData[1][2], self.options.toolOffset)))
else:
self.storeData('PU',
self.vData[2][1],
self.vData[2][2])
if self.vData[3][0] == 'PD':
self.storeData('PD',
self.changeLengthX(self.vData[3][1], self.vData[3][2], self.vData[2][1], self.vData[2][2], -(self.options.toolOffset * self.options.toolOffsetReturn)),
self.changeLengthY(self.vData[3][1], self.vData[3][2], self.vData[2][1], self.vData[2][2], -(self.options.toolOffset * self.options.toolOffsetReturn)))
else:
self.storeData(self.vData[2][0], self.vData[2][1], self.vData[2][2])
def storeData(self, command, x, y):
if self.dryRun:
if x < self.xDivergence: self.xDivergence = x
if y < self.yDivergence: self.yDivergence = y
else:
if x < 0: x = 0 # only positive values are allowed
if y < 0: y = 0
self.hpgl += '%s%d,%d;' % (command, x, y)
if self.options.sendToPlotter:
self.S.write('%s%d,%d;' % (command, x, y))
def changeLengthX(self, x1, y1, x2, y2, offset):
# change length between two points - x axis
if offset < 0: offset = max(-self.getLength(x1, y1, x2, y2), offset)
return x2 + (x2 - x1) / self.getLength(x1, y1, x2, y2, False) * offset;
def changeLengthY(self, x1, y1, x2, y2, offset):
# change length between two points - y axis
if offset < 0: offset = max(-self.getLength(x1, y1, x2, y2), offset)
return y2 + (y2 - y1) / self.getLength(x1, y1, x2, y2, False) * offset;
def output(self):
# print to file
print self.hpgl
if __name__ == '__main__':
# Raise recursion limit to avoid exceptions on big documents
sys.setrecursionlimit(20000);
# start calculations
e = MyEffect()
e.affect()
# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99