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

0 Members and 1 Guest are viewing this topic.

• 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.
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.
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.
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.                 {
246.                     ed.WriteMessage("\nLoaded 'FLEX' linetype from file.");
247.                 }
248.                 catch (System.Exception ex)
249.                 {
251.                 }
252.             }
253.             else
254.             {
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
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);
313.             tr.Commit();
314.         }
315.     }
316. }

And this is the relevant part of my code that runs this jig:
Code - C#: [Select]
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:
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

• 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