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:
Andrej730
2024-08-30 16:25:34 +05:00
parent acbdc0ed4c
commit bea837299e
5 changed files with 266 additions and 57 deletions
@@ -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