summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSebastian Wüst <sebi@timewaster.de>2013-11-03 14:59:10 +0000
committerSebastian Wüst <sebi@timewaster.de>2013-11-03 14:59:10 +0000
commitc08b173f997276920c5fe7e7aedcc7c5b4d83d72 (patch)
tree3bc81c0ff519bd759645ce1611b0b2a1579c8822
parentMinimise diff against libcroco-0.6.8 (pass 1) (diff)
parentadded more todos (diff)
downloadinkscape-c08b173f997276920c5fe7e7aedcc7c5b4d83d72.tar.gz
inkscape-c08b173f997276920c5fe7e7aedcc7c5b4d83d72.zip
hpgl export + import and serial plotter driver extension with tool offset correction for drag knife plotters
(bzr r12773)
-rw-r--r--share/extensions/hpgl_decoder.py118
-rw-r--r--share/extensions/hpgl_encoder.py296
-rw-r--r--share/extensions/hpgl_input.inx5
-rw-r--r--share/extensions/hpgl_input.py125
-rw-r--r--share/extensions/hpgl_output.inx75
-rwxr-xr-xshare/extensions/hpgl_output.py301
-rw-r--r--share/extensions/plotter.inx78
-rw-r--r--share/extensions/plotter.py126
8 files changed, 739 insertions, 385 deletions
diff --git a/share/extensions/hpgl_decoder.py b/share/extensions/hpgl_decoder.py
new file mode 100644
index 000000000..870775cb2
--- /dev/null
+++ b/share/extensions/hpgl_decoder.py
@@ -0,0 +1,118 @@
+#!/usr/bin/env python
+# coding=utf-8
+'''
+Copyright (C) 2013 Sebastian Wüst, sebi@timewaster.de
+
+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
+'''
+
+# standard libraries
+from StringIO import StringIO
+import math
+# local library
+import inkex
+
+
+# TODO: Unittests
+class hpglDecoder:
+
+ def __init__(self, hpglString, options):
+ ''' options:
+ "resolutionX":float
+ "resolutionY":float
+ "showMovements":bool
+ '''
+ self.hpglString = hpglString
+ self.options = options
+ self.scaleX = options.resolutionX / 90.0 # dots/inch to dots/pixels
+ self.scaleY = options.resolutionY / 90.0 # dots/inch to dots/pixels
+ self.warnings = []
+
+ def getSvg(self):
+ # parse hpgl data
+ # prepare document
+ self.doc = inkex.etree.parse(StringIO('<svg xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" width="%s" height="%s"></svg>' %
+ (self.options.docWidth, self.options.docHeight)))
+ actualLayer = 0
+ self.layers = {}
+ 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
+ hpglData = self.hpglString.split(';')
+ if len(hpglData) < 3:
+ raise Exception('NO_HPGL_DATA')
+ oldCoordinates = (0.0, self.options.docHeight)
+ path = ''
+ for i, command in enumerate(hpglData):
+ if command.strip() != '':
+ if command[:2] == 'IN':
+ # if Initialize command, ignore
+ pass
+ elif command[:2] == 'SP':
+ # if Select Pen command
+ actualLayer = command[2:]
+ self.createLayer(actualLayer)
+ elif command[:2] == 'PU':
+ # if Pen Up command
+ if ' L ' in path:
+ self.addPathToLayer(path, actualLayer)
+ if self.options.showMovements and i != len(hpglData) - 1:
+ path = 'M %f,%f' % oldCoordinates
+ path += ' L %f,%f' % self.getParameters(command[2:])
+ self.addPathToLayer(path, 0)
+ path = 'M %f,%f' % self.getParameters(command[2:])
+ oldCoordinates = self.getParameters(command[2:])
+ elif command[:2] == 'PD':
+ # if Pen Down command
+ 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:
+ self.addPathToLayer(path, actualLayer)
+ return (self.doc, self.warnings)
+
+ def createLayer(self, layerNumber):
+ self.layers[layerNumber] = inkex.etree.SubElement(self.doc.getroot(), 'g',
+ {inkex.addNS('groupmode', 'inkscape'): 'layer', inkex.addNS('label', 'inkscape'): 'Drawing Pen ' + layerNumber})
+
+ def addPathToLayer(self, path, layerNumber):
+ lineColor = '000000'
+ if layerNumber == 0:
+ lineColor = 'ff0000'
+ 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 []
+ # remove command delimiter
+ parameterString = parameterString.replace(';', '').strip()
+ # split parameter
+ parameter = parameterString.split(',')
+ # convert to svg coordinate system and return
+ return (float(parameter[0]) / self.scaleX, self.options.docHeight - float(parameter[1]) / self.scaleY)
+
+# 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
new file mode 100644
index 000000000..90d2734be
--- /dev/null
+++ b/share/extensions/hpgl_encoder.py
@@ -0,0 +1,296 @@
+#!/usr/bin/env python
+# coding=utf-8
+'''
+Copyright (C) 2008 Aaron Spike, aaron@ekips.org
+Copyright (C) 2013 Sebastian Wüst, sebi@timewaster.de
+
+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
+'''
+
+# standard libraries
+import math
+import string
+# local libraries
+import bezmisc
+import cspsubdiv
+import cubicsuperpath
+import inkex
+import simplestyle
+import simpletransform
+
+
+# TODO: Unittests
+class hpglEncoder:
+ PI = math.pi
+ TWO_PI = PI * 2
+
+ def __init__(self, effect):
+ ''' options:
+ "resolutionX":float
+ "resolutionY":float
+ "pen":int
+ "orientation":string // "0", "90", "-90", "180"
+ "mirrorX":bool
+ "mirrorY":bool
+ "center":bool
+ "flat":float
+ "useOvercut":bool
+ "overcut":float
+ "useToolOffset":bool
+ "toolOffset":float
+ "precut":bool
+ "offsetX":float
+ "offsetY":float
+ '''
+ self.doc = effect.document.getroot()
+ self.options = effect.options
+ self.divergenceX = 'False'
+ self.divergenceY = 'False'
+ 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)
+ self.options.offsetY = self.options.offsetY * 3.5433070866 * self.scaleY # mm to dots
+ 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 attribute to correct page scaling
+ viewBox = self.doc.get('viewBox')
+ self.viewBoxTransformX = 1
+ self.viewBoxTransformY = 1
+ if viewBox:
+ viewBox = string.split(viewBox, ' ')
+ if viewBox[2] and viewBox[3]:
+ self.viewBoxTransformX = float(effect.unittouu(self.doc.get('width'))) / float(viewBox[2])
+ self.viewBoxTransformY = float(effect.unittouu(self.doc.get('height'))) / float(viewBox[3])
+
+ def getHpgl(self):
+ # dryRun to find edges
+ self.groupmat = [[[self.mirrorX * self.scaleX * self.viewBoxTransformX, 0.0, 0.0], [0.0, self.mirrorY * self.scaleY * self.viewBoxTransformY, 0.0]]]
+ 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]]
+ self.process_group(self.doc, self.groupmat)
+ if self.divergenceX == 'False' or self.divergenceY == 'False' or self.sizeX == 'False' or self.sizeY == 'False':
+ raise Exception('NO_PATHS')
+ # live run
+ self.dryRun = False
+ if self.options.center:
+ self.divergenceX += (self.sizeX - self.divergenceX) / 2
+ self.divergenceY += (self.sizeY - self.divergenceY) / 2
+ elif self.options.useToolOffset:
+ self.options.offsetX += self.options.toolOffset
+ self.options.offsetY += self.options.toolOffset
+ self.groupmat = [[[self.mirrorX * self.scaleX * self.viewBoxTransformX, 0.0, - self.divergenceX + self.options.offsetX],
+ [0.0, self.mirrorY * self.scaleY * self.viewBoxTransformY, - self.divergenceY + self.options.offsetY]]]
+ 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
+ # add precut
+ if self.options.useToolOffset and self.options.precut:
+ self.calcOffset('PU', 0, 0)
+ self.calcOffset('PD', 0, self.options.toolOffset * 8)
+ # start conversion
+ self.process_group(self.doc, self.groupmat)
+ # 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;'
+ return self.hpgl
+
+ def process_group(self, group, groupmat):
+ # process groups
+ style = group.get('style')
+ if style:
+ style = simplestyle.parseStyle(style)
+ if style.has_key('display'):
+ if style['display'] == 'none':
+ return
+ trans = group.get('transform')
+ if trans:
+ groupmat.append(simpletransform.composeTransform(groupmat[-1], simpletransform.parseTransform(trans)))
+ for node in group:
+ if node.tag == inkex.addNS('path', 'svg'):
+ self.process_path(node, groupmat[-1])
+ if node.tag == inkex.addNS('g', 'svg'):
+ # TODO: Remove recursion
+ self.process_group(node, groupmat)
+ if trans:
+ groupmat.pop()
+
+ def process_path(self, node, mat):
+ # process path
+ drawing = node.get('d')
+ if drawing:
+ # transform path
+ paths = cubicsuperpath.parsePath(drawing)
+ trans = node.get('transform')
+ if trans:
+ mat = simpletransform.composeTransform(mat, simpletransform.parseTransform(trans))
+ simpletransform.applyTransformToPath(mat, paths)
+ cspsubdiv.cspsubdiv(paths, self.options.flat)
+ # path to HPGL commands
+ oldPosX = ''
+ oldPosY = ''
+ # TODO: Plot smallest parts first to avid plotter dragging parts of foil around (on text)
+ for singlePath in paths:
+ cmd = 'PU'
+ for singlePathPoint in singlePath:
+ posX, posY = singlePathPoint[1]
+ # check if point is repeating, if so, ignore
+ if posX != oldPosX or posY != oldPosY:
+ self.calcOffset(cmd, posX, posY)
+ cmd = 'PD'
+ oldPosX = posX
+ oldPosY = posY
+ # perform overcut
+ # TODO: Find that evasive bug that produces an extra point between the end of the path and the 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(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]
+ # check if point is repeating, if so, ignore
+ if posX != oldPosX or posY != oldPosY:
+ overcutLength += self.getLength(oldPosX, oldPosY, posX, posY)
+ if overcutLength >= self.options.overcut:
+ newLength = self.changeLength(oldPosX, oldPosY, posX, posY, - (overcutLength - self.options.overcut))
+ self.calcOffset(cmd, newLength[0], newLength[1])
+ break
+ else:
+ self.calcOffset(cmd, posX, posY)
+ oldPosX = posX
+ oldPosY = posY
+
+ def getLength(self, x1, y1, x2, y2, absolute=True):
+ # calc absoulute or relative length between two points
+ length = math.sqrt((x2 - x1) ** 2.0 + (y2 - y1) ** 2.0)
+ if absolute:
+ length = math.fabs(length)
+ return length
+
+ def changeLength(self, x1, y1, x2, y2, offset):
+ # change length of line
+ if offset < 0:
+ offset = max( - self.getLength(x1, y1, x2, y2), offset)
+ x = x2 + (x2 - x1) / self.getLength(x1, y1, x2, y2, False) * offset
+ y = y2 + (y2 - y1) / self.getLength(x1, y1, x2, y2, False) * offset
+ return [x, y]
+
+ def getAlpha(self, x1, y1, x2, y2, x3, y3):
+ # get alpha of point 2
+ 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)
+ return math.acos(max(min(temp1 / temp2, 1.0), -1.0))
+
+ def calcOffset(self, cmd, posX, posY):
+ # calculate offset correction (or dont)
+ if not self.options.useToolOffset or self.dryRun:
+ self.storePoint(cmd, posX, posY)
+ else:
+ # insert data into cache
+ self.vData.pop(0)
+ self.vData.insert(3, [cmd, posX, posY])
+ # decide if enough data is availabe
+ if self.vData[2][1] != -1.0:
+ if self.vData[1][1] == -1.0:
+ self.storePoint(self.vData[2][0], self.vData[2][1], self.vData[2][2])
+ else:
+ # perform tool offset correction (It's a *tad* complicated, if you want to understand it draw the data as lines on paper)
+ if self.vData[2][0] == 'PD': # If the 3rd entry in the cache is a pen down command make the line longer by the tool offset
+ pointThree = self.changeLength(self.vData[1][1], self.vData[1][2], self.vData[2][1], self.vData[2][2], self.options.toolOffset)
+ self.storePoint('PD', pointThree[0], pointThree[1])
+ elif self.vData[0][1] != -1.0:
+ # Elif the 1st entry in the cache is filled with data and the 3rd entry is a pen up command shift
+ # the 3rd entry by the current tool offset position according to the 2nd command
+ pointThree = self.changeLength(self.vData[0][1], self.vData[0][2], self.vData[1][1], self.vData[1][2], self.options.toolOffset)
+ pointThree[0] = self.vData[2][1] - (self.vData[1][1] - pointThree[0])
+ pointThree[1] = self.vData[2][2] - (self.vData[1][2] - pointThree[1])
+ self.storePoint('PU', pointThree[0], pointThree[1])
+ else:
+ # Else just write the 3rd entry
+ pointThree = [self.vData[2][1], self.vData[2][2]]
+ self.storePoint('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 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])))
+ # get angle start and angle vector
+ angleStart = math.atan2(pointThree[1] - self.vData[2][2], pointThree[0] - self.vData[2][1])
+ angleVector = math.atan2(pointFour[1] - self.vData[2][2], pointFour[0] - self.vData[2][1]) - angleStart
+ # switch direction when arc is bigger than 180°
+ if angleVector > self.PI:
+ angleVector -= self.TWO_PI
+ elif angleVector < - self.PI:
+ angleVector += self.TWO_PI
+ # draw arc
+ if angleVector >= 0:
+ angle = angleStart + self.toolOffsetFlat
+ while angle < angleStart + angleVector:
+ self.storePoint('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 + angleVector:
+ self.storePoint('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.storePoint('PD', pointFour[0], pointFour[1])
+
+ def storePoint(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
+ if self.divergenceY == 'False' or y < self.divergenceY:
+ self.divergenceY = y
+ if self.sizeX == 'False' or x > self.sizeX:
+ self.sizeX = x
+ if self.sizeY == 'False' or y > self.sizeY:
+ self.sizeY = y
+ else:
+ # store point
+ if not self.options.center:
+ # only positive values are allowed (usually)
+ if x < 0:
+ x = 0
+ if y < 0:
+ y = 0
+ # do not repeat command
+ if command == 'PD' and self.lastPoint[0] == 'PD':
+ self.hpgl += ',%d,%d' % (x, y)
+ else:
+ self.hpgl += ';%s%d,%d' % (command, x, y)
+ self.lastPoint = [command, x, 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 0d8cad158..69711d85a 100644
--- a/share/extensions/hpgl_input.inx
+++ b/share/extensions/hpgl_input.inx
@@ -3,10 +3,13 @@
<_name>HPGL Input</_name>
<id>org.inkscape.input.hpgl</id>
<dependency type="executable" location="extensions">hpgl_input.py</dependency>
+ <dependency type="executable" location="extensions">hpgl_decoder.py</dependency>
<dependency type="executable" location="extensions">inkex.py</dependency>
+ <_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.</_param>
+ <param name="space" type="description">&#xa0;</param>
<param name="resolutionX" type="float" min="1.0" max="4096.0" precision="1" _gui-text="Resolution X (dpi)" _gui-description="The amount of steps in one inch on the X axis (Default: 1016.0)">1016.0</param>
<param name="resolutionY" type="float" min="1.0" max="4096.0" precision="1" _gui-text="Resolution Y (dpi)" _gui-description="The amount of steps in one inch on the Y axis (Default: 1016.0)">1016.0</param>
- <param name="showMovements" type="boolean" _gui-text="Show Movements between paths" _gui-description="Show movements between paths in a different color (Default: Un-checked)">false</param>
+ <param name="showMovements" type="boolean" _gui-text="Show Movements between paths" _gui-description="Check this to show movements between paths (Default: Unchecked)">false</param>
<input>
<extension>.hpgl</extension>
<mimetype>image/hpgl</mimetype>
diff --git a/share/extensions/hpgl_input.py b/share/extensions/hpgl_input.py
index d20b344f5..d1d46c76f 100644
--- a/share/extensions/hpgl_input.py
+++ b/share/extensions/hpgl_input.py
@@ -1,9 +1,7 @@
#!/usr/bin/env python
# coding=utf-8
'''
-hpgl_input.py - input a HP Graphics Language file
-
-Copyright (C) 2013 Sebastian Wüst, sebi@timewaster.de, http://www.timewasters-place.com/
+Copyright (C) 2013 Sebastian Wüst, sebi@timewaster.de
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
@@ -20,94 +18,53 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
'''
-# standard library
+# standard libraries
+import sys
from StringIO import StringIO
-# local library
+# local libraries
+import hpgl_decoder
import inkex
-
+import sys
inkex.localize()
+
+# TODO: Unittests
# parse options
-parser = inkex.optparse.OptionParser(usage="usage: %prog [options] HPGLfile", option_class=inkex.InkOption)
-parser.add_option("-a", "--resolutionX",
- action="store", type="float",
- dest="resolutionX", default=1016.0,
- help="Resolution X (dpi)")
-parser.add_option("-b", "--resolutionY",
- action="store", type="float",
- dest="resolutionY", default=1016.0,
- help="Resolution Y (dpi)")
-parser.add_option("-c", "--showMovements",
- action="store", type="inkbool",
- dest="showMovements", default="FALSE",
- help="Show Movements between paths")
+parser = inkex.optparse.OptionParser(usage='usage: %prog [options] HPGLfile', option_class=inkex.InkOption)
+parser.add_option('--resolutionX', action='store', type='float', dest='resolutionX', default=1016.0, help='Resolution X (dpi)')
+parser.add_option('--resolutionY', action='store', type='float', dest='resolutionY', default=1016.0, help='Resolution Y (dpi)')
+parser.add_option('--showMovements', action='store', type='inkbool', dest='showMovements', default='FALSE', help='Show Movements between paths')
(options, args) = parser.parse_args(inkex.sys.argv[1:])
-# global vars
-scaleX = options.resolutionX / 90.0 # dots/inch to dots/pixels
-scaleY = options.resolutionY / 90.0 # dots/inch to dots/pixels
-documentWidth = 210.0 * 3.5433070866 # 210mm to pixels
-documentHeight = 297.0 * 3.5433070866 # 297mm to pixels
-hasUnknownCommands = False
-
-def getData(file):
- # read file (read only one line, there should not be more than one line)
- stream = open(file, 'r')
- data = stream.readline().strip()
- data = data.split(';')
- if len(data) < 2:
- inkex.errormsg(_("No HPGL data found."))
- return False
- if data[-1].strip() == '':
- data.pop()
- return data
+# needed to initialize the document
+options.docWidth = 210.0 * 3.5433070866 # 210mm to pixels (DIN A4)
+options.docHeight = 297.0 * 3.5433070866 # 297mm to pixels (DIN A4)
-def getCoordinates(coord):
- # process coordinates
- (x, y) = coord.split(',')
- x = float(x) / scaleX; # convert to pixels coordinate system
- y = documentHeight - float(y) / scaleY; # convert to pixels coordinate system
- return (x, y)
+# read file
+fobj = open(args[0], 'r')
+hpglString = []
+for line in fobj:
+ hpglString.append(line.strip())
+fobj.close()
+# combine all lines
+hpglString = ';'.join(hpglString)
-# prepare document
-doc = inkex.etree.parse(StringIO('<svg xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" width="%s" height="%s"></svg>' % (documentWidth, documentHeight)))
-layerDrawing = inkex.etree.SubElement(doc.getroot(), 'g', {inkex.addNS('groupmode','inkscape'):'layer', inkex.addNS('label','inkscape'):'Drawing'})
-if options.showMovements:
- layerMovements = inkex.etree.SubElement(doc.getroot(), 'g', {inkex.addNS('groupmode','inkscape'):'layer', inkex.addNS('label','inkscape'):'Movements'})
-
-# load data
-data = getData(args[0])
-if data != False:
- # parse paths
- oldCoordinates = (0.0, documentHeight)
- path = ''
- for i, command in enumerate(data):
- if command.strip() != '':
- if command[:2] == 'PU': # if Pen Up command
- if " L" in path:
- inkex.etree.SubElement(layerDrawing, 'path', {'d':path, 'style':'stroke:#000000; stroke-width:0.2; fill:none;'})
- if options.showMovements and i != len(data) - 1:
- path = 'M %f,%f' % oldCoordinates
- path += ' L %f,%f' % getCoordinates(command[2:])
- inkex.etree.SubElement(layerMovements, 'path', {'d':path, 'style':'stroke:#ff0000; stroke-width:0.2; fill:none;'})
- path = 'M %f,%f' % getCoordinates(command[2:])
- elif command[:2] == 'PD': # if Pen Down command
- path += ' L %f,%f' % getCoordinates(command[2:])
- oldCoordinates = getCoordinates(command[2:])
- elif command[:2] == 'IN': # if Initialize command
- pass
- elif command[:2] == 'SP': # if Select Pen command
- pass
- else:
- hasUnknownCommands = True
- if " L" in path:
- inkex.etree.SubElement(layerDrawing, 'path', {'d':path, 'style':'stroke:#000000; stroke-width:0.2; fill:none;'})
-
-# deliver document to inkscape
-doc.write(inkex.sys.stdout)
-
-# issue warning if unknown commands where found
-if hasUnknownCommands:
- inkex.errormsg(_("The HPGL data contained unknown (unsupported) commands, there is a possibility that the drawing is missing some content."))
+# interpret HPGL data
+myHpglDecoder = hpgl_decoder.hpglDecoder(hpglString, options)
+try:
+ doc, warnings = myHpglDecoder.getSvg()
+ # issue warning if unknown commands where found
+ if 'UNKNOWN_COMMANDS' in warnings:
+ inkex.errormsg(_("The HPGL data contained unknown (unsupported) commands, there is a possibility that the drawing is missing some content."))
+ # deliver document to inkscape
+ doc.write(inkex.sys.stdout)
+except Exception as inst:
+ if inst.args[0] == 'NO_HPGL_DATA':
+ # issue error if no hpgl data found
+ inkex.errormsg(_("No HPGL data found."))
+ print 1
+ else:
+ type, value, traceback = sys.exc_info()
+ raise ValueError, ("", type, value), traceback
-# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99
+# 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_output.inx b/share/extensions/hpgl_output.inx
index 2ce284200..7f8705996 100644
--- a/share/extensions/hpgl_output.inx
+++ b/share/extensions/hpgl_output.inx
@@ -4,49 +4,39 @@
<id>org.ekips.output.hpgl</id>
<dependency type="extension">org.inkscape.output.svg.inkscape</dependency>
<dependency type="executable" location="extensions">hpgl_output.py</dependency>
+ <dependency type="executable" location="extensions">hpgl_encoder.py</dependency>
<dependency type="executable" location="extensions">inkex.py</dependency>
- <_param name="introduction" type="description">Please make sure that all objects you want to plot are converted to paths. The plot will automatically be aligned to the zero point.</_param>
- <param name="horizontal_guide" type="description">—————————————————————————————</param>
- <param name="resolution" type="int" min="90" max="2048" _gui-text="Resolution (dpi):" _gui-description="The amount of steps the cutter moves if it moves for 1 inch, either get this value from your plotter manual or learn it by trial and error (Standard: '1016')">1016</param>
- <param name="pen" type="int" min="1" max="10" _gui-text="Pen number:" _gui-description="The number of the pen (tool) to use, on most plotters 1 (Standard: '1')">1</param>
- <param name="angle" type="optiongroup" appearance="minimal" _gui-text="Orientation:" _gui-description="Orientation of the plot, change this if your plotter is plotting horizontal instead of vertical (Standard: '90°')">
- <option value="90">90°</option>
- <option value="-90">-90°</option>
- <option value="0">0°</option>
- <option value="180">180°</option>
- </param>
- <param name="mirror" type="boolean" _gui-text="Mirror Y-axis" _gui-description="Whether to mirror the Y axis. Some plotters need this, some not. Look in your plotter manual or learn it by trial and error (Standard: 'False')">false</param>
- <param name="center" type="boolean" _gui-text="Center Zero Point" _gui-description="Whether the plotter needs the zero point to be in the center of the drawing. Some plotters need this, some not. Look in your plotter manual or learn it by trial and error (Standard: 'False')">false</param>
- <param name="flat" type="float" min="0.1" max="10.0" precision="1" _gui-text="Curve flatness:" _gui-description="Curves are divided into lines, this number controls how fine the curves will be reproduced, the smaller the finer (Standard: '1.2')">1.2</param>
- <param name="horizontal_guide" type="description">—————————————————————————————</param>
- <param name="useOvercut" type="boolean" _gui-text="Use Overcut" _gui-description="Whether the overcut will be used, if not the 'Overcut' parameter is unused (Standard: 'True')">true</param>
- <param name="overcut" type="float" min="0.0" max="100.0" precision="2" _gui-text="Overcut (mm):" _gui-description="The distance in mm that will be cut over the starting point of the path to prevent open paths (Standard: '1.00')">1.00</param>
- <param name="horizontal_guide" type="description">—————————————————————————————</param>
- <param name="correctToolOffset" type="boolean" _gui-text="Correct tool offset" _gui-description="Whether the tool offset should be corrected, if not the 'Tool offset' and 'Return Factor' parameters are unused (Standard: 'True')">true</param>
- <param name="toolOffset" type="float" min="0.0" max="100.0" precision="2" _gui-text="Tool offset (mm):" _gui-description="The offset from the tool tip to the tool axis in mm (Standard: '0.25')">0.25</param>
- <param name="toolOffsetReturn" type="float" min="1.0" max="10.0" precision="2" _gui-text="Return Factor:" _gui-description="The return factor multiplied by the tool offset is the length that is used to guide the tool back to the original path after an overcut is performed, you can only determine this value by experimentation (Standard: '2.50')">2.50</param>
- <param name="horizontal_guide" type="description">—————————————————————————————</param>
- <param name="xOffset" type="float" min="0.0" max="10000.0" precision="2" _gui-text="X offset (mm):" _gui-description="The offset to move your plot away from the zero point in mm (Standard: '0.00')">0.00</param>
- <param name="yOffset" type="float" min="0.0" max="10000.0" precision="2" _gui-text="Y offset (mm):" _gui-description="The offset to move your plot away from the zero point in mm (Standard: '0.00')">0.00</param>
- <param name="plotInvisibleLayers" type="boolean" _gui-text="Plot invisible layers" _gui-description="Plot invisible layers (Standard: 'False')">false</param>
- <param name="horizontal_guide" type="description">—————————————————————————————</param>
- <param name="sendToPlotter" type="boolean" _gui-text="Send to Plotter also" _gui-description="Sends the generated HPGL data also via serial connection to your plotter (Standard: 'False')">false</param>
- <param name="port" type="string" _gui-text="Serial Port:" _gui-description="The port of your serial connection, on Windows something like 'COM1', on Linux something like: '/dev/ttyUSB0' (Standard: 'COM1')">COM1</param>
- <param name="baudRate" type="optiongroup" appearance="minimal" _gui-text="Baud Rate:" _gui-description="The Baud rate of your serial connection (Standard: '9600')">
- <option value="110">110</option>
- <option value="300">300</option>
- <option value="600">600</option>
- <option value="1200">1200</option>
- <option value="2400">2400</option>
- <option value="4800">4800</option>
- <option value="9600">9600</option>
- <option value="14400">14400</option>
- <option value="19200">19200</option>
- <option value="28800">28800</option>
- <option value="38400">38400</option>
- <option value="56000">56000</option>
- <option value="57600">57600</option>
- <option value="115200">115200</option>
+ <_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 (Extensions menu) to plot directly on a plotter.</_param>
+ <param name="tab" type="notebook">
+ <page name="plotter" _gui-text="Plotter Settings">
+ <param name="pen" type="int" min="0" max="10" _gui-text="Pen number" _gui-description="The number of the pen (tool) to use, on most plotters 1 (Standard: '1')">1</param>
+ <param name="resolutionX" type="float" min="1.0" max="4096.0" precision="1" _gui-text="Resolution X (dpi)" _gui-description="The amount of steps the cutter moves if it moves for 1 inch on the X axis - Try different settings to find the one that fits your plotter (Default: 1016.0)">1016.0</param>
+ <param name="resolutionY" type="float" min="1.0" max="4096.0" precision="1" _gui-text="Resolution Y (dpi)" _gui-description="The amount of steps the cutter moves if it moves for 1 inch on the Y axis - Try different settings to find the one that fits your plotter (Default: 1016.0)">1016.0</param>
+ <param name="mirrorX" type="boolean" _gui-text="Mirror X-axis" _gui-description="Check this to mirror the X axis - Try different settings to find the one that fits your plotter (Default: Unchecked)">false</param>
+ <param name="mirrorY" type="boolean" _gui-text="Mirror Y-axis" _gui-description="Check this to mirror the Y axis - Try different settings to find the one that fits your plotter (Default: Unchecked)">false</param>
+ <param name="orientation" type="optiongroup" appearance="minimal" _gui-text="Rotation (Clockwise)" _gui-description="Rotation of the drawing - Try different settings to find the one that fits your plotter (Default: 0°)">
+ <option value="0">0°</option>
+ <option value="90">90°</option>
+ <option value="180">180°</option>
+ <option value="270">270°</option>
+ </param>
+ <param name="center" type="boolean" _gui-text="Center zero point" _gui-description="Check this if your plotter uses a centered zero point - Try different settings to find the one that fits your plotter (Default: Unchecked)">false</param>
+ </page>
+ <page name="overcutToolOffset" _gui-text="Overcut &#x26; Tool Offset">
+ <param name="useOvercut" type="boolean" _gui-text="Use overcut" _gui-description="Check this to use the overcut, if not checked the 'Overcut' parameter is unused (Default: Checked)">true</param>
+ <param name="overcut" type="float" min="0.0" max="100.0" precision="2" _gui-text="Overcut (mm)" _gui-description="The distance in mm that will be cut over the starting point of the path to prevent open paths (Default: 1.00)">1.00</param>
+ <param name="space" type="description">&#xa0;</param>
+ <param name="useToolOffset" type="boolean" _gui-text="Use tool offset correction" _gui-description="Check this to use the tool offset correction, if not checked the 'Tool offset' and 'Precut' parameters are unused (Default: Checked)">true</param>
+ <param name="toolOffset" type="float" min="0.0" max="20.0" precision="2" _gui-text="Tool offset (mm)" _gui-description="The offset from the tool tip to the tool axis in mm (Default: 0.25)">0.25</param>
+ <param name="precut" type="boolean" _gui-text="Use precut" _gui-description="Check this to cut a small line before the real drawing to align the tool orientation for the first cut (Default: Checked)">true</param>
+ <param name="space" type="description">&#xa0;</param>
+ <_param name="offsetNote" type="description">Please note that using the tool offset correction will move your plot away from the zero point by the length in mm specified by the tool offset.</_param>
+ </page>
+ <page name="misc" _gui-text="Miscellaneous">
+ <param name="flat" type="float" min="0.1" max="10.0" precision="1" _gui-text="Curve flatness" _gui-description="Curves are divided into lines, this number controls how fine the curves will be reproduced, the smaller the finer (Default: '1.2')">1.2</param>
+ <param name="offsetX" type="float" min="-10000.0" max="10000.0" precision="2" _gui-text="X offset (mm)" _gui-description="The offset to shift your plot away from the zero point in mm (Default: '0.00')">0.00</param>
+ <param name="offsetY" type="float" min="-10000.0" max="10000.0" precision="2" _gui-text="Y offset (mm)" _gui-description="The offset to shift your plot away from the zero point in mm (Default: '0.00')">0.00</param>
+ </page>
</param>
<output>
<extension>.hpgl</extension>
@@ -57,6 +47,5 @@
</output>
<script>
<command reldir="extensions" interpreter="python">hpgl_output.py</command>
- <helper_extension>org.inkscape.output.svg.inkscape</helper_extension>
</script>
</inkscape-extension>
diff --git a/share/extensions/hpgl_output.py b/share/extensions/hpgl_output.py
index eb3d6ffe3..c5fec0ec8 100755
--- a/share/extensions/hpgl_output.py
+++ b/share/extensions/hpgl_output.py
@@ -1,8 +1,7 @@
-#!/usr/bin/env python
+#!/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/
+Copyright (C) 2013 Sebastian Wüst, sebi@timewaster.de
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
@@ -18,262 +17,50 @@ 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("-v", "--center",
- action="store", type="inkbool",
- dest="center", default="FALSE",
- help="Center Zero Point")
- 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 = 9999999999999.0
- self.yDivergence = 9999999999999.0
- self.xSize = -9999999999999.0
- self.ySize = -9999999999999.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(self.unittouu(doc.get('width'))) / float(viewBox[2])
- viewBoxTransformY = float(self.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)
- if self.options.center:
- self.xDivergence += (self.xSize - self.xDivergence) / 2
- self.yDivergence += (self.ySize - self.yDivergence) / 2
- # 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
+# standard library
+import sys
+# local libraries
+import hpgl_encoder
+import inkex
+inkex.localize()
- 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
- if x > self.xSize: self.xSize = x
- if y > self.ySize: self.ySize = y
- else:
- if not self.options.center:
- 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))
+# TODO: Unittests
+class MyEffect(inkex.Effect):
- 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 __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('--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)')
- 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 effect(self):
+ # get hpgl data
+ myHpglEncoder = hpgl_encoder.hpglEncoder(self)
+ try:
+ self.hpgl = myHpglEncoder.getHpgl()
+ except Exception as inst:
+ if inst.args[0] == 'NO_PATHS':
+ # issue error if no paths found
+ inkex.errormsg(_("No paths where found. Please convert all objects you want to save into paths."))
+ self.hpgl = 1
+ else:
+ type, value, traceback = sys.exc_info()
+ raise ValueError, ("", type, value), traceback
def output(self):
# print to file
@@ -281,9 +68,9 @@ class MyEffect(inkex.Effect):
if __name__ == '__main__':
# Raise recursion limit to avoid exceptions on big documents
- sys.setrecursionlimit(20000);
- # start calculations
+ sys.setrecursionlimit(20000)
+ # start extension
e = MyEffect()
e.affect()
-# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99
+# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99 \ No newline at end of file
diff --git a/share/extensions/plotter.inx b/share/extensions/plotter.inx
new file mode 100644
index 000000000..b950e0246
--- /dev/null
+++ b/share/extensions/plotter.inx
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
+ <_name>Plot</_name>
+ <id>org.ekips.filter.plot</id>
+ <dependency type="executable" location="extensions">plotter.py</dependency>
+ <dependency type="executable" location="extensions">hpgl_decoder.py</dependency>
+ <dependency type="executable" location="extensions">hpgl_encoder.py</dependency>
+ <dependency type="executable" location="extensions">inkex.py</dependency>
+ <_param name="introduction" type="description">Please make sure that all objects you want to plot are converted to paths. The plot will automatically be aligned to the zero point.</_param>
+ <param name="tab" type="notebook">
+ <page name="misc" _gui-text="Connection">
+ <param name="serialPort" type="string" _gui-text="Serial Port" _gui-description="The port of your serial connection, on Windows something like 'COM1', on Linux something like: '/dev/ttyUSB0' (Default: COM1)">COM1</param>
+ <param name="serialBaudRate" type="optiongroup" appearance="minimal" _gui-text="Serial Baud rate" _gui-description="The Baud rate of your serial connection (Default: 9600)">
+ <option value="110">110</option>
+ <option value="300">300</option>
+ <option value="600">600</option>
+ <option value="1200">1200</option>
+ <option value="2400">2400</option>
+ <option value="4800">4800</option>
+ <option value="9600">9600</option>
+ <option value="14400">14400</option>
+ <option value="19200">19200</option>
+ <option value="28800">28800</option>
+ <option value="38400">38400</option>
+ <option value="56000">56000</option>
+ <option value="57600">57600</option>
+ <option value="115200">115200</option>
+ </param>
+ <param name="flowControl" type="optiongroup" appearance="minimal" _gui-text="Flow control" _gui-description="Software / Hardware flow control - Try different settings to find the one that fits your plotter (Default: None)">
+ <option value="0">None</option>
+ <option value="1">Software (XON/XOFF)</option>
+ <option value="2">Hardware (RTS/CTS)</option>
+ </param>
+ <param name="space" type="description">&#xa0;</param>
+ <_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>
+ <_param name="parallelHelp" type="description">Please note that Parallel (LPT) connections are not supported.</_param>
+ <_param name="hpglNote" type="description">Please note that only the HPGL command language is supported at the moment.</_param>
+ </page>
+ <page name="plotter" _gui-text="Plotter Settings">
+ <param name="pen" type="int" min="0" max="10" _gui-text="Pen number" _gui-description="The number of the pen (tool) to use, on most plotters 1 (Standard: '1')">1</param>
+ <param name="resolutionX" type="float" min="1.0" max="4096.0" precision="1" _gui-text="Resolution X (dpi)" _gui-description="The amount of steps the cutter moves if it moves for 1 inch on the X axis - Try different settings to find the one that fits your plotter (Default: 1016.0)">1016.0</param>
+ <param name="resolutionY" type="float" min="1.0" max="4096.0" precision="1" _gui-text="Resolution Y (dpi)" _gui-description="The amount of steps the cutter moves if it moves for 1 inch on the Y axis - Try different settings to find the one that fits your plotter (Default: 1016.0)">1016.0</param>
+ <param name="mirrorX" type="boolean" _gui-text="Mirror X-axis" _gui-description="Check this to mirror the X axis (Default: Unchecked)">false</param>
+ <param name="mirrorY" type="boolean" _gui-text="Mirror Y-axis" _gui-description="Check this to mirror the Y axis - Try different settings to find the one that fits your plotter (Default: Unchecked)">false</param>
+ <param name="orientation" type="optiongroup" appearance="minimal" _gui-text="Rotation (Clockwise)" _gui-description="Rotation of the plot - Try different settings to find the one that fits your plotter (Default: 0°)">
+ <option value="0">0°</option>
+ <option value="90">90°</option>
+ <option value="180">180°</option>
+ <option value="270">270°</option>
+ </param>
+ <param name="center" type="boolean" _gui-text="Center zero point" _gui-description="Check this if your plotter uses a centered zero point - Try different settings to find the one that fits your plotter (Default: Unchecked)">false</param>
+ </page>
+ <page name="overcutToolOffset" _gui-text="Overcut &#x26; Tool Offset">
+ <param name="useOvercut" type="boolean" _gui-text="Use overcut" _gui-description="Check this to use the overcut, if not checked the 'Overcut' parameter is unused (Default: Checked)">true</param>
+ <param name="overcut" type="float" min="0.0" max="100.0" precision="2" _gui-text="Overcut (mm)" _gui-description="The distance in mm that will be cut over the starting point of the path to prevent open paths (Default: 1.00)">1.00</param>
+ <param name="space" type="description">&#xa0;</param>
+ <param name="useToolOffset" type="boolean" _gui-text="Use tool offset correction" _gui-description="Check this to use the tool offset correction, if not checked the 'Tool offset' and 'Precut' parameters are unused (Default: Checked)">true</param>
+ <param name="toolOffset" type="float" min="0.0" max="20.0" precision="2" _gui-text="Tool offset (mm)" _gui-description="The offset from the tool tip to the tool axis in mm (Default: 0.25)">0.25</param>
+ <param name="precut" type="boolean" _gui-text="Use precut" _gui-description="Check this to cut a small line before the real drawing to align the tool orientation for the first cut (Default: Checked)">true</param>
+ <param name="space" type="description">&#xa0;</param>
+ <_param name="offsetNote" type="description">Please note that using the tool offset correction will move your plot away from the zero point by the length in mm specified by the tool offset.</_param>
+ </page>
+ <page name="misc" _gui-text="Miscellaneous">
+ <param name="flat" type="float" min="0.1" max="10.0" precision="1" _gui-text="Curve flatness" _gui-description="Curves are divided into lines, this number controls how fine the curves will be reproduced, the smaller the finer (Default: '1.2')">1.2</param>
+ <param name="offsetX" type="float" min="-10000.0" max="10000.0" precision="2" _gui-text="X offset (mm)" _gui-description="The offset to move your plot away from the zero point in mm (Default: '0.00')">0.00</param>
+ <param name="offsetY" type="float" min="-10000.0" max="10000.0" precision="2" _gui-text="Y offset (mm)" _gui-description="The offset to move your plot away from the zero point in mm (Default: '0.00')">0.00</param>
+ </page>
+ </param>
+ <effect needs-live-preview="false">
+ <object-type>path</object-type>
+ <effects-menu>
+ <submenu _name="Plotter"/>
+ </effects-menu>
+ </effect>
+ <script>
+ <command reldir="extensions" interpreter="python">plotter.py</command>
+ </script>
+</inkscape-extension>
diff --git a/share/extensions/plotter.py b/share/extensions/plotter.py
new file mode 100644
index 000000000..aef9e016d
--- /dev/null
+++ b/share/extensions/plotter.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python
+# coding=utf-8
+'''
+Copyright (C) 2013 Sebastian Wüst, sebi@timewaster.de
+
+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
+'''
+
+# standard library
+import sys
+# local libraries
+import gettext
+import hpgl_decoder
+import hpgl_encoder
+import inkex
+inkex.localize()
+
+
+# TODO: Unittests
+# TODO: Material feed after plot, plot rectangles
+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('--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
+ try:
+ import serial
+ except ImportError, e:
+ inkex.errormsg(_("pySerial is not installed."
+ + "\n\n1. Download pySerial here (not the \".exe\"): http://pypi.python.org/pypi/pyserial"
+ + "\n2. Extract the \"serial\" subfolder from the zip to the following folder: C:\\[Program files]\\inkscape\\python\\Lib\\"
+ + "\n3. Restart Inkscape."))
+ return
+ # TODO: Maybe implement DMPL?
+ # get hpgl data
+ myHpglEncoder = hpgl_encoder.hpglEncoder(self)
+ try:
+ self.hpgl = myHpglEncoder.getHpgl()
+ except Exception as inst:
+ if inst.args[0] == 'NO_PATHS':
+ # issue error if no paths found
+ inkex.errormsg(_("No paths where found. Please convert all objects you want to plot into paths."))
+ return 1
+ else:
+ type, value, traceback = sys.exc_info()
+ raise ValueError, ("", type, value), traceback
+ # TODO: Get preview to work. This requires some work on the C++ side to be able to determine if it is
+ # a preview or a final run. (Remember to set <effect needs-live-preview='false'> to true)
+ '''
+ # reparse data for preview
+ self.options.showMovements = True
+ self.options.docWidth = float(inkex.unittouu(self.document.getroot().get('width')))
+ self.options.docHeight = float(inkex.unittouu(self.document.getroot().get('height')))
+ myHpglDecoder = hpgl_decoder.hpglDecoder(self.hpgl, self.options)
+ try:
+ doc, warnings = myHpglDecoder.getSvg()
+ # deliver document to inkscape
+ self.document = doc
+ except Exception as inst:
+ if inst.args[0] == 'NO_HPGL_DATA':
+ # do nothing
+ pass
+ else:
+ type, value, traceback = sys.exc_info()
+ raise ValueError, ("", type, value), traceback
+ '''
+ # send data to plotter
+ if self.options.flowControl == '1':
+ mySerial = serial.Serial(port=self.options.serialPort, baudrate=self.options.serialBaudRate, timeout=0.1, writeTimeout=10, xonxoff=True)
+ elif self.options.flowControl == '2':
+ mySerial = serial.Serial(port=self.options.serialPort, baudrate=self.options.serialBaudRate, timeout=0.1, writeTimeout=10, dsrdtr=None, rtscts=True)
+ else:
+ mySerial = serial.Serial(port=self.options.serialPort, baudrate=self.options.serialBaudRate, timeout=0.1, writeTimeout=10)
+ try:
+ mySerial.write(self.hpgl)
+ except Exception as inst:
+ if inst.args[0] == 'Write timeout':
+ inkex.errormsg(_("Could not send data. Please check that your plotter is running, connected and the settings are correct."))
+ else:
+ type, value, traceback = sys.exc_info()
+ raise ValueError, ("", type, value), traceback
+ # Read back 2 chars to avoid plotter not plotting last command (I have no idea why this is necessary)
+ mySerial.read(2)
+ mySerial.close()
+
+if __name__ == '__main__':
+ # Raise recursion limit to avoid exceptions on big documents
+ sys.setrecursionlimit(20000)
+ # start extension
+ e = MyEffect()
+ e.affect()
+
+# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99 \ No newline at end of file