The Migrate recipe previously crashed mid-loop with the cryptic
`RuntimeError: Entity with name '' not found in schema 'IFC2X3'` when
asked to downgrade an IFC4 or IFC4X3 file to IFC2X3 — the
class_4_to_2x3 mapping marks IFC4-only geometry / element classes with
an empty-string sentinel and the old code blindly forwarded that to
create_entity. Real files routinely contain IfcPolygonalFaceSet,
IfcTriangulatedFaceSet, IfcIndexedPolyCurve, IfcLamp, IfcPipeSegment,
IfcGeographicElement, etc.
The recipe now runs a preprocessing pipeline when the target is IFC2X3
and the source is IFC4 or IFC4X3:
- DowngradeIndexedPolyCurve flattens IfcIndexedPolyCurve to IfcPolyline
for the whole file (arcs included — see below).
- IfcPolygonalFaceSet / IfcTriangulatedFaceSet are converted directly
to IfcFacetedBrep at the entity level via
ifcopenshell.util.shape_builder.polygonal_face_set_to_faceted_brep,
preserving topology including IfcIndexedPolygonalFaceWithVoids inner
bounds. IfcShapeRepresentation carriers have their RepresentationType
tag updated from "Tessellation" to "Brep".
- Orphan source-only geometry instances (left over after the rewires)
are purged iteratively via
geometry_classes_introduced_after(target, source).
The Migrator is invoked with fallback_element_to_proxy=True so
IFC4-only IfcElement subclasses (IfcLamp, IfcPipeSegment,
IfcGeographicElement, ...) become IfcBuildingElementProxy in the
output. A post-pass encodes "<OriginalClass>/<PredefinedType>" into
ObjectType (e.g. "IfcLamp/COMPACTFLUORESCENT") when ObjectType is
empty, so the lost subclass identity survives the downgrade as
searchable text.
The migration loop now collects per-entity failures into a list rather
than crashing on the first; a summary RuntimeError fires at end if any
failed, naming up to 20 with their inverse references. Successful
migrations log a single count line via self.logger.
DowngradeIndexedPolyCurve extended:
- Arc segments (IfcArcIndex) are flattened via
ifcopenshell.util.shape_builder.arc_to_polyline_points with
ARC_SUBDIVISION=16 chord points per arc.
- Multi-index IfcLineIndex segments handled correctly.
- Absent Segments list (IFC4 polyline-through-all-coords case) handled.
Test coverage: 11 tests across the two recipes covering all four
preprocessing branches, the IFC4X3 source gate, the ObjectType
encoding (incl. author-supplied ObjectType preservation), the summary
RuntimeError shape, and the arc subdivision.
Generated with the assistance of an AI coding tool.
Mentioned in https://github.com/IfcOpenShell/IfcOpenShell/issues/7667#issuecomment-4076645173
Traceback:
```
Traceback (most recent call last):
File "\bonsai\bim\module\patch\prop.py", line 55, in get_ifcpatch_recipes
docs = ifcpatch.extract_docs(f, "Patcher", "__init__", ("src", "file", "logger", "args"))
File "\ifcpatch\__init__.py", line 168, in extract_docs
spec.loader.exec_module(submodule)
~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
File "<frozen importlib._bootstrap_external>", line 1027, in exec_module
File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
File "\ifcpatch/recipes/FixRevit2025TINs.py", line 31, in <module>
class Patcher:
...<509 lines>...
return co / self.unit_scale
File "\ifcpatch/recipes/FixRevit2025TINs.py", line 168, in Patcher
def create_edges(self, obj: bpy.types.Object) -> None:
^^^
NameError: name 'bpy' is not defined
File "\bonsai\bim\module\patch\prop.py", line 43, in get_ifcpatch_recipes
```
To fix warnings below:
```
<python-input-1>:1: DeprecationWarning: datetime.datetime.utcnow() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.datetime.now(datetime.UTC).
```