Blender  V3.3
usd_writer_mesh.cc
Go to the documentation of this file.
1 /* SPDX-License-Identifier: GPL-2.0-or-later
2  * Copyright 2019 Blender Foundation. All rights reserved. */
3 #include "usd_writer_mesh.h"
5 
6 #include <pxr/usd/usdGeom/mesh.h>
7 #include <pxr/usd/usdShade/material.h>
8 #include <pxr/usd/usdShade/materialBindingAPI.h>
9 
10 #include "BLI_assert.h"
11 #include "BLI_math_vector.h"
12 
13 #include "BKE_attribute.h"
14 #include "BKE_customdata.h"
15 #include "BKE_lib_id.h"
16 #include "BKE_material.h"
17 #include "BKE_mesh.h"
18 #include "BKE_modifier.h"
19 #include "BKE_object.h"
20 
21 #include "DEG_depsgraph.h"
22 
23 #include "DNA_layer_types.h"
24 #include "DNA_mesh_types.h"
25 #include "DNA_meshdata_types.h"
26 #include "DNA_modifier_types.h"
28 #include "DNA_particle_types.h"
29 
30 #include <iostream>
31 
32 namespace blender::io::usd {
33 
35 {
36 }
37 
39 {
41  return context->is_object_visible(usd_export_context_.export_params.evaluation_mode);
42  }
43  return true;
44 }
45 
47 {
48  Object *object_eval = context.object;
49  bool needsfree = false;
50  Mesh *mesh = get_export_mesh(object_eval, needsfree);
51 
52  if (mesh == nullptr) {
53  return;
54  }
55 
56  try {
57  write_mesh(context, mesh);
58 
59  if (needsfree) {
61  }
62  }
63  catch (...) {
64  if (needsfree) {
66  }
67  throw;
68  }
69 }
70 
72 {
73  BKE_id_free(nullptr, mesh);
74 }
75 
76 struct USDMeshData {
77  pxr::VtArray<pxr::GfVec3f> points;
78  pxr::VtIntArray face_vertex_counts;
79  pxr::VtIntArray face_indices;
80  std::map<short, pxr::VtIntArray> face_groups;
81 
82  /* The length of this array specifies the number of creases on the surface. Each element gives
83  * the number of (must be adjacent) vertices in each crease, whose indices are linearly laid out
84  * in the 'creaseIndices' attribute. Since each crease must be at least one edge long, each
85  * element of this array should be greater than one. */
86  pxr::VtIntArray crease_lengths;
87  /* The indices of all vertices forming creased edges. The size of this array must be equal to the
88  * sum of all elements of the 'creaseLengths' attribute. */
89  pxr::VtIntArray crease_vertex_indices;
90  /* The per-crease or per-edge sharpness for all creases (Usd.Mesh.SHARPNESS_INFINITE for a
91  * perfectly sharp crease). Since 'creaseLengths' encodes the number of vertices in each crease,
92  * the number of elements in this array will be either 'len(creaseLengths)' or the sum over all X
93  * of '(creaseLengths[X] - 1)'. Note that while the RI spec allows each crease to have either a
94  * single sharpness or a value per-edge, USD will encode either a single sharpness per crease on
95  * a mesh, or sharpness's for all edges making up the creases on a mesh. */
96  pxr::VtFloatArray crease_sharpnesses;
97 
98  /* The lengths of this array specifies the number of sharp corners (or vertex crease) on the
99  * surface. Each value is the index of a vertex in the mesh's vertex list. */
100  pxr::VtIntArray corner_indices;
101  /* The per-vertex sharpnesses. The lengths of this array must match that of `corner_indices`. */
102  pxr::VtFloatArray corner_sharpnesses;
103 };
104 
105 void USDGenericMeshWriter::write_uv_maps(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh)
106 {
107  pxr::UsdTimeCode timecode = get_export_time_code();
108 
109  const CustomData *ldata = &mesh->ldata;
110  for (int layer_idx = 0; layer_idx < ldata->totlayer; layer_idx++) {
111  const CustomDataLayer *layer = &ldata->layers[layer_idx];
112  if (layer->type != CD_MLOOPUV) {
113  continue;
114  }
115 
116  /* UV coordinates are stored in a Primvar on the Mesh, and can be referenced from materials.
117  * The primvar name is the same as the UV Map name. This is to allow the standard name "st"
118  * for texture coordinates by naming the UV Map as such, without having to guess which UV Map
119  * is the "standard" one. */
120  pxr::TfToken primvar_name(pxr::TfMakeValidIdentifier(layer->name));
121  pxr::UsdGeomPrimvar uv_coords_primvar = usd_mesh.CreatePrimvar(
122  primvar_name, pxr::SdfValueTypeNames->TexCoord2fArray, pxr::UsdGeomTokens->faceVarying);
123 
124  MLoopUV *mloopuv = static_cast<MLoopUV *>(layer->data);
125  pxr::VtArray<pxr::GfVec2f> uv_coords;
126  for (int loop_idx = 0; loop_idx < mesh->totloop; loop_idx++) {
127  uv_coords.push_back(pxr::GfVec2f(mloopuv[loop_idx].uv));
128  }
129 
130  if (!uv_coords_primvar.HasValue()) {
131  uv_coords_primvar.Set(uv_coords, pxr::UsdTimeCode::Default());
132  }
133  const pxr::UsdAttribute &uv_coords_attr = uv_coords_primvar.GetAttr();
134  usd_value_writer_.SetAttribute(uv_coords_attr, pxr::VtValue(uv_coords), timecode);
135  }
136 }
137 
138 void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
139 {
140  pxr::UsdTimeCode timecode = get_export_time_code();
141  pxr::UsdTimeCode defaultTime = pxr::UsdTimeCode::Default();
142  pxr::UsdStageRefPtr stage = usd_export_context_.stage;
143  const pxr::SdfPath &usd_path = usd_export_context_.usd_path;
144 
145  pxr::UsdGeomMesh usd_mesh = pxr::UsdGeomMesh::Define(stage, usd_path);
146  write_visibility(context, timecode, usd_mesh);
147 
148  USDMeshData usd_mesh_data;
149  get_geometry_data(mesh, usd_mesh_data);
150 
152  if (!mark_as_instance(context, usd_mesh.GetPrim())) {
153  return;
154  }
155 
156  /* The material path will be of the form </_materials/{material name}>, which is outside the
157  * sub-tree pointed to by ref_path. As a result, the referenced data is not allowed to point
158  * out of its own sub-tree. It does work when we override the material with exactly the same
159  * path, though. */
161  assign_materials(context, usd_mesh, usd_mesh_data.face_groups);
162  }
163 
164  return;
165  }
166 
167  pxr::UsdAttribute attr_points = usd_mesh.CreatePointsAttr(pxr::VtValue(), true);
168  pxr::UsdAttribute attr_face_vertex_counts = usd_mesh.CreateFaceVertexCountsAttr(pxr::VtValue(),
169  true);
170  pxr::UsdAttribute attr_face_vertex_indices = usd_mesh.CreateFaceVertexIndicesAttr(pxr::VtValue(),
171  true);
172 
173  if (!attr_points.HasValue()) {
174  /* Provide the initial value as default. This makes USD write the value as constant if they
175  * don't change over time. */
176  attr_points.Set(usd_mesh_data.points, defaultTime);
177  attr_face_vertex_counts.Set(usd_mesh_data.face_vertex_counts, defaultTime);
178  attr_face_vertex_indices.Set(usd_mesh_data.face_indices, defaultTime);
179  }
180 
181  usd_value_writer_.SetAttribute(attr_points, pxr::VtValue(usd_mesh_data.points), timecode);
182  usd_value_writer_.SetAttribute(
183  attr_face_vertex_counts, pxr::VtValue(usd_mesh_data.face_vertex_counts), timecode);
184  usd_value_writer_.SetAttribute(
185  attr_face_vertex_indices, pxr::VtValue(usd_mesh_data.face_indices), timecode);
186 
187  if (!usd_mesh_data.crease_lengths.empty()) {
188  pxr::UsdAttribute attr_crease_lengths = usd_mesh.CreateCreaseLengthsAttr(pxr::VtValue(), true);
189  pxr::UsdAttribute attr_crease_indices = usd_mesh.CreateCreaseIndicesAttr(pxr::VtValue(), true);
190  pxr::UsdAttribute attr_crease_sharpness = usd_mesh.CreateCreaseSharpnessesAttr(pxr::VtValue(),
191  true);
192 
193  if (!attr_crease_lengths.HasValue()) {
194  attr_crease_lengths.Set(usd_mesh_data.crease_lengths, defaultTime);
195  attr_crease_indices.Set(usd_mesh_data.crease_vertex_indices, defaultTime);
196  attr_crease_sharpness.Set(usd_mesh_data.crease_sharpnesses, defaultTime);
197  }
198 
199  usd_value_writer_.SetAttribute(
200  attr_crease_lengths, pxr::VtValue(usd_mesh_data.crease_lengths), timecode);
201  usd_value_writer_.SetAttribute(
202  attr_crease_indices, pxr::VtValue(usd_mesh_data.crease_vertex_indices), timecode);
203  usd_value_writer_.SetAttribute(
204  attr_crease_sharpness, pxr::VtValue(usd_mesh_data.crease_sharpnesses), timecode);
205  }
206 
207  if (!usd_mesh_data.corner_indices.empty() &&
208  usd_mesh_data.corner_indices.size() == usd_mesh_data.corner_sharpnesses.size()) {
209  pxr::UsdAttribute attr_corner_indices = usd_mesh.CreateCornerIndicesAttr(pxr::VtValue(), true);
210  pxr::UsdAttribute attr_corner_sharpnesses = usd_mesh.CreateCornerSharpnessesAttr(
211  pxr::VtValue(), true);
212 
213  if (!attr_corner_indices.HasValue()) {
214  attr_corner_indices.Set(usd_mesh_data.corner_indices, defaultTime);
215  attr_corner_sharpnesses.Set(usd_mesh_data.corner_sharpnesses, defaultTime);
216  }
217 
218  usd_value_writer_.SetAttribute(
219  attr_corner_indices, pxr::VtValue(usd_mesh_data.corner_indices), timecode);
220  usd_value_writer_.SetAttribute(
221  attr_corner_sharpnesses, pxr::VtValue(usd_mesh_data.crease_sharpnesses), timecode);
222  }
223 
225  write_uv_maps(mesh, usd_mesh);
226  }
228  write_normals(mesh, usd_mesh);
229  }
230  write_surface_velocity(mesh, usd_mesh);
231 
232  /* TODO(Sybren): figure out what happens when the face groups change. */
234  return;
235  }
236 
237  usd_mesh.CreateSubdivisionSchemeAttr().Set(pxr::UsdGeomTokens->none);
238 
240  assign_materials(context, usd_mesh, usd_mesh_data.face_groups);
241  }
242 }
243 
244 static void get_vertices(const Mesh *mesh, USDMeshData &usd_mesh_data)
245 {
246  usd_mesh_data.points.reserve(mesh->totvert);
247 
248  const MVert *verts = mesh->mvert;
249  for (int i = 0; i < mesh->totvert; ++i) {
250  usd_mesh_data.points.push_back(pxr::GfVec3f(verts[i].co));
251  }
252 }
253 
254 static void get_loops_polys(const Mesh *mesh, USDMeshData &usd_mesh_data)
255 {
256  /* Only construct face groups (a.k.a. geometry subsets) when we need them for material
257  * assignments. */
258  bool construct_face_groups = mesh->totcol > 1;
259 
260  usd_mesh_data.face_vertex_counts.reserve(mesh->totpoly);
261  usd_mesh_data.face_indices.reserve(mesh->totloop);
262 
263  MLoop *mloop = mesh->mloop;
264  MPoly *mpoly = mesh->mpoly;
265  for (int i = 0; i < mesh->totpoly; ++i, ++mpoly) {
266  MLoop *loop = mloop + mpoly->loopstart;
267  usd_mesh_data.face_vertex_counts.push_back(mpoly->totloop);
268  for (int j = 0; j < mpoly->totloop; ++j, ++loop) {
269  usd_mesh_data.face_indices.push_back(loop->v);
270  }
271 
272  if (construct_face_groups) {
273  usd_mesh_data.face_groups[mpoly->mat_nr].push_back(i);
274  }
275  }
276 }
277 
278 static void get_edge_creases(const Mesh *mesh, USDMeshData &usd_mesh_data)
279 {
280  const float factor = 1.0f / 255.0f;
281 
282  MEdge *edge = mesh->medge;
283  float sharpness;
284  for (int edge_idx = 0, totedge = mesh->totedge; edge_idx < totedge; ++edge_idx, ++edge) {
285  if (edge->crease == 0) {
286  continue;
287  }
288 
289  if (edge->crease == 255) {
290  sharpness = pxr::UsdGeomMesh::SHARPNESS_INFINITE;
291  }
292  else {
293  sharpness = static_cast<float>(edge->crease) * factor;
294  }
295 
296  usd_mesh_data.crease_vertex_indices.push_back(edge->v1);
297  usd_mesh_data.crease_vertex_indices.push_back(edge->v2);
298  usd_mesh_data.crease_lengths.push_back(2);
299  usd_mesh_data.crease_sharpnesses.push_back(sharpness);
300  }
301 }
302 
303 static void get_vert_creases(const Mesh *mesh, USDMeshData &usd_mesh_data)
304 {
305  const float *creases = static_cast<const float *>(CustomData_get_layer(&mesh->vdata, CD_CREASE));
306 
307  if (!creases) {
308  return;
309  }
310 
311  for (int i = 0, v = mesh->totvert; i < v; i++) {
312  const float sharpness = creases[i];
313 
314  if (sharpness != 0.0f) {
315  usd_mesh_data.corner_indices.push_back(i);
316  usd_mesh_data.corner_sharpnesses.push_back(sharpness);
317  }
318  }
319 }
320 
321 void USDGenericMeshWriter::get_geometry_data(const Mesh *mesh, USDMeshData &usd_mesh_data)
322 {
323  get_vertices(mesh, usd_mesh_data);
324  get_loops_polys(mesh, usd_mesh_data);
325  get_edge_creases(mesh, usd_mesh_data);
326  get_vert_creases(mesh, usd_mesh_data);
327 }
328 
329 void USDGenericMeshWriter::assign_materials(const HierarchyContext &context,
330  pxr::UsdGeomMesh usd_mesh,
331  const MaterialFaceGroups &usd_face_groups)
332 {
333  if (context.object->totcol == 0) {
334  return;
335  }
336 
337  /* Binding a material to a geometry subset isn't supported by the Hydra GL viewport yet,
338  * which is why we always bind the first material to the entire mesh. See
339  * https://github.com/PixarAnimationStudios/USD/issues/542 for more info. */
340  bool mesh_material_bound = false;
341  pxr::UsdShadeMaterialBindingAPI material_binding_api(usd_mesh.GetPrim());
342  for (int mat_num = 0; mat_num < context.object->totcol; mat_num++) {
343  Material *material = BKE_object_material_get(context.object, mat_num + 1);
344  if (material == nullptr) {
345  continue;
346  }
347 
348  pxr::UsdShadeMaterial usd_material = ensure_usd_material(context, material);
349  material_binding_api.Bind(usd_material);
350 
351  /* USD seems to support neither per-material nor per-face-group double-sidedness, so we just
352  * use the flag from the first non-empty material slot. */
353  usd_mesh.CreateDoubleSidedAttr(
354  pxr::VtValue((material->blend_flag & MA_BL_CULL_BACKFACE) == 0));
355 
356  mesh_material_bound = true;
357  break;
358  }
359 
360  if (!mesh_material_bound) {
361  /* Blender defaults to double-sided, but USD to single-sided. */
362  usd_mesh.CreateDoubleSidedAttr(pxr::VtValue(true));
363  }
364 
365  if (!mesh_material_bound || usd_face_groups.size() < 2) {
366  /* Either all material slots were empty or there is only one material in use. As geometry
367  * subsets are only written when actually used to assign a material, and the mesh already has
368  * the material assigned, there is no need to continue. */
369  return;
370  }
371 
372  /* Define a geometry subset per material. */
373  for (const MaterialFaceGroups::value_type &face_group : usd_face_groups) {
374  short material_number = face_group.first;
375  const pxr::VtIntArray &face_indices = face_group.second;
376 
377  Material *material = BKE_object_material_get(context.object, material_number + 1);
378  if (material == nullptr) {
379  continue;
380  }
381 
382  pxr::UsdShadeMaterial usd_material = ensure_usd_material(context, material);
383  pxr::TfToken material_name = usd_material.GetPath().GetNameToken();
384 
385  pxr::UsdGeomSubset usd_face_subset = material_binding_api.CreateMaterialBindSubset(
386  material_name, face_indices);
387  pxr::UsdShadeMaterialBindingAPI(usd_face_subset.GetPrim()).Bind(usd_material);
388  }
389 }
390 
391 void USDGenericMeshWriter::write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh)
392 {
393  pxr::UsdTimeCode timecode = get_export_time_code();
394  const float(*lnors)[3] = static_cast<float(*)[3]>(CustomData_get_layer(&mesh->ldata, CD_NORMAL));
395 
396  pxr::VtVec3fArray loop_normals;
397  loop_normals.reserve(mesh->totloop);
398 
399  if (lnors != nullptr) {
400  /* Export custom loop normals. */
401  for (int loop_idx = 0, totloop = mesh->totloop; loop_idx < totloop; ++loop_idx) {
402  loop_normals.push_back(pxr::GfVec3f(lnors[loop_idx]));
403  }
404  }
405  else {
406  /* Compute the loop normals based on the 'smooth' flag. */
407  const float(*vert_normals)[3] = BKE_mesh_vertex_normals_ensure(mesh);
408  const float(*face_normals)[3] = BKE_mesh_poly_normals_ensure(mesh);
409  MPoly *mpoly = mesh->mpoly;
410  for (int poly_idx = 0, totpoly = mesh->totpoly; poly_idx < totpoly; ++poly_idx, ++mpoly) {
411  MLoop *mloop = mesh->mloop + mpoly->loopstart;
412 
413  if ((mpoly->flag & ME_SMOOTH) == 0) {
414  /* Flat shaded, use common normal for all verts. */
415  pxr::GfVec3f pxr_normal(face_normals[poly_idx]);
416  for (int loop_idx = 0; loop_idx < mpoly->totloop; ++loop_idx) {
417  loop_normals.push_back(pxr_normal);
418  }
419  }
420  else {
421  /* Smooth shaded, use individual vert normals. */
422  for (int loop_idx = 0; loop_idx < mpoly->totloop; ++loop_idx, ++mloop) {
423  loop_normals.push_back(pxr::GfVec3f(vert_normals[mloop->v]));
424  }
425  }
426  }
427  }
428 
429  pxr::UsdAttribute attr_normals = usd_mesh.CreateNormalsAttr(pxr::VtValue(), true);
430  if (!attr_normals.HasValue()) {
431  attr_normals.Set(loop_normals, pxr::UsdTimeCode::Default());
432  }
433  usd_value_writer_.SetAttribute(attr_normals, pxr::VtValue(loop_normals), timecode);
434  usd_mesh.SetNormalsInterpolation(pxr::UsdGeomTokens->faceVarying);
435 }
436 
437 void USDGenericMeshWriter::write_surface_velocity(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh)
438 {
439  /* Export velocity attribute output by fluid sim, sequence cache modifier
440  * and geometry nodes. */
441  CustomDataLayer *velocity_layer = BKE_id_attribute_find(
442  &mesh->id, "velocity", CD_PROP_FLOAT3, ATTR_DOMAIN_POINT);
443 
444  if (velocity_layer == nullptr) {
445  return;
446  }
447 
448  const float(*velocities)[3] = reinterpret_cast<float(*)[3]>(velocity_layer->data);
449 
450  /* Export per-vertex velocity vectors. */
451  pxr::VtVec3fArray usd_velocities;
452  usd_velocities.reserve(mesh->totvert);
453 
454  for (int vertex_idx = 0, totvert = mesh->totvert; vertex_idx < totvert; ++vertex_idx) {
455  usd_velocities.push_back(pxr::GfVec3f(velocities[vertex_idx]));
456  }
457 
458  pxr::UsdTimeCode timecode = get_export_time_code();
459  usd_mesh.CreateVelocitiesAttr().Set(usd_velocities, timecode);
460 }
461 
463 {
464 }
465 
466 Mesh *USDMeshWriter::get_export_mesh(Object *object_eval, bool & /*r_needsfree*/)
467 {
468  return BKE_object_get_evaluated_mesh(object_eval);
469 }
470 
471 } // namespace blender::io::usd
typedef float(TangentPoint)[2]
Generic geometry attributes built on CustomData.
@ ATTR_DOMAIN_POINT
Definition: BKE_attribute.h:27
struct CustomDataLayer * BKE_id_attribute_find(const struct ID *id, const char *name, int type, eAttrDomain domain)
CustomData interface, see also DNA_customdata_types.h.
void * CustomData_get_layer(const struct CustomData *data, int type)
void BKE_id_free(struct Main *bmain, void *idv)
General operations, lookup, etc. for materials.
struct Material * BKE_object_material_get(struct Object *ob, short act)
Definition: material.c:687
const float(* BKE_mesh_poly_normals_ensure(const struct Mesh *mesh))[3]
const float(* BKE_mesh_vertex_normals_ensure(const struct Mesh *mesh))[3]
General operations, lookup, etc. for blender objects.
struct Mesh * BKE_object_get_evaluated_mesh(const struct Object *object)
@ CD_PROP_FLOAT3
@ CD_MLOOPUV
@ MA_BL_CULL_BACKFACE
@ ME_SMOOTH
__forceinline bool none(const avxb &b)
Definition: avxb.h:209
ATTR_WARN_UNUSED_RESULT const BMVert * v
pxr::UsdShadeMaterial ensure_usd_material(const HierarchyContext &context, Material *material)
const pxr::SdfPath & usd_path() const
void write_visibility(const HierarchyContext &context, const pxr::UsdTimeCode timecode, pxr::UsdGeomImageable &usd_geometry)
virtual bool mark_as_instance(const HierarchyContext &context, const pxr::UsdPrim &prim)
pxr::UsdTimeCode get_export_time_code() const
pxr::UsdUtilsSparseValueWriter usd_value_writer_
const USDExporterContext usd_export_context_
USDGenericMeshWriter(const USDExporterContext &ctx)
virtual void do_write(HierarchyContext &context) override
virtual bool is_supported(const HierarchyContext *context) const override
virtual Mesh * get_export_mesh(Object *object_eval, bool &r_needsfree)=0
virtual void free_export_mesh(Mesh *mesh)
USDMeshWriter(const USDExporterContext &ctx)
virtual Mesh * get_export_mesh(Object *object_eval, bool &r_needsfree) override
EvaluationStage stage
Definition: deg_eval.cc:89
Material material
static float verts[][3]
static void get_loops_polys(const Mesh *mesh, USDMeshData &usd_mesh_data)
static void get_vert_creases(const Mesh *mesh, USDMeshData &usd_mesh_data)
static void get_vertices(const Mesh *mesh, USDMeshData &usd_mesh_data)
static void get_edge_creases(const Mesh *mesh, USDMeshData &usd_mesh_data)
CustomDataLayer * layers
unsigned int v
short mat_nr
struct MEdge * medge
CustomData vdata
struct MVert * mvert
int totedge
int totvert
struct MLoop * mloop
int totpoly
short totcol
int totloop
struct MPoly * mpoly
CustomData ldata
enum eEvaluationMode evaluation_mode
Definition: usd.h:34
bool export_materials
Definition: usd.h:30
bool visible_objects_only
Definition: usd.h:32
bool export_normals
Definition: usd.h:29
bool use_instancing
Definition: usd.h:33
bool export_uvmaps
Definition: usd.h:28
pxr::VtIntArray crease_vertex_indices
pxr::VtFloatArray corner_sharpnesses
std::map< short, pxr::VtIntArray > face_groups
pxr::VtFloatArray crease_sharpnesses
pxr::VtArray< pxr::GfVec3f > points