Skip to content
Snippets Groups Projects
__init__.py 42.4 KiB
Newer Older
    bl_idname = 'export_scene.gltf'
    bl_label = 'Export glTF 2.0'

    filename_ext = ''

    filter_glob: StringProperty(default='*.glb;*.gltf', options={'HIDDEN'})


def menu_func_export(self, context):
    self.layout.operator(ExportGLTF2.bl_idname, text='glTF 2.0 (.glb/.gltf)')


class ImportGLTF2(Operator, ImportHelper):
    """Load a glTF 2.0 file"""
    bl_idname = 'import_scene.gltf'
    bl_label = 'Import glTF 2.0'
    bl_options = {'REGISTER', 'UNDO'}

    filter_glob: StringProperty(default="*.glb;*.gltf", options={'HIDDEN'})

    files: CollectionProperty(
        name="File Path",
        type=bpy.types.OperatorFileListElement,
    )

    loglevel: IntProperty(
        name='Log Level',
        description="Log Level")

    import_pack_images: BoolProperty(
        name='Pack Images',
        description='Pack all images into .blend file',
        default=True
    )

    merge_vertices: BoolProperty(
        name='Merge Vertices',
        description=(
            'The glTF format requires discontinuous normals, UVs, and '
            'other vertex attributes to be stored as separate vertices, '
Julien Duroure's avatar
Julien Duroure committed
            'as required for rendering on typical graphics hardware. '
            'This option attempts to combine co-located vertices where possible. '
            'Currently cannot combine verts with different normals'
        ),
        default=False,
    )

    import_shading: EnumProperty(
        name="Shading",
        items=(("NORMALS", "Use Normal Data", ""),
               ("FLAT", "Flat Shading", ""),
               ("SMOOTH", "Smooth Shading", "")),
        description="How normals are computed during import",
        default="NORMALS")

    bone_heuristic: EnumProperty(
        name="Bone Dir",
        items=(
            ("BLENDER", "Blender (best for re-importing)",
Julien Duroure's avatar
Julien Duroure committed
                "Good for re-importing glTFs exported from Blender. "
                "Bone tips are placed on their local +Y axis (in glTF space)"),
            ("TEMPERANCE", "Temperance (average)",
Julien Duroure's avatar
Julien Duroure committed
                "Decent all-around strategy. "
                "A bone with one child has its tip placed on the local axis "
                "closest to its child"),
            ("FORTUNE", "Fortune (may look better, less accurate)",
Julien Duroure's avatar
Julien Duroure committed
                "Might look better than Temperance, but also might have errors. "
                "A bone with one child has its tip placed at its child's root. "
                "Non-uniform scalings may get messed up though, so beware"),
        ),
        description="Heuristic for placing bones. Tries to make bones pretty",
        default="TEMPERANCE",
    )

    guess_original_bind_pose: BoolProperty(
        name='Guess Original Bind Pose',
        description=(
            'Try to guess the original bind pose for skinned meshes from '
Julien Duroure's avatar
Julien Duroure committed
            'the inverse bind matrices. '
            'When off, use default/rest pose as bind pose'
        ),
        default=True,
    )

    def draw(self, context):
        layout = self.layout

        layout.use_property_split = True
        layout.use_property_decorate = False  # No animation.

        layout.prop(self, 'import_pack_images')
        layout.prop(self, 'merge_vertices')
        layout.prop(self, 'import_shading')
        layout.prop(self, 'guess_original_bind_pose')
        layout.prop(self, 'bone_heuristic')

    def invoke(self, context, event):
        import sys
        preferences = bpy.context.preferences
        for addon_name in preferences.addons.keys():
            try:
                if hasattr(sys.modules[addon_name], 'glTF2ImportUserExtension') or hasattr(sys.modules[addon_name], 'glTF2ImportUserExtensions'):
                    importer_extension_panel_unregister_functors.append(sys.modules[addon_name].register_panel())
            except Exception:
                pass

        self.has_active_importer_extensions = len(importer_extension_panel_unregister_functors) > 0
        return ImportHelper.invoke(self, context, event)

    def execute(self, context):
        return self.import_gltf2(context)

    def import_gltf2(self, context):
        import os

        self.set_debug_log()
        import_settings = self.as_keywords()

        user_extensions = []

        import sys
        preferences = bpy.context.preferences
        for addon_name in preferences.addons.keys():
            try:
                module = sys.modules[addon_name]
            except Exception:
                continue
            if hasattr(module, 'glTF2ImportUserExtension'):
                extension_ctor = module.glTF2ImportUserExtension
                user_extensions.append(extension_ctor())
        import_settings['import_user_extensions'] = user_extensions

        if self.files:
            # Multiple file import
            ret = {'CANCELLED'}
            dirname = os.path.dirname(self.filepath)
            for file in self.files:
                path = os.path.join(dirname, file.name)
                if self.unit_import(path, import_settings) == {'FINISHED'}:
                    ret = {'FINISHED'}
            return ret
        else:
            # Single file import
            return self.unit_import(self.filepath, import_settings)

    def unit_import(self, filename, import_settings):
        import time
        from .io.imp.gltf2_io_gltf import glTFImporter, ImportError
        from .blender.imp.gltf2_blender_gltf import BlenderGlTF

        try:
            gltf_importer = glTFImporter(filename, import_settings)
            gltf_importer.read()
            gltf_importer.checks()

            print("Data are loaded, start creating Blender stuff")

            start_time = time.time()
            BlenderGlTF.create(gltf_importer)
            elapsed_s = "{:.2f}s".format(time.time() - start_time)
            print("glTF import finished in " + elapsed_s)

            gltf_importer.log.removeHandler(gltf_importer.log_handler)

            return {'FINISHED'}

        except ImportError as e:
            self.report({'ERROR'}, e.args[0])
            return {'CANCELLED'}

    def set_debug_log(self):
        import logging
        if bpy.app.debug_value == 0:
            self.loglevel = logging.CRITICAL
        elif bpy.app.debug_value == 1:
            self.loglevel = logging.ERROR
        elif bpy.app.debug_value == 2:
            self.loglevel = logging.WARNING
        elif bpy.app.debug_value == 3:
            self.loglevel = logging.INFO
        else:
            self.loglevel = logging.NOTSET


