material.assign_material - support batching #4474

This commit is contained in:
Andrej730
2024-04-12 11:21:35 +05:00
parent 32d2f1d629
commit 59cc9c0a16
43 changed files with 432 additions and 236 deletions
@@ -78,7 +78,7 @@ class Usecase:
# our window too, but to keep this example simple, geometry is
# optional and it is enough to say that this window is made out of
# aluminium and glass.
ifcopenshell.api.run("material.assign_material", model, product=window_type, material=material_set)
ifcopenshell.api.run("material.assign_material", model, products=[window_type], material=material_set)
"""
self.file = file
self.settings = {"constituent_set": constituent_set, "material": material}
@@ -76,7 +76,7 @@ class Usecase:
ifcopenshell.api.run("material.edit_layer", model, layer=layer, attributes={"LayerThickness": 13})
# Great! Let's assign our material set to our wall type.
ifcopenshell.api.run("material.assign_material", model, product=wall_type, material=material_set)
ifcopenshell.api.run("material.assign_material", model, products=[wall_type], material=material_set)
"""
self.file = file
self.settings = {"layer_set": layer_set, "material": material}
@@ -77,7 +77,7 @@ class Usecase:
# our window too, but to keep this example simple, geometry is
# optional and it is enough to say that this window is made out of
# aluminium and glass.
ifcopenshell.api.run("material.assign_material", model, product=window_type, material=material_set)
ifcopenshell.api.run("material.assign_material", model, products=[window_type], material=material_set)
"""
self.file = file
self.settings = {"material_list": material_list, "material": material}
@@ -68,7 +68,7 @@ class Usecase:
# Assign the concrete material to that bench. Note that no colour
# "Style" has been specified.
ifcopenshell.api.run("material.assign_material", model, product=concrete_bench, material=concrete)
ifcopenshell.api.run("material.assign_material", model, products=[concrete_bench], material=concrete)
"""
self.file = file
self.settings = {"name": name or "Unnamed", "category": category}
@@ -100,7 +100,7 @@ class Usecase:
ifcopenshell.api.run("material.edit_layer", model, layer=layer, attributes={"LayerThickness": 13})
# Great! Let's assign our material set to our wall type.
ifcopenshell.api.run("material.assign_material", model, product=wall_type, material=material_set)
ifcopenshell.api.run("material.assign_material", model, products=[wall_type], material=material_set)
"""
self.file = file
self.settings = {"name": name or "Unnamed", "set_type": set_type or "IfcMaterialConstituentSet"}
@@ -79,7 +79,7 @@ class Usecase:
profile_set=material_set, material=steel, profile=hea100)
# Great! Let's assign our material set to our beam type.
ifcopenshell.api.run("material.assign_material", model, product=beam_type, material=material_set)
ifcopenshell.api.run("material.assign_material", model, products=[beam_type], material=material_set)
"""
self.file = file
self.settings = {"profile_set": profile_set, "material": material, "profile": profile}
@@ -27,11 +27,11 @@ class Usecase:
def __init__(
self,
file: ifcopenshell.file,
product: ifcopenshell.entity_instance,
products: list[ifcopenshell.entity_instance],
type: str = "IfcMaterial",
material: Optional[ifcopenshell.entity_instance] = None,
):
"""Assigns a material to a product
"""Assigns a material to the list of products
Will unassign previously assigned material.
@@ -62,9 +62,9 @@ class Usecase:
This allows individual occurrences to override the layered or profiled
construction offset from a reference line.
:param product: The IfcProduct to assign the material or material set
:param products: The list of IfcProducts to assign the material or material set
to.
:type product: ifcopenshell.entity_instance.entity_instance
:type products: list[ifcopenshell.entity_instance.entity_instance]
:param type: Choose from "IfcMaterial", "IfcMaterialConstituentSet",
"IfcMaterialLayerSet", "IfcMaterialLayerSetUsage",
"IfcMaterialProfileSet", "IfcMaterialProfileSetUsage", or
@@ -75,8 +75,14 @@ class Usecase:
If type is Usage then no need to provide `material`, it will be deduced
from the element type automatically.
:type material: ifcopenshell.entity_instance.entity_instance, optional
:return: The IfcRelAssociatesMaterial entity
:rtype: ifcopenshell.entity_instance.entity_instance
:return: IfcRelAssociatesMaterial entity
or a list of IfcRelAssociatesMaterial entities
(possible if `type` is Usage
and `products` require different Usages)
or `None` if `products` was empty list.
:rtype: Union[
ifcopenshell.entity_instance.entity_instance,
list[ifcopenshell.entity_instance.entity_instance], None]
Example:
@@ -89,7 +95,7 @@ class Usecase:
# material. Let's assign it to the type.
bench_type = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcFurnitureType")
ifcopenshell.api.run("material.assign_material", model,
product=bench_type, type="IfcMaterial", material=concrete)
products=[bench_type], type="IfcMaterial", material=concrete)
# Let's imagine there are a two occurrences of this bench. It's not
# necessary to assign any material to these benches as they
@@ -113,7 +119,7 @@ class Usecase:
# Our wall type now has the layer set assigned to it
ifcopenshell.api.run("material.assign_material", model,
product=wall_type, type="IfcMaterialLayerSet", material=material_set)
products=[wall_type], type="IfcMaterialLayerSet", material=material_set)
# Let's imagine an occurrence of this wall type.
wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
@@ -125,7 +131,7 @@ class Usecase:
# they automatically detect the inherited material set from the
# type. You'd write similar code for a profile set.
ifcopenshell.api.run("material.assign_material", model,
product=wall, type="IfcMaterialLayerSetUsage")
products=[wall], type="IfcMaterialLayerSetUsage")
# To be complete, let's create the wall's axis and body
# representation. Notice how the axis guides the walls "reference
@@ -141,12 +147,19 @@ class Usecase:
ifcopenshell.api.run("geometry.edit_object_placement", model, product=wall)
"""
self.file = file
self.settings = {"product": product, "type": type, "material": material}
self.settings = {"products": products, "type": type, "material": material}
def execute(self) -> Union[ifcopenshell.entity_instance, list[ifcopenshell.entity_instance], None]:
self.products: set[ifcopenshell.entity_instance] = set(self.settings["products"])
if not self.products:
return
# NOTE: we always reassign material, even if it might be assigned before
for product in self.products:
material = ifcopenshell.util.element.get_material(product)
if material:
ifcopenshell.api.run("material.unassign_material", self.file, product=product)
def execute(self) -> ifcopenshell.entity_instance:
material = ifcopenshell.util.element.get_material(self.settings["product"])
if material:
ifcopenshell.api.run("material.unassign_material", self.file, product=self.settings["product"])
if self.settings["type"] == "IfcMaterial" or (
self.settings["material"] and not self.settings["material"].is_a("IfcMaterial")
):
@@ -161,71 +174,104 @@ class Usecase:
return self.create_material_association(material_set)
elif self.settings["type"] == "IfcMaterialLayerSetUsage":
element_type = ifcopenshell.util.element.get_type(self.settings["product"])
if element_type:
element_type_material = ifcopenshell.util.element.get_material(element_type)
if element_type_material and element_type_material.is_a("IfcMaterialLayerSet"):
material_set = element_type_material
# NOTE: might return list of rels
types_to_products_layers_sets: dict[
tuple[Union[ifcopenshell.entity_instance, None], str], tuple[ifcopenshell.entity_instance, list]
] = dict()
AXIS3_CLASSES = [
"IfcSlab",
"IfcSlabStandardCase",
"IfcSlabElementedCase",
"IfcRoof",
"IfcRamp",
"IfcPlate",
"IfcPlateStandardCase",
]
for product in self.products:
element_type = ifcopenshell.util.element.get_type(product)
layer_set_direction = "AXIS3" if product.is_a() in AXIS3_CLASSES else "AXIS2"
material_layer_type = (element_type, layer_set_direction)
if material_layer_type in types_to_products_layers_sets:
types_to_products_layers_sets[material_layer_type][1].append(product)
continue
if element_type:
element_type_material = ifcopenshell.util.element.get_material(element_type)
if element_type_material and element_type_material.is_a("IfcMaterialLayerSet"):
material_set = element_type_material
else:
material_set = self.file.create_entity("IfcMaterialLayerSet")
else:
material_set = self.file.create_entity("IfcMaterialLayerSet")
else:
material_set = self.file.create_entity("IfcMaterialLayerSet")
material_set_usage = self.create_layer_set_usage(material_set)
return self.create_material_association(material_set_usage)
types_to_products_layers_sets[material_layer_type] = (material_set, [product])
rels = [
self.create_layer_set_usage(material_set, layer_set_direction, products)
for (_, layer_set_direction), (material_set, products) in types_to_products_layers_sets.items()
]
return rels[0] if len(rels) == 1 else rels
elif self.settings["type"] == "IfcMaterialProfileSet":
material_set = self.file.create_entity(self.settings["type"])
return self.create_material_association(material_set)
elif self.settings["type"] == "IfcMaterialProfileSetUsage":
element_type = ifcopenshell.util.element.get_type(self.settings["product"])
if element_type:
element_type_material = ifcopenshell.util.element.get_material(element_type)
if element_type_material and element_type_material.is_a("IfcMaterialProfileSet"):
material_set = element_type_material
# NOTE: might return list of rels
types_to_products_profile_sets: dict[
Union[ifcopenshell.entity_instance, None], tuple[ifcopenshell.entity_instance, list]
] = dict()
for product in self.products:
element_type = ifcopenshell.util.element.get_type(product)
if element_type in types_to_products_profile_sets:
types_to_products_profile_sets[element_type][1].append(product)
continue
if element_type:
element_type_material = ifcopenshell.util.element.get_material(element_type)
if element_type_material and element_type_material.is_a("IfcMaterialProfileSet"):
material_set = element_type_material
else:
material_set = self.file.create_entity("IfcMaterialProfileSet")
else:
material_set = self.file.create_entity("IfcMaterialProfileSet")
else:
material_set = self.file.create_entity("IfcMaterialProfileSet")
types_to_products_profile_sets[element_type] = (material_set, [product])
self.update_representation_profile(material_set)
material_set_usage = self.create_profile_set_usage(material_set)
return self.create_material_association(material_set_usage)
rels = []
for _, (material_set, products) in types_to_products_profile_sets.items():
self.update_representation_profile(material_set, products)
material_set_usage = self.create_profile_set_usage(material_set)
rels.append(self.create_material_association(material_set_usage, products))
return rels[0] if len(rels) == 1 else rels
elif self.settings["type"] == "IfcMaterialList":
material_set = self.file.create_entity(self.settings["type"])
material_set.Materials = [self.settings["material"]]
return self.create_material_association(material_set)
def update_representation_profile(self, material_set: ifcopenshell.entity_instance) -> None:
def update_representation_profile(
self, material_set: ifcopenshell.entity_instance, products: list[ifcopenshell.entity_instance]
) -> None:
profile = material_set.CompositeProfile
if not profile and material_set.MaterialProfiles:
profile = material_set.MaterialProfiles[0].Profile
if not profile:
return
representation = ifcopenshell.util.representation.get_representation(
self.settings["product"], "Model", "Body", "MODEL_VIEW"
)
if not representation:
return
for subelement in self.file.traverse(representation):
if subelement.is_a("IfcSweptAreaSolid"):
subelement.SweptArea = profile
for product in products:
representation = ifcopenshell.util.representation.get_representation(product, "Model", "Body", "MODEL_VIEW")
if not representation:
return
for subelement in self.file.traverse(representation):
if subelement.is_a("IfcSweptAreaSolid"):
subelement.SweptArea = profile
def create_layer_set_usage(self, material_set: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
if self.settings["product"].is_a() in [
"IfcSlab",
"IfcSlabStandardCase",
"IfcSlabElementedCase",
"IfcRoof",
"IfcRamp",
"IfcPlate",
"IfcPlateStandardCase",
]:
layer_set_direction = "AXIS3"
else:
layer_set_direction = "AXIS2"
return self.file.create_entity(
def create_layer_set_usage(
self,
material_set: ifcopenshell.entity_instance,
layer_set_direction: str,
products: list[ifcopenshell.entity_instance],
) -> ifcopenshell.entity_instance:
usage = self.file.create_entity(
"IfcMaterialLayerSetUsage",
**{
"ForLayerSet": material_set,
@@ -234,29 +280,34 @@ class Usecase:
"OffsetFromReferenceLine": 0,
}
)
return self.create_material_association(usage, products)
def create_profile_set_usage(self, material_set: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
return self.file.create_entity("IfcMaterialProfileSetUsage", **{"ForProfileSet": material_set})
def assign_ifc_material(self) -> ifcopenshell.entity_instance:
rel = self.get_rel_associates_material(self.settings["material"])
material = self.settings["material"] or self.file.create_entity("IfcMaterial")
rel = self.get_rel_associates_material(material)
if not rel:
return self.create_material_association(self.settings["material"])
related_objects = list(rel.RelatedObjects)
related_objects.append(self.settings["product"])
rel.RelatedObjects = related_objects
return self.create_material_association(material)
previous_related_objects = set(rel.RelatedObjects)
rel.RelatedObjects = list(previous_related_objects | self.products)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
return rel
def create_material_association(
self, relating_material: ifcopenshell.entity_instance
self,
relating_material: ifcopenshell.entity_instance,
products: Optional[list[ifcopenshell.entity_instance]] = None,
) -> ifcopenshell.entity_instance:
if products is None:
products = list(self.products)
return self.file.create_entity(
"IfcRelAssociatesMaterial",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
"RelatedObjects": [self.settings["product"]],
"RelatedObjects": products,
"RelatingMaterial": relating_material,
}
)
@@ -265,11 +316,12 @@ class Usecase:
self, material: ifcopenshell.entity_instance
) -> Union[ifcopenshell.entity_instance, None]:
if self.file.schema == "IFC2X3" or material.is_a("IfcMaterialList"):
rel = [
r
for r in self.file.by_type("IfcRelAssociatesMaterial")
if r.RelatingMaterial == self.settings["material"]
]
return rel[0] if rel else None
if self.settings["material"].AssociatedTo:
return self.settings["material"].AssociatedTo[0]
return next(
(
r
for r in self.file.by_type("IfcRelAssociatesMaterial")
if r.RelatingMaterial == self.settings["material"]
),
None,
)
return next(iter(material.AssociatedTo), None)
@@ -66,12 +66,12 @@ class Usecase:
profile_set=material_set, material=steel, profile=hea100)
# Great! Let's assign our material set to our beam type.
ifcopenshell.api.run("material.assign_material", model, product=beam_type, material=material_set)
ifcopenshell.api.run("material.assign_material", model, products=[beam_type], material=material_set)
# Let's create an occurrence of this beam.
beam = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBeam", name="B1.01")
ifcopenshell.api.run("material.assign_material", model,
product=beam, type="IfcMaterialProfileSetUsage")
products=[beam], type="IfcMaterialProfileSetUsage")
# Let's give a 1000mm long beam body representation.
body = ifcopenshell.api.run("geometry.add_profile_representation",
@@ -55,7 +55,7 @@ class Usecase:
# Our wall type now has the layer set assigned to it
ifcopenshell.api.run("material.assign_material", model,
product=wall_type, type="IfcMaterialLayerSet", material=material_set)
products=[wall_type], type="IfcMaterialLayerSet", material=material_set)
# Let's imagine an occurrence of this wall type.
wall = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcWall")
@@ -67,7 +67,7 @@ class Usecase:
# they automatically detect the inherited material set from the
# type. You'd write similar code for a profile set.
rel = ifcopenshell.api.run("material.assign_material", model,
product=wall, type="IfcMaterialLayerSetUsage")
products=[wall], type="IfcMaterialLayerSetUsage")
# Let's change the offset from the reference line to be 200mm
# instead of the default of 0mm.
@@ -70,12 +70,12 @@ class Usecase:
profile_set=material_set, material=steel, profile=hea100)
# Great! Let's assign our material set to our beam type.
ifcopenshell.api.run("material.assign_material", model, product=beam_type, material=material_set)
ifcopenshell.api.run("material.assign_material", model, products=[beam_type], material=material_set)
# Let's create an occurrence of this beam.
beam = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBeam", name="B1.01")
rel = ifcopenshell.api.run("material.assign_material", model,
product=beam, type="IfcMaterialProfileSetUsage")
products=[beam], type="IfcMaterialProfileSetUsage")
# Let's give a 1000mm long beam body representation.
body = ifcopenshell.api.run("geometry.add_profile_representation",
@@ -44,7 +44,7 @@ class Usecase:
# Let's imagine a concrete bench made out of concrete.
bench_type = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcFurnitureType")
ifcopenshell.api.run("material.assign_material", model,
product=bench_type, type="IfcMaterial", material=concrete)
products=[bench_type], type="IfcMaterial", material=concrete)
# Let's change our mind and remove the concrete assignment. The
# concrete material still exists, but the bench is no longer made