During the first two parts of this series we looked at different techniques for creating polylines programmatically in AutoCAD (while allowing the user to select the various vertices).
In this post look at the most advanced technique yet, the use of a "Jig". A Jig is a special construct in AutoCAD that hosts an object - in our case a polyline - and feeds user input information into this object, which then determines what graphics gets displayed. This is especially useful when dealing with complex objects, such as polylines with arc segments.
We're going to start with a fairly basic Jig, one that simply collects vertices and creates straight-line segments, but I'd quite like to take this further to support a number of different advanced capabilities: arc segments (which will require keyword implementation), dynamic dimensions, and possibly even undo (a request that just came in as a comment to the previous post).
Anyway, here's the C# code of the basic Jig implementation:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
namespace MyPlineApp
{
public class MyPlineCmds
{
class PlineJig : EntityJig
{
// Maintain a list of vertices...
// Not strictly necessary, as these will be stored in the
// polyline, but will not adversely impact performance
Point3dCollection m_pts;
// Use a separate variable for the most recent point...
// Again, not strictly necessary, but easier to reference
Point3d m_tempPoint;
Plane m_plane;
public PlineJig(Matrix3d ucs)
: base(new Polyline())
{
// Create a point collection to store our vertices
m_pts = new Point3dCollection();
// Create a temporary plane, to help with calcs
Point3d origin = new Point3d(0, 0, 0);
Vector3d normal = new Vector3d(0, 0, 1);
normal = normal.TransformBy(ucs);
m_plane = new Plane(origin, normal);
// Create polyline, set defaults, add dummy vertex
Polyline pline = Entity as Polyline;
pline.SetDatabaseDefaults();
pline.Normal = normal;
pline.AddVertexAt(0, new Point2d(0, 0), 0, 0, 0);
}
protected override SamplerStatus Sampler(JigPrompts prompts)
{
JigPromptPointOptions jigOpts =
new JigPromptPointOptions();
jigOpts.UserInputControls =
(UserInputControls.Accept3dCoordinates |
UserInputControls.NullResponseAccepted |
UserInputControls.NoNegativeResponseAccepted
);
if (m_pts.Count == 0)
{
// For the first vertex, just ask for the point
jigOpts.Message =
"\nStart point of polyline: ";
}
else if (m_pts.Count > 0)
{
// For subsequent vertices, use a base point
jigOpts.BasePoint = m_pts[m_pts.Count - 1];
jigOpts.UseBasePoint = true;
jigOpts.Message = "\nPolyline vertex: ";
}
else // should never happen
return SamplerStatus.Cancel;
// Get the point itself
PromptPointResult res =
prompts.AcquirePoint(jigOpts);
// Check if it has changed or not
// (reduces flicker)
if (m_tempPoint == res.Value)
{
return SamplerStatus.NoChange;
}
else if (res.Status == PromptStatus.OK)
{
m_tempPoint = res.Value;
return SamplerStatus.OK;
}
return SamplerStatus.Cancel;
}
protected override bool Update()
{
// Update the dummy vertex to be our
// 3D point projected onto our plane
Polyline pline = Entity as Polyline;
pline.SetPointAt(
pline.NumberOfVertices - 1,
m_tempPoint.Convert2d(m_plane)
);
return true;
}
public Entity GetEntity()
{
return Entity;
}
public void AddLatestVertex()
{
// Add the latest selected point to
// our internal list...
// This point will already be in the
// most recently added pline vertex
m_pts.Add(m_tempPoint);
Polyline pline = Entity as Polyline;
// Create a new dummy vertex...
// can have any initial value
pline.AddVertexAt(
pline.NumberOfVertices,
new Point2d(0,0),
0,0,0
);
}
public void RemoveLastVertex()
{
// Let's remove our dummy vertex
Polyline pline = Entity as Polyline;
pline.RemoveVertexAt(m_pts.Count);
}
}
[CommandMethod("MYPOLY")]
public void MyPolyJig()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
// Get the current UCS, to pass to the Jig
Matrix3d ucs =
ed.CurrentUserCoordinateSystem;
// Create our Jig object
PlineJig jig = new PlineJig(ucs);
// Loop to set the vertices directly on the polyline
bool bSuccess = true, bComplete = false;
do
{
PromptResult res = ed.Drag(jig);
bSuccess =
(res.Status == PromptStatus.OK);
// A new point was added
if (bSuccess)
jig.AddLatestVertex();
// Null input terminates the command
bComplete =
(res.Status == PromptStatus.None);
if (bComplete)
// Let's clean-up the polyline before adding it
jig.RemoveLastVertex();
} while (bSuccess && !bComplete);
// If the jig completed successfully, add the polyline
if (bComplete)
{
// Append entity
Database db = doc.Database;
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
BlockTable bt =
(BlockTable)tr.GetObject(
db.BlockTableId,
OpenMode.ForRead,
false
);
BlockTableRecord btr =
(BlockTableRecord)tr.GetObject(
bt[BlockTableRecord.ModelSpace],
OpenMode.ForWrite,
false
);
btr.AppendEntity(jig.GetEntity());
tr.AddNewlyCreatedDBObject(jig.GetEntity(), true);
tr.Commit();
}
}
}
}
}
That's it for the basic Jig implementation. For further information on Jigs, I suggest taking a look at the "EllipseJig" sample on the ObjectARX SDK (under samples/dotNet) and the ObjectARX Developer's Guide (the C++ documentation is much more extensive for now). I'll also take a closer look in future posts, so please keep your questions coming.
Update
For an updated implementation that supports keywords, arc segments and has better support for non-standard UCS settings, please see this post.