Skip to content

Commit

Permalink
material.unassign_material - support batching #4474
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrej730 committed Apr 12, 2024
1 parent 59cc9c0 commit eb46de9
Show file tree
Hide file tree
Showing 9 changed files with 179 additions and 84 deletions.
4 changes: 2 additions & 2 deletions src/blenderbim/blenderbim/bim/module/geometry/operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,8 +324,8 @@ def update_obj_mesh_representation(self, context, obj):
# We are explicitly casting to a tessellation, so remove all parametric materials.
element_type = ifcopenshell.util.element.get_type(product)
if element_type: # Some invalid IFCs use material sets without a type.
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=element_type)
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), product=product)
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), products=[element_type])
ifcopenshell.api.run("material.unassign_material", tool.Ifc.get(), products=[product])
else:
# These objects are parametrically based on an axis and should not be modified as a mesh
return
Expand Down
2 changes: 1 addition & 1 deletion src/blenderbim/blenderbim/bim/module/model/product.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,7 @@ def ensure_material_assigned(usecase_path, ifc_file, settings):


def ensure_material_unassigned(usecase_path, ifc_file, settings):
elements = [settings["product"]]
elements = settings["products"]
if elements[0].is_a("IfcElementType"):
elements.extend(ifcopenshell.util.element.get_types(elements[0]))
for element in elements:
Expand Down
6 changes: 3 additions & 3 deletions src/blenderbim/blenderbim/core/material.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,12 @@ def unassign_material(ifc, material_tool, objects):
inherited_material = material_tool.get_material(element, should_inherit=True)
if material and "Usage" in material.is_a():
element_type = material_tool.get_type(element)
ifc.run("material.unassign_material", product=element_type)
ifc.run("material.unassign_material", products=[element_type])
elif not material and inherited_material:
element_type = material_tool.get_type(element)
ifc.run("material.unassign_material", product=element_type)
ifc.run("material.unassign_material", products=[element_type])
elif material:
ifc.run("material.unassign_material", product=element)
ifc.run("material.unassign_material", products=[element])


