Support for loading face colors from ifc (IfcIndexedColourMap)

example - https://imgur.com/a/l8Yn4Rr
This commit is contained in:
Andrej730
2024-09-16 14:58:11 +05:00
parent 6d8ffc27cc
commit c75fa70d23
4 changed files with 120 additions and 51 deletions
+7 -1
View File
@@ -111,7 +111,7 @@ class MaterialCreator:
material = self.styles[style_or_material_id]
if coords := self.get_ifc_coordinate(material):
tool.Loader.load_indexed_texture_map(coords, self.mesh)
tool.Loader.load_indexed_map(coords, self.mesh)
def assign_material_slots_to_faces(self) -> None:
if not self.mesh["ios_materials"]:
@@ -1382,6 +1382,12 @@ class IfcImporter:
mesh.polygons.foreach_set("loop_total", loop_total)
mesh.polygons.foreach_set("use_smooth", [0] * total_faces)
mesh.update()
# TODO: geometry id is not always an int.
rep_id = geometry.id
if rep_id.isdigit():
rep = self.file.by_id(int(rep_id))
tool.Loader.load_indexed_colour_map(rep, mesh)
else:
e = geometry.edges
v = verts
+1
View File
@@ -648,6 +648,7 @@ class Geometry(bonsai.core.tool.Geometry):
ifc_importer.material_creator.load_existing_materials()
ifc_importer.material_creator.create(element, obj, mesh)
mesh.BIMMeshProperties.has_openings_applied = apply_openings
tool.Loader.load_indexed_colour_map(representation, mesh)
return mesh
+72 -21
View File
@@ -472,20 +472,47 @@ class Loader(bonsai.core.tool.Loader):
blender_material.node_tree.links.new(coord.outputs["UV"], node.inputs["Vector"])
@classmethod
def load_indexed_texture_map(cls, coordinates: ifcopenshell.entity_instance, mesh: bpy.types.Mesh) -> None:
def load_indexed_colour_map(cls, representation: ifcopenshell.entity_instance, mesh: bpy.types.Mesh) -> None:
if representation.RepresentationType != "Tessellation":
return
colours = []
for item in representation.Items:
if not item.is_a("IfcTessellatedFaceSet"):
continue
colours.extend(item.HasColours)
if not colours:
return
for colour in colours:
cls.load_indexed_map(colour, mesh)
@classmethod
def load_indexed_map(cls, index_map: ifcopenshell.entity_instance, mesh: bpy.types.Mesh) -> None:
"""Add data from index map as blender mesh attribute.
:param index_map: IfcIndexedTextureMap or IfcIndexedColourMap
"""
map_type = "UV" if index_map.is_a("IfcIndexedTextureMap") else "Color"
# Get a BMesh representation
bm = bmesh.new()
bm.from_mesh(mesh)
# constistent naming with how Blender does it
uv_layer = bm.loops.layers.uv.active or bm.loops.layers.uv.new("UVMap")
if map_type == "UV":
# constistent naming with how Blender does it
layer = bm.loops.layers.uv.active or bm.loops.layers.uv.new("UVMap")
else:
layer = bm.loops.layers.float_color.new("Color")
# remap the faceset CoordList index to the vertices in blender mesh
coordinates_remap = []
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
faceset = coordinates.MappedTo
faceset = index_map.MappedTo
for co in faceset.Coordinates.CoordList:
co = Vector(co) * si_conversion
index = next(v.index for v in bm.verts if (v.co - co).length_squared < 1e-5)
index = min(bm.verts, key=lambda v: (v.co - co).length_squared).index
coordinates_remap.append(index)
# ifc indices start with 1
@@ -493,37 +520,61 @@ class Loader(bonsai.core.tool.Loader):
# faces_remap - ifc faces described using blender verts indices
# IFC4.3+
if coordinates.is_a("IfcIndexedPolygonalTextureMap"):
if index_map.is_a("IfcIndexedPolygonalTextureMap"):
faces_remap = [
remap_verts_to_blender(tex_coord_index.TexCoordsOf.CoordIndex)
for tex_coord_index in coordinates.TexCoordIndices
for tex_coord_index in index_map.TexCoordIndices
]
texture_map = [tex_coord_index.TexCoordIndex for tex_coord_index in coordinates.TexCoordIndices]
else: # IfcIndexedTriangleTextureMap
texture_map = [tex_coord_index.TexCoordIndex for tex_coord_index in index_map.TexCoordIndices]
else: # IfcIndexedTriangleTextureMap or IfcIndexedColourMap
if faceset.is_a("IfcTriangulatedFaceSet"):
faces_remap = [remap_verts_to_blender(triangle_face) for triangle_face in faceset.CoordIndex]
else: # IfcPolygonalFaceSet
faces_remap = [remap_verts_to_blender(triangle_face.CoordIndex) for triangle_face in faceset.Faces]
texture_map = coordinates.TexCoordIndex
faces_remap = [remap_verts_to_blender(face.CoordIndex) for face in faceset.Faces]
if index_map.is_a("IfcIndexedTriangleTextureMap"):
texture_map = index_map.TexCoordIndex
else:
texture_map = index_map.ColourIndex
# apply uv to each face
if map_type == "UV":
data_list = index_map.TexCoords.TexCoordsList
else:
data_list = index_map.Colours.ColourList
opacity = index_map.Opacity
opacity = opacity if opacity is not None else 1.0
data_list = [d + (opacity,) for d in data_list]
# Apply attribute to each face
for bface in bm.faces:
face = [loop.vert.index for loop in bface.loops]
# find the corresponding TexCoordIndex by matching ifc faceset with blender face
# remap TexCoordIndex as the loop start may different from blender face
texCoordIndex = next(
[tex_coord_index[face_remap.index(i)] for i in face]
for tex_coord_index, face_remap in zip(texture_map, faces_remap, strict=True)
if all(i in face_remap for i in face)
)
# Find the corresponding index in data list by matching ifc faceset with blender face.
data_index = None
for tex_coord_index, face_remap in zip(texture_map, faces_remap, strict=True):
if not all(i in face_remap for i in face):
continue
# Subtract 1 as tex_coord_index starts with 1.
if map_type == "UV":
data_index = [tex_coord_index[face_remap.index(i)] - 1 for i in face]
else:
data_index = [tex_coord_index - 1 for i in face]
break
assert data_index is not None
# apply uv to each loop
for loop, i in zip(bface.loops, texCoordIndex):
loop[uv_layer].uv = coordinates.TexCoords.TexCoordsList[i - 1]
for loop, i in zip(bface.loops, data_index):
if map_type == "UV":
loop[layer].uv = data_list[i]
else:
loop[layer] = data_list[i]
# Finish up, write the bmesh back to the mesh
bm.to_mesh(mesh)
bm.free()
if map_type == "Color":
# Couldn't find a way to do it from bmesh.
mesh.color_attributes.active_color_index = 0
@classmethod
def is_point_far_away(
cls, point: Union[ifcopenshell.entity_instance, npt.NDArray[np.float64]], is_meters: bool = True
+40 -29
View File
@@ -391,12 +391,11 @@ class TestCreatingStyles(NewFile):
), f"Failed to match pixels for {n_components}.\nBlender pixel_data: {image_node.image.pixels[:]}.\nExpected data: {expected_pixel_data}"
class TestLoadingIndexedTextureMap(NewFile):
def test_run(self):
class TestLoadingIndexedMap(NewFile):
def test_load_texture_map(self):
bpy.context.scene.unit_settings.length_unit = "MILLIMETERS"
bpy.ops.bim.create_project()
# TODO: replace with loading geometry from IFC
# create a cube without uv and triangulate it
mesh = bpy.data.meshes.new("Cube")
bm = tool.Blender.get_bmesh_for_mesh(mesh, clean=True)
@@ -408,31 +407,7 @@ class TestLoadingIndexedTextureMap(NewFile):
builder = ShapeBuilder(ifc_file)
# tesselated cube
points = (
V(-1000, -1000, -1000),
V(-1000, -1000, 1000),
V(-1000, 1000, -1000),
V(-1000, 1000, 1000),
V(1000, -1000, -1000),
V(1000, -1000, 1000),
V(1000, 1000, -1000),
V(1000, 1000, 1000),
)
faces = (
(1, 2, 0),
(3, 6, 2),
(7, 4, 6),
(5, 0, 4),
(6, 0, 2),
(3, 5, 7),
(1, 3, 2),
(3, 7, 6),
(7, 5, 4),
(5, 1, 0),
(6, 4, 0),
(3, 1, 5),
)
face_set = builder.polygonal_face_set(points, faces)
face_set = builder.polygonal_face_set([v.co for v in mesh.vertices], [p.vertices[:] for p in mesh.polygons])
uv_indices = (
(1, 2, 3),
@@ -493,9 +468,45 @@ class TestLoadingIndexedTextureMap(NewFile):
texture_coord.TexCoordIndex = uv_indices
texture_coord.TexCoords = uv_verts_list
subject.load_indexed_texture_map(texture_coord, mesh)
subject.load_indexed_map(texture_coord, mesh)
uv_layer = mesh.uv_layers.active
assert uv_layer is not None
for ifc_uv, blender_uv in zip(uv_verts, uv_layer.uv, strict=True):
assert tool.Cad.are_vectors_equal(Vector(ifc_uv), blender_uv.vector)
def test_load_color_map(self):
bpy.context.scene.unit_settings.length_unit = "MILLIMETERS"
bpy.ops.bim.create_project()
# create a cube without uv and triangulate it
mesh = bpy.data.meshes.new("Cube")
bm = tool.Blender.get_bmesh_for_mesh(mesh, clean=True)
bmesh.ops.create_cube(bm, size=2.0, calc_uvs=False)
bmesh.ops.triangulate(bm, faces=bm.faces[:])
tool.Blender.apply_bmesh(mesh, bm)
ifc_file = tool.Ifc.get()
builder = ShapeBuilder(ifc_file)
# tesselated cube
face_set = builder.polygonal_face_set([v.co for v in mesh.vertices], [p.vertices[:] for p in mesh.polygons])
colors = ((1.0, 0.0, 0.0), (0.0, 0.5, 0.0), (1.0, 1.0, 0.0))
color_index = (0, 0, 0, 0, 2, 1, 0, 0, 0, 0, 2, 1)
ifc_colors_list = ifc_file.create_entity("IfcColourRgbList", colors)
colour_map = ifc_file.create_entity("IfcIndexedColourMap")
colour_map.MappedTo = face_set
colour_map.Colours = ifc_colors_list
colour_map.ColourIndex = [i + 1 for i in color_index]
subject.load_indexed_map(colour_map, mesh)
layer = mesh.color_attributes.active_color
assert layer is not None
for face_i, face in enumerate(mesh.polygons):
for loop_i in face.loop_indices:
blender_color = Vector(layer.data[loop_i].color)
ifc_color = Vector(colors[color_index[face_i]] + (1.0,))
assert tool.Cad.are_vectors_equal(blender_color, ifc_color)