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