mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2025-12-07 19:32:06 +00:00
commit
7f92da42c1
6 changed files with 286 additions and 10 deletions
|
|
@ -23,9 +23,8 @@ PrioritizeFiles:
|
|||
attachments.scad
|
||||
shapes2d.scad
|
||||
shapes3d.scad
|
||||
masks.scad
|
||||
drawing.scad
|
||||
masks2d.scad
|
||||
masks3d.scad
|
||||
distributors.scad
|
||||
color.scad
|
||||
partitions.scad
|
||||
|
|
@ -70,6 +69,7 @@ PrioritizeFiles:
|
|||
tripod_mounts.scad
|
||||
walls.scad
|
||||
wiring.scad
|
||||
hooks.scad
|
||||
DefineHeader(BulletList): Side Effects
|
||||
DefineHeader(Table;Headers=Anchor Name|Position): Named Anchors
|
||||
DefineHeader(Table;Headers=Anchor Type|What it is): Anchor Types
|
||||
|
|
|
|||
269
hooks.scad
Normal file
269
hooks.scad
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
include<BOSL2/std.scad>
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// LibFile: hooks.scad
|
||||
// Functions and modules for creating hooks and hook like parts.
|
||||
// At the moment only one part is supported, a ring hook.
|
||||
// Includes:
|
||||
// include <BOSL2/hooks.scad>
|
||||
// FileGroup: Parts
|
||||
// FileSummary: Hooks and hook-like parts.
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
// Module: ring_hook()
|
||||
// Synopsis: A hook with a circular hole or attached cylinder
|
||||
// SynTags: Geom
|
||||
// Topics: Parts
|
||||
// See Also: prismoid(), rounded_prism(), ycyl()
|
||||
// Usage:
|
||||
// ring_hook(base_size, hole_z, or, od=, [ir=], [hole=], [rounding=], [fillet=], [hole_rounding=], [anchor=], [spin=], [orient=])
|
||||
// Description:
|
||||
// Form a part that attaches a loop hook with a cylindrical hole a specified distance away from its mount point.
|
||||
// You specify a rectangle defining the base a hole diameter or radius, and `hole_z`, a distance from the base to the hole.
|
||||
// You can set the hole diameter to zero to create a solid paddle with no hole.
|
||||
// .
|
||||
// In order to calculate a tangent where the base joins the cylinder,
|
||||
// the lower corners of the base must be outside the cylinder (see Example 3). This scenario occurs when
|
||||
// the base is narrower than the Y-cylinder and hole_z is less than Y-cylinder radius. Also, hole_z must
|
||||
// be large enough to accommodate hole rounding and base rounding.
|
||||
// Arguments:
|
||||
// base_size = 2-vector specifying x and y sizes of the base
|
||||
// hole_z = distance in the z direction from the base to the center of the hole
|
||||
// or = radius of the cylindrical portion of the part (or zero to create no hole)
|
||||
// ---
|
||||
// od = diameter of the cylindrical portion of the part
|
||||
// ir, id = optional radius/diameter of the center hole
|
||||
// hole = Set to "circle" for a circle hole, "D" for a D-shaped (semicircular) hole or a path to create a custom hole. Default: "circle"
|
||||
// rounding = rounding of the vertical-ish edges of the prismoid and the exposed edges of the cylinder
|
||||
// fillet = base rounding, set negative to form a rounded edge instead of fillet
|
||||
// hole_rounding = rounding of the optional hole
|
||||
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
|
||||
// spin = Rotate this many degrees around the Z axis. See [spin](attachments.scad#subsection-spin). Default: `0`
|
||||
// orient = Vector to rotate top towards. See [orient](attachments.scad#subsection-orient). Default: `UP`
|
||||
// Named anchors:
|
||||
// hole_front = front, center of the cylindrical portion of the part (same as the part FRONT if hole_z=or)
|
||||
// hole_back = back, center of the cylindrical portion of the part (same as the part BACK if hole_z=or)
|
||||
// tangent_right = right side anchor at the point where the prismoid merges with Y-cylinder, at y=0
|
||||
// tangent_left = left side anchor at the point where the prismoid merges with Y-cylinder, at y=0
|
||||
// Attachable Parts:
|
||||
// "inside" = The inner hole (not defined if there is no hole)
|
||||
// Example: Ring connector
|
||||
// ring_hook([50, 10], 25, 25, ir=20);
|
||||
// Example: Widen the base, add base fillet, no hole
|
||||
// ring_hook([70, 10], 25, or=25, ir=0, fillet=3, rounding=1.5);
|
||||
// Example: Narrow base
|
||||
// ring_hook([40, 10], 25, or=25, ir=0, fillet=3, rounding=1.5);
|
||||
// Example(3D,VPR=[90,0,0]): If the base is narrower than the cylinder diameter then its corners have to be outside the cylinder for this shape to be defined because it requires a tangent line to the cylinder. This example shows a valid base corner point in blue. An invalid corner point appears in red: no tangent to the circle exists through the red point.
|
||||
// hole_z = 20;
|
||||
// base_size = [40, 10];
|
||||
// outer_radius = 25;
|
||||
// ring_hook(base_size, hole_z, outer_radius, ir=0);
|
||||
// up(hole_z) color("blue", 0.25) ycyl(r=outer_radius, h=base_size.y + 2);
|
||||
// right(0.5*base_size.x) color("blue") ycyl(r=1, h=base_size.y + 2, $fn=12);
|
||||
// right(0.3*base_size.x) color("red") ycyl(r=1, h=base_size.y + 2, $fn=12);
|
||||
// Example(3D,VPR=[60.60,0.00,62.10]): Through hole can be specified using or/od, ir/id, wall variables. All of these are equivalent.
|
||||
// ydistribute(spacing = 25) {
|
||||
// ring_hook([50, 10], 40, or=25, ir=20);
|
||||
// ring_hook([50, 10], 40, 25, wall=5);
|
||||
// ring_hook([50, 10], 40, wall=5, ir=20);
|
||||
// ring_hook([50, 10], 40, od=50, id=40);
|
||||
// ring_hook([50, 10], 40, od=50, wall=5);
|
||||
// ring_hook([50, 10], 40, wall=5, id=40);
|
||||
// }
|
||||
// Example: Semi-circular through hole (a D-hole):
|
||||
// ring_hook([50, 10], 12, 25, ir=15, hole="D", rounding=3, hole_rounding=3, fillet=2);
|
||||
// Example: hole_z must be greater than 0 with no hole or with hole="D". Here hole_z is 1, close to the minimum value of zero.
|
||||
// xdistribute(spacing=60){
|
||||
// ring_hook([50, 10], 1, 25, ir=0);
|
||||
// ring_hook([50, 10], 1, 25, ir=15, hole="D");
|
||||
// }
|
||||
// Example: hole_z must be greater than ir + hole_rounding + fillet when hole="circle". Here hole_z is only 1 larger than the minimum.
|
||||
// ring_hook([50, 10], hole_z=27, or=25, ir=20, hole_rounding=3, fillet=3);
|
||||
// Example: Rounding all edges
|
||||
// ring_hook([50, 10], 40, 25, ir=15, rounding=5, hole_rounding=5, fillet=5);
|
||||
// Example: Giving an arbitrary path for the hole, in this case an octagon to make the object printable without support.
|
||||
// ring_hook([50, 20],30, 25, ir=10, hole=octagon(side=10,realign=true), hole_rounding=3, rounding=4) ;
|
||||
// Example: The ring_hook includes 4 custom anchors: front & back at the center of the cylinder component and left & right at the tangent points.
|
||||
// ring_hook([55, 10], 12, 25, ir=0) show_anchors(std=false);
|
||||
// Example: Use the custom anchor to place a screw hole
|
||||
// include <BOSL2/screws.scad>
|
||||
// diff()
|
||||
// ring_hook([20, 10], 15, 7, ir=0, fillet=3)
|
||||
// attach("hole_front")
|
||||
// screw_hole("M5", length=20, head="socket", atype="head", anchor=TOP, orient=UP);
|
||||
// Example: Use the custom anchor to create a cylindrical extension instead of a hole
|
||||
// $fs=1;$fa=2;
|
||||
// ring_hook([30,10], hole_z=17, or=10, ir=0, rounding=1.5)
|
||||
// attach("hole_front", BOT)
|
||||
// cyl(d=10, h=14, rounding1=-2, rounding2=2);
|
||||
// Example(3D,VPR=[83.70,0.00,29.20]): Use the "inner" part to create a bar across the hole:
|
||||
// diff()
|
||||
// ring_hook([50, 20],30, 25, ir=10, hole_rounding=3, rounding=4)
|
||||
// attach_part("inner")
|
||||
// prism_connector( circle(3, $fn=16),
|
||||
// parent(), LEFT,
|
||||
// parent(), RIGHT, fillet=1);
|
||||
|
||||
|
||||
module ring_hook(base_size, hole_z, or, ir, od, id, wall, hole="circle",
|
||||
rounding=0, fillet=0, hole_rounding=0,
|
||||
anchor=BOTTOM, spin=0, orient=UP) {
|
||||
|
||||
or_tmp = get_radius(r=or, d=od);
|
||||
ir_tmp = get_radius(r=ir, d=id);
|
||||
dummy = assert(num_defined([ir_tmp, or_tmp, wall])==2, "Must define exactly two of r/d, ir/id and wall");
|
||||
ir = is_def(ir_tmp) ? ir_tmp : or_tmp - wall;
|
||||
or = is_def(or_tmp) ? or_tmp : ir + wall;
|
||||
dummy2 = assert(ir <= or, "Hole doesn't fit or wall size is negative")
|
||||
assert(sqrt((0.5*base_size.x)^2 + hole_z^2) > or, "Base corners must be outside the cylinder")
|
||||
assert(in_list(hole,["circle","D"]) || is_path(hole,2), "hole must be \"circle\", \"D\" or a 2d path")
|
||||
assert(all_nonnegative([hole_rounding]), "hole_rounding must be greater than or equal to 0");
|
||||
|
||||
if (ir > 0 && hole=="circle")
|
||||
assert(ir + hole_rounding < hole_z-fillet,str("ir + hole_rounding must be less than ",hole_z-fillet));
|
||||
|
||||
z_offset = (hole_z - or)/2;
|
||||
tangents = circle_point_tangents(
|
||||
r=or,
|
||||
cp=[0,hole_z],
|
||||
pt=[0.5*base_size.x, 0]);
|
||||
|
||||
// we want the tangent with the larger y value
|
||||
tangent = tangents[0].y > tangents[1].y
|
||||
? tangents[0] : tangents[1];
|
||||
|
||||
// anchor calcs
|
||||
angle = atan((tangent.x - 0.5*base_size.x)/tangent.y);
|
||||
top_x = 0.5*base_size.x + (hole_z + or)*tan(angle);
|
||||
// when or > 0.5*base_size.x, need to move the anchor
|
||||
// use x^2 + y^2 = r^2, x = sqrt(r^2 - y^2)
|
||||
delta_y = z_offset;
|
||||
mid_x = sqrt(or^2 - delta_y^2);
|
||||
|
||||
h = hole_z + or;
|
||||
w = base_size.y;
|
||||
size = [base_size.x, w];
|
||||
size2 = [2*top_x, w];
|
||||
|
||||
right_tang_dir = unit([tangent.x, 0, tangent.y-hole_z]);
|
||||
left_tang_dir = unit([-tangent.x,0, tangent.y-hole_z]);
|
||||
|
||||
anchors = [
|
||||
named_anchor("hole_front", [0, -w/2, z_offset], FRONT, 0),
|
||||
named_anchor("hole_back", [0, w/2, z_offset], BACK, 180),
|
||||
named_anchor("tangent_right", [tangent[0], 0, tangent[1] - hole_z + z_offset], right_tang_dir, _compute_spin(right_tang_dir,UP,BACK)),
|
||||
named_anchor("tangent_left", [-tangent[0], 0, tangent[1] - hole_z + z_offset], left_tang_dir, _compute_spin(left_tang_dir,UP,BACK)),
|
||||
];
|
||||
override = [
|
||||
for (i = [-1, 1], j=[-1:1], k=[0:1])
|
||||
if (k==0 && j!=0 && or > 0.5*base_size.x)
|
||||
[[i, j, 0],
|
||||
[mid_x*unit([i, 0, 0]) + 0.5*base_size.y*unit([0, j, 0])]]
|
||||
else if (k==0 && or > 0.5*base_size.x)
|
||||
[[i, 0, 0], [mid_x*unit([i, 0, 0])]]
|
||||
else if (k==1 && j==0)
|
||||
[[i, 0, 1], [or*sin(45)*unit([i, 0, 0])
|
||||
+ (z_offset + or*sin(45))*unit([0, 0, k])]]
|
||||
else if (k==1)
|
||||
[[i, j, 1], [or*sin(45)*unit([i, 0, 0])
|
||||
+ 0.5*base_size.y*unit([0, j, 0])
|
||||
+ (z_offset + or*sin(45))*unit([0, 0, k])]]
|
||||
];
|
||||
|
||||
|
||||
profile = is_path(hole) ? hole
|
||||
: hole=="D" ? arc(angle=180, r=ir, rounding=hole_rounding, wedge=true)
|
||||
: ir > 0 ? circle(ir)
|
||||
: undef;
|
||||
|
||||
parts = is_undef(profile) ? undef
|
||||
:[
|
||||
define_part("inner",
|
||||
attach_geom(
|
||||
region=[ymove(z_offset,profile)], l=size.y),
|
||||
T=xrot(90),
|
||||
inside=true)
|
||||
];
|
||||
|
||||
attachable( anchor, spin, orient,
|
||||
size=point3d(size,h),
|
||||
size2=size2,
|
||||
anchors=anchors,
|
||||
override=override,
|
||||
parts=parts
|
||||
) {
|
||||
down(h/2)
|
||||
difference() {
|
||||
union() {
|
||||
startangle = atan2(tangent.y-hole_z, tangent.x);
|
||||
endangle = posmod(atan2(tangent.y-hole_z, -tangent.x),360);
|
||||
steps = segs(or,endangle-startangle)+1;
|
||||
delta = (endangle-startangle)/(steps-1);
|
||||
|
||||
|
||||
profile = rounding == 0 ? [[or,0,-base_size.y/2],[or,0,base_size.y/2]]
|
||||
: let(
|
||||
// rounded prism roundings are computed on top face, so cos() correction is needed
|
||||
// to get them to align properly
|
||||
bez = _smooth_bez_fill([//[or-rounding*(startangle>0?cos(startangle):1),0,-base_size.y/2],
|
||||
[or-rounding,0,-base_size.y/2],
|
||||
[or,0,-base_size.y/2],
|
||||
[or,0,-base_size.y/2+rounding]],0.92),
|
||||
pts = bezier_curve(bez)
|
||||
)
|
||||
concat(pts, reverse(zflip(pts)));
|
||||
|
||||
toplist = [
|
||||
[for(pt=profile) [0,-or,pt.z]],
|
||||
if (startangle<0)
|
||||
move(-[tangent.x-base_size.x/2,tangent.y] ,zrot(startangle, profile)),
|
||||
for(angle = lerpn(startangle, endangle, steps)) zrot(angle, profile),
|
||||
if (startangle<0)
|
||||
move(-[-tangent.x+base_size.x/2,tangent.y] ,zrot(endangle, profile)),
|
||||
];
|
||||
intersection(){
|
||||
up(hole_z)xrot(90)
|
||||
vnf_vertex_array(transpose(toplist),caps=true,col_wrap=true,reverse=true,triangulate=true);
|
||||
cuboid([max(base_size.x,2*or),w+1, or+hole_z+1],anchor=BOT);
|
||||
}
|
||||
|
||||
// When base is outside the circle the base needs to be clipped so the roundings don't interfere
|
||||
// This mask does this clipping
|
||||
maskpath2 = [zrot(startangle,[or+1,0,0]),
|
||||
zrot(startangle,[or-rounding, 0, 0]),
|
||||
zrot(startangle+delta, [or-rounding-.1, 0, 0]),
|
||||
];
|
||||
maskpath = up(hole_z,xrot(90, [each maskpath2,
|
||||
[maskpath2[0].x, maskpath2[0].x*tan(startangle+delta),0]
|
||||
]));
|
||||
|
||||
difference(){
|
||||
rounded_prism(
|
||||
rect(base_size),
|
||||
rect( [ 2*tangent.x, w ] ),
|
||||
h=tangent.y,
|
||||
joint_bot=-fillet,
|
||||
joint_sides=rounding,
|
||||
k_sides=0.92, k_bot=0.92,
|
||||
anchor=BOT );
|
||||
if (startangle>0)
|
||||
xflip_copy()
|
||||
vnf_vertex_array([fwd(w/2+1, maskpath), back(w/2+1, maskpath)],
|
||||
col_wrap=true,caps=true,reverse=true);
|
||||
}
|
||||
}
|
||||
|
||||
if (ir > 0) {
|
||||
up(hole_z)
|
||||
prism_connector(
|
||||
profile,
|
||||
parent(), FRONT,
|
||||
parent(), BACK,
|
||||
fillet=hole_rounding);
|
||||
}
|
||||
}
|
||||
children();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -280,9 +280,9 @@ function mask2d_roundover(r, inset=0, mask_angle=90, excess=0.01, clip_angle, fl
|
|||
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
|
||||
// Example(2D): Mask defined by cut
|
||||
// mask2d_smooth(cut=3);
|
||||
// Example(2D): Mask defined by symmetric joint length
|
||||
// mask2d_smooth(joint=10);
|
||||
// Example(2D): Asymmetric mask by joint length with different lengths and a larger excess
|
||||
// Example(2D): Mask defined by symmetric joint length with larger excess (which helps show the ends of the mask)
|
||||
// mask2d_smooth(joint=10,excess=0.5);
|
||||
// Example(2D): Asymmetric mask by joint length with different lengths
|
||||
// mask2d_smooth(joint=[10,7],excess=0.5);
|
||||
// Example(2D): Acute angle mask by cut
|
||||
// mask2d_smooth(mask_angle=66,cut=3,excess=0.5);
|
||||
|
|
|
|||
14
regions.scad
14
regions.scad
|
|
@ -858,8 +858,10 @@ function _point_dist(path,pathseg_unit,pathseg_len,pt) =
|
|||
// The erroneous removal of segments is more common when your input
|
||||
// contains very small segments and in this case can result in an invalid situation where the remaining
|
||||
// valid segments are parallel and cannot be connected to form an offset curve. If this happens, you
|
||||
// get an error message to this effect. The only solutions are either to remove the small segments with {{deduplicate()}},
|
||||
// or if your path permits it, to set check_valid to false.
|
||||
// get an error message to this effect. The only solutions are either to remove small segments with {{resample_path()}} or
|
||||
// generate your data with fewer points (e.g. by using a smaller `$fn` or larger `$fa` and `$fs` when constructing your input).
|
||||
// Be aware that chamfer and rounding increase the length of the path, so iterated offsets can lead to exponential
|
||||
// growth in the path length.
|
||||
// .
|
||||
// Another situation that can arise with validity testing is that the test is not sufficiently thorough and some
|
||||
// segments persist that should be eliminated. In this case, increase `quality` from its default of 1 to a value of 2 or 3.
|
||||
|
|
@ -869,7 +871,7 @@ function _point_dist(path,pathseg_unit,pathseg_len,pt) =
|
|||
// .
|
||||
// When invalid segments are eliminated, the path length decreases, and multiple points on the input path map to the same point
|
||||
// on the offset path. If you use chamfering or rounding, then
|
||||
// the chamfers and roundings can increase the length of the output path. Hence points in the output may be
|
||||
// the chamfers and roundings increase the length of the output path. Hence points in the output may be
|
||||
// difficult to associate with the input. If you want to maintain alignment between the points you
|
||||
// can use the `same_length` option. This option requires that you use `delta=` with `chamfer=false` to ensure
|
||||
// that no points are added. with `same_length`, when points collapse to a single point in the offset, the output includes
|
||||
|
|
@ -1067,7 +1069,11 @@ function offset(
|
|||
cornercheck = [for(i=idx(goodsegs)) (!closed && (i==0 || i==len(goodsegs)-1))
|
||||
|| is_def(sharpcorners[i])
|
||||
|| approx(unit(deltas(select(goodsegs,i-1))[0]) * unit(deltas(goodsegs[i])[0]),-1)],
|
||||
dummyA = assert(len(sharpcorners)==2 || all(cornercheck),"\nTwo consecutive valid offset segments are parallel but do not meet at their ends, maybe because path contains very short segments that were mistakenly flagged as invalid; unable to compute offset. If you get this error from offset_sweep() try setting ofset=\"delta\"."),
|
||||
dummyA = assert(len(sharpcorners)==2 || all(cornercheck),
|
||||
str("\nUnable to compute offset due to segments that are very close to parallel but not exactly parallel. \n",
|
||||
"This is usually caused by too many points or points that are too close together. \n",
|
||||
"Use fewer points (lower $fn, larger $fa/$fs) or use resample_path(). \n",
|
||||
"If you get this error from offset_sweep() using offset=\"delta\" may help")),
|
||||
reversecheck =
|
||||
!same_length
|
||||
|| !(is_def(delta) && !chamfer) // Reversals only a problem in delta mode without chamfers
|
||||
|
|
|
|||
|
|
@ -598,7 +598,7 @@ function _rounding_offsets(edgespec,z_dir=1) =
|
|||
// Synopsis: Create a smoothed path passing through all the points of a given path, or passing through all the segment midpoint tangents.
|
||||
// SynTags: Path
|
||||
// Topics: Rounding, Paths
|
||||
// See Also: round_corners(), smooth_path(), path_join(), offset_stroke()
|
||||
// See Also: round_corners(), smooth_path(), path_join(), offset_stroke(), squircle()
|
||||
// Usage: "edges" method
|
||||
// smoothed = smooth_path(path, [tangents], [size=|relsize=], [method="edges"], [splinesteps=], [closed=], [uniform=]);
|
||||
// Usage: "corners" method
|
||||
|
|
|
|||
|
|
@ -1827,6 +1827,7 @@ function rect_tube(
|
|||
// wedge([40, 80, 30], center=true)
|
||||
// show_anchors(std=false);
|
||||
// Example(3D): Rounding the top of the wedge using the "top_edge" anchor
|
||||
// $fn=32;
|
||||
// diff()
|
||||
// wedge([10,15,7])
|
||||
// attach("top_edge", FWD+LEFT, inside=true)
|
||||
|
|
|
|||
Loading…
Reference in a new issue