diff --git a/src/NwcIO/Internal/Export/IfcExporter.cs b/src/NwcIO/Internal/Export/IfcExporter.cs index 6f84abc..277b3d3 100644 --- a/src/NwcIO/Internal/Export/IfcExporter.cs +++ b/src/NwcIO/Internal/Export/IfcExporter.cs @@ -210,7 +210,7 @@ public static void Write( // Export properties if requested if (options.ExportProperties) { - WriteMeshProperties(w, elemId, mesh, ownerHistoryId); + WriteMeshProperties(w, elemId, mesh, ownerHistoryId, vertices, scale); } } @@ -437,7 +437,7 @@ public static void WriteAssembly( if (options.ExportProperties) { - WriteMeshProperties(w, elemId, mesh, ownerHistoryId); + WriteMeshProperties(w, elemId, mesh, ownerHistoryId, vertices, scale); } } @@ -587,7 +587,13 @@ private static int AddIfcProduct( }; } - private static void WriteMeshProperties(StepWriter w, int elementId, NwcMesh mesh, int ownerHistoryId) + private static void WriteMeshProperties( + StepWriter w, + int elementId, + NwcMesh mesh, + int ownerHistoryId, + IReadOnlyList vertices, + double scale) { var propIds = new List(); @@ -610,14 +616,21 @@ private static void WriteMeshProperties(StepWriter w, int elementId, NwcMesh mes propIds.Add(pMat); } - if (mesh.BoundingBox is { } bbox && !bbox.IsEmpty) + var bounds = BoundingBox3d.Empty; + foreach (var vertex in vertices) + { + bounds = bounds.Union(vertex); + } + + if (!bounds.IsEmpty) { - int pDimX = w.Add("IFCPROPERTYSINGLEVALUE", - $"'Length_X',$,IFCREAL({StepWriter.Real(bbox.Size.X)}),$"); - int pDimY = w.Add("IFCPROPERTYSINGLEVALUE", - $"'Width_Y',$,IFCREAL({StepWriter.Real(bbox.Size.Y)}),$"); - int pDimZ = w.Add("IFCPROPERTYSINGLEVALUE", - $"'Height_Z',$,IFCREAL({StepWriter.Real(bbox.Size.Z)}),$"); + var size = bounds.Size * scale; + var pDimX = w.Add("IFCPROPERTYSINGLEVALUE", + $"'Length_X',$,IFCLENGTHMEASURE({StepWriter.Real(size.X)}),$"); + var pDimY = w.Add("IFCPROPERTYSINGLEVALUE", + $"'Width_Y',$,IFCLENGTHMEASURE({StepWriter.Real(size.Y)}),$"); + var pDimZ = w.Add("IFCPROPERTYSINGLEVALUE", + $"'Height_Z',$,IFCLENGTHMEASURE({StepWriter.Real(size.Z)}),$"); propIds.Add(pDimX); propIds.Add(pDimY); propIds.Add(pDimZ); diff --git a/tests/NwcIO.Tests/IfcExportTests.cs b/tests/NwcIO.Tests/IfcExportTests.cs index 89eaf19..f0571c9 100644 --- a/tests/NwcIO.Tests/IfcExportTests.cs +++ b/tests/NwcIO.Tests/IfcExportTests.cs @@ -1,4 +1,5 @@ using NwcIO.Model; +using NwcIO.Model.Sectioning; using Xunit; namespace NwcIO.Tests; @@ -217,6 +218,97 @@ public void SaveIfc_WithMetreUnits_ScalesCoordinates() Assert.Contains("1.", ifcM); } + [Theory] + [InlineData(false, NwcIfcLengthUnit.Millimetre, true, "1000.")] + [InlineData(true, NwcIfcLengthUnit.Millimetre, true, "1000.")] + [InlineData(false, NwcIfcLengthUnit.Metre, true, "1.")] + [InlineData(true, NwcIfcLengthUnit.Metre, true, "1.")] + [InlineData(false, NwcIfcLengthUnit.Metre, false, "1000.")] + [InlineData(true, NwcIfcLengthUnit.Metre, false, "1000.")] + public void SaveIfc_Dimensions_UseExportedLengthUnits( + bool assembly, NwcIfcLengthUnit unit, bool convert, string expected) + { + var doc = CreateSyntheticDocument(); + var ifc = ExportDimensions(doc, assembly, new NwcIfcExportOptions + { + LengthUnit = unit, + ConvertMmToMetres = convert, + }); + + AssertDimensions(ifc, expected, expected, expected); + } + + [Theory] + [InlineData(false, true)] + [InlineData(true, true)] + [InlineData(false, false)] + [InlineData(true, false)] + public void SaveIfc_Dimensions_UseSelectedCoordinateSpace(bool assembly, bool worldCoordinates) + { + var doc = CreateSyntheticDocument(); + doc.Meshes[0].Transform = new NwcTransform + { + Id = 1, + Translation = new Vector3d(5000, -3000, 2000), + // Rotate the cube 45 degrees about Z, increasing its X and Y extents. + Rotation = new Quaterniond(0, 0, Math.Sin(Math.PI / 8), Math.Cos(Math.PI / 8)), + }; + var ifc = ExportDimensions(doc, assembly, new NwcIfcExportOptions + { + WorldCoordinates = worldCoordinates, + LengthUnit = NwcIfcLengthUnit.Metre, + ConvertMmToMetres = true, + }); + + foreach (var name in new[] { "Length_X", "Width_Y", "Height_Z" }) + { + var match = System.Text.RegularExpressions.Regex.Match( + ifc, $@"IFCPROPERTYSINGLEVALUE\('{name}',\$,IFCLENGTHMEASURE\(([^)]+)\),\$\)"); + Assert.True(match.Success, $"Missing length measure for {name}"); + var actual = double.Parse(match.Groups[1].Value, System.Globalization.CultureInfo.InvariantCulture); + var expected = worldCoordinates && name != "Height_Z" ? Math.Sqrt(2) : 1; + Assert.Equal(expected, actual, 6); + } + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void SaveIfc_Dimensions_RetainPostClipExtents(bool assembly) + { + var doc = CreateSyntheticDocument(); + var ifc = ExportDimensions(doc, assembly, new NwcIfcExportOptions + { + LengthUnit = NwcIfcLengthUnit.Metre, + ConvertMmToMetres = true, + SectionPlanes = new[] { new NwcSectionPlane { Normal = new Vector3d(1, 0, 0), Distance = 250 } }, + }); + + AssertDimensions(ifc, "0.75", "1.", "1."); + } + + private static string ExportDimensions(NwcDocument doc, bool assembly, NwcIfcExportOptions options) + { + using var sw = new StringWriter(); + if (assembly) + { + NwcAssembly.Create("Dimensions", doc).SaveIfc(sw, options); + } + else + { + doc.SaveIfc(sw, options); + } + + return sw.ToString(); + } + + private static void AssertDimensions(string ifc, string x, string y, string z) + { + Assert.Contains($"IFCPROPERTYSINGLEVALUE('Length_X',$,IFCLENGTHMEASURE({x}),$)", ifc); + Assert.Contains($"IFCPROPERTYSINGLEVALUE('Width_Y',$,IFCLENGTHMEASURE({y}),$)", ifc); + Assert.Contains($"IFCPROPERTYSINGLEVALUE('Height_Z',$,IFCLENGTHMEASURE({z}),$)", ifc); + } + [Fact] public void SaveIfc_SharedVerticesTrue_EmitsExactlyUniqueMeshPoints() {