File headers: use SPDX license identifiers
[blender-addons.git] / io_scene_gltf2 / __init__.py
blob6b3dd1966b4d1b4722da4f1c595325409563e019
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, 2, 2),
8 'blender': (3, 1, 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'),
153 description=(
154 'Output format for images. PNG is lossless and generally preferred, but JPEG might be preferable for web '
155 'applications due to the smaller file size'
157 default='AUTO'
160 export_texture_dir: StringProperty(
161 name='Textures',
162 description='Folder to place texture files in. Relative to the .gltf file',
163 default='',
166 export_keep_originals: BoolProperty(
167 name='Keep original',
168 description=('Keep original textures files if possible. '
169 'WARNING: if you use more than one texture, '
170 'where pbr standard requires only one, only one texture will be used. '
171 'This can lead to unexpected results'
173 default=False,
176 export_texcoords: BoolProperty(
177 name='UVs',
178 description='Export UVs (texture coordinates) with meshes',
179 default=True
182 export_normals: BoolProperty(
183 name='Normals',
184 description='Export vertex normals with meshes',
185 default=True
188 export_draco_mesh_compression_enable: BoolProperty(
189 name='Draco mesh compression',
190 description='Compress mesh using Draco',
191 default=False
194 export_draco_mesh_compression_level: IntProperty(
195 name='Compression level',
196 description='Compression level (0 = most speed, 6 = most compression, higher values currently not supported)',
197 default=6,
198 min=0,
199 max=10
202 export_draco_position_quantization: IntProperty(
203 name='Position quantization bits',
204 description='Quantization bits for position values (0 = no quantization)',
205 default=14,
206 min=0,
207 max=30
210 export_draco_normal_quantization: IntProperty(
211 name='Normal quantization bits',
212 description='Quantization bits for normal values (0 = no quantization)',
213 default=10,
214 min=0,
215 max=30
218 export_draco_texcoord_quantization: IntProperty(
219 name='Texcoord quantization bits',
220 description='Quantization bits for texture coordinate values (0 = no quantization)',
221 default=12,
222 min=0,
223 max=30
226 export_draco_color_quantization: IntProperty(
227 name='Color quantization bits',
228 description='Quantization bits for color values (0 = no quantization)',
229 default=10,
230 min=0,
231 max=30
234 export_draco_generic_quantization: IntProperty(
235 name='Generic quantization bits',
236 description='Quantization bits for generic coordinate values like weights or joints (0 = no quantization)',
237 default=12,
238 min=0,
239 max=30
242 export_tangents: BoolProperty(
243 name='Tangents',
244 description='Export vertex tangents with meshes',
245 default=False
248 export_materials: EnumProperty(
249 name='Materials',
250 items=(('EXPORT', 'Export',
251 'Export all materials used by included objects'),
252 ('PLACEHOLDER', 'Placeholder',
253 'Do not export materials, but write multiple primitive groups per mesh, keeping material slot information'),
254 ('NONE', 'No export',
255 'Do not export materials, and combine mesh primitive groups, losing material slot information')),
256 description='Export materials ',
257 default='EXPORT'
260 export_colors: BoolProperty(
261 name='Vertex Colors',
262 description='Export vertex colors with meshes',
263 default=True
266 use_mesh_edges: BoolProperty(
267 name='Loose Edges',
268 description=(
269 'Export loose edges as lines, using the material from the first material slot'
271 default=False,
274 use_mesh_vertices: BoolProperty(
275 name='Loose Points',
276 description=(
277 'Export loose points as glTF points, using the material from the first material slot'
279 default=False,
282 export_cameras: BoolProperty(
283 name='Cameras',
284 description='Export cameras',
285 default=False
288 # keep it for compatibility (for now)
289 export_selected: BoolProperty(
290 name='Selected Objects',
291 description='Export selected objects only',
292 default=False
295 use_selection: BoolProperty(
296 name='Selected Objects',
297 description='Export selected objects only',
298 default=False
301 use_visible: BoolProperty(
302 name='Visible Objects',
303 description='Export visible objects only',
304 default=False
307 use_renderable: BoolProperty(
308 name='Renderable Objects',
309 description='Export renderable objects only',
310 default=False
313 use_active_collection: BoolProperty(
314 name='Active Collection',
315 description='Export objects in the active collection only',
316 default=False
319 export_extras: BoolProperty(
320 name='Custom Properties',
321 description='Export custom properties as glTF extras',
322 default=False
325 export_yup: BoolProperty(
326 name='+Y Up',
327 description='Export using glTF convention, +Y up',
328 default=True
331 export_apply: BoolProperty(
332 name='Apply Modifiers',
333 description='Apply modifiers (excluding Armatures) to mesh objects -'
334 'WARNING: prevents exporting shape keys',
335 default=False
338 export_animations: BoolProperty(
339 name='Animations',
340 description='Exports active actions and NLA tracks as glTF animations',
341 default=True
344 export_frame_range: BoolProperty(
345 name='Limit to Playback Range',
346 description='Clips animations to selected playback range',
347 default=True
350 export_frame_step: IntProperty(
351 name='Sampling Rate',
352 description='How often to evaluate animated values (in frames)',
353 default=1,
354 min=1,
355 max=120
358 export_force_sampling: BoolProperty(
359 name='Always Sample Animations',
360 description='Apply sampling to all animations',
361 default=True
364 export_nla_strips: BoolProperty(
365 name='Group by NLA Track',
366 description=(
367 "When on, multiple actions become part of the same glTF animation if "
368 "they're pushed onto NLA tracks with the same name. "
369 "When off, all the currently assigned actions become one glTF animation"
371 default=True
374 export_def_bones: BoolProperty(
375 name='Export Deformation Bones Only',
376 description='Export Deformation bones only (and needed bones for hierarchy)',
377 default=False
380 export_current_frame: BoolProperty(
381 name='Use Current Frame',
382 description='Export the scene in the current animation frame',
383 default=False
386 export_skins: BoolProperty(
387 name='Skinning',
388 description='Export skinning (armature) data',
389 default=True
392 export_all_influences: BoolProperty(
393 name='Include All Bone Influences',
394 description='Allow >4 joint vertex influences. Models may appear incorrectly in many viewers',
395 default=False
398 export_morph: BoolProperty(
399 name='Shape Keys',
400 description='Export shape keys (morph targets)',
401 default=True
404 export_morph_normal: BoolProperty(
405 name='Shape Key Normals',
406 description='Export vertex normals with shape keys (morph targets)',
407 default=True
410 export_morph_tangent: BoolProperty(
411 name='Shape Key Tangents',
412 description='Export vertex tangents with shape keys (morph targets)',
413 default=False
416 export_lights: BoolProperty(
417 name='Punctual Lights',
418 description='Export directional, point, and spot lights. '
419 'Uses "KHR_lights_punctual" glTF extension',
420 default=False
423 export_displacement: BoolProperty(
424 name='Displacement Textures (EXPERIMENTAL)',
425 description='EXPERIMENTAL: Export displacement textures. '
426 'Uses incomplete "KHR_materials_displacement" glTF extension',
427 default=False
430 will_save_settings: BoolProperty(
431 name='Remember Export Settings',
432 description='Store glTF export settings in the Blender project',
433 default=False)
435 # Custom scene property for saving settings
436 scene_key = "glTF2ExportSettings"
440 def check(self, _context):
441 # Ensure file extension matches format
442 old_filepath = self.filepath
443 self.filepath = ensure_filepath_matches_export_format(
444 self.filepath,
445 self.export_format,
447 return self.filepath != old_filepath
449 def invoke(self, context, event):
450 settings = context.scene.get(self.scene_key)
451 self.will_save_settings = False
452 if settings:
453 try:
454 if 'export_selected' in settings.keys(): # Back compatibility for export_selected --> use_selection
455 setattr(self, "use_selection", settings['export_selected'])
456 settings["use_selection"] = settings['export_selected']
457 del settings['export_selected']
458 print("export_selected is now renamed use_selection, and will be deleted in a few release")
459 for (k, v) in settings.items():
460 setattr(self, k, v)
461 self.will_save_settings = True
463 except (AttributeError, TypeError):
464 self.report({"ERROR"}, "Loading export settings failed. Removed corrupted settings")
465 del context.scene[self.scene_key]
467 import sys
468 preferences = bpy.context.preferences
469 for addon_name in preferences.addons.keys():
470 try:
471 if hasattr(sys.modules[addon_name], 'glTF2ExportUserExtension') or hasattr(sys.modules[addon_name], 'glTF2ExportUserExtensions'):
472 exporter_extension_panel_unregister_functors.append(sys.modules[addon_name].register_panel())
473 except Exception:
474 pass
476 self.has_active_exporter_extensions = len(exporter_extension_panel_unregister_functors) > 0
477 return ExportHelper.invoke(self, context, event)
479 def save_settings(self, context):
480 # find all props to save
481 exceptional = [
482 # options that don't start with 'export_'
483 'use_selection',
484 'use_visible',
485 'use_renderable',
486 'use_active_collection',
487 'use_mesh_edges',
488 'use_mesh_vertices',
490 all_props = self.properties
491 export_props = {
492 x: getattr(self, x) for x in dir(all_props)
493 if (x.startswith("export_") or x in exceptional) and all_props.get(x) is not None
495 if 'export_selected' in export_props.keys():
496 del export_props['export_selected'] # Do not save this property, only here for backward compatibility
497 context.scene[self.scene_key] = export_props
499 def execute(self, context):
500 import os
501 import datetime
502 from .blender.exp import gltf2_blender_export
504 if self.will_save_settings:
505 self.save_settings(context)
507 self.check(context) # ensure filepath has the right extension
509 # All custom export settings are stored in this container.
510 export_settings = {}
512 export_settings['timestamp'] = datetime.datetime.now()
514 export_settings['gltf_filepath'] = self.filepath
515 export_settings['gltf_filedirectory'] = os.path.dirname(export_settings['gltf_filepath']) + '/'
516 export_settings['gltf_texturedirectory'] = os.path.join(
517 export_settings['gltf_filedirectory'],
518 self.export_texture_dir,
520 export_settings['gltf_keep_original_textures'] = self.export_keep_originals
522 export_settings['gltf_format'] = self.export_format
523 export_settings['gltf_image_format'] = self.export_image_format
524 export_settings['gltf_copyright'] = self.export_copyright
525 export_settings['gltf_texcoords'] = self.export_texcoords
526 export_settings['gltf_normals'] = self.export_normals
527 export_settings['gltf_tangents'] = self.export_tangents and self.export_normals
528 export_settings['gltf_loose_edges'] = self.use_mesh_edges
529 export_settings['gltf_loose_points'] = self.use_mesh_vertices
531 if self.is_draco_available:
532 export_settings['gltf_draco_mesh_compression'] = self.export_draco_mesh_compression_enable
533 export_settings['gltf_draco_mesh_compression_level'] = self.export_draco_mesh_compression_level
534 export_settings['gltf_draco_position_quantization'] = self.export_draco_position_quantization
535 export_settings['gltf_draco_normal_quantization'] = self.export_draco_normal_quantization
536 export_settings['gltf_draco_texcoord_quantization'] = self.export_draco_texcoord_quantization
537 export_settings['gltf_draco_color_quantization'] = self.export_draco_color_quantization
538 export_settings['gltf_draco_generic_quantization'] = self.export_draco_generic_quantization
539 else:
540 export_settings['gltf_draco_mesh_compression'] = False
542 export_settings['gltf_materials'] = self.export_materials
543 export_settings['gltf_colors'] = self.export_colors
544 export_settings['gltf_cameras'] = self.export_cameras
546 # compatibility after renaming export_selected to use_selection
547 if self.export_selected is True:
548 self.report({"WARNING"}, "export_selected is now renamed use_selection, and will be deleted in a few release")
549 export_settings['gltf_selected'] = self.export_selected
550 else:
551 export_settings['gltf_selected'] = self.use_selection
553 export_settings['gltf_visible'] = self.use_visible
554 export_settings['gltf_renderable'] = self.use_renderable
555 export_settings['gltf_active_collection'] = self.use_active_collection
557 # export_settings['gltf_selected'] = self.use_selection This can be uncomment when removing compatibility of export_selected
558 export_settings['gltf_layers'] = True # self.export_layers
559 export_settings['gltf_extras'] = self.export_extras
560 export_settings['gltf_yup'] = self.export_yup
561 export_settings['gltf_apply'] = self.export_apply
562 export_settings['gltf_current_frame'] = self.export_current_frame
563 export_settings['gltf_animations'] = self.export_animations
564 if self.export_animations:
565 export_settings['gltf_frame_range'] = self.export_frame_range
566 export_settings['gltf_force_sampling'] = self.export_force_sampling
567 if self.export_force_sampling:
568 export_settings['gltf_def_bones'] = self.export_def_bones
569 else:
570 export_settings['gltf_def_bones'] = False
571 export_settings['gltf_nla_strips'] = self.export_nla_strips
572 else:
573 export_settings['gltf_frame_range'] = False
574 export_settings['gltf_move_keyframes'] = False
575 export_settings['gltf_force_sampling'] = False
576 export_settings['gltf_def_bones'] = False
577 export_settings['gltf_skins'] = self.export_skins
578 if self.export_skins:
579 export_settings['gltf_all_vertex_influences'] = self.export_all_influences
580 else:
581 export_settings['gltf_all_vertex_influences'] = False
582 export_settings['gltf_frame_step'] = self.export_frame_step
583 export_settings['gltf_morph'] = self.export_morph
584 if self.export_morph:
585 export_settings['gltf_morph_normal'] = self.export_morph_normal
586 else:
587 export_settings['gltf_morph_normal'] = False
588 if self.export_morph and self.export_morph_normal:
589 export_settings['gltf_morph_tangent'] = self.export_morph_tangent
590 else:
591 export_settings['gltf_morph_tangent'] = False
593 export_settings['gltf_lights'] = self.export_lights
594 export_settings['gltf_displacement'] = self.export_displacement
596 export_settings['gltf_binary'] = bytearray()
597 export_settings['gltf_binaryfilename'] = (
598 os.path.splitext(os.path.basename(self.filepath))[0] + '.bin'
601 user_extensions = []
602 pre_export_callbacks = []
603 post_export_callbacks = []
605 import sys
606 preferences = bpy.context.preferences
607 for addon_name in preferences.addons.keys():
608 try:
609 module = sys.modules[addon_name]
610 except Exception:
611 continue
612 if hasattr(module, 'glTF2ExportUserExtension'):
613 extension_ctor = module.glTF2ExportUserExtension
614 user_extensions.append(extension_ctor())
615 if hasattr(module, 'glTF2ExportUserExtensions'):
616 extension_ctors = module.glTF2ExportUserExtensions
617 for extension_ctor in extension_ctors:
618 user_extensions.append(extension_ctor())
619 if hasattr(module, 'glTF2_pre_export_callback'):
620 pre_export_callbacks.append(module.glTF2_pre_export_callback)
621 if hasattr(module, 'glTF2_post_export_callback'):
622 post_export_callbacks.append(module.glTF2_post_export_callback)
623 export_settings['gltf_user_extensions'] = user_extensions
624 export_settings['pre_export_callbacks'] = pre_export_callbacks
625 export_settings['post_export_callbacks'] = post_export_callbacks
627 return gltf2_blender_export.save(context, export_settings)
629 def draw(self, context):
630 pass # Is needed to get panels available
633 class GLTF_PT_export_main(bpy.types.Panel):
634 bl_space_type = 'FILE_BROWSER'
635 bl_region_type = 'TOOL_PROPS'
636 bl_label = ""
637 bl_parent_id = "FILE_PT_operator"
638 bl_options = {'HIDE_HEADER'}
640 @classmethod
641 def poll(cls, context):
642 sfile = context.space_data
643 operator = sfile.active_operator
645 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
647 def draw(self, context):
648 layout = self.layout
649 layout.use_property_split = True
650 layout.use_property_decorate = False # No animation.
652 sfile = context.space_data
653 operator = sfile.active_operator
655 layout.prop(operator, 'export_format')
656 if operator.export_format == 'GLTF_SEPARATE':
657 layout.prop(operator, 'export_keep_originals')
658 if operator.export_keep_originals is False:
659 layout.prop(operator, 'export_texture_dir', icon='FILE_FOLDER')
661 layout.prop(operator, 'export_copyright')
662 layout.prop(operator, 'will_save_settings')
665 class GLTF_PT_export_include(bpy.types.Panel):
666 bl_space_type = 'FILE_BROWSER'
667 bl_region_type = 'TOOL_PROPS'
668 bl_label = "Include"
669 bl_parent_id = "FILE_PT_operator"
670 bl_options = {'DEFAULT_CLOSED'}
672 @classmethod
673 def poll(cls, context):
674 sfile = context.space_data
675 operator = sfile.active_operator
677 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
679 def draw(self, context):
680 layout = self.layout
681 layout.use_property_split = True
682 layout.use_property_decorate = False # No animation.
684 sfile = context.space_data
685 operator = sfile.active_operator
687 col = layout.column(heading = "Limit to", align = True)
688 col.prop(operator, 'use_selection')
689 col.prop(operator, 'use_visible')
690 col.prop(operator, 'use_renderable')
691 col.prop(operator, 'use_active_collection')
693 col = layout.column(heading = "Data", align = True)
694 col.prop(operator, 'export_extras')
695 col.prop(operator, 'export_cameras')
696 col.prop(operator, 'export_lights')
699 class GLTF_PT_export_transform(bpy.types.Panel):
700 bl_space_type = 'FILE_BROWSER'
701 bl_region_type = 'TOOL_PROPS'
702 bl_label = "Transform"
703 bl_parent_id = "FILE_PT_operator"
704 bl_options = {'DEFAULT_CLOSED'}
706 @classmethod
707 def poll(cls, context):
708 sfile = context.space_data
709 operator = sfile.active_operator
711 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
713 def draw(self, context):
714 layout = self.layout
715 layout.use_property_split = True
716 layout.use_property_decorate = False # No animation.
718 sfile = context.space_data
719 operator = sfile.active_operator
721 layout.prop(operator, 'export_yup')
724 class GLTF_PT_export_geometry(bpy.types.Panel):
725 bl_space_type = 'FILE_BROWSER'
726 bl_region_type = 'TOOL_PROPS'
727 bl_label = "Geometry"
728 bl_parent_id = "FILE_PT_operator"
729 bl_options = {'DEFAULT_CLOSED'}
731 @classmethod
732 def poll(cls, context):
733 sfile = context.space_data
734 operator = sfile.active_operator
736 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
738 def draw(self, context):
739 layout = self.layout
740 layout.use_property_split = True
741 layout.use_property_decorate = False # No animation.
743 sfile = context.space_data
744 operator = sfile.active_operator
746 layout.prop(operator, 'export_apply')
747 layout.prop(operator, 'export_texcoords')
748 layout.prop(operator, 'export_normals')
749 col = layout.column()
750 col.active = operator.export_normals
751 col.prop(operator, 'export_tangents')
752 layout.prop(operator, 'export_colors')
754 col = layout.column()
755 col.prop(operator, 'use_mesh_edges')
756 col.prop(operator, 'use_mesh_vertices')
758 layout.prop(operator, 'export_materials')
759 col = layout.column()
760 col.active = operator.export_materials == "EXPORT"
761 col.prop(operator, 'export_image_format')
764 class GLTF_PT_export_geometry_compression(bpy.types.Panel):
765 bl_space_type = 'FILE_BROWSER'
766 bl_region_type = 'TOOL_PROPS'
767 bl_label = "Compression"
768 bl_parent_id = "GLTF_PT_export_geometry"
769 bl_options = {'DEFAULT_CLOSED'}
771 def __init__(self):
772 from io_scene_gltf2.io.com import gltf2_io_draco_compression_extension
773 self.is_draco_available = gltf2_io_draco_compression_extension.dll_exists(quiet=True)
775 @classmethod
776 def poll(cls, context):
777 sfile = context.space_data
778 operator = sfile.active_operator
779 if operator.is_draco_available:
780 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
782 def draw_header(self, context):
783 sfile = context.space_data
784 operator = sfile.active_operator
785 self.layout.prop(operator, "export_draco_mesh_compression_enable", text="")
787 def draw(self, context):
788 layout = self.layout
789 layout.use_property_split = True
790 layout.use_property_decorate = False # No animation.
792 sfile = context.space_data
793 operator = sfile.active_operator
795 layout.active = operator.export_draco_mesh_compression_enable
796 layout.prop(operator, 'export_draco_mesh_compression_level')
798 col = layout.column(align=True)
799 col.prop(operator, 'export_draco_position_quantization', text="Quantize Position")
800 col.prop(operator, 'export_draco_normal_quantization', text="Normal")
801 col.prop(operator, 'export_draco_texcoord_quantization', text="Tex Coord")
802 col.prop(operator, 'export_draco_color_quantization', text="Color")
803 col.prop(operator, 'export_draco_generic_quantization', text="Generic")
806 class GLTF_PT_export_animation(bpy.types.Panel):
807 bl_space_type = 'FILE_BROWSER'
808 bl_region_type = 'TOOL_PROPS'
809 bl_label = "Animation"
810 bl_parent_id = "FILE_PT_operator"
811 bl_options = {'DEFAULT_CLOSED'}
813 @classmethod
814 def poll(cls, context):
815 sfile = context.space_data
816 operator = sfile.active_operator
818 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
820 def draw(self, context):
821 layout = self.layout
822 layout.use_property_split = True
823 layout.use_property_decorate = False # No animation.
825 sfile = context.space_data
826 operator = sfile.active_operator
828 layout.prop(operator, 'export_current_frame')
831 class GLTF_PT_export_animation_export(bpy.types.Panel):
832 bl_space_type = 'FILE_BROWSER'
833 bl_region_type = 'TOOL_PROPS'
834 bl_label = "Animation"
835 bl_parent_id = "GLTF_PT_export_animation"
836 bl_options = {'DEFAULT_CLOSED'}
838 @classmethod
839 def poll(cls, context):
840 sfile = context.space_data
841 operator = sfile.active_operator
843 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
845 def draw_header(self, context):
846 sfile = context.space_data
847 operator = sfile.active_operator
848 self.layout.prop(operator, "export_animations", text="")
850 def draw(self, context):
851 layout = self.layout
852 layout.use_property_split = True
853 layout.use_property_decorate = False # No animation.
855 sfile = context.space_data
856 operator = sfile.active_operator
858 layout.active = operator.export_animations
860 layout.prop(operator, 'export_frame_range')
861 layout.prop(operator, 'export_frame_step')
862 layout.prop(operator, 'export_force_sampling')
863 layout.prop(operator, 'export_nla_strips')
865 row = layout.row()
866 row.active = operator.export_force_sampling
867 row.prop(operator, 'export_def_bones')
870 class GLTF_PT_export_animation_shapekeys(bpy.types.Panel):
871 bl_space_type = 'FILE_BROWSER'
872 bl_region_type = 'TOOL_PROPS'
873 bl_label = "Shape Keys"
874 bl_parent_id = "GLTF_PT_export_animation"
875 bl_options = {'DEFAULT_CLOSED'}
877 @classmethod
878 def poll(cls, context):
879 sfile = context.space_data
880 operator = sfile.active_operator
882 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
884 def draw_header(self, context):
885 sfile = context.space_data
886 operator = sfile.active_operator
887 self.layout.prop(operator, "export_morph", text="")
889 def draw(self, context):
890 layout = self.layout
891 layout.use_property_split = True
892 layout.use_property_decorate = False # No animation.
894 sfile = context.space_data
895 operator = sfile.active_operator
897 layout.active = operator.export_morph
899 layout.prop(operator, 'export_morph_normal')
900 col = layout.column()
901 col.active = operator.export_morph_normal
902 col.prop(operator, 'export_morph_tangent')
905 class GLTF_PT_export_animation_skinning(bpy.types.Panel):
906 bl_space_type = 'FILE_BROWSER'
907 bl_region_type = 'TOOL_PROPS'
908 bl_label = "Skinning"
909 bl_parent_id = "GLTF_PT_export_animation"
910 bl_options = {'DEFAULT_CLOSED'}
912 @classmethod
913 def poll(cls, context):
914 sfile = context.space_data
915 operator = sfile.active_operator
917 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
919 def draw_header(self, context):
920 sfile = context.space_data
921 operator = sfile.active_operator
922 self.layout.prop(operator, "export_skins", text="")
924 def draw(self, context):
925 layout = self.layout
926 layout.use_property_split = True
927 layout.use_property_decorate = False # No animation.
929 sfile = context.space_data
930 operator = sfile.active_operator
932 layout.active = operator.export_skins
933 layout.prop(operator, 'export_all_influences')
935 class GLTF_PT_export_user_extensions(bpy.types.Panel):
936 bl_space_type = 'FILE_BROWSER'
937 bl_region_type = 'TOOL_PROPS'
938 bl_label = "Exporter Extensions"
939 bl_parent_id = "FILE_PT_operator"
940 bl_options = {'DEFAULT_CLOSED'}
942 @classmethod
943 def poll(cls, context):
944 sfile = context.space_data
945 operator = sfile.active_operator
947 return operator.bl_idname == "EXPORT_SCENE_OT_gltf" and operator.has_active_exporter_extensions
949 def draw(self, context):
950 layout = self.layout
951 layout.use_property_split = True
952 layout.use_property_decorate = False # No animation.
954 class GLTF_PT_import_user_extensions(bpy.types.Panel):
955 bl_space_type = 'FILE_BROWSER'
956 bl_region_type = 'TOOL_PROPS'
957 bl_label = "Importer Extensions"
958 bl_parent_id = "FILE_PT_operator"
959 bl_options = {'DEFAULT_CLOSED'}
961 @classmethod
962 def poll(cls, context):
963 sfile = context.space_data
964 operator = sfile.active_operator
965 return operator.bl_idname == "IMPORT_SCENE_OT_gltf" and operator.has_active_importer_extensions
967 def draw(self, context):
968 layout = self.layout
969 layout.use_property_split = True
970 layout.use_property_decorate = False # No animation.
972 class ExportGLTF2(bpy.types.Operator, ExportGLTF2_Base, ExportHelper):
973 """Export scene as glTF 2.0 file"""
974 bl_idname = 'export_scene.gltf'
975 bl_label = 'Export glTF 2.0'
977 filename_ext = ''
979 filter_glob: StringProperty(default='*.glb;*.gltf', options={'HIDDEN'})
982 def menu_func_export(self, context):
983 self.layout.operator(ExportGLTF2.bl_idname, text='glTF 2.0 (.glb/.gltf)')
986 class ImportGLTF2(Operator, ImportHelper):
987 """Load a glTF 2.0 file"""
988 bl_idname = 'import_scene.gltf'
989 bl_label = 'Import glTF 2.0'
990 bl_options = {'REGISTER', 'UNDO'}
992 filter_glob: StringProperty(default="*.glb;*.gltf", options={'HIDDEN'})
994 files: CollectionProperty(
995 name="File Path",
996 type=bpy.types.OperatorFileListElement,
999 loglevel: IntProperty(
1000 name='Log Level',
1001 description="Log Level")
1003 import_pack_images: BoolProperty(
1004 name='Pack Images',
1005 description='Pack all images into .blend file',
1006 default=True
1009 merge_vertices: BoolProperty(
1010 name='Merge Vertices',
1011 description=(
1012 'The glTF format requires discontinuous normals, UVs, and '
1013 'other vertex attributes to be stored as separate vertices, '
1014 'as required for rendering on typical graphics hardware. '
1015 'This option attempts to combine co-located vertices where possible. '
1016 'Currently cannot combine verts with different normals'
1018 default=False,
1021 import_shading: EnumProperty(
1022 name="Shading",
1023 items=(("NORMALS", "Use Normal Data", ""),
1024 ("FLAT", "Flat Shading", ""),
1025 ("SMOOTH", "Smooth Shading", "")),
1026 description="How normals are computed during import",
1027 default="NORMALS")
1029 bone_heuristic: EnumProperty(
1030 name="Bone Dir",
1031 items=(
1032 ("BLENDER", "Blender (best for re-importing)",
1033 "Good for re-importing glTFs exported from Blender. "
1034 "Bone tips are placed on their local +Y axis (in glTF space)"),
1035 ("TEMPERANCE", "Temperance (average)",
1036 "Decent all-around strategy. "
1037 "A bone with one child has its tip placed on the local axis "
1038 "closest to its child"),
1039 ("FORTUNE", "Fortune (may look better, less accurate)",
1040 "Might look better than Temperance, but also might have errors. "
1041 "A bone with one child has its tip placed at its child's root. "
1042 "Non-uniform scalings may get messed up though, so beware"),
1044 description="Heuristic for placing bones. Tries to make bones pretty",
1045 default="TEMPERANCE",
1048 guess_original_bind_pose: BoolProperty(
1049 name='Guess Original Bind Pose',
1050 description=(
1051 'Try to guess the original bind pose for skinned meshes from '
1052 'the inverse bind matrices. '
1053 'When off, use default/rest pose as bind pose'
1055 default=True,
1058 def draw(self, context):
1059 layout = self.layout
1061 layout.use_property_split = True
1062 layout.use_property_decorate = False # No animation.
1064 layout.prop(self, 'import_pack_images')
1065 layout.prop(self, 'merge_vertices')
1066 layout.prop(self, 'import_shading')
1067 layout.prop(self, 'guess_original_bind_pose')
1068 layout.prop(self, 'bone_heuristic')
1070 def invoke(self, context, event):
1071 import sys
1072 preferences = bpy.context.preferences
1073 for addon_name in preferences.addons.keys():
1074 try:
1075 if hasattr(sys.modules[addon_name], 'glTF2ImportUserExtension') or hasattr(sys.modules[addon_name], 'glTF2ImportUserExtensions'):
1076 importer_extension_panel_unregister_functors.append(sys.modules[addon_name].register_panel())
1077 except Exception:
1078 pass
1080 self.has_active_importer_extensions = len(importer_extension_panel_unregister_functors) > 0
1081 return ImportHelper.invoke(self, context, event)
1083 def execute(self, context):
1084 return self.import_gltf2(context)
1086 def import_gltf2(self, context):
1087 import os
1089 self.set_debug_log()
1090 import_settings = self.as_keywords()
1092 user_extensions = []
1094 import sys
1095 preferences = bpy.context.preferences
1096 for addon_name in preferences.addons.keys():
1097 try:
1098 module = sys.modules[addon_name]
1099 except Exception:
1100 continue
1101 if hasattr(module, 'glTF2ImportUserExtension'):
1102 extension_ctor = module.glTF2ImportUserExtension
1103 user_extensions.append(extension_ctor())
1104 import_settings['import_user_extensions'] = user_extensions
1106 if self.files:
1107 # Multiple file import
1108 ret = {'CANCELLED'}
1109 dirname = os.path.dirname(self.filepath)
1110 for file in self.files:
1111 path = os.path.join(dirname, file.name)
1112 if self.unit_import(path, import_settings) == {'FINISHED'}:
1113 ret = {'FINISHED'}
1114 return ret
1115 else:
1116 # Single file import
1117 return self.unit_import(self.filepath, import_settings)
1119 def unit_import(self, filename, import_settings):
1120 import time
1121 from .io.imp.gltf2_io_gltf import glTFImporter, ImportError
1122 from .blender.imp.gltf2_blender_gltf import BlenderGlTF
1124 try:
1125 gltf_importer = glTFImporter(filename, import_settings)
1126 gltf_importer.read()
1127 gltf_importer.checks()
1129 print("Data are loaded, start creating Blender stuff")
1131 start_time = time.time()
1132 BlenderGlTF.create(gltf_importer)
1133 elapsed_s = "{:.2f}s".format(time.time() - start_time)
1134 print("glTF import finished in " + elapsed_s)
1136 gltf_importer.log.removeHandler(gltf_importer.log_handler)
1138 return {'FINISHED'}
1140 except ImportError as e:
1141 self.report({'ERROR'}, e.args[0])
1142 return {'CANCELLED'}
1144 def set_debug_log(self):
1145 import logging
1146 if bpy.app.debug_value == 0:
1147 self.loglevel = logging.CRITICAL
1148 elif bpy.app.debug_value == 1:
1149 self.loglevel = logging.ERROR
1150 elif bpy.app.debug_value == 2:
1151 self.loglevel = logging.WARNING
1152 elif bpy.app.debug_value == 3:
1153 self.loglevel = logging.INFO
1154 else:
1155 self.loglevel = logging.NOTSET
1158 def menu_func_import(self, context):
1159 self.layout.operator(ImportGLTF2.bl_idname, text='glTF 2.0 (.glb/.gltf)')
1162 classes = (
1163 ExportGLTF2,
1164 GLTF_PT_export_main,
1165 GLTF_PT_export_include,
1166 GLTF_PT_export_transform,
1167 GLTF_PT_export_geometry,
1168 GLTF_PT_export_geometry_compression,
1169 GLTF_PT_export_animation,
1170 GLTF_PT_export_animation_export,
1171 GLTF_PT_export_animation_shapekeys,
1172 GLTF_PT_export_animation_skinning,
1173 GLTF_PT_export_user_extensions,
1174 ImportGLTF2,
1175 GLTF_PT_import_user_extensions
1179 def register():
1180 for c in classes:
1181 bpy.utils.register_class(c)
1182 # bpy.utils.register_module(__name__)
1184 # add to the export / import menu
1185 bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
1186 bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
1189 def unregister():
1190 for c in classes:
1191 bpy.utils.unregister_class(c)
1192 for f in exporter_extension_panel_unregister_functors:
1194 exporter_extension_panel_unregister_functors.clear()
1196 for f in importer_extension_panel_unregister_functors:
1198 importer_extension_panel_unregister_functors.clear()
1200 # bpy.utils.unregister_module(__name__)
1202 # remove from the export / import menu
1203 bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
1204 bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)