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 <kuang.claire@gmail.com>
This commit is contained in:
KatKatKateryna
2025-05-19 15:59:47 +01:00
committed by GitHub
parent 05c84c92f2
commit 84f6f3fbe6
9 changed files with 164 additions and 13 deletions
@@ -0,0 +1,24 @@
namespace Speckle.Converters.Rhino.Extensions;
public static class GeometryBaseExtensions
{
/// <summary>
/// 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
/// </summary>
/// <returns>
/// Vector from origin to Geometry bbox center (if translation needed), otherwise zero-length vector.
/// </returns>
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;
}
}
@@ -2,4 +2,7 @@
namespace Speckle.Converters.Rhino;
public record RhinoConversionSettings(RhinoDoc Document, string SpeckleUnits);
/// <summary>
/// Represents the settings used for Rhino and Grasshopper conversions.
/// </summary>
public record RhinoConversionSettings(RhinoDoc Document, string SpeckleUnits, bool ModelFarFromOrigin);
@@ -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());
/// <summary>
/// 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.
/// </summary>
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;
}
}
@@ -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)
/// <summary>
/// Extracting Rhino Mesh from Rhino GeometryBase using specified MeshingParameters settings, e.g. minimumEdgeLength.
/// </summary>
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;
}
/// <summary>
/// Calculating optimal meshing parameter 'minimumEdgeLength' for the given geometry.
/// </summary>
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;
}
/// <summary>
/// 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
/// </summary>
/// <returns>List of converted Speckle meshes</returns>
public static List<SOG.Mesh> GetSpeckleMeshes(
RG.GeometryBase geometry,
bool modelFarFromOrigin,
string units,
ITypedConverter<RG.Mesh, SOG.Mesh> 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<SOG.Mesh> 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<SOG.Mesh> 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));
}
}
}
@@ -28,8 +28,12 @@ public class BrepToSpeckleConverter : ITypedConverter<RG.Brep, SOG.BrepX>
{
var brepEncoding = RawEncodingCreator.Encode(target, _settingsStore.Current.Document);
var displayMesh = DisplayMeshExtractor.GetGeometryDisplayMesh(target);
List<SOG.Mesh> displayValue = displayMesh is null ? new() : new() { _meshConverter.Convert(displayMesh) };
List<SOG.Mesh> displayValue = DisplayMeshExtractor.GetSpeckleMeshes(
target,
_settingsStore.Current.ModelFarFromOrigin,
_settingsStore.Current.SpeckleUnits,
_meshConverter
);
var bx = new SOG.BrepX()
{
@@ -28,8 +28,12 @@ public class ExtrusionToSpeckleConverter : ITypedConverter<RG.Extrusion, SOG.Ext
{
var extrusionEncoding = RawEncodingCreator.Encode(target, _settingsStore.Current.Document);
var displayMesh = DisplayMeshExtractor.GetGeometryDisplayMesh(target);
List<SOG.Mesh> displayValue = displayMesh is null ? new() : new() { _meshConverter.Convert(displayMesh) };
List<SOG.Mesh> displayValue = DisplayMeshExtractor.GetSpeckleMeshes(
target,
_settingsStore.Current.ModelFarFromOrigin,
_settingsStore.Current.SpeckleUnits,
_meshConverter
);
var bx = new SOG.ExtrusionX()
{
@@ -38,8 +38,13 @@ public class HatchToSpeckleConverter : ITypedConverter<RG.Hatch, SOG.Region>
// create display mesh from region by converting to brep first
var brep = RG.Brep.TryConvertBrep(target);
var displayMesh = DisplayMeshExtractor.GetGeometryDisplayMesh(brep);
List<SOG.Mesh> displayValue = displayMesh is null ? new() : new() { _meshConverter.Convert(displayMesh) };
List<SOG.Mesh> displayValue = DisplayMeshExtractor.GetSpeckleMeshes(
brep,
_settingsStore.Current.ModelFarFromOrigin,
_settingsStore.Current.SpeckleUnits,
_meshConverter
);
return new SOG.Region
{
@@ -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<RG.Mesh, SOG.Mesh>
{
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++)
@@ -28,8 +28,12 @@ public class SubDToSpeckleConverter : ITypedConverter<RG.SubD, SOG.SubDX>
{
var subdEncoding = RawEncodingCreator.Encode(target, _settingsStore.Current.Document);
var displayMesh = DisplayMeshExtractor.GetGeometryDisplayMesh(target);
List<SOG.Mesh> displayValue = displayMesh is null ? new() : new() { _meshConverter.Convert(displayMesh) };
List<SOG.Mesh> displayValue = DisplayMeshExtractor.GetSpeckleMeshes(
target,
_settingsStore.Current.ModelFarFromOrigin,
_settingsStore.Current.SpeckleUnits,
_meshConverter
);
var bx = new SOG.SubDX()
{