# Copyright 2018-2019 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. 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": (0, 9, 54), 'blender': (2, 81, 6), 'location': 'File > Import-Export', 'description': 'Import-Export as glTF 2.0', 'warning': '', 'wiki_url': "https://docs.blender.org/manual/en/latest/addons/io_scene_gltf2.html", 'tracker_url': "https://github.com/KhronosGroup/glTF-Blender-IO/issues/", 'support': 'OFFICIAL', 'category': 'Import-Export', } def get_version_string(): return str(bl_info['version'][0]) + '.' + str(bl_info['version'][1]) + '.' + str(bl_info['version'][2]) # # Script reloading (if the user calls 'Reload Scripts' from Blender) # def reload_package(module_dict_main): import importlib from pathlib import Path def reload_package_recursive(current_dir, module_dict): for path in current_dir.iterdir(): if "__init__" in str(path) or path.stem not in module_dict: continue if path.is_file() and path.suffix == ".py": importlib.reload(module_dict[path.stem]) elif path.is_dir(): reload_package_recursive(path, module_dict[path.stem].__dict__) reload_package_recursive(Path(__file__).parent, module_dict_main) if "bpy" in locals(): reload_package(locals()) import bpy from bpy.props import (StringProperty, BoolProperty, EnumProperty, IntProperty) from bpy.types import Operator from bpy_extras.io_utils import ImportHelper, ExportHelper from io_scene_gltf2.io.exp import gltf2_io_draco_compression_extension # # Functions / Classes. # class ExportGLTF2_Base: # TODO: refactor to avoid boilerplate def __init__(self): self.is_draco_available = gltf2_io_draco_compression_extension.dll_exists() bl_options = {'UNDO', 'PRESET'} 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' ), default='GLB' ) ui_tab: EnumProperty( items=(('GENERAL', "General", "General settings"), ('MESHES', "Meshes", "Mesh settings"), ('OBJECTS', "Objects", "Object settings"), ('ANIMATION', "Animation", "Animation settings")), name="ui_tab", description="Export setting categories", ) export_copyright: StringProperty( name='Copyright', description='Legal rights and conditions for the model', default='' ) export_image_format: EnumProperty( name='Images', items=(('NAME', 'Automatic', 'Determine the image format from the blender image name'), ('JPEG', 'JPEG Format (.jpg)', 'Encode and save textures as .jpg files. Be aware of a possible loss in quality'), ('PNG', 'PNG Format (.png)', 'Encode and save textures as .png files') ), description=( 'Output format for images. PNG is lossless and generally preferred, but JPEG might be preferable for web ' 'applications due to the smaller file size' ), default='NAME' ) 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_draco_mesh_compression_enable: BoolProperty( name='Draco mesh compression', description='Compress mesh using Draco', default=False ) export_draco_mesh_compression_level: IntProperty( name='Compression level', description='Compression level (0 = most speed, 6 = most compression, higher values currently not supported)', default=6, min=0, max=6 ) export_draco_position_quantization: IntProperty( name='Position quantization bits', description='Quantization bits for position values (0 = no quantization)', default=14, min=0, max=30 ) export_draco_normal_quantization: IntProperty( name='Normal quantization bits', description='Quantization bits for normal values (0 = no quantization)', default=10, min=0, max=30 ) export_draco_texcoord_quantization: IntProperty( name='Texcoord quantization bits', description='Quantization bits for texture coordinate values (0 = no quantization)', default=12, min=0, max=30 ) export_tangents: BoolProperty( name='Tangents', description='Export vertex tangents with meshes', default=False ) 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', default=False ) 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 (excluding Armatures) 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_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=False ) export_skins: BoolProperty( name='Skinning', description='Export skinning (armature) data', default=True ) export_all_influences: BoolProperty( name='Include All Bone Influences', description='Allow >4 joint vertex influences. Models may appear incorrectly in many viewers', default=False ) 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)', default=False ) export_lights: BoolProperty( name='Punctual Lights', description='Export directional, point, and spot lights. ' 'Uses "KHR_lights_punctual" 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( name='Remember Export Settings', description='Store glTF export settings in the Blender project', 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 os 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_image_format'] = self.export_image_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 if self.is_draco_available: export_settings['gltf_draco_mesh_compression'] = self.export_draco_mesh_compression_enable export_settings['gltf_draco_mesh_compression_level'] = self.export_draco_mesh_compression_level export_settings['gltf_draco_position_quantization'] = self.export_draco_position_quantization export_settings['gltf_draco_normal_quantization'] = self.export_draco_normal_quantization export_settings['gltf_draco_texcoord_quantization'] = self.export_draco_texcoord_quantization else: export_settings['gltf_draco_mesh_compression'] = False 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_current_frame'] = self.export_current_frame export_settings['gltf_animations'] = self.export_animations if self.export_animations: export_settings['gltf_frame_range'] = self.export_frame_range export_settings['gltf_force_sampling'] = self.export_force_sampling else: 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_all_vertex_influences'] = self.export_all_influences else: 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_displacement'] = self.export_displacement export_settings['gltf_binary'] = bytearray() export_settings['gltf_binaryfilename'] = os.path.splitext(os.path.basename( bpy.path.ensure_ext(self.filepath,self.filename_ext)))[0] + '.bin' return gltf2_blender_export.save(context, export_settings) def draw(self, context): pass class GLTF_PT_export_main(bpy.types.Panel): bl_space_type = 'FILE_BROWSER' bl_region_type = 'TOOL_PROPS' bl_label = "" bl_parent_id = "FILE_PT_operator" bl_options = {'HIDE_HEADER'} @classmethod def poll(cls, context): sfile = context.space_data operator = sfile.active_operator return operator.bl_idname == "EXPORT_SCENE_OT_gltf" def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False # No animation. sfile = context.space_data operator = sfile.active_operator layout.prop(operator, 'export_format') layout.prop(operator, 'export_copyright') layout.prop(operator, 'will_save_settings') class GLTF_PT_export_include(bpy.types.Panel): bl_space_type = 'FILE_BROWSER' bl_region_type = 'TOOL_PROPS' bl_label = "Include" bl_parent_id = "FILE_PT_operator" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): sfile = context.space_data operator = sfile.active_operator return operator.bl_idname == "EXPORT_SCENE_OT_gltf" def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False # No animation. sfile = context.space_data operator = sfile.active_operator layout.prop(operator, 'export_selected') layout.prop(operator, 'export_extras') layout.prop(operator, 'export_cameras') layout.prop(operator, 'export_lights') class GLTF_PT_export_transform(bpy.types.Panel): bl_space_type = 'FILE_BROWSER' bl_region_type = 'TOOL_PROPS' bl_label = "Transform" bl_parent_id = "FILE_PT_operator" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): sfile = context.space_data operator = sfile.active_operator return operator.bl_idname == "EXPORT_SCENE_OT_gltf" def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False # No animation. sfile = context.space_data operator = sfile.active_operator layout.prop(operator, 'export_yup') class GLTF_PT_export_geometry(bpy.types.Panel): bl_space_type = 'FILE_BROWSER' bl_region_type = 'TOOL_PROPS' bl_label = "Geometry" bl_parent_id = "FILE_PT_operator" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): sfile = context.space_data operator = sfile.active_operator return operator.bl_idname == "EXPORT_SCENE_OT_gltf" def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False # No animation. sfile = context.space_data operator = sfile.active_operator layout.prop(operator, 'export_apply') layout.prop(operator, 'export_texcoords') layout.prop(operator, 'export_normals') col = layout.column() col.active = operator.export_normals col.prop(operator, 'export_tangents') layout.prop(operator, 'export_colors') layout.prop(operator, 'export_materials') col = layout.column() col.active = operator.export_materials col.prop(operator, 'export_image_format') class GLTF_PT_export_geometry_compression(bpy.types.Panel): bl_space_type = 'FILE_BROWSER' bl_region_type = 'TOOL_PROPS' bl_label = "Compression" bl_parent_id = "GLTF_PT_export_geometry" bl_options = {'DEFAULT_CLOSED'} def __init__(self): self.is_draco_available = gltf2_io_draco_compression_extension.dll_exists() @classmethod def poll(cls, context): sfile = context.space_data operator = sfile.active_operator if operator.is_draco_available: return operator.bl_idname == "EXPORT_SCENE_OT_gltf" def draw_header(self, context): sfile = context.space_data operator = sfile.active_operator self.layout.prop(operator, "export_draco_mesh_compression_enable", text="") def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False # No animation. sfile = context.space_data operator = sfile.active_operator layout.active = operator.export_draco_mesh_compression_enable layout.prop(operator, 'export_draco_mesh_compression_level') col = layout.column(align=True) col.prop(operator, 'export_draco_position_quantization', text="Quantize Position") col.prop(operator, 'export_draco_normal_quantization', text="Normal") col.prop(operator, 'export_draco_texcoord_quantization', text="Tex Coords") class GLTF_PT_export_animation(bpy.types.Panel): bl_space_type = 'FILE_BROWSER' bl_region_type = 'TOOL_PROPS' bl_label = "Animation" bl_parent_id = "FILE_PT_operator" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): sfile = context.space_data operator = sfile.active_operator return operator.bl_idname == "EXPORT_SCENE_OT_gltf" def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False # No animation. sfile = context.space_data operator = sfile.active_operator layout.prop(operator, 'export_current_frame') class GLTF_PT_export_animation_export(bpy.types.Panel): bl_space_type = 'FILE_BROWSER' bl_region_type = 'TOOL_PROPS' bl_label = "Animation" bl_parent_id = "GLTF_PT_export_animation" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): sfile = context.space_data operator = sfile.active_operator return operator.bl_idname == "EXPORT_SCENE_OT_gltf" def draw_header(self, context): sfile = context.space_data operator = sfile.active_operator self.layout.prop(operator, "export_animations", text="") def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False # No animation. sfile = context.space_data operator = sfile.active_operator layout.active = operator.export_animations layout.prop(operator, 'export_frame_range') layout.prop(operator, 'export_frame_step') layout.prop(operator, 'export_force_sampling') class GLTF_PT_export_animation_shapekeys(bpy.types.Panel): bl_space_type = 'FILE_BROWSER' bl_region_type = 'TOOL_PROPS' bl_label = "Shape Keys" bl_parent_id = "GLTF_PT_export_animation" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): sfile = context.space_data operator = sfile.active_operator return operator.bl_idname == "EXPORT_SCENE_OT_gltf" def draw_header(self, context): sfile = context.space_data operator = sfile.active_operator self.layout.prop(operator, "export_morph", text="") def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False # No animation. sfile = context.space_data operator = sfile.active_operator layout.active = operator.export_morph layout.prop(operator, 'export_morph_normal') col = layout.column() col.active = operator.export_morph_normal col.prop(operator, 'export_morph_tangent') class GLTF_PT_export_animation_skinning(bpy.types.Panel): bl_space_type = 'FILE_BROWSER' bl_region_type = 'TOOL_PROPS' bl_label = "Skinning" bl_parent_id = "GLTF_PT_export_animation" bl_options = {'DEFAULT_CLOSED'} @classmethod def poll(cls, context): sfile = context.space_data operator = sfile.active_operator return operator.bl_idname == "EXPORT_SCENE_OT_gltf" def draw_header(self, context): sfile = context.space_data operator = sfile.active_operator self.layout.prop(operator, "export_skins", text="") def draw(self, context): layout = self.layout layout.use_property_split = True layout.use_property_decorate = False # No animation. sfile = context.space_data operator = sfile.active_operator layout.active = operator.export_skins layout.prop(operator, 'export_all_influences') class ExportGLTF2(bpy.types.Operator, ExportGLTF2_Base, ExportHelper): """Export scene as glTF 2.0 file""" 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' filter_glob: StringProperty(default="*.glb;*.gltf", options={'HIDDEN'}) loglevel: IntProperty( name='Log Level', description="Log Level") 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, 'import_pack_images') layout.prop(self, 'import_shading') def execute(self, context): return self.import_gltf2(context) def import_gltf2(self, context): import time from .io.imp.gltf2_io_gltf import glTFImporter from .blender.imp.gltf2_blender_gltf import BlenderGlTF self.set_debug_log() 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") start_time = time.time() BlenderGlTF.create(self.gltf_importer) elapsed_s = "{:.2f}s".format(time.time() - start_time) self.gltf_importer.log.critical("glTF import finished in " + elapsed_s) self.gltf_importer.log.removeHandler(self.gltf_importer.log_handler) return {'FINISHED'} 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 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, ImportGLTF2 ) def register(): 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(): for c in classes: bpy.utils.unregister_class(c) # 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)