chiark / gitweb /
Add "Reload platform" to refresh all the objects on the platform from their files
[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                 if abs(p1 - p2) < 0.0001:
63                         return p1, 0.0, math.pi, complex(0.0, 0.0)
64                 rot = math.radians(node.rotation)
65                 r = node.radius
66
67                 #http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
68                 diff = (p1 - p2) / 2
69                 p1alt = diff #TODO: apply rot
70                 p2alt = -diff #TODO: apply rot
71                 rx2 = r.real*r.real
72                 ry2 = r.imag*r.imag
73                 x1alt2 = p1alt.real*p1alt.real
74                 y1alt2 = p1alt.imag*p1alt.imag
75
76                 f = x1alt2 / rx2 + y1alt2 / ry2
77                 if f >= 1.0:
78                         r *= math.sqrt(f+0.000001)
79                         rx2 = r.real*r.real
80                         ry2 = r.imag*r.imag
81
82                 if rx2*y1alt2+ry2*x1alt2 == 0.0:
83                         f = 0
84                 else:
85                         f = math.sqrt((rx2*ry2 - rx2*y1alt2 - ry2*x1alt2) / (rx2*y1alt2+ry2*x1alt2))
86                 if node.large == node.sweep:
87                         f = -f
88                 cAlt = f * complex(r.real*p1alt.imag/r.imag, -r.imag*p1alt.real/r.real)
89
90                 c = cAlt + (p1 + p2) / 2 #TODO: apply rot
91
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)
94
95                 large = abs(a2 - a1) > math.pi
96                 if large != node.large:
97                         if a1 < a2:
98                                 a1 += math.pi * 2
99                         else:
100                                 a2 += math.pi * 2
101
102                 return c, a1, a2, r
103
104         def getPoints(self, accuracy = 1.0):
105                 pointList = [(self._startPoint, -1)]
106                 p1 = self._startPoint
107                 idx = -1
108                 for node in self._nodes:
109                         idx += 1
110                         if node.type == Node.LINE:
111                                 p1 = node.position
112                                 pointList.append((p1, idx))
113                         elif node.type == Node.ARC:
114                                 p2 = node.position
115                                 c, a1, a2, r = self.getArcInfo(node)
116
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))
123
124                                 pointList.append((p2, idx))
125                                 p1 = p2
126                         elif node.type == Node.CURVE:
127                                 p2 = node.position
128                                 cp1 = node.cp1
129                                 cp2 = node.cp2
130
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)
136                                         g = 1.0-f
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))
139
140                                 pointList.append((p2, idx))
141                                 p1 = p2
142
143                 return pointList
144
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:
151                                 p0 = node.position
152                                 ret += 'L %f %f' % (p0.real, p0.imag)
153                         elif node.type == Node.ARC:
154                                 p0 = node.position
155                                 radius = node.radius
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:
158                                 p0 = node.position
159                                 cp1 = node.cp1
160                                 cp2 = node.cp2
161                                 ret += 'C %f %f %f %f %f %f' % (cp1.real, cp1.imag, cp2.real, cp2.imag, p0.real, p0.imag)
162
163                 return ret
164
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)
174                 return ret
175
176         def _m(self, p):
177                 tmp = numpy.matrix([p.real, p.imag, 1], numpy.float64) * self._matrix
178                 return complex(tmp[0,0], tmp[0,1])
179
180         def _r(self, p):
181                 tmp = numpy.matrix([p.real, p.imag], numpy.float64) * self._relMatrix
182                 return complex(tmp[0,0], tmp[0,1])
183
184 class Drawing(object):
185         def __init__(self):
186                 self.paths = []
187
188         def addPath(self, x, y, matrix=numpy.matrix(numpy.identity(3, numpy.float64))):
189                 p = Path(x, y, matrix)
190                 self.paths.append(p)
191                 return p
192
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:
200                                         pass
201                                         #path._nodes = []
202
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()))
207
208         def readFromFile(self, file):
209                 self.paths = []
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:]:
215                                 if item[0] == 'L':
216                                         path.addLineTo(float(item[1]), float(item[2]))
217                                 elif item[0] == 'A':
218                                         path.addArcTo(float(item[1]), float(item[2]), 0, float(item[3]), float(item[4]), int(item[5]) != 0, int(item[6]) != 0)
219                                 elif item[0] == 'C':
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()
223
224         def saveAsHtml(self, filename):
225                 f = open(filename, "w")
226
227                 posMax = complex(-1000, -1000)
228                 posMin = complex( 1000,  1000)
229                 for path in self.paths:
230                         points = path.getPoints()
231                         for p in points:
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)
240
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))
250                 f.write("\"/>")
251                 f.write("</g>\n")
252
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())
257                 f.write("\"/>")
258                 f.write("</g>\n")
259
260                 f.write("</svg>\n")
261                 f.write("</body></html>")
262                 f.close()