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