From d03685abfa6aea5787b0256cb9470b80bbe2da28 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 8 Feb 2022 15:32:03 +1100 Subject: [PATCH] UV coordinates for triangulated face sets are now saved in IFC. --- .../bim/module/geometry/operator.py | 5 ++-- src/blenderbim/blenderbim/core/geometry.py | 1 + src/blenderbim/blenderbim/core/tool.py | 1 + src/blenderbim/blenderbim/tool/geometry.py | 9 ++++++ src/blenderbim/test/core/test_geometry.py | 4 +++ src/blenderbim/test/tool/test_geometry.py | 27 +++++++++++++++++ .../api/geometry/add_representation.py | 30 ++++++++++++++++++- 7 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 07858b5a53..5660ee76b3 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -188,8 +188,9 @@ class UpdateRepresentation(bpy.types.Operator): "geometry": obj.data, "coordinate_offset": coordinate_offset, "total_items": max(1, len(obj.material_slots)), - "should_force_faceted_brep": context.scene.BIMGeometryProperties.should_force_faceted_brep, - "should_force_triangulation": context.scene.BIMGeometryProperties.should_force_triangulation, + "should_force_faceted_brep": tool.Geometry.should_force_faceted_brep(), + "should_force_triangulation": tool.Geometry.should_force_triangulation(), + "should_generate_uvs": tool.Geometry.should_generate_uvs(obj), "ifc_representation_class": self.ifc_representation_class, } diff --git a/src/blenderbim/blenderbim/core/geometry.py b/src/blenderbim/blenderbim/core/geometry.py index 75420301d3..c2329e44dd 100644 --- a/src/blenderbim/blenderbim/core/geometry.py +++ b/src/blenderbim/blenderbim/core/geometry.py @@ -50,6 +50,7 @@ def add_representation( total_items=geometry.get_total_representation_items(obj), should_force_faceted_brep=geometry.should_force_faceted_brep(), should_force_triangulation=geometry.should_force_triangulation(), + should_generate_uvs=geometry.should_generate_uvs(obj), ifc_representation_class=ifc_representation_class, profile_set_usage=profile_set_usage, ) diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 2401ce8882..503295de3c 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -217,6 +217,7 @@ class Geometry: def run_style_add_style(cls, obj=None): pass def should_force_faceted_brep(cls): pass def should_force_triangulation(cls): pass + def should_generate_uvs(cls, obj): pass def should_use_presentation_style_assignment(cls): pass diff --git a/src/blenderbim/blenderbim/tool/geometry.py b/src/blenderbim/blenderbim/tool/geometry.py index 6a07ad5ab6..1b4011d4e7 100644 --- a/src/blenderbim/blenderbim/tool/geometry.py +++ b/src/blenderbim/blenderbim/tool/geometry.py @@ -298,6 +298,15 @@ class Geometry(blenderbim.core.tool.Geometry): def should_force_triangulation(cls): return bpy.context.scene.BIMGeometryProperties.should_force_triangulation + @classmethod + def should_generate_uvs(cls, obj): + for slot in obj.material_slots: + if slot.material and slot.material.use_nodes: + for node in slot.material.node_tree.nodes: + if node.type == "TEX_COORD" and node.outputs['UV'].links: + return True + return False + @classmethod def should_use_presentation_style_assignment(cls): return bpy.context.scene.BIMGeometryProperties.should_use_presentation_style_assignment diff --git a/src/blenderbim/test/core/test_geometry.py b/src/blenderbim/test/core/test_geometry.py index 5f218a3b3b..bb3da2f854 100644 --- a/src/blenderbim/test/core/test_geometry.py +++ b/src/blenderbim/test/core/test_geometry.py @@ -45,6 +45,7 @@ class TestAddRepresentation: geometry.get_total_representation_items("obj").should_be_called().will_return(1) geometry.should_force_faceted_brep().should_be_called().will_return(False) geometry.should_force_triangulation().should_be_called().will_return(True) + geometry.should_generate_uvs("obj").should_be_called().will_return(True) ifc.run( "geometry.add_representation", context="context", @@ -54,6 +55,7 @@ class TestAddRepresentation: total_items=1, should_force_faceted_brep=False, should_force_triangulation=True, + should_generate_uvs=True, ifc_representation_class="ifc_representation_class", profile_set_usage="profile_set_usage", ).should_be_called().will_return("representation") @@ -109,6 +111,7 @@ class TestAddRepresentation: geometry.get_total_representation_items("obj").should_be_called().will_return(1) geometry.should_force_faceted_brep().should_be_called().will_return(False) geometry.should_force_triangulation().should_be_called().will_return(True) + geometry.should_generate_uvs("obj").should_be_called().will_return(True) ifc.run( "geometry.add_representation", context="context", @@ -118,6 +121,7 @@ class TestAddRepresentation: total_items=1, should_force_faceted_brep=False, should_force_triangulation=True, + should_generate_uvs=True, ifc_representation_class="ifc_representation_class", profile_set_usage="profile_set_usage", ).should_be_called().will_return("representation") diff --git a/src/blenderbim/test/tool/test_geometry.py b/src/blenderbim/test/tool/test_geometry.py index 6f1e1ce21f..26f8aba986 100644 --- a/src/blenderbim/test/tool/test_geometry.py +++ b/src/blenderbim/test/tool/test_geometry.py @@ -462,6 +462,33 @@ class TestShouldForceTriangulation(NewFile): assert subject.should_force_triangulation() is result +class TestShouldGenerateUVs(NewFile): + def test_needs_mesh_data(self): + obj = bpy.data.objects.new("Object", None) + assert subject.should_generate_uvs(obj) is False + + def test_needs_nodes(self): + obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh")) + material = bpy.data.materials.new("Material") + obj.data.materials.append(material) + material.use_nodes = False + assert subject.should_generate_uvs(obj) is False + + def test_needs_texture_coordinates_with_a_uv_output(self): + obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh")) + material = bpy.data.materials.new("Material") + obj.data.materials.append(material) + material.use_nodes = True + + bsdf = material.node_tree.nodes["Principled BSDF"] + node = material.node_tree.nodes.new(type="ShaderNodeTexImage") + material.node_tree.links.new(bsdf.inputs["Base Color"], node.outputs["Color"]) + + coords = material.node_tree.nodes.new(type="ShaderNodeTexCoord") + material.node_tree.links.new(node.inputs["Vector"], coords.outputs["UV"]) + assert subject.should_generate_uvs(obj) is True + + class TestShouldUsePresentationStyleAssignment(NewFile): def test_run(self): result = bpy.context.scene.BIMGeometryProperties.should_use_presentation_style_assignment diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py index 79d207adef..9bac3ba502 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/add_representation.py @@ -40,6 +40,7 @@ class Usecase: "unit_scale": None, # A scale factor to apply for all vectors in case the unit is different "should_force_faceted_brep": False, # If we should force faceted breps for meshes "should_force_triangulation": False, # If we should force triangulation for meshes + "should_generate_uvs": False, # If UV coordinates should also be generated # Possible IFC representation classes: # IfcExtrudedAreaSolid/IfcRectangleProfileDef # IfcExtrudedAreaSolid/IfcCircleProfileDef @@ -505,16 +506,43 @@ class Usecase: def create_triangulated_face_set(self): ifc_raw_items = [None] * self.settings["total_items"] + if self.settings["should_generate_uvs"]: + ifc_raw_uv_items = [None] * self.settings["total_items"] for i, value in enumerate(ifc_raw_items): ifc_raw_items[i] = [] + if self.settings["should_generate_uvs"]: + ifc_raw_uv_items[i] = [] for polygon in self.settings["geometry"].polygons: ifc_raw_items[polygon.material_index % self.settings["total_items"]].append( [v + 1 for v in polygon.vertices] ) + if self.settings["should_generate_uvs"]: + ifc_raw_uv_items[polygon.material_index % self.settings["total_items"]].append( + [uv + 1 for uv in polygon.loop_indices] + ) + coordinates = self.file.createIfcCartesianPointList3D( [self.convert_si_to_unit(v.co) for v in self.settings["geometry"].vertices] ) - items = [self.file.createIfcTriangulatedFaceSet(coordinates, None, None, i) for i in ifc_raw_items if i] + + if self.settings["should_generate_uvs"]: + # Blender supports multiple UV layers. We don't. Too bad. + tex_coords = self.file.createIfcTextureVertexList( + [tuple(x.uv) for x in self.settings["geometry"].uv_layers[0].data] + ) + items = [] + for i, coord_index in enumerate(ifc_raw_items): + if not coord_index: + continue + tex_coords_index = ifc_raw_uv_items[i] + face_set = self.file.createIfcTriangulatedFaceSet(coordinates, None, None, coord_index) + texture_map = self.file.createIfcIndexedTriangleTextureMap( + MappedTo=face_set, TexCoords=tex_coords, TexCoordIndex=tex_coords_index + ) + items.append(face_set) + else: + items = [self.file.createIfcTriangulatedFaceSet(coordinates, None, None, i) for i in ifc_raw_items if i] + return self.file.createIfcShapeRepresentation( self.settings["context"], self.settings["context"].ContextIdentifier,