diff --git a/README.md b/README.md index 23aa93a..99f9ace 100644 --- a/README.md +++ b/README.md @@ -14,13 +14,17 @@ For purposes of the BOSL library, the following terms apply: - **Bottom**/**Down**/**Below**: Towards Z- - **Top**/**Up**/**Above**: Towards Z+ -- **Axis-Positive**: Towards the negative end of the axis the object is oriented on. IE: X-, Y-, or Z-. +- **Axis-Positive**: Towards the positive end of the axis the object is oriented on. IE: X+, Y+, or Z+. - **Axis-Negative**: Towards the negative end of the axis the object is oriented on. IE: X-, Y-, or Z-. -- **Fillet**: A rounding of an interior or exterior edge. -- **Chamfer**: A bevelling of an interior or exterior edge. -- **Orientation**: The axis a part should be oriented to. -- **Alignment**: The side of the origin that the part should be on. Given as a vector. +## Common Arguments: + +Args | What it is +------- | ---------------------------------------- +fillet | Radius of rounding for interior or exterior edges. +chamfer | Size of chamfers/bevels for interior or exterior edges. +orient | Axis a part should be oriented along. Given as an XYZ triplet of rotation angles. It is recommended that you use the `ORIENT_` constants from `constants.scad`. Default is usually `ORIENT_Z` for vertical orientation. +align | Side of the origin that the part should be on. Given as a vector away from the origin. It is recommended that you use the `V_` constants from `constants.scad`. Default is usually `V_ZERO` for centered. ## Examples diff --git a/beziers.scad b/beziers.scad index 1404399..65747c5 100644 --- a/beziers.scad +++ b/beziers.scad @@ -1,5 +1,11 @@ ////////////////////////////////////////////////////////////////////// -// Bezier functions and modules. +// LibFile: beziers.scad +// Bezier functions and modules. +// To use, add the following lines to the beginning of your file: +// ``` +// include +// use +// ``` ////////////////////////////////////////////////////////////////////// /* @@ -31,12 +37,56 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -include -include -include +include +use +use +use -// Formulae to calculate points on an N-point bezier curve. +// Section: Terminology +// **Polyline**: A series of points joined by straight line segements. +// +// **Bezier Curve**: A mathematical curve that joins two endpoints, following a curve determined by one or more control points. +// +// **Endpoint**: A point that is on the end of a bezier segment. This point lies on the bezier curve. +// +// **Control Point**: A point that influences the shape of the curve that connects two endpoints. This is often *NOT* on the bezier curve. +// +// **Degree**: The number of control points, plus one endpoint, needed to specify a bezier segment. Most beziers are cubic (degree 3). +// +// **Bezier Segment**: A list consisting of an endpoint, one or more control points, and a final endpoint. The number of control points is one less than the degree of the bezier. A cubic (degree 3) bezier segment looks something like: +// `[endpt1, cp1, cp2, endpt2]` +// +// **Bezier Path**: A list of bezier segments flattened out into a list of points, where each segment shares the endpoint of the previous segment as a start point. A cubic Bezier Path looks something like: +// `[endpt1, cp1, cp2, endpt2, cp3, cp4, endpt3]` +// **NOTE**: A bezier path is *NOT* a polyline. It is only the points and controls used to define the curve. +// +// **Spline Steps**: The number of straight-line segments to split a bezier segment into, to approximate the bezier curve. The more spline steps, the closer the approximation will be to the curve, but the slower it will be to generate. Usually defaults to 16. + + +// Section: Functions + +// Function: bez_point() +// Usage: +// bez_point(curve, u) +// Description: +// Formula to calculate points on a bezier curve. The degree of +// the curve, N, is one less than the number of points in `curve`. +// Arguments: +// curve = The list of endpoints and control points for this bezier segment. +// u = The proportion of the way along the curve to find the point of. 0<=`u`<=1 +// Example(2D): Quadratic (Degree 2) Bezier. +// bez = [[0,0], [30,30], [80,0]]; +// trace_bezier(bez, N=len(bez)-1); +// translate(bez_point(bez, 0.3)) color("red") sphere(1); +// Example(2D): Cubic (Degree 3) Bezier +// bez = [[0,0], [5,35], [60,-25], [80,0]]; +// trace_bezier(bez, N=len(bez)-1); +// translate(bez_point(bez, 0.4)) color("red") sphere(1); +// Example(2D): Degree 4 Bezier. +// bez = [[0,0], [5,15], [40,20], [60,-15], [80,0]]; +// trace_bezier(bez, N=len(bez)-1); +// translate(bez_point(bez, 0.8)) color("red") sphere(1); function bez_point(curve,u)= (len(curve) <= 1) ? curve[0] : @@ -45,7 +95,25 @@ function bez_point(curve,u)= u ); -// Takes an array of bezier points and converts it into a 3D polyline. + +// Function: bezier_polyline() +// Usage: +// bezier_polyline(bezier, [splinesteps], [N]) +// Description: +// Takes a bezier path and converts it into a polyline. +// Arguments: +// bezier = A bezier path to approximate. +// splinesteps = Number of straight lines to split each bezier segment into. default=16 +// N = The degree of the bezier curves. Cubic beziers have N=3. Default: 3 +// Example(2D): +// bez = [ +// [0,0], [-5,30], +// [20,60], [50,50], [110,30], +// [60,25], [70,0], [80,-25], +// [80,-50], [50,-50] +// ]; +// trace_polyline(bez, size=1, N=3, showpts=true); +// trace_polyline(bezier_polyline(bez, N=3), size=3); function bezier_polyline(bezier, splinesteps=16, N=3) = concat( [ for ( @@ -60,93 +128,86 @@ function bezier_polyline(bezier, splinesteps=16, N=3) = concat( ); -// Takes a closed 2D bezier path, and creates a 2D polygon from it. -// bezier = array of 2D bezier path points -// splinesteps = number of straight lines to split each bezier segment into. default=16 -// N = number of points in each bezier segment. default=3 (cubic) -module bezier_polygon(bezier, splinesteps=16, N=3) { - polypoints=bezier_polyline(bezier, splinesteps, N); - polygon(points=slice(polypoints, 0, -1)); -} - - -// Generate bezier curve to fillet 2 line segments between 3 points. -// Returns two path points with surrounding cubic (N=3) bezier control points. -function fillet3pts(p0, p1, p2, r) = let( +// Function: fillet3pts() +// Usage: +// fillet3pts(p0, p1, p2, r); +// Description: +// Takes three points, defining two line segments, and works out the +// cubic (degree 3) bezier segment (and surrounding control points) +// needed to approximate a rounding of the corner with radius `r`. +// If there isn't room for a radius `r` rounding, uses the largest +// radius that will fit. Returns [cp1, endpt1, cp2, cp3, endpt2, cp4] +// Arguments: +// p0 = The starting point. +// p1 = The middle point. +// p2 = The ending point. +// r = The radius of the fillet/rounding. +// maxerr = Max amount bezier curve should diverge from actual radius curve. Default: 0.1 +// Example(2D): +// p0 = [40, 0]; +// p1 = [0, 0]; +// p2 = [30, 30]; +// trace_polyline([p0,p1,p2], showpts=true, size=0.5, color="green"); +// fbez = fillet3pts(p0,p1,p2, 10); +// trace_bezier(slice(fbez, 1, -2), size=1); +function fillet3pts(p0, p1, p2, r, maxerr=0.1, w=0.5, dw=0.25) = let( v0 = normalize(p0-p1), v1 = normalize(p2-p1), + midv = normalize((v0+v1)/2), a = vector3d_angle(v0,v1), - mr = min(distance(p0,p1), distance(p2,p1))*0.9, - tr = min(r/tan(a/2), mr), - tp0 = p1+v0*tr, - tp1 = p1+v1*tr, - w=-2.7e-5*a*a + 8.5e-3*a - 3e-3, - nw=max(0, w), - cp0 = tp0+nw*(p1-tp0), - cp1 = tp1+nw*(p1-tp1) - ) [tp0, tp0, cp0, cp1, tp1, tp1]; + tanr = min(r/tan(a/2), norm(p0-p1)*0.99, norm(p2-p1)*0.99), + tp0 = p1+v0*tanr, + tp1 = p1+v1*tanr, + cp = p1 + midv * tanr / cos(a/2), + cp0 = lerp(tp0, p1, w), + cp1 = lerp(tp1, p1, w), + cpr = norm(cp-tp0), + bp = bez_point([tp0, cp0, cp1, tp1], 0.5), + tdist = norm(cp-bp) + ) (abs(tdist-cpr) <= maxerr)? [tp0, tp0, cp0, cp1, tp1, tp1] : + (tdist +// ``` +////////////////////////////////////////////////////////////////////// + + +/* +BSD 2-Clause License + +Copyright (c) 2017, Revar Desmera +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +// Section: Functions + + +// Function: default() +// Description: +// Returns the value given as `v` if it is not `undef`. +// Otherwise, returns the value of `dflt`. +// Arguments: +// v = Value to pass through if not `undef`. +// dflt = Value to return if `v` *is* `undef`. +function default(v,dflt=undef) = v==undef? dflt : v; + + +// Function: is_def() +// Description: Returns true if given value is not `undef`. +function is_def(v) = (version_num()>20190000)? !is_undef(v) : (v != undef); + + +// Function: is_str() +// Description: Given a value, returns true if it is a string. +function is_str(v) = (version_num()>20190000)? is_string(v) : (is_def(v) && is_def(len(v)) && (len(str(v,v)) == len(v)*2)); + + +// Function: is_boolean() +// Description: Given a value, returns true if it is a boolean. +function is_boolean(v) = (version_num()>20190000)? is_bool(v) : (!is_str(v) && (str(v) == "true" || str(v) == "false")); + + +// Function: is_scalar() +// Description: Given a value, returns true if it is a scalar number. +function is_scalar(v) = (version_num()>20190000)? is_num(v) : (!is_boolean(v) && is_def(v+0)); + + +// Function: is_array() +// Description: Given a value, returns true if it is an array/list/vector. +function is_array(v) = (version_num()>20190000)? is_list(v) : (!is_str(v) && is_def(len(v))); + + +// Function: get_radius() +// Description: +// Given various radii and diameters, returns the most specific radius. +// If a diameter is most specific, returns half its value, giving the radius. +// If no radii or diameters are defined, returns the value of dflt. +// Value specificity order is r1, d1, r, d, then dflt +// Arguments: +// r1 = Most specific radius. +// d1 = Most specific Diameter. +// r = Most general radius. +// d = Most general diameter. +// dflt = Value to return if all other values given are `undef`. +function get_radius(r1=undef, r=undef, d1=undef, d=undef, dflt=undef) = ( + is_def(r1)? r1 : + is_def(d1)? d1/2 : + is_def(r)? r : + is_def(d)? d/2 : + dflt +); + + +// Function: Len() +// Description: +// Given an array, returns number of items in array. Otherwise returns `undef`. +function Len(v) = is_array(v)? len(v) : undef; + + +// Function: remove_undefs() +// Description: Removes all `undef`s from a list. +function remove_undefs(v) = [for (x = v) if (is_def(x)) x]; + + +// Function: first_defined() +// Description: +// Returns the first item in the list that is not `undef`. +// If all items are `undef`, or list is empty, returns `undef`. +function first_defined(v) = remove_undefs(v)[0]; + + +// Function: any_defined() +// Description: +// Returns true if any item in the given array is not `undef`. +function any_defined(v) = len(remove_undefs(v))>0; + + +// Function: scalar_vec3() +// Usage: +// scalar_vec3(v, [dflt]); +// Description: +// If `v` is a scalar, and `dflt==undef`, returns `[v, v, v]`. +// If `v` is a scalar, and `dflt!=undef`, returns `[v, dflt, dflt]`. +// If `v` is a vector, returns the first 3 items, with any missing values replaced by `dflt`. +// If `v` is `undef`, returns `undef`. +// Arguments: +// v = Value to return vector from. +// dflt = Default value to set empty vector parts from. +function scalar_vec3(v, dflt=undef) = + !is_def(v)? undef : + is_array(v)? [for (i=[0:2]) default(v[i], default(dflt, 0))] : + is_def(dflt)? [v,dflt,dflt] : [v,v,v]; + + + +// Section: Modules + + +// Module: assert_in_list() +// Usage: +// assert_in_list(argname, val, l, [idx]); +// Description: +// Emulates the newer OpenSCAD `assert()` with an `in_list()` test. +// Arguments: +// argname = The name of the argument value being tested. +// val = The value to test if it exists in the list. +// l = The list to look for `val` in. +// idx = If given, and `l` is a list of lists, look for `val` in the given index of each sublist. +module assert_in_list(argname, val, l, idx=undef) { + succ = search([val], l, num_returns_per_match=1, index_col_num=idx) != [[]]; + if (!succ) { + msg = str( + "In argument '", argname, "', ", + (is_string(val)? str("\"", val, "\"") : val), + " must be one of ", + (is_def(idx)? [for (v=l) v[idx]] : l) + ); + assertion(succ, msg); + } +} + + +// Module: assertion() +// Usage: +// assertion(succ, msg); +// Description: +// Backwards compatible assert() semi-replacement. If `succ` is false, then print an error with `msg`. +// Arguments: +// succ = If this is `false`, trigger the assertion. +// msg = The message to emit if `succ` is `false`. +module assertion(succ, msg) { + if (version_num() > 20190000) { + // assert() will echo the variable name, and `succ` looks confusing there. So we store it in FAILED. + FAILED = succ; + assert(FAILED, msg); + } else if (!succ) { + echo_error(msg); + } +} + + +// Module: echo_error() +// Usage: +// echo_error(msg, [pfx]); +// Description: Emulates printing of an error message. The text will be shaded red. +// Arguments: +// msg = The message to print. +// pfx = The prefix to print before `msg`. Default: `ERROR` +module echo_error(msg, pfx="ERROR") { + echo(str("

", pfx, ": ", msg, "

")); +} + + +// Module: echo_warning() +// Usage: +// echo_warning(msg, [pfx]); +// Description: Emulates printing of a warning message. The text will be shaded yellow. +// Arguments: +// msg = The message to print. +// pfx = The prefix to print before `msg`. Default: `WARNING` +module echo_warning(msg, pfx="WARNING") { + echo(str("

", pfx, ": ", msg, "

")); +} + + +// Module: deprecate() +// Usage: +// deprecate(name, [suggest]); +// Description: Show module deprecation warnings. +// Arguments: +// name = The name of the module that is deprecated. +// suggest = If given, the module to recommend using instead. +module deprecate(name, suggest=undef) { + echo_warning(pfx="DEPRECATED", + str( + "`", name, "` is deprecated and should not be used.", + !is_def(suggest)? "" : str( + " You should use `", suggest, "` instead." + ) + ) + ); +} + + +// Module: deprecate_argument() +// Usage: +// deprecate(name, arg, [suggest]); +// Description: Show module argument deprecation warnings. +// Arguments: +// name = The name of the module the deprecated argument is used in. +// arg = The name of the deprecated argument. +// suggest = If given, the argument to recommend using instead. +module deprecate_argument(name, arg, suggest=undef) { + echo(str( + "

", + "DEPRECATED ARGUMENT: In `", name, "`, ", + "the argument `", arg, "` ", + "is deprecated and should not be used.", + !is_def(suggest)? "" : str( + " You should use `", suggest, "` instead." + ), + "

" + )); +} + + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/constants.scad b/constants.scad index 707aeac..e5a7e7e 100644 --- a/constants.scad +++ b/constants.scad @@ -1,5 +1,10 @@ ////////////////////////////////////////////////////////////////////// -// Useful Constants +// LibFile: constants.scad +// Useful Constants. +// To use this, add the following line to the top of your file. +// ``` +// include +// ``` ////////////////////////////////////////////////////////////////////// /* @@ -31,79 +36,273 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -// Vectors useful for mirror(), offsetcube(), rotate(), etc. +// Section: General Constants + +PRINTER_SLOP = 0.20; // The printer specific amount of slop in mm to print with to make parts fit exactly. You may need to override this value for your printer. + + + +// Section: Directional Vectors +// Vectors useful for `rotate()`, `mirror()`, and `align` arguments for `cuboid()`, `cyl()`, etc. + +// Constant: V_LEFT +// Description: Vector pointing left. [-1,0,0] +// Example(3D): Usage with `align` +// cuboid(20, align=V_LEFT); V_LEFT = [-1, 0, 0]; + +// Constant: V_RIGHT +// Description: Vector pointing right. [1,0,0] +// Example(3D): Usage with `align` +// cuboid(20, align=V_RIGHT); V_RIGHT = [ 1, 0, 0]; + +// Constant: V_FWD +// Description: Vector pointing forward. [0,-1,0] +// Example(3D): Usage with `align` +// cuboid(20, align=V_FWD); V_FWD = [ 0, -1, 0]; + +// Constant: V_BACK +// Description: Vector pointing back. [0,1,0] +// Example(3D): Usage with `align` +// cuboid(20, align=V_BACK); V_BACK = [ 0, 1, 0]; + +// Constant: V_DOWN +// Description: Vector pointing down. [0,0,-1] +// Example(3D): Usage with `align` +// cuboid(20, align=V_DOWN); V_DOWN = [ 0, 0, -1]; + +// Constant: V_UP +// Description: Vector pointing up. [0,0,1] +// Example(3D): Usage with `align` +// cuboid(20, align=V_UP); V_UP = [ 0, 0, 1]; -V_ZERO = [ 0, 0, 0]; + +// Constant: V_ALLPOS +// Description: Vector pointing right, back, and up. [1,1,1] +// Example(3D): Usage with `align` +// cuboid(20, align=V_ALLPOS); +V_ALLPOS = [ 1, 1, 1]; // Vector pointing X+,Y+,Z+. + +// Constant: V_ALLNEG +// Description: Vector pointing left, forwards, and down. [-1,-1,-1] +// Example(3D): Usage with `align` +// cuboid(20, align=V_ALLNEG); +V_ALLNEG = [-1, -1, -1]; // Vector pointing X-,Y-,Z-. + +// Constant: V_ZERO +// Description: Zero vector. Centered. [0,0,0] +// Example(3D): Usage with `align` +// cuboid(20, align=V_ZERO); +V_ZERO = [ 0, 0, 0]; // Centered zero vector. -// Orientations for cyl(), etc. Euller angles for rotating a vertical shape into the given orientations. -ORIENT_X = [ 0, 90, 0]; -ORIENT_Y = [-90, 0, 0]; -ORIENT_Z = [ 0, 0, 0]; -ORIENT_XNEG = [ 0,-90, 0]; -ORIENT_YNEG = [ 90, 0, 0]; -ORIENT_ZNEG = [ 0,180, 0]; +// Section: Vector Aliases +// Useful aliases for use with `align`. + +V_CENTER = V_ZERO; // Centered, alias to `V_ZERO`. +V_ABOVE = V_UP; // Vector pointing up, alias to `V_UP`. +V_BELOW = V_DOWN; // Vector pointing down, alias to `V_DOWN`. +V_BEFORE = V_FWD; // Vector pointing forward, alias to `V_FWD`. +V_BEHIND = V_BACK; // Vector pointing back, alias to `V_BACK`. + +V_TOP = V_UP; // Vector pointing up, alias to `V_UP`. +V_BOTTOM = V_DOWN; // Vector pointing down, alias to `V_DOWN`. +V_FRONT = V_FWD; // Vector pointing forward, alias to `V_FWD`. +V_REAR = V_BACK; // Vector pointing back, alias to `V_BACK`. -// Constants for defining edges for chamfer(), etc. -EDGE_TOP_BK = [[1,0,0,0], [0,0,0,0], [0,0,0,0]]; -EDGE_TOP_FR = [[0,1,0,0], [0,0,0,0], [0,0,0,0]]; -EDGE_BOT_FR = [[0,0,1,0], [0,0,0,0], [0,0,0,0]]; -EDGE_BOT_BK = [[0,0,0,1], [0,0,0,0], [0,0,0,0]]; -EDGE_TOP_RT = [[0,0,0,0], [1,0,0,0], [0,0,0,0]]; -EDGE_TOP_LF = [[0,0,0,0], [0,1,0,0], [0,0,0,0]]; -EDGE_BOT_LF = [[0,0,0,0], [0,0,1,0], [0,0,0,0]]; -EDGE_BOT_RT = [[0,0,0,0], [0,0,0,1], [0,0,0,0]]; - -EDGE_BK_RT = [[0,0,0,0], [0,0,0,0], [1,0,0,0]]; -EDGE_BK_LF = [[0,0,0,0], [0,0,0,0], [0,1,0,0]]; -EDGE_FR_LF = [[0,0,0,0], [0,0,0,0], [0,0,1,0]]; -EDGE_FR_RT = [[0,0,0,0], [0,0,0,0], [0,0,0,1]]; - -EDGES_X_TOP = [[1,1,0,0], [0,0,0,0], [0,0,0,0]]; -EDGES_X_BOT = [[0,0,1,1], [0,0,0,0], [0,0,0,0]]; -EDGES_X_FR = [[0,1,1,0], [0,0,0,0], [0,0,0,0]]; -EDGES_X_BK = [[1,0,0,1], [0,0,0,0], [0,0,0,0]]; -EDGES_X_ALL = [[1,1,1,1], [0,0,0,0], [0,0,0,0]]; - -EDGES_Y_TOP = [[0,0,0,0], [1,1,0,0], [0,0,0,0]]; -EDGES_Y_BOT = [[0,0,0,0], [0,0,1,1], [0,0,0,0]]; -EDGES_Y_LF = [[0,0,0,0], [0,1,1,0], [0,0,0,0]]; -EDGES_Y_RT = [[0,0,0,0], [1,0,0,1], [0,0,0,0]]; -EDGES_Y_ALL = [[0,0,0,0], [1,1,1,1], [0,0,0,0]]; - -EDGES_Z_BK = [[0,0,0,0], [0,0,0,0], [1,1,0,0]]; -EDGES_Z_FR = [[0,0,0,0], [0,0,0,0], [0,0,1,1]]; -EDGES_Z_LF = [[0,0,0,0], [0,0,0,0], [0,1,1,0]]; -EDGES_Z_RT = [[0,0,0,0], [0,0,0,0], [1,0,0,1]]; -EDGES_Z_ALL = [[0,0,0,0], [0,0,0,0], [1,1,1,1]]; - -EDGES_LEFT = [[0,0,0,0], [0,1,1,0], [0,1,1,0]]; -EDGES_RIGHT = [[0,0,0,0], [1,0,0,1], [1,0,0,1]]; - -EDGES_FRONT = [[0,1,1,0], [0,0,0,0], [0,0,1,1]]; -EDGES_BACK = [[1,0,0,1], [0,0,0,0], [1,1,0,0]]; - -EDGES_BOTTOM = [[0,0,1,1], [0,0,1,1], [0,0,0,0]]; -EDGES_TOP = [[1,1,0,0], [1,1,0,0], [0,0,0,0]]; - -EDGES_NONE = [[0,0,0,0], [0,0,0,0], [0,0,0,0]]; -EDGES_ALL = [[1,1,1,1], [1,1,1,1], [1,1,1,1]]; +// Section: Pre-Orientation Alignments +// Constants for pre-orientation alignments. -EDGE_OFFSETS = [ +// Constant: ALIGN_POS +// Description: Align the axis-positive end to the origin. +// Example(3D): orient=ORIENT_X +// cyl(d1=10, d2=5, h=20, orient=ORIENT_X, align=ALIGN_POS); +// Example(3D): orient=ORIENT_Y +// cyl(d1=10, d2=5, h=20, orient=ORIENT_Y, align=ALIGN_POS); +// Example(3D): orient=ORIENT_Z +// cyl(d1=10, d2=5, h=20, orient=ORIENT_Z, align=ALIGN_POS); +// Example(3D): orient=ORIENT_XNEG +// cyl(d1=10, d2=5, h=20, orient=ORIENT_XNEG, align=ALIGN_POS); +// Example(3D): orient=ORIENT_YNEG +// cyl(d1=10, d2=5, h=20, orient=ORIENT_YNEG, align=ALIGN_POS); +// Example(3D): orient=ORIENT_ZNEG +// cyl(d1=10, d2=5, h=20, orient=ORIENT_ZNEG, align=ALIGN_POS); +ALIGN_POS = 1; + + +ALIGN_CENTER = 0; // Align centered. + +// Constant: ALIGN_NEG +// Description: Align the axis-negative end to the origin. +// Example(3D): orient=ORIENT_X +// cyl(d1=10, d2=5, h=20, orient=ORIENT_X, align=ALIGN_NEG); +// Example(3D): orient=ORIENT_Y +// cyl(d1=10, d2=5, h=20, orient=ORIENT_Y, align=ALIGN_NEG); +// Example(3D): orient=ORIENT_Z +// cyl(d1=10, d2=5, h=20, orient=ORIENT_Z, align=ALIGN_NEG); +// Example(3D): orient=ORIENT_XNEG +// cyl(d1=10, d2=5, h=20, orient=ORIENT_XNEG, align=ALIGN_NEG); +// Example(3D): orient=ORIENT_YNEG +// cyl(d1=10, d2=5, h=20, orient=ORIENT_YNEG, align=ALIGN_NEG); +// Example(3D): orient=ORIENT_ZNEG +// cyl(d1=10, d2=5, h=20, orient=ORIENT_ZNEG, align=ALIGN_NEG); +ALIGN_NEG = -1; + + +// CommonCode: +// orientations = [ +// ORIENT_X, ORIENT_Y, ORIENT_Z, +// ORIENT_XNEG, ORIENT_YNEG, ORIENT_ZNEG, +// ORIENT_X_90, ORIENT_Y_90, ORIENT_Z_90, +// ORIENT_XNEG_90, ORIENT_YNEG_90, ORIENT_ZNEG_90, +// ORIENT_X_180, ORIENT_Y_180, ORIENT_Z_180, +// ORIENT_XNEG_180, ORIENT_YNEG_180, ORIENT_ZNEG_180, +// ORIENT_X_270, ORIENT_Y_270, ORIENT_Z_270, +// ORIENT_XNEG_270, ORIENT_YNEG_270, ORIENT_ZNEG_270 +// ]; +// axiscolors = ["red", "forestgreen", "dodgerblue"]; +// module text3d(text, h=0.01, size=3) { +// linear_extrude(height=h, convexity=10) { +// text(text=text, size=size, valign="center", halign="center"); +// } +// } +// module orient_cube(ang) { +// color("lightgray") cube(20, center=true); +// color(axiscolors.x) up ((20-1)/2+0.01) back ((20-1)/2+0.01) cube([18,1,1], center=true); +// color(axiscolors.y) up ((20-1)/2+0.01) right((20-1)/2+0.01) cube([1,18,1], center=true); +// color(axiscolors.z) back((20-1)/2+0.01) right((20-1)/2+0.01) cube([1,1,18], center=true); +// for (axis=[0:2], neg=[0:1]) { +// idx = axis + 3*neg + 6*ang/90; +// rotate(orientations[idx]) { +// up(10) { +// back(4) color("black") text3d(text=str(ang), size=4); +// fwd(4) color(axiscolors[axis]) text3d(text=str(["X","Y","Z"][axis], ["+","NEG"][neg]), size=4); +// } +// } +// } +// } + + +// Section: Standard Orientations +// Orientations for `cyl()`, `prismoid()`, etc. They take the form of standard [X,Y,Z] +// rotation angles for rotating a vertical shape into the given orientations. +// Figure(Spin): Standard Orientations +// orient_cube(0); +ORIENT_X = [ 90, 0, 90]; // Orient along the X axis. +ORIENT_Y = [ 90, 0, 180]; // Orient along the Y axis. +ORIENT_Z = [ 0, 0, 0]; // Orient along the Z axis. +ORIENT_XNEG = [ 90, 0, -90]; // Orient reversed along the X axis. +ORIENT_YNEG = [ 90, 0, 0]; // Orient reversed along the Y axis. +ORIENT_ZNEG = [ 0, 180, 0]; // Orient reversed along the Z axis. + +// Section: Orientations Rotated 90º +// Orientations for `cyl()`, `prismoid()`, etc. They take the form of standard [X,Y,Z] +// rotation angles for rotating a vertical shape into the given orientations. +// Figure(Spin): Orientations Rotated 90º +// orient_cube(90); +ORIENT_X_90 = [ 90, -90, 90]; // Orient along the X axis, then rotate 90 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. +ORIENT_Y_90 = [ 90, -90, 180]; // Orient along the Y axis, then rotate 90 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. +ORIENT_Z_90 = [ 0, 0, 90]; // Orient along the Z axis, then rotate 90 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. +ORIENT_XNEG_90 = [ 0, -90, 0]; // Orient reversed along the X axis, then rotate 90 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. +ORIENT_YNEG_90 = [ 90, -90, 0]; // Orient reversed along the Y axis, then rotate 90 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. +ORIENT_ZNEG_90 = [ 0, 180, -90]; // Orient reversed along the Z axis, then rotate 90 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. + +// Section: Orientations Rotated 180º +// Orientations for `cyl()`, `prismoid()`, etc. They take the form of standard [X,Y,Z] +// rotation angles for rotating a vertical shape into the given orientations. +// Figure(Spin): Orientations Rotated 180º +// orient_cube(180); +ORIENT_X_180 = [-90, 0, -90]; // Orient along the X axis, then rotate 180 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. +ORIENT_Y_180 = [-90, 0, 0]; // Orient along the Y axis, then rotate 180 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. +ORIENT_Z_180 = [ 0, 0, 180]; // Orient along the Z axis, then rotate 180 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. +ORIENT_XNEG_180 = [-90, 0, 90]; // Orient reversed along the X axis, then rotate 180 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. +ORIENT_YNEG_180 = [-90, 0, 180]; // Orient reversed along the Y axis, then rotate 180 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. +ORIENT_ZNEG_180 = [ 0, 180, 180]; // Orient reversed along the Z axis, then rotate 180 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. + +// Section: Orientations Rotated 270º +// Orientations for `cyl()`, `prismoid()`, etc. They take the form of standard [X,Y,Z] +// rotation angles for rotating a vertical shape into the given orientations. +// Figure(Spin): Orientations Rotated 270º +// orient_cube(270); +ORIENT_X_270 = [ 90, 90, 90]; // Orient along the X axis, then rotate 270 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. +ORIENT_Y_270 = [ 90, 90, 180]; // Orient along the Y axis, then rotate 270 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. +ORIENT_Z_270 = [ 0, 0, -90]; // Orient along the Z axis, then rotate 270 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. +ORIENT_XNEG_270 = [ 90, 90, -90]; // Orient reversed along the X axis, then rotate 270 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. +ORIENT_YNEG_270 = [ 90, 90, 0]; // Orient reversed along the Y axis, then rotate 270 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. +ORIENT_ZNEG_270 = [ 0, 180, 90]; // Orient reversed along the Z axis, then rotate 270 degrees counter-clockwise on that axis, as seen when facing the origin from that axis orientation. + + +// Section: Individual Edges +// Constants for specifying edges for `cuboid()`, etc. + +EDGE_TOP_BK = [[1,0,0,0], [0,0,0,0], [0,0,0,0]]; // Top Back edge. +EDGE_TOP_FR = [[0,1,0,0], [0,0,0,0], [0,0,0,0]]; // Top Front edge. +EDGE_BOT_FR = [[0,0,1,0], [0,0,0,0], [0,0,0,0]]; // Bottom Front Edge. +EDGE_BOT_BK = [[0,0,0,1], [0,0,0,0], [0,0,0,0]]; // Bottom Back Edge. + +EDGE_TOP_RT = [[0,0,0,0], [1,0,0,0], [0,0,0,0]]; // Top Right edge. +EDGE_TOP_LF = [[0,0,0,0], [0,1,0,0], [0,0,0,0]]; // Top Left edge. +EDGE_BOT_LF = [[0,0,0,0], [0,0,1,0], [0,0,0,0]]; // Bottom Left edge. +EDGE_BOT_RT = [[0,0,0,0], [0,0,0,1], [0,0,0,0]]; // Bottom Right edge. + +EDGE_BK_RT = [[0,0,0,0], [0,0,0,0], [1,0,0,0]]; // Back Right edge. +EDGE_BK_LF = [[0,0,0,0], [0,0,0,0], [0,1,0,0]]; // Back Left edge. +EDGE_FR_LF = [[0,0,0,0], [0,0,0,0], [0,0,1,0]]; // Front Left edge. +EDGE_FR_RT = [[0,0,0,0], [0,0,0,0], [0,0,0,1]]; // Front Right edge. + +// Section: Sets of Edges +// Constants for specifying edges for `cuboid()`, etc. + +EDGES_X_TOP = [[1,1,0,0], [0,0,0,0], [0,0,0,0]]; // Both X-aligned Top edges. +EDGES_X_BOT = [[0,0,1,1], [0,0,0,0], [0,0,0,0]]; // Both X-aligned Bottom edges. +EDGES_X_FR = [[0,1,1,0], [0,0,0,0], [0,0,0,0]]; // Both X-aligned Front edges. +EDGES_X_BK = [[1,0,0,1], [0,0,0,0], [0,0,0,0]]; // Both X-aligned Back edges. +EDGES_X_ALL = [[1,1,1,1], [0,0,0,0], [0,0,0,0]]; // All four X-aligned edges. + +EDGES_Y_TOP = [[0,0,0,0], [1,1,0,0], [0,0,0,0]]; // Both Y-aligned Top edges. +EDGES_Y_BOT = [[0,0,0,0], [0,0,1,1], [0,0,0,0]]; // Both Y-aligned Bottom edges. +EDGES_Y_LF = [[0,0,0,0], [0,1,1,0], [0,0,0,0]]; // Both Y-aligned Left edges. +EDGES_Y_RT = [[0,0,0,0], [1,0,0,1], [0,0,0,0]]; // Both Y-aligned Right edges. +EDGES_Y_ALL = [[0,0,0,0], [1,1,1,1], [0,0,0,0]]; // All four Y-aligned edges. + +EDGES_Z_BK = [[0,0,0,0], [0,0,0,0], [1,1,0,0]]; // Both Z-aligned Back edges. +EDGES_Z_FR = [[0,0,0,0], [0,0,0,0], [0,0,1,1]]; // Both Z-aligned Front edges. +EDGES_Z_LF = [[0,0,0,0], [0,0,0,0], [0,1,1,0]]; // Both Z-aligned Left edges. +EDGES_Z_RT = [[0,0,0,0], [0,0,0,0], [1,0,0,1]]; // Both Z-aligned Right edges. +EDGES_Z_ALL = [[0,0,0,0], [0,0,0,0], [1,1,1,1]]; // All four Z-aligned edges. + +EDGES_LEFT = [[0,0,0,0], [0,1,1,0], [0,1,1,0]]; // All four Left edges. +EDGES_RIGHT = [[0,0,0,0], [1,0,0,1], [1,0,0,1]]; // All four Right edges. +EDGES_FRONT = [[0,1,1,0], [0,0,0,0], [0,0,1,1]]; // All four Front edges. +EDGES_BACK = [[1,0,0,1], [0,0,0,0], [1,1,0,0]]; // All four Back edges. +EDGES_BOTTOM = [[0,0,1,1], [0,0,1,1], [0,0,0,0]]; // All four Bottom edges. +EDGES_TOP = [[1,1,0,0], [1,1,0,0], [0,0,0,0]]; // All four Top edges. + +EDGES_NONE = [[0,0,0,0], [0,0,0,0], [0,0,0,0]]; // No edges. +EDGES_ALL = [[1,1,1,1], [1,1,1,1], [1,1,1,1]]; // All edges. + + +// Section: Edge Helpers + +EDGE_OFFSETS = [ // Array of XYZ offsets to the center of each edge. [[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]] ]; +// Function: corner_edge_count() +// 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. function corner_edge_count(edges, v) = (v[2]<=0)? ( (v[1]<=0)? ( @@ -136,4 +335,5 @@ function corner_edge_count(edges, v) = ); + // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/debug.scad b/debug.scad index ff1cb65..dc9504a 100644 --- a/debug.scad +++ b/debug.scad @@ -1,5 +1,11 @@ ////////////////////////////////////////////////////////////////////// -// Helpers to make debugging OpenScad code easier. +// LibFile: debug.scad +// Helpers to make debugging OpenScad code easier. +// To use, add the following lines to the beginning of your file: +// ``` +// include +// use +// ``` ////////////////////////////////////////////////////////////////////// /* @@ -36,76 +42,21 @@ include include -// Renders lines between each point of a polyline path. -// Can also optionally show the individual vertex points. -// pline = the array of points in the polyline. -// showpts = If true, draw vertices and control points. -// N = Mark the first and every Nth vertex after in a different color and shape. -// size = diameter of the lines drawn. -// color = Color to draw the lines (but not vertices) in. -// Example: -// bez = [ -// [-10, 0, 0], [-15, -5, 9], [0, -3, 5], [5, -10, 0], -// [15, 0, -5], [5, 12, -8], [0, 10, -5] -// ]; -// trace_polyline(bez, N=3, showpts=true, size=0.5, color="lightgreen"); -module trace_polyline(pline, N=1, showpts=false, size=1, color="yellow") { - if (showpts) { - for (i = [0:len(pline)-1]) { - translate(pline[i]) { - if (i%N == 0) { - color("blue") sphere(d=size*2.5, $fn=8); - } else { - color("red") { - cylinder(d=size/2, h=size*3, center=true, $fn=8); - xrot(90) cylinder(d=size/2, h=size*3, center=true, $fn=8); - yrot(90) cylinder(d=size/2, h=size*3, center=true, $fn=8); - } - } - } - } - } - for (i = [0:len(pline)-2]) { - if (N!=3 || (i%N) != 1) { - color(color) extrude_from_to(pline[i], pline[i+1]) circle(d=size/2); - } - } -} +// Section: Debugging Polyhedrons -// Renders lines between each point of a polyline path. -// Can also optionally show the individual vertex points. -// bez = the array of points in the bezier. -// N = Mark the first and every Nth vertex after in a different color and shape. -// size = diameter of the lines drawn. -// Example: -// bez = [ -// [-10, 0], [-15, -5], -// [ -5, -10], [ 0, -10], [ 5, -10], -// [ 14, -5], [ 15, 0], [16, 5], -// [ 5, 10], [ 0, 10] -// ]; -// trace_bezier(bez, N=3, size=0.5); -module trace_bezier(bez, N=3, size=1) { - trace_polyline(bez, N=N, showpts=true, size=size/2, color="green"); - trace_polyline(bezier_polyline(bez, N=N), size=size); -} - - -// Draws all the vertices in an array, at their 3D position, numbered by their -// position in the vertex array. Also draws any children of this module with -// transparency. +// Module: debug_vertices() +// Description: +// Draws all the vertices in an array, at their 3D position, numbered by their +// position in the vertex array. Also draws any children of this module with +// transparency. +// Arguments: // vertices = Array of point vertices. // size = The size of the text used to label the vertices. // disabled = If true, don't draw numbers, and draw children without transparency. Default = false. // Example: -// verts = [ -// [-10, 0, -10], [10, 0, -10], -// [0, -10, 10], [0, 10, 10] -// ]; -// faces = [ -// [0,2,1], [1,2,3], [0,3,2], [1,3,0] -// ]; +// verts = [for (z=[-10,10], y=[-10,10], x=[-10,10]) [x,y,z]]; +// faces = [[0,1,2], [1,3,2], [0,4,5], [0,5,1], [1,5,7], [1,7,3], [3,7,6], [3,6,2], [2,6,4], [2,4,0], [4,6,7], [4,7,5]]; // debug_vertices(vertices=verts, size=2) { // polyhedron(points=verts, faces=faces); // } @@ -128,7 +79,7 @@ module debug_vertices(vertices, size=1, disabled=false) { } if ($children > 0) { if (!disabled) { - color([0.5, 0.5, 0, 0.25]) children(); + color([0.2, 1.0, 0, 0.5]) children(); } else { children(); } @@ -137,22 +88,20 @@ module debug_vertices(vertices, size=1, disabled=false) { -// Draws all the vertices at their 3D position, numbered in blue by their -// position in the vertex array. Each face will have their face number drawn -// in red, aligned with the center of face. All children of this module are drawn -// with transparency. +// Module: debug_faces() +// Description: +// Draws all the vertices at their 3D position, numbered in blue by their +// position in the vertex array. Each face will have their face number drawn +// in red, aligned with the center of face. All children of this module are drawn +// with transparency. +// Arguments: // vertices = Array of point vertices. // faces = Array of faces by vertex numbers. // size = The size of the text used to label the faces and vertices. // disabled = If true, don't draw numbers, and draw children without transparency. Default = false. // Example: -// verts = [ -// [-10, 0, -10], [10, 0, -10], -// [0, -10, 10], [0, 10, 10] -// ]; -// faces = [ -// [0,2,1], [1,2,3], [0,3,2], [1,3,0] -// ]; +// verts = [for (z=[-10,10], y=[-10,10], x=[-10,10]) [x,y,z]]; +// faces = [[0,1,2], [1,3,2], [0,4,5], [0,5,1], [1,5,7], [1,7,3], [3,7,6], [3,6,2], [2,6,4], [2,4,0], [4,6,7], [4,7,5]]; // debug_faces(vertices=verts, faces=faces, size=2) { // polyhedron(points=verts, faces=faces); // } @@ -201,20 +150,23 @@ module debug_faces(vertices, faces, size=1, disabled=false) { -// A drop-in module to replace `polyhedron()` and help debug vertices and faces. -// Draws all the vertices at their 3D position, numbered in blue by their -// position in the vertex array. Each face will have their face number drawn -// in red, aligned with the center of face. All given faces are drawn with -// transparency. All children of this module are drawn with transparency. -// Works best with Thrown-Together preview mode, to see reversed faces. +// Module: debug_polyhedron() +// Description: +// A drop-in module to replace `polyhedron()` and help debug vertices and faces. +// Draws all the vertices at their 3D position, numbered in blue by their +// position in the vertex array. Each face will have their face number drawn +// in red, aligned with the center of face. All given faces are drawn with +// transparency. All children of this module are drawn with transparency. +// Works best with Thrown-Together preview mode, to see reversed faces. +// Arguments: // vertices = Array of point vertices. // faces = Array of faces by vertex numbers. // txtsize = The size of the text used to label the faces and vertices. // disabled = If true, act exactly like `polyhedron()`. Default = false. // Example: -// pts = [[-5,0,-5], [5,0,-5], [0,-5,5], [0,5,5]]; -// fcs = [[0,2,1], [1,2,3], [1,3,0], [0,2,3]]; // Last face reversed -// debug_polyhedron(points=pts, faces=fcs, txtsize=1); +// verts = [for (z=[-10,10], a=[0:120:359.9]) [10*cos(a),10*sin(a),z]]; +// faces = [[0,1,2], [5,4,3], [0,3,4], [0,4,1], [1,4,5], [1,5,2], [2,5,3], [2,3,0]]; +// debug_polyhedron(points=verts, faces=faces, txtsize=1); module debug_polyhedron(points, faces, convexity=10, txtsize=1, disabled=false) { debug_faces(vertices=points, faces=faces, size=txtsize, disabled=disabled) { polyhedron(points=points, faces=faces, convexity=convexity); diff --git a/examples/orientations.json b/examples/orientations.json new file mode 100644 index 0000000..4b918ba --- /dev/null +++ b/examples/orientations.json @@ -0,0 +1,11 @@ +{ + "parameterSets": { + "design default values": { + "axiscolors": "[\"red\", \"forestgreen\", \"dodgerblue\"]", + "axisdiam": "0.5", + "axislbllen": "15", + "axislen": "9" + } + }, + "fileFormatVersion": "1" +} diff --git a/examples/orientations.scad b/examples/orientations.scad new file mode 100644 index 0000000..6437ea5 --- /dev/null +++ b/examples/orientations.scad @@ -0,0 +1,83 @@ +use +use +include + +// Shows all the orientations on cubes in their correct rotations. + +orientations = [ + ORIENT_X, ORIENT_Y, ORIENT_Z, + ORIENT_XNEG, ORIENT_YNEG, ORIENT_ZNEG, + + ORIENT_X_90, ORIENT_Y_90, ORIENT_Z_90, + ORIENT_XNEG_90, ORIENT_YNEG_90, ORIENT_ZNEG_90, + + ORIENT_X_180, ORIENT_Y_180, ORIENT_Z_180, + ORIENT_XNEG_180, ORIENT_YNEG_180, ORIENT_ZNEG_180, + + ORIENT_X_270, ORIENT_Y_270, ORIENT_Z_270, + ORIENT_XNEG_270, ORIENT_YNEG_270, ORIENT_ZNEG_270 +]; + + +axisdiam = 0.5; +axislen = 12; +axislbllen = 15; +axiscolors = ["red", "forestgreen", "dodgerblue"]; + +module text3d(text, h=0.01, size=3) { + linear_extrude(height=h, convexity=10) { + text(text=text, size=size, valign="center", halign="center"); + } +} + +module dottedline(l, d) for(y = [0:d*3:l]) up(y) sphere(d=d); + +module orient_cubes() { + color(axiscolors[0]) { + yrot( 90) cylinder(h=axislen, d=axisdiam, center=false); + right(axislbllen) rot([90,0,0]) text3d(text="X+"); + yrot(-90) dottedline(l=axislen, d=axisdiam); + left(axislbllen) rot([90,0,180]) text3d(text="X-"); + } + color(axiscolors[1]) { + xrot(-90) cylinder(h=axislen, d=axisdiam, center=false); + back(axislbllen) rot([90,0,90]) text3d(text="Y+"); + xrot( 90) dottedline(l=axislen, d=axisdiam); + fwd(axislbllen) rot([90,0,-90]) text3d(text="Y-"); + } + color(axiscolors[2]) { + cylinder(h=axislen, d=axisdiam, center=false); + up(axislbllen) rot([0,-90,90+$vpr[2]]) text3d(text="Z+"); + xrot(180) dottedline(l=axislen, d=axisdiam); + down(axislbllen) rot([0,90,-90+$vpr[2]]) text3d(text="Z-"); + } + + + for (ang = [0:90:270]) { + translate(cylindrical_to_xyz(40, ang+90, 0)) { + color("lightgray") cube(20, center=true); + } + } + + for (axis=[0:2], neg=[0:1], ang = [0:90:270]) { + idx = axis + 3*neg + 6*ang/90; + translate(cylindrical_to_xyz(40, ang+90, 0)) { + rotate(orientations[idx]) { + up(10) { + ydistribute(8) { + color("black") text3d(text=str(ang, "º"), size=5); + color(axiscolors[axis]) text3d(text=str(["X","Y","Z"][axis], ["+","-"][neg]), size=5); + } + } + } + } + } +} + + +//rotate(a=180, v=[1,1,0]) +orient_cubes(); + + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/involute_gears.scad b/involute_gears.scad index c626d1b..ff32f04 100644 --- a/involute_gears.scad +++ b/involute_gears.scad @@ -1,70 +1,160 @@ ////////////////////////////////////////////////////////////////////////////////////////////// -// Public Domain Parametric Involute Spur Gear (and involute helical gear and involute rack) -// version 1.1 -// by Leemon Baird, 2011, Leemon@Leemon.com -// http://www.thingiverse.com/thing:5505 -// Tweaked, and improved by Revar Desmera, 2017-2019, revarbat@gmail.com -// -// This file is public domain. Use it for any purpose, including commercial -// applications. Attribution would be nice, but is not required. There is -// no warranty of any kind, including its correctness, usefulness, or safety. -// -// This is parameterized involute spur (or helical) gear. It is much simpler and less powerful than -// others on Thingiverse. But it is public domain. I implemented it from scratch from the -// descriptions and equations on Wikipedia and the web, using Mathematica for calculations and testing, -// and I now release it into the public domain. -// -// http://en.wikipedia.org/wiki/Involute_gear -// http://en.wikipedia.org/wiki/Gear -// http://en.wikipedia.org/wiki/List_of_gear_nomenclature -// http://gtrebaol.free.fr/doc/catia/spur_gear.html -// http://www.cs.cmu.edu/~rapidproto/mechanisms/chpt7.html -// -// The module gear() gives an involute spur gear, with reasonable defaults for all the parameters. -// Normally, you should just choose the first 4 parameters, and let the rest be default values. -// The module gear() gives a gear in the XY plane, centered on the origin, with one tooth centered on -// the positive Y axis. The various functions below it take the same parameters, and return various -// measurements for the gear. The most important is pitch_radius, which tells how far apart to space -// gears that are meshing, and adendum_radius, which gives the size of the region filled by the gear. -// A gear has a "pitch circle", which is an invisible circle that cuts through the middle of each -// tooth (though not the exact center). In order for two gears to mesh, their pitch circles should -// just touch. So the distance between their centers should be pitch_radius() for one, plus pitch_radius() -// for the other, which gives the radii of their pitch circles. -// -// In order for two gears to mesh, they must have the same mm_per_tooth and pressure_angle parameters. -// mm_per_tooth gives the number of millimeters of arc around the pitch circle covered by one tooth and one -// space between teeth. The pitch angle controls how flat or bulged the sides of the teeth are. Common -// values include 14.5 degrees and 20 degrees, and occasionally 25. Though I've seen 28 recommended for -// plastic gears. Larger numbers bulge out more, giving stronger teeth, so 28 degrees is the default here. -// -// The ratio of number_of_teeth for two meshing gears gives how many times one will make a full -// revolution when the the other makes one full revolution. If the two numbers are coprime (i.e. -// are not both divisible by the same number greater than 1), then every tooth on one gear -// will meet every tooth on the other, for more even wear. So coprime numbers of teeth are good. -// -// The module rack() gives a rack, which is a bar with teeth. A rack can mesh with any -// gear that has the same mm_per_tooth and pressure_angle. -// -// Some terminology: -// The outline of a gear is a smooth circle (the "pitch circle") which has mountains and valleys -// added so it is toothed. So there is an inner circle (the "root circle") that touches the -// base of all the teeth, an outer circle that touches the tips of all the teeth, -// and the invisible pitch circle in between them. There is also a "base circle", which can be smaller than -// all three of the others, which controls the shape of the teeth. The side of each tooth lies on the path -// that the end of a string would follow if it were wrapped tightly around the base circle, then slowly unwound. -// That shape is an "involute", which gives this type of gear its name. -// +// LibFile: involute_gears.scad +// Involute Spur Gears and Racks +// +// by Leemon Baird, 2011, Leemon@Leemon.com +// http://www.thingiverse.com/thing:5505 +// +// Additional fixes and improvements by Revar Desmera, 2017-2019, revarbat@gmail.com +// +// This file is public domain. Use it for any purpose, including commercial +// applications. Attribution would be nice, but is not required. There is +// no warranty of any kind, including its correctness, usefulness, or safety. +// +// This is parameterized involute spur (or helical) gear. It is much simpler +// and less powerful than others on Thingiverse. But it is public domain. I +// implemented it from scratch from the descriptions and equations on Wikipedia +// and the web, using Mathematica for calculations and testing, and I now +// release it into the public domain. +// +// To use, add the following line to the beginning of your file: +// ``` +// include +// use +// ``` ////////////////////////////////////////////////////////////////////////////////////////////// -//gear_tooth_profile(mm_per_tooth=5, number_of_teeth=20, pressure_angle=20); +use +include + + +// Section: Terminology +// The outline of a gear is a smooth circle (the "pitch circle") which has +// mountains and valleys added so it is toothed. There is an inner +// circle (the "root circle") that touches the base of all the teeth, an +// outer circle that touches the tips of all the teeth, and the invisible +// pitch circle in between them. There is also a "base circle", which can +// be smaller than all three of the others, which controls the shape of +// the teeth. The side of each tooth lies on the path that the end of a +// string would follow if it were wrapped tightly around the base circle, +// then slowly unwound. That shape is an "involute", which gives this +// type of gear its name. + + +// Section: Functions +// These functions let the user find the derived dimensions of the gear. +// A gear fits within a circle of radius outer_radius, and two gears should have +// their centers separated by the sum of their pitch_radius. + + +// Function: circular_pitch() +// Description: Get tooth density expressed as "circular pitch". +// Arguments: +// mm_per_tooth = Distance between teeth around the pitch circle, in mm. +function circular_pitch(mm_per_tooth=5) = mm_per_tooth; + + +// Function: diametral_pitch() +// Description: Get tooth density expressed as "diametral pitch". +// Arguments: +// mm_per_tooth = Distance between teeth around the pitch circle, in mm. +function diametral_pitch(mm_per_tooth=5) = PI / mm_per_tooth; + + +// Function: module_value() +// Description: Get tooth density expressed as "module" or "modulus" in millimeters +// Arguments: +// mm_per_tooth = Distance between teeth around the pitch circle, in mm. +function module_value(mm_per_tooth=5) = mm_per_tooth / PI; + + +// Function: adendum() +// Description: The height of the gear tooth above the pitch radius. +// Arguments: +// mm_per_tooth = Distance between teeth around the pitch circle, in mm. +function adendum(mm_per_tooth=5) = module_value(mm_per_tooth); + + +// Function: dedendum() +// Description: The depth of the gear tooth valley, below the pitch radius. +// Arguments: +// mm_per_tooth = Distance between teeth around the pitch circle, in mm. +// clearance = If given, sets the clearance between meshing teeth. +function dedendum(mm_per_tooth=5, clearance=undef) = + (clearance==undef)? (1.25 * module_value(mm_per_tooth)) : (module_value(mm_per_tooth) + clearance); + + +// Function: pitch_radius() +// Description: Calculates the pitch radius for the gear. +// Arguments: +// mm_per_tooth = Distance between teeth around the pitch circle, in mm. +// number of teeth = The number of teeth on the gear. +function pitch_radius(mm_per_tooth=5, number_of_teeth=11) = + mm_per_tooth * number_of_teeth / PI / 2; + + +// Function: outer_radius() +// Description: +// Calculates the outer radius for the gear. The gear fits entirely within a cylinder of this radius. +// Arguments: +// mm_per_tooth = Distance between teeth around the pitch circle, in mm. +// number of teeth = The number of teeth on the gear. +// clearance = If given, sets the clearance between meshing teeth. +// interior = If true, calculate for an interior gear. +function outer_radius(mm_per_tooth=5, number_of_teeth=11, clearance=undef, interior=false) = + pitch_radius(mm_per_tooth, number_of_teeth) + + (interior? dedendum(mm_per_tooth, clearance) : adendum(mm_per_tooth)); + + +// Function: root_radius() +// Description: +// Calculates the root radius for the gear, at the base of the dedendum. +// Arguments: +// mm_per_tooth = Distance between teeth around the pitch circle, in mm. +// number of teeth = The number of teeth on the gear. +// clearance = If given, sets the clearance between meshing teeth. +// interior = If true, calculate for an interior gear. +function root_radius(mm_per_tooth=5, number_of_teeth=11, clearance=undef, interior=false) + = pitch_radius(mm_per_tooth, number_of_teeth) - + (interior? adendum(mm_per_tooth) : dedendum(mm_per_tooth, clearance)); + + +// Function: base_radius() +// Description: Get the base circle for involute teeth. +// Arguments: +// mm_per_tooth = Distance between teeth around the pitch circle, in mm. +// number_of_teeth = The number of teeth on the gear. +// pressure_angle = Pressure angle in degrees. Controls how straight or bulged the tooth sides are. +function base_radius(mm_per_tooth=5, number_of_teeth=11, pressure_angle=28) + = pitch_radius(mm_per_tooth, number_of_teeth) * cos(pressure_angle); + + + +// Section: Modules + + +// Module: gear_tooth_profile() +// Description: +// Creates the 2D profile for an individual gear tooth. +// Arguments: +// mm_per_tooth = This is the "circular pitch", the circumference of the pitch circle divided by the number of teeth +// number_of_teeth = Total number of teeth along the rack +// pressure_angle = Controls how straight or bulged the tooth sides are. In degrees. +// backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle +// bevelang = Angle of beveled gear face. +// clearance = Gap between top of a tooth on one gear and bottom of valley on a meshing gear (in millimeters) +// interior = If true, create a mask for difference()ing from something else. +// Example(2D): +// gear_tooth_profile(mm_per_tooth=5, number_of_teeth=20, pressure_angle=20); module gear_tooth_profile( mm_per_tooth = 3, //this is the "circular pitch", the circumference of the pitch circle divided by the number of teeth number_of_teeth = 11, //total number of teeth around the entire perimeter pressure_angle = 28, //Controls how straight or bulged the tooth sides are. In degrees. backlash = 0.0, //gap between two meshing teeth, in the direction along the circumference of the pitch circle bevelang = 0.0, //Gear face angle for bevelled gears. - clearance = undef //gap between top of a tooth on one gear and bottom of valley on a meshing gear (in millimeters) + clearance = undef, //gap between top of a tooth on one gear and bottom of valley on a meshing gear (in millimeters) + interior = false ) { function polar(r,theta) = r*[sin(theta), cos(theta)]; //convert polar to cartesian coordinates function iang(r1,r2) = sqrt((r2/r1)*(r2/r1) - 1)/PI*180 - acos(r1/r2); //unwind a string this many degrees to go from radius r1 to radius r2 @@ -72,8 +162,8 @@ module gear_tooth_profile( function q6(b,s,t,d) = polar(d,s*(iang(b,d)+t)); //point at radius d on the involute curve p = pitch_radius(mm_per_tooth, number_of_teeth); - c = outer_radius(mm_per_tooth, number_of_teeth); - r = root_radius(mm_per_tooth, number_of_teeth, clearance); + c = outer_radius(mm_per_tooth, number_of_teeth, clearance, interior); + r = root_radius(mm_per_tooth, number_of_teeth, clearance, interior); b = base_radius(mm_per_tooth, number_of_teeth, pressure_angle); t = mm_per_tooth/2-backlash/2; //tooth thickness at pitch circle k = -iang(b, p) - t/2/p/PI*180; //angle to where involute meets base circle on each side of tooth @@ -94,19 +184,27 @@ module gear_tooth_profile( } -// Creates a 2D involute spur gear, with reasonable defaults for all the parameters. -// Normally, you should just specify the first 2 parameters, and let the rest be default values. -// Meshing gears must match in mm_per_tooth, pressure_angle, and twist, -// and be separated by the sum of their pitch radii, which can be found with pitch_radius(). +// Module: gear2d() +// Description: +// Creates a 2D involute spur gear, with reasonable defaults for all the parameters. +// Normally, you should just specify the first 2 parameters, and let the rest be default values. +// Meshing gears must match in mm_per_tooth, pressure_angle, and twist, +// and be separated by the sum of their pitch radii, which can be found with pitch_radius(). +// Arguments: // mm_per_tooth = This is the "circular pitch", the circumference of the pitch circle divided by the number of teeth // number_of_teeth = Total number of teeth along the rack // teeth_to_hide = Number of teeth to delete to make this only a fraction of a circle // pressure_angle = Controls how straight or bulged the tooth sides are. In degrees. // clearance = Gap between top of a tooth on one gear and bottom of valley on a meshing gear (in millimeters) // backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle -// Example: +// bevelang = Angle of beveled gear face. +// interior = If true, create a mask for difference()ing from something else. +// Example(2D): Typical Gear Shape // gear2d(mm_per_tooth=5, number_of_teeth=20); -// linear_extrude(height=5*20/PI/2/2, scale=0.5) gear2d(mm_per_tooth=5, number_of_teeth=20); +// Example(2D): Lower Pressure Angle +// gear2d(mm_per_tooth=5, number_of_teeth=20, pressure_angle=20); +// Example(2D): Partial Gear +// gear2d(mm_per_tooth=5, number_of_teeth=20, teeth_to_hide=15, pressure_angle=20); module gear2d( mm_per_tooth = 3, //this is the "circular pitch", the circumference of the pitch circle divided by the number of teeth number_of_teeth = 11, //total number of teeth around the entire perimeter @@ -114,9 +212,10 @@ module gear2d( pressure_angle = 28, //Controls how straight or bulged the tooth sides are. In degrees. clearance = undef, //gap between top of a tooth on one gear and bottom of valley on a meshing gear (in millimeters) backlash = 0.0, //gap between two meshing teeth, in the direction along the circumference of the pitch circle - bevelang = 0.0 + bevelang = 0.0, + interior = false ) { - r = root_radius(mm_per_tooth, number_of_teeth, clearance); + r = root_radius(mm_per_tooth, number_of_teeth, clearance, interior); ang = 360/number_of_teeth/2; union() { for (i = [0:number_of_teeth-teeth_to_hide-1] ) { @@ -128,7 +227,8 @@ module gear2d( pressure_angle = pressure_angle, clearance = clearance, backlash = backlash, - bevelang = bevelang + bevelang = bevelang, + interior = interior ); } polygon([ @@ -142,24 +242,58 @@ module gear2d( } -// Creates an involute spur gear, with reasonable defaults for all the parameters. -// Normally, you should just choose the first 4 parameters, and let the rest be default values. -// Meshing gears must match in mm_per_tooth, pressure_angle, and twist, -// and be separated by the sum of their pitch radii, which can be found with pitch_radius(). -// mm_per_tooth = This is the "circular pitch", the circumference of the pitch circle divided by the number of teeth -// number_of_teeth = Total number of teeth along the rack -// thickness = Thickness of rack in mm (affects each tooth) -// hole_diameter = Diameter of centeral shaft hole. +// Module: gear() +// Description: +// Creates a (potentially helical) involute spur gear. +// The module `gear()` gives an involute spur gear, with reasonable +// defaults for all the parameters. Normally, you should just choose +// the first 4 parameters, and let the rest be default values. The +// module `gear()` gives a gear in the XY plane, centered on the origin, +// with one tooth centered on the positive Y axis. The various functions +// below it take the same parameters, and return various measurements +// for the gear. The most important is `pitch_radius()`, which tells +// how far apart to space gears that are meshing, and `outer_radius()`, +// which gives the size of the region filled by the gear. A gear has +// a "pitch circle", which is an invisible circle that cuts through +// the middle of each tooth (though not the exact center). In order +// for two gears to mesh, their pitch circles should just touch. So +// the distance between their centers should be `pitch_radius()` for +// one, plus `pitch_radius()` for the other, which gives the radii of +// their pitch circles. +// In order for two gears to mesh, they must have the same `mm_per_tooth` +// and `pressure_angle` parameters. `mm_per_tooth` gives the number +// of millimeters of arc around the pitch circle covered by one tooth +// and one space between teeth. The `pressure_angle` controls how flat or +// bulged the sides of the teeth are. Common values include 14.5 +// degrees and 20 degrees, and occasionally 25. Though I've seen 28 +// recommended for plastic gears. Larger numbers bulge out more, giving +// stronger teeth, so 28 degrees is the default here. +// The ratio of `number_of_teeth` for two meshing gears gives how many +// times one will make a full revolution when the the other makes one +// full revolution. If the two numbers are coprime (i.e. are not +// both divisible by the same number greater than 1), then every tooth +// on one gear will meet every tooth on the other, for more even wear. +// So coprime numbers of teeth are good. +// Arguments: +// mm_per_tooth = This is the "circular pitch", the circumference of the pitch circle divided by the number of teeth +// number_of_teeth = Total number of teeth around the entire perimeter +// thickness = Thickness of gear in mm +// hole_diameter = Diameter of the hole in the center, in mm // teeth_to_hide = Number of teeth to delete to make this only a fraction of a circle // pressure_angle = Controls how straight or bulged the tooth sides are. In degrees. -// clearance = Gap between top of a tooth on one gear and bottom of valley on a meshing gear (in millimeters) +// clearance = Clearance gap at the bottom of the inter-tooth valleys. // backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle -// twist = Teeth rotate this many degrees from bottom of gear to top. 360 makes the gear a screw with each thread going around once +// bevelang = Angle of beveled gear face. +// twist = Teeth rotate this many degrees from bottom of gear to top. 360 makes the gear a screw with each thread going around once. +// slices = Number of vertical layers to divide gear into. Useful for refining gears with `twist`. // scale = Scale of top of gear compared to bottom. Useful for making crown gears. -// slices = Number of slices to divide gear into. Useful for refining gears with `twist`. -// Example: +// interior = If true, create a mask for difference()ing from something else. +// orient = Orientation of the gear. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the gear. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// Example: Spur Gear +// gear(mm_per_tooth=5, number_of_teeth=20, thickness=8, hole_diameter=5); +// Example: Beveled Gear // gear(mm_per_tooth=5, number_of_teeth=20, thickness=10*cos(45), hole_diameter=5, twist=-30, bevelang=45, slices=12, $fa=1, $fs=1); -// gear(mm_per_tooth=5, number_of_teeth=20, thickness=8, hole_diameter=5, $fa=1, $fs=1); module gear( mm_per_tooth = 3, //this is the "circular pitch", the circumference of the pitch circle divided by the number of teeth number_of_teeth = 11, //total number of teeth around the entire perimeter @@ -171,33 +305,39 @@ module gear( backlash = 0.0, //gap between two meshing teeth, in the direction along the circumference of the pitch circle bevelang = 0.0, //angle of bevelled gear face. twist = undef, //teeth rotate this many degrees from bottom of gear to top. 360 makes the gear a screw with each thread going around once - slices = undef //Number of slices to divide gear into. Useful for refining gears with `twist`. + slices = undef, //Number of slices to divide gear into. Useful for refining gears with `twist`. + interior = false, + orient = ORIENT_Z, + align = V_CENTER ) { p = pitch_radius(mm_per_tooth, number_of_teeth); - c = outer_radius(mm_per_tooth, number_of_teeth); - r = root_radius(mm_per_tooth, number_of_teeth, clearance); + c = outer_radius(mm_per_tooth, number_of_teeth, clearance, interior); + r = root_radius(mm_per_tooth, number_of_teeth, clearance, interior); p2 = p - (thickness*tan(bevelang)); - difference() { - linear_extrude(height=thickness, center=true, convexity=10, twist=twist, scale=p2/p, slices=slices) { - gear2d( - mm_per_tooth = mm_per_tooth, - number_of_teeth = number_of_teeth, - teeth_to_hide = teeth_to_hide, - pressure_angle = pressure_angle, - clearance = clearance, - backlash = backlash, - bevelang = bevelang - ); - } - if (hole_diameter > 0) { - cylinder(h=2*thickness+1, r=hole_diameter/2, center=true); - } - if (bevelang != 0) { - h = (c-r)*sin(bevelang); - translate([0,0,-thickness/2]) { - difference() { - cube([2*c/cos(bevelang),2*c/cos(bevelang),2*h], center=true); - cylinder(h=h, r1=r, r2=c, center=false); + orient_and_align([p, p, thickness], orient, align) { + difference() { + linear_extrude(height=thickness, center=true, convexity=10, twist=twist, scale=p2/p, slices=slices) { + gear2d( + mm_per_tooth = mm_per_tooth, + number_of_teeth = number_of_teeth, + teeth_to_hide = teeth_to_hide, + pressure_angle = pressure_angle, + clearance = clearance, + backlash = backlash, + bevelang = bevelang, + interior = interior + ); + } + if (hole_diameter > 0) { + cylinder(h=2*thickness+1, r=hole_diameter/2, center=true); + } + if (bevelang != 0) { + h = (c-r)*sin(bevelang); + translate([0,0,-thickness/2]) { + difference() { + cube([2*c/cos(bevelang),2*c/cos(bevelang),2*h], center=true); + cylinder(h=h, r1=r, r2=c, center=false); + } } } } @@ -205,17 +345,22 @@ module gear( } -// Creates a rack, which is a straight line with teeth. -// The same as a segment of teeth from an infinite diameter gear. -// The "pitch circle" is a line along the X axis. +// Module: rack() +// Description: +// The module `rack()` gives a rack, which is a bar with teeth. A +// rack can mesh with any gear that has the same `mm_per_tooth` and +// `pressure_angle`. +// Arguments: // mm_per_tooth = This is the "circular pitch", the circumference of the pitch circle divided by the number of teeth // number_of_teeth = Total number of teeth along the rack // thickness = Thickness of rack in mm (affects each tooth) // height = Height of rack in mm, from tooth top to back of rack. // pressure_angle = Controls how straight or bulged the tooth sides are. In degrees. // backlash = Gap between two meshing teeth, in the direction along the circumference of the pitch circle +// orient = Orientation of the rack. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_X`. +// align = Alignment of the rack. Use the `V_` constants from `constants.scad`. Default: `V_RIGHT`. // Example: -// rack(mm_per_tooth=5, number_of_teeth=30, thickness=5, height=5, pressure_angle=20); +// rack(mm_per_tooth=5, number_of_teeth=10, thickness=5, height=5, pressure_angle=20); module rack( mm_per_tooth = 5, //this is the "circular pitch", the circumference of the pitch circle divided by the number of teeth number_of_teeth = 20, //total number of teeth along the rack @@ -223,57 +368,39 @@ module rack( height = 10, //height of rack in mm, from tooth top to back of rack. pressure_angle = 28, //Controls how straight or bulged the tooth sides are. In degrees. backlash = 0.0, //gap between two meshing teeth, in the direction along the circumference of the pitch circle - clearance = undef + clearance = undef, + orient = ORIENT_X, + align = V_RIGHT ) { a = adendum(mm_per_tooth); d = dedendum(mm_per_tooth, clearance); xa = a * sin(pressure_angle); xd = d * sin(pressure_angle); - linear_extrude(height = thickness, center = true, convexity = 10) { - for (i = [0:number_of_teeth-1] ) { - translate([i*mm_per_tooth,0,0]) { - polygon( - points=[ - [-1/2 * mm_per_tooth - 0.01, a-height], - [-1/2 * mm_per_tooth, -d], - [-1/4 * mm_per_tooth + backlash - xd, -d], - [-1/4 * mm_per_tooth + backlash + xa, a], - [ 1/4 * mm_per_tooth - backlash - xa, a], - [ 1/4 * mm_per_tooth - backlash + xd, -d], - [ 1/2 * mm_per_tooth, -d], - [ 1/2 * mm_per_tooth + 0.01, a-height], - ] - ); + orient_and_align([(number_of_teeth-1)*mm_per_tooth, height, thickness], orient, align, orig_orient=ORIENT_X) { + left((number_of_teeth-1)*mm_per_tooth/2) { + linear_extrude(height = thickness, center = true, convexity = 10) { + for (i = [0:number_of_teeth-1] ) { + translate([i*mm_per_tooth,0,0]) { + polygon( + points=[ + [-1/2 * mm_per_tooth - 0.01, a-height], + [-1/2 * mm_per_tooth, -d], + [-1/4 * mm_per_tooth + backlash - xd, -d], + [-1/4 * mm_per_tooth + backlash + xa, a], + [ 1/4 * mm_per_tooth - backlash - xa, a], + [ 1/4 * mm_per_tooth - backlash + xd, -d], + [ 1/2 * mm_per_tooth, -d], + [ 1/2 * mm_per_tooth + 0.01, a-height], + ] + ); + } + } } } } } -//These functions let the user find the derived dimensions of the gear. -//A gear fits within a circle of radius outer_radius, and two gears should have -//their centers separated by the sum of their pitch_radius. -function circular_pitch(mm_per_tooth=5) = mm_per_tooth; //tooth density expressed as "circular pitch" in millimeters -function diametral_pitch(mm_per_tooth=5) = PI / mm_per_tooth; //tooth density expressed as "diametral pitch" in teeth per millimeter - -function module_value(mm_per_tooth=5) = mm_per_tooth / PI; //tooth density expressed as "module" or "modulus" in millimeters -function adendum (mm_per_tooth=5) = module_value(mm_per_tooth); -function dedendum (mm_per_tooth=5, clearance=undef) = (clearance==undef)? (1.25 * module_value(mm_per_tooth)) : (module_value(mm_per_tooth) + clearance); -function pitch_radius(mm_per_tooth=5, number_of_teeth=11) = mm_per_tooth * number_of_teeth / PI / 2; - -//The gear fits entirely within a cylinder of this radius. -function outer_radius(mm_per_tooth=5, number_of_teeth=11) - = pitch_radius(mm_per_tooth, number_of_teeth) + adendum(mm_per_tooth); - -// Radius of circle at base of dedendum. -function root_radius(mm_per_tooth=5, number_of_teeth=11, clearance=undef) - = pitch_radius(mm_per_tooth, number_of_teeth) - dedendum(mm_per_tooth, clearance); - -// The base circle for involute teeth. -function base_radius(mm_per_tooth=5, number_of_teeth=11, pressure_angle=28) - = pitch_radius(mm_per_tooth, number_of_teeth) * cos(pressure_angle); - - ////////////////////////////////////////////////////////////////////////////////////////////// //example gear train. //Try it with OpenSCAD View/Animate command with 20 steps and 24 FPS. diff --git a/joiners.scad b/joiners.scad index 5207084..6cc8972 100644 --- a/joiners.scad +++ b/joiners.scad @@ -1,5 +1,11 @@ ////////////////////////////////////////////////////////////////////// -// Snap-together joiners +// LibFile: joiners.scad +// Snap-together joiners. +// To use, add the following lines to the beginning of your file: +// ``` +// include +// use +// ``` ////////////////////////////////////////////////////////////////////// /* @@ -31,180 +37,421 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -include +use +use +include +include -module half_joiner_clear(h=20, w=10, a=30, clearance=0) +// Section: Half Joiners + + +// Module: half_joiner_clear() +// Description: +// Creates a mask to clear an area so that a half_joiner can be placed there. +// Usage: +// half_joiner_clear(h, w, [a], [clearance], [overlap], [orient], [align]) +// Arguments: +// h = Height of the joiner to clear space for. +// w = Width of the joiner to clear space for. +// a = Overhang angle of the joiner. +// clearance = Extra width to clear. +// overlap = Extra depth to clear. +// orient = Orientation of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. +// align = Alignment of the shape by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// Example: +// half_joiner_clear(orient=ORIENT_X); +module half_joiner_clear(h=20, w=10, a=30, clearance=0, overlap=0.01, orient=ORIENT_Y, align=V_CENTER) { dmnd_height = h*1.0; dmnd_width = dmnd_height*tan(a); guide_size = w/3; guide_width = 2*(dmnd_height/2-guide_size)*tan(a); - difference() { - // Diamonds. - scale([w+clearance, dmnd_width/2, dmnd_height/2]) { - xrot(45) cube(size=[1,sqrt(2),sqrt(2)], center=true); - } - // Blunt point of tab. - grid_of(ya=[-(guide_width/2+2), (guide_width/2+2)]) { - cube(size=[(w+clearance)*1.05, 4, h*0.99], center=true); - } - } -} -//half_joiner_clear(); - - - -module half_joiner(h=20, w=10, l=10, a=30, screwsize=undef, guides=true, slop=printer_slop) -{ - dmnd_height = h*1.0; - dmnd_width = dmnd_height*tan(a); - guide_size = w/3; - guide_width = 2*(dmnd_height/2-guide_size)*tan(a); - - difference() { - union() { - // Make base. + orient_and_align([w, guide_width, h], orient, align, orig_orient=ORIENT_Y) { + yspread(overlap, n=overlap>0? 2 : 1) { difference() { - // Solid backing base. - translate([0,-l/2,0]) - cube(size=[w, l, h], center=true); - - // Clear diamond for tab - grid_of(xa=[-(w*2/3), (w*2/3)]) { - half_joiner_clear(h=h+0.01, w=w, clearance=slop*2, a=a); + // Diamonds. + scale([w+clearance, dmnd_width/2, dmnd_height/2]) { + xrot(45) cube(size=[1,sqrt(2),sqrt(2)], center=true); } - } - - difference() { - // Make tab - scale([w/3-slop*2, dmnd_width/2, dmnd_height/2]) xrot(45) - cube(size=[1,sqrt(2),sqrt(2)], center=true); - // Blunt point of tab. - translate([0,guide_width/2+2,0]) - cube(size=[w*0.99,4,guide_size*2], center=true); - } - - - // Guide ridges. - if (guides == true) { - xspread(w/3-slop*2) { - // Guide ridge. - fwd(0.05/2) { - scale([0.75, 1, 2]) yrot(45) - cube(size=[guide_size/sqrt(2), guide_width+0.05, guide_size/sqrt(2)], center=true); - } - - // Snap ridge. - scale([0.25, 0.5, 1]) zrot(45) - cube(size=[guide_size/sqrt(2), guide_size/sqrt(2), dmnd_width], center=true); + yspread(guide_width+4) { + cube(size=[(w+clearance)*1.05, 4, h*0.99], center=true); } } } + if (overlap>0) cube([w+clearance, overlap+0.001, h], center=true); + } +} - // Make screwholes, if needed. - if (screwsize != undef) { - yrot(90) cylinder(r=screwsize*1.1/2, h=w+1, center=true, $fn=12); + + +// Module: half_joiner() +// Usage: +// half_joiner(h, w, l, [a], [screwsize], [guides], [slop], [orient], [align]) +// Description: +// Creates a half_joiner object that can be attached to half_joiner2 object. +// Arguments: +// h = Height of the half_joiner. +// w = Width of the half_joiner. +// l = Length of the backing to the half_joiner. +// a = Overhang angle of the half_joiner. +// screwsize = Diameter of screwhole. +// guides = If true, create sliding alignment guides. +// slop = Printer specific slop value to make parts fit more closely. +// orient = Orientation of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. +// align = Alignment of the shape by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// Example: +// half_joiner(screwsize=3, orient=ORIENT_X); +module half_joiner(h=20, w=10, l=10, a=30, screwsize=undef, guides=true, slop=PRINTER_SLOP, orient=ORIENT_Y, align=V_CENTER) +{ + dmnd_height = h*1.0; + dmnd_width = dmnd_height*tan(a); + guide_size = w/3; + guide_width = 2*(dmnd_height/2-guide_size)*tan(a); + + if ($children > 0) { + difference() { + children(); + half_joiner_clear(h=h, w=w, a=a, clearance=0.1, overlap=0.01, orient=orient, align=align); + } + } + render(convexity=12) + orient_and_align([w, 2*l, h], orient, align, orig_orient=ORIENT_Y) { + difference() { + union() { + // Make base. + difference() { + // Solid backing base. + fwd(l/2) cube(size=[w, l, h], center=true); + + // Clear diamond for tab + grid3d(xa=[-(w*2/3), (w*2/3)]) { + half_joiner_clear(h=h+0.01, w=w, clearance=slop*2, a=a); + } + } + + difference() { + // Make tab + scale([w/3-slop*2, dmnd_width/2, dmnd_height/2]) xrot(45) + cube(size=[1,sqrt(2),sqrt(2)], center=true); + + // Blunt point of tab. + back(guide_width/2+2) + cube(size=[w*0.99,4,guide_size*2], center=true); + } + + + // Guide ridges. + if (guides == true) { + xspread(w/3-slop*2) { + // Guide ridge. + fwd(0.05/2) { + scale([0.75, 1, 2]) yrot(45) + cube(size=[guide_size/sqrt(2), guide_width+0.05, guide_size/sqrt(2)], center=true); + } + + // Snap ridge. + scale([0.25, 0.5, 1]) zrot(45) + cube(size=[guide_size/sqrt(2), guide_size/sqrt(2), dmnd_width], center=true); + } + } + } + + // Make screwholes, if needed. + if (screwsize != undef) { + yrot(90) cylinder(r=screwsize*1.1/2, h=w+1, center=true, $fn=12); + } } } } -//half_joiner(screwsize=3); +//half_joiner(screwsize=3, orient=ORIENT_Z, align=V_UP); -module half_joiner2(h=20, w=10, l=10, a=30, screwsize=undef, guides=true) +// Module: half_joiner2() +// Usage: +// half_joiner2(h, w, l, [a], [screwsize], [guides], [orient], [align]) +// Description: +// Creates a half_joiner2 object that can be attached to half_joiner object. +// Arguments: +// h = Height of the half_joiner. +// w = Width of the half_joiner. +// l = Length of the backing to the half_joiner. +// a = Overhang angle of the half_joiner. +// screwsize = Diameter of screwhole. +// guides = If true, create sliding alignment guides. +// orient = Orientation of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. +// align = Alignment of the shape by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// Example: +// half_joiner2(screwsize=3, orient=ORIENT_X); +module half_joiner2(h=20, w=10, l=10, a=30, screwsize=undef, guides=true, orient=ORIENT_Y, align=V_CENTER) { - difference() { - union () { - translate([0,-l/2,0]) - cube(size=[w, l, h], center=true); - half_joiner_clear(h=h, w=w, a=a); + dmnd_height = h*1.0; + dmnd_width = dmnd_height*tan(a); + guide_size = w/3; + guide_width = 2*(dmnd_height/2-guide_size)*tan(a); + + if ($children > 0) { + difference() { + children(); + half_joiner_clear(h=h, w=w, a=a, clearance=0.1, overlap=0.01, orient=orient, align=align); } + } - // Subtract mated half_joiner. - zrot(180) half_joiner(h=h+0.05, w=w+0.05, l=l+0.05, a=a, screwsize=undef, guides=guides, slop=0.0); + render(convexity=12) + orient_and_align([w, 2*l, h], orient, align, orig_orient=ORIENT_Y) { + difference() { + union () { + fwd(l/2) cube(size=[w, l, h], center=true); + cube([w, guide_width, h], center=true); + } - // Make screwholes, if needed. - if (screwsize != undef) { - yrot(90) cylinder(r=screwsize*1.1/2, h=w+1, center=true, $fn=12); + // Subtract mated half_joiner. + zrot(180) half_joiner(h=h+0.01, w=w+0.01, l=guide_width+0.01, a=a, screwsize=undef, guides=guides, slop=0.0); + + // Make screwholes, if needed. + if (screwsize != undef) { + xcyl(r=screwsize*1.1/2, l=w+1, $fn=12); + } } } } -//half_joiner2(screwsize=3); -module joiner(h=40, w=10, l=10, a=30, screwsize=undef, guides=true, slop=printer_slop) +// Section: Full Joiners + + +// Module: joiner_clear() +// Description: +// Creates a mask to clear an area so that a joiner can be placed there. +// Usage: +// joiner_clear(h, w, [a], [clearance], [overlap], [orient], [align]) +// Arguments: +// h = Height of the joiner to clear space for. +// w = Width of the joiner to clear space for. +// a = Overhang angle of the joiner. +// clearance = Extra width to clear. +// overlap = Extra depth to clear. +// orient = Orientation of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. +// align = Alignment of the shape by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// Example: +// joiner_clear(orient=ORIENT_X); +module joiner_clear(h=40, w=10, a=30, clearance=0, overlap=0.01, orient=ORIENT_Y, align=V_CENTER) { - union() { - translate([0,0,h/4]) - half_joiner(h=h/2, w=w, l=l, a=a, screwsize=screwsize, guides=guides, slop=slop); - translate([0,0,-h/4]) - half_joiner2(h=h/2, w=w, l=l, a=a, screwsize=screwsize, guides=guides); + dmnd_height = h*0.5; + dmnd_width = dmnd_height*tan(a); + guide_size = w/3; + guide_width = 2*(dmnd_height/2-guide_size)*tan(a); + + orient_and_align([w, guide_width, h], orient, align, orig_orient=ORIENT_Y) { + up(h/4) half_joiner_clear(h=h/2.0-0.01, w=w, a=a, overlap=overlap, clearance=clearance); + down(h/4) half_joiner_clear(h=h/2.0-0.01, w=w, a=a, overlap=overlap, clearance=-0.01); } } -//joiner(screwsize=3); -module joiner_clear(h=40, w=10, a=30, clearance=0) +// Module: joiner() +// Usage: +// joiner(h, w, l, [a], [screwsize], [guides], [slop], [orient], [align]) +// Description: +// Creates a joiner object that can be attached to another joiner object. +// Arguments: +// h = Height of the joiner. +// w = Width of the joiner. +// l = Length of the backing to the joiner. +// a = Overhang angle of the joiner. +// screwsize = Diameter of screwhole. +// guides = If true, create sliding alignment guides. +// slop = Printer specific slop value to make parts fit more closely. +// orient = Orientation of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. +// align = Alignment of the shape by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// Examples: +// joiner(screwsize=3, orient=ORIENT_X); +// joiner(w=10, l=10, h=40, orient=ORIENT_X) cuboid([10, 10*2, 40], align=V_LEFT); +module joiner(h=40, w=10, l=10, a=30, screwsize=undef, guides=true, slop=PRINTER_SLOP, orient=ORIENT_Y, align=V_CENTER) { - up(h/4) half_joiner_clear(h=h/2.0-0.01, w=w, a=a, clearance=clearance); - down(h/4) half_joiner_clear(h=h/2.0-0.01, w=w, a=a, clearance=-0.01); + if ($children > 0) { + difference() { + children(); + joiner_clear(h=h, w=w, a=a, clearance=0.1, orient=orient, align=align); + } + } + orient_and_align([w, 2*l, h], orient, align, orig_orient=ORIENT_Y) { + up(h/4) half_joiner(h=h/2, w=w, l=l, a=a, screwsize=screwsize, guides=guides, slop=slop); + down(h/4) half_joiner2(h=h/2, w=w, l=l, a=a, screwsize=screwsize, guides=guides); + } } -//joiner_clear(); -module joiner_pair(spacing=100, h=40, w=10, l=10, a=30, screwsize=undef, guides=true, slop=printer_slop) +// Section: Full Joiners Pairs/Sets + + +// Module: joiner_pair_clear() +// Description: +// Creates a mask to clear an area so that a pair of joiners can be placed there. +// Usage: +// joiner_pair_clear(spacing, [n], [h], [w], [a], [clearance], [overlap], [orient], [align]) +// Arguments: +// spacing = Spacing between joiner centers. +// h = Height of the joiner to clear space for. +// w = Width of the joiner to clear space for. +// a = Overhang angle of the joiner. +// n = Number of joiners (2 by default) to clear for. +// clearance = Extra width to clear. +// overlap = Extra depth to clear. +// orient = Orientation of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. +// align = Alignment of the shape by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// Examples: +// joiner_pair_clear(spacing=50, n=2); +// joiner_pair_clear(spacing=50, n=3); +module joiner_pair_clear(spacing=100, h=40, w=10, a=30, n=2, clearance=0, overlap=0.01, orient=ORIENT_Y, align=V_CENTER) { - yrot_copies([0,180]) { - translate([spacing/2, 0, 0]) { - joiner(h=h, w=w, l=l, a=a, screwsize=screwsize, guides=guides, slop=slop); + dmnd_height = h*0.5; + dmnd_width = dmnd_height*tan(a); + guide_size = w/3; + guide_width = 2*(dmnd_height/2-guide_size)*tan(a); + + orient_and_align([spacing+w, guide_width, h], orient, align, orig_orient=ORIENT_Y) { + xspread(spacing, n=n) { + joiner_clear(h=h, w=w, a=a, clearance=clearance, overlap=overlap); } } } -//joiner_pair(spacing=100, h=40, w=10, l=10, a=30, screwsize=3, guides=true); -module joiner_pair_clear(spacing=100, h=40, w=10, a=30, clearance=0) +// Module: joiner_pair() +// Usage: +// joiner_pair(h, w, l, [a], [screwsize], [guides], [slop], [orient], [align]) +// Description: +// Creates a joiner_pair object that can be attached to other joiner_pairs . +// Arguments: +// spacing = Spacing between joiner centers. +// h = Height of the joiners. +// w = Width of the joiners. +// l = Length of the backing to the joiners. +// a = Overhang angle of the joiners. +// n = Number of joiners in a row. Default: 2 +// alternate = If true (default), each joiner alternates it's orientation. If alternate is "alt", do opposite alternating orientations. +// screwsize = Diameter of screwhole. +// guides = If true, create sliding alignment guides. +// slop = Printer specific slop value to make parts fit more closely. +// orient = Orientation of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. +// align = Alignment of the shape by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// Examples: +// joiner_pair(spacing=50, l=10, orient=ORIENT_X) cuboid([10, 50+10-0.1, 40], align=V_LEFT); +// joiner_pair(spacing=50, l=10, n=2, orient=ORIENT_X); +// joiner_pair(spacing=50, l=10, n=3, alternate=false, orient=ORIENT_X); +// joiner_pair(spacing=50, l=10, n=3, alternate=true, orient=ORIENT_X); +// joiner_pair(spacing=50, l=10, n=3, alternate="alt", orient=ORIENT_X); +module joiner_pair(spacing=100, h=40, w=10, l=10, a=30, n=2, alternate=true, screwsize=undef, guides=true, slop=PRINTER_SLOP, orient=ORIENT_Y, align=V_CENTER) { - yrot_copies([0,180]) { - translate([spacing/2, 0, 0]) { - joiner_clear(h=h, w=w, a=a, clearance=clearance); + if ($children > 0) { + difference() { + children(); + joiner_pair_clear(spacing=spacing, h=h, w=w, a=a, clearance=0.1, orient=orient, align=align); + } + } + orient_and_align([spacing+w, 2*l, h], orient, align, orig_orient=ORIENT_Y) { + left((n-1)*spacing/2) { + for (i=[0:n-1]) { + right(i*spacing) { + yrot(180 + (alternate? (i*180+(alternate=="alt"?180:0))%360 : 0)) { + joiner(h=h, w=w, l=l, a=a, screwsize=screwsize, guides=guides, slop=slop); + } + } + } } } } -//joiner_pair_clear(spacing=100, h=40, w=10, a=30); -module joiner_quad(xspacing=100, yspacing=50, h=40, w=10, l=10, a=30, screwsize=undef, guides=true, slop=printer_slop) +// Section: Full Joiners Quads/Sets + + +// Module: joiner_quad_clear() +// Description: +// Creates a mask to clear an area so that a pair of joiners can be placed there. +// Usage: +// joiner_quad_clear(spacing, [n], [h], [w], [a], [clearance], [overlap], [orient], [align]) +// Arguments: +// spacing1 = Spacing between joiner centers. +// spacing2 = Spacing between back-to-back pairs/sets of joiners. +// h = Height of the joiner to clear space for. +// w = Width of the joiner to clear space for. +// a = Overhang angle of the joiner. +// n = Number of joiners in a row. Default: 2 +// clearance = Extra width to clear. +// overlap = Extra depth to clear. +// orient = Orientation of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. +// align = Alignment of the shape by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// Examples: +// joiner_quad_clear(spacing1=50, spacing2=50, n=2); +// joiner_quad_clear(spacing1=50, spacing2=50, n=3); +module joiner_quad_clear(xspacing=undef, yspacing=undef, spacing1=undef, spacing2=undef, n=2, h=40, w=10, a=30, clearance=0, overlap=0.01, orient=ORIENT_Y, align=V_CENTER) { - zrot_copies([0,180]) { - translate([0, yspacing/2, 0]) { - joiner_pair(spacing=xspacing, h=h, w=w, l=l, a=a, screwsize=screwsize, guides=guides, slop=slop); + spacing1 = first_defined([spacing1, xspacing, 100]); + spacing2 = first_defined([spacing2, yspacing, 50]); + orient_and_align([w+spacing1, spacing2, h], orient, align, orig_orient=ORIENT_Y) { + zrot_copies(n=2) { + back(spacing2/2) { + joiner_pair_clear(spacing=spacing1, n=n, h=h, w=w, a=a, clearance=clearance, overlap=overlap); + } } } } -//joiner_quad(xspacing=100, yspacing=50, h=40, w=10, l=10, a=30, screwsize=3, guides=true); -module joiner_quad_clear(xspacing=100, yspacing=50, h=40, w=10, a=30, clearance=0) +// Module: joiner_quad() +// Usage: +// joiner_quad(h, w, l, [a], [screwsize], [guides], [slop], [orient], [align]) +// Description: +// Creates a joiner_quad object that can be attached to other joiner_pairs . +// Arguments: +// spacing = Spacing between joiner centers. +// h = Height of the joiners. +// w = Width of the joiners. +// l = Length of the backing to the joiners. +// a = Overhang angle of the joiners. +// n = Number of joiners in a row. Default: 2 +// alternate = If true (default), each joiner alternates it's orientation. If alternate is "alt", do opposite alternating orientations. +// screwsize = Diameter of screwhole. +// guides = If true, create sliding alignment guides. +// slop = Printer specific slop value to make parts fit more closely. +// orient = Orientation of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. +// align = Alignment of the shape by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// Examples: +// joiner_quad(spacing1=50, spacing2=50, l=10, orient=ORIENT_X) cuboid([50, 50+10-0.1, 40]); +// joiner_quad(spacing1=50, spacing2=50, l=10, n=2, orient=ORIENT_X); +// joiner_quad(spacing1=50, spacing2=50, l=10, n=3, alternate=false, orient=ORIENT_X); +// joiner_quad(spacing1=50, spacing2=50, l=10, n=3, alternate=true, orient=ORIENT_X); +// joiner_quad(spacing1=50, spacing2=50, l=10, n=3, alternate="alt", orient=ORIENT_X); +module joiner_quad(spacing1=undef, spacing2=undef, xspacing=undef, yspacing=undef, h=40, w=10, l=10, a=30, n=2, alternate=true, screwsize=undef, guides=true, slop=PRINTER_SLOP, orient=ORIENT_Y, align=V_CENTER) { - zrot_copies([0,180]) { - translate([0, yspacing/2, 0]) { - joiner_pair_clear(spacing=xspacing, h=h, w=w, a=a, clearance=clearance); + spacing1 = first_defined([spacing1, xspacing, 100]); + spacing2 = first_defined([spacing2, yspacing, 50]); + if ($children > 0) { + difference() { + children(); + joiner_quad_clear(spacing1=spacing1, spacing2=spacing2, h=h, w=w, a=a, clearance=0.1, orient=orient, align=align); + } + } + orient_and_align([w+spacing1, spacing2, h], orient, align, orig_orient=ORIENT_Y) { + zrot_copies(n=2) { + back(spacing2/2) { + joiner_pair(spacing=spacing1, n=n, h=h, w=w, l=l, a=a, screwsize=screwsize, guides=guides, slop=slop); + } } } } -//joiner_quad_clear(xspacing=100, yspacing=50, h=40, w=10, a=30); // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap - diff --git a/linear_bearings.scad b/linear_bearings.scad index d438920..dc788b6 100644 --- a/linear_bearings.scad +++ b/linear_bearings.scad @@ -1,5 +1,11 @@ ////////////////////////////////////////////////////////////////////// -// Linear Bearings. +// LibFile: linear_bearings.scad +// Linear Bearing clips/holders. +// To use, add these lines to the top of your file: +// ``` +// include +// use +// ``` ////////////////////////////////////////////////////////////////////// /* @@ -35,6 +41,13 @@ include include +// Section: Functions + + +// Function: get_lmXuu_bearing_diam() +// Description: Get outside diameter, in mm, of a standard lmXuu bearing. +// Arguments: +// size = Inner size of lmXuu bearing, in mm. function get_lmXuu_bearing_diam(size) = lookup(size, [ [ 4.0, 8.0], [ 5.0, 10.0], @@ -56,6 +69,10 @@ function get_lmXuu_bearing_diam(size) = lookup(size, [ ]); +// Function: get_lmXuu_bearing_length() +// Description: Get length, in mm, of a standard lmXuu bearing. +// Arguments: +// size = Inner size of lmXuu bearing, in mm. function get_lmXuu_bearing_length(size) = lookup(size, [ [ 4.0, 12.0], [ 5.0, 15.0], @@ -77,7 +94,10 @@ function get_lmXuu_bearing_length(size) = lookup(size, [ ]); -// Creates a model of a clamp to hold a given linear bearing cartridge. +// Module: linear_bearing_housing() +// Description: +// Creates a model of a clamp to hold a generic linear bearing cartridge. +// Arguments: // d = Diameter of linear bearing. (Default: 15) // l = Length of linear bearing. (Default: 24) // tab = Clamp tab height. (Default: 7) @@ -85,46 +105,56 @@ function get_lmXuu_bearing_length(size) = lookup(size, [ // wall = Wall thickness of clamp housing. (Default: 3) // gap = Gap in clamp. (Default: 5) // screwsize = Size of screw to use to tighten clamp. (Default: 3) +// orient = Orientation of the housing. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_X`. +// align = Alignment of the housing by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `V_UP` // Example: // linear_bearing_housing(d=19, l=29, wall=2, tab=6, screwsize=2.5); -module linear_bearing_housing(d=15,l=24,tab=7,gap=5,wall=3,tabwall=5,screwsize=3) +module linear_bearing_housing(d=15, l=24, tab=7, gap=5, wall=3, tabwall=5, screwsize=3, orient=ORIENT_X, align=V_UP) { od = d+2*wall; ogap = gap+2*tabwall; tabh = tab/2+od/2*sqrt(2)-ogap/2; - translate([0,0,od/2]) difference() { - union() { - rotate([0,0,90]) - teardrop(r=od/2,h=l); - translate([0,0,tabh]) - cube(size=[l,ogap,tab+0.05], center=true); - translate([0,0,-od/4]) - cube(size=[l,od,od/2], center=true); - } - rotate([0,0,90]) - teardrop(r=d/2,h=l+0.05); - translate([0,0,(d*sqrt(2)+tab)/2]) - cube(size=[l+0.05,gap,d+tab], center=true); - translate([0,0,tabh]) { - translate([0,-ogap/2+2-0.05,0]) - rotate([90,0,0]) - screw(screwsize=screwsize*1.06, screwlen=ogap, headsize=screwsize*2, headlen=10); - translate([0,ogap/2+0.05,0]) - rotate([90,0,0]) - metric_nut(size=screwsize,hole=false); + orient_and_align([l, od, od], orient, align, orig_orient=ORIENT_X) { + difference() { + union() { + zrot(90) teardrop(r=od/2,h=l); + up(tabh) cube(size=[l,ogap,tab+0.05], center=true); + down(od/4) cube(size=[l,od,od/2], center=true); + } + zrot(90) teardrop(r=d/2,h=l+0.05); + up((d*sqrt(2)+tab)/2) + cube(size=[l+0.05,gap,d+tab], center=true); + up(tabh) { + fwd(ogap/2-2+0.01) + xrot(90) screw(screwsize=screwsize*1.06, screwlen=ogap, headsize=screwsize*2, headlen=10); + back(ogap/2+0.01) + xrot(90) metric_nut(size=screwsize, hole=false); + } } } } -module lmXuu_housing(size=8,tab=7,gap=5,wall=3,tabwall=5,screwsize=3) +// Module: lmXuu_housing() +// Description: +// Creates a model of a clamp to hold a standard sized lmXuu linear bearing cartridge. +// Arguments: +// size = Standard lmXuu inner size. +// tab = Clamp tab height. Default: 7 +// tabwall = Clamp Tab thickness. Default: 5 +// wall = Wall thickness of clamp housing. Default: 3 +// gap = Gap in clamp. Default: 5 +// screwsize = Size of screw to use to tighten clamp. Default: 3 +// orient = Orientation of the housing. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_X`. +// align = Alignment of the housing by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `V_UP` +// Example: +// lmXuu_housing(size=10, wall=2, tab=6, screwsize=2.5); +module lmXuu_housing(size=8, tab=7, gap=5, wall=3, tabwall=5, screwsize=3, orient=ORIENT_X, align=V_UP) { d = get_lmXuu_bearing_diam(size); l = get_lmXuu_bearing_length(size); - linear_bearing_housing(d=d,l=l,tab=tab,gap=gap,wall=wall,tabwall=tabwall,screwsize=screwsize); + linear_bearing_housing(d=d,l=l,tab=tab,gap=gap,wall=wall,tabwall=tabwall,screwsize=screwsize, orient=orient, align=align); } -//lmXuu_housing(size=8); -//lmXuu_housing(size=10); // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/masks.scad b/masks.scad index ddb87cb..4c24e26 100644 --- a/masks.scad +++ b/masks.scad @@ -1,5 +1,11 @@ ////////////////////////////////////////////////////////////////////// -// Masking shapes. +// LibFile: masks.scad +// Masking shapes. +// To use, add the following lines to the beginning of your file: +// ``` +// include +// use +// ``` ////////////////////////////////////////////////////////////////////// /* @@ -34,215 +40,65 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use use use +include include -module angle_half_pie_mask( - ang=45, h=1, - r=undef, r1=undef, r2=undef, - d=1.0, d1=undef, d2=undef, -) { - r = (r != undef)? r : (d/2); - r1 = (r1 != undef)? r1 : ((d1 != undef)? (d1/2) : r); - r2 = (r2 != undef)? r2 : ((d2 != undef)? (d2/2) : r); - rm = max(r1,r2); - difference() { - cylinder(h=h, r1=r1, r2=r2, center=true); - translate([0, -rm/2, 0]) - cube(size=[rm*2+1, rm, h+1], center=true); - zrot(ang) { - translate([0, rm/2, 0]) { - cube(size=[rm*2.1, rm, h+1], center=true); - } - } - } -} +// Section: General Masks - -// Creates a pie wedge shape that can be used to mask other shapes. -// You must specify either r or d, or their r1/r2, d1/d2 variants. +// Module: angle_pie_mask() +// Usage: +// angle_pie_mask(r|d, l, ang, [orient], [align], [center]); +// angle_pie_mask(r1|d1, r2|d2, l, ang, [orient], [align], [center]); +// Description: +// Creates a pie wedge shape that can be used to mask other shapes. +// Arguments: // ang = angle of wedge in degrees. -// h = height of wedge. +// l = height of wedge. // r = Radius of circle wedge is created from. (optional) // r1 = Bottom radius of cone that wedge is created from. (optional) // r2 = Upper radius of cone that wedge is created from. (optional) // d = Diameter of circle wedge is created from. (optional) // d1 = Bottom diameter of cone that wedge is created from. (optional) // d2 = Upper diameter of cone that wedge is created from. (optional) -// Example: -// angle_pie_mask(ang=30, d=100, h=20); +// orient = Orientation of the pie slice. Use the ORIENT_ constants from constants.h. Default: ORIENT_Z. +// align = Alignment of the pie slice. Use the V_ constants from constants.h. Default: V_CENTER. +// center = If true, centers vertically. If false, lift up to sit on top of the XY plane. Overrides `align`. +// Example(FR): +// angle_pie_mask(ang=30, d=100, l=20); module angle_pie_mask( - ang=45, h=1, + ang=45, l=undef, r=undef, r1=undef, r2=undef, - d=1.0, d1=undef, d2=undef, + d=undef, d1=undef, d2=undef, + orient=ORIENT_Z, align=V_CENTER, + h=undef, center=undef ) { - a1 = min(ang, 180.0); - a2 = max(0.0, ang-180.0); - r = (r != undef)? r : (d/2); - r1 = (r1 != undef)? r1 : ((d1 != undef)? (d1/2) : r); - r2 = (r2 != undef)? r2 : ((d2 != undef)? (d2/2) : r); - union() { - angle_half_pie_mask(h=h, r1=r1, r2=r2, ang=a1); - if (a2 > 0.0) { - zrot(180) angle_half_pie_mask(h=h, r1=r1, r2=r2, ang=a2); - } + l = first_defined([l, h, 1]); + r1 = get_radius(r1, r, d1, d, 10); + r2 = get_radius(r2, r, d2, d, 10); + orient_and_align([2*r1, 2*r1, l], orient, align, center=center) { + pie_slice(ang=ang, l=l+0.1, r1=r1, r2=r2, align=V_CENTER); } } -// Creates a shape that can be used to chamfer a 90 degree edge. -// Difference it from the object to be chamfered. The center of -// the mask object should align exactly with the edge to be chamfered. -// l = Height of mask -// chamfer = Size of chamfer -// orient = Orientation of the cylinder. Use the ORIENT_ constants from constants.h. Default: vertical. -// align = Alignment of the cylinder. Use the V_ constants from constants.h. Default: centered. -// Example: -// difference() { -// cube(50); -// #chamfer_mask(l=50.1, chamfer=10.0, orient=ORIENT_X, align=V_RIGHT); -// } -module chamfer_mask(l=1.0, chamfer=1.0, orient=ORIENT_Z, align=V_ZERO) { - cyl(d=chamfer*2, l=l, align=align, orient=orient, $fn=4); -} - - -// Creates a shape that can be used to chamfer a 90 degree edge along the Z axis. -// Difference it from the object to be chamfered. The center of the mask -// object should align exactly with the edge to be chamfered. -// l = Height of mask -// chamfer = size of chamfer -// align = Alignment of the cylinder. Use the V_ constants from constants.h. Default: centered. -// Example: -// difference() { -// down(5) cube(10); -// chamfer_mask_z(l=10.1, chamfer=2.0); -// } -module chamfer_mask_z(l=1.0, chamfer=1.0, align=V_ZERO) { - chamfer_mask(l=l, chamfer=chamfer, orient=ORIENT_Z, align=align); -} - - -// Creates a shape that can be used to chamfer a 90 degree edge along the Y axis. -// Difference it from the object to be chamfered. The center of the mask -// object should align exactly with the edge to be chamfered. -// l = Height of mask -// chamfer = size of chamfer -// align = Alignment of the cylinder. Use the V_ constants from constants.h. Default: centered. -// Example: -// difference() { -// fwd(5) cube(10); -// chamfer_mask_y(l=10.1, chamfer=2.0); -// } -module chamfer_mask_y(l=1.0, chamfer=1.0, align=V_ZERO) { - chamfer_mask(l=l, chamfer=chamfer, orient=ORIENT_Y, align=align); -} - - -// Creates a shape that can be used to chamfer a 90 degree edge along the X axis. -// Difference it from the object to be chamfered. The center of the mask -// object should align exactly with the edge to be chamfered. -// l = Height of mask -// chamfer = size of chamfer -// align = Alignment of the cylinder. Use the V_ constants from constants.h. Default: centered. -// Example: -// difference() { -// left(5) cube(10); -// chamfer_mask_x(l=10.1, chamfer=2.0); -// } -module chamfer_mask_x(l=1.0, chamfer=1.0, align=V_ZERO) { - chamfer_mask(l=l, chamfer=chamfer, orient=ORIENT_X, align=align); -} - - -// Chamfers the edges of a cuboid region containing the given children. -// chamfer = Inset of the chamfer from the edge. (Default: 1) -// size = The size of the rectangular cuboid we want to chamfer. -// edges = Which edges do we want to chamfer. Recommend to use EDGE constants from constants.scad. -// [ -// [Y+Z+, Y-Z+, Y-Z-, Y+Z-], -// [X+Z+, X-Z+, X-Z-, X+Z-], -// [X+Y+, X-Y+, X-Y-, X+Y-] -// ] -// Example: -// include -// chamfer(chamfer=2, size=[10,40,30], edges=EDGE_BOT_BK + EDGE_TOP_RT + EDGE_TOP_LF) { -// cube(size=[10,40,30], center=true); -// } -module chamfer(chamfer=1, size=[1,1,1], edges=[[0,0,0,0], [1,1,0,0], [0,0,0,0]]) -{ - eps = 0.1; - x = size[0]; - y = size[1]; - z = size[2]; - lx = x + eps; - ly = y + eps; - lz = z + eps; - difference() { - union() { - children(); - } - union() { - if (edges[0][0] > 0) - up(z/2) back(y/2) chamfer_mask_x(l=lx, chamfer=chamfer); - if (edges[0][1] > 0) - up(z/2) fwd(y/2) chamfer_mask_x(l=lx, chamfer=chamfer); - if (edges[0][2] > 0) - down(z/2) back(y/2) chamfer_mask_x(l=lx, chamfer=chamfer); - if (edges[0][3] > 0) - down(z/2) fwd(y/2) chamfer_mask_x(l=lx, chamfer=chamfer); - - if (edges[1][0] > 0) - up(z/2) right(x/2) chamfer_mask_y(l=ly, chamfer=chamfer); - if (edges[1][1] > 0) - up(z/2) left(x/2) chamfer_mask_y(l=ly, chamfer=chamfer); - if (edges[1][2] > 0) - down(z/2) right(x/2) chamfer_mask_y(l=ly, chamfer=chamfer); - if (edges[1][3] > 0) - down(z/2) left(x/2) chamfer_mask_y(l=ly, chamfer=chamfer); - - if (edges[2][0] > 0) - back(y/2) right(x/2) chamfer_mask_z(l=lz, chamfer=chamfer); - if (edges[2][1] > 0) - back(y/2) left(x/2) chamfer_mask_z(l=lz, chamfer=chamfer); - if (edges[2][2] > 0) - fwd(y/2) right(x/2) chamfer_mask_z(l=lz, chamfer=chamfer); - if (edges[2][3] > 0) - fwd(y/2) left(x/2) chamfer_mask_z(l=lz, chamfer=chamfer); - } - } -} - - -// Create a mask that can be used to bevel/chamfer the end of a cylindrical region. -// Difference it from the end of the region to be chamferred. The center of the mask -// object should align exactly with the center of the end of the cylindrical region -// to be chamferred. -// r = Radius of cylinder to chamfer. -// d = Diameter of cylinder to chamfer. Use instead of r. -// chamfer = Size of the edge chamferred, inset from edge. (Default: 0.25) -// ang = Angle of chamfer in degrees from vertical. (Default: 45) -// from_end = If true, chamfer size is measured from end of cylinder. If false, chamfer is measured outset from the radius of the cylinder. (Default: false) -// orient = Orientation of the mask. Use the `ORIENT_` constants from `constants.h`. Default: ORIENT_Z. -// Example: -// $fa=2; $fs=2; -// difference() { -// cylinder(r=50, h=100, center=true); -// up(50) !chamfer_cylinder_mask(r=50, chamfer=10); -// } -module chamfer_cylinder_mask(r=1.0, d=undef, chamfer=0.25, ang=45, from_end=false, orient=ORIENT_Z) -{ - r = get_radius(r=r, d=d, dflt=1); - rot(orient) cylinder_mask(l=chamfer*3, r=r, chamfer2=chamfer, chamfang2=ang, from_end=from_end, ends_only=true, align=V_DOWN); -} - - -// If passed children, bevels/chamfers and/or rounds/fillets the ends of the -// cylindrical/conical region specified. If passed no children, creates -// a mask to bevel/chamfer and/or fillet the ends of the cylindrical -// region specified. Difference the mask from the region. The center -// of the mask object should align exactly with the center of the -// cylindrical region to be chamferred. +// Module: cylinder_mask() +// Usage: Mask objects +// cylinder_mask(l, r|d, chamfer, [chamfang], [from_end], [circum], [overage], [ends_only], [orient], [align]); +// cylinder_mask(l, r|d, fillet, [circum], [overage], [ends_only], [orient], [align]); +// cylinder_mask(l, r|d, [chamfer1|fillet1], [chamfer2|fillet2], [chamfang1], [chamfang2], [from_end], [circum], [overage], [ends_only], [orient], [align]); +// Usage: Masking operators +// cylinder_mask(l, r|d, chamfer, [chamfang], [from_end], [circum], [overage], [ends_only], [orient], [align]) ... +// cylinder_mask(l, r|d, fillet, [circum], [overage], [ends_only], [orient], [align]) ... +// cylinder_mask(l, r|d, [chamfer1|fillet1], [chamfer2|fillet2], [chamfang1], [chamfang2], [from_end], [circum], [overage], [ends_only], [orient], [align]) ... +// Description: +// If passed children, bevels/chamfers and/or rounds/fillets one or +// both ends of the origin-centered cylindrical region specified. If +// passed no children, creates a mask to bevel/chamfer and/or round/fillet +// one or both ends of the cylindrical region. Difference the mask +// from the region, making sure the center of the mask object is align +// exactly with the center of the cylindrical region to be chamferred. +// Arguments: // l = Length of the cylindrical/conical region. // r = Radius of cylindrical region to chamfer. // r1 = Radius of axis-negative end of the region to chamfer. @@ -264,13 +120,13 @@ module chamfer_cylinder_mask(r=1.0, d=undef, chamfer=0.25, ang=45, from_end=fals // overage = The extra thickness of the mask. Default: `10`. // ends_only = If true, only mask the ends and not around the middle of the cylinder. // orient = Orientation. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. -// align = Alignment of the region. Use the `V_` constants from `constants.scad`. Default: `V_ZERO`. +// align = Alignment of the region. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. // Example: -// $fa=2; $fs=2; // difference() { // cylinder(h=100, r1=60, r2=30, center=true); // cylinder_mask(l=100, r1=60, r2=30, chamfer=10, from_end=true); // } +// Example: // cylinder_mask(l=100, r=50, chamfer1=10, fillet2=10) { // cube([100,50,100], center=true); // } @@ -283,7 +139,7 @@ module cylinder_mask( fillet=undef, fillet1=undef, fillet2=undef, circum=false, from_end=false, overage=10, ends_only=false, - orient=ORIENT_Z, align=V_ZERO + orient=ORIENT_Z, align=V_CENTER ) { r1 = get_radius(r=r, d=d, r1=r1, d1=d1, dflt=1); r2 = get_radius(r=r, d=d, r1=r2, d1=d2, dflt=1); @@ -325,20 +181,179 @@ module cylinder_mask( -// Create a mask that can be used to bevel/chamfer the end of a cylindrical hole. -// Difference it from the hole to be chamferred. The center of the mask object -// should align exactly with the center of the end of the hole to be chamferred. +// Section: Chamfers + + +// Module: chamfer_mask() +// Usage: +// chamfer_mask(l, chamfer, [orient], [align], [center]); +// Description: +// Creates a shape that can be used to chamfer a 90 degree edge. +// Difference it from the object to be chamfered. The center of +// the mask object should align exactly with the edge to be chamfered. +// Arguments: +// l = Length of mask. +// chamfer = Size of chamfer +// orient = Orientation of the mask. Use the `ORIENT_` constants from `constants.h`. Default: vertical. +// align = Alignment of the mask. Use the `V_` constants from `constants.h`. Default: centered. +// center = If true, centers vertically. If false, lift up to sit on top of the XY plane. Overrides `align`. +// Example: +// difference() { +// cube(50); +// #chamfer_mask(l=50, chamfer=10, orient=ORIENT_X, align=V_RIGHT); +// } +module chamfer_mask(l=1, chamfer=1, orient=ORIENT_Z, align=V_CENTER, center=undef) { + orient_and_align([chamfer, chamfer, l], orient, align, center=center) { + cylinder(d=chamfer*2, h=l+0.1, center=true, $fn=4); + } +} + + +// Module: chamfer_mask_x() +// Usage: +// chamfer_mask_x(l, chamfer, [align]); +// Description: +// Creates a shape that can be used to chamfer a 90 degree edge along the X axis. +// Difference it from the object to be chamfered. The center of the mask +// object should align exactly with the edge to be chamfered. +// Arguments: +// l = Height of mask +// chamfer = size of chamfer +// align = Alignment of the cylinder. Use the V_ constants from constants.h. Default: centered. +// Example: +// difference() { +// left(40) cube(80); +// #chamfer_mask_x(l=80, chamfer=20); +// } +module chamfer_mask_x(l=1.0, chamfer=1.0, align=V_CENTER) { + chamfer_mask(l=l, chamfer=chamfer, orient=ORIENT_X, align=align); +} + + +// Module: chamfer_mask_y() +// Usage: +// chamfer_mask_y(l, chamfer, [align]); +// Description: +// Creates a shape that can be used to chamfer a 90 degree edge along the Y axis. +// Difference it from the object to be chamfered. The center of the mask +// object should align exactly with the edge to be chamfered. +// Arguments: +// l = Height of mask +// chamfer = size of chamfer +// align = Alignment of the cylinder. Use the V_ constants from constants.h. Default: centered. +// Example: +// difference() { +// fwd(40) cube(80); +// right(80) #chamfer_mask_y(l=80, chamfer=20); +// } +module chamfer_mask_y(l=1.0, chamfer=1.0, align=V_CENTER) { + chamfer_mask(l=l, chamfer=chamfer, orient=ORIENT_Y, align=align); +} + + +// Module: chamfer_mask_z() +// Usage: +// chamfer_mask_z(l, chamfer, [align]); +// Description: +// Creates a shape that can be used to chamfer a 90 degree edge along the Z axis. +// Difference it from the object to be chamfered. The center of the mask +// object should align exactly with the edge to be chamfered. +// Arguments: +// l = Height of mask +// chamfer = size of chamfer +// align = Alignment of the cylinder. Use the V_ constants from constants.h. Default: centered. +// Example: +// difference() { +// down(40) cube(80); +// #chamfer_mask_z(l=80, chamfer=20); +// } +module chamfer_mask_z(l=1.0, chamfer=1.0, align=V_CENTER) { + chamfer_mask(l=l, chamfer=chamfer, orient=ORIENT_Z, align=align); +} + + +// Module: chamfer() +// Usage: +// chamfer(chamfer, size, [edges]) ... +// Description: +// Chamfers the edges of a cuboid region containing childrem, centered on the origin. +// Arguments: +// chamfer = Inset of the chamfer from the edge. (Default: 1) +// size = The size of the rectangular cuboid we want to chamfer. +// edges = Which edges do we want to chamfer. Recommend to use EDGE constants from constants.scad. +// Description: +// You should use `EDGE` constants from `constants.scad` with the `edge` argument. +// However, if you must handle it raw, the edge ordering is this: +// [ +// [Y+Z+, Y-Z+, Y-Z-, Y+Z-], +// [X+Z+, X-Z+, X-Z-, X+Z-], +// [X+Y+, X-Y+, X-Y-, X+Y-] +// ] +// Example(FR): +// chamfer(chamfer=2, size=[20,40,30]) { +// cube(size=[20,40,30], center=true); +// } +// Example(FR): +// chamfer(chamfer=2, size=[20,40,30], edges=EDGES_TOP - EDGE_TOP_LF + EDGE_FR_RT) { +// cube(size=[20,40,30], center=true); +// } +module chamfer(chamfer=1, size=[1,1,1], edges=EDGES_ALL) +{ + difference() { + children(); + difference() { + cube(size, center=true); + cuboid(size+[1,1,1]*0.02, chamfer=chamfer+0.01, edges=edges, trimcorners=true); + } + } +} + + +// Module: chamfer_cylinder_mask() +// Usage: +// chamfer_cylinder_mask(r|d, chamfer, [ang], [from_end], [orient]) +// Description: +// Create a mask that can be used to bevel/chamfer the end of a cylindrical region. +// Difference it from the end of the region to be chamferred. The center of the mask +// object should align exactly with the center of the end of the cylindrical region +// to be chamferred. +// Arguments: +// r = Radius of cylinder to chamfer. +// d = Diameter of cylinder to chamfer. Use instead of r. +// chamfer = Size of the edge chamferred, inset from edge. (Default: 0.25) +// ang = Angle of chamfer in degrees from vertical. (Default: 45) +// from_end = If true, chamfer size is measured from end of cylinder. If false, chamfer is measured outset from the radius of the cylinder. (Default: false) +// orient = Orientation of the mask. Use the `ORIENT_` constants from `constants.h`. Default: ORIENT_Z. +// Example: +// difference() { +// cylinder(r=50, h=100, center=true); +// up(50) #chamfer_cylinder_mask(r=50, chamfer=10); +// } +module chamfer_cylinder_mask(r=1.0, d=undef, chamfer=0.25, ang=45, from_end=false, orient=ORIENT_Z) +{ + r = get_radius(r=r, d=d, dflt=1); + rot(orient) cylinder_mask(l=chamfer*3, r=r, chamfer2=chamfer, chamfang2=ang, from_end=from_end, ends_only=true, align=V_DOWN); +} + + +// Module: chamfer_hole_mask() +// Usage: +// chamfer_hole_mask(r|d, chamfer, [ang], [from_end]); +// Description: +// Create a mask that can be used to bevel/chamfer the end of a cylindrical hole. +// Difference it from the hole to be chamferred. The center of the mask object +// should align exactly with the center of the end of the hole to be chamferred. +// Arguments: // r = Radius of hole to chamfer. // d = Diameter of hole to chamfer. Use instead of r. // chamfer = Size of the chamfer. (Default: 0.25) // ang = Angle of chamfer in degrees from vertical. (Default: 45) // from_end = If true, chamfer size is measured from end of hole. If false, chamfer is measured outset from the radius of the hole. (Default: false) // Example: -// $fa=2; $fs=2; // difference() { // cube(100, center=true); // cylinder(d=50, h=100.1, center=true); -// up(50) chamfer_hole_mask(d=50, chamfer=10); +// up(50) #chamfer_hole_mask(d=50, chamfer=10); // } module chamfer_hole_mask(r=1.0, d=undef, chamfer=0.25, ang=45, from_end=false) { @@ -350,114 +365,150 @@ module chamfer_hole_mask(r=1.0, d=undef, chamfer=0.25, ang=45, from_end=false) -// Creates a shape that can be used to fillet a vertical 90 degree edge. -// Difference it from the object to be filletted. The center of the mask -// object should align exactly with the edge to be filletted. -// h = height of vertical mask. -// r = radius of the fillet. -// center = If true, vertically center mask. +// Section: Filleting/Rounding + +// Module: fillet_mask() +// Usage: +// fillet_mask(l|h, r, [orient], [align], [center]) +// Description: +// Creates a shape that can be used to fillet a vertical 90 degree edge. +// Difference it from the object to be filletted. The center of the mask +// object should align exactly with the edge to be filletted. +// Arguments: +// l = Length of mask. +// r = Radius of the fillet. +// orient = Orientation of the mask. Use the `ORIENT_` constants from `constants.h`. Default: vertical. +// align = Alignment of the mask. Use the `V_` constants from `constants.h`. Default: centered. +// center = If true, centers vertically. If false, lift up to sit on top of the XY plane. Overrides `align`. // Example: // difference() { // cube(size=100, center=false); -// up(50) fillet_mask(h=100.1, r=25.0); +// #fillet_mask(l=100, r=25, orient=ORIENT_Z, align=V_UP); // } -module fillet_mask(h=1.0, r=1.0, center=true) +module fillet_mask(l=undef, r=1.0, orient=ORIENT_Z, align=V_CENTER, h=undef, center=undef) { - n = ceil(segs(r)/4)*4; - linear_extrude(height=h, convexity=4, center=center) { - polygon( - points=concat( - [for (a = [ 0:360/n: 90]) [r*cos(a)-r, r*sin(a)-r]], - [for (a = [270:360/n:360]) [r*cos(a)-r, r*sin(a)+r]], - [for (a = [180:360/n:270]) [r*cos(a)+r, r*sin(a)+r]], - [for (a = [ 90:360/n:180]) [r*cos(a)+r, r*sin(a)-r]] - ) - ); + l = first_defined([l, h, 1]); + sides = quantup(segs(r),4); + orient_and_align([2*r, 2*r, l], orient, align, center=center) { + linear_extrude(height=l+0.1, convexity=4, center=true) { + difference() { + square(2*r, center=true); + xspread(2*r) yspread(2*r) circle(r=r, $fn=sides); + } + } } } -module fillet_mask_z(l=1.0, r=1.0) fillet_mask(h=l, r=r, center=true); -module fillet_mask_y(l=1.0, r=1.0) xrot(90) fillet_mask(h=l, r=r, center=true); -module fillet_mask_x(l=1.0, r=1.0) yrot(90) fillet_mask(h=l, r=r, center=true); -// Fillets the edges of a cuboid region containing the given children. +// Module: fillet_mask_x() +// Usage: +// fillet_mask_x(l, r, [align], [center]) +// Description: +// Creates a shape that can be used to fillet a 90 degree edge oriented +// along the X axis. Difference it from the object to be filletted. +// The center of the mask object should align exactly with the edge to +// be filletted. +// Arguments: +// l = Length of mask. +// r = Radius of the fillet. +// align = Alignment of the mask. Use the `V_` constants from `constants.h`. Default: centered. +// center = If true, centers vertically. If false, lift up to sit on top of the XY plane. Overrides `align`. +// Example: +// difference() { +// cube(size=100, center=false); +// #fillet_mask_x(l=100, r=25, align=V_RIGHT); +// } +module fillet_mask_x(l=1.0, r=1.0, align=V_CENTER) fillet_mask(l=l, r=r, orient=ORIENT_X, align=align); + + +// Module: fillet_mask_y() +// Usage: +// fillet_mask_y(l, r, [align], [center]) +// Description: +// Creates a shape that can be used to fillet a 90 degree edge oriented +// along the Y axis. Difference it from the object to be filletted. +// The center of the mask object should align exactly with the edge to +// be filletted. +// Arguments: +// l = Length of mask. +// r = Radius of the fillet. +// align = Alignment of the mask. Use the `V_` constants from `constants.h`. Default: centered. +// center = If true, centers vertically. If false, lift up to sit on top of the XY plane. Overrides `align`. +// Example: +// difference() { +// cube(size=100, center=false); +// right(100) #fillet_mask_y(l=100, r=25, align=V_BACK); +// } +module fillet_mask_y(l=1.0, r=1.0, align=V_CENTER) fillet_mask(l=l, r=r, orient=ORIENT_Y, align=align); + + +// Module: fillet_mask_z() +// Usage: +// fillet_mask_z(l, r, [align], [center]) +// Description: +// Creates a shape that can be used to fillet a 90 degree edge oriented +// along the Z axis. Difference it from the object to be filletted. +// The center of the mask object should align exactly with the edge to +// be filletted. +// Arguments: +// l = Length of mask. +// r = Radius of the fillet. +// align = Alignment of the mask. Use the `V_` constants from `constants.h`. Default: centered. +// center = If true, centers vertically. If false, lift up to sit on top of the XY plane. Overrides `align`. +// Example: +// difference() { +// cube(size=100, center=false); +// #fillet_mask_z(l=100, r=25, align=V_UP); +// } +module fillet_mask_z(l=1.0, r=1.0, align=V_CENTER) fillet_mask(l=l, r=r, orient=ORIENT_Z, align=align); + + +// Module: fillet() +// Usage: +// fillet(fillet, size, [edges]) ... +// Description: +// Fillets the edges of a cuboid region containing the given children. +// Arguments: // fillet = Radius of the fillet. (Default: 1) // size = The size of the rectangular cuboid we want to chamfer. // edges = Which edges do we want to chamfer. Recommend to use EDGE constants from constants.scad. -// [ -// [Y+Z+, Y-Z+, Y-Z-, Y+Z-], -// [X+Z+, X-Z+, X-Z-, X+Z-], -// [X+Y+, X-Y+, X-Y-, X+Y-] -// ] -// Example: -// include -// fillet(fillet=10, size=[50,100,150], edges=EDGES_TOP + EDGES_RIGHT - EDGE_BOT_RT, $fn=24) { +// Description: +// You should use `EDGE` constants from `constants.scad` with the `edge` argument. +// However, if you must handle it raw, the edge ordering is this: +// [ +// [Y+Z+, Y-Z+, Y-Z-, Y+Z-], +// [X+Z+, X-Z+, X-Z-, X+Z-], +// [X+Y+, X-Y+, X-Y-, X+Y-] +// ] +// Example(FR): +// fillet(fillet=10, size=[50,100,150], $fn=24) { // cube(size=[50,100,150], center=true); // } -module fillet(fillet=1, size=[1,1,1], edges=[[0,0,0,0], [1,1,0,0], [0,0,0,0]]) +// Example(FR,FlatSpin): +// fillet(fillet=10, size=[50,50,75], edges=EDGES_TOP - EDGE_TOP_LF + EDGE_FR_RT, $fn=24) { +// cube(size=[50,50,75], center=true); +// } +module fillet(fillet=1, size=[1,1,1], edges=EDGES_ALL) { - eps = 0.1; - x = size[0]; - y = size[1]; - z = size[2]; - lx = x + eps; - ly = y + eps; - lz = z + eps; - rx = x - 2*fillet; - ry = y - 2*fillet; - rz = z - 2*fillet; - majrots = [[0,90,0], [90,0,0], [0,0,0]]; - sides = quantup(segs(fillet),4); - sc = 1/cos(180/sides); difference() { children(); - - // Round edges. - for (axis=[0:2], i=[0:3]) { - if (edges[axis][i]>0) { - difference() { - translate(vmul(EDGE_OFFSETS[axis][i], [lx,ly,lz]/2)) { - rotate(majrots[axis]) { - cube([fillet*2, fillet*2, size[axis]+eps], center=true); - } - } - translate(vmul(EDGE_OFFSETS[axis][i], [rx,ry,rz]/2)) { - rotate(majrots[axis]) { - zrot(180/sides) cylinder(h=size[axis]+eps*2, r=fillet*sc, center=true, $fn=sides); - } - } - } - } - } - - // Round corners. - for (za=[-1,1], ya=[-1,1], xa=[-1,1]) { - if (corner_edge_count(edges, [xa,ya,za]) > 2) { - difference() { - translate(vmul([xa,ya,za]/2, [lx,ly,lz])) { - cube(fillet*2, center=true); - } - translate(vmul([xa,ya,za]/2, [rx,ry,rz])) { - zrot(180/sides) { - rotate_extrude(convexity=2) { - difference() { - zrot(180/sides) circle(r=fillet*sc*sc, $fn=sides); - left(fillet*2) square(fillet*2*2, center=true); - } - } - } - } - } - } + difference() { + cube(size, center=true); + cuboid(size+[1,1,1]*0.01, fillet=fillet, edges=edges, trimcorners=true); } } } -// Creates a vertical mask that can be used to fillet the edge where two -// face meet, at any arbitrary angle. Difference it from the object to -// be filletted. The center of the mask should align exactly with the -// edge to be filletted. +// Module: fillet_angled_edge_mask() +// Usage: +// fillet_angled_edge_mask(h, r, [ang], [center]); +// Description: +// Creates a vertical mask that can be used to fillet the edge where two +// face meet, at any arbitrary angle. Difference it from the object to +// be filletted. The center of the mask should align exactly with the +// edge to be filletted. +// Arguments: // h = height of vertical mask. // r = radius of the fillet. // ang = angle that the planes meet at. @@ -465,7 +516,7 @@ module fillet(fillet=1, size=[1,1,1], edges=[[0,0,0,0], [1,1,0,0], [0,0,0,0]]) // Example: // difference() { // angle_pie_mask(ang=70, h=50, d=100); -// fillet_angled_edge_mask(h=51, r=20.0, ang=70, $fn=32); +// #fillet_angled_edge_mask(h=51, r=20.0, ang=70, $fn=32); // } module fillet_angled_edge_mask(h=1.0, r=1.0, ang=90, center=true) { @@ -487,9 +538,14 @@ module fillet_angled_edge_mask(h=1.0, r=1.0, ang=90, center=true) } -// Creates a shape that can be used to fillet the corner of an angle. -// Difference it from the object to be filletted. The center of the mask -// object should align exactly with the point of the corner to be filletted. +// Module: fillet_angled_corner_mask() +// Usage: +// fillet_angled_corner_mask(fillet, ang); +// Description: +// Creates a shape that can be used to fillet the corner of an angle. +// Difference it from the object to be filletted. The center of the mask +// object should align exactly with the point of the corner to be filletted. +// Arguments: // fillet = radius of the fillet. // ang = angle between planes that you need to fillet the corner of. // Example: @@ -497,7 +553,7 @@ module fillet_angled_edge_mask(h=1.0, r=1.0, ang=90, center=true) // difference() { // angle_pie_mask(ang=ang, h=50, r=200); // up(50/2) { -// fillet_angled_corner_mask(fillet=20, ang=ang); +// #fillet_angled_corner_mask(fillet=20, ang=ang); // zrot_copies([0, ang]) right(200/2) fillet_mask_x(l=200, r=20); // } // fillet_angled_edge_mask(h=51, r=20, ang=ang); @@ -523,70 +579,88 @@ module fillet_angled_corner_mask(fillet=1.0, ang=90) } -// Creates a shape that you can use to round 90 degree corners on a fillet. -// Difference it from the object to be filletted. The center of the mask -// object should align exactly with the corner to be filletted. +// Module: fillet_corner_mask() +// Usage: +// fillet_corner_mask(r); +// Description: +// Creates a shape that you can use to round 90 degree corners on a fillet. +// Difference it from the object to be filletted. The center of the mask +// object should align exactly with the corner to be filletted. +// Arguments: // r = radius of corner fillet. // Example: -// $fa=1; $fs=1; +// fillet_corner_mask(r=20.0); +// Example: // difference() { -// cube(size=[6,10,16], center=true); -// translate([0, 5, 8]) yrot(90) fillet_mask(h=7, r=3); -// translate([3, 0, 8]) xrot(90) fillet_mask(h=11, r=3); -// translate([3, 5, 0]) fillet_mask(h=17, r=3); -// translate([3, 5, 8]) fillet_corner_mask(r=3); +// cube(size=[30, 50, 80], center=true); +// translate([0, 25, 40]) fillet_mask_x(l=31, r=15); +// translate([15, 0, 40]) fillet_mask_y(l=51, r=15); +// translate([15, 25, 0]) fillet_mask_z(l=81, r=15); +// translate([15, 25, 40]) #fillet_corner_mask(r=15); // } module fillet_corner_mask(r=1.0) { difference() { cube(size=r*2, center=true); - grid_of(count=[2,2,2], spacing=r*2-0.05) { - sphere(r=r, center=true); + grid3d(n=[2,2,2], spacing=r*2-0.05) { + sphere(r=r); } } } -//!fillet_corner_mask(r=10.0); -// Create a mask that can be used to round the end of a cylinder. -// Difference it from the cylinder to be filletted. The center of the -// mask object should align exactly with the center of the end of the -// cylinder to be filletted. +// Module: fillet_cylinder_mask() +// Usage: +// fillet_cylinder_mask(r, fillet, [xtilt], [ytilt]); +// Description: +// Create a mask that can be used to round the end of a cylinder. +// Difference it from the cylinder to be filletted. The center of the +// mask object should align exactly with the center of the end of the +// cylinder to be filletted. +// Arguments: // r = radius of cylinder to fillet. (Default: 1.0) // fillet = radius of the edge filleting. (Default: 0.25) // xtilt = angle of tilt of end of cylinder in the X direction. (Default: 0) // ytilt = angle of tilt of end of cylinder in the Y direction. (Default: 0) // Example: -// $fa=2; $fs=2; // difference() { -// cylinder(r=50, h=100, center=true); -// up(50) fillet_cylinder_mask(r=50, fillet=10, xtilt=30); +// cylinder(r=50, h=50, center=false); +// up(50) #fillet_cylinder_mask(r=50, fillet=10); +// } +// Example: +// difference() { +// cylinder(r=50, h=100, center=false); +// up(75) fillet_cylinder_mask(r=50, fillet=10, xtilt=30); // } module fillet_cylinder_mask(r=1.0, fillet=0.25, xtilt=0, ytilt=0) { skew_xz(za=xtilt) { skew_yz(za=ytilt) { - cylinder_mask(l=fillet*3, r=r, fillet2=fillet, ends_only=true, align=V_DOWN); + cylinder_mask(l=fillet*3, r=r, fillet2=fillet, overage=fillet+2*r*sin(max(xtilt,ytilt)), ends_only=true, align=V_DOWN); } } } -// Create a mask that can be used to round the edge of a circular hole. -// Difference it from the hole to be filletted. The center of the -// mask object should align exactly with the center of the end of the -// hole to be filletted. +// Module: fillet_hole_mask() +// Usage: +// fillet_hole_mask(r, fillet, [xtilt], [ytilt]); +// Description: +// Create a mask that can be used to round the edge of a circular hole. +// Difference it from the hole to be filletted. The center of the +// mask object should align exactly with the center of the end of the +// hole to be filletted. +// Arguments: // r = radius of hole to fillet. (Default: 1.0) // fillet = radius of the edge filleting. (Default: 0.25) // xtilt = angle of tilt of end of cylinder in the X direction. (Default: 0) // ytilt = angle of tilt of end of cylinder in the Y direction. (Default: 0) // Example: -// $fa=2; $fs=2; // difference() { // cube([150,150,100], center=true); // cylinder(r=50, h=100.1, center=true); -// up(50) fillet_hole_mask(r=50, fillet=10, xtilt=0, ytilt=0); +// up(50) #fillet_hole_mask(r=50, fillet=10, xtilt=0, ytilt=0); // } module fillet_hole_mask(r=1.0, fillet=0.25, xtilt=0, ytilt=0) { diff --git a/math.scad b/math.scad index 60edadf..63c66e2 100644 --- a/math.scad +++ b/math.scad @@ -1,5 +1,10 @@ ////////////////////////////////////////////////////////////////////// -// Math helper functions. +// LibFile: math.scad +// Math helper functions. +// To use, add the following lines to the beginning of your file: +// ``` +// use +// ``` ////////////////////////////////////////////////////////////////////// /* @@ -30,232 +35,564 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +include +// Function: Cpi() +// Status: DEPRECATED, use `PI` instead. +// Description: +// Returns the value of pi. function Cpi() = PI; // Deprecated! Use the variable PI instead. -// Quantize a value x to an integer multiple of y, rounding to the nearest multiple. +// Section: Simple Calculations + +// Function: quant() +// Description: +// Quantize a value `x` to an integer multiple of `y`, rounding to the nearest multiple. +// Arguments: +// x = The value to quantize. +// y = The multiple to quantize to. function quant(x,y) = floor(x/y+0.5)*y; -// Quantize a value x to an integer multiple of y, rounding down to the previous multiple. +// Function: quantdn() +// Description: +// Quantize a value `x` to an integer multiple of `y`, rounding down to the previous multiple. +// Arguments: +// x = The value to quantize. +// y = The multiple to quantize to. function quantdn(x,y) = floor(x/y)*y; -// Quantize a value x to an integer multiple of y, rounding up to the next multiple. +// Function: quantup() +// Description: +// Quantize a value `x` to an integer multiple of `y`, rounding up to the next multiple. +// Arguments: +// x = The value to quantize. +// y = The multiple to quantize to. function quantup(x,y) = ceil(x/y)*y; -// Calculate OpenSCAD standard number of segments in a circle based on $fn, $fa, and $fs. -// r = radius of circle to get the number of segments for. -function segs(r) = $fn>0?($fn>3?$fn:3):(ceil(max(min(360.0/$fa,abs(r)*2*PI/$fs),5))); - - -// Interpolate between two values or vectors. 0.0 <= u <= 1.0 -function lerp(a,b,u) = (1-u)*a + u*b; - - -// Calculate hypotenuse length of 2D triangle. -function hypot(x,y) = sqrt(x*x+y*y); - - -// Calculate hypotenuse length of 3D triangle. -function hypot3(x,y,z) = sqrt(x*x+y*y+z*z); - - -// Returns all but the first item of a given array. -function cdr(list) = len(list)>1?[for (i=[1:len(list)-1]) list[i]]:[]; - - -// Reverses a list/array. -function reverse(list) = [ for (i = [len(list)-1 : -1 : 0]) list[i] ]; - - -// Returns a slice of the given array, wrapping around past the beginning, if end < start -function wrap_range(list, start, end) = - let( - l = len(list), - st = start<0? (start%l)+l : (start%l), - en = end<0? (end%l)+l : (end%l) - ) - (en=len(v))? 0 : ((v[n]*v[n]) + sum_of_squares(v,n+1)); - - -// Returns a 3D vector/point from a 2D or 3D vector. -function point3d(p) = [p[0], p[1], ((len(p) < 3)? 0 : p[2])]; - - -// Returns an array of 3D vectors/points from a 2D or 3D vector array. -function path3d(points) = [for (point = points) point3d(point)]; - - -// Returns the distance between a pair of 2D or 3D points. -function distance(p1, p2) = let(d = point3d(p2) - point3d(p1)) hypot3(d[0], d[1], d[2]); - - -// Multiplies corresponding elements in two vectors. -function vmul(v1, v2) = [for (i = [0:len(v1)-1]) v1[i]*v2[i]]; - - -// Create an identity matrix, for a given number of axes. -function ident(n) = [for (i = [0:n-1]) [for (j = [0:n-1]) (i==j)?1:0]]; - - -// Create an identity matrix, for 3 axes. -ident3 = ident(3); -ident4 = ident(4); - - -// Takes a 3x3 matrix and returns its 4x4 equivalent. -function mat3_to_mat4(m) = concat( - [for (r = [0:2]) - concat( - [for (c = [0:2]) m[r][c]], - [0] - ) - ], - [[0, 0, 0, 1]] -); - - -// Returns the 3x3 matrix to perform a rotation of a vector around the X axis. -// ang = number of degrees to rotate. -function matrix3_xrot(ang) = [ - [1, 0, 0], - [0, cos(ang), -sin(ang)], - [0, sin(ang), cos(ang)] -]; - - -// Returns the 4x4 matrix to perform a rotation of a vector around the X axis. -// ang = number of degrees to rotate. -function matrix4_xrot(ang) = mat3_to_mat4(matrix3_xrot(ang)); - - -// Returns the 3x3 matrix to perform a rotation of a vector around the Y axis. -// ang = number of degrees to rotate. -function matrix3_yrot(ang) = [ - [ cos(ang), 0, sin(ang)], - [ 0, 1, 0], - [-sin(ang), 0, cos(ang)], -]; - - -// Returns the 4x4 matrix to perform a rotation of a vector around the Y axis. -// ang = number of degrees to rotate. -function matrix4_yrot(ang) = mat3_to_mat4(matrix3_yrot(ang)); - - -// Returns the 3x3 matrix to perform a rotation of a vector around the Z axis. -// ang = number of degrees to rotate. -function matrix3_zrot(ang) = [ - [cos(ang), -sin(ang), 0], - [sin(ang), cos(ang), 0], - [ 0, 0, 1] -]; - -// Returns the 4x4 matrix to perform a rotation of a vector around the Z axis. -// ang = number of degrees to rotate. -function matrix4_zrot(ang) = mat3_to_mat4(matrix3_zrot(ang)); - - -// Returns the 3x3 matrix to perform a rotation of a vector around an axis. -// u = axis vector to rotate around. -// ang = number of degrees to rotate. -function matrix3_rot_by_axis(u, ang) = let( - u = normalize(u), - c = cos(ang), - c2 = 1-c, s = sin(ang) -) [ - [u[0]*u[0]*c2+c, u[0]*u[1]*c2-u[2]*s, u[0]*u[2]*c2+u[1]*s], - [u[1]*u[0]*c2+u[2]*s, u[1]*u[1]*c2+c, u[1]*u[2]*c2-u[0]*s], - [u[2]*u[0]*c2-u[1]*s, u[2]*u[1]*c2+u[0]*s, u[2]*u[2]*c2+c ] -]; - - -// Returns the 4x4 matrix to perform a rotation of a vector around an axis. -// u = axis vector to rotate around. -// ang = number of degrees to rotate. -function matrix4_rot_by_axis(u, ang) = mat3_to_mat4(matrix3_rot_by_axis(u, ang)); - - -// moves each point in an array by a given amount. -function translate_points(pts, v=[0,0,0]) = [for (pt = pts) pt+v]; - - -// Scales each point in an array by a given amount, around a given centerpoint. -function scale_points(pts, v=[0,0,0], cp=[0,0,0]) = [for (pt = pts) [for (i = [0:len(pt)-1]) (pt[i]-cp[i])*v[i]+cp[i]]]; - - -// Rotates each 2D point in an array by a given amount, around a given centerpoint. -function rotate_points2d(pts, ang, cp=[0,0]) = let( - m = matrix3_zrot(ang) - ) [for (pt = pts) m*point3d(pt-cp)+cp]; - - -// Rotates each 3D point in an array by a given amount, around a given centerpoint. -function rotate_points3d(pts, v=[0,0,0], cp=[0,0,0]) = let( - m = matrix4_zrot(v[2]) * matrix4_yrot(v[1]) * matrix4_xrot(v[0]) - ) [for (pt = pts) m*concat(point3d(pt)-cp, 0)+cp]; - - -// Rotates each 3D point in an array by a given amount, around a given centerpoint and axis. -function rotate_points3d_around_axis(pts, ang, u=[0,0,0], cp=[0,0,0]) = let( - m = matrix4_rot_by_axis(u, ang) - ) [for (pt = pts) m*concat(point3d(pt)-cp, 0)+cp]; - - -// Gives the sum of a series of sines, at a given angle. -// a = angle to get the value for. -// sines = array of [amplitude, frequency] pairs, where the frequency is the -// number of times the cycle repeats around the circle. -function sum_of_sines(a,sines) = len(sines)==0? 0 : - len(sines)==1?sines[0][0]*sin(a*sines[0][1]+(len(sines[0])>2?sines[0][2]:0)): - sum_of_sines(a,[sines[0]])+sum_of_sines(a,cdr(sines)); - - -// Constrains value to a range of values between minval and maxval, inclusive. +// Function: constrain() +// Usage: +// constrain(v, minval, maxval); +// Description: +// Constrains value to a range of values between minval and maxval, inclusive. +// Arguments: // v = value to constrain. // minval = minimum value to return, if out of range. // maxval = maximum value to return, if out of range. function constrain(v, minval, maxval) = min(maxval, max(minval, v)); -// Returns unit length normalized version of vector v. + +// Function: posmod() +// Usage: +// posmod(x,m) +// Description: +// Returns the positive modulo `m` of `x`. Value returned will be in the range 0 ... `m`-1. +// This if useful for normalizing angles to 0 ... 360. +// Arguments: +// x = The value to constrain. +// m = Modulo value. +function posmod(x,m) = (x % m + m) % m; + + +// Function: modrange() +// Usage: +// modrange(x, y, m, [step]) +// Description: +// Returns a normalized list of values from `x` to `y`, by `step`, modulo `m`. Wraps if `x` > `y`. +// Arguments: +// x = The start value to constrain. +// y = The end value to constrain. +// m = Modulo value. +// step = Step by this amount. +// Examples: +// echo(modrange(90,270,360, step=45)); // Outputs [90,135,180,225,270] +// echo(modrange(270,90,360, step=45)); // Outputs [270,315,0,45,90] +// echo(modrange(90,270,360, step=-45)); // Outputs [90,45,0,315,270] +// echo(modrange(270,90,360, step=-45)); // Outputs [270,225,180,135,90] +function modrange(x, y, m, step=1) = + let( + a = posmod(x, m), + b = posmod(y, m), + c = step>0? (a>b? b+m : b) : (a0?($fn>3?$fn:3):(ceil(max(min(360.0/$fa,abs(r)*2*PI/$fs),5))); + + +// Function: lerp() +// Description: Interpolate between two values or vectors. +// Arguments: +// a = First value. +// b = Second value. +// u = The proportion from `a` to `b` to calculate. Valid range is 0.0 to 1.0, inclusive. +function lerp(a,b,u) = (1-u)*a + u*b; + + +// Function: hypot() +// Description: Calculate hypotenuse length of a 2D or 3D triangle. +// Arguments: +// x = Length on the X axis. +// y = Length on the Y axis. +// z = Length on the Z axis. +function hypot(x,y,z=0) = norm([x,y,z]); + + +// Function: hypot3() +// Status: DEPRECATED, use `norm([x,y,z])` instead. +// Description: Calculate hypotenuse length of 3D triangle. +// Arguments: +// x = Length on the X axis. +// y = Length on the Y axis. +// z = Length on the Z axis. +function hypot3(x,y,z) = norm([x,y,z]); + + +// Function: distance() +// Status: DEPRECATED, use `norm(p2-p1)` instead. It's shorter. +// Description: Returns the distance between a pair of 2D or 3D points. +function distance(p1, p2) = norm(point3d(p2)-point3d(p1)); + + +// Function: sinh() +// Description: Takes a radians value `x`, and returns the hyperbolic sine of it. +function sinh(x) = (exp(x)-exp(-x))/2; + + +// Function: cosh() +// Description: Takes a radians value `x`, and returns the hyperbolic cosine of it. +function cosh(x) = (exp(x)+exp(-x))/2; + + +// Function: tanh() +// Description: Takes a radians value `x`, and returns the hyperbolic tangent of it. +function tanh(x) = sinh(x)/cosh(x); + + +// Function: asinh() +// Description: Takes a value `x`, and returns the inverse hyperbolic sine of it in radians. +function asinh(x) = ln(x+sqrt(x*x+1)); + + +// Function: acosh() +// Description: Takes a value `x`, and returns the inverse hyperbolic cosine of it in radians. +function acosh(x) = ln(x+sqrt(x*x-1)); + + +// Function: atanh() +// Description: Takes a value `x`, and returns the inverse hyperbolic tangent of it in radians. +function atanh(x) = ln((1+x)/(1-x))/2; + + +// Function: sum() +// Description: +// Returns the sum of all entries in the given array. +// If passed an array of vectors, returns a vector of sums of each part. +// Arguments: +// v = The vector to get the sum of. +// Example: +// sum([1,2,3]); // returns 6. +// sum([[1,2,3], [3,4,5], [5,6,7]]); // returns [9, 12, 15] +function sum(v, i=0, tot=undef) = i>=len(v)? tot : sum(v, i+1, ((tot==undef)? v[i] : tot+v[i])); + + +// Function: sum_of_squares() +// Description: +// Returns the sum of the square of each element of a vector. +// Arguments: +// v = The vector to get the sum of. +// Example: +// sum_of_squares([1,2,3]); // returns 14. +function sum_of_squares(v, i=0, tot=0) = sum(vmul(v,v)); + + +// Function: sum_of_sines() +// Usage: +// sum_of_sines(a,sines) +// Description: +// Gives the sum of a series of sines, at a given angle. +// Arguments: +// a = Angle to get the value for. +// sines = List of [amplitude, frequency, offset] items, where the frequency is the number of times the cycle repeats around the circle. +function sum_of_sines(a, sines) = + sum([ + for (s = sines) let( + ss=point3d(s), + v=ss.x*sin(a*ss.y+ss.z) + ) v + ]); + + +// Function: mean() +// Description: +// Returns the mean of all entries in the given array. +// If passed an array of vectors, returns a vector of mean of each part. +// Arguments: +// v = The list of values to get the mean of. +// Example: +// mean([2,3,4]); // returns 4.5. +// mean([[1,2,3], [3,4,5], [5,6,7]]); // returns [4.5, 6, 7.5] +function mean(v) = sum(v)/len(v); + + +// Section: List/Array Operations + +// Function: cdr() +// Status: DEPRECATED, use `slice(list,1,-1)` instead. +// Description: Returns all but the first item of a given array. +// Arguments: +// list = The list to get the tail of. +function cdr(list) = len(list)<=1? [] : [for (i=[1:len(list)-1]) list[i]]; + + +// Function: any() +// Description: Returns true if any item in list `l` evaluates as true. +// Arguments: +// l = The list to test for true items. +// Example: +// any([0,false,undef]); // Returns false. +// any([1,false,undef]); // Returns true. +// any([1,5,true]); // Returns true. +function any(l) = sum([for (x=l) x?1:0]) > 0; + + +// Function: all() +// Description: Returns true if all items in list `l` evaluate as true. +// Arguments: +// l = The list to test for true items. +// Example: +// all([0,false,undef]); // Returns false. +// all([1,false,undef]); // Returns false. +// all([1,5,true]); // Returns true. +function all(l) = sum([for (x=l) x?1:0]) == len(l); + + +// Function: in_list() +// Description: Returns true if value `x` is in list `l`. +// Arguments: +// x = The value to search for. +// l = The list to search. +// idx = If given, searches the given subindexes for matches for `x`. +// Example: +// in_list("bar", ["foo", "bar", "baz"]); // Returns true. +// in_list("bee", ["foo", "bar", "baz"]); // Returns false. +// in_list("bar", [[2,"foo"], [4,"bar"], [3,"baz"]], idx=1); // Returns true. +function in_list(x,l,idx=undef) = search([x], l, num_returns_per_match=1, index_col_num=idx) != [[]]; + + +// Function: slice() +// Description: +// Returns a slice of a list. The first item is index 0. +// Negative indexes are counted back from the end. The last item is -1. +// Arguments: +// arr = The array/list to get the slice of. +// st = The index of the first item to return. +// end = The index after the last item to return, unless negative, in which case the last item to return. +// Example: +// slice([3,4,5,6,7,8,9], 3, 5); // Returns [6,7] +// slice([3,4,5,6,7,8,9], 2, -1); // Returns [5,6,7,8,9] +// slice([3,4,5,6,7,8,9], 1, 1); // Returns [] +// slice([3,4,5,6,7,8,9], 6, -1); // Returns [9] +// slice([3,4,5,6,7,8,9], 2, -2); // Returns [5,6,7,8] +function slice(arr,st,end) = let( + s=st<0?(len(arr)+st):st, + e=end<0?(len(arr)+end+1):end + ) (s==e)? [] : [for (i=[s:e-1]) if (e>s) arr[i]]; + + +// Function: wrap_range() +// Description: +// Returns a portion of a list, wrapping around past the beginning, if ends) arr[i]]; +// Section: Matrix Manipulation + +// Function: ident() +// Description: Create an `n` by `n` identity matrix. +// Arguments: +// n = The size of the identity matrix square, `n` by `n`. +function ident(n) = [for (i = [0:n-1]) [for (j = [0:n-1]) (i==j)?1:0]]; -function default(v,dflt) = v==undef? dflt : v; +// Create an identity matrix, for 3 axes. +ident3 = ident(3); +ident4 = ident(4); -function get_radius(r1=undef, r=undef, d1=undef, d=undef, dflt=undef) = ( - (r1!=undef)? r1 : - (d1!=undef)? d1/2 : - (r!=undef)? r : - (d!=undef)? d/2 : - dflt +// Function: mat3_to_mat4() +// Description: Takes a 3x3 matrix and returns its 4x4 equivalent. +function mat3_to_mat4(m) = concat( + [for (r = [0:2]) + concat( + [for (c = [0:2]) m[r][c]], + [0] + ) + ], + [[0, 0, 0, 1]] ); -// Returns the first item in the list that is not undef. -function first_defined(v) = [for (x = v) if (x!=undef) x][0]; +// Function: matrix3_translate() +// Description: +// Returns the 3x3 matrix to perform a 2D translation. +// Arguments: +// v = 2D Offset to translate by. [X,Y] +function matrix3_translate(v) = [ + [1, 0, v.x], + [0, 1, v.y], + [0 ,0, 1] +]; -// Returns true if any item in the given array is not undef. -function any_defined(v) = len([for (x = v) if (x!=undef) x])>0; +// Function: matrix4_translate() +// Description: +// Returns the 4x4 matrix to perform a 3D translation. +// Arguments: +// v = 3D offset to translate by. [X,Y,Z] +function matrix4_translate(v) = [ + [1, 0, 0, v.x], + [0, 1, 0, v.y], + [0, 0, 1, v.z], + [0 ,0, 0, 1] +]; -// If given a vector, returns the vector. If given a scalar, returns [scalar, scalar, scalar] -function scalar_vec(v) = v[0]==undef? [v,v,v] : v; +// Function: matrix3_scale() +// Description: +// Returns the 3x3 matrix to perform a 2D scaling transformation. +// Arguments: +// v = 2D vector of scaling factors. [X,Y] +function matrix3_scale(v) = [ + [v.x, 0, 0], + [ 0, v.y, 0], + [ 0, 0, 1] +]; +// Function: matrix4_scale() +// Description: +// Returns the 4x4 matrix to perform a 3D scaling transformation. +// Arguments: +// v = 3D vector of scaling factors. [X,Y,Z] +function matrix4_scale(v) = [ + [v.x, 0, 0, 0], + [ 0, v.y, 0, 0], + [ 0, 0, v.z, 0], + [ 0, 0, 0, 1] +]; + + +// Function: matrix3_zrot() +// Description: +// Returns the 3x3 matrix to perform a rotation of a 2D vector around the Z axis. +// Arguments: +// ang = Number of degrees to rotate. +function matrix3_zrot(ang) = [ + [cos(ang), -sin(ang), 0], + [sin(ang), cos(ang), 0], + [ 0, 0, 1] +]; + + +// Function: matrix4_xrot() +// Description: +// Returns the 4x4 matrix to perform a rotation of a 3D vector around the X axis. +// Arguments: +// ang = number of degrees to rotate. +function matrix4_xrot(ang) = [ + [1, 0, 0, 0], + [0, cos(ang), -sin(ang), 0], + [0, sin(ang), cos(ang), 0], + [0, 0, 0, 1] +]; + + +// Function: matrix4_yrot() +// Description: +// Returns the 4x4 matrix to perform a rotation of a 3D vector around the Y axis. +// Arguments: +// ang = Number of degrees to rotate. +function matrix4_yrot(ang) = [ + [ cos(ang), 0, sin(ang), 0], + [ 0, 1, 0, 0], + [-sin(ang), 0, cos(ang), 0], + [ 0, 0, 0, 1] +]; + + +// Function: matrix4_zrot() +// Usage: +// matrix4_zrot(ang) +// Description: +// Returns the 4x4 matrix to perform a rotation of a 3D vector around the Z axis. +// Arguments: +// ang = number of degrees to rotate. +function matrix4_zrot(ang) = [ + [cos(ang), -sin(ang), 0, 0], + [sin(ang), cos(ang), 0, 0], + [ 0, 0, 1, 0], + [ 0, 0, 0, 1] +]; + + +// Function: matrix4_rot_by_axis() +// Usage: +// matrix4_rot_by_axis(u, ang); +// Description: +// Returns the 4x4 matrix to perform a rotation of a 3D vector around an axis. +// Arguments: +// u = 3D axis vector to rotate around. +// ang = number of degrees to rotate. +function matrix4_rot_by_axis(u, ang) = let( + u = normalize(u), + c = cos(ang), + c2 = 1-c, s = sin(ang) +) [ + [u[0]*u[0]*c2+c , u[0]*u[1]*c2-u[2]*s, u[0]*u[2]*c2+u[1]*s, 0], + [u[1]*u[0]*c2+u[2]*s, u[1]*u[1]*c2+c , u[1]*u[2]*c2-u[0]*s, 0], + [u[2]*u[0]*c2-u[1]*s, u[2]*u[1]*c2+u[0]*s, u[2]*u[2]*c2+c , 0], + [ 0, 0, 0, 1] +]; + + +// Function: matrix3_skew() +// Usage: +// matrix3_skew(xa, ya) +// Description: +// Returns the 3x3 matrix to skew a 2D vector along the XY plane. +// Arguments: +// xa = Skew angle, in degrees, in the direction of the X axis. +// ya = Skew angle, in degrees, in the direction of the Y axis. +function matrix3_skew(xa, ya) = [ + [1, tan(xa), 0], + [tan(ya), 1, 0], + [0, 0, 1] +]; + + + +// Function: matrix4_skew_xy() +// Usage: +// matrix4_skew_xy(xa, ya) +// Description: +// Returns the 4x4 matrix to perform a skew transformation along the XY plane.. +// Arguments: +// xa = Skew angle, in degrees, in the direction of the X axis. +// ya = Skew angle, in degrees, in the direction of the Y axis. +function matrix4_skew_xy(xa, ya) = [ + [1, 0, tan(xa), 0], + [0, 1, tan(ya), 0], + [0, 0, 1, 0], + [0, 0, 0, 1] +]; + + + +// Function: matrix4_skew_xz() +// Usage: +// matrix4_skew_xz(xa, za) +// Description: +// Returns the 4x4 matrix to perform a skew transformation along the XZ plane. +// Arguments: +// xa = Skew angle, in degrees, in the direction of the X axis. +// za = Skew angle, in degrees, in the direction of the Z axis. +function matrix4_skew_xz(xa, za) = [ + [1, tan(xa), 0, 0], + [0, 1, 0, 0], + [0, tan(za), 1, 0], + [0, 0, 0, 1] +]; + + +// Function: matrix4_skew_yz() +// Usage: +// matrix4_skew_yz(ya, za) +// Description: +// Returns the 4x4 matrix to perform a skew transformation along the YZ plane. +// Arguments: +// ya = Skew angle, in degrees, in the direction of the Y axis. +// za = Skew angle, in degrees, in the direction of the Z axis. +function matrix4_skew_yz(ya, za) = [ + [ 1, 0, 0, 0], + [tan(ya), 1, 0, 0], + [tan(za), 0, 1, 0], + [ 0, 0, 0, 1] +]; + + +// Function: matrix3_mult() +// Usage: +// matrix3_mult(matrices) +// Description: +// Returns a 3x3 transformation matrix which results from applying each matrix in `matrices` in order. +// Arguments: +// matrices = A list of 3x3 matrices. +// m = Optional starting matrix to apply everything to. +function matrix3_mult(matrices, m=ident(3), i=0) = + (i>=len(matrices))? m : + let (newmat = is_def(m)? matrices[i] * m : matrices[i]) + matrix3_mult(matrices, m=newmat, i=i+1); + + +// Function: matrix4_mult() +// Usage: +// matrix4_mult(matrices) +// Description: +// Returns a 4x4 transformation matrix which results from applying each matrix in `matrices` in order. +// Arguments: +// matrices = A list of 4x4 matrices. +// m = Optional starting matrix to apply everything to. +function matrix4_mult(matrices, m=ident(4), i=0) = + (i>=len(matrices))? m : + let (newmat = is_def(m)? matrices[i] * m : matrices[i]) + matrix4_mult(matrices, m=newmat, i=i+1); + + +// Function: matrix3_apply() +// Usage: +// matrix3_apply(pts, matrices) +// Description: +// Given a list of transformation matrices, applies them in order to the points in the point list. +// Arguments: +// pts = A list of 2D points to transform. +// matrices = A list of 3x3 matrices to apply, in order. +// Example: +// npts = matrix3_apply( +// pts = [for (x=[0:3]) [5*x,0]], +// matrices =[ +// matrix3_scale([3,1]), +// matrix3_rot(90), +// matrix3_translate([5,5]) +// ] +// ); // Returns [[5,5], [5,20], [5,35], [5,50]] +function matrix3_apply(pts, matrices) = let(m = matrix3_mult(matrices)) [for (p = pts) point2d(m * concat(point2d(p),[1]))]; + + +// Function: matrix4_apply() +// Usage: +// matrix4_apply(pts, matrices) +// Description: +// Given a list of transformation matrices, applies them in order to the points in the point list. +// Arguments: +// pts = A list of 3D points to transform. +// matrices = A list of 4x4 matrices to apply, in order. +// Example: +// npts = matrix4_apply( +// pts = [for (x=[0:3]) [5*x,0,0]], +// matrices =[ +// matrix4_scale([2,1,1]), +// matrix4_zrot(90), +// matrix4_translate([5,5,10]) +// ] +// ); // Returns [[5,5,10], [5,15,10], [5,25,10], [5,35,10]] + +function matrix4_apply(pts, matrices) = let(m = matrix4_mult(matrices)) [for (p = pts) point3d(m * concat(point3d(p),[1]))]; + + +// Section: Geometry + +// Function: point_on_segment() +// Usage: +// point_on_segment(point, edge); +// Description: +// Determine if the point is on the line segment between two points. +// Returns true if yes, and false if not. +// Arguments: +// point = The point to check colinearity of. +// edge = Array of two points forming the line segment to test against. +function point_on_segment(point, edge) = + point==edge[0] || point==edge[1] || // The point is an endpoint + sign(edge[0].x-point.x)==sign(point.x-edge[1].x) // point is in between the + && sign(edge[0].y-point.y)==sign(point.y-edge[1].y) // edge endpoints + && point_left_of_segment(point, edge)==0; // and on the line defined by edge + + +// Function: point_left_of_segment() +// Usage: +// point_left_of_segment(point, edge); +// Description: +// Return >0 if point is left of the line defined by edge. +// Return =0 if point is on the line. +// Return <0 if point is right of the line. +// Arguments: +// point = The point to check position of. +// edge = Array of two points forming the line segment to test against. +function point_left_of_segment(point, edge) = + (edge[1].x-edge[0].x) * (point.y-edge[0].y) - (point.x-edge[0].x) * (edge[1].y-edge[0].y); + + +// Internal non-exposed function. +function _point_above_below_segment(point, edge) = + edge[0].y <= point.y? ( + (edge[1].y > point.y && point_left_of_segment(point, edge) > 0)? 1 : 0 + ) : ( + (edge[1].y <= point.y && point_left_of_segment(point, edge) < 0)? -1 : 0 + ); + + +// Function: point_in_polygon() +// Usage: +// point_in_polygon(point, path) +// Description: +// This function tests whether the given point is inside, outside or on the boundary of +// the specified polygon using the Winding Number method. (http://geomalgorithms.com/a03-_inclusion.html) +// The polygon is given as a list of points, not including the repeated end point. +// Returns -1 if the point is outside the polyon. +// Returns 0 if the point is on the boundary. +// Returns 1 if the point lies in the interior. +// The polygon does not need to be simple: it can have self-intersections. +// But the polygon cannot have holes (it must be simply connected). +// Rounding error may give mixed results for points on or near the boundary. +// Arguments: +// point = The point to check position of. +// path = The list of 2D path points forming the perimeter of the polygon. +function point_in_polygon(point, path) = + // Does the point lie on any edges? If so return 0. + sum([for(i=[0:len(path)-1]) point_on_segment(point, wrap_range(path, i, i+1))?1:0])>0 ? 0 : + // Otherwise compute winding number and return 1 for interior, -1 for exterior + sum([for(i=[0:len(path)-1]) _point_above_below_segment(point, wrap_range(path, i, i+1))]) != 0 ? 1 : -1; + + +// Function: pointlist_bounds() +// Usage: +// pointlist_bounds(pts); +// Description: +// Finds the bounds containing all the points in pts. +// Returns [[minx, miny, minz], [maxx, maxy, maxz]] +// Arguments: +// pts = List of points. +function pointlist_bounds(pts) = [ + [for (a=[0:2]) min([ for (x=pts) point3d(x)[a] ]) ], + [for (a=[0:2]) max([ for (x=pts) point3d(x)[a] ]) ] +]; + // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/metric_screws.scad b/metric_screws.scad index 6c24902..cca7864 100644 --- a/metric_screws.scad +++ b/metric_screws.scad @@ -1,5 +1,11 @@ ////////////////////////////////////////////////////////////////////// -// Screws, Bolts, and Nuts. +// LibFile: metric_screws.scad +// Screws, Bolts, and Nuts. +// To use, include the following lines at the top of your file: +// ``` +// include +// use +// ``` ////////////////////////////////////////////////////////////////////// /* @@ -31,6 +37,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +include use use use @@ -39,6 +46,11 @@ use use +// Section: Functions + + +// Function: get_metric_bolt_head_size() +// Description: Returns the diameter of a typical metric bolt's head, based on the bolt `size`. function get_metric_bolt_head_size(size) = lookup(size, [ [ 3.0, 5.5], [ 4.0, 7.0], @@ -62,6 +74,8 @@ function get_metric_bolt_head_size(size) = lookup(size, [ ]); +// Function: get_metric_bolt_head_height() +// Description: Returns the height of a typical metric bolt's head, based on the bolt `size`. function get_metric_bolt_head_height(size) = lookup(size, [ [ 1.6, 1.23], [ 2.0, 1.53], @@ -86,6 +100,8 @@ function get_metric_bolt_head_height(size) = lookup(size, [ ]); +// Function: get_metric_socket_cap_diam() +// Description: Returns the diameter of a typical metric socket cap bolt's head, based on the bolt `size`. function get_metric_socket_cap_diam(size) = lookup(size, [ [ 1.6, 3.0], [ 2.0, 3.8], @@ -114,6 +130,8 @@ function get_metric_socket_cap_diam(size) = lookup(size, [ ]); +// Function: get_metric_socket_cap_height() +// Description: Returns the height of a typical metric socket cap bolt's head, based on the bolt `size`. function get_metric_socket_cap_height(size) = lookup(size, [ [ 1.6, 1.7], [ 2.0, 2.0], @@ -142,6 +160,8 @@ function get_metric_socket_cap_height(size) = lookup(size, [ ]); +// Function: get_metric_socket_cap_socket_size() +// Description: Returns the diameter of a typical metric socket cap bolt's hex drive socket, based on the bolt `size`. function get_metric_socket_cap_socket_size(size) = lookup(size, [ [ 1.6, 1.5], [ 2.0, 1.5], @@ -170,6 +190,8 @@ function get_metric_socket_cap_socket_size(size) = lookup(size, [ ]); +// Function: get_metric_socket_cap_socket_depth() +// Description: Returns the depth of a typical metric socket cap bolt's hex drive socket, based on the bolt `size`. function get_metric_socket_cap_socket_depth(size) = lookup(size, [ [ 1.6, 0.7], [ 2.0, 1.0], @@ -198,6 +220,8 @@ function get_metric_socket_cap_socket_depth(size) = lookup(size, [ ]); +// Function: get_metric_iso_coarse_thread_pitch() +// Description: Returns the ISO metric standard coarse threading pitch for a given bolt `size`. function get_metric_iso_coarse_thread_pitch(size) = lookup(size, [ [ 1.6, 0.35], [ 2.0, 0.40], @@ -229,6 +253,8 @@ function get_metric_iso_coarse_thread_pitch(size) = lookup(size, [ ]); +// Function: get_metric_iso_fine_thread_pitch() +// Description: Returns the ISO metric standard fine threading pitch for a given bolt `size`. function get_metric_iso_fine_thread_pitch(size) = lookup(size, [ [ 1.6, 0.35], [ 2.0, 0.40], @@ -260,6 +286,8 @@ function get_metric_iso_fine_thread_pitch(size) = lookup(size, [ ]); +// Function: get_metric_iso_superfine_thread_pitch() +// Description: Returns the ISO metric standard superfine threading pitch for a given bolt `size`. function get_metric_iso_superfine_thread_pitch(size) = lookup(size, [ [ 1.6, 0.35], [ 2.0, 0.40], @@ -291,6 +319,8 @@ function get_metric_iso_superfine_thread_pitch(size) = lookup(size, [ ]); +// Function: get_metric_jis_thread_pitch() +// Description: Returns the JIS metric standard threading pitch for a given bolt `size`. function get_metric_jis_thread_pitch(size) = lookup(size, [ [ 2.0, 0.40], [ 2.5, 0.45], @@ -309,6 +339,8 @@ function get_metric_jis_thread_pitch(size) = lookup(size, [ ]); +// Function: get_metric_nut_size() +// Description: Returns the typical metric nut flat-to-flat diameter for a given bolt `size`. function get_metric_nut_size(size) = lookup(size, [ [ 2.0, 4.0], [ 2.5, 5.0], @@ -327,6 +359,8 @@ function get_metric_nut_size(size) = lookup(size, [ ]); +// Function: get_metric_nut_thickness() +// Description: Returns the typical metric nut thickness for a given bolt `size`. function get_metric_nut_thickness(size) = lookup(size, [ [ 1.6, 1.3], [ 2.0, 1.6], @@ -353,53 +387,105 @@ function get_metric_nut_thickness(size) = lookup(size, [ ]); -// Makes a very simple screw model, useful for making screwholes. + +// Section: Modules + + +// Module: screw() +// Description: +// Makes a very simple screw model, useful for making screwholes. +// Usage: +// screw(screwsize, screwlen, headsize, headlen, [countersunk], [orient], [align]) +// Arguments: // screwsize = diameter of threaded part of screw. // screwlen = length of threaded part of screw. // headsize = diameter of the screw head. // headlen = length of the screw head. // countersunk = If true, center from cap's top instead of it's bottom. -// Example: +// orient = Orientation of the screw. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the screw. Use the `V_` constants from `constants.scad` or `"sunken"`, or `"base"`. Default: `"base"`. +// Examples: // screw(screwsize=3,screwlen=10,headsize=6,headlen=3,countersunk=true); +// screw(screwsize=3,screwlen=10,headsize=6,headlen=3, align="base"); module screw( screwsize=3, screwlen=10, headsize=6, headlen=3, pitch=undef, - countersunk=false + countersunk=false, + orient=ORIENT_Z, + align="base" ) { sides = max(12, segs(screwsize/2)); - down(countersunk? headlen-0.01 : 0) { - down(screwlen/2) { - if (pitch == undef) { - cylinder(r=screwsize/2, h=screwlen+0.05, center=true, $fn=sides); - } else { - threaded_rod(d=screwsize, l=screwlen+0.05, pitch=pitch, $fn=sides); + algn = countersunk? ALIGN_NEG : align; + alignments = [ + ["base", [0,0,-headlen/2+screwlen/2]], + ["sunken", [0,0,(headlen+screwlen)/2-0.01]] + ]; + orient_and_align([headsize, headsize, headlen+screwlen], orient, algn, alignments=alignments) { + down(headlen/2-screwlen/2) { + down(screwlen/2) { + if (pitch == undef) { + cylinder(r=screwsize/2, h=screwlen+0.05, center=true, $fn=sides); + } else { + threaded_rod(d=screwsize, l=screwlen+0.05, pitch=pitch, $fn=sides); + } } + up(headlen/2) cylinder(r=headsize/2, h=headlen, center=true, $fn=sides*2); } - up(headlen/2) cylinder(r=headsize/2, h=headlen, center=true, $fn=sides*2); } } -// Makes a standard metric screw model. -// size = diameter of threaded part of screw. -// headtype = One of "hex", "pan", "button", "round", "countersunk", "oval", "socket". Default: "socket" -// l = length of screw, except for the head. +// Module: metric_bolt() +// Description: +// Makes a standard metric screw model. +// Arguments: +// size = Diameter of threaded part of screw. +// headtype = One of `"hex"`, `"pan"`, `"button"`, `"round"`, `"countersunk"`, `"oval"`, `"socket`". Default: `"socket"` +// l = Length of screw, except for the head. // shank = Length of unthreaded portion of the shaft. // pitch = If given, render threads of the given pitch. If 0, then no threads. Overrides coarse argument. // details = If true model should be rendered with extra details. (Default: false) // coarse = If true, make coarse threads instead of fine threads. Default = true -// flange = radius of flange beyond the head. Default = 0 (no flange) +// flange = Radius of flange beyond the head. Default = 0 (no flange) // phillips = If given, the size of the phillips drive hole to add. (ie: "#1", "#2", or "#3") // torx = If given, the size of the torx drive hole to add. (ie: 10, 20, 30, etc.) -// Examples: -// metric_bolt(headtype="pan", size=10, l=15, details=true, phillips="#2"); -// metric_bolt(headtype="countersunk", size=10, l=15, details=true, phillips="#2"); -// metric_bolt(headtype="socket", size=10, l=15, flange=4, coarse=false, shank=5, details=true); -// metric_bolt(headtype="hex", size=10, l=15, flange=4, coarse=false, shank=5, details=true, phillips="#2"); -// metric_bolt(headtype="hex", size=10, l=15, flange=4, coarse=false, shank=5, details=true, torx=50); +// orient = Orientation of the bolt. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the bolt. Use the `V_` constants from `constants.scad` or `"sunken"`, `"base"`, or `"shank"`. Default: `"base"`. +// Example: Bolt Head Types +// ydistribute(40) { +// xdistribute(30) { +// // Front Row, Left to Right +// metric_bolt(headtype="pan", size=10, l=15, details=true, phillips="#2"); +// metric_bolt(headtype="button", size=10, l=15, details=true, phillips="#2"); +// metric_bolt(headtype="round", size=10, l=15, details=true, phillips="#2"); +// } +// xdistribute(30) { +// // Back Row, Left to Right +// metric_bolt(headtype="socket", size=10, l=15, details=true); +// metric_bolt(headtype="hex", size=10, l=15, details=true, phillips="#2"); +// metric_bolt(headtype="countersunk", size=10, l=15, details=true, phillips="#2"); +// metric_bolt(headtype="oval", size=10, l=15, details=true, phillips="#2"); +// } +// } +// Example: Details +// metric_bolt(size=10, l=15, details=true, $fn=32); +// Example: No Details Except Threads +// metric_bolt(size=10, l=15); +// Example: No Details, No Threads +// metric_bolt(size=10, l=15, pitch=0); +// Example: Fine Threads +// metric_bolt(size=10, l=15, coarse=false); +// Example: Flange +// metric_bolt(size=10, l=15, flange=5); +// Example: Shank +// metric_bolt(size=10, l=25, shank=10); +// Example: Hex Head with Phillips +// metric_bolt(headtype="hex", size=10, l=15, phillips="#2"); +// Example: Hex Head with Torx +// metric_bolt(headtype="hex", size=10, l=15, torx=50); module metric_bolt( headtype="socket", size=3, @@ -410,7 +496,9 @@ module metric_bolt( coarse=true, phillips=undef, torx=undef, - flange=0 + flange=0, + orient=ORIENT_Z, + align="base" ) { D = headtype != "hex"? get_metric_socket_cap_diam(size) : @@ -427,122 +515,157 @@ module metric_bolt( bevtop = (tcirc-D)/2; bevbot = P/2; + //algn = (headtype == "countersunk" || headtype == "oval")? (D-size)/2 : 0; + headlen = ( + (headtype == "pan" || headtype == "round" || headtype == "button")? H*0.75 : + (headtype == "countersunk")? (D-size)/2 : + (headtype == "oval")? ((D-size)/2 + D/2/3) : + H + ); + base = l/2 - headlen/2; + sunklen = ( + (headtype == "oval")? (D-size)/2 : + headlen-0.001 + ); + + alignments = [ + ["sunken", [0,0,base+sunklen]], + ["base", [0,0,base]], + ["shank", [0,0,base-shank]] + ]; + color("silver") - down(headtype == "countersunk" || headtype == "oval"? (D-size)/2 : 0) { - difference() { - union() { - // Head - if (headtype == "hex") { - difference() { - cylinder(d=tcirc, h=H, center=false, $fn=6); + orient_and_align([D+flange, D+flange, headlen+l], orient, align, alignments=alignments) { + up(base) { + difference() { + union() { + // Head + if (headtype == "hex") { + difference() { + cylinder(d=tcirc, h=H, center=false, $fn=6); - // Bevel hex nut top - if (details) { - up(H-bevtop) { + // Bevel hex nut top + if (details) { + up(H-bevtop) { + difference() { + upcube([tcirc+1, tcirc+1, bevtop+0.5]); + down(0.01) cylinder(d1=tcirc, d2=tcirc-bevtop*2, h=bevtop+0.02, center=false); + } + } + } + } + } else if (headtype == "socket") { + sockw = get_metric_socket_cap_socket_size(size); + sockd = get_metric_socket_cap_socket_depth(size); + difference() { + cylinder(d=D, h=H, center=false); + up(H-sockd) cylinder(h=sockd+0.1, d=sockw/cos(30), center=false, $fn=6); + if (details) { + kcnt = 36; + zring(n=kcnt, r=D/2) up(H/3) upcube([PI*D/kcnt/2, PI*D/kcnt/2, H]); + } + } + } else if (headtype == "pan") { + cyl(l=H*0.75, d=D, fillet2=H*0.75/2, align=V_UP); + } else if (headtype == "round") { + top_half() zscale(H*0.75/D*2) sphere(d=D); + } else if (headtype == "button") { + up(H*0.75/3) top_half() zscale(H*0.75*2/3/D*2) sphere(d=D); + cylinder(d=D, h=H*0.75/3+0.01, center=false); + } else if (headtype == "countersunk") { + cylinder(h=(D-size)/2, d1=size, d2=D, center=false); + } else if (headtype == "oval") { + up((D-size)/2) top_half() zscale(0.333) sphere(d=D); + cylinder(h=(D-size)/2, d1=size, d2=D, center=false); + } + + // Flange + if (flange>0) { + up(headtype == "countersunk" || headtype == "oval"? (D-size)/2 : 0) { + cylinder(d=D+flange, h=H/8, center=false); + up(H/8) cylinder(d1=D+flange, d2=D, h=H/8, center=false); + } + } + + // Unthreaded Shank + if (tlen < l) { + down(l-tlen) cylinder(d=size, h=l-tlen+0.05, center=false, $fn=sides); + } + + // Threads + down(l) { + difference() { + up(tlen/2+0.05) { + if (tlen > 0) { + if (P > 0) { + threaded_rod(d=size, l=tlen+0.05, pitch=P, $fn=sides); + } else { + cylinder(d=size, h=tlen+0.05, $fn=sides, center=true); + } + } + } + + // Bevel bottom end of threads + if (details) { difference() { - upcube([tcirc+1, tcirc+1, bevtop+0.5]); - down(0.01) cylinder(d1=tcirc, d2=tcirc-bevtop*2, h=bevtop+0.02, center=false); + down(0.5) upcube([size+1, size+1, bevbot+0.5]); + cylinder(d1=size-bevbot*2, d2=size, h=bevbot+0.01, center=false); } } } } - } else if (headtype == "socket") { - sockw = get_metric_socket_cap_socket_size(size); - sockd = get_metric_socket_cap_socket_depth(size); - difference() { - cylinder(d=D, h=H, center=false); - up(H-sockd) cylinder(h=sockd+0.1, d=sockw/cos(30), center=false, $fn=6); - if (details) { - kcnt = 36; - zring(n=kcnt, r=D/2) up(H/3) upcube([PI*D/kcnt/2, PI*D/kcnt/2, H]); - } - } - } else if (headtype == "pan") { - top_half() rcylinder(h=H*0.75*2, d=D, fillet=H/2, center=true); - } else if (headtype == "round") { - top_half() zscale(H*0.75/D*2) sphere(d=D); - } else if (headtype == "button") { - up(H*0.75/3) top_half() zscale(H*0.75*2/3/D*2) sphere(d=D); - cylinder(d=D, h=H*0.75/3+0.01, center=false); - } else if (headtype == "countersunk") { - cylinder(h=(D-size)/2, d1=size, d2=D, center=false); - } else if (headtype == "oval") { - up((D-size)/2) top_half() zscale(0.333) sphere(d=D); - cylinder(h=(D-size)/2, d1=size, d2=D, center=false); } - // Flange - if (flange>0) { - up(headtype == "countersunk" || headtype == "oval"? (D-size)/2 : 0) { - cylinder(d=D+flange, h=H/8, center=false); - up(H/8) cylinder(d1=D+flange, d2=D, h=H/8, center=false); + // Phillips drive hole + if (headtype != "socket" && phillips != undef) { + down(headtype != "hex"? H/6 : 0) { + phillips_drive(size=phillips, shaft=D); } } - // Unthreaded Shank - if (tlen < l) { - down(l-tlen) cylinder(d=size, h=l-tlen+0.05, center=false, $fn=sides); + // Torx drive hole + if (headtype != "socket" && torx != undef) { + up(1) torx_drive(size=torx, l=H+0.1, center=false); } - - // Threads - down(l) { - difference() { - up(tlen/2+0.05) { - if (tlen > 0) { - if (P > 0) { - threaded_rod(d=size, l=tlen+0.05, pitch=P, $fn=sides); - } else { - cylinder(d=size, h=tlen+0.05, $fn=sides, center=true); - } - } - } - - // Bevel bottom end of threads - if (details) { - difference() { - down(0.5) upcube([size+1, size+1, bevbot+0.5]); - cylinder(d1=size-bevbot*2, d2=size, h=bevbot+0.01, center=false); - } - } - } - } - } - - // Phillips drive hole - if (headtype != "socket" && phillips != undef) { - down(headtype != "hex"? H/6 : 0) { - phillips_drive(size=phillips, shaft=D); - } - } - - // Torx drive hole - if (headtype != "socket" && torx != undef) { - up(1) torx_drive(size=torx, l=H+0.1, center=false); } } } } -// Makes a model of a standard nut for a standard metric screw. +// Module: metric_nut() +// Description: +// Makes a model of a standard nut for a standard metric screw. +// Arguments: // size = standard metric screw size in mm. (Default: 3) // hole = include the hole in the nut. (Default: true) // pitch = pitch of threads in the hole. No threads if not given. // flange = radius of flange beyond the head. Default = 0 (no flange) // details = true if model should be rendered with extra details. (Default: false) -// center = If true, center the nut at the origin, otherwise on top of the XY plane. Default = false. -// Example: -// metric_nut(size=6, hole=false); -// metric_nut(size=8, hole=true); -// metric_nut(size=6, hole=true, pitch=1, details=true, center=true); -// metric_nut(size=8, hole=true, pitch=1, details=true, flange=3, center=true); +// orient = Orientation of the nut. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the nut. Use the `V_` constants from `constants.scad`. Default: `V_UP`. +// center = If true, centers the nut at the origin. If false, sits on top of XY plane. Overrides `align` if given. +// Example: No details, No Hole. Useful for a mask. +// metric_nut(size=10, hole=false); +// Example: Hole, with No Threads +// metric_nut(size=10, hole=true); +// Example: Threads +// metric_nut(size=10, hole=true, pitch=1.5); +// Example: Details +// metric_nut(size=10, hole=true, pitch=1.5, details=true); +// Example: Centered +// metric_nut(size=10, hole=true, pitch=1.5, details=true, center=true); +// Example: Flange +// metric_nut(size=10, hole=true, pitch=1.5, flange=3, details=true); module metric_nut( size=3, hole=true, pitch=undef, details=false, flange=0, - center=false + center=undef, + orient=ORIENT_Z, + align=V_UP ) { H = get_metric_nut_thickness(size); D = get_metric_nut_size(size); @@ -550,9 +673,9 @@ module metric_nut( nutfn = max(12, segs(D/2)); dcirc = D/cos(30); bevtop = (dcirc - D)/2; - offset = (center == true)? 0 : H/2; + color("silver") - up(offset) { + orient_and_align([dcirc+flange, dcirc+flange, H], orient, align, center) { difference() { union() { difference() { diff --git a/nema_steppers.scad b/nema_steppers.scad index 7e4bd3f..3901264 100644 --- a/nema_steppers.scad +++ b/nema_steppers.scad @@ -1,5 +1,11 @@ ////////////////////////////////////////////////////////////////////// -// Masks and models for NEMA stepper motors. +// LibFile: nema_steppers.scad +// Masks and models for NEMA stepper motors. +// To use, add these lines to the top of your file: +// ``` +// include +// use +// ``` ////////////////////////////////////////////////////////////////////// /* @@ -30,11 +36,20 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -include -include -include +include +use +use +use +use +// Section: Functions + + +// Function: nema_motor_width() +// Description: Gets width of NEMA motor of given standard size. +// Arguments: +// size = The standard NEMA motor size. function nema_motor_width(size) = lookup(size, [ [11.0, 28.2], [14.0, 35.2], @@ -43,6 +58,11 @@ function nema_motor_width(size) = lookup(size, [ [34.0, 86.0], ]); + +// Function: nema_motor_plinth_height() +// Description: Gets plinth height of NEMA motor of given standard size. +// Arguments: +// size = The standard NEMA motor size. function nema_motor_plinth_height(size) = lookup(size, [ [11.0, 1.5], [14.0, 2.0], @@ -51,6 +71,11 @@ function nema_motor_plinth_height(size) = lookup(size, [ [34.0, 2.03], ]); + +// Function: nema_motor_plinth_diam() +// Description: Gets plinth diameter of NEMA motor of given standard size. +// Arguments: +// size = The standard NEMA motor size. function nema_motor_plinth_diam(size) = lookup(size, [ [11.0, 22.0], [14.0, 22.0], @@ -59,6 +84,11 @@ function nema_motor_plinth_diam(size) = lookup(size, [ [34.0, 73.0], ]); + +// Function: nema_motor_screw_spacing() +// Description: Gets screw spacing of NEMA motor of given standard size. +// Arguments: +// size = The standard NEMA motor size. function nema_motor_screw_spacing(size) = lookup(size, [ [11.0, 23.11], [14.0, 26.0], @@ -67,6 +97,11 @@ function nema_motor_screw_spacing(size) = lookup(size, [ [34.0, 69.6], ]); + +// Function: nema_motor_screw_size() +// Description: Gets mount screw size of NEMA motor of given standard size. +// Arguments: +// size = The standard NEMA motor size. function nema_motor_screw_size(size) = lookup(size, [ [11.0, 2.6], [14.0, 3.0], @@ -75,6 +110,11 @@ function nema_motor_screw_size(size) = lookup(size, [ [34.0, 5.5], ]); + +// Function: nema_motor_screw_depth() +// Description: Gets mount screwhole depth of NEMA motor of given standard size. +// Arguments: +// size = The standard NEMA motor size. function nema_motor_screw_depth(size) = lookup(size, [ [11.0, 3.0], [14.0, 4.5], @@ -84,7 +124,20 @@ function nema_motor_screw_depth(size) = lookup(size, [ ]); -module nema11_stepper(h=24, shaft=5, shaft_len=20) +// Section: Motor Models + + +// Module: nema11_stepper() +// Description: Creates a model of a NEMA 11 stepper motor. +// Arguments: +// h = Length of motor body. Default: 24mm +// shaft = Shaft diameter. Default: 5mm +// shaft_len = Length of shaft protruding out the top of the stepper motor. Default: 20mm +// orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_DOWN`. +// Example: +// nema11_stepper(); +module nema11_stepper(h=24, shaft=5, shaft_len=20, orient=ORIENT_Z, align=V_DOWN) { size = 11; motor_width = nema_motor_width(size); @@ -94,30 +147,39 @@ module nema11_stepper(h=24, shaft=5, shaft_len=20) screw_size = nema_motor_screw_size(size); screw_depth = nema_motor_screw_depth(size); - difference() { - color([0.4, 0.4, 0.4]) { - translate([0, 0, -h/2]) { - rrect(size=[motor_width, motor_width, h], r=2, center=true); + orient_and_align([motor_width, motor_width, h], orient, align, orig_align=V_DOWN) { + difference() { + color([0.4, 0.4, 0.4]) + cuboid(size=[motor_width, motor_width, h], chamfer=2, edges=EDGES_Z_ALL, align=V_DOWN); + color("silver") + xspread(screw_spacing) + yspread(screw_spacing) + cyl(r=screw_size/2, h=screw_depth*2, $fn=max(12,segs(screw_size/2))); + } + color([0.6, 0.6, 0.6]) { + difference() { + cylinder(h=plinth_height, d=plinth_diam); + cyl(h=plinth_height*3, d=shaft+0.75); } } color("silver") - xspread(screw_spacing) - yspread(screw_spacing) - down(screw_depth/2-0.05) - cylinder(r=screw_size/2, h=screw_depth, center=true, $fn=max(12,segs(screw_size/2))); + cylinder(h=shaft_len, d=shaft, $fn=max(12,segs(shaft/2))); } - color([0.4, 0.4, 0.4]) - translate([0, 0, plinth_height/2]) - cylinder(h=plinth_height, r=plinth_diam/2, center=true); - color("silver") - translate([0, 0, shaft_len/2]) - cylinder(h=shaft_len, r=shaft/2, center=true, $fn=max(12,segs(shaft/2))); } -//!nema11_stepper(); -module nema14_stepper(h=24, shaft=5, shaft_len=24) +// Module: nema14_stepper() +// Description: Creates a model of a NEMA 14 stepper motor. +// Arguments: +// h = Length of motor body. Default: 24mm +// shaft = Shaft diameter. Default: 5mm +// shaft_len = Length of shaft protruding out the top of the stepper motor. Default: 24mm +// orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_DOWN`. +// Example: +// nema14_stepper(); +module nema14_stepper(h=24, shaft=5, shaft_len=24, orient=ORIENT_Z, align=V_DOWN) { size = 14; motor_width = nema_motor_width(size); @@ -127,30 +189,39 @@ module nema14_stepper(h=24, shaft=5, shaft_len=24) screw_size = nema_motor_screw_size(size); screw_depth = nema_motor_screw_depth(size); - difference() { - color([0.4, 0.4, 0.4]) { - translate([0, 0, -h/2]) { - rrect(size=[motor_width, motor_width, h], r=2, center=true); + orient_and_align([motor_width, motor_width, h], orient, align, orig_align=V_DOWN) { + difference() { + color([0.4, 0.4, 0.4]) + cuboid(size=[motor_width, motor_width, h], chamfer=2, edges=EDGES_Z_ALL, align=V_DOWN); + color("silver") + xspread(screw_spacing) + yspread(screw_spacing) + cyl(d=screw_size, h=screw_depth*2, $fn=max(12,segs(screw_size/2))); + } + color([0.6, 0.6, 0.6]) { + difference() { + cylinder(h=plinth_height, d=plinth_diam); + cyl(h=plinth_height*3, d=shaft+0.75); } } color("silver") - xspread(screw_spacing) - yspread(screw_spacing) - down(screw_depth/2-0.05) - cylinder(r=screw_size/2, h=screw_depth, center=true, $fn=max(12,segs(screw_size/2))); + cyl(h=shaft_len, d=shaft, align=V_UP, $fn=max(12,segs(shaft/2))); } - color([0.4, 0.4, 0.4]) - translate([0, 0, plinth_height/2]) - cylinder(h=plinth_height, r=plinth_diam/2, center=true); - color("silver") - translate([0, 0, shaft_len/2]) - cylinder(h=shaft_len, r=shaft/2, center=true, $fn=max(12,segs(shaft/2))); } -//!nema14_stepper(); -module nema17_stepper(h=34, shaft=5, shaft_len=20) +// Module: nema17_stepper() +// Description: Creates a model of a NEMA 17 stepper motor. +// Arguments: +// h = Length of motor body. Default: 34mm +// shaft = Shaft diameter. Default: 5mm +// shaft_len = Length of shaft protruding out the top of the stepper motor. Default: 20mm +// orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_DOWN`. +// Example: +// nema17_stepper(); +module nema17_stepper(h=34, shaft=5, shaft_len=20, orient=ORIENT_Z, align=V_DOWN) { size = 17; motor_width = nema_motor_width(size); @@ -160,49 +231,57 @@ module nema17_stepper(h=34, shaft=5, shaft_len=20) screw_size = nema_motor_screw_size(size); screw_depth = nema_motor_screw_depth(size); - difference() { - color([0.4, 0.4, 0.4]) { - down(h/2) { - rrect(size=[motor_width, motor_width, h], r=2, center=true); + orient_and_align([motor_width, motor_width, h], orient, align, orig_align=V_DOWN) { + difference() { + color([0.4, 0.4, 0.4]) + cuboid([motor_width, motor_width, h], chamfer=2, edges=EDGES_Z_ALL, align=V_DOWN); + color("silver") + xspread(screw_spacing) + yspread(screw_spacing) + cyl(d=screw_size, h=screw_depth*2, $fn=max(12,segs(screw_size/2))); + } + color([0.6, 0.6, 0.6]) { + difference() { + cylinder(h=plinth_height, d=plinth_diam); + cyl(h=plinth_height*3, d=shaft+0.75); } } - color("silver") - xspread(screw_spacing) - yspread(screw_spacing) - down(screw_depth/2-0.05) - cylinder(r=screw_size/2, h=screw_depth, center=true, $fn=max(12,segs(screw_size/2))); - } - color([0.4, 0.4, 0.4]) - up(plinth_height/2) - cylinder(h=plinth_height, r=plinth_diam/2, center=true); - color([0.9, 0.9, 0.9]) { - down(h-motor_width/12) { - fwd(motor_width/2+motor_width/24/2-0.1) { - difference() { - cube(size=[motor_width/8, motor_width/24, motor_width/8], center=true); - xrot(90) { - cylinder(d=motor_width/8-2, h=motor_width/6, center=true, $fn=12); + color([0.9, 0.9, 0.9]) { + down(h-motor_width/12) { + fwd(motor_width/2+motor_width/24/2-0.1) { + difference() { + cube(size=[motor_width/8, motor_width/24, motor_width/8], center=true); + cyl(d=motor_width/8-2, h=motor_width/6, orient=ORIENT_Y, $fn=12); + } + } + } + } + color("silver") { + difference() { + cylinder(h=shaft_len, d=shaft, $fn=max(12,segs(shaft/2))); + up(shaft_len/2+1) { + right(shaft-0.75) { + cube([shaft, shaft, shaft_len], center=true); } } } } } - color("silver") { - difference() { - cylinder(h=shaft_len, r=shaft/2, $fn=max(12,segs(shaft/2))); - up(shaft_len/2+1) { - right(shaft_len/2+shaft/2-0.5) { - cube(shaft_len, center=true); - } - } - } - } } -//!nema17_stepper(); -module nema23_stepper(h=50, shaft=6.35, shaft_len=25) +// Module: nema23_stepper() +// Description: Creates a model of a NEMA 23 stepper motor. +// Arguments: +// h = Length of motor body. Default: 50mm +// shaft = Shaft diameter. Default: 6.35mm +// shaft_len = Length of shaft protruding out the top of the stepper motor. Default: 25mm +// orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_DOWN`. +// Example: +// nema23_stepper(); +module nema23_stepper(h=50, shaft=6.35, shaft_len=25, orient=ORIENT_Z, align=V_DOWN) { size = 23; motor_width = nema_motor_width(size); @@ -213,37 +292,41 @@ module nema23_stepper(h=50, shaft=6.35, shaft_len=25) screw_depth = nema_motor_screw_depth(size); screw_inset = motor_width - screw_spacing + 1; - difference() { - union() { - color([0.4, 0.4, 0.4]) { - translate([0, 0, -h/2]) { - rrect(size=[motor_width, motor_width, h], r=2, center=true); - } + orient_and_align([motor_width, motor_width, h], orient, align, orig_align=V_DOWN) { + difference() { + union() { + color([0.4, 0.4, 0.4]) + cuboid([motor_width, motor_width, h], chamfer=2, edges=EDGES_Z_ALL, align=V_DOWN); + color([0.4, 0.4, 0.4]) + cylinder(h=plinth_height, d=plinth_diam); + color("silver") + cylinder(h=shaft_len, d=shaft, $fn=max(12,segs(shaft/2))); } - color([0.4, 0.4, 0.4]) - translate([0, 0, plinth_height/2]) - cylinder(h=plinth_height, r=plinth_diam/2, center=true); - color("silver") - translate([0, 0, shaft_len/2]) - cylinder(h=shaft_len, r=shaft/2, center=true, $fn=max(12,segs(shaft/2))); - } - color([0.4, 0.4, 0.4]) { - xspread(screw_spacing) { - yspread(screw_spacing) { - down(screw_depth/2) - cylinder(r=screw_size/2, h=screw_depth+2, center=true, $fn=max(12,segs(screw_size/2))); - down(screw_depth+h/2) - cube(size=[screw_inset, screw_inset, h], center=true); + color([0.4, 0.4, 0.4]) { + xspread(screw_spacing) { + yspread(screw_spacing) { + cyl(d=screw_size, h=screw_depth*3, $fn=max(12,segs(screw_size/2))); + down(screw_depth) cuboid([screw_inset, screw_inset, h], align=V_DOWN); + } } } } } } -//!nema23_stepper(); -module nema34_stepper(h=75, shaft=12.7, shaft_len=32) +// Module: nema34_stepper() +// Description: Creates a model of a NEMA 34 stepper motor. +// Arguments: +// h = Length of motor body. Default: 75mm +// shaft = Shaft diameter. Default: 12.7mm +// shaft_len = Length of shaft protruding out the top of the stepper motor. Default: 32mm +// orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_DOWN`. +// Example: +// nema34_stepper(); +module nema34_stepper(h=75, shaft=12.7, shaft_len=32, orient=ORIENT_Z, align=V_DOWN) { size = 34; motor_width = nema_motor_width(size); @@ -254,67 +337,195 @@ module nema34_stepper(h=75, shaft=12.7, shaft_len=32) screw_depth = nema_motor_screw_depth(size); screw_inset = motor_width - screw_spacing + 1; - difference() { - union() { - color([0.4, 0.4, 0.4]) { - translate([0, 0, -h/2]) { - rrect(size=[motor_width, motor_width, h], r=2, center=true); - } + orient_and_align([motor_width, motor_width, h], orient, align, orig_align=V_DOWN) { + difference() { + union() { + color([0.4, 0.4, 0.4]) + cuboid(size=[motor_width, motor_width, h], chamfer=2, edges=EDGES_Z_ALL, align=V_DOWN); + color([0.4, 0.4, 0.4]) + cylinder(h=plinth_height, d=plinth_diam); + color("silver") + cylinder(h=shaft_len, d=shaft, $fn=max(24,segs(shaft/2))); } - color([0.4, 0.4, 0.4]) - translate([0, 0, plinth_height/2]) - cylinder(h=plinth_height, r=plinth_diam/2, center=true); - color("silver") - translate([0, 0, shaft_len/2]) - cylinder(h=shaft_len, r=shaft/2, center=true, $fn=max(24,segs(shaft/2))); - } - color([0.4, 0.4, 0.4]) { - xspread(screw_spacing) { - yspread(screw_spacing) { - down(screw_depth/2) - cylinder(r=screw_size/2, h=screw_depth+2, center=true, $fn=max(12,segs(screw_size/2))); - down(screw_depth+h/2) - cube(size=[screw_inset, screw_inset, h], center=true); + color([0.4, 0.4, 0.4]) { + xspread(screw_spacing) { + yspread(screw_spacing) { + cylinder(d=screw_size, h=screw_depth*3, center=true, $fn=max(12,segs(screw_size/2))); + down(screw_depth) downcube([screw_inset, screw_inset, h]); + } } } } } } -//!nema34_stepper(); -module nema17_mount_holes(depth=5, l=5, slop=printer_slop) +// Section: Masking Modules + + + +// Module: nema_mount_holes() +// Description: Creates a mask to use when making standard NEMA stepper motor mounts. +// Arguments: +// size = The standard NEMA motor size to make a mount for. +// depth = The thickness of the mounting hole mask. Default: 5 +// l = The length of the slots, for making an adjustable motor mount. Default: 5 +// slop = The printer-specific slop value to make parts fit just right. Default: `PRINTER_SLOP` +// orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// Example: +// nema_mount_holes(size=14, depth=5, l=5); +// Example: +// nema_mount_holes(size=17, depth=5, l=5); +// Example: +// nema_mount_holes(size=17, depth=5, l=0); +module nema_mount_holes(size=17, depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, align=V_CENTER) { - size = 17; + motor_width = nema_motor_width(size); plinth_diam = nema_motor_plinth_diam(size)+slop; screw_spacing = nema_motor_screw_spacing(size); screw_size = nema_motor_screw_size(size)+slop; - union() { - xspread(screw_spacing) { - yspread(screw_spacing) { - if (l>0) { - union() { - yspread(l) cylinder(h=depth, d=screw_size, center=true, $fn=max(8,segs(screw_size/2))); - cube([screw_size, l, depth], center=true); + orient_and_align([motor_width, motor_width, l], orient, align) { + union() { + xspread(screw_spacing) { + yspread(screw_spacing) { + if (l>0) { + union() { + yspread(l) cyl(h=depth, d=screw_size, $fn=max(8,segs(screw_size/2))); + cube([screw_size, l, depth], center=true); + } + } else { + cyl(h=depth, d=screw_size, $fn=max(8,segs(screw_size/2))); } - } else { - cylinder(h=depth, d=screw_size, center=true, $fn=max(8,segs(screw_size/2))); } } } - } - if (l>0) { - union () { - yspread(l) cylinder(h=depth, d=plinth_diam, center=true); - cube([plinth_diam, l, depth], center=true); + if (l>0) { + union () { + yspread(l) cyl(h=depth, d=plinth_diam); + cube([plinth_diam, l, depth], center=true); + } + } else { + cyl(h=depth, d=plinth_diam); } - } else { - cylinder(h=depth, d=plinth_diam, center=true); } } -//!nema17_mount_holes(depth=5, l=5); + + + +// Module: nema11_mount_holes() +// Description: Creates a mask to use when making NEMA 11 stepper motor mounts. +// Arguments: +// depth = The thickness of the mounting hole mask. Default: 5 +// l = The length of the slots, for making an adjustable motor mount. Default: 5 +// slop = The printer-specific slop value to make parts fit just right. Default: `PRINTER_SLOP` +// orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// Example: +// nema11_mount_holes(depth=5, l=5); +// Example: +// nema11_mount_holes(depth=5, l=0); +module nema11_mount_holes(depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, align=V_CENTER) +{ + nema_mount_holes(size=11, depth=depth, l=l, slop=slop, orient=orient, align=align); +} + + + +// Module: nema14_mount_holes() +// Description: Creates a mask to use when making NEMA 14 stepper motor mounts. +// Arguments: +// depth = The thickness of the mounting hole mask. Default: 5 +// l = The length of the slots, for making an adjustable motor mount. Default: 5 +// slop = The printer-specific slop value to make parts fit just right. Default: `PRINTER_SLOP` +// orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// Example: +// nema14_mount_holes(depth=5, l=5); +// Example: +// nema14_mount_holes(depth=5, l=0); +module nema14_mount_holes(depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, align=V_CENTER) +{ + nema_mount_holes(size=14, depth=depth, l=l, slop=slop, orient=orient, align=align); +} + + + +// Module: nema17_mount_holes() +// Description: Creates a mask to use when making NEMA 17 stepper motor mounts. +// Arguments: +// depth = The thickness of the mounting hole mask. Default: 5 +// l = The length of the slots, for making an adjustable motor mount. Default: 5 +// slop = The printer-specific slop value to make parts fit just right. Default: `PRINTER_SLOP` +// orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// Example: +// nema17_mount_holes(depth=5, l=5); +// Example: +// nema17_mount_holes(depth=5, l=0); +module nema17_mount_holes(depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, align=V_CENTER) +{ + nema_mount_holes(size=17, depth=depth, l=l, slop=slop, orient=orient, align=align); +} + + + +// Module: nema23_mount_holes() +// Description: Creates a mask to use when making NEMA 23 stepper motor mounts. +// Arguments: +// depth = The thickness of the mounting hole mask. Default: 5 +// l = The length of the slots, for making an adjustable motor mount. Default: 5 +// slop = The printer-specific slop value to make parts fit just right. Default: `PRINTER_SLOP` +// orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// Example: +// nema23_mount_holes(depth=5, l=5); +// Example: +// nema23_mount_holes(depth=5, l=0); +module nema23_mount_holes(depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, align=V_CENTER) +{ + nema_mount_holes(size=23, depth=depth, l=l, slop=slop, orient=orient, align=align); +} + + + +// Module: nema34_mount_holes() +// Description: Creates a mask to use when making NEMA 34 stepper motor mounts. +// Arguments: +// depth = The thickness of the mounting hole mask. Default: 5 +// l = The length of the slots, for making an adjustable motor mount. Default: 5 +// slop = The printer-specific slop value to make parts fit just right. Default: `PRINTER_SLOP` +// orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// Example: +// nema34_mount_holes(depth=5, l=5); +// Example: +// nema34_mount_holes(depth=5, l=0); +module nema34_mount_holes(depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, align=V_CENTER) +{ + nema_mount_holes(size=34, depth=depth, l=l, slop=slop, orient=orient, align=align); +} + + + +// Module: nema34_mount_holes() +// Description: Creates a mask to use when making NEMA 34 stepper motor mounts. +// Arguments: +// depth = The thickness of the mounting hole mask. Default: 5 +// l = The length of the slots, for making an adjustable motor mount. Default: 5 +// slop = The printer-specific slop value to make parts fit just right. Default: `PRINTER_SLOP` +// orient = Orientation of the stepper. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the stepper. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// Example: +// nema34_mount_holes(depth=5, l=5); +// Example: +// nema34_mount_holes(depth=5, l=0); +module nema34_mount_holes(depth=5, l=5, slop=PRINTER_SLOP, orient=ORIENT_Z, align=V_CENTER) +{ + nema_mount_holes(size=34, depth=depth, l=l, slop=slop, orient=orient, align=align); +} diff --git a/paths.scad b/paths.scad index a869210..92ee33b 100644 --- a/paths.scad +++ b/paths.scad @@ -1,5 +1,11 @@ ////////////////////////////////////////////////////////////////////// -// 2D and Bezier Stuff. +// LibFile: paths.scad +// Polylines, polygons and paths. +// To use, add the following lines to the beginning of your file: +// ``` +// include +// use +// ``` ////////////////////////////////////////////////////////////////////// /* @@ -31,78 +37,25 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -include -include -include -include +include +use +use +use +use -// Creates a 2D polygon circle, modulated by one or more superimposed -// sine waves. -// r = radius of the base circle. -// sines = array of [amplitude, frequency] pairs, where the frequency is the -// number of times the cycle repeats around the circle. -// Example: -// modulated_circle(r=40, sines=[[3, 11], [1, 31]], $fn=6); -module modulated_circle(r=40, sines=[10]) -{ - freqs = len(sines)>0? [for (i=sines) i[1]] : [5]; - points = [ - for (a = [0 : (360/segs(r)/max(freqs)) : 360]) - let(nr=r+sum_of_sines(a,sines)) [nr*cos(a), nr*sin(a)] - ]; - polygon(points); -} +// Section: Functions -// Extrudes a 2D shape between the points pt1 and pt2. -// Takes as children a set of 2D shapes to extrude. -// 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: -// extrude_from_to([0,0,0], [10,20,30], convexity=4, twist=360, scale=3.0, slices=40) { -// xspread(3) circle(3, $fn=32); -// } -module extrude_from_to(pt1, pt2, convexity=undef, twist=undef, scale=undef, slices=undef) { - rtp = xyz_to_spherical(pt2-pt1); - translate(pt1) { - rotate([0, rtp[2], rtp[1]]) { - linear_extrude(height=rtp[0], convexity=convexity, center=false, slices=slices, twist=twist, scale=scale) { - children(); - } - } - } -} - - - -// Similar to linear_extrude(), except the result is a hollow shell. -// wall = thickness of shell wall. -// height = height of extrusion. -// twist = degrees of twist, from bottom to top. -// slices = how many slices to use when making extrusion. -// Example: -// extrude_2d_hollow(wall=2, height=100, twist=90, slices=50) -// circle(r=40, center=true, $fn=6); -module extrude_2d_hollow(wall=2, height=50, twist=90, slices=60) -{ - linear_extrude(height=height, twist=twist, slices=slices) { - difference() { - children(); - offset(r=-wall) { - children(); - } - } - } -} - - -// Takes a 2D polyline and removes uneccessary collinear points. -function simplify2d_path(path) = concat( +// Function: simplify2d_path() +// Description: +// Takes a 2D polyline and removes unnecessary collinear points. +// Usage: +// simplify2d_path(path, [eps]) +// Arguments: +// path = A list of 2D path points. +// eps = Largest angle delta between segments to count as colinear. Default: 1e-6 +function simplify2d_path(path, eps=1e-6) = concat( [path[0]], [ for ( @@ -110,14 +63,21 @@ function simplify2d_path(path) = concat( ) let ( v1 = path[i] - path[i-1], v2 = path[i+1] - path[i-1] - ) if (abs(cross(v1,v2)) > 1e-6) path[i] + ) if (abs(cross(v1,v2)) > eps) path[i] ], [path[len(path)-1]] ); -// Takes a 3D polyline and removes uneccessary collinear points. -function simplify3d_path(path) = concat( +// Function: simplify3d_path() +// Description: +// Takes a 3D polyline and removes unnecessary collinear points. +// Usage: +// simplify3d_path(path, [eps]) +// Arguments: +// path = A list of 3D path points. +// eps = Largest angle delta between segments to count as colinear. Default: 1e-6 +function simplify3d_path(path, eps=1e-6) = concat( [path[0]], [ for ( @@ -125,67 +85,75 @@ function simplify3d_path(path) = concat( ) let ( v1 = path[i] - path[i-1], v2 = path[i+1] - path[i-1] - ) if (vector3d_angle(v1,v2) > 1e-6) path[i] + ) if (vector3d_angle(v1,v2) > eps) path[i] ], [path[len(path)-1]] ); - -// Takes a closed 2D polyline path, centered on the XY plane, and -// extrudes it along a 3D spiral path of a given radius, height and twist. -// polyline = Array of points of a polyline path, to be extruded. -// h = height of the spiral to extrude along. -// r = radius of the spiral to extrude along. -// twist = number of degrees of rotation to spiral up along height. -// Example: -// poly = [[-10,0], [-3,-5], [3,-5], [10,0], [0,-30]]; -// extrude_2dpath_along_spiral(poly, h=200, r=50, twist=1000, $fn=36); -module extrude_2dpath_along_spiral(polyline, h, r, twist=360) { - pline_count = len(polyline); - steps = ceil(segs(r)*(twist/360)); - - poly_points = [ - for ( - p = [0:steps] - ) let ( - a = twist * (p/steps), - dx = r*cos(a), - dy = r*sin(a), - dz = h * (p/steps), - cp = [dx, dy, dz], - rotx = matrix3_xrot(90), - rotz = matrix3_zrot(a), - rotm = rotz * rotx - ) for ( - b = [0:pline_count-1] - ) rotm*point3d(polyline[b])+cp +// Function: path2d_regular_ngon() +// Description: +// Returns a 2D open counter-clockwise path of the vertices of a regular polygon of `n` sides. +// Usage: +// path2d_regular_ngon(n, r|d, [cp], [scale]); +// Arguments: +// n = Number of polygon sides. +// r = Radius of regular polygon. +// d = Radius of regular polygon. +// cp = Centerpoint of regular polygon. Default: `[0,0]` +// scale = [X,Y] scaling factors for each axis. Default: `[1,1]` +// Example(2D): +// trace_polyline(path2d_regular_ngon(n=12, r=50), N=1, showpts=true); +function path2d_regular_ngon(n=6, r=undef, d=undef, cp=[0,0], scale=[1,1]) = let( + rr=get_radius(r=r, d=d, dflt=100) + ) [ + for (i=[0:n-1]) + rr * [cos(i*360/n)*scale.x, sin(i*360/n)*scale.y] + cp ]; - poly_faces = concat( - [[for (b = [0:pline_count-1]) b]], - [ - for ( - p = [0:steps-1], - b = [0:pline_count-1], - i = [0:1] - ) let ( - b2 = (b == pline_count-1)? 0 : b+1, - p0 = p * pline_count + b, - p1 = p * pline_count + b2, - p2 = (p+1) * pline_count + b2, - p3 = (p+1) * pline_count + b, - pt = (i==0)? [p0, p2, p1] : [p0, p3, p2] - ) pt - ], - [[for (b = [pline_count-1:-1:0]) b+(steps)*pline_count]] - ); - tri_faces = triangulate_faces(poly_points, poly_faces); - polyhedron(points=poly_points, faces=tri_faces, convexity=10); -} +// Function: path3d_spiral() +// Description: +// Returns a 3D spiral path. +// Usage: +// path3d_spiral(turns, h, n, r|d, [cp], [scale]); +// Arguments: +// h = Height of spiral. +// turns = Number of turns in spiral. +// n = Number of spiral sides. +// r = Radius of spiral. +// d = Radius of spiral. +// cp = Centerpoint of spiral. Default: `[0,0]` +// scale = [X,Y] scaling factors for each axis. Default: `[1,1]` +// Example(3D): +// trace_polyline(path3d_spiral(turns=2.5, h=100, n=24, r=50), N=1, showpts=true); +function path3d_spiral(turns=3, h=100, n=12, r=undef, d=undef, cp=[0,0], scale=[1,1]) = let( + rr=get_radius(r=r, d=d, dflt=100), + cnt=floor(turns*n), + dz=h/cnt + ) [ + for (i=[0:cnt]) [ + rr * cos(i*360/n) * scale.x + cp.x, + rr * sin(i*360/n) * scale.y + cp.y, + i*dz + ] + ]; +// Function: points_along_path3d() +// Usage: +// points_along_path3d(polyline, path); +// Description: +// Calculates the vertices needed to create a `polyhedron()` of the +// extrusion of `polyline` along `path`. The closed 2D path shold be +// centered on the XY plane. The 2D path is extruded perpendicularly +// along the 3D path. Produces a list of 3D vertices. Vertex count +// is `len(polyline)*len(path)`. Gives all the reoriented vertices +// for `polyline` at the first point in `path`, then for the second, +// and so on. +// Arguments: +// polyline = A closed list of 2D path points. +// path = A list of 3D path points. function points_along_path3d( polyline, // The 2D polyline to drag along the 3D path. path, // The 3D polyline path to follow. @@ -209,19 +177,165 @@ function points_along_path3d( ); -// Takes a closed 2D polyline path, centered on the XY plane, and -// extrudes it perpendicularly along a 3D polyline path, forming a solid. + +// Section: 2D Modules + + +// Module: modulated_circle() +// Description: +// Creates a 2D polygon circle, modulated by one or more superimposed sine waves. +// Arguments: +// r = radius of the base circle. +// sines = array of [amplitude, frequency] pairs, where the frequency is the number of times the cycle repeats around the circle. +// Example(2D): +// modulated_circle(r=40, sines=[[3, 11], [1, 31]], $fn=6); +module modulated_circle(r=40, sines=[10]) +{ + freqs = len(sines)>0? [for (i=sines) i[1]] : [5]; + points = [ + for (a = [0 : (360/segs(r)/max(freqs)) : 360]) + let(nr=r+sum_of_sines(a,sines)) [nr*cos(a), nr*sin(a)] + ]; + polygon(points); +} + + +// Section: 3D Modules + + +// Module: extrude_from_to() +// Description: +// Extrudes a 2D shape between the 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): +// extrude_from_to([0,0,0], [10,20,30], convexity=4, twist=360, scale=3.0, slices=40) { +// xspread(3) circle(3, $fn=32); +// } +module extrude_from_to(pt1, pt2, convexity=undef, twist=undef, scale=undef, slices=undef) { + rtp = xyz_to_spherical(pt2-pt1); + translate(pt1) { + rotate([0, rtp[2], rtp[1]]) { + linear_extrude(height=rtp[0], convexity=convexity, center=false, slices=slices, twist=twist, scale=scale) { + children(); + } + } + } +} + + + +// Module: extrude_2d_hollow() +// Description: +// Similar to linear_extrude(), except the result is a hollow shell. +// Arguments: +// wall = thickness of shell wall. +// height = height of extrusion. +// twist = degrees of twist, from bottom to top. +// slices = how many slices to use when making extrusion. +// Example: +// extrude_2d_hollow(wall=2, height=100, twist=90, slices=50) +// circle(r=40, $fn=6); +module extrude_2d_hollow(wall=2, height=50, twist=90, slices=60, center=undef, orient=ORIENT_Z, align=V_UP) +{ + orient_and_align([0,0,height], orient, align, center) { + linear_extrude(height=height, twist=twist, slices=slices) { + difference() { + children(); + offset(r=-wall) { + children(); + } + } + } + } +} + + +// Module: extrude_2dpath_along_spiral() +// Description: +// Takes a closed 2D polyline path, centered on the XY plane, and +// extrudes it along a 3D spiral path of a given radius, height and twist. +// Arguments: +// polyline = Array of points of a polyline path, to be extruded. +// h = height of the spiral to extrude along. +// r = radius of the spiral to extrude along. +// twist = number of degrees of rotation to spiral up along height. +// Example: +// poly = [[-10,0], [-3,-5], [3,-5], [10,0], [0,-30]]; +// extrude_2dpath_along_spiral(poly, h=200, r=50, twist=1080, $fn=36); +module extrude_2dpath_along_spiral(polyline, h, r, twist=360, center=undef, orient=ORIENT_Z, align=V_CENTER) { + pline_count = len(polyline); + steps = ceil(segs(r)*(twist/360)); + + poly_points = [ + for ( + p = [0:steps] + ) let ( + a = twist * (p/steps), + dx = r*cos(a), + dy = r*sin(a), + dz = h * (p/steps), + pts = matrix4_apply( + polyline, [ + matrix4_xrot(90), + matrix4_zrot(a), + matrix4_translate([dx, dy, dz]) + ] + ) + ) for (pt = pts) pt + ]; + + poly_faces = concat( + [[for (b = [0:pline_count-1]) b]], + [ + for ( + p = [0:steps-1], + b = [0:pline_count-1], + i = [0:1] + ) let ( + b2 = (b == pline_count-1)? 0 : b+1, + p0 = p * pline_count + b, + p1 = p * pline_count + b2, + p2 = (p+1) * pline_count + b2, + p3 = (p+1) * pline_count + b, + pt = (i==0)? [p0, p2, p1] : [p0, p3, p2] + ) pt + ], + [[for (b = [pline_count-1:-1:0]) b+(steps)*pline_count]] + ); + + tri_faces = triangulate_faces(poly_points, poly_faces); + orient_and_align([r,r,h], orient, align, center) { + polyhedron(points=poly_points, faces=tri_faces, convexity=10); + } +} + + +// Module: extrude_2dpath_along_3dpath() +// Description: +// Takes a closed 2D path `polyline`, centered on the XY plane, and extrudes it perpendicularly along a 3D path `path`, forming a solid. +// Arguments: // polyline = Array of points of a polyline path, to be extruded. // path = Array of points of a polyline path, to extrude along. +// ang = Angle in degrees to rotate 2D polyline before extrusion. // convexity = max number of surfaces any single ray could pass through. -// Example: -// shape = [[-10,0], [-3,-5], [3,-5], [10,0], [0,-30]]; -// path = [ [0, 0, 0], [100, 33, 33], [200, -33, -33], [300, 0, 0] ]; -// extrude_2dpath_along_3dpath(shape, path); -module extrude_2dpath_along_3dpath(polyline, path, convexity=10) { +// Example(FlatSpin): +// shape = [[0,-10], [5,-3], [5,3], [0,10], [30,0]]; +// path = concat( +// [for (a=[30:30:180]) [50*cos(a)+50, 50*sin(a), 20*sin(a)]], +// [for (a=[330:-30:180]) [50*cos(a)-50, 50*sin(a), 20*sin(a)]] +// ); +// extrude_2dpath_along_3dpath(shape, path, ang=140); +module extrude_2dpath_along_3dpath(polyline, path, ang=0, convexity=10) { pline_count = len(polyline); path_count = len(path); + polyline = rotate_points2d(path2d(polyline), ang); poly_points = points_along_path3d(polyline, path); poly_faces = concat( @@ -249,13 +363,16 @@ module extrude_2dpath_along_3dpath(polyline, path, convexity=10) { -// Extrudes 2D children along a 3D polyline path. +// Module: extrude_2d_shapes_along_3dpath() +// Description: +// Extrudes 2D children along a 3D polyline 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: -// path = [ [0, 0, 0], [33, 33, 33], [66, 33, 40], [100, 0, 0] ]; -// extrude_2d_shapes_along_3dpath(path) circle(r=10, center=true, $fn=5); +// Example(FlatSpin): +// path = [ [0, 0, 0], [33, 33, 33], [66, 33, 40], [100, 0, 0], [150,0,0] ]; +// extrude_2d_shapes_along_3dpath(path) circle(r=10, $fn=6); module extrude_2d_shapes_along_3dpath(path, convexity=10, clipsize=100) { function polyquats(path, q=Q_Ident(), v=[0,0,1], i=0) = let( v2 = path[i+1] - path[i], @@ -298,5 +415,102 @@ module extrude_2d_shapes_along_3dpath(path, convexity=10, clipsize=100) { } +// Module: trace_polyline() +// Description: +// Renders lines between each point of a polyline path. +// Can also optionally show the individual vertex points. +// Arguments: +// pline = The array of points in the polyline. +// showpts = If true, draw vertices and control points. +// N = Mark the first and every Nth vertex after in a different color and shape. +// size = Diameter of the lines drawn. +// color = Color to draw the lines (but not vertices) in. +// Example(FlatSpin): +// polyline = [for (a=[0:30:210]) 10*[cos(a), sin(a), sin(a)]]; +// trace_polyline(polyline, showpts=true, size=0.5, color="lightgreen"); +module trace_polyline(pline, N=1, showpts=false, size=1, color="yellow") { + if (showpts) { + for (i = [0:len(pline)-1]) { + translate(pline[i]) { + if (i%N == 0) { + color("blue") sphere(d=size*2.5, $fn=8); + } else { + color("red") { + cylinder(d=size/2, h=size*3, center=true, $fn=8); + xrot(90) cylinder(d=size/2, h=size*3, center=true, $fn=8); + yrot(90) cylinder(d=size/2, h=size*3, center=true, $fn=8); + } + } + } + } + } + for (i = [0:len(pline)-2]) { + if (N!=3 || (i%N) != 1) { + color(color) extrude_from_to(pline[i], pline[i+1]) circle(d=size/2); + } + } +} + + +// Module: debug_polygon() +// Description: A drop-in replacement for `polygon()` that renders and labels the path points. +// Arguments: +// points = The array of 2D polygon vertices. +// paths = The path connections between the vertices. +// convexity = The max number of walls a ray can pass through the given polygon paths. +// Example(2D): +// debug_polygon( +// points=concat( +// path2d_regular_ngon(r=10, n=8), +// path2d_regular_ngon(r=8, n=8) +// ), +// paths=[ +// [for (i=[0:7]) i], +// [for (i=[15:-1:8]) i] +// ] +// ); +module debug_polygon(points, paths=undef, convexity=2, size=1) +{ + pths = (!is_def(paths))? [for (i=[0:len(points)-1]) i] : is_scalar(paths[0])? [paths] : paths; + echo(points=points); + echo(paths=paths); + linear_extrude(height=0.01, convexity=convexity, center=true) { + polygon(points=points, paths=paths, convexity=convexity); + } + for (i = [0:len(points)-1]) { + color("red") { + up(0.2) { + translate(points[i]) { + linear_extrude(height=0.1, convexity=10, center=true) { + text(text=str(i), size=size, halign="center", valign="center"); + } + } + } + } + } + for (j = [0:len(paths)-1]) { + path = paths[j]; + translate(points[path[0]]) { + color("cyan") up(0.1) cylinder(d=size*1.5, h=0.01, center=false, $fn=12); + } + translate(points[path[len(path)-1]]) { + color("pink") up(0.11) cylinder(d=size*1.5, h=0.01, center=false, $fn=4); + } + for (i = [0:len(path)-1]) { + midpt = (points[path[i]] + points[path[(i+1)%len(path)]])/2; + color("blue") { + up(0.2) { + translate(midpt) { + linear_extrude(height=0.1, convexity=10, center=true) { + text(text=str(chr(65+j),i), size=size/2, halign="center", valign="center"); + } + } + } + } + } + } +} + + // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/phillips_drive.scad b/phillips_drive.scad index 9894421..08dde3f 100644 --- a/phillips_drive.scad +++ b/phillips_drive.scad @@ -1,5 +1,11 @@ ////////////////////////////////////////////////////////////////////// -// Phillips driver bits +// LibFile: phillips_drive.scad +// Phillips driver bits +// To use, add these lines to the top of your file: +// ``` +// include +// use +// ``` ////////////////////////////////////////////////////////////////////// /* @@ -33,19 +39,26 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use use +include +include -// Creates a model of a phillips driver bit of a given named size. +// Section: Modules + + +// Module: phillips_drive() +// Description: Creates a model of a phillips driver bit of a given named size. +// Arguments: // size = The size of the bit. "#1", "#2", or "#3" // shaft = The diameter of the drive bit's shaft. // l = The length of the drive bit. // Example: // xdistribute(10) { -// phillips_drive(size="#1", shaft=4, l=30); -// phillips_drive(size="#2", shaft=6, l=30); -// phillips_drive(size="#3", shaft=6, l=30); +// phillips_drive(size="#1", shaft=4, l=20); +// phillips_drive(size="#2", shaft=6, l=20); +// phillips_drive(size="#3", shaft=6, l=20); // } -module phillips_drive(size="#2", shaft=6, l=20) { +module phillips_drive(size="#2", shaft=6, l=20, orient=ORIENT_Z, align=V_UP) { // These are my best guess reverse-engineered measurements of // the tip diameters of various phillips screwdriver sizes. ang = 11; @@ -54,31 +67,35 @@ module phillips_drive(size="#2", shaft=6, l=20) { r = radidx == []? 0 : rads[radidx][1]; h = (r/2)/tan(ang); cr = r/2; - difference() { - intersection() { - union() { - clip = (shaft-1.2*r)/2/tan(26.5); - zrot(360/8/2) cylinder(h=clip, d1=1.2*r/cos(360/8/2), d2=shaft/cos(360/8/2), center=false, $fn=8); - up(clip-0.01) cylinder(h=l-clip, d=shaft, center=false, $fn=24); - } - cylinder(d=shaft, h=l, center=false, $fn=24); - } - zrot(45) - zring(n=4) { - yrot(ang) { - zrot(-45) { - off = (r/2-cr*(sqrt(2)-1))/sqrt(2); - translate([off, off, 0]) { - linear_extrude(height=l*2, convexity=4) { - difference() { - union() { - square([shaft, shaft], center=false); - back(cr) zrot(1.125) square([shaft, shaft], center=false); - right(cr) zrot(-1.125) square([shaft, shaft], center=false); - } - difference() { - square([cr*2, cr*2], center=true); - translate([cr,cr,0]) circle(r=cr, $fn=8); + orient_and_align([shaft, shaft, l], orient, align) { + down(l/2) { + difference() { + intersection() { + union() { + clip = (shaft-1.2*r)/2/tan(26.5); + zrot(360/8/2) cylinder(h=clip, d1=1.2*r/cos(360/8/2), d2=shaft/cos(360/8/2), center=false, $fn=8); + up(clip-0.01) cylinder(h=l-clip, d=shaft, center=false, $fn=24); + } + cylinder(d=shaft, h=l, center=false, $fn=24); + } + zrot(45) + zring(n=4) { + yrot(ang) { + zrot(-45) { + off = (r/2-cr*(sqrt(2)-1))/sqrt(2); + translate([off, off, 0]) { + linear_extrude(height=l*2, convexity=4) { + difference() { + union() { + square([shaft, shaft], center=false); + back(cr) zrot(1.125) square([shaft, shaft], center=false); + right(cr) zrot(-1.125) square([shaft, shaft], center=false); + } + difference() { + square([cr*2, cr*2], center=true); + translate([cr,cr,0]) circle(r=cr, $fn=8); + } + } } } } diff --git a/quaternions.scad b/quaternions.scad index 4610fed..7ffb26d 100644 --- a/quaternions.scad +++ b/quaternions.scad @@ -1,5 +1,10 @@ /////////////////////////////////////////// -// Quaternions +// LibFile: quaternions.scad +// Support for Quaternions. +// To use, add the following line to the beginning of your file: +// ``` +// use +// ``` /////////////////////////////////////////// /* @@ -34,17 +39,63 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use -// Quaternions are stored internally as a 4-value vector: -// [X, Y, Z, W] = W + Xi + Yj + Zk +// Section: Quaternions +// Quaternions are fast methods of storing and calculating arbitrary rotations. +// Quaternions contain information on both axis of rotation, and rotation angle. +// You can chain multiple rotation together by multiplying quaternions together. +// They don't suffer from the gimbal-lock issues that [X,Y,Z] rotation angles do. +// Quaternions are stored internally as a 4-value vector: +// `[X, Y, Z, W] = W + Xi + Yj + Zk` + + +// Internal function _Quat(a,s,w) = [a[0]*s, a[1]*s, a[2]*s, w]; + + +// Function: Quat() +// Usage: +// Quat(ax, ang); +// Description: Create a new Quaternion from axis and angle of rotation. +// Arguments: +// ax = Vector of axis of rotation. +// ang = Number of degrees to rotate around the axis counter-clockwise, when facing the origin. function Quat(ax=[0,0,1], ang=0) = _Quat(ax/norm(ax), sin(ang/2), cos(ang/2)); + +// Function: QuatX() +// Usage: +// QuatX(a); +// Description: Create a new Quaternion for rotating around the X axis [1,0,0]. +// Arguments: +// a = Number of degrees to rotate around the axis counter-clockwise, when facing the origin. function QuatX(a=0) = Quat([1,0,0],a); + + +// Function: QuatY() +// Usage: +// QuatY(a); +// Description: Create a new Quaternion for rotating around the Y axis [0,1,0]. +// Arguments: +// a = Number of degrees to rotate around the axis counter-clockwise, when facing the origin. function QuatY(a=0) = Quat([0,1,0],a); + +// Function: QuatZ() +// Usage: +// QuatZ(a); +// Description: Create a new Quaternion for rotating around the Z axis [0,0,1]. +// Arguments: +// a = Number of degrees to rotate around the axis counter-clockwise, when facing the origin. function QuatZ(a=0) = Quat([0,0,1],a); -// Creates a quaternion from standard [X,Y,Z] euller rotation angles in degrees. -function QuatEuller(a=[0,0,0]) = + +// Function: QuatXYZ() +// Usage: +// QuatXYZ([X,Y,Z]) +// Description: +// Creates a quaternion from standard [X,Y,Z] rotation angles in degrees. +// Arguments: +// a = The triplet of rotation angles, [X,Y,Z] +function QuatXYZ(a=[0,0,0]) = let( qx = QuatX(a[0]), qy = QuatY(a[1]), @@ -52,42 +103,138 @@ function QuatEuller(a=[0,0,0]) = ) Q_Mul(qz, Q_Mul(qy, qx)); + +// Function: Q_Ident() +// Description: Returns the "Identity" zero-rotation Quaternion. function Q_Ident() = [0, 0, 0, 1]; -function Q_Add_S(q, s) = [q[0], q[1], q[2], q[3]+s]; -function Q_Sub_S(q, s) = [q[0], q[1], q[2], q[3]-s]; -function Q_Mul_S(q, s) = [q[0]*s, q[1]*s, q[2]*s, q[3]*s]; -function Q_Div_S(q, s) = [q[0]/s, q[1]/s, q[2]/s, q[3]/s]; -function Q_Add(a, b) = [a[0]+b[0], a[1]+b[1], a[2]+b[2], a[3]+b[3]]; -function Q_Sub(a, b) = [a[0]-b[0], a[1]-b[1], a[2]-b[2], a[3]-b[3]]; +// Function: Q_Add_S() +// Usage: +// Q_Add_S(q, s) +// Description: Adds a scalar value `s` to the W part of a quaternion `q`. +function Q_Add_S(q, s) = q+[0,0,0,s]; + + +// Function: Q_Sub_S() +// Usage: +// Q_Sub_S(q, s) +// Description: Subtracts a scalar value `s` from the W part of a quaternion `q`. +function Q_Sub_S(q, s) = q-[0,0,0,s]; + + +// Function: Q_Mul_S() +// Usage: +// Q_Mul_S(q, s) +// Description: Multiplies each part of a quaternion `q` by a scalar value `s`. +function Q_Mul_S(q, s) = q*s; + + +// Function: Q_Div_S() +// Usage: +// Q_Div_S(q, s) +// Description: Divides each part of a quaternion `q` by a scalar value `s`. +function Q_Div_S(q, s) = q/s; + + +// Function: Q_Add() +// Usage: +// Q_Add(a, b) +// Description: Adds each part of two quaternions together. +function Q_Add(a, b) = a+b; + + +// Function: Q_Sub() +// Usage: +// Q_Sub(a, b) +// Description: Subtracts each part of quaternion `b` from quaternion `a`. +function Q_Sub(a, b) = a-b; + + +// Function: Q_Mul() +// Usage: +// Q_Mul(a, b) +// Description: Multiplies quaternion `a` by quaternion `b`. function Q_Mul(a, b) = [ - a[3]*b[0] + a[0]*b[3] + a[1]*b[2] - a[2]*b[1], - a[3]*b[1] - a[0]*b[2] + a[1]*b[3] + a[2]*b[0], - a[3]*b[2] + a[0]*b[1] - a[1]*b[0] + a[2]*b[3], - a[3]*b[3] - a[0]*b[0] - a[1]*b[1] - a[2]*b[2], + a[3]*b.x + a.x*b[3] + a.y*b.z - a.z*b.y, + a[3]*b.y - a.x*b.z + a.y*b[3] + a.z*b.x, + a[3]*b.z + a.x*b.y - a.y*b.x + a.z*b[3], + a[3]*b[3] - a.x*b.x - a.y*b.y - a.z*b.z, ]; + + +// Function: Q_Dot() +// Usage: +// Q_Dot(a, b) +// Description: Calculates the dot product between quaternions `a` and `b`. function Q_Dot(a, b) = a[0]*b[0] + a[1]*b[1] + a[2]*b[2] + a[3]*b[3]; -function Q_Neg(q) = [-q[0], -q[1], -q[2], -q[3]]; -function Q_Conj(q) = [-q[0], -q[1], -q[2], q[3]]; -function Q_Norm(q) = sqrt(q[0]*q[0] + q[1]*q[1] + q[2]*q[2] + q[3]*q[3]); -function Q_Normalize(q) = q/Q_Norm(q); -function Q_Dist(q1, q2) = Q_Norm(Q_Sub(q1-q2)); + +// Function: Q_Neg() +// Usage: +// Q_Neg(q) +// Description: Returns the negative of quaternion `q`. +function Q_Neg(q) = -q; -// Returns a spherical interpolation between two quaternions. -function Q_Slerp(q1, q2, t) = let( +// Function: Q_Conj() +// Usage: +// Q_Conj(q) +// Description: Returns the conjugate of quaternion `q`. +function Q_Conj(q) = [-q.x, -q.y, -q.z, q[3]]; + + +// Function: Q_Norm() +// Usage: +// Q_Norm(q) +// Description: Returns the `norm()` "length" of quaternion `q`. +function Q_Norm(q) = norm(q); + + +// Function: Q_Normalize() +// Usage: +// Q_Normalize(q) +// Description: Normalizes quaternion `q`, so that norm([W,X,Y,Z]) == 1. +function Q_Normalize(q) = q/norm(q); + + +// Function: Q_Dist() +// Usage: +// Q_Dist(q1, q2) +// Description: Returns the "distance" between two quaternions. +function Q_Dist(q1, q2) = norm(q2-q1); + + +// Function: Q_Slerp() +// Usage: +// Q_Slerp(q1, q2, u); +// Description: +// Returns a quaternion that is a spherical interpolation between two quaternions. +// Arguments: +// q1 = The first quaternion. (u=0) +// q2 = The second quaternion. (u=1) +// u = The proportional value, from 0 to 1, of what part of the interpolation to return. +// Example(3D): +// a = QuatY(15); +// b = QuatY(75); +// color("blue",0.25) Qrot(a) cylinder(d=1, h=80); +// color("red",0.25) Qrot(b) cylinder(d=1, h=80); +// Qrot(Q_Slerp(a, b, 0.6)) cylinder(d=1, h=80); +function Q_Slerp(q1, q2, u) = let( dot = Q_Dot(q1, q2), qq2 = dot<0? Q_Neg(q2) : q2, dott = dot<0? -dot : dot, - theta = t * acos(constrain(dott,-1,1)) + theta = u * acos(constrain(dott,-1,1)) ) (dott>0.9995)? - Q_Normalize(Q_Add(q1, Q_Mul_S(Q_Sub(qq2,q1), t))) : - Q_Add(Q_Mul_S(q1,cos(theta)), Q_Mul_S(Q_Normalize(Q_Sub(qq2, Q_Mul_S(q1, dott))), sin(theta))); + Q_Normalize(q1 + ((qq2-q1) * u)) : + (q1*cos(theta) + (Q_Normalize(qq2 - (q1 * dott)) * sin(theta))); -// Returns the 3x3 rotation matrix for the given normalized quaternion q. +// Function: Q_Matrix3() +// Usage: +// Q_Matrix3(q); +// Description: +// Returns the 3x3 rotation matrix for the given normalized quaternion q. function Q_Matrix3(q) = [ [1-2*q[1]*q[1]-2*q[2]*q[2], 2*q[0]*q[1]-2*q[2]*q[3], 2*q[0]*q[2]+2*q[1]*q[3]], [ 2*q[0]*q[1]+2*q[2]*q[3], 1-2*q[0]*q[0]-2*q[2]*q[2], 2*q[1]*q[2]-2*q[0]*q[3]], @@ -95,7 +242,11 @@ function Q_Matrix3(q) = [ ]; -// Returns the 4x4 rotation matrix for the given normalized quaternion q. +// Function: Q_Matrix4() +// Usage: +// Q_Matrix4(q); +// Description: +// Returns the 4x4 rotation matrix for the given normalized quaternion q. function Q_Matrix4(q) = [ [1-2*q[1]*q[1]-2*q[2]*q[2], 2*q[0]*q[1]-2*q[2]*q[3], 2*q[0]*q[2]+2*q[1]*q[3], 0], [ 2*q[0]*q[1]+2*q[2]*q[3], 1-2*q[0]*q[0]-2*q[2]*q[2], 2*q[1]*q[2]-2*q[0]*q[3], 0], @@ -104,19 +255,39 @@ function Q_Matrix4(q) = [ ]; -// Returns the quaternion's axis of rotation as a vector. +// Function: Q_Axis() +// Usage: +// Q_Axis(q) +// Description: +// Returns the axis of rotation of a normalized quaternion `q`. function Q_Axis(q) = let(d = sqrt(1-(q[3]*q[3]))) (d==0)? [0,0,1] : [q[0]/d, q[1]/d, q[2]/d]; -// Returns the quaternion's angle of rotation in degrees. +// Function: Q_Angle() +// Usage: +// Q_Angle(q) +// Description: +// Returns the angle of rotation (in degrees) of a normalized quaternion `q`. function Q_Angle(q) = 2 * acos(q[3]); -// Returns the vector `v` after rotating it by the quaternion `q`. +// Function: Q_Rot_Vector() +// Usage: +// Q_Rot_Vector(v,q); +// Description: +// Returns the vector `v` after rotating it by the quaternion `q`. function Q_Rot_Vector(v,q) = Q_Mul(Q_Mul(q,concat(v,0)),Q_Conj(q)); -// Rotates all children by the given quaternion q. +// Module: Qrot() +// Usage: +// Qrot(q) ... +// Description: +// Rotate all children by the rotation stored in quaternion `q`. +// Example(FlatSpin): +// q = QuatXYZ([45,35,10]); +// color("red",0.25) cylinder(d=1,h=80); +// Qrot(q) cylinder(d=1,h=80); module Qrot(q) { multmatrix(Q_Matrix4(q)) { children(); diff --git a/scripts/docs_gen.py b/scripts/docs_gen.py new file mode 100755 index 0000000..1e5c1eb --- /dev/null +++ b/scripts/docs_gen.py @@ -0,0 +1,653 @@ +#!/usr/bin/env python + +from __future__ import print_function + +import os +import re +import sys +import math +import random +import os.path +import argparse +import subprocess + + + +def get_header_link(name): + refpat = re.compile("[^a-z0-9_ -]") + return refpat.sub("", name.lower()).replace(" ", "-") + + +def toc_entry(name, indent, count=None): + ref = get_header_link(name) + if name.endswith( (")", "}", "]") ): + name = "`" + name.replace("\\", "") + "`" + return "{0}{1} [{2}](#{3})".format( + indent, + ("%d." % count) if count else "-", + name, + ref + ) + + +def mkdn_esc(txt): + out = "" + quotpat = re.compile(r'([^`]*)(`[^`]*`)(.*$)'); + while txt: + m = quotpat.match(txt) + if m: + out += m.group(1).replace(r'_', r'\_') + out += m.group(2) + txt = m.group(3) + else: + out += txt.replace(r'_', r'\_') + txt = "" + return out + + +def get_comment_block(lines, prefix, blanks=1): + out = [] + blankcnt = 0 + while lines: + if not lines[0].startswith(prefix + " "): + break + line = lines.pop(0).rstrip().lstrip("/") + if line == "": + blankcnt += 1 + if blankcnt >= blanks: + break + else: + blankcnt = 0 + line = line[len(prefix):] + out.append(line) + return (lines, out) + + +class ImageProcessing(object): + def __init__(self): + self.examples = [] + self.commoncode = [] + self.imgroot = "" + self.keep_scripts = False + + def set_keep_scripts(self, x): + self.keep_scripts = x + + def add_image(self, libfile, imgfile, code, extype): + self.examples.append((libfile, imgfile, code, extype)) + + def set_commoncode(self, code): + self.commoncode = code + + def process_examples(self, imgroot): + self.imgroot = imgroot + for libfile, imgfile, code, extype in self.examples: + self.gen_example_image(libfile, imgfile, code, extype) + + def gen_example_image(self, libfile, imgfile, code, extype): + OPENSCAD = "/Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD" + CONVERT = "/usr/local/bin/convert" + + if extype == "NORENDER": + return + + scriptfile = "tmp_{0}.scad".format(imgfile.replace(".", "_")) + + stdlibs = ["constants.scad", "math.scad", "transforms.scad", "shapes.scad", "debug.scad"] + script = "" + for lib in stdlibs: + script += "include \n" % lib + if libfile not in stdlibs: + script += "include \n" % libfile + for line in self.commoncode: + script += line+"\n" + for line in code: + script += line+"\n" + + with open(scriptfile, "w") as f: + f.write(script) + + if "Med" in extype: + imgsizes = ["800,600", "400x300"] + elif "Big" in extype: + imgsizes = ["1280,960", "640x480"] + elif "distribute" in script: + print(script) + imgsizes = ["800,600", "400x300"] + else: # Small + imgsizes = ["480,360", "240x180"] + + print("") + print("{}: {}".format(libfile, imgfile)) + + tmpimgs = [] + if "Spin" in extype: + for ang in range(0,359,10): + tmpimgfile = "{0}tmp_{2}_{1}.png".format(self.imgroot, ang, imgfile.replace(".", "_")) + arad = ang * math.pi / 180; + eye = "{0},{1},{2}".format( + 500*math.cos(arad), + 500*math.sin(arad), + 500 if "Flat" in extype else 500*math.sin(arad) + ) + scadcmd = [ + OPENSCAD, + "-o", tmpimgfile, + "--imgsize={}".format(imgsizes[0]), + "--hardwarnings", + "--projection=o", + "--view=axes,scales", + "--autocenter", + "--viewall", + "--camera", eye+",0,0,0" + ] + if "FR" in extype: # Force render + scadcmd.extend(["--render", ""]) + scadcmd.append(scriptfile) + print(" ".join(scadcmd)) + res = subprocess.call(scadcmd) + if res != 0: + print(script) + sys.exit(res) + tmpimgs.append(tmpimgfile) + else: + tmpimgfile = self.imgroot + "tmp_" + imgfile + scadcmd = [ + OPENSCAD, + "-o", tmpimgfile, + "--imgsize={}".format(imgsizes[0]), + "--hardwarnings", + "--projection=o", + "--view=axes,scales", + "--autocenter", + "--viewall" + ] + if "2D" in extype: # 2D viewpoint + scadcmd.extend(["--camera", "0,0,0,0,0,0,500"]) + if "FR" in extype: # Force render + scadcmd.extend(["--render", ""]) + scadcmd.append(scriptfile) + + print(" ".join(scadcmd)) + res = subprocess.call(scadcmd) + if res != 0: + print(script) + sys.exit(res) + tmpimgs.append(tmpimgfile) + + if not self.keep_scripts: + os.unlink(scriptfile) + outimgfile = self.imgroot + imgfile + if len(tmpimgs) == 1: + cnvcmd = [CONVERT, tmpimgfile, "-resize", imgsizes[1], outimgfile] + print(" ".join(cnvcmd)) + res = subprocess.call(cnvcmd) + if res != 0: + sys.exit(res) + os.unlink(tmpimgs.pop(0)) + else: + cnvcmd = [ + CONVERT, + "-delay", "25", + "-loop", "0", + "-coalesce", + "-scale", imgsizes[1], + "-fuzz", "2%", + "+dither", + "-layers", "Optimize", + "+map" + ] + cnvcmd.extend(tmpimgs) + cnvcmd.append(outimgfile) + print(" ".join(cnvcmd)) + res = subprocess.call(cnvcmd) + if res != 0: + sys.exit(res) + for tmpimg in tmpimgs: + os.unlink(tmpimg) + + +imgprc = ImageProcessing() + + +class LeafNode(object): + def __init__(self): + self.name = "" + self.leaftype = "" + self.status = "" + self.description = [] + self.usages = [] + self.arguments = [] + self.side_effects = [] + self.examples = [] + + @classmethod + def match_line(cls, line, prefix): + if line.startswith(prefix + "Constant: "): + return True + if line.startswith(prefix + "Function: "): + return True + if line.startswith(prefix + "Module: "): + return True + return False + + def add_example(self, title, code, extype): + self.examples.append((title, code, extype)) + + def parse_lines(self, lines, prefix): + blankcnt = 0 + expat = re.compile(r"^(Examples?)(\(([^\)]*)\))?: *(.*)$") + while lines: + if prefix and not lines[0].startswith(prefix.strip()): + break + line = lines.pop(0).rstrip() + if line.lstrip("/").strip() == "": + blankcnt += 1 + if blankcnt >= 2: + break + continue + blankcnt = 0 + line = line[len(prefix):] + if line.startswith("Constant:"): + leaftype, title = line.split(":", 1) + self.name = title.strip() + self.leaftype = leaftype.strip() + if line.startswith("Function:"): + leaftype, title = line.split(":", 1) + self.name = title.strip() + self.leaftype = leaftype.strip() + if line.startswith("Module:"): + leaftype, title = line.split(":", 1) + self.name = title.strip() + self.leaftype = leaftype.strip() + if line.startswith("Status:"): + dummy, status = line.split(":", 1) + self.status = status.strip() + if line.startswith("Description:"): + dummy, desc = line.split(":", 1) + desc = desc.strip() + if desc: + self.description.append(desc) + lines, block = get_comment_block(lines, prefix) + self.description.extend(block) + if line.startswith("Usage:"): + dummy, title = line.split(":", 1) + title = title.strip() + lines, block = get_comment_block(lines, prefix) + self.usages.append([title, block]) + if line.startswith("Arguments:"): + lines, block = get_comment_block(lines, prefix) + for line in block: + if "=" not in line: + print("Error: bad argument line:") + print(line) + sys.exit(2) + argname, argdesc = line.split("=", 1) + argname = argname.strip() + argdesc = argdesc.strip() + self.arguments.append([argname, argdesc]) + if line.startswith("Side Effects:"): + lines, block = get_comment_block(lines, prefix) + self.side_effects.extend(block) + m = expat.match(line) + if m: # Example(TYPE): + plural = m.group(1) == "Examples" + extype = m.group(3) + title = m.group(4) + lines, block = get_comment_block(lines, prefix) + if not extype: + extype = "3D" if self.leaftype == "Module" else "NORENDER" + if not plural: + self.add_example(title=title, code=block, extype=extype) + else: + for line in block: + self.add_example(title="", code=[line], extype=extype) + return lines + + def gen_md(self, fileroot, imgroot): + out = [] + if self.name: + out.append("### " + mkdn_esc(self.name)) + out.append("") + if self.status: + out.append("**{0}**".format(mkdn_esc(self.status))) + out.append("") + for title, usages in self.usages: + if not title: + title = "Usage" + out.append("**{0}**:".format(mkdn_esc(title))) + for usage in usages: + out.append("- {0}".format(mkdn_esc(usage))) + out.append("") + if self.description: + out.append("**Description**:") + for line in self.description: + out.append(mkdn_esc(line)) + out.append("") + if self.arguments: + out.append("Argument | What it does") + out.append("--------------- | ------------------------------") + for argname, argdesc in self.arguments: + argname = argname.replace(" / ", "` / `") + out.append( + "{0:15s} | {1}".format( + "`{0}`".format(argname), + mkdn_esc(argdesc) + ) + ) + out.append("") + if self.side_effects: + out.append("**Side Effects**:") + for sfx in self.side_effects: + out.append("- " + mkdn_esc(sfx)) + out.append("") + exnum = 0 + for title, excode, extype in self.examples: + exnum += 1 + if len(self.examples) < 2: + extitle = "**Example**:" + else: + extitle = "**Example {0}**:".format(exnum) + if title: + extitle += " " + mkdn_esc(title) + out.append(extitle) + out.append("") + for line in excode: + out.append(" " + line) + out.append("") + san_name = re.sub(r"[^A-Za-z0-9_]", "", self.name) + imgfile = "{0}{1}.{2}".format( + san_name, + ("_%d" % exnum) if exnum > 1 else "", + "gif" if "Spin" in extype else "png" + ) + if extype != "NORENDER": + out.append( + "![{0} Example{1}]({2}{3})".format( + mkdn_esc(self.name), + (" %d" % exnum) if len(self.examples) > 1 else "", + imgroot, + imgfile + ) + ) + out.append("") + imgprc.add_image(fileroot+".scad", imgfile, excode, extype) + out.append("---") + out.append("") + return out + + +class Section(object): + fignum = 0 + def __init__(self): + self.name = "" + self.description = [] + self.leaf_nodes = [] + self.figures = [] + + @classmethod + def match_line(cls, line, prefix): + if line.startswith(prefix + "Section: "): + return True + return False + + def add_figure(self, figtitle, figcode, figtype): + self.figures.append((figtitle, figcode, figtype)) + + def parse_lines(self, lines, prefix): + line = lines.pop(0).rstrip() + dummy, title = line.split(": ", 1) + self.name = title.strip() + lines, block = get_comment_block(lines, prefix, blanks=2) + self.description.extend(block) + blankcnt = 0 + figpat = re.compile(r"^(Figures?)(\(([^\)]*)\))?: *(.*)$") + while lines: + if prefix and not lines[0].startswith(prefix.strip()): + break + line = lines.pop(0).rstrip() + if line.lstrip("/").strip() == "": + blankcnt += 1 + if blankcnt >= 2: + break + continue + blankcnt = 0 + line = line[len(prefix):] + m = figpat.match(line) + if m: # Figures(TYPE): + plural = m.group(1) == "Figures" + figtype = m.group(3) + title = m.group(4) + lines, block = get_comment_block(lines, prefix) + if not figtype: + figtype = "3D" if self.figtype == "Module" else "NORENDER" + if not plural: + self.add_figure(title, block, figtype) + else: + for line in block: + self.add_figure("", [line], figtype) + return lines + + def gen_md_toc(self, count): + indent="" + out = [] + if self.name: + out.append(toc_entry(self.name, indent, count=count)) + indent += " " + for node in self.leaf_nodes: + out.append(toc_entry(node.name, indent)) + out.append("") + return out + + def gen_md(self, count, fileroot, imgroot): + out = [] + if self.name: + out.append("# %d. %s" % (count, mkdn_esc(self.name))) + out.append("") + if self.description: + in_block = False + for line in self.description: + if line.startswith("```"): + in_block = not in_block + if in_block or line.startswith(" "): + out.append(line) + else: + out.append(mkdn_esc(line)) + out.append("") + for title, figcode, figtype in self.figures: + Section.fignum += 1 + figtitle = "**Figure {0}**:".format(Section.fignum) + if title: + figtitle += " " + mkdn_esc(title) + out.append(figtitle) + out.append("") + imgfile = "{}{}.{}".format( + "figure", + Section.fignum, + "gif" if "Spin" in figtype else "png" + ) + if figtype != "NORENDER": + out.append( + "![{0} Figure {1}]({2}{3})".format( + mkdn_esc(self.name), + Section.fignum, + imgroot, + imgfile + ) + ) + out.append("") + imgprc.add_image(fileroot+".scad", imgfile, figcode, figtype) + in_block = False + for node in self.leaf_nodes: + out += node.gen_md(fileroot, imgroot) + return out + + +class LibFile(object): + def __init__(self): + self.name = "" + self.description = [] + self.commoncode = [] + self.sections = [] + self.dep_sect = None + + def parse_lines(self, lines, prefix): + currsect = None + constpat = re.compile(r"^([A-Z_0-9][A-Z_0-9]*) *=.* // (.*$)") + while lines: + while lines and prefix and not lines[0].startswith(prefix.strip()): + line = lines.pop(0) + m = constpat.match(line) + if m: + if currsect == None: + currsect = Section() + self.sections.append(currsect) + node = LeafNode(); + node.extype = "Constant" + node.name = m.group(1).strip() + node.description.append(m.group(2).strip()) + currsect.leaf_nodes.append(node) + + # Check for LibFile header. + if lines and lines[0].startswith(prefix + "LibFile: "): + line = lines.pop(0).rstrip() + dummy, title = line.split(": ", 1) + self.name = title.strip() + lines, block = get_comment_block(lines, prefix, blanks=2) + self.description.extend(block) + + # Check for CommonCode header. + if lines and lines[0].startswith(prefix + "CommonCode:"): + lines.pop(0) + lines, block = get_comment_block(lines, prefix) + self.commoncode.extend(block) + + # Check for Section header. + if lines and Section.match_line(lines[0], prefix): + sect = Section() + lines = sect.parse_lines(lines, prefix) + self.sections.append(sect) + currsect = sect + + # Check for LeafNode. + if lines and LeafNode.match_line(lines[0], prefix): + node = LeafNode() + lines = node.parse_lines(lines, prefix) + deprecated = node.status.startswith("DEPRECATED") + if deprecated: + if self.dep_sect == None: + self.dep_sect = Section() + self.dep_sect.name = "Deprecations" + sect = self.dep_sect + else: + if currsect == None: + currsect = Section() + self.sections.append(currsect) + sect = currsect + sect.leaf_nodes.append(node) + if lines: + lines.pop(0) + return lines + + def gen_md(self, fileroot, imgroot): + imgprc.set_commoncode(self.commoncode) + out = [] + if self.name: + out.append("# Library File " + mkdn_esc(self.name)) + out.append("") + if self.description: + in_block = False + for line in self.description: + if line.startswith("```"): + in_block = not in_block + if in_block or line.startswith(" "): + out.append(line) + else: + out.append(mkdn_esc(line)) + out.append("") + in_block = False + if self.name or self.description: + out.append("---") + out.append("") + + if self.sections or self.dep_sect: + out.append("# Table of Contents") + out.append("") + cnt = 0 + for sect in self.sections: + cnt += 1 + out += sect.gen_md_toc(cnt) + if self.dep_sect: + cnt += 1 + out += self.dep_sect.gen_md_toc(cnt) + out.append("---") + out.append("") + + cnt = 0 + for sect in self.sections: + cnt += 1 + out += sect.gen_md(cnt, fileroot, imgroot) + if self.dep_sect: + cnt += 1 + out += self.dep_sect.gen_md(cnt, fileroot, imgroot) + return out + + +def processFile(infile, outfile=None, gen_imgs=False, imgroot="", prefix=""): + if imgroot and not imgroot.endswith('/'): + imgroot += "/" + + libfile = LibFile() + with open(infile, "r") as f: + lines = f.readlines() + libfile.parse_lines(lines, prefix) + + if outfile == None: + f = sys.stdout + else: + f = open(outfile, "w") + + fileroot = os.path.splitext(os.path.basename(infile))[0] + outdata = libfile.gen_md(fileroot, imgroot) + for line in outdata: + print(line, file=f) + + if gen_imgs: + imgprc.process_examples(imgroot) + + if outfile: + f.close() + + +def main(): + parser = argparse.ArgumentParser(prog='docs_gen') + parser.add_argument('-k', '--keep-scripts', action="store_true", + help="If given, don't delete the temporary image OpenSCAD scripts.") + parser.add_argument('-c', '--comments-only', action="store_true", + help='If given, only process lines that start with // comments.') + parser.add_argument('-i', '--images', action="store_true", + help='If given, generate images for examples with OpenSCAD.') + parser.add_argument('-I', '--imgroot', default="", + help='The directory to put generated images in.') + parser.add_argument('-o', '--outfile', + help='Output file, if different from infile.') + parser.add_argument('infile', help='Input filename.') + args = parser.parse_args() + + imgprc.set_keep_scripts(args.keep_scripts) + processFile( + args.infile, + outfile=args.outfile, + gen_imgs=args.images, + imgroot=args.imgroot, + prefix="// " if args.comments_only else "" + ) + + sys.exit(0) + + +if __name__ == "__main__": + main() + + +# vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/scripts/make_all_docs.sh b/scripts/make_all_docs.sh new file mode 100755 index 0000000..02a70b7 --- /dev/null +++ b/scripts/make_all_docs.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +PREVIEW_LIBS="constants compat transforms shapes masks paths beziers math metric_screws threading involute_gears sliders joiners linear_bearings nema_steppers wiring triangulation quaternions phillips_drive torx_drive debug" + +dir="$(basename $PWD)" +if [ "$dir" = "BOSL" ]; then + cd BOSL.wiki +elif [ "$dir" != "BOSL.wiki" ]; then + echo "Must run this script from the BOSL or BOSL/BOSL.wiki directories." + exit 1 +fi + +rm -f tmpscad*.scad +for lib in $PREVIEW_LIBS; do + mkdir -p images/$lib + rm -f images/$lib/*.png images/$lib/*.gif + echo ../scripts/docs_gen.py ../$lib.scad -o $lib.scad.md -c -i -I images/$lib/ + ../scripts/docs_gen.py ../$lib.scad -o $lib.scad.md -c -i -I images/$lib/ || exit 1 + open -a Typora $lib.scad.md +done + + diff --git a/shapes.scad b/shapes.scad index 8532108..062ce09 100644 --- a/shapes.scad +++ b/shapes.scad @@ -1,5 +1,11 @@ ////////////////////////////////////////////////////////////////////// -// Compound Shapes. +// LibFile: shapes.scad +// Common useful shapes and structured objects. +// To use, add the following lines to the beginning of your file: +// ``` +// include +// use +// ``` ////////////////////////////////////////////////////////////////////// /* @@ -33,224 +39,337 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use use +include include -// For when you MUST pass a child to a module, but you want it to be nothing. -module nil() union() {} +// Section: Cuboids -// Creates a cube or cuboid object. +// Module: cuboid() +// +// Description: +// Creates a cube or cuboid object, with optional chamfering or filleting/rounding. +// +// Arguments: // size = The size of the cube. -// align = The side of the origin to align to. Use V_ constants from constants.scad. // chamfer = Size of chamfer, inset from sides. Default: No chamferring. // fillet = Radius of fillet for edge rounding. Default: No filleting. -// edges = Edges to chamfer/fillet. Use EDGE constants from constants.scad. Default: EDGES_ALL -// trimcorners = If true, rounds or chamfers corners where three chamferred/filleted edges meet. Default: true -// Examples: +// edges = Edges to chamfer/fillet. Use `EDGE` constants from constants.scad. Default: `EDGES_ALL` +// trimcorners = If true, rounds or chamfers corners where three chamferred/filleted edges meet. Default: `true` +// p1 = Align the cuboid's corner at `p1`, if given. Forces `align=V_UP+V_BACK+V_RIGHT`. +// p2 = If given with `p1`, defines the cornerpoints of the cuboid. +// align = The side of the origin to align to. Use `V_` constants from `constants.scad`. Default: `V_CENTER` +// center = If given, overrides `align`. A true value sets `align=V_CENTER`, false sets `align=V_UP+V_BACK+V_RIGHT`. +// +// Example: Simple regular cube. // cuboid(40); -// cuboid(40, align=V_UP+V_BACK); -// cuboid([20,40,60]); -// cuboid([30,40,60], chamfer=5); -// cuboid([30,40,60], fillet=10); -// cuboid([30,40,60], chamfer=5, edges=EDGE_TOP_FR+EDGE_TOP_RT+EDGE_FR_RT, $fn=24); -// cuboid([30,40,60], fillet=5, edges=EDGE_TOP_FR+EDGE_TOP_RT+EDGE_FR_RT, $fn=24); +// Example: Cube with minimum cornerpoint given. +// cuboid(20, p1=[10,0,0]); +// Example: Rectangular cube, with given X, Y, and Z sizes. +// cuboid([20,40,50]); +// Example: Rectangular cube defined by opposing cornerpoints. +// cuboid(p1=[0,10,0], p2=[20,30,30]); +// Example: Rectangular cube with chamferred edges and corners. +// cuboid([30,40,50], chamfer=5); +// Example: Rectangular cube with chamferred edges, without trimmed corners. +// cuboid([30,40,50], chamfer=5, trimcorners=false); +// Example: Rectangular cube with rounded edges and corners. +// cuboid([30,40,50], fillet=10); +// Example: Rectangular cube with rounded edges, without trimmed corners. +// cuboid([30,40,50], fillet=10, trimcorners=false); +// Example: Rectangular cube with only some edges chamferred. +// cuboid([30,40,50], chamfer=5, edges=EDGE_TOP_FR+EDGE_TOP_RT+EDGE_FR_RT, $fn=24); +// Example: Rectangular cube with only some edges rounded. +// cuboid([30,40,50], fillet=5, edges=EDGE_TOP_FR+EDGE_TOP_RT+EDGE_FR_RT, $fn=24); module cuboid( size=[1,1,1], - align=[0,0,0], + p1=undef, p2=undef, chamfer=undef, fillet=undef, edges=EDGES_ALL, - trimcorners=true + trimcorners=true, + align=[0,0,0], + center=undef ) { - size = scalar_vec(size); - majrots = [[0,90,0], [90,0,0], [0,0,0]]; - if (chamfer != undef) { - if (version_num()>20190000) { - assert(chamfer <= min(size)/2, "chamfer must be smaller than half the cube width, length, or height."); + size = scalar_vec3(size); + if (is_def(p1)) { + if (is_def(p2)) { + translate([for (v=array_zip([p1,p2],0)) min(v)]) { + cuboid(size=vabs(p2-p1), chamfer=chamfer, fillet=fillet, edges=edges, trimcorners=trimcorners, align=V_ALLPOS); + } } else { - if(chamfer > min(size)/2) { - echo("WARNING: chamfer must be smaller than half the cube width, length, or height."); + translate(p1) { + cuboid(size=size, chamfer=chamfer, fillet=fillet, edges=edges, trimcorners=trimcorners, align=V_ALLPOS); } } - } - if (fillet != undef) { - if (version_num()>20190000) { - assert(fillet <= min(size)/2, "fillet must be smaller than half the cube width, length, or height."); - } else { - if(fillet > min(size)/2) { - echo("WARNING: fillet must be smaller than half the cube width, length, or height."); - } - } - } - translate(vmul(size/2, align)) { - if (chamfer != undef) { - isize = [for (v = size) max(0.001, v-2*chamfer)]; - if (edges == EDGES_ALL && trimcorners) { - hull() { - cube([size[0], isize[1], isize[2]], center=true); - cube([isize[0], size[1], isize[2]], center=true); - cube([isize[0], isize[1], size[2]], center=true); - } - } else { - difference() { - cube(size, center=true); - - // Chamfer edges - for (i = [0:3], axis=[0:2]) { - if (edges[axis][i]>0) { - translate(vmul(EDGE_OFFSETS[axis][i], size/2)) { - rotate(majrots[axis]) { - zrot(45) cube([chamfer*sqrt(2), chamfer*sqrt(2), size[axis]+0.01], center=true); - } - } - } + } else { + majrots = [[0,90,0], [90,0,0], [0,0,0]]; + if (chamfer != undef) assertion(chamfer <= min(size)/2, "chamfer must be smaller than half the cube width, length, or height."); + if (fillet != undef) assertion(fillet <= min(size)/2, "fillet must be smaller than half the cube width, length, or height."); + algn = (!is_def(center))? (is_scalar(align)? align*V_UP : align) : (center==true)? V_CENTER : V_ALLPOS; + translate(vmul(size/2, algn)) { + if (chamfer != undef) { + isize = [for (v = size) max(0.001, v-2*chamfer)]; + if (edges == EDGES_ALL && trimcorners) { + hull() { + cube([size[0], isize[1], isize[2]], center=true); + cube([isize[0], size[1], isize[2]], center=true); + cube([isize[0], isize[1], size[2]], center=true); } + } else { + difference() { + cube(size, center=true); - // Chamfer triple-edge corners. - if (trimcorners) { - for (za=[-1,1], ya=[-1,1], xa=[-1,1]) { - if (corner_edge_count(edges, [xa,ya,za]) > 2) { - translate(vmul([xa,ya,za]/2, size-[1,1,1]*chamfer*4/3)) { - rotate_from_to(V_UP, [xa,ya,za]) { - upcube(chamfer*3); - } - } - } - } - } - } - } - } else if (fillet != undef) { - sides = quantup(segs(fillet),4); - sc = 1/cos(180/sides); - isize = [for (v = size) max(0.001, v-2*fillet)]; - if (edges == EDGES_ALL) { - minkowski() { - cube(isize, center=true); - if (trimcorners) { - sphere(r=fillet*sc, $fn=sides); - } else { - intersection() { - zrot(180/sides) cylinder(r=fillet*sc, h=fillet*2, center=true, $fn=sides); - rotate([90,0,0]) zrot(180/sides) cylinder(r=fillet*sc, h=fillet*2, center=true, $fn=sides); - rotate([0,90,0]) zrot(180/sides) cylinder(r=fillet*sc, h=fillet*2, center=true, $fn=sides); - } - } - } - } else { - difference() { - cube(size, center=true); - - // Round edges. - for (i = [0:3], axis=[0:2]) { - if (edges[axis][i]>0) { - difference() { + // Chamfer edges + for (i = [0:3], axis=[0:2]) { + if (edges[axis][i]>0) { translate(vmul(EDGE_OFFSETS[axis][i], size/2)) { - rotate(majrots[axis]) cube([fillet*2, fillet*2, size[axis]+0.1], center=true); - } - translate(vmul(EDGE_OFFSETS[axis][i], size/2 - [1,1,1]*fillet)) { - rotate(majrots[axis]) zrot(180/sides) cylinder(h=size[axis]+0.2, r=fillet*sc, center=true, $fn=sides); + rotate(majrots[axis]) { + zrot(45) cube([chamfer*sqrt(2), chamfer*sqrt(2), size[axis]+0.01], center=true); + } } } } - } - // Round triple-edge corners. - if (trimcorners) { - for (za=[-1,1], ya=[-1,1], xa=[-1,1]) { - if (corner_edge_count(edges, [xa,ya,za]) > 2) { - difference() { - translate(vmul([xa,ya,za], size/2)) { - cube(fillet*2, center=true); - } - translate(vmul([xa,ya,za], size/2-[1,1,1]*fillet)) { - zrot(180/sides) sphere(r=fillet*sc*sc, $fn=sides); + // Chamfer triple-edge corners. + if (trimcorners) { + for (za=[-1,1], ya=[-1,1], xa=[-1,1]) { + if (corner_edge_count(edges, [xa,ya,za]) > 2) { + translate(vmul([xa,ya,za]/2, size-[1,1,1]*chamfer*4/3)) { + rot(from=V_UP, to=[xa,ya,za]) { + upcube(chamfer*3); + } } } } } } } + } else if (fillet != undef) { + sides = quantup(segs(fillet),4); + sc = 1/cos(180/sides); + isize = [for (v = size) max(0.001, v-2*fillet)]; + if (edges == EDGES_ALL) { + minkowski() { + cube(isize, center=true); + if (trimcorners) { + sphere(r=fillet*sc, $fn=sides); + } else { + intersection() { + zrot(180/sides) cylinder(r=fillet*sc, h=fillet*2, center=true, $fn=sides); + rotate([90,0,0]) zrot(180/sides) cylinder(r=fillet*sc, h=fillet*2, center=true, $fn=sides); + rotate([0,90,0]) zrot(180/sides) cylinder(r=fillet*sc, h=fillet*2, center=true, $fn=sides); + } + } + } + } else { + difference() { + cube(size, center=true); + + // Round edges. + for (i = [0:3], axis=[0:2]) { + if (edges[axis][i]>0) { + difference() { + translate(vmul(EDGE_OFFSETS[axis][i], size/2)) { + rotate(majrots[axis]) cube([fillet*2, fillet*2, size[axis]+0.1], center=true); + } + translate(vmul(EDGE_OFFSETS[axis][i], size/2 - [1,1,1]*fillet)) { + rotate(majrots[axis]) zrot(180/sides) cylinder(h=size[axis]+0.2, r=fillet*sc, center=true, $fn=sides); + } + } + } + } + + // Round triple-edge corners. + if (trimcorners) { + for (za=[-1,1], ya=[-1,1], xa=[-1,1]) { + if (corner_edge_count(edges, [xa,ya,za]) > 2) { + difference() { + translate(vmul([xa,ya,za], size/2)) { + cube(fillet*2, center=true); + } + translate(vmul([xa,ya,za], size/2-[1,1,1]*fillet)) { + zrot(180/sides) sphere(r=fillet*sc*sc, $fn=sides); + } + } + } + } + } + } + } + } else { + cube(size=size, center=true); } - } else { - cube(size=size, center=true); } } } -// Creates a cube between two points. +// Module: cube2pt() +// Status: DEPRECATED, use `cuboid(p1,p2)` instead. +// +// Usage: +// cube2pt(p1,p2) +// +// Description: +// Creates a cube between two points. +// +// Arguments: // p1 = Coordinate point of one cube corner. // p2 = Coordinate point of opposite cube corner. -// Example: -// cube2pt([10,20,30], [40,-10,10]); module cube2pt(p1,p2) { - translate([min(p1[0],p2[0]), min(p1[1],p2[1]), min(p1[2],p2[2])]) { - cube([abs(p2[0]-p1[0]), abs(p2[1]-p1[1]), abs(p2[2]-p1[2])], center=false); - } + deprecate("cube2pt()", "cuboid(p1,p2)"); + cuboid(p1=p1, p2=p2); } -// Creates a cube that spans the X, Y, and Z ranges given. + +// Module: span_cube() +// +// Description: +// Creates a cube that spans the X, Y, and Z ranges given. +// +// Arguments: // xspan = [min, max] X axis range. // yspan = [min, max] Y axis range. // zspan = [min, max] Z axis range. +// // Example: -// span_cube([10,40], [-10, 20], [10,30]); +// span_cube([0,15], [5,10], [0, 10]); module span_cube(xspan, yspan, zspan) { - cube2pt([xspan[0], yspan[0], zspan[0]], [xspan[1], yspan[1], zspan[1]]); + span = [xspan, yspan, zspan]; + cuboid(p1=array_subindex(span,0), p2=array_subindex(span,1)); } - -// Makes a cube that is offset along the given vector by half the cube's size. -// For example, if v=[-1,1,0], the cube's front right edge will be centered at the origin. +// Module: offsetcube() +// Status: DEPRECATED, use `cuboid(..., align)` instead. +// +// Description: +// Makes a cube that is offset along the given vector by half the cube's size. +// For example, if `v=[-1,1,0]`, the cube's front right edge will be centered at the origin. +// +// Arguments: // size = size of cube. // v = vector to offset along. -// Example: -// offsetcube([3,4,5], [-1,1,0]); module offsetcube(size=[1,1,1], v=[0,0,0]) { - echo("DEPRECATED: You should use cuboid() instead of offsetcube()"); + deprecate("offsetcube()", "cuboid()"); cuboid(size=size, align=v); } -// Makes a cube that has its right face centered at the origin. -module leftcube(size=[1,1,1]) cuboid(size=size, align=V_LEFT); - - -// Makes a cube that has its left face centered at the origin. -module rightcube(size=[1,1,1]) cuboid(size=size, align=V_RIGHT); - - -// Makes a cube that has its back face centered at the origin. -module fwdcube(size=[1,1,1]) cuboid(size=size, align=V_FWD); - - -// Makes a cube that has its front face centered at the origin. -module backcube(size=[1,1,1]) cuboid(size=size, align=V_BACK); - - -// Makes a cube that has its top face centered at the origin. -module downcube(size=[1,1,1]) cuboid(size=size, align=V_DOWN); - - -// Makes a cube that has its bottom face centered at the origin. -module upcube(size=[1,1,1]) cuboid(size=size, align=V_UP); - - -// Makes a cube with chamfered edges. -// size = size of cube [X,Y,Z]. (Default: [1,1,1]) -// chamfer = chamfer inset along axis. (Default: 0.25) -// chamfaxes = Array [X, Y, Z] of boolean values to specify which axis edges should be chamfered. -// chamfcorners = boolean to specify if corners should be flat chamferred. +// Module: leftcube() +// +// Description: +// Makes a cube that is aligned on the left side of the origin. +// +// Usage: +// leftcube(size); +// +// Arguments: +// size = The size of the cube to make. +// // Example: -// chamfcube(size=[10,30,50], chamfer=1, chamfaxes=[1,1,1], chamfcorners=true); +// leftcube([20,30,40]); +module leftcube(size=[1,1,1]) {siz = scalar_vec3(size); left(siz[0]/2) cube(size=size, center=true);} + + +// Module: rightcube() +// +// Description: +// Makes a cube that is aligned on the right side of the origin. +// +// Usage: +// rightcube(size); +// +// Arguments: +// size = The size of the cube to make. +// +// Example: +// rightcube([20,30,40]); +module rightcube(size=[1,1,1]) {siz = scalar_vec3(size); right(siz[0]/2) cube(size=size, center=true);} + + +// Module: fwdcube() +// +// Description: +// Makes a cube that is aligned on the front side of the origin. +// +// Usage: +// fwdcube(size); +// +// Arguments: +// size = The size of the cube to make. +// +// Example: +// fwdcube([20,30,40]); +module fwdcube(size=[1,1,1]) {siz = scalar_vec3(size); fwd(siz[1]/2) cube(size=size, center=true);} + + +// Module: backcube() +// +// Description: +// Makes a cube that is aligned on the front side of the origin. +// +// Usage: +// backcube(size); +// +// Arguments: +// size = The size of the cube to make. +// +// Example: +// backcube([20,30,40]); +module backcube(size=[1,1,1]) {siz = scalar_vec3(size); back(siz[1]/2) cube(size=size, center=true);} + + +// Module: downcube() +// +// Description: +// Makes a cube that is aligned on the bottom side of the origin. +// +// Usage: +// downcube(size); +// +// Arguments: +// size = The size of the cube to make. +// +// Example: +// downcube([20,30,40]); +module downcube(size=[1,1,1]) {siz = scalar_vec3(size); down(siz[2]/2) cube(size=size, center=true);} + + +// Module: upcube() +// +// Description: +// Makes a cube that is aligned on the top side of the origin. +// +// Usage: +// upcube(size); +// +// Arguments: +// size = The size of the cube to make. +// +// Example: +// upcube([20,30,40]); +module upcube(size=[1,1,1]) {siz = scalar_vec3(size); up(siz[2]/2) cube(size=size, center=true);} + + +// Module: chamfcube() +// Status: DEPRECATED, use `cuboid(..., chamfer, edges, trimcorners)` instead. +// +// Description: +// Makes a cube with chamfered edges. +// +// Arguments: +// size = Size of cube [X,Y,Z]. (Default: `[1,1,1]`) +// chamfer = Chamfer inset along axis. (Default: `0.25`) +// chamfaxes = Array [X,Y,Z] of boolean values to specify which axis edges should be chamfered. +// chamfcorners = Boolean to specify if corners should be flat chamferred. module chamfcube(size=[1,1,1], chamfer=0.25, chamfaxes=[1,1,1], chamfcorners=false) { - echo("DEPRECATED: Use cuboid() instead of chamfcube()"); + deprecate("chamfcube()", "cuboid()"); cuboid( size=size, chamfer=chamfer, @@ -264,80 +383,404 @@ module chamfcube(size=[1,1,1], chamfer=0.25, chamfaxes=[1,1,1], chamfcorners=fal } -// Makes a cube with rounded (filletted) vertical edges. The r size will be -// limited to a maximum of half the length of the shortest XY side. -// size = size of cube [X,Y,Z]. (Default: [1,1,1]) -// r = radius of edge/corner rounding. (Default: 0.25) -// center = if true, object will be centered. If false, sits on top of XY plane. -// Examples: -// rrect(size=[9,4,1], r=1, center=true); -// rrect(size=[5,7,3], r=1, $fn=24); +// Module: rrect() +// Status: DEPRECATED, use `cuboid(..., fillet, edges)` instead. +// +// Description: +// Makes a cube with rounded (filletted) vertical edges. The `r` size will be +// limited to a maximum of half the length of the shortest XY side. +// +// Arguments: +// size = Size of cube [X,Y,Z]. (Default: `[1,1,1]`) +// r = Radius of edge/corner rounding. (Default: `0.25`) +// center = If true, object will be centered. If false, sits on top of XY plane. module rrect(size=[1,1,1], r=0.25, center=false) { - echo("DEPRECATED: Use cuboid() instead of rrect()"); - cuboid(size=size, filler=r, edges=EDGES_Z_ALL, align=center? V_ZERO : V_UP); + deprecate("rrect()", "cuboid()"); + cuboid(size=size, fillet=r, edges=EDGES_Z_ALL, align=center? V_CENTER : V_UP); } -// Makes a cube with rounded (filletted) edges and corners. The r size will be -// limited to a maximum of half the length of the shortest side. -// size = size of cube [X,Y,Z]. (Default: [1,1,1]) -// r = radius of edge/corner rounding. (Default: 0.25) -// center = if true, object will be centered. If false, sits on top of XY plane. -// Examples: -// rcube(size=[9,4,1], r=0.333, center=true, $fn=24); -// rcube(size=[5,7,3], r=1); +// Module: rcube() +// Status: DEPRECATED, use `cuboid(..., fillet)` instead. +// +// Description: +// Makes a cube with rounded (filletted) edges and corners. The `r` size will be +// limited to a maximum of half the length of the shortest cube side. +// +// Arguments: +// size = Size of cube [X,Y,Z]. (Default: `[1,1,1]`) +// r = Radius of edge/corner rounding. (Default: `0.25`) +// center = If true, object will be centered. If false, sits on top of XY plane. module rcube(size=[1,1,1], r=0.25, center=false) { - echo("DEPRECATED: Use cuboid() instead of rcube()"); - cuboid(size=size, fillet=r, align=center? V_ZERO : V_UP); + deprecate("rcube()", "cuboid()"); + cuboid(size=size, fillet=r, align=center? V_CENTER : V_UP); } -// Creates cylinders in various alignments and orientations, -// with optional fillets and chamfers. -// l = Length of cylinder along axis. +// Section: Prismoids + + +// Module: prismoid() +// +// Description: +// Creates a rectangular prismoid shape. +// +// Usage: +// prismoid(size1, size2, h, [shift], [orient], [align|center]); +// +// Arguments: +// size1 = [width, length] of the axis-negative end of the prism. +// size2 = [width, length] of the axis-positive end of the prism. +// h = Height of the prism. +// shift = [x, y] amount to shift the center of the top with respect to the center of the bottom. +// orient = Orientation of the prismoid. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the prismoid by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `ALIGN_POS`. +// center = If given, overrides `align`. A true value sets `align=V_CENTER`, false sets `align=ALIGN_POS`. +// +// Example: Rectangular Pyramid +// prismoid(size1=[40,40], size2=[0,0], h=20); +// Example: Prism +// prismoid(size1=[40,40], size2=[0,40], h=20); +// Example: Truncated Pyramid +// prismoid(size1=[35,50], size2=[20,30], h=20); +// Example: Wedge +// prismoid(size1=[60,35], size2=[30,0], h=30); +// Example: Truncated Tetrahedron +// prismoid(size1=[10,40], size2=[40,10], h=40); +// Example: Inverted Truncated Pyramid +// prismoid(size1=[15,5], size2=[30,20], h=20); +// Example: Right Prism +// prismoid(size1=[30,60], size2=[0,60], shift=[-15,0], h=30); +// Example(FlatSpin): Shifting/Skewing +// prismoid(size1=[50,30], size2=[20,20], h=20, shift=[15,5]); +module prismoid( + size1=[1,1], size2=[1,1], h=1, shift=[0,0], + orient=ORIENT_Z, align=ALIGN_POS, center=undef +) { + eps = 0.001; + s1 = [max(size1[0], eps), max(size1[1], eps)]; + s2 = [max(size2[0], eps), max(size2[1], eps)]; + shiftby = point3d(shift); + orient_and_align([s1[0], s1[1], h], orient, align, center, noncentered=ALIGN_POS) { + polyhedron( + points=[ + [+s2[0]/2, +s2[1]/2, +h/2] + shiftby, + [+s2[0]/2, -s2[1]/2, +h/2] + shiftby, + [-s2[0]/2, -s2[1]/2, +h/2] + shiftby, + [-s2[0]/2, +s2[1]/2, +h/2] + shiftby, + [+s1[0]/2, +s1[1]/2, -h/2], + [+s1[0]/2, -s1[1]/2, -h/2], + [-s1[0]/2, -s1[1]/2, -h/2], + [-s1[0]/2, +s1[1]/2, -h/2], + ], + faces=[ + [0, 1, 2], + [0, 2, 3], + [0, 4, 5], + [0, 5, 1], + [1, 5, 6], + [1, 6, 2], + [2, 6, 7], + [2, 7, 3], + [3, 7, 4], + [3, 4, 0], + [4, 7, 6], + [4, 6, 5], + ], + convexity=2 + ); + } +} + + +// Module: trapezoid() +// Status: DEPRECATED, use `prismoid()` instead. +// +// Description: +// Creates a rectangular prismoid shape. +// +// Usage: +// trapezoid(size1, size2, h, [shift], [orient], [align|center]); +// +// Arguments: +// size1 = [width, length] of the axis-negative end of the prism. +// size2 = [width, length] of the axis-positive end of the prism. +// h = Height of the prism. +// shift = [x, y] amount to shift the center of the top with respect to the center of the bottom. +// orient = Orientation of the prismoid. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the prismoid by the axis-negative (size1) end. Use the `V_` constants from `constants.scad`. Default: `V_UP` +// center = If given, overrides `align`. A true value sets `align=V_CENTER`, false sets `align=V_UP`. +module trapezoid(size1=[1,1], size2=[1,1], h=1, center=false) { + deprecate("trapezoid()", "prismoid()"); + prismoid(size=size, size2=size2, h=h, center=center); +} + + +// Module: rounded_prismoid() +// +// Description: +// Creates a rectangular prismoid shape with rounded vertical edges. +// +// Arguments: +// size1 = [width, length] of the bottom of the prism. +// size2 = [width, length] of the top of the prism. +// h = Height of the prism. +// r = radius of vertical edge fillets. +// r1 = radius of vertical edge fillets at bottom. +// r2 = radius of vertical edge fillets at top. +// shift = [x, y] amount to shift the center of the top with respect to the center of the bottom. +// orient = Orientation of the prismoid. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the prismoid by the axis-negative (`size1`) end. Use the `V_` constants from `constants.scad`. Default: `V_UP`. +// center = vertically center the prism. Overrides `align`. +// +// Example: Rounded Pyramid +// rounded_prismoid(size1=[40,40], size2=[0,0], h=25, r=5); +// Example: Centered Rounded Pyramid +// rounded_prismoid(size1=[40,40], size2=[0,0], h=25, r=5, center=true); +// Example: Disparate Top and Bottom Radii +// rounded_prismoid(size1=[40,60], size2=[40,60], h=20, r1=3, r2=10, $fn=24); +// Example(FlatSpin): Shifting/Skewing +// rounded_prismoid(size1=[50,30], size2=[20,20], h=20, shift=[15,5], r=5); +module rounded_prismoid( + size1, size2, h, shift=[0,0], + r=undef, r1=undef, r2=undef, + align=V_UP, orient=ORIENT_Z, center=undef +) { + eps = 0.001; + maxrad1 = min(size1[0]/2, size1[1]/2); + maxrad2 = min(size2[0]/2, size2[1]/2); + rr1 = min(maxrad1, (r1!=undef)? r1 : r); + rr2 = min(maxrad2, (r2!=undef)? r2 : r); + shiftby = point3d(shift); + orient_and_align([size1.x, size1.y, h], orient, align, center, noncentered=ALIGN_POS) { + down(h/2) + hull() { + linear_extrude(height=eps, center=false, convexity=2) { + offset(r=rr1) { + square([max(eps, size1[0]-2*rr1), max(eps, size1[1]-2*rr1)], center=true); + } + } + up(h-0.01) { + translate(shiftby) { + linear_extrude(height=eps, center=false, convexity=2) { + offset(r=rr2) { + square([max(eps, size2[0]-2*rr2), max(eps, size2[1]-2*rr2)], center=true); + } + } + } + } + } + } +} + + + +// Module: pyramid() +// Status: DEPRECATED, use `cyl(, r2=0, $fn=N)` instead. +// +// Usage: +// pyramid(n, h, l|r|d, [circum]); +// +// Description: +// Creates a pyramidal prism with a given number of sides. +// +// Arguments: +// n = number of pyramid sides. +// h = height of the pyramid. +// l = length of one side of the pyramid. (optional) +// r = radius of the base of the pyramid. (optional) +// d = diameter of the base of the pyramid. (optional) +// circum = base circumscribes the circle of the given radius or diam. +module pyramid(n=4, h=1, l=1, r=undef, d=undef, circum=false) +{ + deprecate("pyramid()", "cyl()"); + radius = get_radius(r=r, d=d, dflt=l/2/sin(180/n)); + cyl(r1=radius, r2=0, l=h, circum=circum, $fn=n, realign=true, align=ALIGN_POS); +} + + +// Module: prism() +// Status: DEPRECATED, use `cyl(..., $fn=N)` instead. +// +// Usage: +// prism(n, h, l|r|d, [circum]); +// +// Description: +// Creates a vertical prism with a given number of sides. +// +// Arguments: +// n = number of sides. +// h = height of the prism. +// l = length of one side of the prism. (optional) +// r = radius of the prism. (optional) +// d = diameter of the prism. (optional) +// circum = prism circumscribes the circle of the given radius or diam. +module prism(n=3, h=1, l=1, r=undef, d=undef, circum=false, center=false) +{ + deprecate("prism()", "cyl()"); + radius = get_radius(r=r, d=d, dflt=l/2/sin(180/n)); + cyl(r=radius, l=h, circum=circum, $fn=n, realign=true, center=center); +} + + +// Module: right_triangle() +// +// Description: +// Creates a 3D right triangular prism. +// +// Usage: +// right_triangle(size, [orient], [align|center]); +// +// Arguments: +// size = [width, thickness, height] +// orient = The axis to place the hypotenuse along. Only accepts `ORIENT_X`, `ORIENT_Y`, or `ORIENT_Z` from `constants.scad`. Default: `ORIENT_Y`. +// align = The side of the origin to align to. Use `V_` constants from `constants.scad`. Default: `V_UP+V_BACK+V_RIGHT`. +// center = If given, overrides `align`. A true value sets `align=V_CENTER`, false sets `align=V_UP+V_BACK+V_RIGHT`. +// +// Example: Centered +// right_triangle([60, 10, 40], center=true); +// Example: *Non*-Centered +// right_triangle([60, 10, 40]); +module right_triangle(size=[1, 1, 1], orient=ORIENT_Y, align=V_ALLPOS, center=undef) +{ + siz = scalar_vec3(size); + orient_and_align(siz, align=align, center=center) { + if (orient == ORIENT_X) { + ang = atan2(siz[1], siz[2]); + masksize = [siz[0], siz[1], norm([siz[1],siz[2]])] + [1,1,1]; + xrot(ang) { + difference() { + xrot(-ang) cube(siz, center=true); + back(masksize[1]/2) cube(masksize, center=true); + } + } + } else if (orient == ORIENT_Y) { + ang = atan2(siz[0], siz[2]); + masksize = [siz[0], siz[1], norm([siz[0],siz[2]])] + [1,1,1]; + yrot(-ang) { + difference() { + yrot(ang) cube(siz, center=true); + right(masksize[0]/2) cube(masksize, center=true); + } + } + } else if (orient == ORIENT_Z) { + ang = atan2(siz[0], siz[1]); + masksize = [norm([siz[0],siz[1]]), siz[1], siz[2]] + [1,1,1]; + zrot(-ang) { + difference() { + zrot(ang) cube(siz, center=true); + back(masksize[1]/2) cube(masksize, center=true); + } + } + } + } +} + + + +// Section: Cylindroids + + +// Module: cyl() +// +// Description: +// Creates cylinders in various alignments and orientations, +// with optional fillets and chamfers. You can use `r` and `l` +// interchangably, and all variants allow specifying size +// by either `r`|`d`, or `r1`|`d1` and `r2`|`d2`. +// Note that that chamfers and fillets cannot cross the +// midpoint of the cylinder's length. +// +// Usage: Normal Cylinders +// cyl(l|h, r|d, [circum], [realign], [orient], [align], [center]); +// cyl(l|h, r1|d1, r2/d2, [circum], [realign], [orient], [align], [center]); +// +// Usage: Chamferred Cylinders +// cyl(l|h, r|d, chamfer, [chamfang], [from_end], [circum], [realign], [orient], [align], [center]); +// cyl(l|h, r|d, chamfer1, [chamfang1], [from_end], [circum], [realign], [orient], [align], [center]); +// cyl(l|h, r|d, chamfer2, [chamfang2], [from_end], [circum], [realign], [orient], [align], [center]); +// cyl(l|h, r|d, chamfer1, chamfer2, [chamfang1], [chamfang2], [from_end], [circum], [realign], [orient], [align], [center]); +// +// Usage: Rounded/Filleted Cylinders +// cyl(l|h, r|d, fillet, [circum], [realign], [orient], [align], [center]); +// cyl(l|h, r|d, fillet1, [circum], [realign], [orient], [align], [center]); +// cyl(l|h, r|d, fillet2, [circum], [realign], [orient], [align], [center]); +// cyl(l|h, r|d, fillet1, fillet2, [circum], [realign], [orient], [align], [center]); +// +// Arguments: +// l / h = Length of cylinder along oriented axis. (Default: 1.0) // r = Radius of cylinder. // r1 = Radius of the negative (X-, Y-, Z-) end of cylinder. // r2 = Radius of the positive (X+, Y+, Z+) end of cylinder. // d = Diameter of cylinder. // d1 = Diameter of the negative (X-, Y-, Z-) end of cylinder. // d2 = Diameter of the positive (X+, Y+, Z+) end of cylinder. +// circum = If true, cylinder should circumscribe the circle of the given size. Otherwise inscribes. Default: `false` // chamfer = The size of the chamfers on the ends of the cylinder. Default: none. // chamfer1 = The size of the chamfer on the axis-negative end of the cylinder. Default: none. // chamfer2 = The size of the chamfer on the axis-positive end of the cylinder. Default: none. // chamfang = The angle in degrees of the chamfers on the ends of the cylinder. // chamfang1 = The angle in degrees of the chamfer on the axis-negative end of the cylinder. // chamfang2 = The angle in degrees of the chamfer on the axis-positive end of the cylinder. -// from_end = If true, chamfer is measured from the end of the cylinder, instead of inset from the edge. Default: false. +// from_end = If true, chamfer is measured from the end of the cylinder, instead of inset from the edge. Default: `false`. // fillet = The radius of the fillets on the ends of the cylinder. Default: none. // fillet1 = The radius of the fillet on the axis-negative end of the cylinder. // fillet2 = The radius of the fillet on the axis-positive end of the cylinder. -// circum = If true, cylinder should circumscribe the circle of the given size. Otherwise inscribes. Default: false // realign = If true, rotate the cylinder by half the angle of one face. -// orient = Orientation of the cylinder. Use the ORIENT_ constants from constants.h. Default: vertical. -// align = Alignment of the cylinder. Use the V_ constants from constants.h. Default: centered. -// Examples: -// cyl(l=100, r=25); -// cyl(l=100, r=25, orient=ORIENT_Y); -// cyl(l=100, d1=50, d2=20); -// cyl(l=100, r=25, chamfer=10); -// cyl(l=100, r=25, fillet=10); -// cyl(l=100, d1=50, d2=30, chamfer1=10, fillet2=8, from_end=true); +// orient = Orientation of the cylinder. Use the `ORIENT_` constants from `constants.scad`. Default: vertical. +// align = Alignment of the cylinder. Use the `V_` constants from `constants.scad`. Default: centered. +// center = If given, overrides `align`. A true value sets `align=V_CENTER`, false sets `align=ALIGN_POS`. +// +// Example: By Radius +// xdistribute(30) { +// cyl(l=40, r=10); +// cyl(l=40, r1=10, r2=5); +// } +// +// Example: By Diameter +// xdistribute(30) { +// cyl(l=40, d=25); +// cyl(l=40, d1=25, d2=10); +// } +// +// Example: Chamferring +// xdistribute(60) { +// // Shown Left to right. +// cyl(l=40, d=40, chamfer=7); // Default chamfang=45 +// cyl(l=40, d=40, chamfer=7, chamfang=30, from_end=false); +// cyl(l=40, d=40, chamfer=7, chamfang=30, from_end=true); +// } +// +// Example: Rounding/Filleting +// cyl(l=40, d=40, fillet=10); +// +// Example: Heterogenous Chamfers and Fillets +// ydistribute(80) { +// // Shown Front to Back. +// cyl(l=40, d=40, fillet1=15, orient=ORIENT_X); +// cyl(l=40, d=40, chamfer2=5, orient=ORIENT_X); +// cyl(l=40, d=40, chamfer1=12, fillet2=10, orient=ORIENT_X); +// } +// +// Example: Putting it all together +// cyl(l=40, d1=25, d2=15, chamfer1=10, chamfang1=30, from_end=true, fillet2=5); module cyl( - l=1, + l=undef, h=undef, r=undef, r1=undef, r2=undef, d=undef, d1=undef, d2=undef, chamfer=undef, chamfer1=undef, chamfer2=undef, chamfang=undef, chamfang1=undef, chamfang2=undef, fillet=undef, fillet1=undef, fillet2=undef, circum=false, realign=false, from_end=false, - orient=ORIENT_Z, align=V_ZERO + orient=ORIENT_Z, align=V_CENTER, center=undef ) { r1 = get_radius(r1, r, d1, d, 1); r2 = get_radius(r2, r, d2, d, 1); + l = first_defined([l, h, 1]); sides = segs(max(r1,r2)); sc = circum? 1/cos(180/sides) : 1; - orient_and_align([r1*2,r1*2,l], orient, align) { + orient_and_align([r1*2,r1*2,l], orient, align, center=center) { zrot(realign? 180/sides : 0) { if (!any_defined([chamfer, chamfer1, chamfer2, fillet, fillet1, fillet2])) { cylinder(h=l, r1=r1*sc, r2=r2*sc, center=true, $fn=sides); @@ -349,23 +792,31 @@ module cyl( cham2 = first_defined([chamfer2, chamfer]) * (from_end? 1 : tan(chang2)); fil1 = first_defined([fillet1, fillet]); fil2 = first_defined([fillet2, fillet]); - if (version_num()>20190000) { - if (cham1 != undef) { - assert(cham1 <= r1, "chamfer1 is larger than the r1 radius of the cylinder."); - assert(cham1 <= l/2, "chamfer1 is larger than half the length of the cylinder."); - } - if (cham2 != undef) { - assert(cham2 <= r2, "chamfer2 is larger than the r2 radius of the cylinder."); - assert(cham2 <= l/2, "chamfer2 is larger than half the length of the cylinder."); - } - if (fil1 != undef) { - assert(fil1 <= r1, "fillet1 is larger than the r1 radius of the cylinder."); - assert(fil1 <= l/2, "fillet1 is larger than half the length of the cylinder."); - } - if (fil2 != undef) { - assert(fil2 <= r2, "fillet2 is larger than the r1 radius of the cylinder."); - assert(fil2 <= l/2, "fillet2 is larger than half the length of the cylinder."); - } + if (chamfer != undef) { + assertion(chamfer <= r1, "chamfer is larger than the r1 radius of the cylinder."); + assertion(chamfer <= r2, "chamfer is larger than the r2 radius of the cylinder."); + assertion(chamfer <= l/2, "chamfer is larger than half the length of the cylinder."); + } + if (cham1 != undef) { + assertion(cham1 <= r1, "chamfer1 is larger than the r1 radius of the cylinder."); + assertion(cham1 <= l/2, "chamfer1 is larger than half the length of the cylinder."); + } + if (cham2 != undef) { + assertion(cham2 <= r2, "chamfer2 is larger than the r2 radius of the cylinder."); + assertion(cham2 <= l/2, "chamfer2 is larger than half the length of the cylinder."); + } + if (fillet != undef) { + assertion(fillet <= r1, "fillet is larger than the r1 radius of the cylinder."); + assertion(fillet <= r2, "fillet is larger than the r2 radius of the cylinder."); + assertion(fillet <= l/2, "fillet is larger than half the length of the cylinder."); + } + if (fil1 != undef) { + assertion(fil1 <= r1, "fillet1 is larger than the r1 radius of the cylinder."); + assertion(fil1 <= l/2, "fillet1 is larger than half the length of the cylinder."); + } + if (fil2 != undef) { + assertion(fil2 <= r2, "fillet2 is larger than the r1 radius of the cylinder."); + assertion(fil2 <= l/2, "fillet2 is larger than half the length of the cylinder."); } dy1 = first_defined([cham1, fil1, 0]); @@ -407,7 +858,6 @@ module cyl( if (cham1!=undef && cham1>0) { rr1 = sc * (r1 + (r2-r1)*dy1/l); chlen1 = min(rr1, cham1/sin(chang1)); - echo(vang=vang,chang1=chang2, chang2=chang2); translate([rr1,cham1]) { rotate(chang1) { left(chlen1) { @@ -456,20 +906,31 @@ module cyl( -// Creates a cylinder with its top face centered at the origin. -// h = height of cylinder. (Default: 1.0) -// r = radius of cylinder. (Default: 1.0) -// r1 = optional bottom radius of cylinder. -// r2 = optional top radius of cylinder. -// d = optional diameter of cylinder. (use instead of r) -// d1 = optional bottom diameter of cylinder. -// d2 = optional top diameter of cylinder. -// Example: -// downcyl(r=10, h=50); -// downcyl(r1=15, r2=5, h=45); -// downcyl(d=15, h=40); -module downcyl(r=undef, h=1, d=undef, d1=undef, d2=undef, r1=undef, r2=undef) +// Module: downcyl() +// +// Description: +// Creates a cylinder aligned below the origin. +// +// Usage: +// downcyl(l|h, r|d); +// downcyl(l|h, r1|d1, r2|d2); +// +// Arguments: +// l / h = Length of cylinder. (Default: 1.0) +// r = Radius of cylinder. +// r1 = Bottom radius of cylinder. +// r2 = Top radius of cylinder. +// d = Diameter of cylinder. (use instead of r) +// d1 = Bottom diameter of cylinder. +// d2 = Top diameter of cylinder. +// +// Example: Cylinder +// downcyl(r=20, h=40); +// Example: Cone +// downcyl(r1=10, r2=20, h=40); +module downcyl(r=undef, h=undef, l=undef, d=undef, d1=undef, d2=undef, r1=undef, r2=undef) { + h = first_defined([l, h, 1]); down(h/2) { cylinder(r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, h=h, center=true); } @@ -477,402 +938,236 @@ module downcyl(r=undef, h=1, d=undef, d1=undef, d2=undef, r1=undef, r2=undef) -// Creates a cylinder oriented along the X axis. -// Use like the built-in cylinder(), except use `l` instead of `h`. -// l = length of cylinder. (Default: 1.0) -// r = radius of cylinder. -// r1 = optional radius of left (X-) end of cylinder. -// r2 = optional radius of right (X+) end of cylinder. -// d = optional diameter of cylinder. (use instead of r) -// d1 = optional diameter of left (X-) end of cylinder. -// d2 = optional diameter of right (X+) end of cylinder. -// align = 0 for centered, +1 for left, -1 for right. -// Examples: -// xcyl(d1=5, d2=15, l=20, align=-1); -// xcyl(d=10, l=25); -module xcyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, align=0) +// Module: xcyl() +// +// Description: +// Creates a cylinder oriented along the X axis. +// +// Usage: +// xcyl(l|h, r|d, [align|center]); +// xcyl(l|h, r1|d1, r2|d2, [align|center]); +// +// Arguments: +// l / h = Length of cylinder along oriented axis. (Default: `1.0`) +// r = Radius of cylinder. +// r1 = Optional radius of left (X-) end of cylinder. +// r2 = Optional radius of right (X+) end of cylinder. +// d = Optional diameter of cylinder. (use instead of `r`) +// d1 = Optional diameter of left (X-) end of cylinder. +// d2 = Optional diameter of right (X+) end of cylinder. +// align = The side of the origin to align to. Use `V_` constants from `constants.scad`. Default: `V_CENTER` +// center = If given, overrides `align`. A `true` value sets `align=V_CENTER`, `false` sets `align=ALIGN_POS`. +// +// Example: By Radius +// ydistribute(50) { +// xcyl(l=35, r=10); +// xcyl(l=35, r1=15, r2=5); +// } +// +// Example: By Diameter +// ydistribute(50) { +// xcyl(l=35, d=20); +// xcyl(l=35, d1=30, d2=10); +// } +module xcyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, h=undef, align=V_CENTER, center=undef) { - right(align*l/2) { - yrot(90) cylinder(h=l, r=r, d=d, r1=r1, r2=r2, d1=d1, d2=d2, center=true); - } + cyl(l=l, h=h, r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, orient=ORIENT_X, align=align, center=center); } -// Creates a cylinder oriented along the Y axis. -// Use like the built-in cylinder(), except use `l` instead of `h`. -// l = length of cylinder. (Default: 1.0) -// r = radius of cylinder. -// r1 = optional radius of front (Y-) end of cylinder. -// r2 = optional radius of back (Y+) end of cylinder. -// d = optional diameter of cylinder. (use instead of r) -// d1 = optional diameter of front (Y-) end of cylinder. -// d2 = optional diameter of back (Y+) end of cylinder. -// align = 0 for centered, +1 for back, -1 for forward. -// Examples: -// ycyl(d1=5, d2=15, l=20, align=-1); -// ycyl(d=10, l=25); -module ycyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, align=0) +// Module: ycyl() +// +// Description: +// Creates a cylinder oriented along the Y axis. +// +// Usage: +// ycyl(l|h, r|d, [align|center]); +// ycyl(l|h, r1|d1, r2|d2, [align|center]); +// +// Arguments: +// l / h = Length of cylinder along oriented axis. (Default: `1.0`) +// r = Radius of cylinder. +// r1 = Radius of front (Y-) end of cone. +// r2 = Radius of back (Y+) end of one. +// d = Diameter of cylinder. +// d1 = Diameter of front (Y-) end of one. +// d2 = Diameter of back (Y+) end of one. +// align = The side of the origin to align to. Use `V_` constants from `constants.scad`. Default: `V_CENTER` +// center = Overrides `align` if given. If true, `align=V_CENTER`, if false, `align=ALIGN_POS`. +// +// Example: By Radius +// xdistribute(50) { +// ycyl(l=35, r=10); +// ycyl(l=35, r1=15, r2=5); +// } +// +// Example: By Diameter +// xdistribute(50) { +// ycyl(l=35, d=20); +// ycyl(l=35, d1=30, d2=10); +// } +module ycyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, h=undef, align=V_CENTER, center=undef) { - back(align*l/2) { - xrot(-90) cylinder(h=l, r=r, d=d, r1=r1, r2=r2, d1=d1, d2=d2, center=true); - } + cyl(l=l, h=h, r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, orient=ORIENT_Y, align=align, center=center); } -// Creates a cylinder oriented along the Z axis. Use like the built-in -// cylinder(), except use `l` instead of `h`. This module exists -// mostly for symmetry with xcyl() and ycyl(). -// l = length of cylinder. (Default: 1.0) -// r = radius of cylinder. -// r1 = optional radius of bottom (Z-) end of cylinder. -// r2 = optional radius of top (Z+) end of cylinder. -// d = optional diameter of cylinder. (use instead of r) -// d1 = optional diameter of bottom (Z-) end of cylinder. -// d2 = optional diameter of top (Z+) end of cylinder. -// align = 0 for centered, +1 for top, -1 for bottom. -// Examples: -// zcyl(d1=5, d2=15, l=20, align=-1); -// zcyl(d=10, l=25); -module zcyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, align=0) +// Module: zcyl() +// +// Description: +// Creates a cylinder oriented along the Z axis. +// +// Usage: +// zcyl(l|h, r|d, [align|center]); +// zcyl(l|h, r1|d1, r2|d2, [align|center]); +// +// Arguments: +// l / h = Length of cylinder along oriented axis. (Default: 1.0) +// r = Radius of cylinder. +// r1 = Radius of front (Y-) end of cone. +// r2 = Radius of back (Y+) end of one. +// d = Diameter of cylinder. +// d1 = Diameter of front (Y-) end of one. +// d2 = Diameter of back (Y+) end of one. +// align = The side of the origin to align to. Use `V_` constants from `constants.scad`. Default: `V_CENTER` +// center = Overrides `align` if given. If true, `align=V_CENTER`, if false, `align=ALIGN_POS`. +// +// Example: By Radius +// xdistribute(50) { +// zcyl(l=35, r=10); +// zcyl(l=35, r1=15, r2=5); +// } +// +// Example: By Diameter +// xdistribute(50) { +// zcyl(l=35, d=20); +// zcyl(l=35, d1=30, d2=10); +// } +module zcyl(l=undef, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, h=undef, align=V_CENTER, center=undef) { - up(align*l/2) { - cylinder(h=l, r=r, d=d, r1=r1, r2=r2, d1=d1, d2=d2, center=true); - } + cyl(l=l, h=h, r=r, r1=r1, r2=r2, d=d, d1=d1, d2=d2, orient=ORIENT_Z, align=align, center=center); } -// Creates a cylinder with chamferred (bevelled) edges. +// Module: chamferred_cylinder() +// Status: DEPRECATED, use `cyl(..., chamfer)` instead. +// +// Usage: +// chamferred_cylinder(h, r|d, chamfer|chamfedge, [top], [bottom], [center]) +// +// Description: +// Creates a cylinder with chamferred (bevelled) edges. +// +// Arguments: // h = height of cylinder. (Default: 1.0) // r = radius of cylinder. (Default: 1.0) // d = diameter of cylinder. (use instead of r) // chamfer = radial inset of the edge chamfer. (Default: 0.25) // chamfedge = length of the chamfer edge. (Use instead of chamfer) -// center = boolean. If true, cylinder is centered. (Default: false) // top = boolean. If true, chamfer the top edges. (Default: True) // bottom = boolean. If true, chamfer the bottom edges. (Default: True) -// Example: -// chamferred_cylinder(h=50, r=20, chamfer=5, angle=45, bottom=false, center=true); -// chamferred_cylinder(h=50, r=20, chamfedge=10, angle=30, center=true); +// center = boolean. If true, cylinder is centered. (Default: false) module chamferred_cylinder(h=1, r=undef, d=undef, chamfer=0.25, chamfedge=undef, angle=45, center=false, top=true, bottom=true) { - echo("DEPRECATED: You should use cyl() instead of chamf_cyl() or chamferred_cylinder()."); + deprecate("chamf_cyl()` and `chamferred_cylinder()", "cyl()"); r = get_radius(r=r, d=d, dflt=1); chamf = (chamfedge == undef)? chamfer : chamfedge * cos(angle); - cyl(l=h, r=r, chamfer1=bottom? chamf : 0, chamfer2=top? chamf : 0, chamfang=angle, align=center? V_ZERO : V_UP); + cyl(l=h, r=r, chamfer1=bottom? chamf : 0, chamfer2=top? chamf : 0, chamfang=angle, center=center); } + + +// Module: chamf_cyl() +// Status: DEPRECATED, use `cyl(..., chamfer)` instead. +// +// Usage: +// chamf_cyl(h, r|d, chamfer|chamfedge, [top], [bottom], [center]) +// +// Description: +// Creates a cylinder with chamferred (bevelled) edges. Basically a shortcut of `chamferred_cylinder()` +// +// Arguments: +// h = height of cylinder. (Default: 1.0) +// r = radius of cylinder. (Default: 1.0) +// d = diameter of cylinder. (use instead of r) +// chamfer = radial inset of the edge chamfer. (Default: 0.25) +// chamfedge = length of the chamfer edge. (Use instead of chamfer) +// top = boolean. If true, chamfer the top edges. (Default: True) +// bottom = boolean. If true, chamfer the bottom edges. (Default: True) +// center = boolean. If true, cylinder is centered. (Default: false) module chamf_cyl(h=1, r=undef, d=undef, chamfer=0.25, chamfedge=undef, angle=45, center=false, top=true, bottom=true) chamferred_cylinder(h=h, r=r, d=d, chamfer=chamfer, chamfedge=chamfedge, angle=angle, center=center, top=top, bottom=bottom); -//!chamf_cyl(h=20, d=20, chamfedge=10, angle=60, center=true, $fn=36); -// Creates a cylinder with filletted (rounded) ends. +// Module: filleted_cylinder() +// Status: DEPRECATED, use `cyl(..., fillet)` instead. +// +// Usage: +// filleted_cylinder(h, r|d, fillet, [center]); +// +// Description: +// Creates a cylinder with filletted (rounded) ends. +// +// Arguments: // h = height of cylinder. (Default: 1.0) // r = radius of cylinder. (Default: 1.0) // d = diameter of cylinder. (Use instead of r) // fillet = radius of the edge filleting. (Default: 0.25) // center = boolean. If true, cylinder is centered. (Default: false) -// Example: -// rcylinder(h=50, r1=20, r2=30, fillet=5, center=true); -// rcylinder(h=50, r=20, fillet=5, center=true); -module rcylinder(h=1, r=1, r1=undef, r2=undef, d=undef, d1=undef, d2=undef, fillet=0.25, center=false) { - echo("DEPRECATED: use cyl() instead of rcylinder()"); - cyl(l=h, r=r, d=d, r1=r1, r2=r2, d1=d1, d2=d2, fillet=fillet, orient=V_UP, align=center? V_ZERO : V_UP); -} - - - module filleted_cylinder(h=1, r=undef, d=undef, r1=undef, r2=undef, d1=undef, d2=undef, fillet=0.25, center=false) { - echo("DEPRECATED: use cyl() instead of filleted_cylinder()"); - cyl(l=h, r=r, d=d, r1=r1, r2=r2, d1=d1, d2=d2, fillet=fillet, orient=ORIENT_Z, align=center? V_ZERO : V_UP); + deprecate("filleted_cylinder()", "cyl()"); + cyl(l=h, r=r, d=d, r1=r1, r2=r2, d1=d1, d2=d2, fillet=fillet, orient=ORIENT_Z, center=center); } -// Creates a pyramidal prism with a given number of sides. -// n = number of pyramid sides. -// h = height of the pyramid. -// l = length of one side of the pyramid. (optional) -// r = radius of the base of the pyramid. (optional) -// d = diameter of the base of the pyramid. (optional) -// circum = base circumscribes the circle of the given radius or diam. -// Example: -// pyramid(h=3, d=4, n=6, circum=true); -module pyramid(n=4, h=1, l=1, r=undef, d=undef, circum=false) -{ - echo("DEPRECATED: use cyl() instead of pyramid()"); - radius = get_radius(r=r, d=d, dflt=l/2/sin(180/n)); - cyl(r1=radius, r2=0, l=h, circum=circum, $fn=n, realign=true, align=V_UP); -} - - -// Creates a vertical prism with a given number of sides. -// n = number of sides. -// h = height of the prism. -// l = length of one side of the prism. (optional) -// r = radius of the prism. (optional) -// d = diameter of the prism. (optional) -// circum = prism circumscribes the circle of the given radius or diam. -// Example: -// prism(n=8, h=3, d=4, circum=true); -module prism(n=3, h=1, l=1, r=undef, d=undef, circum=false, center=false) -{ - echo("DEPRECATED: use cyl() instead of prism()"); - radius = get_radius(r=r, d=d, dflt=l/2/sin(180/n)); - cyl(r=radius, l=h, circum=circum, $fn=n, realign=true, align=center? V_ZERO : V_UP); -} - - -// Creates a right triangle, with the hypotenuse on the right (X+) side. -// size = [width, thickness, height] -// center = true if triangle will be centered. -// Examples: -// right_triangle([4, 1, 6], center=true); -// right_triangle([4, 1, 9]); -module right_triangle(size=[1, 1, 1], center=false) -{ - w = size[0]; - thick = size[1]; - h = size[2]; - translate(center? [-w/2, -thick/2, -h/2] : [0, 0, 0]) { - polyhedron( - points=[ - [0, 0, 0], - [0, 0, h], - [w, 0, 0], - [0, thick, 0], - [0, thick, h], - [w, thick, 0] - ], - faces=[ - [0, 1, 2], - [0, 2, 5], - [0, 5, 3], - [0, 3, 4], - [0, 4, 1], - [1, 4, 5], - [1, 5, 2], - [3, 5, 4] - ], - convexity=2 - ); - } -} - - -// Creates a shape that can be unioned into a concave joint between two faces, to fillet them. -// Center this part along the edge to be chamferred and union it in. -// l = length of edge to fillet. -// r = radius of fillet. -// ang = angle between faces to fillet. -// overlap = overlap size for unioning with faces. -// Example: -// union() { -// translate([0,-2,-4]) upcube([20, 4, 24]); -// translate([0,10,-4]) upcube([20, 20, 4]); -// color("green") interior_fillet(l=20, r=10); -// } -module interior_fillet(l=1.0, r=1.0, ang=90, overlap=0.01) { - dy = r/tan(ang/2); - difference() { - translate([0,-overlap/tan(ang/2),-overlap]) { - if (ang == 90) { - translate([0,r/2,r/2]) cube([l,r,r], center=true); - } else { - rotate([90,0,90]) pie_slice(ang=ang, r=dy+overlap, h=l, center=true); - } - } - translate([0,dy,r]) xcyl(l=l+0.1, r=r); - } +// Module: rcylinder() +// Status: DEPRECATED, use `cyl(..., fillet)` instead. +// +// Usage: +// rcylinder(h, r|d, fillet, [center]); +// +// Description: +// Creates a cylinder with filletted (rounded) ends. +// Basically a shortcut for `filleted_cylinder()`. +// +// Arguments: +// h = height of cylinder. (Default: 1.0) +// r = radius of cylinder. (Default: 1.0) +// d = diameter of cylinder. (Use instead of r) +// fillet = radius of the edge filleting. (Default: 0.25) +// center = boolean. If true, cylinder is centered. (Default: false) +module rcylinder(h=1, r=1, r1=undef, r2=undef, d=undef, d1=undef, d2=undef, fillet=0.25, center=false) { + deprecate("rcylinder()", "cyl(..., fillet)"); + cyl(l=h, r=r, d=d, r1=r1, r2=r2, d1=d1, d2=d2, fillet=fillet, orient=ORIENT_Z, center=center); } -// Deprecated. Renamed to prismoid. -module trapezoid(size1=[1,1], size2=[1,1], h=1, center=false) { - echo("DEPRECATED: trapezoid() has been renamed to prismoid()."); - prismoid(size=size, size2=size2, h=h, align=center? V_ZERO : V_UP); -} - - -// Creates a rectangular prismoid shape. -// size1 = [width, length] of the axis-negative end of the prism. -// size2 = [width, length] of the axis-positive end of the prism. -// h = Height of the prism. -// orient = Orientation of the prismoid. Use the ORIENT_ constants from constants.h. Default: ORIENT_Z. -// align = Alignment of the prismoid by the axis-negative (size1) end. Use the V_ constants from constants.h. Default: V_UP. -// center = vertically center the prism. DEPRECATED ARGUMENT. Use align instead. -// Example: -// prismoid(size1=[2,6], size2=[4,0], h=4, center=false); -// prismoid(size1=[1,4], size2=[4,1], h=4, orient=ORIENT_X, align=V_UP+V_RIGHT+V_FWD); -// prismoid(size1=[1,4], size2=[4,1], h=4); -module prismoid( - size1=[1,1], size2=[1,1], h=1, - align=V_UP, orient=ORIENT_Z, center=undef) -{ - if (center != undef) { - echo("DEPRECATED ARGUMENT: in prismoid, use align instead of center"); - } - algn = (center == undef)? align : (center? V_ZERO : V_UP); - s1 = [max(size1[0], 0.001), max(size1[1], 0.001)]; - s2 = [max(size2[0], 0.001), max(size2[1], 0.001)]; - orient_and_align([s1[0], s1[1], h], orient, algn) { - polyhedron( - points=[ - [+s2[0]/2, +s2[1]/2, +h/2], - [+s2[0]/2, -s2[1]/2, +h/2], - [-s2[0]/2, -s2[1]/2, +h/2], - [-s2[0]/2, +s2[1]/2, +h/2], - [+s1[0]/2, +s1[1]/2, -h/2], - [+s1[0]/2, -s1[1]/2, -h/2], - [-s1[0]/2, -s1[1]/2, -h/2], - [-s1[0]/2, +s1[1]/2, -h/2], - ], - faces=[ - [0, 1, 2], - [0, 2, 3], - [0, 4, 5], - [0, 5, 1], - [1, 5, 6], - [1, 6, 2], - [2, 6, 7], - [2, 7, 3], - [3, 7, 4], - [3, 4, 0], - [4, 7, 6], - [4, 6, 5], - ], - convexity=2 - ); - } -} - - -// Creates a rectangular prismoid shape -// with rounded vertical edges. -// size1 = [width, length] of the bottom of the prism. -// size2 = [width, length] of the top of the prism. -// h = Height of the prism. -// r = radius of vertical edge fillets. -// r1 = radius of vertical edge fillets at bottom. -// r2 = radius of vertical edge fillets at top. -// orient = Orientation of the prismoid. Use the ORIENT_ constants from constants.h. Default: ORIENT_Z. -// align = Alignment of the prismoid by the axis-negative (size1) end. Use the V_ constants from constants.h. Default: V_UP. -// center = vertically center the prism. DEPRECATED ARGUMENT. Use align instead. -// Example: -// rounded_prismoid(size1=[40,40], size2=[0,0], h=40, r=5, center=false); -// rounded_prismoid(size1=[20,60], size2=[40,30], h=40, r1=5, r2=10, center=false); -// rounded_prismoid(size1=[40,60], size2=[35,55], h=40, r1=0, r2=10, center=true); -module rounded_prismoid( - size1, size2, h, - r=undef, r1=undef, r2=undef, - align=V_UP, orient=ORIENT_Z, center=undef -) { - eps = 0.001; - maxrad1 = min(size1[0]/2, size1[1]/2); - maxrad2 = min(size2[0]/2, size2[1]/2); - rr1 = min(maxrad1, (r1!=undef)? r1 : r); - rr2 = min(maxrad2, (r2!=undef)? r2 : r); - orient_and_align([size1[0], size1[1], h], orient, align) { - hull() { - linear_extrude(height=eps, center=false, convexity=2) { - offset(r=rr1) { - square([max(eps, size1[0]-2*rr1), max(eps, size1[1]-2*rr1)], center=true); - } - } - up(h-0.01) { - linear_extrude(height=eps, center=false, convexity=2) { - offset(r=rr2) { - square([max(eps, size2[0]-2*rr2), max(eps, size2[1]-2*rr2)], center=true); - } - } - } - } - } -} - - - - -// Makes a 2D teardrop shape. Useful for extruding into 3D printable holes. -// r = radius of circular part of teardrop. (Default: 1) -// d = diameter of spherical portion of bottom. (Use instead of r) -// ang = angle of hat walls from the Y axis. (Default: 45 degrees) -// cap_h = if given, height above center where the shape will be truncated. -// Examples: -// teardrop2d(r=30, ang=30); -// teardrop2d(r=35, ang=45, cap_h=40); -module teardrop2d(r=1, d=undef, ang=45, cap_h=undef) -{ - r = get_radius(r=r, d=d, dflt=1); - difference() { - hull() { - back(r*sin(ang)) { - yscale(1/tan(ang)) { - difference() { - zrot(45) square([2*r*cos(ang)/sqrt(2), 2*r*cos(ang)/sqrt(2)], center=true); - fwd(r/2) square([2*r, r], center=true); - } - } - } - zrot(90) circle(r=r, center=true); - } - if (cap_h != undef) { - back(r*3/2+cap_h) square([r*3, r*3], center=true); - } - } -} - - -// Makes a teardrop shape in the XZ plane. Useful for 3D printable holes. -// r = radius of circular part of teardrop. (Default: 1) -// d = diameter of spherical portion of bottom. (Use instead of r) -// h = thickness of teardrop. (Default: 1) -// ang = angle of hat walls from the Z axis. (Default: 45 degrees) -// cap_h = if given, height above center where the shape will be truncated. -// Example: -// teardrop(r=30, h=10, ang=30); -module teardrop(r=undef, d=undef, h=1, ang=45, cap_h=undef) -{ - r = get_radius(r=r, d=d, dflt=1); - xrot(90) { - linear_extrude(height=h, center=true, steps=2) { - teardrop2d(r=r, ang=ang, cap_h=cap_h); - } - } -} - - -// Created a sphere with a conical hat, to make a 3D teardrop. -// r = radius of spherical portion of the bottom. (Default: 1) -// d = diameter of spherical portion of bottom. (Use instead of r) -// h = height above sphere center to truncate teardrop shape. (Default: 1) -// maxang = angle of cone on top from vertical. -// Example: -// onion(h=15, r=10, maxang=30); -module onion(h=1, r=1, d=undef, maxang=45) -{ - r = (d!=undef)? (d/2.0) : r; - rotate_extrude(angle=360, convexity=2) { - difference() { - teardrop2d(r=r, ang=maxang, cap_h=h); - left(r+h/2) square(size=r*2+h, center=true); - } - } -} - - -// Makes a hollow tube with the given outer size and wall thickness. +// Module: tube() +// +// Description: +// Makes a hollow tube with the given outer size and wall thickness. +// +// Usage: +// tube(h, ir|id, wall, [realign], [orient], [align]); +// tube(h, or|od, wall, [realign], [orient], [align]); +// tube(h, ir|id, or|od, [realign], [orient], [align]); +// tube(h, ir1|id1, ir2|id2, wall, [realign], [orient], [align]); +// tube(h, or1|od1, or2|od2, wall, [realign], [orient], [align]); +// tube(h, ir1|id1, ir2|id2, or1|od1, or2|od2, [realign], [orient], [align]); +// +// Arguments: // h = height of tube. (Default: 1) -// r = Outer radius of tube. -// r1 = Outer radius of bottom of tube. (Default: value of r) -// r2 = Outer radius of top of tube. (Default: value of r) -// d = Outer diameter of tube. -// d1 = Outer diameter of bottom of tube. -// d2 = Outer diameter of top of tube. +// or = Outer radius of tube. +// or1 = Outer radius of bottom of tube. (Default: value of r) +// or2 = Outer radius of top of tube. (Default: value of r) +// od = Outer diameter of tube. +// od1 = Outer diameter of bottom of tube. +// od2 = Outer diameter of top of tube. // wall = horizontal thickness of tube wall. (Default 0.5) // ir = Inner radius of tube. // ir1 = Inner radius of bottom of tube. @@ -880,44 +1175,60 @@ module onion(h=1, r=1, d=undef, maxang=45) // id = Inner diameter of tube. // id1 = Inner diameter of bottom of tube. // id2 = Inner diameter of top of tube. -// orient = Orientation of the tube. Use the ORIENT_ constants from constants.h. Default: vertical. -// align = Alignment of the tube. Use the V_ constants from constants.h. Default: centered. -// Example: -// tube(h=3, r=4, wall=1, center=true); -// tube(h=6, r=4, wall=2, $fn=6); -// tube(h=3, r1=5, r2=7, wall=2, center=true); -// tube(h=30, r1=50, r2=70, ir1=50, ir2=50, center=true); -// tube(h=30, wall=5, r1=40, r2=50, center=false); +// realign = If true, rotate the tube by half the angle of one face. +// orient = Orientation of the tube. Use the `ORIENT_` constants from `constants.scad`. Default: vertical. +// align = Alignment of the tube. Use the `V_` constants from `constants.scad`. Default: centered. +// +// Example: These all Produce the Same Tube +// tube(h=30, or=40, wall=5); +// tube(h=30, ir=35, wall=5); +// tube(h=30, or=40, ir=35); +// tube(h=30, od=80, id=70); +// Example: These all Produce the Same Conical Tube +// tube(h=30, or1=40, or2=25, wall=5); +// tube(h=30, ir1=35, or2=20, wall=5); +// tube(h=30, or1=40, or2=25, ir1=35, ir2=20); +// Example: Circular Wedge +// tube(h=30, or1=40, or2=30, ir1=20, ir2=30); module tube( h=1, wall=undef, r=undef, r1=undef, r2=undef, d=undef, d1=undef, d2=undef, + or=undef, or1=undef, or2=undef, + od=undef, od1=undef, od2=undef, ir=undef, id=undef, ir1=undef, ir2=undef, id1=undef, id2=undef, - center=undef, orient=ORIENT_Z, align=V_UP + center=undef, orient=ORIENT_Z, align=ALIGN_POS, + realign=false ) { - r1 = first_defined([r1, d1/2, r, d/2, ir1+wall, id1/2+wall, ir+wall, id/2+wall]); - r2 = first_defined([r2, d2/2, r, d/2, ir2+wall, id2/2+wall, ir+wall, id/2+wall]); + r1 = first_defined([or1, od1/2, r1, d1/2, or, od/2, r, d/2, ir1+wall, id1/2+wall, ir+wall, id/2+wall]); + r2 = first_defined([or2, od2/2, r2, d2/2, or, od/2, r, d/2, ir2+wall, id2/2+wall, ir+wall, id/2+wall]); ir1 = first_defined([ir1, id1/2, ir, id/2, r1-wall, d1/2-wall, r-wall, d/2-wall]); ir2 = first_defined([ir2, id2/2, ir, id/2, r2-wall, d2/2-wall, r-wall, d/2-wall]); - if (version_num()>20190000) { - assert(ir1 <= r1, "Inner radius is larger than outer radius."); - assert(ir2 <= r2, "Inner radius is larger than outer radius."); - } else { - if (ir1 > r1) echo("WARNING: r1 is smaller than ir1."); - if (ir2 > r2) echo("WARNING: r2 is smaller than ir2."); - } - algn = (center == undef)? align : (center? V_ZERO : V_UP); - orient_and_align([r1*2,r1*2,h], orient, algn) { - difference() { - cylinder(h=h, r1=r1, r2=r2, center=true); - cylinder(h=h+0.05, r1=ir1, r2=ir2, center=true); + assertion(ir1 <= r1, "Inner radius is larger than outer radius."); + assertion(ir2 <= r2, "Inner radius is larger than outer radius."); + sides = segs(max(r1,r2)); + orient_and_align([r1*2,r1*2,h], orient, align, center=center) { + zrot(realign? 180/sides : 0) { + difference() { + cylinder(h=h, r1=r1, r2=r2, center=true, $fn=sides); + cylinder(h=h+0.05, r1=ir1, r2=ir2, center=true); + } } } } -// Creates a torus shape. +// Module: torus() +// +// Descriptiom: +// Creates a torus shape. +// +// Usage: +// torus(r|d, r2|d2, [orient], [align]); +// torus(or|od, ir|id, [orient], [align]); +// +// Arguments: // r = major radius of torus ring. (use with of 'r2', or 'd2') // r2 = minor radius of torus ring. (use with of 'r', or 'd') // d = major diameter of torus ring. (use with of 'r2', or 'd2') @@ -926,182 +1237,275 @@ module tube( // ir = inside radius of the torus. (use with 'or', or 'od') // od = outer diameter of the torus. (use with 'ir' or 'id') // id = inside diameter of the torus. (use with 'or' or 'od') +// orient = Orientation of the torus. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the torus. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// // Example: -// torus(r=30, r2=5); -// torus(d=50, r2=5); -// torus(d=60, d2=15); -// torus(od=60, ir=15); -// torus(or=30, ir=20, $fa=1, $fs=1); +// // These all produce the same torus. +// torus(r=22.5, r2=7.5); +// torus(d=45, d2=15); +// torus(or=30, ir=15); +// torus(od=60, id=30); module torus( r=undef, d=undef, r2=undef, d2=undef, or=undef, od=undef, - ir=undef, id=undef + ir=undef, id=undef, + orient=ORIENT_Z, align=V_CENTER, center=undef ) { orr = get_radius(r=or, d=od, dflt=1.0); irr = get_radius(r=ir, d=id, dflt=0.5); majrad = get_radius(r=r, d=d, dflt=(orr+irr)/2); minrad = get_radius(r=r2, d=d2, dflt=(orr-irr)/2); - rotate_extrude(convexity = 4) { - right(majrad) circle(minrad); + orient_and_align([(majrad+minrad)*2, (majrad+minrad)*2, minrad*2], orient, align, center=center) { + rotate_extrude(convexity=4) { + right(majrad) circle(minrad); + } } } -// Creates a pie slice shape. -// ang = pie slice angle in degrees. -// h = height of pie slice. -// r = radius of pie slice. -// r1 = bottom radius of pie slice. -// r2 = top radius of pie slice. -// d = diameter of pie slice. -// d1 = bottom diameter of pie slice. -// d2 = top diameter of pie slice. -// center = if true, centers pie slice vertically. Default: false + +// Section: Spheroids + + +// Module: staggered_sphere() +// +// Description: +// An alternate construction to the standard `sphere()` built-in, with different triangulation. +// +// Usage: +// staggered_sphere(r|d, [circum]) +// +// Arguments: +// r = Radius of the sphere. +// d = Diameter of the sphere. +// circum = If true, circumscribes the perfect sphere of the given size. +// // Example: -// pie_slice(ang=45, h=30, r1=100, r2=80); -module pie_slice(ang=30, h=1, r=10, r1=undef, r2=undef, d=undef, d1=undef, d2=undef, center=false) -{ - r1 = get_radius(r1, r, d1, d, 10); - r2 = get_radius(r2, r, d2, d, 10); - steps = ceil(segs(max(r1,r2))*ang/360); - step = ang/steps; +// staggered_sphere(d=100, circum=true, $fn=10); +module staggered_sphere(r=undef, d=undef, circum=false, align=V_CENTER) { + r = get_radius(r=r, d=d, dflt=1); + sides = segs(r); + vsides = max(3, ceil(sides/2))+1; + step = 360/sides; + vstep = 180/(vsides-1); + rr = circum? r/cos(180/sides)/cos(180/sides) : r; pts = concat( - [[0,0]], - [for (i=[0:steps]) let(a = i*step) [r1*cos(a), r1*sin(a)]] + [[0,0,rr]], + [ + for (p = [1:vsides-2], t = [0:sides-1]) let( + ta = (t+(p%2/2))*step, + pa = p*vstep + ) spherical_to_xyz(rr, ta, pa) + ], + [[0,0,-rr]] ); - linear_extrude(height=h, scale=r2/r1, center=center, convexity=2) { - polygon(pts); - } + pcnt = len(pts); + faces = concat( + [ + for (i = [1:sides]) each [ + [0, i%sides+1, i], + [pcnt-1, pcnt-1-(i%sides+1), pcnt-1-i] + ] + ], + [ + for (p = [0:vsides-4], i = [0:sides-1]) let( + b1 = 1+p*sides, + b2 = 1+(p+1)*sides, + v1 = b1+i, + v2 = b1+(i+1)%sides, + v3 = b2+((i+((p%2)?(sides-1):0))%sides), + v4 = b2+((i+1+((p%2)?(sides-1):0))%sides) + ) each [[v1,v4,v3], [v1,v2,v4]] + ] + ); + polyhedron(points=pts, faces=faces); } -// Makes a linear slot with rounded ends, appropriate for bolts to slide along. -// p1 = center of starting circle of slot. (Default: [0,0,0]) -// p2 = center of ending circle of slot. (Default: [1,0,0]) -// l = length of slot along the X axis. Use instead of p1 and p2. -// h = height of slot shape. (default: 1.0) -// r = radius of slot circle. (default: 0.5) -// r1 = bottom radius of slot cone. (use instead of r) -// r2 = top radius of slot cone. (use instead of r) -// d = diameter of slot circle. (default: 1.0) -// d1 = bottom diameter of slot cone. (use instead of d) -// d2 = top diameter of slot cone. (use instead of d) -// center = If true (default) centers vertically. Else, drops flush with XY plane. -// Examples: -// slot(l=50, h=5, d1=8, d2=10, center=false); -// slot([0,0,0], [50,50,0], h=5, d=10); -module slot( - p1=[0,0,0], p2=[1,0,0], h=1.0, - l=undef, center=true, - r=undef, r1=undef, r2=undef, - d=1.0, d1=undef, d2=undef -) { - r = (r != undef)? r : (d/2); - r1 = (r1 != undef)? r1 : ((d1 != undef)? (d1/2) : r); - r2 = (r2 != undef)? r2 : ((d2 != undef)? (d2/2) : r); - pt1 = l==undef? p1 : [-l/2, 0, 0]; - pt2 = l==undef? p2 : [ l/2, 0, 0]; - $fn = quantup(segs(max(r1,r2)),4); - down(center? 0 : h/2) { + +// Section: 3D Printing Shapes + + +// Module: teardrop2d() +// +// Description: +// Makes a 2D teardrop shape. Useful for extruding into 3D printable holes. +// +// Usage: +// teardrop2d(r|d, [ang], [cap_h]); +// +// Arguments: +// r = radius of circular part of teardrop. (Default: 1) +// d = diameter of spherical portion of bottom. (Use instead of r) +// ang = angle of hat walls from the Y axis. (Default: 45 degrees) +// cap_h = if given, height above center where the shape will be truncated. +// +// Example(2D): Typical Shape +// teardrop2d(r=30, ang=30); +// Example(2D): Crop Cap +// teardrop2d(r=30, ang=30, cap_h=40); +// Example(2D): Close Crop +// teardrop2d(r=30, ang=30, cap_h=20); +module teardrop2d(r=1, d=undef, ang=45, cap_h=undef) +{ + eps = 0.01; + r = get_radius(r=r, d=d, dflt=1); + cord = 2 * r * cos(ang); + cord_h = r * sin(ang); + tip_y = (cord/2)/tan(ang); + cap_h = min((is_def(cap_h)? cap_h : tip_y+cord_h), tip_y+cord_h); + cap_w = cord * (1 - (cap_h - cord_h)/tip_y); + difference() { hull() { - translate(pt1) cylinder(h=h, r1=r1, r2=r2, center=true); - translate(pt2) cylinder(h=h, r1=r1, r2=r2, center=true); + zrot(90) circle(r=r); + back(cap_h-eps/2) square([max(eps,cap_w), eps], center=true); + } + back(r+cap_h) square(2*r, center=true); + } +} + + +// Module: teardrop() +// +// Description: +// Makes a teardrop shape in the XZ plane. Useful for 3D printable holes. +// +// Usage: +// teardrop(r|d, l|h, [ang], [cap_h], [orient], [align]) +// +// Arguments: +// r = Radius of circular part of teardrop. (Default: 1) +// d = Diameter of circular portion of bottom. (Use instead of r) +// l = Thickness of teardrop. (Default: 1) +// ang = Angle of hat walls from the Z axis. (Default: 45 degrees) +// cap_h = If given, height above center where the shape will be truncated. +// orient = Orientation of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. +// align = Alignment of the shape. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// +// Example: Typical Shape +// teardrop(r=30, h=10, ang=30); +// Example: Crop Cap +// teardrop(r=30, h=10, ang=30, cap_h=40); +// Example: Close Crop +// teardrop(r=30, h=10, ang=30, cap_h=20); +module teardrop(r=undef, d=undef, l=undef, h=undef, ang=45, cap_h=undef, orient=ORIENT_Y, align=V_CENTER) +{ + r = get_radius(r=r, d=d, dflt=1); + l = first_defined([l, h, 1]); + orient_and_align([r*2,r*2,l], orient, align) { + linear_extrude(height=l, center=true, slices=2) { + teardrop2d(r=r, ang=ang, cap_h=cap_h); } } } -// Makes an arced slot, appropriate for bolts to slide along. -// cp = centerpoint of slot arc. (default: [0, 0, 0]) -// h = height of slot arc shape. (default: 1.0) -// r = radius of slot arc. (default: 0.5) -// d = diameter of slot arc. (default: 1.0) -// sr = radius of slot channel. (default: 0.5) -// sd = diameter of slot channel. (default: 0.5) -// sr1 = bottom radius of slot channel cone. (use instead of sr) -// sr2 = top radius of slot channel cone. (use instead of sr) -// sd1 = bottom diameter of slot channel cone. (use instead of sd) -// sd2 = top diameter of slot channel cone. (use instead of sd) -// sa = starting angle. (Default: 0.0) -// ea = ending angle. (Default: 90.0) -// Examples: -// arced_slot(d=100, h=15, sd=10, sa=60, ea=280); -// arced_slot(r=100, h=10, sd1=30, sd2=10, sa=45, ea=180, $fa=5, $fs=2); -module arced_slot( - cp=[0,0,0], - r=undef, d=1.0, h=1.0, - sr=undef, sr1=undef, sr2=undef, - sd=1.0, sd1=undef, sd2=undef, - sa=0, ea=90 -) { - r = (r != undef)? r : (d/2); - sr = (sr != undef)? sr : (sd/2); - sr1 = (sr1 != undef)? sr1 : ((sd1 != undef)? (sd1/2) : sr); - sr2 = (sr2 != undef)? sr2 : ((sd2 != undef)? (sd2/2) : sr); - da = ea - sa; - steps = segs(r+max(sr1,sr2)); - zrot(sa) { - right(r) cylinder(h=h, r1=sr1, r2=sr2, center=true); - difference() { - linear_extrude(height=h, scale=(r+sr2)/(r+sr1), center=true, convexity=4) { - polygon( - points=concat( - [[0,0]], - [ - for (i = [0:steps]) [ - (r+sr1)*cos(da*i/steps), - (r+sr1)*sin(da*i/steps) - ] - ] - ) - ); +// Module: onion() +// +// Description: +// Creates a sphere with a conical hat, to make a 3D teardrop. +// +// Usage: +// onion(r|d, [maxang], [cap_h], [orient], [align]); +// +// Arguments: +// r = radius of spherical portion of the bottom. (Default: 1) +// d = diameter of spherical portion of bottom. +// cap_h = height above sphere center to truncate teardrop shape. +// maxang = angle of cone on top from vertical. +// orient = Orientation of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. +// align = Alignment of the shape. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// +// Example: Typical Shape +// onion(r=30, maxang=30); +// Example: Crop Cap +// onion(r=30, maxang=30, cap_h=40); +// Example: Close Crop +// onion(r=30, maxang=30, cap_h=20); +module onion(cap_h=undef, r=undef, d=undef, maxang=45, h=undef, orient=ORIENT_Z, align=V_CENTER) +{ + r = get_radius(r=r, d=d, dflt=1); + h = first_defined([cap_h, h]); + maxd = 3*r/tan(maxang); + orient_and_align([r*2,r*2,r*2], orient, align) { + rotate_extrude(convexity=2) { + difference() { + teardrop2d(r=r, ang=maxang, cap_h=h); + left(r) square(size=[2*r,maxd], center=true); } - cylinder(h=h+0.01, r1=(r-sr1), r2=(r-sr2), center=true); } - zrot(da) right(r) cylinder(h=h, r1=sr1, r2=sr2, center=true); } } -// Makes a rectangular strut with the top side narrowing in a triangle. -// The shape created may be likened to an extruded home plate from baseball. -// This is useful for constructing parts that minimize the need to support -// overhangs. +// Module: narrowing_strut() +// +// Description: +// Makes a rectangular strut with the top side narrowing in a triangle. +// The shape created may be likened to an extruded home plate from baseball. +// This is useful for constructing parts that minimize the need to support +// overhangs. +// +// Usage: +// narrowing_strut(w, l, wall, [ang], [orient], [align]); +// +// Arguments: // w = Width (thickness) of the strut. // l = Length of the strut. // wall = height of rectangular portion of the strut. // ang = angle that the trianglar side will converge at. +// orient = Orientation of the length axis of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. +// align = Alignment of the shape. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// // Example: // narrowing_strut(w=10, l=100, wall=5, ang=30); -module narrowing_strut(w=10, l=100, wall=5, ang=30) +module narrowing_strut(w=10, l=100, wall=5, ang=30, orient=ORIENT_Y, align=V_UP) { - tipy = wall + (w/2)*sin(90-ang)/sin(ang); - xrot(90) linear_extrude(height=l, center=true, steps=2) { - polygon( - points=[ - [-w/2, 0], - [-w/2, wall], - [0, tipy], - [w/2, wall], - [w/2, 0] - ] - ); + h = wall + w/2/tan(ang); + orient_and_align([w, h, l], orient, align, orig_orient=ORIENT_Z) { + fwd(h/2) { + linear_extrude(height=l, center=true, slices=2) { + back(wall/2) square([w, wall], center=true); + back(wall-0.001) { + yscale(1/tan(ang)) { + difference() { + zrot(45) square(w/sqrt(2), center=true); + fwd(w/2) square(w, center=true); + } + } + } + } + } } } -// Makes a rectangular wall which thins to a smaller width in the center, -// with angled supports to prevent critical overhangs. +// Module: thinning_wall() +// +// Description: +// Makes a rectangular wall which thins to a smaller width in the center, +// with angled supports to prevent critical overhangs. +// +// Usage: +// thinning_wall(h, l, thick, [ang], [strut], [wall], [orient], [align]); +// +// Arguments: // h = height of wall. -// l = length of wall. +// l = length of wall. If given as a vector of two numbers, specifies bottom and top lengths, respectively. // thick = thickness of wall. // ang = maximum overhang angle of diagonal brace. // strut = the width of the diagonal brace. // wall = the thickness of the thinned portion of the wall. -// Example: -// thinning_wall(h=50, l=100, thick=4, ang=30, strut=5, wall=2); -module thinning_wall(h=50, l=100, thick=5, ang=30, strut=5, wall=2) +// orient = Orientation of the length axis of the wall. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_X`. +// align = Alignment of the shape. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// +// Example: Typical Shape +// thinning_wall(h=50, l=80, thick=4); +// Example: Trapezoidal +// thinning_wall(h=50, l=[80,50], thick=4); +module thinning_wall(h=50, l=100, thick=5, ang=30, strut=5, wall=2, orient=ORIENT_X, align=V_CENTER) { l1 = (l[0] == undef)? l : l[0]; l2 = (l[1] == undef)? l : l[1]; @@ -1124,7 +1528,7 @@ module thinning_wall(h=50, l=100, thick=5, ang=30, strut=5, wall=2) y1 = thick/2; y2 = y1 - min(z2-z3, x2-x3) * sin(ang); - zrot(90) { + orient_and_align([l1, thick, h], orient, align, orig_orient=ORIENT_X) { polyhedron( points=[ [-x4, -y1, -z1], @@ -1218,14 +1622,34 @@ module thinning_wall(h=50, l=100, thick=5, ang=30, strut=5, wall=2) ); } } -//!thinning_wall(h=50, l=[100, 80], thick=4, ang=30, strut=5, wall=2); -module braced_thinning_wall(h=50, l=100, thick=5, ang=30, strut=5, wall=2) +// Module: braced_thinning_wall() +// +// Description: +// Makes a rectangular wall with cross-bracing, which thins to a smaller width in the center, +// with angled supports to prevent critical overhangs. +// +// Usage: +// braced_thinning_wall(h, l, thick, [ang], [strut], [wall], [orient], [align]); +// +// Arguments: +// h = height of wall. +// l = length of wall. +// thick = thickness of wall. +// ang = maximum overhang angle of diagonal brace. +// strut = the width of the diagonal brace. +// wall = the thickness of the thinned portion of the wall. +// orient = Orientation of the length axis of the wall. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. +// align = Alignment of the shape. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// +// Example: Typical Shape +// braced_thinning_wall(h=50, l=100, thick=5); +module braced_thinning_wall(h=50, l=100, thick=5, ang=30, strut=5, wall=2, orient=ORIENT_Y, align=V_CENTER) { dang = atan((h-2*strut)/(l-2*strut)); dlen = (h-2*strut)/sin(dang); - union() { + orient_and_align([thick, l, h], orient, align, orig_orient=ORIENT_Y) { xrot_copies([0, 180]) { down(h/2) narrowing_strut(w=thick, l=l, wall=strut, ang=ang); fwd(l/2) xrot(-90) narrowing_strut(w=thick, l=h-0.1, wall=strut, ang=ang); @@ -1246,8 +1670,17 @@ module braced_thinning_wall(h=50, l=100, thick=5, ang=30, strut=5, wall=2) } -// Makes a triangular wall with thick edges, which thins to a smaller width in -// the center, with angled supports to prevent critical overhangs. + +// Module: thinning_triangle() +// +// Description: +// Makes a triangular wall with thick edges, which thins to a smaller width in +// the center, with angled supports to prevent critical overhangs. +// +// Usage: +// thinning_triangle(h, l, thick, [ang], [strut], [wall], [diagonly], [orient], [align|center]); +// +// Arguments: // h = height of wall. // l = length of wall. // thick = thickness of wall. @@ -1255,14 +1688,21 @@ module braced_thinning_wall(h=50, l=100, thick=5, ang=30, strut=5, wall=2) // strut = the width of the diagonal brace. // wall = the thickness of the thinned portion of the wall. // diagonly = boolean, which denotes only the diagonal side (hypotenuse) should be thick. -// center = if true (default) centers triangle at the origin. -// Example: -// thinning_triangle(h=50, l=100, thick=4, ang=30, strut=5, wall=2, diagonly=true); -module thinning_triangle(h=50, l=100, thick=5, ang=30, strut=5, wall=3, diagonly=false, center=true) +// orient = Orientation of the length axis of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. +// align = Alignment of the shape. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// center = If true, centers shape. If false, overrides `align` with `V_UP+V_BACK`. +// +// Example: Centered +// thinning_triangle(h=50, l=80, thick=4, ang=30, strut=5, wall=2, center=true); +// Example: All Braces +// thinning_triangle(h=50, l=80, thick=4, ang=30, strut=5, wall=2, center=false); +// Example: Diagonal Brace Only +// thinning_triangle(h=50, l=80, thick=4, ang=30, strut=5, wall=2, diagonly=true, center=false); +module thinning_triangle(h=50, l=100, thick=5, ang=30, strut=5, wall=3, diagonly=false, center=undef, orient=ORIENT_Y, align=V_CENTER) { dang = atan(h/l); dlen = h/sin(dang); - translate(center? [0, 0, 0] : [0, l/2, h/2]) { + orient_and_align([thick, l, h], orient, align, center=center, noncentered=V_UP+V_BACK, orig_orient=ORIENT_Y) { difference() { union() { if (!diagonly) { @@ -1289,25 +1729,103 @@ module thinning_triangle(h=50, l=100, thick=5, ang=30, strut=5, wall=3, diagonly } -// Makes a triangular wall which thins to a smaller width in the center, -// with angled supports to prevent critical overhangs. Basically an alias -// of thinning_triangle(), with diagonly=true. +// Module: thinning_brace() +// Status: DEPRECATED, use `thinning_triangle(..., diagonly=true)` instead. +// +// Description: +// Makes a triangular wall which thins to a smaller width in the center, +// with angled supports to prevent critical overhangs. Basically an alias +// of thinning_triangle(), with diagonly=true. +// +// Usage: +// thinning_brace(h, l, thick, [ang], [strut], [wall], [center]) +// +// Arguments: // h = height of wall. // l = length of wall. // thick = thickness of wall. // ang = maximum overhang angle of diagonal brace. // strut = the width of the diagonal brace. // wall = the thickness of the thinned portion of the wall. -// Example: -// thinning_brace(h=50, l=100, thick=4, ang=30, strut=5, wall=2); module thinning_brace(h=50, l=100, thick=5, ang=30, strut=5, wall=3, center=true) { + deprecate("thinning_brace()", "thinning_triangle(..., diagonly=true)"); thinning_triangle(h=h, l=l, thick=thick, ang=ang, strut=strut, wall=wall, diagonly=true, center=center); } -// Makes an open rectangular strut with X-shaped cross-bracing, designed to reduce the -// need for support material in 3D printing. +// Module: sparse_strut() +// +// Description: +// Makes an open rectangular strut with X-shaped cross-bracing, designed to reduce +// the need for support material in 3D printing. +// +// Usage: +// sparse_strut(h, l, thick, [strut], [maxang], [max_bridge], [orient], [align]) +// +// Arguments: +// h = height of strut wall. +// l = length of strut wall. +// thick = thickness of strut wall. +// maxang = maximum overhang angle of cross-braces. +// max_bridge = maximum bridging distance between cross-braces. +// strut = the width of the cross-braces. +// orient = Orientation of the length axis of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. +// align = Alignment of the shape. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// +// Example: Typical Shape +// sparse_strut(h=40, l=100, thick=3); +// Example: Thinner Strut +// sparse_strut(h=40, l=100, thick=3, strut=2); +// Example: Larger maxang +// sparse_strut(h=40, l=100, thick=3, strut=2, maxang=45); +// Example: Longer max_bridge +// sparse_strut(h=40, l=100, thick=3, strut=2, maxang=45, max_bridge=30); +module sparse_strut(h=50, l=100, thick=4, maxang=30, strut=5, max_bridge=20, orient=ORIENT_Y, align=V_CENTER) +{ + zoff = h/2 - strut/2; + yoff = l/2 - strut/2; + + maxhyp = 1.5 * (max_bridge+strut)/2 / sin(maxang); + maxz = 2 * maxhyp * cos(maxang); + + zreps = ceil(2*zoff/maxz); + zstep = 2*zoff / zreps; + + hyp = zstep/2 / cos(maxang); + maxy = min(2 * hyp * sin(maxang), max_bridge+strut); + + yreps = ceil(2*yoff/maxy); + ystep = 2*yoff / yreps; + + ang = atan(ystep/zstep); + len = zstep / cos(ang); + + orient_and_align([thick, l, h], orient, align, orig_orient=ORIENT_Y) { + zspread(zoff*2) + cube(size=[thick, l, strut], center=true); + yspread(yoff*2) + cube(size=[thick, strut, h], center=true); + yspread(ystep, n=yreps) { + zspread(zstep, n=zreps) { + xrot( ang) cube(size=[thick, strut, len], center=true); + xrot(-ang) cube(size=[thick, strut, len], center=true); + } + } + } +} + + +// Module: sparse_strut3d() +// +// Usage: +// sparse_strut3d(h, w, l, [thick], [maxang], [max_bridge], [strut], [orient], [align]); +// +// Description: +// Makes an open rectangular strut with X-shaped cross-bracing, designed to reduce the +// need for support material in 3D printing. +// +// Arguments: // h = Z size of strut. // w = X size of strut. // l = Y size of strut. @@ -1315,11 +1833,18 @@ module thinning_brace(h=50, l=100, thick=5, ang=30, strut=5, wall=3, center=true // maxang = maximum overhang angle of cross-braces. // max_bridge = maximum bridging distance between cross-braces. // strut = the width of the cross-braces. -// Example: -// sparse_strut3d(h=100, w=33, l=33, thick=3, strut=3, maxang=30, max_bridge=20); -// sparse_strut3d(h=40, w=40, l=120, thick=3, maxang=30, strut=3, max_bridge=20); -// sparse_strut3d(h=30, w=30, l=180, thick=2.5, strut=2.5, maxang=30, max_bridge=20); -module sparse_strut3d(h=50, l=100, w=50, thick=3, maxang=40, strut=3, max_bridge = 20) +// orient = Orientation of the length axis of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. +// align = Alignment of the shape. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// +// Example: Typical Shape +// sparse_strut3d(h=30, w=30, l=100); +// Example: Thinner strut +// sparse_strut3d(h=30, w=30, l=100, strut=2); +// Example: Larger maxang +// sparse_strut3d(h=30, w=30, l=100, strut=2, maxang=50); +// Example: Smaller max_bridge +// sparse_strut3d(h=30, w=30, l=100, strut=2, maxang=50, max_bridge=20); +module sparse_strut3d(h=50, l=100, w=50, thick=3, maxang=40, strut=3, max_bridge=30, orient=ORIENT_Y, align=V_CENTER) { xoff = w - thick; @@ -1341,32 +1866,36 @@ module sparse_strut3d(h=50, l=100, w=50, thick=3, maxang=40, strut=3, max_bridge supp_reps = floor(cross_len/2/(zstep*sin(supp_ang))); supp_step = cross_len/2/supp_reps; - union() { - ybridge = (l - (yreps+1) * strut) / yreps; - xspread(xoff) sparse_strut(h=h, l=l, thick=thick, maxang=maxang, strut=strut, max_bridge=ybridge/ceil(ybridge/max_bridge)); - yspread(yoff) zrot(90) sparse_strut(h=h, l=w, thick=thick, maxang=maxang, strut=strut, max_bridge=max_bridge); - for(zs = [0:zreps-1]) { - for(xs = [0:xreps-1]) { - for(ys = [0:yreps-1]) { - translate([(xs+0.5)*xstep-xoff/2, (ys+0.5)*ystep-yoff/2, (zs+0.5)*zstep-zoff/2]) { - zflip_copy(offset=-(zstep-strut)/2) { - xflip_copy() { - zrot(cross_ang) { - down(strut/2) { - cube([strut, cross_len, strut], center=true); - } - if (zreps>1) { - back(cross_len/2) { - zrot(-cross_ang) { - down(strut) upcube([strut, strut, zstep+strut], center=true); + orient_and_align([w, l, h], orient, align, orig_orient=ORIENT_Y) { + intersection() { + union() { + ybridge = (l - (yreps+1) * strut) / yreps; + xspread(xoff) sparse_strut(h=h, l=l, thick=thick, maxang=maxang, strut=strut, max_bridge=ybridge/ceil(ybridge/max_bridge)); + yspread(yoff) zrot(90) sparse_strut(h=h, l=w, thick=thick, maxang=maxang, strut=strut, max_bridge=max_bridge); + for(zs = [0:zreps-1]) { + for(xs = [0:xreps-1]) { + for(ys = [0:yreps-1]) { + translate([(xs+0.5)*xstep-xoff/2, (ys+0.5)*ystep-yoff/2, (zs+0.5)*zstep-zoff/2]) { + zflip_copy(offset=-(zstep-strut)/2) { + xflip_copy() { + zrot(cross_ang) { + down(strut/2) { + cube([strut, cross_len, strut], center=true); } - } - } - for (soff = [0 : supp_reps-1] ) { - yflip_copy() { - back(soff*supp_step) { - skew_xy(ya=supp_ang) { - upcube([strut, strut, zstep]); + if (zreps>1) { + back(cross_len/2) { + zrot(-cross_ang) { + down(strut) upcube([strut, strut, zstep+strut]); + } + } + } + for (soff = [0 : supp_reps-1] ) { + yflip_copy() { + back(soff*supp_step) { + skew_xy(ya=supp_ang) { + upcube([strut, strut, zstep]); + } + } } } } @@ -1377,83 +1906,267 @@ module sparse_strut3d(h=50, l=100, w=50, thick=3, maxang=40, strut=3, max_bridge } } } + cube([w,l,h], center=true); } } } -// Makes an open rectangular strut with X-shaped cross-bracing, designed to reduce -// the need for support material in 3D printing. -// h = height of strut wall. -// l = length of strut wall. -// thick = thickness of strut wall. -// maxang = maximum overhang angle of cross-braces. -// max_bridge = maximum bridging distance between cross-braces. -// strut = the width of the cross-braces. -// Example: -// sparse_strut(h=40, l=120, thick=4, maxang=30, strut=5, max_bridge=20); -module sparse_strut(h=50, l=100, thick=4, maxang=30, strut=5, max_bridge = 20) -{ - - zoff = h/2 - strut/2; - yoff = l/2 - strut/2; - - maxhyp = 1.5 * (max_bridge+strut)/2 / sin(maxang); - maxz = 2 * maxhyp * cos(maxang); - - zreps = ceil(2*zoff/maxz); - zstep = 2*zoff / zreps; - - hyp = zstep/2 / cos(maxang); - maxy = min(2 * hyp * sin(maxang), max_bridge+strut); - - yreps = ceil(2*yoff/maxy); - ystep = 2*yoff / yreps; - - ang = atan(ystep/zstep); - len = zstep / cos(ang); - - union() { - zspread(zoff*2) - cube(size=[thick, l, strut], center=true); - yspread(yoff*2) - cube(size=[thick, strut, h], center=true); - grid_of(ya=[-yoff+ystep/2:ystep:yoff], za=[-zoff+zstep/2:zstep:zoff]) { - xrot( ang) cube(size=[thick, strut, len], center=true); - xrot(-ang) cube(size=[thick, strut, len], center=true); - } - } -} - - -// Makes a corrugated wall which relieves contraction stress while still -// providing support strength. Designed with 3D printing in mind. +// Module: corrugated_wall() +// +// Description: +// Makes a corrugated wall which relieves contraction stress while still +// providing support strength. Designed with 3D printing in mind. +// +// Usage: +// corrugated_wall(h, l, thick, [strut], [wall], [orient], [align]); +// +// Arguments: // h = height of strut wall. // l = length of strut wall. // thick = thickness of strut wall. // strut = the width of the cross-braces. // wall = thickness of corrugations. -// Example: -// corrugated_wall(h=50, l=100, thick=4, strut=5, wall=2, $fn=12); -module corrugated_wall(h=50, l=100, thick=5, strut=5, wall=2) +// orient = Orientation of the length axis of the shape. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. +// align = Alignment of the shape. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// +// Example: Typical Shape +// corrugated_wall(h=50, l=100); +// Example: Wider Strut +// corrugated_wall(h=50, l=100, strut=8); +// Example: Thicker Wall +// corrugated_wall(h=50, l=100, strut=8, wall=3); +module corrugated_wall(h=50, l=100, thick=5, strut=5, wall=2, orient=ORIENT_Y, align=V_CENTER) { amplitude = (thick - wall) / 2; period = min(15, thick * 2); steps = quantup(segs(thick/2),4); step = period/steps; il = l - 2*strut + 2*step; - linear_extrude(height=h-2*strut+0.1, steps=2, convexity=ceil(2*il/period), center=true) { - polygon( - points=concat( - [for (y=[-il/2:step:il/2]) [amplitude*sin(y/period*360)-wall/2, y] ], - [for (y=[il/2:-step:-il/2]) [amplitude*sin(y/period*360)+wall/2, y] ] - ) - ); - } + orient_and_align([thick, l, h], orient, align, orig_orient=ORIENT_Y) { + linear_extrude(height=h-2*strut+0.1, slices=2, convexity=ceil(2*il/period), center=true) { + polygon( + points=concat( + [for (y=[-il/2:step:il/2]) [amplitude*sin(y/period*360)-wall/2, y] ], + [for (y=[il/2:-step:-il/2]) [amplitude*sin(y/period*360)+wall/2, y] ] + ) + ); + } - difference() { - cube([thick, l, h], center=true); - cube([thick+0.5, l-2*strut, h-2*strut], center=true); + difference() { + cube([thick, l, h], center=true); + cube([thick+0.5, l-2*strut, h-2*strut], center=true); + } + } +} + + +// Section: Miscellaneous + + +// Module: nil() +// +// Description: +// Useful when you MUST pass a child to a module, but you want it to be nothing. +module nil() union() {} + + +// Module: noop() +// +// Description: +// Passes through the children passed to it, with no action at all. +// Useful while debugging when you want to replace a command. +module noop() children(); + + +// Module: pie_slice() +// +// Description: +// Creates a pie slice shape. +// +// Usage: +// pie_slice(ang, l|h, r|d, [orient], [align|center]); +// pie_slice(ang, l|h, r1|d1, r2|d2, [orient], [align|center]); +// +// Arguments: +// ang = pie slice angle in degrees. +// h = height of pie slice. +// r = radius of pie slice. +// r1 = bottom radius of pie slice. +// r2 = top radius of pie slice. +// d = diameter of pie slice. +// d1 = bottom diameter of pie slice. +// d2 = top diameter of pie slice. +// orient = Orientation of the pie slice. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the pie slice. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// center = If given, overrides `align`. A true value sets `align=V_CENTER`, false sets `align=ALIGN_POS`. +// +// Example: Cylindrical Pie Slice +// pie_slice(ang=45, l=20, r=30); +// Example: Conical Pie Slice +// pie_slice(ang=60, l=20, d1=50, d2=70); +module pie_slice( + ang=30, l=undef, + r=10, r1=undef, r2=undef, + d=undef, d1=undef, d2=undef, + orient=ORIENT_Z, align=ALIGN_POS, + center=undef, h=undef +) { + l = first_defined([l, h, 1]); + r1 = get_radius(r1, r, d1, d, 10); + r2 = get_radius(r2, r, d2, d, 10); + maxd = max(r1,r2)+0.1; + orient_and_align([2*r1, 2*r1, l], orient, align, center=center) { + difference() { + cylinder(r1=r1, r2=r2, h=l, center=true); + if (ang<180) rotate(ang) back(maxd/2) cube([2*maxd, maxd, l+0.1], center=true); + difference() { + fwd(maxd/2) cube([2*maxd, maxd, l+0.2], center=true); + if (ang>180) rotate(ang-180) back(maxd/2) cube([2*maxd, maxd, l+0.1], center=true); + } + } + } +} + + +// Module: interior_fillet() +// +// Description: +// Creates a shape that can be unioned into a concave joint between two faces, to fillet them. +// Center this part along the concave edge to be chamferred and union it in. +// +// Usage: +// interior_fillet(l, r, [ang], [overlap], [orient], [align]); +// +// Arguments: +// l = length of edge to fillet. +// r = radius of fillet. +// ang = angle between faces to fillet. +// overlap = overlap size for unioning with faces. +// orient = Orientation of the fillet. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_X`. +// align = Alignment of the fillet. Use the `V_` or `ALIGN_` constants from `constants.scad`. Default: `V_CENTER`. +// +// Example: +// union() { +// translate([0,2,-4]) upcube([20, 4, 24]); +// translate([0,-10,-4]) upcube([20, 20, 4]); +// color("green") interior_fillet(l=20, r=10, orient=ORIENT_XNEG); +// } +// +// Example: +// interior_fillet(l=40, r=10, orient=ORIENT_Y_90); +module interior_fillet(l=1.0, r=1.0, ang=90, overlap=0.01, orient=ORIENT_X, align=V_CENTER) { + dy = r/tan(ang/2); + orient_and_align([l,r,r], orient, align, orig_orient=ORIENT_X) { + difference() { + translate([0,-overlap/tan(ang/2),-overlap]) { + if (ang == 90) { + translate([0,r/2,r/2]) cube([l,r,r], center=true); + } else { + rotate([90,0,90]) pie_slice(ang=ang, r=dy+overlap, h=l, center=true); + } + } + translate([0,dy,r]) xcyl(l=l+0.1, r=r); + } + } +} + + + +// Module: slot() +// +// Description: +// Makes a linear slot with rounded ends, appropriate for bolts to slide along. +// +// Usage: +// slot(h, l, r|d, [orient], [align|center]); +// slot(h, p1, p2, r|d, [orient], [align|center]); +// slot(h, l, r1|d1, r2|d2, [orient], [align|center]); +// slot(h, p1, p2, r1|d1, r2|d2, [orient], [align|center]); +// +// Arguments: +// p1 = center of starting circle of slot. +// p2 = center of ending circle of slot. +// l = length of slot along the X axis. +// h = height of slot shape. (default: 10) +// r = radius of slot circle. (default: 5) +// r1 = bottom radius of slot cone. +// r2 = top radius of slot cone. +// d = diameter of slot circle. +// d1 = bottom diameter of slot cone. +// d2 = top diameter of slot cone. +// +// Example: Between Two Points +// slot([0,0,0], [50,50,0], r1=5, r2=10, h=5); +// Example: By Length +// slot(l=50, r1=5, r2=10, h=5); +module slot( + p1=undef, p2=undef, h=10, l=undef, + r=undef, r1=undef, r2=undef, + d=undef, d1=undef, d2=undef +) { + r1 = get_radius(r1=r1, r=r, d1=d1, d=d, dflt=5); + r2 = get_radius(r1=r2, r=r, d1=d2, d=d, dflt=5); + sides = quantup(segs(max(r1, r2)), 4); + hull() spread(p1=p1, p2=p2, l=l, n=2) cyl(l=h, r1=r1, r2=r2, center=true, $fn=sides); +} + + +// Module: arced_slot() +// +// Description: +// Makes an arced slot, appropriate for bolts to slide along. +// +// Usage: +// arced_slot(h, r|d, sr|sd, [sa], [ea], [orient], [align|center], [$fn2]); +// arced_slot(h, r|d, sr1|sd1, sr2|sd2, [sa], [ea], [orient], [align|center], [$fn2]); +// +// Arguments: +// cp = centerpoint of slot arc. (default: [0, 0, 0]) +// h = height of slot arc shape. (default: 1.0) +// r = radius of slot arc. (default: 0.5) +// d = diameter of slot arc. (default: 1.0) +// sr = radius of slot channel. (default: 0.5) +// sd = diameter of slot channel. (default: 0.5) +// sr1 = bottom radius of slot channel cone. (use instead of sr) +// sr2 = top radius of slot channel cone. (use instead of sr) +// sd1 = bottom diameter of slot channel cone. (use instead of sd) +// sd2 = top diameter of slot channel cone. (use instead of sd) +// sa = starting angle. (Default: 0.0) +// ea = ending angle. (Default: 90.0) +// orient = Orientation of the arced slot. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the arced slot. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// center = If true, centers vertically. If false, drops flush with XY plane. Overrides `align`. +// $fn2 = The $fn value to use on the small round endcaps. The major arcs are still based on $fn. Default: $fn +// +// Example: Typical Arced Slot +// arced_slot(d=60, h=5, sd=10, sa=60, ea=280); +// Example: Conical Arced Slot +// arced_slot(r=60, h=5, sd1=10, sd2=15, sa=45, ea=180); +module arced_slot( + r=undef, d=undef, h=1.0, + sr=undef, sr1=undef, sr2=undef, + sd=undef, sd1=undef, sd2=undef, + sa=0, ea=90, cp=[0,0,0], + orient=ORIENT_Z, align=V_CENTER, + $fn2 = undef +) { + r = get_radius(r=r, d=d, dflt=2); + sr1 = get_radius(sr1, sr, sd1, sd, 2); + sr2 = get_radius(sr2, sr, sd2, sd, 2); + fn_minor = first_defined([$fn2, $fn]); + da = ea - sa; + orient_and_align([r+sr1, r+sr1, h], orient, align) { + translate(cp) { + zrot(sa) { + difference() { + pie_slice(ang=da, l=h, r1=r+sr1, r2=r+sr2, orient=ORIENT_Z, align=V_CENTER); + cylinder(h=h+0.1, r1=r-sr1, r2=r-sr2, center=true); + } + right(r) cylinder(h=h, r1=sr1, r2=sr2, center=true, $fn=fn_minor); + zrot(da) right(r) cylinder(h=h, r1=sr1, r2=sr2, center=true, $fn=fn_minor); + } + } } } diff --git a/sliders.scad b/sliders.scad index fb78248..54b0e6e 100644 --- a/sliders.scad +++ b/sliders.scad @@ -1,5 +1,11 @@ ////////////////////////////////////////////////////////////////////// -// Sliders and Rails. +// LibFile: sliders.scad +// Simple V-groove based sliders and rails. +// To use, add these lines to the beginning of your file: +// ``` +// include +// use +// ``` ////////////////////////////////////////////////////////////////////// /* @@ -31,71 +37,51 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ use +use +include +include -// Creates a slider to match a rail. -// l = length (long axis) of slider. -// w = width of slider. -// h = height of slider. -// base = height of slider base. -// wall = width of wall behind each side of the slider. -// ang = overhang angle for slider, to facilitate supportless printig. -// slop = printer-specific slop value to make parts fit exactly. +// Section: Modules + + +// Module: slider() +// Description: +// Creates a slider to match a V-groove rail. +// Usage: +// slider(l, w, h, [base], [wall], [ang], [slop], [orient], [align]) +// Arguments: +// l = Length (long axis) of slider. +// w = Width of slider. +// h = Height of slider. +// base = Height of slider base. +// wall = Width of wall behind each side of the slider. +// ang = Overhang angle for slider, to facilitate supportless printig. +// slop = Printer-specific slop value to make parts fit exactly. +// orient = Orientation of the slider. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. +// align = Alignment of the slider. Use the `V_` constants from `constants.scad`. Default: `V_UP`. // Example: -// slider(l=30, base=10, wall=4, slop=0.2); -module slider(l=30, w=10, h=10, base=10, wall=5, ang=30, slop=printer_slop) +// slider(l=30, base=10, wall=4, slop=0.2, orient=ORIENT_Y); +module slider(l=30, w=10, h=10, base=10, wall=5, ang=30, slop=PRINTER_SLOP, orient=ORIENT_Y, align=V_UP) { full_width = w + 2*wall; full_height = h + base; - difference() { - // Overall slider shell - up(full_height/2) cube([w+2*wall, l, full_height], center=true); + orient_and_align([full_width, l, h+2*base], orient, align, orig_orient=ORIENT_Y) { + down(base+h/2) { + // Base + cuboid([full_width, l, base-slop], chamfer=2, edges=EDGE_TOP_FR+EDGE_TOP_BK+EDGES_Z_ALL, align=V_UP); - up(base-slop) { - // Clear slider gap - up((h+5)/2) { - cube([w+slop, l+1, h+5], center=true); + // Wall + xflip_copy(offset=w/2+slop) { + cuboid([wall, l, full_height], chamfer=2, edges=EDGE_TOP_RT+EDGE_FR_RT+EDGE_BK_RT, align=V_UP+V_RIGHT); } - // Horiz edge bevel - yspread(l) { - scale([1, 1, tan(30)]) { - xrot(45) cube([w+slop, 2*sqrt(2), 2*sqrt(2)], center=true); - } - } - } - - // Back top bevel - up(full_height) { - xspread(full_width) { - yrot(45) { - cube([wall/2*sqrt(2), l+1, wall/2*sqrt(2)], center=true); - } - } - } - } - up(base) { - up(h/2) { - xflip_copy() { - left((w+slop)/2) { - difference() { - // Rails - right_half() { - scale([tan(ang), 1, 1]) { - yrot(45) cube([h*sin(45), l, h*sin(45)], center=true); - } - } - - // Rail bevels - yflip_copy() { - right(sqrt(2)*h/2) { - fwd(l/2) { - zrot(45) cube(h, center=true); - } - } - } - } + // Sliders + up(base+h/2) { + xflip_copy(offset=w/2+slop+0.02) { + bev_h = h/2*tan(ang); + prismoid([l, h], [l-w, 0], h=bev_h+0.01, orient=ORIENT_XNEG, align=V_LEFT); } } } @@ -104,15 +90,22 @@ module slider(l=30, w=10, h=10, base=10, wall=5, ang=30, slop=printer_slop) -// Creates a slider to match a rail. -// l = length (long axis) of slider. -// w = width of slider. -// h = height of slider. -// chamfer = size of chamfer at end of rail. -// ang = overhang angle for slider, to facilitate supportless printig. +// Module: rail() +// Description: +// Creates a V-groove rail. +// Usage: +// rail(l, w, h, [chamfer], [ang], [orient], [align]) +// Arguments: +// l = Length (long axis) of slider. +// w = Width of slider. +// h = Height of slider. +// chamfer = Size of chamfer at end of rail. +// ang = Overhang angle for slider, to facilitate supportless printig. +// orient = Orientation of the rail. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Y`. +// align = Alignment of the rail. Use the `V_` constants from `constants.scad`. Default: `V_UP`. // Example: // rail(l=100, w=10, h=10); -module rail(l=30, w=10, h=10, chamfer=1.0, ang=30) +module rail(l=30, w=10, h=10, chamfer=1.0, ang=30, orient=ORIENT_Y, align=V_UP) { attack_ang = 30; attack_len = 2; @@ -138,102 +131,103 @@ module rail(l=30, w=10, h=10, chamfer=1.0, ang=30) y1 = l/2; y2 = y1 - attack_len * cos(attack_ang); - polyhedron( - convexity=4, - points=[ - [-x5, -y1, z3], - [ x5, -y1, z3], - [ x7, -y1, z4], - [ x4, -y1, -z1-0.05], - [-x4, -y1, -z1-0.05], - [-x7, -y1, z4], + orient_and_align([w, l, h], orient, align, orig_orient=ORIENT_Y) { + polyhedron( + convexity=4, + points=[ + [-x5, -y1, z3], + [ x5, -y1, z3], + [ x7, -y1, z4], + [ x4, -y1, -z1-0.05], + [-x4, -y1, -z1-0.05], + [-x7, -y1, z4], - [-x3, -y2, z1], - [ x3, -y2, z1], - [ x2, -y2, z2], - [ x6, -y2, z4], - [ x1, -y2, -z1-0.05], - [-x1, -y2, -z1-0.05], - [-x6, -y2, z4], - [-x2, -y2, z2], + [-x3, -y2, z1], + [ x3, -y2, z1], + [ x2, -y2, z2], + [ x6, -y2, z4], + [ x1, -y2, -z1-0.05], + [-x1, -y2, -z1-0.05], + [-x6, -y2, z4], + [-x2, -y2, z2], - [ x5, y1, z3], - [-x5, y1, z3], - [-x7, y1, z4], - [-x4, y1, -z1-0.05], - [ x4, y1, -z1-0.05], - [ x7, y1, z4], + [ x5, y1, z3], + [-x5, y1, z3], + [-x7, y1, z4], + [-x4, y1, -z1-0.05], + [ x4, y1, -z1-0.05], + [ x7, y1, z4], - [ x3, y2, z1], - [-x3, y2, z1], - [-x2, y2, z2], - [-x6, y2, z4], - [-x1, y2, -z1-0.05], - [ x1, y2, -z1-0.05], - [ x6, y2, z4], - [ x2, y2, z2], - ], - faces=[ - [0, 1, 2], - [0, 2, 5], - [2, 3, 4], - [2, 4, 5], + [ x3, y2, z1], + [-x3, y2, z1], + [-x2, y2, z2], + [-x6, y2, z4], + [-x1, y2, -z1-0.05], + [ x1, y2, -z1-0.05], + [ x6, y2, z4], + [ x2, y2, z2], + ], + faces=[ + [0, 1, 2], + [0, 2, 5], + [2, 3, 4], + [2, 4, 5], - [0, 13, 6], - [0, 6, 7], - [0, 7, 1], - [1, 7, 8], - [1, 8, 9], - [1, 9, 2], - [2, 9, 10], - [2, 10, 3], - [3, 10, 11], - [3, 11, 4], - [4, 11, 12], - [4, 12, 5], - [5, 12, 13], - [5, 13, 0], + [0, 13, 6], + [0, 6, 7], + [0, 7, 1], + [1, 7, 8], + [1, 8, 9], + [1, 9, 2], + [2, 9, 10], + [2, 10, 3], + [3, 10, 11], + [3, 11, 4], + [4, 11, 12], + [4, 12, 5], + [5, 12, 13], + [5, 13, 0], - [14, 15, 16], - [14, 16, 19], - [16, 17, 18], - [16, 18, 19], + [14, 15, 16], + [14, 16, 19], + [16, 17, 18], + [16, 18, 19], - [14, 27, 20], - [14, 20, 21], - [14, 21, 15], - [15, 21, 22], - [15, 22, 23], - [15, 23, 16], - [16, 23, 24], - [16, 24, 17], - [17, 24, 25], - [17, 25, 18], - [18, 25, 26], - [18, 26, 19], - [19, 26, 27], - [19, 27, 14], + [14, 27, 20], + [14, 20, 21], + [14, 21, 15], + [15, 21, 22], + [15, 22, 23], + [15, 23, 16], + [16, 23, 24], + [16, 24, 17], + [17, 24, 25], + [17, 25, 18], + [18, 25, 26], + [18, 26, 19], + [19, 26, 27], + [19, 27, 14], - [6, 21, 20], - [6, 20, 7], - [7, 20, 27], - [7, 27, 8], - [8, 27, 26], - [8, 26, 9], - [9, 26, 25], - [9, 25, 10], - [10, 25, 24], - [10, 24, 11], - [11, 24, 23], - [11, 23, 12], - [12, 23, 22], - [12, 22, 13], - [13, 22, 21], - [13, 21, 6], - ] - ); + [6, 21, 20], + [6, 20, 7], + [7, 20, 27], + [7, 27, 8], + [8, 27, 26], + [8, 26, 9], + [9, 26, 25], + [9, 25, 10], + [10, 25, 24], + [10, 24, 11], + [11, 24, 23], + [11, 23, 12], + [12, 23, 22], + [12, 22, 13], + [13, 22, 21], + [13, 21, 6], + ] + ); + } } -//!rail(l=30, w=10, h=10); diff --git a/threading.scad b/threading.scad index a6f59a7..9c023cb 100644 --- a/threading.scad +++ b/threading.scad @@ -1,5 +1,11 @@ ////////////////////////////////////////////////////////////////////// -// Trapezoidal-threaded (ACME) Screw Rods and Nuts +// LibFile: threading.scad +// Triangular and Trapezoidal-Threaded Screw Rods and Nuts. +// To use, add the following lines to the beginning of your file: +// ``` +// include +// use +// ``` ////////////////////////////////////////////////////////////////////// /* @@ -30,20 +36,27 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -include -include +include +use +use +use function _trpzd_thread_pt(thread, threads, start, starts, astep, asteps, part, parts) = astep + asteps * (thread + threads * (part + parts * start)); -// Constructs a generic trapezoidal threaded screw rod. This method makes -// much smoother threads than the naive linear_extrude method. -// For metric trapezoidal threads, use thread_angle=15 and thread_depth=pitch/2. -// For ACME threads, use thread_angle=14.5 and thread_depth=pitch/2. -// For square threads, use thread_angle=0 and thread_depth=pitch/2. -// For normal screw threads, use thread_angle=30 and thread_depth=pitch*3*sqrt(3)/8. +// Section: Generic Trapezoidal Threading + +// Module: trapezoidal_threaded_rod() +// Description: +// Constructs a generic trapezoidal threaded screw rod. This method makes +// much smoother threads than the naive linear_extrude method. +// For metric trapezoidal threads, use thread_angle=15 and thread_depth=pitch/2. +// For ACME threads, use thread_angle=14.5 and thread_depth=pitch/2. +// For square threads, use thread_angle=0 and thread_depth=pitch/2. +// For normal screw threads, use thread_angle=30 and thread_depth=pitch*3*sqrt(3)/8. +// Arguments: // d = Outer diameter of threaded rod. // l = Length of threaded rod. // pitch = Length between threads. @@ -52,6 +65,9 @@ function _trpzd_thread_pt(thread, threads, start, starts, astep, asteps, part, p // left_handed = If true, create left-handed threads. Default = false // bevel = if true, bevel the thread ends. Default: true // starts = The number of lead starts. Default = 1 +// orient = Orientation of the rod. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the rod. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. +// center = If given, overrides `align`. A true value sets `align=V_CENTER`, false sets `align=ALIGN_POS`. // Examples: // trapezoidal_threaded_rod(d=10, l=100, pitch=2, thread_angle=15, $fn=32); // trapezoidal_threaded_rod(d=3/8*25.4, l=20, pitch=1/8*25.4, thread_angle=29, $fn=32); @@ -60,8 +76,8 @@ function _trpzd_thread_pt(thread, threads, start, starts, astep, asteps, part, p // trapezoidal_threaded_rod(d=16, l=40, pitch=2, thread_angle=30); // trapezoidal_threaded_rod(d=10, l=40, pitch=3, thread_angle=15, left_handed=true, starts=3, $fn=36); // trapezoidal_threaded_rod(d=25, l=100, pitch=10, thread_depth=8/3, thread_angle=50, starts=4, center=false, $fa=2, $fs=2); -// trapezoidal_threaded_rod(d=50, l=75, pitch=8, thread_angle=30, starts=3); -// trapezoidal_threaded_rod(l=25, d=10, pitch=2, thread_angle=15, starts=3, $fa=1, $fs=1); +// trapezoidal_threaded_rod(d=50, l=75, pitch=8, thread_angle=30, starts=3, bevel=true); +// trapezoidal_threaded_rod(l=25, d=10, pitch=2, thread_angle=15, starts=3, $fa=1, $fs=1, orient=ORIENT_X, align=ALIGN_POS); module trapezoidal_threaded_rod( d=10, l=100, @@ -70,8 +86,10 @@ module trapezoidal_threaded_rod( thread_depth=undef, left_handed=false, bevel=false, - center=true, - starts=1 + starts=1, + orient=ORIENT_Z, + align=V_CENTER, + center=undef ) { astep = 360 / quantup(segs(d/2), starts); asteps = ceil(360/astep); @@ -204,32 +222,26 @@ module trapezoidal_threaded_rod( ) otri ] ); - up(center? 0 : l/2) { + orient_and_align([d,d,l], orient, align, center) { difference() { polyhedron(points=poly_points, faces=poly_faces, convexity=threads*starts*2); - zspread(1.5*l) cube([d+1, d+1, l/2], center=true); - if (bevel) { - zflip_copy() { - down(l/2+0.01) { - difference() { - up(depth/2-0.01) cube([d*2, d*2, depth+0.01], center=true); - cylinder(r1=d/2-depth, r2=d/2, h=depth+0.01, center=false); - } - } - } - } + zspread(l+4*pitch*starts) cube([d+1, d+1, 4*pitch*starts], center=true); + if (bevel) cylinder_mask(d=d, l=l+0.01, chamfer=depth); } } } -// Constructs a hex nut for a threaded screw rod. This method makes -// much smoother threads than the naive linear_extrude method. -// For metric screw threads, use thread_angle=30 and leave out thread_depth argument. -// For SAE screw threads, use thread_angle=30 and leave out thread_depth argument. -// For metric trapezoidal threads, use thread_angle=15 and thread_depth=pitch/2. -// For ACME threads, use thread_angle=14.5 and thread_depth=pitch/2. -// For square threads, use thread_angle=0 and thread_depth=pitch/2. +// Module: trapezoidal_threaded_nut() +// Description: +// Constructs a hex nut for a threaded screw rod. This method makes +// much smoother threads than the naive linear_extrude method. +// For metric screw threads, use thread_angle=30 and leave out thread_depth argument. +// For SAE screw threads, use thread_angle=30 and leave out thread_depth argument. +// For metric trapezoidal threads, use thread_angle=15 and thread_depth=pitch/2. +// For ACME threads, use thread_angle=14.5 and thread_depth=pitch/2. +// For square threads, use thread_angle=0 and thread_depth=pitch/2. +// Arguments: // od = diameter of the nut. // id = diameter of threaded rod to screw onto. // h = height/thickness of nut. @@ -240,8 +252,10 @@ module trapezoidal_threaded_rod( // starts = The number of lead starts. Default = 1 // slop = printer slop calibration to allow for tight fitting of parts. default=0.2 // bevel = if true, bevel the thread ends. Default: true +// orient = Orientation of the nut. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the nut. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. // Examples: -// trapezoidal_threaded_nut(od=16, id=8, h=8, pitch=2, slop=0.2); +// trapezoidal_threaded_nut(od=16, id=8, h=8, pitch=2, slop=0.2, align=V_UP); // trapezoidal_threaded_nut(od=17.4, id=10, h=10, pitch=2, slop=0.2, left_handed=true); // trapezoidal_threaded_nut(od=17.4, id=10, h=10, pitch=2, thread_angle=15, starts=3, $fa=1, $fs=1); module trapezoidal_threaded_nut( @@ -254,26 +268,30 @@ module trapezoidal_threaded_nut( left_handed=false, starts=1, bevel=true, - slop=0.2 + slop=PRINTER_SLOP, + orient=ORIENT_Z, + align=V_CENTER ) { depth = min((thread_depth==undef? pitch/2 : thread_depth), pitch/2/tan(thread_angle)); - difference() { - cylinder(r=od/2/cos(30), h=h, center=true, $fn=6); - zspread(slop, n=slop>0?2:1) { - trapezoidal_threaded_rod( - d=id+2*slop, - l=h+1, - pitch=pitch, - thread_depth=depth, - thread_angle=thread_angle, - left_handed=left_handed, - starts=starts - ); - } - if (bevel) { - zflip_copy() { - down(h/2+0.01) { - cylinder(r1=id/2+slop, r2=id/2+slop-depth, h=depth, center=false); + orient_and_align([od/cos(30),od,h], orient, align) { + difference() { + cylinder(d=od/cos(30), h=h, center=true, $fn=6); + zspread(slop, n=slop>0?2:1) { + trapezoidal_threaded_rod( + d=id+2*slop, + l=h+1, + pitch=pitch, + thread_depth=depth, + thread_angle=thread_angle, + left_handed=left_handed, + starts=starts + ); + } + if (bevel) { + zflip_copy() { + down(h/2+0.01) { + cylinder(r1=id/2+slop, r2=id/2+slop-depth, h=depth, center=false); + } } } } @@ -281,30 +299,41 @@ module trapezoidal_threaded_nut( } +// Section: Triangular Threading -// Constructs a standard metric or UTS threaded screw rod. This method -// makes much smoother threads than the naive linear_extrude method. +// Module: threaded_rod() +// Description: +// Constructs a standard metric or UTS threaded screw rod. This method +// makes much smoother threads than the naive linear_extrude method. +// Arguments: // d = Outer diameter of threaded rod. // l = length of threaded rod. // pitch = Length between threads. // left_handed = if true, create left-handed threads. Default = false // bevel = if true, bevel the thread ends. Default: false +// orient = Orientation of the rod. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the rod. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. // Examples: // threaded_rod(d=10, l=30, pitch=1.25, left_handed=true, $fa=1, $fs=1); -module threaded_rod(d=10, l=100, pitch=2, left_handed=false, bevel=false) { +module threaded_rod(d=10, l=100, pitch=2, left_handed=false, bevel=false, orient=ORIENT_Z, align=V_CENTER) { trapezoidal_threaded_rod( d=d, l=l, pitch=pitch, thread_depth=pitch*3*sqrt(3)/8, thread_angle=30, left_handed=left_handed, - bevel=bevel + bevel=bevel, + orient=orient, + align=align ); } -// Constructs a hex nut for a metric or UTS threaded screw rod. This method -// makes much smoother threads than the naive linear_extrude method. +// Module: threaded_nut() +// Description: +// Constructs a hex nut for a metric or UTS threaded screw rod. This method +// makes much smoother threads than the naive linear_extrude method. +// Arguments: // od = diameter of the nut. // id = diameter of threaded rod to screw onto. // h = height/thickness of nut. @@ -312,32 +341,71 @@ module threaded_rod(d=10, l=100, pitch=2, left_handed=false, bevel=false) { // left_handed = if true, create left-handed threads. Default = false // bevel = if true, bevel the thread ends. Default: false // slop = printer slop calibration to allow for tight fitting of parts. default=0.2 +// orient = Orientation of the nut. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the nut. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. // Examples: -// threaded_nut(od=16, id=8, l=8, pitch=1.25, left_handed=true, slop=0.2, $fa=1, $fs=1); -module threaded_nut(od=16, id=10, h=10, pitch=2, left_handed=false, bevel=false, slop=0.2) { - trapezoidal_threaded_nut(od=od, id=id, h=h, pitch=pitch, thread_angle=30, thread_depth=pitch*3*sqrt(3)/8, left_handed=left_handed, bevel=bevel, slop=slop); +// threaded_nut(od=16, id=8, h=8, pitch=1.25, left_handed=true, slop=0.2, $fa=1, $fs=1); +module threaded_nut( + od=16, id=10, h=10, + pitch=2, left_handed=false, + bevel=false, slop=0.2, + orient=ORIENT_Z, align=V_CENTER +) { + trapezoidal_threaded_nut( + od=od, id=id, h=h, + pitch=pitch, thread_angle=30, + thread_depth=pitch*3*sqrt(3)/8, + left_handed=left_handed, + bevel=bevel, slop=slop, + orient=orient, align=align + ); } +// Section: Metric Trapezoidal Threading -// Constructs a metric trapezoidal threaded screw rod. This method makes much -// smoother threads than the naive linear_extrude method. +// Module: metric_trapezoidal_threaded_rod() +// Description: +// Constructs a metric trapezoidal threaded screw rod. This method makes much +// smoother threads than the naive linear_extrude method. +// Arguments: // d = Outer diameter of threaded rod. // l = length of threaded rod. // pitch = Length between threads. // left_handed = if true, create left-handed threads. Default = false // bevel = if true, bevel the thread ends. Default: false // starts = The number of lead starts. Default = 1 +// orient = Orientation of the rod. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the rod. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. // Examples: // metric_trapezoidal_threaded_rod(d=10, l=30, pitch=2, left_handed=true, $fa=1, $fs=1); -module metric_trapezoidal_threaded_rod(d=10, l=100, pitch=2, left_handed=false, starts=1, bevel=false) { - trapezoidal_threaded_rod(d=d, l=l, pitch=pitch, thread_angle=15, left_handed=left_handed, starts=starts, bevel=bevel); +module metric_trapezoidal_threaded_rod( + d=10, l=100, pitch=2, + left_handed=false, + starts=1, + bevel=false, + orient=ORIENT_Z, + align=V_CENTER +) { + trapezoidal_threaded_rod( + d=d, l=l, + pitch=pitch, + thread_angle=15, + left_handed=left_handed, + starts=starts, + bevel=bevel, + orient=orient, + align=align + ); } -// Constructs a hex nut for a metric trapezoidal threaded screw rod. This method -// makes much smoother threads than the naive linear_extrude method. +// Module: metric_trapezoidal_threaded_nut() +// Description: +// Constructs a hex nut for a metric trapezoidal threaded screw rod. This method +// makes much smoother threads than the naive linear_extrude method. +// Arguments: // od = diameter of the nut. // id = diameter of threaded rod to screw onto. // h = height/thickness of nut. @@ -346,16 +414,40 @@ module metric_trapezoidal_threaded_rod(d=10, l=100, pitch=2, left_handed=false, // bevel = if true, bevel the thread ends. Default: false // starts = The number of lead starts. Default = 1 // slop = printer slop calibration to allow for tight fitting of parts. default=0.2 +// orient = Orientation of the nut. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the nut. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. // Examples: -// metric_trapezoidal_threaded_nut(od=16, d=10, h=10, pitch=2, left_handed=true, bevel=true, $fa=1, $fs=1); -module metric_trapezoidal_threaded_nut(od=17.4, id=10.5, h=10, pitch=3.175, left_handed=false, starts=1, bevel=false, slop=0.2) { - trapezoidal_threaded_nut(od=od, id=id, h=h, pitch=pitch, thread_angle=15, left_handed=left_handed, starts=starts, bevel=bevel, slop=slop); +// metric_trapezoidal_threaded_nut(od=16, id=10, h=10, pitch=2, left_handed=true, bevel=true, $fa=1, $fs=1); +module metric_trapezoidal_threaded_nut( + od=17.4, id=10.5, h=10, + pitch=3.175, + starts=1, + left_handed=false, + bevel=false, + slop=PRINTER_SLOP, + orient=ORIENT_Z, + align=V_CENTER +) { + trapezoidal_threaded_nut( + od=od, id=id, h=h, + pitch=pitch, thread_angle=15, + left_handed=left_handed, + starts=starts, + bevel=bevel, + slop=slop, + orient=orient, + align=align + ); } +// Section: ACME Trapezoidal Threading -// Constructs an ACME trapezoidal threaded screw rod. This method makes -// much smoother threads than the naive linear_extrude method. +// Module: acme_threaded_rod() +// Description: +// Constructs an ACME trapezoidal threaded screw rod. This method makes +// much smoother threads than the naive linear_extrude method. +// Arguments: // d = Outer diameter of threaded rod. // l = length of threaded rod. // pitch = Length between threads. @@ -364,24 +456,40 @@ module metric_trapezoidal_threaded_nut(od=17.4, id=10.5, h=10, pitch=3.175, left // starts = The number of lead starts. Default = 1 // left_handed = if true, create left-handed threads. Default = false // bevel = if true, bevel the thread ends. Default: false +// orient = Orientation of the rod. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the rod. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. // Examples: // acme_threaded_rod(d=3/8*25.4, l=20, pitch=1/8*25.4, $fn=32); // acme_threaded_rod(d=10, l=40, pitch=2, starts=3, $fa=1, $fs=1); -module acme_threaded_rod(d=10, l=100, pitch=2, thread_angle=14.5, thread_depth=undef, starts=1, left_handed=false, bevel=false) { +module acme_threaded_rod( + d=10, l=100, pitch=2, + thread_angle=14.5, + thread_depth=undef, + starts=1, + left_handed=false, + bevel=false, + orient=ORIENT_Z, + align=V_CENTER +) { trapezoidal_threaded_rod( d=d, l=l, pitch=pitch, thread_angle=thread_angle, thread_depth=thread_depth, starts=starts, left_handed=left_handed, - bevel=bevel + bevel=bevel, + orient=orient, + align=align ); } -// Constructs a hex nut for an ACME threaded screw rod. This method makes -// much smoother threads than the naive linear_extrude method. +// Module: acme_threaded_nut() +// Description: +// Constructs a hex nut for an ACME threaded screw rod. This method makes +// much smoother threads than the naive linear_extrude method. +// Arguments: // od = diameter of the nut. // id = diameter of threaded rod to screw onto. // h = height/thickness of nut. @@ -391,10 +499,22 @@ module acme_threaded_rod(d=10, l=100, pitch=2, thread_angle=14.5, thread_depth=u // left_handed = if true, create left-handed threads. Default = false // bevel = if true, bevel the thread ends. Default: false // slop = printer slop calibration to allow for tight fitting of parts. default=0.2 +// orient = Orientation of the nut. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the nut. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. // Examples: // acme_threaded_nut(od=16, id=3/8*25.4, h=8, pitch=1/8*25.4, slop=0.2); // acme_threaded_nut(od=16, id=10, h=10, pitch=2, starts=3, slop=0.2, $fa=1, $fs=1); -module acme_threaded_nut(od, id, h, pitch, thread_angle=14.5, thread_depth=undef, starts=1, left_handed=false, bevel=false, slop=0.2) { +module acme_threaded_nut( + od, id, h, pitch, + thread_angle=14.5, + thread_depth=undef, + starts=1, + left_handed=false, + bevel=false, + slop=PRINTER_SLOP, + orient=ORIENT_Z, + align=V_CENTER +) { trapezoidal_threaded_nut( od=od, id=id, h=h, pitch=pitch, thread_depth=thread_depth, @@ -402,30 +522,56 @@ module acme_threaded_nut(od, id, h, pitch, thread_angle=14.5, thread_depth=undef left_handed=left_handed, bevel=bevel, starts=starts, - slop=slop + slop=slop, + orient=orient, + align=align ); } +// Section: Square Threading -// Constructs a square profile threaded screw rod. This method makes -// much smoother threads than the naive linear_extrude method. +// Module: square_threaded_rod() +// Description: +// Constructs a square profile threaded screw rod. This method makes +// much smoother threads than the naive linear_extrude method. +// Arguments: // d = Outer diameter of threaded rod. // l = length of threaded rod. // pitch = Length between threads. // left_handed = if true, create left-handed threads. Default = false // bevel = if true, bevel the thread ends. Default: false // starts = The number of lead starts. Default = 1 +// orient = Orientation of the rod. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the rod. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. // Examples: // square_threaded_rod(d=10, l=30, pitch=2, starts=2, $fn=32); -module square_threaded_rod(d=10, l=100, pitch=2, left_handed=false, bevel=false, starts=1) { - trapezoidal_threaded_rod(d=d, l=l, pitch=pitch, thread_angle=0, left_handed=left_handed, bevel=bevel, starts=starts); +module square_threaded_rod( + d=10, l=100, pitch=2, + left_handed=false, + bevel=false, + starts=1, + orient=ORIENT_Z, + align=V_CENTER +) { + trapezoidal_threaded_rod( + d=d, l=l, pitch=pitch, + thread_angle=0, + left_handed=left_handed, + bevel=bevel, + starts=starts, + orient=orient, + align=align + ); } -// Constructs a hex nut for a square profile threaded screw rod. This method -// makes much smoother threads than the naive linear_extrude method. +// Module: square_threaded_nut() +// Description: +// Constructs a hex nut for a square profile threaded screw rod. This method +// makes much smoother threads than the naive linear_extrude method. +// Arguments: // od = diameter of the nut. // id = diameter of threaded rod to screw onto. // h = height/thickness of nut. @@ -434,16 +580,29 @@ module square_threaded_rod(d=10, l=100, pitch=2, left_handed=false, bevel=false, // bevel = if true, bevel the thread ends. Default: false // starts = The number of lead starts. Default = 1 // slop = printer slop calibration to allow for tight fitting of parts. default=0.2 +// orient = Orientation of the nut. Use the `ORIENT_` constants from `constants.scad`. Default: `ORIENT_Z`. +// align = Alignment of the nut. Use the `V_` constants from `constants.scad`. Default: `V_CENTER`. // Examples: // square_threaded_nut(od=16, id=10, h=10, pitch=2, starts=2, slop=0.15, $fn=32); -module square_threaded_nut(od=17.4, id=10.5, h=10, pitch=3.175, left_handed=false, bevel=false, starts=1, slop=0.2) { +module square_threaded_nut( + od=17.4, id=10.5, h=10, + pitch=3.175, + left_handed=false, + bevel=false, + starts=1, + slop=PRINTER_SLOP, + orient=ORIENT_Z, + align=V_CENTER +) { trapezoidal_threaded_nut( od=od, id=id, h=h, pitch=pitch, thread_angle=0, left_handed=left_handed, bevel=bevel, starts=starts, - slop=slop + slop=slop, + orient=orient, + align=align ); } diff --git a/torx_drive.scad b/torx_drive.scad index 0523e8a..9805df7 100644 --- a/torx_drive.scad +++ b/torx_drive.scad @@ -1,5 +1,11 @@ ////////////////////////////////////////////////////////////////////// -// Torx driver bits +// LibFile: torx_drive.scad +// Torx driver bits +// To use, add these lines to the top of your file: +// ``` +// include +// use +// ``` ////////////////////////////////////////////////////////////////////// /* @@ -33,9 +39,16 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use use +include +include -// Typical outer diameter of Torx profile. +// Section: Functions + + +// Function: torx_outer_diam() +// Description: Get the typical outer diameter of Torx profile. +// Arguments: // size = Torx size. function torx_outer_diam(size) = lookup(size, [ [ 6, 1.75], @@ -57,7 +70,9 @@ function torx_outer_diam(size) = lookup(size, [ ]); -// Typical inner diameter of Torx profile. +// Function: torx_inner_diam() +// Description: Get typical inner diameter of Torx profile. +// Arguments: // size = Torx size. function torx_inner_diam(size) = lookup(size, [ [ 6, 1.27], @@ -79,7 +94,9 @@ function torx_inner_diam(size) = lookup(size, [ ]); -// Typical drive depth. +// Function: torx_depth() +// Description: Gets typical drive hole depth. +// Arguments: // size = Torx size. function torx_depth(size) = lookup(size, [ [ 6, 1.82], @@ -101,7 +118,9 @@ function torx_depth(size) = lookup(size, [ ]); -// Minor rounding radius of Torx profile. +// Function: torx_tip_radius() +// Description: Gets minor rounding radius of Torx profile. +// Arguments: // size = Torx size. function torx_tip_radius(size) = lookup(size, [ [ 6, 0.132], @@ -123,7 +142,9 @@ function torx_tip_radius(size) = lookup(size, [ ]); -// Major rounding radius of Torx profile. +// Function: torx_rounding_radius() +// Description: Gets major rounding radius of Torx profile. +// Arguments: // size = Torx size. function torx_rounding_radius(size) = lookup(size, [ [ 6, 0.383], @@ -145,9 +166,14 @@ function torx_rounding_radius(size) = lookup(size, [ ]); -// Creates a torx bit 2D profile. +// Section: Modules + + +// Module: torx_drive2d() +// Description: Creates a torx bit 2D profile. +// Arguments: // size = Torx size. -// Examples: +// Example(2D): // torx_drive2d(size=30, $fa=1, $fs=1); module torx_drive2d(size) { od = torx_outer_diam(size); @@ -181,15 +207,20 @@ module torx_drive2d(size) { -// Creates a torx bit tip. +// Module: torx_drive() +// Description: Creates a torx bit tip. +// Arguments: // size = Torx size. // l = Length of bit. // center = If true, centers bit vertically. // Examples: // torx_drive(size=30, l=10, $fa=1, $fs=1); -module torx_drive(size, l=5, center=undef) { - linear_extrude(height=l, convexity=4, center=center) { - torx_drive2d(size); +module torx_drive(size, l=5, center=undef, orient=ORIENT_Z, align=V_UP) { + od = torx_outer_diam(size); + orient_and_align([od, od, l], orient, align, center) { + linear_extrude(height=l, convexity=4, center=true) { + torx_drive2d(size); + } } } diff --git a/transforms.scad b/transforms.scad index 77768ca..075aac9 100644 --- a/transforms.scad +++ b/transforms.scad @@ -1,5 +1,11 @@ ////////////////////////////////////////////////////////////////////// -// Transformations, distributors, duplicators, and manipulators. +// LibFile: transforms.scad +// This is the file that the most commonly used transformations, distributors, and mutator are in. +// To use, add the following lines to the beginning of your file: +// ``` +// include +// use +// ``` ////////////////////////////////////////////////////////////////////// /* @@ -31,197 +37,1061 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -include +use +include include -printer_slop = 0.20; // mm ////////////////////////////////////////////////////////////////////// -// Transformations. +// Section: Translations ////////////////////////////////////////////////////////////////////// -// Moves/translates children. +// Module: move() +// +// Description: +// Moves/translates children. +// +// Usage: +// move([x], [y], [z]) ... +// move([x,y,z]) ... +// +// Arguments: // x = X axis translation. // y = Y axis translation. // z = Z axis translation. +// // Example: -// move([10,20,30]) sphere(r=1); -// move(y=10) sphere(r=1); -// move(x=5, z=10) sphere(r=1); -module move(a=[0,0,0], x=0, y=0, z=0) { - translate(a) translate([x,y,z]) children(); +// #sphere(d=10); +// move([0,20,30]) sphere(d=10); +// +// Example: +// #sphere(d=10); +// move(y=20) sphere(d=10); +// +// Example: +// #sphere(d=10); +// move(x=-10, y=-5) sphere(d=10); +module move(a=[0,0,0], x=0, y=0, z=0) +{ + translate(a+[x,y,z]) children(); } -// Moves/translates children the given amount along the X axis. +// Module: xmove() +// +// Description: +// Moves/translates children the given amount along the X axis. +// +// Usage: +// xmove(x) ... +// +// Arguments: +// x = Amount to move right along the X axis. Negative values move left. +// // Example: -// xmove(10) sphere(r=1); -module xmove(x=0) { translate([x,0,0]) children(); } +// #sphere(d=10); +// xmove(20) sphere(d=10); +module xmove(x=0) translate([x,0,0]) children(); -// Moves/translates children the given amount along the Y axis. +// Module: ymove() +// +// Description: +// Moves/translates children the given amount along the Y axis. +// +// Usage: +// ymove(y) ... +// +// Arguments: +// y = Amount to move back along the Y axis. Negative values move forward. +// // Example: -// ymove(10) sphere(r=1); -module ymove(y=0) { translate([0,y,0]) children(); } +// #sphere(d=10); +// ymove(20) sphere(d=10); +module ymove(y=0) translate([0,y,0]) children(); -// Moves/translates children the given amount along the Z axis. +// Module: zmove() +// +// Description: +// Moves/translates children the given amount along the Z axis. +// +// Usage: +// zmove(z) ... +// +// Arguments: +// z = Amount to move up along the Z axis. Negative values move down. +// // Example: -// zmove(10) sphere(r=1); -module zmove(z=0) { translate([0,0,z]) children(); } +// #sphere(d=10); +// zmove(20) sphere(d=10); +module zmove(z=0) translate([0,0,z]) children(); -// Moves children left by the given amount in the -X direction. +// Module: left() +// +// Description: +// Moves children left (in the X- direction) by the given amount. +// +// Usage: +// left(x) ... +// +// Arguments: +// x = Scalar amount to move left. +// // Example: -// left(10) sphere(r=1); -module left(x=0) { translate([-x,0,0]) children(); } +// #sphere(d=10); +// left(20) sphere(d=10); +module left(x=0) translate([-x,0,0]) children(); -// Moves children right by the given amount in the +X direction. +// Module: right() +// +// Description: +// Moves children right (in the X+ direction) by the given amount. +// +// Usage: +// right(x) ... +// +// Arguments: +// x = Scalar amount to move right. +// // Example: -// right(10) sphere(r=1); -module right(x=0) { translate([x,0,0]) children(); } +// #sphere(d=10); +// right(20) sphere(d=10); +module right(x=0) translate([x,0,0]) children(); -// Moves children forward by x amount in the -Y direction. +// Module: fwd() / forward() +// +// Description: +// Moves children forward (in the Y- direction) by the given amount. +// +// Usage: +// fwd(y) ... +// forward(y) ... +// +// Arguments: +// y = Scalar amount to move forward. +// // Example: -// forward(10) sphere(r=1); -module forward(y=0) { translate([0,-y,0]) children(); } -module fwd(y=0) { translate([0,-y,0]) children(); } +// #sphere(d=10); +// fwd(20) sphere(d=10); +module forward(y=0) translate([0,-y,0]) children(); +module fwd(y=0) translate([0,-y,0]) children(); -// Moves children back by the given amount in the +Y direction. +// Module: back() +// +// Description: +// Moves children back (in the Y+ direction) by the given amount. +// +// Usage: +// back(y) ... +// +// Arguments: +// y = Scalar amount to move back. +// // Example: -// back(10) sphere(r=1); -module back(y=0) { translate([0,y,0]) children(); } +// #sphere(d=10); +// back(20) sphere(d=10); +module back(y=0) translate([0,y,0]) children(); -// Moves children down by the given amount in the -Z direction. +// Module: down() +// +// Description: +// Moves children down (in the Z- direction) by the given amount. +// +// Usage: +// down(z) ... +// +// Arguments: +// z = Scalar amount to move down. +// // Example: -// down(10) sphere(r=1); -module down(z=0) { translate([0,0,-z]) children(); } +// #sphere(d=10); +// down(20) sphere(d=10); +module down(z=0) translate([0,0,-z]) children(); -// Moves children up by the given amount in the +Z direction. +// Module: up() +// +// Description: +// Moves children up (in the Z+ direction) by the given amount. +// +// Usage: +// up(z) ... +// +// Arguments: +// z = Scalar amount to move up. +// // Example: -// up(10) sphere(r=1); -module up(z=0) { translate([0,0,z]) children(); } +// #sphere(d=10); +// up(20) sphere(d=10); +module up(z=0) translate([0,0,z]) children(); -// Rotates children around an arbitrary axis by the given number of degrees. -// Can be used as a drop-in replacement for `rotate()`, with extra features. -// a = Scalar angle or vector of Euller angles to rotate by, in degrees. -// v = vector for the axis of rotation. Default: [0,0,1] + +////////////////////////////////////////////////////////////////////// +// Section: Rotations +////////////////////////////////////////////////////////////////////// + + +// Module: rot() +// +// Description: +// Rotates children around an arbitrary axis by the given number of degrees. +// Can be used as a drop-in replacement for `rotate()`, with extra features. +// +// Usage: +// rot(a, [cp], [reverse]) ... +// rot([X,Y,Z], [cp], [reverse]) ... +// rot(a, v, [cp], [reverse]) ... +// rot(from, to, [a], [reverse]) ... +// +// Arguments: +// a = Scalar angle or vector of XYZ rotation angles to rotate by, in degrees. +// v = vector for the axis of rotation. Default: [0,0,1] or V_UP // cp = centerpoint to rotate around. Default: [0,0,0] +// from = Starting vector for vector-based rotations. +// to = Target vector for vector-based rotations. +// reverse = If true, exactly reverses the rotation, including axis rotation ordering. Default: false +// // Example: +// #cube([2,4,9]); // rot([30,60,0], cp=[0,0,9]) cube([2,4,9]); +// +// Example: +// #cube([2,4,9]); // rot(30, v=[1,1,0], cp=[0,0,9]) cube([2,4,9]); -module rot(a=0, v=undef, cp=undef) { - if (a == 0) { - children(); // May be slightly faster? - } else if (cp == undef) { - rotate(a=a, v=v) children(); - } else { - translate(cp) rotate(a=a, v=v) translate(-cp) children(); - } -} - - -// Rotates children from a starting vector to a target vector. -// v1 = Starting vector. -// v2 = Target vector. -// a = Angle to rotate around v1, before rotating to v2. Default: 0 +// // Example: -// rotate_from_to(V_UP, V_RIGHT+V_DOWN) { -// cylinder(h=10, d=1, center=false); -// } -module rotate_from_to(v1,v2,a=0) { - if (v1 == v2 && a == 0) { - children(); // May be slightly faster? - } else { - axis = normalize(cross(v1,v2)); - ang = vector3d_angle(v1,v2); - rotate(a=ang, v=axis) { - rotate(a=a, v=v1) children(); - } - } -} - - -// Rotates children around the X axis by the given number of degrees. -// a = angle to rotate by in degrees. -// cp = centerpoint to rotate around. Default: [0,0,0] -// Example: -// xrot(90) cylinder(h=10, r=2, center=true); -module xrot(a=0, cp=undef) { - if (a==0) { - children(); // May be slightly faster? - } else if (cp == undef) { - rotate([a, 0, 0]) children(); - } else { - translate(cp) rotate([a, 0, 0]) translate(-cp) children(); - } -} - - -// Rotates children around the Y axis by the given number of degrees. -// a = angle to rotate by in degrees. -// cp = centerpoint to rotate around. Default: [0,0,0] -// Example: -// yrot(90) cylinder(h=10, r=2, center=true); -module yrot(a=0, cp=undef) { - if (a==0) { - children(); // May be slightly faster? - } else if (cp == undef) { - rotate([0, a, 0]) children(); - } else { - translate(cp) rotate([0, a, 0]) translate(-cp) children(); - } -} - - -// Rotates children around the Z axis by the given number of degrees. -// a = angle to rotate by in degrees. -// cp = centerpoint to rotate around. Default: [0,0,0] -// Example: -// zrot(90) cube(size=[9,1,4], center=true); -module zrot(a=0, cp=undef) { - if (a==0) { - children(); // May be slightly faster? - } else if (cp == undef) { - rotate(a) children(); - } else { - translate(cp) rotate(a) translate(-cp) children(); - } -} - - - -// Takes a vertically oriented shape, and re-orients and aligns it. -// size = The size of the part. -// orient = The axis to align to. Use ORIENT_ constants from constants.scad -// align = The side of the origin the part should be aligned with. -module orient_and_align(size, orient=ORIENT_Z, align=V_ZERO) { - if (align == V_ZERO) { - // Shortcut past translate() if not needed. - if (orient == ORIENT_Z) { - children(); // Shortcut past rotation() if not needed. +// #cube([2,4,9]); +// rot(from=V_UP, to=V_LEFT+V_BACK) cube([2,4,9]); +module rot(a=0, v=undef, cp=undef, from=undef, to=undef, reverse=false) +{ + if (is_def(cp)) { + translate(cp) rot(a=a, v=v, from=from, to=to, reverse=reverse) translate(-cp) children(); + } else if (is_def(from)) { + eps = 0.00001; + assertion(is_def(to), "`from` and `to` should be used together."); + vv1 = normalize(from); + vv2 = normalize(to); + if (norm(vv2-vv1) < eps && a == 0) { + children(); // May be slightly faster? } else { - rotate(orient) children(); + vv3 = ( + (norm(vv1+vv2) > eps)? vv2 : + (norm(vabs(vv2)-V_UP) > eps)? V_UP : + V_RIGHT + ); + axis = normalize(cross(vv1, vv3)); + ang = vector3d_angle(vv1, vv2); + if (reverse) { + rotate(a=-ang, v=axis) rotate(a=-a, v=vv1) children(); + } else { + rotate(a=ang, v=axis) rotate(a=a, v=vv1) children(); + } + } + } else if (a == 0) { + children(); // May be slightly faster? + } else if (reverse) { + if (is_def(v)) { + rotate(a=-a, v=v) children(); + } else if (is_scalar(a)) { + rotate(-a) children(); + } else { + rotate([-a[0],0,0]) rotate([0,-a[1],0]) rotate([0,0,-a[2]]) children(); } } else { - offset = ( - (orient==ORIENT_X)? [size[2], size[1], size[0]] : - (orient==ORIENT_Y)? [size[0], size[2], size[1]] : - size - ); - translate(vmul(offset/2,align)) { - if (orient == ORIENT_Z) { - children(); // Shortcut past rotation() if not needed. - } else { - rotate(orient) children(); + rotate(a=a, v=v) children(); + } +} + + +// Module: xrot() +// +// Description: +// Rotates children around the X axis by the given number of degrees. +// +// Usage: +// xrot(a, [cp]) ... +// +// Arguments: +// a = angle to rotate by in degrees. +// cp = centerpoint to rotate around. Default: [0,0,0] +// +// Example: +// #cylinder(h=50, r=10, center=true); +// xrot(90) cylinder(h=50, r=10, center=true); +module xrot(a=0, cp=undef) +{ + if (a==0) { + children(); // May be slightly faster? + } else if (is_def(cp)) { + translate(cp) rotate([a, 0, 0]) translate(-cp) children(); + } else { + rotate([a, 0, 0]) children(); + } +} + + +// Module: yrot() +// +// Description: +// Rotates children around the Y axis by the given number of degrees. +// +// Usage: +// yrot(a, [cp]) ... +// +// Arguments: +// a = angle to rotate by in degrees. +// cp = centerpoint to rotate around. Default: [0,0,0] +// +// Example: +// #cylinder(h=50, r=10, center=true); +// yrot(90) cylinder(h=50, r=10, center=true); +module yrot(a=0, cp=undef) +{ + if (a==0) { + children(); // May be slightly faster? + } else if (is_def(cp)) { + translate(cp) rotate([0, a, 0]) translate(-cp) children(); + } else { + rotate([0, a, 0]) children(); + } +} + + +// Module: zrot() +// +// Description: +// Rotates children around the Z axis by the given number of degrees. +// +// Usage: +// zrot(a, [cp]) ... +// +// Arguments: +// a = angle to rotate by in degrees. +// cp = centerpoint to rotate around. Default: [0,0,0] +// +// Example: +// #cube(size=[60,20,40], center=true); +// zrot(90) cube(size=[60,20,40], center=true); +module zrot(a=0, cp=undef) +{ + if (a==0) { + children(); // May be slightly faster? + } else if (is_def(cp)) { + translate(cp) rotate(a) translate(-cp) children(); + } else { + rotate(a) children(); + } +} + + + +////////////////////////////////////////////////////////////////////// +// Section: Scaling and Mirroring +////////////////////////////////////////////////////////////////////// + + +// Module: xscale() +// +// Description: +// Scales children by the given factor on the X axis. +// +// Usage: +// xscale(x) ... +// +// Arguments: +// x = Factor to scale by along the X axis. +// +// Example: +// xscale(3) sphere(r=10); +module xscale(x) scale([x,1,1]) children(); + + +// Module: yscale() +// +// Description: +// Scales children by the given factor on the Y axis. +// +// Usage: +// yscale(y) ... +// +// Arguments: +// y = Factor to scale by along the Y axis. +// +// Example: +// yscale(3) sphere(r=10); +module yscale(y) scale([1,y,1]) children(); + + +// Module: zscale() +// +// Description: +// Scales children by the given factor on the Z axis. +// +// Usage: +// zscale(z) ... +// +// Arguments: +// z = Factor to scale by along the Z axis. +// +// Example: +// zscale(3) sphere(r=10); +module zscale(z) scale([1,1,z]) children(); + + +// Module: xflip() +// +// Description: +// Mirrors the children along the X axis, like `mirror([1,0,0])` or `xscale(-1)` +// +// Usage: +// xflip([cp]) ... +// +// Arguments: +// cp = A point that lies on the plane of reflection. +// +// Example: +// xflip() yrot(90) cylinder(d1=10, d2=0, h=20); +// color("blue", 0.25) cube([0.01,15,15], center=true); +// color("red", 0.333) yrot(90) cylinder(d1=10, d2=0, h=20); +// +// Example: +// xflip(cp=[-5,0,0]) yrot(90) cylinder(d1=10, d2=0, h=20); +// color("blue", 0.25) left(5) cube([0.01,15,15], center=true); +// color("red", 0.333) yrot(90) cylinder(d1=10, d2=0, h=20); +module xflip(cp=[0,0,0]) translate(cp) mirror([1,0,0]) translate(-cp) children(); + + +// Module: yflip() +// +// Description: +// Mirrors the children along the Y axis, like `mirror([0,1,0])` or `yscale(-1)` +// +// Usage: +// yflip([cp]) ... +// +// Arguments: +// cp = A point that lies on the plane of reflection. +// +// Example: +// yflip() xrot(90) cylinder(d1=10, d2=0, h=20); +// color("blue", 0.25) cube([15,0.01,15], center=true); +// color("red", 0.333) xrot(90) cylinder(d1=10, d2=0, h=20); +// +// Example: +// yflip(cp=[0,5,0]) xrot(90) cylinder(d1=10, d2=0, h=20); +// color("blue", 0.25) back(5) cube([15,0.01,15], center=true); +// color("red", 0.333) xrot(90) cylinder(d1=10, d2=0, h=20); +module yflip(cp=[0,0,0]) translate(cp) mirror([0,1,0]) translate(-cp) children(); + + +// Module: zflip() +// +// Description: +// Mirrors the children along the Z axis, like `mirror([0,0,1])` or `zscale(-1)` +// +// Usage: +// zflip([cp]) ... +// +// Arguments: +// cp = A point that lies on the plane of reflection. +// +// Example: +// zflip() cylinder(d1=10, d2=0, h=20); +// color("blue", 0.25) cube([15,15,0.01], center=true); +// color("red", 0.333) cylinder(d1=10, d2=0, h=20); +// +// Example: +// zflip(cp=[0,0,-5]) cylinder(d1=10, d2=0, h=20); +// color("blue", 0.25) down(5) cube([15,15,0.01], center=true); +// color("red", 0.333) cylinder(d1=10, d2=0, h=20); +module zflip(cp=[0,0,0]) translate(cp) mirror([0,0,1]) translate(-cp) children(); + + + +////////////////////////////////////////////////////////////////////// +// Section: Skewing +////////////////////////////////////////////////////////////////////// + + +// Module: skew_xy() / skew_z() +// +// Description: +// Skews children on the X-Y plane, keeping constant in Z. +// +// Usage: +// skew_xy([xa], [ya]) ... +// skew_z([xa], [ya]) ... +// +// Arguments: +// xa = skew angle towards the X direction. +// ya = skew angle towards the Y direction. +// +// Example(FlatSpin): +// #cube(size=10); +// skew_xy(xa=30, ya=15) cube(size=10); +module skew_xy(xa=0, ya=0) multmatrix(m = matrix4_skew_xy(xa, ya)) children(); +module zskew(xa=0, ya=0) multmatrix(m = matrix4_skew_xy(xa, ya)) children(); + + +// Module: skew_yz() / skew_x() +// +// Description: +// Skews children on the Y-Z plane, keeping constant in X. +// +// Usage: +// skew_yz([ya], [za]) ... +// skew_x([ya], [za]) ... +// +// Arguments: +// ya = skew angle towards the Y direction. +// za = skew angle towards the Z direction. +// +// Example(FlatSpin): +// #cube(size=10); +// skew_yz(ya=30, za=15) cube(size=10); +module skew_yz(ya=0, za=0) multmatrix(m = matrix4_skew_yz(ya, za)) children(); +module xskew(ya=0, za=0) multmatrix(m = matrix4_skew_yz(ya, za)) children(); + + +// Module: skew_xz() / skew_y() +// +// Description: +// Skews children on the X-Z plane, keeping constant in Y. +// +// Usage: +// skew_xz([xa], [za]) ... +// skew_y([xa], [za]) ... +// +// Arguments: +// xa = skew angle towards the X direction. +// za = skew angle towards the Z direction. +// +// Example(FlatSpin): +// #cube(size=10); +// skew_xz(xa=15, za=-10) cube(size=10); +module skew_xz(xa=0, za=0) multmatrix(m = matrix4_skew_xz(xa, za)) children(); +module yskew(xa=0, za=0) multmatrix(m = matrix4_skew_xz(xa, za)) children(); + + + +////////////////////////////////////////////////////////////////////// +// Section: Translational Distributors +////////////////////////////////////////////////////////////////////// + + +// Module: place_copies() +// +// Description: +// Makes copies of the given children at each of the given offsets. +// +// Usage: +// place_copies(a) ... +// +// Arguments: +// a = array of XYZ offset vectors. Default [[0,0,0]] +// +// Side Effects: +// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. +// +// Example: +// #sphere(r=10); +// place_copies([[-25,-25,0], [25,-25,0], [0,0,50], [0,25,0]]) sphere(r=10); +module place_copies(a=[[0,0,0]]) +{ + for (off = a) translate(off) children(); +} + + +// Module: translate_copies() +// Status: DEPRECATED, use `place_copies()` instead. +// +// Description: +// Makes copies of the given children at each of the given offsets. +// +// Usage: +// translate_copies(a) ... +// +// Arguments: +// a = array of XYZ offset vectors. Default [[0,0,0]] +// +// Side Effects: +// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. +module translate_copies(a=[[0,0,0]]) +{ + deprecate("translate_copies()", "place_copies()"); + place_copies(a) children(); +} + + +// Module: line_of() +// Status: DEPRECATED, use `spread(p1,p2)` instead +// +// Description: +// Evenly distributes n duplicate children along an XYZ line. +// +// Usage: +// line_of(p1, p2, [n]) ... +// +// Arguments: +// p1 = starting point of line. (Default: [0,0,0]) +// p2 = ending point of line. (Default: [10,0,0]) +// n = number of copies to distribute along the line. (Default: 2) +// +// Side Effects: +// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. +module line_of(p1=[0,0,0], p2=[10,0,0], n=2) +{ + deprecate("line_of()", "spread()"); + spread(p1=p1, p2=p2, n=n) children(); +} + + + +// Module: spread() +// +// Description: +// Evenly distributes `n` copies of the children along a line. +// +// Usage: +// spread(l, [n], [p1]) ... +// spread(l, spacing, [p1]) ... +// spread(spacing, [n], [p1]) ... +// spread(p1, p2, [n]) ... +// spread(p1, p2, spacing) ... +// +// Arguments: +// p1 = Starting point of line. +// p2 = Ending point of line. +// l = Length to spread copies over. +// spacing = A 3D vector indicating which direction and distance to place each subsequent copy at. +// n = Number of copies to distribute along the line. (Default: 2) +// +// Side Effects: +// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. +// `$idx` is set to the index number of each child being copied. +// +// Example(FlatSpin): +// spread([0,0,0], [5,5,20], n=6) cube(size=[3,2,1],center=true); +// Examples: +// spread(l=40, n=6) cube(size=[3,2,1],center=true); +// spread(l=[15,30], n=6) cube(size=[3,2,1],center=true); +// spread(l=40, spacing=10) cube(size=[3,2,1],center=true); +// spread(spacing=[5,5,0], n=5) cube(size=[3,2,1],center=true); +module spread(p1=undef, p2=undef, spacing=undef, l=undef, n=undef) +{ + ll = ( + is_def(l)? scalar_vec3(l, 0) : + (is_def(spacing) && is_def(n))? (n * scalar_vec3(spacing, 0)) : + (is_def(p1) && is_def(p2))? point3d(p2-p1) : + undef + ); + cnt = ( + is_def(n)? n : + (is_def(spacing) && is_def(ll))? floor(norm(ll) / norm(scalar_vec3(spacing, 0)) + 1.000001) : + 2 + ); + spc = ( + !is_def(spacing)? (ll/(cnt-1)) : + is_scalar(spacing) && is_def(ll)? (ll/(cnt-1)) : + scalar_vec3(spacing, 0) + ); + assertion(is_def(cnt), "Need two of `spacing`, 'l', 'n', or `p1`/`p2` arguments in `spread()`."); + spos = is_def(p1)? point3d(p1) : -(cnt-1)/2 * spc; + for (i=[0 : cnt-1]) { + pos = i * spc + spos; + $pos = pos; + $idx = i; + translate(pos) children(); + } +} + + +// Module: xspread() +// +// Description: +// Spreads out `n` copies of the children along the X axis. +// +// Usage: +// xspread(spacing, [n], [p1]) ... +// xspread(l, [n], [p1]) ... +// +// Arguments: +// spacing = spacing between copies. (Default: 1.0) +// n = Number of copies to spread out. (Default: 2) +// l = Length to spread copies over. +// p1 = Starting point of line if given. If not, line is centered at origin. +// +// Side Effects: +// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. +// `$idx` is set to the index number of each child being copied. +// +// Examples: +// xspread(20) sphere(3); +// xspread(20, n=3) sphere(3); +// xspread(spacing=15, l=50) sphere(3); +// xspread(n=5, l=40, p1=[0,0]) sphere(3); +module xspread(spacing=undef, n=undef, l=undef, p1=undef) +{ + spread(l=l*V_RIGHT, spacing=spacing*V_RIGHT, n=n, p1=p1) children(); +} + + +// Module: yspread() +// +// Description: +// Spreads out `n` copies of the children along the Y axis. +// +// Usage: +// yspread(spacing, [n], [p1]) ... +// yspread(l, [n], [p1]) ... +// +// Arguments: +// spacing = spacing between copies. (Default: 1.0) +// n = Number of copies to spread out. (Default: 2) +// l = Length to spread copies over. +// p1 = Starting point of line if given. If not, line is centered at origin. +// +// Side Effects: +// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. +// `$idx` is set to the index number of each child being copied. +// +// Examples: +// yspread(20) sphere(3); +// yspread(20, n=3) sphere(3); +// yspread(spacing=15, l=50) sphere(3); +// yspread(n=5, l=40, p1=[0,0]) sphere(3); +module yspread(spacing=undef, n=undef, l=undef, p1=undef) +{ + spread(l=l*V_BACK, spacing=spacing*V_BACK, n=n, p1=p1) children(); +} + + +// Module: zspread() +// +// Description: +// Spreads out `n` copies of the children along the Z axis. +// +// Usage: +// zspread(spacing, [n], [p1]) ... +// zspread(l, [n], [p1]) ... +// +// Arguments: +// spacing = spacing between copies. (Default: 1.0) +// n = Number of copies to spread out. (Default: 2) +// l = Length to spread copies over. +// p1 = Starting point of line if given. If not, line is centered at origin. +// +// Side Effects: +// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. +// `$idx` is set to the index number of each child being copied. +// +// Examples: +// zspread(20) sphere(3); +// zspread(20, n=3) sphere(3); +// zspread(spacing=15, l=50) sphere(3); +// zspread(n=5, l=40, p1=[0,0]) sphere(3); +module zspread(spacing=undef, n=undef, l=undef, p1=undef) +{ + spread(l=l*V_UP, spacing=spacing*V_UP, n=n, p1=p1) children(); +} + + + +// Module: distribute() +// +// Description: +// Spreads out each individual child along the direction `dir`. +// This is useful for laying out groups of disparate objects +// where you only really care about the spacing between them. +// +// Usage: +// distribute(spacing, dir, [sizes]) ... +// distribute(l, dir, [sizes]) ... +// +// Arguments: +// spacing = Spacing to add between each child. (Default: 10.0) +// sizes = Array containing how much space each child will need. +// dir = Vector direction to distribute copies along. +// l = Length to distribute copies along. +// +// Side Effect: +// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. +// `$idx` is set to the index number of each child being copied. +// +// Example: +// distribute(sizes=[100, 30, 50], dir=V_UP) { +// sphere(r=50); +// cube([10,20,30], center=true); +// cylinder(d=30, h=50, center=true); +// } +module distribute(spacing=undef, sizes=undef, dir=V_RIGHT, l=undef) +{ + gaps = ($children < 2)? [0] : + is_def(sizes)? [for (i=[0:$children-2]) sizes[i]/2 + sizes[i+1]/2] : + [for (i=[0:$children-2]) 0]; + spc = is_def(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); + gaps2 = [for (gap = gaps) gap+spc]; + spos = dir * -sum(gaps2)/2; + for (i=[0:$children-1]) { + totspc = sum(concat([0], slice(gaps2, 0, i))); + $pos = spos + totspc * dir; + $idx = i; + translate($pos) children(i); + } +} + + +// Module: xdistribute() +// +// Description: +// Spreads out each individual child along the X axis. +// This is useful for laying out groups of disparate objects +// where you only really care about the spacing between them. +// +// Usage: +// xdistribute(spacing, [sizes]) ... +// xdistribute(l, [sizes]) ... +// +// Arguments: +// spacing = spacing between each child. (Default: 10.0) +// sizes = Array containing how much space each child will need. +// l = Length to distribute copies along. +// +// Side Effect: +// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. +// `$idx` is set to the index number of each child being copied. +// +// Example: +// xdistribute(sizes=[100, 10, 30], spacing=40) { +// sphere(r=50); +// cube([10,20,30], center=true); +// cylinder(d=30, h=50, center=true); +// } +module xdistribute(spacing=10, sizes=undef, l=undef) +{ + dir = V_RIGHT; + gaps = ($children < 2)? [0] : + is_def(sizes)? [for (i=[0:$children-2]) sizes[i]/2 + sizes[i+1]/2] : + [for (i=[0:$children-2]) 0]; + spc = is_def(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); + gaps2 = [for (gap = gaps) gap+spc]; + spos = dir * -sum(gaps2)/2; + for (i=[0:$children-1]) { + totspc = sum(concat([0], slice(gaps2, 0, i))); + $pos = spos + totspc * dir; + $idx = i; + translate($pos) children(i); + } +} + + +// Module: ydistribute() +// +// Description: +// Spreads out each individual child along the Y axis. +// This is useful for laying out groups of disparate objects +// where you only really care about the spacing between them. +// +// Usage: +// ydistribute(spacing, [sizes]) +// ydistribute(l, [sizes]) +// +// Arguments: +// spacing = spacing between each child. (Default: 10.0) +// sizes = Array containing how much space each child will need. +// l = Length to distribute copies along. +// +// Side Effect: +// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. +// `$idx` is set to the index number of each child being copied. +// +// Example: +// ydistribute(sizes=[30, 20, 100], spacing=40) { +// cylinder(d=30, h=50, center=true); +// cube([10,20,30], center=true); +// sphere(r=50); +// } +module ydistribute(spacing=10, sizes=undef, l=undef) +{ + dir = V_BACK; + gaps = ($children < 2)? [0] : + is_def(sizes)? [for (i=[0:$children-2]) sizes[i]/2 + sizes[i+1]/2] : + [for (i=[0:$children-2]) 0]; + spc = is_def(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); + gaps2 = [for (gap = gaps) gap+spc]; + spos = dir * -sum(gaps2)/2; + for (i=[0:$children-1]) { + totspc = sum(concat([0], slice(gaps2, 0, i))); + $pos = spos + totspc * dir; + $idx = i; + translate($pos) children(i); + } +} + + +// Module: zdistribute() +// +// Description: +// Spreads out each individual child along the Z axis. +// This is useful for laying out groups of disparate objects +// where you only really care about the spacing between them. +// +// Usage: +// zdistribute(spacing, [sizes]) +// zdistribute(l, [sizes]) +// +// Arguments: +// spacing = spacing between each child. (Default: 10.0) +// sizes = Array containing how much space each child will need. +// l = Length to distribute copies along. +// +// Side Effect: +// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. +// `$idx` is set to the index number of each child being copied. +// +// Example: +// zdistribute(sizes=[30, 20, 100], spacing=40) { +// cylinder(d=30, h=50, center=true); +// cube([10,20,30], center=true); +// sphere(r=50); +// } +module zdistribute(spacing=10, sizes=undef, l=undef) +{ + dir = V_UP; + gaps = ($children < 2)? [0] : + is_def(sizes)? [for (i=[0:$children-2]) sizes[i]/2 + sizes[i+1]/2] : + [for (i=[0:$children-2]) 0]; + spc = is_def(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); + gaps2 = [for (gap = gaps) gap+spc]; + spos = dir * -sum(gaps2)/2; + for (i=[0:$children-1]) { + totspc = sum(concat([0], slice(gaps2, 0, i))); + $pos = spos + totspc * dir; + $idx = i; + translate($pos) children(i); + } +} + + + +// Module: grid2d() +// +// Description: +// Makes a square or hexagonal grid of copies of children. +// +// Usage: +// grid2d(size, spacing, [stagger], [scale], [in_poly], [orient], [align]) ... +// grid2d(size, cols, rows, [stagger], [scale], [in_poly], [orient], [align]) ... +// grid2d(spacing, cols, rows, [stagger], [scale], [in_poly], [orient], [align]) ... +// grid2d(spacing, in_poly, [stagger], [scale], [orient], [align]) ... +// grid2d(cols, rows, in_poly, [stagger], [scale], [orient], [align]) ... +// +// Arguments: +// size = The [X,Y] size to spread the copies over. +// spacing = Distance between copies in [X,Y] or scalar distance. +// cols = How many columns of copies to make. If staggered, count both staggered and unstaggered columns. +// rows = How many rows of copies to make. If staggered, count both staggered and unstaggered rows. +// stagger = If true, make a staggered (hexagonal) grid. If false, make square grid. If "alt", makes alternate staggered pattern. Default: false +// scale = [X,Y] scaling factors to reshape grid. +// in_poly = If given a list of polygon points, only creates copies whose center would be inside the polygon. +// orient = Orientation axis for the grid. Orientation is NOT applied to individual children. +// align = Alignment of the grid. Alignment is NOT applies to individual children. +// +// Side Effects: +// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. +// `$col` is set to the integer column number for each child. +// `$row` is set to the integer row number for each child. +// +// Examples: +// grid2d(size=50, spacing=10, stagger=false) cylinder(d=10, h=1); +// grid2d(spacing=10, rows=7, cols=13, stagger=true) cylinder(d=6, h=5); +// grid2d(spacing=10, rows=7, cols=13, stagger="alt") cylinder(d=6, h=5); +// grid2d(size=50, rows=11, cols=11, stagger=true) cylinder(d=5, h=1); +// +// Example: +// poly = [[-25,-25], [25,25], [-25,25], [25,-25]]; +// grid2d(spacing=5, stagger=true, in_poly=poly) +// zrot(180/6) cylinder(d=5, h=1, $fn=6); +// %polygon(poly); +// +// Example: +// // Makes a grid of hexagon pillars whose tops are all +// // angled to reflect light at [0,0,50], if they were shiny. +// hexregion = [for (a = [0:60:359.9]) 50.01*[cos(a), sin(a)]]; +// grid2d(spacing=10, stagger=true, in_poly=hexregion) { +// // Note: You must use for(var=[val]) or let(var=val) +// // to set vars from $pos or other special vars in this scope. +// let (ref_v = (normalize([0,0,50]-point3d($pos)) + V_UP)/2) +// half_of(v=-ref_v, cp=[0,0,5]) +// zrot(180/6) +// cylinder(h=20, d=10/cos(180/6)+0.01, $fn=6); +// } +module grid2d(size=undef, spacing=undef, cols=undef, rows=undef, stagger=false, scale=[1,1,1], in_poly=undef, orient=ORIENT_Z, align=V_CENTER) +{ + assert_in_list("stagger", stagger, [false, true, "alt"]); + scl = vmul(scalar_vec3(scale, 1), (stagger!=false? [0.5, sin(60), 0] : [1,1,0])); + if (is_def(size)) { + siz = scalar_vec3(size); + if (is_def(spacing)) { + spc = vmul(scalar_vec3(spacing), scl); + maxcols = ceil(siz[0]/spc[0]); + maxrows = ceil(siz[1]/spc[1]); + grid2d(spacing=spacing, cols=maxcols, rows=maxrows, stagger=stagger, scale=scale, in_poly=in_poly, orient=orient, align=align) children(); + } else { + spc = [siz[0]/cols, siz[1]/rows, 0]; + grid2d(spacing=spc, cols=cols, rows=rows, stagger=stagger, scale=scale, in_poly=in_poly, orient=orient, align=align) children(); + } + } else { + spc = is_array(spacing)? spacing : vmul(scalar_vec3(spacing), scl); + bounds = is_def(in_poly)? pointlist_bounds(in_poly) : undef; + bnds = is_def(bounds)? [for (a=[0:1]) 2*max(vabs([ for (i=[0,1]) bounds[i][a] ]))+1 ] : undef; + mcols = is_def(cols)? cols : (is_def(spc) && is_def(bnds))? quantup(ceil(bnds[0]/spc[0])-1, 4)+1 : undef; + mrows = is_def(rows)? rows : (is_def(spc) && is_def(bnds))? quantup(ceil(bnds[1]/spc[1])-1, 4)+1 : undef; + siz = vmul(spc, [mcols-1, mrows-1, 0]); + staggermod = (stagger == "alt")? 1 : 0; + if (stagger == false) { + orient_and_align(siz, orient, align) { + for (row = [0:mrows-1]) { + for (col = [0:mcols-1]) { + pos = [col*spc[0], row*spc[1]] - point2d(siz/2); + if (!is_def(in_poly) || point_in_polygon(pos, in_poly)>=0) { + $col = col; + $row = row; + $pos = pos; + translate(pos) rot(orient,reverse=true) children(); + } + } + } + } + } else { + // stagger == true or stagger == "alt" + orient_and_align(siz, orient, align) { + cols1 = ceil(mcols/2); + cols2 = mcols - cols1; + for (row = [0:mrows-1]) { + rowcols = ((row%2) == staggermod)? cols1 : cols2; + if (rowcols > 0) { + for (col = [0:rowcols-1]) { + rowdx = (row%2 != staggermod)? spc[0] : 0; + pos = [2*col*spc[0]+rowdx, row*spc[1]] - point2d(siz/2); + if (!is_def(in_poly) || point_in_polygon(pos, in_poly)>=0) { + $col = col * 2 + ((row%2!=staggermod)? 1 : 0); + $row = row; + $pos = pos; + translate(pos) rot(orient,reverse=true) children(); + } + } + } + } } } } @@ -229,118 +1099,872 @@ module orient_and_align(size, orient=ORIENT_Z, align=V_ZERO) { -// Scales children by the given factor in the X axis. -// Example: -// xscale(3) sphere(r=100, center=true); -module xscale(x) {scale([x,1,1]) children();} - - -// Scales children by the given factor in the Y axis. -// Example: -// yscale(3) sphere(r=100, center=true); -module yscale(y) {scale([1,y,1]) children();} - - -// Scales children by the given factor in the Z axis. -// Example: -// zscale(3) sphere(r=100, center=true); -module zscale(z) {scale([1,1,z]) children();} - - -// Mirrors the children along the X axis, kind of like xscale(-1) -// Example: -// xflip() rotate([-30,30]) cylinder(d=10, h=40, center=false); -module xflip() mirror([1,0,0]) children(); - - -// Mirrors the children along the Y axis, kind of like yscale(-1) -// Example: -// yflip() rotate([-30,30]) cylinder(d=10, h=40, center=false); -module yflip() mirror([0,1,0]) children(); - - -// Mirrors the children along the Z axis, kind of like zscale(-1) -// Example: -// zflip() rotate([-30,30]) cylinder(d=10, h=40, center=false); -module zflip() mirror([0,0,1]) children(); - - -// Skews children on the X-Y plane, keeping constant in Z. -// xa = skew angle towards the X direction. -// ya = skew angle towards the Y direction. +// Module: grid3d() +// +// Description: +// Makes a 3D grid of duplicate children. +// +// Usage: +// grid3d(n, spacing) ... +// grid3d(n=[Xn,Yn,Zn], spacing=[dX,dY,dZ]) ... +// grid3d([xa], [ya], [za]) ... +// +// Arguments: +// xa = array or range of X-axis values to offset by. (Default: [0]) +// ya = array or range of Y-axis values to offset by. (Default: [0]) +// za = array or range of Z-axis values to offset by. (Default: [0]) +// n = Optional number of copies to have per axis. +// spacing = spacing of copies per axis. Use with `n`. +// +// Side Effect: +// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. +// `$idx` is set to the [Xidx,Yidx,Zidx] index values of each child copy, when using `count` and `n`. +// +// Examples(FlatSpin): +// grid3d(xa=[0:25:50],ya=[0,40],za=[-20:40:20]) sphere(r=5); +// grid3d(n=[3, 4, 2], spacing=[60, 50, 40]) sphere(r=10); // Examples: -// skew_xy(xa=15) cube(size=10); -// skew_xy(xa=30, ya=15) cube(size=10); -module skew_xy(xa=0, ya=0) +// grid3d(ya=[-60:40:60],za=[0,70]) sphere(r=10); +// grid3d(n=3, spacing=30) sphere(r=10); +// grid3d(n=[3, 1, 2], spacing=30) sphere(r=10); +// grid3d(n=[3, 4], spacing=[80, 60]) sphere(r=10); +// Examples: +// grid3d(n=[10, 10, 10], spacing=50) color($idx/9) cube(50, center=true); +module grid3d(xa=[0], ya=[0], za=[0], n=undef, spacing=undef) { - multmatrix(m = [ - [1, 0, tan(xa), 0], - [0, 1, tan(ya), 0], - [0, 0, 1, 0], - [0, 0, 0, 1] - ]) { - children(); + n = scalar_vec3(n, 1); + spacing = scalar_vec3(spacing, undef); + if (is_def(n) && is_def(spacing)) { + for (xi = [0:n.x-1]) { + for (yi = [0:n.y-1]) { + for (zi = [0:n.z-1]) { + $idx = [xi,yi,zi]; + $pos = vmul(spacing, $idx - (n-[1,1,1])/2); + translate($pos) children(); + } + } + } + } else { + for (xoff = xa, yoff = ya, zoff = za) { + $pos = [xoff, yoff, zoff]; + translate($pos) children(); + } } } -module zskew(xa=0,ya=0) skew_xy(xa=xa,ya=ya) children(); -// Skews children on the Y-Z plane, keeping constant in X. -// ya = skew angle towards the Y direction. -// za = skew angle towards the Z direction. -// Examples: -// skew_yz(ya=15) cube(size=10); -// skew_yz(ya=30, za=15) cube(size=10); -module skew_yz(ya=0, za=0) + +// Module: grid_of() +// Status: DEPRECATED, use `grid3d()` instead. +// +// Description: +// Makes a 3D grid of duplicate children. +// +// Usage: +// grid_of(n, spacing) ... +// grid_of(n=[Xn,Yn,Zn], spacing=[dX,dY,dZ]) ... +// grid_of([xa], [ya], [za]) ... +// +// Arguments: +// xa = array or range of X-axis values to offset by. (Default: [0]) +// ya = array or range of Y-axis values to offset by. (Default: [0]) +// za = array or range of Z-axis values to offset by. (Default: [0]) +// n = Optional number of copies to have per axis. +// spacing = spacing of copies per axis. Use with `n`. +// +// Side Effect: +// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. +// `$idx` is set to the [Xidx,Yidx,Zidx] index values of each child copy, when using `count` and `n`. +module grid_of(xa=[0], ya=[0], za=[0], count=undef, spacing=undef) { - multmatrix(m = [ - [1, 0, 0, 0], - [tan(ya), 1, 0, 0], - [tan(za), 0, 1, 0], - [0, 0, 0, 1] - ]) { - children(); - } + deprecate("grid_of()", "grid3d()"); + grid3d(xa=xa, ya=ya, za=za, n=count, spacing=spacing) children(); } -module xskew(ya=0,za=0) skew_yz(ya=ya,za=za) children(); - - -// Skews children on the X-Z plane, keeping constant in Y. -// xa = skew angle towards the X direction. -// za = skew angle towards the Z direction. -// Examples: -// skew_xz(xa=15) cube(size=10); -// skew_xz(xa=-15, za=15) cube(size=10); -module skew_xz(xa=0, za=0) -{ - multmatrix(m = [ - [1, tan(xa), 0, 0], - [0, 1, 0, 0], - [0, tan(za), 1, 0], - [0, 0, 0, 1] - ]) { - children(); - } -} -module yskew(xa=0,za=0) skew_xz(xa=xa,za=za) children(); ////////////////////////////////////////////////////////////////////// -// Mutators. +// Section: Rotational Distributors ////////////////////////////////////////////////////////////////////// -// Performs hull operations between consecutive pairs of children, -// then unions all of the hull results. +// Module: rot_copies() +// +// Description: +// Given a number of XYZ rotation angles, or a list of angles and an axis `v`, +// rotates copies of the children to each of those angles. +// +// Usage: +// rot_copies(rots, [cp], [sa], [delta], [subrot]) ... +// rot_copies(rots, v, [cp], [sa], [delta], [subrot]) ... +// rot_copies(n, [v], [cp], [sa], [delta], [subrot]) ... +// +// Arguments: +// rots = A list of [X,Y,Z] rotation angles in degrees. If `v` is given, this will be a list of scalar angles in degrees to rotate around `v`. +// v = If given, this is the vector to rotate around. +// cp = Centerpoint to rotate around. +// n = Optional number of evenly distributed copies, rotated around the ring. +// sa = Starting angle, in degrees. For use with `n`. Angle is in degrees counter-clockwise. +// delta = [X,Y,Z] amount to move away from cp before rotating. Makes rings of copies. +// subrot = If false, don't sub-rotate children as they are copied around the ring. +// +// Side Effects: +// `$ang` is set to the rotation angle (or XYZ rotation triplet) of each child copy, and can be used to modify each child individually. +// `$idx` is set to the index value of each child copy. +// +// Example: +// #cylinder(h=20, r1=5, r2=0); +// rot_copies([[45,0,0],[0,45,90],[90,-45,270]]) cylinder(h=20, r1=5, r2=0); +// +// Example: +// rot_copies([45, 90, 135], v=V_DOWN+V_BACK) +// yrot(90) cylinder(h=20, r1=5, r2=0); +// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0); +// +// Example: +// rot_copies(n=6, v=V_DOWN+V_BACK) +// yrot(90) cylinder(h=20, r1=5, r2=0); +// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0); +// +// Example: +// rot_copies(n=6, v=V_DOWN+V_BACK, delta=[10,0,0]) +// yrot(90) cylinder(h=20, r1=5, r2=0); +// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0); +// +// Example: +// rot_copies(n=6, v=V_UP+V_FWD, delta=[10,0,0], sa=45) +// yrot(90) cylinder(h=20, r1=5, r2=0); +// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0); +// +// Example: +// rot_copies(n=6, v=V_DOWN+V_BACK, delta=[20,0,0], subrot=false) +// yrot(90) cylinder(h=20, r1=5, r2=0); +// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0); +module rot_copies(rots=[], v=undef, cp=[0,0,0], count=undef, n=undef, sa=0, offset=0, delta=[0,0,0], subrot=true) +{ + cnt = first_defined([count, n]); + sang = sa + offset; + angs = is_def(cnt)? (cnt<=0? [] : [for (i=[0:cnt-1]) i/cnt*360+sang]) : rots; + if (cp != [0,0,0]) { + translate(cp) rot_copies(rots=rots, v=v, n=cnt, sa=sang, delta=delta, subrot=subrot) children(); + } else if (subrot) { + for ($idx = [0:len(angs)-1]) { + $ang = angs[$idx]; + rotate(a=$ang,v=v) translate(delta) rot(a=sang,v=v,reverse=true) children(); + } + } else { + for ($idx = [0:len(angs)-1]) { + $ang = angs[$idx]; + rotate(a=$ang,v=v) translate(delta) rot(a=$ang,v=v,reverse=true) children(); + } + } +} + + +// Module: xrot_copies() +// +// Description: +// Given an array of angles, rotates copies of the children +// to each of those angles around the X axis. +// +// Usage: +// xrot_copies(rots, [r], [cp], [sa], [subrot]) ... +// xrot_copies(n, [r], [cp], [sa], [subrot]) ... +// +// Arguments: +// rots = Optional array of rotation angles, in degrees, to make copies at. +// cp = Centerpoint to rotate around. +// n = Optional number of evenly distributed copies to be rotated around the ring. +// sa = Starting angle, in degrees. For use with `n`. Angle is in degrees counter-clockwise from Y+, when facing the origin from X+. First unrotated copy is placed at that angle. +// r = Radius to move children back, away from cp, before rotating. Makes rings of copies. +// subrot = If false, don't sub-rotate children as they are copied around the ring. +// +// Side Effects: +// `$ang` is set to the rotation angle of each child copy, and can be used to modify each child individually. +// +// Example: +// xrot_copies([180, 270, 315]) +// cylinder(h=20, r1=5, r2=0); +// color("red",0.333) cylinder(h=20, r1=5, r2=0); +// +// Example: +// xrot_copies(n=6) +// cylinder(h=20, r1=5, r2=0); +// color("red",0.333) cylinder(h=20, r1=5, r2=0); +// +// Example: +// xrot_copies(n=6, r=10) +// xrot(-90) cylinder(h=20, r1=5, r2=0); +// color("red",0.333) xrot(-90) cylinder(h=20, r1=5, r2=0); +// +// Example: +// xrot_copies(n=6, r=10, sa=45) +// xrot(-90) cylinder(h=20, r1=5, r2=0); +// color("red",0.333) xrot(-90) cylinder(h=20, r1=5, r2=0); +// +// Example: +// xrot_copies(n=6, r=20, subrot=false) +// xrot(-90) cylinder(h=20, r1=5, r2=0, center=true); +// color("red",0.333) xrot(-90) cylinder(h=20, r1=5, r2=0, center=true); +module xrot_copies(rots=[], cp=[0,0,0], n=undef, count=undef, sa=0, offset=0, r=0, subrot=true) +{ + cnt = first_defined([count, n]); + sang = sa + offset; + rot_copies(rots=rots, v=V_RIGHT, cp=cp, n=cnt, sa=sang, delta=[0, r, 0], subrot=subrot) children(); +} + + +// Module: yrot_copies() +// +// Description: +// Given an array of angles, rotates copies of the children +// to each of those angles around the Y axis. +// +// Usage: +// yrot_copies(rots, [r], [cp], [sa], [subrot]) ... +// yrot_copies(n, [r], [cp], [sa], [subrot]) ... +// +// Arguments: +// rots = Optional array of rotation angles, in degrees, to make copies at. +// cp = Centerpoint to rotate around. +// n = Optional number of evenly distributed copies to be rotated around the ring. +// sa = Starting angle, in degrees. For use with `n`. Angle is in degrees counter-clockwise from X-, when facing the origin from Y+. +// r = Radius to move children left, away from cp, before rotating. Makes rings of copies. +// subrot = If false, don't sub-rotate children as they are copied around the ring. +// +// Side Effects: +// `$ang` is set to the rotation angle of each child copy, and can be used to modify each child individually. +// +// Example: +// yrot_copies([180, 270, 315]) +// cylinder(h=20, r1=5, r2=0); +// color("red",0.333) cylinder(h=20, r1=5, r2=0); +// +// Example: +// yrot_copies(n=6) +// cylinder(h=20, r1=5, r2=0); +// color("red",0.333) cylinder(h=20, r1=5, r2=0); +// +// Example: +// yrot_copies(n=6, r=10) +// yrot(-90) cylinder(h=20, r1=5, r2=0); +// color("red",0.333) yrot(-90) cylinder(h=20, r1=5, r2=0); +// +// Example: +// yrot_copies(n=6, r=10, sa=45) +// yrot(-90) cylinder(h=20, r1=5, r2=0); +// color("red",0.333) yrot(-90) cylinder(h=20, r1=5, r2=0); +// +// Example: +// yrot_copies(n=6, r=20, subrot=false) +// yrot(-90) cylinder(h=20, r1=5, r2=0, center=true); +// color("red",0.333) yrot(-90) cylinder(h=20, r1=5, r2=0, center=true); +module yrot_copies(rots=[], cp=[0,0,0], n=undef, count=undef, sa=0, offset=0, r=0, subrot=true) +{ + cnt = first_defined([count, n]); + sang = sa + offset; + rot_copies(rots=rots, v=V_BACK, cp=cp, n=cnt, sa=sang, delta=[-r, 0, 0], subrot=subrot) children(); +} + + +// Module: zrot_copies() +// +// Description: +// Given an array of angles, rotates copies of the children +// to each of those angles around the Z axis. +// +// Usage: +// zrot_copies(rots, [r], [cp], [sa], [subrot]) ... +// zrot_copies(n, [r], [cp], [sa], [subrot]) ... +// +// Arguments: +// rots = Optional array of rotation angles, in degrees, to make copies at. +// cp = Centerpoint to rotate around. +// n = Optional number of evenly distributed copies to be rotated around the ring. +// sa = Starting angle, in degrees. For use with `n`. Angle is in degrees counter-clockwise from X+, when facing the origin from Z+. +// r = Radius to move children right, away from cp, before rotating. Makes rings of copies. +// subrot = If false, don't sub-rotate children as they are copied around the ring. +// +// Side Effects: +// `$ang` is set to the rotation angle of each child copy, and can be used to modify each child individually. +// +// Example: +// zrot_copies([180, 270, 315]) +// yrot(90) cylinder(h=20, r1=5, r2=0); +// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0); +// +// Example: +// zrot_copies(n=6) +// yrot(90) cylinder(h=20, r1=5, r2=0); +// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0); +// +// Example: +// zrot_copies(n=6, r=10) +// yrot(90) cylinder(h=20, r1=5, r2=0); +// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0); +// +// Example: +// zrot_copies(n=6, r=20, sa=45) +// yrot(90) cylinder(h=20, r1=5, r2=0, center=true); +// color("red",0.333) yrot(90) cylinder(h=20, r1=5, r2=0, center=true); +// +// Example: +// zrot_copies(n=6, r=20, subrot=false) +// yrot(-90) cylinder(h=20, r1=5, r2=0, center=true); +// color("red",0.333) yrot(-90) cylinder(h=20, r1=5, r2=0, center=true); +module zrot_copies(rots=[], cp=[0,0,0], n=undef, count=undef, sa=0, offset=0, r=0, subrot=true) +{ + cnt = first_defined([count, n]); + sang = sa + offset; + rot_copies(rots=rots, v=V_UP, cp=cp, n=cnt, sa=sang, delta=[r, 0, 0], subrot=subrot) children(); +} + + +// Module: xring() +// +// Description: +// Distributes `n` copies of the given children on a circle of radius `r` +// around the X axis. If `rot` is true, each copy is rotated in place to keep +// the same side towards the center. The first, unrotated copy will be at the +// starting angle `sa`. +// +// Usage: +// xring(r, n, [sa], [cp], [rot]) ... +// +// Arguments: +// n = number of copies of children to distribute around the circle. (Default: 2) +// r = radius of ring to distribute children around. (Default: 0) +// sa = start angle for first (unrotated) copy. (Default: 0) +// rot = If true, rotate each copy to keep the same side towards the center of the ring. Default: true. +// +// Side Effects: +// `$ang` is set to the rotation angle of each child copy, and can be used to modify each child individually. +// `$idx` is set to the index value of each child copy. +// +// Examples: +// xring(n=6, r=10) xrot(-90) cylinder(h=20, r1=5, r2=0); +// xring(n=6, r=10, sa=45) xrot(-90) cylinder(h=20, r1=5, r2=0); +// xring(n=6, r=20, rot=false) cylinder(h=20, r1=6, r2=0, center=true); +module xring(n=2, r=0, sa=0, cp=[0,0,0], rot=true) +{ + xrot_copies(count=n, sa=sa, r=r, cp=cp, subrot=rot) children(); +} + + +// Module: yring() +// +// Description: +// Distributes `n` copies of the given children on a circle of radius `r` +// around the Y axis. If `rot` is true, each copy is rotated in place to keep +// the same side towards the center. The first, unrotated copy will be at the +// starting angle `sa`. +// +// Usage: +// yring(r, n, [sa], [cp], [rot]) ... +// +// Arguments: +// n = number of copies of children to distribute around the circle. (Default: 2) +// r = radius of ring to distribute children around. (Default: 0) +// sa = start angle for first (unrotated) copy. (Default: 0) +// rot = If true, rotate each copy to keep the same side towards the center of the ring. Default: true. +// +// Side Effects: +// `$ang` is set to the rotation angle of each child copy, and can be used to modify each child individually. +// `$idx` is set to the index value of each child copy. +// +// Examples: +// yring(n=6, r=10) yrot(-90) cylinder(h=20, r1=5, r2=0); +// yring(n=6, r=10, sa=45) yrot(-90) cylinder(h=20, r1=5, r2=0); +// yring(n=6, r=20, rot=false) cylinder(h=20, r1=6, r2=0, center=true); +module yring(n=2, r=0, sa=0, cp=[0,0,0], rot=true) +{ + yrot_copies(count=n, sa=sa, r=r, cp=cp, subrot=rot) children(); +} + + +// Module: zring() +// +// Description: +// Distributes `n` copies of the given children on a circle of radius `r` +// around the Z axis. If `rot` is true, each copy is rotated in place to keep +// the same side towards the center. The first, unrotated copy will be at the +// starting angle `sa`. +// +// Usage: +// zring(r, n, [sa], [cp], [rot]) ... +// +// Arguments: +// n = Number of copies of children to distribute around the circle. (Default: 2) +// r = Radius of ring to distribute children around. (Default: 0) +// sa = Start angle for first (unrotated) copy. (Default: 0) +// cp = Centerpoint of the ring. +// rot = If true, rotate each copy to keep the same side towards the center of the ring. Default: true. +// +// Side Effects: +// `$ang` is set to the relative angle from `cp` of each child copy, and can be used to modify each child individually. +// `$idx` is set to the index value of each child copy. +// +// Examples: +// zring(n=6, r=10) yrot(90) cylinder(h=20, r1=5, r2=0); +// zring(n=6, r=10, sa=45) yrot(90) cylinder(h=20, r1=5, r2=0); +// zring(n=6, r=20, rot=false) yrot(90) cylinder(h=20, r1=6, r2=0, center=true); +module zring(n=2, r=0, sa=0, cp=[0,0,0], rot=true) +{ + zrot_copies(count=n, sa=sa, r=r, cp=cp, subrot=rot) children(); +} + + +// Module: arc_of() +// +// Description: +// Evenly distributes n duplicate children around an ovoid arc on the XY plane. +// +// Usage: +// arc_of(r|d, n, [sa], [ea], [rot] +// arc_of(rx|dx, ry|dy, n, [sa], [ea], [rot] +// +// Arguments: +// n = number of copies to distribute around the circle. (Default: 6) +// r = radius of circle (Default: 1) +// rx = radius of ellipse on X axis. Used instead of r. +// ry = radius of ellipse on Y axis. Used instead of r. +// d = diameter of circle. (Default: 2) +// dx = diameter of ellipse on X axis. Used instead of d. +// dy = diameter of ellipse on Y axis. Used instead of d. +// rot = whether to rotate the copied children. (Default: false) +// sa = starting angle. (Default: 0.0) +// ea = ending angle. Will distribute copies CCW from sa to ea. (Default: 360.0) +// +// Side Effects: +// `$ang` is set to the rotation angle of each child copy, and can be used to modify each child individually. +// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. +// `$idx` is set to the index value of each child copy. +// +// Example: +// #cube(size=[10,3,3],center=true); +// arc_of(d=40, n=5) cube(size=[10,3,3],center=true); +// +// Example: +// #cube(size=[10,3,3],center=true); +// arc_of(d=40, n=5, sa=45, ea=225) cube(size=[10,3,3],center=true); +// +// Example: +// #cube(size=[10,3,3],center=true); +// arc_of(r=15, n=8, rot=false) cube(size=[10,3,3],center=true); +// +// Example: +// #cube(size=[10,3,3],center=true); +// arc_of(rx=20, ry=10, n=8) cube(size=[10,3,3],center=true); +module arc_of( + n=6, + r=undef, rx=undef, ry=undef, + d=undef, dx=undef, dy=undef, + sa=0, ea=360, + rot=true +) { + rx = get_radius(rx, r, dx, d, 1); + ry = get_radius(ry, r, dy, d, 1); + sa = posmod(sa, 360); + ea = posmod(ea, 360); + n = (abs(ea-sa)<0.01)?(n+1):n; + delt = (((ea<=sa)?360.0:0)+ea-sa)/(n-1); + for ($idx = [0:n-1]) { + $ang = sa + ($idx * delt); + $pos =[rx*cos($ang), ry*sin($ang), 0]; + translate($pos) { + zrot(rot? atan2(ry*sin($ang), rx*cos($ang)) : 0) { + children(); + } + } + } +} + + + +// Module: ovoid_spread() +// +// Description: +// Spreads children semi-evenly over the surface of a sphere. +// +// Usage: +// ovoid_spread(r|d, n, [cone_ang], [scale], [perp]) ... +// +// Arguments: +// r = Radius of the sphere to distribute over +// d = Diameter of the sphere to distribute over +// n = How many copies to evenly spread over the surface. +// cone_ang = Angle of the cone, in degrees, to limit how much of the sphere gets covered. For full sphere coverage, use 180. Measured pre-scaling. Default: 180 +// scale = The [X,Y,Z] scaling factors to reshape the sphere being covered. +// perp = If true, rotate children to be perpendicular to the sphere surface. Default: true +// +// Side Effects: +// `$pos` is set to the relative post-scaled centerpoint of each child copy, and can be used to modify each child individually. +// `$theta` is set to the theta angle of the child from the center of the sphere. +// `$phi` is set to the pre-scaled phi angle of the child from the center of the sphere. +// `$rad` is set to the pre-scaled radial distance of the child from the center of the sphere. +// `$idx` is set to the index number of each child being copied. +// +// Example: +// ovoid_spread(n=250, d=100, cone_ang=45, scale=[3,3,1]) +// cylinder(d=10, h=10, center=false); +// +// Example: +// ovoid_spread(n=500, d=100, cone_ang=180) +// color(normalize(point3d(vabs($pos)))) +// cylinder(d=8, h=10, center=false); +module ovoid_spread(r=undef, d=undef, n=100, cone_ang=90, scale=[1,1,1], perp=true) +{ + r = get_radius(r=r, d=d, dflt=50); + cnt = ceil(n / (cone_ang/180)); + + // Calculate an array of [theta,phi] angles for `n` number of + // points, almost evenly spaced across the surface of a sphere. + // This approximation is based on the golden spiral method. + theta_phis = [for (x=[0:n-1]) [180*(1+sqrt(5))*(x+0.5)%360, acos(1-2*(x+0.5)/cnt)]]; + + for ($idx = [0:len(theta_phis)-1]) { + tp = theta_phis[$idx]; + xyz = spherical_to_xyz(r, tp[0], tp[1]); + $pos = vmul(xyz,scale); + $theta = tp[0]; + $phi = tp[1]; + $rad = r; + translate($pos) { + if (perp) { + rot(from=V_UP, to=xyz) children(); + } else { + children(); + } + } + } +} + + + +////////////////////////////////////////////////////////////////////// +// Section: Reflectional Distributors +////////////////////////////////////////////////////////////////////// + + +// Module: mirror_copy() +// +// Description: +// Makes a copy of the children, mirrored across the given plane. +// +// Usage: +// mirror_copy(v, [cp], [offset]) ... +// +// Arguments: +// v = The normal vector of the plane to mirror across. +// offset = distance to offset away from the plane. +// cp = A point that lies on the mirroring plane. +// +// Side Effects: +// `$orig` is true for the original instance of children. False for the copy. +// `$idx` is set to the index value of each copy. +// +// Example: +// mirror_copy([1,-1,0]) zrot(-45) yrot(90) cylinder(d1=10, d2=0, h=20); +// color("blue",0.25) zrot(-45) cube([0.01,15,15], center=true); +// +// Example: +// mirror_copy([1,1,0], offset=5) rot(a=90,v=[-1,1,0]) cylinder(d1=10, d2=0, h=20); +// color("blue",0.25) zrot(45) cube([0.01,15,15], center=true); +// +// Example: +// mirror_copy(V_UP+V_BACK, cp=[0,-5,-5]) rot(from=V_UP, to=V_BACK+V_UP) cylinder(d1=10, d2=0, h=20); +// color("blue",0.25) translate([0,-5,-5]) rot(from=V_UP, to=V_BACK+V_UP) cube([15,15,0.01], center=true); +module mirror_copy(v=[0,0,1], offset=0, cp=[0,0,0]) +{ + nv = v/norm(v); + off = nv*offset; + if (cp == [0,0,0]) { + translate(off) { + $orig = true; + $idx = 0; + children(); + } + mirror(nv) translate(off) { + $orig = false; + $idx = 1; + children(); + } + } else { + translate(off) children(); + translate(cp) mirror(nv) translate(-cp) translate(off) children(); + } +} + + +// Module: xflip_copy() +// +// Description: +// Makes a copy of the children, mirrored across the X axis. +// +// Usage: +// xflip_copy([cp], [offset]) ... +// +// Arguments: +// offset = Distance to offset children right, before copying. +// cp = A point that lies on the mirroring plane. +// +// Side Effects: +// `$orig` is true for the original instance of children. False for the copy. +// `$idx` is set to the index value of each copy. +// +// Example: +// xflip_copy() yrot(90) cylinder(h=20, r1=4, r2=0); +// color("blue",0.25) cube([0.01,15,15], center=true); +// +// Example: +// xflip_copy(offset=5) yrot(90) cylinder(h=20, r1=4, r2=0); +// color("blue",0.25) cube([0.01,15,15], center=true); +// +// Example: +// xflip_copy(cp=[-5,0,0]) yrot(90) cylinder(h=20, r1=4, r2=0); +// color("blue",0.25) left(5) cube([0.01,15,15], center=true); +module xflip_copy(offset=0, cp=[0,0,0]) +{ + mirror_copy(v=[1,0,0], offset=offset, cp=cp) children(); +} + + +// Module: yflip_copy() +// +// Description: +// Makes a copy of the children, mirrored across the Y axis. +// +// Usage: +// yflip_copy([cp], [offset]) ... +// +// Arguments: +// offset = Distance to offset children back, before copying. +// cp = A point that lies on the mirroring plane. +// +// Side Effects: +// `$orig` is true for the original instance of children. False for the copy. +// `$idx` is set to the index value of each copy. +// +// Example: +// yflip_copy() xrot(-90) cylinder(h=20, r1=4, r2=0); +// color("blue",0.25) cube([15,0.01,15], center=true); +// +// Example: +// yflip_copy(offset=5) xrot(-90) cylinder(h=20, r1=4, r2=0); +// color("blue",0.25) cube([15,0.01,15], center=true); +// +// Example: +// yflip_copy(cp=[0,-5,0]) xrot(-90) cylinder(h=20, r1=4, r2=0); +// color("blue",0.25) fwd(5) cube([15,0.01,15], center=true); +module yflip_copy(offset=0, cp=[0,0,0]) +{ + mirror_copy(v=[0,1,0], offset=offset, cp=cp) children(); +} + + +// Module: zflip_copy() +// +// Description: +// Makes a copy of the children, mirrored across the Z axis. +// +// Usage: +// zflip_copy([cp], [offset]) ... +// `$idx` is set to the index value of each copy. +// +// Arguments: +// offset = Distance to offset children up, before copying. +// cp = A point that lies on the mirroring plane. +// +// Side Effects: +// `$orig` is true for the original instance of children. False for the copy. +// +// Example: +// zflip_copy() cylinder(h=20, r1=4, r2=0); +// color("blue",0.25) cube([15,15,0.01], center=true); +// +// Example: +// zflip_copy(offset=5) cylinder(h=20, r1=4, r2=0); +// color("blue",0.25) cube([15,15,0.01], center=true); +// +// Example: +// zflip_copy(cp=[0,0,-5]) cylinder(h=20, r1=4, r2=0); +// color("blue",0.25) down(5) cube([15,15,0.01], center=true); +module zflip_copy(offset=0, cp=[0,0,0]) +{ + mirror_copy(v=[0,0,1], offset=offset, cp=cp) children(); +} + + +////////////////////////////////////////////////////////////////////// +// Section: Mutators +////////////////////////////////////////////////////////////////////// + + +// Module: half_of() +// +// Usage: +// half_of(v, [cp], [s]) ... +// +// Description: +// Slices an object at a cut plane, and masks away everything that is on one side. +// +// Arguments: +// v = Normal of plane to slice at. Keeps everything on the side the normal points to. Default: [0,0,1] (V_UP) +// cp = A point that is on the cut plane. This can be used to shift where it slices the object at. 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. Default: 100 +// +// Examples: +// half_of(V_DOWN+V_BACK, cp=[0,-10,0]) cylinder(h=40, r1=10, r2=0, center=false); +// half_of(V_DOWN+V_LEFT, s=200) sphere(d=150); +module half_of(v=V_UP, cp=[0,0,0], s=100) +{ + if (cp != [0,0,0]) { + translate(cp) half_of(v=v, s=s) translate(-cp) children(); + } else { + difference() { + children(); + rot(from=V_UP, to=-v) { + up(s/2) cube(s, center=true); + } + } + } +} + + +// Module: top_half() +// +// Usage: +// top_half([cp], [s]) ... +// +// Description: +// Slices an object at a horizontal X-Y cut plane, and masks away everything that is below it. +// +// Arguments: +// cp = A point that is on the cut plane. This can be used to shift where it slices the object at. 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. Default: 100 +// +// Examples(Spin): +// top_half() sphere(r=20); +// top_half(cp=[0,0,-5]) sphere(r=20); +module top_half(s=100, cp=[0,0,0]) translate(cp) difference() {translate(-cp) children(); down(s/2) cube(s, center=true);} + + + +// Module: bottom_half() +// +// Usage: +// bottom_half([cp], [s]) ... +// +// Description: +// Slices an object at a horizontal X-Y cut plane, and masks away everything that is above it. +// +// Arguments: +// cp = A point that is on the cut plane. This can be used to shift where it slices the object at. 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. Default: 100 +// +// Examples: +// bottom_half() sphere(r=20); +// bottom_half(cp=[0,0,10]) sphere(r=20); +module bottom_half(s=100, cp=[0,0,0]) translate(cp) difference() {translate(-cp) children(); up(s/2) cube(s, center=true);} + + + +// Module: left_half() +// +// Usage: +// left_half([cp], [s]) ... +// +// Description: +// Slices an object at a vertical Y-Z cut plane, and masks away everything that is right of it. +// +// Arguments: +// cp = A point that is on the cut plane. This can be used to shift where it slices the object at. 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. Default: 100 +// +// Examples: +// left_half() sphere(r=20); +// left_half(cp=[8,0,0]) sphere(r=20); +module left_half(s=100, cp=[0,0,0]) translate(cp) difference() {translate(-cp) children(); right(s/2) cube(s, center=true);} + + + +// Module: right_half() +// +// Usage: +// right_half([cp], [s]) ... +// +// Description: +// Slices an object at a vertical Y-Z cut plane, and masks away everything that is left of it. +// +// Arguments: +// cp = A point that is on the cut plane. This can be used to shift where it slices the object at. 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. Default: 100 +// +// Examples(FlatSpin): +// right_half() sphere(r=20); +// right_half(cp=[-5,0,0]) sphere(r=20); +module right_half(s=100, cp=[0,0,0]) translate(cp) difference() {translate(-cp) children(); left(s/2) cube(s, center=true);} + + + +// Module: front_half() +// +// Usage: +// front_half([cp], [s]) ... +// +// Description: +// Slices an object at a vertical X-Z cut plane, and masks away everything that is behind it. +// +// Arguments: +// cp = A point that is on the cut plane. This can be used to shift where it slices the object at. 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. Default: 100 +// +// Examples(FlatSpin): +// front_half() sphere(r=20); +// front_half(cp=[0,5,0]) sphere(r=20); +module front_half(s=100, cp=[0,0,0]) translate(cp) difference() {translate(-cp) children(); back(s/2) cube(s, center=true);} + + + +// Module: back_half() +// +// Usage: +// back_half([cp], [s]) ... +// +// Description: +// Slices an object at a vertical X-Z cut plane, and masks away everything that is in front of it. +// +// Arguments: +// cp = A point that is on the cut plane. This can be used to shift where it slices the object at. 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. Default: 100 +// +// Examples: +// back_half() sphere(r=20); +// back_half(cp=[0,-10,0]) sphere(r=20); +module back_half(s=100, cp=[0,0,0]) translate(cp) difference() {translate(-cp) children(); fwd(s/2) cube(s, center=true);} + + + +// 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. +// // Example: // chain_hull() { // cube(5, center=true); -// translate([50, 0, 0]) sphere(d=15); -// translate([100, 50, 0]) cylinder(d=10, h=20); -// translate([100, 100, 0]) cube([10,20,20], center=false); +// 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); // } -module chain_hull() { +module chain_hull() +{ union() { if ($children == 1) { children(); @@ -356,378 +1980,153 @@ module chain_hull() { } +// Module: extrude_arc() +// +// Description: +// Extrudes 2D shapes around a partial circle arc, with optional rounded caps. +// This is mostly useful for backwards compatability with older OpenSCAD versions +// without the `angle` argument in rotate_extrude. +// +// Usage: +// extrude_arc(arc, r|d, [sa], [caps], [orient], [align], [masksize]) ... +// +// Arguments: +// arc = Number of degrees to traverse. +// sa = Start angle in degrees. +// r = Radius of arc. +// d = Diameter of arc. +// orient = The axis to align to. Use `ORIENT_` constants from `constants.scad` +// align = The side of the origin the part should be aligned with. Use `V_` constants from `constants.scad` +// masksize = size of mask used to clear unused part of circle arc. should be larger than height or width of 2D shapes to extrude. +// caps = If true, spin the 2D shapes to make rounded caps the ends of the arc. +// convexity = Max number of times a ray passes through the 2D shape's walls. +// +// Example: +// pts=[[-5/2, -5], [-5/2, 0], [-5/2-3, 5], [5/2+3, 5], [5/2, 0], [5/2, -5]]; +// #polygon(points=pts); +// extrude_arc(arc=270, sa=45, r=40, caps=true, convexity=4, $fa=2, $fs=2) { +// polygon(points=pts); +// } +module extrude_arc(arc=90, sa=0, r=undef, d=undef, orient=ORIENT_Z, align=V_CENTER, masksize=100, caps=false, convexity=4) +{ + eps = 0.001; + r = get_radius(r=r, d=d, dflt=100); + orient_and_align([2*r, 2*r, 0], orient, align) { + zrot(sa) { + if (caps) { + place_copies([[r,0,0], cylindrical_to_xyz(r, arc, 0)]) { + rotate_extrude(convexity=convexity) { + difference() { + children(); + left(masksize/2) square(masksize, center=true); + } + } + } + } + difference() { + rotate_extrude(angle=arc, convexity=convexity*2) { + right(r) { + children(); + } + } + if(version_num() < 20190000) { + maxd = r + masksize; + if (arc<180) rotate(arc) back(maxd/2) cube([2*maxd, maxd, masksize+0.1], center=true); + difference() { + fwd(maxd/2) cube([2*maxd, maxd, masksize+0.2], center=true); + if (arc>180) rotate(arc-180) back(maxd/2) cube([2*maxd, maxd, masksize+0.1], center=true); + } + } + } + } + } +} + ////////////////////////////////////////////////////////////////////// -// Duplicators and Distributers. +// Section: Miscellaneous ////////////////////////////////////////////////////////////////////// -// Makes a copy of the children, mirrored across the given plane. -// v = The normal vector of the plane to mirror across. -// offset = distance to offset away from the plane. +// Module: orient_and_align() +// +// Description: +// Takes a vertically oriented shape, and re-orients and aligns it. +// This is useful for making a custom shape available in various +// orientations and alignments without extra translate()s and rotate()s. +// Children should be vertically (Z-axis) oriented, and centered. +// Non-extremity alignment points should be named via the `alignments` arg. +// Named alignments, as well as `ALIGN_NEG`/`ALIGN_POS` are aligned pre-rotation. +// +// Usage: +// orient_and_align(size, [orient], [align], [center], [noncentered], [orig_orient], [orig_align], [alignments]) ... +// +// Arguments: +// size = The size of the part. +// orient = The axis to align to. Use ORIENT_ constants from constants.scad +// align = The side of the origin the part should be aligned with. +// center = If given, overrides `align`. If true, centers vertically. If false, `align` will be set to the value in `noncentered`. +// noncentered = The value to set `align` to if `center` == `false`. Default: `V_UP`. +// orig_orient = The original orientation of the part. Default: `ORIENT_Z`. +// orig_align = The original alignment of the part. Default: `V_CENTER`. +// alignments = A list of `["name", [X,Y,Z]]` alignment-label/offset pairs. +// // Example: -// mirror_copy([1,-1,0]) yrot(30) cylinder(h=10, r=1, center=true); -// mirror_copy([1,1,1], offset=17.32) cylinder(h=10, r=1, center=false); -// mirror_copy([1,-1,0]) yrot(30) cylinder(h=10, r=1, center=false); -module mirror_copy(v=[0,0,1], offset=0) -{ - l = sqrt(v[0]*v[0]+v[1]*v[1]+v[2]*v[2]); - nv = v/l; - off = nv*offset; - union() { - translate(off) children(); - mirror(nv) translate(off) children(); - } -} - - -// Makes a copy of the children, mirrored across the X axis. -// offset = distance to offset children along the X axis, away from the origin. -// Example: -// xflip_copy() yrot(30) cylinder(h=10, r=1, center=false); -// xflip_copy(offset=10) yrot(30) cylinder(h=10, r=1, center=false); -module xflip_copy(offset=0) {right(offset) children(); mirror([1,0,0]) right(offset) children();} - - -// Makes a copy of the children, mirrored across the Y axis. -// offset = distance to offset children along the Y axis, away from the origin. -// Example: -// yflip_copy() xrot(30) cylinder(h=10, r=1, center=false); -// yflip_copy(offset=10) xrot(30) cylinder(h=10, r=1, center=false); -module yflip_copy(offset=0) {back(offset) children(); mirror([0,1,0]) back(offset) children();} - - -// Makes a copy of the children, mirrored across the Z axis. -// offset = distance to offset children along the Z axis, away from the origin. -// Example: -// zflip_copy() yrot(30) cylinder(h=10, r=1, center=false); -// zflip_copy(offset=10) yrot(30) cylinder(h=10, r=1, center=false); -module zflip_copy(offset=0) {up(offset) children(); mirror([0,0,1]) up(offset) children();} - - -// Given a number of euller angles, rotates copies of the given children to each of those angles. -// Example: -// rot_copies(rots=[[45,0,0],[0,45,120],[90,-45,270]]) -// translate([6,0,0]) cube(size=[9,1,4], center=true); -module rot_copies(rots=[[0,0,0]]) -{ - for (rot = rots) rotate(rot) children(); -} - - -// Given an array of angles, rotates copies of the children to each of those angles around the X axis. -// rots = Optional array of angles, in degrees, to make copies at. -// count = Optional number of evenly distributed copies, rotated around a circle. -// offset = Angle offset in degrees, for use with count. -// Example: -// xrot_copies(rots=[15,30,60,120,240]) translate([0,6,0]) cube(size=[4,9,1], center=true); -// xrot_copies(count=6, offset=15) translate([0,6,0]) cube(size=[4,9,1], center=true); -module xrot_copies(rots=[0], offset=0, count=undef) -{ - if (count != undef) { - for (i = [0 : count-1]) { - a = (i / count) * 360.0; - rotate([a+offset, 0, 0]) { - children(); - } - } - } else { - for (a = rots) { - rotate([a+offset, 0, 0]) { - children(); - } - } - } -} - - -// Given an array of angles, rotates copies of the children to each of those angles around the Y axis. -// rots = Optional array of angles, in degrees, to make copies at. -// count = Optional number of evenly distributed copies, rotated around a circle. -// offset = Angle offset in degrees, for use with count. -// Example: -// yrot_copies(rots=[15,30,60,120,240]) translate([6,0,0]) cube(size=[9,4,1], center=true); -// yrot_copies(count=6, offset=15) translate([6,0,0]) cube(size=[9,4,1], center=true); -module yrot_copies(rots=[0], offset=0, count=undef) -{ - if (count != undef) { - for (i = [0 : count-1]) { - a = (i / count) * 360.0; - rotate([0, a+offset, 0]) { - children(); - } - } - } else { - for (a = rots) { - rotate([0, a+offset, 0]) { - children(); - } - } - } -} - - -// Given an array of angles, rotates copies of the children to each of those angles around the Z axis. -// rots = Optional array of angles, in degrees, to make copies at. -// count = Optional number of evenly distributed copies, rotated around a circle. -// offset = Angle offset in degrees for first copy. -// Example: -// zrot_copies(rots=[15,30,60,120,240]) translate([6,0,0]) cube(size=[9,1,4], center=true); -// zrot_copies(count=6, offset=15) translate([6,0,0]) cube(size=[9,1,4], center=true); -module zrot_copies(rots=[0], offset=0, count=undef) -{ - if (count != undef) { - for (i = [0 : count-1]) { - a = (i / count) * 360.0; - rotate([0, 0, a+offset]) { - children(); - } - } - } else { - for (a = rots) { - rotate([0, 0, a+offset]) { - children(); - } - } - } -} - - -// Makes copies of the given children at each of the given offsets. -// a = array of XYZ offset vectors. Default [[0,0,0]] -// Example: -// translate_copies([[-5,-5,0], [5,-5,0], [0,-5,7], [0,5,0]]) -// sphere(r=3,center=true); -module translate_copies(a=[[0,0,0]]) -{ - for (off = a) translate(off) children(); -} -module place_copies(a=[[0,0,0]]) {translate_copies(a) children();} - - -// Evenly distributes n duplicate children along an XYZ line. -// p1 = starting point of line. (Default: [0,0,0]) -// p2 = ending point of line. (Default: [10,0,0]) -// n = number of copies to distribute along the line. (Default: 2) -// Examples: -// line_of(p1=[0,0,0], p2=[-10,15,20], n=5) cube(size=[3,1,1],center=true); -module line_of(p1=[0,0,0], p2=[10,0,0], n=2) -{ - delta = (p2 - p1) / (n-1); - for (i = [0:n-1]) translate(p1+delta*i) children(); -} -module spread(p1,p2,n=3) {line_of(p1,p2,n) children();} - - -// Evenly distributes n duplicate children around an ovoid arc on the XY plane. -// n = number of copies to distribute around the circle. (Default: 6) -// r = radius of circle (Default: 1) -// rx = radius of ellipse on X axis. Used instead of r. -// ry = radius of ellipse on Y axis. Used instead of r. -// d = diameter of circle. (Default: 2) -// dx = diameter of ellipse on X axis. Used instead of d. -// dy = diameter of ellipse on Y axis. Used instead of d. -// rot = whether to rotate the copied children. (Default: false) -// sa = starting angle. (Default: 0.0) -// ea = ending angle. Will distribute copies CCW from sa to ea. (Default: 360.0) -// Examples: -// arc_of(d=8,n=5) -// cube(size=[3,1,1],center=true); -// arc_of(r=10,n=12,rot=true) -// cube(size=[3,1,1],center=true); -// arc_of(rx=15,ry=10,n=12,rot=true) -// cube(size=[3,1,1],center=true); -// arc_of(r=10,n=5,rot=true,sa=30.0,ea=150.0) -// cube(size=[3,1,1],center=true); -module arc_of( - n=6, - r=1, rx=undef, ry=undef, - d=undef, dx=undef, dy=undef, - sa=0.0, ea=360.0, - rot=false +// #cylinder(d=5, h=10); +// orient_and_align([5,5,10], orient=ORIENT_Y, align=V_BACK, orig_align=V_UP) cylinder(d=5, h=10); +module orient_and_align( + size=undef, orient=ORIENT_Z, align=V_CENTER, + center=undef, noncentered=ALIGN_POS, + orig_orient=ORIENT_Z, orig_align=V_CENTER, + alignments=[] ) { - r = (d != undef)? d/2 : r; - rx = (dx != undef)? dx/2 : ((rx != undef)? rx : r); - ry = (dy != undef)? dy/2 : ((ry != undef)? ry : r); - sa = ((sa % 360.0) + 360.0) % 360.0; // make 0 < ang < 360 - ea = ((ea % 360.0) + 360.0) % 360.0; // make 0 < ang < 360 - n = (abs(ea-sa)<0.01)?(n+1):n; - delt = (((ea<=sa)?360.0:0)+ea-sa)/(n-1); - for (i = [0:n-1]) { - ang = sa + (i * delt); - translate([cos(ang)*rx, sin(ang)*ry, 0]) { - zrot(rot? atan2(sin(ang)*ry,cos(ang)*rx) : 0) { - children(); - } + algn = is_def(center)? (center? V_CENTER : noncentered) : align; + if (orig_align != V_CENTER) { + orient_and_align(size=size, orient=orient, align=algn) { + translate(vmul(size/2, -orig_align)) children(); } - } -} - - -// Evenly distributes n duplicate children around a circle on the YZ plane, around the -// X axis. First moves children away from the X axis by r distance, in direction sa. -// Then copies them around the axis of rotation, for a total of n copies. If rot is -// true, each copy is rotated in place to orient to the center of rotation. -// n = number of copies of children to distribute around the circle. (Default: 2) -// r = radius of ring to distribute children around. (Default: 0) -// sa = start angle for first (unrotated) copy. (Default: 0) -// rot = if true, rotate each copy of children with respect to the center of the ring. -// Example: -// xring(n=3, r=10, sa=270) yspread(10) yrot(120) cylinder(h=10, d=1, center=false); -// xring(n=6, r=20) cylinder(d1=6, d2=0.01, h=6, center=true); -module xring(n=2,r=0,sa=0,rot=true) {if (n>0) for (i=[0:n-1]) {a=i*360/n; xrot(a+sa) back(r) xrot((rot?0:-a)-sa) children();}} - - -// Evenly distributes n duplicate children around a circle on the XZ plane, around the -// Y axis. First moves children away from the Y axis by r distance, in direction sa. -// Then copies them around the axis of rotation, for a total of n copies. If rot is -// true, each copy is rotated in place to orient to the center of rotation. -// n = number of copies of children to distribute around the circle. (Default: 2) -// r = radius of ring to distribute children around. (Default: 0) -// sa = start angle for first (unrotated) copy. (Default: 0) -// rot = if true, rotate each copy of children with respect to the center of the ring. -// Example: -// yring(n=3, r=10, sa=270) xspread(10) xrot(-120) cylinder(h=10, d=1, center=false); -// yring(n=6, r=20) cylinder(d1=6, d2=0.01, h=6, center=true); -module yring(n=2,r=0,sa=0,rot=true) {if (n>0) for (i=[0:n-1]) {a=i*360/n; yrot(a-sa) right(r) yrot((rot?0:-a)+sa) children();}} - - -// Evenly distributes n duplicate children around a circle on the XY plane, around the -// Z axis. First moves children away from the Z axis by r distance, in direction sa. -// Then copies them around the axis of rotation, for a total of n copies. If rot is -// true, each copy is rotated in place to orient to the center of rotation. -// n = number of copies of children to distribute around the circle. (Default: 2) -// r = radius of ring to distribute children around. (Default: 0) -// sa = start angle for first (unrotated) copy. (Default: 0) -// rot = if true, rotate each copy of children with respect to the center of the ring. -// Example: -// zring(n=3, r=10, sa=90) xspread(10) xrot(30) cylinder(h=10, d=1, center=false); -// zring(n=6, r=20) xrot(-90) cylinder(d1=6, d2=0.01, h=6, center=true); -module zring(n=2,r=0,sa=0,rot=true) {if (n>0) for (i=[0:n-1]) {a=i*360/n; zrot(a+sa) right(r) zrot((rot?0:-a)-sa) children();}} - - -// Spreads out n copies of the given children along the X axis. -// spacing = spacing between copies. (Default: 1.0) -// n = Number of copies to spread out. (Default: 2) -// Examples: -// xspread(25) sphere(1); -// xspread(25,3) sphere(1); -// xspread(25, n=3) sphere(1); -// xspread(spacing=20, n=4) sphere(1); -module xspread(spacing=1,n=2) for (i=[0:n-1]) right((i-(n-1)/2.0)*spacing) children(); - - -// Spreads out n copies of the given children along the Y axis. -// spacing = spacing between copies. (Default: 1.0) -// n = Number of copies to spread out. (Default: 2) -// Examples: -// yspread(25) sphere(1); -// yspread(25,3) sphere(1); -// yspread(25, n=3) sphere(1); -// yspread(spacing=20, n=4) sphere(1); -module yspread(spacing=1,n=2) for (i=[0:n-1]) back((i-(n-1)/2)*spacing) children(); - - -// Spreads out n copies of the given children along the Z axis. -// spacing = spacing between copies. (Default: 1.0) -// n = Number of copies to spread out. (Default: 2) -// Examples: -// zspread(25) sphere(1); -// zspread(25,3) sphere(1); -// zspread(25, n=3) sphere(1); -// zspread(spacing=20, n=4) sphere(1); -module zspread(spacing=1,n=2) for (i=[0:n-1]) up((i-(n-1)/2.0)*spacing) children(); - - -// Spreads out the given children along the X axis. -// spacing = spacing between each child. (Default: 10.0) -// Examples: -// xdistribute(15) { -// sphere(1); -// cube([1,2,3], center=true); -// cylinder(d=2, h=5); -// } -module xdistribute(spacing=10) for (i=[0:$children-1]) right((i-($children-1)/2.0)*spacing) children(i); - - -// Spreads out the given children along the Y axis. -// spacing = spacing between each child. (Default: 10.0) -// Examples: -// ydistribute(15) { -// sphere(1); -// cube([1,2,3], center=true); -// cylinder(d=2, h=5); -// } -module ydistribute(spacing=10) for (i=[0:$children-1]) back((i-($children-1)/2.0)*spacing) children(i); - - -// Spreads out the given children along the Z axis. -// spacing = spacing between each child. (Default: 10.0) -// Examples: -// zdistribute(15) { -// sphere(1); -// cube([1,2,3], center=true); -// cylinder(d=2, h=5); -// } -module zdistribute(spacing=10) for (i=[0:$children-1]) up((i-($children-1)/2.0)*spacing) children(i); - - -// Makes a 3D grid of duplicate children. -// xa = array or range of X-axis values to offset by. (Default: [0]) -// ya = array or range of Y-axis values to offset by. (Default: [0]) -// za = array or range of Z-axis values to offset by. (Default: [0]) -// count = Optional number of copies to have per axis. (Default: none) -// spacing = spacing of copies per axis. Use with count. (Default: 0) -// Examples: -// grid_of(xa=[0,2,3,5],ya=[3:5],za=[-4:2:6]) sphere(r=0.5,center=true); -// grid_of(ya=[-6:3:6],za=[4,7]) sphere(r=1,center=true); -// grid_of(count=3, spacing=10) sphere(r=1,center=true); -// grid_of(count=[3, 1, 2], spacing=10) sphere(r=1,center=true); -// grid_of(count=[3, 4], spacing=[10, 8]) sphere(r=1,center=true); -// grid_of(count=[3, 4, 2], spacing=[10, 8, 5]) sphere(r=1,center=true, $fn=24); -module grid_of(xa=[0], ya=[0], za=[0], count=[], spacing=[]) -{ - count = (len(count) == undef)? [count,1,1] : - ((len(count) == 1)? [count[0], 1, 1] : - ((len(count) == 2)? [count[0], count[1], 1] : - ((len(count) == 3)? count : undef))); - - spacing = (len(spacing) == undef)? [spacing,spacing,spacing] : - ((len(spacing) == 1)? [spacing[0], 0, 0] : - ((len(spacing) == 2)? [spacing[0], spacing[1], 0] : - ((len(spacing) == 3)? spacing : undef))); - - if (count != undef && spacing != undef) { - for (x = [-(count[0]-1)/2 : (count[0]-1)/2 + 0.1]) { - for (y = [-(count[1]-1)/2 : (count[1]-1)/2 + 0.1]) { - for (z = [-(count[2]-1)/2 : (count[2]-1)/2 + 0.1]) { - translate([x*spacing[0], y*spacing[1], z*spacing[2]]) { - children(); - } - } - } + } else if (orig_orient != ORIENT_Z) { + rotsize = ( + (orig_orient==ORIENT_X)? [size[1], size[2], size[0]] : + (orig_orient==ORIENT_Y)? [size[0], size[2], size[1]] : + vabs(rotate_points3d([size], orig_orient, reverse=true)[0]) + ); + orient_and_align(size=rotsize, orient=orient, align=algn) { + rot(orig_orient,reverse=true) children(); } + } else if (is_scalar(algn)) { + // If align is a number and not a vector, then translate PRE-rotation. + orient_and_align(size=size, orient=orient) { + translate(vmul(size/2, algn*V_UP)) children(); + } + } else if (is_str(algn)) { + // If align is a string, look for an alignments label that matches. + found = search([algn], alignments, num_returns_per_match=1); + if (found != [[]]) { + orient_and_align(size=size, orient=orient) { + idx = found[0]; + delta = alignments[idx][1]; + translate(-delta) children(); + } + } else { + assertion(1==0, str("Alignment label '", algn, "' is not known.", (alignments? str(" Try one of ", [for (v=alignments) v[0]], ".") : ""))); + } + } else if (orient != ORIENT_Z) { + rotsize = ( + (orient==ORIENT_X)? [size[2], size[0], size[1]] : + (orient==ORIENT_Y)? [size[0], size[2], size[1]] : + vabs(rotate_points3d([size], orient)[0]) + ); + orient_and_align(size=rotsize, align=algn) { + rotate(orient) children(); + } + } else if (is_def(algn) && algn != [0,0,0]) { + translate(vmul(size/2, algn)) children(); } else { - for (xoff = xa) { - for (yoff = ya) { - for (zoff = za) { - translate([xoff,yoff,zoff]) { - children(); - } - } - } - } + children(); } } -module top_half (s=100) difference() {children(); down(s/2) cube(s, center=true);} -module bottom_half(s=100) difference() {children(); up(s/2) cube(s, center=true);} -module left_half (s=100) difference() {children(); right(s/2) cube(s, center=true);} -module right_half (s=100) difference() {children(); left(s/2) cube(s, center=true);} -module front_half (s=100) difference() {children(); back(s/2) cube(s, center=true);} -module back_half (s=100) difference() {children(); fwd(s/2) cube(s, center=true);} - - // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/triangulation.scad b/triangulation.scad index 0ef2cb3..d92afa5 100644 --- a/triangulation.scad +++ b/triangulation.scad @@ -1,8 +1,52 @@ +////////////////////////////////////////////////////////////////////// +// LibFile: triangulation.scad +// Functions to triangulate polyhedron faces. +// To use, add the following lines to the beginning of your file: +// ``` +// use +// ``` +////////////////////////////////////////////////////////////////////// + +/* +BSD 2-Clause License + +Copyright (c) 2017, Revar Desmera +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + use -// Given an array of vertices (`points`), and a list of indexes into the -// vertex array (`face`), returns the normal vector of the face. +// Section: Functions + + +// Function: face_normal() +// Description: +// Given an array of vertices (`points`), and a list of indexes into the +// vertex array (`face`), returns the normal vector of the face. +// Arguments: // points = Array of vertices for the polyhedron. // face = The face, given as a list of indices into the vertex array `points`. function face_normal(points, face) = @@ -20,7 +64,10 @@ function face_normal(points, face) = ; -// Returns the index of a convex point on the given face. +// Function: find_convex_vertex() +// Description: +// Returns the index of a convex point on the given face. +// Arguments: // points = Array of vertices for the polyhedron. // face = The face, given as a list of indices into the vertex array `points`. // facenorm = The normal vector of the face. @@ -37,21 +84,25 @@ function find_convex_vertex(points, face, facenorm, i=0) = ; +// Function: point_in_ear() +// Description: Determine if a point is in a clipable convex ear. +// Arguments: // points = Array of vertices for the polyhedron. // face = The face, given as a list of indices into the vertex array `points`. function point_in_ear(points, face, tests, i=0) = (iprev[0])? [test, i] : prev : - [check_point_in_ear(points[face[i]], tests), i] + [_check_point_in_ear(points[face[i]], tests), i] ; -function check_point_in_ear(point, tests) = +// Internal non-exposed function. +function _check_point_in_ear(point, tests) = let( result=[ (point*tests[0][0])-tests[0][1], @@ -63,7 +114,9 @@ function check_point_in_ear(point, tests) = ; -// Removes the last item in an array if it is the same as the first item. +// Function: normalize_vertex_perimeter() +// Description: Removes the last item in an array if it is the same as the first item. +// Arguments: // v = The array to normalize. function normalize_vertex_perimeter(v) = (len(v) < 2)? v : @@ -72,8 +125,11 @@ function normalize_vertex_perimeter(v) = ; -// Given a face in a polyhedron, and a vertex in that face, returns true -// if that vertex is the only non-colinear vertex in the face. +// Function: is_only_noncolinear_vertex() +// Description: +// Given a face in a polyhedron, and a vertex in that face, returns true +// if that vertex is the only non-colinear vertex in the face. +// Arguments: // points = Array of vertices for the polyhedron. // facelist = The face, given as a list of indices into the vertex array `points`. // vertex = The index into `facelist`, of the vertex to test. @@ -95,8 +151,11 @@ function is_only_noncolinear_vertex(points, facelist, vertex) = ; -// Given a face in a polyhedron, subdivides the face into triangular faces. -// Returns an array of faces, where each face is a list of vertex indices. +// Function: triangulate_face() +// Description: +// Given a face in a polyhedron, subdivides the face into triangular faces. +// Returns an array of faces, where each face is a list of three vertex indices. +// Arguments: // points = Array of vertices for the polyhedron. // face = The face, given as a list of indices into the vertex array `points`. function triangulate_face(points, face) = @@ -142,8 +201,11 @@ function triangulate_face(points, face) = ; -// Subdivides all faces for the given polyhedron that have more than 3 vertices. -// Returns an array of faces where each face is a list of 3 vertex array indices. +// Function: triangulate_faces() +// Description: +// Subdivides all faces for the given polyhedron that have more than three vertices. +// Returns an array of faces where each face is a list of three vertex array indices. +// Arguments: // points = Array of vertices for the polyhedron. // faces = Array of faces for the polyhedron. Each face is a list of 3 or more indices into the `points` array. function triangulate_faces(points, faces) = diff --git a/wiring.scad b/wiring.scad index 6f5c95c..dd16f84 100644 --- a/wiring.scad +++ b/wiring.scad @@ -1,5 +1,10 @@ ////////////////////////////////////////////////////////////////////// -// Rendering for wiring bundles +// LibFile: wiring.scad +// Rendering for wiring bundles +// To use, include the following line at the top of your file: +// ``` +// use +// ``` ////////////////////////////////////////////////////////////////////// /* @@ -36,43 +41,73 @@ include include -// Returns an array of 1 or 6 points that form a ring, based on wire diam and ring level. -// Level 0 returns a single point at 0,0. All greater levels return 6 points. -function hex_offset_ring(wirediam, lev=0) = +// Section: Functions + + +// Function: hex_offset_ring() +// Description: +// Returns a hexagonal ring of points, with a spacing of `d`. +// If `lev=0`, returns a single point at `[0,0]`. All greater +// levels return 6 times `lev` points. +// Usage: +// hex_offset_ring(d, lev) +// Arguments: +// d = Base unit diameter to build rings upon. +// lev = How many rings to produce. +// Example: +// hex_offset_ring(d=1, lev=3); // Returns a hex ring of 18 points. +function hex_offset_ring(d, lev=0) = (lev == 0)? [[0,0]] : [ for ( sideang = [0:60:359.999], - sidewire = [1:lev] + sidenum = [1:lev] ) [ - lev*wirediam*cos(sideang)+sidewire*wirediam*cos(sideang+120), - lev*wirediam*sin(sideang)+sidewire*wirediam*sin(sideang+120) + lev*d*cos(sideang)+sidenum*d*cos(sideang+120), + lev*d*sin(sideang)+sidenum*d*sin(sideang+120) ] ]; -// Returns an array of 2D centerpoints for each of a bundle of wires of given diameter. -// The lev and arr variables are used for internal recursion. -function hex_offsets(wires, wirediam, lev=0, arr=[]) = - (len(arr) >= wires)? arr : +// Function: hex_offsets() +// Description: +// Returns the centerpoints for the optimal hexagonal packing +// of at least `n` circular items, of diameter `d`. Will return +// enough points to fill out the last ring, even if that is more +// than `n` points. +// Usage: +// hex_offsets(n, d) +// Arguments: +// n = Number of items to bundle. +// d = How far to space each point away from others. +function hex_offsets(n, d, lev=0, arr=[]) = + (len(arr) >= n)? arr : hex_offsets( - wires=wires, - wirediam=wirediam, + n=n, + d=d, lev=lev+1, - arr=concat(arr, hex_offset_ring(wirediam, lev=lev)) + arr=concat(arr, hex_offset_ring(d, lev=lev)) ); -// Returns a 3D object representing a bundle of wires that follow a given path, -// with the corners filleted to a given radius. There are 17 base wire colors. -// If you have more than 17 wires, colors will get re-used. -// Arguments: -// path: The 3D polyline path that the wire bundle should follow. -// wires: The number of wires in the wiring bundle. -// wirediam: The diameter of each wire in the bundle. -// fillet: The radius that the path corners will be filleted to. -// wirenum: The first wire's offset into the color table. -// bezsteps: The corner fillets in the path will be converted into this number of segments. + +// Section: Modules + + +// Module: wiring() +// Description: +// Returns a 3D object representing a bundle of wires that follow a given path, +// with the corners filleted to a given radius. There are 17 base wire colors. +// If you have more than 17 wires, colors will get re-used. // Usage: +// wiring(path, wires, [wirediam], [fillet], [wirenum], [bezsteps]); +// Arguments: +// path = The 3D polyline path that the wire bundle should follow. +// wires = The number of wires in the wiring bundle. +// wirediam = The diameter of each wire in the bundle. +// fillet = The radius that the path corners will be filleted to. +// wirenum = The first wire's offset into the color table. +// bezsteps = The corner fillets in the path will be converted into this number of segments. +// Example: // wiring([[50,0,-50], [50,50,-50], [0,50,-50], [0,0,-50], [0,0,0]], fillet=10, wires=13); module wiring(path, wires, wirediam=2, fillet=10, wirenum=0, bezsteps=12) { colors = [