# SPDX-License-Identifier: MIT OR GPL-3.0-or-later import dataclasses import datetime import logging from collections.abc import Set as AbstractSet from typing import Optional import bpy from bpy.types import Context, Event, Image, Material, Operator from io_scene_gltf2.io.com import gltf2_io from ..common.logger import get_logger logger = get_logger(__name__) class WM_OT_vrm_io_scene_gltf2_disabled_warning(Operator): bl_label = "glTF 2.0 add-on is disabled" bl_idname = "wm.vrm_gltf2_addon_disabled_warning" bl_options: AbstractSet[str] = {"REGISTER"} def execute(self, _context: Context) -> set[str]: return {"FINISHED"} def invoke(self, context: Context, _event: Event) -> set[str]: return context.window_manager.invoke_props_dialog(self, width=500) def draw(self, _context: Context) -> None: self.layout.label( text='Official add-on "glTF 2.0 format" is required. Please enable it.' ) def image_to_image_bytes( image: Image, export_settings: dict[str, object] ) -> tuple[bytes, str]: mime_type = "image/jpeg" if image.file_format == "JPEG" else "image/png" if bpy.app.version < (3, 6, 0): from io_scene_gltf2.blender.exp.gltf2_blender_image import ( ExportImage as ExportImage_Before_3_6, ) export_image_before_3_6 = ExportImage_Before_3_6.from_blender_image(image) if bpy.app.version < (3, 5, 0): encoded = export_image_before_3_6.encode(mime_type) if isinstance(encoded, bytes): # bpy.app.version < (3, 3, 0) return encoded, mime_type image_bytes, _specular_color_factor = encoded return image_bytes, mime_type image_bytes, _specular_color_factor = export_image_before_3_6.encode( mime_type, export_settings ) return image_bytes, mime_type if bpy.app.version < (4, 3): from io_scene_gltf2.blender.exp.material.extensions.gltf2_blender_image import ( ExportImage as ExportImage_Before_4_3, ) export_image_before_4_3 = ExportImage_Before_4_3.from_blender_image(image) image_bytes, _specular_color_factor = export_image_before_4_3.encode( mime_type, export_settings ) return image_bytes, mime_type from io_scene_gltf2.blender.exp.material.encode_image import ExportImage export_image = ExportImage.from_blender_image(image) image_bytes, _specular_color_factor = export_image.encode( mime_type, export_settings ) return image_bytes, mime_type def init_extras_export() -> None: try: if bpy.app.version < (4, 5): from io_scene_gltf2.blender.com.gltf2_blender_extras import BLACK_LIST else: from io_scene_gltf2.blender.com.extras import BLACK_LIST except ImportError: return key = "vrm_addon_extension" if key not in BLACK_LIST: BLACK_LIST.append(key) def create_export_settings() -> dict[str, object]: # https://github.com/KhronosGroup/glTF-Blender-IO/blob/b9bdc358ebf41e5f14be397d0d612cc8d645a09e/addons/io_scene_gltf2/__init__.py#L1054 loglevel = logging.INFO export_settings: dict[str, object] = { "loglevel": loglevel, # https://github.com/KhronosGroup/glTF-Blender-IO/blob/67b2ed150b0eba08129b970dbe1116c633a77d24/addons/io_scene_gltf2/__init__.py#L522 "timestamp": datetime.datetime.now(datetime.timezone.utc), # https://github.com/KhronosGroup/glTF-Blender-IO/blob/67b2ed150b0eba08129b970dbe1116c633a77d24/addons/io_scene_gltf2/__init__.py#L258-L268 # https://github.com/KhronosGroup/glTF-Blender-IO/blob/67b2ed150b0eba08129b970dbe1116c633a77d24/addons/io_scene_gltf2/__init__.py#L552 "gltf_materials": True, # https://github.com/KhronosGroup/glTF-Blender-IO/blob/67b2ed150b0eba08129b970dbe1116c633a77d24/addons/io_scene_gltf2/__init__.py#L120-L137 # https://github.com/KhronosGroup/glTF-Blender-IO/blob/67b2ed150b0eba08129b970dbe1116c633a77d24/addons/io_scene_gltf2/__init__.py#L532 "gltf_format": "GLB", # https://github.com/KhronosGroup/glTF-Blender-IO/blob/67b2ed150b0eba08129b970dbe1116c633a77d24/addons/io_scene_gltf2/__init__.py#L154-L168 # https://github.com/KhronosGroup/glTF-Blender-IO/blob/67b2ed150b0eba08129b970dbe1116c633a77d24/addons/io_scene_gltf2/__init__.py#L533 "gltf_image_format": "AUTO", # https://github.com/KhronosGroup/glTF-Blender-IO/blob/67b2ed150b0eba08129b970dbe1116c633a77d24/addons/io_scene_gltf2/__init__.py#L329-L333 # https://github.com/KhronosGroup/glTF-Blender-IO/blob/67b2ed150b0eba08129b970dbe1116c633a77d24/addons/io_scene_gltf2/__init__.py#L569 "gltf_extras": True, # https://github.com/KhronosGroup/glTF-Blender-IO/blob/67b2ed150b0eba08129b970dbe1116c633a77d24/addons/io_scene_gltf2/__init__.py#L611-L633 "gltf_user_extensions": [], # https://github.com/KhronosGroup/glTF-Blender-IO/blob/67b2ed150b0eba08129b970dbe1116c633a77d24/addons/io_scene_gltf2/__init__.py#L606 "gltf_binary": bytearray(), # https://github.com/KhronosGroup/glTF-Blender-IO/blob/67b2ed150b0eba08129b970dbe1116c633a77d24/addons/io_scene_gltf2/__init__.py#L176-L184 # https://github.com/KhronosGroup/glTF-Blender-IO/blob/67b2ed150b0eba08129b970dbe1116c633a77d24/addons/io_scene_gltf2/__init__.py#L530 "gltf_keep_original_textures": False, # https://github.com/KhronosGroup/glTF-Blender-IO/blob/bfe4ff8b1b5c26ba17b0531b67798376147d9fa7/addons/io_scene_gltf2/__init__.py#L273-L279 # https://github.com/KhronosGroup/glTF-Blender-IO/blob/bfe4ff8b1b5c26ba17b0531b67798376147d9fa7/addons/io_scene_gltf2/__init__.py#L579 "gltf_original_specular": False, # https://github.com/KhronosGroup/glTF-Blender-IO/blob/e662c281fc830d7ad3ea918d38c6a1881ee143c5/addons/io_scene_gltf2/__init__.py#L208-L214 # https://github.com/KhronosGroup/glTF-Blender-IO/blob/e662c281fc830d7ad3ea918d38c6a1881ee143c5/addons/io_scene_gltf2/__init__.py#L617 "gltf_jpeg_quality": 75, # https://github.com/KhronosGroup/glTF-Blender-IO/blob/a2eac6c4b4ef957b654b61970dc554e3803a642e/addons/io_scene_gltf2/__init__.py#L233-L240 # https://github.com/KhronosGroup/glTF-Blender-IO/blob/a2eac6c4b4ef957b654b61970dc554e3803a642e/addons/io_scene_gltf2/__init__.py#L787 "gltf_image_quality": 75, # https://github.com/KhronosGroup/glTF-Blender-IO/blob/765c1bd8f59ce34d6e346147f379af191969777f/addons/io_scene_gltf2/__init__.py#L785 # https://github.com/KhronosGroup/glTF-Blender-IO/blob/765c1bd8f59ce34d6e346147f379af191969777f/addons/io_scene_gltf2/__init__.py#L201-L208 "gltf_add_webp": False, # https://github.com/KhronosGroup/glTF-Blender-IO/blob/2debd75ace303f3a3b00a43e9d7a9507af32f194/addons/io_scene_gltf2/__init__.py#L941 "exported_images": {}, # https://github.com/KhronosGroup/glTF-Blender-IO/blob/2debd75ace303f3a3b00a43e9d7a9507af32f194/addons/io_scene_gltf2/__init__.py#L942 "exported_texture_nodes": [], # https://github.com/KhronosGroup/glTF-Blender-IO/blob/2debd75ace303f3a3b00a43e9d7a9507af32f194/addons/io_scene_gltf2/__init__.py#L943 "additional_texture_export": [], # https://github.com/KhronosGroup/glTF-Blender-IO/blob/2debd75ace303f3a3b00a43e9d7a9507af32f194/addons/io_scene_gltf2/__init__.py#L944 "additional_texture_export_current_idx": 0, # https://github.com/KhronosGroup/glTF-Blender-IO/blob/2debd75ace303f3a3b00a43e9d7a9507af32f194/addons/io_scene_gltf2/__init__.py#L985-L986 "gltf_unused_textures": False, "gltf_unused_images": False, # for https://github.com/KhronosGroup/glTF-Blender-IO/blob/06f0f908e883add2767fde828f52a013086a17c3/addons/io_scene_gltf2/blender/exp/material/extensions/gltf2_blender_gather_materials_emission.py#L72 "current_paths": {}, # for https://github.com/KhronosGroup/glTF-Blender-IO/blob/06f0f908e883add2767fde828f52a013086a17c3/addons/io_scene_gltf2/blender/exp/material/gltf2_blender_gather_materials.py#L171 # https://github.com/KhronosGroup/glTF-Blender-IO/blob/06f0f908e883add2767fde828f52a013086a17c3/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather.py#L62-L66 "KHR_animation_pointer": {"materials": {}, "lights": {}, "cameras": {}}, } if bpy.app.version < (4, 2): return export_settings if bpy.app.version < (4, 3): from io_scene_gltf2.io.com.gltf2_io_debug import Log as Log_Before_4_3 export_settings["log"] = Log_Before_4_3(loglevel) else: from io_scene_gltf2.io.com.debug import Log export_settings["log"] = Log(loglevel) return export_settings @dataclasses.dataclass class ImportSceneGltfArguments: filepath: str import_pack_images: bool bone_heuristic: str guess_original_bind_pose: bool disable_bone_shape: bool import_scene_as_collection: bool def import_scene_gltf(arguments: ImportSceneGltfArguments) -> set[str]: if bpy.app.version < (4, 2): return bpy.ops.import_scene.gltf( filepath=arguments.filepath, import_pack_images=arguments.import_pack_images, bone_heuristic=arguments.bone_heuristic, guess_original_bind_pose=arguments.guess_original_bind_pose, ) if bpy.app.version < (4, 5): return bpy.ops.import_scene.gltf( filepath=arguments.filepath, import_pack_images=arguments.import_pack_images, bone_heuristic=arguments.bone_heuristic, guess_original_bind_pose=arguments.guess_original_bind_pose, disable_bone_shape=arguments.disable_bone_shape, ) return bpy.ops.import_scene.gltf( filepath=arguments.filepath, import_pack_images=arguments.import_pack_images, bone_heuristic=arguments.bone_heuristic, guess_original_bind_pose=arguments.guess_original_bind_pose, disable_bone_shape=arguments.disable_bone_shape, import_scene_as_collection=arguments.import_scene_as_collection, ) @dataclasses.dataclass class ExportSceneGltfArguments: filepath: str check_existing: bool export_format: str export_extras: bool export_def_bones: bool export_current_frame: bool use_selection: bool use_active_scene: bool export_animations: bool export_armature_object_remove: bool export_rest_position_armature: bool export_all_influences: bool export_vertex_color: str export_lights: bool export_try_sparse_sk: bool export_apply: bool def __invoke_export_scene_gltf(arguments: ExportSceneGltfArguments) -> set[str]: if bpy.app.version < (3, 2): return bpy.ops.export_scene.gltf( filepath=arguments.filepath, check_existing=arguments.check_existing, export_format=arguments.export_format, export_extras=arguments.export_extras, export_def_bones=False, export_current_frame=arguments.export_current_frame, use_selection=arguments.use_selection, export_animations=arguments.export_animations, export_all_influences=arguments.export_all_influences, export_lights=arguments.export_lights, export_apply=arguments.export_apply, ) if bpy.app.version < (3, 6): return bpy.ops.export_scene.gltf( filepath=arguments.filepath, check_existing=arguments.check_existing, export_format=arguments.export_format, export_extras=arguments.export_extras, export_def_bones=(bpy.app.version >= (3, 3)) and arguments.export_def_bones, export_current_frame=arguments.export_current_frame, use_selection=arguments.use_selection, use_active_scene=arguments.use_active_scene, export_animations=arguments.export_animations, export_all_influences=arguments.export_all_influences, export_lights=arguments.export_lights, export_apply=arguments.export_apply, ) if bpy.app.version < (4,): return bpy.ops.export_scene.gltf( filepath=arguments.filepath, check_existing=arguments.check_existing, export_format=arguments.export_format, export_extras=arguments.export_extras, export_def_bones=arguments.export_def_bones, export_current_frame=arguments.export_current_frame, use_selection=arguments.use_selection, use_active_scene=arguments.use_active_scene, export_animations=arguments.export_animations, export_rest_position_armature=arguments.export_rest_position_armature, export_all_influences=arguments.export_all_influences, export_lights=arguments.export_lights, export_apply=arguments.export_apply, ) if bpy.app.version < (4, 2): return bpy.ops.export_scene.gltf( filepath=arguments.filepath, check_existing=arguments.check_existing, export_format=arguments.export_format, export_extras=arguments.export_extras, export_def_bones=arguments.export_def_bones, export_current_frame=arguments.export_current_frame, use_selection=arguments.use_selection, use_active_scene=arguments.use_active_scene, export_animations=arguments.export_animations, export_rest_position_armature=arguments.export_rest_position_armature, export_try_sparse_sk=arguments.export_try_sparse_sk, export_all_influences=arguments.export_all_influences, export_lights=arguments.export_lights, export_apply=arguments.export_apply, ) return bpy.ops.export_scene.gltf( filepath=arguments.filepath, check_existing=arguments.check_existing, export_format=arguments.export_format, export_extras=arguments.export_extras, export_def_bones=arguments.export_def_bones, export_current_frame=arguments.export_current_frame, use_selection=arguments.use_selection, use_active_scene=arguments.use_active_scene, export_animations=arguments.export_animations, export_armature_object_remove=arguments.export_armature_object_remove, export_rest_position_armature=arguments.export_rest_position_armature, export_vertex_color=arguments.export_vertex_color, export_try_sparse_sk=arguments.export_try_sparse_sk, export_all_influences=arguments.export_all_influences, export_lights=arguments.export_lights, export_apply=arguments.export_apply, ) def export_scene_gltf(arguments: ExportSceneGltfArguments) -> set[str]: last_exception: Optional[Exception] = None try: return __invoke_export_scene_gltf(arguments) except RuntimeError as exception: last_exception = exception # export_armature_object_remove is unstable, and the conditions under which it # fails are very complex, so I cannot fully grasp all of them. # https://github.com/KhronosGroup/glTF-Blender-IO/issues/2436 # https://github.com/saturday06/VRM-Addon-for-Blender/issues/1033 if arguments.export_armature_object_remove: arguments.export_armature_object_remove = False logger.warning("retrying with `export_armature_object_remove = False`") try: return __invoke_export_scene_gltf(arguments) except RuntimeError as exception: last_exception = exception # https://github.com/saturday06/VRM-Addon-for-Blender/commit/26f566d43b15c7a403c0f007b0290f9aef569114 if arguments.export_animations: arguments.export_animations = False logger.warning("retrying with `export_animations = False`") try: return __invoke_export_scene_gltf(arguments) except RuntimeError as exception: last_exception = exception raise last_exception def gather_gltf2_io_material( material: Material, export_settings: dict[str, object] ) -> Optional[gltf2_io.Material]: if bpy.app.version < (3, 2): from io_scene_gltf2.blender.exp.gltf2_blender_gather_materials import ( gather_material as gather_material_before_3_2, ) return gather_material_before_3_2(material, export_settings) if bpy.app.version < (3, 6): from io_scene_gltf2.blender.exp.gltf2_blender_gather_materials import ( gather_material as gather_material_before_3_6, ) return gather_material_before_3_6(material, 0, export_settings) if bpy.app.version < (4, 0): from io_scene_gltf2.blender.exp.material.gltf2_blender_gather_materials import ( gather_material as gather_material_before_4_0, ) return gather_material_before_4_0(material, 0, export_settings) if bpy.app.version < (4, 3): from io_scene_gltf2.blender.exp.material.gltf2_blender_gather_materials import ( gather_material as gather_material_before_4_3, ) gltf2_io_material, _ = gather_material_before_4_3(material, export_settings) return gltf2_io_material from io_scene_gltf2.blender.exp.material.materials import gather_material gltf2_io_material, _ = gather_material(material, export_settings) return gltf2_io_material