Creating jigsaw puzzles inside AutoCAD using .NET

Too. Much. Fun. As mentioned in the last post, a colleague came to me with a problem… for an internal team-building exercise, he needed to manufacture a circular, 60-piece jigsaw puzzle with 6 groups of 10 pieces, each of which should be roughly the same size. The pieces will also have some text engraved on them, but that's a minor detail.

I searched the darkest corners of the Internet to find an online tool to generate a pattern for this, but then realised I'd spend my time more effectively by writing one myself and sharing it here. So that's what we're going to see today. The eventual goal is to laser cut the puzzle, of course, but first things first.

The first step was to work out the overall distribution of pieces. I fairly quickly worked out that 4 concentric rows of pieces containing 6 additional pieces in each row (i.e. rows with 6, 12, 18 & 24 pieces) would add up to 60. Then it was a matter of determining the radii of the various concentric rings to make the area the same for each piece – a fairly simple trigonometry problem. This left me with the basic grid of lines/arcs.

Our basic pattern

I created the outline with concentric circles (of course) and then polar arrays of lines. These I then exploded, resulting in 4 circles and the rest of the linear entities as short (non-contiguous) segments.

To generate a jigsaw pattern for the above outline, I decided on a couple of commands:

  1. JIGL is a command that takes the selected line segments and creates a spline at the location of each. This is for all the short, straight-line segments.
  2. JIG does the same thing, but works on a single curve (including circles – important for us, here) and allows us to select intersecting entities that define the limits of the curve section to process. This is for the concentric circles, and we select the appropriate radial lines as delimiters.

So how do we create a spline including a tab? It's actually really easy. We take the end-points of the line (or the intersection of the circle and the selected entities) and then move inwards, adding 4 more fit points – 2 on the line, 2 at a distance from it – as you can see below:

A single segment

I used a random Boolean to decide which direction it gets created in (in the above case that means up or down) and an additional random factor that creates the tab at a slightly different position. I could also have varied the shape of the tab, for that matter… I think I'll add that in v2 (as well as a command to add that random factor to existing tabs).

Here's how these commands can be combined to create the puzzle. The first step shows the initial splines being created by JIGL, the subsequent frames show the JIG command being used to generate the remaining segments. I also added arcs to fill in gaps, as needed.

Creating the jigsaw

Here's the C# source code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Runtime;

using System;

 

namespace JigsawGenerator

{

  public class Commands

  {

    [CommandMethod("JIG")]

    public void JigEntity()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (null == doc)

        return;

      var db = doc.Database;

      var ed = doc.Editor;

 

      // Select our entity to create a tab for

 

      var peo = new PromptEntityOptions("\nSelect entity to jig");

      peo.SetRejectMessage("\nEntity must be a curve.");

      peo.AddAllowedClass(typeof(Curve), false);

 

      var per = ed.GetEntity(peo);

      if (per.Status != PromptStatus.OK)

        return;

 

      // We'll ask the user to select intersecting/delimiting

      // entities: if they choose none we use the whole length

 

      ed.WriteMessage(

        "\nSelect intersecting entities. " +

        "Hit enter to use whole entity."

      );

 

      var pso = new PromptSelectionOptions();

      var psr = ed.GetSelection();

      if (

        psr.Status != PromptStatus.OK &&

        psr.Status != PromptStatus.Error // No selection

      )

        return;

 

      using (var tr = doc.TransactionManager.StartTransaction())

      {

        // Open our main curve

 

        var cur =

          tr.GetObject(per.ObjectId, OpenMode.ForRead) as Curve;

 

        double start = 0, end = 0;

        bool bounded = false;

 

        if (cur != null)

        {

          // We'll collect the intersections, if we have

          // delimiting entities selected

 

          var pts = new Point3dCollection();

 

          if (psr.Value != null)

          {

            // Loop through and collect the intersections

 

            foreach (var id in psr.Value.GetObjectIds())

            {

              var ent = (Entity)tr.GetObject(id, OpenMode.ForRead);

 

              cur.IntersectWith(

                ent,

                Intersect.OnBothOperands,

                pts,

                IntPtr.Zero,

                IntPtr.Zero

              );

            }

          }

 

          ed.WriteMessage(

            "\nFound {0} intersection points.", pts.Count

          );

 

          // If we have no intersections, use the start and end

          // points

 

          if (pts.Count == 0)

          {

            start = cur.StartParam;

            end = cur.EndParam;

            pts.Add(cur.StartPoint);

            pts.Add(cur.EndPoint);

            bounded = true;

          }

          else if (pts.Count == 2)

          {

            start = cur.GetParameterAtPoint(pts[0]);

            end = cur.GetParameterAtPoint(pts[1]);

            bounded = true;

          }

 

          // If we have a bounded length, create our tab in a random

          // direction

 

          if (bounded)

          {

            var rnd = new Random();

            var left = rnd.NextDouble() >= 0.5;

 

            CreateTab(db, tr, cur, start, end, pts, left);

          }

        }

 

        tr.Commit();

      }

    }

 

