feat (autocad): region and hatches conversions (#681)

* clean send (except closed circular arcs)

* split functions

* include inner loops for complex regions

* reorient only polycurves with arcs

* regions to host

* merge conflict

* substract regions on receive

* optimize

* add checks

* fix icurve, receive hatches

* hatch receive

* reduce dataObject conversions

* finally hatches are recorded in a database

* regrouped

* simplify

* rename

* send hatch (only the first)

* remove hatches for now

* comment

* fixed icurve converter and reference in DataObject converter

* reformat

* hatch to speckle

* hatch to host

* hatch receive works

* set solid pattern on receive

* send properly and throw if complex hacth

* calculate mesh area from region

* prevent Autocad crash by catching exceptions in the middle of transaction

* sending both polylines and curves for hatches

* boolean operation for sending hatches

* turned 2d Hatch curves into 3d - now Brep creation doesn't fail!

* circles handled

* construct both polyline and vertices in parallel

* handling splines on receive

* comments

* don't reverse proper segments

* basic comment fixes

* open block table for write more concise

* use top-level transaction

---------

Co-authored-by: Claire Kuang <kuang.claire@gmail.com>
This commit is contained in:
KatKatKateryna
2025-04-01 11:36:04 +01:00
committed by GitHub
parent 13f3bb8ae5
commit bcefe3b4c4
8 changed files with 549 additions and 48 deletions
@@ -32,6 +32,10 @@
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Geometry\CurveToHostConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Geometry\EllipseToHostConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Geometry\PolycurveToHostConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Geometry\RegionToHostConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Raw\RegionHatchToHostRawConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Raw\RegionToHostRawConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Raw\ICurveToHostRawConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Raw\ArcToHostRawConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Raw\CurveToHostRawConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Raw\IntervalToHostRawConverter.cs" />
@@ -44,6 +48,7 @@
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Raw\PointToHostRawConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToHost\Geometry\PointToHostConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Geometry\ArcToSpeckleConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Geometry\HatchToSpeckleConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Geometry\RegionToSpeckleConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Geometry\SurfaceToSpeckleConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Geometry\Solid3dToSpeckleConverter.cs" />
@@ -77,4 +82,4 @@
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Raw\DBSplineToSpeckleRawConverter.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ToSpeckle\Raw\VectorToSpeckleRawConverter.cs" />
</ItemGroup>
</Project>
</Project>
@@ -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<DataObject, List<(ADB.Entity a, Base b)>>
{
private readonly ITypedConverter<SOG.Arc, ADB.Arc> _arcConverter;
private readonly ITypedConverter<ICurve, List<(ADB.Entity, Base)>> _curveConverter;
private readonly ITypedConverter<SOG.BrepX, List<(ADB.Entity a, Base b)>> _brepXConverter;
private readonly ITypedConverter<SOG.Circle, ADB.Circle> _circleConverter;
private readonly ITypedConverter<SOG.Curve, ADB.Curve> _curveConverter;
private readonly ITypedConverter<SOG.Ellipse, ADB.Ellipse> _ellipseConverter;
private readonly ITypedConverter<SOG.ExtrusionX, List<(ADB.Entity a, Base b)>> _extrusionXConverter;
private readonly ITypedConverter<SOG.Line, ADB.Line> _lineConverter;
private readonly ITypedConverter<SOG.Mesh, ADB.PolyFaceMesh> _meshConverter;
private readonly ITypedConverter<SOG.Point, ADB.DBPoint> _pointConverter;
private readonly ITypedConverter<SOG.Polycurve, List<(ADB.Entity a, Base b)>> _polycurveConverter;
private readonly ITypedConverter<SOG.Polyline, ADB.Polyline3d> _polylineConverter;
private readonly ITypedConverter<SOG.SubDX, List<(ADB.Entity a, Base b)>> _subDXConverter;
private readonly ITypedConverter<SOG.Region, ADB.Entity> _regionConverter;
public DataObjectConverter(
ITypedConverter<SOG.Arc, ADB.Arc> arcConverter,
ITypedConverter<ICurve, List<(ADB.Entity, Base)>> curveConverter,
ITypedConverter<SOG.BrepX, List<(ADB.Entity a, Base b)>> brepXConverter,
ITypedConverter<SOG.Circle, ADB.Circle> circleConverter,
ITypedConverter<SOG.Curve, ADB.Curve> curveConverter,
ITypedConverter<SOG.Ellipse, ADB.Ellipse> ellipseConverter,
ITypedConverter<SOG.ExtrusionX, List<(ADB.Entity a, Base b)>> extrusionXConverter,
ITypedConverter<SOG.Line, ADB.Line> lineConverter,
ITypedConverter<SOG.Mesh, ADB.PolyFaceMesh> meshConverter,
ITypedConverter<SOG.Point, ADB.DBPoint> pointConverter,
ITypedConverter<SOG.Polycurve, List<(ADB.Entity, Base)>> polycurveConverter,
ITypedConverter<SOG.Polyline, ADB.Polyline3d> polylineConverter,
ITypedConverter<SOG.SubDX, List<(ADB.Entity a, Base b)>> subDXConverter
ITypedConverter<SOG.SubDX, List<(ADB.Entity a, Base b)>> subDXConverter,
ITypedConverter<SOG.Region, ADB.Entity> 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<Dat
{
switch (displayObject)
{
case SOG.Arc arc:
yield return (_arcConverter.Convert(arc), arc);
break;
case SOG.BrepX brepX:
foreach (var i in _brepXConverter.Convert(brepX))
{
yield return i;
}
break;
case SOG.Circle circle:
yield return (_circleConverter.Convert(circle), circle);
break;
case SOG.Curve curve:
yield return (_curveConverter.Convert(curve), curve);
break;
case SOG.Ellipse ellipse:
yield return (_ellipseConverter.Convert(ellipse), ellipse);
break;
case SOG.ExtrusionX extrusionX:
foreach (var i in _extrusionXConverter.Convert(extrusionX))
{
yield return i;
}
break;
case SOG.Line line:
yield return (_lineConverter.Convert(line), line);
break;
case SOG.Mesh mesh:
yield return (_meshConverter.Convert(mesh), mesh);
break;
case SOG.Point point:
yield return (_pointConverter.Convert(point), point);
break;
case SOG.Polycurve polycurve:
foreach (var i in _polycurveConverter.Convert(polycurve))
case ICurve curve:
foreach (var result in _curveConverter.Convert(curve))
{
yield return i;
yield return result;
}
break;
case SOG.Polyline polyline:
yield return (_polylineConverter.Convert(polyline), polyline);
break;
case SOG.SubDX subDX:
foreach (var i in _subDXConverter.Convert(subDX))
{
yield return i;
}
break;
case SOG.Region region:
yield return (_regionConverter.Convert(region), region);
break;
default:
throw new ConversionException($"Found unsupported geometry: {displayObject.GetType()}");
@@ -0,0 +1,33 @@
using Speckle.Converters.Common;
using Speckle.Converters.Common.Objects;
using Speckle.Sdk.Models;
namespace Speckle.Converters.Autocad.ToHost.Geometry;
[NameAndRankValue(typeof(SOG.Region), NameAndRankValueAttribute.SPECKLE_DEFAULT_RANK)]
public class RegionToHostConverter : IToHostTopLevelConverter, ITypedConverter<SOG.Region, ADB.Entity>
{
private readonly ITypedConverter<SOG.Region, ADB.Region> _regionConverter;
private readonly ITypedConverter<SOG.Region, ADB.Hatch> _hatchConverter;
public RegionToHostConverter(
ITypedConverter<SOG.Region, ADB.Region> regionConverter,
ITypedConverter<SOG.Region, ADB.Hatch> 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);
}
}
@@ -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<ICurve, List<(ADB.Entity, Base)>>
{
private readonly ITypedConverter<SOG.Line, ADB.Line> _lineConverter;
private readonly ITypedConverter<SOG.Arc, ADB.Arc> _arcConverter;
private readonly ITypedConverter<SOG.Ellipse, ADB.Ellipse> _ellipseConverter;
private readonly ITypedConverter<SOG.Circle, ADB.Circle> _circleConverter;
private readonly ITypedConverter<SOG.Polyline, ADB.Polyline3d> _polylineConverter;
private readonly ITypedConverter<SOG.Polycurve, List<(ADB.Entity, Base)>> _polycurveConverter;
private readonly ITypedConverter<SOG.Curve, ADB.Curve> _curveConverter;
public ICurveToHostRawConverter(
ITypedConverter<SOG.Line, ADB.Line> lineConverter,
ITypedConverter<SOG.Arc, ADB.Arc> arcConverter,
ITypedConverter<SOG.Ellipse, ADB.Ellipse> ellipseConverter,
ITypedConverter<SOG.Circle, ADB.Circle> circleConverter,
ITypedConverter<SOG.Polyline, ADB.Polyline3d> polylineConverter,
ITypedConverter<SOG.Polycurve, List<(ADB.Entity, Base)>> polycurveConverter,
ITypedConverter<SOG.Curve, ADB.Curve> curveConverter
)
{
_lineConverter = lineConverter;
_arcConverter = arcConverter;
_ellipseConverter = ellipseConverter;
_circleConverter = circleConverter;
_polylineConverter = polylineConverter;
_polycurveConverter = polycurveConverter;
_curveConverter = curveConverter;
}
/// <summary>
/// Converts a given ICurve object to a list of ADB.Curve.
/// </summary>
/// <param name="target">The ICurve object to convert.</param>
/// <returns>The converted list of ADB.Curve.</returns>
/// <exception cref="NotSupportedException">Thrown when the conversion is not supported for the given type of curve.</exception>
/// <remarks>⚠️ This conversion does NOT perform scaling.</remarks>
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}")
};
}
@@ -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<SOG.Region, ADB.Hatch>
{
private readonly ITypedConverter<ICurve, List<(ADB.Entity, Base)>> _curveConverter;
private readonly IConverterSettingsStore<AutocadConversionSettings> _settingsStore;
public RegionHatchToHostRawConverter(
ITypedConverter<ICurve, List<(ADB.Entity, Base)>> curveConverter,
IConverterSettingsStore<AutocadConversionSettings> 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}");
}
}
}
@@ -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<SOG.Region, ADB.Region>
{
private readonly ITypedConverter<ICurve, List<(ADB.Entity, Base)>> _curveConverter;
public RegionToHostRawConverter(ITypedConverter<ICurve, List<(ADB.Entity, Base)>> 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<ADB.Curve>();
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;
}
}
@@ -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<ADB.Hatch, SOG.Region>
{
private readonly ITypedConverter<ADB.Region, SOG.Region> _regionConverter;
public HatchToSpeckleConverter(ITypedConverter<ADB.Region, SOG.Region> 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.");
}
}
@@ -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<ADB.Region, SOG.Mesh>
public class RegionToSpeckleConverter : IToSpeckleTopLevelConverter, ITypedConverter<ADB.Region, SOG.Region>
{
private readonly ITypedConverter<ABR.Brep, SOG.Mesh> _brepConverter;
private readonly ITypedConverter<AG.LineSegment3d, SOG.Line> _lineConverter;
private readonly ITypedConverter<AG.CircularArc3d, SOG.Arc> _arcConverter;
private readonly ITypedConverter<ADB.Circle, SOG.Circle> _circleConverter;
private readonly IConverterSettingsStore<AutocadConversionSettings> _settingsStore;
public RegionToSpeckleConverter(ITypedConverter<ABR.Brep, SOG.Mesh> brepConverter)
public RegionToSpeckleConverter(
ITypedConverter<ABR.Brep, SOG.Mesh> brepConverter,
ITypedConverter<AG.LineSegment3d, SOG.Line> lineConverter,
ITypedConverter<AG.CircularArc3d, SOG.Arc> arcConverter,
ITypedConverter<ADB.Circle, SOG.Circle> circleConverter,
IConverterSettingsStore<AutocadConversionSettings> 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<ICurve> GetConvertedLoops(IEnumerable<ABR.BoundaryLoop> brepLoops, bool getOuterLoop)
{
var loops = new List<ICurve>();
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<AG.Curve3d>();
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<AG.Curve3d> 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}");
}
}