Author Topic: Laying a 3D Solid Flat to XY Plane  (Read 712 times)

0 Members and 1 Guest are viewing this topic.

Jonesy97

  • Mosquito
  • Posts: 4
Laying a 3D Solid Flat to XY Plane
« on: February 29, 2024, 07:43:26 AM »
Hi all,

I'm trying to take a 3D solid on any orientation, and lie it's largest face flat to the XY plane.

I have got as far as working out which face is largest using Brep.Faces and .GetArea.

My current thinking is to try and to calculate the angle of the normal vector of this face, and then rotate the 3d solid by this angle, but I can't quite figure out how to get this angle.

Any pointers would be much appreciated, or if you can think of a different way to achieve this. I've put my code so far below.

Thanks

Code - C#: [Select]
  1.       public void Test()
  2.       {
  3.                 Document doc = ACAD_APP.DocumentManager.MdiActiveDocument;
  4.                 Editor ed = doc.Editor;
  5.                 Database db = HostApplicationServices.WorkingDatabase;
  6.  
  7.                 using (DocumentLock docLoc = doc.LockDocument())
  8.                 {
  9.  
  10.                     PromptEntityOptions peo = new PromptEntityOptions("Select 3D solid.");
  11.                     peo.SetRejectMessage("\nInvalid selection...");
  12.                     peo.AddAllowedClass(typeof(Solid3d), true);
  13.  
  14.                     PromptEntityResult per = ed.GetEntity(peo);
  15.  
  16.                     if (per.Status != PromptStatus.OK)
  17.                                         {
  18.                                                 return;
  19.                                         }
  20.                                         else
  21.                                         {
  22.                                                 using (Transaction trans = db.TransactionManager.StartTransaction())
  23.                                                 {
  24.                                                         Solid3d solid = trans.GetObject(per.ObjectId, OpenMode.ForRead) as Solid3d;
  25.                                                         ObjectId[] ids = new ObjectId[] { per.ObjectId };
  26.  
  27.                                                         FullSubentityPath path = new FullSubentityPath(ids, new SubentityId(SubentityType.Null, IntPtr.Zero));
  28.  
  29.                                                         using (Brep brep = new Brep(path))
  30.                                                         {
  31.                                                                 var faceAreaList = new List<double>();
  32.                                                                 foreach (Autodesk.AutoCAD.BoundaryRepresentation.Face face in brep.Faces)
  33.                                                                 {
  34.                                                                          faceAreaList.Add(face.GetArea());
  35.                                                                        
  36.                                                                 }
  37.                                                                 faceAreaList.Sort();
  38.  
  39.                                                                 foreach (Autodesk.AutoCAD.BoundaryRepresentation.Face face in brep.Faces)
  40.                                                                 {
  41.                                                                         if (face.GetArea() == faceAreaList[0])
  42.                                                                         {
  43.                                                                                  Autodesk.AutoCAD.Geometry.Surface surf = face.Surface;
  44.                                                                                  ExternalBoundedSurface ebSurf = surf as ExternalBoundedSurface;
  45.  
  46.                                                                          }
  47.  
  48.                                                                 }
  49.  
  50.                                                        }
  51.                                                 }
  52.                                         }
  53.                  }
  54.         }
  55.  

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8690
  • AKA Daniel
Re: Laying a 3D Solid Flat to XY Plane
« Reply #1 on: February 29, 2024, 08:29:33 AM »
I think you have to dill down to get the edges for the faces.
Edges have a Curve property, or you can get the vertexes, which have a point property
From the edges or vertexes you’ll want to get the direction of the edges, then you can cross product to get the normal.

I had a sample for doing cuboids, but I lost it.  With cuboids that have a common thickness, I.e. a door,  you can map out which face you want to use

Just add, you’ll want to make sure that your normal isn’t pointing towards the inside the solid

« Last Edit: February 29, 2024, 08:36:15 AM by It's Alive! »

Jonesy97

  • Mosquito
  • Posts: 4
Re: Laying a 3D Solid Flat to XY Plane
« Reply #2 on: February 29, 2024, 09:05:55 AM »
Update: I think I've managed to grab the normal vector of the largest face now, just not sure how to rotate the part by this vector yet.