    [CommandMethod("JIGL")]

    public void JigLines()

    {

      var doc = Application.DocumentManager.MdiActiveDocument;

      if (null == doc)

        return;

      var db = doc.Database;

      var ed = doc.Editor;

 

      // Here we're going to get a selection set, but only care

      // about lines

 

      var pso = new PromptSelectionOptions();

      var psr = ed.GetSelection();

      if (psr.Status != PromptStatus.OK)

        return;

 

      using (var tr = doc.TransactionManager.StartTransaction())

      {

        // We'll be generating random numbers to decide direction

        // for each tab

 

        var rnd = new Random();

 

        foreach (var id in psr.Value.GetObjectIds())

        {

          // We only care about lines

 

          var ln = tr.GetObject(id, OpenMode.ForRead) as Line;

          if (ln != null)

          {

            // Get the start and end points in a collection

 

            var pts =

              new Point3dCollection(

                new Point3d[] {

                  ln.StartPoint,

                  ln.EndPoint

                }

              );

 

            // Decide the direction (randomly) then create the tab

 

            var left = rnd.NextDouble() >= 0.5;

            CreateTab(

              db, tr, ln, ln.StartParam, ln.EndParam, pts, left

            );

          }

        }

        tr.Commit();

      }

    }

 

    private static void CreateTab(

      Database db, Transaction tr,

      Curve cur, double start, double end, Point3dCollection pts,

      bool left = true

    )

    {

      // Again we're going to generate random numbers

 

      var rnd = new Random();

 

      // We're calculating a random delta to adjust the location

      // of the tab along the length

 

      double delta = 0.1 * (rnd.NextDouble() - 0.5);

 

      // Calculate the length of this curve (or section)

 

      var len =

        Math.Abs(

          cur.GetDistanceAtParameter(end) -

          cur.GetDistanceAtParameter(start)

        );

 

      // We're going to offset to the side of the core curve for

      // the tab points. This is currently a fixed tab size

      // (could also make this proportional to the curve)

 

      double off = 0.5;

      double fac = 0.5 * (len - 0.5 * off) / len;

      if (left) off = -off;

 

      // Get the next parameter along the length of the curve

      // and add the point associated with it into our fit points

 

      var nxtParam = start + (end - start) * (fac + delta);

      var nxt = cur.GetPointAtParameter(nxtParam);

      pts.Insert(1, nxt);

 

      // Get the direction vector of the curve

 

      var vec = pts[1] - pts[0];

 

      // Rotate it by 90 degrees in the direction we chose,

      // then normalise it and use it to calculate the location

      // of the next point

 

      vec = vec.RotateBy(Math.PI * 0.5, Vector3d.ZAxis);

      vec = off * vec / vec.Length;

      pts.Insert(2, nxt + vec);

 

      // Now we calculate the mirror points to complete the

      // splines definition

 

      nxtParam = end - (end - start) * (fac - delta);

      nxt = cur.GetPointAtParameter(nxtParam);

      pts.Insert(3, nxt + vec);

      pts.Insert(4, nxt);

 

      // Finally we create our spline and add it to the modelspace

 

      var sp = new Spline(pts, 1, 0);

 

      var btr =

        (BlockTableRecord)tr.GetObject(

          SymbolUtilityServices.GetBlockModelSpaceId(db),

          OpenMode.ForWrite

        );

      btr.AppendEntity(sp);

      tr.AddNewlyCreatedDBObject(sp, true);

    }

  }

}

The JIG command is only really needed if you want non-recto-linear patterns. Here's how the JIGL command deals with a straight rectangular grid (created using RECTANG, ARRAY, EXPLODE and OVERKILL, as we don't want overlapping lines).

Creating a rectangular jigsaw

8 responses to “Creating jigsaw puzzles inside AutoCAD using .NET”

  1. Subir Kumar Dutta Avatar
    Subir Kumar Dutta

    After a long time, once again a good Blog on Geometry. But please add the geometry tag to this blog so that we can find out later.

    1. Added!

      Kean

  2. True i enjoyed reading the article. After a long time i read something creative and informative. Thanks Kean. Autocad is an amazing Cad design tool which has fantastic uses for creative ideas.

  3. how i can insert this code in autocad ?

    1. Kean Walmsley Avatar

      You need to build it into a ClassLibrary project in Visual Studio - you can use NuGet to get the AutoCAD .NET reference libraries - and then NETLOAD the resultant .DLL into AutoCAD.

      Kean

      1. i can not do this . can you help me ?

        1. Kean Walmsley Avatar

          Sorry, no. There are "getting started" posts on this blog - beyond that I'm afraid I can't step you through it more than I have.

          Kean

          1. ok thank you 🙂

Leave a Reply to Roger King Cancel reply

Your email address will not be published. Required fields are marked *