mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 02:02:22 +00:00
root.reassign_class to handle switching occurrence class <-> type class #5260
Kind of experimental. The idea is that it will reassign class e.g. from IfcWindow to IfcWindowType (or vice versa) and will keep the IFC representations and property sets. Example - https://imgur.com/a/X7MHR0s
This commit is contained in:
@@ -17,10 +17,16 @@
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api.aggregate
|
||||
import ifcopenshell.api.geometry
|
||||
import ifcopenshell.api.spatial
|
||||
import ifcopenshell.api.type
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.representation
|
||||
import ifcopenshell.util.type
|
||||
import ifcopenshell.util.schema
|
||||
import ifcopenshell.util.element
|
||||
from typing import Optional
|
||||
from typing import Optional, Union, Literal
|
||||
|
||||
|
||||
def reassign_class(
|
||||
@@ -46,15 +52,14 @@ def reassign_class(
|
||||
cannot be unambiguously derived, so you are required to manually check
|
||||
this.
|
||||
|
||||
Reassigning type class to occurrence (and vice versa) is supported.
|
||||
|
||||
:param product: The IfcProduct that you want to change the class of.
|
||||
:type product: ifcopenshell.entity_instance
|
||||
:param ifc_class: The new IFC class you want to change it to.
|
||||
:type ifc_class: str,optional
|
||||
:param predefined_type: In case you want to change the predefined type
|
||||
too. User defined types are also allowed, just type what you want.
|
||||
:type predefined_type: str,optional
|
||||
:return: The newly modified product.
|
||||
:rtype: ifcopenshell.entity_instance
|
||||
|
||||
Example:
|
||||
|
||||
@@ -80,38 +85,141 @@ def reassign_class(
|
||||
|
||||
|
||||
class Usecase:
|
||||
def execute(self):
|
||||
element = self.reassign_class(self.settings["product"], self.settings["ifc_class"])
|
||||
file: ifcopenshell.file
|
||||
|
||||
def execute(self):
|
||||
ifc_class: str = self.settings["ifc_class"]
|
||||
product: ifcopenshell.entity_instance = self.settings["product"]
|
||||
predefined_type: Union[str, None] = self.settings["predefined_type"]
|
||||
|
||||
was_type_product_before = product.is_a("IfcTypeProduct")
|
||||
schema = ifcopenshell.schema_by_name(self.file.schema)
|
||||
is_type_product_after = schema.declaration_by_name(ifc_class)._is("IfcTypeProduct")
|
||||
|
||||
if was_type_product_before == is_type_product_after:
|
||||
return self.simple_reassignment(product, ifc_class, predefined_type)
|
||||
|
||||
switch_type = "occurrence_to_type" if is_type_product_after else "type_to_occurrence"
|
||||
|
||||
return self.switch_between_class_types(
|
||||
product,
|
||||
switch_type,
|
||||
ifc_class,
|
||||
predefined_type,
|
||||
)
|
||||
|
||||
def switch_between_class_types(
|
||||
self,
|
||||
element: ifcopenshell.entity_instance,
|
||||
switch_type: Literal["occurrence_to_type", "type_to_occurrence"],
|
||||
ifc_class: str,
|
||||
predefined_type: Union[str, None],
|
||||
) -> ifcopenshell.entity_instance:
|
||||
|
||||
psets_to_reassign: list[ifcopenshell.entity_instance] = []
|
||||
|
||||
representations = list(ifcopenshell.util.representation.get_representations_iter(element))
|
||||
for rep in representations:
|
||||
if switch_type == "type_to_occurrence":
|
||||
rep = ifcopenshell.util.representation.resolve_representation(rep)
|
||||
ifcopenshell.api.geometry.unassign_representation(self.file, product=element, representation=rep)
|
||||
|
||||
if switch_type == "type_to_occurrence":
|
||||
occurrences = ifcopenshell.util.element.get_types(element)
|
||||
psets_to_reassign = element.HasPropertySets or []
|
||||
|
||||
element = self.reassign_class(element, ifc_class, predefined_type)
|
||||
ifcopenshell.api.type.unassign_type(self.file, occurrences)
|
||||
|
||||
for pset in psets_to_reassign:
|
||||
rel = self.file.create_entity(
|
||||
"IfcRelDefinesByProperties",
|
||||
GlobalId=ifcopenshell.guid.new(),
|
||||
RelatedObjects=[element],
|
||||
RelatingPropertyDefinition=pset,
|
||||
)
|
||||
else: # occurrence_to_type
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
# Handle element type.
|
||||
if element_type:
|
||||
ifcopenshell.api.type.unassign_type(self.file, [element])
|
||||
|
||||
# Handle containers and aggregates.
|
||||
if ifcopenshell.util.element.get_container(element):
|
||||
ifcopenshell.api.spatial.unassign_container(self.file, [element])
|
||||
elif ifcopenshell.util.element.get_aggregate(element):
|
||||
ifcopenshell.api.aggregate.unassign_object(self.file, [element])
|
||||
|
||||
for rel in element.IsDefinedBy:
|
||||
pset: ifcopenshell.entity_instance = rel.RelatingPropertyDefinition
|
||||
objs = list(rel.RelatedObjects)
|
||||
objs.remove(element)
|
||||
if objs:
|
||||
rel.RelatedObjects = objs
|
||||
else:
|
||||
history = getattr(rel, "OwnerHistory", None)
|
||||
self.file.remove(rel)
|
||||
if history:
|
||||
ifcopenshell.util.element.remove_deep2(self.file, history)
|
||||
psets_to_reassign.append(pset)
|
||||
|
||||
psets = ifcopenshell.util.element.get_psets(element)
|
||||
for pset_name in psets:
|
||||
pset = self.file.by_id(psets[pset_name]["id"])
|
||||
|
||||
element = self.reassign_class(element, ifc_class, predefined_type)
|
||||
|
||||
# Reassign psets.
|
||||
if psets_to_reassign:
|
||||
element.HasPropertySets = psets_to_reassign
|
||||
|
||||
# Reassign representations.
|
||||
for rep in representations:
|
||||
ifcopenshell.api.geometry.assign_representation(self.file, product=element, representation=rep)
|
||||
|
||||
# Keep IFC valid.
|
||||
if switch_type == "type_to_occurrence" and representations:
|
||||
ifcopenshell.api.geometry.edit_object_placement(self.file, product=element)
|
||||
|
||||
return element
|
||||
|
||||
def simple_reassignment(
|
||||
self,
|
||||
element: ifcopenshell.entity_instance,
|
||||
ifc_class: str,
|
||||
predefined_type: Union[str, None],
|
||||
) -> ifcopenshell.entity_instance:
|
||||
element = self.reassign_class(element, ifc_class, predefined_type)
|
||||
if element.is_a("IfcTypeProduct"):
|
||||
for occurrence in ifcopenshell.util.element.get_types(element) or []:
|
||||
ifc_class = ifcopenshell.util.type.get_applicable_entities(self.settings["ifc_class"])[0]
|
||||
self.reassign_class(occurrence, ifc_class)
|
||||
for occurrence in ifcopenshell.util.element.get_types(element):
|
||||
ifc_class_ = ifcopenshell.util.type.get_applicable_entities(ifc_class)[0]
|
||||
self.reassign_class(occurrence, ifc_class_, predefined_type)
|
||||
else:
|
||||
element_type = ifcopenshell.util.element.get_type(element)
|
||||
if element_type:
|
||||
ifc_class = ifcopenshell.util.type.get_applicable_types(self.settings["ifc_class"])
|
||||
if ifc_class and len(ifc_class) == 1:
|
||||
element_type = self.reassign_class(element_type, ifc_class[0])
|
||||
ifc_class_ = next(iter(ifcopenshell.util.type.get_applicable_types(ifc_class)))
|
||||
element_type = self.reassign_class(element_type, ifc_class_, predefined_type)
|
||||
ifc_class = element.is_a()
|
||||
for occurrence in ifcopenshell.util.element.get_types(element_type) or []:
|
||||
for occurrence in ifcopenshell.util.element.get_types(element_type):
|
||||
if occurrence == element:
|
||||
continue
|
||||
self.reassign_class(occurrence, ifc_class)
|
||||
self.reassign_class(occurrence, ifc_class, predefined_type)
|
||||
return element
|
||||
|
||||
def reassign_class(self, element, ifc_class):
|
||||
def reassign_class(
|
||||
self, element: ifcopenshell.entity_instance, ifc_class: str, predefined_type: Union[str, None]
|
||||
) -> ifcopenshell.entity_instance:
|
||||
element = ifcopenshell.util.schema.reassign_class(self.file, element, ifc_class)
|
||||
if self.settings["predefined_type"] and hasattr(element, "PredefinedType"):
|
||||
if predefined_type and hasattr(element, "PredefinedType"):
|
||||
try:
|
||||
element.PredefinedType = self.settings["predefined_type"]
|
||||
element.PredefinedType = predefined_type
|
||||
except:
|
||||
# PredefinedType wasn't in the respective enum, assume it's actually USERDEFINED
|
||||
# and set .ElementType / .ObjectType to the provided predefined type
|
||||
element.PredefinedType = "USERDEFINED"
|
||||
if element.is_a("IfcTypeProduct"):
|
||||
element.ElementType = self.settings["predefined_type"]
|
||||
element.ElementType = predefined_type
|
||||
else:
|
||||
element.ObjectType = self.settings["predefined_type"]
|
||||
element.ObjectType = predefined_type
|
||||
|
||||
return element
|
||||
|
||||
@@ -16,9 +16,14 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import ifcopenshell.api.aggregate
|
||||
import ifcopenshell.api.geometry
|
||||
import ifcopenshell.api.spatial
|
||||
import ifcopenshell.util.representation
|
||||
import test.bootstrap
|
||||
import ifcopenshell.api.type
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.api.root
|
||||
import ifcopenshell.api.type
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
@@ -95,6 +100,102 @@ class TestReassignClass(test.bootstrap.IFC4):
|
||||
assert len(self.file.by_type("IfcWall")) == 0
|
||||
assert len(self.file.by_type("IfcWallType")) == 0
|
||||
|
||||
# Switching between occurrence / type classes.
|
||||
def test_unassign_type_from_occurrences_if_switching_from_type_class_to_occurrence_class(self):
|
||||
element_type = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWallType")
|
||||
element1 = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||
ifcopenshell.api.type.assign_type(self.file, related_objects=[element1], relating_type=element_type)
|
||||
element2 = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||
ifcopenshell.api.type.assign_type(self.file, related_objects=[element2], relating_type=element_type)
|
||||
|
||||
element_type = ifcopenshell.api.root.reassign_class(self.file, product=element_type, ifc_class="IfcSlab")
|
||||
|
||||
# Type is unassigned.
|
||||
assert ifcopenshell.util.element.get_type(element1) is None
|
||||
assert ifcopenshell.util.element.get_type(element2) is None
|
||||
assert len(self.file.by_type("IfcRelDefinesByType")) == 0
|
||||
|
||||
assert len(self.file.by_type("IfcWall")) == 2
|
||||
assert len(self.file.by_type("IfcSlab")) == 1
|
||||
|
||||
def test_unassign_type_from_element_if_switching_from_occurrence_class_to_type_class(self):
|
||||
element_type = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWallType")
|
||||
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||
ifcopenshell.api.type.assign_type(self.file, related_objects=[element], relating_type=element_type)
|
||||
|
||||
element = ifcopenshell.api.root.reassign_class(self.file, product=element, ifc_class="IfcSlabType")
|
||||
|
||||
# Type is unassigned.
|
||||
assert ifcopenshell.util.element.get_types(element_type) == []
|
||||
assert len(self.file.by_type("IfcRelDefinesByType")) == 0
|
||||
|
||||
assert len(self.file.by_type("IfcWallType")) == 1
|
||||
assert len(self.file.by_type("IfcSlabType")) == 1
|
||||
|
||||
def test_unassigning_container_switching_from_occurrence_class_to_type_class(self):
|
||||
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||
container = self.file.create_entity("IfcBuilding")
|
||||
ifcopenshell.api.spatial.assign_container(self.file, products=[element], relating_structure=container)
|
||||
element = ifcopenshell.api.root.reassign_class(self.file, product=element, ifc_class="IfcSlabType")
|
||||
|
||||
assert len(self.file.by_type("IfcRelContainedInSpatialStructure")) == 0
|
||||
assert len(self.file.by_type("IfcWall")) == 0
|
||||
assert len(self.file.by_type("IfcSlabType")) == 1
|
||||
|
||||
def test_unassigning_aggregate_switching_from_occurrence_class_to_type_class(self):
|
||||
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||
aggregate = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcSlab")
|
||||
ifcopenshell.api.aggregate.assign_object(self.file, products=[element], relating_object=aggregate)
|
||||
element = ifcopenshell.api.root.reassign_class(self.file, product=element, ifc_class="IfcSlabType")
|
||||
|
||||
assert len(self.file.by_type("IfcRelAggregates")) == 0
|
||||
assert len(self.file.by_type("IfcWall")) == 0
|
||||
assert len(self.file.by_type("IfcSlabType")) == 1
|
||||
|
||||
def test_keeping_psets_switching_from_occurrence_class_to_type_class(self):
|
||||
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||
pset = ifcopenshell.api.pset.add_pset(self.file, element, name="TestPset")
|
||||
element = ifcopenshell.api.root.reassign_class(self.file, product=element, ifc_class="IfcSlabType")
|
||||
|
||||
assert ifcopenshell.util.element.get_pset(element, name="TestPset")["id"] == pset.id()
|
||||
assert len(self.file.by_type("IfcRelDefinesByProperties")) == 0
|
||||
assert len(self.file.by_type("IfcWall")) == 0
|
||||
assert len(self.file.by_type("IfcSlabType")) == 1
|
||||
|
||||
def test_keeping_psets_switching_from_type_class_to_occurrence_class(self):
|
||||
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWallType")
|
||||
pset = ifcopenshell.api.pset.add_pset(self.file, element, name="TestPset")
|
||||
element = ifcopenshell.api.root.reassign_class(self.file, product=element, ifc_class="IfcSlab")
|
||||
|
||||
assert ifcopenshell.util.element.get_pset(element, name="TestPset")["id"] == pset.id()
|
||||
assert len(self.file.by_type("IfcRelDefinesByProperties")) == 1
|
||||
assert len(self.file.by_type("IfcWallType")) == 0
|
||||
assert len(self.file.by_type("IfcSlab")) == 1
|
||||
|
||||
def test_keeping_representations_switching_from_occurrence_class_to_type_class(self):
|
||||
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall")
|
||||
context = self.file.create_entity("IfcGeometricRepresentationContext")
|
||||
representation = self.file.create_entity("IfcShapeRepresentation", ContextOfItems=context)
|
||||
ifcopenshell.api.geometry.assign_representation(self.file, element, representation)
|
||||
element = ifcopenshell.api.root.reassign_class(self.file, product=element, ifc_class="IfcSlabType")
|
||||
|
||||
assert ifcopenshell.util.representation.get_representation(element, context=context) == representation
|
||||
assert len(self.file.by_type("IfcWall")) == 0
|
||||
assert len(self.file.by_type("IfcSlabType")) == 1
|
||||
|
||||
def test_keeping_representations_switching_from_type_class_to_occurrence_class(self):
|
||||
element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWallType")
|
||||
context = self.file.create_entity("IfcGeometricRepresentationContext")
|
||||
representation = self.file.create_entity("IfcShapeRepresentation", ContextOfItems=context)
|
||||
representation.Items = [self.file.create_entity("IfcExtrudedAreaSolid")]
|
||||
ifcopenshell.api.geometry.assign_representation(self.file, element, representation)
|
||||
element = ifcopenshell.api.root.reassign_class(self.file, product=element, ifc_class="IfcSlab")
|
||||
|
||||
assert len(self.file.by_type("IfcRepresentationMap")) == 0
|
||||
assert ifcopenshell.util.representation.get_representation(element, context=context) == representation
|
||||
assert len(self.file.by_type("IfcWallType")) == 0
|
||||
assert len(self.file.by_type("IfcSlab")) == 1
|
||||
|
||||
|
||||
class TestReassignClassIFC2X3(test.bootstrap.IFC2X3, TestReassignClass):
|
||||
pass
|
||||
|
||||
Reference in New Issue
Block a user