Author Topic: Jigs and Polar Tracking  (Read 213 times)

0 Members and 1 Guest are viewing this topic.

cmwade77

  • Swamp Rat
  • Posts: 1443
Jigs and Polar Tracking
« on: March 13, 2024, 07:17:15 PM »
In the code below, when the jig starts, if polar tracking is on, it will try to track from the last point as set in the AutoCAD lastpoint variable, this is not how other AutoCAD commands behave.

Code - C#: [Select]
  1. public static class JigUtils
  2. {
  3.     public static double Atan(double y, double x)
  4.     {
  5.         if (x > 0)
  6.             return Math.Atan(y / x);
  7.         else if (x < 0)
  8.             return Math.Atan(y / x) + Math.PI;
  9.         else // x == 0
  10.         {
  11.             if (y > 0)
  12.                 return Math.PI / 2;
  13.             else if (y < 0)
  14.                 return -Math.PI / 2;
  15.             else // y == 0, theta is undefined
  16.                 return 0.0;
  17.         }
  18.     }
  19.  
  20.     public static double ComputeAngle(Point3d startPoint, Point3d endPoint, Vector3d xdir, Matrix3d ucs)
  21.     {
  22.         Vector3d v = new Vector3d((endPoint.X - startPoint.X) / 2, (endPoint.Y - startPoint.Y) / 2, (endPoint.Z - startPoint.Z) / 2);
  23.         double cos = v.DotProduct(xdir);
  24.         double sin = v.DotProduct(Vector3d.ZAxis.TransformBy(ucs).CrossProduct(xdir));
  25.         return Atan(sin, cos);
  26.     }
  27. }
  28.  
  29.  
  30. public class BulgePolyJig : EntityJig
  31. {
  32.     public Stack<Point3d> _pointHistory = new Stack<Point3d>();
  33.     public Point3d _tempPoint;
  34.     Plane _plane;
  35.     bool _isArcSeg = true; // Arc is the default
  36.     bool _isUndoing = false;
  37.     Matrix3d _ucs;
  38.     Polyline FlexLine;
  39.  
  40.     public BulgePolyJig(Matrix3d ucs) : base(new Polyline())
  41.     {
  42.         _ucs = ucs;
  43.         CoordinateSystem3d cs = ucs.CoordinateSystem3d;
  44.         Vector3d normal = cs.Zaxis;
  45.         _plane = new Plane(Point3d.Origin, normal);
  46.  
  47.         Polyline pline = Entity as Polyline;
  48.         pline.SetDatabaseDefaults();
  49.         pline.Normal = normal;            
  50.  
  51.         Point3d closest = cs.Origin.Project(_plane, normal);
  52.         Vector3d disp = closest - cs.Origin;
  53.  
  54.         pline.Elevation = disp.IsCodirectionalTo(normal) ? -disp.Length : disp.Length;
  55.  
  56.         AddDummyVertex();
  57.     }
  58.  
  59.     protected override SamplerStatus Sampler(JigPrompts prompts)
  60.     {
  61.         JigPromptPointOptions jigOpts = new JigPromptPointOptions();
  62.  
  63.         jigOpts.UserInputControls = UserInputControls.Accept3dCoordinates | UserInputControls.NullResponseAccepted | UserInputControls.NoNegativeResponseAccepted | UserInputControls.GovernedByOrthoMode;
  64.  
  65.         _isUndoing = false;
  66.  
  67.         Polyline pline = Entity as Polyline;
  68.  
  69.         if (pline.NumberOfVertices == 1)
  70.         {
  71.             jigOpts.Message = "\nSpecify start point: ";
  72.         }
  73.         else if (pline.NumberOfVertices > 1)
  74.         {
  75.             string msgAndKwds = _isArcSeg ? "\nSpecify endpoint of arc or [Line/Undo]: " : "\nSpecify next point or [Arc/Undo]: ";
  76.             string kwds = _isArcSeg ? "Line Undo" : "Arc Undo";
  77.             jigOpts.SetMessageAndKeywords(msgAndKwds, kwds);
  78.         }
  79.         else
  80.             return SamplerStatus.Cancel; // Should never happen
  81.  
  82.         PromptPointResult res = prompts.AcquirePoint(jigOpts);
  83.  
  84.         if (res.Status == PromptStatus.Keyword)
  85.         {
  86.             if (res.StringResult.Equals("ARC", StringComparison.CurrentCultureIgnoreCase))
  87.                 _isArcSeg = true;
  88.             else if (string.Equals(res.StringResult, "LINE", StringComparison.OrdinalIgnoreCase))
  89.                 _isArcSeg = false;
  90.             else if (string.Equals(res.StringResult, "UNDO", StringComparison.OrdinalIgnoreCase))
  91.                 _isUndoing = true;
  92.  
  93.             return SamplerStatus.OK;
  94.         }
  95.         else if (res.Status == PromptStatus.OK)
  96.         {
  97.             if (_tempPoint == res.Value)
  98.                 return SamplerStatus.NoChange;
  99.             else
  100.             {
  101.                 _tempPoint = res.Value;
  102.                 _pointHistory.Push(_tempPoint); // Store the point
  103.                
  104.                 return SamplerStatus.OK;
  105.             }
  106.         }
  107.  
  108.         return SamplerStatus.Cancel;
  109.     }
  110.  
  111.     protected override bool Update()
  112.     {
  113.         Polyline pl = Entity as Polyline;
  114.         FlexLine = pl;
  115.         double width = (double)GlobalVariables.MainWindow.Dim_Width.Value; // Get the desired width
  116.         // Check if we're setting the first real point
  117.         if (pl.NumberOfVertices == 1 && _tempPoint != Point3d.Origin)
  118.         {
  119.             pl.SetPointAt(0, _tempPoint.Convert2d(_plane));
  120.             pl.SetStartWidthAt(0, width);
  121.             pl.SetEndWidthAt(0, width);
  122.         }
  123.         else if (_isArcSeg && pl.NumberOfVertices >= 2)
  124.         {
  125.             // Safe to assume there are at least 2 vertices here
  126.             Point3d lastVertex = pl.GetPoint3dAt(pl.NumberOfVertices - 2);
  127.             Vector3d refDir;
  128.  
  129.             // Determine reference direction for calculating bulge
  130.             if (pl.NumberOfVertices < 3)
  131.             {
  132.                 refDir = new Vector3d(1.0, 1.0, 0.0);
  133.             }
  134.             else
  135.             {
  136.                 double bulge = pl.GetBulgeAt(pl.NumberOfVertices - 3);
  137.                 if (bulge != 0)
  138.                 {
  139.                     CircularArc3d arcSegment = pl.GetArcSegmentAt(pl.NumberOfVertices - 3);
  140.                     Line3d tangent = arcSegment.GetTangent(lastVertex);
  141.                     refDir = tangent.Direction.MultiplyBy(-1.0);
  142.                 }
  143.                 else
  144.                 {
  145.                     Point3d ptBeforeLast = pl.GetPoint3dAt(pl.NumberOfVertices - 3);
  146.                     refDir = lastVertex - ptBeforeLast;
  147.                 }
  148.             }
  149.  
  150.             double angle = JigUtils.ComputeAngle(lastVertex, _tempPoint, refDir, _ucs);
  151.             double bulgeValue = Math.Tan(angle * 0.5);
  152.             pl.SetBulgeAt(pl.NumberOfVertices - 2, bulgeValue);
  153.         }
  154.         else if (!_isArcSeg && pl.NumberOfVertices >= 2)
  155.         {
  156.             // For line segments, just ensure the last bulge is set to 0
  157.             if (pl.NumberOfVertices > 1)
  158.             {
  159.                 pl.SetBulgeAt(pl.NumberOfVertices - 2, 0);
  160.             }
  161.         }
  162.  
  163.         // Always update the last vertex position
  164.         pl.SetPointAt(pl.NumberOfVertices - 1, _tempPoint.Convert2d(_plane));
  165.  
  166.         return true;
  167.     }
  168.  
  169.  
  170.     public bool IsUndoing
  171.     {
  172.         get
  173.         {
  174.             return _isUndoing;
  175.         }
  176.     }
  177.  
  178.     public void AddDummyVertex()
  179.     {
  180.         Polyline pline = Entity as Polyline;
  181.         double width = (double)GlobalVariables.MainWindow.Dim_Width.Value; // Assuming this is accessible here
  182.  
  183.         // Add a vertex with the specified width. If this is the first vertex, it doesn't form a segment yet,
  184.         // so the width will apply to the segment formed when the next vertex is added.
  185.         pline.AddVertexAt(pline.NumberOfVertices, new Point2d(0, 0), 0, width, width);
  186.     }
  187.  
  188.  
  189.     public void RemoveLastVertex()
  190.     {
  191.         Document doc = Application.DocumentManager.MdiActiveDocument;
  192.         Editor ed = doc.Editor;
  193.         Polyline pline = Entity as Polyline;
  194.  
  195.         if (pline.NumberOfVertices > 0)
  196.         {
  197.             // Remove the last vertex
  198.             pline.RemoveVertexAt(pline.NumberOfVertices - 1);
  199.         }
  200.  
  201.         // After removal, check if there are enough vertices to determine the last point
  202.         if (pline.NumberOfVertices >= 2)
  203.         {
  204.             // Get the endpoint of the new last segment
  205.             Point3d newLastEndPoint = pline.GetPoint3dAt(pline.NumberOfVertices - 2);
  206.  
  207.             // Update LASTPOINT system variable
  208.             Application.SetSystemVariable("LASTPOINT", newLastEndPoint);
  209.         }
  210.         else
  211.         {
  212.             // If no vertices are left, consider how you want to handle LASTPOINT.
  213.             // You might leave it as it is or reset it to some default value.
  214.             // This example resets it to the origin, but you might choose a different approach.
  215.             Application.SetSystemVariable("LASTPOINT", Point3d.Origin);
  216.         }
  217.  
  218.         // Adjust arc segment flag based on the last segment's bulge, if applicable.
  219.         if (pline.NumberOfVertices >= 2)
  220.         {
  221.             double blg = pline.GetBulgeAt(pline.NumberOfVertices - 2);
  222.             _isArcSeg = (blg != 0);
  223.         }
  224.     }
  225.  
  226.     private void EnsureFlexLinetypeLoaded(Document doc)
  227.     {
  228.         Database db = doc.Database;
  229.         Editor ed = doc.Editor;
  230.  
  231.         using (Transaction tr = db.TransactionManager.StartTransaction())
  232.         {
  233.             LinetypeTable ltTable = (LinetypeTable)tr.GetObject(db.LinetypeTableId, OpenMode.ForRead);
  234.  
  235.             // Check if "FLEX" linetype is loaded
  236.             if (!ltTable.Has("FLEX"))
  237.             {
  238.                 // Determine the path to the linetype file (.lin)
  239.                 string assemblyLocation = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
  240.                 string linFilePath = System.IO.Path.Combine(assemblyLocation, @"Linetypes\Flex.lin");
  241.  
  242.                 // Load the linetype from the file
  243.                 try
  244.                 {
  245.                     db.LoadLineTypeFile("FLEX", linFilePath);
  246.                     ed.WriteMessage("\nLoaded 'FLEX' linetype from file.");
  247.                 }
  248.                 catch (System.Exception ex)
  249.                 {
  250.                     ed.WriteMessage($"\nError loading 'FLEX' linetype: {ex.Message}");
  251.                 }
  252.             }
  253.             else
  254.             {
  255.                 ed.WriteMessage("\n'FLEX' linetype is already loaded.");
  256.             }
  257.  
  258.             tr.Commit();
  259.         }
  260.     }
  261.     public void MakeFlex()
  262.     {
  263.         SetPolylineLinetypeToFlex(Application.DocumentManager.MdiActiveDocument, FlexLine);          
  264.     }
  265.     private void SetPolylineLinetypeToFlex(Document doc, Polyline pline)
  266.     {
  267.         string objLayer;
  268.         Database db = doc.Database;
  269.         using (Transaction tr = db.TransactionManager.StartTransaction())
  270.         {
  271.             LinetypeTable ltTable = (LinetypeTable)tr.GetObject(db.LinetypeTableId, OpenMode.ForRead);
  272.  
  273.             // Ensure the "FLEX" linetype is available
  274.             EnsureFlexLinetypeLoaded(doc);
  275.  
  276.             // Open the LinetypeTable for read
  277.             LinetypeTableRecord ltr = (LinetypeTableRecord)tr.GetObject(ltTable["FLEX"], OpenMode.ForRead);
  278.  
  279.             // Set the polyline's linetype to "FLEX"
  280.             pline.LinetypeId = ltr.ObjectId;
  281.  
  282.             using (LayerTable lt = tr.GetObject(db.LayerTableId, OpenMode.ForRead) as LayerTable)
  283.             {
  284.                 Main main = GlobalVariables.MainWindow;
  285.                 objLayer = main.Layer.Text;
  286.                 if (lt.Has(objLayer))
  287.                 {
  288.                     pline.Layer = objLayer;
  289.                 }
  290.             }
  291.  
  292.             tr.Commit();
  293.         }
  294.     }
  295.  
  296.  
  297.     public void DisposeEntity()
  298.     {
  299.         this.Entity.Dispose();
  300.     }
  301.  
  302.  
  303.     public void Append()
  304.     {
  305.         Database db = HostApplicationServices.WorkingDatabase;
  306.         using (Transaction tr = db.TransactionManager.StartTransaction())
  307.         {
  308.             BlockTable bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable;
  309.             BlockTableRecord btr = tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForWrite) as BlockTableRecord;
  310.  
  311.             btr.AppendEntity(this.Entity);
  312.             tr.AddNewlyCreatedDBObject(this.Entity, true);
  313.             tr.Commit();
  314.         }
  315.     }
  316. }


