Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 51 additions & 29 deletions attachments.scad
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ $parent_size = undef;
$parent_geom = undef;
$parent_parts = undef;

$change_anchors = undef; // Anchor changes to propagate; processed by attachable()

$attach_inside = false; // If true, flip the meaning of the inside parameter for align() and attach()

$edge_angle = undef;
Expand Down Expand Up @@ -753,11 +755,12 @@ function _quant_anch(x) = approx(x,0) ? 0 : sign(x);

// Make arbitrary anchor legal for a given geometry
function _make_anchor_legal(anchor,geom) =
is_string(anchor) ? anchor
: in_list(geom[0], ["prismoid","trapezoid"]) ? [for(v=anchor) _quant_anch(v)]
: in_list(geom[0], ["conoid", "extrusion_extent"]) ? [anchor.x,anchor.y, _quant_anch(anchor.z)]
: anchor;

is_string(anchor) ? anchor
: in_list(geom[0], ["prismoid","trapezoid"]) ? [for(v=anchor) _quant_anch(v)]
: in_list(geom[0], ["conoid", "extrusion_extent"]) ? [anchor.x,anchor.y, _quant_anch(point3d(anchor).z)]
: anchor;




// Module: attach()
Expand Down Expand Up @@ -2417,7 +2420,7 @@ module attachable(
expose_tags=false, keep_color=false
) {
dummy1 =
assert($children==2, "\nattachable() expects exactly two children: the shape to manage, and the union of all attachment candidates.")
assert($children==2, "\nattachable() expects exactly two children: the shape to manage, and the union of all attachments.")
assert(is_undef(anchor) || is_vector(anchor) || is_string(anchor), str("Invalid anchor: ",anchor))
assert(is_undef(spin) || is_finite(spin), str("\nInvalid spin: ",spin))
assert(is_undef(orient) || is_vector(orient,3), str("\nInvalid orient: ",orient));
Expand All @@ -2437,18 +2440,20 @@ module attachable(
cp=cp, offset=offset, anchors=anchors,
two_d=two_d, axis=axis, override=override
);
m = _attach_transform(anchor,spin,orient,geom);
final_geom=_change_anchors(geom);
m = _attach_transform(anchor,spin,orient,final_geom);
multmatrix(m) {
$parent_anchor = anchor;
$parent_spin = spin;
$parent_orient = orient;
$parent_geom = geom;
$parent_size = _attach_geom_size(geom);
$parent_geom = final_geom;
$parent_size = _attach_geom_size(final_geom);
$attach_to = undef;
$anchor_override=undef;
$attach_alignment=undef;
$parent_parts = parts;
$anchor_inside = false;
$change_anchors = undef;
if (expose_tags || _is_shown()){
if (!keep_color)
_color($color)
Expand Down Expand Up @@ -2644,9 +2649,11 @@ function reorient(
// rot = A 4×4 rotations matrix, which may include a translation
// flip = If true, flip the anchor the opposite direction. Default: false
function named_anchor(name, pos, orient, spin, rot, flip, info) =
assert(is_vector(orient) || is_undef(orient), "\norient must be a vector")
assert(num_defined([orient,spin])==0 || num_defined([rot,flip])==0, "\nCannot mix orient or spin with rot or flip.")
assert(num_defined([pos,rot])>0, "\nMust give pos or rot")
is_undef(rot) ? [name, pos, default(orient,UP), default(spin,0), if (info) info]
let(orient=point3d(default(orient,UP)))
is_undef(rot) ? [name, pos, orient, default(spin,0), if (info) info]
:
let(
flip = default(flip,false),
Expand All @@ -2668,33 +2675,49 @@ function named_anchor(name, pos, orient, spin, rot, flip, info) =
// Topics: Attachments
// See Also: named_anchor(), attachable()
// Usage:
// PARENT() change_anchors([named],[alias=],[remove=]) CHILDREN;
// change_anchors([named],[alias=],[remove=]) PARENT() CHILDREN;
// Description:
// Modifies the named anchors inherited from the parent object or adds new named anchors. The `named` parameter gives a list
// Modifies the named anchors propagated an object or adds new named anchors. The `named` parameter gives a list
// of new or replacement named anchors, specified in the usual way with {{named_anchor()}}.
// The `alias` parameter specifies a list of pairs of the form `[newname, anchor]` where
// `newname` is a new anchor name and `anchor` is an existing anchor for the parent, either a named anchor or a regular anchor. The
// existing parent anchor will be propagated to the child under the new name. The old name is not changed,
// and will also be propagated to the child. The `remove` parameter removes named anchors inherited from
// the parent so they are not propagated to the child. You can use it to remove an anchor that you have
// aliased so that only the new name propagates to the child.
// `newname` is a new anchor name and `anchor` is an existing anchor for the object, either a named anchor or a regular anchor. The
// existing anchor will be propagated to the child under the new name. The old name is not changed,
// and will also be propagated to the child. The `remove` parameter removes named anchors defined by the object
// so they are not propagated to the child. You can use it to remove an anchor that you have
// aliased so that only the new name propagates to the child.
// .
// This works by setting a `$change_anchors` which will be interpreted by the first `attachable()` child invocation.
// Arguments:
// named = list of named anchors to add
// ---
// alias = list of string pairs of the form [newname,oldname] creating named anchor aliases
// remove = list of strings giving anchors to remove

module change_anchors(named=[], alias=[], remove=[])
// Side Effects:
// Sets `$change_anchors`.

function _change_anchors(geom) =
is_undef($change_anchors) || geom==[] ? geom
:
let(
named=default($change_anchors[0],[]),
alias=default($change_anchors[1],[]),
remove=default($change_anchors[2],[]),
oldanch = last(geom),
allremove = concat(column(named,0), remove),
keepanch = [for(anch=oldanch) if (!in_list(anch[0],allremove)) anch],
aliasanch = [for(name=alias) list_set(_find_anchor(name[1],geom),0,name[0])],
newanch = concat(keepanch, aliasanch, named),
new_geom = list_set(geom,-1,newanch)
)
new_geom;



module change_anchors(named=undef, alias=undef, remove=undef)
{
dummy=assert($parent_geom != undef, "\nNo object with anchors to change!");
oldanch = last($parent_geom);
allremove = concat(column(named,0), remove);
keepanch = [for(anch=oldanch) if (!in_list(anch[0],allremove)) anch];
aliasanch = [for(name=alias) list_set(_find_anchor(name[1],$parent_geom),0,name[0])];
newanch = concat(keepanch, aliasanch, named);
$parent_geom = list_set($parent_geom,-1,newanch);
$change_anchors = [named, alias, remove];
children();
}
}


// Function: attach_geom()
Expand Down Expand Up @@ -3208,7 +3231,7 @@ function _attach_transform(anchor, spin, orient, geom, p) =
two_d = _attach_geom_2d(geom),
m = is_def($attach_to) ? // $attach_to is the attachment point on this object
( // which will attach to the parent
let(
let(
anch = _find_anchor($attach_to, geom),
// if $anchor_override is set it defines the object position anchor (but note not direction or spin).
// Otherwise we use the provided anchor for the object.
Expand Down Expand Up @@ -3537,7 +3560,6 @@ function _find_anchor(anchor, geom)=
let(
vnf=geom[1],
override = geom[2](anchor)
//,fd=echo(cp=cp)
) // CENTER anchors anchor on cp, "origin" anchors on [0,0]
approx(anchor,CTR)? [anchor, default(override[0],cp),default(override[1],UP),default(override[2], 0)] :
vnf==EMPTY_VNF? [anchor, [0,0,0], unit(anchor,UP), 0] :
Expand Down
1 change: 1 addition & 0 deletions comparisons.scad
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,7 @@ function deduplicate_indexed(list, indices, closed=false, eps=_EPSILON) =

function list_wrap(list, eps=_EPSILON) =
assert(is_list(list))
assert(is_finite(eps) && eps>=0)
len(list)<2 || are_ends_equal(list,eps=eps)? list : [each list, list[0]];


Expand Down
40 changes: 26 additions & 14 deletions masks.scad
Original file line number Diff line number Diff line change
Expand Up @@ -1800,6 +1800,8 @@ module corner_profile(corners=CORNERS_ALL, except=[], r, d, axis="Z", convexity=
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
// Side Effects:
// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
// Named Anchors:
// "corner" = The center point of the mask with the correct direiction to anchor to an edge
// Example:
// chamfer_edge_mask(l=50, chamfer=10);
// Example:
Expand All @@ -1817,8 +1819,9 @@ function chamfer_edge_mask(l, chamfer=1, excess=0.1, h, length, height, anchor=C
module chamfer_edge_mask(l, chamfer=1, excess=0.1, h, length, height, anchor=CENTER, spin=0, orient=UP) {
l = is_def($edge_length) && !any_defined([l,length,h,height]) ? $edge_length
: one_defined([l,length,h,height],"l,length,h,height");
anchors=[named_anchor("corner", CTR, -[1,1],_compute_spin(-[1,1,0], UP))];
default_tag("remove") {
attachable(anchor,spin,orient, size=[chamfer*2, chamfer*2, l]) {
attachable(anchor,spin,orient, size=[chamfer*2, chamfer*2, l],anchors=anchors) {
cylinder(r=chamfer, h=l+excess, center=true, $fn=4);
children();
}
Expand Down Expand Up @@ -1871,6 +1874,8 @@ module chamfer_edge_mask(l, chamfer=1, excess=0.1, h, length, height, anchor=CEN
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
// Side Effects:
// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
// Named Anchors:
// "corner" = Corner point of the mask with the correct direction to anchor to an edge
// Example(VPD=200,VPR=[55,0,120]):
// rounding_edge_mask(l=50, r=15);
// Example(VPD=200,VPR=[55,0,120]): With different radii at each end
Expand Down Expand Up @@ -2026,11 +2031,10 @@ module rounding_edge_mask(l, r, ang, r1, r2, excess=0.01, d1, d2,d,r,length, h,
left_dir = cylindrical_to_xyz(1,ang,0);
zdir = unit([length, 0,-(r2-r1)/tan(ang/2)]);
cutfact = 1/sin(ang/2)-1;

v=unit(zrot(ang,zdir)+left_normal);
ref = UP - (v*UP)*v;
backleft_spin=-vector_angle(rot(from=UP,to=v,p=BACK),ref);

anchors=[named_anchor("corner",CENTER, left_normal+FWD,ang/2-90)];
override = [
[CENTER, [CENTER,UP]],
[TOP, [[0,0,length/2]]],
Expand Down Expand Up @@ -2062,34 +2066,39 @@ module rounding_edge_mask(l, r, ang, r1, r2, excess=0.01, d1, d2,d,r,length, h,
];
vnf = vnf_vertex_array(reverse(pathlist), col_wrap=true,caps=true);
default_tag("remove", _remove_tag)
attachable(anchor,spin,orient,size=[1,1,length],override=override){
attachable(anchor,spin,orient,size=[1,1,length],override=override,anchors=anchors){
vnf_polyhedron(vnf);
children();
}
}


// Module: teardrop_edge_mask()
// Synopsis: Creates a shape to round a 90° edge but limit the angle of overhang.
// Synopsis: Creates a shape to round an edge while limiting the angle of overhang.
// SynTags: Geom
// Topics: Masking, Rounding, Shapes (3D), FDM Optimized
// See Also: teardrop_corner_mask(), teardrop_edge_mask(), default_tag(), diff()
// Usage:
// teardrop_edge_mask(l|h=|length=|height=, r|d=, [angle], [excess], [anchor], [spin], [orient]) [ATTACHMENTS];
// Description:
// Makes an apropriate 3D edge rounding mask that keeps within `angle` degrees of vertical.
// Makes an apropriate 3D edge rounding mask that keeps within `angle` degrees of vertical. Don't confuse `angle`
// with `ang`, the parameter which specifies the angle of the corner being rounded. If the flat section ends up
// on the wrong side you can fix this with `spin=180`.
// Arguments:
// l/h/length/height = length of mask
// r = Radius of the mask rounding.
// angle = Maximum angle from vertical. Default: 45
// excess = Excess mask size. Default: 0.1
// ---
// d = Diameter of the mask rounding.
// ang = Angle between faces for rounding. Default: $edge_angle if defined, otherwise 90
// anchor = Translate so anchor point is at origin (0,0,0). See [anchor](attachments.scad#subsection-anchor). Default: `CENTER`
// spin = Rotate this many degrees around the Z axis after anchor. See [spin](attachments.scad#subsection-spin). Default: `0`
// orient = Vector to rotate top towards, after spin. See [orient](attachments.scad#subsection-orient). Default: `UP`
// Side Effects:
// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
// Named Anchors:
// "corner" = Corner point of the mask with the correct direction to anchor to an edge
// Example(VPD=50,VPR=[55,0,120]):
// teardrop_edge_mask(l=20, r=10, angle=40);
// Example(VPD=300,VPR=[75,0,25]):
Expand All @@ -2100,24 +2109,27 @@ module rounding_edge_mask(l, r, ang, r1, r2, excess=0.01, d1, d2,d,r,length, h,
// corner_mask(BOT)
// teardrop_corner_mask(r=10, angle=40);
// }
function teardrop_edge_mask(l, r, angle=45, excess=0.1, d, anchor, spin, orient,h,height,length) = no_function("teardrop_edge_mask");
module teardrop_edge_mask(l, r, angle=45, excess=0.1, d, anchor=CTR, spin=0, orient=UP,h,height,length)
function teardrop_edge_mask(l, r, angle=45, excess=0.1, ang, d, anchor, spin, orient,h,height,length) = no_function("teardrop_edge_mask");
module teardrop_edge_mask(l, r, angle=45, excess=0.1, ang, d, anchor=CTR, spin=0, orient=UP,h,height,length)
{
l = one_defined([l, h, height, length], "l,h,height,length");
check =
assert(is_num(l) && l>0, "Length of mask must be positive")
assert(is_num(angle) && angle>0 && angle<90, "Angle must be a number between 0 and 90")
assert(is_num(excess));
r = get_radius(r=r, d=d, dflt=1);
path = mask2d_teardrop(r=r, angle=angle, excess=excess);
default_tag("remove") {
linear_sweep(path, height=l, center=true, atype="bbox", anchor=anchor, spin=spin, orient=orient) children();
ang = first_defined([ang,$edge_angle,90]);
corner = cylindrical_to_xyz(1,90+ang,0)+FWD;
anchors=[named_anchor("corner",CENTER, corner, ang/2-90)];
path = mask2d_teardrop(r=r, angle=angle, mask_angle=ang, excess=excess);
default_tag("remove") {
change_anchors(named=anchors)
linear_sweep(path, height=l, atype="bbox", anchor=anchor, spin=spin, orient=orient)
children();
}
}




// Module: polygon_edge_mask()
// Synopsis: Extrudes a 2d mask polygon to an edge mask with a correct corner anchor
// SynTags: Geom
Expand Down Expand Up @@ -2155,7 +2167,7 @@ module teardrop_edge_mask(l, r, angle=45, excess=0.1, d, anchor=CTR, spin=0, ori
// "hull" = Anchors to the virtual convex hull of the shape.
// "intersect" = Anchors to the surface of the shape.
// Named Anchors:
// "corner" = The center point of the mask with the correct direiction to anchor to an edge
// "corner" = Corner point of the mask with the correct direction to anchor to an edge
// Side Effects:
// Tags the children with "remove" (and hence sets `$tag`) if no tag is already set.
function polygon_edge_mask(mask, length, height, l, h, scale=1, anchor="origin", atype="hull", spin=0, orient=UP) = no_function("polygon_edge_mask");
Expand Down
2 changes: 1 addition & 1 deletion math.scad
Original file line number Diff line number Diff line change
Expand Up @@ -1215,7 +1215,7 @@ function spherical_random_points(n=1, radius=1, seed) =


// Function: random_polygon()
// Synopsis: Returns the CCW path of a simple random polygon.
// Synopsis: Returns the clockwise path of a simple random polygon.
// Topics: Random, Polygon
// See Also: random_points(), spherical_random_points()
// Usage:
Expand Down
16 changes: 9 additions & 7 deletions paths.scad
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,9 @@ function _path_self_intersections(path, closed=true, eps=_EPSILON) =


// Section: Resampling - changing the number of points in a path

// Unlike other path related functions, the resampling functions in this section
// default to `closed=true`, which means they assume the input path represents
// a polygon.

// Input `data` is a list that sums to an integer.
// Returns rounded version of input data so that every
Expand Down Expand Up @@ -623,10 +625,11 @@ function resample_path(path, n, spacing, keep_corners, closed=true) =
// newpath = simplify_path(path, maxerr, [closed=]);
// Description:
// This is intended for irregular paths such as coastlines, or paths having fractal self-similarity.
// By default the path is assumed to describe a closed polygon (`closed=true`).
// The original path is simplified by removing points that fall within a specified margin of error,
// leaving behind those points that contribute to dominant features of the path. This operation has the
// effect of making the point spacing somewhat more uniform. For coastlines, up to 80% reduction in path
// length is possible with small degradation of the original shape. The input path may be 2D or 3D.
// length is possible with small degradation of the original shape.
// .
// The `maxerr` parameter determines which points of the original path are kept. A point is kept if it
// deviates beyond `maxerr` distance from a straight line between the last kept point and a point further
Expand All @@ -643,7 +646,7 @@ function resample_path(path, n, spacing, keep_corners, closed=true) =
// path = Path in any dimension or 1-region
// maxerr = Maximum deviation from line connecting last kept point to a further point; points beyond this deviation are kept.
// ---
// closed = Set to true if path is closed. Default: false for paths, true for 1-regions
// closed = Set to true if path is closed. Default: true
// Example(2D,Med,VPD=38000,VPT=[5600,6500,0]): A map of California, originally a 262-point polygon (yellow, on left), reduced to 39 points (green, on right).
// calif = [
// [225,12681], [199,12544], [180,12490], [221,12435], [300,12342], [310,12315], [320,12263], [350,12154],
Expand Down Expand Up @@ -684,11 +687,10 @@ function resample_path(path, n, spacing, keep_corners, closed=true) =
// left(4000) polygon(calif);
// right(4000) color("lightgreen") polygon(newpoly);

function simplify_path(path, maxerr, closed=false) =
is_1region(path) ? simplify_path(path[0], maxerr, default(closed,true)) :
let(closed=default(closed,false))
function simplify_path(path, maxerr, closed=true) =
let(path = force_path(path))
assert(is_bool(closed))
assert(is_path(path), "\nInvalid 2D or 3D path or 1-region.")
assert(is_path(path,undef), "\nInvalid path or 1-region.")
assert(is_num(maxerr) && maxerr>0, "\nParameter 'maxerr' must be a positive number.")
let(
n = len(path),
Expand Down
2 changes: 1 addition & 1 deletion rounding.scad
Original file line number Diff line number Diff line change
Expand Up @@ -4882,6 +4882,7 @@ module prism_connector(profile, desc1, anchor1, desc2, anchor2, shift1, shift2,
move(base_root)rot(from=UP,to=prism_axis)
linear_extrude(height=norm(base_root-aux_root))zrot(base_spin-spin)polygon(profile);
else{
change_anchors(alias=[["end1","root"],["end2","end"]], remove=["root","end"])
join_prism(zrot(base_spin-spin,profile),
base=base_type, base_r=u_mul(base_r,base_inside),
aux=aux_type, aux_T=aux_T, aux_r=u_mul(aux_r,aux_inside),
Expand All @@ -4892,7 +4893,6 @@ module prism_connector(profile, desc1, anchor1, desc2, anchor2, shift1, shift2,
base_smooth_normals = base_smooth_normals, aux_smooth_normals=aux_smooth_normals,
debug=debug,
_name1="desc1", _name2="desc2")
change_anchors(alias=[["end1","root"],["end2","end"]], remove=["root","end"])
children();
}
}
Expand Down
Loading
Loading