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 rot = math.radians(node.rotation)
65 #http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
67 p1alt = diff #TODO: apply rot
68 p2alt = -diff #TODO: apply rot
71 x1alt2 = p1alt.real*p1alt.real
72 y1alt2 = p1alt.imag*p1alt.imag
74 f = x1alt2 / rx2 + y1alt2 / ry2
76 r *= math.sqrt(f+0.000001)
80 if rx2*y1alt2+ry2*x1alt2 == 0.0:
83 f = math.sqrt((rx2*ry2 - rx2*y1alt2 - ry2*x1alt2) / (rx2*y1alt2+ry2*x1alt2))
84 if node.large == node.sweep:
86 cAlt = f * complex(r.real*p1alt.imag/r.imag, -r.imag*p1alt.real/r.real)
88 c = cAlt + (p1 + p2) / 2 #TODO: apply rot
90 a1 = math.atan2((p1alt.imag - cAlt.imag) / r.imag, (p1alt.real - cAlt.real) / r.real)
91 a2 = math.atan2((p2alt.imag - cAlt.imag) / r.imag, (p2alt.real - cAlt.real) / r.real)
93 large = abs(a2 - a1) > math.pi
94 if large != node.large:
102 def getPoints(self, accuracy = 1.0):
103 pointList = [(self._startPoint, -1)]
104 p1 = self._startPoint
106 for node in self._nodes:
108 if node.type == Node.LINE:
110 pointList.append((p1, idx))
111 elif node.type == Node.ARC:
113 c, a1, a2, r = self.getArcInfo(node)
115 pCenter = c + complex(math.cos(a1 + 0.5*(a2-a1)) * r.real, math.sin(a1 + 0.5*(a2-a1)) * r.imag)
116 dist = abs(pCenter - p1) + abs(pCenter - p2)
117 segments = int(dist / accuracy) + 1
118 for n in xrange(1, segments):
119 p = c + complex(math.cos(a1 + n*(a2-a1)/segments) * r.real, math.sin(a1 + n*(a2-a1)/segments) * r.imag)
120 pointList.append((p, idx))
122 pointList.append((p2, idx))
124 elif node.type == Node.CURVE:
129 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
130 dist = abs(pCenter - p1) + abs(pCenter - p2)
131 segments = int(dist / accuracy) + 1
132 for n in xrange(1, segments):
133 f = n / float(segments)
135 point = p1*g*g*g + cp1*3.0*g*g*f + cp2*3.0*g*f*f + p2*f*f*f
136 pointList.append((point, idx))
138 pointList.append((p2, idx))
143 #getSVGPath returns an SVG path string. Ths path string is not perfect when matrix transformations are involved.
144 def getSVGPath(self):
145 p0 = self._startPoint
146 ret = 'M %f %f ' % (p0.real, p0.imag)
147 for node in self._nodes:
148 if node.type == Node.LINE:
150 ret += 'L %f %f' % (p0.real, p0.imag)
151 elif node.type == Node.ARC:
154 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)
155 elif node.type == Node.CURVE:
159 ret += 'C %f %f %f %f %f %f' % (cp1.real, cp1.imag, cp2.real, cp2.imag, p0.real, p0.imag)
163 def getPathString(self):
164 ret = '%f %f' % (self._startPoint.real, self._startPoint.imag)
165 for node in self._nodes:
166 if node.type == Node.LINE:
167 ret += '|L %f %f' % (node.position.real, node.position.imag)
168 elif node.type == Node.ARC:
169 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)
170 elif node.type == Node.CURVE:
171 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)
175 tmp = numpy.matrix([p.real, p.imag, 1], numpy.float64) * self._matrix
176 return complex(tmp[0,0], tmp[0,1])
179 tmp = numpy.matrix([p.real, p.imag], numpy.float64) * self._relMatrix
180 return complex(tmp[0,0], tmp[0,1])
182 class Drawing(object):
186 def addPath(self, x, y, matrix=numpy.matrix(numpy.identity(3, numpy.float64))):
187 p = Path(x, y, matrix)
191 def _postProcessPaths(self):
192 for path in self.paths:
193 if not path.isClosed():
194 if abs(path._nodes[-1].position - path._startPoint) < 0.001:
195 path._isClosed = True
196 if path.isClosed() and len(path._nodes) == 2 and path._nodes[0].type == Node.ARC and path._nodes[1].type == Node.ARC:
197 if abs(path._nodes[0].radius - path._nodes[1].radius) < 0.001:
201 def dumpToFile(self, file):
202 file.write("%d\n" % (len(self.paths)))
203 for path in self.paths:
204 file.write("%s\n" % (path.getPathString()))
206 def readFromFile(self, file):
208 pathCount = int(file.readline())
209 for n in xrange(0, pathCount):
210 line = map(str.split, file.readline().strip().split('|'))
211 path = Path(float(line[0][0]), float(line[0][1]))
212 for item in line[1:]:
214 path.addLineTo(float(item[1]), float(item[2]))
216 path.addArcTo(float(item[1]), float(item[2]), 0, float(item[3]), float(item[4]), int(item[5]) != 0, int(item[6]) != 0)
218 path.addCurveTo(float(item[1]), float(item[2]), float(item[3]), float(item[4]), float(item[5]), float(item[6]))
219 self.paths.append(path)
220 self._postProcessPaths()
222 def saveAsHtml(self, filename):
223 f = open(filename, "w")
225 posMax = complex(-1000, -1000)
226 posMin = complex( 1000, 1000)
227 for path in self.paths:
228 points = path.getPoints()
230 if p.real > posMax.real:
231 posMax = complex(p.real, posMax.imag)
232 if p.imag > posMax.imag:
233 posMax = complex(posMax.real, p.imag)
234 if p.real < posMin.real:
235 posMin = complex(p.real, posMin.imag)
236 if p.imag < posMin.imag:
237 posMin = complex(posMin.real, p.imag)
239 f.write("<!DOCTYPE html><html><body>\n")
240 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))
241 f.write("<g fill-rule='evenodd' style=\"fill: gray; stroke:black;stroke-width:2\">\n")
242 f.write("<path d=\"")
243 for path in self.paths:
244 points = path.getPoints()
245 f.write("M %f %f " % (points[0].real - posMin.real, points[0].imag - posMin.imag))
246 for point in points[1:]:
247 f.write("L %f %f " % (point.real - posMin.real, point.imag - posMin.imag))
251 f.write("<g style=\"fill: none; stroke:red;stroke-width:1\">\n")
252 f.write("<path d=\"")
253 for path in self.paths:
254 f.write(path.getSVGPath())
259 f.write("</body></html>")