JustinSDK / dotSCAD

Reduce the burden of mathematics when playing OpenSCAD
https://openhome.cc/zh-tw/openscad/
GNU Lesser General Public License v3.0
784 stars 107 forks source link

Code sample for a fully fluent turtle #10

Closed dpellegr closed 4 years ago

dpellegr commented 4 years ago

Hi, please consider the code below. It allows for constructing a polyline directly from a sequence of turtle commands. When constructing the list of 3D points, I automatically drop the turn commands which would produce duplicates.

It would also be interesting to add some commands to produce arcs in a straightforward way, but that is for another time.

use <turtle/t3d.scad>;
use <hull_polyline3d.scad>;

leng = 10;
angle = 120;
thickness = 1;

function drop_front(lst) =
  [ for (i=[1 : 1 : len(lst)-1]) lst[i] ];

function in(sub, s, i=0, j=0) = ( i==len(sub) ) 
  ? true
  : ( j==len(s) )
    ? false
    : in(sub, s, ( sub[i]==s[j] ) ? i+1 : 0, j+1);

function t3dc(pt, cmds, pts=[]) = (len(cmds) == 0) 
  ? concat(pts, [t3d(pt, "point")])
  : t3dc( t3d(pt, [cmds[0]]),
          drop_front(cmds), 
          concat( pts, in("turn", cmds[0][0]) ? [] : [t3d(pt, "point")] )
        );

pol = t3dc( t3d(point = [0, 0, 0]),
  [
    ["xforward", leng],
    ["zturn", angle],
    ["xforward", leng],
    ["zturn", angle],
    ["xforward", leng]
  ]
);   

hull_polyline3d(pol, thickness);
JustinSDK commented 4 years ago

Sound like what L-system does? https://github.com/JustinSDK/dotSCAD/blob/master/examples/turtle/lsystem2_collection.scad https://github.com/JustinSDK/dotSCAD/blob/master/examples/turtle/lsystem3_collection.scad

Some screenshots.

dpellegr commented 4 years ago

Maybe, I am not sure. I just need to draw this irregular pipe, nothing like those fancy fractals.

pipe

I needed to avoid having to explicitly define every point as in the turtle examples. If L-system covers this, maybe an example would be useful.

JustinSDK commented 4 years ago

L-system functions will be published and documented in the next release. Your requirement seems like using a turtle to imitate freehand drawing. As you said, it might be useful in some situations. I'll consider implementing it some other day.

dpellegr commented 4 years ago

Exactly! The code above works properly, although having access to the interior of the library it can probably be implemented in a more efficient way. No need to rush. Thank you for the great library!

On Fri, May 8, 2020, 01:37 Justin Lin notifications@github.com wrote:

L-system functions will be published and documented in the next release. Your requirement seems like using a turtle to imitate freehand drawing. As you said, it might be useful in some situations. I'll consider implementing it some other day.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/JustinSDK/dotSCAD/issues/10#issuecomment-625549936, or unsubscribe https://github.com/notifications/unsubscribe-auth/AB3UFLTGBALIXNAOSAMCHTDRQNA5DANCNFSM4M3FJHKQ .

dpellegr commented 4 years ago

Ah, FYI, this is the code that I used to make the arc:

function arc(r, a, dir="z", $fn=$fn) =
  let(
    t=a/$fn, l=2*r*sin(t/2),
    lst = [for (i=[1:1:$fn]) [[str(dir,"turn"), (i==1)?t/2:t], ["xforward", l]] ] 
  )
  concat(lst, [lst[0][0]]);

It produces an ordered list of $fn xforward commands and $fn+1 turn commands to create an arc of radius r and total bending angle a around the dir axis. Note how the first and last turn rotate by half of the angle step.

JustinSDK commented 4 years ago

2D footprints2 function:

footprints2

use <turtle/footprints2.scad>;
use <hull_polyline2d.scad>;

function arc_cmds(radius, angle, steps) = 
    let(
        fa = angle / steps,
        ta = fa / 2,
        leng = sin(ta) * radius * 2
    )
    concat(
        [["turn", ta]],
        [
            for(i = [0:steps - 2])
            each [["forward", leng], ["turn", fa]]
        ],
        [["forward", leng], ["turn", ta]]
    );

poly = footprints2(
    concat(
        [
            ["forward", 10],
            ["turn", 90],
            ["forward", 10] 
        ], 
        arc_cmds(5, 180, 12),
        [
            ["turn", -90],
            ["forward", 10],
            ["turn", 90],
            ["forward", 10],
            ["turn", 90],
            ["forward", 10]
        ]
    )
);

hull_polyline2d(poly, width = 1);
JustinSDK commented 4 years ago

3D footprints3 function: footprints3

use <hull_polyline3d.scad>;
use <turtle/footprints3.scad>;

function xy_arc_cmds(radius, angle, steps) = 
    let(
        fa = angle / steps,
        ta = fa / 2,
        leng = sin(ta) * radius * 2
    )
    concat(
        [["turn", ta]],
        [
            for(i = [0:steps - 2])
            each [["forward", leng], ["turn", fa]]
        ],
        [["forward", leng], ["turn", ta]]
    );

// cmds: "forward" ("xu_move"), "turn" ("zu_turn"), "roll" (negative "xu_turn"), "pitch" (negative "yu_turn")    
poly = footprints3(
    concat(
        [
            ["forward", 10],
            ["turn", 90],
            ["forward", 10] 
        ], 
        xy_arc_cmds(5, 180, 12),
        [
            ["pitch", 90],
            ["forward", 10],
            ["roll", 90]
        ],
        xy_arc_cmds(5, 180, 12),
        [
            ["forward", 10]
        ]
    )
);

hull_polyline3d(poly, thickness = 1);
dpellegr commented 4 years ago

Another improvement: you can avoid the extra brackets around commands when mixing them with arcs. For that I use the repack function. It fully flattens a nested list, then it groups back the elements in sub-lists of the desired length (2 in this case). For instance:

let( L = [1,2,[3],[4,[5,6]]] )
fullflat( L ) => [1,2,3,4,5,6]
repack( L, 2) => [[1,2],[3,4],[5,6]]
repack( L, 3) => [[1,2,3],[4,5,6]]

so you can do:

poly = footprints3(
    repack([
        ["forward", 10],
        ["turn", 90],
        ["forward", 10], 
        xy_arc_cmds(5, 180, 12),
        ["pitch", 90],
        ["forward", 10],
        ["roll", 90],
        xy_arc_cmds(5, 180, 12),
        ["forward", 10]
    ])
);

You can also move the repack call inside the footprint3 function so that the user does not need to call it himself.

Here is an implementation of repack:

function flatten(l) = [ for (a = l) if (is_string(a)) a else for (b = a) b ];
function fullflat(l) = l == flatten(l) ? l : fullflat(flatten(l));
function repack(l, n=2) = let(lf=fullflat(l)) [ for (i=[0:n:len(lf)-1]) [for (j=[0:1:n-1]) lf[i+j] ]];
JustinSDK commented 4 years ago

repack is another story. I think it's better to be a utility function independent of footprints functions.

dpellegr commented 4 years ago

Yes of course. It just comes in very handy in here :)

On Sat, May 9, 2020, 12:36 Justin Lin notifications@github.com wrote:

repack is another story. I think it's better to be a utility function independent of footprints functions.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/JustinSDK/dotSCAD/issues/10#issuecomment-626145701, or unsubscribe https://github.com/notifications/unsubscribe-auth/AB3UFLV5VNFHNUMC2UHVYE3RQUWZTANCNFSM4M3FJHKQ .