Friday, July 24, 2009

Help AutoCAD Users Visually with Transient Graphics – Part 2

In my previous article (Part 1), I showed how to use AutoCAD (2009 or later) Transient Graphics to highlight entities in interest when mouse hover them. In this article, I continue on the topic of using Transient Graphics to help CAD users.

When VBA was adopted in AutoCAD (R14.01, I think), many AutoCAD programmers jumped into it and they were very pleased with VBA making AutoCAD programming a lot more smooth and powerful. However, there was one missing thing that makes Lisp programmer often laughing at VBA programmer: when a VBA programmer tries to make a VBA operation more like AutoCAD built-in command, such as move/copy/insert…, there is no way to generate a ghost image during the command execution, like AutoCAD does, while Lisp programmers can simply do (command “…”). Many VBA programmers take pains in vain trying to find a solution, and I saw questions on this regard posted in VBA forum from time to time.

Of course, this can be done using Jig with C++ ObjectARX in any version of AutoCAD, and since ObjectARX .NET API finally available (AutoCAD 2005, but 2006 is the first useable version), doing a Jig with .NET API is not that difficult any more. But it is still fairly advanced programming tricks in term of ObjectARX .NET API.

Now, with Transient Graphics available since AutoCAD 2009, we can achieve the visual effect similar to AutoCAD Jig API with transient graphics with easier coding (comparing to coding a Jig).

I am going to create a polyline transient graphic jig (yes, polyline again, because my current CAD related work mainly deals with closed polyline. You can easily extend the idea showed here to other types of entity).

Here is the code:



using System;
using System.Collections.Generic;
using System.Text;

using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.GraphicsInterface;

[assembly: CommandClass(typeof(TransientGraphicsSample.ADSCommands))]

