From 535646c01b6fde10429dd498e413dcb3221f816a Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Mon, 14 Oct 2024 16:11:19 -0400 Subject: [PATCH 01/10] fix rounded_prism bug --- rounding.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rounding.scad b/rounding.scad index be31892..fa7dca8 100644 --- a/rounding.scad +++ b/rounding.scad @@ -3722,7 +3722,7 @@ function _prism_fillet_cyl(name, R, bot, top, d, k, N, overlap, uniform, debug) d_step = abs(d)*unit(top[i]-isect[i])+(uniform?isect[i]:corner) ) assert(is_vector(corner,3),str("Fillet does not fit. Decrease size of fillet (",name,").")) - assert(debug || R<0 || (d_step-corner)*(corner-isect[i])>=0, + assert(debug || R<0 || (d_step-corner)*(corner-isect[i])>=-EPSILON, str("Unable to fit fillet, probably due to steep curvature of the cylinder (",name,").")) let( bez = _smooth_bez_fill([d_step,corner,edgepoint], k) From 0088ce9776cfce8b713cf2e0d9a9e2a689a01dce Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Mon, 28 Oct 2024 17:45:51 -0400 Subject: [PATCH 02/10] typo doc fix --- shapes3d.scad | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shapes3d.scad b/shapes3d.scad index 9fe181a..bfaf459 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -3353,7 +3353,7 @@ function onion(r, ang=45, cap_h, d, anchor=CENTER, spin=0, orient=UP) = // orient = Vector to rotate top towards. See [orient](attachments.scad#subsection-orient). Default: `UP` // Anchor Types: // baseline = Anchor center is relative to text baseline -// ycenter = Anchor center is relative to the actualy y direction center of the text +// ycenter = Anchor center is relative to the actual y direction center of the text // Examples: // text3d("Fogmobar", h=3, size=10); // text3d("Fogmobar", h=2, size=12, font=":style=bold"); From f9e707b33aa3fab9809d2b13dcd3ca2431e92cd7 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Wed, 30 Oct 2024 17:14:02 -0400 Subject: [PATCH 03/10] minor doc fix --- attachments.scad | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/attachments.scad b/attachments.scad index 795f5c8..df63d7a 100644 --- a/attachments.scad +++ b/attachments.scad @@ -2686,9 +2686,9 @@ module corner_profile(corners=CORNERS_ALL, except=[], r, d, convexity=10) { // for the anchor points referred to by `from` and `to` are fetched, // which will include position, direction, and spin. With that info, // the following transformations are performed: -// * Translates this part so it's anchor position matches the parent's anchor position. -// * Rotates this part so it's anchor direction vector exactly opposes the parent's anchor direction vector. -// * Rotates this part so it's anchor spin matches the parent's anchor spin. +// * Translates this part so its anchor position matches the parent's anchor position. +// * Rotates this part so its anchor direction vector exactly opposes the parent's anchor direction vector. +// * Rotates this part so its anchor spin matches the parent's anchor spin. // . // In addition to handling positioning of the attachable object, // this module is also responsible for handing coloring of objects with {{recolor()}} and {{color_this()}}, and From aa8b06c346172db4932b2528d678251ab0dc516c Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sun, 3 Nov 2024 07:10:47 -0500 Subject: [PATCH 04/10] add nurbs --- nurbs.scad | 576 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 576 insertions(+) create mode 100644 nurbs.scad diff --git a/nurbs.scad b/nurbs.scad new file mode 100644 index 0000000..48db015 --- /dev/null +++ b/nurbs.scad @@ -0,0 +1,576 @@ +///////////////////////////////////////////////////////////////////// +// LibFile: beziers.scad +// B-Splines and Non-uniform Rational B-Splines (NURBS) are a way to represent smooth curves and smoothly curving +// surfaces with a set of control points. The curve or surface is defined by +// the control points and a set of "knot" points. The NURBS can be "clamped" in which case the curve passes through +// the first and last point, or they can be "closed" in which case the first and last point are coincident. Also possible +// are "open" curves which do not necessarily pass through any of their control points. Unlike Bezier curves, a NURBS +// can have an unlimited number of control points and changes to the control points only affect the curve locally. +// +// Includes: +// include +// include +// FileGroup: Advanced Modeling +// FileSummary: NURBS and B-spline curves and surfaces. +////////////////////////////////////////////////////////////////////// + +include +include + +// Section: NURBS Curves + +// Function: nurbs_curve() +// Synopsis: Computes one more more points on a NURBS curve. +// SynTags: Path +// Topics: NURBS Curves +// See Also: debug_nurbs() +// Usage: +// 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. +// Only the control points are required. The knots and weights default to uniform, in which case you get a uniform B-spline. +// 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. +// . +// 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 +// 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 +// 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`, +// 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. +// . +// 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 +// to use between each knot and guarantees a point exactly at each knot. This may be important if you set the knot multiplicity +// to the degree somewhere in your curve, which creates a corner at the knot, because it guarantees a sharp corner regardless +// of the number of points. +// Arguments: +// control = list of control points in any dimension +// degree = degree of NURBS +// splinesteps = evaluate whole spline with this number of segments between each pair of knots +// --- +// u = list of values or range in the interval [0,1] where the NURBS should be evaluated +// mult = list of multiplicities of the knots. Default: all 1 +// weights = vector whose length is the same as control giving weights at each control point. Default: all 1 +// type = One of "clamped", "closed" or "open" to define end point handling of the spline. Default: "clamped" +// knots = List of knot values. Default: uniform +// Example(2D,NoAxes): Simple quadratic uniform clamped b-spline with some points computed using splinesteps. +// pts = [[13,43],[30,52],[49,22],[24,3]]; +// debug_nurbs(pts,2); +// npts = nurbs_curve(pts, 2, splinesteps=3); +// color("red")move_copies(npts) circle(r=1); +// Example(2D,NoAxes): Simple quadratic uniform clamped b-spline with some points computed using the u parameter. Note that a uniform u parameter doesn't necessarily sample the curve uniformly. +// pts = [[13,43],[30,52],[49,22],[24,3]]; +// debug_nurbs(pts,2); +// npts = nurbs_curve(pts, 2, u=[0:.2:1]); +// color("red")move_copies(npts) circle(r=1); +// Example(2D,NoAxes): Same control points, but cubic +// pts = [[13,43],[30,52],[49,22],[24,3]]; +// debug_nurbs(pts,3); +// Example(2D,NoAxes): Same control points, quadratic and closed +// pts = [[13,43],[30,52],[49,22],[24,3]]; +// debug_nurbs(pts,2,type="closed"); +// Example(2D,NoAxes): Same control points, cubic and closed +// pts = [[13,43],[30,52],[49,22],[24,3]]; +// debug_nurbs(pts,3,type="closed"); +// Example(2D,NoAxes): Ten control points, quadratic, clamped +// pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; +// debug_nurbs(pts,2); +// Example(2D,NoAxes): Same thing, degree 4 +// pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; +// debug_nurbs(pts,4); +// Example(2D,NoAxes): Same control points, degree 2, open. Note it doesn't reach the ends +// pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; +// debug_nurbs(pts,2, type="open"); +// Example(2D,NoAxes): Same control points, degree 4, open. Note it starts farther from the ends +// pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; +// debug_nurbs(pts,4,type="open"); +// Example(2D,NoAxes): Same control points, degree 2, closed +// pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; +// debug_nurbs(pts,2,type="closed"); +// Example(2D,NoAxes): Same control points, degree 4, closed +// pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; +// debug_nurbs(pts,4,type="closed"); +// Example(2D,NoAxes): Adding weights +// pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; +// weights = [1,1,1,3,1,1,3,1,1,1]; +// debug_nurbs(pts,4,type="clamped",weights=weights); +// Example(2D,NoAxes): Using knot multiplicity with quadratic clamped case. Knot count is len(control)-degree+1 = 9. The multiplicity 2 knot creates a corner for a quadratic. +// pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; +// mult = [1,1,1,2,1,1,1,1]; +// debug_nurbs(pts,2,mult=mult,show_knots=true); +// Example(2D,NoAxes): Using knot multiplicity with quadratic clamped case. Two knots of multiplicity 2 gives two corners +// pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; +// mult = [1,1,1,2,2,1,1]; +// debug_nurbs(pts,2,mult=mult,show_knots=true); +// Example(2D,NoAxes): Using knot multiplicity with cubic clamped case. Knot count is now 8. We need multiplicity equal to degree (3) to create a corner. +// pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; +// mult = [1,3,1,1,1,1]; +// debug_nurbs(pts,3,mult=mult,show_knots=true); +// Example(2D,NoAxes): Using knot multiplicity with cubic closed case. Knot count is now len(control)+1=11. Here are three corners. +// pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; +// mult = [1,3,1,3,3]; +// debug_nurbs(pts,3,mult=mult,type="closed",show_knots=true); +// Example(2D,NoAxes): Explicitly specified knots only change the quadratic clamped curve slightly. Knot count is len(control)-degree+1 = 9. +// pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; +// knots = [0,1,3,5,9,13,14,19,21]; +// debug_nurbs(pts,2); +// Example(2D,NoAxes): Combining explicit knots with mult for the quadratic curve to add a corner +// pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; +// knots = [0,1,3,9,13,14,19,21]; +// mult = [1,1,1,2,1,1,1,1]; +// debug_nurbs(pts,2,knots=knots,mult=mult); +// Example(2D,NoAxes): Directly repeating a knot in the knot list to create a corner for a cubic spline +// pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; +// knots = [0,1,3,13,13,13,19,21]; +// debug_nurbs(pts,3,knots=knots); +// Example(2D,NoAxes): Open cubic spline with explicit knots +// pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; +// knots = [0,1,3,13,13,13,19,21,27,28,29,40,42,44]; +// debug_nurbs(pts,3,knots=knots,type="open"); +// Example(2D,NoAxes): Closed quintic spline with explicit knots +// pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; +// knots = [0,1,3,13,13,13,19,21,27,28,33]; +// debug_nurbs(pts,5,knots=knots,type="closed"); +// Example(2D,NoAxes): Closed quintic spline with explicit knots and weights +// pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; +// weights = [1,2,3,4,5,6,7,6,5,4]; +// knots = [0,1,3,13,13,13,19,21,27,28,33]; +// debug_nurbs(pts,5,knots=knots,weights=weights,type="closed"); +// Example(2D,NoAxes): Circular arcs are possible with NURBS. This example gives a semi-circle +// control = [[1,0],[1,2],[-1,2],[-1,0]]; +// w = [1,1/3,1/3,1]; +// debug_nurbs(control, 3, weights=w, width=0.1, size=.2); +// Example(2D,NoAxes): Gluing two semi-circles together gives a whole circle. Note that this is a clamped not closed NURBS. The interface uses a knot of multiplicity 3 where the clamped ends of the semi-circles meet. +// control = [[1,0],[1,2],[-1,2],[-1,0],[-1,-2],[1,-2],[1,0]]; +// w = [1,1/3,1/3,1,1/3,1/3,1]; +// debug_nurbs(control, 3, splinesteps=16,weights=w,mult=[1,3,1],width=.1,size=.2); +// Example(2D,NoAxes): Circle constructed with type="closed" +// control = [[1,0],[1,2],[-1,2],[-1,0],[-1,-2],[1,-2]]; +// w = [1,1/3,1/3,1,1/3,1/3]; +// debug_nurbs(control, 3, splinesteps=16,weights=w,mult=[1,3,3],width=.1,size=.2,type="closed",show_knots=true); + +function nurbs_curve(control,degree,splinesteps,u, mult,weights,type="clamped",knots) = + assert(num_defined([splinesteps,u])==1, "Must define exactly one of u and splinesteps") + is_finite(u) ? nurbs_curve(control,degree,[u],mult,weights,type=type)[0] + : assert(is_undef(splinesteps) || (is_int(splinesteps) || splinesteps>0), "splinesteps must be a positive integer") + let(u=is_range(u) ? list(u) : u,f=echo(u=u)) + assert(is_undef(u) || (is_vector(u) && min(u)>=0 && max(u)<=1), "u must be a list of points on the interval [0,1] or a range contained in that interval") + is_def(weights) ? assert(is_vector(weights, len(control)), "Weights should be a vector whose length is the number of control points") + let( + dim = len(control[0]), + control = [for(i=idx(control)) [each control[i]*weights[i],weights[i]]], + curve = nurbs_curve(control,degree,u=u,splinesteps=splinesteps, mult=mult,type=type) + ) + [for(pt=curve) select(pt,0,-2)/last(pt)] + : + let( + uniform = is_undef(knots), + dum=assert(in_list(type, ["closed","open","clamped"]), str("Unknown nurbs spline type", type)) + assert(type=="closed" || len(control)>=degree+1, str(type," nurbs requires at least degree+1 control points")) + assert(is_undef(mult) || is_vector(mult), "mult must be a vector"), + badmult = is_undef(mult) ? [] + : [for(i=idx(mult)) if (!( + is_int(mult[i]) + && mult[i]>0 + && (mult[i]<=degree + || (type!="closed" + && mult[i]==degree+1 + && (i==0 || i==len(mult)-1) + ) + ) + )) i], + dummy0 = assert(badmult==[], str("mult vector should contain positive integers no larger than the degree, except at ends of open splines, ", + "where degree+1 is allowed. The mult vector has bad values at indices: ",badmult)) + assert(is_undef(knots) || is_undef(mult) || len(mult)==len(knots), "If both mult and knots are given they must be vectors of the same length") + assert(is_undef(mult) || type!="clamped" || sum(mult)==len(control)-degree+1, + str("For ",type," spline knot count (sum of multiplicity vector) must be ",len(control)-degree+1," but is instead ",mult?sum(mult):0)) + assert(is_undef(mult) || type!="closed" || sum(mult)==len(control)+1, + str("For closed spline knot count (sum of multiplicity vector) must be ",len(control)+1," but is instead ",mult?sum(mult):0)) + assert(is_undef(mult) || type!="open" || sum(mult)==len(control)+degree+1, + str("For closed spline knot count (sum of multiplicity vector) must be ",len(control)+degree+1," but is instead ",mult?sum(mult):0)), + control = type=="open" ? control + : type=="clamped" ? control //concat(repeat(control[0], degree),control, repeat(last(control),degree)) + : /*type=="closed"*/ concat(control, select(control,count(degree))), + // n = len(control)-1, // number of control pts -1 + // m = n+p+1, // number of knots - 1 + mult = !uniform ? mult + : type=="clamped" ? assert(is_undef(mult) || mult[0]==1 && last(mult)==1,"For clamped b-splines, first and last multiplicity must be 1") + [degree+1,each slice(default(mult, repeat(1,len(control)-degree+1)),1,-2),degree+1] + : is_undef(mult) ? repeat(1,len(control)+degree+1) + : type=="open" ? mult + : /* type=="closed" */ + let( // Closed spline requires that we identify first and last knots and then step at same + // interval spacing periodically through the knot vector. This means we pick up the first + // multiplicity minus 1 and have to add it to the last multiplicity. + lastmult = last(mult)+mult[0]-1, + dummy=assert(lastmult<=degree, "For closed spline, first and last knot multiplicity cannot total more than the degree+1"), + adjlast = [ + each select(mult,0,-2), + lastmult + ] + ) + _extend_knot_mult(adjlast,1,len(control)+degree+1), + knot = uniform && is_undef(mult) ? lerpn(0,1,len(control)+degree+1) + : 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") + [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 ", + len(control)+degree+1," but has length ", len(xknots))) + xknots + : type=="clamped" ? assert(len(xknots) == len(control)+1-degree, str("For clamped spline, knot vector with multiplicity must have length ", + len(control)+1-degree," but has length ", len(xknots))) + assert(xknots[0]!=xknots[1] && last(xknots)!=select(xknots,-2), + "For clamped splint, first and last knots cannot repeat (must have multiplicity one") + concat(repeat(xknots[0],degree), xknots, repeat(last(xknots),degree)) + : /*type=="closed"*/ assert(len(xknots) == len(control)+1-degree, str("For closed spline, knot vector (including multiplicity) must have length ", + len(control)+1-degree," but has length ", len(xknots),control)) + let(gmult=_calc_mult(xknots)) + assert(gmult[0]+last(gmult)<=degree+1, "For closed spline, first and last knot multiplicity together cannot total more than the degree+1") + _extend_knot_vector(xknots,0,len(control)+degree+1), + bound = type=="clamped" ? undef + : [knot[degree], knot[len(control)]], + adjusted_u = !is_undef(splinesteps) ? + [for(i=[degree:1:len(control)-1]) + each + if (knot[i]!=knot[i+1]) + lerpn(knot[i],knot[i+1],splinesteps, endpoint=false), + if (type!="closed") knot[len(control)] + ] + : is_undef(bound) ? u + : add_scalar((bound[1]-bound[0])*u,bound[0]) + ) + uniform? + let( + msum = cumsum(mult), + ) + [for(uval=adjusted_u) + let( + mind = floor(uval*(len(mult)-1)), + knotidxR=msum[mind]-1, + knotidx = knotidxR=len(kmult)-1 || kind+kmult[kmultind]>=len(control)))) ? kind-kmult[kmultind-1] + : (uind=knot[kind] && adjusted_u[uind]>=knot[kind] && adjusted_u[uind]=knot[kind+kmult[kmultind]], + kind = inc_k ? kind+kmult[kmultind] : kind, + kmultind = inc_k ? kmultind+1 : kmultind + ) + if (is_def(output)) output] + ) + [for(i=idx(adjusted_u)) + bspline_pt_recurse(knot,select(control, knotidx[i]-degree,knotidx[i]), adjusted_u[i], 1, degree, 0, degree, knotidx[i]) + ]; + + +function bspline_pt_recurse(knot, control, u, r, h,s,p,k) = + r>h ? control[0] + : + let( + ctrl_new = [for(i=[k-p+r:1:k-s]) + let( + alpha = (u-knot[i]) / (knot[i+p-r+1]-knot[i]) + ) + (1-alpha) * control[i-1-(k-p)-r+1] + alpha*control[i-(k-p)-r+1] + ] + ) + bspline_pt_recurse(knot,ctrl_new,u,r+1,h,s,p,k); + + +function _extend_knot_mult(mult, next, len) = + let(total = sum(mult)) + total == len ? mult + : total>len ? [ each select(mult,0,-2), last(mult)-(total-len) ] + : _extend_knot_mult([each mult,mult[next]], next+1, len); + +function _extend_knot_vector(knots,next,len) = + len(knots)==len ? knots + : _extend_knot_vector([each knots, last(knots)+knots[next+1]-knots[next]], next+1, len); + + +function _calc_mult(knots) = + let( + ind=[ 0, + for(i=[1:len(knots)-1]) + if (knots[i]!=knots[i-1]) i, + len(knots) + ] + ) + deltas(ind); + + + +// Module: debug_nurbs() +// Synopsis: Shows a NURBS curve and its control points, knots and weights +// SynTags: Geom +// Topics: NURBS, Debugging +// See Also: nurbs_curve() +// Usage: +// debug_nurbs(control, degree, [width], [splinesteps=], [type=], [mult=], [knots=], [size=], [show_weights=], [show_knots=], [show_idx=]); +// Description: +// Displays a 2D or 3D NURBS and the associated control points to help debug NURBS curves. You can display the +// control point indices and weights, and can also display the knot points. +// Arguments: +// control = control points for NURBS +// degree = degree of NURBS +// splinesteps = number of segments between each pair of knots. Default: 16 +// width = width of the line. Default: 1 +// size = size of text annotations. Default: 3 times the width +// mult = multiplicity vector for NURBS +// weights = weight vector for NURBS +// type = NURBS type, one of "clamped", "open" or "closed". Default: "clamped" +// show_index = if true then display index of each control point vertex. Default: true +// show_weights = if true then display any non-unity weights. Default: true if weights vector is supplied, false otherwise +// show_knots = If true then show the knots on the spline curve. Default: false + + +module debug_nurbs(control,degree,splinesteps=16,width=1, size, mult,weights,type="clamped",knots, show_weights, show_knots=false, show_index=true) +{ + $fn=8; + size = default(size, 3*width); + show_weights = default(show_weights, is_def(weights)); + N=len(control); + twodim = len(control[0])==2; + curve = nurbs_curve(control=control,degree=degree,splinesteps=splinesteps, mult=mult,weights=weights, type=type, knots=knots); + stroke(curve, width=width, closed=type=="closed");//, color="green"); + stroke(control, width=width/2, color="lightblue", closed=type=="closed"); + if (show_knots){ + knotpts = nurbs_curve(control=control, degree=degree, splinesteps=1, mult=mult, weights=weights, type=type, knots=knots); + color("green") + move_copies(knotpts) + if (twodim)circle(r=width); + else sphere(r=width); + } + color("blue") + if (show_index) + move_copies(control){ + let(label = str($idx), + anch = show_weights && is_def(weights[$idx]) && weights[$idx]!=1 ? FWD : CENTER) + if (twodim) text(text=label, size=size, anchor=anch); + else rot($vpr) text3d(text=label, size=size, anchor=anch); + } + color("blue") + if ( show_weights) + move_copies(control){ + if(is_def(weights[$idx]) && weights[$idx]!=1) + let(label = str("w=",weights[$idx]), + anch = show_index ? BACK : CENTER + ) + if (twodim) fwd(size/2*0)text(text=label, size=size, anchor=anch); + else rot($vpr) text3d(text=label, size=size, anchor=anch); + } + +} + + +// Section: NURBS Surfaces + + +// Function: is_nurbs_patch() +// Synopsis: Returns true if the given item looks like a NURBS patch. +// Topics: NURBS Patches, Type Checking +// Usage: +// bool = is_nurbs_patch(x); +// Description: +// Returns true if the given item looks like a NURBS patch. (a 2D array of 3D points.) +// Arguments: +// x = The value to check the type of. +function is_nurbs_patch(x) = + is_list(x) && is_list(x[0]) && is_vector(x[0][0]) && len(x[0]) == len(x[len(x)-1]); + + + +// Function: nurbs_patch_points() +// Synopsis: Computes specifies point(s) on a NURBS surface patch +// Topics: NURBS Patches +// See Also: nurbs_vnf(), nurbs_curve() +// Usage: +// pointgrid = nurbs_patch_points(patch, degree, [splinesteps], [u=], [v=], [weights=], [type=], [mult=], [knots=]); +// Description: +// Sample a NURBS patch on a point set. If you give splinesteps then it will sampled uniformly in the spline +// parameter between the knots, ensuring that a sample appears at every knot. If you instead give u and v then +// the values at those points in parameter space will be returned. The various NURBS parameters can all be +// single values, if the NURBS has the same parameters in both directions, or pairs listing the value for the +// two directions. +// Arguments: +// patch = rectangular list of control points in any dimension +// degree = a scalar or 2-vector giving the degree of the NURBS in the two directions +// splinesteps = a scalar or 2-vector giving the number of segments between each knot in the two directions +// --- +// u = evaluation points in the u direction of the patch +// v = evaluation points in the v direction of the patch +// mult = a single list or pair of lists giving the knot multiplicity in the two directions. Default: all 1 +// knots = a single list of pair of lists giving the knot vector in each of the two directions. Default: uniform +// weights = a single list or pair of lists giving the weight at each control point in the patch. Default: all 1 +// type = a single string or pair of strings giving the NURBS type, where each entry is one of "clamped", "open" or "closed". Default: "clamped" +// Example(NoScale): Computing points on a patch using ranges +// patch = [ +// [[-50, 50, 0], [-16, 50, 20], [ 16, 50, 20], [50, 50, 0]], +// [[-50, 16, 20], [-16, 16, 40], [ 16, 16, 40], [50, 16, 20]], +// [[-50,-16, 20], [-16,-16, 40], [ 16,-16, 40], [50,-16, 20]], +// [[-50,-50, 0], [-16,-50, 20], [ 16,-50, 20], [50,-50, 0]], +// ]; +// pts = nurbs_patch_points(patch, 3, u=[0:.1:1], v=[0:.3:1]); +// move_copies(flatten(pts)) sphere(r=2,$fn=16); +// Example(NoScale): Computing points using splinesteps +// patch = [ +// [[-50, 50, 0], [-16, 50, 20], [ 16, 50, 20], [50, 50, 0]], +// [[-50, 16, 20], [-16, 16, 40], [ 16, 16, 40], [50, 16, 20]], +// [[-50,-16, 20], [-16,-16, 40], [ 16,-16, 40], [50,-16, 20]], +// [[-50,-50, 0], [-16,-50, 20], [ 16,-50, 20], [50,-50, 0]], +// ]; +// pts = nurbs_patch_points(patch, 3, splinesteps=5); +// move_copies(flatten(pts)) sphere(r=2,$fn=16); + +function nurbs_patch_points(patch, degree, splinesteps, u, v, weights, type=["clamped","clamped"], mult=[undef,undef], knots=[undef,undef]) = + assert(is_undef(splinesteps) || !any_defined([u,v]), "Cannot combine splinesteps with u and v") + is_def(weights) ? + assert(is_matrix(weights,len(patch),len(patch[0])), "The weights parameter must be a matrix that matches the size of the patch array") + let( + patch = [for(i=idx(patch)) [for (j=idx(patch[0])) [each patch[i][j]*weights[i][j], weights[i][j]]]], + pts = nurbs_patch_points(patch=patch, degree=degree, splinesteps=splinesteps, u=u, v=v, type=type, mult=mult, knots=knots) + ) + [for(row=pts) [for (pt=row) select(pt,0,-2)/last(pt)]] + : + assert(is_undef(u) || is_range(u) || is_vector(u) || is_finite(u), "Input u is invalid") + assert(is_undef(v) || is_range(v) || is_vector(v) || is_finite(v), "Input v is invalid") + assert(num_defined([u,v])!=1, "Must define both u and v (when using)") + let( + u=is_range(u) ? list(u) : u, + v=is_range(v) ? list(v) : v, + degree = force_list(degree,2), + type = force_list(type,2), + splinesteps = is_undef(splinesteps) ? [undef,undef] : force_list(splinesteps,2), + mult = is_vector(mult) || is_undef(mult) ? [mult,mult] + : assert((is_undef(mult[0]) || is_vector(mult[0])) && (is_undef(mult[1]) || is_vector(mult[1])), "mult must be a vector or list of two vectors") + mult, + knots = is_vector(knots) || is_undef(knots) ? [knots,knots] + : assert((is_undef(knots[0]) || is_vector(knots[0])) && (is_undef(knots[1]) || is_vector(knots[1])), "knots must be a vector or list of two vectors") + knots, + ) + is_num(u) && is_num(v)? nurbs_curve([for (control=patch) nurbs_curve(control, degree[1], u=v, type=type[1], mult=mult[1], knots=knots[1])], + degree[0], u=u, type=type[0], mult=mult[0], knots=knots[0]) + : is_num(u) ? nurbs_patch_points(patch, degree, u=[u], v=v, knots=knots, mult=mult, type=type)[0] + : is_num(v) ? column(nurbs_patch_points(patch, degree, u=u, v=[v], knots=knots, mult=mult, type=type),0) + : + let( + + vsplines = [for (i = idx(patch[0])) nurbs_curve(column(patch,i), degree[0], splinesteps=splinesteps[0],u=u, type=type[0],mult=mult[0],knots=knots[0])] + ) + [for (i = idx(vsplines[0])) nurbs_curve(column(vsplines,i), degree[1], splinesteps=splinesteps[1], u=v, mult=mult[1], knots=knots[1], type=type[1])]; + +// Function: nurbs_vnf() +// Synopsis: Generates a (possibly non-manifold) VNF for a single NURBS surface patch. +// SynTags: VNF +// Topics: NURBS Patches +// See Also: nurbs_patch_points() +// Usage: +// vnf = nurbs_vnf(patch, degree, [splinesteps], [mult=], [knots=], [weights=], [type=], [style=]); +// Description: +// Compute a (possibly non-manifold) VNF for a NURBS. The input patch must be an array of control points. If weights is given it +// must be an array of weights that matches the size of the control points. The style parameter +// gives the {{vnf_vertex_array()}} style to use. The other parameters may specify the NURBS parameters in the two directions +// by giving a single value, which applies to both directions, or a list of two values to specify different values in each direction. +// You can specify undef for for a direction to keep the default, such as `mult=[undef,v_multiplicity]`. +// Arguments: +// patch = rectangular list of control points in any dimension +// degree = a scalar or 2-vector giving the degree of the NURBS in the two directions +// splinesteps = a scalar or 2-vector giving the number of segments between each knot in the two directions +// --- +// mult = a single list or pair of lists giving the knot multiplicity in the two directions. Default: all 1 +// knots = a single list of pair of lists giving the knot vector in each of the two directions. Default: uniform +// weights = a single list or pair of lists giving the weight at each control point in the. Default: all 1 +// type = a single string or pair of strings giving the NURBS type, where each entry is one of "clamped", "open" or "closed". Default: "clamped" +// style = {{vnf_vertex_array ()}} style to use for triangulating the surface. Default: "default" +// Example: Quadratic B-spline surface +// patch = [ +// [[-50, 50, 0], [-16, 50, 20], [ 16, 50, 20], [50, 50, 0]], +// [[-50, 16, 20], [-16, 16, 40], [ 16, 16, 40], [50, 16, 20]], +// [[-50,-16, 20], [-16,-16, 40], [ 16,-16, 40], [50,-16, 20]], +// [[-50,-50, 0], [-16,-50, 20], [ 16,-50, 20], [50,-50, 0]], +// ]; +// vnf = nurbs_vnf(patch, 2); +// vnf_polyhedron(vnf); +// Example: Cubic B-spline surface +// patch = [ +// [[-50, 50, 0], [-16, 50, 20], [ 16, 50, 20], [50, 50, 0]], +// [[-50, 16, 20], [-16, 16, 40], [ 16, 16, 40], [50, 16, 20]], +// [[-50,-16, 20], [-16,-16, 40], [ 16,-16, 40], [50,-16, 20]], +// [[-50,-50, 0], [-16,-50, 20], [ 16,-50, 20], [50,-50, 0]], +// ]; +// vnf = nurbs_vnf(patch, 3); +// vnf_polyhedron(vnf); +// Example: Cubic B-spline surface, closed in one direction +// patch = [ +// [[-50, 50, 0], [-16, 50, 20], [ 16, 50, 20], [50, 50, 0]], +// [[-50, 16, 20], [-16, 16, 40], [ 16, 16, 40], [50, 16, 20]], +// [[-50,-16, 20], [-16,-16, 40], [ 16,-16, 40], [50,-16, 20]], +// [[-50,-50, 0], [-16,-50, 20], [ 16,-50, 20], [50,-50, 0]], +// ]; +// vnf = nurbs_vnf(patch, 3, type=["closed","clamped"]); +// vnf_polyhedron(vnf); +// Example: B-spline surface cubic in one direction, quadratic in the other +// patch = [ +// [[-50, 50, 0], [-16, 50, 20], [ 16, 50, 20], [50, 50, 0]], +// [[-50, 16, 20], [-16, 16, 40], [ 16, 16, 40], [50, 16, 20]], +// [[-50,-16, 20], [-16,-16, 40], [ 16,-16, 40], [50,-16, 20]], +// [[-50,-50, 0], [-16,-50, 20], [ 16,-50, 20], [50,-50, 0]], +// ]; +// vnf = nurbs_vnf(patch, [3,2],type=["closed","clamped"]); +// vnf_polyhedron(vnf); +// Example: The sphere can be represented using NURBS +// patch = [ +// [[0,0,1], [0,0,1], [0,0,1], [0,0,1], [0,0,1], [0,0,1], [0,0,1]], +// [[2,0,1], [2,4,1], [-2,4,1], [-2,0,1], [-2,-4,1], [2,-4,1], [2,0,1]], +// [[2,0,-1],[2,4,-1],[-2,4,-1],[-2,0,-1],[-2,-4,-1], [2,-4,-1],[2,0,-1]], +// [[0,0,-1],[0,0,-1],[0,0,-1], [0,0,-1], [0,0,-1], [0,0,-1], [0,0,-1]] +// ]; +// weights = [ +// [9,3,3,9,3,3,9], +// [3,1,1,3,1,1,3], +// [3,1,1,3,1,1,3], +// [9,3,3,9,3,3,9], +// ]/9; +// vknots = [0, 1/2, 1/2, 1/2, 1]; +// vnf = nurbs_vnf(patch, 3,weights=weights, knots=[undef,vknots]); +// vnf_polyhedron(vnf); +function nurbs_vnf(patch, degree, splinesteps=16, weights, type="clamped", mult, knots, style="default") = + assert(is_nurbs_patch(patch),"Input patch is not a rectangular aray of points") + let( + pts = nurbs_patch_points(patch=patch, degree=degree, splinesteps=splinesteps, type=type, mult=mult, knots=knots, weights=weights), + ) + vnf_vertex_array(pts, style=style, row_wrap=type[0]=="closed", col_wrap=type[1]=="closed"); + From c3ca921f28303fa017181904f316ab2de227f1f6 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sun, 3 Nov 2024 07:11:57 -0500 Subject: [PATCH 05/10] make tag_diff() and similar functions default to no tag --- attachments.scad | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/attachments.scad b/attachments.scad index df63d7a..8af6e0b 100644 --- a/attachments.scad +++ b/attachments.scad @@ -12,8 +12,6 @@ // FileFootnotes: STD=Included in std.scad ////////////////////////////////////////////////////////////////////// -include - // Default values for attachment code. $tags=undef; // for backward compatibility $tag = ""; @@ -1487,7 +1485,7 @@ module diff(remove="remove", keep="keep") // Topics: Attachments // See Also: tag(), force_tag(), recolor(), show_only(), hide(), diff(), intersect(), tag_intersect() // Usage: -// tag_diff(tag, [remove], [keep]) PARENT() CHILDREN; +// tag_diff([tag], [remove], [keep]) PARENT() CHILDREN; // Description: // Perform a differencing operation in the manner of {{diff()}} using tags to control what happens, // and then tag the resulting difference object with the specified tag. This forces the specified @@ -1497,7 +1495,7 @@ module diff(remove="remove", keep="keep") // . // For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments). // Arguments: -// tag = Tag string to apply to this difference object +// tag = Tag string to apply to this difference object. Default: `""` (no tag) // remove = String containing space delimited set of tag names of children to difference away. Default: `"remove"` // keep = String containing space delimited set of tag names of children to keep; that is, to union into the model after differencing is completed. Default: `"keep"` // Side Effects: @@ -1534,7 +1532,7 @@ module diff(remove="remove", keep="keep") // cyl(r=7,h=7) // tag("remove")cyl(r=6,h=8) // tag("keep")cyl(r=5,h=9); -module tag_diff(tag,remove="remove", keep="keep") +module tag_diff(tag="",remove="remove", keep="keep") { req_children($children); assert(is_string(remove),"remove must be a string of tags"); @@ -1630,7 +1628,7 @@ module intersect(intersect="intersect",keep="keep") // Topics: Attachments // See Also: tag(), force_tag(), recolor(), show_only(), hide(), diff(), tag_diff(), intersect() // Usage: -// tag_intersect(tag, [intersect], [keep]) PARENT() CHILDREN; +// tag_intersect([tag], [intersect], [keep]) PARENT() CHILDREN; // Description: // Perform an intersection operation in the manner of {{intersect()}} using tags to control what happens, // and then tag the resulting difference object with the specified tag. This forces the specified @@ -1640,7 +1638,7 @@ module intersect(intersect="intersect",keep="keep") // . // For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments). // Arguments: -// tag = Tag to set for the intersection +// tag = Tag to set for the intersection. Default: `""` (no tag) // intersect = String containing space delimited set of tag names of children to intersect. Default: "intersect" // keep = String containing space delimited set of tag names of children to keep whole. Default: "keep" // Side Effects: @@ -1663,7 +1661,7 @@ module intersect(intersect="intersect",keep="keep") // tag("intersect")position(RIGHT) cyl(r=7,h=10); // tag("keep")position(LEFT)cyl(r=4,h=10); // } -module tag_intersect(tag,intersect="intersect",keep="keep") +module tag_intersect(tag="",intersect="intersect",keep="keep") { assert(is_string(intersect),"intersect must be a string of tags"); assert(is_string(keep),"keep must be a string of tags"); @@ -1725,7 +1723,7 @@ module conv_hull(keep="keep") // Topics: Attachments // See Also: tag(), recolor(), show_only(), hide(), diff(), intersect() // Usage: -// tag_conv_hull(tag, [keep]) CHILDREN; +// tag_conv_hull([tag], [keep]) CHILDREN; // Description: // Perform a convex hull operation in the manner of {{conv_hull()}} using tags to control what happens, // and then tag the resulting hull object with the specified tag. This forces the specified @@ -1735,6 +1733,7 @@ module conv_hull(keep="keep") // . // For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments). // Arguments: +// tag = Tag string to apply to this convex hull object. Default: `""` (no tag) // keep = String containing space delimited set of tag names of children to keep out of the hull. Default: "keep" // Side Effects: // Sets `$tag` to the tag you specify, possibly with a scope prefix. @@ -1756,7 +1755,7 @@ module conv_hull(keep="keep") // tag("keep")position(FRONT+LEFT)cyl(r=4,h=10); // } // } -module tag_conv_hull(tag,keep="keep") +module tag_conv_hull(tag="",keep="keep") { req_children($children); assert(is_string(keep),"keep must be a string of tags"); @@ -1839,7 +1838,6 @@ module hide_this() children(); } - // Module: show_only() // Synopsis: Show only the children with the listed tags. // See Also: tag(), recolor(), show_all(), show_int(), diff(), intersect() @@ -3102,9 +3100,9 @@ module attachable( // . // If `$attach_to` is defined, as a consequence of `attach(from,to)`, then // the following transformations are performed in order: -// * Translates this part so it's anchor position matches the parent's anchor position. -// * Rotates this part so it's anchor direction vector exactly opposes the parent's anchor direction vector. -// * Rotates this part so it's anchor spin matches the parent's anchor spin. +// * Translates this part so its anchor position matches the parent's anchor position. +// * Rotates this part so its anchor direction vector exactly opposes the parent's anchor direction vector. +// * Rotates this part so its anchor spin matches the parent's anchor spin. // . // For a step-by-step explanation of attachments, see the [Attachments Tutorial](Tutorial-Attachments). // From 3aff85aedc6bfd2943b47738eb7069a4bd472c52 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sun, 3 Nov 2024 07:12:38 -0500 Subject: [PATCH 06/10] add structs.scad to std.scad; fix various it's typos in docs --- .openscad_docsgen_rc | 1 + ball_bearings.scad | 4 ++-- gears.scad | 2 +- rounding.scad | 7 ++++--- std.scad | 1 + structs.scad | 2 +- turtle3d.scad | 2 +- 7 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.openscad_docsgen_rc b/.openscad_docsgen_rc index 1e93946..0260ae3 100644 --- a/.openscad_docsgen_rc +++ b/.openscad_docsgen_rc @@ -35,6 +35,7 @@ PrioritizeFiles: skin.scad vnf.scad beziers.scad + nurbs.scad rounding.scad turtle3d.scad math.scad diff --git a/ball_bearings.scad b/ball_bearings.scad index 5f4fd40..3801800 100644 --- a/ball_bearings.scad +++ b/ball_bearings.scad @@ -50,8 +50,8 @@ module ball_bearing(trade_size, id, od, width, shield=true, flange=false, fd, fw ball_bearing_info(trade_size); check = assert(all_defined(select(info, 0,4)), "Bad Input"); if(flange){ - assert(!is_undef(fd), "If flange is set you must specify it's diameter"); - assert(!is_undef(fw), "If flange is set you must specify it's width"); + assert(!is_undef(fd), "If flange is set you must specify its diameter"); + assert(!is_undef(fw), "If flange is set you must specify its width"); } id = info[0]; od = info[1]; diff --git a/gears.scad b/gears.scad index 4586c65..dd3a442 100644 --- a/gears.scad +++ b/gears.scad @@ -465,7 +465,7 @@ function _inherit_gear_thickness(thickness,dflt=10) = // It is possible to design the worm to follow the curved shape of its mated gear, resulting // in an enveloping (also called "globoid") worm. This type of worm makes better contact with // the worm gear, but is less often used due to manufacturing complexity and consequent expense. -// Figure(3D,Big,NoAxes,VPT=[0,0,0],VPR=[192,0,180],VPD=172.84): A cylindrical worm appears on the left in green. Note it's straight sides. The enveloping (globoid) worm gears appears on the right in green. Note that its sides curve so several teeth can mate with the worm gear, and it requires a complex tooth form +// Figure(3D,Big,NoAxes,VPT=[0,0,0],VPR=[192,0,180],VPD=172.84): A cylindrical worm appears on the left in green. Note its straight sides. The enveloping (globoid) worm gears appears on the right in green. Note that its sides curve so several teeth can mate with the worm gear, and it requires a complex tooth form // tilt=20; // starts=1; // ps=0; diff --git a/rounding.scad b/rounding.scad index fa7dca8..d75f7ef 100644 --- a/rounding.scad +++ b/rounding.scad @@ -1249,7 +1249,7 @@ function _stroke_end(width,left, right, spec) = function _path_line_intersection(path, line, ind=0) = ind==len(path)-1 ? undef : let(intersect=line_intersection(line, select(path,ind,ind+1),LINE,SEGMENT)) - // If it intersects the segment excluding it's final point, then we're done + // If it intersects the segment excluding its final point, then we're done // The final point is treated as part of the next segment is_def(intersect) && intersect != path[ind+1]? [intersect, ind+1] : @@ -2210,10 +2210,11 @@ module rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_bot dummy1 = assert(in_list(atype, ["intersect","hull","surf_intersect","surf_hull","prismoid"]), "Anchor type must be one of: \"hull\", \"intersect\", \"surf_hull\", \"surf_intersect\" or \"prismoid\"") assert(atype!="prismoid" || len(bottom)==4, "Anchor type \"prismoid\" requires that len(bottom)=4"); + result = rounded_prism(bottom=bottom, top=top, joint_bot=joint_bot, joint_top=joint_top, joint_sides=joint_sides, k_bot=k_bot, k_top=k_top, k_sides=k_sides, k=k, splinesteps=splinesteps, h=h, length=length, height=height, l=l, debug=debug, _full_info=true); - + height = one_defined([l,h,height,length], "l,h,height,length", dflt=u_add(bottom_height,top_height)); top = is_undef(top) ? path3d(bottom,height/2) : len(top[0])==2 ? path3d(top,height/2) : top; @@ -2792,7 +2793,7 @@ Access to the derivative smoothing parameter? // prism axis with the auxiliary object. Note that this means that if `aux_T` is a rotation it will change the joiner prism root, because // the rotated prism axis will intersect the base in a different location. If you do not give an auxiliary object then you must give // the length/height parameter to specify the prism length. This gives the length of the prism measured from the root to the end point. -// Note that the joint with a curved base may significantly extend the length of the joiner prism: it's total length will often be larger than +// Note that the joint with a curved base may significantly extend the length of the joiner prism: its total length will often be larger than // the length you request. // . // For the cylinder and spherical objects you may wish to joint a prism to the concave surface. You can do this by setting a negative diff --git a/std.scad b/std.scad index e41c7a0..f20de64 100644 --- a/std.scad +++ b/std.scad @@ -37,6 +37,7 @@ include include include include +include // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/structs.scad b/structs.scad index 98f2ba6..0c16839 100644 --- a/structs.scad +++ b/structs.scad @@ -5,9 +5,9 @@ // by key. // Includes: // include -// include // FileGroup: Data Management // FileSummary: Structure/Dictionary Manipulation +// FileFootnotes: STD=Included in std.scad ////////////////////////////////////////////////////////////////////// diff --git a/turtle3d.scad b/turtle3d.scad index 2c74b25..3895679 100644 --- a/turtle3d.scad +++ b/turtle3d.scad @@ -460,7 +460,7 @@ function _turtle3d_command_len(commands, index) = in_list(commands[index],["repeat","arctodir","arcrot"]) ? 3 : // Repeat, arctodir and arcrot commands require 2 args // For these, the first arg is required, second arg is present if it is not a string or list in_list(commands[index], one_or_two_arg) && len(commands)>index+2 && !is_string(commands[index+2]) && !is_list(commands[index+2]) ? 3 : - is_string(commands[index+1]) || is_list(commands[index])? 1 : // If 2nd item is a string it's must be a new command; + is_string(commands[index+1]) || is_list(commands[index])? 1 : // If 2nd item is a string it must be a new command; // If first item is a list it's a compound command 2; // Otherwise we have command and arg From 400f3efa97526183bca79abe38947576971a334c Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sun, 3 Nov 2024 07:26:41 -0500 Subject: [PATCH 07/10] skin automatically flips faces when paths are reversed; bugfix in rounded_prism --- rounding.scad | 2 +- skin.scad | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/rounding.scad b/rounding.scad index d75f7ef..5b9d138 100644 --- a/rounding.scad +++ b/rounding.scad @@ -2214,7 +2214,7 @@ module rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_bot result = rounded_prism(bottom=bottom, top=top, joint_bot=joint_bot, joint_top=joint_top, joint_sides=joint_sides, k_bot=k_bot, k_top=k_top, k_sides=k_sides, k=k, splinesteps=splinesteps, h=h, length=length, height=height, l=l, debug=debug, _full_info=true); - height = one_defined([l,h,height,length], "l,h,height,length", dflt=u_add(bottom_height,top_height)); + height = one_defined([l,h,height,length], "l,h,height,length", dflt=undef); top = is_undef(top) ? path3d(bottom,height/2) : len(top[0])==2 ? path3d(top,height/2) : top; diff --git a/skin.scad b/skin.scad index bce4e31..6ba2c1e 100644 --- a/skin.scad +++ b/skin.scad @@ -38,7 +38,11 @@ // 2d curves with heights given in the `z` parameter. It is your responsibility to ensure // that the resulting polyhedron is free from self-intersections, which would make it invalid // and can result in cryptic CGAL errors upon rendering with a second object present, even though the polyhedron appears -// OK during preview or when rendered by itself. +// OK during preview or when rendered by itself. The order of points in your profiles must be +// consistent from slice to slice so that points match up without creating twists. You can specify +// profiles in any consistent order: if necessary, skin() will reverse the faces to ensure that the final +// result has clockwise faces as required by CGAL. Note that the face reversal test may give random results +// if you use skin to construct self-intersecting (invalid) polyhedra. // . // For this operation to be well-defined, the profiles must all have the same vertex count and // we must assume that profiles are aligned so that vertex `i` links to vertex `i` on all polygons. @@ -502,10 +506,11 @@ function skin(profiles, slices, refine=1, method="direct", sampling, caps, close refine[i] * len(pair[0]) ) subdivide_and_slice(pair,slices[i], nsamples, method=sampling)], - vnf=vnf_join( + pvnf=vnf_join( [for(i=idx(full_list)) vnf_vertex_array(full_list[i], cap1=i==0 && fullcaps[0], cap2=i==len(full_list)-1 && fullcaps[1], - col_wrap=true, style=style)]) + col_wrap=true, style=style)]), + vnf = vnf_volume(pvnf)<0 ? vnf_reverse_faces(pvnf) : pvnf ) reorient(anchor,spin,orient,vnf=vnf,p=vnf,extent=atype=="hull",cp=cp); From da261588b7eee6486a6fe24eb59e470d52291fe6 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sun, 3 Nov 2024 08:06:28 -0500 Subject: [PATCH 08/10] improve docs for tag_scope() --- attachments.scad | 69 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 8 deletions(-) diff --git a/attachments.scad b/attachments.scad index 8af6e0b..087cc3a 100644 --- a/attachments.scad +++ b/attachments.scad @@ -1252,13 +1252,66 @@ module default_tag(tag,do_tag=true) // Creates a tag scope with locally altered tag names to avoid tag name conflict with other code. // This is necessary when writing modules because the module's caller might happen to use the same tags. // Note that if you directly set the `$tag` variable then tag scoping will not work correctly. +// Usually you will want to use tag_scope in the first child of {{attachable()}} to isolate the geometry +// of your attachable object. If you put it **outside** the {{attachable()}} call, then it will +// set a scope that also applies to the children passed to your attachable object. // Side Effects: // `$tag_prefix` is set to the value of `scope=` if given, otherwise is set to a random string. -// Example: In this example the ring module uses "remove" tags which will conflict with use of the same tags by the parent. -// module ring(r,h,w=1,anchor,spin,orient) +// Example(3D,NoAxes): In this example, tag_scope() is required for things to work correctly. +// module myring(){ +// attachable(anchor=CENTER, spin=0, d=60, l=60) { +// tag_scope() +// diff() +// cyl(d=60, l=60){ +// tag("remove") +// color_this("lightblue") +// cyl(d=30, l=61); +// } +// children(); +// } +// } +// diff() +// myring() +// color_this("green") cyl(d=20, l=61) +// tag("remove") color_this("yellow") cyl(d=10, l=65); +// Example(3D,NoAxes): Without tag_scope() we get this result +// module myring(){ +// attachable(anchor=CENTER, spin=0, d=60, l=60) { +// diff() +// cyl(d=60, l=60){ +// tag("remove") +// color_this("lightblue") +// cyl(d=30, l=61); +// } +// children(); +// } +// } +// diff() +// myring() +// color_this("green") cyl(d=20, l=61) +// tag("remove") color_this("yellow") cyl(d=10, l=65); +// Example(3D,NoAxes): If the tag_scope() is outside the attachable() call then something different goes wrong: +// module myring(){ +// tag_scope() +// attachable(anchor=CENTER, spin=0, d=60, l=60) { +// diff() +// cyl(d=60, l=60){ +// tag("remove") +// color_this("lightblue") +// cyl(d=30, l=61); +// } +// children(); +// } +// } +// diff() +// myring() +// color_this("green") cyl(d=20, l=61) +// tag("remove") color_this("yellow") cyl(d=10, l=65); +// Example: In this example the myring module uses "remove" tags which will conflict with use of the same tags elsewhere in a diff() operation, even without a parent-child relationship. Without the tag_scope() the result is a solid cylinder. +// module myring(r,h,w=1,anchor,spin,orient) // { -// tag_scope("ringscope") // attachable(anchor,spin,orient,r=r,h=h){ +// tag_scope("myringscope") // diff() // cyl(r=r,h=h) // tag("remove") cyl(r=r-w,h=h+1); @@ -1267,14 +1320,14 @@ module default_tag(tag,do_tag=true) // } // // Calling the module using "remove" tags // // will conflict with internal tag use in -// // the ring module. +// // the myring module. // $fn=32; // diff(){ -// ring(10,7,w=4); -// tag("remove")ring(8,8); +// myring(10,7,w=4); +// tag("remove")myring(8,8); // tag("remove")diff("rem"){ -// ring(9.5,8,w=1); -// tag("rem")ring(9.5,8,w=.3); +// myring(9.5,8,w=1); +// tag("rem")myring(9.5,8,w=.3); // } // } module tag_scope(scope){ From 41963419ef63389565568ae0c70882a9a4add463 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sun, 3 Nov 2024 08:53:02 -0500 Subject: [PATCH 09/10] remove comma --- attachments.scad | 4 ++-- nurbs.scad | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/attachments.scad b/attachments.scad index 087cc3a..e9f0a07 100644 --- a/attachments.scad +++ b/attachments.scad @@ -1254,7 +1254,7 @@ module default_tag(tag,do_tag=true) // Note that if you directly set the `$tag` variable then tag scoping will not work correctly. // Usually you will want to use tag_scope in the first child of {{attachable()}} to isolate the geometry // of your attachable object. If you put it **outside** the {{attachable()}} call, then it will -// set a scope that also applies to the children passed to your attachable object. +// set a scope that also applies to the children passed to your attachable object, which is probably not what you want. // Side Effects: // `$tag_prefix` is set to the value of `scope=` if given, otherwise is set to a random string. // Example(3D,NoAxes): In this example, tag_scope() is required for things to work correctly. @@ -1290,7 +1290,7 @@ module default_tag(tag,do_tag=true) // myring() // color_this("green") cyl(d=20, l=61) // tag("remove") color_this("yellow") cyl(d=10, l=65); -// Example(3D,NoAxes): If the tag_scope() is outside the attachable() call then something different goes wrong: +// Example(3D,NoAxes): If the tag_scope() is outside the attachable() call then the scope applies to the children and something different goes wrong: // module myring(){ // tag_scope() // attachable(anchor=CENTER, spin=0, d=60, l=60) { diff --git a/nurbs.scad b/nurbs.scad index 48db015..5a8e7b3 100644 --- a/nurbs.scad +++ b/nurbs.scad @@ -258,7 +258,7 @@ function nurbs_curve(control,degree,splinesteps,u, mult,weights,type="clamped", ) uniform? let( - msum = cumsum(mult), + msum = cumsum(mult) ) [for(uval=adjusted_u) let( From f844a7c4793eceae89ddb68f7806ceb5e943810b Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Sun, 3 Nov 2024 09:29:50 -0500 Subject: [PATCH 10/10] delete more commas --- nurbs.scad | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nurbs.scad b/nurbs.scad index 5a8e7b3..78e4390 100644 --- a/nurbs.scad +++ b/nurbs.scad @@ -479,7 +479,7 @@ function nurbs_patch_points(patch, degree, splinesteps, u, v, weights, type=["cl mult, knots = is_vector(knots) || is_undef(knots) ? [knots,knots] : assert((is_undef(knots[0]) || is_vector(knots[0])) && (is_undef(knots[1]) || is_vector(knots[1])), "knots must be a vector or list of two vectors") - knots, + knots ) is_num(u) && is_num(v)? nurbs_curve([for (control=patch) nurbs_curve(control, degree[1], u=v, type=type[1], mult=mult[1], knots=knots[1])], degree[0], u=u, type=type[0], mult=mult[0], knots=knots[0]) @@ -570,7 +570,7 @@ function nurbs_patch_points(patch, degree, splinesteps, u, v, weights, type=["cl function nurbs_vnf(patch, degree, splinesteps=16, weights, type="clamped", mult, knots, style="default") = assert(is_nurbs_patch(patch),"Input patch is not a rectangular aray of points") let( - pts = nurbs_patch_points(patch=patch, degree=degree, splinesteps=splinesteps, type=type, mult=mult, knots=knots, weights=weights), + pts = nurbs_patch_points(patch=patch, degree=degree, splinesteps=splinesteps, type=type, mult=mult, knots=knots, weights=weights) ) vnf_vertex_array(pts, style=style, row_wrap=type[0]=="closed", col_wrap=type[1]=="closed");