Hello!
I want to create an outermost border for curves in a selection, some work well and some polylines don't. I've tried everything but can't fix it, hope you can help me fix it
Imports Autodesk.AutoCAD.ApplicationServices
Imports Autodesk.AutoCAD.BoundaryRepresentation
Imports Autodesk.AutoCAD.DatabaseServices
Imports Autodesk.AutoCAD.EditorInput
Imports Autodesk.AutoCAD.Geometry
Imports Autodesk.AutoCAD.Runtime
Imports CadDb = Autodesk.AutoCAD.DatabaseServices
Public Module M_Boundary
Public Class OutLiner
Private _dwg As Document
Public Sub New(dwg As Document)
_dwg = dwg
End Sub
Public Sub DrawOutline(entIds As IEnumerable(Of ObjectId))
Using polyline = GetOutline(entIds)
Using tran = _dwg.TransactionManager.StartTransaction()
Dim space = DirectCast(tran.GetObject(_dwg.Database.CurrentSpaceId, OpenMode.ForWrite), BlockTableRecord)
space.AppendEntity(TryCast(polyline, Entity))
tran.AddNewlyCreatedDBObject(TryCast(polyline, Entity), True)
tran.Commit()
End Using
End Using
End Sub
Public Function GetOutline(entIds As IEnumerable(Of ObjectId)) As Entity
Dim regions = New List(Of Region)()
Using tran = _dwg.TransactionManager.StartTransaction()
For Each entId In entIds
Dim entity = TryCast(tran.GetObject(entId, OpenMode.ForRead), Entity)
If entity IsNot Nothing Then
Dim entityRegion As List(Of Region) = Nothing
Select Case entity.GetType()
Case GetType(Polyline)
entityRegion = GetRegionFromPolyline(TryCast(entity, Polyline))
Case GetType(Circle)
entityRegion = GetRegionFromCircle(TryCast(entity, Circle))
Case GetType(Arc)
entityRegion = GetRegionFromArc(TryCast(entity, Arc))
Case GetType(Ellipse)
entityRegion = GetRegionFromEllipse(TryCast(entity, Ellipse))
Case GetType(Line)
entityRegion = GetRegionFromLine(TryCast(entity, Line))
Case Else
' Unsupported type
Continue For
End Select
If entityRegion IsNot Nothing Then
regions.AddRange(entityRegion)
End If
End If
Next
tran.Commit()
End Using
Using region = MergeRegions(regions)
If region IsNot Nothing Then
Dim brep = New Brep(region)
Dim points = New List(Of Point2d)()
Dim faceCount = brep.Faces.Count()
Dim face = brep.Faces.First()
For Each iloop In face.Loops
If iloop.LoopType = LoopType.LoopExterior Then
For Each vertex In iloop.Vertices
points.Add(New Point2d(vertex.Point.X, vertex.Point.Y))
Next
Exit For
End If
Next
Return CreatePolyline(points)
Else
Return Nothing
End If
End Using
End Function
'Private Function GetRegionFromPolyline(poly As CadDb.Polyline) As List(Of Region)
' Return GetRegionFromCurve(poly)
'End Function
Private Function GetRegionFromPolyline(poly As CadDb.Polyline) As List(Of Region)
Dim regions = New List(Of Region)()
Dim sourceCol = New DBObjectCollection()
' Create a list of all points in the polyline
Dim points = New List(Of Point3d)()
For i = 0 To poly.NumberOfVertices - 1
points.Add(poly.GetPoint3dAt(i))
Next
' Calculate the centroid of points
Dim centroid = New Point3d(points.Average(Function(p) p.X), points.Average(Function(p) p.Y), points.Average(Function(p) p.Z))
' Sort points by angle
points.Sort(Function(p1, p2) Math.Atan2(p1.Y - centroid.Y, p1.X - centroid.X).CompareTo(Math.Atan2(p2.Y - centroid.Y, p2.X - centroid.X)))
' Create a new closed polyline from the sorted points
Dim sortedPoly = New CadDb.Polyline()
For i As Integer = 0 To points.Count - 1
sortedPoly.AddVertexAt(i, New Point2d(points(i).X, points(i).Y), 0, 0, 0)
Next
sortedPoly.Closed = True
' Add the polyline to the source collection
sourceCol.Add(sortedPoly)
' Create regions from the source collection
Dim dbObjs = Region.CreateFromCurves(sourceCol)
For Each obj In dbObjs
If TypeOf obj Is Region Then regions.Add(TryCast(obj, Region))
Next
Return regions
End Function
Public Function GetRegionFromCircle(circle As CadDb.Circle) As List(Of Region)
Dim regions = New List(Of Region)()
Dim sourceCol = New DBObjectCollection()
Dim dbObj = New CadDb.Polyline()
Dim numPoints As Integer = 100 ' increase this for higher precision
For i = 0 To numPoints
Dim param = 2 * Math.PI * i / numPoints
Dim point = New Point2d(circle.Center.X + circle.Radius * Math.Cos(param), circle.Center.Y + circle.Radius * Math.Sin(param))
dbObj.AddVertexAt(i, point, 0.0, 0.3, 0.3)
Next
dbObj.Closed = True
sourceCol.Add(dbObj)
Dim dbObjs = Region.CreateFromCurves(sourceCol)
For Each obj In dbObjs
If TypeOf obj Is Region Then regions.Add(TryCast(obj, Region))
Next
Return regions
End Function
Public Function GetRegionFromLine(line As CadDb.Line) As List(Of Region)
Dim regions = New List(Of Region)()
Dim sourceCol = New DBObjectCollection()
' Create a parallel line
Dim offsetVector = (line.EndPoint - line.StartPoint).RotateBy(Math.PI / 2, New Vector3d(0, 0, 1)) * 0.1
Dim parallelLine = DirectCast(line.GetOffsetCurves(offsetVector.Length)(0), CadDb.Line)
' Create a polyline from the two lines
Dim poly = New CadDb.Polyline()
poly.AddVertexAt(0, New Point2d(line.StartPoint.X, line.StartPoint.Y), 0.0, 0.3, 0.3)
poly.AddVertexAt(1, New Point2d(line.EndPoint.X, line.EndPoint.Y), 0.0, 0.3, 0.3)
poly.AddVertexAt(2, New Point2d(parallelLine.EndPoint.X, parallelLine.EndPoint.Y), 0.0, 0.3, 0.3)
poly.AddVertexAt(3, New Point2d(parallelLine.StartPoint.X, parallelLine.StartPoint.Y), 0.0, 0.3, 0.3)
poly.Closed = True
sourceCol.Add(poly)
Dim dbObjs = Region.CreateFromCurves(sourceCol)
For Each obj In dbObjs
If TypeOf obj Is Region Then regions.Add(TryCast(obj, Region))
Next
Return regions
End Function
Private Function GetRegionFromEllipse(ellipse As CadDb.Ellipse) As List(Of Region)
Dim regions = New List(Of Region)()
Using tran = _dwg.TransactionManager.StartTransaction()
Dim tempEllipse = ellipse.Clone()
tempEllipse.TransformBy(Matrix3d.Displacement(ellipse.Center.GetVectorTo(Point3d.Origin)))
tran.AddNewlyCreatedDBObject(tempEllipse, True)
Dim outlineId = tempEllipse.GetOffsetCurves(0.001)(0)
Dim outline = tran.GetObject(outlineId, OpenMode.ForWrite)
tran.AddNewlyCreatedDBObject(outline, True)
If TypeOf outline Is CadDb.Polyline Then
regions.AddRange(GetRegionFromPolyline(TryCast(outline, CadDb.Polyline)))
ElseIf TypeOf outline Is CadDb.Arc Then
regions.AddRange(GetRegionFromArc(TryCast(outline, CadDb.Arc)))
End If
outline.Erase()
tempEllipse.Erase()
tran.Commit()
End Using
Return regions
End Function
Public Function GetRegionFromArc(arc As CadDb.Arc) As List(Of Region)
Dim regions = New List(Of Region)()
Dim sourceCol = New DBObjectCollection()
Dim dbObj = New CadDb.Polyline()
Dim numPoints As Integer = 100 ' increase this for higher precision
For i = 0 To numPoints
Dim param = arc.StartAngle + (arc.EndAngle - arc.StartAngle) * i / numPoints
Dim point = New Point2d(arc.Center.X + arc.Radius * Math.Cos(param), arc.Center.Y + arc.Radius * Math.Sin(param))
dbObj.AddVertexAt(i, point, 0.0, 0.3, 0.3)
Next
dbObj.AddVertexAt(numPoints + 1, New Point2d(arc.Center.X, arc.Center.Y), 0.0, 0.3, 0.3) ' add center point
dbObj.Closed = True
sourceCol.Add(dbObj)
Dim dbObjs = Region.CreateFromCurves(sourceCol)
For Each obj In dbObjs
If TypeOf obj Is Region Then regions.Add(TryCast(obj, Region))
Next
Return regions
End Function
Private Function GetRegionFromCurve(curve As CadDb.Curve) As List(Of Region)
Dim regions = New List(Of Region)()
Dim sourceCol = New DBObjectCollection()
Dim dbObj = DirectCast(curve.Clone(), CadDb.Curve)
sourceCol.Add(dbObj)
Dim dbObjs = Region.CreateFromCurves(sourceCol)
For Each obj In dbObjs
If TypeOf obj Is Region Then regions.Add(TryCast(obj, Region))
Next
Return regions
End Function
Private Function MergeRegions(regions As List(Of Region)) As Region
If regions.Count = 0 Then Return Nothing
If regions.Count = 1 Then Return regions(0)
Dim region = regions(0)
For i = 1 To regions.Count - 1
Dim rg = regions(i)
region.BooleanOperation(BooleanOperationType.BoolUnite, rg)
rg.Dispose()
Next
Return region
End Function
Private Function CreatePolyline(points As List(Of Point2d)) As CadDb.Polyline
Dim poly = New CadDb.Polyline(points.Count())
For i = 0 To points.Count - 1
poly.AddVertexAt(i, points(i), 0.0, 0.1, 0.1)
Next
poly.SetDatabaseDefaults(_dwg.Database)
poly.ColorIndex = 1
poly.Closed = True
Return poly
End Function
End Class
<CommandMethod("Outline")>
Public Sub RunMyCommand()
Dim dwg = Application.DocumentManager.MdiActiveDocument
Dim ed = dwg.Editor
Try
Dim ids = SelectEntities(ed)
If ids IsNot Nothing Then
Dim liner = New OutLiner(dwg)
liner.DrawOutline(ids)
Else
ed.WriteMessage(vbLf & "*Cancel*")
End If
Catch ex As System.Exception
ed.WriteMessage(vbLf & "Command failed:" & vbLf & "{0}", ex.Message)
ed.WriteMessage(vbLf & "*Cancel*")
End Try
End Sub
Private Function SelectEntities(ed As Editor) As ObjectId()
' Allow selection of all types of entities.
Dim res = ed.GetSelection()
If res.Status = PromptStatus.OK Then
Return res.Value.GetObjectIds()
Else
Return Nothing
End If
End Function
End Module
Solved! Go to Solution.
Solved by _gile. Go to Solution.
Hi,
This code (inspired by this one) seems to work as expected (at least with your drawing example).
using Autodesk.AutoCAD.ApplicationServices.Core;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MergeCurvesSample
{
public class Commands
{
struct Segment
{
public Point2d StartPt { get; set; }
public Point2d EndPt { get; set; }
public double Bulge { get; set; }
}
private static IEnumerable<Polyline> MergeCurves(IEnumerable<Curve> curves)
{
var splitCurves = new List<Curve>();
var curveSegments = new DBObjectCollection();
foreach (Curve curve in curves)
{
if (curve is Arc || curve is Circle || curve is Line || curve is Polyline)
{
foreach (Curve c in GetSplitSegments(curve, curves))
{
splitCurves.Add(c);
curveSegments.Add(c);
}
}
}
var regions = Region.CreateFromCurves(curveSegments);
foreach (Curve c in splitCurves)
{
c.Dispose();
}
if (regions.Count == 0) yield break;
Region reg = (Region)regions[0];
for (int i = 1; i < regions.Count; i++)
{
reg.BooleanOperation(BooleanOperationType.BoolUnite, (Region)regions[i]);
regions[i].Dispose();
}
var segments = new DBObjectCollection();
reg.Explode(segments);
reg.Dispose();
var segs = new List<Segment>();
var plane = new Plane(Point3d.Origin, Vector3d.ZAxis);
for (int i = 0; i < segments.Count; i++)
{
if (segments[i] is Region r)
{
r.Explode(segments);
continue;
}
Curve crv = (Curve)segments[i];
Point3d start = crv.StartPoint;
Point3d end = crv.EndPoint;
double bulge = 0.0;
if (crv is Arc arc)
{
double angle = arc.Center.GetVectorTo(start).GetAngleTo(arc.Center.GetVectorTo(end), arc.Normal);
bulge = Math.Tan(angle / 4.0);
}
segs.Add(new Segment { StartPt = start.Convert2d(plane), EndPt = end.Convert2d(plane), Bulge = bulge });
}
foreach (DBObject o in segments) o.Dispose();
while (segs.Count > 0)
{
var pline = new Polyline();
pline.AddVertexAt(0, segs[0].StartPt, segs[0].Bulge, 0.0, 0.0);
Point2d pt = segs[0].EndPt;
segs.RemoveAt(0);
int vtx = 1;
while (true)
{
int i = segs.FindIndex((s) => s.StartPt.IsEqualTo(pt) || s.EndPt.IsEqualTo(pt));
if (i < 0) break;
Segment seg = segs[i];
if (seg.EndPt.IsEqualTo(pt))
seg = new Segment { StartPt = seg.EndPt, EndPt = seg.StartPt, Bulge = -seg.Bulge };
pline.AddVertexAt(vtx, seg.StartPt, seg.Bulge, 0.0, 0.0);
pt = seg.EndPt;
segs.RemoveAt(i);
vtx++;
}
pline.Closed = true;
yield return pline;
}
}
private static DBObjectCollection GetSplitSegments(Curve curve, IEnumerable<Curve> curves)
{
var points = new Point3dCollection();
foreach (var c in curves)
{
curve.IntersectWith(c, Intersect.OnBothOperands, points, IntPtr.Zero, IntPtr.Zero);
}
if (points.Count == 0) return new DBObjectCollection { (Curve)curve.Clone() };
var parameters = points
.Cast<Point3d>()
.Select(p => curve.GetParameterAtPoint(p))
.OrderBy(p => p)
.ToArray();
return curve.GetSplitCurves(new DoubleCollection(parameters));
}
[CommandMethod("TEST")]
public static void Test()
{
var doc = Application.DocumentManager.MdiActiveDocument;
var db = doc.Database;
var ed = doc.Editor;
var filter = new SelectionFilter(new[]
{
new TypedValue(0, "ARC,CIRCLE,LINE,LWPOLYLINE")
});
var selection = ed.GetSelection(filter);
if (selection.Status != PromptStatus.OK)
return;
using (var tr = db.TransactionManager.StartTransaction())
{
var curves = selection.Value.GetObjectIds()
.Select(id => (Curve)tr.GetObject(id, OpenMode.ForRead));
var curSpace = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
foreach (var pline in MergeCurves(curves))
{
curSpace.AppendEntity(pline);
tr.AddNewlyCreatedDBObject(pline, true);
}
tr.Commit();
}
}
}
}
Great, excellent, wonderfull! Thank you very much
1. Can you add more ellipse objects if you want?
2. If you want to create internal boundary Boundary of qq307501169
@quyenpv a écrit :
1. Can you add more ellipse objects if you want?
Polyline can only contain linear and circular arc segments. If you want to convert true ellipses to polyline approximations, you can use the Ellipse.ToPolyline method from the GeometryExtensions library.
@quyenpv a écrit :
2. If you want to create internal boundary Boundary of qq307501169
I thaught you wanted to learn .NET programmation. I won't do your work for you.
Thank you!
Based on your suggestion, I did it
I'm learning VB.Net for real, but it's not to ask him to do the work that should be mine, but I really can't find a solution, so I need help.
Based on the above suggestion, I understood that I had to transform ellipse and arc into polyline to create boundary
Once again thank you very much! I wish you good health
I edited the upper code so that all selected curves are splited at intersections.
That, I believe, is one of the hugest cause of errors in .NET programming
. . . people making incorrect translations from C# to VB.net.
. . . but people generally don't want to listen when the suggestion is made to learn C# properly to save the hassle 🙂
// Called Kerry in my other life.
Everything will work just as you expect it to, unless your expectations are incorrect.
Sometimes the question is more important than the answer.
class keyThumper<T> : Lazy<T>; another Swamper
Yes, that (reply 7#) didn't include any code.
And the origin (2#) only accepts Arc, Circle, Line, and Polyline objects.
foreach (Curve curve in curves) { if (curve is Arc || curve is Circle || curve is Line || curve is Polyline)
@GiaBach a écrit :
Yes, that (reply 7#) didn't include any code.
In reply #7 I announced the "upper code" (reply #2) had been edited.
@GiaBach a écrit :
And the origin (2#) only accepts Arc, Circle, Line, and Polyline objects.
foreach (Curve curve in curves) { if (curve is Arc || curve is Circle || curve is Line || curve is Polyline)
The code still accepts only arcs, circles, lines and polylines because polylines "can only contain linear and circular arc segments" as said in reply #4.
@kdub_nz a écrit :
Is it possible that your translation to VB.NET is faulty ??
This may be due to the for statement:
for (int i = 0; i < segments.Count; i++)
{
if (segments[i] is Region r)
{
r.Explode(segments);
continue;
}
Curve crv = (Curve)segments[i];
Point3d start = crv.StartPoint;
Point3d end = crv.EndPoint;
double bulge = 0.0;
if (crv is Arc arc)
{
double angle = arc.Center.GetVectorTo(start).GetAngleTo(arc.Center.GetVectorTo(end), arc.Normal);
bulge = Math.Tan(angle / 4.0);
}
segs.Add(new Segment { StartPt = start.Convert2d(plane), EndPt = end.Convert2d(plane), Bulge = bulge });
}
Because in C#, the loop condition is evaluated on each iteration, and in VB.NET, it is only evaluated on entry to the loop. In this loop if the entity is a region it is explode and the resulting entities are added to the segments collection.
In VB, this for statement have to be replaced by a while statement.
This version references the GeometryExtensions library to be able to directly work with Ellipses and Splines.
Due to the conversion of Ellipses and Splines into polyline approximations it needs a Tolerance.
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.Runtime;
using Gile.AutoCAD.Geometry;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MergeCurvesUsingGeometryExtensions
{
public class Commands
{
private static IEnumerable<Polyline> MergeCurves(IEnumerable<Curve> curves, Tolerance tolerance)
{
var curveSegments = new DBObjectCollection();
foreach (Curve curve in curves)
{
foreach (Curve c in GetSplitSegments(curve, curves))
{
curveSegments.Add(c);
}
}
var regions = Region.CreateFromCurves(curveSegments);
foreach (Curve c in curveSegments)
{
c.Dispose();
}
if (regions.Count == 0) yield break;
Region reg = (Region)regions[0];
for (int i = 1; i < regions.Count; i++)
{
reg.BooleanOperation(BooleanOperationType.BoolUnite, (Region)regions[i]);
regions[i].Dispose();
}
var segments = new DBObjectCollection();
reg.Explode(segments);
reg.Dispose();
var plane = new Plane(Point3d.Origin, Vector3d.ZAxis);
var polylineSegments = new PolylineSegmentCollection();
for (int i = 0; i < segments.Count; i++)
{
var entity = (Entity)segments[i];
switch (entity)
{
case Region region:
region.Explode(segments);
break;
case Ellipse ellipse:
polylineSegments.AddRange(ellipse.ToPolyline());
break;
case Spline spline:
polylineSegments.AddRange((Polyline)spline.ToPolyline());
break;
case Line line:
polylineSegments.Add(new PolylineSegment(new LineSegment2d(
line.StartPoint.Convert2d(plane),
line.EndPoint.Convert2d(plane))));
break;
case Arc arc:
polylineSegments.Add(new PolylineSegment(new CircularArc2d(
arc.StartPoint.Convert2d(plane),
arc.GetPointAtParameter((arc.StartParam + arc.EndParam) * 0.5).Convert2d(plane),
arc.EndPoint.Convert2d(plane))));
break;
default:
break; ;
}
}
foreach (var segmentCollection in polylineSegments.Join(tolerance))
{
yield return segmentCollection.ToPolyline();
}
}
private static DBObjectCollection GetSplitSegments(Curve curve, IEnumerable<Curve> curves)
{
var points = new Point3dCollection();
foreach (var c in curves)
{
curve.IntersectWith(c, Intersect.OnBothOperands, points, IntPtr.Zero, IntPtr.Zero);
}
if (points.Count == 0) return new DBObjectCollection { (Curve)curve.Clone() };
var parameters = points
.Cast<Point3d>()
.Select(p => curve.GetParameterAtPoint(p))
.OrderBy(p => p)
.ToArray();
return curve.GetSplitCurves(new DoubleCollection(parameters));
}
[CommandMethod("TEST")]
public static void Test()
{
var doc = Application.DocumentManager.MdiActiveDocument;
var db = doc.Database;
var ed = doc.Editor;
var filter = new SelectionFilter(new[]
{
new TypedValue(-4, "<OR"),
new TypedValue(0, "ARC,CIRCLE,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>")
});
var selection = ed.GetSelection(filter);
if (selection.Status != PromptStatus.OK)
return;
using (var tr = db.TransactionManager.StartTransaction())
{
var curves = selection.Value.GetObjectIds()
.Select(id => (Curve)tr.GetObject(id, OpenMode.ForRead));
var curSpace = (BlockTableRecord)tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite);
var tolerance = curves.Any(c => c is Ellipse || c is Spline) ?
new Tolerance(0.1, 0.5) :
Tolerance.Global;
foreach (var pline in MergeCurves(curves, tolerance))
{
curSpace.AppendEntity(pline);
tr.AddNewlyCreatedDBObject(pline, true);
}
tr.Commit();
}
}
}
}
Can't find what you're looking for? Ask the community or share your knowledge.