Kean Walmsley


  • About the Author
    Kean on Google+

July 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    








« Back from India | Main | A new blog on AutoCAD Civil 3D development »

April 07, 2011

Jigging an AutoCAD hatch using .NET

This question came in a few weeks ago, which I thought worth addressing:

Hi, Kean! I have a question - how can we create hatch during jig?

The code in this post endeavours to do just that, but – be warned – isn’t fully successful. The problem I chose to interpret this as is “how do you jig the creation of a polyline, using it as the boundary for an associative hatch?”, and it’s not an easy one to solve.

I started by taking Philippe Leefsma’s code from this previous post, which jigs a polyline (including arc segments). I switched it to use a DrawJig – rather than an EntityJig – as we’re creating multiple entities (a polyline and a hatch). I created the objects we want to jig, adding them to the database and the transaction before passing them to the jig’s constructor – an approach that has proven successful for jigging objects in a certain way, whether a Solid3d with a visual style, or a BlockReference with attributes.

Here’s the C# code:

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Geometry;

using Autodesk.AutoCAD.Runtime;

using Autodesk.AutoCAD.Colors;

using System;

 

namespace HatchJig

{

  class JigUtils

  {

    // Custom ArcTangent method, as the Math.Atan

    // doesn't handle specific cases

 

    public static double Atan(double y, double x)

    {

      if (x > 0)

        return Math.Atan(y / x);

      else if (x < 0)

        return Math.Atan(y / x) - Math.PI;

      else  // x == 0

      {

        if (y > 0)

          return Math.PI;

        else if (y < 0)

          return -Math.PI;

        else // if (y == 0) theta is undefined

          return 0.0;

      }

    }

 

    // Computes Angle between current direction

    // (vector from last vertex to current vertex)

    // and the last pline segment

 

    public static double ComputeAngle(

      Point3d startPoint, Point3d endPoint,

      Vector3d xdir, Matrix3d ucs

    )

    {

      Vector3d v =

        new Vector3d(

          (endPoint.X - startPoint.X) / 2,

          (endPoint.Y - startPoint.Y) / 2,

          (endPoint.Z - startPoint.Z) / 2

        );

 

      double cos = v.DotProduct(xdir);

      double sin =

        v.DotProduct(

          Vector3d.ZAxis.TransformBy(ucs).CrossProduct(xdir)

        );

 

      return Atan(sin, cos);

    }

  }

 

  public class HatchJig : DrawJig

  {

    Point3d _tempPoint;

    bool _isArcSeg = false;

    bool _isUndoing = false;

    Matrix3d _ucs;

    Plane _plane;

    Polyline _pline = null;

    Hatch _hat = null;

 

    public HatchJig(

      Matrix3d ucs, Plane plane, Polyline pl, Hatch hat

    )

    {

      _ucs = ucs;

      _plane = plane;

      _pline = pl;

      _hat = hat;

      AddDummyVertex();

    }

 

    protected override bool WorldDraw(

      Autodesk.AutoCAD.GraphicsInterface.WorldDraw wd

    )