Code - C#: [Select]
  1.       public void Test()
  2.       {
  3.                 Document doc = ACAD_APP.DocumentManager.MdiActiveDocument;
  4.                 Editor ed = doc.Editor;
  5.                 Database db = HostApplicationServices.WorkingDatabase;
  6.  
  7.                 using (DocumentLock docLoc = doc.LockDocument())
  8.                 {
  9.  
  10.                     PromptEntityOptions peo = new PromptEntityOptions("Select 3D solid.");
  11.                     peo.SetRejectMessage("\nInvalid selection...");
  12.                     peo.AddAllowedClass(typeof(Solid3d), true);
  13.  
  14.                     PromptEntityResult per = ed.GetEntity(peo);
  15.  
  16.                     if (per.Status != PromptStatus.OK)
  17.                                         {
  18.                                                 return;
  19.                                         }
  20.                                         else
  21.                                         {
  22.                                                 using (Transaction trans = db.TransactionManager.StartTransaction())
  23.                                                 {
  24.                                                         Solid3d solid = trans.GetObject(per.ObjectId, OpenMode.ForRead) as Solid3d;
  25.                                                         ObjectId[] ids = new ObjectId[] { per.ObjectId };
  26.  
  27.                                                         FullSubentityPath path = new FullSubentityPath(ids, new SubentityId(SubentityType.Null, IntPtr.Zero));
  28.  
  29.                                                         using (Brep brep = new Brep(path))
  30.                                                         {
  31.                                                                 var faceAreaList = new List<double>();
  32.                                                                 foreach (Autodesk.AutoCAD.BoundaryRepresentation.Face face in brep.Faces)
  33.                                                                 {
  34.                                                                          faceAreaList.Add(face.GetArea());
  35.                                                                        
  36.                                                                 }
  37.                                                                 faceAreaList.Sort();
  38.  
  39.                                                                 foreach (Autodesk.AutoCAD.BoundaryRepresentation.Face face in brep.Faces)
  40.                                                                 {
  41.                                                                         if (face.GetArea() == faceAreaList[0])
  42.                                                                         {
  43.                                                                                  Autodesk.AutoCAD.Geometry.Surface surf = face.Surface;
  44.                                                                                  
  45.                                                                                 Interval[] box = surf.GetEnvelope();
  46.                                                                                 double p1 = box[0].LowerBound + box[0].Length / 2.0;
  47.                                                                                 double p2 = box[1].LowerBound + box[1].Length / 2.0;
  48.                                                                                 Point2d ptParams = new Point2d(p1, p2);
  49.                                                                                 PointOnSurface pos = new PointOnSurface(surf, ptParams);
  50.                                                                                 Vector3d normal = pos.GetNormal(ptParams);
  51.                                                                          }
  52.  
  53.                                                                 }
  54.  
  55.                                                        }
  56.                                                 }
  57.                                         }
  58.                  }
  59.         }
  60.  

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8690
  • AKA Daniel
Re: Laying a 3D Solid Flat to XY Plane
« Reply #3 on: February 29, 2024, 09:18:17 AM »
One option is to build a matrix, then transformby

Jonesy97

  • Mosquito
  • Posts: 4
Re: Laying a 3D Solid Flat to XY Plane
« Reply #4 on: March 04, 2024, 05:57:08 AM »
Thanks for the tips. So I've got this far making a matrix and then transforming by, however when I run the line "solidEnt.TransformBy(resultantMatrix);" my autocad freezes with no error message, despite the try catch (System.Exception ex).

Any ideas? Appreciate the help so far!

