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
classes = (
operator.LoadLayers,
operator.DisableLayerEditingUI,
operator.EnableEditingLayer,
operator.DisableEditingLayer,
operator.AddPresentationLayer,
operator.EditPresentationLayer,
operator.RemovePresentationLayer,
operator.AssignPresentationLayer,
operator.DisableEditingLayer,
operator.DisableLayerEditingUI,
operator.EditPresentationLayer,
operator.EnableEditingLayer,
operator.LoadLayers,
operator.RemovePresentationLayer,
operator.SelectLayerProducts,
operator.UnassignPresentationLayer,
prop.Layer,
prop.BIMLayerProperties,
@@ -18,9 +18,11 @@
import bpy
import json
import ifcopenshell.util.attribute
import ifcopenshell.api
import ifcopenshell.util.element
import ifcopenshell.util.attribute
import blenderbim.bim.helper
import blenderbim.tool as tool
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.layer.data import Data
@@ -182,3 +184,19 @@ class UnassignPresentationLayer(bpy.types.Operator):
)
Data.load(IfcStore.get_file())
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)
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.disable_editing_layer", text="", icon="CANCEL")
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
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.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:
.. code:: python
element = ifcopenshell.by_type("IfcBuildingElement")[0]
psets = ifcopenshell.util.element.get_psets(element, psets_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):
"""
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
:type element: ifcopenshell.entity_instance.entity_instance
:return: The predefined type of the element
:rtype: str
Example:
.. 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)
if element_type:
@@ -146,17 +154,19 @@ def get_predefined_type(element):
def get_type(element):
"""
Retrieves the Element Type entity related to an element entity.
"""Retrieves the construction type element of an element occurrence
:param element: The IFC Element entity
:return: The Element Type entity defining the element
:param element: The element occurrence
:type: ifcopenshell.entity_instance.entity_instance
:return: The related type element
:rtype ifcopenshell.entity_instance.entity_instance
Example:
.. 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"):
return element
@@ -169,6 +179,20 @@ def get_type(element):
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", []):
return rel.RelatedObjects
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):
"""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:
for relationship in element.HasAssociations:
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):
"""
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
usage.
:param ifc_file: The IFC file
:type ifc_file: ifcopenshell.file.file
:param material: The IFC Material entity
:type material: ifcopenshell.entity_instance.entity_instance
:return: A list of elements using the to the material
:rtype: list[ifcopenshell.entity_instance.entity_instance]
Example:
@@ -233,12 +282,14 @@ def get_elements_by_material(ifc_file, material):
def get_elements_by_style(ifc_file, style):
"""
Retrieves the elements related to a style.
"""Retrieves the elements whose geometric representation uses a style
: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
:rtype: list[ifcopenshell.entity_instance.entity_instance]
Example:
@@ -271,6 +322,22 @@ def get_elements_by_style(ifc_file, style):
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.update(pr.ShapeOfProduct) for pr in representation.OfProductRepresentation]
for rep_map in representation.RepresentationMap:
@@ -286,7 +353,47 @@ def get_elements_by_representation(ifc_file, representation):
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):
"""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 = []
representations = []
if getattr(element, "Representation", None):
@@ -338,10 +445,11 @@ def get_container(element, should_get_direct=False):
def get_referenced_structures(element):
"""
Retreives a list of referenced spatial elements, typically useful for
multistorey elements or elements that span multiple spaces or in-between
spaces.
"""Retreives a list of referenced spatial elements
Typically useful for multistorey elements, such as columns or facade
elements, or elements that span multiple spaces or in-between spaces, such
as stairs, doors, etc.
:param element: The IFC element
:type element: ifcopenshell.entity_instance.entity_instance
@@ -469,6 +577,10 @@ def has_element_reference(value, 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.
ifc_file.batch()
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=[]):
"""
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
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
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
:type element: ifcopenshell.entity_instance.entity_instance
"""
@@ -536,8 +649,11 @@ def copy(ifc_file, element):
GlobalIds are regenerated.
:param ifc_file: The IFC file object
:type ifc_file: ifcopenshell.file.file
:param element: The IFC element to copy
:type element: ifcopenshell.entity_instance.entity_instance
:return: The newly copied element
:rtype: ifcopenshell.entity_instance.entity_instance
"""
new = ifc_file.create_entity(element.is_a())
for i, attribute in enumerate(element):
@@ -557,11 +673,14 @@ def copy_deep(ifc_file, element, exclude=None):
GlobalIds are regenerated.
:param ifc_file: The IFC file object
:type ifc_file: ifcopenshell.file.file
: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.
If any of the subelement is this class, it will not be copied and the
original instance will be referenced.
:return: The newly copied element
:rtype: ifcopenshell.entity_instance.entity_instance
"""
new = ifc_file.create_entity(element.is_a())
for i, attribute in enumerate(element):
@@ -441,9 +441,7 @@ class TestGetElementsByRepresentation(test.bootstrap.IFC4):
self.file.createIfcShapeRepresentation(
Items=[
self.file.createIfcMappedItem(
MappingSource=self.file.createIfcRepresentationMap(
MappedRepresentation=representation
)
MappingSource=self.file.createIfcRepresentationMap(MappedRepresentation=representation)
)
]
)
@@ -458,6 +456,16 @@ class TestGetElementsByRepresentation(test.bootstrap.IFC4):
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):
def test_getting_the_layer_of_a_product_representation(self):
element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall")