diff --git a/Converters/Autocad/Speckle.Converters.AutocadShared/Speckle.Converters.AutocadShared.projitems b/Converters/Autocad/Speckle.Converters.AutocadShared/Speckle.Converters.AutocadShared.projitems
index 452b5b533..f4722bef0 100644
--- a/Converters/Autocad/Speckle.Converters.AutocadShared/Speckle.Converters.AutocadShared.projitems
+++ b/Converters/Autocad/Speckle.Converters.AutocadShared/Speckle.Converters.AutocadShared.projitems
@@ -32,6 +32,10 @@
+
+
+
+
@@ -44,6 +48,7 @@
+
@@ -77,4 +82,4 @@
-
\ No newline at end of file
+
diff --git a/Converters/Autocad/Speckle.Converters.AutocadShared/ToHost/Geometry/DataObjectConverter.cs b/Converters/Autocad/Speckle.Converters.AutocadShared/ToHost/Geometry/DataObjectConverter.cs
index 4181d0b38..133638a5c 100644
--- a/Converters/Autocad/Speckle.Converters.AutocadShared/ToHost/Geometry/DataObjectConverter.cs
+++ b/Converters/Autocad/Speckle.Converters.AutocadShared/ToHost/Geometry/DataObjectConverter.cs
@@ -1,5 +1,6 @@
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
+using Speckle.Objects;
using Speckle.Objects.Data;
using Speckle.Sdk.Common.Exceptions;
using Speckle.Sdk.Models;
@@ -9,46 +10,31 @@ namespace Speckle.Converters.AutocadShared.ToHost.Geometry;
[NameAndRankValue(typeof(DataObject), NameAndRankValueAttribute.SPECKLE_DEFAULT_RANK)]
public class DataObjectConverter : IToHostTopLevelConverter, ITypedConverter>
{
- private readonly ITypedConverter _arcConverter;
+ private readonly ITypedConverter> _curveConverter;
private readonly ITypedConverter> _brepXConverter;
- private readonly ITypedConverter _circleConverter;
- private readonly ITypedConverter _curveConverter;
- private readonly ITypedConverter _ellipseConverter;
private readonly ITypedConverter> _extrusionXConverter;
- private readonly ITypedConverter _lineConverter;
private readonly ITypedConverter _meshConverter;
private readonly ITypedConverter _pointConverter;
- private readonly ITypedConverter> _polycurveConverter;
- private readonly ITypedConverter _polylineConverter;
private readonly ITypedConverter> _subDXConverter;
+ private readonly ITypedConverter _regionConverter;
public DataObjectConverter(
- ITypedConverter arcConverter,
+ ITypedConverter> curveConverter,
ITypedConverter> brepXConverter,
- ITypedConverter circleConverter,
- ITypedConverter curveConverter,
- ITypedConverter ellipseConverter,
ITypedConverter> extrusionXConverter,
- ITypedConverter lineConverter,
ITypedConverter meshConverter,
ITypedConverter pointConverter,
- ITypedConverter> polycurveConverter,
- ITypedConverter polylineConverter,
- ITypedConverter> subDXConverter
+ ITypedConverter> subDXConverter,
+ ITypedConverter regionConverter
)
{
- _arcConverter = arcConverter;
- _brepXConverter = brepXConverter;
- _circleConverter = circleConverter;
_curveConverter = curveConverter;
- _ellipseConverter = ellipseConverter;
+ _brepXConverter = brepXConverter;
_extrusionXConverter = extrusionXConverter;
- _lineConverter = lineConverter;
_meshConverter = meshConverter;
_pointConverter = pointConverter;
- _polycurveConverter = polycurveConverter;
- _polylineConverter = polylineConverter;
_subDXConverter = subDXConverter;
+ _regionConverter = regionConverter;
}
public object Convert(Base target) => Convert((DataObject)target);
@@ -67,54 +53,39 @@ public class DataObjectConverter : IToHostTopLevelConverter, ITypedConverter
+{
+ private readonly ITypedConverter _regionConverter;
+ private readonly ITypedConverter _hatchConverter;
+
+ public RegionToHostConverter(
+ ITypedConverter regionConverter,
+ ITypedConverter hatchConverter
+ )
+ {
+ _regionConverter = regionConverter;
+ _hatchConverter = hatchConverter;
+ }
+
+ public object Convert(Base target) => Convert((SOG.Region)target);
+
+ public ADB.Entity Convert(SOG.Region target)
+ {
+ // Generalizing return type as Entity, because it can be a simple Region, or a Hatch
+ if (target.hasHatchPattern)
+ {
+ return _hatchConverter.Convert(target);
+ }
+ return _regionConverter.Convert(target);
+ }
+}
diff --git a/Converters/Autocad/Speckle.Converters.AutocadShared/ToHost/Raw/ICurveToHostRawConverter.cs b/Converters/Autocad/Speckle.Converters.AutocadShared/ToHost/Raw/ICurveToHostRawConverter.cs
new file mode 100644
index 000000000..48fec94d9
--- /dev/null
+++ b/Converters/Autocad/Speckle.Converters.AutocadShared/ToHost/Raw/ICurveToHostRawConverter.cs
@@ -0,0 +1,56 @@
+using Speckle.Converters.Common.Objects;
+using Speckle.Objects;
+using Speckle.Sdk.Common.Exceptions;
+using Speckle.Sdk.Models;
+
+namespace Speckle.Converters.AutocadShared.ToHost.Raw;
+
+public class ICurveToHostRawConverter : ITypedConverter>
+{
+ private readonly ITypedConverter _lineConverter;
+ private readonly ITypedConverter _arcConverter;
+ private readonly ITypedConverter _ellipseConverter;
+ private readonly ITypedConverter _circleConverter;
+ private readonly ITypedConverter _polylineConverter;
+ private readonly ITypedConverter> _polycurveConverter;
+ private readonly ITypedConverter _curveConverter;
+
+ public ICurveToHostRawConverter(
+ ITypedConverter lineConverter,
+ ITypedConverter arcConverter,
+ ITypedConverter ellipseConverter,
+ ITypedConverter circleConverter,
+ ITypedConverter polylineConverter,
+ ITypedConverter> polycurveConverter,
+ ITypedConverter curveConverter
+ )
+ {
+ _lineConverter = lineConverter;
+ _arcConverter = arcConverter;
+ _ellipseConverter = ellipseConverter;
+ _circleConverter = circleConverter;
+ _polylineConverter = polylineConverter;
+ _polycurveConverter = polycurveConverter;
+ _curveConverter = curveConverter;
+ }
+
+ ///
+ /// Converts a given ICurve object to a list of ADB.Curve.
+ ///
+ /// The ICurve object to convert.
+ /// The converted list of ADB.Curve.
+ /// Thrown when the conversion is not supported for the given type of curve.
+ /// ⚠️ This conversion does NOT perform scaling.
+ public List<(ADB.Entity, Base)> Convert(ICurve target) =>
+ target switch
+ {
+ SOG.Line line => new() { (_lineConverter.Convert(line), line) },
+ SOG.Arc arc => new() { (_arcConverter.Convert(arc), arc) },
+ SOG.Circle circle => new() { (_circleConverter.Convert(circle), circle) },
+ SOG.Ellipse ellipse => new() { (_ellipseConverter.Convert(ellipse), ellipse) },
+ SOG.Polyline polyline => new() { (_polylineConverter.Convert(polyline), polyline) },
+ SOG.Curve curve => new() { (_curveConverter.Convert(curve), curve) },
+ SOG.Polycurve polycurve => _polycurveConverter.Convert(polycurve),
+ _ => throw new ValidationException($"Unable to convert curves of type {target.GetType().Name}")
+ };
+}
diff --git a/Converters/Autocad/Speckle.Converters.AutocadShared/ToHost/Raw/RegionHatchToHostRawConverter.cs b/Converters/Autocad/Speckle.Converters.AutocadShared/ToHost/Raw/RegionHatchToHostRawConverter.cs
new file mode 100644
index 000000000..eb1afbae6
--- /dev/null
+++ b/Converters/Autocad/Speckle.Converters.AutocadShared/ToHost/Raw/RegionHatchToHostRawConverter.cs
@@ -0,0 +1,112 @@
+using Speckle.Converters.Common;
+using Speckle.Converters.Common.Objects;
+using Speckle.Objects;
+using Speckle.Sdk.Common.Exceptions;
+using Speckle.Sdk.Models;
+
+namespace Speckle.Converters.Autocad.ToHost.Raw;
+
+public class RegionHatchToHostRawConverter : ITypedConverter
+{
+ private readonly ITypedConverter> _curveConverter;
+ private readonly IConverterSettingsStore _settingsStore;
+
+ public RegionHatchToHostRawConverter(
+ ITypedConverter> curveConverter,
+ IConverterSettingsStore settingsStore
+ )
+ {
+ _curveConverter = curveConverter;
+ _settingsStore = settingsStore;
+ }
+
+ public ADB.Hatch Convert(SOG.Region target)
+ {
+ // Access a top-level transaction
+ ADB.Transaction tr = _settingsStore.Current.Document.TransactionManager.TopTransaction;
+ var btr = (ADB.BlockTableRecord)
+ tr.GetObject(_settingsStore.Current.Document.Database.CurrentSpaceId, ADB.OpenMode.ForWrite);
+
+ // initialize Hatch, append to blockTableRecord
+ ADB.Hatch acHatch = new();
+ btr.AppendEntity(acHatch);
+ tr.AddNewlyCreatedDBObject(acHatch, true);
+
+ // Set essential properties of the hatch object
+ acHatch.SetDatabaseDefaults();
+ acHatch.SetHatchPattern(ADB.HatchPatternType.PreDefined, "SOLID");
+
+ // Associative property must be set after the hatch object is
+ // appended to the block table record and before AppendLoop
+ acHatch.Associative = true;
+
+ // convert and assign boundary loop
+ ConvertAndAssignHatchLoop(btr, tr, acHatch, target.boundary, ADB.HatchLoopTypes.External);
+ foreach (var loop in target.innerLoops)
+ {
+ ConvertAndAssignHatchLoop(btr, tr, acHatch, loop, ADB.HatchLoopTypes.Outermost);
+ }
+
+ return acHatch;
+ }
+
+ private void ConvertAndAssignHatchLoop(
+ ADB.BlockTableRecord acBlkTblRec,
+ ADB.Transaction acTrans,
+ ADB.Hatch hatch,
+ ICurve curve,
+ ADB.HatchLoopTypes loopType
+ )
+ {
+ // convert loop, add to ObjectIdCollection
+ var convertedCurve = _curveConverter.Convert(curve);
+ CheckForNonPlanarLoops(convertedCurve);
+ var dbCurve = (ADB.Curve)convertedCurve[0].Item1;
+
+ // If Spline, turn into segmented polyline - this is how AutoCAD imports Hatches with Curve boundaries from Rhino
+ if (dbCurve is ADB.Spline spline)
+ {
+ if (spline.NurbsData.Degree == 1)
+ {
+ // for simple polylines ".ToPolylineWithPrecision" distorts the shape, so just applying a list of vertices
+ dbCurve = new ADB.Polyline3d(ADB.Poly3dType.SimplePoly, spline.NurbsData.GetControlPoints(), true);
+ }
+ else
+ {
+ dbCurve = spline.ToPolylineWithPrecision(10, false, false);
+ }
+ }
+ using ADB.ObjectIdCollection tempDBObjColl = CreateTempObjectIdCollection(acBlkTblRec, acTrans, dbCurve);
+
+ // append loop: possible Autodesk.AutoCAD.Runtime.Exception: eInvalidInput
+ hatch.AppendLoop(loopType, tempDBObjColl);
+ hatch.EvaluateHatch(true);
+ dbCurve.Erase();
+ }
+
+ private ADB.ObjectIdCollection CreateTempObjectIdCollection(
+ ADB.BlockTableRecord acBlkTblRec,
+ ADB.Transaction acTrans,
+ ADB.Entity loopEntity
+ )
+ {
+ // Add the new curve object to the block table record and the transaction
+ acBlkTblRec.AppendEntity(loopEntity);
+ acTrans.AddNewlyCreatedDBObject(loopEntity, true);
+
+ // Adds the entity to an object id array
+ ADB.ObjectIdCollection tempDBObjColl = new();
+ tempDBObjColl.Add(loopEntity.ObjectId);
+
+ return tempDBObjColl;
+ }
+
+ private void CheckForNonPlanarLoops(List<(ADB.Entity, Base)> convertedResult)
+ {
+ if (convertedResult.Count != 1)
+ {
+ // this will only be the case if it was a non-planar Polycurve: throw error
+ throw new ConversionException($"Non-planar Polycurve cannot be used as a Region loop: {convertedResult}");
+ }
+ }
+}
diff --git a/Converters/Autocad/Speckle.Converters.AutocadShared/ToHost/Raw/RegionToHostRawConverter.cs b/Converters/Autocad/Speckle.Converters.AutocadShared/ToHost/Raw/RegionToHostRawConverter.cs
new file mode 100644
index 000000000..b63889aeb
--- /dev/null
+++ b/Converters/Autocad/Speckle.Converters.AutocadShared/ToHost/Raw/RegionToHostRawConverter.cs
@@ -0,0 +1,89 @@
+using Speckle.Converters.Common.Objects;
+using Speckle.Objects;
+using Speckle.Sdk.Common.Exceptions;
+using Speckle.Sdk.Models;
+
+namespace Speckle.Converters.Autocad.ToHost.Raw;
+
+public class RegionToHostRawConverter : ITypedConverter
+{
+ private readonly ITypedConverter> _curveConverter;
+
+ public RegionToHostRawConverter(ITypedConverter> curveConverter)
+ {
+ _curveConverter = curveConverter;
+ }
+
+ public ADB.Region Convert(SOG.Region target)
+ {
+ // Notes from docs: The curveSegments must contain only Line, Arc, Ellipse, Circle, Spline, Polyline3d, or Polyline2d objects.
+ // The objects in curveSegments must be opened for read and not for write. If the objects are opened, calling this function will crash AutoCAD.
+
+ // Converted boundary
+ List<(ADB.Entity, Base)> convertedBoundary = _curveConverter.Convert(target.boundary);
+ ADB.Curve nativeBoundary = ValidateCurve(convertedBoundary);
+
+ // Converted loops
+ var nativeLoops = new List();
+ foreach (var loop in target.innerLoops)
+ {
+ List<(ADB.Entity, Base)> convertedLoop = _curveConverter.Convert(loop);
+ nativeLoops.Add(ValidateCurve(convertedLoop));
+ }
+
+ // Add boundary to the ADB.DBObjectCollection
+ // Calculate the outer region, method should return an array with 1 region
+ // https://help.autodesk.com/view/OARX/2025/ENU/?guid=GUID-684E602E-3555-4370-BCDC-1CE594676C43
+ ADB.DBObjectCollection boundaryDBObjColl = new();
+ boundaryDBObjColl.Add(nativeBoundary);
+ using (ADB.DBObjectCollection outerRegionColl = ADB.Region.CreateFromCurves(boundaryDBObjColl))
+ {
+ if (outerRegionColl.Count != 1)
+ {
+ throw new ConversionException(
+ $"Region conversion failed for {target}: unexpected number of shapes generated ({outerRegionColl.Count}). Make sure that input loops are planar, closed, non self-intersecting curves."
+ );
+ }
+ if (outerRegionColl[0] is ADB.Region adbRegion)
+ {
+ // Create and subtract the inner loops' regions, iterate through each
+ foreach (var nativeLoop in nativeLoops)
+ {
+ // Same as above: Add loop segments to the ADB.DBObjectCollection
+ // Calculate the inner region, method should return an array with 1 region
+ ADB.DBObjectCollection loopDBObjColl = new();
+ loopDBObjColl.Add(nativeLoop);
+ using (ADB.DBObjectCollection innerRegionColl = ADB.Region.CreateFromCurves(loopDBObjColl))
+ {
+ if (innerRegionColl.Count != 1)
+ {
+ throw new ConversionException(
+ $"Region conversion failed for {target}: unexpected number of shapes generated ({innerRegionColl.Count}). Make sure that input loops are planar, closed, non self-intersecting curves."
+ );
+ }
+ if (innerRegionColl[0] is ADB.Region adbInnerRegion)
+ {
+ // substract region from Boundary region
+ adbRegion.BooleanOperation(ADB.BooleanOperationType.BoolSubtract, adbInnerRegion);
+ adbInnerRegion.Dispose();
+ }
+ }
+ }
+
+ return adbRegion;
+ }
+ }
+
+ throw new ConversionException($"Region conversion failed: {target}");
+ }
+
+ private ADB.Curve ValidateCurve(List<(ADB.Entity, Base)> convertedResult)
+ {
+ if (convertedResult.Count != 1)
+ {
+ // this will only be the case if it was a non-planar Polycurve: throw error
+ throw new ConversionException($"Non-planar Polycurve cannot be used as a Region loop: {convertedResult}");
+ }
+ return (ADB.Curve)convertedResult[0].Item1;
+ }
+}
diff --git a/Converters/Autocad/Speckle.Converters.AutocadShared/ToSpeckle/Geometry/HatchToSpeckleConverter.cs b/Converters/Autocad/Speckle.Converters.AutocadShared/ToSpeckle/Geometry/HatchToSpeckleConverter.cs
new file mode 100644
index 000000000..3d724b445
--- /dev/null
+++ b/Converters/Autocad/Speckle.Converters.AutocadShared/ToSpeckle/Geometry/HatchToSpeckleConverter.cs
@@ -0,0 +1,119 @@
+using Speckle.Converters.Common;
+using Speckle.Converters.Common.Objects;
+using Speckle.Sdk.Common.Exceptions;
+using Speckle.Sdk.Models;
+
+namespace Speckle.Converters.Autocad.Geometry;
+
+[NameAndRankValue(typeof(ADB.Hatch), NameAndRankValueAttribute.SPECKLE_DEFAULT_RANK)]
+public class HatchToSpeckleConverter : IToSpeckleTopLevelConverter, ITypedConverter
+{
+ private readonly ITypedConverter _regionConverter;
+
+ public HatchToSpeckleConverter(ITypedConverter regionConverter)
+ {
+ _regionConverter = regionConverter;
+ }
+
+ public Base Convert(object target) => Convert((ADB.Hatch)target);
+
+ public SOG.Region Convert(ADB.Hatch target)
+ {
+ ADB.Region? regionToConvert = null;
+
+ for (int i = 0; i < target.NumberOfLoops; i++)
+ {
+ // Create 3d polyline from the HatchLoop
+ ADB.HatchLoop loop = target.GetLoopAt(i);
+ ADB.Curve polyline = PolylineFromLoop(loop);
+ ADB.DBObjectCollection objCollection = new();
+ objCollection.Add(polyline);
+
+ // Convert polyline into an individual Region
+ using (ADB.DBObjectCollection regionCollection = ADB.Region.CreateFromCurves(objCollection))
+ {
+ if (regionCollection.Count != 1)
+ {
+ throw new ConversionException(
+ $"Hatch conversion failed {target}: unexpected number of regions generated from 1 hatch loop"
+ );
+ }
+ ADB.Region loopRegion = (ADB.Region)regionCollection[0];
+
+ // Assign first loop as the main Region, other Regions will be subtracted from it
+ if (i == 0)
+ {
+ regionToConvert = loopRegion;
+ }
+ else
+ {
+ if (regionToConvert == null)
+ {
+ throw new ConversionException($"Hatch conversion failed: {target}");
+ }
+ // subtract region from Boundary region
+ double areaBefore = regionToConvert.Area;
+ regionToConvert.BooleanOperation(ADB.BooleanOperationType.BoolSubtract, loopRegion);
+
+ // check if the region did not change after subtraction: means the loop was a separate hatch part
+ if (Math.Abs(areaBefore - regionToConvert.Area) < 0.00001)
+ {
+ throw new ConversionException($"Composite hatches are not supported: {target}");
+ }
+ }
+ }
+ }
+
+ if (regionToConvert == null)
+ {
+ throw new ConversionException($"Hatch conversion failed: {target}");
+ }
+
+ // convert and store Regions
+ SOG.Region convertedRegion = _regionConverter.Convert(regionToConvert);
+ convertedRegion.hasHatchPattern = true;
+
+ return convertedRegion;
+ }
+
+ private ADB.Curve PolylineFromLoop(ADB.HatchLoop loop)
+ {
+ if (loop.IsPolyline)
+ {
+ // disposable object, wrapping into "using"
+ using (AG.Point3dCollection vertices = new())
+ {
+ // collect vertices and construct a polyline simultaneously, it will be clear what to use after iterating
+ ADB.Polyline polyline = new() { Closed = true };
+
+ int count = 0;
+ foreach (ADB.BulgeVertex bVertex in loop.Polyline)
+ {
+ // don't add the end point that's the same as the start point
+ AG.Point3d newPt = new(bVertex.Vertex.X, bVertex.Vertex.Y, 0);
+ if (count == 0 || vertices[0].DistanceTo(newPt) > 0.00001)
+ {
+ vertices.Add(newPt);
+ polyline.AddVertexAt(count, bVertex.Vertex, bVertex.Bulge, 0, 0);
+ count++;
+ }
+ }
+
+ // if only 2 points, that's a circle
+ if (vertices.Count == 2)
+ {
+ AG.Point3d centerPt =
+ new(
+ vertices[0].X + (vertices[1].X - vertices[0].X) / 2,
+ vertices[0].Y + (vertices[1].Y - vertices[0].Y) / 2,
+ 0
+ );
+ return new ADB.Circle(centerPt, AG.Vector3d.ZAxis, vertices[0].DistanceTo(vertices[1]) / 2);
+ }
+ return polyline;
+ }
+ }
+
+ throw new ConversionException("Hatch loop conversion failed.");
+ }
+}
diff --git a/Converters/Autocad/Speckle.Converters.AutocadShared/ToSpeckle/Geometry/RegionToSpeckleConverter.cs b/Converters/Autocad/Speckle.Converters.AutocadShared/ToSpeckle/Geometry/RegionToSpeckleConverter.cs
index 72ba00eef..25ac8acd8 100644
--- a/Converters/Autocad/Speckle.Converters.AutocadShared/ToSpeckle/Geometry/RegionToSpeckleConverter.cs
+++ b/Converters/Autocad/Speckle.Converters.AutocadShared/ToSpeckle/Geometry/RegionToSpeckleConverter.cs
@@ -1,24 +1,40 @@
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
+using Speckle.Objects;
using Speckle.Sdk.Common.Exceptions;
using Speckle.Sdk.Models;
namespace Speckle.Converters.Autocad.Geometry;
[NameAndRankValue(typeof(ADB.Region), NameAndRankValueAttribute.SPECKLE_DEFAULT_RANK)]
-public class RegionToSpeckleConverter : IToSpeckleTopLevelConverter, ITypedConverter
+public class RegionToSpeckleConverter : IToSpeckleTopLevelConverter, ITypedConverter
{
private readonly ITypedConverter _brepConverter;
+ private readonly ITypedConverter _lineConverter;
+ private readonly ITypedConverter _arcConverter;
+ private readonly ITypedConverter _circleConverter;
+ private readonly IConverterSettingsStore _settingsStore;
- public RegionToSpeckleConverter(ITypedConverter brepConverter)
+ public RegionToSpeckleConverter(
+ ITypedConverter brepConverter,
+ ITypedConverter lineConverter,
+ ITypedConverter arcConverter,
+ ITypedConverter circleConverter,
+ IConverterSettingsStore settingsStore
+ )
{
_brepConverter = brepConverter;
+ _lineConverter = lineConverter;
+ _arcConverter = arcConverter;
+ _circleConverter = circleConverter;
+ _settingsStore = settingsStore;
}
public Base Convert(object target) => Convert((ADB.Region)target);
- public SOG.Mesh Convert(ADB.Region target)
+ public SOG.Region Convert(ADB.Region target)
{
+ // generate Mesh for displayValue
using ABR.Brep brep = new(target);
if (brep.IsNull)
{
@@ -28,6 +44,106 @@ public class RegionToSpeckleConverter : IToSpeckleTopLevelConverter, ITypedConve
SOG.Mesh mesh = _brepConverter.Convert(brep);
mesh.area = target.Area;
- return mesh;
+ // get all brep loops: can consist of LineSegment3d or CircularArc3d edges
+ var brepLoops = brep
+ .Complexes.SelectMany(complex => complex.Shells)
+ .SelectMany(shell => shell.Faces)
+ .SelectMany(face => face.Loops);
+
+ // Get and convert boundary and inner loops
+ var boundary = GetConvertedLoops(brepLoops, true)[0];
+ var innerLoops = GetConvertedLoops(brepLoops, false);
+
+ return new SOG.Region()
+ {
+ boundary = boundary,
+ innerLoops = innerLoops,
+ hasHatchPattern = false,
+ displayValue = [mesh],
+ units = _settingsStore.Current.SpeckleUnits
+ };
+ }
+
+ private List GetConvertedLoops(IEnumerable brepLoops, bool getOuterLoop)
+ {
+ var loops = new List();
+ foreach (var loop in brepLoops)
+ {
+ bool outer = loop.LoopType == ABR.LoopType.LoopExterior;
+
+ // continue only if the loop type is as requester (outer or inner)
+ if ((outer && getOuterLoop) || (!outer && !getOuterLoop))
+ {
+ // create segment collection for the current loop
+ var segments = new List();
+ foreach (var edge in loop.Edges)
+ {
+ var curve = edge.Curve;
+ if (curve is AG.ExternalCurve3d xCurve && xCurve.IsNativeCurve)
+ {
+ segments.Add(xCurve.NativeCurve);
+ }
+ else
+ {
+ throw new ConversionException("Unsupported curve type for Region conversion");
+ }
+ }
+ // reverse segment collection with arcs in case end-start points of subsequent segments don't match
+ if (
+ segments.Any(x => x is AG.CircularArc3d)
+ && segments.Count > 1
+ && Math.Abs(segments[0].EndPoint.DistanceTo(segments[1].StartPoint)) > 0.00001
+ )
+ {
+ segments.Reverse();
+ }
+
+ // convert segments to Speckle Polycurve or Circle
+ var convertedLoop = ConvertSegmentsToICurve(segments);
+ loops.Add(convertedLoop);
+ }
+ }
+
+ return loops;
+ }
+
+ private ICurve ConvertSegmentsToICurve(List segments)
+ {
+ ICurve convertedLoop;
+
+ // Handle edge case: if the segment is a closed Arc, then use Circle conversion to create a valid shape.
+ // Also, closed arcs cause errors when receiving in other host apps, like Rhino.
+ if (segments.Count == 1 && segments[0] is AG.CircularArc3d arc && arc.StartAngle + arc.EndAngle == 0)
+ {
+ convertedLoop = _circleConverter.Convert(
+ new ADB.Circle(arc.GetPlane().PointOnPlane, arc.GetPlane().Normal, arc.Radius)
+ );
+ }
+ // otherwise, just construct a Polycurve from subsequent segments
+ else
+ {
+ // Maybe we need to convert to AutoCAD Polycurve
+ convertedLoop = new SOG.Polycurve()
+ {
+ segments = segments.Select(x => ConvertSegment(x)).ToList(),
+ closed = true,
+ units = _settingsStore.Current.SpeckleUnits
+ };
+ }
+
+ return convertedLoop;
+ }
+
+ private ICurve ConvertSegment(AG.Curve3d curve)
+ {
+ switch (curve)
+ {
+ case AG.LineSegment3d line:
+ return _lineConverter.Convert(line);
+ case AG.CircularArc3d arc:
+ return _arcConverter.Convert(arc);
+ }
+
+ throw new ConversionException($"Unsupported curve type for Region conversion: {curve}");
}
}