chiark / gitweb /
Change how the matrixes are applied on SVG objects. Fix a minor bug when the renderin...
[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 Drawing(object):
8         def __init__(self):
9                 self.paths = []
10
11         def addPath(self, x, y, matrix=numpy.matrix(numpy.identity(3, numpy.float64))):
12                 p = Path(x, y, matrix)
13                 self.paths.append(p)
14                 return p
15
16         def _postProcessPaths(self):
17                 for path in self.paths:
18                         if not path.isClosed():
19                                 if abs(self._nodes[-1]['p'] - self._startPoint) < 0.001:
20                                         self._isClosed = True
21
22         def saveAsHtml(self, filename):
23                 f = open(filename, "w")
24
25                 posMax = complex(-1000, -1000)
26                 posMin = complex( 1000,  1000)
27                 for path in self.paths:
28                         points = path.getPoints()
29                         for p in points:
30                                 if p.real > posMax.real:
31                                         posMax = complex(p.real, posMax.imag)
32                                 if p.imag > posMax.imag:
33                                         posMax = complex(posMax.real, p.imag)
34                                 if p.real < posMin.real:
35                                         posMin = complex(p.real, posMin.imag)
36                                 if p.imag < posMin.imag:
37                                         posMin = complex(posMin.real, p.imag)
38
39                 f.write("<!DOCTYPE html><html><body>\n")
40                 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))
41                 f.write("<g fill-rule='evenodd' style=\"fill: gray; stroke:black;stroke-width:2\">\n")
42                 f.write("<path d=\"")
43                 for path in self.paths:
44                         points = path.getPoints()
45                         f.write("M %f %f " % (points[0].real - posMin.real, points[0].imag - posMin.imag))
46                         for point in points[1:]:
47                                 f.write("L %f %f " % (point.real - posMin.real, point.imag - posMin.imag))
48                 f.write("\"/>")
49                 f.write("</g>\n")
50
51                 f.write("<g style=\"fill: none; stroke:red;stroke-width:1\">\n")
52                 f.write("<path d=\"")
53                 for path in self.paths:
54                         f.write(path.getSVGPath())
55                 f.write("\"/>")
56                 f.write("</g>\n")
57
58                 f.write("</svg>\n")
59                 f.write("</body></html>")
60                 f.close()
61
62 class Path(object):
63         LINE = 0
64         ARC = 1
65         CURVE = 2
66
67         def __init__(self, x, y, matrix=numpy.matrix(numpy.identity(3, numpy.float64))):
68                 self._matrix = matrix
69                 self._relMatrix = numpy.matrix([[matrix[0,0],matrix[1,0]],[matrix[0,1],matrix[1,1]]], numpy.float64)
70                 self._startPoint = self._m(complex(x, y))
71                 self._nodes = []
72                 self._isClosed = False
73
74         def addLineTo(self, x, y):
75                 self._nodes.append({'type': Path.LINE, 'p': self._m(complex(x, y))})
76
77         def addArcTo(self, x, y, rot, rx, ry, large, sweep):
78                 self._nodes.append({
79                         'type': Path.ARC,
80                         'p': self._m(complex(x, y)),
81                         'rot': rot,
82                         'radius': self._r(complex(rx, ry)),
83                         'large': large,
84                         'sweep': sweep
85                 })
86
87         def addCurveTo(self, x, y, cp1x, cp1y, cp2x, cp2y):
88                 self._nodes.append({
89                         'type': Path.CURVE,
90                         'p': self._m(complex(x, y)),
91                         'cp1': self._m(complex(cp1x, cp1y)),
92                         'cp2': self._m(complex(cp2x, cp2y))
93                 })
94
95         def isClosed(self):
96                 return self._isClosed
97
98         def closePath(self):
99                 self._nodes.append({'type': Path.LINE, 'p': self._startPoint})
100                 self._isClosed = True
101
102         def getPoints(self, accuracy = 1):
103                 pointList = [self._startPoint]
104                 p1 = self._startPoint
105                 for p in self._nodes:
106                         if p['type'] == Path.LINE:
107                                 p1 = p['p']
108                                 pointList.append(p1)
109                         elif p['type'] == Path.ARC:
110                                 p2 = p['p']
111                                 rot = math.radians(p['rot'])
112                                 r = p['radius']
113
114                                 #http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
115                                 diff = (p1 - p2) / 2
116                                 p1alt = diff #TODO: apply rot
117                                 p2alt = -diff #TODO: apply rot
118                                 rx2 = r.real*r.real
119                                 ry2 = r.imag*r.imag
120                                 x1alt2 = p1alt.real*p1alt.real
121                                 y1alt2 = p1alt.imag*p1alt.imag
122
123                                 f = x1alt2 / rx2 + y1alt2 / ry2
124                                 if f >= 1.0:
125                                         r *= math.sqrt(f+0.000001)
126                                         rx2 = r.real*r.real
127                                         ry2 = r.imag*r.imag
128
129                                 f = math.sqrt((rx2*ry2 - rx2*y1alt2 - ry2*x1alt2) / (rx2*y1alt2+ry2*x1alt2))
130                                 if p['large'] == p['sweep']:
131                                         f = -f
132                                 cAlt = f * complex(r.real*p1alt.imag/r.imag, -r.imag*p1alt.real/r.real)
133
134                                 c = cAlt + (p1 + p2) / 2 #TODO: apply rot
135
136                                 a1 = math.atan2((p1alt.imag - cAlt.imag) / r.imag, (p1alt.real - cAlt.real) / r.real)
137                                 a2 = math.atan2((p2alt.imag - cAlt.imag) / r.imag, (p2alt.real - cAlt.real) / r.real)
138
139                                 large = abs(a2 - a1) > math.pi
140                                 if large != p['large']:
141                                         if a1 < a2:
142                                                 a1 += math.pi * 2
143                                         else:
144                                                 a2 += math.pi * 2
145
146                                 pCenter = c + complex(math.cos(a1 + 0.5*(a2-a1)) * r.real, math.sin(a1 + 0.5*(a2-a1)) * r.imag)
147                                 dist = abs(pCenter - p1) + abs(pCenter - p2)
148                                 segments = int(dist / accuracy) + 1
149                                 for n in xrange(1, segments):
150                                         pointList.append(c + complex(math.cos(a1 + n*(a2-a1)/segments) * r.real, math.sin(a1 + n*(a2-a1)/segments) * r.imag))
151
152                                 pointList.append(p2)
153                                 p1 = p2
154                         elif p['type'] == Path.CURVE:
155                                 p2 = p['p']
156                                 cp1 = p['cp1']
157                                 cp2 = p['cp2']
158
159                                 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
160                                 dist = abs(pCenter - p1) + abs(pCenter - p2)
161                                 segments = int(dist / accuracy) + 1
162                                 for n in xrange(1, segments):
163                                         f = n / float(segments)
164                                         g = 1.0-f
165                                         point = p1*g*g*g + cp1*3.0*g*g*f + cp2*3.0*g*f*f + p2*f*f*f
166                                         pointList.append(point)
167
168                                 pointList.append(p2)
169                                 p1 = p['p']
170
171                 return pointList
172
173         #getSVGPath returns an SVG path string. Ths path string is not perfect when matrix transformations are involved.
174         def getSVGPath(self):
175                 p0 = self._startPoint
176                 ret = 'M %f %f ' % (p0.real, p0.imag)
177                 for p in self._nodes:
178                         if p['type'] == Path.LINE:
179                                 p0 = p['p']
180                                 ret += 'L %f %f' % (p0.real, p0.imag)
181                         elif p['type'] == Path.ARC:
182                                 p0 = p['p']
183                                 radius = p['radius']
184                                 ret += 'A %f %f 0 %d %d %f %f' % (radius.real, radius.imag, 1 if p['large'] else 0, 1 if p['sweep'] else 0, p0.real, p0.imag)
185                         elif p['type'] == Path.CURVE:
186                                 p0 = p['p']
187                                 cp1 = p['cp1']
188                                 cp2 = p['cp2']
189                                 ret += 'C %f %f %f %f %f %f' % (cp1.real, cp1.imag, cp2.real, cp2.imag, p0.real, p0.imag)
190
191                 return ret
192
193         def _m(self, p):
194                 tmp = numpy.matrix([p.real, p.imag, 1], numpy.float64) * self._matrix
195                 return complex(tmp[0,0], tmp[0,1])
196
197         def _r(self, p):
198                 tmp = numpy.matrix([p.real, p.imag], numpy.float64) * self._relMatrix
199                 return complex(tmp[0,0], tmp[0,1])