back out rounding.scad changes that were in error and include

advertised changes in shapes3d for plot_revolution
This commit is contained in:
Adrian Mariano
2025-04-17 23:19:53 -04:00
parent 615a088488
commit f3f7e222c9
2 changed files with 298 additions and 187 deletions

View File

@@ -4537,10 +4537,6 @@ function _find_center_anchor(desc1, desc2, anchor2, flip) =
function _is_anchor(a) = is_string(a) || is_vector(a,2) || is_vector(a,3);
function _is_anchor_list(list) = is_list(list) && [for(a=list) if (!_is_anchor(a)) a]==[];
module prism_connector(profile, desc1, anchor1, desc2, anchor2, shift1=0, shift2=0, spin_align=1,
scale=1,
@@ -4551,173 +4547,155 @@ module prism_connector(profile, desc1, anchor1, desc2, anchor2, shift1=0, shift2
smooth_normals, smooth_normals1, smooth_normals2,
debug=false, debug_pos=false)
{
dummy1 = assert(_is_anchor(anchor1) || _is_anchor_list(anchor1) , "anchor1 must be an anchor (string or a 3-vector) or a list of anchors")
assert(_is_anchor(anchor2) || _is_anchor_list(anchor2) , "anchor2 must be an anchor (string or a 3-vector) or a list of anchors");
if (_is_anchor_list(anchor1) || _is_anchor_list(anchor2)) {
anchor1_list=_is_anchor(anchor1) ? [anchor1] : anchor1;
anchor2_list=_is_anchor(anchor2) ? [anchor2] : anchor2;
for(i = idx(anchor1_list))
for(j= idx(anchor2_list))
{
$anchor1 = anchor1_list[i];
$anchor2 = anchor2_list[j];
$idx = [i,j];
prism_connector(profile=profile, desc1=desc1, anchor1=$anchor1, desc2=desc2, anchor2=$anchor2, shift1=shift1, shift2=shift2, spin_align=spin_align,
scale=scale, fillet=fillet, fillet1=fillet1, fillet2=fillet2, overlap=overlap, overlap1=overlap1, overlap2=overlap2,
k=k, k1=k1, k2=k2, n=n, n1=n1, n2=n2, uniform=uniform, uniform1=uniform1, uniform2=uniform2,
smooth_normals=smooth_normals, smooth_normals1=smooth_normals1, smooth_normals2=smooth_normals2,
debug=debug, debug_pos=debug_pos) children();
base_fillet = first_defined([fillet1,fillet,0]);
aux_fillet = first_defined([fillet2,fillet,0]);
base_overlap = first_defined([overlap1,overlap,1]);
aux_overlap = first_defined([overlap2,overlap,1]);
base_n = first_defined([n1,n,15]);
aux_n = first_defined([n2,n,15]);
base_uniform = first_defined([uniform1, uniform, true]);
aux_uniform = first_defined([uniform2, uniform, true]);
base_k = first_defined([k1,k,0.7]);
aux_k = first_defined([k2,k,0.7]);
profile = force_path(profile,"profile");
dummy0 = assert(is_path(profile,2), "profile must be a 2d path");
corrected_base_anchor = is_vector(anchor1) && norm(anchor1)==0 ? _find_center_anchor(desc1,desc2,anchor2,true) : undef;
corrected_aux_anchor = is_vector(anchor2) && norm(anchor2)==0 ? _find_center_anchor(desc2,desc1,anchor1,false) : undef;
base_anchor=is_string(anchor1) ? anchor1
: is_def(corrected_base_anchor) ? corrected_base_anchor[0]
: point3d(anchor1);
aux_anchor=is_string(anchor2) ? anchor2
: is_def(corrected_aux_anchor) ? corrected_aux_anchor[0]
: point3d(anchor2);
base=desc1;
aux=desc2;
current = parent();
tobase = current==base ? ident(4) : linear_solve(current[0],base[0]); // Maps from current frame into the base frame
auxmap = linear_solve(base[0], aux[0]); // Map from the base (desc1) coordinate frame into the aux (desc2) coordinate frame
dummy = assert(is_vector(base_anchor) || is_string(base_anchor), "anchor1 must be a string or a 3-vector")
assert(is_vector(aux_anchor) || is_string(aux_anchor), "anchor2 must be a string or a 3-vector")
assert(is_rotation(auxmap), "desc1 and desc2 are not related to each other by a rotation (and translation)");
base_type = _get_obj_type(1,base[1],base_anchor,profile);
base_axis = base_type=="cyl" ? base[1][5] : RIGHT;
base_edge = _is_geom_an_edge(base[1],base_anchor);
base_r = in_list(base_type,["cyl","sphere"]) ? base[1][1] : 1;
base_anch = _find_anchor(base_anchor, base[1]);
base_spin = base_anch[3];
base_anch_pos = base_anch[1];
base_anch_dir = base_anch[2];
prelim_shift1 = _check_join_shift(1,base_type,shift1,true);
shift1 = corrected_base_anchor ? corrected_base_anchor[1] + prelim_shift1 : prelim_shift1;
aux_type = _get_obj_type(2,aux[1],aux_anchor,profile);
aux_anch = _find_anchor(aux_anchor, aux[1]);
aux_edge = _is_geom_an_edge(aux[1],aux_anchor);
aux_r = in_list(aux_type,["cyl","sphere"]) ? aux[1][1] : 1;
aux_anch_pos = aux_anch[1];
aux_anch_dir = aux_anch[2];
aux_spin = aux_anch[3];
aux_spin_dir = apply(rot(from=UP,to=aux_anch[2])*zrot(aux_spin),BACK);
aux_axis = aux_type=="cyl" ? aux[1][5] : RIGHT;
prelim_shift2 = _check_join_shift(2,aux_type,shift2,false);
shift2 = corrected_aux_anchor ? corrected_aux_anchor[1] + prelim_shift2 : prelim_shift2;
base_smooth_normals = first_defined([smooth_normals1, smooth_normals,!base_edge]);
aux_smooth_normals = first_defined([smooth_normals2, smooth_normals,!aux_edge]);
backdir = base_type=="cyl" ? base_axis
: apply(rot(from=UP,to=base_anch_dir)*zrot(base_spin),BACK);
anch_shift = base_type=="plane" || is_list(base_type) ? base_anch_pos : CENTER;
// Map from the starting position for a join_prism object to
// the standard position for the object.
// If the aux object is an edge (type is a list) then the starting position
// of the prism is with the edge lying on the X axis and the object below.
aux_to_canonical = aux_type=="sphere" ? IDENT
: aux_type=="cyl" ? frame_map(x=aux_axis, z=aux_anch[2])
: aux_type=="plane" ? move(aux_anch_pos) * rot(from=UP, to=aux_anch[2])*zrot(aux_spin)
: /* list */ move(aux_anch_pos) * frame_map(z=aux_anch[2],x=aux_spin_dir) ;
// aux_T is the map that maps the auxiliary object from its initial position to
// the position for the prism. The initial position is centered for a sphere,
// with the axis X aligned for a cylinder, and with the face of a polyhedron
// laying in the XY plane for polyhedra.
aux_T = move(-shift1)
* frame_map(x=backdir, z=base_anch_dir, reverse=true)
* move(-anch_shift)
* auxmap
* aux_to_canonical
* move(shift2);
aux_root = aux_type=="plane" || is_list(aux_type) || aux_anchor==CTR ? apply(aux_T,CTR)
: apply(aux_T * matrix_inverse(aux_to_canonical), aux_anch_pos);
base_root = base_type=="plane" || is_list(base_type) || base_anchor==CTR ? CENTER : base_r*UP;
prism_axis = aux_root-base_root;
base_inside = prism_axis.z<0 ? -1 : 1;
aux_normal = aux_type=="cyl" || aux_type=="sphere" ? apply(aux_T*matrix_inverse(aux_to_canonical), aux_anch[2]) - apply(aux_T*matrix_inverse(aux_to_canonical), CTR)
: apply(aux_T, UP) - apply(aux_T,CTR); // Is this right? Added second term
aux_inside = aux_normal*(base_root-aux_root) < 0 ? -1 : 1;
shaft = rot(from=UP,to=prism_axis, p=zrot(base_spin,BACK));
obj1_back = apply(frame_map(x=backdir,z=base_anch_dir,reverse=true)*rot(from=UP,to=base_anch_dir)*zrot(base_spin),BACK);
obj2_back = aux_type=="plane" ? apply(aux_T,BACK)-apply(aux_T,CTR)
: is_list(aux_type) ? apply(aux_T*matrix_inverse(frame_map(z=aux_anch[2],x=aux_spin_dir)),aux_spin_dir)-apply(aux_T,CTR)
: aux_type=="sphere"? apply(aux_T,aux_spin_dir)-apply(aux_T,CTR)
/*cyl*/ : apply(aux_T*matrix_inverse(aux_to_canonical),aux_spin_dir)-apply(aux_T,CTR);
v1 = vector_perp(prism_axis, shaft);
v2 = vector_perp(prism_axis, obj1_back);
v3 = vector_perp(prism_axis, obj2_back);
sign1 = cross(v1,v2)*prism_axis < 0 ? 1 : -1;
sign2 = cross(v3,v1)*prism_axis < 0 ? 1 : -1;
spin1 = sign1 * vector_angle(v1,v2);
spin2 = -sign2 * vector_angle(v3,v1);
spin = spin_align==1 ? spin1
: spin_align==2 ? spin2
: spin_align==12 ? mean_angle(spin1,spin2)
: spin_align==21 ? mean_angle(spin2,spin1)
: assert(false, str("spin_align must be one of 1, 2, 12, or 21 but was ",spin_align));
multmatrix(tobase)
move(anch_shift)
frame_map(x=backdir, z=base_anch_dir)
move(shift1){
// For debugging spin, shows line in the spin direction
//stroke([aux_root,aux_root+unit(obj2_back)*15], width=1,color="lightblue");
//stroke([base_root,base_root+unit(obj1_back)*15], width=1,color="lightgreen");
if (debug_pos)
move(base_root)rot(from=UP,to=prism_axis)
linear_extrude(height=norm(base_root-aux_root))zrot(base_spin-spin)polygon(profile);
else{
join_prism(zrot(base_spin-spin,profile),
base=base_type, base_r=u_mul(base_r,base_inside),
aux=aux_type, aux_T=aux_T, aux_r=u_mul(aux_r,aux_inside),
scale=scale,
start=base_root, end=aux_root,
base_k=base_k, aux_k=aux_k, base_overlap=base_overlap, aux_overlap=aux_overlap,
base_n=base_n, aux_n=aux_n, base_fillet=base_fillet, aux_fillet=aux_fillet,
base_smooth_normals = base_smooth_normals, aux_smooth_normals=aux_smooth_normals,
debug=debug,
_name1="desc1", _name2="desc2") children();
}
}
else {
base_fillet = first_defined([fillet1,fillet,0]);
aux_fillet = first_defined([fillet2,fillet,0]);
base_overlap = first_defined([overlap1,overlap,1]);
aux_overlap = first_defined([overlap2,overlap,1]);
base_n = first_defined([n1,n,15]);
aux_n = first_defined([n2,n,15]);
base_uniform = first_defined([uniform1, uniform, true]);
aux_uniform = first_defined([uniform2, uniform, true]);
base_k = first_defined([k1,k,0.7]);
aux_k = first_defined([k2,k,0.7]);
profile = force_path(profile,"profile");
dummy0 = assert(is_path(profile,2), "profile must be a 2d path");
corrected_base_anchor = is_vector(anchor1) && norm(anchor1)==0 ? _find_center_anchor(desc1,desc2,anchor2,true) : undef;
corrected_aux_anchor = is_vector(anchor2) && norm(anchor2)==0 ? _find_center_anchor(desc2,desc1,anchor1,false) : undef;
base_anchor=is_string(anchor1) ? anchor1
: is_def(corrected_base_anchor) ? corrected_base_anchor[0]
: point3d(anchor1);
aux_anchor=is_string(anchor2) ? anchor2
: is_def(corrected_aux_anchor) ? corrected_aux_anchor[0]
: point3d(anchor2);
base=desc1;
aux=desc2;
current = parent();
tobase = current==base ? ident(4) : linear_solve(current[0],base[0]); // Maps from current frame into the base frame
auxmap = linear_solve(base[0], aux[0]); // Map from the base (desc1) coordinate frame into the aux (desc2) coordinate frame
dummy =
assert(is_rotation(auxmap), "desc1 and desc2 are not related to each other by a rotation (and translation)");
base_type = _get_obj_type(1,base[1],base_anchor,profile);
base_axis = base_type=="cyl" ? base[1][5] : RIGHT;
base_edge = _is_geom_an_edge(base[1],base_anchor);
base_r = in_list(base_type,["cyl","sphere"]) ? base[1][1] : 1;
base_anch = _find_anchor(base_anchor, base[1]);
base_spin = base_anch[3];
base_anch_pos = base_anch[1];
base_anch_dir = base_anch[2];
prelim_shift1 = _check_join_shift(1,base_type,shift1,true);
shift1 = corrected_base_anchor ? corrected_base_anchor[1] + prelim_shift1 : prelim_shift1;
aux_type = _get_obj_type(2,aux[1],aux_anchor,profile);
aux_anch = _find_anchor(aux_anchor, aux[1]);
aux_edge = _is_geom_an_edge(aux[1],aux_anchor);
aux_r = in_list(aux_type,["cyl","sphere"]) ? aux[1][1] : 1;
aux_anch_pos = aux_anch[1];
aux_anch_dir = aux_anch[2];
aux_spin = aux_anch[3];
aux_spin_dir = apply(rot(from=UP,to=aux_anch[2])*zrot(aux_spin),BACK);
aux_axis = aux_type=="cyl" ? aux[1][5] : RIGHT;
prelim_shift2 = _check_join_shift(2,aux_type,shift2,false);
shift2 = corrected_aux_anchor ? corrected_aux_anchor[1] + prelim_shift2 : prelim_shift2;
base_smooth_normals = first_defined([smooth_normals1, smooth_normals,!base_edge]);
aux_smooth_normals = first_defined([smooth_normals2, smooth_normals,!aux_edge]);
backdir = base_type=="cyl" ? base_axis
: apply(rot(from=UP,to=base_anch_dir)*zrot(base_spin),BACK);
anch_shift = base_type=="plane" || is_list(base_type) ? base_anch_pos : CENTER;
// Map from the starting position for a join_prism object to
// the standard position for the object.
// If the aux object is an edge (type is a list) then the starting position
// of the prism is with the edge lying on the X axis and the object below.
aux_to_canonical = aux_type=="sphere" ? IDENT
: aux_type=="cyl" ? frame_map(x=aux_axis, z=aux_anch[2])
: aux_type=="plane" ? move(aux_anch_pos) * rot(from=UP, to=aux_anch[2])*zrot(aux_spin)
: /* list */ move(aux_anch_pos) * frame_map(z=aux_anch[2],x=aux_spin_dir) ;
// aux_T is the map that maps the auxiliary object from its initial position to
// the position for the prism. The initial position is centered for a sphere,
// with the axis X aligned for a cylinder, and with the face of a polyhedron
// laying in the XY plane for polyhedra.
aux_T = move(-shift1)
* frame_map(x=backdir, z=base_anch_dir, reverse=true)
* move(-anch_shift)
* auxmap
* aux_to_canonical
* move(shift2);
aux_root = aux_type=="plane" || is_list(aux_type) || aux_anchor==CTR ? apply(aux_T,CTR)
: apply(aux_T * matrix_inverse(aux_to_canonical), aux_anch_pos);
base_root = base_type=="plane" || is_list(base_type) || base_anchor==CTR ? CENTER : base_r*UP;
prism_axis = aux_root-base_root;
base_inside = prism_axis.z<0 ? -1 : 1;
aux_normal = aux_type=="cyl" || aux_type=="sphere" ? apply(aux_T*matrix_inverse(aux_to_canonical), aux_anch[2]) - apply(aux_T*matrix_inverse(aux_to_canonical), CTR)
: apply(aux_T, UP) - apply(aux_T,CTR); // Is this right? Added second term
aux_inside = aux_normal*(base_root-aux_root) < 0 ? -1 : 1;
shaft = rot(from=UP,to=prism_axis, p=zrot(base_spin,BACK));
obj1_back = apply(frame_map(x=backdir,z=base_anch_dir,reverse=true)*rot(from=UP,to=base_anch_dir)*zrot(base_spin),BACK);
obj2_back = aux_type=="plane" ? apply(aux_T,BACK)-apply(aux_T,CTR)
: is_list(aux_type) ? apply(aux_T*matrix_inverse(frame_map(z=aux_anch[2],x=aux_spin_dir)),aux_spin_dir)-apply(aux_T,CTR)
: aux_type=="sphere"? apply(aux_T,aux_spin_dir)-apply(aux_T,CTR)
/*cyl*/ : apply(aux_T*matrix_inverse(aux_to_canonical),aux_spin_dir)-apply(aux_T,CTR);
v1 = vector_perp(prism_axis, shaft);
v2 = vector_perp(prism_axis, obj1_back);
v3 = vector_perp(prism_axis, obj2_back);
sign1 = cross(v1,v2)*prism_axis < 0 ? 1 : -1;
sign2 = cross(v3,v1)*prism_axis < 0 ? 1 : -1;
spin1 = sign1 * vector_angle(v1,v2);
spin2 = -sign2 * vector_angle(v3,v1);
spin = spin_align==1 ? spin1
: spin_align==2 ? spin2
: spin_align==12 ? mean_angle(spin1,spin2)
: spin_align==21 ? mean_angle(spin2,spin1)
: assert(false, str("spin_align must be one of 1, 2, 12, or 21 but was ",spin_align));
multmatrix(tobase)
move(anch_shift)
frame_map(x=backdir, z=base_anch_dir)
move(shift1){
// For debugging spin, shows line in the spin direction
//stroke([aux_root,aux_root+unit(obj2_back)*15], width=1,color="lightblue");
//stroke([base_root,base_root+unit(obj1_back)*15], width=1,color="lightgreen");
if (debug_pos)
move(base_root)rot(from=UP,to=prism_axis)
linear_extrude(height=norm(base_root-aux_root))zrot(base_spin-spin)polygon(profile);
else{
join_prism(zrot(base_spin-spin,profile),
base=base_type, base_r=u_mul(base_r,base_inside),
aux=aux_type, aux_T=aux_T, aux_r=u_mul(aux_r,aux_inside),
scale=scale,
start=base_root, end=aux_root,
base_k=base_k, aux_k=aux_k, base_overlap=base_overlap, aux_overlap=aux_overlap,
base_n=base_n, aux_n=aux_n, base_fillet=base_fillet, aux_fillet=aux_fillet,
base_smooth_normals = base_smooth_normals, aux_smooth_normals=aux_smooth_normals,
debug=debug,
_name1="desc1", _name2="desc2") children();
}
}
}
}

View File

@@ -4218,11 +4218,11 @@ module fillet(l, r, ang, r1, r2, excess=0.01, d1, d2,d,length, h, height, anchor
// Synopsis: Generates a surface by evaluating a function on a 2D grid
// SynTags: Geom, VNF
// Topics: Function Plotting
// See Also: cylindrical_heightfield()
// See Also: plot_revolution()
// Usage: As Module
// plot3d(f, xrange, yrange, [zclip=], [zspan=], [base=], [convexity=], [style=]) [ATTACHMENTS];
// plot3d(f, x, y, [zclip=], [zspan=], [base=], [convexity=], [style=]) [ATTACHMENTS];
// Usage: As Function
// vnf = plot3d(f, xrange, yrange, [zclip=], [zspan=], [base=], [style=]);
// vnf = plot3d(f, x, y, [zclip=], [zspan=], [base=], [style=]);
// Description:
// Given a function literal taking 2 parameters and a 2d grid, generate a surface where the height at any point is
// the value of the function. You can specify the grid using a range or using a list of points that
@@ -4241,11 +4241,11 @@ module fillet(l, r, ang, r1, r2, excess=0.01, d1, d2,d,length, h, height, anchor
// in `zspan`.
// Arguments:
// f = function literal accepting two arguments (x and y) that defines the function to compute
// xrange = A list or range of values for x
// yrange = A list or range of values for y
// x = A list or range of values for x
// y = A list or range of values for y
// ---
// zclip = Constrain the function to these bounds.
// zspan = Rescale and shift the function values so the minimum value of f appears at zspan[0] and the maximum at zspan[1].
// zclip = A vector `[zmin,zmax]' that constrains the output of function to these bounds. Cannot be used with `zspan`.
// zspan = Rescale and shift the function values so the minimum value of f appears at zspan[0] and the maximum at zspan[1]. Cannot be used with `zclip`.
// base = Amount of extra thickness to add at the bottom of the model. If set to zero, produce a non-manifold zero-thickness VNF. Default: 1
// style = {{vnf_vertex_array()}} style used to triangulate heightfield textures. Default: "default"
// convexity = Max number of times a line could intersect a wall of the surface being formed. Module only. Default: 10
@@ -4280,22 +4280,22 @@ module fillet(l, r, ang, r1, r2, excess=0.01, d1, d2,d,length, h, height, anchor
// f = function(x,y) 10*(sin(20*x)^2+cos(20*y)^2)/norm([2*x,y]);
// plot3d(f, [10:.3:40], [4:.3:37],zspan=[0,25],anchor=BOT);
module plot3d(f,xrange,yrange,zclip, zspan, base=1, anchor="origin", orient=UP, spin=0, atype="hull", cp="box", convexity=4, style="default")
vnf_polyhedron(plot3d(f,xrange,yrange,zclip, zspan,base, style=style), atype=atype, orient=orient, anchor=anchor, cp=cp, convexity=convexity) children();
module plot3d(f,x,y,zclip, zspan, base=1, anchor="origin", orient=UP, spin=0, atype="hull", cp="box", convexity=4, style="default")
vnf_polyhedron(plot3d(f,x,y,zclip, zspan,base, style=style), atype=atype, orient=orient, anchor=anchor, cp=cp, convexity=convexity) children();
function plot3d(f,xrange,yrange,zclip, zspan, base=1, anchor="origin", orient=UP, spin=0, atype="hull", cp="box", style="default") =
function plot3d(f,x,y,zclip, zspan, base=1, anchor="origin", orient=UP, spin=0, atype="hull", cp="box", style="default") =
assert(is_finite(base) && base>=0, "base must be a nonnegative number")
assert(is_vector(xrange) || valid_range(xrange), "xrange must be a vector or nonempty range")
assert(is_vector(yrange) || valid_range(yrange), "yrange must be a vector or nonempty range")
assert(is_range(xrange) || is_increasing(xrange, strict=true), "xrange must be strictly increasing")
assert(is_range(yrange) || is_increasing(yrange, strict=true), "yrange must be strictly increasing")
assert(is_vector(x) || valid_range(x), "x must be a vector or nonempty range")
assert(is_vector(y) || valid_range(y), "y must be a vector or nonempty range")
assert(is_range(x) || is_increasing(x, strict=true), "x must be strictly increasing")
assert(is_range(y) || is_increasing(y, strict=true), "y must be strictly increasing")
assert(num_defined([zclip,zspan])<2, "Cannot give both zclip and zspan")
assert(is_undef(zclip) || (is_list(zclip) && len(zclip)==2 && is_num(zclip[0]) && is_num(zclip[1])), "zclip must be a list of two values (which may be infinite)")
assert(is_undef(zspan) || (is_vector(zspan,2) && zspan[0]<zspan[1]) ,"zspan must be a 2-vector whose first entry is smaller than the second")
let(
zclip = default(zclip, [-INF,INF]),
data = [for(x=xrange) [for(y=yrange) [x,y,min(max(f(x,y),zclip[0]),zclip[1])]]],
dummy=assert(len(data[0])>1 && len(data)>1, "xrange and yrange must both provide at least 2 points"),
data = [for(x=x) [for(y=y) [x,y,min(max(f(x,y),zclip[0]),zclip[1])]]],
dummy=assert(len(data[0])>1 && len(data)>1, "x and y must both provide at least 2 points"),
minval = min(column(flatten(data),2)),
maxval = max(column(flatten(data),2)),
sdata = is_undef(zspan) ? data
@@ -4320,6 +4320,140 @@ function plot3d(f,xrange,yrange,zclip, zspan, base=1, anchor="origin", orient=UP
// Function&Module: plot_revolution()
// Synopsis: Generates a surface by evaluating a of z and theta and putting the result on a surface of revolution
// SynTags: Geom, VNF
// Topics: Function Plotting
// See Also: plot3d()
// Usage: To create a cylinder or cone (by angle)
// plot_revolution(f, angle, z, [r=/d=] [r1=/d1], [r2=/d2=], [rclip=], [rspan=], [horiz=], [style=], [convexity=], ...) [ATTACHMENTS];
// Usage: To create a cylinder or cone (by arclength)
// plot_revolution(f, arclength=, z=, [r=/d=] [r1=/d1], [r2=/d2=], [rclip=], [rspan=], [horiz=], [style=], [convexity=], ...) [ATTACHMENTS];
// Usage: To create a surface of revolution
// plot_revolution(f, [angle], [arclength=], path=, [rclip=], [rspan=], [horiz=], [style=], [convexity=], ...) [ATTACHMENTS];
// Usage: As Function
// vnf = plot_revolution(...);
// Description:
// Given a function literal, `f`, sets `r=f(theta,z)` over a range of theta and z values, and uses the
// computed r values to define the offset from a cylinder or surface of revolution. You can specify
// the theta range as an angle or based on arc length. If the computed value produces a radius smaller
// than zero it will be rounded up to 0.01. You can specify the cylinder using the usual length and
// radius or diameter parameters, or you can give `path`, a path which whose x values are strictly positive
// to define the textured surface of revolution.
// .
// Your function may have have excessively large values at some points, or you may not know exactly
// what its extreme values are. To manage these situations you can use either the `rclip` or `rspan`
// parameter (but not both). The `rclip` parameter is a 2-vector giving a minimum and maximum
// value, either of which can be infinite. If the function falls below the minimum it is set
// equal to the minimum, and if it rises above the maximum it is set equal to the maximum. The
// `rspan` parameter is a 2-vector giving a minum and maximum value which must both be finite.
// The function's values will be scaled and shifted to exactly cover the range you specifiy
// in `rspan`.
// .
// The default is to erect the function normal to the surface. You can also set `horiz=true` to
// erect the function perpendicular to the rotation axis. In the former case, the caps of the
// model are likely to be irregularly shaped and not exactly the requested size, unless the function
// evaluates to zero at the top and bottom of the path. When `horiz=true` the top and bottom will
// be flat.
// Arguments:
// f = function literal accepting two arguments (x and y) that defines the function to compute
// ---
// r / d = radius or diameter of cylinder
// r1 / d1 = radius or diameter of bottom end
// r2 / d2 = radius or diameter of top end
// path = path to revolve to produce the shape
// rclip = A vector `[rmin,rmax]' that constrains the output of function to these bounds. Cannot be used with `rspan`.
// rspan = Rescale and shift the function values so the minimum value of f appears at rspan[0] and the maximum at rspan[1]. Cannot be used with `rclip`.
// style = {{vnf_vertex_array()}} style used to triangulate heightfield textures. Default: "default"
// convexity = Max number of times a line could intersect a wall of the surface being formed. Module only. Default: 10
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis. See [spin](attachments.scad#subsection-spin). Default: `0`
// orient = Vector to rotate top towards. See [orient](attachments.scad#subsection-orient). Default: `UP`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
// orient = Vector to rotate top toward, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
// atype = Select "hull" or "intersect" anchor type. Default: "hull"
// cp = Centerpoint for determining intersection anchors or centering the shape. Determines the base of the anchor vector. Can be "centroid", "mean", "box" or a 3D point. Default: "centroid"
// Anchor Types:
// "hull" = Anchors to the virtual convex hull of the shape.
// "intersect" = Anchors to the surface of the shape.
// Named Anchors:
// "origin" = Anchor at the origin, oriented UP.
// Example(3D,NoScale):
// f = function (x,y) 5*cos(5*norm([x*180/50,y*180/50]))+5;
// plot_revolution(f, arclength=[-50:1:50], z=[-50:1:50], r=30);
// Example(3D,NoScale): When specifying angle, the pattern shrinks at the top of the cone.
// g = function (x,y) 5*sin(4*x)*cos(6*y)+5;
// plot_revolution(g, z=[-60:2:60], angle=[-180:4:180], r1=30, r2=16);
// Example(3D,NoScale): When specifying arc length, the shape wraps around more cone at the top
// g = function (x,y) 5*sin(8*x)*cos(6*y)+5;
// plot_revolution(g, z=[-60:.5:60], arclength=[-45:.5:45], r1=30,r2=16);
module plot_revolution(f,angle,z,arclength, path, rclip, rspan, horiz=false,r1,r2,r,d1,d2,d,convexity=4,
anchor="origin", orient=UP, spin=0, atype="hull", cp="centroid", style="min_edge")
vnf_polyhedron(plot_revolution(f=f,angle=angle,z=z,arclength=arclength,path=path, rclip=rclip, rspan=rspan, horiz=horiz, style=style,
r=r,d=d,r1=r1,d1=d1,r2=r2,d2=d2), anchor=anchor, orient=orient, spin=spin, atype=atype, cp=cp);
function plot_revolution(f,angle,z,arclength, path, rclip, rspan, horiz=false,r1,r2,r,d1,d2,d,
anchor="origin", orient=UP, spin=0, atype="hull", cp="centroid", style="min_edge") =
assert(num_defined([angle,arclength])==1, "must define exactly one of angle and arclength")
assert(is_vector(z) || valid_range(z), "z must be a vector or nonempty range")
assert(is_range(z) || is_increasing(z, strict=true), "z must be strictly increasing")
assert(is_undef(path) || num_defined([r1,r2,d1,d2,r,d,z])==0, "Cannot define the z parameter or any radius or diameter parameters in combination with path")
assert(num_defined([rclip,rspan])<2, "Cannot give both rclip and rspan")
assert(is_undef(rclip) || (is_list(rclip) && len(rclip)==2 && is_finite(rclip[0]) && rclip[0]>0 && is_num(rclip[1])),
"rclip must be a list of two values (r[1] may be infinite)")
assert(is_undef(rspan) || (is_vector(rspan,2) && rspan[0]>0 && rspan[0]<rspan[1]) ,"rspan must be a 2-vector whose first entry is smaller than the second")
let(
r1 = get_radius(r1=r1, r=r, d1=d1, d=d),
r2 = get_radius(r1=r2, r=r, d1=d2, d=d),
rmin=0.01,
z = list(z),
thetarange = list(first_defined([angle,arclength])),
dummy = assert(is_vector(thetarange) && len(thetarange)>1 && is_increasing(thetarange,strict=true),
"angle/arclength must be a strictly increasing array or range with at least 2 elements")
assert(is_def(arclength) || (last(thetarange)-thetarange[0])<=360, "angle span exceeds 360 degrees"),
path = is_def(path) ? path
: let(
rvals = add_scalar(add_scalar(z,-z[0]) / (last(z)-z[0]) * (r2-r1) ,r1)
)
hstack([rvals,z]),
normals = horiz ? repeat([1,0], len(path))
: path_normals(path),
rclip = default(rclip, [0.01,INF]),
rdata = [for(pt=path)
let(
angscale = is_def(angle) ? 1 : 360/2/PI/pt.x
)
[for(theta=thetarange) min(max(f(theta /*angscale*/,pt.y),rclip[0]),rclip[1])]],
dummy2=assert(len(rdata[0])>1 && len(rdata)>1, "xrange and yrange must both provide at least 2 points"),
minval = min(flatten(rdata)),
maxval = max(flatten(rdata)),
sdata = is_undef(rspan) && minval>0 ? rdata
: is_undef(rspan) ? [for(row=rdata) [for (entry=row) max(rmin,entry)]]
: let(
scale = (rspan[1]-rspan[0])/(maxval-minval)
)
[for(row=rdata) [for (entry=row) scale*(entry.z-minval)+rspan[0]]],
closed = is_def(angle) && last(thetarange)-thetarange[0]==360,
final = [for(i=idx(path))
let(
angscale = is_def(angle) ? 1
: 360/2/PI/path[i].x
)
assert(angscale*(last(thetarange)-thetarange[0])<=360, str("arclength span is more than 360 degrees at profile index ",i," with radius ",path[i].x))
[
if (!closed) [0,0,path[i].y],
for(j=idx(sdata[0]))
cylindrical_to_xyz(path[i].x+sdata[i][j]*normals[i].x, angscale*thetarange[j], path[i].y+sdata[i][j]*normals[i].y)
]
]
)
vnf_vertex_array(final, col_wrap=true, caps=true);
/// Function&Module: heightfield()
/// Synopsis: Generates a 3D surface from a 2D grid of values.
/// SynTags: Geom, VNF
@@ -4461,7 +4595,6 @@ function heightfield(data, size=[100,100], bottom=-20, maxz=100, xrange=[-1:0.04
// Synopsis: Generates a cylindrical 3d surface from a 2D grid of values.
// SynTags: Geom, VNF
// Topics: Extrusion, Textures, Knurling, Heightfield
// See Also: heightfield()
// Usage: As Function
// vnf = cylindrical_heightfield(data, l|length=|h=|height=, r|d=, [base=], [transpose=], [aspect=]);
// Usage: As Module