As promised, here’s the cleaned-up code to jig a frustum inside AutoCAD. When I took on the task of writing this code – live, during the “AutoCAD Programming Gurus Go Head to Head” class at AU – I thought to myself “that should be easy enough – I’m sure I have some code to jig a solid on my blog”. Well, I did, but it turned out the code showed how to jig a box, and the code was in Python and Ruby but not C#. So I ended up having to code for my supper, after all. ;-)
One of the main components of the request was for the jigged solid to reflect the current visual style: the main trick to getting this is to work is to add the solid to the database prior to starting the jig, a technique that also works well when jigging a block with attributes.
Here’s the C# code:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
namespace CreateFrustumJig
{
// Our jig has three phases: we start by selecting the bottom
// radiu, then the height, then the top radius of the frustum
enum JigPhase
{
Bottom = 0,
Height = 1,
Top = 2
}
// Our jig class
class FrustumJig : EntityJig
{
// Member data
Matrix3d _ucs;
Point3d _cen;
Solid3d _sol;
JigPhase _phase;
double _top, _bot, _hgt;
// A public property for our "phase"
public JigPhase Phase
{
get { return _phase; }
set { _phase = value; }
}
// The constructor
public FrustumJig(Matrix3d ucs, Solid3d sol, Point3d cen)
: base(sol)
{
_ucs = ucs;
_sol = sol;
_cen = cen;
// Start at the bottom
_phase = JigPhase.Bottom;
// Use a number close to zero, but large enough for
// the CreateFrustum call to work
_top = 1e-5;
_bot = _top;
_hgt = _top;
}
protected override SamplerStatus Sampler(JigPrompts prompts)
{
var opts = new JigPromptDistanceOptions();
opts.UserInputControls =
(UserInputControls.Accept3dCoordinates
| UserInputControls.NoZeroResponseAccepted
| UserInputControls.NoNegativeResponseAccepted);
// All our distance inputs will be with a base point
// (and most will be from the initial center point)
opts.BasePoint = _cen.TransformBy(_ucs);
opts.UseBasePoint = true;
opts.Cursor = CursorType.RubberBand;
if (_phase == JigPhase.Bottom)
{
opts.Message = "\nSpecify bottom radius: ";
}
else if (_phase == JigPhase.Height)
{
opts.Message = "\nSpecify height: ";
}
else if (_phase == JigPhase.Top)
{
// We'll adjust the base point for the top radius
// selection to be the center of the top circle
opts.Message = "\nSpecify top radius: ";
opts.BasePoint +=
new Vector3d(0, 0, _hgt).TransformBy(_ucs);
}
PromptDoubleResult pdr = prompts.AcquireDistance(opts);
if (pdr.Status == PromptStatus.OK)
{
// Work out the value to check against based on the phase
double prev =
(_phase == JigPhase.Bottom ? _bot :
(_phase == JigPhase.Height ? _hgt : _top)
);
// If the difference between the new value and its
// previous value is negligible, return "no change"
if (
System.Math.Abs(prev - pdr.Value) <
Tolerance.Global.EqualPoint
)
{
return SamplerStatus.NoChange;
}
else
{
// Otherwise we update the appropriate variable
// based on the phase
if (_phase == JigPhase.Bottom)
_bot = pdr.Value;
else if (_phase == JigPhase.Height)
_hgt = pdr.Value;
else
_top = pdr.Value;
}
}
return SamplerStatus.OK;
}
protected override bool Update()
{
try
{
// Try to create a frustum with the provided parameters
_sol.CreateFrustum(_hgt, _bot, _bot, _top);
// Transform it appropriately so that it sits on the
// base point relative to the current UCS
_sol.TransformBy(
Matrix3d.Displacement(
_cen.GetAsVector() + new Vector3d(0, 0, _hgt / 2)
).PreMultiplyBy(_ucs)
);
}
catch (System.Exception)
{
return false;
}
return true;
}
public Entity GetEntity()
{
return Entity;
}
}
public class Commands
{
[CommandMethod("FJ")]
public void FrustumJig()
{
var doc =
Application.DocumentManager.MdiActiveDocument;
var db = doc.Database;
var ed = doc.Editor;
// First let's get the start position of the frustum
var opts =
new PromptPointOptions("\nSpecify frustum location: ");
var ppr = ed.GetPoint(opts);
if (ppr.Status == PromptStatus.OK)
{
// In order for the visual style to be respected,
// we'll add the to-be-jigged solid to the database
Transaction tr =
doc.TransactionManager.StartTransaction();
using (tr)
{
var btr =
(BlockTableRecord)tr.GetObject(
db.CurrentSpaceId, OpenMode.ForWrite
);
var sol = new Solid3d();
btr.AppendEntity(sol);
tr.AddNewlyCreatedDBObject(sol, true);
// Create our jig object passing in the selected point
var fj =
new FrustumJig(
ed.CurrentUserCoordinateSystem, sol, ppr.Value
);
// Perform the jig operation in a loop
while (true)
{
var res = ed.Drag(fj);
switch (res.Status)
{
// We progress the "phase" each time
case PromptStatus.OK:
if (fj.Phase == JigPhase.Bottom)
fj.Phase = JigPhase.Height;
else if (fj.Phase == JigPhase.Height)
fj.Phase = JigPhase.Top;
else if (fj.Phase == JigPhase.Top)
{
tr.Commit();
return;
}
break;
// The user cancelled the command
default:
return;
}
}
}
}
}
}
}
And here is our FJ command in action:
Looking at the above code, it occurs to me there’s some opportunity to factor out the input phases into a more generalised mechanism: the calling code would pass in a list of these input phases and provide a callback for the entity creation. I’ll add this to my list of things to look at – I think it might prove to be a straightforward way to simplify the code used to jig objects inside AutoCAD. We’ll see.