[OpenSCAD] Getting more information

Dan Shriver tabbydan at gmail.com
Fri Jul 5 09:42:54 EDT 2019


use <list-comprehension/skin.scad>;
use <nSpline/splines.scad>;
use <scad-utils/transformations.scad>
use <scad-utils/lists.scad>


function echoit(x) = echo(x) x;
//function echoit2(y,x) = echo(y,x) x; //debug version
function echoit2(y,x) =  x; //debug off
function echoit3(y,x) = x; //debug off
//function echoit4(y,x) = echo(y,x) x; //debug version
function echoit4(y,x) =  x; //debug off
function echoit5(y,x) = echo(y,x) x; //debug

function innerAngle(radius, thickness, angle) =
atan(((radius-thickness)*sin(angle))/((radius-thickness)*cos(angle)));

function partitionNum(pn,points) =
    (pn < (points-2)/4) ? 0 :         //OUTER RIGHT
    (pn < (points-2)/4 + 1)  ? 1 :    //OUTER TOP
    (pn < (points-2)/2 + 1) ? 2:      //OUTER LEFT
    (pn < (3*(points-2))/4 + 1) ? 3:  //INNER LEFT
    (pn < (3*(points-2))/4 + 2) ? 4:  //INNER TOP
    5;                                // INNER RIGHT

function subPointNum(points, pointNum, partition) =
 [pointNum,
  1, //doesn't matter
  ((points-2)/2) - pointNum,
  pointNum - points/2,
  1, //doesn't matter
  ((points-1) - pointNum)]
 [partition];


//one more point in the outer arcs one less in the inner
function partitionNumNc(pn,points,solid) = (solid) ? (pn < (points)/2) ? 0
:         //OUTER RIGHT
    (pn == (points)/2)  ? 1 :    //OUTER TOP
    2 :
    (pn < (points)/4) ? 0 :         //OUTER RIGHT
    (pn == (points)/4)  ? 1 :    //OUTER TOP
    (pn < ((points)/2)+1) ? 2:      //OUTER LEFT
    (pn < (3*points)/4) ? 3:  //INNER LEFT
    (pn == (3*points)/4) ? 4:  //INNER TOP
    5                                // INNER RIGHT
    ;

function subPointNumNc(points, pointNum, partition) =
 [pointNum,
  1, //doesn't matter
  ((points)/2) - pointNum,
  pointNum - (points/2+1),
  1, //doesn't matter
  ((points-1) - pointNum)]
 [partition];


//a roman arch with 4n + 4 points to match an ogee
//no cap arch with 8n points
//use a number of points that is a multiple of 8
 function archXNc(radius, thickness, angle, pointNum, points, solid) =
let(inner = innerAngle(radius, thickness, angle),
    partition = partitionNumNc(pointNum,points,solid),
    subPoint = subPointNumNc(points, pointNum, partition),
    steps = (pointNum < (points/2)) ? (((points)/4)) : (((points)/4) - 1))
    [radius * cos(angle/steps * pointNum) - radius * (cos(angle)),
     0,
     -(radius * cos(angle/steps * subPoint) - radius * (cos(angle))), //
PTS - 2 EXP + 1
    -(((radius - thickness) * cos((angle/steps) *  subPoint)) - ((radius -
thickness)*cos(angle))), //PTS -2  EXP +1
     0,
    (radius - thickness) * cos((angle/steps) *  subPoint) -
(radius-thickness)*cos(angle) //PTS - 2 EXP +2
    ]
    [partition];


function archYNc(radius, thickness, angle, pointNum, points, solid) =
let(inner = innerAngle(radius, thickness, angle),
    partition = partitionNumNc(pointNum,points,solid),
    subPoint = subPointNumNc(points, pointNum, partition),
    steps = (pointNum < (points/2)) ? ((points)/4) : ((points)/4) - 1)
    [radius * sin(angle/steps * pointNum),
     radius * sin(angle),
     (radius * sin(angle/steps * subPoint)),
     (radius - thickness) * sin( angle/steps * subPoint),
     (radius - thickness) * sin(angle),
     (radius - thickness) * sin((angle*subPoint)/steps)
    ]
    [partition];


  //use this with a no-cap ogee as this works out to
  //be 4n +  4 points which meshes with 8n of a no cap ogee
  // 8, 12, 16, 20,....
  // you will have to use ODD N values to match with
  // a linear progression of 8n
  // (4m)+ 4 = 8n for 1 on both sides 3m=2n; 5m = 3n etc
