resources rework completed
[PyX.git] / pyx / deformer.py
blob99b2de564c9518c91b2bb00a4029e5f31b0c3615
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 math, warnings
26 import attr, 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 warnings.warn("The connecting bezier is not uniquely determined. The simple heuristic solution may not be optimal.")
144 a = b = 1.5 * math.hypot(A[0] - B[0], A[1] - B[1])
145 else:
146 if abs(curvA) < epsilon:
147 b = D / T
148 a = - (E + b*abs(b)*curvB*0.5) / T
149 elif abs(curvB) < epsilon:
150 a = -E / T
151 b = (D - a*abs(a)*curvA*0.5) / T
152 else:
153 a, b = None, None
155 # else find a solution for the full problem
156 if a is None:
157 try:
158 # we first try to find all the zeros of the polynomials for a or b (4th order)
159 # this needs Numeric and LinearAlgebra
160 # First try the equation for a
161 coeffs = (0.125*curvA*curvA*curvB, 0, -0.5*D*curvA*curvB, T**3, T*T*E + 0.5*curvB*D*D)
162 cands = [cand for cand in realpolyroots(coeffs) if cand > 0]
164 if cands:
165 a = min(cands)
166 b = (D - 0.5*curvA*a*a) / T
167 else:
168 # then, try the equation for b
169 coeffs = (0.125*curvB*curvB*curvA, 0, 0.5*E*curvA*curvB, T**3, -T*T*D + 0.5*curvA*E*E)
170 cands = [cand for cand in realpolyroots(coeffs) if cand > 0]
171 if cands:
172 b = min(cands)
173 a = - (E + 0.5*curvB*b*b) / T
174 else:
175 a = b = None
177 except ImportError:
178 # if the Numeric modules are not available:
179 # solve the coupled system by Newton iteration
180 # 0 = Ga(a,b) = 0.5 a |a| curvA + b * T - D
181 # 0 = Gb(a,b) = 0.5 b |b| curvB + a * T + E
182 # this system is equivalent to the geometric contraints:
183 # the curvature and the normal tangent vectors
184 # at parameters 0 and 1 are to be continuous
185 # the system is solved by 2-dim Newton-Iteration
186 # (a,b)^{i+1} = (a,b)^i - (DG)^{-1} (Ga(a^i,b^i), Gb(a^i,b^i))
187 a = b = 0
188 Ga = Gb = 1
189 while max(abs(Ga),abs(Gb)) > epsilon:
190 detDG = abs(a*b) * curvA*curvB - T*T
191 invDG = [[curvB*abs(b)/detDG, -T/detDG], [-T/detDG, curvA*abs(a)/detDG]]
193 Ga = a*abs(a)*curvA*0.5 + b*T - D
194 Gb = b*abs(b)*curvB*0.5 + a*T + E
196 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
199 return (abs(a) / 3.0, abs(b) / 3.0)
200 # }}}
202 def curvecontrols_from_endpoints_pt(A, B, tangentA, tangentB, curvA, curvB, obeycurv=0, epsilon=1e-5): # {{{
203 a, b = curveparams_from_endpoints_pt(A, B, tangentA, tangentB, curvA, curvB, obeycurv=obeycurv, epsilon=epsilon)
204 # XXX respect the normalization from curveparams_from_endpoints_pt
205 return A, (A[0] + a * tangentA[0], A[1] + a * tangentA[1]), (B[0] - b * tangentB[0], B[1] - b * tangentB[1]), B
206 # }}}
209 class deformer(attr.attr):
211 def deform (self, basepath):
212 return origpath
214 class cycloid(deformer): # {{{
215 """Wraps a cycloid around a path.
217 The outcome looks like a metal spring with the originalpath as the axis.
218 radius: radius of the cycloid
219 loops: number of loops from beginning to end of the original path
220 skipfirst/skiplast: undeformed end lines of the original path
224 def __init__(self, radius=0.5*unit.t_cm, halfloops=10,
225 skipfirst=1*unit.t_cm, skiplast=1*unit.t_cm, curvesperhloop=3, sign=1, turnangle=45):
226 self.skipfirst = skipfirst
227 self.skiplast = skiplast
228 self.radius = radius
229 self.halfloops = halfloops
230 self.curvesperhloop = curvesperhloop
231 self.sign = sign
232 self.turnangle = turnangle
234 def __call__(self, radius=None, halfloops=None,
235 skipfirst=None, skiplast=None, curvesperhloop=None, sign=None, turnangle=None):
236 if radius is None:
237 radius = self.radius
238 if halfloops is None:
239 halfloops = self.halfloops
240 if skipfirst is None:
241 skipfirst = self.skipfirst
242 if skiplast is None:
243 skiplast = self.skiplast
244 if curvesperhloop is None:
245 curvesperhloop = self.curvesperhloop
246 if sign is None:
247 sign = self.sign
248 if turnangle is None:
249 turnangle = self.turnangle
251 return cycloid(radius=radius, halfloops=halfloops, skipfirst=skipfirst, skiplast=skiplast,
252 curvesperhloop=curvesperhloop, sign=sign, turnangle=turnangle)
254 def deform(self, abasepath):
255 basepath = abasepath.normpath()
257 for sp in basepath.normsubpaths:
258 if sp == basepath.normsubpaths[0]:
259 cycloidpath = self.deformsubpath(sp)
260 else:
261 cycloidpath.join(self.deformsubpath(sp))
263 return cycloidpath
265 def deformsubpath(self, subpath):
267 skipfirst = abs(unit.topt(self.skipfirst))
268 skiplast = abs(unit.topt(self.skiplast))
269 radius = abs(unit.topt(self.radius))
270 turnangle = self.turnangle * math.pi / 180.0
272 cosTurn = math.cos(turnangle)
273 sinTurn = math.sin(turnangle)
275 # make list of the lengths and parameters at points on subpath where we will add cycloid-points
276 totlength = subpath.arclen_pt()
277 if totlength <= skipfirst + skiplast + 2*radius*sinTurn:
278 warnings.warn("subpath is too short for deformation with cycloid -- skipping...")
279 return path.normpath([subpath])
281 # parametrisation is in rotation-angle around the basepath
282 # differences in length, angle ... between two basepoints
283 # and between basepoints and controlpoints
284 Dphi = math.pi / self.curvesperhloop
285 phis = [i * Dphi for i in range(self.halfloops * self.curvesperhloop + 1)]
286 DzDphi = (totlength - skipfirst - skiplast - 2*radius*sinTurn) * 1.0 / (self.halfloops * math.pi * cosTurn)
287 Dz = (totlength - skipfirst - skiplast - 2*radius*sinTurn) * 1.0 / (self.halfloops * self.curvesperhloop * cosTurn)
288 zs = [i * Dz for i in range(self.halfloops * self.curvesperhloop + 1)]
289 # from path._arctobcurve:
290 # optimal relative distance along tangent for second and third control point
291 L = 4 * radius * (1 - math.cos(Dphi/2)) / (3 * math.sin(Dphi/2))
293 # Now the transformation of z into the turned coordinate system
294 Zs = [ skipfirst + radius*sinTurn # here the coordinate z starts
295 - sinTurn*radius*math.cos(phi) + cosTurn*DzDphi*phi # the transformed z-coordinate
296 for phi in phis]
297 params = subpath._arclentoparam_pt(Zs)[0]
299 # get the positions of the splitpoints in the cycloid
300 points = []
301 for phi, param in zip(phis, params):
302 # the cycloid is a circle that is stretched along the subpath
303 # here are the points of that circle
304 basetrafo = subpath.trafo([param])[0]
306 # The point on the cycloid, in the basepath's local coordinate system
307 baseZ, baseY = 0, radius*math.sin(phi)
309 # The tangent there, also in local coords
310 tangentX = -cosTurn*radius*math.sin(phi) + sinTurn*DzDphi
311 tangentY = radius*math.cos(phi)
312 tangentZ = sinTurn*radius*math.sin(phi) + DzDphi*cosTurn
313 norm = math.sqrt(tangentX*tangentX + tangentY*tangentY + tangentZ*tangentZ)
314 tangentY, tangentZ = tangentY/norm, tangentZ/norm
316 # Respect the curvature of the basepath for the cycloid's curvature
317 # XXX this is only a heuristic, not a "true" expression for
318 # the curvature in curved coordinate systems
319 pathradius = subpath.curveradius_pt([param])[0]
320 if pathradius is not None:
321 factor = (pathradius - baseY) / pathradius
322 factor = abs(factor)
323 else:
324 factor = 1
325 l = L * factor
327 # The control points prior and after the point on the cycloid
328 preeZ, preeY = baseZ - l * tangentZ, baseY - l * tangentY
329 postZ, postY = baseZ + l * tangentZ, baseY + l * tangentY
331 # Now put everything at the proper place
332 points.append(basetrafo._apply(preeZ, self.sign * preeY) +
333 basetrafo._apply(baseZ, self.sign * baseY) +
334 basetrafo._apply(postZ, self.sign * postY))
336 if len(points) <= 1:
337 warnings.warn("subpath is too short for deformation with cycloid -- skipping...")
338 return path.normpath([subpath])
340 # Build the path from the pointlist
341 # containing (control x 2, base x 2, control x 2)
342 if skipfirst > subpath.epsilon:
343 newpath = subpath.segments([0, params[0]])[0]
344 newpath.append(path.normcurve_pt(*(points[0][2:6] + points[1][0:4])))
345 cycloidpath = path.normpath([newpath])
346 else:
347 cycloidpath = path.normpath([path.normsubpath([path.normcurve_pt(*(points[0][2:6] + points[1][0:4]))], 0)])
348 for i in range(1, len(points)-1):
349 cycloidpath.normsubpaths[-1].append(path.normcurve_pt(*(points[i][2:6] + points[i+1][0:4])))
350 if skiplast > subpath.epsilon:
351 cycloidpath.join(path.normpath(subpath.segments([params[-1], len(subpath)])))
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 = abasepath.normpath()
392 smoothpath = path.path()
394 for sp in basepath.normsubpaths:
395 smoothpath += self.deformsubpath(sp)
397 return smoothpath
399 def deformsubpath(self, normsubpath):
401 radius = unit.topt(self.radius)
403 npitems = normsubpath.normsubpathitems
404 arclens = [npitem.arclen_pt(normsubpath.epsilon) for npitem in npitems]
406 # 1. Build up a list of all relevant normsubpathitems
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 normsubpathitems
413 # the rest will queeze the radius
414 if alen > radius:
415 npitemnumbers.append(no)
416 else:
417 warnings.warn("smoothed is skipping a normsubpathitem that is too short")
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)], normsubpath.epsilon)[0]
431 elif alen > 2 * radius:
432 pars = npitem._arclentoparam_pt([radius, alen - radius], normsubpath.epsilon)[0]
433 else:
434 pars = npitem._arclentoparam_pt([0.5 * alen], normsubpath.epsilon)[0]
436 # find points, tangents and curvatures
437 ts,cs,ps = [],[],[]
438 for par in pars:
439 # XXX: there is no trafo method for normsubpathitems?
440 thetrafo = normsubpath.trafo([par + no])[0]
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.curveradius_pt([par])[0]
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]].segments([0, params[0]])[0]
463 if do_moveto:
464 smoothpath.append(path.moveto_pt(*bpart.atbegin_pt()))
465 do_moveto = 0
466 if isinstance(bpart, path.normline_pt):
467 smoothpath.append(path.lineto_pt(*bpart.atend_pt()))
468 elif isinstance(bpart, path.normcurve_pt):
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.segments(params[this] + [1])[0]
482 if do_moveto:
483 smoothpath.append(path.moveto_pt(*mpart.atbegin_pt()))
484 do_moveto = 0
485 if isinstance(mpart, path.normline_pt):
486 smoothpath.append(path.lineto_pt(*mpart.atend_pt()))
487 elif isinstance(mpart, path.normcurve_pt):
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_pt) and isinstance(nextnpitem, path.normline_pt) \
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.atend_pt(), tangents[this][-1], tangents[next][0],
495 math.hypot(points[this][-1][0] - thisnpitem.atend_pt()[0], points[this][-1][1] - thisnpitem.atend_pt()[1]),
496 math.hypot(points[next][0][0] - nextnpitem.atbegin_pt()[0], points[next][0][1] - nextnpitem.atbegin_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.atbegin()))
522 warnings.warn("The whole subpath has been smoothed away -- sorry")
523 smoothpath.append(path.closepath())
524 else:
525 epart = npitems[npitemnumbers[-1]].segments([params[-1][0], 1])[0]
526 if do_moveto:
527 smoothpath.append(path.moveto_pt(*epart.atbegin_pt()))
528 do_moveto = 0
529 if isinstance(epart, path.normline_pt):
530 smoothpath.append(path.lineto_pt(*epart.atend_pt()))
531 elif isinstance(epart, path.normcurve_pt):
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))