mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-22 21:02:28 +00:00
Texture support is now compatible with glTF texture maps
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user