From 84f6f3fbe646f40ea5a60c3cdb09f5fd51ff07fc Mon Sep 17 00:00:00 2001 From: KatKatKateryna <89912278+KatKatKateryna@users.noreply.github.com> Date: Mon, 19 May 2025 15:59:47 +0100 Subject: [PATCH] Kateryna/cnx 1498 rhino mesh conversion problem on some geometries (#836) * move geometry to origin before meshing * reorder stuff * move what's relevalt to MeshExtractor * add minEdgeLength * comments * add minEdge setting * adjust variables * fix the matrix * move all injections outside the method (except converter) * add methods to Extrusion and Hatches. SubD doesn't seem to be affected * typo * restructure * typos * move methods * get min edge length * make function available for mesh * add method to mesh conversions * add logic to subd * unnecessary change; spaces * typo * comments * move FarFromOrigin to extension * remove duplicate code; adjust MeshConverter logic * reduce threshold to 1e5 * comment --------- Co-authored-by: Claire Kuang --- .../Extensions/GeometryBaseExtensions.cs | 24 ++++++ .../RhinoConversionSettings.cs | 5 +- .../RhinoConversionSettingsFactory.cs | 16 +++- .../ToSpeckle/Meshing/DisplayMeshExtractor.cs | 73 ++++++++++++++++++- .../ToSpeckle/Raw/BrepToSpeckleConverter.cs | 8 +- .../Raw/ExtrusionToSpeckleConverter.cs | 8 +- .../ToSpeckle/Raw/HatchToSpeckleConverter.cs | 9 ++- .../ToSpeckle/Raw/MeshToSpeckleConverter.cs | 26 +++++++ .../ToSpeckle/Raw/SubDToSpeckleConverter.cs | 8 +- 9 files changed, 164 insertions(+), 13 deletions(-) create mode 100644 Converters/Rhino/Speckle.Converters.RhinoShared/Extensions/GeometryBaseExtensions.cs diff --git a/Converters/Rhino/Speckle.Converters.RhinoShared/Extensions/GeometryBaseExtensions.cs b/Converters/Rhino/Speckle.Converters.RhinoShared/Extensions/GeometryBaseExtensions.cs new file mode 100644 index 000000000..bb870fce1 --- /dev/null +++ b/Converters/Rhino/Speckle.Converters.RhinoShared/Extensions/GeometryBaseExtensions.cs @@ -0,0 +1,24 @@ +namespace Speckle.Converters.Rhino.Extensions; + +public static class GeometryBaseExtensions +{ + /// + /// Getting translation vector from origin to the Geometry bbox Center (if geometry is far from origin and translation needed) + /// This is needed for some objects, because of Rhino using single precision numbers for Mesh vertices: https://wiki.mcneel.com/rhino/farfromorigin + /// + /// + /// Vector from origin to Geometry bbox center (if translation needed), otherwise zero-length vector. + /// + public static bool IsFarFromOrigin(this RG.GeometryBase geometry, out RG.Vector3d vectorToGeometry) + { + var geometryBbox = geometry.GetBoundingBox(false); // 'false' for 'accurate' parameter to accelerate bbox calculation + if (geometryBbox.Min.DistanceTo(RG.Point3d.Origin) > 1e5 || geometryBbox.Max.DistanceTo(RG.Point3d.Origin) > 1e5) + { + vectorToGeometry = new RG.Vector3d(geometryBbox.Center); + return true; + } + + vectorToGeometry = new RG.Vector3d(); + return false; + } +} diff --git a/Converters/Rhino/Speckle.Converters.RhinoShared/RhinoConversionSettings.cs b/Converters/Rhino/Speckle.Converters.RhinoShared/RhinoConversionSettings.cs index 2b7fd8f10..6255f6009 100644 --- a/Converters/Rhino/Speckle.Converters.RhinoShared/RhinoConversionSettings.cs +++ b/Converters/Rhino/Speckle.Converters.RhinoShared/RhinoConversionSettings.cs @@ -2,4 +2,7 @@ namespace Speckle.Converters.Rhino; -public record RhinoConversionSettings(RhinoDoc Document, string SpeckleUnits); +/// +/// Represents the settings used for Rhino and Grasshopper conversions. +/// +public record RhinoConversionSettings(RhinoDoc Document, string SpeckleUnits, bool ModelFarFromOrigin); diff --git a/Converters/Rhino/Speckle.Converters.RhinoShared/RhinoConversionSettingsFactory.cs b/Converters/Rhino/Speckle.Converters.RhinoShared/RhinoConversionSettingsFactory.cs index ae372fdd1..91e68540f 100644 --- a/Converters/Rhino/Speckle.Converters.RhinoShared/RhinoConversionSettingsFactory.cs +++ b/Converters/Rhino/Speckle.Converters.RhinoShared/RhinoConversionSettingsFactory.cs @@ -13,5 +13,19 @@ public class RhinoConversionSettingsFactory( public RhinoConversionSettings Current => settingsStore.Current; public RhinoConversionSettings Create(RhinoDoc document) => - new(document, unitsConverter.ConvertOrThrow(RhinoDoc.ActiveDoc.ModelUnitSystem)); + new(document, unitsConverter.ConvertOrThrow(RhinoDoc.ActiveDoc.ModelUnitSystem), ModelFarFromOrigin()); + + /// + /// Quick check whether any of the objects in the scene might be located too far from origin and cause precision issues during meshing. + /// It prevents 'normal' Rhino models (not too far from origin) from unnecessary Bbox calculations on every object on Send. + /// + private bool ModelFarFromOrigin() + { + RG.BoundingBox bbox = RhinoDoc.ActiveDoc.Objects.BoundingBox; + if (bbox.Min.DistanceTo(RG.Point3d.Origin) > 1e5 || bbox.Max.DistanceTo(RG.Point3d.Origin) > 1e5) + { + return true; + } + return false; + } } diff --git a/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Meshing/DisplayMeshExtractor.cs b/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Meshing/DisplayMeshExtractor.cs index c50488845..6c5b7e8bf 100644 --- a/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Meshing/DisplayMeshExtractor.cs +++ b/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Meshing/DisplayMeshExtractor.cs @@ -1,4 +1,7 @@ using Rhino.DocObjects; +using Speckle.Converters.Common.Objects; +using Speckle.Converters.Rhino.Extensions; +using Speckle.DoubleNumerics; using Speckle.Sdk.Common.Exceptions; namespace Speckle.Converters.Rhino.ToSpeckle.Meshing; @@ -43,8 +46,13 @@ public static class DisplayMeshExtractor return joinedMesh; } - public static RG.Mesh? GetGeometryDisplayMesh(RG.GeometryBase geometry) + /// + /// Extracting Rhino Mesh from Rhino GeometryBase using specified MeshingParameters settings, e.g. minimumEdgeLength. + /// + public static RG.Mesh GetGeometryDisplayMesh(RG.GeometryBase geometry, bool highPrecision = false) { + double minEdgeLength = highPrecision ? GetAccurateMinEdgeLegth(geometry) : 0.05; + // declare "renderMeshes" as a separate var, because it needs to be checked for null after each Mesh.Create method RG.Mesh[] renderMeshes; var joinedMesh = new RG.Mesh(); @@ -52,7 +60,7 @@ public static class DisplayMeshExtractor switch (geometry) { case RG.Brep brep: - renderMeshes = RG.Mesh.CreateFromBrep(brep, new(0.05, 0.05)); + renderMeshes = RG.Mesh.CreateFromBrep(brep, new(0.05, minEdgeLength)); break; case RG.SubD subd: #pragma warning disable CA2000 @@ -61,7 +69,7 @@ public static class DisplayMeshExtractor renderMeshes = [subdMesh]; break; case RG.Extrusion extrusion: - renderMeshes = RG.Mesh.CreateFromBrep(extrusion.ToBrep(), new(0.05, 0.05)); + renderMeshes = RG.Mesh.CreateFromBrep(extrusion.ToBrep(), new(0.05, minEdgeLength)); break; default: throw new ConversionException($"Unsupported object for display mesh generation {geometry.GetType().FullName}"); @@ -76,4 +84,63 @@ public static class DisplayMeshExtractor joinedMesh.Append(renderMeshes); return joinedMesh; } + + /// + /// Calculating optimal meshing parameter 'minimumEdgeLength' for the given geometry. + /// + private static double GetAccurateMinEdgeLegth(RG.GeometryBase geometry) + { + // adjust meshing parameters if Brep edges are too close to the document tolerance + double minEdgeLength = 0.05; + if (geometry is RG.Brep brep && brep.Edges.Any(x => x.GetLength() < minEdgeLength)) + { + return 0; + } + + return minEdgeLength; + } + + /// + /// Extracting Rhino Mesh and converting to Speckle with the most suitable settings (e.g. moving to origin first, if needed) + /// This is needed because of Rhino using single precision numbers for Mesh vertices: https://wiki.mcneel.com/rhino/farfromorigin + /// + /// List of converted Speckle meshes + public static List GetSpeckleMeshes( + RG.GeometryBase geometry, + bool modelFarFromOrigin, + string units, + ITypedConverter meshConverter + ) + { + RG.GeometryBase geometryToMesh = geometry; + RG.Vector3d? vector = null; + + // 1.1. If needed, move geometry to origin + if (modelFarFromOrigin && geometry.IsFarFromOrigin(out RG.Vector3d vectorToGeometry)) + { + geometryToMesh = geometry.Duplicate(); + geometryToMesh.Transform(RG.Transform.Translation(-vectorToGeometry)); + vector = vectorToGeometry; + } + // 1.2. Extract Rhino Mesh + RG.Mesh movedDisplayMesh = GetGeometryDisplayMesh(geometryToMesh, true); + + // 2. Convert extracted Mesh to Speckle. We don't move geometry back yet, because 'far from origin' geometry is causing Speckle conversion issues too + List displayValue = new() { meshConverter.Convert(movedDisplayMesh) }; + + // 3. Move Speckle geometry back from origin, if translation was applied + MoveSpeckleMeshes(displayValue, vector, units); + + return displayValue; + } + + public static void MoveSpeckleMeshes(List displayValue, RG.Vector3d? vectorToGeometry, string units) + { + if (vectorToGeometry is RG.Vector3d vector) + { + Matrix4x4 matrix = new(1, 0, 0, vector.X, 0, 1, 0, vector.Y, 0, 0, 1, vector.Z, 0, 0, 0, 1); + SO.Transform transform = new() { matrix = matrix, units = units }; + displayValue.ForEach(x => x.Transform(transform)); + } + } } diff --git a/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Raw/BrepToSpeckleConverter.cs b/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Raw/BrepToSpeckleConverter.cs index 8ff4a0f8e..88b32cca0 100644 --- a/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Raw/BrepToSpeckleConverter.cs +++ b/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Raw/BrepToSpeckleConverter.cs @@ -28,8 +28,12 @@ public class BrepToSpeckleConverter : ITypedConverter { var brepEncoding = RawEncodingCreator.Encode(target, _settingsStore.Current.Document); - var displayMesh = DisplayMeshExtractor.GetGeometryDisplayMesh(target); - List displayValue = displayMesh is null ? new() : new() { _meshConverter.Convert(displayMesh) }; + List displayValue = DisplayMeshExtractor.GetSpeckleMeshes( + target, + _settingsStore.Current.ModelFarFromOrigin, + _settingsStore.Current.SpeckleUnits, + _meshConverter + ); var bx = new SOG.BrepX() { diff --git a/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Raw/ExtrusionToSpeckleConverter.cs b/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Raw/ExtrusionToSpeckleConverter.cs index ea47919ec..1c086fc23 100644 --- a/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Raw/ExtrusionToSpeckleConverter.cs +++ b/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Raw/ExtrusionToSpeckleConverter.cs @@ -28,8 +28,12 @@ public class ExtrusionToSpeckleConverter : ITypedConverter displayValue = displayMesh is null ? new() : new() { _meshConverter.Convert(displayMesh) }; + List displayValue = DisplayMeshExtractor.GetSpeckleMeshes( + target, + _settingsStore.Current.ModelFarFromOrigin, + _settingsStore.Current.SpeckleUnits, + _meshConverter + ); var bx = new SOG.ExtrusionX() { diff --git a/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Raw/HatchToSpeckleConverter.cs b/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Raw/HatchToSpeckleConverter.cs index b04c05f68..94c7bbdd6 100644 --- a/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Raw/HatchToSpeckleConverter.cs +++ b/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Raw/HatchToSpeckleConverter.cs @@ -38,8 +38,13 @@ public class HatchToSpeckleConverter : ITypedConverter // create display mesh from region by converting to brep first var brep = RG.Brep.TryConvertBrep(target); - var displayMesh = DisplayMeshExtractor.GetGeometryDisplayMesh(brep); - List displayValue = displayMesh is null ? new() : new() { _meshConverter.Convert(displayMesh) }; + + List displayValue = DisplayMeshExtractor.GetSpeckleMeshes( + brep, + _settingsStore.Current.ModelFarFromOrigin, + _settingsStore.Current.SpeckleUnits, + _meshConverter + ); return new SOG.Region { diff --git a/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Raw/MeshToSpeckleConverter.cs b/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Raw/MeshToSpeckleConverter.cs index 8214ab5f5..e95e35de2 100644 --- a/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Raw/MeshToSpeckleConverter.cs +++ b/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Raw/MeshToSpeckleConverter.cs @@ -1,5 +1,7 @@ using Speckle.Converters.Common; using Speckle.Converters.Common.Objects; +using Speckle.Converters.Rhino.Extensions; +using Speckle.Converters.Rhino.ToSpeckle.Meshing; using Speckle.Sdk.Common.Exceptions; namespace Speckle.Converters.Rhino.ToSpeckle.Raw; @@ -31,6 +33,30 @@ public class MeshToSpeckleConverter : ITypedConverter { throw new ValidationException("Cannot convert a mesh with 0 vertices/faces"); } + + // Extracting Rhino Mesh and converting to Speckle with the most suitable settings (e.g. moving to origin first, if needed) + // This is needed because of Rhino using single precision numbers for Mesh vertices: https://wiki.mcneel.com/rhino/farfromorigin + RG.Mesh meshToConvert = target; + RG.Vector3d? vector = null; + + // 1. If needed, move geometry to origin + if (_settingsStore.Current.ModelFarFromOrigin && target.IsFarFromOrigin(out RG.Vector3d vectorToGeometry)) + { + meshToConvert = (RG.Mesh)target.Duplicate(); + meshToConvert.Transform(RG.Transform.Translation(-vectorToGeometry)); + vector = vectorToGeometry; + } + // 2. Convert extracted Mesh to Speckle. We don't move geometry back yet, because 'far from origin' geometry is causing Speckle conversion issues too + SOG.Mesh convertedMesh = ConvertMesh(meshToConvert); + + // 3. Move Speckle geometry back from origin, if translation was applied + DisplayMeshExtractor.MoveSpeckleMeshes([convertedMesh], vector, _settingsStore.Current.SpeckleUnits); + + return convertedMesh; + } + + private SOG.Mesh ConvertMesh(RG.Mesh target) + { var vertexCoordinates = new double[target.Vertices.Count * 3]; var x = 0; for (int i = 0; i < target.Vertices.Count; i++) diff --git a/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Raw/SubDToSpeckleConverter.cs b/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Raw/SubDToSpeckleConverter.cs index 2f550c8f1..98091f519 100644 --- a/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Raw/SubDToSpeckleConverter.cs +++ b/Converters/Rhino/Speckle.Converters.RhinoShared/ToSpeckle/Raw/SubDToSpeckleConverter.cs @@ -28,8 +28,12 @@ public class SubDToSpeckleConverter : ITypedConverter { var subdEncoding = RawEncodingCreator.Encode(target, _settingsStore.Current.Document); - var displayMesh = DisplayMeshExtractor.GetGeometryDisplayMesh(target); - List displayValue = displayMesh is null ? new() : new() { _meshConverter.Convert(displayMesh) }; + List displayValue = DisplayMeshExtractor.GetSpeckleMeshes( + target, + _settingsStore.Current.ModelFarFromOrigin, + _settingsStore.Current.SpeckleUnits, + _meshConverter + ); var bx = new SOG.SubDX() {