- some bargraph examples
[PyX/mjg.git] / pyx / deformer.py
blobe653c4c6c3ea8c739b87cd5f30f0da43db15a058
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(attr.attr):
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 = abasepath.normpath()
258 for sp in basepath.normsubpaths:
259 if sp == basepath.normsubpaths[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 newpath = subpath.split([params[0]])[0]
345 newpath.append(path.normcurve(*(points[0][2:6] + points[1][0:4])))
346 cycloidpath = path.normpath([newpath])
347 else:
348 cycloidpath = path.normpath([path.normsubpath([path.normcurve(*(points[0][2:6] + points[1][0:4]))], 0)])
349 for i in range(1, len(points)-1):
350 cycloidpath.normsubpaths[-1].append(path.normcurve(*(points[i][2:6] + points[i+1][0:4])))
351 if skiplast > subpath.epsilon:
352 cycloidpath.join(path.normpath([subpath.split([params[-1]])[-1]]))
354 # That's it
355 return cycloidpath
356 # }}}
358 class smoothed(deformer): # {{{
360 """Bends corners in a path.
362 This decorator replaces corners in a path with bezier curves. There are two cases:
363 - If the corner lies between two lines, _two_ bezier curves will be used
364 that are highly optimized to look good (their curvature is to be zero at the ends
365 and has to have zero derivative in the middle).
366 Additionally, it can controlled by the softness-parameter.
367 - If the corner lies between curves then _one_ bezier is used that is (except in some
368 special cases) uniquely determined by the tangents and curvatures at its end-points.
369 In some cases it is necessary to use only the absolute value of the curvature to avoid a
370 cusp-shaped connection of the new bezier to the old path. In this case the use of
371 "obeycurv=0" allows the sign of the curvature to switch.
372 - The radius argument gives the arclength-distance of the corner to the points where the
373 old path is cut and the beziers are inserted.
374 - Path elements that are too short (shorter than the radius) are skipped
377 def __init__(self, radius, softness=1, obeycurv=0):
378 self.radius = radius
379 self.softness = softness
380 self.obeycurv = obeycurv
382 def __call__(self, radius=None, softness=None, obeycurv=None):
383 if radius is None:
384 radius = self.radius
385 if softness is None:
386 softness = self.softness
387 if obeycurv is None:
388 obeycurv = self.obeycurv
389 return smoothed(radius=radius, softness=softness, obeycurv=obeycurv)
391 def deform(self, abasepath):
392 basepath = abasepath.normpath()
393 smoothpath = path.path()
395 for sp in basepath.normsubpaths:
396 smoothpath += self.deformsubpath(sp)
398 return smoothpath
400 def deformsubpath(self, normsubpath):
402 radius = unit.topt(self.radius)
404 npitems = normsubpath.normsubpathitems
405 arclens = [npitem.arclen_pt() for npitem in npitems]
407 # 1. Build up a list of all relevant normsubpathitems
408 # and the lengths where they will be cut (length with respect to the normsubpath)
409 npitemnumbers = []
410 cumalen = 0
411 for no in range(len(arclens)):
412 alen = arclens[no]
413 # a first selection criterion for skipping too short normsubpathitems
414 # the rest will queeze the radius
415 if alen > radius:
416 npitemnumbers.append(no)
417 else:
418 sys.stderr.write("*** PyX Warning: smoothed is skipping a normsubpathitem that is too short\n")
419 cumalen += alen
420 # XXX: what happens, if 0 or -1 is skipped and path not closed?
422 # 2. Find the parameters, points,
423 # and calculate tangents and curvatures
424 params, tangents, curvatures, points = [], [], [], []
425 for no in npitemnumbers:
426 npitem = npitems[no]
427 alen = arclens[no]
429 # find the parameter(s): either one or two
430 if no is npitemnumbers[0] and not normsubpath.closed:
431 pars = npitem._arclentoparam_pt([max(0, alen - radius)])[0]
432 elif alen > 2 * radius:
433 pars = npitem._arclentoparam_pt([radius, alen - radius])[0]
434 else:
435 pars = npitem._arclentoparam_pt([0.5 * alen])[0]
437 # find points, tangents and curvatures
438 ts,cs,ps = [],[],[]
439 for par in pars:
440 # XXX: there is no trafo method for normsubpathitems?
441 thetrafo = normsubpath.trafo(par + no)
442 p = thetrafo._apply(0,0)
443 # XXX thetrafo._apply(1,0) causes numeric instabilities in
444 # bezier_by_endpoints
445 t = thetrafo._apply(100,0)
446 ps.append(p)
447 ts.append((t[0]-p[0], t[1]-p[1]))
448 c = npitem.curvradius_pt(par)
449 if c is None: cs.append(0)
450 else: cs.append(1.0/c)
452 params.append(pars)
453 points.append(ps)
454 tangents.append(ts)
455 curvatures.append(cs)
457 # create empty path to collect pathitems
458 # this will be returned as normpath, later
459 smoothpath = path.path()
460 do_moveto = 1 # we do not know yet where to moveto
461 # 3. First part of extra handling of closed paths
462 if not normsubpath.closed:
463 bpart = npitems[npitemnumbers[0]].split(params[0])[0]
464 if do_moveto:
465 smoothpath.append(path.moveto_pt(*bpart.begin_pt()))
466 do_moveto = 0
467 if isinstance(bpart, path.normline):
468 smoothpath.append(path.lineto_pt(*bpart.end_pt()))
469 elif isinstance(bpart, path.normcurve):
470 smoothpath.append(path.curveto_pt(bpart.x1_pt, bpart.y1_pt, bpart.x2_pt, bpart.y2_pt, bpart.x3_pt, bpart.y3_pt))
471 do_moveto = 0
473 # 4. Do the splitting for the first to the last element,
474 # a closed path must be closed later
475 for i in range(len(npitemnumbers)-1+(normsubpath.closed==1)):
476 this = npitemnumbers[i]
477 next = npitemnumbers[(i+1) % len(npitemnumbers)]
478 thisnpitem, nextnpitem = npitems[this], npitems[next]
480 # split thisnpitem apart and take the middle peace
481 if len(points[this]) == 2:
482 mpart = thisnpitem.split(params[this])[1]
483 if do_moveto:
484 smoothpath.append(path.moveto_pt(*mpart.begin_pt()))
485 do_moveto = 0
486 if isinstance(mpart, path.normline):
487 smoothpath.append(path.lineto_pt(*mpart.end_pt()))
488 elif isinstance(mpart, path.normcurve):
489 smoothpath.append(path.curveto_pt(mpart.x1_pt, mpart.y1_pt, mpart.x2_pt, mpart.y2_pt, mpart.x3_pt, mpart.y3_pt))
491 # add the curve(s) replacing the corner
492 if isinstance(thisnpitem, path.normline) and isinstance(nextnpitem, path.normline) \
493 and (next-this == 1 or (this==0 and next==len(npitems)-1)):
494 d1,g1,f1,e,f2,g2,d2 = curvescontrols_from_endlines_pt(
495 thisnpitem.end_pt(), tangents[this][-1], tangents[next][0],
496 math.hypot(points[this][-1][0] - thisnpitem.end_pt()[0], points[this][-1][1] - thisnpitem.end_pt()[1]),
497 math.hypot(points[next][0][0] - nextnpitem.begin_pt()[0], points[next][0][1] - nextnpitem.begin_pt()[1]),
498 softness=self.softness)
499 if do_moveto:
500 smoothpath.append(path.moveto_pt(*d1))
501 do_moveto = 0
502 smoothpath.append(path.curveto_pt(*(g1 + f1 + e)))
503 smoothpath.append(path.curveto_pt(*(f2 + g2 + d2)))
504 #for X in [d1,g1,f1,e,f2,g2,d2]:
505 # dp.subcanvas.fill(path.circle_pt(X[0], X[1], 1.0))
506 else:
507 A,B,C,D = curvecontrols_from_endpoints_pt(
508 points[this][-1], points[next][0],
509 tangents[this][-1], tangents[next][0],
510 curvatures[this][-1], curvatures[next][0],
511 obeycurv=self.obeycurv, epsilon=normsubpath.epsilon)
512 if do_moveto:
513 smoothpath.append(path.moveto_pt(*A))
514 do_moveto = 0
515 smoothpath.append(path.curveto_pt(*(B + C + D)))
516 #for X in [A,B,C,D]:
517 # dp.subcanvas.fill(path.circle_pt(X[0], X[1], 1.0))
519 # 5. Second part of extra handling of closed paths
520 if normsubpath.closed:
521 if do_moveto:
522 smoothpath.append(path.moveto_pt(*dp.strokepath.begin()))
523 sys.stderr.write("*** PyXWarning: The whole subpath has been smoothed away -- sorry\n")
524 smoothpath.append(path.closepath())
525 else:
526 epart = npitems[npitemnumbers[-1]].split([params[-1][0]])[-1]
527 if do_moveto:
528 smoothpath.append(path.moveto_pt(*epart.begin_pt()))
529 do_moveto = 0
530 if isinstance(epart, path.normline):
531 smoothpath.append(path.lineto_pt(*epart.end_pt()))
532 elif isinstance(epart, path.normcurve):
533 smoothpath.append(path.curveto_pt(epart.x1_pt, epart.y1_pt, epart.x2_pt, epart.y2_pt, epart.x3_pt, epart.y3_pt))
535 return smoothpath
536 # }}}
538 smoothed.clear = attr.clearclass(smoothed)
540 _base = unit.v_cm
541 smoothed.SHARP = smoothed(radius=_base/math.sqrt(64))
542 smoothed.SHARp = smoothed(radius=_base/math.sqrt(32))
543 smoothed.SHArp = smoothed(radius=_base/math.sqrt(16))
544 smoothed.SHarp = smoothed(radius=_base/math.sqrt(8))
545 smoothed.Sharp = smoothed(radius=_base/math.sqrt(4))
546 smoothed.sharp = smoothed(radius=_base/math.sqrt(2))
547 smoothed.normal = smoothed(radius=_base)
548 smoothed.round = smoothed(radius=_base*math.sqrt(2))
549 smoothed.Round = smoothed(radius=_base*math.sqrt(4))
550 smoothed.ROund = smoothed(radius=_base*math.sqrt(8))
551 smoothed.ROUnd = smoothed(radius=_base*math.sqrt(16))
552 smoothed.ROUNd = smoothed(radius=_base*math.sqrt(32))
553 smoothed.ROUND = smoothed(radius=_base*math.sqrt(64))