mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Changes are now synchronised prior to switching representations.
This commit is contained in:
@@ -101,6 +101,7 @@ class SwitchRepresentation(bpy.types.Operator, Operator):
|
||||
should_reload=self.should_reload,
|
||||
enable_dynamic_voids=self.disable_opening_subtractions,
|
||||
is_global=self.should_switch_all_meshes,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
|
||||
@@ -217,7 +218,7 @@ class UpdateRepresentation(bpy.types.Operator):
|
||||
core.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_representation)
|
||||
Data.load(self.file, obj.BIMObjectProperties.ifc_definition_id)
|
||||
if obj.data.BIMMeshProperties.ifc_parameters:
|
||||
bpy.ops.bim.get_representation_ifc_parameters()
|
||||
core.get_representation_ifc_parameters(tool.Geometry, obj=obj)
|
||||
|
||||
|
||||
class UpdateParametricRepresentation(bpy.types.Operator):
|
||||
@@ -244,35 +245,20 @@ class UpdateParametricRepresentation(bpy.types.Operator):
|
||||
should_reload=True,
|
||||
enable_dynamic_voids=False,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
if show_representation_parameters:
|
||||
bpy.ops.bim.get_representation_ifc_parameters()
|
||||
core.get_representation_ifc_parameters(tool.Geometry, obj=obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class GetRepresentationIfcParameters(bpy.types.Operator):
|
||||
class GetRepresentationIfcParameters(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.get_representation_ifc_parameters"
|
||||
bl_label = "Get Representation IFC Parameters"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def execute(self, context):
|
||||
self.file = IfcStore.get_file()
|
||||
obj = context.active_object
|
||||
props = obj.data.BIMMeshProperties
|
||||
elements = self.file.traverse(self.file.by_id(props.ifc_definition_id))
|
||||
props.ifc_parameters.clear()
|
||||
for element in elements:
|
||||
if element.is_a("IfcRepresentationItem") or element.is_a("IfcParameterizedProfileDef"):
|
||||
for i in range(0, len(element)):
|
||||
if element.attribute_type(i) == "DOUBLE":
|
||||
new = props.ifc_parameters.add()
|
||||
new.name = "{}/{}".format(element.is_a(), element.attribute_name(i))
|
||||
new.step_id = element.id()
|
||||
new.type = element.attribute_type(i)
|
||||
new.index = i
|
||||
if element[i]:
|
||||
new.value = element[i]
|
||||
return {"FINISHED"}
|
||||
def _execute(self, context):
|
||||
core.get_representation_ifc_parameters(tool.Geometry, obj=context.active_object)
|
||||
|
||||
|
||||
class CopyRepresentation(bpy.types.Operator, Operator):
|
||||
|
||||
@@ -66,6 +66,7 @@ def mode_callback(obj, data):
|
||||
should_reload=True,
|
||||
enable_dynamic_voids=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
IfcStore.edited_objs.add(obj)
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
|
||||
@@ -238,6 +238,7 @@ class DynamicallyVoidProduct(bpy.types.Operator):
|
||||
should_reload=True,
|
||||
enable_dynamic_voids=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
if was_edit_mode:
|
||||
bpy.ops.object.mode_set(mode="EDIT")
|
||||
@@ -296,6 +297,7 @@ def regenerate_profile_usage(usecase_path, ifc_file, settings):
|
||||
should_reload=True,
|
||||
enable_dynamic_voids=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -220,6 +220,7 @@ class DumbProfileRegenerator:
|
||||
should_reload=True,
|
||||
enable_dynamic_voids=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
def sync_object(self, element):
|
||||
|
||||
@@ -1087,7 +1087,7 @@ class ImportP6(bpy.types.Operator, ImportHelper):
|
||||
def poll(cls, context):
|
||||
ifc_file = IfcStore.get_file()
|
||||
return ifc_file is not None
|
||||
|
||||
|
||||
def execute(self, context):
|
||||
from ifc4d.p62ifc import P62Ifc
|
||||
|
||||
@@ -1114,7 +1114,7 @@ class ImportP6XER(bpy.types.Operator, ImportHelper):
|
||||
def poll(cls, context):
|
||||
ifc_file = IfcStore.get_file()
|
||||
return ifc_file is not None
|
||||
|
||||
|
||||
def execute(self, context):
|
||||
from ifc4d.p6xer2ifc import P6XER2Ifc
|
||||
|
||||
@@ -1141,7 +1141,7 @@ class ImportPP(bpy.types.Operator, ImportHelper):
|
||||
def poll(cls, context):
|
||||
ifc_file = IfcStore.get_file()
|
||||
return ifc_file is not None
|
||||
|
||||
|
||||
def execute(self, context):
|
||||
from ifc4d.pp2ifc import PP2Ifc
|
||||
|
||||
@@ -1168,7 +1168,7 @@ class ImportMSP(bpy.types.Operator, ImportHelper):
|
||||
def poll(cls, context):
|
||||
ifc_file = IfcStore.get_file()
|
||||
return ifc_file is not None
|
||||
|
||||
|
||||
def execute(self, context):
|
||||
from ifc4d.msp2ifc import MSP2Ifc
|
||||
|
||||
|
||||
@@ -129,6 +129,7 @@ class RemoveOpening(bpy.types.Operator):
|
||||
should_reload=True,
|
||||
enable_dynamic_voids=False,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
Data.load(IfcStore.get_file(), obj.BIMObjectProperties.ifc_definition_id)
|
||||
|
||||
@@ -75,8 +75,17 @@ def add_representation(
|
||||
|
||||
|
||||
def switch_representation(
|
||||
geometry, obj=None, representation=None, should_reload=True, enable_dynamic_voids=True, is_global=True
|
||||
geometry,
|
||||
obj=None,
|
||||
representation=None,
|
||||
should_reload=True,
|
||||
enable_dynamic_voids=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=False,
|
||||
):
|
||||
if should_sync_changes_first and geometry.is_edited(obj) and not geometry.is_box_representation(representation):
|
||||
geometry.run_geometry_update_representation(obj=obj)
|
||||
|
||||
representation = geometry.resolve_mapped_representation(representation)
|
||||
existing_data = geometry.get_representation_data(representation)
|
||||
|
||||
@@ -98,6 +107,10 @@ def switch_representation(
|
||||
geometry.create_dynamic_voids(obj)
|
||||
|
||||
|
||||
def get_representation_ifc_parameters(geometry, obj=None, should_sync_changes_first=False):
|
||||
geometry.import_representation_parameters(geometry.get_object_data(obj))
|
||||
|
||||
|
||||
def remove_representation(ifc, geometry, obj=None, representation=None):
|
||||
element = ifc.get_entity(obj)
|
||||
if geometry.is_mapped_representation(representation) or geometry.is_type_product(element):
|
||||
|
||||
@@ -89,13 +89,17 @@ class Geometry:
|
||||
def get_total_representation_items(cls, obj): pass
|
||||
def has_data_users(cls, data): pass
|
||||
def import_representation(cls, obj, representation, enable_dynamic_voids=False): pass
|
||||
def import_representation_parameters(cls, data): pass
|
||||
def is_body_representation(cls, representation): pass
|
||||
def is_box_representation(cls, representation): pass
|
||||
def is_edited(cls, obj): pass
|
||||
def is_mapped_representation(cls, representation): pass
|
||||
def is_type_product(cls, element): pass
|
||||
def link(cls, element, obj): pass
|
||||
def rename_object(cls, obj, name): pass
|
||||
def replace_object_with_empty(cls, obj): pass
|
||||
def resolve_mapped_representation(cls, representation): pass
|
||||
def run_geometry_update_representation(cls, obj=None): pass
|
||||
def should_force_faceted_brep(cls): pass
|
||||
def should_force_triangulation(cls): pass
|
||||
def should_use_presentation_style_assignment(cls): pass
|
||||
|
||||
@@ -160,10 +160,35 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
ifc_importer.material_creator.create(element, obj, mesh)
|
||||
return mesh
|
||||
|
||||
@classmethod
|
||||
def import_representation_parameters(cls, data):
|
||||
props = data.BIMMeshProperties
|
||||
elements = tool.Ifc.get().traverse(tool.Ifc.get().by_id(props.ifc_definition_id))
|
||||
props.ifc_parameters.clear()
|
||||
for element in elements:
|
||||
if element.is_a("IfcRepresentationItem") or element.is_a("IfcParameterizedProfileDef"):
|
||||
for i in range(0, len(element)):
|
||||
if element.attribute_type(i) == "DOUBLE":
|
||||
new = props.ifc_parameters.add()
|
||||
new.name = "{}/{}".format(element.is_a(), element.attribute_name(i))
|
||||
new.step_id = element.id()
|
||||
new.type = element.attribute_type(i)
|
||||
new.index = i
|
||||
if element[i]:
|
||||
new.value = element[i]
|
||||
|
||||
@classmethod
|
||||
def is_body_representation(cls, representation):
|
||||
return representation.ContextOfItems.ContextIdentifier == "Body"
|
||||
|
||||
@classmethod
|
||||
def is_box_representation(cls, representation):
|
||||
return representation.ContextOfItems.ContextIdentifier == "Box"
|
||||
|
||||
@classmethod
|
||||
def is_edited(cls, obj):
|
||||
return list(obj.scale) != [1.0, 1.0, 1.0] or obj in IfcStore.edited_objs
|
||||
|
||||
@classmethod
|
||||
def is_mapped_representation(cls, representation):
|
||||
return representation.RepresentationType == "MappedRepresentation"
|
||||
@@ -200,6 +225,10 @@ class Geometry(blenderbim.core.tool.Geometry):
|
||||
return cls.resolve_mapped_representation(representation.Items[0].MappingSource.MappedRepresentation)
|
||||
return representation
|
||||
|
||||
@classmethod
|
||||
def run_geometry_update_representation(cls, obj=None):
|
||||
bpy.ops.bim.update_representation(obj=obj.name, ifc_representation_class="")
|
||||
|
||||
@classmethod
|
||||
def should_force_faceted_brep(cls):
|
||||
return bpy.context.scene.BIMGeometryProperties.should_force_faceted_brep
|
||||
|
||||
@@ -114,4 +114,5 @@ class Type(blenderbim.core.tool.Type):
|
||||
should_reload=should_reload,
|
||||
enable_dynamic_voids=enable_dynamic_voids,
|
||||
is_global=is_global,
|
||||
should_sync_changes_first=False,
|
||||
)
|
||||
|
||||
@@ -73,6 +73,37 @@ Scenario: Switch representation
|
||||
And I press "bim.switch_representation(obj='IfcWall/Cube', ifc_definition_id={representation})"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Switch representation - current edited representation is updated prior to switch
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And the variable "context" is "{ifc}.by_type('IfcGeometricRepresentationSubContext')[-1].id()"
|
||||
And I set "scene.BIMProperties.contexts" to "{context}"
|
||||
And I press "bim.add_representation"
|
||||
When the object "IfcWall/Cube" is scaled to "2"
|
||||
And the variable "representation" is "{ifc}.by_type('IfcShapeRepresentation')[0].id()"
|
||||
And I press "bim.switch_representation(obj='IfcWall/Cube', ifc_definition_id={representation}, should_reload=True)"
|
||||
And the variable "representation" is "{ifc}.by_type('IfcShapeRepresentation')[-1].id()"
|
||||
And I press "bim.switch_representation(obj='IfcWall/Cube', ifc_definition_id={representation}, should_reload=True)"
|
||||
When I press "export_ifc.bim(filepath='{cwd}/test/files/export.ifc')"
|
||||
Then the object "IfcWall/Cube" dimensions are "4,4,0"
|
||||
|
||||
Scenario: Switch representation - current edited representation is discarded if switching to a box
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
When the object "IfcWall/Cube" is scaled to "2"
|
||||
And the variable "representation" is "{ifc}.by_type('IfcShapeRepresentation')[-1].id()"
|
||||
And I press "bim.switch_representation(obj='IfcWall/Cube', ifc_definition_id={representation}, should_reload=True)"
|
||||
And the variable "representation" is "{ifc}.by_type('IfcShapeRepresentation')[0].id()"
|
||||
And I press "bim.switch_representation(obj='IfcWall/Cube', ifc_definition_id={representation}, should_reload=True)"
|
||||
When I press "export_ifc.bim(filepath='{cwd}/test/files/export.ifc')"
|
||||
Then the object "IfcWall/Cube" dimensions are "2,2,2"
|
||||
|
||||
Scenario: Switch representation - existing Blender modifiers must be purged
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
@@ -210,6 +241,16 @@ Scenario: Update representation - updating a profiled extrusion
|
||||
When I press "bim.update_representation(obj='IfcWall/Cube')"
|
||||
Then the object "IfcWall/Cube" has a "SweptSolid" representation of "Model/Body/MODEL_VIEW"
|
||||
|
||||
Scenario: Get representation IFC parameters
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
And the object "Cube" is selected
|
||||
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
|
||||
And I press "bim.assign_class"
|
||||
And I press "bim.update_representation(ifc_representation_class='IfcExtrudedAreaSolid/IfcRectangleProfileDef')"
|
||||
When I press "bim.get_representation_ifc_parameters"
|
||||
Then nothing happens
|
||||
|
||||
Scenario: Copy representation
|
||||
Given an empty IFC project
|
||||
And I add a cube
|
||||
|
||||
@@ -182,6 +182,7 @@ class TestAddRepresentation:
|
||||
|
||||
class TestSwitchRepresentation:
|
||||
def test_switching_to_a_freshly_loaded_representation(self, geometry):
|
||||
geometry.is_edited("obj").should_be_called().will_return(False)
|
||||
geometry.resolve_mapped_representation("mapped_rep").should_be_called().will_return("representation")
|
||||
geometry.get_representation_data("representation").should_be_called().will_return(None)
|
||||
geometry.import_representation(
|
||||
@@ -201,9 +202,11 @@ class TestSwitchRepresentation:
|
||||
should_reload=True,
|
||||
enable_dynamic_voids=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
def test_switching_to_a_reloaded_representation_and_deleting_the_existing_data(self, geometry):
|
||||
geometry.is_edited("obj").should_be_called().will_return(False)
|
||||
geometry.resolve_mapped_representation("mapped_rep").should_be_called().will_return("representation")
|
||||
geometry.get_representation_data("representation").should_be_called().will_return("existing_data")
|
||||
geometry.import_representation(
|
||||
@@ -224,9 +227,11 @@ class TestSwitchRepresentation:
|
||||
should_reload=True,
|
||||
enable_dynamic_voids=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
def test_switching_to_an_existing_representation(self, geometry):
|
||||
geometry.is_edited("obj").should_be_called().will_return(False)
|
||||
geometry.resolve_mapped_representation("mapped_rep").should_be_called().will_return("representation")
|
||||
geometry.get_representation_data("representation").should_be_called().will_return("data")
|
||||
geometry.change_object_data("obj", "data", is_global=True).should_be_called()
|
||||
@@ -240,9 +245,11 @@ class TestSwitchRepresentation:
|
||||
should_reload=False,
|
||||
enable_dynamic_voids=True,
|
||||
is_global=True,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
def test_switching_to_non_dynamic_baked_voids(self, geometry):
|
||||
geometry.is_edited("obj").should_be_called().will_return(False)
|
||||
geometry.resolve_mapped_representation("mapped_rep").should_be_called().will_return("representation")
|
||||
geometry.get_representation_data("representation").should_be_called().will_return("data")
|
||||
geometry.change_object_data("obj", "data", is_global=False).should_be_called()
|
||||
@@ -254,8 +261,34 @@ class TestSwitchRepresentation:
|
||||
should_reload=False,
|
||||
enable_dynamic_voids=False,
|
||||
is_global=False,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
def test_updating_a_representation_if_the_blender_object_has_been_edited_prior_to_switching(self, geometry):
|
||||
geometry.is_edited("obj").should_be_called().will_return(True)
|
||||
geometry.is_box_representation("mapped_rep").should_be_called().will_return(False)
|
||||
geometry.run_geometry_update_representation(obj="obj").should_be_called()
|
||||
geometry.resolve_mapped_representation("mapped_rep").should_be_called().will_return("representation")
|
||||
geometry.get_representation_data("representation").should_be_called().will_return("data")
|
||||
geometry.change_object_data("obj", "data", is_global=False).should_be_called()
|
||||
geometry.clear_modifiers("obj").should_be_called()
|
||||
subject.switch_representation(
|
||||
geometry,
|
||||
obj="obj",
|
||||
representation="mapped_rep",
|
||||
should_reload=False,
|
||||
enable_dynamic_voids=False,
|
||||
is_global=False,
|
||||
should_sync_changes_first=True,
|
||||
)
|
||||
|
||||
|
||||
class TestGetRepresentationIfcParameters:
|
||||
def test_run(self, geometry):
|
||||
geometry.get_object_data("obj").should_be_called().will_return("data")
|
||||
geometry.import_representation_parameters("data").should_be_called()
|
||||
subject.get_representation_ifc_parameters(geometry, obj="obj", should_sync_changes_first=False)
|
||||
|
||||
|
||||
class TestRemoveRepresentation:
|
||||
def test_removing_an_actively_used_mapped_representation_by_remapping_usages_to_an_empty(self, ifc, geometry):
|
||||
|
||||
@@ -248,6 +248,24 @@ class TestImportRepresentation(NewFile):
|
||||
assert len(mesh.edges) == 4
|
||||
|
||||
|
||||
class TestImportRepresentationParameters(NewFile):
|
||||
def test_run(self):
|
||||
ifc = ifcopenshell.file()
|
||||
tool.Ifc.set(ifc)
|
||||
swept_area = ifc.createIfcCircleProfileDef(Radius=1)
|
||||
item = ifc.createIfcExtrudedAreaSolid(SweptArea=swept_area, Depth=2)
|
||||
representation = ifc.createIfcShapeRepresentation(Items=[item])
|
||||
data = bpy.data.meshes.new("Mesh")
|
||||
data.BIMMeshProperties.ifc_definition_id = representation.id()
|
||||
subject.import_representation_parameters(data)
|
||||
assert data.BIMMeshProperties.ifc_parameters[0].name == "IfcExtrudedAreaSolid/Depth"
|
||||
assert data.BIMMeshProperties.ifc_parameters[0].step_id == item.id()
|
||||
assert data.BIMMeshProperties.ifc_parameters[0].index == 3
|
||||
assert data.BIMMeshProperties.ifc_parameters[1].name == "IfcCircleProfileDef/Radius"
|
||||
assert data.BIMMeshProperties.ifc_parameters[1].step_id == swept_area.id()
|
||||
assert data.BIMMeshProperties.ifc_parameters[1].index == 3
|
||||
|
||||
|
||||
class TestIsBodyRepresentation(NewFile):
|
||||
def test_run(self):
|
||||
ifc = ifcopenshell.file()
|
||||
@@ -260,6 +278,30 @@ class TestIsBodyRepresentation(NewFile):
|
||||
assert subject.is_body_representation(representation) is False
|
||||
|
||||
|
||||
class TestIsBoxRepresentation(NewFile):
|
||||
def test_run(self):
|
||||
ifc = ifcopenshell.file()
|
||||
context = ifc.createIfcGeometricRepresentationContext()
|
||||
representation = ifc.createIfcShapeRepresentation()
|
||||
representation.ContextOfItems = context
|
||||
context.ContextIdentifier = "Box"
|
||||
assert subject.is_box_representation(representation) is True
|
||||
context.ContextIdentifier = "Clearance"
|
||||
assert subject.is_box_representation(representation) is False
|
||||
|
||||
|
||||
class TestIsEdited(NewFile):
|
||||
def test_run(self):
|
||||
obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh"))
|
||||
assert subject.is_edited(obj) is False
|
||||
obj.scale[0] = 2
|
||||
assert subject.is_edited(obj) is True
|
||||
obj.scale[0] = 1
|
||||
assert subject.is_edited(obj) is False
|
||||
IfcStore.edited_objs.add(obj)
|
||||
assert subject.is_edited(obj) is True
|
||||
|
||||
|
||||
class TestIsMappedRepresentation(NewFile):
|
||||
def test_run(self):
|
||||
ifc = ifcopenshell.file()
|
||||
@@ -327,6 +369,11 @@ class TestResolveMappedRepresentation(NewFile):
|
||||
assert subject.resolve_mapped_representation(representation) == representation
|
||||
|
||||
|
||||
class TestRunGeometryUpdateRepresentation(NewFile):
|
||||
def test_nothing(self):
|
||||
pass
|
||||
|
||||
|
||||
class TestShouldForceFacetedBrep(NewFile):
|
||||
def test_run(self):
|
||||
result = bpy.context.scene.BIMGeometryProperties.should_force_faceted_brep
|
||||
|
||||
Reference in New Issue
Block a user