November 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            










« Querying for XCLIP information inside AutoCAD using .NET | Main | Creating a face-recognising security cam with a Raspberry Pi – Part 4 »

October 08, 2012

Animating transient AutoCAD graphics using .NET

A developer had an interesting requirement that I thought I’d spend some time looking at: to animate transient graphics inside AutoCAD according to data they’ve pulled in from an external simulation system.

It’s clear that AutoCAD is really not an animation platform – we have other products that are better suited to working in this way – but I thought it would be interesting to see what was possible.

I decided to take the implementation shown in this previous post and throw in some code to animate a few different things:

  • Change the per-vertex colours of our transient box
    • These are set to purely random values, but you could imagine setting them in a coherent way to animate some kind of flow across a mesh
  • Display a number of points in a 3D volume
    • These should be rendered as filled circles – to provide some level of size – rather than just relying on the Polypoint() method

We’re also displaying some screen-fixed text at the bottom left, as that was in the previous project and it seemed helpful to leave it in.

The update of the data – which would be pulled in from an external system, but in our case is just randomly generated – is performed when AutoCAD is idle. This event isn’t fired as often as it might be, so I’ve also added some code to force processing of Windows messages (much in the same way as I did with the Kinect jig implementation). This helps the messages – and events – to flow more smoothly, resulting in a smoother animation.

Here’s the C# code to do this:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.GraphicsInterface;

using Autodesk.AutoCAD.Colors;

using System.Collections.Generic;

 

namespace TransientSelection

{

  public class TransientBox : Transient

  {

    // Internal state

 

    private TextStyle _style;

    Point3dCollection _verts;

    IntegerCollection _faces;

    EdgeData _edgeData;

    FaceData _faceData;

    VertexData _vertData;

 

    Point3dCollection _specks;

 

    public TransientBox(double side)

    {

      // Create the style for our text

 

      _style = new TextStyle();

      _style.Font =

        new FontDescriptor("Calibri", false, true, 0, 0);

      _style.TextSize = 10;

 

      // Add our vertices manually (8 of them for a box)

 

      _verts = new Point3dCollection();

 

      _verts.Add(Point3d.Origin);                // 0

      _verts.Add(new Point3d(side, 0, 0));       // 1

      _verts.Add(new Point3d(side, side, 0));    // 2

      _verts.Add(new Point3d(0, side, 0));       // 3

 

      _verts.Add(new Point3d(0, 0, side));       // 4

      _verts.Add(new Point3d(side, 0, side));    // 5

      _verts.Add(new Point3d(side, side, side)); // 6

      _verts.Add(new Point3d(0, side, side));    // 7

 

      // Our faces are defined in sets of 3 vertices

      // (listed below two per line, each making a square face)

 

      int[] polys =

      {

        0, 1, 2, 2, 3, 0, // Bottom

        0, 4, 5, 5, 1, 0, // Front

        1, 5, 6, 6, 2, 1, // Right

        2, 6, 7, 7, 3, 2, // Back

        0, 3, 7, 7, 4, 0, // Left

        4, 7, 6, 6, 5, 4  // Top

      };

      const int polySize = 3;

 

      // Create our faces from the polys array

 

      _faces = new IntegerCollection();

      for (int p = 0; p < (polys.Length / polySize); p++)

      {

        _faces.Add(polySize);

        for (int v = 0; v < polySize; v++)

        {

          _faces.Add(polys[p * polySize + v]);

        }

      }

 

      // Create our edge data

 

      _edgeData = new EdgeData();

 

      // Each face's edges should have the same colour as the face

 

      _edgeData.SetColors(

        new short[]

        {

          1, 1, 1, 2, 2, 2, 3, 3, 3,

          4, 4, 4, 5, 5, 5, 6, 6, 6,

          7, 7, 7, 8, 8, 8, 9, 9, 9,

          10, 10, 10, 11, 11, 11, 12, 12, 12

        }

      );

 

      // Create our face data

 

      _faceData = new FaceData();

 

      // The face colours match their edges

 

      _faceData.SetColors(

        new short[]

        {

          1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12

        }

      );

 

      // Create our vertex data

 

      _vertData = new VertexData();

      _vertData.OrientationFlag = OrientationType.Clockwise;

 

      _specks = new Point3dCollection();

    }

 

    private void GenerateSpecksAndColours()

