1 # -*- coding: ISO-8859-1 -*-
3 # Copyright (C) 2011 Michael Schindler <m-schindler@users.sourceforge.net>
5 # This file is part of PyX (http://pyx.sourceforge.net/).
7 # PyX is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
12 # PyX is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with PyX; if not, write to the Free Software
19 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
21 from math
import atan2
, radians
22 from pyx
import unit
, attr
, normpath
23 from pyx
import path
as pathmodule
25 from .mp_path
import mp_endpoint
, mp_explicit
, mp_given
, mp_curl
, mp_open
, mp_end_cycle
, mp_make_choices
27 # global epsilon (default precision length of metapost, in pt)
30 def set(epsilon
=None):
32 if epsilon
is not None:
35 ################################################################################
37 ################################################################################
41 """Internal knot as used in MetaPost (mp.c)"""
43 def __init__(self
, x_pt
, y_pt
, ltype
, lx_pt
, ly_pt
, rtype
, rx_pt
, ry_pt
):
52 # this is a linked list:
55 def set_left_tension(self
, tens
):
57 def set_right_tension(self
, tens
):
59 def set_left_curl(self
, curl
):
61 def set_right_curl(self
, curl
):
63 set_left_given
= set_left_curl
64 set_right_given
= set_right_curl
66 def left_tension(self
):
68 def right_tension(self
):
74 left_given
= left_curl
75 right_given
= right_curl
78 """returns the length of a circularly linked list of knots"""
89 if self
.ltype
== mp_endpoint
:
91 elif self
.ltype
== mp_explicit
:
92 result
+= "{explicit %s %s}" % (self
.lx_pt
, self
.ly_pt
)
93 elif self
.ltype
== mp_given
:
94 result
+= "{given %g tens %g}" % (self
.lx_pt
, self
.ly_pt
)
95 elif self
.ltype
== mp_curl
:
96 result
+= "{curl %g tens %g}" % (self
.lx_pt
, self
.ly_pt
)
97 elif self
.ltype
== mp_open
:
98 result
+= "{open tens %g}" % (self
.ly_pt
)
99 elif self
.ltype
== mp_end_cycle
:
100 result
+= "{cycle tens %g}" % (self
.ly_pt
)
101 result
+= "(%g %g)" % (self
.x_pt
, self
.y_pt
)
103 if self
.rtype
== mp_endpoint
:
105 elif self
.rtype
== mp_explicit
:
106 result
+= "{explicit %g %g}" % (self
.rx_pt
, self
.ry_pt
)
107 elif self
.rtype
== mp_given
:
108 result
+= "{given %g tens %g}" % (self
.rx_pt
, self
.ry_pt
)
109 elif self
.rtype
== mp_curl
:
110 result
+= "{curl %g tens %g}" % (self
.rx_pt
, self
.ry_pt
)
111 elif self
.rtype
== mp_open
:
112 result
+= "{open tens %g}" % (self
.ry_pt
)
113 elif self
.rtype
== mp_end_cycle
:
114 result
+= "{cycle tens %g}" % (self
.ry_pt
)
117 class beginknot_pt(_knot
):
119 """A knot which interrupts a path, or which allows to continue it with a straight line"""
121 def __init__(self
, x_pt
, y_pt
, curl
=1, angle
=None):
123 type, value
= mp_curl
, curl
125 type, value
= mp_given
, angle
126 # tensions are modified by the adjacent curve, but default is 1
127 _knot
.__init
__(self
, x_pt
, y_pt
, mp_endpoint
, None, None, type, value
, 1)
129 class beginknot(beginknot_pt
):
131 def __init__(self
, x
, y
, curl
=1, angle
=None):
132 if not (angle
is None):
133 angle
= radians(angle
)
134 beginknot_pt
.__init
__(self
, unit
.topt(x
), unit
.topt(y
), curl
, angle
)
136 startknot
= beginknot
138 class endknot_pt(_knot
):
140 """A knot which interrupts a path, or which allows to continue it with a straight line"""
142 def __init__(self
, x_pt
, y_pt
, curl
=1, angle
=None):
144 type, value
= mp_curl
, curl
146 type, value
= mp_given
, angle
147 # tensions are modified by the adjacent curve, but default is 1
148 _knot
.__init
__(self
, x_pt
, y_pt
, type, value
, 1, mp_endpoint
, None, None)
150 class endknot(endknot_pt
):
152 def __init__(self
, x
, y
, curl
=1, angle
=None):
153 if not (angle
is None):
154 angle
= radians(angle
)
155 endknot_pt
.__init
__(self
, unit
.topt(x
), unit
.topt(y
), curl
, angle
)
157 class smoothknot_pt(_knot
):
159 """A knot with continous tangent and "mock" curvature."""
161 def __init__(self
, x_pt
, y_pt
):
162 # tensions are modified by the adjacent curve, but default is 1
163 _knot
.__init
__(self
, x_pt
, y_pt
, mp_open
, None, 1, mp_open
, None, 1)
165 class smoothknot(smoothknot_pt
):
167 def __init__(self
, x
, y
):
168 smoothknot_pt
.__init
__(self
, unit
.topt(x
), unit
.topt(y
))
172 class roughknot_pt(_knot
):
174 """A knot with noncontinous tangent."""
176 def __init__(self
, x_pt
, y_pt
, lcurl
=1, rcurl
=None, langle
=None, rangle
=None):
177 """Specify either the relative curvatures, or tangent angles left (l)
178 or right (r) of the point."""
180 ltype
, lvalue
= mp_curl
, lcurl
182 ltype
, lvalue
= mp_given
, langle
183 if rcurl
is not None:
184 rtype
, rvalue
= mp_curl
, rcurl
185 elif rangle
is not None:
186 rtype
, rvalue
= mp_given
, rangle
188 rtype
, rvalue
= ltype
, lvalue
189 # tensions are modified by the adjacent curve, but default is 1
190 _knot
.__init
__(self
, x_pt
, y_pt
, ltype
, lvalue
, 1, rtype
, rvalue
, 1)
192 class roughknot(roughknot_pt
):
194 def __init__(self
, x
, y
, lcurl
=1, rcurl
=None, langle
=None, rangle
=None):
195 if langle
is not None:
196 langle
= radians(langle
)
197 if rangle
is not None:
198 rangle
= radians(rangle
)
199 roughknot_pt
.__init
__(self
, unit
.topt(x
), unit
.topt(y
), lcurl
, rcurl
, langle
, rangle
)
201 ################################################################################
203 ################################################################################
206 def set_knots(self
, left_knot
, right_knot
):
207 """Sets the internal properties of the metapost knots"""
212 """A straight line"""
214 def __init__(self
, keepangles
=False):
215 """The option keepangles will guarantee a continuous tangent. The
216 curvature may become discontinuous, however"""
217 self
.keepangles
= keepangles
219 def set_knots(self
, left_knot
, right_knot
):
220 left_knot
.rtype
= mp_endpoint
221 right_knot
.ltype
= mp_endpoint
222 left_knot
.rx_pt
, left_knot
.ry_pt
= None, None
223 right_knot
.lx_pt
, right_knot
.ly_pt
= None, None
225 angle
= atan2(right_knot
.y_pt
-left_knot
.y_pt
, right_knot
.x_pt
-left_knot
.x_pt
)
226 left_knot
.ltype
= mp_given
227 left_knot
.set_left_given(angle
)
228 right_knot
.rtype
= mp_given
229 right_knot
.set_right_given(angle
)
232 class controlcurve_pt(_link
):
234 """A cubic Bezier curve which has its control points explicity set"""
236 def __init__(self
, lcontrol_pt
, rcontrol_pt
):
237 """The control points at the beginning (l) and the end (r) must be
239 self
.lcontrol_pt
= lcontrol_pt
240 self
.rcontrol_pt
= rcontrol_pt
242 def set_knots(self
, left_knot
, right_knot
):
243 left_knot
.rtype
= mp_explicit
244 right_knot
.ltype
= mp_explicit
245 left_knot
.rx_pt
, left_knot
.ry_pt
= self
.lcontrol_pt
246 right_knot
.lx_pt
, right_knot
.ly_pt
= self
.rcontrol_pt
248 class controlcurve(controlcurve_pt
):
250 def __init__(self
, lcontrol
, rcontrol
):
251 controlcurve_pt
.__init
__(self
, (unit
.topt(lcontrol
[0]), unit
.topt(lcontrol
[1])),
252 (unit
.topt(rcontrol
[0]), unit
.topt(rcontrol
[1])))
255 class tensioncurve(_link
):
257 """A yet unspecified cubic Bezier curve"""
259 def __init__(self
, ltension
=1, latleast
=False, rtension
=None, ratleast
=None):
260 """The tension parameters indicate the tensions at the beginning (l)
261 and the end (r) of the curve. Set the parameters (l/r)atleast to True
262 if you want to avoid inflection points."""
267 # make sure that tension >= 0.75 (p. 9 mpman.pdf)
268 self
.ltension
= max(0.75, abs(ltension
))
269 self
.rtension
= max(0.75, abs(rtension
))
271 self
.ltension
= -self
.ltension
273 self
.rtension
= -self
.rtension
275 def set_knots(self
, left_knot
, right_knot
):
276 if left_knot
.rtype
<= mp_explicit
or right_knot
.ltype
<= mp_explicit
:
277 raise Exception("metapost curve with given tension cannot have explicit knots")
278 left_knot
.set_right_tension(self
.ltension
)
279 right_knot
.set_left_tension(self
.rtension
)
284 ################################################################################
285 # Path creation class
286 ################################################################################
288 class path(pathmodule
.path
):
290 """A MetaPost-like path, which finds an optimal way through given points.
292 At points, you can either specify a given tangent direction (angle in
293 degrees) or a certain "curlyness" (relative to the curvature at the other
294 end of a curve), or nothing. In the latter case, both the tangent and the
295 "mock" curvature (an approximation to the real curvature, introduced by
296 J.D. Hobby in MetaPost) will be continuous.
298 The shape of the cubic Bezier curves between two points is controlled by
299 its "tension", unless you choose to set the control points manually."""
301 def __init__(self
, elems
, epsilon
=None):
302 """elems should contain metapost knots or links"""
307 for i
, elem
in enumerate(elems
):
308 if isinstance(elem
, _link
):
309 elem
.set_knots(elems
[i
-1], elems
[(i
+1)%len(elems
)])
310 elif isinstance(elem
, _knot
):
312 if elem
.ltype
== mp_endpoint
or elem
.rtype
== mp_endpoint
:
315 # link the knots among each other
316 for i
in range(len(knots
)):
317 knots
[i
-1].next
= knots
[i
]
319 # determine the control points
320 mp_make_choices(knots
[0], epsilon
)
322 pathmodule
.path
.__init
__(self
)
328 for i
, elem
in enumerate(elems
):
329 if isinstance(elem
, _link
):
331 if isinstance(elem
, line
):
332 do_lineto
, do_curveto
= True, False
334 do_lineto
, do_curveto
= False, True
335 elif isinstance(elem
, _knot
):
337 self
.append(pathmodule
.moveto_pt(elem
.x_pt
, elem
.y_pt
))
339 self
.append(pathmodule
.lineto_pt(elem
.x_pt
, elem
.y_pt
))
341 self
.append(pathmodule
.curveto_pt(prev
.rx_pt
, prev
.ry_pt
, elem
.lx_pt
, elem
.ly_pt
, elem
.x_pt
, elem
.y_pt
))
347 # close the path if necessary
348 if knots
[0].ltype
== mp_explicit
:
350 if do_lineto
and is_closed
:
351 self
.append(pathmodule
.closepath())
353 self
.append(pathmodule
.curveto_pt(prev
.rx_pt
, prev
.ry_pt
, elem
.lx_pt
, elem
.ly_pt
, elem
.x_pt
, elem
.y_pt
))
355 self
.append(pathmodule
.closepath())