IfcOpenShell : Associate material constituents to specific faces in a Brep representation
I'm trying to export an IfcWindow constituted with several parts, which I've simplified to wooden frames and a glass.
It's a single object in Blender. i've assigned the Wood material to the frame, and the Glass material to the glass.
Up until there, easy peezey. After skimming through the docs and studying the example library that comes with the BlenderBIM addon I've elected using a IfcMaterialConstituentSet, adding two constituents linked to a Wood and a Glass material. I then create a Brep geometry representation for the object, and link everything together.
The Constituent Set seems to be correctly exported, along with my two materials, but I'm missing something somewhere to actually assign the correct faces to their corrsponding material. The Brep creation code in get_brep_representation
does seggregate the polygons by material index in items
so the information is already here and passed along with file.createIfcShapeRepresentation
, I just don't understand how to make the link between the geometry and the material in ifc :)
The docs state "NOTE See the "Material Use Definition" at the individual element to which an IfcMaterialConstituentSet may apply for a required or recommended definition of such keywords." but I've not seen this information explained anywhere...
When I roundtrip the file the occurence does have an IfcMaterialConstituentSet with my two materials
But the object is only assigned the Glass material
I'm using Blender but I guess this could be applied to any software or even plain geometry data.
Here's the code for anyone interested in lending a hand or giving any insight into my endeavour :p
Cheers :)
# This can be substituted for your own filepath
from pathlib import Path
import bpy
blend_path = Path(bpy.data.filepath)
blend_name = blend_path.stem
filepath = str(blend_path.with_name(blend_name + "_test.ifc"))
obj_blend = bpy.context.active_object
# Retrieve the object placement in the world
def edit_object_placement(file, product):
run(
"geometry.edit_object_placement",
file,
product=product,
matrix=obj_blend.matrix_world.copy(),
is_si=False,
)
# Transform the blender mesh data to Brep
def get_brep_representation(file, body):
import bpy
# Note : copy/pasted from https://github.com/IfcOpenShell/IfcOpenShell/blob/v0.7.0/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py#L552-L575
matrix = obj_blend.matrix_world.copy()
depsgraph = bpy.context.evaluated_depsgraph_get()
obj_blender_evaluated = obj_blend.evaluated_get(depsgraph)
mesh_evaluated = obj_blender_evaluated.data
ifc_vertices = [file.createIfcCartesianPoint(v.co) for v in mesh_evaluated.vertices]
ifc_raw_items = [[]] * max(1, len(obj_blender_evaluated.material_slots))
for polygon in mesh_evaluated.polygons:
ifc_raw_items[polygon.material_index % max(1, len(obj_blender_evaluated.material_slots))].append(
file.createIfcFace(
[
file.createIfcFaceOuterBound(
file.createIfcPolyLoop([ifc_vertices[vertex] for vertex in polygon.vertices]),
True,
)
]
)
)
items = [file.createIfcFacetedBrep(file.createIfcClosedShell(i)) for i in ifc_raw_items if i]
# items is a list of list of faces
# the index of sub-lists corresponds to the index of the material in the material slots
# How do I link it to the MaterialConstituentSet ??
return file.createIfcShapeRepresentation(
body,
body.ContextIdentifier,
"Brep",
items,
)
from ifcopenshell.api import run
import ifcopenshell
file = run("project.create_file")
# Boilerplate
project = run("root.create_entity", file, ifc_class="IfcProject", name="My Project")
context = run("context.add_context", file, context_type="Model")
body = run(
"context.add_context",
file,
context_type="Model",
context_identifier="Body",
target_view="MODEL_VIEW",
parent=context,
)
run("unit.assign_unit", file, length={"is_metric": True, "raw": "METERS"})
site = run("root.create_entity", file, ifc_class="IfcSite", name="My Site")
building = run("root.create_entity", file, ifc_class="IfcBuilding", name="Building A")
storey = run("root.create_entity", file, ifc_class="IfcBuildingStorey", name="Storey 0")
run("aggregate.assign_object", file, relating_object=project, product=site)
run("aggregate.assign_object", file, relating_object=site, product=building)
run("aggregate.assign_object", file, relating_object=building, product=storey)
# Create material constituent set with 2 materials : Glass and Wood
material_set = run(
"material.add_material_set", file, **{"name": "WindowSet", "set_type": "IfcMaterialConstituentSet"}
)
material_glass = run("material.add_material", file, name="Glass")
run("material.add_constituent", file, **{"constituent_set": material_set, "material": material_glass})
material_wood = run("material.add_material", file, name="Wood")
run("material.add_constituent", file, **{"constituent_set": material_set, "material": material_wood})
# Create a window occurrence, setup its representation
product = run(
"root.create_entity",
file,
ifc_class="IfcWindow",
predefined_type="WINDOW",
name="Window",
)
representation = get_brep_representation(file, body)
run(
"geometry.assign_representation",
file,
product=product,
representation=representation,
)
edit_object_placement(file, product)
run("spatial.assign_container", file, relating_structure=storey, product=product)
# Add the constituent set to the window
file.createIfcRelAssociatesMaterial(
ifcopenshell.guid.new(),
None,
None,
None,
[product],
material_set,
)
file.write(filepath)
Comments
As I understand correctly


