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())