Nabble has removed Mailing-list integration.
Posts created here DO NOT GET SENT TO THE MAILING LIST.
Mailing-list emails DO NOT GET POSTED TO THE FORUM.
So basically the Forum is now out of date, we are looking into migrating the history.

For now you should send emails, people will see them, discuss@lists.openscad.org.

enabling/disabling code

classic Classic list List threaded Threaded
8 messages Options
Reply | Threaded
Open this post in threaded view
|

enabling/disabling code

jon_bondy
I sometimes run an OpenSCAD program in more than one mode, to create
more than one variant of an object.

Sometimes I can do something like this

// variant 1

rotate(...)

     translate(...)

         cube(...);

// variant 2

*rotate(...)

     translate(...)

         cube(...);

and use a leading "*" to control which code section I want to be
active.  This is annoying, but tolerable, if the number or such code
sections is small.


But sometimes it looks like this:

rotate(...)

//    translate(...)        // variant 1

     translate(...)        // variant 2

         cube(...);

and I end up commenting out a single line.  This approach is much more
error prone and annoying


Am I missing a language feature that would help in the 2nd case? In the
1st case I know I can use if() statements, but sometimes that makes the
code more obscure.

Thanks!

Jon


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: enabling/disabling code

nophead
rotate(...)
      translate(condition ? [ ... ] : [ ... ]) 
           cube();

On Sun, 6 Sep 2020 at 20:11, jon <[hidden email]> wrote:
I sometimes run an OpenSCAD program in more than one mode, to create
more than one variant of an object.

Sometimes I can do something like this

// variant 1

rotate(...)

     translate(...)

         cube(...);

// variant 2

*rotate(...)

     translate(...)

         cube(...);

and use a leading "*" to control which code section I want to be
active.  This is annoying, but tolerable, if the number or such code
sections is small.


But sometimes it looks like this:

rotate(...)

//    translate(...)        // variant 1

     translate(...)        // variant 2

         cube(...);

and I end up commenting out a single line.  This approach is much more
error prone and annoying


Am I missing a language feature that would help in the 2nd case? In the
1st case I know I can use if() statements, but sometimes that makes the
code more obscure.

Thanks!

Jon


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: enabling/disabling code

jon_bondy

Thanks!  That is a big help if you have 2 versions!

On 9/6/2020 4:28 PM, nop head wrote:
rotate(...)
      translate(condition ? [ ... ] : [ ... ]) 
           cube();

On Sun, 6 Sep 2020 at 20:11, jon <[hidden email]> wrote:
I sometimes run an OpenSCAD program in more than one mode, to create
more than one variant of an object.

Sometimes I can do something like this

// variant 1

rotate(...)

     translate(...)

         cube(...);

// variant 2

*rotate(...)

     translate(...)

         cube(...);

and use a leading "*" to control which code section I want to be
active.  This is annoying, but tolerable, if the number or such code
sections is small.


But sometimes it looks like this:

rotate(...)

//    translate(...)        // variant 1

     translate(...)        // variant 2

         cube(...);

and I end up commenting out a single line.  This approach is much more
error prone and annoying


Am I missing a language feature that would help in the 2nd case? In the
1st case I know I can use if() statements, but sometimes that makes the
code more obscure.

Thanks!

Jon


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: enabling/disabling code

acwest
You can extend it to as many conditions as you want, to:
rotate(...)
      translate(
        condition ?
          [ ... ]
        : condition2 ? 
          [ ... ]
        :
          [...] 
       ) 
           cube();

On Sun, 6 Sep 2020, 18:38 jon, <[hidden email]> wrote:

Thanks!  That is a big help if you have 2 versions!

On 9/6/2020 4:28 PM, nop head wrote:
rotate(...)
      translate(condition ? [ ... ] : [ ... ]) 
           cube();

On Sun, 6 Sep 2020 at 20:11, jon <[hidden email]> wrote:
I sometimes run an OpenSCAD program in more than one mode, to create
more than one variant of an object.

Sometimes I can do something like this

// variant 1

rotate(...)

     translate(...)

         cube(...);

// variant 2

*rotate(...)

     translate(...)

         cube(...);

and use a leading "*" to control which code section I want to be
active.  This is annoying, but tolerable, if the number or such code
sections is small.


But sometimes it looks like this:

rotate(...)

//    translate(...)        // variant 1

     translate(...)        // variant 2

         cube(...);

and I end up commenting out a single line.  This approach is much more
error prone and annoying


Am I missing a language feature that would help in the 2nd case? In the
1st case I know I can use if() statements, but sometimes that makes the
code more obscure.

Thanks!