// This function actuall works on ugly numbers 6 + 4(n) points: 10, 14, 18,
22 ...
function romanArchNc(radius, thickness, angle, pointCnt, solid = false)
  = (solid) ? [ for (i = [0:1:(pointCnt/2 -1)]) [archXNc(radius, thickness,
angle, i, pointCnt, solid), archYNc(radius, thickness, angle, i, pointCnt,
solid)] ] : [ for (i = [0:1:(pointCnt-1)]) [archXNc(radius, thickness,
angle, i, pointCnt, solid), archYNc(radius, thickness, angle, i, pointCnt,
solid)] ];



//the second half is missing one point, figure it out...
//romanArch(20, 1, 70, 200);



//8n (if capwidth = 0); 8n + 2 otherwise
function ogeepoly(width, thickness, lowerradius, upperradius, points, solid
= false, capwidth = 0.0, apexangle = 45.0) =
    let(
    // Angle extended by the arcs
    lowerphi = 180.0 - acos((0.5*(width - capwidth) - lowerradius -
thickness - upperradius * cos(0.5*apexangle)) / (lowerradius + thickness +
upperradius)),
    upperphi = lowerphi - 0.5 * apexangle,
    apexhalf = 0.5 * apexangle,

    // Number of points per lower arc
    lowerpoints = echoit2("lowerpoints ",round((capwidth > 0.0) ?
lowerphi*(points-4)/(8*lowerphi-4*apexangle) :
lowerphi*(points-2)/(8*lowerphi-4*apexangle))),

    // Actual number of points in the upper arc
    uppertemp = echoit2("uppertemp ",round((capwidth > 0.0) ?
(lowerphi-apexangle)*(points-4)/(8*lowerphi-4*apexangle) :
(lowerphi-apexangle)*(points-2)/(8*lowerphi-4*apexangle))),
    targetpoints = echoit2("targetpoints ", (apexangle > 0.0) ? points - 5
: points - 2),
    upperpoints = echoit2("upperpoints ",(targetpoints - 4*(lowerpoints +
uppertemp) >= -1) ? uppertemp + 1 : uppertemp),

    // Key horizontal coordinates. Note: w0=x1, w6=x2
    w0 = 0.5*width - thickness - lowerradius,
    w1 = 0.5*capwidth,
    w2 = w0 + lowerradius * cos(lowerphi),
    w3 = w0 + (lowerradius + thickness) * cos(lowerphi),
    w4 = w0 + lowerradius,
    w5 = w0 + lowerradius + thickness,
    w6 = w0 + (lowerradius + thickness + upperradius) * cos(lowerphi),

    // Key vertical coordinates. Note: y1=h0=0, y2=h6
    h6 = (lowerradius + thickness + upperradius) * sin(lowerphi),
    h5 = h6 - upperradius * sin(apexhalf),
    h4 = h5 - 0.5*capwidth * tan(apexhalf),
    h3 = h6 - (upperradius + thickness) * sqrt(1.0 - w6*w6 / ((upperradius
+ thickness) * (upperradius + thickness))),
    h2 = (lowerradius + thickness) * sin(lowerphi),
    h1 = lowerradius * sin(lowerphi),
    h0 = 0.0,
    height = (apexangle > 0.0) ? (h5 + 0.5*capwidth / tan(apexhalf)) : h5,

    // We use the same number of points for the upper inner arc,
    // even though it is a bit smaller angle.
    innertheta = acos(w6 / (upperradius + thickness)),
    innerphi = lowerphi - innertheta,

    // Lower Right Outer arc
    lro = echoit3("lro ",[ for (i = [0:1:lowerpoints-1]) [ w0 +
(lowerradius + thickness) * cos(i * lowerphi / lowerpoints), h0 +
(lowerradius + thickness) * sin(i * lowerphi / lowerpoints) ] ]),
    // Upper Right Outer arc
    uro = echoit3("uro ",[ for (i = [upperpoints-1:-1:0]) [ w6 -
upperradius * cos(apexhalf + i * upperphi / upperpoints), h6 - upperradius
* sin(apexhalf + i * upperphi / upperpoints) ] ]),
    // Cap
    cap = (capwidth > 0.0) ? [ [ w1, h5 ], [ 0, height ] ] : [],
    // Upper Left Outer arc
    ulo = echoit3("ulo ",[ for (i = [0:1:upperpoints-1]) [ -w6 +
upperradius * cos(apexhalf + i * upperphi / upperpoints), h6 - upperradius
* sin(apexhalf + i * upperphi / upperpoints) ] ]),
    // Lower Left Outer arc
    //incorrectly 4 pts
    //changing loop first contition to lowerpoints-1 from "lowerpoints"
    llo = echoit3("llo ",[ for (i = [lowerpoints-1:-1:0]) [ -w0 -
(lowerradius + thickness) * cos(i * lowerphi / lowerpoints), h0 +
(lowerradius + thickness) * sin(i * lowerphi / lowerpoints) ] ]),
    // Lower Left Inner arc
    lli = (solid) ? [] : [ for (i = [0:1:lowerpoints-1]) [ -w0 -
lowerradius * cos(i * lowerphi / lowerpoints), h0 + lowerradius * sin(i *
lowerphi / lowerpoints) ] ],
    // Upper Left Inner arc
    uli = (solid) ? [] : [ for (i = [upperpoints-1:-1:0]) [ -w6 +
(upperradius + thickness) * cos(innertheta + i * innerphi / upperpoints),
h6 - (upperradius + thickness) * sin(innertheta + i * innerphi /
upperpoints) ] ],
    // Upper Right Inner arc
    uri = (solid) ? [] : [ for (i = [0:1:upperpoints-1]) [ w6 -
(upperradius + thickness) * cos(innertheta + i * innerphi / upperpoints),
h6 - (upperradius + thickness) * sin(innertheta + i * innerphi /
upperpoints) ] ],
    // Lower Right Inner arc
    lri = (solid) ? [] : [ for (i = [lowerpoints-1:-1:0]) [ w0  +
lowerradius * cos(i * lowerphi / lowerpoints), h0 + lowerradius * sin(i *
lowerphi / lowerpoints) ] ]
)
    concat(lro, uro, cap, ulo, llo, lli, uli, uri, lri);

