glTF exporter: add postprocessing using gltfpack
[blender-addons.git] / io_scene_gltf2 / blender / exp / gltf2_blender_export.py
blobdbfc0ae26331bbce82e753e65e23119bd2d86bd7
1 # SPDX-FileCopyrightText: 2018-2021 The glTF-Blender-IO authors
3 # SPDX-License-Identifier: Apache-2.0
5 import os
6 import subprocess
7 import time
9 import bpy
10 import sys
11 import traceback
13 from ...io.com.gltf2_io_debug import print_console, print_newline
14 from ...io.exp import gltf2_io_export
15 from ...io.exp import gltf2_io_draco_compression_extension
16 from ...io.exp.gltf2_io_user_extensions import export_user_extensions
17 from ..com import gltf2_blender_json
18 from . import gltf2_blender_gather
19 from .gltf2_blender_gltf2_exporter import GlTF2Exporter
20 from .gltf2_blender_gltf2_exporter import fix_json
23 def save(context, export_settings):
24 """Start the glTF 2.0 export and saves to content either to a .gltf or .glb file."""
25 if bpy.context.active_object is not None:
26 if bpy.context.active_object.mode != "OBJECT": # For linked object, you can't force OBJECT mode
27 bpy.ops.object.mode_set(mode='OBJECT')
29 original_frame = bpy.context.scene.frame_current
30 if not export_settings['gltf_current_frame']:
31 bpy.context.scene.frame_set(0)
33 __notify_start(context)
34 start_time = time.time()
35 pre_export_callbacks = export_settings["pre_export_callbacks"]
36 for callback in pre_export_callbacks:
37 callback(export_settings)
39 json, buffer = __export(export_settings)
41 post_export_callbacks = export_settings["post_export_callbacks"]
42 for callback in post_export_callbacks:
43 callback(export_settings)
44 __write_file(json, buffer, export_settings)
46 end_time = time.time()
47 __notify_end(context, end_time - start_time)
49 if not export_settings['gltf_current_frame']:
50 bpy.context.scene.frame_set(int(original_frame))
51 return {'FINISHED'}
54 def __export(export_settings):
55 exporter = GlTF2Exporter(export_settings)
56 __gather_gltf(exporter, export_settings)
57 buffer = __create_buffer(exporter, export_settings)
58 exporter.finalize_images()
60 export_user_extensions('gather_gltf_extensions_hook', export_settings, exporter.glTF)
61 exporter.traverse_extensions()
63 # now that addons possibly add some fields in json, we can fix in needed
64 json = fix_json(exporter.glTF.to_dict())
66 # Convert additional data if needed
67 if export_settings['gltf_unused_textures'] is True:
68 additional_json_textures = fix_json([i.to_dict() for i in exporter.additional_data.additional_textures])
70 # Now that we have the final json, we can add the additional data
71 # We can not do that for all people, because we don't want this extra to become "a standard"
72 # So let's use the "extras" field filled by a user extension
74 export_user_extensions('gather_gltf_additional_textures_hook', export_settings, json, additional_json_textures)
76 # if len(additional_json_textures) > 0:
77 # if json.get('extras') is None:
78 # json['extras'] = {}
79 # json['extras']['additionalTextures'] = additional_json_textures
81 return json, buffer
84 def __gather_gltf(exporter, export_settings):
85 active_scene_idx, scenes, animations = gltf2_blender_gather.gather_gltf2(export_settings)
87 unused_skins = export_settings['vtree'].get_unused_skins()
89 if export_settings['gltf_draco_mesh_compression']:
90 gltf2_io_draco_compression_extension.encode_scene_primitives(scenes, export_settings)
91 exporter.add_draco_extension()
93 export_user_extensions('gather_gltf_hook', export_settings, active_scene_idx, scenes, animations)
95 for idx, scene in enumerate(scenes):
96 exporter.add_scene(scene, idx==active_scene_idx, export_settings=export_settings)
97 for animation in animations:
98 exporter.add_animation(animation)
99 exporter.traverse_unused_skins(unused_skins)
100 exporter.traverse_additional_textures()
101 exporter.traverse_additional_images()
104 def __create_buffer(exporter, export_settings):
105 buffer = bytes()
106 if export_settings['gltf_format'] == 'GLB':
107 buffer = exporter.finalize_buffer(export_settings['gltf_filedirectory'], is_glb=True)
108 else:
109 exporter.finalize_buffer(export_settings['gltf_filedirectory'],
110 export_settings['gltf_binaryfilename'])
112 return buffer
114 def __postprocess_with_gltfpack(export_settings):
116 gltfpack_binary_file_path = bpy.context.preferences.addons['io_scene_gltf2'].preferences.gltfpack_path_ui
118 gltf_file_path = export_settings['gltf_filepath']
119 gltf_file_base = os.path.splitext(os.path.basename(gltf_file_path))[0]
120 gltf_file_extension = os.path.splitext(os.path.basename(gltf_file_path))[1]
121 gltf_file_directory = os.path.dirname(gltf_file_path)
122 gltf_output_file_directory = os.path.join(gltf_file_directory, "gltfpacked")
123 if (os.path.exists(gltf_output_file_directory) is False):
124 os.makedirs(gltf_output_file_directory)
126 gltf_input_file_path = gltf_file_path
127 gltf_output_file_path = os.path.join(gltf_output_file_directory, gltf_file_base + gltf_file_extension)
129 options = []
131 if (export_settings['gltf_gltfpack_tc']):
132 options.append("-tc")
134 if (export_settings['gltf_gltfpack_tq']):
135 options.append("-tq")
136 options.append(f"{export_settings['gltf_gltfpack_tq']}")
138 if (export_settings['gltf_gltfpack_si'] != 1.0):
139 options.append("-si")
140 options.append(f"{export_settings['gltf_gltfpack_si']}")
142 if (export_settings['gltf_gltfpack_sa']):
143 options.append("-sa")
145 if (export_settings['gltf_gltfpack_slb']):
146 options.append("-slb")
148 if (export_settings['gltf_gltfpack_noq']):
149 options.append("-noq")
150 else:
151 options.append("-vp")
152 options.append(f"{export_settings['gltf_gltfpack_vp']}")
153 options.append("-vt")
154 options.append(f"{export_settings['gltf_gltfpack_vt']}")
155 options.append("-vn")
156 options.append(f"{export_settings['gltf_gltfpack_vn']}")
157 options.append("-vc")
158 options.append(f"{export_settings['gltf_gltfpack_vc']}")
160 match export_settings['gltf_gltfpack_vpi']:
161 case "Integer":
162 options.append("-vpi")
163 case "Normalized":
164 options.append("-vpn")
165 case "Floating-point":
166 options.append("-vpf")
168 parameters = []
169 parameters.append("-i")
170 parameters.append(gltf_input_file_path)
171 parameters.append("-o")
172 parameters.append(gltf_output_file_path)
174 try:
175 subprocess.run([gltfpack_binary_file_path] + options + parameters, check=True)
176 except subprocess.CalledProcessError as e:
177 print_console('ERROR', "Calling gltfpack was not successful")
179 def __write_file(json, buffer, export_settings):
180 try:
181 gltf2_io_export.save_gltf(
182 json,
183 export_settings,
184 gltf2_blender_json.BlenderJSONEncoder,
185 buffer)
186 if (export_settings['gltf_use_gltfpack'] == True):
187 __postprocess_with_gltfpack(export_settings)
189 except AssertionError as e:
190 _, _, tb = sys.exc_info()
191 traceback.print_tb(tb) # Fixed format
192 tb_info = traceback.extract_tb(tb)
193 for tbi in tb_info:
194 filename, line, func, text = tbi
195 print_console('ERROR', 'An error occurred on line {} in statement {}'.format(line, text))
196 print_console('ERROR', str(e))
197 raise e
200 def __notify_start(context):
201 print_console('INFO', 'Starting glTF 2.0 export')
202 context.window_manager.progress_begin(0, 100)
203 context.window_manager.progress_update(0)
206 def __notify_end(context, elapsed):
207 print_console('INFO', 'Finished glTF 2.0 export in {} s'.format(elapsed))
208 context.window_manager.progress_end()
209 print_newline()