Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulien Duroure <julien.duroure@gmail.com>2021-07-04 18:54:15 +0300
committerJulien Duroure <julien.duroure@gmail.com>2021-07-04 18:54:15 +0300
commit0cdaac6f9a3e318b1d5db04ade2838d004cd500d (patch)
tree211a162c5899de040d87aee252bc16065a15f725
parent0aa618c849ffef7b11cef81f1712b89e0b0e337b (diff)
glTF exporter: Add option to keep original texture files
WARNING: if you use more than one texture, where pbr standard requires only one, only one texture will be used. This can lead to unexpected results
-rwxr-xr-xio_scene_gltf2/__init__.py18
-rwxr-xr-xio_scene_gltf2/blender/exp/gltf2_blender_gather_image.py173
-rw-r--r--io_scene_gltf2/blender/exp/gltf2_blender_image.py14
3 files changed, 136 insertions, 69 deletions
diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py
index 4c758229..c2c028b9 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, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
- "version": (1, 7, 15),
+ "version": (1, 7, 16),
'blender': (2, 91, 0),
'location': 'File > Import-Export',
'description': 'Import-Export as glTF 2.0',
@@ -173,6 +173,16 @@ class ExportGLTF2_Base:
default='',
)
+ export_keep_originals: BoolProperty(
+ name='Keep original',
+ description=('Keep original textures files if possible. '
+ 'WARNING: if you use more than one texture, '
+ 'where pbr standard requires only one, only one texture will be used.'
+ 'This can lead to unexpected results'
+ ),
+ default=False,
+ )
+
export_texcoords: BoolProperty(
name='UVs',
description='Export UVs (texture coordinates) with meshes',
@@ -517,6 +527,7 @@ class ExportGLTF2_Base:
export_settings['gltf_filedirectory'],
self.export_texture_dir,
)
+ export_settings['gltf_keep_original_textures'] = self.export_keep_originals
export_settings['gltf_format'] = self.export_format
export_settings['gltf_image_format'] = self.export_image_format
@@ -653,7 +664,10 @@ class GLTF_PT_export_main(bpy.types.Panel):
layout.prop(operator, 'export_format')
if operator.export_format == 'GLTF_SEPARATE':
- layout.prop(operator, 'export_texture_dir', icon='FILE_FOLDER')
+ layout.prop(operator, 'export_keep_originals')
+ if operator.export_keep_originals is False:
+ layout.prop(operator, 'export_texture_dir', icon='FILE_FOLDER')
+
layout.prop(operator, 'export_copyright')
layout.prop(operator, 'will_save_settings')
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py
index b0fb2c25..8e441f9c 100755
--- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_image.py
@@ -42,7 +42,12 @@ def gather_image(
mime_type = __gather_mime_type(blender_shader_sockets, image_data, export_settings)
name = __gather_name(image_data, export_settings)
- uri = __gather_uri(image_data, mime_type, name, export_settings)
+ if image_data.original is None:
+ uri = __gather_uri(image_data, mime_type, name, export_settings)
+ else:
+ # Retrieve URI relative to exported glTF files
+ uri = __gather_original_uri(image_data.original.filepath, export_settings)
+
buffer_view = __gather_buffer_view(image_data, mime_type, name, export_settings)
image = __make_image(
@@ -59,6 +64,27 @@ def gather_image(
return image
+def __gather_original_uri(original_uri, export_settings):
+
+ def _path_to_uri(path):
+ import urllib
+ path = os.path.normpath(path)
+ path = path.replace(os.sep, '/')
+ return urllib.parse.quote(path)
+
+ path_to_image = bpy.path.abspath(original_uri)
+ if not os.path.exists(path_to_image): return None
+ try:
+ rel_path = os.path.relpath(
+ path_to_image,
+ start=export_settings[gltf2_blender_export_keys.FILE_DIRECTORY],
+ )
+ except ValueError:
+ # eg. because no relative path between C:\ and D:\ on Windows
+ return None
+ return _path_to_uri(rel_path)
+
+
@cached
def __make_image(buffer_view, extensions, extras, mime_type, name, uri, export_settings):
return gltf2_io.Image(
@@ -99,7 +125,12 @@ def __gather_mime_type(sockets, export_image, export_settings):
return "image/png"
if export_settings["gltf_image_format"] == "AUTO":
- image = export_image.blender_image()
+ if export_image.original is None: # We are going to create a new image
+ image = export_image.blender_image()
+ else:
+ # Using original image
+ image = export_image.original
+
if image is not None and __is_blender_image_a_jpeg(image):
return "image/jpeg"
return "image/png"
@@ -109,30 +140,33 @@ def __gather_mime_type(sockets, export_image, export_settings):
def __gather_name(export_image, export_settings):
- # Find all Blender images used in the ExportImage
- imgs = []
- for fill in export_image.fills.values():
- if isinstance(fill, FillImage):
- img = fill.image
- if img not in imgs:
- imgs.append(img)
-
- # If all the images have the same path, use the common filename
- filepaths = set(img.filepath for img in imgs)
- if len(filepaths) == 1:
- filename = os.path.basename(list(filepaths)[0])
- name, extension = os.path.splitext(filename)
- if extension.lower() in ['.png', '.jpg', '.jpeg']:
- if name:
- return name
-
- # Combine the image names: img1-img2-img3
- names = []
- for img in imgs:
- name, extension = os.path.splitext(img.name)
- names.append(name)
- name = '-'.join(names)
- return name or 'Image'
+ if export_image.original is None:
+ # Find all Blender images used in the ExportImage
+ imgs = []
+ for fill in export_image.fills.values():
+ if isinstance(fill, FillImage):
+ img = fill.image
+ if img not in imgs:
+ imgs.append(img)
+
+ # If all the images have the same path, use the common filename
+ filepaths = set(img.filepath for img in imgs)
+ if len(filepaths) == 1:
+ filename = os.path.basename(list(filepaths)[0])
+ name, extension = os.path.splitext(filename)
+ if extension.lower() in ['.png', '.jpg', '.jpeg']:
+ if name:
+ return name
+
+ # Combine the image names: img1-img2-img3
+ names = []
+ for img in imgs:
+ name, extension = os.path.splitext(img.name)
+ names.append(name)
+ name = '-'.join(names)
+ return name or 'Image'
+ else:
+ return export_image.original.name
@cached
@@ -161,46 +195,55 @@ def __get_image_data(sockets, export_settings) -> ExportImage:
result.shader_node.image))
continue
- # rudimentarily try follow the node tree to find the correct image data.
- src_chan = Channel.R
- for elem in result.path:
- if isinstance(elem.from_node, bpy.types.ShaderNodeSeparateRGB):
- src_chan = {
- 'R': Channel.R,
- 'G': Channel.G,
- 'B': Channel.B,
- }[elem.from_socket.name]
- if elem.from_socket.name == 'Alpha':
- src_chan = Channel.A
-
- dst_chan = None
-
- # some sockets need channel rewriting (gltf pbr defines fixed channels for some attributes)
- if socket.name == 'Metallic':
- dst_chan = Channel.B
- elif socket.name == 'Roughness':
- dst_chan = Channel.G
- elif socket.name == 'Occlusion':
- dst_chan = Channel.R
- elif socket.name == 'Alpha':
- dst_chan = Channel.A
- elif socket.name == 'Clearcoat':
- dst_chan = Channel.R
- elif socket.name == 'Clearcoat Roughness':
- dst_chan = Channel.G
-
- if dst_chan is not None:
- composed_image.fill_image(result.shader_node.image, dst_chan, src_chan)
-
- # Since metal/roughness are always used together, make sure
- # the other channel is filled.
- if socket.name == 'Metallic' and not composed_image.is_filled(Channel.G):
- composed_image.fill_white(Channel.G)
- elif socket.name == 'Roughness' and not composed_image.is_filled(Channel.B):
- composed_image.fill_white(Channel.B)
+ # Assume that user know what he does, and that channels/images are already combined correctly for pbr
+ # If not, we are going to keep only the first texture found
+ # Example : If user set up 2 or 3 different textures for Metallic / Roughness / Occlusion
+ # Only 1 will be used at export
+ # This Warning is displayed in UI of this option
+ if export_settings['gltf_keep_original_textures']:
+ composed_image = ExportImage.from_original(result.shader_node.image)
+
else:
- # copy full image...eventually following sockets might overwrite things
- composed_image = ExportImage.from_blender_image(result.shader_node.image)
+ # rudimentarily try follow the node tree to find the correct image data.
+ src_chan = Channel.R
+ for elem in result.path:
+ if isinstance(elem.from_node, bpy.types.ShaderNodeSeparateRGB):
+ src_chan = {
+ 'R': Channel.R,
+ 'G': Channel.G,
+ 'B': Channel.B,
+ }[elem.from_socket.name]
+ if elem.from_socket.name == 'Alpha':
+ src_chan = Channel.A
+
+ dst_chan = None
+
+ # some sockets need channel rewriting (gltf pbr defines fixed channels for some attributes)
+ if socket.name == 'Metallic':
+ dst_chan = Channel.B
+ elif socket.name == 'Roughness':
+ dst_chan = Channel.G
+ elif socket.name == 'Occlusion':
+ dst_chan = Channel.R
+ elif socket.name == 'Alpha':
+ dst_chan = Channel.A
+ elif socket.name == 'Clearcoat':
+ dst_chan = Channel.R
+ elif socket.name == 'Clearcoat Roughness':
+ dst_chan = Channel.G
+
+ if dst_chan is not None:
+ composed_image.fill_image(result.shader_node.image, dst_chan, src_chan)
+
+ # Since metal/roughness are always used together, make sure
+ # the other channel is filled.
+ if socket.name == 'Metallic' and not composed_image.is_filled(Channel.G):
+ composed_image.fill_white(Channel.G)
+ elif socket.name == 'Roughness' and not composed_image.is_filled(Channel.B):
+ composed_image.fill_white(Channel.B)
+ else:
+ # copy full image...eventually following sockets might overwrite things
+ composed_image = ExportImage.from_blender_image(result.shader_node.image)
return composed_image
diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_image.py b/io_scene_gltf2/blender/exp/gltf2_blender_image.py
index a0952bb5..8ac272d8 100644
--- a/io_scene_gltf2/blender/exp/gltf2_blender_image.py
+++ b/io_scene_gltf2/blender/exp/gltf2_blender_image.py
@@ -64,9 +64,12 @@ class ExportImage:
intelligent decisions about how to encode the image.
"""
- def __init__(self):
+ def __init__(self, original=None):
self.fills = {}
+ # In case of keeping original texture images
+ self.original = original
+
@staticmethod
def from_blender_image(image: bpy.types.Image):
export_image = ExportImage()
@@ -74,6 +77,10 @@ class ExportImage:
export_image.fill_image(image, dst_chan=chan, src_chan=chan)
return export_image
+ @staticmethod
+ def from_original(image: bpy.types.Image):
+ return ExportImage(image)
+
def fill_image(self, image: bpy.types.Image, dst_chan: Channel, src_chan: Channel):
self.fills[dst_chan] = FillImage(image, src_chan)
@@ -84,7 +91,10 @@ class ExportImage:
return chan in self.fills
def empty(self) -> bool:
- return not self.fills
+ if self.original is None:
+ return not self.fills
+ else:
+ return False
def blender_image(self) -> Optional[bpy.types.Image]:
"""If there's an existing Blender image we can use,