Code - C#: [Select]
  1.       public void Test()
  2.       {
  3.                 Document doc = ACAD_APP.DocumentManager.MdiActiveDocument;
  4.                 Editor ed = doc.Editor;
  5.                 Database db = HostApplicationServices.WorkingDatabase;
  6.  
  7.                 using (DocumentLock docLoc = doc.LockDocument())
  8.                 {
  9.                         try
  10.                         {
  11.  
  12.                                 PromptEntityOptions peo = new PromptEntityOptions("Select 3D solid.");
  13.                                 peo.SetRejectMessage("\nInvalid selection...");
  14.                                 peo.AddAllowedClass(typeof(Solid3d), true);
  15.  
  16.                                 PromptEntityResult per = ed.GetEntity(peo);
  17.  
  18.                                  if (per.Status != PromptStatus.OK)
  19.                                 {
  20.                                         return;
  21.                                 }
  22.                                 else
  23.                                 {
  24.                                                 using (Transaction trans = db.TransactionManager.StartTransaction())
  25.                                                 {
  26.                                                         Solid3d solid = trans.GetObject(per.ObjectId, OpenMode.ForRead) as Solid3d;
  27.                                                         ObjectId[] ids = new ObjectId[] { per.ObjectId };
  28.  
  29.                                                         FullSubentityPath path = new FullSubentityPath(ids, new SubentityId(SubentityType.Null, IntPtr.Zero));
  30.  
  31.                                                         using (Brep brep = new Brep(path))
  32.                                                         {
  33.                                                                 //get biggest face by making list of areas, and then sorting
  34.                                                                 var faceAreaList = new List<double>();
  35.                                                                 foreach (Autodesk.AutoCAD.BoundaryRepresentation.Face face in brep.Faces)
  36.                                                                 {
  37.                                                                          faceAreaList.Add(face.GetArea());
  38.                                                                        
  39.                                                                 }
  40.                                                                 faceAreaList.Sort();
  41.                                                                 faceAreaList.Reverse();
  42.  
  43.                                                                 foreach (Autodesk.AutoCAD.BoundaryRepresentation.Face face in brep.Faces)
  44.                                                                 {
  45.                                                                         if (face.GetArea() == faceAreaList[0])
  46.                                                                         {
  47.                                                                                 //get point in middle of biggest face, and normal vector
  48.                                                                                 Autodesk.AutoCAD.Geometry.Surface surf = face.Surface;
  49.                                                                                  
  50.                                                                                 Interval[] box = surf.GetEnvelope();
  51.                                                                                 double p1 = box[0].LowerBound + box[0].Length / 2.0;
  52.                                                                                 double p2 = box[1].LowerBound + box[1].Length / 2.0;
  53.                                                                                 Point2d ptParams = new Point2d(p1, p2);
  54.                                                                                 PointOnSurface pos = new PointOnSurface(surf, ptParams);
  55.                                                                                 Vector3d normal = pos.GetNormal(ptParams);
  56.                                                                                 Point3d pt = pos.GetPoint(ptParams);
  57.                                                                                
  58.                                                                                 Matrix3d resultantMatrix = Matrix3d.Identity;
  59.  
  60.                                                                                 //rotation around z axis
  61.                                                                                 Plane xy = new Plane(Point3d.Origin, Vector3d.ZAxis);
  62.  
  63.                                                                                 Matrix3d zAxisTrans = Matrix3d.Rotation(Vector3d.ZAxis.AngleOnPlane(xy) - normal.AngleOnPlane(xy),Vector3d.ZAxis,pt);
  64.                                                                                 normal = normal.TransformBy(zAxisTrans);
  65.                                                                                 resultantMatrix = resultantMatrix.PreMultiplyBy(zAxisTrans);
  66.  
  67.                                                                                 //rotation around x axis
  68.                                                                                 Plane yz = new Plane(Point3d.Origin, Vector3d.ZAxis);
  69.  
  70.                                                                                 Matrix3d xAxisTrans = Matrix3d.Rotation(Vector3d.ZAxis.AngleOnPlane(yz) - normal.AngleOnPlane(yz), Vector3d.ZAxis, pt);
  71.                                                                                 normal = normal.TransformBy(xAxisTrans);
  72.                                                                                 resultantMatrix = resultantMatrix.PreMultiplyBy(xAxisTrans);
  73.  
  74.                                                                                 //rotation around y axis
  75.                                                                                 Plane xz = new Plane(Point3d.Origin, Vector3d.ZAxis);
  76.  
  77.                                                                                 Matrix3d yAxisTrans = Matrix3d.Rotation(Vector3d.ZAxis.AngleOnPlane(xz) - normal.AngleOnPlane(xz), Vector3d.ZAxis, pt);
  78.                                                                                 normal = normal.TransformBy(yAxisTrans);
  79.                                                                                 resultantMatrix = resultantMatrix.PreMultiplyBy(yAxisTrans);
  80.  
  81.                                                                                 solidEnt.TransformBy(resultantMatrix);                                            
  82.                                                                          }
  83.  
  84.                                                                 }
  85.  
  86.                                                        }
  87.                                                        trans.Commit();
  88.                                                 }
  89.                                 }
  90.                      }
  91.                     catch (System.Exception ex)
  92.                     {
  93.                              Autodesk.AutoCAD.ApplicationServices.Core.Application.ShowAlertDialog(ex.Message);
  94.                     }
  95.                  }
  96.         }
  97.  


