mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2024-12-29 00:09:41 +00:00
removed old triangulation.scad
other file name changes
This commit is contained in:
parent
6e61a6567f
commit
856f54df32
10 changed files with 940 additions and 1174 deletions
934
drawing.scad
Normal file
934
drawing.scad
Normal file
|
@ -0,0 +1,934 @@
|
|||
//////////////////////////////////////////////////////////////////////
|
||||
// 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 {
|
||||
spos = path_pos_from_start(path,trim1,closed=false);
|
||||
epos = path_pos_from_end(path,trim2,closed=false);
|
||||
path2 = path_subselect(path, spos[0], spos[1], epos[0], epos[1]);
|
||||
widths = concat(
|
||||
[lerp(width[spos[0]], width[(spos[0]+1)%len(width)], spos[1])],
|
||||
[for (i = [spos[0]+1:1:epos[0]]) width[i]],
|
||||
[lerp(width[epos[0]], width[(epos[0]+1)%len(width)], epos[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))
|
||||
[];
|
||||
|
|
@ -1604,8 +1604,8 @@ function polygon_triangulate(poly, ind, eps=EPSILON) =
|
|||
|| (is_vector(ind) && min(ind)>=0 && max(ind)<len(poly) ),
|
||||
"Improper or out of bounds list of indices")
|
||||
let( ind = deduplicate_indexed(poly,is_undef(ind) ? count(len(poly)) : ind) )
|
||||
len(ind) < 3 ? [] :
|
||||
len(ind) == 3 ? [ind] :
|
||||
len(ind) < 3 ? [] :
|
||||
len(poly[ind[0]]) == 3
|
||||
? // represents the polygon projection on its plane as a 2d polygon
|
||||
let(
|
||||
|
|
30
paths.scad
30
paths.scad
|
@ -6,9 +6,6 @@
|
|||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
include <triangulation.scad>
|
||||
|
||||
|
||||
// Section: Functions
|
||||
|
||||
|
||||
|
@ -609,33 +606,6 @@ function path_add_jitter(path, dist=1/512, closed=true) =
|
|||
];
|
||||
|
||||
|
||||
// Function: path3d_spiral()
|
||||
// Description:
|
||||
// Returns a 3D spiral path.
|
||||
// Usage:
|
||||
// path3d_spiral(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(path3d_spiral(turns=2.5, h=100, n=24, r=50), N=1, showpts=true);
|
||||
function path3d_spiral(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: path_self_intersections()
|
||||
// Usage:
|
||||
|
|
897
shapes2d.scad
897
shapes2d.scad
|
@ -1,10 +1,6 @@
|
|||
//////////////////////////////////////////////////////////////////////
|
||||
// LibFile: shapes2d.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. You can create regular polygons
|
||||
// This file lets you create regular polygons
|
||||
// with optional rounded corners and alignment features not
|
||||
// available with circle(). The file also provides teardrop2d,
|
||||
// which is useful for 3d printable holes. Lastly you can use the
|
||||
|
@ -16,897 +12,6 @@
|
|||
// 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 {
|
||||
spos = path_pos_from_start(path,trim1,closed=false);
|
||||
epos = path_pos_from_end(path,trim2,closed=false);
|
||||
path2 = path_subselect(path, spos[0], spos[1], epos[0], epos[1]);
|
||||
widths = concat(
|
||||
[lerp(width[spos[0]], width[(spos[0]+1)%len(width)], spos[1])],
|
||||
[for (i = [spos[0]+1:1:epos[0]]) width[i]],
|
||||
[lerp(width[epos[0]], width[(epos[0]+1)%len(width)], epos[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);
|
||||
}
|
||||
|
||||
|
||||
// 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 _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))
|
||||
[];
|
||||
|
||||
|
||||
|
||||
// Section: 2D Primitives
|
||||
|
||||
|
|
3
std.scad
3
std.scad
|
@ -15,8 +15,9 @@ 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>
|
||||
|
|
|
@ -1,55 +1,6 @@
|
|||
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();
|
||||
|
||||
|
||||
module test_rect() {
|
||||
|
|
|
@ -1,194 +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(
|
||||
points = path3d(points),
|
||||
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.
|
||||
concat(
|
||||
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.
|
||||
[
|
||||
select(face, pv, nv),
|
||||
each triangulate_face(points, select(face, nv, pv))
|
||||
]
|
||||
: // If there is a point inside the ear, make a diagonal and clip along that.
|
||||
concat(
|
||||
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
|
5
vnf.scad
5
vnf.scad
|
@ -9,8 +9,6 @@
|
|||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
include <triangulation.scad>
|
||||
|
||||
// Creating Polyhedrons with VNF Structures
|
||||
|
||||
// Section: VNF Testing and Access
|
||||
|
@ -474,7 +472,8 @@ function vnf_triangulate(vnf) =
|
|||
let(
|
||||
vnf = is_vnf_list(vnf)? vnf_merge(vnf) : vnf,
|
||||
verts = vnf[0],
|
||||
faces = [for (face=vnf[1]) each polygon_triangulate(verts, face)]
|
||||
faces = [for (face=vnf[1]) each len(face)==3 ? [face] :
|
||||
polygon_triangulate(verts, face)]
|
||||
) [verts, faces];
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue