What I’m trying to acheive, is to verify that my custom export function is actually doing what it is supposed to be doing. This export function takes a subset of the geometry in a 3dm file, does some black magic to it, adds the results to a headless RhinoDoc and finally writes it to a 3dm file.
The code seems to be working fine, but once I run it in a unit test an System.AccessViolationException gets thrown somewhat randomly by the RhinoDoc finalizer. I observed this by decompiling the code. This crashes the unit test thread - but I can continue debugging and the code keeps running smoothly and does everything it is supposed to do - but because the unit test thread crashes and burns the test it self fails.
I’ve used this project as a baseline for my XUnit project:
Any thoughts on why this might be happening?
For some code context see the simplified and redacted code example:
[Theory]
[InlineData("TestModels\\SquareBox.3dm")]
public void TryExportGeometry(string filePath)
{
var tempFolder = IOUtil.GetTempFolder();
var extractionFilePath = Path.Combine(tempFolder, "extract.3dm");
try
{
var doc = RhinoDoc.OpenHeadless(filePath);
{
var isExported = Export.TryExportGeometry(doc, extractionFilePath, isDebug: false);
Assert.True(isExported);
Assert.True(File.Exists(extractionFilePath));
... further testing
}
}
finally
{
IOUtil.DeleteFolder(tempFolder);
}
}
public static bool TryExportGeometry(RhinoDoc doc, string output3dmFilePath, bool isDebug)
{
using (var extract = RhinoUtil.ExtractGeometry(doc))
{
... analysis and manipulation of extracted geometry
extract.Document.SaveAs(output3dmFilePath);
}
}
public static GeometryExtractionResult ExtractGeometry(RhinoDoc doc)
{
bool isSuccess = false;
var tempFolder = IOUtil.GetTempFolder();
RhinoDoc exportDoc = RhinoDoc.CreateHeadless(null);
try
{
var docMetersPath = Path.Combine(tempFolder, $"{Guid.NewGuid()}.3dm");
doc.SaveAs(docMetersPath);
using (var docMeters = RhinoDoc.OpenHeadless(docMetersPath))
{
if (docMeters.GetUnitSystemName(true, true, true, true) != "m")
{
docMeters.AdjustModelUnitSystem(UnitSystem.Meters, scale: true);
docMeters.Save();
}
var geometryIndex = docMeters.Layers.FindByFullPath(Paths.Geometry, notFoundReturnValue: -1);
var geometryLayer = docMeters.Layers[geometryIndex];
var geometryLayers = geometryLayer.GetChildren();
var onlyMesh = docMeters.LayersOnlyContainRecursive(geometryLayers, ObjectType.Mesh);
List<IGeometryInfo> exportedGeometry = new List<IGeometryInfo>();
if (onlyMesh)
{
if (!TryExportAsMesh(doc, exportDoc, docMeters, geometryLayers, out var meshesInModel))
return null;
exportedGeometry.AddRange(meshesInModel);
}
else
{
if (!TryExtractAsBreps(doc, exportDoc, docMeters, geometryLayers, out var brepsInModel))
return null;
exportedGeometry.AddRange(brepsInModel);
}
isSuccess = true; //Othewise it will be disposed
return new GeometryExtractionResult(exportDoc, exportedGeometry);
}
}
finally
{
IOUtil.DeleteFolder(tempFolder);
if (!isSuccess)
exportDoc?.Dispose();
}
}
public class GeometryExtractionResult : IDisposable
{
public RhinoDoc Document { get; set; }
public IEnumerable<BrepInfo> BrepsInModel => Geometry.Where(x => x is BrepInfo).Select(x => x as BrepInfo);
public IEnumerable<MeshInfo> MeshesInModel => Geometry.Where(x => x is MeshInfo).Select(x => x as MeshInfo);
public List<IGeometryInfo> Geometry { get; private set; }
public GeometryExtractionResult(RhinoDoc doc, IEnumerable<IGeometryInfo> geometry)
{
Document = doc;
Geometry = new List<IGeometryInfo>(geometry);
}
public string CalcGeometryHash()
{
var sources = Geometry.GroupBy(x => x.SourceObjectId).Select(x => new { SourceObjectId = x.Key, SourceGeometry = x.First().SourceGeometry, LayerPath = x.Select(y => y.LayerPath).Distinct().Single() });
if (!sources.Any())
return null;
return RhinoUtil.CalculateGeometryHash(sources.Select(x => (x.SourceGeometry, x.SourceObjectId, x.LayerPath)));
}
public void Dispose()
{
Document?.Dispose();
foreach (var geometry in Geometry)
{
geometry?.Dispose();
}
}
}
public class BrepInfo : IGeometryInfo, IDisposable
{
public readonly Brep Brep;
public int LayerIndex { get; private set; }
public string LayerPath { get; private set; }
public Guid ObjectId { get; private set; }
public Guid SourceObjectId { get; private set; }
public GeometryBase SourceGeometry { get; private set; }
public BrepInfo(Brep brep, GeometryBase sourceGeometry, int layerId, string layerPath, Guid objectId, Guid sourceObjectId)
{
this.Brep = brep.DuplicateBrep();
this.LayerIndex = layerId;
this.ObjectId = objectId;
this.LayerPath = layerPath;
this.SourceObjectId = sourceObjectId;
this.SourceGeometry = sourceGeometry.Duplicate();
}
public void Dispose()
{
Brep?.Dispose();
SourceGeometry?.Dispose();
}
}