~ubuntu-branches/debian/sid/pyx/sid

« back to all changes in this revision

Viewing changes to pyx/metapost/path.py

  • Committer: Package Import Robot
  • Author(s): Stuart Prescott
  • Date: 2012-12-17 13:45:12 UTC
  • mto: (9.1.1 experimental)
  • mto: This revision was merged to the branch mainline in revision 10.
  • Revision ID: package-import@ubuntu.com-20121217134512-q85pr3q75fxii7mq
Tags: upstream-0.12.1
ImportĀ upstreamĀ versionĀ 0.12.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: ISO-8859-1 -*-
 
2
#
 
3
# Copyright (C) 2011 Michael Schindler <m-schindler@users.sourceforge.net>
 
4
#
 
5
# This file is part of PyX (http://pyx.sourceforge.net/).
 
6
#
 
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.
 
11
#
 
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.
 
16
#
 
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
 
20
 
 
21
from math import atan2, radians
 
22
from pyx import unit, attr, normpath
 
23
from pyx import path as pathmodule
 
24
 
 
25
from mp_path import mp_endpoint, mp_explicit, mp_given, mp_curl, mp_open, mp_end_cycle, mp_make_choices
 
26
 
 
27
# global epsilon (default precision length of metapost, in pt)
 
28
_epsilon = 1e-5
 
29
 
 
30
def set(epsilon=None):
 
31
    global _epsilon
 
32
    if epsilon is not None:
 
33
        _epsilon = epsilon
 
34
 
 
35
################################################################################
 
36
# Path knots
 
37
################################################################################
 
38
 
 
39
class _knot:
 
40
 
 
41
    """Internal knot as used in MetaPost (mp.c)"""
 
42
 
 
43
    def __init__(self, x_pt, y_pt, ltype, lx_pt, ly_pt, rtype, rx_pt, ry_pt):
 
44
        self.x_pt = x_pt
 
45
        self.y_pt = y_pt
 
46
        self.ltype = ltype
 
47
        self.lx_pt = lx_pt
 
48
        self.ly_pt = ly_pt
 
49
        self.rtype = rtype
 
50
        self.rx_pt = rx_pt
 
51
        self.ry_pt = ry_pt
 
52
        # this is a linked list:
 
53
        self.next = self
 
54
 
 
55
    def set_left_tension(self, tens):
 
56
        self.ly_pt = tens
 
57
    def set_right_tension(self, tens):
 
58
        self.ry_pt = tens
 
59
    def set_left_curl(self, curl):
 
60
        self.lx_pt = curl
 
61
    def set_right_curl(self, curl):
 
62
        self.rx_pt = curl
 
63
    set_left_given = set_left_curl
 
64
    set_right_given = set_right_curl
 
65
 
 
66
    def left_tension(self):
 
67
        return self.ly_pt
 
68
    def right_tension(self):
 
69
        return self.ry_pt
 
70
    def left_curl(self):
 
71
        return self.lx_pt
 
72
    def right_curl(self):
 
73
        return self.rx_pt
 
74
    left_given = left_curl
 
75
    right_given = right_curl
 
76
 
 
77
    def linked_len(self):
 
78
        """returns the length of a circularly linked list of knots"""
 
79
        n = 1
 
80
        p = self.next
 
81
        while not p is self:
 
82
            n += 1
 
83
            p = p.next
 
84
        return n
 
85
 
 
86
    def __repr__(self):
 
87
        result = ""
 
88
        # left
 
89
        if self.ltype == mp_endpoint:
 
90
            pass
 
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)
 
102
        # right
 
103
        if self.rtype == mp_endpoint:
 
104
            pass
 
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)
 
115
        return result
 
116
 
 
117
class beginknot_pt(_knot):
 
118
 
 
119
    """A knot which interrupts a path, or which allows to continue it with a straight line"""
 
120
 
 
121
    def __init__(self, x_pt, y_pt, curl=1, angle=None):
 
122
        if angle is None:
 
123
            type, value = mp_curl, curl
 
124
        else:
 
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)
 
128
 
 
129
class beginknot(beginknot_pt):
 
130
 
 
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)
 
135
 
 
136
startknot = beginknot
 
137
 
 
138
class endknot_pt(_knot):
 
139
 
 
140
    """A knot which interrupts a path, or which allows to continue it with a straight line"""
 
141
 
 
142
    def __init__(self, x_pt, y_pt, curl=1, angle=None):
 
143
        if angle is None:
 
144
            type, value = mp_curl, curl
 
145
        else:
 
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)
 
149
 
 
150
class endknot(endknot_pt):
 
151
 
 
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)
 
156
 
 
157
class smoothknot_pt(_knot):
 
158
 
 
159
    """A knot with continous tangent and "mock" curvature."""
 
160
 
 
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)
 
164
 
 
165
class smoothknot(smoothknot_pt):
 
166
 
 
167
    def __init__(self, x, y):
 
