Kean Walmsley


  • About the Author
    Kean on Google+

April 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      







« Handling DPI scaling in your .NET application’s forms | Main | Adding ortho support to the transient graphics AutoCAD MOVE using .NET »

March 14, 2011

Using transient graphics to simulate AutoCAD’s MOVE command using .NET

A big thanks to Norman Yuan for the technique shown in this post. I stumbled across Norman’s post on this topic, and decided to take his code and rework it for posting here, with Norman’s permission, of course. Very interesting stuff! :-)

First things first: you probably don’t have a burning need to reinvent either the wheel or AutoCAD’s MOVE command. That said, this is a technique that may be of interest to many of you, especially if – for whatever reason – you’re looking for an alternative to using a jig. The technique Norman has shown uses a combination of a PointMonitor and AutoCAD’s transient graphics API (available since AutoCAD 2009), to effectively draw a set of entities transiently at the cursor as you move it across the screen.

From a purist’s perspective, I’d almost always use a jig for this – as in many ways this is the kind of low-level behaviour a DrawJig encapsulates, for instance – but then I’m also aware that there are always strange edge-cases where people want more control, or just want to do something downright strange. This technique is another tool in your box that may be of assistance in those situations, and is certainly not lacking in ingenuity.

My main changes to Norman’s original code were to remove the use of member state – which also meant shifting away from a member variable for the PointMonitor event handler to using a local delegate with access to variables in scope when the handler was added – as well as removing some manual techniques for selection & highlighting. If you’re interested, please do head across to Norman’s blog to check out his original implementation.

Here’s the C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.GraphicsInterface;

using Autodesk.AutoCAD.Runtime;

using System.Collections.Generic;

using System;

 

namespace CustomMove

{

  public class Commands

  {

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

    public static void CustomMoveCmd()

    {

      Document doc =

        Autodesk.AutoCAD.ApplicationServices.

          Application.DocumentManager.MdiActiveDocument;

      Editor ed = doc.Editor;

 

      // Start by getting the objects to move

      // (will use the pickfirst set, if defined)

 

      PromptSelectionResult psr = ed.GetSelection();

      if (psr.Status != PromptStatus.OK || psr.Value.Count == 0)

        return;

 

      // Create a collection of the selected objects' IDs

 

      ObjectIdCollection ids =

        new ObjectIdCollection(psr.Value.GetObjectIds());

 

      // Ask the user to select a base point for the move

 

      PromptPointResult ppr =

        ed.GetPoint("\nSpecify base point: ");

      if (ppr.Status != PromptStatus.OK)

        return;

 

      Point3d basePt = ppr.Value;

      Point3d curPt = basePt;

 

      // A local delegate for our event handler so

      // we can remove it at the end

 

      PointMonitorEventHandler handler = null;

 

      // Our transaction

 

      Transaction tr =

        doc.Database.TransactionManager.StartTransaction();

      using (tr)

      {

        // Create our transient drawables, with associated

        // graphics, from the selected objects

 

        List<Drawable> drawables = CreateTransGraphics(tr, ids);

        try

        {

          // Add our point monitor

          // (as a delegate we have access to basePt and curPt,

          //  which avoids having to access global/member state)

 

          handler =

            delegate(object sender, PointMonitorEventArgs e)

            {

              // Get the point, with "ortho" applied, if needed

 

              Point3d pt = e.Context.RawPoint;

              if (IsOrthModeOn())

                pt = GetOrthoPoint(basePt, pt);

 

              // Update our graphics and the current point

 

              UpdateTransGraphics(drawables, curPt, pt);

              curPt = pt;

            };

 

          ed.PointMonitor += handler;

 

          // Ask for the destination, during which the point

          // monitor will be updating the transient graphics

 

          PromptPointOptions opt =

            new PromptPointOptions("\nSpecify second point: ");

          opt.UseBasePoint = true;

          opt.BasePoint = basePt;

          ppr = ed.GetPoint(opt);

 

          // If the point was selected successfully...

 

          if (ppr.Status == PromptStatus.OK)

          {

            // ... move the entities to their destination

 

            MoveEntities(

              tr, basePt,

              IsOrthModeOn() ?

                GetOrthoPoint(basePt, ppr.Value) :

                ppr.Value,

              ids

            );

 

            // And inform the user

 

            ed.WriteMessage(

              "\n{0} object{1} moved", ids.Count,

              ids.Count == 1 ? "" : "s"

            );

          }

        }

        catch (Autodesk.AutoCAD.Runtime.Exception ex)

        {

          ed.WriteMessage("\nException: {0}", ex.Message);

        }

        finally

        {

          // Clear any transient graphics

 

          ClearTransGraphics(drawables);

 

          // Remove the event handler

 

          if (handler != null)

            ed.PointMonitor -= handler;

 

          tr.Commit();

          tr.Dispose();

        }

      }

    }

 

