Turtle programming in OpenPythonSCAD

There have been a few instances of this being used for modeling, but the default library expects a screen to be available, which doesn’t work in OpenPythonSCAD, so it will be necessary to re-implement.

We start with:

#!/usr/bin/env python

from openscad import *

and then need to consider the class and necessary variables…

Naturally, this will need to be a class, and variables will be needed for:

  • X/Y/Z position
  • current pointing direction for XY rotation
  • current inclination/declination of movement to allow for 3D
  • current/default object shape

which results in:

class threeDmodelturtle:

    def __init__(self,
                 xpos = 0,
                 ypos = 0,
                 zpos = 0,
                 XYdir = 0,
                 Zdir = 0,
                 size = 5,
                 turtle = sphere
                 ):
        self.xpos = xpos
        self.ypos = ypos
        self.zpos = zpos
        self.XYdir = XYdir
        self.Zdir = Zdir
        self.size = size
        self.turtle = turtle
        self.model = sphere(0.000000001)

Next, we need some commands…

But the need for the commands argues for:

import math

The directional commands are obvious (but had to be fixed/corrected and in addition, code added to adjust for exceeding 360):

    def left(self, angle):
        self.XYdir = self.XYdir + angle
        if self.XYdir > 360 :
            self.XYdir = self.XYdir - 360
    
    def right(self, angle):
        self.XYdir = self.XYdir + (360 - angle)
        if self.XYdir < 360 :
            self.XYdir = self.XYdir + 360

    def incline(self, angle):
        self.Zdir = self.Zdir - angle
    
    def decline(self, angle):
        self.Zdir = self.Zdir + angle

but forward(steps) demands trigonometry in three dimensions…

Probably, to prevent divide by zero errors, we should check for the cardinal directions… or, rather than do trigonometry, it should work just to move the turtle, then rotate it per the XYdir and Zdir angles…

Roughing things out in BlockSCAD we get:

so it should work to draw the steps as if all angles were zero, then rotate, then apply the distance transformation — which gets us back to trigonometry, unless the translated object can be queried for its position?!?

Starting out by handling the cardinal directions and then the interstices and their trigonometric calculations become obvious:

    def forward(self, steps):
        if self.turtle == sphere :
            tortoise = sphere(self.size/2)
        pastturtle = tortoise.translate([self.xpos, self.ypos, self.zpos])
        xpast = self.xpos
        ypast = self.ypos
        zpast = self.zpos
        dot = sphere(self.size/4)
        if self.XYdir == 0 :
            self.xpos = self.xpos + steps
        if (self.XYdir > 0 and self.XYdir <90) :
            self.xpos = self.xpos + math.cos(math.radians(self.XYdir)) * steps
            self.ypos = self.ypos + math.sin(math.radians(self.XYdir)) * steps
        if self.XYdir == 90 : 
            self.ypos = self.ypos + steps
        if (self.XYdir > 90 and self.XYdir <180) :
            self.xpos = self.xpos + math.sin(math.radians(90 - self.XYdir)) * steps
            self.ypos = self.ypos + math.cos(math.radians(90 - self.XYdir)) * steps
        if self.XYdir == 180 :
            self.xpos = self.xpos - steps
        if (self.XYdir > 180 and self.XYdir <270) :
            self.xpos = self.xpos - math.cos(math.radians(180 - self.XYdir)) * steps
            self.ypos = self.ypos + math.sin(math.radians(180 - self.XYdir)) * steps
        if self.XYdir == 270 : 
            self.ypos = self.ypos - steps
        if (self.XYdir > 270 and self.XYdir <360) :
            self.xpos = self.xpos - math.sin(math.radians(270 - self.XYdir)) * steps
            self.ypos = self.ypos - math.cos(math.radians(270 - self.XYdir)) * steps
        futureturtle = tortoise.translate([self.xpos, self.ypos, self.zpos])  
        path = hull(dot.translate([xpast, ypast, zpast]), dot.translate([self.xpos, self.ypos, self.zpos]))
        self.model = self.model.union(pastturtle, path, futureturtle)

    def showmodel(self):
        show(self.model)

in lieu of doing the 3D trigonometry, we just add commands for climb/descend:

    def climb(self,steps):
        if self.turtle == sphere :
            tortoise = sphere(self.size/2)
        zpast = self.zpos
        pastturtle = tortoise.translate([self.xpos, self.ypos, self.zpos])
        dot = sphere(self.size/4)
        self.zpos = self.zpos + steps
        futureturtle = tortoise.translate([self.xpos, self.ypos, self.zpos])  
        path = hull(dot.translate([self.xpos, self.ypos, zpast]), dot.translate([self.xpos, self.ypos, self.zpos]))
        self.model = self.model.union(pastturtle, path, futureturtle)

    def descend(self,steps):
        if self.turtle == sphere :
            tortoise = sphere(self.size/2)
        zpast = self.zpos
        pastturtle = tortoise.translate([self.xpos, self.ypos, self.zpos])
        dot = sphere(self.size/4)
        self.zpos = self.zpos - steps
        futureturtle = tortoise.translate([self.xpos, self.ypos, self.zpos])  
        path = hull(dot.translate([self.xpos, self.ypos, zpast]), dot.translate([self.xpos, self.ypos, self.zpos]))
        self.model = self.model.union(pastturtle, path, futureturtle)

which lets us move in three dimensions at right angles:

Files pushed up to:

Since this was first pushed up, the trigonometry for angles in XY has been added.

1 Like

That’s pretty cool. I think I would have started with just moving in 2D like the typical Turtle programming movement then added Z. But I get you’ve had your head in 3 dimensions in this space for awhile.
Keep up the good work!

1 Like

I bailed on doing more than up/down in Z — the math is at least moderately complex — there’s a bit of discussion about vector math on the subreddit for openpythonscad — maybe that will yield a more useful approach.

1 Like

Turns out this was much simpler than I thought — since the XY position is calculated, the Z is just one additional trigonometric calculation:

z = zpos + steps * Sin(zdir)

Note that the new OpenPythonSCAD adds trigonometric functions:

hence the “Sin()” (as opposed to math.sin(math.radians(zdir)) or something along those lines.

That this will result in the proper position is easily visualized by drawing a cone defined by the length and height and angle with the result that the radius coincides with the XY position.

1 Like

New version up at:

and the code is sufficiently obvious I won’t belabour it.

The next thing to look into is if this could be paired w/

so that OpenPythonSCAD could function as a direct and immediate/interactive 3D previewer.

1 Like