class GLTF_AddonPreferences(bpy.types.AddonPreferences):
    bl_idname = __package__

    settings_node_ui : bpy.props.BoolProperty(
            default= False,
            description="Displays glTF Settings node in Shader Editor (Menu Add > Output)"
            )


    def draw(self, context):
        layout = self.layout
        row = layout.row()
        row.prop(self, "settings_node_ui", text="Shader Editor Add-ons")

def menu_func_import(self, context):
    self.layout.operator(ImportGLTF2.bl_idname, text='glTF 2.0 (.glb/.gltf)')


classes = (
    ExportGLTF2,
    GLTF_PT_export_main,
    GLTF_PT_export_include,
    GLTF_PT_export_transform,
    GLTF_PT_export_geometry,
    GLTF_PT_export_geometry_compression,
    GLTF_PT_export_animation,
    GLTF_PT_export_animation_export,
    GLTF_PT_export_animation_shapekeys,
    GLTF_PT_export_animation_skinning,
    GLTF_PT_export_user_extensions,
    GLTF_PT_import_user_extensions,
    GLTF_AddonPreferences
    import io_scene_gltf2.blender.com.gltf2_blender_ui as blender_ui
    for c in classes:
        bpy.utils.register_class(c)
    # bpy.utils.register_module(__name__)

    # add to the export / import menu
    bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
    bpy.types.TOPBAR_MT_file_import.append(menu_func_import)


def unregister():
    import io_scene_gltf2.blender.com.gltf2_blender_ui as blender_ui
    for c in classes:
        bpy.utils.unregister_class(c)
    for f in exporter_extension_panel_unregister_functors:
        f()
    exporter_extension_panel_unregister_functors.clear()

    for f in importer_extension_panel_unregister_functors:
    importer_extension_panel_unregister_functors.clear()
    # bpy.utils.unregister_module(__name__)

    # remove from the export / import menu
    bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
    bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)