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:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+70
-3
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+6
-2
@@ -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()
|
||||
{
|
||||
|
||||
+6
-2
@@ -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()
|
||||
{
|
||||
|
||||
+7
-2
@@ -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
|
||||
{
|
||||
|
||||
+26
@@ -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++)
|
||||
|
||||
+6
-2
@@ -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()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user