Hi,

Guest

N

neri-engineering

Wed, Jan 24, 2024 2:52 PM

Normally I would not include copious amounts of code in my email. I recently discovered the ability to recursively create lists of points & faces, where the size of these lists is undetermined at code time. It's determined at runtime. In other words I'm using append() trickery together with recursive function calls (*not* iterative). This technique had escaped me until now for some reason. Even though I studied computer science and learned about iterative vs. recursive approach, this technique of dynamically building points lists eluded me until now.

I build my helix consisting of one single polyhedron, defined with convexity=10. I use that helix as the subtrahend; the thing I subtract away from is a thin tube. I get missing faces in F5 render, but of course F6 renders okay. While this is not a showstopper I am experimenting with new strategies to do things in OpenSCAD, and was wondering if there is any "secret sauce" that I am missing to get the correct F5 render. Thanks.

Below is the self-contained code which I save to a SCAD file. There are no external deps; you can run the code to try it.

diameter=3.0;

pitch=0.5;

length=0.5+2*$t;

z_start=0.77-$t;

fn=23;

difference() {

translate([0,0,z_start+length/2]) {

difference() {

cylinder(r=metric_thread_major_radius(diameter,pitch),

h=length,

center=true,

$fn=fn);

cylinder(r=metric_thread_minor_radius(diameter,pitch),

h=length*1.125,

center=true,

$fn=fn);

}

}

external_metric_thread_plug(diameter, pitch, length,

z_start=z_start, n_starts=3,

fn=fn);

}

function metric_thread_angle()=

30;

function metric_thread_perfect_height(pitch)=

pitch/(2*tan(metric_thread_angle()));

// Up the radii of internal (female) threads in order to provide a little bit

// of wiggle room. This function can be more complex and can involve the

// diameter in its decision; right now it's rather simple. You can hack this.

function __thread_internal_radius_increase(diameter,pitch)=

0.1*metric_thread_perfect_height(pitch);

function metric_thread_major_radius(diameter,pitch,internal=false)=

diameter/2+(internal?__thread_internal_radius_increase(diameter,pitch):0);

// What portion of the total pitch is taken up by the angled thread section

// (and not the squared off valley and tip). The remaining distance will be

// divided equally between the squared off valley and tip.

function __thread_effective_ratio()=

0.7;

function metric_thread_minor_radius(diameter,pitch,internal=false)=

metric_thread_major_radius(diameter,pitch,internal)

-__thread_effective_ratio()*metric_thread_perfect_height(pitch);

// Where the major radius would be if the cuts were perfect, without flat spots

// in the valleys and on tips of threads.

function metric_thread_perfect_major_radius(diameter,pitch,internal=false)=

metric_thread_major_radius(diameter,pitch,internal)

+((1.0-__thread_effective_ratio())*

metric_thread_perfect_height(pitch)/2.0);

// Where the minor radius would be if the cuts were perfect, without flat spots

// in the valleys and on tips of threads.

function metric_thread_perfect_minor_radius(diameter,pitch,internal=false)=

metric_thread_perfect_major_radius(diameter,pitch,internal)

-metric_thread_perfect_height(pitch);

function metric_thread_segments(diameter,fn=-1)=

(fn<3)?min(50,max(ceil(diameter*6),25)):fn;

module external_metric_thread_plug(diameter,

pitch,

length,

z_start=0.0,

n_starts=1,

fn=-1)

{

n_segments = metric_thread_segments(diameter=diameter,fn=fn);

z_end = z_start+length;

epsilon = pitch/48; // Decrease outer radius by this amount.

outer_r = metric_thread_perfect_major_radius(diameter,pitch)-epsilon;

inner_r = metric_thread_perfect_minor_radius(diameter,pitch);

z_rot = (n_starts*pitch)/n_segments;
d_mid = epsilon*tan(metric_thread_angle());

for(thread=[0:n_starts-1])

{

start_turn = (z_start/pitch-1.0-thread)/n_starts;

end_turn = (z_end/pitch-thread)/n_starts;

n_curr = floor(start_turn*n_segments);
n_to_go = ceil(end_turn*n_segments)-n_curr+1;

z_off = thread*pitch;

polyhedron(points=__ext_thread_plug_points_builder(n_to_go,

n_curr,

n_segments,

outer_r,

inner_r,

z_rot,

pitch,

d_mid,

z_off),

faces=__ext_thread_plug_faces_builder(0,n_to_go-1),

convexity=10);

}

}

// Parameter 'n_to_go' is the number of slices to create. One polyhedron will

// have two slices, and this is the bare minumum. Parameter 'm_curr' tells

// where on the polyhedron spiral to start creating triangle slices; this value

// may be very large in magnitude, and it can be negative.

function __ext_thread_plug_points_builder(n_to_go,

n_curr,

n_segments, // These last seven

outer_r, // parameters are

inner_r, // unchanging as we

z_rot, // recurse.

pitch,

d_mid,

z_off)= // Z offset for multi-starts.

