Through the Interface: Turning AutoCAD into a Spirograph using F#

May 2015

Sun Mon Tue Wed Thu Fri Sat
          1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30


« Streamlined QuickSaveAs command for AutoCAD 2010 | Main | AU 2009 classes, both physical and virtual »

November 02, 2009

Turning AutoCAD into a Spirograph using F#

I’ve been toying for some time with the idea of writing some code to turn AutoCAD into a Spirograph, a device which I’m sure fascinated and inspired many of you as children (just as it did me). I chose to write the application in F# for a couple of reasons: this type of task is fundamentally mathematical in nature – so a functional programming language should be well-suited to the task – and I needed to dust off my F# skills in time for my F# class at AU. Searching the web I came across this helpful post providing some functional C# code to plot points along the path followed by a Spirograph (in this case for UI automation), which in turn referenced a post with the underlying mathematics stated relatively simply.

I won’t bother reproducing the relevant contents of these two posts: please visit them if you’re interested in the background to the code. The changes I’ve made have largely been around gathering the appropriate information needed to customize each path. The code creates a simple polyline with plain-old linear segments (we’ll use enough to easily approximate a curve, although choosing this “simple” approach means we need to create more segments than would otherwise been needed to show the path smoothly, which clearly means heavier objects).

Here’s some F# code to create Spirograph-style patterns in AutoCAD:

// Declare a specific namespace and module name


module Spirograph.Commands


// Import managed assemblies


open Autodesk.AutoCAD.ApplicationServices

open Autodesk.AutoCAD.Runtime

open Autodesk.AutoCAD.DatabaseServices

open Autodesk.AutoCAD.EditorInput

open Autodesk.AutoCAD.Geometry

open System


// Return a sampling of points along a Spirograph's path


let pointsOnSpirograph cenX cenY inRad outRad a tStart tEnd =


    for i in tStart .. tEnd * 10 do


      let t = (float i) * 0.1

      let diff = inRad - outRad

      let ratio = inRad / outRad

      let x =

        diff * Math.Cos(ratio * t) +

          a * Math.Cos((1.0 - ratio) * t)

      let y =

        diff * Math.Sin(ratio * t) -

          a * Math.Sin((1.0 - ratio) * t)


      yield new Point2d(cenX + x, cenY + y)



// Our command



let spirograph() =


  // Let's get the usual helpful AutoCAD objects


  let doc =


  let ed = doc.Editor

  let db = doc.Database


  // Prompt the user for the center of the spirograph


  let cenRes = ed.GetPoint("\nSelect center point: ")


  if cenRes.Status = PromptStatus.OK then


    let cen = cenRes.Value


    // Now the radius of the outer circle


    let pdo =

      new PromptDistanceOptions

        ("\nEnter radius of outer circle: ")

    pdo.BasePoint <- cen

    pdo.UseBasePoint <- true


    let radRes = ed.GetDistance(pdo)


    if radRes.Status = PromptStatus.OK then


      let outerRad = radRes.Value


      // And the radius of the smaller circle


      pdo.Message <-

        "\nEnter radius of smaller circle: "


      let loopRes = ed.GetDistance(pdo)     


      if loopRes.Status = PromptStatus.OK then


        let innerRad = loopRes.Value


        // And finally the value of "a", the distance of the

        // "pen" from the center of the smaller circle


        pdo.Message <-

          "\nEnter pen distance from center of smaller circle: "


        let aRes = ed.GetDistance(pdo)


        if aRes.Status = PromptStatus.OK then


          let a = aRes.Value


          // Now we can get a sampling of points along our path


          let pts =


              cen.X cen.Y innerRad outerRad a 0 300


          // And we'll add a simple polyline with these points


          use tr =



          // Get appropriately-typed BlockTable and BTRs


          let bt =



              :?> BlockTable


          let ms =




              :?> BlockTableRecord


          // Create our polyline


          let pl = new Polyline(pts.Length)



          // Add the various vertices to the polyline


          for i in 0 .. pts.Length-1 do

            pl.AddVertexAt(i, pts.[i], 0.0, 0.0, 0.0)


          // Add our polyline to the modelspace


          let id = ms.AppendEntity(pl)

          tr.AddNewlyCreatedDBObject(pl, true)



When we run our SPI command we get prompted for a number of parameters: the centre of the path, the radius of the outer circle, the radius of the smaller circle which will “roll” around the outer one, and finally the position of the pen relative to the centre of the smaller circle:

Command: SPI

Select center point:

Enter radius of outer circle:

Enter radius of smaller circle:

Enter pen distance from center of smaller circle:

Here’s a simple script you can execute to create a number of simple paths (by copy and pasting the contents into a text file, saving it with the .SCR extension, and running it inside AutoCAD via the SCRIPT command):

SPI 0,0,0 10 5 3

SPI 12,0,0 10 6 4

SPI 28,0,0 10 3 2

SPI 48,0,0 8 1 3

SPI 68,0,0 10 3 1

SPI 84,0,0 8 2 4

SPI 100,0,0 8 1 2

SPI 116,0,0 8 2 2

SPI 132,0,0 10 3 3

SPI 150,0,0 10 2 .3

SPI 168,0,0 10 3 .5

SPI 185,0,0 12 7 3

SPI 202,0,0 12 7 4

Which creates these fairly simple Spirograph paths:

Simple Spirographs in AutoCAD

By just playing around selecting values you can come up with much more complex shapes:

Various Spirographs in AutoCAD

[A quick aside regarding the command’s user-input: the various distances are input relative to the centre of the Spirograph, which isn’t ideal. I would probably have preferred to select the last two items - the smaller circle radius and the pen distance – relative to the center of the smaller circle. But as we’re using GetDistance() to get the outer radius we’d have to choose an arbitrary location for this circle, which would actually have made the selection process more confusing. If we knew where the distance point was actually selected we could implement this easily, but using GetPoint() instead of GetDistance() (along with some simple vector arithmetic to calculate the underlying distance between the two circles’ centres) means the user couldn’t then enter a numerical value for the outer radius (which would make it all more confusing still). Implementing a jig to display temporary circles etc. as the locations get selected would probably be the way to go, should we want to turn this into a real application, but as it’s all really just a bit of fun I’m going to leave it at that. :-)]

blog comments powered by Disqus


10 Random Posts