namespace TransientGraphicsSample
{
public class ADSCommands : IExtensionApplication
{
public ADSCommands()
{
//
// TODO: Add constructor logic here
//
}

#region Show TG Jig

[CommandMethod("TGJig")]
public static void ShowTGJig()
{
Document dwg = Application.DocumentManager.MdiActiveDocument;

TGPolylineJig jig = new TGPolylineJig(dwg);

jig.ShowTGJig();
}

#endregion
}
public class TGPolylineJig
{
private Polyline mTGPolyline=null;
private Polyline mTargetPolyline;
private Editor mEditor;
private Database mDB;
private Document mDoc;

private Point3d mBasePoint;
private int mColorIndex = 1;

public TGPolylineJig(Document doc)
{
mDoc = doc;
mDB = doc.Database;
mEditor = doc.Editor;
}

public void ShowTGJig()
{
//Option to pick the target polyline
PromptEntityOptions eOpt =
new PromptEntityOptions("\nPick a polyline:");

//Only allow to pick polyline
eOpt.SetRejectMessage("\nYou must pick a polyline:");
eOpt.AddAllowedClass(typeof(Polyline), true);

//Do the pick
PromptEntityResult eRes = mEditor.GetEntity(eOpt);
if (eRes.Status != PromptStatus.OK) return;

//Get the target polyline
ObjectId targetId = eRes.ObjectId;
Polyline target = GetPolyline(targetId);
if (target == null) return;

mTargetPolyline = target;

//Prompt to pick base point and exit
PromptPointOptions opt1 =
new PromptPointOptions("\nClick base point:");
PromptPointOptions opt2 =
new PromptPointOptions("\nClick anywhere to exit:");

//Pick base point
PromptPointResult res = mEditor.GetPoint(opt1);
if (res.Status != PromptStatus.OK) return;

mBasePoint = res.Value;

//Set base point for second pick so that a rubberband
//will be shown when mouse is moving
opt2.BasePoint = mBasePoint;
opt2.UseBasePoint = true;

//Start monitoring pointer move
mEditor.PointMonitor +=
new PointMonitorEventHandler(mEditor_PointMonitor);

//Start asking user to do second pick (exit)
//so that the "jig" image will move with mouse
mEditor.GetPoint(opt2);

//Once the second point is picked, clear the
//transient graphic "jig". After this, since
//a new point is obtained with the jig;s visual
//help, one can then do copy/move/insert...
ClearTGPolyline();

//End pointer move monitoring
mEditor.PointMonitor -=
new PointMonitorEventHandler(mEditor_PointMonitor);
}

private void mEditor_PointMonitor
(object sender, PointMonitorEventArgs e)
{
//Whenever point moves, draw a new transient
//graphic image at mouse point
DrawTGPolyline(e.Context.RawPoint);

//rotate colorindex, so the jig's color
//changes with mouse move, thus more eye-catching
mColorIndex += 1;
if (mColorIndex > 8) mColorIndex = 1;
}

private Polyline GetPolyline(ObjectId id)
{
Polyline pl = null;

using (Transaction tran =
mDB.TransactionManager.StartOpenCloseTransaction())
{
pl = tran.GetObject(id, OpenMode.ForRead) as Polyline;
tran.Commit();
}

return pl;
}

private void DrawTGPolyline(Point3d movePoint)
{
//Clear existing transient graphic image
ClearTGPolyline();

//Get displacement increments of current mouse
//point against picked base point
double x = movePoint.X - mBasePoint.X;
double y = movePoint.Y - mBasePoint.Y;

//Create a polyline for the transient graphics
mTGPolyline =
new Polyline(mTargetPolyline.NumberOfVertices);

for (int i = 0; i < mTargetPolyline.NumberOfVertices; i++)
{
Point2d p = mTargetPolyline.GetPoint2dAt(i);
Point2d pt = new Point2d(p.X + x, p.Y + y);

mTGPolyline.AddVertexAt(i, pt, 0.0, 1.0, 1.0);
}

mTGPolyline.Closed = mTargetPolyline.Closed;
mTGPolyline.SetDatabaseDefaults();

//Set color
mTGPolyline.ColorIndex = mColorIndex;
mTGPolyline.LineWeight = LineWeight.LineWeight200;

//Add transient graphics
IntegerCollection col = new IntegerCollection();
TransientManager.CurrentTransientManager.
AddTransient
(
mTGPolyline,
TransientDrawingMode.DirectShortTerm,
128,
col
);
}

private void ClearTGPolyline()
{
if (mTGPolyline != null)
{
//Remove transient graphic effect
IntegerCollection intCol =
new IntegerCollection();

TransientManager.CurrentTransientManager.
EraseTransient(mTGPolyline, intCol);

//dispose the polyline used to
//Show transient graphic effect
mTGPolyline.Dispose();
mTGPolyline = null;
}
}
}
}



You can click here to show a short video clip.

As you can see, the code is surprisingly simple. There are a lot of enhancements you can do with this code base. For example, as previously stated, you can use other entity types rather than polyline; you can select a group of entities and do the jig on all the selected entities; you can build a small dialog box to allow user to configure LineType, Color, LineWeight for the transient graphic object before it is shown. One possible enhancement might be a bit difficult and tricky: what if the selected target entity is a BlockReference? Well, someone may want to try it.

I may continue on the topic of transient graphic in next post.

Sunday, July 19, 2009

Help AutoCAD Users Visually with Transient Graphics – Part 1

An AutoCAD drawing could contains tens or hundreds of thousands entities, which makes it very difficult for a CAD user to find certain entities or specific type of entities he/she is trying to work with. Surely, the CAD user can use “quick select” tool, or other means provided by AutoCAD out of box to find locate what he/she is looking for. However, it would be a nice help if we programmers could make a visual help tool, which would visually prompt the CAD user of the targeting entities when he/she move the mouse around over the entities, or do a window selecting (of many entities). Actually AutoCAD has already provided this kind of capability, such as Quick Properties. But to do this kind of stuff in our own program was not an easy task, or not even possible if you do not do C++, until AutoCAD 2009, in which Transient Graphics API is available for .NET API development.

In this article, I’ll show how to use Transient Graphics to visually “highlight” certain type of entity (lightweight polyline in my code, but you can easily modify the code to target other type of entity): when user moves the cursor over certain type of entity, the entity is highlighted, while the cursor moves away, the highlighting graphics goes away, too.

Here is the code, followed by some explanation:




using System;

using System.Collections.Generic;

using System.Text;

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.GraphicsInterface;



