Fix representation where removing a representation left objects with an empty mesh. Now a proper empty is used.

This commit is contained in:
Dion Moult
2021-11-08 21:04:48 +11:00
parent 0471f17d9a
commit 7fbf317ad2
8 changed files with 175 additions and 32 deletions
@@ -108,37 +108,15 @@ class RemoveRepresentation(bpy.types.Operator, Operator):
bl_idname = "bim.remove_representation" bl_idname = "bim.remove_representation"
bl_label = "Remove Representation" bl_label = "Remove Representation"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
obj: bpy.props.StringProperty()
representation_id: bpy.props.IntProperty() representation_id: bpy.props.IntProperty()
def _execute(self, context): def _execute(self, context):
self.file = IfcStore.get_file() core.remove_representation(
representation = self.file.by_id(self.representation_id) tool.Ifc,
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object tool.Geometry,
is_mapped_representation = representation.RepresentationType == "MappedRepresentation" obj=context.active_object,
if is_mapped_representation: representation=tool.Ifc.get().by_id(self.representation_id),
mesh_name = "{}/{}".format(
representation.ContextOfItems.id(), representation.Items[0].MappingSource.MappedRepresentation.id()
)
else:
mesh_name = "{}/{}".format(representation.ContextOfItems.id(), representation.id())
mesh = bpy.data.meshes.get(mesh_name)
if mesh:
if obj.data == mesh:
# TODO we can do better than this
void_mesh = bpy.data.meshes.get("Void")
if not void_mesh:
void_mesh = bpy.data.meshes.new("Void")
obj.data = void_mesh
if not is_mapped_representation:
bpy.data.meshes.remove(mesh)
product = self.file.by_id(obj.BIMObjectProperties.ifc_definition_id)
ifcopenshell.api.run(
"geometry.unassign_representation", self.file, **{"product": product, "representation": representation}
) )
ifcopenshell.api.run("geometry.remove_representation", self.file, **{"representation": representation})
Data.load(self.file, product.id())
return {"FINISHED"}
class UpdateRepresentation(bpy.types.Operator): class UpdateRepresentation(bpy.types.Operator):
@@ -236,7 +214,7 @@ class UpdateRepresentation(bpy.types.Operator):
obj.data.BIMMeshProperties.ifc_definition_id = int(new_representation.id()) obj.data.BIMMeshProperties.ifc_definition_id = int(new_representation.id())
obj.data.name = f"{old_representation.ContextOfItems.id()}/{new_representation.id()}" obj.data.name = f"{old_representation.ContextOfItems.id()}/{new_representation.id()}"
bpy.ops.bim.remove_representation(representation_id=old_representation.id(), obj=obj.name) core.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_representation)
Data.load(self.file, obj.BIMObjectProperties.ifc_definition_id) Data.load(self.file, obj.BIMObjectProperties.ifc_definition_id)
if obj.data.BIMMeshProperties.ifc_parameters: if obj.data.BIMMeshProperties.ifc_parameters:
bpy.ops.bim.get_representation_ifc_parameters() bpy.ops.bim.get_representation_ifc_parameters()
@@ -255,7 +255,7 @@ def generate_box(usecase_path, ifc_file, settings):
old_box = ifcopenshell.util.representation.get_representation(product, "Model", "Box", "MODEL_VIEW") old_box = ifcopenshell.util.representation.get_representation(product, "Model", "Box", "MODEL_VIEW")
if settings["context"].ContextType == "Model" and getattr(settings["context"], "ContextIdentifier") == "Body": if settings["context"].ContextType == "Model" and getattr(settings["context"], "ContextIdentifier") == "Body":
if old_box: if old_box:
bpy.ops.bim.remove_representation(representation_id=old_box.id(), obj=obj.name) blenderbim.core.geometry.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_box)
new_settings = settings.copy() new_settings = settings.copy()
new_settings["context"] = box_context new_settings["context"] = box_context
@@ -26,6 +26,7 @@ import ifcopenshell.util.element
import mathutils.geometry import mathutils.geometry
import blenderbim.bim.handler import blenderbim.bim.handler
import blenderbim.core.type import blenderbim.core.type
import blenderbim.core.geometry
import blenderbim.tool as tool import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from math import pi, degrees from math import pi, degrees
@@ -112,7 +113,9 @@ def generate_footprint(usecase_path, ifc_file, settings):
old_footprint = ifcopenshell.util.representation.get_representation(product, "Plan", "FootPrint", "SKETCH_VIEW") old_footprint = ifcopenshell.util.representation.get_representation(product, "Plan", "FootPrint", "SKETCH_VIEW")
if settings["context"].ContextType == "Model" and getattr(settings["context"], "ContextIdentifier") == "Body": if settings["context"].ContextType == "Model" and getattr(settings["context"], "ContextIdentifier") == "Body":
if old_footprint: if old_footprint:
bpy.ops.bim.remove_representation(representation_id=old_footprint.id(), obj=obj.name) blenderbim.core.geometry.remove_representation(
tool.Ifc, tool.Geometry, obj=obj, representation=old_footprint
)
helper = Helper(ifc_file) helper = Helper(ifc_file)
indices = helper.auto_detect_arbitrary_profile_with_voids_extruded_area_solid(settings["geometry"]) indices = helper.auto_detect_arbitrary_profile_with_voids_extruded_area_solid(settings["geometry"])
@@ -28,6 +28,7 @@ import mathutils.geometry
import blenderbim.bim.handler import blenderbim.bim.handler
import blenderbim.core.type import blenderbim.core.type
import blenderbim.core.root import blenderbim.core.root
import blenderbim.core.geometry
import blenderbim.tool as tool import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.pset.data import Data as PsetData from ifcopenshell.api.pset.data import Data as PsetData
@@ -828,7 +829,7 @@ def generate_axis(usecase_path, ifc_file, settings):
old_axis = ifcopenshell.util.representation.get_representation(product, "Model", "Axis", "GRAPH_VIEW") old_axis = ifcopenshell.util.representation.get_representation(product, "Model", "Axis", "GRAPH_VIEW")
if settings["context"].ContextType == "Model" and getattr(settings["context"], "ContextIdentifier") == "Body": if settings["context"].ContextType == "Model" and getattr(settings["context"], "ContextIdentifier") == "Body":
if old_axis: if old_axis:
bpy.ops.bim.remove_representation(representation_id=old_axis.id(), obj=obj.name) blenderbim.core.geometry.remove_representation(tool.Ifc, tool.Geometry, obj=obj, representation=old_axis)
new_settings = settings.copy() new_settings = settings.copy()
new_settings["context"] = axis_context new_settings["context"] = axis_context
@@ -76,6 +76,14 @@ class Geometry(blenderbim.core.tool.Geometry):
) )
) )
@classmethod
def get_element_type(cls, element):
return ifcopenshell.util.element.get_type(element)
@classmethod
def get_elements_of_type(cls, type):
return ifcopenshell.util.element.get_types(type)
@classmethod @classmethod
def get_ifc_representation_class(cls, element, representation): def get_ifc_representation_class(cls, element, representation):
material = ifcopenshell.util.element.get_material(element) material = ifcopenshell.util.element.get_material(element)
@@ -124,6 +132,10 @@ class Geometry(blenderbim.core.tool.Geometry):
def get_total_representation_items(cls, obj): def get_total_representation_items(cls, obj):
return max(1, len(obj.material_slots)) return max(1, len(obj.material_slots))
@classmethod
def has_data_users(cls, data):
return data.users != 0
@classmethod @classmethod
def import_representation(cls, obj, representation, enable_dynamic_voids=False): def import_representation(cls, obj, representation, enable_dynamic_voids=False):
logger = logging.getLogger("ImportIFC") logger = logging.getLogger("ImportIFC")
@@ -152,6 +164,14 @@ class Geometry(blenderbim.core.tool.Geometry):
def is_body_representation(cls, representation): def is_body_representation(cls, representation):
return representation.ContextOfItems.ContextIdentifier == "Body" return representation.ContextOfItems.ContextIdentifier == "Body"
@classmethod
def is_mapped_representation(cls, representation):
return representation.RepresentationType == "MappedRepresentation"
@classmethod
def is_type_product(cls, element):
return element.is_a("IfcTypeProduct")
@classmethod @classmethod
def link(cls, element, obj): def link(cls, element, obj):
obj.BIMMeshProperties.ifc_definition_id = element.id() obj.BIMMeshProperties.ifc_definition_id = element.id()
@@ -160,6 +180,20 @@ class Geometry(blenderbim.core.tool.Geometry):
def rename_object(cls, obj, name): def rename_object(cls, obj, name):
obj.name = name obj.name = name
@classmethod
def replace_object_with_empty(cls, obj):
element = tool.Ifc.get_entity(obj)
name = obj.name
tool.Ifc.unlink(obj)
obj.name = ifcopenshell.guid.new()
new_obj = bpy.data.objects.new(name, None)
if element:
tool.Ifc.link(element, new_obj)
for collection in obj.users_collection:
collection.objects.link(new_obj)
new_obj.matrix_world = obj.matrix_world
bpy.data.objects.remove(obj)
@classmethod @classmethod
def resolve_mapped_representation(cls, representation): def resolve_mapped_representation(cls, representation):
if representation.RepresentationType == "MappedRepresentation": if representation.RepresentationType == "MappedRepresentation":
@@ -84,6 +84,66 @@ Scenario: Switch representation - existing Blender modifiers must be purged
And I press "bim.switch_representation(obj='IfcWall/Cube', ifc_definition_id={representation})" And I press "bim.switch_representation(obj='IfcWall/Cube', ifc_definition_id={representation})"
Then the object "IfcWall/Cube" has no modifiers Then the object "IfcWall/Cube" has no modifiers
Scenario: Remove representation - remove an active representation
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I add an array modifier
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
When the variable "representation" is "{ifc}.by_type('IfcShapeRepresentation')[0].id()"
And I press "bim.remove_representation(representation_id={representation})"
Then the object "IfcWall/Cube" has no data
Scenario: Remove representation - remove an unloaded representation
Given an empty IFC project
And I add a cube
And the object "Cube" is selected
And I add an array modifier
And I set "scene.BIMRootProperties.ifc_class" to "IfcWall"
And I press "bim.assign_class"
When the variable "representation" is "{ifc}.by_type('IfcShapeRepresentation')[1].id()"
And I press "bim.remove_representation(representation_id={representation})"
Then the object "IfcWall/Cube" has data which is an IFC representation
Scenario: Remove representation - remove an instanced representation from an active type object
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.BIMModelProperties.ifc_class" to "IfcWallType"
And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()"
And I set "scene.BIMModelProperties.relating_type" to "{cube}"
And I press "bim.add_type_instance"
And I press "bim.add_type_instance"
And the object "IfcWallType/Cube" is selected
When the variable "representation" is "{ifc}.by_type('IfcWallType')[0].RepresentationMaps[1].MappedRepresentation.id()"
And I press "bim.remove_representation(representation_id={representation})"
Then the object "IfcWallType/Cube" has no data
Then the object "IfcWall/Instance" has no data
Then the object "IfcWall/Instance.001" has no data
Scenario: Remove representation - remove an instanced representation from an active instance object
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.BIMModelProperties.ifc_class" to "IfcWallType"
And the variable "cube" is "{ifc}.by_type('IfcWallType')[0].id()"
And I set "scene.BIMModelProperties.relating_type" to "{cube}"
And I press "bim.add_type_instance"
And I press "bim.add_type_instance"
And the object "IfcWall/Instance" is selected
When the variable "representation" is "{ifc}.by_type('IfcWall')[0].Representation.Representations[1].id()"
And I press "bim.remove_representation(representation_id={representation})"
Then the object "IfcWallType/Cube" has no data
Then the object "IfcWall/Instance" has no data
Then the object "IfcWall/Instance.001" has no data
Scenario: Update representation - updating a tessellation Scenario: Update representation - updating a tessellation
Given an empty IFC project Given an empty IFC project
And I add a cube And I add a cube
+5
View File
@@ -356,6 +356,11 @@ def the_object_name_is_not_an_ifc_element(name):
assert id == 0, f"The ID is {id}" assert id == 0, f"The ID is {id}"
@then(parsers.parse('the object "{name}" has no data'))
def the_object_name_has_no_data(name):
assert the_object_name_exists(name).data is None
@then(parsers.parse('the object "{name}" has data which is an IFC representation')) @then(parsers.parse('the object "{name}" has data which is an IFC representation'))
def the_object_name_is_not_an_ifc_element(name): def the_object_name_is_not_an_ifc_element(name):
id = the_object_name_exists(name).data.BIMMeshProperties.ifc_definition_id id = the_object_name_exists(name).data.BIMMeshProperties.ifc_definition_id
+63 -1
View File
@@ -184,6 +184,26 @@ class TestGetCartesianPointCoordinateOffset(NewFile):
assert subject.get_cartesian_point_coordinate_offset(obj) is None assert subject.get_cartesian_point_coordinate_offset(obj) is None
class TestGetElementType(NewFile):
def test_run(self):
bpy.ops.bim.create_project()
ifc = tool.Ifc.get()
element = ifc.createIfcWall()
type = ifc.createIfcWallType()
ifcopenshell.api.run("type.assign_type", ifc, related_object=element, relating_type=type)
assert subject.get_element_type(element) == type
class TestGetElementsOfType(NewFile):
def test_run(self):
bpy.ops.bim.create_project()
ifc = tool.Ifc.get()
element = ifc.createIfcWall()
type = ifc.createIfcWallType()
ifcopenshell.api.run("type.assign_type", ifc, related_object=element, relating_type=type)
assert subject.get_elements_of_type(type) == (element,)
class TestGetTotalRepresentationItems(NewFile): class TestGetTotalRepresentationItems(NewFile):
def test_run(self): def test_run(self):
material1 = bpy.data.materials.new("Material") material1 = bpy.data.materials.new("Material")
@@ -194,6 +214,14 @@ class TestGetTotalRepresentationItems(NewFile):
assert subject.get_total_representation_items(obj) == 2 assert subject.get_total_representation_items(obj) == 2
class TestHasDataUsers(NewFile):
def test_run(self):
data = bpy.data.meshes.new("Mesh")
assert subject.has_data_users(data) is False
bpy.data.objects.new("Object", data)
assert subject.has_data_users(data) is True
class TestImportRepresentation(NewFile): class TestImportRepresentation(NewFile):
def test_importing_a_normal_shape(self): def test_importing_a_normal_shape(self):
ifc = ifcopenshell.open("test/files/basic.ifc") ifc = ifcopenshell.open("test/files/basic.ifc")
@@ -232,6 +260,22 @@ class TestIsBodyRepresentation(NewFile):
assert subject.is_body_representation(representation) is False assert subject.is_body_representation(representation) is False
class TestIsMappedRepresentation(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
representation = ifc.createIfcShapeRepresentation()
assert subject.is_mapped_representation(representation) is False
representation.RepresentationType = "MappedRepresentation"
assert subject.is_mapped_representation(representation) is True
class TestIsTypeProduct(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
assert subject.is_type_product(ifc.createIfcWall()) is False
assert subject.is_type_product(ifc.createIfcWallType()) is True
class TestLink(NewFile): class TestLink(NewFile):
def test_run(self): def test_run(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
@@ -241,13 +285,31 @@ class TestLink(NewFile):
assert obj.BIMMeshProperties.ifc_definition_id == element.id() assert obj.BIMMeshProperties.ifc_definition_id == element.id()
class TestRenameObjectData(NewFile): class TestRenameObject(NewFile):
def test_run(self): def test_run(self):
obj = bpy.data.meshes.new("Mesh") obj = bpy.data.meshes.new("Mesh")
subject.rename_object(obj, "name") subject.rename_object(obj, "name")
assert obj.name == "name" assert obj.name == "name"
class TestReplaceObjectWithEmpty(NewFile):
def test_run(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh"))
obj.matrix_world[0][3] = 1
bpy.context.scene.collection.objects.link(obj)
element = ifc.createIfcWall()
tool.Ifc.link(element, obj)
subject.replace_object_with_empty(obj)
obj = bpy.data.objects.get("Object")
assert obj.users_collection[0] == bpy.context.scene.collection
assert tool.Ifc.get_entity(obj) == element
assert tool.Ifc.get_object(element) == obj
assert obj.data is None
assert obj.matrix_world[0][3] == 1
class TestResolveMappedRepresentation(NewFile): class TestResolveMappedRepresentation(NewFile):
def test_run(self): def test_run(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()