[Bf-extensions-cvs] [09508f2d] master: glTF exporter: allow combining different-sized textures (eg for ORM)
Julien Duroure
noreply at git.blender.org
Tue Jun 16 21:33:54 CEST 2020
Commit: 09508f2dcf2a6af149b7817d9337333e9a0f2d4b
Author: Julien Duroure
Date: Tue Jun 16 21:32:57 2020 +0200
Branches: master
https://developer.blender.org/rBA09508f2dcf2a6af149b7817d9337333e9a0f2d4b
glTF exporter: allow combining different-sized textures (eg for ORM)
===================================================================
M io_scene_gltf2/__init__.py
M io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py
M io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py
M io_scene_gltf2/blender/exp/gltf2_blender_image.py
===================================================================
diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py
index 5a283201..130f7c1b 100755
--- a/io_scene_gltf2/__init__.py
+++ b/io_scene_gltf2/__init__.py
@@ -15,7 +15,7 @@
bl_info = {
'name': 'glTF 2.0 format',
'author': 'Julien Duroure, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
- "version": (1, 3, 16),
+ "version": (1, 3, 17),
'blender': (2, 90, 0),
'location': 'File > Import-Export',
'description': 'Import-Export as glTF 2.0',
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py
index 42c71150..f55d9440 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_materials.py
@@ -26,6 +26,7 @@ from io_scene_gltf2.blender.exp import gltf2_blender_gather_materials_pbr_metall
from ..com.gltf2_blender_extras import generate_extras
from io_scene_gltf2.blender.exp import gltf2_blender_get
from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
+from io_scene_gltf2.io.com.gltf2_io_debug import print_console
@cached
@@ -192,7 +193,13 @@ def __gather_orm_texture(blender_material, export_settings):
else:
result = (occlusion, roughness_socket, metallic_socket)
- # Double-check this will past the filter in texture_info (otherwise there are different resolutions or other problems).
+ if not gltf2_blender_gather_texture_info.check_same_size_images(result):
+ print_console("INFO",
+ "Occlusion and metal-roughness texture will be exported separately "
+ "(use same-sized images if you want them combined)")
+ return None
+
+ # Double-check this will past the filter in texture_info
info = gltf2_blender_gather_texture_info.gather_texture_info(result, export_settings)
if info is None:
return None
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py
index 430dbda8..57b0b5a2 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_texture_info.py
@@ -19,7 +19,6 @@ from io_scene_gltf2.io.com import gltf2_io
from io_scene_gltf2.blender.exp import gltf2_blender_gather_texture
from io_scene_gltf2.blender.exp import gltf2_blender_search_node_tree
from io_scene_gltf2.blender.exp import gltf2_blender_get
-from io_scene_gltf2.io.com.gltf2_io_debug import print_console
from io_scene_gltf2.io.com.gltf2_io_extensions import Extension
from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions
@@ -56,19 +55,6 @@ def __filter_texture_info(blender_shader_sockets_or_texture_slots, export_settin
# sockets do not lead to a texture --> discard
return False
- resolution = __get_tex_from_socket(blender_shader_sockets_or_texture_slots[0]).shader_node.image.size
- if any(any(a != b for a, b in zip(__get_tex_from_socket(elem).shader_node.image.size, resolution))
- for elem in blender_shader_sockets_or_texture_slots):
- def format_image(image_node):
- return "{} ({}x{})".format(image_node.image.name, image_node.image.size[0], image_node.image.size[1])
-
- images = [format_image(__get_tex_from_socket(elem).shader_node) for elem in
- blender_shader_sockets_or_texture_slots]
-
- print_console("ERROR", "Image sizes do not match. In order to be merged into one image file, "
- "images need to be of the same size. Images: {}".format(images))
- return False
-
return True
@@ -141,3 +127,21 @@ def __get_tex_from_socket(socket):
if result[0].shader_node.image is None:
return None
return result[0]
+
+
+def check_same_size_images(
+ blender_shader_sockets: typing.Tuple[bpy.types.NodeSocket],
+) -> bool:
+ """Check that all sockets leads to images of the same size."""
+ if not blender_shader_sockets or not all(blender_shader_sockets):
+ return False
+
+ sizes = set()
+ for socket in blender_shader_sockets:
+ tex = __get_tex_from_socket(socket)
+ if tex is None:
+ return False
+ size = tex.shader_node.image.size
+ sizes.add((size[0], size[1]))
+
+ return len(sizes) == 1
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_image.py b/io_scene_gltf2/blender/exp/gltf2_blender_image.py
index 34bb8e87..ee45784c 100644
--- a/io_scene_gltf2/blender/exp/gltf2_blender_image.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_image.py
@@ -123,61 +123,59 @@ class ExportImage:
def __encode_unhappy(self) -> bytes:
# We need to assemble the image out of channels.
# Do it with numpy and image.pixels.
- result = None
- img_fills = {
- chan: fill
- for chan, fill in self.fills.items()
- if isinstance(fill, FillImage)
- }
- # Loop over images instead of dst_chans; ensures we only decode each
- # image once even if it's used in multiple channels.
- image_names = list(set(fill.image.name for fill in img_fills.values()))
- for image_name in image_names:
- image = bpy.data.images[image_name]
-
- if result is None:
- dim = (image.size[0], image.size[1])
- result = np.ones(dim[0] * dim[1] * 4, np.float32)
- tmp_buf = np.empty(dim[0] * dim[1] * 4, np.float32)
- # Images should all be the same size (should be guaranteed by
- # gather_texture_info).
- assert (image.size[0], image.size[1]) == dim
-
- image.pixels.foreach_get(tmp_buf)
-
- for dst_chan, img_fill in img_fills.items():
- if img_fill.image == image:
- result[int(dst_chan)::4] = tmp_buf[int(img_fill.src_chan)::4]
-
- tmp_buf = None # GC this
+ # Find all Blender images used
+ images = []
+ for fill in self.fills.values():
+ if isinstance(fill, FillImage):
+ if fill.image not in images:
+ images.append(fill.image)
- if result is None:
+ if not images:
# No ImageFills; use a 1x1 white pixel
- dim = (1, 1)
- result = np.array([1.0, 1.0, 1.0, 1.0])
+ pixels = np.array([1.0, 1.0, 1.0, 1.0])
+ return self.__encode_from_numpy_array(pixels, (1, 1))
- return self.__encode_from_numpy_array(result, dim)
+ width = max(image.size[0] for image in images)
+ height = max(image.size[1] for image in images)
+
+ out_buf = np.ones(width * height * 4, np.float32)
+ tmp_buf = np.empty(width * height * 4, np.float32)
+
+ for image in images:
+ if image.size[0] == width and image.size[1] == height:
+ image.pixels.foreach_get(tmp_buf)
+ else:
+ # Image is the wrong size; make a temp copy and scale it.
+ with TmpImageGuard() as guard:
+ _make_temp_image_copy(guard, src_image=image)
+ tmp_image = guard.image
+ tmp_image.scale(width, height)
+ tmp_image.pixels.foreach_get(tmp_buf)
+
+ # Copy any channels for this image to the output
+ for dst_chan, fill in self.fills.items():
+ if isinstance(fill, FillImage) and fill.image == image:
+ out_buf[int(dst_chan)::4] = tmp_buf[int(fill.src_chan)::4]
+
+ tmp_buf = None # GC this
+
+ return self.__encode_from_numpy_array(out_buf, (width, height))
def __encode_from_numpy_array(self, pixels: np.ndarray, dim: Tuple[int, int]) -> bytes:
- tmp_image = None
- try:
- tmp_image = bpy.data.images.new(
+ with TmpImageGuard() as guard:
+ guard.image = bpy.data.images.new(
"##gltf-export:tmp-image##",
width=dim[0],
height=dim[1],
alpha=Channel.A in self.fills,
)
- assert tmp_image.channels == 4 # 4 regardless of the alpha argument above.
+ tmp_image = guard.image
tmp_image.pixels.foreach_set(pixels)
return _encode_temp_image(tmp_image, self.file_format)
- finally:
- if tmp_image is not None:
- bpy.data.images.remove(tmp_image, do_unlink=True)
-
def __encode_from_image(self, image: bpy.types.Image) -> bytes:
# See if there is an existing file we can use.
data = None
@@ -200,22 +198,10 @@ class ExportImage:
return data
# Copy to a temp image and save.
- tmp_image = None
- try:
- tmp_image = image.copy()
- tmp_image.update()
-
- if image.is_dirty:
- # Copy the pixels to get the changes
- tmp_buf = np.empty(image.size[0] * image.size[1] * 4, np.float32)
- image.pixels.foreach_get(tmp_buf)
- tmp_image.pixels.foreach_set(tmp_buf)
- tmp_buf = None # GC this
-
+ with TmpImageGuard() as guard:
+ _make_temp_image_copy(guard, src_image=image)
+ tmp_image = guard.image
return _encode_temp_image(tmp_image, self.file_format)
- finally:
- if tmp_image is not None:
- bpy.data.images.remove(tmp_image, do_unlink=True)
def _encode_temp_image(tmp_image: bpy.types.Image, file_format: str) -> bytes:
@@ -229,3 +215,30 @@ def _encode_temp_image(tmp_image: bpy.types.Image, file_format: str) -> bytes:
with open(tmpfilename, "rb") as f:
return f.read()
+
+
+class TmpImageGuard:
+ """Guard to automatically clean up temp images (use it with `with`)."""
+ def __init__(self):
+ self.image = None
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ if se
@@ Diff output truncated at 10240 characters. @@
More information about the Bf-extensions-cvs
mailing list