2020-02-01 01:03:41 +00:00
//////////////////////////////////////////////////////////////////////
2021-01-05 10:19:46 +00:00
// LibFile: mutators.scad
2020-02-01 01:03:41 +00:00
// Functions and modules to mutate children in various ways.
2021-01-05 09:20:01 +00:00
// Includes:
2020-02-01 01:03:41 +00:00
// include <BOSL2/std.scad>
//////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
2020-09-14 04:50:39 +00:00
// Section: Volume Division Mutators
2020-02-01 01:03:41 +00:00
//////////////////////////////////////////////////////////////////////
2020-09-14 04:50:39 +00:00
// Module: bounding_box()
// Usage:
// bounding_box() ...
// Description:
2021-01-08 04:16:28 +00:00
// Returns the smallest axis-aligned square (or cube) shape that contains all the 2D (or 3D)
// children given. The module children() is supposed to be a 3d shape when planar=false and
// a 2d shape when planar=true otherwise the system will issue a warning of mixing dimension
// or scaling by 0.
2020-09-14 04:50:39 +00:00
// Arguments:
// excess = The amount that the bounding box should be larger than needed to bound the children, in each axis.
2020-12-30 05:02:46 +00:00
// planar = If true, creates a 2D bounding rectangle. Is false, creates a 3D bounding cube. Default: false
2021-01-08 04:16:28 +00:00
// Example(3D):
// module shapes() {
2020-09-14 04:50:39 +00:00
// translate([10,8,4]) cube(5);
// translate([3,0,12]) cube(2);
// }
2021-01-08 04:16:28 +00:00
// #bounding_box() shapes();
// shapes();
// Example(2D):
// module shapes() {
// translate([10,8]) square(5);
// translate([3,0]) square(2);
// }
// #bounding_box(planar=true) shapes();
// shapes();
2021-01-05 10:06:39 +00:00
module bounding_box ( excess = 0 , planar = false ) {
2021-01-08 04:16:28 +00:00
// a 3d (or 2d when planar=true) approx. of the children projection on X axis
module _xProjection ( ) {
2020-12-30 05:02:46 +00:00
if ( planar ) {
2020-12-19 03:04:28 +00:00
projection ( )
rotate ( [ 90 , 0 , 0 ] )
2021-01-08 04:16:28 +00:00
linear_extrude ( 1 , center = true )
2020-12-30 05:02:46 +00:00
hull ( )
children ( ) ;
} else {
2021-01-08 04:16:28 +00:00
xs = excess < . 1 ? 1 : excess ;
2020-12-30 05:02:46 +00:00
linear_extrude ( xs , center = true )
projection ( )
rotate ( [ 90 , 0 , 0 ] )
linear_extrude ( xs , center = true )
projection ( )
hull ( )
children ( ) ;
}
2021-01-08 04:16:28 +00:00
}
2020-09-14 04:50:39 +00:00
// a bounding box with an offset of 1 in all axis
module _oversize_bbox ( ) {
2020-12-30 06:04:47 +00:00
if ( planar ) {
minkowski ( ) {
_xProjection ( ) children ( ) ; // x axis
rotate ( - 90 ) _xProjection ( ) rotate ( 90 ) children ( ) ; // y axis
}
} else {
minkowski ( ) {
_xProjection ( ) children ( ) ; // x axis
2020-12-30 05:02:46 +00:00
rotate ( - 90 ) _xProjection ( ) rotate ( 90 ) children ( ) ; // y axis
2020-12-30 06:04:47 +00:00
rotate ( [ 0 , - 90 , 0 ] ) _xProjection ( ) rotate ( [ 0 , 90 , 0 ] ) children ( ) ; // z axis
2020-12-30 05:02:46 +00:00
}
2020-09-14 04:50:39 +00:00
}
}
2021-01-08 04:16:28 +00:00
// offsets a cube by `excess`
2020-09-14 04:50:39 +00:00
module _shrink_cube ( ) {
intersection ( ) {
2021-01-08 04:16:28 +00:00
translate ( ( 1 - excess ) * [ 1 , 1 , 1 ] ) children ( ) ;
translate ( ( 1 - excess ) * [ - 1 , - 1 , - 1 ] ) children ( ) ;
2020-09-14 04:50:39 +00:00
}
}
2021-01-08 04:16:28 +00:00
if ( planar ) {
offset ( excess - 1 / 2 ) _oversize_bbox ( ) children ( ) ;
2020-09-14 04:50:39 +00:00
} else {
2021-01-08 04:16:28 +00:00
render ( convexity = 2 )
if ( excess > . 1 ) {
_oversize_bbox ( ) children ( ) ;
} else {
_shrink_cube ( ) _oversize_bbox ( ) children ( ) ;
}
2020-09-14 04:50:39 +00:00
}
}
2020-12-03 00:01:39 +00:00
// Function&Module: half_of()
2020-02-01 01:03:41 +00:00
//
2020-12-03 00:01:39 +00:00
// Usage: as module
2021-06-27 03:59:33 +00:00
// half_of(v, [cp], [s], [planar]) ...
2020-12-03 00:01:39 +00:00
// Usage: as function
2021-06-27 03:59:33 +00:00
// result = half_of(p,v,[cp]);
2020-02-01 01:03:41 +00:00
//
// Description:
2021-04-19 20:58:17 +00:00
// Slices an object at a cut plane, and masks away everything that is on one side. The v parameter is either a plane specification or
// a normal vector. The s parameter is needed for the module
// version to control the size of the masking cube, which affects preview display.
// When called as a function, you must supply a vnf, path or region in p. If planar is set to true for the module version the operation
2021-07-30 20:38:25 +00:00
// is performed in and UP and DOWN are treated as equivalent to BACK and FWD respectively.
2020-02-01 01:03:41 +00:00
//
// Arguments:
2021-04-19 20:58:17 +00:00
// p = path, region or VNF to slice. (Function version)
2020-02-01 01:03:41 +00:00
// v = Normal of plane to slice at. Keeps everything on the side the normal points to. Default: [0,0,1] (UP)
2021-04-19 20:58:17 +00:00
// cp = If given as a scalar, moves the cut plane along the normal by the given amount. If given as a point, specifies a point on the cut plane. Default: [0,0,0]
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, it messes with centering your view. Ignored for function version. Default: 1000
// planar = If true, perform a 2D operation. When planar, a `v` of `UP` or `DOWN` becomes equivalent of `BACK` and `FWD` respectively.
2020-02-01 01:03:41 +00:00
//
// Examples:
// half_of(DOWN+BACK, cp=[0,-10,0]) cylinder(h=40, r1=10, r2=0, center=false);
// half_of(DOWN+LEFT, s=200) sphere(d=150);
// Example(2D):
// half_of([1,1], planar=true) circle(d=50);
2020-04-30 05:45:41 +00:00
module half_of ( v = UP , cp , s = 1000 , planar = false )
2020-02-01 01:03:41 +00:00
{
2020-05-30 02:04:34 +00:00
cp = is_vector ( v , 4 ) ? assert ( cp = = undef , "Don't use cp with plane definition." ) plane_normal ( v ) * v [ 3 ] :
is_vector ( cp ) ? cp :
is_num ( cp ) ? cp * unit ( v ) :
[ 0 , 0 , 0 ] ;
v = is_vector ( v , 4 ) ? plane_normal ( v ) : v ;
if ( cp ! = [ 0 , 0 , 0 ] ) {
translate ( cp ) half_of ( v = v , s = s , planar = planar ) translate ( - cp ) children ( ) ;
} else if ( planar ) {
v = ( v = = UP ) ? BACK : ( v = = DOWN ) ? FWD : v ;
ang = atan2 ( v . y , v . x ) ;
difference ( ) {
children ( ) ;
rotate ( ang + 90 ) {
back ( s / 2 ) square ( s , center = true ) ;
}
}
} else {
difference ( ) {
children ( ) ;
rot ( from = UP , to = - v ) {
up ( s / 2 ) cube ( s , center = true ) ;
}
}
}
2020-02-01 01:03:41 +00:00
}
2021-04-19 20:58:17 +00:00
function half_of ( p , v = UP , cp ) =
is_vnf ( p ) ?
assert ( is_vector ( v ) && ( len ( v ) = = 3 || len ( v ) = = 4 ) , str ( "Must give 3-vector or plane specification" , v ) )
assert ( select ( v , 0 , 2 ) ! = [ 0 , 0 , 0 ] , "vector v must be nonzero" )
let (
plane = is_vector ( v , 4 ) ? assert ( cp = = undef , "Don't use cp with plane definition." ) v
: is_undef ( cp ) ? [ each v , 0 ]
: is_num ( cp ) ? [ each v , cp * ( v * v ) / norm ( v ) ]
: assert ( is_vector ( cp , 3 ) , "Centerpoint must be a 3-vector" )
[ each v , cp * v ]
)
vnf_halfspace ( plane , p )
: is_path ( p ) || is_region ( p ) ?
let (
v = ( v = = UP ) ? BACK : ( v = = DOWN ) ? FWD : v ,
cp = is_undef ( cp ) ? [ 0 , 0 ]
2021-07-30 20:38:25 +00:00
: is_num ( cp ) ? v * cp
2021-04-19 20:58:17 +00:00
: assert ( is_vector ( cp , 2 ) || ( is_vector ( cp , 3 ) && cp . z = = 0 ) , "Centerpoint must be 2-vector" )
cp
)
assert ( is_vector ( v , 2 ) || ( is_vector ( v , 3 ) && v . z = = 0 ) , "Must give 2-vector" )
assert ( ! all_zero ( v ) , "Vector v must be nonzero" )
let (
bounds = pointlist_bounds ( move ( - cp , p ) ) ,
L = 2 * max ( flatten ( bounds ) ) ,
n = unit ( v ) ,
u = [ - n . y , n . x ] ,
box = [ cp + u * L , cp + ( v + u ) * L , cp + ( v - u ) * L , cp - u * L ]
)
intersection ( box , p )
: assert ( false , "Input must be a region, path or VNF" ) ;
2021-07-30 20:38:25 +00:00
2021-04-19 20:58:17 +00:00
/* This code cut 3d paths but leaves behind connecting line segments
2020-12-03 00:01:39 +00:00
is_path ( p ) ?
2021-04-19 20:58:17 +00:00
//assert(len(p[0]) == d, str("path must have dimension ", d))
2020-12-03 00:01:39 +00:00
let ( z = [ for ( x = p ) ( x - cp ) * v ] )
[ for ( i = [ 0 : len ( p ) - 1 ] ) each concat ( z [ i ] >= 0 ? [ p [ i ] ] : [ ] ,
// we assume a closed path here;
// to make this correct for an open path,
// just replace this by [] when i==len(p)-1:
let ( j = ( i + 1 ) % len ( p ) )
// the remaining path may have flattened sections, but this cannot
// create self-intersection or whiskers:
z [ i ] * z [ j ] >= 0 ? [ ] : [ ( z [ j ] * p [ i ] - z [ i ] * p [ j ] ) / ( z [ j ] - z [ i ] ) ] ) ]
:
2021-04-19 20:58:17 +00:00
* /
2020-02-01 01:03:41 +00:00
2020-12-03 20:21:05 +00:00
// Function&Module: left_half()
2020-02-01 01:03:41 +00:00
//
2020-12-03 20:21:05 +00:00
// Usage: as module
2021-06-27 03:59:33 +00:00
// left_half([s], [x]) ...
// left_half(planar=true, [s], [x]) ...
2020-12-03 20:21:05 +00:00
// Usage: as function
2021-06-27 03:59:33 +00:00
// result = left_half(p, [x]);
2020-02-01 01:03:41 +00:00
//
// Description:
// Slices an object at a vertical Y-Z cut plane, and masks away everything that is right of it.
//
// Arguments:
2021-04-19 20:58:17 +00:00
// p = VNF, region or path to slice (function version)
2020-02-01 01:03:41 +00:00
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may be incorrect. Default: 10000
// x = The X coordinate of the cut-plane. Default: 0
2021-04-19 20:58:17 +00:00
// planar = If true, perform a 2D operation.
2020-02-01 01:03:41 +00:00
//
// Examples:
// left_half() sphere(r=20);
// left_half(x=-8) sphere(r=20);
// Example(2D):
// left_half(planar=true) circle(r=20);
module left_half ( s = 1000 , x = 0 , planar = false )
{
2020-05-30 02:04:34 +00:00
dir = LEFT ;
difference ( ) {
children ( ) ;
translate ( [ x , 0 , 0 ] - dir * s / 2 ) {
if ( planar ) {
square ( s , center = true ) ;
} else {
cube ( s , center = true ) ;
}
}
}
2020-02-01 01:03:41 +00:00
}
2021-04-19 20:58:17 +00:00
function left_half ( p , x = 0 ) = half_of ( p , LEFT , [ x , 0 , 0 ] ) ;
2020-02-01 01:03:41 +00:00
2020-12-03 20:21:05 +00:00
// Function&Module: right_half()
2020-02-01 01:03:41 +00:00
//
2021-04-19 20:58:17 +00:00
// Usage: as module
2020-02-01 01:03:41 +00:00
// right_half([s], [x]) ...
// right_half(planar=true, [s], [x]) ...
2021-04-19 20:58:17 +00:00
// Usage: as function
2021-06-27 03:59:33 +00:00
// result = right_half(p, [x]);
2020-02-01 01:03:41 +00:00
//
// Description:
// Slices an object at a vertical Y-Z cut plane, and masks away everything that is left of it.
//
// Arguments:
2021-04-19 20:58:17 +00:00
// p = VNF, region or path to slice (function version)
2020-02-01 01:03:41 +00:00
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may be incorrect. Default: 10000
// x = The X coordinate of the cut-plane. Default: 0
2021-04-19 20:58:17 +00:00
// planar = If true perform a 2D operation.
2020-02-01 01:03:41 +00:00
//
2021-02-20 03:56:43 +00:00
// Examples(FlatSpin,VPD=175):
2020-02-01 01:03:41 +00:00
// right_half() sphere(r=20);
// right_half(x=-5) sphere(r=20);
// Example(2D):
// right_half(planar=true) circle(r=20);
module right_half ( s = 1000 , x = 0 , planar = false )
{
2020-05-30 02:04:34 +00:00
dir = RIGHT ;
difference ( ) {
children ( ) ;
translate ( [ x , 0 , 0 ] - dir * s / 2 ) {
if ( planar ) {
square ( s , center = true ) ;
} else {
cube ( s , center = true ) ;
}
}
}
2020-02-01 01:03:41 +00:00
}
2021-04-19 20:58:17 +00:00
function right_half ( p , x = 0 ) = half_of ( p , RIGHT , [ x , 0 , 0 ] ) ;
2020-02-01 01:03:41 +00:00
2020-12-03 20:21:05 +00:00
// Function&Module: front_half()
2020-02-01 01:03:41 +00:00
//
// Usage:
2021-06-27 03:59:33 +00:00
// front_half([s], [y]) ...
// front_half(planar=true, [s], [y]) ...
2021-04-19 20:58:17 +00:00
// Usage: as function
2021-06-27 03:59:33 +00:00
// result = front_half(p, [y]);
2020-02-01 01:03:41 +00:00
//
// Description:
// Slices an object at a vertical X-Z cut plane, and masks away everything that is behind it.
//
// Arguments:
2021-04-19 20:58:17 +00:00
// p = VNF, region or path to slice (function version)
2020-02-01 01:03:41 +00:00
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may be incorrect. Default: 10000
// y = The Y coordinate of the cut-plane. Default: 0
2021-04-19 20:58:17 +00:00
// planar = If true perform a 2D operation.
2020-02-01 01:03:41 +00:00
//
2021-02-20 03:56:43 +00:00
// Examples(FlatSpin,VPD=175):
2020-02-01 01:03:41 +00:00
// front_half() sphere(r=20);
// front_half(y=5) sphere(r=20);
// Example(2D):
// front_half(planar=true) circle(r=20);
module front_half ( s = 1000 , y = 0 , planar = false )
{
2020-05-30 02:04:34 +00:00
dir = FWD ;
difference ( ) {
children ( ) ;
translate ( [ 0 , y , 0 ] - dir * s / 2 ) {
if ( planar ) {
square ( s , center = true ) ;
} else {
cube ( s , center = true ) ;
}
}
}
2020-02-01 01:03:41 +00:00
}
2021-04-19 20:58:17 +00:00
function front_half ( p , y = 0 ) = half_of ( p , FRONT , [ 0 , y , 0 ] ) ;
2020-02-01 01:03:41 +00:00
2020-12-03 20:21:05 +00:00
// Function&Module: back_half()
2020-02-01 01:03:41 +00:00
//
// Usage:
2021-06-27 03:59:33 +00:00
// back_half([s], [y]) ...
// back_half(planar=true, [s], [y]) ...
2021-04-19 20:58:17 +00:00
// Usage: as function
2021-06-27 03:59:33 +00:00
// result = back_half(p, [y]);
2020-02-01 01:03:41 +00:00
//
// Description:
// Slices an object at a vertical X-Z cut plane, and masks away everything that is in front of it.
//
// Arguments:
2021-04-19 20:58:17 +00:00
// p = VNF, region or path to slice (function version)
2020-02-01 01:03:41 +00:00
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may be incorrect. Default: 10000
// y = The Y coordinate of the cut-plane. Default: 0
2021-04-19 20:58:17 +00:00
// planar = If true perform a 2D operation.
2020-02-01 01:03:41 +00:00
//
// Examples:
// back_half() sphere(r=20);
// back_half(y=8) sphere(r=20);
// Example(2D):
// back_half(planar=true) circle(r=20);
module back_half ( s = 1000 , y = 0 , planar = false )
{
2020-05-30 02:04:34 +00:00
dir = BACK ;
difference ( ) {
children ( ) ;
translate ( [ 0 , y , 0 ] - dir * s / 2 ) {
if ( planar ) {
square ( s , center = true ) ;
} else {
cube ( s , center = true ) ;
}
}
}
2020-02-01 01:03:41 +00:00
}
2021-04-19 20:58:17 +00:00
function back_half ( p , y = 0 ) = half_of ( p , BACK , [ 0 , y , 0 ] ) ;
2020-02-01 01:03:41 +00:00
2020-12-03 20:21:05 +00:00
// Function&Module: bottom_half()
2020-02-01 01:03:41 +00:00
//
// Usage:
2021-06-27 03:59:33 +00:00
// bottom_half([s], [z]) ...
2021-04-19 20:58:17 +00:00
// Usage: as function
2021-06-27 03:59:33 +00:00
// result = bottom_half(p, [z]);
2020-02-01 01:03:41 +00:00
//
// Description:
// Slices an object at a horizontal X-Y cut plane, and masks away everything that is above it.
//
// Arguments:
2021-04-19 20:58:17 +00:00
// p = VNF, region or path to slice (function version)
2020-02-01 01:03:41 +00:00
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may be incorrect. Default: 10000
// z = The Z coordinate of the cut-plane. Default: 0
//
// Examples:
// bottom_half() sphere(r=20);
// bottom_half(z=-10) sphere(r=20);
module bottom_half ( s = 1000 , z = 0 )
{
2020-05-30 02:04:34 +00:00
dir = DOWN ;
difference ( ) {
children ( ) ;
translate ( [ 0 , 0 , z ] - dir * s / 2 ) {
cube ( s , center = true ) ;
}
}
2020-02-01 01:03:41 +00:00
}
2021-04-19 20:58:17 +00:00
function bottom_half ( p , z = 0 ) = half_of ( p , BOTTOM , [ 0 , 0 , z ] ) ;
2020-02-01 01:03:41 +00:00
2020-12-03 20:21:05 +00:00
// Function&Module: top_half()
2020-02-01 01:03:41 +00:00
//
// Usage:
2021-06-27 03:59:33 +00:00
// top_half([s], [z]) ...
// result = top_half(p, [z]);
2020-02-01 01:03:41 +00:00
//
// Description:
// Slices an object at a horizontal X-Y cut plane, and masks away everything that is below it.
//
// Arguments:
2021-04-19 20:58:17 +00:00
// p = VNF, region or path to slice (function version)
2020-02-01 01:03:41 +00:00
// s = Mask size to use. Use a number larger than twice your object's largest axis. If you make this too large, OpenSCAD's preview rendering may be incorrect. Default: 10000
// z = The Z coordinate of the cut-plane. Default: 0
//
2021-02-20 03:56:43 +00:00
// Examples(Spin,VPD=175):
2020-02-01 01:03:41 +00:00
// top_half() sphere(r=20);
// top_half(z=5) sphere(r=20);
module top_half ( s = 1000 , z = 0 )
{
2020-05-30 02:04:34 +00:00
dir = UP ;
difference ( ) {
children ( ) ;
translate ( [ 0 , 0 , z ] - dir * s / 2 ) {
cube ( s , center = true ) ;
}
}
2020-02-01 01:03:41 +00:00
}
2021-04-19 20:58:17 +00:00
function top_half ( p , z = 0 ) = half_of ( p , UP , [ 0 , 0 , z ] ) ;
2020-02-01 01:03:41 +00:00
//////////////////////////////////////////////////////////////////////
2021-01-17 09:36:43 +00:00
// Section: Warp Mutators
2020-02-01 01:03:41 +00:00
//////////////////////////////////////////////////////////////////////
2021-01-17 09:36:43 +00:00
2020-02-01 01:03:41 +00:00
// Module: chain_hull()
//
// Usage:
// chain_hull() ...
//
// Description:
// Performs hull operations between consecutive pairs of children,
// then unions all of the hull results. This can be a very slow
// operation, but it can provide results that are hard to get
// otherwise.
//
// Side Effects:
// `$idx` is set to the index value of the first child of each hulling pair, and can be used to modify each child pair individually.
// `$primary` is set to true when the child is the first in a chain pair.
//
// Example:
// chain_hull() {
// cube(5, center=true);
// translate([30, 0, 0]) sphere(d=15);
// translate([60, 30, 0]) cylinder(d=10, h=20);
// translate([60, 60, 0]) cube([10,1,20], center=false);
// }
// Example: Using `$idx` and `$primary`
// chain_hull() {
// zrot( 0) right(100) if ($primary) cube(5+3*$idx,center=true); else sphere(r=10+3*$idx);
// zrot( 45) right(100) if ($primary) cube(5+3*$idx,center=true); else sphere(r=10+3*$idx);
// zrot( 90) right(100) if ($primary) cube(5+3*$idx,center=true); else sphere(r=10+3*$idx);
// zrot(135) right(100) if ($primary) cube(5+3*$idx,center=true); else sphere(r=10+3*$idx);
// zrot(180) right(100) if ($primary) cube(5+3*$idx,center=true); else sphere(r=10+3*$idx);
// }
module chain_hull ( )
{
2020-05-30 02:04:34 +00:00
union ( ) {
if ( $children = = 1 ) {
children ( ) ;
} else if ( $children > 1 ) {
for ( i = [ 1 : 1 : $children - 1 ] ) {
$ idx = i ;
hull ( ) {
let ( $ primary = true ) children ( i - 1 ) ;
let ( $ primary = false ) children ( i ) ;
}
}
}
}
2020-02-01 01:03:41 +00:00
}
2021-01-17 09:36:43 +00:00
// Module: path_extrude2d()
// Usage:
2021-06-27 03:59:33 +00:00
// path_extrude2d(path, [caps]) {...}
2021-01-17 09:36:43 +00:00
// Description:
// Extrudes 2D children along the given 2D path, with optional rounded endcaps.
// Arguments:
// path = The 2D path to extrude the geometry along.
// caps = If true, caps each end of the path with a `rotate_extrude()`d copy of the children. This may interact oddly when given asymmetric profile children.
// Example:
// path = [
// each right(50, p=arc(d=100,angle=[90,180])),
// each left(50, p=arc(d=100,angle=[0,-90])),
// ];
// path_extrude2d(path,caps=false) {
// fwd(2.5) square([5,6],center=true);
// fwd(6) square([10,5],center=true);
// }
// Example:
// path_extrude2d(arc(d=100,angle=[180,270]))
// trapezoid(w1=10, w2=5, h=10, anchor=BACK);
// Example:
// include <BOSL2/beziers.scad>
// path = bezier_path([
// [-50,0], [-25,50], [0,0], [50,0]
// ]);
// path_extrude2d(path, caps=false)
// trapezoid(w1=10, w2=1, h=5, anchor=BACK);
module path_extrude2d ( path , caps = true ) {
thin = 0.01 ;
path = deduplicate ( path ) ;
for ( p = pair ( path ) ) {
delt = p [ 1 ] - p [ 0 ] ;
translate ( p [ 0 ] ) {
rot ( from = BACK , to = delt ) {
minkowski ( ) {
cube ( [ thin , norm ( delt ) , thin ] , anchor = FRONT ) ;
rotate ( [ 90 , 0 , 0 ] ) linear_extrude ( height = thin , center = true ) children ( ) ;
}
}
}
}
for ( t = triplet ( path ) ) {
2021-06-15 03:28:49 +00:00
ang = v_theta ( t [ 2 ] - t [ 1 ] ) - v_theta ( t [ 1 ] - t [ 0 ] ) ;
2021-01-17 09:36:43 +00:00
delt = t [ 2 ] - t [ 1 ] ;
translate ( t [ 1 ] ) {
minkowski ( ) {
cube ( thin , center = true ) ;
if ( ang >= 0 ) {
rotate ( 90 - ang )
rot ( from = LEFT , to = delt )
rotate_extrude ( angle = ang + 0.01 )
right_half ( planar = true ) children ( ) ;
} else {
rotate ( - 90 )
rot ( from = RIGHT , to = delt )
rotate_extrude ( angle = - ang + 0.01 )
left_half ( planar = true ) children ( ) ;
}
}
}
}
if ( caps ) {
move_copies ( [ path [ 0 ] , last ( path ) ] )
rotate_extrude ( )
right_half ( planar = true ) children ( ) ;
}
}
2020-02-01 01:03:41 +00:00
2020-05-15 20:59:27 +00:00
// Module: cylindrical_extrude()
// Usage:
// cylindrical_extrude(size, ir|id, or|od, [convexity]) ...
// Description:
2020-08-27 03:39:45 +00:00
// Extrudes all 2D children outwards, curved around a cylindrical shape.
2020-05-15 20:59:27 +00:00
// Arguments:
// or = The outer radius to extrude to.
// od = The outer diameter to extrude to.
// ir = The inner radius to extrude from.
// id = The inner diameter to extrude from.
// size = The [X,Y] size of the 2D children to extrude. Default: [1000,1000]
// convexity = The max number of times a line could pass though a wall. Default: 10
// spin = Amount in degrees to spin around cylindrical axis. Default: 0
// orient = The orientation of the cylinder to wrap around, given as a vector. Default: UP
// Example:
// cylindrical_extrude(or=50, ir=45)
// text(text="Hello World!", size=10, halign="center", valign="center");
// Example: Spin Around the Cylindrical Axis
// cylindrical_extrude(or=50, ir=45, spin=90)
// text(text="Hello World!", size=10, halign="center", valign="center");
// Example: Orient to the Y Axis.
// cylindrical_extrude(or=40, ir=35, orient=BACK)
// text(text="Hello World!", size=10, halign="center", valign="center");
module cylindrical_extrude ( or , ir , od , id , size = 1000 , convexity = 10 , spin = 0 , orient = UP ) {
2020-05-30 02:04:34 +00:00
assert ( is_num ( size ) || is_vector ( size , 2 ) ) ;
size = is_num ( size ) ? [ size , size ] : size ;
ir = get_radius ( r = ir , d = id ) ;
or = get_radius ( r = or , d = od ) ;
index_r = or ;
circumf = 2 * PI * index_r ;
width = min ( size . x , circumf ) ;
assert ( width < = circumf , "Shape would more than completely wrap around." ) ;
sides = segs ( or ) ;
step = circumf / sides ;
steps = ceil ( width / step ) ;
rot ( from = UP , to = orient ) rot ( spin ) {
for ( i = [ 0 : 1 : steps - 2 ] ) {
x = ( i + 0.5 - steps / 2 ) * step ;
zrot ( 360 * x / circumf ) {
fwd ( or * cos ( 180 / sides ) ) {
xrot ( - 90 ) {
linear_extrude ( height = or - ir , scale = [ ir / or , 1 ] , center = false , convexity = convexity ) {
yflip ( )
intersection ( ) {
left ( x ) children ( ) ;
rect ( [ quantup ( step , pow ( 2 , - 15 ) ) , size . y ] , center = true ) ;
}
}
}
}
}
}
}
2020-05-15 20:59:27 +00:00
}
2021-09-17 21:07:18 +00:00
// Module: extrude_from_to()
// Description:
// Extrudes a 2D shape between the 3d points pt1 and pt2. Takes as children a set of 2D shapes to extrude.
// Arguments:
// pt1 = starting point of extrusion.
// pt2 = ending point of extrusion.
// convexity = max number of times a line could intersect a wall of the 2D shape being extruded.
// twist = number of degrees to twist the 2D shape over the entire extrusion length.
// scale = scale multiplier for end of extrusion compared the start.
// slices = Number of slices along the extrusion to break the extrusion into. Useful for refining `twist` extrusions.
// Example(FlatSpin,VPD=200,VPT=[0,0,15]):
// extrude_from_to([0,0,0], [10,20,30], convexity=4, twist=360, scale=3.0, slices=40) {
// xcopies(3) circle(3, $fn=32);
// }
module extrude_from_to ( pt1 , pt2 , convexity , twist , scale , slices ) {
assert ( is_vector ( pt1 ) ) ;
assert ( is_vector ( pt2 ) ) ;
pt1 = point3d ( pt1 ) ;
pt2 = point3d ( pt2 ) ;
rtp = xyz_to_spherical ( pt2 - pt1 ) ;
translate ( pt1 ) {
rotate ( [ 0 , rtp [ 2 ] , rtp [ 1 ] ] ) {
if ( rtp [ 0 ] > 0 ) {
linear_extrude ( height = rtp [ 0 ] , convexity = convexity , center = false , slices = slices , twist = twist , scale = scale ) {
children ( ) ;
}
}
}
}
}
// Module: spiral_sweep()
// Description:
// Takes a closed 2D polygon path, centered on the XY plane, and sweeps/extrudes it along a 3D spiral path
// of a given radius, height and twist. The origin in the profile traces out the helix of the specified radius.
// If twist is positive the path will be right-handed; if twist is negative the path will be left-handed.
// .
// Higbee specifies tapering applied to the ends of the extrusion and is given as the linear distance
// over which to taper.
// Arguments:
// poly = Array of points of a polygon path, to be extruded.
// h = height of the spiral to extrude along.
// r = Radius of the spiral to extrude along. Default: 50
// twist = number of degrees of rotation to spiral up along height.
// ---
// d = Diameter of the spiral to extrude along.
// higbee = Length to taper thread ends over.
// higbee1 = Taper length at start
// higbee2 = Taper length at end
// internal = direction to taper the threads with higbee. If true threads taper outward; if false they taper inward. Default: false
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#orient). Default: `UP`
// center = If given, overrides `anchor`. A true value sets `anchor=CENTER`, false sets `anchor=BOTTOM`.
// Example:
// poly = [[-10,0], [-3,-5], [3,-5], [10,0], [0,-30]];
// spiral_sweep(poly, h=200, r=50, twist=1080, $fn=36);
module spiral_sweep ( poly , h , r , twist = 360 , higbee , center , r1 , r2 , d , d1 , d2 , higbee1 , higbee2 , internal = false , anchor , spin = 0 , orient = UP ) {
higsample = 10 ; // Oversample factor for higbee tapering
dummy1 = assert ( is_num ( twist ) && twist ! = 0 ) ;
bounds = pointlist_bounds ( poly ) ;
yctr = ( bounds [ 0 ] . y + bounds [ 1 ] . y ) / 2 ;
xmin = bounds [ 0 ] . x ;
xmax = bounds [ 1 ] . x ;
poly = path3d ( clockwise_polygon ( poly ) ) ;
anchor = get_anchor ( anchor , center , BOT , BOT ) ;
r1 = get_radius ( r1 = r1 , r = r , d1 = d1 , d = d , dflt = 50 ) ;
r2 = get_radius ( r1 = r2 , r = r , d1 = d2 , d = d , dflt = 50 ) ;
sides = segs ( max ( r1 , r2 ) ) ;
dir = sign ( twist ) ;
ang_step = 360 / sides * dir ;
anglist = [ for ( ang = [ 0 : ang_step : twist - EPSILON ] ) ang ,
twist ] ;
higbee1 = first_defined ( [ higbee1 , higbee , 0 ] ) ;
higbee2 = first_defined ( [ higbee2 , higbee , 0 ] ) ;
higang1 = 360 * higbee1 / ( 2 * r1 * PI ) ;
higang2 = 360 * higbee2 / ( 2 * r2 * PI ) ;
dummy2 = assert ( higbee1 >= 0 && higbee2 >= 0 )
assert ( higang1 < dir * twist / 2 , "Higbee1 is more than half the threads" )
assert ( higang2 < dir * twist / 2 , "Higbee2 is more than half the threads" ) ;
function polygon_r ( N , theta ) =
let ( alpha = 360 / N )
cos ( alpha / 2 ) / ( cos ( posmod ( theta , alpha ) - alpha / 2 ) ) ;
higofs = pow ( 0.05 , 2 ) ; // Smallest hig scale is the square root of this value
function taperfunc ( x ) = sqrt ( ( 1 - higofs ) * x + higofs ) ;
interp_ang = [
for ( i = idx ( anglist , e = - 2 ) )
each lerpn ( anglist [ i ] , anglist [ i + 1 ] ,
( higang1 > 0 && higang1 > dir * anglist [ i + 1 ]
|| ( higang2 > 0 && higang2 > dir * ( twist - anglist [ i ] ) ) ) ? ceil ( ( anglist [ i + 1 ] - anglist [ i ] ) / ang_step * higsample )
: 1 ,
endpoint = false ) ,
last ( anglist )
] ;
skewmat = affine3d_skew_xz ( xa = atan2 ( r2 - r1 , h ) ) ;
points = [
for ( a = interp_ang ) let (
hsc = dir * a < higang1 ? taperfunc ( dir * a / higang1 )
: dir * ( twist - a ) < higang2 ? taperfunc ( dir * ( twist - a ) / higang2 )
: 1 ,
u = a / twist ,
r = lerp ( r1 , r2 , u ) ,
mat = affine3d_zrot ( a )
* affine3d_translate ( [ polygon_r ( sides , a ) * r , 0 , h * ( u - 0.5 ) ] )
* affine3d_xrot ( 90 )
* skewmat
* scale ( [ hsc , lerp ( hsc , 1 , 0.25 ) , 1 ] , cp = [ internal ? xmax : xmin , yctr , 0 ] ) ,
pts = apply ( mat , poly )
) pts
] ;
vnf = vnf_vertex_array (
points , col_wrap = true , caps = true , reverse = dir > 0 ? true : false ,
style = higbee1 > 0 || higbee2 > 0 ? "quincunx" : "alt"
) ;
attachable ( anchor , spin , orient , r1 = r1 , r2 = r2 , l = h ) {
vnf_polyhedron ( vnf , convexity = ceil ( 2 * dir * twist / 360 ) ) ;
children ( ) ;
}
}
// Module: path_extrude()
// Description:
// Extrudes 2D children along a 3D path. This may be slow.
// Arguments:
// path = array of points for the bezier path to extrude along.
// convexity = maximum number of walls a ran can pass through.
// clipsize = increase if artifacts are left. Default: 1000
// Example(FlatSpin,VPD=600,VPT=[75,16,20]):
// path = [ [0, 0, 0], [33, 33, 33], [66, 33, 40], [100, 0, 0], [150,0,0] ];
// path_extrude(path) circle(r=10, $fn=6);
module path_extrude ( path , convexity = 10 , clipsize = 100 ) {
function polyquats ( path , q = q_ident ( ) , v = [ 0 , 0 , 1 ] , i = 0 ) = let (
v2 = path [ i + 1 ] - path [ i ] ,
ang = vector_angle ( v , v2 ) ,
axis = ang > 0.001 ? unit ( cross ( v , v2 ) ) : [ 0 , 0 , 1 ] ,
newq = q_mul ( quat ( axis , ang ) , q ) ,
dist = norm ( v2 )
) i < ( len ( path ) - 2 ) ?
concat ( [ [ dist , newq , ang ] ] , polyquats ( path , newq , v2 , i + 1 ) ) :
[ [ dist , newq , ang ] ] ;
epsilon = 0.0001 ; // Make segments ever so slightly too long so they overlap.
ptcount = len ( path ) ;
pquats = polyquats ( path ) ;
for ( i = [ 0 : 1 : ptcount - 2 ] ) {
pt1 = path [ i ] ;
pt2 = path [ i + 1 ] ;
dist = pquats [ i ] [ 0 ] ;
q = pquats [ i ] [ 1 ] ;
difference ( ) {
translate ( pt1 ) {
q_rot ( q ) {
down ( clipsize / 2 / 2 ) {
if ( ( dist + clipsize / 2 ) > 0 ) {
linear_extrude ( height = dist + clipsize / 2 , convexity = convexity ) {
children ( ) ;
}
}
}
}
}
translate ( pt1 ) {
hq = ( i > 0 ) ? q_slerp ( q , pquats [ i - 1 ] [ 1 ] , 0.5 ) : q ;
q_rot ( hq ) down ( clipsize / 2 + epsilon ) cube ( clipsize , center = true ) ;
}
translate ( pt2 ) {
hq = ( i < ptcount - 2 ) ? q_slerp ( q , pquats [ i + 1 ] [ 1 ] , 0.5 ) : q ;
q_rot ( hq ) up ( clipsize / 2 + epsilon ) cube ( clipsize , center = true ) ;
}
}
}
}
2020-05-15 20:59:27 +00:00
2020-02-01 01:03:41 +00:00
//////////////////////////////////////////////////////////////////////
// Section: Offset Mutators
//////////////////////////////////////////////////////////////////////
2020-12-30 05:02:46 +00:00
// Module: minkowski_difference()
2020-02-01 01:03:41 +00:00
// Usage:
2020-12-30 05:02:46 +00:00
// minkowski_difference() { base_shape(); diff_shape(); ... }
2020-02-01 01:03:41 +00:00
// Description:
2020-12-30 05:02:46 +00:00
// Takes a 3D base shape and one or more 3D diff shapes, carves out the diff shapes from the
// surface of the base shape, in a way complementary to how `minkowski()` unions shapes to the
// surface of its base shape.
2020-02-01 01:03:41 +00:00
// Arguments:
2020-12-30 05:02:46 +00:00
// planar = If true, performs minkowski difference in 2D. Default: false (3D)
// Example:
// minkowski_difference() {
// union() {
// cube([120,70,70], center=true);
// cube([70,120,70], center=true);
// cube([70,70,120], center=true);
// }
// sphere(r=10);
// }
module minkowski_difference ( planar = false ) {
difference ( ) {
bounding_box ( excess = 0 , planar = planar ) children ( 0 ) ;
render ( convexity = 20 ) {
2020-05-30 02:04:34 +00:00
minkowski ( ) {
difference ( ) {
2020-12-30 05:02:46 +00:00
bounding_box ( excess = 1 , planar = planar ) children ( 0 ) ;
children ( 0 ) ;
2020-05-30 02:04:34 +00:00
}
2020-12-30 05:02:46 +00:00
for ( i = [ 1 : 1 : $children - 1 ] ) children ( i ) ;
2020-05-30 02:04:34 +00:00
}
}
}
2020-02-01 01:03:41 +00:00
}
// Module: round2d()
// Usage:
// round2d(r) ...
// round2d(or) ...
// round2d(ir) ...
// round2d(or, ir) ...
// Description:
// Rounds arbitrary 2D objects. Giving `r` rounds all concave and convex corners. Giving just `ir`
// rounds just concave corners. Giving just `or` rounds convex corners. Giving both `ir` and `or`
// can let you round to different radii for concave and convex corners. The 2D object must not have
// any parts narrower than twice the `or` radius. Such parts will disappear.
// Arguments:
// r = Radius to round all concave and convex corners to.
// or = Radius to round only outside (convex) corners to. Use instead of `r`.
// ir = Radius to round only inside (concave) corners to. Use instead of `r`.
// Examples(2D):
// round2d(r=10) {square([40,100], center=true); square([100,40], center=true);}
// round2d(or=10) {square([40,100], center=true); square([100,40], center=true);}
// round2d(ir=10) {square([40,100], center=true); square([100,40], center=true);}
// round2d(or=16,ir=8) {square([40,100], center=true); square([100,40], center=true);}
module round2d ( r , or , ir )
{
2020-05-30 02:04:34 +00:00
or = get_radius ( r1 = or , r = r , dflt = 0 ) ;
ir = get_radius ( r1 = ir , r = r , dflt = 0 ) ;
offset ( or ) offset ( - ir - or ) offset ( delta = ir , chamfer = true ) children ( ) ;
2020-02-01 01:03:41 +00:00
}
// Module: shell2d()
// Usage:
// shell2d(thickness, [or], [ir], [fill], [round])
// Description:
// Creates a hollow shell from 2D children, with optional rounding.
// Arguments:
// thickness = Thickness of the shell. Positive to expand outward, negative to shrink inward, or a two-element list to do both.
2020-12-30 05:02:46 +00:00
// or = Radius to round corners on the outside of the shell. If given a list of 2 radii, [CONVEX,CONCAVE], specifies the radii for convex and concave corners separately. Default: 0 (no outside rounding)
// ir = Radius to round corners on the inside of the shell. If given a list of 2 radii, [CONVEX,CONCAVE], specifies the radii for convex and concave corners separately. Default: 0 (no inside rounding)
2020-02-01 01:03:41 +00:00
// Examples(2D):
// shell2d(10) {square([40,100], center=true); square([100,40], center=true);}
// shell2d(-10) {square([40,100], center=true); square([100,40], center=true);}
// shell2d([-10,10]) {square([40,100], center=true); square([100,40], center=true);}
// shell2d(10,or=10) {square([40,100], center=true); square([100,40], center=true);}
// shell2d(10,ir=10) {square([40,100], center=true); square([100,40], center=true);}
2021-01-05 10:14:04 +00:00
// shell2d(10,or=[10,0]) {square([40,100], center=true); square([100,40], center=true);}
// shell2d(10,or=[0,10]) {square([40,100], center=true); square([100,40], center=true);}
// shell2d(10,ir=[10,0]) {square([40,100], center=true); square([100,40], center=true);}
// shell2d(10,ir=[0,10]) {square([40,100], center=true); square([100,40], center=true);}
// shell2d(8,or=[16,8],ir=[16,8]) {square([40,100], center=true); square([100,40], center=true);}
2020-12-30 05:02:46 +00:00
module shell2d ( thickness , or = 0 , ir = 0 )
2020-02-01 01:03:41 +00:00
{
2020-05-30 02:04:34 +00:00
thickness = is_num ( thickness ) ? (
thickness < 0 ? [ thickness , 0 ] : [ 0 , thickness ]
) : ( thickness [ 0 ] > thickness [ 1 ] ) ? (
[ thickness [ 1 ] , thickness [ 0 ] ]
) : thickness ;
2020-12-30 05:02:46 +00:00
orad = is_finite ( or ) ? [ or , or ] : or ;
irad = is_finite ( ir ) ? [ ir , ir ] : ir ;
2020-05-30 02:04:34 +00:00
difference ( ) {
2020-12-30 05:02:46 +00:00
round2d ( or = orad [ 0 ] , ir = orad [ 1 ] )
2020-05-30 02:04:34 +00:00
offset ( delta = thickness [ 1 ] )
children ( ) ;
2020-12-30 05:02:46 +00:00
round2d ( or = irad [ 1 ] , ir = irad [ 0 ] )
2020-05-30 02:04:34 +00:00
offset ( delta = thickness [ 0 ] )
children ( ) ;
}
2020-02-01 01:03:41 +00:00
}
2020-12-30 05:02:46 +00:00
// Module: offset3d()
2020-09-14 04:50:39 +00:00
// Usage:
2020-12-30 05:02:46 +00:00
// offset3d(r, [size], [convexity]);
2020-09-14 04:50:39 +00:00
// Description:
2020-12-30 05:02:46 +00:00
// Expands or contracts the surface of a 3D object by a given amount. This is very, very slow.
// No really, this is unbearably slow. It uses `minkowski()`. Use this as a last resort.
// This is so slow that no example images will be rendered.
// Arguments:
// r = Radius to expand object by. Negative numbers contract the object.
// size = Maximum size of object to be contracted, given as a scalar. Default: 100
// convexity = Max number of times a line could intersect the walls of the object. Default: 10
module offset3d ( r = 1 , size = 100 , convexity = 10 ) {
n = quant ( max ( 8 , segs ( abs ( r ) ) ) , 4 ) ;
if ( r = = 0 ) {
children ( ) ;
} else if ( r > 0 ) {
render ( convexity = convexity )
minkowski ( ) {
children ( ) ;
sphere ( r , $fn = n ) ;
}
} else {
size2 = size * [ 1 , 1 , 1 ] ;
size1 = size2 * 1.02 ;
render ( convexity = convexity )
difference ( ) {
cube ( size2 , center = true ) ;
2020-09-14 04:50:39 +00:00
minkowski ( ) {
difference ( ) {
2020-12-30 05:02:46 +00:00
cube ( size1 , center = true ) ;
children ( ) ;
2020-09-14 04:50:39 +00:00
}
2020-12-30 05:02:46 +00:00
sphere ( - r , $fn = n ) ;
2020-09-14 04:50:39 +00:00
}
}
}
}
2020-12-30 05:02:46 +00:00
// Module: round3d()
// Usage:
// round3d(r) ...
// round3d(or) ...
// round3d(ir) ...
// round3d(or, ir) ...
// Description:
// Rounds arbitrary 3D objects. Giving `r` rounds all concave and convex corners. Giving just `ir`
// rounds just concave corners. Giving just `or` rounds convex corners. Giving both `ir` and `or`
// can let you round to different radii for concave and convex corners. The 3D object must not have
// any parts narrower than twice the `or` radius. Such parts will disappear. This is an *extremely*
// slow operation. I cannot emphasize enough just how slow it is. It uses `minkowski()` multiple times.
// Use this as a last resort. This is so slow that no example images will be rendered.
// Arguments:
// r = Radius to round all concave and convex corners to.
// or = Radius to round only outside (convex) corners to. Use instead of `r`.
// ir = Radius to round only inside (concave) corners to. Use instead of `r`.
module round3d ( r , or , ir , size = 100 )
{
or = get_radius ( r1 = or , r = r , dflt = 0 ) ;
ir = get_radius ( r1 = ir , r = r , dflt = 0 ) ;
offset3d ( or , size = size )
offset3d ( - ir - or , size = size )
offset3d ( ir , size = size )
children ( ) ;
}
2020-09-14 04:50:39 +00:00
2020-02-01 01:03:41 +00:00
//////////////////////////////////////////////////////////////////////
// Section: Colors
//////////////////////////////////////////////////////////////////////
// Function&Module: HSL()
// Usage:
// HSL(h,[s],[l],[a]) ...
// rgb = HSL(h,[s],[l]);
// Description:
// When called as a function, returns the [R,G,B] color for the given hue `h`, saturation `s`, and lightness `l` from the HSL colorspace.
// When called as a module, sets the color to the given hue `h`, saturation `s`, and lightness `l` from the HSL colorspace.
// Arguments:
// h = The hue, given as a value between 0 and 360. 0=red, 60=yellow, 120=green, 180=cyan, 240=blue, 300=magenta.
// s = The saturation, given as a value between 0 and 1. 0 = grayscale, 1 = vivid colors. Default: 1
// l = The lightness, between 0 and 1. 0 = black, 0.5 = bright colors, 1 = white. Default: 0.5
// a = When called as a module, specifies the alpha channel as a value between 0 and 1. 0 = fully transparent, 1=opaque. Default: 1
// Example:
// HSL(h=120,s=1,l=0.5) sphere(d=60);
// Example:
// rgb = HSL(h=270,s=0.75,l=0.6);
// color(rgb) cube(60, center=true);
function HSL ( h , s = 1 , l = 0.5 ) =
2020-05-30 02:04:34 +00:00
let (
h = posmod ( h , 360 )
) [
for ( n = [ 0 , 8 , 4 ] ) let (
k = ( n + h / 30 ) % 12
) l - s * min ( l , 1 - l ) * max ( min ( k - 3 , 9 - k , 1 ) , - 1 )
] ;
2020-02-01 01:03:41 +00:00
module HSL ( h , s = 1 , l = 0.5 , a = 1 ) color ( HSL ( h , s , l ) , a ) children ( ) ;
// Function&Module: HSV()
// Usage:
// HSV(h,[s],[v],[a]) ...
// rgb = HSV(h,[s],[v]);
// Description:
// When called as a function, returns the [R,G,B] color for the given hue `h`, saturation `s`, and value `v` from the HSV colorspace.
// When called as a module, sets the color to the given hue `h`, saturation `s`, and value `v` from the HSV colorspace.
// Arguments:
// h = The hue, given as a value between 0 and 360. 0=red, 60=yellow, 120=green, 180=cyan, 240=blue, 300=magenta.
// s = The saturation, given as a value between 0 and 1. 0 = grayscale, 1 = vivid colors. Default: 1
// v = The value, between 0 and 1. 0 = darkest black, 1 = bright. Default: 1
// a = When called as a module, specifies the alpha channel as a value between 0 and 1. 0 = fully transparent, 1=opaque. Default: 1
// Example:
// HSV(h=120,s=1,v=1) sphere(d=60);
// Example:
// rgb = HSV(h=270,s=0.75,v=0.9);
// color(rgb) cube(60, center=true);
function HSV ( h , s = 1 , v = 1 ) =
2021-07-30 20:38:25 +00:00
assert ( s >= 0 && s < = 1 )
assert ( v >= 0 && v < = 1 )
2020-05-30 02:04:34 +00:00
let (
2021-07-30 20:38:25 +00:00
h = posmod ( h , 360 ) ,
c = v * s ,
hprime = h / 60 ,
x = c * ( 1 - abs ( hprime % 2 - 1 ) ) ,
rgbprime = hprime < = 1 ? [ c , x , 0 ]
: hprime < = 2 ? [ x , c , 0 ]
: hprime < = 3 ? [ 0 , c , x ]
: hprime < = 4 ? [ 0 , x , c ]
: hprime < = 5 ? [ x , 0 , c ]
: hprime < = 6 ? [ c , 0 , x ]
: [ 0 , 0 , 0 ] ,
m = v - c
)
rgbprime + [ m , m , m ] ;
2020-02-01 01:03:41 +00:00
module HSV ( h , s = 1 , v = 1 , a = 1 ) color ( HSV ( h , s , v ) , a ) children ( ) ;
// Module: rainbow()
// Usage:
// rainbow(list) ...
// Description:
// Iterates the list, displaying children in different colors for each list item.
// This is useful for debugging lists of paths and such.
// Arguments:
// list = The list of items to iterate through.
// stride = Consecutive colors stride around the color wheel divided into this many parts.
2021-09-21 23:19:02 +00:00
// maxhues = max number of hues to use (to prevent lots of indistinguishable hues)
2021-10-10 01:44:26 +00:00
// shuffle = if true then shuffle the hues in a random order. Default: false
// seed = seed to use for shuffle
2020-02-01 01:03:41 +00:00
// Side Effects:
// Sets the color to progressive values along the ROYGBIV spectrum for each item.
// Sets `$idx` to the index of the current item in `list` that we want to show.
// Sets `$item` to the current item in `list` that we want to show.
// Example(2D):
// rainbow(["Foo","Bar","Baz"]) fwd($idx*10) text(text=$item,size=8,halign="center",valign="center");
// Example(2D):
// rgn = [circle(d=45,$fn=3), circle(d=75,$fn=4), circle(d=50)];
// rainbow(rgn) stroke($item, closed=true);
2021-10-10 01:44:26 +00:00
module rainbow ( list , stride = 1 , maxhues , shuffle = false , seed )
2020-02-01 01:03:41 +00:00
{
2020-05-30 02:04:34 +00:00
ll = len ( list ) ;
2021-09-21 23:19:02 +00:00
maxhues = first_defined ( [ maxhues , ll ] ) ;
huestep = 360 / maxhues ;
2021-10-10 01:44:26 +00:00
huelist = [ for ( i = [ 0 : 1 : ll - 1 ] ) posmod ( i * huestep + i * 360 / stride , 360 ) ] ;
hues = shuffle ? shuffle ( huelist , seed = seed ) : huelist ;
2020-05-30 02:04:34 +00:00
for ( $ idx = idx ( list ) ) {
$ item = list [ $ idx ] ;
HSV ( h = hues [ $ idx ] ) children ( ) ;
}
2020-02-01 01:03:41 +00:00
}
2020-05-30 02:04:34 +00:00
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap