000

Creating A "Move" Command With .NET API - Take 2

.
In a previous post Creating A "Move" Command With .NET API, I demonstrated how to use Editor.Drag() to build a custom "MOVE" command. It was pretty easy. However, as Jeroen Verdonschot pointed out in his comment, "Ortho" mode does not work within Editor.Drag(). This would be a serious drawback if you want the custom "MOVE" command to be really useful.

As matter of fact, in one of my earliest post here I demonstrated a technique of using Transient Graphics with a video clip. One can see it was almost a nearly finished "MOVE" command. So, I figured, I could just take on the "MOVE" command again and handle the "Ortho on/off" situation.

Here is the class "MyMoveCmd" that does the moving work:

using System;
using System.Collections.Generic;

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

namespace MoveWithTG
{
public class MyMoveCmd
{
private Document _dwg;
private Editor _editor;
private ObjectIdCollection _selectedIds;
private Point3d _basePoint;
private Point3d _currentPoint;
private Point3d _moveToPoint;
private List _tgDrawables;
private bool _pointMonitored = false;

public MyMoveCmd(Document dwg)
{
_dwg=dwg;
_editor=_dwg.Editor;
}

public void DoMove()
{
//Select object and highlight selected
ObjectIdCollection ids = new ObjectIdCollection();

using (Transaction tran = _dwg.Database.
TransactionManager.StartOpenCloseTransaction())
{
if (!GetSelectedEntities(tran, out ids))
{
_editor.WriteMessage("\n*Cancel*");
ids = new ObjectIdCollection();
}

}

if (ids.Count == 0) return;

_selectedIds = ids;

//Calculate base point (lower-left conner of
//bounding box that encloses all selected entities)
_basePoint = GetDefaultBasePoint();

//current dragging point
_currentPoint = _basePoint;

Transaction trans = _dwg.Database.
TransactionManager.StartTransaction();

CreateTransientGraphics(trans);

try
{
_editor.PointMonitor +=
new PointMonitorEventHandler(Editor_PointMonitor);
_pointMonitored = true;

while (true)
{
PromptPointOptions opt =
new PromptPointOptions("\nSelect point to move to: ");
opt.UseBasePoint = true;
opt.BasePoint = _basePoint;
opt.Keywords.Add("Base point");
opt.AppendKeywordsToMessage = true;
PromptPointResult res = _editor.GetPoint(opt);
if (res.Status == PromptStatus.OK)
{
//Get "Move To" point
if (IsOrthModeOn())
{
_moveToPoint = GetOrthoPoint(res.Value);
}
else
{
_moveToPoint = res.Value;
}

MoveEntities(trans);
_editor.WriteMessage("\n{0} moved", _selectedIds.Count);
break;
}
else if (res.Status == PromptStatus.Keyword)
{
//If user choose to pick BasePoint,
//Stop habdling PointMonitor
ClearTransientGraphics();

_editor.PointMonitor -=
new PointMonitorEventHandler(Editor_PointMonitor);
_pointMonitored = false;

Point3d p;
if (!PickBasePoint(out p))
{
_editor.WriteMessage("\n*Cancel*");
break;
}
else
{
//Reset base point and current dragging point
_basePoint = p;
_currentPoint = _basePoint;

//Re-create transient graphics
CreateTransientGraphics(trans);

_editor.PointMonitor +=
new PointMonitorEventHandler(Editor_PointMonitor);
_pointMonitored = true;
}
}
else
{
_editor.WriteMessage("\n*Cancel*");
break;
}
}
}
catch
{
throw;
}
finally
{
ClearHighlight(trans);
ClearTransientGraphics();

if (_pointMonitored)
{
_editor.PointMonitor -=
new PointMonitorEventHandler(Editor_PointMonitor);
}

trans.Commit();
trans.Dispose();
}

}

#region private metods

private bool GetSelectedEntities(Transaction tran, out ObjectIdCollection ids)
{
ids = new ObjectIdCollection();

PromptSelectionResult res = _editor.SelectImplied();
if (res.Status == PromptStatus.OK)
{
foreach (ObjectId id in res.Value.GetObjectIds())
{
ids.Add(id);
HighlightEntity(tran, id, true);
}
}
else
{
while (true)
{
string msg=ids.Count>0?ids.Count + " selected. Pick entity: ":"Pick entity: ";
PromptEntityOptions opt = new PromptEntityOptions("\n" + msg);
opt.AllowNone = true;
PromptEntityResult entRes = _editor.GetEntity(opt);
if (entRes.Status == PromptStatus.OK)
{
bool exists = false;
foreach (ObjectId id in ids)
{
if (id == entRes.ObjectId)
{
exists = true;
break;
}
}

if (!exists)
{
ids.Add(entRes.ObjectId);
HighlightEntity(tran, entRes.ObjectId,true);
}
}
else if (entRes.Status == PromptStatus.None)
{
break;
}
else
{
return false;
}
}
}

return true;
}

private void HighlightEntity(Transaction tran, ObjectId id, bool highlight)
{
Entity ent = (Entity)tran.GetObject(id, OpenMode.ForWrite);
if (highlight)
ent.Highlight();
else
ent.Unhighlight();
}

private Point3d GetDefaultBasePoint()
{
Extents3d exts = new Extents3d(
new Point3d(0.0, 0.0, 0.0), new Point3d(0.0, 0.0, 0.0));

using (Transaction tran =
_dwg.Database.TransactionManager.StartTransaction())
{
for (int i = 0; i < _selectedIds.Count; i++)
{
ObjectId id = _selectedIds[i];
Entity ent = (Entity)tran.GetObject(id, OpenMode.ForRead);

if (i == 0)
{
exts = ent.GeometricExtents;
}
else
{
Extents3d ext = ent.GeometricExtents;
exts.AddExtents(ext);
}
}

tran.Commit();
}

return exts.MinPoint;
}

private bool PickBasePoint(out Point3d pt)
{
pt = new Point3d();

PromptPointOptions opt = new PromptPointOptions("Pick base point: ");
PromptPointResult res = _editor.GetPoint(opt);
if (res.Status == PromptStatus.OK)
{
pt = res.Value;
return true;
}
else
{
return false;
}
}

private bool IsOrthModeOn()
{
object orth = Autodesk.AutoCAD.ApplicationServices.
Application.GetSystemVariable("ORTHOMODE");

return Convert.ToInt32(orth) > 0;
}

private Point3d GetOrthoPoint(Point3d pt)
{
double x=pt.X;
double y=pt.Y;

Vector3d vec = _basePoint.GetVectorTo(pt);
if (Math.Abs(vec.X)>=Math.Abs(vec.Y))
{
y = _basePoint.Y;
}
else
{
x = _basePoint.X;
}

return new Point3d(x, y, 0.0);
}

private void ClearHighlight(Transaction trans)
{
foreach (ObjectId id in _selectedIds)
{
HighlightEntity(trans, id, false);
}
}

private void MoveEntities(Transaction tran)
{
Matrix3d mat = Matrix3d.Displacement(_basePoint.GetVectorTo(_moveToPoint));
foreach (ObjectId id in _selectedIds)
{
Entity ent = (Entity)tran.GetObject(id, OpenMode.ForWrite);
ent.TransformBy(mat);
}
}

#endregion

#region private method: handling PointMonitor and transient graphics

private void Editor_PointMonitor(object sender, PointMonitorEventArgs e)
{
Point3d pt = e.Context.RawPoint;
if (IsOrthModeOn())
{
pt = GetOrthoPoint(pt);
}

UpdateTransientGraphics(pt);

_currentPoint = pt;
}

private void CreateTransientGraphics(Transaction tran)
{
_tgDrawables = new List();

foreach (ObjectId id in _selectedIds)
{
Entity ent = (Entity)tran.GetObject(id, OpenMode.ForRead);

Entity drawable = ent.Clone() as Entity;
drawable.ColorIndex = 1;
_tgDrawables.Add(drawable);
}

foreach (Drawable d in _tgDrawables)
{
TransientManager.CurrentTransientManager.
AddTransient(d, TransientDrawingMode.DirectShortTerm,
128, new IntegerCollection());
}
}

private void UpdateTransientGraphics(Point3d moveToPoint)
{
Matrix3d mat = Matrix3d.Displacement(_currentPoint.GetVectorTo(moveToPoint));
foreach (Drawable d in _tgDrawables)
{
Entity e = d as Entity;
e.TransformBy(mat);

TransientManager.CurrentTransientManager.
UpdateTransient(d, new IntegerCollection());
}
}

private void ClearTransientGraphics()
{
TransientManager.CurrentTransientManager.
EraseTransients(TransientDrawingMode.DirectShortTerm,
128, new IntegerCollection());

foreach (Drawable d in _tgDrawables)
{
d.Dispose();
}

_tgDrawables.Clear();
}

#endregion
}
}

