Since starting to use the transient graphics API in AutoCAD 2009, I’ve believed that any DBPoints you display would not respect PDMODE (the AutoCAD system variable allowing the user to control how points are displayed). A recent internal discussion was on this very subject, and one of our engineering team, Longchao Jiang, interjected with a comment explaining that only points on the DEFPOINTS layer get displayed without PDMODE being applied.
This was quite a revelation for me: DBPoints apparently get created on DEFPOINTS by default and simply changing them to layer 0 (say) would cause them to respect PDMODE when displayed transiently? I had to try that out.
To do so, I took the code from a couple of recent posts, one gathering points on a 3D object and another using transient graphics to implement a custom MOVE command, and plugged them together. For each of the objects selected to move, the code now gathers points from them and creates and displays these transiently along with the base geometry.
The updates to the point-gathering code were really minor – just about changing the signatures of the functions we want to use to be static and/or public – so we can simply call them from the other source file without much effort. Because the changes were minor, I’ve placed the code here for you to download.
The other source file has changed slightly more, but not very much: we now call into the other file to gather points on each object and pass them through to the modified CreateTransGraphics() function. This function now creates a DBPoint for each point, making sure it is on layer 0 before adding it to our list of transient objects to display.
Here’s the C# code from that file:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;
using Autodesk.AutoCAD.Runtime;
using System.Collections.Generic;
using System;
namespace CustomMove
{
public class Commands
{
[CommandMethod("MYMOVE", CommandFlags.UsePickSet)]
public static void CustomMoveCmd()
{
Document doc =
Autodesk.AutoCAD.ApplicationServices.
Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
Matrix3d ucs = ed.CurrentUserCoordinateSystem;
// Start by getting the objects to move
// (will use the pickfirst set, if defined)
PromptSelectionResult psr = ed.GetSelection();
if (psr.Status != PromptStatus.OK || psr.Value.Count == 0)
return;
// Create a collection of the selected objects' IDs
ObjectIdCollection ids =
new ObjectIdCollection(psr.Value.GetObjectIds());
// Ask the user to select a base point for the move
PromptPointResult ppr =
ed.GetPoint("\nSpecify base point: ");
if (ppr.Status != PromptStatus.OK)
return;
Point3d baseUcs = ppr.Value;
CoordinateSystem3d cs = ucs.CoordinateSystem3d;
// Transform from UCS to WCS
Matrix3d mat =
Matrix3d.AlignCoordinateSystem(
Point3d.Origin,
Vector3d.XAxis,
Vector3d.YAxis,
Vector3d.ZAxis,
cs.Origin,
cs.Xaxis,
cs.Yaxis,
cs.Zaxis
);
Point3d baseWcs = baseUcs.TransformBy(mat);
Point3d curPt = baseWcs;
// A local delegate for our event handler so
// we can remove it at the end
PointMonitorEventHandler handler = null;
// Our transaction
Transaction tr =
doc.Database.TransactionManager.StartTransaction();
using (tr)
{
// Gather our points
Point3dCollection pts = new Point3dCollection();
foreach (ObjectId id in ids)
{
Entity ent = (Entity)tr.GetObject(id, OpenMode.ForRead);
PointGathering.Commands.CollectPoints(tr, ent, pts);
}
// Create our transient drawables, with associated
// graphics, from the selected objects
List<Drawable> drawables =
CreateTransGraphics(tr, ids, pts);
try
{
// Add our point monitor
// (as a delegate we have access to basePt and curPt,
// which avoids having to access global/member state)
handler =
delegate(object sender, PointMonitorEventArgs e)
{
// Get the point, with "ortho" applied, if needed
Point3d pt = e.Context.ComputedPoint;
if (IsOrthModeOn())
{
// Point is in WCS, we need UCS for ortho calcs
Point3d destUcs = pt.TransformBy(mat.Inverse());
pt = GetOrthoPoint(baseUcs, destUcs);
// Then we transform back to WCS
pt = pt.TransformBy(mat);
}
// Update our graphics and the current point
UpdateTransGraphics(drawables, curPt, pt);
curPt = pt;
};
ed.PointMonitor += handler;
// Ask for the destination, during which the point
// monitor will be updating the transient graphics
PromptPointOptions opt =
new PromptPointOptions("\nSpecify second point: ");
opt.UseBasePoint = true;
opt.BasePoint = baseUcs;
ppr = ed.GetPoint(opt);
// If the point was selected successfully...
if (ppr.Status == PromptStatus.OK)
{
// ... move the entities to their destination
Point3d destUcs = ppr.Value;
Point3d dest =
(IsOrthModeOn() ?
GetOrthoPoint(baseUcs, destUcs) :
destUcs);
MoveEntities(tr, baseWcs, dest.TransformBy(mat), ids);
// And inform the user
ed.WriteMessage(
"\n{0} object{1} moved.", ids.Count,
ids.Count == 1 ? "" : "s"
);
}
}
catch (Autodesk.AutoCAD.Runtime.Exception ex)
{
ed.WriteMessage("\nException: {0}", ex.Message);
}
finally
{
// Clear any transient graphics
ClearTransGraphics(drawables);
// Remove the event handler
if (handler != null)
ed.PointMonitor -= handler;
tr.Commit();
tr.Dispose();
}
}
}
private static bool IsOrthModeOn()
{
// Check the value of the ORTHOMODE sysvar
object orth =
Autodesk.AutoCAD.ApplicationServices.Application.
GetSystemVariable("ORTHOMODE");
return Convert.ToInt32(orth) > 0;
}
private static Point3d GetOrthoPoint(
Point3d basePt, Point3d pt
)
{
// Apply an orthographic mode
double x = pt.X,
y = pt.Y,
z = pt.Z;
Vector3d vec = basePt.GetVectorTo(pt);
double absX = Math.Abs(vec.X),
absY = Math.Abs(vec.Y),
absZ = Math.Abs(vec.Z);
if (absX < absY && absX < absZ)
x = basePt.X;
else if (absY < absX && absY < absZ)
y = basePt.Y;
else
z = pt.Z;
return new Point3d(x, y, z);
}
private static void MoveEntities(
Transaction tr, Point3d basePt, Point3d moveTo,
ObjectIdCollection ids
)
{
// Transform a set of entities to a new location
Matrix3d mat =
Matrix3d.Displacement(basePt.GetVectorTo(moveTo));
foreach (ObjectId id in ids)
{
Entity ent = (Entity)tr.GetObject(id, OpenMode.ForWrite);
ent.TransformBy(mat);
}
}
private static List<Drawable> CreateTransGraphics(
Transaction tr, ObjectIdCollection ids, Point3dCollection pts
)
{
// Create our list of drawables to return
List<Drawable> drawables = new List<Drawable>();
foreach (ObjectId id in ids)
{
// Read each entity
Entity ent = (Entity)tr.GetObject(id, OpenMode.ForRead);
// Clone it, make it red & add the clone to the list
Entity drawable = ent.Clone() as Entity;
drawable.ColorIndex = 1;
drawables.Add(drawable);
}
// Create a transient point for each member of the collection
foreach (Point3d pt in pts)
{
// Make sure each point is on layer 0 (i.e. not DEFPOINTS)
// and make it red, too
DBPoint dbp = new DBPoint(pt);
dbp.Layer = "0";
dbp.ColorIndex = 1;
drawables.Add(dbp);
}
// Draw each one initially
foreach (Drawable d in drawables)
{
TransientManager.CurrentTransientManager.AddTransient(
d, TransientDrawingMode.DirectShortTerm,
128, new IntegerCollection()
);
}
return drawables;
}
private static void UpdateTransGraphics(
List<Drawable> drawables, Point3d curPt, Point3d moveToPt
)
{
// Displace each of our drawables
Matrix3d mat =
Matrix3d.Displacement(curPt.GetVectorTo(moveToPt));
// Update their graphics
foreach (Drawable d in drawables)
{
Entity e = d as Entity;
e.TransformBy(mat);
TransientManager.CurrentTransientManager.UpdateTransient(
d, new IntegerCollection()
);
}
}
private static void ClearTransGraphics(
List<Drawable> drawables
)
{
// Clear the transient graphics for our drawables
TransientManager.CurrentTransientManager.EraseTransients(
TransientDrawingMode.DirectShortTerm,
128, new IntegerCollection()
);
// Dispose of them and clear the list
foreach (Drawable d in drawables)
{
d.Dispose();
}
drawables.Clear();
}
}
}
Here’s what happens when we execute the code and move a sphere, with PDMODE set to 0 (you should just be able to make out the points displayed as dots):
And here’s what happens with PDMODE set to 2 (which looks a bit like something out of an old-school Star Wars video game):
Very cool – thanks for the tip, Longchao! :-)
I’m now at the airport, heading for Bangalore via Heathrow. I’ll be working from our office there for the coming week, spending time with our team there. Aside from catching up with everyone, I’m looking forward to watching India vs. Pakistan during the 2011 Cricket World Cup semi-final on Wednesday: I’m not really a cricket fan, but watching a match like this in India is a real experience (even if you’re only watching it on TV, as I’ll be doing).