glTF exporter: avoid crash when apply modifiers + shapekeys
[blender-addons.git] / io_scene_gltf2 / __init__.py
blob07e4824a859e7bebc4869aebe47b2876d159e00a
1 # SPDX-License-Identifier: Apache-2.0
2 # Copyright 2018-2021 The glTF-Blender-IO authors.
4 bl_info = {
5 'name': 'glTF 2.0 format',
6 'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
7 "version": (3, 4, 18),
8 'blender': (3, 3, 0),
9 'location': 'File > Import-Export',
10 'description': 'Import-Export as glTF 2.0',
11 'warning': '',
12 'doc_url': "{BLENDER_MANUAL_URL}/addons/import_export/scene_gltf2.html",
13 'tracker_url': "https://github.com/KhronosGroup/glTF-Blender-IO/issues/",
14 'support': 'OFFICIAL',
15 'category': 'Import-Export',
18 def get_version_string():
19 return str(bl_info['version'][0]) + '.' + str(bl_info['version'][1]) + '.' + str(bl_info['version'][2])
22 # Script reloading (if the user calls 'Reload Scripts' from Blender)
25 def reload_package(module_dict_main):
26 import importlib
27 from pathlib import Path
29 def reload_package_recursive(current_dir, module_dict):
30 for path in current_dir.iterdir():
31 if "__init__" in str(path) or path.stem not in module_dict:
32 continue
34 if path.is_file() and path.suffix == ".py":
35 importlib.reload(module_dict[path.stem])
36 elif path.is_dir():
37 reload_package_recursive(path, module_dict[path.stem].__dict__)
39 reload_package_recursive(Path(__file__).parent, module_dict_main)
42 if "bpy" in locals():
43 reload_package(locals())
45 import bpy
46 from bpy.props import (StringProperty,
47 BoolProperty,
48 EnumProperty,
49 IntProperty,
50 CollectionProperty)
51 from bpy.types import Operator
52 from bpy_extras.io_utils import ImportHelper, ExportHelper
56 # Functions / Classes.
59 exporter_extension_panel_unregister_functors = []
60 importer_extension_panel_unregister_functors = []
63 def ensure_filepath_matches_export_format(filepath, export_format):
64 import os
65 filename = os.path.basename(filepath)
66 if not filename:
67 return filepath
69 stem, ext = os.path.splitext(filename)
70 if stem.startswith('.') and not ext:
71 stem, ext = '', stem
73 desired_ext = '.glb' if export_format == 'GLB' else '.gltf'
74 ext_lower = ext.lower()
75 if ext_lower not in ['.glb', '.gltf']:
76 return filepath + desired_ext
77 elif ext_lower != desired_ext:
78 filepath = filepath[:-len(ext)] # strip off ext
79 return filepath + desired_ext
80 else:
81 return filepath
84 def on_export_format_changed(self, context):
85 # Update the filename in the file browser when the format (.glb/.gltf)
86 # changes
87 sfile = context.space_data
88 if not isinstance(sfile, bpy.types.SpaceFileBrowser):
89 return
90 if not sfile.active_operator:
91 return
92 if sfile.active_operator.bl_idname != "EXPORT_SCENE_OT_gltf":
93 return
95 sfile.params.filename = ensure_filepath_matches_export_format(
96 sfile.params.filename,
97 self.export_format,
101 class ExportGLTF2_Base:
102 # TODO: refactor to avoid boilerplate
104 def __init__(self):
105 from io_scene_gltf2.io.com import gltf2_io_draco_compression_extension
106 self.is_draco_available = gltf2_io_draco_compression_extension.dll_exists()
108 bl_options = {'PRESET'}
110 export_format: EnumProperty(
111 name='Format',
112 items=(('GLB', 'glTF Binary (.glb)',
113 'Exports a single file, with all data packed in binary form. '
114 'Most efficient and portable, but more difficult to edit later'),
115 ('GLTF_SEPARATE', 'glTF Separate (.gltf + .bin + textures)',
116 'Exports multiple files, with separate JSON, binary and texture data. '
117 'Easiest to edit later'),
118 ('GLTF_EMBEDDED', 'glTF Embedded (.gltf)',
119 'Exports a single file, with all data packed in JSON. '
120 'Less efficient than binary, but easier to edit later')),
121 description=(
122 'Output format and embedding options. Binary is most efficient, '
123 'but JSON (embedded or separate) may be easier to edit later'
125 default='GLB',
126 update=on_export_format_changed,
129 ui_tab: EnumProperty(
130 items=(('GENERAL', "General", "General settings"),
131 ('MESHES', "Meshes", "Mesh settings"),
132 ('OBJECTS', "Objects", "Object settings"),
133 ('ANIMATION', "Animation", "Animation settings")),
134 name="ui_tab",
135 description="Export setting categories",
138 export_copyright: StringProperty(
139 name='Copyright',
140 description='Legal rights and conditions for the model',
141 default=''
144 export_image_format: EnumProperty(
145 name='Images',
146 items=(('AUTO', 'Automatic',
147 'Save PNGs as PNGs and JPEGs as JPEGs. '
148 'If neither one, use PNG'),
149 ('JPEG', 'JPEG Format (.jpg)',
150 'Save images as JPEGs. (Images that need alpha are saved as PNGs though.) '
151 'Be aware of a possible loss in quality'),
152 ('NONE', 'None',
153 'Don\'t export images'),
155 description=(
156 'Output format for images. PNG is lossless and generally preferred, but JPEG might be preferable for web '
157 'applications due to the smaller file size. Alternatively they can be omitted if they are not needed'
159 default='AUTO'
162 export_texture_dir: StringProperty(
163 name='Textures',
164 description='Folder to place texture files in. Relative to the .gltf file',
165 default='',
168 export_keep_originals: BoolProperty(
169 name='Keep original',
170 description=('Keep original textures files if possible. '
171 'WARNING: if you use more than one texture, '
172 'where pbr standard requires only one, only one texture will be used. '
173 'This can lead to unexpected results'
175 default=False,
178 export_texcoords: BoolProperty(
179 name='UVs',
180 description='Export UVs (texture coordinates) with meshes',
181 default=True
184 export_normals: BoolProperty(
185 name='Normals',
186 description='Export vertex normals with meshes',
187 default=True
190 export_draco_mesh_compression_enable: BoolProperty(
191 name='Draco mesh compression',
192 description='Compress mesh using Draco',
193 default=False
196 export_draco_mesh_compression_level: IntProperty(
197 name='Compression level',
198 description='Compression level (0 = most speed, 6 = most compression, higher values currently not supported)',
199 default=6,
200 min=0,
201 max=10
204 export_draco_position_quantization: IntProperty(
205 name='Position quantization bits',
206 description='Quantization bits for position values (0 = no quantization)',
207 default=14,
208 min=0,
209 max=30
212 export_draco_normal_quantization: IntProperty(
213 name='Normal quantization bits',
214 description='Quantization bits for normal values (0 = no quantization)',
215 default=10,
216 min=0,
217 max=30
220 export_draco_texcoord_quantization: IntProperty(
221 name='Texcoord quantization bits',
222 description='Quantization bits for texture coordinate values (0 = no quantization)',
223 default=12,
224 min=0,
225 max=30
228 export_draco_color_quantization: IntProperty(
229 name='Color quantization bits',
230 description='Quantization bits for color values (0 = no quantization)',
231 default=10,
232 min=0,
233 max=30
236 export_draco_generic_quantization: IntProperty(
237 name='Generic quantization bits',
238 description='Quantization bits for generic coordinate values like weights or joints (0 = no quantization)',
239 default=12,
240 min=0,
241 max=30
244 export_tangents: BoolProperty(
245 name='Tangents',
246 description='Export vertex tangents with meshes',
247 default=False
250 export_materials: EnumProperty(
251 name='Materials',
252 items=(('EXPORT', 'Export',
253 'Export all materials used by included objects'),
254 ('PLACEHOLDER', 'Placeholder',
255 'Do not export materials, but write multiple primitive groups per mesh, keeping material slot information'),
256 ('NONE', 'No export',
257 'Do not export materials, and combine mesh primitive groups, losing material slot information')),
258 description='Export materials ',
259 default='EXPORT'
262 export_original_specular: BoolProperty(
263 name='Export original PBR Specular',
264 description=(
265 'Export original glTF PBR Specular, instead of Blender Principled Shader Specular'
267 default=False,
270 export_colors: BoolProperty(
271 name='Vertex Colors',
272 description='Export vertex colors with meshes',
273 default=True
276 use_mesh_edges: BoolProperty(
277 name='Loose Edges',
278 description=(
279 'Export loose edges as lines, using the material from the first material slot'
281 default=False,
284 use_mesh_vertices: BoolProperty(
285 name='Loose Points',
286 description=(
287 'Export loose points as glTF points, using the material from the first material slot'
289 default=False,
292 export_cameras: BoolProperty(
293 name='Cameras',
294 description='Export cameras',
295 default=False
298 use_selection: BoolProperty(
299 name='Selected Objects',
300 description='Export selected objects only',
301 default=False
304 use_visible: BoolProperty(
305 name='Visible Objects',
306 description='Export visible objects only',
307 default=False
310 use_renderable: BoolProperty(
311 name='Renderable Objects',
312 description='Export renderable objects only',
313 default=False
316 use_active_collection: BoolProperty(
317 name='Active Collection',
318 description='Export objects in the active collection only',
319 default=False
322 use_active_scene: BoolProperty(
323 name='Active Scene',
324 description='Export active scene only',
325 default=False
328 export_extras: BoolProperty(
329 name='Custom Properties',
330 description='Export custom properties as glTF extras',
331 default=False
334 export_yup: BoolProperty(
335 name='+Y Up',
336 description='Export using glTF convention, +Y up',
337 default=True
340 export_apply: BoolProperty(
341 name='Apply Modifiers',
342 description='Apply modifiers (excluding Armatures) to mesh objects -'
343 'WARNING: prevents exporting shape keys',
344 default=False
347 export_animations: BoolProperty(
348 name='Animations',
349 description='Exports active actions and NLA tracks as glTF animations',
350 default=True
353 export_frame_range: BoolProperty(
354 name='Limit to Playback Range',
355 description='Clips animations to selected playback range',
356 default=True
359 export_frame_step: IntProperty(
360 name='Sampling Rate',
361 description='How often to evaluate animated values (in frames)',
362 default=1,
363 min=1,
364 max=120
367 export_force_sampling: BoolProperty(
368 name='Always Sample Animations',
369 description='Apply sampling to all animations',
370 default=True
373 export_nla_strips: BoolProperty(
374 name='Group by NLA Track',
375 description=(
376 "When on, multiple actions become part of the same glTF animation if "
377 "they're pushed onto NLA tracks with the same name. "
378 "When off, all the currently assigned actions become one glTF animation"
380 default=True
383 export_nla_strips_merged_animation_name: StringProperty(
384 name='Merged Animation Name',
385 description=(
386 "Name of single glTF animation to be exported"
388 default='Animation'
391 export_def_bones: BoolProperty(
392 name='Export Deformation Bones Only',
393 description='Export Deformation bones only',
394 default=False
397 export_optimize_animation_size: BoolProperty(
398 name='Optimize Animation Size',
399 description=(
400 "Reduce exported file-size by removing duplicate keyframes"
401 "(can cause problems with stepped animation)"
403 default=False
406 export_anim_single_armature: BoolProperty(
407 name='Export all Armature Actions',
408 description=(
409 "Export all actions, bound to a single armature. "
410 "WARNING: Option does not support exports including multiple armatures"
412 default=True
415 export_current_frame: BoolProperty(
416 name='Use Current Frame',
417 description='Export the scene in the current animation frame',
418 default=False
421 export_skins: BoolProperty(
422 name='Skinning',
423 description='Export skinning (armature) data',
424 default=True
427 export_all_influences: BoolProperty(
428 name='Include All Bone Influences',
429 description='Allow >4 joint vertex influences. Models may appear incorrectly in many viewers',
430 default=False
433 export_morph: BoolProperty(
434 name='Shape Keys',
435 description='Export shape keys (morph targets)',
436 default=True
439 export_morph_normal: BoolProperty(
440 name='Shape Key Normals',
441 description='Export vertex normals with shape keys (morph targets)',
442 default=True
445 export_morph_tangent: BoolProperty(
446 name='Shape Key Tangents',
447 description='Export vertex tangents with shape keys (morph targets)',
448 default=False
451 export_lights: BoolProperty(
452 name='Punctual Lights',
453 description='Export directional, point, and spot lights. '
454 'Uses "KHR_lights_punctual" glTF extension',
455 default=False
458 will_save_settings: BoolProperty(
459 name='Remember Export Settings',
460 description='Store glTF export settings in the Blender project',
461 default=False)
463 # Custom scene property for saving settings
464 scene_key = "glTF2ExportSettings"
468 def check(self, _context):
469 # Ensure file extension matches format
470 old_filepath = self.filepath
471 self.filepath = ensure_filepath_matches_export_format(
472 self.filepath,
473 self.export_format,
475 return self.filepath != old_filepath
477 def invoke(self, context, event):
478 settings = context.scene.get(self.scene_key)
479 self.will_save_settings = False
480 if settings:
481 try:
482 for (k, v) in settings.items():
483 setattr(self, k, v)
484 self.will_save_settings = True
486 except (AttributeError, TypeError):
487 self.report({"ERROR"}, "Loading export settings failed. Removed corrupted settings")
488 del context.scene[self.scene_key]
490 import sys
491 preferences = bpy.context.preferences
492 for addon_name in preferences.addons.keys():
493 try:
494 if hasattr(sys.modules[addon_name], 'glTF2ExportUserExtension') or hasattr(sys.modules[addon_name], 'glTF2ExportUserExtensions'):
495 exporter_extension_panel_unregister_functors.append(sys.modules[addon_name].register_panel())
496 except Exception:
497 pass
499 self.has_active_exporter_extensions = len(exporter_extension_panel_unregister_functors) > 0
500 return ExportHelper.invoke(self, context, event)
502 def save_settings(self, context):
503 # find all props to save
504 exceptional = [
505 # options that don't start with 'export_'
506 'use_selection',
507 'use_visible',
508 'use_renderable',
509 'use_active_collection',
510 'use_mesh_edges',
511 'use_mesh_vertices',
512 'use_active_scene',
514 all_props = self.properties
515 export_props = {
516 x: getattr(self, x) for x in dir(all_props)
517 if (x.startswith("export_") or x in exceptional) and all_props.get(x) is not None
519 context.scene[self.scene_key] = export_props
521 def execute(self, context):
522 import os
523 import datetime
524 from .blender.exp import gltf2_blender_export
526 if self.will_save_settings:
527 self.save_settings(context)
529 self.check(context) # ensure filepath has the right extension
531 # All custom export settings are stored in this container.
532 export_settings = {}
534 export_settings['timestamp'] = datetime.datetime.now()
536 export_settings['gltf_filepath'] = self.filepath
537 export_settings['gltf_filedirectory'] = os.path.dirname(export_settings['gltf_filepath']) + '/'
538 export_settings['gltf_texturedirectory'] = os.path.join(
539 export_settings['gltf_filedirectory'],
540 self.export_texture_dir,
542 export_settings['gltf_keep_original_textures'] = self.export_keep_originals
544 export_settings['gltf_format'] = self.export_format
545 export_settings['gltf_image_format'] = self.export_image_format
546 export_settings['gltf_copyright'] = self.export_copyright
547 export_settings['gltf_texcoords'] = self.export_texcoords
548 export_settings['gltf_normals'] = self.export_normals
549 export_settings['gltf_tangents'] = self.export_tangents and self.export_normals
550 export_settings['gltf_loose_edges'] = self.use_mesh_edges
551 export_settings['gltf_loose_points'] = self.use_mesh_vertices
553 if self.is_draco_available:
554 export_settings['gltf_draco_mesh_compression'] = self.export_draco_mesh_compression_enable
555 export_settings['gltf_draco_mesh_compression_level'] = self.export_draco_mesh_compression_level
556 export_settings['gltf_draco_position_quantization'] = self.export_draco_position_quantization
557 export_settings['gltf_draco_normal_quantization'] = self.export_draco_normal_quantization
558 export_settings['gltf_draco_texcoord_quantization'] = self.export_draco_texcoord_quantization
559 export_settings['gltf_draco_color_quantization'] = self.export_draco_color_quantization
560 export_settings['gltf_draco_generic_quantization'] = self.export_draco_generic_quantization
561 else:
562 export_settings['gltf_draco_mesh_compression'] = False
564 export_settings['gltf_materials'] = self.export_materials
565 export_settings['gltf_colors'] = self.export_colors
566 export_settings['gltf_cameras'] = self.export_cameras
568 export_settings['gltf_original_specular'] = self.export_original_specular
570 export_settings['gltf_visible'] = self.use_visible
571 export_settings['gltf_renderable'] = self.use_renderable
572 export_settings['gltf_active_collection'] = self.use_active_collection
573 export_settings['gltf_active_scene'] = self.use_active_scene
575 export_settings['gltf_selected'] = self.use_selection
576 export_settings['gltf_layers'] = True # self.export_layers
577 export_settings['gltf_extras'] = self.export_extras
578 export_settings['gltf_yup'] = self.export_yup
579 export_settings['gltf_apply'] = self.export_apply
580 export_settings['gltf_current_frame'] = self.export_current_frame
581 export_settings['gltf_animations'] = self.export_animations
582 export_settings['gltf_def_bones'] = self.export_def_bones
583 if self.export_animations:
584 export_settings['gltf_frame_range'] = self.export_frame_range
585 export_settings['gltf_force_sampling'] = self.export_force_sampling
586 if not self.export_force_sampling:
587 export_settings['gltf_def_bones'] = False
588 export_settings['gltf_nla_strips'] = self.export_nla_strips
589 export_settings['gltf_nla_strips_merged_animation_name'] = self.export_nla_strips_merged_animation_name
590 export_settings['gltf_optimize_animation'] = self.export_optimize_animation_size
591 export_settings['gltf_export_anim_single_armature'] = self.export_anim_single_armature
592 else:
593 export_settings['gltf_frame_range'] = False
594 export_settings['gltf_move_keyframes'] = False
595 export_settings['gltf_force_sampling'] = False
596 export_settings['gltf_optimize_animation'] = False
597 export_settings['gltf_export_anim_single_armature'] = False
598 export_settings['gltf_skins'] = self.export_skins
599 if self.export_skins:
600 export_settings['gltf_all_vertex_influences'] = self.export_all_influences
601 else:
602 export_settings['gltf_all_vertex_influences'] = False
603 export_settings['gltf_def_bones'] = False
604 export_settings['gltf_frame_step'] = self.export_frame_step
605 export_settings['gltf_morph'] = self.export_morph
606 if self.export_morph:
607 export_settings['gltf_morph_normal'] = self.export_morph_normal
608 else:
609 export_settings['gltf_morph_normal'] = False
610 if self.export_morph and self.export_morph_normal:
611 export_settings['gltf_morph_tangent'] = self.export_morph_tangent
612 else:
613 export_settings['gltf_morph_tangent'] = False
615 export_settings['gltf_lights'] = self.export_lights
617 export_settings['gltf_binary'] = bytearray()
618 export_settings['gltf_binaryfilename'] = (
619 os.path.splitext(os.path.basename(self.filepath))[0] + '.bin'
622 user_extensions = []
623 pre_export_callbacks = []
624 post_export_callbacks = []
626 import sys
627 preferences = bpy.context.preferences
628 for addon_name in preferences.addons.keys():
629 try:
630 module = sys.modules[addon_name]
631 except Exception:
632 continue
633 if hasattr(module, 'glTF2ExportUserExtension'):
634 extension_ctor = module.glTF2ExportUserExtension
635 user_extensions.append(extension_ctor())
636 if hasattr(module, 'glTF2ExportUserExtensions'):
637 extension_ctors = module.glTF2ExportUserExtensions
638 for extension_ctor in extension_ctors:
639 user_extensions.append(extension_ctor())
640 if hasattr(module, 'glTF2_pre_export_callback'):
641 pre_export_callbacks.append(module.glTF2_pre_export_callback)
642 if hasattr(module, 'glTF2_post_export_callback'):
643 post_export_callbacks.append(module.glTF2_post_export_callback)
644 export_settings['gltf_user_extensions'] = user_extensions
645 export_settings['pre_export_callbacks'] = pre_export_callbacks
646 export_settings['post_export_callbacks'] = post_export_callbacks
648 return gltf2_blender_export.save(context, export_settings)
650 def draw(self, context):
651 pass # Is needed to get panels available
654 class GLTF_PT_export_main(bpy.types.Panel):
655 bl_space_type = 'FILE_BROWSER'
656 bl_region_type = 'TOOL_PROPS'
657 bl_label = ""
658 bl_parent_id = "FILE_PT_operator"
659 bl_options = {'HIDE_HEADER'}
661 @classmethod
662 def poll(cls, context):
663 sfile = context.space_data
664 operator = sfile.active_operator
666 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
668 def draw(self, context):
669 layout = self.layout
670 layout.use_property_split = True
671 layout.use_property_decorate = False # No animation.
673 sfile = context.space_data
674 operator = sfile.active_operator
676 layout.prop(operator, 'export_format')
677 if operator.export_format == 'GLTF_SEPARATE':
678 layout.prop(operator, 'export_keep_originals')
679 if operator.export_keep_originals is False:
680 layout.prop(operator, 'export_texture_dir', icon='FILE_FOLDER')
682 layout.prop(operator, 'export_copyright')
683 layout.prop(operator, 'will_save_settings')
686 class GLTF_PT_export_include(bpy.types.Panel):
687 bl_space_type = 'FILE_BROWSER'
688 bl_region_type = 'TOOL_PROPS'
689 bl_label = "Include"
690 bl_parent_id = "FILE_PT_operator"
691 bl_options = {'DEFAULT_CLOSED'}
693 @classmethod
694 def poll(cls, context):
695 sfile = context.space_data
696 operator = sfile.active_operator
698 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
700 def draw(self, context):
701 layout = self.layout
702 layout.use_property_split = True
703 layout.use_property_decorate = False # No animation.
705 sfile = context.space_data
706 operator = sfile.active_operator
708 col = layout.column(heading = "Limit to", align = True)
709 col.prop(operator, 'use_selection')
710 col.prop(operator, 'use_visible')
711 col.prop(operator, 'use_renderable')
712 col.prop(operator, 'use_active_collection')
713 col.prop(operator, 'use_active_scene')
715 col = layout.column(heading = "Data", align = True)
716 col.prop(operator, 'export_extras')
717 col.prop(operator, 'export_cameras')
718 col.prop(operator, 'export_lights')
721 class GLTF_PT_export_transform(bpy.types.Panel):
722 bl_space_type = 'FILE_BROWSER'
723 bl_region_type = 'TOOL_PROPS'
724 bl_label = "Transform"
725 bl_parent_id = "FILE_PT_operator"
726 bl_options = {'DEFAULT_CLOSED'}
728 @classmethod
729 def poll(cls, context):
730 sfile = context.space_data
731 operator = sfile.active_operator
733 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
735 def draw(self, context):
736 layout = self.layout
737 layout.use_property_split = True
738 layout.use_property_decorate = False # No animation.
740 sfile = context.space_data
741 operator = sfile.active_operator
743 layout.prop(operator, 'export_yup')
746 class GLTF_PT_export_geometry(bpy.types.Panel):
747 bl_space_type = 'FILE_BROWSER'
748 bl_region_type = 'TOOL_PROPS'
749 bl_label = "Geometry"
750 bl_parent_id = "FILE_PT_operator"
751 bl_options = {'DEFAULT_CLOSED'}
753 @classmethod
754 def poll(cls, context):
755 sfile = context.space_data
756 operator = sfile.active_operator
758 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
760 def draw(self, context):
761 pass
763 class GLTF_PT_export_geometry_mesh(bpy.types.Panel):
764 bl_space_type = 'FILE_BROWSER'
765 bl_region_type = 'TOOL_PROPS'
766 bl_label = "Mesh"
767 bl_parent_id = "GLTF_PT_export_geometry"
768 bl_options = {'DEFAULT_CLOSED'}
770 @classmethod
771 def poll(cls, context):
772 sfile = context.space_data
773 operator = sfile.active_operator
774 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
776 def draw(self, context):
777 layout = self.layout
778 layout.use_property_split = True
779 layout.use_property_decorate = False # No animation.
781 sfile = context.space_data
782 operator = sfile.active_operator
784 layout.prop(operator, 'export_apply')
785 layout.prop(operator, 'export_texcoords')
786 layout.prop(operator, 'export_normals')
787 col = layout.column()
788 col.active = operator.export_normals
789 col.prop(operator, 'export_tangents')
790 layout.prop(operator, 'export_colors')
792 col = layout.column()
793 col.prop(operator, 'use_mesh_edges')
794 col.prop(operator, 'use_mesh_vertices')
797 class GLTF_PT_export_geometry_material(bpy.types.Panel):
798 bl_space_type = 'FILE_BROWSER'
799 bl_region_type = 'TOOL_PROPS'
800 bl_label = "Material"
801 bl_parent_id = "GLTF_PT_export_geometry"
802 bl_options = {'DEFAULT_CLOSED'}
804 @classmethod
805 def poll(cls, context):
806 sfile = context.space_data
807 operator = sfile.active_operator
808 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
810 def draw(self, context):
811 layout = self.layout
812 layout.use_property_split = True
813 layout.use_property_decorate = False # No animation.
815 sfile = context.space_data
816 operator = sfile.active_operator
818 layout.prop(operator, 'export_materials')
819 col = layout.column()
820 col.active = operator.export_materials == "EXPORT"
821 col.prop(operator, 'export_image_format')
823 class GLTF_PT_export_geometry_original_pbr(bpy.types.Panel):
824 bl_space_type = 'FILE_BROWSER'
825 bl_region_type = 'TOOL_PROPS'
826 bl_label = "PBR Extensions"
827 bl_parent_id = "GLTF_PT_export_geometry_material"
828 bl_options = {'DEFAULT_CLOSED'}
830 @classmethod
831 def poll(cls, context):
832 sfile = context.space_data
833 operator = sfile.active_operator
834 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
836 def draw(self, context):
837 layout = self.layout
838 layout.use_property_split = True
839 layout.use_property_decorate = False # No animation.
841 sfile = context.space_data
842 operator = sfile.active_operator
844 layout.prop(operator, 'export_original_specular')
847 class GLTF_PT_export_geometry_compression(bpy.types.Panel):
848 bl_space_type = 'FILE_BROWSER'
849 bl_region_type = 'TOOL_PROPS'
850 bl_label = "Compression"
851 bl_parent_id = "GLTF_PT_export_geometry"
852 bl_options = {'DEFAULT_CLOSED'}
854 def __init__(self):
855 from io_scene_gltf2.io.com import gltf2_io_draco_compression_extension
856 self.is_draco_available = gltf2_io_draco_compression_extension.dll_exists(quiet=True)
858 @classmethod
859 def poll(cls, context):
860 sfile = context.space_data
861 operator = sfile.active_operator
862 if operator.is_draco_available:
863 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
865 def draw_header(self, context):
866 sfile = context.space_data
867 operator = sfile.active_operator
868 self.layout.prop(operator, "export_draco_mesh_compression_enable", text="")
870 def draw(self, context):
871 layout = self.layout
872 layout.use_property_split = True
873 layout.use_property_decorate = False # No animation.
875 sfile = context.space_data
876 operator = sfile.active_operator
878 layout.active = operator.export_draco_mesh_compression_enable
879 layout.prop(operator, 'export_draco_mesh_compression_level')
881 col = layout.column(align=True)
882 col.prop(operator, 'export_draco_position_quantization', text="Quantize Position")
883 col.prop(operator, 'export_draco_normal_quantization', text="Normal")
884 col.prop(operator, 'export_draco_texcoord_quantization', text="Tex Coord")
885 col.prop(operator, 'export_draco_color_quantization', text="Color")
886 col.prop(operator, 'export_draco_generic_quantization', text="Generic")
889 class GLTF_PT_export_animation(bpy.types.Panel):
890 bl_space_type = 'FILE_BROWSER'
891 bl_region_type = 'TOOL_PROPS'
892 bl_label = "Animation"
893 bl_parent_id = "FILE_PT_operator"
894 bl_options = {'DEFAULT_CLOSED'}
896 @classmethod
897 def poll(cls, context):
898 sfile = context.space_data
899 operator = sfile.active_operator
901 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
903 def draw(self, context):
904 layout = self.layout
905 layout.use_property_split = True
906 layout.use_property_decorate = False # No animation.
908 sfile = context.space_data
909 operator = sfile.active_operator
911 layout.prop(operator, 'export_current_frame')
914 class GLTF_PT_export_animation_export(bpy.types.Panel):
915 bl_space_type = 'FILE_BROWSER'
916 bl_region_type = 'TOOL_PROPS'
917 bl_label = "Animation"
918 bl_parent_id = "GLTF_PT_export_animation"
919 bl_options = {'DEFAULT_CLOSED'}
921 @classmethod
922 def poll(cls, context):
923 sfile = context.space_data
924 operator = sfile.active_operator
926 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
928 def draw_header(self, context):
929 sfile = context.space_data
930 operator = sfile.active_operator
931 self.layout.prop(operator, "export_animations", text="")
933 def draw(self, context):
934 layout = self.layout
935 layout.use_property_split = True
936 layout.use_property_decorate = False # No animation.
938 sfile = context.space_data
939 operator = sfile.active_operator
941 layout.active = operator.export_animations
943 layout.prop(operator, 'export_frame_range')
944 layout.prop(operator, 'export_frame_step')
945 layout.prop(operator, 'export_force_sampling')
946 layout.prop(operator, 'export_nla_strips')
947 if operator.export_nla_strips is False:
948 layout.prop(operator, 'export_nla_strips_merged_animation_name')
949 layout.prop(operator, 'export_optimize_animation_size')
950 layout.prop(operator, 'export_anim_single_armature')
953 class GLTF_PT_export_animation_shapekeys(bpy.types.Panel):
954 bl_space_type = 'FILE_BROWSER'
955 bl_region_type = 'TOOL_PROPS'
956 bl_label = "Shape Keys"
957 bl_parent_id = "GLTF_PT_export_animation"
958 bl_options = {'DEFAULT_CLOSED'}
960 @classmethod
961 def poll(cls, context):
962 sfile = context.space_data
963 operator = sfile.active_operator
965 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
967 def draw_header(self, context):
968 sfile = context.space_data
969 operator = sfile.active_operator
970 self.layout.prop(operator, "export_morph", text="")
972 def draw(self, context):
973 layout = self.layout
974 layout.use_property_split = True
975 layout.use_property_decorate = False # No animation.
977 sfile = context.space_data
978 operator = sfile.active_operator
980 layout.active = operator.export_morph
982 layout.prop(operator, 'export_morph_normal')
983 col = layout.column()
984 col.active = operator.export_morph_normal
985 col.prop(operator, 'export_morph_tangent')
988 class GLTF_PT_export_animation_skinning(bpy.types.Panel):
989 bl_space_type = 'FILE_BROWSER'
990 bl_region_type = 'TOOL_PROPS'
991 bl_label = "Skinning"
992 bl_parent_id = "GLTF_PT_export_animation"
993 bl_options = {'DEFAULT_CLOSED'}
995 @classmethod
996 def poll(cls, context):
997 sfile = context.space_data
998 operator = sfile.active_operator
1000 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
1002 def draw_header(self, context):
1003 sfile = context.space_data
1004 operator = sfile.active_operator
1005 self.layout.prop(operator, "export_skins", text="")
1007 def draw(self, context):
1008 layout = self.layout
1009 layout.use_property_split = True
1010 layout.use_property_decorate = False # No animation.
1012 sfile = context.space_data
1013 operator = sfile.active_operator
1015 layout.active = operator.export_skins
1016 layout.prop(operator, 'export_all_influences')
1018 row = layout.row()
1019 row.active = operator.export_force_sampling
1020 row.prop(operator, 'export_def_bones')
1021 if operator.export_force_sampling is False and operator.export_def_bones is True:
1022 layout.label(text="Export only deformation bones is not possible when not sampling animation")
1024 class GLTF_PT_export_user_extensions(bpy.types.Panel):
1025 bl_space_type = 'FILE_BROWSER'
1026 bl_region_type = 'TOOL_PROPS'
1027 bl_label = "Exporter Extensions"
1028 bl_parent_id = "FILE_PT_operator"
1029 bl_options = {'DEFAULT_CLOSED'}
1031 @classmethod
1032 def poll(cls, context):
1033 sfile = context.space_data
1034 operator = sfile.active_operator
1036 return operator.bl_idname == "EXPORT_SCENE_OT_gltf" and operator.has_active_exporter_extensions
1038 def draw(self, context):
1039 layout = self.layout
1040 layout.use_property_split = True
1041 layout.use_property_decorate = False # No animation.
1043 class GLTF_PT_import_user_extensions(bpy.types.Panel):
1044 bl_space_type = 'FILE_BROWSER'
1045 bl_region_type = 'TOOL_PROPS'
1046 bl_label = "Importer Extensions"
1047 bl_parent_id = "FILE_PT_operator"
1048 bl_options = {'DEFAULT_CLOSED'}
1050 @classmethod
1051 def poll(cls, context):
1052 sfile = context.space_data
1053 operator = sfile.active_operator
1054 return operator.bl_idname == "IMPORT_SCENE_OT_gltf" and operator.has_active_importer_extensions
1056 def draw(self, context):
1057 layout = self.layout
1058 layout.use_property_split = True
1059 layout.use_property_decorate = False # No animation.
1061 class ExportGLTF2(bpy.types.Operator, ExportGLTF2_Base, ExportHelper):
1062 """Export scene as glTF 2.0 file"""
1063 bl_idname = 'export_scene.gltf'
1064 bl_label = 'Export glTF 2.0'
1066 filename_ext = ''
1068 filter_glob: StringProperty(default='*.glb;*.gltf', options={'HIDDEN'})
1071 def menu_func_export(self, context):
1072 self.layout.operator(ExportGLTF2.bl_idname, text='glTF 2.0 (.glb/.gltf)')
1075 class ImportGLTF2(Operator, ImportHelper):
1076 """Load a glTF 2.0 file"""
1077 bl_idname = 'import_scene.gltf'
1078 bl_label = 'Import glTF 2.0'
1079 bl_options = {'REGISTER', 'UNDO'}
1081 filter_glob: StringProperty(default="*.glb;*.gltf", options={'HIDDEN'})
1083 files: CollectionProperty(
1084 name="File Path",
1085 type=bpy.types.OperatorFileListElement,
1088 loglevel: IntProperty(
1089 name='Log Level',
1090 description="Log Level")
1092 import_pack_images: BoolProperty(
1093 name='Pack Images',
1094 description='Pack all images into .blend file',
1095 default=True
1098 merge_vertices: BoolProperty(
1099 name='Merge Vertices',
1100 description=(
1101 'The glTF format requires discontinuous normals, UVs, and '
1102 'other vertex attributes to be stored as separate vertices, '
1103 'as required for rendering on typical graphics hardware. '
1104 'This option attempts to combine co-located vertices where possible. '
1105 'Currently cannot combine verts with different normals'
1107 default=False,
1110 import_shading: EnumProperty(
1111 name="Shading",
1112 items=(("NORMALS", "Use Normal Data", ""),
1113 ("FLAT", "Flat Shading", ""),
1114 ("SMOOTH", "Smooth Shading", "")),
1115 description="How normals are computed during import",
1116 default="NORMALS")
1118 bone_heuristic: EnumProperty(
1119 name="Bone Dir",
1120 items=(
1121 ("BLENDER", "Blender (best for re-importing)",
1122 "Good for re-importing glTFs exported from Blender. "
1123 "Bone tips are placed on their local +Y axis (in glTF space)"),
1124 ("TEMPERANCE", "Temperance (average)",
1125 "Decent all-around strategy. "
1126 "A bone with one child has its tip placed on the local axis "
1127 "closest to its child"),
1128 ("FORTUNE", "Fortune (may look better, less accurate)",
1129 "Might look better than Temperance, but also might have errors. "
1130 "A bone with one child has its tip placed at its child's root. "
1131 "Non-uniform scalings may get messed up though, so beware"),
1133 description="Heuristic for placing bones. Tries to make bones pretty",
1134 default="TEMPERANCE",
1137 guess_original_bind_pose: BoolProperty(
1138 name='Guess Original Bind Pose',
1139 description=(
1140 'Try to guess the original bind pose for skinned meshes from '
1141 'the inverse bind matrices. '
1142 'When off, use default/rest pose as bind pose'
1144 default=True,
1147 def draw(self, context):
1148 layout = self.layout
1150 layout.use_property_split = True
1151 layout.use_property_decorate = False # No animation.
1153 layout.prop(self, 'import_pack_images')
1154 layout.prop(self, 'merge_vertices')
1155 layout.prop(self, 'import_shading')
1156 layout.prop(self, 'guess_original_bind_pose')
1157 layout.prop(self, 'bone_heuristic')
1159 def invoke(self, context, event):
1160 import sys
1161 preferences = bpy.context.preferences
1162 for addon_name in preferences.addons.keys():
1163 try:
1164 if hasattr(sys.modules[addon_name], 'glTF2ImportUserExtension') or hasattr(sys.modules[addon_name], 'glTF2ImportUserExtensions'):
1165 importer_extension_panel_unregister_functors.append(sys.modules[addon_name].register_panel())
1166 except Exception:
1167 pass
1169 self.has_active_importer_extensions = len(importer_extension_panel_unregister_functors) > 0
1170 return ImportHelper.invoke(self, context, event)
1172 def execute(self, context):
1173 return self.import_gltf2(context)
1175 def import_gltf2(self, context):
1176 import os
1178 self.set_debug_log()
1179 import_settings = self.as_keywords()
1181 user_extensions = []
1183 import sys
1184 preferences = bpy.context.preferences
1185 for addon_name in preferences.addons.keys():
1186 try:
1187 module = sys.modules[addon_name]
1188 except Exception:
1189 continue
1190 if hasattr(module, 'glTF2ImportUserExtension'):
1191 extension_ctor = module.glTF2ImportUserExtension
1192 user_extensions.append(extension_ctor())
1193 import_settings['import_user_extensions'] = user_extensions
1195 if self.files:
1196 # Multiple file import
1197 ret = {'CANCELLED'}
1198 dirname = os.path.dirname(self.filepath)
1199 for file in self.files:
1200 path = os.path.join(dirname, file.name)
1201 if self.unit_import(path, import_settings) == {'FINISHED'}:
1202 ret = {'FINISHED'}
1203 return ret
1204 else:
1205 # Single file import
1206 return self.unit_import(self.filepath, import_settings)
1208 def unit_import(self, filename, import_settings):
1209 import time
1210 from .io.imp.gltf2_io_gltf import glTFImporter, ImportError
1211 from .blender.imp.gltf2_blender_gltf import BlenderGlTF
1213 try:
1214 gltf_importer = glTFImporter(filename, import_settings)
1215 gltf_importer.read()
1216 gltf_importer.checks()
1218 print("Data are loaded, start creating Blender stuff")
1220 start_time = time.time()
1221 BlenderGlTF.create(gltf_importer)
1222 elapsed_s = "{:.2f}s".format(time.time() - start_time)
1223 print("glTF import finished in " + elapsed_s)
1225 gltf_importer.log.removeHandler(gltf_importer.log_handler)
1227 return {'FINISHED'}
1229 except ImportError as e:
1230 self.report({'ERROR'}, e.args[0])
1231 return {'CANCELLED'}
1233 def set_debug_log(self):
1234 import logging
1235 if bpy.app.debug_value == 0:
1236 self.loglevel = logging.CRITICAL
1237 elif bpy.app.debug_value == 1:
1238 self.loglevel = logging.ERROR
1239 elif bpy.app.debug_value == 2:
1240 self.loglevel = logging.WARNING
1241 elif bpy.app.debug_value == 3:
1242 self.loglevel = logging.INFO
1243 else:
1244 self.loglevel = logging.NOTSET
1247 def gltf_variant_ui_update(self, context):
1248 from .blender.com.gltf2_blender_ui import variant_register, variant_unregister
1249 if self.KHR_materials_variants_ui is True:
1250 # register all needed types
1251 variant_register()
1252 else:
1253 variant_unregister()
1255 class GLTF_AddonPreferences(bpy.types.AddonPreferences):
1256 bl_idname = __package__
1258 settings_node_ui : bpy.props.BoolProperty(
1259 default= False,
1260 description="Displays glTF Material Output node in Shader Editor (Menu Add > Output)"
1263 KHR_materials_variants_ui : bpy.props.BoolProperty(
1264 default= False,
1265 description="Displays glTF UI to manage material variants",
1266 update=gltf_variant_ui_update
1270 def draw(self, context):
1271 layout = self.layout
1272 row = layout.row()
1273 row.prop(self, "settings_node_ui", text="Shader Editor Add-ons")
1274 row.prop(self, "KHR_materials_variants_ui", text="Material Variants")
1276 def menu_func_import(self, context):
1277 self.layout.operator(ImportGLTF2.bl_idname, text='glTF 2.0 (.glb/.gltf)')
1280 classes = (
1281 ExportGLTF2,
1282 GLTF_PT_export_main,
1283 GLTF_PT_export_include,
1284 GLTF_PT_export_transform,
1285 GLTF_PT_export_geometry,
1286 GLTF_PT_export_geometry_mesh,
1287 GLTF_PT_export_geometry_material,
1288 GLTF_PT_export_geometry_original_pbr,
1289 GLTF_PT_export_geometry_compression,
1290 GLTF_PT_export_animation,
1291 GLTF_PT_export_animation_export,
1292 GLTF_PT_export_animation_shapekeys,
1293 GLTF_PT_export_animation_skinning,
1294 GLTF_PT_export_user_extensions,
1295 ImportGLTF2,
1296 GLTF_PT_import_user_extensions,
1297 GLTF_AddonPreferences
1301 def register():
1302 import io_scene_gltf2.blender.com.gltf2_blender_ui as blender_ui
1303 for c in classes:
1304 bpy.utils.register_class(c)
1305 # bpy.utils.register_module(__name__)
1307 blender_ui.register()
1308 if bpy.context.preferences.addons['io_scene_gltf2'].preferences.KHR_materials_variants_ui is True:
1309 blender_ui.variant_register()
1311 # add to the export / import menu
1312 bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
1313 bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
1316 def unregister():
1317 import io_scene_gltf2.blender.com.gltf2_blender_ui as blender_ui
1318 blender_ui.unregister()
1319 if bpy.context.preferences.addons['io_scene_gltf2'].preferences.KHR_materials_variants_ui is True:
1320 blender_ui.variant_unregister()
1322 for c in classes:
1323 bpy.utils.unregister_class(c)
1324 for f in exporter_extension_panel_unregister_functors:
1326 exporter_extension_panel_unregister_functors.clear()
1328 for f in importer_extension_panel_unregister_functors:
1330 importer_extension_panel_unregister_functors.clear()
1332 # bpy.utils.unregister_module(__name__)
1334 # remove from the export / import menu
1335 bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
1336 bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)