September 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        








« Creating a motion-detecting security cam with a Raspberry Pi – Part 2 | Main | Creating a selection filter that finds dynamic blocks in AutoCAD using .NET »

September 06, 2012

Creating reactive, transient AutoCAD geometry using .NET

This is a really interesting topic. At least I think it is – hopefully at least some of you will agree. :-)

The requirement was to create selectable – or at least manipulatable – transient graphics inside AutoCAD’s drawing canvas. As many of you are probably aware, transient graphics are not hooked into AutoCAD’s selection mechanism. This is mostly fine, but if you want to implement a ViewCube-like gizmo that manipulates the view or drawing settings in some way, it’s hard to do so without the ability to react to the current cursor position is and what’s happening with the mouse.

When my esteemed colleague, Christer Janson, first pointed me at the internal C++ protocol extension that allows you to receive Windows messages and point input inside your application, I assumed there was no chance I’d be able to get this working in .NET (as it’s not exposed through the public ObjectARX API). But after some digging and head-scratching, I managed to find how it had been exposed via the .NET API and create a sample that makes use of it.

Here’s the C# code, showing how to use this very interesting mechanism:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.GraphicsInterface;

 

namespace TransientSelection

{

  public class SelectableTransient : Transient

  {

    // Windows messages we care about

 

    const int WM_LBUTTONDOWN = 513;

    const int WM_LBUTTONUP = 514;

 

    // Internal state

 

    Entity _ent = null;

    bool _picked = false, _clicked = false;

 

    public SelectableTransient(Entity ent)

    {

      _ent = ent;

    }

 

    protected override int SubSetAttributes(DrawableTraits traits)

    {

      // If the cursor is over the entity, make it colored

      // (whether it's red or yellow will depend on whether

      // there's a mouse-button click, too)

 

      traits.Color = (short)(_picked ? (_clicked ? 1 : 2) : 0);

 

      return (int)DrawableAttributes.None;

    }

 

    protected override void SubViewportDraw(ViewportDraw vd)

    {

      _ent.ViewportDraw(vd);

    }

 

    protected override bool SubWorldDraw(WorldDraw wd)

    {

      _ent.WorldDraw(wd);

 

      return true;

    }

 

    protected override void OnDeviceInput(DeviceInputEventArgs e)

    {

      bool redraw = false;

 

      if (e.Message == WM_LBUTTONDOWN)

      {

        _clicked = true;

 

        // If we're over the entity, absorb the click

        // (stops the window selection from happening)

 

        if (_picked)

        {

          e.Handled = true;

        }

        redraw = true;

      }

      else if (e.Message == WM_LBUTTONUP)

      {

        _clicked = false;

        redraw = true;

      }

 

      // Only update the graphics if things have changed

 

      if (redraw)

      {

        TransientManager.CurrentTransientManager.UpdateTransient(

          this, new IntegerCollection()

        );

 

        // Force a Windows message, as we may have absorbed the

        // click event (and this also helps when unclicking)

 

        ForceMessage();

      }

 

      base.OnDeviceInput(e);

    }

 

    private void ForceMessage()

    {

      // Set the cursor without ectually moving it - enough to

      // generate a Windows message

 

      System.Drawing.Point pt =

        System.Windows.Forms.Cursor.Position;

      System.Windows.Forms.Cursor.Position =

        new System.Drawing.Point(pt.X, pt.Y);

    }

 

    protected override void OnPointInput(PointInputEventArgs e)

    {

      bool wasPicked = _picked;

 

      _picked = false;

 

      Curve cv = _ent as Curve;

      if (cv != null)

      {

        Point3d pt =

          cv.GetClosestPointTo(e.Context.ComputedPoint, false);

        if (

          pt.DistanceTo(e.Context.ComputedPoint) <= 0.1

          // Tolerance.Global.EqualPoint is too small

        )

        {

          _picked = true;

        }

      }

 

      // Only update the graphics if things have changed

 

      if (_picked != wasPicked)

      {

        TransientManager.CurrentTransientManager.UpdateTransient(

          this, new IntegerCollection()

        );

      }

      base.OnPointInput(e);

    }

  }

 

  public class Commands

  {

    Line _ln = null;

    SelectableTransient _st = null;

 

    [CommandMethod("TRS")]

    public void TransientSelection()

    {

      // Create a line and pass it to the SelectableTransient

      // This makes cleaning up much more straightforward

 

      _ln = new Line(Point3d.Origin, new Point3d(10, 10, 0));

      _st = new SelectableTransient(_ln);

 

      // Tell AutoCAD to call into this transient's extended

      // protocol when appropriate

 

      Transient.CapturedDrawable = _st;

 

      // Go ahead and draw the transient

 

      TransientManager.CurrentTransientManager.AddTransient(

        _st, TransientDrawingMode.DirectShortTerm,

        128, new IntegerCollection()

      );

    }

 

    [CommandMethod("TRU")]

    public void RemoveTransientSelection()

    {

      // Removal is performed by setting to null

 

      Transient.CapturedDrawable = null;

 

      // Erase the transient graphics and dispose of the transient

 

      if (_st != null)

      {

        TransientManager.CurrentTransientManager.EraseTransient(

          _st,

          new IntegerCollection()

        );

        _st.Dispose();

        _st = null;

      }

 

      // And dispose of our line

 

      if (_ln != null)

      {

        _ln.Dispose();

        _ln = null;

      }

    }

  }

}

When you run the TRS command, you’ll see a line between the origin and the point 10,10. This is transient geometry, although not as we know it. ;-)

Our transient line

When you move your cursor across the line (to within 0.1 of a drawing unit, which I chose as the standard geometric tolerance was way too small to be usable when dealing with point input in this way) it should turn yellow:

Which turns yellow when hovered over

And when you click the left-button of the mouse, it should turn red:

And red when clicked

In itself this isn’t anything very impressive, but it provides the underpinnings to create something much more so. You’re essentially now able to create a transient gizmo that can react to mouse movement and button clicking, and in turn manipulate state in your AutoCAD session, in some way.

I’ve provided a TRU command to remove graphics and dispose of everything properly. You would probably want to take care of all this in your own application initialization and termination code, of course.

Incidentally there are optimisations that are probably worth making to this particular implementation: for instance, as this mechanism could end up being used by users throughout their typical drawing session – much in the way the ViewCube is – I’d suggest checking the cursor position against an area of the drawing that you hold in memory, to stop always checking it against the geometry itself (as I’ve done in this sample). I’m sure you’ll find out other ways to optimise the use of this mechanism as you work through the specifics of your own implementation.

blog comments powered by Disqus

Feed/Share

10 Random Posts