mirror of
https://github.com/revarbat/BOSL2.git
synced 2025-08-17 20:01:34 +02:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
@@ -43,6 +43,9 @@ $ghost_this=false;
|
||||
$ghost=false;
|
||||
$ghosting=false; // Ghosting is in effect, so don't apply it again
|
||||
|
||||
$highlight_this=false;
|
||||
$highlight=false;
|
||||
|
||||
_ANCHOR_TYPES = ["intersect","hull"];
|
||||
|
||||
|
||||
@@ -1154,8 +1157,8 @@ module tag_this(tag)
|
||||
// that don't use {{attachable()}} or built in modules such as
|
||||
// - `polygon()`
|
||||
// - `projection()`
|
||||
// - `polyhedron()` (or use [`vnf_polyhedron()`](vnf.scad#vnf_polyhedron))
|
||||
// - `linear_extrude()` (or use [`linear_sweep()`](regions.scad#linear_sweep))
|
||||
// - `polyhedron()` (or use {{vnf_polyhedron()}})
|
||||
// - `linear_extrude()` (or use {{linear_sweep()}})
|
||||
// - `rotate_extrude()`
|
||||
// - `surface()`
|
||||
// - `import()`
|
||||
@@ -3107,24 +3110,15 @@ module attachable(
|
||||
if (expose_tags || _is_shown()){
|
||||
if (!keep_color)
|
||||
_color($color)
|
||||
if (($ghost || $ghost_this) && !$ghosting)
|
||||
%union(){
|
||||
$ghosting=true;
|
||||
children(0);
|
||||
}
|
||||
else children(0);
|
||||
_show_ghost() children(0);
|
||||
else {
|
||||
$save_color=undef; // Force color_this() color in effect to persist for the entire object
|
||||
if (($ghost || $ghost_this) && !$ghosting)
|
||||
%union(){
|
||||
$ghosting=true;
|
||||
children(0);
|
||||
}
|
||||
else children(0);
|
||||
_show_ghost() children(0);
|
||||
}
|
||||
}
|
||||
let(
|
||||
$ghost_this=false,
|
||||
$highlight_this=false,
|
||||
$tag=default($save_tag,$tag),
|
||||
$save_tag=undef,
|
||||
$color=default($save_color,$color),
|
||||
@@ -3134,6 +3128,26 @@ module attachable(
|
||||
}
|
||||
}
|
||||
|
||||
module _show_highlight()
|
||||
{
|
||||
if ($highlight || $highlight_this)
|
||||
#children();
|
||||
else
|
||||
children();
|
||||
}
|
||||
|
||||
|
||||
module _show_ghost()
|
||||
{
|
||||
if (($ghost || $ghost_this) && !$ghosting)
|
||||
%union(){
|
||||
$ghosting=true;
|
||||
_show_highlight()children();
|
||||
}
|
||||
else _show_highlight()children();
|
||||
}
|
||||
|
||||
|
||||
// Function: reorient()
|
||||
// Synopsis: Calculates the transformation matrix needed to reorient an object.
|
||||
// SynTags: Trans, Path, VNF
|
||||
|
@@ -24,10 +24,11 @@
|
||||
// id = Inner diameter of ball bearing assembly.
|
||||
// od = Outer diameter of ball bearing assembly.
|
||||
// width = Width of ball bearing assembly.
|
||||
// shield = Does the ball bearing assembly have a shield.
|
||||
// flange = Does the ball bearing assembly have a flange.
|
||||
// fd = Diameter of the flange (only used if `flange=true`).
|
||||
// fw = Width of the flange (only used if `flange=true`).
|
||||
// shield = If true, the ball bearing assembly has a shield.
|
||||
// flange = If true, the ball bearing assembly has a flange.
|
||||
// fd = Diameter of the flange (required if `flange=true`).
|
||||
// fw = Width of the flange (required if `flange=true`).
|
||||
// rounding = Edge rounding radius, if any. The outermost top and bottom edges are rounded by this amount. The edges of the inner hole are also rounded. If you set `trade_size` and you want edges rounded, you must set `rounding` yourself. This parameter has no default value because the rounding depends on manufacturer and bearing size.
|
||||
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
|
||||
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
|
||||
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
|
||||
@@ -43,9 +44,9 @@
|
||||
// ball_bearing("MF105ZZ", $fn=72);
|
||||
// Example:
|
||||
// ball_bearing("F688ZZ", $fn=72);
|
||||
// Example:
|
||||
// ball_bearing(id=12,od=24,width=6,shield=true, flange=true, fd=26.5, fw=1.5, $fn=72);
|
||||
module ball_bearing(trade_size, id, od, width, shield=true, flange=false, fd, fw, anchor=CTR, spin=0, orient=UP) {
|
||||
// Example: With flange, shield, and rounded edges.
|
||||
// ball_bearing(id=12,od=24,width=6,shield=true, flange=true, fd=26.5, fw=1.5, rounding=0.6, $fn=72);
|
||||
module ball_bearing(trade_size, id, od, width, shield=true, flange=false, fd, fw, rounding, anchor=CTR, spin=0, orient=UP) {
|
||||
info = is_undef(trade_size)? [id, od, width, shield, flange, fd, fw] :
|
||||
ball_bearing_info(trade_size);
|
||||
check = assert(all_defined(select(info, 0,4)), "Bad Input");
|
||||
@@ -65,18 +66,18 @@ module ball_bearing(trade_size, id, od, width, shield=true, flange=false, fd, fw
|
||||
color("silver")
|
||||
attachable(anchor,spin,orient, d=od, l=width) {
|
||||
if (shield) {
|
||||
tube(id=id, wall=wall, h=width);
|
||||
tube(od=od, wall=wall, h=width);
|
||||
tube(id=id, wall=wall, h=width, irounding=rounding);
|
||||
tube(od=od, wall=wall, h=width, orounding1=flange?undef:rounding, orounding2=rounding);
|
||||
tube(id=id+0.1, od=od-0.1, h=(wall*2+width)/2);
|
||||
if (flange){
|
||||
translate([0,0,-width/2+fw/2])tube(id=od, od=fd, h=fw);
|
||||
translate([0,0,-width/2+fw/2])tube(id=od, od=fd, h=fw, orounding1=rounding);
|
||||
}
|
||||
} else {
|
||||
ball_cnt = floor(PI*mid_d*0.95 / (wall*2));
|
||||
difference() {
|
||||
union() {
|
||||
tube(id=id, wall=wall, h=width);
|
||||
tube(od=od, wall=wall, h=width);
|
||||
tube(id=id, wall=wall, h=width, irounding=rounding);
|
||||
tube(od=od, wall=wall, h=width, orounding1=flange?undef:rounding, orounding2=rounding);
|
||||
}
|
||||
torus(r_maj=mid_d/2, r_min=wall);
|
||||
}
|
||||
@@ -84,11 +85,9 @@ module ball_bearing(trade_size, id, od, width, shield=true, flange=false, fd, fw
|
||||
zrot(i*360/ball_cnt) right(mid_d/2) sphere(d=wall*2);
|
||||
}
|
||||
if (flange){
|
||||
translate([0,0,-width/2+fw/2])tube(id=od, od=fd, h=fw);
|
||||
translate([0,0,-width/2+fw/2])tube(id=od, od=fd, h=fw, orounding1=rounding);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
children();
|
||||
}
|
||||
}
|
||||
|
73
color.scad
73
color.scad
@@ -161,21 +161,74 @@ module color_overlaps(color="red") {
|
||||
%children();
|
||||
}
|
||||
|
||||
// Section: Setting Object Transparency
|
||||
// Section: Setting Object Modifiers
|
||||
|
||||
|
||||
|
||||
|
||||
// Module: highlight()
|
||||
// Synopsis: Sets # modifier for attachable children and their descendents.
|
||||
// SynTags: Trans
|
||||
// Topics: Attachments, Modifiers
|
||||
// See Also: highlight_this(), ghost(), ghost_this(), recolor(), color_this()
|
||||
// Usage:
|
||||
// highlight([highlight]) CHILDREN;
|
||||
// Description:
|
||||
// Sets the `#` modifier for the attachable children and their descendents until another {{highlight()}} or {{highlight_this()}}.
|
||||
// By default, turns `#` on, which makes the children transparent pink and displays them even if they are subtracted from the model.
|
||||
// Give the `false` parameter to disable the modifier and restore children to normal.
|
||||
// Do not mix this with user supplied `#` modifiers anywhere in the geometry tree.
|
||||
// Arguments:
|
||||
// highlight = If true set the descendents to use `#`; if false, disable `#` for descendents. Default: true
|
||||
// Example(3D):
|
||||
// highlight() cuboid(10)
|
||||
// highlight(false) attach(RIGHT,BOT)cuboid(5);
|
||||
function highlight(highlight) = no_function("highlight");
|
||||
module highlight(highlight=true)
|
||||
{
|
||||
$highlight=highlight;
|
||||
children();
|
||||
}
|
||||
|
||||
|
||||
// Module: highlight_this()
|
||||
// Synopsis: Apply # modifier to children at a single level.
|
||||
// SynTags: Trans
|
||||
// Topics: Attachments, Modifiers
|
||||
// See Also: highlight(), ghost(), ghost_this(), recolor(), color_this()
|
||||
// Usage:
|
||||
// highlight_this() CHILDREN;
|
||||
// Description:
|
||||
// Applies the `#` modifier to the children at a single level, reverting to the previous highlight state for further descendents.
|
||||
// This works only with attachables and you cannot give the `#` operator anywhere in the geometry tree.
|
||||
// Example(3D):
|
||||
// highlight_this()
|
||||
// cuboid(10)
|
||||
// attach(TOP,BOT)cuboid(5);
|
||||
function highlight_this() = no_function("highlight_this");
|
||||
module highlight_this()
|
||||
{
|
||||
$highlight_this=true;
|
||||
children();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// Module: ghost()
|
||||
// Synopsis: Sets transparency for attachable children and their descendents.
|
||||
// Synopsis: Sets % modifier for attachable children and their descendents.
|
||||
// SynTags: Trans
|
||||
// Topics: Attachments
|
||||
// Topics: Attachments, Modifiers
|
||||
// See Also: ghost_this(), recolor(), color_this()
|
||||
// Usage:
|
||||
// ghost([ghost]) CHILDREN;
|
||||
// Description:
|
||||
// Sets the transparency for the attachable children and their descendents until another {{ghost()}} or {{ghost_this()}}.
|
||||
// By default, turns transparency on. Give the `false` parameter to disable transparency.
|
||||
// Do not mix this with user supplied `%` operators anywhere in the geometry tree.
|
||||
// Sets the `%` modifier for the attachable children and their descendents until another {{ghost()}} or {{ghost_this()}}.
|
||||
// By default, turns `%` on, which makes the children gray and also removes them from interaction with the model.
|
||||
// Give the `false` parameter to disable the modifier and restore children to normal.
|
||||
// Do not mix this with user supplied `%` modifiers anywhere in the geometry tree.
|
||||
// Arguments:
|
||||
// ghost = If true set the descendents to be transparent; if false, disable transparency. Default: true
|
||||
// ghost = If true set the descendents to use `%`; if false, disable `%` for descendents. Default: true
|
||||
// Example(3D):
|
||||
// ghost() cuboid(10)
|
||||
// ghost(false) cuboid(5);
|
||||
@@ -188,14 +241,14 @@ module ghost(ghost=true)
|
||||
|
||||
|
||||
// Module: ghost_this()
|
||||
// Synopsis: Makes the children at a single level transparent.
|
||||
// Synopsis: Apply % modifier to children at a single level.
|
||||
// SynTags: Trans
|
||||
// Topics: Attachments
|
||||
// Topics: Attachments, Modifiers
|
||||
// See Also: ghost(), recolor(), color_this()
|
||||
// Usage:
|
||||
// ghost_this() CHILDREN;
|
||||
// Description:
|
||||
// Makes the children transparent for one level, reverting to the previous transparency state for further descendents.
|
||||
// Applies the `%` modifier to the children at a single level, reverting to the previous ghost state for further descendents.
|
||||
// This works only with attachables and you cannot give the `%` operator anywhere in the geometry tree.
|
||||
// Example(3D):
|
||||
// ghost_this() cuboid(10)
|
||||
|
@@ -1609,6 +1609,14 @@ function real_roots(p,eps=undef,tol=1e-14) =
|
||||
// x0 = endpoint of interval to search for root
|
||||
// x1 = second endpoint of interval to search for root
|
||||
// tol = tolerance for solution. Default: 1e-15
|
||||
// Example(2D): Solve x*sin(x)=4
|
||||
// f = function (x) x*sin(x)-4;
|
||||
// root=root_find(f, 0,25); // root = 15.2284
|
||||
// // Graphical verification
|
||||
// stroke([for(x=[0:25]) [x,f(x)]],width=.2);
|
||||
// color("red")move([root,f(root)])
|
||||
// circle(r=.25,$fn=16);
|
||||
|
||||
|
||||
// The algorithm is based on Brent's method and is a combination of
|
||||
// bisection and inverse quadratic approximation, where bisection occurs
|
||||
|
132
shapes2d.scad
132
shapes2d.scad
@@ -1751,6 +1751,136 @@ module glued_circles(r, spread=10, tangent=30, d, anchor=CENTER, spin=0) {
|
||||
}
|
||||
|
||||
|
||||
// Function&Module: squircle()
|
||||
// Synopsis: Creates a shape between a circle and a square, centered on the origin.
|
||||
// SynTags: Geom, Path
|
||||
// Topics: Shapes (2D), Paths (2D), Path Generators, Attachable
|
||||
// See Also: circle(), square(), supershape()
|
||||
// Usage: As Module
|
||||
// squircle(size, [squareness], [style]) [ATTACHMENTS];
|
||||
// Usage: As Function
|
||||
// path = squircle(size, [squareness], [style]);
|
||||
// Description:
|
||||
// A [squircle](https://en.wikipedia.org/wiki/Squircle) is a shape intermediate between a square/rectangle and a circle/ellipse. Squircles are sometimes used to make dinner plates (more area for the same radius as a circle), keyboard buttons, and smartphone icons. Old CRT television screens also resembled elongated squircles.
|
||||
// .
|
||||
// Multiple definitions exist for the squircle. We support two versions: the superellipse {{supershape()}} Example 3, also known as the Lamé upper squircle), and the Fernandez-Guasti squircle. They are visually almost indistinguishable, with the superellipse having slightly rounder "corners" than FG at the same corner radius. These two squircles have different, unintuitive methods for controlling how square or circular the shape is. A `squareness` parameter determines the shape, specifying the corner position linearly, with 0 being on the circle and 1 being the square. Vertices are positioned to be more dense near the corners to preserve smoothness at low values of `$fn`'.
|
||||
// .
|
||||
// For the "superellipse" style, the special case where the superellipse exponent is 4 results in a squircle at the geometric mean between radial points on the circle and square, corresponding to squareness=0.456786.
|
||||
// .
|
||||
// When called as a module, creates a 2D squircle with the desired squareness.
|
||||
// When called as a function, returns a 2D path for a squircle.
|
||||
// Arguments:
|
||||
// size = Same as the `size` parameter in `square()`, can be a single number or a vector `[xsize,ysize]`.
|
||||
// squareness = Value between 0 and 1. Controls the shape, setting the location of a squircle "corner" at the specified interpolated position between a circle and a square. When `squareness=0` the shape is a circle, and when `squareness=1` the shape is a square. Default: 0.5
|
||||
// ---
|
||||
// style = method for generating a squircle, "fg" for Fernández-Guasti and "superellipse" for superellipse. Default: "fg"
|
||||
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
|
||||
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
|
||||
// atype = anchor type, "box" for bounding box corners and sides, "perim" for the squircle corners. Default: "box"
|
||||
// $fn = Number of points. The special variables `$fs` and `$fa` are ignored. If set, `$fn` must be 12 or greater, and is rounded to the nearest multiple of 4. Points are generated so they are more dense around sharper curves. Default if not set: 48
|
||||
// Examples(2D):
|
||||
// squircle(size=50, squareness=0.4);
|
||||
// squircle([80,60], 0.7, $fn=64);
|
||||
// Example(2D,VPD=265,NoAxes): Ten increments of squareness parameter for a superellipse squircle
|
||||
// color("green") for(sq=[0:0.1:1])
|
||||
// stroke(squircle(100, sq, style="superellipse", $fn=96), closed=true, width=0.5);
|
||||
// Example(2D): Standard vector anchors are based on the bounding box
|
||||
// squircle(50, 0.6) show_anchors();
|
||||
// Example(2D): Perimeter anchors, anchoring at bottom left and spinning 20°
|
||||
// squircle([60,40], 0.5, anchor=(BOTTOM+LEFT), atype="perim", spin=20)
|
||||
// show_anchors();
|
||||
|
||||
module squircle(size, squareness=0.5, style="fg", anchor=CENTER, spin=0, atype="box" ) {
|
||||
check = assert(squareness >= 0 && squareness <= 1);
|
||||
anchorchk = assert(in_list(atype, ["box", "perim"]));
|
||||
size = is_num(size) ? [size,size] : point2d(size);
|
||||
assert(all_positive(size), "All components of size must be positive.");
|
||||
path = squircle(size, squareness, style, atype="box");
|
||||
if (atype == "box") {
|
||||
attachable(anchor, spin, two_d=true, size=size, extent=false) {
|
||||
polygon(path);
|
||||
children();
|
||||
}
|
||||
} else { // atype=="perim"
|
||||
attachable(anchor, spin, two_d=true, extent=true, path=path) {
|
||||
polygon(path);
|
||||
children();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function squircle(size, squareness=0.5, style="fg", anchor=CENTER, spin=0, atype="box") =
|
||||
assert(squareness >= 0 && squareness <= 1)
|
||||
assert(is_num(size) || is_vector(size,2))
|
||||
assert(in_list(atype, ["box", "perim"]))
|
||||
let(
|
||||
size = is_num(size) ? [size,size] : point2d(size),
|
||||
path = style == "fg" ? _squircle_fg(size, squareness)
|
||||
: style == "superellipse" ? _squircle_se(size, squareness)
|
||||
: assert(false, "Style must be \"fg\" or \"superellipse\"")
|
||||
) reorient(anchor, spin, two_d=true, size=atype=="box"?size:undef, path=atype=="box"?undef:path, p=path, extent=true);
|
||||
|
||||
|
||||
/* FG squircle functions */
|
||||
|
||||
function _squircle_fg(size, squareness) = [
|
||||
let(
|
||||
sq = _linearize_squareness(squareness),
|
||||
size = is_num(size) ? [size,size] : point2d(size),
|
||||
aspect = size[1] / size[0],
|
||||
r = 0.5 * size[0],
|
||||
astep = $fn>=12 ? 90/round($fn/4) : 360/48
|
||||
) for(a=[360:-astep:0.01]) let(
|
||||
theta = a + sq * sin(4*a) * 30/PI, // tighter angle steps at corners
|
||||
p = squircle_radius_fg(sq, r, theta)
|
||||
) p*[cos(theta), aspect*sin(theta)]
|
||||
];
|
||||
|
||||
function squircle_radius_fg(squareness, r, angle) = let(
|
||||
s2a = abs(squareness*sin(2*angle))
|
||||
) s2a>0 ? r*sqrt(2)/s2a * sqrt(1 - sqrt(1 - s2a*s2a)) : r;
|
||||
|
||||
function _linearize_squareness(s) =
|
||||
// from Chamberlain Fong (2016). "Squircular Calculations". arXiv.
|
||||
// https://arxiv.org/pdf/1604.02174v5
|
||||
let(c = 2 - 2*sqrt(2), d = 1 - 0.5*c*s)
|
||||
2 * sqrt((1+c)*s*s - c*s) / (d*d);
|
||||
|
||||
|
||||
/* Superellipse squircle functions */
|
||||
|
||||
function _squircle_se(size, squareness) = [
|
||||
let(
|
||||
n = _squircle_se_exponent(squareness),
|
||||
size = is_num(size) ? [size,size] : point2d(size),
|
||||
ra = 0.5*size[0],
|
||||
rb = 0.5*size[1],
|
||||
astep = $fn>=12 ? 90/round($fn/4) : 360/48,
|
||||
fgsq = _linearize_squareness(min(0.998,squareness)) // works well for distributing theta
|
||||
) for(a=[360:-astep:0.01]) let(
|
||||
theta = a + fgsq*sin(4*a)*30/PI, // tighter angle steps at corners
|
||||
x = cos(theta),
|
||||
y = sin(theta),
|
||||
r = (abs(x)^n + abs(y)^n)^(1/n) // superellipse
|
||||
//r = _superformula(theta=theta, m1=4,m2=4,n1=n,n2=n,n3=n,a=1,b=1)
|
||||
) [ra*x, rb*y] / r
|
||||
];
|
||||
|
||||
function squircle_radius_se(n, r, angle) = let(
|
||||
x = cos(angle),
|
||||
y = sin(angle)
|
||||
) (abs(x)^n + abs(y)^n)^(1/n) / r;
|
||||
|
||||
function _squircle_se_exponent(squareness) = let(
|
||||
// limit squareness; error if >0.99889, limit is smaller for r>1
|
||||
s=min(0.998,squareness),
|
||||
rho = 1 + s*(sqrt(2)-1),
|
||||
x = rho / sqrt(2)
|
||||
) log(0.5) / log(x);
|
||||
|
||||
|
||||
|
||||
// Function&Module: keyhole()
|
||||
// Synopsis: Creates a 2D keyhole shape.
|
||||
// SynTags: Geom, Path
|
||||
@@ -1929,7 +2059,7 @@ function _superformula(theta,m1,m2,n1,n2=1,n3=1,a=1,b=1) =
|
||||
// Usage: As Function
|
||||
// path = reuleaux_polygon(n, r|d=, ...);
|
||||
// Description:
|
||||
// When called as a module, reates a 2D Reuleaux Polygon; a constant width shape that is not circular. Uses "intersect" type anchoring.
|
||||
// When called as a module, creates a 2D Reuleaux Polygon; a constant width shape that is not circular. Uses "intersect" type anchoring.
|
||||
// When called as a function, returns a 2D path for a Reulaux Polygon.
|
||||
// Arguments:
|
||||
// n = Number of "sides" to the Reuleaux Polygon. Must be an odd positive number. Default: 3
|
||||
|
239
vnf.scad
239
vnf.scad
@@ -50,7 +50,7 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
|
||||
// Arguments:
|
||||
// points = A list of vertices to divide into columns and rows.
|
||||
// ---
|
||||
// caps = If true, add endcap faces to the first AND last rows.
|
||||
// caps = If true, add endcap faces to the first **and** last rows.
|
||||
// cap1 = If true, add an endcap face to the first row.
|
||||
// cap2 = If true, add an endcap face to the last row.
|
||||
// col_wrap = If true, add faces to connect the last column to the first.
|
||||
@@ -69,6 +69,38 @@ EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
|
||||
// col_wrap=true, caps=true, reverse=true, style="alt"
|
||||
// );
|
||||
// vnf_polyhedron(vnf);
|
||||
// Example(3D,NoAxes,ThrownTogether,VPD=183): Open shape made from a three arcs.
|
||||
// rows = [
|
||||
// for(h=[-20:20:20])
|
||||
// path3d(arc(r=40-abs(h), angle=280, 10), h)
|
||||
// ];
|
||||
// vnf = vnf_vertex_array(rows, reverse=true);
|
||||
// vnf_polyhedron(vnf);
|
||||
// color("green") vnf_wireframe(vnf);
|
||||
// Example(3D,NoAxes,ThrownTogether,VPD=183): Open shape made from a three arcs, with `row_wrap=true`.
|
||||
// rows = [
|
||||
// for(h=[-20:20:20])
|
||||
// path3d(arc(r=40-abs(h), angle=280, 10), h)
|
||||
// ];
|
||||
// vnf = vnf_vertex_array(rows, reverse=true, row_wrap=true);
|
||||
// vnf_polyhedron(vnf);
|
||||
// color("green") vnf_wireframe(vnf);
|
||||
// Example(3D,NoAxes,ThrownTogether,VPD=183): Open shape made from a three arcs, with `col_wrap=true`.
|
||||
// rows = [
|
||||
// for(h=[-20:20:20])
|
||||
// path3d(arc(r=40-abs(h), angle=280, 10), h)
|
||||
// ];
|
||||
// vnf = vnf_vertex_array(rows, reverse=true, col_wrap=true);
|
||||
// vnf_polyhedron(vnf);
|
||||
// color("green") vnf_wireframe(vnf);
|
||||
// Example(3D,NoAxes,ThrownTogether,VPD=183): Open shape made from a three arcs, with `caps=true` and `col_wrap=true`.
|
||||
// rows = [
|
||||
// for(h=[-20:20:20])
|
||||
// path3d(arc(r=40-abs(h), angle=280, 10), h)
|
||||
// ];
|
||||
// vnf = vnf_vertex_array(rows, reverse=true, caps=true, col_wrap=true);
|
||||
// vnf_polyhedron(vnf);
|
||||
// color("green") vnf_wireframe(vnf);
|
||||
// Example(3D): Both `col_wrap` and `row_wrap` are true to make a torus.
|
||||
// vnf = vnf_vertex_array(
|
||||
// points=[
|
||||
@@ -237,21 +269,28 @@ function vnf_vertex_array(
|
||||
|
||||
|
||||
// Function: vnf_tri_array()
|
||||
// Synopsis: Returns a VNF from an array of points.
|
||||
// Synopsis: Returns a VNF from an array of points. The array need not be rectangular.
|
||||
// SynTags: VNF
|
||||
// Topics: VNF Generators, Lists
|
||||
// See Also: vnf_vertex_array(), vnf_join(), vnf_from_polygons(), vnf_from_region()
|
||||
// See Also: vnf_vertex_array(), vnf_join(), vnf_from_polygons(), vnf_merge_points()
|
||||
// Usage:
|
||||
// vnf = vnf_tri_array(points, [row_wrap], [reverse])
|
||||
// vnf = vnf_tri_array(points, [caps=], [cap1=], [cap2=], [reverse=], [col_wrap=], [row_wrap=])
|
||||
// Description:
|
||||
// Produces a VNF from an array of points where each row length can differ from the adjacent rows by up to 2 in length. This enables
|
||||
// the construction of triangular VNF patches. The resulting VNF can be wrapped along the rows by setting `row_wrap` to true.
|
||||
// You cannot wrap columns: if you need to do that you'll need to merge two VNF arrays that share edges. Degenerate faces
|
||||
// are not included in the output, but if this results in unused vertices they will still appear in the output.
|
||||
// Produces a VNF from an array of points where each row length can differ from the adjacent rows by any amount. This enables the construction of triangular or even irregular VNF patches. The resulting VNF can be wrapped along the rows by setting `row_wrap` to true, and wrapped along columns by setting `col_wrap` to true. It is possible to do both at once.
|
||||
// If `row_wrap` is false or not provided, end caps can be generated across the top and/or bottom rows.
|
||||
// .
|
||||
// The algorithm starts with the first point on each row and recursively walks around finding the minimum-length edge to make each new triangle face. This may result in several triangles being connected to one vertex. When triangulating two rows that happen to be equal length, the result is equivalent to {{vnf_vertex_array()}} using the "min_edge" style. If you already have a rectangular vertex list (equal length rows), you should use `vnf_vertex_array()` if you need a different triangulation style.
|
||||
// .
|
||||
// If you need to merge two VNF arrays that share edges using `vnf_join()` you can remove the duplicated vertices using `vnf_merge_points()`.
|
||||
// Arguments:
|
||||
// points = List of point lists for each row
|
||||
// row_wrap = If true then add faces connecting the first row and last row. These rows must differ by at most 2 in length.
|
||||
// reverse = Set this to reverse the direction of the faces
|
||||
// points = List of point lists for each row.
|
||||
// ---
|
||||
// caps = If true, add endcap faces to the first **and** last rows.
|
||||
// cap1 = If true, add an endcap face to the first row.
|
||||
// cap2 = If true, add an endcap face to the last row.
|
||||
// col_wrap = If true, add faces to connect the last column to the first.
|
||||
// row_wrap = If true, add faces to connect the last row to the first.
|
||||
// reverse = If true, reverse all face normals.
|
||||
// Example(3D,NoAxes): Each row has one more point than the preceeding one.
|
||||
// pts = [for(y=[1:1:10]) [for(x=[0:y-1]) [x,y,y]]];
|
||||
// vnf = vnf_tri_array(pts);
|
||||
@@ -276,60 +315,125 @@ function vnf_vertex_array(
|
||||
// vnf_tri_array(pts2)]);
|
||||
// color("green")vnf_wireframe(vnf,width=0.1);
|
||||
// vnf_polyhedron(vnf);
|
||||
// Example(3D,NoAxes): Point count can change irregularly
|
||||
// lens = [10,9,7,5,6,8,8,10];
|
||||
// Example(3D,NoAxes): The number of points per row can change irregularly by any amount.
|
||||
// lens = [10,9,8,6,4,8,2,5,3,10,4];
|
||||
// pts = [for(y=idx(lens)) lerpn([-lens[y],y,y],[lens[y],y,y],lens[y])];
|
||||
// vnf = vnf_tri_array(pts);
|
||||
// vnf_wireframe(vnf,width=0.1);
|
||||
// color("red")move_copies(flatten(pts)) sphere(r=.15,$fn=9);
|
||||
function vnf_tri_array(points, row_wrap=false, reverse=false) =
|
||||
// Example(3D,NoAxes,Edges,VPR=[65,0,25],VPD=380,Med): Model of a cymbal with roughly same-size facets, using a different number of points for each concentric ring of vertices.
|
||||
// include <BOSL2/beziers.scad>
|
||||
// bez = [
|
||||
// [[0,26], [35,26], [29,0], [80,16], [102,0]], //top
|
||||
// [[99,-1], [79,15], [28,-1], [34,25], [-1,25]] // bottom
|
||||
// ];
|
||||
// points = [
|
||||
// for(b=bez)
|
||||
// for(u=[0.01:0.04:1]) let(
|
||||
// bzp = bezier_points(b,u),
|
||||
// r = bzp[0],
|
||||
// n = max(3, round(360 / (6/r * 180/PI)))
|
||||
// ) path3d(regular_ngon(n, r=r), bzp[1])
|
||||
// ];
|
||||
// vnf = vnf_tri_array(points, reverse=true, col_wrap=true, caps=true);
|
||||
// color("brown") difference() {
|
||||
// vnf_polyhedron(vnf);
|
||||
// cylinder(30, d=8);
|
||||
// }
|
||||
function vnf_tri_array(
|
||||
points,
|
||||
caps, cap1, cap2,
|
||||
col_wrap=false,
|
||||
row_wrap=false,
|
||||
reverse=false
|
||||
) =
|
||||
assert(!(any([caps,cap1,cap2]) && row_wrap), "Cannot combine caps with row_wrap")
|
||||
let(
|
||||
lens = [for(row=points) len(row)],
|
||||
rowstarts = [0,each cumsum(lens)],
|
||||
faces =
|
||||
[for(i=[0:1:len(points) - 1 - (row_wrap ? 0 : 1)]) each
|
||||
let(
|
||||
rowstart = rowstarts[i],
|
||||
nextrow = select(rowstarts,i+1),
|
||||
delta = select(lens,i+1)-lens[i]
|
||||
)
|
||||
delta == 0 ?
|
||||
[for(j=[0:1:lens[i]-2]) reverse ? [j+rowstart+1, j+rowstart, j+nextrow] : [j+rowstart, j+rowstart+1, j+nextrow],
|
||||
for(j=[0:1:lens[i]-2]) reverse ? [j+rowstart+1, j+nextrow, j+nextrow+1] : [j+rowstart+1, j+nextrow+1, j+nextrow]] :
|
||||
delta == 1 ?
|
||||
[for(j=[0:1:lens[i]-2]) reverse ? [j+rowstart+1, j+rowstart, j+nextrow+1] : [j+rowstart, j+rowstart+1, j+nextrow+1],
|
||||
for(j=[0:1:lens[i]-1]) reverse ? [j+rowstart, j+nextrow, j+nextrow+1] : [j+rowstart, j+nextrow+1, j+nextrow]] :
|
||||
delta == -1 ?
|
||||
[for(j=[0:1:lens[i]-3]) reverse ? [j+rowstart+1, j+nextrow, j+nextrow+1]: [j+rowstart+1, j+nextrow+1, j+nextrow],
|
||||
for(j=[0:1:lens[i]-2]) reverse ? [j+rowstart+1, j+rowstart, j+nextrow] : [j+rowstart, j+rowstart+1, j+nextrow]] :
|
||||
let(count = floor((lens[i]-1)/2))
|
||||
delta == 2 ?
|
||||
[
|
||||
for(j=[0:1:count-1]) reverse ? [j+rowstart+1, j+rowstart, j+nextrow+1] : [j+rowstart, j+rowstart+1, j+nextrow+1], // top triangles left
|
||||
for(j=[count:1:lens[i]-2]) reverse ? [j+rowstart+1, j+rowstart, j+nextrow+2] : [j+rowstart, j+rowstart+1, j+nextrow+2], // top triangles right
|
||||
for(j=[0:1:count]) reverse ? [j+rowstart, j+nextrow, j+nextrow+1] : [j+rowstart, j+nextrow+1, j+nextrow], // bot triangles left
|
||||
for(j=[count+1:1:select(lens,i+1)-2]) reverse ? [j+rowstart-1, j+nextrow, j+nextrow+1] : [j+rowstart-1, j+nextrow+1, j+nextrow], // bot triangles right
|
||||
] :
|
||||
delta == -2 ?
|
||||
[
|
||||
for(j=[0:1:count-2]) reverse ? [j+nextrow, j+nextrow+1, j+rowstart+1] : [j+nextrow, j+rowstart+1, j+nextrow+1],
|
||||
for(j=[count-1:1:lens[i]-4]) reverse ? [j+nextrow,j+nextrow+1,j+rowstart+2] : [j+nextrow,j+rowstart+2, j+nextrow+1],
|
||||
for(j=[0:1:count-1]) reverse ? [j+nextrow, j+rowstart+1, j+rowstart] : [j+nextrow, j+rowstart, j+rowstart+1],
|
||||
for(j=[count:1:select(lens,i+1)]) reverse ? [ j+nextrow-1, j+rowstart+1, j+rowstart]: [ j+nextrow-1, j+rowstart, j+rowstart+1],
|
||||
] :
|
||||
assert(false,str("Unsupported row length difference of ",delta, " between row ",i," and ",(i+1)%len(points)))
|
||||
],
|
||||
verts = flatten(points),
|
||||
culled_faces=
|
||||
[for(face=faces)
|
||||
if (norm(verts[face[0]]-verts[face[1]])>EPSILON &&
|
||||
norm(verts[face[1]]-verts[face[2]])>EPSILON &&
|
||||
norm(verts[face[2]]-verts[face[0]])>EPSILON)
|
||||
face
|
||||
]
|
||||
)
|
||||
[flatten(points), culled_faces];
|
||||
plen = len(points),
|
||||
// append first vertex of each polygon to its end if wrapping columns
|
||||
st = col_wrap ? [
|
||||
for(i=[0:plen-1])
|
||||
points[i][0] != points[i][len(points[i])-1]
|
||||
? concat(points[i], [points[i][0]])
|
||||
: points[i]
|
||||
] : points,
|
||||
addcol = col_wrap ? len(st[0])-len(points[0]) : 0,
|
||||
rowstarts = [ for(i=[0:plen-1]) len(st[i]) ],
|
||||
capfirst = first_defined([cap1,caps,false]),
|
||||
caplast = first_defined([cap2,caps,false]),
|
||||
pcumlen = [0, each cumsum(rowstarts)],
|
||||
faces = flatten([
|
||||
// close first end
|
||||
if (capfirst)
|
||||
if (reverse) [[ for(i=[0:rowstarts[0]-1-addcol]) i ]]
|
||||
else [[ for(i=[rowstarts[0]-1-addcol:-1:0]) i ]],
|
||||
// triangulate between the two polygons
|
||||
for(i = [0:plen-2+(row_wrap?1:0)]) let(j = (i+1)%plen)
|
||||
_lofttri(st[i], st[j], pcumlen[i], pcumlen[j], rowstarts[i], rowstarts[j], reverse),
|
||||
// close up the last end
|
||||
if (caplast)
|
||||
if (reverse) [[ for(i=[pcumlen[plen]-1-addcol:-1:pcumlen[plen-1]]) i ]]
|
||||
else [[ for(i=[pcumlen[plen-1]:pcumlen[plen]-1-addcol]) i ]]
|
||||
]),
|
||||
vnf = [flatten(st), faces]
|
||||
) col_wrap ? vnf_merge_points(vnf) : vnf;
|
||||
|
||||
/*
|
||||
Recursively triangulate between two 3D paths (which can be different
|
||||
in length by any amount), starting at index 0 and generate a list of
|
||||
triangles with minimum new-side length.
|
||||
The first side of the first triangle always connects the two first
|
||||
vertices of each path.
|
||||
To triangulate between two closed paths, the first and last vertices
|
||||
must be the same.
|
||||
Parameters:
|
||||
p1 = first path, an array of [x,y,z] vertices
|
||||
p2 = second path, an array of [x,y,z] vertices
|
||||
i1offset = index offset of first vertex in the first path
|
||||
(sum of any prior path lengths)
|
||||
i2offset = index offset of first vertex in the second path
|
||||
(sum of any prior path lengths)
|
||||
n1 = number of vertices in first path
|
||||
n2 = number of vertices in second path
|
||||
reverse = if true, assume a polygon path goes around
|
||||
counterclockwise with respect to the direction from
|
||||
p1 to p2 (right hand rule), clockwise if false
|
||||
Other parameters are for internal use:
|
||||
trilist[] = array of triangles to return
|
||||
i1 = vertex index on p1 of the next triangle
|
||||
i2 = vertex index on p2 of the next triangle
|
||||
(next triangle vertex found can be on either p1 or p2, depending
|
||||
on which triangle is smaller.)
|
||||
|
||||
Returns an array of triangles using vertex indices offset by
|
||||
i1offset and i2offset
|
||||
*/
|
||||
function _lofttri(p1, p2, i1offset, i2offset, n1, n2, reverse=false, trilist=[], i1=0, i2=0) = n1!=n2 ?
|
||||
// unequal row lengths
|
||||
let(
|
||||
t1 = i1 < n1 ? i1+1 : n1, // test point 1
|
||||
t2 = i2 < n2 ? i2+1 : n2, // test point 2
|
||||
d12 = t2>=n2 ? 9e+9 : norm(p2[t2]-p1[i1]), // distance from i1 to t2
|
||||
d21 = t1>=n1 ? 9e+9 : norm(p1[t1]-p2[i2]), // distance from i2 to t1
|
||||
triangle = reverse ?
|
||||
[i1offset+i1, i2offset+i2, d12<d21 ? i2offset+t2 : i1offset+t1] :
|
||||
[i2offset+i2, i1offset+i1, d12<d21 ? i2offset+t2 : i1offset+t1]
|
||||
) t1>=n1 && t2>=n2 ? trilist :
|
||||
_lofttri(p1, p2, i1offset, i2offset, n1, n2, reverse, concat(trilist, [triangle]), d12<d21 ? i1 : t1, d12<d21 ? t2 : i2)
|
||||
|
||||
: // equal row lengths
|
||||
let(n=n1, i=i1,
|
||||
t = i < n ? i+1 : n, // test point
|
||||
d12 = t>=n ? 9e+9 : norm(p2[t]-p1[i]), // distance from p1 to new p2
|
||||
d21 = t>=n ? 9e+9 : norm(p1[t]-p2[i]), // distance from p2 to new p1
|
||||
triangle1 = reverse ?
|
||||
[i1offset+i, i2offset+i, d12<d21 ? i2offset+t : i1offset+t] :
|
||||
[i2offset+i, i1offset+i, d12<d21 ? i2offset+t : i1offset+t],
|
||||
triangle2 = reverse ?
|
||||
[i2offset+t, i1offset+t, d12<d21 ? i1offset+i : i2offset+i] :
|
||||
[i1offset+t, i2offset+t, d12<d21 ? i1offset+i : i2offset+i]
|
||||
) t>=n ? trilist :
|
||||
_lofttri(p1, p2, i1offset, i2offset, n, n, reverse, concat(trilist, [triangle1, triangle2]), t, t);
|
||||
|
||||
|
||||
// Function: vnf_join()
|
||||
@@ -436,7 +540,22 @@ function vnf_join(vnfs) =
|
||||
// Arguments:
|
||||
// polygons = The list of 3D polygons to turn into a VNF
|
||||
// fast = Set to true to skip area and coplanarity checks for increased speed. Default: false
|
||||
// eps = Polygons with area small than this are discarded. Default: EPSILON
|
||||
// eps = Polygons with area smaller than this are discarded. Default: EPSILON
|
||||
// Example(3D,VPR=[60,0,40]): Construction of a dodecahedron from pentagon faces.
|
||||
// dihedral = 2*atan(PHI); // dodecahedron face dihedral
|
||||
// rpenta = 10; // pentagon face radius
|
||||
// edge = 2*rpenta*sin(36); // edge length
|
||||
// inrad = 0.5*edge * PHI*PHI/sqrt(3-PHI); // inner radius
|
||||
// face3d = path3d(pentagon(rpenta), inrad); // single face
|
||||
// facepoints = [
|
||||
// face3d,
|
||||
// for(a=[36:72:360]) zrot(a, yrot(180-dihedral, face3d)),
|
||||
// for(a=[36:72:360]) zrot(a, yrot(360-dihedral, face3d)),
|
||||
// yrot(180, face3d)
|
||||
// ];
|
||||
// vnf = vnf_from_polygons(facepoints, fast=true);
|
||||
// vnf_polyhedron(vnf);
|
||||
|
||||
function vnf_from_polygons(polygons,fast=false,eps=EPSILON) =
|
||||
assert(is_list(polygons) && is_path(polygons[0]),"Input should be a list of polygons")
|
||||
let(
|
||||
|
Reference in New Issue
Block a user