misc doc fixes, adjust scale to allow independent x and y scales

This commit is contained in:
Adrian Mariano 2022-10-17 19:26:37 -04:00
parent 48292d9e8f
commit 30a7e40cf9
3 changed files with 48 additions and 19 deletions

View file

@ -97,7 +97,8 @@ include <screw_drive.scad>
// from the screw size, but by passing the `drive_size=` argument you can override the default, or // from the screw size, but by passing the `drive_size=` argument you can override the default, or
// in cases where no default exists you can specify it. Flat head screws have variations such as 100 degree // in cases where no default exists you can specify it. Flat head screws have variations such as 100 degree
// angle for UTS, or undercut heads. You can also request a "sharp" screw which will set the screw diameter // angle for UTS, or undercut heads. You can also request a "sharp" screw which will set the screw diameter
// the theoretical maximum and produce sharp corners instead of a flat edge on the head. The flat head options // the theoretical maximum and produce sharp corners instead of a flat edge on the head. For a flat head screw
// the drive specification must start with "flat", but the flat head options
// can be mixed in any order, for example, "flat sharp undercut" or "flat undercut sharp". // can be mixed in any order, for example, "flat sharp undercut" or "flat undercut sharp".
// Subsection: Nuts // Subsection: Nuts
// Nuts come in standard sizes and BOSL2 has tables to produce sizes for both Imperial and metric nuts. // Nuts come in standard sizes and BOSL2 has tables to produce sizes for both Imperial and metric nuts.
@ -2512,7 +2513,7 @@ function _screw_info_metric(diam, pitch, head, thread, drive) =
[20, [10, undef, undef]], [20, [10, undef, undef]],
], ],
entry = struct_val(metric_setscrew, diam), entry = struct_val(metric_setscrew, diam),
dummy=assert(is_def(entry), str("Screw size M",diam," unsupported for headless screws")), dummy=assert(drive=="none" || is_undef(drive) || is_def(entry), str("Screw size M",diam," unsupported for headless screws")),
drive_dim = drive=="hex" ? [["drive_size", entry[0]], ["drive_depth", diam/2]] drive_dim = drive=="hex" ? [["drive_size", entry[0]], ["drive_depth", diam/2]]
: drive=="torx" ? [["drive_size", entry[1]], ["drive_depth", entry[2]]] : drive=="torx" ? [["drive_size", entry[1]], ["drive_depth", entry[2]]]
: drive=="slot" ? [["drive_size", entry[3]], ["drive_depth", entry[4]]] : drive=="slot" ? [["drive_size", entry[3]], ["drive_depth", entry[4]]]

View file

