Blender V4.5
fbx_import.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2025 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
8
9#include "BKE_camera.h"
10#include "BKE_layer.hh"
11#include "BKE_light.h"
12#include "BKE_object.hh"
13#include "BKE_report.hh"
14
15#include "BLI_fileops.h"
16#include "BLI_math_rotation.h"
17#include "BLI_task.hh"
18
19#include "DEG_depsgraph.hh"
21
22#include "DNA_camera_types.h"
24#include "DNA_light_types.h"
25#include "DNA_material_types.h"
26#include "DNA_scene_types.h"
27
28#include "IO_fbx.hh"
29
30#include "fbx_import.hh"
31#include "fbx_import_anim.hh"
34#include "fbx_import_mesh.hh"
35#include "fbx_import_util.hh"
36
37#include "CLG_log.h"
38static CLG_LogRef LOG = {"io.fbx"};
39
41
44 const ufbx_scene &fbx;
46 std::string base_dir;
48
49 FbxImportContext(Main *main, const ufbx_scene *fbx, const FBXImportParams &params)
51 {
52 char basedir[FILE_MAX];
53 BLI_path_split_dir_part(params.filepath, basedir, sizeof(basedir));
54 base_dir = basedir;
55
56 ufbx_transform root_tr;
57 root_tr.translation = ufbx_zero_vec3;
58 root_tr.rotation = this->fbx.metadata.root_rotation;
59 root_tr.scale.x = root_tr.scale.y = root_tr.scale.z = this->fbx.metadata.root_scale;
60 this->mapping.global_conv_matrix = ufbx_transform_to_matrix(&root_tr);
61
62#ifdef FBX_DEBUG_PRINT
63 std::string debug_file_path = params.filepath;
64 debug_file_path = debug_file_path.substr(0, debug_file_path.size() - 4) + "-dbg-b.txt";
65 g_debug_file = BLI_fopen(debug_file_path.c_str(), "wb");
66#endif
67 }
68
70 {
71#ifdef FBX_DEBUG_PRINT
72 if (g_debug_file) {
73 fclose(g_debug_file);
74 }
75#endif
76 }
77
78 void import_globals(Scene *scene) const;
79 void import_materials();
80 void import_meshes();
81 void import_cameras();
82 void import_lights();
83 void import_empties();
84 void import_armatures();
85 void import_animation(double fps);
86
87 void setup_hierarchy();
88};
89
91{
92 /* Set scene frame-rate to that of FBX file. */
93 double fps = this->fbx.settings.frames_per_second;
94 scene->r.frs_sec = roundf(fps);
95 scene->r.frs_sec_base = scene->r.frs_sec / fps;
96}
97
99{
100 for (const ufbx_material *fmat : this->fbx.materials) {
101 Material *mat = io::fbx::import_material(this->bmain, this->base_dir, *fmat);
102 if (this->params.use_custom_props) {
103 read_custom_properties(fmat->props, mat->id, this->params.props_enum_as_string);
104 }
105 this->mapping.mat_to_material.add(fmat, mat);
106 }
107}
108
110{
111 io::fbx::import_meshes(*this->bmain, this->fbx, this->mapping, this->params);
112}
113
114static bool should_import_camera(const ufbx_scene &fbx, const ufbx_camera *camera)
115{
116 BLI_assert(camera->instances.count > 0);
117 const ufbx_node *node = camera->instances[0];
118 /* Files produced by MotionBuilder have several cameras at the root,
119 * which just map to "viewports" and should not get imported. */
120 if (node->node_depth == 1 && node->children.count == 0 &&
121 STREQ("MotionBuilder", fbx.metadata.original_application.name.data))
122 {
123 if (STREQ(node->name.data, camera->name.data)) {
124 if (STREQ("Producer Perspective", node->name.data) ||
125 STREQ("Producer Front", node->name.data) || STREQ("Producer Back", node->name.data) ||
126 STREQ("Producer Right", node->name.data) || STREQ("Producer Left", node->name.data) ||
127 STREQ("Producer Top", node->name.data) || STREQ("Producer Bottom", node->name.data))
128 {
129 return false;
130 }
131 }
132 }
133 return true;
134}
135
137{
138 for (const ufbx_camera *fcam : this->fbx.cameras) {
139 if (fcam->instances.count == 0) {
140 continue; /* Ignore if not used by any objects. */
141 }
142 if (!should_import_camera(this->fbx, fcam)) {
143 continue;
144 }
145 const ufbx_node *node = fcam->instances[0];
146
147 Camera *bcam = BKE_camera_add(this->bmain, get_fbx_name(fcam->name, "Camera"));
148 if (this->params.use_custom_props) {
149 read_custom_properties(fcam->props, bcam->id, this->params.props_enum_as_string);
150 }
151
152 bcam->type = fcam->projection_mode == UFBX_PROJECTION_MODE_ORTHOGRAPHIC ? CAM_ORTHO :
153 CAM_PERSP;
154 bcam->dof.focus_distance = ufbx_find_real(&fcam->props, "FocusDistance", 10.0f) *
155 this->fbx.metadata.geometry_scale * this->fbx.metadata.root_scale;
156 if (ufbx_find_bool(&fcam->props, "UseDepthOfField", false)) {
157 bcam->dof.flag |= CAM_DOF_ENABLED;
158 }
159 bcam->lens = fcam->focal_length_mm;
160 constexpr double m_to_in = 0.0393700787;
161 bcam->sensor_x = fcam->film_size_inch.x / m_to_in;
162 bcam->sensor_y = fcam->film_size_inch.y / m_to_in;
163
164 /* Note: do not use `fcam->orthographic_extent` to match Python importer behavior, which was
165 * not taking ortho units into account. */
166 bcam->ortho_scale = ufbx_find_real(&fcam->props, "OrthoZoom", 1.0);
167
168 bcam->shiftx = ufbx_find_real(&fcam->props, "FilmOffsetX", 0.0) / (m_to_in * bcam->sensor_x);
169 bcam->shifty = ufbx_find_real(&fcam->props, "FilmOffsetY", 0.0) / (m_to_in * bcam->sensor_x);
170 bcam->clip_start = fcam->near_plane * this->fbx.metadata.root_scale;
171 bcam->clip_end = fcam->far_plane * this->fbx.metadata.root_scale;
172
174 obj->data = bcam;
175 if (!node->visible) {
176 obj->visibility_flag |= OB_HIDE_VIEWPORT;
177 }
178 if (this->params.use_custom_props) {
179 read_custom_properties(node->props, obj->id, this->params.props_enum_as_string);
180 }
181 node_matrix_to_obj(node, obj, this->mapping);
182 this->mapping.el_to_object.add(&node->element, obj);
183 this->mapping.imported_objects.add(obj);
184 }
185}
186
188{
189 for (const ufbx_light *flight : this->fbx.lights) {
190 if (flight->instances.count == 0) {
191 continue; /* Ignore if not used by any objects. */
192 }
193 const ufbx_node *node = flight->instances[0];
194
195 Light *lamp = BKE_light_add(this->bmain, get_fbx_name(flight->name, "Light"));
196 if (this->params.use_custom_props) {
197 read_custom_properties(flight->props, lamp->id, this->params.props_enum_as_string);
198 }
199 switch (flight->type) {
200 case UFBX_LIGHT_POINT:
201 lamp->type = LA_LOCAL;
202 break;
203 case UFBX_LIGHT_DIRECTIONAL:
204 lamp->type = LA_SUN;
205 break;
206 case UFBX_LIGHT_SPOT:
207 lamp->type = LA_SPOT;
208 lamp->spotsize = DEG2RAD(flight->outer_angle);
209 lamp->spotblend = 1.0f - flight->inner_angle / flight->outer_angle;
210 break;
211 default:
212 break;
213 }
214
215 lamp->r = flight->color.x;
216 lamp->g = flight->color.y;
217 lamp->b = flight->color.z;
218 lamp->energy = flight->intensity;
219 lamp->exposure = ufbx_find_real(&flight->props, "Exposure", 0.0);
220 if (flight->cast_shadows) {
221 lamp->mode |= LA_SHADOW;
222 }
223 //@TODO: if hasattr(lamp, "cycles"): lamp.cycles.cast_shadow = lamp.use_shadow
224
226 obj->data = lamp;
227 if (!node->visible) {
228 obj->visibility_flag |= OB_HIDE_VIEWPORT;
229 }
230
231 if (this->params.use_custom_props) {
232 read_custom_properties(node->props, obj->id, this->params.props_enum_as_string);
233 }
234 node_matrix_to_obj(node, obj, this->mapping);
235 this->mapping.el_to_object.add(&node->element, obj);
236 this->mapping.imported_objects.add(obj);
237 }
238}
239
241{
242 io::fbx::import_armatures(*this->bmain, this->fbx, this->mapping, this->params);
243}
244
246{
247 /* Create empties for fbx nodes. */
248 for (const ufbx_node *node : this->fbx.nodes) {
249 /* Ignore root, bones and nodes for which we have created objects already. */
250 if (node->is_root || this->mapping.node_is_blender_bone.contains(node) ||
251 this->mapping.el_to_object.contains(&node->element))
252 {
253 continue;
254 }
255 /* Ignore nodes at root for cameras (normally already imported, except for ignored cameras)
256 * and camera switchers. */
257 if (ELEM(node->attrib_type, UFBX_ELEMENT_CAMERA, UFBX_ELEMENT_CAMERA_SWITCHER) &&
258 node->node_depth == 1 && node->children.count == 0)
259 {
260 continue;
261 }
263 obj->data = nullptr;
264 if (!node->visible) {
265 obj->visibility_flag |= OB_HIDE_VIEWPORT;
266 }
267 if (this->params.use_custom_props) {
268 read_custom_properties(node->props, obj->id, this->params.props_enum_as_string);
269 }
270 node_matrix_to_obj(node, obj, this->mapping);
271 this->mapping.el_to_object.add(&node->element, obj);
272 this->mapping.imported_objects.add(obj);
273 }
274}
275
277{
278 if (this->params.use_anim) {
280 *this->bmain, this->fbx, this->mapping, fps, this->params.anim_offset);
281 }
282}
283
285{
286 for (const auto &item : this->mapping.el_to_object.items()) {
287 if (item.value->parent != nullptr) {
288 continue; /* Parent is already set up (e.g. armature). */
289 }
290 const ufbx_node *node = ufbx_as_node(item.key);
291 if (node == nullptr) {
292 continue;
293 }
294 if (node->parent) {
295 Object *obj_par = this->mapping.el_to_object.lookup_default(&node->parent->element, nullptr);
296 if (!ELEM(obj_par, nullptr, item.value)) {
297 item.value->parent = obj_par;
298 }
299 }
300 }
301}
302
303static void fbx_task_run_fn(void * /* user */,
304 ufbx_thread_pool_context ctx,
305 uint32_t /* group */,
306 uint32_t start_index,
307 uint32_t count)
308{
309 threading::parallel_for_each(IndexRange(start_index, count), [&](const int64_t index) {
310 ufbx_thread_pool_run_task(ctx, index);
311 });
312}
313
314static void fbx_task_wait_fn(void * /* user */,
315 ufbx_thread_pool_context /* ctx */,
316 uint32_t /* group */,
317 uint32_t /* max_index */)
318{
319 /* Empty implementation; #fbx_task_run_fn already waits for the tasks.
320 * This means that only one fbx "task group" is effectively scheduled at once. */
321}
322
323void importer_main(Main *bmain, Scene *scene, ViewLayer *view_layer, const FBXImportParams &params)
324{
325 FILE *file = BLI_fopen(params.filepath, "rb");
326 if (!file) {
327 CLOG_ERROR(&LOG, "Failed to open FBX file '%s'", params.filepath);
328 BKE_reportf(params.reports, RPT_ERROR, "FBX Import: Cannot open file '%s'", params.filepath);
329 return;
330 }
331
332 ufbx_load_opts opts = {};
333 opts.filename.data = params.filepath;
334 opts.filename.length = strlen(params.filepath);
335 opts.evaluate_skinning = false;
336 opts.evaluate_caches = false;
337 opts.load_external_files = false;
338 opts.clean_skin_weights = true;
339 opts.use_blender_pbr_material = true;
340
341 opts.geometry_transform_handling = UFBX_GEOMETRY_TRANSFORM_HANDLING_MODIFY_GEOMETRY;
342 opts.pivot_handling = UFBX_PIVOT_HANDLING_ADJUST_TO_ROTATION_PIVOT;
343
344 opts.space_conversion = UFBX_SPACE_CONVERSION_ADJUST_TRANSFORMS;
345 opts.target_axes.right = UFBX_COORDINATE_AXIS_POSITIVE_X;
346 opts.target_axes.up = UFBX_COORDINATE_AXIS_POSITIVE_Z;
347 opts.target_axes.front = UFBX_COORDINATE_AXIS_NEGATIVE_Y;
348 opts.target_unit_meters = 1.0f / params.global_scale;
349
350 opts.target_camera_axes.right = UFBX_COORDINATE_AXIS_POSITIVE_X;
351 opts.target_camera_axes.up = UFBX_COORDINATE_AXIS_POSITIVE_Y;
352 opts.target_camera_axes.front = UFBX_COORDINATE_AXIS_POSITIVE_Z;
353 opts.target_light_axes.right = UFBX_COORDINATE_AXIS_POSITIVE_X;
354 opts.target_light_axes.up = UFBX_COORDINATE_AXIS_POSITIVE_Y;
355 opts.target_light_axes.front = UFBX_COORDINATE_AXIS_POSITIVE_Z;
356
357 /* Setup ufbx threading to go through our own task system. */
358 opts.thread_opts.pool.run_fn = fbx_task_run_fn;
359 opts.thread_opts.pool.wait_fn = fbx_task_wait_fn;
360
361 ufbx_error fbx_error;
362 ufbx_scene *fbx = ufbx_load_stdio(file, &opts, &fbx_error);
363 fclose(file);
364
365 if (!fbx) {
367 "Failed to import FBX file '%s': '%s'\n",
368 params.filepath,
369 fbx_error.description.data);
370 BKE_reportf(params.reports,
371 RPT_ERROR,
372 "FBX Import: Cannot import file '%s': '%s'",
373 params.filepath,
374 fbx_error.description.data);
375 return;
376 }
377
379 //@TODO: do we need to sort objects by name? (faster to create within blender)
380
381 FbxImportContext ctx(bmain, fbx, params);
382 ctx.import_globals(scene);
383
384#ifdef FBX_DEBUG_PRINT
385 {
386 fprintf(g_debug_file, "Initial NODE local matrices:\n");
388 for (const ufbx_node *node : ctx.fbx.nodes) {
389 if (node->is_root) {
390 continue;
391 }
392 nodes.append(node);
393 }
394 std::sort(nodes.begin(), nodes.end(), [](const ufbx_node *a, const ufbx_node *b) {
395 int ncmp = strcmp(a->name.data, b->name.data);
396 if (ncmp != 0) {
397 return ncmp < 0;
398 }
399 return a->attrib_type > b->attrib_type;
400 });
401 for (const ufbx_node *node : nodes) {
402 ufbx_matrix mtx = ufbx_matrix_mul(node->node_depth < 2 ? &node->node_to_world :
403 &node->node_to_parent,
404 &node->geometry_to_node);
405 fprintf(g_debug_file, "init NODE %s self.matrix:\n", node->name.data);
406 print_matrix(mtx);
407 }
408 fprintf(g_debug_file, "\n");
409 }
410#endif
411
412 ctx.import_materials();
413 ctx.import_armatures();
414 ctx.import_meshes();
415 ctx.import_cameras();
416 ctx.import_lights();
417 ctx.import_empties();
418 ctx.import_animation(FPS);
419 ctx.setup_hierarchy();
420
421 ufbx_free_scene(fbx);
422
423 /* Add objects to collection. */
424 for (Object *obj : ctx.mapping.imported_objects) {
425 BKE_collection_object_add(bmain, lc->collection, obj);
426 }
427
428 /* Select objects, sync layers etc. */
429 BKE_view_layer_base_deselect_all(scene, view_layer);
430 BKE_view_layer_synced_ensure(scene, view_layer);
431 for (Object *obj : ctx.mapping.imported_objects) {
432 Base *base = BKE_view_layer_base_find(view_layer, obj);
434
437 DEG_id_tag_update_ex(bmain, &obj->id, flags);
438 }
439 DEG_id_tag_update(&lc->collection->id, ID_RECALC_SYNC_TO_EVAL);
440
443}
444
445} // namespace blender::io::fbx
Camera data-block and utility functions.
struct Camera * BKE_camera_add(struct Main *bmain, const char *name)
bool BKE_collection_object_add(Main *bmain, Collection *collection, Object *ob)
LayerCollection * BKE_layer_collection_get_active(ViewLayer *view_layer)
void BKE_view_layer_synced_ensure(const Scene *scene, ViewLayer *view_layer)
void BKE_view_layer_base_deselect_all(const Scene *scene, ViewLayer *view_layer)
Base * BKE_view_layer_base_find(ViewLayer *view_layer, Object *ob)
void BKE_view_layer_base_select_and_set_active(ViewLayer *view_layer, Base *selbase)
General operations, lookup, etc. for blender lights.
Light * BKE_light_add(Main *bmain, const char *name) ATTR_WARN_UNUSED_RESULT
General operations, lookup, etc. for blender objects.
Object * BKE_object_add_only_object(Main *bmain, int type, const char *name) ATTR_RETURNS_NONNULL
void BKE_reportf(ReportList *reports, eReportType type, const char *format,...) ATTR_PRINTF_FORMAT(3
#define BLI_assert(a)
Definition BLI_assert.h:46
File and directory operations.
FILE * BLI_fopen(const char *filepath, const char *mode) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
#define DEG2RAD(_deg)
#define FILE_MAX
void void BLI_path_split_dir_part(const char *filepath, char *dir, size_t dir_maxncpy) ATTR_NONNULL(1
#define ELEM(...)
#define STREQ(a, b)
#define CLOG_ERROR(clg_ref,...)
Definition CLG_log.h:182
void DEG_id_tag_update(ID *id, unsigned int flags)
void DEG_relations_tag_update(Main *bmain)
@ ID_RECALC_TRANSFORM
Definition DNA_ID.h:962
@ ID_RECALC_SYNC_TO_EVAL
Definition DNA_ID.h:1026
@ ID_RECALC_ANIMATION
Definition DNA_ID.h:985
@ ID_RECALC_GEOMETRY
Definition DNA_ID.h:982
@ ID_RECALC_BASE_FLAGS
Definition DNA_ID.h:1012
@ CAM_DOF_ENABLED
@ CAM_PERSP
@ CAM_ORTHO
Object groups, one object can be in many groups at once.
@ LA_SHADOW
@ LA_LOCAL
@ LA_SPOT
@ LA_SUN
@ OB_HIDE_VIEWPORT
@ OB_EMPTY
@ OB_CAMERA
@ OB_LAMP
#define FPS
long long int int64_t
#define this
#define main()
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
int count
DEG_id_tag_update_ex(cb_data->bmain, cb_data->owner_id, ID_RECALC_TAG_FOR_UNDO|ID_RECALC_SYNC_TO_EVAL)
#define LOG(severity)
Definition log.h:32
static void fbx_task_run_fn(void *, ufbx_thread_pool_context ctx, uint32_t, uint32_t start_index, uint32_t count)
const char * get_fbx_name(const ufbx_string &name, const char *def)
static bool should_import_camera(const ufbx_scene &fbx, const ufbx_camera *camera)
void read_custom_properties(const ufbx_props &props, ID &id, bool enums_as_strings)
void node_matrix_to_obj(const ufbx_node *node, Object *obj, const FbxElementMapping &mapping)
Material * import_material(Main *bmain, const std::string &base_dir, const ufbx_material &fmat)
void importer_main(Main *bmain, Scene *scene, ViewLayer *view_layer, const FBXImportParams &params)
void import_animations(Main &bmain, const ufbx_scene &fbx, const FbxElementMapping &mapping, const double fps, const float anim_offset)
static void fbx_task_wait_fn(void *, ufbx_thread_pool_context, uint32_t, uint32_t)
void import_armatures(Main &bmain, const ufbx_scene &fbx, FbxElementMapping &mapping, const FBXImportParams &params)
void import_meshes(Main &bmain, const ufbx_scene &fbx, FbxElementMapping &mapping, const FBXImportParams &params)
void parallel_for_each(Range &&range, const Function &function)
Definition BLI_task.hh:56
float clip_end
float sensor_y
float sensor_x
float clip_start
struct CameraDOFSettings dof
float ortho_scale
float energy
float spotblend
float spotsize
float exposure
short type
struct RenderData r
FbxImportContext(Main *main, const ufbx_scene *fbx, const FBXImportParams &params)
Definition fbx_import.cc:49
void import_globals(Scene *scene) const
Definition fbx_import.cc:90
const FBXImportParams & params
Definition fbx_import.cc:45