mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 18:16:40 +00:00
Bonsai: copy-property-to-selection must not edit the inherited type pset (#7412)
"Copy property to selection" edited the value on the element's TYPE instead of creating an override on the occurrence. core.copy_property_to_selection resolved the target via tool.Pset.get_element_pset, which uses ifcopenshell.util.element.get_pset with should_inherit=True (the default). When the occurrence has no pset of its own but its type does, get_pset returns the type's pset, so pset.edit_pset then changed the value for the type and every occurrence sharing it. Give get_element_pset a should_inherit parameter (default True, so the ~30 other callers are unchanged) and pass should_inherit=False from copy_property_to_selection. Now only the element's own pset is considered; if none exists the existing fallback creates an override pset on the occurrence. The core interface stub and the four Prophecy mock expectations are updated to match. Verified live in headless Blender: with a type-level Pset_Custom.MyProp, copying MyProp to two occurrences leaves the type value untouched and creates an own-pset override on each occurrence. Core tests (test_pset.py) pass. Generated with the assistance of an AI coding tool. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -40,7 +40,11 @@ def copy_property_to_selection(
|
||||
element = ifc.get_entity(obj)
|
||||
if not element:
|
||||
return
|
||||
ifc_pset = pset.get_element_pset(element, pset_name)
|
||||
# Only consider the element's own property set, never one inherited from its
|
||||
# type. Otherwise copying an occurrence override would edit the type's pset
|
||||
# (and thus every occurrence) instead of creating/updating the override on
|
||||
# this element. See https://github.com/IfcOpenShell/IfcOpenShell/issues/7412.
|
||||
ifc_pset = pset.get_element_pset(element, pset_name, should_inherit=False)
|
||||
if not ifc_pset:
|
||||
ifc_pset = ifc.run("pset.add_pset" if is_pset else "pset.add_qto", product=element, name=pset_name)
|
||||
if is_pset:
|
||||
|
||||
@@ -814,7 +814,7 @@ class Pset:
|
||||
def cast_string_to_primitive(cls, value: str): pass
|
||||
def clear_blender_pset_properties(cls, props): pass
|
||||
def enable_proposed_pset(cls, props, pset_name, pset_type, has_template): pass
|
||||
def get_element_pset(cls, element, pset_name): pass
|
||||
def get_element_pset(cls, element, pset_name, should_inherit=True): pass
|
||||
def get_prop_template_primitive_type(cls, prop_template): pass
|
||||
def get_pset_name(cls, obj, obj_type, pset_type): pass
|
||||
def get_pset_template(cls, name): pass
|
||||
|
||||
@@ -70,9 +70,9 @@ class Pset(bonsai.core.tool.Pset):
|
||||
|
||||
@classmethod
|
||||
def get_element_pset(
|
||||
cls, element: ifcopenshell.entity_instance, pset_name: str
|
||||
cls, element: ifcopenshell.entity_instance, pset_name: str, should_inherit: bool = True
|
||||
) -> Union[ifcopenshell.entity_instance, None]:
|
||||
pset = ifcopenshell.util.element.get_pset(element, pset_name)
|
||||
pset = ifcopenshell.util.element.get_pset(element, pset_name, should_inherit=should_inherit)
|
||||
if pset:
|
||||
return tool.Ifc.get().by_id(pset["id"])
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class TestCopyPropertyToSelection:
|
||||
|
||||
def test_copying_the_property_to_an_existing_pset(self, ifc, pset):
|
||||
ifc.get_entity("obj").should_be_called().will_return("element")
|
||||
pset.get_element_pset("element", "pset_name").should_be_called().will_return("pset")
|
||||
pset.get_element_pset("element", "pset_name", should_inherit=False).should_be_called().will_return("pset")
|
||||
ifc.run("pset.edit_pset", pset="pset", properties={"prop_name": "prop_value"}).should_be_called()
|
||||
subject.copy_property_to_selection(
|
||||
ifc, pset, is_pset=True, obj="obj", pset_name="pset_name", prop_name="prop_name", prop_value="prop_value"
|
||||
@@ -37,7 +37,7 @@ class TestCopyPropertyToSelection:
|
||||
|
||||
def test_creating_a_new_pset_if_it_doesnt_exist(self, ifc, pset):
|
||||
ifc.get_entity("obj").should_be_called().will_return("element")
|
||||
pset.get_element_pset("element", "pset_name").should_be_called().will_return(None)
|
||||
pset.get_element_pset("element", "pset_name", should_inherit=False).should_be_called().will_return(None)
|
||||
ifc.run("pset.add_pset", product="element", name="pset_name").should_be_called().will_return("pset")
|
||||
ifc.run("pset.edit_pset", pset="pset", properties={"prop_name": "prop_value"}).should_be_called()
|
||||
subject.copy_property_to_selection(
|
||||
@@ -46,7 +46,7 @@ class TestCopyPropertyToSelection:
|
||||
|
||||
def test_copying_the_quantity_to_an_existing_qto(self, ifc, pset):
|
||||
ifc.get_entity("obj").should_be_called().will_return("element")
|
||||
pset.get_element_pset("element", "qto_name").should_be_called().will_return("qto")
|
||||
pset.get_element_pset("element", "qto_name", should_inherit=False).should_be_called().will_return("qto")
|
||||
ifc.run("pset.edit_qto", qto="qto", properties={"prop_name": "prop_value"}).should_be_called()
|
||||
subject.copy_property_to_selection(
|
||||
ifc, pset, is_pset=False, obj="obj", pset_name="qto_name", prop_name="prop_name", prop_value="prop_value"
|
||||
@@ -54,7 +54,7 @@ class TestCopyPropertyToSelection:
|
||||
|
||||
def test_creating_a_new_qto_if_it_doesnt_exist(self, ifc, pset):
|
||||
ifc.get_entity("obj").should_be_called().will_return("element")
|
||||
pset.get_element_pset("element", "qto_name").should_be_called().will_return(None)
|
||||
pset.get_element_pset("element", "qto_name", should_inherit=False).should_be_called().will_return(None)
|
||||
ifc.run("pset.add_qto", product="element", name="qto_name").should_be_called().will_return("qto")
|
||||
ifc.run("pset.edit_qto", qto="qto", properties={"prop_name": "prop_value"}).should_be_called()
|
||||
subject.copy_property_to_selection(
|
||||
|
||||
Reference in New Issue
Block a user