Then the simple command class:

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.Runtime;

[assembly: CommandClass(typeof(MoveWithTG.MyCommands))]

namespace MoveWithTG
{
public class MyCommands
{
[CommandMethod("MyMove",CommandFlags.UsePickSet)]
public static void DoCommand()
{
Document dwg = Autodesk.AutoCAD.ApplicationServices.
Application.DocumentManager.MdiActiveDocument;

MyMoveCmd move = new MyMoveCmd(dwg);
move.DoMove();
}
}
}

The behaviour of the custom "MOVE" command can be seen here.

If you want the selected objects not only move horizontally or vertically when "Ortho" mode is on, but also can be moved along a specific angle, one of my other posts here would be of help.

Note: due to the blog's format, The Generic type cannot be shown in the post correctly. The "List" shown in red should be List(Of Drawable) in VB.NET. It looks like I have to post code in VB.NET if my code uses Generic. Too bad.

Note again: due to the blog's format, the code using Generic in C# cannot be shown here correctly for its angled bracket, I now post the VB.NET code here. In my future post, if there is Generic involved, I'll try post code in VB.NET.

Here is the class "MyMoveCmd" in VB.NET:

Imports System.Collections.Generic

Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.DatabaseServices
Imports Autodesk.AutoCAD.EditorInput
Imports Autodesk.AutoCAD.Geometry
Imports Autodesk.AutoCAD.GraphicsInterface


