From 2bcd3b113026906b8552ef7540ab370371cf98cf Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Tue, 17 Mar 2026 19:00:34 -0400 Subject: [PATCH 1/2] add rounded caps to path_join handle parameter lists in debug_nurbs --- nurbs.scad | 87 +++++++++++++++++++++++++++------------------------ rounding.scad | 61 ++++++++++++++++++++++++++++++------ 2 files changed, 98 insertions(+), 50 deletions(-) diff --git a/nurbs.scad b/nurbs.scad index f61ecf39..da7e9a22 100644 --- a/nurbs.scad +++ b/nurbs.scad @@ -360,7 +360,6 @@ function _calc_mult(knots) = deltas(ind); - // Module: debug_nurbs() // Synopsis: Shows a NURBS curve and its control points, knots and weights // SynTags: Geom @@ -370,9 +369,10 @@ function _calc_mult(knots) = // 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. +// control point indices and weights, and can also display the knot points. +// Instead of providing separate parameters you can give a first parameter of the form of a NURBS parameter list: `[type degree, control, knots, weights]`. // Arguments: -// control = control points for NURBS +// control = list of control points in any dimension or a NURBS parameter list // degree = degree of NURBS // splinesteps = number of segments between each pair of knots. Default: 16 // width = width of the line. Default: 1 @@ -395,42 +395,50 @@ function _calc_mult(knots) = // debug_nurbs(pts,4,type="closed",weights=weights); 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); - echo(knotpts); - color([1,.5,1]) - move_copies(knotpts) - if (twodim)circle(r=width); - else sphere(r=width); +{ + if (is_list(control) && in_list(control[0], ["closed","open","clamped"])) { + assert(len(control)>=5, "Invalid NURBS parameter list") + assert(num_defined([degree,mult,weights,knots])==0, + "Cannot give degree, mult, weights or knots when you provide a NURBS parameter list") + debug_nurbs(control[2], control[1], splinesteps, width, size, weights=control[4], type=control[0], knots=control[3], + show_weights=show_weights, show_knots=false, show_index=true); } - 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); + else { + $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); + echo(knotpts); + color([1,.5,1]) + move_copies(knotpts) + if (twodim)circle(r=width); + else sphere(r=width); } - 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); - } - + 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); + } + } } @@ -496,7 +504,6 @@ function is_nurbs_patch(x) = // 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]) = -let(fo=echo(intype=type)) is_list(patch) && _valid_surface_type(patch[0]) ? assert(len(patch)>=5, "NURBS parameter list is invalid") assert(num_defined([degree,weights])==0 && mult==[undef,undef] && knots==[undef,undef], @@ -518,9 +525,7 @@ let(fo=echo(intype=type)) u=is_range(u) ? list(u) : u, v=is_range(v) ? list(v) : v, degree = force_list(degree,2), - feee=echo(type=type), type = force_list(type,2), -fda= echo(type=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") @@ -535,11 +540,11 @@ fda= echo(type=type, force_list(type,2)), : 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&Module: nurbs_vnf() // Synopsis: Generates a (possibly non-manifold) VNF for a single NURBS surface patch. // SynTags: VNF diff --git a/rounding.scad b/rounding.scad index 90e7380e..b27ffef8 100644 --- a/rounding.scad +++ b/rounding.scad @@ -781,13 +781,15 @@ function _scalar_to_vector(value,length,varname) = // back by the joint length. Rounding is using continous curvature 4th order bezier splines and // the parameter `k` specifies how smooth the curvature match is. This parameter ranges from 0 to 1 with // a default of 0.5. Use a larger k value to get a curve that is bigger for the same joint value. When -// k=1 the curve may be similar to a circle if your curves are symmetric. As the path is built up, the joint +// k=0.9 the curve may be similar to a circle if your curves are symmetric. As the path is built up, the joint // parameter applies to the growing path, so if you pick a large joint parameter it may interact with the // previous path sections. See [Types of Roundover](rounding.scad#subsection-types-of-roundover) for more details // on continuous curvature rounding. // . // The rounding is created by extending the two clipped paths to define a corner point. If the extensions of -// the paths do not intersect, the function issues an error. When closed=true the final path should actually close +// the paths do not intersect they will be capped by a curve resembling a semi-circle if this is possible. But sometimes +// the connecting path will need to be S-shaped, which is not supported. If this situation arises, the function issues an error. +// When closed=true the final path should actually close // the shape, repeating the starting point of the shape. If it does not, then the rounding fills the gap. // . // The number of segments in the roundovers is set based on $fn and $fs. If you use $fn it specifies the number of @@ -881,6 +883,16 @@ function _scalar_to_vector(value,length,varname) = // tri = regular_ngon(n=3, r=7); // stroke(path_join([tri], joint=3,closed=true,$fn=12), // closed=true,width=.5); +// Example(2D): Here lines are joined that do not intersect when they are extended, so the joint is sort of like a semi-circle. We show the effect of the `k` parameter at its default value of 0.5 or at 0.92 which is close to a circle. +// p=path_join([ +// [[1,3],[15,8]], +// [[17,0],[0,0]], +// [[0,-8],[15,-3]] +// ], +// k=[.5,.9], +// relocate=false); +// stroke(p); + module path_join(paths,joint=0,k=0.5,relocate=true,closed=false) { no_module();} function path_join(paths,joint=0,k=0.5,relocate=true,closed=false)= assert(is_list(paths),"Input paths must be a list of paths") @@ -935,14 +947,21 @@ function _path_join(paths,joint,k=0.5,i=0,result=[],relocate=true,closed=false) corner = approx(firstcut[0],nextcut[0]) ? firstcut[0] : line_intersection([firstcut[0], firstcut[0]-first_dir], [nextcut[0], nextcut[0]-next_dir],RAY,RAY) ) - assert(is_def(corner), str("Curve directions at cut points don't intersect in a corner when ", - loop?"closing the path":str("adding path ",i+1))) let( - bezpts = _smooth_bez_fill([firstcut[0], corner, nextcut[0]],k[i]), - N = max(3,$fn>0 ?$fn : ceil(bezier_length(bezpts)/$fs)), - bezpath = approx(firstcut[0],corner) && approx(corner,nextcut[0]) - ? [] - : bezier_curve(bezpts,N), + bezpath = + is_def(corner) ? let( + bezpts = _smooth_bez_fill([firstcut[0], corner, nextcut[0]],k[i]), + N = max(3,$fn>0 ?$fn : ceil(bezier_length(bezpts)/$fs)), + bezpath = approx(firstcut[0],corner) && approx(corner,nextcut[0]) + ? [] + : bezier_curve(bezpts,N) + ) + bezpath + : let(bezpath = _smooth_bezier_cap([firstcut[0]+first_dir, firstcut[0]], + [nextcut[0]+next_dir, nextcut[0]], k[i])) + assert(is_def(bezpath),str("Curve directions at cut points requires an S-curve joint (which is not supported) when ", + loop?"closing the path":str("adding path ",i+1))) + bezpath, new_result = [each select(result,loop?nextcut[1]:0,len(revresult)-1-firstcut[1]), each bezpath, nextcut[0], @@ -955,6 +974,30 @@ function _path_join(paths,joint,k=0.5,i=0,result=[],relocate=true,closed=false) +// connect tails of v1 and v2, starting at v1 +// returns undef if an S curve is needed instead of a cap +function _smooth_bezier_cap(v1,v2,k=0.8,r) = + let( + r=default(r,norm(v1[1]-v2[1])/2), + n=line_normal(v1[1],v2[1]), + sign = (v1[1]-v1[0])*n>0 ? 1 : -1, + check= (v2[1]-v2[0])*n>0 ? 1 : -1 + ) + check != sign ? undef + : + let( + tangent = move(sign*n*r, [(v1[1]+v2[1])/2,v2[1]]), + corner1 = line_intersection(tangent, v1, LINE), + corner2 = line_intersection(tangent, v2, LINE) + ) + concat( + list_head(_bezcorner([v1[1],corner1, tangent[0]],k)), + _bezcorner([tangent[0], corner2, v2[1]],k) + ); + + + + // Function&Module: offset_stroke() // Synopsis: Draws a line along a path with options to specify angles and roundings at the ends. // SynTags: Path, Region From 739a743ca0aea01d7fd8b486cadfff6490e50532 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Tue, 17 Mar 2026 19:37:24 -0400 Subject: [PATCH 2/2] remove echo from random_polygon() --- math.scad | 1 - 1 file changed, 1 deletion(-) diff --git a/math.scad b/math.scad index e06f0f4b..5cf181b7 100644 --- a/math.scad +++ b/math.scad @@ -1262,7 +1262,6 @@ function random_polygon(n=3,size=1, angle_sep=0.2, seed) = max(dang)<180 ? angs : randang(seed=u_add(seed,angs[0])), angs = randang(seed), rads = is_undef(seed) ? rands(rmin,rmax,n) : rands(rmin,rmax,n,seed+angs[0]) - ,f=echo(rads=rads) ) [for(i=count(n)) rads[i]*[cos(angs[i]), -sin(angs[i])]];