mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-26 18:21:59 +00:00
style.add_surface_texture to work without blender material provided
This commit is contained in:
@@ -566,7 +566,7 @@ class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
"Define shading/render style first",
|
"Define shading/render style first",
|
||||||
)
|
)
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
textures = tool.Ifc.run("style.add_surface_textures", material=material, uv_maps=[])
|
textures = tool.Ifc.run("style.add_surface_textures", textures=self.get_texture_attributes(), uv_maps=[])
|
||||||
texture_style = tool.Ifc.run(
|
texture_style = tool.Ifc.run(
|
||||||
"style.add_surface_style",
|
"style.add_surface_style",
|
||||||
style=self.style,
|
style=self.style,
|
||||||
@@ -603,9 +603,6 @@ class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
)
|
)
|
||||||
tool.Loader.create_surface_style_rendering(material, surface_style)
|
tool.Loader.create_surface_style_rendering(material, surface_style)
|
||||||
elif self.props.is_editing_class == "IfcSurfaceStyleWithTextures":
|
elif self.props.is_editing_class == "IfcSurfaceStyleWithTextures":
|
||||||
# TODO: rework add_surface_textures to work without blender
|
|
||||||
# otherwise we lose textures that are not used in the shader
|
|
||||||
# and we also doesn't recognize relative paths if .blend file is not saved
|
|
||||||
# TODO: provide `uv_maps` - need to rework .get_uv_maps not to depend on a single representation
|
# TODO: provide `uv_maps` - need to rework .get_uv_maps not to depend on a single representation
|
||||||
material = tool.Ifc.get_object(self.style)
|
material = tool.Ifc.get_object(self.style)
|
||||||
shading_style = self.rendering_style or self.shading_style
|
shading_style = self.rendering_style or self.shading_style
|
||||||
@@ -617,7 +614,7 @@ class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
"Define shading/render style first",
|
"Define shading/render style first",
|
||||||
)
|
)
|
||||||
return {"CANCELLED"}
|
return {"CANCELLED"}
|
||||||
textures = tool.Ifc.run("style.add_surface_textures", material=material, uv_maps=[])
|
textures = tool.Ifc.run("style.add_surface_textures", textures=self.get_texture_attributes(), uv_maps=[])
|
||||||
if textures:
|
if textures:
|
||||||
texture_style = tool.Ifc.run(
|
texture_style = tool.Ifc.run(
|
||||||
"style.add_surface_style",
|
"style.add_surface_style",
|
||||||
@@ -670,6 +667,21 @@ class EditSurfaceStyle(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
"SpecularHighlight": specular_highlight,
|
"SpecularHighlight": specular_highlight,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# TODO: support RepeatS/RepeatT params in UI:
|
||||||
|
# add it to prop.Texture and Style.get_texture_style_data_from_props
|
||||||
|
def get_texture_attributes(self):
|
||||||
|
textures = []
|
||||||
|
for texture in self.props.textures:
|
||||||
|
texture_data = {
|
||||||
|
"URLReference": texture.path,
|
||||||
|
"Mode": texture.mode,
|
||||||
|
"RepeatS": True,
|
||||||
|
"RepeatT": True,
|
||||||
|
"uv_mode": self.props.uv_mode,
|
||||||
|
}
|
||||||
|
textures.append(texture_data)
|
||||||
|
return textures
|
||||||
|
|
||||||
def color_to_dict(self, x):
|
def color_to_dict(self, x):
|
||||||
return {"Red": x[0], "Green": x[1], "Blue": x[2]}
|
return {"Red": x[0], "Green": x[1], "Blue": x[2]}
|
||||||
|
|
||||||
|
|||||||
@@ -21,10 +21,8 @@ import ifcopenshell.api
|
|||||||
|
|
||||||
|
|
||||||
class Usecase:
|
class Usecase:
|
||||||
def __init__(self, file, material=None, uv_maps=None):
|
def __init__(self, file, material=None, uv_maps=None, textures=None):
|
||||||
"""Add a surface texture based on a Blender material definition
|
"""Add surface texture based on a Blender material definition or texture data.
|
||||||
|
|
||||||
Warning: this API can only be used with Blender data structures.
|
|
||||||
|
|
||||||
:param material: The Blender material definition with a node tree that
|
:param material: The Blender material definition with a node tree that
|
||||||
is compatible with glTF. See one of the valid combinations here:
|
is compatible with glTF. See one of the valid combinations here:
|
||||||
@@ -34,12 +32,26 @@ class Usecase:
|
|||||||
IfcTessellatedFaceSets that the representation has, obtained from
|
IfcTessellatedFaceSets that the representation has, obtained from
|
||||||
the HasTextures attribute.
|
the HasTextures attribute.
|
||||||
:type uv_maps: list[ifcopenshell.entity_instance.entity_instance]
|
:type uv_maps: list[ifcopenshell.entity_instance.entity_instance]
|
||||||
|
:param textures: A list of dictionaries containing:
|
||||||
|
|
||||||
|
1. Attributes to create IfcImageTexture.
|
||||||
|
2. One additional parameter `uv_mode` to map IfcImageTexture to correct
|
||||||
|
IfcTextureCoordinate type.
|
||||||
|
|
||||||
|
Possible `uv_mode` values:
|
||||||
|
|
||||||
|
* `UV` - use IfcTextureCoordinate from `uv_maps` parameter;
|
||||||
|
* `Generated` - IfcTextureCoordinateGenerator with mode COORD (autogenerated UV
|
||||||
|
based on geometry);
|
||||||
|
* `Camera` - IfcTextureCoordinateGenerator with mode COORD_EYE (autogenerated UV
|
||||||
|
based on camera position)
|
||||||
|
:type textures: list[dict]
|
||||||
:return: A list of IfcImageTexture
|
:return: A list of IfcImageTexture
|
||||||
:rtype: list[ifcopenshell.entity_instance.entity_instance]
|
:rtype: list[ifcopenshell.entity_instance.entity_instance]
|
||||||
"""
|
"""
|
||||||
# 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
|
||||||
self.settings = {"material": material, "uv_maps": uv_maps or []}
|
self.settings = {"material": material, "uv_maps": uv_maps or [], "textures": textures or []}
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
if self.file.schema == "IFC2X3":
|
if self.file.schema == "IFC2X3":
|
||||||
@@ -51,6 +63,23 @@ class Usecase:
|
|||||||
# glTF, X3D, and IFC are compatible. As long as they have something that
|
# glTF, X3D, and IFC are compatible. As long as they have something that
|
||||||
# loosely resembles the node tree, we treat it as valid.
|
# loosely resembles the node tree, we treat it as valid.
|
||||||
self.textures = []
|
self.textures = []
|
||||||
|
|
||||||
|
for texture in self.settings["textures"]:
|
||||||
|
uv_mode = texture.get("uv_mode", None)
|
||||||
|
texture_data = texture.copy()
|
||||||
|
texture_data.pop("uv_mode", None)
|
||||||
|
texture = self.file.create_entity("IfcImageTexture", **texture_data)
|
||||||
|
if uv_mode == "Generated":
|
||||||
|
self.file.create_entity("IfcTextureCoordinateGenerator", Maps=[texture], Mode="COORD")
|
||||||
|
elif uv_mode == "Camera":
|
||||||
|
self.file.create_entity("IfcTextureCoordinateGenerator", Maps=[texture], Mode="COORD-EYE")
|
||||||
|
elif uv_mode == "UV":
|
||||||
|
self.apply_uv_map_to_texture(texture)
|
||||||
|
self.textures.append(texture)
|
||||||
|
|
||||||
|
if self.settings["material"] is None:
|
||||||
|
return self.textures
|
||||||
|
|
||||||
output = {n.type: n for n in self.settings["material"].node_tree.nodes}.get("OUTPUT_MATERIAL", None)
|
output = {n.type: n for n in self.settings["material"].node_tree.nodes}.get("OUTPUT_MATERIAL", None)
|
||||||
|
|
||||||
if not output:
|
if not output:
|
||||||
@@ -73,6 +102,7 @@ class Usecase:
|
|||||||
self.detect_occlusion_map()
|
self.detect_occlusion_map()
|
||||||
self.detect_diffuse_map(bsdf)
|
self.detect_diffuse_map(bsdf)
|
||||||
# We do not support Phong shading. What year is this, 1995?
|
# We do not support Phong shading. What year is this, 1995?
|
||||||
|
|
||||||
return self.textures
|
return self.textures
|
||||||
|
|
||||||
def detect_unlit_emissive_map(self, bsdf):
|
def detect_unlit_emissive_map(self, bsdf):
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# 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 TestAddSurfaceTexture(test.bootstrap.IFC4):
|
||||||
|
def get_default_texture_data(self):
|
||||||
|
return [
|
||||||
|
{"Mode": "DIFFUSE", "RepeatS": True, "RepeatT": True, "URLReference": "diffuse.jpg"},
|
||||||
|
{"Mode": "NORMAL", "RepeatS": False, "RepeatT": False, "URLReference": "normal.jpg"},
|
||||||
|
{"Mode": "METALLICROUGHNESS", "RepeatS": True, "RepeatT": True, "URLReference": "metallic_roughness.jpg"},
|
||||||
|
{"Mode": "OCCLUSION", "RepeatS": True, "RepeatT": True, "URLReference": "ambient_occlusion.jpg"},
|
||||||
|
]
|
||||||
|
|
||||||
|
def compare_texture_to_data(self, texture, data, uv_maps=[]):
|
||||||
|
texture_data = texture.get_info()
|
||||||
|
for attribute in ("Mode", "RepeatS", "RepeatT", "URLReference"):
|
||||||
|
assert texture_data[attribute] == data.get(attribute, None)
|
||||||
|
|
||||||
|
uv_mode = data.get("uv_mode", None)
|
||||||
|
if uv_mode is None:
|
||||||
|
assert texture.IsMappedBy == ()
|
||||||
|
elif uv_mode == "Generated":
|
||||||
|
assert len(texture.IsMappedBy) == 1
|
||||||
|
assert texture.IsMappedBy[0].Mode == "COORD"
|
||||||
|
elif uv_mode == "Camera":
|
||||||
|
assert len(texture.IsMappedBy) == 1
|
||||||
|
assert texture.IsMappedBy[0].Mode == "COORD-EYE"
|
||||||
|
elif uv_mode == "UV":
|
||||||
|
assert set(texture.IsMappedBy) == set(uv_maps)
|
||||||
|
|
||||||
|
def test_add_surface_textures_from_data(self):
|
||||||
|
texture_data = self.get_default_texture_data()
|
||||||
|
|
||||||
|
textures = ifcopenshell.api.run("style.add_surface_textures", self.file, textures=texture_data)
|
||||||
|
assert len(list(self.file)) == len(texture_data)
|
||||||
|
|
||||||
|
for texture, data in zip(textures, texture_data):
|
||||||
|
self.compare_texture_to_data(texture, data)
|
||||||
|
|
||||||
|
def test_add_surface_textures_from_data_with_uv_mode(self):
|
||||||
|
texture_data = self.get_default_texture_data()
|
||||||
|
texture_data[0]["uv_mode"] = "Generated"
|
||||||
|
texture_data[1]["uv_mode"] = "Camera"
|
||||||
|
texture_data[2]["uv_mode"] = "UV"
|
||||||
|
texture_data[3]["uv_mode"] = None
|
||||||
|
|
||||||
|
textures = ifcopenshell.api.run("style.add_surface_textures", self.file, textures=texture_data)
|
||||||
|
for texture, data in zip(textures, texture_data):
|
||||||
|
self.compare_texture_to_data(texture, data)
|
||||||
|
|
||||||
|
def test_add_surface_textures_from_data_with_uv_maps(self):
|
||||||
|
texture_data = self.get_default_texture_data()
|
||||||
|
texture_data[0]["uv_mode"] = "Generated"
|
||||||
|
texture_data[1]["uv_mode"] = "Camera"
|
||||||
|
texture_data[2]["uv_mode"] = "UV"
|
||||||
|
texture_data[3]["uv_mode"] = None
|
||||||
|
|
||||||
|
uv_maps = [self.file.create_entity("IfcTextureCoordinateGenerator", Maps=[], Mode="COORD") for i in range(5)]
|
||||||
|
textures = ifcopenshell.api.run("style.add_surface_textures", self.file, textures=texture_data, uv_maps=uv_maps)
|
||||||
|
for texture, data in zip(textures, texture_data):
|
||||||
|
self.compare_texture_to_data(texture, data, uv_maps)
|
||||||
Reference in New Issue
Block a user