Fix ci-bonsai-daily: assign_class drops active_object; assign_material usage

Two root causes behind ~16 failing BDD scenarios in the material /
assign-type area:

- AssignClass._execute (bim/module/root/operator.py) relinks objects into
  new collections (e.g. types into the hidden "IfcTypeProduct" collection)
  and then restores the selection via
  tool.Blender.validate_object_selection. Without a
  context.view_layer.update() in between, a just-relinked (but valid)
  active object is seen as "not in the current view layer" and dropped, so
  context.active_object becomes None for the rest of the caller and every
  scenario that reads active_object after assigning a type fails. Add the
  view_layer.update() before the selection-restore (an idiom already used
  ~15 times elsewhere).

- core.assign_material forwarded the selected plain IfcMaterial into
  ifcopenshell.api.material.assign_material even when auto-upgrading the
  assignment to a "...Usage" type, which only accepts a material *set* or
  None, tripping an intentional API assertion. Pass material=None for the
  usage branch and let the existing add_material_to_set follow-up populate
  the derived set.

Verified in headless Blender: the material / assign_type / assigned-material
BDD cluster goes from 23 failed / 14 passed to 7 failed / 30 passed. The 7
remaining failures are an unrelated stale-STEP-id fixture cluster
(Item/IfcFace/NN) handled separately.

This change was made with the assistance of an AI tool.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Petru Conduraru
2026-07-13 06:23:09 +03:00
parent ffb867f254
commit 8e2cb4c071
2 changed files with 22 additions and 1 deletions
@@ -380,6 +380,14 @@ class AssignClass(bpy.types.Operator, tool.Ifc.Operator):
# TODO: reload representation might lead to the object being replaced by object of the other type.
# We probably should track it somehow and keep the original selection.
# Objects may have just been relinked into new collections (e.g. types
# into the "IfcTypeProduct" collection) above. Make sure the view
# layer's object bases are up to date before validating the selection
# below, otherwise a just-relinked (but perfectly valid) active object
# can be seen as "not in the current view layer" and be dropped,
# leaving `context.active_object` as None for the rest of the caller.
context.view_layer.update()
# Validate selection and reapply it.
current_selection = tool.Blender.validate_object_selection(*current_selection)
tool.Blender.set_objects_selection(*current_selection)
+14 -1
View File
@@ -130,10 +130,23 @@ def assign_material(
if can_be_usage and not material_tool.is_type_product(element):
element_material_type = material_type + "Usage"
# A "...Usage" type always wraps an existing material *set*
# (IfcMaterialLayerSet/IfcMaterialProfileSet), never a plain
# IfcMaterial. `material` here is whatever single IfcMaterial is
# selected in the Object Materials UI, so it can't be passed
# through as-is or ifcopenshell.api.material.assign_material will
# assert (e.g. "IfcMaterial cannot be assiged as a
# IfcMaterialLayerSetUsage."). Passing None lets the API derive
# (or create) the proper set from the element's type; the
# selected material is added into that set below instead.
material_for_assignment = None
else:
element_material_type = material_type
material_for_assignment = material
ifc.run("material.assign_material", products=[element], type=element_material_type, material=material)
ifc.run(
"material.assign_material", products=[element], type=element_material_type, material=material_for_assignment
)
assigned_material = material_tool.get_material(element)
assert assigned_material # Type checker.