mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-16 18:44:47 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user