Fix #2285. You can now get all the elements in a presentation layer.

This commit is contained in:
Dion Moult
2023-01-12 12:28:09 +11:00
parent d1a40d129f
commit ba7741fef5
5 changed files with 183 additions and 31 deletions
@@ -20,14 +20,15 @@ import bpy
from . import ui, prop, operator from . import ui, prop, operator
classes = ( classes = (
operator.LoadLayers,
operator.DisableLayerEditingUI,
operator.EnableEditingLayer,
operator.DisableEditingLayer,
operator.AddPresentationLayer, operator.AddPresentationLayer,
operator.EditPresentationLayer,
operator.RemovePresentationLayer,
operator.AssignPresentationLayer, operator.AssignPresentationLayer,
operator.DisableEditingLayer,
operator.DisableLayerEditingUI,
operator.EditPresentationLayer,
operator.EnableEditingLayer,
operator.LoadLayers,
operator.RemovePresentationLayer,
operator.SelectLayerProducts,
operator.UnassignPresentationLayer, operator.UnassignPresentationLayer,
prop.Layer, prop.Layer,
prop.BIMLayerProperties, prop.BIMLayerProperties,
@@ -18,9 +18,11 @@
import bpy import bpy
import json import json
import ifcopenshell.util.attribute
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.util.element
import ifcopenshell.util.attribute
import blenderbim.bim.helper import blenderbim.bim.helper
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.layer.data import Data from ifcopenshell.api.layer.data import Data
@@ -182,3 +184,19 @@ class UnassignPresentationLayer(bpy.types.Operator):
) )
Data.load(IfcStore.get_file()) Data.load(IfcStore.get_file())
return {"FINISHED"} return {"FINISHED"}
class SelectLayerProducts(bpy.types.Operator):
bl_idname = "bim.select_layer_products"
bl_label = "Select Layer Products"
bl_options = {"REGISTER", "UNDO"}
layer: bpy.props.IntProperty()
def execute(self, context):
elements = ifcopenshell.util.element.get_elements_by_layer(tool.Ifc.get(), tool.Ifc.get().by_id(self.layer))
for obj in context.visible_objects:
obj.select_set(False)
element = tool.Ifc.get_entity(obj)
if element and element in elements:
obj.select_set(True)
return {"FINISHED"}
@@ -91,11 +91,17 @@ class BIM_UL_layers(UIList):
row.operator("bim.disable_editing_layer", text="", icon="FREEZE", emboss=False) row.operator("bim.disable_editing_layer", text="", icon="FREEZE", emboss=False)
if context.scene.BIMLayerProperties.active_layer_id == item.ifc_definition_id: if context.scene.BIMLayerProperties.active_layer_id == item.ifc_definition_id:
op = row.operator("bim.select_layer_products", text="", icon="RESTRICT_SELECT_OFF")
op.layer = item.ifc_definition_id
row.operator("bim.edit_presentation_layer", text="", icon="CHECKMARK") row.operator("bim.edit_presentation_layer", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_layer", text="", icon="CANCEL") row.operator("bim.disable_editing_layer", text="", icon="CANCEL")
elif context.scene.BIMLayerProperties.active_layer_id: elif context.scene.BIMLayerProperties.active_layer_id:
op = row.operator("bim.select_layer_products", text="", icon="RESTRICT_SELECT_OFF")
op.layer = item.ifc_definition_id
row.operator("bim.remove_presentation_layer", text="", icon="X").layer = item.ifc_definition_id row.operator("bim.remove_presentation_layer", text="", icon="X").layer = item.ifc_definition_id
else: else:
op = row.operator("bim.select_layer_products", text="", icon="RESTRICT_SELECT_OFF")
op.layer = item.ifc_definition_id
op = row.operator("bim.enable_editing_layer", text="", icon="GREASEPENCIL") op = row.operator("bim.enable_editing_layer", text="", icon="GREASEPENCIL")
op.layer = item.ifc_definition_id op.layer = item.ifc_definition_id
row.operator("bim.remove_presentation_layer", text="", icon="X").layer = item.ifc_definition_id row.operator("bim.remove_presentation_layer", text="", icon="X").layer = item.ifc_definition_id
@@ -31,6 +31,7 @@ def get_psets(element, psets_only=False, qtos_only=False, should_inherit=True):
Example: Example:
.. code:: python .. code:: python
element = ifcopenshell.by_type("IfcBuildingElement")[0] element = ifcopenshell.by_type("IfcBuildingElement")[0]
psets = ifcopenshell.util.element.get_psets(element, psets_only=True) psets = ifcopenshell.util.element.get_psets(element, psets_only=True)
qsets = ifcopenshell.util.element.get_psets(element, qtos_only=True) qsets = ifcopenshell.util.element.get_psets(element, qtos_only=True)
@@ -120,17 +121,24 @@ def get_properties(properties):
def get_predefined_type(element): def get_predefined_type(element):
""" """Retrieves the PrefefinedType attribute of an element.
Retrieves the PrefefinedType attribute of an element.
If the predefined type is user defined, the custom type (such as object
type, element type, or process type depending on the class) is returned
instead. Predefined types from the associated type element are also
considered first.
:param element: The IFC Element entity :param element: The IFC Element entity
:type element: ifcopenshell.entity_instance.entity_instance
:return: The predefined type of the element :return: The predefined type of the element
:rtype: str
Example: Example:
.. code:: python .. code:: python
element = ifcopenshell.by_type("IfcWall")[0]
predefined_type = ifcopenshell.util.element.get_predefined_type(element) element = ifcopenshell.by_type("IfcWall")[0]
predefined_type = ifcopenshell.util.element.get_predefined_type(element)
""" """
element_type = get_type(element) element_type = get_type(element)
if element_type: if element_type:
@@ -146,17 +154,19 @@ def get_predefined_type(element):
def get_type(element): def get_type(element):
""" """Retrieves the construction type element of an element occurrence
Retrieves the Element Type entity related to an element entity.
:param element: The IFC Element entity :param element: The element occurrence
:return: The Element Type entity defining the element :type: ifcopenshell.entity_instance.entity_instance
:return: The related type element
:rtype ifcopenshell.entity_instance.entity_instance
Example: Example:
.. code:: python .. code:: python
element = ifcopenshell.by_type("IfcWall")[0]
element_type = ifcopenshell.util.element.get_type(element) element = ifcopenshell.by_type("IfcWall")[0]
element_type = ifcopenshell.util.element.get_type(element)
""" """
if element.is_a("IfcTypeObject"): if element.is_a("IfcTypeObject"):
return element return element
@@ -169,6 +179,20 @@ def get_type(element):
def get_types(type): def get_types(type):
"""Get all the occurrences of a type element
:param type: The type element
:type type: ifcopenshell.entity_instance.entity_instance
:return: A list of occurrences of that type
:rtype: list[ifcopenshell.entity_instance.entity_instance]
Example:
.. code:: python
element_type = ifcopenshell.by_type("IfcWallType")[0]
walls = ifcopenshell.util.element.get_types(element_type)
"""
for rel in getattr(type, "Types", []): for rel in getattr(type, "Types", []):
return rel.RelatedObjects return rel.RelatedObjects
for rel in getattr(type, "ObjectTypeOf", []): for rel in getattr(type, "ObjectTypeOf", []):
@@ -177,6 +201,29 @@ def get_types(type):
def get_material(element, should_skip_usage=False, should_inherit=True): def get_material(element, should_skip_usage=False, should_inherit=True):
"""Gets the material of the element
The material may be a single material, material set (layered, profiled, or
constituent), or a material set usage.
:param should_skip_usage: If set to True, if the material is a material set
usage, the material set itself will be returned. Useful if you don't
care about occurrence usage parameters. If False, the usage will be
returned.
:type should_skip_usage: bool
:param should_inherit: If True, any inherited materials from associated
types will be considered.
:type should_inherit: bool
:return: The associated material of the element.
:rtype: ifcopenshell.entity_instance.entity_instance
Example:
.. code:: python
element = ifcopenshell.by_type("IfcWall")[0]
material = ifcopenshell.util.element.material(element)
"""
if hasattr(element, "HasAssociations") and element.HasAssociations: if hasattr(element, "HasAssociations") and element.HasAssociations:
for relationship in element.HasAssociations: for relationship in element.HasAssociations:
if relationship.is_a("IfcRelAssociatesMaterial"): if relationship.is_a("IfcRelAssociatesMaterial"):
@@ -193,15 +240,17 @@ def get_material(element, should_skip_usage=False, should_inherit=True):
def get_elements_by_material(ifc_file, material): def get_elements_by_material(ifc_file, material):
""" """Retrieves the elements related to a material.
Retrieves the elements related to a material.
This includes elements using the material as part of a material set or set This includes elements using the material as part of a material set or set
usage. usage.
:param ifc_file: The IFC file :param ifc_file: The IFC file
:type ifc_file: ifcopenshell.file.file
:param material: The IFC Material entity :param material: The IFC Material entity
:type material: ifcopenshell.entity_instance.entity_instance
:return: A list of elements using the to the material :return: A list of elements using the to the material
:rtype: list[ifcopenshell.entity_instance.entity_instance]
Example: Example:
@@ -233,12 +282,14 @@ def get_elements_by_material(ifc_file, material):
def get_elements_by_style(ifc_file, style): def get_elements_by_style(ifc_file, style):
""" """Retrieves the elements whose geometric representation uses a style
Retrieves the elements related to a style.
:param ifc_file: The IFC file :param ifc_file: The IFC file
:param style: The IFC Style entity :type ifc_file: ifcopenshell.file.file
:param style: The IfcPresentationStyle entity
:type style: ifcopenshell.entity_instance.entity_instance
:return: The elements related to the style :return: The elements related to the style
:rtype: list[ifcopenshell.entity_instance.entity_instance]
Example: Example:
@@ -271,6 +322,22 @@ def get_elements_by_style(ifc_file, style):
def get_elements_by_representation(ifc_file, representation): def get_elements_by_representation(ifc_file, representation):
"""Gets all elements using a geometric representation
:param ifc_file: The IFC file
:type ifc_file: ifcopenshell.file.file
:param representation: The IfcShapeRepresentation representation
:type representation: ifcopenshell.entity_instance.entity_instance
:return: The elements using the geometric representation
:rtype: list[ifcopenshell.entity_instance.entity_instance]
Example:
.. code:: python
representation = file.by_type("IfcShapeRepresentation")[0]
elements = ifcopenshell.util.element.get_elements_by_representation(file, representation)
"""
results = set() results = set()
[results.update(pr.ShapeOfProduct) for pr in representation.OfProductRepresentation] [results.update(pr.ShapeOfProduct) for pr in representation.OfProductRepresentation]
for rep_map in representation.RepresentationMap: for rep_map in representation.RepresentationMap:
@@ -286,7 +353,47 @@ def get_elements_by_representation(ifc_file, representation):
return results return results
def get_elements_by_layer(ifc_file, layer):
"""Get all the elements that are used by a presentation layer
:param ifc_file: The IFC file
:type ifc_file: ifcopenshell.file.file
:param layer: The IfcPresentationLayerAssignment layer
:type layer: ifcopenshell.entity_instance.entity_instance
:return: The elements using the geometric representation
:rtype: list[ifcopenshell.entity_instance.entity_instance]
"""
results = set()
for item in layer.AssignedItems:
if item.is_a("IfcShapeRepresentation"):
results.update(get_elements_by_representation(ifc_file, item))
elif item.is_a("IfcRepresentationItem"):
for inverse in ifc_file.get_inverse(item):
if inverse.is_a("IfcShapeRepresentation"):
results.update(get_elements_by_representation(ifc_file, inverse))
return results
def get_layers(ifc_file, element): def get_layers(ifc_file, element):
"""Get the CAD layers that an element is part of
An element may have portions or all of its geometry assigned to a
traditional CAD presentation layer.
:param ifc_file: The IFC file object
:type ifc_file: ifcopenshell.file.file
:param element: The IFC element to interrogate
:type element: ifcopenshell.entity_instance.entity_instance
:return: A list of IfcPresentationLayerAssignment
:rtype: list[ifcopenshell.entity_instance.entity_instance]
Example:
.. code:: python
element = ifcopenshell.by_type("IfcWall")[0]
layers = ifcopenshell.util.element.get_layers(element)
"""
layers = [] layers = []
representations = [] representations = []
if getattr(element, "Representation", None): if getattr(element, "Representation", None):
@@ -338,10 +445,11 @@ def get_container(element, should_get_direct=False):
def get_referenced_structures(element): def get_referenced_structures(element):
""" """Retreives a list of referenced spatial elements
Retreives a list of referenced spatial elements, typically useful for
multistorey elements or elements that span multiple spaces or in-between Typically useful for multistorey elements, such as columns or facade
spaces. elements, or elements that span multiple spaces or in-between spaces, such
as stairs, doors, etc.
:param element: The IFC element :param element: The IFC element
:type element: ifcopenshell.entity_instance.entity_instance :type element: ifcopenshell.entity_instance.entity_instance
@@ -469,6 +577,10 @@ def has_element_reference(value, element):
def remove_deep(ifc_file, element): def remove_deep(ifc_file, element):
"""Recursively purges a subgraph safely.
Do not use, use remove_deep2() instead.
"""
# @todo maybe some sort of try-finally mechanism. # @todo maybe some sort of try-finally mechanism.
ifc_file.batch() ifc_file.batch()
subgraph = list(ifc_file.traverse(element, breadth_first=True)) subgraph = list(ifc_file.traverse(element, breadth_first=True))
@@ -480,8 +592,7 @@ def remove_deep(ifc_file, element):
def remove_deep2(ifc_file, element, also_consider=[], do_not_delete=[]): def remove_deep2(ifc_file, element, also_consider=[], do_not_delete=[]):
""" """Recursively purges a subgraph safely, starting at an element
Recursively purges a subgraph safely, starting at an element
This should always be used instead of remove_deep. See #1812. The start This should always be used instead of remove_deep. See #1812. The start
element must have no inverses. The subgraph to be purged is calculated using element must have no inverses. The subgraph to be purged is calculated using
@@ -505,6 +616,8 @@ def remove_deep2(ifc_file, element, also_consider=[], do_not_delete=[]):
The do_not_delete argument contains all elements that may be part of the The do_not_delete argument contains all elements that may be part of the
subgraph but are protected from deletion. subgraph but are protected from deletion.
:param ifc_file: The IFC file object
:type ifc_file: ifcopenshell.file.file
:param element: The starting element that defines the subgraph :param element: The starting element that defines the subgraph
:type element: ifcopenshell.entity_instance.entity_instance :type element: ifcopenshell.entity_instance.entity_instance
""" """
@@ -536,8 +649,11 @@ def copy(ifc_file, element):
GlobalIds are regenerated. GlobalIds are regenerated.
:param ifc_file: The IFC file object :param ifc_file: The IFC file object
:type ifc_file: ifcopenshell.file.file
:param element: The IFC element to copy :param element: The IFC element to copy
:type element: ifcopenshell.entity_instance.entity_instance
:return: The newly copied element :return: The newly copied element
:rtype: ifcopenshell.entity_instance.entity_instance
""" """
new = ifc_file.create_entity(element.is_a()) new = ifc_file.create_entity(element.is_a())
for i, attribute in enumerate(element): for i, attribute in enumerate(element):
@@ -557,11 +673,14 @@ def copy_deep(ifc_file, element, exclude=None):
GlobalIds are regenerated. GlobalIds are regenerated.
:param ifc_file: The IFC file object :param ifc_file: The IFC file object
:type ifc_file: ifcopenshell.file.file
:param element: The IFC element to copy :param element: The IFC element to copy
:type element: ifcopenshell.entity_instance.entity_instance
:param exclude: An optional list of strings of IFC class names to not copy. :param exclude: An optional list of strings of IFC class names to not copy.
If any of the subelement is this class, it will not be copied and the If any of the subelement is this class, it will not be copied and the
original instance will be referenced. original instance will be referenced.
:return: The newly copied element :return: The newly copied element
:rtype: ifcopenshell.entity_instance.entity_instance
""" """
new = ifc_file.create_entity(element.is_a()) new = ifc_file.create_entity(element.is_a())
for i, attribute in enumerate(element): for i, attribute in enumerate(element):
@@ -441,9 +441,7 @@ class TestGetElementsByRepresentation(test.bootstrap.IFC4):
self.file.createIfcShapeRepresentation( self.file.createIfcShapeRepresentation(
Items=[ Items=[
self.file.createIfcMappedItem( self.file.createIfcMappedItem(
MappingSource=self.file.createIfcRepresentationMap( MappingSource=self.file.createIfcRepresentationMap(MappedRepresentation=representation)
MappedRepresentation=representation
)
) )
] ]
) )
@@ -458,6 +456,16 @@ class TestGetElementsByRepresentation(test.bootstrap.IFC4):
assert subject.get_elements_by_representation(self.file, representation) == {element} assert subject.get_elements_by_representation(self.file, representation) == {element}
class TestGetElementsByLayer(test.bootstrap.IFC4):
def test_getting_the_elements_of_a_layer(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")
layer = ifcopenshell.api.run("layer.add_layer", self.file)
representation = self.file.createIfcShapeRepresentation()
element.Representation = self.file.createIfcProductDefinitionShape(Representations=[representation])
ifcopenshell.api.run("layer.assign_layer", self.file, item=representation, layer=layer)
assert list(subject.get_elements_by_layer(self.file, layer)) == [element]
class TestGetlayers(test.bootstrap.IFC4): class TestGetlayers(test.bootstrap.IFC4):
def test_getting_the_layer_of_a_product_representation(self): def test_getting_the_layer_of_a_product_representation(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")