Bug Description
update_window_modifier_representation() writes the edited window's OverallWidth / OverallHeight to every occurrence of the same IfcWindowType, even when each occurrence owns its own geometry.
Geometry is handled correctly. It goes through get_part_of_product(), so an occurrence with its own IfcProductDefinitionShape keeps its own body, and only the type-mapped case writes to the shared IfcRepresentationMap. The attribute loop directly below carries no such condition:
# occurrences attributes
occurrences = tool.Ifc.get_all_element_occurrences(element)
for occurrence in occurrences:
occurrence.OverallWidth = props.overall_width / si_conversion
occurrence.OverallHeight = props.overall_height / si_conversion
get_all_element_occurrences() returns every occurrence of the type when the element is typed, and [element] when it is not.
The result is a state no consumer can reconcile: sibling windows keep geometry of one size while reporting the attributes of another. Quantity take-off, schedules and IDS all read the attributes, not the body.
This is reachable through Bonsai's own default flow. mesh.add_window creates an untyped occurrence, so authoring several differently sized windows and typing them afterwards is the normal path — and it produces exactly the mixed state in which the loop does damage.
The module states its intended scope itself, in _WindowEditMixin:
Single-object by design (window edits target the active object only).
The geometry path honours that. The attribute path does not.
tool.Model.update_simple_openings() iterates the same set and makes its assumption explicit in a comment: "We assume all occurrences of the same element type (e.g. a window) will use openings of the same thickness." That assumption only holds when the occurrences share the type's IfcRepresentationMap — which is precisely the condition get_part_of_product() already evaluates two lines earlier.
Steps to reproduce
Verified in an empty project, IFC4, two elements, no parameter changed:
- New IFC project.
- mesh.add_window, then bim.enable_editing_window / bim.finish_editing_window with overall_width = 0.80, overall_height = 1.00. → window A.
- mesh.add_window again, same cycle with overall_width = 1.60, overall_height = 2.20. → window B.
- Create an IfcWindowType and assign both occurrences to it. should_map_representations=False, so both keep their own representations and only the attribute path is under test.
- Select window A. bim.enable_editing_window, then bim.finish_editing_window — without changing any parameter.
Observed on window B:
|
dimensions |
representation ids |
OverallWidth × OverallHeight |
| before |
1.60 × 2.20 |
287, 330, 347 |
1.60 × 2.20 |
| after |
1.60 × 2.20 |
287, 330, 347 — unchanged |
0.80 × 1.00 |
Window A behaved as expected: its own representations were replaced (145 → 352), geometry and attributes unchanged.
Also observed on a production model, IFC4X3, with 28 IfcWindow occurrences on a single IfcWindowType, 21 distinct OverallWidth / OverallHeight pairs, each with its own body representation: one null edit rewrote the attributes of 24 of them. Body geometry of the others was untouched, confirming the same split at scale.
Suggested fix
Gate the loop on the same condition as the geometry: write to all occurrences only when the representation is type-mapped, otherwise to the active element alone.
Debug and Error Output
blender_version: 5.2.0 LTS
bonsai_version: 0.8.5
schema: IFC4 (minimal repro), IFC4X3 (production model)
Source checked against branch v0.8.0 at the time of writing; the loop is unchanged there.
Bug Description
update_window_modifier_representation() writes the edited window's OverallWidth / OverallHeight to every occurrence of the same IfcWindowType, even when each occurrence owns its own geometry.
Geometry is handled correctly. It goes through get_part_of_product(), so an occurrence with its own IfcProductDefinitionShape keeps its own body, and only the type-mapped case writes to the shared IfcRepresentationMap. The attribute loop directly below carries no such condition:
get_all_element_occurrences() returns every occurrence of the type when the element is typed, and [element] when it is not.
The result is a state no consumer can reconcile: sibling windows keep geometry of one size while reporting the attributes of another. Quantity take-off, schedules and IDS all read the attributes, not the body.
This is reachable through Bonsai's own default flow. mesh.add_window creates an untyped occurrence, so authoring several differently sized windows and typing them afterwards is the normal path — and it produces exactly the mixed state in which the loop does damage.
The module states its intended scope itself, in _WindowEditMixin:
The geometry path honours that. The attribute path does not.
tool.Model.update_simple_openings() iterates the same set and makes its assumption explicit in a comment: "We assume all occurrences of the same element type (e.g. a window) will use openings of the same thickness." That assumption only holds when the occurrences share the type's IfcRepresentationMap — which is precisely the condition get_part_of_product() already evaluates two lines earlier.
Steps to reproduce
Verified in an empty project, IFC4, two elements, no parameter changed:
Observed on window B:
Window A behaved as expected: its own representations were replaced (145 → 352), geometry and attributes unchanged.
Also observed on a production model, IFC4X3, with 28 IfcWindow occurrences on a single IfcWindowType, 21 distinct OverallWidth / OverallHeight pairs, each with its own body representation: one null edit rewrote the attributes of 24 of them. Body geometry of the others was untouched, confirming the same split at scale.
Suggested fix
Gate the loop on the same condition as the geometry: write to all occurrences only when the representation is type-mapped, otherwise to the active element alone.
Debug and Error Output
Source checked against branch v0.8.0 at the time of writing; the loop is unchanged there.