Jon


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: enabling/disabling code

alexgibson
In reply to this post by nophead

I have multiple designs like this where I need to enable/disable sometimes complex sets of features.

 

My current method is to set a variable to 1 (true) or 2 (false) and scale the feature by this number:

 

is_version_1 = 1;

 

                scale([is_version_1, is_version_1, is_version_1])

                                {

                                …

                                }

 

This is very easy to do and can be done inline in lots of code just by multiplying the feature – this is very useful where dimensions vary between versions:

 

 

 

is_version_1 = 1;

is_version_2 =0;

is_version_2 = 0;

 

                translate([is_version_1*50+ is_version_2*60+ is_version_3*70, 0,0])

                                {

                                …

                                }

                               

I’m not sure I understand nop head’s ‘condition’ – will look this up, it might be the correct way to do what I’m hacking here – but it works well.

 

 

Alex Gibson

 

admg consulting

 

edumaker limited

 

·         Project management

·         Operations & Process improvement

·         3D Printing

 

From: Discuss [mailto:[hidden email]] On Behalf Of nop head
Sent: 06 September 2020 21:28
To: OpenSCAD general discussion
Subject: Re: [OpenSCAD] enabling/disabling code

 

rotate(...)

      translate(condition ? [ ... ] : [ ... ]) 

           cube();

 

On Sun, 6 Sep 2020 at 20:11, jon <[hidden email]> wrote:

I sometimes run an OpenSCAD program in more than one mode, to create
more than one variant of an object.

Sometimes I can do something like this

// variant 1

rotate(...)

     translate(...)

         cube(...);

// variant 2

*rotate(...)

     translate(...)

         cube(...);

and use a leading "*" to control which code section I want to be
active.  This is annoying, but tolerable, if the number or such code
sections is small.


But sometimes it looks like this:

rotate(...)

//    translate(...)        // variant 1

     translate(...)        // variant 2

         cube(...);

and I end up commenting out a single line.  This approach is much more
error prone and annoying


Am I missing a language feature that would help in the 2nd case? In the
1st case I know I can use if() statements, but sometimes that makes the
code more obscure.

Thanks!

Jon


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org

 

Virus-free. www.avg.com

 


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Ned
Reply | Threaded
Open this post in threaded view
|

Re: enabling/disabling code

Ned
Rather than embed the variants in the code, create a group of named variables (to be used as constants) at the beginning of your program. Create a duplicate group of variables of the same names but with different values for another configuration, and a third, and so on. You can enable a particular set of values  by commenting out the sets not to be used.  

I have a program that produces 3D-printed fastener test blocks, each block with a number of holes to test sizes for M6 or 10-32. I think it has 10 configurations so far. 

On Sep 6, 2020, at 4:00 PM, Alex Gibson <[hidden email]> wrote:



I have multiple designs like this where I need to enable/disable sometimes complex sets of features.

 

My current method is to set a variable to 1 (true) or 2 (false) and scale the feature by this number:

 

is_version_1 = 1;

 

                scale([is_version_1, is_version_1, is_version_1])

                                {

                                …

                                }

 

This is very easy to do and can be done inline in lots of code just by multiplying the feature – this is very useful where dimensions vary between versions:

 

 

 

is_version_1 = 1;

is_version_2 =0;

is_version_2 = 0;

 

                translate([is_version_1*50+ is_version_2*60+ is_version_3*70, 0,0])

                                {

                                …

                                }

                               

I’m not sure I understand nop head’s ‘condition’ – will look this up, it might be the correct way to do what I’m hacking here – but it works well.

 

 

Alex Gibson

 

admg consulting

 

edumaker limited

 

·         Project management

·         Operations & Process improvement

·         3D Printing

 

From: Discuss [mailto:[hidden email]] On Behalf Of nop head
Sent: 06 September 2020 21:28
To: OpenSCAD general discussion
Subject: Re: [OpenSCAD] enabling/disabling code

 

rotate(...)

      translate(condition ? [ ... ] : [ ... ]) 

           cube();

 

On Sun, 6 Sep 2020 at 20:11, jon <[hidden email]> wrote:

I sometimes run an OpenSCAD program in more than one mode, to create
more than one variant of an object.

Sometimes I can do something like this

// variant 1

rotate(...)

     translate(...)

         cube(...);

// variant 2

*rotate(...)

     translate(...)

         cube(...);

and use a leading "*" to control which code section I want to be
active.  This is annoying, but tolerable, if the number or such code
sections is small.


But sometimes it looks like this:

rotate(...)

//    translate(...)        // variant 1

     translate(...)        // variant 2

         cube(...);

