From 18c93a13ec0316cbfc4ff4a003b0fff77867d98b Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Tue, 28 Nov 2023 15:33:30 +0500 Subject: [PATCH] moved load_texture_map to tool.Loader, added a test --- src/blenderbim/blenderbim/bim/import_ifc.py | 53 +-------- src/blenderbim/blenderbim/tool/loader.py | 53 +++++++++ src/blenderbim/test/tool/test_loader.py | 113 ++++++++++++++++++++ 3 files changed, 167 insertions(+), 52 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index d6941aa95b..dbac12a5a8 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -143,62 +143,11 @@ class MaterialCreator: return if coords := get_ifc_coordinate(material): - self.load_texture_map(coords) + tool.Loader.load_indexed_texture_map(coords, self.mesh) if self.mesh.materials.find(material.name) == -1: self.mesh.materials.append(material) return True - def load_texture_map(self, coordinates): - # Get a BMesh representation - bm = bmesh.new() - bm.from_mesh(self.mesh) - uv_layer = bm.loops.layers.uv.verify() - - # remap the faceset CoordList index to the vertices in blender mesh - coordinates_remap = [] - si_conversion = ifcopenshell.util.unit.calculate_unit_scale(self.ifc_importer.file) - faceset = coordinates.MappedTo - for co in faceset.Coordinates.CoordList: - co = mathutils.Vector(co) * si_conversion - index = next(v.index for v in bm.verts if (v.co - co).length_squared < 1e-5) - coordinates_remap.append(index) - - # ifc indices start with 1 - remap_verts_to_blender = lambda ifc_verts: [coordinates_remap[i - 1] for i in ifc_verts] - - # faces_remap - ifc faces described using blender verts indices - # IFC4.3+ - if coordinates.is_a("IfcIndexedPolygonalTextureMap"): - faces_remap = [ - remap_verts_to_blender(tex_coord_index.TexCoordsOf.CoordIndex) - for tex_coord_index in coordinates.TexCoordIndices - ] - texture_map = [tex_coord_index.TexCoordIndex for tex_coord_index in coordinates.TexCoordIndices] - else: # IfcIndexedTriangleTextureMap - 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 - - # apply uv 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) - ) - # apply uv to each loop - for loop, i in zip(bface.loops, texCoordIndex): - loop[uv_layer].uv = coordinates.TexCoords.TexCoordsList[i - 1] - - # Finish up, write the bmesh back to the mesh - bm.to_mesh(self.mesh) - bm.free() - def assign_material_slots_to_faces(self): if "ios_materials" not in self.mesh or not self.mesh["ios_materials"]: return diff --git a/src/blenderbim/blenderbim/tool/loader.py b/src/blenderbim/blenderbim/tool/loader.py index 9b00453bdb..41ba64fe71 100644 --- a/src/blenderbim/blenderbim/tool/loader.py +++ b/src/blenderbim/blenderbim/tool/loader.py @@ -18,6 +18,7 @@ import re import bpy +import bmesh import ifcopenshell.util.element import blenderbim.core.tool import blenderbim.tool as tool @@ -380,3 +381,55 @@ class Loader(blenderbim.core.tool.Loader): blender_material.node_tree.links.new(coord.outputs["Camera"], node.inputs["Vector"]) else: # uv_mode == UV blender_material.node_tree.links.new(coord.outputs["UV"], node.inputs["Vector"]) + + @classmethod + def load_indexed_texture_map(cls, coordinates, mesh): + # Get a BMesh representation + bm = bmesh.new() + bm.from_mesh(mesh) + uv_layer = bm.loops.layers.uv.verify() + + # 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 + 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) + coordinates_remap.append(index) + + # ifc indices start with 1 + remap_verts_to_blender = lambda ifc_verts: [coordinates_remap[i - 1] for i in ifc_verts] + + # faces_remap - ifc faces described using blender verts indices + # IFC4.3+ + if coordinates.is_a("IfcIndexedPolygonalTextureMap"): + faces_remap = [ + remap_verts_to_blender(tex_coord_index.TexCoordsOf.CoordIndex) + for tex_coord_index in coordinates.TexCoordIndices + ] + texture_map = [tex_coord_index.TexCoordIndex for tex_coord_index in coordinates.TexCoordIndices] + else: # IfcIndexedTriangleTextureMap + 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 + + # apply uv 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) + ) + # apply uv to each loop + for loop, i in zip(bface.loops, texCoordIndex): + loop[uv_layer].uv = coordinates.TexCoords.TexCoordsList[i - 1] + + # Finish up, write the bmesh back to the mesh + bm.to_mesh(mesh) + bm.free() diff --git a/src/blenderbim/test/tool/test_loader.py b/src/blenderbim/test/tool/test_loader.py index f10e0a3341..3ea3b59a5d 100644 --- a/src/blenderbim/test/tool/test_loader.py +++ b/src/blenderbim/test/tool/test_loader.py @@ -17,12 +17,15 @@ # along with BlenderBIM Add-on. If not, see . import bpy +import bmesh import ifcopenshell import blenderbim.core.tool import blenderbim.tool as tool from test.bim.bootstrap import NewFile from blenderbim.tool.loader import Loader as subject import numpy as np +from ifcopenshell.util.shape_builder import ShapeBuilder, V +from mathutils import Vector class TestImplementsTool(NewFile): @@ -78,3 +81,113 @@ class TestCreatingStyles(NewFile): image_node = tool.Blender.get_material_node(material, "TEX_IMAGE") assert image_node.outputs["Color"].links[0].to_socket.name == "Base Color" assert image_node.inputs["Vector"].links[0].from_socket.name == "Generated" + + +class TestLoadingIndexedTextureMap(NewFile): + def test_run(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) + 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 + 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) + + uv_indices = ( + (1, 2, 3), + (4, 5, 6), + (7, 8, 9), + (10, 11, 12), + (13, 14, 15), + (16, 17, 18), + (19, 20, 21), + (22, 23, 24), + (25, 26, 27), + (28, 29, 30), + (31, 32, 33), + (34, 35, 36), + ) + uv_verts = ( + (0.625, 0.0), + (0.375, 0.25), + (0.375, 0.0), + (0.625, 0.25), + (0.375, 0.5), + (0.375, 0.25), + (0.625, 0.5), + (0.375, 0.75), + (0.375, 0.5), + (0.625, 0.75), + (0.375, 1.0), + (0.375, 0.75), + (0.375, 0.5), + (0.125, 0.75), + (0.125, 0.5), + (0.875, 0.5), + (0.625, 0.75), + (0.625, 0.5), + (0.625, 0.0), + (0.625, 0.25), + (0.375, 0.25), + (0.625, 0.25), + (0.625, 0.5), + (0.375, 0.5), + (0.625, 0.5), + (0.625, 0.75), + (0.375, 0.75), + (0.625, 0.75), + (0.625, 1.0), + (0.375, 1.0), + (0.375, 0.5), + (0.375, 0.75), + (0.125, 0.75), + (0.875, 0.5), + (0.875, 0.75), + (0.625, 0.75), + ) + uv_verts_list = ifc_file.createIfcTextureVertexList() + uv_verts_list.TexCoordsList = uv_verts + texture_coord = ifc_file.createIfcIndexedTriangleTextureMap() + texture_coord.MappedTo = face_set + texture_coord.TexCoordIndex = uv_indices + texture_coord.TexCoords = uv_verts_list + + subject.load_indexed_texture_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)