discuss@lists.openscad.org

OpenSCAD general discussion Mailing-list

View all threads

Oh the things you will learn!

FH
Father Horton
Fri, Feb 18, 2022 5:46 PM

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.

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.
D
dpa
Fri, Feb 18, 2022 7:25 PM

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

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..
FH
Father Horton
Fri, Feb 18, 2022 11:59 PM

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:

  1. Make sure the exit test works.
  2. Worth with small lists.
  3. Echo the results. Something handy I just discovered: echo() is a
    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.

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: 1) Make sure the exit test works. 2) Worth with small lists. 3) Echo the results. Something handy I just discovered: echo() is a 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.
AM
Adrian Mariano
Mon, Feb 21, 2022 12:32 AM

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:

  1. Make sure the exit test works.
  2. Worth with small lists.
  3. Echo the results. Something handy I just discovered: echo() is a 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

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: > 1) Make sure the exit test works. > 2) Worth with small lists. > 3) Echo the results. Something handy I just discovered: echo() is a 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
FH
Father Horton
Mon, Feb 21, 2022 12:45 AM

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:

  1. Make sure the exit test works.
  2. Worth with small lists.
  3. Echo the results. Something handy I just discovered: echo() is a

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

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: > > 1) Make sure the exit test works. > > 2) Worth with small lists. > > 3) Echo the results. Something handy I just discovered: echo() is a > 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 >