mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-06 07:51:47 +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
|
||||
|
||||
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])
|
||||
tool.Collector.assign(obj)
|
||||
|
||||
@@ -37,24 +37,11 @@ def load_post(*args):
|
||||
"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(
|
||||
"material.edit_profile",
|
||||
"Bonsai.DumbProfile.RegenerateFromProfile",
|
||||
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(
|
||||
"type.assign_type",
|
||||
|
||||
@@ -380,7 +380,7 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator):
|
||||
ifc_class=instance_class,
|
||||
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()
|
||||
ifc_context = None
|
||||
@@ -433,7 +433,7 @@ class AddOccurrence(bpy.types.Operator, tool.Ifc.Operator):
|
||||
)
|
||||
|
||||
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:
|
||||
representation = ifcopenshell.util.representation.get_representation(element, existing_context)
|
||||
|
||||
@@ -267,22 +267,6 @@ class DumbProfileRegenerator:
|
||||
results.extend(inverse.RelatedObjects)
|
||||
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):
|
||||
bl_idname = "bim.extend_profile"
|
||||
|
||||
@@ -243,38 +243,6 @@ class DumbSlabPlaner:
|
||||
for element in rel.RelatedObjects:
|
||||
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):
|
||||
self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||
layer_set = material_set_usage.ForLayerSet
|
||||
|
||||
@@ -1132,37 +1132,6 @@ class DumbWallPlaner:
|
||||
walls.extend([tool.Ifc.get_object(e) for e in rel.RelatedObjects])
|
||||
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:
|
||||
def __init__(self):
|
||||
|
||||
@@ -74,7 +74,7 @@ class AssignType(bpy.types.Operator, tool.Ifc.Operator):
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if not element or not element.is_a("IfcObject"):
|
||||
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
|
||||
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:
|
||||
selected_element = tool.Ifc.get_entity(selected_obj)
|
||||
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":
|
||||
selected_obj.name = tool.Model.generate_occurrence_name(new, selected_element.is_a())
|
||||
|
||||
|
||||
@@ -31,14 +31,22 @@ if TYPE_CHECKING:
|
||||
|
||||
def assign_type(
|
||||
ifc: type[tool.Ifc],
|
||||
model: type[tool.Model],
|
||||
type_tool: type[tool.Type],
|
||||
element: ifcopenshell.entity_instance,
|
||||
type: ifcopenshell.entity_instance,
|
||||
) -> None:
|
||||
usage_attributes = type_tool.record_material_usage_attributes(element)
|
||||
ifc.run("type.assign_type", related_objects=[element], relating_type=type)
|
||||
obj = ifc.get_object(element)
|
||||
if type_tool.has_material_usage(element):
|
||||
pass # for now, representation regeneration handled by API listeners
|
||||
if (usage := model.get_usage_type(type)) and usage_attributes:
|
||||
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:
|
||||
type_data = type_tool.get_object_data(ifc.get_object(type))
|
||||
if type_data:
|
||||
|
||||
@@ -1279,7 +1279,7 @@ class Drawing(bonsai.core.tool.Drawing):
|
||||
|
||||
@classmethod
|
||||
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
|
||||
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)
|
||||
|
||||
@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
|
||||
def run_ifcsverchok_graph_on_bonsai_file(cls, node_tree: sverchok.node_tree.SverchCustomTree) -> None:
|
||||
from ifcsverchok.ifcstore import SvIfcStore
|
||||
|
||||
@@ -1177,6 +1177,7 @@ class Spatial(bonsai.core.tool.Spatial):
|
||||
|
||||
@classmethod
|
||||
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()
|
||||
ifc_file = tool.Ifc.get()
|
||||
relating_type_id = props.relating_type_id
|
||||
@@ -1196,7 +1197,7 @@ class Spatial(bonsai.core.tool.Spatial):
|
||||
element: ifcopenshell.entity_instance,
|
||||
relating_type: ifcopenshell.entity_instance,
|
||||
) -> 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
|
||||
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,
|
||||
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/>.
|
||||
|
||||
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:
|
||||
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()
|
||||
type.has_material_usage("element").should_be_called().will_return(False)
|
||||
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()
|
||||
ifc.get_object("element").should_be_called().will_return("obj")
|
||||
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):
|
||||
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)
|
||||
ifc.get_object("element").should_be_called().will_return("obj")
|
||||
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:
|
||||
|
||||
Reference in New Issue
Block a user