glTF exporter: Fix T99306 : Fix camera & light export when Yup is off
[blender-addons.git] / io_scene_gltf2 / __init__.py
blob39763f8d6d0c0efe8bf27507bdd71979e9a06be4
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, 3, 11),
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_colors: BoolProperty(
263 name='Vertex Colors',
264 description='Export vertex colors with meshes',
265 default=True
268 use_mesh_edges: BoolProperty(
269 name='Loose Edges',
270 description=(
271 'Export loose edges as lines, using the material from the first material slot'
273 default=False,
276 use_mesh_vertices: BoolProperty(
277 name='Loose Points',
278 description=(
279 'Export loose points as glTF points, using the material from the first material slot'
281 default=False,
284 export_cameras: BoolProperty(
285 name='Cameras',
286 description='Export cameras',
287 default=False
290 use_selection: BoolProperty(
291 name='Selected Objects',
292 description='Export selected objects only',
293 default=False
296 use_visible: BoolProperty(
297 name='Visible Objects',
298 description='Export visible objects only',
299 default=False
302 use_renderable: BoolProperty(
303 name='Renderable Objects',
304 description='Export renderable objects only',
305 default=False
308 use_active_collection: BoolProperty(
309 name='Active Collection',
310 description='Export objects in the active collection only',
311 default=False
314 use_active_scene: BoolProperty(
315 name='Active Scene',
316 description='Export active scene only',
317 default=False
320 export_extras: BoolProperty(
321 name='Custom Properties',
322 description='Export custom properties as glTF extras',
323 default=False
326 export_yup: BoolProperty(
327 name='+Y Up',
328 description='Export using glTF convention, +Y up',
329 default=True
332 export_apply: BoolProperty(
333 name='Apply Modifiers',
334 description='Apply modifiers (excluding Armatures) to mesh objects -'
335 'WARNING: prevents exporting shape keys',
336 default=False
339 export_animations: BoolProperty(
340 name='Animations',
341 description='Exports active actions and NLA tracks as glTF animations',
342 default=True
345 export_frame_range: BoolProperty(
346 name='Limit to Playback Range',
347 description='Clips animations to selected playback range',
348 default=True
351 export_frame_step: IntProperty(
352 name='Sampling Rate',
353 description='How often to evaluate animated values (in frames)',
354 default=1,
355 min=1,
356 max=120
359 export_force_sampling: BoolProperty(
360 name='Always Sample Animations',
361 description='Apply sampling to all animations',
362 default=True
365 export_nla_strips: BoolProperty(
366 name='Group by NLA Track',
367 description=(
368 "When on, multiple actions become part of the same glTF animation if "
369 "they're pushed onto NLA tracks with the same name. "
370 "When off, all the currently assigned actions become one glTF animation"
372 default=True
375 export_nla_strips_merged_animation_name: StringProperty(
376 name='Merged Animation Name',
377 description=(
378 "Name of single glTF animation to be exported"
380 default='Animation'
383 export_def_bones: BoolProperty(
384 name='Export Deformation Bones Only',
385 description='Export Deformation bones only',
386 default=False
389 optimize_animation_size: BoolProperty(
390 name='Optimize Animation Size',
391 description=(
392 "Reduce exported file-size by removing duplicate keyframes"
393 "(can cause problems with stepped animation)"
395 default=False
398 export_current_frame: BoolProperty(
399 name='Use Current Frame',
400 description='Export the scene in the current animation frame',
401 default=False
404 export_skins: BoolProperty(
405 name='Skinning',
406 description='Export skinning (armature) data',
407 default=True
410 export_all_influences: BoolProperty(
411 name='Include All Bone Influences',
412 description='Allow >4 joint vertex influences. Models may appear incorrectly in many viewers',
413 default=False
416 export_morph: BoolProperty(
417 name='Shape Keys',
418 description='Export shape keys (morph targets)',
419 default=True
422 export_morph_normal: BoolProperty(
423 name='Shape Key Normals',
424 description='Export vertex normals with shape keys (morph targets)',
425 default=True
428 export_morph_tangent: BoolProperty(
429 name='Shape Key Tangents',
430 description='Export vertex tangents with shape keys (morph targets)',
431 default=False
434 export_lights: BoolProperty(
435 name='Punctual Lights',
436 description='Export directional, point, and spot lights. '
437 'Uses "KHR_lights_punctual" glTF extension',
438 default=False
441 export_displacement: BoolProperty(
442 name='Displacement Textures (EXPERIMENTAL)',
443 description='EXPERIMENTAL: Export displacement textures. '
444 'Uses incomplete "KHR_materials_displacement" glTF extension',
445 default=False
448 will_save_settings: BoolProperty(
449 name='Remember Export Settings',
450 description='Store glTF export settings in the Blender project',
451 default=False)
453 # Custom scene property for saving settings
454 scene_key = "glTF2ExportSettings"
458 def check(self, _context):
459 # Ensure file extension matches format
460 old_filepath = self.filepath
461 self.filepath = ensure_filepath_matches_export_format(
462 self.filepath,
463 self.export_format,
465 return self.filepath != old_filepath
467 def invoke(self, context, event):
468 settings = context.scene.get(self.scene_key)
469 self.will_save_settings = False
470 if settings:
471 try:
472 for (k, v) in settings.items():
473 setattr(self, k, v)
474 self.will_save_settings = True
476 except (AttributeError, TypeError):
477 self.report({"ERROR"}, "Loading export settings failed. Removed corrupted settings")
478 del context.scene[self.scene_key]
480 import sys
481 preferences = bpy.context.preferences
482 for addon_name in preferences.addons.keys():
483 try:
484 if hasattr(sys.modules[addon_name], 'glTF2ExportUserExtension') or hasattr(sys.modules[addon_name], 'glTF2ExportUserExtensions'):
485 exporter_extension_panel_unregister_functors.append(sys.modules[addon_name].register_panel())
486 except Exception:
487 pass
489 self.has_active_exporter_extensions = len(exporter_extension_panel_unregister_functors) > 0
490 return ExportHelper.invoke(self, context, event)
492 def save_settings(self, context):
493 # find all props to save
494 exceptional = [
495 # options that don't start with 'export_'
496 'use_selection',
497 'use_visible',
498 'use_renderable',
499 'use_active_collection',
500 'use_mesh_edges',
501 'use_mesh_vertices',
502 'use_active_scene',
504 all_props = self.properties
505 export_props = {
506 x: getattr(self, x) for x in dir(all_props)
507 if (x.startswith("export_") or x in exceptional) and all_props.get(x) is not None
509 context.scene[self.scene_key] = export_props
511 def execute(self, context):
512 import os
513 import datetime
514 from .blender.exp import gltf2_blender_export
516 if self.will_save_settings:
517 self.save_settings(context)
519 self.check(context) # ensure filepath has the right extension
521 # All custom export settings are stored in this container.
522 export_settings = {}
524 export_settings['timestamp'] = datetime.datetime.now()
526 export_settings['gltf_filepath'] = self.filepath
527 export_settings['gltf_filedirectory'] = os.path.dirname(export_settings['gltf_filepath']) + '/'
528 export_settings['gltf_texturedirectory'] = os.path.join(
529 export_settings['gltf_filedirectory'],
530 self.export_texture_dir,
532 export_settings['gltf_keep_original_textures'] = self.export_keep_originals
534 export_settings['gltf_format'] = self.export_format
535 export_settings['gltf_image_format'] = self.export_image_format
536 export_settings['gltf_copyright'] = self.export_copyright
537 export_settings['gltf_texcoords'] = self.export_texcoords
538 export_settings['gltf_normals'] = self.export_normals
539 export_settings['gltf_tangents'] = self.export_tangents and self.export_normals
540 export_settings['gltf_loose_edges'] = self.use_mesh_edges
541 export_settings['gltf_loose_points'] = self.use_mesh_vertices
543 if self.is_draco_available:
544 export_settings['gltf_draco_mesh_compression'] = self.export_draco_mesh_compression_enable
545 export_settings['gltf_draco_mesh_compression_level'] = self.export_draco_mesh_compression_level
546 export_settings['gltf_draco_position_quantization'] = self.export_draco_position_quantization
547 export_settings['gltf_draco_normal_quantization'] = self.export_draco_normal_quantization
548 export_settings['gltf_draco_texcoord_quantization'] = self.export_draco_texcoord_quantization
549 export_settings['gltf_draco_color_quantization'] = self.export_draco_color_quantization
550 export_settings['gltf_draco_generic_quantization'] = self.export_draco_generic_quantization
551 else:
552 export_settings['gltf_draco_mesh_compression'] = False
554 export_settings['gltf_materials'] = self.export_materials
555 export_settings['gltf_colors'] = self.export_colors
556 export_settings['gltf_cameras'] = self.export_cameras
559 export_settings['gltf_visible'] = self.use_visible
560 export_settings['gltf_renderable'] = self.use_renderable
561 export_settings['gltf_active_collection'] = self.use_active_collection
562 export_settings['gltf_active_scene'] = self.use_active_scene
564 export_settings['gltf_selected'] = self.use_selection
565 export_settings['gltf_layers'] = True # self.export_layers
566 export_settings['gltf_extras'] = self.export_extras
567 export_settings['gltf_yup'] = self.export_yup
568 export_settings['gltf_apply'] = self.export_apply
569 export_settings['gltf_current_frame'] = self.export_current_frame
570 export_settings['gltf_animations'] = self.export_animations
571 export_settings['gltf_def_bones'] = self.export_def_bones
572 if self.export_animations:
573 export_settings['gltf_frame_range'] = self.export_frame_range
574 export_settings['gltf_force_sampling'] = self.export_force_sampling
575 if not self.export_force_sampling:
576 export_settings['gltf_def_bones'] = False
577 export_settings['gltf_nla_strips'] = self.export_nla_strips
578 export_settings['gltf_nla_strips_merged_animation_name'] = self.export_nla_strips_merged_animation_name
579 export_settings['gltf_optimize_animation'] = self.optimize_animation_size
580 else:
581 export_settings['gltf_frame_range'] = False
582 export_settings['gltf_move_keyframes'] = False
583 export_settings['gltf_force_sampling'] = False
584 export_settings['gltf_optimize_animation'] = False
585 export_settings['gltf_skins'] = self.export_skins
586 if self.export_skins:
587 export_settings['gltf_all_vertex_influences'] = self.export_all_influences
588 else:
589 export_settings['gltf_all_vertex_influences'] = False
590 export_settings['gltf_def_bones'] = False
591 export_settings['gltf_frame_step'] = self.export_frame_step
592 export_settings['gltf_morph'] = self.export_morph
593 if self.export_morph:
594 export_settings['gltf_morph_normal'] = self.export_morph_normal
595 else:
596 export_settings['gltf_morph_normal'] = False
597 if self.export_morph and self.export_morph_normal:
598 export_settings['gltf_morph_tangent'] = self.export_morph_tangent
599 else:
600 export_settings['gltf_morph_tangent'] = False
602 export_settings['gltf_lights'] = self.export_lights
603 export_settings['gltf_displacement'] = self.export_displacement
605 export_settings['gltf_binary'] = bytearray()
606 export_settings['gltf_binaryfilename'] = (
607 os.path.splitext(os.path.basename(self.filepath))[0] + '.bin'
610 user_extensions = []
611 pre_export_callbacks = []
612 post_export_callbacks = []
614 import sys
615 preferences = bpy.context.preferences
616 for addon_name in preferences.addons.keys():
617 try:
618 module = sys.modules[addon_name]
619 except Exception:
620 continue
621 if hasattr(module, 'glTF2ExportUserExtension'):
622 extension_ctor = module.glTF2ExportUserExtension
623 user_extensions.append(extension_ctor())
624 if hasattr(module, 'glTF2ExportUserExtensions'):
625 extension_ctors = module.glTF2ExportUserExtensions
626 for extension_ctor in extension_ctors:
627 user_extensions.append(extension_ctor())
628 if hasattr(module, 'glTF2_pre_export_callback'):
629 pre_export_callbacks.append(module.glTF2_pre_export_callback)
630 if hasattr(module, 'glTF2_post_export_callback'):
631 post_export_callbacks.append(module.glTF2_post_export_callback)
632 export_settings['gltf_user_extensions'] = user_extensions
633 export_settings['pre_export_callbacks'] = pre_export_callbacks
634 export_settings['post_export_callbacks'] = post_export_callbacks
636 return gltf2_blender_export.save(context, export_settings)
638 def draw(self, context):
639 pass # Is needed to get panels available
642 class GLTF_PT_export_main(bpy.types.Panel):
643 bl_space_type = 'FILE_BROWSER'
644 bl_region_type = 'TOOL_PROPS'
645 bl_label = ""
646 bl_parent_id = "FILE_PT_operator"
647 bl_options = {'HIDE_HEADER'}
649 @classmethod
650 def poll(cls, context):
651 sfile = context.space_data
652 operator = sfile.active_operator
654 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
656 def draw(self, context):
657 layout = self.layout
658 layout.use_property_split = True
659 layout.use_property_decorate = False # No animation.
661 sfile = context.space_data
662 operator = sfile.active_operator
664 layout.prop(operator, 'export_format')
665 if operator.export_format == 'GLTF_SEPARATE':
666 layout.prop(operator, 'export_keep_originals')
667 if operator.export_keep_originals is False:
668 layout.prop(operator, 'export_texture_dir', icon='FILE_FOLDER')
670 layout.prop(operator, 'export_copyright')
671 layout.prop(operator, 'will_save_settings')
674 class GLTF_PT_export_include(bpy.types.Panel):
675 bl_space_type = 'FILE_BROWSER'
676 bl_region_type = 'TOOL_PROPS'
677 bl_label = "Include"
678 bl_parent_id = "FILE_PT_operator"
679 bl_options = {'DEFAULT_CLOSED'}
681 @classmethod
682 def poll(cls, context):
683 sfile = context.space_data
684 operator = sfile.active_operator
686 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
688 def draw(self, context):
689 layout = self.layout
690 layout.use_property_split = True
691 layout.use_property_decorate = False # No animation.
693 sfile = context.space_data
694 operator = sfile.active_operator
696 col = layout.column(heading = "Limit to", align = True)
697 col.prop(operator, 'use_selection')
698 col.prop(operator, 'use_visible')
699 col.prop(operator, 'use_renderable')
700 col.prop(operator, 'use_active_collection')
701 col.prop(operator, 'use_active_scene')
703 col = layout.column(heading = "Data", align = True)
704 col.prop(operator, 'export_extras')
705 col.prop(operator, 'export_cameras')
706 col.prop(operator, 'export_lights')
709 class GLTF_PT_export_transform(bpy.types.Panel):
710 bl_space_type = 'FILE_BROWSER'
711 bl_region_type = 'TOOL_PROPS'
712 bl_label = "Transform"
713 bl_parent_id = "FILE_PT_operator"
714 bl_options = {'DEFAULT_CLOSED'}
716 @classmethod
717 def poll(cls, context):
718 sfile = context.space_data
719 operator = sfile.active_operator
721 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
723 def draw(self, context):
724 layout = self.layout
725 layout.use_property_split = True
726 layout.use_property_decorate = False # No animation.
728 sfile = context.space_data
729 operator = sfile.active_operator
731 layout.prop(operator, 'export_yup')
734 class GLTF_PT_export_geometry(bpy.types.Panel):
735 bl_space_type = 'FILE_BROWSER'
736 bl_region_type = 'TOOL_PROPS'
737 bl_label = "Geometry"
738 bl_parent_id = "FILE_PT_operator"
739 bl_options = {'DEFAULT_CLOSED'}
741 @classmethod
742 def poll(cls, context):
743 sfile = context.space_data
744 operator = sfile.active_operator
746 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
748 def draw(self, context):
749 layout = self.layout
750 layout.use_property_split = True
751 layout.use_property_decorate = False # No animation.
753 sfile = context.space_data
754 operator = sfile.active_operator
756 layout.prop(operator, 'export_apply')
757 layout.prop(operator, 'export_texcoords')
758 layout.prop(operator, 'export_normals')
759 col = layout.column()
760 col.active = operator.export_normals
761 col.prop(operator, 'export_tangents')
762 layout.prop(operator, 'export_colors')
764 col = layout.column()
765 col.prop(operator, 'use_mesh_edges')
766 col.prop(operator, 'use_mesh_vertices')
768 layout.prop(operator, 'export_materials')
769 col = layout.column()
770 col.active = operator.export_materials == "EXPORT"
771 col.prop(operator, 'export_image_format')
774 class GLTF_PT_export_geometry_compression(bpy.types.Panel):
775 bl_space_type = 'FILE_BROWSER'
776 bl_region_type = 'TOOL_PROPS'
777 bl_label = "Compression"
778 bl_parent_id = "GLTF_PT_export_geometry"
779 bl_options = {'DEFAULT_CLOSED'}
781 def __init__(self):
782 from io_scene_gltf2.io.com import gltf2_io_draco_compression_extension
783 self.is_draco_available = gltf2_io_draco_compression_extension.dll_exists(quiet=True)
785 @classmethod
786 def poll(cls, context):
787 sfile = context.space_data
788 operator = sfile.active_operator
789 if operator.is_draco_available:
790 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
792 def draw_header(self, context):
793 sfile = context.space_data
794 operator = sfile.active_operator
795 self.layout.prop(operator, "export_draco_mesh_compression_enable", text="")
797 def draw(self, context):
798 layout = self.layout
799 layout.use_property_split = True
800 layout.use_property_decorate = False # No animation.
802 sfile = context.space_data
803 operator = sfile.active_operator
805 layout.active = operator.export_draco_mesh_compression_enable
806 layout.prop(operator, 'export_draco_mesh_compression_level')
808 col = layout.column(align=True)
809 col.prop(operator, 'export_draco_position_quantization', text="Quantize Position")
810 col.prop(operator, 'export_draco_normal_quantization', text="Normal")
811 col.prop(operator, 'export_draco_texcoord_quantization', text="Tex Coord")
812 col.prop(operator, 'export_draco_color_quantization', text="Color")
813 col.prop(operator, 'export_draco_generic_quantization', text="Generic")
816 class GLTF_PT_export_animation(bpy.types.Panel):
817 bl_space_type = 'FILE_BROWSER'
818 bl_region_type = 'TOOL_PROPS'
819 bl_label = "Animation"
820 bl_parent_id = "FILE_PT_operator"
821 bl_options = {'DEFAULT_CLOSED'}
823 @classmethod
824 def poll(cls, context):
825 sfile = context.space_data
826 operator = sfile.active_operator
828 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
830 def draw(self, context):
831 layout = self.layout
832 layout.use_property_split = True
833 layout.use_property_decorate = False # No animation.
835 sfile = context.space_data
836 operator = sfile.active_operator
838 layout.prop(operator, 'export_current_frame')
841 class GLTF_PT_export_animation_export(bpy.types.Panel):
842 bl_space_type = 'FILE_BROWSER'
843 bl_region_type = 'TOOL_PROPS'
844 bl_label = "Animation"
845 bl_parent_id = "GLTF_PT_export_animation"
846 bl_options = {'DEFAULT_CLOSED'}
848 @classmethod
849 def poll(cls, context):
850 sfile = context.space_data
851 operator = sfile.active_operator
853 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
855 def draw_header(self, context):
856 sfile = context.space_data
857 operator = sfile.active_operator
858 self.layout.prop(operator, "export_animations", text="")
860 def draw(self, context):
861 layout = self.layout
862 layout.use_property_split = True
863 layout.use_property_decorate = False # No animation.
865 sfile = context.space_data
866 operator = sfile.active_operator
868 layout.active = operator.export_animations
870 layout.prop(operator, 'export_frame_range')
871 layout.prop(operator, 'export_frame_step')
872 layout.prop(operator, 'export_force_sampling')
873 layout.prop(operator, 'export_nla_strips')
874 if operator.export_nla_strips is False:
875 layout.prop(operator, 'export_nla_strips_merged_animation_name')
876 layout.prop(operator, 'optimize_animation_size')
879 class GLTF_PT_export_animation_shapekeys(bpy.types.Panel):
880 bl_space_type = 'FILE_BROWSER'
881 bl_region_type = 'TOOL_PROPS'
882 bl_label = "Shape Keys"
883 bl_parent_id = "GLTF_PT_export_animation"
884 bl_options = {'DEFAULT_CLOSED'}
886 @classmethod
887 def poll(cls, context):
888 sfile = context.space_data
889 operator = sfile.active_operator
891 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
893 def draw_header(self, context):
894 sfile = context.space_data
895 operator = sfile.active_operator
896 self.layout.prop(operator, "export_morph", text="")
898 def draw(self, context):
899 layout = self.layout
900 layout.use_property_split = True
901 layout.use_property_decorate = False # No animation.
903 sfile = context.space_data
904 operator = sfile.active_operator
906 layout.active = operator.export_morph
908 layout.prop(operator, 'export_morph_normal')
909 col = layout.column()
910 col.active = operator.export_morph_normal
911 col.prop(operator, 'export_morph_tangent')
914 class GLTF_PT_export_animation_skinning(bpy.types.Panel):
915 bl_space_type = 'FILE_BROWSER'
916 bl_region_type = 'TOOL_PROPS'
917 bl_label = "Skinning"
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_skins", 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_skins
942 layout.prop(operator, 'export_all_influences')
944 row = layout.row()
945 row.active = operator.export_force_sampling
946 row.prop(operator, 'export_def_bones')
947 if operator.export_force_sampling is False and operator.export_def_bones is True:
948 layout.label(text="Export only deformation bones is not possible when not sampling animation")
950 class GLTF_PT_export_user_extensions(bpy.types.Panel):
951 bl_space_type = 'FILE_BROWSER'
952 bl_region_type = 'TOOL_PROPS'
953 bl_label = "Exporter Extensions"
954 bl_parent_id = "FILE_PT_operator"
955 bl_options = {'DEFAULT_CLOSED'}
957 @classmethod
958 def poll(cls, context):
959 sfile = context.space_data
960 operator = sfile.active_operator
962 return operator.bl_idname == "EXPORT_SCENE_OT_gltf" and operator.has_active_exporter_extensions
964 def draw(self, context):
965 layout = self.layout
966 layout.use_property_split = True
967 layout.use_property_decorate = False # No animation.
969 class GLTF_PT_import_user_extensions(bpy.types.Panel):
970 bl_space_type = 'FILE_BROWSER'
971 bl_region_type = 'TOOL_PROPS'
972 bl_label = "Importer Extensions"
973 bl_parent_id = "FILE_PT_operator"
974 bl_options = {'DEFAULT_CLOSED'}
976 @classmethod
977 def poll(cls, context):
978 sfile = context.space_data
979 operator = sfile.active_operator
980 return operator.bl_idname == "IMPORT_SCENE_OT_gltf" and operator.has_active_importer_extensions
982 def draw(self, context):
983 layout = self.layout
984 layout.use_property_split = True
985 layout.use_property_decorate = False # No animation.
987 class ExportGLTF2(bpy.types.Operator, ExportGLTF2_Base, ExportHelper):
988 """Export scene as glTF 2.0 file"""
989 bl_idname = 'export_scene.gltf'
990 bl_label = 'Export glTF 2.0'
992 filename_ext = ''
994 filter_glob: StringProperty(default='*.glb;*.gltf', options={'HIDDEN'})
997 def menu_func_export(self, context):
998 self.layout.operator(ExportGLTF2.bl_idname, text='glTF 2.0 (.glb/.gltf)')
1001 class ImportGLTF2(Operator, ImportHelper):
1002 """Load a glTF 2.0 file"""
1003 bl_idname = 'import_scene.gltf'
1004 bl_label = 'Import glTF 2.0'
1005 bl_options = {'REGISTER', 'UNDO'}
1007 filter_glob: StringProperty(default="*.glb;*.gltf", options={'HIDDEN'})
1009 files: CollectionProperty(
1010 name="File Path",
1011 type=bpy.types.OperatorFileListElement,
1014 loglevel: IntProperty(
1015 name='Log Level',
1016 description="Log Level")
1018 import_pack_images: BoolProperty(
1019 name='Pack Images',
1020 description='Pack all images into .blend file',
1021 default=True
1024 merge_vertices: BoolProperty(
1025 name='Merge Vertices',
1026 description=(
1027 'The glTF format requires discontinuous normals, UVs, and '
1028 'other vertex attributes to be stored as separate vertices, '
1029 'as required for rendering on typical graphics hardware. '
1030 'This option attempts to combine co-located vertices where possible. '
1031 'Currently cannot combine verts with different normals'
1033 default=False,
1036 import_shading: EnumProperty(
1037 name="Shading",
1038 items=(("NORMALS", "Use Normal Data", ""),
1039 ("FLAT", "Flat Shading", ""),
1040 ("SMOOTH", "Smooth Shading", "")),
1041 description="How normals are computed during import",
1042 default="NORMALS")
1044 bone_heuristic: EnumProperty(
1045 name="Bone Dir",
1046 items=(
1047 ("BLENDER", "Blender (best for re-importing)",
1048 "Good for re-importing glTFs exported from Blender. "
1049 "Bone tips are placed on their local +Y axis (in glTF space)"),
1050 ("TEMPERANCE", "Temperance (average)",
1051 "Decent all-around strategy. "
1052 "A bone with one child has its tip placed on the local axis "
1053 "closest to its child"),
1054 ("FORTUNE", "Fortune (may look better, less accurate)",
1055 "Might look better than Temperance, but also might have errors. "
1056 "A bone with one child has its tip placed at its child's root. "
1057 "Non-uniform scalings may get messed up though, so beware"),
1059 description="Heuristic for placing bones. Tries to make bones pretty",
1060 default="TEMPERANCE",
1063 guess_original_bind_pose: BoolProperty(
1064 name='Guess Original Bind Pose',
1065 description=(
1066 'Try to guess the original bind pose for skinned meshes from '
1067 'the inverse bind matrices. '
1068 'When off, use default/rest pose as bind pose'
1070 default=True,
1073 def draw(self, context):
1074 layout = self.layout
1076 layout.use_property_split = True
1077 layout.use_property_decorate = False # No animation.
1079 layout.prop(self, 'import_pack_images')
1080 layout.prop(self, 'merge_vertices')
1081 layout.prop(self, 'import_shading')
1082 layout.prop(self, 'guess_original_bind_pose')
1083 layout.prop(self, 'bone_heuristic')
1085 def invoke(self, context, event):
1086 import sys
1087 preferences = bpy.context.preferences
1088 for addon_name in preferences.addons.keys():
1089 try:
1090 if hasattr(sys.modules[addon_name], 'glTF2ImportUserExtension') or hasattr(sys.modules[addon_name], 'glTF2ImportUserExtensions'):
1091 importer_extension_panel_unregister_functors.append(sys.modules[addon_name].register_panel())
1092 except Exception:
1093 pass
1095 self.has_active_importer_extensions = len(importer_extension_panel_unregister_functors) > 0
1096 return ImportHelper.invoke(self, context, event)
1098 def execute(self, context):
1099 return self.import_gltf2(context)
1101 def import_gltf2(self, context):
1102 import os
1104 self.set_debug_log()
1105 import_settings = self.as_keywords()
1107 user_extensions = []
1109 import sys
1110 preferences = bpy.context.preferences
1111 for addon_name in preferences.addons.keys():
1112 try:
1113 module = sys.modules[addon_name]
1114 except Exception:
1115 continue
1116 if hasattr(module, 'glTF2ImportUserExtension'):
1117 extension_ctor = module.glTF2ImportUserExtension
1118 user_extensions.append(extension_ctor())
1119 import_settings['import_user_extensions'] = user_extensions
1121 if self.files:
1122 # Multiple file import
1123 ret = {'CANCELLED'}
1124 dirname = os.path.dirname(self.filepath)
1125 for file in self.files:
1126 path = os.path.join(dirname, file.name)
1127 if self.unit_import(path, import_settings) == {'FINISHED'}:
1128 ret = {'FINISHED'}
1129 return ret
1130 else:
1131 # Single file import
1132 return self.unit_import(self.filepath, import_settings)
1134 def unit_import(self, filename, import_settings):
1135 import time
1136 from .io.imp.gltf2_io_gltf import glTFImporter, ImportError
1137 from .blender.imp.gltf2_blender_gltf import BlenderGlTF
1139 try:
1140 gltf_importer = glTFImporter(filename, import_settings)
1141 gltf_importer.read()
1142 gltf_importer.checks()
1144 print("Data are loaded, start creating Blender stuff")
1146 start_time = time.time()
1147 BlenderGlTF.create(gltf_importer)
1148 elapsed_s = "{:.2f}s".format(time.time() - start_time)
1149 print("glTF import finished in " + elapsed_s)
1151 gltf_importer.log.removeHandler(gltf_importer.log_handler)
1153 return {'FINISHED'}
1155 except ImportError as e:
1156 self.report({'ERROR'}, e.args[0])
1157 return {'CANCELLED'}
1159 def set_debug_log(self):
1160 import logging
1161 if bpy.app.debug_value == 0:
1162 self.loglevel = logging.CRITICAL
1163 elif bpy.app.debug_value == 1:
1164 self.loglevel = logging.ERROR
1165 elif bpy.app.debug_value == 2:
1166 self.loglevel = logging.WARNING
1167 elif bpy.app.debug_value == 3:
1168 self.loglevel = logging.INFO
1169 else:
1170 self.loglevel = logging.NOTSET
1173 class GLTF_AddonPreferences(bpy.types.AddonPreferences):
1174 bl_idname = __package__
1176 settings_node_ui : bpy.props.BoolProperty(
1177 default= False,
1178 description="Displays glTF Settings node in Shader Editor (Menu Add > Output)"
1182 def draw(self, context):
1183 layout = self.layout
1184 row = layout.row()
1185 row.prop(self, "settings_node_ui", text="Shader Editor Add-ons")
1187 def menu_func_import(self, context):
1188 self.layout.operator(ImportGLTF2.bl_idname, text='glTF 2.0 (.glb/.gltf)')
1191 classes = (
1192 ExportGLTF2,
1193 GLTF_PT_export_main,
1194 GLTF_PT_export_include,
1195 GLTF_PT_export_transform,
1196 GLTF_PT_export_geometry,
1197 GLTF_PT_export_geometry_compression,
1198 GLTF_PT_export_animation,
1199 GLTF_PT_export_animation_export,
1200 GLTF_PT_export_animation_shapekeys,
1201 GLTF_PT_export_animation_skinning,
1202 GLTF_PT_export_user_extensions,
1203 ImportGLTF2,
1204 GLTF_PT_import_user_extensions,
1205 GLTF_AddonPreferences
1209 def register():
1210 import io_scene_gltf2.blender.com.gltf2_blender_ui as blender_ui
1211 for c in classes:
1212 bpy.utils.register_class(c)
1213 # bpy.utils.register_module(__name__)
1215 blender_ui.register()
1217 # add to the export / import menu
1218 bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
1219 bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
1222 def unregister():
1223 import io_scene_gltf2.blender.com.gltf2_blender_ui as blender_ui
1224 for c in classes:
1225 bpy.utils.unregister_class(c)
1226 for f in exporter_extension_panel_unregister_functors:
1228 exporter_extension_panel_unregister_functors.clear()
1230 for f in importer_extension_panel_unregister_functors:
1232 importer_extension_panel_unregister_functors.clear()
1234 blender_ui.unregister()
1236 # bpy.utils.unregister_module(__name__)
1238 # remove from the export / import menu
1239 bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
1240 bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)