Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 26 additions & 14 deletions nurbs.scad
Original file line number Diff line number Diff line change
Expand Up @@ -28,29 +28,41 @@ _BOSL2_NURBS = is_undef(_BOSL2_STD) && (is_undef(BOSL2_NO_STD_WARNING) || !BOSL2
// pts = nurbs_curve(control, degree, splinesteps, [mult=], [weights=], [type=], [knots=]);
// pts = nurbs_curve(control, degree, u=, [mult=], [weights=], [type=], [knots=]);
// Description:
// Compute the points specified by a NURBS curve. You specify the NURBS by supplying the control points, knots and weights. and knots.
// Compute the points specified by a NURBS curve. You specify the NURBS by supplying the control points, knots and weights.
// Only the control points are required. The knots and weights default to uniform, in which case you get a uniform B-spline.
// The length of `weights`, if given, must match the length of `control`.
// You can specify endpoint behavior using the `type` parameter. The default, "clamped", gives a curve which starts and
// ends at the first and last control points and moves in the tangent direction to the first and last control point segments.
// If you request an "open" spline you get a curve which starts somewhere in the middle of the control points.
// Finally, a "closed" curve is a one that starts where it ends. Note that each of these types of curve require
// a different number of knots.
// A "closed" curve is a one that starts where it ends. An "open" spline is a generic curve that starts somewhere
// in the middle of the control points. The "open" curve is less common; you only need this if you are managing the
// knots and control points yourself to create your own clamped or closed curve, so avoid this type unless you know what you're doing.
// Each of these types of curve require a different number of knots as described below.
// .
// The control points are the most important control over the shape
// of the curve. You must have at least p+1 control points for clamped and open NURBS. Unlike a bezier, there is no maximum
// of the curve. You must have at least degree+1 control points for clamped and open NURBS. Don't confuse the degree of a
// NURBS with its *order*: the order of a NURBS, often called $p$, is degree+1. Unlike a bezier, there is no maximum
// number of control points. A single NURBS is more like a bezier **path** than like a single bezier spline.
// .
// A NURBS or B-spline is a curve made from a moving average of several Bezier curves. The knots specify when one Bezier fades
// away to be replaced by the next one. At generic points, the curves are differentiable, but by increasing knot multiplicity, you
// can decrease smoothness, or even produce a sharp corner. The knots must be an ascending sequence of values, but repeating values
// is OK and controls the smoothness at the knots. The easiest way to specify the knots is to take the default of uniform knots,
// and simply set the multiplicity to create repeated knots as needed. The total number of knots is then the sum of the multiplicity
// vector. Alternatively you can simply list the knots yourself. Note that regardless of knot values, the domain of evaluation
// away to be replaced by the next one. The knot list is a non-increasing list of values that you specify using two parameters,
// `knots` and `mult`. In practice changing the knot values doesn't have a strong effect on the curve, so it usually suffices
// to use a uniform knot vector, which is the default. The major exception to this is repeated knot values.
// At generic points in the NURBS, the curve is infinitely differentiable, but at a point that
// corresponds to a knot, a NURBS with degree $d$ will have a $(d-1)$th derivative that is continuous.
// However, if a value repeats in the knot vector that creates a knot with a multiplicity larger than 1, and each
// repetition decreases the smoothness of the curve at the corresponding NURBS point by 1. This means that
// if the multiplicity equals the degree then the curve has a corner at the knot point. Using the `mult` parameter
// without giving `knots` allows you to give a vector of multiplicities, which produces a knot vector that is uniform
// except it has some repeated knots. A value of 1 in the `mult` vector means the knot is not repeated; a value of 2 means it is
// repeated twice. The multiplicity can be as large as the degree but no larger. (A special exception is at the ends for open
// NURBS.) When you specify the multiplicity vector the total number of knots is the sum of that vector. You can also list
// the knots explicitly yourself. The knot values you give can cover any range; they will be scaled to correspond properly
// to the NURBS parameter space: rgardless of the knot values you give, the domain of evaluation
// for u is always the interval [0,1], and it will be scaled to give the entire valid portion of the curve you have chosen.
// If you give both a knot vector and multiplicity then the multiplicity vector is appled to the provided knots.
// For an open spline the number of knots must be `len(control)+p+1`. For a clamped spline the number of knots is `len(control)-p+1`,
// .
// For an open spline the number of knots must be `len(control)+degree+1`. For a clamped spline the number of knots is `len(control)-degree+1`,
// and for a closed spline you need `len(control)+1` knots. If you are using the default uniform knots then the way to
// ensure that you have the right number is to check that `sum(mult)` is either not set or equal to the correct value.
// ensure that you have the right number is to check that mult is not set or `sum(mult)` equals the correct value.
// .
// You can use this function to evaluate the NURBS at `u`, which can be a single point or a list of points. You can also
// use it to evaluate the NURBS over its entire domain by giving a splinesteps value. This specifies the number of segments
Expand Down Expand Up @@ -236,7 +248,7 @@ function nurbs_curve(control,degree,splinesteps,u, mult,weights,type="clamped",
: uniform ? [for(i=idx(mult)) each repeat(i/(len(mult)-1),mult[i])]
: let(
xknots = is_undef(mult)? knots
: assert(len(mult) == len(knots), "If knot vector and mult vector must be the same length")
: assert(len(mult) == len(knots), "Knot vector and mult vector must be the same length")
[for(i=idx(mult)) each repeat(knots[i], mult[i])]
)
type=="open" ? assert(len(xknots)==len(control)+degree+1, str("For open spline, knot vector with multiplicity must have length ",
Expand Down
54 changes: 29 additions & 25 deletions rounding.scad
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,7 @@ function _rounding_offsets(edgespec,z_dir=1) =
// For `method="edges"` (default), every segment (edge) of the path is replaced by a cubic curve with `splinesteps`
// points, and the cubic interpolation passes through every input point on the path, matching the tangents at every
// point. If you do not specify `tangents`, they are computed using {{path_tangents()}} with `uniform=false` by
// default. Only the dirction of a tangent vector matters, not the vector length.
// default. Only the direction of a tangent vector matters, not the vector length.
// Setting `uniform=true` with non-uniform sampling may be desirable in some cases but tends to
// produces curves that overshoot the point on the path.
// .
Expand All @@ -621,17 +621,21 @@ function _rounding_offsets(edgespec,z_dir=1) =
// are tangent to the midpoint of every segment. The `tangents` and `uniform` parameters don't apply to the
// "corners" method. Using either one with "corners" causes an error.
// .
// The `size` or `relsize` parameters apply to both methods. They determine how far the curve can bend away
// from the input path. In the case where the path has three non-collinear points, the size specifies the
// exact distance between the specified path and the curve (maximum distance from edge if for the "edges"
// method, or distance from corner with the "corners" method).
// In 2D when the spline may make an S-curve, for the "edges" method the size parameter specifies the sum
// of the deviations of the two peaks of the curve. In 3-space the bezier curve may have three extrema: two
// maxima and one minimum. In this case the size specifies the sum of the maxima minus the minimum.
// If the input is collinear, including the case of a 2 point path, then the output is the same as the input
// for the "corners" method and for the "edges" method the output is also the same as the input unless you specify
// tangents that are not aligned with the input path.
// .
// If you give `relsize` instead, then for the "edges" method, the maximum deviation from the segment is
// relative to the segment length (e.g. 0.05 means 5% of the segment length). For the "corners" method,
// `relsize` determines where the curve intersects the corner bisector, relative to the maximum deviation
// The `size` and `relsize` parameters apply to both methods. They determine how far the curve can deviate
// from the input path. With the "edges" method each segment is replaced by a bezier. Three things can happen.
// The bezier may bulge to one side or the other of the input segemnt. In this case `size` specifies the exact (maximum) distance
// between the path segment and the output curve. Another possibility is that the spline makes an S-curve.
// In this case, `size` specifies the sum of the deviations at the two peaks of the curve. In 3-space the bezier
// curve may have three extrema: two maxima and one minimum. In this case `size` specifies the sum of the maxima minus the minimum.
// If you give `relsize` instead then `size` is set for each segment of the path relative to the length of that segment,
// so if a segment has length 10 then `relsize=0.05` would choose `size=0.5` for that segment.
// .
// For the "corners" method, `size` specifies the maximum distance from the corner of the original path to the bezier that replaces the corner,
// and `relsize` determines where the curve intersects the corner bisector, relative to the maximum deviation
// possible (which corresponds to a circle rounding from the shortest leg of the corner). For example,
// `relsize=1` is the maximum deviation from the corner (a circle arc from the shortest leg), and `relsize=0.5`
// causes the curve to intersect the corner bisector halfway between that maximum and the tip of the corner.
Expand Down Expand Up @@ -737,21 +741,21 @@ function _rounding_offsets(edgespec,z_dir=1) =
// color("red")move_copies(pts)circle(r=.15,$fn=12);
module smooth_path(path, tangents, size, relsize, method="edges", splinesteps=10, uniform, closed=false) {no_module();}
function smooth_path(path, tangents, size, relsize, method="edges", splinesteps=10, uniform, closed) =
is_1region(path)
? smooth_path(path[0], tangents, size, relsize, method, splinesteps, uniform, default(closed,true))
: assert(method=="edges" || method=="corners", "method must be \"edges\" or \"corners\".")
assert(method=="edges" || (is_undef(tangents) && is_undef(uniform)), "The tangents and uniform parameters are incompatible with method=\"corners\".")
let (
uniform = default(uniform,false),
bez = method=="edges"
? path_to_bezpath(path, tangents=tangents, size=size, relsize=relsize, uniform=uniform, closed=default(closed,false))
: path_to_bezcornerpath(path, size=size, relsize=relsize, closed=default(closed,false)),
smoothed = bezpath_curve(bez,splinesteps=splinesteps)
)
closed ? list_unwrap(smoothed) : smoothed;


is_1region(path)
? smooth_path(path[0], tangents, size, relsize, method, splinesteps, uniform, default(closed,true))
: assert(method=="edges" || method=="corners", "method must be \"edges\" or \"corners\".")
assert(method=="edges" || (is_undef(tangents) && is_undef(uniform)), "The tangents and uniform parameters are incompatible with method=\"corners\".")
method=="corners" && len(path)==2 ? path
: let(
uniform = default(uniform,false),
bez = method=="edges"
? path_to_bezpath(path, tangents=tangents, size=size, relsize=relsize, uniform=uniform, closed=default(closed,false))
: path_to_bezcornerpath(path, size=size, relsize=relsize, closed=default(closed,false)),
smoothed = bezpath_curve(bez,splinesteps=splinesteps)
)
closed ? list_unwrap(smoothed) : smoothed;


function _scalar_to_vector(value,length,varname) =
is_vector(value)
? assert(len(value)==length, str(varname," must be length ",length))
Expand Down