MickD

  • King Gator
  • Posts: 3636
  • (x-in)->[process]->(y-out) ... simples!
Re: Laying a 3D Solid Flat to XY Plane
« Reply #5 on: March 04, 2024, 10:00:39 PM »
Hi Jonesy

I would build a matrix something like this (from memory, may not be correct but you get the idea :) ):
Code - C#: [Select]
  1. Point3d faceOrigin = faceLoop.Vertices[0];
  2. Point3d pnt1 = faceLoop.Vertices[1];
  3. Point3d pnt2 = faceLoop.Vertices[faceLoop.Vertices.Length() - 1];
  4.  
  5. var xvec = faceOrigin.GetVectorTo(pnt1).GetNormal();
  6. var yvec = faceOrigin.GetVectorTo(pnt2); // this is probably not 'square' yet
  7. var zvec = xvec.CrossProduct(yvec).GetNormal();
  8.  
  9. // z and x should be rothoganal, now square up the yvec
  10. yvec = zvec.CrossProduct(xvec).GetNormal();
  11.  
  12. Matrix3d mat = new Matrix3d();
  13. mat = Matrix3d.AlignCoordinateSystem(faceOrigin, xvec, yvec, zvec,
  14.     new Point3d(), Vector3d.XAxis, Vector3d.YAxis, Vector3d.ZAxis);
  15.  
  16.  

Basically, if you can construct the 3 orthogonal vectors from a plane/surface you can build a simple coordinate system matrix which handles all the seperate axes calc's for you.
And to put the solid back just use mat.Inverse().
You need to be careful that you don't get a point 'to the right' of the pnt1, this will cause the zvec to point 'inside' the solid instead of out but it will still put it to WCS albeit upside down.

To get rid of the second foreach you could also have a face holder variable and temp area variable for checking, if the next face is bigger, store it and the area for next test, you should be left with the largest face in the end.
"Programming is really just the mundane aspect of expressing a solution to a problem."
- John Carmack

"Short cuts make long delays,' argued Pippin.”
- J.R.R. Tolkien

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: Laying a 3D Solid Flat to XY Plane
« Reply #6 on: March 05, 2024, 02:30:41 AM »
Hi,

If I do not misunderstand the request, this should work.
Code - C#: [Select]
  1.         [CommandMethod("TEST")]
  2.         public void Test()
  3.         {
  4.             var doc = Application.DocumentManager.MdiActiveDocument;
  5.             var db = doc.Database;
  6.             var ed = doc.Editor;
  7.  
  8.             var peo = new PromptEntityOptions("\nSelect 3d solid: ");
  9.             peo.SetRejectMessage("\nSelected object is not a 3d solid.");
  10.             peo.AddAllowedClass(typeof(Solid3d), false);
  11.             var per = ed.GetEntity(peo);
  12.             if (per.Status != PromptStatus.OK)
  13.                 return;
  14.  
  15.             using (var tr = db.TransactionManager.StartTransaction())
  16.             {
  17.                 var solid = (Solid3d)tr.GetObject(per.ObjectId, OpenMode.ForWrite);
  18.                 using (var brep = new Brep(solid))
  19.                 {
  20.                     var surfaces = brep.Faces
  21.                         .Select(face => new { Surface = (ExternalBoundedSurface)face.Surface, Area = face.GetArea() })
  22.                         .Where(x => x.Surface.IsPlane);
  23.                     if (surfaces.Any())
  24.                     {
  25.                         var plane = surfaces
  26.                             .Aggregate((x1, x2) => x1.Area < x2.Area ? x2 : x1)
  27.                             .Surface
  28.                             .BaseSurface as Plane;
  29.                         var vector = new Vector3d(plane.PointOnPlane.X, plane.PointOnPlane.Y, 0.0);
  30.                         var xform = Matrix3d.Displacement(vector) * Matrix3d.WorldToPlane(plane);
  31.                         solid.TransformBy(xform);
  32.                     }
  33.                 }
  34.                 tr.Commit();
  35.             }
  36.         }
