[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