[assembly: CommandClass(typeof(TransientGraphicsSample.TGCommands))]



namespace TransientGraphicsSample

{

public class TGCommands

{

public TGCommands()

{

//

// TODO: Add constructor logic here

//

}



#region Polyline Transient Graphics test



private static PolylineTGTracks mTGTracks = null;

private static IPolylineTGFilter mFilter = null;



[CommandMethod("PolyTGOn")]

public static void TurnOnPolylineTG()

{

if (mTGTracks == null) mTGTracks = new PolylineTGTracks();



//Instantiate polyline filter

if (mFilter == null) mFilter = new MyPolylineTGFilter();



Document dwg =

Application.DocumentManager.MdiActiveDocument;



mTGTracks.

SetMouseOverEntityTransientGraphics

(dwg, mFilter, true);

}



[CommandMethod("PolyTGOff")]

public static void TurnOffPolyTG()

{

if (mTGTracks == null) return;



Document dwg =

Application.DocumentManager.MdiActiveDocument;



mTGTracks.SetMouseOverEntityTransientGraphics

(dwg, null, false);

}



#endregion

}



// A class to hold information of transient graphics created

// in a document

public class TGInformation

{

private Document mDwg;

private Editor mEditor;

private Database mDB;



// Targed entity. Should be a Polyine in this example

private ObjectId mCurrentMouseOverObject = ObjectId.Null;



// A polyline object used as transient graphic object

private Polyline mMouseOverTGObject = null;



// Flaging if the transient graphic visual prompt takes effect

private bool mMouseOverEntityMonitored = false;



// Visual prompt's color

private int mTGColorIndex=1;



// Width of the polyline as transient graphic object

private double mLineWidth = 1.0;



// Targeting polyline filter. See more explanation on

// IPolylineTGFilter interface and its implementation

private IPolylineTGFilter mFilter;



// Constructor

public TGInformation

(

Document dwg,

IPolylineTGFilter filter

)

{

mDwg = dwg;

mEditor = mDwg.Editor;

mDB = mDwg.Database;

mFilter = filter;

}



// Overloaded Constructor

public TGInformation

(

Document dwg,

IPolylineTGFilter filter,

double linewidth

)

{

mDwg = dwg;

mEditor = mDwg.Editor;

mDB = mDwg.Database;

mFilter = filter;

mLineWidth = linewidth;

}



public double TGLineWidth

{

set { mLineWidth = value; }

get { return mLineWidth; }

}



// This property indicates if Mouse-Over

// visual effect is set to on or off

public bool MouseOverEntityMonitored

{

set

{

if (mMouseOverEntityMonitored == value)

{

return;

}



if (value)

{

BeginMonitoringMouseOverEntity();

}

else

{

EndMonitoringMouseOverEntity();

}

}

get

{

return mMouseOverEntityMonitored;

}

}



#region private methods



private void BeginMonitoringMouseOverEntity()

{

//Clear previous transient graphic objec

ClearMouseOverTGObject();



mCurrentMouseOverObject = ObjectId.Null;



//Start handling Editor.PointMinotor event

mEditor.PointMonitor +=

new PointMonitorEventHandler(mEditor_PointMonitor);



mMouseOverEntityMonitored = true;

}



private void EndMonitoringMouseOverEntity()

{

//Remove Ediotr.PointMonitor event handler

mEditor.PointMonitor -=

new PointMonitorEventHandler(mEditor_PointMonitor);



mMouseOverEntityMonitored = false;



//Clear exisitn transient graphic object

ClearMouseOverTGObject();

mCurrentMouseOverObject = ObjectId.Null;

}



// Handling Editor.PointMonitor event. so that

//if the cursor is hovering on targeting polyline,

//transient graphic object is created, thus,

//the targeting polyline is highlighted

void mEditor_PointMonitor(object sender,

PointMonitorEventArgs e)

{

FullSubentityPath[] entPaths =

e.Context.GetPickedEntities();



if (entPaths.Length > 0)

{

//When the cursor does hover something

//Get the ObjectId of the entity

FullSubentityPath entPath = entPaths[0];



ObjectId id = entPath.GetObjectIds()[0];



//If the entity is polyline, in this example

if (id.ObjectClass ==

Polyline.GetClass(typeof(Polyline)))

{

//Add your filter here, to decide if

//this polyline is one of the targeting

//polylines in interest

if (mFilter.IsTGTarget(id))

{

//Set the transient graphic highlight

AddOrModifyMouseOverTGObject(id);

}

}

}

else

{

//No entity present below the cursor,

//so clear transient graphic highlight

ClearMouseOverTGObject();

}

}



//Do the transient graphic highlighting

private void AddOrModifyMouseOverTGObject

(ObjectId id)

{

if (mCurrentMouseOverObject == id)

{

//If the cursor is moving over on the

//same targeting polyline, update the

//highlight (changing its color) to

//achieve eye-catching effect

ModifyMouseOverTG();

}

else

{

//Create a new transient graphic object

mCurrentMouseOverObject = id;

AddMouseOverTGObject();

}

}



// Clear existing transient graphic effect

private void ClearMouseOverTGObject()

{

if (mMouseOverTGObject != null)

{

//Remove transient graphic effect

IntegerCollection intCol =

new IntegerCollection();



TransientManager.CurrentTransientManager.

EraseTransient(mMouseOverTGObject, intCol);



//dispose the polyline used to

//show transient graphic effect

mMouseOverTGObject.Dispose();

mMouseOverTGObject = null;

}

}



//Create new transient graphic object

private void AddMouseOverTGObject()

{

//Get the targeting polyline

Polyline target = GetTargetPolyline();



//Create transient graphic polyline,

//which the exactly the same as

//the targeting polyline geometrically

mMouseOverTGObject =

new Polyline(target.NumberOfVertices);



//Set vertices' coordinate and width

for (int i = 0; i < target.NumberOfVertices; i++)

{

Point2d pt = target.GetPoint2dAt(i);

mMouseOverTGObject.AddVertexAt

(i, pt, 0.0, mLineWidth, mLineWidth);

}



//Set color

mMouseOverTGObject.ColorIndex = mTGColorIndex;



//Let the color changes from 1 to 8

//so that when mouse moves, highlighting

//color changes

mTGColorIndex += 1;

if (mTGColorIndex > 8) mTGColorIndex = 1;


mMouseOverTGObject.Closed = target.Closed;

mMouseOverTGObject.SetDatabaseDefaults();



//Add transient graphics

IntegerCollection col = new IntegerCollection();



TransientManager.CurrentTransientManager.

AddTransient

(

mMouseOverTGObject,

TransientDrawingMode.DirectShortTerm,

128,

col

);

}



private void ModifyMouseOverTG()

{

if (mMouseOverTGObject==null)

{

AddMouseOverTGObject();

}

else

{

Polyline target = GetTargetPolyline();



//reset vertices' coordinate, in case the user

//changed the targeting polyline by dragging

//its grip

for (int i = 0;

i < target.NumberOfVertices - 1; i++)

{

Point2d pt = target.GetPoint2dAt(i);

mMouseOverTGObject.SetPointAt(i, pt);

}



//Set color

mMouseOverTGObject.ColorIndex =

mTGColorIndex;



mTGColorIndex += 1;

if (mTGColorIndex > 8) mTGColorIndex = 1;




//Update transient graphics

IntegerCollection col =

new IntegerCollection();



TransientManager.CurrentTransientManager.

UpdateTransient(mMouseOverTGObject, col);

}

}



private Polyline GetTargetPolyline()

{

Polyline target=null;



using (Transaction tran =

mDB.TransactionManager.

StartOpenCloseTransaction())

{

target = tran.GetObject(

mCurrentMouseOverObject,

OpenMode.ForRead) as Polyline;

}



return target;

}



#endregion

}



// A Dictionary collection to hold transient graphics

// information for each opened document

public class PolylineTGTracks :

Dictionary

{

#region public methods



public void SetMouseOverEntityTransientGraphics

(

Document dwg,

IPolylineTGFilter filter,

bool turnOn

)

{

if (turnOn)

{

if (!this.ContainsKey(dwg))

{

TGInformation tgInfo =

new TGInformation(dwg,filter);



this.Add(dwg, tgInfo);

}



//Start monitor mouse moving and

//do the transient graphic highlight

//when targeted polyline is under

//under the mouse cursor

this[dwg].MouseOverEntityMonitored = true;

}

else

{

if (this.ContainsKey(dwg))

{

//Stop transient graphic effect

this[dwg].MouseOverEntityMonitored = false;

}

}

}



//overload, taking a parameter for

//specifc line width

public void SetMouseOverEntityTransientGraphics

(Document dwg, IPolylineTGFilter filter,

double linewidth, bool turnOn)

{

if (turnOn)

{

if (!this.ContainsKey(dwg))

{



TGInformation tgInfo =

new TGInformation(

dwg, filter,linewidth);



this.Add(dwg, tgInfo);

}

else

{

this[dwg].TGLineWidth = linewidth;

}



this[dwg].MouseOverEntityMonitored = true;

}

else

{

if (this.ContainsKey(dwg))

{



this[dwg].MouseOverEntityMonitored



= false;



}

}

}



#endregion

}



// Interface used as filter to find targeting polyline.

// Implement this interface based on your need.

// For example, you can decide if a polyline is the

// targeting entity based on its layer, layout, XData...

public interface IPolylineTGFilter

{

bool IsTGTarget(ObjectId id);

}



//My implementation of IPolylineTGFilter in

//this example: all polylines are targeted

public class MyPolylineTGFilter : IPolylineTGFilter

{



#region PolylineTGFilter Members



public bool IsTGTarget(ObjectId id)

{

//I could do this



//====================



//Entity ent=GetEntityByObjectId(id);



//if ent.Layer!="Layer1") return false;



//=====================



return true;

}



#endregion

}

}



