From 617bb5f12d3c3a6e1e9ac249923f318ee0562c24 Mon Sep 17 00:00:00 2001 From: Claire Kuang Date: Wed, 16 Oct 2024 15:31:17 +0100 Subject: [PATCH] feat(civil3d): adds corridors (#302) * adds general and class properties extractors * Update Speckle.Converters.Civil3dShared.projitems * adds class properties for catchments * adds catchment group proxies * catchment proxy bug fix * adds site props * Update ClassPropertiesExtractor.cs * Update ClassPropertiesExtractor.cs * adds network, structure, and pipes * registers pipe network * adds alignment basecurves and properties * adds profiles to alignments * adds corridors * fixes di and other corridor bugs * parses corridor solid property sets * Update CorridorHandler.cs * Update CorridorHandler.cs * adds body raw converter to autocad * adds calculated info * Update PropertySetDefinitionHandler.cs * Update DBBodyToSpeckleRawConverter.cs --- ...Speckle.Converters.AutocadShared.projitems | 2 + .../Raw/CircularArc2dToSpeckleRawConverter.cs | 75 ++++ .../Raw/DBBodyToSpeckleRawConverter.cs | 95 +++++ .../Civil3dRootToSpeckleConverter.cs | 6 +- .../Helpers/BaseCurveExtractor.cs | 84 ++++- .../Helpers/CorridorHandler.cs | 329 ++++++++++++++++++ .../ServiceRegistration.cs | 1 + ...Speckle.Converters.Civil3dShared.projitems | 4 + .../CivilEntityToSpeckleTopLevelConverter.cs | 54 ++- .../Properties/ClassPropertiesExtractor.cs | 43 ++- .../Properties/GeneralPropertiesExtractor.cs | 216 +++++++++++- .../PropertySetDefinitionHandler.cs | 8 +- .../Properties/PropertySetExtractor.cs | 15 +- ...gnmentSubentityArcToSpeckleRawConverter.cs | 98 ++++++ ...nmentSubentityLineToSpeckleRawConverter.cs | 49 +++ ...entSubentitySpiralToSpeckleRawConverter.cs | 75 ++++ 16 files changed, 1114 insertions(+), 40 deletions(-) create mode 100644 Converters/Autocad/Speckle.Converters.AutocadShared/ToSpeckle/Raw/CircularArc2dToSpeckleRawConverter.cs create mode 100644 Converters/Autocad/Speckle.Converters.AutocadShared/ToSpeckle/Raw/DBBodyToSpeckleRawConverter.cs create mode 100644 Converters/Civil3d/Speckle.Converters.Civil3dShared/Helpers/CorridorHandler.cs create mode 100644 Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Raw/AlignmentSubentityArcToSpeckleRawConverter.cs create mode 100644 Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Raw/AlignmentSubentityLineToSpeckleRawConverter.cs create mode 100644 Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Raw/AlignmentSubentitySpiralToSpeckleRawConverter.cs diff --git a/Converters/Autocad/Speckle.Converters.AutocadShared/Speckle.Converters.AutocadShared.projitems b/Converters/Autocad/Speckle.Converters.AutocadShared/Speckle.Converters.AutocadShared.projitems index f7089bc6f..d303d2d3d 100644 --- a/Converters/Autocad/Speckle.Converters.AutocadShared/Speckle.Converters.AutocadShared.projitems +++ b/Converters/Autocad/Speckle.Converters.AutocadShared/Speckle.Converters.AutocadShared.projitems @@ -42,10 +42,12 @@ + + diff --git a/Converters/Autocad/Speckle.Converters.AutocadShared/ToSpeckle/Raw/CircularArc2dToSpeckleRawConverter.cs b/Converters/Autocad/Speckle.Converters.AutocadShared/ToSpeckle/Raw/CircularArc2dToSpeckleRawConverter.cs new file mode 100644 index 000000000..7b9635e0c --- /dev/null +++ b/Converters/Autocad/Speckle.Converters.AutocadShared/ToSpeckle/Raw/CircularArc2dToSpeckleRawConverter.cs @@ -0,0 +1,75 @@ +using Speckle.Converters.Common; +using Speckle.Converters.Common.Objects; + +namespace Speckle.Converters.Autocad.ToSpeckle.Raw; + +public class CircularArc2dToSpeckleConverter : ITypedConverter +{ + private readonly ITypedConverter _planeConverter; + private readonly IConverterSettingsStore _settingsStore; + + public CircularArc2dToSpeckleConverter( + ITypedConverter planeConverter, + IConverterSettingsStore settingsStore + ) + { + _planeConverter = planeConverter; + _settingsStore = settingsStore; + } + + public SOG.Arc Convert(AG.CircularArc2d target) + { + string units = _settingsStore.Current.SpeckleUnits; + + // find arc plane (normal is in clockwise dir) + var center3 = new AG.Point3d(target.Center.X, target.Center.Y, 0); + AG.Plane plane = target.IsClockWise + ? new AG.Plane(center3, AG.Vector3d.ZAxis.MultiplyBy(-1)) + : new AG.Plane(center3, AG.Vector3d.ZAxis); + + // calculate total angle. TODO: This needs to be validated across all possible arc orientations + var totalAngle = target.IsClockWise + ? Math.Abs(target.EndAngle - target.StartAngle) + : Math.Abs(target.EndAngle - target.StartAngle); + + double startParam = target.GetParameterOf(target.StartPoint); + double endParam = target.GetParameterOf(target.EndPoint); + AG.Point2d midPoint = target.EvaluatePoint(target.StartAngle + (target.EndAngle - target.StartAngle) / 2); + + // create arc + var arc = new SOG.Arc() + { + plane = _planeConverter.Convert(plane), + radius = target.Radius, + startPoint = new() + { + x = target.StartPoint.X, + y = target.StartPoint.Y, + z = 0, + units = units + }, + endPoint = new() + { + x = target.EndPoint.X, + y = target.EndPoint.Y, + z = 0, + units = units + }, + midPoint = new() + { + x = midPoint.X, + y = midPoint.Y, + z = 0, + units = units + }, + startAngle = target.StartAngle, + endAngle = target.EndAngle, + angleRadians = totalAngle, + domain = new SOP.Interval { start = startParam, end = endParam }, + length = target.GetLength(0, 1), + units = units + }; + + return arc; + } +} diff --git a/Converters/Autocad/Speckle.Converters.AutocadShared/ToSpeckle/Raw/DBBodyToSpeckleRawConverter.cs b/Converters/Autocad/Speckle.Converters.AutocadShared/ToSpeckle/Raw/DBBodyToSpeckleRawConverter.cs new file mode 100644 index 000000000..7c3278849 --- /dev/null +++ b/Converters/Autocad/Speckle.Converters.AutocadShared/ToSpeckle/Raw/DBBodyToSpeckleRawConverter.cs @@ -0,0 +1,95 @@ +using Speckle.Converters.Common; +using Speckle.Converters.Common.Objects; +using Speckle.Sdk; +using Speckle.Sdk.Common.Exceptions; +using Speckle.Sdk.Models; + +namespace Speckle.Converters.Autocad.ToSpeckle.Raw; + +public class DBBodyToSpeckleRawConverter : ITypedConverter +{ + private readonly ITypedConverter _pointConverter; + private readonly ITypedConverter _boxConverter; + private readonly IConverterSettingsStore _settingsStore; + + public DBBodyToSpeckleRawConverter( + ITypedConverter pointConverter, + ITypedConverter boxConverter, + IConverterSettingsStore settingsStore + ) + { + _pointConverter = pointConverter; + _boxConverter = boxConverter; + _settingsStore = settingsStore; + } + + public Base Convert(object target) => Convert((ADB.Body)target); + + public SOG.Mesh Convert(ADB.Body target) + { + using ABR.Brep brep = new(target); + if (brep.IsNull) + { + throw new ConversionException("Could not retrieve brep from the body."); + } + + var vertices = new List(); + var faces = new List(); + + // create mesh from solid with mesh filter + using ABR.Mesh2dControl control = new(); + control.MaxSubdivisions = 10000; // POC: these settings may need adjusting + using ABR.Mesh2dFilter filter = new(); + filter.Insert(brep, control); + using ABR.Mesh2d m = new(filter); + foreach (ABR.Element2d e in m.Element2ds) + { + // get vertices + List faceIndices = new(); + foreach (ABR.Node n in e.Nodes) + { + faceIndices.Add(vertices.Count); + vertices.Add(n.Point); + n.Dispose(); + } + + // get faces + List faceList = new() { e.Nodes.Count() }; + for (int i = 0; i < e.Nodes.Count(); i++) + { + faceList.Add(faceIndices[i]); + } + + faces.AddRange(faceList); + + e.Dispose(); + } + + // mesh props + var convertedVertices = vertices.SelectMany(o => _pointConverter.Convert(o).ToList()).ToList(); + SOG.Box bbox = _boxConverter.Convert(target.GeometricExtents); + + // create speckle mesh + SOG.Mesh mesh = + new() + { + vertices = convertedVertices, + faces = faces, + units = _settingsStore.Current.SpeckleUnits, + bbox = bbox + }; + + try + { + mesh.area = brep.GetSurfaceArea(); + } + catch (Exception e) when (!e.IsFatal()) { } + try + { + mesh.volume = brep.GetVolume(); + } + catch (Exception e) when (!e.IsFatal()) { } + + return mesh; + } +} diff --git a/Converters/Civil3d/Speckle.Converters.Civil3dShared/Civil3dRootToSpeckleConverter.cs b/Converters/Civil3d/Speckle.Converters.Civil3dShared/Civil3dRootToSpeckleConverter.cs index 36a8ed040..e6b8c554e 100644 --- a/Converters/Civil3d/Speckle.Converters.Civil3dShared/Civil3dRootToSpeckleConverter.cs +++ b/Converters/Civil3d/Speckle.Converters.Civil3dShared/Civil3dRootToSpeckleConverter.cs @@ -88,21 +88,21 @@ public Base Convert(object target) // get general properties Dictionary? generalProperties = _generalPropertiesExtractor.GetGeneralProperties(entity); - if (generalProperties is not null) + if (generalProperties is not null && generalProperties.Count > 0) { properties.Add("Properties", generalProperties); } // get part data Dictionary? partData = _partDataExtractor.GetPartData(entity); - if (partData is not null) + if (partData is not null && partData.Count > 0) { properties.Add("Part Data", partData); } // get property set data Dictionary? propertySets = _propertySetExtractor.GetPropertySets(entity); - if (propertySets is not null) + if (propertySets is not null && propertySets.Count > 0) { properties.Add("Property Sets", propertySets); } diff --git a/Converters/Civil3d/Speckle.Converters.Civil3dShared/Helpers/BaseCurveExtractor.cs b/Converters/Civil3d/Speckle.Converters.Civil3dShared/Helpers/BaseCurveExtractor.cs index 02acc8130..f8ee35255 100644 --- a/Converters/Civil3d/Speckle.Converters.Civil3dShared/Helpers/BaseCurveExtractor.cs +++ b/Converters/Civil3d/Speckle.Converters.Civil3dShared/Helpers/BaseCurveExtractor.cs @@ -1,7 +1,6 @@ using Microsoft.Extensions.Logging; using Speckle.Converters.Common; using Speckle.Converters.Common.Objects; -using Speckle.Objects; namespace Speckle.Converters.Civil3dShared.Helpers; @@ -10,26 +9,37 @@ public sealed class BaseCurveExtractor private readonly ILogger _logger; private readonly IConverterSettingsStore _converterSettings; private readonly ITypedConverter _lineConverter; - - //private readonly ITypedConverter _arcConverter; + private readonly ITypedConverter _arcConverter; + private readonly ITypedConverter _alignmentLineConverter; + private readonly ITypedConverter _alignmentArcConverter; + private readonly ITypedConverter< + (CDB.AlignmentSubEntitySpiral, CDB.Alignment), + SOG.Polyline + > _alignmentSpiralConverter; private readonly ITypedConverter _curveConverter; public BaseCurveExtractor( ITypedConverter lineConverter, - //ITypedConverter arcConverter, + ITypedConverter arcConverter, + ITypedConverter alignmentLineConverter, + ITypedConverter alignmentArcConverter, + ITypedConverter<(CDB.AlignmentSubEntitySpiral, CDB.Alignment), SOG.Polyline> alignmentSpiralConverter, ITypedConverter curveConverter, ILogger logger, IConverterSettingsStore converterSettings ) { _lineConverter = lineConverter; - //_arcConverter = arcConverter; + _arcConverter = arcConverter; + _alignmentLineConverter = alignmentLineConverter; + _alignmentArcConverter = alignmentArcConverter; + _alignmentSpiralConverter = alignmentSpiralConverter; _curveConverter = curveConverter; _logger = logger; _converterSettings = converterSettings; } - public List? GetBaseCurve(CDB.Entity entity) + public List? GetBaseCurves(CDB.Entity entity) { switch (entity) { @@ -37,19 +47,65 @@ IConverterSettingsStore converterSettings // do not use basecurve for pipes 😡 // currently not handling arc pipes due to lack of CircularArc2D converter, and also way to properly retrieve 2d arc curve case CDB.Pipe pipe: - ICurve pipeCurve = - //pipe.SubEntityType == PipeSubEntityType.Straight ? - _lineConverter.Convert(new AG.LineSegment3d(pipe.StartPoint, pipe.EndPoint)); - //: _arcConverter.Convert(pipe.Curve2d); - return new() { pipeCurve }; + return GetPipeBaseCurves(pipe); - case CDB.Alignment: - ICurve baseCurve = _curveConverter.Convert(entity.BaseCurve); - return new() { baseCurve }; + case CDB.Alignment alignment: + return GetAlignmentBaseCurves(alignment); // for any entities that don't use their basecurve prop default: return null; } } + + private List GetPipeBaseCurves(CDB.Pipe pipe) + { + switch (pipe.SubEntityType) + { + case CDB.PipeSubEntityType.Curved: + return new() { _arcConverter.Convert(pipe.Curve2d) }; + + // POC: don't know how to properly handle segmented and flex pipes for now, sending them as lines + case CDB.PipeSubEntityType.Straight: + default: + return new() { _lineConverter.Convert(new AG.LineSegment3d(pipe.StartPoint, pipe.EndPoint)) }; + } + } + + private List GetAlignmentBaseCurves(CDB.Alignment alignment) + { + // get the alignment subentity curves + List curves = new(); + for (int i = 0; i < alignment.Entities.Count; i++) + { + CDB.AlignmentEntity entity = alignment.Entities.GetEntityByOrder(i); + for (int j = 0; j < entity.SubEntityCount; j++) + { + CDB.AlignmentSubEntity subEntity = entity[j]; + switch (subEntity.SubEntityType) + { + case CDB.AlignmentSubEntityType.Arc: + if (subEntity is CDB.AlignmentSubEntityArc arc) + { + curves.Add(_alignmentArcConverter.Convert(arc)); + } + break; + case CDB.AlignmentSubEntityType.Line: + if (subEntity is CDB.AlignmentSubEntityLine line) + { + curves.Add(_alignmentLineConverter.Convert(line)); + } + break; + case CDB.AlignmentSubEntityType.Spiral: + if (subEntity is CDB.AlignmentSubEntitySpiral spiral) + { + curves.Add(_alignmentSpiralConverter.Convert((spiral, alignment))); + } + break; + } + } + } + + return curves; + } } diff --git a/Converters/Civil3d/Speckle.Converters.Civil3dShared/Helpers/CorridorHandler.cs b/Converters/Civil3d/Speckle.Converters.Civil3dShared/Helpers/CorridorHandler.cs new file mode 100644 index 000000000..477bca041 --- /dev/null +++ b/Converters/Civil3d/Speckle.Converters.Civil3dShared/Helpers/CorridorHandler.cs @@ -0,0 +1,329 @@ +using Speckle.Converters.Civil3dShared.Extensions; +using Speckle.Converters.Common; +using Speckle.Converters.Common.Objects; +using Speckle.Sdk; +using Speckle.Sdk.Models; + +namespace Speckle.Converters.Civil3dShared.Helpers; + +public sealed class CorridorHandler +{ + /// + /// Keeps track of all corridor solids by their hierarchy of (corridor, baseline, region, applied assembly, applied subassembly) in the current send operation. + /// This should be added to the display value of the corridor applied subassemblies after they are processed + /// Handles should be used instead of Handle.Value (as is typically used for speckle app ids) since the exported solid property sets only stores the handle + /// + public Dictionary<(string, string, string, string, string), List> CorridorSolidsCache { get; } = new(); + + // these ints are used to retrieve the correct values from the exported corridor solids property sets to cache them + // they were determined via trial and error +#pragma warning disable CA1805 // Initialized explicitly to 0 + private readonly int _corridorHandleIndex = 0; +#pragma warning restore CA1805 // Initialized explicitly to 0 + private readonly int _baselineGuidIndex = 6; + private readonly int _regionGuidIndex = 7; + private readonly int _assemblyHandleIndex = 3; + private readonly int _subassemblyHandleIndex = 4; + + private readonly ITypedConverter _solidConverter; + private readonly ITypedConverter _bodyConverter; + private readonly IConverterSettingsStore _settingsStore; + + public CorridorHandler( + ITypedConverter solidConverter, + ITypedConverter bodyConverter, + IConverterSettingsStore settingsStore + ) + { + _solidConverter = solidConverter; + _bodyConverter = bodyConverter; + _settingsStore = settingsStore; + } + + // Ok, this is going to be very complicated. + // We are building a nested `Base.elements` of corridor subelements in this hierarchy: corridor -> baselines -> baseline regions -> applied assemblies -> applied subassemblies + // This is because none of these entities inherit from CDB.Entity, and we need to match the corridor solids with the corresponding applied subassembly. + public List GetCorridorChildren(CDB.Corridor corridor) + { + // first extract all corridor solids. + // this needs to be done before traversing children, so we can match the solid mesh to the appropriate subassembly + HandleCorridorSolids(corridor); + + // track children hierarchy ids: + string corridorHandle = corridor.Handle.ToString(); + + // process baselines + List baselines = new(corridor.Baselines.Count); + foreach (CDB.Baseline baseline in corridor.Baselines) + { + string baselineGuid = baseline.baselineGUID.ToString(); + + Base convertedBaseline = + new() + { + ["type"] = baseline.GetType().ToString().Split('.').Last(), + ["name"] = baseline.Name, + ["startStation"] = baseline.StartStation, + ["endStation"] = baseline.EndStation, + ["units"] = _settingsStore.Current.SpeckleUnits, + ["applicationId"] = baselineGuid, + }; + + // get profile and alignment if nonfeaturelinebased + // for featureline based corridors, accessing AlignmentId and ProfileId will return NULL + // and throw an exception ""This operation on feature line based baseline is invalid". + if (baseline.IsFeatureLineBased()) + { + convertedBaseline["featureLineId"] = baseline.FeatureLineId.GetSpeckleApplicationId(); + } + else + { + convertedBaseline["alignmentId"] = baseline.AlignmentId.GetSpeckleApplicationId(); + convertedBaseline["profileId"] = baseline.ProfileId.GetSpeckleApplicationId(); + } + + // get the baseline regions + List regions = new(); + foreach (CDB.BaselineRegion region in baseline.BaselineRegions) + { + string regionGuid = region.RegionGUID.ToString(); + + Base convertedRegion = + new() + { + ["type"] = region.GetType().ToString().Split('.').Last(), + ["name"] = region.Name, + ["startStation"] = region.StartStation, + ["endStation"] = region.EndStation, + ["assemblyId"] = region.AssemblyId.GetSpeckleApplicationId(), + ["units"] = _settingsStore.Current.SpeckleUnits, + ["applicationId"] = regionGuid, + }; + + // get the region applied assemblies + List appliedAssemblies = new(); + double[] sortedStations = region.SortedStations(); + for (int i = 0; i < sortedStations.Length; i++) + { + double station = sortedStations[i]; + + CDB.AppliedAssembly appliedAssembly = region.AppliedAssemblies[i]; + string assemblyHandle = appliedAssembly.AssemblyId.Handle.ToString(); + Base convertedAppliedAssembly = + new() + { + ["type"] = appliedAssembly.GetType().ToString().Split('.').Last(), + ["assemblyId"] = appliedAssembly.AssemblyId.GetSpeckleApplicationId(), + ["station"] = station, + ["units"] = _settingsStore.Current.SpeckleUnits + }; + + try + { + convertedAppliedAssembly["adjustedElevation"] = appliedAssembly.AdjustedElevation; + } + catch (ArgumentException e) when (!e.IsFatal()) + { + // Do nothing. Leave the value as null. Not sure why accessing adjusted elevation sometimes throws. + } + + // get the applied assembly's applied subassemblies + List appliedSubassemblies = new(appliedAssembly.GetAppliedSubassemblies().Count); + + foreach (CDB.AppliedSubassembly appliedSubassembly in appliedAssembly.GetAppliedSubassemblies()) + { + string subassemblyHandle = appliedSubassembly.SubassemblyId.Handle.ToString(); + + Base convertedAppliedSubassembly = + new() + { + ["type"] = appliedSubassembly.GetType().ToString().Split('.').Last(), + ["subassemblyId"] = appliedSubassembly.SubassemblyId.GetSpeckleApplicationId(), + ["units"] = _settingsStore.Current.SpeckleUnits + }; + + // try to get the display value mesh + (string, string, string, string, string) corridorSolidsKey = ( + corridorHandle, + baselineGuid, + regionGuid, + assemblyHandle, + subassemblyHandle + ); + + if (CorridorSolidsCache.TryGetValue(corridorSolidsKey, out List display)) + { + convertedAppliedSubassembly["displayValue"] = display; + } + + // get the applied subassembly's calculated stuff + AddSubassemblyCalculatedProperties(convertedAppliedSubassembly, appliedSubassembly); + + appliedSubassemblies.Add(convertedAppliedSubassembly); + } + + convertedAppliedAssembly["elements"] = appliedSubassemblies; + appliedAssemblies.Add(convertedAppliedAssembly); + } + + convertedRegion["elements"] = appliedAssemblies; + regions.Add(convertedRegion); + } + + convertedBaseline["elements"] = regions; + baselines.Add(convertedBaseline); + } + + return baselines; + } + + // Adds the calculated shapes > calculated links > calculated points as dicts to the applied subassembly + private void AddSubassemblyCalculatedProperties( + Base speckleAppliedSubassembly, + CDB.AppliedSubassembly appliedSubassembly + ) + { + Dictionary calculatedShapes = new(); + int shapeCount = 0; + foreach (CDB.CalculatedShape shape in appliedSubassembly.Shapes) + { + Dictionary calculatedLinks = new(); + int linkCount = 0; + foreach (CDB.CalculatedLink link in shape.CalculatedLinks) + { + Dictionary calculatedPoints = new(); + int pointCount = 0; + foreach (CDB.CalculatedPoint point in link.CalculatedPoints) + { + calculatedPoints[pointCount.ToString()] = new Dictionary() + { + ["xyz"] = point.XYZ.ToArray(), + ["corridorCodes"] = point.CorridorCodes.ToList(), + ["stationOffsetElevationToBaseline"] = point.StationOffsetElevationToBaseline.ToArray(), + }; + pointCount++; + } + + calculatedLinks[linkCount.ToString()] = new Dictionary() + { + ["corridorCodes"] = link.CorridorCodes.ToList(), + ["calculatedPoints"] = calculatedPoints + }; + + linkCount++; + } + + calculatedShapes[shapeCount.ToString()] = new Dictionary() + { + ["corridorCodes"] = shape.CorridorCodes.ToList(), + ["area"] = shape.Area, + ["calculatedLinks"] = calculatedLinks + }; + } + + speckleAppliedSubassembly["calculatedShapes"] = calculatedShapes; + } + + /// + /// Extracts the solids from a corridor and stores in according to property sets on the solid. + /// + /// + /// + private void HandleCorridorSolids(CDB.Corridor corridor) + { + CDB.ExportCorridorSolidsParams param = new(); + + using (var tr = _settingsStore.Current.Document.Database.TransactionManager.StartTransaction()) + { + foreach (ADB.ObjectId solidId in corridor.ExportSolids(param, corridor.Database)) + { + SOG.Mesh? mesh = null; + var solid = tr.GetObject(solidId, ADB.OpenMode.ForRead); + if (solid is ADB.Solid3d solid3d) + { + // get the solid mesh + mesh = _solidConverter.Convert(solid3d); + } + else if (solid is ADB.Body body) + { + mesh = _bodyConverter.Convert(body); + } + + if (mesh is null) + { + continue; + } + + // get the (corridor handle, baseline guid, region guid, assembly handle, subassembly handle) of the solid property sets + (string corridor, string baseline, string region, string assembly, string subassembly)? solidKey = + GetCorridorSolidIdFromPropertySet(solid, tr); + + if (solidKey is (string, string, string, string, string) validSolidKey) + { + if (CorridorSolidsCache.TryGetValue(validSolidKey, out List display)) + { + display.Add(mesh); + } + else + { + CorridorSolidsCache[validSolidKey] = new() { mesh }; + } + } + } + + tr.Commit(); + } + } + + private (string, string, string, string, string)? GetCorridorSolidIdFromPropertySet( + ADB.DBObject obj, + ADB.Transaction tr + ) + { + ADB.ObjectIdCollection? propertySetIds; + + try + { + propertySetIds = AAECPDB.PropertyDataServices.GetPropertySets(obj); + } + catch (Exception e) when (!e.IsFatal()) + { + return null; + } + + if (propertySetIds is null || propertySetIds.Count == 0) + { + return null; + } + + foreach (ADB.ObjectId id in propertySetIds) + { + AAECPDB.PropertySet propertySet = (AAECPDB.PropertySet)tr.GetObject(id, ADB.OpenMode.ForRead); + + if (propertySet.PropertySetDefinitionName == "Corridor Identity") + { + AAECPDB.PropertySetData corridorData = propertySet.PropertySetData[_corridorHandleIndex]; + AAECPDB.PropertySetData baselineData = propertySet.PropertySetData[_baselineGuidIndex]; + AAECPDB.PropertySetData regionData = propertySet.PropertySetData[_regionGuidIndex]; + AAECPDB.PropertySetData assemblyData = propertySet.PropertySetData[_assemblyHandleIndex]; + AAECPDB.PropertySetData subassemblyData = propertySet.PropertySetData[_subassemblyHandleIndex]; + + return + corridorData.GetData() is not string corridorHandle + || baselineData.GetData() is not string baselineGuid // guid is uppercase and enclosed in {} which need to be removed + || regionData.GetData() is not string regionGuid // guid is uppercase and enclosed in {} which need to be removed + || assemblyData.GetData() is not string assemblyHandle + || subassemblyData.GetData() is not string subassemblyHandle + ? null + : ( + corridorHandle, + baselineGuid[1..^1].ToLower(), + regionGuid[1..^1].ToLower(), + assemblyHandle, + subassemblyHandle + ); + } + } + return null; + } +} diff --git a/Converters/Civil3d/Speckle.Converters.Civil3dShared/ServiceRegistration.cs b/Converters/Civil3d/Speckle.Converters.Civil3dShared/ServiceRegistration.cs index e65e403f1..38f277c47 100644 --- a/Converters/Civil3d/Speckle.Converters.Civil3dShared/ServiceRegistration.cs +++ b/Converters/Civil3d/Speckle.Converters.Civil3dShared/ServiceRegistration.cs @@ -44,5 +44,6 @@ public static void AddCivil3dConverters(this IServiceCollection serviceCollectio serviceCollection.AddScoped(); serviceCollection.AddScoped(); serviceCollection.AddScoped(); + serviceCollection.AddScoped(); } } diff --git a/Converters/Civil3d/Speckle.Converters.Civil3dShared/Speckle.Converters.Civil3dShared.projitems b/Converters/Civil3d/Speckle.Converters.Civil3dShared/Speckle.Converters.Civil3dShared.projitems index eb1c74523..62c0f54d4 100644 --- a/Converters/Civil3d/Speckle.Converters.Civil3dShared/Speckle.Converters.Civil3dShared.projitems +++ b/Converters/Civil3d/Speckle.Converters.Civil3dShared/Speckle.Converters.Civil3dShared.projitems @@ -15,6 +15,7 @@ + @@ -26,7 +27,10 @@ + + + diff --git a/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/BuiltElements/CivilEntityToSpeckleTopLevelConverter.cs b/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/BuiltElements/CivilEntityToSpeckleTopLevelConverter.cs index f2433fa62..25243105c 100644 --- a/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/BuiltElements/CivilEntityToSpeckleTopLevelConverter.cs +++ b/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/BuiltElements/CivilEntityToSpeckleTopLevelConverter.cs @@ -1,3 +1,4 @@ +using Speckle.Converters.Civil3dShared.Extensions; using Speckle.Converters.Civil3dShared.Helpers; using Speckle.Converters.Common; using Speckle.Converters.Common.Objects; @@ -13,31 +14,38 @@ public class CivilEntityToSpeckleTopLevelConverter : IToSpeckleTopLevelConverter private readonly DisplayValueExtractor _displayValueExtractor; private readonly BaseCurveExtractor _baseCurveExtractor; private readonly ClassPropertiesExtractor _classPropertiesExtractor; + private readonly CorridorHandler _corridorHandler; public CivilEntityToSpeckleTopLevelConverter( IConverterSettingsStore settingsStore, DisplayValueExtractor displayValueExtractor, BaseCurveExtractor baseCurveExtractor, - ClassPropertiesExtractor classPropertiesExtractor + ClassPropertiesExtractor classPropertiesExtractor, + CorridorHandler corridorHandler ) { _settingsStore = settingsStore; _displayValueExtractor = displayValueExtractor; _baseCurveExtractor = baseCurveExtractor; _classPropertiesExtractor = classPropertiesExtractor; + _corridorHandler = corridorHandler; } public Base Convert(object target) => Convert((CDB.Entity)target); public Base Convert(CDB.Entity target) { - Base civilObject = new(); - civilObject["type"] = target.GetType().ToString().Split('.').Last(); - civilObject["name"] = target.Name; - civilObject["units"] = _settingsStore.Current.SpeckleUnits; + Base civilObject = + new() + { + ["type"] = target.GetType().ToString().Split('.').Last(), + ["name"] = target.Name, + ["units"] = _settingsStore.Current.SpeckleUnits, + applicationId = target.GetSpeckleApplicationId() + }; // get basecurve - List? baseCurves = _baseCurveExtractor.GetBaseCurve(target); + List? baseCurves = _baseCurveExtractor.GetBaseCurves(target); if (baseCurves is not null) { civilObject["baseCurves"] = baseCurves; @@ -60,6 +68,40 @@ public Base Convert(CDB.Entity target) } } + // determine if this entity has any children elements that need to be converted. + // this is a bespoke method by class type. + List? children = null; + switch (target) + { + case CDB.Alignment alignment: + children = GetAlignmentChildren(alignment); + break; + case CDB.Corridor corridor: + children = _corridorHandler.GetCorridorChildren(corridor); + break; + } + if (children is not null) + { + civilObject["@elements"] = children; + } + return civilObject; } + + private List? GetAlignmentChildren(CDB.Alignment alignment) + { + List profiles = new(); + using (var tr = _settingsStore.Current.Document.Database.TransactionManager.StartTransaction()) + { + foreach (ADB.ObjectId profileId in alignment.GetProfileIds()) + { + var profile = (CDB.Profile)tr.GetObject(profileId, ADB.OpenMode.ForRead); + profiles.Add(Convert(profile)); + } + + tr.Commit(); + } + + return profiles.Count > 0 ? profiles : null; + } } diff --git a/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Properties/ClassPropertiesExtractor.cs b/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Properties/ClassPropertiesExtractor.cs index f7ce2f20f..bfe72bccd 100644 --- a/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Properties/ClassPropertiesExtractor.cs +++ b/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Properties/ClassPropertiesExtractor.cs @@ -1,4 +1,3 @@ -using Autodesk.Civil.DatabaseServices; using Speckle.Converters.Civil3dShared.Extensions; using Speckle.Converters.Civil3dShared.Helpers; using Speckle.Converters.Common; @@ -47,11 +46,19 @@ PipeNetworkHandler pipeNetworkHandler return ExtractCatchmentProperties(catchment); case CDB.Site site: return ExtractSiteProperties(site); + + // pipe networks case CDB.Pipe pipe: return ExtractPipeProperties(pipe); case CDB.Structure structure: return ExtractStructureProperties(structure); + // alignments + case CDB.Alignment alignment: + return ExtractAlignmentProperties(alignment); + case CDB.Profile profile: + return ExtractProfileProperties(profile); + default: return null; } @@ -76,11 +83,43 @@ PipeNetworkHandler pipeNetworkHandler return pointProperties; } + private Dictionary ExtractProfileProperties(CDB.Profile profile) + { + return new() + { + ["offset"] = profile.Offset, + ["startingStation"] = profile.StartingStation, + ["endingStation"] = profile.EndingStation, + ["profileType"] = profile.ProfileType.ToString(), + ["elevationMin"] = profile.ElevationMin, + ["elevationMax"] = profile.ElevationMax + }; + } + + private Dictionary ExtractAlignmentProperties(CDB.Alignment alignment) + { + Dictionary alignmentProperties = + new() + { + ["startingStation"] = alignment.StartingStation, + ["endingStation"] = alignment.EndingStation, + ["alignmentType"] = alignment.AlignmentType.ToString() + }; + + if (!alignment.IsSiteless) + { + alignmentProperties["siteId"] = alignment.SiteId.GetSpeckleApplicationId(); + } + + return alignmentProperties; + } + private Dictionary ExtractPipeProperties(CDB.Pipe pipe) { Dictionary pipeProperties = new() { + ["bearing"] = pipe.Bearing, ["innerDiameterOrWidth"] = pipe.InnerDiameterOrWidth, ["innerHeight"] = pipe.InnerHeight, ["slope"] = pipe.Slope, @@ -123,7 +162,7 @@ PipeNetworkHandler pipeNetworkHandler ["innerDiameterOrWidth"] = structure.InnerDiameterOrWidth }; - if (structure.BoundingShape == BoundingShapeType.Box) + if (structure.BoundingShape == CDB.BoundingShapeType.Box) { structureProperties["innerLength"] = structure.InnerLength; structureProperties["length"] = structure.Length; diff --git a/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Properties/GeneralPropertiesExtractor.cs b/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Properties/GeneralPropertiesExtractor.cs index fd8ed7a3e..2dcbc4b4e 100644 --- a/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Properties/GeneralPropertiesExtractor.cs +++ b/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Properties/GeneralPropertiesExtractor.cs @@ -1,4 +1,5 @@ using System.Reflection; +using Speckle.Converters.Civil3dShared.Extensions; namespace Speckle.Converters.Civil3dShared.ToSpeckle; @@ -16,13 +17,222 @@ public GeneralPropertiesExtractor() { } /// public Dictionary? GetGeneralProperties(CDB.Entity entity) { - Dictionary? generalPropertiesDict = null; switch (entity) { // surface -> properties -> statistics -> general, extended, and tin/grid properties case CDB.Surface surface: - generalPropertiesDict = ExtractSurfaceProperties(surface); - break; + return ExtractSurfaceProperties(surface); + + // alignment -> properties -> station control -> station equations, station information, reference point + // alignment -> properties -> offset parameters -> parent alignment + case CDB.Alignment alignment: + return ExtractAlignmentProperties(alignment); + + // corridor -> properties -> codes, featurelines, surfaces + case CDB.Corridor corridor: + return ExtractCorridorProperties(corridor); + + //case CDB.Assembly assembly: + //return ExtractAssemblyProperties(assembly); + + //case CDB.Subassembly subassembly: + //return ExtractSubassemblyProperties(subassembly); + + default: + return null; + } + } + + //private Dictionary ExtractSubassemblyProperties(CDB.Subassembly subassembly) { } + + //private Dictionary ExtractAssemblyProperties(CDB.Assembly assembly) { } + + private void ProcessCorridorFeaturelinePoints( + CDB.CorridorFeatureLine featureline, + Dictionary> featureLinesDict + ) + { + if (featureLinesDict.TryGetValue(featureline.CodeName, out Dictionary value)) + { + Dictionary pointsDict = new(featureline.FeatureLinePoints.Count); + int pointCount = 0; + foreach (CDB.FeatureLinePoint point in featureline.FeatureLinePoints) + { + pointsDict[pointCount.ToString()] = new Dictionary() + { + ["station"] = point.Station, + ["xyz"] = point.XYZ.ToArray(), + ["isBreak"] = point.IsBreak, + ["offset"] = point.Offset + }; + + pointCount++; + } + + value["featureLinePoints"] = pointsDict; + } + } + + private Dictionary ExtractCorridorProperties(CDB.Corridor corridor) + { + Dictionary generalPropertiesDict = new(); + + // get codes props + Dictionary codesDict = + new() + { + ["link"] = corridor.GetLinkCodes(), + ["point"] = corridor.GetPointCodes(), + ["shape"] = corridor.GetShapeCodes() + }; + generalPropertiesDict["codes"] = codesDict; + + // get feature lines props + // this is pretty complicated: need to extract featureline points as dicts, but can only do this by iterating through baselines. Need to match the iterated featurelines with the featureline code info. + Dictionary> featureLinesDict = new(); + // first build dict from the code info + foreach (CDB.FeatureLineCodeInfo featureLineCode in corridor.FeatureLineCodeInfos) + { + featureLinesDict[featureLineCode.CodeName] = new Dictionary() + { + ["codeName"] = featureLineCode.CodeName, + ["isConnected"] = featureLineCode.IsConnected, + ["payItems"] = featureLineCode.PayItems + }; + } + // then iterate through baseline featurelines to populate point info + foreach (CDB.Baseline baseline in corridor.Baselines) + { + // main featurelines + foreach ( + CDB.FeatureLineCollection mainFeaturelineCollection in baseline + .MainBaselineFeatureLines + .FeatureLineCollectionMap + ) + { + foreach (CDB.CorridorFeatureLine featureline in mainFeaturelineCollection) + { + ProcessCorridorFeaturelinePoints(featureline, featureLinesDict); + } + } + + // offset featurelines + foreach (CDB.BaselineFeatureLines offsetFeaturelineCollection in baseline.OffsetBaselineFeatureLinesCol) + { + foreach ( + CDB.FeatureLineCollection featurelineCollection in offsetFeaturelineCollection.FeatureLineCollectionMap + ) + { + foreach (CDB.CorridorFeatureLine featureline in featurelineCollection) + { + ProcessCorridorFeaturelinePoints(featureline, featureLinesDict); + } + } + } + } + if (featureLinesDict.Count > 0) + { + generalPropertiesDict["Feature Lines"] = featureLinesDict; + } + + // get surfaces props + Dictionary surfacesDict = new(); + foreach (CDB.CorridorSurface surface in corridor.CorridorSurfaces) + { + surfacesDict[surface.Name] = new Dictionary() + { + ["name"] = surface.Name, + ["surfaceId"] = surface.SurfaceId.GetSpeckleApplicationId(), + ["description"] = surface.Description, + ["overhangCorrection"] = surface.OverhangCorrection.ToString() + }; + } + if (surfacesDict.Count > 0) + { + generalPropertiesDict["Surfaces"] = surfacesDict; + } + + return generalPropertiesDict; + } + + private Dictionary ExtractAlignmentProperties(CDB.Alignment alignment) + { + Dictionary generalPropertiesDict = new(); + + // get station control props + Dictionary stationControlDict = new(); + + Dictionary stationEquationsDict = new(); + int equationCount = 0; + foreach (var stationEquation in alignment.StationEquations) + { + stationEquationsDict[equationCount.ToString()] = new Dictionary() + { + ["rawStationBack"] = stationEquation.RawStationBack, + ["stationBack"] = stationEquation.StationBack, + ["stationAhead"] = stationEquation.StationAhead, + ["equationType"] = stationEquation.EquationType.ToString() + }; + equationCount++; + } + if (stationEquationsDict.Count > 0) + { + stationControlDict["Station Equations"] = stationEquationsDict; + } + + Dictionary referencePointDict = + new() + { + ["x"] = alignment.ReferencePoint.X, + ["y"] = alignment.ReferencePoint.Y, + ["station"] = alignment.ReferencePointStation + }; + stationControlDict["Reference Point"] = referencePointDict; + + if (stationControlDict.Count > 0) + { + generalPropertiesDict["Station Control"] = stationControlDict; + } + + // get design criteria props + Dictionary designCriteriaDict = new(); + + Dictionary designSpeedsDict = new(); + int speedsCount = 0; + foreach (CDB.DesignSpeed designSpeed in alignment.DesignSpeeds) + { + designSpeedsDict[speedsCount.ToString()] = new Dictionary() + { + ["number"] = designSpeed.SpeedNumber, + ["station"] = designSpeed.Station, + ["value"] = designSpeed.Value + }; + speedsCount++; + } + designCriteriaDict["Design Speeds"] = designSpeedsDict; + if (designSpeedsDict.Count > 0) + { + designCriteriaDict["Design Speeds"] = designSpeedsDict; + } + + if (designCriteriaDict.Count > 0) + { + generalPropertiesDict["Design Critera"] = designCriteriaDict; + } + + // get offset alignment props + if (alignment.IsOffsetAlignment) + { + var offsetInfo = alignment.OffsetAlignmentInfo; + Dictionary offsetAlignmentDict = + new() + { + ["side"] = offsetInfo.Side.ToString(), + ["parentAlignmentId"] = offsetInfo.ParentAlignmentId.GetSpeckleApplicationId(), + ["nominalOffset"] = offsetInfo.NominalOffset + }; + + generalPropertiesDict["Offset Parameters"] = offsetAlignmentDict; } return generalPropertiesDict; diff --git a/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Properties/PropertySetDefinitionHandler.cs b/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Properties/PropertySetDefinitionHandler.cs index 73f210461..ef9a50a95 100644 --- a/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Properties/PropertySetDefinitionHandler.cs +++ b/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Properties/PropertySetDefinitionHandler.cs @@ -19,9 +19,7 @@ public class PropertySetDefinitionHandler /// /// The property set definition. Assumes this is opened for Read already. /// - public (Dictionary propertyDefinitionNames, string name) HandleDefinition( - AAECPDB.PropertySetDefinition setDefinition - ) + public Dictionary HandleDefinition(AAECPDB.PropertySetDefinition setDefinition) { Dictionary propertyDefinitionsDict = new(); // this is used to store on the property set definition Dictionary propertyDefinitionNames = new(); // this is used to pass to the instance for property value retrieval @@ -53,7 +51,7 @@ AAECPDB.PropertySetDefinition setDefinition if (Definitions.ContainsKey(name)) { - return (propertyDefinitionNames, name); + return propertyDefinitionNames; } Definitions[name] = new Dictionary() @@ -62,6 +60,6 @@ AAECPDB.PropertySetDefinition setDefinition ["propertyDefinitions"] = propertyDefinitionsDict }; - return (propertyDefinitionNames, name); + return propertyDefinitionNames; } } diff --git a/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Properties/PropertySetExtractor.cs b/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Properties/PropertySetExtractor.cs index 6c56bae7b..6cb52a8dd 100644 --- a/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Properties/PropertySetExtractor.cs +++ b/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Properties/PropertySetExtractor.cs @@ -73,21 +73,22 @@ ILogger logger { // var isNullOrEmpty = value == null || (value is string s && string.IsNullOrEmpty(s)); // POC: should add same check as in revit for sending null or empty values - var setDefinition = (AAECPDB.PropertySetDefinition) tr.GetObject(propertySet.PropertySetDefinition, ADB.OpenMode.ForRead); + Dictionary? propertyDefinitionNames = null; + string name = setDefinition.Name; - (Dictionary propertyDefinitionNames, string name) = _propertySetDefinitionHandler.HandleDefinition( - setDefinition - ); + propertyDefinitionNames = _propertySetDefinitionHandler.HandleDefinition(setDefinition); // get all property values in the propertyset Dictionary properties = new(); foreach (AAECPDB.PropertySetData data in propertySet.PropertySetData) { - string dataName = propertyDefinitionNames.TryGetValue(data.Id, out string propertyDefinitionName) - ? propertyDefinitionName - : data.FieldBucketId; + string dataName = + propertyDefinitionNames is not null + && propertyDefinitionNames.TryGetValue(data.Id, out string propertyDefinitionName) + ? propertyDefinitionName + : data.FieldBucketId; var value = GetValue(data); diff --git a/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Raw/AlignmentSubentityArcToSpeckleRawConverter.cs b/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Raw/AlignmentSubentityArcToSpeckleRawConverter.cs new file mode 100644 index 000000000..5c6d9c563 --- /dev/null +++ b/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Raw/AlignmentSubentityArcToSpeckleRawConverter.cs @@ -0,0 +1,98 @@ +using Speckle.Converters.Common; +using Speckle.Converters.Common.Objects; +using Speckle.Sdk; + +namespace Speckle.Converters.Civil3dShared.ToSpeckle.Raw; + +public class AlignmentSubentityArcToSpeckleRawConverter : ITypedConverter +{ + private readonly ITypedConverter _planeConverter; + private readonly IConverterSettingsStore _settingsStore; + + public AlignmentSubentityArcToSpeckleRawConverter( + ITypedConverter planeConverter, + IConverterSettingsStore settingsStore + ) + { + _planeConverter = planeConverter; + _settingsStore = settingsStore; + } + + public SOG.Arc Convert(object target) => Convert((CDB.AlignmentSubEntityArc)target); + + public SOG.Arc Convert(CDB.AlignmentSubEntityArc target) + { + string units = _settingsStore.Current.SpeckleUnits; + + // calculate midpoint of chord as between start and end point + AG.Point2d chordMid = + new((target.StartPoint.X + target.EndPoint.X) / 2, (target.StartPoint.Y + target.EndPoint.Y) / 2); + + // calculate sagitta as radius minus distance between arc center and chord midpoint + double sagitta = target.Radius - target.CenterPoint.GetDistanceTo(chordMid); + + // get unit vector from arc center to chord mid + AG.Vector2d midVector = target.CenterPoint.GetVectorTo(chordMid); + AG.Vector2d unitMidVector = midVector.DivideBy(midVector.Length); + + // get midpoint of arc by moving chord mid point the length of the sagitta along mid vector + // if greater than 180 >, move in other direction of distance radius + radius - sagitta + // in the case of an exactly perfect half circle arc...🤷‍♀️ + AG.Point2d midPoint = chordMid.Add(unitMidVector.MultiplyBy(sagitta)); + try + { + if (target.GreaterThan180) // this can throw : The property gets an invalid value according to the entity's constraint type. + { + midPoint = chordMid.Add(unitMidVector.Negate().MultiplyBy(2 * target.Radius - sagitta)); + } + } + catch (Exception e) when (!e.IsFatal()) { } // continue with original midpoint if GreaterThan180 doesn't apply to this arc + + // find arc plane (normal is in clockwise dir) + var center3 = new AG.Point3d(target.CenterPoint.X, target.CenterPoint.Y, 0); + AG.Plane plane = target.Clockwise + ? new AG.Plane(center3, AG.Vector3d.ZAxis.MultiplyBy(-1)) + : new AG.Plane(center3, AG.Vector3d.ZAxis); + + // create arc + SOG.Arc arc = + new() + { + startPoint = new() + { + x = target.StartPoint.X, + y = target.StartPoint.Y, + z = 0, + units = units + }, + endPoint = new() + { + x = target.EndPoint.X, + y = target.EndPoint.Y, + z = 0, + units = units + }, + midPoint = new() + { + x = midPoint.X, + y = midPoint.Y, + z = 0, + units = units + }, + plane = _planeConverter.Convert(plane), + radius = target.Radius, + length = target.Length, + + // additional alignment subentity props + ["startStation"] = target.StartStation, + ["endStation"] = target.EndStation, + ["startDirection"] = target.StartDirection, + ["endDirection"] = target.EndDirection, + ["delta"] = target.Delta, + ["deflectedAngle"] = target.DeflectedAngle, + ["minumumRadius"] = target.MinimumRadius + }; + + return arc; + } +} diff --git a/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Raw/AlignmentSubentityLineToSpeckleRawConverter.cs b/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Raw/AlignmentSubentityLineToSpeckleRawConverter.cs new file mode 100644 index 000000000..f286129ea --- /dev/null +++ b/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Raw/AlignmentSubentityLineToSpeckleRawConverter.cs @@ -0,0 +1,49 @@ +using Speckle.Converters.Common; +using Speckle.Converters.Common.Objects; + +namespace Speckle.Converters.Civil3dShared.ToSpeckle.Raw; + +public class AlignmentSubentityLineToSpeckleRawConverter : ITypedConverter +{ + private readonly IConverterSettingsStore _settingsStore; + + public AlignmentSubentityLineToSpeckleRawConverter(IConverterSettingsStore settingsStore) + { + _settingsStore = settingsStore; + } + + public SOG.Line Convert(object target) => Convert((CDB.AlignmentSubEntityLine)target); + + public SOG.Line Convert(CDB.AlignmentSubEntityLine target) + { + SOG.Point start = + new() + { + x = target.StartPoint.X, + y = target.StartPoint.Y, + z = 0, + units = _settingsStore.Current.SpeckleUnits + }; + + SOG.Point end = + new() + { + x = target.EndPoint.X, + y = target.EndPoint.Y, + z = 0, + units = _settingsStore.Current.SpeckleUnits + }; + + return new() + { + start = start, + end = end, + units = _settingsStore.Current.SpeckleUnits, + + // additional alignment props + ["direction"] = target.Direction, + ["startStation"] = target.StartStation, + ["endStation"] = target.EndStation + }; + } +} diff --git a/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Raw/AlignmentSubentitySpiralToSpeckleRawConverter.cs b/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Raw/AlignmentSubentitySpiralToSpeckleRawConverter.cs new file mode 100644 index 000000000..939548081 --- /dev/null +++ b/Converters/Civil3d/Speckle.Converters.Civil3dShared/ToSpeckle/Raw/AlignmentSubentitySpiralToSpeckleRawConverter.cs @@ -0,0 +1,75 @@ +using Speckle.Converters.Common; +using Speckle.Converters.Common.Objects; + +namespace Speckle.Converters.Civil3dShared.ToSpeckle.Raw; + +public class AlignmentSubentitySpiralToSpeckleRawConverter + : ITypedConverter<(CDB.AlignmentSubEntitySpiral, CDB.Alignment), SOG.Polyline> +{ + private readonly ITypedConverter _planeConverter; + private readonly IConverterSettingsStore _settingsStore; + + public AlignmentSubentitySpiralToSpeckleRawConverter( + ITypedConverter planeConverter, + IConverterSettingsStore settingsStore + ) + { + _planeConverter = planeConverter; + _settingsStore = settingsStore; + } + + public SOG.Polyline Convert(object target) => Convert(((CDB.AlignmentSubEntitySpiral, CDB.Alignment))target); + + public SOG.Polyline Convert((CDB.AlignmentSubEntitySpiral, CDB.Alignment) target) + { + CDB.AlignmentSubEntitySpiral spiral = target.Item1; + CDB.Alignment alignment = target.Item2; + + string units = _settingsStore.Current.SpeckleUnits; + + // create polyline, default tessellation length is 1 + var tessellation = 1; + int spiralSegmentCount = System.Convert.ToInt32(Math.Ceiling(spiral.Length / tessellation)); + spiralSegmentCount = (spiralSegmentCount < 10) ? 10 : spiralSegmentCount; + double spiralSegmentLength = spiral.Length / spiralSegmentCount; + List polylineValue = new(spiralSegmentCount * 3) { spiral.StartPoint.X, spiral.StartPoint.Y, 0 }; + for (int i = 1; i < spiralSegmentCount; i++) + { + double x = 0; + double y = 0; + double z = 0; + alignment.PointLocation(spiral.StartStation + i * spiralSegmentLength, 0, 0.001, ref x, ref y, ref z); + polylineValue.Add(x); + polylineValue.Add(y); + polylineValue.Add(z); + } + polylineValue.Add(spiral.EndPoint.X); + polylineValue.Add(spiral.EndPoint.Y); + polylineValue.Add(0); + + SOG.Polyline polyline = + new() + { + value = polylineValue, + units = units, + closed = spiral.StartPoint == spiral.EndPoint, + // add alignment spiral props + length = spiral.Length, + ["delta"] = spiral.Delta, + ["direction"] = spiral.Direction.ToString(), + ["spiralDefinition"] = spiral.SpiralDefinition.ToString(), + ["totalX"] = spiral.TotalX, + ["totalY"] = spiral.TotalY, + ["startStation"] = spiral.StartStation, + ["endStation"] = spiral.EndStation, + ["startDirection"] = spiral.StartDirection, + ["endDirection"] = spiral.EndDirection, + ["a"] = spiral.A, + ["k"] = spiral.K, + ["p"] = spiral.P, + ["minimumTransitionLength"] = spiral.MinimumTransitionLength + }; + + return polyline; + } +}