    {

      // Update the dummy vertex to be our 3D point

      // projected onto our plane

 

      if (_isArcSeg)

      {

        Point3d lastVertex =

          _pline.GetPoint3dAt(_pline.NumberOfVertices - 2);

 

        Vector3d refDir;

 

        if (_pline.NumberOfVertices < 3)

          refDir = new Vector3d(1.0, 1.0, 0.0);

        else

        {

          // Check bulge to see if last segment was an arc or a line

 

          if (_pline.GetBulgeAt(_pline.NumberOfVertices - 3) != 0)

          {

            CircularArc3d arcSegment =

              _pline.GetArcSegmentAt(_pline.NumberOfVertices - 3);

 

            Line3d tangent = arcSegment.GetTangent(lastVertex);

 

            // Reference direction is the invert of the arc tangent

            // at last vertex

 

            refDir = tangent.Direction.MultiplyBy(-1.0);

          }

          else

          {

            Point3d pt =

              _pline.GetPoint3dAt(_pline.NumberOfVertices - 3);

 

            refDir =

              new Vector3d(

                lastVertex.X - pt.X,

                lastVertex.Y - pt.Y,

                lastVertex.Z - pt.Z

              );

          }

        }

 

        double angle =

          JigUtils.ComputeAngle(

            lastVertex, _tempPoint, refDir, _ucs

          );

 

        // Bulge is defined as tan of one fourth of included angle

        // Need to double the angle since it represents the included

        // angle of the arc

        // So formula is: bulge = Tan(angle * 2 * 0.25)

 

        double bulge = Math.Tan(angle * 0.5);

 

        _pline.SetBulgeAt(_pline.NumberOfVertices - 2, bulge);

      }

      else

      {

        // Line mode. Need to remove last bulge if there was one

 

        if (_pline.NumberOfVertices > 1)

          _pline.SetBulgeAt(_pline.NumberOfVertices - 2, 0);

      }

 

      _pline.SetPointAt(

        _pline.NumberOfVertices - 1, _tempPoint.Convert2d(_plane)

      );

 

      if (_pline.NumberOfVertices == 3)

      {

        _pline.Closed = true;

 

        ObjectIdCollection ids = new ObjectIdCollection();

        ids.Add(_pline.ObjectId);

 

        // Add the hatch loops and complete the hatch

 

        _hat.Associative = true;

        _hat.AppendLoop(HatchLoopTypes.Default, ids);

      }

 

      if (!wd.RegenAbort)

      {

        wd.Geometry.Draw(_pline);

 

        if (_pline.NumberOfVertices > 2)

        {

          _hat.EvaluateHatch(true);

          if (!wd.RegenAbort)

            wd.Geometry.Draw(_hat);

        }

      }

 

      return true;

    }

 

    protected override SamplerStatus Sampler(JigPrompts prompts)

    {

      JigPromptPointOptions jigOpts = new JigPromptPointOptions();

 

      jigOpts.UserInputControls =

        (UserInputControls.Accept3dCoordinates |

         UserInputControls.NullResponseAccepted |

         UserInputControls.NoNegativeResponseAccepted |

         UserInputControls.GovernedByOrthoMode);

 

      _isUndoing = false;

 

      if (_pline.NumberOfVertices == 1)

      {

        // For the first vertex, just ask for the point

 

        jigOpts.Message = "\nSpecify start point: ";

      }

      else if (_pline.NumberOfVertices > 1)

      {

        string msgAndKwds =

          (_isArcSeg ?

            "\nSpecify endpoint of arc or [Line/Undo]: " :

            "\nSpecify next point or [Arc/Undo]: "

          );

 

        string kwds = (_isArcSeg ? "Line Undo" : "Arc Undo");

 

        jigOpts.SetMessageAndKeywords(msgAndKwds, kwds);

      }

      else

        return SamplerStatus.Cancel; // Should never happen

 

      // Get the point itself

 

      PromptPointResult res = prompts.AcquirePoint(jigOpts);

 

      if (res.Status == PromptStatus.Keyword)

      {

        if (res.StringResult.ToUpper() == "ARC")

          _isArcSeg = true;

        else if (res.StringResult.ToUpper() == "LINE")

          _isArcSeg = false;

        else if (res.StringResult.ToUpper() == "UNDO")

          _isUndoing = true;

 

        return SamplerStatus.OK;

      }

      else if (res.Status == PromptStatus.OK)

      {

        // Check if it has changed or not (reduces flicker)

 

        if (_tempPoint == res.Value)

          return SamplerStatus.NoChange;

        else

        {

          _tempPoint = res.Value;

          return SamplerStatus.OK;

        }

      }

 

      return SamplerStatus.Cancel;

    }

 

    public bool IsUndoing

    {

      get

      {

        return _isUndoing;

      }

    }

 

    public void AddDummyVertex()

    {

      // Create a new dummy vertex... can have any initial value

 

      _pline.AddVertexAt(

        _pline.NumberOfVertices, new Point2d(0, 0), 0, 0, 0

      );

    }

 

    public void RemoveLastVertex()

