BOSL2/edges.scad
Adrian Mariano 703c2693a4 again
2021-09-17 20:53:52 -04:00

581 lines
19 KiB
OpenSCAD

//////////////////////////////////////////////////////////////////////
// LibFile: edges.scad
// Routines to work with edge sets and edge set descriptors.
// Includes:
// include <BOSL2/std.scad>
//////////////////////////////////////////////////////////////////////
module _edges_text3d(txt,size=3) {
if (is_list(txt)) {
for (i=idx(txt)) {
down((i-len(txt)/2+0.5)*size*1.5) {
_edges_text3d(txt[i], size=size);
}
}
} else {
xrot(90) color("#000")
linear_extrude(height=0.1) {
text(text=txt, size=size, halign="center", valign="center");
}
}
}
function _edges_vec_txt(x) = is_string(x)? str("\"", x, "\"") :
assert(is_string(x) || is_vector(x,3), str(x))
let(
lst = concat(
x.z>0? ["TOP"] : x.z<0? ["BTM"] : [],
x.y>0? ["BACK"] : x.y<0? ["FWD"] : [],
x.x>0? ["RIGHT"] : x.x<0? ["LEFT"] : []
),
out = [
for (i = idx(lst))
i>0? str("+",lst[i]) : lst[i]
]
) out;
function _edges_text(edges) =
is_string(edges) ? [str("\"",edges,"\"")] :
edges==EDGES_NONE ? ["EDGES_NONE"] :
edges==EDGES_ALL ? ["EDGES_ALL"] :
_is_edge_array(edges) ? [""] :
is_vector(edges,3) ? _edges_vec_txt(edges) :
is_list(edges) ? let(
lst = [for (x=edges) each _edges_text(x)],
out = [
for (i=idx(lst))
str(
(i==0? "[" : ""),
lst[i],
(i<len(lst)-1? "," : ""),
(i==len(lst)-1? "]" : "")
)
]
) out :
[""];
// Section: Edge Constants
// Constant: EDGES_NONE
// Topics: Edges
// See Also: EDGES_ALL, edges()
// Description:
// The set of no edges.
// Figure(3D):
// show_edges(edges="NONE");
EDGES_NONE = [[0,0,0,0], [0,0,0,0], [0,0,0,0]];
// Constant: EDGES_ALL
// Topics: Edges
// See Also: EDGES_NONE, edges()
// Description:
// The set of all edges.
// Figure(3D):
// show_edges(edges="ALL");
EDGES_ALL = [[1,1,1,1], [1,1,1,1], [1,1,1,1]];
// Constant: EDGES_OFFSETS
// Topics: Edges
// See Also: EDGES_NONE, EDGES_ALL, edges()
// Description:
// The vectors pointing to the center of each edge of a unit sized cube.
// Each item in an edge array will have a corresponding vector in this array.
EDGE_OFFSETS = [
[
[ 0,-1,-1],
[ 0, 1,-1],
[ 0,-1, 1],
[ 0, 1, 1]
], [
[-1, 0,-1],
[ 1, 0,-1],
[-1, 0, 1],
[ 1, 0, 1]
], [
[-1,-1, 0],
[ 1,-1, 0],
[-1, 1, 0],
[ 1, 1, 0]
]
];
// Section: Edge Helpers
/// Internal Function: _is_edge_array()
/// Topics: Edges, Type Checking
/// Usage:
/// bool = _is_edge_array(x);
/// Description:
/// Returns true if the given value has the form of an edge array.
/// Arguments:
/// x = The item to check the type of.
/// See Also: edges(), EDGES_NONE, EDGES_ALL
function _is_edge_array(x) = is_list(x) && is_vector(x[0]) && len(x)==3 && len(x[0])==4;
function _edge_set(v) =
_is_edge_array(v)? v : [
for (ax=[0:2]) [
for (b=[-1,1], a=[-1,1]) let(
v2=[[0,a,b],[a,0,b],[a,b,0]][ax]
) (
is_string(v)? (
v=="X"? (ax==0) : // Return all X axis aligned edges.
v=="Y"? (ax==1) : // Return all Y axis aligned edges.
v=="Z"? (ax==2) : // Return all Z axis aligned edges.
v=="ALL"? true : // Return all edges.
v=="NONE"? false : // Return no edges.
let(valid_values = ["X", "Y", "Z", "ALL", "NONE"])
assert(
in_list(v, valid_values),
str(v, " must be a vector, edge array, or one of ", valid_values)
) v
) :
let(nonz = sum(v_abs(v)))
nonz==2? (v==v2) : // Edge: return matching edge.
let(
matches = count_true([
for (i=[0:2]) v[i] && (v[i]==v2[i])
])
)
nonz==1? (matches==1) : // Face: return surrounding edges.
(matches==2) // Corner: return touching edges.
)? 1 : 0
]
];
/// Internal Function: _normalize_edges()
/// Topics: Edges
/// Usage:
/// edges = _normalize_edges(v);
/// Description:
/// Normalizes all values in an edge array to be `1`, if it was originally greater than `0`,
/// or `0`, if it was originally less than or equal to `0`.
/// See Also: edges(), EDGES_NONE, EDGES_ALL
function _normalize_edges(v) = [for (ax=v) [for (edge=ax) edge>0? 1 : 0]];
// Function: edges()
// Topics: Edges
// Usage:
// edgs = edges(v);
// edgs = edges(v, except);
//
// Description:
// Takes a list of edge set descriptors, and returns a normalized edges array
// that represents all those given edges. If the `except` argument is given
// a list of edge set descriptors, then all those edges will be removed
// from the returned edges array. If either argument only has a single edge
// set descriptor, you do not have to pass it in a list.
// Each edge set descriptor can be any of:
// - A vector pointing towards an edge.
// - A vector pointing towards a face, indicating all edges surrounding that face.
// - A vector pointing towards a corner, indicating all edges touching that corner.
// - The string `"X"`, indicating all X axis aligned edges.
// - The string `"Y"`, indicating all Y axis aligned edges.
// - The string `"Z"`, indicating all Z axis aligned edges.
// - The string `"ALL"`, indicating all edges.
// - The string `"NONE"`, indicating no edges at all.
// - A raw edges array, where each edge is represented by a 1 or a 0. The edge ordering is:
// ```
// [
// [Y-Z-, Y+Z-, Y-Z+, Y+Z+],
// [X-Z-, X+Z-, X-Z+, X+Z+],
// [X-Y-, X+Y-, X-Y+, X+Y+]
// ]
// ```
// Figure(3D,Big): Edge Vectors
// ydistribute(50) {
// xdistribute(30) {
// show_edges(edges=BOT+RIGHT);
// show_edges(edges=BOT+BACK);
// show_edges(edges=BOT+LEFT);
// show_edges(edges=BOT+FRONT);
// }
// xdistribute(30) {
// show_edges(edges=FWD+RIGHT);
// show_edges(edges=BACK+RIGHT);
// show_edges(edges=BACK+LEFT);
// show_edges(edges=FWD+LEFT);
// }
// xdistribute(30) {
// show_edges(edges=TOP+RIGHT);
// show_edges(edges=TOP+BACK);
// show_edges(edges=TOP+LEFT);
// show_edges(edges=TOP+FRONT);
// }
// }
// Figure(3D,Big): Corner Vector Edge Sets
// ydistribute(50) {
// xdistribute(30) {
// show_edges(edges=FRONT+LEFT+TOP);
// show_edges(edges=FRONT+RIGHT+TOP);
// show_edges(edges=FRONT+LEFT+BOT);
// show_edges(edges=FRONT+RIGHT+BOT);
// }
// xdistribute(30) {
// show_edges(edges=TOP+LEFT+BACK);
// show_edges(edges=TOP+RIGHT+BACK);
// show_edges(edges=BOT+LEFT+BACK);
// show_edges(edges=BOT+RIGHT+BACK);
// }
// }
// Figure(3D,Med): Face Vector Edge Sets
// ydistribute(50) {
// xdistribute(30) {
// show_edges(edges=LEFT);
// show_edges(edges=FRONT);
// show_edges(edges=RIGHT);
// }
// xdistribute(30) {
// show_edges(edges=TOP);
// show_edges(edges=BACK);
// show_edges(edges=BOTTOM);
// }
// }
// Figure(3D,Med): Named Edge Sets
// ydistribute(50) {
// xdistribute(30) {
// show_edges(edges="X");
// show_edges(edges="Y");
// show_edges(edges="Z");
// }
// xdistribute(30) {
// show_edges(edges="ALL");
// show_edges(edges="NONE");
// }
// }
//
// Arguments:
// v = The edge set to include.
// except = The edge set to specifically exclude, even if they are in `v`.
//
// See Also: EDGES_NONE, EDGES_ALL
//
// Example(3D): Just the front-top edge
// edg = edges(FRONT+TOP);
// show_edges(edges=edg);
// Example(3D): All edges surrounding either the front or top faces
// edg = edges([FRONT,TOP]);
// show_edges(edges=edg);
// Example(3D): All edges around the bottom face, except any that are also on the front
// edg = edges(BTM, except=FRONT);
// show_edges(edges=edg);
// Example(3D): All edges except those around the bottom face.
// edg = edges("ALL", except=BOTTOM);
// show_edges(edges=edg);
// Example(3D): All Z-aligned edges except those around the back face.
// edg = edges("Z", except=BACK);
// show_edges(edges=edg);
// Example(3D): All edges around the bottom or front faces, except the bottom-front edge.
// edg = edges([BOTTOM,FRONT], except=BOTTOM+FRONT);
// show_edges(edges=edg);
// Example(3D): All edges, except Z-aligned edges on the front.
// edg = edges("ALL", except=edges("Z", except=BACK));
// show_edges(edges=edg);
function edges(v, except=[]) =
(is_string(v) || is_vector(v) || _is_edge_array(v))? edges([v], except=except) :
(is_string(except) || is_vector(except) || _is_edge_array(except))? edges(v, except=[except]) :
except==[]? _normalize_edges(sum([for (x=v) _edge_set(x)])) :
_normalize_edges(
_normalize_edges(sum([for (x=v) _edge_set(x)])) -
sum([for (x=except) _edge_set(x)])
);
// Module: show_edges()
// Topics: Edges, Debugging
// Usage:
// show_edges(edges, [size=], [text=], [txtsize=]);
// Description:
// Draws a semi-transparent cube with the given edges highlighted in red.
// Arguments:
// edges = The edges to highlight.
// size = The scalar size of the cube.
// text = The text to show on the front of the cube.
// txtsize = The size of the text.
// See Also: edges(), EDGES_NONE, EDGES_ALL
// Example:
// show_edges(size=30, edges=["X","Y"]);
module show_edges(edges="ALL", size=20, text, txtsize=3) {
edge_set = edges(edges);
text = !is_undef(text) ? text : _edges_text(edges);
color("red") {
for (axis=[0:2], i=[0:3]) {
if (edge_set[axis][i] > 0) {
translate(EDGE_OFFSETS[axis][i]*size/2) {
if (axis==0) xcyl(h=size, d=2);
if (axis==1) ycyl(h=size, d=2);
if (axis==2) zcyl(h=size, d=2);
}
}
}
}
fwd(size/2) _edges_text3d(text, size=txtsize);
color("yellow",0.7) cuboid(size=size);
}
// Section: Corner Constants
// Constants for working with corners.
// Constant: CORNERS_NONE
// Topics: Corners
// Description:
// The set of no corners.
// Figure(3D):
// show_corners(corners="NONE");
// See Also: CORNERS_ALL, corners()
CORNERS_NONE = [0,0,0,0,0,0,0,0]; // No corners.
// Constant: CORNERS_ALL
// Topics: Corners
// Description:
// The set of all corners.
// Figure(3D):
// show_corners(corners="ALL");
// See Also: CORNERS_NONE, corners()
CORNERS_ALL = [1,1,1,1,1,1,1,1];
// Constant: CORNER_OFFSETS
// Topics: Corners
// Description:
// The vectors pointing to each corner of a unit sized cube.
// Each item in a corner array will have a corresponding vector in this array.
// See Also: CORNERS_NONE, CORNERS_ALL, corners()
CORNER_OFFSETS = [
[-1,-1,-1], [ 1,-1,-1], [-1, 1,-1], [ 1, 1,-1],
[-1,-1, 1], [ 1,-1, 1], [-1, 1, 1], [ 1, 1, 1]
];
// Section: Corner Helpers
/// Internal Function: _is_corner_array()
/// Topics: Corners, Type Checking
/// Usage:
/// bool = _is_corner_array(x)
/// Description:
/// Returns true if the given value has the form of a corner array.
/// See Also: CORNERS_NONE, CORNERS_ALL, corners()
function _is_corner_array(x) = is_vector(x) && len(x)==8 && all([for (xx=x) xx==1||xx==0]);
/// Internal Function: _normalize_corners()
/// Topics: Corners
/// Usage:
/// corns = _normalize_corners(v);
/// Description:
/// Normalizes all values in a corner array to be `1`, if it was originally greater than `0`,
/// or `0`, if it was originally less than or equal to `0`.
/// See Also: CORNERS_NONE, CORNERS_ALL, corners()
function _normalize_corners(v) = [for (x=v) x>0? 1 : 0];
function _corner_set(v) =
_is_corner_array(v)? v : [
for (i=[0:7]) let(
v2 = CORNER_OFFSETS[i]
) (
is_string(v)? (
v=="ALL"? true : // Return all corners.
v=="NONE"? false : // Return no corners.
let(valid_values = ["ALL", "NONE"])
assert(
in_list(v, valid_values),
str(v, " must be a vector, corner array, or one of ", valid_values)
) v
) :
all([for (i=[0:2]) !v[i] || (v[i]==v2[i])])
)? 1 : 0
];
// Function: corners()
// Topics: Corners
// Usage:
// corns = corners(v);
// corns = corners(v, except);
// Description:
// Takes a list of corner set descriptors, and returns a normalized corners array
// that represents all those given corners. If the `except` argument is given
// a list of corner set descriptors, then all those corners will be removed
// from the returned corners array. If either argument only has a single corner
// set descriptor, you do not have to pass it in a list.
// Each corner set descriptor can be any of:
// - A vector pointing towards an edge indicating both corners at the ends of that edge.
// - A vector pointing towards a face, indicating all the corners of that face.
// - A vector pointing towards a corner, indicating just that corner.
// - The string `"ALL"`, indicating all corners.
// - The string `"NONE"`, indicating no corners at all.
// - A raw corners array, where each corner is represented by a 1 or a 0. The corner ordering is:
// ```
// [X-Y-Z-, X+Y-Z-, X-Y+Z-, X+Y+Z-, X-Y-Z+, X+Y-Z+, X-Y+Z+, X+Y+Z+]
// ```
// Figure(3D,Big): Corners by Corner Vector
// ydistribute(55) {
// xdistribute(35) {
// show_corners(corners=FRONT+LEFT+TOP);
// show_corners(corners=FRONT+RIGHT+TOP);
// show_corners(corners=FRONT+LEFT+BOT);
// show_corners(corners=FRONT+RIGHT+BOT);
// }
// xdistribute(35) {
// show_corners(corners=TOP+LEFT+BACK);
// show_corners(corners=TOP+RIGHT+BACK);
// show_corners(corners=BOT+LEFT+BACK);
// show_corners(corners=BOT+RIGHT+BACK);
// }
// }
// Figure(3D,Big): Corners by Edge Vectors
// ydistribute(55) {
// xdistribute(35) {
// show_corners(corners=BOT+RIGHT);
// show_corners(corners=BOT+BACK);
// show_corners(corners=BOT+LEFT);
// show_corners(corners=BOT+FRONT);
// }
// xdistribute(35) {
// show_corners(corners=FWD+RIGHT);
// show_corners(corners=BACK+RIGHT);
// show_corners(corners=BACK+LEFT);
// show_corners(corners=FWD+LEFT);
// }
// xdistribute(35) {
// show_corners(corners=TOP+RIGHT);
// show_corners(corners=TOP+BACK);
// show_corners(corners=TOP+LEFT);
// show_corners(corners=TOP+FRONT);
// }
// }
// Figure(3D,Med): Corners by Face Vectors
// ydistribute(55) {
// xdistribute(35) {
// show_corners(corners=LEFT);
// show_corners(corners=FRONT);
// show_corners(corners=RIGHT);
// }
// xdistribute(35) {
// show_corners(corners=TOP);
// show_corners(corners=BACK);
// show_corners(corners=BOTTOM);
// }
// }
// Figure(3D,Med): Corners by Name
// xdistribute(35) {
// show_corners(corners="ALL");
// show_corners(corners="NONE");
// }
// See Also: CORNERS_NONE, CORNERS_ALL
// Example(3D): Just the front-top-right corner
// crn = corners(FRONT+TOP+RIGHT);
// show_corners(corners=crn);
// Example(3D): All corners surrounding either the front or top faces
// crn = corners([FRONT,TOP]);
// show_corners(corners=crn);
// Example(3D): All corners around the bottom face, except any that are also on the front
// crn = corners(BTM, except=FRONT);
// show_corners(corners=crn);
// Example(3D): All corners except those around the bottom face.
// crn = corners("ALL", except=BOTTOM);
// show_corners(corners=crn);
// Example(3D): All corners around the bottom or front faces, except those on the bottom-front edge.
// crn = corners([BOTTOM,FRONT], except=BOTTOM+FRONT);
// show_corners(corners=crn);
function corners(v, except=[]) =
(is_string(v) || is_vector(v) || _is_corner_array(v))? corners([v], except=except) :
(is_string(except) || is_vector(except) || _is_corner_array(except))? corners(v, except=[except]) :
except==[]? _normalize_corners(sum([for (x=v) _corner_set(x)])) :
let(
a = _normalize_corners(sum([for (x=v) _corner_set(x)])),
b = _normalize_corners(sum([for (x=except) _corner_set(x)]))
) _normalize_corners(a - b);
/// Internal Function: _corner_edges()
/// Topics: Corners
/// Description:
/// Returns [XCOUNT,YCOUNT,ZCOUNT] where each is the count of edges aligned with that
/// axis that are in the edge set and touch the given corner.
/// Arguments:
/// edges = Standard edges array.
/// v = Vector pointing to the corner to count edge intersections at.
/// See Also: CORNERS_NONE, CORNERS_ALL, corners()
function _corner_edges(edges, v) =
let(u = (v+[1,1,1])/2) [edges[0][u.y+u.z*2], edges[1][u.x+u.z*2], edges[2][u.x+u.y*2]];
/// InternalFunction: _corner_edge_count()
/// Topics: Corners
/// Description:
/// Counts how many given edges intersect at a specific corner.
/// Arguments:
/// edges = Standard edges array.
/// v = Vector pointing to the corner to count edge intersections at.
/// See Also: CORNERS_NONE, CORNERS_ALL, corners()
function _corner_edge_count(edges, v) =
let(u = (v+[1,1,1])/2) edges[0][u.y+u.z*2] + edges[1][u.x+u.z*2] + edges[2][u.x+u.y*2];
function _corners_text(corners) =
is_string(corners) ? [str("\"",corners,"\"")] :
corners==CORNERS_NONE ? ["CORNERS_NONE"] :
corners==CORNERS_ALL ? ["CORNERS_ALL"] :
_is_corner_array(corners) ? [""] :
is_vector(corners,3) ? _edges_vec_txt(corners) :
is_list(corners) ? let(
lst = [for (x=corners) each _corners_text(x)],
out = [
for (i=idx(lst))
str(
(i==0? "[" : ""),
lst[i],
(i<len(lst)-1? "," : ""),
(i==len(lst)-1? "]" : "")
)
]
) out :
[""];
// Module: show_corners()
// Topics: Corners, Debugging
// Usage:
// show_corners(corners, [size=], [text=], [txtsize=]);
// Description:
// Draws a semi-transparent cube with the given corners highlighted in red.
// Arguments:
// corners = The corners to highlight.
// size = The scalar size of the cube.
// text = If given, overrides the text to be shown on the front of the cube.
// txtsize = The size of the text.
// See Also: CORNERS_NONE, CORNERS_ALL, corners()
// Example:
// show_corners(corners=FWD+RIGHT, size=30);
module show_corners(corners="ALL", size=20, text, txtsize=3) {
corner_set = corners(corners);
text = !is_undef(text) ? text : _corners_text(corners);
for (i=[0:7]) if (corner_set[i]>0)
translate(CORNER_OFFSETS[i]*size/2)
color("red") sphere(d=2, $fn=16);
fwd(size/2) _edges_text3d(text, size=txtsize);
color("yellow",0.7) cuboid(size=size);
}
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap