Author Topic: Turtle graphics  (Read 227 times)

raymw

  • Newbie
  • *
  • Posts: 39
    • View Profile
Turtle graphics
« on: 2022-02-12, 19:33:31 »
I was around when Logo and turtle graphics were first popular, but more or less ignored them. Recently I saw a Utube video (numberphile) where it was explained hor turtle graphics could produce SierpiƄski Triangles, and I thought that maybe using AngelCAD could possibly do something similar but in 3d, for 3d printing. I've not used AngelCAD for sometime, and it is a struggle getting back into the flow, so to speak. I ran some simple tests, and decided that I would not be able to plot a meaningful triangle using my PC, in any reasonable amount of time.

However, with significant help from Carsten, I managed to produce something based loosely on turtle graphics, and I've put the code below. It sort of can print various patterned tubes, with or without a base. ( a vase, if you like), but if you water tight, you will need to adjust the various values. However, for the variables I've set, it fails after about 1250 iterations (i.e. in my code 'range' set to more than 1250) , however that may be a limitation in my particular PC. I started by generating a tube, then decided to add a base, and give a few other options, which can readily alter the appearance of the whole object. If the angle is not a factor of 360, then you will get a sort of spiral shape, as in the values I've shown. There are still a number of features that could be added - I've just added a print out of the approximate height, but with a bit of effort that could be more precisely calculated, and that value used to calculate 'range' .

Code: [Select]
double rfd = PI/180;  //rads from degrees

void settings (thing &out it)
{
    it.le = 20;  // length of rod
    it.w = 2;    // width of rod
    it.h = 2;    // height of rod
    it.rot = false; // rotate rods by 45 degree, to remove horizontal overhangs
    it.range = 1250;   // number of rods
    it.a = 50;    // angle between rods
    it.b = it.h/(360/it.a);  // vertical angle
    it.base = true; // false if base not needed
    it.round = false; // set to true if wanted rounded adges instead of square (diamete =w)
    it.blob = 0;  // diameter of blob at corners (0 to ignore)
   
    ////////////////////////////////////
    it.bang=0;
    it.cang=0;
return;

 
class thing
{
   double le, w, h, a, b, blob, bang, cang = 0;   
   int range = 0;
   bool base, round, rot = true;
}
   
solid@ part(thing it)
{       
           solid@ stroke = cuboid(it.le,it.w,it.h,false);
           
           if (it.rot == true)
           {
                 @stroke = rotate_x(rad:rfd*45)* stroke; // angle to make printable
           }
    if (it.round==true)
    {       
           @stroke  = rotate_y(rad:PI/2)*cylinder(it.le,it.w/2);               
    }       
return stroke;
}     
   
solid@ ball(double blob)

return sphere(r:blob/2);
}
     
   
solid@ draw( thing it, pos3d@ now, pos3d@ &out last)
{

   solid@ ans=
      translate(now.x(),now.y(),now.z())*
      rotate_z(rad:rfd*it.cang)*
      part(it);
   // calc 'last' for next 'now'
        @last = now + pos3d(cos(rfd*it.cang)*it.le,sin(rfd*it.cang)*it.le,it.bang );
   // put sphere at corner if needed
       if (it.blob >0 )
       {           
          @ans =ans +  translate(last.x(),last.y(),last.z())*ball(it.blob);
       }   
return ans;
}
 
void main()
{
    thing it;
    settings(it); //get values
    pos3d@ now = {0,0,0};
    pos3d@ last= {it.le,0,0};
    array <pos2d@> points;
    solid@ obj;
 
       @obj = part(it);  // this is to become the set of lines
     int count=0;   
       while( count < it.range )
       {       
           it.bang=0;  // flatten bottom ( b, vertical angle =0) and make polygon
              if (count <= 360/it.a)
              {
               points.push_back(pos2d(last.x(),last.y()));
              }               
                 if (count>((360/it.a)))   
                 {
                 it.bang=it.bang+it.b;  // now add vertical angle
                 }
                    if (count>=it.range-(360/it.a)-1)  // to give flat top   
                     {
                     it.bang=0;  // vertical angle 0 for last few
                     }
           it.cang=it.cang+it.a;
              @now = last;
              @obj = obj+draw(it,now,last);
        count++;
      }       
           if (it.round==true)
           {
           @obj= translate(0,0,it.w/2)*obj;
           }
               if (it.base==true)  // make solid base
              {
               polygon@ solid_base = polygon (points);
               @obj=obj + linear_extrude(solid_base,height:it.h);
              }
         // assume no overlap
              cout<< "aproximate height " << (it.range /(360/it.a))*it.h << endl;
      obj.write_xcsg(GetInputFullPath(),secant_tolerance:-1.0);
}

I've put the values you can change at the top of the program listing, in the settings function, but of course, you can do what you like with the rest of it. If you want to play with it, then set range to 590, to get a quick fstl, which will give you an idea of the patterns you can get.

raymw

  • Newbie
  • *
  • Posts: 39
    • View Profile