OK, the abundant comments between code lines should be explanative enough, let take look the result of running this code.

To run the code, start AutoCAD (2009/2010), “Netload” the code, draw a couple of polylines, then enter command “PolyTGOn”. Now make cursor move over/out one of the polylines. You would see a very eye-catching highlight appear along the polyline, when the cursor hangs over the target polyline and dispears when the cursor moves out the polyline. See a short video clip here fore the result

You would also notice the highlighting color changes with the move of the cursor, which makes a strong visual prompt, doesn’t it?

Now, open another new drawing and draw a few polylines. However, if you move the mouse cursor over on those polylines, no visual highlighting shows, until you issue command “PolyTGOn” to this drawing.

Of course, if you now switch back to previous drawing, the transient graphic effect is still there until you issue command “PolyTGOff”.

An obviously possible enhancement to this code would be to allow user to configure MyPolylineTGFilter class, so that the transient graphic effect can appear on desired entities according to user inputs.

Hello, My Readers

From my blog title, you must have already known the main topics of this blog are about AutoCAD related programming.

My 15-year programming experience was started with AutoCAD programming (LISP, of course). It gradually expanded to more general programming that touches all sort of technologies (well, on MS' Windws platform, mostly). Since the employers I have been worked for so far all use AutoCAD (or its vertical products) as one of the main production tools, I always have AutoCAD programming under my interest radar and actually do some interesting development from time to time.

From my both experience of doing line of business programming and AutoCAD programming, I can tell how much this kind of skill set is needed by business and how much one can do and enjoy from doing it.

I'll try to share some interesting topics on latest AutoCAD programming tricks (well, latest, as it turns out, would be only "latest" as I can get. For example, I am using AutoCAD 2009 now, and am not sure when I can touch on AutoCAD 2010...). I am especially interested in integrating MS' new technologies, such as WPF, WCF, WWW, with AutoCAD application development.

I am not a good technical writer at all and may not have that much time available often to sit down and write something. So, I'll try to get something out whenever I run into an interesting topic. Some topics may be originated from various user discussion groups, which I visit routinely and do post there (mostly replies) occasionally.

Followers

About Me

My photo
After graduating from university, I worked as civil engineer for more than 10 years. It was AutoCAD use that led me to the path of computer programming. Although I now do more generic business software development, such as enterprise system, timesheet, billing, web services..., AutoCAD related programming is always interesting me and I still get AutoCAD programming tasks assigned to me from time to time. So, AutoCAD goes, I go.