Public Class MyMoveCmd

Private _dwg As Document
Private _editor As Editor
Private _selectedIds As ObjectIdCollection
Private _basePoint As Point3d
Private _currentPoint As Point3d
Private _moveToPoint As Point3d
Private _tgDrawables As List(Of Drawable)
Private _pointMonitored As Boolean = False

Public Sub New(ByVal dwg As Document)
_dwg = dwg
_editor = _dwg.Editor
End Sub

Public Sub DoMove()
'Select object and highlight selected
Dim ids As New ObjectIdCollection()

Using tran As Transaction = _
_dwg.Database.TransactionManager.StartOpenCloseTransaction()

If Not GetSelectedEntities(tran, ids) Then
_editor.WriteMessage(vbLf & "*Cancel*")
ids = New ObjectIdCollection()

End If
End Using

If ids.Count = 0 Then
Return
End If

_selectedIds = ids

'Calculate base point (lower-left conner of
'bounding box that encloses all selected entities)
_basePoint = GetDefaultBasePoint()

'current dragging point
_currentPoint = _basePoint

Dim trans As Transaction = _
_dwg.Database.TransactionManager.StartTransaction()

CreateTransientGraphics(trans)

Try

AddHandler _editor.PointMonitor, AddressOf Editor_PointMonitor
_pointMonitored = True