and I end up commenting out a single line.  This approach is much more
error prone and annoying


Am I missing a language feature that would help in the 2nd case? In the
1st case I know I can use if() statements, but sometimes that makes the
code more obscure.

Thanks!

Jon


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org

 

Virus-free. www.avg.com

 

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: enabling/disabling code

JordanBrown
In reply to this post by jon_bondy
For the case of translating or rotating differently, I would think in terms of setting a variable to the desired translation.
// Pick one of these two somehow.
origin = [0,0,0];
// origin = [100,100,100];

rotate(...)
    translate(origin)
        cube(...);

For the problem of selecting individual components out of a model, I have had two approaches:

One was where I had an object that was built out of multiple individual printed components.  I wanted to be able to display it either assembled (for design) or pluck out an individual component (for printing).

I used these modules:
module ifpart(p) {
    if (is_undef($part) || $part == p) {
        children();
    }
}

module ifpartonly(p) {
    if (!is_undef($part) && $part == p) {
        children();
    }
}

module ifpartall() {
    if (is_undef($part)) {
        children();
    }
}

Setting $part would let you select a particular component, and then inside the model

ifpart("a") { ... part A ... }

Executes if part A is selected, or if everything is selected.

ifpartonly("a") { ... }

Executes if part A is explicitly selected.  (For instance, for printing a component that occurs multiple times in the full model.)

ifpartall() { ... }

Executes if everything is selected.  (Complementary to ifpartonly(), for the repetitions of a component that need to be displayed in the full assembly.)


A related set was intended for managing multiple models in a single file:

module contents(d=100) {
    nx = ceil(sqrt($children));
    ny = ceil($children/nx);
    for (xi=[0:nx-1], yi=[0:ny-1]) {
        i = xi + yi*nx;
        if (i < $children) {
            $content_origin=[xi*d, yi*d];
            children(i);
        }
    }
}

module item(name, png=true, stl=true, distance, xrot, zrot, z) {
    if (!is_undef($content_inventory)) {
        _distance = default(distance, 150);
        _zrot = default(zrot, 30);
        _xrot = default(xrot, 60);
        _z = default(z, 15);
        camera = is_undef(distance) && is_undef(zrot) && is_undef(z) && is_undef(xrot)
            ? "--viewall"
            : str("--camera 0,0,", _z, ",", _xrot, ",0,", _zrot, ",", _distance);
        if ($content_inventory == "all")
            echo("PART", name);
        else if ($content_inventory == "png" && png)
            echo("PART", name, camera);
        else if ($content_inventory == "stl" && stl)
            echo("PART", name);
    } else {
        if (is_undef($content_selected)) {
            translate($content_origin)
                children();
        } else if ($content_selected == name) {
                children();
        }
    }
}


This is used like so:

contents() {
    item("piano") rotate(180) piano();
    item("console") dining_console();
    item("chair1") drchair();
    item("chair2") drchair2();
    item("silverwarechest", distance=120) silverwarechest();
    item("pianobench") pianobench();
    item("pianobench_top", png=false) pianobench($part="top");
    item("pianobench_leg", png=false) pianobench($part="leg");
    item("table") drtable();
    item("table_frame", png=false) drtable($part="frame");
    item("table_center", png=false) drtable($part="center");
    item("table_top", png=false) drtable($part="top");
    item("table_leaves", png=false) drtable_leaves();
    item("bookshelves") bookshelves();
}
Note that the default is to render all of the components, spread in a grid, but you can ask for an inventory (with no rendering) or that only a particular component be rendered, or an inventory of those components that I want a PNG for, or that I want an STL for.

and I have a script that runs OpenSCAD in batch mode with various settings, first to get the list of parts and then to render STL and PNG files for each:
#! /bin/sh

case $# in
0|1)
    echo "usage: $0 outdir file.scad ..." >&2
    exit 1
    ;;
esac

d="$1"
shift

function parts()
{
    openscad -D "\$content_inventory=\"$2\"" -o junk.stl "$1" 2>&1 |
        tr -d '\r' |
        sed -n -e 's/^ECHO: "PART", "\(.*\)"$/\1/p' |
        sed 's/", "/ /g'
}

for i; do
    base=$(basename "$i" .scad)
    parts $i png |
        while read part camera; do
            def="\$content_selected=\"$part\""
            printf "%s %s png...\n" "$base" "$part"
            openscad -D "$def" $camera -o "$d/$base.$part.png" "$i"
        done
    parts $i stl |
        while read part; do
            def="\$content_selected=\"$part\""
            printf "%s %s stl...\n" "$base" "$part"
            openscad -D "$def" -o "$d/$base.$part.stl" "$i"
        done
