From 4fc2f623cff39dcf47bf05d7e4aa41e8c23d457f Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Tue, 2 Dec 2025 16:32:37 +0100 Subject: [PATCH] Count number of emitted normals in OBJ serializer, which might be less than verts when writing line geometries --- .../mapping/IfcOpenCrossProfileDef.cpp | 94 +++++++++++++++++++ src/ifcgeom/mapping/mapping.i | 3 + src/serializers/WavefrontObjSerializer.cpp | 30 +++--- src/serializers/WavefrontObjSerializer.h | 2 +- 4 files changed, 116 insertions(+), 13 deletions(-) create mode 100644 src/ifcgeom/mapping/IfcOpenCrossProfileDef.cpp diff --git a/src/ifcgeom/mapping/IfcOpenCrossProfileDef.cpp b/src/ifcgeom/mapping/IfcOpenCrossProfileDef.cpp new file mode 100644 index 0000000000..d3083a23e7 --- /dev/null +++ b/src/ifcgeom/mapping/IfcOpenCrossProfileDef.cpp @@ -0,0 +1,94 @@ +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +#include "../../ifcgeom/profile_helper.h" +#include "../../ifcgeom/infra_sweep_helper.h" + +#include +const double PI = boost::math::constants::pi(); + +#ifdef SCHEMA_HAS_IfcOpenCrossProfileDef + + +taxonomy::ptr mapping::map_impl(const IfcSchema::IfcOpenCrossProfileDef* inst) { + if (inst->ProfileType() != IfcSchema::IfcProfileTypeEnum::IfcProfileType_CURVE) { + Logger::Warning("Expected IfcOpenCrossProfileDef.ProfileType to be CURVE", inst); + return nullptr; + } + + std::vector points; + taxonomy::point3::ptr start; + if (inst->OffsetPoint()) { + start = taxonomy::cast(map(inst->OffsetPoint())); + } else { + start = taxonomy::make(0., 0., 0.); + } + points.push_back(start); + + boost::optional> tags = inst->Tags(); + boost::optional tag = boost::none; + if (tags.has_value() && !tags.get().empty()) { + tag = tags.get()[0]; + } + start->tag = tag; + + auto widths = inst->Widths(); + auto angles = inst->Slopes(); // these are actually angles, but the attribute is called Slopes + + if (widths.size() != angles.size()) { + Logger::Warning("Expected Widths and Slopes to be equal length, but got " + std::to_string(widths.size()) + " and " + std::to_string(angles.size()) + " respectively", inst); + return nullptr; + } + + auto horizontal_widths = inst->HorizontalWidths(); + + double x = start->ccomponents().x(); + double y = start->ccomponents().y(); + double z = start->ccomponents().z(); + for (size_t i = 0; i < widths.size(); ++i) { + double w = widths[i] * length_unit_; + double a = PI - angles[i] * angle_unit_; + double dx = (horizontal_widths ? w : w * std::cos(a)); + double dy = (horizontal_widths ? w * std::tan(a) : w * std::sin(a)); + double dz = 0.; + x -= dx; // subtract because X is positive to the left + y += dy; + z += dz; + + if (tags.has_value() && !tags.get().empty()) { + tag = tags.get()[i+1]; + } + + points.push_back(taxonomy::make(x, y, z, tag)); + } + + auto mapped = polygon_from_points(points); + if (mapped->kind() == taxonomy::LOOP) { + auto r = taxonomy::loop::ptr((taxonomy::loop*)mapped->clone_()); + r->closed = false; + return r; + } + return mapped; +} + +#endif diff --git a/src/ifcgeom/mapping/mapping.i b/src/ifcgeom/mapping/mapping.i index adc2e3c10b..c67d0f7f4a 100644 --- a/src/ifcgeom/mapping/mapping.i +++ b/src/ifcgeom/mapping/mapping.i @@ -135,6 +135,9 @@ BIND(IfcFixedReferenceSweptAreaSolid) #ifdef SCHEMA_HAS_IfcSectionedSolidHorizontal BIND(IfcSectionedSolidHorizontal) #endif +#ifdef SCHEMA_HAS_IfcOpenCrossProfileDef +BIND(IfcOpenCrossProfileDef) +#endif #ifdef SCHEMA_HAS_IfcSectionedSurface BIND(IfcSectionedSurface) #endif diff --git a/src/serializers/WavefrontObjSerializer.cpp b/src/serializers/WavefrontObjSerializer.cpp index 300fceeb88..f29612a65e 100644 --- a/src/serializers/WavefrontObjSerializer.cpp +++ b/src/serializers/WavefrontObjSerializer.cpp @@ -93,7 +93,9 @@ void WaveFrontOBJSerializer::write(const IfcGeom::TriangulationElement* o) const IfcGeom::Representation::Triangulation& mesh = o->geometry(); - const int vcount = (int)mesh.verts().size() / 3; + size_t vcount = mesh.verts().size() / 3; + size_t ncount = mesh.normals().size() / 3; + for (auto it = mesh.verts().begin(); it != mesh.verts().end();) { const double x = *(it++); const double y = *(it++); @@ -139,22 +141,25 @@ void WaveFrontOBJSerializer::write(const IfcGeom::TriangulationElement* o) previous_material_id = material_id; } - const int v1 = *(it++)+vcount_total; - const int v2 = *(it++)+vcount_total; - const int v3 = *(it++)+vcount_total; + const int v1 = *(it++) + vcount_total; + const int v2 = *(it++) + vcount_total; + const int v3 = *(it++) + vcount_total; - if (has_normals && has_uvs) { - obj_stream.stream << "f " << v1 << "/" << v1 << "/" << v1 << " " - << v2 << "/" << v2 << "/" << v2 << " " - << v3 << "/" << v3 << "/" << v3 << "\n"; + const int n1 = v1 - vcount_total + ncount_total; + const int n2 = v2 - vcount_total + ncount_total; + const int n3 = v3 - vcount_total + ncount_total; + + if (has_normals && has_uvs) { + obj_stream.stream << "f " << v1 << "/" << n1 << "/" << n1 << " " + << v2 << "/" << n2 << "/" << n2 << " " + << v3 << "/" << n3 << "/" << n3 << "\n"; } else if (has_normals) { - obj_stream.stream << "f " << v1 << "//" << v1 << " " - << v2 << "//" << v2 << " " - << v3 << "//" << v3 << "\n"; + obj_stream.stream << "f " << v1 << "//" << n1 << " " + << v2 << "//" << n2 << " " + << v3 << "//" << n3 << "\n"; } else { obj_stream.stream << "f " << v1 << " " << v2 << " " << v3 << "\n"; } - } std::set faces_set (mesh.faces().begin(), mesh.faces().end()); @@ -189,4 +194,5 @@ void WaveFrontOBJSerializer::write(const IfcGeom::TriangulationElement* o) } vcount_total += vcount; + ncount_total += ncount; } diff --git a/src/serializers/WavefrontObjSerializer.h b/src/serializers/WavefrontObjSerializer.h index 4085dfa376..d3ea41f69e 100644 --- a/src/serializers/WavefrontObjSerializer.h +++ b/src/serializers/WavefrontObjSerializer.h @@ -32,7 +32,7 @@ class SERIALIZERS_API WaveFrontOBJSerializer : public WriteOnlyGeometrySerialize private: stream_or_filename obj_stream; stream_or_filename mtl_stream; - unsigned int vcount_total; + size_t vcount_total, ncount_total; std::set materials; public: WaveFrontOBJSerializer(const stream_or_filename& obj_filename, const stream_or_filename& mtl_filename, const ifcopenshell::geometry::Settings& geometry_settings, const ifcopenshell::geometry::SerializerSettings& settings);