TheSwamp

Code Red => .NET => Topic started by: Kerry on January 27, 2010, 03:34:28 AM

Title: .NET GEOMETRY Routines
Post by: Kerry on January 27, 2010, 03:34:28 AM
LIBRARY THREAD for  AutoCAD GEOMETRY
 Members are encouraged to post any functions, methods, snips regarding
AutoCAD GEOMETRY in .NET : C# ,  VB , F# , Python , etc

Feel free to include comments, descriptive notes, limitations,  and images to document your post.

Please post questions in a regular thread.
Title: Re: .NET GEOMETRY Routines
Post by: gile on January 27, 2010, 05:28:26 PM
Hi,

Some geometry extensions methods for Point2d, Point3d, CircularArc2d, Polyline and Polyline2d classes and two little (maybe usefull) classes: Triangle2d, Triangle3d.

All these classes work together and have to be compiled in a single DLL.

The extension methods requires to reference System.Core (.NET Framework 3.5)

EDIT: extended

Available on Github (https://github.com/gilles-chanteau/GeometryExtensions).

The GeomExt class, contains the following extension methods:

Matrix3d Editor.UCS2WCS()
Matrix3d Editor.WCS2UCS()
Matrix3d Editor.DCS2WCS()
Matrix3d Editor.WCS2DCS()
Matrix3d Editor.DCS2PSDCS()
Matrix3d Editor.PSDCS2DCS()

Matrix3d Viewport.DCS2WCS()
Matrix3d Viewport.WCS2DCS()
Matrix3d Viewport.PSDCS2DCS()
Matrix3d Viewport.DCS2PSDCS()

Point3d Point2d.Convert3d()
Point3d Point2d.Convert3d(Plane plane)
Point3d Point2d.Convert3d(Vector3d normal, double elevation)
Point2d Point2d.Flatten(Vector3d normal)
bool IsBetween(Point2d p1, Point2d p2)
bool IsBetween(Point2d p1, Point2d p2, Tolerance tol)
Point2d Point2d.Polar(double angle, double distance)

Point3d Point3d.Convert2d()
Point3d Point3d.Flatten()
bool IsBetween(Point3d p1, Point3d p2)
bool IsBetween(Point3d p1, Point3d p2, Tolerance tol)
Point3d Point3d.Polar(double angle, double distance)
Point3d Point3d.Trans(int from, int to)
Point3d Point3d.Trans(Editor ed, int from, int to)
Point3d Point3d.Trans(CoordSystem from, CoordSystem to)
Point3d Point3d.Trans(Editor ed, CoordSystem from, CoordSystem to)

Vector3d Vector3d .Flatten()

void Point2dCollection.RemoveDuplicate()
void Point2dCollection.RemoveDuplicate(Tolerance tol)
bool Point2dCollection.Contains(Point2d pt, Tolerance tol)

void Point3dCollection.RemoveDuplicate()
void Point3dCollection.RemoveDuplicate(Tolerance tol)
bool Point3dCollection.Contains(Point3d pt, Tolerance tol)

double CircularArc2d.AlgebricArea()
Point2d CircularArc2d.Centroid()

Point2d Polyline.Centroid2d()
Point3d Polyline.Centroid()
Polyline Polyline.GetProjectedPolyline(Plane plane, Vector3d direction)
Polyline Polyline.GetOrthoProjectedPolyline(Plane plane)

Point3d Polyline2d.Centroid()
CircularArc3d Polyline2d.GetArcSegmentAt(int index)
CircularArc2d Polyline2d.GetArcSegment2dAt(int index)
LineSegment3d Polyline2d.GetLineSegmentAt(int index)
LineSegment2d Polyline2d.GetLineSegment2dAt(int index)
Polyline Polyline2d.GetProjectedPolyline(Plane plane, Vector3d direction)
Polyline Polyline2d.GetOrthoProjectedPolyline(Plane plane)
List<Vertex2d> Polyline2d.GetVertices()

Polyline Polyline3d.GetProjectedPolyline(Plane plane, Vector3d direction)
Polyline Polyline3d.GetOrthoProjectedPolyline(Plane plane)

Point3d Region.Centroid()

Point3d Spline.Centroid()

Polyline Ellipse.ToPolyline()


Triangle<T> abstract class

Constructors
Triangle()
Triangle(T[] pts)
Triangle(T a, T b, T c)

Indexor
T Item

Methods
T Inverse()
void Set(T[] pts)
void Set(T a, T b, T c)
T[] ToArray()


Triangle2d : Triangle<Point2d> class

Constructors
Triangle2d() : base()
Triangle2d(Point2d[] pts) : base(pts)
Triangle2d(Point2d a, Point2d b, Point2d c) : base(a, b, c)
Triangle2d(Point2d org, Vector2d v1, Vector2d v2)

Properties
double AlgebricArea
Point2d Centroid
CircularArc2d CircumscribedCircle
CircularArc2d InscribedCircle
bool IsClockwise

Methods
Triangle3d Convert3d(Plane plane)
Triangle3d Convert3d(Vector3d normal, double elevation)
double GetAngleAt(int index)
LineSegment2d GetSegmentAt(int index)
List<Point2d> IntersectWith(LinearEntity2d le2d)
List<Point2d> IntersectWith(LinearEntity2d le2d, Tolerance tol)
bool IsEqualTo(Triangle2d t2d)
bool IsEqualTo(Triangle2d t2d, Tolerance tol)
bool IsPointInside(Point2d pt)
bool IsPointOn(Point2d pt)
void Set(Point2d org, Vector2d v1, Vector2d v2)
Triangle2d TransformBy(Matrix2d mat)


Triangle3d : Triangle<Point3d> class

Constructors
Triangle3d() : base()
Triangle3d(Point3d[] pts) : base(pts)
Triangle3d(Point3d a, Point3d b, Point3d c) : base(a, b, c)
Triangle3d(Point3d org, Vector3d v1, Vector3d v2)

Properties
double Area
Point3d Centroid
CircularArc3d CircumscribedCircle
double Elevation
Vector3d GreatestSlope
Vector3d Horizontal
CircularArc3d InscribedCircle
bool IsHorizontal
Vector3d Normal
double SlopePerCent
Matrix3d SlopeUCS

Methods
Triangle2d Convert2d()
Triangle2d Flatten()
double GetAngleAt(int index)
BoundedPlane GetBoundedPlane()
Plane GetPlane()
LineSegment3d GetSegmentAt(int index)
bool IsEqualTo(Triangle3d t3d)
bool IsEqualTo(Triangle3d t3d, Tolerance tol)
bool IsPointInside(Point3d pt)
bool IsPointOn(Point3d pt)
void Set(Point3d org, Vector3d v1, Vector3d v2)
Triangle3d Transformby(Matrix3d mat)


PolylineSegment class

Constructors

PolylineSegment(Point2d startPoint, Point2d endPoint)
PolylineSegment(Point2d startPoint, Point2d endPoint, double bulge)
PolylineSegment(Point2d startPoint, Point2d endPoint, double bulge, double constantWidth)
PolylineSegment(Point2d startPoint, Point2d endPoint, double bulge, double startWidth, double endWidth)
PolylineSegment(Line2dSegment line)
PolylineSegment(CircularArc2d arc)

Properties

double Bulge
Point2d EndPoint
double EndWidth
bool IsLinear
Point2d StartPoint
double StartWidth

Methods

bool Equals(object obj)
PolylineSegment Clone()
int GetHashCode()
double GetParameterOf(Point3d pt)
void Inverse()
CircularArc2d ToCircularArc()
Curve2d ToCurve2d()
LineSegment2d ToLineSegment()
string ToString()


PolylineSegmentCollection : IList<PolylineSegment> class

Constructors

PolylineSegmentCollection()
PolylineSegmentCollection(Circle circle)
PolylineSegmentCollection(Ellipse ellipse)
PolylineSegmentCollection(Polyline pline)
PolylineSegmentCollection(Polyline2d pline)
PolylineSegmentCollection(IEnumerable<PolylineSegment> segments)

Methods

void Add(PolylineSegment segment)
void AddRange(IEnumerable<PolylineSegment> range)
void Clear()
bool Contains(PolylineSegment segment)
int FinIindex(Predicate<PolylineSegment> match)
int GetClosestSegmentTo(Point2d pt)
IEnumerator<PolylineSegment> GetEnumerator()
int IndexOf(PolylineSegment item)
void Insert(int index, PolylineSegment item)
void InsertRange(int index, IEnumerable<PolylineSegment> collection)
List<PolylineSegmentCollection> Join()
List<PolylineSegmentCollection> Join(Tolerance tol)
bool Remove(PolylineSegment item)
void RemoveAt(int index)
public void RemoveRange(int index, int count)
Polyline ToPolyline()

For more information, see the attached documentation.

GeomExtDoc.zip contains a .chm documentation file.
GeometryExtensions(1.6).zip contains the C# source code files and the DLLs (GeometryExtensions_18.dll for A2010-2012 and GeometryExtensions_19 for A2013+)

<EDIT: version 1.6> added CircularArc2d and CircularArc3d GetTangentsTo methods, reorganized: one class per extended type.
Title: Re: .NET GEOMETRY Routines
Post by: gile on February 11, 2010, 04:31:52 PM
Added some extension methods for Polyline2d.
Title: Re: .NET GEOMETRY Routines
Post by: gile on February 16, 2010, 06:03:22 PM
I added some more extensions and rewrite the Triangle2d and Triangle3d classes
Title: Re: .NET GEOMETRY Routines
Post by: gile on April 17, 2010, 08:56:17 AM
Hi,

I added some properties and methods to the Triangle2d and Triangle3d classes that I needed to deal with 3dfaces (ground modeling).

Triangle2d class:
- Inverse() method returns the triangle in inverse order (Triangle2d)

Triangle3d class :
- GreatestSlope property returns the single unit vector of the triangle plane greatest slope (Vector3d)
- Horizontal property returns the single unit vector of the triangle plane horizontal (Vector3d)
- IsHorizontal property returns true if the triangle plane is parallel to WCS XY plane (Boolean)
- SlopePerCent property returns the percent expression of the greatest slope (Double)
- SlopeUCS property returns a coordinates system matrix which origin is the triangle centroid, X axis is horizontal vector, Y axis the negated greatest slope vector (Matrix3d)
- Flatten() method returns the flatten triangle (Triangle2d)
- Inverse() method returns the triangle in inverse order (Triangle3d)


Title: Re: .NET GEOMETRY Routines
Post by: gile on August 22, 2010, 02:18:56 PM
Hi,

Some more adds:

Point3d and Point2d extension methods:
- IsBetween returns true if the first point (first parameter) is strictly between the two others (with or without Tolerance).

Triangle2d methods:
- GetSegmentAt: returns the LinearSegment2d at specified index.
- IntersecWith: returns a List<Point2d> of the intersection points between the triangle and a LinearEntity2d (with or without Tolerance).
- IsPointInside: returns true if the point is strictly inside the triangle.
- IsPointOn: returns true if the point lies on an edge.

Triangle3d methods:
- GetBoundedPlane: returns a BoundedPlane object.
- IsPointInside: returns true if the point is strictly inside the triangle.
- IsPointOn: returns true if the point lies on an edge.
Title: Re: .NET GEOMETRY Routines
Post by: gile on August 23, 2010, 03:49:33 AM
 :oops:

There was a mistake in the Triangle2d.IsPointInside() method.
Attached file in the first post is updated.
Title: Re: .NET GEOMETRY Routines
Post by: Kerry on August 23, 2010, 04:45:37 AM

Thanks gile, that should be a handy library.
Title: Re: .NET GEOMETRY Routines
Post by: fixo on August 23, 2010, 05:23:12 AM
:oops:

There was a mistake in the Triangle2d.IsPointInside() method.
Attached file in the first post is updated.

Thanks Gilles, I've used it once, very useful
I'm pretty sure you working on Rectangle 2d/3d as well :)

Oleg
Title: Re: .NET GEOMETRY Routines
Post by: gile on August 23, 2010, 05:48:04 AM
Thanks both, you're welcome.

Quote
I'm pretty sure you working on Rectangle 2d/3d as well
I'm not so sure, IMO, in geometry (almost 3d geometry) rectangles are'nt as useful as triangles which are not deformable, define planes and so on...
Title: Re: .NET GEOMETRY Routines
Post by: gile on February 03, 2011, 05:03:03 AM
Hi,

Added some extension methods:

void Point2dCollection.RemoveDuplicate()
void Point2dCollection.RemoveDuplicate(Tolerance tol)
bool Point2dCollection.Contains(Point2d pt, Tolerance tol)

void Point3dCollection.RemoveDuplicate()
void Point3dCollection.RemoveDuplicate(Tolerance tol)
bool Point3dCollection.Contains(Point3d pt, Tolerance tol)

And edit the Triangle2d and Triangle3d classes:
both inherits from a 'generic' abstract class: Triangle<T> and have two new properties : CircumscribedCircle and InscribedCircle
Title: Re: .NET GEOMETRY Routines
Post by: jgr on March 03, 2011, 10:37:26 AM
Convex Hull - Graham scan algorithm

Based on this:
http://www.partow.net/projects/fastgeo/index.html
http://www.partow.net/downloads/FastGEOConvexHull.zip
Title: Re: .NET GEOMETRY Routines
Post by: gile on April 14, 2011, 05:16:55 PM
Hi

Here's a F# convex hull (Graham's scan)

