Texture support is now compatible with glTF texture maps

This commit is contained in:
Dion Moult
2022-02-09 11:34:59 +11:00
parent 4eaf0e7a9c
commit dd7a47ab99
19 changed files with 380 additions and 336 deletions
@@ -26,6 +26,7 @@ classes = (
operator.EditStyle, operator.EditStyle,
operator.RemoveStyle, operator.RemoveStyle,
operator.UpdateStyleColours, operator.UpdateStyleColours,
operator.UpdateStyleTextures,
operator.UnlinkStyle, operator.UnlinkStyle,
prop.BIMStyleProperties, prop.BIMStyleProperties,
ui.BIM_PT_style, ui.BIM_PT_style,
@@ -20,6 +20,7 @@ import bpy
import blenderbim.bim.handler import blenderbim.bim.handler
import blenderbim.tool as tool import blenderbim.tool as tool
import blenderbim.core.style as core import blenderbim.core.style as core
import ifcopenshell.util.representation
from blenderbim.bim.ifc import IfcStore 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) 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): class RemoveStyle(bpy.types.Operator, Operator):
bl_idname = "bim.remove_style" bl_idname = "bim.remove_style"
bl_label = "Remove Style" bl_label = "Remove Style"
@@ -42,6 +42,7 @@ class BIM_PT_style(Panel):
row = self.layout.row(align=True) row = self.layout.row(align=True)
if props.ifc_style_id: if props.ifc_style_id:
row.operator("bim.update_style_colours", icon="GREASEPENCIL") 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.unlink_style", icon="UNLINKED", text="")
row.operator("bim.remove_style", icon="X", text="") row.operator("bim.remove_style", icon="X", text="")
else: else:
+16 -11
View File
@@ -59,20 +59,25 @@ def update_style_colours(ifc, style, obj=None):
else: else:
ifc.run("style.add_surface_style", style=element, ifc_class="IfcSurfaceStyleShading", attributes=attributes) 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) 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: if texture_style:
ifc.run("style.edit_surface_style", style=texture_style, attributes={"Textures": textures}) ifc.run("style.remove_surface_style", style=texture_style)
else: ifc.run(
ifc.run( "style.add_surface_style",
"style.add_surface_style", style=element,
style=element, ifc_class="IfcSurfaceStyleWithTextures",
ifc_class="IfcSurfaceStyleWithTextures", attributes={"Textures": textures},
attributes={"Textures": textures}, )
)
elif texture_style: 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): def unlink_style(ifc, style, obj=None):
+1 -2
View File
@@ -396,7 +396,6 @@ class Structural:
@interface @interface
class Style: class Style:
def can_support_rendering_style(cls, obj): pass def can_support_rendering_style(cls, obj): pass
def can_support_texture_style(cls, obj): pass
def disable_editing(cls, obj): pass def disable_editing(cls, obj): pass
def enable_editing(cls, obj): pass def enable_editing(cls, obj): pass
def export_surface_attributes(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_attributes(cls, obj): pass
def get_surface_shading_style(cls, obj): pass def get_surface_shading_style(cls, obj): pass
def get_surface_texture_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 def import_surface_attributes(cls, style, obj): pass
@@ -300,6 +300,8 @@ class Geometry(blenderbim.core.tool.Geometry):
@classmethod @classmethod
def should_generate_uvs(cls, obj): def should_generate_uvs(cls, obj):
if tool.Ifc.get().schema == "IFC2X3":
return False
for slot in obj.material_slots: for slot in obj.material_slots:
if slot.material and slot.material.use_nodes: if slot.material and slot.material.use_nodes:
for node in slot.material.node_tree.nodes: for node in slot.material.node_tree.nodes:
+12 -76
View File
@@ -27,41 +27,6 @@ class Style(blenderbim.core.tool.Style):
def can_support_rendering_style(cls, obj): def can_support_rendering_style(cls, obj):
return obj.use_nodes and hasattr(obj.node_tree, "nodes") 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 @classmethod
def disable_editing(cls, obj): def disable_editing(cls, obj):
obj.BIMStyleProperties.is_editing = False obj.BIMStyleProperties.is_editing = False
@@ -174,7 +139,7 @@ class Style(blenderbim.core.tool.Style):
def get_surface_shading_style(cls, obj): def get_surface_shading_style(cls, obj):
if obj.BIMMaterialProperties.ifc_style_id: if obj.BIMMaterialProperties.ifc_style_id:
style = tool.Ifc.get().by_id(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: if items:
return items[0] return items[0]
@@ -187,47 +152,18 @@ class Style(blenderbim.core.tool.Style):
return items[0] return items[0]
@classmethod @classmethod
def get_surface_textures(cls, obj): def get_uv_maps(cls, representation):
output = {n.type: n for n in obj.node_tree.nodes}.get("OUTPUT_MATERIAL", None) items = []
bsdf = output.inputs["Surface"].links[0].from_node for item in representation.Items:
node_mappings = { if item.is_a("IfcMappedItem"):
"BSDF_GLOSSY": { items.extend(item.MappingSource.MappedRepresentation.Items)
"DIFFUSE": "Color", items.append(item)
"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",
},
}
maps = {} results = []
if bsdf.type not in node_mappings: for item in items:
return maps for uv_map in item.HasTextures or []:
results.append(uv_map)
for map_type, input_name in node_mappings[bsdf.type].items(): return results
if bsdf.inputs[input_name].links:
maps[map_type] = bsdf.inputs[input_name].links[0].from_node
return maps
@classmethod @classmethod
def import_surface_attributes(cls, style, obj): def import_surface_attributes(cls, style, obj):
+41 -50
View File
@@ -32,8 +32,6 @@ class TestAddStyle:
"style.add_surface_style", style="style", ifc_class="IfcSurfaceStyleRendering", attributes="attributes" "style.add_surface_style", style="style", ifc_class="IfcSurfaceStyleRendering", attributes="attributes"
).should_be_called() ).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) ifc.get_entity("obj").should_be_called().will_return(None)
assert subject.add_style(ifc, style, obj="obj") == "style" 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" "style.add_surface_style", style="style", ifc_class="IfcSurfaceStyleShading", attributes="attributes"
).should_be_called() ).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") ifc.get_entity("obj").should_be_called().will_return("material")
style.get_context("obj").should_be_called().will_return("context") 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() ifc.run("style.assign_material_style", material="material", style="style", context="context").should_be_called()
@@ -66,93 +62,88 @@ class TestRemoveStyle:
class TestUpdateStyleColours: class TestUpdateStyleColours:
def test_updating_rendering_style_if_available(self, ifc, style): 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.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_style("obj").should_be_called().will_return("style")
style.get_surface_rendering_attributes("obj").should_be_called().will_return("attributes") 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() 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") subject.update_style_colours(ifc, style, obj="obj")
def test_adding_a_rendering_style_if_not_available(self, ifc, style): 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.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_style("obj").should_be_called().will_return(None)
style.get_surface_rendering_attributes("obj").should_be_called().will_return("attributes") style.get_surface_rendering_attributes("obj").should_be_called().will_return("attributes")
ifc.run( ifc.run(
"style.add_surface_style", style="element", ifc_class="IfcSurfaceStyleRendering", attributes="attributes" "style.add_surface_style", style="element", ifc_class="IfcSurfaceStyleRendering", attributes="attributes"
).should_be_called() ).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") subject.update_style_colours(ifc, style, obj="obj")
def test_updating_shading_style_as_a_fallback_if_available(self, ifc, style): 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.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_style("obj").should_be_called().will_return("style")
style.get_surface_shading_attributes("obj").should_be_called().will_return("attributes") 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() 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") subject.update_style_colours(ifc, style, obj="obj")
def test_adding_a_shading_style_as_a_fallback_if_not_available(self, ifc, style): 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.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_style("obj").should_be_called().will_return(None)
style.get_surface_shading_attributes("obj").should_be_called().will_return("attributes") style.get_surface_shading_attributes("obj").should_be_called().will_return("attributes")
ifc.run( ifc.run(
"style.add_surface_style", style="element", ifc_class="IfcSurfaceStyleShading", attributes="attributes" "style.add_surface_style", style="element", ifc_class="IfcSurfaceStyleShading", attributes="attributes"
).should_be_called() ).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") 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.get_surface_texture_style("obj").should_be_called().will_return("style")
style.can_support_texture_style("obj").should_be_called().will_return(True) ifc.run("style.remove_surface_style", style="style").should_be_called()
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( ifc.run(
"style.add_surface_style", "style.add_surface_style",
style="element", style="element",
ifc_class="IfcSurfaceStyleWithTextures", ifc_class="IfcSurfaceStyleWithTextures",
attributes={"Textures": "ifc_textures"}, attributes={"Textures": "textures"},
).should_be_called() ).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): def test_adding_a_fresh_texture_style(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_uv_maps("representation").should_be_called().will_return("uv_maps")
style.get_surface_shading_style("obj").should_be_called().will_return("style") ifc.run("style.add_surface_textures", material="obj", uv_maps="uv_maps").should_be_called().will_return(
style.get_surface_shading_attributes("obj").should_be_called().will_return("attributes") "textures"
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)
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.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_surface_style", style="style").should_be_called()
ifc.run("style.remove_style", style="style").should_be_called() subject.update_style_textures(ifc, style, obj="obj", representation="representation")
subject.update_style_colours(ifc, style, obj="obj")
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: class TestUnlinkStyle:
@@ -464,10 +464,14 @@ class TestShouldForceTriangulation(NewFile):
class TestShouldGenerateUVs(NewFile): class TestShouldGenerateUVs(NewFile):
def test_needs_mesh_data(self): def test_needs_mesh_data(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
obj = bpy.data.objects.new("Object", None) obj = bpy.data.objects.new("Object", None)
assert subject.should_generate_uvs(obj) is False assert subject.should_generate_uvs(obj) is False
def test_needs_nodes(self): def test_needs_nodes(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh")) obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh"))
material = bpy.data.materials.new("Material") material = bpy.data.materials.new("Material")
obj.data.materials.append(material) obj.data.materials.append(material)
@@ -475,6 +479,8 @@ class TestShouldGenerateUVs(NewFile):
assert subject.should_generate_uvs(obj) is False assert subject.should_generate_uvs(obj) is False
def test_needs_texture_coordinates_with_a_uv_output(self): 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")) obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh"))
material = bpy.data.materials.new("Material") material = bpy.data.materials.new("Material")
obj.data.materials.append(material) obj.data.materials.append(material)
@@ -489,6 +495,8 @@ class TestShouldGenerateUVs(NewFile):
assert subject.should_generate_uvs(obj) is True assert subject.should_generate_uvs(obj) is True
def test_accepts_a_uv_map_node(self): 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")) obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh"))
material = bpy.data.materials.new("Material") material = bpy.data.materials.new("Material")
obj.data.materials.append(material) obj.data.materials.append(material)
+15 -135
View File
@@ -42,42 +42,6 @@ class TestCanSupportRenderingStyle(NewFile):
assert subject.can_support_rendering_style(obj) is False 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): class TestDisableEditing(NewFile):
def test_run(self): def test_run(self):
obj = bpy.data.materials.new("Material") obj = bpy.data.materials.new("Material")
@@ -338,6 +302,14 @@ class TestGetSurfaceShadingStyle(NewFile):
obj.BIMMaterialProperties.ifc_style_id = style.id() obj.BIMMaterialProperties.ifc_style_id = style.id()
assert subject.get_surface_shading_style(obj) == style_item 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): class TestGetSurfaceTextureStyle(NewFile):
def test_run(self): def test_run(self):
@@ -349,105 +321,13 @@ class TestGetSurfaceTextureStyle(NewFile):
assert subject.get_surface_texture_style(obj) == style_item assert subject.get_surface_texture_style(obj) == style_item
class TestGetSurfaceTextures(NewFile): class TestGetUVMaps(NewFile):
def test_get_the_leaf_node_of_each_map_in_a_glossy_bsdf(self): def test_run(self):
obj = bpy.data.materials.new("Material") ifc = ifcopenshell.file()
obj.use_nodes = True item = ifc.createIfcTriangulatedFaceSet()
bsdf = obj.node_tree.nodes["Principled BSDF"] uv_map = ifc.createIfcIndexedTriangleTextureMap(MappedTo=item)
representation = ifc.createIfcShapeRepresentation(Items=[item])
obj.node_tree.nodes.remove(bsdf) assert subject.get_uv_maps(representation) == [uv_map]
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 TestImportSurfaceAttributes(NewFile): class TestImportSurfaceAttributes(NewFile):
@@ -29,9 +29,11 @@ class Usecase:
def execute(self): def execute(self):
styled_items = set() styled_items = set()
presentation_layer_assignments = set() presentation_layer_assignments = set()
textures = set()
for subelement in self.file.traverse(self.settings["representation"]): for subelement in self.file.traverse(self.settings["representation"]):
if subelement.is_a("IfcRepresentationItem") and subelement.StyledByItem: if subelement.is_a("IfcRepresentationItem"):
[styled_items.add(s) for s in subelement.StyledByItem] [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"): elif subelement.is_a("IfcRepresentation"):
for inverse in self.file.get_inverse(subelement): for inverse in self.file.get_inverse(subelement):
if inverse.is_a("IfcPresentationLayerAssignment"): if inverse.is_a("IfcPresentationLayerAssignment"):
@@ -44,6 +46,9 @@ class Usecase:
do_not_delete=self.file.by_type("IfcGeometricRepresentationContext"), 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: for element in styled_items:
if not element.Item: if not element.Item:
self.file.remove(element) self.file.remove(element)
@@ -39,7 +39,7 @@ class Usecase:
select_class = "IfcSurfaceStyleShading" select_class = "IfcSurfaceStyleShading"
duplicate_items = [s for s in styles if s.is_a(select_class)] duplicate_items = [s for s in styles if s.is_a(select_class)]
for duplicate_item in duplicate_items: 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 = list(self.settings["style"].Styles or [])
styles.append(style_item) styles.append(style_item)
@@ -24,69 +24,117 @@ class Usecase:
def __init__(self, file, **settings): def __init__(self, file, **settings):
# TODO: This usecase currently depends on Blender's data model # TODO: This usecase currently depends on Blender's data model
self.file = file self.file = file
# Textures is assumed to be a dictionary of texture maps. self.settings = {"material": None, "uv_maps": []}
# The key is the texture map name, and the value is the last texture manipulation in the graph.
self.settings = {"textures": None}
for key, value in settings.items(): for key, value in settings.items():
self.settings[key] = value self.settings[key] = value
def execute(self): 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 = [] self.textures = []
for texture, node in self.settings["textures"].items(): output = {n.type: n for n in self.settings["material"].node_tree.nodes}.get("OUTPUT_MATERIAL", None)
parameters = self.get_default_parameters(texture)
self.traverse_node(node, parameters)
return list(reversed(self.textures))
def get_default_parameters(self, texture): if not output:
return {"mode": "REPLACE", "source": "", "function": "", "rgb": "1 1 1", "alpha": "1", "texture": texture} return self.textures
def traverse_node(self, node, parameters): bsdf = output.inputs["Surface"].links[0].from_node
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"
if not node.inputs["Fac"].links: if bsdf.type == "ADD_SHADER":
if parameters["mode"] == "REPLACE": for socket in bsdf.inputs:
parameters["mode"] = "BLEND" if socket.links and socket.links[0].from_node.type == "BSDF_PRINCIPLED":
parameters["mode"] += "FACTORALPHA" bsdf = socket.links[0].from_node
parameters["alpha"] = str(node.inputs["Fac"].default_value) break
if node.inputs["Color1"].links and node.inputs["Color2"].links: if bsdf.type == "MIX_SHADER":
self.traverse_node(node.inputs["Color2"].links[0].from_node, parameters) self.detect_unlit_emissive_map(bsdf)
self.traverse_node( elif bsdf.type == "BSDF_PRINCIPLED":
node.inputs["Color1"].links[0].from_node, self.get_default_parameters(parameters["texture"]) self.detect_normal_map(bsdf)
) self.detect_emissive_map(bsdf)
elif node.inputs["Color1"].links: self.detect_metallicroughness_map(bsdf)
parameters["rgb"] = " ".join(map(str, list(node.inputs["Color2"].default_value)[0:3])) self.detect_occlusion_map()
parameters["source"] = "FACTOR" self.detect_diffuse_map(bsdf)
self.traverse_node(node.inputs["Color1"].links[0].from_node, parameters) # We do not support Phong shading. What year is this, 1995?
elif node.inputs["Color2"].links: return self.textures
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
def create_surface_texture(self, node, parameters): def detect_unlit_emissive_map(self, bsdf):
self.textures.append( for socket in bsdf.inputs:
self.file.create_entity( if socket.links and socket.links[0].from_node.type == "TEX_IMAGE":
"IfcImageTexture", return self.create_surface_texture(socket.links[0].from_node, "EMISSIVE")
RepeatS=node.extension == "REPEAT",
RepeatT=node.extension == "REPEAT", def detect_normal_map(self, bsdf):
Mode=parameters["mode"], if bsdf.inputs["Normal"].links and bsdf.inputs["Normal"].links[0].from_node.type == "NORMAL_MAP":
Parameter=[parameters[p] for p in ["source", "function", "rgb", "alpha", "texture"]], normal = bsdf.inputs["Normal"].links[0].from_node
URLReference=node.image.filepath, 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)
@@ -28,7 +28,9 @@ class Usecase:
def execute(self): def execute(self):
self.purge_styled_items(self.settings["style"]) 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): def purge_styled_items(self, style):
for inverse in self.file.get_inverse(style): for inverse in self.file.get_inverse(style):
@@ -0,0 +1,46 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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)
@@ -62,12 +62,14 @@ class TestRemoveRepresentation(test.bootstrap.IFC4):
assert self.file.by_type("IfcShapeRepresentation")[0].RepresentationType != "MappedRepresentation" assert self.file.by_type("IfcShapeRepresentation")[0].RepresentationType != "MappedRepresentation"
assert len(self.file.by_type("IfcRepresentationMap")) == 1 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() item = self.file.createIfcExtrudedAreaSolid()
representation = self.file.createIfcShapeRepresentation(Items=[item]) 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) ifcopenshell.api.run("geometry.remove_representation", self.file, representation=representation)
assert len(self.file.by_type("IfcStyledItem")) == 0 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): def test_not_purging_styled_items_if_used_elsewhere(self):
item = self.file.createIfcExtrudedAreaSolid() item = self.file.createIfcExtrudedAreaSolid()
@@ -97,3 +99,28 @@ class TestRemoveRepresentation(test.bootstrap.IFC4):
representation = self.file.createIfcShapeRepresentation(ContextOfItems=context) representation = self.file.createIfcShapeRepresentation(ContextOfItems=context)
ifcopenshell.api.run("geometry.remove_representation", self.file, representation=representation) ifcopenshell.api.run("geometry.remove_representation", self.file, representation=representation)
assert len(self.file.by_type("IfcGeometricRepresentationContext")) == 1 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
@@ -0,0 +1,36 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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
@@ -0,0 +1,41 @@
# IfcOpenShell - IFC toolkit and geometry engine
# Copyright (C) 2022 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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
@@ -432,7 +432,7 @@ class TestRemoveDeep2IFC4(test.bootstrap.IFC4):
assert self.file.by_id(1) assert self.file.by_id(1)
assert self.file.by_guid("id2") 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() owner = self.file.createIfcOwnerHistory()
element = self.file.createIfcWall(GlobalId="id1", OwnerHistory=owner) element = self.file.createIfcWall(GlobalId="id1", OwnerHistory=owner)
subject.remove_deep2(self.file, owner) subject.remove_deep2(self.file, owner)