discuss@lists.openscad.org

OpenSCAD general discussion Mailing-list

View all threads

Convexity & difference()

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_startspitch)/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_turnn_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_rcos(360(n_curr%n_segments)/n_segments),
outer_rsin(360(n_curr%n_segments)/n_segments),
z_rotn_curr+pitch-d_mid+z_off],
[outer_r
cos(360*(n_curr%n_segments)/n_segments),
outer_rsin(360(n_curr%n_segments)/n_segments),
z_rotn_curr+d_mid+z_off],
[inner_r
cos(360*(n_curr%n_segments)/n_segments),
inner_rsin(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 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_startspitch)/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_rcos(360(n_curr%n_segments)/n_segments),
outer_rsin(360(n_curr%n_segments)/n_segments),
z_rotn_curr+pitch-d_mid+z_off],
[outer_r
cos(360*(n_curr%n_segments)/n_segments),
outer_rsin(360(n_curr%n_segments)/n_segments),
z_rotn_curr+d_mid+z_off],
[inner_r
cos(360*(n_curr%n_segments)/n_segments),
inner_rsin(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

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_startspitch)/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_turnn_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_rcos(360(n_curr%n_segments)/n_segments),
outer_rsin(360(n_curr%n_segments)/n_segments),
z_rotn_curr+pitch-d_mid+z_off],
[outer_r
cos(360*(n_curr%n_segments)/n_segments),
outer_rsin(360(n_curr%n_segments)/n_segments),
z_rotn_curr+d_mid+z_off],
[inner_r
cos(360*(n_curr%n_segments)/n_segments),
inner_rsin(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 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(p1p1+p2p2))    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

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(p1p1+p2p2))    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

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:

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


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