//this is used to make half of an ogee arch
// it is a "pathway" on which the real arches are
// centered on in arch2()
function ogeepolyPath(width, thickness, lowerradius, upperradius, points,
capwidth = 0.0, apexangle = 45.0) =
    let(
    // Angle extended by the arcs
    lowerphi = 180.0 - acos((0.5*(width - capwidth) - lowerradius -
thickness - upperradius * cos(0.5*apexangle)) / (lowerradius + thickness +
upperradius)),
    upperphi = lowerphi - 0.5 * apexangle,
    apexhalf = 0.5 * apexangle,

    // Number of points per lower arc
    lowerpoints = round((capwidth > 0.0) ?
lowerphi*(points-4)/(8*lowerphi-4*apexangle) :
lowerphi*(points-2)/(8*lowerphi-4*apexangle)),

    // Actual number of points in the upper arc
    uppertemp = round((capwidth > 0.0) ?
(lowerphi-apexangle)*(points-4)/(8*lowerphi-4*apexangle) :
(lowerphi-apexangle)*(points-2)/(8*lowerphi-4*apexangle)),
    targetpoints = (apexangle > 0.0) ? points - 5 : points - 2,
    upperpoints = (targetpoints - 4*(lowerpoints + uppertemp) >= -1) ?
uppertemp + 1 : uppertemp,

    // Key horizontal coordinates. Note: w0=x1, w6=x2
    w0 = 0.5*width - thickness - lowerradius,
    w1 = 0.5*capwidth,
    w2 = w0 + lowerradius * cos(lowerphi),
    w3 = w0 + (lowerradius + thickness) * cos(lowerphi),
    w4 = w0 + lowerradius,
    w5 = w0 + lowerradius + thickness,
    w6 = w0 + (lowerradius + thickness + upperradius) * cos(lowerphi),

    // Key vertical coordinates. Note: y1=h0=0, y2=h6
    h6 = (lowerradius + thickness + upperradius) * sin(lowerphi),
    h5 = h6 - upperradius * sin(apexhalf),
    h4 = h5 - 0.5*capwidth * tan(apexhalf),
    h3 = h6 - (upperradius + thickness) * sqrt(1.0 - w6*w6 / ((upperradius
+ thickness) * (upperradius + thickness))),
    h2 = (lowerradius + thickness) * sin(lowerphi),
    h1 = lowerradius * sin(lowerphi),
    h0 = 0.0,
    height = (apexangle > 0.0) ? (h5 + 0.5*capwidth / tan(apexhalf)) : h5,

    // We use the same number of points for the upper inner arc,
    // even though it is a bit smaller angle.
    innertheta = acos(w6 / (upperradius + thickness)),
    innerphi = lowerphi - innertheta,

    // Lower Right Outer arc
    lro = [ for (i = [0:1:lowerpoints-1]) [ w0 + (lowerradius + thickness)
* cos(i * lowerphi / lowerpoints), 0, h0 + (lowerradius + thickness) *
sin(i * lowerphi / lowerpoints) ] ],
    // Upper Right Outer arc
    uro = [ for (i = [upperpoints-1:-1:0]) [ w6 - upperradius *
cos(apexhalf + i * upperphi / upperpoints), 0, h6 - upperradius *
sin(apexhalf + i * upperphi / upperpoints) ] ],
    // Cap
    cap = (capwidth > 0.0) ? [ [ w1, 0, h5 ], [ 0, 0, height ] ] : [],
    // Upper Left Outer arc
    ulo = [ for (i = [0:1:upperpoints-1]) [ -w6 + upperradius *
cos(apexhalf + i * upperphi / upperpoints), 0, h6 - upperradius *
sin(apexhalf + i * upperphi / upperpoints) ] ],
    // Lower Left Outer arc
    llo = [ for (i = [lowerpoints:-1:0]) [ -w0 - (lowerradius + thickness)
* cos(i * lowerphi / lowerpoints), 0, h0 + (lowerradius + thickness) *
sin(i * lowerphi / lowerpoints) ] ]
)
    concat(lro, uro, cap, ulo, llo);

