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.

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:

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:

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.

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

## Recent Comments

## Archives

More...