- added new helper methods _distributeparams and _findnormpathitem to
[PyX/mjg.git] / pyx / deformer.py
blob426a2ff4c46e751c1ea5a8236830fd94a602c35f
1 #!/usr/bin/env python
2 # -*- coding: ISO-8859-1 -*-
5 # Copyright (C) 2003-2004 Michael Schindler <m-schindler@users.sourceforge.net>
6 # Copyright (C) 2002-2004 André Wobst <wobsta@users.sourceforge.net>
8 # This file is part of PyX (http://pyx.sourceforge.net/).
10 # PyX is free software; you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation; either version 2 of the License, or
13 # (at your option) any later version.
15 # PyX is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
20 # You should have received a copy of the GNU General Public License
21 # along with PyX; if not, write to the Free Software
22 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 import sys, math
26 import attr, base, canvas, color, helper, path, style, trafo, unit
28 ### helpers
30 def sign0(x):
31 return (x > 0) and 1 or (((x == 0) and 1 or 0) - 1)
32 def sign1(x):
33 return (x >= 0) and 1 or -1
35 def realpolyroots(coeffs, epsilon=1e-5): # {{{
36 # use Numeric to find the roots (via an equivalent eigenvalue problem)
37 import Numeric, LinearAlgebra
39 N = len(coeffs)
40 # build the Matrix of the polynomial problem
41 mat = Numeric.zeros((N, N), Numeric.Float)
42 for i in range(N-1):
43 mat[i+1][i] = 1
44 for i in range(N-1):
45 mat[0][i] = -coeffs[i+1]/coeffs[0]
46 # find the eigenvalues of the matrix (== the zeros of the polynomial)
47 zeros = [complex(zero) for zero in LinearAlgebra.eigenvalues(mat)]
48 # take only the real zeros
49 zeros = [zero.real for zero in zeros if -epsilon < zero.imag < epsilon]
51 ## check if the zeros are really zeros!
52 #for zero in zeros:
53 # p = 0
54 # for i in range(N):
55 # p += coeffs[i] * zero**(N-i)
56 # if abs(p) > epsilon:
57 # raise Exception("value %f instead of 0" % p)
59 return zeros
60 # }}}
62 def curvescontrols_from_endlines_pt(B, tangent1, tangent2, r1, r2, softness): # {{{
63 # calculates the parameters for two bezier curves connecting two lines (curvature=0)
64 # starting at B - r1*tangent1
65 # ending at B + r2*tangent2
67 # Takes the corner B
68 # and two tangent vectors heading to and from B
69 # and two radii r1 and r2:
70 # All arguments must be in Points
71 # Returns the seven control points of the two bezier curves:
72 # - start d1
73 # - control points g1 and f1
74 # - midpoint e
75 # - control points f2 and g2
76 # - endpoint d2
78 # make direction vectors d1: from B to A
79 # d2: from B to C
80 d1 = -tangent1[0] / math.hypot(*tangent1), -tangent1[1] / math.hypot(*tangent1)
81 d2 = tangent2[0] / math.hypot(*tangent2), tangent2[1] / math.hypot(*tangent2)
83 # 0.3192 has turned out to be the maximum softness available
84 # for straight lines ;-)
85 f = 0.3192 * softness
86 g = (15.0 * f + math.sqrt(-15.0*f*f + 24.0*f))/12.0
88 # make the control points of the two bezier curves
89 f1 = B[0] + f * r1 * d1[0], B[1] + f * r1 * d1[1]
90 f2 = B[0] + f * r2 * d2[0], B[1] + f * r2 * d2[1]
91 g1 = B[0] + g * r1 * d1[0], B[1] + g * r1 * d1[1]
92 g2 = B[0] + g * r2 * d2[0], B[1] + g * r2 * d2[1]
93 d1 = B[0] + r1 * d1[0], B[1] + r1 * d1[1]
94 d2 = B[0] + r2 * d2[0], B[1] + r2 * d2[1]
95 e = 0.5 * (f1[0] + f2[0]), 0.5 * (f1[1] + f2[1])
97 return (d1, g1, f1, e, f2, g2, d2)
98 # }}}
100 def curveparams_from_endpoints_pt(A, B, tangentA, tangentB, curvA, curvB, obeycurv=0, epsilon=1e-5): # {{{
101 # connects points A and B with a bezier curve that has
102 # prescribed tangents dirA, dirB and curvatures curA, curB at the end points.
104 # The sign of the tangent vectors is _always_ enforced. If the solution goes
105 # into the opposite direction, None will be returned.
107 # If obeycurv, the sign of the curvature will be enforced which may cause a
108 # strange solution to be found (or none at all).
110 # normalise the tangent vectors
111 # XXX we get numeric instabilities for ||dirA||==||dirB||==1
112 #norm = math.hypot(*tangentA)
113 #norm = 1
114 #dirA = (tangentA[0] / norm, tangentA[1] / norm)
115 #norm = math.hypot(*tangentB)
116 #norm = 1
117 #dirB = (tangentB[0] / norm, tangentB[1] / norm)
118 dirA = tangentA
119 dirB = tangentB
120 # some shortcuts
121 T = dirA[0] * dirB[1] - dirA[1] * dirB[0]
122 D = 3 * (dirA[0] * (B[1]-A[1]) - dirA[1] * (B[0]-A[0]))
123 E = 3 * (dirB[0] * (B[1]-A[1]) - dirB[1] * (B[0]-A[0]))
124 # the variables: \dot X(0) = a * dirA
125 # \dot X(1) = b * dirB
126 a, b = None, None
128 # we may switch the sign of the curvatures if not obeycurv
129 # XXX are these formulae correct ??
130 # XXX improve the heuristic for choosing the sign of the curvature !!
131 if not obeycurv:
132 curvA = abs(curvA) * sign1(D)
133 curvB = abs(curvB) * sign1(E)
135 # ask for some special cases where the equations decouple
136 if abs(T) < epsilon*epsilon:
137 try:
138 a = 2.0 * D / curvA
139 a = math.sqrt(abs(a)) * sign1(a)
140 b = -2.0 * E / curvB
141 b = math.sqrt(abs(b)) * sign1(b)
142 except ZeroDivisionError:
143 sys.stderr.write("*** PyX Warning: The connecting bezier is not uniquely determined. "
144 "The simple heuristic solution may not be optimal.\n")
145 a = b = 1.5 * math.hypot(A[0] - B[0], A[1] - B[1])
146 else:
147 if abs(curvA) < epsilon:
148 b = D / T
149 a = - (E + b*abs(b)*curvB*0.5) / T
150 elif abs(curvB) < epsilon:
151 a = -E / T
152 b = (D - a*abs(a)*curvA*0.5) / T
153 else:
154 a, b = None, None
156 # else find a solution for the full problem
157 if a is None:
158 try:
159 # we first try to find all the zeros of the polynomials for a or b (4th order)
160 # this needs Numeric and LinearAlgebra
161 # First try the equation for a
162 coeffs = (0.125*curvA*curvA*curvB, 0, -0.5*D*curvA*curvB, T**3, T*T*E + 0.5*curvB*D*D)
163 cands = [cand for cand in realpolyroots(coeffs) if cand > 0]
165 if cands:
166 a = min(cands)
167 b = (D - 0.5*curvA*a*a) / T
168 else:
169 # then, try the equation for b
170 coeffs = (0.125*curvB*curvB*curvA, 0, 0.5*E*curvA*curvB, T**3, -T*T*D + 0.5*curvA*E*E)
171 cands = [cand for cand in realpolyroots(coeffs) if cand > 0]
172 if cands:
173 b = min(cands)
174 a = - (E + 0.5*curvB*b*b) / T
175 else:
176 a = b = None
178 except ImportError:
179 # if the Numeric modules are not available:
180 # solve the coupled system by Newton iteration
181 # 0 = Ga(a,b) = 0.5 a |a| curvA + b * T - D
182 # 0 = Gb(a,b) = 0.5 b |b| curvB + a * T + E
183 # this system is equivalent to the geometric contraints:
184 # the curvature and the normal tangent vectors
185 # at parameters 0 and 1 are to be continuous
186 # the system is solved by 2-dim Newton-Iteration
187 # (a,b)^{i+1} = (a,b)^i - (DG)^{-1} (Ga(a^i,b^i), Gb(a^i,b^i))
188 a = b = 0
189 Ga = Gb = 1
190 while max(abs(Ga),abs(Gb)) > epsilon:
191 detDG = abs(a*b) * curvA*curvB - T*T
192 invDG = [[curvB*abs(b)/detDG, -T/detDG], [-T/detDG, curvA*abs(a)/detDG]]
194 Ga = a*abs(a)*curvA*0.5 + b*T - D
195 Gb = b*abs(b)*curvB*0.5 + a*T + E
197 a, b = a - 0.5*invDG[0][0]*Ga - 0.5*invDG[0][1]*Gb, b - 0.5*invDG[1][0]*Ga - 0.5*invDG[1][1]*Gb
200 return (abs(a) / 3.0, abs(b) / 3.0)
201 # }}}
203 def curvecontrols_from_endpoints_pt(A, B, tangentA, tangentB, curvA, curvB, obeycurv=0, epsilon=1e-5): # {{{
204 a, b = curveparams_from_endpoints_pt(A, B, tangentA, tangentB, curvA, curvB, obeycurv=obeycurv, epsilon=epsilon)
205 # XXX respect the normalization from curveparams_from_endpoints_pt
206 return A, (A[0] + a * tangentA[0], A[1] + a * tangentA[1]), (B[0] - b * tangentB[0], B[1] - b * tangentB[1]), B
207 # }}}
210 class deformer:
212 def deform (self, basepath):
213 return origpath
215 class cycloid(deformer): # {{{
216 """Wraps a cycloid around a path.
218 The outcome looks like a metal spring with the originalpath as the axis.
219 radius: radius of the cycloid
220 loops: number of loops from beginning to end of the original path
221 skipfirst/skiplast: undeformed end lines of the original path
225 def __init__(self, radius=0.5*unit.t_cm, halfloops=10,
226 skipfirst=1*unit.t_cm, skiplast=1*unit.t_cm, curvesperhloop=3, sign=1, turnangle=45):
227 self.skipfirst = skipfirst
228 self.skiplast = skiplast
229 self.radius = radius
230 self.halfloops = halfloops
231 self.curvesperhloop = curvesperhloop
232 self.sign = sign
233 self.turnangle = turnangle
235 def __call__(self, radius=None, halfloops=None,
236 skipfirst=None, skiplast=None, curvesperhloop=None, sign=None, turnangle=None):
237 if radius is None:
238 radius = self.radius
239 if halfloops is None:
240 halfloops = self.halfloops
241 if skipfirst is None:
242 skipfirst = self.skipfirst
243 if skiplast is None:
244 skiplast = self.skiplast
245 if curvesperhloop is None:
246 curvesperhloop = self.curvesperhloop
247 if sign is None:
248 sign = self.sign
249 if turnangle is None:
250 turnangle = self.turnangle
252 return cycloid(radius=radius, halfloops=halfloops, skipfirst=skipfirst, skiplast=skiplast,
253 curvesperhloop=curvesperhloop, sign=sign, turnangle=turnangle)
255 def deform(self, abasepath):
256 basepath = path.normpath(abasepath)
258 for sp in basepath.subpaths:
259 if sp == basepath.subpaths[0]:
260 cycloidpath = self.deformsubpath(sp)
261 else:
262 cycloidpath.join(self.deformsubpath(sp))
264 return cycloidpath
266 def deformsubpath(self, subpath):
268 skipfirst = abs(unit.topt(self.skipfirst))
269 skiplast = abs(unit.topt(self.skiplast))
270 radius = abs(unit.topt(self.radius))
271 turnangle = self.turnangle * math.pi / 180.0
273 cosTurn = math.cos(turnangle)
274 sinTurn = math.sin(turnangle)
276 # make list of the lengths and parameters at points on subpath where we will add cycloid-points
277 totlength = subpath.arclen_pt()
278 if totlength <= skipfirst + skiplast + 2*radius*sinTurn:
279 sys.stderr.write("*** PyX Warning: subpath is too short for deformation with cycloid -- skipping...\n")
280 return path.normpath([subpath])
282 # parametrisation is in rotation-angle around the basepath
283 # differences in length, angle ... between two basepoints
284 # and between basepoints and controlpoints
285 Dphi = math.pi / self.curvesperhloop
286 phis = [i * Dphi for i in range(self.halfloops * self.curvesperhloop + 1)]
287 DzDphi = (totlength - skipfirst - skiplast - 2*radius*sinTurn) * 1.0 / (self.halfloops * math.pi * cosTurn)
288 Dz = (totlength - skipfirst - skiplast - 2*radius*sinTurn) * 1.0 / (self.halfloops * self.curvesperhloop * cosTurn)
289 zs = [i * Dz for i in range(self.halfloops * self.curvesperhloop + 1)]
290 # from path._arctobcurve:
291 # optimal relative distance along tangent for second and third control point
292 L = 4 * radius * (1 - math.cos(Dphi/2)) / (3 * math.sin(Dphi/2))
294 # Now the transformation of z into the turned coordinate system
295 Zs = [ skipfirst + radius*sinTurn # here the coordinate z starts
296 - sinTurn*radius*math.cos(phi) + cosTurn*DzDphi*phi # the transformed z-coordinate
297 for phi in phis]
298 params = subpath._arclentoparam_pt(Zs)[0]
300 # get the positions of the splitpoints in the cycloid
301 points = []
302 for phi, param in zip(phis, params):
303 # the cycloid is a circle that is stretched along the subpath
304 # here are the points of that circle
305 basetrafo = subpath.trafo(param)
307 # The point on the cycloid, in the basepath's local coordinate system
308 baseZ, baseY = 0, radius*math.sin(phi)
310 # The tangent there, also in local coords
311 tangentX = -cosTurn*radius*math.sin(phi) + sinTurn*DzDphi
312 tangentY = radius*math.cos(phi)
313 tangentZ = sinTurn*radius*math.sin(phi) + DzDphi*cosTurn
314 norm = math.sqrt(tangentX*tangentX + tangentY*tangentY + tangentZ*tangentZ)
315 tangentY, tangentZ = tangentY/norm, tangentZ/norm
317 # Respect the curvature of the basepath for the cycloid's curvature
318 # XXX this is only a heuristic, not a "true" expression for
319 # the curvature in curved coordinate systems
320 pathradius = subpath.curvradius_pt(param)
321 if pathradius is not None:
322 factor = (pathradius - baseY) / pathradius
323 factor = abs(factor)
324 else:
325 factor = 1
326 l = L * factor
328 # The control points prior and after the point on the cycloid
329 preeZ, preeY = baseZ - l * tangentZ, baseY - l * tangentY
330 postZ, postY = baseZ + l * tangentZ, baseY + l * tangentY
332 # Now put everything at the proper place
333 points.append(basetrafo._apply(preeZ, self.sign * preeY) +
334 basetrafo._apply(baseZ, self.sign * baseY) +
335 basetrafo._apply(postZ, self.sign * postY))
337 if len(points) <= 1:
338 sys.stderr.write("*** PyX Warning: subpath is too short for deformation with cycloid -- skipping...\n")
339 return path.normpath([subpath])
341 # Build the path from the pointlist
342 # containing (control x 2, base x 2, control x 2)
343 if skipfirst > subpath.epsilon:
344 cycloidpath = path.normpath([subpath.split([params[0]])[0]])
345 cycloidpath.append(path.curveto_pt(*(points[0][4:6] + points[1][0:4])))
346 else:
347 cycloidpath = path.normpath([path.normsubpath([path.normcurve(*(points[0][2:6] + points[1][0:4]))], 0)])
348 for i in range(1, len(points)-1):
349 cycloidpath.append(path.curveto_pt(*(points[i][4:6] + points[i+1][0:4])))
350 if skiplast > subpath.epsilon:
351 cycloidpath.join(path.normpath([subpath.split([params[-1]])[-1]]))
353 # That's it
354 return cycloidpath
355 # }}}
357 class smoothed(deformer): # {{{
359 """Bends corners in a path.
361 This decorator replaces corners in a path with bezier curves. There are two cases:
362 - If the corner lies between two lines, _two_ bezier curves will be used
363 that are highly optimized to look good (their curvature is to be zero at the ends
364 and has to have zero derivative in the middle).
365 Additionally, it can controlled by the softness-parameter.
366 - If the corner lies between curves then _one_ bezier is used that is (except in some
367 special cases) uniquely determined by the tangents and curvatures at its end-points.
368 In some cases it is necessary to use only the absolute value of the curvature to avoid a
369 cusp-shaped connection of the new bezier to the old path. In this case the use of
370 "obeycurv=0" allows the sign of the curvature to switch.
371 - The radius argument gives the arclength-distance of the corner to the points where the
372 old path is cut and the beziers are inserted.
373 - Path elements that are too short (shorter than the radius) are skipped
376 def __init__(self, radius, softness=1, obeycurv=0):
377 self.radius = radius
378 self.softness = softness
379 self.obeycurv = obeycurv
381 def __call__(self, radius=None, softness=None, obeycurv=None):
382 if radius is None:
383 radius = self.radius
384 if softness is None:
385 softness = self.softness
386 if obeycurv is None:
387 obeycurv = self.obeycurv
388 return smoothed(radius=radius, softness=softness, obeycurv=obeycurv)
390 def deform(self, abasepath):
391 basepath = path.normpath(abasepath)
392 smoothpath = path.path()
394 for sp in basepath.subpaths:
395 smoothpath += self.deformsubpath(sp)
397 return smoothpath
399 def deformsubpath(self, normsubpath):
401 radius = unit.topt(self.radius)
403 npitems = normsubpath.normpathitems
404 arclens = [npitem.arclen_pt() for npitem in npitems]
406 # 1. Build up a list of all relevant normpathitems
407 # and the lengths where they will be cut (length with respect to the normsubpath)
408 npitemnumbers = []
409 cumalen = 0
410 for no in range(len(arclens)):
411 alen = arclens[no]
412 # a first selection criterion for skipping too short normpathitems
413 # the rest will queeze the radius
414 if alen > radius:
415 npitemnumbers.append(no)
416 else:
417 sys.stderr.write("*** PyX Warning: smoothed is skipping a normpathitem that is too short\n")
418 cumalen += alen
419 # XXX: what happens, if 0 or -1 is skipped and path not closed?
421 # 2. Find the parameters, points,
422 # and calculate tangents and curvatures
423 params, tangents, curvatures, points = [], [], [], []
424 for no in npitemnumbers:
425 npitem = npitems[no]
426 alen = arclens[no]
428 # find the parameter(s): either one or two
429 if no is npitemnumbers[0] and not normsubpath.closed:
430 pars = npitem._arclentoparam_pt([max(0, alen - radius)])[0]
431 elif alen > 2 * radius:
432 pars = npitem._arclentoparam_pt([radius, alen - radius])[0]
433 else:
434 pars = npitem._arclentoparam_pt([0.5 * alen])[0]
436 # find points, tangents and curvatures
437 ts,cs,ps = [],[],[]
438 for par in pars:
439 # XXX: there is no trafo method for normpathitems?
440 thetrafo = normsubpath.trafo(par + no)
441 p = thetrafo._apply(0,0)
442 # XXX thetrafo._apply(1,0) causes numeric instabilities in
443 # bezier_by_endpoints
444 t = thetrafo._apply(100,0)
445 ps.append(p)
446 ts.append((t[0]-p[0], t[1]-p[1]))
447 c = npitem.curvradius_pt(par)
448 if c is None: cs.append(0)
449 else: cs.append(1.0/c)
451 params.append(pars)
452 points.append(ps)
453 tangents.append(ts)
454 curvatures.append(cs)
456 # create empty path to collect pathitems
457 # this will be returned as normpath, later
458 smoothpath = path.path()
459 do_moveto = 1 # we do not know yet where to moveto
460 # 3. First part of extra handling of closed paths
461 if not normsubpath.closed:
462 bpart = npitems[npitemnumbers[0]].split(params[0])[0]
463 if do_moveto:
464 smoothpath.append(path.moveto_pt(*bpart.begin_pt()))
465 do_moveto = 0
466 if isinstance(bpart, path.normline):
467 smoothpath.append(path.lineto_pt(*bpart.end_pt()))
468 elif isinstance(bpart, path.normcurve):
469 smoothpath.append(path.curveto_pt(bpart.x1_pt, bpart.y1_pt, bpart.x2_pt, bpart.y2_pt, bpart.x3_pt, bpart.y3_pt))
470 do_moveto = 0
472 # 4. Do the splitting for the first to the last element,
473 # a closed path must be closed later
474 for i in range(len(npitemnumbers)-1+(normsubpath.closed==1)):
475 this = npitemnumbers[i]
476 next = npitemnumbers[(i+1) % len(npitemnumbers)]
477 thisnpitem, nextnpitem = npitems[this], npitems[next]
479 # split thisnpitem apart and take the middle peace
480 if len(points[this]) == 2:
481 mpart = thisnpitem.split(params[this])[1]
482 if do_moveto:
483 smoothpath.append(path.moveto_pt(*mpart.begin_pt()))
484 do_moveto = 0
485 if isinstance(mpart, path.normline):
486 smoothpath.append(path.lineto_pt(*mpart.end_pt()))
487 elif isinstance(mpart, path.normcurve):
488 smoothpath.append(path.curveto_pt(mpart.x1_pt, mpart.y1_pt, mpart.x2_pt, mpart.y2_pt, mpart.x3_pt, mpart.y3_pt))
490 # add the curve(s) replacing the corner
491 if isinstance(thisnpitem, path.normline) and isinstance(nextnpitem, path.normline) \
492 and (next-this == 1 or (this==0 and next==len(npitems)-1)):
493 d1,g1,f1,e,f2,g2,d2 = curvescontrols_from_endlines_pt(
494 thisnpitem.end_pt(), tangents[this][-1], tangents[next][0],
495 math.hypot(points[this][-1][0] - thisnpitem.end_pt()[0], points[this][-1][1] - thisnpitem.end_pt()[1]),
496 math.hypot(points[next][0][0] - nextnpitem.begin_pt()[0], points[next][0][1] - nextnpitem.begin_pt()[1]),
497 softness=self.softness)
498 if do_moveto:
499 smoothpath.append(path.moveto_pt(*d1))
500 do_moveto = 0
501 smoothpath.append(path.curveto_pt(*(g1 + f1 + e)))
502 smoothpath.append(path.curveto_pt(*(f2 + g2 + d2)))
503 #for X in [d1,g1,f1,e,f2,g2,d2]:
504 # dp.subcanvas.fill(path.circle_pt(X[0], X[1], 1.0))
505 else:
506 A,B,C,D = curvecontrols_from_endpoints_pt(
507 points[this][-1], points[next][0],
508 tangents[this][-1], tangents[next][0],
509 curvatures[this][-1], curvatures[next][0],
510 obeycurv=self.obeycurv, epsilon=normsubpath.epsilon)
511 if do_moveto:
512 smoothpath.append(path.moveto_pt(*A))
513 do_moveto = 0
514 smoothpath.append(path.curveto_pt(*(B + C + D)))
515 #for X in [A,B,C,D]:
516 # dp.subcanvas.fill(path.circle_pt(X[0], X[1], 1.0))
518 # 5. Second part of extra handling of closed paths
519 if normsubpath.closed:
520 if do_moveto:
521 smoothpath.append(path.moveto_pt(*dp.strokepath.begin()))
522 sys.stderr.write("*** PyXWarning: The whole subpath has been smoothed away -- sorry\n")
523 smoothpath.append(path.closepath())
524 else:
525 epart = npitems[npitemnumbers[-1]].split([params[-1][0]])[-1]
526 if do_moveto:
527 smoothpath.append(path.moveto_pt(*epart.begin_pt()))
528 do_moveto = 0
529 if isinstance(epart, path.normline):
530 smoothpath.append(path.lineto_pt(*epart.end_pt()))
531 elif isinstance(epart, path.normcurve):
532 smoothpath.append(path.curveto_pt(epart.x1_pt, epart.y1_pt, epart.x2_pt, epart.y2_pt, epart.x3_pt, epart.y3_pt))
534 return smoothpath
535 # }}}
537 smoothed.clear = attr.clearclass(smoothed)
539 _base = unit.v_cm
540 smoothed.SHARP = smoothed(radius=_base/math.sqrt(64))
541 smoothed.SHARp = smoothed(radius=_base/math.sqrt(32))
542 smoothed.SHArp = smoothed(radius=_base/math.sqrt(16))
543 smoothed.SHarp = smoothed(radius=_base/math.sqrt(8))
544 smoothed.Sharp = smoothed(radius=_base/math.sqrt(4))
545 smoothed.sharp = smoothed(radius=_base/math.sqrt(2))
546 smoothed.normal = smoothed(radius=_base)
547 smoothed.round = smoothed(radius=_base*math.sqrt(2))
548 smoothed.Round = smoothed(radius=_base*math.sqrt(4))
549 smoothed.ROund = smoothed(radius=_base*math.sqrt(8))
550 smoothed.ROUnd = smoothed(radius=_base*math.sqrt(16))
551 smoothed.ROUNd = smoothed(radius=_base*math.sqrt(32))
552 smoothed.ROUND = smoothed(radius=_base*math.sqrt(64))