Speaking English as a French Frog

It's Alive!

  • Retired
  • Needs a day job
  • Posts: 8690
  • AKA Daniel
Re: Laying a 3D Solid Flat to XY Plane
« Reply #7 on: March 05, 2024, 03:05:57 AM »
nice!

Jonesy97

  • Mosquito
  • Posts: 4
Re: Laying a 3D Solid Flat to XY Plane
« Reply #8 on: March 06, 2024, 05:43:33 AM »
Thanks for the help guys

retsameht

  • Mosquito
  • Posts: 14
Re: Laying a 3D Solid Flat to XY Plane
« Reply #9 on: March 09, 2024, 02:44:02 PM »

Code - C#: [Select]
  1.     var plane = surfaces.Aggregate((x1, x2) => x1.Area < x2.Area ? x2 : x1)

Hi Gile.

If you're running on .NET 6.0 or later, you can use:

Code - C#: [Select]
  1. var plane = surfaces.MaxBy(x => x.Area);

Which is more efficient, because unlike Aggregate(), it only needs to evaluate the selector function once per-item.


gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: Laying a 3D Solid Flat to XY Plane
« Reply #10 on: March 10, 2024, 04:04:46 AM »

Code - C#: [Select]
  1.     var plane = surfaces.Aggregate((x1, x2) => x1.Area < x2.Area ? x2 : x1)

Hi Gile.

If you're running on .NET 6.0 or later, you can use:

Code - C#: [Select]
  1. var plane = surfaces.MaxBy(x => x.Area);

Which is more efficient, because unlike Aggregate(), it only needs to evaluate the selector function once per-item.

Soon, with the new AutoCAD version...
I could have used the following implementation (available in this library)
Code - C#: [Select]
  1.         /// <summary>
  2.         /// Gets the greatest item of the sequence using 'comparer' with the 'selector' function returned values.
  3.         /// </summary>
  4.         /// <typeparam name="TSource">Type the items.</typeparam>
  5.         /// <typeparam name="TKey">Type of the returned value of 'selector' function.</typeparam>
  6.         /// <param name="source">Sequence to which the method applies.</param>
  7.         /// <param name="selector">Mapping function from 'TSource' to 'TKey'.</param>
  8.         /// <param name="comparer">Comparer used for the 'TKey' type; uses 'Comparer<TKey>.Default' if null or omitted.</param>
  9.         /// <returns>The greatest item in the sequence.</returns>
  10.         /// <exception cref="System.ArgumentNullException">Thrown if 'source' is null.</exception>
  11.         /// <exception cref="System.ArgumentNullException">Thrown if 'selector' is null.</exception>
  12.         public static TSource MaxBy<TSource, TKey>(
  13.             this IEnumerable<TSource> source,
  14.             Func<TSource, TKey> selector,
  15.             IComparer<TKey> comparer = null)
  16.         {
  17.             Assert.IsNotNull(source, nameof(source));
  18.             Assert.IsNotNull(selector, nameof(selector));
  19.  
  20.             comparer = comparer ?? Comparer<TKey>.Default;
  21.             using (var iterator = source.GetEnumerator())
  22.             {
  23.                 if (!iterator.MoveNext())
  24.                 {
  25.                     throw new InvalidOperationException("Empty sequence");
  26.                 }
  27.                 var max = iterator.Current;
  28.                 var maxKey = selector(max);
  29.                 while (iterator.MoveNext())
  30.                 {
  31.                     var current = iterator.Current;
  32.                     var currentKey = selector(current);
  33.                     if (comparer.Compare(currentKey, maxKey) > 0)
  34.                     {
  35.                         max = current;
  36.                         maxKey = currentKey;
  37.                     }
  38.                 }
  39.                 return max;
  40.             }
  41.         }