EDIT: new code see here (http://www.theswamp.org/index.php?topic=37928.msg429537#msg429537)

EDIT: a tolerance in comparisons was needed in some cases with colinear points

Code - F#: [Select]
  1. module ConvexHull
  2.  
  3. open System
  4. open Autodesk.AutoCAD.ApplicationServices
  5. open Autodesk.AutoCAD.DatabaseServices
  6. open Autodesk.AutoCAD.EditorInput
  7. open Autodesk.AutoCAD.Geometry
  8. open Autodesk.AutoCAD.Runtime
  9.  
  10. let clockwise (p1:Point2d) (p2:Point2d) (p3:Point2d) =
  11.     (p2.X - p1.X) * (p3.Y - p1.Y) - (p2.Y - p1.Y) * (p3.X - p1.X) < 1e-8
  12.    
  13. let convexHull (pts : Point2d seq) =
  14.     let rec fill acc pt =
  15.         match acc with
  16.         | a :: b :: _ when clockwise b a pt -> fill acc.Tail pt
  17.         | _ -> pt :: acc
  18.     let p0 = pts
  19.              |> Seq.reduce (fun p1 p2 ->
  20.                 if p2.Y < p1.Y || (p1.Y = p2.Y && p2.X < p1.X) then p2 else p1)
  21.     pts
  22.     |> List.ofSeq
  23.     |> List.sortBy (fun p ->
  24.         let d = p0.GetDistanceTo(p)
  25.         (Math.Round((p0.X - p.X) / d, 8), d))
  26.     |> List.fold fill []
  27.     |> List.rev
  28.  
  29. [<CommandMethod("ch")>]
  30. let Test() =
  31.     let doc = Application.DocumentManager.MdiActiveDocument
  32.     let psr = doc.Editor.GetSelection(new SelectionFilter([| new TypedValue(0, "POINT") |]))
  33.     if psr.Status = PromptStatus.OK then
  34.         use tr = doc.TransactionManager.StartTransaction()
  35.         use pl = new Polyline()
  36.         psr.Value
  37.         |> Seq.cast
  38.         |> Seq.map (fun (so : SelectedObject) ->
  39.             let pt = tr.GetObject(so.ObjectId, OpenMode.ForRead) :?> DBPoint
  40.             new Point2d(pt.Position.X, pt.Position.Y))
  41.         |> convexHull
  42.         |> List.iteri(fun i p -> pl.AddVertexAt(i, p, 0.0, 0.0, 0.0))
  43.         pl.Closed <- true
  44.         let btr = tr.GetObject(doc.Database.CurrentSpaceId, OpenMode.ForWrite) :?> BlockTableRecord
  45.         btr.AppendEntity(pl) |> ignore
  46.         tr.AddNewlyCreatedDBObject(pl, true)
  47.         tr.Commit()
Title: Re: .NET GEOMETRY Routines
Post by: gile on April 15, 2011, 06:09:33 AM
And the new C# implementation:

EDIT: I repalced the lambda function with a delegate (ComparePoints) so that it can be used with NET Framework 2.0 (default for A2007 -> A2009)

Code - C#: [Select]
  1. using System.Collections.Generic;
  2. using Autodesk.AutoCAD.ApplicationServices;
  3. using Autodesk.AutoCAD.DatabaseServices;
  4. using Autodesk.AutoCAD.EditorInput;
  5. using Autodesk.AutoCAD.Geometry;
  6. using Autodesk.AutoCAD.Runtime;
  7. using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;
  8.  
  9. namespace ConvexHull
  10. {
  11.     public class Commands
  12.     {
  13.         private Point2d _p0;
  14.  
  15.         private bool Clockwise(Point2d p1, Point2d p2, Point2d p3)
  16.         {
  17.             return ((p2.X - p1.X) * (p3.Y - p1.Y) - (p2.Y - p1.Y) * (p3.X - p1.X)) < 1e-8;
  18.         }
  19.  
  20.         private int ComparePoints(Point2d p1, Point2d p2)
  21.         {
  22.             if (p1.IsEqualTo(p2)) return 0;
  23.             double d1 = _p0.GetDistanceTo(p1);
  24.             double d2 = _p0.GetDistanceTo(p2);
  25.             if (d1 == 0.0) return -1;
  26.             if (d2 == 0.0) return 1;
  27.             double cos = (p2.X - _p0.X) / d2 - (p1.X - _p0.X) / d1;
  28.             if (cos < -1e-8) return -1;
  29.             if (cos > 1e-8) return 1;
  30.             return d1.CompareTo(d2);
  31.         }
  32.  
  33.         private List<Point2d> ConvexHull(List<Point2d> pts)
  34.         {
  35.             _p0 = pts[0];
  36.             for (int i = 1; i < pts.Count; i++)
  37.             {
  38.                 Point2d pt = pts[i];
  39.                 if (pt.Y < _p0.Y || (pt.Y == _p0.Y && pt.X < _p0.X))
  40.                     _p0 = pt;
  41.             }
  42.             pts.Sort(ComparePoints);
  43.             for (int i = 1; i < pts.Count - 1; i++)
  44.             {
  45.                 while (i > 0 && Clockwise(pts[i - 1], pts[i], pts[i + 1]))
  46.                 {
  47.                     pts.RemoveAt(i);
  48.                     i--;
  49.                 }
  50.             }
  51.             return pts;
  52.         }
  53.  
  54.         [CommandMethod("ch")]
  55.         public void testCh()
  56.         {
  57.             Document doc = AcAp.DocumentManager.MdiActiveDocument;
  58.             Database db = doc.Database;
  59.             Editor ed = doc.Editor;
  60.             TypedValue[] filter = new TypedValue[1] { new TypedValue(0, "POINT") };
  61.             PromptSelectionResult psr = ed.GetSelection(new SelectionFilter(filter));
  62.             if (psr.Status != PromptStatus.OK) return;
  63.             using (Transaction tr = db.TransactionManager.StartTransaction())
  64.             using (Polyline pline = new Polyline())
  65.             {
  66.                 List<Point2d> pts = new List<Point2d>();
  67.                 foreach (SelectedObject so in psr.Value)
  68.                 {
  69.                     DBPoint dbPt = (DBPoint)tr.GetObject(so.ObjectId, OpenMode.ForRead);
  70.                     pts.Add(new Point2d(dbPt.Position.X, dbPt.Position.Y));
  71.                 }
  72.                 for (int i = 0; i < ConvexHull(pts).Count; i++)
  73.                 {
  74.                     pline.AddVertexAt(i, pts[i], 0.0, 0.0, 0.0);
  75.                 }
  76.                 pline.Closed = true;
  77.                 pline.SetDatabaseDefaults();
  78.                 BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
  79.                 btr.AppendEntity(pline);
  80.                 tr.AddNewlyCreatedDBObject(pline, true);
  81.                 tr.Commit();
  82.             }
  83.         }
  84.     }
  85. }
Title: Re: .NET GEOMETRY Routines
Post by: fixo on April 18, 2011, 04:18:27 PM
Nice as always, thanks :)
Regards,

Oleg
Title: Re: .NET GEOMETRY Routines
Post by: gile on April 19, 2011, 01:16:01 PM
Thanks, Oleg. You're very welcome.
Title: Re: .NET GEOMETRY Routines
Post by: gile on April 25, 2011, 01:55:10 PM
A new C# implementation of the ConvexHull using Linq (.NET Framework 3 or upper)

Code - C#: [Select]
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using Autodesk.AutoCAD.ApplicationServices;
  5. using Autodesk.AutoCAD.DatabaseServices;
  6. using Autodesk.AutoCAD.EditorInput;
  7. using Autodesk.AutoCAD.Geometry;
  8. using Autodesk.AutoCAD.Runtime;
  9. using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;
  10.  
  11. namespace LinqConvexHull
  12. {
  13.     public class Commands
  14.     {
  15.         private Point2d _p0;
  16.  
  17.         private bool Clockwise(Point2d p1, Point2d p2, Point2d p3)
  18.         {
  19.             return ((p2.X - p1.X) * (p3.Y - p1.Y) - (p2.Y - p1.Y) * (p3.X - p1.X)) < 1e-9;
  20.         }
  21.  
  22.         private double Cosine(Point2d pt)
  23.         {
  24.             double d = _p0.GetDistanceTo(pt);
  25.             return d == 0.0 ? 1.0 : Math.Round((pt.X - _p0.X) / d, 9);
  26.         }
  27.  
  28.         private List<Point2d> ConvexHull(List<Point2d> pts)
  29.         {
  30.             _p0 = pts.OrderBy(p => p.Y).ThenBy(p => p.X).First();
  31.             pts = pts.OrderByDescending(p => Cosine(p)).ThenBy(p => _p0.GetDistanceTo(p)).ToList();
  32.             for (int i = 1; i < pts.Count - 1; i++)
  33.             {
  34.                 while (i > 0 && Clockwise(pts[i - 1], pts[i], pts[i + 1]))
  35.                 {
  36.                     pts.RemoveAt(i);
  37.                     i--;
  38.                 }
  39.             }
  40.             return pts;
  41.         }
  42.  
  43.         [CommandMethod("Test")]
  44.         public void Test()
  45.         {
  46.             Document doc = AcAp.DocumentManager.MdiActiveDocument;
  47.             Database db = doc.Database;
  48.             Editor ed = doc.Editor;
  49.             TypedValue[] filter = new TypedValue[1] { new TypedValue(0, "POINT") };
  50.             PromptSelectionResult psr = ed.GetSelection(new SelectionFilter(filter));
  51.             if (psr.Status != PromptStatus.OK) return;
  52.             using (Transaction tr = db.TransactionManager.StartTransaction())
  53.             using (Polyline pline = new Polyline())
  54.             {
  55.                 List<Point2d> pts = new List<Point2d>();
  56.                 foreach (SelectedObject so in psr.Value)
  57.                 {
  58.                     DBPoint dbPt = (DBPoint)tr.GetObject(so.ObjectId, OpenMode.ForRead);
  59.                     pts.Add(new Point2d(dbPt.Position.X, dbPt.Position.Y));
  60.                 }
  61.                 pts = ConvexHull(pts);
  62.                 for (int i = 0; i < pts.Count; i++)
  63.                 {
  64.                     pline.AddVertexAt(i, pts[i], 0.0, 0.0, 0.0);
  65.                 }
  66.                 pline.Closed = true;
  67.                 pline.SetDatabaseDefaults();
  68.                 BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
  69.                 btr.AppendEntity(pline);
  70.                 tr.AddNewlyCreatedDBObject(pline, true);
  71.                 tr.Commit();
  72.             }
  73.         }
  74.     }
  75. }
  76.  
Title: Re: .NET GEOMETRY Routines
Post by: gile on July 10, 2011, 10:41:46 AM
Hi,

I made a minor update in GeometryExtensions (http://www.theswamp.org/index.php?topic=31865.msg373672#msg373672) and I added comments to create a documentation file using Sandcastle Help File Builder (http://shfb.codeplex.com/).

The GeomExtDoc.chm is downloadable from the reply #2 (http://www.theswamp.org/index.php?topic=31865.msg373672#msg373672).

Title: Re: .NET GEOMETRY Routines
Post by: qjchen on July 13, 2011, 09:41:50 AM
Gile, Thank you your great library, I will learn a lot from it~

And this code about convex hull which using Linq is so concise, very cool~
Title: Re: .NET GEOMETRY Routines
Post by: kaefer on July 13, 2011, 10:39:07 AM
Gile, Thank you your great library, I will learn a lot from it~

I can only second that. Merci beaucoup!

Alas, there was a minor snag in appropriating your intellectual property: The paper space transformations are extension methods to Editor, whereas I needed them for Viewport to replicate the CHSPACE command. Any chance to make them more generic?

Code: [Select]
type Viewport with
    member vp.DCS2PSDCS =
        Matrix3d.Scaling(vp.CustomScale, vp.CenterPoint) *
        Matrix3d.Displacement(vp.CenterPoint - Point3d.Origin) *
        Matrix3d.Displacement(Point3d.Origin - vp.ViewCenter.To3d)
    member vp.PSDCS2DCS = vp.DCS2PSDCS.Inverse()

    member vp.DCS2WCS =
        Matrix3d.Rotation(-vp.TwistAngle, vp.ViewDirection, vp.ViewTarget) *
        Matrix3d.Displacement(vp.ViewTarget - Point3d.Origin) *
        Matrix3d.PlaneToWorld vp.ViewDirection
    member vp.WCS2DCS = vp.DCS2WCS.Inverse()
Title: Re: .NET GEOMETRY Routines
Post by: gile on July 13, 2011, 01:39:14 PM
Thank you both.

Quote
there was a minor snag in appropriating your intellectual property
I do not really care about "intellectual property" with the code I post on TheSwamp or elsewhere. I learn a lot from you and others and most of the code I write is "hacked" from what I read.
If I can bring my "two cents" sometimes, it makes me happy when other find it usefull.

Anyway, you're right, the Viewport extensions methods may be useful as the actual methods only work with the current (or last activated) one (I wanted to mimic the AutoLISP trans function with the Point3d.Trans() method).

If you don't mind it's an "appropriation of your intellectual proprety" ;-) , I'd add them to the library.
Title: Re: .NET GEOMETRY Routines
Post by: jgr on July 15, 2011, 07:49:05 PM
Generic .Net geometry libraries

http://www.codeproject.com/KB/recipes/sharp3dmath.aspx
http://www.codeproject.com/KB/recipes/exocortex.aspx
http://www.codeproject.com/KB/GDI-plus/exoengine.aspx

Look at triangles class's...Do you know this?
Title: Re: .NET GEOMETRY Routines
Post by: gile on August 29, 2011, 04:16:38 AM
Hi,

Here's a method to write the Brep (Boundary Representation) geométric informations of Solid3d, Region, Surface to an Xml file.
Requires A2009 or upper.

Method parameters:
- id: A Solid3d, Region or Surface ObjectId
- filename: The Xml file complete path
- append: If the file exists and this parameter is True, the new data is append to the file, else, the file is overwritten