    {

      // Generate "specks" in a volume of 10 x 10 x 100

 

      const int length = 100, width = 10, height = 10;

 

      // We'll add an offset to put them away from the origin

 

      Vector3d offset = new Vector3d(0, 50, 0);

 

      // Generate a thousand random specs in our 3D volume

 

      System.Random ran = new System.Random();

 

      _specks.Clear();

      for (int i = 0; i < 1000; i++)

      {

        _specks.Add(

          new Point3d(

            ran.NextDouble() * length,

            ran.NextDouble() * width,

            ran.NextDouble() * height

          ) + offset

        );

      }

 

      // Generate a list of eight random colors,

      // one for each corner of our cube

 

      List<EntityColor> vertCols = new List<EntityColor>(8);

      for (int i = 0; i < 8; i++)

      {

        vertCols.Add(

          new EntityColor(ColorMethod.ByAci, (short)ran.Next(12))

        );

      }

 

      // Set the

      _vertData.SetTrueColors(vertCols.ToArray());

    }

 

    protected override int SubSetAttributes(DrawableTraits traits)

    {

      // Let's make our circles filled

 

      traits.FillType = FillType.FillAlways;

 

      return (int)DrawableAttributes.None;

    }

 

    protected override void SubViewportDraw(ViewportDraw vd)

    {

      // Draw our screen-fixed text

 

      DrawText(vd.Geometry, "ViewportDraw");

    }

 

    protected override bool SubWorldDraw(WorldDraw wd)

    {

      // Draw our box

 

      wd.Geometry.Shell(

        _verts, _faces, _edgeData, _faceData, _vertData, false

      );

 

      GenerateSpecksAndColours();

 

      // Display our specks as circles

      // (filled via SubSetAttributes())

 

      foreach (Point3d pt in _specks)

      {

        wd.Geometry.Circle(pt, 0.1, Vector3d.YAxis);

      }

 

      // We might choose to display our "point cloud" with a

      // single pixel per point using Polypoint()

 

      // wd.Geometry.Polypoint(_specks, null, null);

 

      ForceMessage();

 

      // Draw our screen-fixed text

 

      DrawText(wd.Geometry, "WorldDraw");

      return true;

    }

 

    private void DrawText(Geometry g, string text)

    {

      // We make use of another interface to push our transforms

 

      if (g != null)

      {

        // Push our transforms onto the stack

 

        g.PushOrientationTransform(OrientationBehavior.Screen);

 

        g.PushPositionTransform(

          PositionBehavior.Screen,

          new Point2d(30, 30)

        );

 

        // Draw our screen-fixed text

 

        g.Text(

          new Point3d(0, 0, 0),  // Position

          new Vector3d(0, 0, 1), // Normal

          new Vector3d(1, 0, 0), // Direction

          text,                  // Text

          true,                  // Rawness

          _style                 // TextStyle

        );

 

        // Remember to pop our transforms off the stack

 

        g.PopModelTransform();

        g.PopModelTransform();

      }

    }

 

    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 OnDeviceInput(DeviceInputEventArgs e)

    {

      base.OnDeviceInput(e);

    }

 

    protected override void OnPointInput(PointInputEventArgs e)

    {

      base.OnPointInput(e);

    }

  }

 

  public class Commands

  {

    TransientBox _tb = null;

 

    [CommandMethod("TB")]

    public void TransientBox()

    {

      _tb = new TransientBox(10);

 

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

      // protocol when appropriate

 

      Transient.CapturedDrawable = _tb;

 

      // Go ahead and draw the transient

 

      TransientManager.CurrentTransientManager.AddTransient(

        _tb, TransientDrawingMode.Main,

        128, new IntegerCollection()

      );

 

      Application.Idle += new System.EventHandler(OnIdle);

    }

 

    void OnIdle(object sender, System.EventArgs e)

    {

      TransientManager.CurrentTransientManager.UpdateTransient(

        _tb, new IntegerCollection()

      );

    }

 

    [CommandMethod("TBR")]

    public void RemoveTransientBox()

    {

      // Erase the transient graphics and dispose of the transient

 

      if (_tb != null)

      {

        Application.Idle -= new System.EventHandler(OnIdle);

 

        TransientManager.CurrentTransientManager.EraseTransient(

          _tb,

          new IntegerCollection()

        );

        _tb.Dispose();

        _tb = null;

      }

    }

  }

}

Here’s the code in action:

Transient animation

blog comments powered by Disqus

Feed/Share

10 Random Posts