雪山飞狐_lzh 发表于 2010-12-19 18:29:07

一个简单的转置矩阵
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]
查看完整版本: Kean专题(13)—Geometry