function stupid(i) = (i==1) ? ogeepoly(65,3,60,60,100,1,10) : romanArch(70,
3, 62, 200);



//makes an arch interpolation between a wider roman
//arch and ogee arch, uses sin() so the transition
//is curved
function waveOut(i,verticies,thickness,solid) = abs(sin(i)) *
romanArchNc(65, thickness, 70, echoit5("verticies",verticies),solid) + (1 -
abs(sin(i))) *
ogeepoly(65,thickness,60,60,echoit5("verticies",verticies),solid,0.0,0);//was
1,10 at end

//makes an arch interpolation between a narrow roman
//arch and ogee arch, uses sin() so the transition
//is curved
function waveIn(i,verticies,thickness,solid) = abs(sin(i)) *
romanArchNc(95, thickness, 40, echoit5("verticies",verticies),solid) + (1 -
abs(sin(i))) *
ogeepoly(65,thickness,60,60,echoit5("verticies",verticies),solid,0.0,0);//was
1,10 at end


//alternates wave in and wave out...
function wave(i,verticies=200,thickness,solid) = (i < 31) ?
waveOut(echoit4("<31",(i*6)),echoit5("verticies",verticies),thickness,solid)
:
                   (i < 61) ?
waveIn(echoit4("<61",(180-((i-30)*6))),verticies,thickness,solid) :
                   (i < 91) ?
waveOut(echoit4("<91",(i-60)*6),verticies,thickness,solid) :

 waveIn(echoit4("last",180-((i-90)*6)),verticies,thickness,solid);

function archselect(i,verticies=160,thickness,solid) = (i == 0) ?
romanArchNc(95, thickness, 40, verticies,solid) + (1 - abs(sin(i))) *
ogeepoly(65,thickness,60,60,verticies,solid,0.0,0)
:ogeepoly(65,thickness,60,60,verticies,solid,0.0,0);



//this makes the shape I want a transtion between
//roman and ogee arches on a pathway that is half
//an ogee arch
//I'd use 6 of these to make the hallways I want
//16 verticies simplifies things
module arches2(verticies=192, thickness = 8, solid = false) {

    yxzpath = ogeepolyPath(650, 3, 600, 600, 420, 1, 10);

    //echo(yxzpath);
    //echo("blah");
    //echo(yxzpath[10]);

    skin([
           for (i=[1 : 1 :  105])
transform(translation(yxzpath[i]) * rotation([0,0,0]),
wave(i,verticies,thickness,solid))
]);
}

