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