chiark / gitweb /
509aeffa0a82e1b14cff9a32dbd30cdc5537a70c
[cura.git] / Cura / util / drawingLoader / drawing.py
1 from __future__ import absolute_import
2 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
3
4 import math
5 import numpy
6
7 class Node(object):
8         LINE = 0
9         ARC = 1
10         CURVE = 2
11
12         def __init__(self, type, position):
13                 self.type = type
14                 self.position = position
15
16 class LineNode(Node):
17         def __init__(self, position):
18                 super(LineNode, self).__init__(Node.LINE, position)
19
20 class ArcNode(Node):
21         def __init__(self, position, rotation, radius, large, sweep):
22                 super(ArcNode, self).__init__(Node.ARC, position)
23                 self.rotation = rotation
24                 self.radius = radius
25                 self.large = large
26                 self.sweep = sweep
27
28 class Path(object):
29         def __init__(self, x, y, matrix=numpy.matrix(numpy.identity(3, numpy.float64))):
30                 self._matrix = matrix
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))
33                 self._nodes = []
34                 self._isClosed = False
35
36         def addLineTo(self, x, y):
37                 self._nodes.append(LineNode(self._m(complex(x, y))))
38
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))
41
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)
47
48         def isClosed(self):
49                 return self._isClosed
50
51         def closePath(self):
52                 if abs(self._nodes[-1].position - self._startPoint) > 0.01:
53                         self._nodes.append(Node(Node.LINE, self._startPoint))
54                 self._isClosed = True
55
56         def getArcInfo(self, node):
57                 p1 = self._startPoint
58                 if self._nodes[0] != node:
59                         p1 = self._nodes[self._nodes.index(node) - 1].position
60
61                 p2 = node.position
62                 rot = math.radians(node.rotation)
63                 r = node.radius
64
65                 #http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
66                 diff = (p1 - p2) / 2
67                 p1alt = diff #TODO: apply rot
68                 p2alt = -diff #TODO: apply rot
69                 rx2 = r.real*r.real
70                 ry2 = r.imag*r.imag
71                 x1alt2 = p1alt.real*p1alt.real
72                 y1alt2 = p1alt.imag*p1alt.imag
73
74                 f = x1alt2 / rx2 + y1alt2 / ry2
75                 if f >= 1.0:
76                         r *= math.sqrt(f+0.000001)
77                         rx2 = r.real*r.real
78                         ry2 = r.imag*r.imag
79
80                 if rx2*y1alt2+ry2*x1alt2 == 0.0:
81                         f = 0
82                 else:
83                         f = math.sqrt((rx2*ry2 - rx2*y1alt2 - ry2*x1alt2) / (rx2*y1alt2+ry2*x1alt2))
84                 if node.large == node.sweep:
85                         f = -f
86                 cAlt = f * complex(r.real*p1alt.imag/r.imag, -r.imag*p1alt.real/r.real)
87
88                 c = cAlt + (p1 + p2) / 2 #TODO: apply rot
89
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)
92
93                 large = abs(a2 - a1) > math.pi
94                 if large != node.large:
95                         if a1 < a2:
96                                 a1 += math.pi * 2
97                         else:
98                                 a2 += math.pi * 2
99
100                 return c, a1, a2, r
101
102         def getPoints(self, accuracy = 1.0):
103                 pointList = [(self._startPoint, -1)]
104                 p1 = self._startPoint
105                 idx = -1
106                 for node in self._nodes:
107                         idx += 1
108                         if node.type == Node.LINE:
109                                 p1 = node.position
110                                 pointList.append((p1, idx))
111                         elif node.type == Node.ARC:
112                                 p2 = node.position
113                                 c, a1, a2, r = self.getArcInfo(node)
114
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))
121
122                                 pointList.append((p2, idx))
123                                 p1 = p2
124                         elif node.type == Node.CURVE:
125                                 p2 = node.position
126                                 cp1 = node.cp1
127                                 cp2 = node.cp2
128
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)
134                                         g = 1.0-f
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))
137
138                                 pointList.append((p2, idx))
139                                 p1 = p2
140
141                 return pointList
142
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:
149                                 p0 = node.position
150                                 ret += 'L %f %f' % (p0.real, p0.imag)
151                         elif node.type == Node.ARC:
152                                 p0 = node.position
153                                 radius = node.radius
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:
156                                 p0 = node.position
157                                 cp1 = node.cp1
158                                 cp2 = node.cp2
159                                 ret += 'C %f %f %f %f %f %f' % (cp1.real, cp1.imag, cp2.real, cp2.imag, p0.real, p0.imag)
160
161                 return ret
162
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)
172                 return ret
173
174         def _m(self, p):
175                 tmp = numpy.matrix([p.real, p.imag, 1], numpy.float64) * self._matrix
176                 return complex(tmp[0,0], tmp[0,1])
177
178         def _r(self, p):
179                 tmp = numpy.matrix([p.real, p.imag], numpy.float64) * self._relMatrix
180                 return complex(tmp[0,0], tmp[0,1])
181
182 class Drawing(object):
183         def __init__(self):
184                 self.paths = []
185
186         def addPath(self, x, y, matrix=numpy.matrix(numpy.identity(3, numpy.float64))):
187                 p = Path(x, y, matrix)
188                 self.paths.append(p)
189                 return p
190
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:
198                                         pass
199                                         #path._nodes = []
200
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()))
205
206         def readFromFile(self, file):
207                 self.paths = []
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:]:
213                                 if item[0] == 'L':
214                                         path.addLineTo(float(item[1]), float(item[2]))
215                                 elif item[0] == 'A':
216                                         path.addArcTo(float(item[1]), float(item[2]), 0, float(item[3]), float(item[4]), int(item[5]) != 0, int(item[6]) != 0)
217                                 elif item[0] == 'C':
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()
221
222         def saveAsHtml(self, filename):
223                 f = open(filename, "w")
224
225                 posMax = complex(-1000, -1000)
226                 posMin = complex( 1000,  1000)
227                 for path in self.paths:
228                         points = path.getPoints()
229                         for p in points:
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)
238
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))
248                 f.write("\"/>")
249                 f.write("</g>\n")
250
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())
255                 f.write("\"/>")
256                 f.write("</g>\n")
257
258                 f.write("</svg>\n")
259                 f.write("</body></html>")
260                 f.close()