@ -1104,9 +1104,9 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb
// Function&Module: path_sweep() // Function&Module: path_sweep()
// Usage: As module // Usage: As module
// path_sweep(shape, path, [method], [normal=], [closed=], [twist=], [twist_by_length=], [symmetry=], [last_normal=], [tangent=], [uniform=], [relaxed=], [caps=], [style=], [convexity=], [anchor=], [cp=], [spin=], [orient=], [atype=]) [ATTACHMENTS]; // path_sweep(shape, path, [method], [normal=], [closed=], [twist=], [twist_by_length=], [symmetry=], [scale=], [scale_by_length=], [last_normal=], [tangent=], [uniform=], [relaxed=], [caps=], [style=], [convexity=], [anchor=], [cp=], [spin=], [orient=], [atype=]) [ATTACHMENTS];
// Usage: As function // Usage: As function
// vnf = path_sweep(shape, path, [method], [normal=], [closed=], [twist=], [twist_by_length=], [symmetry=], [last_normal=], [tangent=], [uniform=], [relaxed=], [caps=], [style=], [transforms=], [anchor=], [cp=], [spin=], [orient=], [atype=]); // vnf = path_sweep(shape, path, [method], [normal=], [closed=], [twist=], [twist_by_length=], [symmetry=], [scale=], [scale_by_length=], [last_normal=], [tangent=], [uniform=], [relaxed=], [caps=], [style=], [transforms=], [anchor=], [cp=], [spin=], [orient=], [atype=]);
// Description: // Description:
// Takes as input `shape`, a 2D polygon path (list of points), and `path`, a 2d or 3d path (also a list of points) // Takes as input `shape`, a 2D polygon path (list of points), and `path`, a 2d or 3d path (also a list of points)
// and constructs a polyhedron by sweeping the shape along the path. When run as a module returns the polyhedron geometry. // and constructs a polyhedron by sweeping the shape along the path. When run as a module returns the polyhedron geometry.
@ -1223,6 +1223,11 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb
// the cross section orientation. Specifying a list of normal vectors gives you complete control over the orientation of your // the cross section orientation. Specifying a list of normal vectors gives you complete control over the orientation of your
// cross sections and can be useful if you want to position your model to be on the surface of some solid. // cross sections and can be useful if you want to position your model to be on the surface of some solid.
// . // .
// You can also apply scaling to the profile along the path. You can give a list of scalar scale factors or a list of 2-vector scale.
// In the latter scale the x and y scales of the profile are scaled separately before the profile is placed onto the path. For non-closed
// paths you can also give a single scale value or a 2-vector which is treated as the final scale. The intermediate sections
// are then scaled by linear interpolation either relative to length (if scale_by_length is true) or by point count otherwise.
// .
// You can use set `transforms` to true to return a list of transformation matrices instead of the swept shape. In this case, you can // You can use set `transforms` to true to return a list of transformation matrices instead of the swept shape. In this case, you can
// often omit shape entirely. The exception is when `closed=true` and you are using the "incremental" method. In this case, `path_sweep` // often omit shape entirely. The exception is when `closed=true` and you are using the "incremental" method. In this case, `path_sweep`
// uses the shape to correct for twist when the shape closes on itself, so you must include a valid shape. // uses the shape to correct for twist when the shape closes on itself, so you must include a valid shape.
@ -1234,8 +1239,10 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb
// normal = normal vector for initializing the incremental method, or for setting normals with method="manual". Default: UP if the path makes an angle lower than 45 degrees to the xy plane, BACK otherwise. // normal = normal vector for initializing the incremental method, or for setting normals with method="manual". Default: UP if the path makes an angle lower than 45 degrees to the xy plane, BACK otherwise.
// closed = path is a closed loop. Default: false // closed = path is a closed loop. Default: false
// twist = amount of twist to add in degrees. For closed sweeps must be a multiple of 360/symmetry. Default: 0 // twist = amount of twist to add in degrees. For closed sweeps must be a multiple of 360/symmetry. Default: 0
// scale = Amount to scale the profiles. If you give a scalar the scale starts at 1 and ends at your specified value. You can also give a vector of values, one for each path point. Default: 1 (no scaling) // twist_by_length = if true then interpolate twist based on the path length of the path. If false interoplate based on point count. Default: true
// symmetry = symmetry of the shape when closed=true. Allows the shape to join with a 360/symmetry rotation instead of a full 360 rotation. Default: 1 // symmetry = symmetry of the shape when closed=true. Allows the shape to join with a 360/symmetry rotation instead of a full 360 rotation. Default: 1
// scale = Amount to scale the profiles. If you give a scalar the scale starts at 1 and ends at your specified value. The same is true for a 2-vector, but x and y are scaled separately. You can also give a vector of values, one for each path point, and you can give a list of 2-vectors that give the x and y scales of your profile for every point on the path (a Nx2 matrix for a path of length N. Default: 1 (no scaling)
// scale_by_length = if true then interpolate scale based on the path length of the path. If false interoplate based on point count. Default: true
// last_normal = normal to last point in the path for the "incremental" method. Constrains the orientation of the last cross section if you supply it. // last_normal = normal to last point in the path for the "incremental" method. Constrains the orientation of the last cross section if you supply it.
// uniform = if set to false then compute tangents using the uniform=false argument, which may give better results when your path is non-uniformly sampled. This argument is passed to {{path_tangents()}}. Default: true // uniform = if set to false then compute tangents using the uniform=false argument, which may give better results when your path is non-uniformly sampled. This argument is passed to {{path_tangents()}}. Default: true
// tangent = a list of tangent vectors in case you need more accuracy (particularly at the end points of your curve) // tangent = a list of tangent vectors in case you need more accuracy (particularly at the end points of your curve)
@ -1492,6 +1499,18 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb
// outside = [for(i=[0:len(trans)-1]) trans[i]*scale(lerp(1,1.5,i/(len(trans)-1)))]; // outside = [for(i=[0:len(trans)-1]) trans[i]*scale(lerp(1,1.5,i/(len(trans)-1)))];
// inside = [for(i=[len(trans)-1:-1:0]) trans[i]*scale(lerp(1.1,1.4,i/(len(trans)-1)))]; // inside = [for(i=[len(trans)-1:-1:0]) trans[i]*scale(lerp(1.1,1.4,i/(len(trans)-1)))];
// sweep(shape, concat(outside,inside),closed=true); // sweep(shape, concat(outside,inside),closed=true);
// Example(NoScales): An easier way to scale your model is to use the scale parameter.
// elliptic_arc = xscale(2, p=arc($fn=64,angle=[0,180], r=3));
// path_sweep(pentagon(r=1), path3d(elliptic_arc), scale=2);
// Example(NoScales): Scaling only in the y direction of the profile (z direction in the model in this case)
// elliptic_arc = xscale(2, p=arc($fn=64,angle=[0,180], r=3));
// path_sweep(rect(2), path3d(elliptic_arc), scale=[1,2]);
// Example(NoScales): Specifying scale at every point for a closed path
// N=64;
// path = circle(r=5, $fn=64);
// theta = lerpn(0,360,N,endpoint=false);
// scale = [for(t=theta) sin(6*t)/5+1];
// path_sweep(rect(2), path3d(path), closed=true, scale=scale);
// Example(Med,NoScales): Using path_sweep on a region // Example(Med,NoScales): Using path_sweep on a region
// rgn1 = [for (d=[10:10:60]) circle(d=d,$fn=8)]; // rgn1 = [for (d=[10:10:60]) circle(d=d,$fn=8)];
// rgn2 = [square(30,center=false)]; // rgn2 = [square(30,center=false)];
@ -1518,17 +1537,17 @@ module spiral_sweep(poly, h, r, turns=1, higbee, center, r1, r2, d, d1, d2, higb
// method="manual", normal=UP); // method="manual", normal=UP);
// } // }
module path_sweep(shape, path, method="incremental", normal, closed, twist=0, twist_by_length=true, scale=1, module path_sweep(shape, path, method="incremental", normal, closed, twist=0, twist_by_length=true, scale=1, scale_by_length=true,
symmetry=1, last_normal, tangent, uniform=true, relaxed=false, caps, style="min_edge", convexity=10, symmetry=1, last_normal, tangent, uniform=true, relaxed=false, caps, style="min_edge", convexity=10,
anchor="origin",cp="centroid",spin=0, orient=UP, atype="hull",profiles=false,width=1) anchor="origin",cp="centroid",spin=0, orient=UP, atype="hull",profiles=false,width=1)
{ {
dummy = assert(is_region(shape) || is_path(shape,2), "shape must be a 2D path or region"); dummy = assert(is_region(shape) || is_path(shape,2), "shape must be a 2D path or region");
vnf = path_sweep(shape, path, method, normal, closed, twist, twist_by_length, scale, vnf = path_sweep(shape, path, method, normal, closed, twist, twist_by_length, scale, scale_by_length,
symmetry, last_normal, tangent, uniform, relaxed, caps, style); symmetry, last_normal, tangent, uniform, relaxed, caps, style);
if (profiles){ if (profiles){
assert(in_list(atype, _ANCHOR_TYPES), "Anchor type must be \"hull\" or \"intersect\""); assert(in_list(atype, _ANCHOR_TYPES), "Anchor type must be \"hull\" or \"intersect\"");
tran = path_sweep(shape, path, method, normal, closed, twist, twist_by_length, scale, tran = path_sweep(shape, path, method, normal, closed, twist, twist_by_length, scale, scale_by_length,
symmetry, last_normal, tangent, uniform, relaxed,transforms=true); symmetry, last_normal, tangent, uniform, relaxed,transforms=true);
rshape = is_path(shape) ? [path3d(shape)] rshape = is_path(shape) ? [path3d(shape)]
: [for(s=shape) path3d(s)]; : [for(s=shape) path3d(s)];
@ -1543,11 +1562,11 @@ module path_sweep(shape, path, method="incremental", normal, closed, twist=0, tw
} }
function path_sweep(shape, path, method="incremental", normal, closed, twist=0, twist_by_length=true, scale=1, function path_sweep(shape, path, method="incremental", normal, closed, twist=0, twist_by_length=true, scale=1, scale_by_length=true,
symmetry=1, last_normal, tangent, uniform=true, relaxed=false, caps, style="min_edge", transforms=false, symmetry=1, last_normal, tangent, uniform=true, relaxed=false, caps, style="min_edge", transforms=false,
anchor="origin",cp="centroid",spin=0, orient=UP, atype="hull") = anchor="origin",cp="centroid",spin=0, orient=UP, atype="hull") =
is_1region(path) ? path_sweep(shape=shape,path=path[0], method=method, normal=normal, closed=default(closed,true), is_1region(path) ? path_sweep(shape=shape,path=path[0], method=method, normal=normal, closed=default(closed,true),
twist=twist, scale=scale, twist_by_length=twist_by_length, symmetry=symmetry, last_normal=last_normal, twist=twist, scale=scale, scale_by_length=scale_by_length, twist_by_length=twist_by_length, symmetry=symmetry, last_normal=last_normal,
tangent=tangent, uniform=uniform, relaxed=relaxed, caps=caps, style=style, transforms=transforms, tangent=tangent, uniform=uniform, relaxed=relaxed, caps=caps, style=style, transforms=transforms,
anchor=anchor, cp=cp, spin=spin, orient=orient, atype=atype) : anchor=anchor, cp=cp, spin=spin, orient=orient, atype=atype) :
let(closed=default(closed,false)) let(closed=default(closed,false))
@ -1561,12 +1580,15 @@ function path_sweep(shape, path, method="incremental", normal, closed, twist=0,
assert((is_region(shape) || is_path(shape,2)) || (transforms && !(closed && method=="incremental")),"shape must be a 2d path or region") assert((is_region(shape) || is_path(shape,2)) || (transforms && !(closed && method=="incremental")),"shape must be a 2d path or region")
let( let(
path = path3d(path), path = path3d(path),
f=echo(caps=caps),
caps = is_def(caps) ? caps : caps = is_def(caps) ? caps :
closed ? false : true, closed ? false : true,
capsOK = is_bool(caps) || is_bool_list(caps,2), capsOK = is_bool(caps) || is_bool_list(caps,2),
fullcaps = is_bool(caps) ? [caps,caps] : caps, fullcaps = is_bool(caps) ? [caps,caps] : caps,
normalOK = is_undef(normal) || (method!="natural" && is_vector(normal,3)) normalOK = is_undef(normal) || (method!="natural" && is_vector(normal,3))
|| (method=="manual" && same_shape(normal,path)) || (method=="manual" && same_shape(normal,path)),
scaleOK = scale==1 || ((is_num(scale) || is_vector(scale,2)) && !closed) || is_vector(scale,len(path)) || is_matrix(scale,len(path),2)
) )
assert(normalOK, method=="natural" ? "Cannot specify normal with the \"natural\" method" assert(normalOK, method=="natural" ? "Cannot specify normal with the \"natural\" method"
: method=="incremental" ? "Normal with \"incremental\" method must be a 3-vector" : method=="incremental" ? "Normal with \"incremental\" method must be a 3-vector"
@ -1575,16 +1597,21 @@ function path_sweep(shape, path, method="incremental", normal, closed, twist=0,
assert(!closed || !caps, "Cannot make closed shape with caps") assert(!closed || !caps, "Cannot make closed shape with caps")
assert(is_undef(normal) || (is_vector(normal) && len(normal)==3) || (is_path(normal) && len(normal)==len(path) && len(normal[0])==3), "Invalid normal specified") assert(is_undef(normal) || (is_vector(normal) && len(normal)==3) || (is_path(normal) && len(normal)==len(path) && len(normal[0])==3), "Invalid normal specified")
assert(is_undef(tangent) || (is_path(tangent) && len(tangent)==len(path) && len(tangent[0])==3), "Invalid tangent specified") assert(is_undef(tangent) || (is_path(tangent) && len(tangent)==len(path) && len(tangent[0])==3), "Invalid tangent specified")
assert(is_num(scale) || is_vector(scale,len(path)), str("Incompatible or invalid scale: must be a scalar or vector of length ",len(path))) assert(scaleOK,str("Incompatible or invalid scale",closed?" for closed path":"",": must be ", closed?"":"a scalar, a 2-vector, ",
"a vector of length ",len(path)," or a ",len(path),"x2 matrix of scales"))
let( let(
scale = is_num(scale) ? lerpn(1,scale,len(path)) : scale, scale = !(is_num(scale) || is_vector(scale,2)) ? scale
: let(s=is_num(scale) ? [scale,scale] : scale)
!scale_by_length ? lerpn([1,1],s,len(path))
: lerp([1,1],s, path_length_fractions(path,false)),
scale_list = [for(s=scale) scale(s),if (closed) scale(scale[0])], scale_list = [for(s=scale) scale(s),if (closed) scale(scale[0])],
tangents = is_undef(tangent) ? path_tangents(path,uniform=uniform,closed=closed) : [for(t=tangent) unit(t)], tangents = is_undef(tangent) ? path_tangents(path,uniform=uniform,closed=closed) : [for(t=tangent) unit(t)],
normal = is_path(normal) ? [for(n=normal) unit(n)] : normal = is_path(normal) ? [for(n=normal) unit(n)] :
is_def(normal) ? unit(normal) : is_def(normal) ? unit(normal) :
method =="incremental" && abs(tangents[0].z) > 1/sqrt(2) ? BACK : UP, method =="incremental" && abs(tangents[0].z) > 1/sqrt(2) ? BACK : UP,
normals = is_path(normal) ? normal : repeat(normal,len(path)), normals = is_path(normal) ? normal : repeat(normal,len(path)),
pathfrac = twist_by_length ? path_length_fractions(path, closed) : [for(i=[0:1:len(path)]) i / (len(path)-(closed?0:1))], tpathfrac = twist_by_length ? path_length_fractions(path, closed) : [for(i=[0:1:len(path)]) i / (len(path)-(closed?0:1))],
spathfrac = scale_by_length ? path_length_fractions(path, closed) : [for(i=[0:1:len(path)]) i / (len(path)-(closed?0:1))],
L = len(path), L = len(path),
unscaled_transform_list = unscaled_transform_list =
method=="incremental" ? method=="incremental" ?
@ -1618,7 +1645,7 @@ function path_sweep(shape, path, method="incremental", normal, closed, twist=0,
twistfix = correction_twist%(360/symmetry), twistfix = correction_twist%(360/symmetry),
adjusted_final = !closed ? undef : adjusted_final = !closed ? undef :
translate(path[0]) * rotations[0] * zrot(-correction_twist+correction_twist%(360/symmetry)-twist) translate(path[0]) * rotations[0] * zrot(-correction_twist+correction_twist%(360/symmetry)-twist)
) [for(i=idx(path)) translate(path[i]) * rotations[i] * zrot((twistfix-twist)*pathfrac[i]), if(closed) adjusted_final] ) [for(i=idx(path)) translate(path[i]) * rotations[i] * zrot((twistfix-twist)*tpathfrac[i]), if(closed) adjusted_final]
: method=="manual" ? : method=="manual" ?
[for(i=[0:L-(closed?0:1)]) let( [for(i=[0:L-(closed?0:1)]) let(
ynormal = relaxed ? normals[i%L] : normals[i%L] - (normals[i%L] * tangents[i%L])*tangents[i%L], ynormal = relaxed ? normals[i%L] : normals[i%L] - (normals[i%L] * tangents[i%L])*tangents[i%L],
@ -1626,7 +1653,7 @@ function path_sweep(shape, path, method="incremental", normal, closed, twist=0,
rotation = frame_map(y=ynormal, z=znormal) rotation = frame_map(y=ynormal, z=znormal)
) )
assert(approx(ynormal*znormal,0),str("Supplied normal is parallel to the path tangent at point ",i)) assert(approx(ynormal*znormal,0),str("Supplied normal is parallel to the path tangent at point ",i))
translate(path[i%L])*rotation*zrot(-twist*pathfrac[i]) translate(path[i%L])*rotation*zrot(-twist*tpathfrac[i])
] ]
: method=="natural" ? // map x axis of shape to the path normal, which points in direction of curvature : method=="natural" ? // map x axis of shape to the path normal, which points in direction of curvature
let (pathnormal = path_normals(path, tangents, closed)) let (pathnormal = path_normals(path, tangents, closed))
@ -1638,7 +1665,7 @@ function path_sweep(shape, path, method="incremental", normal, closed, twist=0,
[for(i=[0:L-(closed?0:1)]) let( [for(i=[0:L-(closed?0:1)]) let(
rotation = frame_map(x=pathnormal[i%L], z=tangents[i%L]) rotation = frame_map(x=pathnormal[i%L], z=tangents[i%L])
) )
translate(path[i%L])*rotation*zrot(-twist*pathfrac[i]) translate(path[i%L])*rotation*zrot(-twist*tpathfrac[i])
] ]
: assert(false,"Unknown method or no method given"), // unknown method : assert(false,"Unknown method or no method given"), // unknown method
transform_list = v_mul(unscaled_transform_list, scale_list), transform_list = v_mul(unscaled_transform_list, scale_list),

View file

@ -1381,7 +1381,8 @@ module generic_threaded_nut(
// . // .
// Unlike generic_threaded_rod, when internal=true this module generates the threads, not a thread mask. // Unlike generic_threaded_rod, when internal=true this module generates the threads, not a thread mask.
// The profile needs to be inverted to produce the proper thread form. If you use the built-in trapezoidal // The profile needs to be inverted to produce the proper thread form. If you use the built-in trapezoidal
// thread you get the inverted thread, designed so that the inner diameter is d. With adequate clearance // thread you get the inverted thread, designed so that the inner diameter is d. If you supply a custom profile
// you must invert it yourself to get internal threads. With adequate clearance
// this thread will mate with the thread that uses the same parameters but has internal=false. Note that // this thread will mate with the thread that uses the same parameters but has internal=false. Note that
// unlike the threaded_rod modules, thread_helix does not adjust the diameter for faceting, nor does it // unlike the threaded_rod modules, thread_helix does not adjust the diameter for faceting, nor does it
// subtract any $slop for clearance. // subtract any $slop for clearance.
@ -1398,7 +1399,7 @@ module generic_threaded_nut(
// profile = If an asymmetrical thread profile is needed, it can be specified here. // profile = If an asymmetrical thread profile is needed, it can be specified here.
// starts = The number of thread starts. Default: 1 // starts = The number of thread starts. Default: 1
// left_handed = If true, thread has a left-handed winding. // left_handed = If true, thread has a left-handed winding.
// internal = If true, invert threads for internal threading. // internal = If true, apply tapers for internal threading, and invert the default profile. Default: false
// d1 = Bottom inside base diameter of threads. // d1 = Bottom inside base diameter of threads.
// d2 = Top inside base diameter of threads. // d2 = Top inside base diameter of threads.
// higbee = Length to taper thread ends over. Default: 0 // higbee = Length to taper thread ends over. Default: 0