From 8bc4fd8b36f86b6a6c02a5118486bc7da999076f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20W=C3=BCst?= Date: Sat, 19 Oct 2013 20:50:05 +0200 Subject: finished tool offset correction; optimized hpgl data; fixed texts, small bugs, parameters; added flow control parameter; and more (bzr r12417.1.21) --- share/extensions/hpgl_decoder.py | 27 +++++++++------- share/extensions/hpgl_encoder.py | 67 ++++++++++++++++++++++++---------------- share/extensions/hpgl_input.inx | 2 ++ share/extensions/hpgl_output.inx | 10 ++---- share/extensions/hpgl_output.py | 1 - share/extensions/plotter.inx | 10 ++++-- share/extensions/plotter.py | 52 +++++++++++++++++-------------- 7 files changed, 95 insertions(+), 74 deletions(-) diff --git a/share/extensions/hpgl_decoder.py b/share/extensions/hpgl_decoder.py index 6331d8c03..802f6d0e4 100644 --- a/share/extensions/hpgl_decoder.py +++ b/share/extensions/hpgl_decoder.py @@ -21,6 +21,7 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # standard library from StringIO import StringIO +import math # local library import inkex @@ -46,7 +47,6 @@ class hpglDecoder: if self.options.showMovements: self.layers[0] = inkex.etree.SubElement(self.doc.getroot(), 'g', {inkex.addNS('groupmode','inkscape'):'layer', inkex.addNS('label','inkscape'):'Movements'}) # parse paths - # TODO:2013-07-13:Sebastian Wüst:Try to parse all the different HPGL formats correctly. hpglData = self.hpglString.split(';') if len(hpglData) < 3: raise Exception('NO_HPGL_DATA') @@ -54,7 +54,6 @@ class hpglDecoder: path = '' for i, command in enumerate(hpglData): if command.strip() != '': - # TODO:2013-07-13:Sebastian Wüst:Implement all the HP-GL commands. (Or pass it as PLT to UniConverter when unknown commands are found?) if command[:2] == 'IN': # if Initialize command, ignore pass elif command[:2] == 'SP': # if Select Pen command @@ -70,8 +69,18 @@ class hpglDecoder: path = 'M %f,%f' % self.getParameters(command[2:]) oldCoordinates = self.getParameters(command[2:]) elif command[:2] == 'PD': # if Pen Down command - path += ' L %f,%f' % self.getParameters(command[2:]) - oldCoordinates = self.getParameters(command[2:]) + parameterString = command[2:] + if parameterString.strip() != '': + parameterString = parameterString.replace(';', '').strip() + parameter = parameterString.split(',') + oldCoordinates = (float(parameter[-2]) / self.scaleX, self.options.docHeight - float(parameter[-1]) / self.scaleX) + for i, param in enumerate(parameter): + if i % 2 == 0: + parameter[i] = str(float(param) / self.scaleX) + else: + parameter[i] = str(self.options.docHeight - float(param) / self.scaleY) + parameterString = ','.join(parameter) + path += ' L %s' % parameterString else: self.warnings.append('UNKNOWN_COMMANDS') if ' L ' in path: @@ -87,7 +96,7 @@ class hpglDecoder: else: lineColor = '000000' inkex.etree.SubElement(self.layers[layerNumber], 'path', {'d':path, 'style':'stroke:#' + lineColor + '; stroke-width:0.4; fill:none;'}) - + def getParameters(self, parameterString): # process coordinates if parameterString.strip() == '': return [] @@ -95,12 +104,6 @@ class hpglDecoder: parameterString = parameterString.replace(';', '').strip() # split parameter parameter = parameterString.split(',') - # convert to svg coordinate system - parameter[0] = float(parameter[0]) / self.scaleX; # convert to pixels coordinate system - parameter[1] = self.options.docHeight - float(parameter[1]) / self.scaleY; # convert to pixels coordinate system, flip vertically for inkscape coordinate system - if len(parameter) == 2: - return (parameter[0], parameter[1]) - elif len(parameter) == 3: - return (parameter[0], parameter[1], parameter[2]) + return (float(parameter[0]) / self.scaleX, self.options.docHeight - float(parameter[1]) / self.scaleY) # convert to svg coordinate system # vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99 \ No newline at end of file diff --git a/share/extensions/hpgl_encoder.py b/share/extensions/hpgl_encoder.py index ce668ff28..58935e8fb 100644 --- a/share/extensions/hpgl_encoder.py +++ b/share/extensions/hpgl_encoder.py @@ -25,6 +25,10 @@ import math, string import bezmisc, cspsubdiv, cubicsuperpath, inkex, simplestyle, simpletransform class hpglEncoder: + + PI = math.pi + TWO_PI = PI * 2 + def __init__(self, doc, options): ''' options: "resolutionX":float @@ -39,7 +43,6 @@ class hpglEncoder: "overcut":float "useToolOffset":bool "toolOffset":float - "toolOffsetReturn":float "precut":bool "offsetX":float "offsetY":float @@ -51,6 +54,7 @@ class hpglEncoder: self.sizeX = 'False' self.sizeY = 'False' self.dryRun = True + self.lastPoint = [0, 0, 0] self.scaleX = self.options.resolutionX / 90 # inch to pixels self.scaleY = self.options.resolutionY / 90 # inch to pixels self.options.offsetX = self.options.offsetX * 3.5433070866 * self.scaleX # mm to dots (plotter coordinate system) @@ -58,13 +62,14 @@ class hpglEncoder: self.options.overcut = self.options.overcut * 3.5433070866 * ((self.scaleX + self.scaleY) / 2) # mm to dots self.options.toolOffset = self.options.toolOffset * 3.5433070866 * ((self.scaleX + self.scaleY) / 2) # mm to dots self.options.flat = ((self.options.resolutionX + self.options.resolutionY) / 2) * self.options.flat / 1000 # scale flatness to resolution + self.toolOffsetFlat = self.options.flat / self.options.toolOffset * 4.5 # scale flatness to offset self.mirrorX = 1.0 if self.options.mirrorX: self.mirrorX = -1.0 self.mirrorY = -1.0 if self.options.mirrorY: self.mirrorY = 1.0 - # process viewBox parameter to correct scaling + # process viewBox attribute to correct page scaling viewBox = doc.get('viewBox') self.viewBoxTransformX = 1 self.viewBoxTransformY = 1 @@ -94,7 +99,7 @@ class hpglEncoder: self.groupmat[0] = simpletransform.composeTransform(self.groupmat[0], simpletransform.parseTransform('rotate(' + self.options.orientation + ')')) self.vData = [['', -1.0, -1.0], ['', -1.0, -1.0], ['', -1.0, -1.0], ['', -1.0, -1.0]] # store first hpgl commands - self.hpgl = 'IN;SP%d;' % self.options.pen + self.hpgl = 'IN;SP%d' % self.options.pen # add precut if self.options.useToolOffset and self.options.precut: self.calcOffset('PU', 0, 0) @@ -104,7 +109,7 @@ class hpglEncoder: # shift an empty node in in order to process last node in cache self.calcOffset('PU', 0, 0) # add return to zero point - self.hpgl += 'PU0,0;' + self.hpgl += ';PU0,0;' return self.hpgl def process_group(self, group, groupmat): @@ -153,7 +158,7 @@ class hpglEncoder: # perform overcut if self.options.useOvercut and not self.dryRun: # check if last and first points are the same, otherwise the path is not closed and no overcut can be performed - if int(oldPosX) == int(singlePath[0][1][0]) and int(oldPosY) == int(singlePath[0][1][1]): + if int(round(oldPosX)) == int(round(singlePath[0][1][0])) and int(round(oldPosY)) == int(round(singlePath[0][1][1])): overcutLength = 0 for singlePathPoint in singlePath: posX, posY = singlePathPoint[1] @@ -217,24 +222,39 @@ class hpglEncoder: else: # Else just write the 3rd entry pointThree = [self.vData[2][1], self.vData[2][2]] self.storeData('PU', pointThree[0], pointThree[1]) - if self.vData[3][0] == 'PD': # If the 4th entry in the cache is a pen down command guide tool to next angle - # Create a circle between the prolonged 3rd and 4th entry to correctly guide the tool around the corner + if self.vData[3][0] == 'PD': # If the 4th entry in the cache is a pen down command guide tool to next line with a circle between the prolonged 3rd and 4th entry if self.getLength(self.vData[2][1], self.vData[2][2], self.vData[3][1], self.vData[3][2]) >= self.options.toolOffset: pointFour = self.changeLength(self.vData[3][1], self.vData[3][2], self.vData[2][1], self.vData[2][2], -self.options.toolOffset) else: pointFour = self.changeLength(self.vData[2][1], self.vData[2][2], self.vData[3][1], self.vData[3][2], (self.options.toolOffset - self.getLength(self.vData[2][1], self.vData[2][2], self.vData[3][1], self.vData[3][2]))) - alpha = self.angleDiff(math.atan2(pointThree[1] - self.vData[2][2], pointThree[0] - self.vData[2][1]) * 57.295779, - math.atan2(pointFour[1] - self.vData[2][2], pointFour[0] - self.vData[2][1]) * 57.295779) - if alpha > 15.0: - self.storeData('AA', self.vData[2][1], self.vData[2][2], alpha - 10) - if alpha < -15.0: - self.storeData('AA', self.vData[2][1], self.vData[2][2], alpha + 10) + # get start and end angle + angleStart = math.atan2(pointThree[1] - self.vData[2][2], pointThree[0] - self.vData[2][1]) + angleDiff = math.atan2(pointFour[1] - self.vData[2][2], pointFour[0] - self.vData[2][1]) - angleStart + # switch direction when arc is bigger than 180° + if angleDiff > self.PI: + angleDiff -= self.TWO_PI + elif angleDiff < -self.PI: + angleDiff += self.TWO_PI + # draw arc + if angleDiff >= 0: + angle = angleStart + self.toolOffsetFlat + while angle < angleStart + angleDiff: + self.storeData('PD', self.vData[2][1] + math.cos(angle) * self.options.toolOffset, self.vData[2][2] + math.sin(angle) * self.options.toolOffset) + angle += self.toolOffsetFlat + else: + angle = angleStart - self.toolOffsetFlat + while angle > angleStart + angleDiff: + self.storeData('PD', self.vData[2][1] + math.cos(angle) * self.options.toolOffset, self.vData[2][2] + math.sin(angle) * self.options.toolOffset) + angle -= self.toolOffsetFlat self.storeData('PD', pointFour[0], pointFour[1]) - def storeData(self, command, x, y, z="False"): + def storeData(self, command, x, y): x = int(round(x)) y = int(round(y)) + # skip when no change in movement + if self.lastPoint[0] == command and self.lastPoint[1] == x and self.lastPoint[2] == y: + return if self.dryRun: # find edges if self.divergenceX == 'False' or x < self.divergenceX: self.divergenceX = x @@ -246,18 +266,13 @@ class hpglEncoder: if not self.options.center: if x < 0: x = 0 # only positive values are allowed (usually) if y < 0: y = 0 - if z == "False": - self.hpgl += '%s%d,%d;' % (command, x, y) + # do not repeat command + if command == 'PD' and self.lastPoint[0] == 'PD': + self.hpgl += ',%d,%d' % (x, y) else: - z = int(round(z)) - self.hpgl += '%s%d,%d,%d;' % (command, x, y, z) - - def angleDiff(self, a1, a2): - diff = a2 - a1 - if diff > 180: - diff -= 360 - elif diff < -180: - diff += 360 - return diff + self.hpgl += ';%s%d,%d' % (command, x, y) + self.lastPoint[0] = command + self.lastPoint[1] = x + self.lastPoint[2] = y # vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99 \ No newline at end of file diff --git a/share/extensions/hpgl_input.inx b/share/extensions/hpgl_input.inx index dcf78c6ce..be0a0e943 100644 --- a/share/extensions/hpgl_input.inx +++ b/share/extensions/hpgl_input.inx @@ -5,6 +5,8 @@ hpgl_input.py hpgl_decoder.py inkex.py + <_param name="introduction" type="description">Please note that you can only open HPGL files written by Inkscape, to open other HPGL files please change their file extension to .plt, make sure you have UniConverter installed and open them again. +   1016.0 1016.0 false diff --git a/share/extensions/hpgl_output.inx b/share/extensions/hpgl_output.inx index e295df054..087ddeb7d 100644 --- a/share/extensions/hpgl_output.inx +++ b/share/extensions/hpgl_output.inx @@ -6,14 +6,12 @@ hpgl_output.py hpgl_encoder.py inkex.py - <_param name="introduction" type="description">Please make sure that all objects you want to save are converted to paths. The plot will automatically be aligned to the zero point. + <_param name="introduction" type="description">Please make sure that all objects you want to save are converted to paths. The drawing will be automatically aligned to the zero point. Please use the plotter extension from the extension menu to plot directly out of Inkscape. 1 -   1016.0 1016.0 -   false false @@ -29,22 +27,18 @@ 1.00 - true + true 0.25 - 2.50 -   true   <_param name="offsetNote" type="description">Please note that using the tool offset correction will shift your drawing away from the zero point by the length of the tool offset on both axes. 1.2 -   0.00 0.00 - <_param name="plottingNote" type="description">Please use the plotter extension from the extension menu to plot directly out of Inkscape. .hpgl image/hpgl diff --git a/share/extensions/hpgl_output.py b/share/extensions/hpgl_output.py index adee92cae..d2f9a8e75 100755 --- a/share/extensions/hpgl_output.py +++ b/share/extensions/hpgl_output.py @@ -42,7 +42,6 @@ class MyEffect(inkex.Effect): self.OptionParser.add_option('--overcut', action='store', type='float', dest='overcut', default=1.0, help='Overcut (mm)') self.OptionParser.add_option('--useToolOffset', action='store', type='inkbool', dest='useToolOffset', default='TRUE', help='Correct tool offset') self.OptionParser.add_option('--toolOffset', action='store', type='float', dest='toolOffset', default=0.25, help='Tool offset (mm)') - self.OptionParser.add_option('--toolOffsetReturn', action='store', type='float', dest='toolOffsetReturn', default=2.5, help='Return factor') self.OptionParser.add_option('--precut', action='store', type='inkbool', dest='precut', default='TRUE', help='Use precut') self.OptionParser.add_option('--offsetX', action='store', type='float', dest='offsetX', default=0.0, help='X offset (mm)') self.OptionParser.add_option('--offsetY', action='store', type='float', dest='offsetY', default=0.0, help='Y offset (mm)') diff --git a/share/extensions/plotter.inx b/share/extensions/plotter.inx index b034fd4a5..dc4152180 100644 --- a/share/extensions/plotter.inx +++ b/share/extensions/plotter.inx @@ -40,6 +40,12 @@ + + + + + +   <_param name="serialHelp" type="description">This can be a physical serial connection or a USB-to-Serial bridge. Ask your plotter manufacturer for drivers if needed. <_param name="parallelHelp" type="description">Please note that Parallel (LPT) connections are not supported. <_param name="hpglNote" type="description">Please note that only the HPGL command language is supported at the moment. @@ -49,10 +55,8 @@ 1.00 - true + true 0.25 - 2.50 -   true   <_param name="offsetNote" type="description">Please note that using the tool offset correction will move your drawing away from the zero point on both axes by the tool offset specified. diff --git a/share/extensions/plotter.py b/share/extensions/plotter.py index db05c03cc..7e2481d29 100644 --- a/share/extensions/plotter.py +++ b/share/extensions/plotter.py @@ -28,25 +28,25 @@ inkex.localize() class MyEffect(inkex.Effect): def __init__(self): inkex.Effect.__init__(self) - self.OptionParser.add_option('--tab', action='store', type='string', dest='tab') - self.OptionParser.add_option('--resolutionX', action='store', type='float', dest='resolutionX', default=1016.0, help='Resolution X (dpi)') - self.OptionParser.add_option('--resolutionY', action='store', type='float', dest='resolutionY', default=1016.0, help='Resolution Y (dpi)') - self.OptionParser.add_option('--pen', action='store', type='int', dest='pen', default=1, help='Pen number') - self.OptionParser.add_option('--orientation', action='store', type='string', dest='orientation', default='90', help='orientation') - self.OptionParser.add_option('--mirrorX', action='store', type='inkbool', dest='mirrorX', default='FALSE', help='Mirror X-axis') - self.OptionParser.add_option('--mirrorY', action='store', type='inkbool', dest='mirrorY', default='FALSE', help='Mirror Y-axis') - self.OptionParser.add_option('--center', action='store', type='inkbool', dest='center', default='FALSE', help='Center zero point') - self.OptionParser.add_option('--flat', action='store', type='float', dest='flat', default=1.2, help='Curve flatness') - self.OptionParser.add_option('--useOvercut', action='store', type='inkbool', dest='useOvercut', default='TRUE', help='Use overcut') - self.OptionParser.add_option('--overcut', action='store', type='float', dest='overcut', default=1.0, help='Overcut (mm)') - self.OptionParser.add_option('--useToolOffset', action='store', type='inkbool', dest='useToolOffset', default='TRUE', help='Correct tool offset') - self.OptionParser.add_option('--toolOffset', action='store', type='float', dest='toolOffset', default=0.25, help='Tool offset (mm)') - self.OptionParser.add_option('--toolOffsetReturn', action='store', type='float', dest='toolOffsetReturn', default=2.5, help='Return factor') - self.OptionParser.add_option('--precut', action='store', type='inkbool', dest='precut', default='TRUE', help='Use precut') - self.OptionParser.add_option('--offsetX', action='store', type='float', dest='offsetX', default=0.0, help='X offset (mm)') - self.OptionParser.add_option('--offsetY', action='store', type='float', dest='offsetY', default=0.0, help='Y offset (mm)') - self.OptionParser.add_option('--serialPort', action='store', type='string', dest='serialPort', default='COM1', help='Serial port') - self.OptionParser.add_option('--serialBaudRate', action='store', type='string', dest='serialBaudRate', default='9600', help='Serial Baud rate') + self.OptionParser.add_option('--tab', action='store', type='string', dest='tab') + self.OptionParser.add_option('--resolutionX', action='store', type='float', dest='resolutionX', default=1016.0, help='Resolution X (dpi)') + self.OptionParser.add_option('--resolutionY', action='store', type='float', dest='resolutionY', default=1016.0, help='Resolution Y (dpi)') + self.OptionParser.add_option('--pen', action='store', type='int', dest='pen', default=1, help='Pen number') + self.OptionParser.add_option('--orientation', action='store', type='string', dest='orientation', default='90', help='orientation') + self.OptionParser.add_option('--mirrorX', action='store', type='inkbool', dest='mirrorX', default='FALSE', help='Mirror X-axis') + self.OptionParser.add_option('--mirrorY', action='store', type='inkbool', dest='mirrorY', default='FALSE', help='Mirror Y-axis') + self.OptionParser.add_option('--center', action='store', type='inkbool', dest='center', default='FALSE', help='Center zero point') + self.OptionParser.add_option('--flat', action='store', type='float', dest='flat', default=1.2, help='Curve flatness') + self.OptionParser.add_option('--useOvercut', action='store', type='inkbool', dest='useOvercut', default='TRUE', help='Use overcut') + self.OptionParser.add_option('--overcut', action='store', type='float', dest='overcut', default=1.0, help='Overcut (mm)') + self.OptionParser.add_option('--useToolOffset', action='store', type='inkbool', dest='useToolOffset', default='TRUE', help='Correct tool offset') + self.OptionParser.add_option('--toolOffset', action='store', type='float', dest='toolOffset', default=0.25, help='Tool offset (mm)') + self.OptionParser.add_option('--precut', action='store', type='inkbool', dest='precut', default='TRUE', help='Use precut') + self.OptionParser.add_option('--offsetX', action='store', type='float', dest='offsetX', default=0.0, help='X offset (mm)') + self.OptionParser.add_option('--offsetY', action='store', type='float', dest='offsetY', default=0.0, help='Y offset (mm)') + self.OptionParser.add_option('--serialPort', action='store', type='string', dest='serialPort', default='COM1', help='Serial port') + self.OptionParser.add_option('--serialBaudRate', action='store', type='string', dest='serialBaudRate', default='9600', help='Serial Baud rate') + self.OptionParser.add_option('--flowControl', action='store', type='string', dest='flowControl', default='0', help='Flow control') def effect(self): # gracefully exit script when pySerial is missing @@ -55,10 +55,10 @@ class MyEffect(inkex.Effect): except ImportError, e: inkex.errormsg(_("pySerial is not installed." + "\n\n1. Download pySerial here: http://pypi.python.org/pypi/pyserial" - + "\n2. Extract the \"serial\" subfolder from the zip to the following folder: \"C:\\Program Files (x86)\\inkscape\\python\\Lib\\\" (Or wherever your Inkscape is installed to)" + + "\n2. Extract the \"serial\" subfolder from the zip to the following folder: \"\\inkscape\\python\\Lib\\\" (Or wherever your Inkscape is installed to)" + "\n3. Restart Inkscape.")) return - # TODO:2013-07-13:Sebastian Wüst:Maybe implement DMPL? + # TODO:2013-10-01:Sebastian Wüst:Maybe implement DMPL? # get hpgl data myHpglEncoder = hpgl_encoder.hpglEncoder(self.document.getroot(), self.options) try: @@ -91,9 +91,13 @@ class MyEffect(inkex.Effect): raise ValueError, ("", type, value), traceback ''' # send data to plotter - # TODO:2013-07-13:Sebastian Wüst:Slow down sending to prevent buffer overflow (Somewhat esotherical) - # TODO:2013-10-09:Sebastian Wüst: add rtscts=1 ? - mySerial = serial.Serial(port=self.options.serialPort, baudrate=self.options.serialBaudRate, timeout=0.1, writeTimeout=None) + # TODO:2013-07-13:Sebastian Wüst:Slow down sending to prevent buffer overflow on plotter side (should be investigated first) + if self.options.flowControl == '1': + mySerial = serial.Serial(port=self.options.serialPort, baudrate=self.options.serialBaudRate, timeout=0.1, writeTimeout=None, rtscts=True) + elif self.options.flowControl == '2': + mySerial = serial.Serial(port=self.options.serialPort, baudrate=self.options.serialBaudRate, timeout=0.1, writeTimeout=None, dsrdtr=True) + else: + mySerial = serial.Serial(port=self.options.serialPort, baudrate=self.options.serialBaudRate, timeout=0.1, writeTimeout=None, xonxoff=True) mySerial.write(self.hpgl) # Read back 2 chars to avoid plotter not plotting last command (I have no idea why this is necessary) mySerial.read(2) -- cgit v1.2.3