Fix bug where assigning a type did not use existing representation data in the scene if the context had changed

This commit is contained in:
Dion Moult
2021-11-06 20:56:53 +11:00
parent 4149292a13
commit 8e215382f7
9 changed files with 99 additions and 66 deletions
+2 -1
View File
@@ -223,11 +223,12 @@ class Surveyor:
@interface
class Type:
def change_object_data(cls, obj, data, is_global=False): pass
def disable_editing(cls, obj): pass
def get_any_representation(cls, element): pass
def get_body_context(cls): pass
def get_body_representation(cls, element): pass
def get_ifc_representation_class(cls, element): pass
def get_object_data(cls, obj): pass
def get_profile_set_usage(cls, element): pass
def get_representation_context(cls, representation): pass
def has_dynamic_voids(cls, obj): pass
+11 -13
View File
@@ -36,18 +36,16 @@ def assign_type(ifc, type_tool, element=None, type=None):
ifc_representation_class=type_tool.get_ifc_representation_class(element),
profile_set_usage=type_tool.get_profile_set_usage(element),
)
should_reload = True
if representation:
type_tool.run_geometry_switch_representation(
obj=obj,
representation=representation,
should_reload=True,
enable_dynamic_voids=type_tool.has_dynamic_voids(obj),
is_global=False,
)
else:
representation = type_tool.get_body_representation(element)
if not representation:
representation = type_tool.get_any_representation(element)
should_reload = False
if representation:
type_tool.run_geometry_switch_representation(
obj=obj,
representation=representation,
should_reload=should_reload,
enable_dynamic_voids=type_tool.has_dynamic_voids(obj),
is_global=False,
)
type_data = type_tool.get_object_data(ifc.get_object(type))
if type_data:
type_tool.change_object_data(obj, type_data, is_global=False)
type_tool.disable_editing(obj)
@@ -52,7 +52,6 @@ class Geometry(blenderbim.core.tool.Geometry):
modifier.solver = "EXACT"
modifier.use_self = True
@classmethod
def delete_data(cls, data):
bpy.data.meshes.remove(data)
+11 -7
View File
@@ -25,15 +25,15 @@ import blenderbim.bim.helper
class Type(blenderbim.core.tool.Type):
@classmethod
def disable_editing(cls, obj):
obj.BIMTypeProperties.is_editing_type = False
def change_object_data(cls, obj, data, is_global=False):
if is_global:
obj.data.user_remap(data)
else:
obj.data = data
@classmethod
def get_any_representation(cls, element):
if element.is_a("IfcProduct") and element.Representation and element.Representation.Representations:
return element.Representation.Representations[0]
elif element.is_a("IfcTypeProduct") and element.RepresentationMaps:
return element.RepresentationMaps[0].MappedRepresentation
def disable_editing(cls, obj):
obj.BIMTypeProperties.is_editing_type = False
@classmethod
def get_body_context(cls):
@@ -59,6 +59,10 @@ class Type(blenderbim.core.tool.Type):
elif material.is_a("IfcMaterialLayerSetUsage"):
return "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids"
@classmethod
def get_object_data(cls, obj):
return obj.data
@classmethod
def get_profile_set_usage(cls, element):
material = ifcopenshell.util.element.get_material(element)
@@ -18,11 +18,11 @@ Scenario: Add representation
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
And the object "IfcWall/Cube" is selected
Then the object "IfcWall/Cube" has a "Tessellation" representation of "Model/Body/MODEL_VIEW"
Then the object "IfcWall/Cube" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW"
When the variable "context" is "{ifc}.by_type('IfcGeometricRepresentationSubContext')[-1].id()"
And I set "scene.BIMProperties.contexts" to "{context}"
And I press "bim.add_representation"
Then the object "IfcWall/Cube" has a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW"
Then the object "IfcWall/Cube" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW"
Scenario: Add representation - add a new representation to a typed instance
Given an empty IFC project
@@ -33,14 +33,14 @@ Scenario: Add representation - add a new representation to a typed instance
And I press "bim.assign_class"
And I press "bim.add_type_instance"
And I press "bim.add_type_instance"
Then the object "IfcWall/Instance" has a "MappedRepresentation" representation of "Model/Body/MODEL_VIEW"
And the object "IfcWall/Instance.001" has a "MappedRepresentation" representation of "Model/Body/MODEL_VIEW"
Then the object "IfcWall/Instance" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW"
And the object "IfcWall/Instance.001" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW"
When the object "IfcWall/Instance" is selected
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"
Then the object "IfcWall/Instance" has a "MappedRepresentation" representation of "Plan/Annotation/PLAN_VIEW"
And the object "IfcWall/Instance.001" has a "MappedRepresentation" representation of "Plan/Annotation/PLAN_VIEW"
Then the object "IfcWall/Instance" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW"
And the object "IfcWall/Instance.001" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW"
Scenario: Switch representation
Given an empty IFC project
@@ -27,6 +27,27 @@ Scenario: Add type instance - add from an empty
When I press "bim.add_type_instance"
Then the object "IfcWall/Instance" exists
Scenario: Add type instance - add a mesh where existing instances have changed context
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType"
And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType"
And I press "bim.assign_class"
And I set "scene.BIMTypeProperties.ifc_class" to "IfcWallType"
And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()"
And I set "scene.BIMTypeProperties.relating_type" to "{cube}"
And I press "bim.add_type_instance"
And the object "IfcWall/Instance" data is a "Tessellation" representation of "Model/Body/MODEL_VIEW"
And the object "IfcWall/Instance" is selected
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"
And the object "IfcWall/Instance" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW"
When I press "bim.add_type_instance"
Then the object "IfcWall/Instance" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW"
And the object "IfcWall/Instance.001" data is a "Annotation2D" representation of "Plan/Annotation/PLAN_VIEW"
Scenario: Add grid
Given an empty IFC project
When I press "mesh.add_grid"
+14
View File
@@ -228,6 +228,7 @@ def the_object_name_has_a_body_of_value(name, value):
assert the_object_name_exists(name).data.body == value
@given(parsers.parse('the object "{name}" has a "{type}" representation of "{context}"'))
@then(parsers.parse('the object "{name}" has a "{type}" representation of "{context}"'))
def the_object_name_has_a_representation_type_of_context(name, type, context):
ifc = an_ifc_file_exists()
@@ -238,6 +239,19 @@ def the_object_name_has_a_representation_type_of_context(name, type, context):
assert rep.RepresentationType == type
@given(parsers.parse('the object "{name}" data is a "{type}" representation of "{context}"'))
@then(parsers.parse('the object "{name}" data is a "{type}" representation of "{context}"'))
def the_object_name_has_a_representation_type_of_context(name, type, context):
ifc = an_ifc_file_exists()
context, subcontext, target_view = context.split("/")
rep = ifc.by_id(the_object_name_exists(name).data.BIMMeshProperties.ifc_definition_id)
assert rep
assert rep.RepresentationType == type
assert rep.ContextOfItems.ContextType == context
assert rep.ContextOfItems.ContextIdentifier == subcontext
assert rep.ContextOfItems.TargetView == target_view
@then(parsers.parse('the material "{name}" exists'))
def the_material_name_exists(name) -> bpy.types.Material:
obj = bpy.data.materials.get(name)
+7 -22
View File
@@ -21,36 +21,21 @@ from test.core.bootstrap import ifc, type
class TestAssignType:
def test_assigning_and_switching_preferably_to_a_body_representation(self, ifc, type):
def test_assigning_and_switching_to_an_existing_type_data(self, ifc, type):
ifc.run("type.assign_type", related_object="element", relating_type="type").should_be_called()
type.has_material_usage("element").should_be_called().will_return(False)
type.get_body_representation("element").should_be_called().will_return("mapped_rep")
ifc.get_object("type").should_be_called().will_return("type_obj")
type.get_object_data("type_obj").should_be_called().will_return("type_obj_data")
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.has_dynamic_voids("obj").should_be_called().will_return(False)
type.run_geometry_switch_representation(
obj="obj", representation="mapped_rep", should_reload=False, enable_dynamic_voids=False, is_global=False
).should_be_called()
type.disable_editing("obj").should_be_called()
subject.assign_type(ifc, type, element="element", type="type")
def test_assigning_and_switching_to_any_representation_as_a_fallback(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_object="element", relating_type="type").should_be_called()
type.has_material_usage("element").should_be_called().will_return(False)
type.get_body_representation("element").should_be_called().will_return(None)
type.get_any_representation("element").should_be_called().will_return("mapped_rep")
ifc.get_object("element").should_be_called().will_return("obj")
type.has_dynamic_voids("obj").should_be_called().will_return(False)
type.run_geometry_switch_representation(
obj="obj", representation="mapped_rep", should_reload=False, enable_dynamic_voids=False, is_global=False
).should_be_called()
type.disable_editing("obj").should_be_called()
subject.assign_type(ifc, type, element="element", type="type")
def test_assigning_and_not_changing_representation_if_there_is_no_representation_to_change_to(self, ifc, type):
ifc.run("type.assign_type", related_object="element", relating_type="type").should_be_called()
type.has_material_usage("element").should_be_called().will_return(False)
type.get_body_representation("element").should_be_called().will_return(None)
type.get_any_representation("element").should_be_called().will_return(None)
ifc.get_object("type").should_be_called().will_return("type_obj")
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")
+27 -16
View File
@@ -29,6 +29,26 @@ class TestImplementsTool(NewFile):
assert isinstance(subject(), blenderbim.core.tool.Type)
class TestChangeObjectData(NewFile):
def test_change_single_object_data(self):
data1 = bpy.data.meshes.new("Mesh")
data2 = bpy.data.meshes.new("Mesh")
obj1 = bpy.data.objects.new("Object", data1)
obj2 = bpy.data.objects.new("Object", data1)
subject.change_object_data(obj1, data2, is_global=False)
assert obj1.data == data2
assert obj2.data == data1
def test_change_object_data_globally(self):
data1 = bpy.data.meshes.new("Mesh")
data2 = bpy.data.meshes.new("Mesh")
obj1 = bpy.data.objects.new("Object", data1)
obj2 = bpy.data.objects.new("Object", data1)
subject.change_object_data(obj1, data2, is_global=True)
assert obj1.data == data2
assert obj2.data == data2
class TestDisableEditing(NewFile):
def test_run(self):
obj = bpy.data.objects.new("Object", None)
@@ -37,22 +57,6 @@ class TestDisableEditing(NewFile):
assert obj.BIMTypeProperties.is_editing_type is False
class TestGetAnyRepresentation(NewFile):
def test_get_product_representation(self):
ifc = ifcopenshell.file()
representation = ifc.createIfcShapeRepresentation()
element = ifc.createIfcWall(Representation=ifc.createIfcProductRepresentation(Representations=[representation]))
assert subject.get_any_representation(element) == representation
def test_get_type_product_representation(self):
ifc = ifcopenshell.file()
representation = ifc.createIfcShapeRepresentation()
element = ifc.createIfcWallType(
RepresentationMaps=[ifc.createIfcRepresentationMap(MappedRepresentation=representation)]
)
assert subject.get_any_representation(element) == representation
class TestGetBodyContext(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
@@ -110,6 +114,13 @@ class TestGetIfcRepresentationClass(NewFile):
assert subject.get_ifc_representation_class(ifc.createIfcColumn()) is None
class TestGetObjectData(NewFile):
def test_run(self):
data = bpy.data.meshes.new("Mesh")
obj = bpy.data.objects.new("Object", data)
assert subject.get_object_data(obj) == obj.data
class TestGetProfileSetUsage(NewFile):
def test_getting_a_profile_set_usage(self):
ifc = ifcopenshell.file()