((n_to_go==0)?[]:

concat(__ext_thread_plug_points_builder(n_to_go-1,

n_curr+1,

n_segments,

outer_r,

inner_r,

z_rot,

pitch,

d_mid,

z_off),

// Modulo operator is used to reduce range & increase precision.

[[outer_r*cos(360*(n_curr%n_segments)/n_segments),

outer_r*sin(360*(n_curr%n_segments)/n_segments),

z_rot*n_curr+pitch-d_mid+z_off],
[outer_r*cos(360*(n_curr%n_segments)/n_segments),

outer_r

z_rot

[inner_r

inner_r

z_rot*n_curr+pitch/2+z_off]]));

// Parameter 'i' should be initialized to zero.

// Parameter 'n' should be set to the number of polyhedron in the spiral, >= 1.

function __ext_thread_plug_faces_builder(i,n)=

((i==(n*3))?

[[0,1,2],

[i+2, i+1, i+0]]:

concat(__ext_thread_plug_faces_builder(i+3,n),

[[i+0, i+3, i+4, i+1],

[i+1, i+5, i+2],

[i+1, i+4, i+5],

[i+0, i+2, i+5], [i+0, i+5, i+3]]));

Sent with Proton Mail secure email.

Normally I would not include copious amounts of code in my email. I recently discovered the ability to recursively create lists of points & faces, where the size of these lists is undetermined at code time. It's determined at runtime. In other words I'm using append() trickery together with recursive function calls (_not_ iterative). This technique had escaped me until now for some reason. Even though I studied computer science and learned about iterative vs. recursive approach, this technique of dynamically building points lists eluded me until now.
I build my helix consisting of one single polyhedron, defined with convexity=10. I use that helix as the subtrahend; the thing I subtract away from is a thin tube. I get missing faces in F5 render, but of course F6 renders okay. While this is not a showstopper I am experimenting with new strategies to do things in OpenSCAD, and was wondering if there is any "secret sauce" that I am missing to get the correct F5 render. Thanks.
Below is the self-contained code which I save to a SCAD file. There are no external deps; you can run the code to try it.
diameter=3.0;
pitch=0.5;
length=0.5+2*$t;
z_start=0.77-$t;
fn=23;
difference() {
translate([0,0,z_start+length/2]) {
difference() {
cylinder(r=metric_thread_major_radius(diameter,pitch),
h=length,
center=true,
$fn=fn);
cylinder(r=metric_thread_minor_radius(diameter,pitch),
h=length*1.125,
center=true,
$fn=fn);
}
}
external_metric_thread_plug(diameter, pitch, length,
z_start=z_start, n_starts=3,
fn=fn);
}
function metric_thread_angle()=
30;
function metric_thread_perfect_height(pitch)=
pitch/(2*tan(metric_thread_angle()));
// Up the radii of internal (female) threads in order to provide a little bit
// of wiggle room. This function can be more complex and can involve the
// diameter in its decision; right now it's rather simple. You can hack this.
function __thread_internal_radius_increase(diameter,pitch)=
0.1*metric_thread_perfect_height(pitch);
function metric_thread_major_radius(diameter,pitch,internal=false)=
diameter/2+(internal?__thread_internal_radius_increase(diameter,pitch):0);
// What portion of the total pitch is taken up by the angled thread section
// (and not the squared off valley and tip). The remaining distance will be
// divided equally between the squared off valley and tip.
function __thread_effective_ratio()=
0.7;
function metric_thread_minor_radius(diameter,pitch,internal=false)=
metric_thread_major_radius(diameter,pitch,internal)
-__thread_effective_ratio()*metric_thread_perfect_height(pitch);
// Where the major radius would be if the cuts were perfect, without flat spots
// in the valleys and on tips of threads.
function metric_thread_perfect_major_radius(diameter,pitch,internal=false)=
metric_thread_major_radius(diameter,pitch,internal)
+((1.0-__thread_effective_ratio())*
metric_thread_perfect_height(pitch)/2.0);
// Where the minor radius would be if the cuts were perfect, without flat spots
// in the valleys and on tips of threads.
function metric_thread_perfect_minor_radius(diameter,pitch,internal=false)=
metric_thread_perfect_major_radius(diameter,pitch,internal)
-metric_thread_perfect_height(pitch);
function metric_thread_segments(diameter,fn=-1)=
(fn<3)?min(50,max(ceil(diameter*6),25)):fn;
module external_metric_thread_plug(diameter,
pitch,
length,
z_start=0.0,
n_starts=1,
fn=-1)
{
n_segments = metric_thread_segments(diameter=diameter,fn=fn);
z_end = z_start+length;
epsilon = pitch/48; // Decrease outer radius by this amount.
outer_r = metric_thread_perfect_major_radius(diameter,pitch)-epsilon;
inner_r = metric_thread_perfect_minor_radius(diameter,pitch);
z_rot = (n_starts*pitch)/n_segments;
d_mid = epsilon*tan(metric_thread_angle());
for(thread=[0:n_starts-1])
{
start_turn = (z_start/pitch-1.0-thread)/n_starts;
end_turn = (z_end/pitch-thread)/n_starts;
n_curr = floor(start_turn*n_segments);
n_to_go = ceil(end_turn*n_segments)-n_curr+1;
z_off = thread*pitch;
polyhedron(points=__ext_thread_plug_points_builder(n_to_go,
n_curr,
n_segments,
outer_r,
inner_r,
z_rot,
pitch,
d_mid,
z_off),
faces=__ext_thread_plug_faces_builder(0,n_to_go-1),
convexity=10);
}
}
// Parameter 'n_to_go' is the number of slices to create. One polyhedron will
// have two slices, and this is the bare minumum. Parameter 'm_curr' tells
// where on the polyhedron spiral to start creating triangle slices; this value
// may be very large in magnitude, and it can be negative.
function __ext_thread_plug_points_builder(n_to_go,
n_curr,
n_segments, // These last seven
outer_r, // parameters are
inner_r, // unchanging as we
z_rot, // recurse.
pitch,
d_mid,
z_off)= // Z offset for multi-starts.
((n_to_go==0)?[]:
concat(__ext_thread_plug_points_builder(n_to_go-1,
n_curr+1,
n_segments,
outer_r,
inner_r,
z_rot,
pitch,
d_mid,
z_off),
// Modulo operator is used to reduce range & increase precision.
[[outer_r*cos(360*(n_curr%n_segments)/n_segments),
outer_r*sin(360*(n_curr%n_segments)/n_segments),
z_rot*n_curr+pitch-d_mid+z_off],
[outer_r*cos(360*(n_curr%n_segments)/n_segments),
outer_r*sin(360*(n_curr%n_segments)/n_segments),
z_rot*n_curr+d_mid+z_off],
[inner_r*cos(360*(n_curr%n_segments)/n_segments),
inner_r*sin(360*(n_curr%n_segments)/n_segments),
z_rot*n_curr+pitch/2+z_off]]));
// Parameter 'i' should be initialized to zero.
// Parameter 'n' should be set to the number of polyhedron in the spiral, >= 1.
function __ext_thread_plug_faces_builder(i,n)=
((i==(n*3))?
[[0,1,2],
[i+2, i+1, i+0]]:
concat(__ext_thread_plug_faces_builder(i+3,n),
[[i+0, i+3, i+4, i+1],
[i+1, i+5, i+2],
[i+1, i+4, i+5],
[i+0, i+2, i+5], [i+0, i+5, i+3]]));
Sent with [Proton Mail](https://proton.me/) secure email.

NH

nop head

Wed, Jan 24, 2024 3:03 PM

Throwntogether mode shows a lot of purple, so I think some of your faces

are inside out.

On Wed, 24 Jan 2024 at 14:52, neri-engineering via Discuss <

discuss@lists.openscad.org> wrote:

Normally I would not include copious amounts of code in my email. I

recently discovered the ability to recursively create lists of points &

faces, where the size of these lists is undetermined at code time. It's

determined at runtime. In other words I'm using append() trickery together

with recursive function calls (*not* iterative). This technique had

escaped me until now for some reason. Even though I studied computer

science and learned about iterative vs. recursive approach, this technique

of dynamically building points lists eluded me until now.

I build my helix consisting of one single polyhedron, defined with

convexity=10. I use that helix as the subtrahend; the thing I subtract

away from is a thin tube. I get missing faces in F5 render, but of course

F6 renders okay. While this is not a showstopper I am experimenting with

new strategies to do things in OpenSCAD, and was wondering if there is any

"secret sauce" that I am missing to get the correct F5 render. Thanks.

Below is the self-contained code which I save to a SCAD file. There are

no external deps; you can run the code to try it.

diameter=3.0;

pitch=0.5;

length=0.5+2*$t;

z_start=0.77-$t;

fn=23;

difference() {

translate([0,0,z_start+length/2]) {

difference() {

cylinder(r=metric_thread_major_radius(diameter,pitch),

h=length,

center=true,

$fn=fn);

cylinder(r=metric_thread_minor_radius(diameter,pitch),

h=length*1.125,

center=true,

$fn=fn);

}

}

external_metric_thread_plug(diameter, pitch, length,

z_start=z_start, n_starts=3,

fn=fn);

}

function metric_thread_angle()=

30;

function metric_thread_perfect_height(pitch)=

pitch/(2*tan(metric_thread_angle()));

// Up the radii of internal (female) threads in order to provide a little

bit

// of wiggle room. This function can be more complex and can involve the

// diameter in its decision; right now it's rather simple. You can hack

this.

function __thread_internal_radius_increase(diameter,pitch)=

0.1*metric_thread_perfect_height(pitch);

function metric_thread_major_radius(diameter,pitch,internal=false)=

diameter/2+(internal?__thread_internal_radius_increase(diameter,pitch):0);

// What portion of the total pitch is taken up by the angled thread section

// (and not the squared off valley and tip). The remaining distance will

be

// divided equally between the squared off valley and tip.

function __thread_effective_ratio()=

0.7;

function metric_thread_minor_radius(diameter,pitch,internal=false)=

metric_thread_major_radius(diameter,pitch,internal)

-__thread_effective_ratio()*metric_thread_perfect_height(pitch);

// Where the major radius would be if the cuts were perfect, without flat

spots

// in the valleys and on tips of threads.

function metric_thread_perfect_major_radius(diameter,pitch,internal=false)=

metric_thread_major_radius(diameter,pitch,internal)

+((1.0-__thread_effective_ratio())*

metric_thread_perfect_height(pitch)/2.0);

// Where the minor radius would be if the cuts were perfect, without flat

spots

// in the valleys and on tips of threads.

function metric_thread_perfect_minor_radius(diameter,pitch,internal=false)=

metric_thread_perfect_major_radius(diameter,pitch,internal)

-metric_thread_perfect_height(pitch);

function metric_thread_segments(diameter,fn=-1)=

(fn<3)?min(50,max(ceil(diameter*6),25)):fn;

module external_metric_thread_plug(diameter,

pitch,

length,

z_start=0.0,

n_starts=1,

fn=-1)

{

n_segments = metric_thread_segments(diameter=diameter,fn=fn);

z_end = z_start+length;

epsilon = pitch/48; // Decrease outer radius by this amount.

outer_r =

metric_thread_perfect_major_radius(diameter,pitch)-epsilon;

inner_r = metric_thread_perfect_minor_radius(diameter,pitch);

z_rot = (n_starts*pitch)/n_segments;
d_mid = epsilon*tan(metric_thread_angle());

```
for(thread=[0:n_starts-1])
{
start_turn = (z_start/pitch-1.0-thread)/n_starts;
end_turn = (z_end/pitch-thread)/n_starts;
n_curr = floor(start_turn*n_segments);
n_to_go = ceil(end_turn*n_segments)-n_curr+1;
z_off = thread*pitch;
polyhedron(points=__ext_thread_plug_points_builder(n_to_go,
n_curr,
n_segments,
outer_r,
inner_r,
z_rot,
pitch,
d_mid,
z_off),
faces=__ext_thread_plug_faces_builder(0,n_to_go-1),
convexity=10);
}
```

}

// Parameter 'n_to_go' is the number of slices to create. One polyhedron

will

// have two slices, and this is the bare minumum. Parameter 'm_curr' tells

// where on the polyhedron spiral to start creating triangle slices; this

value

// may be very large in magnitude, and it can be negative.

function __ext_thread_plug_points_builder(n_to_go,

n_curr,

n_segments, // These last seven

outer_r, // parameters are

inner_r, // unchanging as we

z_rot, // recurse.

pitch,

d_mid,

z_off)= // Z offset for

multi-starts.

((n_to_go==0)?[]:

concat(__ext_thread_plug_points_builder(n_to_go-1,

n_curr+1,

n_segments,

outer_r,

inner_r,

z_rot,

pitch,

d_mid,

z_off),

// Modulo operator is used to reduce range & increase

precision.

[[outer_r*cos(360*(n_curr%n_segments)/n_segments),

outer_r*sin(360*(n_curr%n_segments)/n_segments),

z_rot*n_curr+pitch-d_mid+z_off],
[outer_r*cos(360*(n_curr%n_segments)/n_segments),

outer_r

z_rot

[inner_r

inner_r

z_rot*n_curr+pitch/2+z_off]]));

// Parameter 'i' should be initialized to zero.

// Parameter 'n' should be set to the number of polyhedron in the spiral,

= 1.

function __ext_thread_plug_faces_builder(i,n)=

((i==(n*3))?

[[0,1,2],

[i+2, i+1, i+0]]:

concat(__ext_thread_plug_faces_builder(i+3,n),

[[i+0, i+3, i+4, i+1],

[i+1, i+5, i+2],

[i+1, i+4, i+5],

[i+0, i+2, i+5],

[i+0, i+5, i+3]]));

Sent with Proton Mail https://proton.me/ secure email.

OpenSCAD mailing list

To unsubscribe send an email to discuss-leave@lists.openscad.org

Throwntogether mode shows a lot of purple, so I think some of your faces
are inside out.
On Wed, 24 Jan 2024 at 14:52, neri-engineering via Discuss <
discuss@lists.openscad.org> wrote:
> Normally I would not include copious amounts of code in my email. I
> recently discovered the ability to recursively create lists of points &
> faces, where the size of these lists is undetermined at code time. It's
> determined at runtime. In other words I'm using append() trickery together
> with recursive function calls (_not_ iterative). This technique had
> escaped me until now for some reason. Even though I studied computer
> science and learned about iterative vs. recursive approach, this technique
> of dynamically building points lists eluded me until now.
>
> I build my helix consisting of one single polyhedron, defined with
> convexity=10. I use that helix as the subtrahend; the thing I subtract
> away from is a thin tube. I get missing faces in F5 render, but of course
> F6 renders okay. While this is not a showstopper I am experimenting with
> new strategies to do things in OpenSCAD, and was wondering if there is any
> "secret sauce" that I am missing to get the correct F5 render. Thanks.
>
> Below is the self-contained code which I save to a SCAD file. There are
> no external deps; you can run the code to try it.
>
> diameter=3.0;
> pitch=0.5;
> length=0.5+2*$t;
> z_start=0.77-$t;
> fn=23;
> difference() {
> translate([0,0,z_start+length/2]) {
> difference() {
> cylinder(r=metric_thread_major_radius(diameter,pitch),
> h=length,
> center=true,
> $fn=fn);
> cylinder(r=metric_thread_minor_radius(diameter,pitch),
> h=length*1.125,
> center=true,
> $fn=fn);
> }
> }
> external_metric_thread_plug(diameter, pitch, length,
> z_start=z_start, n_starts=3,
> fn=fn);
> }
>
> function metric_thread_angle()=
> 30;
>
> function metric_thread_perfect_height(pitch)=
> pitch/(2*tan(metric_thread_angle()));
>
> // Up the radii of internal (female) threads in order to provide a little
> bit
> // of wiggle room. This function can be more complex and can involve the
> // diameter in its decision; right now it's rather simple. You can hack
> this.
> function __thread_internal_radius_increase(diameter,pitch)=
> 0.1*metric_thread_perfect_height(pitch);
>
> function metric_thread_major_radius(diameter,pitch,internal=false)=
>
> diameter/2+(internal?__thread_internal_radius_increase(diameter,pitch):0);
>
> // What portion of the total pitch is taken up by the angled thread section
> // (and not the squared off valley and tip). The remaining distance will
> be
> // divided equally between the squared off valley and tip.
> function __thread_effective_ratio()=
> 0.7;
>
> function metric_thread_minor_radius(diameter,pitch,internal=false)=
> metric_thread_major_radius(diameter,pitch,internal)
> -__thread_effective_ratio()*metric_thread_perfect_height(pitch);
>
> // Where the major radius would be if the cuts were perfect, without flat
> spots
> // in the valleys and on tips of threads.
> function metric_thread_perfect_major_radius(diameter,pitch,internal=false)=
> metric_thread_major_radius(diameter,pitch,internal)
> +((1.0-__thread_effective_ratio())*
> metric_thread_perfect_height(pitch)/2.0);
>
> // Where the minor radius would be if the cuts were perfect, without flat
> spots
> // in the valleys and on tips of threads.
> function metric_thread_perfect_minor_radius(diameter,pitch,internal=false)=
> metric_thread_perfect_major_radius(diameter,pitch,internal)
> -metric_thread_perfect_height(pitch);
>
> function metric_thread_segments(diameter,fn=-1)=
> (fn<3)?min(50,max(ceil(diameter*6),25)):fn;
>
> module external_metric_thread_plug(diameter,
> pitch,
> length,
> z_start=0.0,
> n_starts=1,
> fn=-1)
> {
> n_segments = metric_thread_segments(diameter=diameter,fn=fn);
> z_end = z_start+length;
> epsilon = pitch/48; // Decrease outer radius by this amount.
> outer_r =
> metric_thread_perfect_major_radius(diameter,pitch)-epsilon;
> inner_r = metric_thread_perfect_minor_radius(diameter,pitch);
> z_rot = (n_starts*pitch)/n_segments;
> d_mid = epsilon*tan(metric_thread_angle());
>
> for(thread=[0:n_starts-1])
> {
> start_turn = (z_start/pitch-1.0-thread)/n_starts;
> end_turn = (z_end/pitch-thread)/n_starts;
> n_curr = floor(start_turn*n_segments);
> n_to_go = ceil(end_turn*n_segments)-n_curr+1;
> z_off = thread*pitch;
>
> polyhedron(points=__ext_thread_plug_points_builder(n_to_go,
> n_curr,
> n_segments,
> outer_r,
> inner_r,
> z_rot,
> pitch,
> d_mid,
> z_off),
> faces=__ext_thread_plug_faces_builder(0,n_to_go-1),
> convexity=10);
> }
> }
>
> // Parameter 'n_to_go' is the number of slices to create. One polyhedron
> will
> // have two slices, and this is the bare minumum. Parameter 'm_curr' tells
> // where on the polyhedron spiral to start creating triangle slices; this
> value
> // may be very large in magnitude, and it can be negative.
> function __ext_thread_plug_points_builder(n_to_go,
> n_curr,
> n_segments, // These last seven
> outer_r, // parameters are
> inner_r, // unchanging as we
> z_rot, // recurse.
> pitch,
> d_mid,
> z_off)= // Z offset for
> multi-starts.
> ((n_to_go==0)?[]:
> concat(__ext_thread_plug_points_builder(n_to_go-1,
> n_curr+1,
> n_segments,
> outer_r,
> inner_r,
> z_rot,
> pitch,
> d_mid,
> z_off),
> // Modulo operator is used to reduce range & increase
> precision.
> [[outer_r*cos(360*(n_curr%n_segments)/n_segments),
> outer_r*sin(360*(n_curr%n_segments)/n_segments),
> z_rot*n_curr+pitch-d_mid+z_off],
> [outer_r*cos(360*(n_curr%n_segments)/n_segments),
> outer_r*sin(360*(n_curr%n_segments)/n_segments),
> z_rot*n_curr+d_mid+z_off],
> [inner_r*cos(360*(n_curr%n_segments)/n_segments),
> inner_r*sin(360*(n_curr%n_segments)/n_segments),
> z_rot*n_curr+pitch/2+z_off]]));
>
> // Parameter 'i' should be initialized to zero.
> // Parameter 'n' should be set to the number of polyhedron in the spiral,
> >= 1.
> function __ext_thread_plug_faces_builder(i,n)=
> ((i==(n*3))?
> [[0,1,2],
> [i+2, i+1, i+0]]:
> concat(__ext_thread_plug_faces_builder(i+3,n),
> [[i+0, i+3, i+4, i+1],
> [i+1, i+5, i+2],
> [i+1, i+4, i+5],
> [i+0, i+2, i+5],
> [i+0, i+5, i+3]]));
>
> Sent with Proton Mail <https://proton.me/> secure email.
> _______________________________________________
> OpenSCAD mailing list
> To unsubscribe send an email to discuss-leave@lists.openscad.org
>

N

neri-engineering

Wed, Jan 24, 2024 5:19 PM

Yes, indeed! Thanks.

Sent with Proton Mail secure email.

On Wednesday, January 24th, 2024 at 9:03 AM, nop head via Discuss discuss@lists.openscad.org wrote:

Throwntogether mode shows a lot of purple, so I think some of your faces are inside out.

On Wed, 24 Jan 2024 at 14:52, neri-engineering via Discuss discuss@lists.openscad.org wrote:

Normally I would not include copious amounts of code in my email. I recently discovered the ability to recursively create lists of points & faces, where the size of these lists is undetermined at code time. It's determined at runtime. In other words I'm using append() trickery together with recursive function calls (*not* iterative). This technique had escaped me until now for some reason. Even though I studied computer science and learned about iterative vs. recursive approach, this technique of dynamically building points lists eluded me until now.

I build my helix consisting of one single polyhedron, defined with convexity=10. I use that helix as the subtrahend; the thing I subtract away from is a thin tube. I get missing faces in F5 render, but of course F6 renders okay. While this is not a showstopper I am experimenting with new strategies to do things in OpenSCAD, and was wondering if there is any "secret sauce" that I am missing to get the correct F5 render. Thanks.

Below is the self-contained code which I save to a SCAD file. There are no external deps; you can run the code to try it.

diameter=3.0;

pitch=0.5;

length=0.5+2*$t;

z_start=0.77-$t;

fn=23;

difference() {

translate([0,0,z_start+length/2]) {

difference() {

cylinder(r=metric_thread_major_radius(diameter,pitch),

h=length,

center=true,

$fn=fn);

cylinder(r=metric_thread_minor_radius(diameter,pitch),

h=length*1.125,

center=true,

$fn=fn);

}

}

external_metric_thread_plug(diameter, pitch, length,

z_start=z_start, n_starts=3,

fn=fn);

}

function metric_thread_angle()=

30;

function metric_thread_perfect_height(pitch)=

pitch/(2*tan(metric_thread_angle()));

// Up the radii of internal (female) threads in order to provide a little bit

// of wiggle room. This function can be more complex and can involve the

// diameter in its decision; right now it's rather simple. You can hack this.

function __thread_internal_radius_increase(diameter,pitch)=

0.1*metric_thread_perfect_height(pitch);

function metric_thread_major_radius(diameter,pitch,internal=false)=

diameter/2+(internal?__thread_internal_radius_increase(diameter,pitch):0);

// What portion of the total pitch is taken up by the angled thread section

// (and not the squared off valley and tip). The remaining distance will be

// divided equally between the squared off valley and tip.

function __thread_effective_ratio()=

0.7;

function metric_thread_minor_radius(diameter,pitch,internal=false)=

metric_thread_major_radius(diameter,pitch,internal)

-__thread_effective_ratio()*metric_thread_perfect_height(pitch);

// Where the major radius would be if the cuts were perfect, without flat spots

// in the valleys and on tips of threads.

function metric_thread_perfect_major_radius(diameter,pitch,internal=false)=

metric_thread_major_radius(diameter,pitch,internal)

+((1.0-__thread_effective_ratio())*

metric_thread_perfect_height(pitch)/2.0);

// Where the minor radius would be if the cuts were perfect, without flat spots

// in the valleys and on tips of threads.

function metric_thread_perfect_minor_radius(diameter,pitch,internal=false)=

metric_thread_perfect_major_radius(diameter,pitch,internal)

-metric_thread_perfect_height(pitch);

function metric_thread_segments(diameter,fn=-1)=

(fn<3)?min(50,max(ceil(diameter*6),25)):fn;

module external_metric_thread_plug(diameter,

pitch,

length,

z_start=0.0,

n_starts=1,

fn=-1)

{

n_segments = metric_thread_segments(diameter=diameter,fn=fn);

z_end = z_start+length;

epsilon = pitch/48; // Decrease outer radius by this amount.

outer_r = metric_thread_perfect_major_radius(diameter,pitch)-epsilon;

inner_r = metric_thread_perfect_minor_radius(diameter,pitch);

z_rot = (n_starts*pitch)/n_segments;
d_mid = epsilon*tan(metric_thread_angle());

for(thread=[0:n_starts-1])

{

start_turn = (z_start/pitch-1.0-thread)/n_starts;

end_turn = (z_end/pitch-thread)/n_starts;

n_curr = floor(start_turn*n_segments);
n_to_go = ceil(end_turn*n_segments)-n_curr+1;

z_off = thread*pitch;

polyhedron(points=__ext_thread_plug_points_builder(n_to_go,

n_curr,

n_segments,

outer_r,

inner_r,

z_rot,

pitch,

d_mid,

z_off),

faces=__ext_thread_plug_faces_builder(0,n_to_go-1),

convexity=10);

}

}

// Parameter 'n_to_go' is the number of slices to create. One polyhedron will

// have two slices, and this is the bare minumum. Parameter 'm_curr' tells

// where on the polyhedron spiral to start creating triangle slices; this value

// may be very large in magnitude, and it can be negative.

function __ext_thread_plug_points_builder(n_to_go,

n_curr,

n_segments, // These last seven

outer_r, // parameters are

inner_r, // unchanging as we

z_rot, // recurse.

pitch,

d_mid,

z_off)= // Z offset for multi-starts.

((n_to_go==0)?[]:

concat(__ext_thread_plug_points_builder(n_to_go-1,

n_curr+1,

n_segments,

outer_r,

inner_r,

z_rot,

pitch,

d_mid,

z_off),

// Modulo operator is used to reduce range & increase precision.

[[outer_r*cos(360*(n_curr%n_segments)/n_segments),

outer_r*sin(360*(n_curr%n_segments)/n_segments),

z_rot*n_curr+pitch-d_mid+z_off],
[outer_r*cos(360*(n_curr%n_segments)/n_segments),

outer_r

z_rot

[inner_r

inner_r

z_rot*n_curr+pitch/2+z_off]]));

// Parameter 'i' should be initialized to zero.

// Parameter 'n' should be set to the number of polyhedron in the spiral, >= 1.

function __ext_thread_plug_faces_builder(i,n)=

((i==(n*3))?

[[0,1,2],

[i+2, i+1, i+0]]:

concat(__ext_thread_plug_faces_builder(i+3,n),

[[i+0, i+3, i+4, i+1],

[i+1, i+5, i+2],

[i+1, i+4, i+5],

[i+0, i+2, i+5], [i+0, i+5, i+3]]));

Sent with Proton Mail secure email.

OpenSCAD mailing list

To unsubscribe send an email to discuss-leave@lists.openscad.org

Yes, indeed! Thanks.
Sent with [Proton Mail](https://proton.me/) secure email.
On Wednesday, January 24th, 2024 at 9:03 AM, nop head via Discuss <discuss@lists.openscad.org> wrote:
> Throwntogether mode shows a lot of purple, so I think some of your faces are inside out.
>
> On Wed, 24 Jan 2024 at 14:52, neri-engineering via Discuss <discuss@lists.openscad.org> wrote:
>
>> Normally I would not include copious amounts of code in my email. I recently discovered the ability to recursively create lists of points & faces, where the size of these lists is undetermined at code time. It's determined at runtime. In other words I'm using append() trickery together with recursive function calls (_not_ iterative). This technique had escaped me until now for some reason. Even though I studied computer science and learned about iterative vs. recursive approach, this technique of dynamically building points lists eluded me until now.
>>
>> I build my helix consisting of one single polyhedron, defined with convexity=10. I use that helix as the subtrahend; the thing I subtract away from is a thin tube. I get missing faces in F5 render, but of course F6 renders okay. While this is not a showstopper I am experimenting with new strategies to do things in OpenSCAD, and was wondering if there is any "secret sauce" that I am missing to get the correct F5 render. Thanks.
>>
>> Below is the self-contained code which I save to a SCAD file. There are no external deps; you can run the code to try it.
>>
>> diameter=3.0;
>> pitch=0.5;
>> length=0.5+2*$t;
>> z_start=0.77-$t;
>> fn=23;
>> difference() {
>> translate([0,0,z_start+length/2]) {
>> difference() {
>> cylinder(r=metric_thread_major_radius(diameter,pitch),
>> h=length,
>> center=true,
>> $fn=fn);
>> cylinder(r=metric_thread_minor_radius(diameter,pitch),
>> h=length*1.125,
>> center=true,
>> $fn=fn);
>> }
>> }
>> external_metric_thread_plug(diameter, pitch, length,
>> z_start=z_start, n_starts=3,
>> fn=fn);
>> }
>>
>> function metric_thread_angle()=
>> 30;
>>
>> function metric_thread_perfect_height(pitch)=
>> pitch/(2*tan(metric_thread_angle()));
>>
>> // Up the radii of internal (female) threads in order to provide a little bit
>> // of wiggle room. This function can be more complex and can involve the
>> // diameter in its decision; right now it's rather simple. You can hack this.
>> function __thread_internal_radius_increase(diameter,pitch)=
>> 0.1*metric_thread_perfect_height(pitch);
>>
>> function metric_thread_major_radius(diameter,pitch,internal=false)=
>> diameter/2+(internal?__thread_internal_radius_increase(diameter,pitch):0);
>>
>> // What portion of the total pitch is taken up by the angled thread section
>> // (and not the squared off valley and tip). The remaining distance will be
>> // divided equally between the squared off valley and tip.
>> function __thread_effective_ratio()=
>> 0.7;
>>
>> function metric_thread_minor_radius(diameter,pitch,internal=false)=
>> metric_thread_major_radius(diameter,pitch,internal)
>> -__thread_effective_ratio()*metric_thread_perfect_height(pitch);
>>
>> // Where the major radius would be if the cuts were perfect, without flat spots
>> // in the valleys and on tips of threads.
>> function metric_thread_perfect_major_radius(diameter,pitch,internal=false)=
>> metric_thread_major_radius(diameter,pitch,internal)
>> +((1.0-__thread_effective_ratio())*
>> metric_thread_perfect_height(pitch)/2.0);
>>
>> // Where the minor radius would be if the cuts were perfect, without flat spots
>> // in the valleys and on tips of threads.
>> function metric_thread_perfect_minor_radius(diameter,pitch,internal=false)=
>> metric_thread_perfect_major_radius(diameter,pitch,internal)
>> -metric_thread_perfect_height(pitch);
>>
>> function metric_thread_segments(diameter,fn=-1)=
>> (fn<3)?min(50,max(ceil(diameter*6),25)):fn;
>>
>> module external_metric_thread_plug(diameter,
>> pitch,
>> length,
>> z_start=0.0,
>> n_starts=1,
>> fn=-1)
>> {
>> n_segments = metric_thread_segments(diameter=diameter,fn=fn);
>> z_end = z_start+length;
>> epsilon = pitch/48; // Decrease outer radius by this amount.
>> outer_r = metric_thread_perfect_major_radius(diameter,pitch)-epsilon;
>> inner_r = metric_thread_perfect_minor_radius(diameter,pitch);
>> z_rot = (n_starts*pitch)/n_segments;
>> d_mid = epsilon*tan(metric_thread_angle());
>>
>> for(thread=[0:n_starts-1])
>> {
>> start_turn = (z_start/pitch-1.0-thread)/n_starts;
>> end_turn = (z_end/pitch-thread)/n_starts;
>> n_curr = floor(start_turn*n_segments);
>> n_to_go = ceil(end_turn*n_segments)-n_curr+1;
>> z_off = thread*pitch;
>>
>> polyhedron(points=__ext_thread_plug_points_builder(n_to_go,
>> n_curr,
>> n_segments,
>> outer_r,
>> inner_r,
>> z_rot,
>> pitch,
>> d_mid,
>> z_off),
>> faces=__ext_thread_plug_faces_builder(0,n_to_go-1),
>> convexity=10);
>> }
>> }
>>
>> // Parameter 'n_to_go' is the number of slices to create. One polyhedron will
>> // have two slices, and this is the bare minumum. Parameter 'm_curr' tells
>> // where on the polyhedron spiral to start creating triangle slices; this value
>> // may be very large in magnitude, and it can be negative.
>> function __ext_thread_plug_points_builder(n_to_go,
>> n_curr,
>> n_segments, // These last seven
>> outer_r, // parameters are
>> inner_r, // unchanging as we
>> z_rot, // recurse.
>> pitch,
>> d_mid,
>> z_off)= // Z offset for multi-starts.
>> ((n_to_go==0)?[]:
>> concat(__ext_thread_plug_points_builder(n_to_go-1,
>> n_curr+1,
>> n_segments,
>> outer_r,
>> inner_r,
>> z_rot,
>> pitch,
>> d_mid,
>> z_off),
>> // Modulo operator is used to reduce range & increase precision.
>> [[outer_r*cos(360*(n_curr%n_segments)/n_segments),
>> outer_r*sin(360*(n_curr%n_segments)/n_segments),
>> z_rot*n_curr+pitch-d_mid+z_off],
>> [outer_r*cos(360*(n_curr%n_segments)/n_segments),
>> outer_r*sin(360*(n_curr%n_segments)/n_segments),
>> z_rot*n_curr+d_mid+z_off],
>> [inner_r*cos(360*(n_curr%n_segments)/n_segments),
>> inner_r*sin(360*(n_curr%n_segments)/n_segments),
>> z_rot*n_curr+pitch/2+z_off]]));
>>
>> // Parameter 'i' should be initialized to zero.
>> // Parameter 'n' should be set to the number of polyhedron in the spiral, >= 1.
>> function __ext_thread_plug_faces_builder(i,n)=
>> ((i==(n*3))?
>> [[0,1,2],
>> [i+2, i+1, i+0]]:
>> concat(__ext_thread_plug_faces_builder(i+3,n),
>> [[i+0, i+3, i+4, i+1],
>> [i+1, i+5, i+2],
>> [i+1, i+4, i+5],
>> [i+0, i+2, i+5], [i+0, i+5, i+3]]));
>>
>> Sent with [Proton Mail](https://proton.me/) secure email.
>> _______________________________________________
>> OpenSCAD mailing list
>> To unsubscribe send an email to discuss-leave@lists.openscad.org

DP

David Phillip Oster

Wed, Jan 24, 2024 8:06 PM

Thank you for that neri-engineering, and nop head. That’s a fun example.

Thank you for that neri-engineering, and nop head. That’s a fun example.

N

neri-engineering

Wed, Jan 24, 2024 8:29 PM

With recursive functions it's a bit tricky to figure out the order of points after they're all assembled. The problem I had was that the recursive function which called concat() on two lists did it in an order that resulted in the final list having its points in a reverse order from what I expected. There is always "trickery" and a fair bit of "sorcery" involved in getting recursive functions to do the right thing. Anyhow this reverse order (from what I was assuming) is what caused the clockwise/counterclockwise polygon traversal situation.

Sent with Proton Mail secure email.

On Wednesday, January 24th, 2024 at 2:06 PM, David Phillip Oster via Discuss discuss@lists.openscad.org wrote:

Thank you for that neri-engineering, and nop head. That’s a fun example.

With recursive functions it's a bit tricky to figure out the order of points after they're all assembled. The problem I had was that the recursive function which called concat() on two lists did it in an order that resulted in the final list having its points in a reverse order from what I expected. There is always "trickery" and a fair bit of "sorcery" involved in getting recursive functions to do the right thing. Anyhow this reverse order (from what I was assuming) is what caused the clockwise/counterclockwise polygon traversal situation.
Sent with [Proton Mail](https://proton.me/) secure email.
On Wednesday, January 24th, 2024 at 2:06 PM, David Phillip Oster via Discuss <discuss@lists.openscad.org> wrote:
> Thank you for that neri-engineering, and nop head. That’s a fun example.

AM

Adrian Mariano

Thu, Jan 25, 2024 1:21 AM

I don't understand why it's any trickier to assemble the order of points

with recursive programming. In either case, if you have a growing list

(the result) you compute new values and add them to the end of the list.

Normally I write a recursive function something like

function func(result,info) =

done ? result

: func(concat(result, more_results_based_on_info),

new_info));

The result grows in an obvious way and there is no "trickery" or "sorcery"

involved. It's as straight forward as any other kind of coding, though it

can get annoying if "info" contains many things, and I can't say anything

good about the speed of execution.

On Wed, Jan 24, 2024 at 3:30 PM neri-engineering via Discuss <

discuss@lists.openscad.org> wrote:

With recursive functions it's a bit tricky to figure out the order of

points after they're all assembled. The problem I had was that the

recursive function which called concat() on two lists did it in an order

that resulted in the final list having its points in a reverse order from

what I expected. There is always "trickery" and a fair bit of "sorcery"

involved in getting recursive functions to do the right thing. Anyhow this

reverse order (from what I was assuming) is what caused the

clockwise/counterclockwise polygon traversal situation.

Sent with Proton Mail https://proton.me/ secure email.

On Wednesday, January 24th, 2024 at 2:06 PM, David Phillip Oster via

Discuss discuss@lists.openscad.org wrote:

Thank you for that neri-engineering, and nop head. That’s a fun example.

OpenSCAD mailing list

To unsubscribe send an email to discuss-leave@lists.openscad.org

I don't understand why it's any trickier to assemble the order of points
with recursive programming. In either case, if you have a growing list
(the result) you compute new values and add them to the end of the list.
Normally I write a recursive function something like
function func(result,info) =
done ? result
: func(concat(result, more_results_based_on_info),
new_info));
The result grows in an obvious way and there is no "trickery" or "sorcery"
involved. It's as straight forward as any other kind of coding, though it
can get annoying if "info" contains many things, and I can't say anything
good about the speed of execution.
On Wed, Jan 24, 2024 at 3:30 PM neri-engineering via Discuss <
discuss@lists.openscad.org> wrote:
> With recursive functions it's a bit tricky to figure out the order of
> points after they're all assembled. The problem I had was that the
> recursive function which called concat() on two lists did it in an order
> that resulted in the final list having its points in a reverse order from
> what I expected. There is always "trickery" and a fair bit of "sorcery"
> involved in getting recursive functions to do the right thing. Anyhow this
> reverse order (from what I was assuming) is what caused the
> clockwise/counterclockwise polygon traversal situation.
>
> Sent with Proton Mail <https://proton.me/> secure email.
>
> On Wednesday, January 24th, 2024 at 2:06 PM, David Phillip Oster via
> Discuss <discuss@lists.openscad.org> wrote:
>
> Thank you for that neri-engineering, and nop head. That’s a fun example.
>
>
> _______________________________________________
> OpenSCAD mailing list
> To unsubscribe send an email to discuss-leave@lists.openscad.org
>

N

neri-engineering

Thu, Jan 25, 2024 3:46 AM

It's because in functions you can't declare local variables. So many times you end up writing helper functions which take the semi-compiled form of equations/data. This is one of the reasons why recursive functions in OpenSCAD are difficult to write. It certainly takes some getting used to. That's why I call it "sorcery". It's good mental gymnastics to keep you sharp, to prevent the ole Alzheimer's from setting in. We keep our bodies sharp by working out and we keep our minds sharp by writing recursive functions in OpenSCAD. Some examples I can give you which demonstrate my point. With a standard iterative process we would accomplish the following with simpler code.

// There are 21 steps currently.

animation_durations=[1, // # 1 slipper_back_plate()

1, // # 2 slipper_bearing()

2, // # 3 top_shaft()

1, // # 4 slipper_back_plate_pin()

2, // # 5 spur_gear() & left_slipper_pad()

1, // # 6 right_slipper_pad()

1, // # 7 slipper_front_plate()

1, // # 8 slipper_spring()

3, // # 9 slipper_nut()

1, // #10 slipper_to_drive_gear_spacer()

1, // #11 drive_gear()

1, // #12 top_shaft_left_spacer() &

// left_top_shaft_bearing()

1, // #13 top_shaft_right_spacer() &

// right_top_shaft_bearing()

2, // #14 top_shaft_end_screw()

2]; // # ending()

// Returns a 1-based part number. Does not start from zero. Identifies the

// part being "introduced" in its introductory animation.

function get_part_number(tick,index)=

(tick<0)?index:

(get_part_number(tick-animation_durations[index],

index+1));

function total_animation_steps(index,step_count)=

(index>=len(animation_durations))?

step_count:

total_animation_steps(index+1,

step_count+animation_durations[index]);

// Parameter 'partnum' is one-based. Returns the number of animation steps

// that have preceded the beginning of part intro animation for specified part.

function total_animation_steps_up_to(partnum,index,step_count)=

(index>=(partnum-1))?

step_count:

total_animation_steps_up_to(partnum,index+1,

step_count+animation_durations[index]);

// The input parameter is 1-based and represents the part #.

function part_animation_steps(partnum)=

animation_durations[partnum-1];

// Value 't' is a value in [0,1), it increases linearly with time to span the

// entirety of animation.

t=0.34; // For example, we're a bit more than 1/3 of the way through the anim.

// Parameter 'i' is the animation ticker, zero-based.

i=floor(t*total_animation_steps(0,0)); // Start from 0.

p=get_part_number(i,0);

// Parameter 'w' is like 't', meaning it is in the range 0 <= w < 1, but it

// is specific to the part intro animation. The part intro animation begins

// at w=0 and is already over at w=1.

pre_w=(t*total_animation_steps(0,0)

-total_animation_steps_up_to(p,0,0))/part_animation_steps(p);

// First it will print out a one-based index describing the part we're on.

echo(p);

// Next it will print out which fraction of this part's scene we've done.echo(pre_w);

Sent with Proton Mail secure email.

On Wednesday, January 24th, 2024 at 7:21 PM, Adrian Mariano via Discuss discuss@lists.openscad.org wrote:

I don't understand why it's any trickier to assemble the order of points with recursive programming. In either case, if you have a growing list (the result) you compute new values and add them to the end of the list. Normally I write a recursive function something like

function func(result,info) =

done ? result

: func(concat(result, more_results_based_on_info),

new_info));

The result grows in an obvious way and there is no "trickery" or "sorcery" involved. It's as straight forward as any other kind of coding, though it can get annoying if "info" contains many things, and I can't say anything good about the speed of execution.

On Wed, Jan 24, 2024 at 3:30 PM neri-engineering via Discuss discuss@lists.openscad.org wrote:

With recursive functions it's a bit tricky to figure out the order of points after they're all assembled. The problem I had was that the recursive function which called concat() on two lists did it in an order that resulted in the final list having its points in a reverse order from what I expected. There is always "trickery" and a fair bit of "sorcery" involved in getting recursive functions to do the right thing. Anyhow this reverse order (from what I was assuming) is what caused the clockwise/counterclockwise polygon traversal situation.

Sent with Proton Mail secure email.

On Wednesday, January 24th, 2024 at 2:06 PM, David Phillip Oster via Discuss discuss@lists.openscad.org wrote:

Thank you for that neri-engineering, and nop head. That’s a fun example.

OpenSCAD mailing list

To unsubscribe send an email to discuss-leave@lists.openscad.org

It's because in functions you can't declare local variables. So many times you end up writing helper functions which take the semi-compiled form of equations/data. This is one of the reasons why recursive functions in OpenSCAD are difficult to write. It certainly takes some getting used to. That's why I call it "sorcery". It's good mental gymnastics to keep you sharp, to prevent the ole Alzheimer's from setting in. We keep our bodies sharp by working out and we keep our minds sharp by writing recursive functions in OpenSCAD. Some examples I can give you which demonstrate my point. With a standard iterative process we would accomplish the following with simpler code.
// There are 21 steps currently.
animation_durations=[1, // # 1 slipper_back_plate()
1, // # 2 slipper_bearing()
2, // # 3 top_shaft()
1, // # 4 slipper_back_plate_pin()
2, // # 5 spur_gear() & left_slipper_pad()
1, // # 6 right_slipper_pad()
1, // # 7 slipper_front_plate()
1, // # 8 slipper_spring()
3, // # 9 slipper_nut()
1, // #10 slipper_to_drive_gear_spacer()
1, // #11 drive_gear()
1, // #12 top_shaft_left_spacer() &
// left_top_shaft_bearing()
1, // #13 top_shaft_right_spacer() &
// right_top_shaft_bearing()
2, // #14 top_shaft_end_screw()
2]; // # ending()
// Returns a 1-based part number. Does not start from zero. Identifies the
// part being "introduced" in its introductory animation.
function get_part_number(tick,index)=
(tick<0)?index:
(get_part_number(tick-animation_durations[index],
index+1));
function total_animation_steps(index,step_count)=
(index>=len(animation_durations))?
step_count:
total_animation_steps(index+1,
step_count+animation_durations[index]);
// Parameter 'partnum' is one-based. Returns the number of animation steps
// that have preceded the beginning of part intro animation for specified part.
function total_animation_steps_up_to(partnum,index,step_count)=
(index>=(partnum-1))?
step_count:
total_animation_steps_up_to(partnum,index+1,
step_count+animation_durations[index]);
// The input parameter is 1-based and represents the part #.
function part_animation_steps(partnum)=
animation_durations[partnum-1];
// Value 't' is a value in [0,1), it increases linearly with time to span the
// entirety of animation.
t=0.34; // For example, we're a bit more than 1/3 of the way through the anim.
// Parameter 'i' is the animation ticker, zero-based.
i=floor(t*total_animation_steps(0,0)); // Start from 0.
p=get_part_number(i,0);
// Parameter 'w' is like 't', meaning it is in the range 0 <= w < 1, but it
// is specific to the part intro animation. The part intro animation begins
// at w=0 and is already over at w=1.
pre_w=(t*total_animation_steps(0,0)
-total_animation_steps_up_to(p,0,0))/part_animation_steps(p);
// First it will print out a one-based index describing the part we're on.
echo(p);
// Next it will print out which fraction of this part's scene we've done.echo(pre_w);
Sent with [Proton Mail](https://proton.me/) secure email.
On Wednesday, January 24th, 2024 at 7:21 PM, Adrian Mariano via Discuss <discuss@lists.openscad.org> wrote:
> I don't understand why it's any trickier to assemble the order of points with recursive programming. In either case, if you have a growing list (the result) you compute new values and add them to the end of the list. Normally I write a recursive function something like
>
> function func(result,info) =
> done ? result
> : func(concat(result, more_results_based_on_info),
> new_info));
>
> The result grows in an obvious way and there is no "trickery" or "sorcery" involved. It's as straight forward as any other kind of coding, though it can get annoying if "info" contains many things, and I can't say anything good about the speed of execution.
>
> On Wed, Jan 24, 2024 at 3:30 PM neri-engineering via Discuss <discuss@lists.openscad.org> wrote:
>
>> With recursive functions it's a bit tricky to figure out the order of points after they're all assembled. The problem I had was that the recursive function which called concat() on two lists did it in an order that resulted in the final list having its points in a reverse order from what I expected. There is always "trickery" and a fair bit of "sorcery" involved in getting recursive functions to do the right thing. Anyhow this reverse order (from what I was assuming) is what caused the clockwise/counterclockwise polygon traversal situation.
>>
>> Sent with [Proton Mail](https://proton.me/) secure email.
>>
>> On Wednesday, January 24th, 2024 at 2:06 PM, David Phillip Oster via Discuss <discuss@lists.openscad.org> wrote:
>>
>>> Thank you for that neri-engineering, and nop head. That’s a fun example.
>>
>> _______________________________________________
>> OpenSCAD mailing list
>> To unsubscribe send an email to discuss-leave@lists.openscad.org

FH

Father Horton

Thu, Jan 25, 2024 3:50 AM

Actually you can declare local variables. It's just not obvious how.

From the manual:

function get_square_triangle_perimeter(p1, p2) = let (hypotenuse =

sqrt(p1*p1+p2*p2)) p1 + p2 + hypotenuse;

On Wed, Jan 24, 2024 at 9:47 PM neri-engineering via Discuss <

discuss@lists.openscad.org> wrote:

It's because in functions you can't declare local variables. So many

times you end up writing helper functions which take the semi-compiled form

of equations/data. This is one of the reasons why recursive functions in

OpenSCAD are difficult to write. It certainly takes some getting used to.

That's why I call it "sorcery". It's good mental gymnastics to keep you

sharp, to prevent the ole Alzheimer's from setting in. We keep our bodies

sharp by working out and we keep our minds sharp by writing recursive

functions in OpenSCAD. Some examples I can give you which demonstrate my

point. With a standard iterative process we would accomplish the following

with simpler code.

// There are 21 steps currently.

animation_durations=[1, // # 1 slipper_back_plate()

1, // # 2 slipper_bearing()

2, // # 3 top_shaft()

1, // # 4 slipper_back_plate_pin()

2, // # 5 spur_gear() & left_slipper_pad()

1, // # 6 right_slipper_pad()

1, // # 7 slipper_front_plate()

1, // # 8 slipper_spring()

3, // # 9 slipper_nut()

1, // #10 slipper_to_drive_gear_spacer()

1, // #11 drive_gear()

1, // #12 top_shaft_left_spacer() &

// left_top_shaft_bearing()

1, // #13 top_shaft_right_spacer() &

// right_top_shaft_bearing()

2, // #14 top_shaft_end_screw()

2]; // # ending()

// Returns a 1-based part number. Does not start from zero. Identifies

the

// part being "introduced" in its introductory animation.

function get_part_number(tick,index)=

(tick<0)?index:

(get_part_number(tick-animation_durations[index],

index+1));

function total_animation_steps(index,step_count)=

(index>=len(animation_durations))?

step_count:

total_animation_steps(index+1,

step_count+animation_durations[index]);

// Parameter 'partnum' is one-based. Returns the number of animation steps

// that have preceded the beginning of part intro animation for specified

part.

function total_animation_steps_up_to(partnum,index,step_count)=

(index>=(partnum-1))?

step_count:

total_animation_steps_up_to(partnum,index+1,

step_count+animation_durations[index]);

// The input parameter is 1-based and represents the part #.

function part_animation_steps(partnum)=

animation_durations[partnum-1];

// Value 't' is a value in [0,1), it increases linearly with time to span

the

// entirety of animation.

t=0.34; // For example, we're a bit more than 1/3 of the way through the

anim.

// Parameter 'i' is the animation ticker, zero-based.

i=floor(t*total_animation_steps(0,0)); // Start from 0.

p=get_part_number(i,0);

// Parameter 'w' is like 't', meaning it is in the range 0 <= w < 1, but

it

// is specific to the part intro animation. The part intro animation

begins

// at w=0 and is already over at w=1.

pre_w=(t*total_animation_steps(0,0)

-total_animation_steps_up_to(p,0,0))/part_animation_steps(p);

// First it will print out a one-based index describing the part we're on.

echo(p);

// Next it will print out which fraction of this part's scene we've done.

echo(pre_w);

Sent with Proton Mail https://proton.me/ secure email.

On Wednesday, January 24th, 2024 at 7:21 PM, Adrian Mariano via Discuss <

discuss@lists.openscad.org> wrote:

I don't understand why it's any trickier to assemble the order of points

with recursive programming. In either case, if you have a growing list (the

result) you compute new values and add them to the end of the list.

Normally I write a recursive function something like

function func(result,info) =

done ? result

: func(concat(result, more_results_based_on_info),

new_info));

The result grows in an obvious way and there is no "trickery" or "sorcery"

involved. It's as straight forward as any other kind of coding, though it

can get annoying if "info" contains many things, and I can't say anything

good about the speed of execution.

On Wed, Jan 24, 2024 at 3:30 PM neri-engineering via Discuss <

discuss@lists.openscad.org> wrote:

points after they're all assembled. The problem I had was that the

recursive function which called concat() on two lists did it in an order

that resulted in the final list having its points in a reverse order from

what I expected. There is always "trickery" and a fair bit of "sorcery"

involved in getting recursive functions to do the right thing. Anyhow this

reverse order (from what I was assuming) is what caused the

clockwise/counterclockwise polygon traversal situation.

Sent with Proton Mail https://proton.me/ secure email.

Discuss discuss@lists.openscad.org wrote:

Thank you for that neri-engineering, and nop head. That’s a fun example.

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

Actually you can declare local variables. It's just not obvious how.
From the manual:
function get_square_triangle_perimeter(p1, p2) = let (hypotenuse =
sqrt(p1*p1+p2*p2)) p1 + p2 + hypotenuse;
On Wed, Jan 24, 2024 at 9:47 PM neri-engineering via Discuss <
discuss@lists.openscad.org> wrote:
> It's because in functions you can't declare local variables. So many
> times you end up writing helper functions which take the semi-compiled form
> of equations/data. This is one of the reasons why recursive functions in
> OpenSCAD are difficult to write. It certainly takes some getting used to.
> That's why I call it "sorcery". It's good mental gymnastics to keep you
> sharp, to prevent the ole Alzheimer's from setting in. We keep our bodies
> sharp by working out and we keep our minds sharp by writing recursive
> functions in OpenSCAD. Some examples I can give you which demonstrate my
> point. With a standard iterative process we would accomplish the following
> with simpler code.
>
>
> // There are 21 steps currently.
> animation_durations=[1, // # 1 slipper_back_plate()
> 1, // # 2 slipper_bearing()
> 2, // # 3 top_shaft()
> 1, // # 4 slipper_back_plate_pin()
> 2, // # 5 spur_gear() & left_slipper_pad()
> 1, // # 6 right_slipper_pad()
> 1, // # 7 slipper_front_plate()
> 1, // # 8 slipper_spring()
> 3, // # 9 slipper_nut()
> 1, // #10 slipper_to_drive_gear_spacer()
> 1, // #11 drive_gear()
> 1, // #12 top_shaft_left_spacer() &
> // left_top_shaft_bearing()
> 1, // #13 top_shaft_right_spacer() &
> // right_top_shaft_bearing()
> 2, // #14 top_shaft_end_screw()
> 2]; // # ending()
>
> // Returns a 1-based part number. Does not start from zero. Identifies
> the
> // part being "introduced" in its introductory animation.
> function get_part_number(tick,index)=
> (tick<0)?index:
> (get_part_number(tick-animation_durations[index],
> index+1));
>
> function total_animation_steps(index,step_count)=
> (index>=len(animation_durations))?
> step_count:
> total_animation_steps(index+1,
> step_count+animation_durations[index]);
>
> // Parameter 'partnum' is one-based. Returns the number of animation steps
> // that have preceded the beginning of part intro animation for specified
> part.
> function total_animation_steps_up_to(partnum,index,step_count)=
> (index>=(partnum-1))?
> step_count:
> total_animation_steps_up_to(partnum,index+1,
> step_count+animation_durations[index]);
>
> // The input parameter is 1-based and represents the part #.
> function part_animation_steps(partnum)=
> animation_durations[partnum-1];
>
> // Value 't' is a value in [0,1), it increases linearly with time to span
> the
> // entirety of animation.
> t=0.34; // For example, we're a bit more than 1/3 of the way through the
> anim.
>
> // Parameter 'i' is the animation ticker, zero-based.
> i=floor(t*total_animation_steps(0,0)); // Start from 0.
> p=get_part_number(i,0);
>
> // Parameter 'w' is like 't', meaning it is in the range 0 <= w < 1, but
> it
> // is specific to the part intro animation. The part intro animation
> begins
> // at w=0 and is already over at w=1.
> pre_w=(t*total_animation_steps(0,0)
> -total_animation_steps_up_to(p,0,0))/part_animation_steps(p);
>
> // First it will print out a one-based index describing the part we're on.
> echo(p);
>
> // Next it will print out which fraction of this part's scene we've done.
> echo(pre_w);
>
>
> Sent with Proton Mail <https://proton.me/> secure email.
>
> On Wednesday, January 24th, 2024 at 7:21 PM, Adrian Mariano via Discuss <
> discuss@lists.openscad.org> wrote:
>
> I don't understand why it's any trickier to assemble the order of points
> with recursive programming. In either case, if you have a growing list (the
> result) you compute new values and add them to the end of the list.
> Normally I write a recursive function something like
>
> function func(result,info) =
> done ? result
> : func(concat(result, more_results_based_on_info),
> new_info));
>
> The result grows in an obvious way and there is no "trickery" or "sorcery"
> involved. It's as straight forward as any other kind of coding, though it
> can get annoying if "info" contains many things, and I can't say anything
> good about the speed of execution.
>
> On Wed, Jan 24, 2024 at 3:30 PM neri-engineering via Discuss <
> discuss@lists.openscad.org> wrote:
>
>> With recursive functions it's a bit tricky to figure out the order of
>> points after they're all assembled. The problem I had was that the
>> recursive function which called concat() on two lists did it in an order
>> that resulted in the final list having its points in a reverse order from
>> what I expected. There is always "trickery" and a fair bit of "sorcery"
>> involved in getting recursive functions to do the right thing. Anyhow this
>> reverse order (from what I was assuming) is what caused the
>> clockwise/counterclockwise polygon traversal situation.
>>
>> Sent with Proton Mail <https://proton.me/> secure email.
>>
>> On Wednesday, January 24th, 2024 at 2:06 PM, David Phillip Oster via
>> Discuss <discuss@lists.openscad.org> wrote:
>>
>> Thank you for that neri-engineering, and nop head. That’s a fun example.
>>
>>
>> _______________________________________________
>> 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
>

FH

Father Horton

Thu, Jan 25, 2024 4:04 AM

Also note that you can have multiple assignments in a let statement. Just

separate them with commas. I assume they evaluate left-to-right, though I

don't think I've ever put that to the test.

On Wed, Jan 24, 2024 at 9:50 PM Father Horton fatherhorton@gmail.com

wrote:

Actually you can declare local variables. It's just not obvious how.

From the manual:

function get_square_triangle_perimeter(p1, p2) = let (hypotenuse = sqrt(p1*p1+p2*p2)) p1 + p2 + hypotenuse;

On Wed, Jan 24, 2024 at 9:47 PM neri-engineering via Discuss <

discuss@lists.openscad.org> wrote:

It's because in functions you can't declare local variables. So many

times you end up writing helper functions which take the semi-compiled form

of equations/data. This is one of the reasons why recursive functions in

OpenSCAD are difficult to write. It certainly takes some getting used to.

That's why I call it "sorcery". It's good mental gymnastics to keep you

sharp, to prevent the ole Alzheimer's from setting in. We keep our bodies

sharp by working out and we keep our minds sharp by writing recursive

functions in OpenSCAD. Some examples I can give you which demonstrate my

point. With a standard iterative process we would accomplish the following

with simpler code.

// There are 21 steps currently.

animation_durations=[1, // # 1 slipper_back_plate()

1, // # 2 slipper_bearing()

2, // # 3 top_shaft()

1, // # 4 slipper_back_plate_pin()

2, // # 5 spur_gear() & left_slipper_pad()

1, // # 6 right_slipper_pad()

1, // # 7 slipper_front_plate()

1, // # 8 slipper_spring()

3, // # 9 slipper_nut()

1, // #10 slipper_to_drive_gear_spacer()

1, // #11 drive_gear()

1, // #12 top_shaft_left_spacer() &

// left_top_shaft_bearing()

1, // #13 top_shaft_right_spacer() &

// right_top_shaft_bearing()

2, // #14 top_shaft_end_screw()

2]; // # ending()

// Returns a 1-based part number. Does not start from zero. Identifies

the

// part being "introduced" in its introductory animation.

function get_part_number(tick,index)=

(tick<0)?index:

(get_part_number(tick-animation_durations[index],

index+1));

function total_animation_steps(index,step_count)=

(index>=len(animation_durations))?

step_count:

total_animation_steps(index+1,

step_count+animation_durations[index]);

// Parameter 'partnum' is one-based. Returns the number of animation

steps

// that have preceded the beginning of part intro animation for specified

part.

function total_animation_steps_up_to(partnum,index,step_count)=

(index>=(partnum-1))?

step_count:

total_animation_steps_up_to(partnum,index+1,

step_count+animation_durations[index]);

// The input parameter is 1-based and represents the part #.

function part_animation_steps(partnum)=

animation_durations[partnum-1];

// Value 't' is a value in [0,1), it increases linearly with time to span

the

// entirety of animation.

t=0.34; // For example, we're a bit more than 1/3 of the way through the

anim.

// Parameter 'i' is the animation ticker, zero-based.

i=floor(t*total_animation_steps(0,0)); // Start from 0.

p=get_part_number(i,0);

// Parameter 'w' is like 't', meaning it is in the range 0 <= w < 1, but

it

// is specific to the part intro animation. The part intro animation

begins

// at w=0 and is already over at w=1.

pre_w=(t*total_animation_steps(0,0)

-total_animation_steps_up_to(p,0,0))/part_animation_steps(p);

// First it will print out a one-based index describing the part we're on.

echo(p);

// Next it will print out which fraction of this part's scene we've done.

echo(pre_w);

Sent with Proton Mail https://proton.me/ secure email.

On Wednesday, January 24th, 2024 at 7:21 PM, Adrian Mariano via Discuss <

discuss@lists.openscad.org> wrote:

with recursive programming. In either case, if you have a growing list (the

result) you compute new values and add them to the end of the list.

Normally I write a recursive function something like

done ? result

: func(concat(result, more_results_based_on_info),

new_info));

"sorcery" involved. It's as straight forward as any other kind of coding,

though it can get annoying if "info" contains many things, and I can't say

anything good about the speed of execution.

On Wed, Jan 24, 2024 at 3:30 PM neri-engineering via Discuss <

discuss@lists.openscad.org> wrote:

points after they're all assembled. The problem I had was that the

recursive function which called concat() on two lists did it in an order

that resulted in the final list having its points in a reverse order from

what I expected. There is always "trickery" and a fair bit of "sorcery"

involved in getting recursive functions to do the right thing. Anyhow this

reverse order (from what I was assuming) is what caused the

clockwise/counterclockwise polygon traversal situation.

Sent with Proton Mail https://proton.me/ secure email.

Discuss discuss@lists.openscad.org wrote:

Thank you for that neri-engineering, and nop head. That’s a fun example.

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

Also note that you can have multiple assignments in a let statement. Just
separate them with commas. I assume they evaluate left-to-right, though I
don't think I've ever put that to the test.
On Wed, Jan 24, 2024 at 9:50 PM Father Horton <fatherhorton@gmail.com>
wrote:
> Actually you can declare local variables. It's just not obvious how.
>
> From the manual:
>
> function get_square_triangle_perimeter(p1, p2) = let (hypotenuse = sqrt(p1*p1+p2*p2)) p1 + p2 + hypotenuse;
>
>
> On Wed, Jan 24, 2024 at 9:47 PM neri-engineering via Discuss <
> discuss@lists.openscad.org> wrote:
>
>> It's because in functions you can't declare local variables. So many
>> times you end up writing helper functions which take the semi-compiled form
>> of equations/data. This is one of the reasons why recursive functions in
>> OpenSCAD are difficult to write. It certainly takes some getting used to.
>> That's why I call it "sorcery". It's good mental gymnastics to keep you
>> sharp, to prevent the ole Alzheimer's from setting in. We keep our bodies
>> sharp by working out and we keep our minds sharp by writing recursive
>> functions in OpenSCAD. Some examples I can give you which demonstrate my
>> point. With a standard iterative process we would accomplish the following
>> with simpler code.
>>
>>
>> // There are 21 steps currently.
>> animation_durations=[1, // # 1 slipper_back_plate()
>> 1, // # 2 slipper_bearing()
>> 2, // # 3 top_shaft()
>> 1, // # 4 slipper_back_plate_pin()
>> 2, // # 5 spur_gear() & left_slipper_pad()
>> 1, // # 6 right_slipper_pad()
>> 1, // # 7 slipper_front_plate()
>> 1, // # 8 slipper_spring()
>> 3, // # 9 slipper_nut()
>> 1, // #10 slipper_to_drive_gear_spacer()
>> 1, // #11 drive_gear()
>> 1, // #12 top_shaft_left_spacer() &
>> // left_top_shaft_bearing()
>> 1, // #13 top_shaft_right_spacer() &
>> // right_top_shaft_bearing()
>> 2, // #14 top_shaft_end_screw()
>> 2]; // # ending()
>>
>> // Returns a 1-based part number. Does not start from zero. Identifies
>> the
>> // part being "introduced" in its introductory animation.
>> function get_part_number(tick,index)=
>> (tick<0)?index:
>> (get_part_number(tick-animation_durations[index],
>> index+1));
>>
>> function total_animation_steps(index,step_count)=
>> (index>=len(animation_durations))?
>> step_count:
>> total_animation_steps(index+1,
>> step_count+animation_durations[index]);
>>
>> // Parameter 'partnum' is one-based. Returns the number of animation
>> steps
>> // that have preceded the beginning of part intro animation for specified
>> part.
>> function total_animation_steps_up_to(partnum,index,step_count)=
>> (index>=(partnum-1))?
>> step_count:
>> total_animation_steps_up_to(partnum,index+1,
>> step_count+animation_durations[index]);
>>
>> // The input parameter is 1-based and represents the part #.
>> function part_animation_steps(partnum)=
>> animation_durations[partnum-1];
>>
>> // Value 't' is a value in [0,1), it increases linearly with time to span
>> the
>> // entirety of animation.
>> t=0.34; // For example, we're a bit more than 1/3 of the way through the
>> anim.
>>
>> // Parameter 'i' is the animation ticker, zero-based.
>> i=floor(t*total_animation_steps(0,0)); // Start from 0.
>> p=get_part_number(i,0);
>>
>> // Parameter 'w' is like 't', meaning it is in the range 0 <= w < 1, but
>> it
>> // is specific to the part intro animation. The part intro animation
>> begins
>> // at w=0 and is already over at w=1.
>> pre_w=(t*total_animation_steps(0,0)
>> -total_animation_steps_up_to(p,0,0))/part_animation_steps(p);
>>
>> // First it will print out a one-based index describing the part we're on.
>> echo(p);
>>
>> // Next it will print out which fraction of this part's scene we've done.
>> echo(pre_w);
>>
>>
>> Sent with Proton Mail <https://proton.me/> secure email.
>>
>> On Wednesday, January 24th, 2024 at 7:21 PM, Adrian Mariano via Discuss <
>> discuss@lists.openscad.org> wrote:
>>
>> I don't understand why it's any trickier to assemble the order of points
>> with recursive programming. In either case, if you have a growing list (the
>> result) you compute new values and add them to the end of the list.
>> Normally I write a recursive function something like
>>
>> function func(result,info) =
>> done ? result
>> : func(concat(result, more_results_based_on_info),
>> new_info));
>>
>> The result grows in an obvious way and there is no "trickery" or
>> "sorcery" involved. It's as straight forward as any other kind of coding,
>> though it can get annoying if "info" contains many things, and I can't say
>> anything good about the speed of execution.
>>
>> On Wed, Jan 24, 2024 at 3:30 PM neri-engineering via Discuss <
>> discuss@lists.openscad.org> wrote:
>>
>>> With recursive functions it's a bit tricky to figure out the order of
>>> points after they're all assembled. The problem I had was that the
>>> recursive function which called concat() on two lists did it in an order
>>> that resulted in the final list having its points in a reverse order from
>>> what I expected. There is always "trickery" and a fair bit of "sorcery"
>>> involved in getting recursive functions to do the right thing. Anyhow this
>>> reverse order (from what I was assuming) is what caused the
>>> clockwise/counterclockwise polygon traversal situation.
>>>
>>> Sent with Proton Mail <https://proton.me/> secure email.
>>>
>>> On Wednesday, January 24th, 2024 at 2:06 PM, David Phillip Oster via
>>> Discuss <discuss@lists.openscad.org> wrote:
>>>
>>> Thank you for that neri-engineering, and nop head. That’s a fun example.
>>>
>>>
>>> _______________________________________________
>>> 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
>>
>

N

neri-engineering

Thu, Jan 25, 2024 4:35 AM

Well that certainly makes things easier, thanks. This syntax is reminiscent of TinyScheme, which I used inside of GIMP image tool.

Sent with Proton Mail secure email.

On Wednesday, January 24th, 2024 at 10:04 PM, Father Horton via Discuss discuss@lists.openscad.org wrote:

Also note that you can have multiple assignments in a let statement. Just separate them with commas. I assume they evaluate left-to-right, though I don't think I've ever put that to the test.

On Wed, Jan 24, 2024 at 9:50 PM Father Horton fatherhorton@gmail.com wrote:

Actually you can declare local variables. It's just not obvious how.

From the manual:

function

get_square_triangle_perimeter

(

p1

,

p2

)

=

let

(

hypotenuse

=

sqrt

(

p1

p1

p2

p2

))

p1

p2

hypotenuse

;

On Wed, Jan 24, 2024 at 9:47 PM neri-engineering via Discuss discuss@lists.openscad.org wrote:

animation_durations=[1, // # 1 slipper_back_plate()

1, // # 2 slipper_bearing()

2, // # 3 top_shaft()

1, // # 4 slipper_back_plate_pin()

2, // # 5 spur_gear() & left_slipper_pad()

1, // # 6 right_slipper_pad()

1, // # 7 slipper_front_plate()

1, // # 8 slipper_spring()

3, // # 9 slipper_nut()

1, // #10 slipper_to_drive_gear_spacer()

1, // #11 drive_gear()

1, // #12 top_shaft_left_spacer() &

// left_top_shaft_bearing()

1, // #13 top_shaft_right_spacer() &

// right_top_shaft_bearing()

2, // #14 top_shaft_end_screw()

2]; // # ending()

// part being "introduced" in its introductory animation.

function get_part_number(tick,index)=

(tick<0)?index:

(get_part_number(tick-animation_durations[index],

index+1));

(index>=len(animation_durations))?

step_count:

total_animation_steps(index+1,

step_count+animation_durations[index]);

// that have preceded the beginning of part intro animation for specified part.

function total_animation_steps_up_to(partnum,index,step_count)=

(index>=(partnum-1))?

step_count:

total_animation_steps_up_to(partnum,index+1,

step_count+animation_durations[index]);

function part_animation_steps(partnum)=

animation_durations[partnum-1];

// entirety of animation.

t=0.34; // For example, we're a bit more than 1/3 of the way through the anim.

i=floor(t*total_animation_steps(0,0)); // Start from 0.

p=get_part_number(i,0);

// is specific to the part intro animation. The part intro animation begins

// at w=0 and is already over at w=1.

pre_w=(t*total_animation_steps(0,0)

-total_animation_steps_up_to(p,0,0))/part_animation_steps(p);

// First it will print out a one-based index describing the part we're on.

echo(p);

// Next it will print out which fraction of this part's scene we've done.echo(pre_w);

Sent with Proton Mail secure email.

On Wednesday, January 24th, 2024 at 7:21 PM, Adrian Mariano via Discuss discuss@lists.openscad.org wrote:

done ? result

: func(concat(result, more_results_based_on_info),

new_info));

