fix doc errors

extend path_text to 2d
This commit is contained in:
Adrian Mariano 2021-09-07 22:49:15 -04:00
parent c7fd5d05fa
commit 814a3981ce
3 changed files with 322 additions and 288 deletions

View file

@ -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], <r=|d=>, [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], <r=|d=>, [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) =

View file

@ -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) {

View file

@ -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