//this does one fully roman arch and one fully
// ogee arch (or it should) for testing
module arches5(verticies=160, thickness = 4, solid = false) {
    skin([
        for (i=[0 : 1 : 1])
          transform(translation([0,0,(i*90)]) * rotation([0,0,0]),
archselect(i, verticies, thickness, solid))
    ]);
}

//for testing, does the arches transitioning
//but on a linear path
module arches6(verticies=192, thickness = 8, solid = false) {
    skin([
           for (i=[1 : 1 : 105])
transform(translation([0,0,i]) * rotation([0,0,0]),
wave(i,verticies,thickness,solid))
]);
}

//for testing
//do fewer interpolations to find where it goes wrong
module arches7(verticies=192, thickness = 8, solid = false) {

    yxzpath = ogeepolyPath(650, 3, 600, 600, 420, 1, 10);

    //echo(yxzpath);
    //echo("blah");
    //echo(yxzpath[10]);

    skin([
           for (i=[90 : 1 :  105])
transform(translation(yxzpath[i]) * rotation([0,0,0]),
wave(i,verticies,thickness,solid))
]);
}

//for testing
/*uses wave but on a linear path to make it easier to view
allows you to select how many slices to use
*/
module arches8(verticies=192, thickness = 8, solid = false, start = 0, end
= 105) {

    skin([
           for (i=[start : 1 :  end])
transform(translation([0,0,(i-start)]) * rotation([0,0,0]),
wave(i,verticies,thickness,solid))
]);
}

//for testing
//another linear path with wave in and out
module arches4(verticies=190) {


    skin([
           for (i=[1 : 1 : 90])
transform(translation([0,0,i*0.5]) * rotation([0,0,0]), wave(i,verticies))
]);
}

//for testing
//another linear path this one with just wave out
module arches3(start,end,verticies=190) {

    skin([
           for (i=[start : 1 : end])
transform(translation([0,0,i*0.5]) * rotation([0,0,0]),
waveOut(i,verticies))
]);
}


//polygon test of roman arch or ogee poly
//they both look ok with the same number of verticies
//(10,14,18,22,26.... 6+4(n))
//but the verticies must not be in the same order
//or number because if you do "arches3(0,90,22);"
//there is a twist...
//pts = romanArch(65, 3, 70, 22);
//pts = ogeepoly(65,3,60,60,22,0.0,0);
//polygon(pts);


//pts = romanArchNc(65, 3, 70, 24);
//polygon(pts);

//90 goes from ogee to roman completely
//arches3(0,90,24);

//half the passageway
//arches2(64,4);

//arches5(16,2);


//polygon(romanArchNc(65, 3, 70, 16));

//polygon(ogeepoly(65,3,60,60,16,0,10));

//this test case works
/*
union () {
arches5(48);

translate([0,0,90]) {cube([80,80,80], true); }
}
*/

/*this test case seems to work:
does give the disturbing warning

"DEPRECATED: Using ranges of the form [begin:end] with begin value greater
than the end value is deprecated.
Rendering Polygon Mesh using CGAL...
ERROR: Unable to convert point at index 1 to a vec3 of numbers
PolySet has nonplanar faces. Attempting alternate construction"
*/
/*
difference() {
    arches5(24);
    mirror([1,0,1]) {
       arches5(24,true); //subtract a SOLID version
    }
}
mirror([1,0,1]) {
       arches5(24);
    }
*/

//works
//arches6(8,2);

//works
/*
union () {
arches6(8,2);

translate([0,0,90]) {cube([80,80,80], true); }
}
*/

//this test case says non-planiar facets, but renders
//24 verticies try lower
/*
difference() {
    arches6(24,2);
    mirror([1,0,1]) {
       arches6(24,2,true); //subtract a SOLID version
    }
}
mirror([1,0,1]) {
       arches6(24,2);
    }
 */

//translate([0,0,750]) {cube([80,80,80], true); }

//a test of unioning half the passageway with a cube
//gets errors

//works
//arches2(24,2);