168
        smoothknot_pt.__init__(self, unit.topt(x), unit.topt(y))
 
169
 
 
170
knot = smoothknot
 
171
 
 
172
class roughknot_pt(_knot):
 
173
 
 
174
    """A knot with noncontinous tangent."""
 
175
 
 
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."""
 
179
        if langle is None:
 
180
            ltype, lvalue = mp_curl, lcurl
 
181
        else:
 
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
 
187
        else:
 
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)
 
191
 
 
192
class roughknot(roughknot_pt):
 
193
 
 
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)
 
200
 
 
201
################################################################################
 
202
# Path links
 
203
################################################################################
 
204
 
 
205
class _link:
 
206
    def set_knots(self, left_knot, right_knot):
 
207
        """Sets the internal properties of the metapost knots"""
 
208
        pass
 
209
 
 
210
class line(_link):
 
211
 
 
212
    """A straight line"""
 
213
 
 
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
 
218
 
 
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
 
224
        if self.keepangles:
 
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)
 
230
 
 
231
 
 
232
class controlcurve_pt(_link):
 
233
 
 
234
    """A cubic Bezier curve which has its control points explicity set"""
 
235
 
 
236
    def __init__(self, lcontrol_pt, rcontrol_pt):
 
237
        """The control points at the beginning (l) and the end (r) must be
 
238
        coordinate pairs"""
 
239
        self.lcontrol_pt = lcontrol_pt
 
240
        self.rcontrol_pt = rcontrol_pt
 
241
 
 
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
 
247
 
 
248
class controlcurve(controlcurve_pt):
 
249
 
 
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])))
 
253
 
 
254
 
 
255
class tensioncurve(_link):
 
256
 
 
257
    """A yet unspecified cubic Bezier curve"""
 
258
 
 
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."""
 
263
        if rtension is None:
 
264
            rtension = ltension
 
265
        if ratleast is None:
 
266
            ratleast = latleast
 
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))
 
270
        if latleast:
 
271
            self.ltension = -self.ltension
 
272
        if ratleast:
 
273
            self.rtension = -self.rtension
 
274
 
 
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)
 
280
 
 
281
curve = tensioncurve
 
282
 
 
283
 
 
284
################################################################################
 
285
# Path creation class
 
286
################################################################################
 
287
 
 
288
class path(pathmodule.path):
 
289
 
 
290
    """A MetaPost-like path, which finds an optimal way through given points.
 
291
 
 
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.
 
297
 
 
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."""
 
300
 
 
301
    def __init__(self, elems, epsilon=None):
 
302
        """elems should contain metapost knots or links"""
 
303
        if epsilon is None:
 
304
            epsilon = _epsilon
 
305
        knots = []
 
306
        is_closed = True
 
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):
 
311
                knots.append(elem)
 
312
                if elem.ltype == mp_endpoint or elem.rtype == mp_endpoint:
 
313
                    is_closed = False
 
314
 
 
315
        # link the knots among each other
 
316
        for i in range(len(knots)):
 
317
            knots[i-1].next = knots[i]
 
318
 
 
319
        # determine the control points
 
320
        mp_make_choices(knots[0], epsilon)
 
321
 
 
322
        pathmodule.path.__init__(self)
 
323
        # build up the path
 
324
        do_moveto = True
 
325
        do_lineto = False
 
326
        do_curveto = False
 
327
        prev = None
 
328
        for i, elem in enumerate(elems):
 
329
            if isinstance(elem, _link):
 
330
                do_moveto = False
 
331
                if isinstance(elem, line):
 
332
                    do_lineto, do_curveto = True, False
 
333
                else:
 
334
                    do_lineto, do_curveto = False, True
 
335
            elif isinstance(elem, _knot):
 
336
                if do_moveto:
 
337
                    self.append(pathmodule.moveto_pt(elem.x_pt, elem.y_pt))
 
338
                if do_lineto:
 
339
                    self.append(pathmodule.lineto_pt(elem.x_pt, elem.y_pt))
 
340
                elif do_curveto:
 
341
                    self.append(pathmodule.curveto_pt(prev.rx_pt, prev.ry_pt, elem.lx_pt, elem.ly_pt, elem.x_pt, elem.y_pt))
 
342
                do_moveto = True
 
343
                do_lineto = False
 
344
                do_curveto = False
 
345
                prev = elem
 
346
 
 
347
        # close the path if necessary
 
348
        if knots[0].ltype == mp_explicit:
 
349
            elem = knots[0]
 
350
            if do_lineto and is_closed:
 
351
                self.append(pathmodule.closepath())
 
352
            elif do_curveto:
 
353
                self.append(pathmodule.curveto_pt(prev.rx_pt, prev.ry_pt, elem.lx_pt, elem.ly_pt, elem.x_pt, elem.y_pt))
 
354
                if is_closed:
 
355
                    self.append(pathmodule.closepath())
 
356