Skip to content
Snippets Groups Projects
Commit 3a506106 authored by gijs's avatar gijs
Browse files

Started on encoding HPGL

parent 5c87da1c
No related branches found
No related tags found
No related merge requests found
Pipeline #743 canceled
from flask import Flask, request, render_template
from flask import Flask, Response, request, render_template
import subprocess
import os
import re
import sys
import tempfile
import io
from svg_to_hpgl import svgToHPGL
app = Flask(__name__)
......@@ -158,5 +160,30 @@ def catalogue():
output = output,
params = params)
def make_svg ():
return ''
@app.route('/hpgl/')
def hpgl ():
# generate svg
svg = make_svg()
# store as a temporary file
(svg_file, svg_path) = tempfile.mkstemp()
svg_file.write(svg)
# transform to hpgl
hpgl = svgToHPGL(svg_path)
# remove tmp file
os.remove(svg_path)
r = Response(hpgl, mimetype='application/hpgl')
r.headers.extend({
'Content-Disposition': 'attachment; filename="cobbled-paths.hpgl"'
})
return r
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
\ No newline at end of file
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
'''
# standard libraries
import math
import re
import sys
sys.path.append('/usr/share/inkscape/extensions')
# local libraries
import bezmisc
import cspsubdiv
import cubicsuperpath
import inkex
import simplestyle
import simpletransform
class hpglMultipenEncoder:
PI = math.pi
TWO_PI = PI * 2
def __init__(self, effect):
''' options:
"resolutionX":float
"resolutionY":float
"pen":int
"force:int
"speed:int
"orientation":string // "0", "90", "-90", "180"
"mirrorX":bool
"mirrorY":bool
"center":bool
"flat":float
"overcut":float
"toolOffset":float
"precut":bool
"autoAlign":bool
"debug":bool
'''
self.options = effect.options
self.doc = effect.document.getroot()
self.docWidth = effect.unittouu(self.doc.get('width'))
self.docHeight = effect.unittouu(self.doc.get('height'))
self.hpgl = ''
self.divergenceX = 'False'
self.divergenceY = 'False'
self.sizeX = 'False'
self.sizeY = 'False'
self.dryRun = True
self.lastPoint = [0, 0, 0]
self.lastPen = -1
self.offsetX = 0
self.offsetY = 0
self.penIndex = {}
self.penCount = self.options.penCount
self.scaleX = self.options.resolutionX / effect.unittouu("1.0in") # dots per inch to dots per user unit
self.scaleY = self.options.resolutionY / effect.unittouu("1.0in") # dots per inch to dots per user unit
scaleXY = (self.scaleX + self.scaleY) / 2
self.overcut = effect.unittouu(str(self.options.overcut) + "mm") * scaleXY # mm to dots (plotter coordinate system)
self.toolOffset = effect.unittouu(str(self.options.toolOffset) + "mm") * scaleXY # mm to dots
self.flat = self.options.flat / (1016 / ((self.options.resolutionX + self.options.resolutionY) / 2)) # scale flatness to resolution
if self.toolOffset > 0.0:
self.toolOffsetFlat = self.flat / self.toolOffset * 4.5 # scale flatness to offset
else:
self.toolOffsetFlat = 0.0
self.mirrorX = 1.0
if self.options.mirrorX:
self.mirrorX = -1.0
self.mirrorY = -1.0
if self.options.mirrorY:
self.mirrorY = 1.0
if self.options.debug:
self.debugValues = {}
self.debugValues['docWidth'] = self.docWidth
self.debugValues['docHeight'] = self.docHeight
# process viewBox attribute to correct page scaling
self.viewBoxTransformX = 1
self.viewBoxTransformY = 1
if self.options.debug:
self.debugValues['viewBoxWidth'] = "-"
self.debugValues['viewBoxHeight'] = "-"
viewBox = self.doc.get('viewBox')
if viewBox:
viewBox2 = viewBox.split(',')
if len(viewBox2) < 4:
viewBox2 = viewBox.split(' ')
if self.options.debug:
self.debugValues['viewBoxWidth'] = viewBox2[2]
self.debugValues['viewBoxHeight'] = viewBox2[3]
self.viewBoxTransformX = self.docWidth / effect.unittouu(effect.addDocumentUnit(viewBox2[2]))
self.viewBoxTransformY = self.docHeight / effect.unittouu(effect.addDocumentUnit(viewBox2[3]))
def getHpgl(self):
# dryRun to find edges
groupmat = [[self.mirrorX * self.scaleX * self.viewBoxTransformX, 0.0, 0.0], [0.0, self.mirrorY * self.scaleY * self.viewBoxTransformY, 0.0]]
groupmat = simpletransform.composeTransform(groupmat, simpletransform.parseTransform('rotate(' + self.options.orientation + ')'))
self.vData = [['', 'False', 0, 0], ['', 'False', 0, 0], ['', 'False', 0, 0], ['', 'False', 0, 0]]
self.processGroups(self.doc, 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.debug:
self.debugValues['drawingWidth'] = self.sizeX - self.divergenceX
self.debugValues['drawingHeight'] = self.sizeY - self.divergenceY
self.debugValues['drawingWidthUU'] = self.debugValues['drawingWidth'] / self.scaleX
self.debugValues['drawingHeightUU'] = self.debugValues['drawingHeight'] / self.scaleY
# move drawing according to various modifiers
if self.options.autoAlign:
if self.options.center:
self.offsetX -= (self.sizeX - self.divergenceX) / 2
self.offsetY -= (self.sizeY - self.divergenceY) / 2
else:
self.divergenceX = 0.0
self.divergenceY = 0.0
if self.options.center:
if self.options.orientation == '0':
self.offsetX -= (self.docWidth * self.scaleX) / 2
self.offsetY += (self.docHeight * self.scaleY) / 2
if self.options.orientation == '90':
self.offsetY += (self.docWidth * self.scaleX) / 2
self.offsetX += (self.docHeight * self.scaleY) / 2
if self.options.orientation == '180':
self.offsetX += (self.docWidth * self.scaleX) / 2
self.offsetY -= (self.docHeight * self.scaleY) / 2
if self.options.orientation == '270':
self.offsetY -= (self.docWidth * self.scaleX) / 2
self.offsetX -= (self.docHeight * self.scaleY) / 2
else:
if self.options.orientation == '0':
self.offsetY += self.docHeight * self.scaleY
if self.options.orientation == '90':
self.offsetY += self.docWidth * self.scaleX
self.offsetX += self.docHeight * self.scaleY
if self.options.orientation == '180':
self.offsetX += self.docWidth * self.scaleX
if not self.options.center and self.toolOffset > 0.0:
self.offsetX += self.toolOffset
self.offsetY += self.toolOffset
# initialize transformation matrix and cache
groupmat = [[self.mirrorX * self.scaleX * self.viewBoxTransformX, 0.0, -self.divergenceX + self.offsetX],
[0.0, self.mirrorY * self.scaleY * self.viewBoxTransformY, -self.divergenceY + self.offsetY]]
groupmat = simpletransform.composeTransform(groupmat, simpletransform.parseTransform('rotate(' + self.options.orientation + ')'))
self.vData = [['', 'False', 0, 0], ['', 'False', 0, 0], ['', 'False', 0, 0], ['', 'False', 0, 0]]
# add move to zero point and precut
if self.toolOffset > 0.0 and self.options.precut:
if self.options.center:
# position precut outside of drawing plus one time the tooloffset
if self.offsetX >= 0.0:
precutX = self.offsetX + self.toolOffset
else:
precutX = self.offsetX - self.toolOffset
if self.offsetY >= 0.0:
precutY = self.offsetY + self.toolOffset
else:
precutY = self.offsetY - self.toolOffset
self.processOffset('PU', precutX, precutY, self.options.pen)
self.processOffset('PD', precutX, precutY + self.toolOffset * 8, self.options.pen)
else:
self.processOffset('PU', 0, 0, self.options.pen)
self.processOffset('PD', 0, self.toolOffset * 8, self.options.pen)
# start conversion
self.processGroups(self.doc, groupmat)
# shift an empty node in in order to process last node in cache
if self.toolOffset > 0.0 and not self.dryRun:
self.processOffset('PU', 0, 0, 0)
if self.options.debug:
return self.hpgl, self
else:
return self.hpgl, ""
def processGroups(self, doc, groupmat):
# flatten layers and groups to avoid recursion
paths = []
for node in doc:
if (node.tag == inkex.addNS('g', 'svg') and self.isGroupVisible(node)) or node.tag == inkex.addNS('path', 'svg'):
paths.append([node.tag, node, self.mergeTransform(node, groupmat), self.getPenNumber(node)])
doc = ''
hasGroups = True
while hasGroups:
hasGroups = False
for i, elm in enumerate(paths):
if paths[i][0] == inkex.addNS('g', 'svg') and self.isGroupVisible(paths[i][1]):
hasGroups = True
for path in paths[i][1]:
if (path.tag == inkex.addNS('g', 'svg') and self.isGroupVisible(path)) or path.tag == inkex.addNS('path', 'svg'):
paths.insert(i + 1, [path.tag, path, self.mergeTransform(path, paths[i][2]), paths[i][3]])
paths[i][0] = ''
for node in paths:
if node[0] == inkex.addNS('path', 'svg'):
self.processPath(node[1], node[2], node[3])
def getPenNumber(self, doc):
penNum = str(doc.get('{' + inkex.NSS['inkscape'] + '}label')).lower().strip(' \t\n\r')
if re.search(r'( |\A)pen *\d+( |\Z)', penNum):
penNum = re.sub(r'(.* |\A)pen *(\d+)( .*|\Z)', r'\2', penNum, 1)
return int(penNum)
else:
style = doc.get('style')
style = simplestyle.parseStyle(style)
if 'stroke' in style and style['stroke'] != 'none':
color = style['stroke']
elif 'fill' in style and style['fill'] != 'none':
color = style['fill']
else:
return None
if not self.penIndex.has_key(color):
penNum = (len(self.penIndex) + 1) % self.penCount
self.penIndex[color] = self.penCount if penNum == 0 else penNum
return self.penIndex[color]
# return self.options.pen
def mergeTransform(self, doc, matrix):
# get and merge two matrixes into one
trans = doc.get('transform')
if trans:
return simpletransform.composeTransform(matrix, simpletransform.parseTransform(trans))
else:
return matrix
def isGroupVisible(self, group):
style = group.get('style')
if style:
style = simplestyle.parseStyle(style)
if 'display' in style and style['display'] == 'none':
return False
return True
def processPath(self, node, mat, pen):
# process path
path = node.get('d')
if path:
# parse and transform path
path = cubicsuperpath.parsePath(path)
simpletransform.applyTransformToPath(mat, path)
cspsubdiv.cspsubdiv(path, self.flat)
# path to HPGL commands
oldPosX = 0.0
oldPosY = 0.0
for singlePath in path:
cmd = 'PU'
for singlePathPoint in singlePath:
posX, posY = singlePathPoint[1]
# check if point is repeating, if so, ignore
if int(round(posX)) != int(round(oldPosX)) or int(round(posY)) != int(round(oldPosY)):
self.processOffset(cmd, posX, posY, pen)
cmd = 'PD'
oldPosX = posX
oldPosY = posY
# perform overcut
if self.overcut > 0.0 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 int(round(posX)) != int(round(oldPosX)) or int(round(posY)) != int(round(oldPosY)):
overcutLength += self.getLength(oldPosX, oldPosY, posX, posY)
if overcutLength >= self.overcut:
newLength = self.changeLength(oldPosX, oldPosY, posX, posY, - (overcutLength - self.overcut))
self.processOffset(cmd, newLength[0], newLength[1], pen)
break
else:
self.processOffset(cmd, posX, posY, pen)
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 processOffset(self, cmd, posX, posY, pen):
# calculate offset correction (or dont)
if self.toolOffset == 0.0 or self.dryRun:
self.storePoint(cmd, posX, posY, pen)
else:
# insert data into cache
self.vData.pop(0)
self.vData.insert(3, [cmd, posX, posY, pen])
# decide if enough data is availabe
if self.vData[2][1] != 'False':
if self.vData[1][1] == 'False':
self.storePoint(self.vData[2][0], self.vData[2][1], self.vData[2][2], self.vData[2][3])
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.toolOffset)
self.storePoint('PD', pointThree[0], pointThree[1], self.vData[2][3])
elif self.vData[0][1] != 'False':
# 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.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], self.vData[2][3])
else:
# Else just write the 3rd entry
pointThree = [self.vData[2][1], self.vData[2][2]]
self.storePoint('PU', pointThree[0], pointThree[1], self.vData[2][3])
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.toolOffset:
pointFour = self.changeLength(self.vData[3][1], self.vData[3][2], self.vData[2][1], self.vData[2][2], - self.toolOffset)
else:
pointFour = self.changeLength(self.vData[2][1], self.vData[2][2], self.vData[3][1], self.vData[3][2],
(self.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.toolOffset, self.vData[2][2] + math.sin(angle) * self.toolOffset, self.vData[2][3])
angle += self.toolOffsetFlat
else:
angle = angleStart - self.toolOffsetFlat
while angle > angleStart + angleVector:
self.storePoint('PD', self.vData[2][1] + math.cos(angle) * self.toolOffset, self.vData[2][2] + math.sin(angle) * self.toolOffset, self.vData[2][3])
angle -= self.toolOffsetFlat
self.storePoint('PD', pointFour[0], pointFour[1], self.vData[3][3])
def storePoint(self, command, x, y, pen):
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
# select correct pen
if self.lastPen != pen:
self.hpgl += ';SP%d' % pen
# do not repeat command
if command == 'PD' and self.lastPoint[0] == 'PD' and self.lastPen == pen:
self.hpgl += ',%d,%d' % (x, y)
else:
self.hpgl += ';%s%d,%d' % (command, x, y)
self.lastPen = pen
self.lastPoint = [command, x, y]
# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99
#! /usr/bin/python2
# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
'''
# standard library
import sys
# local libraries
import hpgl_multipen_encoder
sys.path.append('/usr/share/inkscape/extensions')
import inkex
class HpglMultipenOutput(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('--penCount', action='store', type='int', dest='penCount', default=1, help='Amount of pens to choose from')
self.OptionParser.add_option('--force', action='store', type='int', dest='force', default=24, help='Pen force (g)')
self.OptionParser.add_option('--speed', action='store', type='int', dest='speed', default=20, help='Pen speed (cm/s)')
self.OptionParser.add_option('--orientation', action='store', type='string', dest='orientation', default='90', help='Rotation (Clockwise)')
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('--overcut', action='store', type='float', dest='overcut', default=1.0, help='Overcut (mm)')
self.OptionParser.add_option('--toolOffset', action='store', type='float', dest='toolOffset', default=0.25, help='Tool (Knife) offset correction (mm)')
self.OptionParser.add_option('--precut', action='store', type='inkbool', dest='precut', default='TRUE', help='Use precut')
self.OptionParser.add_option('--flat', action='store', type='float', dest='flat', default=1.2, help='Curve flatness')
self.OptionParser.add_option('--autoAlign', action='store', type='inkbool', dest='autoAlign', default='TRUE', help='Auto align')
def effect(self):
self.options.debug = False
# get hpgl data
myHpglEncoder = hpgl_multipen_encoder.hpglMultipenEncoder(self)
try:
self.hpgl, debugObject = 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 = ''
return
else:
type, value, traceback = sys.exc_info()
raise ValueError("", type, value).with_traceback(traceback)
# convert raw HPGL to HPGL
hpglInit = 'IN'
if self.options.force > 0:
hpglInit += ';FS%d' % self.options.force
if self.options.speed > 0:
hpglInit += ';VS%d' % self.options.speed
self.hpgl = hpglInit + self.hpgl + ';SP0;PU0,0;IN; '
def output(self):
# print to file
if self.hpgl != '':
print(self.hpgl)
if __name__ == '__main__':
# start extension
e = HpglOutput()
e.affect()
# vim: expandtab shiftwidth=4 tabstop=8 softtabstop=4 fileencoding=utf-8 textwidth=99
Flask
\ No newline at end of file
from hpgl_multipen_output import HpglMultipenOutput
from sys import argv
def svgToHPGL (path, speed=1, penCount=8, force=2):
e = HpglMultipenOutput()
e.affect([
'--orientation', '0',
'--force', '0',
'--overcut', '0',
'--precut', 'false',
'--flat', '4',
'--toolOffset', '0',
'--autoAlign', 'false',
'--speed', str(speed),
'--penCount', str(penCount),
'--force', str(force),
path], False)
return e.hpgl
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment