From 814a3981ce61e60df7f3c737508c657de40c08e8 Mon Sep 17 00:00:00 2001 From: Adrian Mariano Date: Tue, 7 Sep 2021 22:49:15 -0400 Subject: [PATCH] fix doc errors extend path_text to 2d --- attachments.scad | 524 ++++++++++++++++++++++++----------------------- debug.scad | 2 +- paths.scad | 84 +++++--- 3 files changed, 322 insertions(+), 288 deletions(-) diff --git a/attachments.scad b/attachments.scad index af85a4b..cfceef4 100644 --- a/attachments.scad +++ b/attachments.scad @@ -497,6 +497,257 @@ module hulling(a) } } + + +// Section: Attachable Masks + + +// Module: edge_mask() +// Usage: +// edge_mask([edges], [except]) {...} +// Topics: Attachments +// See Also: attachable(), position(), attach(), face_profile(), edge_profile(), corner_mask() +// Description: +// Takes a 3D mask shape, and attaches it to the given edges, with the appropriate orientation to be +// `diff()`ed away. The mask shape should be vertically oriented (Z-aligned) with the back-right +// quadrant (X+Y+) shaped to be diffed away from the edge of parent attachable shape. For a more +// step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]]. +// Figure: A Typical Edge Rounding Mask +// module roundit(l,r) difference() { +// translate([-1,-1,-l/2]) +// cube([r+1,r+1,l]); +// translate([r,r]) +// cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4)); +// } +// roundit(l=30,r=10); +// Arguments: +// edges = Edges to mask. See the docs for [`edges()`](edges.scad#edges) to see acceptable values. Default: All edges. +// except = Edges to explicitly NOT mask. See the docs for [`edges()`](edges.scad#edges) to see acceptable values. Default: No edges. +// Side Effects: +// Sets `$tags = "mask"` for all children. +// Example: +// diff("mask") +// cube([50,60,70],center=true) +// edge_mask([TOP,"Z"],except=[BACK,TOP+LEFT]) +// rounding_mask_z(l=71,r=10); +module edge_mask(edges=EDGES_ALL, except=[]) { + assert($parent_geom != undef, "No object to attach to!"); + edges = edges(edges, except=except); + vecs = [ + for (i = [0:3], axis=[0:2]) + if (edges[axis][i]>0) + EDGE_OFFSETS[axis][i] + ]; + for (vec = vecs) { + vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0); + assert(vcount == 2, "Not an edge vector!"); + anch = _find_anchor(vec, $parent_geom); + $attach_to = undef; + $attach_anchor = anch; + $attach_norot = true; + $tags = "mask"; + rotang = + vec.z<0? [90,0,180+v_theta(vec)] : + vec.z==0 && sign(vec.x)==sign(vec.y)? 135+v_theta(vec) : + vec.z==0 && sign(vec.x)!=sign(vec.y)? [0,180,45+v_theta(vec)] : + [-90,0,180+v_theta(vec)]; + translate(anch[1]) rot(rotang) children(); + } +} + + +// Module: corner_mask() +// Usage: +// corner_mask([corners], [except]) {...} +// Topics: Attachments +// See Also: attachable(), position(), attach(), face_profile(), edge_profile(), edge_mask() +// Description: +// Takes a 3D mask shape, and attaches it to the given corners, with the appropriate orientation to +// be `diff()`ed away. The 3D corner mask shape should be designed to mask away the X+Y+Z+ octant. +// For a more step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]]. +// Arguments: +// corners = Edges to mask. See the docs for [`corners()`](edges.scad#corners) to see acceptable values. Default: All corners. +// except = Edges to explicitly NOT mask. See the docs for [`corners()`](edges.scad#corners) to see acceptable values. Default: No corners. +// Side Effects: +// Sets `$tags = "mask"` for all children. +// Example: +// diff("mask") +// cube(100, center=true) +// corner_mask([TOP,FRONT],LEFT+FRONT+TOP) +// difference() { +// translate(-0.01*[1,1,1]) cube(20); +// translate([20,20,20]) sphere(r=20); +// } +module corner_mask(corners=CORNERS_ALL, except=[]) { + assert($parent_geom != undef, "No object to attach to!"); + corners = corners(corners, except=except); + vecs = [for (i = [0:7]) if (corners[i]>0) CORNER_OFFSETS[i]]; + for (vec = vecs) { + vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0); + assert(vcount == 3, "Not an edge vector!"); + anch = _find_anchor(vec, $parent_geom); + $attach_to = undef; + $attach_anchor = anch; + $attach_norot = true; + $tags = "mask"; + rotang = vec.z<0? + [ 0,0,180+v_theta(vec)-45] : + [180,0,-90+v_theta(vec)-45]; + translate(anch[1]) rot(rotang) children(); + } +} + + +// Module: face_profile() +// Usage: +// face_profile(faces, r|d=, [convexity=]) {...} +// Topics: Attachments +// See Also: attachable(), position(), attach(), edge_profile(), corner_profile() +// Description: +// Given a 2D edge profile, extrudes it into a mask for all edges and corners bounding each given face. +// For a more step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]]. +// Arguments: +// faces = Faces to mask edges and corners of. +// r = Radius of corner mask. +// --- +// d = Diameter of corner mask. +// convexity = Max number of times a line could intersect the perimeter of the mask shape. Default: 10 +// Side Effects: +// Sets `$tags = "mask"` for all children. +// Example: +// diff("mask") +// cube([50,60,70],center=true) +// face_profile(TOP,r=10) +// mask2d_roundover(r=10); +module face_profile(faces=[], r, d, convexity=10) { + faces = is_vector(faces)? [faces] : faces; + assert(all([for (face=faces) is_vector(face) && sum([for (x=face) x!=0? 1 : 0])==1]), "Vector in faces doesn't point at a face."); + r = get_radius(r=r, d=d, dflt=undef); + assert(is_num(r) && r>0); + edge_profile(faces) children(); + corner_profile(faces, convexity=convexity, r=r) children(); +} + + +// Module: edge_profile() +// Usage: +// edge_profile([edges], [except], [convexity]) {...} +// Topics: Attachments +// See Also: attachable(), position(), attach(), face_profile(), corner_profile() +// Description: +// Takes a 2D mask shape and attaches it to the selected edges, with the appropriate orientation and +// extruded length to be `diff()`ed away, to give the edge a matching profile. For a more step-by-step +// explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]]. +// Arguments: +// edges = Edges to mask. See the docs for [`edges()`](edges.scad#edges) to see acceptable values. Default: All edges. +// except = Edges to explicitly NOT mask. See the docs for [`edges()`](edges.scad#edges) to see acceptable values. Default: No edges. +// convexity = Max number of times a line could intersect the perimeter of the mask shape. Default: 10 +// Side Effects: +// Sets `$tags = "mask"` for all children. +// Example: +// diff("mask") +// cube([50,60,70],center=true) +// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT]) +// mask2d_roundover(r=10, inset=2); +module edge_profile(edges=EDGES_ALL, except=[], convexity=10) { + assert($parent_geom != undef, "No object to attach to!"); + edges = edges(edges, except=except); + vecs = [ + for (i = [0:3], axis=[0:2]) + if (edges[axis][i]>0) + EDGE_OFFSETS[axis][i] + ]; + for (vec = vecs) { + vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0); + assert(vcount == 2, "Not an edge vector!"); + anch = _find_anchor(vec, $parent_geom); + $attach_to = undef; + $attach_anchor = anch; + $attach_norot = true; + $tags = "mask"; + psize = point3d($parent_size); + length = [for (i=[0:2]) if(!vec[i]) psize[i]][0]+0.1; + rotang = + vec.z<0? [90,0,180+v_theta(vec)] : + vec.z==0 && sign(vec.x)==sign(vec.y)? 135+v_theta(vec) : + vec.z==0 && sign(vec.x)!=sign(vec.y)? [0,180,45+v_theta(vec)] : + [-90,0,180+v_theta(vec)]; + translate(anch[1]) { + rot(rotang) { + linear_extrude(height=length, center=true, convexity=convexity) { + children(); + } + } + } + } +} + +// Module: corner_profile() +// Usage: +// corner_profile([corners], [except], , [convexity=]) {...} +// Topics: Attachments +// See Also: attachable(), position(), attach(), face_profile(), edge_profile() +// Description: +// Takes a 2D mask shape, rotationally extrudes and converts it into a corner mask, and attaches it +// to the selected corners with the appropriate orientation. Tags it as a "mask" to allow it to be +// `diff()`ed away, to give the corner a matching profile. For a more step-by-step explanation of +// attachments, see the [[Attachments Tutorial|Tutorial-Attachments]]. +// Arguments: +// corners = Edges to mask. See the docs for [`corners()`](edges.scad#corners) to see acceptable values. Default: All corners. +// except = Edges to explicitly NOT mask. See the docs for [`corners()`](edges.scad#corners) to see acceptable values. Default: No corners. +// --- +// r = Radius of corner mask. +// d = Diameter of corner mask. +// convexity = Max number of times a line could intersect the perimeter of the mask shape. Default: 10 +// Side Effects: +// Sets `$tags = "mask"` for all children. +// Example: +// diff("mask") +// cuboid([50,60,70],rounding=10,edges="Z",anchor=CENTER) { +// corner_profile(BOT,r=10) +// mask2d_teardrop(r=10, angle=40); +// } +module corner_profile(corners=CORNERS_ALL, except=[], r, d, convexity=10) { + assert($parent_geom != undef, "No object to attach to!"); + r = get_radius(r=r, d=d, dflt=undef); + assert(is_num(r)); + corners = corners(corners, except=except); + vecs = [for (i = [0:7]) if (corners[i]>0) CORNER_OFFSETS[i]]; + for (vec = vecs) { + vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0); + assert(vcount == 3, "Not an edge vector!"); + anch = _find_anchor(vec, $parent_geom); + $attach_to = undef; + $attach_anchor = anch; + $attach_norot = true; + $tags = "mask"; + rotang = vec.z<0? + [ 0,0,180+v_theta(vec)-45] : + [180,0,-90+v_theta(vec)-45]; + translate(anch[1]) { + rot(rotang) { + render(convexity=convexity) + difference() { + translate(-0.1*[1,1,1]) cube(r+0.1, center=false); + right(r) back(r) zrot(180) { + rotate_extrude(angle=90, convexity=convexity) { + xflip() left(r) { + difference() { + square(r,center=false); + children(); + } + } + } + } + } + } + } + } +} + + + + // Section: Making your objects attachable @@ -915,8 +1166,8 @@ function reorient( // Usage: VNF Geometry // geom = _attach_geom(vnf=, [extent=], ...); // -// Topics: Attachments -// See Also: reorient(), attachable() +/// Topics: Attachments +/// See Also: reorient(), attachable() // // Description: // Given arguments that describe the geometry of an attachable object, returns the internal geometry description. @@ -1093,8 +1344,8 @@ function _attach_geom( /// Internal Function: _attach_geom_2d() // Usage: // bool = _attach_geom_2d(geom); -// Topics: Attachments -// See Also: reorient(), attachable() +/// Topics: Attachments +/// See Also: reorient(), attachable() // Description: // Returns true if the given attachment geometry description is for a 2D shape. function _attach_geom_2d(geom) = @@ -1106,8 +1357,8 @@ function _attach_geom_2d(geom) = /// Internal Function: _attach_geom_size() // Usage: // bounds = _attach_geom_size(geom); -// Topics: Attachments -// See Also: reorient(), attachable() +/// Topics: Attachments +/// See Also: reorient(), attachable() // Description: // Returns the `[X,Y,Z]` bounding size for the given attachment geometry description. function _attach_geom_size(geom) = @@ -1172,8 +1423,8 @@ function _attach_geom_size(geom) = // mat = _attach_transform(anchor, spin, orient, geom); // Usage: To Transform Points, Paths, Patches, or VNFs // new_p = _attach_transform(anchor, spin, orient, geom, p); -// Topics: Attachments -// See Also: reorient(), attachable() +/// Topics: Attachments +/// See Also: reorient(), attachable() // Description: // Returns the affine3d transformation matrix needed to `anchor`, `spin`, and `orient` // the given geometry `geom` shape into position. @@ -1245,261 +1496,12 @@ function _attach_transform(anchor, spin, orient, geom, p) = apply(m, p); -// Section: Attachable Masks - - - - - -// Module: edge_mask() -// Usage: -// edge_mask([edges], [except]) {...} -// Topics: Attachments -// See Also: attachable(), position(), attach(), face_profile(), edge_profile(), corner_mask() -// Description: -// Takes a 3D mask shape, and attaches it to the given edges, with the appropriate orientation to be -// `diff()`ed away. The mask shape should be vertically oriented (Z-aligned) with the back-right -// quadrant (X+Y+) shaped to be diffed away from the edge of parent attachable shape. For a more -// step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]]. -// Figure: A Typical Edge Rounding Mask -// module roundit(l,r) difference() { -// translate([-1,-1,-l/2]) -// cube([r+1,r+1,l]); -// translate([r,r]) -// cylinder(h=l+1,r=r,center=true, $fn=quantup(segs(r),4)); -// } -// roundit(l=30,r=10); -// Arguments: -// edges = Edges to mask. See the docs for [`edges()`](edges.scad#edges) to see acceptable values. Default: All edges. -// except = Edges to explicitly NOT mask. See the docs for [`edges()`](edges.scad#edges) to see acceptable values. Default: No edges. -// Side Effects: -// Sets `$tags = "mask"` for all children. -// Example: -// diff("mask") -// cube([50,60,70],center=true) -// edge_mask([TOP,"Z"],except=[BACK,TOP+LEFT]) -// rounding_mask_z(l=71,r=10); -module edge_mask(edges=EDGES_ALL, except=[]) { - assert($parent_geom != undef, "No object to attach to!"); - edges = edges(edges, except=except); - vecs = [ - for (i = [0:3], axis=[0:2]) - if (edges[axis][i]>0) - EDGE_OFFSETS[axis][i] - ]; - for (vec = vecs) { - vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0); - assert(vcount == 2, "Not an edge vector!"); - anch = _find_anchor(vec, $parent_geom); - $attach_to = undef; - $attach_anchor = anch; - $attach_norot = true; - $tags = "mask"; - rotang = - vec.z<0? [90,0,180+v_theta(vec)] : - vec.z==0 && sign(vec.x)==sign(vec.y)? 135+v_theta(vec) : - vec.z==0 && sign(vec.x)!=sign(vec.y)? [0,180,45+v_theta(vec)] : - [-90,0,180+v_theta(vec)]; - translate(anch[1]) rot(rotang) children(); - } -} - - -// Module: corner_mask() -// Usage: -// corner_mask([corners], [except]) {...} -// Topics: Attachments -// See Also: attachable(), position(), attach(), face_profile(), edge_profile(), edge_mask() -// Description: -// Takes a 3D mask shape, and attaches it to the given corners, with the appropriate orientation to -// be `diff()`ed away. The 3D corner mask shape should be designed to mask away the X+Y+Z+ octant. -// For a more step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]]. -// Arguments: -// corners = Edges to mask. See the docs for [`corners()`](edges.scad#corners) to see acceptable values. Default: All corners. -// except = Edges to explicitly NOT mask. See the docs for [`corners()`](edges.scad#corners) to see acceptable values. Default: No corners. -// Side Effects: -// Sets `$tags = "mask"` for all children. -// Example: -// diff("mask") -// cube(100, center=true) -// corner_mask([TOP,FRONT],LEFT+FRONT+TOP) -// difference() { -// translate(-0.01*[1,1,1]) cube(20); -// translate([20,20,20]) sphere(r=20); -// } -module corner_mask(corners=CORNERS_ALL, except=[]) { - assert($parent_geom != undef, "No object to attach to!"); - corners = corners(corners, except=except); - vecs = [for (i = [0:7]) if (corners[i]>0) CORNER_OFFSETS[i]]; - for (vec = vecs) { - vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0); - assert(vcount == 3, "Not an edge vector!"); - anch = _find_anchor(vec, $parent_geom); - $attach_to = undef; - $attach_anchor = anch; - $attach_norot = true; - $tags = "mask"; - rotang = vec.z<0? - [ 0,0,180+v_theta(vec)-45] : - [180,0,-90+v_theta(vec)-45]; - translate(anch[1]) rot(rotang) children(); - } -} - - -// Module: face_profile() -// Usage: -// face_profile(faces, r|d=, [convexity=]) {...} -// Topics: Attachments -// See Also: attachable(), position(), attach(), edge_profile(), corner_profile() -// Description: -// Given a 2D edge profile, extrudes it into a mask for all edges and corners bounding each given face. -// For a more step-by-step explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]]. -// Arguments: -// faces = Faces to mask edges and corners of. -// r = Radius of corner mask. -// --- -// d = Diameter of corner mask. -// convexity = Max number of times a line could intersect the perimeter of the mask shape. Default: 10 -// Side Effects: -// Sets `$tags = "mask"` for all children. -// Example: -// diff("mask") -// cube([50,60,70],center=true) -// face_profile(TOP,r=10) -// mask2d_roundover(r=10); -module face_profile(faces=[], r, d, convexity=10) { - faces = is_vector(faces)? [faces] : faces; - assert(all([for (face=faces) is_vector(face) && sum([for (x=face) x!=0? 1 : 0])==1]), "Vector in faces doesn't point at a face."); - r = get_radius(r=r, d=d, dflt=undef); - assert(is_num(r) && r>0); - edge_profile(faces) children(); - corner_profile(faces, convexity=convexity, r=r) children(); -} - - -// Module: edge_profile() -// Usage: -// edge_profile([edges], [except], [convexity]) {...} -// Topics: Attachments -// See Also: attachable(), position(), attach(), face_profile(), corner_profile() -// Description: -// Takes a 2D mask shape and attaches it to the selected edges, with the appropriate orientation and -// extruded length to be `diff()`ed away, to give the edge a matching profile. For a more step-by-step -// explanation of attachments, see the [[Attachments Tutorial|Tutorial-Attachments]]. -// Arguments: -// edges = Edges to mask. See the docs for [`edges()`](edges.scad#edges) to see acceptable values. Default: All edges. -// except = Edges to explicitly NOT mask. See the docs for [`edges()`](edges.scad#edges) to see acceptable values. Default: No edges. -// convexity = Max number of times a line could intersect the perimeter of the mask shape. Default: 10 -// Side Effects: -// Sets `$tags = "mask"` for all children. -// Example: -// diff("mask") -// cube([50,60,70],center=true) -// edge_profile([TOP,"Z"],except=[BACK,TOP+LEFT]) -// mask2d_roundover(r=10, inset=2); -module edge_profile(edges=EDGES_ALL, except=[], convexity=10) { - assert($parent_geom != undef, "No object to attach to!"); - edges = edges(edges, except=except); - vecs = [ - for (i = [0:3], axis=[0:2]) - if (edges[axis][i]>0) - EDGE_OFFSETS[axis][i] - ]; - for (vec = vecs) { - vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0); - assert(vcount == 2, "Not an edge vector!"); - anch = _find_anchor(vec, $parent_geom); - $attach_to = undef; - $attach_anchor = anch; - $attach_norot = true; - $tags = "mask"; - psize = point3d($parent_size); - length = [for (i=[0:2]) if(!vec[i]) psize[i]][0]+0.1; - rotang = - vec.z<0? [90,0,180+v_theta(vec)] : - vec.z==0 && sign(vec.x)==sign(vec.y)? 135+v_theta(vec) : - vec.z==0 && sign(vec.x)!=sign(vec.y)? [0,180,45+v_theta(vec)] : - [-90,0,180+v_theta(vec)]; - translate(anch[1]) { - rot(rotang) { - linear_extrude(height=length, center=true, convexity=convexity) { - children(); - } - } - } - } -} - -// Module: corner_profile() -// Usage: -// corner_profile([corners], [except], , [convexity=]) {...} -// Topics: Attachments -// See Also: attachable(), position(), attach(), face_profile(), edge_profile() -// Description: -// Takes a 2D mask shape, rotationally extrudes and converts it into a corner mask, and attaches it -// to the selected corners with the appropriate orientation. Tags it as a "mask" to allow it to be -// `diff()`ed away, to give the corner a matching profile. For a more step-by-step explanation of -// attachments, see the [[Attachments Tutorial|Tutorial-Attachments]]. -// Arguments: -// corners = Edges to mask. See the docs for [`corners()`](edges.scad#corners) to see acceptable values. Default: All corners. -// except = Edges to explicitly NOT mask. See the docs for [`corners()`](edges.scad#corners) to see acceptable values. Default: No corners. -// --- -// r = Radius of corner mask. -// d = Diameter of corner mask. -// convexity = Max number of times a line could intersect the perimeter of the mask shape. Default: 10 -// Side Effects: -// Sets `$tags = "mask"` for all children. -// Example: -// diff("mask") -// cuboid([50,60,70],rounding=10,edges="Z",anchor=CENTER) { -// corner_profile(BOT,r=10) -// mask2d_teardrop(r=10, angle=40); -// } -module corner_profile(corners=CORNERS_ALL, except=[], r, d, convexity=10) { - assert($parent_geom != undef, "No object to attach to!"); - r = get_radius(r=r, d=d, dflt=undef); - assert(is_num(r)); - corners = corners(corners, except=except); - vecs = [for (i = [0:7]) if (corners[i]>0) CORNER_OFFSETS[i]]; - for (vec = vecs) { - vcount = (vec.x?1:0) + (vec.y?1:0) + (vec.z?1:0); - assert(vcount == 3, "Not an edge vector!"); - anch = _find_anchor(vec, $parent_geom); - $attach_to = undef; - $attach_anchor = anch; - $attach_norot = true; - $tags = "mask"; - rotang = vec.z<0? - [ 0,0,180+v_theta(vec)-45] : - [180,0,-90+v_theta(vec)-45]; - translate(anch[1]) { - rot(rotang) { - render(convexity=convexity) - difference() { - translate(-0.1*[1,1,1]) cube(r+0.1, center=false); - right(r) back(r) zrot(180) { - rotate_extrude(angle=90, convexity=convexity) { - xflip() left(r) { - difference() { - square(r,center=false); - children(); - } - } - } - } - } - } - } - } -} - /// Internal Function: _find_anchor() // Usage: // anchorinfo = _find_anchor(anchor, geom); -// Topics: Attachments -// See Also: reorient(), attachable() +/// Topics: Attachments +/// See Also: reorient(), attachable() // Description: // Calculates the anchor data for the given `anchor` vector or name, in the given attachment // geometry. Returns `[ANCHOR, POS, VEC, ANG]` where `ANCHOR` is the requested anchorname @@ -1743,8 +1745,8 @@ function _find_anchor(anchor, geom) = /// Internal Function: _attachment_is_shown() // Usage: // bool = _attachment_is_shown(tags); -// Topics: Attachments -// See Also: reorient(), attachable() +/// Topics: Attachments +/// See Also: reorient(), attachable() // Description: // Returns true if shapes tagged with any of the given space-delimited string of tag names should currently be shown. function _attachment_is_shown(tags) = diff --git a/debug.scad b/debug.scad index 73afb13..2289cb6 100644 --- a/debug.scad +++ b/debug.scad @@ -365,7 +365,7 @@ module expose_anchors(opacity=0.2) { // cube(50, center=true) show_anchors(); module show_anchors(s=10, std=true, custom=true) { check = assert($parent_geom != undef) 1; - two_d = attach_geom_2d($parent_geom); + two_d = _attach_geom_2d($parent_geom); if (std) { for (anchor=standard_anchors(two_d=two_d)) { if(two_d) { diff --git a/paths.scad b/paths.scad index 7529230..a3f9b08 100644 --- a/paths.scad +++ b/paths.scad @@ -1476,16 +1476,17 @@ function _cut_interp(pathcut, path, data) = // path_text(path, text, [size], [thickness], [font], [lettersize], [offset], [reverse], [normal], [top], [textmetrics]) // Description: // Place the text letter by letter onto the specified path using textmetrics (if available and requested) -// or user specified letter spacing. By default letters are positioned on the tangent line to the path with the path normal +// or user specified letter spacing. The path can be 2D or 3D. In 2D the text appears along the path with letters upright +// as determined by the path direction. In 3D by default letters are positioned on the tangent line to the path with the path normal // pointing toward the reader. The path normal points away from the center of curvature (the opposite of the normal produced // by path_normals()). Note that this means that if the center of curvature switches sides the text will flip upside down. -// If you want text on such a path you must supply your own normal or top vector. +// If you want text on such a path you must supply your own normal or top vector. // . -// Text appears starting at the beginning of the path, so if the path moves right to left -// then a left-to-right reading language will display in the wrong order. The text appears positioned to be -// read from "outside" of the curve (from a point on the other side of the curve from the center of curvature). -// If you need the text to read properly from the inside, you can set reverse to true to flip the text, or supply -// your own normal. +// Text appears starting at the beginning of the path, so if the 3D path moves right to left +// then a left-to-right reading language will display in the wrong order. (For a 2D path text will appear upside down.) +// The text for a 3D path appears positioned to be read from "outside" of the curve (from a point on the other side of the +// curve from the center of curvature). If you need the text to read properly from the inside, you can set reverse to +// true to flip the text, or supply your own normal. // . // If you do not have the experimental textmetrics feature enabled then you must specify the space for the letters // using lettersize, which can be a scalar or array. You will have the easiest time getting good results by using @@ -1505,14 +1506,14 @@ function _cut_interp(pathcut, path, data) = // path = path to place the text on // text = text to create // size = font size -// thickness = thickness of letters +// thickness = thickness of letters (not allowed for 2D path) // font = font to use // --- // lettersize = scalar or array giving size of letters -// offset = distance to shift letters "up" (towards the reader). Default: 0 -// normal = direction or list of directions pointing towards the reader of the text +// offset = distance to shift letters "up" (towards the reader). Not allowed for 2D path. Default: 0 +// normal = direction or list of directions pointing towards the reader of the text. Not allowed for 2D path. // top = direction or list of directions pointing toward the top of the text -// reverse = reverse the letters if true. Default: false +// reverse = reverse the letters if true. Not allowed for 2D path. Default: false // textmetrics = if set to true and lettersize is not given then use the experimental textmetrics feature. You must be running a dev snapshot that includes this feature and have the feature turned on in your preferences. Default: false // Example: The examples use Courier, a monospaced font. The width is 1/1.2 times the specified size for this font. This text could wrap around a cylinder. // path = path3d(arc(100, r=25, angle=[245, 370])); @@ -1550,6 +1551,14 @@ function _cut_interp(pathcut, path, data) = // path = arc(100, points = [[-20, 0, 20], [0,0,5], [20,0,20]]); // color("red")stroke(path,width=.2); // path_text(path, "Example Text", size=5, lettersize=5/1.2, font="Courier", top=UP); +// Example: This sine wave wrapped around the cylinder has a twisting normal that produces wild letter layout. We fix it with a custom normal which is different at every path point. +// path = [for(theta = [0:360]) [25*cos(theta), 25*sin(theta), 4*cos(theta*4)]]; +// normal = [for(theta = [0:360]) [cos(theta), sin(theta),0]]; +// zrot(-120) +// difference(){ +// cyl(r=25, h=20, $fn=120); +// path_text(path, "A sine wave wiggles", font="Courier", lettersize=5/1.2, size=5, normal=normal); +// } // Example: The path center of curvature changes, and the text flips. // path = zrot(-120,p=path3d( concat(arc(100, r=25, angle=[0,90]), back(50,p=arc(100, r=25, angle=[268, 180]))))); // color("red")stroke(path,width=.2); @@ -1558,14 +1567,30 @@ function _cut_interp(pathcut, path, data) = // path = zrot(-120,p=path3d( concat(arc(100, r=25, angle=[0,90]), back(50,p=arc(100, r=25, angle=[268, 180]))))); // color("red")stroke(path,width=.2); // path_text(path, "A shorter example", size=5, lettersize=5/1.2, font="Courier", thickness=2, top=UP); -module path_text(path, text, font, size, thickness=1, lettersize, offset=0, reverse=false, normal, top, textmetrics=false) +// Example(2D): With a 2D path instead of 3D there's no ambiguity about direction and it works by default: +// path = zrot(-120,p=concat(arc(100, r=25, angle=[0,90]), back(50,p=arc(100, r=25, angle=[268, 180])))); +// color("red")stroke(path,width=.2); +// path_text(path, "A shorter example", size=5, lettersize=5/1.2, font="Courier"); +module path_text(path, text, font, size, thickness, lettersize, offset=0, reverse=false, normal, top, textmetrics=false) { - dummy2=assert(num_defined([normal,top])<=1, "Cannot define both normal and top"); - normal = is_def(normal) && len(normal)==3 ? repeat(normal, len(path)) + dummy2=assert(is_path(path,[2,3]),"Must supply a 2d or 3d path") + assert(num_defined([normal,top])<=1, "Cannot define both \"normal\" and \"top\""); + dim = len(path[0]); + normalok = is_undef(normal) || is_vector(normal,3) || (is_path(normal,3) && len(normal)==len(path)); + topok = is_undef(top) || is_vector(top,dim) || (dim==2 && is_vector(top,3) && top[2]==0) + || (is_path(top,dim) && len(top)==len(path)); + dummy4 = assert(dim==3 || is_undef(thickness), "Cannot give a thickness with 2d path") + assert(dim==3 || !reverse, "Reverse not allowed with 2d path") + assert(dim==3 || offset==0, "Cannot give offset with 2d path") + assert(dim==3 || is_undef(normal), "Cannot define \"normal\" for a 2d path, only \"top\"") + assert(normalok,"\"normal\" must be a vector or path compatible with the given path") + assert(topok,"\"top\" must be a vector or path compatible with the given path"); + thickness = first_defined([thickness,1]); + normal = is_vector(normal) ? repeat(normal, len(path)) : is_def(normal) ? normal : undef; - top = is_def(top) && len(top)==3 ? repeat(top, len(path)) + top = is_vector(top) ? repeat(dim==2?point2d(top):top, len(path)) : is_def(top) ? top : undef; @@ -1583,25 +1608,32 @@ module path_text(path, text, font, size, thickness=1, lettersize, offset=0, reve normpts = is_undef(normal) ? (reverse?1:-1)*subindex(pts,3) : _cut_interp(pts,path, normal); toppts = is_undef(top) ? undef : _cut_interp(pts,path,top); for(i=idx(text)) - assert(!usetop || !approx(pts[i][2]*toppts[i],norm(top[i])*norm(pts[i][2])), + let( tangent = pts[i][2] ) + assert(!usetop || !approx(tangent*toppts[i],norm(top[i])*norm(tangent)), str("Specified top direction parallel to path at character ",i)) - assert(usetop || !approx(pts[i][2]*normpts[i],norm(normpts[i])*norm(pts[i][2])), + assert(usetop || !approx(tangent*normpts[i],norm(normpts[i])*norm(tangent)), str("Specified normal direction parallel to path at character ",i)) let( - adjustment = usetop ? (pts[i][2]*toppts[i])*toppts[i]/(toppts[i]*toppts[i]) - : usernorm ? (pts[i][2]*normpts[i])*normpts[i]/(normpts[i]*normpts[i]) + adjustment = usetop ? (tangent*toppts[i])*toppts[i]/(toppts[i]*toppts[i]) + : usernorm ? (tangent*normpts[i])*normpts[i]/(normpts[i]*normpts[i]) : [0,0,0] - ) move(pts[i][0]) - frame_map(x=pts[i][2]-adjustment, - z=usetop ? undef : normpts[i], - y=usetop ? toppts[i] : undef) - up(offset-thickness/2) - linear_extrude(height=thickness) - left(lsize[0]/2)text(text[i], font=font, size=size); + if(dim==3){ + frame_map(x=tangent-adjustment, + z=usetop ? undef : normpts[i], + y=usetop ? toppts[i] : undef) + up(offset-thickness/2) + linear_extrude(height=thickness) + left(lsize[0]/2)text(text[i], font=font, size=size); + } else { + frame_map(x=point3d(tangent-adjustment), y=point3d(usetop ? toppts[i] : -normpts[i])) + left(lsize[0]/2)text(text[i], font=font, size=size); + } } + + // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap