Cleanup: strip trailing space, remove BOM
[blender-addons.git] / io_scene_gltf2 / __init__.py
blobccb4517de08ac3e0cf73aa7214c715461dd52da1
1 # Copyright 2018-2021 The glTF-Blender-IO authors.
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
15 bl_info = {
16 'name': 'glTF 2.0 format',
17 'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
18 "version": (1, 7, 17),
19 'blender': (2, 91, 0),
20 'location': 'File > Import-Export',
21 'description': 'Import-Export as glTF 2.0',
22 'warning': '',
23 'doc_url': "{BLENDER_MANUAL_URL}/addons/import_export/scene_gltf2.html",
24 'tracker_url': "https://github.com/KhronosGroup/glTF-Blender-IO/issues/",
25 'support': 'OFFICIAL',
26 'category': 'Import-Export',
29 def get_version_string():
30 return str(bl_info['version'][0]) + '.' + str(bl_info['version'][1]) + '.' + str(bl_info['version'][2])
33 # Script reloading (if the user calls 'Reload Scripts' from Blender)
36 def reload_package(module_dict_main):
37 import importlib
38 from pathlib import Path
40 def reload_package_recursive(current_dir, module_dict):
41 for path in current_dir.iterdir():
42 if "__init__" in str(path) or path.stem not in module_dict:
43 continue
45 if path.is_file() and path.suffix == ".py":
46 importlib.reload(module_dict[path.stem])
47 elif path.is_dir():
48 reload_package_recursive(path, module_dict[path.stem].__dict__)
50 reload_package_recursive(Path(__file__).parent, module_dict_main)
53 if "bpy" in locals():
54 reload_package(locals())
56 import bpy
57 from bpy.props import (StringProperty,
58 BoolProperty,
59 EnumProperty,
60 IntProperty,
61 CollectionProperty)
62 from bpy.types import Operator
63 from bpy_extras.io_utils import ImportHelper, ExportHelper
67 # Functions / Classes.
70 extension_panel_unregister_functors = []
73 def ensure_filepath_matches_export_format(filepath, export_format):
74 import os
75 filename = os.path.basename(filepath)
76 if not filename:
77 return filepath
79 stem, ext = os.path.splitext(filename)
80 if stem.startswith('.') and not ext:
81 stem, ext = '', stem
83 desired_ext = '.glb' if export_format == 'GLB' else '.gltf'
84 ext_lower = ext.lower()
85 if ext_lower not in ['.glb', '.gltf']:
86 return filepath + desired_ext
87 elif ext_lower != desired_ext:
88 filepath = filepath[:-len(ext)] # strip off ext
89 return filepath + desired_ext
90 else:
91 return filepath
94 def on_export_format_changed(self, context):
95 # Update the filename in the file browser when the format (.glb/.gltf)
96 # changes
97 sfile = context.space_data
98 if not isinstance(sfile, bpy.types.SpaceFileBrowser):
99 return
100 if not sfile.active_operator:
101 return
102 if sfile.active_operator.bl_idname != "EXPORT_SCENE_OT_gltf":
103 return
105 sfile.params.filename = ensure_filepath_matches_export_format(
106 sfile.params.filename,
107 self.export_format,
111 class ExportGLTF2_Base:
112 # TODO: refactor to avoid boilerplate
114 def __init__(self):
115 from io_scene_gltf2.io.com import gltf2_io_draco_compression_extension
116 self.is_draco_available = gltf2_io_draco_compression_extension.dll_exists()
118 bl_options = {'PRESET'}
120 export_format: EnumProperty(
121 name='Format',
122 items=(('GLB', 'glTF Binary (.glb)',
123 'Exports a single file, with all data packed in binary form. '
124 'Most efficient and portable, but more difficult to edit later'),
125 ('GLTF_EMBEDDED', 'glTF Embedded (.gltf)',
126 'Exports a single file, with all data packed in JSON. '
127 'Less efficient than binary, but easier to edit later'),
128 ('GLTF_SEPARATE', 'glTF Separate (.gltf + .bin + textures)',
129 'Exports multiple files, with separate JSON, binary and texture data. '
130 'Easiest to edit later')),
131 description=(
132 'Output format and embedding options. Binary is most efficient, '
133 'but JSON (embedded or separate) may be easier to edit later'
135 default='GLB',
136 update=on_export_format_changed,
139 ui_tab: EnumProperty(
140 items=(('GENERAL', "General", "General settings"),
141 ('MESHES', "Meshes", "Mesh settings"),
142 ('OBJECTS', "Objects", "Object settings"),
143 ('ANIMATION', "Animation", "Animation settings")),
144 name="ui_tab",
145 description="Export setting categories",
148 export_copyright: StringProperty(
149 name='Copyright',
150 description='Legal rights and conditions for the model',
151 default=''
154 export_image_format: EnumProperty(
155 name='Images',
156 items=(('AUTO', 'Automatic',
157 'Save PNGs as PNGs and JPEGs as JPEGs. '
158 'If neither one, use PNG'),
159 ('JPEG', 'JPEG Format (.jpg)',
160 'Save images as JPEGs. (Images that need alpha are saved as PNGs though.) '
161 'Be aware of a possible loss in quality'),
163 description=(
164 'Output format for images. PNG is lossless and generally preferred, but JPEG might be preferable for web '
165 'applications due to the smaller file size'
167 default='AUTO'
170 export_texture_dir: StringProperty(
171 name='Textures',
172 description='Folder to place texture files in. Relative to the .gltf file',
173 default='',
176 export_keep_originals: BoolProperty(
177 name='Keep original',
178 description=('Keep original textures files if possible. '
179 'WARNING: if you use more than one texture, '
180 'where pbr standard requires only one, only one texture will be used. '
181 'This can lead to unexpected results'
183 default=False,
186 export_texcoords: BoolProperty(
187 name='UVs',
188 description='Export UVs (texture coordinates) with meshes',
189 default=True
192 export_normals: BoolProperty(
193 name='Normals',
194 description='Export vertex normals with meshes',
195 default=True
198 export_draco_mesh_compression_enable: BoolProperty(
199 name='Draco mesh compression',
200 description='Compress mesh using Draco',
201 default=False
204 export_draco_mesh_compression_level: IntProperty(
205 name='Compression level',
206 description='Compression level (0 = most speed, 6 = most compression, higher values currently not supported)',
207 default=6,
208 min=0,
209 max=10
212 export_draco_position_quantization: IntProperty(
213 name='Position quantization bits',
214 description='Quantization bits for position values (0 = no quantization)',
215 default=14,
216 min=0,
217 max=30
220 export_draco_normal_quantization: IntProperty(
221 name='Normal quantization bits',
222 description='Quantization bits for normal values (0 = no quantization)',
223 default=10,
224 min=0,
225 max=30
228 export_draco_texcoord_quantization: IntProperty(
229 name='Texcoord quantization bits',
230 description='Quantization bits for texture coordinate values (0 = no quantization)',
231 default=12,
232 min=0,
233 max=30
236 export_draco_color_quantization: IntProperty(
237 name='Color quantization bits',
238 description='Quantization bits for color values (0 = no quantization)',
239 default=10,
240 min=0,
241 max=30
244 export_draco_generic_quantization: IntProperty(
245 name='Generic quantization bits',
246 description='Quantization bits for generic coordinate values like weights or joints (0 = no quantization)',
247 default=12,
248 min=0,
249 max=30
252 export_tangents: BoolProperty(
253 name='Tangents',
254 description='Export vertex tangents with meshes',
255 default=False
258 export_materials: EnumProperty(
259 name='Materials',
260 items=(('EXPORT', 'Export',
261 'Export all materials used by included objects'),
262 ('PLACEHOLDER', 'Placeholder',
263 'Do not export materials, but write multiple primitive groups per mesh, keeping material slot information'),
264 ('NONE', 'No export',
265 'Do not export materials, and combine mesh primitive groups, losing material slot information')),
266 description='Export materials ',
267 default='EXPORT'
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 # keep it for compatibility (for now)
299 export_selected: BoolProperty(
300 name='Selected Objects',
301 description='Export selected objects only',
302 default=False
305 use_selection: BoolProperty(
306 name='Selected Objects',
307 description='Export selected objects only',
308 default=False
311 use_visible: BoolProperty(
312 name='Visible Objects',
313 description='Export visible objects only',
314 default=False
317 use_renderable: BoolProperty(
318 name='Renderable Objects',
319 description='Export renderable objects only',
320 default=False
323 use_active_collection: BoolProperty(
324 name='Active Collection',
325 description='Export objects in the active collection only',
326 default=False
329 export_extras: BoolProperty(
330 name='Custom Properties',
331 description='Export custom properties as glTF extras',
332 default=False
335 export_yup: BoolProperty(
336 name='+Y Up',
337 description='Export using glTF convention, +Y up',
338 default=True
341 export_apply: BoolProperty(
342 name='Apply Modifiers',
343 description='Apply modifiers (excluding Armatures) to mesh objects -'
344 'WARNING: prevents exporting shape keys',
345 default=False
348 export_animations: BoolProperty(
349 name='Animations',
350 description='Exports active actions and NLA tracks as glTF animations',
351 default=True
354 export_frame_range: BoolProperty(
355 name='Limit to Playback Range',
356 description='Clips animations to selected playback range',
357 default=True
360 export_frame_step: IntProperty(
361 name='Sampling Rate',
362 description='How often to evaluate animated values (in frames)',
363 default=1,
364 min=1,
365 max=120
368 export_force_sampling: BoolProperty(
369 name='Always Sample Animations',
370 description='Apply sampling to all animations',
371 default=True
374 export_nla_strips: BoolProperty(
375 name='Group by NLA Track',
376 description=(
377 "When on, multiple actions become part of the same glTF animation if "
378 "they're pushed onto NLA tracks with the same name. "
379 "When off, all the currently assigned actions become one glTF animation"
381 default=True
384 export_def_bones: BoolProperty(
385 name='Export Deformation Bones Only',
386 description='Export Deformation bones only (and needed bones for hierarchy)',
387 default=False
390 export_current_frame: BoolProperty(
391 name='Use Current Frame',
392 description='Export the scene in the current animation frame',
393 default=False
396 export_skins: BoolProperty(
397 name='Skinning',
398 description='Export skinning (armature) data',
399 default=True
402 export_all_influences: BoolProperty(
403 name='Include All Bone Influences',
404 description='Allow >4 joint vertex influences. Models may appear incorrectly in many viewers',
405 default=False
408 export_morph: BoolProperty(
409 name='Shape Keys',
410 description='Export shape keys (morph targets)',
411 default=True
414 export_morph_normal: BoolProperty(
415 name='Shape Key Normals',
416 description='Export vertex normals with shape keys (morph targets)',
417 default=True
420 export_morph_tangent: BoolProperty(
421 name='Shape Key Tangents',
422 description='Export vertex tangents with shape keys (morph targets)',
423 default=False
426 export_lights: BoolProperty(
427 name='Punctual Lights',
428 description='Export directional, point, and spot lights. '
429 'Uses "KHR_lights_punctual" glTF extension',
430 default=False
433 export_displacement: BoolProperty(
434 name='Displacement Textures (EXPERIMENTAL)',
435 description='EXPERIMENTAL: Export displacement textures. '
436 'Uses incomplete "KHR_materials_displacement" glTF extension',
437 default=False
440 will_save_settings: BoolProperty(
441 name='Remember Export Settings',
442 description='Store glTF export settings in the Blender project',
443 default=False)
445 # Custom scene property for saving settings
446 scene_key = "glTF2ExportSettings"
450 def check(self, _context):
451 # Ensure file extension matches format
452 old_filepath = self.filepath
453 self.filepath = ensure_filepath_matches_export_format(
454 self.filepath,
455 self.export_format,
457 return self.filepath != old_filepath
459 def invoke(self, context, event):
460 settings = context.scene.get(self.scene_key)
461 self.will_save_settings = False
462 if settings:
463 try:
464 for (k, v) in settings.items():
465 if k == "export_selected": # Back compatibility for export_selected --> use_selection
466 setattr(self, "use_selection", v)
467 del settings[k]
468 settings["use_selection"] = v
469 print("export_selected is now renamed use_selection, and will be deleted in a few release")
470 else:
471 setattr(self, k, v)
472 self.will_save_settings = True
474 except (AttributeError, TypeError):
475 self.report({"ERROR"}, "Loading export settings failed. Removed corrupted settings")
476 del context.scene[self.scene_key]
478 import sys
479 preferences = bpy.context.preferences
480 for addon_name in preferences.addons.keys():
481 try:
482 if hasattr(sys.modules[addon_name], 'glTF2ExportUserExtension') or hasattr(sys.modules[addon_name], 'glTF2ExportUserExtensions'):
483 extension_panel_unregister_functors.append(sys.modules[addon_name].register_panel())
484 except Exception:
485 pass
487 self.has_active_extensions = len(extension_panel_unregister_functors) > 0
488 return ExportHelper.invoke(self, context, event)
490 def save_settings(self, context):
491 # find all props to save
492 exceptional = [
493 # options that don't start with 'export_'
494 'use_selection',
495 'use_visible',
496 'use_renderable',
497 'use_active_collection',
498 'use_mesh_edges',
499 'use_mesh_vertices',
501 all_props = self.properties
502 export_props = {
503 x: getattr(self, x) for x in dir(all_props)
504 if (x.startswith("export_") or x in exceptional) and all_props.get(x) is not None
507 context.scene[self.scene_key] = export_props
509 def execute(self, context):
510 import os
511 import datetime
512 from .blender.exp import gltf2_blender_export
514 if self.will_save_settings:
515 self.save_settings(context)
517 self.check(context) # ensure filepath has the right extension
519 # All custom export settings are stored in this container.
520 export_settings = {}
522 export_settings['timestamp'] = datetime.datetime.now()
524 export_settings['gltf_filepath'] = self.filepath
525 export_settings['gltf_filedirectory'] = os.path.dirname(export_settings['gltf_filepath']) + '/'
526 export_settings['gltf_texturedirectory'] = os.path.join(
527 export_settings['gltf_filedirectory'],
528 self.export_texture_dir,
530 export_settings['gltf_keep_original_textures'] = self.export_keep_originals
532 export_settings['gltf_format'] = self.export_format
533 export_settings['gltf_image_format'] = self.export_image_format
534 export_settings['gltf_copyright'] = self.export_copyright
535 export_settings['gltf_texcoords'] = self.export_texcoords
536 export_settings['gltf_normals'] = self.export_normals
537 export_settings['gltf_tangents'] = self.export_tangents and self.export_normals
538 export_settings['gltf_loose_edges'] = self.use_mesh_edges
539 export_settings['gltf_loose_points'] = self.use_mesh_vertices
541 if self.is_draco_available:
542 export_settings['gltf_draco_mesh_compression'] = self.export_draco_mesh_compression_enable
543 export_settings['gltf_draco_mesh_compression_level'] = self.export_draco_mesh_compression_level
544 export_settings['gltf_draco_position_quantization'] = self.export_draco_position_quantization
545 export_settings['gltf_draco_normal_quantization'] = self.export_draco_normal_quantization
546 export_settings['gltf_draco_texcoord_quantization'] = self.export_draco_texcoord_quantization
547 export_settings['gltf_draco_color_quantization'] = self.export_draco_color_quantization
548 export_settings['gltf_draco_generic_quantization'] = self.export_draco_generic_quantization
549 else:
550 export_settings['gltf_draco_mesh_compression'] = False
552 export_settings['gltf_materials'] = self.export_materials
553 export_settings['gltf_colors'] = self.export_colors
554 export_settings['gltf_cameras'] = self.export_cameras
556 # compatibility after renaming export_selected to use_selection
557 if self.export_selected is True:
558 self.report({"WARNING"}, "export_selected is now renamed use_selection, and will be deleted in a few release")
559 export_settings['gltf_selected'] = self.export_selected
560 else:
561 export_settings['gltf_selected'] = self.use_selection
563 export_settings['gltf_visible'] = self.use_visible
564 export_settings['gltf_renderable'] = self.use_renderable
565 export_settings['gltf_active_collection'] = self.use_active_collection
567 # export_settings['gltf_selected'] = self.use_selection This can be uncomment when removing compatibility of export_selected
568 export_settings['gltf_layers'] = True # self.export_layers
569 export_settings['gltf_extras'] = self.export_extras
570 export_settings['gltf_yup'] = self.export_yup
571 export_settings['gltf_apply'] = self.export_apply
572 export_settings['gltf_current_frame'] = self.export_current_frame
573 export_settings['gltf_animations'] = self.export_animations
574 if self.export_animations:
575 export_settings['gltf_frame_range'] = self.export_frame_range
576 export_settings['gltf_force_sampling'] = self.export_force_sampling
577 if self.export_force_sampling:
578 export_settings['gltf_def_bones'] = self.export_def_bones
579 else:
580 export_settings['gltf_def_bones'] = False
581 export_settings['gltf_nla_strips'] = self.export_nla_strips
582 else:
583 export_settings['gltf_frame_range'] = False
584 export_settings['gltf_move_keyframes'] = False
585 export_settings['gltf_force_sampling'] = False
586 export_settings['gltf_def_bones'] = False
587 export_settings['gltf_skins'] = self.export_skins
588 if self.export_skins:
589 export_settings['gltf_all_vertex_influences'] = self.export_all_influences
590 else:
591 export_settings['gltf_all_vertex_influences'] = False
592 export_settings['gltf_frame_step'] = self.export_frame_step
593 export_settings['gltf_morph'] = self.export_morph
594 if self.export_morph:
595 export_settings['gltf_morph_normal'] = self.export_morph_normal
596 else:
597 export_settings['gltf_morph_normal'] = False
598 if self.export_morph and self.export_morph_normal:
599 export_settings['gltf_morph_tangent'] = self.export_morph_tangent
600 else:
601 export_settings['gltf_morph_tangent'] = False
603 export_settings['gltf_lights'] = self.export_lights
604 export_settings['gltf_displacement'] = self.export_displacement
606 export_settings['gltf_binary'] = bytearray()
607 export_settings['gltf_binaryfilename'] = (
608 os.path.splitext(os.path.basename(self.filepath))[0] + '.bin'
611 user_extensions = []
612 pre_export_callbacks = []
613 post_export_callbacks = []
615 import sys
616 preferences = bpy.context.preferences
617 for addon_name in preferences.addons.keys():
618 try:
619 module = sys.modules[addon_name]
620 except Exception:
621 continue
622 if hasattr(module, 'glTF2ExportUserExtension'):
623 extension_ctor = module.glTF2ExportUserExtension
624 user_extensions.append(extension_ctor())
625 if hasattr(module, 'glTF2ExportUserExtensions'):
626 extension_ctors = module.glTF2ExportUserExtensions
627 for extension_ctor in extension_ctors:
628 user_extensions.append(extension_ctor())
629 if hasattr(module, 'glTF2_pre_export_callback'):
630 pre_export_callbacks.append(module.glTF2_pre_export_callback)
631 if hasattr(module, 'glTF2_post_export_callback'):
632 post_export_callbacks.append(module.glTF2_post_export_callback)
633 export_settings['gltf_user_extensions'] = user_extensions
634 export_settings['pre_export_callbacks'] = pre_export_callbacks
635 export_settings['post_export_callbacks'] = post_export_callbacks
637 return gltf2_blender_export.save(context, export_settings)
639 def draw(self, context):
640 pass # Is needed to get panels available
643 class GLTF_PT_export_main(bpy.types.Panel):
644 bl_space_type = 'FILE_BROWSER'
645 bl_region_type = 'TOOL_PROPS'
646 bl_label = ""
647 bl_parent_id = "FILE_PT_operator"
648 bl_options = {'HIDE_HEADER'}
650 @classmethod
651 def poll(cls, context):
652 sfile = context.space_data
653 operator = sfile.active_operator
655 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
657 def draw(self, context):
658 layout = self.layout
659 layout.use_property_split = True
660 layout.use_property_decorate = False # No animation.
662 sfile = context.space_data
663 operator = sfile.active_operator
665 layout.prop(operator, 'export_format')
666 if operator.export_format == 'GLTF_SEPARATE':
667 layout.prop(operator, 'export_keep_originals')
668 if operator.export_keep_originals is False:
669 layout.prop(operator, 'export_texture_dir', icon='FILE_FOLDER')
671 layout.prop(operator, 'export_copyright')
672 layout.prop(operator, 'will_save_settings')
675 class GLTF_PT_export_include(bpy.types.Panel):
676 bl_space_type = 'FILE_BROWSER'
677 bl_region_type = 'TOOL_PROPS'
678 bl_label = "Include"
679 bl_parent_id = "FILE_PT_operator"
680 bl_options = {'DEFAULT_CLOSED'}
682 @classmethod
683 def poll(cls, context):
684 sfile = context.space_data
685 operator = sfile.active_operator
687 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
689 def draw(self, context):
690 layout = self.layout
691 layout.use_property_split = True
692 layout.use_property_decorate = False # No animation.
694 sfile = context.space_data
695 operator = sfile.active_operator
697 col = layout.column(heading = "Limit to", align = True)
698 col.prop(operator, 'use_selection')
699 col.prop(operator, 'use_visible')
700 col.prop(operator, 'use_renderable')
701 col.prop(operator, 'use_active_collection')
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')
875 row = layout.row()
876 row.active = operator.export_force_sampling
877 row.prop(operator, 'export_def_bones')
880 class GLTF_PT_export_animation_shapekeys(bpy.types.Panel):
881 bl_space_type = 'FILE_BROWSER'
882 bl_region_type = 'TOOL_PROPS'
883 bl_label = "Shape Keys"
884 bl_parent_id = "GLTF_PT_export_animation"
885 bl_options = {'DEFAULT_CLOSED'}
887 @classmethod
888 def poll(cls, context):
889 sfile = context.space_data
890 operator = sfile.active_operator
892 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
894 def draw_header(self, context):
895 sfile = context.space_data
896 operator = sfile.active_operator
897 self.layout.prop(operator, "export_morph", text="")
899 def draw(self, context):
900 layout = self.layout
901 layout.use_property_split = True
902 layout.use_property_decorate = False # No animation.
904 sfile = context.space_data
905 operator = sfile.active_operator
907 layout.active = operator.export_morph
909 layout.prop(operator, 'export_morph_normal')
910 col = layout.column()
911 col.active = operator.export_morph_normal
912 col.prop(operator, 'export_morph_tangent')
915 class GLTF_PT_export_animation_skinning(bpy.types.Panel):
916 bl_space_type = 'FILE_BROWSER'
917 bl_region_type = 'TOOL_PROPS'
918 bl_label = "Skinning"
919 bl_parent_id = "GLTF_PT_export_animation"
920 bl_options = {'DEFAULT_CLOSED'}
922 @classmethod
923 def poll(cls, context):
924 sfile = context.space_data
925 operator = sfile.active_operator
927 return operator.bl_idname == "EXPORT_SCENE_OT_gltf"
929 def draw_header(self, context):
930 sfile = context.space_data
931 operator = sfile.active_operator
932 self.layout.prop(operator, "export_skins", text="")
934 def draw(self, context):
935 layout = self.layout
936 layout.use_property_split = True
937 layout.use_property_decorate = False # No animation.
939 sfile = context.space_data
940 operator = sfile.active_operator
942 layout.active = operator.export_skins
943 layout.prop(operator, 'export_all_influences')
945 class GLTF_PT_export_user_extensions(bpy.types.Panel):
946 bl_space_type = 'FILE_BROWSER'
947 bl_region_type = 'TOOL_PROPS'
948 bl_label = "Extensions"
949 bl_parent_id = "FILE_PT_operator"
950 bl_options = {'DEFAULT_CLOSED'}
952 @classmethod
953 def poll(cls, context):
954 sfile = context.space_data
955 operator = sfile.active_operator
957 return operator.bl_idname == "EXPORT_SCENE_OT_gltf" and operator.has_active_extensions
959 def draw(self, context):
960 layout = self.layout
961 layout.use_property_split = True
962 layout.use_property_decorate = False # No animation.
965 class ExportGLTF2(bpy.types.Operator, ExportGLTF2_Base, ExportHelper):
966 """Export scene as glTF 2.0 file"""
967 bl_idname = 'export_scene.gltf'
968 bl_label = 'Export glTF 2.0'
970 filename_ext = ''
972 filter_glob: StringProperty(default='*.glb;*.gltf', options={'HIDDEN'})
975 def menu_func_export(self, context):
976 self.layout.operator(ExportGLTF2.bl_idname, text='glTF 2.0 (.glb/.gltf)')
979 class ImportGLTF2(Operator, ImportHelper):
980 """Load a glTF 2.0 file"""
981 bl_idname = 'import_scene.gltf'
982 bl_label = 'Import glTF 2.0'
983 bl_options = {'REGISTER', 'UNDO'}
985 filter_glob: StringProperty(default="*.glb;*.gltf", options={'HIDDEN'})
987 files: CollectionProperty(
988 name="File Path",
989 type=bpy.types.OperatorFileListElement,
992 loglevel: IntProperty(
993 name='Log Level',
994 description="Log Level")
996 import_pack_images: BoolProperty(
997 name='Pack Images',
998 description='Pack all images into .blend file',
999 default=True
1002 merge_vertices: BoolProperty(
1003 name='Merge Vertices',
1004 description=(
1005 'The glTF format requires discontinuous normals, UVs, and '
1006 'other vertex attributes to be stored as separate vertices, '
1007 'as required for rendering on typical graphics hardware. '
1008 'This option attempts to combine co-located vertices where possible. '
1009 'Currently cannot combine verts with different normals'
1011 default=False,
1014 import_shading: EnumProperty(
1015 name="Shading",
1016 items=(("NORMALS", "Use Normal Data", ""),
1017 ("FLAT", "Flat Shading", ""),
1018 ("SMOOTH", "Smooth Shading", "")),
1019 description="How normals are computed during import",
1020 default="NORMALS")
1022 bone_heuristic: EnumProperty(
1023 name="Bone Dir",
1024 items=(
1025 ("BLENDER", "Blender (best for re-importing)",
1026 "Good for re-importing glTFs exported from Blender. "
1027 "Bone tips are placed on their local +Y axis (in glTF space)"),
1028 ("TEMPERANCE", "Temperance (average)",
1029 "Decent all-around strategy. "
1030 "A bone with one child has its tip placed on the local axis "
1031 "closest to its child"),
1032 ("FORTUNE", "Fortune (may look better, less accurate)",
1033 "Might look better than Temperance, but also might have errors. "
1034 "A bone with one child has its tip placed at its child's root. "
1035 "Non-uniform scalings may get messed up though, so beware"),
1037 description="Heuristic for placing bones. Tries to make bones pretty",
1038 default="TEMPERANCE",
1041 guess_original_bind_pose: BoolProperty(
1042 name='Guess Original Bind Pose',
1043 description=(
1044 'Try to guess the original bind pose for skinned meshes from '
1045 'the inverse bind matrices. '
1046 'When off, use default/rest pose as bind pose'
1048 default=True,
1051 def draw(self, context):
1052 layout = self.layout
1054 layout.use_property_split = True
1055 layout.use_property_decorate = False # No animation.
1057 layout.prop(self, 'import_pack_images')
1058 layout.prop(self, 'merge_vertices')
1059 layout.prop(self, 'import_shading')
1060 layout.prop(self, 'guess_original_bind_pose')
1061 layout.prop(self, 'bone_heuristic')
1063 def execute(self, context):
1064 return self.import_gltf2(context)
1066 def import_gltf2(self, context):
1067 import os
1069 self.set_debug_log()
1070 import_settings = self.as_keywords()
1072 if self.files:
1073 # Multiple file import
1074 ret = {'CANCELLED'}
1075 dirname = os.path.dirname(self.filepath)
1076 for file in self.files:
1077 path = os.path.join(dirname, file.name)
1078 if self.unit_import(path, import_settings) == {'FINISHED'}:
1079 ret = {'FINISHED'}
1080 return ret
1081 else:
1082 # Single file import
1083 return self.unit_import(self.filepath, import_settings)
1085 def unit_import(self, filename, import_settings):
1086 import time
1087 from .io.imp.gltf2_io_gltf import glTFImporter, ImportError
1088 from .blender.imp.gltf2_blender_gltf import BlenderGlTF
1090 try:
1091 gltf_importer = glTFImporter(filename, import_settings)
1092 gltf_importer.read()
1093 gltf_importer.checks()
1095 print("Data are loaded, start creating Blender stuff")
1097 start_time = time.time()
1098 BlenderGlTF.create(gltf_importer)
1099 elapsed_s = "{:.2f}s".format(time.time() - start_time)
1100 print("glTF import finished in " + elapsed_s)
1102 gltf_importer.log.removeHandler(gltf_importer.log_handler)
1104 return {'FINISHED'}
1106 except ImportError as e:
1107 self.report({'ERROR'}, e.args[0])
1108 return {'CANCELLED'}
1110 def set_debug_log(self):
1111 import logging
1112 if bpy.app.debug_value == 0:
1113 self.loglevel = logging.CRITICAL
1114 elif bpy.app.debug_value == 1:
1115 self.loglevel = logging.ERROR
1116 elif bpy.app.debug_value == 2:
1117 self.loglevel = logging.WARNING
1118 elif bpy.app.debug_value == 3:
1119 self.loglevel = logging.INFO
1120 else:
1121 self.loglevel = logging.NOTSET
1124 def menu_func_import(self, context):
1125 self.layout.operator(ImportGLTF2.bl_idname, text='glTF 2.0 (.glb/.gltf)')
1128 classes = (
1129 ExportGLTF2,
1130 GLTF_PT_export_main,
1131 GLTF_PT_export_include,
1132 GLTF_PT_export_transform,
1133 GLTF_PT_export_geometry,
1134 GLTF_PT_export_geometry_compression,
1135 GLTF_PT_export_animation,
1136 GLTF_PT_export_animation_export,
1137 GLTF_PT_export_animation_shapekeys,
1138 GLTF_PT_export_animation_skinning,
1139 GLTF_PT_export_user_extensions,
1140 ImportGLTF2
1144 def register():
1145 for c in classes:
1146 bpy.utils.register_class(c)
1147 # bpy.utils.register_module(__name__)
1149 # add to the export / import menu
1150 bpy.types.TOPBAR_MT_file_export.append(menu_func_export)
1151 bpy.types.TOPBAR_MT_file_import.append(menu_func_import)
1154 def unregister():
1155 for c in classes:
1156 bpy.utils.unregister_class(c)
1157 for f in extension_panel_unregister_functors:
1159 extension_panel_unregister_functors.clear()
1161 # bpy.utils.unregister_module(__name__)
1163 # remove from the export / import menu
1164 bpy.types.TOPBAR_MT_file_export.remove(menu_func_export)
1165 bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)