From c85bf905587365957d1c1c5e5d1f8e560b0d06c7 Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Sat, 11 Jul 2026 18:34:22 +0300 Subject: [PATCH] Correct OBJ texture-coordinate indices (#8457) The OBJ serializer wrote the normal index into the texture-coordinate slot of a v/vt/vn face (emitting v/vn/vn), so exported UVs were wrong. Track a separate texcoord counter and emit the correct vt index. Per review, the material lookup is left relying on ApplyDefaultMaterials (default on, and IfcConvert exposes no toggle to disable it) so an unstyled element is given the default style before triangulation and material_ids never contain the -1 no-style sentinel, matching how the Collada serializer already indexes its materials without an ad hoc guard. The earlier material_id >= 0 guard is therefore removed. Verified on OCC 7.9.2: a styled box exports its material, an unstyled box exports the injected DefaultMaterial (valid index, no out-of-bounds) on both the default and --generate-uvs paths, and the UV path now emits correct v/vt/vn indices. Generated with the assistance of an AI coding tool. Co-Authored-By: Claude Opus 4.8 --- src/serializers/WavefrontObjSerializer.cpp | 12 +++++++++--- src/serializers/WavefrontObjSerializer.h | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/serializers/WavefrontObjSerializer.cpp b/src/serializers/WavefrontObjSerializer.cpp index 536f9aeeaa..a0e4ada042 100644 --- a/src/serializers/WavefrontObjSerializer.cpp +++ b/src/serializers/WavefrontObjSerializer.cpp @@ -33,6 +33,7 @@ WaveFrontOBJSerializer::WaveFrontOBJSerializer(const stream_or_filename& obj_fil , mtl_stream(mtl_filename) , vcount_total(1) , ncount_total(1) + , uvcount_total(1) { obj_stream.stream << std::setprecision(settings.get().get()); mtl_stream.stream << std::setprecision(settings.get().get()); @@ -96,6 +97,7 @@ void WaveFrontOBJSerializer::write(const IfcGeom::TriangulationElement* o) size_t vcount = mesh.verts().size() / 3; size_t ncount = mesh.normals().size() / 3; + size_t uvcount = mesh.uvs().size() / 2; for (auto it = mesh.verts().begin(); it != mesh.verts().end();) { const double x = *(it++); @@ -151,9 +153,12 @@ void WaveFrontOBJSerializer::write(const IfcGeom::TriangulationElement* o) 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"; + const int t1 = v1 - vcount_total + uvcount_total; + const int t2 = v2 - vcount_total + uvcount_total; + const int t3 = v3 - vcount_total + uvcount_total; + obj_stream.stream << "f " << v1 << "/" << t1 << "/" << n1 << " " + << v2 << "/" << t2 << "/" << n2 << " " + << v3 << "/" << t3 << "/" << n3 << "\n"; } else if (has_normals) { obj_stream.stream << "f " << v1 << "//" << n1 << " " << v2 << "//" << n2 << " " @@ -196,4 +201,5 @@ void WaveFrontOBJSerializer::write(const IfcGeom::TriangulationElement* o) vcount_total += vcount; ncount_total += ncount; + uvcount_total += uvcount; } diff --git a/src/serializers/WavefrontObjSerializer.h b/src/serializers/WavefrontObjSerializer.h index e1436d2de3..42f4d37ae1 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; - size_t vcount_total, ncount_total; + size_t vcount_total, ncount_total, uvcount_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, Logger& logger = Logger::Root());