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.

1 comment:

Dang D. Khanh said...

Hi sir, please reupload video to demonstrate result!

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.