From 0469677258ea59d2e2de2bb16fc21c26a8ab7d33 Mon Sep 17 00:00:00 2001 From: daid Date: Sat, 28 Sep 2013 16:29:26 +0200 Subject: [PATCH] Lots of updates on the unused SVG class. (Totally unrelated to Cura right now) --- Cura/util/svg.py | 640 ++++++++++++++++++++++++++++++----------------- 1 file changed, 410 insertions(+), 230 deletions(-) diff --git a/Cura/util/svg.py b/Cura/util/svg.py index 9c45fba7..cd9acb54 100644 --- a/Cura/util/svg.py +++ b/Cura/util/svg.py @@ -2,245 +2,425 @@ from __future__ import absolute_import __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License" import math +import re +import sys +import numpy from xml.etree import ElementTree -import profile - -def processRect(e): - x = float(e.get('x')) - y = float(e.get('y')) - width = float(e.get('width')) - height = float(e.get('height')) - return [[complex(x, -y), complex(x+width, -y), complex(x+width, -(y+height)), complex(x, -(y+height)), complex(x, -y)]] - -def processPath(e): - d = e.get('d').replace(',', ' ') - num = "" - cmd = None - paths = [] - curPath = None - - p = complex(0, 0) - for c in d + "#": - if c in "-+.0123456789e": - num += c - if c in " \t\n\r#": - if len(num) > 0: - param.append(float(num)) - num = "" - if c in "MmZzLlHhVvCcSsQqTtAa#": - if cmd == 'M': - p = complex(param[0], -param[1]) - curPath = None - i = 2 - while i < len(param): - if curPath == None: - curPath = [p] - paths.append(curPath) - p = complex(param[i], -param[i+1]) - curPath.append(p) - i += 2 - elif cmd == 'm': - p += complex(param[0], -param[1]) - curPath = None - i = 2 - while i < len(param): - if curPath == None: - curPath = [p] - paths.append(curPath) - p += complex(param[i], -param[i+1]) - curPath.append(p) - i += 2 - elif cmd == 'L': - if curPath == None: - curPath = [p] - paths.append(curPath) - i = 0 - while i < len(param): - p = complex(param[i], -param[i+1]) - curPath.append(p) - i += 2 - elif cmd == 'l': - if curPath == None: - curPath = [p] - paths.append(curPath) - i = 0 - while i < len(param): - p += complex(param[i], -param[i+1]) - curPath.append(p) - i += 2 - curPath.append(p) - elif cmd == 'C': - if curPath == None: - curPath = [p] - paths.append(curPath) - i = 0 - while i < len(param): - addCurve(curPath, p, complex(param[i], -param[i+1]), complex(param[i+2], -param[i+3]), complex(param[i+4], -param[i+5])) - p = complex(param[i+4], -param[i+5]) - curPath.append(p) - i += 6 - elif cmd == 'c': - if curPath == None: - curPath = [p] - paths.append(curPath) - i = 0 - while i < len(param): - addCurve(curPath, p, p + complex(param[i], -param[i+1]), p + complex(param[i+2], -param[i+3]), p + complex(param[i+4], -param[i+5])) - p += complex(param[i+4], -param[i+5]) - curPath.append(p) - i += 6 - elif cmd == 'a': - if curPath == None: - curPath = [p] - paths.append(curPath) - i = 0 - print(param) - while i < len(param): - endPoint = p + complex(param[i+5], -param[i+6]) - addArc(curPath, p, endPoint, complex(param[i], param[i+1]), param[i+2], param[i+3], param[i+4]) - p = endPoint - curPath.append(p) - i += 7 - elif cmd == 'Z' or cmd == 'z': - curPath.append(curPath[0]) - elif cmd != None: - print(cmd) - cmd = c - param = [] - return paths - -def interpolate(p0, p1, f): - return complex(p0.real + (p1.real - p0.real) * f, p0.imag + (p1.imag - p0.imag) * f) - -def addCurve(path, p0, q0, q1, p1): - oldPoint = p0 - for n in xrange(0, 100): - k = n / 100.0 - r0 = interpolate(p0, q0, k); - r1 = interpolate(q0, q1, k); - r2 = interpolate(q1, p1, k); - b0 = interpolate(r0, r1, k); - b1 = interpolate(r1, r2, k); - s = interpolate(b0, b1, k); - if abs(s - oldPoint) > 1.0: - path.append(s) - oldPoint = s - -def addArc(path, begin, end, radius, xAxisRotation, largeArcFlag, sweepFlag): - xAxisRotationComplex = complex(math.cos(math.radians(xAxisRotation)), math.sin(math.radians(xAxisRotation))) - reverseXAxisRotationComplex = complex(xAxisRotationComplex.real, -xAxisRotationComplex.imag) - beginRotated = begin * reverseXAxisRotationComplex - endRotated = end * reverseXAxisRotationComplex - beginTransformed = complex(beginRotated.real / radius.real, beginRotated.imag / radius.imag) - endTransformed = complex(endRotated.real / radius.real, endRotated.imag / radius.imag) - midpointTransformed = 0.5 * (beginTransformed + endTransformed) - midMinusBeginTransformed = midpointTransformed - beginTransformed - midMinusBeginTransformedLength = abs(midMinusBeginTransformed) - - if midMinusBeginTransformedLength > 1.0: - radius *= midMinusBeginTransformedLength - beginTransformed /= midMinusBeginTransformedLength - endTransformed /= midMinusBeginTransformedLength - midpointTransformed /= midMinusBeginTransformedLength - midMinusBeginTransformed /= midMinusBeginTransformedLength - midMinusBeginTransformedLength = 1.0 - midWiddershinsTransformed = complex(-midMinusBeginTransformed.imag, midMinusBeginTransformed.real) - midWiddershinsLengthSquared = 1.0 - midMinusBeginTransformedLength * midMinusBeginTransformedLength - if midWiddershinsLengthSquared < 0.0: - midWiddershinsLengthSquared = 0.0 - midWiddershinsLength = math.sqrt(midWiddershinsLengthSquared) - midWiddershinsTransformed *= midWiddershinsLength / abs(midWiddershinsTransformed) - centerTransformed = midpointTransformed - if largeArcFlag == sweepFlag: - centerTransformed -= midWiddershinsTransformed - else: - centerTransformed += midWiddershinsTransformed - beginMinusCenterTransformed = beginTransformed - centerTransformed - beginMinusCenterTransformedLength = abs(beginMinusCenterTransformed) - if beginMinusCenterTransformedLength <= 0.0: - return end - beginAngle = math.atan2(beginMinusCenterTransformed.imag, beginMinusCenterTransformed.real) - endMinusCenterTransformed = endTransformed - centerTransformed - angleDifference = getAngleDifferenceByComplex(endMinusCenterTransformed, beginMinusCenterTransformed) - if sweepFlag: - if angleDifference < 0.0: - angleDifference += 2.0 * math.pi - else: - if angleDifference > 0.0: - angleDifference -= 2.0 * math.pi - - center = complex(centerTransformed.real * radius.real, centerTransformed.imag * radius.imag) * xAxisRotationComplex - for side in xrange(1, 32): - a = beginAngle + float(side) * math.pi * 2 / 32 - circumferential = complex(math.cos(a) * radius.real, math.sin(a) * radius.imag) * beginMinusCenterTransformedLength - point = center + circumferential * xAxisRotationComplex - path.append(point) - -def getAngleDifferenceByComplex( subtractFromComplex, subtractComplex ): - subtractComplexMirror = complex( subtractComplex.real , - subtractComplex.imag ) - differenceComplex = subtractComplexMirror * subtractFromComplex - return math.atan2( differenceComplex.imag, differenceComplex.real ) - - -def movePath(p, offset): - return map(lambda _p: _p - offset, p) +def applyTransformString(matrix, transform): + s = transform.find('(') + e = transform.find(')') + if s < 0 or e < 0: + print 'Unknown transform: %s' % (transform) + return matrix + tag = transform[:s] + data = map(float, re.split('[ \t,]+', transform[s+1:e].strip())) + if tag == 'matrix' and len(data) == 6: + return matrix * numpy.matrix([[data[0],data[1],0],[data[2],data[3],0],[data[4],data[5],1]], numpy.float64) + elif tag == 'translate' and len(data) == 1: + return matrix * numpy.matrix([[1,0,data[0]],[0,1,0],[0,0,1]], numpy.float64) + elif tag == 'translate' and len(data) == 2: + return matrix * numpy.matrix([[1,0,0],[0,1,0],[data[0],data[1],1]], numpy.float64) + elif tag == 'scale' and len(data) == 1: + return matrix * numpy.matrix([[data[0],0,0],[0,data[0],0],[0,0,1]], numpy.float64) + elif tag == 'scale' and len(data) == 2: + return matrix * numpy.matrix([[data[0],0,0],[0,data[1],0],[0,0,1]], numpy.float64) + elif tag == 'rotate' and len(data) == 1: + r = math.radians(data[0]) + return matrix * numpy.matrix([[math.cos(r),math.sin(r),0],[-math.sin(r),math.cos(r),0],[0,0,1]], numpy.float64) + elif tag == 'skewX' and len(data) == 1: + return matrix * numpy.matrix([[1,0,0],[math.tan(data[0]),1,0],[0,0,1]], numpy.float64) + elif tag == 'skewY' and len(data) == 1: + return matrix * numpy.matrix([[1,math.tan(data[0]),0],[0,1,0],[0,0,1]], numpy.float64) + print 'Unknown transform: %s' % (transform) + return matrix + +class Path(object): + LINE = 0 + ARC = 1 + CURVE = 2 + + def __init__(self, x, y, matrix): + self._matrix = matrix + self._relMatrix = numpy.matrix([[matrix[0,0],matrix[0,1]], [matrix[1,0],matrix[1,1]]]) + self._startPoint = complex(x, y) + self._points = [] + + def addLineTo(self, x, y): + self._points.append({'type': Path.LINE, 'p': complex(x, y)}) + + def addArcTo(self, x, y, rot, rx, ry, large, sweep): + self._points.append({ + 'type': Path.ARC, + 'p': complex(x, y), + 'rot': rot, + 'radius': complex(rx, ry), + 'large': large, + 'sweep': sweep + }) + + def addCurveTo(self, x, y, cp1x, cp1y, cp2x, cp2y): + self._points.append({ + 'type': Path.CURVE, + 'p': complex(x, y), + 'cp1': complex(cp1x, cp1y), + 'cp2': complex(cp2x, cp2y) + }) + + def closePath(self): + self._points.append({'type': Path.LINE, 'p': self._startPoint}) + + def getPoints(self): + pointList = [self._m(self._startPoint)] + p1 = self._startPoint + for p in self._points: + if p['type'] == Path.LINE: + p1 = p['p'] + pointList.append(self._m(p1)) + elif p['type'] == Path.ARC: + p2 = p['p'] + rot = math.radians(p['rot']) + r = p['radius'] + + #http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter + diff = (p1 - p2) / 2 + p1alt = diff #TODO: apply rot + p2alt = -diff #TODO: apply rot + rx2 = r.real*r.real + ry2 = r.imag*r.imag + x1alt2 = p1alt.real*p1alt.real + y1alt2 = p1alt.imag*p1alt.imag + + f = x1alt2 / rx2 + y1alt2 / ry2 + if f >= 1.0: + r *= math.sqrt(f+0.000001) + rx2 = r.real*r.real + ry2 = r.imag*r.imag + + f = math.sqrt((rx2*ry2 - rx2*y1alt2 - ry2*x1alt2) / (rx2*y1alt2+ry2*x1alt2)) + if p['large'] == p['sweep']: + f = -f + cAlt = f * complex(r.real*p1alt.imag/r.imag, -r.imag*p1alt.real/r.real) + + c = cAlt + (p1 + p2) / 2 #TODO: apply rot + + a1 = math.atan2((p1alt.imag - cAlt.imag) / r.imag, (p1alt.real - cAlt.real) / r.real) + a2 = math.atan2((p2alt.imag - cAlt.imag) / r.imag, (p2alt.real - cAlt.real) / r.real) + + #if a1 > a2: + # a2 += math.pi * 2 + large = abs(a2 - a1) > math.pi + if large != p['large']: + if a1 < a2: + a1 += math.pi * 2 + else: + a2 += math.pi * 2 + + for n in xrange(0, 16): + pointList.append(self._m(c + complex(math.cos(a1 + n*(a2-a1)/16) * r.real, math.sin(a1 + n*(a2-a1)/16) * r.imag))) + + pointList.append(self._m(p2)) + p1 = p2 + elif p['type'] == Path.CURVE: + p1_ = self._m(p1) + p2 = self._m(p['p']) + cp1 = self._m(p['cp1']) + cp2 = self._m(p['cp2']) + + for n in xrange(0, 10): + f = n / 10.0 + g = 1.0-f + point = p1_*g*g*g + cp1*3.0*g*g*f + cp2*3.0*g*f*f + p2*f*f*f + pointList.append(point) + + pointList.append(p2) + p1 = p['p'] + + return pointList + + def getSVGPath(self): + p0 = self._m(self._startPoint) + ret = 'M %f %f ' % (p0.real, p0.imag) + for p in self._points: + if p['type'] == Path.LINE: + p0 = self._m(p['p']) + ret += 'L %f %f' % (p0.real, p0.imag) + elif p['type'] == Path.ARC: + p0 = self._m(p['p']) + radius = p['radius'] + ret += 'A %f %f 0 %d %d %f %f' % (radius.real, radius.imag, p['large'], p['sweep'], p0.real, p0.imag) + elif p['type'] == Path.CURVE: + p0 = self._m(p['p']) + cp1 = self._m(p['cp1']) + cp2 = self._m(p['cp2']) + ret += 'C %f %f %f %f %f %f' % (cp1.real, cp1.imag, cp2.real, cp2.imag, p0.real, p0.imag) + + return ret + + def _m(self, p): + tmp = numpy.matrix([p.real, p.imag, 1], numpy.float64) * self._matrix + return complex(tmp[0,0], tmp[0,1]) + def _r(self, p): + tmp = numpy.matrix([p.real, p.imag], numpy.float64) * self._relMatrix + return complex(tmp[0,0], tmp[0,1]) class SVG(object): def __init__(self, filename): - tagProcess = {} - tagProcess['rect'] = processRect - tagProcess['path'] = processPath + self.tagProcess = {} + self.tagProcess['rect'] = self._processRectTag + self.tagProcess['line'] = self._processLineTag + self.tagProcess['polyline'] = self._processPolylineTag + self.tagProcess['polygon'] = self._processPolygonTag + self.tagProcess['elipse'] = self._processPolygonTag + self.tagProcess['circle'] = self._processCircleTag + self.tagProcess['ellipse'] = self._processEllipseTag + self.tagProcess['path'] = self._processPathTag + self.tagProcess['g'] = self._processGTag + self.tagProcess['a'] = self._processGTag + self.tagProcess['text'] = None #No text implementation yet + self.tagProcess['image'] = None + self.tagProcess['metadata'] = None + self.tagProcess['defs'] = None + self.tagProcess['title'] = None + self.tagProcess['animate'] = None + self.tagProcess['set'] = None + self.tagProcess['script'] = None + + #From Inkscape + self.tagProcess['namedview'] = None + #From w3c testsuite + self.tagProcess['SVGTestCase'] = None self.paths = [] - for e in ElementTree.parse(open(filename, "r")).getiterator(): + f = open(filename, "r") + self._processGTag(ElementTree.parse(f).getroot(), numpy.matrix(numpy.identity(3, numpy.float64))) + f.close() + + def _processGTag(self, tag, baseMatrix): + for e in tag: + if e.get('transform') is None: + matrix = baseMatrix + else: + matrix = applyTransformString(baseMatrix, e.get('transform')) tag = e.tag[e.tag.find('}')+1:] - if not tag in tagProcess: - #print 'unknown tag: %s' % (tag) - continue - self.paths.extend(tagProcess[tag](e)) - - def center(self, centerPoint): - offset = complex(0, 0) - n = 0 - for path in self.paths: - for point in path: - offset += point - n += 1 - offset /= n - offset -= centerPoint - - self.paths = [movePath(p, offset) for p in self.paths] + if not tag in self.tagProcess: + print 'unknown tag: %s' % (tag) + elif self.tagProcess[tag] is not None: + self.tagProcess[tag](e, matrix) + + def _processLineTag(self, tag, matrix): + x1 = float(tag.get('x1', 0)) + y1 = float(tag.get('y1', 0)) + x2 = float(tag.get('x2', 0)) + y2 = float(tag.get('y2', 0)) + p = Path(x1, y1, matrix) + p.addLineTo(x2, y2) + self.paths.append(p) + + def _processPolylineTag(self, tag, matrix): + values = map(float, re.split('[, \t]+', tag.get('points', '').replace('-', ' -').strip())) + p = Path(values[0], values[1], matrix) + for n in xrange(2, len(values)-1, 2): + p.addLineTo(values[n], values[n+1]) + self.paths.append(p) + + def _processPolygonTag(self, tag, matrix): + values = map(float, re.split('[, \t]+', tag.get('points', '').replace('-', ' -').strip())) + p = Path(values[0], values[1], matrix) + for n in xrange(2, len(values)-1, 2): + p.addLineTo(values[n], values[n+1]) + p.closePath() + self.paths.append(p) + + def _processCircleTag(self, tag, matrix): + cx = float(tag.get('cx', 0)) + cy = float(tag.get('cy', 0)) + r = float(tag.get('r', 0)) + p = Path(cx-r, cy, matrix) + p.addArcTo(cx+r, cy, 0, r, r, False, False) + p.addArcTo(cx-r, cy, 0, r, r, False, False) + self.paths.append(p) + + def _processEllipseTag(self, tag, matrix): + cx = float(tag.get('cx', 0)) + cy = float(tag.get('cy', 0)) + rx = float(tag.get('rx', 0)) + ry = float(tag.get('rx', 0)) + p = Path(cx-rx, cy, matrix) + p.addArcTo(cx+rx, cy, 0, rx, ry, False, False) + p.addArcTo(cx-rx, cy, 0, rx, ry, False, False) + self.paths.append(p) + + def _processRectTag(self, tag, matrix): + x = float(tag.get('x', 0)) + y = float(tag.get('y', 0)) + width = float(tag.get('width', 0)) + height = float(tag.get('height', 0)) + if width <= 0 or height <= 0: + return + rx = tag.get('rx') + ry = tag.get('ry') + if rx is not None or ry is not None: + if ry is None: + ry = rx + if rx is None: + rx = ry + rx = float(rx) + ry = float(ry) + if rx > width / 2: + rx = width / 2 + if ry > height / 2: + ry = height / 2 + else: + rx = 0.0 + ry = 0.0 + + if rx > 0 and ry > 0: + p = Path(x+rx, y, matrix) + p.addLineTo(x+width-rx, y) + p.addArcTo(x+width,y+ry, 0, rx, ry, False, True) + p.addLineTo(x+width, y+height-ry) + p.addArcTo(x+width-rx,y+height, 0, rx, ry, False, True) + p.addLineTo(x+rx, y+height) + p.addArcTo(x,y+height-ry, 0, rx, ry, False, True) + p.addLineTo(x, y+ry) + p.addArcTo(x+rx,y, 0, rx, ry, False, True) + self.paths.append(p) + else: + p = Path(x, y, matrix) + p.addLineTo(x,y+height) + p.addLineTo(x+width,y+height) + p.addLineTo(x+width,y) + p.closePath() + self.paths.append(p) + + def _processPathTag(self, tag, matrix): + pathString = tag.get('d', '').replace(',', ' ').replace('-', ' -') + x = 0 + y = 0 + path = None + for command in re.findall('[a-df-zA-DF-Z][^a-df-zA-DF-Z]*', pathString): + params = re.split(' +', command[1:].strip()) + if len(params) > 0 and params[0] == '': + params = params[1:] + if len(params) > 0 and params[-1] == '': + params = params[:-1] + params = map(float, params) + command = command[0] + + if command == 'm': + x += params[0] + y += params[1] + path = Path(x, y, matrix) + self.paths.append(path) + params = params[2:] + while len(params) > 1: + x += params[0] + y += params[1] + params = params[2:] + path.addLineTo(x, y) + elif command == 'M': + x = params[0] + y = params[1] + path = Path(x, y, matrix) + self.paths.append(path) + params = params[2:] + while len(params) > 1: + x = params[0] + y = params[1] + params = params[2:] + path.addLineTo(x, y) + elif command == 'l': + while len(params) > 1: + x += params[0] + y += params[1] + params = params[2:] + path.addLineTo(x, y) + elif command == 'L': + while len(params) > 1: + x = params[0] + y = params[1] + params = params[2:] + path.addLineTo(x, y) + elif command == 'h': + x += params[0] + path.addLineTo(x, y) + elif command == 'H': + x = params[0] + path.addLineTo(x, y) + elif command == 'v': + y += params[0] + path.addLineTo(x, y) + elif command == 'V': + y = params[0] + path.addLineTo(x, y) + elif command == 'a': + while len(params) > 6: + x += params[5] + y += params[6] + path.addArcTo(x, y, params[2], params[0], params[1], params[3] > 0, params[4] > 0) + params = params[7:] + elif command == 'A': + while len(params) > 6: + x = params[5] + y = params[6] + path.addArcTo(x, y, params[2], params[0], params[1], params[3] > 0, params[4] > 0) + params = params[7:] + elif command == 'c': + while len(params) > 5: + c1x = x + params[0] + c1y = y + params[1] + c2x = x + params[2] + c2y = y + params[3] + x += params[4] + y += params[5] + path.addCurveTo(x, y, c1x, c1y, c2x, c2y) + params = params[6:] + elif command == 'C': + while len(params) > 5: + c1x = params[0] + c1y = params[1] + c2x = params[2] + c2y = params[3] + x = params[4] + y = params[5] + path.addCurveTo(x, y, c1x, c1y, c2x, c2y) + params = params[6:] + elif command == 'z' or command == 'Z': + path.closePath() + x = path._startPoint.real + y = path._startPoint.imag + else: + print 'Unknown path command:', command, params + if __name__ == '__main__': - svg = SVG("../logo.svg") - f = open("../../test_export.gcode", "w") - - f.write(';TYPE:CUSTOM\n') - f.write(profile.getAlterationFileContents('start.gcode')) - svg.center(complex(profile.getMachineSettingFloat('machine_width') / 2, profile.getMachineSettingFloat('machine_depth') / 2)) - - layerThickness = 0.4 - filamentRadius = profile.getProfileSettingFloat('filament_diameter') / 2 - filamentArea = math.pi * filamentRadius * filamentRadius - lineWidth = profile.getProfileSettingFloat('nozzle_size') * 2 - - e = 0 - z = layerThickness - - for n in xrange(0, 20): - f.write("G1 Z%f F%f\n" % (z, profile.getProfileSettingFloat('max_z_speed')*60)) - for path in svg.paths: - oldPoint = path[0] - extrusionMMperDist = lineWidth * layerThickness / filamentArea - f.write("G1 X%f Y%f F%f\n" % (oldPoint.real, oldPoint.imag, profile.getProfileSettingFloat('travel_speed')*60)) - f.write("G1 F%f\n" % (profile.getProfileSettingFloat('print_speed')*60)) - for point in path[1:]: - dist = abs(oldPoint - point) - e += dist * extrusionMMperDist - f.write("G1 X%f Y%f E%f\n" % (point.real, point.imag, e)) - oldPoint = point - z += layerThickness - f.write(profile.getAlterationFileContents('end.gcode')) + for n in xrange(1, len(sys.argv)): + print 'File: %s' % (sys.argv[n]) + svg = SVG(sys.argv[n]) + + f = open("test_export.html", "w") + + f.write("\n") + f.write("\n" % (1000, 1000)) + f.write("\n") + f.write("") + f.write("\n") + + f.write("\n") + f.write("") + f.write("\n") + + f.write("\n") + f.write("") f.close() -- 2.30.2