def patch_non_parametric_mep_segment(ifc, material_tool, profile_tool, obj):
Expand Down
9 changes: 4 additions & 5 deletions src/ifcopenshell-python/ifcopenshell/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,12 @@ def batching_argument_deprecation(
"type.unassign_type": partial(
batching_argument_deprecation, prev_argument="related_object", new_argument="related_objects"
),
"system.assign_system": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
"system.unassign_system": partial(
"system.assign_system": partial(batching_argument_deprecation, prev_argument="product", new_argument="products"),
"system.unassign_system": partial(batching_argument_deprecation, prev_argument="product", new_argument="products"),
"material.assign_material": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
"material.assign_material": partial(
"material.unassign_material": partial(
batching_argument_deprecation, prev_argument="product", new_argument="products"
),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,9 @@ def execute(self) -> Union[ifcopenshell.entity_instance, list[ifcopenshell.entit
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)
products_to_unassign_material = [p for p in self.products if ifcopenshell.util.element.get_material(p)]
if products_to_unassign_material:
ifcopenshell.api.run("material.unassign_material", self.file, products=products_to_unassign_material)

if self.settings["type"] == "IfcMaterial" or (
self.settings["material"] and not self.settings["material"].is_a("IfcMaterial")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,22 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.

import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.element


class Usecase:
def __init__(self, file, product=None):
"""Removes any material relationship with a product
def __init__(self, file: ifcopenshell.file, products: list[ifcopenshell.entity_instance]):
"""Removes any material relationship with the list of products
A product can only have one material assigned to it, which is why it is
not necessary to specify the material to unassign. The material is not
removed, only the relationship is removed.
If the product does not have a material, nothing happens.
:param product: The IfcProduct that may or may not have a material
:type product: ifcopenshell.entity_instance.entity_instance
:param products: The list IfcProducts that may or may not have a material
:type product: list[ifcopenshell.entity_instance.entity_instance]
:return: None
:rtype: None
Expand All @@ -49,20 +50,34 @@ def __init__(self, file, product=None):
# Let's change our mind and remove the concrete assignment. The
# concrete material still exists, but the bench is no longer made
# out of concrete now.
ifcopenshell.api.run("material.unassign_material", model, product=bench_type)
ifcopenshell.api.run("material.unassign_material", model, products=[bench_type])
"""
self.file = file
self.settings = {"product": product}
self.settings = {"products": products}

def execute(self):
if self.settings["product"].is_a("IfcTypeObject"):
material = ifcopenshell.util.element.get_material(self.settings["product"])
def execute(self) -> None:
self.products = set(self.settings["products"])
if not self.products:
return

self.remove_material_usages_from_types()
self.unassign_materials()

def remove_material_usages_from_types(self) -> None:
# remove material usages from types
for product in self.products:
if not product.is_a("IfcTypeObject"):
continue
material = ifcopenshell.util.element.get_material(product)
if not material:
continue
if material.is_a() in ["IfcMaterialLayerSet", "IfcMaterialProfileSet"]:
# Remove set usages
for inverse in self.file.get_inverse(material):
if self.file.schema == "IFC2X3":
if not inverse.is_a("IfcMaterialLayerSetUsage"):
continue
# in IFC2X3 there is no .AssociatedTo
for inverse2 in self.file.get_inverse(inverse):
if inverse2.is_a("IfcRelAssociatesMaterial"):
history = inverse2.OwnerHistory
Expand All @@ -73,21 +88,40 @@ def execute(self):
if not inverse.is_a("IfcMaterialUsageDefinition"):
continue
for rel in inverse.AssociatedTo:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
self.file.remove(inverse)

for rel in self.settings["product"].HasAssociations:
if rel.is_a("IfcRelAssociatesMaterial"):
if rel.RelatingMaterial.is_a() in ["IfcMaterialLayerSetUsage", "IfcMaterialProfileSetUsage"]:
def unassign_materials(self) -> None:
associations: set[ifcopenshell.entity_instance] = set()
for product in self.products:
associations.update(product.HasAssociations)

# we ensure that `associations` won't have removed elements
# to avoid crash during `material_inverses.issubset(associations)`
while associations:
rel = next(iter(associations))

if not rel.is_a("IfcRelAssociatesMaterial"):
associations.remove(rel)
else:
material = rel.RelatingMaterial
related_objects = set(rel.RelatedObjects) - self.products

if material.is_a() in ["IfcMaterialLayerSetUsage", "IfcMaterialProfileSetUsage"]:
# Warning: this may leave the model in a non-compliant state.
if self.file.get_total_inverses(rel.RelatingMaterial) == 1 and len(rel.RelatedObjects) == 1:
self.file.remove(rel.RelatingMaterial)
if len(rel.RelatedObjects) == 1:
material_inverses = set(self.file.get_inverse(material))
if material_inverses.issubset(associations) and not related_objects:
self.file.remove(material)
associations.remove(rel)

if not related_objects:
history = rel.OwnerHistory
self.file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(self.file, history)
continue
related_objects = set(rel.RelatedObjects)
related_objects.remove(self.settings["product"])
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def element_exists(element_id):
pset=inverse.RelatingPropertyDefinition,
)
elif inverse.is_a("IfcRelAssociatesMaterial"):
ifcopenshell.api.run("material.unassign_material", self.file, product=self.settings["product"])
ifcopenshell.api.run("material.unassign_material", self.file, products=[self.settings["product"]])
elif inverse.is_a("IfcRelDefinesByType"):
if inverse.RelatingType == self.settings["product"]:
ifcopenshell.api.run("type.unassign_type", self.file, related_objects=inverse.RelatedObjects)
Expand Down

0 comments on commit eb46de9

Please sign in to comment.