And this is the relevant part of my code that runs this jig:
Code - C#: [Select]
  1.                   Document doc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
  2.                   Database db = doc.Database;
  3.                   Editor ed = doc.Editor;
  4.  
  5.                   BulgePolyJig jig = new BulgePolyJig(ed.CurrentUserCoordinateSystem);
  6.  
  7.                   while (true)
  8.                   {
  9.                       PromptResult res = ed.Drag(jig);
  10.  
  11.                       switch (res.Status)
  12.                       {
  13.                           case PromptStatus.OK:
  14.                               jig.AddDummyVertex();
  15.                               break;
  16.  
  17.                           case PromptStatus.Keyword:
  18.                               if (jig.IsUndoing)
  19.                               {
  20.                                   jig.RemoveLastVertex();
  21.                                                                      
  22.                               }
  23.                               break;
  24.  
  25.                           case PromptStatus.None:
  26.                               jig.RemoveLastVertex(); // Clean up last dummy vertex
  27.                               // This is the point where you know the polyline is complete
  28.                               // Set the linetype to "FLEX" here
  29.                               jig.MakeFlex();
  30.  
  31.                               jig.Append(); // Append to the database
  32.                               return;
  33.  
  34.                           default:
  35.                               jig.DisposeEntity(); // Dispose of the entity properly
  36.                               return;
  37.                       }
  38.                   }

