My first attempt at putting text on an arc used literally thousands of list
comprehensions. After reading posts here and experimenting, I figured out
how to re-implement it with recursive functions.
Old way sample render time: Total rendering time: 0:00:03.286
New way sample render time: Total rendering time: 0:00:00.167
These are on a pretty hefty computer. On my older MacBook, it took a lot
longer!
Thanks to everyone who helped me dive deeper into the OpenSCAD mindset.
Hi Fatherhorton!
Am Fr., 18. Feb. 2022 um 18:47 Uhr schrieb Father Horton <
fatherhorton@gmail.com>:
[..] putting text on an arc
[..] implement it with recursive functions.
Do you have more on this topic? I would also be interested..
dpa asked if I could explain how to implement/debug recursive functions,
and I thought this would be a topic of interest to many (or at least
several), so here goes:
My code is here: https://www.thingiverse.com/thing:4886196
It actually deals with text along an ellipse, which is harder than text on
a circular arc, because there's no closed form equation to calculate
distances along an arc. (On a circle, where you can use trig, it can be
done fairly easily as a simple list comprehension.) So what I have to do is
to step along the outside of the ellipse until I'm far enough away from the
previous character. That looks like this:
function get_angle(from, width, step = 1 / precision, guess = 0) =
dist(points(from), points(from + guess)) > width ?
from + guess :
get_angle(from, width, step, guess + step);
where from is the starting angle, width is the width of the letter,
and step controls
how small the steps along the ellipse are; smaller steps = greater accuracy
but more time. guess is mainly used to step along the function, but you can
start with an initial estimate if there's a reason to. points(angle) returns
the coordinates of the point on the circumference of the ellipse at the
given angle. dist returns the distance between its arguments.
Stage 1: If the distance between the starting point and the point indicated
by from+guess is sufficient, we're done, and the desired angle is from+guess
Stage 2: If it's not enough, we recurse, incrementing guess so that the
guessed point moves along the ellipse.
My first try didn't work--it recursed indefinitely. One way to debug
recursive functions is to invoke them with arguments that should trigger
the termination clause so it exits. That worked, so I kept poking around
until I noticed that I was recursing like this: get_angle(from, width,
step, guess), so that the guess never moved. (In my defense, I had been
working on this too long without a break.)
My end goal is to produce a list of the angles for each letter, and that
looks like this:
function get_angles(list, from, half, i = 0) =
len(list) == i + 1 ?
get_angle(from, half + widths[i] / 2) :
let (a = get_angle(from, half + widths[i] / 2)) concat(a,
get_angles(list, a, widths[i] / 2, i + 1));
where list is the list of letters, widths is a ist of letter widths, and
half is half the width of the previous letter (we're actually going from
midpoint to midpoint). The variable i is used to move along the list.
Stage 1: If we're on the last letter, then this is just a simple invocation
of get_angle. But I goofed on the conditional, forgetting that len(list) needs
to be compared to i + 1 instead of just i. That gave me an undef warning
that was easy enough to clear once I woke up.
Stage 2: The let statement is really handy here. I only have to calculate
the angle for the letter once. I take the angle I found, and concatenate it
to the list from the succeeding points--so the list actually gets built
from the last letter forward.
This gave me weird results too. I tried invoking it with a shorter list,
without finding the problem ... until I noticed that get_angle was
returning just guess instead of from + guess, so the problem wasn't
with get_angles
at all.
In terms of debugging tips:
I hope all of that is both helpful and of some interest.
I implemented code to write text along an arbitrary path. You could
of course compute points on an ellipse and use that as your path.
This code is in BOSL2 if you want to take a look. The way I see it,
you need a list of letter positions, which you can either build using
textmetrics if you're using a dev version, or you can use a fixed
width font. Once you know the position in the path you need the
tangent and normal to position the letter. If you know the distance
you need to go to position a letter then you can walk the path,
summing up segments until you find the segment that contains the
desired point. Then you linearly interpolate along that segment to
get the exact location. If you're working in 2d you can just get the
tangent from the orientation of that segment. In 3d things are
somewhat more complicated.
There are several examples you can take a look at here:
https://github.com/revarbat/BOSL2/wiki/shapes3d.scad#module-path_text
On Fri, Feb 18, 2022 at 7:00 PM Father Horton fatherhorton@gmail.com wrote:
dpa asked if I could explain how to implement/debug recursive functions, and I thought this would be a topic of interest to many (or at least several), so here goes:
My code is here: https://www.thingiverse.com/thing:4886196
It actually deals with text along an ellipse, which is harder than text on a circular arc, because there's no closed form equation to calculate distances along an arc. (On a circle, where you can use trig, it can be done fairly easily as a simple list comprehension.) So what I have to do is to step along the outside of the ellipse until I'm far enough away from the previous character. That looks like this:
function get_angle(from, width, step = 1 / precision, guess = 0) = dist(points(from), points(from + guess)) > width ?
from + guess :
get_angle(from, width, step, guess + step);
where from is the starting angle, width is the width of the letter, and step controls how small the steps along the ellipse are; smaller steps = greater accuracy but more time. guess is mainly used to step along the function, but you can start with an initial estimate if there's a reason to. points(angle) returns the coordinates of the point on the circumference of the ellipse at the given angle. dist returns the distance between its arguments.
Stage 1: If the distance between the starting point and the point indicated by from+guess is sufficient, we're done, and the desired angle is from+guess
Stage 2: If it's not enough, we recurse, incrementing guess so that the guessed point moves along the ellipse.
My first try didn't work--it recursed indefinitely. One way to debug recursive functions is to invoke them with arguments that should trigger the termination clause so it exits. That worked, so I kept poking around until I noticed that I was recursing like this: get_angle(from, width, step, guess), so that the guess never moved. (In my defense, I had been working on this too long without a break.)
My end goal is to produce a list of the angles for each letter, and that looks like this:
function get_angles(list, from, half, i = 0) =
len(list) == i + 1 ?
get_angle(from, half + widths[i] / 2) :
let (a = get_angle(from, half + widths[i] / 2)) concat(a, get_angles(list, a, widths[i] / 2, i + 1));
where list is the list of letters, widths is a ist of letter widths, and half is half the width of the previous letter (we're actually going from midpoint to midpoint). The variable i is used to move along the list.
Stage 1: If we're on the last letter, then this is just a simple invocation of get_angle. But I goofed on the conditional, forgetting that len(list) needs to be compared to i + 1 instead of just i. That gave me an undef warning that was easy enough to clear once I woke up.
Stage 2: The let statement is really handy here. I only have to calculate the angle for the letter once. I take the angle I found, and concatenate it to the list from the succeeding points--so the list actually gets built from the last letter forward.
This gave me weird results too. I tried invoking it with a shorter list, without finding the problem ... until I noticed that get_angle was returning just guess instead of from + guess, so the problem wasn't with get_angles at all.
In terms of debugging tips:
I hope all of that is both helpful and of some interest.
OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org
I must have missed that when I was looking at the library. What you
describe is pretty much what I did (in 2D).
On Sun, Feb 20, 2022 at 6:33 PM Adrian Mariano avm4@cornell.edu wrote:
I implemented code to write text along an arbitrary path. You could
of course compute points on an ellipse and use that as your path.
This code is in BOSL2 if you want to take a look. The way I see it,
you need a list of letter positions, which you can either build using
textmetrics if you're using a dev version, or you can use a fixed
width font. Once you know the position in the path you need the
tangent and normal to position the letter. If you know the distance
you need to go to position a letter then you can walk the path,
summing up segments until you find the segment that contains the
desired point. Then you linearly interpolate along that segment to
get the exact location. If you're working in 2d you can just get the
tangent from the orientation of that segment. In 3d things are
somewhat more complicated.
There are several examples you can take a look at here:
https://github.com/revarbat/BOSL2/wiki/shapes3d.scad#module-path_text
On Fri, Feb 18, 2022 at 7:00 PM Father Horton fatherhorton@gmail.com
wrote:
dpa asked if I could explain how to implement/debug recursive functions,
and I thought this would be a topic of interest to many (or at least
several), so here goes:
My code is here: https://www.thingiverse.com/thing:4886196
It actually deals with text along an ellipse, which is harder than text
on a circular arc, because there's no closed form equation to calculate
distances along an arc. (On a circle, where you can use trig, it can be
done fairly easily as a simple list comprehension.) So what I have to do is
to step along the outside of the ellipse until I'm far enough away from the
previous character. That looks like this:
function get_angle(from, width, step = 1 / precision, guess = 0) =
dist(points(from), points(from + guess)) > width ?
from + guess :
get_angle(from, width, step, guess + step);
where from is the starting angle, width is the width of the letter, and
step controls how small the steps along the ellipse are; smaller steps =
greater accuracy but more time. guess is mainly used to step along the
function, but you can start with an initial estimate if there's a reason
to. points(angle) returns the coordinates of the point on the circumference
of the ellipse at the given angle. dist returns the distance between its
arguments.
Stage 1: If the distance between the starting point and the point
indicated by from+guess is sufficient, we're done, and the desired angle is
from+guess
Stage 2: If it's not enough, we recurse, incrementing guess so that the
guessed point moves along the ellipse.
My first try didn't work--it recursed indefinitely. One way to debug
recursive functions is to invoke them with arguments that should trigger
the termination clause so it exits. That worked, so I kept poking around
until I noticed that I was recursing like this: get_angle(from, width,
step, guess), so that the guess never moved. (In my defense, I had been
working on this too long without a break.)
My end goal is to produce a list of the angles for each letter, and that
looks like this:
function get_angles(list, from, half, i = 0) =
len(list) == i + 1 ?
get_angle(from, half + widths[i] / 2) :
let (a = get_angle(from, half + widths[i] / 2)) concat(a,
get_angles(list, a, widths[i] / 2, i + 1));
where list is the list of letters, widths is a ist of letter widths, and
half is half the width of the previous letter (we're actually going from
midpoint to midpoint). The variable i is used to move along the list.
Stage 1: If we're on the last letter, then this is just a simple
invocation of get_angle. But I goofed on the conditional, forgetting that
len(list) needs to be compared to i + 1 instead of just i. That gave me an
undef warning that was easy enough to clear once I woke up.
Stage 2: The let statement is really handy here. I only have to
calculate the angle for the letter once. I take the angle I found, and
concatenate it to the list from the succeeding points--so the list actually
gets built from the last letter forward.
This gave me weird results too. I tried invoking it with a shorter list,
without finding the problem ... until I noticed that get_angle was
returning just guess instead of from + guess, so the problem wasn't with
get_angles at all.
In terms of debugging tips:
function, not a statement! So in get_angles, I can write:
let (a = get_angle(from, half + widths[i] / 2), dummy = echo(a))
concat(a, get_angles(list, a, widths[i] / 2, i + 1));
and see each angle as it's generated. The result of the function does
have to be assigned to something, even though the result is always undef.
I hope all of that is both helpful and of some interest.
OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org
OpenSCAD mailing list
To unsubscribe send an email to discuss-leave@lists.openscad.org