Code - C#: [Select]
  1.         public void WriteBrepToXml(ObjectId id, string filename, bool append)
  2.         {
  3.             XmlDocument xmlDoc = new XmlDocument();
  4.             if (System.IO.File.Exists(filename) && append)
  5.             {
  6.                 xmlDoc.Load(filename);
  7.             }
  8.             else
  9.             {
  10.                 string s = @"<?xml version='1.0' ?>
  11.                <Brep>
  12.                </Brep>";
  13.                 xmlDoc.LoadXml(s);
  14.             }
  15.             Database db = HostApplicationServices.WorkingDatabase;
  16.             using (Transaction tr = db.TransactionManager.StartTransaction())
  17.             {
  18.                 Entity ent = (Entity)tr.GetObject(id, OpenMode.ForRead);
  19.                 string solidType;
  20.                 if (ent is Solid3d) solidType = "Solide";
  21.                 else if (ent is Region) solidType = "Region";
  22.                 else if (ent is AcDb.Surface) solidType = "Surface";
  23.                 else return;
  24.                 using (Brep brp = new Brep(ent))
  25.                 {
  26.                     XmlElement entNode = xmlDoc.CreateElement(solidType);
  27.                     entNode.SetAttribute("Handle", ent.Handle.ToString());
  28.                     xmlDoc.DocumentElement.AppendChild(entNode);
  29.                     foreach (Complex cplx in brp.Complexes)
  30.                     {
  31.                         XmlElement cplxNode = xmlDoc.CreateElement("Complex");
  32.                         entNode.AppendChild(cplxNode);
  33.                         foreach (Shell shl in cplx.Shells)
  34.                         {
  35.                             XmlElement shlNode = xmlDoc.CreateElement("Shell");
  36.                             shlNode.SetAttribute("Type", shl.ShellType.ToString());
  37.                             cplxNode.AppendChild(shlNode);
  38.                             foreach (AcBr.Face face in shl.Faces)
  39.                             {
  40.                                 XmlElement faceNode = xmlDoc.CreateElement("Face");
  41.                                 shlNode.AppendChild(faceNode);
  42.                                 try
  43.                                 {
  44.                                     foreach (BoundaryLoop loop in face.Loops)
  45.                                     {
  46.                                         XmlElement loopNode = xmlDoc.CreateElement("Loop");
  47.                                         loopNode.SetAttribute("Type", loop.LoopType.ToString());
  48.                                         faceNode.AppendChild(loopNode);
  49.                                         foreach (Edge edge in loop.Edges)
  50.                                         {
  51.                                             ExternalCurve3d curve = (ExternalCurve3d)edge.Curve;
  52.                                             switch (curve.NativeCurve.GetType().Name)
  53.                                             {
  54.                                                 case "LineSegment3d":
  55.                                                     LineSegment3d ls = (LineSegment3d)curve.NativeCurve;
  56.                                                     XmlElement lsNode = xmlDoc.CreateElement("LineSegment3d");
  57.                                                     lsNode.SetAttribute("StartPoint", ls.StartPoint.ToString());
  58.                                                     lsNode.SetAttribute("EndPoint", ls.EndPoint.ToString());
  59.                                                     lsNode.SetAttribute("Direction", ls.Direction.ToString());
  60.                                                     loopNode.AppendChild(lsNode);
  61.                                                     break;
  62.                                                 case "CircularArc3d":
  63.                                                     CircularArc3d ca = (CircularArc3d)curve.NativeCurve;
  64.                                                     double angle = Vector3d.XAxis
  65.                                                          .TransformBy(Matrix3d.PlaneToWorld(ca.Normal))
  66.                                                          .GetAngleTo(ca.ReferenceVector, ca.Normal);
  67.                                                     XmlElement caNode = xmlDoc.CreateElement("CircularArc3d");
  68.                                                     caNode.SetAttribute("StartPoint", ca.StartPoint.ToString());
  69.                                                     caNode.SetAttribute("EndPoint", ca.EndPoint.ToString());
  70.                                                     caNode.SetAttribute("Center", ca.Center.ToString());
  71.                                                     caNode.SetAttribute("Radius", ca.Radius.ToString());
  72.                                                     caNode.SetAttribute("StartAngle", (ca.StartAngle + angle).ToString());
  73.                                                     caNode.SetAttribute("EndAngle", (ca.EndAngle + angle).ToString());
  74.                                                     caNode.SetAttribute("Normal", ca.Normal.ToString());
  75.                                                     loopNode.AppendChild(caNode);
  76.                                                     break;
  77.                                                 case "EllipticalArc3d":
  78.                                                     EllipticalArc3d ea = (EllipticalArc3d)curve.NativeCurve;
  79.                                                     XmlElement eaNode = xmlDoc.CreateElement("EllipticalArc3d");
  80.                                                     eaNode.SetAttribute("StartPoint", ea.StartPoint.ToString());
  81.                                                     eaNode.SetAttribute("EndPoint", ea.EndPoint.ToString());
  82.                                                     eaNode.SetAttribute("Center", ea.Center.ToString());
  83.                                                     eaNode.SetAttribute("StartAngle", ea.StartAngle.ToString());
  84.                                                     eaNode.SetAttribute("EndAngle", ea.EndAngle.ToString());
  85.                                                     eaNode.SetAttribute("MajorAxis", ea.MajorAxis.ToString());
  86.                                                     eaNode.SetAttribute("MinorAxis", ea.MinorAxis.ToString());
  87.                                                     eaNode.SetAttribute("MajorRadius", ea.MajorRadius.ToString());
  88.                                                     eaNode.SetAttribute("MinorRadius", ea.MinorRadius.ToString());
  89.                                                     eaNode.SetAttribute("Normal", ea.Normal.ToString());
  90.                                                     loopNode.AppendChild(eaNode);
  91.                                                     break;
  92.                                                 case "NurbCurve3d":
  93.                                                     XmlElement ncNode = xmlDoc.CreateElement("NurbCurve3d");
  94.                                                     NurbCurve3d nc = (NurbCurve3d)curve.NativeCurve;
  95.                                                     NurbCurve3dData data = nc.DefinitionData;
  96.                                                     ncNode.SetAttribute("StartPoint", nc.StartPoint.ToString());
  97.                                                     ncNode.SetAttribute("EndPoint", nc.EndPoint.ToString());
  98.                                                     ncNode.SetAttribute("Degree", data.Degree.ToString());
  99.                                                     ncNode.SetAttribute("Periodic", data.Periodic.ToString());
  100.                                                     ncNode.SetAttribute("Rational", data.Rational.ToString());
  101.                                                     for (int i = 0; i < data.Knots.Count; i++)
  102.                                                     {
  103.                                                         ncNode.SetAttribute(string.Format("Knot_{0}", i + 1), data.Knots[i].ToString());
  104.                                                     }
  105.                                                     for (int i = 0; i < data.Weights.Count; i++)
  106.                                                     {
  107.                                                         ncNode.SetAttribute(string.Format("Weight_{0}", i + 1), data.Weights[i].ToString());
  108.                                                     }
  109.                                                     for (int i = 0; i < data.ControlPoints.Count; i++)
  110.                                                     {
  111.                                                         ncNode.SetAttribute(string.Format("ControlPoint_{0}", i + 1), data.ControlPoints[i].ToString());
  112.                                                     }
  113.                                                     loopNode.AppendChild(ncNode);
  114.                                                     break;
  115.                                             }
  116.                                         }
  117.                                     }
  118.                                 }
  119.                                 catch
  120.                                 {
  121.                                     XmlElement loopNode = xmlDoc.CreateElement("LoopUnbounded");
  122.                                     faceNode.AppendChild(loopNode);
  123.                                 }
  124.                             }
  125.                         }
  126.                     }
  127.                     xmlDoc.Save(filename);
  128.                 }
  129.                 tr.Commit();
  130.             }
  131.         }

Heres a little example of the returned file content for a region made of a rectangle from 0,0 to 80,50 with a circular hole which center is at 20,30 and a 10 radius:
Code - XML: [Select]
  1. <?xml version="1.0"?>
  2. <Brep>
  3.   <Region Handle="2747">
  4.     <Complex>
  5.       <Shell Type="ShellExterior">
  6.         <Face>
  7.           <Loop Type="LoopInterior">
  8.             <CircularArc3d StartPoint="(30,30,0)" EndPoint="(30,30,0)" Center="(20,30,0)" Radius="10" StartAngle="3.14159265358979" EndAngle="9.42477796076938" Normal="(0,0,-1)" />
  9.           </Loop>
  10.           <Loop Type="LoopExterior">
  11.             <LineSegment3d StartPoint="(0,0,0)" EndPoint="(80,0,0)" Direction="(1,0,0)" />
  12.             <LineSegment3d StartPoint="(80,0,0)" EndPoint="(80,50,0)" Direction="(0,1,0)" />
  13.             <LineSegment3d StartPoint="(80,50,0)" EndPoint="(0,50,0)" Direction="(-1,0,0)" />
  14.             <LineSegment3d StartPoint="(0,50,0)" EndPoint="(0,0,0)" Direction="(0,-1,0)" />
  15.           </Loop>
  16.         </Face>
  17.       </Shell>
  18.     </Complex>
  19.   </Region>
  20. </Brep>
Title: Re: .NET GEOMETRY Routines
Post by: gile on September 11, 2011, 08:36:50 AM
Hi,

I expanded a little the GeometryExtensions library.
There're two more classes: PolylineSegment and PolylineSegmentCollection (which inherits from List<PolylineSegment>) and a new extension method: Ellipse.ToPolyline().
I hope these classes make easier some work with polylines or to create polylines from other entities.

Here's a quick and dirty example (will give unexpected result with entities which don't lies on the WCS XY plane).
The MJOIN command joins the selected entities: line, arc, polyline, ellipse and spline (A2010 or later for spline) into one or more polyline(s).

Code: [Select]
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using GeometryExtensions;
using AcAp = Autodesk.AutoCAD.ApplicationServices.Application;

namespace GeometryExtensionsTestCommands
{
    public class MjoinCommand
    {
        [CommandMethod("mjoin")]
        public void PolyJoin()
        {
            Document doc = AcAp.DocumentManager.MdiActiveDocument;
            Database db = doc.Database;
            Editor ed = doc.Editor;
            TypedValue[] filter = {   
                new TypedValue(-4,"<OR"),
                new TypedValue(0, "ARC,ELLIPSE,LINE,LWPOLYLINE"),
                new TypedValue(-4, "<AND"),
                new TypedValue(0, "SPLINE"),
                new TypedValue(-4, "&"),
                new TypedValue(70, 8),
                new TypedValue(-4, "AND>"),
                new TypedValue(-4, "OR>")};
            PromptSelectionResult psr = ed.GetSelection(new SelectionFilter(filter));
            if (psr.Status != PromptStatus.OK) return;
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                BlockTableRecord btr =
                    (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
                PolylineSegmentCollection psc = new PolylineSegmentCollection();
                Plane plane = new Plane(Point3d.Origin, Vector3d.ZAxis);
                foreach (ObjectId id in psr.Value.GetObjectIds())
                {
                    Entity ent = (Entity)tr.GetObject(id, OpenMode.ForRead);
                    switch (ent.GetType().Name)
                    {
                        case "Arc":
                            Arc arc = (Arc)ent;
                            psc.Add(new PolylineSegment(
                                new CircularArc2d(
                                    arc.Center.Convert2d(plane),
                                    arc.Radius,
                                    arc.StartAngle,
                                    arc.EndAngle,
                                    Vector2d.XAxis,
                                    false)));
                            break;
                        case "Ellipse":
                            Ellipse el = (Ellipse)ent;
                            psc.AddRange(new PolylineSegmentCollection(el));
                            break;
                        case "Line":
                            Line l = (Line)ent;
                            psc.Add(new PolylineSegment(
                                new LineSegment2d(
                                    l.StartPoint.Convert2d(plane),
                                    l.EndPoint.Convert2d(plane))));
                            break;
                        case "Polyline":
                            Polyline pl = (Polyline)ent;
                            psc.AddRange(new PolylineSegmentCollection(pl));
                            break;
                        case "Spline":
                            try
                            {
                                Spline spl = (Spline)ent;
                                psc.AddRange(new PolylineSegmentCollection((Polyline)spl.ToPolyline()));
                            }
                            catch { }
                            break;
                        default:
                            break;
                    }
                }
                foreach (PolylineSegmentCollection segs in psc.Join())
                {
                    Polyline pline = segs.ToPolyline();
                    btr.AppendEntity(pline);
                    tr.AddNewlyCreatedDBObject(pline, true);
                }
                tr.Commit();
            }
        }
    }
}
Title: Re: .NET GEOMETRY Routines
Post by: gile on October 16, 2011, 11:02:36 AM
Hi,

The GeometryExtension librairy keeps on growing.

The main new things are Polyline, Polyline2d and Polyline3d extensions methods : GetProjectedPolyline and GetOrthoProjectedPolyline.

These methods create a new Polyline object and may replace the GetProjectedCurve and GetOrthoProjectedCurve methods which do not give expected results with Polyline and Polyline2d entities.
The elliptical arcs resulting of the polyline arcs projection are approximated using the PolylineSegmentCollection(Ellipse ellipse) constructor. This constructor uses the same geometric method as the 'old' AutoCAD native approximation of ellipses (i.e. PELLIPSE = 1).
Title: Re: .NET GEOMETRY Routines
Post by: gile on November 14, 2011, 03:13:32 PM
New version of GeometryExtensions (1.3).
Download updated in the first message.
Corrected some bugs with the GetProjectedPolyline and GetOrthoProjectedPolyline methods.
Title: Re: .NET GEOMETRY Routines
Post by: fixo on May 05, 2012, 04:56:14 AM
Draw bisector berween 2 lines
Code: [Select]
        [CommandMethod("bisect", CommandFlags.Modal | CommandFlags.Redraw)]
        public static void TestForBiLine()
        {
            // Get the current document and database
            Document doc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
            Database db = doc.Database;
            Editor ed = doc.Editor;
            // Select lines
            PromptEntityOptions opt = new PromptEntityOptions("\nSelect a first line:");
            opt.SetRejectMessage("\nMust be selected the line only!");
            opt.AddAllowedClass(typeof(Line),false);

            PromptEntityResult res1 = ed.GetEntity(opt);
            // Set 'black hole' to exit if something wrong
            if (res1.Status != PromptStatus.OK)return;
         opt=   new PromptEntityOptions("\nSelect a second line:");

            PromptEntityResult res2 = ed.GetEntity(opt);
            // Set 'black hole' to exit if something wrong
            if (res2.Status != PromptStatus.OK) return;
            // Open transaction
             Transaction tr = db.TransactionManager.StartTransaction();
             using (tr)
             {
             
                 DBObject obj1 = tr.GetObject(res1.ObjectId, OpenMode.ForWrite);
                 Line ln1 = obj1 as Line;
                 // Set 'black hole' to exit if something wrong
                 if (ln1 == null) return;
               

                 DBObject obj2 = tr.GetObject(res2.ObjectId, OpenMode.ForWrite);
                 Line ln2 = obj2 as Line;
                 // Set 'black hole' to exit if something wrong
                 if (ln2 == null) return;

         
                 Point3d ps1 = ln1.StartPoint;
                 Point3d pe1 = ln1.EndPoint;
                 Point3d ps2 = ln2.StartPoint;
                 Point3d pe2 = ln2.EndPoint;

                 Point3dCollection pts = new Point3dCollection();
                 ln1.IntersectWith(ln2, Intersect.ExtendBoth, pts, 0, 0);
                 // Set 'black hole' to exit if no intersection
                 if (pts.Count != 1)
                 {
                     acadApp.ShowAlertDialog("Lines are colinear or does not intersects each another");

                     return;
                 }
                 // Calculate nearest point to the first line
                 Point3d pk1 = res1.PickedPoint;
                 pk1 = ln1.GetClosestPointTo(pk1, true);
                 // Calculate nearest point to the first line
                 Point3d pk2 = res2.PickedPoint;
                 pk2 = ln2.GetClosestPointTo(pk2, true);
                 // Swap start and end points if lines has different direction
                 if (ps1.DistanceTo(ps2) + pe1.DistanceTo(pe2) > (ps1.DistanceTo(pe2) + pe1.DistanceTo(ps2)))
                 {
                     Point3d tp = new Point3d(ps1.X, ps1.Y, ps1.Z);
                     ps1 = pe1;
                     pe1 = tp;

                 }
                 // create the first temporary Line3d object
                 Line3d tl1 = new Line3d(ps1, ps2);
                 // Calculate midpoint of this line
                 Point3d tp1 = tl1.EvaluatePoint(0.5);
                 // create the second temporary Line3d object
                 Line3d tl2 = new Line3d(pe1, pe2);
                 // Calculate midpoint of this line
                 Point3d tp2 = tl2.EvaluatePoint(0.5);
                 // Create bisector line
                 Line ln = new Line(tp1, tp2);
                 ln.SetDatabaseDefaults();
                 BlockTableRecord btr = tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite) as BlockTableRecord;
                 // Append line to the current space and transaction
                 btr.AppendEntity(ln);
                 tr.AddNewlyCreatedDBObject(ln, true);
                 // Commit transaction
                 tr.Commit();
             }
        }