« Last Edit: March 10, 2024, 04:13:57 AM by gile »
Speaking English as a French Frog

retsameht

  • Mosquito
  • Posts: 14
Re: Laying a 3D Solid Flat to XY Plane
« Reply #11 on: March 12, 2024, 02:55:52 AM »
I could have used the following implementation (available in this library)

I posted something very much like that about 12 years ago (don't remember if it was here or not, but one place I do remember posting it to was connect.microsoft.com, when I had originally proposed it along with DistinctBy(), and few other variations on existing LINQ operators).

So, it only took them ~10 years to finally implement it.  :roll:

I would still advise anyone that might need MaxBy() to use the official version in .NET 6.0+, since it is optimized to run much faster when it's passed an ICollection, IList or an array.

gile

  • Gator
  • Posts: 2507
  • Marseille, France
Re: Laying a 3D Solid Flat to XY Plane
« Reply #12 on: March 12, 2024, 03:09:41 AM »
So, it only took them ~10 years to finally implement it.  :roll:
Especially since early versions of F# (at least F# 2.0) already had a Seq.maxBy function.
Speaking English as a French Frog

dexus

  • Bull Frog
  • Posts: 201
Re: Laying a 3D Solid Flat to XY Plane
« Reply #13 on: March 12, 2024, 04:52:27 AM »
I recently changed code found in this topic: https://www.theswamp.org/index.php?topic=49524.msg546663#msg546663
So that it lays a 3D Solid onto the XY plane.
Someone might find it usefull instead of using .NET
Code - Auto/Visual Lisp: [Select]
  1. (defun c:placeOnFace (/ cen mat name ss Mat:mxm Mat:mxv Mat:trp Mat:TransFromTo *error*)
  2.  
  3.   (defun *error* (msg)
  4.     (if (and msg (not (wcmatch (strcase msg t) "*break,*cancel*,*exit*")))
  5.       (princ (strcat "\nError: " msg))
  6.     )
  7.     (princ)
  8.   )
  9.  
  10.   (setq cmd (getvar 'cmdecho))
  11.   (setvar 'cmdecho 0)
  12.  
  13.   (if (setq ss (ssget "_+.:E:S" '((0 . "3DSOLID"))))
  14.     (progn
  15.       (setq cen (vla-get-centroid (vlax-ename->vla-object (ssname ss 0)))
  16.             name (strcat "TMP_UCS_" (rtos (getvar 'cdate) 2 8)))
  17.       (princ "\nSelect face for top: ")
  18.       (command "_.ucs" "_face" "\\" "")
  19.       (command "_.ucs" "_named" "_save" name)
  20.       (setq mat
  21.         (vlax-tmatrix
  22.           (append ; Based on gc:TMatrixFromTo by gile
  23.             (mapcar
  24.               (function (lambda (v o) (append (trans v 1 0 t) (list o))))
  25.               (list '(1.0 0.0 0.0) '(0.0 1.0 0.0) '(0.0 0.0 -1.0))
  26.               (mapcar '* (trans '(0 0 0) 0 1) '(1 1 -1))
  27.             )
  28.             '((0.0 0.0 0.0 -1.0))
  29.           )
  30.         )
  31.       )
  32.       (command "_.ucs" "_world")
  33.       (command "_.ucs" "_named" "_delete" name)
  34.       (repeat (setq n (sslength ss))
  35.         (setq ent (vlax-ename->vla-object (ssname ss (setq n (1- n)))))
  36.         (vla-transformby ent mat)
  37.         (vla-move ent (vla-get-centroid ent) cen)
  38.       )
  39.     )
  40.   )
  41.  
  42.   (setvar 'cmdecho cmd)
  43.   (princ)
  44. )
« Last Edit: March 12, 2024, 07:08:24 AM by dexus »