On Wed, Jan 24, 2024 at 3:30 PM neri-engineering via Discuss discuss@lists.openscad.org wrote:

Sent with Proton Mail secure email.

Thank you for that neri-engineering, and nop head. That’s a fun example.

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

Well that certainly makes things easier, thanks. This syntax is reminiscent of TinyScheme, which I used inside of GIMP image tool.
Sent with [Proton Mail](https://proton.me/) secure email.
On Wednesday, January 24th, 2024 at 10:04 PM, Father Horton via Discuss <discuss@lists.openscad.org> wrote:
> Also note that you can have multiple assignments in a let statement. Just separate them with commas. I assume they evaluate left-to-right, though I don't think I've ever put that to the test.
>
> On Wed, Jan 24, 2024 at 9:50 PM Father Horton <fatherhorton@gmail.com> wrote:
>
>> Actually you can declare local variables. It's just not obvious how.
>>
>> From the manual:
>>
>> function
>>
>> get_square_triangle_perimeter
>>
>> (
>>
>> p1
>>
>> ,
>>
>> p2
>>
>> )
>>
>> =
>>
>> let
>>
>> (
>>
>> hypotenuse
>>
>> =
>>
>> sqrt
>>
>> (
>>
>> p1
>>
>> *
>>
>> p1
>>
>> +
>>
>> p2
>>
>> *
>>
>> p2
>>
>> ))
>>
>> p1
>>
>> +
>>
>> p2
>>
>> +
>>
>> hypotenuse
>>
>> ;
>>
>> On Wed, Jan 24, 2024 at 9:47 PM neri-engineering via Discuss <discuss@lists.openscad.org> wrote:
>>
>>> It's because in functions you can't declare local variables. So many times you end up writing helper functions which take the semi-compiled form of equations/data. This is one of the reasons why recursive functions in OpenSCAD are difficult to write. It certainly takes some getting used to. That's why I call it "sorcery". It's good mental gymnastics to keep you sharp, to prevent the ole Alzheimer's from setting in. We keep our bodies sharp by working out and we keep our minds sharp by writing recursive functions in OpenSCAD. Some examples I can give you which demonstrate my point. With a standard iterative process we would accomplish the following with simpler code.
>>>
>>> // There are 21 steps currently.
>>> animation_durations=[1, // # 1 slipper_back_plate()
>>> 1, // # 2 slipper_bearing()
>>> 2, // # 3 top_shaft()
>>> 1, // # 4 slipper_back_plate_pin()
>>> 2, // # 5 spur_gear() & left_slipper_pad()
>>> 1, // # 6 right_slipper_pad()
>>> 1, // # 7 slipper_front_plate()
>>> 1, // # 8 slipper_spring()
>>> 3, // # 9 slipper_nut()
>>> 1, // #10 slipper_to_drive_gear_spacer()
>>> 1, // #11 drive_gear()
>>> 1, // #12 top_shaft_left_spacer() &
>>> // left_top_shaft_bearing()
>>> 1, // #13 top_shaft_right_spacer() &
>>> // right_top_shaft_bearing()
>>> 2, // #14 top_shaft_end_screw()
>>> 2]; // # ending()
>>>
>>> // Returns a 1-based part number. Does not start from zero. Identifies the
>>> // part being "introduced" in its introductory animation.
>>> function get_part_number(tick,index)=
>>> (tick<0)?index:
>>> (get_part_number(tick-animation_durations[index],
>>> index+1));
>>>
>>> function total_animation_steps(index,step_count)=
>>> (index>=len(animation_durations))?
>>> step_count:
>>> total_animation_steps(index+1,
>>> step_count+animation_durations[index]);
>>>
>>> // Parameter 'partnum' is one-based. Returns the number of animation steps
>>> // that have preceded the beginning of part intro animation for specified part.
>>> function total_animation_steps_up_to(partnum,index,step_count)=
>>> (index>=(partnum-1))?
>>> step_count:
>>> total_animation_steps_up_to(partnum,index+1,
>>> step_count+animation_durations[index]);
>>>
>>> // The input parameter is 1-based and represents the part #.
>>> function part_animation_steps(partnum)=
>>> animation_durations[partnum-1];
>>>
>>> // Value 't' is a value in [0,1), it increases linearly with time to span the
>>> // entirety of animation.
>>> t=0.34; // For example, we're a bit more than 1/3 of the way through the anim.
>>>
>>> // Parameter 'i' is the animation ticker, zero-based.
>>> i=floor(t*total_animation_steps(0,0)); // Start from 0.
>>> p=get_part_number(i,0);
>>>
>>> // Parameter 'w' is like 't', meaning it is in the range 0 <= w < 1, but it
>>> // is specific to the part intro animation. The part intro animation begins
>>> // at w=0 and is already over at w=1.
>>> pre_w=(t*total_animation_steps(0,0)
>>> -total_animation_steps_up_to(p,0,0))/part_animation_steps(p);
>>>
>>> // First it will print out a one-based index describing the part we're on.
>>> echo(p);
>>>
>>> // Next it will print out which fraction of this part's scene we've done.echo(pre_w);
>>>
>>> Sent with [Proton Mail](https://proton.me/) secure email.
>>>
>>> On Wednesday, January 24th, 2024 at 7:21 PM, Adrian Mariano via Discuss <discuss@lists.openscad.org> wrote:
>>>
>>>> I don't understand why it's any trickier to assemble the order of points with recursive programming. In either case, if you have a growing list (the result) you compute new values and add them to the end of the list. Normally I write a recursive function something like
>>>>
>>>> function func(result,info) =
>>>> done ? result
>>>> : func(concat(result, more_results_based_on_info),
>>>> new_info));
>>>>
>>>> The result grows in an obvious way and there is no "trickery" or "sorcery" involved. It's as straight forward as any other kind of coding, though it can get annoying if "info" contains many things, and I can't say anything good about the speed of execution.
>>>>
>>>> On Wed, Jan 24, 2024 at 3:30 PM neri-engineering via Discuss <discuss@lists.openscad.org> wrote:
>>>>
>>>>> With recursive functions it's a bit tricky to figure out the order of points after they're all assembled. The problem I had was that the recursive function which called concat() on two lists did it in an order that resulted in the final list having its points in a reverse order from what I expected. There is always "trickery" and a fair bit of "sorcery" involved in getting recursive functions to do the right thing. Anyhow this reverse order (from what I was assuming) is what caused the clockwise/counterclockwise polygon traversal situation.
>>>>>
>>>>> Sent with [Proton Mail](https://proton.me/) secure email.
>>>>>
>>>>> On Wednesday, January 24th, 2024 at 2:06 PM, David Phillip Oster via Discuss <discuss@lists.openscad.org> wrote:
>>>>>
>>>>>> Thank you for that neri-engineering, and nop head. That’s a fun example.
>>>>>
>>>>> _______________________________________________
>>>>> 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

Replying to:

Empathy v1.0
2024 ©Harmonylists.com