« February 2008 | Main | April 2008 »
Using a jig to rotate an AutoCAD entity via .NET
An interesting request came in via a previous post followed up by a similar question came in via another post. The original problem was to rotate a rectangular polyline entity around its centre, and be able to continue rotating it afterwards. A few things were interesting to me:
- Rectangles are simply polylines between four points, so have no inherent concept of either a centre point or a rotation angle
- The obvious answer (to me, at least) being to calculate the centre and store the rotation as XData on the polyline
- To modify an entity graphically it makes sense to use a jig
- Most examples show how to use jigs to create new entities, not modify existing ones
So with that in mind, I created the below C# code to solve this problem:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.Geometry;
namespace RotatingRectangles
{
public class Commands
{
// Define some constants we'll use to
// store our XData
// AppName is our RDS (TTIF, for
// "Through The InterFace") plus an indicator
// what it's for (ROTation)
const string kRegAppName = "TTIF_ROT";
const int kAppCode = 1001;
const int kRotCode = 1040;
class RotateJig : EntityJig
{
// Declare some internal state
double m_baseAngle, m_deltaAngle;
Point3d m_rotationPoint;
Matrix3d m_ucs;
// Constructor sets the state and clones
// the entity passed in
// (adequate for simple entities)
public RotateJig(
Entity ent,
Point3d rotationPoint,
double baseAngle,
Matrix3d ucs)
: base(ent.Clone() as Entity)
{
m_rotationPoint = rotationPoint;
m_baseAngle = baseAngle;
m_ucs = ucs;
}
protected override SamplerStatus Sampler(
JigPrompts jp
)
{
// We acquire a single angular value
JigPromptAngleOptions jo =
new JigPromptAngleOptions(
"\nAngle of rotation: "
);
jo.BasePoint = m_rotationPoint;
jo.UseBasePoint = true;
PromptDoubleResult pdr =
jp.AcquireAngle(jo);
if (pdr.Status == PromptStatus.OK)
{
// Check if it has changed or not
// (reduces flicker)
if (m_deltaAngle == pdr.Value)
{
return SamplerStatus.NoChange;
}
else
{
// Set the change in angle to
// the new value
m_deltaAngle = pdr.Value;
return SamplerStatus.OK;
}
}
return SamplerStatus.Cancel;
}
protected override bool Update()
{
// We rotate the polyline by the change
// minus the base angle
Matrix3d trans =
Matrix3d.Rotation(
m_deltaAngle - m_baseAngle,
m_ucs.CoordinateSystem3d.Zaxis,
m_rotationPoint);
Entity.TransformBy(trans);
// The base becomes the previous delta
// and the delta gets set to zero
m_baseAngle = m_deltaAngle;
m_deltaAngle = 0.0;
return true;
}
public Entity GetEntity()
{
return Entity;
}
public double GetRotation()
{
// The overall rotation is the
// base plus the delta
return m_baseAngle + m_deltaAngle;
}
}
[CommandMethod("ROT")]
public void RotateEntity()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
Database db = doc.Database;
// First we prompt for the entity to rotate
PromptEntityOptions peo =
new PromptEntityOptions(
"\nSelect entity to rotate: "
);
PromptEntityResult per =
ed.GetEntity(peo);
if (per.Status == PromptStatus.OK)
{
Transaction tr =
db.TransactionManager.StartTransaction();
using (tr)
{
DBObject obj =
tr.GetObject(per.ObjectId, OpenMode.ForRead);
Entity ent = obj as Entity;
// Use the origin as the default center
Point3d rotationPoint = Point3d.Origin;
// If the entity is a polyline,
// assume it is rectangular and then
// set the rotation point as its center
Polyline pl = obj as Polyline;
if (pl != null)
{
LineSegment3d ps0 =
pl.GetLineSegmentAt(0);
LineSegment3d ps1 =
pl.GetLineSegmentAt(1);
Vector3d vec =
((ps0.EndPoint - ps0.StartPoint) / 2.0) +
((ps1.EndPoint - ps1.StartPoint) / 2.0);
rotationPoint = pl.StartPoint + vec;
}
// Get the base rotation angle stored with the
// entity, if there was one (default is 0.0)
double baseAngle = GetStoredRotation(obj);
if (ent != null)
{
// Get the current UCS, to pass to the Jig
Matrix3d ucs =
ed.CurrentUserCoordinateSystem;
// Create our jig object
RotateJig jig =
new RotateJig(
ent,
rotationPoint,
baseAngle,
ucs
);
PromptResult res = ed.Drag(jig);
if (res.Status == PromptStatus.OK)
{
// Get the overall rotation angle
// and dispose of the temp clone
double newAngle = jig.GetRotation();
jig.GetEntity().Dispose();
// Rotate the original entity
Matrix3d trans =
Matrix3d.Rotation(
newAngle - baseAngle,
ucs.CoordinateSystem3d.Zaxis,
rotationPoint);
ent.UpgradeOpen();
ent.TransformBy(trans);
// Store the new rotation as XData
SetStoredRotation(ent, newAngle);
}
}
tr.Commit();
}
}
}
// Helper function to create a RegApp
static void AddRegAppTableRecord(string regAppName)
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Editor ed = doc.Editor;
Database db = doc.Database;
Transaction tr =
doc.TransactionManager.StartTransaction();
using (tr)
{
RegAppTable rat =
(RegAppTable)tr.GetObject(
db.RegAppTableId,
OpenMode.ForRead,
false
);
if (!rat.Has(regAppName))
{
rat.UpgradeOpen();
RegAppTableRecord ratr =
new RegAppTableRecord();
ratr.Name = regAppName;
rat.Add(ratr);
tr.AddNewlyCreatedDBObject(ratr, true);
}
tr.Commit();
}
}
// Store our rotation angle as XData
private void SetStoredRotation(
DBObject obj, double rotation)
{
AddRegAppTableRecord(kRegAppName);
ResultBuffer rb = obj.XData;
if (rb == null)
{
rb =
new ResultBuffer(
new TypedValue(kAppCode, kRegAppName),
new TypedValue(kRotCode, rotation)
);
}
else
{
// We can simply add our values - no need
// to remove the previous ones, the new ones
// are the ones that get stored
rb.Add(new TypedValue(kAppCode, kRegAppName));
rb.Add(new TypedValue(kRotCode, rotation));
}
obj.XData = rb;
rb.Dispose();
}
// Retrieve the existing rotation angle from XData
private double GetStoredRotation(DBObject obj)
{
double ret = 0.0;
ResultBuffer rb = obj.XData;
if (rb != null)
{
// If we find our group code, it means that on
// the next iteration, we'll get our rotation
bool bReadyForRot = false;
foreach (TypedValue tv in rb)
{
if (bReadyForRot)
{
if (tv.TypeCode == kRotCode)
ret = (double)tv.Value;
bReadyForRot = false;
}
if (tv.TypeCode == kAppCode)
bReadyForRot = true;
}
rb.Dispose();
}
return ret;
}
}
}
To try this out, we can use the RECTANG command, to create a horizontal rectangle, and then use our custom ROT command to rotate it:
Calling the ROT command subsequently works fine, as it "remembers" the angle it was rotated at. If other tools are used to rotate the rectangle, all bets are off. One alternative would be to determine the "rotation" by performing slightly deeper analysis on the rectangle: determining the longer side and getting its angle should do it. It would also be trivial to implement a command to set the rotation to one input by the user (whether by selecting two points or entering it).
March 28, 2008 in AutoCAD, AutoCAD .NET, Jigs | Permalink | Comments (6) | TrackBack
Embedding AutoCAD 2009 in a standalone dialog
This post takes a look at another topic outlined in this overview of the new API features in AutoCAD 2009.
AutoCAD 2009 introduces the ability to embed the application in a standalone dialog or form via an ActiveX control. This capability has been around for a number of releases of AutoCAD OEM, but this feature has now been made available in the main AutoCAD product.
The way the control works is to launch an instance of AutoCAD in the background (it should go without saying that AutoCAD needs to be installed on the system, but I've said it, anyway :-) and it then pipes the graphics generated by AutoCAD into the area specified by the bounds of the control. It also then pipes back any mouse movements or keystrokes, to allow the embedded AutoCAD to be controlled. It's pretty neat: you'll see the standard cursor, be able to enter commands via dynamic input, and more-or-less do whatever can be done inside the full product.
The control is especially handy if you want to present a reduced user-interface to the people using the product (which is really what AutoCAD OEM is for, in a nutshell, although the development effort involved in creating a full AutoCAD OEM application makes it inappropriate for quick & easy UI streamlining).
Let's start our look at this control by creating a new C# Windows Application project in Visual Studio 2005 (you can use whatever ActiveX container you like, though - it should even work from a web-page or an Office document):
Once Visual Studio has created the new project, we need to add our control to the toolbox. If you right-click on the toolbox, you'll be able to select "Choose Items...".
From here, there should be an item "AcCtrl" in the list of COM Components. Otherwise you can browse to it in c:\Program Files\Common Files\Autodesk Shared\AcCtrl.dll.
Then you simply need to place the control on your form.
Once we've done that, we're going to add a few more controls - for the drawing path, and a text string for commands we want to try "posting" to the embedded AutoCAD application.
Here's the C# code we'll use to drive the embedded control from the form. You should be able to work out what the various controls have been called in the project by looking at the code.
using System;
using System.Windows.Forms;
namespace EmbedAutoCAD
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void browseButton_Click(
object sender, EventArgs e)
{
OpenFileDialog dlg =
new OpenFileDialog();
dlg.InitialDirectory =
System.Environment.CurrentDirectory;
dlg.Filter =
"DWG files (*.dwg)|*.dwg|All files (*.*)|*.*";
Cursor oc = Cursor;
String fn = "";
if (dlg.ShowDialog() ==
DialogResult.OK)
{
Cursor = Cursors.WaitCursor;
fn = dlg.FileName;
Refresh();
}
if (fn != "")
this.drawingPath.Text = fn;
Cursor = oc;
}
private void loadButton_Click(
object sender, EventArgs e)
{
if (System.IO.File.Exists(drawingPath.Text))
axAcCtrl1.Src = drawingPath.Text;
else
MessageBox.Show("File does not exist");
}
private void postButton_Click(
object sender, EventArgs e)
{
axAcCtrl1.PostCommand(cmdString.Text);
}
}
}
Finally, when we run the application and load a drawing via the browse/load buttons, the real fun starts. :-)
Try entering commands via dynamic input, or via the "Post a command" textbox. You might feel a little disorientated due to the lack of a command-line (I do love my command-line ;-), but dynamic input allows you to at least see what you're typing.
Here's the C# project for you to download.
March 27, 2008 in AutoCAD, AutoCAD .NET, User interface | Permalink | Comments (0) | TrackBack
Initialization code in your F# AutoCAD application
Back from a nice long weekend, although I spent most of it sick with a cold. I find this increasingly the way with me: I fend off illness for months at a time (probably through stress, truth be told) but then I get a few days off and wham. A shame, as we had a huge dump of snow over the weekend... we get white Christmases here every five years or so, but it's really uncommon to get a white Easter.
I had a very interesting question come in by email from 冷血儿, who wanted to get the technique shown in this post working in his F# application.
Here's the F# code I managed to put together after consulting hubFS, in particular:
#light
namespace MyNamespace
open Autodesk.AutoCAD.Runtime
open Autodesk.AutoCAD.ApplicationServices
type InitTest() =
class
let ed =
Application.DocumentManager.MdiActiveDocument.Editor
interface IExtensionApplication with
member x.Initialize() =
ed.WriteMessage
("\nInitializing - do something useful.")
member x.Terminate() =
printfn "\nCleaning up..."
end
end
module MyApplication =
let ed =
Application.DocumentManager.MdiActiveDocument.Editor
[<CommandMethod("TST")>]
let f () =
ed.WriteMessage("\nThis is the TST command.")
[<assembly: ExtensionApplication(type InitTest)>]
do
ed.WriteMessage("\nModule do")
Here's what happens when we load our module and run the TST command:
Command: NETLOAD
Module do
Initializing - do something useful.
Command: TST
This is the TST command.
March 25, 2008 in AutoCAD, AutoCAD .NET, F#, Notification / Events | Permalink | Comments (0) | TrackBack
Implementing your own AutoCAD color combobox control using .NET
A big thanks to Scott McFarlane, from Geotropix, Inc., for sharing the code in this post. Here's an email I received from Scott:
I was reading this blog entry on “Through the Interface” and some folks were asking about how to implement .NET combo box versions of the color and linetype ActiveX controls that are available. I just wanted to share a simple .NET implementation of a color combo box. The color combo is quite easy, really. The linetype one would be more difficult.
Attached is the source code. This is just a generic color combo, that loads up with the 255 ACI colors. It has no dependency on AutoCAD – I was actually using this in an external program. It would be easy, however, to modify this to provide a list item to launch the built-in AutoCAD color dialog if used inside AutoCAD.
Here is the C# code Scott provided (which was in a source file named AcColorComboBox.cs):
using System;
using System.Collections;
using System.Drawing;
using System.Windows.Forms;
public class AcColorComboBox : ComboBox
{
public class ColorItem
{
private short _colorIndex;
private Color _color;
public ColorItem(short colorIndex, Color color)
{
_colorIndex = colorIndex;
_color = color;
}
public short ColorIndex
{
get { return _colorIndex; }
}
public Color Color
{
get { return _color; }
}
public override string ToString()
{
return AcColorComboBox.ColorNameOf(_colorIndex);
}
}
public class ColorItemSorter : IComparer
{
public int Compare(object x, object y)
{
return ((ColorItem)x).ColorIndex - ((ColorItem)y).ColorIndex;
}
}
private short _colorIndex;
#region " Windows Form Designer generated code "
public AcColorComboBox()
: base()
{
// This call is required by the Windows Form Designer.
InitializeComponent();
// Add any initialization after the InitializeComponent() call
DrawMode = DrawMode.OwnerDrawFixed;
DropDownStyle = ComboBoxStyle.DropDownList;
}
// Override dispose to clean up the component list.
protected override void Dispose(bool disposing)
{
if (disposing)
{
if ((components != null))
{
components.Dispose();
}
}
base.Dispose(disposing);
}
// Required by the Windows Form Designer
private System.ComponentModel.IContainer components;
// NOTE: The following procedure is required by the Windows Form Designer
// It can be modified using the Windows Form Designer.
// Do not modify it using the code editor.
[System.Diagnostics.DebuggerStepThrough()]
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion
protected override void OnDrawItem(System.Windows.Forms.DrawItemEventArgs e)
{
if (e.Index >= 0)
{
e.DrawBackground();
e.DrawFocusRectangle();
Rectangle r = e.Bounds;
r.Inflate(-1, -1);
r.Width = 20;
r.Offset(1, 0);
ColorItem objColor = (ColorItem)Items[e.Index];
e.Graphics.FillRectangle(new System.Drawing.SolidBrush(objColor.Color), r);
e.Graphics.DrawRectangle(new System.Drawing.Pen(Color.Black), r);
e.Graphics.DrawString(objColor.ToString(), e.Font, new SolidBrush(e.ForeColor), e.Bounds.X + r.Width + 4, e.Bounds.Y);
}
}
protected override void OnCreateControl()
{
Items.Clear();
for (short i = 1; i < 256; i++)
Items.Add(new ColorItem(i, ColorOf(i)));
base.OnCreateControl();
}
// ColorValue represents colorId
public short ColorIndex
{
get { return _colorIndex; }
set
{
_colorIndex = value;
foreach (ColorItem objColor in Items)
{
if (objColor.ColorIndex == value)
{
SelectedItem = objColor;
break;
}
}
}
}
protected override void OnSelectedIndexChanged(System.EventArgs e)
{
_colorIndex = ((ColorItem)Items[SelectedIndex]).ColorIndex;
base.OnSelectedIndexChanged(e);
}
public static string ColorNameOf(short colorIndex)
{
switch (colorIndex)
{
case 1:
return "1 - Red";
case 2:
return "2 - Yellow";
case 3:
return "3 - Green";
case 4:
return "4 - Cyan";
case 5:
return "5 - Blue";
case 6:
return "6 - Magenta";
case 7:
return "7 - White";
case 8:
return "8 - Grey";
default:
return colorIndex.ToString();
}
}
public static Color ColorOf(short colorIndex)
{
switch (colorIndex)
{
case 1:
return Color.FromArgb(255, 0, 0);
case 2:
return Color.FromArgb(255, 255, 0);
case 3:
return Color.FromArgb(0, 255, 0);
case 4:
return Color.FromArgb(0, 255, 255);
case 5:
return Color.FromArgb(0, 0, 255);
case 6:
return Color.FromArgb(255, 0, 255);
case 7:
return Color.FromArgb(255, 255, 255);
case 8:
return Color.FromArgb(128, 128, 128);
case 9:
return Color.FromArgb(192, 192, 192);
case 10:
return Color.FromArgb(255, 0, 0);
case 11:
return Color.FromArgb(255, 127, 127);
case 12:
return Color.FromArgb(204, 0, 0);
case 13:
return Color.FromArgb(204, 102, 102);
case 14:
return Color.FromArgb(153, 0, 0);
case 15:
return Color.FromArgb(153, 76, 76);
case 16:
return Color.FromArgb(127, 0, 0);
case 17:
return Color.FromArgb(127, 63, 63);
case 18:
return Color.FromArgb(76, 0, 0);
case 19:
return Color.FromArgb(76, 38, 38);
case 20:
return Color.FromArgb(255, 63, 0);
case 21:
return Color.FromArgb(255, 159, 127);
case 22:
return Color.FromArgb(204, 51, 0);
case 23:
return Color.FromArgb(204, 127, 102);
case 24:
return Color.FromArgb(153, 38, 0);
case 25:
return Color.FromArgb(153, 95, 76);
case 26:
return Color.FromArgb(127, 31, 0);
case 27:
return Color.FromArgb(127, 79, 63);
case 28:
return Color.FromArgb(76, 19, 0);
case 29:
return Color.FromArgb(76, 47, 38);
case 30:
return Color.FromArgb(255, 127, 0);
case 31:
return Color.FromArgb(255, 191, 127);
case 32:
return Color.FromArgb(204, 102, 0);
case 33:
return Color.FromArgb(204, 153, 102);
case 34:
return Color.FromArgb(153, 76, 0);
case 35:
return Color.FromArgb(153, 114, 76);
case 36:
return Color.FromArgb(127, 63, 0);
case 37:
return Color.FromArgb(127, 95, 63);
case 38:
return Color.FromArgb(76, 38, 0);
case 39:
return Color.FromArgb(76, 57, 38);
case 40:
return Color.FromArgb(255, 191, 0);
case 41:
return Color.FromArgb(255, 223, 127);
case 42:
return Color.FromArgb(204, 153, 0);
case 43:
return Color.FromArgb(204, 178, 102);
case 44:
return Color.FromArgb(153, 114, 0);
case 45:
return Color.FromArgb(153, 133, 76);
case 46:
return Color.FromArgb(127, 95, 0);
case 47:
return Color.FromArgb(127, 111, 63);
case 48:
return Color.FromArgb(76, 57, 0);
case 49:
return Color.FromArgb(76, 66, 38);
case 50:
return Color.FromArgb(255, 255, 0);
case 51:
return Color.FromArgb(255, 255, 127);
case 52:
return Color.FromArgb(204, 204, 0);
case 53:
return Color.FromArgb(204, 204, 102);
case 54:
return Color.FromArgb(153, 153, 0);
case 55:
return Color.FromArgb(153, 153, 76);
case 56:
return Color.FromArgb(127, 127, 0);
case 57:
return Color.FromArgb(127, 127, 63);
case 58:
return Color.FromArgb(76, 76, 0);
case 59:
return Color.FromArgb(76, 76, 38);
case 60:
return Color.FromArgb(191, 255, 0);
case 61:
return Color.FromArgb(223, 255, 127);
case 62:
return Color.FromArgb(153, 204, 0);
case 63:
return Color.FromArgb(178, 204, 102);
case 64:
return Color.FromArgb(114, 153, 0);
case 65:
return Color.FromArgb(133, 153, 76);
case 66:
return Color.FromArgb(95, 127, 0);
case 67:
return Color.FromArgb(111, 127, 63);
case 68:
return Color.FromArgb(57, 76, 0);
case 69:
return Color.FromArgb(66, 76, 38);
case 70:
return Color.FromArgb(127, 255, 0);
case 71:
return Color.FromArgb(191, 255, 127);
case 72:
return Color.FromArgb(102, 204, 0);
case 73:
return Color.FromArgb(153, 204, 102);
case 74:
return Color.FromArgb(76, 153, 0);
case 75:
return Color.FromArgb(114, 153, 76);
case 76:
return Color.FromArgb(63, 127, 0);
case 77:
return Color.FromArgb(95, 127, 63);
case 78:
return Color.FromArgb(38, 76, 0);
case 79:
return Color.FromArgb(57, 76, 38);
case 80:
return Color.FromArgb(63, 255, 0);
case 81:
return Color.FromArgb(159, 255, 127);
case 82:
return Color.FromArgb(51, 204, 0);
case 83:
return Color.FromArgb(127, 204, 102);
case 84:
return Color.FromArgb(38, 153, 0);
case 85:
return Color.FromArgb(95, 153, 76);
case 86:
return Color.FromArgb(31, 127, 0);
case 87:
return Color.FromArgb(79, 127, 63);
case 88:
return Color.FromArgb(19, 76, 0);
case 89:
return Color.FromArgb(47, 76, 38);
case 90:
return Color.FromArgb(0, 255, 0);
case 91:
return Color.FromArgb(127, 255, 127);
case 92:
return Color.FromArgb(0, 204, 0);
case 93:
return Color.FromArgb(102, 204, 102);
case 94:
return Color.FromArgb(0, 153, 0);
case 95:
return Color.FromArgb(76, 153, 76);
case 96:
return Color.FromArgb(0, 127, 0);
case 97:
return Color.FromArgb(63, 127, 63);
case 98:
return Color.FromArgb(0, 76, 0);
case 99:
return Color.FromArgb(38, 76, 38);
case 100:
return Color.FromArgb(0, 255, 63);
case 101:
return Color.FromArgb(127, 255, 159);
case 102:
return Color.FromArgb(0, 204, 51);
case 103:
return Color.FromArgb(102, 204, 127);
case 104:
return Color.FromArgb(0, 153, 38);
case 105:
return Color.FromArgb(76, 153, 95);
case 106:
return Color.FromArgb(0, 127, 31);
case 107:
return Color.FromArgb(63, 127, 79);
case 108:
return Color.FromArgb(0, 76, 19);
case 109:
return Color.FromArgb(38, 76, 47);
case 110:
return Color.FromArgb(0, 255, 127);
case 111:
return Color.FromArgb(127, 255, 191);
case 112:
return Color.FromArgb(0, 204, 102);
case 113:
return Color.FromArgb(102, 204, 153);
case 114:
return Color.FromArgb(0, 153, 76);
case 115:
return Color.FromArgb(76, 153, 114);
case 116:
return Color.FromArgb(0, 127, 63);
case 117:
return Color.FromArgb(63, 127, 95);
case 118:
return Color.FromArgb(0, 76, 38);
case 119:
return Color.FromArgb(38, 76, 57);
case 120:
return Color.FromArgb(0, 255, 191);
case 121:
return Color.FromArgb(127, 255, 223);
case 122:
return Color.FromArgb(0, 204, 153);
case 123:
return Color.FromArgb(102, 204, 178);
case 124:
return Color.FromArgb(0, 153, 114);
case 125:
return Color.FromArgb(76, 153, 133);
case 126:
return Color.FromArgb(0, 127, 95);
case 127:
return Color.FromArgb(63, 127, 111);
case 128:
return Color.FromArgb(0, 76, 57);
case 129:
return Color.FromArgb(38, 76, 66);
case 130:
return Color.FromArgb(0, 255, 255);
case 131:
return Color.FromArgb(127, 255, 255);
case 132:
return<

Atom