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