Re: Turtle graphics
« Reply #1 on: 2022-03-07, 18:26:37 »
I've added a bit more complexity to the original code. It can produce interesting stl files 3d prints, or can, if the values are not correct, produce equally interesting errors.
With careful choice of parameters you can create a multi spiral sided pot, with a screw-on lid, for example. I have just added a facility to reverse the direction of the spiral at various heights. The values I have in the example code works, produces a water tight pot, other values may throw errors, at the moment it is almost trial and error. to choose workable values. I will add some images, if possible.
Code: [Select]
// turtle tube

double rfd = PI/180;  //rads from degrees

void settings (thing &out it)
{
    it.le = 20;  // length of rod 
    it.w = 1.5;    // width of rod
    it.h = 1.5;    // height of rod
    it.rot = false; // rotate rectangular rods by 45 degree, to remove horizontal overhangs
    it.range = 200;   // number of rods
    it.a =61;    // angle between rods
   // it.b = 0.7*it.h/(360/it.a);  // vertical angle
    it.b = 0.5*it.h/(360/it.a); // smaller vertical angle to allow for reversal of direction.
    it.base = true;// false if base not needed
    it.flat = false ; // true for flat base
    it.round = true; // set to true if wanted rounded adges instead of square (diamete =w)
    it.blob = 2;  // diameter of blob at corners (0 to ignore)
    it.rev = false;  // set true if you want spiral (if any) reversed
    it.cut = 0 ; // if a positive value, it cuts off the top of 'cylinder', at cut.height
    it.dir =24 ;  // if a positive value, it reverses direction every dir number of rods
    it.ar = 59 ;  // 'reverse direction' angle
    ////////////////////////////////////
   
return;

 
class thing
{
   double le, w, h, a, b, blob, bang, cang, cut, dir, dirc, ar = 0;   
   int range = 0;
   bool base, round, rot, flat, rev = true;
}
   
solid@ part(thing it)
{       
           solid@ stroke = translate(it.le/2,0,0)*cuboid(it.le,it.w,it.h,true);
           
           if (it.rot == true)
           {
                 @stroke = rotate_x(rad:rfd*45)* stroke; // angle to make 3d printable - 45 deg overhangs
           }
    if (it.round==true)
    {       
           @stroke  = rotate_y (rad:PI/2)*cylinder(it.le,it.w/2);               
    }       
return stroke;
}     
   
solid@ ball(double blob)

return sphere(r:blob/2);
}
     
   
solid@ draw( thing it, pos3d@ now, pos3d@ &out last)
{

   solid@ ans=
      translate(now.x(),now.y(),now.z())*
      rotate_z(rad:rfd*it.cang)*
      part(it);
   // calc 'last' for next 'now'
        @last = now + pos3d(cos(rfd*it.cang)*it.le,sin(rfd*it.cang)*it.le,it.bang );
   // put sphere at corner if needed
       if (it.blob >0 )
       {           
          @ans =ans +  translate(last.x(),last.y(),last.z())*ball(it.blob);
       }   
return ans;
}
 
void main()
{
    thing it;
    settings(it); //get values
    pos3d@ now = {0,0,0};
    pos3d@ last= {it.le,0,0};
    array <pos2d@> points;
    solid@ obj;
    double tempa = it.a;
 
  if (it.base == true)
  {
     it.flat=true;
  }   
       @obj = part(it);  // this is to become the set of lines
     int count=0;   
       while( count < it.range )
       {       
           it.bang=0;  // flatten bottom ( b, vertical angle =0) and make polygon
             
             if (it.flat==true)
             {
               if (count <= 360/it.a)
               {                   
                       points.push_back(pos2d(last.x(),last.y()));
               }               
                 if (count>((360/it.a)))   
                 {
                       it.bang=it.bang+it.b;  // now add vertical angle
                 }
                    if (count>=it.range-(360/it.a)-1)  // to give flat top   
                    {
                          it.bang=0;  // vertical angle 0 for last few
                    }
            }
           
            if (it.flat == false)
            {
               it.bang=it.bang+it.b;
            }
              it.cang=it.cang+it.a;
              @now = last;
              @obj = obj+draw(it,now,last);
              // change direction
              if (it.dir >=1)
              {
                  it.dirc=it.dirc+1;           
                    if (it.dir== it.dirc)
                    {
                       tempa=it.a;
                       it.a=it.ar;
                       it.ar=tempa;
                       it.dirc=0;
                    }
              }
             
        count++;
      }       
           if (it.rot==true)
           {
            // get rotaton position of base
             @obj= translate(0,0,(sqrt((it.w *it.w) + (it.h*it.h))*0.5)-it.w/2)*obj;
           
            }
           @obj= translate(0,0,it.w/2)*obj;
       
               if (it.base==true)  // make solid base
              {
               polygon@ solid_base = polygon (points);
               @obj=obj + linear_extrude(solid_base,height:it.h);
                if (it.rev == true)
                {
                  @obj= mirror(1,0,0)*obj;
                }
                   if (it.cut>0)  // cut off top using 500 cube centred
                   {
                     @obj = obj-translate(0,0,it.cut+250)*cube(500,true);
                   }
     }
       
  // assume no overlap
              cout<< "aproximate height " << (it.range /(360/it.a))*it.h << endl;
             obj.write_xcsg(GetInputFullPath(),secant_tolerance:0.0010);
}