mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
Fix #3764. WARNING: Preserve material usage attributes when switching types and refactor type regeneration API listeners
This is a dangerous commit because the effect of refactoring of the listeners is hard to know.
This commit is contained in:
@@ -168,7 +168,7 @@ def create_annotation_occurrence(context):
|
|||||||
)
|
)
|
||||||
assert element
|
assert element
|
||||||
|
|
||||||
bonsai.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type)
|
bonsai.core.type.assign_type(tool.Ifc, tool.Model, tool.Type, element=element, type=relating_type)
|
||||||
|
|
||||||
ifcopenshell.api.group.assign_group(ifc_file, group=tool.Drawing.get_drawing_group(drawing), products=[element])
|
ifcopenshell.api.group.assign_group(ifc_file, group=tool.Drawing.get_drawing_group(drawing), products=[element])
|
||||||
tool.Collector.assign(obj)
|
tool.Collector.assign(obj)
|
||||||
|
|||||||
@@ -37,24 +37,11 @@ def load_post(*args):
|
|||||||
"sequence.edit_task_time", "Bonsai.Task.CalculateQuantities", task.calculate_quantities
|
"sequence.edit_task_time", "Bonsai.Task.CalculateQuantities", task.calculate_quantities
|
||||||
)
|
)
|
||||||
|
|
||||||
ifcopenshell.api.add_post_listener(
|
|
||||||
"type.assign_type", "Bonsai.DumbWall.RegenerateFromType", wall.DumbWallPlaner().regenerate_from_type
|
|
||||||
)
|
|
||||||
|
|
||||||
ifcopenshell.api.add_post_listener(
|
|
||||||
"type.assign_type", "Bonsai.DumbSlab.RegenerateFromType", slab.DumbSlabPlaner().regenerate_from_type
|
|
||||||
)
|
|
||||||
|
|
||||||
ifcopenshell.api.add_post_listener(
|
ifcopenshell.api.add_post_listener(
|
||||||
"material.edit_profile",
|
"material.edit_profile",
|
||||||
"Bonsai.DumbProfile.RegenerateFromProfile",
|
"Bonsai.DumbProfile.RegenerateFromProfile",
|
||||||
profile.DumbProfileRegenerator().regenerate_from_profile,
|
profile.DumbProfileRegenerator().regenerate_from_profile,
|
||||||
)
|
)
|
||||||
ifcopenshell.api.add_post_listener(
|
|
||||||
"type.assign_type",
|
|
||||||
"Bonsai.DumbProfile.RegenerateFromType",
|
|
||||||
profile.DumbProfileRegenerator().regenerate_from_type,
|
|
||||||
)
|
|
||||||
|
|
||||||
ifcopenshell.api.add_post_listener(
|
ifcopenshell.api.add_post_listener(
|
||||||
"type.assign_type",
|
"type.assign_type",
|
||||||
|
|||||||
@@ -380,7 +380,7 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
ifc_class=instance_class,
|
ifc_class=instance_class,
|
||||||
should_add_representation=False,
|
should_add_representation=False,
|
||||||
)
|
)
|
||||||
bonsai.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type)
|
bonsai.core.type.assign_type(tool.Ifc, tool.Model, tool.Type, element=element, type=relating_type)
|
||||||
|
|
||||||
rprops = tool.Root.get_root_props()
|
rprops = tool.Root.get_root_props()
|
||||||
ifc_context = None
|
ifc_context = None
|
||||||
@@ -433,7 +433,7 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
)
|
)
|
||||||
|
|
||||||
element = tool.Ifc.get_entity(obj)
|
element = tool.Ifc.get_entity(obj)
|
||||||
bonsai.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type)
|
bonsai.core.type.assign_type(tool.Ifc, tool.Model, tool.Type, element=element, type=relating_type)
|
||||||
|
|
||||||
if existing_context:
|
if existing_context:
|
||||||
representation = ifcopenshell.util.representation.get_representation(element, existing_context)
|
representation = ifcopenshell.util.representation.get_representation(element, existing_context)
|
||||||
|
|||||||
@@ -267,22 +267,6 @@ class DumbProfileRegenerator:
|
|||||||
results.extend(inverse.RelatedObjects)
|
results.extend(inverse.RelatedObjects)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
def regenerate_from_type(self, usecase_path, ifc_file, settings):
|
|
||||||
relating_type = settings["relating_type"]
|
|
||||||
|
|
||||||
new_material = ifcopenshell.util.element.get_material(relating_type)
|
|
||||||
if not new_material or not new_material.is_a("IfcMaterialProfileSet"):
|
|
||||||
return
|
|
||||||
|
|
||||||
for related_object in settings["related_objects"]:
|
|
||||||
self._regenerate_from_type(related_object)
|
|
||||||
|
|
||||||
def _regenerate_from_type(self, related_object: ifcopenshell.entity_instance) -> None:
|
|
||||||
obj = tool.Ifc.get_object(related_object)
|
|
||||||
if not obj or not tool.Geometry.get_active_representation(obj):
|
|
||||||
return
|
|
||||||
DumbProfileRecalculator().recalculate([obj])
|
|
||||||
|
|
||||||
|
|
||||||
class ExtendProfile(bpy.types.Operator, tool.Ifc.Operator):
|
class ExtendProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
bl_idname = "bim.extend_profile"
|
bl_idname = "bim.extend_profile"
|
||||||
|
|||||||
@@ -243,38 +243,6 @@ class DumbSlabPlaner:
|
|||||||
for element in rel.RelatedObjects:
|
for element in rel.RelatedObjects:
|
||||||
self.change_thickness(element, total_thickness, preserve_offset=True)
|
self.change_thickness(element, total_thickness, preserve_offset=True)
|
||||||
|
|
||||||
def regenerate_from_type(self, usecase_path, ifc_file, settings):
|
|
||||||
relating_type = settings["relating_type"]
|
|
||||||
|
|
||||||
new_material = ifcopenshell.util.element.get_material(relating_type)
|
|
||||||
if not new_material or not new_material.is_a("IfcMaterialLayerSet"):
|
|
||||||
return
|
|
||||||
|
|
||||||
parametric = ifcopenshell.util.element.get_psets(settings["relating_type"]).get("EPset_Parametric")
|
|
||||||
layer_set_direction = None
|
|
||||||
if parametric:
|
|
||||||
layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction)
|
|
||||||
new_thickness = sum([l.LayerThickness for l in new_material.MaterialLayers])
|
|
||||||
|
|
||||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
|
||||||
for related_object in settings["related_objects"]:
|
|
||||||
self._regenerate_from_type(related_object, layer_set_direction, new_thickness)
|
|
||||||
|
|
||||||
def _regenerate_from_type(
|
|
||||||
self, related_object: ifcopenshell.entity_instance, layer_set_direction: Optional[str], new_thickness: float
|
|
||||||
) -> None:
|
|
||||||
obj = tool.Ifc.get_object(related_object)
|
|
||||||
if not obj or not tool.Geometry.get_active_representation(obj):
|
|
||||||
return
|
|
||||||
|
|
||||||
material = ifcopenshell.util.element.get_material(related_object)
|
|
||||||
if not material or not material.is_a("IfcMaterialLayerSetUsage"):
|
|
||||||
return
|
|
||||||
if layer_set_direction:
|
|
||||||
material.LayerSetDirection = layer_set_direction
|
|
||||||
if material.LayerSetDirection == "AXIS3":
|
|
||||||
self.change_thickness(related_object, new_thickness)
|
|
||||||
|
|
||||||
def regenerate_from_occurence(self, element, material_set_usage):
|
def regenerate_from_occurence(self, element, material_set_usage):
|
||||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
layer_set = material_set_usage.ForLayerSet
|
layer_set = material_set_usage.ForLayerSet
|
||||||
|
|||||||
@@ -1132,37 +1132,6 @@ class DumbWallPlaner:
|
|||||||
walls.extend([tool.Ifc.get_object(e) for e in rel.RelatedObjects])
|
walls.extend([tool.Ifc.get_object(e) for e in rel.RelatedObjects])
|
||||||
tool.Model.recalculate_walls([w for w in set(walls) if w])
|
tool.Model.recalculate_walls([w for w in set(walls) if w])
|
||||||
|
|
||||||
def regenerate_from_type(self, usecase_path, ifc_file, settings):
|
|
||||||
relating_type = settings["relating_type"]
|
|
||||||
|
|
||||||
new_material = ifcopenshell.util.element.get_material(relating_type)
|
|
||||||
if not new_material or not new_material.is_a("IfcMaterialLayerSet"):
|
|
||||||
return
|
|
||||||
|
|
||||||
parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric")
|
|
||||||
layer_set_direction = None
|
|
||||||
if parametric:
|
|
||||||
layer_set_direction = parametric.get("LayerSetDirection", layer_set_direction)
|
|
||||||
|
|
||||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
|
|
||||||
for related_object in settings["related_objects"]:
|
|
||||||
self._regenerate_from_type(related_object, layer_set_direction)
|
|
||||||
|
|
||||||
def _regenerate_from_type(
|
|
||||||
self, related_object: ifcopenshell.entity_instance, layer_set_direction: Optional[str]
|
|
||||||
) -> None:
|
|
||||||
obj = tool.Ifc.get_object(related_object)
|
|
||||||
if not obj or not tool.Geometry.get_active_representation(obj):
|
|
||||||
return
|
|
||||||
|
|
||||||
material = ifcopenshell.util.element.get_material(related_object)
|
|
||||||
if not material or not material.is_a("IfcMaterialLayerSetUsage"):
|
|
||||||
return
|
|
||||||
if layer_set_direction:
|
|
||||||
material.LayerSetDirection = layer_set_direction
|
|
||||||
if material.LayerSetDirection == "AXIS2":
|
|
||||||
tool.Model.recalculate_walls([obj])
|
|
||||||
|
|
||||||
|
|
||||||
class DumbWallJoiner:
|
class DumbWallJoiner:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class AssignType(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
element = tool.Ifc.get_entity(obj)
|
element = tool.Ifc.get_entity(obj)
|
||||||
if not element or not element.is_a("IfcObject"):
|
if not element or not element.is_a("IfcObject"):
|
||||||
continue
|
continue
|
||||||
core.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type)
|
core.assign_type(tool.Ifc, tool.Model, tool.Type, element=element, type=relating_type)
|
||||||
|
|
||||||
# Switch to the drawing's target view if available
|
# Switch to the drawing's target view if available
|
||||||
if active_target_view and element.Representation:
|
if active_target_view and element.Representation:
|
||||||
@@ -384,7 +384,7 @@ class DuplicateType(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
for selected_obj in selected_objects:
|
for selected_obj in selected_objects:
|
||||||
selected_element = tool.Ifc.get_entity(selected_obj)
|
selected_element = tool.Ifc.get_entity(selected_obj)
|
||||||
if selected_element and selected_element.is_a("IfcObject"):
|
if selected_element and selected_element.is_a("IfcObject"):
|
||||||
core.assign_type(tool.Ifc, tool.Type, element=selected_element, type=new)
|
core.assign_type(tool.Ifc, tool.Model, tool.Type, element=selected_element, type=new)
|
||||||
if prefs.occurrence_name_style == "TYPE":
|
if prefs.occurrence_name_style == "TYPE":
|
||||||
selected_obj.name = tool.Model.generate_occurrence_name(new, selected_element.is_a())
|
selected_obj.name = tool.Model.generate_occurrence_name(new, selected_element.is_a())
|
||||||
|
|
||||||
|
|||||||
@@ -31,14 +31,22 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
def assign_type(
|
def assign_type(
|
||||||
ifc: type[tool.Ifc],
|
ifc: type[tool.Ifc],
|
||||||
|
model: type[tool.Model],
|
||||||
type_tool: type[tool.Type],
|
type_tool: type[tool.Type],
|
||||||
element: ifcopenshell.entity_instance,
|
element: ifcopenshell.entity_instance,
|
||||||
type: ifcopenshell.entity_instance,
|
type: ifcopenshell.entity_instance,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
usage_attributes = type_tool.record_material_usage_attributes(element)
|
||||||
ifc.run("type.assign_type", related_objects=[element], relating_type=type)
|
ifc.run("type.assign_type", related_objects=[element], relating_type=type)
|
||||||
obj = ifc.get_object(element)
|
obj = ifc.get_object(element)
|
||||||
if type_tool.has_material_usage(element):
|
if (usage := model.get_usage_type(type)) and usage_attributes:
|
||||||
pass # for now, representation regeneration handled by API listeners
|
type_tool.restore_material_usage_attributes(element, usage_attributes)
|
||||||
|
if (usage := model.get_usage_type(type)) == "PROFILE":
|
||||||
|
model.regenerate_profile(obj)
|
||||||
|
elif usage == "LAYER2":
|
||||||
|
model.recalculate_walls([obj])
|
||||||
|
elif usage == "LAYER3":
|
||||||
|
model.regenerate_slab(obj)
|
||||||
else:
|
else:
|
||||||
type_data = type_tool.get_object_data(ifc.get_object(type))
|
type_data = type_tool.get_object_data(ifc.get_object(type))
|
||||||
if type_data:
|
if type_data:
|
||||||
|
|||||||
@@ -1279,7 +1279,7 @@ class Drawing(bonsai.core.tool.Drawing):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def run_type_assign_type(cls, element: ifcopenshell.entity_instance, relating_type: ifcopenshell.entity_instance):
|
def run_type_assign_type(cls, element: ifcopenshell.entity_instance, relating_type: ifcopenshell.entity_instance):
|
||||||
return bonsai.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=relating_type)
|
return bonsai.core.type.assign_type(tool.Ifc, tool.Model, tool.Type, element=element, type=relating_type)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def reload_representation(cls, obj: bpy.types.Object, representation: ifcopenshell.entity_instance):
|
def reload_representation(cls, obj: bpy.types.Object, representation: ifcopenshell.entity_instance):
|
||||||
|
|||||||
@@ -2743,6 +2743,21 @@ class Model(bonsai.core.tool.Model):
|
|||||||
|
|
||||||
cls.recreate_wall(element, wall)
|
cls.recreate_wall(element, wall)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def regenerate_slab(cls, obj: bpy.types.Object) -> None:
|
||||||
|
from bonsai.bim.module.model.slab import DumbSlabPlaner
|
||||||
|
|
||||||
|
element = tool.Ifc.get_entity(obj)
|
||||||
|
material_set = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
|
||||||
|
new_thickness = sum([l.LayerThickness for l in material_set.MaterialLayers])
|
||||||
|
DumbSlabPlaner().change_thickness(element, new_thickness)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def regenerate_profile(cls, obj: bpy.types.Object) -> None:
|
||||||
|
from bonsai.bim.module.model.profile import DumbProfileRecalculator
|
||||||
|
|
||||||
|
DumbProfileRecalculator().recalculate([obj])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def run_ifcsverchok_graph_on_bonsai_file(cls, node_tree: sverchok.node_tree.SverchCustomTree) -> None:
|
def run_ifcsverchok_graph_on_bonsai_file(cls, node_tree: sverchok.node_tree.SverchCustomTree) -> None:
|
||||||
from ifcsverchok.ifcstore import SvIfcStore
|
from ifcsverchok.ifcstore import SvIfcStore
|
||||||
|
|||||||
@@ -1177,6 +1177,7 @@ class Spatial(bonsai.core.tool.Spatial):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def assign_type_to_obj(cls, obj: bpy.types.Object) -> None:
|
def assign_type_to_obj(cls, obj: bpy.types.Object) -> None:
|
||||||
|
# TODO this code looks in the wrong spot and suspicious
|
||||||
props = tool.Model.get_model_props()
|
props = tool.Model.get_model_props()
|
||||||
ifc_file = tool.Ifc.get()
|
ifc_file = tool.Ifc.get()
|
||||||
relating_type_id = props.relating_type_id
|
relating_type_id = props.relating_type_id
|
||||||
@@ -1196,7 +1197,7 @@ class Spatial(bonsai.core.tool.Spatial):
|
|||||||
element: ifcopenshell.entity_instance,
|
element: ifcopenshell.entity_instance,
|
||||||
relating_type: ifcopenshell.entity_instance,
|
relating_type: ifcopenshell.entity_instance,
|
||||||
) -> None:
|
) -> None:
|
||||||
bonsai.core.type.assign_type(ifc, type, element=element, type=relating_type)
|
bonsai.core.type.assign_type(ifc, tool.Model, type, element=element, type=relating_type)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def regen_obj_representation(cls, obj: bpy.types.Object, body: ifcopenshell.entity_instance) -> None:
|
def regen_obj_representation(cls, obj: bpy.types.Object, body: ifcopenshell.entity_instance) -> None:
|
||||||
|
|||||||
@@ -143,3 +143,20 @@ class Type(bonsai.core.tool.Type):
|
|||||||
obj=obj,
|
obj=obj,
|
||||||
representation=representation,
|
representation=representation,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def record_material_usage_attributes(cls, element: ifcopenshell.entity_instance) -> dict | None:
|
||||||
|
if (material := ifcopenshell.util.element.get_material(element)) and "Usage" in material.is_a():
|
||||||
|
return material.get_info()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def restore_material_usage_attributes(cls, element: ifcopenshell.entity_instance, usage_attributes: dict) -> None:
|
||||||
|
if (material := ifcopenshell.util.element.get_material(element)) and material.is_a() == usage_attributes[
|
||||||
|
"type"
|
||||||
|
]:
|
||||||
|
if usage_attributes["type"] == "IfcMaterialLayerSetUsage":
|
||||||
|
for attr in ("LayerSetDirection", "DirectionSense", "OffsetFromReferenceLine", "ReferenceExtent"):
|
||||||
|
setattr(material, attr, usage_attributes.get(attr))
|
||||||
|
elif usage_attributes["type"] == "IfcMaterialProfileSetUsage":
|
||||||
|
for attr in ("CardinalPoint", "ReferenceExtent"):
|
||||||
|
setattr(material, attr, usage_attributes.get(attr))
|
||||||
|
|||||||
@@ -17,11 +17,11 @@
|
|||||||
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import bonsai.core.type as subject
|
import bonsai.core.type as subject
|
||||||
from test.core.bootstrap import geometry, ifc, type
|
from test.core.bootstrap import ifc, model, type, geometry
|
||||||
|
|
||||||
|
|
||||||
class TestAssignType:
|
class TestAssignType:
|
||||||
def test_assigning_and_switching_to_an_existing_type_data(self, ifc, type):
|
def test_assigning_and_switching_to_an_existing_type_data(self, ifc, model, type):
|
||||||
ifc.run("type.assign_type", related_objects=["element"], relating_type="type").should_be_called()
|
ifc.run("type.assign_type", related_objects=["element"], relating_type="type").should_be_called()
|
||||||
type.has_material_usage("element").should_be_called().will_return(False)
|
type.has_material_usage("element").should_be_called().will_return(False)
|
||||||
ifc.get_object("type").should_be_called().will_return("type_obj")
|
ifc.get_object("type").should_be_called().will_return("type_obj")
|
||||||
@@ -29,7 +29,7 @@ class TestAssignType:
|
|||||||
type.change_object_data("obj", "type_obj_data", is_global=False).should_be_called()
|
type.change_object_data("obj", "type_obj_data", is_global=False).should_be_called()
|
||||||
ifc.get_object("element").should_be_called().will_return("obj")
|
ifc.get_object("element").should_be_called().will_return("obj")
|
||||||
type.disable_editing("obj").should_be_called()
|
type.disable_editing("obj").should_be_called()
|
||||||
subject.assign_type(ifc, type, element="element", type="type")
|
subject.assign_type(ifc, model, type, element="element", type="type")
|
||||||
|
|
||||||
def test_assigning_and_not_changing_data_if_the_type_has_no_data(self, ifc, type):
|
def test_assigning_and_not_changing_data_if_the_type_has_no_data(self, ifc, type):
|
||||||
ifc.run("type.assign_type", related_objects=["element"], relating_type="type").should_be_called()
|
ifc.run("type.assign_type", related_objects=["element"], relating_type="type").should_be_called()
|
||||||
@@ -38,7 +38,7 @@ class TestAssignType:
|
|||||||
type.get_object_data("type_obj").should_be_called().will_return(None)
|
type.get_object_data("type_obj").should_be_called().will_return(None)
|
||||||
ifc.get_object("element").should_be_called().will_return("obj")
|
ifc.get_object("element").should_be_called().will_return("obj")
|
||||||
type.disable_editing("obj").should_be_called()
|
type.disable_editing("obj").should_be_called()
|
||||||
subject.assign_type(ifc, type, element="element", type="type")
|
subject.assign_type(ifc, model, type, element="element", type="type")
|
||||||
|
|
||||||
|
|
||||||
class TestPurgeUnusedTypes:
|
class TestPurgeUnusedTypes:
|
||||||
|
|||||||
Reference in New Issue
Block a user