Merge remote-tracking branch 'upstream/master'

This commit is contained in:
RonaldoCMP 2021-09-20 13:47:23 +01:00
commit 9a0f18ed44
61 changed files with 8931 additions and 9209 deletions

View file

@ -3,6 +3,7 @@ IgnoreFiles:
foo.scad
std.scad
bosl1compat.scad
builtins.scad
version.scad
tmp_*.scad
PrioritizeFiles:
@ -14,6 +15,7 @@ PrioritizeFiles:
primitives.scad
shapes.scad
shapes2d.scad
drawing.scad
masks.scad
math.scad
vectors.scad
@ -29,7 +31,6 @@ PrioritizeFiles:
debug.scad
common.scad
strings.scad
errors.scad
beziers.scad
threading.scad
rounding.scad
@ -40,8 +41,6 @@ PrioritizeFiles:
hull.scad
triangulation.scad
turtle3d.scad
stacks.scad
queues.scad
structs.scad
DefineHeader(BulletList): Side Effects
DefineHeader(Table:Anchor Name|Position): Extra Anchors

View file

@ -142,6 +142,9 @@ function affine3d_to_2d(m) =
// Applies the specified transformation matrix to a point, pointlist, bezier patch or VNF.
// Both inputs can be 2D or 3D, and it is also allowed to supply 3D transformations with 2D
// data as long as the the only action on the z coordinate is a simple scaling.
// .
// If you construct your own matrices you can also use a transform that acts like a projection
// with fewer rows to produce lower dimensional output.
// Arguments:
// transform = The 2D or 3D transformation matrix to apply to the point/points.
// points = The point, pointlist, bezier patch, or VNF to apply the transformation to.
@ -173,14 +176,15 @@ function apply(transform,points) =
? /* BezPatch */ [for (x=points) apply(transform,x)] :
let(
tdim = len(transform[0])-1,
datadim = len(points[0])
datadim = len(points[0]),
outdim = min(datadim,len(transform)),
matrix = [for(i=[0:1:tdim]) [for(j=[0:1:outdim-1]) transform[j][i]]]
)
tdim == 3 && datadim == 3 ? [for(p=points) point3d(transform*concat(p,[1]))] :
tdim == 2 && datadim == 2 ? [for(p=points) point2d(transform*concat(p,[1]))] :
tdim == 3 && datadim == 2 ?
tdim==datadim && (datadim==3 || datadim==2) ? [for(p=points) concat(p,1)] * matrix
: tdim == 3 && datadim == 2 ?
assert(is_2d_transform(transform), str("Transforms is 3d but points are 2d"))
[for(p=points) point2d(transform*concat(p,[0,1]))] :
assert(false, str("Unsupported combination: transform with dimension ",tdim,", data of dimension ",datadim));
[for(p=points) concat(p,[0,1])]*matrix
: assert(false, str("Unsupported combination: transform with dimension ",tdim,", data of dimension ",datadim));
// Function: rot_decode()
@ -882,64 +886,6 @@ function affine3d_rot_from_to(from, to) =
];
// Function: affine3d_frame_map()
// Usage:
// map = affine3d_frame_map(v1, v2, v3, [reverse=]);
// map = affine3d_frame_map(x=VECTOR1, y=VECTOR2, [reverse=]);
// map = affine3d_frame_map(x=VECTOR1, z=VECTOR2, [reverse=]);
// map = affine3d_frame_map(y=VECTOR1, z=VECTOR2, [reverse=]);
// Topics: Affine, Matrices, Transforms, Rotation
// See Also: rot(), xrot(), yrot(), zrot(), affine2d_zrot()
// Description:
// Returns a transformation that maps one coordinate frame to another. You must specify two or
// three of `x`, `y`, and `z`. The specified axes are mapped to the vectors you supplied. If you
// give two inputs, the third vector is mapped to the appropriate normal to maintain a right hand
// coordinate system. If the vectors you give are orthogonal the result will be a rotation and the
// `reverse` parameter will supply the inverse map, which enables you to map two arbitrary
// coordinate systems to each other by using the canonical coordinate system as an intermediary.
// You cannot use the `reverse` option with non-orthogonal inputs.
// Arguments:
// x = Destination 3D vector for x axis.
// y = Destination 3D vector for y axis.
// z = Destination 3D vector for z axis.
// reverse = reverse direction of the map for orthogonal inputs. Default: false
// Example:
// T = affine3d_frame_map(x=[1,1,0], y=[-1,1,0]); // This map is just a rotation around the z axis
// Example:
// T = affine3d_frame_map(x=[1,0,0], y=[1,1,0]); // This map is not a rotation because x and y aren't orthogonal
// Example:
// // The next map sends [1,1,0] to [0,1,1] and [-1,1,0] to [0,-1,1]
// T = affine3d_frame_map(x=[0,1,1], y=[0,-1,1]) * affine3d_frame_map(x=[1,1,0], y=[-1,1,0],reverse=true);
function affine3d_frame_map(x,y,z, reverse=false) =
assert(num_defined([x,y,z])>=2, "Must define at least two inputs")
let(
xvalid = is_undef(x) || (is_vector(x) && len(x)==3),
yvalid = is_undef(y) || (is_vector(y) && len(y)==3),
zvalid = is_undef(z) || (is_vector(z) && len(z)==3)
)
assert(xvalid,"Input x must be a length 3 vector")
assert(yvalid,"Input y must be a length 3 vector")
assert(zvalid,"Input z must be a length 3 vector")
let(
x = is_undef(x)? undef : unit(x,RIGHT),
y = is_undef(y)? undef : unit(y,BACK),
z = is_undef(z)? undef : unit(z,UP),
map = is_undef(x)? [cross(y,z), y, z] :
is_undef(y)? [x, cross(z,x), z] :
is_undef(z)? [x, y, cross(x,y)] :
[x, y, z]
)
reverse? (
let(
ocheck = (
approx(map[0]*map[1],0) &&
approx(map[0]*map[2],0) &&
approx(map[1]*map[2],0)
)
)
assert(ocheck, "Inputs must be orthogonal when reverse==true")
[for (r=map) [for (c=r) c, 0], [0,0,0,1]]
) : [for (r=transpose(map)) [for (c=r) c, 0], [0,0,0,1]];

View file

@ -161,12 +161,12 @@ function last(list) =
// Arguments:
// list = The list to get the head of.
// to = The last index to include. If negative, adds the list length to it. ie: -1 is the last list item.
// Examples:
// hlist = list_head(["foo", "bar", "baz"]); // Returns: ["foo", "bar"]
// hlist = list_head(["foo", "bar", "baz"], -3); // Returns: ["foo"]
// hlist = list_head(["foo", "bar", "baz"], 2); // Returns: ["foo","bar"]
// hlist = list_head(["foo", "bar", "baz"], -5); // Returns: []
// hlist = list_head(["foo", "bar", "baz"], 5); // Returns: ["foo","bar","baz"]
// Example:
// hlist1 = list_head(["foo", "bar", "baz"]); // Returns: ["foo", "bar"]
// hlist2 = list_head(["foo", "bar", "baz"], -3); // Returns: ["foo"]
// hlist3 = list_head(["foo", "bar", "baz"], 2); // Returns: ["foo","bar"]
// hlist4 = list_head(["foo", "bar", "baz"], -5); // Returns: []
// hlist5 = list_head(["foo", "bar", "baz"], 5); // Returns: ["foo","bar","baz"]
function list_head(list, to=-2) =
assert(is_list(list))
assert(is_finite(to))
@ -188,12 +188,12 @@ function list_head(list, to=-2) =
// Arguments:
// list = The list to get the tail of.
// from = The first index to include. If negative, adds the list length to it. ie: -1 is the last list item.
// Examples:
// tlist = list_tail(["foo", "bar", "baz"]); // Returns: ["bar", "baz"]
// tlist = list_tail(["foo", "bar", "baz"], -1); // Returns: ["baz"]
// tlist = list_tail(["foo", "bar", "baz"], 2); // Returns: ["baz"]
// tlist = list_tail(["foo", "bar", "baz"], -5); // Returns: ["foo","bar","baz"]
// tlist = list_tail(["foo", "bar", "baz"], 5); // Returns: []
// Example:
// tlist1 = list_tail(["foo", "bar", "baz"]); // Returns: ["bar", "baz"]
// tlist2 = list_tail(["foo", "bar", "baz"], -1); // Returns: ["baz"]
// tlist3 = list_tail(["foo", "bar", "baz"], 2); // Returns: ["baz"]
// tlist4 = list_tail(["foo", "bar", "baz"], -5); // Returns: ["foo","bar","baz"]
// tlist5 = list_tail(["foo", "bar", "baz"], 5); // Returns: []
function list_tail(list, from=1) =
assert(is_list(list))
assert(is_finite(from))
@ -236,7 +236,7 @@ function list(l) = is_list(l)? l : [for (x=l) x];
// value = The value or list to coerce into a list.
// n = The number of items in the coerced list. Default: 1
// fill = The value to pad the coerced list with, after the firt value. Default: undef (pad with copies of `value`)
// Examples:
// Example:
// x = force_list([3,4,5]); // Returns: [3,4,5]
// y = force_list(5); // Returns: [5]
// z = force_list(7, n=3); // Returns: [7,7,7]
@ -509,7 +509,7 @@ function list_rotate(list,n=1) =
// list = The list to deduplicate.
// closed = If true, drops trailing items if they match the first list item.
// eps = The maximum tolerance between items.
// Examples:
// Example:
// a = deduplicate([8,3,4,4,4,8,2,3,3,8,8]); // Returns: [8,3,4,8,2,3,8]
// b = deduplicate(closed=true, [8,3,4,4,4,8,2,3,3,8,8]); // Returns: [8,3,4,8,2,3]
// c = deduplicate("Hello"); // Returns: "Helo"
@ -539,7 +539,7 @@ function deduplicate(list, closed=false, eps=EPSILON) =
// indices = The list of indices to deduplicate.
// closed = If true, drops trailing indices if what they index matches what the first index indexes.
// eps = The maximum difference to allow between numbers or vectors.
// Examples:
// Example:
// a = deduplicate_indexed([8,6,4,6,3], [1,4,3,1,2,2,0,1]); // Returns: [1,4,3,2,0,1]
// b = deduplicate_indexed([8,6,4,6,3], [1,4,3,1,2,2,0,1], closed=true); // Returns: [1,4,3,2,0]
// c = deduplicate_indexed([[7,undef],[7,undef],[1,4],[1,4],[1,4+1e-12]],eps=0); // Returns: [0,2,4]
@ -592,7 +592,7 @@ function deduplicate_indexed(list, indices, closed=false, eps=EPSILON) =
// list = list whose entries will be repeated
// N = scalar total number of points desired or vector requesting N[i] copies of vertex i.
// exact = if true return exactly the requested number of points, possibly sacrificing uniformity. If false, return uniform points that may not match the number of points requested. Default: True
// Examples:
// Example:
// list = [0,1,2,3];
// a = repeat_entries(list, 6); // Returns: [0,0,1,2,2,3]
// b = repeat_entries(list, 6, exact=false); // Returns: [0,0,1,1,2,2,3,3]
@ -629,7 +629,7 @@ function repeat_entries(list, N, exact=true) =
// values = List of values to set.
// dflt = Default value to store in sparse skipped indices.
// minlen = Minimum length to expand list to.
// Examples:
// Example:
// a = list_set([2,3,4,5], 2, 21); // Returns: [2,3,21,5]
// b = list_set([2,3,4,5], [1,3], [81,47]); // Returns: [2,81,4,47]
function list_set(list=[],indices,values,dflt=0,minlen=0) =
@ -1369,11 +1369,10 @@ function triplet(list, wrap=false) =
// Function: combinations()
// Usage:
// list = combinations(l, [n]);
// for (p = combinations(l, [n])) ...
// Topics: List Handling, Iteration
// See Also: idx(), enumerate(), pair(), triplet(), permutations()
// Description:
// Returns an ordered list of every unique permutation of `n` items out of the given list `l`.
// Returns a list of all of the (unordered) combinations of `n` items out of the given list `l`.
// For the list `[1,2,3,4]`, with `n=2`, this will return `[[1,2], [1,3], [1,4], [2,3], [2,4], [3,4]]`.
// For the list `[1,2,3,4]`, with `n=3`, this will return `[[1,2,3], [1,2,4], [1,3,4], [2,3,4]]`.
// Arguments:
@ -1395,21 +1394,17 @@ function combinations(l,n=2,_s=0) =
// Function: permutations()
// Usage:
// list = permutations(l, [n]);
// for (p = permutations(l, [n])) ...
// Topics: List Handling, Iteration
// See Also: idx(), enumerate(), pair(), triplet(), combinations()
// Description:
// Returns an ordered list of every unique permutation of `n` items out of the given list `l`.
// For the list `[1,2,3,4]`, with `n=2`, this will return `[[1,2], [1,3], [1,4], [2,3], [2,4], [3,4]]`.
// For the list `[1,2,3,4]`, with `n=3`, this will return `[[1,2,3], [1,2,4], [1,3,4], [2,3,4]]`.
// Returns a list of all of the (ordered) permutation `n` items out of the given list `l`.
// For the list `[1,2,3]`, with `n=2`, this will return `[[1,2],[1,3],[2,1],[2,3],[3,1],[3,2]]`
// For the list `[1,2,3]`, with `n=3`, this will return `[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]`
// Arguments:
// l = The list to provide permutations for.
// n = The number of items in each permutation. Default: 2
// Example:
// pairs = permutations([3,4,5,6]); // Returns: [[3,4],[3,5],[3,6],[4,5],[4,6],[5,6]]
// triplets = permutations([3,4,5,6],n=3); // Returns: [[3,4,5],[3,4,6],[3,5,6],[4,5,6]]
// Example(2D):
// for (p=permutations(regular_ngon(n=7,d=100))) stroke(p);
// pairs = permutations([3,4,5,6]); // // Returns: [[3,4],[3,5],[3,6],[4,3],[4,5],[4,6],[5,3],[5,4],[5,6],[6,3],[6,4],[6,5]]
function permutations(l,n=2) =
assert(is_list(l), "Invalid list." )
assert( is_finite(n) && n>=1 && n<=len(l), "Invalid number `n`." )
@ -1895,7 +1890,7 @@ function _array_dim_recurse(v) =
// Arguments:
// v = Array to get dimensions of.
// depth = Dimension to get size of. If not given, returns a list of dimension lengths.
// Examples:
// Example:
// a = array_dim([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]]); // Returns [2,2,3]
// b = array_dim([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]], 0); // Returns 2
// c = array_dim([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]], 2); // Returns 3

File diff suppressed because it is too large Load diff

View file

@ -9,7 +9,8 @@
include <threading.scad>
include <knurling.scad>
include <structs.scad>
include <rounding.scad>
// Section: PCO-1810 Bottle Threading
@ -53,7 +54,7 @@ module pco1810_neck(wall=2, anchor="support-ring", spin=0, orient=UP)
tamper_base_h = 14.10;
threadbase_d = 24.51;
thread_pitch = 3.18;
thread_angle = 20;
flank_angle = 20;
thread_od = 27.43;
lip_d = 25.07;
lip_h = 1.70;
@ -66,8 +67,8 @@ module pco1810_neck(wall=2, anchor="support-ring", spin=0, orient=UP)
h = support_h+neck_h;
thread_h = (thread_od-threadbase_d)/2;
anchors = [
anchorpt("support-ring", [0,0,neck_h-h/2]),
anchorpt("tamper-ring", [0,0,h/2-tamper_base_h])
named_anchor("support-ring", [0,0,neck_h-h/2]),
named_anchor("tamper-ring", [0,0,h/2-tamper_base_h])
];
attachable(anchor,spin,orient, d1=neck_d, d2=lip_recess_d+2*lip_leadin_r, l=h, anchors=anchors) {
down(h/2) {
@ -113,7 +114,7 @@ module pco1810_neck(wall=2, anchor="support-ring", spin=0, orient=UP)
d=threadbase_d-0.1,
pitch=thread_pitch,
thread_depth=thread_h+0.1,
thread_angle=thread_angle,
flank_angle=flank_angle,
twist=810,
higbee=thread_h*2,
anchor=TOP
@ -164,7 +165,7 @@ module pco1810_cap(wall=2, texture="none", anchor=BOTTOM, spin=0, orient=UP)
cap_id = 28.58;
tamper_ring_h = 14.10;
thread_pitch = 3.18;
thread_angle = 20;
flank_angle = 20;
thread_od = cap_id;
thread_depth = 1.6;
@ -172,7 +173,7 @@ module pco1810_cap(wall=2, texture="none", anchor=BOTTOM, spin=0, orient=UP)
w = cap_id + 2*wall;
h = tamper_ring_h + wall;
anchors = [
anchorpt("inside-top", [0,0,-(h/2-wall)])
named_anchor("inside-top", [0,0,-(h/2-wall)])
];
attachable(anchor,spin,orient, d=w, l=h, anchors=anchors) {
down(h/2) zrot(45) {
@ -192,7 +193,7 @@ module pco1810_cap(wall=2, texture="none", anchor=BOTTOM, spin=0, orient=UP)
}
up(wall) cyl(d=cap_id, h=tamper_ring_h+wall, anchor=BOTTOM);
}
up(wall+2) thread_helix(d=thread_od-thread_depth*2, pitch=thread_pitch, thread_depth=thread_depth, thread_angle=thread_angle, twist=810, higbee=thread_depth, internal=true, anchor=BOTTOM);
up(wall+2) thread_helix(d=thread_od-thread_depth*2, pitch=thread_pitch, thread_depth=thread_depth, flank_angle=flank_angle, twist=810, higbee=thread_depth, internal=true, anchor=BOTTOM);
}
children();
}
@ -246,7 +247,7 @@ module pco1881_neck(wall=2, anchor="support-ring", spin=0, orient=UP)
tamper_divot_r = 1.08;
threadbase_d = 24.20;
thread_pitch = 2.70;
thread_angle = 15;
flank_angle = 15;
thread_od = 27.4;
lip_d = 25.07;
lip_h = 1.70;
@ -259,8 +260,8 @@ module pco1881_neck(wall=2, anchor="support-ring", spin=0, orient=UP)
h = support_h+neck_h;
thread_h = (thread_od-threadbase_d)/2;
anchors = [
anchorpt("support-ring", [0,0,neck_h-h/2]),
anchorpt("tamper-ring", [0,0,h/2-tamper_base_h])
named_anchor("support-ring", [0,0,neck_h-h/2]),
named_anchor("tamper-ring", [0,0,h/2-tamper_base_h])
];
attachable(anchor,spin,orient, d1=neck_d, d2=lip_recess_d+2*lip_leadin_r, l=h, anchors=anchors) {
down(h/2) {
@ -306,7 +307,7 @@ module pco1881_neck(wall=2, anchor="support-ring", spin=0, orient=UP)
d=threadbase_d-0.1,
pitch=thread_pitch,
thread_depth=thread_h+0.1,
thread_angle=thread_angle,
flank_angle=flank_angle,
twist=650,
higbee=thread_h*2,
anchor=TOP
@ -356,7 +357,7 @@ module pco1881_cap(wall=2, texture="none", anchor=BOTTOM, spin=0, orient=UP)
w = 28.58 + 2*wall;
h = 11.2 + wall;
anchors = [
anchorpt("inside-top", [0,0,-(h/2-wall)])
named_anchor("inside-top", [0,0,-(h/2-wall)])
];
attachable(anchor,spin,orient, d=w, l=h, anchors=anchors) {
down(h/2) zrot(45) {
@ -376,7 +377,7 @@ module pco1881_cap(wall=2, texture="none", anchor=BOTTOM, spin=0, orient=UP)
}
up(wall) cyl(d=28.58, h=11.2+wall, anchor=BOTTOM);
}
up(wall+2) thread_helix(d=25.5, pitch=2.7, thread_depth=1.6, thread_angle=15, twist=650, higbee=1.6, internal=true, anchor=BOTTOM);
up(wall+2) thread_helix(d=25.5, pitch=2.7, thread_depth=1.6, flank_angle=15, twist=650, higbee=1.6, internal=true, anchor=BOTTOM);
}
children();
}
@ -428,7 +429,7 @@ module generic_bottle_neck(
neck_d = neck_d;
supp_d = max(neck_d, support_d);
thread_pitch = pitch;
thread_angle = 15;
flank_angle = 15;
diamMagMult = neck_d / 26.19;
heightMagMult = height / 17.00;
@ -447,7 +448,7 @@ module generic_bottle_neck(
$fn = segs(33 / 2);
thread_h = (thread_od - threadbase_d) / 2;
anchors = [
anchorpt("support-ring", [0, 0, 0 - h / 2])
named_anchor("support-ring", [0, 0, 0 - h / 2])
];
attachable(anchor, spin, orient, d1 = neck_d, d2 = 0, l = h, anchors = anchors) {
down(h / 2) {
@ -478,7 +479,7 @@ module generic_bottle_neck(
d = threadbase_d - 0.1 * diamMagMult,
pitch = thread_pitch,
thread_depth = thread_h + 0.1 * diamMagMult,
thread_angle = thread_angle,
flank_angle = flank_angle,
twist = 360 * (height - pitch - lip_roundover_r) * .6167 / pitch,
higbee = thread_h * 2,
anchor = TOP
@ -527,7 +528,7 @@ function generic_bottle_neck(
// thread_od = Outer diameter of the threads in mm.
// tolerance = Extra space to add to the outer diameter of threads and neck in mm. Applied to radius.
// neck_od = Outer diameter of neck in mm.
// thread_angle = Angle of taper on threads.
// flank_angle = Angle of taper on threads.
// pitch = Thread pitch in mm.
// 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`
@ -545,7 +546,7 @@ module generic_bottle_cap(
thread_od = 28.58,
tolerance = .2,
neck_od = 25.5,
thread_angle = 15,
flank_angle = 15,
pitch = 4,
anchor = BOTTOM,
spin = 0,
@ -562,7 +563,7 @@ module generic_bottle_cap(
heightMagMult = (height > 11.2) ? height / 11.2 : 1;
anchors = [
anchorpt("inside-top", [0, 0, -(h / 2 - wall)])
named_anchor("inside-top", [0, 0, -(h / 2 - wall)])
];
attachable(anchor, spin, orient, d = w, l = h, anchors = anchors) {
down(h / 2) {
@ -587,7 +588,7 @@ module generic_bottle_cap(
}
difference(){
up(wall + pitch / 2) {
thread_helix(d = neckOuterDTol, pitch = pitch, thread_depth = threadDepth, thread_angle = thread_angle, twist = 360 * ((height - pitch) / pitch), higbee = threadDepth, internal = true, anchor = BOTTOM);
thread_helix(d = neckOuterDTol, pitch = pitch, thread_depth = threadDepth, flank_angle = flank_angle, twist = 360 * ((height - pitch) / pitch), higbee = threadDepth, internal = true, anchor = BOTTOM);
}
}
}
@ -598,7 +599,7 @@ module generic_bottle_cap(
function generic_bottle_cap(
wall, texture, height,
thread_od, tolerance,
neck_od, thread_angle, pitch,
neck_od, flank_angle, pitch,
anchor, spin, orient
) = no_function("generic_bottle_cap");
@ -689,7 +690,7 @@ module bottle_adapter_neck_to_cap(
thread_od = cap_thread_od,
tolerance = tolerance,
neck_od = cap_neck_od,
thread_angle = cap_thread_taper,
flank_angle = cap_thread_taper,
orient = DOWN,
pitch = cap_thread_pitch
);
@ -948,4 +949,216 @@ function bottle_adapter_neck_to_neck(
// Section: SPI Bottle Threading
// Module: sp_neck()
// Usage:
// sp_neck(diam, type, wall|id, [style], [bead], [anchor], [spin], [orient])
// Description:
// Make a SPI (Society of Plastics Industry) threaded bottle neck. You must
// supply the nominal outer diameter of the threads and the thread type, one of
// 400, 410 and 415. The 400 type neck has 360 degrees of thread, the 410
// neck has 540 degrees of thread, and the 415 neck has 720 degrees of thread.
// You can also choose between the L style thread, which is symmetric and
// the M style thread, which is an asymmetric buttress thread. You can
// specify the wall thickness (measured from the base of the threads) or
// the inner diameter, and you can specify an optional bead at the base of the threads.
// Arguments:
// diam = nominal outer diameter of threads
// type = thread type, one of 400, 410 and 415
// wall = wall thickness
// ---
// id = inner diameter
// style = Either "L" or "M" to specify the thread style. Default: "L"
// bead = if true apply a bad to the neck. Default: false
// 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`
// Examples:
// sp_neck(48,400,2);
// sp_neck(48,400,2,bead=true);
// sp_neck(22,410,2);
// sp_neck(22,410,2,bead=true);
// sp_neck(28,415,id=20,style="M");
// sp_neck(13,415,wall=1,style="M",bead=true);
// Thread specs from https://www.isbt.com/threadspecs-downloads.asp
_sp_specs = [
[400, //diam T I H S tpi
[[ 18, [ 17.68, 8.26, 9.42, 0.94, 8]],
[ 20, [ 19.69, 10.26, 9.42, 0.94, 8]],
[ 22, [ 21.69, 12.27, 9.42, 0.94, 8]],
[ 24, [ 23.67, 13.11, 10.16, 1.17, 8]],
[ 28, [ 27.38, 15.60, 10.16, 1.17, 6]],
[ 30, [ 28.37, 16.59, 10.24, 1.17, 6]],
[ 33, [ 31.83, 20.09, 10.24, 1.17, 6]],
[ 35, [ 34.34, 22.23, 10.24, 1.17, 6]],
[ 38, [ 37.19, 25.07, 10.24, 1.17, 6]],
[ 40, [ 39.75, 27.71, 10.24, 1.17, 6]],
[ 43, [ 41.63, 29.59, 10.24, 1.17, 6]],
[ 45, [ 43.82, 31.78, 10.24, 1.17, 6]],
[ 48, [ 47.12, 35.08, 10.24, 1.17, 6]],
[ 51, [ 49.56, 37.57, 10.36, 1.17, 6]],
[ 53, [ 52.07, 40.08, 10.36, 1.17, 6]],
[ 58, [ 56.06, 44.07, 10.36, 1.17, 6]],
[ 60, [ 59.06, 47.07, 10.36, 1.17, 6]],
[ 63, [ 62.08, 50.09, 10.36, 1.17, 6]],
[ 66, [ 65.07, 53.09, 10.36, 1.17, 6]],
[ 70, [ 69.06, 57.07, 10.36, 1.17, 6]],
[ 75, [ 73.56, 61.57, 10.36, 1.17, 6]],
[ 77, [ 76.66, 64.67, 12.37, 1.52, 6]],
[ 83, [ 82.58, 69.93, 12.37, 1.52, 5]],
[ 89, [ 88.75, 74.12, 13.59, 1.52, 5]],
[100, [ 99.57, 84.94, 15.16, 1.52, 5]],
[110, [109.58, 94.92, 15.16, 1.52, 5]],
[120, [119.56,104.93, 17.40, 1.52, 5]],
]],
[410, //diam T I H S tpi L W
[[ 18, [ 17.68, 8.26, 13.28, 0.94, 8, 9.17, 2.13]],
[ 20, [ 19.59, 10.26, 14.07, 0.94, 8, 9.17, 2.13]],
[ 22, [ 21.69, 12.27, 14.86, 0.94, 8, 9.55, 2.13]],
[ 24, [ 23.67, 13.11, 16.41, 1.17, 8, 11.10, 2.13]],
[ 28, [ 27.38, 15.60, 17.98, 1.17, 6, 11.76, 2.39]],
]],
[415, //diam T I H S tpi L W
[[ 13, [ 12.90, 5.54, 11.48, 0.94,12, 7.77, 1.14]],
[ 15, [ 14.61, 6.55, 14.15, 0.94,12, 8.84, 1.14]],
[ 18, [ 17.68, 8.26, 15.67, 0.94, 8, 10.90, 2.13]],
[ 20, [ 19.69, 10.26, 18.85, 0.94, 8, 11.58, 2.13]],
[ 22, [ 21.69, 12.27, 21.26, 0.94, 8, 13.87, 2.13]],
[ 24, [ 23.67, 13.11, 24.31, 1.17, 8, 14.25, 2.13]],
[ 28, [ 27.38, 15.60, 27.48, 1.17, 6, 16.64, 2.39]],
[ 33, [ 31.83, 20.09, 32.36, 1.17, 6, 19.61, 2.39]],
]]
];
_sp_twist = [ [400, 360],
[410, 540],
[415, 720]
];
// profile data: tpi, total width, depth,
_sp_thread_width= [
[5, 3.05],
[6, 2.39],
[8, 2.13],
[12, 1.14], // But note style M is different
];
function _sp_thread_profile(tpi, a, S, style) =
let(
pitch = 1/tpi*INCH,
cL = a*(1-1/sqrt(3)),
cM = (1-tan(10))*a/2,
// SP specified roundings for the thread profile have special case for tpi=12
roundings = style=="L" && tpi < 12 ? 0.5
: style=="M" && tpi < 12 ? [0.25, 0.25, 0.75, 0.75]
: style=="L" ? [0.38, 0.13, 0.13, 0.38]
: /* style=="M" */ [0.25, 0.25, 0.2, 0.5],
path = style=="L"
? round_corners([[-1/2*pitch,-a/2],
[-a/2,-a/2],
[-cL/2,0],
[cL/2,0],
[a/2,-a/2],
[1/2*pitch,-a/2]], radius=roundings, closed=false,$fn=24)
: round_corners(
[[-1/2*pitch,-a/2],
[-a/2, -a/2],
[-cM, 0],
[0,0],
[a/2,-a/2],
[1/2*pitch,-a/2]], radius=roundings, closed=false, $fn=24)
)
// Shift so that the profile is S mm from the right end to create proper length S top gap
select(right(-a/2+1/2-S,p=path),1,-2)/pitch;
function sp_neck(diam,type,wall,id,style="L",bead=false, anchor, spin, orient) = no_function("sp_neck");
module sp_neck(diam,type,wall,id,style="L",bead=false, anchor, spin, orient)
{
assert(num_defined([wall,id])==1, "Must define exactly one of wall and id");
table = struct_val(_sp_specs,type);
dum1=assert(is_def(table),"Unknown SP closure type. Type must be one of 400, 410, or 415");
entry = struct_val(table, diam);
dum2=assert(is_def(entry), str("Unknown closure nominal diameter. Allowed diameters for SP",type,": ",struct_keys(table)))
assert(style=="L" || style=="M", "style must be \"L\" or \"M\"");
T = entry[0];
I = entry[1];
H = entry[2];
S = entry[3];
tpi = entry[4];
a = (style=="M" && tpi==12) ? 1.3 : struct_val(_sp_thread_width,tpi);
twist = struct_val(_sp_twist, type);
profile = _sp_thread_profile(tpi,a,S,style);
depth = a/2;
higlen = 2*a;
higang = higlen / ((T-2*depth)*PI) * 360;
beadmax = type==400 ? (T/2-depth)+depth*1.25
: diam <=15 ? (T-.15)/2 : (T-.05)/2;
W = type==400 ? a*1.5 // arbitrary decision for type 400
: entry[6]; // specified width for 410 and 415
beadpts = [
[0,-W/2],
each arc(16, points = [[T/2-depth, -W/2],
[beadmax, 0],
[T/2-depth, W/2]]),
[0,W/2]
];
isect400 = [for(seg=pair(beadpts)) let(segisect = line_intersection([[T/2,0],[T/2,1]] , seg, LINE, SEGMENT)) if (is_def(segisect)) segisect.y];
extra_bot = type==400 && bead ? -min(subindex(beadpts,1))+max(isect400) : 0;
bead_shift = type==400 ? H+max(isect400) : entry[5]+W/2; // entry[5] is L
attachable(anchor,spin,orient,r=bead ? beadmax : T/2, l=H+extra_bot){
up((H+extra_bot)/2){
difference(){
union(){
thread_helix(d=T-.01, profile=profile, pitch = INCH/tpi, twist=twist+2*higang, higbee=higlen, anchor=TOP);
cylinder(d=T-depth*2,l=H,anchor=TOP);
if (bead)
down(bead_shift)
rotate_extrude()
polygon(beadpts);
}
up(.5)cyl(d=is_def(id) ? id : T-a-2*wall, l=H-extra_bot+1, anchor=TOP);
}
}
children();
}
}
// Function: sp_diameter()
// Usage:
// true_diam = sp_diameter(diam,type)
// Description:
// Returns the actual base diameter (root of the threads) for a SPI plastic bottle neck given the nominal diameter and type number (400, 410, 415).
function sp_diameter(diam,type) =
let(
table = struct_val(_sp_specs,type)
)
assert(is_def(table),"Unknown SP closure type. Type must be one of 400, 410, or 415")
let(
entry = struct_val(table, diam)
)
assert(is_def(entry), str("Unknown closure nominal diameter. Allowed diameters for SP",type,": ",struct_keys(table)))
entry[0];
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

30
builtins.scad Normal file
View file

@ -0,0 +1,30 @@
//////////////////////////////////////////////////////////////////////
/// Undocumented LibFile: builtins.scad
// This file has indirect calls to OpenSCAD's builtin functions and modules.
/// Includes:
// use <BOSL2/builtins.scad>
//////////////////////////////////////////////////////////////////////
/// Section: Builtin Functions
/// Section: Builtin Modules
module _square(size,center=false) square(size,center=center);
module _circle(r,d) circle(r=r,d=d);
module _text(t,size,font,halign,valign,spacing,direction,language,script)
text(t, size=size, font=font,
halign=halign, valign=valign,
spacing=spacing, direction=direction,
language=language, script=script
;)
module _cube(size,center) cube(size,center=center);
module _cylinder(h,r1,r2,center,r,d,d1,d2) cylinder(h,r=r,d=d,r1=r1,r2=r2,d1=d1,d2=d2,center=center);
module _sphere(r,center,d) sphere(r=r,d=d,center=center);
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -190,4 +190,37 @@ CTR = CENTER;
// Constant: SEGMENT
// Topics: Constants, Lines
// See Also: RAY, LINE
// Description: Treat a line as a segment. [true, true]
// Example: Usage with line_intersection:
// line1 = 10*[[9, 4], [5, 7]];
// line2 = 10*[[2, 3], [6, 5]];
// isect = line_intersection(line1, line2, SEGMENT, SEGMENT);
SEGMENT = [true,true];
// Constant: RAY
// Topics: Constants, Lines
// See Also: SEGMENT, LINE
// Description: Treat a line as a ray, based at the first point. [true, false]
// Example: Usage with line_intersection:
// line = [[-30,0],[30,30]];
// pt = [40,25];
// closest = line_closest_point(line,pt,RAY);
RAY = [true, false];
// Constant: LINE
// Topics: Constants, Lines
// See Also: RAY, SEGMENT
// Description: Treat a line as an unbounded line. [false, false]
// Example: Usage with line_intersection:
// line1 = 10*[[9, 4], [5, 7]];
// line2 = 10*[[2, 3], [6, 5]];
// isect = line_intersection(line1, line2, LINE, SEGMENT);
LINE = [false, false];
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -18,7 +18,7 @@
// Arguments:
// p = The coordinates to force into a 2D vector/point.
// fill = Value to fill missing values in vector with.
function point2d(p, fill=0) = [for (i=[0:1]) (p[i]==undef)? fill : p[i]];
function point2d(p, fill=0) = assert(is_list(p)) [for (i=[0:1]) (p[i]==undef)? fill : p[i]];
// Function: path2d()
@ -49,7 +49,9 @@ function path2d(points) =
// Arguments:
// p = The coordinates to force into a 3D vector/point.
// fill = Value to fill missing values in vector with.
function point3d(p, fill=0) = [for (i=[0:2]) (p[i]==undef)? fill : p[i]];
function point3d(p, fill=0) =
assert(is_list(p))
[for (i=[0:2]) (p[i]==undef)? fill : p[i]];
// Function: path3d()
@ -86,7 +88,8 @@ function path3d(points, fill=0) =
// Arguments:
// p = The coordinates to force into a 4D vector/point.
// fill = Value to fill missing values in vector with.
function point4d(p, fill=0) = [for (i=[0:3]) (p[i]==undef)? fill : p[i]];
function point4d(p, fill=0) = assert(is_list(p))
[for (i=[0:3]) (p[i]==undef)? fill : p[i]];
// Function: path4d()
@ -133,7 +136,7 @@ function path4d(points, fill=0) =
// Arguments:
// r = distance from the origin.
// theta = angle in degrees, counter-clockwise of X+.
// Examples:
// Example:
// xy = polar_to_xy(20,45); // Returns: ~[14.1421365, 14.1421365]
// xy = polar_to_xy(40,30); // Returns: ~[34.6410162, 15]
// xy = polar_to_xy([40,30]); // Returns: ~[34.6410162, 15]
@ -162,7 +165,7 @@ function polar_to_xy(r,theta=undef) = let(
// Arguments:
// x = X coordinate.
// y = Y coordinate.
// Examples:
// Example:
// plr = xy_to_polar(20,30);
// plr = xy_to_polar([40,60]);
// Example(2D):
@ -197,7 +200,6 @@ function xy_to_polar(x,y=undef) = let(
// If you omit the point specification then `project_plane()` returns a rotation matrix that maps the specified plane to the XY plane.
// Note that if you apply this transformation to data lying on the plane it will produce 3D points with the Z coordinate of zero.
// Topics: Coordinates, Points, Paths
// See Also: project_plane(), projection_on_plane()
// Arguments:
// plane = plane specification or point list defining the plane
// p = 3D point, path, region, VNF or bezier patch to project
@ -218,13 +220,13 @@ function xy_to_polar(x,y=undef) = let(
// stroke(xypath,closed=true);
function project_plane(plane,p) =
is_matrix(plane,3,3) && is_undef(p) ? // no data, 3 points given
assert(!collinear(plane),"Points defining the plane must not be collinear")
assert(!is_collinear(plane),"Points defining the plane must not be collinear")
let(
v = plane[2]-plane[0],
y = unit(plane[1]-plane[0]), // y axis goes to point b
x = unit(v-(v*y)*y) // x axis
)
affine3d_frame_map(x,y) * move(-plane[0])
frame_map(x,y) * move(-plane[0])
: is_vector(plane,4) && is_undef(p) ? // no data, plane given in "plane"
assert(_valid_plane(plane), "Plane is not valid")
let(
@ -243,7 +245,7 @@ function project_plane(plane,p) =
[for(plist=p) project_plane(plane,plist)]
: assert(is_vector(p,3) || is_path(p,3),str("Data must be a 3d point, path, region, vnf or bezier patch",p))
is_matrix(plane,3,3) ?
assert(!collinear(plane),"Points defining the plane must not be collinear")
assert(!is_collinear(plane),"Points defining the plane must not be collinear")
let(
v = plane[2]-plane[0],
y = unit(plane[1]-plane[0]), // y axis goes to point b
@ -280,7 +282,7 @@ function lift_plane(plane, p) =
y = unit(plane[1]-plane[0]), // y axis goes to point b
x = unit(v-(v*y)*y) // x axis
)
move(plane[0]) * affine3d_frame_map(x,y,reverse=true)
move(plane[0]) * frame_map(x,y,reverse=true)
: is_vector(plane,4) && is_undef(p) ? // no data, plane given in "plane"
assert(_valid_plane(plane), "Plane is not valid")
let(
@ -318,7 +320,7 @@ function lift_plane(plane, p) =
// r = distance from the Z axis.
// theta = angle in degrees, counter-clockwise of X+ on the XY plane.
// z = Height above XY plane.
// Examples:
// Example:
// xyz = cylindrical_to_xyz(20,30,40);
// xyz = cylindrical_to_xyz([40,60,50]);
function cylindrical_to_xyz(r,theta=undef,z=undef) = let(
@ -341,7 +343,7 @@ function cylindrical_to_xyz(r,theta=undef,z=undef) = let(
// x = X coordinate.
// y = Y coordinate.
// z = Z coordinate.
// Examples:
// Example:
// cyl = xyz_to_cylindrical(20,30,40);
// cyl = xyz_to_cylindrical([40,50,70]);
function xyz_to_cylindrical(x,y=undef,z=undef) = let(
@ -361,7 +363,7 @@ function xyz_to_cylindrical(x,y=undef,z=undef) = let(
// r = distance from origin.
// theta = angle in degrees, counter-clockwise of X+ on the XY plane.
// phi = angle in degrees from the vertical Z+ axis.
// Examples:
// Example:
// xyz = spherical_to_xyz(20,30,40);
// xyz = spherical_to_xyz([40,60,50]);
function spherical_to_xyz(r,theta=undef,phi=undef) = let(
@ -384,7 +386,7 @@ function spherical_to_xyz(r,theta=undef,phi=undef) = let(
// x = X coordinate.
// y = Y coordinate.
// z = Z coordinate.
// Examples:
// Example:
// sph = xyz_to_spherical(20,30,40);
// sph = xyz_to_spherical([40,50,70]);
function xyz_to_spherical(x,y=undef,z=undef) = let(
@ -405,7 +407,7 @@ function xyz_to_spherical(x,y=undef,z=undef) = let(
// alt = altitude angle in degrees above the XY plane.
// az = azimuth angle in degrees clockwise of Y+ on the XY plane.
// r = distance from origin.
// Examples:
// Example:
// xyz = altaz_to_xyz(20,30,40);
// xyz = altaz_to_xyz([40,60,50]);
function altaz_to_xyz(alt,az=undef,r=undef) = let(
@ -430,7 +432,7 @@ function altaz_to_xyz(alt,az=undef,r=undef) = let(
// x = X coordinate.
// y = Y coordinate.
// z = Z coordinate.
// Examples:
// Example:
// aa = xyz_to_altaz(20,30,40);
// aa = xyz_to_altaz([40,50,70]);
function xyz_to_altaz(x,y=undef,z=undef) = let(

View file

@ -346,7 +346,9 @@ module expose_anchors(opacity=0.2) {
show("anchor-arrow")
children();
hide("anchor-arrow")
color(is_string($color)? $color : point3d($color), opacity)
color(is_undef($color)? [0,0,0] :
is_string($color)? $color :
point3d($color), opacity)
children();
}
@ -365,7 +367,7 @@ module expose_anchors(opacity=0.2) {
// cube(50, center=true) show_anchors();
module show_anchors(s=10, std=true, custom=true) {
check = assert($parent_geom != undef) 1;
two_d = attach_geom_2d($parent_geom);
two_d = _attach_geom_2d($parent_geom);
if (std) {
for (anchor=standard_anchors(two_d=two_d)) {
if(two_d) {

View file

@ -7,7 +7,7 @@
//////////////////////////////////////////////////////////////////////
// Section: Translational Distributors
// Section: Translating copies of all the children
//////////////////////////////////////////////////////////////////////
@ -267,185 +267,6 @@ module zcopies(spacing, n, l, sp)
// Module: distribute()
//
// Description:
// Spreads out each individual child along the direction `dir`.
// Every child is placed at a different position, in order.
// This is useful for laying out groups of disparate objects
// where you only really care about the spacing between them.
//
// Usage:
// distribute(spacing, dir, [sizes]) ...
// distribute(l, dir, [sizes]) ...
//
// Arguments:
// spacing = Spacing to add between each child. (Default: 10.0)
// sizes = Array containing how much space each child will need.
// dir = Vector direction to distribute copies along.
// l = Length to distribute copies along.
//
// Side Effects:
// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
// `$idx` is set to the index number of each child being copied.
//
// Example:
// distribute(sizes=[100, 30, 50], dir=UP) {
// sphere(r=50);
// cube([10,20,30], center=true);
// cylinder(d=30, h=50, center=true);
// }
module distribute(spacing=undef, sizes=undef, dir=RIGHT, l=undef)
{
gaps = ($children < 2)? [0] :
!is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] :
[for (i=[0:1:$children-2]) 0];
spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10);
gaps2 = [for (gap = gaps) gap+spc];
spos = dir * -sum(gaps2)/2;
spacings = cumsum([0, each gaps2]);
for (i=[0:1:$children-1]) {
$pos = spos + spacings[i] * dir;
$idx = i;
translate($pos) children(i);
}
}
// Module: xdistribute()
//
// Description:
// Spreads out each individual child along the X axis.
// Every child is placed at a different position, in order.
// This is useful for laying out groups of disparate objects
// where you only really care about the spacing between them.
//
// Usage:
// xdistribute(spacing, [sizes]) ...
// xdistribute(l, [sizes]) ...
//
// Arguments:
// spacing = spacing between each child. (Default: 10.0)
// sizes = Array containing how much space each child will need.
// l = Length to distribute copies along.
//
// Side Effects:
// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
// `$idx` is set to the index number of each child being copied.
//
// Example:
// xdistribute(sizes=[100, 10, 30], spacing=40) {
// sphere(r=50);
// cube([10,20,30], center=true);
// cylinder(d=30, h=50, center=true);
// }
module xdistribute(spacing=10, sizes=undef, l=undef)
{
dir = RIGHT;
gaps = ($children < 2)? [0] :
!is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] :
[for (i=[0:1:$children-2]) 0];
spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10);
gaps2 = [for (gap = gaps) gap+spc];
spos = dir * -sum(gaps2)/2;
spacings = cumsum([0, each gaps2]);
for (i=[0:1:$children-1]) {
$pos = spos + spacings[i] * dir;
$idx = i;
translate($pos) children(i);
}
}
// Module: ydistribute()
//
// Description:
// Spreads out each individual child along the Y axis.
// Every child is placed at a different position, in order.
// This is useful for laying out groups of disparate objects
// where you only really care about the spacing between them.
//
// Usage:
// ydistribute(spacing, [sizes])
// ydistribute(l, [sizes])
//
// Arguments:
// spacing = spacing between each child. (Default: 10.0)
// sizes = Array containing how much space each child will need.
// l = Length to distribute copies along.
//
// Side Effects:
// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
// `$idx` is set to the index number of each child being copied.
//
// Example:
// ydistribute(sizes=[30, 20, 100], spacing=40) {
// cylinder(d=30, h=50, center=true);
// cube([10,20,30], center=true);
// sphere(r=50);
// }
module ydistribute(spacing=10, sizes=undef, l=undef)
{
dir = BACK;
gaps = ($children < 2)? [0] :
!is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] :
[for (i=[0:1:$children-2]) 0];
spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10);
gaps2 = [for (gap = gaps) gap+spc];
spos = dir * -sum(gaps2)/2;
spacings = cumsum([0, each gaps2]);
for (i=[0:1:$children-1]) {
$pos = spos + spacings[i] * dir;
$idx = i;
translate($pos) children(i);
}
}
// Module: zdistribute()
//
// Description:
// Spreads out each individual child along the Z axis.
// Every child is placed at a different position, in order.
// This is useful for laying out groups of disparate objects
// where you only really care about the spacing between them.
//
// Usage:
// zdistribute(spacing, [sizes])
// zdistribute(l, [sizes])
//
// Arguments:
// spacing = spacing between each child. (Default: 10.0)
// sizes = Array containing how much space each child will need.
// l = Length to distribute copies along.
//
// Side Effects:
// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
// `$idx` is set to the index number of each child being copied.
//
// Example:
// zdistribute(sizes=[30, 20, 100], spacing=40) {
// cylinder(d=30, h=50, center=true);
// cube([10,20,30], center=true);
// sphere(r=50);
// }
module zdistribute(spacing=10, sizes=undef, l=undef)
{
dir = UP;
gaps = ($children < 2)? [0] :
!is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] :
[for (i=[0:1:$children-2]) 0];
spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10);
gaps2 = [for (gap = gaps) gap+spc];
spos = dir * -sum(gaps2)/2;
spacings = cumsum([0, each gaps2]);
for (i=[0:1:$children-1]) {
$pos = spos + spacings[i] * dir;
$idx = i;
translate($pos) children(i);
}
}
// Module: grid2d()
@ -632,7 +453,7 @@ module grid3d(xa=[0], ya=[0], za=[0], n=undef, spacing=undef)
//////////////////////////////////////////////////////////////////////
// Section: Rotational Distributors
// Section: Rotating copies of all children
//////////////////////////////////////////////////////////////////////
@ -1018,10 +839,139 @@ module ovoid_spread(r=undef, d=undef, n=100, cone_ang=90, scale=[1,1,1], perp=tr
}
}
// Section: Placing copies of all children on a path
// Module: path_spread()
//
// Description:
// Uniformly spreads out copies of children along a path. Copies are located based on path length. If you specify `n` but not spacing then `n` copies will be placed
// with one at path[0] of `closed` is true, or spanning the entire path from start to end if `closed` is false.
// If you specify `spacing` but not `n` then copies will spread out starting from one at path[0] for `closed=true` or at the path center for open paths.
// If you specify `sp` then the copies will start at `sp`.
//
// Usage:
// path_spread(path), [n], [spacing], [sp], [rotate_children], [closed]) ...
//
// Arguments:
// path = the path where children are placed
// n = number of copies
// spacing = space between copies
// sp = if given, copies will start distance sp from the path start and spread beyond that point
//
// Side Effects:
// `$pos` is set to the center of each copy
// `$idx` is set to the index number of each copy. In the case of closed paths the first copy is at `path[0]` unless you give `sp`.
// `$dir` is set to the direction vector of the path at the point where the copy is placed.
// `$normal` is set to the direction of the normal vector to the path direction that is coplanar with the path at this point
//
// Example(2D):
// spiral = [for(theta=[0:360*8]) theta * [cos(theta), sin(theta)]]/100;
// stroke(spiral,width=.25);
// color("red") path_spread(spiral, n=100) circle(r=1);
// Example(2D):
// circle = regular_ngon(n=64, or=10);
// stroke(circle,width=1,closed=true);
// color("green") path_spread(circle, n=7, closed=true) circle(r=1+$idx/3);
// Example(2D):
// heptagon = regular_ngon(n=7, or=10);
// stroke(heptagon, width=1, closed=true);
// color("purple") path_spread(heptagon, n=9, closed=true) rect([0.5,3],anchor=FRONT);
// Example(2D): Direction at the corners is the average of the two adjacent edges
// heptagon = regular_ngon(n=7, or=10);
// stroke(heptagon, width=1, closed=true);
// color("purple") path_spread(heptagon, n=7, closed=true) rect([0.5,3],anchor=FRONT);
// Example(2D): Don't rotate the children
// heptagon = regular_ngon(n=7, or=10);
// stroke(heptagon, width=1, closed=true);
// color("red") path_spread(heptagon, n=9, closed=true, rotate_children=false) rect([0.5,3],anchor=FRONT);
// Example(2D): Open path, specify `n`
// sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]];
// stroke(sinwav,width=.1);
// color("red") path_spread(sinwav, n=5) rect([.2,1.5],anchor=FRONT);
// Example(2D): Open path, specify `n` and `spacing`
// sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]];
// stroke(sinwav,width=.1);
// color("red") path_spread(sinwav, n=5, spacing=1) rect([.2,1.5],anchor=FRONT);
// Example(2D): Closed path, specify `n` and `spacing`, copies centered around circle[0]
// circle = regular_ngon(n=64,or=10);
// stroke(circle,width=.1,closed=true);
// color("red") path_spread(circle, n=10, spacing=1, closed=true) rect([.2,1.5],anchor=FRONT);
// Example(2D): Open path, specify `spacing`
// sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]];
// stroke(sinwav,width=.1);
// color("red") path_spread(sinwav, spacing=5) rect([.2,1.5],anchor=FRONT);
// Example(2D): Open path, specify `sp`
// sinwav = [for(theta=[0:360]) 5*[theta/180, sin(theta)]];
// stroke(sinwav,width=.1);
// color("red") path_spread(sinwav, n=5, sp=18) rect([.2,1.5],anchor=FRONT);
// Example(2D):
// wedge = arc(angle=[0,100], r=10, $fn=64);
// difference(){
// polygon(concat([[0,0]],wedge));
// path_spread(wedge,n=5,spacing=3) fwd(.1) rect([1,4],anchor=FRONT);
// }
// Example(Spin,VPD=115): 3d example, with children rotated into the plane of the path
// tilted_circle = lift_plane([[0,0,0], [5,0,5], [0,2,3]],regular_ngon(n=64, or=12));
// path_sweep(regular_ngon(n=16,or=.1),tilted_circle);
// path_spread(tilted_circle, n=15,closed=true) {
// color("blue") cyl(h=3,r=.2, anchor=BOTTOM); // z-aligned cylinder
// color("red") xcyl(h=10,r=.2, anchor=FRONT+LEFT); // x-aligned cylinder
// }
// Example(Spin,VPD=115): 3d example, with rotate_children set to false
// tilted_circle = lift_plane([[0,0,0], [5,0,5], [0,2,3]], regular_ngon(n=64, or=12));
// path_sweep(regular_ngon(n=16,or=.1),tilted_circle);
// path_spread(tilted_circle, n=25,rotate_children=false,closed=true) {
// color("blue") cyl(h=3,r=.2, anchor=BOTTOM); // z-aligned cylinder
// color("red") xcyl(h=10,r=.2, anchor=FRONT+LEFT); // x-aligned cylinder
// }
module path_spread(path, n, spacing, sp=undef, rotate_children=true, closed=false)
{
length = path_length(path,closed);
distances =
is_def(sp)? ( // Start point given
is_def(n) && is_def(spacing)? count(n,sp,spacing) :
is_def(n)? lerpn(sp, length, n) :
list([sp:spacing:length])
)
: is_def(n) && is_undef(spacing)? lerpn(0,length,n,!closed) // N alone given
: ( // No start point and spacing is given, N maybe given
let(
n = is_def(n)? n : floor(length/spacing)+(closed?0:1),
ptlist = count(n,0,spacing),
listcenter = mean(ptlist)
) closed?
sort([for(entry=ptlist) posmod(entry-listcenter,length)]) :
[for(entry=ptlist) entry + length/2-listcenter ]
);
distOK = is_def(n) || (min(distances)>=0 && max(distances)<=length);
assert(distOK,"Cannot fit all of the copies");
cutlist = _path_cut_points(path, distances, closed, direction=true);
planar = len(path[0])==2;
if (true) for(i=[0:1:len(cutlist)-1]) {
$pos = cutlist[i][0];
$idx = i;
$dir = rotate_children ? (planar?[1,0]:[1,0,0]) : cutlist[i][2];
$normal = rotate_children? (planar?[0,1]:[0,0,1]) : cutlist[i][3];
translate($pos) {
if (rotate_children) {
if(planar) {
rot(from=[0,1],to=cutlist[i][3]) children();
} else {
frame_map(x=cutlist[i][2], z=cutlist[i][3])
children();
}
} else {
children();
}
}
}
}
//////////////////////////////////////////////////////////////////////
// Section: Reflectional Distributors
// Section: Making a copy of all children with reflection
//////////////////////////////////////////////////////////////////////
@ -1177,6 +1127,190 @@ module zflip_copy(offset=0, z=0)
mirror_copy(v=[0,0,1], offset=offset, cp=[0,0,z]) children();
}
////////////////////
// Section: Distributing children individually along a line
///////////////////
// Module: distribute()
//
// Description:
// Spreads out each individual child along the direction `dir`.
// Every child is placed at a different position, in order.
// This is useful for laying out groups of disparate objects
// where you only really care about the spacing between them.
//
// Usage:
// distribute(spacing, dir, [sizes]) ...
// distribute(l, dir, [sizes]) ...
//
// Arguments:
// spacing = Spacing to add between each child. (Default: 10.0)
// sizes = Array containing how much space each child will need.
// dir = Vector direction to distribute copies along.
// l = Length to distribute copies along.
//
// Side Effects:
// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
// `$idx` is set to the index number of each child being copied.
//
// Example:
// distribute(sizes=[100, 30, 50], dir=UP) {
// sphere(r=50);
// cube([10,20,30], center=true);
// cylinder(d=30, h=50, center=true);
// }
module distribute(spacing=undef, sizes=undef, dir=RIGHT, l=undef)
{
gaps = ($children < 2)? [0] :
!is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] :
[for (i=[0:1:$children-2]) 0];
spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10);
gaps2 = [for (gap = gaps) gap+spc];
spos = dir * -sum(gaps2)/2;
spacings = cumsum([0, each gaps2]);
for (i=[0:1:$children-1]) {
$pos = spos + spacings[i] * dir;
$idx = i;
translate($pos) children(i);
}
}
// Module: xdistribute()
//
// Description:
// Spreads out each individual child along the X axis.
// Every child is placed at a different position, in order.
// This is useful for laying out groups of disparate objects
// where you only really care about the spacing between them.
//
// Usage:
// xdistribute(spacing, [sizes]) ...
// xdistribute(l, [sizes]) ...
//
// Arguments:
// spacing = spacing between each child. (Default: 10.0)
// sizes = Array containing how much space each child will need.
// l = Length to distribute copies along.
//
// Side Effects:
// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
// `$idx` is set to the index number of each child being copied.
//
// Example:
// xdistribute(sizes=[100, 10, 30], spacing=40) {
// sphere(r=50);
// cube([10,20,30], center=true);
// cylinder(d=30, h=50, center=true);
// }
module xdistribute(spacing=10, sizes=undef, l=undef)
{
dir = RIGHT;
gaps = ($children < 2)? [0] :
!is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] :
[for (i=[0:1:$children-2]) 0];
spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10);
gaps2 = [for (gap = gaps) gap+spc];
spos = dir * -sum(gaps2)/2;
spacings = cumsum([0, each gaps2]);
for (i=[0:1:$children-1]) {
$pos = spos + spacings[i] * dir;
$idx = i;
translate($pos) children(i);
}
}
// Module: ydistribute()
//
// Description:
// Spreads out each individual child along the Y axis.
// Every child is placed at a different position, in order.
// This is useful for laying out groups of disparate objects
// where you only really care about the spacing between them.
//
// Usage:
// ydistribute(spacing, [sizes])
// ydistribute(l, [sizes])
//
// Arguments:
// spacing = spacing between each child. (Default: 10.0)
// sizes = Array containing how much space each child will need.
// l = Length to distribute copies along.
//
// Side Effects:
// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
// `$idx` is set to the index number of each child being copied.
//
// Example:
// ydistribute(sizes=[30, 20, 100], spacing=40) {
// cylinder(d=30, h=50, center=true);
// cube([10,20,30], center=true);
// sphere(r=50);
// }
module ydistribute(spacing=10, sizes=undef, l=undef)
{
dir = BACK;
gaps = ($children < 2)? [0] :
!is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] :
[for (i=[0:1:$children-2]) 0];
spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10);
gaps2 = [for (gap = gaps) gap+spc];
spos = dir * -sum(gaps2)/2;
spacings = cumsum([0, each gaps2]);
for (i=[0:1:$children-1]) {
$pos = spos + spacings[i] * dir;
$idx = i;
translate($pos) children(i);
}
}
// Module: zdistribute()
//
// Description:
// Spreads out each individual child along the Z axis.
// Every child is placed at a different position, in order.
// This is useful for laying out groups of disparate objects
// where you only really care about the spacing between them.
//
// Usage:
// zdistribute(spacing, [sizes])
// zdistribute(l, [sizes])
//
// Arguments:
// spacing = spacing between each child. (Default: 10.0)
// sizes = Array containing how much space each child will need.
// l = Length to distribute copies along.
//
// Side Effects:
// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually.
// `$idx` is set to the index number of each child being copied.
//
// Example:
// zdistribute(sizes=[30, 20, 100], spacing=40) {
// cylinder(d=30, h=50, center=true);
// cube([10,20,30], center=true);
// sphere(r=50);
// }
module zdistribute(spacing=10, sizes=undef, l=undef)
{
dir = UP;
gaps = ($children < 2)? [0] :
!is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] :
[for (i=[0:1:$children-2]) 0];
spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10);
gaps2 = [for (gap = gaps) gap+spc];
spos = dir * -sum(gaps2)/2;
spacings = cumsum([0, each gaps2]);
for (i=[0:1:$children-1]) {
$pos = spos + spacings[i] * dir;
$idx = i;
translate($pos) children(i);
}
}
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

930
drawing.scad Normal file
View file

@ -0,0 +1,930 @@
//////////////////////////////////////////////////////////////////////
// LibFile: drawing.scad
// This file includes stroke(), which converts a path into a
// geometric object, like drawing with a pen. It even works on
// three-dimensional paths. You can make a dashed line or add arrow
// heads. The turtle() function provides a turtle graphics style
// approach for producing paths. The arc() function produces arc paths,
// and helix() produces helix paths.
// Includes:
// include <BOSL2/std.scad>
//////////////////////////////////////////////////////////////////////
// Section: Line Drawing
// Module: stroke()
// Usage:
// stroke(path, [width], [closed], [endcaps], [endcap_width], [endcap_length], [endcap_extent], [trim]);
// stroke(path, [width], [closed], [endcap1], [endcap2], [endcap_width1], [endcap_width2], [endcap_length1], [endcap_length2], [endcap_extent1], [endcap_extent2], [trim1], [trim2]);
// Topics: Paths (2D), Paths (3D), Drawing Tools
// Description:
// Draws a 2D or 3D path with a given line width. Endcaps can be specified for each end individually.
// Figure(Med,NoAxes,2D,VPR=[0,0,0],VPD=250): Endcap Types
// cap_pairs = [
// ["butt", "chisel" ],
// ["round", "square" ],
// ["line", "cross" ],
// ["x", "diamond"],
// ["dot", "block" ],
// ["tail", "arrow" ],
// ["tail2", "arrow2" ]
// ];
// for (i = idx(cap_pairs)) {
// fwd((i-len(cap_pairs)/2+0.5)*13) {
// stroke([[-20,0], [20,0]], width=3, endcap1=cap_pairs[i][0], endcap2=cap_pairs[i][1]);
// color("black") {
// stroke([[-20,0], [20,0]], width=0.25, endcaps=false);
// left(28) text(text=cap_pairs[i][0], size=5, halign="right", valign="center");
// right(28) text(text=cap_pairs[i][1], size=5, halign="left", valign="center");
// }
// }
// }
// Arguments:
// path = The path to draw along.
// width = The width of the line to draw. If given as a list of widths, (one for each path point), draws the line with varying thickness to each point.
// closed = If true, draw an additional line from the end of the path to the start.
// plots = Specifies the plot point shape for every point of the line. If a 2D path is given, use that to draw custom plot points.
// joints = Specifies the joint shape for each joint of the line. If a 2D path is given, use that to draw custom joints.
// endcaps = Specifies the endcap type for both ends of the line. If a 2D path is given, use that to draw custom endcaps.
// endcap1 = Specifies the endcap type for the start of the line. If a 2D path is given, use that to draw a custom endcap.
// endcap2 = Specifies the endcap type for the end of the line. If a 2D path is given, use that to draw a custom endcap.
// plot_width = Some plot point shapes are wider than the line. This specifies the width of the shape, in multiples of the line width.
// joint_width = Some joint shapes are wider than the line. This specifies the width of the shape, in multiples of the line width.
// endcap_width = Some endcap types are wider than the line. This specifies the size of endcaps, in multiples of the line width.
// endcap_width1 = This specifies the size of starting endcap, in multiples of the line width.
// endcap_width2 = This specifies the size of ending endcap, in multiples of the line width.
// plot_length = Length of plot point shape, in multiples of the line width.
// joint_length = Length of joint shape, in multiples of the line width.
// endcap_length = Length of endcaps, in multiples of the line width.
// endcap_length1 = Length of starting endcap, in multiples of the line width.
// endcap_length2 = Length of ending endcap, in multiples of the line width.
// plot_extent = Extents length of plot point shape, in multiples of the line width.
// joint_extent = Extents length of joint shape, in multiples of the line width.
// endcap_extent = Extents length of endcaps, in multiples of the line width.
// endcap_extent1 = Extents length of starting endcap, in multiples of the line width.
// endcap_extent2 = Extents length of ending endcap, in multiples of the line width.
// plot_angle = Extra rotation given to plot point shapes, in degrees. If not given, the shapes are fully spun.
// joint_angle = Extra rotation given to joint shapes, in degrees. If not given, the shapes are fully spun.
// endcap_angle = Extra rotation given to endcaps, in degrees. If not given, the endcaps are fully spun.
// endcap_angle1 = Extra rotation given to a starting endcap, in degrees. If not given, the endcap is fully spun.
// endcap_angle2 = Extra rotation given to a ending endcap, in degrees. If not given, the endcap is fully spun.
// trim = Trim the the start and end line segments by this much, to keep them from interfering with custom endcaps.
// trim1 = Trim the the starting line segment by this much, to keep it from interfering with a custom endcap.
// trim2 = Trim the the ending line segment by this much, to keep it from interfering with a custom endcap.
// convexity = Max number of times a line could intersect a wall of an endcap.
// hull = If true, use `hull()` to make higher quality joints between segments, at the cost of being much slower. Default: true
// Example(2D): Drawing a Path
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
// stroke(path, width=20);
// Example(2D): Closing a Path
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
// stroke(path, width=20, endcaps=true, closed=true);
// Example(2D): Fancy Arrow Endcaps
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
// stroke(path, width=10, endcaps="arrow2");
// Example(2D): Modified Fancy Arrow Endcaps
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
// stroke(path, width=10, endcaps="arrow2", endcap_width=6, endcap_length=3, endcap_extent=2);
// Example(2D): Mixed Endcaps
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
// stroke(path, width=10, endcap1="tail2", endcap2="arrow2");
// Example(2D): Plotting Points
// path = [for (a=[0:30:360]) [a-180, 60*sin(a)]];
// stroke(path, width=3, joints="diamond", endcaps="arrow2", plot_angle=0, plot_width=5);
// Example(2D): Joints and Endcaps
// path = [for (a=[0:30:360]) [a-180, 60*sin(a)]];
// stroke(path, width=3, joints="dot", endcaps="arrow2", joint_angle=0);
// Example(2D): Custom Endcap Shapes
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
// arrow = [[0,0], [2,-3], [0.5,-2.3], [2,-4], [0.5,-3.5], [-0.5,-3.5], [-2,-4], [-0.5,-2.3], [-2,-3]];
// stroke(path, width=10, trim=3.5, endcaps=arrow);
// Example(2D): Variable Line Width
// path = circle(d=50,$fn=18);
// widths = [for (i=idx(path)) 10*i/len(path)+2];
// stroke(path,width=widths,$fa=1,$fs=1);
// Example: 3D Path with Endcaps
// path = rot([15,30,0], p=path3d(pentagon(d=50)));
// stroke(path, width=2, endcaps="arrow2", $fn=18);
// Example: 3D Path with Flat Endcaps
// path = rot([15,30,0], p=path3d(pentagon(d=50)));
// stroke(path, width=2, endcaps="arrow2", endcap_angle=0, $fn=18);
// Example: 3D Path with Mixed Endcaps
// path = rot([15,30,0], p=path3d(pentagon(d=50)));
// stroke(path, width=2, endcap1="arrow2", endcap2="tail", endcap_angle2=0, $fn=18);
// Example: 3D Path with Joints and Endcaps
// path = [for (i=[0:10:360]) [(i-180)/2,20*cos(3*i),20*sin(3*i)]];
// stroke(path, width=2, joints="dot", endcap1="round", endcap2="arrow2", joint_width=2.0, endcap_width2=3, $fn=18);
function stroke(
path, width=1, closed=false,
endcaps, endcap1, endcap2, joints, plots,
endcap_width, endcap_width1, endcap_width2, joint_width, plot_width,
endcap_length, endcap_length1, endcap_length2, joint_length, plot_length,
endcap_extent, endcap_extent1, endcap_extent2, joint_extent, plot_extent,
endcap_angle, endcap_angle1, endcap_angle2, joint_angle, plot_angle,
trim, trim1, trim2,
convexity=10, hull=true
) = no_function("stroke");
module stroke(
path, width=1, closed=false,
endcaps, endcap1, endcap2, joints, plots,
endcap_width, endcap_width1, endcap_width2, joint_width, plot_width,
endcap_length, endcap_length1, endcap_length2, joint_length, plot_length,
endcap_extent, endcap_extent1, endcap_extent2, joint_extent, plot_extent,
endcap_angle, endcap_angle1, endcap_angle2, joint_angle, plot_angle,
trim, trim1, trim2,
convexity=10, hull=true
) {
function _shape_defaults(cap) =
cap==undef? [1.00, 0.00, 0.00] :
cap==false? [1.00, 0.00, 0.00] :
cap==true? [1.00, 1.00, 0.00] :
cap=="butt"? [1.00, 0.00, 0.00] :
cap=="round"? [1.00, 1.00, 0.00] :
cap=="chisel"? [1.00, 1.00, 0.00] :
cap=="square"? [1.00, 1.00, 0.00] :
cap=="block"? [3.00, 1.00, 0.00] :
cap=="diamond"? [3.50, 1.00, 0.00] :
cap=="dot"? [3.00, 1.00, 0.00] :
cap=="x"? [3.50, 0.40, 0.00] :
cap=="cross"? [4.50, 0.22, 0.00] :
cap=="line"? [4.50, 0.22, 0.00] :
cap=="arrow"? [3.50, 0.40, 0.50] :
cap=="arrow2"? [3.50, 1.00, 0.14] :
cap=="tail"? [3.50, 0.47, 0.50] :
cap=="tail2"? [3.50, 0.28, 0.50] :
is_path(cap)? [0.00, 0.00, 0.00] :
assert(false, str("Invalid cap or joint: ",cap));
function _shape_path(cap,linewidth,w,l,l2) = (
(cap=="butt" || cap==false || cap==undef)? [] :
(cap=="round" || cap==true)? scale([w,l], p=circle(d=1, $fn=max(8, segs(w/2)))) :
cap=="chisel"? scale([w,l], p=circle(d=1,$fn=4)) :
cap=="diamond"? circle(d=w,$fn=4) :
cap=="square"? scale([w,l], p=square(1,center=true)) :
cap=="block"? scale([w,l], p=square(1,center=true)) :
cap=="dot"? circle(d=w, $fn=max(12, segs(w*3/2))) :
cap=="x"? [for (a=[0:90:270]) each rot(a,p=[[w+l/2,w-l/2]/2, [w-l/2,w+l/2]/2, [0,l/2]]) ] :
cap=="cross"? [for (a=[0:90:270]) each rot(a,p=[[l,w]/2, [-l,w]/2, [-l,l]/2]) ] :
cap=="line"? scale([w,l], p=square(1,center=true)) :
cap=="arrow"? [[0,0], [w/2,-l2], [w/2,-l2-l], [0,-l], [-w/2,-l2-l], [-w/2,-l2]] :
cap=="arrow2"? [[0,0], [w/2,-l2-l], [0,-l], [-w/2,-l2-l]] :
cap=="tail"? [[0,0], [w/2,l2], [w/2,l2-l], [0,-l], [-w/2,l2-l], [-w/2,l2]] :
cap=="tail2"? [[w/2,0], [w/2,-l], [0,-l-l2], [-w/2,-l], [-w/2,0]] :
is_path(cap)? cap :
assert(false, str("Invalid endcap: ",cap))
) * linewidth;
assert(is_bool(closed));
assert(is_list(path));
if (len(path) > 1) {
assert(is_path(path,[2,3]), "The path argument must be a list of 2D or 3D points.");
}
path = deduplicate( closed? close_path(path) : path );
assert(is_num(width) || (is_vector(width) && len(width)==len(path)));
width = is_num(width)? [for (x=path) width] : width;
assert(all([for (w=width) w>0]));
endcap1 = first_defined([endcap1, endcaps, if(!closed) plots, "round"]);
endcap2 = first_defined([endcap2, endcaps, plots, "round"]);
joints = first_defined([joints, plots, "round"]);
assert(is_bool(endcap1) || is_string(endcap1) || is_path(endcap1));
assert(is_bool(endcap2) || is_string(endcap2) || is_path(endcap2));
assert(is_bool(joints) || is_string(joints) || is_path(joints));
endcap1_dflts = _shape_defaults(endcap1);
endcap2_dflts = _shape_defaults(endcap2);
joint_dflts = _shape_defaults(joints);
endcap_width1 = first_defined([endcap_width1, endcap_width, plot_width, endcap1_dflts[0]]);
endcap_width2 = first_defined([endcap_width2, endcap_width, plot_width, endcap2_dflts[0]]);
joint_width = first_defined([joint_width, plot_width, joint_dflts[0]]);
assert(is_num(endcap_width1));
assert(is_num(endcap_width2));
assert(is_num(joint_width));
endcap_length1 = first_defined([endcap_length1, endcap_length, plot_length, endcap1_dflts[1]*endcap_width1]);
endcap_length2 = first_defined([endcap_length2, endcap_length, plot_length, endcap2_dflts[1]*endcap_width2]);
joint_length = first_defined([joint_length, plot_length, joint_dflts[1]*joint_width]);
assert(is_num(endcap_length1));
assert(is_num(endcap_length2));
assert(is_num(joint_length));
endcap_extent1 = first_defined([endcap_extent1, endcap_extent, plot_extent, endcap1_dflts[2]*endcap_width1]);
endcap_extent2 = first_defined([endcap_extent2, endcap_extent, plot_extent, endcap2_dflts[2]*endcap_width2]);
joint_extent = first_defined([joint_extent, plot_extent, joint_dflts[2]*joint_width]);
assert(is_num(endcap_extent1));
assert(is_num(endcap_extent2));
assert(is_num(joint_extent));
endcap_angle1 = first_defined([endcap_angle1, endcap_angle, plot_angle]);
endcap_angle2 = first_defined([endcap_angle2, endcap_angle, plot_angle]);
joint_angle = first_defined([joint_angle, plot_angle]);
assert(is_undef(endcap_angle1)||is_num(endcap_angle1));
assert(is_undef(endcap_angle2)||is_num(endcap_angle2));
assert(is_undef(joint_angle)||is_num(joint_angle));
endcap_shape1 = _shape_path(endcap1, width[0], endcap_width1, endcap_length1, endcap_extent1);
endcap_shape2 = _shape_path(endcap2, last(width), endcap_width2, endcap_length2, endcap_extent2);
trim1 = width[0] * first_defined([
trim1, trim,
(endcap1=="arrow")? endcap_length1-0.01 :
(endcap1=="arrow2")? endcap_length1*3/4 :
0
]);
assert(is_num(trim1));
trim2 = last(width) * first_defined([
trim2, trim,
(endcap2=="arrow")? endcap_length2-0.01 :
(endcap2=="arrow2")? endcap_length2*3/4 :
0
]);
assert(is_num(trim2));
if (len(path) == 1) {
if (len(path[0]) == 2) {
translate(path[0]) circle(d=width[0]);
} else {
translate(path[0]) sphere(d=width[0]);
}
} else {
pathcut = _path_cut_points(path, [trim1, path_length(path)-trim2], closed=false);
pathcut_su = _cut_to_seg_u_form(pathcut,path);
path2 = _path_cut_getpaths(path, pathcut, closed=false)[1];
widths = _path_select(width, pathcut_su[0][0], pathcut_su[0][1], pathcut_su[1][0], pathcut_su[1][1]);
start_vec = path[0] - path[1];
end_vec = last(path) - select(path,-2);
if (len(path[0]) == 2) {
// Straight segments
for (i = idx(path2,e=-2)) {
seg = select(path2,i,i+1);
delt = seg[1] - seg[0];
translate(seg[0]) {
rot(from=BACK,to=delt) {
trapezoid(w1=widths[i], w2=widths[i+1], h=norm(delt), anchor=FRONT);
}
}
}
// Joints
for (i = [1:1:len(path2)-2]) {
$fn = quantup(segs(widths[i]/2),4);
translate(path2[i]) {
if (joints != undef) {
joint_shape = _shape_path(
joints, width[i],
joint_width,
joint_length,
joint_extent
);
v1 = unit(path2[i] - path2[i-1]);
v2 = unit(path2[i+1] - path2[i]);
vec = unit((v1+v2)/2);
mat = is_undef(joint_angle)
? rot(from=BACK,to=v1)
: zrot(joint_angle);
multmatrix(mat) polygon(joint_shape);
} else if (hull) {
hull() {
rot(from=BACK, to=path2[i]-path2[i-1])
circle(d=widths[i]);
rot(from=BACK, to=path2[i+1]-path2[i])
circle(d=widths[i]);
}
} else {
rot(from=BACK, to=path2[i]-path2[i-1])
circle(d=widths[i]);
rot(from=BACK, to=path2[i+1]-path2[i])
circle(d=widths[i]);
}
}
}
// Endcap1
translate(path[0]) {
mat = is_undef(endcap_angle1)? rot(from=BACK,to=start_vec) :
zrot(endcap_angle1);
multmatrix(mat) polygon(endcap_shape1);
}
// Endcap2
translate(last(path)) {
mat = is_undef(endcap_angle2)? rot(from=BACK,to=end_vec) :
zrot(endcap_angle2);
multmatrix(mat) polygon(endcap_shape2);
}
} else {
quatsums = q_cumulative([
for (i = idx(path2,e=-2)) let(
vec1 = i==0? UP : unit(path2[i]-path2[i-1], UP),
vec2 = unit(path2[i+1]-path2[i], UP),
axis = vector_axis(vec1,vec2),
ang = vector_angle(vec1,vec2)
) quat(axis,ang)
]);
rotmats = [for (q=quatsums) q_matrix4(q)];
sides = [
for (i = idx(path2,e=-2))
quantup(segs(max(widths[i],widths[i+1])/2),4)
];
// Straight segments
for (i = idx(path2,e=-2)) {
dist = norm(path2[i+1] - path2[i]);
w1 = widths[i]/2;
w2 = widths[i+1]/2;
$fn = sides[i];
translate(path2[i]) {
multmatrix(rotmats[i]) {
cylinder(r1=w1, r2=w2, h=dist, center=false);
}
}
}
// Joints
for (i = [1:1:len(path2)-2]) {
$fn = sides[i];
translate(path2[i]) {
if (joints != undef) {
joint_shape = _shape_path(
joints, width[i],
joint_width,
joint_length,
joint_extent
);
multmatrix(rotmats[i] * xrot(180)) {
$fn = sides[i];
if (is_undef(joint_angle)) {
rotate_extrude(convexity=convexity) {
right_half(planar=true) {
polygon(joint_shape);
}
}
} else {
rotate([90,0,joint_angle]) {
linear_extrude(height=max(widths[i],0.001), center=true, convexity=convexity) {
polygon(joint_shape);
}
}
}
}
} else if (hull) {
hull(){
multmatrix(rotmats[i]) {
sphere(d=widths[i],style="aligned");
}
multmatrix(rotmats[i-1]) {
sphere(d=widths[i],style="aligned");
}
}
} else {
multmatrix(rotmats[i]) {
sphere(d=widths[i],style="aligned");
}
multmatrix(rotmats[i-1]) {
sphere(d=widths[i],style="aligned");
}
}
}
}
// Endcap1
translate(path[0]) {
multmatrix(rotmats[0] * xrot(180)) {
$fn = sides[0];
if (is_undef(endcap_angle1)) {
rotate_extrude(convexity=convexity) {
right_half(planar=true) {
polygon(endcap_shape1);
}
}
} else {
rotate([90,0,endcap_angle1]) {
linear_extrude(height=max(widths[0],0.001), center=true, convexity=convexity) {
polygon(endcap_shape1);
}
}
}
}
}
// Endcap2
translate(last(path)) {
multmatrix(last(rotmats)) {
$fn = last(sides);
if (is_undef(endcap_angle2)) {
rotate_extrude(convexity=convexity) {
right_half(planar=true) {
polygon(endcap_shape2);
}
}
} else {
rotate([90,0,endcap_angle2]) {
linear_extrude(height=max(last(widths),0.001), center=true, convexity=convexity) {
polygon(endcap_shape2);
}
}
}
}
}
}
}
}
// Function&Module: dashed_stroke()
// Usage: As a Module
// dashed_stroke(path, dashpat, [closed=]);
// Usage: As a Function
// dashes = dashed_stroke(path, dashpat, width=, [closed=]);
// Topics: Paths, Drawing Tools
// See Also: stroke(), path_cut()
// Description:
// Given a path and a dash pattern, creates a dashed line that follows that
// path with the given dash pattern.
// - When called as a function, returns a list of dash sub-paths.
// - When called as a module, draws all those subpaths using `stroke()`.
// Arguments:
// path = The path to subdivide into dashes.
// dashpat = A list of alternating dash lengths and space lengths for the dash pattern. This will be scaled by the width of the line.
// ---
// width = The width of the dashed line to draw. Module only. Default: 1
// closed = If true, treat path as a closed polygon. Default: false
// Example(2D): Open Path
// path = [for (a=[-180:10:180]) [a/3,20*sin(a)]];
// dashed_stroke(path, [3,2], width=1);
// Example(2D): Closed Polygon
// path = circle(d=100,$fn=72);
// dashpat = [10,2,3,2,3,2];
// dashed_stroke(path, dashpat, width=1, closed=true);
// Example(FlatSpin,VPD=250): 3D Dashed Path
// path = [for (a=[-180:5:180]) [a/3, 20*cos(3*a), 20*sin(3*a)]];
// dashed_stroke(path, [3,2], width=1);
function dashed_stroke(path, dashpat=[3,3], closed=false) =
let(
path = closed? close_path(path) : path,
dashpat = len(dashpat)%2==0? dashpat : concat(dashpat,[0]),
plen = path_length(path),
dlen = sum(dashpat),
doff = cumsum(dashpat),
reps = floor(plen / dlen),
step = plen / reps,
cuts = [
for (i=[0:1:reps-1], off=doff)
let (st=i*step, x=st+off)
if (x>0 && x<plen) x
],
dashes = path_cut(path, cuts, closed=false),
evens = [for (i=idx(dashes)) if (i%2==0) dashes[i]]
) evens;
module dashed_stroke(path, dashpat=[3,3], width=1, closed=false) {
segs = dashed_stroke(path, dashpat=dashpat*width, closed=closed);
for (seg = segs)
stroke(seg, width=width, endcaps=false);
}
// Section: Computing paths
// Function&Module: arc()
// Usage: 2D arc from 0º to `angle` degrees.
// arc(N, r|d=, angle);
// Usage: 2D arc from START to END degrees.
// arc(N, r|d=, angle=[START,END])
// Usage: 2D arc from `start` to `start+angle` degrees.
// arc(N, r|d=, start=, angle=)
// Usage: 2D circle segment by `width` and `thickness`, starting and ending on the X axis.
// arc(N, width=, thickness=)
// Usage: Shortest 2D or 3D arc around centerpoint `cp`, starting at P0 and ending on the vector pointing from `cp` to `P1`.
// arc(N, cp=, points=[P0,P1], [long=], [cw=], [ccw=])
// Usage: 2D or 3D arc, starting at `P0`, passing through `P1` and ending at `P2`.
// arc(N, points=[P0,P1,P2])
// Topics: Paths (2D), Paths (3D), Shapes (2D), Path Generators
// Description:
// If called as a function, returns a 2D or 3D path forming an arc.
// If called as a module, creates a 2D arc polygon or pie slice shape.
// Arguments:
// N = Number of vertices to form the arc curve from.
// r = Radius of the arc.
// angle = If a scalar, specifies the end angle in degrees (relative to start parameter). If a vector of two scalars, specifies start and end angles.
// ---
// d = Diameter of the arc.
// cp = Centerpoint of arc.
// points = Points on the arc.
// long = if given with cp and points takes the long arc instead of the default short arc. Default: false
// cw = if given with cp and 2 points takes the arc in the clockwise direction. Default: false
// ccw = if given with cp and 2 points takes the arc in the counter-clockwise direction. Default: false
// width = If given with `thickness`, arc starts and ends on X axis, to make a circle segment.
// thickness = If given with `width`, arc starts and ends on X axis, to make a circle segment.
// start = Start angle of arc.
// wedge = If true, include centerpoint `cp` in output to form pie slice shape.
// endpoint = If false exclude the last point (function only). Default: true
// Examples(2D):
// arc(N=4, r=30, angle=30, wedge=true);
// arc(r=30, angle=30, wedge=true);
// arc(d=60, angle=30, wedge=true);
// arc(d=60, angle=120);
// arc(d=60, angle=120, wedge=true);
// arc(r=30, angle=[75,135], wedge=true);
// arc(r=30, start=45, angle=75, wedge=true);
// arc(width=60, thickness=20);
// arc(cp=[-10,5], points=[[20,10],[0,35]], wedge=true);
// arc(points=[[30,-5],[20,10],[-10,20]], wedge=true);
// arc(points=[[5,30],[-10,-10],[30,5]], wedge=true);
// Example(2D):
// path = arc(points=[[5,30],[-10,-10],[30,5]], wedge=true);
// stroke(closed=true, path);
// Example(FlatSpin,VPD=175):
// path = arc(points=[[0,30,0],[0,0,30],[30,0,0]]);
// trace_path(path, showpts=true, color="cyan");
function arc(N, r, angle, d, cp, points, width, thickness, start, wedge=false, long=false, cw=false, ccw=false, endpoint=true) =
assert(is_bool(endpoint))
!endpoint ? assert(!wedge, "endpoint cannot be false if wedge is true")
list_head(arc(N+1,r,angle,d,cp,points,width,thickness,start,wedge,long,cw,ccw,true)) :
assert(is_undef(N) || (is_integer(N) && N>=2), "Number of points must be an integer 2 or larger")
// First try for 2D arc specified by width and thickness
is_def(width) && is_def(thickness)? (
assert(!any_defined([r,cp,points]) && !any([cw,ccw,long]),"Conflicting or invalid parameters to arc")
assert(width>0, "Width must be postive")
assert(thickness>0, "Thickness must be positive")
arc(N,points=[[width/2,0], [0,thickness], [-width/2,0]],wedge=wedge)
) : is_def(angle)? (
let(
parmok = !any_defined([points,width,thickness]) &&
((is_vector(angle,2) && is_undef(start)) || is_num(angle))
)
assert(parmok,"Invalid parameters in arc")
let(
cp = first_defined([cp,[0,0]]),
start = is_def(start)? start : is_vector(angle) ? angle[0] : 0,
angle = is_vector(angle)? angle[1]-angle[0] : angle,
r = get_radius(r=r, d=d)
)
assert(is_vector(cp,2),"Centerpoint must be a 2d vector")
assert(angle!=0, "Arc has zero length")
assert(is_def(r) && r>0, "Arc radius invalid")
let(
N = is_def(N) ? N : max(3, ceil(segs(r)*abs(angle)/360)),
arcpoints = [for(i=[0:N-1]) let(theta = start + i*angle/(N-1)) r*[cos(theta),sin(theta)]+cp],
extra = wedge? [cp] : []
)
concat(extra,arcpoints)
) :
assert(is_path(points,[2,3]),"Point list is invalid")
// Arc is 3D, so transform points to 2D and make a recursive call, then remap back to 3D
len(points[0])==3? (
assert(!(cw || ccw), "(Counter)clockwise isn't meaningful in 3d, so `cw` and `ccw` must be false")
assert(is_undef(cp) || is_vector(cp,3),"points are 3d so cp must be 3d")
let(
plane = [is_def(cp) ? cp : points[2], points[0], points[1]],
center2d = is_def(cp) ? project_plane(plane,cp) : undef,
points2d = project_plane(plane, points)
)
lift_plane(plane,arc(N,cp=center2d,points=points2d,wedge=wedge,long=long))
) : is_def(cp)? (
// Arc defined by center plus two points, will have radius defined by center and points[0]
// and extent defined by direction of point[1] from the center
assert(is_vector(cp,2), "Centerpoint must be a 2d vector")
assert(len(points)==2, "When pointlist has length 3 centerpoint is not allowed")
assert(points[0]!=points[1], "Arc endpoints are equal")
assert(cp!=points[0]&&cp!=points[1], "Centerpoint equals an arc endpoint")
assert(count_true([long,cw,ccw])<=1, str("Only one of `long`, `cw` and `ccw` can be true",cw,ccw,long))
let(
angle = vector_angle(points[0], cp, points[1]),
v1 = points[0]-cp,
v2 = points[1]-cp,
prelim_dir = sign(det2([v1,v2])), // z component of cross product
dir = prelim_dir != 0
? prelim_dir
: assert(cw || ccw, "Collinear inputs don't define a unique arc")
1,
r=norm(v1),
final_angle = long || (ccw && dir<0) || (cw && dir>0) ? -dir*(360-angle) : dir*angle
)
arc(N,cp=cp,r=r,start=atan2(v1.y,v1.x),angle=final_angle,wedge=wedge)
) : (
// Final case is arc passing through three points, starting at point[0] and ending at point[3]
let(col = is_collinear(points[0],points[1],points[2]))
assert(!col, "Collinear inputs do not define an arc")
let(
cp = line_intersection(_normal_segment(points[0],points[1]),_normal_segment(points[1],points[2])),
// select order to be counterclockwise
dir = det2([points[1]-points[0],points[2]-points[1]]) > 0,
points = dir? select(points,[0,2]) : select(points,[2,0]),
r = norm(points[0]-cp),
theta_start = atan2(points[0].y-cp.y, points[0].x-cp.x),
theta_end = atan2(points[1].y-cp.y, points[1].x-cp.x),
angle = posmod(theta_end-theta_start, 360),
arcpts = arc(N,cp=cp,r=r,start=theta_start,angle=angle,wedge=wedge)
)
dir ? arcpts : reverse(arcpts)
);
module arc(N, r, angle, d, cp, points, width, thickness, start, wedge=false)
{
path = arc(N=N, r=r, angle=angle, d=d, cp=cp, points=points, width=width, thickness=thickness, start=start, wedge=wedge);
polygon(path);
}
// Function: helix()
// Description:
// Returns a 3D helical path.
// Usage:
// helix(turns, h, n, r|d, [cp], [scale]);
// Arguments:
// h = Height of spiral.
// turns = Number of turns in spiral.
// n = Number of spiral sides.
// r = Radius of spiral.
// d = Radius of spiral.
// cp = Centerpoint of spiral. Default: `[0,0]`
// scale = [X,Y] scaling factors for each axis. Default: `[1,1]`
// Example(3D):
// trace_path(helix(turns=2.5, h=100, n=24, r=50), N=1, showpts=true);
function helix(turns=3, h=100, n=12, r, d, cp=[0,0], scale=[1,1]) = let(
rr=get_radius(r=r, d=d, dflt=100),
cnt=floor(turns*n),
dz=h/cnt
) [
for (i=[0:1:cnt]) [
rr * cos(i*360/n) * scale.x + cp.x,
rr * sin(i*360/n) * scale.y + cp.y,
i*dz
]
];
function _normal_segment(p1,p2) =
let(center = (p1+p2)/2)
[center, center + norm(p1-p2)/2 * line_normal(p1,p2)];
// Function: turtle()
// Usage:
// turtle(commands, [state], [full_state=], [repeat=])
// Topics: Shapes (2D), Path Generators (2D), Mini-Language
// See Also: turtle3d()
// Description:
// Use a sequence of turtle graphics commands to generate a path. The parameter `commands` is a list of
// turtle commands and optional parameters for each command. The turtle state has a position, movement direction,
// movement distance, and default turn angle. If you do not give `state` as input then the turtle starts at the
// origin, pointed along the positive x axis with a movement distance of 1. By default, `turtle` returns just
// the computed turtle path. If you set `full_state` to true then it instead returns the full turtle state.
// You can invoke `turtle` again with this full state to continue the turtle path where you left off.
// .
// The turtle state is a list with three entries: the path constructed so far, the current step as a 2-vector, the current default angle,
// and the current arcsteps setting.
// .
// Commands | Arguments | What it does
// ------------ | ------------------ | -------------------------------
// "move" | [dist] | Move turtle scale*dist units in the turtle direction. Default dist=1.
// "xmove" | [dist] | Move turtle scale*dist units in the x direction. Default dist=1. Does not change turtle direction.
// "ymove" | [dist] | Move turtle scale*dist units in the y direction. Default dist=1. Does not change turtle direction.
// "xymove" | vector | Move turtle by the specified vector. Does not change turtle direction.
// "untilx" | xtarget | Move turtle in turtle direction until x==xtarget. Produces an error if xtarget is not reachable.
// "untily" | ytarget | Move turtle in turtle direction until y==ytarget. Produces an error if xtarget is not reachable.
// "jump" | point | Move the turtle to the specified point
// "xjump" | x | Move the turtle's x position to the specified value
// "yjump | y | Move the turtle's y position to the specified value
// "turn" | [angle] | Turn turtle direction by specified angle, or the turtle's default turn angle. The default angle starts at 90.
// "left" | [angle] | Same as "turn"
// "right" | [angle] | Same as "turn", -angle
// "angle" | angle | Set the default turn angle.
// "setdir" | dir | Set turtle direction. The parameter `dir` can be an angle or a vector.
// "length" | length | Change the turtle move distance to `length`
// "scale" | factor | Multiply turtle move distance by `factor`
// "addlength" | length | Add `length` to the turtle move distance
// "repeat" | count, commands | Repeats a list of commands `count` times.
// "arcleft" | radius, [angle] | Draw an arc from the current position toward the left at the specified radius and angle. The turtle turns by `angle`. A negative angle draws the arc to the right instead of the left, and leaves the turtle facing right. A negative radius draws the arc to the right but leaves the turtle facing left.
// "arcright" | radius, [angle] | Draw an arc from the current position toward the right at the specified radius and angle
// "arcleftto" | radius, angle | Draw an arc at the given radius turning toward the left until reaching the specified absolute angle.
// "arcrightto" | radius, angle | Draw an arc at the given radius turning toward the right until reaching the specified absolute angle.
// "arcsteps" | count | Specifies the number of segments to use for drawing arcs. If you set it to zero then the standard `$fn`, `$fa` and `$fs` variables define the number of segments.
//
// Arguments:
// commands = List of turtle commands
// state = Starting turtle state (from previous call) or starting point. Default: start at the origin, pointing right.
// ---
// full_state = If true return the full turtle state for continuing the path in subsequent turtle calls. Default: false
// repeat = Number of times to repeat the command list. Default: 1
//
// Example(2D): Simple rectangle
// path = turtle(["xmove",3, "ymove", "xmove",-3, "ymove",-1]);
// stroke(path,width=.1);
// Example(2D): Pentagon
// path=turtle(["angle",360/5,"move","turn","move","turn","move","turn","move"]);
// stroke(path,width=.1,closed=true);
// Example(2D): Pentagon using the repeat argument
// path=turtle(["move","turn",360/5],repeat=5);
// stroke(path,width=.1,closed=true);
// Example(2D): Pentagon using the repeat turtle command, setting the turn angle
// path=turtle(["angle",360/5,"repeat",5,["move","turn"]]);
// stroke(path,width=.1,closed=true);
// Example(2D): Pentagram
// path = turtle(["move","left",144], repeat=4);
// stroke(path,width=.05,closed=true);
// Example(2D): Sawtooth path
// path = turtle([
// "turn", 55,
// "untily", 2,
// "turn", -55-90,
// "untily", 0,
// "turn", 55+90,
// "untily", 2.5,
// "turn", -55-90,
// "untily", 0,
// "turn", 55+90,
// "untily", 3,
// "turn", -55-90,
// "untily", 0
// ]);
// stroke(path, width=.1);
// Example(2D): Simpler way to draw the sawtooth. The direction of the turtle is preserved when executing "yjump".
// path = turtle([
// "turn", 55,
// "untily", 2,
// "yjump", 0,
// "untily", 2.5,
// "yjump", 0,
// "untily", 3,
// "yjump", 0,
// ]);
// stroke(path, width=.1);
// Example(2DMed): square spiral
// path = turtle(["move","left","addlength",1],repeat=50);
// stroke(path,width=.2);
// Example(2DMed): pentagonal spiral
// path = turtle(["move","left",360/5,"addlength",1],repeat=50);
// stroke(path,width=.2);
// Example(2DMed): yet another spiral, without using `repeat`
// path = turtle(concat(["angle",71],flatten(repeat(["move","left","addlength",1],50))));
// stroke(path,width=.2);
// Example(2DMed): The previous spiral grows linearly and eventually intersects itself. This one grows geometrically and does not.
// path = turtle(["move","left",71,"scale",1.05],repeat=50);
// stroke(path,width=.05);
// Example(2D): Koch Snowflake
// function koch_unit(depth) =
// depth==0 ? ["move"] :
// concat(
// koch_unit(depth-1),
// ["right"],
// koch_unit(depth-1),
// ["left","left"],
// koch_unit(depth-1),
// ["right"],
// koch_unit(depth-1)
// );
// koch=concat(["angle",60,"repeat",3],[concat(koch_unit(3),["left","left"])]);
// polygon(turtle(koch));
module turtle(commands, state=[[[0,0]],[1,0],90,0], full_state=false, repeat=1) {no_module();}
function turtle(commands, state=[[[0,0]],[1,0],90,0], full_state=false, repeat=1) =
let( state = is_vector(state) ? [[state],[1,0],90,0] : state )
repeat == 1?
_turtle(commands,state,full_state) :
_turtle_repeat(commands, state, full_state, repeat);
function _turtle_repeat(commands, state, full_state, repeat) =
repeat==1?
_turtle(commands,state,full_state) :
_turtle_repeat(commands, _turtle(commands, state, true), full_state, repeat-1);
function _turtle_command_len(commands, index) =
let( one_or_two_arg = ["arcleft","arcright", "arcleftto", "arcrightto"] )
commands[index] == "repeat"? 3 : // Repeat command requires 2 args
// For these, the first arg is required, second arg is present if it is not a string
in_list(commands[index], one_or_two_arg) && len(commands)>index+2 && !is_string(commands[index+2]) ? 3 :
is_string(commands[index+1])? 1 : // If 2nd item is a string it's must be a new command
2; // Otherwise we have command and arg
function _turtle(commands, state, full_state, index=0) =
index < len(commands) ?
_turtle(commands,
_turtle_command(commands[index],commands[index+1],commands[index+2],state,index),
full_state,
index+_turtle_command_len(commands,index)
) :
( full_state ? state : state[0] );
// Turtle state: state = [path, step_vector, default angle, default arcsteps]
function _turtle_command(command, parm, parm2, state, index) =
command == "repeat"?
assert(is_num(parm),str("\"repeat\" command requires a numeric repeat count at index ",index))
assert(is_list(parm2),str("\"repeat\" command requires a command list parameter at index ",index))
_turtle_repeat(parm2, state, true, parm) :
let(
path = 0,
step=1,
angle=2,
arcsteps=3,
parm = !is_string(parm) ? parm : undef,
parm2 = !is_string(parm2) ? parm2 : undef,
needvec = ["jump", "xymove"],
neednum = ["untilx","untily","xjump","yjump","angle","length","scale","addlength"],
needeither = ["setdir"],
chvec = !in_list(command,needvec) || is_vector(parm,2),
chnum = !in_list(command,neednum) || is_num(parm),
vec_or_num = !in_list(command,needeither) || (is_num(parm) || is_vector(parm,2)),
lastpt = last(state[path])
)
assert(chvec,str("\"",command,"\" requires a vector parameter at index ",index))
assert(chnum,str("\"",command,"\" requires a numeric parameter at index ",index))
assert(vec_or_num,str("\"",command,"\" requires a vector or numeric parameter at index ",index))
command=="move" ? list_set(state, path, concat(state[path],[default(parm,1)*state[step]+lastpt])) :
command=="untilx" ? (
let(
int = line_intersection([lastpt,lastpt+state[step]], [[parm,0],[parm,1]]),
xgood = sign(state[step].x) == sign(int.x-lastpt.x)
)
assert(xgood,str("\"untilx\" never reaches desired goal at index ",index))
list_set(state,path,concat(state[path],[int]))
) :
command=="untily" ? (
let(
int = line_intersection([lastpt,lastpt+state[step]], [[0,parm],[1,parm]]),
ygood = is_def(int) && sign(state[step].y) == sign(int.y-lastpt.y)
)
assert(ygood,str("\"untily\" never reaches desired goal at index ",index))
list_set(state,path,concat(state[path],[int]))
) :
command=="xmove" ? list_set(state, path, concat(state[path],[default(parm,1)*norm(state[step])*[1,0]+lastpt])):
command=="ymove" ? list_set(state, path, concat(state[path],[default(parm,1)*norm(state[step])*[0,1]+lastpt])):
command=="xymove" ? list_set(state, path, concat(state[path], [lastpt+parm])):
command=="jump" ? list_set(state, path, concat(state[path],[parm])):
command=="xjump" ? list_set(state, path, concat(state[path],[[parm,lastpt.y]])):
command=="yjump" ? list_set(state, path, concat(state[path],[[lastpt.x,parm]])):
command=="turn" || command=="left" ? list_set(state, step, rot(default(parm,state[angle]),p=state[step],planar=true)) :
command=="right" ? list_set(state, step, rot(-default(parm,state[angle]),p=state[step],planar=true)) :
command=="angle" ? list_set(state, angle, parm) :
command=="setdir" ? (
is_vector(parm) ?
list_set(state, step, norm(state[step]) * unit(parm)) :
list_set(state, step, norm(state[step]) * [cos(parm),sin(parm)])
) :
command=="length" ? list_set(state, step, parm*unit(state[step])) :
command=="scale" ? list_set(state, step, parm*state[step]) :
command=="addlength" ? list_set(state, step, state[step]+unit(state[step])*parm) :
command=="arcsteps" ? list_set(state, arcsteps, parm) :
command=="arcleft" || command=="arcright" ?
assert(is_num(parm),str("\"",command,"\" command requires a numeric radius value at index ",index))
let(
myangle = default(parm2,state[angle]),
lrsign = command=="arcleft" ? 1 : -1,
radius = parm*sign(myangle),
center = lastpt + lrsign*radius*line_normal([0,0],state[step]),
steps = state[arcsteps]==0 ? segs(abs(radius)) : state[arcsteps],
arcpath = myangle == 0 || radius == 0 ? [] : arc(
steps,
points = [
lastpt,
rot(cp=center, p=lastpt, a=sign(parm)*lrsign*myangle/2),
rot(cp=center, p=lastpt, a=sign(parm)*lrsign*myangle)
]
)
)
list_set(
state, [path,step], [
concat(state[path], list_tail(arcpath)),
rot(lrsign * myangle,p=state[step],planar=true)
]
) :
command=="arcleftto" || command=="arcrightto" ?
assert(is_num(parm),str("\"",command,"\" command requires a numeric radius value at index ",index))
assert(is_num(parm2),str("\"",command,"\" command requires a numeric angle value at index ",index))
let(
radius = parm,
lrsign = command=="arcleftto" ? 1 : -1,
center = lastpt + lrsign*radius*line_normal([0,0],state[step]),
steps = state[arcsteps]==0 ? segs(abs(radius)) : state[arcsteps],
start_angle = posmod(atan2(state[step].y, state[step].x),360),
end_angle = posmod(parm2,360),
delta_angle = -start_angle + (lrsign * end_angle < lrsign*start_angle ? end_angle+lrsign*360 : end_angle),
arcpath = delta_angle == 0 || radius==0 ? [] : arc(
steps,
points = [
lastpt,
rot(cp=center, p=lastpt, a=sign(radius)*delta_angle/2),
rot(cp=center, p=lastpt, a=sign(radius)*delta_angle)
]
)
)
list_set(
state, [path,step], [
concat(state[path], list_tail(arcpath)),
rot(delta_angle,p=state[step],planar=true)
]
) :
assert(false,str("Unknown turtle command \"",command,"\" at index",index))
[];

View file

@ -41,7 +41,7 @@ function _edges_text(edges) =
is_string(edges) ? [str("\"",edges,"\"")] :
edges==EDGES_NONE ? ["EDGES_NONE"] :
edges==EDGES_ALL ? ["EDGES_ALL"] :
is_edge_array(edges) ? [""] :
_is_edge_array(edges) ? [""] :
is_vector(edges,3) ? _edges_vec_txt(edges) :
is_list(edges) ? let(
lst = [for (x=edges) each _edges_text(x)],
@ -109,20 +109,20 @@ EDGE_OFFSETS = [
// Section: Edge Helpers
// Function: is_edge_array()
// Topics: Edges, Type Checking
// Usage:
// bool = is_edge_array(x);
// Description:
// Returns true if the given value has the form of an edge array.
// Arguments:
// x = The item to check the type of.
// See Also: edges(), EDGES_NONE, EDGES_ALL
function is_edge_array(x) = is_list(x) && is_vector(x[0]) && len(x)==3 && len(x[0])==4;
/// Internal Function: _is_edge_array()
/// Topics: Edges, Type Checking
/// Usage:
/// bool = _is_edge_array(x);
/// Description:
/// Returns true if the given value has the form of an edge array.
/// Arguments:
/// x = The item to check the type of.
/// See Also: edges(), EDGES_NONE, EDGES_ALL
function _is_edge_array(x) = is_list(x) && is_vector(x[0]) && len(x)==3 && len(x[0])==4;
function _edge_set(v) =
is_edge_array(v)? v : [
_is_edge_array(v)? v : [
for (ax=[0:2]) [
for (b=[-1,1], a=[-1,1]) let(
v2=[[0,a,b],[a,0,b],[a,b,0]][ax]
@ -153,15 +153,15 @@ function _edge_set(v) =
];
// Function: normalize_edges()
// Topics: Edges
// Usage:
// edges = normalize_edges(v);
// Description:
// Normalizes all values in an edge array to be `1`, if it was originally greater than `0`,
// or `0`, if it was originally less than or equal to `0`.
// See Also: is_edge_array(), edges(), EDGES_NONE, EDGES_ALL
function normalize_edges(v) = [for (ax=v) [for (edge=ax) edge>0? 1 : 0]];
/// Internal Function: _normalize_edges()
/// Topics: Edges
/// Usage:
/// edges = _normalize_edges(v);
/// Description:
/// Normalizes all values in an edge array to be `1`, if it was originally greater than `0`,
/// or `0`, if it was originally less than or equal to `0`.
/// See Also: edges(), EDGES_NONE, EDGES_ALL
function _normalize_edges(v) = [for (ax=v) [for (edge=ax) edge>0? 1 : 0]];
// Function: edges()
@ -259,7 +259,7 @@ function normalize_edges(v) = [for (ax=v) [for (edge=ax) edge>0? 1 : 0]];
// v = The edge set to include.
// except = The edge set to specifically exclude, even if they are in `v`.
//
// See Also: is_edge_array(), normalize_edges(), EDGES_NONE, EDGES_ALL
// See Also: EDGES_NONE, EDGES_ALL
//
// Example(3D): Just the front-top edge
// edg = edges(FRONT+TOP);
@ -283,11 +283,11 @@ function normalize_edges(v) = [for (ax=v) [for (edge=ax) edge>0? 1 : 0]];
// edg = edges("ALL", except=edges("Z", except=BACK));
// show_edges(edges=edg);
function edges(v, except=[]) =
(is_string(v) || is_vector(v) || is_edge_array(v))? edges([v], except=except) :
(is_string(except) || is_vector(except) || is_edge_array(except))? edges(v, except=[except]) :
except==[]? normalize_edges(sum([for (x=v) _edge_set(x)])) :
normalize_edges(
normalize_edges(sum([for (x=v) _edge_set(x)])) -
(is_string(v) || is_vector(v) || _is_edge_array(v))? edges([v], except=except) :
(is_string(except) || is_vector(except) || _is_edge_array(except))? edges(v, except=[except]) :
except==[]? _normalize_edges(sum([for (x=v) _edge_set(x)])) :
_normalize_edges(
_normalize_edges(sum([for (x=v) _edge_set(x)])) -
sum([for (x=except) _edge_set(x)])
);
@ -303,7 +303,7 @@ function edges(v, except=[]) =
// size = The scalar size of the cube.
// text = The text to show on the front of the cube.
// txtsize = The size of the text.
// See Also: is_edge_array(), edges(), EDGES_NONE, EDGES_ALL
// See Also: edges(), EDGES_NONE, EDGES_ALL
// Example:
// show_edges(size=30, edges=["X","Y"]);
module show_edges(edges="ALL", size=20, text, txtsize=3) {
@ -365,29 +365,29 @@ CORNER_OFFSETS = [
// Section: Corner Helpers
// Function: is_corner_array()
// Topics: Corners, Type Checking
// Usage:
// bool = is_corner_array(x)
// Description:
// Returns true if the given value has the form of a corner array.
// See Also: CORNERS_NONE, CORNERS_ALL, corners()
function is_corner_array(x) = is_vector(x) && len(x)==8 && all([for (xx=x) xx==1||xx==0]);
/// Internal Function: _is_corner_array()
/// Topics: Corners, Type Checking
/// Usage:
/// bool = _is_corner_array(x)
/// Description:
/// Returns true if the given value has the form of a corner array.
/// See Also: CORNERS_NONE, CORNERS_ALL, corners()
function _is_corner_array(x) = is_vector(x) && len(x)==8 && all([for (xx=x) xx==1||xx==0]);
// Function: normalize_corners()
// Topics: Corners
// Usage:
// corns = normalize_corners(v);
// Description:
// Normalizes all values in a corner array to be `1`, if it was originally greater than `0`,
// or `0`, if it was originally less than or equal to `0`.
// See Also: CORNERS_NONE, CORNERS_ALL, is_corner_array(), corners()
function normalize_corners(v) = [for (x=v) x>0? 1 : 0];
/// Internal Function: _normalize_corners()
/// Topics: Corners
/// Usage:
/// corns = _normalize_corners(v);
/// Description:
/// Normalizes all values in a corner array to be `1`, if it was originally greater than `0`,
/// or `0`, if it was originally less than or equal to `0`.
/// See Also: CORNERS_NONE, CORNERS_ALL, corners()
function _normalize_corners(v) = [for (x=v) x>0? 1 : 0];
function _corner_set(v) =
is_corner_array(v)? v : [
_is_corner_array(v)? v : [
for (i=[0:7]) let(
v2 = CORNER_OFFSETS[i]
) (
@ -480,7 +480,7 @@ function _corner_set(v) =
// show_corners(corners="ALL");
// show_corners(corners="NONE");
// }
// See Also: CORNERS_NONE, CORNERS_ALL, is_corner_array(), normalize_corners()
// See Also: CORNERS_NONE, CORNERS_ALL
// Example(3D): Just the front-top-right corner
// crn = corners(FRONT+TOP+RIGHT);
// show_corners(corners=crn);
@ -497,37 +497,37 @@ function _corner_set(v) =
// crn = corners([BOTTOM,FRONT], except=BOTTOM+FRONT);
// show_corners(corners=crn);
function corners(v, except=[]) =
(is_string(v) || is_vector(v) || is_corner_array(v))? corners([v], except=except) :
(is_string(except) || is_vector(except) || is_corner_array(except))? corners(v, except=[except]) :
except==[]? normalize_corners(sum([for (x=v) _corner_set(x)])) :
(is_string(v) || is_vector(v) || _is_corner_array(v))? corners([v], except=except) :
(is_string(except) || is_vector(except) || _is_corner_array(except))? corners(v, except=[except]) :
except==[]? _normalize_corners(sum([for (x=v) _corner_set(x)])) :
let(
a = normalize_corners(sum([for (x=v) _corner_set(x)])),
b = normalize_corners(sum([for (x=except) _corner_set(x)]))
) normalize_corners(a - b);
a = _normalize_corners(sum([for (x=v) _corner_set(x)])),
b = _normalize_corners(sum([for (x=except) _corner_set(x)]))
) _normalize_corners(a - b);
// Function: corner_edges()
// Topics: Corners
// Description:
// Returns [XCOUNT,YCOUNT,ZCOUNT] where each is the count of edges aligned with that
// axis that are in the edge set and touch the given corner.
// Arguments:
// edges = Standard edges array.
// v = Vector pointing to the corner to count edge intersections at.
// See Also: CORNERS_NONE, CORNERS_ALL, is_corner_array(), corners(), corner_edge_count()
function corner_edges(edges, v) =
/// Internal Function: _corner_edges()
/// Topics: Corners
/// Description:
/// Returns [XCOUNT,YCOUNT,ZCOUNT] where each is the count of edges aligned with that
/// axis that are in the edge set and touch the given corner.
/// Arguments:
/// edges = Standard edges array.
/// v = Vector pointing to the corner to count edge intersections at.
/// See Also: CORNERS_NONE, CORNERS_ALL, corners()
function _corner_edges(edges, v) =
let(u = (v+[1,1,1])/2) [edges[0][u.y+u.z*2], edges[1][u.x+u.z*2], edges[2][u.x+u.y*2]];
// Function: corner_edge_count()
// Topics: Corners
// Description:
// Counts how many given edges intersect at a specific corner.
// Arguments:
// edges = Standard edges array.
// v = Vector pointing to the corner to count edge intersections at.
// See Also: CORNERS_NONE, CORNERS_ALL, is_corner_array(), corners(), corner_edges()
function corner_edge_count(edges, v) =
/// InternalFunction: _corner_edge_count()
/// Topics: Corners
/// Description:
/// Counts how many given edges intersect at a specific corner.
/// Arguments:
/// edges = Standard edges array.
/// v = Vector pointing to the corner to count edge intersections at.
/// See Also: CORNERS_NONE, CORNERS_ALL, corners()
function _corner_edge_count(edges, v) =
let(u = (v+[1,1,1])/2) edges[0][u.y+u.z*2] + edges[1][u.x+u.z*2] + edges[2][u.x+u.y*2];
@ -535,7 +535,7 @@ function _corners_text(corners) =
is_string(corners) ? [str("\"",corners,"\"")] :
corners==CORNERS_NONE ? ["CORNERS_NONE"] :
corners==CORNERS_ALL ? ["CORNERS_ALL"] :
is_corner_array(corners) ? [""] :
_is_corner_array(corners) ? [""] :
is_vector(corners,3) ? _edges_vec_txt(corners) :
is_list(corners) ? let(
lst = [for (x=corners) each _corners_text(x)],
@ -563,7 +563,7 @@ function _corners_text(corners) =
// size = The scalar size of the cube.
// text = If given, overrides the text to be shown on the front of the cube.
// txtsize = The size of the text.
// See Also: CORNERS_NONE, CORNERS_ALL, is_corner_array(), corners()
// See Also: CORNERS_NONE, CORNERS_ALL, corners()
// Example:
// show_corners(corners=FWD+RIGHT, size=30);
module show_corners(corners="ALL", size=20, text, txtsize=3) {

View file

@ -579,12 +579,12 @@ function rack2d(
xd = d * sin(pressure_angle),
l = teeth * pitch,
anchors = [
anchorpt("adendum", [ 0, a,0], BACK),
anchorpt("adendum-left", [-l/2, a,0], LEFT),
anchorpt("adendum-right", [ l/2, a,0], RIGHT),
anchorpt("dedendum", [ 0,-d,0], BACK),
anchorpt("dedendum-left", [-l/2,-d,0], LEFT),
anchorpt("dedendum-right", [ l/2,-d,0], RIGHT),
named_anchor("adendum", [ 0, a,0], BACK),
named_anchor("adendum-left", [-l/2, a,0], LEFT),
named_anchor("adendum-right", [ l/2, a,0], RIGHT),
named_anchor("dedendum", [ 0,-d,0], BACK),
named_anchor("dedendum-left", [-l/2,-d,0], LEFT),
named_anchor("dedendum-right", [ l/2,-d,0], RIGHT),
],
path = [
[-(teeth-1)/2 * pitch + -1/2 * pitch, a-height],
@ -619,12 +619,12 @@ module rack2d(
d = dedendum(pitch, clearance);
l = teeth * pitch;
anchors = [
anchorpt("adendum", [ 0, a,0], BACK),
anchorpt("adendum-left", [-l/2, a,0], LEFT),
anchorpt("adendum-right", [ l/2, a,0], RIGHT),
anchorpt("dedendum", [ 0,-d,0], BACK),
anchorpt("dedendum-left", [-l/2,-d,0], LEFT),
anchorpt("dedendum-right", [ l/2,-d,0], RIGHT),
named_anchor("adendum", [ 0, a,0], BACK),
named_anchor("adendum-left", [-l/2, a,0], LEFT),
named_anchor("adendum-right", [ l/2, a,0], RIGHT),
named_anchor("dedendum", [ 0,-d,0], BACK),
named_anchor("dedendum-left", [-l/2,-d,0], LEFT),
named_anchor("dedendum-right", [ l/2,-d,0], RIGHT),
];
path = rack2d(
pitch = pitch,
@ -994,9 +994,9 @@ function bevel_gear(
lvnf = left_handed? vnf1 : xflip(p=vnf1),
vnf = down(cpz, p=lvnf),
anchors = [
anchorpt("pitchbase", [0,0,pitchoff-thickness/2]),
anchorpt("flattop", [0,0,thickness/2]),
anchorpt("apex", [0,0,hyp_ang_to_opp(ocone_rad,90-pitch_angle)+pitchoff-thickness/2])
named_anchor("pitchbase", [0,0,pitchoff-thickness/2]),
named_anchor("flattop", [0,0,thickness/2]),
named_anchor("apex", [0,0,hyp_ang_to_opp(ocone_rad,90-pitch_angle)+pitchoff-thickness/2])
]
) reorient(anchor,spin,orient, vnf=vnf, extent=true, anchors=anchors, p=vnf);
@ -1048,9 +1048,9 @@ module bevel_gear(
axis_zs = [for (p=vnf[0]) if(norm(point2d(p)) < EPSILON) p.z];
thickness = max(axis_zs) - min(axis_zs);
anchors = [
anchorpt("pitchbase", [0,0,pitchoff-thickness/2]),
anchorpt("flattop", [0,0,thickness/2]),
anchorpt("apex", [0,0,adj_ang_to_opp(pr,90-pitch_angle)+pitchoff-thickness/2])
named_anchor("pitchbase", [0,0,pitchoff-thickness/2]),
named_anchor("flattop", [0,0,thickness/2]),
named_anchor("apex", [0,0,adj_ang_to_opp(pr,90-pitch_angle)+pitchoff-thickness/2])
];
attachable(anchor,spin,orient, r1=pr, r2=ipr, h=thickness, anchors=anchors) {
difference() {
@ -1137,16 +1137,16 @@ module rack(
d = dedendum(pitch, clearance);
l = teeth * pitch;
anchors = [
anchorpt("adendum", [0,0,a], BACK),
anchorpt("adendum-left", [-l/2,0,a], LEFT),
anchorpt("adendum-right", [ l/2,0,a], RIGHT),
anchorpt("adendum-front", [0,-thickness/2,a], DOWN),
anchorpt("adendum-back", [0, thickness/2,a], UP),
anchorpt("dedendum", [0,0,-d], BACK),
anchorpt("dedendum-left", [-l/2,0,-d], LEFT),
anchorpt("dedendum-right", [ l/2,0,-d], RIGHT),
anchorpt("dedendum-front", [0,-thickness/2,-d], DOWN),
anchorpt("dedendum-back", [0, thickness/2,-d], UP),
named_anchor("adendum", [0,0,a], BACK),
named_anchor("adendum-left", [-l/2,0,a], LEFT),
named_anchor("adendum-right", [ l/2,0,a], RIGHT),
named_anchor("adendum-front", [0,-thickness/2,a], DOWN),
named_anchor("adendum-back", [0, thickness/2,a], UP),
named_anchor("dedendum", [0,0,-d], BACK),
named_anchor("dedendum-left", [-l/2,0,-d], LEFT),
named_anchor("dedendum-right", [ l/2,0,-d], RIGHT),
named_anchor("dedendum-front", [0,-thickness/2,-d], DOWN),
named_anchor("dedendum-back", [0, thickness/2,-d], UP),
];
attachable(anchor,spin,orient, size=[l, thickness, 2*abs(a-height)], anchors=anchors) {
skew(sxy=tan(helical)) xrot(90) {
@ -1186,16 +1186,16 @@ function rack(
d = dedendum(pitch, clearance),
l = teeth * pitch,
anchors = [
anchorpt("adendum", [0,0,a], BACK),
anchorpt("adendum-left", [-l/2,0,a], LEFT),
anchorpt("adendum-right", [ l/2,0,a], RIGHT),
anchorpt("adendum-front", [0,-thickness/2,a], DOWN),
anchorpt("adendum-back", [0, thickness/2,a], UP),
anchorpt("dedendum", [0,0,-d], BACK),
anchorpt("dedendum-left", [-l/2,0,-d], LEFT),
anchorpt("dedendum-right", [ l/2,0,-d], RIGHT),
anchorpt("dedendum-front", [0,-thickness/2,-d], DOWN),
anchorpt("dedendum-back", [0, thickness/2,-d], UP),
named_anchor("adendum", [0,0,a], BACK),
named_anchor("adendum-left", [-l/2,0,a], LEFT),
named_anchor("adendum-right", [ l/2,0,a], RIGHT),
named_anchor("adendum-front", [0,-thickness/2,a], DOWN),
named_anchor("adendum-back", [0, thickness/2,a], UP),
named_anchor("dedendum", [0,0,-d], BACK),
named_anchor("dedendum-left", [-l/2,0,-d], LEFT),
named_anchor("dedendum-right", [ l/2,0,-d], RIGHT),
named_anchor("dedendum-front", [0,-thickness/2,-d], DOWN),
named_anchor("dedendum-back", [0, thickness/2,-d], UP),
],
path = rack2d(
pitch = pitch,

File diff suppressed because it is too large Load diff

View file

@ -186,7 +186,7 @@ function hull3d_faces(points) =
remaining = [for (i = [0:1:len(points)-1]) if (i!=a && i!=b && i!=c && i!=d) i],
// Build an initial tetrahedron.
// Swap b, c if d is in front of triangle t.
ifop = in_front_of_plane(plane, points[d]),
ifop = _is_point_above_plane(plane, points[d]),
bc = ifop? [c,b] : [b,c],
b = bc[0],
c = bc[1],
@ -245,7 +245,7 @@ function _remove_internal_edges(halfedges) = [
];
function _find_first_noncoplanar(plane, points, i=0) =
(i >= len(points) || !points_on_plane([points[i]],plane))? i :
(i >= len(points) || !are_points_on_plane([points[i]],plane))? i :
_find_first_noncoplanar(plane, points, i+1);

View file

@ -86,9 +86,9 @@ module linear_bearing_housing(d=15, l=24, tab=7, gap=5, wall=3, tabwall=5, screw
tabh = tab/2+od/2*sqrt(2)-ogap/2;
h = od+tab/2;
anchors = [
anchorpt("axis", [0,0,-tab/2/2]),
anchorpt("screw", [0,2-ogap/2,tabh-tab/2/2],FWD),
anchorpt("nut", [0,ogap/2-2,tabh-tab/2/2],FWD)
named_anchor("axis", [0,0,-tab/2/2]),
named_anchor("screw", [0,2-ogap/2,tabh-tab/2/2],FWD),
named_anchor("nut", [0,ogap/2-2,tabh-tab/2/2],FWD)
];
attachable(anchor,spin,orient, size=[l, od, h], anchors=anchors) {
down(tab/2/2)

View file

@ -35,7 +35,7 @@ NAN = acos(2);
// If given a number, returns the square of that number,
// If given a vector, returns the sum-of-squares/dot product of the vector elements.
// If given a matrix, returns the matrix multiplication of the matrix with itself.
// Examples:
// Example:
// sqr(3); // Returns: 9
// sqr(-4); // Returns: 16
// sqr([2,3,4]); // Returns: 29
@ -50,7 +50,7 @@ function sqr(x) =
// foo = log2(x);
// Description:
// Returns the logarithm base 2 of the value given.
// Examples:
// Example:
// log2(0.125); // Returns: -3
// log2(16); // Returns: 4
// log2(256); // Returns: 8
@ -187,7 +187,7 @@ function lerp(a,b,u) =
// b = Second value or vector.
// n = The number of values to return.
// endpoint = If true, the last value will be exactly `b`. If false, the last value will be one step less.
// Examples:
// Example:
// l = lerpn(-4,4,9); // Returns: [-4,-3,-2,-1,0,1,2,3,4]
// l = lerpn(-4,4,8,false); // Returns: [-4,-3,-2,-1,0,1,2,3]
// l = lerpn(0,1,6); // Returns: [0, 0.2, 0.4, 0.6, 0.8, 1]
@ -308,7 +308,8 @@ function atanh(x) =
// num = quant(x, y);
// Description:
// Quantize a value `x` to an integer multiple of `y`, rounding to the nearest multiple.
// If `x` is a list, then every item in that list will be recursively quantized.
// The value of `y` does NOT have to be an integer. If `x` is a list, then every item
// in that list will be recursively quantized.
// Arguments:
// x = The value to quantize.
// y = The non-zero integer quantum of the quantization.
@ -326,9 +327,11 @@ function atanh(x) =
// k = quant(10.5,3); // Returns: 12
// l = quant(11,3); // Returns: 12
// m = quant(12,3); // Returns: 12
// n = quant([12,13,13.1,14,14.1,15,16],4); // Returns: [12,12,12,16,16,16,16]
// o = quant([9,10,10.4,10.5,11,12],3); // Returns: [9,9,9,12,12,12]
// p = quant([[9,10,10.4],[10.5,11,12]],3); // Returns: [[9,9,9],[12,12,12]]
// n = quant(11,2.5); // Returns: 10
// o = quant(12,2.5); // Returns: 12.5
// p = quant([12,13,13.1,14,14.1,15,16],4); // Returns: [12,12,12,16,16,16,16]
// q = quant([9,10,10.4,10.5,11,12],3); // Returns: [9,9,9,12,12,12]
// r = quant([[9,10,10.4],[10.5,11,12]],3); // Returns: [[9,9,9],[12,12,12]]
function quant(x,y) =
assert( is_finite(y) && y>0, "The quantum `y` must be a non zero integer.")
is_list(x)
@ -342,11 +345,12 @@ function quant(x,y) =
// num = quantdn(x, y);
// Description:
// Quantize a value `x` to an integer multiple of `y`, rounding down to the previous multiple.
// If `x` is a list, then every item in that list will be recursively quantized down.
// The value of `y` does NOT have to be an integer. If `x` is a list, then every item in that
// list will be recursively quantized down.
// Arguments:
// x = The value to quantize.
// y = The non-zero integer quantum of the quantization.
// Examples:
// Example:
// a = quantdn(12,4); // Returns: 12
// b = quantdn(13,4); // Returns: 12
// c = quantdn(13.1,4); // Returns: 12
@ -360,9 +364,11 @@ function quant(x,y) =
// k = quantdn(10.5,3); // Returns: 9
// l = quantdn(11,3); // Returns: 9
// m = quantdn(12,3); // Returns: 12
// n = quantdn([12,13,13.1,14,14.1,15,16],4); // Returns: [12,12,12,12,12,12,16]
// o = quantdn([9,10,10.4,10.5,11,12],3); // Returns: [9,9,9,9,9,12]
// p = quantdn([[9,10,10.4],[10.5,11,12]],3); // Returns: [[9,9,9],[9,9,12]]
// n = quantdn(11,2.5); // Returns: 10
// o = quantdn(12,2.5); // Returns: 10
// p = quantdn([12,13,13.1,14,14.1,15,16],4); // Returns: [12,12,12,12,12,12,16]
// q = quantdn([9,10,10.4,10.5,11,12],3); // Returns: [9,9,9,9,9,12]
// r = quantdn([[9,10,10.4],[10.5,11,12]],3); // Returns: [[9,9,9],[9,9,12]]
function quantdn(x,y) =
assert( is_finite(y) && y>0, "The quantum `y` must be a non zero integer.")
is_list(x)
@ -376,11 +382,12 @@ function quantdn(x,y) =
// num = quantup(x, y);
// Description:
// Quantize a value `x` to an integer multiple of `y`, rounding up to the next multiple.
// If `x` is a list, then every item in that list will be recursively quantized up.
// The value of `y` does NOT have to be an integer. If `x` is a list, then every item in
// that list will be recursively quantized up.
// Arguments:
// x = The value to quantize.
// y = The non-zero integer quantum of the quantization.
// Examples:
// Example:
// a = quantup(12,4); // Returns: 12
// b = quantup(13,4); // Returns: 16
// c = quantup(13.1,4); // Returns: 16
@ -394,9 +401,11 @@ function quantdn(x,y) =
// k = quantup(10.5,3); // Returns: 12
// l = quantup(11,3); // Returns: 12
// m = quantup(12,3); // Returns: 12
// o = quantup([12,13,13.1,14,14.1,15,16],4); // Returns: [12,16,16,16,16,16,16]
// p = quantup([9,10,10.4,10.5,11,12],3); // Returns: [9,12,12,12,12,12]
// quantup([[9,10,10.4],[10.5,11,12]],3); // Returns: [[9,12,12],[12,12,12]]
// n = quantdn(11,2.5); // Returns: 12.5
// o = quantdn(12,2.5); // Returns: 12.5
// p = quantup([12,13,13.1,14,14.1,15,16],4); // Returns: [12,16,16,16,16,16,16]
// q = quantup([9,10,10.4,10.5,11,12],3); // Returns: [9,12,12,12,12,12]
// r = quantup([[9,10,10.4],[10.5,11,12]],3); // Returns: [[9,12,12],[12,12,12]]
function quantup(x,y) =
assert( is_finite(y) && y>0, "The quantum `y` must be a non zero integer.")
is_list(x)
@ -630,7 +639,7 @@ function _cumsum(v,_i=0,_acc=[]) =
// Arguments:
// a = Angle to get the value for.
// sines = List of [amplitude, frequency, offset] items, where the frequency is the number of times the cycle repeats around the circle.
// Examples:
// Example:
// v = sum_of_sines(30, [[10,3,0], [5,5.5,60]]);
function sum_of_sines(a, sines) =
assert( is_finite(a) && is_matrix(sines,undef,3), "Invalid input.")
@ -1216,7 +1225,7 @@ function all_equal(vec,eps=0) =
// true if every item of the list is an integer. Otherwise, returns false.
// Arguments:
// x = The value to check.
// Examples:
// Example:
// b = all_integer(true); // Returns: false
// b = all_integer("foo"); // Returns: false
// b = all_integer(4); // Returns: true

View file

@ -395,8 +395,8 @@ module generic_screw(
) {
sides = max(12, segs(screwsize/2));
anchors = [
anchorpt("countersunk", [0,0,(headlen+screwlen)/2-0.01]),
anchorpt("base", [0,0,-headlen/2+screwlen/2])
named_anchor("countersunk", [0,0,(headlen+screwlen)/2-0.01]),
named_anchor("base", [0,0,-headlen/2+screwlen/2])
];
attachable(anchor,spin,orient, d=screwsize, l=headlen+screwlen, anchors=anchors) {
down(headlen/2-screwlen/2) {
@ -517,9 +517,9 @@ module metric_bolt(
);
anchors = [
anchorpt("countersunk", [0,0,base+sunklen]),
anchorpt("base", [0,0,base]),
anchorpt("shank", [0,0,base-shank])
named_anchor("countersunk", [0,0,base+sunklen]),
named_anchor("base", [0,0,base]),
named_anchor("shank", [0,0,base-shank])
];
//color("silver")

View file

@ -193,15 +193,15 @@ module modular_hose(size, type, clearance=0, waist_len, anchor=BOTTOM, spin=0,or
// Arguments:
// size = size of hose part, must be 1/4, 1/2 or 3/4
// outer = set to true to get the outer diameter.
// Example:
// Example(3D):
// $fn=64;
// back_half()
// diff("remove")
// cuboid(50){
// attach(TOP) modular_hose(1/2, "ball");
// position(TOP+RIGHT) y
// rot(181)
// xrot(90)
// up(0.01)position(TOP+RIGHT)tags("remove")
// rot(180)
// xrot(-90)
// rotate_extrude(angle=135)
// right(25)
// circle(r=modular_hose_radius(1/2));
@ -213,8 +213,7 @@ function modular_hose_radius(size, outer=false) =
assert(ind!=[], "Must specify size as 1/4, 1/2 or 3/4")
let(
b = select(_big_end[ind], [0,-1]),
s = select(_small_end[ind], [0,-1]),
dd=echo(b=b)echo(s=s)
s = select(_small_end[ind], [0,-1])
)
outer ? b[1][0] : b[0][0];

View file

@ -105,7 +105,7 @@ module bounding_box(excess=0, planar=false) {
// a normal vector. The s parameter is needed for the module
// version to control the size of the masking cube, which affects preview display.
// When called as a function, you must supply a vnf, path or region in p. If planar is set to true for the module version the operation
// is performed in and UP and DOWN are treated as equivalent to BACK and FWD respectively.
// is performed in and UP and DOWN are treated as equivalent to BACK and FWD respectively.
//
// Arguments:
// p = path, region or VNF to slice. (Function version)
@ -163,7 +163,7 @@ function half_of(p, v=UP, cp) =
let(
v = (v==UP)? BACK : (v==DOWN)? FWD : v,
cp = is_undef(cp) ? [0,0]
: is_num(cp) ? v*cp
: is_num(cp) ? v*cp
: assert(is_vector(cp,2) || (is_vector(cp,3) && cp.z==0),"Centerpoint must be 2-vector")
cp
)
@ -179,7 +179,7 @@ function half_of(p, v=UP, cp) =
intersection(box,p)
: assert(false, "Input must be a region, path or VNF");
/* This code cut 3d paths but leaves behind connecting line segments
is_path(p) ?
@ -598,6 +598,188 @@ module cylindrical_extrude(or, ir, od, id, size=1000, convexity=10, spin=0, orie
}
// Module: extrude_from_to()
// Description:
// Extrudes a 2D shape between the 3d points pt1 and pt2. Takes as children a set of 2D shapes to extrude.
// Arguments:
// pt1 = starting point of extrusion.
// pt2 = ending point of extrusion.
// convexity = max number of times a line could intersect a wall of the 2D shape being extruded.
// twist = number of degrees to twist the 2D shape over the entire extrusion length.
// scale = scale multiplier for end of extrusion compared the start.
// slices = Number of slices along the extrusion to break the extrusion into. Useful for refining `twist` extrusions.
// Example(FlatSpin,VPD=200,VPT=[0,0,15]):
// extrude_from_to([0,0,0], [10,20,30], convexity=4, twist=360, scale=3.0, slices=40) {
// xcopies(3) circle(3, $fn=32);
// }
module extrude_from_to(pt1, pt2, convexity, twist, scale, slices) {
assert(is_vector(pt1));
assert(is_vector(pt2));
pt1 = point3d(pt1);
pt2 = point3d(pt2);
rtp = xyz_to_spherical(pt2-pt1);
translate(pt1) {
rotate([0, rtp[2], rtp[1]]) {
if (rtp[0] > 0) {
linear_extrude(height=rtp[0], convexity=convexity, center=false, slices=slices, twist=twist, scale=scale) {
children();
}
}
}
}
}
// Module: spiral_sweep()
// Description:
// Takes a closed 2D polygon path, centered on the XY plane, and sweeps/extrudes it along a 3D spiral path
// of a given radius, height and twist. The origin in the profile traces out the helix of the specified radius.
// If twist is positive the path will be right-handed; if twist is negative the path will be left-handed.
// .
// Higbee specifies tapering applied to the ends of the extrusion and is given as the linear distance
// over which to taper.
// Arguments:
// poly = Array of points of a polygon path, to be extruded.
// h = height of the spiral to extrude along.
// r = Radius of the spiral to extrude along. Default: 50
// twist = number of degrees of rotation to spiral up along height.
// ---
// d = Diameter of the spiral to extrude along.
// higbee = Length to taper thread ends over.
// higbee1 = Taper length at start
// higbee2 = Taper length at end
// internal = direction to taper the threads with higbee. If true threads taper outward; if false they taper inward. Default: false
// 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`
// center = If given, overrides `anchor`. A true value sets `anchor=CENTER`, false sets `anchor=BOTTOM`.
// Example:
// poly = [[-10,0], [-3,-5], [3,-5], [10,0], [0,-30]];
// spiral_sweep(poly, h=200, r=50, twist=1080, $fn=36);
module spiral_sweep(poly, h, r, twist=360, higbee, center, r1, r2, d, d1, d2, higbee1, higbee2, internal=false, anchor, spin=0, orient=UP) {
higsample = 10; // Oversample factor for higbee tapering
dummy1=assert(is_num(twist) && twist != 0);
bounds = pointlist_bounds(poly);
yctr = (bounds[0].y+bounds[1].y)/2;
xmin = bounds[0].x;
xmax = bounds[1].x;
poly = path3d(clockwise_polygon(poly));
anchor = get_anchor(anchor,center,BOT,BOT);
r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=50);
r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=50);
sides = segs(max(r1,r2));
dir = sign(twist);
ang_step = 360/sides*dir;
anglist = [for(ang = [0:ang_step:twist-EPSILON]) ang,
twist];
higbee1 = first_defined([higbee1, higbee, 0]);
higbee2 = first_defined([higbee2, higbee, 0]);
higang1 = 360 * higbee1 / (2 * r1 * PI);
higang2 = 360 * higbee2 / (2 * r2 * PI);
dummy2=assert(higbee1>=0 && higbee2>=0)
assert(higang1 < dir*twist/2,"Higbee1 is more than half the threads")
assert(higang2 < dir*twist/2,"Higbee2 is more than half the threads");
function polygon_r(N,theta) =
let( alpha = 360/N )
cos(alpha/2)/(cos(posmod(theta,alpha)-alpha/2));
higofs = pow(0.05,2); // Smallest hig scale is the square root of this value
function taperfunc(x) = sqrt((1-higofs)*x+higofs);
interp_ang = [
for(i=idx(anglist,e=-2))
each lerpn(anglist[i],anglist[i+1],
(higang1>0 && higang1>dir*anglist[i+1]
|| (higang2>0 && higang2>dir*(twist-anglist[i]))) ? ceil((anglist[i+1]-anglist[i])/ang_step*higsample)
: 1,
endpoint=false),
last(anglist)
];
skewmat = affine3d_skew_xz(xa=atan2(r2-r1,h));
points = [
for (a = interp_ang) let (
hsc = dir*a<higang1 ? taperfunc(dir*a/higang1)
: dir*(twist-a)<higang2 ? taperfunc(dir*(twist-a)/higang2)
: 1,
u = a/twist,
r = lerp(r1,r2,u),
mat = affine3d_zrot(a)
* affine3d_translate([polygon_r(sides,a)*r, 0, h * (u-0.5)])
* affine3d_xrot(90)
* skewmat
* scale([hsc,lerp(hsc,1,0.25),1], cp=[internal ? xmax : xmin, yctr, 0]),
pts = apply(mat, poly)
) pts
];
vnf = vnf_vertex_array(
points, col_wrap=true, caps=true, reverse=dir>0?true:false,
style=higbee1>0 || higbee2>0 ? "quincunx" : "alt"
);
attachable(anchor,spin,orient, r1=r1, r2=r2, l=h) {
vnf_polyhedron(vnf, convexity=ceil(2*dir*twist/360));
children();
}
}
// Module: path_extrude()
// Description:
// Extrudes 2D children along a 3D path. This may be slow.
// Arguments:
// path = array of points for the bezier path to extrude along.
// convexity = maximum number of walls a ran can pass through.
// clipsize = increase if artifacts are left. Default: 1000
// Example(FlatSpin,VPD=600,VPT=[75,16,20]):
// path = [ [0, 0, 0], [33, 33, 33], [66, 33, 40], [100, 0, 0], [150,0,0] ];
// path_extrude(path) circle(r=10, $fn=6);
module path_extrude(path, convexity=10, clipsize=100) {
function polyquats(path, q=q_ident(), v=[0,0,1], i=0) = let(
v2 = path[i+1] - path[i],
ang = vector_angle(v,v2),
axis = ang>0.001? unit(cross(v,v2)) : [0,0,1],
newq = q_mul(quat(axis, ang), q),
dist = norm(v2)
) i < (len(path)-2)?
concat([[dist, newq, ang]], polyquats(path, newq, v2, i+1)) :
[[dist, newq, ang]];
epsilon = 0.0001; // Make segments ever so slightly too long so they overlap.
ptcount = len(path);
pquats = polyquats(path);
for (i = [0:1:ptcount-2]) {
pt1 = path[i];
pt2 = path[i+1];
dist = pquats[i][0];
q = pquats[i][1];
difference() {
translate(pt1) {
q_rot(q) {
down(clipsize/2/2) {
if ((dist+clipsize/2) > 0) {
linear_extrude(height=dist+clipsize/2, convexity=convexity) {
children();
}
}
}
}
}
translate(pt1) {
hq = (i > 0)? q_slerp(q, pquats[i-1][1], 0.5) : q;
q_rot(hq) down(clipsize/2+epsilon) cube(clipsize, center=true);
}
translate(pt2) {
hq = (i < ptcount-2)? q_slerp(q, pquats[i+1][1], 0.5) : q;
q_rot(hq) up(clipsize/2+epsilon) cube(clipsize, center=true);
}
}
}
}
//////////////////////////////////////////////////////////////////////
// Section: Offset Mutators
@ -824,13 +1006,23 @@ module HSL(h,s=1,l=0.5,a=1) color(HSL(h,s,l),a) children();
// rgb = HSV(h=270,s=0.75,v=0.9);
// color(rgb) cube(60, center=true);
function HSV(h,s=1,v=1) =
assert(s>=0 && s<=1)
assert(v>=0 && v<=1)
let(
h=posmod(h,360),
v2=v*(1-s),
r=lookup(h,[[0,v], [60,v], [120,v2], [240,v2], [300,v], [360,v]]),
g=lookup(h,[[0,v2], [60,v], [180,v], [240,v2], [360,v2]]),
b=lookup(h,[[0,v2], [120,v2], [180,v], [300,v], [360,v2]])
) [r,g,b];
h = posmod(h,360),
c = v * s,
hprime = h/60,
x = c * (1- abs(hprime % 2 - 1)),
rgbprime = hprime <=1 ? [c,x,0]
: hprime <=2 ? [x,c,0]
: hprime <=3 ? [0,c,x]
: hprime <=4 ? [0,x,c]
: hprime <=5 ? [x,0,c]
: hprime <=6 ? [c,0,x]
: [0,0,0],
m=v-c
)
rgbprime+[m,m,m];
module HSV(h,s=1,v=1,a=1) color(HSV(h,s,v),a) children();

View file

@ -122,14 +122,14 @@ module nema11_stepper(h=24, shaft=5, shaft_len=20, anchor=TOP, spin=0, orient=UP
screw_depth = nema_motor_screw_depth(size);
anchors = [
anchorpt("shaft-top", [0,0,h/2+shaft_len]),
anchorpt("shaft-middle", [0,0,h/2+plinth_height+(shaft_len-plinth_height)/2]),
anchorpt("shaft-bottom", [0,0,h/2+plinth_height+0.1]),
anchorpt("plinth-top", [0,0,h/2+plinth_height]),
anchorpt("screw1", [+screw_spacing/2, +screw_spacing/2, h/2]),
anchorpt("screw2", [-screw_spacing/2, +screw_spacing/2, h/2]),
anchorpt("screw3", [-screw_spacing/2, -screw_spacing/2, h/2]),
anchorpt("screw4", [+screw_spacing/2, -screw_spacing/2, h/2]),
named_anchor("shaft-top", [0,0,h/2+shaft_len]),
named_anchor("shaft-middle", [0,0,h/2+plinth_height+(shaft_len-plinth_height)/2]),
named_anchor("shaft-bottom", [0,0,h/2+plinth_height+0.1]),
named_anchor("plinth-top", [0,0,h/2+plinth_height]),
named_anchor("screw1", [+screw_spacing/2, +screw_spacing/2, h/2]),
named_anchor("screw2", [-screw_spacing/2, +screw_spacing/2, h/2]),
named_anchor("screw3", [-screw_spacing/2, -screw_spacing/2, h/2]),
named_anchor("screw4", [+screw_spacing/2, -screw_spacing/2, h/2]),
];
attachable(anchor,spin,orient, size=[motor_width, motor_width, h], anchors=anchors) {
up(h/2)
@ -187,14 +187,14 @@ module nema14_stepper(h=24, shaft=5, shaft_len=24, anchor=TOP, spin=0, orient=UP
screw_depth = nema_motor_screw_depth(size);
anchors = [
anchorpt("shaft-top", [0,0,h/2+shaft_len]),
anchorpt("shaft-middle", [0,0,h/2+plinth_height+(shaft_len-plinth_height)/2]),
anchorpt("shaft-bottom", [0,0,h/2+plinth_height+0.1]),
anchorpt("plinth-top", [0,0,h/2+plinth_height]),
anchorpt("screw1", [+screw_spacing/2, +screw_spacing/2, h/2]),
anchorpt("screw2", [-screw_spacing/2, +screw_spacing/2, h/2]),
anchorpt("screw3", [-screw_spacing/2, -screw_spacing/2, h/2]),
anchorpt("screw4", [+screw_spacing/2, -screw_spacing/2, h/2]),
named_anchor("shaft-top", [0,0,h/2+shaft_len]),
named_anchor("shaft-middle", [0,0,h/2+plinth_height+(shaft_len-plinth_height)/2]),
named_anchor("shaft-bottom", [0,0,h/2+plinth_height+0.1]),
named_anchor("plinth-top", [0,0,h/2+plinth_height]),
named_anchor("screw1", [+screw_spacing/2, +screw_spacing/2, h/2]),
named_anchor("screw2", [-screw_spacing/2, +screw_spacing/2, h/2]),
named_anchor("screw3", [-screw_spacing/2, -screw_spacing/2, h/2]),
named_anchor("screw4", [+screw_spacing/2, -screw_spacing/2, h/2]),
];
attachable(anchor,spin,orient, size=[motor_width, motor_width, h], anchors=anchors) {
up(h/2)
@ -252,14 +252,14 @@ module nema17_stepper(h=34, shaft=5, shaft_len=20, anchor=TOP, spin=0, orient=UP
screw_depth = nema_motor_screw_depth(size);
anchors = [
anchorpt("shaft-top", [0,0,h/2+shaft_len]),
anchorpt("shaft-middle", [0,0,h/2+plinth_height+(shaft_len-plinth_height)/2]),
anchorpt("shaft-bottom", [0,0,h/2+plinth_height+0.1]),
anchorpt("plinth-top", [0,0,h/2+plinth_height]),
anchorpt("screw1", [+screw_spacing/2, +screw_spacing/2, h/2]),
anchorpt("screw2", [-screw_spacing/2, +screw_spacing/2, h/2]),
anchorpt("screw3", [-screw_spacing/2, -screw_spacing/2, h/2]),
anchorpt("screw4", [+screw_spacing/2, -screw_spacing/2, h/2]),
named_anchor("shaft-top", [0,0,h/2+shaft_len]),
named_anchor("shaft-middle", [0,0,h/2+plinth_height+(shaft_len-plinth_height)/2]),
named_anchor("shaft-bottom", [0,0,h/2+plinth_height+0.1]),
named_anchor("plinth-top", [0,0,h/2+plinth_height]),
named_anchor("screw1", [+screw_spacing/2, +screw_spacing/2, h/2]),
named_anchor("screw2", [-screw_spacing/2, +screw_spacing/2, h/2]),
named_anchor("screw3", [-screw_spacing/2, -screw_spacing/2, h/2]),
named_anchor("screw4", [+screw_spacing/2, -screw_spacing/2, h/2]),
];
attachable(anchor,spin,orient, size=[motor_width, motor_width, h], anchors=anchors) {
up(h/2)
@ -337,14 +337,14 @@ module nema23_stepper(h=50, shaft=6.35, shaft_len=25, anchor=TOP, spin=0, orient
screw_inset = motor_width - screw_spacing + 1;
anchors = [
anchorpt("shaft-top", [0,0,h/2+shaft_len]),
anchorpt("shaft-middle", [0,0,h/2+plinth_height+(shaft_len-plinth_height)/2]),
anchorpt("shaft-bottom", [0,0,h/2+plinth_height+0.1]),
anchorpt("plinth-top", [0,0,h/2+plinth_height]),
anchorpt("screw1", [+screw_spacing/2, +screw_spacing/2, h/2]),
anchorpt("screw2", [-screw_spacing/2, +screw_spacing/2, h/2]),
anchorpt("screw3", [-screw_spacing/2, -screw_spacing/2, h/2]),
anchorpt("screw4", [+screw_spacing/2, -screw_spacing/2, h/2]),
named_anchor("shaft-top", [0,0,h/2+shaft_len]),
named_anchor("shaft-middle", [0,0,h/2+plinth_height+(shaft_len-plinth_height)/2]),
named_anchor("shaft-bottom", [0,0,h/2+plinth_height+0.1]),
named_anchor("plinth-top", [0,0,h/2+plinth_height]),
named_anchor("screw1", [+screw_spacing/2, +screw_spacing/2, h/2]),
named_anchor("screw2", [-screw_spacing/2, +screw_spacing/2, h/2]),
named_anchor("screw3", [-screw_spacing/2, -screw_spacing/2, h/2]),
named_anchor("screw4", [+screw_spacing/2, -screw_spacing/2, h/2]),
];
attachable(anchor,spin,orient, size=[motor_width, motor_width, h], anchors=anchors) {
up(h/2)
@ -404,14 +404,14 @@ module nema34_stepper(h=75, shaft=12.7, shaft_len=32, anchor=TOP, spin=0, orient
screw_inset = motor_width - screw_spacing + 1;
anchors = [
anchorpt("shaft-top", [0,0,h/2+shaft_len]),
anchorpt("shaft-middle", [0,0,h/2+plinth_height+(shaft_len-plinth_height)/2]),
anchorpt("shaft-bottom", [0,0,h/2+plinth_height+0.1]),
anchorpt("plinth-top", [0,0,h/2+plinth_height]),
anchorpt("screw1", [+screw_spacing/2, +screw_spacing/2, h/2]),
anchorpt("screw2", [-screw_spacing/2, +screw_spacing/2, h/2]),
anchorpt("screw3", [-screw_spacing/2, -screw_spacing/2, h/2]),
anchorpt("screw4", [+screw_spacing/2, -screw_spacing/2, h/2]),
named_anchor("shaft-top", [0,0,h/2+shaft_len]),
named_anchor("shaft-middle", [0,0,h/2+plinth_height+(shaft_len-plinth_height)/2]),
named_anchor("shaft-bottom", [0,0,h/2+plinth_height+0.1]),
named_anchor("plinth-top", [0,0,h/2+plinth_height]),
named_anchor("screw1", [+screw_spacing/2, +screw_spacing/2, h/2]),
named_anchor("screw2", [-screw_spacing/2, +screw_spacing/2, h/2]),
named_anchor("screw3", [-screw_spacing/2, -screw_spacing/2, h/2]),
named_anchor("screw4", [+screw_spacing/2, -screw_spacing/2, h/2]),
];
attachable(anchor,spin,orient, size=[motor_width, motor_width, h], anchors=anchors) {
up(h/2)
@ -472,10 +472,10 @@ module nema_mount_holes(size=17, depth=5, l=5, anchor=CENTER, spin=0, orient=UP)
screw_size = nema_motor_screw_size(size)+$slop;
anchors = [
anchorpt("screw1", [+screw_spacing/2, +screw_spacing/2, depth/2]),
anchorpt("screw2", [-screw_spacing/2, +screw_spacing/2, depth/2]),
anchorpt("screw3", [-screw_spacing/2, -screw_spacing/2, depth/2]),
anchorpt("screw4", [+screw_spacing/2, -screw_spacing/2, depth/2]),
named_anchor("screw1", [+screw_spacing/2, +screw_spacing/2, depth/2]),
named_anchor("screw2", [-screw_spacing/2, +screw_spacing/2, depth/2]),
named_anchor("screw3", [-screw_spacing/2, -screw_spacing/2, depth/2]),
named_anchor("screw4", [+screw_spacing/2, -screw_spacing/2, depth/2]),
];
screwfn = quantup(max(8,segs(screw_size/2)),4);
plinthfn = quantup(max(8,segs(plinth_diam/2)),4);

View file

@ -106,6 +106,8 @@ module partition_mask(l=100, w=100, h=100, cutsize=10, cutpath="jigsaw", gap=0,
// partition_cut_mask(l, w, h, [cutsize], [cutpath], [gap], [inverse], [spin], [orient]);
// Description:
// Creates a mask that you can use to difference with an object to cut it into two sub-parts that can be assembled.
// The `$slop` value is important to get the proper fit and should probably be smaller than 0.2. The examples below
// use larger values to make the mask easier to see.
// Arguments:
// l = The length of the cut axis.
// w = The width of the part to be masked, back from the cut plane.
@ -115,20 +117,20 @@ module partition_mask(l=100, w=100, h=100, cutsize=10, cutpath="jigsaw", gap=0,
// gap = Empty gaps between cutpath iterations. Default: 0
// spin = Rotate this many degrees around the Z axis. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards. See [orient](attachments.scad#orient). Default: `UP`
// $slop = The width of the cut mask, to correct for printer-specific fitting. Min: 0.1.
// $slop = The width of the cut mask, to correct for printer-specific fitting. Min: 0.05.
// Examples:
// partition_cut_mask(gap=0, cutpath="dovetail");
// partition_cut_mask(gap=30, cutpath="dovetail");
// partition_cut_mask(gap=30, cutsize=15, cutpath="dovetail");
// partition_cut_mask(gap=30, cutsize=[20,20], cutpath="dovetail");
// Examples(2DMed):
// partition_cut_mask(cutpath="sawtooth");
// partition_cut_mask(cutpath="sinewave");
// partition_cut_mask(cutpath="comb");
// partition_cut_mask(cutpath="finger");
// partition_cut_mask(cutpath="dovetail");
// partition_cut_mask(cutpath="hammerhead");
// partition_cut_mask(cutpath="jigsaw");
// partition_cut_mask(cutpath="sawtooth",$slop=0.5);
// partition_cut_mask(cutpath="sinewave",$slop=0.5);
// partition_cut_mask(cutpath="comb",$slop=0.5);
// partition_cut_mask(cutpath="finger",$slop=0.5);
// partition_cut_mask(cutpath="dovetail",$slop=1);
// partition_cut_mask(cutpath="hammerhead",$slop=1);
// partition_cut_mask(cutpath="jigsaw",$slop=0.5);
module partition_cut_mask(l=100, h=100, cutsize=10, cutpath="jigsaw", gap=0, anchor=CENTER, spin=0, orient=UP)
{
cutsize = is_vector(cutsize)? cutsize : [cutsize*2, cutsize];

File diff suppressed because it is too large Load diff

View file

@ -1,307 +0,0 @@
//////////////////////////////////////////////////////////////////////
// LibFile: primitives.scad
// The basic built-in shapes, reworked to integrate better with
// other BOSL2 library shapes and utilities.
// Includes:
// include <BOSL2/std.scad>
//////////////////////////////////////////////////////////////////////
// Section: 2D Primitives
// Function&Module: square()
// Topics: Shapes (2D), Path Generators (2D)
// Usage: As a Built-in Module
// square(size, [center]);
// Usage: As a Function
// path = square(size, [center]);
// See Also: rect()
// Description:
// When called as the builtin module, creates a 2D square or rectangle of the given size.
// When called as a function, returns a 2D path/list of points for a square/rectangle of the given size.
// Arguments:
// size = The size of the square to create. If given as a scalar, both X and Y will be the same size.
// center = If given and true, overrides `anchor` to be `CENTER`. If given and false, overrides `anchor` to be `FRONT+LEFT`.
// ---
// anchor = (Function only) Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = (Function only) Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// Example(2D):
// square(40);
// Example(2D): Centered
// square([40,30], center=true);
// Example(2D): Called as Function
// path = square([40,30], anchor=FRONT, spin=30);
// stroke(path, closed=true);
// move_copies(path) color("blue") circle(d=2,$fn=8);
function square(size=1, center, anchor, spin=0) =
let(
anchor = get_anchor(anchor, center, [-1,-1], [-1,-1]),
size = is_num(size)? [size,size] : point2d(size),
path = [
[ size.x,-size.y],
[-size.x,-size.y],
[-size.x, size.y],
[ size.x, size.y]
] / 2
) reorient(anchor,spin, two_d=true, size=size, p=path);
// Function&Module: circle()
// Topics: Shapes (2D), Path Generators (2D)
// Usage: As a Built-in Module
// circle(r|d=, ...);
// Usage: As a Function
// path = circle(r|d=, ...);
// See Also: oval()
// Description:
// When called as the builtin module, creates a 2D polygon that approximates a circle of the given size.
// When called as a function, returns a 2D list of points (path) for a polygon that approximates a circle of the given size.
// Arguments:
// r = The radius of the circle to create.
// d = The diameter of the circle to create.
// ---
// anchor = (Function only) Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = (Function only) Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// Example(2D): By Radius
// circle(r=25);
// Example(2D): By Diameter
// circle(d=50);
// Example(NORENDER): Called as Function
// path = circle(d=50, anchor=FRONT, spin=45);
function circle(r, d, anchor=CENTER, spin=0) =
let(
r = get_radius(r=r, d=d, dflt=1),
sides = segs(r),
path = [for (i=[0:1:sides-1]) let(a=360-i*360/sides) r*[cos(a),sin(a)]]
) reorient(anchor,spin, two_d=true, r=r, p=path);
// Section: Primitive 3D Shapes
// Function&Module: cube()
// Topics: Shapes (3D), Attachable, VNF Generators
// Usage: As Module
// cube(size, [center], ...);
// Usage: With Attachments
// cube(size, [center], ...) { attachments }
// Usage: As Function
// vnf = cube(size, [center], ...);
// See Also: cuboid(), prismoid()
// Description:
// Creates a 3D cubic object with support for anchoring and attachments.
// This can be used as a drop-in replacement for the built-in `cube()` module.
// When called as a function, returns a [VNF](vnf.scad) for a cube.
// Arguments:
// size = The size of the cube.
// center = If given, overrides `anchor`. A true value sets `anchor=CENTER`, false sets `anchor=ALLNEG`.
// ---
// 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`
// Example: Simple cube.
// cube(40);
// Example: Rectangular cube.
// cube([20,40,50]);
// Example: Anchoring.
// cube([20,40,50], anchor=BOTTOM+FRONT);
// Example: Spin.
// cube([20,40,50], anchor=BOTTOM+FRONT, spin=30);
// Example: Orientation.
// cube([20,40,50], anchor=BOTTOM+FRONT, spin=30, orient=FWD);
// Example: Standard Connectors.
// cube(40, center=true) show_anchors();
// Example: Called as Function
// vnf = cube([20,40,50]);
// vnf_polyhedron(vnf);
module cube(size=1, center, anchor, spin=0, orient=UP)
{
anchor = get_anchor(anchor, center, ALLNEG, ALLNEG);
size = scalar_vec3(size);
attachable(anchor,spin,orient, size=size) {
if (size.z > 0) {
linear_extrude(height=size.z, center=true, convexity=2) {
square([size.x,size.y], center=true);
}
}
children();
}
}
function cube(size=1, center, anchor, spin=0, orient=UP) =
let(
siz = scalar_vec3(size),
anchor = get_anchor(anchor, center, ALLNEG, ALLNEG),
unscaled = [
[-1,-1,-1],[1,-1,-1],[1,1,-1],[-1,1,-1],
[-1,-1, 1],[1,-1, 1],[1,1, 1],[-1,1, 1],
]/2,
verts = is_num(size)? unscaled * size :
is_vector(size,3)? [for (p=unscaled) v_mul(p,size)] :
assert(is_num(size) || is_vector(size,3)),
faces = [
[0,1,2], [0,2,3], //BOTTOM
[0,4,5], [0,5,1], //FRONT
[1,5,6], [1,6,2], //RIGHT
[2,6,7], [2,7,3], //BACK
[3,7,4], [3,4,0], //LEFT
[6,4,7], [6,5,4] //TOP
]
) [reorient(anchor,spin,orient, size=siz, p=verts), faces];
// Function&Module: cylinder()
// Topics: Shapes (3D), Attachable, VNF Generators
// Usage: As Module
// cylinder(h, r=/d=, [center=], ...);
// cylinder(h, r1/d1=, r2/d2=, [center=], ...);
// Usage: With Attachments
// cylinder(h, r=/d=, [center=]) {attachments}
// Usage: As Function
// vnf = cylinder(h, r=/d=, [center=], ...);
// vnf = cylinder(h, r1/d1=, r2/d2=, [center=], ...);
// See Also: cyl()
// Description:
// Creates a 3D cylinder or conic object with support for anchoring and attachments.
// This can be used as a drop-in replacement for the built-in `cylinder()` module.
// When called as a function, returns a [VNF](vnf.scad) for a cylinder.
// Arguments:
// l / h = The height of the cylinder.
// r1 = The bottom radius of the cylinder. (Before orientation.)
// r2 = The top radius of the cylinder. (Before orientation.)
// center = If given, overrides `anchor`. A true value sets `anchor=CENTER`, false sets `anchor=BOTTOM`.
// ---
// d1 = The bottom diameter of the cylinder. (Before orientation.)
// d2 = The top diameter of the cylinder. (Before orientation.)
// r = The radius of the cylinder.
// d = The diameter of the cylinder.
// 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`
// Example: By Radius
// xdistribute(30) {
// cylinder(h=40, r=10);
// cylinder(h=40, r1=10, r2=5);
// }
// Example: By Diameter
// xdistribute(30) {
// cylinder(h=40, d=25);
// cylinder(h=40, d1=25, d2=10);
// }
// Example(Med): Anchoring
// cylinder(h=40, r1=10, r2=5, anchor=BOTTOM+FRONT);
// Example(Med): Spin
// cylinder(h=40, r1=10, r2=5, anchor=BOTTOM+FRONT, spin=45);
// Example(Med): Orient
// cylinder(h=40, r1=10, r2=5, anchor=BOTTOM+FRONT, spin=45, orient=FWD);
// Example(Big): Standard Connectors
// xdistribute(40) {
// cylinder(h=30, d=25) show_anchors();
// cylinder(h=30, d1=25, d2=10) show_anchors();
// }
module cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP)
{
anchor = get_anchor(anchor, center, BOTTOM, BOTTOM);
r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
l = first_defined([h, l, 1]);
sides = segs(max(r1,r2));
attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
if (r1 > r2) {
if (l > 0) {
linear_extrude(height=l, center=true, convexity=2, scale=r2/r1) {
circle(r=r1);
}
}
} else {
zflip() {
if (l > 0) {
linear_extrude(height=l, center=true, convexity=2, scale=r1/r2) {
circle(r=r2);
}
}
}
}
children();
}
}
function cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP) =
let(
anchor = get_anchor(anchor, center, BOTTOM, BOTTOM),
r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1),
r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1),
l = first_defined([h, l, 1]),
sides = segs(max(r1,r2)),
verts = [
for (i=[0:1:sides-1]) let(a=360*(1-i/sides)) [r1*cos(a),r1*sin(a),-l/2],
for (i=[0:1:sides-1]) let(a=360*(1-i/sides)) [r2*cos(a),r2*sin(a), l/2],
],
faces = [
[for (i=[0:1:sides-1]) sides-1-i],
for (i=[0:1:sides-1]) [i, ((i+1)%sides)+sides, i+sides],
for (i=[0:1:sides-1]) [i, (i+1)%sides, ((i+1)%sides)+sides],
[for (i=[0:1:sides-1]) sides+i]
]
) [reorient(anchor,spin,orient, l=l, r1=r1, r2=r2, p=verts), faces];
// Function&Module: sphere()
// Topics: Shapes (3D), Attachable, VNF Generators
// Usage: As Module
// sphere(r|d=, [circum=], [style=], ...);
// Usage: With Attachments
// sphere(r|d=, ...) { attachments }
// Usage: As Function
// vnf = sphere(r|d=, [circum=], [style=], ...);
// See Also: spheroid()
// Description:
// Creates a sphere object, with support for anchoring and attachments.
// This is a drop-in replacement for the built-in `sphere()` module.
// When called as a function, returns a [VNF](vnf.scad) for a sphere.
// Arguments:
// r = Radius of the sphere.
// ---
// d = Diameter of the sphere.
// circum = If true, the sphere is made large enough to circumscribe the sphere of the ideal side. Otherwise inscribes. Default: false (inscribes)
// style = The style of the sphere's construction. One of "orig", "aligned", "stagger", "octa", or "icosa". Default: "orig"
// 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`
// Example: By Radius
// sphere(r=50);
// Example: By Diameter
// sphere(d=100);
// Example: style="orig"
// sphere(d=100, style="orig", $fn=10);
// Example: style="aligned"
// sphere(d=100, style="aligned", $fn=10);
// Example: style="stagger"
// sphere(d=100, style="stagger", $fn=10);
// Example: style="icosa"
// sphere(d=100, style="icosa", $fn=10);
// // In "icosa" style, $fn is quantized
// // to the nearest multiple of 5.
// Example: Anchoring
// sphere(d=100, anchor=FRONT);
// Example: Spin
// sphere(d=100, anchor=FRONT, spin=45);
// Example: Orientation
// sphere(d=100, anchor=FRONT, spin=45, orient=FWD);
// Example: Standard Connectors
// sphere(d=50) show_anchors();
// Example: Called as Function
// vnf = sphere(d=100, style="icosa");
// vnf_polyhedron(vnf);
module sphere(r, d, circum=false, style="orig", anchor=CENTER, spin=0, orient=UP)
spheroid(r=r, d=d, circum=circum, style=style, anchor=anchor, spin=spin, orient=orient) children();
function sphere(r, d, circum=false, style="orig", anchor=CENTER, spin=0, orient=UP) =
spheroid(r=r, d=d, circum=circum, style=style, anchor=anchor, spin=spin, orient=orient);
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -1,198 +0,0 @@
//////////////////////////////////////////////////////////////////////
// LibFile: queues.scad
// Queue data structure implementation.
// Includes:
// include <BOSL2/std.scad>
// include <BOSL2/queues.scad>
//////////////////////////////////////////////////////////////////////
// Section: Queue Data Structure
// A queue is a first-in-first-out collection of items. You can add items onto the tail of the
// queue, or pop items off the head. While you can treat a queue as an opaque data type, using the
// functions below, it's simply implemented as a list. This means that you can use any list
// function to manipulate the queue. The first item in the list is the head queue item.
// Function: queue_init()
// Usage:
// queue = queue_init();
// Description:
// Creates an empty queue/list.
// Example:
// queue = queue_init(); // Return: []
function queue_init() = [];
// Function: queue_empty()
// Usage:
// if (queue_empty(queue)) ...
// Description:
// Returns true if the given queue is empty.
// Arguments:
// queue = The queue to test if empty.
// Example:
// queue = queue_init();
// is_empty = queue_empty(queue); // Returns: true
// queue2 = queue_add(queue, "foo");
// is_empty2 = queue_empty(queue2); // Returns: false
function queue_empty(queue) =
assert(is_list(queue))
len(queue)==0;
// Function: queue_size()
// Usage:
// depth = queue_size(queue);
// Description:
// Returns the number of items in the given queue.
// Arguments:
// queue = The queue to get the size of.
// Example:
// queue = queue_init();
// depth = queue_size(queue); // Returns: 0
// queue2 = queue_add(queue, "foo");
// depth2 = queue_size(queue2); // Returns: 1
// queue3 = queue_add(queue2, ["bar","baz","qux"]);
// depth3 = queue_size(queue3); // Returns: 4
function queue_size(queue) =
assert(is_list(queue))
len(queue);
// Function: queue_head()
// Usage:
// item = queue_head(queue);
// list = queue_head(queue,n);
// Description:
// If `n` is not given, returns the first item from the head of the queue.
// If `n` is given, returns a list of the first `n` items from the head of the queue.
// Arguments:
// queue = The queue/list to get item(s) from the head of.
// Example:
// queue = [4,5,6,7,8,9];
// item = queue_head(queue); // Returns: 4
// list = queue_head(queue,n=3); // Returns: [4,5,6]
function queue_head(queue,n=undef) =
assert(is_list(queue))
is_undef(n)? (
queue[0]
) : (
let(queuesize = len(queue))
assert(is_num(n))
assert(n>=0)
assert(queuesize>=n, "queue underflow")
[for (i=[0:1:n-1]) queue[i]]
);
// Function: queue_tail()
// Usage:
// item = queue_tail(queue);
// list = queue_tail(queue,n);
// Description:
// If `n` is not given, returns the last item from the tail of the queue.
// If `n` is given, returns a list of the last `n` items from the tail of the queue.
// Arguments:
// queue = The queue/list to get item(s) from the tail of.
// Example:
// queue = [4,5,6,7,8,9];
// item = queue_tail(queue); // Returns: 9
// list = queue_tail(queue,n=3); // Returns: [7,8,9]
function queue_tail(queue,n=undef) =
assert(is_list(queue))
let(queuesize = len(queue))
is_undef(n)? (
queue[queuesize-1]
) : (
assert(is_num(n))
assert(n>=0)
assert(queuesize>=n, "queue underflow")
[for (i=[0:1:n-1]) queue[queuesize-n+i]]
);
// Function: queue_peek()
// Usage:
// item = queue_peek(queue,[pos]);
// list = queue_peek(queue,pos,n);
// Description:
// If `n` is not given, returns the queue item at position `pos`.
// If `n` is given, returns a list of the `n` queue items at and after position `pos`.
// Arguments:
// queue = The queue to read from.
// pos = The position of the queue item to read. Default: 0
// n = The number of queue items to return. Default: undef (Return only the queue item at `pos`)
// Example:
// queue = [2,3,4,5,6,7,8,9];
// item = queue_peek(queue); // Returns: 2
// item2 = queue_peek(queue, 3); // Returns: 5
// list = queue_peek(queue, 4, 3); // Returns: [6,7,8]
function queue_peek(queue,pos=0,n=undef) =
assert(is_list(queue))
assert(is_num(pos))
assert(pos>=0)
let(queuesize = len(queue))
assert(queuesize>=pos, "queue underflow")
is_undef(n)? (
queue[pos]
) : (
assert(is_num(n))
assert(n>=0)
assert(n<queuesize-pos)
[for (i=[0:1:n-1]) queue[pos+i]]
);
// Function: queue_add()
// Usage:
// modified_queue = queue_add(queue,items);
// Description:
// Adds the given `items` onto the queue `queue`. Returns the modified queue.
// Arguments:
// queue = The queue to modify.
// items = A value or list of values to add to the queue.
// Example:
// queue = [4,9,2,3];
// queue2 = queue_add(queue,7); // Returns: [4,9,2,3,7]
// queue3 = queue_add(queue2,[6,1]); // Returns: [4,9,2,3,7,6,1]
// queue4 = queue_add(queue,[[5,8]]); // Returns: [4,9,2,3,[5,8]]
// queue5 = queue_add(queue,[[5,8],6,7]); // Returns: [4,9,2,3,[5,8],6,7]
// Example: Typical Producer and Consumer
// q2 = queue_add(q, "foo");
// ...
// val = queue_head(q2);
// q3 = queue_pop(q2);
function queue_add(queue,items) =
assert(is_list(queue))
is_list(items)? concat(queue, items) : concat(queue, [items]);
// Function: queue_pop()
// Usage:
// modified_queue = queue_pop(queue, [n]);
// Description:
// Removes `n` items from the head of the queue. Returns the modified queue.
// Arguments:
// queue = The queue to modify.
// n = The number of items to remove from the head of the queue. Default: 1
// Example:
// queue = [4,5,6,7,8,9];
// queue2 = queue_pop(queue); // Returns: [5,6,7,8,9]
// queue3 = queue_pop(queue2,n=3); // Returns: [8,9]
// Example: Typical Producer and Consumer
// q2 = queue_add(q, "foo");
// ...
// val = queue_head(q2);
// q3 = queue_pop(q2);
function queue_pop(queue,n=1) =
assert(is_list(queue))
assert(is_num(n))
assert(n>=0)
let(queuesize = len(queue))
assert(queuesize>=n, "queue underflow")
[for (i = [n:1:queuesize-1]) queue[i]];
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -248,7 +248,7 @@ function split_path_at_region_crossings(path, region, closed=true, eps=EPSILON)
subpaths = [
for (p = pair(crossings))
deduplicate(
path_subselect(path, p[0][0], p[0][1], p[1][0], p[1][1], closed=closed),
_path_select(path, p[0][0], p[0][1], p[1][0], p[1][1], closed=closed),
eps=eps
)
]
@ -482,6 +482,7 @@ function _offset_chamfer(center, points, delta) =
function _shift_segment(segment, d) =
assert(!approx(segment[0],segment[1]),"Path has repeated points")
move(d*line_normal(segment),segment);
@ -581,7 +582,7 @@ function _point_dist(path,pathseg_unit,pathseg_len,pt) =
function _offset_region(
paths, r, delta, chamfer, closed,
maxstep, check_valid, quality,
check_valid, quality,
return_faces, firstface_index,
flip_faces, _acc=[], _i=0
) =
@ -593,7 +594,7 @@ function _offset_region(
offset(
paths[_i].y,
r=r, delta=delta, chamfer=chamfer, closed=closed,
maxstep=maxstep, check_valid=check_valid, quality=quality,
check_valid=check_valid, quality=quality,
return_faces=return_faces, firstface_index=firstface_index,
flip_faces=flip_faces
)
@ -603,14 +604,14 @@ function _offset_region(
offset(
paths[_i].y,
r=u_mul(-1,r), delta=u_mul(-1,delta), chamfer=chamfer, closed=closed,
maxstep=maxstep, check_valid=check_valid, quality=quality,
check_valid=check_valid, quality=quality,
return_faces=return_faces, firstface_index=firstface_index,
flip_faces=flip_faces
)
])
),
r=r, delta=delta, chamfer=chamfer, closed=closed,
maxstep=maxstep, check_valid=check_valid, quality=quality,
check_valid=check_valid, quality=quality,
return_faces=return_faces, firstface_index=firstface_index, flip_faces=flip_faces
);
@ -675,7 +676,7 @@ function _offset_region(
// Example(2D):
// star = star(5, r=100, ir=30);
// #stroke(closed=true, star);
// stroke(closed=true, offset(star, r=-10, closed=true));
// stroke(closed=true, offset(star, r=-10, closed=true, $fn=20));
// Example(2D): This case needs `quality=2` for success
// test = [[0,0],[10,0],[10,7],[0,7], [-1,-3]];
// polygon(offset(test,r=-1.9, closed=true, quality=2));
@ -713,14 +714,14 @@ function _offset_region(
// region(offset(rgn, r=-5));
function offset(
path, r=undef, delta=undef, chamfer=false,
maxstep=0.1, closed=false, check_valid=true,
closed=false, check_valid=true,
quality=1, return_faces=false, firstface_index=0,
flip_faces=false
) =
is_region(path)? (
assert(!return_faces, "return_faces not supported for regions.")
let(
path = [for (p=path) polygon_is_clockwise(p)? p : reverse(p)],
path = [for (p=path) clockwise_polygon(p)],
rgn = exclusive_or([for (p = path) [p]]),
pathlist = sort(idx=0,[
for (i=[0:1:len(rgn)-1]) [
@ -733,7 +734,7 @@ function offset(
])
) _offset_region(
pathlist, r=r, delta=delta, chamfer=chamfer, closed=true,
maxstep=maxstep, check_valid=check_valid, quality=quality,
check_valid=check_valid, quality=quality,
return_faces=return_faces, firstface_index=firstface_index,
flip_faces=flip_faces
)
@ -742,7 +743,7 @@ function offset(
let(
chamfer = is_def(r) ? false : chamfer,
quality = max(0,round(quality)),
flip_dir = closed && !polygon_is_clockwise(path)? -1 : 1,
flip_dir = closed && !is_polygon_clockwise(path)? -1 : 1,
d = flip_dir * (is_def(r) ? r : delta),
shiftsegs = [for(i=[0:len(path)-1]) _shift_segment(select(path,i,i+1), d)],
// good segments are ones where no point on the segment is less than distance d from any point on the path
@ -777,43 +778,39 @@ function offset(
],
steps = is_def(delta) ? [] : [
for(i=[0:len(goodsegs)-1])
r==0 ? 0 :
ceil(
abs(r)*vector_angle(
select(goodsegs,i-1)[1]-goodpath[i],
goodsegs[i][0]-goodpath[i]
)*PI/180/maxstep
)
r==0 ? 0
// floor is important here to ensure we don't generate extra segments when nearly straight paths expand outward
: 1+floor(segs(r)*vector_angle(
select(goodsegs,i-1)[1]-goodpath[i],
goodsegs[i][0]-goodpath[i])
/360)
],
// If rounding is true then newcorners replaces sharpcorners with rounded arcs where needed
// Otherwise it's the same as sharpcorners
// If rounding is on then newcorners[i] will be the point list that replaces goodpath[i] and newcorners later
// gets flattened. If rounding is off then we set it to [sharpcorners] so we can later flatten it and get
// plain sharpcorners back.
newcorners = is_def(delta) && !chamfer ? [sharpcorners] : [
for(i=[0:len(goodsegs)-1]) (
(!chamfer && steps[i] <=2) //Chamfer all points but only round if steps is 3 or more
|| !outsidecorner[i] // Don't round inside corners
|| (!closed && (i==0 || i==len(goodsegs)-1)) // Don't round ends of an open path
)? [sharpcorners[i]] : (
chamfer?
_offset_chamfer(
goodpath[i], [
select(goodsegs,i-1)[1],
sharpcorners[i],
goodsegs[i][0]
], d
) :
arc(
cp=goodpath[i],
points=[
select(goodsegs,i-1)[1],
goodsegs[i][0]
],
N=steps[i]
)
)
],
newcorners = is_def(delta) && !chamfer ? [sharpcorners]
: [for(i=[0:len(goodsegs)-1])
(!chamfer && steps[i] <=1) // Don't round if steps is smaller than 2
|| !outsidecorner[i] // Don't round inside corners
|| (!closed && (i==0 || i==len(goodsegs)-1)) // Don't round ends of an open path
? [sharpcorners[i]]
: chamfer ? _offset_chamfer(
goodpath[i], [
select(goodsegs,i-1)[1],
sharpcorners[i],
goodsegs[i][0]
], d
)
: // rounded case
arc(cp=goodpath[i],
points=[
select(goodsegs,i-1)[1],
goodsegs[i][0]
],
N=steps[i])
],
pointcount = (is_def(delta) && !chamfer)?
repeat(1,len(sharpcorners)) :
[for(i=[0:len(goodsegs)-1]) len(newcorners[i])],
@ -864,7 +861,7 @@ function _tagged_region(region1,region2,keep1,keep2,eps=EPSILON) =
[for (tagpath = tagged1) if (in_list(tagpath[0], keep1)) tagpath[1]],
[for (tagpath = tagged2) if (in_list(tagpath[0], keep2)) tagpath[1]]
),
outregion = assemble_path_fragments(tagged, eps=eps)
outregion = _assemble_path_fragments(tagged, eps=eps)
) outregion;

View file

@ -649,14 +649,14 @@ function _path_join(paths,joint,k=0.5,i=0,result=[],relocate=true,closed=false)
assert(d_first<path_length(revresult),str("Path ",i," is too short for specified cut distance ",d_first))
assert(d_next<path_length(nextpath), str("Path ",i+1," is too short for specified cut distance ",d_next))
let(
firstcut = path_cut_points(revresult, d_first, direction=true),
nextcut = path_cut_points(nextpath, d_next, direction=true)
firstcut = _path_cut_points(revresult, d_first, direction=true),
nextcut = _path_cut_points(nextpath, d_next, direction=true)
)
assert(!loop || nextcut[1] < len(revresult)-1-firstcut[1], "Path is too short to close the loop")
let(
first_dir=firstcut[2],
next_dir=nextcut[2],
corner = ray_intersection([firstcut[0], firstcut[0]-first_dir], [nextcut[0], nextcut[0]-next_dir])
corner = line_intersection([firstcut[0], firstcut[0]-first_dir], [nextcut[0], nextcut[0]-next_dir],RAY,RAY)
)
assert(is_def(corner), str("Curve directions at cut points don't intersect in a corner when ",
loop?"closing the path":str("adding path ",i+1)))
@ -692,16 +692,14 @@ function _path_join(paths,joint,k=0.5,i=0,result=[],relocate=true,closed=false)
// The path is shifted by `offset()` multiple times in sequence
// to produce the final shape (not multiple shifts from one parent), so coarse definition of the input path will degrade
// from the successive shifts. If the result seems rough or strange try increasing the number of points you use for
// your input. If you get unexpected corners in your result, decrease `offset_maxstep` or decrease `steps`. You must
// choose `offset_maxstep` small enough so that the first offset step rounds, otherwise you will probably not get any
// rounding, even if you have selected rounding. This may require a much smaller value than you expect. However, be
// aware that large numbers of points (especially when check_valid is true) can lead to lengthy run times. If your
// shape doesn't develop corners you may be able to save a lot of time by setting `check_valid=false`. Be aware that
// your input. If you get unexpected corners in your result you may have forgotten to set `$fn` or `$fa` and `$fs`.
// Be aware that large numbers of points (especially when check_valid is true) can lead to lengthy run times. If your
// shape doesn't develop new corners from the offsetting you may be able to save a lot of time by setting `check_valid=false`. Be aware that
// disabling the validity check when it is needed can generate invalid polyhedra that will produce CGAL errors upon
// rendering. Such validity errors will also occur if you specify a self-intersecting shape.
// The offset profile is quantized to 1/1024 steps to avoid failures in offset() that can occur with very tiny offsets.
// .
// The build-in profiles are: circular rounding, teardrop rounding, chamfer, continuous curvature rounding, and chamfer.
// The build-in profiles are: circular rounding, teardrop rounding, continuous curvature rounding, and chamfer.
// Also note that when a rounding radius is negative the rounding will flare outwards. The easiest way to specify
// the profile is by using the profile helper functions. These functions take profile parameters, as well as some
// general settings and translate them into a profile specification, with error checking on your input. The description below
@ -711,7 +709,7 @@ function _path_join(paths,joint,k=0.5,i=0,result=[],relocate=true,closed=false)
// - profile: os_profile(points)
// Define the offset profile with a list of points. The first point must be [0,0] and the roundover should rise in the positive y direction, with positive x values for inward motion (standard roundover) and negative x values for flaring outward. If the y value ever decreases then you might create a self-intersecting polyhedron, which is invalid. Such invalid polyhedra will create cryptic assertion errors when you render your model and it is your responsibility to avoid creating them. Note that the starting point of the profile is the center of the extrusion. If you use a profile as the top it will rise upwards. If you use it as the bottom it will be inverted, and will go downward.
// - circle: os_circle(r|cut). Define circular rounding either by specifying the radius or cut distance.
// - smooth: os_smooth(cut|joint). Define continuous curvature rounding, with `cut` and `joint` as for round_corners.
// - smooth: os_smooth(cut|joint, [k]). Define continuous curvature rounding, with `cut` and `joint` as for round_corners. The k parameter controls how fast the curvature changes and should be between 0 and 1.
// - teardrop: os_teardrop(r|cut). Rounding using a 1/8 circle that then changes to a 45 degree chamfer. The chamfer is at the end, and enables the object to be 3d printed without support. The radius gives the radius of the circular part.
// - chamfer: os_chamfer([height], [width], [cut], [angle]). Chamfer the edge at desired angle or with desired height and width. You can specify height and width together and the angle will be ignored, or specify just one of height and width and the angle is used to determine the shape. Alternatively, specify "cut" along with angle to specify the cut back distance of the chamfer.
// - mask: os_mask(mask, [out]). Create a profile from one of the [2d masking shapes](shapes2d.scad#5-2d-masking-shapes). The `out` parameter specifies that the mask should flare outward (like crown molding or baseboard). This is set false by default.
@ -721,14 +719,14 @@ function _path_join(paths,joint,k=0.5,i=0,result=[],relocate=true,closed=false)
// - check_valid: passed to offset(). Default: true
// - quality: passed to offset(). Default: 1
// - steps: Number of vertical steps to use for the profile. (Not used by os_profile). Default: 16
// - offset_maxstep: The maxstep distance for offset() calls; controls the horizontal step density. Set smaller if you don't get the expected rounding. Default: 1
// - offset: Select "round" (r=) or "delta" (delta=) offset types for offset. You can also choose "chamfer" but this leads to exponential growth in the number of vertices with the steps parameter. Default: "round"
// .
// Many of the arguments are described as setting "default" values because they establish settings which may be overridden by
// the top and bottom profile specifications.
// .
// You will generally want to use the above helper functions to generate the profiles.
// The profile specification is a list of pairs of keywords and values, e.g. ["r",12, type, "circle"]. The keywords are
// The profile specification is a list of pairs of keywords and values, e.g. ["for","offset_sweep","r",12, type, "circle"]. The keywords are
// - "for" - must appear first in the list and have the value "offset_sweep"
// - "type" - type of rounding to apply, one of "circle", "teardrop", "chamfer", "smooth", or "profile" (Default: "circle")
// - "r" - the radius of the roundover, which may be zero for no roundover, or negative to round or flare outward. Default: 0
// - "cut" - the cut distance for the roundover or chamfer, which may be negative for flares
@ -742,7 +740,6 @@ function _path_join(paths,joint,k=0.5,i=0,result=[],relocate=true,closed=false)
// - "check_valid" - passed to offset. Default: true.
// - "quality" - passed to offset. Default: 1.
// - "steps" - number of vertical steps to use for the roundover. Default: 16.
// - "offset_maxstep" - maxstep distance for offset() calls; controls the horizontal step density. Set smaller if you don't get expected rounding. Default: 1
// - "offset" - select "round" (r=), "delta" (delta=), or "chamfer" offset type for offset. Default: "round"
// .
// Note that if you set the "offset" parameter to "chamfer" then every exterior corner turns from one vertex into two vertices with
@ -763,7 +760,6 @@ function _path_join(paths,joint,k=0.5,i=0,result=[],relocate=true,closed=false)
// steps = default step count. Default: 16
// quality = default quality. Default: 1
// check_valid = default check_valid. Default: true.
// offset_maxstep = default maxstep value to pass to offset. Default: 1
// extra = default extra height. Default: 0
// cut = default cut value.
// chamfer_width = default width value for chamfers.
@ -781,24 +777,26 @@ function _path_join(paths,joint,k=0.5,i=0,result=[],relocate=true,closed=false)
// star = star(5, r=22, ir=13);
// rounded_star = round_corners(star, cut=flatten(repeat([.5,0],5)), $fn=24);
// offset_sweep(rounded_star, height=20, bottom=os_circle(r=4), top=os_circle(r=1), steps=15);
// Example: Rounding a star shaped prism with negative radius values
// star = star(5, r=22, ir=13);
// rounded_star = round_corners(star, cut=flatten(repeat([.5,0],5)), $fn=24);
// Example: Rounding a star shaped prism with negative radius values. The starting shape has no corners, so the value of `$fn` does not matter.
// star = star(5, r=22, ir=13);
// rounded_star = round_corners(star, cut=flatten(repeat([.5,0],5)), $fn=36);
// offset_sweep(rounded_star, height=20, bottom=os_circle(r=-4), top=os_circle(r=-1), steps=15);
// Example: Unexpected corners in the result even with `offset="round"` (the default), even with offset_maxstep set small.
// Example: If the shape has sharp corners, make sure to set `$fn/$fs/$fa`. The corners of this triangle are not round, even though `offset="round"` (the default) because the number of segments is small.
// triangle = [[0,0],[10,0],[5,10]];
// offset_sweep(triangle, height=6, bottom = os_circle(r=-2),steps=16,offset_maxstep=0.25);
// Example: Can improve the result by decreasing the number of steps
// offset_sweep(triangle, height=6, bottom = os_circle(r=-2),steps=4);
// Example: Can improve the result by increasing $fn
// $fn=12;
// triangle = [[0,0],[10,0],[5,10]];
// offset_sweep(triangle, height=6, bottom = os_circle(r=-2),steps=4,offset_maxstep=0.25);
// Example: Or by decreasing `offset_maxstep`
// offset_sweep(triangle, height=6, bottom = os_circle(r=-2),steps=4);
// Example: Using $fa and $fs works too; it produces a different looking triangulation of the rounded corner
// $fa=1;$fs=0.3;
// triangle = [[0,0],[10,0],[5,10]];
// offset_sweep(triangle, height=6, bottom = os_circle(r=-2),steps=16,offset_maxstep=0.01);
// Example: Here is the star chamfered at the top with a teardrop rounding at the bottom. Check out the rounded corners on the chamfer. Note that a very small value of `offset_maxstep` is needed to keep these round. Observe how the rounded star points vanish at the bottom in the teardrop: the number of vertices does not remain constant from layer to layer.
// offset_sweep(triangle, height=6, bottom = os_circle(r=-2),steps=4);
// Example: Here is the star chamfered at the top with a teardrop rounding at the bottom. Check out the rounded corners on the chamfer. The large $fn value ensures a smooth curve on the concave corners of the chamfer. It has no effect anywhere else on the model. Observe how the rounded star points vanish at the bottom in the teardrop: the number of vertices does not remain constant from layer to layer.
// star = star(5, r=22, ir=13);
// rounded_star = round_corners(star, cut=flatten(repeat([.5,0],5)), $fn=24);
// offset_sweep(rounded_star, height=20, bottom=os_teardrop(r=4), top=os_chamfer(width=4,offset_maxstep=.1));
// Example: We round a cube using the continous curvature rounding profile. But note that the corners are not smooth because the curved square collapses into a square with corners. When a collapse like this occurs, we cannot turn `check_valid` off.
// offset_sweep(rounded_star, height=20, bottom=os_teardrop(r=4), top=os_chamfer(width=4),$fn=64);
// Example: We round a cube using the continous curvature rounding profile. But note that the corners are not smooth because the curved square collapses into a square with corners. When a collapse like this occurs, we cannot turn `check_valid` off. For a better result use `rounded_prism()` instead.
// square = square(1);
// rsquare = round_corners(square, method="smooth", cut=0.1, k=0.7, $fn=36);
// end_spec = os_smooth(cut=0.1, k=0.7, steps=22);
@ -820,13 +818,14 @@ function _path_join(paths,joint,k=0.5,i=0,result=[],relocate=true,closed=false)
// height=50;
// back_half(y=25, s=200)
// difference(){
// offset_sweep(roundbox, height=height, bottom=["r",10,"type","teardrop"], top=["r",2], steps = 22, check_valid=false);
// offset_sweep(roundbox, height=height, bottom=["for","offset_sweep","r",10,"type","teardrop"],
// top=["for","offset_sweep","r",2], steps = 22, check_valid=false);
// up(thickness)
// offset_sweep(offset(roundbox, r=-thickness, closed=true),
// height=height-thickness, steps=22,
// bottom=["r",6],
// top=["type","chamfer","angle",30,"chamfer_height",-3,"extra",1,"check_valid",false]);
// }
// bottom=["for","offset_sweep","r",6],
// top=["for","offset_sweep","type","chamfer","angle",30,"chamfer_height",-3,"extra",1,"check_valid",false]);
// }
// Example: A box with multiple sections and rounded dividers
// thickness = 2;
// box = square([255,50]);
@ -850,30 +849,30 @@ function _path_join(paths,joint,k=0.5,i=0,result=[],relocate=true,closed=false)
// thickness = 2;
// ht=20;
// difference(){
// offset_sweep(rounded_star, height=ht, bottom=["r",4], top=["r",1], steps=15);
// offset_sweep(rounded_star, height=ht, bottom=["for","offset_sweep","r",4], top=["for","offset_sweep","r",1], steps=15);
// up(thickness)
// offset_sweep(offset(rounded_star,r=-thickness,closed=true),
// height=ht-thickness, check_valid=false,
// bottom=os_circle(r=7), top=os_circle(r=-1, extra=1));
// bottom=os_circle(r=7), top=os_circle(r=-1, extra=1),$fn=40);
// }
// Example: A profile defined by an arbitrary sequence of points.
// star = star(5, r=22, ir=13);
// rounded_star = round_corners(star, cut=flatten(repeat([.5,0],5)), $fn=24);
// profile = os_profile(points=[[0,0],[.3,.1],[.6,.3],[.9,.9], [1.2, 2.7],[.8,2.7],[.8,3]]);
// offset_sweep(reverse(rounded_star), height=20, top=profile, bottom=profile);
// offset_sweep(reverse(rounded_star), height=20, top=profile, bottom=profile, $fn=32);
// Example: Parabolic rounding
// star = star(5, r=22, ir=13);
// rounded_star = round_corners(star, cut=flatten(repeat([.5,0],5)), $fn=24);
// offset_sweep(rounded_star, height=20, top=os_profile(points=[for(r=[0:.1:2])[sqr(r),r]]),
// bottom=os_profile(points=[for(r=[0:.2:5])[-sqrt(r),r]]));
// Example: This example uses a sine wave offset profile. Note that because the offsets occur sequentially and the path grows incrementally the offset needs a very fine resolution to produce the proper result. Note that we give no specification for the bottom, so it is straight.
// bottom=os_profile(points=[for(r=[0:.2:5])[-sqrt(r),r]]),$fn=32);
// Example: This example uses a sine wave offset profile. Note that we give no specification for the bottom, so it is straight.
// sq = [[0,0],[20,0],[20,20],[0,20]];
// sinwave = os_profile(points=[for(theta=[0:5:720]) [4*sin(theta), theta/700*15]]);
// offset_sweep(sq, height=20, top=sinwave, offset_maxstep=.05);
// offset_sweep(sq, height=20, top=sinwave, $fn=32);
// Example: The same as the previous example but `offset="delta"`
// sq = [[0,0],[20,0],[20,20],[0,20]];
// sinwave = os_profile(points=[for(theta=[0:5:720]) [4*sin(theta), theta/700*15]]);
// offset_sweep(sq, height=20, top=sinwave, offset_maxstep=.05, offset="delta");
// offset_sweep(sq, height=20, top=sinwave, offset="delta");
// Example: a box with a flared top. A nice roundover on the top requires a profile edge, but we can use "extra" to create a small chamfer.
// rhex = round_corners(hexagon(side=10), method="smooth", joint=2, $fs=0.2);
// back_half()
@ -895,7 +894,7 @@ function _path_join(paths,joint,k=0.5,i=0,result=[],relocate=true,closed=false)
// This function does the actual work of repeatedly calling offset() and concatenating the resulting face and vertex lists to produce
// the inputs for the polyhedron module.
function _make_offset_polyhedron(path,offsets, offset_type, flip_faces, quality, check_valid, maxstep, offsetind=0,
function _make_offset_polyhedron(path,offsets, offset_type, flip_faces, quality, check_valid, offsetind=0,
vertexcount=0, vertices=[], faces=[] )=
offsetind==len(offsets)? (
let(
@ -913,14 +912,14 @@ function _make_offset_polyhedron(path,offsets, offset_type, flip_faces, quality,
vertices_faces = offset(
path, r=r, delta=delta, chamfer = do_chamfer, closed=true,
check_valid=check_valid, quality=quality,
maxstep=maxstep, return_faces=true,
return_faces=true,
firstface_index=vertexcount,
flip_faces=flip_faces
)
)
_make_offset_polyhedron(
vertices_faces[0], offsets, offset_type,
flip_faces, quality, check_valid, maxstep,
flip_faces, quality, check_valid,
offsetind+1, vertexcount+len(path),
vertices=concat(
vertices,
@ -931,24 +930,29 @@ function _make_offset_polyhedron(path,offsets, offset_type, flip_faces, quality,
);
function _struct_valid(spec, func, name) =
spec==[] ? true :
assert(is_list(spec) && len(spec)>=2 && spec[0]=="for",str("Specification for \"", name, "\" is an invalid structure"))
assert(spec[1]==func, str("Specification for \"",name,"\" is for a different function (",func,")"));
function offset_sweep(
path, height,
bottom=[], top=[],
h, l,
offset="round", r=0, steps=16,
quality=1, check_valid=true,
offset_maxstep=1, extra=0,
extra=0,
cut=undef, chamfer_width=undef, chamfer_height=undef,
joint=undef, k=0.75, angle=45
) =
let(
argspec = [
["for",""],
["r",r],
["extra",extra],
["type","circle"],
["check_valid",check_valid],
["quality",quality],
["offset_maxstep", offset_maxstep],
["steps",steps],
["offset",offset],
["chamfer_width",chamfer_width],
@ -960,21 +964,23 @@ function offset_sweep(
["points", []],
],
path = check_and_fix_path(path, [2], closed=true),
clockwise = polygon_is_clockwise(path),
clockwise = is_polygon_clockwise(path),
dummy1 = _struct_valid(top,"offset_sweep","top"),
dummy2 = _struct_valid(bottom,"offset_sweep","bottom"),
top = struct_set(argspec, top, grow=false),
bottom = struct_set(argspec, bottom, grow=false),
// This code does not work. It hits the error in _make_offset_polyhedron from offset being wrong
// before this code executes. Had to move the test into _make_offset_polyhedron, which is ugly since it's in the loop
offsetsok = in_list(struct_val(top, "offset"),["round","delta"])
&& in_list(struct_val(bottom, "offset"),["round","delta"])
offsetsok = in_list(struct_val(top, "offset"),["round","delta","chamfer"])
&& in_list(struct_val(bottom, "offset"),["round","delta","chamfer"])
)
assert(offsetsok,"Offsets must be one of \"round\" or \"delta\"")
assert(offsetsok,"Offsets must be one of \"round\", \"delta\", or \"chamfer\"")
let(
offsets_bot = _rounding_offsets(bottom, -1),
offsets_top = _rounding_offsets(top, 1),
dummy = offset == "chamfer" && (len(offsets_bot)>5 || len(offsets_top)>5)
dummy = (struct_val(top,"offset")=="chamfer" && len(offsets_top)>5)
|| (struct_val(bottom,"offset")=="chamfer" && len(offsets_bot)>5)
? echo("WARNING: You have selected offset=\"chamfer\", which leads to exponential growth in the vertex count and requested more than 5 layers. This can be slow or run out of recursion depth.")
: 0,
@ -997,7 +1003,6 @@ function offset_sweep(
path, offsets_bot, struct_val(bottom,"offset"), clockwise,
struct_val(bottom,"quality"),
struct_val(bottom,"check_valid"),
struct_val(bottom,"offset_maxstep"),
vertices=initial_vertices_bot
),
@ -1008,7 +1013,6 @@ function offset_sweep(
struct_val(top,"offset"), !clockwise,
struct_val(top,"quality"),
struct_val(top,"check_valid"),
struct_val(top,"offset_maxstep"),
vertexcount=top_start_ind,
vertices=initial_vertices_top
),
@ -1027,14 +1031,14 @@ module offset_sweep(path, height,
h, l,
offset="round", r=0, steps=16,
quality=1, check_valid=true,
offset_maxstep=1, extra=0,
extra=0,
cut=undef, chamfer_width=undef, chamfer_height=undef,
joint=undef, k=0.75, angle=45,
convexity=10,anchor="origin",cp,
spin=0, orient=UP, extent=false)
{
vnf = offset_sweep(path=path, height=height, h=h, l=l, top=top, bottom=bottom, offset=offset, r=r, steps=steps,
quality=quality, check_valid=true, offset_maxstep=offset_maxstep, extra=extra, cut=cut, chamfer_width=chamfer_width,
quality=quality, check_valid=true, extra=extra, cut=cut, chamfer_width=chamfer_width,
chamfer_height=chamfer_height, joint=joint, k=k, angle=angle);
attachable(anchor=anchor, spin=spin, orient=orient, vnf=vnf, extent=extent, cp=is_def(cp) ? cp : vnf_centroid(vnf))
@ -1046,9 +1050,10 @@ module offset_sweep(path, height,
function os_circle(r,cut,extra,check_valid, quality,steps, offset_maxstep, offset) =
function os_circle(r,cut,extra,check_valid, quality,steps, offset) =
assert(num_defined([r,cut])==1, "Must define exactly one of `r` and `cut`")
_remove_undefined_vals([
"for", "offset_sweep",
"type", "circle",
"r",r,
"cut",cut,
@ -1056,13 +1061,13 @@ function os_circle(r,cut,extra,check_valid, quality,steps, offset_maxstep, offse
"check_valid",check_valid,
"quality", quality,
"steps", steps,
"offset_maxstep", offset_maxstep,
"offset", offset
]);
function os_teardrop(r,cut,extra,check_valid, quality,steps, offset_maxstep, offset) =
function os_teardrop(r,cut,extra,check_valid, quality,steps, offset) =
assert(num_defined([r,cut])==1, "Must define exactly one of `r` and `cut`")
_remove_undefined_vals([
"for", "offset_sweep",
"type", "teardrop",
"r",r,
"cut",cut,
@ -1070,14 +1075,14 @@ function os_teardrop(r,cut,extra,check_valid, quality,steps, offset_maxstep, off
"check_valid",check_valid,
"quality", quality,
"steps", steps,
"offset_maxstep", offset_maxstep,
"offset", offset
]);
function os_chamfer(height, width, cut, angle, extra,check_valid, quality,steps, offset_maxstep, offset) =
function os_chamfer(height, width, cut, angle, extra,check_valid, quality,steps, offset) =
let(ok = (is_def(cut) && num_defined([height,width])==0) || num_defined([height,width])>0)
assert(ok, "Must define `cut`, or one or both of `width` and `height`")
_remove_undefined_vals([
"for", "offset_sweep",
"type", "chamfer",
"chamfer_width",width,
"chamfer_height",height,
@ -1087,13 +1092,13 @@ function os_chamfer(height, width, cut, angle, extra,check_valid, quality,steps,
"check_valid",check_valid,
"quality", quality,
"steps", steps,
"offset_maxstep", offset_maxstep,
"offset", offset
]);
function os_smooth(cut, joint, k, extra,check_valid, quality,steps, offset_maxstep, offset) =
function os_smooth(cut, joint, k, extra,check_valid, quality,steps, offset) =
assert(num_defined([joint,cut])==1, "Must define exactly one of `joint` and `cut`")
_remove_undefined_vals([
"for", "offset_sweep",
"type", "smooth",
"joint",joint,
"k",k,
@ -1102,24 +1107,23 @@ function os_smooth(cut, joint, k, extra,check_valid, quality,steps, offset_maxst
"check_valid",check_valid,
"quality", quality,
"steps", steps,
"offset_maxstep", offset_maxstep,
"offset", offset
]);
function os_profile(points, extra,check_valid, quality, offset_maxstep, offset) =
function os_profile(points, extra,check_valid, quality, offset) =
assert(is_path(points),"Profile point list is not valid")
_remove_undefined_vals([
"for", "offset_sweep",
"type", "profile",
"points", points,
"extra",extra,
"check_valid",check_valid,
"quality", quality,
"offset_maxstep", offset_maxstep,
"offset", offset
]);
function os_mask(mask, out=false, extra,check_valid, quality, offset_maxstep, offset) =
function os_mask(mask, out=false, extra,check_valid, quality, offset) =
let(
origin_index = [for(i=idx(mask)) if (mask[i].x<0 && mask[i].y<0) i],
xfactor = out ? -1 : 1
@ -1128,7 +1132,7 @@ function os_mask(mask, out=false, extra,check_valid, quality, offset_maxstep, of
let(
points = ([for(pt=polygon_shift(mask,origin_index[0])) [xfactor*max(pt.x,0),-max(pt.y,0)]])
)
os_profile(deduplicate(move(-points[1],p=list_tail(points))), extra,check_valid,quality,offset_maxstep,offset);
os_profile(deduplicate(move(-points[1],p=list_tail(points))), extra,check_valid,quality,offset);
// Module: convex_offset_extrude()
@ -1137,10 +1141,12 @@ function os_mask(mask, out=false, extra,check_valid, quality, offset_maxstep, of
// Extrudes 2d children with layers formed from the convex hull of the offset of each child according to a sequence of offset values.
// Like `offset_sweep` this module can use built-in offset profiles to provide treatments such as roundovers or chamfers but unlike `offset_sweep()` it
// operates on 2d children rather than a point list. Each offset is computed using
// the native `offset()` module from the input geometry. If your geometry has internal holes or is too small for the specified offset then you may get
// the native `offset()` module from the input geometry.
// If your shape has corners that you want rounded by offset be sure to set `$fn` or `$fs` appropriately.
// If your geometry has internal holes or is too small for the specified offset then you may get
// unexpected results.
// .
// The build-in profiles are: circular rounding, teardrop rounding, chamfer, continuous curvature rounding, and chamfer.
// The build-in profiles are: circular rounding, teardrop rounding, continuous curvature rounding, and chamfer.
// Also note that when a rounding radius is negative the rounding will flare outwards. The easiest way to specify
// the profile is by using the profile helper functions. These functions take profile parameters, as well as some
// general settings and translate them into a profile specification, with error checking on your input. The description below
@ -1154,7 +1160,7 @@ function os_mask(mask, out=false, extra,check_valid, quality, offset_maxstep, of
// - profile: os_profile(points)
// Define the offset profile with a list of points. The first point must be [0,0] and the roundover should rise in the positive y direction, with positive x values for inward motion (standard roundover) and negative x values for flaring outward. If the y value ever decreases then you might create a self-intersecting polyhedron, which is invalid. Such invalid polyhedra will create cryptic assertion errors when you render your model and it is your responsibility to avoid creating them. Note that the starting point of the profile is the center of the extrusion. If you use a profile as the top it will rise upwards. If you use it as the bottom it will be inverted, and will go downward.
// - circle: os_circle(r|cut). Define circular rounding either by specifying the radius or cut distance.
// - smooth: os_smooth(cut|joint). Define continuous curvature rounding, with `cut` and `joint` as for round_corners.
// - smooth: os_smooth(cut|joint, [k]). Define continuous curvature rounding, with `cut` and `joint` as for round_corners. The k parameter controls how fast the curvature changes and should be between 0 and 1.
// - teardrop: os_teardrop(r|cut). Rounding using a 1/8 circle that then changes to a 45 degree chamfer. The chamfer is at the end, and enables the object to be 3d printed without support. The radius gives the radius of the circular part.
// - chamfer: os_chamfer([height], [width], [cut], [angle]). Chamfer the edge at desired angle or with desired height and width. You can specify height and width together and the angle will be ignored, or specify just one of height and width and the angle is used to determine the shape. Alternatively, specify "cut" along with angle to specify the cut back distance of the chamfer.
// .
@ -1206,7 +1212,7 @@ function os_mask(mask, out=false, extra,check_valid, quality, offset_maxstep, of
// xscale(4)circle(r=6,$fn=64);
// Example: If you give a non-convex input you get a convex hull output
// right(50) linear_extrude(height=7) star(5,r=22,ir=13);
// convex_offset_extrude(bottom = os_chamfer(height=-2), top=os_chamfer(height=1), height=7)
// convex_offset_extrude(bottom = os_chamfer(height=-2), top=os_chamfer(height=1), height=7, $fn=32)
// star(5,r=22,ir=13);
function convex_offset_extrude(
height, h, l,
@ -1227,6 +1233,7 @@ module convex_offset_extrude(
convexity=10, thickness = 1/1024
) {
argspec = [
["for", ""],
["r",r],
["extra",extra],
["type","circle"],
@ -1308,16 +1315,16 @@ function _remove_undefined_vals(list) =
// Function&Module: offset_stroke()
// Usage: as module
// offset_stroke(path, [width], [rounded=], [chamfer=], [start=], [end=], [check_valid=], [quality=], [maxstep=], [closed=]);
// offset_stroke(path, [width], [rounded=], [chamfer=], [start=], [end=], [check_valid=], [quality=], [closed=]);
// Usage: as function
// path = offset_stroke(path, [width], closed=false, [rounded=], [chamfer=], [start=], [end=], [check_valid=], [quality=], [maxstep=]);
// region = offset_stroke(path, [width], closed=true, [rounded=], [chamfer=], [start=], [end=], [check_valid=], [quality=], [maxstep=]);
// path = offset_stroke(path, [width], closed=false, [rounded=], [chamfer=], [start=], [end=], [check_valid=], [quality=]);
// region = offset_stroke(path, [width], closed=true, [rounded=], [chamfer=], [start=], [end=], [check_valid=], [quality=]);
// Description:
// Uses `offset()` to compute a stroke for the input path. Unlike `stroke`, the result does not need to be
// centered on the input path. The corners can be rounded, pointed, or chamfered, and you can make the ends
// rounded, flat or pointed with the `start` and `end` parameters.
// .
// The `check_valid`, `quality` and `maxstep` parameters are passed through to `offset()`
// The `check_valid` and `quality` parameters are passed through to `offset()`
// .
// If `width` is a scalar then the output will be a centered stroke of the specified width. If width
// is a list of two values then those two values will define the stroke side positions relative to the center line, where
@ -1334,23 +1341,24 @@ function _remove_undefined_vals(list) =
// .
// More complex end treatments are available through parameter lists with helper functions to ease parameter passing. The parameter list
// keywords are
// - "for" : must appear first in the list and have the value "offset_stroke"
// - "type": the type of end treatment, one of "shifted_point", "roundover", or "flat"
// - "angle": relative angle (relative to the path)
// - "abs_angle": absolute angle (angle relative to x-axis)
// - "cut": cut distance for roundovers, a single value to round both corners identically or a list of two values for the two corners. Negative values round outward.
// - "k": curvature smoothness parameter for roundovers, default 0.75
// .
// Function helpers for defining ends, prefixed by "os" for offset_stroke.
// .
// os_flat(angle|absangle): specify a flat end either relative to the path or relative to the x-axis
// os_pointed(loc,dist): specify a pointed tip where the point is distance `loc` from the centerline (positive is the left direction as for offset), and `dist` is the distance from the path end to the point tip. The default value for `loc` is zero (the center). You must specify `dist` when using this option.
// os_round(cut,angle|absangle,k). Rounded ends with the specified cut distance, based on the specified angle or absolute angle. The `k` parameter is the smoothness parameter for continuous curvature rounding.
// Function helpers for defining ends, prefixed by "os" for offset_stroke, are:
// - os_flat(angle|absangle): specify a flat end either relative to the path or relative to the x-axis
// - os_pointed(dist, [loc]): specify a pointed tip where the point is distance `loc` from the centerline (positive is the left direction as for offset), and `dist` is the distance from the path end to the point tip. The default value for `loc` is zero (the center). You must specify `dist` when using this option.
// - os_round(cut, [angle|absangle], [k]). Rounded ends with the specified cut distance, based on the specified angle or absolute angle. The `k` parameter is the smoothness parameter for continuous curvature rounding.
// .
// Note that `offset_stroke()` will attempt to apply roundovers and angles at the ends even when it means deleting segments of the stroke, unlike round_corners which only works on a segment adjacent to a corner. If you specify an overly extreme angle it will fail to find an intersection with the stroke and display an error. When you specify an angle the end segment is rotated around the center of the stroke and the last segment of the stroke one one side is extended to the corner.
// .
// The $fn and $fs variables are used to determine the number of segments for rounding, while maxstep is used to determine the segments of `offset`. If you
// get the expected rounding along the path, decrease `maxstep` and if the curves created by `os_round()` are too coarse, adjust $fn or $fs.
//
// The `$fn` and `$fs` variables are used in the usual way to determine the number of segments for roundings produced by the offset
// invocations and roundings produced by the semi-circular "round" end treatment. The os_round() end treatment
// uses a bezier curve, and will produce segments of approximate length `$fs` or it will produce `$fn` segments.
// (This means that even a quarter circle will have `$fn` segments, unlike the usual case where it would have `$fn/4` segments.)
// Arguments:
// path = 2d path that defines the stroke
// width = width of the stroke, a scalar or a vector of 2 values giving the offset from the path. Default: 1
@ -1361,7 +1369,6 @@ function _remove_undefined_vals(list) =
// end = end treatment for the end of the stroke. See above for details. Default: "flat"
// check_valid = passed to offset(). Default: true
// quality = passed to offset(). Default: 1
// maxstep = passed to offset() to define number of points in the offset. Default: 0.1
// closed = true if the curve is closed, false otherwise. Default: false
//
// Example(2D): Basic examples illustrating flat, round, and pointed ends, on a finely sampled arc and a path made from 3 segments.
@ -1380,14 +1387,14 @@ function _remove_undefined_vals(list) =
// Example(2D): The effect of the `rounded` and `chamfer` options is most evident at sharp corners. This only affects the middle of the path, not the ends.
// sharppath = [[0,0], [1.5,5], [3,0]];
// xdistribute(spacing=5){
// offset_stroke(sharppath);
// offset_stroke(sharppath, $fn=16);
// offset_stroke(sharppath, rounded=false);
// offset_stroke(sharppath, rounded=false, chamfer=true);
// }
// Example(2D): When closed is enabled all the corners are affected by those options.
// sharppath = [[0,0], [1.5,5], [3,0]];
// xdistribute(spacing=5){
// offset_stroke(sharppath,closed=true);
// offset_stroke(sharppath,closed=true, $fn=16);
// offset_stroke(sharppath, rounded=false, closed=true);
// offset_stroke(sharppath, rounded=false, chamfer=true, closed=true);
// }
@ -1424,14 +1431,14 @@ function _remove_undefined_vals(list) =
// offset_stroke(path, width=2, rounded=false,start=os_round(cut=-1, abs_angle=90), end=os_round(cut=-0.5, abs_angle=0),$fn=36);
// right(10)
// offset_stroke(arc, width=2, rounded=false, start=os_round(cut=[-.75,-.2], angle=-45), end=os_round(cut=[-.2,.2], angle=20),$fn=36);
// Example(2D): Setting the width to a vector allows generation of a set of parallel strokes
// Example(2D): Setting the width to a vector allows you to offset the stroke. Here with successive increasing offsets we create a set of parallel strokes
// path = [[0,0],[4,4],[8,4],[2,9],[10,10]];
// for(i=[0:.25:2])
// offset_stroke(path, rounded=false,width = [i,i+.08]);
// Example(2D): Setting rounded=true in the above example makes a very big difference in the result.
// Example(2D): Setting rounded=true in the above example makes a very big difference in the result.
// path = [[0,0],[4,4],[8,4],[2,9],[10,10]];
// for(i=[0:.25:2])
// offset_stroke(path, rounded=true,width = [i,i+.08]);
// offset_stroke(path, rounded=true,width = [i,i+.08], $fn=36);
// Example(2D): In this example a spurious triangle appears. This results from overly enthusiastic validity checking. Turning validity checking off fixes it in this case.
// path = [[0,0],[4,4],[8,4],[2,9],[10,10]];
// offset_stroke(path, check_valid=true,rounded=false,width = [1.4, 1.5]);
@ -1443,17 +1450,18 @@ function _remove_undefined_vals(list) =
// translate([1,-0.25])
// offset_stroke(path, check_valid=false,rounded=false,width = [1.9, 2]);
// Example(2D): Self-intersecting paths are handled differently than with the `stroke()` module.
// $fn=16;
// path = turtle(["move",10,"left",144], repeat=4);
// stroke(path, closed=true);
// right(12)
// offset_stroke(path, width=1, closed=true);
function offset_stroke(path, width=1, rounded=true, start="flat", end="flat", check_valid=true, quality=1, maxstep=0.1, chamfer=false, closed=false) =
function offset_stroke(path, width=1, rounded=true, start="flat", end="flat", check_valid=true, quality=1, chamfer=false, closed=false) =
assert(is_path(path,2),"path is not a 2d path")
let(closedok = !closed || (is_undef(start) && is_undef(end)))
assert(closedok, "Parameters `start` and `end` not allowed with closed path")
let(
start = closed? [] : _parse_stroke_end(default(start,"flat")),
end = closed? [] : _parse_stroke_end(default(end,"flat")),
start = closed? [] : _parse_stroke_end(default(start,"flat"),"start"),
end = closed? [] : _parse_stroke_end(default(end,"flat"),"end"),
width = is_list(width)? reverse(sort(width)) : [1,-1]*width/2,
left_r = !rounded? undef : width[0],
left_delta = rounded? undef : width[0],
@ -1462,12 +1470,12 @@ function offset_stroke(path, width=1, rounded=true, start="flat", end="flat", ch
left_path = offset(
path, delta=left_delta, r=left_r, closed=closed,
check_valid=check_valid, quality=quality,
chamfer=chamfer, maxstep=maxstep
chamfer=chamfer
),
right_path = offset(
path, delta=right_delta, r=right_r, closed=closed,
check_valid=check_valid, quality=quality,
chamfer=chamfer, maxstep=maxstep
chamfer=chamfer
)
)
closed? [left_path, right_path] :
@ -1485,15 +1493,17 @@ function offset_stroke(path, width=1, rounded=true, start="flat", end="flat", ch
);
function os_pointed(loc=0,dist) =
function os_pointed(dist,loc=0) =
assert(is_def(dist), "Must specify `dist`")
[
"for", "offset_stroke",
"type", "shifted_point",
"loc",loc,
"dist",dist
];
function os_round(cut, angle, abs_angle, k) =
function os_round(cut, angle, abs_angle, k, r) =
assert(is_undef(r), "Radius not supported for os_round with offset_stroke. (Did you mean os_circle for offset_sweep?)")
let(
acount = num_defined([angle,abs_angle]),
use_angle = first_defined([angle,abs_angle,0])
@ -1501,6 +1511,7 @@ function os_round(cut, angle, abs_angle, k) =
assert(acount<2, "You must define only one of `angle` and `abs_angle`")
assert(is_def(cut), "Parameter `cut` not defined.")
[
"for", "offset_stroke",
"type", "roundover",
"angle", use_angle,
"absolute", is_def(abs_angle),
@ -1516,6 +1527,7 @@ function os_flat(angle, abs_angle) =
)
assert(acount<2, "You must define only one of `angle` and `abs_angle`")
[
"for", "offset_stroke",
"type", "flat",
"angle", use_angle,
"absolute", is_def(abs_angle)
@ -1531,14 +1543,17 @@ function angle_between_lines(line1,line2) =
angle;
function _parse_stroke_end(spec) =
function _parse_stroke_end(spec,name) =
is_string(spec)?
assert(
in_list(spec,["flat","round","pointed"]),
str("Unknown end string specification \"", spec,"\". Must be \"flat\", \"round\", or \"pointed\"")
)
[["type", spec]] :
struct_set([], spec);
assert(
in_list(spec,["flat","round","pointed"]),
str("Unknown \"",name,"\" string specification \"", spec,"\". Must be \"flat\", \"round\", or \"pointed\"")
)
[["type", spec]]
: let(
dummy = _struct_valid(spec,"offset_stroke",name)
)
struct_set([], spec);
function _stroke_end(width,left, right, spec) =
@ -1596,8 +1611,8 @@ function _stroke_end(width,left, right, spec) =
90-vector_angle([newright[1],newright[0],newleft[0]])/2,
jointleft = 8*cutleft/cos(leftangle)/(1+4*bez_k),
jointright = 8*cutright/cos(rightangle)/(1+4*bez_k),
pathcutleft = path_cut_points(newleft,abs(jointleft)),
pathcutright = path_cut_points(newright,abs(jointright)),
pathcutleft = _path_cut_points(newleft,abs(jointleft)),
pathcutright = _path_cut_points(newright,abs(jointright)),
leftdelete = intright? pathcutleft[1] : pathcutleft[1] + pathclip[1] -1,
rightdelete = intright? pathcutright[1] + pathclip[1] -1 : pathcutright[1],
leftcorner = line_intersection([pathcutleft[0], newleft[pathcutleft[1]]], [newright[0],newleft[0]]),
@ -1626,21 +1641,21 @@ function _stroke_end(width,left, right, spec) =
// returns [intersection_pt, index of first point in path after the intersection]
function _path_line_intersection(path, line, ind=0) =
ind==len(path)-1 ? undef :
let(intersect=line_segment_intersection(line, select(path,ind,ind+1)))
let(intersect=line_intersection(line, select(path,ind,ind+1),LINE,SEGMENT))
// If it intersects the segment excluding it's final point, then we're done
// The final point is treated as part of the next segment
is_def(intersect) && intersect != path[ind+1]?
[intersect, ind+1] :
_path_line_intersection(path, line, ind+1);
module offset_stroke(path, width=1, rounded=true, start, end, check_valid=true, quality=1, maxstep=0.1, chamfer=false, closed=false)
module offset_stroke(path, width=1, rounded=true, start, end, check_valid=true, quality=1, chamfer=false, closed=false)
{
no_children($children);
result = offset_stroke(
path, width=width, rounded=rounded,
start=start, end=end,
check_valid=check_valid, quality=quality,
maxstep=maxstep, chamfer=chamfer,
chamfer=chamfer,
closed=closed
);
if (closed) {
@ -1679,8 +1694,10 @@ function _rp_compute_patches(top, bot, rtop, rsides, ktop, ksides, concave) =
let(
prev_corner = prev_offset + abs(rtop_in)*in_prev,
next_corner = next_offset + abs(rtop_in)*in_next,
prev_degenerate = is_undef(ray_intersection(path2d([far_corner, far_corner+prev]), path2d([prev_offset, prev_offset+in_prev]))),
next_degenerate = is_undef(ray_intersection(path2d([far_corner, far_corner+next]), path2d([next_offset, next_offset+in_next])))
prev_degenerate = is_undef(line_intersection(path2d([far_corner, far_corner+prev]),
path2d([prev_offset, prev_offset+in_prev]),RAY,RAY)),
next_degenerate = is_undef(line_intersection(path2d([far_corner, far_corner+next]),
path2d([next_offset, next_offset+in_next]),RAY,RAY))
)
[ prev_degenerate ? far_corner : prev_corner,
far_corner,
@ -1832,8 +1849,8 @@ function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_b
let(
// Determine which points are concave by making bottom 2d if necessary
bot_proj = len(bottom[0])==2 ? bottom : project_plane(select(bottom,0,2),bottom),
bottom_sign = polygon_is_clockwise(bot_proj) ? 1 : -1,
concave = [for(i=[0:N-1]) bottom_sign*sign(point_left_of_line2d(select(bot_proj,i+1), select(bot_proj, i-1,i)))>0],
bottom_sign = is_polygon_clockwise(bot_proj) ? 1 : -1,
concave = [for(i=[0:N-1]) bottom_sign*sign(_point_left_of_line2d(select(bot_proj,i+1), select(bot_proj, i-1,i)))>0],
top = is_undef(top) ? path3d(bottom,height/2) :
len(top[0])==2 ? path3d(top,height/2) :
top,
@ -1848,16 +1865,16 @@ function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_b
assert(jsvecok || jssingleok,
str("Argument joint_sides is invalid. All entries must be nonnegative, and it must be a number, 2-vector, or a length ",N," list those."))
assert(is_num(k_sides) || is_vector(k_sides,N), str("Curvature parameter k_sides must be a number or length ",N," vector"))
assert(coplanar(bottom))
assert(coplanar(top))
assert(is_coplanar(bottom))
assert(is_coplanar(top))
assert(!is_num(k_sides) || (k_sides>=0 && k_sides<=1), "Curvature parameter k_sides must be in interval [0,1]")
let(
non_coplanar=[for(i=[0:N-1]) if (!coplanar(concat(select(top,i,i+1), select(bottom,i,i+1)))) [i,(i+1)%N]],
non_coplanar=[for(i=[0:N-1]) if (!is_coplanar(concat(select(top,i,i+1), select(bottom,i,i+1)))) [i,(i+1)%N]],
k_sides_vec = is_num(k_sides) ? repeat(k_sides, N) : k_sides,
kbad = [for(i=[0:N-1]) if (k_sides_vec[i]<0 || k_sides_vec[i]>1) i],
joint_sides_vec = jssingleok ? repeat(joint_sides,N) : joint_sides,
top_collinear = [for(i=[0:N-1]) if (collinear(select(top,i-1,i+1))) i],
bot_collinear = [for(i=[0:N-1]) if (collinear(select(bottom,i-1,i+1))) i]
top_collinear = [for(i=[0:N-1]) if (is_collinear(select(top,i-1,i+1))) i],
bot_collinear = [for(i=[0:N-1]) if (is_collinear(select(bottom,i-1,i+1))) i]
)
assert(non_coplanar==[], str("Side faces are non-coplanar at edges: ",non_coplanar))
assert(top_collinear==[], str("Top has collinear or duplicated points at indices: ",top_collinear))
@ -1923,14 +1940,14 @@ function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_b
vline = concat(select(subindex(top_patch[i],j),2,4),
select(subindex(bot_patch[i],j),2,4))
)
if (!collinear(vline)) [i,j]],
if (!is_collinear(vline)) [i,j]],
//verify horiz edges
verify_horiz=[for(i=[0:N-1], j=[0:4])
let(
hline_top = concat(select(top_patch[i][j],2,4), select(select(top_patch, i+1)[j],0,2)),
hline_bot = concat(select(bot_patch[i][j],2,4), select(select(bot_patch, i+1)[j],0,2))
)
if (!collinear(hline_top) || !collinear(hline_bot)) [i,j]]
if (!is_collinear(hline_top) || !is_collinear(hline_bot)) [i,j]]
)
assert(debug || top_intersections==[],
"Roundovers interfere with each other on top face: either input is self intersecting or top joint length is too large")
@ -1941,7 +1958,8 @@ function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_b
vnf = vnf_merge([ each subindex(top_samples,0),
each subindex(bot_samples,0),
for(pts=edge_points) vnf_vertex_array(pts),
vnf_triangulate(vnf_add_faces(EMPTY_VNF,faces))
debug ? vnf_add_faces(EMPTY_VNF,faces)
: vnf_triangulate(vnf_add_faces(EMPTY_VNF,faces))
])
)
debug ? [concat(top_patch, bot_patch), vnf] : vnf;

View file

@ -837,18 +837,18 @@ module screw_head(screw_info,details=false) {
// anchor = anchor relative to the shaft of the screw
// anchor_head = anchor relative to the screw head
// Example(Med): Selected UTS (English) screws
// $fn=32;
// xdistribute(spacing=8){
// screw("#6", length=12);
// screw("#6-32", head="button", drive="torx",length=12);
// screw("#6-32,3/4", head="hex");
// screw("#6", thread="fine", head="fillister",length=12, drive="phillips");
// screw("#6", head="flat small",length=12,drive="slot");
// screw("#6-32", head="flat large", length=12, drive="torx");
// screw("#6-32", head="flat undercut",length=12);
// screw("#6-24", head="socket",length=12); // Non-standard threading
// screw("#6-32", drive="hex", drive_size=1.5, length=12);
// }
$fn=32;
xdistribute(spacing=8){
screw("#6", length=12);
screw("#6-32", head="button", drive="torx",length=12);
screw("#6-32,3/4", head="hex");
screw("#6", thread="fine", head="fillister",length=12, drive="phillips");
screw("#6", head="flat small",length=12,drive="slot");
screw("#6-32", head="flat large", length=12, drive="torx");
screw("#6-32", head="flat undercut",length=12);
screw("#6-24", head="socket",length=12); // Non-standard threading
screw("#6-32", drive="hex", drive_size=1.5, length=12);
}
// Example(Med): A few examples of ISO (metric) screws
// $fn=32;
// xdistribute(spacing=8){
@ -1243,38 +1243,30 @@ function thread_specification(screw_spec, internal=false, tolerance=undef) =
function _thread_profile(thread) =
let(
pitch = struct_val(thread,"pitch"),
basicrad = struct_val(thread,"basic")/2,
meanpitchrad = mean(struct_val(thread,"d_pitch"))/2,
meanminorrad = mean(struct_val(thread,"d_minor"))/2,
meanmajorrad = mean(struct_val(thread,"d_major"))/2,
depth = (meanmajorrad-meanminorrad)/pitch,
crestwidth = (pitch/2 - 2*(meanmajorrad-meanpitchrad)/sqrt(3))/pitch
depth = meanmajorrad-meanminorrad,
crestwidth = pitch/2 - 2*(meanmajorrad-meanpitchrad)/sqrt(3)
)
[
[-depth/sqrt(3)-crestwidth/2, -depth],
[ -crestwidth/2, 0],
[ crestwidth/2, 0],
[ depth/sqrt(3)+crestwidth/2, -depth]
]/pitch;
/* Old non-centered profile
[
[-1/2,-depth],
[depth/sqrt(3)-1/2,0],
[depth/sqrt(3)+crestwidth-1/2, 0],
[crestwidth + 2*depth/sqrt(3)-1/2,-depth]
];
function _thread_profile_e(thread) =
let(
pitch = struct_val(thread,"pitch"),
basicrad = struct_val(thread,"basic")/2,
meanpitchrad = mean(struct_val(thread,"d_pitch"))/2,
meanminorrad = mean(struct_val(thread,"d_minor"))/2,
meanmajorrad = mean(struct_val(thread,"d_major"))/2,
depth = (meanmajorrad-meanminorrad)/pitch,
crestwidth = (pitch/2 - 2*(meanmajorrad-meanpitchrad)/sqrt(3))/pitch
)
[
[-1/2,-1], // -1 instead of -depth?
[depth/sqrt(3)-1/2,0],
[depth/sqrt(3)+crestwidth-1/2, 0],
[crestwidth + 2*depth/sqrt(3)-1/2,-1]
];
]
;
*/
module _rod(spec, length, tolerance, orient=UP, spin=0, anchor=CENTER)
@ -1283,11 +1275,19 @@ module _rod(spec, length, tolerance, orient=UP, spin=0, anchor=CENTER)
echo(d_major_mean = mean(struct_val(threadspec, "d_major")));
echo(bolt_profile=_thread_profile(threadspec));
trapezoidal_threaded_rod( d=mean(struct_val(threadspec, "d_major")),
threaded_rod([mean(struct_val(threadspec, "d_minor")),
mean(struct_val(threadspec, "d_pitch")),
mean(struct_val(threadspec, "d_major"))],
pitch = struct_val(threadspec, "pitch"),
l=length, left_handed=false,
bevel=false, orient=orient, anchor=anchor, spin=spin);
/*
generic_threaded_rod( d=mean(struct_val(threadspec, "d_major")),
l=length,
pitch = struct_val(threadspec, "pitch"),
profile = _thread_profile(threadspec),left_handed=false,
bevel=false, orient=orient, anchor=anchor, spin=spin);
*/
}
@ -1352,7 +1352,7 @@ module nut(name, diameter, thickness, thread="coarse", oversize=0, spec, toleran
threadspec = thread_specification(spec, internal=true, tolerance=tolerance);
echo(threadspec=threadspec,"for nut threads");
echo(nut_minor_diam = mean(struct_val(threadspec,"d_minor")));
trapezoidal_threaded_nut(
generic_threaded_nut(
od=diameter, id=mean(struct_val(threadspec, "d_major")), h=thickness,
pitch=struct_val(threadspec, "pitch"),
profile=_thread_profile(threadspec),

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,84 @@
//////////////////////////////////////////////////////////////////////
// LibFile: shapes.scad
// LibFile: shapes3d.scad
// Common useful shapes and structured objects.
// Includes:
// include <BOSL2/std.scad>
//////////////////////////////////////////////////////////////////////
// Section: Cuboids
// Section: Cuboids, Prismoids and Pyramids
// Function&Module: cube()
// Topics: Shapes (3D), Attachable, VNF Generators
// Usage: As Module
// cube(size, [center], ...);
// Usage: With Attachments
// cube(size, [center], ...) { attachments }
// Usage: As Function
// vnf = cube(size, [center], ...);
// See Also: cuboid(), prismoid()
// Description:
// Creates a 3D cubic object with support for anchoring and attachments.
// This can be used as a drop-in replacement for the built-in `cube()` module.
// When called as a function, returns a [VNF](vnf.scad) for a cube.
// Arguments:
// size = The size of the cube.
// center = If given, overrides `anchor`. A true value sets `anchor=CENTER`, false sets `anchor=ALLNEG`.
// ---
// 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`
// Example: Simple cube.
// cube(40);
// Example: Rectangular cube.
// cube([20,40,50]);
// Example: Anchoring.
// cube([20,40,50], anchor=BOTTOM+FRONT);
// Example: Spin.
// cube([20,40,50], anchor=BOTTOM+FRONT, spin=30);
// Example: Orientation.
// cube([20,40,50], anchor=BOTTOM+FRONT, spin=30, orient=FWD);
// Example: Standard Connectors.
// cube(40, center=true) show_anchors();
// Example: Called as Function
// vnf = cube([20,40,50]);
// vnf_polyhedron(vnf);
module cube(size=1, center, anchor, spin=0, orient=UP)
{
anchor = get_anchor(anchor, center, ALLNEG, ALLNEG);
size = scalar_vec3(size);
attachable(anchor,spin,orient, size=size) {
if (size.z > 0) {
linear_extrude(height=size.z, center=true, convexity=2) {
square([size.x,size.y], center=true);
}
}
children();
}
}
function cube(size=1, center, anchor, spin=0, orient=UP) =
let(
siz = scalar_vec3(size),
anchor = get_anchor(anchor, center, ALLNEG, ALLNEG),
unscaled = [
[-1,-1,-1],[1,-1,-1],[1,1,-1],[-1,1,-1],
[-1,-1, 1],[1,-1, 1],[1,1, 1],[-1,1, 1],
]/2,
verts = is_num(size)? unscaled * size :
is_vector(size,3)? [for (p=unscaled) v_mul(p,size)] :
assert(is_num(size) || is_vector(size,3)),
faces = [
[0,1,2], [0,2,3], //BOTTOM
[0,4,5], [0,5,1], //FRONT
[1,5,6], [1,6,2], //RIGHT
[2,6,7], [2,7,3], //BACK
[3,7,4], [3,4,0], //LEFT
[6,4,7], [6,5,4] //TOP
]
) [reorient(anchor,spin,orient, size=siz, p=verts), faces];
// Module: cuboid()
//
@ -107,7 +179,7 @@ module cuboid(
orient=UP
) {
module corner_shape(corner) {
e = corner_edges(edges, corner);
e = _corner_edges(edges, corner);
cnt = sum(e);
r = first_defined([chamfer, rounding, 0]);
c = [min(r,size.x/2), min(r,size.y/2), min(r,size.z/2)];
@ -220,7 +292,7 @@ module cuboid(
// Add multi-edge corners.
if (trimcorners) {
for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
ce = corner_edges(edges, [xa,ya,za]);
ce = _corner_edges(edges, [xa,ya,za]);
if (ce.x + ce.y > 1) {
translate(v_mul([xa,ya,za]/2, size+[ach-0.01,ach-0.01,-ach])) {
cube([ach+0.01,ach+0.01,ach], center=true);
@ -307,7 +379,7 @@ module cuboid(
// Add multi-edge corners.
if (trimcorners) {
for (za=[-1,1], ya=[-1,1], xa=[-1,1]) {
ce = corner_edges(edges, [xa,ya,za]);
ce = _corner_edges(edges, [xa,ya,za]);
if (ce.x + ce.y > 1) {
translate(v_mul([xa,ya,za]/2, size+[ard-0.01,ard-0.01,-ard])) {
cube([ard+0.01,ard+0.01,ard], center=true);
@ -365,9 +437,6 @@ function cuboid(
// Section: Prismoids
// Function&Module: prismoid()
//
// Usage: Typical Prismoids
@ -804,7 +873,104 @@ function right_triangle(size=[1,1,1], center, anchor, spin=0, orient=UP) =
no_function("right_triangle");
// Section: Cylindroids
// Section: Cylinders
// Function&Module: cylinder()
// Topics: Shapes (3D), Attachable, VNF Generators
// Usage: As Module
// cylinder(h, r=/d=, [center=], ...);
// cylinder(h, r1/d1=, r2/d2=, [center=], ...);
// Usage: With Attachments
// cylinder(h, r=/d=, [center=]) {attachments}
// Usage: As Function
// vnf = cylinder(h, r=/d=, [center=], ...);
// vnf = cylinder(h, r1/d1=, r2/d2=, [center=], ...);
// See Also: cyl()
// Description:
// Creates a 3D cylinder or conic object with support for anchoring and attachments.
// This can be used as a drop-in replacement for the built-in `cylinder()` module.
// When called as a function, returns a [VNF](vnf.scad) for a cylinder.
// Arguments:
// l / h = The height of the cylinder.
// r1 = The bottom radius of the cylinder. (Before orientation.)
// r2 = The top radius of the cylinder. (Before orientation.)
// center = If given, overrides `anchor`. A true value sets `anchor=CENTER`, false sets `anchor=BOTTOM`.
// ---
// d1 = The bottom diameter of the cylinder. (Before orientation.)
// d2 = The top diameter of the cylinder. (Before orientation.)
// r = The radius of the cylinder.
// d = The diameter of the cylinder.
// 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`
// Example: By Radius
// xdistribute(30) {
// cylinder(h=40, r=10);
// cylinder(h=40, r1=10, r2=5);
// }
// Example: By Diameter
// xdistribute(30) {
// cylinder(h=40, d=25);
// cylinder(h=40, d1=25, d2=10);
// }
// Example(Med): Anchoring
// cylinder(h=40, r1=10, r2=5, anchor=BOTTOM+FRONT);
// Example(Med): Spin
// cylinder(h=40, r1=10, r2=5, anchor=BOTTOM+FRONT, spin=45);
// Example(Med): Orient
// cylinder(h=40, r1=10, r2=5, anchor=BOTTOM+FRONT, spin=45, orient=FWD);
// Example(Big): Standard Connectors
// xdistribute(40) {
// cylinder(h=30, d=25) show_anchors();
// cylinder(h=30, d1=25, d2=10) show_anchors();
// }
module cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP)
{
anchor = get_anchor(anchor, center, BOTTOM, BOTTOM);
r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1);
r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1);
l = first_defined([h, l, 1]);
sides = segs(max(r1,r2));
attachable(anchor,spin,orient, r1=r1, r2=r2, l=l) {
if (r1 > r2) {
if (l > 0) {
linear_extrude(height=l, center=true, convexity=2, scale=r2/r1) {
circle(r=r1);
}
}
} else {
zflip() {
if (l > 0) {
linear_extrude(height=l, center=true, convexity=2, scale=r1/r2) {
circle(r=r2);
}
}
}
}
children();
}
}
function cylinder(h, r1, r2, center, l, r, d, d1, d2, anchor, spin=0, orient=UP) =
let(
anchor = get_anchor(anchor, center, BOTTOM, BOTTOM),
r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=1),
r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=1),
l = first_defined([h, l, 1]),
sides = segs(max(r1,r2)),
verts = [
for (i=[0:1:sides-1]) let(a=360*(1-i/sides)) [r1*cos(a),r1*sin(a),-l/2],
for (i=[0:1:sides-1]) let(a=360*(1-i/sides)) [r2*cos(a),r2*sin(a), l/2],
],
faces = [
[for (i=[0:1:sides-1]) sides-1-i],
for (i=[0:1:sides-1]) [i, ((i+1)%sides)+sides, i+sides],
for (i=[0:1:sides-1]) [i, (i+1)%sides, ((i+1)%sides)+sides],
[for (i=[0:1:sides-1]) sides+i]
]
) [reorient(anchor,spin,orient, l=l, r1=r1, r2=r2, p=verts), faces];
// Module: cyl()
@ -1229,106 +1395,64 @@ module tube(
}
// Module: torus()
//
// Usage: Typical
// torus(r_maj|d_maj, r_min|d_min, [center], ...);
// torus(or|od, ir|id, ...);
// torus(r_maj|d_maj, or|od, ...);
// torus(r_maj|d_maj, ir|id, ...);
// torus(r_min|d_min, or|od, ...);
// torus(r_min|d_min, ir|id, ...);
// Usage: Attaching Children
// torus(or|od, ir|id, ...) [attachments];
//
// Section: Other Round Objects
// Function&Module: sphere()
// Topics: Shapes (3D), Attachable, VNF Generators
// Usage: As Module
// sphere(r|d=, [circum=], [style=], ...);
// Usage: With Attachments
// sphere(r|d=, ...) { attachments }
// Usage: As Function
// vnf = sphere(r|d=, [circum=], [style=], ...);
// See Also: spheroid()
// Description:
// Creates a torus shape.
//
// Figure(2D,Med):
// module text3d(t,size=8) text(text=t,size=size,font="Helvetica", halign="center",valign="center");
// module dashcirc(r,start=0,angle=359.9,dashlen=5) let(step=360*dashlen/(2*r*PI)) for(a=[start:step:start+angle]) stroke(arc(r=r,start=a,angle=step/2));
// r = 75; r2 = 30;
// down(r2+0.1) #torus(r_maj=r, r_min=r2, $fn=72);
// color("blue") linear_extrude(height=0.01) {
// dashcirc(r=r,start=15,angle=45);
// dashcirc(r=r-r2, start=90+15, angle=60);
// dashcirc(r=r+r2, start=180+45, angle=30);
// dashcirc(r=r+r2, start=15, angle=30);
// }
// rot(240) color("blue") linear_extrude(height=0.01) {
// stroke([[0,0],[r+r2,0]], endcaps="arrow2",width=2);
// right(r) fwd(9) rot(-240) text3d("or",size=10);
// }
// rot(135) color("blue") linear_extrude(height=0.01) {
// stroke([[0,0],[r-r2,0]], endcaps="arrow2",width=2);
// right((r-r2)/2) back(8) rot(-135) text3d("ir",size=10);
// }
// rot(45) color("blue") linear_extrude(height=0.01) {
// stroke([[0,0],[r,0]], endcaps="arrow2",width=2);
// right(r/2) back(8) text3d("r_maj",size=9);
// }
// rot(30) color("blue") linear_extrude(height=0.01) {
// stroke([[r,0],[r+r2,0]], endcaps="arrow2",width=2);
// right(r+r2/2) fwd(8) text3d("r_min",size=7);
// }
//
// Creates a sphere object, with support for anchoring and attachments.
// This is a drop-in replacement for the built-in `sphere()` module.
// When called as a function, returns a [VNF](vnf.scad) for a sphere.
// Arguments:
// r_maj = major radius of torus ring. (use with 'r_min', or 'd_min')
// r_min = minor radius of torus ring. (use with 'r_maj', or 'd_maj')
// center = If given, overrides `anchor`. A true value sets `anchor=CENTER`, false sets `anchor=DOWN`.
// r = Radius of the sphere.
// ---
// d_maj = major diameter of torus ring. (use with 'r_min', or 'd_min')
// d_min = minor diameter of torus ring. (use with 'r_maj', or 'd_maj')
// or = outer radius of the torus. (use with 'ir', or 'id')
// ir = inside radius of the torus. (use with 'or', or 'od')
// od = outer diameter of the torus. (use with 'ir' or 'id')
// id = inside diameter of the torus. (use with 'or' or 'od')
// d = Diameter of the sphere.
// circum = If true, the sphere is made large enough to circumscribe the sphere of the ideal side. Otherwise inscribes. Default: false (inscribes)
// style = The style of the sphere's construction. One of "orig", "aligned", "stagger", "octa", or "icosa". Default: "orig"
// 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`
//
// Example:
// // These all produce the same torus.
// torus(r_maj=22.5, r_min=7.5);
// torus(d_maj=45, d_min=15);
// torus(or=30, ir=15);
// torus(od=60, id=30);
// torus(d_maj=45, id=30);
// torus(d_maj=45, od=60);
// torus(d_min=15, id=30);
// torus(d_min=15, od=60);
// Example: By Radius
// sphere(r=50);
// Example: By Diameter
// sphere(d=100);
// Example: style="orig"
// sphere(d=100, style="orig", $fn=10);
// Example: style="aligned"
// sphere(d=100, style="aligned", $fn=10);
// Example: style="stagger"
// sphere(d=100, style="stagger", $fn=10);
// Example: style="icosa"
// sphere(d=100, style="icosa", $fn=10);
// // In "icosa" style, $fn is quantized
// // to the nearest multiple of 5.
// Example: Anchoring
// sphere(d=100, anchor=FRONT);
// Example: Spin
// sphere(d=100, anchor=FRONT, spin=45);
// Example: Orientation
// sphere(d=100, anchor=FRONT, spin=45, orient=FWD);
// Example: Standard Connectors
// torus(od=60, id=30) show_anchors();
module torus(
r_maj, r_min, center,
d_maj, d_min,
or, od, ir, id,
anchor, spin=0, orient=UP
) {
_or = get_radius(r=or, d=od, dflt=undef);
_ir = get_radius(r=ir, d=id, dflt=undef);
_r_maj = get_radius(r=r_maj, d=d_maj, dflt=undef);
_r_min = get_radius(r=r_min, d=d_min, dflt=undef);
majrad = is_finite(_r_maj)? _r_maj :
is_finite(_ir) && is_finite(_or)? (_or + _ir)/2 :
is_finite(_ir) && is_finite(_r_min)? (_ir + _r_min) :
is_finite(_or) && is_finite(_r_min)? (_or - _r_min) :
assert(false, "Bad Parameters");
minrad = is_finite(_r_min)? _r_min :
is_finite(_ir)? (majrad - _ir) :
is_finite(_or)? (_or - majrad) :
assert(false, "Bad Parameters");
anchor = get_anchor(anchor, center, BOT, CENTER);
attachable(anchor,spin,orient, r=(majrad+minrad), l=minrad*2) {
rotate_extrude(convexity=4) {
right(majrad) circle(r=minrad);
}
children();
}
}
// sphere(d=50) show_anchors();
// Example: Called as Function
// vnf = sphere(d=100, style="icosa");
// vnf_polyhedron(vnf);
module sphere(r, d, circum=false, style="orig", anchor=CENTER, spin=0, orient=UP)
spheroid(r=r, d=d, circum=circum, style=style, anchor=anchor, spin=spin, orient=orient) children();
// Section: Spheroid
function sphere(r, d, circum=false, style="orig", anchor=CENTER, spin=0, orient=UP) =
spheroid(r=r, d=d, circum=circum, style=style, anchor=anchor, spin=spin, orient=orient);
// Function&Module: spheroid()
@ -1550,7 +1674,102 @@ function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, or
// Section: 3D Printing Shapes
// Module: torus()
//
// Usage: Typical
// torus(r_maj|d_maj, r_min|d_min, [center], ...);
// torus(or|od, ir|id, ...);
// torus(r_maj|d_maj, or|od, ...);
// torus(r_maj|d_maj, ir|id, ...);
// torus(r_min|d_min, or|od, ...);
// torus(r_min|d_min, ir|id, ...);
// Usage: Attaching Children
// torus(or|od, ir|id, ...) [attachments];
//
// Description:
// Creates a torus shape.
//
// Figure(2D,Med):
// module text3d(t,size=8) text(text=t,size=size,font="Helvetica", halign="center",valign="center");
// module dashcirc(r,start=0,angle=359.9,dashlen=5) let(step=360*dashlen/(2*r*PI)) for(a=[start:step:start+angle]) stroke(arc(r=r,start=a,angle=step/2));
// r = 75; r2 = 30;
// down(r2+0.1) #torus(r_maj=r, r_min=r2, $fn=72);
// color("blue") linear_extrude(height=0.01) {
// dashcirc(r=r,start=15,angle=45);
// dashcirc(r=r-r2, start=90+15, angle=60);
// dashcirc(r=r+r2, start=180+45, angle=30);
// dashcirc(r=r+r2, start=15, angle=30);
// }
// rot(240) color("blue") linear_extrude(height=0.01) {
// stroke([[0,0],[r+r2,0]], endcaps="arrow2",width=2);
// right(r) fwd(9) rot(-240) text3d("or",size=10);
// }
// rot(135) color("blue") linear_extrude(height=0.01) {
// stroke([[0,0],[r-r2,0]], endcaps="arrow2",width=2);
// right((r-r2)/2) back(8) rot(-135) text3d("ir",size=10);
// }
// rot(45) color("blue") linear_extrude(height=0.01) {
// stroke([[0,0],[r,0]], endcaps="arrow2",width=2);
// right(r/2) back(8) text3d("r_maj",size=9);
// }
// rot(30) color("blue") linear_extrude(height=0.01) {
// stroke([[r,0],[r+r2,0]], endcaps="arrow2",width=2);
// right(r+r2/2) fwd(8) text3d("r_min",size=7);
// }
//
// Arguments:
// r_maj = major radius of torus ring. (use with 'r_min', or 'd_min')
// r_min = minor radius of torus ring. (use with 'r_maj', or 'd_maj')
// center = If given, overrides `anchor`. A true value sets `anchor=CENTER`, false sets `anchor=DOWN`.
// ---
// d_maj = major diameter of torus ring. (use with 'r_min', or 'd_min')
// d_min = minor diameter of torus ring. (use with 'r_maj', or 'd_maj')
// or = outer radius of the torus. (use with 'ir', or 'id')
// ir = inside radius of the torus. (use with 'or', or 'od')
// od = outer diameter of the torus. (use with 'ir' or 'id')
// id = inside diameter of the torus. (use with 'or' or 'od')
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP`
//
// Example:
// // These all produce the same torus.
// torus(r_maj=22.5, r_min=7.5);
// torus(d_maj=45, d_min=15);
// torus(or=30, ir=15);
// torus(od=60, id=30);
// torus(d_maj=45, id=30);
// torus(d_maj=45, od=60);
// torus(d_min=15, id=30);
// torus(d_min=15, od=60);
// Example: Standard Connectors
// torus(od=60, id=30) show_anchors();
module torus(
r_maj, r_min, center,
d_maj, d_min,
or, od, ir, id,
anchor, spin=0, orient=UP
) {
_or = get_radius(r=or, d=od, dflt=undef);
_ir = get_radius(r=ir, d=id, dflt=undef);
_r_maj = get_radius(r=r_maj, d=d_maj, dflt=undef);
_r_min = get_radius(r=r_min, d=d_min, dflt=undef);
majrad = is_finite(_r_maj)? _r_maj :
is_finite(_ir) && is_finite(_or)? (_or + _ir)/2 :
is_finite(_ir) && is_finite(_r_min)? (_ir + _r_min) :
is_finite(_or) && is_finite(_r_min)? (_or - _r_min) :
assert(false, "Bad Parameters");
minrad = is_finite(_r_min)? _r_min :
is_finite(_ir)? (majrad - _ir) :
is_finite(_or)? (_or - majrad) :
assert(false, "Bad Parameters");
anchor = get_anchor(anchor, center, BOT, CENTER);
attachable(anchor,spin,orient, r=(majrad+minrad), l=minrad*2) {
rotate_extrude(convexity=4) {
right(majrad) circle(r=minrad);
}
children();
}
}
// Module: teardrop()
@ -1614,9 +1833,9 @@ module teardrop(h, r, ang=45, cap_h, r1, r2, d, d1, d2, cap_h1, cap_h2, l, ancho
cap_h2 = min(first_defined([cap_h2, cap_h, tip_y2]), tip_y2);
capvec = unit([0, cap_h1-cap_h2, l]);
anchors = [
anchorpt("cap", [0,0,(cap_h1+cap_h2)/2], capvec),
anchorpt("cap_fwd", [0,-l/2,cap_h1], unit((capvec+FWD)/2)),
anchorpt("cap_back", [0,+l/2,cap_h2], unit((capvec+BACK)/2), 180),
named_anchor("cap", [0,0,(cap_h1+cap_h2)/2], capvec),
named_anchor("cap_fwd", [0,-l/2,cap_h1], unit((capvec+FWD)/2)),
named_anchor("cap_back", [0,+l/2,cap_h2], unit((capvec+BACK)/2), 180),
];
attachable(anchor,spin,orient, r1=r1, r2=r2, l=l, axis=BACK, anchors=anchors) {
rot(from=UP,to=FWD) {
@ -1697,6 +1916,271 @@ module onion(r, ang=45, cap_h, d, anchor=CENTER, spin=0, orient=UP)
}
// Section: Text
// Module: atext()
// Topics: Attachments, Text
// Usage:
// atext(text, [h], [size], [font]);
// Description:
// Creates a 3D text block that can be attached to other attachable objects.
// NOTE: This cannot have children attached to it.
// Arguments:
// text = The text string to instantiate as an object.
// h = The height to which the text should be extruded. Default: 1
// size = The font size used to create the text block. Default: 10
// font = The name of the font used to create the text block. Default: "Courier"
// ---
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `"baseline"`
// spin = Rotate this many degrees around the Z axis. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards. See [orient](attachments.scad#orient). Default: `UP`
// See Also: attachable()
// Extra Anchors:
// "baseline" = Anchors at the baseline of the text, at the start of the string.
// str("baseline",VECTOR) = Anchors at the baseline of the text, modified by the X and Z components of the appended vector.
// Examples:
// atext("Foobar", h=3, size=10);
// atext("Foobar", h=2, size=12, font="Helvetica");
// atext("Foobar", h=2, anchor=CENTER);
// atext("Foobar", h=2, anchor=str("baseline",CENTER));
// atext("Foobar", h=2, anchor=str("baseline",BOTTOM+RIGHT));
// Example: Using line_of() distributor
// txt = "This is the string.";
// line_of(spacing=[10,-5],n=len(txt))
// atext(txt[$idx], size=10, anchor=CENTER);
// Example: Using arc_of() distributor
// txt = "This is the string";
// arc_of(r=50, n=len(txt), sa=0, ea=180)
// atext(select(txt,-1-$idx), size=10, anchor=str("baseline",CENTER), spin=-90);
module atext(text, h=1, size=9, font="Courier", anchor="baseline", spin=0, orient=UP) {
no_children($children);
dummy1 =
assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Got: ",anchor))
assert(is_undef(spin) || is_vector(spin,3) || is_num(spin), str("Got: ",spin))
assert(is_undef(orient) || is_vector(orient,3), str("Got: ",orient));
anchor = default(anchor, CENTER);
spin = default(spin, 0);
orient = default(orient, UP);
geom = _attach_geom(size=[size,size,h]);
anch = !any([for (c=anchor) c=="["])? anchor :
let(
parts = str_split(str_split(str_split(anchor,"]")[0],"[")[1],","),
vec = [for (p=parts) str_float(str_strip_leading(p," "))]
) vec;
ha = anchor=="baseline"? "left" :
anchor==anch && is_string(anchor)? "center" :
anch.x<0? "left" :
anch.x>0? "right" :
"center";
va = starts_with(anchor,"baseline")? "baseline" :
anchor==anch && is_string(anchor)? "center" :
anch.y<0? "bottom" :
anch.y>0? "top" :
"center";
base = anchor=="baseline"? CENTER :
anchor==anch && is_string(anchor)? CENTER :
anch.z<0? BOTTOM :
anch.z>0? TOP :
CENTER;
m = _attach_transform(base,spin,orient,geom);
multmatrix(m) {
$parent_anchor = anchor;
$parent_spin = spin;
$parent_orient = orient;
$parent_geom = geom;
$parent_size = _attach_geom_size(geom);
$attach_to = undef;
do_show = _attachment_is_shown($tags);
if (do_show) {
if (is_undef($color)) {
linear_extrude(height=h, center=true)
text(text=text, size=size, halign=ha, valign=va, font=font);
} else color($color) {
$color = undef;
linear_extrude(height=h, center=true)
text(text=text, size=size, halign=ha, valign=va, font=font);
}
}
}
}
// This could be replaced with _cut_to_seg_u_form
function _cut_interp(pathcut, path, data) =
[for(entry=pathcut)
let(
a = path[entry[1]-1],
b = path[entry[1]],
c = entry[0],
i = max_index(v_abs(b-a)),
factor = (c[i]-a[i])/(b[i]-a[i])
)
(1-factor)*data[entry[1]-1]+ factor * data[entry[1]]
];
// Module: path_text()
// Usage:
// path_text(path, text, [size], [thickness], [font], [lettersize], [offset], [reverse], [normal], [top], [textmetrics])
// Description:
// Place the text letter by letter onto the specified path using textmetrics (if available and requested)
// or user specified letter spacing. The path can be 2D or 3D. In 2D the text appears along the path with letters upright
// as determined by the path direction. In 3D by default letters are positioned on the tangent line to the path with the path normal
// pointing toward the reader. The path normal points away from the center of curvature (the opposite of the normal produced
// by path_normals()). Note that this means that if the center of curvature switches sides the text will flip upside down.
// If you want text on such a path you must supply your own normal or top vector.
// .
// Text appears starting at the beginning of the path, so if the 3D path moves right to left
// then a left-to-right reading language will display in the wrong order. (For a 2D path text will appear upside down.)
// The text for a 3D path appears positioned to be read from "outside" of the curve (from a point on the other side of the
// curve from the center of curvature). If you need the text to read properly from the inside, you can set reverse to
// true to flip the text, or supply your own normal.
// .
// If you do not have the experimental textmetrics feature enabled then you must specify the space for the letters
// using lettersize, which can be a scalar or array. You will have the easiest time getting good results by using
// a monospace font such as Courier. Note that even with text metrics, spacing may be different because path_text()
// doesn't do kerning to adjust positions of individual glyphs. Also if your font has ligatures they won't be used.
// .
// By default letters appear centered on the path. The offset can be specified to shift letters toward the reader (in
// the direction of the normal).
// .
// You can specify your own normal by setting `normal` to a direction or a list of directions. Your normal vector should
// point toward the reader. You can also specify
// top, which directs the top of the letters in a desired direction. If you specify your own directions and they
// are not perpendicular to the path then the direction you specify will take priority and the
// letters will not rest on the tangent line of the path. Note that the normal or top directions that you
// specify must not be parallel to the path.
// Arguments:
// path = path to place the text on
// text = text to create
// size = font size
// thickness = thickness of letters (not allowed for 2D path)
// font = font to use
// ---
// lettersize = scalar or array giving size of letters
// offset = distance to shift letters "up" (towards the reader). Not allowed for 2D path. Default: 0
// normal = direction or list of directions pointing towards the reader of the text. Not allowed for 2D path.
// top = direction or list of directions pointing toward the top of the text
// reverse = reverse the letters if true. Not allowed for 2D path. Default: false
// textmetrics = if set to true and lettersize is not given then use the experimental textmetrics feature. You must be running a dev snapshot that includes this feature and have the feature turned on in your preferences. Default: false
// Example: The examples use Courier, a monospaced font. The width is 1/1.2 times the specified size for this font. This text could wrap around a cylinder.
// path = path3d(arc(100, r=25, angle=[245, 370]));
// color("red")stroke(path, width=.3);
// path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2);
// Example: By setting the normal to UP we can get text that lies flat, for writing around the edge of a disk:
// path = path3d(arc(100, r=25, angle=[245, 370]));
// color("red")stroke(path, width=.3);
// path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2, normal=UP);
// Example: If we want text that reads from the other side we can use reverse. Note we have to reverse the direction of the path and also set the reverse option.
// path = reverse(path3d(arc(100, r=25, angle=[65, 190])));
// color("red")stroke(path, width=.3);
// path_text(path, "Example text", font="Courier", size=5, lettersize = 5/1.2, reverse=true);
// Example: text debossed onto a cylinder in a spiral. The text is 1 unit deep because it is half in, half out.
// text = ("A long text example to wrap around a cylinder, possibly for a few times.");
// L = 5*len(text);
// maxang = 360*L/(PI*50);
// spiral = [for(a=[0:1:maxang]) [25*cos(a), 25*sin(a), 10-30/maxang*a]];
// difference(){
// cyl(d=50, l=50, $fn=120);
// path_text(spiral, text, size=5, lettersize=5/1.2, font="Courier", thickness=2);
// }
// Example: Same example but text embossed. Make sure you have enough depth for the letters to fully overlap the object.
// text = ("A long text example to wrap around a cylinder, possibly for a few times.");
// L = 5*len(text);
// maxang = 360*L/(PI*50);
// spiral = [for(a=[0:1:maxang]) [25*cos(a), 25*sin(a), 10-30/maxang*a]];
// cyl(d=50, l=50, $fn=120);
// path_text(spiral, text, size=5, lettersize=5/1.2, font="Courier", thickness=2);
// Example: Here the text baseline sits on the path. (Note the default orientation makes text readable from below, so we specify the normal.)
// path = arc(100, points = [[-20, 0, 20], [0,0,5], [20,0,20]]);
// color("red")stroke(path,width=.2);
// path_text(path, "Example Text", size=5, lettersize=5/1.2, font="Courier", normal=FRONT);
// Example: If we use top to orient the text upward, the text baseline is no longer aligned with the path.
// path = arc(100, points = [[-20, 0, 20], [0,0,5], [20,0,20]]);
// color("red")stroke(path,width=.2);
// path_text(path, "Example Text", size=5, lettersize=5/1.2, font="Courier", top=UP);
// Example: This sine wave wrapped around the cylinder has a twisting normal that produces wild letter layout. We fix it with a custom normal which is different at every path point.
// path = [for(theta = [0:360]) [25*cos(theta), 25*sin(theta), 4*cos(theta*4)]];
// normal = [for(theta = [0:360]) [cos(theta), sin(theta),0]];
// zrot(-120)
// difference(){
// cyl(r=25, h=20, $fn=120);
// path_text(path, "A sine wave wiggles", font="Courier", lettersize=5/1.2, size=5, normal=normal);
// }
// Example: The path center of curvature changes, and the text flips.
// path = zrot(-120,p=path3d( concat(arc(100, r=25, angle=[0,90]), back(50,p=arc(100, r=25, angle=[268, 180])))));
// color("red")stroke(path,width=.2);
// path_text(path, "A shorter example", size=5, lettersize=5/1.2, font="Courier", thickness=2);
// Example: We can fix it with top:
// path = zrot(-120,p=path3d( concat(arc(100, r=25, angle=[0,90]), back(50,p=arc(100, r=25, angle=[268, 180])))));
// color("red")stroke(path,width=.2);
// path_text(path, "A shorter example", size=5, lettersize=5/1.2, font="Courier", thickness=2, top=UP);
// Example(2D): With a 2D path instead of 3D there's no ambiguity about direction and it works by default:
// path = zrot(-120,p=concat(arc(100, r=25, angle=[0,90]), back(50,p=arc(100, r=25, angle=[268, 180]))));
// color("red")stroke(path,width=.2);
// path_text(path, "A shorter example", size=5, lettersize=5/1.2, font="Courier");
module path_text(path, text, font, size, thickness, lettersize, offset=0, reverse=false, normal, top, textmetrics=false)
{
dummy2=assert(is_path(path,[2,3]),"Must supply a 2d or 3d path")
assert(num_defined([normal,top])<=1, "Cannot define both \"normal\" and \"top\"");
dim = len(path[0]);
normalok = is_undef(normal) || is_vector(normal,3) || (is_path(normal,3) && len(normal)==len(path));
topok = is_undef(top) || is_vector(top,dim) || (dim==2 && is_vector(top,3) && top[2]==0)
|| (is_path(top,dim) && len(top)==len(path));
dummy4 = assert(dim==3 || is_undef(thickness), "Cannot give a thickness with 2d path")
assert(dim==3 || !reverse, "Reverse not allowed with 2d path")
assert(dim==3 || offset==0, "Cannot give offset with 2d path")
assert(dim==3 || is_undef(normal), "Cannot define \"normal\" for a 2d path, only \"top\"")
assert(normalok,"\"normal\" must be a vector or path compatible with the given path")
assert(topok,"\"top\" must be a vector or path compatible with the given path");
thickness = first_defined([thickness,1]);
normal = is_vector(normal) ? repeat(normal, len(path))
: is_def(normal) ? normal
: undef;
top = is_vector(top) ? repeat(dim==2?point2d(top):top, len(path))
: is_def(top) ? top
: undef;
lsize = is_def(lettersize) ? force_list(lettersize, len(text))
: textmetrics ? [for(letter=text) let(t=textmetrics(letter, font=font, size=size)) t.advance[0]]
: assert(false, "textmetrics disabled: Must specify letter size");
dummy1 = assert(sum(lsize)<=path_length(path),"Path is too short for the text");
pts = _path_cut_points(path, add_scalar([0, each cumsum(lsize)],lsize[0]/2), direction=true);
usernorm = is_def(normal);
usetop = is_def(top);
normpts = is_undef(normal) ? (reverse?1:-1)*subindex(pts,3) : _cut_interp(pts,path, normal);
toppts = is_undef(top) ? undef : _cut_interp(pts,path,top);
for(i=idx(text))
let( tangent = pts[i][2] )
assert(!usetop || !approx(tangent*toppts[i],norm(top[i])*norm(tangent)),
str("Specified top direction parallel to path at character ",i))
assert(usetop || !approx(tangent*normpts[i],norm(normpts[i])*norm(tangent)),
str("Specified normal direction parallel to path at character ",i))
let(
adjustment = usetop ? (tangent*toppts[i])*toppts[i]/(toppts[i]*toppts[i])
: usernorm ? (tangent*normpts[i])*normpts[i]/(normpts[i]*normpts[i])
: [0,0,0]
)
move(pts[i][0])
if(dim==3){
frame_map(x=tangent-adjustment,
z=usetop ? undef : normpts[i],
y=usetop ? toppts[i] : undef)
up(offset-thickness/2)
linear_extrude(height=thickness)
left(lsize[0]/2)text(text[i], font=font, size=size);
} else {
frame_map(x=point3d(tangent-adjustment), y=point3d(usetop ? toppts[i] : -normpts[i]))
left(lsize[0]/2)text(text[i], font=font, size=size);
}
}
// Section: Miscellaneous

1313
skin.scad

File diff suppressed because it is too large Load diff

View file

@ -1,191 +0,0 @@
//////////////////////////////////////////////////////////////////////
// LibFile: stacks.scad
// Stack data structure implementation.
// Includes:
// include <BOSL2/std.scad>
// include <BOSL2/stacks.scad>
//////////////////////////////////////////////////////////////////////
// Section: Stack Data Structure
// A stack is a last-in-first-out collection of items. You can push items onto the top of the
// stack, or pop the top item off. While you can treat a stack as an opaque data type, using the
// functions below, it's simply implemented as a list. This means that you can use any list
// function to manipulate the stack. The last item in the list is the topmost stack item.
// The depth of an item is how far buried in the stack that item is. An item at depth 1 is the
// top-most stack item. An item at depth 3 is two items below the top-most stack item.
// Function: stack_init()
// Usage:
// stack = stack_init();
// Description:
// Creates an empty stack/list.
// Example:
// stack = stack_init(); // Return: []
function stack_init() = [];
// Function: stack_empty()
// Usage:
// if (stack_empty(stack)) ...
// Description:
// Returns true if the given stack is empty.
// Arguments:
// stack = The stack to test if empty.
// Example:
// stack = stack_init();
// is_empty = stack_empty(stack); // Returns: true
// stack2 = stack_push(stack, "foo");
// is_empty2 = stack_empty(stack2); // Returns: false
function stack_empty(stack) =
assert(is_list(stack))
len(stack)==0;
// Function: stack_depth()
// Usage:
// depth = stack_depth(stack);
// Description:
// Returns the depth of the given stack.
// Arguments:
// stack = The stack to get the depth of.
// Example:
// stack = stack_init();
// depth = stack_depth(stack); // Returns: 0
// stack2 = stack_push(stack, "foo");
// depth2 = stack_depth(stack2); // Returns: 1
// stack3 = stack_push(stack2, ["bar","baz","qux"]);
// depth3 = stack_depth(stack3); // Returns: 4
function stack_depth(stack) =
assert(is_list(stack))
len(stack);
// Function: stack_top()
// Usage:
// item = stack_top(stack);
// list = stack_top(stack,n);
// Description:
// If n is not given, returns the topmost item of the given stack.
// If n is given, returns a list of the `n` topmost items.
// Arguments:
// stack = The stack/list to get the top item(s) of.
// Example:
// stack = [4,5,6,7];
// item = stack_top(stack); // Returns: 7
// list = stack_top(stack,n=3); // Returns: [5,6,7]
function stack_top(stack,n=undef) =
assert(is_list(stack))
is_undef(n)? (
stack[len(stack)-1]
) : (
let(stacksize = len(stack))
assert(is_num(n))
assert(n>=0)
assert(stacksize>=n, "stack underflow")
[for (i=[0:1:n-1]) stack[stacksize-n+i]]
);
// Function: stack_peek()
// Usage:
// item = stack_peek(stack,[depth]);
// list = stack_peek(stack,depth,n);
// Description:
// If `n` is not given, returns the stack item at depth `depth`.
// If `n` is given, returns a list of the `n` stack items at and above depth `depth`.
// Arguments:
// stack = The stack to read from.
// depth = The depth of the stack item to read. Default: 0
// n = The number of stack items to return. Default: undef (Return only the stack item at `depth`)
// Example:
// stack = [2,3,4,5,6,7,8,9];
// item = stack_peek(stack); // Returns: 9
// item2 = stack_peek(stack, 3); // Returns: 7
// list = stack_peek(stack, 6, 4); // Returns: [4,5,6,7]
function stack_peek(stack,depth=0,n=undef) =
assert(is_list(stack))
assert(is_num(depth))
assert(depth>=0)
let(stacksize = len(stack))
assert(stacksize>=depth, "stack underflow")
is_undef(n)? (
stack[stacksize-depth-1]
) : (
assert(is_num(n))
assert(n>=0)
assert(n<=depth+1)
[for (i=[0:1:n-1]) stack[stacksize-1-depth+i]]
);
// Function: stack_push()
// Usage:
// modified_stack = stack_push(stack,items);
// Description:
// Pushes the given `items` onto the stack `stack`. Returns the modified stack.
// Arguments:
// stack = The stack to modify.
// items = A value or list of values to push onto the stack.
// Example:
// stack = [4,9,2,3];
// stack2 = stack_push(stack,7); // Returns: [4,9,2,3,7]
// stack3 = stack_push(stack2,[6,1]); // Returns: [4,9,2,3,7,6,1]
// stack4 = stack_push(stack,[[5,8]]); // Returns: [4,9,2,3,[5,8]]
// stack5 = stack_push(stack,[[5,8],6,7]); // Returns: [4,9,2,3,[5,8],6,7]
function stack_push(stack,items) =
assert(is_list(stack))
is_list(items)? concat(stack, items) : concat(stack, [items]);
// Function: stack_pop()
// Usage:
// modified_stack = stack_pop(stack, [n]);
// Description:
// Removes the `n` topmost items from the stack. Returns the modified stack.
// Arguments:
// stack = The stack to modify.
// n = The number of items to remove off the top of the stack. Default: 1
// Example:
// stack = [4,5,6,7,8,9];
// stack2 = stack_pop(stack); // Returns: [4,5,6,7,8]
// stack3 = stack_pop(stack2,n=3); // Returns: [4,5]
function stack_pop(stack,n=1) =
assert(is_list(stack))
assert(is_num(n))
assert(n>=0)
assert(len(stack)>=n, "stack underflow")
[for (i = [0:1:len(stack)-1-n]) stack[i]];
// Function: stack_rotate()
// Usage:
// modified_stack = stack_rotate(stack, [n]);
// Description:
// Rotates the top `abs(n)` stack items, and returns the modified stack.
// If `n` is positive, then the depth `n` stack item is rotated (left) to the top.
// If `n` is negative, then the top stack item is rotated (right) to depth `abs(n)`.
// Arguments:
// stack = The stack to modify.
// n = The number of stack items to rotate. If negative, reverse rotation direction. Default: 3
// Example:
// stack = [4,5,6,7,8];
// stack2 = stack_rotate(stack,3); // Returns: [4,5,7,8,6]
// stack3 = stack_rotate(stack2,-4); // Returns: [4,6,5,7,8]
function stack_rotate(stack,n=3) =
assert(is_list(stack))
let(stacksize = len(stack))
assert(stacksize>=n, "stack underflow")
n>=0? concat(
[for (i=[0:1:stacksize-1-n]) stack[i]],
[for (i=[0:1:n-2]) stack[stacksize-n+i+1]],
[stack[stacksize-n]]
) : concat(
[for (i=[0:1:stacksize-1+n]) stack[i]],
[stack[stacksize-1]],
[for (i=[0:1:-n-2]) stack[stacksize+n+i]]
);
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -14,14 +14,15 @@ include <transforms.scad>
include <distributors.scad>
include <mutators.scad>
include <attachments.scad>
include <primitives.scad>
include <shapes.scad>
include <shapes3d.scad>
include <shapes2d.scad>
include <drawing.scad>
include <masks.scad>
include <paths.scad>
include <edges.scad>
include <arrays.scad>
include <math.scad>
include <trigonometry.scad>
include <vectors.scad>
include <quaternions.scad>
include <affine.scad>
@ -32,7 +33,7 @@ include <regions.scad>
include <strings.scad>
include <skin.scad>
include <vnf.scad>
include <common.scad>
include <utility.scad>
include <debug.scad>

View file

@ -216,12 +216,6 @@ test_affine3d_skew_yz();
////////////////////////////
module test_affine3d_frame_map() {
assert(approx(affine3d_frame_map(x=[1,1,0], y=[-1,1,0]), affine3d_zrot(45)));
}
test_affine3d_frame_map();
module test_apply() {
assert(approx(apply(affine3d_xrot(90),2*UP),2*FRONT));
assert(approx(apply(affine3d_yrot(90),2*UP),2*RIGHT));

51
tests/test_drawing.scad Normal file
View file

@ -0,0 +1,51 @@
include <../std.scad>
module test_turtle() {
assert_approx(
turtle([
"move", 10,
"ymove", 5,
"xmove", 5,
"xymove", [10,15],
"left", 135,
"untilx", 0,
"turn", 90,
"untily", 0,
"right", 135,
"arcsteps", 5,
"arcright", 15, 30,
"arcleft", 15, 30,
"arcsteps", 0,
"arcrightto", 15, 90,
"arcleftto", 15, 180,
"jump", [10,10],
"xjump", 15,
"yjump", 15,
"angle", 30,
"length", 15,
"right",
"move",
"scale", 2,
"left",
"move",
"addlength", 5,
"repeat", 3, ["move"],
], $fn=24),
[[0,0],[10,0],[10,5],[15,5],[25,20],[-3.5527136788e-15,45],[-45,0],[-44.8716729206,1.9578928833],[-44.4888873943,3.88228567654],[-43.8581929877,5.74025148548],[-42.9903810568,7.5],[-42.1225691259,9.25974851452],[-41.4918747192,11.1177143235],[-41.1090891929,13.0421071167],[-40.9807621135,15],[-41.0157305757,16.0236362005],[-41.120472923,17.0424997364],[-41.2945007983,18.0518401958],[-41.5370028033,19.0469515674],[-41.8468482818,20.0231941826],[-42.222592591,20.9760163477],[-42.6624838375,21.900975566],[-43.1644710453,22.7937592505],[-43.7262137184,23.6502048317],[-44.345092753,24.4663191649],[-45.0182226494,25.2382971483],[-45.7424649653,25.9625394642],[-46.5144429486,26.6356693606],[-47.3305572818,27.2545483952],[-48.187002863,27.8162910682],[-49.0797865476,28.318278276],[-50.0047457658,28.7581695226],[-50.957567931,29.1339138318],[-51.9338105462,29.4437593102],[-52.9289219177,29.6862613152],[-53.9382623771,29.8602891905],[-54.9571259131,29.9650315379],[-55.9807621135,30],[10,10],[15,10],[15,15],[2.00961894323,22.5],[-27.9903810568,22.5],[-62.9903810568,22.5],[-97.9903810568,22.5],[-132.990381057,22.5]]
);
}
test_turtle();
module test_arc() {
assert_approx(arc(N=8, d=100, angle=135, cp=[10,10]), [[60,10],[57.1941665154,26.5139530978],[49.0915741234,41.1744900929],[36.6016038258,52.3362099614],[21.1260466978,58.7463956091],[4.40177619483,59.6856104947],[-11.6941869559,55.0484433951],[-25.3553390593,45.3553390593]]);
assert_approx(arc(N=8, d=100, angle=135, cp=[10,10],endpoint=false), [[60,10],[57.8470167866,24.5142338627],[51.5734806151,37.778511651],[41.7196642082,48.6505226681],[29.1341716183,56.1939766256],[14.9008570165,59.7592363336],[0.245483899194,59.0392640202],[-13.5698368413,54.0960632174]]);
assert_approx(arc(N=8, d=100, angle=[45,225], cp=[10,10]), [[45.3553390593,45.3553390593],[26.5139530978,57.1941665154],[4.40177619483,59.6856104947],[-16.6016038258,52.3362099614],[-32.3362099614,36.6016038258],[-39.6856104947,15.5982238052],[-37.1941665154,-6.51395309776],[-25.3553390593,-25.3553390593]]);
assert_approx(arc(N=8, d=100, start=45, angle=135, cp=[10,10]), [[45.3553390593,45.3553390593],[31.6941869559,55.0484433951],[15.5982238052,59.6856104947],[-1.12604669782,58.7463956091],[-16.6016038258,52.3362099614],[-29.0915741234,41.1744900929],[-37.1941665154,26.5139530978],[-40,10]]);
assert_approx(arc(N=8, d=100, start=45, angle=-90, cp=[10,10]), [[45.3553390593,45.3553390593],[52.3362099614,36.6016038258],[57.1941665154,26.5139530978],[59.6856104947,15.5982238052],[59.6856104947,4.40177619483],[57.1941665154,-6.51395309776],[52.3362099614,-16.6016038258],[45.3553390593,-25.3553390593]]);
assert_approx(arc(N=8, width=100, thickness=30), [[50,-3.5527136788e-15],[39.5300788555,13.9348601124],[25.3202618476,24.0284558904],[8.71492362453,29.3258437015],[-8.71492362453,29.3258437015],[-25.3202618476,24.0284558904],[-39.5300788555,13.9348601124],[-50,-1.42108547152e-14]]);
assert_approx(arc(N=8, cp=[10,10], points=[[45,45],[-25,45]]), [[45,45],[36.3342442379,51.9107096148],[26.3479795075,56.7198412457],[15.5419588213,59.1862449514],[4.45804117867,59.1862449514],[-6.34797950747,56.7198412457],[-16.3342442379,51.9107096148],[-25,45]]);
assert_approx(arc(N=24, cp=[10,10], points=[[45,45],[-25,45]], long=true), [[45,45],[51.3889035257,37.146982612],[56.0464336973,28.1583574081],[58.7777575294,18.4101349813],[59.4686187624,8.31010126292],[58.0901174104,-1.71924090789],[54.6999187001,-11.2583458482],[49.4398408296,-19.9081753929],[42.5299224539,-27.3068913894],[34.2592180667,-33.1449920477],[24.9737063235,-37.1782589647],[15.0618171232,-39.2379732261],[4.93818287676,-39.2379732261],[-4.97370632349,-37.1782589647],[-14.2592180667,-33.1449920477],[-22.5299224539,-27.3068913894],[-29.4398408296,-19.9081753929],[-34.6999187001,-11.2583458482],[-38.0901174104,-1.71924090789],[-39.4686187624,8.31010126292],[-38.7777575294,18.4101349813],[-36.0464336973,28.1583574081],[-31.3889035257,37.146982612],[-25,45]]);
assert_approx(arc($fn=24, cp=[10,10], points=[[45,45],[-25,45]], long=true), [[45,45],[53.2421021636,34.0856928585],[58.1827254512,21.3324740498],[59.4446596304,7.71403542491],[56.9315576496,-5.72987274525],[50.8352916125,-17.9728253654],[41.6213035891,-28.0800887515],[29.9930697126,-35.2799863457],[16.8383906815,-39.0228152281],[3.16160931847,-39.0228152281],[-9.9930697126,-35.2799863457],[-21.6213035891,-28.0800887515],[-30.8352916125,-17.9728253654],[-36.9315576496,-5.72987274525],[-39.4446596304,7.71403542491],[-38.1827254512,21.3324740498],[-33.2421021636,34.0856928585],[-25,45]]);
}
test_arc();

View file

@ -1,19 +1,19 @@
include <../std.scad>
module test_is_edge_array() {
assert(is_edge_array([[0,0,0,0],[0,0,0,0],[0,0,0,0]]));
assert(is_edge_array([[1,1,1,1],[1,1,1,1],[1,1,1,1]]));
assert(!is_edge_array([[1,1,1],[1,1,1],[1,1,1]]));
assert(!is_edge_array([[1,1,1,1,1],[1,1,1,1,1],[1,1,1,1,1]]));
assert(!is_edge_array([[1,1,1,1],[1,1,1,1]]));
assert(!is_edge_array([1,1,1,1]));
assert(!is_edge_array("foo"));
assert(!is_edge_array(42));
assert(!is_edge_array(true));
assert(is_edge_array(edges(["X","Y"])));
module test__is_edge_array() {
assert(_is_edge_array([[0,0,0,0],[0,0,0,0],[0,0,0,0]]));
assert(_is_edge_array([[1,1,1,1],[1,1,1,1],[1,1,1,1]]));
assert(!_is_edge_array([[1,1,1],[1,1,1],[1,1,1]]));
assert(!_is_edge_array([[1,1,1,1,1],[1,1,1,1,1],[1,1,1,1,1]]));
assert(!_is_edge_array([[1,1,1,1],[1,1,1,1]]));
assert(!_is_edge_array([1,1,1,1]));
assert(!_is_edge_array("foo"));
assert(!_is_edge_array(42));
assert(!_is_edge_array(true));
assert(_is_edge_array(edges(["X","Y"])));
}
test_is_edge_array();
test__is_edge_array();
module test__edge_set() {
@ -62,14 +62,14 @@ module test__edge_set() {
test__edge_set();
module test_normalize_edges() {
assert(normalize_edges([[-2,-2,-2,-2],[-2,-2,-2,-2],[-2,-2,-2,-2]]) == [[0,0,0,0],[0,0,0,0],[0,0,0,0]]);
assert(normalize_edges([[-1,-1,-1,-1],[-1,-1,-1,-1],[-1,-1,-1,-1]]) == [[0,0,0,0],[0,0,0,0],[0,0,0,0]]);
assert(normalize_edges([[0,0,0,0],[0,0,0,0],[0,0,0,0]]) == [[0,0,0,0],[0,0,0,0],[0,0,0,0]]);
assert(normalize_edges([[1,1,1,1],[1,1,1,1],[1,1,1,1]]) == [[1,1,1,1],[1,1,1,1],[1,1,1,1]]);
assert(normalize_edges([[2,2,2,2],[2,2,2,2],[2,2,2,2]]) == [[1,1,1,1],[1,1,1,1],[1,1,1,1]]);
module test__normalize_edges() {
assert(_normalize_edges([[-2,-2,-2,-2],[-2,-2,-2,-2],[-2,-2,-2,-2]]) == [[0,0,0,0],[0,0,0,0],[0,0,0,0]]);
assert(_normalize_edges([[-1,-1,-1,-1],[-1,-1,-1,-1],[-1,-1,-1,-1]]) == [[0,0,0,0],[0,0,0,0],[0,0,0,0]]);
assert(_normalize_edges([[0,0,0,0],[0,0,0,0],[0,0,0,0]]) == [[0,0,0,0],[0,0,0,0],[0,0,0,0]]);
assert(_normalize_edges([[1,1,1,1],[1,1,1,1],[1,1,1,1]]) == [[1,1,1,1],[1,1,1,1],[1,1,1,1]]);
assert(_normalize_edges([[2,2,2,2],[2,2,2,2],[2,2,2,2]]) == [[1,1,1,1],[1,1,1,1],[1,1,1,1]]);
}
test_normalize_edges();
test__normalize_edges();
module test_edges() {
@ -90,24 +90,24 @@ module test_edges() {
test_edges();
module test_corner_edge_count() {
module test__corner_edge_count() {
edges = edges([TOP,FRONT+RIGHT]);
assert(corner_edge_count(edges,TOP+FRONT+RIGHT) == 3);
assert(corner_edge_count(edges,TOP+FRONT+LEFT) == 2);
assert(corner_edge_count(edges,BOTTOM+FRONT+RIGHT) == 1);
assert(corner_edge_count(edges,BOTTOM+FRONT+LEFT) == 0);
assert(_corner_edge_count(edges,TOP+FRONT+RIGHT) == 3);
assert(_corner_edge_count(edges,TOP+FRONT+LEFT) == 2);
assert(_corner_edge_count(edges,BOTTOM+FRONT+RIGHT) == 1);
assert(_corner_edge_count(edges,BOTTOM+FRONT+LEFT) == 0);
}
test_corner_edge_count();
test__corner_edge_count();
module test_corner_edges() {
module test__corner_edges() {
edges = edges([TOP,FRONT+RIGHT]);
assert_equal(corner_edges(edges,TOP+FRONT+RIGHT), [1,1,1]);
assert_equal(corner_edges(edges,TOP+FRONT+LEFT), [1,1,0]);
assert_equal(corner_edges(edges,BOTTOM+FRONT+RIGHT), [0,0,1]);
assert_equal(corner_edges(edges,BOTTOM+FRONT+LEFT), [0,0,0]);
assert_equal(_corner_edges(edges,TOP+FRONT+RIGHT), [1,1,1]);
assert_equal(_corner_edges(edges,TOP+FRONT+LEFT), [1,1,0]);
assert_equal(_corner_edges(edges,BOTTOM+FRONT+RIGHT), [0,0,1]);
assert_equal(_corner_edges(edges,BOTTOM+FRONT+LEFT), [0,0,0]);
}
test_corner_edges();
test__corner_edges();
module test_corners() {
@ -174,36 +174,36 @@ module test_corners() {
test_corners();
module test_is_corner_array() {
module test__is_corner_array() {
edges = edges([TOP,FRONT+RIGHT]);
corners = corners([TOP,FRONT+RIGHT]);
assert(!is_corner_array(undef));
assert(!is_corner_array(true));
assert(!is_corner_array(false));
assert(!is_corner_array(INF));
assert(!is_corner_array(-INF));
assert(!is_corner_array(NAN));
assert(!is_corner_array(-4));
assert(!is_corner_array(0));
assert(!is_corner_array(4));
assert(!is_corner_array("foo"));
assert(!is_corner_array([]));
assert(!is_corner_array([4,5,6]));
assert(!is_corner_array([2:3:9]));
assert(!is_corner_array(edges));
assert(is_corner_array(corners));
assert(!_is_corner_array(undef));
assert(!_is_corner_array(true));
assert(!_is_corner_array(false));
assert(!_is_corner_array(INF));
assert(!_is_corner_array(-INF));
assert(!_is_corner_array(NAN));
assert(!_is_corner_array(-4));
assert(!_is_corner_array(0));
assert(!_is_corner_array(4));
assert(!_is_corner_array("foo"));
assert(!_is_corner_array([]));
assert(!_is_corner_array([4,5,6]));
assert(!_is_corner_array([2:3:9]));
assert(!_is_corner_array(edges));
assert(_is_corner_array(corners));
}
test_is_corner_array();
test__is_corner_array();
module test_normalize_corners() {
assert_equal(normalize_corners([-2,-2,-2,-2,-2,-2,-2,-2]), [0,0,0,0,0,0,0,0]);
assert_equal(normalize_corners([-1,-1,-1,-1,-1,-1,-1,-1]), [0,0,0,0,0,0,0,0]);
assert_equal(normalize_corners([0,0,0,0,0,0,0,0]), [0,0,0,0,0,0,0,0]);
assert_equal(normalize_corners([1,1,1,1,1,1,1,1]), [1,1,1,1,1,1,1,1]);
assert_equal(normalize_corners([2,2,2,2,2,2,2,2]), [1,1,1,1,1,1,1,1]);
module test__normalize_corners() {
assert_equal(_normalize_corners([-2,-2,-2,-2,-2,-2,-2,-2]), [0,0,0,0,0,0,0,0]);
assert_equal(_normalize_corners([-1,-1,-1,-1,-1,-1,-1,-1]), [0,0,0,0,0,0,0,0]);
assert_equal(_normalize_corners([0,0,0,0,0,0,0,0]), [0,0,0,0,0,0,0,0]);
assert_equal(_normalize_corners([1,1,1,1,1,1,1,1]), [1,1,1,1,1,1,1,1]);
assert_equal(_normalize_corners([2,2,2,2,2,2,2,2]), [1,1,1,1,1,1,1,1]);
}
test_normalize_corners();
test__normalize_corners();

View file

@ -6,37 +6,18 @@ include <../std.scad>
test_point_on_segment2d();
test_point_left_of_line2d();
test_collinear();
test_is_point_on_line();
test_is_collinear();
test_point_line_distance();
test_point_segment_distance();
test_segment_distance();
test_line_normal();
test_line_intersection();
//test_line_ray_intersection();
test_line_segment_intersection();
//test_ray_intersection();
//test_ray_segment_intersection();
test_segment_intersection();
//test_line_ray_intersection(); // should add this type of case
//test_ray_intersection(); // should add this type of case
//test_ray_segment_intersection(); // should add this type of case
test_line_closest_point();
//test_ray_closest_point();
test_segment_closest_point();
//test_ray_closest_point(); // should add this type of case
test_line_from_points();
test_tri_calc();
//test_hyp_opp_to_adj();
//test_hyp_ang_to_adj();
//test_opp_ang_to_adj();
//test_hyp_adj_to_opp();
//test_hyp_ang_to_opp();
//test_adj_ang_to_opp();
//test_adj_opp_to_hyp();
//test_adj_ang_to_hyp();
//test_opp_ang_to_hyp();
//test_hyp_adj_to_ang();
//test_hyp_opp_to_ang();
//test_adj_opp_to_ang();
test_triangle_area();
test_plane3pt();
test_plane3pt_indexed();
test_plane_from_normal();
@ -44,52 +25,37 @@ test_plane_from_points();
test_plane_from_polygon();
test_plane_normal();
test_plane_offset();
test_projection_on_plane();
test_plane_point_nearest_origin();
test_plane_closest_point();
test_point_plane_distance();
test__general_plane_line_intersection();
test_plane_line_angle();
test_normalize_plane();
test_plane_line_intersection();
test_polygon_line_intersection();
test_plane_intersection();
test_coplanar();
test_points_on_plane();
test_in_front_of_plane();
test_is_coplanar();
test_are_points_on_plane();
test__is_point_above_plane();
test_circle_2tangents();
test_circle_3points();
test_circle_point_tangents();
test_noncollinear_triple();
test_pointlist_bounds();
test_closest_point();
test_furthest_point();
test_polygon_area();
test_is_convex_polygon();
test_is_polygon_convex();
test_polygon_shift();
test_polygon_shift_to_closest_point();
test_reindex_polygon();
test_align_polygon();
test_centroid();
test_point_in_polygon();
test_polygon_is_clockwise();
test_is_polygon_clockwise();
test_clockwise_polygon();
test_ccw_polygon();
test_reverse_polygon();
//test_polygon_normal();
//test_split_polygons_at_each_x();
//test_split_polygons_at_each_y();
//test_split_polygons_at_each_z();
test_polygon_normal();
//tests to migrate to other files
test_is_path();
test_is_closed_path();
test_close_path();
test_cleanup_path();
test_simplify_path();
test_simplify_path_indexed();
test_is_region();
test_convex_distance();
test_convex_collision();
@ -112,13 +78,13 @@ function info_str(list,i=0,string=chr(10)) =
: info_str(list,i+1,str(string,str(list[i][0],_valstr(list[i][1]),chr(10))));
module test_normalize_plane(){
module test__normalize_plane(){
plane = rands(-5,5,4,seed=333)+[10,0,0,0];
plane2 = normalize_plane(plane);
plane2 = _normalize_plane(plane);
assert_approx(norm(point3d(plane2)),1);
assert_approx(plane*plane2[3],plane2*plane[3]);
}
*test_normalize_plane();
test__normalize_plane();
module test_plane_line_intersection(){
line = [rands(-1,1,3,seed=74),rands(-1,1,3,seed=99)+[2,0,0]];
@ -167,20 +133,10 @@ module test_plane_intersection(){
*test_plane_intersection();
module test_plane_point_nearest_origin(){
point = rands(-1,1,3)+[2,0,0]; // a non zero vector
plane = [ each point, point*point]; // a plane containing `point`
info = info_str([["point = ",point],["plane = ",plane]]);
assert_approx(plane_point_nearest_origin(plane),point,info);
assert_approx(plane_point_nearest_origin([each point,5]),5*unit(point)/norm(point),info);
}
test_plane_point_nearest_origin();
module test_plane_offset(){
plane = rands(-1,1,4)+[2,0,0,0]; // a valid plane
info = info_str([["plane = ",plane]]);
assert_approx(plane_offset(plane), normalize_plane(plane)[3],info);
assert_approx(plane_offset(plane), _normalize_plane(plane)[3],info);
assert_approx(plane_offset([1,1,1,1]), 1/sqrt(3),info);
}
*test_plane_offset();
@ -259,21 +215,21 @@ module test__general_plane_line_intersection() {
*test__general_plane_line_intersection();
module test_points_on_plane() {
module test_are_points_on_plane() {
pts = [for(i=[0:40]) rands(-1,1,3) ];
dir = rands(-10,10,3);
normal0 = [1,2,3];
ang = rands(0,360,1)[0];
normal = rot(a=ang,p=normal0);
plane = [each normal, normal*dir];
prj_pts = projection_on_plane(plane,pts);
prj_pts = plane_closest_point(plane,pts);
info = info_str([["pts = ",pts],["dir = ",dir],["ang = ",ang]]);
assert(points_on_plane(prj_pts,plane),info);
assert(!points_on_plane(concat(pts,[normal-dir]),plane),info);
assert(are_points_on_plane(prj_pts,plane),info);
assert(!are_points_on_plane(concat(pts,[normal-dir]),plane),info);
}
*test_points_on_plane();
*test_are_points_on_plane();
module test_projection_on_plane(){
module test_plane_closest_point(){
ang = rands(0,360,1)[0];
dir = rands(-10,10,3);
normal0 = unit([1,2,3]);
@ -283,16 +239,16 @@ module test_projection_on_plane(){
planem = [each normal, normal*dir];
pts = [for(i=[1:10]) rands(-1,1,3)];
info = info_str([["ang = ",ang],["dir = ",dir]]);
assert_approx( projection_on_plane(plane,pts),
projection_on_plane(plane,projection_on_plane(plane,pts)),info);
assert_approx( projection_on_plane(plane,pts),
rot(a=ang,p=projection_on_plane(plane0,rot(a=-ang,p=pts))),info);
assert_approx( move((-normal*dir)*normal,p=projection_on_plane(planem,pts)),
projection_on_plane(plane,pts),info);
assert_approx( move((normal*dir)*normal,p=projection_on_plane(plane,pts)),
projection_on_plane(planem,pts),info);
assert_approx( plane_closest_point(plane,pts),
plane_closest_point(plane,plane_closest_point(plane,pts)),info);
assert_approx( plane_closest_point(plane,pts),
rot(a=ang,p=plane_closest_point(plane0,rot(a=-ang,p=pts))),info);
assert_approx( move((-normal*dir)*normal,p=plane_closest_point(planem,pts)),
plane_closest_point(plane,pts),info);
assert_approx( move((normal*dir)*normal,p=plane_closest_point(plane,pts)),
plane_closest_point(planem,pts),info);
}
*test_projection_on_plane();
*test_plane_closest_point();
module test_line_from_points() {
assert_approx(line_from_points([[1,0],[0,0],[-1,0]]),[[-1,0],[1,0]]);
@ -302,56 +258,64 @@ module test_line_from_points() {
}
*test_line_from_points();
module test_point_on_segment2d() {
assert(point_on_segment2d([-15,0], [[-10,0], [10,0]]) == false);
assert(point_on_segment2d([-10,0], [[-10,0], [10,0]]) == true);
assert(point_on_segment2d([-5,0], [[-10,0], [10,0]]) == true);
assert(point_on_segment2d([0,0], [[-10,0], [10,0]]) == true);
assert(point_on_segment2d([3,3], [[-10,0], [10,0]]) == false);
assert(point_on_segment2d([5,0], [[-10,0], [10,0]]) == true);
assert(point_on_segment2d([10,0], [[-10,0], [10,0]]) == true);
assert(point_on_segment2d([15,0], [[-10,0], [10,0]]) == false);
module test_is_point_on_line() {
assert(is_point_on_line([-15,0], [[-10,0], [10,0]],SEGMENT) == false);
assert(is_point_on_line([-10,0], [[-10,0], [10,0]],SEGMENT) == true);
assert(is_point_on_line([-5,0], [[-10,0], [10,0]],SEGMENT) == true);
assert(is_point_on_line([0,0], [[-10,0], [10,0]],SEGMENT) == true);
assert(is_point_on_line([3,3], [[-10,0], [10,0]],SEGMENT) == false);
assert(is_point_on_line([5,0], [[-10,0], [10,0]],SEGMENT) == true);
assert(is_point_on_line([10,0], [[-10,0], [10,0]],SEGMENT) == true);
assert(is_point_on_line([15,0], [[-10,0], [10,0]],SEGMENT) == false);
assert(point_on_segment2d([0,-15], [[0,-10], [0,10]]) == false);
assert(point_on_segment2d([0,-10], [[0,-10], [0,10]]) == true);
assert(point_on_segment2d([0, -5], [[0,-10], [0,10]]) == true);
assert(point_on_segment2d([0, 0], [[0,-10], [0,10]]) == true);
assert(point_on_segment2d([3, 3], [[0,-10], [0,10]]) == false);
assert(point_on_segment2d([0, 5], [[0,-10], [0,10]]) == true);
assert(point_on_segment2d([0, 10], [[0,-10], [0,10]]) == true);
assert(point_on_segment2d([0, 15], [[0,-10], [0,10]]) == false);
assert(is_point_on_line([0,-15], [[0,-10], [0,10]],SEGMENT) == false);
assert(is_point_on_line([0,-10], [[0,-10], [0,10]],SEGMENT) == true);
assert(is_point_on_line([0, -5], [[0,-10], [0,10]],SEGMENT) == true);
assert(is_point_on_line([0, 0], [[0,-10], [0,10]],SEGMENT) == true);
assert(is_point_on_line([3, 3], [[0,-10], [0,10]],SEGMENT) == false);
assert(is_point_on_line([0, 5], [[0,-10], [0,10]],SEGMENT) == true);
assert(is_point_on_line([0, 10], [[0,-10], [0,10]],SEGMENT) == true);
assert(is_point_on_line([0, 15], [[0,-10], [0,10]],SEGMENT) == false);
assert(point_on_segment2d([-15,-15], [[-10,-10], [10,10]]) == false);
assert(point_on_segment2d([-10,-10], [[-10,-10], [10,10]]) == true);
assert(point_on_segment2d([ -5, -5], [[-10,-10], [10,10]]) == true);
assert(point_on_segment2d([ 0, 0], [[-10,-10], [10,10]]) == true);
assert(point_on_segment2d([ 0, 3], [[-10,-10], [10,10]]) == false);
assert(point_on_segment2d([ 5, 5], [[-10,-10], [10,10]]) == true);
assert(point_on_segment2d([ 10, 10], [[-10,-10], [10,10]]) == true);
assert(point_on_segment2d([ 15, 15], [[-10,-10], [10,10]]) == false);
assert(is_point_on_line([-15,-15], [[-10,-10], [10,10]],SEGMENT) == false);
assert(is_point_on_line([-10,-10], [[-10,-10], [10,10]],SEGMENT) == true);
assert(is_point_on_line([ -5, -5], [[-10,-10], [10,10]],SEGMENT) == true);
assert(is_point_on_line([ 0, 0], [[-10,-10], [10,10]],SEGMENT) == true);
assert(is_point_on_line([ 0, 3], [[-10,-10], [10,10]],SEGMENT) == false);
assert(is_point_on_line([ 5, 5], [[-10,-10], [10,10]],SEGMENT) == true);
assert(is_point_on_line([ 10, 10], [[-10,-10], [10,10]],SEGMENT) == true);
assert(is_point_on_line([ 15, 15], [[-10,-10], [10,10]],SEGMENT) == false);
assert(is_point_on_line([10,10], [[0,0],[5,5]]) == true);
assert(is_point_on_line([4,4], [[0,0],[5,5]]) == true);
assert(is_point_on_line([-2,-2], [[0,0],[5,5]]) == true);
assert(is_point_on_line([5,5], [[0,0],[5,5]]) == true);
assert(is_point_on_line([10,10], [[0,0],[5,5]],RAY) == true);
assert(is_point_on_line([0,0], [[0,0],[5,5]],RAY) == true);
assert(is_point_on_line([3,3], [[0,0],[5,5]],RAY) == true);
}
*test_point_on_segment2d();
*test_is_point_on_line();
module test_point_left_of_line2d() {
assert(point_left_of_line2d([ -3, 0], [[-10,-10], [10,10]]) > 0);
assert(point_left_of_line2d([ 0, 0], [[-10,-10], [10,10]]) == 0);
assert(point_left_of_line2d([ 3, 0], [[-10,-10], [10,10]]) < 0);
module test__point_left_of_line2d() {
assert(_point_left_of_line2d([ -3, 0], [[-10,-10], [10,10]]) > 0);
assert(_point_left_of_line2d([ 0, 0], [[-10,-10], [10,10]]) == 0);
assert(_point_left_of_line2d([ 3, 0], [[-10,-10], [10,10]]) < 0);
}
*test_point_left_of_line2d();
test__point_left_of_line2d();
module test_collinear() {
assert(collinear([-10,-10], [-15, -16], [10,10]) == false);
assert(collinear([[-10,-10], [-15, -16], [10,10]]) == false);
assert(collinear([-10,-10], [-15, -15], [10,10]) == true);
assert(collinear([[-10,-10], [-15, -15], [10,10]]) == true);
assert(collinear([-10,-10], [ -3, 0], [10,10]) == false);
assert(collinear([-10,-10], [ 0, 0], [10,10]) == true);
assert(collinear([-10,-10], [ 3, 0], [10,10]) == false);
assert(collinear([-10,-10], [ 15, 15], [10,10]) == true);
assert(collinear([-10,-10], [ 15, 16], [10,10]) == false);
module test_is_collinear() {
assert(is_collinear([-10,-10], [-15, -16], [10,10]) == false);
assert(is_collinear([[-10,-10], [-15, -16], [10,10]]) == false);
assert(is_collinear([-10,-10], [-15, -15], [10,10]) == true);
assert(is_collinear([[-10,-10], [-15, -15], [10,10]]) == true);
assert(is_collinear([-10,-10], [ -3, 0], [10,10]) == false);
assert(is_collinear([-10,-10], [ 0, 0], [10,10]) == true);
assert(is_collinear([-10,-10], [ 3, 0], [10,10]) == false);
assert(is_collinear([-10,-10], [ 15, 15], [10,10]) == true);
assert(is_collinear([-10,-10], [ 15, 16], [10,10]) == false);
}
*test_collinear();
*test_is_collinear();
module test_point_line_distance() {
@ -359,17 +323,12 @@ module test_point_line_distance() {
assert_approx(point_line_distance([-1,-1,-1], [[-10,-10,-10], [10,10,10]]), 0);
assert_approx(point_line_distance([1,-1,0], [[-10,-10,-10], [10,10,10]]), sqrt(2));
assert_approx(point_line_distance([8,-8,0], [[-10,-10,-10], [10,10,10]]), 8*sqrt(2));
assert_approx(point_line_distance([3,8], [[-10,0], [10,0]],SEGMENT), 8);
assert_approx(point_line_distance([14,3], [[-10,0], [10,0]],SEGMENT), 5);
}
*test_point_line_distance();
module test_point_segment_distance() {
assert_approx(point_segment_distance([3,8], [[-10,0], [10,0]]), 8);
assert_approx(point_segment_distance([14,3], [[-10,0], [10,0]]), 5);
}
*test_point_segment_distance();
module test_segment_distance() {
assert_approx(segment_distance([[-14,3], [-14,9]], [[-10,0], [10,0]]), 5);
assert_approx(segment_distance([[-14,3], [-15,9]], [[-10,0], [10,0]]), 5);
@ -418,61 +377,35 @@ module test_line_intersection() {
assert(line_intersection([[-10,-10], [ -1, -1]], [[ 10,-10], [ 1, -1]]) == [0,0]);
assert(line_intersection([[-10,-10], [ 10, 10]], [[ 10,-10], [-10, 10]]) == [0,0]);
assert(line_intersection([[ -8, 0], [ 12, 4]], [[ 12, 0], [ -8, 4]]) == [2,2]);
assert(line_intersection([[-10,-10], [ -1,-10]], [[ 10,-10], [ 1,-10]],LINE,SEGMENT) == undef);
assert(line_intersection([[-10, 0], [ -1, 0]], [[ 10, 0], [ 1, 0]],LINE,SEGMENT) == undef);
assert(line_intersection([[-10, 0], [ -1, 0]], [[ 1, 0], [ 10, 0]],LINE,SEGMENT) == undef);
assert(line_intersection([[-10, 0], [ 10, 0]], [[-10, 0], [ 10, 0]],LINE,SEGMENT) == undef);
assert(line_intersection([[-10, 10], [ 10, 10]], [[-10,-10], [ 10,-10]],LINE,SEGMENT) == undef);
assert(line_intersection([[-10,-10], [ -1, -1]], [[ 10,-10], [ 1, -1]],LINE,SEGMENT) == undef);
assert(line_intersection([[-10,-10], [ 10, 10]], [[ 10,-10], [-10, 10]],LINE,SEGMENT) == [0,0]);
assert(line_intersection([[ -8, 0], [ 12, 4]], [[ 12, 0], [ -8, 4]],LINE,SEGMENT) == [2,2]);
assert(line_intersection([[-10,-10], [ 10, 10]], [[ 10,-10], [ 1, -1]],LINE,SEGMENT) == undef);
assert(line_intersection([[-10,-10], [ 10, 10]], [[ 10,-10], [ -1, 1]],LINE,SEGMENT) == [0,0]);
}
*test_line_intersection();
module test_segment_intersection() {
assert(segment_intersection([[-10,-10], [ -1, -1]], [[ 10,-10], [ 1, -1]]) == undef);
assert(segment_intersection([[-10,-10], [ -1,-10]], [[ 10,-10], [ 1,-10]]) == undef);
assert(segment_intersection([[-10, 0], [ -1, 0]], [[ 10, 0], [ 1, 0]]) == undef);
assert(segment_intersection([[-10, 0], [ -1, 0]], [[ 1, 0], [ 10, 0]]) == undef);
assert(segment_intersection([[-10, 10], [ -1, 1]], [[ 10, 10], [ 1, 1]]) == undef);
assert(segment_intersection([[-10, 0], [ 10, 0]], [[-10, 0], [ 10, 0]]) == undef);
assert(segment_intersection([[-10, 10], [ 10, 10]], [[-10,-10], [ 10,-10]]) == undef);
assert(segment_intersection([[-10, 0], [ 0, 10]], [[ 0, 10], [ 10, 0]]) == [0,10]);
assert(segment_intersection([[-10, 0], [ 0, 10]], [[-10, 20], [ 10, 0]]) == [0,10]);
assert(segment_intersection([[-10,-10], [ 10, 10]], [[ 10,-10], [-10, 10]]) == [0,0]);
assert(segment_intersection([[ -8, 0], [ 12, 4]], [[ 12, 0], [ -8, 4]]) == [2,2]);
}
*test_segment_intersection();
module test_line_segment_intersection() {
assert(line_segment_intersection([[-10,-10], [ -1,-10]], [[ 10,-10], [ 1,-10]]) == undef);
assert(line_segment_intersection([[-10, 0], [ -1, 0]], [[ 10, 0], [ 1, 0]]) == undef);
assert(line_segment_intersection([[-10, 0], [ -1, 0]], [[ 1, 0], [ 10, 0]]) == undef);
assert(line_segment_intersection([[-10, 0], [ 10, 0]], [[-10, 0], [ 10, 0]]) == undef);
assert(line_segment_intersection([[-10, 10], [ 10, 10]], [[-10,-10], [ 10,-10]]) == undef);
assert(line_segment_intersection([[-10,-10], [ -1, -1]], [[ 10,-10], [ 1, -1]]) == undef);
assert(line_segment_intersection([[-10,-10], [ 10, 10]], [[ 10,-10], [-10, 10]]) == [0,0]);
assert(line_segment_intersection([[ -8, 0], [ 12, 4]], [[ 12, 0], [ -8, 4]]) == [2,2]);
assert(line_segment_intersection([[-10,-10], [ 10, 10]], [[ 10,-10], [ 1, -1]]) == undef);
assert(line_segment_intersection([[-10,-10], [ 10, 10]], [[ 10,-10], [ -1, 1]]) == [0,0]);
}
*test_line_segment_intersection();
module test_line_closest_point() {
assert(approx(line_closest_point([[-10,-10], [10,10]], [1,-1]), [0,0]));
assert(approx(line_closest_point([[-10,-10], [10,10]], [-1,1]), [0,0]));
assert(approx(line_closest_point([[-10,-20], [10,20]], [1,2]+[-2,1]), [1,2]));
assert(approx(line_closest_point([[-10,-20], [10,20]], [1,2]+[2,-1]), [1,2]));
assert(approx(line_closest_point([[-10,-20], [10,20]], [13,31]), [15,30]));
assert(approx(line_closest_point([[-10,-10], [10,10]], [1,-1],SEGMENT), [0,0]));
assert(approx(line_closest_point([[-10,-10], [10,10]], [-1,1],SEGMENT), [0,0]));
assert(approx(line_closest_point([[-10,-20], [10,20]], [1,2]+[-2,1],SEGMENT), [1,2]));
assert(approx(line_closest_point([[-10,-20], [10,20]], [1,2]+[2,-1],SEGMENT), [1,2]));
assert(approx(line_closest_point([[-10,-20], [10,20]], [13,31],SEGMENT), [10,20]));
assert(approx(line_closest_point([[-10,-20], [10,20]], [15,25],SEGMENT), [10,20]));
}
*test_line_closest_point();
module test_segment_closest_point() {
assert(approx(segment_closest_point([[-10,-10], [10,10]], [1,-1]), [0,0]));
assert(approx(segment_closest_point([[-10,-10], [10,10]], [-1,1]), [0,0]));
assert(approx(segment_closest_point([[-10,-20], [10,20]], [1,2]+[-2,1]), [1,2]));
assert(approx(segment_closest_point([[-10,-20], [10,20]], [1,2]+[2,-1]), [1,2]));
assert(approx(segment_closest_point([[-10,-20], [10,20]], [13,31]), [10,20]));
assert(approx(segment_closest_point([[-10,-20], [10,20]], [15,25]), [10,20]));
}
*test_segment_closest_point();
module test_circle_2tangents() {
//** missing tests with arg tangent=true
assert(approx(circle_2tangents([10,10],[0,0],[10,-10],r=10/sqrt(2))[0],[10,0]));
@ -623,75 +556,6 @@ module test_circle_point_tangents() {
*test_circle_point_tangents();
module test_tri_calc() {
sides = rands(1,100,100,seed_value=8888);
for (p=pair(sides,true)) {
opp = p[0];
adj = p[1];
hyp = norm([opp,adj]);
ang = acos(adj/hyp);
ang2 = 90-ang;
expected = [adj, opp, hyp, ang, ang2];
assert(approx(tri_calc(adj=adj, hyp=hyp), expected));
assert(approx(tri_calc(opp=opp, hyp=hyp), expected));
assert(approx(tri_calc(adj=adj, opp=opp), expected));
assert(approx(tri_calc(adj=adj, ang=ang), expected));
assert(approx(tri_calc(opp=opp, ang=ang), expected, eps=1e-8));
assert(approx(tri_calc(hyp=hyp, ang=ang), expected));
assert(approx(tri_calc(adj=adj, ang2=ang2), expected));
assert(approx(tri_calc(opp=opp, ang2=ang2), expected, eps=1e-8));
assert(approx(tri_calc(hyp=hyp, ang2=ang2), expected));
}
}
*test_tri_calc();
module test_tri_functions() {
sides = rands(1,100,100,seed_value=8181);
for (p = pair(sides,true)) {
adj = p.x;
opp = p.y;
hyp = norm([opp,adj]);
ang = atan2(opp,adj);
assert_approx(hyp_opp_to_adj(hyp,opp), adj);
assert_approx(hyp_ang_to_adj(hyp,ang), adj);
assert_approx(opp_ang_to_adj(opp,ang), adj);
assert_approx(hyp_adj_to_opp(hyp,adj), opp);
assert_approx(hyp_ang_to_opp(hyp,ang), opp);
assert_approx(adj_ang_to_opp(adj,ang), opp);
assert_approx(adj_opp_to_hyp(adj,opp), hyp);
assert_approx(adj_ang_to_hyp(adj,ang), hyp);
assert_approx(opp_ang_to_hyp(opp,ang), hyp);
assert_approx(hyp_adj_to_ang(hyp,adj), ang);
assert_approx(hyp_opp_to_ang(hyp,opp), ang);
assert_approx(adj_opp_to_ang(adj,opp), ang);
}
}
*test_tri_functions();
module test_hyp_opp_to_adj() nil(); // Covered in test_tri_functions()
module test_hyp_ang_to_adj() nil(); // Covered in test_tri_functions()
module test_opp_ang_to_adj() nil(); // Covered in test_tri_functions()
module test_hyp_adj_to_opp() nil(); // Covered in test_tri_functions()
module test_hyp_ang_to_opp() nil(); // Covered in test_tri_functions()
module test_adj_ang_to_opp() nil(); // Covered in test_tri_functions()
module test_adj_opp_to_hyp() nil(); // Covered in test_tri_functions()
module test_adj_ang_to_hyp() nil(); // Covered in test_tri_functions()
module test_opp_ang_to_hyp() nil(); // Covered in test_tri_functions()
module test_hyp_adj_to_ang() nil(); // Covered in test_tri_functions()
module test_hyp_opp_to_ang() nil(); // Covered in test_tri_functions()
module test_adj_opp_to_ang() nil(); // Covered in test_tri_functions()
module test_triangle_area() {
assert(abs(triangle_area([0,0], [0,10], [10,0]) + 50) < EPSILON);
assert(abs(triangle_area([0,0], [0,10], [0,15])) < EPSILON);
assert(abs(triangle_area([0,0], [10,0], [0,10]) - 50) < EPSILON);
}
*test_triangle_area();
module test_plane3pt() {
assert_approx(plane3pt([0,0,20], [0,10,10], [0,0,0]), [1,0,0,0]);
assert_approx(plane3pt([2,0,20], [2,10,10], [2,0,0]), [1,0,0,2]);
@ -727,6 +591,16 @@ module test_plane_from_points() {
*test_plane_from_points();
module test_polygon_normal() {
circ = path3d(circle($fn=37, r=3));
assert_approx(polygon_normal(circ), UP);
assert_approx(polygon_normal(rot(from=UP,to=[1,2,3],p=circ)), unit([1,2,3]));
assert_approx(polygon_normal(rot(from=UP,to=[4,-2,3],p=reverse(circ))), -unit([4,-2,3]));
assert_approx(polygon_normal(path3d([[0,0], [10,10], [11,10], [0,-1], [-1,1]])), UP);
}
*test_polygon_normal();
module test_plane_normal() {
assert_approx(plane_normal(plane3pt([0,0,20], [0,10,10], [0,0,0])), [1,0,0]);
assert_approx(plane_normal(plane3pt([2,0,20], [2,10,10], [2,0,0])), [1,0,0]);
@ -784,66 +658,92 @@ module test_polygon_line_intersection() {
undef, info);
assert_approx(polygon_line_intersection(polygnr,linegnr,bounded=[false,false]),
trnsl, info);
sq = path3d(square(10));
pentagram = 10*path3d(turtle(["move",10,"left",144], repeat=4));
for (tran = [ident(4), skew(sxy=1.2)*scale([.9,1,1.2])*yrot(14)*zrot(37)*xrot(9)])
{
assert_approx(polygon_line_intersection(apply(tran,sq),apply(tran,[[5,5,-1], [5,5,10]])), apply(tran, [5,5,0]));
assert_approx(polygon_line_intersection(apply(tran,sq),apply(tran,[[5,5,1], [5,5,10]])), apply(tran, [5,5,0]));
assert(undef==polygon_line_intersection(apply(tran,sq),apply(tran,[[5,5,1], [5,5,10]]),RAY));
assert(undef==polygon_line_intersection(apply(tran,sq),apply(tran,[[11,11,-1],[11,11,1]])));
assert_approx(polygon_line_intersection(apply(tran,sq),apply(tran,[[5,0,-10], [5,0,10]])), apply(tran, [5,0,0]));
assert_equal(polygon_line_intersection(apply(tran,sq),apply(tran,[[5,0,1], [5,0,10]]),RAY), undef);
assert_approx(polygon_line_intersection(apply(tran,sq),apply(tran,[[10,0,1],[10,0,10]])), apply(tran, [10,0,0]));
assert_approx(polygon_line_intersection(apply(tran,sq),apply(tran,[[1,5,0],[9,6,0]])), apply(tran, [[[0,4.875,0],[10,6.125,0]]]));
assert_approx(polygon_line_intersection(apply(tran,sq),apply(tran,[[1,5,0],[9,6,0]]),SEGMENT), apply(tran, [[[1,5,0],[9,6,0]]]));
assert_approx(polygon_line_intersection(apply(tran,sq),apply(tran,[[-1,-1,0],[8,8,0]])), apply(tran, [[[0,0,0],[10,10,0]]]));
assert_approx(polygon_line_intersection(apply(tran,sq),apply(tran,[[-1,-1,0],[8,8,0]]),SEGMENT), apply(tran, [[[0,0,0],[8,8,0]]]));
assert_approx(polygon_line_intersection(apply(tran,sq),apply(tran,[[-1,-1,0],[8,8,0]]),RAY), apply(tran, [[[0,0,0],[10,10,0]]]));
assert_approx(polygon_line_intersection(apply(tran,sq),apply(tran,[[-2,4,0], [12,11,0]]),RAY), apply(tran, [[[0,5,0],[10,10,0]]]));
assert_equal(polygon_line_intersection(apply(tran,sq),apply(tran,[[-20,0,0],[20,40,0]]),RAY), undef);
assert_approx(polygon_line_intersection(apply(tran,sq),apply(tran,[[-1,0,0],[11,0,0]])), apply(tran, [[[0,0,0],[10,0,0]]]));
}
assert_approx(polygon_line_intersection(path2d(sq),[[1,5],[9,6]],SEGMENT), [[[1,5],[9,6]]]);
assert_approx(polygon_line_intersection(path2d(sq),[[1,5],[9,6]],LINE), [[[0,4.875],[10,6.125]]]);
assert_approx(polygon_line_intersection(pentagram,[[50,10,-4],[54,12,4]], nonzero=true), [52,11,0]);
assert_equal(polygon_line_intersection(pentagram,[[50,10,-4],[54,12,4]], nonzero=false), undef);
assert_approx(polygon_line_intersection(pentagram,[[50,-10,-4],[54,-12,4]], nonzero=true), [52,-11,0]);
assert_approx(polygon_line_intersection(pentagram,[[50,-10,-4],[54,-12,4]], nonzero=false), [52,-11,0]);
assert_approx(polygon_line_intersection(star(8,step=3,od=10), [[-5,3], [5,3]]),
[[[-3.31370849898, 3], [-2.24264068712, 3]],
[[-0.828427124746, 3], [0.828427124746, 3]],
[[2.24264068712, 3], [3.31370849898, 3]]]);
tran = skew(sxy=1.2)*scale([.9,1,1.2])*yrot(14)*zrot(37)*xrot(9);
// assemble multiple edges into one edge
assert_approx(polygon_line_intersection(star(r=15,n=8,step=2), [[20,-5],[-5,20]]), [[[15,0],[0,15]]]);
assert_approx(polygon_line_intersection(apply(tran,path3d(star(r=15,n=8,step=2))), apply(tran,[[20,-5,0],[-5,20,0]])), apply(tran,[[[15,0,0],[0,15,0]]]));
// line going the other direction
assert_approx(polygon_line_intersection(star(r=15,n=8,step=2), [[-5,20],[20,-5]]), [[[0,15],[15,0]]]);
assert_approx(polygon_line_intersection(apply(tran,path3d(star(r=15,n=8,step=2))), apply(tran,[[-5,20,0],[20,-5,0]])),apply(tran, [[[0,15,0],[15,0,0]]]));
// single point
assert_approx(polygon_line_intersection(hexagon(r=15), [[15,-10],[15,13]], RAY), [[[15,0]]]);
assert_approx(polygon_line_intersection(apply(tran,path3d(hexagon(r=15))), apply(tran,[[15,-10,0],[15,13,0]]), RAY),
[[apply(tran,[15,0,0])]]);
// two points
assert_approx(polygon_line_intersection(star(r=15,n=8,step=3), rot(22.5,p=[[15,-10],[15,20]],cp=[15,0])),
[[[15,0]], [[10.6066017178, 10.6066017178]]]);
assert_approx(polygon_line_intersection(apply(tran,path3d(star(r=15,n=8,step=3))), apply(tran,rot(22.5,p=[[15,-10,0],[15,20,0]],cp=[15,0,0]))),
[[apply(tran,[15,0,0])], [apply(tran,[10.6066017178, 10.6066017178,0])]]);
// two segments and one point
star7 = star(r=25,ir=9,n=7);
assert_approx(polygon_line_intersection(star7, [left(10,p=star7[8]), right(50,p=star7[8])]),
[[[-22.5242216976, 10.8470934779]],
[[-5.60077322195, 10.8470934779], [0.997372374838, 10.8470934779]],
[[4.61675816681, 10.8470934779], [11.4280421589, 10.8470934779]]]);
assert_approx(polygon_line_intersection(apply(tran,path3d(star7)),
apply(tran, path3d([left(10,p=star7[8]), right(50,p=star7[8])]))),
[[apply(tran,[-22.5242216976, 10.8470934779,0])],
apply(tran,[[-5.60077322195, 10.8470934779,0], [0.997372374838, 10.8470934779,0]]),
apply(tran,[[4.61675816681, 10.8470934779,0], [11.4280421589, 10.8470934779,0]])]);
}
*test_polygon_line_intersection();
module test_coplanar() {
assert(coplanar([ [5,5,1],[0,0,1],[-1,-1,1] ]) == false);
assert(coplanar([ [5,5,1],[0,0,0],[-1,-1,1] ]) == true);
assert(coplanar([ [0,0,0],[1,0,1],[1,1,1], [0,1,2] ]) == false);
assert(coplanar([ [0,0,0],[1,0,1],[1,1,2], [0,1,1] ]) == true);
module test_is_coplanar() {
assert(is_coplanar([ [5,5,1],[0,0,1],[-1,-1,1] ]) == false);
assert(is_coplanar([ [5,5,1],[0,0,0],[-1,-1,1] ]) == true);
assert(is_coplanar([ [0,0,0],[1,0,1],[1,1,1], [0,1,2] ]) == false);
assert(is_coplanar([ [0,0,0],[1,0,1],[1,1,2], [0,1,1] ]) == true);
}
*test_coplanar();
*test_is_coplanar();
module test_in_front_of_plane() {
module test__is_point_above_plane() {
plane = plane3pt([0,0,0], [0,10,10], [10,0,10]);
assert(in_front_of_plane(plane, [5,5,10]) == false);
assert(in_front_of_plane(plane, [-5,0,0]) == true);
assert(in_front_of_plane(plane, [5,0,0]) == false);
assert(in_front_of_plane(plane, [0,-5,0]) == true);
assert(in_front_of_plane(plane, [0,5,0]) == false);
assert(in_front_of_plane(plane, [0,0,5]) == true);
assert(in_front_of_plane(plane, [0,0,-5]) == false);
assert(_is_point_above_plane(plane, [5,5,10]) == false);
assert(_is_point_above_plane(plane, [-5,0,0]) == true);
assert(_is_point_above_plane(plane, [5,0,0]) == false);
assert(_is_point_above_plane(plane, [0,-5,0]) == true);
assert(_is_point_above_plane(plane, [0,5,0]) == false);
assert(_is_point_above_plane(plane, [0,0,5]) == true);
assert(_is_point_above_plane(plane, [0,0,-5]) == false);
}
*test_in_front_of_plane();
*test__is_point_above_plane();
module test_is_path() {
assert(is_path([[1,2,3],[4,5,6]]));
assert(is_path([[1,2,3],[4,5,6],[7,8,9]]));
assert(!is_path(123));
assert(!is_path("foo"));
assert(!is_path(true));
assert(!is_path([]));
assert(!is_path([[]]));
assert(!is_path([["foo","bar","baz"]]));
assert(!is_path([[1,2,3]]));
assert(!is_path([["foo","bar","baz"],["qux","quux","quuux"]]));
}
*test_is_path();
module test_is_closed_path() {
assert(!is_closed_path([[1,2,3],[4,5,6],[1,8,9]]));
assert(is_closed_path([[1,2,3],[4,5,6],[1,8,9],[1,2,3]]));
}
*test_is_closed_path();
module test_close_path() {
assert(close_path([[1,2,3],[4,5,6],[1,8,9]]) == [[1,2,3],[4,5,6],[1,8,9],[1,2,3]]);
assert(close_path([[1,2,3],[4,5,6],[1,8,9],[1,2,3]]) == [[1,2,3],[4,5,6],[1,8,9],[1,2,3]]);
}
*test_close_path();
module test_cleanup_path() {
assert(cleanup_path([[1,2,3],[4,5,6],[1,8,9]]) == [[1,2,3],[4,5,6],[1,8,9]]);
assert(cleanup_path([[1,2,3],[4,5,6],[1,8,9],[1,2,3]]) == [[1,2,3],[4,5,6],[1,8,9]]);
}
*test_cleanup_path();
module test_polygon_area() {
@ -851,19 +751,23 @@ module test_polygon_area() {
assert(approx(polygon_area(circle(r=50,$fn=1000),signed=true), -PI*50*50, eps=0.1));
assert(approx(polygon_area(rot([13,27,75],
p=path3d(circle(r=50,$fn=1000),fill=23)),
signed=true), -PI*50*50, eps=0.1));
signed=true), PI*50*50, eps=0.1));
assert(abs(triangle_area([0,0], [0,10], [10,0]) + 50) < EPSILON);
assert(abs(triangle_area([0,0], [0,10], [0,15])) < EPSILON);
assert(abs(triangle_area([0,0], [10,0], [0,10]) - 50) < EPSILON);
}
*test_polygon_area();
module test_is_convex_polygon() {
assert(is_convex_polygon([[1,1],[-1,1],[-1,-1],[1,-1]]));
assert(is_convex_polygon(circle(r=50,$fn=1000)));
assert(is_convex_polygon(rot([50,120,30], p=path3d(circle(1,$fn=50)))));
assert(!is_convex_polygon([[1,1],[0,0],[-1,1],[-1,-1],[1,-1]]));
assert(!is_convex_polygon([for (i=[0:36]) let(a=-i*10) (10+i)*[cos(a),sin(a)]])); // spiral
module test_is_polygon_convex() {
assert(is_polygon_convex([[1,1],[-1,1],[-1,-1],[1,-1]]));
assert(is_polygon_convex(circle(r=50,$fn=1000)));
assert(is_polygon_convex(rot([50,120,30], p=path3d(circle(1,$fn=50)))));
assert(!is_polygon_convex([[1,1],[0,0],[-1,1],[-1,-1],[1,-1]]));
assert(!is_polygon_convex([for (i=[0:36]) let(a=-i*10) (10+i)*[cos(a),sin(a)]])); // spiral
}
*test_is_convex_polygon();
*test_is_polygon_convex();
module test_polygon_shift() {
@ -874,15 +778,6 @@ module test_polygon_shift() {
*test_polygon_shift();
module test_polygon_shift_to_closest_point() {
path = [[1,1],[-1,1],[-1,-1],[1,-1]];
assert(polygon_shift_to_closest_point(path,[1.1,1.1]) == [[1,1],[-1,1],[-1,-1],[1,-1]]);
assert(polygon_shift_to_closest_point(path,[-1.1,1.1]) == [[-1,1],[-1,-1],[1,-1],[1,1]]);
assert(polygon_shift_to_closest_point(path,[-1.1,-1.1]) == [[-1,-1],[1,-1],[1,1],[-1,1]]);
assert(polygon_shift_to_closest_point(path,[1.1,-1.1]) == [[1,-1],[1,1],[-1,1],[-1,-1]]);
}
*test_polygon_shift_to_closest_point();
module test_reindex_polygon() {
pent = subdivide_path([for(i=[0:4])[sin(72*i),cos(72*i)]],5);
@ -933,19 +828,6 @@ module test_centroid() {
*test_centroid();
module test_simplify_path() {
path = [[-20,-20], [-10,-20], [0,-10], [10,0], [20,10], [20,20], [15,30]];
assert(simplify_path(path) == [[-20,-20], [-10,-20], [20,10], [20,20], [15,30]]);
}
*test_simplify_path();
module test_simplify_path_indexed() {
pts = [[10,0], [0,-10], [20,20], [20,10], [-20,-20], [15,30], [-10,-20]];
path = [4,6,1,0,3,2,5];
assert(simplify_path_indexed(pts, path) == [4,6,3,2,5]);
}
*test_simplify_path_indexed();
module test_point_in_polygon() {
@ -973,69 +855,14 @@ module test_point_in_polygon() {
*test_point_in_polygon();
module test_pointlist_bounds() {
pts = [
[-53,27,12],
[-63,97,36],
[84,-32,-5],
[63,-24,42],
[23,57,-42]
];
assert(pointlist_bounds(pts) == [[-63,-32,-42], [84,97,42]]);
pts2d = [
[-53,12],
[-63,36],
[84,-5],
[63,42],
[23,-42]
];
assert(pointlist_bounds(pts2d) == [[-63,-42],[84,42]]);
pts5d = [
[-53, 27, 12,-53, 12],
[-63, 97, 36,-63, 36],
[ 84,-32, -5, 84, -5],
[ 63,-24, 42, 63, 42],
[ 23, 57,-42, 23,-42]
];
assert(pointlist_bounds(pts5d) == [[-63,-32,-42,-63,-42],[84,97,42,84,42]]);
assert(pointlist_bounds([[3,4,5,6]]), [[3,4,5,6],[3,4,5,6]]);
module test_is_polygon_clockwise() {
assert(is_polygon_clockwise([[-1,1],[1,1],[1,-1],[-1,-1]]));
assert(!is_polygon_clockwise([[1,1],[-1,1],[-1,-1],[1,-1]]));
assert(is_polygon_clockwise(circle(d=100)));
assert(is_polygon_clockwise(square(100)));
}
*test_pointlist_bounds();
module test_closest_point() {
ptlist = [for (i=count(100)) rands(-100,100,2,seed_value=8463+i)];
testpts = [for (i=count(100)) rands(-100,100,2,seed_value=6834+i)];
for (pt = testpts) {
pidx = closest_point(pt,ptlist);
dists = [for (p=ptlist) norm(pt-p)];
mindist = min(dists);
assert(mindist == dists[pidx]);
}
}
*test_closest_point();
module test_furthest_point() {
ptlist = [for (i=count(100)) rands(-100,100,2,seed_value=8463+i)];
testpts = [for (i=count(100)) rands(-100,100,2,seed_value=6834+i)];
for (pt = testpts) {
pidx = furthest_point(pt,ptlist);
dists = [for (p=ptlist) norm(pt-p)];
mindist = max(dists);
assert(mindist == dists[pidx]);
}
}
*test_furthest_point();
module test_polygon_is_clockwise() {
assert(polygon_is_clockwise([[-1,1],[1,1],[1,-1],[-1,-1]]));
assert(!polygon_is_clockwise([[1,1],[-1,1],[-1,-1],[1,-1]]));
assert(polygon_is_clockwise(circle(d=100)));
assert(polygon_is_clockwise(square(100)));
}
*test_polygon_is_clockwise();
*test_is_polygon_clockwise();
module test_clockwise_polygon() {
@ -1065,17 +892,6 @@ module test_reverse_polygon() {
*test_reverse_polygon();
module test_is_region() {
assert(is_region([circle(d=10),square(10)]));
assert(is_region([circle(d=10),square(10),circle(d=50)]));
assert(is_region([square(10)]));
assert(!is_region([]));
assert(!is_region(23));
assert(!is_region(true));
assert(!is_region("foo"));
}
*test_is_region();
module test_convex_distance() {
// 2D
c1 = circle(10,$fn=24);

View file

@ -13,6 +13,8 @@ module test_quant() {
assert_equal(quant(3,3), 3);
assert_equal(quant(4,3), 3);
assert_equal(quant(7,3), 6);
assert_equal(quant(12,2.5), 12.5);
assert_equal(quant(11,2.5), 10.0);
assert_equal(quant([12,13,13.1,14,14.1,15,16],4), [12,12,12,16,16,16,16]);
assert_equal(quant([9,10,10.4,10.5,11,12],3), [9,9,9,12,12,12]);
assert_equal(quant([[9,10,10.4],[10.5,11,12]],3), [[9,9,9],[12,12,12]]);
@ -31,6 +33,8 @@ module test_quantdn() {
assert_equal(quantdn(3,3), 3);
assert_equal(quantdn(4,3), 3);
assert_equal(quantdn(7,3), 6);
assert_equal(quantdn(12,2.5), 10.0);
assert_equal(quantdn(11,2.5), 10.0);
assert_equal(quantdn([12,13,13.1,14,14.1,15,16],4), [12,12,12,12,12,12,16]);
assert_equal(quantdn([9,10,10.4,10.5,11,12],3), [9,9,9,9,9,12]);
assert_equal(quantdn([[9,10,10.4],[10.5,11,12]],3), [[9,9,9],[9,9,12]]);
@ -49,6 +53,8 @@ module test_quantup() {
assert_equal(quantup(3,3), 3);
assert_equal(quantup(4,3), 6);
assert_equal(quantup(7,3), 9);
assert_equal(quantup(12,2.5), 12.5);
assert_equal(quantup(11,2.5), 12.5);
assert_equal(quantup([12,13,13.1,14,14.1,15,16],4), [12,16,16,16,16,16,16]);
assert_equal(quantup([9,10,10.4,10.5,11,12],3), [9,12,12,12,12,12]);
assert_equal(quantup([[9,10,10.4],[10.5,11,12]],3), [[9,12,12],[12,12,12]]);

45
tests/test_paths.scad Normal file
View file

@ -0,0 +1,45 @@
include<../std.scad>
module test_is_path() {
assert(is_path([[1,2,3],[4,5,6]]));
assert(is_path([[1,2,3],[4,5,6],[7,8,9]]));
assert(!is_path(123));
assert(!is_path("foo"));
assert(!is_path(true));
assert(!is_path([]));
assert(!is_path([[]]));
assert(!is_path([["foo","bar","baz"]]));
assert(!is_path([[1,2,3]]));
assert(!is_path([["foo","bar","baz"],["qux","quux","quuux"]]));
}
test_is_path();
module test_is_closed_path() {
assert(!is_closed_path([[1,2,3],[4,5,6],[1,8,9]]));
assert(is_closed_path([[1,2,3],[4,5,6],[1,8,9],[1,2,3]]));
}
test_is_closed_path();
module test_close_path() {
assert(close_path([[1,2,3],[4,5,6],[1,8,9]]) == [[1,2,3],[4,5,6],[1,8,9],[1,2,3]]);
assert(close_path([[1,2,3],[4,5,6],[1,8,9],[1,2,3]]) == [[1,2,3],[4,5,6],[1,8,9],[1,2,3]]);
}
test_close_path();
module test_cleanup_path() {
assert(cleanup_path([[1,2,3],[4,5,6],[1,8,9]]) == [[1,2,3],[4,5,6],[1,8,9]]);
assert(cleanup_path([[1,2,3],[4,5,6],[1,8,9],[1,2,3]]) == [[1,2,3],[4,5,6],[1,8,9]]);
}
test_cleanup_path();
module test_path_merge_collinear() {
path = [[-20,-20], [-10,-20], [0,-10], [10,0], [20,10], [20,20], [15,30]];
assert(path_merge_collinear(path) == [[-20,-20], [-10,-20], [20,10], [20,20], [15,30]]);
}
test_path_merge_collinear();

View file

@ -1,65 +0,0 @@
include <../std.scad>
module test_square() {
assert(square(100, center=true) == [[50,-50],[-50,-50],[-50,50],[50,50]]);
assert(square(100, center=false) == [[100,0],[0,0],[0,100],[100,100]]);
assert(square(100, anchor=FWD+LEFT) == [[100,0],[0,0],[0,100],[100,100]]);
assert(square(100, anchor=BACK+RIGHT) == [[0,-100],[-100,-100],[-100,0],[0,0]]);
}
test_square();
module test_circle() {
for (pt = circle(d=200)) {
assert(approx(norm(pt),100));
}
for (pt = circle(r=100)) {
assert(approx(norm(pt),100));
}
assert(polygon_is_clockwise(circle(d=200)));
assert(polygon_is_clockwise(circle(r=100)));
assert(len(circle(d=100,$fn=6)) == 6);
assert(len(circle(d=100,$fn=36)) == 36);
}
test_circle();
module test_cube() {
assert_equal(cube(100,center=true), [[[-50,-50,-50],[50,-50,-50],[50,50,-50],[-50,50,-50],[-50,-50,50],[50,-50,50],[50,50,50],[-50,50,50]],[[0,1,2],[0,2,3],[0,4,5],[0,5,1],[1,5,6],[1,6,2],[2,6,7],[2,7,3],[3,7,4],[3,4,0],[6,4,7],[6,5,4]]]);
assert_equal(cube([60,80,100],center=true), [[[-30,-40,-50],[30,-40,-50],[30,40,-50],[-30,40,-50],[-30,-40,50],[30,-40,50],[30,40,50],[-30,40,50]],[[0,1,2],[0,2,3],[0,4,5],[0,5,1],[1,5,6],[1,6,2],[2,6,7],[2,7,3],[3,7,4],[3,4,0],[6,4,7],[6,5,4]]]);
assert_equal(cube([60,80,100],anchor=CENTER), [[[-30,-40,-50],[30,-40,-50],[30,40,-50],[-30,40,-50],[-30,-40,50],[30,-40,50],[30,40,50],[-30,40,50]],[[0,1,2],[0,2,3],[0,4,5],[0,5,1],[1,5,6],[1,6,2],[2,6,7],[2,7,3],[3,7,4],[3,4,0],[6,4,7],[6,5,4]]]);
assert_equal(cube([60,80,100],center=false), [[[0,0,0],[60,0,0],[60,80,0],[0,80,0],[0,0,100],[60,0,100],[60,80,100],[0,80,100]],[[0,1,2],[0,2,3],[0,4,5],[0,5,1],[1,5,6],[1,6,2],[2,6,7],[2,7,3],[3,7,4],[3,4,0],[6,4,7],[6,5,4]]]);
assert_equal(cube([60,80,100]), [[[0,0,0],[60,0,0],[60,80,0],[0,80,0],[0,0,100],[60,0,100],[60,80,100],[0,80,100]],[[0,1,2],[0,2,3],[0,4,5],[0,5,1],[1,5,6],[1,6,2],[2,6,7],[2,7,3],[3,7,4],[3,4,0],[6,4,7],[6,5,4]]]);
assert_equal(cube([60,80,100],anchor=ALLNEG), [[[0,0,0],[60,0,0],[60,80,0],[0,80,0],[0,0,100],[60,0,100],[60,80,100],[0,80,100]],[[0,1,2],[0,2,3],[0,4,5],[0,5,1],[1,5,6],[1,6,2],[2,6,7],[2,7,3],[3,7,4],[3,4,0],[6,4,7],[6,5,4]]]);
assert_equal(cube([60,80,100],anchor=TOP), [[[-30,-40,-100],[30,-40,-100],[30,40,-100],[-30,40,-100],[-30,-40,0],[30,-40,0],[30,40,0],[-30,40,0]],[[0,1,2],[0,2,3],[0,4,5],[0,5,1],[1,5,6],[1,6,2],[2,6,7],[2,7,3],[3,7,4],[3,4,0],[6,4,7],[6,5,4]]]);
}
test_cube();
module test_cylinder() {
$fn=12;
assert_approx(cylinder(r=40,h=100,center=true), [[[40,0,-50],[34.6410161514,-20,-50],[20,-34.6410161514,-50],[0,-40,-50],[-20,-34.6410161514,-50],[-34.6410161514,-20,-50],[-40,0,-50],[-34.6410161514,20,-50],[-20,34.6410161514,-50],[0,40,-50],[20,34.6410161514,-50],[34.6410161514,20,-50],[40,0,50],[34.6410161514,-20,50],[20,-34.6410161514,50],[0,-40,50],[-20,-34.6410161514,50],[-34.6410161514,-20,50],[-40,0,50],[-34.6410161514,20,50],[-20,34.6410161514,50],[0,40,50],[20,34.6410161514,50],[34.6410161514,20,50]],[[11,10,9,8,7,6,5,4,3,2,1,0],[0,13,12],[1,14,13],[2,15,14],[3,16,15],[4,17,16],[5,18,17],[6,19,18],[7,20,19],[8,21,20],[9,22,21],[10,23,22],[11,12,23],[0,1,13],[1,2,14],[2,3,15],[3,4,16],[4,5,17],[5,6,18],[6,7,19],[7,8,20],[8,9,21],[9,10,22],[10,11,23],[11,0,12],[12,13,14,15,16,17,18,19,20,21,22,23]]]);
assert_approx(cylinder(d=80,h=100,center=true), [[[40,0,-50],[34.6410161514,-20,-50],[20,-34.6410161514,-50],[0,-40,-50],[-20,-34.6410161514,-50],[-34.6410161514,-20,-50],[-40,0,-50],[-34.6410161514,20,-50],[-20,34.6410161514,-50],[0,40,-50],[20,34.6410161514,-50],[34.6410161514,20,-50],[40,0,50],[34.6410161514,-20,50],[20,-34.6410161514,50],[0,-40,50],[-20,-34.6410161514,50],[-34.6410161514,-20,50],[-40,0,50],[-34.6410161514,20,50],[-20,34.6410161514,50],[0,40,50],[20,34.6410161514,50],[34.6410161514,20,50]],[[11,10,9,8,7,6,5,4,3,2,1,0],[0,13,12],[1,14,13],[2,15,14],[3,16,15],[4,17,16],[5,18,17],[6,19,18],[7,20,19],[8,21,20],[9,22,21],[10,23,22],[11,12,23],[0,1,13],[1,2,14],[2,3,15],[3,4,16],[4,5,17],[5,6,18],[6,7,19],[7,8,20],[8,9,21],[9,10,22],[10,11,23],[11,0,12],[12,13,14,15,16,17,18,19,20,21,22,23]]]);
assert_approx(cylinder(d=80,h=100,anchor=CENTER), [[[40,0,-50],[34.6410161514,-20,-50],[20,-34.6410161514,-50],[0,-40,-50],[-20,-34.6410161514,-50],[-34.6410161514,-20,-50],[-40,0,-50],[-34.6410161514,20,-50],[-20,34.6410161514,-50],[0,40,-50],[20,34.6410161514,-50],[34.6410161514,20,-50],[40,0,50],[34.6410161514,-20,50],[20,-34.6410161514,50],[0,-40,50],[-20,-34.6410161514,50],[-34.6410161514,-20,50],[-40,0,50],[-34.6410161514,20,50],[-20,34.6410161514,50],[0,40,50],[20,34.6410161514,50],[34.6410161514,20,50]],[[11,10,9,8,7,6,5,4,3,2,1,0],[0,13,12],[1,14,13],[2,15,14],[3,16,15],[4,17,16],[5,18,17],[6,19,18],[7,20,19],[8,21,20],[9,22,21],[10,23,22],[11,12,23],[0,1,13],[1,2,14],[2,3,15],[3,4,16],[4,5,17],[5,6,18],[6,7,19],[7,8,20],[8,9,21],[9,10,22],[10,11,23],[11,0,12],[12,13,14,15,16,17,18,19,20,21,22,23]]]);
assert_approx(cylinder(d=80,h=100,center=false), [[[40,0,0],[34.6410161514,-20,0],[20,-34.6410161514,0],[0,-40,0],[-20,-34.6410161514,0],[-34.6410161514,-20,0],[-40,0,0],[-34.6410161514,20,0],[-20,34.6410161514,0],[0,40,0],[20,34.6410161514,0],[34.6410161514,20,0],[40,0,100],[34.6410161514,-20,100],[20,-34.6410161514,100],[0,-40,100],[-20,-34.6410161514,100],[-34.6410161514,-20,100],[-40,0,100],[-34.6410161514,20,100],[-20,34.6410161514,100],[0,40,100],[20,34.6410161514,100],[34.6410161514,20,100]],[[11,10,9,8,7,6,5,4,3,2,1,0],[0,13,12],[1,14,13],[2,15,14],[3,16,15],[4,17,16],[5,18,17],[6,19,18],[7,20,19],[8,21,20],[9,22,21],[10,23,22],[11,12,23],[0,1,13],[1,2,14],[2,3,15],[3,4,16],[4,5,17],[5,6,18],[6,7,19],[7,8,20],[8,9,21],[9,10,22],[10,11,23],[11,0,12],[12,13,14,15,16,17,18,19,20,21,22,23]]]);
assert_approx(cylinder(d=80,h=100,anchor=BOT), [[[40,0,0],[34.6410161514,-20,0],[20,-34.6410161514,0],[0,-40,0],[-20,-34.6410161514,0],[-34.6410161514,-20,0],[-40,0,0],[-34.6410161514,20,0],[-20,34.6410161514,0],[0,40,0],[20,34.6410161514,0],[34.6410161514,20,0],[40,0,100],[34.6410161514,-20,100],[20,-34.6410161514,100],[0,-40,100],[-20,-34.6410161514,100],[-34.6410161514,-20,100],[-40,0,100],[-34.6410161514,20,100],[-20,34.6410161514,100],[0,40,100],[20,34.6410161514,100],[34.6410161514,20,100]],[[11,10,9,8,7,6,5,4,3,2,1,0],[0,13,12],[1,14,13],[2,15,14],[3,16,15],[4,17,16],[5,18,17],[6,19,18],[7,20,19],[8,21,20],[9,22,21],[10,23,22],[11,12,23],[0,1,13],[1,2,14],[2,3,15],[3,4,16],[4,5,17],[5,6,18],[6,7,19],[7,8,20],[8,9,21],[9,10,22],[10,11,23],[11,0,12],[12,13,14,15,16,17,18,19,20,21,22,23]]]);
assert_approx(cylinder(d=80,h=100), [[[40,0,0],[34.6410161514,-20,0],[20,-34.6410161514,0],[0,-40,0],[-20,-34.6410161514,0],[-34.6410161514,-20,0],[-40,0,0],[-34.6410161514,20,0],[-20,34.6410161514,0],[0,40,0],[20,34.6410161514,0],[34.6410161514,20,0],[40,0,100],[34.6410161514,-20,100],[20,-34.6410161514,100],[0,-40,100],[-20,-34.6410161514,100],[-34.6410161514,-20,100],[-40,0,100],[-34.6410161514,20,100],[-20,34.6410161514,100],[0,40,100],[20,34.6410161514,100],[34.6410161514,20,100]],[[11,10,9,8,7,6,5,4,3,2,1,0],[0,13,12],[1,14,13],[2,15,14],[3,16,15],[4,17,16],[5,18,17],[6,19,18],[7,20,19],[8,21,20],[9,22,21],[10,23,22],[11,12,23],[0,1,13],[1,2,14],[2,3,15],[3,4,16],[4,5,17],[5,6,18],[6,7,19],[7,8,20],[8,9,21],[9,10,22],[10,11,23],[11,0,12],[12,13,14,15,16,17,18,19,20,21,22,23]]]);
}
test_cylinder();
module test_sphere() {
$fn=6;
assert_approx(sphere(r=40), [[[20,0,34.6410161514],[10,17.3205080757,34.6410161514],[-10,17.3205080757,34.6410161514],[-20,0,34.6410161514],[-10,-17.3205080757,34.6410161514],[10,-17.3205080757,34.6410161514],[40,0,0],[20,34.6410161514,0],[-20,34.6410161514,0],[-40,0,0],[-20,-34.6410161514,0],[20,-34.6410161514,0],[20,0,-34.6410161514],[10,17.3205080757,-34.6410161514],[-10,17.3205080757,-34.6410161514],[-20,0,-34.6410161514],[-10,-17.3205080757,-34.6410161514],[10,-17.3205080757,-34.6410161514]],[[5,4,3,2,1,0],[12,13,14,15,16,17],[6,0,1],[6,1,7],[7,1,2],[7,2,8],[8,2,3],[8,3,9],[9,3,4],[9,4,10],[10,4,5],[10,5,11],[11,5,0],[11,0,6],[12,6,7],[12,7,13],[13,7,8],[13,8,14],[14,8,9],[14,9,15],[15,9,10],[15,10,16],[16,10,11],[16,11,17],[17,11,6],[17,6,12]]]);
assert_approx(sphere(r=40,style="orig"), [[[20,0,34.6410161514],[10,17.3205080757,34.6410161514],[-10,17.3205080757,34.6410161514],[-20,0,34.6410161514],[-10,-17.3205080757,34.6410161514],[10,-17.3205080757,34.6410161514],[40,0,0],[20,34.6410161514,0],[-20,34.6410161514,0],[-40,0,0],[-20,-34.6410161514,0],[20,-34.6410161514,0],[20,0,-34.6410161514],[10,17.3205080757,-34.6410161514],[-10,17.3205080757,-34.6410161514],[-20,0,-34.6410161514],[-10,-17.3205080757,-34.6410161514],[10,-17.3205080757,-34.6410161514]],[[5,4,3,2,1,0],[12,13,14,15,16,17],[6,0,1],[6,1,7],[7,1,2],[7,2,8],[8,2,3],[8,3,9],[9,3,4],[9,4,10],[10,4,5],[10,5,11],[11,5,0],[11,0,6],[12,6,7],[12,7,13],[13,7,8],[13,8,14],[14,8,9],[14,9,15],[15,9,10],[15,10,16],[16,10,11],[16,11,17],[17,11,6],[17,6,12]]]);
assert_approx(sphere(r=40,style="aligned"), [[[0,0,40],[34.6410161514,0,20],[17.3205080757,30,20],[-17.3205080757,30,20],[-34.6410161514,0,20],[-17.3205080757,-30,20],[17.3205080757,-30,20],[34.6410161514,0,-20],[17.3205080757,30,-20],[-17.3205080757,30,-20],[-34.6410161514,0,-20],[-17.3205080757,-30,-20],[17.3205080757,-30,-20],[0,0,-40]],[[1,0,2],[13,7,8],[2,0,3],[13,8,9],[3,0,4],[13,9,10],[4,0,5],[13,10,11],[5,0,6],[13,11,12],[6,0,1],[13,12,7],[1,2,8],[1,8,7],[2,3,9],[2,9,8],[3,4,10],[3,10,9],[4,5,11],[4,11,10],[5,6,12],[5,12,11],[6,1,7],[6,7,12]]]);
assert_approx(sphere(r=40,style="stagger"), [[[0,0,40],[30,17.3205080757,20],[0,34.6410161514,20],[-30,17.3205080757,20],[-30,-17.3205080757,20],[0,-34.6410161514,20],[30,-17.3205080757,20],[34.6410161514,0,-20],[17.3205080757,30,-20],[-17.3205080757,30,-20],[-34.6410161514,0,-20],[-17.3205080757,-30,-20],[17.3205080757,-30,-20],[0,0,-40]],[[1,0,2],[13,7,8],[2,0,3],[13,8,9],[3,0,4],[13,9,10],[4,0,5],[13,10,11],[5,0,6],[13,11,12],[6,0,1],[13,12,7],[1,2,8],[1,8,7],[2,3,9],[2,9,8],[3,4,10],[3,10,9],[4,5,11],[4,11,10],[5,6,12],[5,12,11],[6,1,7],[6,7,12]]]);
assert_approx(sphere(r=40,style="octa"), [[[0,0,40],[28.2842712475,0,28.2842712475],[0,28.2842712475,28.2842712475],[-28.2842712475,0,28.2842712475],[0,-28.2842712475,28.2842712475],[40,0,0],[28.2842712475,28.2842712475,0],[0,40,0],[-28.2842712475,28.2842712475,0],[-40,0,0],[-28.2842712475,-28.2842712475,0],[0,-40,0],[28.2842712475,-28.2842712475,0],[28.2842712475,0,-28.2842712475],[0,28.2842712475,-28.2842712475],[-28.2842712475,0,-28.2842712475],[0,-28.2842712475,-28.2842712475],[0,0,-40]],[[0,2,1],[0,3,2],[0,4,3],[0,1,4],[17,15,16],[17,14,15],[17,13,14],[17,16,13],[1,6,5],[1,2,6],[13,5,6],[13,6,14],[2,7,6],[14,6,7],[2,8,7],[2,3,8],[14,7,8],[14,8,15],[3,9,8],[15,8,9],[3,10,9],[3,4,10],[15,9,10],[15,10,16],[4,11,10],[16,10,11],[4,12,11],[4,1,12],[16,11,12],[16,12,13],[1,5,12],[13,12,5]]]);
assert_approx(sphere(r=40,style="icosa"), [[[0,0,40],[28.0251707689,-20.3614784182,20],[28.0251707689,20.3614784182,20],[0,0,40],[28.0251707689,20.3614784182,20],[-10.7046626932,32.9455641419,20],[0,0,40],[-10.7046626932,32.9455641419,20],[-34.6410161514,6.66133814775e-15,20],[0,0,40],[-34.6410161514,0,20],[-10.7046626932,-32.9455641419,20],[0,0,40],[-10.7046626932,-32.9455641419,20],[28.0251707689,-20.3614784182,20],[34.6410161514,0,-20],[28.0251707689,-20.3614784182,20],[28.0251707689,20.3614784182,20],[10.7046626932,32.9455641419,-20],[28.0251707689,20.3614784182,20],[-10.7046626932,32.9455641419,20],[-28.0251707689,20.3614784182,-20],[-10.7046626932,32.9455641419,20],[-34.6410161514,-4.4408920985e-15,20],[-28.0251707689,-20.3614784182,-20],[-34.6410161514,1.11022302463e-15,20],[-10.7046626932,-32.9455641419,20],[10.7046626932,-32.9455641419,-20],[-10.7046626932,-32.9455641419,20],[28.0251707689,-20.3614784182,20],[0,0,-40],[-28.0251707689,20.3614784182,-20],[-28.0251707689,-20.3614784182,-20],[0,0,-40],[-28.0251707689,-20.3614784182,-20],[10.7046626932,-32.9455641419,-20],[0,0,-40],[10.7046626932,-32.9455641419,-20],[34.6410161514,-6.66133814775e-15,-20],[0,0,-40],[34.6410161514,0,-20],[10.7046626932,32.9455641419,-20],[0,0,-40],[10.7046626932,32.9455641419,-20],[-28.0251707689,20.3614784182,-20],[-34.6410161514,0,20],[-28.0251707689,20.3614784182,-20],[-28.0251707689,-20.3614784182,-20],[-10.7046626932,-32.9455641419,20],[-28.0251707689,-20.3614784182,-20],[10.7046626932,-32.9455641419,-20],[28.0251707689,-20.3614784182,20],[10.7046626932,-32.9455641419,-20],[34.6410161514,4.4408920985e-15,-20],[28.0251707689,20.3614784182,20],[34.6410161514,-1.11022302463e-15,-20],[10.7046626932,32.9455641419,-20],[-10.7046626932,32.9455641419,20],[10.7046626932,32.9455641419,-20],[-28.0251707689,20.3614784182,-20]],[[0,2,1],[3,5,4],[6,8,7],[9,11,10],[12,14,13],[16,17,15],[19,20,18],[22,23,21],[25,26,24],[28,29,27],[31,32,30],[34,35,33],[37,38,36],[40,41,39],[43,44,42],[45,47,46],[48,50,49],[51,53,52],[54,56,55],[57,59,58]]]);
}
test_sphere();
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -1,77 +0,0 @@
include <../std.scad>
include <../queues.scad>
module test_queue_init() {
assert(queue_init()==[]);
}
test_queue_init();
module test_queue_empty() {
assert(queue_empty([]));
assert(!queue_empty([3]));
assert(!queue_empty([2,4,8]));
}
test_queue_empty();
module test_queue_size() {
assert(queue_size([]) == 0);
assert(queue_size([3]) == 1);
assert(queue_size([2,4,8]) == 3);
}
test_queue_size();
module test_queue_head() {
assert(queue_head([]) == undef);
assert(queue_head([3,5,7,9]) == 3);
assert(queue_head([3,5,7,9], 3) == [3,5,7]);
}
test_queue_head();
module test_queue_tail() {
assert(queue_tail([]) == undef);
assert(queue_tail([3,5,7,9]) == 9);
assert(queue_tail([3,5,7,9], 3) == [5,7,9]);
}
test_queue_tail();
module test_queue_peek() {
q = [8,5,4,3,2,3,7];
assert(queue_peek(q,0) == 8);
assert(queue_peek(q,2) == 4);
assert(queue_peek(q,2,1) == [4]);
assert(queue_peek(q,2,3) == [4,3,2]);
}
test_queue_peek();
module test_queue_add() {
q1 = queue_init();
q2 = queue_add(q1, "Foo");
assert(q2==["Foo"]);
q3 = queue_add(q2, "Bar");
assert(q3==["Foo","Bar"]);
q4 = queue_add(q3, "Baz");
assert(q4==["Foo","Bar","Baz"]);
}
test_queue_add();
module test_queue_pop() {
q = ["Foo", "Bar", "Baz", "Qux"];
q1 = queue_pop(q);
assert(q1 == ["Bar", "Baz", "Qux"]);
q2 = queue_pop(q,2);
assert(q2 == ["Baz", "Qux"]);
q3 = queue_pop(q,3);
assert(q3 == ["Qux"]);
}
test_queue_pop();
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

12
tests/test_regions.scad Normal file
View file

@ -0,0 +1,12 @@
include<../std.scad>
module test_is_region() {
assert(is_region([circle(d=10),square(10)]));
assert(is_region([circle(d=10),square(10),circle(d=50)]));
assert(is_region([square(10)]));
assert(!is_region([]));
assert(!is_region(23));
assert(!is_region(true));
assert(!is_region("foo"));
}
test_is_region();

View file

@ -1,55 +1,29 @@
include <../std.scad>
module test_turtle() {
assert_approx(
turtle([
"move", 10,
"ymove", 5,
"xmove", 5,
"xymove", [10,15],
"left", 135,
"untilx", 0,
"turn", 90,
"untily", 0,
"right", 135,
"arcsteps", 5,
"arcright", 15, 30,
"arcleft", 15, 30,
"arcsteps", 0,
"arcrightto", 15, 90,
"arcleftto", 15, 180,
"jump", [10,10],
"xjump", 15,
"yjump", 15,
"angle", 30,
"length", 15,
"right",
"move",
"scale", 2,
"left",
"move",
"addlength", 5,
"repeat", 3, ["move"],
], $fn=24),
[[0,0],[10,0],[10,5],[15,5],[25,20],[-3.5527136788e-15,45],[-45,0],[-44.8716729206,1.9578928833],[-44.4888873943,3.88228567654],[-43.8581929877,5.74025148548],[-42.9903810568,7.5],[-42.1225691259,9.25974851452],[-41.4918747192,11.1177143235],[-41.1090891929,13.0421071167],[-40.9807621135,15],[-41.0157305757,16.0236362005],[-41.120472923,17.0424997364],[-41.2945007983,18.0518401958],[-41.5370028033,19.0469515674],[-41.8468482818,20.0231941826],[-42.222592591,20.9760163477],[-42.6624838375,21.900975566],[-43.1644710453,22.7937592505],[-43.7262137184,23.6502048317],[-44.345092753,24.4663191649],[-45.0182226494,25.2382971483],[-45.7424649653,25.9625394642],[-46.5144429486,26.6356693606],[-47.3305572818,27.2545483952],[-48.187002863,27.8162910682],[-49.0797865476,28.318278276],[-50.0047457658,28.7581695226],[-50.957567931,29.1339138318],[-51.9338105462,29.4437593102],[-52.9289219177,29.6862613152],[-53.9382623771,29.8602891905],[-54.9571259131,29.9650315379],[-55.9807621135,30],[10,10],[15,10],[15,15],[2.00961894323,22.5],[-27.9903810568,22.5],[-62.9903810568,22.5],[-97.9903810568,22.5],[-132.990381057,22.5]]
);
module test_square() {
assert(square(100, center=true) == [[50,-50],[-50,-50],[-50,50],[50,50]]);
assert(square(100, center=false) == [[100,0],[0,0],[0,100],[100,100]]);
assert(square(100, anchor=FWD+LEFT) == [[100,0],[0,0],[0,100],[100,100]]);
assert(square(100, anchor=BACK+RIGHT) == [[0,-100],[-100,-100],[-100,0],[0,0]]);
}
test_turtle();
test_square();
module test_arc() {
assert_approx(arc(N=8, d=100, angle=135, cp=[10,10]), [[60,10],[57.1941665154,26.5139530978],[49.0915741234,41.1744900929],[36.6016038258,52.3362099614],[21.1260466978,58.7463956091],[4.40177619483,59.6856104947],[-11.6941869559,55.0484433951],[-25.3553390593,45.3553390593]]);
assert_approx(arc(N=8, d=100, angle=135, cp=[10,10],endpoint=false), [[60,10],[57.8470167866,24.5142338627],[51.5734806151,37.778511651],[41.7196642082,48.6505226681],[29.1341716183,56.1939766256],[14.9008570165,59.7592363336],[0.245483899194,59.0392640202],[-13.5698368413,54.0960632174]]);
assert_approx(arc(N=8, d=100, angle=[45,225], cp=[10,10]), [[45.3553390593,45.3553390593],[26.5139530978,57.1941665154],[4.40177619483,59.6856104947],[-16.6016038258,52.3362099614],[-32.3362099614,36.6016038258],[-39.6856104947,15.5982238052],[-37.1941665154,-6.51395309776],[-25.3553390593,-25.3553390593]]);
assert_approx(arc(N=8, d=100, start=45, angle=135, cp=[10,10]), [[45.3553390593,45.3553390593],[31.6941869559,55.0484433951],[15.5982238052,59.6856104947],[-1.12604669782,58.7463956091],[-16.6016038258,52.3362099614],[-29.0915741234,41.1744900929],[-37.1941665154,26.5139530978],[-40,10]]);
assert_approx(arc(N=8, d=100, start=45, angle=-90, cp=[10,10]), [[45.3553390593,45.3553390593],[52.3362099614,36.6016038258],[57.1941665154,26.5139530978],[59.6856104947,15.5982238052],[59.6856104947,4.40177619483],[57.1941665154,-6.51395309776],[52.3362099614,-16.6016038258],[45.3553390593,-25.3553390593]]);
assert_approx(arc(N=8, width=100, thickness=30), [[50,-3.5527136788e-15],[39.5300788555,13.9348601124],[25.3202618476,24.0284558904],[8.71492362453,29.3258437015],[-8.71492362453,29.3258437015],[-25.3202618476,24.0284558904],[-39.5300788555,13.9348601124],[-50,-1.42108547152e-14]]);
assert_approx(arc(N=8, cp=[10,10], points=[[45,45],[-25,45]]), [[45,45],[36.3342442379,51.9107096148],[26.3479795075,56.7198412457],[15.5419588213,59.1862449514],[4.45804117867,59.1862449514],[-6.34797950747,56.7198412457],[-16.3342442379,51.9107096148],[-25,45]]);
assert_approx(arc(N=24, cp=[10,10], points=[[45,45],[-25,45]], long=true), [[45,45],[51.3889035257,37.146982612],[56.0464336973,28.1583574081],[58.7777575294,18.4101349813],[59.4686187624,8.31010126292],[58.0901174104,-1.71924090789],[54.6999187001,-11.2583458482],[49.4398408296,-19.9081753929],[42.5299224539,-27.3068913894],[34.2592180667,-33.1449920477],[24.9737063235,-37.1782589647],[15.0618171232,-39.2379732261],[4.93818287676,-39.2379732261],[-4.97370632349,-37.1782589647],[-14.2592180667,-33.1449920477],[-22.5299224539,-27.3068913894],[-29.4398408296,-19.9081753929],[-34.6999187001,-11.2583458482],[-38.0901174104,-1.71924090789],[-39.4686187624,8.31010126292],[-38.7777575294,18.4101349813],[-36.0464336973,28.1583574081],[-31.3889035257,37.146982612],[-25,45]]);
assert_approx(arc($fn=24, cp=[10,10], points=[[45,45],[-25,45]], long=true), [[45,45],[53.2421021636,34.0856928585],[58.1827254512,21.3324740498],[59.4446596304,7.71403542491],[56.9315576496,-5.72987274525],[50.8352916125,-17.9728253654],[41.6213035891,-28.0800887515],[29.9930697126,-35.2799863457],[16.8383906815,-39.0228152281],[3.16160931847,-39.0228152281],[-9.9930697126,-35.2799863457],[-21.6213035891,-28.0800887515],[-30.8352916125,-17.9728253654],[-36.9315576496,-5.72987274525],[-39.4446596304,7.71403542491],[-38.1827254512,21.3324740498],[-33.2421021636,34.0856928585],[-25,45]]);
module test_circle() {
for (pt = circle(d=200)) {
assert(approx(norm(pt),100));
}
for (pt = circle(r=100)) {
assert(approx(norm(pt),100));
}
assert(is_polygon_clockwise(circle(d=200)));
assert(is_polygon_clockwise(circle(r=100)));
assert(len(circle(d=100,$fn=6)) == 6);
assert(len(circle(d=100,$fn=36)) == 36);
}
test_arc();
test_circle();
module test_rect() {

View file

@ -1,6 +1,44 @@
include <../std.scad>
include <../hull.scad>
module test_cube() {
assert_equal(cube(100,center=true), [[[-50,-50,-50],[50,-50,-50],[50,50,-50],[-50,50,-50],[-50,-50,50],[50,-50,50],[50,50,50],[-50,50,50]],[[0,1,2],[0,2,3],[0,4,5],[0,5,1],[1,5,6],[1,6,2],[2,6,7],[2,7,3],[3,7,4],[3,4,0],[6,4,7],[6,5,4]]]);
assert_equal(cube([60,80,100],center=true), [[[-30,-40,-50],[30,-40,-50],[30,40,-50],[-30,40,-50],[-30,-40,50],[30,-40,50],[30,40,50],[-30,40,50]],[[0,1,2],[0,2,3],[0,4,5],[0,5,1],[1,5,6],[1,6,2],[2,6,7],[2,7,3],[3,7,4],[3,4,0],[6,4,7],[6,5,4]]]);
assert_equal(cube([60,80,100],anchor=CENTER), [[[-30,-40,-50],[30,-40,-50],[30,40,-50],[-30,40,-50],[-30,-40,50],[30,-40,50],[30,40,50],[-30,40,50]],[[0,1,2],[0,2,3],[0,4,5],[0,5,1],[1,5,6],[1,6,2],[2,6,7],[2,7,3],[3,7,4],[3,4,0],[6,4,7],[6,5,4]]]);
assert_equal(cube([60,80,100],center=false), [[[0,0,0],[60,0,0],[60,80,0],[0,80,0],[0,0,100],[60,0,100],[60,80,100],[0,80,100]],[[0,1,2],[0,2,3],[0,4,5],[0,5,1],[1,5,6],[1,6,2],[2,6,7],[2,7,3],[3,7,4],[3,4,0],[6,4,7],[6,5,4]]]);
assert_equal(cube([60,80,100]), [[[0,0,0],[60,0,0],[60,80,0],[0,80,0],[0,0,100],[60,0,100],[60,80,100],[0,80,100]],[[0,1,2],[0,2,3],[0,4,5],[0,5,1],[1,5,6],[1,6,2],[2,6,7],[2,7,3],[3,7,4],[3,4,0],[6,4,7],[6,5,4]]]);
assert_equal(cube([60,80,100],anchor=ALLNEG), [[[0,0,0],[60,0,0],[60,80,0],[0,80,0],[0,0,100],[60,0,100],[60,80,100],[0,80,100]],[[0,1,2],[0,2,3],[0,4,5],[0,5,1],[1,5,6],[1,6,2],[2,6,7],[2,7,3],[3,7,4],[3,4,0],[6,4,7],[6,5,4]]]);
assert_equal(cube([60,80,100],anchor=TOP), [[[-30,-40,-100],[30,-40,-100],[30,40,-100],[-30,40,-100],[-30,-40,0],[30,-40,0],[30,40,0],[-30,40,0]],[[0,1,2],[0,2,3],[0,4,5],[0,5,1],[1,5,6],[1,6,2],[2,6,7],[2,7,3],[3,7,4],[3,4,0],[6,4,7],[6,5,4]]]);
}
test_cube();
module test_cylinder() {
$fn=12;
assert_approx(cylinder(r=40,h=100,center=true), [[[40,0,-50],[34.6410161514,-20,-50],[20,-34.6410161514,-50],[0,-40,-50],[-20,-34.6410161514,-50],[-34.6410161514,-20,-50],[-40,0,-50],[-34.6410161514,20,-50],[-20,34.6410161514,-50],[0,40,-50],[20,34.6410161514,-50],[34.6410161514,20,-50],[40,0,50],[34.6410161514,-20,50],[20,-34.6410161514,50],[0,-40,50],[-20,-34.6410161514,50],[-34.6410161514,-20,50],[-40,0,50],[-34.6410161514,20,50],[-20,34.6410161514,50],[0,40,50],[20,34.6410161514,50],[34.6410161514,20,50]],[[11,10,9,8,7,6,5,4,3,2,1,0],[0,13,12],[1,14,13],[2,15,14],[3,16,15],[4,17,16],[5,18,17],[6,19,18],[7,20,19],[8,21,20],[9,22,21],[10,23,22],[11,12,23],[0,1,13],[1,2,14],[2,3,15],[3,4,16],[4,5,17],[5,6,18],[6,7,19],[7,8,20],[8,9,21],[9,10,22],[10,11,23],[11,0,12],[12,13,14,15,16,17,18,19,20,21,22,23]]]);
assert_approx(cylinder(d=80,h=100,center=true), [[[40,0,-50],[34.6410161514,-20,-50],[20,-34.6410161514,-50],[0,-40,-50],[-20,-34.6410161514,-50],[-34.6410161514,-20,-50],[-40,0,-50],[-34.6410161514,20,-50],[-20,34.6410161514,-50],[0,40,-50],[20,34.6410161514,-50],[34.6410161514,20,-50],[40,0,50],[34.6410161514,-20,50],[20,-34.6410161514,50],[0,-40,50],[-20,-34.6410161514,50],[-34.6410161514,-20,50],[-40,0,50],[-34.6410161514,20,50],[-20,34.6410161514,50],[0,40,50],[20,34.6410161514,50],[34.6410161514,20,50]],[[11,10,9,8,7,6,5,4,3,2,1,0],[0,13,12],[1,14,13],[2,15,14],[3,16,15],[4,17,16],[5,18,17],[6,19,18],[7,20,19],[8,21,20],[9,22,21],[10,23,22],[11,12,23],[0,1,13],[1,2,14],[2,3,15],[3,4,16],[4,5,17],[5,6,18],[6,7,19],[7,8,20],[8,9,21],[9,10,22],[10,11,23],[11,0,12],[12,13,14,15,16,17,18,19,20,21,22,23]]]);
assert_approx(cylinder(d=80,h=100,anchor=CENTER), [[[40,0,-50],[34.6410161514,-20,-50],[20,-34.6410161514,-50],[0,-40,-50],[-20,-34.6410161514,-50],[-34.6410161514,-20,-50],[-40,0,-50],[-34.6410161514,20,-50],[-20,34.6410161514,-50],[0,40,-50],[20,34.6410161514,-50],[34.6410161514,20,-50],[40,0,50],[34.6410161514,-20,50],[20,-34.6410161514,50],[0,-40,50],[-20,-34.6410161514,50],[-34.6410161514,-20,50],[-40,0,50],[-34.6410161514,20,50],[-20,34.6410161514,50],[0,40,50],[20,34.6410161514,50],[34.6410161514,20,50]],[[11,10,9,8,7,6,5,4,3,2,1,0],[0,13,12],[1,14,13],[2,15,14],[3,16,15],[4,17,16],[5,18,17],[6,19,18],[7,20,19],[8,21,20],[9,22,21],[10,23,22],[11,12,23],[0,1,13],[1,2,14],[2,3,15],[3,4,16],[4,5,17],[5,6,18],[6,7,19],[7,8,20],[8,9,21],[9,10,22],[10,11,23],[11,0,12],[12,13,14,15,16,17,18,19,20,21,22,23]]]);
assert_approx(cylinder(d=80,h=100,center=false), [[[40,0,0],[34.6410161514,-20,0],[20,-34.6410161514,0],[0,-40,0],[-20,-34.6410161514,0],[-34.6410161514,-20,0],[-40,0,0],[-34.6410161514,20,0],[-20,34.6410161514,0],[0,40,0],[20,34.6410161514,0],[34.6410161514,20,0],[40,0,100],[34.6410161514,-20,100],[20,-34.6410161514,100],[0,-40,100],[-20,-34.6410161514,100],[-34.6410161514,-20,100],[-40,0,100],[-34.6410161514,20,100],[-20,34.6410161514,100],[0,40,100],[20,34.6410161514,100],[34.6410161514,20,100]],[[11,10,9,8,7,6,5,4,3,2,1,0],[0,13,12],[1,14,13],[2,15,14],[3,16,15],[4,17,16],[5,18,17],[6,19,18],[7,20,19],[8,21,20],[9,22,21],[10,23,22],[11,12,23],[0,1,13],[1,2,14],[2,3,15],[3,4,16],[4,5,17],[5,6,18],[6,7,19],[7,8,20],[8,9,21],[9,10,22],[10,11,23],[11,0,12],[12,13,14,15,16,17,18,19,20,21,22,23]]]);
assert_approx(cylinder(d=80,h=100,anchor=BOT), [[[40,0,0],[34.6410161514,-20,0],[20,-34.6410161514,0],[0,-40,0],[-20,-34.6410161514,0],[-34.6410161514,-20,0],[-40,0,0],[-34.6410161514,20,0],[-20,34.6410161514,0],[0,40,0],[20,34.6410161514,0],[34.6410161514,20,0],[40,0,100],[34.6410161514,-20,100],[20,-34.6410161514,100],[0,-40,100],[-20,-34.6410161514,100],[-34.6410161514,-20,100],[-40,0,100],[-34.6410161514,20,100],[-20,34.6410161514,100],[0,40,100],[20,34.6410161514,100],[34.6410161514,20,100]],[[11,10,9,8,7,6,5,4,3,2,1,0],[0,13,12],[1,14,13],[2,15,14],[3,16,15],[4,17,16],[5,18,17],[6,19,18],[7,20,19],[8,21,20],[9,22,21],[10,23,22],[11,12,23],[0,1,13],[1,2,14],[2,3,15],[3,4,16],[4,5,17],[5,6,18],[6,7,19],[7,8,20],[8,9,21],[9,10,22],[10,11,23],[11,0,12],[12,13,14,15,16,17,18,19,20,21,22,23]]]);
assert_approx(cylinder(d=80,h=100), [[[40,0,0],[34.6410161514,-20,0],[20,-34.6410161514,0],[0,-40,0],[-20,-34.6410161514,0],[-34.6410161514,-20,0],[-40,0,0],[-34.6410161514,20,0],[-20,34.6410161514,0],[0,40,0],[20,34.6410161514,0],[34.6410161514,20,0],[40,0,100],[34.6410161514,-20,100],[20,-34.6410161514,100],[0,-40,100],[-20,-34.6410161514,100],[-34.6410161514,-20,100],[-40,0,100],[-34.6410161514,20,100],[-20,34.6410161514,100],[0,40,100],[20,34.6410161514,100],[34.6410161514,20,100]],[[11,10,9,8,7,6,5,4,3,2,1,0],[0,13,12],[1,14,13],[2,15,14],[3,16,15],[4,17,16],[5,18,17],[6,19,18],[7,20,19],[8,21,20],[9,22,21],[10,23,22],[11,12,23],[0,1,13],[1,2,14],[2,3,15],[3,4,16],[4,5,17],[5,6,18],[6,7,19],[7,8,20],[8,9,21],[9,10,22],[10,11,23],[11,0,12],[12,13,14,15,16,17,18,19,20,21,22,23]]]);
}
test_cylinder();
module test_sphere() {
$fn=6;
assert_approx(sphere(r=40), [[[20,0,34.6410161514],[10,17.3205080757,34.6410161514],[-10,17.3205080757,34.6410161514],[-20,0,34.6410161514],[-10,-17.3205080757,34.6410161514],[10,-17.3205080757,34.6410161514],[40,0,0],[20,34.6410161514,0],[-20,34.6410161514,0],[-40,0,0],[-20,-34.6410161514,0],[20,-34.6410161514,0],[20,0,-34.6410161514],[10,17.3205080757,-34.6410161514],[-10,17.3205080757,-34.6410161514],[-20,0,-34.6410161514],[-10,-17.3205080757,-34.6410161514],[10,-17.3205080757,-34.6410161514]],[[5,4,3,2,1,0],[12,13,14,15,16,17],[6,0,1],[6,1,7],[7,1,2],[7,2,8],[8,2,3],[8,3,9],[9,3,4],[9,4,10],[10,4,5],[10,5,11],[11,5,0],[11,0,6],[12,6,7],[12,7,13],[13,7,8],[13,8,14],[14,8,9],[14,9,15],[15,9,10],[15,10,16],[16,10,11],[16,11,17],[17,11,6],[17,6,12]]]);
assert_approx(sphere(r=40,style="orig"), [[[20,0,34.6410161514],[10,17.3205080757,34.6410161514],[-10,17.3205080757,34.6410161514],[-20,0,34.6410161514],[-10,-17.3205080757,34.6410161514],[10,-17.3205080757,34.6410161514],[40,0,0],[20,34.6410161514,0],[-20,34.6410161514,0],[-40,0,0],[-20,-34.6410161514,0],[20,-34.6410161514,0],[20,0,-34.6410161514],[10,17.3205080757,-34.6410161514],[-10,17.3205080757,-34.6410161514],[-20,0,-34.6410161514],[-10,-17.3205080757,-34.6410161514],[10,-17.3205080757,-34.6410161514]],[[5,4,3,2,1,0],[12,13,14,15,16,17],[6,0,1],[6,1,7],[7,1,2],[7,2,8],[8,2,3],[8,3,9],[9,3,4],[9,4,10],[10,4,5],[10,5,11],[11,5,0],[11,0,6],[12,6,7],[12,7,13],[13,7,8],[13,8,14],[14,8,9],[14,9,15],[15,9,10],[15,10,16],[16,10,11],[16,11,17],[17,11,6],[17,6,12]]]);
assert_approx(sphere(r=40,style="aligned"), [[[0,0,40],[34.6410161514,0,20],[17.3205080757,30,20],[-17.3205080757,30,20],[-34.6410161514,0,20],[-17.3205080757,-30,20],[17.3205080757,-30,20],[34.6410161514,0,-20],[17.3205080757,30,-20],[-17.3205080757,30,-20],[-34.6410161514,0,-20],[-17.3205080757,-30,-20],[17.3205080757,-30,-20],[0,0,-40]],[[1,0,2],[13,7,8],[2,0,3],[13,8,9],[3,0,4],[13,9,10],[4,0,5],[13,10,11],[5,0,6],[13,11,12],[6,0,1],[13,12,7],[1,2,8],[1,8,7],[2,3,9],[2,9,8],[3,4,10],[3,10,9],[4,5,11],[4,11,10],[5,6,12],[5,12,11],[6,1,7],[6,7,12]]]);
assert_approx(sphere(r=40,style="stagger"), [[[0,0,40],[30,17.3205080757,20],[0,34.6410161514,20],[-30,17.3205080757,20],[-30,-17.3205080757,20],[0,-34.6410161514,20],[30,-17.3205080757,20],[34.6410161514,0,-20],[17.3205080757,30,-20],[-17.3205080757,30,-20],[-34.6410161514,0,-20],[-17.3205080757,-30,-20],[17.3205080757,-30,-20],[0,0,-40]],[[1,0,2],[13,7,8],[2,0,3],[13,8,9],[3,0,4],[13,9,10],[4,0,5],[13,10,11],[5,0,6],[13,11,12],[6,0,1],[13,12,7],[1,2,8],[1,8,7],[2,3,9],[2,9,8],[3,4,10],[3,10,9],[4,5,11],[4,11,10],[5,6,12],[5,12,11],[6,1,7],[6,7,12]]]);
assert_approx(sphere(r=40,style="octa"), [[[0,0,40],[28.2842712475,0,28.2842712475],[0,28.2842712475,28.2842712475],[-28.2842712475,0,28.2842712475],[0,-28.2842712475,28.2842712475],[40,0,0],[28.2842712475,28.2842712475,0],[0,40,0],[-28.2842712475,28.2842712475,0],[-40,0,0],[-28.2842712475,-28.2842712475,0],[0,-40,0],[28.2842712475,-28.2842712475,0],[28.2842712475,0,-28.2842712475],[0,28.2842712475,-28.2842712475],[-28.2842712475,0,-28.2842712475],[0,-28.2842712475,-28.2842712475],[0,0,-40]],[[0,2,1],[0,3,2],[0,4,3],[0,1,4],[17,15,16],[17,14,15],[17,13,14],[17,16,13],[1,6,5],[1,2,6],[13,5,6],[13,6,14],[2,7,6],[14,6,7],[2,8,7],[2,3,8],[14,7,8],[14,8,15],[3,9,8],[15,8,9],[3,10,9],[3,4,10],[15,9,10],[15,10,16],[4,11,10],[16,10,11],[4,12,11],[4,1,12],[16,11,12],[16,12,13],[1,5,12],[13,12,5]]]);
assert_approx(sphere(r=40,style="icosa"), [[[0,0,40],[28.0251707689,-20.3614784182,20],[28.0251707689,20.3614784182,20],[0,0,40],[28.0251707689,20.3614784182,20],[-10.7046626932,32.9455641419,20],[0,0,40],[-10.7046626932,32.9455641419,20],[-34.6410161514,6.66133814775e-15,20],[0,0,40],[-34.6410161514,0,20],[-10.7046626932,-32.9455641419,20],[0,0,40],[-10.7046626932,-32.9455641419,20],[28.0251707689,-20.3614784182,20],[34.6410161514,0,-20],[28.0251707689,-20.3614784182,20],[28.0251707689,20.3614784182,20],[10.7046626932,32.9455641419,-20],[28.0251707689,20.3614784182,20],[-10.7046626932,32.9455641419,20],[-28.0251707689,20.3614784182,-20],[-10.7046626932,32.9455641419,20],[-34.6410161514,-4.4408920985e-15,20],[-28.0251707689,-20.3614784182,-20],[-34.6410161514,1.11022302463e-15,20],[-10.7046626932,-32.9455641419,20],[10.7046626932,-32.9455641419,-20],[-10.7046626932,-32.9455641419,20],[28.0251707689,-20.3614784182,20],[0,0,-40],[-28.0251707689,20.3614784182,-20],[-28.0251707689,-20.3614784182,-20],[0,0,-40],[-28.0251707689,-20.3614784182,-20],[10.7046626932,-32.9455641419,-20],[0,0,-40],[10.7046626932,-32.9455641419,-20],[34.6410161514,-6.66133814775e-15,-20],[0,0,-40],[34.6410161514,0,-20],[10.7046626932,32.9455641419,-20],[0,0,-40],[10.7046626932,32.9455641419,-20],[-28.0251707689,20.3614784182,-20],[-34.6410161514,0,20],[-28.0251707689,20.3614784182,-20],[-28.0251707689,-20.3614784182,-20],[-10.7046626932,-32.9455641419,20],[-28.0251707689,-20.3614784182,-20],[10.7046626932,-32.9455641419,-20],[28.0251707689,-20.3614784182,20],[10.7046626932,-32.9455641419,-20],[34.6410161514,4.4408920985e-15,-20],[28.0251707689,20.3614784182,20],[34.6410161514,-1.11022302463e-15,-20],[10.7046626932,32.9455641419,-20],[-10.7046626932,32.9455641419,20],[10.7046626932,32.9455641419,-20],[-28.0251707689,20.3614784182,-20]],[[0,2,1],[3,5,4],[6,8,7],[9,11,10],[12,14,13],[16,17,15],[19,20,18],[22,23,21],[25,26,24],[28,29,27],[31,32,30],[34,35,33],[37,38,36],[40,41,39],[43,44,42],[45,47,46],[48,50,49],[51,53,52],[54,56,55],[57,59,58]]]);
}
test_sphere();
module test_prismoid() {
$fn=24;
assert_approx(prismoid([100,80],[50,40],h=50), [[[25,20,50],[25,-20,50],[-25,-20,50],[-25,20,50],[50,40,0],[50,-40,0],[-50,-40,0],[-50,40,0]],[[0,1,2],[0,2,3],[0,4,5],[0,5,1],[1,5,6],[1,6,2],[2,6,7],[2,7,3],[3,7,4],[3,4,0],[4,7,6],[4,6,5]]]);

View file

@ -1,79 +0,0 @@
include <../std.scad>
include <../stacks.scad>
module test_stack_init() {
assert(stack_init()==[]);
}
test_stack_init();
module test_stack_empty() {
assert(stack_empty([]));
assert(!stack_empty([3]));
assert(!stack_empty([2,4,8]));
}
test_stack_empty();
module test_stack_depth() {
assert(stack_depth([]) == 0);
assert(stack_depth([3]) == 1);
assert(stack_depth([2,4,8]) == 3);
}
test_stack_depth();
module test_stack_top() {
assert(stack_top([]) == undef);
assert(stack_top([3,5,7,9]) == 9);
assert(stack_top([3,5,7,9], 3) == [5,7,9]);
}
test_stack_top();
module test_stack_peek() {
s = [8,5,4,3,2,3,7];
assert(stack_peek(s,0) == 7);
assert(stack_peek(s,2) == 2);
assert(stack_peek(s,2,1) == [2]);
assert(stack_peek(s,2,3) == [2,3,7]);
}
test_stack_peek();
module test_stack_push() {
s1 = stack_init();
s2 = stack_push(s1, "Foo");
assert(s2==["Foo"]);
s3 = stack_push(s2, "Bar");
assert(s3==["Foo","Bar"]);
s4 = stack_push(s3, "Baz");
assert(s4==["Foo","Bar","Baz"]);
}
test_stack_push();
module test_stack_pop() {
s = ["Foo", "Bar", "Baz", "Qux"];
s1 = stack_pop(s);
assert(s1 == ["Foo", "Bar", "Baz"]);
s2 = stack_pop(s,2);
assert(s2 == ["Foo", "Bar"]);
s3 = stack_pop(s,3);
assert(s3 == ["Foo"]);
}
test_stack_pop();
module test_stack_rotate() {
s = ["Foo", "Bar", "Baz", "Qux", "Quux"];
s1 = stack_rotate(s,4);
assert(s1 == ["Foo", "Baz", "Qux", "Quux", "Bar"]);
s2 = stack_rotate(s,-4);
assert(s2 == ["Foo", "Quux", "Bar", "Baz", "Qux"]);
}
test_stack_rotate();
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -217,32 +217,6 @@ module test_zflip() {
test_zflip();
module test_xyflip() {
assert_approx(xyflip(), [[0,1,0,0],[1,0,0,0],[0,0,1,0],[0,0,0,1]]);
assert_approx(xyflip(p=[1,2,3]), [2,1,3]);
// Verify that module at least doesn't crash.
xyflip() nil();
}
test_xyflip();
module test_xzflip() {
assert_approx(xzflip(), [[0,0,1,0],[0,1,0,0],[1,0,0,0],[0,0,0,1]]);
assert_approx(xzflip(p=[1,2,3]), [3,2,1]);
// Verify that module at least doesn't crash.
xzflip() nil();
}
test_xzflip();
module test_yzflip() {
assert_approx(yzflip(), [[1,0,0,0],[0,0,1,0],[0,1,0,0],[0,0,0,1]]);
assert_approx(yzflip(p=[1,2,3]), [1,3,2]);
// Verify that module at least doesn't crash.
yzflip() nil();
}
test_yzflip();
module test_rot() {
pts2d = 50 * [for (x=[-1,0,1],y=[-1,0,1]) [x,y]];
@ -403,64 +377,12 @@ module test_zrot() {
test_zrot();
module test_xyrot() {
vals = [-270,-135,-90,45,0,30,45,90,135,147,180];
path = path3d(pentagon(d=100), 50);
for (a=vals) {
m = affine3d_rot_by_axis(RIGHT+BACK,a);
assert_approx(xyrot(a), m);
assert_approx(xyrot(a, p=path[0]), apply(m, path[0]));
assert_approx(xyrot(a, p=path), apply(m, path));
// Verify that module at least doesn't crash.
xyrot(a) nil();
}
module test_frame_map() {
assert(approx(frame_map(x=[1,1,0], y=[-1,1,0]), affine3d_zrot(45)));
assert(approx(frame_map(x=[0,1,0], y=[0,0,1]), rot(v=[1,1,1],a=120)));
}
test_xyrot();
module test_xzrot() {
vals = [-270,-135,-90,45,0,30,45,90,135,147,180];
path = path3d(pentagon(d=100), 50);
for (a=vals) {
m = affine3d_rot_by_axis(RIGHT+UP,a);
assert_approx(xzrot(a), m);
assert_approx(xzrot(a, p=path[0]), apply(m, path[0]));
assert_approx(xzrot(a, p=path), apply(m, path));
// Verify that module at least doesn't crash.
xzrot(a) nil();
}
}
test_xzrot();
module test_yzrot() {
vals = [-270,-135,-90,45,0,30,45,90,135,147,180];
path = path3d(pentagon(d=100), 50);
for (a=vals) {
m = affine3d_rot_by_axis(BACK+UP,a);
assert_approx(yzrot(a), m);
assert_approx(yzrot(a, p=path[0]), apply(m, path[0]));
assert_approx(yzrot(a, p=path), apply(m, path));
// Verify that module at least doesn't crash.
yzrot(a) nil();
}
}
test_yzrot();
module test_xyzrot() {
vals = [-270,-135,-90,45,0,30,45,90,135,147,180];
path = path3d(pentagon(d=100), 50);
for (a=vals) {
m = affine3d_rot_by_axis(RIGHT+BACK+UP,a);
assert_approx(xyzrot(a), m);
assert_approx(xyzrot(a, p=path[0]), apply(m, path[0]));
assert_approx(xyzrot(a, p=path), apply(m, path));
// Verify that module at least doesn't crash.
xyzrot(a) nil();
}
}
test_xyzrot();
test_frame_map();
module test_skew() {

View file

@ -0,0 +1,42 @@
include <../std.scad>
module test_tri_functions() {
sides = rands(1,100,100,seed_value=8181);
for (p = pair(sides,true)) {
adj = p.x;
opp = p.y;
hyp = norm([opp,adj]);
ang = atan2(opp,adj);
assert_approx(hyp_ang_to_adj(hyp,ang), adj);
assert_approx(opp_ang_to_adj(opp,ang), adj);
assert_approx(hyp_adj_to_opp(hyp,adj), opp);
assert_approx(hyp_ang_to_opp(hyp,ang), opp);
assert_approx(adj_ang_to_opp(adj,ang), opp);
assert_approx(adj_opp_to_hyp(adj,opp), hyp);
assert_approx(adj_ang_to_hyp(adj,ang), hyp);
assert_approx(opp_ang_to_hyp(opp,ang), hyp);
assert_approx(hyp_adj_to_ang(hyp,adj), ang);
assert_approx(hyp_opp_to_ang(hyp,opp), ang);
assert_approx(adj_opp_to_ang(adj,opp), ang);
}
}
*test_tri_functions();
module test_hyp_opp_to_adj() nil(); // Covered in test_tri_functions()
module test_hyp_ang_to_adj() nil(); // Covered in test_tri_functions()
module test_opp_ang_to_adj() nil(); // Covered in test_tri_functions()
module test_hyp_adj_to_opp() nil(); // Covered in test_tri_functions()
module test_hyp_ang_to_opp() nil(); // Covered in test_tri_functions()
module test_adj_ang_to_opp() nil(); // Covered in test_tri_functions()
module test_adj_opp_to_hyp() nil(); // Covered in test_tri_functions()
module test_adj_ang_to_hyp() nil(); // Covered in test_tri_functions()
module test_opp_ang_to_hyp() nil(); // Covered in test_tri_functions()
module test_hyp_adj_to_ang() nil(); // Covered in test_tri_functions()
module test_hyp_opp_to_ang() nil(); // Covered in test_tri_functions()
module test_adj_opp_to_ang() nil(); // Covered in test_tri_functions()
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -47,6 +47,23 @@ module test_v_ceil() {
test_v_ceil();
module test_v_lookup() {
lup = [[4, [3,4,5]], [5, [5,6,7]], [6, [4,5,6]]];
assert_equal(v_lookup(3,lup), [3,4,5]);
assert_equal(v_lookup(3.5,lup), [3,4,5]);
assert_equal(v_lookup(4,lup), [3,4,5]);
assert_approx(v_lookup(4.2,lup), [3.4,4.4,5.4]);
assert_equal(v_lookup(4.5,lup), [4,5,6]);
assert_equal(v_lookup(5,lup), [5,6,7]);
assert_approx(v_lookup(5.2,lup), [4.8,5.8,6.8]);
assert_equal(v_lookup(5.5,lup), [4.5,5.5,6.5]);
assert_equal(v_lookup(6,lup), [4,5,6]);
assert_equal(v_lookup(6.5,lup), [4,5,6]);
assert_equal(v_lookup(7,lup), [4,5,6]);
}
test_v_lookup();
module test_v_mul() {
assert_equal(v_mul([3,4,5], [8,7,6]), [24,28,30]);
assert_equal(v_mul([1,2,3], [4,5,6]), [4,10,18]);
@ -190,7 +207,61 @@ module test_vector_nearest(){
}
test_vector_nearest();
cube();
module test_pointlist_bounds() {
pts = [
[-53,27,12],
[-63,97,36],
[84,-32,-5],
[63,-24,42],
[23,57,-42]
];
assert(pointlist_bounds(pts) == [[-63,-32,-42], [84,97,42]]);
pts2d = [
[-53,12],
[-63,36],
[84,-5],
[63,42],
[23,-42]
];
assert(pointlist_bounds(pts2d) == [[-63,-42],[84,42]]);
pts5d = [
[-53, 27, 12,-53, 12],
[-63, 97, 36,-63, 36],
[ 84,-32, -5, 84, -5],
[ 63,-24, 42, 63, 42],
[ 23, 57,-42, 23,-42]
];
assert(pointlist_bounds(pts5d) == [[-63,-32,-42,-63,-42],[84,97,42,84,42]]);
assert(pointlist_bounds([[3,4,5,6]]), [[3,4,5,6],[3,4,5,6]]);
}
test_pointlist_bounds();
module test_closest_point() {
ptlist = [for (i=count(100)) rands(-100,100,2,seed_value=8463+i)];
testpts = [for (i=count(100)) rands(-100,100,2,seed_value=6834+i)];
for (pt = testpts) {
pidx = closest_point(pt,ptlist);
dists = [for (p=ptlist) norm(pt-p)];
mindist = min(dists);
assert(mindist == dists[pidx]);
}
}
test_closest_point();
module test_furthest_point() {
ptlist = [for (i=count(100)) rands(-100,100,2,seed_value=8463+i)];
testpts = [for (i=count(100)) rands(-100,100,2,seed_value=6834+i)];
for (pt = testpts) {
pidx = furthest_point(pt,ptlist);
dists = [for (p=ptlist) norm(pt-p)];
mindist = max(dists);
assert(mindist == dists[pidx]);
}
}
test_furthest_point();
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -81,11 +81,18 @@ test_vnf_centroid();
module test_vnf_volume() {
assert_approx(vnf_volume(cube(100, center=false)), 1000000);
assert(approx(vnf_volume(sphere(d=100, anchor=BOT, $fn=144)), 4/3*PI*pow(50,3), eps=1e3));
assert(approx(vnf_volume(sphere(d=100, anchor=BOT, $fn=144)) / (4/3*PI*pow(50,3)),1, eps=.001));
}
test_vnf_volume();
module test_vnf_area(){
assert(approx(vnf_area(sphere(d=100, $fn=144)) / (4*PI*50*50),1, eps=1e-3));
}
test_vnf_area();
module test_vnf_merge() {
vnf1 = vnf_add_face(pts=[[-1,-1,-1],[1,-1,-1],[0,1,-1]]);
vnf2 = vnf_add_face(pts=[[1,1,1],[-1,1,1],[0,1,-1]]);

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,14 @@
//////////////////////////////////////////////////////////////////////
// LibFile: transforms.scad
// Functions and modules for translation, rotation, reflection and skewing.
// Functions and modules that provide shortcuts for translation,
// rotation and mirror operations. Also provided are skew and frame_map
// which remaps the coordinate axes. The shortcuts can act on
// geometry, like the usual OpenSCAD rotate() and translate(). They
// also work as functions that operate on lists of points in various
// forms: paths, VNFS and bezier patches. Lastly, the function form
// of the shortcuts can return a matrix representing the operation
// the shortcut performs. The rotation and scaling shortcuts accept
// an optional centerpoint for the rotation or scaling operation.
// Includes:
// include <BOSL2/std.scad>
//////////////////////////////////////////////////////////////////////
@ -25,7 +33,7 @@
// mat = move([x=], [y=], [z=]);
//
// Topics: Affine, Matrices, Transforms, Translation
// See Also: left(), right(), fwd(), back(), down(), up(), affine2d_translate(), affine3d_translate()
// See Also: left(), right(), fwd(), back(), down(), up(), spherical_to_xyz(), altaz_to_xyz(), cylindrical_to_xyz(), polar_to_xy(), affine2d_translate(), affine3d_translate()
//
// Description:
// Translates position by the given amount.
@ -58,6 +66,14 @@
// #sphere(d=10);
// move(x=-10, y=-5) sphere(d=10);
//
// Example(FlatSpin): Using Altitude-Azimuth Coordinates
// #sphere(d=10);
// move(altaz_to_xyz(30,90,20)) sphere(d=10);
//
// Example(FlatSpin): Using Spherical Coordinates
// #sphere(d=10);
// move(spherical_to_xyz(20,45,30)) sphere(d=10);
//
// Example(2D):
// path = square([50,30], center=true);
// #stroke(path, closed=true);
@ -234,7 +250,7 @@ function fwd(y=0, p) = move([0,-y,0],p=p);
// pt2 = back(20, p=[15,23,42]); // Returns: [15,43,42]
// pt3 = back(3, p=[[1,2,3],[4,5,6]]); // Returns: [[1,5,3], [4,8,6]]
// mat3d = back(4); // Returns: [[1,0,0,0],[0,1,0,4],[0,0,1,0],[0,0,0,1]]
module back(y=0, ) {
module back(y=0, p) {
assert(is_undef(p), "Module form `back()` does not accept p= argument.");
translate([0,y,0]) children();
}
@ -587,184 +603,9 @@ module zrot(a=0, p, cp)
function zrot(a=0, p, cp) = rot(a, cp=cp, p=p);
// Function&Module: xyrot()
//
// Usage: As Module
// xyrot(a, [cp=]) ...
// Usage: As a Function to rotate points
// rotated = xyrot(a, p, [cp=]);
// Usage: As a Function to get rotation matrix
// mat = xyrot(a, [cp=]);
//
// Topics: Affine, Matrices, Transforms, Rotation
// See Also: rot(), xrot(), yrot(), zrot(), xzrot(), yzrot(), xyzrot(), affine3d_rot_by_axis()
//
// Description:
// Rotates around the [1,1,0] vector axis by the given number of degrees. If `cp` is given, rotations are performed around that centerpoint.
// * Called as a module, rotates all children.
// * Called as a function with a `p` argument containing a point, returns the rotated point.
// * Called as a function with a `p` argument containing a list of points, returns the list of rotated points.
// * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the rotated patch.
// * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the rotated VNF.
// * Called as a function without a `p` argument, returns the affine3d rotational matrix.
//
// Arguments:
// a = angle to rotate by in degrees.
// p = If called as a function, this contains data to rotate: a point, list of points, bezier patch or VNF.
// ---
// cp = centerpoint to rotate around. Default: [0,0,0]
//
// Example:
// #cylinder(h=50, r=10, center=true);
// xyrot(90) cylinder(h=50, r=10, center=true);
module xyrot(a=0, p, cp)
{
assert(is_undef(p), "Module form `xyrot()` does not accept p= argument.");
if (a==0) {
children(); // May be slightly faster?
} else {
mat = xyrot(a=a, cp=cp);
multmatrix(mat) children();
}
}
function xyrot(a=0, p, cp) = rot(a=a, v=[1,1,0], cp=cp, p=p);
// Function&Module: xzrot()
//
// Usage: As Module
// xzrot(a, [cp=]) ...
// Usage: As Function to rotate points
// rotated = xzrot(a, p, [cp=]);
// Usage: As Function to return rotation matrix
// mat = xzrot(a, [cp=]);
//
// Topics: Affine, Matrices, Transforms, Rotation
// See Also: rot(), xrot(), yrot(), zrot(), xyrot(), yzrot(), xyzrot(), affine3d_rot_by_axis()
//
// Description:
// Rotates around the [1,0,1] vector axis by the given number of degrees. If `cp` is given, rotations are performed around that centerpoint.
// * Called as a module, rotates all children.
// * Called as a function with a `p` argument containing a point, returns the rotated point.
// * Called as a function with a `p` argument containing a list of points, returns the list of rotated points.
// * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the rotated patch.
// * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the rotated VNF.
// * Called as a function without a `p` argument, returns the affine3d rotational matrix.
//
// Arguments:
// a = angle to rotate by in degrees.
// p = If called as a function, this contains data to rotate: a point, list of points, bezier patch or VNF.
// ---
// cp = centerpoint to rotate around. Default: [0,0,0]
//
// Example:
// #cylinder(h=50, r=10, center=true);
// xzrot(90) cylinder(h=50, r=10, center=true);
module xzrot(a=0, p, cp)
{
assert(is_undef(p), "Module form `xzrot()` does not accept p= argument.");
if (a==0) {
children(); // May be slightly faster?
} else {
mat = xzrot(a=a, cp=cp);
multmatrix(mat) children();
}
}
function xzrot(a=0, p, cp) = rot(a=a, v=[1,0,1], cp=cp, p=p);
// Function&Module: yzrot()
//
// Usage: As Module
// yzrot(a, [cp=]) ...
// Usage: As Function to rotate points
// rotated = yzrot(a, p, [cp=]);
// Usage: As Function to return rotation matrix
// mat = yzrot(a, [cp=]);
//
// Topics: Affine, Matrices, Transforms, Rotation
// See Also: rot(), xrot(), yrot(), zrot(), xyrot(), xzrot(), xyzrot(), affine3d_rot_by_axis()
//
// Description:
// Rotates around the [0,1,1] vector axis by the given number of degrees. If `cp` is given, rotations are performed around that centerpoint.
// * Called as a module, rotates all children.
// * Called as a function with a `p` argument containing a point, returns the rotated point.
// * Called as a function with a `p` argument containing a list of points, returns the list of rotated points.
// * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the rotated patch.
// * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the rotated VNF.
// * Called as a function without a `p` argument, returns the affine3d rotational matrix.
//
// Arguments:
// a = angle to rotate by in degrees.
// p = If called as a function, this contains data to rotate: a point, list of points, bezier patch or VNF.
// ---
// cp = centerpoint to rotate around. Default: [0,0,0]
//
// Example:
// #cylinder(h=50, r=10, center=true);
// yzrot(90) cylinder(h=50, r=10, center=true);
module yzrot(a=0, p, cp)
{
assert(is_undef(p), "Module form `yzrot()` does not accept p= argument.");
if (a==0) {
children(); // May be slightly faster?
} else {
mat = yzrot(a=a, cp=cp);
multmatrix(mat) children();
}
}
function yzrot(a=0, p, cp) = rot(a=a, v=[0,1,1], cp=cp, p=p);
// Function&Module: xyzrot()
//
// Usage: As Module
// xyzrot(a, [cp=]) ...
// Usage: As Function to rotate points
// rotated = xyzrot(a, p, [cp=]);
// Usage: As Function to return rotation matrix
// mat = xyzrot(a, [cp=]);
//
// Topics: Affine, Matrices, Transforms, Rotation
// See Also: rot(), xrot(), yrot(), zrot(), xyrot(), xzrot(), yzrot(), affine3d_rot_by_axis()
//
// Description:
// Rotates around the [1,1,1] vector axis by the given number of degrees. If `cp` is given, rotations are performed around that centerpoint.
// * Called as a module, rotates all children.
// * Called as a function with a `p` argument containing a point, returns the rotated point.
// * Called as a function with a `p` argument containing a list of points, returns the list of rotated points.
// * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the rotated patch.
// * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the rotated VNF.
// * Called as a function without a `p` argument, returns the affine3d rotational matrix.
//
// Arguments:
// a = angle to rotate by in degrees.
// p = If called as a function, this contains data to rotate: a point, list of points, bezier patch or VNF.
// ---
// cp = centerpoint to rotate around. Default: [0,0,0]
//
// Example:
// #cylinder(h=50, r=10, center=true);
// xyzrot(90) cylinder(h=50, r=10, center=true);
module xyzrot(a=0, p, cp)
{
assert(is_undef(p), "Module form `xyzrot()` does not accept p= argument.");
if (a==0) {
children(); // May be slightly faster?
} else {
mat = xyzrot(a=a, cp=cp);
multmatrix(mat) children();
}
}
function xyzrot(a=0, p, cp) = rot(a=a, v=[1,1,1], cp=cp, p=p);
//////////////////////////////////////////////////////////////////////
// Section: Scaling and Mirroring
// Section: Scaling
//////////////////////////////////////////////////////////////////////
@ -1003,6 +844,10 @@ function zscale(z=1, p, cp=0) =
scale([1,1,z], cp=cp, p=p);
//////////////////////////////////////////////////////////////////////
// Section: Reflection (Mirroring)
//////////////////////////////////////////////////////////////////////
// Function&Module: mirror()
// Usage: As Module
// mirror(v) ...
@ -1243,193 +1088,88 @@ function zflip(p, z=0) =
move([0,0,z],p=mirror([0,0,1],p=move([0,0,-z],p=p)));
// Function&Module: xyflip()
//
// Usage: As Module
// xyflip([cp]) ...
// Usage: As Function
// pt = xyflip(p, [cp]);
// Usage: Get Affine Matrix
// pt = xyflip([cp], [planar=]);
//
// Topics: Affine, Matrices, Transforms, Reflection, Mirroring
// See Also: mirror(), xflip(), yflip(), zflip(), xzflip(), yzflip(), affine2d_mirror(), affine3d_mirror()
//
// Description:
// Mirrors/reflects across the origin [0,0,0], along the reflection plane where X=Y. If `cp` is given, the reflection plane passes through that point
// * Called as the built-in module, mirrors all children across the line/plane.
// * Called as a function with a point in the `p` argument, returns the point mirrored across the line/plane.
// * Called as a function with a list of points in the `p` argument, returns the list of points, with each one mirrored across the line/plane.
// * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the mirrored patch.
// * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the mirrored VNF.
// * Called as a function without a `p` argument, and `planer=true`, returns the affine2d 3x3 mirror matrix.
// * Called as a function without a `p` argument, and `planar=false`, returns the affine3d 4x4 mirror matrix.
//
// Arguments:
// p = If given, the point, path, patch, or VNF to mirror. Function use only.
// cp = The centerpoint of the plane of reflection, given either as a point, or as a scalar distance away from the origin.
// ---
// planar = If true, and p is not given, returns a 2D affine transformation matrix. Function use only. Default: False
//
// Example(2D):
// xyflip() text("Foobar", size=20, halign="center");
//
// Example:
// left(10) frame_ref();
// right(10) xyflip() frame_ref();
//
// Example:
// xyflip(cp=-15) frame_ref();
//
// Example:
// xyflip(cp=[10,10,10]) frame_ref();
//
// Example: Called as Function for a 3D matrix
// mat = xyflip();
// multmatrix(mat) frame_ref();
//
// Example(2D): Called as Function for a 2D matrix
// mat = xyflip(planar=true);
// multmatrix(mat) text("Foobar", size=20, halign="center");
module xyflip(p, cp=0, planar) {
assert(is_undef(p), "Module form `xyflip()` does not accept p= argument.");
assert(is_undef(planar), "Module form `xyflip()` does not accept planar= argument.");
mat = xyflip(cp=cp);
multmatrix(mat) children();
}
//////////////////////////////////////////////////////////////////////
// Section: Other Transformations
//////////////////////////////////////////////////////////////////////
function xyflip(p, cp=0, planar=false) =
assert(is_finite(cp) || is_vector(cp))
// Function&Module: frame_map()
// Usage: As module
// frame_map(v1, v2, v3, [reverse=]) { ... }
// Usage: As function to remap points
// transformed = frame_map(v1, v2, v3, p=points, [reverse=]);
// Usage: As function to return a transformation matrix:
// map = frame_map(v1, v2, v3, [reverse=]);
// map = frame_map(x=VECTOR1, y=VECTOR2, [reverse=]);
// map = frame_map(x=VECTOR1, z=VECTOR2, [reverse=]);
// map = frame_map(y=VECTOR1, z=VECTOR2, [reverse=]);
// Topics: Affine, Matrices, Transforms, Rotation
// See Also: rot(), xrot(), yrot(), zrot(), affine2d_zrot()
// Description:
// Maps one coordinate frame to another. You must specify two or
// three of `x`, `y`, and `z`. The specified axes are mapped to the vectors you supplied, so if you
// specify x=[1,1] then the x axis will be mapped to the line y=x. If you
// give two inputs, the third vector is mapped to the appropriate normal to maintain a right hand
// coordinate system. If the vectors you give are orthogonal the result will be a rotation and the
// `reverse` parameter will supply the inverse map, which enables you to map two arbitrary
// coordinate systems to each other by using the canonical coordinate system as an intermediary.
// You cannot use the `reverse` option with non-orthogonal inputs. Note that only the direction
// of the specified vectors matters: the transformation will not apply scaling, though it can
// skew if your provide non-orthogonal axes.
// Arguments:
// x = Destination 3D vector for x axis.
// y = Destination 3D vector for y axis.
// z = Destination 3D vector for z axis.
// reverse = reverse direction of the map for orthogonal inputs. Default: false
// Example: Remap axes after linear extrusion
// frame_map(x=[0,1,0], y=[0,0,1]) linear_extrude(height=10) square(3);
// Example: This map is just a rotation around the z axis
// mat = frame_map(x=[1,1,0], y=[-1,1,0]);
// Example: This map is not a rotation because x and y aren't orthogonal
// mat = frame_map(x=[1,0,0], y=[1,1,0]);
// Example: This sends [1,1,0] to [0,1,1] and [-1,1,0] to [0,-1,1]
// mat = frame_map(x=[0,1,1], y=[0,-1,1]) * frame_map(x=[1,1,0], y=[-1,1,0],reverse=true);
function frame_map(x,y,z, p, reverse=false) =
is_def(p)
? apply(frame_map(x,y,z,reverse=reverse), p)
:
assert(num_defined([x,y,z])>=2, "Must define at least two inputs")
let(
v = unit([-1,1,0]),
n = planar? point2d(v) : v
xvalid = is_undef(x) || (is_vector(x) && len(x)==3),
yvalid = is_undef(y) || (is_vector(y) && len(y)==3),
zvalid = is_undef(z) || (is_vector(z) && len(z)==3)
)
cp == 0 || cp==[0,0,0]? mirror(n, p=p) :
assert(xvalid,"Input x must be a length 3 vector")
assert(yvalid,"Input y must be a length 3 vector")
assert(zvalid,"Input z must be a length 3 vector")
let(
cp = is_finite(cp)? n * cp :
is_vector(cp)? assert(len(cp) == len(n)) cp :
assert(is_finite(cp) || is_vector(cp)),
mat = move(cp) * mirror(n) * move(-cp)
) is_undef(p)? mat : apply(mat, p);
x = is_undef(x)? undef : unit(x,RIGHT),
y = is_undef(y)? undef : unit(y,BACK),
z = is_undef(z)? undef : unit(z,UP),
map = is_undef(x)? [cross(y,z), y, z] :
is_undef(y)? [x, cross(z,x), z] :
is_undef(z)? [x, y, cross(x,y)] :
[x, y, z]
)
reverse? (
let(
ocheck = (
approx(map[0]*map[1],0) &&
approx(map[0]*map[2],0) &&
approx(map[1]*map[2],0)
)
)
assert(ocheck, "Inputs must be orthogonal when reverse==true")
[for (r=map) [for (c=r) c, 0], [0,0,0,1]]
) : [for (r=transpose(map)) [for (c=r) c, 0], [0,0,0,1]];
// Function&Module: xzflip()
//
// Usage: As Module
// xzflip([cp]) ...
// Usage: As Function
// pt = xzflip([cp], p);
// Usage: Get Affine Matrix
// pt = xzflip([cp]);
//
// Topics: Affine, Matrices, Transforms, Reflection, Mirroring
// See Also: mirror(), xflip(), yflip(), zflip(), xyflip(), yzflip(), affine2d_mirror(), affine3d_mirror()
//
// Description:
// Mirrors/reflects across the origin [0,0,0], along the reflection plane where X=Y. If `cp` is given, the reflection plane passes through that point
// * Called as the built-in module, mirrors all children across the line/plane.
// * Called as a function with a point in the `p` argument, returns the point mirrored across the line/plane.
// * Called as a function with a list of points in the `p` argument, returns the list of points, with each one mirrored across the line/plane.
// * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the mirrored patch.
// * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the mirrored VNF.
// * Called as a function without a `p` argument, returns the affine3d 4x4 mirror matrix.
//
// Arguments:
// p = If given, the point, path, patch, or VNF to mirror. Function use only.
// cp = The centerpoint of the plane of reflection, given either as a point, or as a scalar distance away from the origin.
//
// Example:
// left(10) frame_ref();
// right(10) xzflip() frame_ref();
//
// Example:
// xzflip(cp=-15) frame_ref();
//
// Example:
// xzflip(cp=[10,10,10]) frame_ref();
//
// Example: Called as Function
// mat = xzflip();
// multmatrix(mat) frame_ref();
module xzflip(p, cp=0) {
assert(is_undef(p), "Module form `xzflip()` does not accept p= argument.");
mat = xzflip(cp=cp);
multmatrix(mat) children();
module frame_map(x,y,z,p,reverse=false)
{
assert(is_undef(p), "Module form `frame_map()` does not accept p= argument.");
multmatrix(frame_map(x,y,z,reverse=reverse))
children();
}
function xzflip(p, cp=0) =
assert(is_finite(cp) || is_vector(cp))
let( n = unit([-1,0,1]) )
cp == 0 || cp==[0,0,0]? mirror(n, p=p) :
let(
cp = is_finite(cp)? n * cp :
is_vector(cp,3)? cp :
assert(is_finite(cp) || is_vector(cp,3)),
mat = move(cp) * mirror(n) * move(-cp)
) is_undef(p)? mat : apply(mat, p);
// Function&Module: yzflip()
//
// Usage: As Module
// yzflip([x=]) ...
// Usage: As Function
// pt = yzflip(p, [x=]);
// Usage: Get Affine Matrix
// pt = yzflip([x=]);
//
// Topics: Affine, Matrices, Transforms, Reflection, Mirroring
// See Also: mirror(), xflip(), yflip(), zflip(), xyflip(), xzflip(), affine2d_mirror(), affine3d_mirror()
//
// Description:
// Mirrors/reflects across the origin [0,0,0], along the reflection plane where X=Y. If `cp` is given, the reflection plane passes through that point
// * Called as the built-in module, mirrors all children across the line/plane.
// * Called as a function with a point in the `p` argument, returns the point mirrored across the line/plane.
// * Called as a function with a list of points in the `p` argument, returns the list of points, with each one mirrored across the line/plane.
// * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the mirrored patch.
// * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the mirrored VNF.
// * Called as a function without a `p` argument, returns the affine3d 4x4 mirror matrix.
//
// Arguments:
// p = If given, the point, path, patch, or VNF to mirror. Function use only.
// cp = The centerpoint of the plane of reflection, given either as a point, or as a scalar distance away from the origin.
//
// Example:
// left(10) frame_ref();
// right(10) yzflip() frame_ref();
//
// Example:
// yzflip(cp=-15) frame_ref();
//
// Example:
// yzflip(cp=[10,10,10]) frame_ref();
//
// Example: Called as Function
// mat = yzflip();
// multmatrix(mat) frame_ref();
module yzflip(p, cp=0) {
assert(is_undef(p), "Module form `yzflip()` does not accept p= argument.");
mat = yzflip(cp=cp);
multmatrix(mat) children();
}
function yzflip(p, cp=0) =
assert(is_finite(cp) || is_vector(cp))
let( n = unit([0,-1,1]) )
cp == 0 || cp==[0,0,0]? mirror(n, p=p) :
let(
cp = is_finite(cp)? n * cp :
is_vector(cp,3)? cp :
assert(is_finite(cp) || is_vector(cp,3)),
mat = move(cp) * mirror(n) * move(-cp)
) is_undef(p)? mat : apply(mat, p);
//////////////////////////////////////////////////////////////////////
// Section: Skewing
//////////////////////////////////////////////////////////////////////
// Function&Module: skew()
// Usage: As Module

View file

@ -1,193 +0,0 @@
//////////////////////////////////////////////////////////////////////
// LibFile: triangulation.scad
// Functions to triangulate polyhedron faces.
// Includes:
// include <BOSL2/std.scad>
// include <BOSL2/triangulation.scad>
//////////////////////////////////////////////////////////////////////
// Section: Functions
// Function: face_normal()
// Description:
// Given an array of vertices (`points`), and a list of indexes into the
// vertex array (`face`), returns the normal vector of the face.
// Arguments:
// points = Array of vertices for the polyhedron.
// face = The face, given as a list of indices into the vertex array `points`.
function face_normal(points, face) =
let(count=len(face))
unit(
sum(
[
for(i=[0:1:count-1]) cross(
points[face[(i+1)%count]]-points[face[0]],
points[face[(i+2)%count]]-points[face[(i+1)%count]]
)
]
)
)
;
// Function: find_convex_vertex()
// Description:
// Returns the index of a convex point on the given face.
// Arguments:
// points = Array of vertices for the polyhedron.
// face = The face, given as a list of indices into the vertex array `points`.
// facenorm = The normal vector of the face.
function find_convex_vertex(points, face, facenorm, i=0) =
let(count=len(face),
p0=points[face[i]],
p1=points[face[(i+1)%count]],
p2=points[face[(i+2)%count]]
)
(len(face)>i)? (
(cross(p1-p0, p2-p1)*facenorm>0)? (i+1)%count :
find_convex_vertex(points, face, facenorm, i+1)
) : //This should never happen since there is at least 1 convex vertex.
undef
;
// Function: point_in_ear()
// Description: Determine if a point is in a clipable convex ear.
// Arguments:
// points = Array of vertices for the polyhedron.
// face = The face, given as a list of indices into the vertex array `points`.
function point_in_ear(points, face, tests, i=0) =
(i<len(face)-1)?
let(
prev=point_in_ear(points, face, tests, i+1),
test=_check_point_in_ear(points[face[i]], tests)
)
(test>prev[0])? [test, i] : prev
:
[_check_point_in_ear(points[face[i]], tests), i]
;
// Internal non-exposed function.
function _check_point_in_ear(point, tests) =
let(
result=[
(point*tests[0][0])-tests[0][1],
(point*tests[1][0])-tests[1][1],
(point*tests[2][0])-tests[2][1]
]
)
(result[0]>0 && result[1]>0 && result[2]>0)? result[0] : -1
;
// Function: normalize_vertex_perimeter()
// Description: Removes the last item in an array if it is the same as the first item.
// Arguments:
// v = The array to normalize.
function normalize_vertex_perimeter(v) =
let(lv = len(v))
(lv < 2)? v :
(v[lv-1] != v[0])? v :
[for (i=[0:1:lv-2]) v[i]]
;
// Function: is_only_noncolinear_vertex()
// Description:
// Given a face in a polyhedron, and a vertex in that face, returns true
// if that vertex is the only non-colinear vertex in the face.
// Arguments:
// points = Array of vertices for the polyhedron.
// facelist = The face, given as a list of indices into the vertex array `points`.
// vertex = The index into `facelist`, of the vertex to test.
function is_only_noncolinear_vertex(points, facelist, vertex) =
let(
face=select(facelist, vertex+1, vertex-1),
count=len(face)
)
0==sum(
[
for(i=[0:1:count-1]) norm(
cross(
points[face[(i+1)%count]]-points[face[0]],
points[face[(i+2)%count]]-points[face[(i+1)%count]]
)
)
]
)
;
// Function: triangulate_face()
// Description:
// Given a face in a polyhedron, subdivides the face into triangular faces.
// Returns an array of faces, where each face is a list of three vertex indices.
// Arguments:
// points = Array of vertices for the polyhedron.
// face = The face, given as a list of indices into the vertex array `points`.
function triangulate_face(points, face) =
let(
face = deduplicate_indexed(points,face),
count = len(face)
)
(count < 3)? [] :
(count == 3)? [face] :
let(
facenorm=face_normal(points, face),
cv=find_convex_vertex(points, face, facenorm)
)
assert(!is_undef(cv), "Cannot triangulate self-crossing face perimeters.")
let(
pv=(count+cv-1)%count,
nv=(cv+1)%count,
p0=points[face[pv]],
p1=points[face[cv]],
p2=points[face[nv]],
tests=[
[cross(facenorm, p0-p2), cross(facenorm, p0-p2)*p0],
[cross(facenorm, p1-p0), cross(facenorm, p1-p0)*p1],
[cross(facenorm, p2-p1), cross(facenorm, p2-p1)*p2]
],
ear_test=point_in_ear(points, face, tests),
clipable_ear=(ear_test[0]<0),
diagonal_point=ear_test[1]
)
(clipable_ear)? // There is no point inside the ear.
is_only_noncolinear_vertex(points, face, cv)?
// In the point&line degeneracy clip to somewhere in the middle of the line.
flatten([
triangulate_face(points, select(face, cv, (cv+2)%count)),
triangulate_face(points, select(face, (cv+2)%count, cv))
])
:
// Otherwise the ear is safe to clip.
flatten([
[select(face, pv, nv)],
triangulate_face(points, select(face, nv, pv))
])
: // If there is a point inside the ear, make a diagonal and clip along that.
flatten([
triangulate_face(points, select(face, cv, diagonal_point)),
triangulate_face(points, select(face, diagonal_point, cv))
]);
// Function: triangulate_faces()
// Description:
// Subdivides all faces for the given polyhedron that have more than three vertices.
// Returns an array of faces where each face is a list of three vertex array indices.
// Arguments:
// points = Array of vertices for the polyhedron.
// faces = Array of faces for the polyhedron. Each face is a list of 3 or more indices into the `points` array.
function triangulate_faces(points, faces) =
[
for (face=faces) each
len(face)==3? [face] :
triangulate_face(points, normalize_vertex_perimeter(face))
];
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

391
trigonometry.scad Normal file
View file

@ -0,0 +1,391 @@
//////////////////////////////////////////////////////////////////////
// LibFile: trigonometry.scad
// Trigonometry shortcuts for people who can't be bothered to remember
// all the function relations, or silly acronyms like SOHCAHTOA.
// Includes:
// include <BOSL2/std.scad>
//////////////////////////////////////////////////////////////////////
// Section: 2D General Triangle Functions
// Function: law_of_cosines()
// Usage:
// C = law_of_cosines(a, b, c);
// c = law_of_cosines(a, b, C=);
// Topics: Geometry, Trigonometry, Triangles
// Description:
// Applies the Law of Cosines for an arbitrary triangle. Given three side lengths, returns the
// angle in degrees for the corner opposite of the third side. Given two side lengths, and the
// angle between them, returns the length of the third side.
// Figure(2D):
// stroke([[-50,0], [10,60], [50,0]], closed=true);
// color("black") {
// translate([ 33,35]) text(text="a", size=8, halign="center", valign="center");
// translate([ 0,-6]) text(text="b", size=8, halign="center", valign="center");
// translate([-22,35]) text(text="c", size=8, halign="center", valign="center");
// }
// color("blue") {
// translate([-37, 6]) text(text="A", size=8, halign="center", valign="center");
// translate([ 9,51]) text(text="B", size=8, halign="center", valign="center");
// translate([ 38, 6]) text(text="C", size=8, halign="center", valign="center");
// }
// Arguments:
// a = The length of the first side.
// b = The length of the second side.
// c = The length of the third side.
// ---
// C = The angle in degrees of the corner opposite of the third side.
// See Also: law_of_sines()
function law_of_cosines(a, b, c, C) =
// Triangle Law of Cosines:
// c^2 = a^2 + b^2 - 2*a*b*cos(C)
assert(num_defined([c,C]) == 1, "Must give exactly one of c= or C=.")
is_undef(c) ? sqrt(a*a + b*b - 2*a*b*cos(C)) :
acos(constrain((a*a + b*b - c*c) / (2*a*b), -1, 1));
// Function: law_of_sines()
// Usage:
// B = law_of_sines(a, A, b);
// b = law_of_sines(a, A, B=);
// Topics: Geometry, Trigonometry, Triangles
// Description:
// Applies the Law of Sines for an arbitrary triangle. Given two triangle side lengths and the
// angle between them, returns the angle of the corner opposite of the second side. Given a side
// length, the opposing angle, and a second angle, returns the length of the side opposite of the
// second angle.
// Figure(2D):
// stroke([[-50,0], [10,60], [50,0]], closed=true);
// color("black") {
// translate([ 33,35]) text(text="a", size=8, halign="center", valign="center");
// translate([ 0,-6]) text(text="b", size=8, halign="center", valign="center");
// translate([-22,35]) text(text="c", size=8, halign="center", valign="center");
// }
// color("blue") {
// translate([-37, 6]) text(text="A", size=8, halign="center", valign="center");
// translate([ 9,51]) text(text="B", size=8, halign="center", valign="center");
// translate([ 38, 6]) text(text="C", size=8, halign="center", valign="center");
// }
// Arguments:
// a = The length of the first side.
// A = The angle in degrees of the corner opposite of the first side.
// b = The length of the second side.
// ---
// B = The angle in degrees of the corner opposite of the second side.
// See Also: law_of_cosines()
function law_of_sines(a, A, b, B) =
// Triangle Law of Sines:
// a/sin(A) = b/sin(B) = c/sin(C)
assert(num_defined([b,B]) == 1, "Must give exactly one of b= or B=.")
let( r = a/sin(A) )
is_undef(b) ? r*sin(B) :
asin(constrain(b/r, -1, 1));
// Function: triangle_area()
// Usage:
// area = triangle_area(p1,p2,p3);
// Topics: Geometry, Trigonometry, Triangles, Area
// Description:
// Returns the area of a triangle formed between three 2D or 3D vertices.
// Result will be negative if the points are 2D and in clockwise order.
// Arguments:
// p1 = The first vertex of the triangle.
// p2 = The second vertex of the triangle.
// p3 = The third vertex of the triangle.
// Example:
// triangle_area([0,0], [5,10], [10,0]); // Returns -50
// triangle_area([10,0], [5,10], [0,0]); // Returns 50
function triangle_area(p1,p2,p3) =
assert( is_path([p1,p2,p3]), "Invalid points or incompatible dimensions." )
len(p1)==3
? 0.5*norm(cross(p3-p1,p3-p2))
: 0.5*cross(p3-p1,p3-p2);
// Section: 2D Right Triangle Functions
// This is a set of functions to make it easier to perform trig calculations on right triangles.
// In general, all these functions are named using these abbreviations:
// - **hyp**: The length of the Hypotenuse.
// - **adj**: The length of the side adjacent to the angle.
// - **opp**: The length of the side opposite to the angle.
// - **ang**: The angle size in degrees.
// .
// If you know two of those, and want to know the value of a third, you will need to call a
// function named like `AAA_BBB_to_CCC()`. For example, if you know the length of the hypotenuse,
// and the length of the side adjacent to the angle, and want to learn the length of the side
// opposite to the angle, you will call `opp = hyp_adj_to_opp(hyp,adj);`.
// Figure(2D):
// color("brown") {
// stroke([[40,0], [40,10], [50,10]]);
// left(50) stroke(arc(r=37,angle=30));
// }
// color("lightgreen") stroke([[-50,0], [50,60], [50,0]], closed=true);
// color("black") {
// translate([ 62,25]) text(text="opp", size=8, halign="center", valign="center");
// translate([ 0,-6]) text(text="adj", size=8, halign="center", valign="center");
// translate([ 0,40]) text(text="hyp", size=8, halign="center", valign="center");
// translate([-25, 5]) text(text="ang", size=7, halign="center", valign="center");
// }
// Function: hyp_opp_to_adj()
// Alias: opp_hyp_to_adj()
// Usage:
// adj = hyp_opp_to_adj(hyp,opp);
// adj = opp_hyp_to_adj(opp,hyp);
// Topics: Geometry, Trigonometry, Triangles
// Description:
// Given the lengths of the hypotenuse and opposite side of a right triangle, returns the length
// of the adjacent side.
// Arguments:
// hyp = The length of the hypotenuse of the right triangle.
// opp = The length of the side of the right triangle that is opposite from the primary angle.
// Example:
// hyp = hyp_opp_to_adj(5,3); // Returns: 4
function hyp_opp_to_adj(hyp,opp) =
assert(is_finite(hyp+opp) && hyp>=0 && opp>=0,
"Triangle side lengths should be a positive numbers." )
sqrt(hyp*hyp-opp*opp);
function opp_hyp_to_adj(opp,hyp) = hyp_opp_to_adj(hyp,opp);
// Function: hyp_ang_to_adj()
// Alias: ang_hyp_to_adj()
// Usage:
// adj = hyp_ang_to_adj(hyp,ang);
// adj = ang_hyp_to_adj(ang,hyp);
// Topics: Geometry, Trigonometry, Triangles
// Description:
// Given the length of the hypotenuse and the angle of the primary corner of a right triangle,
// returns the length of the adjacent side.
// Arguments:
// hyp = The length of the hypotenuse of the right triangle.
// ang = The angle in degrees of the primary corner of the right triangle.
// Example:
// adj = hyp_ang_to_adj(8,60); // Returns: 4
function hyp_ang_to_adj(hyp,ang) =
assert(is_finite(hyp) && hyp>=0, "Triangle side length should be a positive number." )
assert(is_finite(ang) && ang>-90 && ang<90, "The angle should be an acute angle." )
hyp*cos(ang);
function ang_hyp_to_adj(ang,hyp) = hyp_ang_to_adj(hyp, ang);
// Function: opp_ang_to_adj()
// Alias: ang_opp_to_adj()
// Usage:
// adj = opp_ang_to_adj(opp,ang);
// adj = ang_opp_to_adj(ang,opp);
// Topics: Geometry, Trigonometry, Triangles
// Description:
// Given the angle of the primary corner of a right triangle, and the length of the side opposite of it,
// returns the length of the adjacent side.
// Arguments:
// opp = The length of the side of the right triangle that is opposite from the primary angle.
// ang = The angle in degrees of the primary corner of the right triangle.
// Example:
// adj = opp_ang_to_adj(8,30); // Returns: 4
function opp_ang_to_adj(opp,ang) =
assert(is_finite(opp) && opp>=0, "Triangle side length should be a positive number." )
assert(is_finite(ang) && ang>-90 && ang<90, "The angle should be an acute angle." )
opp/tan(ang);
function ang_opp_to_adj(ang,opp) = opp_ang_to_adj(opp,ang);
// Function: hyp_adj_to_opp()
// Alias: adj_hyp_to_opp()
// Usage:
// opp = hyp_adj_to_opp(hyp,adj);
// opp = adj_hyp_to_opp(adj,hyp);
// Topics: Geometry, Trigonometry, Triangles
// Description:
// Given the length of the hypotenuse and the adjacent side, returns the length of the opposite side.
// Arguments:
// hyp = The length of the hypotenuse of the right triangle.
// adj = The length of the side of the right triangle that is adjacent to the primary angle.
// Example:
// opp = hyp_adj_to_opp(5,4); // Returns: 3
function hyp_adj_to_opp(hyp,adj) =
assert(is_finite(hyp) && hyp>=0 && is_finite(adj) && adj>=0,
"Triangle side lengths should be a positive numbers." )
sqrt(hyp*hyp-adj*adj);
function adj_hyp_to_opp(adj,hyp) = hyp_adj_to_opp(hyp,adj);
// Function: hyp_ang_to_opp()
// Alias: ang_hyp_to_opp()
// Usage:
// opp = hyp_ang_to_opp(hyp,ang);
// opp = ang_hyp_to_opp(ang,hyp);
// Topics: Geometry, Trigonometry, Triangles
// Description:
// Given the length of the hypotenuse of a right triangle, and the angle of the corner, returns the length of the opposite side.
// Arguments:
// hyp = The length of the hypotenuse of the right triangle.
// ang = The angle in degrees of the primary corner of the right triangle.
// Example:
// opp = hyp_ang_to_opp(8,30); // Returns: 4
function hyp_ang_to_opp(hyp,ang) =
assert(is_finite(hyp)&&hyp>=0, "Triangle side length should be a positive number." )
assert(is_finite(ang) && ang>-90 && ang<90, "The angle should be an acute angle." )
hyp*sin(ang);
function ang_hyp_to_opp(ang,hyp) = hyp_ang_to_opp(hyp,ang);
// Function: adj_ang_to_opp()
// Alias: ang_adj_to_opp()
// Usage:
// opp = adj_ang_to_opp(adj,ang);
// opp = ang_adj_to_opp(ang,adj);
// Topics: Geometry, Trigonometry, Triangles
// Description:
// Given the length of the adjacent side of a right triangle, and the angle of the corner, returns the length of the opposite side.
// Arguments:
// adj = The length of the side of the right triangle that is adjacent to the primary angle.
// ang = The angle in degrees of the primary corner of the right triangle.
// Example:
// opp = adj_ang_to_opp(8,45); // Returns: 8
function adj_ang_to_opp(adj,ang) =
assert(is_finite(adj)&&adj>=0, "Triangle side length should be a positive number." )
assert(is_finite(ang) && ang>-90 && ang<90, "The angle should be an acute angle." )
adj*tan(ang);
function ang_adj_to_opp(ang,adj) = adj_ang_to_opp(adj,ang);
// Function: adj_opp_to_hyp()
// Alias: opp_adj_to_hyp()
// Usage:
// hyp = adj_opp_to_hyp(adj,opp);
// hyp = opp_adj_to_hyp(opp,adj);
// Topics: Geometry, Trigonometry, Triangles
// Description:
// Given the length of the adjacent and opposite sides of a right triangle, returns the length of thee hypotenuse.
// Arguments:
// adj = The length of the side of the right triangle that is adjacent to the primary angle.
// opp = The length of the side of the right triangle that is opposite from the primary angle.
// Example:
// hyp = adj_opp_to_hyp(3,4); // Returns: 5
function adj_opp_to_hyp(adj,opp) =
assert(is_finite(opp) && opp>=0 && is_finite(adj) && adj>=0,
"Triangle side lengths should be a positive numbers." )
norm([opp,adj]);
function opp_adj_to_hyp(opp,adj) = adj_opp_to_hyp(adj,opp);
// Function: adj_ang_to_hyp()
// Alias: ang_adj_to_hyp()
// Usage:
// hyp = adj_ang_to_hyp(adj,ang);
// hyp = ang_adj_to_hyp(ang,adj);
// Topics: Geometry, Trigonometry, Triangles
// Description:
// For a right triangle, given the length of the adjacent side, and the corner angle, returns the length of the hypotenuse.
// Arguments:
// adj = The length of the side of the right triangle that is adjacent to the primary angle.
// ang = The angle in degrees of the primary corner of the right triangle.
// Example:
// hyp = adj_ang_to_hyp(4,60); // Returns: 8
function adj_ang_to_hyp(adj,ang) =
assert(is_finite(adj) && adj>=0, "Triangle side length should be a positive number." )
assert(is_finite(ang) && ang>-90 && ang<90, "The angle should be an acute angle." )
adj/cos(ang);
function ang_adj_to_hyp(ang,adj) = adj_ang_to_hyp(adj,ang);
// Function: opp_ang_to_hyp()
// Alias: ang_opp_to_hyp()
// Usage:
// hyp = opp_ang_to_hyp(opp,ang);
// hyp = ang_opp_to_hyp(ang,opp);
// Topics: Geometry, Trigonometry, Triangles
// Description:
// For a right triangle, given the length of the opposite side, and the corner angle, returns the length of the hypotenuse.
// Arguments:
// opp = The length of the side of the right triangle that is opposite from the primary angle.
// ang = The angle in degrees of the primary corner of the right triangle.
// Example:
// hyp = opp_ang_to_hyp(4,30); // Returns: 8
function opp_ang_to_hyp(opp,ang) =
assert(is_finite(opp) && opp>=0, "Triangle side length should be a positive number." )
assert(is_finite(ang) && ang>-90 && ang<90, "The angle should be an acute angle." )
opp/sin(ang);
function ang_opp_to_hyp(ang,opp) = opp_ang_to_hyp(opp,ang);
// Function: hyp_adj_to_ang()
// Alias: adj_hyp_to_ang()
// Usage:
// ang = hyp_adj_to_ang(hyp,adj);
// ang = adj_hyp_to_ang(adj,hyp);
// Description:
// For a right triangle, given the lengths of the hypotenuse and the adjacent sides, returns the angle of the corner.
// Arguments:
// hyp = The length of the hypotenuse of the right triangle.
// adj = The length of the side of the right triangle that is adjacent to the primary angle.
// Example:
// ang = hyp_adj_to_ang(8,4); // Returns: 60 degrees
function hyp_adj_to_ang(hyp,adj) =
assert(is_finite(hyp) && hyp>0 && is_finite(adj) && adj>=0,
"Triangle side lengths should be positive numbers." )
acos(adj/hyp);
function adj_hyp_to_ang(adj,hyp) = hyp_adj_to_ang(hyp,adj);
// Function: hyp_opp_to_ang()
// Alias: opp_hyp_to_ang()
// Usage:
// ang = hyp_opp_to_ang(hyp,opp);
// ang = opp_hyp_to_ang(opp,hyp);
// Topics: Geometry, Trigonometry, Triangles
// Description:
// For a right triangle, given the lengths of the hypotenuse and the opposite sides, returns the angle of the corner.
// Arguments:
// hyp = The length of the hypotenuse of the right triangle.
// opp = The length of the side of the right triangle that is opposite from the primary angle.
// Example:
// ang = hyp_opp_to_ang(8,4); // Returns: 30 degrees
function hyp_opp_to_ang(hyp,opp) =
assert(is_finite(hyp+opp) && hyp>0 && opp>=0,
"Triangle side lengths should be positive numbers." )
asin(opp/hyp);
function opp_hyp_to_ang(opp,hyp) = hyp_opp_to_ang(hyp,opp);
// Function: adj_opp_to_ang()
// Alias: opp_adj_to_ang()
// Usage:
// ang = adj_opp_to_ang(adj,opp);
// ang = opp_adj_to_ang(opp,adj);
// Topics: Geometry, Trigonometry, Triangles
// Description:
// For a right triangle, given the lengths of the adjacent and opposite sides, returns the angle of the corner.
// Arguments:
// adj = The length of the side of the right triangle that is adjacent to the primary angle.
// opp = The length of the side of the right triangle that is opposite from the primary angle.
// Example:
// ang = adj_opp_to_ang(sqrt(3)/2,0.5); // Returns: 30 degrees
function adj_opp_to_ang(adj,opp) =
assert(is_finite(adj+opp) && adj>0 && opp>=0,
"Triangle side lengths should be positive numbers." )
atan2(opp,adj);
function opp_adj_to_ang(opp,adj) = adj_opp_to_ang(adj,opp);
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -433,7 +433,7 @@ function turtle3d(commands, state=RIGHT, transforms=false, full_state=false, rep
state = is_matrix(state,4,4) ? [[state],[yrot(90)],1,90,0] :
is_vector(state,3) ?
let( updir = UP - (UP * state) * state / (state*state) )
[[affine3d_frame_map(x=state, z=approx(norm(updir),0) ? FWD : updir)], [yrot(90)],1, 90, 0]
[[frame_map(x=state, z=approx(norm(updir),0) ? FWD : updir)], [yrot(90)],1, 90, 0]
: assert(_turtle3d_state_valid(state), "Supplied state is not valid")
state,
finalstate = _turtle3d_repeat(commands, state, repeat)

View file

@ -387,6 +387,25 @@ cube([20,11,45], center=true, $tags="hole")
cube([40,10,90], center=true, $tags="body");
```
Tags (and therefore tag-based operations like `diff()`) only work correctly with attachable children.
However, a number of built-in modules for making shapes are *not* attachable. Some notable
non-attachable modules are `circle()`, `square()`, `text()`, `linear_extrude()`, `rotate_extrude()`,
`polygon()`, `polyhedron()`, `import()`, `surface()`, `union()`, `difference()`, `intersection()`,
`offset()`, `hull()`, and `minkowski()`.
To allow you to use tags-based operations with non-attachable shapes, you can wrap them with the
`tags()` module to specify their tags. For example:
```openscad
diff("hole")
cuboid(50)
attach(TOP)
tags("hole")
rotate_extrude()
right(15)
square(10,center=true);
```
### `intersect(a, <b>, <keep>)`
To perform an intersection of attachables, you can use the `intersect()` module. If given one
@ -481,9 +500,9 @@ rounding a corner:
```openscad
module round_corner(r) difference() {
translate(-[1,1,1])
cube(r+1);
cube(r+1);
translate([r,r,r])
sphere(r=r, style="aligned", $fn=quantup(segs(r),4));
sphere(r=r, style="aligned", $fn=quantup(segs(r),4));
}
round_corner(r=10);
```
@ -493,9 +512,9 @@ You can use that mask to round various corners of a cube:
```openscad
module round_corner(r) difference() {
translate(-[1,1,1])
cube(r+1);
cube(r+1);
translate([r,r,r])
sphere(r=r, style="aligned", $fn=quantup(segs(r),4));
sphere(r=r, style="aligned", $fn=quantup(segs(r),4));
}
diff("mask")
cube([50,60,70],center=true)
@ -509,9 +528,9 @@ You can use `edge_mask()` and `corner_mask()` together as well:
```openscad
module round_corner(r) difference() {
translate(-[1,1,1])
cube(r+1);
cube(r+1);
translate([r,r,r])
sphere(r=r, style="aligned", $fn=quantup(segs(r),4));
sphere(r=r, style="aligned", $fn=quantup(segs(r),4));
}
module round_edge(l,r) difference() {
translate([-1,-1,-l/2])

View file

@ -253,9 +253,11 @@ easier to select colors using other color schemes. You can use the HSL or Hue-S
color scheme with the `HSL()` module:
```openscad
for (h=[0:0.1:1], s=[0:0.1:1], l=[0:0.1:1]) {
translate(100*[h,s,l]) {
HSL(h*360,1-s,l) cube(10,center=true);
n = 10; size = 100/n;
for (a=count(n), b=count(n), c=count(n)) {
let( h=360*a/n, s=1-b/(n-1), l=c/(n-1))
translate(size*[a,b,c]) {
HSL(h,s,l) cube(size);
}
}
```
@ -263,9 +265,11 @@ for (h=[0:0.1:1], s=[0:0.1:1], l=[0:0.1:1]) {
You can use the HSV or Hue-Saturation-Value color scheme with the `HSV()` module:
```openscad
for (h=[0:0.1:1], s=[0:0.1:1], v=[0:0.1:1]) {
translate(100*[h,s,v]) {
HSV(h*360,1-s,v) cube(10,center=true);
n = 10; size = 100/n;
for (a=count(n), b=count(n), c=count(n)) {
let( h=360*a/n, s=1-b/(n-1), v=c/(n-1))
translate(size*[a,b,c]) {
HSV(h,s,v) cube(size);
}
}
```

View file

@ -1,6 +1,6 @@
//////////////////////////////////////////////////////////////////////
// LibFile: common.scad
// Common functions used in argument processing.
// LibFile: utility.scad
// Utility functions used in argument processing.
// Includes:
// include <BOSL2/std.scad>
//////////////////////////////////////////////////////////////////////
@ -18,7 +18,7 @@
// Description:
// Returns a string representing the type of the value. One of "undef", "boolean", "number", "nan", "string", "list", "range", "function" or "invalid".
// Some malformed "ranges", like '[0:NAN:INF]' and '[0:"a":INF]', may be classified as "undef" or "invalid".
// Examples:
// Example:
// typ = typeof(undef); // Returns: "undef"
// typ = typeof(true); // Returns: "boolean"
// typ = typeof(42); // Returns: "number"
@ -294,7 +294,7 @@ function default(v,dflt=undef) = is_undef(v)? dflt : v;
// Arguments:
// v = The list whose items are being checked.
// recursive = If true, sublists are checked recursively for defined values. The first sublist that has a defined item is returned.
// Examples:
// Example:
// val = first_defined([undef,7,undef,true]); // Returns: 7
function first_defined(v,recursive=false,_i=0) =
_i<len(v) && (
@ -321,9 +321,9 @@ function first_defined(v,recursive=false,_i=0) =
// vals = The values to return the first one which is not `undef`.
// names = A string with comma-separated names for the arguments whose values are passed in `vals`.
// dflt = If given, the value returned if all `vals` are `undef`.
// Examples:
// length = one_defined([length,L,l], ["length","L","l"]);
// length = one_defined([length,L,l], "length,L,l", dflt=1);
// Example:
// length1 = one_defined([length,L,l], ["length","L","l"]);
// length2 = one_defined([length,L,l], "length,L,l", dflt=1);
function one_defined(vals, names, dflt=_UNDEF) =
let(
@ -420,13 +420,13 @@ function all_defined(v,recursive=false) =
// center = If not `undef`, this overrides the value of `anchor`.
// uncentered = The value to return if `center` is not `undef` and evaluates as false. Default: ALLNEG
// dflt = The default value to return if both `anchor` and `center` are `undef`. Default: `CENTER`
// Examples:
// anchr = get_anchor(undef, undef, BOTTOM, TOP); // Returns: [0, 0, 1] (TOP)
// anchr = get_anchor(RIGHT, undef, BOTTOM, TOP); // Returns: [1, 0, 0] (RIGHT)
// anchr = get_anchor(undef, false, BOTTOM, TOP); // Returns: [0, 0,-1] (BOTTOM)
// anchr = get_anchor(RIGHT, false, BOTTOM, TOP); // Returns: [0, 0,-1] (BOTTOM)
// anchr = get_anchor(undef, true, BOTTOM, TOP); // Returns: [0, 0, 0] (CENTER)
// anchr = get_anchor(RIGHT, true, BOTTOM, TOP); // Returns: [0, 0, 0] (CENTER)
// Example:
// anchr1 = get_anchor(undef, undef, BOTTOM, TOP); // Returns: [0, 0, 1] (TOP)
// anchr2 = get_anchor(RIGHT, undef, BOTTOM, TOP); // Returns: [1, 0, 0] (RIGHT)
// anchr3 = get_anchor(undef, false, BOTTOM, TOP); // Returns: [0, 0,-1] (BOTTOM)
// anchr4 = get_anchor(RIGHT, false, BOTTOM, TOP); // Returns: [0, 0,-1] (BOTTOM)
// anchr5 = get_anchor(undef, true, BOTTOM, TOP); // Returns: [0, 0, 0] (CENTER)
// anchr6 = get_anchor(RIGHT, true, BOTTOM, TOP); // Returns: [0, 0, 0] (CENTER)
function get_anchor(anchor,center,uncentered=BOT,dflt=CENTER) =
!is_undef(center)? (center? CENTER : uncentered) :
!is_undef(anchor)? anchor :
@ -454,7 +454,7 @@ function get_anchor(anchor,center,uncentered=BOT,dflt=CENTER) =
// d2 = Second most specific diameter.
// d = Most general diameter.
// dflt = Value to return if all other values given are `undef`.
// Examples:
// Example:
// r = get_radius(r1=undef, r=undef, dflt=undef); // Returns: undef
// r = get_radius(r1=undef, r=undef, dflt=1); // Returns: 1
// r = get_radius(r1=undef, r=6, dflt=1); // Returns: 6
@ -488,6 +488,8 @@ function get_radius(r1, r2, r, d1, d2, d, dflt) =
// Topics: Argument Handling
// See Also: get_anchor(), get_radius(), force_list()
// Description:
// This is expands a scalar or a list with length less than 3 to a length 3 vector in the
// same way that OpenSCAD expands short vectors in some contexts, e.g. cube(10) or rotate([45,90]).
// If `v` is a scalar, and `dflt==undef`, returns `[v, v, v]`.
// If `v` is a scalar, and `dflt!=undef`, returns `[v, dflt, dflt]`.
// If `v` is a vector, returns the first 3 items, with any missing values replaced by `dflt`.
@ -495,7 +497,7 @@ function get_radius(r1, r2, r, d1, d2, d, dflt) =
// Arguments:
// v = Value to return vector from.
// dflt = Default value to set empty vector parts from.
// Examples:
// Example:
// vec = scalar_vec3(undef); // Returns: undef
// vec = scalar_vec3(10); // Returns: [10,10,10]
// vec = scalar_vec3(10,1); // Returns: [10,1,1]
@ -514,7 +516,7 @@ function scalar_vec3(v, dflt) =
// Calculate the standard number of sides OpenSCAD would give a circle based on `$fn`, `$fa`, and `$fs`.
// Arguments:
// r = Radius of circle to get the number of segments for.
// Examples:
// Example:
// $fn=12; sides=segs(10); // Returns: 12
// $fa=2; $fs=3, sides=segs(10); // Returns: 21
function segs(r) =
@ -566,7 +568,7 @@ function no_function(name) =
// Description:
// Asserts that the called module exists only as a function.
// Example:
// function foo() = no_module();
// module foo() { no_module(); }
module no_module() {
assert(false, str("You called ",parent_module(1),"() as a module but it is available only as a function"));
}

View file

@ -107,6 +107,53 @@ function v_ceil(v) =
[for (x=v) ceil(x)];
// Function: v_lookup()
// Description:
// Works just like the built-in function [`lookup()`](https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/Mathematical_Functions#lookup), except that it can also interpolate between vector result values of the same length.
// Arguments:
// x = The scalar value to look up.
// v = A list of [KEY,VAL] pairs. KEYs are scalars. VALs should either all be scalar, or all be vectors of the same length.
// Example:
// x = v_lookup(4.5, [[4, [3,4,5]], [5, [5,6,7]]]); // Returns: [4,5,6]
function v_lookup(x, v) =
is_num(v[0][1])? lookup(x,v) :
let(
i = lookup(x, [for (i=idx(v)) [v[i].x,i]]),
vlo = v[floor(i)],
vhi = v[ceil(i)],
lo = vlo[1],
hi = vhi[1]
)
assert(is_vector(lo) && is_vector(hi),
"Result values must all be numbers, or all be vectors.")
assert(len(lo) == len(hi), "Vector result values must be the same length")
vlo.x == vhi.x? vlo[1] :
let( u = (x - vlo.x) / (vhi.x - vlo.x) )
lerp(lo,hi,u);
// Function: pointlist_bounds()
// Usage:
// pt_pair = pointlist_bounds(pts);
// Topics: Geometry, Bounding Boxes, Bounds
// Description:
// Finds the bounds containing all the points in `pts` which can be a list of points in any dimension.
// Returns a list of two items: a list of the minimums and a list of the maximums. For example, with
// 3d points `[[MINX, MINY, MINZ], [MAXX, MAXY, MAXZ]]`
// Arguments:
// pts = List of points.
function pointlist_bounds(pts) =
assert(is_path(pts,dim=undef,fast=true) , "Invalid pointlist." )
let(
select = ident(len(pts[0])),
spread = [
for(i=[0:len(pts[0])-1])
let( spreadi = pts*select[i] )
[ min(spreadi), max(spreadi) ]
]
) transpose(spread);
// Function: unit()
// Usage:
// unit(v, [error]);
@ -116,13 +163,13 @@ function v_ceil(v) =
// Arguments:
// v = The vector to normalize.
// error = If given, and input is a zero-length vector, this value is returned. Default: Assert error on zero-length vector.
// Examples:
// unit([10,0,0]); // Returns: [1,0,0]
// unit([0,10,0]); // Returns: [0,1,0]
// unit([0,0,10]); // Returns: [0,0,1]
// unit([0,-10,0]); // Returns: [0,-1,0]
// unit([0,0,0],[1,2,3]); // Returns: [1,2,3]
// unit([0,0,0]); // Asserts an error.
// Example:
// v1 = unit([10,0,0]); // Returns: [1,0,0]
// v2 = unit([0,10,0]); // Returns: [0,1,0]
// v3 = unit([0,0,10]); // Returns: [0,0,1]
// v4 = unit([0,-10,0]); // Returns: [0,-1,0]
// v5 = unit([0,0,0],[1,2,3]); // Returns: [1,2,3]
// v6 = unit([0,0,0]); // Asserts an error.
function unit(v, error=[[["ASSERT"]]]) =
assert(is_vector(v), str("Expected a vector. Got: ",v))
norm(v)<EPSILON? (error==[[["ASSERT"]]]? assert(norm(v)>=EPSILON,"Tried to normalize a zero vector") : error) :
@ -144,13 +191,13 @@ function unit(v, error=[[["ASSERT"]]]) =
// v1 = First vector or point.
// v2 = Second vector or point.
// v3 = Third point in three point mode.
// Examples:
// vector_angle(UP,LEFT); // Returns: 90
// vector_angle(RIGHT,LEFT); // Returns: 180
// vector_angle(UP+RIGHT,RIGHT); // Returns: 45
// vector_angle([10,10], [0,0], [10,-10]); // Returns: 90
// vector_angle([10,0,10], [0,0,0], [-10,10,0]); // Returns: 120
// vector_angle([[10,0,10], [0,0,0], [-10,10,0]]); // Returns: 120
// Example:
// ang1 = vector_angle(UP,LEFT); // Returns: 90
// ang2 = vector_angle(RIGHT,LEFT); // Returns: 180
// ang3 = vector_angle(UP+RIGHT,RIGHT); // Returns: 45
// ang4 = vector_angle([10,10], [0,0], [10,-10]); // Returns: 90
// ang5 = vector_angle([10,0,10], [0,0,0], [-10,10,0]); // Returns: 120
// ang6 = vector_angle([[10,0,10], [0,0,0], [-10,10,0]]); // Returns: 120
function vector_angle(v1,v2,v3) =
assert( ( is_undef(v3) && ( is_undef(v2) || same_shape(v1,v2) ) )
|| is_consistent([v1,v2,v3]) ,
@ -186,13 +233,13 @@ function vector_angle(v1,v2,v3) =
// v1 = First vector or point.
// v2 = Second vector or point.
// v3 = Third point in three point mode.
// Examples:
// vector_axis(UP,LEFT); // Returns: [0,-1,0] (FWD)
// vector_axis(RIGHT,LEFT); // Returns: [0,-1,0] (FWD)
// vector_axis(UP+RIGHT,RIGHT); // Returns: [0,1,0] (BACK)
// vector_axis([10,10], [0,0], [10,-10]); // Returns: [0,0,-1] (DOWN)
// vector_axis([10,0,10], [0,0,0], [-10,10,0]); // Returns: [-0.57735, -0.57735, 0.57735]
// vector_axis([[10,0,10], [0,0,0], [-10,10,0]]); // Returns: [-0.57735, -0.57735, 0.57735]
// Example:
// axis1 = vector_axis(UP,LEFT); // Returns: [0,-1,0] (FWD)
// axis2 = vector_axis(RIGHT,LEFT); // Returns: [0,-1,0] (FWD)
// axis3 = vector_axis(UP+RIGHT,RIGHT); // Returns: [0,1,0] (BACK)
// axis4 = vector_axis([10,10], [0,0], [10,-10]); // Returns: [0,0,-1] (DOWN)
// axis5 = vector_axis([10,0,10], [0,0,0], [-10,10,0]); // Returns: [-0.57735, -0.57735, 0.57735]
// axis6 = vector_axis([[10,0,10], [0,0,0], [-10,10,0]]); // Returns: [-0.57735, -0.57735, 0.57735]
function vector_axis(v1,v2=undef,v3=undef) =
is_vector(v3)
? assert(is_consistent([v3,v2,v1]), "Bad arguments.")
@ -216,9 +263,41 @@ function vector_axis(v1,v2=undef,v3=undef) =
// Section: Vector Searching
// Function: closest_point()
// Usage:
// index = closest_point(pt, points);
// Topics: Geometry, Points, Distance
// Description:
// Given a list of `points`, finds the index of the closest point to `pt`.
// Arguments:
// pt = The point to find the closest point to.
// points = The list of points to search.
function closest_point(pt, points) =
assert( is_vector(pt), "Invalid point." )
assert(is_path(points,dim=len(pt)), "Invalid pointlist or incompatible dimensions." )
min_index([for (p=points) norm(p-pt)]);
// Function: furthest_point()
// Usage:
// index = furthest_point(pt, points);
// Topics: Geometry, Points, Distance
// Description:
// Given a list of `points`, finds the index of the furthest point from `pt`.
// Arguments:
// pt = The point to find the farthest point from.
// points = The list of points to search.
function furthest_point(pt, points) =
assert( is_vector(pt), "Invalid point." )
assert(is_path(points,dim=len(pt)), "Invalid pointlist or incompatible dimensions." )
max_index([for (p=points) norm(p-pt)]);
// Function: vector_search()
// Usage:
// indices = vector_search(query, r, target);

757
vnf.scad
View file

@ -1,222 +1,31 @@
//////////////////////////////////////////////////////////////////////
// LibFile: vnf.scad
// VNF structures, holding Vertices 'N' Faces for use with `polyhedron().`
// The Vertices'N'Faces structure (VNF) holds the data used by polyhedron() to construct objects: a vertex
// list and a list of faces. This library makes it easier to construct polyhedra by providing
// functions to construct, merge, and modify VNF data, while avoiding common pitfalls such as
// reversed faces.
// Includes:
// include <BOSL2/std.scad>
//////////////////////////////////////////////////////////////////////
include <triangulation.scad>
// Creating Polyhedrons with VNF Structures
// Section: Creating Polyhedrons with VNF Structures
// Section: VNF Testing and Access
// VNF stands for "Vertices'N'Faces". VNF structures are 2-item lists, `[VERTICES,FACES]` where the
// first item is a list of vertex points, and the second is a list of face indices into the vertex
// list. Each VNF is self contained, with face indices referring only to its own vertex list.
// You can construct a `polyhedron()` in parts by describing each part in a self-contained VNF, then
// merge the various VNFs to get the completed polyhedron vertex list and faces.
EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
// Function: is_vnf()
// Usage:
// bool = is_vnf(x);
// Description:
// Returns true if the given value looks like a VNF structure.
function is_vnf(x) =
is_list(x) &&
len(x)==2 &&
is_list(x[0]) &&
is_list(x[1]) &&
(x[0]==[] || (len(x[0])>=3 && is_vector(x[0][0]))) &&
(x[1]==[] || is_vector(x[1][0]));
// Function: is_vnf_list()
// Description: Returns true if the given value looks passingly like a list of VNF structures.
function is_vnf_list(x) = is_list(x) && all([for (v=x) is_vnf(v)]);
// Function: vnf_vertices()
// Description: Given a VNF structure, returns the list of vertex points.
function vnf_vertices(vnf) = vnf[0];
// Function: vnf_faces()
// Description: Given a VNF structure, returns the list of faces, where each face is a list of indices into the VNF vertex list.
function vnf_faces(vnf) = vnf[1];
// Function: vnf_quantize()
// Usage:
// vnf2 = vnf_quantize(vnf,[q]);
// Description:
// Quantizes the vertex coordinates of the VNF to the given quanta `q`.
// Arguments:
// vnf = The VNF to quantize.
// q = The quanta to quantize the VNF coordinates to.
function vnf_quantize(vnf,q=pow(2,-12)) =
[[for (pt = vnf[0]) quant(pt,q)], vnf[1]];
// Function: vnf_get_vertex()
// Usage:
// vvnf = vnf_get_vertex(vnf, p);
// Description:
// Finds the index number of the given vertex point `p` in the given VNF structure `vnf`.
// If said point does not already exist in the VNF vertex list, it is added to the returned VNF.
// Returns: `[INDEX, VNF]` where INDEX is the index of the point in the returned VNF's vertex list,
// and VNF is the possibly modified new VNF structure. If `p` is given as a list of points, then
// the returned INDEX will be a list of indices.
// Arguments:
// vnf = The VNF structue to get the point index from.
// p = The point, or list of points to get the index of.
// Example:
// vnf1 = vnf_get_vertex(p=[3,5,8]); // Returns: [0, [[[3,5,8]],[]]]
// vnf2 = vnf_get_vertex(vnf1, p=[3,2,1]); // Returns: [1, [[[3,5,8],[3,2,1]],[]]]
// vnf3 = vnf_get_vertex(vnf2, p=[3,5,8]); // Returns: [0, [[[3,5,8],[3,2,1]],[]]]
// vnf4 = vnf_get_vertex(vnf3, p=[[1,3,2],[3,2,1]]); // Returns: [[1,2], [[[3,5,8],[3,2,1],[1,3,2]],[]]]
function vnf_get_vertex(vnf=EMPTY_VNF, p) =
let(
isvec = is_vector(p),
pts = isvec? [p] : p,
res = set_union(vnf[0], pts, get_indices=true)
) [
(isvec? res[0][0] : res[0]),
[ res[1], vnf[1] ]
];
// Function: vnf_add_face()
// Usage:
// vnf_add_face(vnf, pts);
// Description:
// Given a VNF structure and a list of face vertex points, adds the face to the VNF structure.
// Returns the modified VNF structure `[VERTICES, FACES]`. It is up to the caller to make
// sure that the points are in the correct order to make the face normal point outwards.
// Arguments:
// vnf = The VNF structure to add a face to.
// pts = The vertex points for the face.
function vnf_add_face(vnf=EMPTY_VNF, pts) =
assert(is_vnf(vnf))
assert(is_path(pts))
let(
res = set_union(vnf[0], pts, get_indices=true),
face = deduplicate(res[0], closed=true)
) [
res[1],
concat(vnf[1], len(face)>2? [face] : [])
];
// Function: vnf_add_faces()
// Usage:
// vnf_add_faces(vnf, faces);
// Description:
// Given a VNF structure and a list of faces, where each face is given as a list of vertex points,
// adds the faces to the VNF structure. Returns the modified VNF structure `[VERTICES, FACES]`.
// It is up to the caller to make sure that the points are in the correct order to make the face
// normals point outwards.
// Arguments:
// vnf = The VNF structure to add a face to.
// faces = The list of faces, where each face is given as a list of vertex points.
function vnf_add_faces(vnf=EMPTY_VNF, faces) =
assert(is_vnf(vnf))
assert(is_list(faces))
let(
res = set_union(vnf[0], flatten(faces), get_indices=true),
idxs = res[0],
nverts = res[1],
offs = cumsum([0, for (face=faces) len(face)]),
ifaces = [
for (i=idx(faces)) [
for (j=idx(faces[i]))
idxs[offs[i]+j]
]
]
) [
nverts,
concat(vnf[1],ifaces)
];
// Function: vnf_merge()
// Usage:
// vnf = vnf_merge([VNF, VNF, VNF, ...], [cleanup],[eps]);
// Description:
// Given a list of VNF structures, merges them all into a single VNF structure.
// When cleanup=true, it consolidates all duplicate vertices with a tolerance `eps`,
// drops unreferenced vertices and any final face with less than 3 vertices.
// Unreferenced vertices of the input VNFs that doesn't duplicate any other vertex
// are not dropped.
// Arguments:
// vnfs - a list of the VNFs to merge in one VNF.
// cleanup - when true, consolidates the duplicate vertices of the merge. Default: false
// eps - the tolerance in finding duplicates when cleanup=true. Default: EPSILON
function vnf_merge(vnfs, cleanup=false, eps=EPSILON) =
is_vnf(vnfs) ? vnf_merge([vnfs], cleanup, eps) :
assert( is_vnf_list(vnfs) , "Improper vnf or vnf list")  
let (
offs = cumsum([ 0, for (vnf = vnfs) len(vnf[0]) ]),
verts = [for (vnf=vnfs) each vnf[0]],
faces =
[ for (i = idx(vnfs))
let( faces = vnfs[i][1] )
for (face = faces)
if ( len(face) >= 3 )
[ for (j = face)
assert( j>=0 && j<len(vnfs[i][0]),
str("VNF number ", i, " has a face indexing an nonexistent vertex") )
offs[i] + j ]
]
)
! cleanup ? [verts, faces] :
let(
dedup = vector_search(verts,eps,verts), // collect vertex duplicates
map = [for(i=idx(verts)) min(dedup[i]) ], // remap duplic vertices
offset = cumsum([for(i=idx(verts)) map[i]==i ? 0 : 1 ]), // remaping face vertex offsets
map2 = list(idx(verts))-offset, // map old vertex indices to new indices
nverts = [for(i=idx(verts)) if(map[i]==i) verts[i] ], // eliminates all unreferenced vertices
nfaces =
[ for(face=faces)
let(
nface = [ for(vi=face) map2[map[vi]] ],
dface = [for (i=idx(nface))
if( nface[i]!=nface[(i+1)%len(nface)])
nface[i] ]
)
if(len(dface) >= 3) dface
]
)
[nverts, nfaces];
// Function: vnf_reverse_faces()
// Usage:
// rvnf = vnf_reverse_faces(vnf);
// Description:
// Reverses the facing of all the faces in the given VNF.
function vnf_reverse_faces(vnf) =
[vnf[0], [for (face=vnf[1]) reverse(face)]];
// Function: vnf_triangulate()
// Usage:
// vnf2 = vnf_triangulate(vnf);
// Description:
// Forces triangulation of faces in the VNF that have more than 3 vertices.
function vnf_triangulate(vnf) =
let(
vnf = is_vnf_list(vnf)? vnf_merge(vnf) : vnf,
verts = vnf[0]
) [verts, triangulate_faces(verts, vnf[1])];
// Section: Constructing VNFs
// Function: vnf_vertex_array()
// Usage:
// vnf = vnf_vertex_array(points, [caps], [cap1], [cap2], [reverse], [col_wrap], [row_wrap], [vnf]);
// vnf = vnf_vertex_array(points, [caps], [cap1], [cap2], [style], [reverse], [col_wrap], [row_wrap], [vnf]);
// Description:
// Creates a VNF structure from a vertex list, by dividing the vertices into columns and rows,
// adding faces to tile the surface. You can optionally have faces added to wrap the last column
@ -468,6 +277,209 @@ function vnf_tri_array(points, row_wrap=false, reverse=false, vnf=EMPTY_VNF) =
vnf_merge(cleanup=true, [vnf, [flatten(points), faces]]);
// Function: vnf_add_face()
// Usage:
// vnf_add_face(vnf, pts);
// Description:
// Given a VNF structure and a list of face vertex points, adds the face to the VNF structure.
// Returns the modified VNF structure `[VERTICES, FACES]`. It is up to the caller to make
// sure that the points are in the correct order to make the face normal point outwards.
// Arguments:
// vnf = The VNF structure to add a face to.
// pts = The vertex points for the face.
function vnf_add_face(vnf=EMPTY_VNF, pts) =
assert(is_vnf(vnf))
assert(is_path(pts))
let(
res = set_union(vnf[0], pts, get_indices=true),
face = deduplicate(res[0], closed=true)
) [
res[1],
concat(vnf[1], len(face)>2? [face] : [])
];
// Function: vnf_add_faces()
// Usage:
// vnf_add_faces(vnf, faces);
// Description:
// Given a VNF structure and a list of faces, where each face is given as a list of vertex points,
// adds the faces to the VNF structure. Returns the modified VNF structure `[VERTICES, FACES]`.
// It is up to the caller to make sure that the points are in the correct order to make the face
// normals point outwards.
// Arguments:
// vnf = The VNF structure to add a face to.
// faces = The list of faces, where each face is given as a list of vertex points.
function vnf_add_faces(vnf=EMPTY_VNF, faces) =
assert(is_vnf(vnf))
assert(is_list(faces))
let(
res = set_union(vnf[0], flatten(faces), get_indices=true),
idxs = res[0],
nverts = res[1],
offs = cumsum([0, for (face=faces) len(face)]),
ifaces = [
for (i=idx(faces)) [
for (j=idx(faces[i]))
idxs[offs[i]+j]
]
]
) [
nverts,
concat(vnf[1],ifaces)
];
// Function: vnf_merge()
// Usage:
// vnf = vnf_merge([VNF, VNF, VNF, ...], [cleanup],[eps]);
// Description:
// Given a list of VNF structures, merges them all into a single VNF structure.
// When cleanup=true, it consolidates all duplicate vertices with a tolerance `eps`,
// drops unreferenced vertices and any final face with less than 3 vertices.
// Unreferenced vertices of the input VNFs that doesn't duplicate any other vertex
// are not dropped.
// Arguments:
// vnfs - a list of the VNFs to merge in one VNF.
// cleanup - when true, consolidates the duplicate vertices of the merge. Default: false
// eps - the tolerance in finding duplicates when cleanup=true. Default: EPSILON
function vnf_merge(vnfs, cleanup=false, eps=EPSILON) =
is_vnf(vnfs) ? vnf_merge([vnfs], cleanup, eps) :
assert( is_vnf_list(vnfs) , "Improper vnf or vnf list")  
let (
offs = cumsum([ 0, for (vnf = vnfs) len(vnf[0]) ]),
verts = [for (vnf=vnfs) each vnf[0]],
faces =
[ for (i = idx(vnfs))
let( faces = vnfs[i][1] )
for (face = faces)
if ( len(face) >= 3 )
[ for (j = face)
assert( j>=0 && j<len(vnfs[i][0]),
str("VNF number ", i, " has a face indexing an nonexistent vertex") )
offs[i] + j ]
]
)
! cleanup ? [verts, faces] :
let(
dedup = vector_search(verts,eps,verts), // collect vertex duplicates
map = [for(i=idx(verts)) min(dedup[i]) ], // remap duplic vertices
offset = cumsum([for(i=idx(verts)) map[i]==i ? 0 : 1 ]), // remaping face vertex offsets
map2 = list(idx(verts))-offset, // map old vertex indices to new indices
nverts = [for(i=idx(verts)) if(map[i]==i) verts[i] ], // eliminates all unreferenced vertices
nfaces =
[ for(face=faces)
let(
nface = [ for(vi=face) map2[map[vi]] ],
dface = [for (i=idx(nface))
if( nface[i]!=nface[(i+1)%len(nface)])
nface[i] ]
)
if(len(dface) >= 3) dface
]
)
[nverts, nfaces];
// Function: is_vnf()
// Usage:
// bool = is_vnf(x);
// Description:
// Returns true if the given value looks like a VNF structure.
function is_vnf(x) =
is_list(x) &&
len(x)==2 &&
is_list(x[0]) &&
is_list(x[1]) &&
(x[0]==[] || (len(x[0])>=3 && is_vector(x[0][0]))) &&
(x[1]==[] || is_vector(x[1][0]));
// Function: is_vnf_list()
// Description: Returns true if the given value looks passingly like a list of VNF structures.
function is_vnf_list(x) = is_list(x) && all([for (v=x) is_vnf(v)]);
// Function: vnf_vertices()
// Description: Given a VNF structure, returns the list of vertex points.
function vnf_vertices(vnf) = vnf[0];
// Function: vnf_faces()
// Description: Given a VNF structure, returns the list of faces, where each face is a list of indices into the VNF vertex list.
function vnf_faces(vnf) = vnf[1];
// Function: vnf_get_vertex()
// Usage:
// vvnf = vnf_get_vertex(vnf, p);
// Description:
// Finds the index number of the given vertex point `p` in the given VNF structure `vnf`.
// If said point does not already exist in the VNF vertex list, it is added to the returned VNF.
// Returns: `[INDEX, VNF]` where INDEX is the index of the point in the returned VNF's vertex list,
// and VNF is the possibly modified new VNF structure. If `p` is given as a list of points, then
// the returned INDEX will be a list of indices.
// Arguments:
// vnf = The VNF structue to get the point index from.
// p = The point, or list of points to get the index of.
// Example:
// vnf1 = vnf_get_vertex(p=[3,5,8]); // Returns: [0, [[[3,5,8]],[]]]
// vnf2 = vnf_get_vertex(vnf1, p=[3,2,1]); // Returns: [1, [[[3,5,8],[3,2,1]],[]]]
// vnf3 = vnf_get_vertex(vnf2, p=[3,5,8]); // Returns: [0, [[[3,5,8],[3,2,1]],[]]]
// vnf4 = vnf_get_vertex(vnf3, p=[[1,3,2],[3,2,1]]); // Returns: [[1,2], [[[3,5,8],[3,2,1],[1,3,2]],[]]]
function vnf_get_vertex(vnf=EMPTY_VNF, p) =
let(
isvec = is_vector(p),
pts = isvec? [p] : p,
res = set_union(vnf[0], pts, get_indices=true)
) [
(isvec? res[0][0] : res[0]),
[ res[1], vnf[1] ]
];
// Section: Altering the VNF Internals
// Function: vnf_reverse_faces()
// Usage:
// rvnf = vnf_reverse_faces(vnf);
// Description:
// Reverses the facing of all the faces in the given VNF.
function vnf_reverse_faces(vnf) =
[vnf[0], [for (face=vnf[1]) reverse(face)]];
// Function: vnf_quantize()
// Usage:
// vnf2 = vnf_quantize(vnf,[q]);
// Description:
// Quantizes the vertex coordinates of the VNF to the given quanta `q`.
// Arguments:
// vnf = The VNF to quantize.
// q = The quanta to quantize the VNF coordinates to.
function vnf_quantize(vnf,q=pow(2,-12)) =
[[for (pt = vnf[0]) quant(pt,q)], vnf[1]];
// Function: vnf_triangulate()
// Usage:
// vnf2 = vnf_triangulate(vnf);
// Description:
// Triangulates faces in the VNF that have more than 3 vertices.
function vnf_triangulate(vnf) =
let(
vnf = is_vnf_list(vnf)? vnf_merge(vnf) : vnf,
verts = vnf[0],
faces = [for (face=vnf[1]) each len(face)==3 ? [face] :
polygon_triangulate(verts, face)]
) [verts, faces];
// Section: Turning a VNF into geometry
// Module: vnf_polyhedron()
// Usage:
@ -493,7 +505,6 @@ module vnf_polyhedron(vnf, convexity=2, extent=true, cp=[0,0,0], anchor="origin"
}
// Module: vnf_wireframe()
// Usage:
// vnf_wireframe(vnf, <r|d>);
@ -528,6 +539,8 @@ module vnf_wireframe(vnf, r, d)
}
// Section: Operations on VNFs
// Function: vnf_volume()
// Usage:
// vol = vnf_volume(vnf);
@ -545,6 +558,16 @@ function vnf_volume(vnf) =
])/6;
// Function: vnf_area()
// Usage:
// area = vnf_area(vnf);
// Description:
// Returns the surface area in any VNF by adding up the area of all its faces. The VNF need not be a manifold.
function vnf_area(vnf) =
let(verts=vnf[0])
sum([for(face=vnf[1]) polygon_area(select(verts,face))]);
// Function: vnf_centroid()
// Usage:
// vol = vnf_centroid(vnf);
@ -573,6 +596,115 @@ function vnf_centroid(vnf) =
pos[1]/pos[0]/4;
// Function: vnf_halfspace()
// Usage:
// newvnf = vnf_halfspace(plane, vnf, [closed]);
// Description:
// Returns the intersection of the vnf with a half space. The half space is defined by
// plane = [A,B,C,D], taking the side where the normal [A,B,C] points: Ax+By+CzD.
// If closed is set to false then the cut face is not included in the vnf. This could
// allow further extension of the vnf by merging with other vnfs.
// Arguments:
// plane = plane defining the boundary of the half space
// vnf = vnf to cut
// closed = if false do not return include cut face(s). Default: true
// Example:
// vnf = cube(10,center=true);
// cutvnf = vnf_halfspace([-1,1,-1,0], vnf);
// vnf_polyhedron(cutvnf);
// Example: Cut face has 2 components
// vnf = path_sweep(circle(r=4, $fn=16),
// circle(r=20, $fn=64),closed=true);
// cutvnf = vnf_halfspace([-1,1,-4,0], vnf);
// vnf_polyhedron(cutvnf);
// Example: Cut face is not simply connected
// vnf = path_sweep(circle(r=4, $fn=16),
// circle(r=20, $fn=64),closed=true);
// cutvnf = vnf_halfspace([0,0.7,-4,0], vnf);
// vnf_polyhedron(cutvnf);
// Example: Cut object has multiple components
// function knot(a,b,t) = // rolling knot
// [ a * cos (3 * t) / (1 - b* sin (2 *t)),
// a * sin( 3 * t) / (1 - b* sin (2 *t)),
// 1.8 * b * cos (2 * t) /(1 - b* sin (2 *t))];
// a = 0.8; b = sqrt (1 - a * a);
// ksteps = 400;
// knot_path = [for (i=[0:ksteps-1]) 50 * knot(a,b,(i/ksteps)*360)];
// ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]];
// knot=path_sweep(ushape, knot_path, closed=true, method="incremental");
// cut_knot = vnf_halfspace([1,0,0,0], knot);
// vnf_polyhedron(cut_knot);
function vnf_halfspace(plane, vnf, closed=true) =
let(
inside = [for(x=vnf[0]) plane*[each x,-1] >= 0 ? 1 : 0],
vertexmap = [0,each cumsum(inside)],
faces_edges_vertices = _vnfcut(plane, vnf[0],vertexmap,inside, vnf[1], last(vertexmap)),
newvert = concat(bselect(vnf[0],inside), faces_edges_vertices[2])
)
closed==false ? [newvert, faces_edges_vertices[0]] :
let(
allpaths = _assemble_paths(newvert, faces_edges_vertices[1]),
newpaths = [for(p=allpaths) if (len(p)>=3) p
else assert(approx(p[0],p[1]),"Orphan edge found when assembling cut edges.")
]
)
len(newpaths)<=1 ? [newvert, concat(faces_edges_vertices[0], newpaths)]
:
let(
faceregion = project_plane(plane, newpaths),
facevnf = region_faces(faceregion,reverse=true)
)
vnf_merge([[newvert, faces_edges_vertices[0]], lift_plane(plane, facevnf)]);
function _assemble_paths(vertices, edges, paths=[],i=0) =
i==len(edges) ? paths :
norm(vertices[edges[i][0]]-vertices[edges[i][1]])<EPSILON ? echo(degen=i)_assemble_paths(vertices,edges,paths,i+1) :
let( // Find paths that connects on left side and right side of the edges (if one exists)
left = [for(j=idx(paths)) if (approx(vertices[last(paths[j])],vertices[edges[i][0]])) j],
right = [for(j=idx(paths)) if (approx(vertices[edges[i][1]],vertices[paths[j][0]])) j]
)
assert(len(left)<=1 && len(right)<=1)
let(
keep_path = list_remove(paths,concat(left,right)),
update_path = left==[] && right==[] ? edges[i]
: left==[] ? concat([edges[i][0]],paths[right[0]])
: right==[] ? concat(paths[left[0]],[edges[i][1]])
: left != right ? concat(paths[left[0]], paths[right[0]])
: paths[left[0]]
)
_assemble_paths(vertices, edges, concat(keep_path, [update_path]), i+1);
function _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount, newfaces=[], newedges=[], newvertices=[], i=0) =
i==len(faces) ? [newfaces, newedges, newvertices] :
let(
pts_inside = select(inside,faces[i])
)
all(pts_inside) ? _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount,
concat(newfaces, [select(vertexmap,faces[i])]), newedges, newvertices, i+1):
!any(pts_inside) ? _vnfcut(plane, vertices, vertexmap,inside, faces, vertcount, newfaces, newedges, newvertices, i+1):
let(
first = search([[1,0]],pair(pts_inside,wrap=true),0)[0],
second = search([[0,1]],pair(pts_inside,wrap=true),0)[0]
)
assert(len(first)==1 && len(second)==1, "Found concave face in VNF. Run vnf_triangulate first to ensure convex faces.")
let(
newface = [each select(vertexmap,select(faces[i],second[0]+1,first[0])),vertcount, vertcount+1],
newvert = [plane_line_intersection(plane, select(vertices,select(faces[i],first[0],first[0]+1)),eps=0),
plane_line_intersection(plane, select(vertices,select(faces[i],second[0],second[0]+1)),eps=0)]
)
true //!approx(newvert[0],newvert[1])
? _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount+2,
concat(newfaces, [newface]), concat(newedges,[[vertcount+1,vertcount]]),concat(newvertices,newvert),i+1)
:len(newface)>3
? _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount+1,
concat(newfaces, [list_head(newface)]), newedges,concat(newvertices,[newvert[0]]),i+1)
:
_vnfcut(plane, vertices, vertexmap, inside, faces, vertcount,newfaces, newedges, newvert, i+1);
function _triangulate_planar_convex_polygons(polys) =
polys==[]? [] :
let(
@ -694,8 +826,8 @@ function vnf_bend(vnf,r,d,axis="Z") =
[for(i = [1:1:steps-1]) i*step+bmin.x],
facepolys = [for (face=vnf[1]) select(verts,face)],
splits = axis=="X"?
split_polygons_at_each_y(facepolys, bend_at) :
split_polygons_at_each_x(facepolys, bend_at),
_split_polygons_at_each_y(facepolys, bend_at) :
_split_polygons_at_each_x(facepolys, bend_at),
newtris = _triangulate_planar_convex_polygons(splits),
bent_faces = [
for (tri = newtris) [
@ -712,6 +844,113 @@ function vnf_bend(vnf,r,d,axis="Z") =
) vnf_add_faces(faces=bent_faces);
function _split_polygon_at_x(poly, x) =
let(
xs = subindex(poly,0)
) (min(xs) >= x || max(xs) <= x)? [poly] :
let(
poly2 = [
for (p = pair(poly,true)) each [
p[0],
if(
(p[0].x < x && p[1].x > x) ||
(p[1].x < x && p[0].x > x)
) let(
u = (x - p[0].x) / (p[1].x - p[0].x)
) [
x, // Important for later exact match tests
u*(p[1].y-p[0].y)+p[0].y,
u*(p[1].z-p[0].z)+p[0].z,
]
]
],
out1 = [for (p = poly2) if(p.x <= x) p],
out2 = [for (p = poly2) if(p.x >= x) p],
out3 = [
if (len(out1)>=3) each split_path_at_self_crossings(out1),
if (len(out2)>=3) each split_path_at_self_crossings(out2),
],
out = [for (p=out3) if (len(p) > 2) cleanup_path(p)]
) out;
function _split_polygon_at_y(poly, y) =
let(
ys = subindex(poly,1)
) (min(ys) >= y || max(ys) <= y)? [poly] :
let(
poly2 = [
for (p = pair(poly,true)) each [
p[0],
if(
(p[0].y < y && p[1].y > y) ||
(p[1].y < y && p[0].y > y)
) let(
u = (y - p[0].y) / (p[1].y - p[0].y)
) [
u*(p[1].x-p[0].x)+p[0].x,
y, // Important for later exact match tests
u*(p[1].z-p[0].z)+p[0].z,
]
]
],
out1 = [for (p = poly2) if(p.y <= y) p],
out2 = [for (p = poly2) if(p.y >= y) p],
out3 = [
if (len(out1)>=3) each split_path_at_self_crossings(out1),
if (len(out2)>=3) each split_path_at_self_crossings(out2),
],
out = [for (p=out3) if (len(p) > 2) cleanup_path(p)]
) out;
/// Function: _split_polygons_at_each_x()
// Usage:
// splitpolys = split_polygons_at_each_x(polys, xs);
/// Topics: Geometry, Polygons, Intersections
// Description:
// Given a list of 3D polygons, splits all of them wherever they cross any X value given in `xs`.
// Arguments:
// polys = A list of 3D polygons to split.
// xs = A list of scalar X values to split at.
function _split_polygons_at_each_x(polys, xs, _i=0) =
assert( [for (poly=polys) if (!is_path(poly,3)) 1] == [], "Expects list of 3D paths.")
assert( is_vector(xs), "The split value list should contain only numbers." )
_i>=len(xs)? polys :
_split_polygons_at_each_x(
[
for (poly = polys)
each _split_polygon_at_x(poly, xs[_i])
], xs, _i=_i+1
);
///Internal Function: _split_polygons_at_each_y()
// Usage:
// splitpolys = _split_polygons_at_each_y(polys, ys);
/// Topics: Geometry, Polygons, Intersections
// Description:
// Given a list of 3D polygons, splits all of them wherever they cross any Y value given in `ys`.
// Arguments:
// polys = A list of 3D polygons to split.
// ys = A list of scalar Y values to split at.
function _split_polygons_at_each_y(polys, ys, _i=0) =
assert( [for (poly=polys) if (!is_path(poly,3)) 1] == [], "Expects list of 3D paths.")
assert( is_vector(ys), "The split value list should contain only numbers." )
_i>=len(ys)? polys :
_split_polygons_at_each_y(
[
for (poly = polys)
each _split_polygon_at_y(poly, ys[_i])
], ys, _i=_i+1
);
// Section: Debugging VNFs
// Function&Module: vnf_validate()
// Usage: As Function
// fails = vnf_validate(vnf);
@ -724,6 +963,7 @@ function vnf_bend(vnf,r,d,axis="Z") =
// bad edges and vertices, overlaid on a transparent gray polyhedron of the VNF.
// .
// Currently checks for these problems:
// .
// Type | Color | Code | Message
// ------- | -------- | ------------ | ---------------------------------
// WARNING | Yellow | BIG_FACE | Face has more than 3 vertices, and may confuse CGAL.
@ -899,7 +1139,7 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) =
c = varr[ic]
)
if (!approx(a,b) && !approx(b,c) && !approx(a,c)) let(
pt = segment_closest_point([a,c],b)
pt = line_closest_point([a,c],b,SEGMENT)
)
if (approx(pt,b))
_vnf_validate_err("T_JUNCTION", [b])
@ -966,7 +1206,7 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) =
faceverts = [for (k=face) varr[k]]
)
if (is_num(area) && abs(area) > EPSILON)
if (!coplanar(faceverts))
if (!is_coplanar(faceverts))
_vnf_validate_err("NONPLANAR", faceverts)
]),
issues = concat(issues, nonplanars)
@ -1039,114 +1279,5 @@ module vnf_validate(vnf, size=1, show_warns=true, check_isects=false) {
}
// Section: VNF Transformations
// Function: vnf_halfspace()
// Usage:
// newvnf = vnf_halfspace(plane, vnf, [closed]);
// Description:
// Returns the intersection of the vnf with a half space. The half space is defined by
// plane = [A,B,C,D], taking the side where the normal [A,B,C] points: Ax+By+CzD.
// If closed is set to false then the cut face is not included in the vnf. This could
// allow further extension of the vnf by merging with other vnfs.
// Arguments:
// plane = plane defining the boundary of the half space
// vnf = vnf to cut
// closed = if false do not return include cut face(s). Default: true
// Example:
// vnf = cube(10,center=true);
// cutvnf = vnf_halfspace([-1,1,-1,0], vnf);
// vnf_polyhedron(cutvnf);
// Example: Cut face has 2 components
// vnf = path_sweep(circle(r=4, $fn=16),
// circle(r=20, $fn=64),closed=true);
// cutvnf = vnf_halfspace([-1,1,-4,0], vnf);
// vnf_polyhedron(cutvnf);
// Example: Cut face is not simply connected
// vnf = path_sweep(circle(r=4, $fn=16),
// circle(r=20, $fn=64),closed=true);
// cutvnf = vnf_halfspace([0,0.7,-4,0], vnf);
// vnf_polyhedron(cutvnf);
// Example: Cut object has multiple components
// function knot(a,b,t) = // rolling knot
// [ a * cos (3 * t) / (1 - b* sin (2 *t)),
// a * sin( 3 * t) / (1 - b* sin (2 *t)),
// 1.8 * b * cos (2 * t) /(1 - b* sin (2 *t))];
// a = 0.8; b = sqrt (1 - a * a);
// ksteps = 400;
// knot_path = [for (i=[0:ksteps-1]) 50 * knot(a,b,(i/ksteps)*360)];
// ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]];
// knot=path_sweep(ushape, knot_path, closed=true, method="incremental");
// cut_knot = vnf_halfspace([1,0,0,0], knot);
// vnf_polyhedron(cut_knot);
function vnf_halfspace(plane, vnf, closed=true) =
let(
inside = [for(x=vnf[0]) plane*[each x,-1] >= 0 ? 1 : 0],
vertexmap = [0,each cumsum(inside)],
faces_edges_vertices = _vnfcut(plane, vnf[0],vertexmap,inside, vnf[1], last(vertexmap)),
newvert = concat(bselect(vnf[0],inside), faces_edges_vertices[2])
)
closed==false ? [newvert, faces_edges_vertices[0]] :
let(
allpaths = _assemble_paths(newvert, faces_edges_vertices[1]),
newpaths = [for(p=allpaths) if (len(p)>=3) p
else assert(approx(p[0],p[1]),"Orphan edge found when assembling cut edges.")
]
)
len(newpaths)<=1 ? [newvert, concat(faces_edges_vertices[0], newpaths)]
:
let(
faceregion = project_plane(plane, newpaths),
facevnf = region_faces(faceregion,reverse=true)
)
vnf_merge([[newvert, faces_edges_vertices[0]], lift_plane(plane, facevnf)]);
function _assemble_paths(vertices, edges, paths=[],i=0) =
i==len(edges) ? paths :
norm(vertices[edges[i][0]]-vertices[edges[i][1]])<EPSILON ? echo(degen=i)_assemble_paths(vertices,edges,paths,i+1) :
let( // Find paths that connects on left side and right side of the edges (if one exists)
left = [for(j=idx(paths)) if (approx(vertices[last(paths[j])],vertices[edges[i][0]])) j],
right = [for(j=idx(paths)) if (approx(vertices[edges[i][1]],vertices[paths[j][0]])) j]
)
assert(len(left)<=1 && len(right)<=1)
let(
keep_path = list_remove(paths,concat(left,right)),
update_path = left==[] && right==[] ? edges[i]
: left==[] ? concat([edges[i][0]],paths[right[0]])
: right==[] ? concat(paths[left[0]],[edges[i][1]])
: left != right ? concat(paths[left[0]], paths[right[0]])
: paths[left[0]]
)
_assemble_paths(vertices, edges, concat(keep_path, [update_path]), i+1);
function _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount, newfaces=[], newedges=[], newvertices=[], i=0) =
i==len(faces) ? [newfaces, newedges, newvertices] :
let(
pts_inside = select(inside,faces[i])
)
all(pts_inside) ? _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount,
concat(newfaces, [select(vertexmap,faces[i])]), newedges, newvertices, i+1):
!any(pts_inside) ? _vnfcut(plane, vertices, vertexmap,inside, faces, vertcount, newfaces, newedges, newvertices, i+1):
let(
first = search([[1,0]],pair(pts_inside,wrap=true),0)[0],
second = search([[0,1]],pair(pts_inside,wrap=true),0)[0]
)
assert(len(first)==1 && len(second)==1, "Found concave face in VNF. Run vnf_triangulate first to ensure convex faces.")
let(
newface = [each select(vertexmap,select(faces[i],second[0]+1,first[0])),vertcount, vertcount+1],
newvert = [plane_line_intersection(plane, select(vertices,select(faces[i],first[0],first[0]+1)),eps=0),
plane_line_intersection(plane, select(vertices,select(faces[i],second[0],second[0]+1)),eps=0)]
)
true //!approx(newvert[0],newvert[1])
? _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount+2,
concat(newfaces, [newface]), concat(newedges,[[vertcount+1,vertcount]]),concat(newvertices,newvert),i+1)
:len(newface)>3
? _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount+1,
concat(newfaces, [list_head(newface)]), newedges,concat(newvertices,[newvert[0]]),i+1)
:
_vnfcut(plane, vertices, vertexmap, inside, faces, vertcount,newfaces, newedges, newvert, i+1);
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -88,7 +88,7 @@ module wiring(path, wires, wirediam=2, rounding=10, wirenum=0, bezsteps=12) {
];
offsets = hex_offsets(wires, wirediam);
bezpath = fillet_path(path, rounding);
poly = simplify_path(path3d(bezier_path(bezpath, bezsteps)));
poly = path_merge_collinear(path3d(bezier_path(bezpath, bezsteps)));
n = max(segs(wirediam), 8);
r = wirediam/2;
for (i = [0:1:wires-1]) {