Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9e16a6cd85 | |||
| 95070bfc65 | |||
| 7c5ddf1553 | |||
| e746548c81 | |||
| e52ed561e2 | |||
| 361195f1f1 | |||
| dc5467de88 | |||
| 48441a3630 | |||
| c08a3e6129 | |||
| 43bbe4efd0 | |||
| 1b94fd9901 | |||
| cfed10e10f | |||
| 723c3dc25d | |||
| a5ccad03c2 | |||
| c06067bfc4 | |||
| b22c92919d | |||
| 96ba945f51 | |||
| 90ecf20582 | |||
| a1d9c0093d | |||
| 26c0558a14 | |||
| 025a99da22 |
@@ -50,7 +50,7 @@ public class LevelUnpacker
|
||||
{
|
||||
name = level.Name,
|
||||
displayValue = [],
|
||||
properties = _propertiesExtractor.GetProperties(level)
|
||||
properties = new Dictionary<string, object?>() { }
|
||||
};
|
||||
var unitSettings = _converterSettings.Current.Document.GetUnits();
|
||||
var lengthUnitType = unitSettings.GetFormatOptions(Autodesk.Revit.DB.SpecTypeId.Length).GetUnitTypeId();
|
||||
|
||||
+19
-6
@@ -110,7 +110,8 @@ public sealed class RevitHostObjectBuilder(
|
||||
// TODO: TransformTo and material baking needs to be fixed in Revit!!
|
||||
|
||||
// create a mapping from original to modified IDs <- so that we can actually map ids in the proxies to the objects
|
||||
Dictionary<string, string> originalToModifiedIds = new();
|
||||
// as part of CNX-2677, we have a one-to-many problem. many instances share the same reference, so we use a list
|
||||
Dictionary<string, List<string>> originalToModifiedIds = new();
|
||||
|
||||
// modify application IDs BEFORE material baking
|
||||
foreach (LocalToGlobalMap localToGlobalMap in localToGlobalMaps)
|
||||
@@ -139,7 +140,13 @@ public sealed class RevitHostObjectBuilder(
|
||||
string modifiedAppId = $"{originalAppId}_{Guid.NewGuid().ToString("N")[..8]}";
|
||||
if (originalAppId != null)
|
||||
{
|
||||
originalToModifiedIds[originalAppId] = modifiedAppId;
|
||||
if (!originalToModifiedIds.TryGetValue(originalAppId, out List<string>? modifiedIds))
|
||||
{
|
||||
modifiedIds = new List<string>();
|
||||
originalToModifiedIds[originalAppId] = modifiedIds;
|
||||
}
|
||||
|
||||
modifiedIds.Add(modifiedAppId);
|
||||
}
|
||||
|
||||
localToGlobalMap.AtomicObject.applicationId = modifiedAppId;
|
||||
@@ -152,14 +159,20 @@ public sealed class RevitHostObjectBuilder(
|
||||
{
|
||||
foreach (var proxy in unpackedRoot.RenderMaterialProxies)
|
||||
{
|
||||
var updatedObjects = new List<string>();
|
||||
var objectIdsToUse = new List<string>();
|
||||
foreach (var objectId in proxy.objects)
|
||||
{
|
||||
// Use the modified ID if it exists, otherwise keep the original <- this SUCKS and we need to change
|
||||
string idToUse = originalToModifiedIds.TryGetValue(objectId, out var modifiedId) ? modifiedId : objectId;
|
||||
updatedObjects.Add(idToUse);
|
||||
if (originalToModifiedIds.TryGetValue(objectId, out var modifiedIds))
|
||||
{
|
||||
objectIdsToUse.AddRange(modifiedIds);
|
||||
}
|
||||
else
|
||||
{
|
||||
objectIdsToUse.Add(objectId);
|
||||
}
|
||||
}
|
||||
proxy.objects = updatedObjects;
|
||||
proxy.objects = objectIdsToUse;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+25
-21
@@ -1,7 +1,7 @@
|
||||
using Autodesk.Revit.DB;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.Connectors.Common.Builders;
|
||||
using Speckle.Connectors.Common.Caching;
|
||||
// using Speckle.Connectors.Common.Caching;
|
||||
using Speckle.Connectors.Common.Conversion;
|
||||
using Speckle.Connectors.Common.Extensions;
|
||||
using Speckle.Connectors.Common.Operations;
|
||||
@@ -21,7 +21,7 @@ namespace Speckle.Connectors.Revit.Operations.Send;
|
||||
public class RevitRootObjectBuilder(
|
||||
IRootToSpeckleConverter converter,
|
||||
IConverterSettingsStore<RevitConversionSettings> converterSettings,
|
||||
ISendConversionCache sendConversionCache,
|
||||
//ISendConversionCache sendConversionCache,
|
||||
ElementUnpacker elementUnpacker,
|
||||
LevelUnpacker levelUnpacker,
|
||||
IThreadContext threadContext,
|
||||
@@ -48,6 +48,7 @@ public class RevitRootObjectBuilder(
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
Console.WriteLine(projectId);
|
||||
var doc = converterSettings.Current.Document;
|
||||
|
||||
if (doc.IsFamilyDocument)
|
||||
@@ -130,7 +131,7 @@ public class RevitRootObjectBuilder(
|
||||
}
|
||||
|
||||
var countProgress = 0;
|
||||
var cacheHitCount = 0;
|
||||
// var cacheHitCount = 0;
|
||||
var skippedObjectCount = 0;
|
||||
|
||||
foreach (var atomicObjectByDocumentAndTransform in atomicObjectsByDocumentAndTransform)
|
||||
@@ -183,26 +184,17 @@ public class RevitRootObjectBuilder(
|
||||
// non-transformed elements can safely rely on cache
|
||||
// TODO: Potential here to transform cached objects and NOT reconvert,
|
||||
// TODO: we wont do !hasTransform here, and re-set application id before this
|
||||
if (!hasTransform && sendConversionCache.TryGetValue(projectId, applicationId, out ObjectReference? value))
|
||||
|
||||
if (hasTransform)
|
||||
{
|
||||
converted = value;
|
||||
cacheHitCount++;
|
||||
}
|
||||
// not in cache means we convert
|
||||
else
|
||||
{
|
||||
// if it has a transform we append transform hash to the applicationId to distinguish the elements from other instances
|
||||
if (hasTransform)
|
||||
{
|
||||
string transformHash = linkedModelHandler.GetTransformHash(
|
||||
atomicObjectByDocumentAndTransform.Transform.NotNull()
|
||||
);
|
||||
applicationId = $"{applicationId}_t{transformHash}";
|
||||
}
|
||||
// normal conversions
|
||||
converted = converter.Convert(revitElement);
|
||||
converted.applicationId = applicationId;
|
||||
string transformHash = linkedModelHandler.GetTransformHash(
|
||||
atomicObjectByDocumentAndTransform.Transform.NotNull()
|
||||
);
|
||||
applicationId = $"{applicationId}_t{transformHash}";
|
||||
}
|
||||
// normal conversions
|
||||
converted = converter.Convert(revitElement);
|
||||
converted.applicationId = applicationId;
|
||||
|
||||
var collection = sendCollectionManager.GetAndCreateObjectHostCollection(
|
||||
revitElement,
|
||||
@@ -248,6 +240,18 @@ public class RevitRootObjectBuilder(
|
||||
var levelProxies = levelUnpacker.Unpack(flatElements);
|
||||
rootObject[ProxyKeys.LEVEL] = levelProxies;
|
||||
|
||||
rootObject[ProxyKeys.INSTANCE_DEFINITION] =
|
||||
revitToSpeckleCacheSingleton.InstanceDefinitionProxiesMap.Values.ToList();
|
||||
rootObject.elements.Add(
|
||||
new Collection()
|
||||
{
|
||||
elements = revitToSpeckleCacheSingleton.InstancedObjects.Values.ToList(),
|
||||
name = "revitInstancedObjects"
|
||||
}
|
||||
);
|
||||
|
||||
revitToSpeckleCacheSingleton.ClearInstanceProxies();
|
||||
|
||||
// NOTE: these are currently not used anywhere, we'll skip them until someone calls for it back
|
||||
// rootObject[ProxyKeys.PARAMETER_DEFINITIONS] = _parameterDefinitionHandler.Definitions;
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Converters.Common.Objects;
|
||||
using Speckle.Converters.Common.ToSpeckle;
|
||||
using Speckle.Converters.RevitShared.Extensions;
|
||||
using Speckle.Converters.RevitShared.Services;
|
||||
using Speckle.Converters.RevitShared.Settings;
|
||||
using Speckle.DoubleNumerics;
|
||||
using Speckle.Objects;
|
||||
using Speckle.Sdk;
|
||||
using Speckle.Sdk.Common;
|
||||
@@ -18,11 +20,11 @@ public sealed class DisplayValueExtractor
|
||||
List<SOG.Mesh>
|
||||
> _meshByMaterialConverter;
|
||||
|
||||
private readonly IScalingServiceToSpeckle _toSpeckleScalingService;
|
||||
private readonly ITypedConverter<DB.Curve, ICurve> _curveConverter;
|
||||
private readonly ITypedConverter<DB.PolyLine, SOG.Polyline> _polylineConverter;
|
||||
private readonly ITypedConverter<DB.Point, SOG.Point> _pointConverter;
|
||||
private readonly ITypedConverter<DB.PointCloudInstance, SOG.Pointcloud> _pointcloudConverter;
|
||||
private readonly ILogger<DisplayValueExtractor> _logger;
|
||||
private readonly IConverterSettingsStore<RevitConversionSettings> _converterSettings;
|
||||
|
||||
public DisplayValueExtractor(
|
||||
@@ -34,8 +36,8 @@ public sealed class DisplayValueExtractor
|
||||
ITypedConverter<DB.PolyLine, SOG.Polyline> polylineConverter,
|
||||
ITypedConverter<DB.Point, SOG.Point> pointConverter,
|
||||
ITypedConverter<DB.PointCloudInstance, SOG.Pointcloud> pointcloudConverter,
|
||||
ILogger<DisplayValueExtractor> logger,
|
||||
IConverterSettingsStore<RevitConversionSettings> converterSettings
|
||||
IConverterSettingsStore<RevitConversionSettings> converterSettings,
|
||||
IScalingServiceToSpeckle toSpeckleScalingService
|
||||
)
|
||||
{
|
||||
_meshByMaterialConverter = meshByMaterialConverter;
|
||||
@@ -43,30 +45,30 @@ public sealed class DisplayValueExtractor
|
||||
_polylineConverter = polylineConverter;
|
||||
_pointConverter = pointConverter;
|
||||
_pointcloudConverter = pointcloudConverter;
|
||||
_logger = logger;
|
||||
_converterSettings = converterSettings;
|
||||
_toSpeckleScalingService = toSpeckleScalingService;
|
||||
}
|
||||
|
||||
public List<Base> GetDisplayValue(DB.Element element)
|
||||
public List<DisplayValueResult> GetDisplayValue(DB.Element element)
|
||||
{
|
||||
switch (element)
|
||||
{
|
||||
// get custom (anything not using element.get_geometry) display values
|
||||
case DB.PointCloudInstance pointcloud:
|
||||
return new() { _pointcloudConverter.Convert(pointcloud) };
|
||||
return [DisplayValueResult.WithoutTransform(_pointcloudConverter.Convert(pointcloud))];
|
||||
case DB.ModelCurve modelCurve:
|
||||
return new() { GetCurveDisplayValue(modelCurve.GeometryCurve) };
|
||||
return [DisplayValueResult.WithoutTransform(GetCurveDisplayValue(modelCurve.GeometryCurve))];
|
||||
case DB.Grid grid:
|
||||
return new() { GetCurveDisplayValue(grid.Curve) };
|
||||
return [DisplayValueResult.WithoutTransform(GetCurveDisplayValue(grid.Curve))];
|
||||
case DB.Area area:
|
||||
List<Base> areaDisplay = new();
|
||||
List<DisplayValueResult> areaDisplay = new();
|
||||
using (var options = new DB.SpatialElementBoundaryOptions())
|
||||
{
|
||||
foreach (IList<DB.BoundarySegment> boundarySegmentGroup in area.GetBoundarySegments(options))
|
||||
{
|
||||
foreach (DB.BoundarySegment boundarySegment in boundarySegmentGroup)
|
||||
{
|
||||
areaDisplay.Add(GetCurveDisplayValue(boundarySegment.GetCurve()));
|
||||
areaDisplay.Add(DisplayValueResult.WithoutTransform(GetCurveDisplayValue(boundarySegment.GetCurve())));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +89,7 @@ public sealed class DisplayValueExtractor
|
||||
return wall.CurtainGrid is not null || wall.IsStackedWall ? new() : GetGeometryDisplayValue(element);
|
||||
// railings should also include toprail which need to be retrieved separately
|
||||
case DBA.Railing railing:
|
||||
List<Base> railingDisplay = GetGeometryDisplayValue(railing);
|
||||
List<DisplayValueResult> railingDisplay = GetGeometryDisplayValue(railing);
|
||||
if (railing.TopRail != DB.ElementId.InvalidElementId)
|
||||
{
|
||||
var topRail = _converterSettings.Current.Document.GetElement(railing.TopRail);
|
||||
@@ -105,10 +107,21 @@ public sealed class DisplayValueExtractor
|
||||
|
||||
private Base GetCurveDisplayValue(DB.Curve curve) => (Base)_curveConverter.Convert(curve);
|
||||
|
||||
private List<Base> GetGeometryDisplayValue(DB.Element element, DB.Options? options = null)
|
||||
private List<DisplayValueResult> GetGeometryDisplayValue(DB.Element element, DB.Options? options = null)
|
||||
{
|
||||
var collections = GetSortedGeometryFromElement(element, options);
|
||||
return ProcessGeometryCollections(element, collections);
|
||||
using DB.Transform? localToDocument = GetTransform(element);
|
||||
using DB.Transform? documentToLocal = localToDocument?.Inverse;
|
||||
|
||||
DB.Transform? documentToWorld = _converterSettings.Current.ReferencePointTransform?.Inverse;
|
||||
using DB.Transform? compoundTransform =
|
||||
localToDocument is not null && documentToWorld is not null
|
||||
? documentToWorld.Multiply(localToDocument)
|
||||
: localToDocument; // don't want to accidentally dispose of the ReferencePointTransform
|
||||
|
||||
DB.Transform? localToWorld = compoundTransform ?? documentToWorld;
|
||||
|
||||
var collections = GetSortedGeometryFromElement(element, options, documentToLocal);
|
||||
return ProcessGeometryCollections(element, collections, localToWorld);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -119,7 +132,15 @@ public sealed class DisplayValueExtractor
|
||||
/// Note: Some special element types (like Rebar) cannot use this method as their
|
||||
/// get_Geometry() returns null, requiring specialized extraction methods.
|
||||
/// </remarks>
|
||||
private GeometryCollections GetSortedGeometryFromElement(DB.Element element, DB.Options? options)
|
||||
/// <param name="element"></param>
|
||||
/// <param name="options"></param>
|
||||
/// <param name="worldToLocal"></param>
|
||||
/// <returns></returns>
|
||||
private GeometryCollections GetSortedGeometryFromElement(
|
||||
DB.Element element,
|
||||
DB.Options? options,
|
||||
DB.Transform? worldToLocal
|
||||
)
|
||||
{
|
||||
//options = ViewSpecificOptions ?? options ?? new Options() { DetailLevel = DetailLevelSetting };
|
||||
options ??= new DB.Options { DetailLevel = _detailLevelMap[_converterSettings.Current.DetailLevel] };
|
||||
@@ -142,7 +163,7 @@ public sealed class DisplayValueExtractor
|
||||
if (geom != null && geom.Any())
|
||||
{
|
||||
// retrieves all meshes and solids from a geometry element
|
||||
SortGeometry(element, collections, geom);
|
||||
SortGeometry(element, collections, geom, worldToLocal);
|
||||
}
|
||||
|
||||
return collections;
|
||||
@@ -155,36 +176,83 @@ public sealed class DisplayValueExtractor
|
||||
/// <remarks>
|
||||
/// Essentially all the ensuing steps after the common get_Geometry element method
|
||||
/// </remarks>
|
||||
private List<Base> ProcessGeometryCollections(DB.Element element, GeometryCollections collections)
|
||||
private List<DisplayValueResult> ProcessGeometryCollections(
|
||||
DB.Element element,
|
||||
GeometryCollections collections,
|
||||
DB.Transform? localToWorld
|
||||
)
|
||||
{
|
||||
List<Base> displayValue = new();
|
||||
|
||||
// handle all solids and meshes by their material
|
||||
var meshesByMaterial = GetMeshesByMaterial(collections.Meshes, collections.Solids);
|
||||
List<SOG.Mesh> displayMeshes = _meshByMaterialConverter.Convert(
|
||||
(meshesByMaterial, element.Id, ShouldSetElementDisplayToTransparent(element))
|
||||
);
|
||||
displayValue.AddRange(displayMeshes);
|
||||
|
||||
// add rest of geometry
|
||||
List<DisplayValueResult> displayValue = new(collections.TotalCount);
|
||||
Matrix4x4? matrix = localToWorld is not null ? TransformToMatrix(localToWorld) : null;
|
||||
|
||||
foreach (SOG.Mesh mesh in displayMeshes)
|
||||
{
|
||||
displayValue.Add(
|
||||
matrix.HasValue
|
||||
? DisplayValueResult.WithTransform(mesh, matrix.Value)
|
||||
: DisplayValueResult.WithoutTransform(mesh)
|
||||
);
|
||||
}
|
||||
|
||||
// add rest of geometry (always without transform)
|
||||
foreach (var curve in collections.Curves)
|
||||
{
|
||||
displayValue.Add(GetCurveDisplayValue(curve));
|
||||
displayValue.Add(DisplayValueResult.WithoutTransform(GetCurveDisplayValue(curve)));
|
||||
}
|
||||
|
||||
foreach (var polyline in collections.Polylines)
|
||||
{
|
||||
displayValue.Add(_polylineConverter.Convert(polyline));
|
||||
displayValue.Add(DisplayValueResult.WithoutTransform(_polylineConverter.Convert(polyline)));
|
||||
}
|
||||
|
||||
foreach (var point in collections.Points)
|
||||
{
|
||||
displayValue.Add(_pointConverter.Convert(point));
|
||||
displayValue.Add(DisplayValueResult.WithoutTransform(_pointConverter.Convert(point)));
|
||||
}
|
||||
|
||||
return displayValue;
|
||||
}
|
||||
|
||||
private Matrix4x4 TransformToMatrix(DB.Transform transform) =>
|
||||
new()
|
||||
{
|
||||
M11 = transform.BasisX.X,
|
||||
M21 = transform.BasisX.Y,
|
||||
M31 = transform.BasisX.Z,
|
||||
M41 = 0,
|
||||
|
||||
M12 = transform.BasisY.X,
|
||||
M22 = transform.BasisY.Y,
|
||||
M32 = transform.BasisY.Z,
|
||||
M42 = 0,
|
||||
|
||||
M13 = transform.BasisZ.X,
|
||||
M23 = transform.BasisZ.Y,
|
||||
M33 = transform.BasisZ.Z,
|
||||
M43 = 0,
|
||||
|
||||
M14 = _toSpeckleScalingService.ScaleLength(transform.Origin.X),
|
||||
M24 = _toSpeckleScalingService.ScaleLength(transform.Origin.Y),
|
||||
M34 = _toSpeckleScalingService.ScaleLength(transform.Origin.Z),
|
||||
M44 = 1
|
||||
};
|
||||
|
||||
private static DB.Transform? GetTransform(DB.Element element)
|
||||
{
|
||||
if (element is DB.Instance i)
|
||||
{
|
||||
return i.GetTotalTransform();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Dictionary<DB.ElementId, List<DB.Mesh>> GetMeshesByMaterial(
|
||||
List<DB.Mesh> meshes,
|
||||
List<DB.Solid> solids
|
||||
@@ -249,7 +317,12 @@ public sealed class DisplayValueExtractor
|
||||
///
|
||||
/// Note: this is basically a geometry unpacker for all types of geometry
|
||||
/// </summary>
|
||||
private void SortGeometry(DB.Element element, GeometryCollections collections, DB.GeometryElement geom)
|
||||
private void SortGeometry(
|
||||
DB.Element element,
|
||||
GeometryCollections collections,
|
||||
DB.GeometryElement geom,
|
||||
DB.Transform? worldToLocal
|
||||
)
|
||||
{
|
||||
foreach (DB.GeometryObject geomObj in geom)
|
||||
{
|
||||
@@ -267,13 +340,22 @@ public sealed class DisplayValueExtractor
|
||||
continue;
|
||||
}
|
||||
|
||||
if (worldToLocal is not null)
|
||||
{
|
||||
solid = DB.SolidUtils.CreateTransformed(solid, worldToLocal);
|
||||
}
|
||||
collections.Solids.Add(solid);
|
||||
break;
|
||||
|
||||
case DB.Mesh mesh:
|
||||
if (worldToLocal is not null)
|
||||
{
|
||||
mesh = mesh.get_Transformed(worldToLocal);
|
||||
}
|
||||
collections.Meshes.Add(mesh);
|
||||
break;
|
||||
|
||||
//Note, we're not applying transforms to curves/polylines/points because ProcessGeometryCollections expects them in world coordinates
|
||||
case DB.Curve curve:
|
||||
collections.Curves.Add(curve);
|
||||
break;
|
||||
@@ -288,12 +370,19 @@ public sealed class DisplayValueExtractor
|
||||
|
||||
case DB.GeometryInstance instance:
|
||||
// element transforms should not be carried down into nested geometryInstances.
|
||||
// Nested geomInstances should have their geom retreived with GetInstanceGeom, not GetSymbolGeom
|
||||
SortGeometry(element, collections, instance.GetInstanceGeometry());
|
||||
// Nested geomInstances should have their geom retrieved with GetInstanceGeom, not GetSymbolGeom
|
||||
if (worldToLocal == null) //see remark on method for why this is safe to do...
|
||||
{
|
||||
SortGeometry(element, collections, instance.GetInstanceGeometry(), null);
|
||||
}
|
||||
else
|
||||
{
|
||||
SortGeometry(element, collections, instance.GetSymbolGeometry(), null);
|
||||
}
|
||||
break;
|
||||
|
||||
case DB.GeometryElement geometryElement:
|
||||
SortGeometry(element, collections, geometryElement);
|
||||
SortGeometry(element, collections, geometryElement, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -424,25 +513,23 @@ public sealed class DisplayValueExtractor
|
||||
/// Instead, we use GetFullGeometryForView() to obtain the geometry and then process it
|
||||
/// using the standard geometry sorting and conversion.
|
||||
/// </remarks>
|
||||
private List<Base> GetRebarVolumetricDisplayValue(DB.Structure.Rebar rebar)
|
||||
private List<DisplayValueResult> GetRebarVolumetricDisplayValue(DB.Structure.Rebar rebar)
|
||||
{
|
||||
var collections = new GeometryCollections();
|
||||
|
||||
// Regular get_Geometry() returns null for rebar, so we need to use GetFullGeometryForView
|
||||
// ❗NOTE: ️view detail level needs to be fine in order for this to work
|
||||
// Same behaviour as sending structural frame though - consistent and therefore okay.
|
||||
DB.GeometryElement geometryElements = rebar.GetFullGeometryForView(_converterSettings.Current.Document.ActiveView);
|
||||
|
||||
SortGeometry(rebar, collections, geometryElements);
|
||||
DB.GeometryElement? geometryElements = rebar.GetFullGeometryForView(_converterSettings.Current.Document.ActiveView);
|
||||
|
||||
if (geometryElements != null)
|
||||
{
|
||||
SortGeometry(rebar, collections, geometryElements);
|
||||
return ProcessGeometryCollections(rebar, collections);
|
||||
SortGeometry(rebar, collections, geometryElements, null);
|
||||
return ProcessGeometryCollections(rebar, collections, null);
|
||||
}
|
||||
|
||||
// Return empty list if no geometry is found - imo not critical
|
||||
return new List<Base>();
|
||||
return new List<DisplayValueResult>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -451,7 +538,7 @@ public sealed class DisplayValueExtractor
|
||||
/// <remarks>
|
||||
/// This method extracts the centerlines of rebar elements when a simplified representation is preferred.
|
||||
/// </remarks>
|
||||
private List<Base> GetRebarCenterlineDisplayValue(DB.Structure.Rebar rebar)
|
||||
private List<DisplayValueResult> GetRebarCenterlineDisplayValue(DB.Structure.Rebar rebar)
|
||||
{
|
||||
bool isSingleLayout = rebar.LayoutRule == DB.Structure.RebarLayoutRule.Single;
|
||||
int numberOfBarPositions = rebar.NumberOfBarPositions;
|
||||
@@ -480,10 +567,10 @@ public sealed class DisplayValueExtractor
|
||||
);
|
||||
}
|
||||
|
||||
List<Base> displayValue = new();
|
||||
List<DisplayValueResult> displayValue = new();
|
||||
foreach (var curve in curves)
|
||||
{
|
||||
displayValue.Add(GetCurveDisplayValue(curve));
|
||||
displayValue.Add(DisplayValueResult.WithoutTransform(GetCurveDisplayValue(curve)));
|
||||
}
|
||||
|
||||
return displayValue;
|
||||
@@ -494,6 +581,10 @@ public sealed class DisplayValueExtractor
|
||||
/// Used to pass multiple geometry collections as a single parameter to improve code readability
|
||||
/// and reduce the risk of parameter ordering errors.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <see cref="Solids"/> and <see cref="Meshes"/> potentially in local coordinate space.
|
||||
/// For now, <see cref="Curves"/>, <see cref="Polylines"/>, <see cref="Points"/> will always be in world space
|
||||
/// </remarks>
|
||||
private sealed record GeometryCollections
|
||||
{
|
||||
public List<DB.Solid> Solids { get; } = new();
|
||||
@@ -501,5 +592,7 @@ public sealed class DisplayValueExtractor
|
||||
public List<DB.Curve> Curves { get; } = new();
|
||||
public List<DB.PolyLine> Polylines { get; } = new();
|
||||
public List<DB.Point> Points { get; } = new();
|
||||
|
||||
public int TotalCount => Solids.Count + Meshes.Count + Curves.Count + Polylines.Count + Points.Count;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Autodesk.Revit.DB;
|
||||
using Speckle.DoubleNumerics;
|
||||
|
||||
namespace Speckle.Converters.RevitShared.Helpers;
|
||||
|
||||
@@ -43,6 +44,30 @@ public static class ReferencePointHelper
|
||||
};
|
||||
}
|
||||
|
||||
public static Matrix4x4 TransformToMatrix(Transform transform) =>
|
||||
new()
|
||||
{
|
||||
M11 = transform.BasisX.X,
|
||||
M21 = transform.BasisX.Y,
|
||||
M31 = transform.BasisX.Z,
|
||||
M41 = 0,
|
||||
|
||||
M12 = transform.BasisY.X,
|
||||
M22 = transform.BasisY.Y,
|
||||
M32 = transform.BasisY.Z,
|
||||
M42 = 0,
|
||||
|
||||
M13 = transform.BasisZ.X,
|
||||
M23 = transform.BasisZ.Y,
|
||||
M33 = transform.BasisZ.Z,
|
||||
M43 = 0,
|
||||
|
||||
M14 = transform.Origin.X,
|
||||
M24 = transform.Origin.Y,
|
||||
M34 = transform.Origin.Z,
|
||||
M44 = 1
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Extracts and reconstructs a transform from the matrix data stored on root object
|
||||
/// </summary>
|
||||
|
||||
+12
@@ -1,4 +1,6 @@
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Models.Instances;
|
||||
|
||||
namespace Speckle.Converters.RevitShared.Helpers;
|
||||
|
||||
@@ -24,6 +26,10 @@ public class RevitToSpeckleCacheSingleton
|
||||
/// </summary>
|
||||
public Dictionary<string, Dictionary<string, RenderMaterialProxy>> ObjectRenderMaterialProxiesMap { get; } = new();
|
||||
|
||||
public Dictionary<string, InstanceDefinitionProxy> InstanceDefinitionProxiesMap { get; } = new();
|
||||
|
||||
public Dictionary<string, Base> InstancedObjects { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Returns the merged material proxy list for the given object ids. Use this to get post conversion a correct list of material proxies for setting on the root commit object.
|
||||
/// </summary>
|
||||
@@ -55,4 +61,10 @@ public class RevitToSpeckleCacheSingleton
|
||||
}
|
||||
return mergeTarget.Values.ToList();
|
||||
}
|
||||
|
||||
public void ClearInstanceProxies()
|
||||
{
|
||||
InstanceDefinitionProxiesMap.Clear();
|
||||
InstancedObjects.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ using System.Reflection;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Converters.Common.Registration;
|
||||
using Speckle.Converters.Revit2023.ToSpeckle.Properties;
|
||||
using Speckle.Converters.RevitShared.Helpers;
|
||||
using Speckle.Converters.RevitShared.Services;
|
||||
using Speckle.Converters.RevitShared.Settings;
|
||||
@@ -19,7 +18,8 @@ public static class ServiceRegistration
|
||||
var converterAssembly = Assembly.GetExecutingAssembly();
|
||||
//register types by default
|
||||
serviceCollection.AddMatchingInterfacesAsTransient(converterAssembly);
|
||||
// Register single root
|
||||
|
||||
// register single root
|
||||
serviceCollection.AddRootCommon<RevitRootToSpeckleConverter>(converterAssembly);
|
||||
|
||||
// register all application converters
|
||||
|
||||
+17
-7
@@ -3,6 +3,7 @@ using Speckle.Converters.Common;
|
||||
using Speckle.Converters.RevitShared.Services;
|
||||
using Speckle.Converters.RevitShared.Settings;
|
||||
using Speckle.Sdk;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Converters.RevitShared.ToSpeckle;
|
||||
|
||||
@@ -42,15 +43,24 @@ public class ParameterExtractor
|
||||
/// </summary>
|
||||
/// <param name="element"></param>
|
||||
/// <returns></returns>
|
||||
public Dictionary<string, object?> GetParameters(DB.Element element)
|
||||
public Base GetParameters(DB.Element element)
|
||||
{
|
||||
var instanceParams = new Base();
|
||||
instanceParams["instanceParameters"] = ParseParameterSet(element.Parameters);
|
||||
var typeParams = new Base();
|
||||
typeParams["typeParameters"] = GetTypeParameterDictionary(element);
|
||||
// NOTE: Woe and despair, I'm really abusing dictionaries here. See note at the top of class.
|
||||
return new Dictionary<string, object?>()
|
||||
{
|
||||
["Instance Parameters"] = ParseParameterSet(element.Parameters),
|
||||
["Type Parameters"] = GetTypeParameterDictionary(element),
|
||||
["System Type Parameters"] = GetSystemTypeParameterDictionary(element)
|
||||
};
|
||||
var props = new Base();
|
||||
props["@Instance Parameters"] = instanceParams;
|
||||
props["@Type Parameters"] = typeParams;
|
||||
props["System Type Parameters"] = GetSystemTypeParameterDictionary(element);
|
||||
// return new Dictionary<string, object?>()
|
||||
// {
|
||||
// ["@Instance Parameters"] = instanceParams,
|
||||
// ["@Type Parameters"] = typeParams,
|
||||
// ["System Type Parameters"] = GetSystemTypeParameterDictionary(element)
|
||||
// };
|
||||
return props;
|
||||
}
|
||||
|
||||
private Dictionary<string, Dictionary<string, object?>>? GetTypeParameterDictionary(DB.Element element)
|
||||
|
||||
+12
-13
@@ -1,4 +1,5 @@
|
||||
using Speckle.Converters.Common.Objects;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Converters.RevitShared.ToSpeckle.Properties;
|
||||
|
||||
@@ -19,25 +20,23 @@ public class PropertiesExtractor
|
||||
_materialQuantityConverter = materialQuantityConverter;
|
||||
}
|
||||
|
||||
public Dictionary<string, object?> GetProperties(DB.Element element)
|
||||
public Base GetProperties(DB.Element element)
|
||||
{
|
||||
var props = new Base();
|
||||
// by default, always get class properties first
|
||||
Dictionary<string, object?> properties = _classPropertiesExtractor.GetClassProperties(element);
|
||||
props["@classProps"] = _classPropertiesExtractor.GetClassProperties(element);
|
||||
|
||||
// add material quantities
|
||||
Dictionary<string, object> matQuantities = _materialQuantityConverter.Convert(element);
|
||||
if (matQuantities.Count > 0)
|
||||
{
|
||||
properties.Add("Material Quantities", matQuantities);
|
||||
}
|
||||
props["@matProps"] = _materialQuantityConverter.Convert(element);
|
||||
// if (matQuantities.Count > 0)
|
||||
// {
|
||||
// properties.Add("Material Quantities", matQuantities);
|
||||
// }
|
||||
|
||||
// add parameters
|
||||
Dictionary<string, object?> parameters = _parameterExtractor.GetParameters(element);
|
||||
if (parameters.Count > 0)
|
||||
{
|
||||
properties.Add("Parameters", parameters);
|
||||
}
|
||||
Base parameters = _parameterExtractor.GetParameters(element);
|
||||
props["@parameters"] = parameters;
|
||||
|
||||
return properties;
|
||||
return parameters;
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ using Speckle.Converters.RevitShared.Settings;
|
||||
using Speckle.Sdk;
|
||||
using Speckle.Sdk.Common;
|
||||
|
||||
namespace Speckle.Converters.Revit2023.ToSpeckle.Properties;
|
||||
namespace Speckle.Converters.RevitShared.ToSpeckle.Properties;
|
||||
|
||||
public readonly struct StructuralAssetProperties(
|
||||
string name,
|
||||
|
||||
+3
-6
@@ -36,12 +36,9 @@ public class MeshListConversionToSpeckle : ITypedConverter<List<DB.Mesh>, SOG.Me
|
||||
|
||||
foreach (DB.XYZ vert in mesh.Vertices)
|
||||
{
|
||||
// We need this method to take into account reference point transforms
|
||||
DB.XYZ extVert = _referencePointConverter.ConvertToExternalCoordinates(vert, true);
|
||||
|
||||
vertices.Add(_toSpeckleScalingService.ScaleLength(extVert.X));
|
||||
vertices.Add(_toSpeckleScalingService.ScaleLength(extVert.Y));
|
||||
vertices.Add(_toSpeckleScalingService.ScaleLength(extVert.Z));
|
||||
vertices.Add(_toSpeckleScalingService.ScaleLength(vert.X));
|
||||
vertices.Add(_toSpeckleScalingService.ScaleLength(vert.Y));
|
||||
vertices.Add(_toSpeckleScalingService.ScaleLength(vert.Z));
|
||||
}
|
||||
|
||||
for (int i = 0; i < mesh.NumTriangles; i++)
|
||||
|
||||
+1
-1
@@ -1,8 +1,8 @@
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Converters.Common.Objects;
|
||||
using Speckle.Converters.Revit2023.ToSpeckle.Properties;
|
||||
using Speckle.Converters.RevitShared.Services;
|
||||
using Speckle.Converters.RevitShared.Settings;
|
||||
using Speckle.Converters.RevitShared.ToSpeckle.Properties;
|
||||
using Speckle.Sdk.Common.Exceptions;
|
||||
using ApplicationException = Autodesk.Revit.Exceptions.ApplicationException;
|
||||
|
||||
|
||||
+80
-4
@@ -1,12 +1,16 @@
|
||||
using Speckle.Converters.Common;
|
||||
using Speckle.Converters.Common.Objects;
|
||||
using Speckle.Converters.Common.ToSpeckle;
|
||||
using Speckle.Converters.RevitShared.Extensions;
|
||||
using Speckle.Converters.RevitShared.Helpers;
|
||||
using Speckle.Converters.RevitShared.Settings;
|
||||
using Speckle.Converters.RevitShared.ToSpeckle.Properties;
|
||||
using Speckle.DoubleNumerics;
|
||||
using Speckle.Objects.Data;
|
||||
using Speckle.Sdk.Common;
|
||||
using Speckle.Sdk.Common.Exceptions;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Models.Instances;
|
||||
|
||||
namespace Speckle.Converters.RevitShared.ToSpeckle;
|
||||
|
||||
@@ -18,9 +22,11 @@ public class ElementTopLevelConverterToSpeckle : IToSpeckleTopLevelConverter
|
||||
private readonly ITypedConverter<DB.Location, Base> _locationConverter;
|
||||
private readonly LevelExtractor _levelExtractor;
|
||||
private readonly IConverterSettingsStore<RevitConversionSettings> _converterSettings;
|
||||
private readonly RevitToSpeckleCacheSingleton _revitToSpeckleCacheSingleton;
|
||||
|
||||
public ElementTopLevelConverterToSpeckle(
|
||||
DisplayValueExtractor displayValueExtractor,
|
||||
RevitToSpeckleCacheSingleton revitToSpeckleCacheSingleton,
|
||||
PropertiesExtractor propertiesExtractor,
|
||||
LevelExtractor levelExtractor,
|
||||
ITypedConverter<DB.Location, Base> locationConverter,
|
||||
@@ -28,6 +34,7 @@ public class ElementTopLevelConverterToSpeckle : IToSpeckleTopLevelConverter
|
||||
)
|
||||
{
|
||||
_displayValueExtractor = displayValueExtractor;
|
||||
_revitToSpeckleCacheSingleton = revitToSpeckleCacheSingleton;
|
||||
_propertiesExtractor = propertiesExtractor;
|
||||
_levelExtractor = levelExtractor;
|
||||
_locationConverter = locationConverter;
|
||||
@@ -95,7 +102,10 @@ public class ElementTopLevelConverterToSpeckle : IToSpeckleTopLevelConverter
|
||||
}
|
||||
|
||||
// get the display value
|
||||
List<Base> displayValue = _displayValueExtractor.GetDisplayValue(target);
|
||||
List<DisplayValueResult> displayValuesWithTransforms = _displayValueExtractor.GetDisplayValue(target);
|
||||
|
||||
// process display values and create instance proxies where applicable
|
||||
List<Base> proxifiedDisplayValues = ProcessDisplayValues(displayValuesWithTransforms);
|
||||
|
||||
// get level
|
||||
string? level = _levelExtractor.GetLevelName(target);
|
||||
@@ -105,7 +115,7 @@ public class ElementTopLevelConverterToSpeckle : IToSpeckleTopLevelConverter
|
||||
var children = GetElementChildren(target).ToList();
|
||||
|
||||
// get properties
|
||||
Dictionary<string, object?> properties = _propertiesExtractor.GetProperties(target);
|
||||
Base properties = _propertiesExtractor.GetProperties(target);
|
||||
|
||||
RevitObject revitObject =
|
||||
new()
|
||||
@@ -117,10 +127,11 @@ public class ElementTopLevelConverterToSpeckle : IToSpeckleTopLevelConverter
|
||||
category = category,
|
||||
location = convertedLocation,
|
||||
elements = children,
|
||||
displayValue = displayValue.Cast<Base>().ToList(),
|
||||
properties = properties,
|
||||
displayValue = proxifiedDisplayValues,
|
||||
properties = new Dictionary<string, object?>() { },
|
||||
units = _converterSettings.Current.SpeckleUnits
|
||||
};
|
||||
revitObject["@serializedProperties"] = properties;
|
||||
|
||||
return revitObject;
|
||||
}
|
||||
@@ -184,4 +195,69 @@ public class ElementTopLevelConverterToSpeckle : IToSpeckleTopLevelConverter
|
||||
yield return Convert(_converterSettings.Current.Document.GetElement(childId));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes display values with transforms and creates instance proxies for meshes that can be instanced.
|
||||
/// </summary>
|
||||
/// <returns>List of processed display values, with meshes replaced by instance proxies where applicable</returns>
|
||||
private List<Base> ProcessDisplayValues(List<DisplayValueResult> displayValues)
|
||||
{
|
||||
List<Base> proxifiedDisplayValues = new();
|
||||
|
||||
foreach (var displayValue in displayValues)
|
||||
{
|
||||
// check if this is a mesh with a transform - potential instance scenario
|
||||
// assumption here is that if we have matrix for corresponding base it is instance-able
|
||||
if (displayValue.Geometry is SOG.Mesh mesh && displayValue.Transform is not null)
|
||||
{
|
||||
var instanceProxy = CreateOrGetInstanceProxy(mesh, displayValue.Transform.Value);
|
||||
proxifiedDisplayValues.Add(instanceProxy);
|
||||
}
|
||||
else
|
||||
{
|
||||
proxifiedDisplayValues.Add(displayValue.Geometry);
|
||||
}
|
||||
}
|
||||
|
||||
return proxifiedDisplayValues;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates or retrieves an instance proxy for a mesh, managing instance definitions and caching.
|
||||
/// </summary>
|
||||
private InstanceProxy CreateOrGetInstanceProxy(SOG.Mesh mesh, Matrix4x4 transform)
|
||||
{
|
||||
var instanceDefinitionId = MeshInstanceIdGenerator.GenerateUntransformedMeshId(mesh);
|
||||
|
||||
// ensure instance definition exists
|
||||
if (!_revitToSpeckleCacheSingleton.InstanceDefinitionProxiesMap.ContainsKey(instanceDefinitionId))
|
||||
{
|
||||
var newInstanceDefinition = new InstanceDefinitionProxy
|
||||
{
|
||||
applicationId = instanceDefinitionId,
|
||||
objects = new List<string> { mesh.applicationId.NotNull() },
|
||||
maxDepth = 0,
|
||||
name = instanceDefinitionId,
|
||||
};
|
||||
_revitToSpeckleCacheSingleton.InstanceDefinitionProxiesMap.Add(instanceDefinitionId, newInstanceDefinition);
|
||||
}
|
||||
|
||||
// cache the untransformed mesh object if not already cached
|
||||
if (!_revitToSpeckleCacheSingleton.InstancedObjects.ContainsKey(instanceDefinitionId))
|
||||
{
|
||||
_revitToSpeckleCacheSingleton.InstancedObjects.Add(instanceDefinitionId, mesh);
|
||||
}
|
||||
|
||||
// create and return instance proxy with transform
|
||||
var instanceProxy = new InstanceProxy
|
||||
{
|
||||
applicationId = Guid.NewGuid().ToString(),
|
||||
definitionId = instanceDefinitionId,
|
||||
transform = transform,
|
||||
maxDepth = 0,
|
||||
units = mesh.units
|
||||
};
|
||||
|
||||
return instanceProxy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Speckle.DoubleNumerics;
|
||||
using Speckle.InterfaceGenerator;
|
||||
using Speckle.Objects.Data;
|
||||
using Speckle.Sdk.Dependencies;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Models.GraphTraversal;
|
||||
@@ -36,6 +37,19 @@ public class LocalToGlobalUnpacker : ILocalToGlobalUnpacker
|
||||
{
|
||||
atomicObjects.Add((objectToUnpack, objectToUnpack.Current));
|
||||
}
|
||||
|
||||
if (objectToUnpack.Current is DataObject dataObject)
|
||||
{
|
||||
foreach (Base displayValue in dataObject.displayValue)
|
||||
{
|
||||
if (displayValue is InstanceProxy instanceProxyInDisplayValue)
|
||||
{
|
||||
instanceProxies.Add(
|
||||
(new TraversalContext(instanceProxyInDisplayValue, parent: objectToUnpack), instanceProxyInDisplayValue)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var objectsAtAbsolute = new HashSet<(TraversalContext tc, Base obj)>();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Objects.Data;
|
||||
using Speckle.Objects.Other;
|
||||
using Speckle.Sdk.Models;
|
||||
using Speckle.Sdk.Models.Collections;
|
||||
using Speckle.Sdk.Models.GraphTraversal;
|
||||
@@ -64,6 +65,17 @@ public class RootObjectUnpacker
|
||||
{
|
||||
atomicObjects.Add(tc);
|
||||
}
|
||||
|
||||
if (tc.Current is DataObject dataObject)
|
||||
{
|
||||
foreach (var displayValue in dataObject.displayValue)
|
||||
{
|
||||
if (displayValue is IInstanceComponent)
|
||||
{
|
||||
instanceComponents.Add(new TraversalContext(displayValue, parent: tc));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (atomicObjects, instanceComponents);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using NUnit.Framework;
|
||||
using Speckle.Converters.Common.ToSpeckle;
|
||||
|
||||
namespace Speckle.Converters.Common.Tests.ToSpeckle;
|
||||
|
||||
public class MeshInstanceIdGeneratorTests
|
||||
{
|
||||
private static IEnumerable<List<double>> TestCases()
|
||||
{
|
||||
int[] testCases = [0, 1, 100, 1_000_000];
|
||||
foreach (int testLength in testCases)
|
||||
{
|
||||
yield return Enumerable
|
||||
.Range(0, testLength)
|
||||
.Select(_ => TestContext.CurrentContext.Random.NextDouble(float.MinValue, float.MaxValue))
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCaseSource(nameof(TestCases))]
|
||||
public void TestEquivalentImplementations(List<double> vertices)
|
||||
{
|
||||
var result = MeshInstanceIdGenerator.GenerateUntransformedMeshId(vertices);
|
||||
var resultSpan = MeshInstanceIdGenerator.GenerateUntransformedMeshId_Span(vertices);
|
||||
|
||||
Assert.That(result, Is.EqualTo(resultSpan));
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,10 @@
|
||||
<TargetFrameworks>net48;net8.0</TargetFrameworks>
|
||||
<Configurations>Debug;Release;Local</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="Speckle.Converters.Common.Tests"/>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using Speckle.DoubleNumerics;
|
||||
using Speckle.Sdk.Models;
|
||||
|
||||
namespace Speckle.Converters.Common.ToSpeckle;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a display value extracted from a host app element, optionally with a transform matrix for instancing.
|
||||
/// </summary>
|
||||
/// <param name="Geometry">The extracted geometry as a Speckle Base object</param>
|
||||
/// <param name="Transform">Optional transform matrix for instanced geometry. Null for non-instanced geometry.</param>
|
||||
public readonly record struct DisplayValueResult(Base Geometry, Matrix4x4? Transform)
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a display value result without a transform (non-instanced geometry).
|
||||
/// </summary>
|
||||
public static DisplayValueResult WithoutTransform(Base geometry) => new(geometry, null);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a display value result with a transform (instanced geometry).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Seems unnecessary, but reads nicely (self-documenting) in usage in my opinion (clear intent).
|
||||
/// </remarks>
|
||||
public static DisplayValueResult WithTransform(Base geometry, Matrix4x4 transform) => new(geometry, transform);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using Speckle.InterfaceGenerator;
|
||||
using Speckle.Objects.Geometry;
|
||||
using Speckle.Sdk.Common;
|
||||
#if NET6_0_OR_GREATER
|
||||
using System.Runtime.InteropServices;
|
||||
#endif
|
||||
|
||||
namespace Speckle.Converters.Common.ToSpeckle;
|
||||
|
||||
[GenerateAutoInterface]
|
||||
public static class MeshInstanceIdGenerator
|
||||
{
|
||||
/// <summary>
|
||||
/// Generate a unique hash from the vertex data of a mesh.
|
||||
/// This is a "good enough" way to compare the equality of meshes.
|
||||
/// Note, does not consider other mesh data, only <see cref="Mesh.vertices"/>
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// There are two implementations of this function because NET Framework lacks some of the Marshall and Span based functions.
|
||||
/// However, their external behaviour is the same.
|
||||
/// </remarks>
|
||||
/// <param name="mesh"></param>
|
||||
/// <returns></returns>
|
||||
[Pure]
|
||||
public static string GenerateUntransformedMeshId(Mesh mesh)
|
||||
{
|
||||
#if NET6_0_OR_GREATER
|
||||
return GenerateUntransformedMeshId_Span(mesh.vertices);
|
||||
#else
|
||||
return GenerateUntransformedMeshId(mesh.vertices);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if NET6_0_OR_GREATER
|
||||
|
||||
[Pure]
|
||||
internal static string GenerateUntransformedMeshId_Span(List<double> vertices)
|
||||
{
|
||||
ReadOnlySpan<double> span = CollectionsMarshal.AsSpan(vertices);
|
||||
ReadOnlySpan<byte> inputBytes = MemoryMarshal.AsBytes(span);
|
||||
|
||||
Span<byte> hash = stackalloc byte[SHA256.HashSizeInBytes];
|
||||
SHA256.HashData(inputBytes, hash);
|
||||
|
||||
return Convert.ToHexString(hash);
|
||||
}
|
||||
#endif
|
||||
|
||||
[Pure]
|
||||
[SuppressMessage(
|
||||
"Performance",
|
||||
"CA1850:Prefer static \'HashData\' method over \'ComputeHash\'",
|
||||
Justification = "Already another overload for .NET Core"
|
||||
)]
|
||||
internal static string GenerateUntransformedMeshId(List<double> vertices)
|
||||
{
|
||||
double[] verts = (double[])s_listItemsField.GetValue(vertices).NotNull();
|
||||
int byteCount = verts.Length * sizeof(double);
|
||||
byte[] inputBytes = new byte[byteCount];
|
||||
Buffer.BlockCopy(verts, 0, inputBytes, 0, byteCount);
|
||||
|
||||
// Compute the SHA256 hash
|
||||
using (SHA256 sha256 = SHA256.Create())
|
||||
{
|
||||
byte[] hashBytes = sha256.ComputeHash(inputBytes);
|
||||
|
||||
// Convert hash to hex string (uppercase, similar to Convert.ToHexString)
|
||||
StringBuilder sb = new(hashBytes.Length * 2);
|
||||
foreach (byte b in hashBytes)
|
||||
{
|
||||
sb.AppendFormat("{0:X2}", b);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly FieldInfo s_listItemsField = typeof(List<double>)
|
||||
.GetField("_items", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
.NotNull();
|
||||
}
|
||||
Reference in New Issue
Block a user