mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
serializers: handle point/vertex-only representations
Addresses aothms's review comment on PR #8759: "I think serializers will also need to be adapted. OBJ, Collada, etc." Adds a proper "points" primitive to IfcGeom::Representation:: Triangulation (points_/points_item_ids_/points_material_ids_, addPoint()), alongside the existing faces_/edges_, for vertices that stand alone as a representation item (Vertex/Point/PointCloud, see #134/#1409/#5218) rather than belonging to a face or edge. points_material_ids_ is a separate array from the shared faces+edges material_ids_, not folded into it: material_ids_ is a single running sequence consumed in strict per-item emission order by every writer below, and interleaving points into it would desync that sequence whenever an item with points is triangulated before/after an item with edges in the same file. - WaveFrontOBJSerializer: emits "p" records for these points (the only way OBJ marks a vertex as a rendered primitive in its own right). Previously these vertices were written as bare "v" lines with no primitive referencing them at all. - GltfSerializer: adds a POINTS (mode 0) primitive branch. Also fixes a latent bug: write() unconditionally returned early when material_ids() was empty, which is exactly the case for point-only geometry, silently dropping the whole node/mesh; and the later material-id iteration would have incremented an already-at-end iterator (UB) had that early return not caught it first. - ColladaSerializer: COLLADA's schema has no points primitive (only lines/polygons/triangles), so there is nothing to write beyond the raw position source; logs a warning instead of silently producing an empty-but-successful mesh. - IfcGeomWrapper.i: exposes points()/points_item_ids()/ points_material_ids() (plus *_buffer variants) symmetrically with the existing edges_item_ids() etc. Ported onto a clean v0.8.0 base (the original prototype was built on top of Dion Moult's experimental ifcviewer-wgpu branch, PR #8759). Fixes two build-time mismatches against that base's API: the logger header is ifcparse/IfcLogger.h here, and Logger only exposes a capitalized Warning(code_prefix, code_number, message, instance) method, not a lowercase warning(); and material name sanitization is IfcUtil::sanitate_material_name(), matching every other call site in this file, not ifcopenshell::sanitate_material_name(). This contribution was produced with the assistance of an AI coding tool.
This commit is contained in:
@@ -112,12 +112,20 @@ namespace IfcGeom {
|
||||
std::vector<std::vector<std::vector<int>>> polyhedral_faces_with_holes_;
|
||||
|
||||
std::vector<int> 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<int> points_;
|
||||
std::vector<double> normals_;
|
||||
std::vector<double> uvs_;
|
||||
std::vector<int> material_ids_;
|
||||
std::vector<ifcopenshell::geometry::taxonomy::style::ptr> materials_;
|
||||
std::vector<int> item_ids_;
|
||||
std::vector<int> edges_item_ids_;
|
||||
std::vector<int> points_item_ids_;
|
||||
// Own array rather than material_ids_, to avoid desyncing the
|
||||
// shared faces/edges running sequence when items are interleaved.
|
||||
std::vector<int> points_material_ids_;
|
||||
size_t weld_offset_;
|
||||
VertexKeyMap welds;
|
||||
|
||||
@@ -132,6 +140,7 @@ namespace IfcGeom {
|
||||
const std::vector<std::vector<int>>& polyhedral_faces_without_holes() const { return polyhedral_faces_without_holes_; }
|
||||
const std::vector<std::vector<std::vector<int>>>& polyhedral_faces_with_holes() const { return polyhedral_faces_with_holes_; }
|
||||
const std::vector<int>& edges() const { return edges_; }
|
||||
const std::vector<int>& points() const { return points_; }
|
||||
const std::vector<double>& normals() const { return normals_; }
|
||||
const std::vector<double>& uvs() const { return uvs_; }
|
||||
std::vector<double>& uvs_ref() { return uvs_; }
|
||||
@@ -139,6 +148,8 @@ namespace IfcGeom {
|
||||
const std::vector<ifcopenshell::geometry::taxonomy::style::ptr>& materials() const { return materials_; }
|
||||
const std::vector<int>& item_ids() const { return item_ids_; }
|
||||
const std::vector<int>& edges_item_ids() const { return edges_item_ids_; }
|
||||
const std::vector<int>& points_item_ids() const { return points_item_ids_; }
|
||||
const std::vector<int>& 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<std::pair<int, int>, int>& edgecount);
|
||||
|
||||
void resetWelds() {
|
||||
|
||||
@@ -670,10 +670,18 @@ struct ShapeRTTI : public boost::static_visitor<PyObject*>
|
||||
return vector_to_buffer(self->edges());
|
||||
}
|
||||
|
||||
std::pair<const char*, size_t> points_buffer() const {
|
||||
return vector_to_buffer(self->points());
|
||||
}
|
||||
|
||||
std::pair<const char*, size_t> material_ids_buffer() const {
|
||||
return vector_to_buffer(self->material_ids());
|
||||
}
|
||||
|
||||
std::pair<const char*, size_t> points_material_ids_buffer() const {
|
||||
return vector_to_buffer(self->points_material_ids());
|
||||
}
|
||||
|
||||
std::pair<const char*, size_t> item_ids_buffer() const {
|
||||
return vector_to_buffer(self->item_ids());
|
||||
}
|
||||
@@ -682,6 +690,10 @@ struct ShapeRTTI : public boost::static_visitor<PyObject*>
|
||||
return vector_to_buffer(self->edges_item_ids());
|
||||
}
|
||||
|
||||
std::pair<const char*, size_t> points_item_ids_buffer() const {
|
||||
return vector_to_buffer(self->points_item_ids());
|
||||
}
|
||||
|
||||
std::pair<const char*, size_t> verts_buffer() const {
|
||||
return vector_to_buffer(self->verts());
|
||||
}
|
||||
@@ -728,6 +740,7 @@ struct ShapeRTTI : public boost::static_visitor<PyObject*>
|
||||
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<PyObject*>
|
||||
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)
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <cmath>
|
||||
|
||||
#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<double>& uvs, const std::vector<std::string>& 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();
|
||||
|
||||
@@ -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<int>::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<int>& 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user