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()
{