done

I could probably have solved both problems with a single set of modules... but I didn't.


While I'm mentioning it, for the "assembled or printable" question, one scheme that I played with that was kind of fun was to have an animation that would switch between assembled form and printable form.
// Given a value and a table of value/position pairs, interpolate a
// position for that value.
// It seems like lookup() should do this sort of vector interpolation
// on its own, but it doesn't seem to.
function xyzinterp(v, table) =
    let (x= [for (i=[0:len(table)-1]) [table[i][0], table[i][1][0]]])
    let (y= [for (i=[0:len(table)-1]) [table[i][0], table[i][1][1]]])
    let (z= [for (i=[0:len(table)-1]) [table[i][0], table[i][1][2]]])
        [lookup(v, x), lookup(v, y), lookup(v,z)];

// Given a table of animation time values (from zero to one) and
// positions for each of those time values, translate the children
// to the appropriate position.
module atranslate(table) {
    translate(xyzinterp($t, table)) children();
}

// Given a table of animation time values (from zero to one) and
// rotations for each of those time values, rotate the children
// to the appropriate position.
module arotate(table) {
    rotate(xyzinterp($t, table)) children();
}

// Given a start point and an end point, translate the children
// from the start to the end and back in each animation cycle.
// Pause briefly at the start and end.
module a2translate(p1, p2) {
    atranslate([[0.05, p1], [0.45, p2], [0.55, p2], [0.95, p1]]) children();
}

// Given a start rotation and an end rotation, rotate the children
// from the start to the end and back in each animation cycle.
// Pause briefly at the start and end.
module a2rotate(p1, p2) {
    arotate([[0.05, p1], [0.45, p2], [0.55, p2], [0.95, p1]]) children();
}

Like so:
a2translate([0,0,0], [0,0,0])
    cube(10);
a2translate([5,5,10], [20,0,10])
    a2rotate([0,0,0], [180,0,0])
    cylinder(h=10, d1=5, d2=10);
a2translate([5,5,20], [32,0,0])
    cylinder(h=10, d1=10, d2=5);

There you can set $t to 0 to get one form, and to 0.5 to get the other form.



_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
Reply | Threaded
Open this post in threaded view
|

Re: enabling/disabling code

nophead
I use the customiser to control which parts of my model to display, i.e. to customise its pose, rather than its intended use: to vary model parameters. I just test the variables it sets with if or use them to translate and rotate.

image.png

In this case I also can cross section the parts with sliders to see inside.

image.png

And I use $preview to switch between the assembly view with F5 and the laid out for printing view with F6

image.png

On Tue, 8 Sep 2020 at 21:03, Jordan Brown <[hidden email]> wrote:
For the case of translating or rotating differently, I would think in terms of setting a variable to the desired translation.
// Pick one of these two somehow.
origin = [0,0,0];
// origin = [100,100,100];

rotate(...)
    translate(origin)
        cube(...);

For the problem of selecting individual components out of a model, I have had two approaches:

One was where I had an object that was built out of multiple individual printed components.  I wanted to be able to display it either assembled (for design) or pluck out an individual component (for printing).

I used these modules:
module ifpart(p) {
    if (is_undef($part) || $part == p) {
        children();
    }
}

module ifpartonly(p) {
    if (!is_undef($part) && $part == p) {
        children();
    }
}

module ifpartall() {
    if (is_undef($part)) {
        children();
    }
}

Setting $part would let you select a particular component, and then inside the model

ifpart("a") { ... part A ... }

Executes if part A is selected, or if everything is selected.

ifpartonly("a") { ... }

Executes if part A is explicitly selected.  (For instance, for printing a component that occurs multiple times in the full model.)

ifpartall() { ... }

Executes if everything is selected.  (Complementary to ifpartonly(), for the repetitions of a component that need to be displayed in the full assembly.)


A related set was intended for managing multiple models in a single file:

module contents(d=100) {
    nx = ceil(sqrt($children));
    ny = ceil($children/nx);
    for (xi=[0:nx-1], yi=[0:ny-1]) {
        i = xi + yi*nx;
        if (i < $children) {
            $content_origin=[xi*d, yi*d];
            children(i);
        }
    }
}

