December 2014

Sun Mon Tue Wed Thu Fri Sat
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      










« Arrived in München | Main | Did not arrive in London »

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 0 5
0 2 0 0 * 5
0 0 2 0 0
0 0 0 1 1

Now if we follow the rules of matrix multiplication, we can see that our resultant point is calculated like this:

a b c d r a*r + b*s + c*t + d*u
e f g h * s = e*r + f*s + g*t + h*u
i j k l t i*r + j*s + k*t + l*u
n o p q u n*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 0 5 10 + 0 + 0 + 0
0 2 0 0 * 5 = 0 + 10 + 0 + 0
0 0 2 0 0 0 + 0 + 0 + 0
0 0 0 1 1 0 + 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

  {

    [CommandMethod("TRANS", CommandFlags.UsePickSet)]

    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[0];

      }

 

      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[cells.Length];

        for (int i = 0; i < cells.Length; i++)

        {

          data[i] = double.Parse(cells[i]);

        }

 

        // 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:

Our entities to transform

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:

Our transformed entities

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.

blog comments powered by Disqus

Feed/Share

10 Random Posts