request clarification
[PyX/mjg.git] / pyx / connector.py
blobbe71fad16f0f421f20dfbba7d2b0187bd74e327e
1 import math
2 from math import sin, cos, atan2, tan, hypot, acos, sqrt
3 import path, trafo, unit, helper
6 #########################
7 ## helpers
8 #########################
10 def _topt(length, default_type=None):
11 if length is None: return None
12 if default_type is not None:
13 return unit.topt(unit.length(length, default_type=default_type))
14 else:
15 return unit.topt(unit.length(length))
17 def _torad(deg):
18 return deg * math.pi / 180
20 def _todeg(rad):
21 return rad * 180.0 / math.pi
23 class _connector(path.normpath):
25 def omitends(self, box1, box2):
26 """intersect a path with the boxes' paths"""
28 sp = self.intersect(box1.path())[0]
29 try: self.path = self.split(sp[-1])[1].path
30 except: pass
32 sp = self.intersect(box2.path())[0]
33 try: self.path = self.split(sp[0])[0].path
34 except: pass
36 def shortenpath(self, dists):
37 """shorten a path by the given distances"""
39 center = [unit.topt(self.begin()[i]) for i in [0,1]]
40 sp = self.intersect( path._circle(center[0], center[1], dists[0]) )[0]
41 try: self.path = self.split(sp[0])[1].path
42 except: pass
44 center = [unit.topt(self.end()[i]) for i in [0,1]]
45 sp = self.intersect( path._circle(center[0], center[1], dists[1]) )[0]
46 try: self.path = self.split(sp[-1])[0].path
47 except: pass
50 ################
51 ## classes
52 ################
55 class _line(_connector):
57 def __init__(self, box1, box2, boxdists=[0,0]):
59 self.box1 = box1
60 self.box2 = box2
62 _connector.__init__(self,
63 path._moveto(*self.box1.center),
64 path._lineto(*self.box2.center))
66 self.omitends(box1, box2)
67 self.shortenpath(boxdists)
70 class _arc(_connector):
72 def __init__(self, box1, box2, relangle=45,
73 absbulge=None, relbulge=None, boxdists=[0,0]):
75 self.box1 = box1
76 self.box2 = box2
78 rel = [self.box2.center[0] - self.box1.center[0],
79 self.box2.center[1] - self.box1.center[1]]
80 distance = hypot(*rel)
82 # usage of bulge overrides the relangle parameter
83 if relbulge is not None or absbulge is not None:
84 relangle = None
85 bulge = 0
86 try: bulge += absbulge
87 except: pass
88 try: bulge += relbulge*distance
89 except: pass
91 try: radius = abs(0.5 * (bulge + 0.25 * distance**2 / bulge))
92 except: radius = 10 * distance # default value for too straight arcs
93 radius = min(radius, 10 * distance)
94 center = 2.0*(radius-abs(bulge))/distance
95 center *= 2*(bulge>0.0)-1
96 # otherwise use relangle
97 else:
98 bulge=None
99 try: radius = 0.5 * distance / abs(cos(0.5*math.pi - _torad(relangle)))
100 except: radius = 10 * distance
101 try: center = tan(0.5*math.pi - _torad(relangle))
102 except: center = 0
104 # up to here center is only
105 # the distance from the middle of the straight connection
106 center = [0.5 * (self.box1.center[0] + self.box2.center[0] - rel[1]*center),
107 0.5 * (self.box1.center[1] + self.box2.center[1] + rel[0]*center)]
108 angle1 = atan2(*[self.box1.center[i] - center[i] for i in [1,0]])
109 angle2 = atan2(*[self.box2.center[i] - center[i] for i in [1,0]])
111 # draw the arc in positive direction by default
112 # negative direction if relangle<0 or bulge<0
113 if (relangle is not None and relangle < 0) or (bulge is not None and bulge < 0):
114 _connector.__init__(self,
115 path._moveto(*self.box1.center),
116 path._arcn(center[0], center[1], radius, _todeg(angle1), _todeg(angle2)))
117 else:
118 _connector.__init__(self,
119 path._moveto(*self.box1.center),
120 path._arc(center[0], center[1], radius, _todeg(angle1), _todeg(angle2)))
122 self.omitends(box1, box2)
123 self.shortenpath(boxdists)
126 class _curve(_connector):
128 def __init__(self, box1, box2,
129 relangle1=45, relangle2=45,
130 absangle1=None, absangle2=None,
131 absbulge=0, relbulge=0.39, boxdists=[0,0]):
132 # relbulge=0.39 and relangle1,2=45 leads
133 # approximately to the arc with angle=45
135 self.box1 = box1
136 self.box2 = box2
138 rel = [self.box2.center[0] - self.box1.center[0],
139 self.box2.center[1] - self.box1.center[1]]
140 distance = hypot(*rel)
141 dangle = atan2(rel[1], rel[0])
143 # calculate the armlength and angles for the control points
144 bulge = abs(distance*relbulge + absbulge)
146 if absangle1 is not None:
147 angle1 = _torad(absangle1)
148 else:
149 angle1 = dangle - _torad(relangle1)
150 if absangle2 is not None:
151 angle2 = _torad(absangle2)
152 else:
153 angle2 = dangle + _torad(relangle2)
155 control1 = [cos(angle1), sin(angle1)]
156 control2 = [cos(angle2), sin(angle2)]
157 control1 = [self.box1.center[i] + control1[i] * bulge for i in [0,1]]
158 control2 = [self.box2.center[i] - control2[i] * bulge for i in [0,1]]
160 _connector.__init__(self,
161 path._moveto(*self.box1.center),
162 path._curveto(*(control1 + control2 + helper.ensurelist(self.box2.center))))
164 self.omitends(box1, box2)
165 self.shortenpath(boxdists)
168 class _twolines(_connector):
170 def __init__(self, box1, box2,
171 absangle1=None, absangle2=None,
172 relangle1=None, relangle2=None, relangleM=None,
173 length1=None, length2=None,
174 bezierradius=None, beziersoftness=1,
175 arcradius=None,
176 boxdists=[0,0]):
178 self.box1 = box1
179 self.box2 = box2
181 begin = self.box1.center
182 end = self.box2.center
183 rel = [self.box2.center[0] - self.box1.center[0],
184 self.box2.center[1] - self.box1.center[1]]
185 distance = hypot(*rel)
186 dangle = atan2(rel[1], rel[0])
188 if relangle1 is not None: relangle1 = _torad(relangle1)
189 if relangle2 is not None: relangle2 = _torad(relangle2)
190 if relangleM is not None: relangleM = _torad(relangleM)
191 # absangle has priority over relangle:
192 if absangle1 is not None: relangle1 = dangle - _torad(absangle1)
193 if absangle2 is not None: relangle2 = math.pi - dangle + _torad(absangle2)
195 # check integrity of arguments
196 no_angles, no_lengths=0,0
197 for anangle in (relangle1, relangle2, relangleM):
198 if anangle is not None: no_angles += 1
199 for alength in (length1, length2):
200 if alength is not None: no_lengths += 1
202 if no_angles + no_lengths != 2:
203 raise NotImplementedError, "Please specify exactly two angles or lengths"
205 # calculate necessary angles and sidelengths
206 # always length1 and relangle1 !
207 # the case with two given angles
208 if no_angles==2:
209 if relangle1 is None: relangle1 = math.pi - relangle2 - relangleM
210 elif relangle2 is None: relangle2 = math.pi - relangle1 - relangleM
211 elif relangleM is None: relangleM = math.pi - relangle1 - relangle2
212 length1 = distance * abs(sin(relangle2)/sin(relangleM))
213 middle = self._middle_a(begin, dangle, length1, relangle1)
214 # the case with two given lengths
215 elif no_lengths==2:
216 relangle1 = acos((distance**2 + length1**2 - length2**2) / (2.0*distance*length1))
217 middle = self._middle_a(begin, dangle, length1, relangle1)
218 # the case with one length and one angle
219 else:
220 if relangle1 is not None:
221 if length1 is not None:
222 middle = self._middle_a(begin, dangle, length1, relangle1)
223 elif length2 is not None:
224 length1 = self._missinglength(length2, distance, relangle1)
225 middle = self._middle_a(begin, dangle, length1, relangle1)
226 elif relangle2 is not None:
227 if length1 is not None:
228 length2 = self._missinglength(length1, distance, relangle2)
229 middle = self._middle_b(end, dangle, length2, relangle2)
230 elif length2 is not None:
231 middle = self._middle_b(end, dangle, length2, relangle2)
232 elif relangleM is not None:
233 if length1 is not None:
234 length2 = self._missinglength(distance, length1, relangleM)
235 relangle1 = acos((distance**2 + length1**2 - length2**2) / (2.0*distance*length1))
236 middle = self._middle_a(begin, dangle, length1, relangle1)
237 elif length2 is not None:
238 length1 = self._missinglength(distance, length2, relangleM)
239 relangle1 = acos((distance**2 + length1**2 - length2**2) / (2.0*distance*length1))
240 middle = self._middle_a(begin, dangle, length1, relangle1)
241 else:
242 raise NotImplementedError, "I found a strange combination of arguments"
244 _connector.__init__(self,
245 path._moveto(*self.box1.center),
246 path._lineto(*middle),
247 path._lineto(*self.box2.center))
249 self.omitends(box1, box2)
250 self.shortenpath(boxdists)
252 def _middle_a(self, begin, dangle, length1, angle1):
253 a = dangle - angle1
254 dir = [cos(a), sin(a)]
255 return [begin[i] + length1*dir[i] for i in [0,1]]
257 def _middle_b(self, end, dangle, length2, angle2):
258 # a = -math.pi + dangle + angle2
259 return self._middle_a(end, -math.pi+dangle, length2, -angle2)
261 def _missinglength(self, lenA, lenB, angleA):
262 # calculate lenC, where side A and angleA are opposite
263 tmp1 = lenB*cos(angleA)
264 tmp2 = sqrt(tmp1**2 - lenB**2 + lenA**2)
265 if tmp1>tmp2: return tmp1-tmp2
266 return tmp1+tmp2
270 class line(_line):
272 """a line is the straight connector between the centers of two boxes"""
274 def __init__(self, box1, box2, boxdists=[0,0]):
276 boxdists_pt = [_topt(helper.getitemno(boxdists,i), default_type="v") for i in [0,1]]
278 _line.__init__(self, box1, box2, boxdists=boxdists_pt)
281 class curve(_curve):
283 """a curve is the curved connector between the centers of two boxes.
284 The constructor needs both angle and bulge"""
287 def __init__(self, box1, box2,
288 relangle1=45, relangle2=45,
289 absangle1=None, absangle2=None,
290 absbulge=0, relbulge=0.39,
291 boxdists=[0,0]):
293 boxdists_pt = [_topt(helper.getitemno(boxdists,i), default_type="v") for i in [0,1]]
295 _curve.__init__(self, box1, box2,
296 relangle1=relangle1, relangle2=relangle2,
297 absangle1=absangle1, absangle2=absangle2,
298 absbulge=_topt(absbulge), relbulge=relbulge,
299 boxdists=boxdists_pt)
301 class arc(_arc):
303 """an arc is a round connector between the centers of two boxes.
304 The constructor gets
305 either an angle in (-pi,pi)
306 or a bulge parameter in (-distance, distance)
307 (relbulge and absbulge are added)"""
309 def __init__(self, box1, box2, relangle=45,
310 absbulge=None, relbulge=None, boxdists=[0,0]):
312 boxdists_pt = [_topt(helper.getitemno(boxdists,i), default_type="v") for i in [0,1]]
314 _arc.__init__(self, box1, box2,
315 relangle=relangle,
316 absbulge=_topt(absbulge), relbulge=relbulge,
317 boxdists=boxdists_pt)
320 class twolines(_twolines):
322 """a twolines is a connector consisting of two straight lines.
323 The construcor takes a combination of angles and lengths:
324 either two angles (relative or absolute)
325 or two lenghts
326 or one length and one angle"""
328 def __init__(self, box1, box2,
329 absangle1=None, absangle2=None,
330 relangle1=None, relangle2=None, relangleM=None,
331 length1=None, length2=None,
332 bezierradius=None, beziersoftness=1,
333 arcradius=None,
334 boxdists=[0,0]):
336 boxdists_pt = [_topt(helper.getitemno(boxdists,i), default_type="v") for i in [0,1]]
338 _twolines.__init__(self, box1, box2,
339 absangle1=absangle1, absangle2=absangle2,
340 relangle1=relangle1, relangle2=relangle2,
341 relangleM=relangleM,
342 length1=_topt(length1), length2=_topt(length2),
343 bezierradius=_topt(bezierradius), beziersoftness=1,
344 arcradius=_topt(arcradius),
345 boxdists=boxdists_pt)