December 16, 2010
A simple command to perform a matrix transformation on an AutoCAD entity using .NET
As promised in the last post and based on the overwhelming feedback in the one before that,today we’re starting a series on how to transform AutoCAD geometry.
Before developing a fancy modeless GUI to make this really easy, we need a base command that can do the hard work. What’s needed from our basic command is the following:
Get a single entity from the pickfirst set (which will help us when calling the command from our modeless UI)
If there isn’t one selected, ask the user for it
Get the property name to transform
Only for writeable Point3d and Vector3d properties
The list of valid properties will be populated in our GUI, so we shouldn’t need much validation
If none is entered, we just transform the whole entity
Get the matrix contents as a comma-delimited string
We’ll then decompose it into the 16 doubles required to define a Matrix3d
Transform the property (or the whole entity) by the provided matrix
We will use Reflection to get and set the Point3d/Vector3d property value
To understand some of the underlying concepts, let’s talk a little about transformation matrices.
We need 4 x 4 matrices when working in 3D space to allow us to perform a full range of transformations: translation, rotation, scaling, mirroring and projection. We could achieve some of these using 3 x 3 matrices, but some of these – particular translation, but probably some of the others (I’m not 100% certain of the specifics) – need the additional cells.
We’ll be looking into different transformation matrix types in more detail when we have a simple UI to play around with them, but for now let’s focus on a simple scaling matrix.
2 0 0 0
0 2 0 0
0 0 2 0
0 0 0 1
When we apply this transformation to an entity, it is basically used to multiply the relevant properties (and basically scales them by a factor of 2).
Let’s see what that means by applying this scaling transformation to the 3D point (5, 5, 0), which could be the centre point of a circle (for instance). We need to add a unit entry (1) to the point, to make it compatible with a 4 x 4 matrix.
2 0 0 05
0 2 0 0 * 5
0 0 2 00
0 0 0 11
Now if we follow the rules of matrix multiplication, we can see that our resultant point is calculated like this:
a b c dra*r + b*s + c*t + d*u
e f g h * s = e*r + f*s + g*t + h*u
i j k lti*r + j*s + k*t + l*u
n o p qun*r + o*s + p*t + q*u
This page has a nice graphical representation of multiplying a matrix with a vector.
Which means for us, specifically:
2 0 0 0510 + 0 + 0 + 0
0 2 0 0 * 5 = 0 + 10 + 0 + 0
0 0 2 000 + 0 + 0 + 0
0 0 0 110 + 0 + 0 + 1
And so our transformed point – which is the top three values of the resultant 4-cell matrix – is (10, 10, 0).
Now let’s see the C# code to transform an entity by a user-specified matrix:
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using System.Reflection;
namespace Transformer
{
public class Commands
{
static public void TransformEntity()
{
Document doc =
Application.DocumentManager.MdiActiveDocument;
Database db = doc.Database;
Editor ed = doc.Editor;
// Our selected entity (only one supported, for now)
ObjectId id;
// First query the pickfirst selection set
PromptSelectionResult psr = ed.SelectImplied();
if (psr.Status != PromptStatus.OK || psr.Value == null)
{
// If nothing selected, ask the user
PromptEntityOptions peo =
new PromptEntityOptions(
"\nSelect entity to transform: "
);
PromptEntityResult per = ed.GetEntity(peo);
if (per.Status != PromptStatus.OK)
return;
id = per.ObjectId;
}
else
{
// If the pickfirst set has one entry, take it
SelectionSet ss = psr.Value;
if (ss.Count != 1)
{
ed.WriteMessage(
"\nThis command works on a single entity."
);
return;
}
ObjectId[] ids = ss.GetObjectIds();
id = ids;
}
PromptResult pr = ed.GetString("\nEnter property name: ");
if (pr.Status != PromptStatus.OK)
return;
string prop = pr.StringResult;
// Now let's ask for the matrix string
pr = ed.GetString("\nEnter matrix values: ");
if (pr.Status != PromptStatus.OK)
return;
// Split the string into its individual cells
string[] cells = pr.StringResult.Split(new char[] { ',' });
if (cells.Length != 16)
{
ed.WriteMessage("\nMust contain 16 entries.");
return;
}
try
{
// Convert the array of strings into one of doubles
double[] data = new double;
for (int i = 0; i < cells.Length; i++)
{
data = double.Parse(cells);
}
// Create a 3D matrix from our cell data
Matrix3d mat = new Matrix3d(data);
// Now we can transform the selected entity
Transaction tr =
doc.TransactionManager.StartTransaction();
using (tr)
{
Entity ent =
tr.GetObject(id, OpenMode.ForWrite)
as Entity;
if (ent != null)
{
bool transformed = false;
// If the user specified a property to modify
if (!string.IsNullOrEmpty(prop))
{
// Query the property's value
object val =
ent.GetType().InvokeMember(
prop, BindingFlags.GetProperty, null, ent, null
);
// We only know how to transform points and vectors
if (val is Point3d)
{
// Cast and transform the point result
Point3d pt = (Point3d)val,
res = pt.TransformBy(mat);
// Set it back on the selected object
ent.GetType().InvokeMember(
prop, BindingFlags.SetProperty, null,
ent, new object[] { res }
);
transformed = true;
}
else if (val is Vector3d)
{
// Cast and transform the vector result
Vector3d vec = (Vector3d)val,
res = vec.TransformBy(mat);
// Set it back on the selected object
ent.GetType().InvokeMember(
prop, BindingFlags.SetProperty, null,
ent, new object[] { res }
);
transformed = true;
}
}
// If we didn't transform a property,
// do the whole object
if (!transformed)
ent.TransformBy(mat);
}
tr.Commit();
}
}
catch (Autodesk.AutoCAD.Runtime.Exception ex)
{
ed.WriteMessage(
"\nCould not transform entity: {0}", ex.Message
);
}
}
}
}
Now let’s use the TRANS command to transform a couple of entities:
We’ll use TRANS to apply the above scaling transformation matrix to the whole circle and then to the EndPoint of the line:
Command: TRANS
Select entity to transform: <selected the circle>
Enter property name:
Enter matrix values: 2,0,0,0,0,2,0,0,0,0,2,0,0,0,0,1
Command: TRANS
Select entity to transform: <selected the line>
Enter property name: EndPoint
Enter matrix values: 2,0,0,0,0,2,0,0,0,0,2,0,0,0,0,1
With these results:
I understand this is quite a tricky topic, so I’d appreciate your feedback: does this initial explanation help, at all? Does the level of detail work for you?
In the coming posts we’ll be looking at more complex transformation matrices – and using a GUI to play around with them – but hopefully this introductory post is a reasonably helpful start.
页:
1
[2]