module item(name, png=true, stl=true, distance, xrot, zrot, z) {
    if (!is_undef($content_inventory)) {
        _distance = default(distance, 150);
        _zrot = default(zrot, 30);
        _xrot = default(xrot, 60);
        _z = default(z, 15);
        camera = is_undef(distance) && is_undef(zrot) && is_undef(z) && is_undef(xrot)
            ? "--viewall"
            : str("--camera 0,0,", _z, ",", _xrot, ",0,", _zrot, ",", _distance);
        if ($content_inventory == "all")
            echo("PART", name);
        else if ($content_inventory == "png" && png)
            echo("PART", name, camera);
        else if ($content_inventory == "stl" && stl)
            echo("PART", name);
    } else {
        if (is_undef($content_selected)) {
            translate($content_origin)
                children();
        } else if ($content_selected == name) {
                children();
        }
    }
}


This is used like so:

contents() {
    item("piano") rotate(180) piano();
    item("console") dining_console();
    item("chair1") drchair();
    item("chair2") drchair2();
    item("silverwarechest", distance=120) silverwarechest();
    item("pianobench") pianobench();
    item("pianobench_top", png=false) pianobench($part="top");
    item("pianobench_leg", png=false) pianobench($part="leg");
    item("table") drtable();
    item("table_frame", png=false) drtable($part="frame");
    item("table_center", png=false) drtable($part="center");
    item("table_top", png=false) drtable($part="top");
    item("table_leaves", png=false) drtable_leaves();
    item("bookshelves") bookshelves();
}
Note that the default is to render all of the components, spread in a grid, but you can ask for an inventory (with no rendering) or that only a particular component be rendered, or an inventory of those components that I want a PNG for, or that I want an STL for.

and I have a script that runs OpenSCAD in batch mode with various settings, first to get the list of parts and then to render STL and PNG files for each:
#! /bin/sh

case $# in
0|1)
    echo "usage: $0 outdir file.scad ..." >&2
    exit 1
    ;;
esac

d="$1"
shift

function parts()
{
    openscad -D "\$content_inventory=\"$2\"" -o junk.stl "$1" 2>&1 |
        tr -d '\r' |
        sed -n -e 's/^ECHO: "PART", "\(.*\)"$/\1/p' |
        sed 's/", "/ /g'
}

for i; do
    base=$(basename "$i" .scad)
    parts $i png |
        while read part camera; do
            def="\$content_selected=\"$part\""
            printf "%s %s png...\n" "$base" "$part"
            openscad -D "$def" $camera -o "$d/$base.$part.png" "$i"
        done
    parts $i stl |
        while read part; do
            def="\$content_selected=\"$part\""
            printf "%s %s stl...\n" "$base" "$part"
            openscad -D "$def" -o "$d/$base.$part.stl" "$i"
        done
done

I could probably have solved both problems with a single set of modules... but I didn't.


While I'm mentioning it, for the "assembled or printable" question, one scheme that I played with that was kind of fun was to have an animation that would switch between assembled form and printable form.
// Given a value and a table of value/position pairs, interpolate a
// position for that value.
// It seems like lookup() should do this sort of vector interpolation
// on its own, but it doesn't seem to.
function xyzinterp(v, table) =
    let (x= [for (i=[0:len(table)-1]) [table[i][0], table[i][1][0]]])
    let (y= [for (i=[0:len(table)-1]) [table[i][0], table[i][1][1]]])
    let (z= [for (i=[0:len(table)-1]) [table[i][0], table[i][1][2]]])
        [lookup(v, x), lookup(v, y), lookup(v,z)];

// Given a table of animation time values (from zero to one) and
// positions for each of those time values, translate the children
// to the appropriate position.
module atranslate(table) {
    translate(xyzinterp($t, table)) children();
}

// Given a table of animation time values (from zero to one) and
// rotations for each of those time values, rotate the children
// to the appropriate position.
module arotate(table) {
    rotate(xyzinterp($t, table)) children();
}

// Given a start point and an end point, translate the children
// from the start to the end and back in each animation cycle.
// Pause briefly at the start and end.
module a2translate(p1, p2) {
    atranslate([[0.05, p1], [0.45, p2], [0.55, p2], [0.95, p1]]) children();
}

// Given a start rotation and an end rotation, rotate the children
// from the start to the end and back in each animation cycle.
// Pause briefly at the start and end.
module a2rotate(p1, p2) {
    arotate([[0.05, p1], [0.45, p2], [0.55, p2], [0.95, p1]]) children();
}

Like so:
a2translate([0,0,0], [0,0,0])
    cube(10);
a2translate([5,5,10], [20,0,10])
    a2rotate([0,0,0], [180,0,0])
    cylinder(h=10, d1=5, d2=10);
a2translate([5,5,20], [32,0,0])
    cylinder(h=10, d1=10, d2=5);

There you can set $t to 0 to get one form, and to 0.5 to get the other form.


_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org

_______________________________________________
OpenSCAD mailing list
[hidden email]
http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org