While True
Dim opt As New PromptPointOptions(vbLf & "Select point to move to: ")
opt.UseBasePoint = True
opt.BasePoint = _basePoint
opt.Keywords.Add("Base point")
opt.AppendKeywordsToMessage = True
Dim res As PromptPointResult = _editor.GetPoint(opt)
If res.Status = PromptStatus.OK Then
'Get "Move To" point
If IsOrthModeOn() Then
_moveToPoint = GetOrthoPoint(res.Value)
Else
_moveToPoint = res.Value
End If

MoveEntities(trans)
_editor.WriteMessage(vbLf & "{0} moved", _selectedIds.Count)
Exit While
ElseIf res.Status = PromptStatus.Keyword Then
'If user choose to pick BasePoint,
'Stop habdling PointMonitor
ClearTransientGraphics()

RemoveHandler _editor.PointMonitor, AddressOf Editor_PointMonitor
_pointMonitored = False

Dim p As Point3d
If Not PickBasePoint(p) Then
_editor.WriteMessage(vbLf & "*Cancel*")
Exit While
Else
'Reset base point and current dragging point
_basePoint = p
_currentPoint = _basePoint

'Re-create transient graphics
CreateTransientGraphics(trans)

AddHandler _editor.PointMonitor, AddressOf Editor_PointMonitor
_pointMonitored = True
End If
Else
_editor.WriteMessage(vbLf & "*Cancel*")
Exit While
End If
End While
Catch
Throw
Finally
ClearHighlight(trans)
ClearTransientGraphics()

If _pointMonitored Then
RemoveHandler _editor.PointMonitor, AddressOf Editor_PointMonitor
End If

trans.Commit()
trans.Dispose()
End Try

End Sub

#Region "private metods"

Private Function GetSelectedEntities(ByVal tran As Transaction, _
ByRef ids As ObjectIdCollection) As Boolean

ids = New ObjectIdCollection()

Dim res As PromptSelectionResult = _editor.SelectImplied()
If res.Status = PromptStatus.OK Then
For Each id As ObjectId In res.Value.GetObjectIds()
ids.Add(id)
HighlightEntity(tran, id, True)
Next
Else
While True
Dim msg As String = If(ids.Count > 0, _
Convert.ToString(ids.Count) & " selected. Pick entity: ", _
"Pick entity: ")
Dim opt As New PromptEntityOptions(vbLf & msg)
opt.AllowNone = True
Dim entRes As PromptEntityResult = _editor.GetEntity(opt)
If entRes.Status = PromptStatus.OK Then
Dim exists As Boolean = False
For Each id As ObjectId In ids
If id = entRes.ObjectId Then
exists = True
Exit For
End If
Next

If Not exists Then
ids.Add(entRes.ObjectId)
HighlightEntity(tran, entRes.ObjectId, True)
End If
ElseIf entRes.Status = PromptStatus.None Then
Exit While
Else
Return False
End If
End While
End If

Return True
End Function

Private Sub HighlightEntity(ByVal tran As Transaction, _
ByVal id As ObjectId, ByVal highlight As Boolean)

Dim ent As Entity = DirectCast(tran.GetObject(id, OpenMode.ForWrite), Entity)
If highlight Then
ent.Highlight()
Else
ent.Unhighlight()
End If

End Sub

Private Function GetDefaultBasePoint() As Point3d

Dim exts As New Extents3d(New Point3d(0.0, 0.0, 0.0), New Point3d(0.0, 0.0, 0.0))

Using tran As Transaction = _dwg.Database.TransactionManager.StartTransaction()
For i As Integer = 0 To _selectedIds.Count - 1
Dim id As ObjectId = _selectedIds(i)
Dim ent As Entity = DirectCast(tran.GetObject(id, OpenMode.ForRead), Entity)

If i = 0 Then
exts = ent.GeometricExtents
Else
Dim ext As Extents3d = ent.GeometricExtents
exts.AddExtents(ext)
End If
Next

tran.Commit()
End Using

