1 from __future__ import absolute_import
2 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
12 def __init__(self, type, position):
14 self.position = position
17 def __init__(self, position):
18 super(LineNode, self).__init__(Node.LINE, position)
21 def __init__(self, position, rotation, radius, large, sweep):
22 super(ArcNode, self).__init__(Node.ARC, position)
23 self.rotation = rotation
29 def __init__(self, x, y, matrix=numpy.matrix(numpy.identity(3, numpy.float64))):
31 self._relMatrix = numpy.matrix([[matrix[0,0],matrix[1,0]],[matrix[0,1],matrix[1,1]]], numpy.float64)
32 self._startPoint = self._m(complex(x, y))
34 self._isClosed = False
36 def addLineTo(self, x, y):
37 self._nodes.append(LineNode(self._m(complex(x, y))))
39 def addArcTo(self, x, y, rotation, rx, ry, large, sweep):
40 self._nodes.append(ArcNode(self._m(complex(x, y)), rotation, self._r(complex(rx, ry)), large, sweep))
42 def addCurveTo(self, x, y, cp1x, cp1y, cp2x, cp2y):
43 node = Node(Node.CURVE, self._m(complex(x, y)))
44 node.cp1 = self._m(complex(cp1x, cp1y))
45 node.cp2 = self._m(complex(cp2x, cp2y))
46 self._nodes.append(node)
52 if abs(self._nodes[-1].position - self._startPoint) > 0.01:
53 self._nodes.append(Node(Node.LINE, self._startPoint))
56 def getArcInfo(self, node):
58 if self._nodes[0] != node:
59 p1 = self._nodes[self._nodes.index(node) - 1].position
62 if abs(p1 - p2) < 0.0001:
63 return p1, 0.0, math.pi, complex(0.0, 0.0)
64 rot = math.radians(node.rotation)
67 #http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
69 p1alt = diff #TODO: apply rot
70 p2alt = -diff #TODO: apply rot
73 x1alt2 = p1alt.real*p1alt.real
74 y1alt2 = p1alt.imag*p1alt.imag
76 f = x1alt2 / rx2 + y1alt2 / ry2
78 r *= math.sqrt(f+0.000001)
82 if rx2*y1alt2+ry2*x1alt2 == 0.0:
85 f = math.sqrt((rx2*ry2 - rx2*y1alt2 - ry2*x1alt2) / (rx2*y1alt2+ry2*x1alt2))
86 if node.large == node.sweep:
88 cAlt = f * complex(r.real*p1alt.imag/r.imag, -r.imag*p1alt.real/r.real)
90 c = cAlt + (p1 + p2) / 2 #TODO: apply rot
92 a1 = math.atan2((p1alt.imag - cAlt.imag) / r.imag, (p1alt.real - cAlt.real) / r.real)
93 a2 = math.atan2((p2alt.imag - cAlt.imag) / r.imag, (p2alt.real - cAlt.real) / r.real)
95 large = abs(a2 - a1) > math.pi
96 if large != node.large:
104 def getPoints(self, accuracy = 1.0):
105 pointList = [(self._startPoint, -1)]
106 p1 = self._startPoint
108 for node in self._nodes:
110 if node.type == Node.LINE:
112 pointList.append((p1, idx))
113 elif node.type == Node.ARC:
115 c, a1, a2, r = self.getArcInfo(node)
117 pCenter = c + complex(math.cos(a1 + 0.5*(a2-a1)) * r.real, math.sin(a1 + 0.5*(a2-a1)) * r.imag)
118 dist = abs(pCenter - p1) + abs(pCenter - p2)
119 segments = int(dist / accuracy) + 1
120 for n in xrange(1, segments):
121 p = c + complex(math.cos(a1 + n*(a2-a1)/segments) * r.real, math.sin(a1 + n*(a2-a1)/segments) * r.imag)
122 pointList.append((p, idx))
124 pointList.append((p2, idx))
126 elif node.type == Node.CURVE:
131 pCenter = p1*0.5*0.5*0.5 + cp1*3.0*0.5*0.5*0.5 + cp2*3.0*0.5*0.5*0.5 + p2*0.5*0.5*0.5
132 dist = abs(pCenter - p1) + abs(pCenter - p2)
133 segments = int(dist / accuracy) + 1
134 for n in xrange(1, segments):
135 f = n / float(segments)
137 point = p1*g*g*g + cp1*3.0*g*g*f + cp2*3.0*g*f*f + p2*f*f*f
138 pointList.append((point, idx))
140 pointList.append((p2, idx))
145 #getSVGPath returns an SVG path string. Ths path string is not perfect when matrix transformations are involved.
146 def getSVGPath(self):
147 p0 = self._startPoint
148 ret = 'M %f %f ' % (p0.real, p0.imag)
149 for node in self._nodes:
150 if node.type == Node.LINE:
152 ret += 'L %f %f' % (p0.real, p0.imag)
153 elif node.type == Node.ARC:
156 ret += 'A %f %f 0 %d %d %f %f' % (radius.real, radius.imag, 1 if node.large else 0, 1 if node.sweep else 0, p0.real, p0.imag)
157 elif node.type == Node.CURVE:
161 ret += 'C %f %f %f %f %f %f' % (cp1.real, cp1.imag, cp2.real, cp2.imag, p0.real, p0.imag)
165 def getPathString(self):
166 ret = '%f %f' % (self._startPoint.real, self._startPoint.imag)
167 for node in self._nodes:
168 if node.type == Node.LINE:
169 ret += '|L %f %f' % (node.position.real, node.position.imag)
170 elif node.type == Node.ARC:
171 ret += '|A %f %f %f %f %d %d' % (node.position.real, node.position.imag, node.radius.real, node.radius.imag, 1 if node.large else 0, 1 if node.sweep else 0)
172 elif node.type == Node.CURVE:
173 ret += '|C %f %f %f %f %f %f' % (node.position.real, node.position.imag, node.cp1.real, node.cp1.imag, node.cp2.real, node.cp2.imag)
177 tmp = numpy.matrix([p.real, p.imag, 1], numpy.float64) * self._matrix
178 return complex(tmp[0,0], tmp[0,1])
181 tmp = numpy.matrix([p.real, p.imag], numpy.float64) * self._relMatrix
182 return complex(tmp[0,0], tmp[0,1])
184 class Drawing(object):
188 def addPath(self, x, y, matrix=numpy.matrix(numpy.identity(3, numpy.float64))):
189 p = Path(x, y, matrix)
193 def _postProcessPaths(self):
194 for path in self.paths:
195 if not path.isClosed():
196 if abs(path._nodes[-1].position - path._startPoint) < 0.001:
197 path._isClosed = True
198 if path.isClosed() and len(path._nodes) == 2 and path._nodes[0].type == Node.ARC and path._nodes[1].type == Node.ARC:
199 if abs(path._nodes[0].radius - path._nodes[1].radius) < 0.001:
203 def dumpToFile(self, file):
204 file.write("%d\n" % (len(self.paths)))
205 for path in self.paths:
206 file.write("%s\n" % (path.getPathString()))
208 def readFromFile(self, file):
210 pathCount = int(file.readline())
211 for n in xrange(0, pathCount):
212 line = map(str.split, file.readline().strip().split('|'))
213 path = Path(float(line[0][0]), float(line[0][1]))
214 for item in line[1:]:
216 path.addLineTo(float(item[1]), float(item[2]))
218 path.addArcTo(float(item[1]), float(item[2]), 0, float(item[3]), float(item[4]), int(item[5]) != 0, int(item[6]) != 0)
220 path.addCurveTo(float(item[1]), float(item[2]), float(item[3]), float(item[4]), float(item[5]), float(item[6]))
221 self.paths.append(path)
222 self._postProcessPaths()
224 def saveAsHtml(self, filename):
225 f = open(filename, "w")
227 posMax = complex(-1000, -1000)
228 posMin = complex( 1000, 1000)
229 for path in self.paths:
230 points = path.getPoints()
232 if p.real > posMax.real:
233 posMax = complex(p.real, posMax.imag)
234 if p.imag > posMax.imag:
235 posMax = complex(posMax.real, p.imag)
236 if p.real < posMin.real:
237 posMin = complex(p.real, posMin.imag)
238 if p.imag < posMin.imag:
239 posMin = complex(posMin.real, p.imag)
241 f.write("<!DOCTYPE html><html><body>\n")
242 f.write("<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" style='width:%dpx;height:%dpx'>\n" % ((posMax - posMin).real, (posMax - posMin).imag))
243 f.write("<g fill-rule='evenodd' style=\"fill: gray; stroke:black;stroke-width:2\">\n")
244 f.write("<path d=\"")
245 for path in self.paths:
246 points = path.getPoints()
247 f.write("M %f %f " % (points[0].real - posMin.real, points[0].imag - posMin.imag))
248 for point in points[1:]:
249 f.write("L %f %f " % (point.real - posMin.real, point.imag - posMin.imag))
253 f.write("<g style=\"fill: none; stroke:red;stroke-width:1\">\n")
254 f.write("<path d=\"")
255 for path in self.paths:
256 f.write(path.getSVGPath())
261 f.write("</body></html>")