Exploratory proof of concept for #134, #1409 and #5218: IfcVertexPoint,
IfcCartesianPoint and IfcCartesianPointList3D used as top-level
representation items ("Vertex"/"Point"/"PointCloud") currently raise
"Failed to process shape" because AbstractKernel::convert_impl for
taxonomy::point3 is never implemented and point3 cannot appear as a
child of the generic items collection.
This makes point3/direction3 derive from geom_item instead of plain
item, so a lone point3 can stand in as a representation item, and adds:
- OpenCascadeKernel::convert_impl(point3): a single point becomes a
TopoDS_Vertex wrapped in a TopoDS_Compound, as aothms suggested in
#5218.
- OpenCascadeKernel::convert_impl(collection): a bulk fast path for a
collection made up entirely of point3 children (e.g. a whole
IfcCartesianPointList3D) that builds ONE compound with all vertices
in a single pass, instead of paying the generic per-item conversion
overhead (cache lookup, heap allocation, a separate Triangulate()
call) once per point.
- mapping for IfcVertexPoint (as a top-level item) and
IfcCartesianPointList3D.
- loose-vertex emission in OpenCascadeShape::Triangulate, since a
vertex-only shape previously triangulated to nothing.
This directly tests aothms's "the overhead is enormous" concern from
#5218. Benchmarked on this machine (Apple M-series, Release build):
- Normal (non-point) geometry is unaffected: a 5178-shape real model
processes in 4.48s before this change and 4.49s after (~0.3%, noise).
- The bulk fast path scales linearly and cheaply: ~0.5-0.9 us/point for
an IfcCartesianPointList3D from 1k to 100k points (100k points in
~93ms total).
- With the fast path disabled (pure per-item conversion, i.e. the naive
reading of "a TopoDS_Compound of TopoDS_Vertex" with no batching),
scaling is still linear, not quadratic, but ~4-7x slower per point
(~3.5-4 us/point at the same scale, 100k points in ~380ms).
So aothms's concern is real as a constant-factor tax from going through
full OCCT BRep objects (TopoDS_Vertex/Compound, shared_ptr taxonomy
nodes, per-item caching) rather than flat coordinate arrays, but it is
not the asymptotic blowup "enormous overhead" might suggest, and a
reasonably-scoped batching fast path narrows the gap substantially.
Given Bonsai already has a working, accepted Python-side bypass for
this (create_point_cloud_mesh / create_structural_point_connection_mesh
in bonsai/tool/loader.py and geometry.py), this is offered as a
proof of concept for evaluation, not a claim that it should override
the prior "something for 0.9" call.
IfcCartesianPointList2D ("PointCloud" in 2D) is intentionally out of
scope for this prototype.
Adds pytest coverage (no Catch2/C++ test harness exists in this
codebase) for all three representation types plus a 1000-point
round-trip/timing sanity check.
Generated with the assistance of an AI coding tool.
IfcSurfaceCurveSweptAreaSolid regressed in 0.8 for geometry far from the
origin (for example parapets on a georeferenced building), which went
missing or glitched.
The kernel offsets the directrix toward the origin when it is far away
(mean.norm() > 1e2), storing the offset copy in a local curve variable and
setting applied_temporary_offset so the finished solid is translated back by
+mean. But the wire was still built from scs->curve, the un-offset original,
so the offset never took effect and the result was translated by +mean from
its correct location. Build the wire from curve instead. When no offset is
applied curve aliases scs->curve, so near-origin geometry is unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
src/ifcgeom/kernels/opencascade/boolean_utils.cpp, OpenCascadeKernel.cpp,
and boolean_result.cpp logged diagnostics (including the "Processed
fully in 2D" family of messages) through the global Logger::Root()
singleton. IfcConvert's main() constructs its own Logger and wires it
to --log-file via SetOutput(), then threads that instance through
Converter/kernel constructors as logger_ (see AbstractKernel). Since
Logger::Root() is never itself configured with an output stream, every
Notice/Warning/Message call through it was silently dropped instead of
reaching the log file - Logger::Message's log1_/log2_ null checks just
no-op.
This made src/ifcopenshell-python/test/test_wall_opening.py fail: it
asserts on specific log messages that the underlying boolean-op code
was still emitting correctly, just to nowhere. The geometry itself was
never wrong.
Add a Logger*, defaulting to null, to boolean_settings (with a log()
accessor falling back to Logger::Root() for the few remaining
call sites with no injected logger available), thread it through
eliminate_narrow_operands and boolean_subtraction_2d_using_builder,
and have OpenCascadeKernel/boolean_result.cpp populate it from their
inherited logger_ member instead of relying on the global singleton.
Generated with the assistance of an AI coding tool.
A face whose inner boundary crosses the outer boundary (or another inner
boundary) is invalid per the schema. Open Cascade silently heals or drops
such a face, so the intended hole is lost or the face is corrupted with no
diagnostic at all (the 2018 report saw a dropped face; on the current line
the face survives as wrong geometry, still silently).
After the wires are collected, if a face has inner boundaries, measure the
BRepExtrema distance between each inner wire and every earlier wire. Two
non intersecting loops have strictly positive distance, so a distance at
or below the modelling precision means the boundaries touch or cross; emit
a warning (GEO 402) naming the offending face. This is diagnostic only, no
geometry change.
The message is emitted via the kernel logger() rather than Logger::Root():
IfcConvert configures a local Logger and worker logs merge into it, while
Logger::Root() is a separate unconfigured singleton whose messages are
discarded (a latent issue affecting some existing GEO messages too).
Verified on OCC 7.9.2 with synthesized IFC4 faces: an inner triangle
crossing the outer edge, and one straddling the bottom edge, each emit one
GEO 402; a valid 4x4 hole emits none and triangulates identically (area
84.0), in both sequential and multithreaded runs. Pure inner self
intersection and full containment are distinct classes and intentionally
left untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fixes builds with newer GCC/libstdc++ that no longer provide <cstdint>,
<cstring>, <cfloat>, <memory>, <algorithm> etc. transitively. Also
disambiguates visit<> calls in taxonomy.h with the full namespace and
casts the character value in IfcCharacterDecoder to uint32_t to silence
ambiguous overload warnings.
The temporary-offset workaround (#7408, commit bd57cc8735) subtracts the
directrix centroid (`mean`) from the curve points before building the
sweep near the origin, then must add it back to restore the original
location. The restore negated the sign — `Move(-mean)` instead of
`Move(+mean)` — placing the swept solid at -mean (mirrored through the
origin) rather than its true position.
Only triggers for polyline directrixes (`is_polyhedron()`) whose centroid
is more than 100 m from the origin (`mean.norm() > 1e2`), so models
centered near the origin are unaffected. Models that keep absolute site
coordinates (e.g. many Revit/ODA IFC exports) render affected swept
solids — reinforcing bars, pipes — at a mirrored phantom location far
from the rest of the model.