Return exts.MinPoint

End Function

Private Function PickBasePoint(ByRef pt As Point3d) As Boolean

pt = New Point3d()

Dim opt As New PromptPointOptions("Pick base point: ")
Dim res As PromptPointResult = _editor.GetPoint(opt)
If res.Status = PromptStatus.OK Then
pt = res.Value
Return True
Else
Return False
End If

End Function

Private Function IsOrthModeOn() As Boolean

Dim orth As Object = Autodesk.AutoCAD.ApplicationServices. _
Application.GetSystemVariable("ORTHOMODE")

Return Convert.ToInt32(orth) > 0

End Function

Private Function GetOrthoPoint(ByVal pt As Point3d) As Point3d

Dim x As Double = pt.X
Dim y As Double = pt.Y

Dim vec As Vector3d = _basePoint.GetVectorTo(pt)
If Math.Abs(vec.X) >= Math.Abs(vec.Y) Then
y = _basePoint.Y
Else
x = _basePoint.X
End If

Return New Point3d(x, y, 0.0)

End Function

Private Sub ClearHighlight(ByVal trans As Transaction)
For Each id As ObjectId In _selectedIds
HighlightEntity(trans, id, False)
Next
End Sub

Private Sub MoveEntities(ByVal tran As Transaction)

Dim mat As Matrix3d = Matrix3d.Displacement(_basePoint.GetVectorTo(_moveToPoint))
For Each id As ObjectId In _selectedIds
Dim ent As Entity = DirectCast(tran.GetObject(id, OpenMode.ForWrite), Entity)
ent.TransformBy(mat)
Next

End Sub

#End Region

#Region "private method: handling PointMonitor and transient graphics"

Private Sub Editor_PointMonitor(ByVal sender As Object, ByVal e As PointMonitorEventArgs)
Dim pt As Point3d = e.Context.RawPoint
If IsOrthModeOn() Then
pt = GetOrthoPoint(pt)
End If

UpdateTransientGraphics(pt)

_currentPoint = pt
End Sub

Private Sub CreateTransientGraphics(ByVal tran As Transaction)

_tgDrawables = New List(Of Drawable)()

For Each id As ObjectId In _selectedIds
Dim ent As Entity = DirectCast(tran.GetObject(id, OpenMode.ForRead), Entity)

Dim drawable As Entity = TryCast(ent.Clone(), Entity)
drawable.ColorIndex = 1
_tgDrawables.Add(drawable)
Next

For Each d As Drawable In _tgDrawables
TransientManager.CurrentTransientManager.AddTransient( _
d, TransientDrawingMode.DirectShortTerm, _
128, New IntegerCollection())
Next
End Sub

Private Sub UpdateTransientGraphics(ByVal moveToPoint As Point3d)

Dim mat As Matrix3d = Matrix3d.Displacement( _
_currentPoint.GetVectorTo(moveToPoint))

For Each d As Drawable In _tgDrawables
Dim e As Entity = TryCast(d, Entity)
e.TransformBy(mat)

TransientManager.CurrentTransientManager.UpdateTransient( _
d, New IntegerCollection())
Next

End Sub

Private Sub ClearTransientGraphics()

TransientManager.CurrentTransientManager.EraseTransients( _
TransientDrawingMode.DirectShortTerm, _
128, New IntegerCollection())

For Each d As Drawable In _tgDrawables
d.Dispose()
Next

_tgDrawables.Clear()

End Sub

#End Region
End Class

Here is the command class:

Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.Runtime
Imports Autodesk.AutoCAD.EditorInput
Imports Autodesk.AutoCAD.DatabaseServices



Public Class MyCommands

_
Public Shared Sub DoCommand()
Dim dwg As Document = Autodesk.AutoCAD.ApplicationServices. _
Application.DocumentManager.MdiActiveDocument

Dim move As New MyMoveCmd(dwg)
move.DoMove()
End Sub

End Class

Blog Archive