diff --git a/src/ifcgeom/IfcGeomRepresentation.h b/src/ifcgeom/IfcGeomRepresentation.h index 2bafa9ab88..bb39e0bdae 100644 --- a/src/ifcgeom/IfcGeomRepresentation.h +++ b/src/ifcgeom/IfcGeomRepresentation.h @@ -112,12 +112,20 @@ namespace IfcGeom { std::vector>> polyhedral_faces_with_holes_; std::vector edges_; + // Vertices that stand alone as a representation item in their own + // right (Vertex/Point/PointCloud, see #134/#1409/#5218), each + // entry an index into verts_, analogous to edges_/faces_. + std::vector points_; std::vector normals_; std::vector uvs_; std::vector material_ids_; std::vector materials_; std::vector item_ids_; std::vector edges_item_ids_; + std::vector points_item_ids_; + // Own array rather than material_ids_, to avoid desyncing the + // shared faces/edges running sequence when items are interleaved. + std::vector points_material_ids_; size_t weld_offset_; VertexKeyMap welds; @@ -132,6 +140,7 @@ namespace IfcGeom { const std::vector>& polyhedral_faces_without_holes() const { return polyhedral_faces_without_holes_; } const std::vector>>& polyhedral_faces_with_holes() const { return polyhedral_faces_with_holes_; } const std::vector& edges() const { return edges_; } + const std::vector& points() const { return points_; } const std::vector& normals() const { return normals_; } const std::vector& uvs() const { return uvs_; } std::vector& uvs_ref() { return uvs_; } @@ -139,6 +148,8 @@ namespace IfcGeom { const std::vector& materials() const { return materials_; } const std::vector& item_ids() const { return item_ids_; } const std::vector& edges_item_ids() const { return edges_item_ids_; } + const std::vector& points_item_ids() const { return points_item_ids_; } + const std::vector& points_material_ids() const { return points_material_ids_; } Triangulation(const BRep& shape_model); @@ -222,6 +233,12 @@ namespace IfcGeom { edges_item_ids_.push_back(item_id); } + void addPoint(int item_id, int style, int vertex_index) { + points_.push_back(vertex_index); + points_item_ids_.push_back(item_id); + points_material_ids_.push_back(style); + } + void registerEdgeCount(int n1, int n2, std::map, int>& edgecount); void resetWelds() { diff --git a/src/ifcwrap/IfcGeomWrapper.i b/src/ifcwrap/IfcGeomWrapper.i index b6dffb3b6e..00dbf39ebc 100644 --- a/src/ifcwrap/IfcGeomWrapper.i +++ b/src/ifcwrap/IfcGeomWrapper.i @@ -670,10 +670,18 @@ struct ShapeRTTI : public boost::static_visitor return vector_to_buffer(self->edges()); } + std::pair points_buffer() const { + return vector_to_buffer(self->points()); + } + std::pair material_ids_buffer() const { return vector_to_buffer(self->material_ids()); } + std::pair points_material_ids_buffer() const { + return vector_to_buffer(self->points_material_ids()); + } + std::pair item_ids_buffer() const { return vector_to_buffer(self->item_ids()); } @@ -682,6 +690,10 @@ struct ShapeRTTI : public boost::static_visitor return vector_to_buffer(self->edges_item_ids()); } + std::pair points_item_ids_buffer() const { + return vector_to_buffer(self->points_item_ids()); + } + std::pair verts_buffer() const { return vector_to_buffer(self->verts()); } @@ -728,6 +740,7 @@ struct ShapeRTTI : public boost::static_visitor return self.polyhedral_faces_with_holes faces = property(get_faces) edges = property(edges) + points = property(points) material_ids = property(material_ids) materials = property(materials) verts = property(verts) @@ -735,12 +748,17 @@ struct ShapeRTTI : public boost::static_visitor item_ids = property(item_ids) uvs = property(uvs) edges_item_ids = property(edges_item_ids) + points_item_ids = property(points_item_ids) + points_material_ids = property(points_material_ids) faces_buffer = property(faces_buffer) edges_buffer = property(edges_buffer) + points_buffer = property(points_buffer) material_ids_buffer = property(material_ids_buffer) item_ids_buffer = property(item_ids_buffer) edges_item_ids_buffer = property(edges_item_ids_buffer) + points_item_ids_buffer = property(points_item_ids_buffer) + points_material_ids_buffer = property(points_material_ids_buffer) verts_buffer = property(verts_buffer) normals_buffer = property(normals_buffer) colors_buffer = property(colors_buffer) diff --git a/src/serializers/ColladaSerializer.cpp b/src/serializers/ColladaSerializer.cpp index 0b1676c338..1f564edcae 100644 --- a/src/serializers/ColladaSerializer.cpp +++ b/src/serializers/ColladaSerializer.cpp @@ -35,6 +35,7 @@ #include #include "../ifcparse/utils.h" +#include "../ifcparse/IfcLogger.h" static std::string& collada_id(std::string& s) { @@ -70,7 +71,15 @@ void ColladaSerializer::ColladaExporter::ColladaGeometries::write( const std::vector& uvs, const std::vector& material_references) { openMesh(mesh_id); - + + if (faces.empty() && edges.empty() && !positions.empty()) { + // Vertex/Point/PointCloud representation (#134/#1409/#5218). COLLADA + // has no native point primitive, only lines/polygons/triangles, so + // there is nothing meaningful to write beyond the raw position + // source below: the geometry will not be visible in the scene. + serializer->logger().Warning("GEO", 410, "Point/vertex-only geometry (" + mesh_id + ") has no COLLADA representation and will not be visible"); + } + // The normals vector can be empty for example when the WELD_VERTICES setting is used. // IfcOpenShell does not provide them with multiple face normals collapsed into a single vertex. const bool has_normals = !normals.empty(); diff --git a/src/serializers/GltfSerializer.cpp b/src/serializers/GltfSerializer.cpp index adc5b0961f..f9bb601fb5 100644 --- a/src/serializers/GltfSerializer.cpp +++ b/src/serializers/GltfSerializer.cpp @@ -182,7 +182,9 @@ size_t write_accessor(json& j, std::ofstream& ofs, It begin, It end, int bufferV } void GltfSerializer::write(const IfcGeom::TriangulationElement* o) { - if (o->geometry().material_ids().empty()) { + // material_ids() covers faces/edges only; points (#134/#1409/#5218) have + // their own points_material_ids(). + if (o->geometry().material_ids().empty() && o->geometry().points_material_ids().empty()) { return; } @@ -297,21 +299,37 @@ void GltfSerializer::write(const IfcGeom::TriangulationElement* o) { auto it = meshes_.find(o->geometry().id()); if (it == meshes_.end()) { - auto mid1 = o->geometry().material_ids().begin(); - auto mid0 = mid1; - std::vector::const_iterator fid0; int stride; int primitive_type; + // Points have their own material id array, since material_ids() is a + // single running sequence shared between faces and edges only. + const std::vector& material_ids = !o->geometry().faces().empty() || !o->geometry().edges().empty() + ? o->geometry().material_ids() + : o->geometry().points_material_ids(); + + auto mid1 = material_ids.begin(); + auto mid0 = mid1; + + if (mid0 == material_ids.end()) { + // No faces, edges or points at all, nothing to write. + return; + } if (!o->geometry().faces().empty()) { stride = 3; fid0 = o->geometry().faces().begin(); primitive_type = PRIM_TRIANGLES; - } else { + } else if (!o->geometry().edges().empty()) { stride = 2; fid0 = o->geometry().edges().begin(); primitive_type = PRIM_LINES; + } else { + // Vertex/Point/PointCloud representation with no owning face or + // edge, see #134/#1409/#5218. + stride = 1; + fid0 = o->geometry().points().begin(); + primitive_type = PRIM_POINTS; } json mesh; @@ -327,7 +345,7 @@ void GltfSerializer::write(const IfcGeom::TriangulationElement* o) { // material. mid1++; - if ((mid1 == o->geometry().material_ids().end()) || (*mid1 != *mid0)) { + if ((mid1 == material_ids.end()) || (*mid1 != *mid0)) { auto n = std::distance(mid0, mid1); auto fid1 = fid0 + n * stride; @@ -362,7 +380,7 @@ void GltfSerializer::write(const IfcGeom::TriangulationElement* o) { mesh["primitives"].push_back(primitive); - if (mid1 == o->geometry().material_ids().end()) { + if (mid1 == material_ids.end()) { break; } diff --git a/src/serializers/WavefrontObjSerializer.cpp b/src/serializers/WavefrontObjSerializer.cpp index 5d178329b3..619dd7716b 100644 --- a/src/serializers/WavefrontObjSerializer.cpp +++ b/src/serializers/WavefrontObjSerializer.cpp @@ -194,6 +194,29 @@ void WaveFrontOBJSerializer::write(const IfcGeom::TriangulationElement* o) obj_stream.stream << "l " << v1 << " " << v2 << "\n"; } + // Standalone points (Vertex/Point/PointCloud representations, no owning + // face or edge), see #134/#1409/#5218. OBJ's "p" element is the only way + // to mark a vertex as a rendered primitive in its own right. Points have + // their own material id array (not material_ids_/material_it above). + auto point_material_it = mesh.points_material_ids().begin(); + for (int point_index : mesh.points()) { + const int material_id = *(point_material_it++); + + if (material_id != previous_material_id) { + const ifcopenshell::geometry::taxonomy::style::ptr material = mesh.materials()[material_id]; + std::string material_name = material->name; + IfcUtil::sanitate_material_name(material_name); + obj_stream.stream << "usemtl " << material_name << "\n"; + if (materials.find(material_name) == materials.end()) { + writeMaterial(material); + materials.insert(material_name); + } + previous_material_id = material_id; + } + + obj_stream.stream << "p " << (point_index + vcount_total) << "\n"; + } + vcount_total += vcount; ncount_total += ncount; }