As mentioned in this previous post, I was very keen to see how AutoCAD could be used to help streamline the process of generating what I’ve now found out can be classified as anamorphic street art. Leon Keer had mentioned that the technique dates back to Leonardo Da Vinci and – sure enough – Wikipedia agrees.
As part of my research, I found the original video that inspired my interest in Leon’s work, which should help put this post in context:
To make the process reasonably realistic – and to some degree replicate the approach Leon has taken in his projects – I managed to track down a free 3D model of a LEGO mini-figure. It’s available in various formats, but I chose to use FBX to bring it into AutoCAD.
If I were generating geometry for a real project, I’d probably create the scene using a product that can effectively manipulate instances of our mini-figure before bringing the full scene into AutoCAD (which presumably means Autodesk Maya, 3ds Max or MotionBuilder).
The workflow for creating the art was pretty standard, all things considered. Here’s a video of the process:
Here’s the process used in more detail:
- Use FBXIMPORT to bring in the standard figure.
- Use INSERT to create an instance of the imported block (having an elevation of –2.5 will make it look like it’s below the surface of the ground).
- Use the ARRAY command to duplicate the block (I used a 3 x 6 grid).
- Change the VISUALSTYLE to Realistic and make sure we’re viewing in perspective mode.
- Use 3DORBIT to adjust the view to one you like.
- Use FLATSHOT to generate a projection of the 3D geometry (without occluded lines) and place it anywhere.
- Delete the flatshot block (you may want to get its name using LIST, first).
- Set the UCS to the current View.
- INSERT the flatshot block into this view at the middle of the 3D scene, then scale it to get it as close to the view as possible.
- MOVE the inserted block so that it matches the view of the 3D geometry.
- Place the flatshot block on its own layer (creating the layer using LAYER and changing the block’s layer property via PROPS or CHPROP).
- You can use 3DORBIT to see that the block is really inserted on the view plane (and then UNDO or ZOOM P to get back to the 3D view).
- Set the UCS back to World.
- Use the custom SKEWSHOT command to generate a projected set of geometry from the flatshot block, adjusting the factor upwards or downwards to get a close fit.
And here’s the C# code defining the SKEWSHOT command:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
public class Commands
{
[CommandMethod(
"SKEWSHOT",
CommandFlags.Modal | CommandFlags.UsePickSet
)]
static public void SkewShot()
{
Document doc = Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
// Select the entities to project (could also come from
// pickfirst selection set)
PromptSelectionOptions pso = new PromptSelectionOptions();
pso.MessageForAdding = "Select entities to project";
PromptSelectionResult psr = ed.GetSelection(pso);
if (psr.Status != PromptStatus.OK)
return;
// Use an arbitrary factor to start with (this is the first
// one that happened to work when I created my first drawing)
double factor = 1.3;
bool done = false;
// Loop until the results are accepted
while (!done)
{
// Ask the user to confirm the factor to use
PromptDoubleOptions pdo =
new PromptDoubleOptions("\nEnter factor");
pdo.DefaultValue = factor;
pdo.UseDefaultValue = true;
pdo.AllowZero = false;
PromptDoubleResult pdr = ed.GetDouble(pdo);
if (pdr.Status != PromptStatus.OK)
return;
factor = pdr.Value;
// Start a transaction, which will get aborted if we iterate
Transaction tr = doc.TransactionManager.StartTransaction();
using (tr)
{
// Project the contents of our selection set to the
// ground plabe, using the current factor
ProjectToGroundBasedOnFactor(tr, doc, psr.Value, factor);
// Ask whether to accept or try again
PromptKeywordOptions pko =
new PromptKeywordOptions(
"\nTry again with a different factor?"
);
pko.AllowNone = true;
pko.Keywords.Add("Yes");
pko.Keywords.Add("No");
pko.Keywords.Default = "No";
PromptResult pkr = ed.GetKeywords(pko);
// Only commit the transaction if the results are accepted
if (pkr.StringResult == "No")
{
done = true;
tr.Commit();
}
}
}
}
private static void ProjectToGroundBasedOnFactor(
Transaction tr, Document doc, SelectionSet ss, double factor
)
{
// We need to be able to write to the modelspace
BlockTable bt =
(BlockTable)tr.GetObject(
doc.Database.BlockTableId, OpenMode.ForRead
);
BlockTableRecord btr =
(BlockTableRecord)tr.GetObject(
bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite
);
// Create our ground plane
Plane pl = new Plane(Point3d.Origin, Vector3d.ZAxis);
// Get the centre and direction of the current view
Point3d vc = (Point3d)Application.GetSystemVariable("VIEWCTR");
Point3d vd = (Point3d)Application.GetSystemVariable("VIEWDIR");
// Use our factor to generate the direction for the projection
Point3d viewcen =
new Point3d(
vc.X + (vd.X * factor),
vc.Y + (vd.Y * factor),
vc.Z + (vd.Z * factor)
);
// Call a recursive function for each object in the selection
// set
foreach (SelectedObject so in ss)
{
DBObject obj = tr.GetObject(so.ObjectId, OpenMode.ForRead);
ProjectCurveOntoPlane(tr, btr, obj, pl, viewcen);
}
}
private static void ProjectCurveOntoPlane(
Transaction tr, BlockTableRecord btr, DBObject obj,
Plane pl, Point3d viewCen
)
{
// Currently support curve and block reference objects
if (obj is BlockReference)
{
// For block references we explode and recurse
BlockReference br = (BlockReference)obj;
DBObjectCollection oc = new DBObjectCollection();
br.Explode(oc);
foreach (DBObject o in oc)
{
ProjectCurveOntoPlane(tr, btr, o, pl, viewCen);
}
}
else if (obj is Curve)
{
// For curves we will project to the ground plane
Curve cur = (Curve)obj;
// Set the projection direction from center of the view to
// the "mid" point of our curve (which is really the mid-point
// between the start and end points, not necessarily the
// geometric mid-point)
Vector3d dir =
(cur.StartPoint + ((cur.EndPoint - cur.StartPoint) / 2)) -
viewCen;
// Get the projected curve and add it to the modelspace
Curve cur2 = cur.GetProjectedCurve(pl, dir);
btr.AppendEntity(cur2);
tr.AddNewlyCreatedDBObject(cur2, true);
}
}
}
The SKEWSHOT command takes a selection set of geometry and projects the curves it contains along the current view onto the ground plane (which clearly may be above or below the 3D model – on our case we’ve chosen it to be above, as our LEGO figures are supposed to be buried in the ground).
We’re actually projecting each curve along a live between its “mid” point (we’re actually taking the point directly between its start and its end – not necessarily it’s geometric mid-point) and the camera. As each curve is projected differently they may end up not touching, but they should nonetheless provide a good enough guide for the drawing’s outline.
Also, each view is a little different, so I’ve added an adjustable factor that can be used to tweak the results. The code works on a loop – with a transaction used for each pass that only gets committed when the results are acceptable – to make it easier to make repeated adjustments.
Here’s the drawing file (saved as an AutoCAD 2013 .dwg – just post a comment if you’d like a previous version to be saved off and posted) I created while recording the above video, if you’d like to take a look at it. It’s clear there’s a big gap between these results and a work of art. :-) This is really trying to demonstrate the principle: more work would be needed to make sure the camera position is at an appropriate point above the ground to give a genuine 3D effect, as well as generating a 3D model that’s aesthetically pleasing and then an effective 2D interpretation of it. CAD is clearly never going to replace art, but it can hopefully take some of the pain out of it. ;-)
I’m hoping to find the time to give this a try over the coming weeks, although obviously on a much smaller scale than Leon & co.
Update:
I’ve just seen Leon has posted a timelapse video of the LEGOLAND Windsor project. You can see my family flash past at around the 2m26s mark. :-)