mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2024-12-29 00:09:41 +00:00
Reworked attachable() internals, and provided geometry only function equivalent called reorient().
This commit is contained in:
parent
863398eb24
commit
6ec5013835
5 changed files with 434 additions and 190 deletions
417
attachments.scad
417
attachments.scad
|
@ -89,7 +89,6 @@ $tags_hidden = [];
|
|||
|
||||
// Section: Functions
|
||||
|
||||
|
||||
// Function: anchorpt()
|
||||
// Usage:
|
||||
// anchor(name, pos, [dir], [rot])
|
||||
|
@ -103,6 +102,142 @@ $tags_hidden = [];
|
|||
function anchorpt(name, pos=[0,0,0], orient=UP, spin=0) = [name, pos, orient, spin];
|
||||
|
||||
|
||||
// Function: attach_geom()
|
||||
//
|
||||
// Usage:
|
||||
// geom = attach_geom(anchor, spin, [orient], two_d, size, [size2], [shift], [offset], [anchors]);
|
||||
// geom = attach_geom(anchor, spin, [orient], two_d, r|d, [offset], [anchors]);
|
||||
// geom = attach_geom(anchor, spin, [orient], two_d, path, [extent], [offset], [anchors]);
|
||||
// geom = attach_geom(anchor, spin, [orient], size, [size2], [shift], [offset], [anchors]);
|
||||
// geom = attach_geom(anchor, spin, [orient], r|d, l, [offset], [anchors]);
|
||||
// geom = attach_geom(anchor, spin, [orient], r1|d1, r2|d2, l, [offset], [anchors]);
|
||||
// geom = attach_geom(anchor, spin, [orient], r|d, [offset], [anchors]);
|
||||
// geom = attach_geom(anchor, spin, [orient], vnf, [extent], [offset], [anchors]);
|
||||
//
|
||||
// Description:
|
||||
// Given arguments that describe the geometry of an attachable object, returns the internal geometry description.
|
||||
//
|
||||
// Arguments:
|
||||
// size = If given as a 3D vector, contains the XY size of the bottom of the cuboidal/prismoidal volume, and the Z height. If given as a 2D vector, contains the front X width of the rectangular/trapezoidal shape, and the Y length.
|
||||
// size2 = If given as a 2D vector, contains the XY size of the top of the prismoidal volume. If given as a number, contains the back width of the trapezoidal shape.
|
||||
// shift = If given as a 2D vector, shifts the top of the prismoidal or conical shape by the given amount. If given as a number, shifts the back of the trapezoidal shape right by that amount. Default: No shift.
|
||||
// r = Radius of the cylindrical/conical volume.
|
||||
// d = Diameter of the cylindrical/conical volume.
|
||||
// r1 = Radius of the bottom of the conical volume.
|
||||
// r2 = Radius of the top of the conical volume.
|
||||
// d1 = Diameter of the bottom of the conical volume.
|
||||
// d2 = Diameter of the top of the conical volume.
|
||||
// l = Length of the cylindrical/conical volume along axis.
|
||||
// vnf = The [VNF](vnf.scad) of the volume.
|
||||
// path = The path to generate a polygon from.
|
||||
// extent = If true, calculate anchors by extents, rather than intersection. Default: false.
|
||||
// offset = If given, offsets the center of the volume.
|
||||
// anchors = If given as a list of anchor points, allows named anchor points.
|
||||
// two_d = If true, the attachable shape is 2D. If false, 3D. Default: false (3D)
|
||||
//
|
||||
// Example(NORENDER): Cubical Shape
|
||||
// geom = attach_geom(anchor, spin, orient, size=size);
|
||||
//
|
||||
// Example(NORENDER): Prismoidal Shape
|
||||
// geom = attach_geom(
|
||||
// anchor, spin, orient,
|
||||
// size=point3d(botsize,h),
|
||||
// size2=topsize, shift=shift
|
||||
// );
|
||||
//
|
||||
// Example(NORENDER): Cylindrical Shape
|
||||
// geom = attach_geom(anchor, spin, orient, r=r, h=h);
|
||||
//
|
||||
// Example(NORENDER): Conical Shape
|
||||
// geom = attach_geom(anchor, spin, orient, r1=r1, r2=r2, h=h);
|
||||
//
|
||||
// Example(NORENDER): Spherical Shape
|
||||
// geom = attach_geom(anchor, spin, orient, r=r);
|
||||
//
|
||||
// Example(NORENDER): Arbitrary VNF Shape
|
||||
// geom = attach_geom(anchor, spin, orient, vnf=vnf);
|
||||
//
|
||||
// Example(NORENDER): 2D Rectangular Shape
|
||||
// geom = attach_geom(anchor, spin, orient, size=size);
|
||||
//
|
||||
// Example(NORENDER): 2D Trapezoidal Shape
|
||||
// geom = attach_geom(
|
||||
// anchor, spin, orient,
|
||||
// size=[x1,y], size2=x2, shift=shift
|
||||
// );
|
||||
//
|
||||
// Example(NORENDER): 2D Circular Shape
|
||||
// geom = attach_geom(anchor, spin, orient, two_d=true, r=r);
|
||||
//
|
||||
// Example(NORENDER): Arbitrary 2D Polygon Shape
|
||||
// geom = attach_geom(anchor, spin, orient, path=path);
|
||||
//
|
||||
function attach_geom(
|
||||
size, size2, shift,
|
||||
r,r1,r2, d,d1,d2, l,h,
|
||||
vnf, path,
|
||||
extent=true,
|
||||
offset=[0,0,0],
|
||||
anchors=[],
|
||||
two_d=false
|
||||
) =
|
||||
assert(is_vector(offset))
|
||||
assert(is_list(anchors))
|
||||
!is_undef(size)? (
|
||||
two_d? (
|
||||
let(
|
||||
size2 = default(size2, size.x),
|
||||
shift = default(shift, 0)
|
||||
)
|
||||
assert(is_vector(size) && len(size)==2)
|
||||
assert(is_num(size2))
|
||||
assert(is_num(shift))
|
||||
["rect", point2d(size), size2, shift, offset, anchors]
|
||||
) : (
|
||||
let(
|
||||
size2 = default(size2, point2d(size)),
|
||||
shift = default(shift, [0,0])
|
||||
)
|
||||
assert(is_vector(size) && len(size)==3)
|
||||
assert(is_vector(size2) && len(size2)==2)
|
||||
assert(is_vector(shift) && len(shift)==2)
|
||||
["cuboid", size, size2, shift, offset, anchors]
|
||||
)
|
||||
) : !is_undef(vnf)? (
|
||||
assert(is_vnf(vnf))
|
||||
assert(two_d == false)
|
||||
extent? ["vnf_extent", vnf, offset, anchors] :
|
||||
["vnf_isect", vnf, offset, anchors]
|
||||
) : !is_undef(path)? (
|
||||
assert(is_path(path))
|
||||
assert(two_d == true)
|
||||
extent? ["path_extent", path, offset, anchors] :
|
||||
["path_isect", path, offset, anchors]
|
||||
) :
|
||||
let(
|
||||
r1 = get_radius(r1=r1,d1=d1,r=r,d=d,dflt=undef)
|
||||
)
|
||||
!is_undef(r1)? (
|
||||
assert(is_num(r1))
|
||||
let( l = default(l, h) )
|
||||
!is_undef(l)? (
|
||||
let(
|
||||
shift = default(shift, [0,0]),
|
||||
r2 = get_radius(r1=r2,d1=d2,r=r,d=d,dflt=undef)
|
||||
)
|
||||
assert(is_num(l))
|
||||
assert(is_num(r2))
|
||||
assert(is_vector(shift) && len(shift)==2)
|
||||
["cyl", r1, r2, l, shift, offset, anchors]
|
||||
) : (
|
||||
two_d? ["circle", r1, offset, anchors] :
|
||||
["spheroid", r1, offset, anchors]
|
||||
)
|
||||
) :
|
||||
assert(false, "Unrecognizable geometry description.");
|
||||
|
||||
|
||||
|
||||
// Function: attach_geom_2d()
|
||||
// Usage:
|
||||
// attach_geom_2d(geom);
|
||||
|
@ -156,6 +291,65 @@ function attach_geom_size(geom) =
|
|||
assert(false, "Unknown attachment geometry type.");
|
||||
|
||||
|
||||
// Function: attach_transform()
|
||||
// Usage:
|
||||
// mat = attach_transform(anchor=CENTER, spin=0, orient=UP, geom);
|
||||
// Description:
|
||||
// Returns the affine3d transformation matrix needed to `anchor`, `spin`, and `orient`
|
||||
// the given geometry `geom` shape into position.
|
||||
// Arguments:
|
||||
// anchor = Anchor point to translate to the origin `[0,0,0]`. See [anchor](attachments.scad#anchor). Default: `CENTER`
|
||||
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
|
||||
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP`
|
||||
// geom = The geometry description of the shape.
|
||||
// p = If given as a VNF, path, or point, applies the affine3d transformation matrix to it and returns the result.
|
||||
function attach_transform(anchor=CENTER, spin=0, orient=UP, geom, p) =
|
||||
assert(is_string(anchor) || is_vector(anchor))
|
||||
assert(is_num(spin))
|
||||
assert(is_vector(orient))
|
||||
let(
|
||||
two_d = attach_geom_2d(geom),
|
||||
m = ($attach_to != undef)? (
|
||||
let(
|
||||
anch = find_anchor($attach_to, geom),
|
||||
pos = anch[1]
|
||||
) two_d? (
|
||||
let(
|
||||
ang = vector_angle(anch[2], BACK)
|
||||
)
|
||||
affine3d_zrot(ang+spin) *
|
||||
affine3d_translate(point3d(-pos))
|
||||
) : (
|
||||
let(
|
||||
ang = vector_angle(anch[2], DOWN),
|
||||
axis = vector_axis(anch[2], DOWN),
|
||||
ang2 = (anch[2]==UP || anch[2]==DOWN)? 0 : 180-anch[3],
|
||||
axis2 = rotate_points3d([axis],[0,0,ang2])[0]
|
||||
)
|
||||
affine3d_rot_by_axis(axis2,ang) *
|
||||
affine3d_zrot(ang2+spin) *
|
||||
affine3d_translate(point3d(-pos))
|
||||
)
|
||||
) : (
|
||||
let(
|
||||
pos = find_anchor(anchor, geom)[1]
|
||||
) two_d? (
|
||||
affine3d_zrot(spin) *
|
||||
affine3d_translate(point3d(-pos))
|
||||
) : (
|
||||
let(
|
||||
axis = vector_axis(UP,orient),
|
||||
ang = vector_angle(UP,orient)
|
||||
)
|
||||
affine3d_rot_by_axis(axis,ang) *
|
||||
affine3d_zrot(spin) *
|
||||
affine3d_translate(point3d(-pos))
|
||||
)
|
||||
)
|
||||
) is_undef(p)? m :
|
||||
is_vnf(p)? [apply(m, p[0]), p[1]] :
|
||||
apply(m, p);
|
||||
|
||||
|
||||
// Function: find_anchor()
|
||||
// Usage:
|
||||
|
@ -325,6 +519,18 @@ function find_anchor(anchor, geom) =
|
|||
assert(false, "Unknown attachment geometry type.");
|
||||
|
||||
|
||||
// Function: attachment_is_shown()
|
||||
// Usage:
|
||||
// attachment_is_shown(tags);
|
||||
// Description:
|
||||
// Returns true if the given space-delimited string of tag names should currently be shown.
|
||||
function attachment_is_shown(tags) =
|
||||
let(
|
||||
tags = _str_char_split(tags, " "),
|
||||
shown = !$tags_shown || any([for (tag=tags) in_list(tag, $tags_shown)]),
|
||||
hidden = any([for (tag=tags) in_list(tag, $tags_hidden)])
|
||||
) shown && !hidden;
|
||||
|
||||
|
||||
function _str_char_split(s,delim,n=0,acc=[],word="") =
|
||||
(n>=len(s))? concat(acc, [word]) :
|
||||
|
@ -333,10 +539,88 @@ function _str_char_split(s,delim,n=0,acc=[],word="") =
|
|||
_str_char_split(s,delim,n+1,acc,str(word,s[n]));
|
||||
|
||||
|
||||
// Function: reorient()
|
||||
//
|
||||
// Usage:
|
||||
// reorient(anchor, spin, [orient], two_d, size, [size2], [shift], [offset], [anchors], [p]);
|
||||
// reorient(anchor, spin, [orient], two_d, r|d, [offset], [anchors], [p]);
|
||||
// reorient(anchor, spin, [orient], two_d, path, [extent], [offset], [anchors], [p]);
|
||||
// reorient(anchor, spin, [orient], size, [size2], [shift], [offset], [anchors], [p]);
|
||||
// reorient(anchor, spin, [orient], r|d, l, [offset], [anchors], [p]);
|
||||
// reorient(anchor, spin, [orient], r1|d1, r2|d2, l, [offset], [anchors], [p]);
|
||||
// reorient(anchor, spin, [orient], r|d, [offset], [anchors], [p]);
|
||||
// reorient(anchor, spin, [orient], vnf, [extent], [offset], [anchors], [p]);
|
||||
//
|
||||
// Description:
|
||||
// Given anchor, spin, orient, and general geometry info for a managed volume, this calculates
|
||||
// the transformation matrix needed to be applied to the contents of that volume. A managed 3D
|
||||
// volume is assumed to be vertically (Z-axis) oriented, and centered. A managed 2D area is just
|
||||
// assumed to be centered.
|
||||
//
|
||||
// If `p` is not given, then the transformation matrix will be returned.
|
||||
// If `p` contains a VNF, a new VNF will be returned with the vertices transformed by the matrix.
|
||||
// If `p` contains a path, a new path will be returned with the vertices transformed by the matrix.
|
||||
// If `p` contains a point, a new point will be returned, transformed by the matrix.
|
||||
//
|
||||
// If `$attach_to` is not defined, then the following transformations are performed in order:
|
||||
// * Translates so the `anchor` point is at the origin (0,0,0).
|
||||
// * Rotates around the Z axis by `spin` degrees counter-clockwise.
|
||||
// * Rotates so the top of the part points towards the vector `orient`.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Arguments:
|
||||
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
|
||||
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
|
||||
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP`
|
||||
// size = If given as a 3D vector, contains the XY size of the bottom of the cuboidal/prismoidal volume, and the Z height. If given as a 2D vector, contains the front X width of the rectangular/trapezoidal shape, and the Y length.
|
||||
// size2 = If given as a 2D vector, contains the XY size of the top of the prismoidal volume. If given as a number, contains the back width of the trapezoidal shape.
|
||||
// shift = If given as a 2D vector, shifts the top of the prismoidal or conical shape by the given amount. If given as a number, shifts the back of the trapezoidal shape right by that amount. Default: No shift.
|
||||
// r = Radius of the cylindrical/conical volume.
|
||||
// d = Diameter of the cylindrical/conical volume.
|
||||
// r1 = Radius of the bottom of the conical volume.
|
||||
// r2 = Radius of the top of the conical volume.
|
||||
// d1 = Diameter of the bottom of the conical volume.
|
||||
// d2 = Diameter of the top of the conical volume.
|
||||
// l = Length of the cylindrical/conical volume along axis.
|
||||
// vnf = The [VNF](vnf.scad) of the volume.
|
||||
// path = The path to generate a polygon from.
|
||||
// extent = If true, calculate anchors by extents, rather than intersection. Default: false.
|
||||
// offset = If given, offsets the center of the volume.
|
||||
// anchors = If given as a list of anchor points, allows named anchor points.
|
||||
// two_d = If true, the attachable shape is 2D. If false, 3D. Default: false (3D)
|
||||
// p = The VNF, path, or point to transform.
|
||||
function reorient(
|
||||
anchor=CENTER,
|
||||
spin=0,
|
||||
orient=UP,
|
||||
size, size2, shift,
|
||||
r,r1,r2, d,d1,d2, l,h,
|
||||
vnf, path,
|
||||
extent=true,
|
||||
offset=[0,0,0],
|
||||
anchors=[],
|
||||
two_d=false,
|
||||
p=undef
|
||||
) = let(
|
||||
geom = attach_geom(
|
||||
size=size, size2=size2, shift=shift,
|
||||
r=r, r1=r1, r2=r2, h=h,
|
||||
d=d, d1=d1, d2=d2, l=l,
|
||||
vnf=vnf, path=path, extent=extent,
|
||||
offset=offset, anchors=anchors,
|
||||
two_d=two_d
|
||||
)
|
||||
) attach_transform(anchor,spin,orient,geom,p);
|
||||
|
||||
|
||||
|
||||
// Section: Attachability Modules
|
||||
|
||||
|
||||
// Module: attachable()
|
||||
//
|
||||
// Usage:
|
||||
|
@ -478,7 +762,7 @@ module attachable(
|
|||
spin=0,
|
||||
orient=UP,
|
||||
size, size2, shift,
|
||||
r,r1,r2, d,d1,d2, l,
|
||||
r,r1,r2, d,d1,d2, l,h,
|
||||
vnf, path,
|
||||
extent=true,
|
||||
offset=[0,0,0],
|
||||
|
@ -486,110 +770,32 @@ module attachable(
|
|||
two_d=false
|
||||
) {
|
||||
assert($children==2);
|
||||
assert(is_string(anchor) || is_vector(anchor));
|
||||
assert(is_num(spin));
|
||||
assert(is_vector(orient));
|
||||
assert(is_vector(offset));
|
||||
assert(is_list(anchors));
|
||||
geom = attach_geom(
|
||||
size=size, size2=size2, shift=shift,
|
||||
r=r, r1=r1, r2=r2, h=h,
|
||||
d=d, d1=d1, d2=d2, l=l,
|
||||
vnf=vnf, path=path, extent=extent,
|
||||
offset=offset, anchors=anchors,
|
||||
two_d=two_d
|
||||
);
|
||||
m = attach_transform(anchor,spin,orient,geom);
|
||||
multmatrix(m) {
|
||||
if (attachment_is_shown($tags)) {
|
||||
$parent_anchor = anchor;
|
||||
$parent_spin = spin;
|
||||
$parent_orient = orient;
|
||||
$parent_geom = geom;
|
||||
$parent_size = attach_geom_size(geom);
|
||||
|
||||
geom = !is_undef(size)? (
|
||||
two_d? (
|
||||
let(
|
||||
size2 = default(size2, size.x),
|
||||
shift = default(shift, 0)
|
||||
)
|
||||
assert(is_vector(size) && len(size)==2)
|
||||
assert(is_num(size2))
|
||||
assert(is_num(shift))
|
||||
["rect", point2d(size), size2, shift, offset, anchors]
|
||||
) : (
|
||||
let(
|
||||
size2 = default(size2, point2d(size)),
|
||||
shift = default(shift, [0,0])
|
||||
)
|
||||
assert(is_vector(size) && len(size)==3)
|
||||
assert(is_vector(size2) && len(size2)==2)
|
||||
assert(is_vector(shift) && len(shift)==2)
|
||||
["cuboid", size, size2, shift, offset, anchors]
|
||||
)
|
||||
) : !is_undef(vnf)? (
|
||||
assert(is_vnf(vnf))
|
||||
assert(two_d == false)
|
||||
extent? ["vnf_extent", vnf, offset, anchors] :
|
||||
["vnf_isect", vnf, offset, anchors]
|
||||
) : !is_undef(path)? (
|
||||
assert(is_path(path))
|
||||
assert(two_d == true)
|
||||
extent? ["path_extent", path, offset, anchors] :
|
||||
["path_isect", path, offset, anchors]
|
||||
) :
|
||||
let(
|
||||
r1 = get_radius(r1=r1,d1=d1,r=r,d=d,dflt=undef)
|
||||
)
|
||||
!is_undef(r1)? (
|
||||
assert(is_num(r1))
|
||||
!is_undef(l)? (
|
||||
let(
|
||||
shift = default(shift, [0,0]),
|
||||
r2 = get_radius(r1=r2,d1=d2,r=r,d=d,dflt=undef)
|
||||
)
|
||||
assert(is_num(l))
|
||||
assert(is_num(r2))
|
||||
assert(is_vector(shift) && len(shift)==2)
|
||||
["cyl", r1, r2, l, shift, offset, anchors]
|
||||
) : (
|
||||
two_d? ["circle", r1, offset, anchors] :
|
||||
["spheroid", r1, offset, anchors]
|
||||
)
|
||||
) :
|
||||
assert(false, "attachable(): Unrecognizable geometry description.");
|
||||
$attach_to = undef;
|
||||
$tags_shown = undef;
|
||||
$tags_hidden = undef;
|
||||
|
||||
pos = find_anchor(anchor, geom)[1];
|
||||
size = attach_geom_size(geom);
|
||||
|
||||
$parent_anchor = anchor;
|
||||
$parent_spin = spin;
|
||||
$parent_orient = orient;
|
||||
$parent_geom = geom;
|
||||
$parent_size = size;
|
||||
|
||||
tags = _str_char_split($tags, " ");
|
||||
s_tags = $tags_shown;
|
||||
h_tags = $tags_hidden;
|
||||
shown = !s_tags || any([for (tag=tags) in_list(tag, s_tags)]);
|
||||
hidden = any([for (tag=tags) in_list(tag, h_tags)]);
|
||||
if ($attach_to != undef) {
|
||||
anch = find_anchor($attach_to, geom);
|
||||
ang = vector_angle(anch[2], two_d? BACK : DOWN);
|
||||
axis = two_d? UP : vector_axis(anch[2], DOWN);
|
||||
ang2 = (anch[2]==UP || anch[2]==DOWN)? 0 : 180-anch[3];
|
||||
axis2 = rotate_points3d([axis],[0,0,ang2])[0];
|
||||
$attach_to = undef;
|
||||
|
||||
rot(ang, v=axis2)
|
||||
rotate(ang2+spin)
|
||||
translate(-anch[1]) {
|
||||
if(shown && !hidden) {
|
||||
if (is_undef($color)) {
|
||||
children(0);
|
||||
} else color($color) {
|
||||
$color = undef;
|
||||
children(0);
|
||||
}
|
||||
}
|
||||
children(1);
|
||||
}
|
||||
} else {
|
||||
rot(from=UP,to=orient)
|
||||
rotate(spin)
|
||||
translate(-pos) {
|
||||
if(shown && !hidden) {
|
||||
if (is_undef($color)) {
|
||||
children(0);
|
||||
} else color($color) {
|
||||
$color = undef;
|
||||
children(0);
|
||||
}
|
||||
if (is_undef($color)) {
|
||||
children(0);
|
||||
} else color($color) {
|
||||
$color = undef;
|
||||
children(0);
|
||||
}
|
||||
children(1);
|
||||
}
|
||||
|
@ -600,7 +806,6 @@ module attachable(
|
|||
|
||||
// Section: Attachment Positioning
|
||||
|
||||
|
||||
// Module: position()
|
||||
// Usage:
|
||||
// position(from, [overlap]) ...
|
||||
|
|
|
@ -241,6 +241,8 @@ module gear_tooth_profile(
|
|||
// clearance = Gap between top of a tooth on one gear and bottom of valley on a meshing gear (in millimeters)
|
||||
// backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle
|
||||
// interior = If true, create a mask for difference()ing from something else.
|
||||
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
|
||||
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
|
||||
// Example(2D): Typical Gear Shape
|
||||
// gear2d(pitch=5, teeth=20);
|
||||
// Example(2D): Lower Pressure Angle
|
||||
|
@ -254,8 +256,11 @@ function gear2d(
|
|||
PA = 28,
|
||||
clearance = undef,
|
||||
backlash = 0.0,
|
||||
interior = false
|
||||
interior = false,
|
||||
anchor = CENTER,
|
||||
spin = 0
|
||||
) = let(
|
||||
pr = pitch_radius(pitch=pitch, teeth=teeth),
|
||||
pts = concat(
|
||||
[for (tooth = [0:1:teeth-hide-1])
|
||||
each rot(tooth*360/teeth,
|
||||
|
@ -273,7 +278,7 @@ function gear2d(
|
|||
],
|
||||
hide>0? [[0,0]] : []
|
||||
)
|
||||
) pts;
|
||||
) attachable(anchor,spin, two_d=true, r=pr, p=pts);
|
||||
|
||||
|
||||
module gear2d(
|
||||
|
@ -283,19 +288,23 @@ module gear2d(
|
|||
PA = 28,
|
||||
clearance = undef,
|
||||
backlash = 0.0,
|
||||
interior = false
|
||||
interior = false,
|
||||
anchor = CENTER,
|
||||
spin = 0
|
||||
) {
|
||||
polygon(
|
||||
gear2d(
|
||||
pitch = pitch,
|
||||
teeth = teeth,
|
||||
hide = hide,
|
||||
PA = PA,
|
||||
clearance = clearance,
|
||||
backlash = backlash,
|
||||
interior = interior
|
||||
)
|
||||
path = gear2d(
|
||||
pitch = pitch,
|
||||
teeth = teeth,
|
||||
hide = hide,
|
||||
PA = PA,
|
||||
clearance = clearance,
|
||||
backlash = backlash,
|
||||
interior = interior
|
||||
);
|
||||
attachable(anchor,spin, two_d=true, r=pr) {
|
||||
polygon(path);
|
||||
children();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -60,7 +60,8 @@ function square(size=1, center, rounding=0, chamfer=0, anchor, spin=0) =
|
|||
assert(is_num(rounding) || len(rounding)==4)
|
||||
let(
|
||||
size = is_num(size)? [size,size] : point2d(size),
|
||||
anchor = get_anchor(anchor, center, FRONT+LEFT, FRONT+LEFT)
|
||||
anchor = get_anchor(anchor, center, FRONT+LEFT, FRONT+LEFT),
|
||||
complex = rounding!=0 || chamfer!=0
|
||||
)
|
||||
(rounding==0 && chamfer==0)? let(
|
||||
path = [
|
||||
|
@ -97,7 +98,9 @@ function square(size=1, center, rounding=0, chamfer=0, anchor, spin=0) =
|
|||
)
|
||||
each [for (a = angs) cp + inset*[cos(a),sin(a)]]
|
||||
]
|
||||
) rot(spin, p=move(-vmul(anchor,size/2), p=path));
|
||||
) complex?
|
||||
reorient(anchor,spin, two_d=true, path=path, p=path) :
|
||||
reorient(anchor,spin, two_d=true, size=size, p=path);
|
||||
|
||||
|
||||
// Function&Module: circle()
|
||||
|
@ -142,7 +145,7 @@ function circle(r, d, realign=false, circum=false, anchor=CENTER, spin=0) =
|
|||
offset = realign? 180/sides : 0,
|
||||
rr = r / (circum? cos(180/sides) : 1),
|
||||
pts = [for (i=[0:1:sides-1]) let(a=360-offset-i*360/sides) rr*[cos(a),sin(a)]]
|
||||
) rot(spin, p=move(-unit(anchor)*rr, p=pts));
|
||||
) reorient(anchor,spin, two_d=true, r=rr, p=pts);
|
||||
|
||||
|
||||
|
||||
|
@ -235,7 +238,6 @@ module cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP)
|
|||
l = first_defined([h, l, 1]);
|
||||
hh = l/2;
|
||||
sides = segs(max(r1,r2));
|
||||
size = [r1*2, r1*2, l];
|
||||
path = [[0,hh],[r2,hh],[r1,-hh],[0,-hh]];
|
||||
attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
|
||||
rotate_extrude(convexity=2, $fn=sides) {
|
||||
|
@ -275,7 +277,6 @@ module sphere(r, d, anchor=CENTER, spin=0, orient=UP)
|
|||
{
|
||||
r = get_radius(r=r, d=d, dflt=1);
|
||||
sides = segs(r);
|
||||
size = [r*2, r*2, r*2];
|
||||
attachable(anchor,spin,orient, r=r) {
|
||||
rotate_extrude(convexity=2) {
|
||||
difference() {
|
||||
|
|
159
shapes2d.scad
159
shapes2d.scad
|
@ -562,13 +562,11 @@ function _turtle_command(command, parm, parm2, state, index) =
|
|||
rot(delta_angle,p=state[step],planar=true)
|
||||
]
|
||||
) :
|
||||
|
||||
|
||||
|
||||
assert(false,str("Unknown turtle command \"",command,"\" at index",index))
|
||||
[];
|
||||
|
||||
|
||||
|
||||
// Section: 2D N-Gons
|
||||
|
||||
// Function&Module: regular_ngon()
|
||||
|
@ -625,7 +623,7 @@ function regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false
|
|||
(r-rounding*sc)*[cos(a),sin(a)] +
|
||||
rounding*[cos(b),sin(b)]
|
||||
]
|
||||
) rot(spin, p=move(-r*unit(anchor), p=path));
|
||||
) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path);
|
||||
|
||||
|
||||
module regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0) {
|
||||
|
@ -643,8 +641,8 @@ module regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false,
|
|||
// Function&Module: pentagon()
|
||||
// Usage:
|
||||
// pentagon(or|od, [realign]);
|
||||
// pentagon(ir|id, [realign];
|
||||
// pentagon(side, [realign];
|
||||
// pentagon(ir|id, [realign]);
|
||||
// pentagon(side, [realign]);
|
||||
// Description:
|
||||
// When called as a function, returns a 2D path for a regular pentagon.
|
||||
// When called as a module, creates a 2D regular pentagon.
|
||||
|
@ -786,14 +784,13 @@ module octagon(r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CEN
|
|||
// stroke(closed=true, trapezoid(h=30, w1=40, w2=20));
|
||||
function trapezoid(h, w1, w2, anchor=CENTER, spin=0) =
|
||||
let(
|
||||
s = anchor.y>0? [w2,h] : anchor.y<0? [w1,h] : [(w1+w2)/2,h],
|
||||
path = [[w1/2,-h/2], [-w1/2,-h/2], [-w2/2,h/2], [w2/2,h/2]]
|
||||
) rot(spin, p=move(-vmul(anchor,s/2), p=path));
|
||||
) reorient(anchor,spin, two_d=true, size=[w1,h], size2=w2, p=path);
|
||||
|
||||
|
||||
|
||||
module trapezoid(h, w1, w2, anchor=CENTER, spin=0) {
|
||||
path = trapezoid(h=h, w1=w1, w2=w2);
|
||||
path = [[w1/2,-h/2], [-w1/2,-h/2], [-w2/2,h/2], [w2/2,h/2]];
|
||||
attachable(anchor,spin, two_d=true, size=[w1,h], size2=w2) {
|
||||
polygon(path);
|
||||
children();
|
||||
|
@ -846,12 +843,14 @@ function teardrop2d(r, d, ang=45, cap_h, anchor=CENTER, spin=0) =
|
|||
ea = 360 + ang,
|
||||
steps = segs(r)*(ea-sa)/360,
|
||||
step = (ea-sa)/steps,
|
||||
path = concat(
|
||||
[[ cap_w/2,cap_h]],
|
||||
[for (i=[0:1:steps]) let(a=ea-i*step) r*[cos(a),sin(a)]],
|
||||
[[-cap_w/2,cap_h]]
|
||||
path = deduplicate(
|
||||
[
|
||||
[ cap_w/2,cap_h],
|
||||
for (i=[0:1:steps]) let(a=ea-i*step) r*[cos(a),sin(a)],
|
||||
[-cap_w/2,cap_h]
|
||||
], closed=true
|
||||
)
|
||||
) rot(spin, p=move(-vmul(anchor,[r,cap_h]), p=deduplicate(path,closed=true)));
|
||||
) reorient(anchor,spin, two_d=true, path=path, p=path);
|
||||
|
||||
|
||||
|
||||
|
@ -891,14 +890,13 @@ function glued_circles(r, d, spread=10, tangent=30, anchor=CENTER, spin=0) =
|
|||
subarc = ea2-sa2,
|
||||
arcsegs = ceil(segs(r2)*abs(subarc)/360),
|
||||
arcstep = subarc / arcsegs,
|
||||
s = [spread/2+r, r],
|
||||
path = concat(
|
||||
[for (i=[0:1:lobesegs]) let(a=sa1+i*lobestep) r * [cos(a),sin(a)] - cp1],
|
||||
tangent==0? [] : [for (i=[0:1:arcsegs]) let(a=ea2-i*arcstep+180) r2 * [cos(a),sin(a)] - cp2],
|
||||
[for (i=[0:1:lobesegs]) let(a=sa1+i*lobestep+180) r * [cos(a),sin(a)] + cp1],
|
||||
tangent==0? [] : [for (i=[0:1:arcsegs]) let(a=ea2-i*arcstep) r2 * [cos(a),sin(a)] + cp2]
|
||||
)
|
||||
) rot(spin, p=move(-vmul(anchor,s), p=path));
|
||||
) reorient(anchor,spin, two_d=true, path=path, extent=true, p=path);
|
||||
|
||||
|
||||
module glued_circles(r, d, spread=10, tangent=30, anchor=CENTER, spin=0) {
|
||||
|
@ -951,12 +949,12 @@ function star(n, r, d, or, od, ir, id, step, realign=false, anchor=CENTER, spin=
|
|||
ir = get_radius(r=ir, d=id, dflt=stepr),
|
||||
offset = 90+(realign? 180/n : 0),
|
||||
path = [for(i=[0:1:2*n-1]) let(theta=180*i/n+offset, radius=(i%2)?ir:r) radius*[cos(theta), sin(theta)]]
|
||||
) rot(spin, p=move(-r*unit(anchor), p=path));
|
||||
) reorient(anchor,spin, two_d=true, path=path, p=path);
|
||||
|
||||
|
||||
module star(n, r, d, or, od, ir, id, step, realign=false, anchor=CENTER, spin=0) {
|
||||
path = star(n=n, r=r, d=d, od=od, or=or, ir=ir, id=id, step=step, realign=realign);
|
||||
attachable(anchor,spin, two_d=true, path=path, extent=true) {
|
||||
attachable(anchor,spin, two_d=true, path=path) {
|
||||
polygon(path);
|
||||
children();
|
||||
}
|
||||
|
@ -1023,11 +1021,11 @@ function supershape(step=0.5,m1=4,m2=undef,n1=1,n2=undef,n3=undef,a=1,b=undef,r=
|
|||
rads = [for (theta = angs) _superformula(theta=theta,m1=m1,m2=m2,n1=n1,n2=n2,n3=n3,a=a,b=b)],
|
||||
scale = is_def(r) ? r/max(rads) : 1,
|
||||
path = [for (i = [0:steps-1]) let(a=angs[i]) scale*rads[i]*[cos(a), sin(a)]]
|
||||
) rot(spin, p=move(-scale*max(rads)*unit(anchor), p=path));
|
||||
) reorient(anchor,spin, two_d=true, path=path, p=path);
|
||||
|
||||
module supershape(step=0.5,m1=4,m2=undef,n1,n2=undef,n3=undef,a=1,b=undef, r=undef, d=undef, anchor=CENTER, spin=0) {
|
||||
path = supershape(step=step,m1=m1,m2=m2,n1=n1,n2=n2,n3=n3,a=a,b=b,r=r,d=d);
|
||||
attachable(anchor,spin, two_d=true, path=path, extent=true) {
|
||||
attachable(anchor,spin, two_d=true, path=path) {
|
||||
polygon(path);
|
||||
children();
|
||||
}
|
||||
|
@ -1057,11 +1055,15 @@ module supershape(step=0.5,m1=4,m2=undef,n1,n2=undef,n3=undef,a=1,b=undef, r=und
|
|||
// cube([50,60,70],center=true)
|
||||
// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
|
||||
// mask2d_roundover(r=10, inset=2);
|
||||
module mask2d_roundover(r, d, excess, inset=0) {
|
||||
polygon(mask2d_roundover(r=r,d=d,excess=excess,inset=inset));
|
||||
module mask2d_roundover(r, d, excess, inset=0, anchor=CENTER,spin=0) {
|
||||
path = mask2d_roundover(r=r,d=d,excess=excess,inset=inset);
|
||||
attachable(anchor,spin, two_d=true, path=path, p=path) {
|
||||
polygon(path);
|
||||
children();
|
||||
}
|
||||
}
|
||||
|
||||
function mask2d_roundover(r, d, excess, inset=0) =
|
||||
function mask2d_roundover(r, d, excess, inset=0, anchor=CENTER,spin=0) =
|
||||
assert(is_num(r)||is_num(d))
|
||||
assert(is_undef(excess)||is_num(excess))
|
||||
assert(is_num(inset)||(is_vector(inset)&&len(inset)==2))
|
||||
|
@ -1070,12 +1072,13 @@ function mask2d_roundover(r, d, excess, inset=0) =
|
|||
excess = default(excess,$overlap),
|
||||
r = get_radius(r=r,d=d,dflt=1),
|
||||
steps = quantup(segs(r),4)/4,
|
||||
step = 90/steps
|
||||
) [
|
||||
[-excess,-excess], [-excess, r+inset.y],
|
||||
for (i=[0:1:steps]) [r,r] + inset + polar_to_xy(r,180+i*step),
|
||||
[r+inset.x,-excess]
|
||||
];
|
||||
step = 90/steps,
|
||||
path = [
|
||||
[-excess,-excess], [-excess, r+inset.y],
|
||||
for (i=[0:1:steps]) [r,r] + inset + polar_to_xy(r,180+i*step),
|
||||
[r+inset.x,-excess]
|
||||
]
|
||||
) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path);
|
||||
|
||||
|
||||
// Function&Module: mask2d_cove()
|
||||
|
@ -1099,11 +1102,15 @@ function mask2d_roundover(r, d, excess, inset=0) =
|
|||
// cube([50,60,70],center=true)
|
||||
// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
|
||||
// mask2d_cove(r=10, inset=2);
|
||||
module mask2d_cove(r, d, inset=0, excess) {
|
||||
polygon(mask2d_cove(r=r,d=d,excess=excess,inset=inset));
|
||||
module mask2d_cove(r, d, inset=0, excess, anchor=CENTER,spin=0) {
|
||||
path = mask2d_cove(r=r,d=d,excess=excess,inset=inset);
|
||||
attachable(anchor,spin, two_d=true, path=path) {
|
||||
polygon(path);
|
||||
children();
|
||||
}
|
||||
}
|
||||
|
||||
function mask2d_cove(r, d, inset=0, excess) =
|
||||
function mask2d_cove(r, d, inset=0, excess, anchor=CENTER,spin=0) =
|
||||
assert(is_num(r)||is_num(d))
|
||||
assert(is_undef(excess)||is_num(excess))
|
||||
assert(is_num(inset)||(is_vector(inset)&&len(inset)==2))
|
||||
|
@ -1112,12 +1119,13 @@ function mask2d_cove(r, d, inset=0, excess) =
|
|||
excess = default(excess,$overlap),
|
||||
r = get_radius(r=r,d=d,dflt=1),
|
||||
steps = quantup(segs(r),4)/4,
|
||||
step = 90/steps
|
||||
) [
|
||||
[-excess,-excess], [-excess, r+inset.y],
|
||||
for (i=[0:1:steps]) inset + polar_to_xy(r,90-i*step),
|
||||
[r+inset.x,-excess]
|
||||
];
|
||||
step = 90/steps,
|
||||
path = [
|
||||
[-excess,-excess], [-excess, r+inset.y],
|
||||
for (i=[0:1:steps]) inset + polar_to_xy(r,90-i*step),
|
||||
[r+inset.x,-excess]
|
||||
]
|
||||
) reorient(anchor,spin, two_d=true, path=path, p=path);
|
||||
|
||||
|
||||
// Function&Module: mask2d_chamfer()
|
||||
|
@ -1147,11 +1155,15 @@ function mask2d_cove(r, d, inset=0, excess) =
|
|||
// cube([50,60,70],center=true)
|
||||
// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
|
||||
// mask2d_chamfer(x=10, inset=2);
|
||||
module mask2d_chamfer(x, y, edge, angle=45, excess, inset=0) {
|
||||
polygon(mask2d_chamfer(x=x, y=y, edge=edge, angle=angle, excess=excess, inset=inset));
|
||||
module mask2d_chamfer(x, y, edge, angle=45, excess, inset=0, anchor=CENTER,spin=0) {
|
||||
path = mask2d_chamfer(x=x, y=y, edge=edge, angle=angle, excess=excess, inset=inset);
|
||||
attachable(anchor,spin, two_d=true, path=path, extent=true) {
|
||||
polygon(path);
|
||||
children();
|
||||
}
|
||||
}
|
||||
|
||||
function mask2d_chamfer(x, y, edge, angle=45, excess, inset=0) =
|
||||
function mask2d_chamfer(x, y, edge, angle=45, excess, inset=0, anchor=CENTER,spin=0) =
|
||||
assert(num_defined([x,y,edge])==1)
|
||||
assert(is_num(first_defined([x,y,edge])))
|
||||
assert(is_num(angle))
|
||||
|
@ -1163,12 +1175,13 @@ function mask2d_chamfer(x, y, edge, angle=45, excess, inset=0) =
|
|||
x = !is_undef(x)? x :
|
||||
!is_undef(y)? adj_ang_to_opp(adj=y,ang=angle) :
|
||||
hyp_ang_to_opp(hyp=edge,ang=angle),
|
||||
y = opp_ang_to_adj(opp=x,ang=angle)
|
||||
) [
|
||||
[-excess, -excess], [-excess, y+inset.y],
|
||||
[inset.x, y+inset.y], [x+inset.x, inset.y],
|
||||
[x+inset.x, -excess]
|
||||
];
|
||||
y = opp_ang_to_adj(opp=x,ang=angle),
|
||||
path = [
|
||||
[-excess, -excess], [-excess, y+inset.y],
|
||||
[inset.x, y+inset.y], [x+inset.x, inset.y],
|
||||
[x+inset.x, -excess]
|
||||
]
|
||||
) reorient(anchor,spin, two_d=true, path=path, extent=true, p=path);
|
||||
|
||||
|
||||
// Function&Module: mask2d_rabbet()
|
||||
|
@ -1191,19 +1204,25 @@ function mask2d_chamfer(x, y, edge, angle=45, excess, inset=0) =
|
|||
// cube([50,60,70],center=true)
|
||||
// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
|
||||
// mask2d_rabbet(size=10);
|
||||
module mask2d_rabbet(size, excess) {
|
||||
polygon(mask2d_rabbet(size=size, excess=excess));
|
||||
module mask2d_rabbet(size, excess, anchor=CENTER,spin=0) {
|
||||
path = mask2d_rabbet(size=size, excess=excess);
|
||||
attachable(anchor,spin, two_d=true, path=path, extent=false, p=path) {
|
||||
polygon(path);
|
||||
children();
|
||||
}
|
||||
}
|
||||
|
||||
function mask2d_rabbet(size, excess) =
|
||||
function mask2d_rabbet(size, excess, anchor=CENTER,spin=0) =
|
||||
assert(is_num(size)||(is_vector(size)&&len(size)==2))
|
||||
assert(is_undef(excess)||is_num(excess))
|
||||
let(
|
||||
excess = default(excess,$overlap),
|
||||
size = is_list(size)? size : [size,size]
|
||||
) [
|
||||
[-excess, -excess], [-excess, size.y], size, [size.x, -excess]
|
||||
];
|
||||
size = is_list(size)? size : [size,size],
|
||||
path = [
|
||||
[-excess, -excess], [-excess, size.y],
|
||||
size, [size.x, -excess]
|
||||
]
|
||||
) reorient(anchor,spin, two_d=true, path=path, extent=false, p=path);
|
||||
|
||||
|
||||
// Function&Module: mask2d_dovetail()
|
||||
|
@ -1234,11 +1253,15 @@ function mask2d_rabbet(size, excess) =
|
|||
// cube([50,60,70],center=true)
|
||||
// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT])
|
||||
// mask2d_dovetail(x=10, inset=2);
|
||||
module mask2d_dovetail(x, y, edge, angle=30, inset=0, shelf=0, excess) {
|
||||
polygon(mask2d_dovetail(x=x, y=y, edge=edge, angle=angle, inset=inset, shelf=shelf, excess=excess));
|
||||
module mask2d_dovetail(x, y, edge, angle=30, inset=0, shelf=0, excess, anchor=CENTER, spin=0) {
|
||||
path = mask2d_dovetail(x=x, y=y, edge=edge, angle=angle, inset=inset, shelf=shelf, excess=excess);
|
||||
attachable(anchor,spin, two_d=true, path=path) {
|
||||
polygon(path);
|
||||
children();
|
||||
}
|
||||
}
|
||||
|
||||
function mask2d_dovetail(x, y, edge, angle=30, inset=0, shelf=0, excess) =
|
||||
function mask2d_dovetail(x, y, edge, angle=30, inset=0, shelf=0, excess, anchor=CENTER, spin=0) =
|
||||
assert(num_defined([x,y,edge])==1)
|
||||
assert(is_num(first_defined([x,y,edge])))
|
||||
assert(is_num(angle))
|
||||
|
@ -1250,11 +1273,12 @@ function mask2d_dovetail(x, y, edge, angle=30, inset=0, shelf=0, excess) =
|
|||
x = !is_undef(x)? x :
|
||||
!is_undef(y)? adj_ang_to_opp(adj=y,ang=angle) :
|
||||
hyp_ang_to_opp(hyp=edge,ang=angle),
|
||||
y = opp_ang_to_adj(opp=x,ang=angle)
|
||||
) [
|
||||
[-excess, 0], [-excess, y+inset.y+shelf],
|
||||
inset+[x,y+shelf], inset+[x,y], inset, [inset.x,0]
|
||||
];
|
||||
y = opp_ang_to_adj(opp=x,ang=angle),
|
||||
path = [
|
||||
[-excess, 0], [-excess, y+inset.y+shelf],
|
||||
inset+[x,y+shelf], inset+[x,y], inset, [inset.x,0]
|
||||
]
|
||||
) reorient(anchor,spin, two_d=true, path=path, p=path);
|
||||
|
||||
|
||||
// Function&Module: mask2d_ogee()
|
||||
|
@ -1289,7 +1313,11 @@ function mask2d_dovetail(x, y, edge, angle=30, inset=0, shelf=0, excess) =
|
|||
// "ystep",1, "xstep",1 // Ending shoulder.
|
||||
// ]);
|
||||
module mask2d_ogee(pattern, excess) {
|
||||
polygon(mask2d_ogee(pattern, excess=excess));
|
||||
path = mask2d_ogee(pattern, excess=excess);
|
||||
attachable(anchor,spin, two_d=true, path=path) {
|
||||
polygon(path);
|
||||
children();
|
||||
}
|
||||
}
|
||||
|
||||
function mask2d_ogee(pattern, excess) =
|
||||
|
@ -1356,8 +1384,9 @@ function mask2d_ogee(pattern, excess) =
|
|||
a = pat[0]=="round"? (180+i*step) : (90-i*step)
|
||||
) pat[2] + abs(r)*[cos(a),sin(a)]
|
||||
]
|
||||
]
|
||||
) deduplicate(path);
|
||||
],
|
||||
path2 = deduplicate(path)
|
||||
) reorient(anchor,spin, two_d=true, path=path2, p=path2);
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
BOSL_VERSION = [2,0,162];
|
||||
BOSL_VERSION = [2,0,163];
|
||||
|
||||
|
||||
// Section: BOSL Library Version Functions
|
||||
|
|
Loading…
Reference in a new issue