    private static bool IsOrthModeOn()

    {

      // Check the value of the ORTHOMODE sysvar

 

      object orth =

        Autodesk.AutoCAD.ApplicationServices.Application.

        GetSystemVariable("ORTHOMODE");

 

      return Convert.ToInt32(orth) > 0;

    }

 

    private static Point3d GetOrthoPoint(

      Point3d basePt, Point3d pt

    )

    {

      // Apply a crude orthographic mode

 

      double x = pt.X;

      double y = pt.Y;

 

      Vector3d vec = basePt.GetVectorTo(pt);

      if (Math.Abs(vec.X) >= Math.Abs(vec.Y))

        y = basePt.Y;

      else

        x = basePt.X;

 

      return new Point3d(x, y, 0.0);

    }

 

    private static void MoveEntities(

      Transaction tr, Point3d basePt, Point3d moveTo,

      ObjectIdCollection ids

    )

    {

      // Transform a set of entities to a new location

 

      Matrix3d mat =

        Matrix3d.Displacement(basePt.GetVectorTo(moveTo));

      foreach (ObjectId id in ids)

      {

        Entity ent = (Entity)tr.GetObject(id, OpenMode.ForWrite);

        ent.TransformBy(mat);

      }

    }

 

    private static List<Drawable> CreateTransGraphics(

      Transaction tr, ObjectIdCollection ids

    )

    {

      // Create our list of drawables to return

 

      List<Drawable> drawables = new List<Drawable>();

 

      foreach (ObjectId id in ids)

      {

        // Read each entity

 

        Entity ent = (Entity)tr.GetObject(id, OpenMode.ForRead);

 

        // Clone it, make it red & add the clone to the list

 

        Entity drawable = ent.Clone() as Entity;

        drawable.ColorIndex = 1;

        drawables.Add(drawable);

      }

 

      // Draw each one initially

 

      foreach (Drawable d in drawables)

      {

        TransientManager.CurrentTransientManager.AddTransient(

          d, TransientDrawingMode.DirectShortTerm,

          128, new IntegerCollection()

        );

      }

      return drawables;

    }

 

    private static void UpdateTransGraphics(

      List<Drawable> drawables, Point3d curPt, Point3d moveToPt

    )

    {

      // Displace each of our drawables

 

      Matrix3d mat =

        Matrix3d.Displacement(curPt.GetVectorTo(moveToPt));

 

      // Update their graphics

 

      foreach (Drawable d in drawables)

      {

        Entity e = d as Entity;

        e.TransformBy(mat);

 

        TransientManager.CurrentTransientManager.UpdateTransient(

          d, new IntegerCollection()

        );

      }

    }

 

    private static void ClearTransGraphics(

      List<Drawable> drawables

    )

    {

      // Clear the transient graphics for our drawables

 

      TransientManager.CurrentTransientManager.EraseTransients(

        TransientDrawingMode.DirectShortTerm,

        128, new IntegerCollection()

      );

 

      // Dispose of them and clear the list

 

      foreach (Drawable d in drawables)

      {

        d.Dispose();

      }

      drawables.Clear();

    }

  }

}

When you run the MYMOVE command, selecting some geometry to move, you’ll be prompted for a base point and destination:

Custom move command in action

You should see the selected geometry in red – unless you’re moving complex such as block references, in which case it depends on whether the contents are picking up the colour of the reference, or not – until you cancel the command or select the destination.

One issue that I deliberately chose not to address, for now, is that of ORTHOMODE support: the above implementation provides fairly crude orthographic drawing support, but I did notice more work is needed for it to work properly in 3D with a non-World UCS. This is probably something I’ll look at addressing in a follow-up post (if Norman doesn’t end up posting a fix here in the meantime :-).

Thanks again, Norman, for this interesting technique!

blog comments powered by Disqus

10 Random Posts