Skip to content
Snippets Groups Projects
__init__.py 17 KiB
Newer Older
  • Learn to ignore specific revisions
  • # Copyright 2018 The glTF-Blender-IO authors.
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #     http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    
    #
    # Imports
    #
    
    import os
    import bpy
    from bpy_extras.io_utils import ImportHelper, ExportHelper
    from bpy.types import Operator, AddonPreferences
    
    
    Julien Duroure's avatar
    Julien Duroure committed
    from .io.com.gltf2_io_debug import Log
    
    
    from bpy.props import (CollectionProperty,
                           StringProperty,
                           BoolProperty,
                           EnumProperty,
                           FloatProperty,
                           IntProperty)
    
    #
    # Globals
    #
    
    bl_info = {
        'name': 'glTF 2.0 format',
        'author': 'Julien Duroure, Norbert Nopper, Urs Hanselmann & Moritz Becher',
        "version": (0, 0, 1),
        'blender': (2, 80, 0),
        'location': 'File > Import-Export',
        'description': 'Import-Export as glTF 2.0',
        'warning': '',
    
        'wiki_url': "https://github.com/KhronosGroup/glTF-Blender-IO",
        'tracker_url': "https://github.com/KhronosGroup/glTF-Blender-IO/issues/",
    
        'support': 'OFFICIAL',
    
        'category': 'Import-Export'}
    
    
    #
    #  Functions / Classes.
    #
    
    
    class GLTF2ExportSettings(bpy.types.Operator):
    
        """Save the export settings on export (saved in .blend). """
        """Toggle off to clear settings"""
    
        bl_label = "Save Settings"
        bl_idname = "scene.gltf2_export_settings_set"
    
        def execute(self, context):
            operator = context.active_operator
            operator.will_save_settings = not operator.will_save_settings
            if not operator.will_save_settings:
                # clear settings
                context.scene.pop(operator.scene_key)
            return {"FINISHED"}
    
    
    class ExportGLTF2_Base:
    
        # TODO: refactor to avoid boilerplate
    
    
        export_format: EnumProperty(
            name='Format',
    
            items=(('GLB', 'glTF Binary (.glb)',
                    'Exports a single file, with all data packed in binary form. '
                    'Most efficient and portable, but more difficult to edit later'),
                   ('GLTF_EMBEDDED', 'glTF Embedded (.gltf)',
                    'Exports a single file, with all data packed in JSON. '
                    'Less efficient than binary, but easier to edit later'),
                   ('GLTF_SEPARATE', 'glTF Separate (.gltf + .bin + textures)',
                    'Exports multiple files, with separate JSON, binary and texture data. '
                    'Easiest to edit later')),
    
            description=(
                'Output format and embedding options. Binary is most efficient, '
                'but JSON (embedded or separate) may be easier to edit later'
            ),
    
        export_copyright: StringProperty(
            name='Copyright',
    
            description='Legal rights and conditions for the model',
    
            default=''
        )
    
        export_texcoords: BoolProperty(
    
            name='UVs',
            description='Export UVs (texture coordinates) with meshes',
    
            default=True
        )
    
        export_normals: BoolProperty(
    
            name='Normals',
            description='Export vertex normals with meshes',
    
            default=True
        )
    
        export_tangents: BoolProperty(
    
            name='Tangents',
            description='Export vertex tangents with meshes',
    
        )
    
        export_materials: BoolProperty(
    
            name='Materials',
            description='Export materials',
    
            default=True
        )
    
        export_colors: BoolProperty(
    
            name='Vertex Colors',
            description='Export vertex colors with meshes',
    
            default=True
        )
    
        export_cameras: BoolProperty(
    
            name='Cameras',
            description='Export cameras',
    
            default=False
        )
    
        export_selected: BoolProperty(
    
            name='Selected Objects',
            description='Export selected objects only',
    
        # export_layers: BoolProperty(
    
        #     name='All layers',
        #     description='Export all layers, rather than just the first',
    
        #     default=True
        # )
    
    
        export_extras: BoolProperty(
    
            name='Custom Properties',
            description='Export custom properties as glTF extras',
    
            default=False
        )
    
        export_yup: BoolProperty(
    
            name='+Y Up',
            description='Export using glTF convention, +Y up',
    
            default=True
        )
    
        export_apply: BoolProperty(
    
            name='Apply Modifiers',
            description='Apply modifiers to mesh objects',
    
            default=False
        )
    
        export_animations: BoolProperty(
    
            name='Animations',
            description='Exports active actions and NLA tracks as glTF animations',
    
            default=True
        )
    
        export_frame_range: BoolProperty(
    
            name='Limit to Playback Range',
            description='Clips animations to selected playback range',
    
            default=True
        )
    
        export_frame_step: IntProperty(
    
            name='Sampling Rate',
            description='How often to evaluate animated values (in frames)',
    
            default=1,
            min=1,
            max=120
        )
    
        export_move_keyframes: BoolProperty(
    
            name='Keyframes Start at 0',
            description='Keyframes start at 0, instead of 1',
    
            default=True
        )
    
        export_force_sampling: BoolProperty(
    
            name='Always Sample Animations',
            description='Apply sampling to all animations',
    
            default=False
        )
    
        export_current_frame: BoolProperty(
    
            name='Use Current Frame',
            description='Export the scene in the current animation frame',
    
            default=True
        )
    
        export_skins: BoolProperty(
    
            name='Skinning',
            description='Export skinning (armature) data',
    
            default=True
        )
    
        export_bake_skins: BoolProperty(
    
            name='Bake Skinning Constraints',
            description='Apply skinning constraints to armatures',
    
        export_all_influences: BoolProperty(
    
            name='Include All Bone Influences',
            description='Allow >4 joint vertex influences. Models may appear' \
                ' incorrectly in many viewers',
    
        export_morph: BoolProperty(
    
            name='Shape Keys',
            description='Export shape keys (morph targets)',
    
            default=True
        )
    
        export_morph_normal: BoolProperty(
    
            name='Shape Key Normals',
            description='Export vertex normals with shape keys (morph targets)',
    
            default=True
        )
    
        export_morph_tangent: BoolProperty(
    
            name='Shape Key Tangents',
            description='Export vertex tangents with shape keys (morph targets)',
    
        )
    
        export_lights: BoolProperty(
    
            name='Punctual Lights',
            description='Export directional, point, and spot lights. Uses ' \
                ' "KHR_lights_punctual" glTF extension',
    
            default=False
        )
    
        export_texture_transform: BoolProperty(
    
            name='Texture Transforms',
            description='Export texture or UV position, rotation, and scale.' \
                ' Uses "KHR_texture_transform" glTF extension',
    
            default=False
        )
    
        export_displacement: BoolProperty(
    
            name='Displacement Textures (EXPERIMENTAL)',
            description='EXPERIMENTAL: Export displacement textures. Uses' \
                ' incomplete "KHR_materials_displacement" glTF extension',
    
            default=False
        )
    
        will_save_settings: BoolProperty(default=False)
    
        # Custom scene property for saving settings
        scene_key = "glTF2ExportSettings"
    
        #
    
        def invoke(self, context, event):
            settings = context.scene.get(self.scene_key)
            self.will_save_settings = False
            if settings:
                try:
                    for (k, v) in settings.items():
                        setattr(self, k, v)
                    self.will_save_settings = True
    
    
                except (AttributeError, TypeError):
    
                    self.report({"ERROR"}, "Loading export settings failed. Removed corrupted settings")
                    del context.scene[self.scene_key]
    
            return ExportHelper.invoke(self, context, event)
    
        def save_settings(self, context):
            # find all export_ props
            all_props = self.properties
    
            export_props = {x: getattr(self, x) for x in dir(all_props)
    
                            if x.startswith("export_") and all_props.get(x) is not None}
    
            context.scene[self.scene_key] = export_props
    
        def execute(self, context):
    
            import datetime
    
            from .blender.exp import gltf2_blender_export
    
            if self.will_save_settings:
                self.save_settings(context)
    
    
            if self.export_format == 'GLB':
                self.filename_ext = '.glb'
            else:
                self.filename_ext = '.gltf'
    
    
            # All custom export settings are stored in this container.
            export_settings = {}
    
    
            export_settings['timestamp'] = datetime.datetime.now()
    
    
            export_settings['gltf_filepath'] = bpy.path.ensure_ext(self.filepath, self.filename_ext)
            export_settings['gltf_filedirectory'] = os.path.dirname(export_settings['gltf_filepath']) + '/'
    
            export_settings['gltf_format'] = self.export_format
            export_settings['gltf_copyright'] = self.export_copyright
            export_settings['gltf_texcoords'] = self.export_texcoords
            export_settings['gltf_normals'] = self.export_normals
            export_settings['gltf_tangents'] = self.export_tangents and self.export_normals
            export_settings['gltf_materials'] = self.export_materials
            export_settings['gltf_colors'] = self.export_colors
            export_settings['gltf_cameras'] = self.export_cameras
            export_settings['gltf_selected'] = self.export_selected
    
            export_settings['gltf_layers'] = True #self.export_layers
    
            export_settings['gltf_extras'] = self.export_extras
            export_settings['gltf_yup'] = self.export_yup
            export_settings['gltf_apply'] = self.export_apply
            export_settings['gltf_animations'] = self.export_animations
            if self.export_animations:
                export_settings['gltf_current_frame'] = False
                export_settings['gltf_frame_range'] = self.export_frame_range
                export_settings['gltf_move_keyframes'] = self.export_move_keyframes
                export_settings['gltf_force_sampling'] = self.export_force_sampling
            else:
                export_settings['gltf_current_frame'] = self.export_current_frame
                export_settings['gltf_frame_range'] = False
                export_settings['gltf_move_keyframes'] = False
                export_settings['gltf_force_sampling'] = False
            export_settings['gltf_skins'] = self.export_skins
            if self.export_skins:
                export_settings['gltf_bake_skins'] = self.export_bake_skins
    
                export_settings['gltf_all_vertex_influences'] = self.export_all_influences
    
            else:
                export_settings['gltf_bake_skins'] = False
    
                export_settings['gltf_all_vertex_influences'] = False
    
            export_settings['gltf_frame_step'] = self.export_frame_step
            export_settings['gltf_morph'] = self.export_morph
            if self.export_morph:
                export_settings['gltf_morph_normal'] = self.export_morph_normal
            else:
                export_settings['gltf_morph_normal'] = False
            if self.export_morph and self.export_morph_normal:
                export_settings['gltf_morph_tangent'] = self.export_morph_tangent
            else:
                export_settings['gltf_morph_tangent'] = False
    
            export_settings['gltf_lights'] = self.export_lights
            export_settings['gltf_texture_transform'] = self.export_texture_transform
            export_settings['gltf_displacement'] = self.export_displacement
    
            export_settings['gltf_binary'] = bytearray()
            export_settings['gltf_binaryfilename'] = os.path.splitext(os.path.basename(self.filepath))[0] + '.bin'
    
    
            return gltf2_blender_export.save(context, export_settings)
    
    
        def draw(self, context):
            layout = self.layout
    
            #
    
            col = layout.box().column()
    
            col.label(text='General:', icon='PREFERENCES')
            col.prop(self, 'export_format')
    
            col.prop(self, 'export_selected')
    
            #col.prop(self, 'export_layers')
    
            col.prop(self, 'export_apply')
    
            col.prop(self, 'export_yup')
            col.prop(self, 'export_extras')
            col.prop(self, 'export_copyright')
    
    
            col = layout.box().column()
    
            col.label(text='Meshes:', icon='MESH_DATA')
    
            col.prop(self, 'export_texcoords')
            col.prop(self, 'export_normals')
            if self.export_normals:
                col.prop(self, 'export_tangents')
            col.prop(self, 'export_colors')
    
            col = layout.box().column()
    
            col.label(text='Objects:', icon='OBJECT_DATA')
    
            col.prop(self, 'export_cameras')
    
            col.prop(self, 'export_lights')
    
    
            col = layout.box().column()
    
            col.label(text='Materials:', icon='MATERIAL_DATA')
    
            col.prop(self, 'export_materials')
            col.prop(self, 'export_texture_transform')
    
    
            col = layout.box().column()
    
            col.label(text='Animation:', icon='ARMATURE_DATA')
    
            col.prop(self, 'export_animations')
            if self.export_animations:
                col.prop(self, 'export_frame_range')
                col.prop(self, 'export_frame_step')
                col.prop(self, 'export_move_keyframes')
                col.prop(self, 'export_force_sampling')
            else:
                col.prop(self, 'export_current_frame')
            col.prop(self, 'export_skins')
            if self.export_skins:
                col.prop(self, 'export_bake_skins')
    
                col.prop(self, 'export_all_influences')
    
            col.prop(self, 'export_morph')
            if self.export_morph:
                col.prop(self, 'export_morph_normal')
                if self.export_morph_normal:
                    col.prop(self, 'export_morph_tangent')
    
            row = layout.row()
            row.operator(
                GLTF2ExportSettings.bl_idname,
                text=GLTF2ExportSettings.bl_label,
                icon="%s" % "PINNED" if self.will_save_settings else "UNPINNED")
    
    
    
    class ExportGLTF2(bpy.types.Operator, ExportGLTF2_Base, ExportHelper):
    
        """Export scene as glTF 2.0 file"""
        bl_idname = 'export_scene.gltf'
    
        bl_label = 'glTF 2.0 (.glb/.gltf)'
    
        filter_glob: StringProperty(default='*.glb;*.gltf', options={'HIDDEN'})
    
    Julien Duroure's avatar
    Julien Duroure committed
    def menu_func_export(self, context):
    
        self.layout.operator(ExportGLTF2.bl_idname, text='glTF 2.0 (.glb/.gltf)')
    
    class ImportGLTF2(Operator, ImportHelper):
    
        bl_idname = 'import_scene.gltf'
    
        bl_label = 'glTF 2.0 (.glb/.gltf)'
    
        filter_glob: StringProperty(default="*.glb;*.gltf", options={'HIDDEN'})
    
        loglevel: EnumProperty(
            items=Log.get_levels(),
            name="Log Level",
            description="Set level of log to display",
            default=Log.default())
    
    
        import_pack_images: BoolProperty(
            name='Pack images',
    
            description='Pack all images into .blend file',
    
            default=True
        )
    
        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")
    
        def draw(self, context):
            layout = self.layout
    
            layout.prop(self, 'loglevel')
            layout.prop(self, 'import_pack_images')
            layout.prop(self, 'import_shading')
    
        def execute(self, context):
            return self.import_gltf2(context)
    
        def import_gltf2(self, context):
    
    Julien Duroure's avatar
    Julien Duroure committed
            from .io.imp.gltf2_io_gltf import glTFImporter
            from .blender.imp.gltf2_blender_gltf import BlenderGlTF
    
    
            import_settings = self.as_keywords()
    
            self.gltf_importer = glTFImporter(self.filepath, import_settings)
            success, txt = self.gltf_importer.read()
            if not success:
                self.report({'ERROR'}, txt)
                return {'CANCELLED'}
            success, txt = self.gltf_importer.checks()
            if not success:
                self.report({'ERROR'}, txt)
                return {'CANCELLED'}
            self.gltf_importer.log.critical("Data are loaded, start creating Blender stuff")
            BlenderGlTF.create(self.gltf_importer)
            self.gltf_importer.log.critical("glTF import is now finished")
            self.gltf_importer.log.removeHandler(self.gltf_importer.log_handler)
    
            return {'FINISHED'}
    
    
    def menu_func_import(self, context):
    
        self.layout.operator(ImportGLTF2.bl_idname, text=ImportGLTF2.bl_label)
    
    
    
    classes = (
        GLTF2ExportSettings,
    
    )
    
    
    def register():
        for c in classes:
            bpy.utils.register_class(c)
        # bpy.utils.register_module(__name__)
    
        # add to the export / import menu
    
    Julien Duroure's avatar
    Julien Duroure committed
        bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
    
        bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
    
    
    def unregister():
        for c in classes:
            bpy.utils.unregister_class(c)
        # bpy.utils.unregister_module(__name__)
    
        # remove from the export / import menu
    
    Julien Duroure's avatar
    Julien Duroure committed
        bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
    
        bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)