some updates to the webpage
[PyX.git] / test / experimental / brace.py
blob438087ecee8a71926b9f2604d24868d55123fd65
1 #!/usr/bin/env python
2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2005 Michael Schindler <m-schindler@users.sourceforge.net>
7 # This file is part of PyX (http://pyx.sourceforge.net/).
9 # PyX is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # PyX is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with PyX; if not, write to the Free Software
21 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24 import sys; sys.path.insert(0, "../..")
25 import math
26 from pyx import *
29 # contains some nice curly braces
30 # This code is experimental because it is unclear
31 # how the brace fits into the concepts of PyX
33 # Some thoughts:
35 # - a brace needs to be decoratable with text
36 # it needs stroking and filling attributes
38 # - the brace is not really a box:
39 # it has two "anchor" points that are important for aligning it to other things
40 # and one "anchor" point (plus direction) for aligning other things
42 # - a brace is not a deformer:
43 # it does not look at anything else than begin/endpoint of a path
45 # - a brace might be a connector (which is to be dissolved into the box concept later?)
48 def unit_hypot(l1, l2):
49 result = unit.length()
50 l1 = unit.length(l1)
51 l2 = unit.length(l2)
52 result.t = math.hypot(l1.t, l2.t)
53 result.u = math.hypot(l1.u, l2.u)
54 result.v = math.hypot(l1.v, l2.v)
55 result.w = math.hypot(l1.w, l2.w)
56 result.x = math.hypot(l1.x, l2.x)
57 return result
60 def straightbrace(x0, x1, y0, y1,
61 totalheight=None,
62 barthickness=None, innerstrokesthickness=None, outerstrokesthickness=None,
63 innerstrokesangle=30, outerstrokesangle=30, slantstrokesangle=0,
64 innerstrokessmoothness=1.0, outerstrokessmoothness=1.0,
65 middlerelpos=0.5):
67 """a straight curly brace (differs from the original brace only via its default parameters)"""
69 return brace(x0, x1, y0, y1,
70 totalheight=totalheight,
71 barthickness=barthickness,
72 innerstrokesthickness=innerstrokesthickness,
73 outerstrokesthickness=outerstrokesthickness,
74 innerstrokesrelheight=0.5, # this makes the brace straight
75 outerstrokesrelheight=0.5, # this makes the brace straight
76 innerstrokesangle=innerstrokesangle,
77 outerstrokesangle=outerstrokesangle,
78 slantstrokesangle=slantstrokesangle,
79 innerstrokessmoothness=innerstrokessmoothness,
80 outerstrokessmoothness=outerstrokessmoothness,
81 middlerelpos=middlerelpos)
84 class brace:
86 def __init__(self, x0, y0, x1, y1,
87 totalheight=None,
88 barthickness=None, innerstrokesthickness=None, outerstrokesthickness=None,
89 innerstrokesrelheight=0.55, outerstrokesrelheight=0.60,
90 innerstrokesangle=30, outerstrokesangle=25, slantstrokesangle=5,
91 innerstrokessmoothness=2.0, outerstrokessmoothness=2.5,
92 middlerelpos=0.5):
94 r"""creates a curly brace
96 inner/\strokes
97 ____________/ \__________
98 / bar bar \outer
99 / \strokes
100 parameters:
101 x0, y0 starting point
102 x1, y1 end point
103 totalheight distance from the jaws to the middle cap (default: 0.10 * totallength)
104 barthickness thickness of the main bars (default: 0.05 * totalheight)
105 innerstrokesthickness thickness of the two ending strokes (default: 0.45 * barthickness)
106 outerstrokesthickness thickness of the inner strokes at the middle cap (default: 0.45 * barthickness)
107 innerstrokesrelheight | height of the inner/outer strokes, relative to the total height
108 outerstrokesrelheight | this determines the angle of the main bars!
109 should be around 0.5
110 Note: if innerstrokesrelheight + outerstrokesrelheight == 1 then the main bars
111 will be aligned parallel to the connecting line between the endpoints
112 outerstrokesangle angle of the two ending strokes
113 innerstrokesangle angle between the inner strokes at the middle cap
114 slantstrokesangle extra slanting of the inner/outer strokes
115 innerstrokessmoothness | smoothing parameter for the inner + outer strokes
116 outerstrokessmoothness | should be around 1 (allowed: [0,infty))
117 middlerelpos position of the middle cap (0 == left, 1 == right)
120 # first all the pyx-length parameters:
121 self.x0 = unit.length(x0)
122 self.y0 = unit.length(y0)
123 self.x1 = unit.length(x1)
124 self.y1 = unit.length(y1)
126 totallength = unit_hypot(x1 - x0, y1 - y0)
128 self.leftlength = middlerelpos * totallength
129 self.rightlength = (1 - middlerelpos) * totallength
130 if totalheight is None:
131 self.totalheight = 0.10 * totallength
132 else:
133 self.totalheight = unit.length(totalheight)
135 # use thicknesses relative to the total height:
136 if barthickness is None:
137 self.barthickness = 0.05 * self.totalheight
138 else:
139 self.barthickness = unit.length(barthickness)
140 if innerstrokesthickness is None:
141 self.innerstrokesthickness = 0.45 * self.barthickness
142 else:
143 self.innerstrokesthickness = unit.length(innerstrokesthickness)
144 if outerstrokesthickness is None:
145 self.outerstrokesthickness = 0.45 * self.barthickness
146 else:
147 self.outerstrokesthickness = unit.length(outerstrokesthickness)
149 # then angle parameters in degrees:
150 self.innerstrokesangle = innerstrokesangle
151 self.outerstrokesangle = outerstrokesangle
152 self.slantstrokesangle = slantstrokesangle
154 # and then simple number parameters:
155 self.innerstrokesrelheight = innerstrokesrelheight
156 self.outerstrokesrelheight = outerstrokesrelheight
157 self.middlerelpos = middlerelpos
159 self.innerstrokessmoothness = innerstrokessmoothness
160 self.outerstrokessmoothness = outerstrokessmoothness
163 def path(self):
165 height_pt = unit.topt(self.totalheight)
166 leftlength_pt = unit.topt(self.leftlength)
167 rightlength_pt = unit.topt(self.rightlength)
169 ithick_pt = unit.topt(self.innerstrokesthickness)
170 othick_pt = unit.topt(self.outerstrokesthickness)
171 bthick_pt = unit.topt(self.barthickness)
173 # create the left halfbrace with positive slanting
174 # because we will mirror this part
175 cos_iangle = math.cos(math.radians(0.5*self.innerstrokesangle - self.slantstrokesangle))
176 sin_iangle = math.sin(math.radians(0.5*self.innerstrokesangle - self.slantstrokesangle))
177 cos_oangle = math.cos(math.radians(self.outerstrokesangle - self.slantstrokesangle))
178 sin_oangle = math.sin(math.radians(self.outerstrokesangle - self.slantstrokesangle))
179 cos_slangle = math.cos(math.radians(-self.slantstrokesangle))
180 sin_slangle = math.sin(math.radians(-self.slantstrokesangle))
181 ilength_pt = self.innerstrokesrelheight * height_pt / cos_iangle
182 olength_pt = self.outerstrokesrelheight * height_pt / cos_oangle
184 bracepath = self.halfbracepath_pt(leftlength_pt, height_pt,
185 ilength_pt, olength_pt, ithick_pt, othick_pt, bthick_pt, cos_iangle,
186 sin_iangle, cos_oangle, sin_oangle, cos_slangle,
187 sin_slangle).reversed().transformed(trafo.mirror(90))
189 # create the right halfbrace with negative slanting
190 cos_iangle = math.cos(math.radians(0.5*self.innerstrokesangle + self.slantstrokesangle))
191 sin_iangle = math.sin(math.radians(0.5*self.innerstrokesangle + self.slantstrokesangle))
192 cos_oangle = math.cos(math.radians(self.outerstrokesangle + self.slantstrokesangle))
193 sin_oangle = math.sin(math.radians(self.outerstrokesangle + self.slantstrokesangle))
194 cos_slangle = math.cos(math.radians(-self.slantstrokesangle))
195 sin_slangle = math.sin(math.radians(-self.slantstrokesangle))
196 ilength_pt = self.innerstrokesrelheight * height_pt / cos_iangle
197 olength_pt = self.outerstrokesrelheight * height_pt / cos_oangle
199 bracepath = bracepath << self.halfbracepath_pt(rightlength_pt, height_pt,
200 ilength_pt, olength_pt, ithick_pt, othick_pt, bthick_pt, cos_iangle,
201 sin_iangle, cos_oangle, sin_oangle, cos_slangle,
202 sin_slangle)
204 x0_pt = unit.topt(self.x0)
205 y0_pt = unit.topt(self.y0)
206 x1_pt = unit.topt(self.x1)
207 y1_pt = unit.topt(self.y1)
208 return bracepath.transformed(
209 # two trafos for matching the given endpoints
210 trafo.translate_pt(x0_pt, y0_pt) *
211 trafo.rotate_pt(math.degrees(math.atan2(y1_pt-y0_pt, x1_pt-x0_pt))) *
212 # one trafo to move the brace's left outer stroke to zero
213 trafo.translate(self.leftlength, 0))
215 def halfbracepath_pt(self, length_pt, height_pt, ilength_pt, olength_pt,
216 ithick_pt, othick_pt, bthick_pt, cos_iangle, sin_iangle, cos_oangle,
217 sin_oangle, cos_slangle, sin_slangle):
219 ismooth = self.innerstrokessmoothness
220 osmooth = self.outerstrokessmoothness
222 # these two parameters are not important enough to be seen outside
223 inner_cap_param = 1.5
224 outer_cap_param = 2.5
225 outerextracurved = 0.6 # in (0, 1]
226 # 1.0 will lead to F=G, the outer strokes will not be curved at their ends.
227 # The smaller, the more curvature
229 # build an orientation path (three straight lines)
231 # \q1
232 # / \
233 # / \
234 # _/ \______________________________________q5
235 # q2 q3 q4 \
238 # \q6
240 # get the points for that:
241 q1 = (0, height_pt - inner_cap_param * ithick_pt + 0.5*ithick_pt/sin_iangle)
242 q2 = (q1[0] + ilength_pt * sin_iangle,
243 q1[1] - ilength_pt * cos_iangle)
244 q6 = (length_pt, 0)
245 q5 = (q6[0] - olength_pt * sin_oangle,
246 q6[1] + olength_pt * cos_oangle)
247 bardir = (q5[0] - q2[0], q5[1] - q2[1])
248 bardirnorm = math.hypot(*bardir)
249 bardir = (bardir[0]/bardirnorm, bardir[1]/bardirnorm)
250 ismoothlength_pt = ilength_pt * ismooth
251 osmoothlength_pt = olength_pt * osmooth
252 if bardirnorm < ismoothlength_pt + osmoothlength_pt:
253 ismoothlength_pt = bardirnorm * ismoothlength_pt / (ismoothlength_pt + osmoothlength_pt)
254 osmoothlength_pt = bardirnorm * osmoothlength_pt / (ismoothlength_pt + osmoothlength_pt)
255 q3 = (q2[0] + ismoothlength_pt * bardir[0],
256 q2[1] + ismoothlength_pt * bardir[1])
257 q4 = (q5[0] - osmoothlength_pt * bardir[0],
258 q5[1] - osmoothlength_pt * bardir[1])
261 # P _O
262 # / | \A2
263 # / A1\ \
264 # / \ B2C2________D2___________E2_______F2___G2
265 # \______________________________________ \
266 # B1,C1 D1 E1 F1 G1 \
267 # \ \
268 # \ \H2
269 # H1\_/I2
270 # I1
272 # the halfbraces meet in P and A1:
273 P = (0, height_pt)
274 A1 = (0, height_pt - inner_cap_param * ithick_pt)
275 # A2 is A1, shifted by the inner thickness
276 A2 = (A1[0] + ithick_pt * cos_iangle,
277 A1[1] + ithick_pt * sin_iangle)
278 s, t = deformer.intersection(P, A2, (cos_slangle, sin_slangle), (sin_iangle, -cos_iangle))
279 O = (P[0] + s * cos_slangle,
280 P[1] + s * sin_slangle)
282 # from D1 to E1 is the straight part of the brace
283 # also back from E2 to D1
284 D1 = (q3[0] + bthick_pt * bardir[1],
285 q3[1] - bthick_pt * bardir[0])
286 D2 = (q3[0] - bthick_pt * bardir[1],
287 q3[1] + bthick_pt * bardir[0])
288 E1 = (q4[0] + bthick_pt * bardir[1],
289 q4[1] - bthick_pt * bardir[0])
290 E2 = (q4[0] - bthick_pt * bardir[1],
291 q4[1] + bthick_pt * bardir[0])
292 # I1, I2 are the control points at the outer stroke
293 I1 = (q6[0] - 0.5 * othick_pt * cos_oangle,
294 q6[1] - 0.5 * othick_pt * sin_oangle)
295 I2 = (q6[0] + 0.5 * othick_pt * cos_oangle,
296 q6[1] + 0.5 * othick_pt * sin_oangle)
297 # get the control points for the curved parts of the brace
298 s, t = deformer.intersection(A1, D1, (sin_iangle, -cos_iangle), bardir)
299 B1 = (D1[0] + t * bardir[0],
300 D1[1] + t * bardir[1])
301 s, t = deformer.intersection(A2, D2, (sin_iangle, -cos_iangle), bardir)
302 B2 = (D2[0] + t * bardir[0],
303 D2[1] + t * bardir[1])
304 s, t = deformer.intersection(E1, I1, bardir, (-sin_oangle, cos_oangle))
305 G1 = (E1[0] + s * bardir[0],
306 E1[1] + s * bardir[1])
307 s, t = deformer.intersection(E2, I2, bardir, (-sin_oangle, cos_oangle))
308 G2 = (E2[0] + s * bardir[0],
309 E2[1] + s * bardir[1])
310 # at the inner strokes: use curvature zero at both ends
311 C1 = B1
312 C2 = B2
313 # at the outer strokes: use curvature zero only at the connection to
314 # the straight part
315 F1 = (outerextracurved * G1[0] + (1 - outerextracurved) * E1[0],
316 outerextracurved * G1[1] + (1 - outerextracurved) * E1[1])
317 F2 = (outerextracurved * G2[0] + (1 - outerextracurved) * E2[0],
318 outerextracurved * G2[1] + (1 - outerextracurved) * E2[1])
319 # the tip of the outer stroke, endpoints of the bezier curve
320 H1 = (I1[0] - outer_cap_param * othick_pt * sin_oangle,
321 I1[1] + outer_cap_param * othick_pt * cos_oangle)
322 H2 = (I2[0] - outer_cap_param * othick_pt * sin_oangle,
323 I2[1] + outer_cap_param * othick_pt * cos_oangle)
325 #for qq in [A1,B1,C1,D1,E1,F1,G1,H1,I1,
326 # A2,B2,C2,D2,E2,F2,G2,H2,I2,
327 # O,P
328 # ]:
329 # cc.fill(path.circle(qq[0], qq[1], 0.5), [color.rgb.green])
331 # now build the right halfbrace
332 bracepath = path.path(path.moveto_pt(*A1))
333 bracepath.append(path.curveto_pt(B1[0], B1[1], C1[0], C1[1], D1[0], D1[1]))
334 bracepath.append(path.lineto_pt(E1[0], E1[1]))
335 bracepath.append(path.curveto_pt(F1[0], F1[1], G1[0], G1[1], H1[0], H1[1]))
336 # the tip of the right halfbrace
337 bracepath.append(path.curveto_pt(I1[0], I1[1], I2[0], I2[1], H2[0], H2[1]))
338 # the rest of the right halfbrace
339 bracepath.append(path.curveto_pt(G2[0], G2[1], F2[0], F2[1], E2[0], E2[1]))
340 bracepath.append(path.lineto_pt(D2[0], D2[1]))
341 bracepath.append(path.curveto_pt(C2[0], C2[1], B2[0], B2[1], A2[0], A2[1]))
342 # the tip in the middle of the brace
343 bracepath.append(path.curveto_pt(O[0], O[1], O[0], O[1], P[0], P[1]))
345 return bracepath
349 A = (0,5)
350 B = (6,18)
352 b1 = straightbrace(A[0], A[1], B[0], B[1],
353 middlerelpos=0.8)
354 b2 = brace(B[0], B[1], A[0], A[1],
355 middlerelpos=0.2,
356 innerstrokesrelheight=0.6, outerstrokesrelheight=0.7,
357 slantstrokesangle=10)
359 c = canvas.canvas()
360 c.fill(path.circle(A[0], A[1], 1), [color.rgb.red])
361 c.fill(path.circle(B[0], B[1], 1), [color.rgb.blue])
362 c.stroke(path.line(A[0], A[1], B[0], B[1]))
363 c.fill(b1.path())
364 c.fill(b2.path())
366 c.writetofile("brace.eps", paperformat=document.paperformat.A4, fittosize=1, rotated=0)