mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
IFC element names no longer treat the Blender name suffix as significant
This commit is contained in:
@@ -113,7 +113,7 @@ class ReassignClass(bpy.types.Operator):
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class AssignClass(bpy.types.Operator):
|
||||
class AssignClass(bpy.types.Operator, Operator):
|
||||
bl_idname = "bim.assign_class"
|
||||
bl_label = "Assign IFC Class"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
@@ -126,135 +126,29 @@ class AssignClass(bpy.types.Operator):
|
||||
should_add_representation: bpy.props.BoolProperty(default=True)
|
||||
ifc_representation_class: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
return IfcStore.execute_ifc_operator(self, context)
|
||||
|
||||
def _execute(self, context):
|
||||
objects = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects
|
||||
self.file = IfcStore.get_file()
|
||||
if not self.ifc_class:
|
||||
self.ifc_class = context.scene.BIMRootProperties.ifc_class
|
||||
self.declaration = IfcStore.get_schema().declaration_by_name(self.ifc_class)
|
||||
if self.predefined_type == "USERDEFINED":
|
||||
self.predefined_type = self.userdefined_type
|
||||
ifc_class = self.ifc_class or context.scene.BIMRootProperties.ifc_class
|
||||
predefined_type = self.userdefined_type if self.predefined_type == "USERDEFINED" else self.predefined_type
|
||||
ifc_context = self.context_id
|
||||
if not ifc_context and get_contexts(self, context):
|
||||
ifc_context = int(context.scene.BIMRootProperties.contexts or "0") or None
|
||||
if ifc_context:
|
||||
ifc_context = tool.Ifc.get().by_id(ifc_context)
|
||||
active_object = context.active_object
|
||||
for obj in objects:
|
||||
self.assign_class(context, obj)
|
||||
return {"FINISHED"}
|
||||
|
||||
def assign_class(self, context, obj):
|
||||
if obj.BIMObjectProperties.ifc_definition_id:
|
||||
return
|
||||
product = ifcopenshell.api.run(
|
||||
"root.create_entity",
|
||||
self.file,
|
||||
**{
|
||||
"ifc_class": self.ifc_class,
|
||||
"predefined_type": self.predefined_type or None,
|
||||
"name": obj.name,
|
||||
},
|
||||
)
|
||||
obj.name = "{}/{}".format(product.is_a(), obj.name)
|
||||
IfcStore.link_element(product, obj)
|
||||
|
||||
if self.should_add_representation:
|
||||
ifc_context = self.context_id
|
||||
if not ifc_context and get_contexts(self, context):
|
||||
ifc_context = int(context.scene.BIMRootProperties.contexts or "0") or None
|
||||
if ifc_context:
|
||||
ifc_context = tool.Ifc.get().by_id(ifc_context)
|
||||
blenderbim.core.geometry.add_representation(
|
||||
core.assign_class(
|
||||
tool.Ifc,
|
||||
tool.Geometry,
|
||||
tool.Style,
|
||||
tool.Surveyor,
|
||||
tool.Collector,
|
||||
tool.Root,
|
||||
obj=obj,
|
||||
ifc_class=ifc_class,
|
||||
predefined_type=predefined_type,
|
||||
should_add_representation=self.should_add_representation,
|
||||
context=ifc_context,
|
||||
ifc_representation_class=self.ifc_representation_class,
|
||||
profile_set_usage=None,
|
||||
)
|
||||
|
||||
if product.is_a("IfcElementType"):
|
||||
tool.Collector.assign(obj)
|
||||
elif product.is_a("IfcOpeningElement"):
|
||||
obj.display_type = "WIRE"
|
||||
tool.Collector.assign(obj)
|
||||
elif (
|
||||
product.is_a("IfcSpatialElement")
|
||||
or product.is_a("IfcSpatialStructureElement")
|
||||
or product.is_a("IfcProject")
|
||||
or product.is_a("IfcContext")
|
||||
):
|
||||
self.place_in_spatial_collection(obj, context)
|
||||
elif product.is_a("IfcStructuralItem"):
|
||||
if product.is_a("IfcStructuralMember"):
|
||||
self.place_in_structural_items_collection(obj, context, structural_collection="Members")
|
||||
elif product.is_a("IfcStructuralConnection"):
|
||||
self.place_in_structural_items_collection(obj, context, structural_collection="Connections")
|
||||
else:
|
||||
self.assign_potential_spatial_container(obj)
|
||||
context.view_layer.objects.active = obj
|
||||
|
||||
def place_in_spatial_collection(self, obj, context):
|
||||
for collection in obj.users_collection:
|
||||
if collection.name == obj.name:
|
||||
return
|
||||
parent_collection = None
|
||||
for collection in obj.users_collection:
|
||||
collection.objects.unlink(obj)
|
||||
if "Ifc" in collection.name:
|
||||
parent_collection = collection
|
||||
collection = bpy.data.collections.new(obj.name)
|
||||
collection.objects.link(obj)
|
||||
if parent_collection:
|
||||
parent_collection.children.link(collection)
|
||||
blenderbim.core.aggregate.assign_object(
|
||||
tool.Ifc,
|
||||
tool.Aggregate,
|
||||
tool.Collector,
|
||||
relating_obj=bpy.data.objects.get(parent_collection.name),
|
||||
related_obj=obj,
|
||||
)
|
||||
else:
|
||||
context.scene.collection.children.link(collection)
|
||||
|
||||
def place_in_structural_items_collection(self, obj, context, structural_collection):
|
||||
for project in [c for c in context.view_layer.layer_collection.children if "IfcProject" in c.name]:
|
||||
if not [c for c in project.children if "StructuralItems" in c.name]:
|
||||
members = bpy.data.collections.new("Members")
|
||||
connections = bpy.data.collections.new("Connections")
|
||||
items = bpy.data.collections.new("StructuralItems")
|
||||
items.children.link(members)
|
||||
items.children.link(connections)
|
||||
project.collection.children.link(items)
|
||||
|
||||
for coll in [c for c in project.children if "StructuralItems" in c.name]:
|
||||
for collection in [c for c in coll.children if structural_collection in c.name]:
|
||||
for user_collection in obj.users_collection:
|
||||
user_collection.objects.unlink(obj)
|
||||
collection.collection.objects.link(obj)
|
||||
break
|
||||
break
|
||||
break
|
||||
|
||||
def assign_potential_spatial_container(self, obj):
|
||||
for collection in obj.users_collection:
|
||||
if "Ifc" not in collection.name or collection.name == obj.name:
|
||||
continue
|
||||
spatial_obj = bpy.data.objects.get(collection.name)
|
||||
if spatial_obj and spatial_obj.BIMObjectProperties.ifc_definition_id:
|
||||
element = self.file.by_id(spatial_obj.BIMObjectProperties.ifc_definition_id)
|
||||
if self.file.schema != "IFC2X3" and not element.is_a("IfcSpatialElement"):
|
||||
continue
|
||||
elif self.file.schema == "IFC2X3" and not element.is_a("IfcSpatialStructureElement"):
|
||||
continue
|
||||
blenderbim.core.spatial.assign_container(
|
||||
tool.Ifc,
|
||||
tool.Collector,
|
||||
tool.Spatial,
|
||||
structure_obj=spatial_obj,
|
||||
element_obj=obj,
|
||||
)
|
||||
break
|
||||
context.view_layer.objects.active = active_object
|
||||
|
||||
|
||||
class UnlinkObject(bpy.types.Operator):
|
||||
|
||||
@@ -150,6 +150,7 @@ class Drawing:
|
||||
def export_text_literal_attributes(cls, obj): pass
|
||||
def generate_drawing_matrix(cls, target_view, location_hint): pass
|
||||
def generate_sheet_identification(cls): pass
|
||||
def get_body_context(cls): pass
|
||||
def get_sheet_filename(cls, document): pass
|
||||
def get_text_literal(cls, obj): pass
|
||||
def get_text_product(cls, element): pass
|
||||
@@ -158,7 +159,7 @@ class Drawing:
|
||||
def import_text_attributes(cls, obj): pass
|
||||
def import_text_product(cls, obj): pass
|
||||
def open_svg(cls, filepath): pass
|
||||
def run_assign_class_operator(cls, obj=None, ifc_class=None, predefined_type=None): pass
|
||||
def run_root_assign_class(cls, obj=None, ifc_class=None, predefined_type=None, should_add_representation=True, context=None, ifc_representation_class=None): pass
|
||||
def update_text_value(cls, obj): pass
|
||||
|
||||
|
||||
@@ -171,6 +172,7 @@ class Geometry:
|
||||
def create_dynamic_voids(cls, obj): pass
|
||||
def delete_data(cls, data): pass
|
||||
def does_object_have_mesh_with_faces(cls, obj): pass
|
||||
def does_representation_id_exist(cls, representation_id): pass
|
||||
def duplicate_object_data(cls, obj): pass
|
||||
def get_cartesian_point_coordinate_offset(cls, obj): pass
|
||||
def get_element_type(cls, element): pass
|
||||
@@ -180,6 +182,7 @@ class Geometry:
|
||||
def get_object_materials_without_styles(cls, obj): pass
|
||||
def get_profile_set_usage(cls, element): pass
|
||||
def get_representation_data(cls, representation): pass
|
||||
def get_representation_id(cls, representation): pass
|
||||
def get_representation_name(cls, representation): pass
|
||||
def get_styles(cls, obj): pass
|
||||
def get_total_representation_items(cls, obj): pass
|
||||
@@ -305,11 +308,14 @@ class Root:
|
||||
def add_dynamic_opening_voids(cls, element, obj): pass
|
||||
def does_type_have_representations(cls, element): pass
|
||||
def get_element_type(cls, element): pass
|
||||
def get_object_name(cls, obj): pass
|
||||
def get_object_representation(cls, obj): pass
|
||||
def get_representation_context(cls, representation): pass
|
||||
def is_opening_element(cls, element): pass
|
||||
def link_object_data(cls, source_obj, destination_obj): pass
|
||||
def run_geometry_add_representation(cls, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None): pass
|
||||
def set_element_specific_display_settings(cls, obj, element): pass
|
||||
def set_object_name(cls, obj, element): pass
|
||||
|
||||
|
||||
@interface
|
||||
|
||||
@@ -48,6 +48,12 @@ class Root(blenderbim.core.tool.Root):
|
||||
def get_element_type(cls, element):
|
||||
return ifcopenshell.util.element.get_type(element)
|
||||
|
||||
@classmethod
|
||||
def get_object_name(cls, obj):
|
||||
if "." in obj.name and obj.name.split(".")[-1].isnumeric():
|
||||
return ".".join(obj.name.split(".")[:-1])
|
||||
return obj.name
|
||||
|
||||
@classmethod
|
||||
def get_object_representation(cls, obj):
|
||||
if obj.data and obj.data.BIMMeshProperties.ifc_definition_id:
|
||||
@@ -80,6 +86,11 @@ class Root(blenderbim.core.tool.Root):
|
||||
profile_set_usage=profile_set_usage,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def set_element_specific_display_settings(cls, obj, element):
|
||||
if element.is_a("IfcOpeningElement"):
|
||||
obj.display_type = "WIRE"
|
||||
|
||||
@classmethod
|
||||
def set_object_name(cls, obj, element):
|
||||
name = obj.name
|
||||
|
||||
@@ -86,3 +86,68 @@ class TestCopyClass:
|
||||
root.is_opening_element("element").should_be_called().will_return(True)
|
||||
root.add_dynamic_opening_voids("element", "obj").should_be_called()
|
||||
subject.copy_class(ifc, collector, geometry, root, obj="obj")
|
||||
|
||||
|
||||
class TestAssignClass:
|
||||
def test_do_nothing_if_already_assigned(self, ifc, collector, root):
|
||||
ifc.get_entity("obj").should_be_called().will_return("entity")
|
||||
subject.assign_class(
|
||||
ifc,
|
||||
collector,
|
||||
root,
|
||||
obj="obj",
|
||||
ifc_class="ifc_class",
|
||||
predefined_type="predefined_type",
|
||||
should_add_representation=True,
|
||||
context="context",
|
||||
ifc_representation_class="ifc_representation_class",
|
||||
)
|
||||
|
||||
def test_assign_a_class_with_geometry_and_autodetected_spatial_container(self, ifc, collector, root):
|
||||
ifc.get_entity("obj").should_be_called().will_return(None)
|
||||
root.get_object_name("obj").should_be_called().will_return("name")
|
||||
ifc.run(
|
||||
"root.create_entity", ifc_class="ifc_class", predefined_type="predefined_type", name="name"
|
||||
).should_be_called().will_return("element")
|
||||
root.set_object_name("obj", "element").should_be_called()
|
||||
ifc.link("element", "obj").should_be_called()
|
||||
root.run_geometry_add_representation(
|
||||
obj="obj", context="context", ifc_representation_class="ifc_representation_class", profile_set_usage=None
|
||||
).should_be_called()
|
||||
root.set_element_specific_display_settings("obj", "element").should_be_called()
|
||||
collector.sync("obj").should_be_called()
|
||||
collector.assign("obj").should_be_called()
|
||||
subject.assign_class(
|
||||
ifc,
|
||||
collector,
|
||||
root,
|
||||
obj="obj",
|
||||
ifc_class="ifc_class",
|
||||
predefined_type="predefined_type",
|
||||
should_add_representation=True,
|
||||
context="context",
|
||||
ifc_representation_class="ifc_representation_class",
|
||||
)
|
||||
|
||||
def test_not_adding_a_representation_if_requested(self, ifc, collector, root):
|
||||
ifc.get_entity("obj").should_be_called().will_return(None)
|
||||
root.get_object_name("obj").should_be_called().will_return("name")
|
||||
ifc.run(
|
||||
"root.create_entity", ifc_class="ifc_class", predefined_type="predefined_type", name="name"
|
||||
).should_be_called().will_return("element")
|
||||
root.set_object_name("obj", "element").should_be_called()
|
||||
ifc.link("element", "obj").should_be_called()
|
||||
root.set_element_specific_display_settings("obj", "element").should_be_called()
|
||||
collector.sync("obj").should_be_called()
|
||||
collector.assign("obj").should_be_called()
|
||||
subject.assign_class(
|
||||
ifc,
|
||||
collector,
|
||||
root,
|
||||
obj="obj",
|
||||
ifc_class="ifc_class",
|
||||
predefined_type="predefined_type",
|
||||
should_add_representation=False,
|
||||
context="context",
|
||||
ifc_representation_class="ifc_representation_class",
|
||||
)
|
||||
|
||||
@@ -74,6 +74,18 @@ class TestGetElementType(NewFile):
|
||||
assert subject.get_element_type(element) == type
|
||||
|
||||
|
||||
class TestGetObjectName(NewFile):
|
||||
def test_run(self):
|
||||
obj = bpy.data.objects.new("Object", None)
|
||||
assert subject.get_object_name(obj) == "Object"
|
||||
|
||||
def test_blender_number_suffixes_are_ignored(self):
|
||||
obj = bpy.data.objects.new("Object.001", None)
|
||||
assert subject.get_object_name(obj) == "Object"
|
||||
obj = bpy.data.objects.new("Object.foo.123", None)
|
||||
assert subject.get_object_name(obj) == "Object.foo"
|
||||
|
||||
|
||||
class TestGetObjectRepresentation(NewFile):
|
||||
def test_run(self):
|
||||
ifc = ifcopenshell.file()
|
||||
@@ -113,3 +125,28 @@ class TestLinkObjectData(NewFile):
|
||||
class TestRunGeometryAddRepresntation(NewFile):
|
||||
def test_nothing(self):
|
||||
pass
|
||||
|
||||
|
||||
class TestSetElementSpecificDisplaySettings(NewFile):
|
||||
def test_opening_elements_display_as_wire(self):
|
||||
ifc = ifcopenshell.file()
|
||||
obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh"))
|
||||
element = ifc.createIfcOpeningElement()
|
||||
subject.set_element_specific_display_settings(obj, element)
|
||||
assert obj.display_type == "WIRE"
|
||||
|
||||
|
||||
class TestSetObjectName(NewFile):
|
||||
def test_run(self):
|
||||
ifc = ifcopenshell.file()
|
||||
obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh"))
|
||||
element = ifc.createIfcWall()
|
||||
subject.set_object_name(obj, element)
|
||||
assert obj.name == "IfcWall/Object"
|
||||
|
||||
def test_existing_ifc_prefixes_are_not_repeated(self):
|
||||
ifc = ifcopenshell.file()
|
||||
obj = bpy.data.objects.new("IfcSlab/Object", bpy.data.meshes.new("Mesh"))
|
||||
element = ifc.createIfcWall()
|
||||
subject.set_object_name(obj, element)
|
||||
assert obj.name == "IfcWall/Object"
|
||||
|
||||
Reference in New Issue
Block a user