Regards,

~'J'~
Title: Re: .NET GEOMETRY Routines
Post by: gile on May 05, 2012, 03:13:26 PM
Hi Oleg,

Here's another way using vectors

Code - C#: [Select]
  1.         [CommandMethod("Bisect")]
  2.         public void Bisector()
  3.         {
  4.             Document doc = AcAp.DocumentManager.MdiActiveDocument;
  5.             Database db = doc.Database;
  6.             Editor ed = doc.Editor;
  7.             PromptEntityOptions peo = new PromptEntityOptions("\nSelect the first line: ");
  8.             peo.SetRejectMessage("Selected object is not a line !");
  9.             peo.AddAllowedClass(typeof(Line), true);
  10.             PromptEntityResult per = ed.GetEntity(peo);
  11.             if (per.Status != PromptStatus.OK) return;
  12.             Point3d p1 = per.PickedPoint.TransformBy(ed.CurrentUserCoordinateSystem);
  13.             ObjectId id1 = per.ObjectId;
  14.             peo.Message = "\nSelect the second line: ";
  15.             per = ed.GetEntity(peo);
  16.             if (per.Status != PromptStatus.OK) return;
  17.             Point3d p2 = per.PickedPoint.TransformBy(ed.CurrentUserCoordinateSystem);
  18.             ObjectId id2 = per.ObjectId;
  19.             using (Transaction tr = db.TransactionManager.StartTransaction())
  20.             {
  21.                 Line l1 = (Line)tr.GetObject(id1, OpenMode.ForRead);
  22.                 Line l2 = (Line)tr.GetObject(id2, OpenMode.ForRead);
  23.  
  24.                 // Checks if lines intersect
  25.                 Plane plane;
  26.                 Line3d line1 = new Line3d(l1.StartPoint, l1.EndPoint);
  27.                 Line3d line2 = new Line3d(l2.StartPoint, l2.EndPoint);
  28.                 if (!line1.IsCoplanarWith(line2, out plane) || line1.IsParallelTo(line2))
  29.                     return;
  30.  
  31.                 // Calculates the bisector
  32.                 Point3d inters = line1.IntersectWith(line2)[0];
  33.                 Vector3d vec1 = line1.Direction;
  34.                 Vector3d vec2 = line2.Direction;
  35.                 // Corrects the vectors direction according to picked points
  36.                 if (vec1.DotProduct(inters.GetVectorTo(p1)) < 0)
  37.                     vec1 = vec1.Negate();
  38.                 if (vec2.DotProduct(inters.GetVectorTo(p2)) < 0)
  39.                     vec2 = vec2.Negate();
  40.                 Vector3d bisectDir = (vec1 + vec2) / 2.0;
  41.  
  42.                 // Draws the bisector (XLine)
  43.                 Xline xline = new Xline();
  44.                 xline.UnitDir = bisectDir.GetNormal();
  45.                 xline.BasePoint = inters;
  46.                 BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
  47.                 btr.AppendEntity(xline);
  48.                 tr.AddNewlyCreatedDBObject(xline, true);
  49.                 tr.Commit();
  50.             }
  51.         }

Note: AFAIK, there's no need to call Dispose() on Line3d objects (as for Vector3d, Point3d, ...).
Title: Re: .NET GEOMETRY Routines
Post by: fixo on May 05, 2012, 03:22:47 PM
Thanks, Gilles, I removed these lines from code
Regards,
Oleg
Title: Re: .NET GEOMETRY Routines
Post by: fixo on May 06, 2012, 02:27:34 AM
Circle by 3 points
Code: [Select]

        [CommandMethod("circ3p")]// This method can have any name
        public static void TestFor3PointCircle()
        {
            // Get the document and database
            Document doc = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager.MdiActiveDocument;
            Database db = doc.Database;
            Editor ed = doc.Editor;
            // If we have a valid point selection, get all 3 points
           PromptPointOptions ppo =   new PromptPointOptions("\nPick a point : ");
            ppo.AllowNone = true;
            PromptPointResult ppr;
                ppr = ed.GetPoint(ppo);
                if (ppr.Status != PromptStatus.OK) return;
            Point3d p1= ppr.Value;
            ppo =   new PromptPointOptions("\nPick a second point : ");
                 ppr = ed.GetPoint(ppo);
                if (ppr.Status != PromptStatus.OK) return;
            Point3d p2= ppr.Value;
                        ppo =   new PromptPointOptions("\nPick a third point : ");
                 ppr = ed.GetPoint(ppo);
                if (ppr.Status != PromptStatus.OK) return;
            Point3d p3= ppr.Value;
            // To pass center point by reference
            Point3d pc = new Point3d();
            // Get result
            bool result = CircleCenter(p1, p2, p3, out pc);
            if (result)
                ed.WriteMessage("\nCenter point calculated:\t{0:f3},{1:f3},{2:f3}", pc.X, pc.Y, pc.Z);
            else return;
            // Calculate radius
            double rad = pc.DistanceTo(p1);
            // Open transaction and add circle to the current space
            using (Transaction tr = db.TransactionManager.StartTransaction())
            {
                Circle circ = new Circle(pc, Vector3d.ZAxis, rad);
                BlockTableRecord btr = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
                btr.AppendEntity(circ);
                tr.AddNewlyCreatedDBObject(circ, true);
                // Commit transaction
                tr.Commit();
               
            }
         
        }

        public static bool CircleCenter(Point3d p0, Point3d pm, Point3d p1, out Point3d pc)
        {
            Vector3d v1 = p0 - pm;

            Vector3d v2 = p1 - pm;

            if (v1.IsParallelTo(v2))
            {
                pc = new Point3d();

                return false;
            }

            CircularArc3d crc = new CircularArc3d(p0, pm, p1);

            pc = crc.Center;

            return true;
        }

    }

Regards,

~'J'~
Title: Re: .NET GEOMETRY Routines
Post by: gile on August 15, 2012, 05:52:44 AM
New release of the GeometryExtensions Library (1.4).
See reply #1 (http://www.theswamp.org/index.php?topic=31865.msg373672#msg373672).
Title: Re: .NET GEOMETRY Routines
Post by: lamarn on August 15, 2012, 10:23:58 AM
Hi Gile,
I am intrested in this extension, however i am not a C programmer.
Do you have a workaround to test it or do you have a test file or programm?
Title: Re: .NET GEOMETRY Routines
Post by: gile on August 15, 2012, 01:05:21 PM
Hi,

You can reference the DLL in a VB project to get all extensions methods and classes available in this project.
Title: Re: .NET GEOMETRY Routines
Post by: Jeff H on September 27, 2012, 01:25:34 AM
Gile I can not believe I have not thanked you yet for extension libraries.
So Thanks.
 
