/usr/share/pyshared/pyx/metapost/path.py is in python-pyx 0.12.1-2.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 | # -*- coding: ISO-8859-1 -*-
#
# Copyright (C) 2011 Michael Schindler <m-schindler@users.sourceforge.net>
#
# This file is part of PyX (http://pyx.sourceforge.net/).
#
# PyX is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# PyX is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with PyX; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
from math import atan2, radians
from pyx import unit, attr, normpath
from pyx import path as pathmodule
from mp_path import mp_endpoint, mp_explicit, mp_given, mp_curl, mp_open, mp_end_cycle, mp_make_choices
# global epsilon (default precision length of metapost, in pt)
_epsilon = 1e-5
def set(epsilon=None):
global _epsilon
if epsilon is not None:
_epsilon = epsilon
################################################################################
# Path knots
################################################################################
class _knot:
"""Internal knot as used in MetaPost (mp.c)"""
def __init__(self, x_pt, y_pt, ltype, lx_pt, ly_pt, rtype, rx_pt, ry_pt):
self.x_pt = x_pt
self.y_pt = y_pt
self.ltype = ltype
self.lx_pt = lx_pt
self.ly_pt = ly_pt
self.rtype = rtype
self.rx_pt = rx_pt
self.ry_pt = ry_pt
# this is a linked list:
self.next = self
def set_left_tension(self, tens):
self.ly_pt = tens
def set_right_tension(self, tens):
self.ry_pt = tens
def set_left_curl(self, curl):
self.lx_pt = curl
def set_right_curl(self, curl):
self.rx_pt = curl
set_left_given = set_left_curl
set_right_given = set_right_curl
def left_tension(self):
return self.ly_pt
def right_tension(self):
return self.ry_pt
def left_curl(self):
return self.lx_pt
def right_curl(self):
return self.rx_pt
left_given = left_curl
right_given = right_curl
def linked_len(self):
"""returns the length of a circularly linked list of knots"""
n = 1
p = self.next
while not p is self:
n += 1
p = p.next
return n
def __repr__(self):
result = ""
# left
if self.ltype == mp_endpoint:
pass
elif self.ltype == mp_explicit:
result += "{explicit %s %s}" % (self.lx_pt, self.ly_pt)
elif self.ltype == mp_given:
result += "{given %g tens %g}" % (self.lx_pt, self.ly_pt)
elif self.ltype == mp_curl:
result += "{curl %g tens %g}" % (self.lx_pt, self.ly_pt)
elif self.ltype == mp_open:
result += "{open tens %g}" % (self.ly_pt)
elif self.ltype == mp_end_cycle:
result += "{cycle tens %g}" % (self.ly_pt)
result += "(%g %g)" % (self.x_pt, self.y_pt)
# right
if self.rtype == mp_endpoint:
pass
elif self.rtype == mp_explicit:
result += "{explicit %g %g}" % (self.rx_pt, self.ry_pt)
elif self.rtype == mp_given:
result += "{given %g tens %g}" % (self.rx_pt, self.ry_pt)
elif self.rtype == mp_curl:
result += "{curl %g tens %g}" % (self.rx_pt, self.ry_pt)
elif self.rtype == mp_open:
result += "{open tens %g}" % (self.ry_pt)
elif self.rtype == mp_end_cycle:
result += "{cycle tens %g}" % (self.ry_pt)
return result
class beginknot_pt(_knot):
"""A knot which interrupts a path, or which allows to continue it with a straight line"""
def __init__(self, x_pt, y_pt, curl=1, angle=None):
if angle is None:
type, value = mp_curl, curl
else:
type, value = mp_given, angle
# tensions are modified by the adjacent curve, but default is 1
_knot.__init__(self, x_pt, y_pt, mp_endpoint, None, None, type, value, 1)
class beginknot(beginknot_pt):
def __init__(self, x, y, curl=1, angle=None):
if not (angle is None):
angle = radians(angle)
beginknot_pt.__init__(self, unit.topt(x), unit.topt(y), curl, angle)
startknot = beginknot
class endknot_pt(_knot):
"""A knot which interrupts a path, or which allows to continue it with a straight line"""
def __init__(self, x_pt, y_pt, curl=1, angle=None):
if angle is None:
type, value = mp_curl, curl
else:
type, value = mp_given, angle
# tensions are modified by the adjacent curve, but default is 1
_knot.__init__(self, x_pt, y_pt, type, value, 1, mp_endpoint, None, None)
class endknot(endknot_pt):
def __init__(self, x, y, curl=1, angle=None):
if not (angle is None):
angle = radians(angle)
endknot_pt.__init__(self, unit.topt(x), unit.topt(y), curl, angle)
class smoothknot_pt(_knot):
"""A knot with continous tangent and "mock" curvature."""
def __init__(self, x_pt, y_pt):
# tensions are modified by the adjacent curve, but default is 1
_knot.__init__(self, x_pt, y_pt, mp_open, None, 1, mp_open, None, 1)
class smoothknot(smoothknot_pt):
def __init__(self, x, y):
smoothknot_pt.__init__(self, unit.topt(x), unit.topt(y))
knot = smoothknot
class roughknot_pt(_knot):
"""A knot with noncontinous tangent."""
def __init__(self, x_pt, y_pt, lcurl=1, rcurl=None, langle=None, rangle=None):
"""Specify either the relative curvatures, or tangent angles left (l)
or right (r) of the point."""
if langle is None:
ltype, lvalue = mp_curl, lcurl
else:
ltype, lvalue = mp_given, langle
if rcurl is not None:
rtype, rvalue = mp_curl, rcurl
elif rangle is not None:
rtype, rvalue = mp_given, rangle
else:
rtype, rvalue = ltype, lvalue
# tensions are modified by the adjacent curve, but default is 1
_knot.__init__(self, x_pt, y_pt, ltype, lvalue, 1, rtype, rvalue, 1)
class roughknot(roughknot_pt):
def __init__(self, x, y, lcurl=1, rcurl=None, langle=None, rangle=None):
if langle is not None:
langle = radians(langle)
if rangle is not None:
rangle = radians(rangle)
roughknot_pt.__init__(self, unit.topt(x), unit.topt(y), lcurl, rcurl, langle, rangle)
################################################################################
# Path links
################################################################################
class _link:
def set_knots(self, left_knot, right_knot):
"""Sets the internal properties of the metapost knots"""
pass
class line(_link):
"""A straight line"""
def __init__(self, keepangles=False):
"""The option keepangles will guarantee a continuous tangent. The
curvature may become discontinuous, however"""
self.keepangles = keepangles
def set_knots(self, left_knot, right_knot):
left_knot.rtype = mp_endpoint
right_knot.ltype = mp_endpoint
left_knot.rx_pt, left_knot.ry_pt = None, None
right_knot.lx_pt, right_knot.ly_pt = None, None
if self.keepangles:
angle = atan2(right_knot.y_pt-left_knot.y_pt, right_knot.x_pt-left_knot.x_pt)
left_knot.ltype = mp_given
left_knot.set_left_given(angle)
right_knot.rtype = mp_given
right_knot.set_right_given(angle)
class controlcurve_pt(_link):
"""A cubic Bezier curve which has its control points explicity set"""
def __init__(self, lcontrol_pt, rcontrol_pt):
"""The control points at the beginning (l) and the end (r) must be
coordinate pairs"""
self.lcontrol_pt = lcontrol_pt
self.rcontrol_pt = rcontrol_pt
def set_knots(self, left_knot, right_knot):
left_knot.rtype = mp_explicit
right_knot.ltype = mp_explicit
left_knot.rx_pt, left_knot.ry_pt = self.lcontrol_pt
right_knot.lx_pt, right_knot.ly_pt = self.rcontrol_pt
class controlcurve(controlcurve_pt):
def __init__(self, lcontrol, rcontrol):
controlcurve_pt.__init__(self, (unit.topt(lcontrol[0]), unit.topt(lcontrol[1])),
(unit.topt(rcontrol[0]), unit.topt(rcontrol[1])))
class tensioncurve(_link):
"""A yet unspecified cubic Bezier curve"""
def __init__(self, ltension=1, latleast=False, rtension=None, ratleast=None):
"""The tension parameters indicate the tensions at the beginning (l)
and the end (r) of the curve. Set the parameters (l/r)atleast to True
if you want to avoid inflection points."""
if rtension is None:
rtension = ltension
if ratleast is None:
ratleast = latleast
# make sure that tension >= 0.75 (p. 9 mpman.pdf)
self.ltension = max(0.75, abs(ltension))
self.rtension = max(0.75, abs(rtension))
if latleast:
self.ltension = -self.ltension
if ratleast:
self.rtension = -self.rtension
def set_knots(self, left_knot, right_knot):
if left_knot.rtype <= mp_explicit or right_knot.ltype <= mp_explicit:
raise Exception("metapost curve with given tension cannot have explicit knots")
left_knot.set_right_tension(self.ltension)
right_knot.set_left_tension(self.rtension)
curve = tensioncurve
################################################################################
# Path creation class
################################################################################
class path(pathmodule.path):
"""A MetaPost-like path, which finds an optimal way through given points.
At points, you can either specify a given tangent direction (angle in
degrees) or a certain "curlyness" (relative to the curvature at the other
end of a curve), or nothing. In the latter case, both the tangent and the
"mock" curvature (an approximation to the real curvature, introduced by
J.D. Hobby in MetaPost) will be continuous.
The shape of the cubic Bezier curves between two points is controlled by
its "tension", unless you choose to set the control points manually."""
def __init__(self, elems, epsilon=None):
"""elems should contain metapost knots or links"""
if epsilon is None:
epsilon = _epsilon
knots = []
is_closed = True
for i, elem in enumerate(elems):
if isinstance(elem, _link):
elem.set_knots(elems[i-1], elems[(i+1)%len(elems)])
elif isinstance(elem, _knot):
knots.append(elem)
if elem.ltype == mp_endpoint or elem.rtype == mp_endpoint:
is_closed = False
# link the knots among each other
for i in range(len(knots)):
knots[i-1].next = knots[i]
# determine the control points
mp_make_choices(knots[0], epsilon)
pathmodule.path.__init__(self)
# build up the path
do_moveto = True
do_lineto = False
do_curveto = False
prev = None
for i, elem in enumerate(elems):
if isinstance(elem, _link):
do_moveto = False
if isinstance(elem, line):
do_lineto, do_curveto = True, False
else:
do_lineto, do_curveto = False, True
elif isinstance(elem, _knot):
if do_moveto:
self.append(pathmodule.moveto_pt(elem.x_pt, elem.y_pt))
if do_lineto:
self.append(pathmodule.lineto_pt(elem.x_pt, elem.y_pt))
elif do_curveto:
self.append(pathmodule.curveto_pt(prev.rx_pt, prev.ry_pt, elem.lx_pt, elem.ly_pt, elem.x_pt, elem.y_pt))
do_moveto = True
do_lineto = False
do_curveto = False
prev = elem
# close the path if necessary
if knots[0].ltype == mp_explicit:
elem = knots[0]
if do_lineto and is_closed:
self.append(pathmodule.closepath())
elif do_curveto:
self.append(pathmodule.curveto_pt(prev.rx_pt, prev.ry_pt, elem.lx_pt, elem.ly_pt, elem.x_pt, elem.y_pt))
if is_closed:
self.append(pathmodule.closepath())
|