Blender V5.0
fbx_import_util.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_idprop.hh"
10#include "BKE_object.hh"
11#include "BKE_object_types.hh"
12
13#include "BLI_string.h"
14#include "BLI_string_utf8.h"
15
16#include "DNA_object_types.h"
17
18#include "fbx_import_util.hh"
19
20namespace blender::io::fbx {
21
22const char *get_fbx_name(const ufbx_string &name, const char *def)
23{
24 return name.length > 0 ? name.data : def;
25}
26
27void matrix_to_m44(const ufbx_matrix &src, float dst[4][4])
28{
29 dst[0][0] = src.m00;
30 dst[1][0] = src.m01;
31 dst[2][0] = src.m02;
32 dst[3][0] = src.m03;
33 dst[0][1] = src.m10;
34 dst[1][1] = src.m11;
35 dst[2][1] = src.m12;
36 dst[3][1] = src.m13;
37 dst[0][2] = src.m20;
38 dst[1][2] = src.m21;
39 dst[2][2] = src.m22;
40 dst[3][2] = src.m23;
41 dst[0][3] = 0.0f;
42 dst[1][3] = 0.0f;
43 dst[2][3] = 0.0f;
44 dst[3][3] = 1.0f;
45}
46
47ufbx_matrix calc_bone_pose_matrix(const ufbx_transform &local_xform,
48 const ufbx_node &node,
49 const ufbx_matrix &local_bind_inv_matrix)
50{
51 ufbx_transform xform = local_xform;
52
53 /* For bones that have "ignore parent scale" on them, ufbx helpfully applies global scale to
54 * the evaluated transform. However we really need to get local transform without global
55 * scale, so undo that. */
56 if (node.adjust_post_scale != 1.0) {
57 xform.scale.x /= node.adjust_post_scale;
58 xform.scale.y /= node.adjust_post_scale;
59 xform.scale.z /= node.adjust_post_scale;
60 }
61
62 /* Transformed to the bind transform in joint-local space. */
63 ufbx_matrix matrix = ufbx_transform_to_matrix(&xform);
64 matrix = ufbx_matrix_mul(&local_bind_inv_matrix, &matrix);
65 return matrix;
66}
67
68void ufbx_matrix_to_obj(const ufbx_matrix &mtx, Object *obj)
69{
70#ifdef FBX_DEBUG_PRINT
71 fprintf(g_debug_file, "init NODE %s self.matrix:\n", obj->id.name + 2);
72 print_matrix(mtx);
73#endif
74
75 float obmat[4][4];
76 matrix_to_m44(mtx, obmat);
77 BKE_object_apply_mat4(obj, obmat, true, false);
78 BKE_object_to_mat4(obj, obj->runtime->object_to_world.ptr());
79}
80
81void node_matrix_to_obj(const ufbx_node *node, Object *obj, const FbxElementMapping &mapping)
82{
83 ufbx_matrix mtx = ufbx_matrix_mul(node->node_depth < 2 ? &node->node_to_world :
84 &node->node_to_parent,
85 &node->geometry_to_node);
86
87 /* Handle case of an object parented to a bone: need to set
88 * bone as parent, and make transform be at the end of the bone. */
89 const ufbx_node *parbone = node->parent;
90 if (obj->parent == nullptr && parbone && mapping.node_is_blender_bone.contains(parbone)) {
91 Object *arm = mapping.bone_to_armature.lookup_default(parbone, nullptr);
92 if (arm != nullptr) {
93 ufbx_matrix offset_mtx = ufbx_identity_matrix;
94 offset_mtx.cols[3].y = -mapping.bone_to_length.lookup_default(parbone, 0.0);
95 if (mapping.node_is_blender_bone.contains(node)) {
96 /* The node itself is a "fake bone", in which case parent it to the matching
97 * fake bone, and matrix is just what puts transform at the bone tail. */
98 parbone = node;
99 mtx = offset_mtx;
100 }
101 else {
102 mtx = ufbx_matrix_mul(&offset_mtx, &mtx);
103 }
104
105 obj->parent = arm;
106 obj->partype = PARBONE;
107 STRNCPY_UTF8(obj->parsubstr, mapping.node_to_name.lookup_default(parbone, "").c_str());
108
109#ifdef FBX_DEBUG_PRINT
110 fprintf(g_debug_file,
111 "parent CHILD %s to ARM %s BONE %s bone_child_mtx:\n",
112 node->name.data,
113 arm->id.name + 2,
114 parbone->name.data);
115 print_matrix(offset_mtx);
116 fprintf(g_debug_file, "- child matrix:\n");
117 print_matrix(mtx);
118#endif
119 }
120 }
121
123}
124
125static void read_ufbx_property(const ufbx_prop &prop, IDProperty *idgroup, bool enums_as_strings)
126{
127 IDProperty *idprop = nullptr;
128 IDPropertyTemplate val = {0};
129 //@TODO: validate_blend_names on the property name
130 const char *name = prop.name.data;
131
132 switch (prop.type) {
133 case UFBX_PROP_BOOLEAN:
134 val.i = prop.value_int;
135 idprop = IDP_New(IDP_BOOLEAN, &val, name);
136 break;
137 case UFBX_PROP_INTEGER: {
138 bool parsed_as_enum = false;
139 if (enums_as_strings && (prop.flags & UFBX_PROP_FLAG_VALUE_STR) &&
140 (prop.value_str.length > 0))
141 {
142 /* "Enum" property with integer value, and enum names as `~` separated string. */
143 const char *tilde = prop.value_str.data;
144 int enum_index = -1;
145 while (true) {
146 const char *tilde_start = tilde;
147 tilde = BLI_strchr_or_end(tilde_start, '~');
148 if (tilde == tilde_start) {
149 break;
150 }
151 /* We have an enum value string. */
152 enum_index++;
153 if (enum_index == prop.value_int) {
154 /* Found the needed one. */
155 parsed_as_enum = true;
156 std::string str_val = StringRef(tilde_start, tilde).trim();
157 val.string.str = str_val.c_str();
158 val.string.len = str_val.size() + 1; /* .len needs to include null terminator. */
160 idprop = IDP_New(IDP_STRING, &val, name);
161 break;
162 }
163 if (tilde[0] == 0) {
164 break;
165 }
166 tilde++;
167 }
168 }
169
170 if (!parsed_as_enum) {
171 val.i = prop.value_int;
172 idprop = IDP_New(IDP_INT, &val, name);
173 }
174
175 } break;
176 case UFBX_PROP_NUMBER:
177 val.d = prop.value_real;
178 idprop = IDP_New(IDP_DOUBLE, &val, name);
179 break;
180 case UFBX_PROP_STRING:
181 if (STREQ(name, "UDP3DSMAX")) {
182 /* 3dsmax user properties are coming as `UDP3DSMAX` property. Parse them
183 * as multi-line text, splitting across `=` within each line. */
184 const char *line = prop.value_str.data;
185 while (true) {
186 const char *line_start = line;
187 line = BLI_strchr_or_end(line_start, '\n');
188 if (line == line_start) {
189 break;
190 }
191
192 /* We have a line, split it by '=' and trim name/value. */
193 const char *eq_pos = line_start;
194 while (eq_pos != line && eq_pos[0] != '=') {
195 eq_pos++;
196 }
197 if (eq_pos[0] == '=') {
198 std::string str_name = StringRef(line_start, eq_pos).trim();
199 std::string str_val = StringRef(eq_pos + 1, line).trim();
200 //@TODO validate_blend_names on str_name
201 val.string.str = str_val.c_str();
202 val.string.len = str_val.size() + 1; /* .len needs to include null terminator. */
204 IDProperty *str_prop = IDP_New(IDP_STRING, &val, str_name.c_str());
205 IDP_AddToGroup(idgroup, str_prop);
206 }
207
208 if (line[0] == 0) {
209 break;
210 }
211 line++;
212 }
213 }
214 else {
215 val.string.str = prop.value_str.data;
216 val.string.len = prop.value_str.length + 1; /* .len needs to include null terminator. */
218 idprop = IDP_New(IDP_STRING, &val, name);
219 }
220 break;
221 case UFBX_PROP_VECTOR:
222 case UFBX_PROP_COLOR:
223 val.array.len = 3;
224 val.array.type = IDP_DOUBLE;
225 idprop = IDP_New(IDP_ARRAY, &val, name);
226 {
227 double *dst = static_cast<double *>(idprop->data.pointer);
228 dst[0] = prop.value_vec3.x;
229 dst[1] = prop.value_vec3.y;
230 dst[2] = prop.value_vec3.z;
231 }
232 break;
233 case UFBX_PROP_COLOR_WITH_ALPHA:
234 val.array.len = 4;
235 val.array.type = IDP_DOUBLE;
236 idprop = IDP_New(IDP_ARRAY, &val, name);
237 {
238 double *dst = static_cast<double *>(idprop->data.pointer);
239 dst[0] = prop.value_vec4.x;
240 dst[1] = prop.value_vec4.y;
241 dst[2] = prop.value_vec4.z;
242 dst[3] = prop.value_vec4.z;
243 }
244 break;
245 default:
246 break;
247 }
248
249 if (idprop != nullptr) {
250 IDP_AddToGroup(idgroup, idprop);
251 }
252}
253
254void read_custom_properties(const ufbx_props &props, ID &id, bool enums_as_strings)
255{
256 for (const ufbx_prop &prop : props.props) {
257 if ((prop.flags & UFBX_PROP_FLAG_USER_DEFINED) == 0) {
258 continue;
259 }
260 IDProperty *idgroup = IDP_EnsureProperties(&id);
261 read_ufbx_property(prop, idgroup, enums_as_strings);
262 }
263}
264
266{
267 if (pchan.prop == nullptr) {
268 pchan.prop = bke::idprop::create_group("").release();
269 }
270 return pchan.prop;
271}
272
273void read_custom_properties(const ufbx_props &props, bPoseChannel &pchan, bool enums_as_strings)
274{
275 for (const ufbx_prop &prop : props.props) {
276 if ((prop.flags & UFBX_PROP_FLAG_USER_DEFINED) == 0) {
277 continue;
278 }
279 IDProperty *idgroup = pchan_EnsureProperties(pchan);
280 read_ufbx_property(prop, idgroup, enums_as_strings);
281 }
282}
283
284#ifdef FBX_DEBUG_PRINT
285FILE *g_debug_file;
286void print_matrix(const ufbx_matrix &m)
287{
288 fprintf(g_debug_file,
289 " (%.3f %.3f %.3f %.3f)\n",
290 adjf(m.cols[0].x),
291 adjf(m.cols[1].x),
292 adjf(m.cols[2].x),
293 adjf(m.cols[3].x));
294 fprintf(g_debug_file,
295 " (%.3f %.3f %.3f %.3f)\n",
296 adjf(m.cols[0].y),
297 adjf(m.cols[1].y),
298 adjf(m.cols[2].y),
299 adjf(m.cols[3].y));
300 fprintf(g_debug_file,
301 " (%.3f %.3f %.3f %.3f)\n",
302 adjf(m.cols[0].z),
303 adjf(m.cols[1].z),
304 adjf(m.cols[2].z),
305 adjf(m.cols[3].z));
306}
307#endif
308
309} // namespace blender::io::fbx
IDProperty * IDP_New(char type, const IDPropertyTemplate *val, blender::StringRef name, eIDPropertyFlag flags={}) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition idprop.cc:1009
bool IDP_AddToGroup(IDProperty *group, IDProperty *prop) ATTR_NONNULL()
Definition idprop.cc:717
IDProperty * IDP_EnsureProperties(ID *id) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition idprop.cc:882
General operations, lookup, etc. for blender objects.
void BKE_object_apply_mat4(Object *ob, const float mat[4][4], bool use_compat, bool use_parent)
void BKE_object_to_mat4(const Object *ob, float r_mat[4][4])
char char size_t char const char * BLI_strchr_or_end(const char *str, char ch) ATTR_WARN_UNUSED_RESULT ATTR_RETURNS_NONNULL ATTR_NONNULL(1)
Definition string.cc:931
#define STRNCPY_UTF8(dst, src)
#define STREQ(a, b)
@ IDP_STRING_SUB_UTF8
@ IDP_DOUBLE
@ IDP_STRING
@ IDP_BOOLEAN
@ IDP_INT
@ IDP_ARRAY
Object is a sort of wrapper for general info.
@ PARBONE
Value lookup_default(const Key &key, const Value &default_value) const
Definition BLI_map.hh:570
bool contains(const Key &key) const
Definition BLI_set.hh:310
constexpr StringRef trim() const
std::unique_ptr< IDProperty, IDPropertyDeleter > create_group(StringRef prop_name, eIDPropertyFlag flags={})
Allocate a new IDProperty of type IDP_GROUP.
void ufbx_matrix_to_obj(const ufbx_matrix &mtx, Object *obj)
const char * get_fbx_name(const ufbx_string &name, const char *def)
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)
void matrix_to_m44(const ufbx_matrix &src, float dst[4][4])
static IDProperty * pchan_EnsureProperties(bPoseChannel &pchan)
ufbx_matrix calc_bone_pose_matrix(const ufbx_transform &local_xform, const ufbx_node &node, const ufbx_matrix &local_bind_inv_matrix)
static void read_ufbx_property(const ufbx_prop &prop, IDProperty *idgroup, bool enums_as_strings)
const char * name
void * pointer
Definition DNA_ID.h:142
IDPropertyData data
Definition DNA_ID.h:169
Definition DNA_ID.h:414
char name[258]
Definition DNA_ID.h:432
IDProperty * prop
Map< const ufbx_node *, ufbx_real > bone_to_length
Map< const ufbx_node *, Object * > bone_to_armature
Map< const ufbx_node *, std::string > node_to_name
Set< const ufbx_node * > node_is_blender_bone
struct IDPropertyTemplate::@032057005265002020267344110225167212360002125060 array
const char * str
Definition BKE_idprop.hh:39
struct IDPropertyTemplate::@306303166102371126056157213146124155011254157272 string