AST parser has changed a bit and there are some minor differences in the
.py output. Updating files just to avoid seeing these diffs when
rerunning rule compiler.
Example error:
```
ast.Str(s=node.attr),
^^^^^^^
AttributeError: module 'ast' has no attribute 'Str'
```
`ast.Str` was deprecated since 3.8 and was removed in 3.14, see
https://docs.python.org/3/whatsnew/3.14.html#id9
* Keep real cause in wrapper ImportError
When the compiled wrapper exists for the current interpreter but fails
to load (for example a glibc version mismatch, as on AWS Lambda in
issue 5927), the bare except rewrote the error into the misleading
"IfcOpenShell not built for '<platform>'" message. Environments such
as AWS Lambda or the Blender add-on dialog only surface the final
exception message, so the actual cause was invisible and undiagnosable.
Keep the "not built for" message only when no matching binary is
present, and otherwise include the original loader error, chaining the
cause in both branches.
This change was AI-generated.
Fixes#5927
* Simplify wrapper import failure to a single message
Per review feedback, drop the filesystem scan and the two message
variants. Always raise the classic "IfcOpenShell not built for
'<platform>'" message with the original exception appended in
parentheses, still chained as the cause. Environments that only show
the final exception message (AWS Lambda, the Blender add-on dialog)
now surface the real loader error, such as the glibc version mismatch
in issue 5927, without any extra logic.
This change was AI-generated.
The ci-lint workflow's ty steps fail on every branch because base
v0.8.0 has four diagnostics.
ty check (bonsai):
- root/operator.py: bpy.data.objects.get() can return None, so
UnlinkObject._execute could put None in its objects list and crash
on the first attribute access when an unknown object name is passed.
Handle the miss explicitly, which also satisfies the declared
list[bpy.types.Object] type.
- tool/sequence.py: ty does not narrow Literal types through
membership tests on list literals, so the assert_never() exhaustive
check was flagged. Use tuple literals, which ty narrows, keeping the
exhaustiveness check intact.
ty check (ios):
- draw.py: arrange_polygons was called through conditional argument
splats that let the same call site work against pre-April-2026
wrappers lacking arrange_polygon_settings and the logger parameter.
No runtime bug for current builds, but the dynamic splats cannot be
typed against the fixed 3-parameter signature. Drop the old-build
workaround and call the current signature directly, following the
precedent of 3d8115ebc5 which dropped similar old-build workarounds
in ifcopenshell.file. Verified against a current wrapper build that
the direct call arranges polygons and serializes to SVG, with and
without a logger.
- Optimise.py: igraph is an optional dependency with a guarded import
and a toposort fallback, but it was missing from the ios type-check
venv so ty could not resolve it. Add it to type-check-requirements
next to the toposort fallback that is already listed.
After this, poe ty-bonsai and poe ty-ios both pass cleanly.
Generated with the assistance of an AI coding tool.
Ran the new sync_stub.py against a real local build: adds
context.delete_same_facet_edge_pairs (present on the compiled wrapper,
missing from the stub) and drops the module-level logger_or_root
(present in the stub, no longer exists on the wrapper at all).
Nothing else changes - no license header rewrite, no docstring loss,
none of the 14 hand-curated named-parameter constructor/function
signatures touched, unlike the wholesale regeneration this replaces.
Generated with the assistance of an AI coding tool.
generate_stub.py (this branch's earlier commit) regenerates
ifcopenshell_wrapper.pyi wholesale from the compiled wrapper: it
reliably fixes real drift, but it also discards everything that isn't
mechanically recoverable from the wrapper alone - the license header,
docstrings, and hand-curated named-parameter signatures for
SWIG-overloaded constructors/functions (SWIG itself always emits
generic `*args` for those, so a regenerator can't tell a deliberate
curation from real drift and just overwrites it).
sync_stub.py takes the smaller-blast-radius approach: it only adds
top-level symbols/class members that are genuinely missing, and only
removes ones that are genuinely gone, cross-checking against
validate_stub.py's own full canonicalisation (via the newly-exposed
get_names_tree()) so it never mistakes a property()/staticmethod()-
wrapped member for something absent just because its own narrower
parser skips that form. Anything that exists on both sides under the
same name but with a different signature - exactly where curation
lives - is left untouched and reported for a human to review instead
of guessed at.
Verified against a real local build: applying it to the current
ifcopenshell_wrapper.pyi produces a small, targeted diff (add one
missing method, drop one stale function) with the license header,
docstrings, and all 14 curated constructor/function signatures
preserved byte-for-byte, versus generate_stub.py's ~1000-line
wholesale rewrite for the same underlying fix.
Generated with the assistance of an AI coding tool.
Follow-up to the scalar-only fix in #8754, per aothms's direct request on
that PR ("Please do make all int types consistent") and his own original
2023 design intent on issue #3058 ("make all integers (incl. schema
namespaces) an int64_t"). Widens the remaining inconsistent spots now that
compatibility isn't a constraint on this v0.9 branch:
- Integer aggregates (IfcTriangulatedFaceSet.CoordIndex and similar
List<int> attributes), including the SWIG to_vec_int/to_vec_vec_int
helpers, which previously silently truncated via static_cast<int> on the
Python-set path - the same bug class as the original scalar issue.
- The schema code generator (express/mapping.py's integer type mapping),
and all 12 generated schema header/source pairs regenerated to match, so
every schema-typed getter/setter (e.g. IfcOwnerHistory::CreationDate) is
int64_t end to end, not just the dynamic attribute-value path.
Instance/reference identifiers (STEP #123 ids) are deliberately left at
32-bit: they're a file-local index into internal maps, not an EXPRESS
domain value an application chooses, and no realistic STEP file has
billions of entities. The lexer's Token_IDENTIFIER parsing still funnels
through a 32-bit int for this reason - flagged as a known, low-risk gap
rather than fixed, since fixing it would mean touching indexing/hashing
code for no realistic benefit.
Verified: original PR's round-trip tests extended with aggregate cases
(IfcTriangulatedFaceSet.CoordIndex, InnerCoordIndices) at 64-bit boundary
values, in memory and through STEP text, IFC2X3 and IFC4. A standalone C++
program exercising the generated schema API directly (Ifc4::IfcOwnerHistory
::setCreationDate/CreationDate, IfcTriangulatedFaceSet::setCoordIndex/
CoordIndex) confirms int64_t end to end, bypassing SWIG. Full build
(BUILD_IFCGEOM, WITH_OPENCASCADE, BUILD_IFCPYTHON, IFC2X3+IFC4) clean.
test/util/test_attribute.py and test_file.py pass unchanged.
This contribution was produced with the assistance of an AI coding tool.
set_shape_transparency() called AIS_InteractiveContext.SetTransparency(),
whose argument count is inconsistent across pythonocc-core versions
(reported as a TypeError in #1037). Set transparency directly on the AIS
object instead, the same stable pattern already used elsewhere in this
file (display_shape() calls ais.SetTransparency() directly, never through
the Context), then call Context.UpdateCurrentViewer() to refresh.
app.py's viewer used a "SetSelectionPriority(counter)"/"SelectionPriority()"
pair as an ad hoc unique key to map a displayed AIS object back to its IFC
product. On modern pythonocc-core this crashed with AttributeError because
.GetObject() (needed to unwrap the old handle-based API) no longer exists
on AIS objects (#1098, PR #1113 partially patched one of the two call
sites but left the one in HandleSelection unguarded).
Live pythonocc-core 7.9.3 testing showed the GetObject() guard alone is
not sufficient: SetSelectionPriority/SelectionPriority themselves have
been removed from AIS_InteractiveObject entirely in modern OCCT (only
AIS_Trihedron keeps a same-named but unrelated method for datum parts),
so gating the .GetObject() call with the existing USE_OCCT_HANDLE flag
would still crash the first time a shape is selected. Verified live that
AIS objects retain correct __eq__/__hash__ (matching the underlying OCCT
instance) across separate SWIG wrapper instances, so ais_to_product is
now keyed directly by the AIS object itself, removing the dependency on
the removed OCCT API and the GetObject()/handle distinction altogether.
Verified live against pythonocc-core 7.9.3 (conda-forge) using real
AIS_Shape objects obtained from ifcopenshell.geom.occ_utils.display_shape()
and a real IFC file: reproduced both the original TypeError (#1037) and
AttributeError (#1098), confirmed both fixes resolve them, and confirmed
the ais_to_product dict lookup round trips correctly through a real
Context.Select()/SelectedInteractive() call. Could not exercise the full
Qt-embedded viewer.finished()/HandleSelection() flow end to end because
this pythonocc-core build segfaults natively when creating a second GL
context inside a Qt widget on this macOS host, a pre-existing environment
issue unrelated to this diff (reproduces identically with unpatched code,
before any touched line executes).
AI-generated, reviewed and tested by Petru Conduraru.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
create_shape() returns a Python-owned Element (SWIG_POINTER_OWN in the
boost::variant out typemap). Its .geometry property calls Element::geometry(),
which returns a reference into the element's boost::shared_ptr<Representation>
_geometry member. SWIG wraps that reference as a non-owning pointer, so the
returned Triangulation/BRep/Serialization proxy does not keep the element alive.
When a caller keeps only .geometry (e.g. create_shape(s, e).geometry) and drops
the parent element, Python garbage-collects the element, destroying its
shared_ptr and freeing the underlying representation. Subsequent reads of
verts/faces then return freed memory: empty or implausible float/int garbage,
non-deterministically depending on GC and allocator timing. This is silent data
corruption, not a crash, and has bitten users since 2020.
Fix: in the TriangulationElement/SerializedElement/BRepElement pythoncode, wrap
the geometry getter so the returned geometry stores a backreference to its
owning element (result._parent = self). This makes the parent's lifetime at
least as long as the geometry's, automatically and transparently, so no caller
has to remember to hold the element. This is aothms's suggested backreference,
applied generically in the binding rather than left as a workaround.
Reproduced deterministically (washBasin fixture): before, verts len 0 vs 133500
across repeated GC-pressure runs; after, 133500 every run for all three element
types. test_create_shape passes; no regressions.
Note: tree.select_ray()'s ray_intersection_result (2024 follow-up in #1124) is a
separate ownership mechanism (std::vector element reference + std::array member
pointer) and is left as follow-up scope.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
`values` dictionary was missing and variables were never collected to it, so `FormulaEvaluator(values)` was always resulting in missing variable error.
The class-pairing validation added in 10ee5aef4f rejects any type
assignment whose class isn't in the buildingSMART implementer
agreement map. That map only covers physical product occurrence/type
pairs (IfcWallType -> IfcWall, etc); IfcTypeProcess and IfcTypeResource
subtypes such as IfcTaskType, IfcProcedureType and the resource types
have no entry, so previously-valid assignments like
IfcTaskType -> IfcTask were rejected with "allowed occurrence
classes: <none>".
These classes still follow the schema's universal Type-suffix naming
convention, so derive the pairing the same way the existing
ApplicableOccurrence fallback does: strip "Type" from the relating
type's class name and accept it only if the schema actually declares
that entity. This can only add pairings implied by the type's own
class name, so it cannot loosen the existing rejection of genuine
mismatches (e.g. IfcWallType -> IfcWindow).
Generated with the assistance of an AI coding tool.
Add svg-use-edge-classification (default off, preserving today's
linework), svg-render-crease-edges, and svg-render-sharp-edges
settings, gating the existing 5-class classification feature so it
can be disabled entirely (falling back to the pre-classification
whole-shape output) or have individual classes suppressed.
Also fixes a bug uncovered while wiring this into Bonsai: ready(),
where geometry_settings() actually gets read into the serializer,
was only ever invoked explicitly by IfcConvert's CLI driver and
isn't exposed to Python. Every Svg* setting -- including the three
from previous rounds -- silently stayed at its hardcoded constructor
default when the serializer was constructed directly through the
Python bindings, as Bonsai does. Fixed by calling ready() from
SvgSerializer's own constructor, safe since it only reads
geometry_settings() with no other side effects, and settings are
always finalized before construction in every call path.
Generated with the assistance of an AI coding tool.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
It was passing `IFC4X3` directly to `schema_by_name` which is expecting
schema identifier (e.g. IFC4X3_ADD2, not IFC4X3 allowed by `IFC_SCHEMA`
- IFC4X3 is one of the IFC4X3 iterations while it was in development,
not the final one).
Noticed by tests failing:
FAILED
test/util/test_schema.py::TestGeometryClassesIntroducedAfter::test_ifc4x3_to_ifc2x3_is_superset_of_ifc4_to_ifc2x3
- RuntimeError: No schema named IFC4X3
FAILED
test/util/test_schema.py::TestGeometryClassesIntroducedAfter::test_ifc4_to_ifc4x3_is_empty
- RuntimeError: No schema named IFC4X3
AST parser has changed a bit and there are some minor differences in the .py output. Updating files just to avoid seeing these diffs when rerunning rule compiler.
Example error:
```
ast.Str(s=node.attr),
^^^^^^^
AttributeError: module 'ast' has no attribute 'Str'
```
`ast.Str` was deprecated since 3.8 and was removed in 3.14, see https://docs.python.org/3/whatsnew/3.14.html#id9
black (the version CI's psf/black@stable resolves to) flags three spots
in util/selector.py: the chained .replace() in FormatTransformer.number,
the suppress_zero_inches kwarg in format_length, and the long
`elif key in (...) and hasattr(...)` placement-key tuple in
set_element_value. Reformat all three to black's multi-line style.
Formatting only, no behavioural change (all keys preserved).
This change was made with the assistance of an AI tool.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
get_pset and get_psets assumed RelatingPropertyDefinition is a single property
definition and read definition.Name directly. When it is an
IfcPropertySetDefinitionSet (a defined type wrapping a list of property set
definitions) that attribute access raised AttributeError, so an element whose
psets are grouped in a set returned none of them.
Unpack IfcPropertySetDefinitionSet into its members in both loops and process
each one. Single property definitions and the psets_only and qtos_only filters
are unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Expose the Euler rotation of an element's placement in degrees through
get_element_value, alongside the existing x/y/z and easting/northing/
elevation keys. This makes element rotation exportable through ifccsv,
e.g. for placing oriented symbols in GIS.
Adopts the approach agreed in the review of the stale PR #6272 by
@TZwielehner: reuse util.shape_builder.np_matrix_to_euler and do the
degree conversion inside get_element_value.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The guard that avoids re-assigning the same object to the same resource
tested is_a("IfclRelAssignsToResource") (stray "l"), so it never matched.
A repeat assignment therefore fell through and appended the related object
to RelatedObjects a second time. Corrected to "IfcRelAssignsToResource".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
FormatTransformer.round() called Decimal() directly on the input value,
which raises decimal.InvalidOperation when the value is a non-numeric
string (a text property, or a value carrying a unit suffix like "12.5 m").
In a spreadsheet export this crashed the entire operation as soon as one
element carried such a value.
Now round() catches InvalidOperation and returns the value unchanged, the
same graceful-fallback convention used by add(). Numeric rounding is
unaffected.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The filter_elements selector grammar had no way to comment out part of a
query, so users had to delete and retype text to temporarily toggle a
facet. Add a /* ... */ block comment terminal that is ignored by the
lexer, and tolerate a trailing "+" so that commenting out the final
operand (e.g. "IfcWall + /* IfcSlab */") parses cleanly. Comments may
span multiple lines; a /* sequence inside a quoted string is not treated
as a comment. Only the filter grammar is affected, not get_element or
format which use "/" for regex and division.
Adds a regression test and documents the syntax.
Generated with the assistance of an AI coding tool.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- add_stationing_referent.py: black reformat (new drift from v0.8.0).
- update_fallback_position.py: v0.8.0's changes to this file made the
ifcopenshell.util.unit import (added in an earlier commit here) unused;
removed per ruff.