//does not work
/* "
Rendering Polygon Mesh using CGAL...
ERROR: CGAL error in CGAL_Nef_polyhedron3(): CGAL ERROR: assertion
violation! Expr: e_below != SHalfedge_handle() File:
/opt/mxe/usr/x86_64-w64-mingw32.static.posix/include/CGAL/Nef_3/SNC_FM_decorator.h
Line: 426
Geometries in cache: 17
Geometry cache size in bytes: 2167192
CGAL Polyhedrons in cache: 4
CGAL cache size in bytes: 11344
Total rendering time: 0 hours, 0 minutes, 4 seconds
   Top level object is a 3D object:
   Simple:        yes
   Vertices:        8
   Halfedges:      24
   Edges:          12
   Halffacets:     12
   Facets:          6
   Volumes:         2
Rendering finished.
"
*/
/*
union () {
arches2(16,4);

translate([0,0,750]) {cube([80,80,80], true); }
}
*/

//arches7(16);

//arches8(16,8,false,1,3);

//this works odd
/*
union() {
arches8(16,8,false,0,105);
    translate([0,20,00]) {cube([50,50,50], true); }
}
*/

//blows up:
/*
union() {
arches7(16,4);
    //for the first 15 translate 300, 0, 10 works nice
    translate([0,0,600]) {cube([40,40,40], true); }
}
*/
//arches2(16,4);
//translate([0,0,600]) {cube([80,80,80], true); }

//arches2(16,2);

//does not work
/*
Rendering Polygon Mesh using CGAL...
ERROR: CGAL error in CGAL_Nef_polyhedron3(): CGAL ERROR: assertion
violation! Expr: e_below != SHalfedge_handle() File:
/opt/mxe/usr/x86_64-w64-mingw32.static.posix/include/CGAL/Nef_3/SNC_FM_decorator.h
Line: 426
ERROR: CGAL error in CGAL_Nef_polyhedron3(): CGAL ERROR: assertion
violation! Expr: e_below != SHalfedge_handle() File:
/opt/mxe/usr/x86_64-w64-mingw32.static.posix/include/CGAL/Nef_3/SNC_FM_decorator.h
Line: 426
Geometries in cache: 21
Geometry cache size in bytes: 3129336
CGAL Polyhedrons in cache: 5
CGAL cache size in bytes: 11344
Total rendering time: 0 hours, 0 minutes, 8 seconds
Rendering finished.
*/
/*
    arches2(16,2);
mirror([0,0,1]) {
       arches2(16,2);
    }
*/

//does not work either
/*
difference() {
    arches2(16,4);
    mirror([0,0,1]) {
       arches2(16,4,true); //subtract a SOLID version
    }
}
*/

//arches2(16);

//making a full passageway, gets errors
/*
gets "
DEPRECATED: Using ranges of the form [begin:end] with begin value greater
than the end value is deprecated. "
partway through the verticies calculation

then at the end it has "

Rendering Polygon Mesh using CGAL...
Geometries in cache: 21
Geometry cache size in bytes: 3129336
CGAL Polyhedrons in cache: 5
CGAL cache size in bytes: 11344
Total rendering time: 0 hours, 0 minutes, 6 second
"
*/
/*
difference() {
    arches2(16);
    mirror([0,0,1]) {
       arches2(16,true); //subtract a SOLID version
    }
}
mirror([0,0,1]) {
       arches2(16);
    }
*/

//polygon(ogeepoly(65,3,60,60,200,0.0,0));

/*
polygon(ogeepoly(65,3,60,60,200,1,10));
polygon(romanArch(65, 3, 70, 200));
polygon(romanArch(95, 3, 40, 200));
*/

On Thu, Jul 4, 2019 at 11:44 PM MichaelAtOz <oz.at.michael at gmail.com> wrote:

> DanS wrote
> > MichaelAtOz:
> > I can show the code to people but it isn't pretty and rightfully people
> > have said in the past "I don't want to debug that"
>
> I'll have a first look, no guarantees tho.
>
>
>
> -----
> Admin - email* me if you need anything, or if I've done something stupid...
>
> * click on my MichaelAtOz label, there is a link to email me.
>
> Unless specifically shown otherwise above, my contribution is in the
> Public Domain; to the extent possible under law, I have waived all
> copyright and related or neighbouring rights to this work. Obviously
> inclusion of works of previous authors is not included in the above.
>
> The TPP is no simple “trade agreement.”   Fight it!
> http://www.ourfairdeal.org/   time is running out!
> --
> Sent from: http://forum.openscad.org/
>
> _______________________________________________
> OpenSCAD mailing list
> Discuss at lists.openscad.org
> http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.openscad.org/pipermail/discuss_lists.openscad.org/attachments/20190705/1bd1ea96/attachment.html>


More information about the Discuss mailing list