From 1d457f0465edaa5f9583167f4c23432b0c4881e0 Mon Sep 17 00:00:00 2001 From: ifcapps Date: Tue, 18 Feb 2025 17:51:52 +0200 Subject: [PATCH 01/23] replace deprecated std::wstring_convert, close shape in Extrude() --- .gitignore | 1 + src/cpp/web-ifc/geometry/nurbs.h | 8 +- .../geometry/operations/geometryutils.h | 17 ++- src/cpp/web-ifc/parsing/string_parsing.cpp | 109 ++++++++++++++++-- 4 files changed, 115 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index 23f43607f..2290e1444 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,4 @@ examples/nodejs/*.ifc tests/ifcfiles/private/*.ifc tests/ifcfiles/private/*.stl tests/ifcfiles/created.ifc +src/cpp/_deps/ diff --git a/src/cpp/web-ifc/geometry/nurbs.h b/src/cpp/web-ifc/geometry/nurbs.h index de7f642d9..fe4c11e0c 100644 --- a/src/cpp/web-ifc/geometry/nurbs.h +++ b/src/cpp/web-ifc/geometry/nurbs.h @@ -13,10 +13,10 @@ namespace tinynurbs{ namespace webifc::geometry{ - class IfcGeometry; - class IfcBound3D; - class BSpline; - class IfcSurface; + struct IfcGeometry; + struct IfcBound3D; + struct BSpline; + struct IfcSurface; constexpr double rotations { 6.0 }; constexpr auto pi {glm::pi()}; diff --git a/src/cpp/web-ifc/geometry/operations/geometryutils.h b/src/cpp/web-ifc/geometry/operations/geometryutils.h index f8b4565b8..a4b989e51 100644 --- a/src/cpp/web-ifc/geometry/operations/geometryutils.h +++ b/src/cpp/web-ifc/geometry/operations/geometryutils.h @@ -152,7 +152,9 @@ namespace webifc::geometry // this is bad news, as it nans the points added to the final mesh // also, it's hard to bail out now :/ // see curve.add() for more info on how this is currently "solved" +#if defined(_DEBUG) printf("NaN perp!\n"); +#endif } glm::dvec3 u1 = glm::normalize(glm::cross(n1, p)); @@ -363,7 +365,9 @@ namespace webifc::geometry // this is bad news, as it nans the points added to the final mesh // also, it's hard to bail out now :/ // see curve.add() for more info on how this is currently "solved" +#if defined(_DEBUG) printf("NaN perp!\n"); +#endif } glm::dvec3 u1 = glm::normalize(glm::cross(n1, p)); @@ -931,19 +935,22 @@ namespace webifc::geometry } uint32_t capSize = profile.curve.points.size(); - for (size_t i = 1; i < capSize; i++) + for (size_t i = 1; i <= capSize; i++) { // https://github.com/tomvandig/web-ifc/issues/5 - if (holesIndicesHash[i]) + if (i < capSize) { - continue; + if (holesIndicesHash[i]) + { + continue; + } } uint32_t bl = i - 1; - uint32_t br = i - 0; + uint32_t br = i % capSize; uint32_t tl = capSize + i - 1; - uint32_t tr = capSize + i - 0; + uint32_t tr = capSize + i % capSize; // this winding should be correct geom.AddFace(geom.GetPoint(tl), diff --git a/src/cpp/web-ifc/parsing/string_parsing.cpp b/src/cpp/web-ifc/parsing/string_parsing.cpp index ec719ca50..9e7becc3a 100644 --- a/src/cpp/web-ifc/parsing/string_parsing.cpp +++ b/src/cpp/web-ifc/parsing/string_parsing.cpp @@ -15,9 +15,44 @@ namespace webifc::parsing { bool foundRoman = false; + std::u16string utf16_from_utf8(const std::string& utf8) { + std::u16string utf16; + size_t i = 0; + + while (i < utf8.size()) { + char16_t ch = 0; + unsigned char byte = utf8[i]; + + if (byte < 0x80) { + // 1-byte character (ASCII) + ch = byte; + i += 1; + } + else if ((byte & 0xE0) == 0xC0) { + // 2-byte character + if (i + 1 >= utf8.size()) throw std::runtime_error("Invalid UTF-8 sequence"); + ch = ((byte & 0x1F) << 6) | (utf8[i + 1] & 0x3F); + i += 2; + } + else if ((byte & 0xF0) == 0xE0) { + // 3-byte character + if (i + 2 >= utf8.size()) throw std::runtime_error("Invalid UTF-8 sequence"); + ch = ((byte & 0x0F) << 12) | ((utf8[i + 1] & 0x3F) << 6) | (utf8[i + 2] & 0x3F); + i += 3; + } + else { + throw std::runtime_error("Unsupported UTF-8 sequence"); + } + + utf16.push_back(ch); + } + + return utf16; + } + void encodeCharacters(std::ostringstream &stream,std::string &data) { - std::u16string utf16 = std::wstring_convert, char16_t>{}.from_bytes(data.data()); + std::u16string utf16 = utf16_from_utf8(data); stream << "\\X2\\" << std::hex <(uC); stream << std::dec<< std::setw(0) << "\\X0\\"; @@ -53,6 +88,60 @@ namespace webifc::parsing { if (inEncode) encodeCharacters(output,tmp); } + std::string utf8_from_utf16(const std::u16string& u16str) { + std::string utf8; + for (char16_t ch : u16str) { + if (ch < 0x80) { + // 1-byte character + utf8.push_back(static_cast(ch)); + } + else if (ch < 0x800) { + // 2-byte character + utf8.push_back(static_cast(0xC0 | (ch >> 6))); + utf8.push_back(static_cast(0x80 | (ch & 0x3F))); + } + else { + // 3-byte character + utf8.push_back(static_cast(0xE0 | (ch >> 12))); + utf8.push_back(static_cast(0x80 | ((ch >> 6) & 0x3F))); + utf8.push_back(static_cast(0x80 | (ch & 0x3F))); + } + } + return utf8; + } + + std::string utf8_from_utf32(const std::u32string& u32str) { + std::string utf8; + for (char32_t ch : u32str) { + if (ch < 0x80) { + // 1-byte character + utf8.push_back(static_cast(ch)); + } + else if (ch < 0x800) { + // 2-byte character + utf8.push_back(static_cast(0xC0 | (ch >> 6))); + utf8.push_back(static_cast(0x80 | (ch & 0x3F))); + } + else if (ch < 0x10000) { + // 3-byte character + utf8.push_back(static_cast(0xE0 | (ch >> 12))); + utf8.push_back(static_cast(0x80 | ((ch >> 6) & 0x3F))); + utf8.push_back(static_cast(0x80 | (ch & 0x3F))); + } + else if (ch <= 0x10FFFF) { + // 4-byte character + utf8.push_back(static_cast(0xF0 | (ch >> 18))); + utf8.push_back(static_cast(0x80 | ((ch >> 12) & 0x3F))); + utf8.push_back(static_cast(0x80 | ((ch >> 6) & 0x3F))); + utf8.push_back(static_cast(0x80 | (ch & 0x3F))); + } + else { + throw std::runtime_error("Invalid UTF-32 code point"); + } + } + return utf8; + } + struct P21Decoder { public: @@ -95,15 +184,14 @@ namespace webifc::parsing { { char d1 = getNextHex(); char d2 = getNextHex(); - char str[2]; - str[0] = (d1 << 4) | d2; - str[1] = 0; - auto cA = reinterpret_cast(str); + char str2[2]; + str2[0] = (d1 << 4) | d2; + str2[1] = 0; + auto cA = reinterpret_cast(str2); if (cA[0] >= 0x80 && cA[0] <= 0x9F) foundRoman = true; if (foundRoman) cA[0]=checkRomanEncoding(cA[0]); std::u16string u16str(cA, 1); - std::wstring_convert,char16_t> convert; - std::string utf8 = convert.to_bytes(u16str); + std::string utf8 = utf8_from_utf16(u16str); std::copy(utf8.begin(), utf8.end(), std::back_inserter(result)); break; } @@ -237,8 +325,7 @@ namespace webifc::parsing { bytes[i+1] = c; } std::u16string u16str(reinterpret_cast(&bytes[0]), bytes.size() / 2); - std::wstring_convert,char16_t> convert; - utf8 = convert.to_bytes(u16str); + utf8 = utf8_from_utf16(u16str); } else if (T == 4) { @@ -251,8 +338,8 @@ namespace webifc::parsing { bytes[i+2] = c; } std::u32string u32str(reinterpret_cast(&bytes[0]), bytes.size() / 4); - std::wstring_convert,char32_t> convert; - utf8 = convert.to_bytes(u32str); + + utf8 = utf8_from_utf32(u32str); } std::copy(utf8.begin(), utf8.end(), std::back_inserter(result)); } From 3241f7a4c1f4b99ead2d43e20765a89463d3d559 Mon Sep 17 00:00:00 2001 From: ifcapps Date: Tue, 18 Feb 2025 18:11:09 +0200 Subject: [PATCH 02/23] In Extrude(), check if first point is equal to last point, otherwise the outer loop of the shape is not closed --- .../geometry/operations/geometryutils.h | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/cpp/web-ifc/geometry/operations/geometryutils.h b/src/cpp/web-ifc/geometry/operations/geometryutils.h index a4b989e51..e3a8808a3 100644 --- a/src/cpp/web-ifc/geometry/operations/geometryutils.h +++ b/src/cpp/web-ifc/geometry/operations/geometryutils.h @@ -824,6 +824,12 @@ namespace webifc::geometry IfcGeometry geom; std::vector holesIndicesHash; + // check if first point is equal to last point, otherwise the outer loop of the shape is not closed + glm::dvec3 lastToFirstPoint = profile.curve.points.front() - profile.curve.points.back(); + if (glm::length(lastToFirstPoint) > 1e-8) { + profile.curve.points.push_back(profile.curve.points.front()); + } + // build the caps { using Point = std::array; @@ -935,31 +941,28 @@ namespace webifc::geometry } uint32_t capSize = profile.curve.points.size(); - for (size_t i = 1; i <= capSize; i++) + for (size_t i = 1; i < capSize; i++) { // https://github.com/tomvandig/web-ifc/issues/5 - if (i < capSize) + if (holesIndicesHash[i]) { - if (holesIndicesHash[i]) - { - continue; - } + continue; } uint32_t bl = i - 1; - uint32_t br = i % capSize; + uint32_t br = i - 0; uint32_t tl = capSize + i - 1; - uint32_t tr = capSize + i % capSize; + uint32_t tr = capSize + i - 0; // this winding should be correct geom.AddFace(geom.GetPoint(tl), - geom.GetPoint(br), - geom.GetPoint(bl)); + geom.GetPoint(br), + geom.GetPoint(bl)); geom.AddFace(geom.GetPoint(tl), - geom.GetPoint(tr), - geom.GetPoint(br)); + geom.GetPoint(tr), + geom.GetPoint(br)); } return geom; From 1f31adb31b7c2d91f9c93ae48c788fd9b58abb82 Mon Sep 17 00:00:00 2001 From: ifcapps Date: Tue, 18 Feb 2025 18:13:33 +0200 Subject: [PATCH 03/23] add tabs to make diff cleaner --- src/cpp/web-ifc/geometry/operations/geometryutils.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/cpp/web-ifc/geometry/operations/geometryutils.h b/src/cpp/web-ifc/geometry/operations/geometryutils.h index e3a8808a3..ef25a8b20 100644 --- a/src/cpp/web-ifc/geometry/operations/geometryutils.h +++ b/src/cpp/web-ifc/geometry/operations/geometryutils.h @@ -957,12 +957,12 @@ namespace webifc::geometry // this winding should be correct geom.AddFace(geom.GetPoint(tl), - geom.GetPoint(br), - geom.GetPoint(bl)); + geom.GetPoint(br), + geom.GetPoint(bl)); geom.AddFace(geom.GetPoint(tl), - geom.GetPoint(tr), - geom.GetPoint(br)); + geom.GetPoint(tr), + geom.GetPoint(br)); } return geom; From a891211ea420e8acab9a6fa9c52c143e0150df02 Mon Sep 17 00:00:00 2001 From: ifcapps Date: Tue, 18 Feb 2025 18:41:45 +0200 Subject: [PATCH 04/23] enable polylines as mesh for IFCGRID. add bounds checks - enable polylines as mesh for IFCGRID, IFCGRIDAXIS - add bounds checks in TriangulateRevolution --- .../web-ifc/geometry/IfcGeometryProcessor.cpp | 37 ++++++++++++++++++- .../web-ifc/geometry/operations/mesh_utils.h | 21 +++++++++-- .../geometry/representation/IfcGeometry.h | 3 +- 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp b/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp index 7668dbfb0..8c1576faf 100644 --- a/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp +++ b/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp @@ -984,6 +984,7 @@ namespace webifc::geometry return mesh; } case schema::IFCGEOMETRICSET: + case schema::IFCGEOMETRICCURVESET: { _loader.MoveToArgumentOffset(expressID, 0); auto items = _loader.GetSetArgument(); @@ -996,11 +997,43 @@ namespace webifc::geometry return mesh; } + case schema::IFCBOUNDINGBOX: + // ignore bounding box + return mesh; + case schema::IFCCIRCLE: case schema::IFCPOLYLINE: case schema::IFCINDEXEDPOLYCURVE: case schema::IFCTRIMMEDCURVE: - // ignore polylines as meshes - return mesh; + { + // could be + // #1324059= IFCGRID('3Yls6_O6GJNv5JAJ8l_3XY',#35,'Raster 001',$,$,#1323916,#1324056,(#1323917),$,$); + // #1323917= IFCGRIDAXIS('7',#1323926,.T.); + // #1323926= IFCINDEXEDPOLYCURVE(#1323922,(IFCLINEINDEX((1,2))),$); + auto lineProfileType = _loader.GetLineType(expressID); + IfcCurve curve = _geometryLoader.GetCurve(expressID, 3, false); + + if (curve.points.size() > 0) { + IfcGeometry geom; + + for (uint32_t i = 0; i < curve.points.size(); i++) + { + auto vert = curve.points[i]; + geom.vertexData.push_back(vert.x); + geom.vertexData.push_back(vert.y); + geom.vertexData.push_back(vert.z); + geom.vertexData.push_back(0); // needs to be 6 values per vertex + geom.vertexData.push_back(0); + geom.vertexData.push_back(1); + geom.indexData.push_back(i); + } + geom.numPoints = curve.points.size(); + geom.isPolygon = true; + mesh.hasGeometry = true; + _expressIDToGeometry[expressID] = geom; + } + + return mesh; + } default: spdlog::error("[GetMesh()] unexpected mesh type {}", expressID, lineType); break; diff --git a/src/cpp/web-ifc/geometry/operations/mesh_utils.h b/src/cpp/web-ifc/geometry/operations/mesh_utils.h index a7481c196..d1e9b7b64 100644 --- a/src/cpp/web-ifc/geometry/operations/mesh_utils.h +++ b/src/cpp/web-ifc/geometry/operations/mesh_utils.h @@ -184,10 +184,23 @@ namespace webifc::geometry for (int r = 0; r < numRots - 1; r++) { int r1 = r + 1; - for (size_t s = 0; s < newPoints[r].size() - 1; s++) - { - geometry.AddFace(newPoints[r][s], newPoints[r][s + 1], newPoints[r1][s]); - geometry.AddFace(newPoints[r1][s], newPoints[r][s + 1], newPoints[r1][s + 1]); + if (r1 >= newPoints.size()) { + break; + } + const std::vector& newPointsR = newPoints[r]; + const std::vector& newPointsR1 = newPoints[r1]; + if (newPointsR.size() > 0) { + for (size_t s = 0; s < newPointsR.size() - 1; s++) + { + if (s + 1 >= newPointsR.size()) { + break; + } + if (s + 1 >= newPointsR1.size()) { + break; + } + geometry.AddFace(newPointsR[s], newPointsR[s + 1], newPointsR1[s]); + geometry.AddFace(newPointsR1[s], newPointsR[s + 1], newPointsR1[s + 1]); + } } } } diff --git a/src/cpp/web-ifc/geometry/representation/IfcGeometry.h b/src/cpp/web-ifc/geometry/representation/IfcGeometry.h index 3186c134d..1ff3c5d53 100644 --- a/src/cpp/web-ifc/geometry/representation/IfcGeometry.h +++ b/src/cpp/web-ifc/geometry/representation/IfcGeometry.h @@ -56,7 +56,8 @@ namespace webifc::geometry { std::vector indexData; std::vector planeData; std::vector planes; - + + bool isPolygon = false; bool hasPlanes = false; uint32_t numPoints = 0; uint32_t numFaces = 0; From 9c1d98e0d102d3de445b4fb1daa4397edbfc66c9 Mon Sep 17 00:00:00 2001 From: ifcapps Date: Tue, 18 Feb 2025 19:11:05 +0200 Subject: [PATCH 05/23] make scaling mehses optional make scaling by _geometryLoader.GetLinearScalingFactor() optional in GetFlatMesh. Alternative would be: make IfcGeometryProcessor::AddComposedMeshToFlatMesh public --- src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp | 8 ++++++-- src/cpp/web-ifc/geometry/IfcGeometryProcessor.h | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp b/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp index 8c1576faf..2f7cbe66b 100644 --- a/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp +++ b/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp @@ -1431,7 +1431,7 @@ namespace webifc::geometry return IfcSurface(); } - IfcFlatMesh IfcGeometryProcessor::GetFlatMesh(uint32_t expressID) + IfcFlatMesh IfcGeometryProcessor::GetFlatMesh(uint32_t expressID, bool applyLinearScalingFactor) { spdlog::debug("[GetFlatMesh({})]",expressID); IfcFlatMesh flatMesh; @@ -1439,7 +1439,11 @@ namespace webifc::geometry IfcComposedMesh composedMesh = GetMesh(expressID); - glm::dmat4 mat = glm::scale(glm::dvec3(_geometryLoader.GetLinearScalingFactor())); + glm::dmat4 mat = glm::dmat4(1); + if (applyLinearScalingFactor) + { + mat = glm::scale(glm::dvec3(_geometryLoader.GetLinearScalingFactor()));; + } AddComposedMeshToFlatMesh(flatMesh, composedMesh, _transformation * NormalizeIFC * mat); diff --git a/src/cpp/web-ifc/geometry/IfcGeometryProcessor.h b/src/cpp/web-ifc/geometry/IfcGeometryProcessor.h index 7b9652daa..7a93db8fe 100644 --- a/src/cpp/web-ifc/geometry/IfcGeometryProcessor.h +++ b/src/cpp/web-ifc/geometry/IfcGeometryProcessor.h @@ -39,7 +39,7 @@ namespace webifc::geometry IfcGeometryProcessor(const webifc::parsing::IfcLoader &loader,const webifc::schema::IfcSchemaManager &schemaManager,uint16_t circleSegments,bool coordinateToOrigin); IfcGeometry &GetGeometry(uint32_t expressID); IfcGeometryLoader GetLoader() const; - IfcFlatMesh GetFlatMesh(uint32_t expressID); + IfcFlatMesh GetFlatMesh(uint32_t expressID, bool applyLinearScalingFactor = true); IfcComposedMesh GetMesh(uint32_t expressID); void SetTransformation(const std::array &val); std::array GetFlatCoordinationMatrix() const; From fc1a599d4be00b3ed72b800fc6d0e1ad8cefffd1 Mon Sep 17 00:00:00 2001 From: ifcapps Date: Tue, 25 Feb 2025 15:48:58 +0200 Subject: [PATCH 06/23] in VectorToAngle: add check for zero, to avoid inf values in mesh --- src/cpp/web-ifc/geometry/operations/geometryutils.h | 3 +++ src/cpp/web-ifc/schema/IfcSchemaManager.h | 1 + 2 files changed, 4 insertions(+) diff --git a/src/cpp/web-ifc/geometry/operations/geometryutils.h b/src/cpp/web-ifc/geometry/operations/geometryutils.h index ef25a8b20..6d9e7df56 100644 --- a/src/cpp/web-ifc/geometry/operations/geometryutils.h +++ b/src/cpp/web-ifc/geometry/operations/geometryutils.h @@ -1000,6 +1000,9 @@ namespace webifc::geometry inline double VectorToAngle(double x, double y) { double dd = sqrt(x * x + y * y); + if (std::abs(dd) < EPS_MINISCULE) { + return 0; + } double xx = x / dd; double yy = y / dd; diff --git a/src/cpp/web-ifc/schema/IfcSchemaManager.h b/src/cpp/web-ifc/schema/IfcSchemaManager.h index f2d052489..79f756f94 100644 --- a/src/cpp/web-ifc/schema/IfcSchemaManager.h +++ b/src/cpp/web-ifc/schema/IfcSchemaManager.h @@ -6,6 +6,7 @@ #include "ifc-schema.h" #include +#include #include #include #include From 398041443f0fd2658ad68fb02e7f52b004112861 Mon Sep 17 00:00:00 2001 From: ifcapps Date: Fri, 28 Feb 2025 20:41:49 +0200 Subject: [PATCH 07/23] add IFCTOPOLOGYREPRESENTATION, add IFCFACESURFACE, IFCEDGE and IFCCARTESIANPOINT as geometric item IFCFACESURFACE, IFCEDGE and IFCCARTESIANPOINT are derived from IfcRepresentationItem, so they could appear as geometric item In IfcGeometryLoader::GetColor: add check for curveColour because it is optional --- .../web-ifc/geometry/IfcGeometryLoader.cpp | 26 ++++- .../web-ifc/geometry/IfcGeometryProcessor.cpp | 103 +++++++++++++++++- src/cpp/web-ifc/parsing/IfcLoader.cpp | 3 +- 3 files changed, 124 insertions(+), 8 deletions(-) diff --git a/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp b/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp index e165bda6a..e5a2a837d 100644 --- a/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp +++ b/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp @@ -909,9 +909,15 @@ namespace webifc::geometry case schema::IFCCURVESTYLE: { _loader.MoveToArgumentOffset(expressID, 3); - auto foundColor = GetColor(_loader.GetRefArgument()); - if (foundColor) - return foundColor; + // argument 3 (CurveColour) is optional, so check if it is set + auto tt = _loader.GetTokenType(); + if (tt == parsing::REF) + { + _loader.StepBack(); + auto foundColor = GetColor(_loader.GetRefArgument()); + if (foundColor) + return foundColor; + } return {}; } case schema::IFCFILLAREASTYLEHATCHING: @@ -1322,6 +1328,20 @@ namespace webifc::geometry switch (lineType) { + + case schema::IFCEDGE: + { + _loader.MoveToArgumentOffset(expressID, 0); + glm::dvec3 p1 = GetVertexPoint(_loader.GetRefArgument()); + _loader.MoveToArgumentOffset(expressID, 1); + glm::dvec3 p2 = GetVertexPoint(_loader.GetRefArgument()); + + IfcCurve curve; + curve.points.push_back(p1); + curve.points.push_back(p2); + + return curve; + } case schema::IFCEDGECURVE: { IfcTrimmingArguments ts; diff --git a/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp b/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp index df044d2f8..f77afb470 100644 --- a/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp +++ b/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp @@ -641,8 +641,10 @@ namespace webifc::geometry return mesh; } + case schema::IFCTOPOLOGYREPRESENTATION: case schema::IFCSHAPEREPRESENTATION: { + // IFCTOPOLOGYREPRESENTATION and IFCSHAPEREPRESENTATION are identical in attributes layout _loader.MoveToArgumentOffset(expressID, 1); auto type = _loader.GetStringArgument(); @@ -695,6 +697,54 @@ namespace webifc::geometry return mesh; } + case schema::IFCFACESURFACE: + { + IfcGeometry geometry; + _loader.MoveToArgumentOffset(expressID, 0); + auto bounds = _loader.GetSetArgument(); + + std::vector bounds3D(bounds.size()); + + for (size_t i = 0; i < bounds.size(); i++) + { + uint32_t boundID = _loader.GetRefArgument(bounds[i]); + bounds3D[i] = _geometryLoader.GetBound(boundID); + } + + TriangulateBounds(geometry, bounds3D, expressID); + + _loader.MoveToArgumentOffset(expressID, 1); + auto surfRef = _loader.GetRefArgument(); + + auto surface = GetSurface(surfRef); + + if (surface.BSplineSurface.Active) + { + TriangulateBspline(geometry, bounds3D, surface, _geometryLoader.GetLinearScalingFactor()); + } + else if (surface.CylinderSurface.Active) + { + TriangulateCylindricalSurface(geometry, bounds3D, surface, _circleSegments); + } + else if (surface.RevolutionSurface.Active) + { + TriangulateRevolution(geometry, bounds3D, surface, _circleSegments); + } + else if (surface.ExtrusionSurface.Active) + { + TriangulateExtrusion(geometry, bounds3D, surface); + } + else + { + TriangulateBounds(geometry, bounds3D, expressID); + } + + _expressIDToGeometry[expressID] = geometry; + mesh.expressID = expressID; + mesh.hasGeometry = true; + + break; + } case schema::IFCTRIANGULATEDIRREGULARNETWORK: case schema::IFCTRIANGULATEDFACESET: { @@ -1031,15 +1081,56 @@ namespace webifc::geometry case schema::IFCBOUNDINGBOX: // ignore bounding box return mesh; + + case schema::IFCCARTESIANPOINT: + { + // IfcCartesianPoint is derived from IfcRepresentationItem and can be used as representation item directly + IfcGeometry geom; + auto point = _geometryLoader.GetCartesianPoint3D(expressID); + geom.vertexData.push_back(point.x); + geom.vertexData.push_back(point.y); + geom.vertexData.push_back(point.z); + geom.vertexData.push_back(0); // needs to be 6 values per vertex + geom.vertexData.push_back(0); + geom.vertexData.push_back(1); + geom.indexData.push_back(0); + + geom.numPoints = 1; + geom.isPolygon = true; + mesh.hasGeometry = true; + _expressIDToGeometry[expressID] = geom; + + return mesh; + } + case schema::IFCEDGE: + { + // IfcEdge is derived from IfcRepresentationItem and can be used as representation item directly + IfcCurve edge = _geometryLoader.GetEdge(expressID); + IfcGeometry geom; + + for (uint32_t i = 0; i < edge.points.size(); i++) + { + auto vert = edge.points[i]; + geom.vertexData.push_back(vert.x); + geom.vertexData.push_back(vert.y); + geom.vertexData.push_back(vert.z); + geom.vertexData.push_back(0); // needs to be 6 values per vertex + geom.vertexData.push_back(0); + geom.vertexData.push_back(1); + geom.indexData.push_back(i); + } + geom.numPoints = edge.points.size(); + geom.isPolygon = true; + mesh.hasGeometry = true; + _expressIDToGeometry[expressID] = geom; + + return mesh; + } case schema::IFCCIRCLE: case schema::IFCPOLYLINE: case schema::IFCINDEXEDPOLYCURVE: case schema::IFCTRIMMEDCURVE: { - // could be - // #1324059= IFCGRID('3Yls6_O6GJNv5JAJ8l_3XY',#35,'Raster 001',$,$,#1323916,#1324056,(#1323917),$,$); - // #1323917= IFCGRIDAXIS('7',#1323926,.T.); - // #1323926= IFCINDEXEDPOLYCURVE(#1323922,(IFCLINEINDEX((1,2))),$); auto lineProfileType = _loader.GetLineType(expressID); IfcCurve curve = _geometryLoader.GetCurve(expressID, 3, false); @@ -1065,6 +1156,10 @@ namespace webifc::geometry return mesh; } + case schema::IFCTEXTLITERAL: + case schema::IFCTEXTLITERALWITHEXTENT: + // TODO: save string of the text literal in IfcComposedMesh + return mesh; default: spdlog::error("[GetMesh()] unexpected mesh type {}", expressID, lineType); break; diff --git a/src/cpp/web-ifc/parsing/IfcLoader.cpp b/src/cpp/web-ifc/parsing/IfcLoader.cpp index 196218af7..9970f1d9e 100644 --- a/src/cpp/web-ifc/parsing/IfcLoader.cpp +++ b/src/cpp/web-ifc/parsing/IfcLoader.cpp @@ -666,7 +666,8 @@ namespace webifc::parsing { if (t==SET_BEGIN) { StepBack(); GetSetArgument(); - continue; + noArguments++; + continue; } if (t == IfcTokenType::STRING || t == IfcTokenType::INTEGER || t == IfcTokenType::REAL || t == IfcTokenType::LABEL || t == IfcTokenType::ENUM) { uint16_t length = _tokenStream->Read(); From e62a5930e5b5c9773177b9ce74ee5fbfaeab79e5 Mon Sep 17 00:00:00 2001 From: ifcapps Date: Thu, 6 Mar 2025 11:24:07 +0100 Subject: [PATCH 08/23] adjustable number of output curve points in BSpline curves - add number of output curve points in BSpline curves as parameter instead of a fixed number of 20. - add some checks in Nurbs::init() to prevent crashes in case the Nurbs surface is not well defined in the IFC model. --- .../web-ifc/geometry/IfcGeometryLoader.cpp | 9 +- src/cpp/web-ifc/geometry/nurbs.cpp | 99 +++++++++++++++++-- src/cpp/web-ifc/geometry/nurbs.h | 1 + .../web-ifc/geometry/operations/curve-utils.h | 7 +- 4 files changed, 102 insertions(+), 14 deletions(-) diff --git a/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp b/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp index e5a2a837d..e5ac78e43 100644 --- a/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp +++ b/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp @@ -2131,7 +2131,8 @@ namespace webifc::geometry ctrolPts.push_back(GetCartesianPoint3D(pointId)); } - std::vector tempPoints = GetRationalBSplineCurveWithKnots(degree, ctrolPts, knots, weights); + double numCurvePoints = ctrolPts.size(); + std::vector tempPoints = GetRationalBSplineCurveWithKnots(degree, ctrolPts, knots, weights, numCurvePoints); for (size_t i = 0; i < tempPoints.size(); i++) curve.Add(tempPoints[i]); } @@ -2219,7 +2220,8 @@ namespace webifc::geometry uint32_t pointId = _loader.GetRefArgument(token); ctrolPts.push_back(GetCartesianPoint3D(pointId)); } - std::vector tempPoints = GetRationalBSplineCurveWithKnots(degree, ctrolPts, knots, weights); + double numCurvePoints = ctrolPts.size(); + std::vector tempPoints = GetRationalBSplineCurveWithKnots(degree, ctrolPts, knots, weights, numCurvePoints); for (size_t i = 0; i < tempPoints.size(); i++) curve.Add(tempPoints[i]); } @@ -2304,7 +2306,8 @@ namespace webifc::geometry ctrolPts.push_back(GetCartesianPoint3D(pointId)); } - std::vector tempPoints = GetRationalBSplineCurveWithKnots(degree, ctrolPts, knots, weights); + double numCurvePoints = ctrolPts.size(); + std::vector tempPoints = GetRationalBSplineCurveWithKnots(degree, ctrolPts, knots, weights, numCurvePoints); for (size_t i = 0; i < tempPoints.size(); i++) curve.Add(tempPoints[i]); } diff --git a/src/cpp/web-ifc/geometry/nurbs.cpp b/src/cpp/web-ifc/geometry/nurbs.cpp index da0d17a90..a50115e55 100644 --- a/src/cpp/web-ifc/geometry/nurbs.cpp +++ b/src/cpp/web-ifc/geometry/nurbs.cpp @@ -11,6 +11,9 @@ namespace webifc::geometry{ void Nurbs::fill_geometry(){ + if (!_initialized) { + return; + } auto uv_points {this->get_uv_points()}; auto indices {get_triangulation_uv_points(uv_points)}; @@ -78,14 +81,89 @@ namespace webifc::geometry{ this->init(); } - void Nurbs::init(){ + void Nurbs::init() { + // Check that the control point grid has sufficient dimensions. + // We need at least (degree + 1) control points in each direction. + if (this->num_u < static_cast(this->bspline_surface.UDegree) + 1) { + spdlog::error("Insufficient control point rows: num_u = {} but UDegree = {} requires at least {} rows", + this->num_u, this->bspline_surface.UDegree, this->bspline_surface.UDegree + 1); + return; // Or throw an exception. + } + if (this->num_v < static_cast(this->bspline_surface.VDegree) + 1) { + spdlog::error("Insufficient control point columns: num_v = {} but VDegree = {} requires at least {} columns", + this->num_v, this->bspline_surface.VDegree, this->bspline_surface.VDegree + 1); + return; // Or throw an exception. + } + + // Validate degrees. + if (this->bspline_surface.UDegree < 0 || this->bspline_surface.VDegree < 0) { + spdlog::error("Invalid degree values: UDegree={}, VDegree={}", + this->bspline_surface.UDegree, this->bspline_surface.VDegree); + return; + } + + // Validate control points count. + size_t expectedCount = this->num_u * this->num_v; + auto controlPoints = this->get_control_points(); + if (controlPoints.size() != expectedCount) { + spdlog::error("Control points count mismatch: expected {}, got {}", expectedCount, controlPoints.size()); + return; + } + auto weights = this->get_weights(); + if (weights.size() != expectedCount) { + spdlog::error("Weights count mismatch: expected {}, got {}", expectedCount, weights.size()); + return; + } + + // Create the NURBS surface. this->nurbs = std::make_shared( - static_cast(static_cast(this->bspline_surface.UDegree)), - static_cast(static_cast(this->bspline_surface.VDegree)), - this->get_knots(this->bspline_surface.UKnots, this->bspline_surface.UMultiplicity), - this->get_knots(this->bspline_surface.VKnots, this->bspline_surface.VMultiplicity), - tinynurbs::array2{this->num_u, this->num_v, this->get_control_points()}, - tinynurbs::array2{this->num_u, this->num_v, this->get_weights()}); + static_cast(this->bspline_surface.UDegree), + static_cast(this->bspline_surface.VDegree), + this->get_knots(this->bspline_surface.UKnots, this->bspline_surface.UMultiplicity), + this->get_knots(this->bspline_surface.VKnots, this->bspline_surface.VMultiplicity), + tinynurbs::array2{this->num_u, this->num_v, controlPoints}, + tinynurbs::array2{this->num_u, this->num_v, weights} + ); + + // Check that the knot vectors are large enough. + if (this->nurbs->knots_u.size() < static_cast(this->nurbs->degree_u + 1)) { + spdlog::error("Invalid knots_u: size {} is less than degree_u+1 ({})", + this->nurbs->knots_u.size(), this->nurbs->degree_u + 1); + return; + } + if (this->nurbs->knots_v.size() < static_cast(this->nurbs->degree_v + 1)) { + spdlog::error("Invalid knots_v: size {} is less than degree_v+1 ({})", + this->nurbs->knots_v.size(), this->nurbs->degree_v + 1); + return; + } + + // Helper lambda to check if a knot vector is monotonic increasing. + auto check_monotonic = [](const std::vector& knots, const std::string& name) -> bool { + for (size_t i = 1; i < knots.size(); i++) { + if (knots[i] < knots[i - 1]) { + spdlog::error("{} is not monotonic increasing at index {} ({} < {})", name, i, knots[i], knots[i - 1]); + return false; + } + } + return true; + }; + + if (!check_monotonic(this->nurbs->knots_u, "knots_u")) return; + if (!check_monotonic(this->nurbs->knots_v, "knots_v")) return; + + // Ensure that we have enough knots to set the range. + if (this->nurbs->knots_u.size() <= this->nurbs->degree_u || + this->nurbs->knots_u.size() <= this->nurbs->degree_u + 1) { + spdlog::error("Not enough knots in knots_u to determine range, size={}, degree_u={}", + this->nurbs->knots_u.size(), this->nurbs->degree_u); + return; + } + if (this->nurbs->knots_v.size() <= this->nurbs->degree_v || + this->nurbs->knots_v.size() <= this->nurbs->degree_v + 1) { + spdlog::error("Not enough knots in knots_v to determine range, size={}, degree_v={}", + this->nurbs->knots_v.size(), this->nurbs->degree_v); + return; + } this->range_knots_u = { this->nurbs->knots_u[this->nurbs->degree_u], this->nurbs->knots_u[this->nurbs->knots_u.size() - this->nurbs->degree_u - 1] @@ -94,14 +172,21 @@ namespace webifc::geometry{ this->nurbs->knots_v[this->nurbs->degree_v], this->nurbs->knots_v[this->nurbs->knots_v.size() - this->nurbs->degree_v - 1] }; + + // Compute sample surface points. this->ptc = tinynurbs::surfacePoint(*this->nurbs, 0.0, 0.0); this->pth = tinynurbs::surfacePoint(*this->nurbs, 1.0, 0.0); this->ptv = tinynurbs::surfacePoint(*this->nurbs, 0.0, 1.0); + + // Compute distances for further use. this->dh = glm::distance(ptc, pth); this->dv = glm::distance(ptc, ptv); this->pr = (dh + 1) / (dv + 1); + + // Scale error tolerances. this->minError /= this->scaling; this->maxError /= this->scaling; + _initialized = true; } std::vector Nurbs::get_weights() const{ std::vector result(this->num_u * this->num_v); diff --git a/src/cpp/web-ifc/geometry/nurbs.h b/src/cpp/web-ifc/geometry/nurbs.h index fe4c11e0c..b7cd9a22c 100644 --- a/src/cpp/web-ifc/geometry/nurbs.h +++ b/src/cpp/web-ifc/geometry/nurbs.h @@ -63,5 +63,6 @@ namespace webifc::geometry{ double dh{0.0}; double dv{0.0}; double pr{0.0}; + bool _initialized = false; }; } \ No newline at end of file diff --git a/src/cpp/web-ifc/geometry/operations/curve-utils.h b/src/cpp/web-ifc/geometry/operations/curve-utils.h index 70e135cc4..b762bc828 100644 --- a/src/cpp/web-ifc/geometry/operations/curve-utils.h +++ b/src/cpp/web-ifc/geometry/operations/curve-utils.h @@ -296,13 +296,12 @@ inline IfcCurve Build3DArc3Pt(const glm::dvec3 &p1, const glm::dvec3 &p2, const - inline std::vector GetRationalBSplineCurveWithKnots(int degree, std::vector points, std::vector knots, std::vector weights) + inline std::vector GetRationalBSplineCurveWithKnots(int degree, std::vector points, std::vector knots, std::vector weights, double numCurvePoints) { - spdlog::debug("[GetRationalBSplineCurveWithKnots({})]"); std::vector c; - - for (double i = 0; i < 1; i += 0.05) + double step = 1.0/numCurvePoints; + for (double i = 0; i < 1; i += step) { glm::dvec3 point = InterpolateRationalBSplineCurveWithKnots(i, degree, points, knots, weights); c.push_back(point); From d702ac6a9a6f029685efafbb5406ff166d9390b4 Mon Sep 17 00:00:00 2001 From: ifcapps Date: Thu, 6 Mar 2025 13:26:06 +0100 Subject: [PATCH 09/23] IfcLoader::SaveFile: option to order the STEP lines Without ordering, the lines are in a random order, which makes it hard to track issues in exported files --- src/cpp/web-ifc/parsing/IfcLoader.cpp | 14 ++++++++------ src/cpp/web-ifc/parsing/IfcLoader.h | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/cpp/web-ifc/parsing/IfcLoader.cpp b/src/cpp/web-ifc/parsing/IfcLoader.cpp index 9587e6999..1a9a948dc 100644 --- a/src/cpp/web-ifc/parsing/IfcLoader.cpp +++ b/src/cpp/web-ifc/parsing/IfcLoader.cpp @@ -76,7 +76,7 @@ namespace webifc::parsing { ParseLines(); } - void IfcLoader::SaveFile(const std::function &outputData) const + void IfcLoader::SaveFile(const std::function &outputData, bool orderLinesByExpressID) const { std::ostringstream output; output << "ISO-10303-21;"<(); std::transform( _lines.begin(), _lines.end(), std::back_inserter( *currentLines ), [](auto &kv){ return kv.second;} ); } + if (orderLinesByExpressID) { + // Sort based on tapeOffset, which preserves the order by which the lines have been pushed + std::sort(currentLines->begin(), currentLines->end(), [](const IfcLine* a, const IfcLine* b) { return a->tapeOffset < b->tapeOffset; }); + } for(uint32_t i=0; i < currentLines->size();i++) { @@ -206,13 +210,11 @@ namespace webifc::parsing { outputData((char*)tmp.c_str(),tmp.size()); } - void IfcLoader::SaveFile(std::ostream &outputData) const + void IfcLoader::SaveFile(std::ostream &outputData, bool orderLinesByExpressID) const { - SaveFile([&](char* src, size_t srcSize) - { + SaveFile([&](char* src, size_t srcSize) { outputData.write(src,srcSize); - } - ); + },orderLinesByExpressID); } bool IfcLoader::IsAtEnd() const diff --git a/src/cpp/web-ifc/parsing/IfcLoader.h b/src/cpp/web-ifc/parsing/IfcLoader.h index d47f2fef9..dbbe18e78 100644 --- a/src/cpp/web-ifc/parsing/IfcLoader.h +++ b/src/cpp/web-ifc/parsing/IfcLoader.h @@ -25,8 +25,8 @@ namespace webifc::parsing const std::vector GetHeaderLinesWithType(const uint32_t type) const; void LoadFile(const std::function &requestData); void LoadFile(std::istream &requestData); - void SaveFile(const std::function &outputData) const; - void SaveFile(std::ostream &outputData) const; + void SaveFile(const std::function &outputData, bool orderLinesByExpressID) const; + void SaveFile(std::ostream &outputData, bool orderLinesByExpressID) const; const std::vector GetExpressIDsWithType(const uint32_t type) const; uint32_t GetMaxExpressId() const; bool IsValidExpressID(const uint32_t expressID) const; From 8a75cf71234a489b834db0ead4c259b6ae394654 Mon Sep 17 00:00:00 2001 From: ifcapps Date: Thu, 6 Mar 2025 20:28:33 +0100 Subject: [PATCH 10/23] complete IFCAXIS2PLACEMENTLINEAR --- .../web-ifc/geometry/IfcGeometryLoader.cpp | 45 ++++++++++++++++--- src/cpp/web-ifc/geometry/IfcGeometryLoader.h | 2 +- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp b/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp index e5ac78e43..d0e694923 100644 --- a/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp +++ b/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp @@ -3107,7 +3107,7 @@ IfcProfile IfcGeometryLoader::GetProfile(uint32_t expressID) const return curve; } - glm::dmat4 IfcGeometryLoader::GetLocalPlacement(uint32_t expressID, glm::dvec3 vector) const + glm::dmat4 IfcGeometryLoader::GetLocalPlacement(uint32_t expressID) const { if(_expressIDToPlacement.contains(expressID)) { return _expressIDToPlacement[expressID]; @@ -3347,15 +3347,48 @@ IfcProfile IfcGeometryLoader::GetProfile(uint32_t expressID) const } case schema::IFCAXIS2PLACEMENTLINEAR: { - glm::dvec3 vector = glm::dvec3(0, 0, 1); + glm::dvec3 xAxis = glm::dvec3(1, 0, 0); + glm::dvec3 zAxis = glm::dvec3(0, 0, 1); + glm::dvec3 Location; _loader.MoveToArgumentOffset(expressID, 0); - uint32_t posID = _loader.GetRefArgument(); - if (_loader.GetRefArgument() == parsing::IfcTokenType::REF) + uint32_t posID = _loader.GetRefArgument(); // Location : IfcPoint; + uint32_t LocationType = _loader.GetLineType(posID); + if (LocationType == schema::IFCPOINTBYDISTANCEEXPRESSION) + { + glm::dmat4 LocationMatrix = GetLocalPlacement(posID); + _expressIDToPlacement[expressID] = LocationMatrix; + return LocationMatrix; + } + else + { + Location = GetCartesianPoint3D(posID); + } + + _loader.MoveToArgumentOffset(expressID, 1); + auto tokenTypeAxis = _loader.GetTokenType(); + if (tokenTypeAxis == parsing::IfcTokenType::REF) { + // Axis : OPTIONAL IfcDirection; The exact direction of the local Z Axis. _loader.StepBack(); - glm::dvec3 vector = GetCartesianPoint3D(_loader.GetRefArgument()); + zAxis = GetCartesianPoint3D(_loader.GetRefArgument()); } - glm::dmat4 result = GetLocalPlacement(posID, vector); + + auto tokenTypeRefDirection = _loader.GetTokenType(); + if (tokenTypeRefDirection == parsing::IfcTokenType::REF) + { + // tokenTypeRefDirection : OPTIONAL IfcDirection; The direction used to determine the direction of the local X Axis. + _loader.StepBack(); + xAxis = GetCartesianPoint3D(_loader.GetRefArgument()); + } + + glm::dvec3 yAxis = glm::normalize(glm::cross(zAxis, xAxis)); + xAxis = glm::normalize(glm::cross(zAxis, yAxis)); + + glm::dmat4 result = glm::dmat4( + glm::dvec4(xAxis, 0), + glm::dvec4(yAxis, 0), + glm::dvec4(zAxis, 0), + glm::dvec4(Location, 1)); _expressIDToPlacement[expressID] = result; return result; diff --git a/src/cpp/web-ifc/geometry/IfcGeometryLoader.h b/src/cpp/web-ifc/geometry/IfcGeometryLoader.h index eb1cea27b..6cf198a81 100644 --- a/src/cpp/web-ifc/geometry/IfcGeometryLoader.h +++ b/src/cpp/web-ifc/geometry/IfcGeometryLoader.h @@ -29,7 +29,7 @@ namespace webifc::geometry void ResetCache(); std::array GetAxis1Placement(const uint32_t expressID) const; glm::dmat3 GetAxis2Placement2D(const uint32_t expressID) const; - glm::dmat4 GetLocalPlacement(const uint32_t expressID, glm::dvec3 vector = glm::dvec3(1)) const; + glm::dmat4 GetLocalPlacement(const uint32_t expressID) const; glm::dvec3 GetCartesianPoint3D(const uint32_t expressID) const; glm::dvec2 GetCartesianPoint2D(const uint32_t expressID) const; glm::dvec3 GetVector(const uint32_t expressID) const; From af83ba024eb9c8fe6eae9c159cb193874bdba29a Mon Sep 17 00:00:00 2001 From: ifcapps Date: Thu, 6 Mar 2025 20:29:19 +0100 Subject: [PATCH 11/23] disable TriangulateCylindricalSurface cylinder surface is not reliable enough, use simple triangulation --- src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp b/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp index f77afb470..00d8a4768 100644 --- a/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp +++ b/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp @@ -1809,7 +1809,9 @@ namespace webifc::geometry } else if (surface.CylinderSurface.Active) { - TriangulateCylindricalSurface(geometry, bounds3D, surface, _circleSegments); + // TriangulateCylindricalSurface(geometry, bounds3D, surface, _circleSegments); + // cylinder surface is not reliable enough, use simple triangulation + TriangulateBounds(geometry, bounds3D, expressID); } else if (surface.RevolutionSurface.Active) { From 47e79978669728b005741c8fb82beff60fb94aaa Mon Sep 17 00:00:00 2001 From: ifcapps Date: Thu, 6 Mar 2025 23:15:28 +0100 Subject: [PATCH 12/23] add IFCPOLYNOMIALCURVE in IfcGeometryLoader::ComputeCurve --- .../web-ifc/geometry/IfcGeometryLoader.cpp | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp b/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp index d0e694923..6e86f374b 100644 --- a/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp +++ b/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp @@ -2075,6 +2075,73 @@ namespace webifc::geometry } break; } + case schema::IFCPOLYNOMIALCURVE: + { + // IfcPolynomialCurve ----------------------------------------------------------- + // IfcPlacement Position; + // std::vector CoefficientsX; //optional + // std::vector CoefficientsY; //optional + // std::vector CoefficientsZ; //optional + + // #452417=IFCPOLYNOMIALCURVE(#452416,(0.0,1.0),(0.0,-0.0249887896744,0.0031250),$); + _loader.MoveToArgumentOffset(expressID, 0); + auto placementID = _loader.GetRefArgument(); + glm::dmat3 placement = GetAxis2Placement2D(placementID); + + //auto type = _loader.GetStringArgument(); + _loader.MoveToArgumentOffset(expressID, 1); + + + // Read coefficients for X, Y, and Z + std::vector CoefficientsX, CoefficientsY, CoefficientsZ; + + auto ReadCoefficients = [&](size_t offset, std::vector& coefficients) { + _loader.MoveToArgumentOffset(expressID, offset); + auto tt = _loader.GetTokenType(); + if ( tt == parsing::SET_BEGIN) { + for (size_t ii = 0; ii < 100; ++ii) { + tt = _loader.GetTokenType(); // Move to next token + if (tt != parsing::IfcTokenType::REAL) { + break; + } + _loader.StepBack(); + coefficients.emplace_back(_loader.GetDoubleArgument()); + } + } + }; + + ReadCoefficients(1, CoefficientsX); + ReadCoefficients(2, CoefficientsY); + ReadCoefficients(3, CoefficientsZ); + + + std::vector points; + double tMin = 0.0, tMax = 1.0; // Define evaluation range + size_t numPoints = 6; // Number of samples for curve resolution + + for (size_t i = 0; i <= numPoints; ++i) { + double t = tMin + (tMax - tMin) * (static_cast(i) / numPoints); + double x = 0.0, y = 0.0, z = 0.0; + + for (size_t j = 0; j < CoefficientsX.size(); ++j) + x += CoefficientsX[j] * std::pow(t, j); + + for (size_t j = 0; j < CoefficientsY.size(); ++j) + y += CoefficientsY[j] * std::pow(t, j); + + for (size_t j = 0; j < CoefficientsZ.size(); ++j) + z += CoefficientsZ[j] * std::pow(t, j); + + points.emplace_back(x, y, z); + } + + // Apply placement transformation + for (auto& pt : points) { + glm::dvec3 transformed = placement * pt; + curve.points.push_back(glm::dvec3(transformed)); + } + break; + } case schema::IFCBSPLINECURVE: { bool condition = sameSense == 0; From 860e837c85b62475891ea15acbf5be887cbb493d Mon Sep 17 00:00:00 2001 From: ifcapps Date: Mon, 10 Mar 2025 11:58:27 +0100 Subject: [PATCH 13/23] add IFCCLOTHOID, add double IfcGeometryLoader::ReadCurveMeasureSelect() const add IFCCLOTHOID curve type in IfcGeometryLoader::ComputeCurve add function double IfcGeometryLoader::ReadCurveMeasureSelect() const initialize glm::dmat4 transformation = glm::dmat4(1.0); in struct IfcPlacedGeometry to avoid uninitialized variables In IfcGeometry::Normalize() add check if numPoints > 0, otherwise extents will be DBL_MAX/DBL_MIN --- src/cpp/test/io_helpers.cpp | 9 ++ src/cpp/test/io_helpers.h | 1 + .../web-ifc/geometry/IfcGeometryLoader.cpp | 148 ++++++++++++++++-- src/cpp/web-ifc/geometry/IfcGeometryLoader.h | 1 + .../geometry/representation/IfcGeometry.cpp | 57 +++---- .../geometry/representation/geometry.h | 18 +-- 6 files changed, 184 insertions(+), 50 deletions(-) diff --git a/src/cpp/test/io_helpers.cpp b/src/cpp/test/io_helpers.cpp index 6730efeea..504385bd7 100644 --- a/src/cpp/test/io_helpers.cpp +++ b/src/cpp/test/io_helpers.cpp @@ -95,6 +95,15 @@ namespace webifc::io } writeFile(filename, makeSVGLines(points2D, indices)); } + void DumpSVGCurveXY(std::vector points, std::string filename, std::vector indices) + { + std::vector points2D; + for (auto &pt : points) + { + points2D.emplace_back(pt.x, pt.y); + } + writeFile(filename, makeSVGLines(points2D, indices)); + } void svgMakeLine(glm::dvec2 a, glm::dvec2 b, std::stringstream &svg, std::string col) { diff --git a/src/cpp/test/io_helpers.h b/src/cpp/test/io_helpers.h index 7b78bb48c..5b3e7e07d 100644 --- a/src/cpp/test/io_helpers.h +++ b/src/cpp/test/io_helpers.h @@ -114,6 +114,7 @@ namespace webifc::io void writeFile(std::string filename, std::string data); void DumpSVGCurve(std::vector points, std::string filename, std::vector indices = {}); + void DumpSVGCurveXY(std::vector points, std::string filename, std::vector indices = {}); void svgMakeLine(glm::dvec2 a, glm::dvec2 b, std::stringstream &svg, std::string col = "rgb(255,0,0)"); void SVGLinesToString(Bounds bounds, glm::dvec2 size, glm::dvec2 offset, SVGLineSet lineSet, std::stringstream &svg); std::string makeSVGLines(SVGDrawing drawing); diff --git a/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp b/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp index 6e86f374b..159d56239 100644 --- a/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp +++ b/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp @@ -6,7 +6,7 @@ #include "IfcGeometryLoader.h" #include "operations/curve-utils.h" #include "operations/geometryutils.h" -#ifdef DEBUG_DUMP_SVG +#if defined(DEBUG_DUMP_SVG) || defined(_DEBUG) #include "../../test/io_helpers.h" #endif @@ -2049,14 +2049,21 @@ namespace webifc::geometry } case schema::IFCCURVESEGMENT: { + // IfcCurveSegment --------------------------------------------- + // IfcTransitionCode Transition; + // IfcPlacement Placement; + // IfcCurveMeasureSelect SegmentStart; + // IfcCurveMeasureSelect SegmentLength; + // IfcCurve ParentCurve; + _loader.MoveToArgumentOffset(expressID, 0); auto type = _loader.GetStringArgument(); _loader.MoveToArgumentOffset(expressID, 1); auto placementID = _loader.GetRefArgument(); _loader.MoveToArgumentOffset(expressID, 2); - double SegmentStart = ReadLenghtMeasure(); + double SegmentStart = ReadCurveMeasureSelect(); _loader.MoveToArgumentOffset(expressID, 4); - double SegmentEnd = ReadLenghtMeasure(); + double SegmentEnd = ReadCurveMeasureSelect(); _loader.MoveToArgumentOffset(expressID, 6); auto curveID = _loader.GetRefArgument(); @@ -2142,6 +2149,90 @@ namespace webifc::geometry } break; } + case schema::IFCCLOTHOID: + { + // IfcClothoid ---------------------------------------------------- + // IfcAxis2Placement Position; + // IfcLengthMeasure ClothoidConstant; + + _loader.MoveToArgumentOffset(expressID, 0); + auto positionID = _loader.GetRefArgument(); + glm::dmat3 placement = GetAxis2Placement2D(positionID); + + _loader.MoveToArgumentOffset(expressID, 1); + double ClothoidConstant = ReadCurveMeasureSelect(); + + //#113=IFCCARTESIANPOINT((0.0,0.0)); + //#114=IFCDIRECTION((1.0,0.0)); + //#115=IFCAXIS2PLACEMENT2D(#113,#114); + //#116=IFCCLOTHOID(#115,-120.0); + //#117=IFCCURVESEGMENT(.CONTSAMEGRADIENTSAMECURVATURE.,#112,IFCPARAMETERVALUE(-82.285),IFCPARAMETERVALUE(82.285),#116); + + double startParam = 0; + double endParam = 1.0; + + if (trim.exist) + { + if (trim.start.hasParam && trim.end.hasParam) + { + startParam = trim.start.param; + endParam = trim.end.param; + } + } + + std::vector clothoidPoints; + if (curve.arcSegments.size() > 1) + { + glm::dvec3 lastPoint2 = curve.points[curve.points.size() - 2]; + glm::dvec3 lastPoint1 = curve.points[curve.points.size() - 1]; + + // Assume parent curve with IFCCURVESEGMENT(.CONTSAMEGRADIENT.) + uint32_t lastPointIndex2 = curve.arcSegments[curve.arcSegments.size()-2]; + uint32_t lastPointIndex1 = curve.arcSegments[curve.arcSegments.size()-1]; + if (lastPointIndex2 < curve.points.size() && lastPointIndex1 < curve.points.size() ) + { + lastPoint1 = curve.points[lastPointIndex1]; + lastPoint2 = curve.points[lastPointIndex2]; + } + + // Compute direction from last two points + glm::dvec3 gradient = glm::normalize(lastPoint1 - lastPoint2); + + // Compute clothoid + uint32_t numPoints = 10; + double step = 1.0 / numPoints; // Adjust step size if needed + double t = 0.0; + + for (uint32_t i = 0; i <= numPoints; ++i) + { + double s = t * ClothoidConstant; + + // Approximate Fresnel integrals (or use lookup table) + double C = s - (pow(s, 5) / 40.0); + double S = (pow(s, 3) / 3.0) - (pow(s, 7) / 56.0); + + glm::dvec3 point(C, S, 0.0); // 2D clothoid curve + clothoidPoints.push_back(lastPoint1 + (gradient * point.x) + (glm::dvec3(0.0, 1.0, 0.0) * point.y)); + + t += step; + } + + for (size_t j = 0; j < clothoidPoints.size(); j++) + { + // Apply placement transformation + clothoidPoints[j] = placement * glm::dvec3(clothoidPoints[j].x, clothoidPoints[j].y, clothoidPoints[j].z); + } + + // Store generated clothoid points + curve.points.insert(curve.points.end(), clothoidPoints.begin(), clothoidPoints.end()); + } + + #ifdef _DEBUG + webifc::io::DumpSVGCurveXY(clothoidPoints, "curve_clothoid.html"); + webifc::io::DumpSVGCurveXY(curve.points, "curve.html"); + #endif + break; + } case schema::IFCBSPLINECURVE: { bool condition = sameSense == 0; @@ -3189,7 +3280,7 @@ IfcProfile IfcGeometryLoader::GetProfile(uint32_t expressID) const { _loader.MoveToArgumentOffset(expressID, 0); IfcCurve curve; - auto lnSegment = 0; + auto lnSegment = 0.0; if (_loader.GetTokenType() != parsing::IfcTokenType::EMPTY) { @@ -3871,18 +3962,47 @@ IfcProfile IfcGeometryLoader::GetProfile(uint32_t expressID) const double IfcGeometryLoader::ReadLenghtMeasure() const { - parsing::IfcTokenType t = _loader.GetTokenType(); - if (t == parsing::IfcTokenType::LABEL) - { - _loader.StepBack(); - if (_loader.GetStringArgument() == "IFCNONNEGATIVELENGTHMEASURE") - { - _loader.GetTokenType(); - return _loader.GetDoubleArgument(); - } - } + parsing::IfcTokenType t = _loader.GetTokenType(); + if (t == parsing::IfcTokenType::LABEL) + { + _loader.StepBack(); + std::string_view selectType = _loader.GetStringArgument(); + if (selectType == "IFCNONNEGATIVELENGTHMEASURE" || selectType == "IFCLENGTHMEASURE") + { + _loader.GetTokenType(); + return _loader.GetDoubleArgument(); + } + } + return 0.0; } + double IfcGeometryLoader::ReadCurveMeasureSelect() const + { + // TYPE IfcCurveMeasureSelect = SELECT(IfcLengthMeasure, IfcParameterValue); + // TYPE IfcParameterValue = REAL; + + parsing::IfcTokenType t = _loader.GetTokenType(); + if (t == parsing::IfcTokenType::LABEL) + { + _loader.StepBack(); + std::string_view selectType = _loader.GetStringArgument(); + if (selectType == "IFCLENGTHMEASURE" || selectType == "IFCPARAMETERVALUE") + { + // argument with SELELCT label: #116=IFCCLOTHOID(#115,IFCPARAMETERVALUE(-120.0)); + _loader.GetTokenType(); + return _loader.GetDoubleArgument(); + } + } + else if (t == parsing::IfcTokenType::REAL) + { + // argument without SELELCT label: #116=IFCCLOTHOID(#115,-120.0); + _loader.StepBack(); + double value = _loader.GetDoubleArgument(); + return value; + } + return 0.0; + } + std::vector IfcGeometryLoader::ReadCurveIndices() const { std::vector result; diff --git a/src/cpp/web-ifc/geometry/IfcGeometryLoader.h b/src/cpp/web-ifc/geometry/IfcGeometryLoader.h index 6cf198a81..a5e471773 100644 --- a/src/cpp/web-ifc/geometry/IfcGeometryLoader.h +++ b/src/cpp/web-ifc/geometry/IfcGeometryLoader.h @@ -68,6 +68,7 @@ namespace webifc::geometry void ComputeCurve(uint32_t expressID, IfcCurve &curve, uint8_t dimensions, bool edge, int sameSense = -1, int trimSense = -1, IfcTrimmingArguments trim = {}) const; void convertAngleUnits(double &Degrees, double &Rad) const; double ReadLenghtMeasure() const; + double ReadCurveMeasureSelect() const; std::vector ReadCurveIndices() const; const webifc::parsing::IfcLoader &_loader; const webifc::schema::IfcSchemaManager &_schemaManager; diff --git a/src/cpp/web-ifc/geometry/representation/IfcGeometry.cpp b/src/cpp/web-ifc/geometry/representation/IfcGeometry.cpp index fd37449e6..905b456ba 100644 --- a/src/cpp/web-ifc/geometry/representation/IfcGeometry.cpp +++ b/src/cpp/web-ifc/geometry/representation/IfcGeometry.cpp @@ -424,43 +424,46 @@ namespace webifc::geometry { glm::dvec3 center = normalizationCenter; if (!normalized) { - glm::dvec3 extents; - GetCenterExtents(center,extents); - for (size_t i = 0; i < vertexData.size(); i += 6) + if (numPoints > 0) { - vertexData[i + 0] = vertexData[i + 0] - center.x; - vertexData[i + 1] = vertexData[i + 1] - center.y; - vertexData[i + 2] = vertexData[i + 2] - center.z; - } - for (size_t i = 0; i < sweptDiskSolid.axis.size(); i++) - { - for (size_t j = 0; j < sweptDiskSolid.axis[i].points.size(); j++) + glm::dvec3 extents; + GetCenterExtents(center, extents); + for (size_t i = 0; i < vertexData.size(); i += 6) { - sweptDiskSolid.axis[i].points[j].x -= center.x; - sweptDiskSolid.axis[i].points[j].y -= center.y; - sweptDiskSolid.axis[i].points[j].z -= center.z; + vertexData[i + 0] = vertexData[i + 0] - center.x; + vertexData[i + 1] = vertexData[i + 1] - center.y; + vertexData[i + 2] = vertexData[i + 2] - center.z; } - } - - for (size_t i = 0; i < sweptDiskSolid.profiles.size(); i++) - { - for (size_t j = 0; j < sweptDiskSolid.profiles[i].curve.points.size(); j++) + for (size_t i = 0; i < sweptDiskSolid.axis.size(); i++) { - sweptDiskSolid.profiles[i].curve.points[j].x -= center.x; - sweptDiskSolid.profiles[i].curve.points[j].y -= center.y; - sweptDiskSolid.profiles[i].curve.points[j].z -= center.z; + for (size_t j = 0; j < sweptDiskSolid.axis[i].points.size(); j++) + { + sweptDiskSolid.axis[i].points[j].x -= center.x; + sweptDiskSolid.axis[i].points[j].y -= center.y; + sweptDiskSolid.axis[i].points[j].z -= center.z; + } } - for (size_t j = 0; j < sweptDiskSolid.profiles[i].holes.size(); j++) + + for (size_t i = 0; i < sweptDiskSolid.profiles.size(); i++) { - for (size_t k = 0; k < sweptDiskSolid.profiles[i].holes[j].points.size(); k++) + for (size_t j = 0; j < sweptDiskSolid.profiles[i].curve.points.size(); j++) + { + sweptDiskSolid.profiles[i].curve.points[j].x -= center.x; + sweptDiskSolid.profiles[i].curve.points[j].y -= center.y; + sweptDiskSolid.profiles[i].curve.points[j].z -= center.z; + } + for (size_t j = 0; j < sweptDiskSolid.profiles[i].holes.size(); j++) { - sweptDiskSolid.profiles[i].holes[j].points[k].x -= center.x; - sweptDiskSolid.profiles[i].holes[j].points[k].y -= center.y; - sweptDiskSolid.profiles[i].holes[j].points[k].z -= center.z; + for (size_t k = 0; k < sweptDiskSolid.profiles[i].holes[j].points.size(); k++) + { + sweptDiskSolid.profiles[i].holes[j].points[k].x -= center.x; + sweptDiskSolid.profiles[i].holes[j].points[k].y -= center.y; + sweptDiskSolid.profiles[i].holes[j].points[k].z -= center.z; + } } } + normalizationCenter = center; } - normalizationCenter = center; normalized = true; } glm::dmat4 resultMat = glm::dmat4(1.0); diff --git a/src/cpp/web-ifc/geometry/representation/geometry.h b/src/cpp/web-ifc/geometry/representation/geometry.h index 70de5ba8e..e5b4df16f 100644 --- a/src/cpp/web-ifc/geometry/representation/geometry.h +++ b/src/cpp/web-ifc/geometry/representation/geometry.h @@ -98,14 +98,14 @@ namespace webifc::geometry { struct Revolution { bool Active = false; - glm::dmat4 Direction; + glm::dmat4 Direction = glm::dmat4(1.0); IfcProfile Profile; }; struct Extrusion { bool Active = false; - glm::dvec3 Direction; + glm::dvec3 Direction = glm::dvec3(0.0,0.0,1.0); IfcProfile Profile; double Length; }; @@ -175,8 +175,8 @@ namespace webifc::geometry { bool hasPos = false; bool hasLenght = false; double param; - glm::dvec2 pos; - glm::dvec3 pos3D; + glm::dvec2 pos = glm::dvec2(0.0,0.0); + glm::dvec3 pos3D= glm::dvec3(0.0,0.0,0.0); }; struct IfcTrimmingArguments @@ -188,8 +188,8 @@ namespace webifc::geometry { struct IfcPlacedGeometry { - glm::dvec4 color; - glm::dmat4 transformation; + glm::dvec4 color = glm::dvec4(1.0,1.0,1.0,1.0); + glm::dmat4 transformation = glm::dmat4(1.0); std::array flatTransformation; uint32_t geometryExpressID; @@ -240,8 +240,8 @@ namespace webifc::geometry { struct IfcComposedMesh { - glm::dvec4 color; - glm::dmat4 transformation; + glm::dvec4 color = glm::dvec4(1.0,1.0,1.0,1.0); + glm::dmat4 transformation = glm::dmat4(1.0); uint32_t expressID; bool hasGeometry = false; bool hasColor = false; @@ -291,7 +291,7 @@ namespace webifc::geometry { struct IfcSurface { - glm::dmat4 transformation; + glm::dmat4 transformation = glm::dmat4(1.0); BSpline BSplineSurface; Cylinder CylinderSurface; Revolution RevolutionSurface; From 670e6d2eb66f3d3a35a6bc04712454d5cd496d07 Mon Sep 17 00:00:00 2001 From: ifcapps Date: Tue, 11 Mar 2025 21:00:08 +0100 Subject: [PATCH 14/23] add IFCCOMPOSITECURVE in IfcGeometryProcessor::GetMesh --- src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp b/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp index f1503128e..a6b381764 100644 --- a/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp +++ b/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp @@ -1108,7 +1108,9 @@ namespace webifc::geometry case schema::IFCCIRCLE: case schema::IFCPOLYLINE: case schema::IFCINDEXEDPOLYCURVE: + case schema::IFCCOMPOSITECURVE: case schema::IFCTRIMMEDCURVE: + case schema::IFCGRADIENTCURVE: { auto lineProfileType = _loader.GetLineType(expressID); IfcCurve curve = _geometryLoader.GetCurve(expressID, 3, false); From 5d2d2f033e402ff199b03ddb92dad52a4f3a2375 Mon Sep 17 00:00:00 2001 From: ifcapps Date: Wed, 12 Mar 2025 11:28:33 +0100 Subject: [PATCH 15/23] complete implementation in SectionedSurface --- .../geometry/operations/geometryutils.h | 160 +++++++++++++++--- .../geometry/representation/IfcCurve.cpp | 3 +- 2 files changed, 138 insertions(+), 25 deletions(-) diff --git a/src/cpp/web-ifc/geometry/operations/geometryutils.h b/src/cpp/web-ifc/geometry/operations/geometryutils.h index 57ba7c5e6..e8ff9b777 100644 --- a/src/cpp/web-ifc/geometry/operations/geometryutils.h +++ b/src/cpp/web-ifc/geometry/operations/geometryutils.h @@ -538,6 +538,25 @@ namespace webifc::geometry return true; } + // Compute a unit normal vector for a polygon given by its vertices. + // The vertices must be in order (clockwise or counterclockwise). + inline glm::dvec3 computePolygonNormal(const std::vector& points) { + size_t numPoints = points.size(); + if (numPoints < 3) { + // Not enough points to form a polygon, return default vector. + return glm::dvec3(0.0, 0.0, 1.0); + } + glm::dvec3 normal(0.0); + for (size_t i = 0; i < numPoints; i++) { + size_t next = (i + 1) % numPoints; + normal.x += (points[i].y - points[next].y) * (points[i].z + points[next].z); + normal.y += (points[i].z - points[next].z) * (points[i].x + points[next].x); + normal.z += (points[i].x - points[next].x) * (points[i].y + points[next].y); + } + + return glm::normalize(normal); + } + inline bool GetBasisFromCoplanarPoints(std::vector &points, glm::dvec3 &v1, glm::dvec3 &v2, glm::dvec3 &v3) { v1 = points[0]; @@ -763,59 +782,152 @@ namespace webifc::geometry } } + inline void createCap(const std::vector >& polygon, uint32_t offset, IfcGeometry& geom) + { + std::vector > > vectorOfPolygon = { polygon }; + std::vector indices = mapbox::earcut(vectorOfPolygon); + + if (indices.size() < 3) + { + // probably a degenerate polygon + spdlog::error("[SectionedSurface()] degenerate polygon in cap"); + return; + } + + bool winding = GetWindingOfTriangle(geom.GetPoint(offset + indices[0]), geom.GetPoint(offset + indices[1]), geom.GetPoint(offset + indices[2])); + bool flipWinding = !winding; + + for (size_t j = 0; j < indices.size(); j += 3) + { + if (flipWinding) + { + geom.AddFace(offset + indices[j + 0], offset + indices[j + 2], offset + indices[j + 1]); + } + else + { + geom.AddFace(offset + indices[j + 0], offset + indices[j + 1], offset + indices[j + 2]); + } + } + } + inline IfcGeometry SectionedSurface(IfcCrossSections profiles) { - spdlog::debug("[SectionedSurface({})]"); + spdlog::debug("[SectionedSurface()]"); IfcGeometry geom; + if (profiles.curves.size() < 2) + { + spdlog::error("[SectionedSurface()] at least two profiles are required in SectionedSurface"); + return geom; + } + // Iterate over each profile, and create a surface by connecting the corresponding points with faces. for (size_t i = 0; i < profiles.curves.size() - 1; i++) { IfcCurve &profile1 = profiles.curves[i]; IfcCurve &profile2 = profiles.curves[i + 1]; + if (profile1.points.size() < 2) + { + continue; + } // Check that the profiles have the same number of points if (profile1.points.size() != profile2.points.size()) { spdlog::error("[SectionedSurface()] profiles must have the same number of points in SectionedSurface"); + continue; } - std::vector indices; - - // Create faces by connecting corresponding points from the two profiles - for (size_t j = 0; j < profile1.points.size(); j++) + // find the start index in profile2 that is closest to the start index in profile1 + size_t startIndexProfile2 = 0; + double minDist = std::numeric_limits::max(); + glm::dvec3 &p1 = profile1.points[0]; + uint32_t numLoopPoints = profile1.points.size(); + for (size_t j = 0; j < numLoopPoints; j++) { - glm::dvec3 &p1 = profile1.points[j]; - int j2 = 0; - if (profile1.points.size() > 1) + glm::dvec3 &p2 = profile2.points[j]; + double dist = glm::distance(p1, p2); + if (dist < minDist) { - double pr = (double)j / (double)(profile1.points.size() - 1); - j2 = pr * (profile2.points.size() - 1); + minDist = dist; + startIndexProfile2 = j; } - glm::dvec3 &p2 = profile2.points[j2]; + } + // Create faces by connecting corresponding points from the two profiles + glm::dvec3 loopNormal = computePolygonNormal(profile1.points); + using Point = std::array; + std::vector> polygon1; + std::vector> polygon2; + std::vector upperLoop, upperLoopNormals; + for (size_t j = 0; j < numLoopPoints; j++) + { + glm::dvec3& p1 = profile1.points[j]; + int j2 = (startIndexProfile2 + j) % numLoopPoints; + glm::dvec3& p2 = profile2.points[j2]; glm::dvec3 normal = glm::dvec3(0.0, 0.0, 1.0); - if (glm::distance(p1, p2) > 1E-5) { - normal = glm::normalize(glm::cross(p2 - p1, glm::cross(p2 - p1, glm::dvec3(0.0, 0.0, 1.0)))); + normal = glm::cross(loopNormal, p2 - p1); + normal = glm::normalize(normal); } - geom.AddPoint(p1, normal); - geom.AddPoint(p2, normal); + upperLoop.push_back(p2); + upperLoopNormals.push_back(normal); + + // project points to XY/XZ/YZ plane + double polygon1X = p1.x; + double polygon1Y = p1.y; + double polygon2X = p2.x; + double polygon2Y = p2.y; + if (std::abs(loopNormal.y) > std::abs(loopNormal.x) && std::abs(loopNormal.y) > std::abs(loopNormal.z)) + { + // y is the largest component, so project to XZ plane + polygon1X = p1.x; + polygon1Y = p1.z; + polygon2X = p2.x; + polygon2Y = p2.z; + } + else if (std::abs(loopNormal.x) > std::abs(loopNormal.y) && std::abs(loopNormal.x) > std::abs(loopNormal.z)) + { + // x is the largest component, so project to YZ plane + polygon1X = p1.y; + polygon1Y = p1.z; + polygon2X = p2.y; + polygon2Y = p2.z; + } - indices.push_back(geom.numPoints - 2); - indices.push_back(geom.numPoints - 1); + polygon1.push_back( {polygon1X, polygon1Y} ); + polygon2.push_back( {polygon2X, polygon2Y} ); } - // Create the faces - if (indices.size() > 0) + for (size_t j = 0; j < numLoopPoints; j++) { - for (size_t j = 0; j < indices.size() - 2; j += 4) - { - geom.AddFace(indices[j], indices[j + 1], indices[j + 2]); - geom.AddFace(indices[j + 2], indices[j + 1], indices[j + 3]); - } + geom.AddPoint(upperLoop[j], upperLoopNormals[j]); + } + + // Create the side faces + for (size_t j = 0; j < numLoopPoints; j++) + { + uint32_t idxBottom = j; + uint32_t idxBottomNext = (j + 1) % numLoopPoints; + uint32_t idxTop = j + numLoopPoints; + uint32_t idxTopNext = idxBottomNext + numLoopPoints; + geom.AddFace(idxBottom, idxBottomNext, idxTopNext); + geom.AddFace(idxTopNext, idxTop, idxBottom); + } + + // Create bottom cap + if (i == 0 ) + { + createCap(polygon1, 0, geom); + } + + // Create the top cap + if (i == 0 || i == profiles.curves.size() - 1) + { + uint32_t offset = polygon2.size(); + createCap(polygon2, offset, geom); } } diff --git a/src/cpp/web-ifc/geometry/representation/IfcCurve.cpp b/src/cpp/web-ifc/geometry/representation/IfcCurve.cpp index 8d306d59a..e675d6d7c 100644 --- a/src/cpp/web-ifc/geometry/representation/IfcCurve.cpp +++ b/src/cpp/web-ifc/geometry/representation/IfcCurve.cpp @@ -69,7 +69,7 @@ namespace webifc::geometry glm::dmat4 IfcCurve::getPlacementAtDistance(double length) { double totalDistance = 0; - glm::dvec3 pos; + glm::dvec3 pos = glm::dvec3(0, 0, 0); glm::dvec3 vx = glm::dvec3(1, 0, 0); glm::dvec3 vy = glm::dvec3(0, 1, 0); glm::dvec3 vz = glm::dvec3(0, 0, 1); @@ -81,6 +81,7 @@ namespace webifc::geometry totalDistance += distance; if (totalDistance >= length) { + // extrapolate from last 2 points if length is behind last point double factor = (totalDistance - length) / distance; pos = points[i] * factor + points[i + 1] * (1 - factor); glm::dvec3 tan = points[i + 1] - points[i]; From 539a6f55750042412b54ccc7583468234bc29f88 Mon Sep 17 00:00:00 2001 From: ifcapps Date: Wed, 12 Mar 2025 16:00:40 +0100 Subject: [PATCH 16/23] complete SectionedSurface --- .../web-ifc/geometry/IfcGeometryLoader.cpp | 42 ++++++++++++------- .../geometry/operations/geometryutils.h | 35 +++++++--------- .../geometry/representation/IfcCurve.cpp | 2 +- 3 files changed, 43 insertions(+), 36 deletions(-) diff --git a/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp b/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp index 0965d4a50..b196b19e9 100644 --- a/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp +++ b/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp @@ -167,36 +167,48 @@ namespace webifc::geometry { case schema::IFCSECTIONEDSOLIDHORIZONTAL: { + // IfcSectionedSolidHorizontal ------------------------------------------- + // IfcCurve Directrix; + // std::vector CrossSections; + // std::vector CrossSectionPositions; + _loader.MoveToArgumentOffset(expressID, 0); auto curveId = _loader.GetRefArgument(); - // faces + // CrossSections _loader.MoveToArgumentOffset(expressID, 1); - auto faces = _loader.GetSetArgument(); + auto CrossSections = _loader.GetSetArgument(); // linear position _loader.MoveToArgumentOffset(expressID, 2); - auto linearPositions = _loader.GetSetArgument(); + auto CrossSectionPositions = _loader.GetSetArgument(); IfcCurve curve = GetCurve(curveId, 3); std::vector profiles; std::vector curves; - std::vector expressIds; + std::vector CrossSectionExpressIds; std::vector transform; - for (auto &linearPosition : linearPositions) + for (auto &CrossSectionPositionTapeOffset : CrossSectionPositions) { - auto expressID = _loader.GetRefArgument(linearPosition); - glm::dmat4 linearPlacement = GetLocalPlacement(expressID) * scale; + auto CrossSectionPositionID = _loader.GetRefArgument(CrossSectionPositionTapeOffset); + glm::dmat4 linearPlacement = GetLocalPlacement(CrossSectionPositionID) * scale; transform.push_back(linearPlacement); } + if (transform.size() < CrossSections.size()) + { + // if there are more cross sections than positions, ignore the extra cross sections + CrossSections.resize(transform.size()); + } + uint32_t id = 0; - for (auto &face : faces) + for (auto &face : CrossSections) { - auto expressID = _loader.GetRefArgument(face); - IfcProfile profile = GetProfile(expressID); + auto CrossSectionExpressID = _loader.GetRefArgument(face); + IfcProfile profile = GetProfile(CrossSectionExpressID); + // the profile normal agrees with the tangent of the Directrix, the profile X axis is oriented perpendicularly to the left of the Directrix for (uint32_t i = 0; i < profile.curve.points.size(); i++) { glm::dvec3 pTemp = transform[id] * glm::dvec4(profile.curve.points[i], 1); @@ -204,12 +216,12 @@ namespace webifc::geometry } profiles.push_back(profile); curves.push_back(profile.curve); - expressIds.push_back(expressID); + CrossSectionExpressIds.push_back(CrossSectionExpressID); id++; } sections.curves = curves; - sections.expressID = expressIds; + sections.expressID = CrossSectionExpressIds; return sections; @@ -2956,7 +2968,7 @@ IfcProfile IfcGeometryLoader::GetProfile(uint32_t expressID) const { for (uint32_t i = 0; i < profile.curve.points.size(); i++) { - profile.curve.points[i] = transformation * glm::dvec3(profile.curve.points[i].x, profile.curve.points[i].y, 1); + profile.curve.points[i] = transformation * glm::dvec3(profile.curve.points[i].x, profile.curve.points[i].y, 0); } } else @@ -2965,7 +2977,7 @@ IfcProfile IfcGeometryLoader::GetProfile(uint32_t expressID) const { for (uint32_t i = 0; i < profile.profiles[j].curve.points.size(); i++) { - profile.profiles[j].curve.points[i] = transformation * glm::dvec3(profile.profiles[j].curve.points[i].x, profile.profiles[j].curve.points[i].y, 1); + profile.profiles[j].curve.points[i] = transformation * glm::dvec3(profile.profiles[j].curve.points[i].x, profile.profiles[j].curve.points[i].y, 0); } } } @@ -3500,7 +3512,7 @@ IfcProfile IfcGeometryLoader::GetProfile(uint32_t expressID) const { glm::dvec3 xAxis = glm::dvec3(1, 0, 0); glm::dvec3 zAxis = glm::dvec3(0, 0, 1); - glm::dvec3 Location; + glm::dvec3 Location = glm::dvec3(0, 0, 0); _loader.MoveToArgumentOffset(expressID, 0); uint32_t posID = _loader.GetRefArgument(); // Location : IfcPoint; uint32_t LocationType = _loader.GetLineType(posID); diff --git a/src/cpp/web-ifc/geometry/operations/geometryutils.h b/src/cpp/web-ifc/geometry/operations/geometryutils.h index e8ff9b777..542002a09 100644 --- a/src/cpp/web-ifc/geometry/operations/geometryutils.h +++ b/src/cpp/web-ifc/geometry/operations/geometryutils.h @@ -790,7 +790,7 @@ namespace webifc::geometry if (indices.size() < 3) { // probably a degenerate polygon - spdlog::error("[SectionedSurface()] degenerate polygon in cap"); + spdlog::warn("[createCap()] degenerate polygon in cap"); return; } @@ -857,8 +857,7 @@ namespace webifc::geometry // Create faces by connecting corresponding points from the two profiles glm::dvec3 loopNormal = computePolygonNormal(profile1.points); using Point = std::array; - std::vector> polygon1; - std::vector> polygon2; + std::vector> projectedPolygon; std::vector upperLoop, upperLoopNormals; for (size_t j = 0; j < numLoopPoints; j++) { @@ -876,29 +875,25 @@ namespace webifc::geometry upperLoopNormals.push_back(normal); // project points to XY/XZ/YZ plane - double polygon1X = p1.x; - double polygon1Y = p1.y; - double polygon2X = p2.x; - double polygon2Y = p2.y; + double polygonX = p1.x; + double polygonY = p1.y; if (std::abs(loopNormal.y) > std::abs(loopNormal.x) && std::abs(loopNormal.y) > std::abs(loopNormal.z)) { // y is the largest component, so project to XZ plane - polygon1X = p1.x; - polygon1Y = p1.z; - polygon2X = p2.x; - polygon2Y = p2.z; + polygonX = p1.x; + polygonY = p1.z; } else if (std::abs(loopNormal.x) > std::abs(loopNormal.y) && std::abs(loopNormal.x) > std::abs(loopNormal.z)) { // x is the largest component, so project to YZ plane - polygon1X = p1.y; - polygon1Y = p1.z; - polygon2X = p2.y; - polygon2Y = p2.z; + polygonX = p1.y; + polygonY = p1.z; + } + else { + spdlog::warn("[SectionedSurface()] unable to project polygon"); } - polygon1.push_back( {polygon1X, polygon1Y} ); - polygon2.push_back( {polygon2X, polygon2Y} ); + projectedPolygon.push_back( {polygonX, polygonY} ); } for (size_t j = 0; j < numLoopPoints; j++) @@ -920,14 +915,14 @@ namespace webifc::geometry // Create bottom cap if (i == 0 ) { - createCap(polygon1, 0, geom); + createCap(projectedPolygon, 0, geom); } // Create the top cap if (i == 0 || i == profiles.curves.size() - 1) { - uint32_t offset = polygon2.size(); - createCap(polygon2, offset, geom); + uint32_t offset = numLoopPoints; + createCap(projectedPolygon, offset, geom); } } diff --git a/src/cpp/web-ifc/geometry/representation/IfcCurve.cpp b/src/cpp/web-ifc/geometry/representation/IfcCurve.cpp index e675d6d7c..1e81bb0db 100644 --- a/src/cpp/web-ifc/geometry/representation/IfcCurve.cpp +++ b/src/cpp/web-ifc/geometry/representation/IfcCurve.cpp @@ -79,7 +79,7 @@ namespace webifc::geometry { double distance = glm::distance(points[i], points[i + 1]); totalDistance += distance; - if (totalDistance >= length) + if (totalDistance >= length || i == points.size() - 2) { // extrapolate from last 2 points if length is behind last point double factor = (totalDistance - length) / distance; From fb8bbb1787d32241dd36cd56f6507ac0bf1b79e7 Mon Sep 17 00:00:00 2001 From: ifcapps Date: Thu, 13 Mar 2025 00:19:35 +0100 Subject: [PATCH 17/23] fix IFCCLOTHOID curve computation --- .../web-ifc/geometry/IfcGeometryLoader.cpp | 130 +++++++++++++----- 1 file changed, 98 insertions(+), 32 deletions(-) diff --git a/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp b/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp index b196b19e9..fa46d2249 100644 --- a/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp +++ b/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp @@ -2034,9 +2034,19 @@ namespace webifc::geometry } case schema::IFCGRADIENTCURVE: { + // IfcGradientCurve -------------------------------------------------------- + // std::vector Segments; + // IfcLogical SelfIntersect; + // IfcBoundedCurve BaseCurve; + // IfcPlacement EndPoint; //optional + + // IfcGradientCurve: 3D curve, based on its 2D projection (BaseCurve) and a height defined by its gradient + // segments which can be derived from the segment start height, its placement and + // the ParentCurve instance and the type of the ParentCurve + _loader.MoveToArgumentOffset(expressID, 0); auto tokens = _loader.GetSetArgument(); - auto u = _loader.GetStringArgument(); + auto SelfIntersect = _loader.GetStringArgument(); auto masterCurveID = _loader.GetRefArgument(); curve = GetCurve(masterCurveID, 3, false); @@ -2077,6 +2087,7 @@ namespace webifc::geometry trim.end.param = SegmentEnd; trim.start.hasParam = true; trim.end.hasParam = true; + trim.exist = true; ComputeCurve(curveID, curve, 3, false, -1, -1, trim); glm::dmat3 placement = GetAxis2Placement2D(placementID); @@ -2165,7 +2176,7 @@ namespace webifc::geometry glm::dmat3 placement = GetAxis2Placement2D(positionID); _loader.MoveToArgumentOffset(expressID, 1); - double ClothoidConstant = ReadCurveMeasureSelect(); + double ClothoidConstantA = ReadCurveMeasureSelect(); //#113=IFCCARTESIANPOINT((0.0,0.0)); //#114=IFCDIRECTION((1.0,0.0)); @@ -2175,60 +2186,115 @@ namespace webifc::geometry double startParam = 0; double endParam = 1.0; - if (trim.exist) { if (trim.start.hasParam && trim.end.hasParam) { - startParam = trim.start.param; - endParam = trim.end.param; + startParam = trim.start.param; // e.g., 0 + endParam = trim.end.param; // e.g., 28 } } std::vector clothoidPoints; - if (curve.arcSegments.size() > 1) + if (curve.points.size() > 1) { + // Determine the last two points (possibly using arcSegments if available) glm::dvec3 lastPoint2 = curve.points[curve.points.size() - 2]; glm::dvec3 lastPoint1 = curve.points[curve.points.size() - 1]; - - // Assume parent curve with IFCCURVESEGMENT(.CONTSAMEGRADIENT.) - uint32_t lastPointIndex2 = curve.arcSegments[curve.arcSegments.size()-2]; - uint32_t lastPointIndex1 = curve.arcSegments[curve.arcSegments.size()-1]; - if (lastPointIndex2 < curve.points.size() && lastPointIndex1 < curve.points.size() ) + if (curve.arcSegments.size() > 1) { - lastPoint1 = curve.points[lastPointIndex1]; - lastPoint2 = curve.points[lastPointIndex2]; + uint32_t lastPointIndex2 = curve.arcSegments[curve.arcSegments.size() - 2]; + uint32_t lastPointIndex1 = curve.arcSegments[curve.arcSegments.size() - 1]; + if (lastPointIndex2 < curve.points.size() && lastPointIndex1 < curve.points.size()) + { + lastPoint1 = curve.points[lastPointIndex1]; + lastPoint2 = curve.points[lastPointIndex2]; + } } - // Compute direction from last two points - glm::dvec3 gradient = glm::normalize(lastPoint1 - lastPoint2); + // Compute the local basis: tangent and a perpendicular 'normal' + glm::dvec3 tangent = glm::normalize(lastPoint1 - lastPoint2); + glm::dvec3 up(0.0, 0.0, 1.0); + if (std::abs(glm::dot(tangent, up)) > 0.999) + up = glm::dvec3(1.0, 0.0, 0.0); + glm::dvec3 normal = glm::normalize(glm::cross(tangent, up)); + + // Number of segments (10 segments means 11 points) + const uint32_t numPoints = 10; + clothoidPoints.resize(numPoints + 1); + + // Our target arc length for the clothoid + double desiredLength = endParam - startParam; + + // Lambda to compute total polyline length for a given scale factor. + auto computeLength = [&](double scaleStep) -> double { + double length = 0.0; + glm::dvec3 prevPoint = lastPoint1; + for (uint32_t i = 0; i <= numPoints; ++i) + { + double u = static_cast(i) / static_cast(numPoints); + // Adjust parameter s by the current scale factor. + double s = startParam + u * (endParam - startParam) * scaleStep; + // Normalize s with the clothoid constant A. + double sScaled = s / ClothoidConstantA; + // Series approximations for the Fresnel integrals, scaled by A. + double C = ClothoidConstantA * (sScaled - (std::pow(sScaled, 5) / 40.0)); + double S = ClothoidConstantA * ((std::pow(sScaled, 3) / 3.0) - (std::pow(sScaled, 7) / 56.0)); + glm::dvec3 currPoint = lastPoint1 + tangent * C + normal * S; + if (i > 0) + { + length += glm::distance(currPoint, prevPoint); + } + prevPoint = currPoint; + } + return length; + }; + + // Find an initial bracket for scaleStep. + double scaleLow = 0.0; + double scaleHigh = 1.0; + double L = computeLength(scaleHigh); + while (L < desiredLength) + { + scaleHigh *= 2.0; + L = computeLength(scaleHigh); + // Safety break to avoid infinite loop. + if (scaleHigh > 1e6) break; + } - // Compute clothoid - uint32_t numPoints = 10; - double step = 1.0 / numPoints; // Adjust step size if needed - double t = 0.0; + // Now perform a binary search for the correct scaleStep that gives us the desired length. + double tol = 0.001; + double scaleStep = scaleHigh; + for (int iter = 0; iter < 100; ++iter) + { + scaleStep = (scaleLow + scaleHigh) * 0.5; + L = computeLength(scaleStep); + if (std::abs(L - desiredLength) < tol) + break; + if (L < desiredLength) + scaleLow = scaleStep; + else + scaleHigh = scaleStep; + } + // With the computed scaleStep, generate the final clothoid points. for (uint32_t i = 0; i <= numPoints; ++i) { - double s = t * ClothoidConstant; - - // Approximate Fresnel integrals (or use lookup table) - double C = s - (pow(s, 5) / 40.0); - double S = (pow(s, 3) / 3.0) - (pow(s, 7) / 56.0); - - glm::dvec3 point(C, S, 0.0); // 2D clothoid curve - clothoidPoints.push_back(lastPoint1 + (gradient * point.x) + (glm::dvec3(0.0, 1.0, 0.0) * point.y)); - - t += step; + double u = static_cast(i) / static_cast(numPoints); + double s = startParam + u * (endParam - startParam) * scaleStep; + double sScaled = s / ClothoidConstantA; + double C = ClothoidConstantA * (sScaled - (std::pow(sScaled, 5) / 40.0)); + double S = ClothoidConstantA * ((std::pow(sScaled, 3) / 3.0) - (std::pow(sScaled, 7) / 56.0)); + clothoidPoints[i] = lastPoint1 + tangent * C + normal * S; } + // Apply any placement transformation if needed. for (size_t j = 0; j < clothoidPoints.size(); j++) { - // Apply placement transformation - clothoidPoints[j] = placement * glm::dvec3(clothoidPoints[j].x, clothoidPoints[j].y, clothoidPoints[j].z); + clothoidPoints[j] = placement * clothoidPoints[j]; } - // Store generated clothoid points + // Append the generated clothoid points to the curve. curve.points.insert(curve.points.end(), clothoidPoints.begin(), clothoidPoints.end()); } From bfb3532413393f47ee2f7fa043fd8de45d1d4282 Mon Sep 17 00:00:00 2001 From: ifcapps Date: Thu, 13 Mar 2025 10:31:53 +0100 Subject: [PATCH 18/23] add argument orderLinesByExpressID in SaveFile --- src/cpp/wasm/web-ifc-wasm.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cpp/wasm/web-ifc-wasm.cpp b/src/cpp/wasm/web-ifc-wasm.cpp index 4202d56db..fc16791d4 100644 --- a/src/cpp/wasm/web-ifc-wasm.cpp +++ b/src/cpp/wasm/web-ifc-wasm.cpp @@ -54,7 +54,7 @@ void SaveModel(uint32_t modelID, emscripten::val callback) { emscripten::val retVal = callback((uint32_t)src, srcSize); } - ); + , false); } int GetModelSize(uint32_t modelID) { From 52901d21d7303d9bfc1bfa4ccc0d9190f1e52911 Mon Sep 17 00:00:00 2001 From: ifcapps Date: Tue, 18 Mar 2025 16:52:02 +0100 Subject: [PATCH 19/23] Restore color of geometric items after flatten() and BoolProcess() --- .../web-ifc/geometry/IfcGeometryProcessor.cpp | 19 +++++++++++++++++++ .../geometry/operations/geometryutils.h | 2 ++ 2 files changed, 21 insertions(+) diff --git a/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp b/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp index a6b381764..d6ee03baf 100644 --- a/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp +++ b/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp @@ -205,6 +205,10 @@ namespace webifc::geometry { joinedVoidGeoms.push_back(geom); } + else if (geom.isPolygon) + { + joinedVoidGeoms.push_back(geom); + } else { std::vector geomVector = {geom}; // Wrap 'geom' in a vector @@ -288,6 +292,21 @@ namespace webifc::geometry resultMesh.hasColor = false; } + // Sometimes, a geometric item like IFCEXTRUDEDAREASOLID has a color assigned. + // With flatten() and BoolProcess, that color gets lost. Restore it here: + if (!mesh.hasGeometry && mesh.children.size() > 0) + { + auto geometricItem = mesh.children.front(); + std::optional geometricItemColor = geometricItem.GetColor(); + if (geometricItemColor.has_value()) + { + glm::dvec4 colorValue = geometricItemColor.value(); + resultMesh.color = colorValue; + resultMesh.hasColor = true; + } + } + + return resultMesh; } else diff --git a/src/cpp/web-ifc/geometry/operations/geometryutils.h b/src/cpp/web-ifc/geometry/operations/geometryutils.h index 542002a09..96275ea3f 100644 --- a/src/cpp/web-ifc/geometry/operations/geometryutils.h +++ b/src/cpp/web-ifc/geometry/operations/geometryutils.h @@ -1255,6 +1255,7 @@ namespace webifc::geometry { IfcGeometry newGeom; newGeom.halfSpace = newMeshGeom.halfSpace; + newGeom.isPolygon = newMeshGeom.isPolygon; if (newGeom.halfSpace) { newGeom.halfSpaceOrigin = newMat * glm::dvec4(newMeshGeom.halfSpaceOrigin, 1); @@ -1290,6 +1291,7 @@ namespace webifc::geometry { IfcGeometry newGeom; newGeom.halfSpace = meshGeom.halfSpace; + newGeom.isPolygon = meshGeom.isPolygon; if (newGeom.halfSpace) { newGeom.halfSpaceOrigin = newMat * glm::dvec4(meshGeom.halfSpaceOrigin, 1); From 0fe5aa169fe55fe3a5e4e06d2c3cbb74b0dab3a2 Mon Sep 17 00:00:00 2001 From: ifcapps Date: Tue, 18 Mar 2025 22:41:53 +0100 Subject: [PATCH 20/23] Add check for nan in Nurbs. Add check for IfcTokenType::LINE_END in IfcLoader to prevent near infinite loop --- src/cpp/web-ifc/geometry/nurbs.cpp | 11 +++++++++++ src/cpp/web-ifc/parsing/IfcLoader.cpp | 7 ++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/cpp/web-ifc/geometry/nurbs.cpp b/src/cpp/web-ifc/geometry/nurbs.cpp index a50115e55..2035e6790 100644 --- a/src/cpp/web-ifc/geometry/nurbs.cpp +++ b/src/cpp/web-ifc/geometry/nurbs.cpp @@ -178,6 +178,11 @@ namespace webifc::geometry{ this->pth = tinynurbs::surfacePoint(*this->nurbs, 1.0, 0.0); this->ptv = tinynurbs::surfacePoint(*this->nurbs, 0.0, 1.0); + if (std::isnan(pth.x) || std::isnan(pth.y) ||std::isnan(pth.z) ) { + spdlog::error("pth isnan: ({}/{}/{})", pth.x, pth.y, pth.z); + return; + } + // Compute distances for further use. this->dh = glm::distance(ptc, pth); this->dv = glm::distance(ptc, ptv); @@ -313,6 +318,12 @@ namespace webifc::geometry{ double rads = (i / rotations) * pi2; double incU = glm::sin(rads) / mul_divisor; double incV = glm::cos(rads) / mul_divisor; + + if (std::isnan(pr)) { + spdlog::error("Invalid pr {} ",pr); + break; + } + if (pr > 1) incV *= pr; else incU /= pr; while (true) diff --git a/src/cpp/web-ifc/parsing/IfcLoader.cpp b/src/cpp/web-ifc/parsing/IfcLoader.cpp index 1a9a948dc..1ce431dca 100644 --- a/src/cpp/web-ifc/parsing/IfcLoader.cpp +++ b/src/cpp/web-ifc/parsing/IfcLoader.cpp @@ -507,6 +507,11 @@ namespace webifc::parsing { { depth--; } + else if (t == IfcTokenType::LINE_END) + { + spdlog::error("[GetSetArgument[{}]) unexpected LINE_END token", GetCurrentLineExpressID()); + break; + } else { tapeOffsets.push_back(offset); @@ -522,7 +527,7 @@ namespace webifc::parsing { } else { - spdlog::error("[GetSetArgument[]) unexpected token", GetCurrentLineExpressID()); + spdlog::error("[GetSetArgument[{}]) unexpected token", GetCurrentLineExpressID()); } } From d199fb8450c7c63b602f47052aefd9985c79bc1a Mon Sep 17 00:00:00 2001 From: ifcapps Date: Thu, 27 Mar 2025 15:13:51 +0100 Subject: [PATCH 21/23] replace map.contains with find, to be able to compile with C++ 17 --- src/cpp/web-ifc/parsing/IfcLoader.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/cpp/web-ifc/parsing/IfcLoader.cpp b/src/cpp/web-ifc/parsing/IfcLoader.cpp index 1ce431dca..65a4db253 100644 --- a/src/cpp/web-ifc/parsing/IfcLoader.cpp +++ b/src/cpp/web-ifc/parsing/IfcLoader.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include "IfcLoader.h" @@ -294,13 +293,14 @@ namespace webifc::parsing { bool IfcLoader::IsValidExpressID(const uint32_t expressID) const { - if (expressID == 0 || expressID > _maxExpressId || !_lines.contains(expressID)) return false; + if (expressID == 0 || expressID > _maxExpressId ) return false; + if (_lines.find(expressID) == _lines.end()) return false; else return true; } uint32_t IfcLoader::GetLineType(const uint32_t expressID) const { - if (expressID == 0 || expressID > _maxExpressId || !_lines.contains(expressID)) { + if (expressID == 0 || expressID > _maxExpressId || _lines.find(expressID) == _lines.end()) { spdlog::error("[GetLineType()] Attempt to Access Invalid ExpressID {}", expressID); return 0; } @@ -318,7 +318,7 @@ namespace webifc::parsing { void IfcLoader::MoveToLineArgument(const uint32_t expressID, const uint32_t argumentIndex) const { - if (!_lines.contains(expressID)) return; + if (_lines.find(expressID) == _lines.end()) return; _tokenStream->MoveTo(_lines.at(expressID)->tapeOffset); ArgumentOffset(argumentIndex); } @@ -343,7 +343,7 @@ namespace webifc::parsing { void IfcLoader::PushDouble(double input) { - std::string numberString = std::format("{}", input); + std::string numberString = std::to_string(input); size_t eLoc = numberString.find_first_of('e'); if (eLoc != std::string::npos) numberString[eLoc]='E'; else if (std::floor(input) == input) numberString+='.'; @@ -426,7 +426,7 @@ namespace webifc::parsing { void IfcLoader::UpdateLineTape(const uint32_t expressID, const uint32_t type, const uint32_t start) { - if (!_lines.contains(expressID)) + if (_lines.find(expressID) == _lines.end()) { // create line object IfcLine * line = new IfcLine(); @@ -656,7 +656,7 @@ namespace webifc::parsing { uint32_t IfcLoader::GetNoLineArguments(uint32_t expressID) const { - if (!_lines.contains(expressID)) return 0; + if (_lines.find(expressID) == _lines.end()) return 0; _tokenStream->MoveTo(_lines.at(expressID)->tapeOffset); _tokenStream->Read(); _tokenStream->Read(); @@ -698,7 +698,7 @@ namespace webifc::parsing { void IfcLoader::MoveToArgumentOffset(const uint32_t expressID, const uint32_t argumentIndex) const { - if (_lines.contains(expressID)) { + if (_lines.find(expressID) != _lines.end()) { _tokenStream->MoveTo(_lines.at(expressID)->tapeOffset); ArgumentOffset(argumentIndex); } @@ -733,7 +733,7 @@ namespace webifc::parsing { uint32_t IfcLoader::GetNextExpressID(uint32_t expressId) const { uint32_t currentId = expressId+1; - while(!_lines.contains(currentId)) currentId++; + while(_lines.find(currentId) == _lines.end()) currentId++; return currentId; } From b576957f2872210b4c6e7fe9ffcd969061e3b8aa Mon Sep 17 00:00:00 2001 From: ifcapps Date: Thu, 27 Mar 2025 16:21:11 +0100 Subject: [PATCH 22/23] add test script "regression-save-glb" --- .gitignore | 1 + package.json | 1 + tests/regression/regression-save-glb.mjs | 210 ++++++++++++++ yarn.lock | 344 ++++++++--------------- 4 files changed, 334 insertions(+), 222 deletions(-) create mode 100644 tests/regression/regression-save-glb.mjs diff --git a/.gitignore b/.gitignore index 2290e1444..0154822bd 100644 --- a/.gitignore +++ b/.gitignore @@ -60,3 +60,4 @@ tests/ifcfiles/private/*.ifc tests/ifcfiles/private/*.stl tests/ifcfiles/created.ifc src/cpp/_deps/ +yarn.lock diff --git a/package.json b/package.json index c7584c330..9e19fa22e 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "postversion": "node src/setversion.js", "benchmark": "ts-node ./tests/benchmark/benchmark.ts", "regression": "node --max-old-space-size=8192 ./tests/regression/regression.mjs", + "regression-save-glb": "node --max-old-space-size=8192 ./tests/regression/regression-save-glb.mjs", "regression-update": "node --max-old-space-size=8192 ./tests/regression/regression.mjs update", "test": "jest --runInBand ", "gen-docs": "typedoc --out dist/docs && cpy ./banner.png ./dist/docs", diff --git a/tests/regression/regression-save-glb.mjs b/tests/regression/regression-save-glb.mjs new file mode 100644 index 000000000..bdc93e54c --- /dev/null +++ b/tests/regression/regression-save-glb.mjs @@ -0,0 +1,210 @@ +import * as THREE from "three"; +import { readFileSync, writeFileSync, readdirSync, readSync, openSync, existsSync, mkdirSync } from "fs"; +import * as path from "path"; +const { createHash } = await import('node:crypto'); +import { IfcAPI } from "../../dist/web-ifc-api-node.js"; +import * as BufferGeometryUtils from "three/examples/jsm/utils/BufferGeometryUtils.js"; +import { Blob, FileReader } from 'vblob'; +global.Blob = Blob; +global.FileReader = FileReader; +import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js'; +import AdmZip from 'adm-zip'; + +const REGRESSION_FILES_DIR = "./tests/ifcfiles/"; +const REGRESSION_RESULT_FILE = "./tests/regression/results.json"; +const OUTPUT_GLB_DIR = path.join("tests", "regression", "glb"); + +var materials = {}; +var ifcAPI; +var regressionResults = {}; + +async function RunRegression() { + try { + let update = false; + if (process.argv.includes("update")) update = true; + ifcAPI = new IfcAPI(); + await ifcAPI.Init(); + let files = await GetRegressionFiles(); + for (let fileName of files) { + let properFileName = fileName.replaceAll("\\", "/"); + regressionResults[properFileName] = await CreateModelResuts(fileName); + regressionResults[properFileName] = createHash('sha256') + .update(JSON.stringify(regressionResults[properFileName])) + .digest('hex'); + } + if (update) { + writeFileSync(REGRESSION_RESULT_FILE, JSON.stringify(regressionResults)); + console.log("--------Results Updated-----------"); + } else { + let regressionResultsCurrent = JSON.parse(readFileSync(REGRESSION_RESULT_FILE)); + console.log("--------Regression Results-----------"); + let passTests = true; + try { + for (let fileName in regressionResults) { + let normFileName = fileName.replaceAll("\\", "/"); + if (normFileName in regressionResultsCurrent) { + if (regressionResultsCurrent[normFileName] == regressionResults[normFileName]) + console.log(normFileName + " - PASS"); + else { + console.log(normFileName + " - FAIL"); + passTests = false; + } + } else console.log("Could not find: " + normFileName); + } + } catch (e) { + console.log(e); + } + if (!passTests) { + console.log("One or more models failed - please verify the models and if you are happy run npm run regression-update"); + process.exit(1); + } + } + } catch (e) { + console.log(e); + } +} + +async function GetRegressionFiles() { + let files = readdirSync(REGRESSION_FILES_DIR + "public/") + .filter((f) => (f.endsWith(".ifc") || f.endsWith(".ifczip"))) + .map((f) => path.join(REGRESSION_FILES_DIR + "public/", f)); + let privateFiles = []; + try { + privateFiles = readdirSync(REGRESSION_FILES_DIR + "private/") + .filter((f) => (f.endsWith(".ifc") || f.endsWith(".ifczip"))) + .map((f) => path.join(REGRESSION_FILES_DIR + "private/", f)); + } catch (e) {} + return files.concat(privateFiles); +} + +async function CreateModelResuts(filename) { + let modelID; + console.log("Parsing: " + filename); + if (filename.includes(".ifczip")) { + let zip = new AdmZip(filename); + zip.getEntries().forEach(function (zipEntry) { + let ifcdata = zipEntry.getData(); + modelID = ifcAPI.OpenModel(ifcdata); + }); + } else { + let file = openSync(filename); + let retriever = function (offset, size) { + let data = new Uint8Array(size); + let bytesRead = readSync(file, data, 0, size, offset); + if (bytesRead <= 0) return new Uint8Array(0); + return data; + } + try { + modelID = ifcAPI.OpenModelFromCallback(retriever); + } catch (e) { + console.log(e); + } + } + let geometries = []; + ifcAPI.StreamAllMeshes(modelID, (mesh) => { + const placedGeometries = mesh.geometries; + for (let i = 0; i < placedGeometries.size(); i++) { + const placedGeometry = placedGeometries.get(i); + let meshObj = getPlacedGeometry(modelID, placedGeometry); + let geom = meshObj.geometry.applyMatrix4(meshObj.matrix); + geometries.push(geom); + } + }); + + console.log("Parsed Model: " + filename + " - Loading " + geometries.length + " geometries"); + if (geometries.length > 0) { + const combinedGeometry = BufferGeometryUtils.mergeGeometries(geometries); + const mat = new THREE.MeshStandardMaterial({ side: THREE.DoubleSide }); + mat.vertexColors = true; + const mergedMesh = new THREE.Mesh(combinedGeometry, mat); + const scene = new THREE.Scene(); + scene.add(mergedMesh); + const exporter = new GLTFExporter(); + + // Ensure output folder exists + if (!existsSync(OUTPUT_GLB_DIR)) { + mkdirSync(OUTPUT_GLB_DIR, { recursive: true }); + } + + return new Promise((resolve, reject) => { + exporter.parse( + scene, + (glb) => { + // glb is an ArrayBuffer when { binary: true } is used. + const baseName = path.basename(filename, path.extname(filename)); + const outFile = path.join(OUTPUT_GLB_DIR, baseName + ".glb"); + writeFileSync(outFile, Buffer.from(glb)); + console.log("Saved GLB file:", outFile); + resolve(glb); + }, + (e) => { reject(e); }, + { binary: true } + ); + }); + } +} + +function getPlacedGeometry(modelID, placedGeometry) { + const geometry = getBufferGeometry(modelID, placedGeometry); + const material = getMeshMaterial(placedGeometry.color); + const mesh = new THREE.Mesh(geometry, material); + mesh.matrix = getMeshMatrix(placedGeometry.flatTransformation); + mesh.matrixAutoUpdate = false; + return mesh; +} + +function getBufferGeometry(modelID, placedGeometry) { + const geometry = ifcAPI.GetGeometry(modelID, placedGeometry.geometryExpressID); + const verts = ifcAPI.GetVertexArray(geometry.GetVertexData(), geometry.GetVertexDataSize()); + const indices = ifcAPI.GetIndexArray(geometry.GetIndexData(), geometry.GetIndexDataSize()); + const bufferGeometry = ifcGeometryToBuffer(placedGeometry.color, verts, indices); + geometry.delete(); + return bufferGeometry; +} + +function getMeshMaterial(color) { + let colID = `${color.x}${color.y}${color.z}${color.w}`; + if (materials[colID]) { + return materials[colID]; + } + const col = new THREE.Color(color.x, color.y, color.z); + const material = new THREE.MeshStandardMaterial({ + color: col, + side: THREE.DoubleSide, + }); + material.transparent = color.w !== 1; + if (material.transparent) material.opacity = color.w; + materials[colID] = material; + return material; +} + +function getMeshMatrix(matrix) { + const mat = new THREE.Matrix4(); + mat.fromArray(matrix); + return mat; +} + +function ifcGeometryToBuffer(color, vertexData, indexData) { + const geometry = new THREE.BufferGeometry(); + let posFloats = new Float32Array(vertexData.length / 2); + let normFloats = new Float32Array(vertexData.length / 2); + let colorFloats = new Float32Array(vertexData.length / 2); + for (let i = 0; i < vertexData.length; i += 6) { + posFloats[i / 2 + 0] = vertexData[i + 0]; + posFloats[i / 2 + 1] = vertexData[i + 1]; + posFloats[i / 2 + 2] = vertexData[i + 2]; + normFloats[i / 2 + 0] = vertexData[i + 3]; + normFloats[i / 2 + 1] = vertexData[i + 4]; + normFloats[i / 2 + 2] = vertexData[i + 5]; + colorFloats[i / 2 + 0] = color.x; + colorFloats[i / 2 + 1] = color.y; + colorFloats[i / 2 + 2] = color.z; + } + geometry.setAttribute("position", new THREE.BufferAttribute(posFloats, 3)); + geometry.setAttribute("normal", new THREE.BufferAttribute(normFloats, 3)); + geometry.setAttribute("color", new THREE.BufferAttribute(colorFloats, 3)); + geometry.setIndex(new THREE.BufferAttribute(indexData, 1)); + return geometry; +} + +RunRegression().then(res => console.log("FINISHED")); diff --git a/yarn.lock b/yarn.lock index 4e7ce60f5..36cdd01cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -32,7 +32,7 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz" integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ== -"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.8.0": +"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.8.0", "@babel/core@>=7.0.0-beta.0 <8": version "7.26.9" resolved "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz" integrity sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw== @@ -281,129 +281,14 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" -"@esbuild/aix-ppc64@0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz#499600c5e1757a524990d5d92601f0ac3ce87f64" - integrity sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ== - -"@esbuild/android-arm64@0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz#b9b8231561a1dfb94eb31f4ee056b92a985c324f" - integrity sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g== - -"@esbuild/android-arm@0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.0.tgz#ca6e7888942505f13e88ac9f5f7d2a72f9facd2b" - integrity sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g== - -"@esbuild/android-x64@0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.0.tgz#e765ea753bac442dfc9cb53652ce8bd39d33e163" - integrity sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg== - -"@esbuild/darwin-arm64@0.25.0": - version "0.25.0" - resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz" - integrity sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw== - -"@esbuild/darwin-x64@0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz#91979d98d30ba6e7d69b22c617cc82bdad60e47a" - integrity sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg== - -"@esbuild/freebsd-arm64@0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz#b97e97073310736b430a07b099d837084b85e9ce" - integrity sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w== - -"@esbuild/freebsd-x64@0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz#f3b694d0da61d9910ec7deff794d444cfbf3b6e7" - integrity sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A== - -"@esbuild/linux-arm64@0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz#f921f699f162f332036d5657cad9036f7a993f73" - integrity sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg== - -"@esbuild/linux-arm@0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz#cc49305b3c6da317c900688995a4050e6cc91ca3" - integrity sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg== - -"@esbuild/linux-ia32@0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz#3e0736fcfab16cff042dec806247e2c76e109e19" - integrity sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg== - -"@esbuild/linux-loong64@0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz#ea2bf730883cddb9dfb85124232b5a875b8020c7" - integrity sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw== - -"@esbuild/linux-mips64el@0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz#4cababb14eede09248980a2d2d8b966464294ff1" - integrity sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ== - -"@esbuild/linux-ppc64@0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz#8860a4609914c065373a77242e985179658e1951" - integrity sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw== - -"@esbuild/linux-riscv64@0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz#baf26e20bb2d38cfb86ee282dff840c04f4ed987" - integrity sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA== - -"@esbuild/linux-s390x@0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz#8323afc0d6cb1b6dc6e9fd21efd9e1542c3640a4" - integrity sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA== - "@esbuild/linux-x64@0.25.0": version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz#08fcf60cb400ed2382e9f8e0f5590bac8810469a" + resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz" integrity sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw== -"@esbuild/netbsd-arm64@0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz#935c6c74e20f7224918fbe2e6c6fe865b6c6ea5b" - integrity sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw== - -"@esbuild/netbsd-x64@0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz#414677cef66d16c5a4d210751eb2881bb9c1b62b" - integrity sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA== - -"@esbuild/openbsd-arm64@0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz#8fd55a4d08d25cdc572844f13c88d678c84d13f7" - integrity sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw== - -"@esbuild/openbsd-x64@0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz#0c48ddb1494bbc2d6bcbaa1429a7f465fa1dedde" - integrity sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg== - -"@esbuild/sunos-x64@0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz#86ff9075d77962b60dd26203d7352f92684c8c92" - integrity sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg== - -"@esbuild/win32-arm64@0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz#849c62327c3229467f5b5cd681bf50588442e96c" - integrity sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw== - -"@esbuild/win32-ia32@0.25.0": - version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz#f62eb480cd7cca088cb65bb46a6db25b725dc079" - integrity sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA== - "@esbuild/win32-x64@0.25.0": version "0.25.0" - resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz#c8e119a30a7c8d60b9d2e22d2073722dde3b710b" + resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz" integrity sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ== "@hapi/bourne@^3.0.0": @@ -632,14 +517,6 @@ resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz" integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== -"@jridgewell/trace-mapping@0.3.9": - version "0.3.9" - resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" - integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": version "0.3.25" resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz" @@ -648,6 +525,14 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@koa/cors@^5.0.0": version "5.0.0" resolved "https://registry.npmjs.org/@koa/cors/-/cors-5.0.0.tgz" @@ -663,7 +548,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": +"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -847,7 +732,7 @@ dependencies: "@types/istanbul-lib-report" "*" -"@types/jest@^27.5.2": +"@types/jest@^27.0.0", "@types/jest@^27.5.2": version "27.5.2" resolved "https://registry.npmjs.org/@types/jest/-/jest-27.5.2.tgz" integrity sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA== @@ -864,7 +749,7 @@ "@types/node@*": version "22.13.10" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.10.tgz#df9ea358c5ed991266becc3109dc2dc9125d77e4" + resolved "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz" integrity sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw== dependencies: undici-types "~6.20.0" @@ -930,17 +815,9 @@ "@webgpu/types@*": version "0.1.55" - resolved "https://registry.yarnpkg.com/@webgpu/types/-/types-0.1.55.tgz#d094eb8ba998d9009afb25f9ba40f725169fe281" + resolved "https://registry.npmjs.org/@webgpu/types/-/types-0.1.55.tgz" integrity sha512-p97I8XEC1h04esklFqyIH+UhFrUcj8/1/vBWgc6lAK4jMJc+KbhUy8D4dquHYztFj6pHLqGcp/P1xvBBF4r3DA== -JSONStream@^1.3.5: - version "1.3.5" - resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz" - integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" - abab@^2.0.3, abab@^2.0.5: version "2.0.6" resolved "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz" @@ -1097,7 +974,7 @@ asynckit@^0.4.0: resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== -babel-jest@^27.5.1: +babel-jest@^27.5.1, "babel-jest@>=27.0.0 <28": version "27.5.1" resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz" integrity sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg== @@ -1205,7 +1082,7 @@ browser-process-hrtime@^1.0.0: resolved "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz" integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== -browserslist@^4.24.0: +browserslist@^4.24.0, "browserslist@>= 4.21.0": version "4.24.4" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz" integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A== @@ -1244,7 +1121,7 @@ byte-size@^9.0.0: resolved "https://registry.npmjs.org/byte-size/-/byte-size-9.0.1.tgz" integrity sha512-YLe9x3rabBrcI0cueCdLS2l5ONUKywcRpTs02B8KP9/Cimhj7o3ZccGrPnRvcbyHMbb7W79/3MUJl7iGgTXKEw== -bytes@3.1.2, bytes@^3.1.2: +bytes@^3.1.2, bytes@3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== @@ -1259,7 +1136,7 @@ cache-content-type@^1.0.0: call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + resolved "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz" integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== dependencies: es-errors "^1.3.0" @@ -1267,7 +1144,7 @@ call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: call-bound@^1.0.2, call-bound@^1.0.3: version "1.0.4" - resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + resolved "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz" integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== dependencies: call-bind-apply-helpers "^1.0.2" @@ -1290,7 +1167,7 @@ camelcase@^6.2.0: caniuse-lite@^1.0.30001688: version "1.0.30001703" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001703.tgz#977cb4920598c158f491ecf4f4f2cfed9e354718" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001703.tgz" integrity sha512-kRlAGTRWgPsOj7oARC9m1okJEXdL/8fekFVcxA8Hl7GH4r/sN4OJn/i6Flde373T50KS7Y37oFbMwlE8+F42kQ== ccount@^2.0.0: @@ -1557,20 +1434,13 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -debug@*, debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: +debug@*, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@4: version "4.4.0" resolved "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz" integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== dependencies: ms "^2.1.3" -debug@2.6.9: - version "2.6.9" - resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - debug@^3.1.0: version "3.2.7" resolved "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz" @@ -1578,6 +1448,13 @@ debug@^3.1.0: dependencies: ms "^2.1.1" +debug@2.6.9: + version "2.6.9" + resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + decimal.js@^10.2.1: version "10.5.0" resolved "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz" @@ -1618,7 +1495,7 @@ delegates@^1.0.0: resolved "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== -depd@2.0.0, depd@^2.0.0, depd@~2.0.0: +depd@^2.0.0, depd@~2.0.0, depd@2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== @@ -1695,7 +1572,7 @@ ee-first@1.1.1: electron-to-chromium@^1.5.73: version "1.5.113" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.113.tgz#1175b8ba4170541e44e9afa8b992e5bbfff0d150" + resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.113.tgz" integrity sha512-wjT2O4hX+wdWPJ76gWSkMhcHAV2PTMX+QetUCPYEdCIe+cxmgzzSSiGRCKW8nuh4mwKZlpv0xvoW7OF2X+wmHg== emittery@^0.8.1: @@ -1747,7 +1624,7 @@ es-errors@^1.3.0: es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: version "1.1.1" - resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + resolved "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz" integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== dependencies: es-errors "^1.3.0" @@ -1803,16 +1680,16 @@ escape-html@^1.0.3, escape-html@~1.0.3: resolved "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz" integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== -escape-string-regexp@5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz" - integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== - escape-string-regexp@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== +escape-string-regexp@5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz" + integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== + escodegen@^2.0.0: version "2.1.0" resolved "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz" @@ -1890,7 +1767,7 @@ fast-glob@^3.3.0: merge2 "^1.3.0" micromatch "^4.0.8" -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: +fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@2.x: version "2.1.0" resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== @@ -1962,11 +1839,6 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^2.3.2: - version "2.3.3" - resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz" - integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== - function-bind@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz" @@ -1984,7 +1856,7 @@ get-caller-file@^2.0.5: get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0: version "1.3.0" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + resolved "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz" integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== dependencies: call-bind-apply-helpers "^1.0.2" @@ -2005,7 +1877,7 @@ get-package-type@^0.1.0: get-proto@^1.0.0, get-proto@^1.0.1: version "1.0.1" - resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + resolved "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz" integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== dependencies: dunder-proto "^1.0.1" @@ -2151,17 +2023,6 @@ http-assert@^1.3.0: deep-equal "~1.0.1" http-errors "~1.8.0" -http-errors@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" - integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== - dependencies: - depd "2.0.0" - inherits "2.0.4" - setprototypeof "1.2.0" - statuses "2.0.1" - toidentifier "1.0.1" - http-errors@^1.6.3, http-errors@^1.7.3, http-errors@^1.8.1, http-errors@~1.8.0: version "1.8.1" resolved "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz" @@ -2183,6 +2044,17 @@ http-errors@~1.6.2: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" +http-errors@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" + integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== + dependencies: + depd "2.0.0" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses "2.0.1" + toidentifier "1.0.1" + http-proxy-agent@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz" @@ -2257,7 +2129,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@~2.0.3: +inherits@~2.0.3, inherits@2, inherits@2.0.4: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -2655,7 +2527,7 @@ jest-resolve-dependencies@^27.5.1: jest-regex-util "^27.5.1" jest-snapshot "^27.5.1" -jest-resolve@^27.5.1: +jest-resolve@*, jest-resolve@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz" integrity sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw== @@ -2808,7 +2680,7 @@ jest-worker@^27.5.1: merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^27.4.2: +jest@^27.0.0, jest@^27.4.2: version "27.5.1" resolved "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz" integrity sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ== @@ -2878,7 +2750,7 @@ json-stringify-safe@5: resolved "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== -json5@2.x, json5@^2.2.3: +json5@^2.2.3, json5@2.x: version "2.2.3" resolved "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== @@ -2888,6 +2760,14 @@ jsonparse@^1.2.0: resolved "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz" integrity sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg== +JSONStream@^1.3.5: + version "1.3.5" + resolved "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz" + integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== + dependencies: + jsonparse "^1.2.0" + through ">=2.2.7 <3" + junk@^4.0.1: version "4.0.1" resolved "https://registry.npmjs.org/junk/-/junk-4.0.1.tgz" @@ -2949,7 +2829,7 @@ koa-etag@^4.0.0: dependencies: etag "^1.8.1" -koa-is-json@1, koa-is-json@^1.0.0: +koa-is-json@^1.0.0, koa-is-json@1: version "1.0.0" resolved "https://registry.npmjs.org/koa-is-json/-/koa-is-json-1.0.0.tgz" integrity sha512-+97CtHAlWDx0ndt0J8y3P12EWLwTLMXIfMnYDev3wOTwH/RpBGMlfn4bDXlMEg1u73K6XRE9BbUp+5ZAYoRYWw== @@ -3004,7 +2884,7 @@ koa-static@^5.0.0: koa@^2.15.3: version "2.16.0" - resolved "https://registry.yarnpkg.com/koa/-/koa-2.16.0.tgz#0a82ed4d460774ff0b444e361cd6e4bd5c767ee3" + resolved "https://registry.npmjs.org/koa/-/koa-2.16.0.tgz" integrity sha512-Afhqq0Vq3W7C+/rW6IqHVBDLzqObwZ07JaUNUEF8yCQ6afiyFE3RAy+i7V0E46XOWlH7vPWn/x0vsZwNy6PWxw== dependencies: accepts "^1.3.5" @@ -3272,7 +3152,7 @@ make-dir@^5.0.0: resolved "https://registry.npmjs.org/make-dir/-/make-dir-5.0.0.tgz" integrity sha512-G0yBotnlWVonPClw+tq+xi4K7DZC9n96HjGTBDdHkstAVsDkfZhi1sTvZypXLpyQTbISBkDtK0E5XlUqDsShQg== -make-error@1.x, make-error@^1.1.1: +make-error@^1.1.1, make-error@1.x: version "1.3.6" resolved "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== @@ -3385,7 +3265,7 @@ micromark-util-symbol@^2.0.0: micromark-util-types@^2.0.0: version "2.0.2" - resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz#f00225f5f5a0ebc3254f96c36b6605c4b393908e" + resolved "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz" integrity sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA== micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.8: @@ -3396,16 +3276,16 @@ micromatch@^4.0.4, micromatch@^4.0.5, micromatch@^4.0.8: braces "^3.0.3" picomatch "^2.3.1" -mime-db@1.52.0: - version "1.52.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" - integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== - "mime-db@>= 1.43.0 < 2": version "1.53.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.53.0.tgz" integrity sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg== +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + mime-types@^2.1.18, mime-types@^2.1.35, mime-types@~2.1.18, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" @@ -3460,16 +3340,16 @@ morgan@^1.6.1: on-finished "~2.3.0" on-headers "~1.0.2" -ms@2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" - integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== - ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +ms@2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz" + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz" @@ -3509,7 +3389,7 @@ npm-run-path@^4.0.1: nwsapi@^2.2.0: version "2.2.18" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.18.tgz#3c4d7927e1ef4d042d319438ecfda6cd81b7ee41" + resolved "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.18.tgz" integrity sha512-p1TRH/edngVEHVbwqWnxUViEmq5znDvyB+Sik5cmuLpGOIfDf/39zLiq3swPF8Vakqn+gvNiOQAZu8djYlQILA== object-inspect@^1.13.3: @@ -3658,7 +3538,7 @@ path-exists@^4.0.0: resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz" integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== -path-is-absolute@1.0.1, path-is-absolute@^1.0.0: +path-is-absolute@^1.0.0, path-is-absolute@1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== @@ -3878,7 +3758,7 @@ resolve@^1.20.0: reusify@^1.0.4: version "1.1.0" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + resolved "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz" integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== rimraf@^3.0.0: @@ -3903,7 +3783,17 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@~5.1.0: + version "5.1.2" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@5.1.2: version "5.1.2" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== @@ -3934,16 +3824,26 @@ saxes@^5.0.1: dependencies: xmlchars "^2.2.0" -semver@7.x, semver@^7.3.2, semver@^7.5.3: - version "7.7.1" - resolved "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz" - integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== - semver@^6.3.0, semver@^6.3.1: version "6.3.1" resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== +semver@^7.3.2: + version "7.7.1" + resolved "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== + +semver@^7.5.3: + version "7.7.1" + resolved "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== + +semver@7.x: + version "7.7.1" + resolved "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz" + integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA== + serve-index-75lb@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/serve-index-75lb/-/serve-index-75lb-2.0.1.tgz" @@ -4093,25 +3993,25 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +statuses@^1.5.0, "statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2": + version "1.5.0" + resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" + integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== + statuses@2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== -"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@^1.5.0: - version "1.5.0" - resolved "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz" - integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== - stream-log-stats@^3.0.2: version "3.0.2" resolved "https://registry.npmjs.org/stream-log-stats/-/stream-log-stats-3.0.2.tgz" integrity sha512-393j7aeF9iRdHvyANqEQU82UQmpw2CTxgsT83caefh+lOxavVLbVrw8Mr4zjXeZLh2+xeHZMKfVx4T0rJ/EchA== dependencies: - JSONStream "^1.3.5" ansi-escape-sequences "^5.1.2" byte-size "^6.2.0" common-log-format "^1.0.0" + JSONStream "^1.3.5" lodash.throttle "^4.1.1" stream-via "^1.0.4" table-layout "~1.0.0" @@ -4134,6 +4034,13 @@ streaming-json-stringify@3: json-stringify-safe "5" readable-stream "2" +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + string-length@^4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" @@ -4169,13 +4076,6 @@ string-width@^5.0.1, string-width@^5.1.2: emoji-regex "^9.2.2" strip-ansi "^7.0.1" -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - stringify-entities@^4.0.0: version "4.0.4" resolved "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz" @@ -4362,7 +4262,7 @@ ts-jest@^27.0.7: semver "7.x" yargs-parser "20.x" -ts-node@^10.9.1: +ts-node@^10.9.1, ts-node@>=9.0.0: version "10.9.2" resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz" integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== @@ -4422,7 +4322,7 @@ typedoc@^0.26.3: shiki "^1.16.2" yaml "^2.5.1" -typescript@^4.7.0: +typescript@^4.7.0, typescript@>=2.7, "typescript@>=3.8 <5.0", "typescript@4.6.x || 4.7.x || 4.8.x || 4.9.x || 5.0.x || 5.1.x || 5.2.x || 5.3.x || 5.4.x || 5.5.x || 5.6.x": version "4.9.5" resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz" integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== @@ -4709,7 +4609,7 @@ yaml@^2.5.1: resolved "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz" integrity sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA== -yargs-parser@20.x, yargs-parser@^20.2.2: +yargs-parser@^20.2.2, yargs-parser@20.x: version "20.2.9" resolved "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== From be02d86465e559ea192b37a0077b2f33702f078c Mon Sep 17 00:00:00 2001 From: ifcapps Date: Fri, 28 Mar 2025 19:14:32 +0100 Subject: [PATCH 23/23] exclude polygons during triangle mesh export --- src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp | 6 ++++-- src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp b/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp index fa46d2249..5548ab190 100644 --- a/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp +++ b/src/cpp/web-ifc/geometry/IfcGeometryLoader.cpp @@ -3338,8 +3338,10 @@ IfcProfile IfcGeometryLoader::GetProfile(uint32_t expressID) const glm::dmat4 IfcGeometryLoader::GetLocalPlacement(uint32_t expressID) const { - if(_expressIDToPlacement.contains(expressID)) { - return _expressIDToPlacement[expressID]; + auto itFind = _expressIDToPlacement.find(expressID); + if (itFind != _expressIDToPlacement.end()) + { + return itFind->second; } else { diff --git a/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp b/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp index d6ee03baf..f50df562a 100644 --- a/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp +++ b/src/cpp/web-ifc/geometry/IfcGeometryProcessor.cpp @@ -1607,6 +1607,10 @@ namespace webifc::geometry auto geom = _expressIDToGeometry[composedMesh.expressID]; + if (geom.isPolygon) + { + return; // only triangles here + } if (geometry.testReverse()) geom.ReverseFaces(); auto translation = glm::dmat4(1.0);