The note under "assign_type preserves per-instance" carried a caveat that
skipping the mapping also keeps the occurrence's own profile/material, "fine
when all types share one profile -- revisit if reassigning across different
profiles". That is exactly what bit: map_material_usages lives inside the same
if-block, so the occurrence kept a usage pointing at the old type's profile set
and the material panel edited the old type.
Replace the caveat with what actually happens, why the fix reuses
material.assign_material rather than setting ForProfileSet by hand, and where
the coverage lives. Tick the matching checklist item.
Generated with the assistance of an AI coding tool.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three tests over core.assign_type with a real IFC file: a per-instance profile
occurrence retyped from one profile type to another must end up with its own
usage pointing at the new type's IfcMaterialProfileSet, must keep its own
non-mapped body at its own extrusion depth while picking up the new type's
profile, and must leave the old type untouched.
The first two fail against the parent commit, with the occurrence's material
resolving to the old type's set and SweptArea never moving off the old profile.
The third passes either way; it is a guard against a future fix that reaches
sideways into the old type.
Placed under test/bim rather than test/core even though the subject is
bonsai.core: Prophecy cannot reach this branch because _is_per_instance_profile
calls ifcopenshell.util directly on the element, so a real entity is required
and Prophecy serialises call arguments to JSON. Driving a real file then imports
ifcopenshell.api.material and so mathutils, which make test-core deliberately
runs without. No bpy is touched.
Generated with the assistance of an AI coding tool.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
assign_type started passing should_map_representations to api type.assign_type,
but the two core tests still predicted the old call. Prophecy serializes kwargs
into the prediction key, so both failed with "Interface 'bonsai.core.tool.Ifc'
was not called with run".
Both cases use a plain string stand-in for the element, which has no material,
so _is_per_instance_profile returns False and the flag is True.
test/core is green again: 385 passed.
Generated with the assistance of an AI coding tool.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
assign_type passes should_map_representations=False for a per-instance profile
occurrence so the new type's shared representation is not mapped over its own
body. That flag gates more than the representation: in api type.assign_type it
also guards map_material_usages, the step that re-points an occurrence's
IfcMaterialProfileSetUsage at the new type's material set.
The occurrence therefore kept a usage whose ForProfileSet still referenced the
OLD type's IfcMaterialProfileSet. get_material(should_skip_usage=True) follows
that pointer, so the material panel showed, and edited, the old type's profile.
Duplicating a profile-based type and then changing the copy's profile silently
rewrote the original's profile instead, taking every other occurrence of the
original with it.
Re-point the usage here when the mapping is suppressed. Reusing
material.assign_material rather than setting ForProfileSet by hand matters: its
update_representation_profile rewrites SweptArea to the new type's profile but
never touches the extrusion depth, so the per-instance length this branch exists
to protect still survives. restore_material_usage_attributes runs afterwards, so
recorded usage attributes land on the new usage.
Traced in Blender against a duplicated IfcFurnitureType: before the fix the
profile edit reported the source type's IfcMaterialProfile, after it the copy's,
while the occurrence's representation entity ids were unchanged across the
retype.
Generated with the assistance of an AI coding tool.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Lets an occurrence's extrusion length be either driven by the type
(typed/shared, e.g. Revit-mapped mullions) or owned by the occurrence
(per-instance, editable), and lets the user switch between the two.
- bim.make_profile_length_per_instance: un-maps a shared mapped body into a
per-instance SweptSolid (shared IfcProfileDef kept; the old mapped body is
orphaned, not deleted, to avoid cascading to sibling instances), gives the
occurrence its own IfcMaterialProfileSetUsage (so the Length UI appears), and
normalizes the placement so the extrusion runs local Z 0->depth -- Revit puts
the object and extrusion origins on opposite ends, which made extend_profile
flip the origin.
- bim.make_profile_length_type_driven: the inverse -- re-maps the occurrence
onto the type's shared representation, promoting the occurrence's body onto
the type first if the type has no RepresentationMap.
- A "Per-instance Length" checkbox in the Type panel (a get/set property that
reads the current mode straight from the IFC, so there is no stored state to
desync).
- New occurrences of a mapped/typed profile type default to per-instance.
- recreate_profile now skips mapped bodies (reloading the mesh instead of
regenerating), so assigning a length-driven type keeps the occurrence typed
rather than un-mapping it mid-assign and emptying sibling instances via the
shared-representation removal cascade.
- assign_type preserves a per-instance occurrence's own geometry/length
(should_map_representations=False) instead of converting it to typed.
Design/working notes under docs/dev-notes/profile-length-per-instance.md.
Closes#8657Closes#8656
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
reimport_element_representations' change_data already handles an empty ->
mesh transition (recreate_object_with_data when the datatypes differ), but the
leftover-data cleanup then called has_data_users(old_data) with old_data=None,
crashing with "'NoneType' object has no attribute 'users'". Guard the cleanup
so an object with no prior mesh reloads cleanly.
Closes#8655
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds bim.make_profile_length_per_instance, which converts profile
occurrences whose length is "typed" (shared via the type's mapped
RepresentationMap) into per-instance editable extrusions.
Per selected occurrence it:
- swaps the mapped body for its own SweptSolid copy (geometry/length
preserved exactly, shared IfcProfileDef kept), leaving the old mapped
IfcShapeRepresentation as a harmless orphan rather than deleting it (a
delete would either cascade to sibling instances via the shared
RepresentationMap or leave Bonsai's Blender-side links dangling);
- gives it its own IfcMaterialProfileSetUsage, since occurrences that only
inherit the type's IfcMaterialProfileSet report get_usage_type()==None and
never get the per-instance Length UI.
Sibling instances and the shared MappingSource/MappedRepresentation are left
untouched. The operator is idempotent: re-running repairs partial conversions
(e.g. adds a missing usage to an already-un-mapped occurrence).
A notice + "Make Length Per-Instance" button in the Type panel surfaces the
conversion when the active occurrence is still typed-length.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Error was:
```
configure: error: could not find a working compiler, see config.log for details
```
config.log:
```
conftest.c: In function 'f':
conftest.c:12:48: error: too many arguments to function 'g'; expected 0, have 6
12 | for(i=0;i<1;i++){if(e(got,got,9,d[i].n)==0)h();g(i,d[i].src,d[i].n,got,d[i].want,9);if(d[i].n)h();}}
| ^ ~
```
Last reference to this file was dropped in 7ae685dbf, though the ref was
pointing to `/patches/opencollada/pr622.patch`, so IIUC
`patches/pr622.patch` was never used.
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
Because uv was always trying to install when starting a venv in `ifcopenshell` folder, though they might be already available globally. And also they were listed twice - in pyproject and in the ci-lint.yml, now there's a single source of truth.
versionURLs in brand.html used http:// while the docs sites are
served over https://, so currentURL.includes(url) never matched and
the <select> never reflected/switched to Unstable. Fixes#8023.
Generated with the assistance of an AI coding tool.
Two TestImplementsTool failures on v0.8.0:
- test_cost.py: Cost could not be instantiated because
core.tool.Cost declared abstract get_direct_cost_item_products, which
tool.cost.Cost never implements. The method is dead (zero call sites;
get_cost_item_products(is_deep=False) already covers the 'direct'
case), so remove the abstract declaration.
- test_ifcgit.py: tool.ifcgit.IfcGit was not declared as a subclass of
its core.tool.IfcGit interface (unlike every sibling tool class), so
the isinstance check failed. Add the base class (and the
bonsai.core.tool import it needs). All 50 interface methods are
already implemented on the concrete class.
No behaviour change. Verified in headless Blender: isinstance(Cost(), core.tool.Cost) and isinstance(IfcGit(), core.tool.IfcGit) both True (were TypeError / False); repo abstract-vs-impl diff confirms all IfcGit abstracts are implemented.
This change was made with the assistance of an AI tool.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>