Kean Walmsley


  • About the Author
    Kean on Google+

April 2014

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

 

[<CommandMethod("spi")>]

let spirograph() =

 

  // Let's get the usual helpful AutoCAD objects

 

  let doc =

    Application.DocumentManager.MdiActiveDocument

  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 =

            pointsOnSpirograph

              cen.X cen.Y innerRad outerRad a 0 300

 

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

 

          use tr =

            db.TransactionManager.StartTransaction()

 

          // Get appropriately-typed BlockTable and BTRs

 

          let bt =

            tr.GetObject

              (db.BlockTableId,OpenMode.ForRead)

              :?> BlockTable

 

          let ms =

            tr.GetObject

              (bt.[BlockTableRecord.ModelSpace],

              OpenMode.ForWrite)

              :?> BlockTableRecord

 

          // Create our polyline

 

          let pl = new Polyline(pts.Length)

          pl.SetDatabaseDefaults()

 

          // 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)

 

          tr.Commit()

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