From dd7a47ab9967bac24fc85011794c274aff4c5bc2 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 9 Feb 2022 11:34:59 +1100 Subject: [PATCH] Texture support is now compatible with glTF texture maps --- .../blenderbim/bim/module/style/__init__.py | 1 + .../blenderbim/bim/module/style/operator.py | 16 ++ .../blenderbim/bim/module/style/ui.py | 1 + src/blenderbim/blenderbim/core/style.py | 27 +-- src/blenderbim/blenderbim/core/tool.py | 3 +- src/blenderbim/blenderbim/tool/geometry.py | 2 + src/blenderbim/blenderbim/tool/style.py | 88 ++-------- src/blenderbim/test/core/test_style.py | 91 +++++----- src/blenderbim/test/tool/test_geometry.py | 8 + src/blenderbim/test/tool/test_style.py | 150 ++--------------- .../api/geometry/remove_representation.py | 9 +- .../api/style/add_surface_style.py | 2 +- .../api/style/add_surface_textures.py | 158 ++++++++++++------ .../ifcopenshell/api/style/remove_style.py | 4 +- .../api/style/remove_surface_style.py | 46 +++++ .../geometry/test_remove_representation.py | 31 +++- .../test/api/style/test_remove_style.py | 36 ++++ .../api/style/test_remove_surface_style.py | 41 +++++ .../test/util/test_element.py | 2 +- 19 files changed, 380 insertions(+), 336 deletions(-) create mode 100644 src/ifcopenshell-python/ifcopenshell/api/style/remove_surface_style.py create mode 100644 src/ifcopenshell-python/test/api/style/test_remove_style.py create mode 100644 src/ifcopenshell-python/test/api/style/test_remove_surface_style.py diff --git a/src/blenderbim/blenderbim/bim/module/style/__init__.py b/src/blenderbim/blenderbim/bim/module/style/__init__.py index 086450e2ea..159b919712 100644 --- a/src/blenderbim/blenderbim/bim/module/style/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/style/__init__.py @@ -26,6 +26,7 @@ classes = ( operator.EditStyle, operator.RemoveStyle, operator.UpdateStyleColours, + operator.UpdateStyleTextures, operator.UnlinkStyle, prop.BIMStyleProperties, ui.BIM_PT_style, diff --git a/src/blenderbim/blenderbim/bim/module/style/operator.py b/src/blenderbim/blenderbim/bim/module/style/operator.py index fdd23e47e7..174f7e16d5 100644 --- a/src/blenderbim/blenderbim/bim/module/style/operator.py +++ b/src/blenderbim/blenderbim/bim/module/style/operator.py @@ -20,6 +20,7 @@ import bpy import blenderbim.bim.handler import blenderbim.tool as tool import blenderbim.core.style as core +import ifcopenshell.util.representation from blenderbim.bim.ifc import IfcStore @@ -39,6 +40,21 @@ class UpdateStyleColours(bpy.types.Operator, Operator): core.update_style_colours(tool.Ifc, tool.Style, obj=context.active_object.active_material) +class UpdateStyleTextures(bpy.types.Operator, Operator): + bl_idname = "bim.update_style_textures" + bl_label = "Update Style Textures" + bl_options = {"REGISTER", "UNDO"} + + def _execute(self, context): + representation = ifcopenshell.util.representation.get_representation( + tool.Ifc.get_entity(context.active_object), "Model", "Body", "MODEL_VIEW" + ) + if representation: + core.update_style_textures( + tool.Ifc, tool.Style, obj=context.active_object.active_material, representation=representation + ) + + class RemoveStyle(bpy.types.Operator, Operator): bl_idname = "bim.remove_style" bl_label = "Remove Style" diff --git a/src/blenderbim/blenderbim/bim/module/style/ui.py b/src/blenderbim/blenderbim/bim/module/style/ui.py index e1e91d064d..abd2ce812c 100644 --- a/src/blenderbim/blenderbim/bim/module/style/ui.py +++ b/src/blenderbim/blenderbim/bim/module/style/ui.py @@ -42,6 +42,7 @@ class BIM_PT_style(Panel): row = self.layout.row(align=True) if props.ifc_style_id: row.operator("bim.update_style_colours", icon="GREASEPENCIL") + row.operator("bim.update_style_textures", icon="TEXTURE", text="") row.operator("bim.unlink_style", icon="UNLINKED", text="") row.operator("bim.remove_style", icon="X", text="") else: diff --git a/src/blenderbim/blenderbim/core/style.py b/src/blenderbim/blenderbim/core/style.py index b4e5c414cd..e3a26dfc65 100644 --- a/src/blenderbim/blenderbim/core/style.py +++ b/src/blenderbim/blenderbim/core/style.py @@ -59,20 +59,25 @@ def update_style_colours(ifc, style, obj=None): else: ifc.run("style.add_surface_style", style=element, ifc_class="IfcSurfaceStyleShading", attributes=attributes) + +def update_style_textures(ifc, style, obj=None, representation=None): + element = style.get_style(obj) + + uv_maps = style.get_uv_maps(representation) + textures = ifc.run("style.add_surface_textures", material=obj, uv_maps=uv_maps) texture_style = style.get_surface_texture_style(obj) - if style.can_support_texture_style(obj): - textures = ifc.run("style.add_surface_textures", textures=style.get_surface_textures(obj)) + + if textures: if texture_style: - ifc.run("style.edit_surface_style", style=texture_style, attributes={"Textures": textures}) - else: - ifc.run( - "style.add_surface_style", - style=element, - ifc_class="IfcSurfaceStyleWithTextures", - attributes={"Textures": textures}, - ) + ifc.run("style.remove_surface_style", style=texture_style) + ifc.run( + "style.add_surface_style", + style=element, + ifc_class="IfcSurfaceStyleWithTextures", + attributes={"Textures": textures}, + ) elif texture_style: - ifc.run("style.remove_style", style=texture_style) + ifc.run("style.remove_surface_style", style=texture_style) def unlink_style(ifc, style, obj=None): diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 503295de3c..4eb31bd230 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -396,7 +396,6 @@ class Structural: @interface class Style: def can_support_rendering_style(cls, obj): pass - def can_support_texture_style(cls, obj): pass def disable_editing(cls, obj): pass def enable_editing(cls, obj): pass def export_surface_attributes(cls, obj): pass @@ -408,7 +407,7 @@ class Style: def get_surface_shading_attributes(cls, obj): pass def get_surface_shading_style(cls, obj): pass def get_surface_texture_style(cls, obj): pass - def get_surface_textures(cls, obj): pass + def get_uv_maps(cls, representation): pass def import_surface_attributes(cls, style, obj): pass diff --git a/src/blenderbim/blenderbim/tool/geometry.py b/src/blenderbim/blenderbim/tool/geometry.py index 26c0369cec..b9d2ec10b8 100644 --- a/src/blenderbim/blenderbim/tool/geometry.py +++ b/src/blenderbim/blenderbim/tool/geometry.py @@ -300,6 +300,8 @@ class Geometry(blenderbim.core.tool.Geometry): @classmethod def should_generate_uvs(cls, obj): + if tool.Ifc.get().schema == "IFC2X3": + return False for slot in obj.material_slots: if slot.material and slot.material.use_nodes: for node in slot.material.node_tree.nodes: diff --git a/src/blenderbim/blenderbim/tool/style.py b/src/blenderbim/blenderbim/tool/style.py index 9ea1647d17..7e6fed32a0 100644 --- a/src/blenderbim/blenderbim/tool/style.py +++ b/src/blenderbim/blenderbim/tool/style.py @@ -27,41 +27,6 @@ class Style(blenderbim.core.tool.Style): def can_support_rendering_style(cls, obj): return obj.use_nodes and hasattr(obj.node_tree, "nodes") - @classmethod - def can_support_texture_style(cls, obj): - if not obj.node_tree: - return False - - output = {n.type: n for n in obj.node_tree.nodes}.get("OUTPUT_MATERIAL", None) - if not output: - return False - - # For now, we assume only a single BSDF is allowed in our node tree. - try: - bsdf = output.inputs["Surface"].links[0].from_node - except: - return False - - # For a quick check whether we have a compatible node tree, we check - # whether or not we have at least one image texture node that has a - # texture assigned and outputs to the bsdf. - textures = [n for n in obj.node_tree.nodes if n.type == "TEX_IMAGE" and n.image] - - def does_texture_connect_to(node, target): - if node == target: - return True - try: - output = node.outputs[0].links[0].to_node - return does_texture_connect_to(output, target) - except: - return False - - for texture in textures: - if does_texture_connect_to(texture, bsdf): - return True - - return False - @classmethod def disable_editing(cls, obj): obj.BIMStyleProperties.is_editing = False @@ -174,7 +139,7 @@ class Style(blenderbim.core.tool.Style): def get_surface_shading_style(cls, obj): if obj.BIMMaterialProperties.ifc_style_id: style = tool.Ifc.get().by_id(obj.BIMMaterialProperties.ifc_style_id) - items = [s for s in style.Styles if s.is_a("IfcSurfaceStyleShading")] + items = [s for s in style.Styles if s.is_a() == "IfcSurfaceStyleShading"] if items: return items[0] @@ -187,47 +152,18 @@ class Style(blenderbim.core.tool.Style): return items[0] @classmethod - def get_surface_textures(cls, obj): - output = {n.type: n for n in obj.node_tree.nodes}.get("OUTPUT_MATERIAL", None) - bsdf = output.inputs["Surface"].links[0].from_node - node_mappings = { - "BSDF_GLOSSY": { - "DIFFUSE": "Color", - "SHININESS": "Roughness", - "NORMAL": "Normal", - }, - "BSDF_DIFFUSE": { - "DIFFUSE": "Color", - "SHININESS": "Roughness", - "NORMAL": "Normal", - }, - "BSDF_GLASS": { - "DIFFUSE": "Color", - "SHININESS": "Roughness", - "NORMAL": "Normal", - }, - "EMISSION": { - "DIFFUSE": "Color", - }, - "BSDF_PRINCIPLED": { - "DIFFUSE": "Base Color", - "SHININESS": "Roughness", - "NORMAL": "Normal", - "SPECULAR": "Specular", - "SELFILLUMINATION": "Emission Strength", - "OPACITY": "Alpha", - }, - } + def get_uv_maps(cls, representation): + items = [] + for item in representation.Items: + if item.is_a("IfcMappedItem"): + items.extend(item.MappingSource.MappedRepresentation.Items) + items.append(item) - maps = {} - if bsdf.type not in node_mappings: - return maps - - for map_type, input_name in node_mappings[bsdf.type].items(): - if bsdf.inputs[input_name].links: - maps[map_type] = bsdf.inputs[input_name].links[0].from_node - - return maps + results = [] + for item in items: + for uv_map in item.HasTextures or []: + results.append(uv_map) + return results @classmethod def import_surface_attributes(cls, style, obj): diff --git a/src/blenderbim/test/core/test_style.py b/src/blenderbim/test/core/test_style.py index 7ae606244f..25e761a3dd 100644 --- a/src/blenderbim/test/core/test_style.py +++ b/src/blenderbim/test/core/test_style.py @@ -32,8 +32,6 @@ class TestAddStyle: "style.add_surface_style", style="style", ifc_class="IfcSurfaceStyleRendering", attributes="attributes" ).should_be_called() - style.can_support_texture_style("obj").should_be_called().will_return(False) - ifc.get_entity("obj").should_be_called().will_return(None) assert subject.add_style(ifc, style, obj="obj") == "style" @@ -48,8 +46,6 @@ class TestAddStyle: "style.add_surface_style", style="style", ifc_class="IfcSurfaceStyleShading", attributes="attributes" ).should_be_called() - style.can_support_texture_style("obj").should_be_called().will_return(False) - ifc.get_entity("obj").should_be_called().will_return("material") style.get_context("obj").should_be_called().will_return("context") ifc.run("style.assign_material_style", material="material", style="style", context="context").should_be_called() @@ -66,93 +62,88 @@ class TestRemoveStyle: class TestUpdateStyleColours: def test_updating_rendering_style_if_available(self, ifc, style): - ifc.get_entity("obj").should_be_called().will_return("element") + style.get_style("obj").should_be_called().will_return("element") style.can_support_rendering_style("obj").should_be_called().will_return(True) style.get_surface_rendering_style("obj").should_be_called().will_return("style") style.get_surface_rendering_attributes("obj").should_be_called().will_return("attributes") ifc.run("style.edit_surface_style", style="style", attributes="attributes").should_be_called() - style.get_surface_texture_style("obj").should_be_called().will_return(None) - style.can_support_texture_style("obj").should_be_called().will_return(False) subject.update_style_colours(ifc, style, obj="obj") def test_adding_a_rendering_style_if_not_available(self, ifc, style): - ifc.get_entity("obj").should_be_called().will_return("element") + style.get_style("obj").should_be_called().will_return("element") style.can_support_rendering_style("obj").should_be_called().will_return(True) style.get_surface_rendering_style("obj").should_be_called().will_return(None) style.get_surface_rendering_attributes("obj").should_be_called().will_return("attributes") ifc.run( "style.add_surface_style", style="element", ifc_class="IfcSurfaceStyleRendering", attributes="attributes" ).should_be_called() - style.get_surface_texture_style("obj").should_be_called().will_return(None) - style.can_support_texture_style("obj").should_be_called().will_return(False) subject.update_style_colours(ifc, style, obj="obj") def test_updating_shading_style_as_a_fallback_if_available(self, ifc, style): - ifc.get_entity("obj").should_be_called().will_return("element") + style.get_style("obj").should_be_called().will_return("element") style.can_support_rendering_style("obj").should_be_called().will_return(False) style.get_surface_shading_style("obj").should_be_called().will_return("style") style.get_surface_shading_attributes("obj").should_be_called().will_return("attributes") ifc.run("style.edit_surface_style", style="style", attributes="attributes").should_be_called() - style.get_surface_texture_style("obj").should_be_called().will_return(None) - style.can_support_texture_style("obj").should_be_called().will_return(False) subject.update_style_colours(ifc, style, obj="obj") def test_adding_a_shading_style_as_a_fallback_if_not_available(self, ifc, style): - ifc.get_entity("obj").should_be_called().will_return("element") + style.get_style("obj").should_be_called().will_return("element") style.can_support_rendering_style("obj").should_be_called().will_return(False) style.get_surface_shading_style("obj").should_be_called().will_return(None) style.get_surface_shading_attributes("obj").should_be_called().will_return("attributes") ifc.run( "style.add_surface_style", style="element", ifc_class="IfcSurfaceStyleShading", attributes="attributes" ).should_be_called() - style.get_surface_texture_style("obj").should_be_called().will_return(None) - style.can_support_texture_style("obj").should_be_called().will_return(False) subject.update_style_colours(ifc, style, obj="obj") - def test_updating_texture_style_if_available(self, ifc, style): - ifc.get_entity("obj").should_be_called().will_return("element") - style.can_support_rendering_style("obj").should_be_called().will_return(False) - style.get_surface_shading_style("obj").should_be_called().will_return("style") - style.get_surface_shading_attributes("obj").should_be_called().will_return("attributes") - ifc.run("style.edit_surface_style", style="style", attributes="attributes").should_be_called() +class TestUpdateStyleTextures: + def test_updating_an_existing_texture_style(self, ifc, style): + style.get_style("obj").should_be_called().will_return("element") + style.get_uv_maps("representation").should_be_called().will_return("uv_maps") + ifc.run("style.add_surface_textures", material="obj", uv_maps="uv_maps").should_be_called().will_return( + "textures" + ) style.get_surface_texture_style("obj").should_be_called().will_return("style") - style.can_support_texture_style("obj").should_be_called().will_return(True) - style.get_surface_textures("obj").should_be_called().will_return("textures") - ifc.run("style.add_surface_textures", textures="textures").should_be_called().will_return("ifc_textures") - ifc.run("style.edit_surface_style", style="style", attributes={"Textures": "ifc_textures"}).should_be_called() - subject.update_style_colours(ifc, style, obj="obj") - - def test_adding_texture_style_if_not_available(self, ifc, style): - ifc.get_entity("obj").should_be_called().will_return("element") - style.can_support_rendering_style("obj").should_be_called().will_return(False) - style.get_surface_shading_style("obj").should_be_called().will_return("style") - style.get_surface_shading_attributes("obj").should_be_called().will_return("attributes") - ifc.run("style.edit_surface_style", style="style", attributes="attributes").should_be_called() - - style.get_surface_texture_style("obj").should_be_called().will_return(None) - style.can_support_texture_style("obj").should_be_called().will_return(True) - style.get_surface_textures("obj").should_be_called().will_return("textures") - ifc.run("style.add_surface_textures", textures="textures").should_be_called().will_return("ifc_textures") + ifc.run("style.remove_surface_style", style="style").should_be_called() ifc.run( "style.add_surface_style", style="element", ifc_class="IfcSurfaceStyleWithTextures", - attributes={"Textures": "ifc_textures"}, + attributes={"Textures": "textures"}, ).should_be_called() - subject.update_style_colours(ifc, style, obj="obj") + subject.update_style_textures(ifc, style, obj="obj", representation="representation") - def test_removing_a_texture_style_if_no_longer_available(self, ifc, style): - ifc.get_entity("obj").should_be_called().will_return("element") - style.can_support_rendering_style("obj").should_be_called().will_return(False) - style.get_surface_shading_style("obj").should_be_called().will_return("style") - style.get_surface_shading_attributes("obj").should_be_called().will_return("attributes") - ifc.run("style.edit_surface_style", style="style", attributes="attributes").should_be_called() + def test_adding_a_fresh_texture_style(self, ifc, style): + style.get_style("obj").should_be_called().will_return("element") + style.get_uv_maps("representation").should_be_called().will_return("uv_maps") + ifc.run("style.add_surface_textures", material="obj", uv_maps="uv_maps").should_be_called().will_return( + "textures" + ) + style.get_surface_texture_style("obj").should_be_called().will_return(None) + ifc.run( + "style.add_surface_style", + style="element", + ifc_class="IfcSurfaceStyleWithTextures", + attributes={"Textures": "textures"}, + ).should_be_called() + subject.update_style_textures(ifc, style, obj="obj", representation="representation") + def test_removing_an_texture_if_no_textures_can_be_added(self, ifc, style): + style.get_style("obj").should_be_called().will_return("element") + style.get_uv_maps("representation").should_be_called().will_return("uv_maps") + ifc.run("style.add_surface_textures", material="obj", uv_maps="uv_maps").should_be_called().will_return(None) style.get_surface_texture_style("obj").should_be_called().will_return("style") - style.can_support_texture_style("obj").should_be_called().will_return(False) - ifc.run("style.remove_style", style="style").should_be_called() - subject.update_style_colours(ifc, style, obj="obj") + ifc.run("style.remove_surface_style", style="style").should_be_called() + subject.update_style_textures(ifc, style, obj="obj", representation="representation") + + def test_doing_nothing_if_no_existing_texture_and_we_cannot_add_a_new_texture(self, ifc, style): + style.get_style("obj").should_be_called().will_return("element") + style.get_uv_maps("representation").should_be_called().will_return("uv_maps") + ifc.run("style.add_surface_textures", material="obj", uv_maps="uv_maps").should_be_called().will_return(None) + style.get_surface_texture_style("obj").should_be_called().will_return(None) + subject.update_style_textures(ifc, style, obj="obj", representation="representation") class TestUnlinkStyle: diff --git a/src/blenderbim/test/tool/test_geometry.py b/src/blenderbim/test/tool/test_geometry.py index 005b14e53b..43bb06c419 100644 --- a/src/blenderbim/test/tool/test_geometry.py +++ b/src/blenderbim/test/tool/test_geometry.py @@ -464,10 +464,14 @@ class TestShouldForceTriangulation(NewFile): class TestShouldGenerateUVs(NewFile): def test_needs_mesh_data(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) obj = bpy.data.objects.new("Object", None) assert subject.should_generate_uvs(obj) is False def test_needs_nodes(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh")) material = bpy.data.materials.new("Material") obj.data.materials.append(material) @@ -475,6 +479,8 @@ class TestShouldGenerateUVs(NewFile): assert subject.should_generate_uvs(obj) is False def test_needs_texture_coordinates_with_a_uv_output(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh")) material = bpy.data.materials.new("Material") obj.data.materials.append(material) @@ -489,6 +495,8 @@ class TestShouldGenerateUVs(NewFile): assert subject.should_generate_uvs(obj) is True def test_accepts_a_uv_map_node(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh")) material = bpy.data.materials.new("Material") obj.data.materials.append(material) diff --git a/src/blenderbim/test/tool/test_style.py b/src/blenderbim/test/tool/test_style.py index 2f6ab95890..b1fc859453 100644 --- a/src/blenderbim/test/tool/test_style.py +++ b/src/blenderbim/test/tool/test_style.py @@ -42,42 +42,6 @@ class TestCanSupportRenderingStyle(NewFile): assert subject.can_support_rendering_style(obj) is False -class TestCanSupportTextureStyle(NewFile): - def test_without_nodes_we_do_not_support_textures(self): - obj = bpy.data.materials.new("Material") - obj.use_nodes = False - assert subject.can_support_texture_style(obj) is False - - def test_we_need_at_least_one_image_connected_to_a_bsdf(self): - obj = bpy.data.materials.new("Material") - obj.use_nodes = True - bsdf = obj.node_tree.nodes["Principled BSDF"] - node = obj.node_tree.nodes.new(type="ShaderNodeTexImage") - cwd = os.path.dirname(os.path.realpath(__file__)) - image_path = os.path.join(cwd, "..", "files", "image.jpg") - node.image = bpy.data.images.load(image_path) - obj.node_tree.links.new(bsdf.inputs["Base Color"], node.outputs["Color"]) - assert subject.can_support_texture_style(obj) is True - - def test_a_texture_without_an_image_filepath_is_not_supported(self): - obj = bpy.data.materials.new("Material") - obj.use_nodes = True - bsdf = obj.node_tree.nodes["Principled BSDF"] - node = obj.node_tree.nodes.new(type="ShaderNodeTexImage") - obj.node_tree.links.new(bsdf.inputs["Base Color"], node.outputs["Color"]) - assert subject.can_support_texture_style(obj) is False - - def test_the_texture_needs_to_connect_to_the_bsdf(self): - obj = bpy.data.materials.new("Material") - obj.use_nodes = True - bsdf = obj.node_tree.nodes["Principled BSDF"] - node = obj.node_tree.nodes.new(type="ShaderNodeTexImage") - cwd = os.path.dirname(os.path.realpath(__file__)) - image_path = os.path.join(cwd, "..", "files", "image.jpg") - node.image = bpy.data.images.load(image_path) - assert subject.can_support_texture_style(obj) is False - - class TestDisableEditing(NewFile): def test_run(self): obj = bpy.data.materials.new("Material") @@ -338,6 +302,14 @@ class TestGetSurfaceShadingStyle(NewFile): obj.BIMMaterialProperties.ifc_style_id = style.id() assert subject.get_surface_shading_style(obj) == style_item + def test_do_not_get_rendering_styles(self): + tool.Ifc.set(ifcopenshell.file()) + style_item = tool.Ifc.get().createIfcSurfaceStyleRendering() + style = tool.Ifc.get().createIfcSurfaceStyle(Styles=[style_item]) + obj = bpy.data.materials.new("Material") + obj.BIMMaterialProperties.ifc_style_id = style.id() + assert subject.get_surface_shading_style(obj) is None + class TestGetSurfaceTextureStyle(NewFile): def test_run(self): @@ -349,105 +321,13 @@ class TestGetSurfaceTextureStyle(NewFile): assert subject.get_surface_texture_style(obj) == style_item -class TestGetSurfaceTextures(NewFile): - def test_get_the_leaf_node_of_each_map_in_a_glossy_bsdf(self): - obj = bpy.data.materials.new("Material") - obj.use_nodes = True - bsdf = obj.node_tree.nodes["Principled BSDF"] - - obj.node_tree.nodes.remove(bsdf) - bsdf = obj.node_tree.nodes.new(type="ShaderNodeBsdfGlossy") - output = {n.type: n for n in obj.node_tree.nodes}.get("OUTPUT_MATERIAL", None) - obj.node_tree.links.new(output.inputs["Surface"], bsdf.outputs["BSDF"]) - - diffuse = obj.node_tree.nodes.new(type="ShaderNodeTexImage") - obj.node_tree.links.new(bsdf.inputs["Color"], diffuse.outputs["Color"]) - shininess = obj.node_tree.nodes.new(type="ShaderNodeTexImage") - obj.node_tree.links.new(bsdf.inputs["Roughness"], shininess.outputs["Color"]) - normal = obj.node_tree.nodes.new(type="ShaderNodeTexImage") - obj.node_tree.links.new(bsdf.inputs["Normal"], normal.outputs["Color"]) - - assert subject.get_surface_textures(obj) == {"DIFFUSE": diffuse, "SHININESS": shininess, "NORMAL": normal} - - def test_get_the_leaf_node_of_each_map_in_a_diffuse_bsdf(self): - obj = bpy.data.materials.new("Material") - obj.use_nodes = True - bsdf = obj.node_tree.nodes["Principled BSDF"] - - obj.node_tree.nodes.remove(bsdf) - bsdf = obj.node_tree.nodes.new(type="ShaderNodeBsdfDiffuse") - output = {n.type: n for n in obj.node_tree.nodes}.get("OUTPUT_MATERIAL", None) - obj.node_tree.links.new(output.inputs["Surface"], bsdf.outputs["BSDF"]) - - diffuse = obj.node_tree.nodes.new(type="ShaderNodeTexImage") - obj.node_tree.links.new(bsdf.inputs["Color"], diffuse.outputs["Color"]) - shininess = obj.node_tree.nodes.new(type="ShaderNodeTexImage") - obj.node_tree.links.new(bsdf.inputs["Roughness"], shininess.outputs["Color"]) - normal = obj.node_tree.nodes.new(type="ShaderNodeTexImage") - obj.node_tree.links.new(bsdf.inputs["Normal"], normal.outputs["Color"]) - - assert subject.get_surface_textures(obj) == {"DIFFUSE": diffuse, "SHININESS": shininess, "NORMAL": normal} - - def test_get_the_leaf_node_of_each_map_in_a_glass_bsdf(self): - obj = bpy.data.materials.new("Material") - obj.use_nodes = True - bsdf = obj.node_tree.nodes["Principled BSDF"] - - obj.node_tree.nodes.remove(bsdf) - bsdf = obj.node_tree.nodes.new(type="ShaderNodeBsdfGlass") - output = {n.type: n for n in obj.node_tree.nodes}.get("OUTPUT_MATERIAL", None) - obj.node_tree.links.new(output.inputs["Surface"], bsdf.outputs["BSDF"]) - - diffuse = obj.node_tree.nodes.new(type="ShaderNodeTexImage") - obj.node_tree.links.new(bsdf.inputs["Color"], diffuse.outputs["Color"]) - shininess = obj.node_tree.nodes.new(type="ShaderNodeTexImage") - obj.node_tree.links.new(bsdf.inputs["Roughness"], shininess.outputs["Color"]) - normal = obj.node_tree.nodes.new(type="ShaderNodeTexImage") - obj.node_tree.links.new(bsdf.inputs["Normal"], normal.outputs["Color"]) - - assert subject.get_surface_textures(obj) == {"DIFFUSE": diffuse, "SHININESS": shininess, "NORMAL": normal} - - def test_get_the_leaf_node_of_each_map_in_a_emission_bsdf(self): - obj = bpy.data.materials.new("Material") - obj.use_nodes = True - bsdf = obj.node_tree.nodes["Principled BSDF"] - - obj.node_tree.nodes.remove(bsdf) - bsdf = obj.node_tree.nodes.new(type="ShaderNodeEmission") - output = {n.type: n for n in obj.node_tree.nodes}.get("OUTPUT_MATERIAL", None) - obj.node_tree.links.new(output.inputs["Surface"], bsdf.outputs["Emission"]) - - diffuse = obj.node_tree.nodes.new(type="ShaderNodeTexImage") - obj.node_tree.links.new(bsdf.inputs["Color"], diffuse.outputs["Color"]) - - assert subject.get_surface_textures(obj) == {"DIFFUSE": diffuse} - - def test_get_the_leaf_node_of_each_map_in_a_principled_bsdf(self): - obj = bpy.data.materials.new("Material") - obj.use_nodes = True - bsdf = obj.node_tree.nodes["Principled BSDF"] - - diffuse = obj.node_tree.nodes.new(type="ShaderNodeTexImage") - obj.node_tree.links.new(bsdf.inputs["Base Color"], diffuse.outputs["Color"]) - shininess = obj.node_tree.nodes.new(type="ShaderNodeTexImage") - obj.node_tree.links.new(bsdf.inputs["Roughness"], shininess.outputs["Color"]) - normal = obj.node_tree.nodes.new(type="ShaderNodeTexImage") - obj.node_tree.links.new(bsdf.inputs["Normal"], normal.outputs["Color"]) - specular = obj.node_tree.nodes.new(type="ShaderNodeTexImage") - obj.node_tree.links.new(bsdf.inputs["Specular"], specular.outputs["Color"]) - emission = obj.node_tree.nodes.new(type="ShaderNodeTexImage") - obj.node_tree.links.new(bsdf.inputs["Emission Strength"], emission.outputs["Color"]) - opacity = obj.node_tree.nodes.new(type="ShaderNodeTexImage") - obj.node_tree.links.new(bsdf.inputs["Alpha"], opacity.outputs["Color"]) - - assert subject.get_surface_textures(obj) == { - "DIFFUSE": diffuse, - "SHININESS": shininess, - "NORMAL": normal, - "SPECULAR": specular, - "SELFILLUMINATION": emission, - "OPACITY": opacity, - } +class TestGetUVMaps(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + item = ifc.createIfcTriangulatedFaceSet() + uv_map = ifc.createIfcIndexedTriangleTextureMap(MappedTo=item) + representation = ifc.createIfcShapeRepresentation(Items=[item]) + assert subject.get_uv_maps(representation) == [uv_map] class TestImportSurfaceAttributes(NewFile): diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/remove_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/remove_representation.py index cb7e6f408b..7902e91b88 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/remove_representation.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/remove_representation.py @@ -29,9 +29,11 @@ class Usecase: def execute(self): styled_items = set() presentation_layer_assignments = set() + textures = set() for subelement in self.file.traverse(self.settings["representation"]): - if subelement.is_a("IfcRepresentationItem") and subelement.StyledByItem: - [styled_items.add(s) for s in subelement.StyledByItem] + if subelement.is_a("IfcRepresentationItem"): + [styled_items.add(s) for s in subelement.StyledByItem or []] + [textures.add(t) for t in getattr(subelement, "HasTextures", []) or []] elif subelement.is_a("IfcRepresentation"): for inverse in self.file.get_inverse(subelement): if inverse.is_a("IfcPresentationLayerAssignment"): @@ -44,6 +46,9 @@ class Usecase: do_not_delete=self.file.by_type("IfcGeometricRepresentationContext"), ) + for texture in textures: + ifcopenshell.util.element.remove_deep2(self.file, texture) + for element in styled_items: if not element.Item: self.file.remove(element) diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_style.py b/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_style.py index 9128064eee..f859419239 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_style.py +++ b/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_style.py @@ -39,7 +39,7 @@ class Usecase: select_class = "IfcSurfaceStyleShading" duplicate_items = [s for s in styles if s.is_a(select_class)] for duplicate_item in duplicate_items: - self.file.remove(duplicate_item) + ifcopenshell.api.run("style.remove_surface_style", self.file, style=duplicate_item) styles = list(self.settings["style"].Styles or []) styles.append(style_item) diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_textures.py b/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_textures.py index d6acd228db..546cd0417f 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_textures.py +++ b/src/ifcopenshell-python/ifcopenshell/api/style/add_surface_textures.py @@ -24,69 +24,117 @@ class Usecase: def __init__(self, file, **settings): # TODO: This usecase currently depends on Blender's data model self.file = file - # Textures is assumed to be a dictionary of texture maps. - # The key is the texture map name, and the value is the last texture manipulation in the graph. - self.settings = {"textures": None} + self.settings = {"material": None, "uv_maps": []} for key, value in settings.items(): self.settings[key] = value def execute(self): + if self.file.schema == "IFC2X3": + # TODO: research how compatible IFC2X3 and IFC4 textures are + return [] + + # We optimistically assume the user has specified one of these valid combinations + # https://docs.blender.org/manual/en/dev/addons/import_export/scene_gltf2.html + # glTF, X3D, and IFC are compatible. As long as they have something that + # loosely resembles the node tree, we treat it as valid. self.textures = [] - for texture, node in self.settings["textures"].items(): - parameters = self.get_default_parameters(texture) - self.traverse_node(node, parameters) - return list(reversed(self.textures)) + output = {n.type: n for n in self.settings["material"].node_tree.nodes}.get("OUTPUT_MATERIAL", None) - def get_default_parameters(self, texture): - return {"mode": "REPLACE", "source": "", "function": "", "rgb": "1 1 1", "alpha": "1", "texture": texture} + if not output: + return self.textures - def traverse_node(self, node, parameters): - if node.type == "MIX_RGB": - if node.blend_type == "MIX": - parameters["mode"] = "REPLACE" - elif node.blend_type == "ADD": - parameters["mode"] = "ADD" - elif node.blend_type == "MULTIPLY": - parameters["mode"] = "MODULATE" + bsdf = output.inputs["Surface"].links[0].from_node - if not node.inputs["Fac"].links: - if parameters["mode"] == "REPLACE": - parameters["mode"] = "BLEND" - parameters["mode"] += "FACTORALPHA" - parameters["alpha"] = str(node.inputs["Fac"].default_value) + if bsdf.type == "ADD_SHADER": + for socket in bsdf.inputs: + if socket.links and socket.links[0].from_node.type == "BSDF_PRINCIPLED": + bsdf = socket.links[0].from_node + break - if node.inputs["Color1"].links and node.inputs["Color2"].links: - self.traverse_node(node.inputs["Color2"].links[0].from_node, parameters) - self.traverse_node( - node.inputs["Color1"].links[0].from_node, self.get_default_parameters(parameters["texture"]) - ) - elif node.inputs["Color1"].links: - parameters["rgb"] = " ".join(map(str, list(node.inputs["Color2"].default_value)[0:3])) - parameters["source"] = "FACTOR" - self.traverse_node(node.inputs["Color1"].links[0].from_node, parameters) - elif node.inputs["Color2"].links: - parameters["rgb"] = " ".join(map(str, list(node.inputs["Color1"].default_value)[0:3])) - parameters["source"] = "FACTOR" - self.traverse_node(node.inputs["Color2"].links[0].from_node, parameters) - elif node.type == "VECT_MATH": - # TODO Handle 2X, 4X, and signed - pass - elif node.type == "INVERT": - parameters["function"] = "COMPLEMENT" if parameters["function"] == "" else "" - elif node.type == "TEX_IMAGE": - return self.create_surface_texture(node, parameters) - else: - # TODO keep traversing backwards - pass + if bsdf.type == "MIX_SHADER": + self.detect_unlit_emissive_map(bsdf) + elif bsdf.type == "BSDF_PRINCIPLED": + self.detect_normal_map(bsdf) + self.detect_emissive_map(bsdf) + self.detect_metallicroughness_map(bsdf) + self.detect_occlusion_map() + self.detect_diffuse_map(bsdf) + # We do not support Phong shading. What year is this, 1995? + return self.textures - def create_surface_texture(self, node, parameters): - self.textures.append( - self.file.create_entity( - "IfcImageTexture", - RepeatS=node.extension == "REPEAT", - RepeatT=node.extension == "REPEAT", - Mode=parameters["mode"], - Parameter=[parameters[p] for p in ["source", "function", "rgb", "alpha", "texture"]], - URLReference=node.image.filepath, - ) + def detect_unlit_emissive_map(self, bsdf): + for socket in bsdf.inputs: + if socket.links and socket.links[0].from_node.type == "TEX_IMAGE": + return self.create_surface_texture(socket.links[0].from_node, "EMISSIVE") + + def detect_normal_map(self, bsdf): + if bsdf.inputs["Normal"].links and bsdf.inputs["Normal"].links[0].from_node.type == "NORMAL_MAP": + normal = bsdf.inputs["Normal"].links[0].from_node + if normal.inputs["Color"].links and normal.inputs["Color"].links[0].from_node.type == "TEX_IMAGE": + return self.create_surface_texture(normal.inputs["Color"].links[0].from_node, "NORMAL") + + def detect_emissive_map(self, bsdf): + if bsdf.outputs[0].links[0].to_node.type != "ADD_SHADER": + return + bsdf = bsdf.outputs[0].links[0].to_node + for socket in bsdf.inputs: + if socket.links and socket.links[0].from_node.type == "EMISSION": + bsdf = socket.links[0].from_node + if bsdf.inputs["Color"].links and bsdf.inputs["Color"].links[0].from_node.type == "TEX_IMAGE": + return self.create_surface_texture(bsdf.inputs["Color"].links[0].from_node, "EMISSIVE") + + def detect_metallicroughness_map(self, bsdf): + if bsdf.inputs["Metallic"].links and bsdf.inputs["Metallic"].links[0].from_node.type == "SEPRGB": + seprgb = bsdf.inputs["Metallic"].links[0].from_node + if seprgb.inputs["Image"].links and seprgb.inputs["Image"].links[0].from_node.type == "TEX_IMAGE": + return self.create_surface_texture(seprgb.inputs["Image"].links[0].from_node, "METALLICROUGHNESS") + if bsdf.inputs["Roughness"].links and bsdf.inputs["Roughness"].links[0].from_node.type == "SEPRGB": + seprgb = bsdf.inputs["Roughness"].links[0].from_node + if seprgb.inputs["Image"].links and seprgb.inputs["Image"].links[0].from_node.type == "TEX_IMAGE": + return self.create_surface_texture(seprgb.inputs["Image"].links[0].from_node, "METALLICROUGHNESS") + + def detect_occlusion_map(self): + for node in self.settings["material"].node_tree.nodes: + if node.type != "GROUP" or node.name != "glTF Settings" or not node.inputs or not node.inputs[0].links: + continue + from_node = node.inputs[0].links[0].from_node + if from_node.type == "SEPRGB": + sep = from_node + if sep.inputs["Image"].links and sep.inputs["Image"].links[0].from_node.type == "TEX_IMAGE": + return self.create_surface_texture(sep.inputs["Image"].links[0].from_node, "OCCLUSION") + elif from_node.type == "TEX_IMAGE": + return self.create_surface_texture(from_node, "OCCLUSION") + + def detect_diffuse_map(self, bsdf): + links = bsdf.inputs["Base Color"].links + if links and links[0].from_node.type == "TEX_IMAGE": + return self.create_surface_texture(links[0].from_node, "DIFFUSE") + + def create_surface_texture(self, node, mode): + texture = self.file.create_entity( + "IfcImageTexture", + RepeatS=node.extension == "REPEAT", + RepeatT=node.extension == "REPEAT", + Mode=mode, + URLReference=node.image.filepath, ) + self.textures.append(texture) + self.process_texture_coordinates(node, texture) + + def process_texture_coordinates(self, node, texture): + if node.inputs["Vector"].links and node.inputs["Vector"].links[0].from_node.type == "TEX_COORD": + if node.inputs["Vector"].links[0].from_socket.name == "UV": + self.apply_uv_map_to_texture(texture) + elif node.inputs["Vector"].links[0].from_socket.name == "Generated": + self.file.create_entity("IfcTextureCoordinateGenerator", Maps=[texture], Mode="COORD") + elif node.inputs["Vector"].links[0].from_socket.name == "Camera": + self.file.create_entity("IfcTextureCoordinateGenerator", Maps=[texture], Mode="COORD-EYE") + elif node.inputs["Vector"].links and node.inputs["Vector"].links[0].from_node.type == "UVMAP": + self.apply_uv_map_to_texture(texture) + + def apply_uv_map_to_texture(self, texture): + print('texture', texture) + for uv_map in self.settings["uv_maps"]: + maps = set(uv_map.Maps or []) + maps.add(texture) + uv_map.Maps = list(maps) diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/remove_style.py b/src/ifcopenshell-python/ifcopenshell/api/style/remove_style.py index abc8c09cc2..761b00fbf8 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/style/remove_style.py +++ b/src/ifcopenshell-python/ifcopenshell/api/style/remove_style.py @@ -28,7 +28,9 @@ class Usecase: def execute(self): self.purge_styled_items(self.settings["style"]) - ifcopenshell.util.element.remove_deep(self.file, self.settings["style"]) + for style in self.settings["style"].Styles or []: + ifcopenshell.api.run("style.remove_surface_style", self.file, style=style) + self.file.remove(self.settings["style"]) def purge_styled_items(self, style): for inverse in self.file.get_inverse(style): diff --git a/src/ifcopenshell-python/ifcopenshell/api/style/remove_surface_style.py b/src/ifcopenshell-python/ifcopenshell/api/style/remove_surface_style.py new file mode 100644 index 0000000000..3a5a0ddf34 --- /dev/null +++ b/src/ifcopenshell-python/ifcopenshell/api/style/remove_surface_style.py @@ -0,0 +1,46 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2022 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import ifcopenshell.util.element + + +class Usecase: + def __init__(self, file, **settings): + self.file = file + self.settings = {"style": None} + for key, value in settings.items(): + self.settings[key] = value + + def execute(self): + to_delete = set() + if self.settings["style"].is_a("IfcSurfaceStyleWithTextures"): + for texture in self.settings["style"].Textures or []: + if texture.IsMappedBy: + for coordinate in texture.IsMappedBy: + to_delete.add(coordinate) + else: + to_delete.add(texture) + + for attribute in self.settings["style"]: + if isinstance(attribute, ifcopenshell.entity_instance): + to_delete.add(attribute) + + self.file.remove(self.settings["style"]) + + for element in to_delete: + ifcopenshell.util.element.remove_deep2(self.file, element) diff --git a/src/ifcopenshell-python/test/api/geometry/test_remove_representation.py b/src/ifcopenshell-python/test/api/geometry/test_remove_representation.py index 5918ef5cf2..53b6acc0e1 100644 --- a/src/ifcopenshell-python/test/api/geometry/test_remove_representation.py +++ b/src/ifcopenshell-python/test/api/geometry/test_remove_representation.py @@ -62,12 +62,14 @@ class TestRemoveRepresentation(test.bootstrap.IFC4): assert self.file.by_type("IfcShapeRepresentation")[0].RepresentationType != "MappedRepresentation" assert len(self.file.by_type("IfcRepresentationMap")) == 1 - def test_purging_styled_items(self): + def test_purging_styled_items_assignments_but_keeping_the_surface_style(self): item = self.file.createIfcExtrudedAreaSolid() representation = self.file.createIfcShapeRepresentation(Items=[item]) - styled_item = self.file.createIfcStyledItem(Item=item) + surface_style = self.file.createIfcSurfaceStyle() + styled_item = self.file.createIfcStyledItem(Item=item, Styles=[surface_style]) ifcopenshell.api.run("geometry.remove_representation", self.file, representation=representation) assert len(self.file.by_type("IfcStyledItem")) == 0 + assert len(self.file.by_type("IfcSurfaceStyle")) == 1 def test_not_purging_styled_items_if_used_elsewhere(self): item = self.file.createIfcExtrudedAreaSolid() @@ -97,3 +99,28 @@ class TestRemoveRepresentation(test.bootstrap.IFC4): representation = self.file.createIfcShapeRepresentation(ContextOfItems=context) ifcopenshell.api.run("geometry.remove_representation", self.file, representation=representation) assert len(self.file.by_type("IfcGeometricRepresentationContext")) == 1 + + def test_purging_texture_coordinates(self): + item = self.file.createIfcTriangulatedFaceSet() + representation = self.file.createIfcShapeRepresentation(Items=[item]) + image = self.file.createIfcImageTexture() + texture = self.file.createIfcIndexedTriangleTextureMap( + TexCoords=self.file.createIfcTextureVertexList(), MappedTo=item, Maps=[image] + ) + ifcopenshell.api.run("geometry.remove_representation", self.file, representation=representation) + assert len(self.file.by_type("IfcIndexedTriangleTextureMap")) == 0 + assert len(self.file.by_type("IfcTextureVertexList")) == 0 + assert len(self.file.by_type("IfcImageTexture")) == 0 + + def test_purging_texture_coordinates_but_not_images_used_in_other_styles(self): + item = self.file.createIfcTriangulatedFaceSet() + representation = self.file.createIfcShapeRepresentation(Items=[item]) + image = self.file.createIfcImageTexture() + texture = self.file.createIfcIndexedTriangleTextureMap( + TexCoords=self.file.createIfcTextureVertexList(), MappedTo=item, Maps=[image] + ) + texture2 = self.file.createIfcIndexedTriangleTextureMap(Maps=[image]) + ifcopenshell.api.run("geometry.remove_representation", self.file, representation=representation) + assert len(self.file.by_type("IfcIndexedTriangleTextureMap")) == 1 + assert len(self.file.by_type("IfcTextureVertexList")) == 0 + assert len(self.file.by_type("IfcImageTexture")) == 1 diff --git a/src/ifcopenshell-python/test/api/style/test_remove_style.py b/src/ifcopenshell-python/test/api/style/test_remove_style.py new file mode 100644 index 0000000000..2b8620b442 --- /dev/null +++ b/src/ifcopenshell-python/test/api/style/test_remove_style.py @@ -0,0 +1,36 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2022 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import pytest +import test.bootstrap +import ifcopenshell.api + + +class TestRemoveStyle(test.bootstrap.IFC4): + def test_removing_a_style(self): + shading = self.file.createIfcSurfaceStyleShading() + style = self.file.createIfcSurfaceStyle(Styles=[shading]) + ifcopenshell.api.run("style.remove_style", self.file, style=style) + assert len(list(self.file)) == 0 + + def test_removing_any_styled_items_referencing_the_style(self): + shading = self.file.createIfcSurfaceStyleShading() + style = self.file.createIfcSurfaceStyle(Styles=[shading]) + styled_item = self.file.createIfcStyledItem(Styles=[style]) + ifcopenshell.api.run("style.remove_style", self.file, style=style) + assert len(list(self.file)) == 0 diff --git a/src/ifcopenshell-python/test/api/style/test_remove_surface_style.py b/src/ifcopenshell-python/test/api/style/test_remove_surface_style.py new file mode 100644 index 0000000000..e922a0ef76 --- /dev/null +++ b/src/ifcopenshell-python/test/api/style/test_remove_surface_style.py @@ -0,0 +1,41 @@ +# IfcOpenShell - IFC toolkit and geometry engine +# Copyright (C) 2022 Dion Moult +# +# This file is part of IfcOpenShell. +# +# IfcOpenShell is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# IfcOpenShell is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with IfcOpenShell. If not, see . + +import pytest +import test.bootstrap +import ifcopenshell.api + + +class TestRemoveSurfaceStyle(test.bootstrap.IFC4): + def test_removing_a_shading_style(self): + style = self.file.createIfcSurfaceStyleShading(SurfaceColour=self.file.createIfcColourRgb()) + ifcopenshell.api.run("style.remove_surface_style", self.file, style=style) + assert len(list(self.file)) == 0 + + def test_removing_a_texture_style(self): + texture = self.file.createIfcImageTexture() + style = self.file.createIfcSurfaceStyleWithTextures(Textures=[texture]) + ifcopenshell.api.run("style.remove_surface_style", self.file, style=style) + assert len(list(self.file)) == 0 + + def test_removing_a_texture_style_with_all_of_its_coordinates(self): + texture = self.file.createIfcImageTexture() + coordinates = self.file.createIfcTextureCoordinateGenerator(Maps=[texture]) + style = self.file.createIfcSurfaceStyleWithTextures(Textures=[texture]) + ifcopenshell.api.run("style.remove_surface_style", self.file, style=style) + assert len(list(self.file)) == 0 diff --git a/src/ifcopenshell-python/test/util/test_element.py b/src/ifcopenshell-python/test/util/test_element.py index 5536effdcb..3ad1ecc4cd 100644 --- a/src/ifcopenshell-python/test/util/test_element.py +++ b/src/ifcopenshell-python/test/util/test_element.py @@ -432,7 +432,7 @@ class TestRemoveDeep2IFC4(test.bootstrap.IFC4): assert self.file.by_id(1) assert self.file.by_guid("id2") - def test_removing_an_element_still_referenced_somewhere(self): + def test_not_removing_an_element_still_referenced_somewhere(self): owner = self.file.createIfcOwnerHistory() element = self.file.createIfcWall(GlobalId="id1", OwnerHistory=owner) subject.remove_deep2(self.file, owner)