diff --git a/rounding.scad b/rounding.scad index 50bb712a..948ef18f 100644 --- a/rounding.scad +++ b/rounding.scad @@ -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(); - } - } + } } diff --git a/shapes3d.scad b/shapes3d.scad index 4df49f3e..ef4f2fe3 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -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]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]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