    {

      // Let's first remove our dummy vertex   

 

      if (_pline.NumberOfVertices > 0)

        _pline.RemoveVertexAt(_pline.NumberOfVertices - 1);

 

      // And then check the type of the last segment

 

      if (_pline.NumberOfVertices >= 2)

      {

        double blg = _pline.GetBulgeAt(_pline.NumberOfVertices - 2);

        _isArcSeg = (blg != 0);

      }

    }

 

    [CommandMethod("HATJIG")]

    public static void RunHatchJig()

    {

      Document doc =

        Application.DocumentManager.MdiActiveDocument;

      Database db = doc.Database;

      Editor ed = doc.Editor;

 

      // Create a transaction, as we're jigging

      // db-resident objects

 

      Transaction tr =

        db.TransactionManager.StartTransaction();

      using (tr)

      {

        BlockTableRecord btr =

          (BlockTableRecord)tr.GetObject(

            db.CurrentSpaceId, OpenMode.ForWrite

          );

 

        Vector3d normal =

          Vector3d.ZAxis.TransformBy(

            ed.CurrentUserCoordinateSystem

          );

 

        // We will pass a plane to our jig, to help

        // with UCS transformations

 

        Plane plane = new Plane(Point3d.Origin, normal);

 

        // We also pass a db-resident polyline

 

        Polyline pl = new Polyline();

        pl.Normal = normal;

 

        btr.AppendEntity(pl);

        tr.AddNewlyCreatedDBObject(pl, true);

 

        // And a db-resident hatch

 

        Hatch hat = new Hatch();

 

        // Use a non-solid hatch pattern, to aid jigging

 

        hat.SetHatchPattern(

          HatchPatternType.PreDefined,

          "ANGLE"

        );

 

        // But let's make it transparent, for fun

        // Alpha value is Truncate(255 * (100-n)/100)

 

        hat.ColorIndex = 1;

        hat.Transparency = new Transparency(127);

 

        // Add the hatch to the modelspace & transaction

 

        ObjectId hatId = btr.AppendEntity(hat);

        tr.AddNewlyCreatedDBObject(hat, true);

 

        // And finally pass everything to the jig

 

        HatchJig jig =

          new HatchJig(

            ed.CurrentUserCoordinateSystem, plane, pl, hat

          );

 

        while (true)

        {

          PromptResult res = ed.Drag(jig);

 

          switch (res.Status)

          {

            // New point was added, keep going

 

            case PromptStatus.OK:

              jig.AddDummyVertex();

              break;

 

            // Keyword was entered

 

            case PromptStatus.Keyword:

              if (jig.IsUndoing)

                jig.RemoveLastVertex();

              break;

 

            // The jig completed successfully

 

            case PromptStatus.None:

 

              // You can remove this next line if you want

              // the vertex being jigged to be included

 

              jig.RemoveLastVertex();

              tr.Commit();

              return;

 

            // User cancelled the command

 

            default:

              // No need to erase the polyline & hatch, as

              // the transaction will simply be aborted

              return;

          }

        }

      }

    }

  }

} 

Overall the code works, but the graphics refresh has some issues. The HATJIG command starts well enough:

Jigging our hatch - starts well

But as we move the cursor around, there is unfortunate ghosting of the hatch graphics:

Jigging our hatch - ugly in the middle

Even as we pick new boundary points the hatch gets shown but doesn’t stop being displayed as the boundary changes. It seems as though the transient graphics sub-system – which is being fed the graphics from the DrawJig’s WorldDraw() method – seems to think more needs to be displayed than is actually the case.

At least when the jig is completed the polyline and hatch are displayed properly:

Jigging our hatch - but ends well

There may be other approaches to solve this – such as using the transient graphics API directly – but I suspect that the nature of the hatch implementation will make it hard to rely on its evaluation and display work effectively when called transiently, whether via a jig or directly.

I decided to post this anyway, in case any readers have bright ideas on how either to fix this implementation or to solve it differently. Someone may even have implemented a solution they don’t mind sharing. Anyone?

Update:

Thanks to input from Holger Rasch, this post shows how to make this work properly.

blog comments powered by Disqus

10 Random Posts