An SI prefix attaches to the base unit symbol and the prefixed symbol is
raised to the power as a whole: DECI CUBIC_METRE is dm3 = a litre = 1e-3 m3,
not 0.1 m3. The scale factor previously applied the prefix multiplier
linearly for all IfcSIUnits, inflating volumes x100 and areas x10 for such
declarations (produced e.g. by MagiCAD for Revit MEP exports).
Following the reviewer note in #9278, the exponent is taken from the
derived attribute IfcSIUnit.Dimensions rather than from substring matching
on the unit name: the multiplier is raised to LengthExponent only when the
unit's dimensions are a pure power of length, so prefixed derived units
(KILO PASCAL, MEGA NEWTON) and non-length units (KILO GRAM) correctly keep
the linear multiplier. This matches the exponent handling already present
in convert() and named_dimensions in the same module.
Adds regression tests for prefixed AREAUNIT/VOLUMEUNIT and for the
linear-prefix behaviour of PRESSUREUNIT/MASSUNIT.
get_element_value could not reach the members of an IfcPhysicalComplexQuantity
(or IfcComplexProperty) by their natural path. util.element expands a complex
quantity into a dict whose nested members live under a "properties" sub-dict,
but the selector's dict navigation only looked at the top level, so
"Qto_Custom.Layer1.Width" returned None and IfcCsv exported nothing for it.
Only the internal "Qto_Custom.Layer1.properties.Width" path worked.
When a key is not a direct member of the value dict, descend into its
"properties" sub-dict so nested quantities/properties resolve with the
natural "Set.Complex.Nested" path. Direct keys still take priority, so the
explicit ".properties." path stays backward compatible and the regex branch
is untouched.
Verified: Qto_Custom.Layer1.Width -> 0.1 and Layer1.Height -> 2.5 (were
None), the sibling simple NetArea still resolves, the legacy .properties.
path still works, and IfcCsv now exports the nested value. test_selector.py:
38 passed (adds test_selecting_a_nested_complex_quantity).
Generated with the assistance of an AI coding tool.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
compare() recursed into list values passing the negated comparison through,
so != meant "at least one item differs" and both = and != matched the same
elements on any multi-valued property (e.g. an enumerated property with two
values selected). Strip the negation for the per-item comparison and negate
the aggregate instead, so != means "no item equals" and stays the complement
of =. The same applies to !*=.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
replace_attribute() rewrites references inside aggregate attributes via
element.walk(), but never checked whether the replacement value was
already present elsewhere in the same aggregate. For an EXPRESS SET
(e.g. IfcProject.RepresentationContexts, IfcRelAggregates.RelatedObjects)
this can leave the same reference listed twice, which is invalid IFC.
LIST and BAG aggregates legitimately allow duplicates, so a blanket dedup
would be wrong; only SET-typed attributes are deduplicated, determined at
runtime from the schema declaration (IfcOpenShell#8706 review comment).
The SET/LIST/BAG check is cached per (schema, class, attribute index), and
the dedup pass itself only runs when a cheap linear pre-check finds the
replacement value already present in the aggregate, so the common case
(no duplicate produced) pays only that pre-check, not a hash-set rebuild.
Benchmarked against a 23MB (431k entities) and a 104MB (2.4M entities) IFC
model against a large SET attribute: worst case adds well under 1ms per
call; the realistic case (merging duplicate contexts, matching the PR
#8706 scenario) shows no measurable regression.
Fixes the root cause flagged in IfcOpenShell#8706 (Moult), obviating the
need for MergeDuplicateContexts' own manual aggregate-dedup pass for that
scenario.
Generated with the assistance of an AI coding tool.
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>
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>
IfcGeometricRepresentationContext.Precision is typed as a plain IfcReal
but is interpreted in the project length unit, so the IfcLengthMeasure
traversal in convert_file_length_units never touched it. A model
converted from mm to m kept a Precision of e.g. 0.01 (fine in mm, huge
in m), which breaks downstream geometry interpretation such as
IfcConvert boolean cleanup.
Subcontexts derive Precision from their parent, so only root contexts
are scaled.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds the IFC-library primitives the ifcpatch Migrate recipe needs for a
defensive IFC4 / IFC4X3 -> IFC2X3 downgrade without each caller
reinventing the wheel.
In ifcopenshell.util.schema:
- Migrator(fallback_element_to_proxy=False) opt-in: when True, IFC4-only
IfcElement subclasses (IfcLamp, IfcPipeSegment, IfcGeographicElement,
...) migrate to IfcBuildingElementProxy instead of raising. Default
preserves the strict failure-on-unmappable contract for existing
callers (classification API, etc.).
- geometry_classes_introduced_after(target, source) derives the
IfcRepresentationItem subclasses present in `source` but absent in
`target` directly from the loaded schemas. Cached per pair. Replaces
hand-curated class lists that drift with each IFC update.
ifc4_only_geometry_classes() retained as an alias.
- generate_default_value synthesises a unit IfcAxis2Placement2D /
IfcAxis2Placement3D when downgrading entities whose Position became
required in the target schema (IfcIShapeProfileDef and friends in
IFC2X3).
- Enum-mismatch detection upgraded from string-matched RuntimeError to a
structural check via ifcopenshell.util.attribute.get_enum_items so
upgrade paths still surface real bugs loudly.
In ifcopenshell.util.shape_builder:
- polygonal_face_set_to_faceted_brep converts IfcPolygonalFaceSet /
IfcTriangulatedFaceSet (IFC4-only) directly to IfcFacetedBrep,
preserving topology including IfcIndexedPolygonalFaceWithVoids inner
bounds. Validates inputs at the boundary.
- arc_to_polyline_points approximates a circular arc through three
points with a chord polyline of configurable subdivisions. Tolerates
floating-point noise on planar Z. Raises on non-planar or invalid
inputs.
Test coverage: 47 unit tests across schema + shape_builder lanes
covering each helper directly (no transitive-only coverage), including
regression pins for the IFC4X3-prefix ordering invariant in
get_fallback_schema and the strict-default Migrator contract.
Generated with the assistance of an AI coding tool.
Centralises the millimetre-to-metre conversion shortcut that
add_door_representation and add_window_representation each defined
locally. Subsequent commits in this PR switch both call sites to
import this from util.unit, removing the duplicate definitions.
Generated with the assistance of an AI coding tool.
Previously, sort, reverse list, and join functionality was implemented
as special cases in Bonsai itself. Given that it has usecases
(especially in material lists, but any sort of list applies) I've moved
this function into the IOS formatting language.
The IOS formatting language previously wasn't capable of this, but the
awesome addition by @falken10vdl made the formatting language accept
queries inline, so that means it can handle lists. I also added tests
for all the new functions and expression syntax (+-*/ operators).
I simplified the code that gets the evaluated text literal - previously
it seems to call format() multiple times.
* Some basic COGO survey points functions
* Update alignment api
Includes defining alignment segment by segment, automatic geometry definitions, and automatric stationing and referents
* Fixes bonsai import alignment from csv
* Adds DMS angle conversion functions to COGO api
* Updated per @civilx64 review comments
* Fixes problem with segment representations
* Fixes problem with segment transition codes
* Allows for compound vertical and horizontal curves
* Implements callbacks for referent naming
* Renames angle_from_bearing to bearing2dd for consistency with ifcopenshell.util.geolocation.dms2dd. Removes angle_from_dms because it duplicates dms2dd
* Documents register_referent_name_callback
* Fixes all sorts of problems with Cant/SegRefCurve implementation
* refactor referent unit tests to use a fixture for test setup
* lint with black
---------
Co-authored-by: Scott Lecher <civilx64@gmail.com>
As described in #6744 partial stub wrapper is completely replacing the type information from the wrapper, so having partial stub is making things worse than they were before.
This commit is adding all available symbols from the wrapper to the stub and allows to gradually include type information to the stub.
It also adds a safe net to ensure stub and wrapper won't go out of sync.