Good stuff.
Title: Re: .NET GEOMETRY Routines
Post by: gile on September 27, 2012, 05:03:11 AM
You're very welcome Jeff.
Title: Re: .NET GEOMETRY Routines
Post by: gile on September 14, 2013, 12:58:46 PM
Related to this thread (http://forums.autodesk.com/t5/NET/how-to-drawing-a-line-tangent-to-two-circles-in-C/m-p/4426261/highlight/false#M36988), a little extension for GeometryExtensions library: two methods which extents the CircularArc3d type to find tangents between to a point or another instance of CircularArc3d.

Code - C#: [Select]
  1. using System;
  2. using Autodesk.AutoCAD.Geometry;
  3.  
  4. namespace GeometryExtensions
  5. {
  6.     /// <summary>
  7.     /// Tangent type enum
  8.     /// </summary>
  9.     [Flags]
  10.     public enum TangentType { Inner = 1, Outer = 2 }
  11.  
  12.     /// <summary>
  13.     /// Provides extension methods for the CircularArc3d class.
  14.     /// </summary>
  15.     public static class CircularArc3dExtensions
  16.     {
  17.         /// <summary>
  18.         /// Returns the tangents between the active CircularArc3d instance complete circle and another one.
  19.         /// </summary>
  20.         /// <remarks>
  21.         /// Tangents start points are on the object to which this method applies, end points on the one passed as argument.
  22.         /// Tangents are always returned in the same order: outer tangents before inner tangents, and for both,
  23.         /// the tangent on the left side of the line from this circular arc center to the other one before the one on the right side.
  24.         /// </remarks>
  25.         /// <param name="arc">The object to which this method applies.</param>
  26.         /// <param name="other">The CircularArc3d to which searched for tangents.</param>
  27.         /// <param name="flags">An enum value specifying which type of tangent is returned.</param>
  28.         /// <returns>An array of LineSegment3d representing the tangents (maybe 2 or 4) or null if there is none.</returns>
  29.         /// <exception cref="Autodesk.AutoCAD.Runtime.exception">eNonCoplanarGeometry is thrown if the objects do not lies on the same plane.</exception>
  30.         public static LineSegment3d[] GetTangentsTo(this CircularArc3d arc, CircularArc3d other, TangentType flags)
  31.         {
  32.             // check if circles lies on the same plane
  33.             Vector3d normal = arc.Normal;
  34.             double elev1 = arc.Center.TransformBy(Matrix3d.WorldToPlane(normal)).Z;
  35.             double elev2 = other.Center.TransformBy(Matrix3d.WorldToPlane(normal)).Z;
  36.             if (!(normal.IsParallelTo(other.Normal) &&
  37.                 Math.Abs(elev1 - elev2) < Tolerance.Global.EqualPoint))
  38.                 throw new Autodesk.AutoCAD.Runtime.Exception(
  39.                     Autodesk.AutoCAD.Runtime.ErrorStatus.NonCoplanarGeometry);
  40.  
  41.             // check if a circle is inside the other
  42.             double dist = arc.Center.DistanceTo(other.Center);
  43.             if (dist - Math.Abs(arc.Radius - other.Radius) <= Tolerance.Global.EqualPoint)
  44.                 return null;
  45.  
  46.             // check if circles overlap
  47.             bool overlap = arc.Radius + other.Radius >= dist;
  48.             if (overlap && flags == TangentType.Inner)
  49.                 return null;
  50.  
  51.             CircularArc3d tmp1, tmp2;
  52.             Point3d[] inters;
  53.             Vector3d vec1, vec2, vec = other.Center - arc.Center;
  54.             int i, j;
  55.             LineSegment3d[] result = new LineSegment3d[(int)flags == 3 && !overlap ? 4 : 2];
  56.  
  57.             // outer tangents
  58.             if (flags.HasFlag(TangentType.Outer))
  59.             {
  60.                 if (arc.Radius == other.Radius)
  61.                 {
  62.                     Line3d perp = new Line3d(arc.Center, vec.CrossProduct(normal));
  63.                     inters = arc.IntersectWith(perp);
  64.                     vec1 = (inters[0] - arc.Center).GetNormal();
  65.                     vec2 = (inters[1] - arc.Center).GetNormal();
  66.                     i = vec.GetAngleTo(vec1, normal) < vec.GetAngleTo(vec2, normal) ? 0 : 1;
  67.                     j = i ^ 1;
  68.                     result[i] = new LineSegment3d(inters[0], inters[0] + vec);
  69.                     result[j] = new LineSegment3d(inters[1], inters[1] + vec);
  70.                 }
  71.                 else
  72.                 {
  73.                     Point3d center = arc.Radius < other.Radius ? other.Center : arc.Center;
  74.                     tmp1 = new CircularArc3d(center, normal, Math.Abs(arc.Radius - other.Radius));
  75.                     tmp2 = new CircularArc3d(arc.Center + vec / 2.0, normal, dist / 2.0);
  76.                     inters = tmp1.IntersectWith(tmp2);
  77.                     vec1 = (inters[0] - center).GetNormal();
  78.                     vec2 = (inters[1] - center).GetNormal();
  79.                     i = vec.GetAngleTo(vec1, normal) < vec.GetAngleTo(vec2, normal) ? 0 : 1;
  80.                     j = i ^ 1;
  81.                     result[i] = new LineSegment3d(arc.Center + vec1 * arc.Radius, other.Center + vec1 * other.Radius);
  82.                     result[j] = new LineSegment3d(arc.Center + vec2 * arc.Radius, other.Center + vec2 * other.Radius);
  83.                 }
  84.             }
  85.  
  86.             // inner tangents
  87.             if (flags.HasFlag(TangentType.Inner) && !overlap)
  88.             {
  89.                 double ratio = (arc.Radius / (arc.Radius + other.Radius)) / 2.0;
  90.                 tmp1 = new CircularArc3d(arc.Center + vec * ratio, normal, dist * ratio);
  91.                 inters = arc.IntersectWith(tmp1);
  92.                 vec1 = (inters[0] - arc.Center).GetNormal();
  93.                 vec2 = (inters[1] - arc.Center).GetNormal();
  94.                 i = vec.GetAngleTo(vec1, normal) < vec.GetAngleTo(vec2, normal) ? 2 : 3;
  95.                 j = i == 2 ? 3 : 2;
  96.                 result[i] = new LineSegment3d(arc.Center + vec1 * arc.Radius, other.Center + vec1.Negate() * other.Radius);
  97.                 result[j] = new LineSegment3d(arc.Center + vec2 * arc.Radius, other.Center + vec2.Negate() * other.Radius);
  98.             }
  99.             return result;
  100.         }
  101.  
  102.         /// <summary>
  103.         /// Returns the tangents between the active CircularArc3d instance complete circle and a point.
  104.         /// </summary>
  105.         /// <remarks>
  106.         /// Tangents start points are on the object to which this method applies, end points on the point passed as argument.
  107.         /// Tangents are always returned in the same order: the tangent on the left side of the line from the circular arc center
  108.         /// to the point before the one on the right side.
  109.         /// </remarks>
  110.         /// <param name="arc">The object to which this method applies.</param>
  111.         /// <param name="pt">The Point3d to which tangents are searched</param>
  112.         /// <returns>An array of LineSegement3d representing the tangents (2) or null if there is none.</returns>
  113.         /// <exception cref="Autodesk.AutoCAD.Runtime.exception">eNonCoplanarGeometry is thrown if the objects do not lies on the same plane.</exception>
  114.         public static LineSegment3d[] GetTangentsTo(this CircularArc3d arc, Point3d pt)
  115.         {
  116.             // check if circle and point lies on the plane
  117.             Vector3d normal = arc.Normal;
  118.             double elev1 = arc.Center.TransformBy(Matrix3d.WorldToPlane(normal)).Z;
  119.             double elev2 = pt.TransformBy(Matrix3d.WorldToPlane(normal)).Z;
  120.             if (Math.Abs(elev1 - elev2) < Tolerance.Global.EqualPoint)
  121.                 throw new Autodesk.AutoCAD.Runtime.Exception(
  122.                     Autodesk.AutoCAD.Runtime.ErrorStatus.NonCoplanarGeometry);
  123.  
  124.             // check if the point is inside the circle
  125.             Point3d center = arc.Center;
  126.             if (pt.DistanceTo(center) <= arc.Radius)
  127.                 return null;
  128.  
  129.             Vector3d vec = pt.GetVectorTo(center) / 2.0;
  130.             CircularArc3d tmp = new CircularArc3d(pt + vec, arc.Normal, vec.Length);
  131.             Point3d[] inters = arc.IntersectWith(tmp);
  132.             LineSegment3d[] result = new LineSegment3d[2];
  133.             int i = vec.GetAngleTo(inters[0] - center, normal) < vec.GetAngleTo(inters[1] - center, normal) ? 0 : 1;
  134.             int j = i ^ 1;
  135.             result[i] = new LineSegment3d(inters[0], pt);
  136.             result[j] = new LineSegment3d(inters[1], pt);
  137.             return result;
  138.         }
  139.     }
  140. }
  141.  

A (maybe usefull) command example which draws a closed polyline along the outer tangents and the trimmed selected circles

Code - C#: [Select]
  1.         [CommandMethod("JoinCircles", CommandFlags.Modal)]
  2.         public void JoinCircles()
  3.         {
  4.             Document doc = Application.DocumentManager.MdiActiveDocument;
  5.             Database db = doc.Database;
  6.             Editor ed = doc.Editor;
  7.  
  8.             PromptEntityOptions peo = new PromptEntityOptions("\nSelect a circle: ");
  9.             peo.SetRejectMessage("Only a circle.");
  10.             peo.AddAllowedClass(typeof(Circle), true);
  11.             PromptEntityResult per = ed.GetEntity(peo);
  12.             if (per.Status != PromptStatus.OK)
  13.                 return;
  14.             ObjectId id1 = per.ObjectId;
  15.  
  16.             peo.Message = "\nSelect another circle: ";
  17.             ObjectId id2;
  18.             while (true)
  19.             {
  20.                 per = ed.GetEntity(peo);
  21.                 if (per.Status != PromptStatus.OK)
  22.                     return;
  23.                 id2 = per.ObjectId;
  24.                 if (id1 == id2)
  25.                     ed.WriteMessage("\nThe second circle is the same as the first one.");
  26.                 else break;
  27.             }
  28.  
  29.             try
  30.             {
  31.                 using (Transaction tr = db.TransactionManager.StartTransaction())
  32.                 {
  33.                     Circle c1 = (Circle)tr.GetObject(id1, OpenMode.ForRead);
  34.                     Circle c2 = (Circle)tr.GetObject(id2, OpenMode.ForRead);
  35.                     CircularArc3d ca1 = new CircularArc3d(c1.Center, c1.Normal, c1.Radius);
  36.                     CircularArc3d ca2 = new CircularArc3d(c2.Center, c2.Normal, c2.Radius);
  37.                     LineSegment3d[] lines = ca1.GetTangentsTo(ca2, TangentType.Outer);
  38.                     if (lines != null)
  39.                     {
  40.                         BlockTableRecord btr =
  41.                             (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
  42.                         Vector3d vec = c1.Center.GetVectorTo(c2.Center);
  43.                         Plane plane = new Plane(Point3d.Origin, c1.Normal);
  44.                         double a1 = vec.GetAngleTo(ca1.Center.GetVectorTo(lines[1].StartPoint), ca1.Normal) -
  45.                             vec.GetAngleTo(ca1.Center.GetVectorTo(lines[0].StartPoint), ca1.Normal);
  46.                         double a2 = vec.Negate().GetAngleTo(ca2.Center.GetVectorTo(lines[0].EndPoint), ca1.Normal) -
  47.                             vec.Negate().GetAngleTo(ca2.Center.GetVectorTo(lines[1].EndPoint), ca1.Normal);
  48.                         Polyline pline = new Polyline(4);
  49.                         pline.AddVertexAt(0, lines[0].StartPoint.Convert2d(plane), Math.Tan(a1 / 4.0), 0.0, 0.0);
  50.                         pline.AddVertexAt(1, lines[1].StartPoint.Convert2d(plane), 0.0, 0.0, 0.0);
  51.                         pline.AddVertexAt(2, lines[1].EndPoint.Convert2d(plane), Math.Tan(a2 / 4.0), 0.0, 0.0);
  52.                         pline.AddVertexAt(3, lines[0].EndPoint.Convert2d(plane), 0.0, 0.0, 0.0);
  53.                         pline.Closed = true;
  54.                         pline.Normal = c1.Normal;
  55.                         pline.Elevation = c1.Center.TransformBy(Matrix3d.WorldToPlane(c1.Normal)).Z;
  56.                         btr.AppendEntity(pline);
  57.                         tr.AddNewlyCreatedDBObject(pline, true);
  58.                         c1.UpgradeOpen();
  59.                         c2.UpgradeOpen();
  60.                         c1.Erase();
  61.                         c2.Erase();
  62.                     }
  63.                     tr.Commit();
  64.                 }
  65.             }
  66.             catch (System.Exception exn)
  67.             {
  68.                 ed.WriteMessage("\nError: " + exn.Message);
  69.             }
  70.         }
Title: Re: .NET GEOMETRY Routines
Post by: gile on September 15, 2013, 11:36:01 AM
As i encountered some issues with the CircularArc3d.IntersectWith() method in some randomly 3d rotated planes, I decided to try the CircularArc2d route it seems to work fine.

The two methods shown upper now call equivalent extension methods for the CircularArc2d type after converting the CircularArc3d instance into CircularArc2d ones.
The Point2d.Convert3d() extension method is extracted from the GeometryExtensions library.

Code - C#: [Select]
  1. using System;
  2. using Autodesk.AutoCAD.Geometry;
  3.  
  4. namespace GeometryExtensions
  5. {
  6.     /// <summary>
  7.     /// Tangent type enum
  8.     /// </summary>
  9.     [Flags]
  10.     public enum TangentType { Inner = 1, Outer = 2 }
  11.  
  12.     /// <summary>
  13.     /// Provides extension methods for the Point2d structure.
  14.     /// </summary>
  15.     public static class Point2dExtensions
  16.     {        
  17.         /// <summary>
  18.         /// Converts a 2d point into a 3d point according to the plane defined by
  19.         /// the specified normal vector and elevation.
  20.         /// </summary>
  21.         /// <param name="pt">The instance to which the method applies.</param>
  22.         /// <param name="normal">The normal vector of the plane which the point lies on.</param>
  23.         /// <param name="elevation">The elevation of the plane which the point lies on.</param>
  24.         /// <returns>The corresponding 3d point</returns>
  25.         public static Point3d Convert3d(this Point2d pt, Vector3d normal, double elevation)
  26.         {
  27.             return new Point3d(pt.X, pt.Y, elevation).TransformBy(Matrix3d.PlaneToWorld(normal));
  28.         }
  29.     }
  30.  
  31.     /// <summary>
  32.     /// Provides extension methods for the CircularArc3d class.
  33.     /// </summary>
  34.     public static class CircularArc3dExtensions
  35.     {
  36.         /// <summary>
  37.         /// Returns the tangents between the active CircularArc3d instance complete circle and a point.
  38.         /// </summary>
  39.         /// <remarks>
  40.         /// Tangents start points are on the object to which this method applies, end points on the point passed as argument.
  41.         /// Tangents are always returned in the same order: the tangent on the left side of the line from the circular arc center
  42.         /// to the point before the one on the right side.
  43.         /// </remarks>
  44.         /// <param name="arc">The object to which this method applies.</param>
  45.         /// <param name="pt">The Point3d to which tangents are searched</param>
  46.         /// <returns>An array of LineSegement3d representing the tangents (2) or null if there is none.</returns>
  47.         /// <exception cref="Autodesk.AutoCAD.Runtime.exception">eNonCoplanarGeometry is thrown if the objects do not lies on the same plane.</exception>
  48.         public static LineSegment3d[] GetTangentsTo(this CircularArc3d arc, Point3d pt)
  49.         {
  50.             // check if arc and point lies on the plane
  51.             Vector3d normal = arc.Normal;
  52.             Matrix3d WCS2OCS = Matrix3d.WorldToPlane(normal);
  53.             double elevation = arc.Center.TransformBy(WCS2OCS).Z;
  54.             if (Math.Abs(elevation - pt.TransformBy(WCS2OCS).Z) < Tolerance.Global.EqualPoint)
  55.                 throw new Autodesk.AutoCAD.Runtime.Exception(
  56.                     Autodesk.AutoCAD.Runtime.ErrorStatus.NonCoplanarGeometry);
  57.  
  58.             Plane plane = new Plane(Point3d.Origin, normal);
  59.             Matrix3d OCS2WCS = Matrix3d.PlaneToWorld(plane);
  60.             CircularArc2d ca2d = new CircularArc2d(arc.Center.Convert2d(plane), arc.Radius);
  61.             LineSegment2d[] lines2d = ca2d.GetTangentsTo(pt.Convert2d(plane));
  62.  
  63.             if (lines2d == null)
  64.                 return null;
  65.  
  66.             LineSegment3d[] result = new LineSegment3d[lines2d.Length];
  67.             for (int i = 0; i < lines2d.Length; i++)
  68.             {
  69.                 LineSegment2d ls2d = lines2d[i];
  70.                 //Point3d p1 = new Point3d(ls2d.StartPoint.X, ls2d.StartPoint.Y, elevation);
  71.                 //Point3d p2 = new Point3d(ls2d.EndPoint.X, ls2d.EndPoint.Y, elevation);
  72.                 //result[i] = new LineSegment3d(p1.TransformBy(OCS2WCS), p2.TransformBy(OCS2WCS));
  73.                 result[i] = new LineSegment3d(ls2d.StartPoint.Convert3d(normal, elevation), ls2d.EndPoint.Convert3d(normal, elevation));
  74.             }
  75.             return result;
  76.         }
  77.  
  78.         /// <summary>
  79.         /// Returns the tangents between the active CircularArc3d instance complete circle and another one.
  80.         /// </summary>
  81.         /// <remarks>
  82.         /// Tangents start points are on the object to which this method applies, end points on the one passed as argument.
  83.         /// Tangents are always returned in the same order: outer tangents before inner tangents, and for both,
  84.         /// the tangent on the left side of the line from this circular arc center to the other one before the one on the right side.
  85.         /// </remarks>
  86.         /// <param name="arc">The object to which this method applies.</param>
  87.         /// <param name="other">The CircularArc3d to which searched for tangents.</param>
  88.         /// <param name="flags">An enum value specifying which type of tangent is returned.</param>
  89.         /// <returns>An array of LineSegment3d representing the tangents (maybe 2 or 4) or null if there is none.</returns>
  90.         /// <exception cref="Autodesk.AutoCAD.Runtime.exception">eNonCoplanarGeometry is thrown if the objects do not lies on the same plane.</exception>
  91.         public static LineSegment3d[] GetTangentsTo(this CircularArc3d arc, CircularArc3d other, TangentType flags)
  92.         {
  93.             // check if circles lies on the same plane
  94.             Vector3d normal = arc.Normal;
  95.             Matrix3d WCS2OCS = Matrix3d.WorldToPlane(normal);
  96.             double elevation = arc.Center.TransformBy(WCS2OCS).Z;
  97.             if (!(normal.IsParallelTo(other.Normal) &&
  98.                 Math.Abs(elevation - other.Center.TransformBy(WCS2OCS).Z) < Tolerance.Global.EqualPoint))
  99.                 throw new Autodesk.AutoCAD.Runtime.Exception(
  100.                     Autodesk.AutoCAD.Runtime.ErrorStatus.NonCoplanarGeometry);
  101.  
  102.             Plane plane = new Plane(Point3d.Origin, normal);
  103.             Matrix3d OCS2WCS = Matrix3d.PlaneToWorld(plane);
  104.             CircularArc2d ca2d1 = new CircularArc2d(arc.Center.Convert2d(plane), arc.Radius);
  105.             CircularArc2d ca2d2 = new CircularArc2d(other.Center.Convert2d(plane), other.Radius);
  106.             LineSegment2d[] lines2d = ca2d1.GetTangentsTo(ca2d2, flags);
  107.  
  108.             if (lines2d == null)
  109.                 return null;
  110.  
  111.             LineSegment3d[] result = new LineSegment3d[lines2d.Length];
  112.             for (int i = 0; i < lines2d.Length; i++)
  113.             {
  114.                 LineSegment2d ls2d = lines2d[i];
  115.                 //Point3d p1 = new Point3d(ls2d.StartPoint.X, ls2d.StartPoint.Y, elevation);
  116.                 //Point3d p2 = new Point3d(ls2d.EndPoint.X, ls2d.EndPoint.Y, elevation);
  117.                 //result[i] = new LineSegment3d(p1.TransformBy(OCS2WCS), p2.TransformBy(OCS2WCS));
  118.                 result[i] = new LineSegment3d(ls2d.StartPoint.Convert3d(normal, elevation), ls2d.EndPoint.Convert3d(normal, elevation));
  119.             }
  120.             return result;
  121.         }
  122.     }
  123.  
  124.     /// <summary>
  125.     /// Provides extension methods for the CircularArc2d class.
  126.     /// </summary>
  127.     public static class CircularArc2dExtensions
  128.     {
  129.         /// <summary>
  130.         /// Returns the tangents between the active CircularArc2d instance complete circle and a point.
  131.         /// </summary>
  132.         /// <remarks>
  133.         /// Tangents start points are on the object to which this method applies, end points on the point passed as argument.
  134.         /// Tangents are always returned in the same order: the tangent on the left side of the line from the circular arc center
  135.         /// to the point before the one on the right side.
  136.         /// </remarks>
  137.         /// <param name="arc">The object to which this method applies.</param>
  138.         /// <param name="pt">The Point2d to which tangents are searched</param>
  139.         /// <returns>An array of LineSegement2d representing the tangents (2) or null if there is none.</returns>
  140.         public static LineSegment2d[] GetTangentsTo(this CircularArc2d arc, Point2d pt)
  141.         {
  142.             // check if the point is inside the circle
  143.             Point2d center = arc.Center;
  144.             if (pt.GetDistanceTo(center) <= arc.Radius)
  145.                 return null;
  146.  
  147.             Vector2d vec = center.GetVectorTo(pt) / 2.0;
  148.             CircularArc2d tmp = new CircularArc2d(center + vec, vec.Length);
  149.             Point2d[] inters = arc.IntersectWith(tmp);
  150.             if (inters == null)
  151.                 return null;
  152.             LineSegment2d[] result = new LineSegment2d[2];
  153.             Vector2d v1 = inters[0] - center;
  154.             Vector2d v2 = inters[1] - center;
  155.             int i = vec.X * v1.Y - vec.Y - v1.X > 0 ? 0 : 1;
  156.             int j = i ^ 1;
  157.             result[i] = new LineSegment2d(inters[0], pt);
  158.             result[j] = new LineSegment2d(inters[1], pt);
  159.             return result;
  160.         }
  161.  
  162.         /// <summary>
  163.         /// Returns the tangents between the active CircularArc2d instance complete circle and another one.
  164.         /// </summary>
  165.         /// <remarks>
  166.         /// Tangents start points are on the object to which this method applies, end points on the one passed as argument.
  167.         /// Tangents are always returned in the same order: outer tangents before inner tangents, and for both,
  168.         /// the tangent on the left side of the line from this circular arc center to the other one before the one on the right side.
  169.         /// </remarks>
  170.         /// <param name="arc">The object to which this method applies.</param>
  171.         /// <param name="other">The CircularArc2d to which searched for tangents.</param>
  172.         /// <param name="flags">An enum value specifying which type of tangent is returned.</param>
  173.         /// <returns>An array of LineSegment2d representing the tangents (maybe 2 or 4) or null if there is none.</returns>
  174.         public static LineSegment2d[] GetTangentsTo(this CircularArc2d arc, CircularArc2d other, TangentType flags)
  175.         {
  176.             // check if a circle is inside the other
  177.             double dist = arc.Center.GetDistanceTo(other.Center);
  178.             if (dist - Math.Abs(arc.Radius - other.Radius) <= Tolerance.Global.EqualPoint)
  179.                 return null;
  180.  
  181.             // check if circles overlap
  182.             bool overlap = arc.Radius + other.Radius >= dist;
  183.             if (overlap && flags == TangentType.Inner)
  184.                 return null;
  185.  
  186.             CircularArc2d tmp1, tmp2;
  187.             Point2d[] inters;
  188.             Vector2d vec1, vec2, vec = other.Center - arc.Center;
  189.             int i, j;
  190.             LineSegment2d[] result = new LineSegment2d[(int)flags == 3 && !overlap ? 4 : 2];
  191.  
  192.             // outer tangents
  193.             if (flags.HasFlag(TangentType.Outer))
  194.             {
  195.                 if (arc.Radius == other.Radius)
  196.                 {
  197.                     Line2d perp = new Line2d(arc.Center, vec.GetPerpendicularVector());
  198.                     inters = arc.IntersectWith(perp);
  199.                     if (inters == null)
  200.                         return null;
  201.                     vec1 = (inters[0] - arc.Center).GetNormal();
  202.                     vec2 = (inters[1] - arc.Center).GetNormal();
  203.                     i = vec.X * vec1.Y - vec.Y - vec1.X > 0 ? 0 : 1;
  204.                     j = i ^ 1;
  205.                     result[i] = new LineSegment2d(inters[0], inters[0] + vec);
  206.                     result[j] = new LineSegment2d(inters[1], inters[1] + vec);
  207.                 }
  208.                 else
  209.                 {
  210.                     Point2d center = arc.Radius < other.Radius ? other.Center : arc.Center;
  211.                     tmp1 = new CircularArc2d(center, Math.Abs(arc.Radius - other.Radius));
  212.                     tmp2 = new CircularArc2d(arc.Center + vec / 2.0, dist / 2.0);
  213.                     inters = tmp1.IntersectWith(tmp2);
  214.                     if (inters == null)
  215.                         return null;
  216.                     vec1 = (inters[0] - center).GetNormal();
  217.                     vec2 = (inters[1] - center).GetNormal();
  218.                     i = vec.X * vec1.Y - vec.Y - vec1.X > 0 ? 0 : 1;
  219.                     j = i ^ 1;
  220.                     result[i] = new LineSegment2d(arc.Center + vec1 * arc.Radius, other.Center + vec1 * other.Radius);
  221.                     result[j] = new LineSegment2d(arc.Center + vec2 * arc.Radius, other.Center + vec2 * other.Radius);
  222.                 }
  223.             }
  224.  
  225.             // inner tangents
  226.             if (flags.HasFlag(TangentType.Inner) && !overlap)
  227.             {
  228.                 double ratio = (arc.Radius / (arc.Radius + other.Radius)) / 2.0;
  229.                 tmp1 = new CircularArc2d(arc.Center + vec * ratio, dist * ratio);
  230.                 inters = arc.IntersectWith(tmp1);
  231.                 if (inters == null)
  232.                     return null;
  233.                 vec1 = (inters[0] - arc.Center).GetNormal();
  234.                 vec2 = (inters[1] - arc.Center).GetNormal();
  235.                 i = vec.X * vec1.Y - vec.Y - vec1.X > 0 ? 2 : 3;
  236.                 j = i == 2 ? 3 : 2;
  237.                 result[i] = new LineSegment2d(arc.Center + vec1 * arc.Radius, other.Center + vec1.Negate() * other.Radius);
  238.                 result[j] = new LineSegment2d(arc.Center + vec2 * arc.Radius, other.Center + vec2.Negate() * other.Radius);
  239.             }
  240.             return result;
  241.         }
  242.     }
  243. }
  244.  

A Test command which draws all tangents between the two selected circles. the lines are colored according to their order in the LineSegment3d array returned by GetTangentsTo() (1 red, 2 yellow, 3 green, 4 cyan).

Code - C#: [Select]
  1.         [CommandMethod("Test")]
  2.         public void Test()
  3.         {
  4.             Document doc = Application.DocumentManager.MdiActiveDocument;
  5.             Database db = doc.Database;
  6.             Editor ed = doc.Editor;
  7.  
  8.             PromptEntityOptions peo = new PromptEntityOptions("\nSelect a circle: ");
  9.             peo.SetRejectMessage("Only a circle.");
  10.             peo.AddAllowedClass(typeof(Circle), true);
  11.             PromptEntityResult per = ed.GetEntity(peo);
  12.             if (per.Status != PromptStatus.OK)
  13.                 return;
  14.             ObjectId id1 = per.ObjectId;
  15.  
  16.             peo.Message = "\nSelect another circle: ";
  17.             ObjectId id2;
  18.             while (true)
  19.             {
  20.                 per = ed.GetEntity(peo);
  21.                 if (per.Status != PromptStatus.OK)
  22.                     return;
  23.                 id2 = per.ObjectId;
  24.                 if (id1 == id2)
  25.                     ed.WriteMessage("\nThe second circle is the same as the first one.");
  26.                 else break;
  27.             }
  28.  
  29.             try
  30.             {
  31.                 using (Transaction tr = db.TransactionManager.StartTransaction())
  32.                 {
  33.                     Circle c1 = (Circle)tr.GetObject(id1, OpenMode.ForRead);
  34.                     Circle c2 = (Circle)tr.GetObject(id2, OpenMode.ForRead);
  35.  
  36.                     CircularArc3d ca1 = new CircularArc3d(c1.Center, c1.Normal, c1.Radius);
  37.                     CircularArc3d ca2 = new CircularArc3d(c2.Center, c2.Normal, c2.Radius);
  38.                     LineSegment3d[] lines = ca1.GetTangentsTo(ca2, TangentType.Inner | TangentType.Outer);
  39.                     if (lines != null)
  40.                     {
  41.                         BlockTableRecord btr =
  42.                             (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
  43.                         for (int i = 0; i < lines.Length; i++)
  44.                         {
  45.                             Line line = new Line(lines[i].StartPoint, lines[i].EndPoint);
  46.                             line.ColorIndex = i + 1;
  47.                             btr.AppendEntity(line);
  48.                             tr.AddNewlyCreatedDBObject(line, true);
  49.                         }
  50.                     }
  51.                     tr.Commit();
  52.                 }
  53.             }
  54.             catch (System.Exception exn)
  55.             {
  56.                 ed.WriteMessage("\n" + exn.Message);
  57.             }
  58.         }
Title: Re: .NET GEOMETRY Routines
Post by: gile on September 15, 2013, 05:35:46 PM
GeometryExtensions Update: version 1.6

Added: CircularArc2d and CircularArc3d GetTangentsTo methods.
Reorganized: one class per extended type.

GeomExtDoc.chm help file updated.

Attachments in reply #1 (http://www.theswamp.org/index.php?topic=31865.msg373672#msg373672)
Title: Re: .NET GEOMETRY Routines
Post by: Bryco on May 14, 2016, 02:14:44 PM
Code to extend a line to the Ucs
Code: [Select]

        [CommandMethod("trt", CommandFlags.UsePickSet)]

        public void TrimToPlane()
        {
            Database db = HostApplicationServices.WorkingDatabase;
            Document doc = acadApp.DocumentManager.MdiActiveDocument;
            Editor ed = doc.Editor;
            PromptEntityOptions peo = new PromptEntityOptions
                ("\nPick a line to extend/trim to the current Ucs:");
            peo.SetRejectMessage("Must be a line:");
            peo.AddAllowedClass(typeof(Line), true);
            PromptEntityResult per = ed.GetEntity(peo);
            if (per.Status != PromptStatus.OK) return;
            Matrix3d Ucs = ed.CurrentUserCoordinateSystem;
            using(Transaction tr = db.TransactionManager.StartTransaction())       
            { 
                Line line=tr.GetObject(per.ObjectId,OpenMode.ForWrite) as Line;
                Point3d p = line.StartPoint;
                Point3d pt = Ucs.CoordinateSystem3d.Origin;
                Vector3d v = line.EndPoint - line.StartPoint; //line.Delta;//.GetNormal();
                Vector3d n = Ucs.CoordinateSystem3d.Zaxis;
                //double Elev = pt.X * n.X + pt.Y * n.Y + pt.Z * n.Z;
                //double Dist = (Elev - (p.X * n.X) - (p.Y * n.Y) - (p.Z * n.Z)) / ((v.X * n.X) + (v.Y * n.Y) + (v.Z * n.Z));
                //Point3d intersectionPt=new Point3d(p.X + Dist * v.X, p.Y + Dist * v.Y, p.Z + Dist * v.Z);
                if (Math.Abs( n.DotProduct(v))<0.00000001)
                {
                    MessageBox.Show("The plane and line-segment are parallel, no intersection:");
                    return;
                }
                double t = n.DotProduct((pt-p))/n.DotProduct(v);
                Point3d ptofIntersection=p + v.MultiplyBy(t);
                if ((p - ptofIntersection).Length > (line.EndPoint - ptofIntersection).Length)
                    line.EndPoint = ptofIntersection;
                else
                    line.StartPoint = ptofIntersection;

            //http://www.thepolygoners.com/tutorials/lineplane/lineplane.html
                //If:
                //t > 0 and t < 1:The intersection occurs between the two end points
                //t = 0:The intersection falls on the first end point
                //t = 1:Intersection falls on the second end point
                //t > 1:Intersection occurs beyond second end Point
                //t < 0:Intersection happens before 1st end point.
                 
                tr.Commit();       
            }

        }
Title: Re: .NET GEOMETRY Routines
Post by: nobody on May 14, 2016, 10:27:19 PM
Thanks..these have been consistently helpful
Title: Re: .NET GEOMETRY Routines
Post by: BKSpurgeon on August 03, 2017, 04:35:11 AM
Does it make sense to add these ones as well? Not sure if I know what I'm doing, these are for the PolylineSegmentCollection class, many thanks to Gilles. chrs.

Code: [Select]
 
/// <summary>
        /// Creates a new instance of PolylineSegmentCollection from a Line.
        /// </summary>
        /// <param name="line">A Line instance.</param>
        public PolylineSegmentCollection(Line line)
        {
            Plane plane = new Plane(Point3d.Origin, Vector3d.ZAxis);
            _contents.Add(new PolylineSegment(
                               new LineSegment2d(
                                   line.StartPoint.Convert2d(plane),
                                   line.EndPoint.Convert2d(plane))));
        }

        /// <summary>
        /// Creates a new instance of PolylineSegmentCollection from a spline.
        /// </summary>
        /// <param name="spline">A spline instance.</param>
        public PolylineSegmentCollection(Spline spline)
        {
            _contents.AddRange(new PolylineSegmentCollection((Polyline)spline.ToPolyline()));
        }

        /// <summary>
        /// Creates a new instance of PolylineSegmentCollection from an arc.
        /// </summary>
        /// <param name="spline">An Arc instance.</param>
        public PolylineSegmentCollection(Arc arc)
        {
            Plane plane = new Plane(Point3d.Origin, Vector3d.ZAxis);
            _contents.Add(new PolylineSegment(
                                new CircularArc2d(
                                    arc.Center.Convert2d(plane),
                                    arc.Radius,
                                    arc.StartAngle,
                                    arc.EndAngle,
                                    Vector2d.XAxis,
                                    false)));
        }


Hi,

Some geometry extensions methods for Point2d, Point3d, CircularArc2d, Polyline and Polyline2d classes and two little (maybe usefull) classes: Triangle2d, Triangle3d.

All these classes work together and have to be compiled in a single DLL.

The extension methods requires to reference System.Core (.NET Framework 3.5)

EDIT: extended

The GeomExt class, contains the following extension methods:

Matrix3d Editor.UCS2WCS()
Matrix3d Editor.WCS2UCS()
Matrix3d Editor.DCS2WCS()
Matrix3d Editor.WCS2DCS()
Matrix3d Editor.DCS2PSDCS()
Matrix3d Editor.PSDCS2DCS()

Matrix3d Viewport.DCS2WCS()
Matrix3d Viewport.WCS2DCS()
Matrix3d Viewport.PSDCS2DCS()
Matrix3d Viewport.DCS2PSDCS()

Point3d Point2d.Convert3d()
Point3d Point2d.Convert3d(Plane plane)
Point3d Point2d.Convert3d(Vector3d normal, double elevation)
Point2d Point2d.Flatten(Vector3d normal)
bool IsBetween(Point2d p1, Point2d p2)
bool IsBetween(Point2d p1, Point2d p2, Tolerance tol)
Point2d Point2d.Polar(double angle, double distance)

Point3d Point3d.Convert2d()
Point3d Point3d.Flatten()
bool IsBetween(Point3d p1, Point3d p2)
bool IsBetween(Point3d p1, Point3d p2, Tolerance tol)
Point3d Point3d.Polar(double angle, double distance)
Point3d Point3d.Trans(int from, int to)
Point3d Point3d.Trans(Editor ed, int from, int to)
Point3d Point3d.Trans(CoordSystem from, CoordSystem to)
Point3d Point3d.Trans(Editor ed, CoordSystem from, CoordSystem to)

Vector3d Vector3d .Flatten()

void Point2dCollection.RemoveDuplicate()
void Point2dCollection.RemoveDuplicate(Tolerance tol)
bool Point2dCollection.Contains(Point2d pt, Tolerance tol)

void Point3dCollection.RemoveDuplicate()
void Point3dCollection.RemoveDuplicate(Tolerance tol)
bool Point3dCollection.Contains(Point3d pt, Tolerance tol)

double CircularArc2d.AlgebricArea()
Point2d CircularArc2d.Centroid()

Point2d Polyline.Centroid2d()
Point3d Polyline.Centroid()
Polyline Polyline.GetProjectedPolyline(Plane plane, Vector3d direction)
Polyline Polyline.GetOrthoProjectedPolyline(Plane plane)

Point3d Polyline2d.Centroid()
CircularArc3d Polyline2d.GetArcSegmentAt(int index)
CircularArc2d Polyline2d.GetArcSegment2dAt(int index)
LineSegment3d Polyline2d.GetLineSegmentAt(int index)
LineSegment2d Polyline2d.GetLineSegment2dAt(int index)
Polyline Polyline2d.GetProjectedPolyline(Plane plane, Vector3d direction)
Polyline Polyline2d.GetOrthoProjectedPolyline(Plane plane)
List<Vertex2d> Polyline2d.GetVertices()

Polyline Polyline3d.GetProjectedPolyline(Plane plane, Vector3d direction)
Polyline Polyline3d.GetOrthoProjectedPolyline(Plane plane)

Point3d Region.Centroid()

Point3d Spline.Centroid()

Polyline Ellipse.ToPolyline()


Triangle<T> abstract class

Constructors
Triangle()
Triangle(T[] pts)
Triangle(T a, T b, T c)

Indexor
T Item

Methods
T Inverse()
void Set(T[] pts)
void Set(T a, T b, T c)
T[] ToArray()


Triangle2d : Triangle<Point2d> class

Constructors
Triangle2d() : base()
Triangle2d(Point2d[] pts) : base(pts)
Triangle2d(Point2d a, Point2d b, Point2d c) : base(a, b, c)
Triangle2d(Point2d org, Vector2d v1, Vector2d v2)

Properties
double AlgebricArea
Point2d Centroid
CircularArc2d CircumscribedCircle
CircularArc2d InscribedCircle
bool IsClockwise

Methods
Triangle3d Convert3d(Plane plane)
Triangle3d Convert3d(Vector3d normal, double elevation)
double GetAngleAt(int index)
LineSegment2d GetSegmentAt(int index)
List<Point2d> IntersectWith(LinearEntity2d le2d)
List<Point2d> IntersectWith(LinearEntity2d le2d, Tolerance tol)
bool IsEqualTo(Triangle2d t2d)
bool IsEqualTo(Triangle2d t2d, Tolerance tol)
bool IsPointInside(Point2d pt)
bool IsPointOn(Point2d pt)
void Set(Point2d org, Vector2d v1, Vector2d v2)
Triangle2d TransformBy(Matrix2d mat)


Triangle3d : Triangle<Point3d> class

Constructors
Triangle3d() : base()
Triangle3d(Point3d[] pts) : base(pts)
Triangle3d(Point3d a, Point3d b, Point3d c) : base(a, b, c)
Triangle3d(Point3d org, Vector3d v1, Vector3d v2)

Properties
double Area
Point3d Centroid
CircularArc3d CircumscribedCircle
double Elevation
Vector3d GreatestSlope
Vector3d Horizontal
CircularArc3d InscribedCircle
bool IsHorizontal
Vector3d Normal
double SlopePerCent
Matrix3d SlopeUCS

Methods
Triangle2d Convert2d()
Triangle2d Flatten()
double GetAngleAt(int index)
BoundedPlane GetBoundedPlane()
Plane GetPlane()
LineSegment3d GetSegmentAt(int index)
bool IsEqualTo(Triangle3d t3d)
bool IsEqualTo(Triangle3d t3d, Tolerance tol)
bool IsPointInside(Point3d pt)
bool IsPointOn(Point3d pt)
void Set(Point3d org, Vector3d v1, Vector3d v2)
Triangle3d Transformby(Matrix3d mat)


PolylineSegment class

Constructors

PolylineSegment(Point2d startPoint, Point2d endPoint)
PolylineSegment(Point2d startPoint, Point2d endPoint, double bulge)
PolylineSegment(Point2d startPoint, Point2d endPoint, double bulge, double constantWidth)
PolylineSegment(Point2d startPoint, Point2d endPoint, double bulge, double startWidth, double endWidth)
PolylineSegment(Line2dSegment line)
PolylineSegment(CircularArc2d arc)

Properties

double Bulge
Point2d EndPoint
double EndWidth
bool IsLinear
Point2d StartPoint
double StartWidth

Methods

bool Equals(object obj)
PolylineSegment Clone()
int GetHashCode()
double GetParameterOf(Point3d pt)
void Inverse()
CircularArc2d ToCircularArc()
Curve2d ToCurve2d()
LineSegment2d ToLineSegment()
string ToString()


PolylineSegmentCollection : IList<PolylineSegment> class

Constructors

PolylineSegmentCollection()
PolylineSegmentCollection(Circle circle)
PolylineSegmentCollection(Ellipse ellipse)
PolylineSegmentCollection(Polyline pline)
PolylineSegmentCollection(Polyline2d pline)
PolylineSegmentCollection(IEnumerable<PolylineSegment> segments)

Methods

void Add(PolylineSegment segment)
void AddRange(IEnumerable<PolylineSegment> range)
void Clear()
bool Contains(PolylineSegment segment)
int FinIindex(Predicate<PolylineSegment> match)
int GetClosestSegmentTo(Point2d pt)
IEnumerator<PolylineSegment> GetEnumerator()
int IndexOf(PolylineSegment item)
void Insert(int index, PolylineSegment item)
void InsertRange(int index, IEnumerable<PolylineSegment> collection)
List<PolylineSegmentCollection> Join()
List<PolylineSegmentCollection> Join(Tolerance tol)
bool Remove(PolylineSegment item)
void RemoveAt(int index)
public void RemoveRange(int index, int count)
Polyline ToPolyline()

For more information, see the attached documentation.

GeomExtDoc.zip contains a .chm documentation file.
GeometryExtensions(1.6).zip contains the C# source code files and the DLLs (GeometryExtensions_18.dll for A2010-2012 and GeometryExtensions_19 for A2013+)

<EDIT: version 1.6> added CircularArc2d and CircularArc3d GetTangentsTo methods, reorganized: one class per extended type.
Title: Re: .NET GEOMETRY Routines
Post by: BKSpurgeon on August 07, 2017, 12:09:12 AM
Why not allow for pull requests for GeometryExtensions classes? That way people can contribute, i'm sure many will.

Here is a small method but handy method i've added to the PolylineExtensions class which you all may find useful:

Code: [Select]
public static Point3d[] GetPolylinePoints(this Polyline pline)
        {
               
            int vn = pline.NumberOfVertices;

            Point3d[] pointsInPolyline = new Point3d[vn];
            for (int i = 0; i < vn; i++)
            {               
                Point3d pt = pline.GetPoint3dAt(i);
                pointsInPolyline[i] = pt;
            }

            return pointsInPolyline;
        }




Hi,

Some geometry extensions methods for Point2d, Point3d, CircularArc2d, Polyline and Polyline2d classes and two little (maybe usefull) classes: Triangle2d, Triangle3d.

All these classes work together and have to be compiled in a single DLL.

The extension methods requires to reference System.Core (.NET Framework 3.5)

EDIT: extended

The GeomExt class, contains the following extension methods:

Matrix3d Editor.UCS2WCS()
Matrix3d Editor.WCS2UCS()
Matrix3d Editor.DCS2WCS()
Matrix3d Editor.WCS2DCS()
Matrix3d Editor.DCS2PSDCS()
Matrix3d Editor.PSDCS2DCS()

Matrix3d Viewport.DCS2WCS()
Matrix3d Viewport.WCS2DCS()
Matrix3d Viewport.PSDCS2DCS()
Matrix3d Viewport.DCS2PSDCS()

Point3d Point2d.Convert3d()
Point3d Point2d.Convert3d(Plane plane)
Point3d Point2d.Convert3d(Vector3d normal, double elevation)
Point2d Point2d.Flatten(Vector3d normal)
bool IsBetween(Point2d p1, Point2d p2)
bool IsBetween(Point2d p1, Point2d p2, Tolerance tol)
Point2d Point2d.Polar(double angle, double distance)

Point3d Point3d.Convert2d()
Point3d Point3d.Flatten()
bool IsBetween(Point3d p1, Point3d p2)
bool IsBetween(Point3d p1, Point3d p2, Tolerance tol)
Point3d Point3d.Polar(double angle, double distance)
Point3d Point3d.Trans(int from, int to)
Point3d Point3d.Trans(Editor ed, int from, int to)
Point3d Point3d.Trans(CoordSystem from, CoordSystem to)
Point3d Point3d.Trans(Editor ed, CoordSystem from, CoordSystem to)

Vector3d Vector3d .Flatten()

void Point2dCollection.RemoveDuplicate()
void Point2dCollection.RemoveDuplicate(Tolerance tol)
bool Point2dCollection.Contains(Point2d pt, Tolerance tol)

void Point3dCollection.RemoveDuplicate()
void Point3dCollection.RemoveDuplicate(Tolerance tol)
bool Point3dCollection.Contains(Point3d pt, Tolerance tol)

double CircularArc2d.AlgebricArea()
Point2d CircularArc2d.Centroid()

Point2d Polyline.Centroid2d()
Point3d Polyline.Centroid()
Polyline Polyline.GetProjectedPolyline(Plane plane, Vector3d direction)
Polyline Polyline.GetOrthoProjectedPolyline(Plane plane)

Point3d Polyline2d.Centroid()
CircularArc3d Polyline2d.GetArcSegmentAt(int index)
CircularArc2d Polyline2d.GetArcSegment2dAt(int index)
LineSegment3d Polyline2d.GetLineSegmentAt(int index)
LineSegment2d Polyline2d.GetLineSegment2dAt(int index)
Polyline Polyline2d.GetProjectedPolyline(Plane plane, Vector3d direction)
Polyline Polyline2d.GetOrthoProjectedPolyline(Plane plane)
List<Vertex2d> Polyline2d.GetVertices()

Polyline Polyline3d.GetProjectedPolyline(Plane plane, Vector3d direction)
Polyline Polyline3d.GetOrthoProjectedPolyline(Plane plane)

Point3d Region.Centroid()

Point3d Spline.Centroid()

Polyline Ellipse.ToPolyline()


Triangle<T> abstract class

Constructors
Triangle()
Triangle(T[] pts)
Triangle(T a, T b, T c)

Indexor
T Item

Methods
T Inverse()
void Set(T[] pts)
void Set(T a, T b, T c)
T[] ToArray()


Triangle2d : Triangle<Point2d> class

Constructors
Triangle2d() : base()
Triangle2d(Point2d[] pts) : base(pts)
Triangle2d(Point2d a, Point2d b, Point2d c) : base(a, b, c)
Triangle2d(Point2d org, Vector2d v1, Vector2d v2)

Properties
double AlgebricArea
Point2d Centroid
CircularArc2d CircumscribedCircle
CircularArc2d InscribedCircle
bool IsClockwise

Methods
Triangle3d Convert3d(Plane plane)
Triangle3d Convert3d(Vector3d normal, double elevation)
double GetAngleAt(int index)
LineSegment2d GetSegmentAt(int index)
List<Point2d> IntersectWith(LinearEntity2d le2d)
List<Point2d> IntersectWith(LinearEntity2d le2d, Tolerance tol)
bool IsEqualTo(Triangle2d t2d)
bool IsEqualTo(Triangle2d t2d, Tolerance tol)
bool IsPointInside(Point2d pt)
bool IsPointOn(Point2d pt)
void Set(Point2d org, Vector2d v1, Vector2d v2)
Triangle2d TransformBy(Matrix2d mat)


Triangle3d : Triangle<Point3d> class

Constructors
Triangle3d() : base()
Triangle3d(Point3d[] pts) : base(pts)
Triangle3d(Point3d a, Point3d b, Point3d c) : base(a, b, c)
Triangle3d(Point3d org, Vector3d v1, Vector3d v2)

Properties
double Area
Point3d Centroid
CircularArc3d CircumscribedCircle
double Elevation
Vector3d GreatestSlope
Vector3d Horizontal
CircularArc3d InscribedCircle
bool IsHorizontal
Vector3d Normal
double SlopePerCent
Matrix3d SlopeUCS

Methods
Triangle2d Convert2d()
Triangle2d Flatten()
double GetAngleAt(int index)
BoundedPlane GetBoundedPlane()
Plane GetPlane()
LineSegment3d GetSegmentAt(int index)
bool IsEqualTo(Triangle3d t3d)
bool IsEqualTo(Triangle3d t3d, Tolerance tol)
bool IsPointInside(Point3d pt)
bool IsPointOn(Point3d pt)
void Set(Point3d org, Vector3d v1, Vector3d v2)
Triangle3d Transformby(Matrix3d mat)


PolylineSegment class

Constructors

PolylineSegment(Point2d startPoint, Point2d endPoint)
PolylineSegment(Point2d startPoint, Point2d endPoint, double bulge)
PolylineSegment(Point2d startPoint, Point2d endPoint, double bulge, double constantWidth)
PolylineSegment(Point2d startPoint, Point2d endPoint, double bulge, double startWidth, double endWidth)
PolylineSegment(Line2dSegment line)
PolylineSegment(CircularArc2d arc)

Properties

double Bulge
Point2d EndPoint
double EndWidth
bool IsLinear
Point2d StartPoint
double StartWidth

Methods

bool Equals(object obj)
PolylineSegment Clone()
int GetHashCode()
double GetParameterOf(Point3d pt)
void Inverse()
CircularArc2d ToCircularArc()
Curve2d ToCurve2d()
LineSegment2d ToLineSegment()
string ToString()


PolylineSegmentCollection : IList<PolylineSegment> class

Constructors

PolylineSegmentCollection()
PolylineSegmentCollection(Circle circle)
PolylineSegmentCollection(Ellipse ellipse)
PolylineSegmentCollection(Polyline pline)
PolylineSegmentCollection(Polyline2d pline)
PolylineSegmentCollection(IEnumerable<PolylineSegment> segments)

Methods

void Add(PolylineSegment segment)
void AddRange(IEnumerable<PolylineSegment> range)
void Clear()
bool Contains(PolylineSegment segment)
int FinIindex(Predicate<PolylineSegment> match)
int GetClosestSegmentTo(Point2d pt)
IEnumerator<PolylineSegment> GetEnumerator()
int IndexOf(PolylineSegment item)
void Insert(int index, PolylineSegment item)
void InsertRange(int index, IEnumerable<PolylineSegment> collection)
List<PolylineSegmentCollection> Join()
List<PolylineSegmentCollection> Join(Tolerance tol)
bool Remove(PolylineSegment item)
void RemoveAt(int index)
public void RemoveRange(int index, int count)
Polyline ToPolyline()

For more information, see the attached documentation.

GeomExtDoc.zip contains a .chm documentation file.
GeometryExtensions(1.6).zip contains the C# source code files and the DLLs (GeometryExtensions_18.dll for A2010-2012 and GeometryExtensions_19 for A2013+)

<EDIT: version 1.6> added CircularArc2d and CircularArc3d GetTangentsTo methods, reorganized: one class per extended type.
Title: Re: .NET GEOMETRY Routines
Post by: gile on November 17, 2021, 02:41:50 PM
Hi,

Looking to answer a question (https://forums.autodesk.com/t5/net/why-segment-of-curves-created-by-region-boundary-is-missing/td-p/10757146/jump-to/first-unread-message) on the Autodesk forum, I thought I'd try to do something more generic to get the curves making up the boundaries of a region, including any islands and separate boundary. If the boundary allows it, the returned curve is a closed polyline.

Code - C#: [Select]
  1. using Autodesk.AutoCAD.BoundaryRepresentation;
  2. using Autodesk.AutoCAD.DatabaseServices;
  3. using Autodesk.AutoCAD.Geometry;
  4.  
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Linq;
  8.  
  9. namespace Gile.AutoCAD.Geometry
  10. {
  11.     public static class Extension
  12.     {
  13.         /// <summary>
  14.         /// Gets the curves constituting the boundaries of the region.
  15.         /// </summary>
  16.         /// <param name="region">The region this method applies to.</param>
  17.         /// <returns>Curve collection.</returns>
  18.         public static IEnumerable<Curve> GetCurves(this Region region)
  19.         {
  20.             using (var brep = new Brep(region))
  21.             {
  22.                 var loops = brep.Complexes
  23.                     .SelectMany(complex => complex.Shells)
  24.                     .SelectMany(shell => shell.Faces)
  25.                     .SelectMany(face => face.Loops);
  26.                 foreach (var loop in loops)
  27.                 {
  28.                     var curves3d = loop.Edges.Select(edge => ((ExternalCurve3d)edge.Curve).NativeCurve);
  29.                     if (1 < curves3d.Count())
  30.                     {
  31.                         if (curves3d.All(curve3d => curve3d is CircularArc3d || curve3d is LineSegment3d))
  32.                         {
  33.                             var pline = (Polyline)Curve.CreateFromGeCurve(new CompositeCurve3d(curves3d.ToOrderedArray()));
  34.                             pline.Closed = true;
  35.                             yield return pline;
  36.                         }
  37.                         else
  38.                         {
  39.                             foreach (Curve3d curve3d in curves3d)
  40.                             {
  41.                                 yield return Curve.CreateFromGeCurve(curve3d);
  42.                             }
  43.                         }
  44.                     }
  45.                     else
  46.                     {
  47.                         yield return Curve.CreateFromGeCurve(curves3d.First());
  48.                     }
  49.                 }
  50.             }
  51.         }
  52.  
  53.         /// <summary>
  54.         /// Order the collection by contiguous curves ([n].EndPoint equals to [n+1].StartPoint)
  55.         /// </summary>
  56.         /// <param name="source">Collection this method applies to.</param>
  57.         /// <returns>Ordered array of Curve3d.</returns>
  58.         public static Curve3d[] ToOrderedArray(this IEnumerable<Curve3d> source)
  59.         {
  60.             var list = source.ToList();
  61.             int count = list.Count;
  62.             var array = new Curve3d[count];
  63.             int i = 0;
  64.             array[0] = list[0];
  65.             list.RemoveAt(0);
  66.             int index;
  67.             while (i < count - 1)
  68.             {
  69.                 var pt = array[i++].EndPoint;
  70.                 if ((index = list.FindIndex(c => c.StartPoint.IsEqualTo(pt))) != -1)
  71.                     array[i] = list[index];
  72.                 else if ((index = list.FindIndex(c => c.EndPoint.IsEqualTo(pt))) != -1)
  73.                     array[i] = list[index].GetReverseParameterCurve();
  74.                 else
  75.                     throw new ArgumentException("Not contiguous curves.");
  76.                 list.RemoveAt(index);
  77.             }
  78.             return array;
  79.         }
  80.     }
  81. }
  82.  
Title: Re: .NET GEOMETRY Routines
Post by: gile on February 07, 2022, 06:25:57 AM
GeometryExtensions is now available on GitHub (https://github.com/gilles-chanteau/GeometryExtensions).
Title: Re: .NET GEOMETRY Routines
Post by: Atook on February 07, 2022, 05:14:54 PM
Thanks Gile! :)
Title: Re: .NET GEOMETRY Routines
Post by: pkohut on February 09, 2022, 02:37:34 AM
Yes, Thanks Gile!
Title: Re: .NET GEOMETRY Routines
Post by: gile on November 14, 2023, 02:59:56 AM
Looking to answer a question (https://forums.autodesk.com/t5/net/hatch-inside-of-a-region/td-p/12370606/jump-to/first-unread-message) on the Autodesk forum, I thought I'd try to do something more generic to get data that can be used to create a Hatch from a Region. I updated the GeometryExtensions library on Github (https://github.com/gileCAD/GeometryExtensions/tree/master).
Code - C#: [Select]
  1.         /// <summary>
  2.         /// Gets the hatch loops data for the supplied region.
  3.         /// </summary>
  4.         /// <param name="region">The instance to which this method applies.</param>
  5.         /// <returns>A collection of tuples containing the loop data.</returns>
  6.         public static IEnumerable<(HatchLoopTypes, Curve2dCollection, IntegerCollection)> GetHatchLoops(this Region region)
  7.         {
  8.             var plane = new Plane(Point3d.Origin, region.Normal);
  9.  
  10.             using (var brep = new Brep(region))
  11.             {
  12.                 foreach (var complex in brep.Complexes)
  13.                 {
  14.                     foreach (var loop in complex.Shells.First().Faces.First().Loops)
  15.                     {
  16.                         var edgePtrCollection = new Curve2dCollection();
  17.                         var edgeTypeCollection = new IntegerCollection();
  18.                         foreach (var edge in loop.Edges.Select(e => ((ExternalCurve3d)e.Curve).NativeCurve).ToOrderedArray())
  19.                         {
  20.                             switch (edge)
  21.                             {
  22.                                 case LineSegment3d lineSegment3D:
  23.                                     edgePtrCollection.Add(
  24.                                         new LineSegment2d(
  25.                                             lineSegment3D.StartPoint.Convert2d(plane),
  26.                                             lineSegment3D.EndPoint.Convert2d(plane)));
  27.                                     edgeTypeCollection.Add(1);
  28.                                     break;
  29.                                 case CircularArc3d circularArc3D:
  30.                                     edgePtrCollection.Add(
  31.                                         new CircularArc2d(
  32.                                             circularArc3D.Center.Convert2d(plane),
  33.                                             circularArc3D.Radius,
  34.                                             circularArc3D.StartAngle,
  35.                                             circularArc3D.EndAngle,
  36.                                             circularArc3D.ReferenceVector.Convert2d(plane),
  37.                                             false));
  38.                                     edgeTypeCollection.Add(2);
  39.                                     break;
  40.                                 case EllipticalArc3d ellipticalArc3D:
  41.                                     edgePtrCollection.Add(
  42.                                         new EllipticalArc2d(
  43.                                             ellipticalArc3D.Center.Convert2d(plane),
  44.                                             ellipticalArc3D.MajorAxis.Convert2d(plane),
  45.                                             ellipticalArc3D.MinorAxis.Convert2d(plane),
  46.                                             ellipticalArc3D.MajorRadius,
  47.                                             ellipticalArc3D.MinorRadius,
  48.                                             ellipticalArc3D.StartAngle,
  49.                                             ellipticalArc3D.EndAngle));
  50.                                     edgeTypeCollection.Add(3);
  51.                                     break;
  52.                                 case NurbCurve3d nurbCurve3D:
  53.                                     var ctrlPts = new Point2dCollection();
  54.                                     for (int i = 0; i < nurbCurve3D.NumberOfControlPoints; i++)
  55.                                     {
  56.                                         ctrlPts.Add(nurbCurve3D.ControlPointAt(i).Convert2d(plane));
  57.                                     }
  58.                                     edgePtrCollection.Add(
  59.                                         new NurbCurve2d(
  60.                                             nurbCurve3D.Degree,
  61.                                             nurbCurve3D.Knots,
  62.                                             ctrlPts,
  63.                                             nurbCurve3D.IsPeriodic(out double _)));
  64.                                     edgeTypeCollection.Add(4);
  65.                                     break;
  66.                                 default:
  67.                                     break;
  68.                             }
  69.                         }
  70.                         if (loop.LoopType == LoopType.LoopExterior)
  71.                             yield return (HatchLoopTypes.External, edgePtrCollection, edgeTypeCollection);
  72.                         else
  73.                             yield return (HatchLoopTypes.Default, edgePtrCollection, edgeTypeCollection);
  74.                     }
  75.                 }
  76.             }
  77.         }
  78.  
Title: Re: .NET GEOMETRY Routines
Post by: MickD on December 06, 2023, 07:23:19 PM
Thanks Gilles for this work, it has come in handy many times :)

I've forked your project to update the files and solution to build for both AutoCAD and BricsCAD

https://github.com/MickDuprez/GeometryExtensions/tree/master

Let me know if you want me to make a pull request if you would like to merge the updates.

cheers,
Mick
Title: Re: .NET GEOMETRY Routines
Post by: gile on December 07, 2023, 04:19:48 AM
Hi Mick,

I think it's a good idea but a very next update is going to be compatibility with AutoCAD 2025 and .NET 8 which I'm thinking of doing in another project (from the same solution) to be able to use new C# 12 features without having to multiply conditional compilation expressions in the code.
I don't know about the Bricscad API (which I use very occasionally) and .NET 8.
Title: Re: .NET GEOMETRY Routines
Post by: MickD on December 07, 2023, 05:06:57 AM
Not a problem Gilles, and thanks again for all of your great work!
Title: Re: .NET GEOMETRY Routines
Post by: gile on December 07, 2023, 06:53:10 AM
We can talk more about this after the update.