I have other jigs that have the same issue, but I am hoping that once I figure out a solution here, I can apply that to my other jigs as well.

JohnK

  • Administrator
  • Seagull
  • Posts: 10623
Re: Jigs and Polar Tracking
« Reply #1 on: March 14, 2024, 08:43:27 PM »
Please forgive me because I am not good at all with C# (don't know much if at all really), so this reply is more on the generalities of programming. I just happened to see a portion of your code while passing by.

1. Shouldn't you use a `switch` statement? I briefly looked up the switch statement in C# and I think I understand enough to write some basic code to convey my point.

2. I don't understand how C# deals with function overloading in your Atan function but you should probably name that function differently -i.e. prefix with the namespace or program name.

I did not check your math logic in your `atan` function but just typed enough to convey my point about using a switch statement (fixing [glaring] math logic bombs is up to you).

3. Functions with multiple exit points is a stylistic thing but can sometimes be a pain. You can eliminate the multiple exits with #1 above and improve readablilty.

Here is my very crude attempt at writing some C# code.
Code - C#: [Select]
  1. public readonly struct Point {
  2.   /* NOTE: grabbed from MS help on switch statement but I understand the concept and I need it for my demo */
  3.   public Point(double x, double y) => (X, Y) = (x, y);
  4.  
  5.   public double X { get; }
  6.   public double Y { get; }
  7. }
  8.  
  9. public static double atan(Point point) => point switch {
  10.         { X: > 0 } => Math.Atan(point.Y / point.X),             /* X > 0 */
  11.         { X: < 0 } => Math.Atan(point.Y / point.X) + Math.PI,   /* X < 0 */
  12.         { X: 0, Y: > 0 } => Math.PI / 2,                        /* x = 0 and y > 0 */
  13.         { X: 0, Y: < 0 } => -Math.PI / 2,
  14.         _ => 0.0,                                               /* discard pattern */
  15. };
  16.  
  17. ...
  18. Point pt = new Point(1, 1);
  19. Console.WriteLine(atan(pt));
  20. ...
TheSwamp.org (serving the CAD community since 2003)
Member location map - Add yourself

Donate to TheSwamp.org

cmwade77

  • Swamp Rat
  • Posts: 1443
Re: Jigs and Polar Tracking
« Reply #2 on: March 14, 2024, 09:50:28 PM »
Oh, this is definitely a work in progress, I have my own process of getting there in the end, but valid points. I just don't understand why I am getting the Ortho Issue