2 from math
import sin
, cos
, atan2
, tan
, hypot
, acos
, sqrt
3 import path
, trafo
, unit
, helper
6 #########################
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
))
15 return unit
.topt(unit
.length(length
))
18 return deg
* math
.pi
/ 180
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
32 sp
= self
.intersect(box2
.path())[0]
33 try: self
.path
= self
.split(sp
[0])[0].path
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
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
55 class _line(_connector
):
57 def __init__(self
, box1
, box2
, boxdists
=[0,0]):
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]):
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:
86 try: bulge
+= absbulge
88 try: bulge
+= relbulge
*distance
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
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
))
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
)))
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
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
)
149 angle1
= dangle
- _torad(relangle1
)
150 if absangle2
is not None:
151 angle2
= _torad(absangle2
)
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,
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
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
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
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
)
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
):
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
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
)
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,
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
)
303 """an arc is a round connector between the centers of two boxes.
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
,
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)
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,
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
,
342 length1
=_topt(length1
), length2
=_topt(length2
),
343 bezierradius
=_topt(bezierradius
), beziersoftness
=1,
344 arcradius
=_topt(arcradius
),
345 boxdists
=boxdists_pt
)