IfcMaterialConstituentSet
can be used to assigne multiple IfcMaterials to several parts of the Geometry of just one IfcElement?I modelled two cubes and exported them as one IfcWindow:
I see BlenderBIM has a button to change the order of the IfcMaterialConstituentSet, but I don't see a menu to assign the material to a geometry representation.
I opened this file in BIMVision too, it shows up as an IfcMaterialLayer, is that correct?
Looking into the IFC itself I see this
I don't even know how to hack into the IFC file to assign the correct material to one geometry representation. Wish I could be of some more use.
Hehe thank you for that, you are helpful ! Simplifying down a problem to find the common denominator is almost always the most sane way to solve it :) I am pretty sure there is no UI in the BlenderBIM Addon to do that, but I know there is one in the IfcOpenShell API, that I have just not found yet.
I do have a file exported from Revit that seems to implement it correctly and that passes the roundtripping unharmed. I'll have a look inside and share it here for whom it may interest.
Nice. Also pushed a test here, too, from Revit that works when imported into BB.
Not sure the subutlies, however, of how to recreate this, via the UI, in BB... if even possible.
So it seems I have made quite a breakthrough with IfcShapeAspect which looks like it is used to map the correct materials to the correct representations. Since the representation is decomposed with items relating to the material index, it should be straightforward to map it correctly. However I still haven't found a way. I don't fully understand the diagram in the docs
Here's how I would naively implement it :
IfcShapeAspect: one entity to rule them all, one entity to find them, one entity to bring them all, and in the darkness bind them.
Relative to how it seems most entities are related to one another, that "Correlation by Name" seems tenuous and unusual.
Perhaps i'm missing something.
@Coen, I might have misunderstood this comment (and you might know how to do this already) but this is how you apply a material to different geometries in one mesh.
https://www.dropbox.com/s/g2olabxw0cz5n6b/2022-12-01_16-04-09_Blender_blender.mp4?dl=0
Awesome ! I actually was wrong in saying BlenderBIM doesn't offer a way to do it, it does it automagically witout any special UI, which is great ! I have to say I'm not a big fan of linking entities by name either with IfcShapeAspect, it seems very error-prone.
BlenderBIM doesn't use IfcShapeAspect but IfcStyledItem which I still don't understand the difference between the two but it looks like it is more straightforward to use.
It seems the constituent and the surface style actually don't keep a reference between themselves, so I'm wondering if in theory the assigned materials can be different from the material constituents ?
Edit : The answer is YES. Hmm.
FWIW I'll share the equivalent file from your video, which I'll dig into to find a solution.
So... Success ?
It seems
"style.add_surface_style"
automatically adds anIfcStyledItem
in the file in addtion to the surface style parameters. I just had to fetch the correct one in myfor
loop and assign it to the brep entity.Then
Or so I thought. It only applies the material to one of the faces ??
Input

Output

Okay, Color me ashamed...
This is once again a lesson to everyone to not try to be smarter than other people when "copying" their code. I have found my error and it is dumb.
I thought this could be optimised
into this
ifc_raw_items = [[]] * max(1, len(obj_blender_evaluated.material_slots))
But alas these are not equivalent. The first script creates a list of pointers to independent empty lists. The second one creates a list of pointers to a single, shared, empty list.
ifc_raw_items[0]
andifc_raw_items [1]
actually point to the same list, so appending an item to one appends it to the second, since they're the same object.Naturally, it leads to funky behaviour when you're trying to reconstruct geometry :)
Fixing this, I arrived to my goal. Revit doesn't even complain :) I'll clean a bit my finalised code and share it afterwards.
Success !
Here's the full script
related conversation around IfcShapeAspect, for better or worse: https://forums.buildingsmart.org/t/ifcshapeaspect/4342
Hehe thanks @theoryshaw, so I guess the answer is to use an aggregate if one wants to define an element composed with different materials... I wonder how this will pass the roundtripping test, especially to other softs like Revit or Archicad...
For my understanding, in theory this could be used to assign a different material to each of the six faces of a simple IfcWall?
@Coen yes I think so, as long as each face is defined using its own representation (or brep definition).
However I think it would be illegal regarding the ifc schema because you would have an association of non-manifold (non-watertight) meshes.
For anyone interested Dion added a very thorough documentation to the ifcopenshell material API
https://github.com/IfcOpenShell/IfcOpenShell/commit/cf87852d9ce442e59fe3f08dd44553b59afd41a5