USD Import: USD Shapes Support #104707

Merged
Hans Goudey merged 6 commits from CharlesWardlaw/blender:D16344-usd-shapes-export into main 2023-02-13 19:49:35 +01:00
7 changed files with 339 additions and 0 deletions

View File

@ -382,6 +382,7 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
const bool import_materials = RNA_boolean_get(op->ptr, "import_materials");
const bool import_meshes = RNA_boolean_get(op->ptr, "import_meshes");
const bool import_volumes = RNA_boolean_get(op->ptr, "import_volumes");
const bool import_shapes = RNA_boolean_get(op->ptr, "import_shapes");
const bool import_subdiv = RNA_boolean_get(op->ptr, "import_subdiv");
@ -443,6 +444,7 @@ static int wm_usd_import_exec(bContext *C, wmOperator *op)
.import_materials = import_materials,
.import_meshes = import_meshes,
.import_volumes = import_volumes,
.import_shapes = import_shapes,
.import_subdiv = import_subdiv,
.import_instance_proxies = import_instance_proxies,
.create_collection = create_collection,
@ -488,6 +490,7 @@ static void wm_usd_import_draw(bContext *UNUSED(C), wmOperator *op)
uiItemR(col, ptr, "import_materials", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "import_meshes", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "import_volumes", 0, NULL, ICON_NONE);
uiItemR(col, ptr, "import_shapes", 0, NULL, ICON_NONE);
uiItemR(box, ptr, "prim_path_mask", 0, NULL, ICON_NONE);
uiItemR(box, ptr, "scale", 0, NULL, ICON_NONE);
@ -577,6 +580,7 @@ void WM_OT_usd_import(struct wmOperatorType *ot)
RNA_def_boolean(ot->srna, "import_materials", true, "Materials", "");
RNA_def_boolean(ot->srna, "import_meshes", true, "Meshes", "");
RNA_def_boolean(ot->srna, "import_volumes", true, "Volumes", "");
RNA_def_boolean(ot->srna, "import_shapes", true, "Shapes", "");
RNA_def_boolean(ot->srna,
"import_subdiv",

View File

@ -83,6 +83,7 @@ set(SRC
intern/usd_reader_mesh.cc
intern/usd_reader_nurbs.cc
intern/usd_reader_prim.cc
intern/usd_reader_shape.cc
intern/usd_reader_stage.cc
intern/usd_reader_volume.cc
intern/usd_reader_xform.cc
@ -111,6 +112,7 @@ set(SRC
intern/usd_reader_mesh.h
intern/usd_reader_nurbs.h
intern/usd_reader_prim.h
intern/usd_reader_shape.h
intern/usd_reader_stage.h
intern/usd_reader_volume.h
intern/usd_reader_xform.h

View File

@ -0,0 +1,245 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
CharlesWardlaw marked this conversation as resolved
Review

Missing license header

Missing license header
Review

Added.

Added.
* Copyright 2023 Nvidia. All rights reserved. */
#include "BKE_lib_id.h"
#include "BKE_mesh.h"
#include "BKE_modifier.h"
#include "BKE_object.h"
#include "DNA_cachefile_types.h"
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
CharlesWardlaw marked this conversation as resolved
Review

The comment says the header is added for FILE_MAX, but FILE_MAX isn't used here. Make sure the headers included here are actually necessary.

The comment says the header is added `for FILE_MAX`, but `FILE_MAX` isn't used here. Make sure the headers included here are actually necessary.
Review

Removed.

Removed.
#include "DNA_object_types.h"
#include "DNA_windowmanager_types.h"
#include "WM_api.h"
#include "usd_reader_shape.h"
#include <pxr/usd/usdGeom/capsule.h>
#include <pxr/usd/usdGeom/cone.h>
#include <pxr/usd/usdGeom/cube.h>
#include <pxr/usd/usdGeom/cylinder.h>
#include <pxr/usd/usdGeom/sphere.h>
#include <pxr/usdImaging/usdImaging/capsuleAdapter.h>
#include <pxr/usdImaging/usdImaging/coneAdapter.h>
#include <pxr/usdImaging/usdImaging/cubeAdapter.h>
#include <pxr/usdImaging/usdImaging/cylinderAdapter.h>
#include <pxr/usdImaging/usdImaging/sphereAdapter.h>
namespace blender::io::usd {
USDShapeReader::USDShapeReader(const pxr::UsdPrim &prim,
const USDImportParams &import_params,
const ImportSettings &settings)
: USDGeomReader(prim, import_params, settings)
{
}
void USDShapeReader::create_object(Main *bmain, double /*motionSampleTime*/)
{
CharlesWardlaw marked this conversation as resolved
Review

Change this to /*motionSampleTime*/ in the function arguments

Change this to `/*motionSampleTime*/` in the function arguments
Review

Changed.

Changed.
Mesh *mesh = BKE_mesh_add(bmain, name_.c_str());
object_ = BKE_object_add_only_object(bmain, OB_MESH, name_.c_str());
object_->data = mesh;
}
void USDShapeReader::read_object_data(Main *bmain, double motionSampleTime)
{
Mesh *mesh = (Mesh *)object_->data;
Mesh *read_mesh = this->read_mesh(
mesh, motionSampleTime, import_params_.mesh_read_flag, nullptr);
if (read_mesh != mesh) {
BKE_mesh_nomain_to_mesh(read_mesh, mesh, object_);
if (is_time_varying()) {
USDGeomReader::add_cache_modifier();
CharlesWardlaw marked this conversation as resolved
Review

This isn't true anymore, this logic with mesh->flag can be removed.

This isn't true anymore, this logic with `mesh->flag` can be removed.
Review

Removed.

Removed.
}
}
USDXformReader::read_object_data(bmain, motionSampleTime);
}
template<typename Adapter>
void USDShapeReader::read_values(const double motionSampleTime,
pxr::VtVec3fArray &positions,
pxr::VtIntArray &face_indices,
pxr::VtIntArray &face_counts) const
{
Adapter adapter;
pxr::VtValue points_val = adapter.GetPoints(prim_, motionSampleTime);
if (points_val.IsHolding<pxr::VtVec3fArray>()) {
positions = points_val.Get<pxr::VtVec3fArray>();
}
pxr::VtValue topology_val = adapter.GetTopology(prim_, pxr::SdfPath(), motionSampleTime);
if (topology_val.IsHolding<pxr::HdMeshTopology>()) {
const pxr::HdMeshTopology &topology = topology_val.Get<pxr::HdMeshTopology>();
face_counts = topology.GetFaceVertexCounts();
face_indices = topology.GetFaceVertexIndices();
}
}
bool USDShapeReader::read_mesh_values(double motionSampleTime,
pxr::VtVec3fArray &positions,
pxr::VtIntArray &face_indices,
pxr::VtIntArray &face_counts) const
{
if (prim_.IsA<pxr::UsdGeomCapsule>()) {
read_values<pxr::UsdImagingCapsuleAdapter>(
motionSampleTime, positions, face_indices, face_counts);
CharlesWardlaw marked this conversation as resolved
Review

Looks like this is missing clang format here

Looks like this is missing clang format here
Review

Reformatted file.

Reformatted file.
return true;
}
if (prim_.IsA<pxr::UsdGeomCylinder>()) {
read_values<pxr::UsdImagingCylinderAdapter>(
CharlesWardlaw marked this conversation as resolved
Review

No need for these separate boolean variables IMO, just put the check directly inside the if statements.

No need for these separate boolean variables IMO, just put the check directly inside the if statements.
Review

Changed.

Changed.
motionSampleTime, positions, face_indices, face_counts);
return true;
}
if (prim_.IsA<pxr::UsdGeomCone>()) {
read_values<pxr::UsdImagingConeAdapter>(
motionSampleTime, positions, face_indices, face_counts);
return true;
}
if (prim_.IsA<pxr::UsdGeomCube>()) {
read_values<pxr::UsdImagingCubeAdapter>(
motionSampleTime, positions, face_indices, face_counts);
return true;
}
if (prim_.IsA<pxr::UsdGeomSphere>()) {
read_values<pxr::UsdImagingSphereAdapter>(
motionSampleTime, positions, face_indices, face_counts);
return true;
}
WM_reportf(RPT_ERROR,
"Unhandled Gprim type: %s (%s)",
prim_.GetTypeName().GetText(),
prim_.GetPath().GetText());
return false;
}
Mesh *USDShapeReader::read_mesh(struct Mesh *existing_mesh,
Review

struct Mesh -> Mesh

`struct Mesh` -> `Mesh`
double motionSampleTime,
int /*read_flag*/,
const char ** /*err_str*/)
{
pxr::VtIntArray face_indices;
pxr::VtIntArray face_counts;
CharlesWardlaw marked this conversation as resolved
Review

No need for the struct keyword in C++ code

No need for the struct keyword in C++ code
Review

Removed.

Removed.
if (!prim_) {
return existing_mesh;
}
/* Should have a good set of data by this point-- copy over. */
Mesh *active_mesh = mesh_from_prim(existing_mesh, motionSampleTime, face_indices, face_counts);
if (active_mesh == existing_mesh) {
return existing_mesh;
CharlesWardlaw marked this conversation as resolved
Review

Suggestion here so the reader has fewer things to keep track of at the same time:

  Mesh *active_mesh = mesh_from_prim(existing_mesh, motionSampleTime, face_indices, face_counts);
  if (active_mesh == existing_mesh) {
    return existing_mesh;
  }

  MutableSpan<MPoly> polys = active_mesh->polys_for_write();
  MutableSpan<MLoop> loops = active_mesh->loops_for_write();

  const char should_smooth = prim_.IsA<pxr::UsdGeomCube>() ? 0 : ME_SMOOTH;

  int loop_index = 0;
  for (int i = 0; i < face_counts.size(); i++) {
    const int face_size = face_counts[i];

    MPoly &poly = polys[i];
    poly.loopstart = loop_index;
    poly.totloop = face_size;

    /* Don't smooth-shade cubes; we're not worrying about sharpness for Gprims. */
    poly.flag |= should_smooth;

    for (int f = 0; f < face_size; ++f, ++loop_index) {
      loops[loop_index].v = face_indices[loop_index];
    }
  }
  BKE_mesh_calc_edges(active_mesh, false, false);

  return active_mesh;
Suggestion here so the reader has fewer things to keep track of at the same time: ``` Mesh *active_mesh = mesh_from_prim(existing_mesh, motionSampleTime, face_indices, face_counts); if (active_mesh == existing_mesh) { return existing_mesh; } MutableSpan<MPoly> polys = active_mesh->polys_for_write(); MutableSpan<MLoop> loops = active_mesh->loops_for_write(); const char should_smooth = prim_.IsA<pxr::UsdGeomCube>() ? 0 : ME_SMOOTH; int loop_index = 0; for (int i = 0; i < face_counts.size(); i++) { const int face_size = face_counts[i]; MPoly &poly = polys[i]; poly.loopstart = loop_index; poly.totloop = face_size; /* Don't smooth-shade cubes; we're not worrying about sharpness for Gprims. */ poly.flag |= should_smooth; for (int f = 0; f < face_size; ++f, ++loop_index) { loops[loop_index].v = face_indices[loop_index]; } } BKE_mesh_calc_edges(active_mesh, false, false); return active_mesh; ```
}
MutableSpan<MPoly> polys = active_mesh->polys_for_write();
CharlesWardlaw marked this conversation as resolved
Review

Declare loop_index at the smallest scope possible, right above the for (int i = 0; i < face_counts.size(); i++) { loop.

Declare `loop_index` at the smallest scope possible, right above the `for (int i = 0; i < face_counts.size(); i++) {` loop.
MutableSpan<MLoop> loops = active_mesh->loops_for_write();
CharlesWardlaw marked this conversation as resolved
Review

Same here with the unused arguments

Same here with the unused arguments
Review

Changed.

Changed.
const char should_smooth = prim_.IsA<pxr::UsdGeomCube>() ? 0 : ME_SMOOTH;
int loop_index = 0;
for (int i = 0; i < face_counts.size(); i++) {
const int face_size = face_counts[i];
MPoly &poly = polys[i];
poly.loopstart = loop_index;
poly.totloop = face_size;
/* Don't smooth-shade cubes; we're not worrying about sharpness for Gprims. */
poly.flag |= should_smooth;
for (int f = 0; f < face_size; ++f, ++loop_index) {
loops[loop_index].v = face_indices[loop_index];
}
}
BKE_mesh_calc_edges(active_mesh, false, false);
return active_mesh;
}
Mesh *USDShapeReader::mesh_from_prim(Mesh *existing_mesh,
double motionSampleTime,
pxr::VtIntArray &face_indices,
pxr::VtIntArray &face_counts) const
{
pxr::VtVec3fArray positions;
CharlesWardlaw marked this conversation as resolved
Review

Why do you need to calculate the edges if you didn't change anything above?

Why do you need to calculate the edges if you didn't change anything above?
Review

mesh_from_prim() does not calculate edges; I deferred their calculation to this spot.

mesh_from_prim() does not calculate edges; I deferred their calculation to this spot.
Review

Yes, but my point is, shouldn't it be in the if (active_mesh != existing_mesh) { where the corner vertices are actually set? Otherwise it looks like it's calculating edges unnecessarily.

Yes, but my point is, shouldn't it be in the `if (active_mesh != existing_mesh) {` where the corner vertices are actually set? Otherwise it looks like it's calculating edges unnecessarily.
Review

No, because active_mesh was created by mesh_from_prim() which does not calculate the edges. They need to be calculated either way.

No, because `active_mesh` was created by `mesh_from_prim()` which does not calculate the edges. They need to be calculated either way.
CharlesWardlaw marked this conversation as resolved
Review

No need to tag normals dirty on a new mesh. You aren't changing the positions here anyway, so it's not the correct update tag if any was actually needed.

No need to tag normals dirty on a new mesh. You aren't changing the positions here anyway, so it's not the correct update tag if any was actually needed.
Review

Seems to be fine now without-- removed.

Seems to be fine now without-- removed.
if (!read_mesh_values(motionSampleTime, positions, face_indices, face_counts)) {
return existing_mesh;
}
const bool poly_counts_match = existing_mesh ? face_counts.size() == existing_mesh->totpoly :
false;
CharlesWardlaw marked this conversation as resolved
Review

Clang format!

Clang format!
Review

Reformatted file.

Reformatted file.
const bool position_counts_match = existing_mesh ? positions.size() == existing_mesh->totvert :
false;
Mesh *active_mesh = nullptr;
if (!position_counts_match || !poly_counts_match) {
active_mesh = BKE_mesh_new_nomain_from_template(
existing_mesh, positions.size(), 0, 0, face_indices.size(), face_counts.size());
}
else {
active_mesh = existing_mesh;
}
MutableSpan<float3> vert_positions = active_mesh->vert_positions_for_write();
for (int i = 0; i < positions.size(); i++) {
vert_positions[i][0] = positions[i][0];
vert_positions[i][1] = positions[i][1];
vert_positions[i][2] = positions[i][2];
CharlesWardlaw marked this conversation as resolved
Review

For consistency, change the verts name to vert_positions

For consistency, change the `verts` name to `vert_positions`
Review

Changed.

Changed.
}
return active_mesh;
}
bool USDShapeReader::is_time_varying()
{
if (prim_.IsA<pxr::UsdGeomCapsule>()) {
pxr::UsdGeomCapsule geom(prim_);
return (geom.GetAxisAttr().ValueMightBeTimeVarying() ||
geom.GetHeightAttr().ValueMightBeTimeVarying() ||
geom.GetRadiusAttr().ValueMightBeTimeVarying());
}
if (prim_.IsA<pxr::UsdGeomCylinder>()) {
pxr::UsdGeomCylinder geom(prim_);
CharlesWardlaw marked this conversation as resolved
Review

Same here with the no need for separate variables

Same here with the no need for separate variables
Review

Removed.

Removed.
return (geom.GetAxisAttr().ValueMightBeTimeVarying() ||
geom.GetHeightAttr().ValueMightBeTimeVarying() ||
geom.GetRadiusAttr().ValueMightBeTimeVarying());
}
if (prim_.IsA<pxr::UsdGeomCone>()) {
pxr::UsdGeomCone geom(prim_);
return (geom.GetAxisAttr().ValueMightBeTimeVarying() ||
geom.GetHeightAttr().ValueMightBeTimeVarying() ||
geom.GetRadiusAttr().ValueMightBeTimeVarying());
}
if (prim_.IsA<pxr::UsdGeomCube>()) {
pxr::UsdGeomCube geom(prim_);
return geom.GetSizeAttr().ValueMightBeTimeVarying();
}
if (prim_.IsA<pxr::UsdGeomSphere>()) {
pxr::UsdGeomSphere geom(prim_);
return geom.GetRadiusAttr().ValueMightBeTimeVarying();
}
WM_reportf(RPT_ERROR,
"Unhandled Gprim type: %s (%s)",
prim_.GetTypeName().GetText(),
prim_.GetPath().GetText());
return false;
}
} // namespace blender::io::usd

View File

@ -0,0 +1,62 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
CharlesWardlaw marked this conversation as resolved
Review

Missing license header

Missing license header
Review

Added.

Added.
* Copyright 2023 Nvidia. All rights reserved. */
#pragma once
#include "usd.h"
#include "usd_reader_geom.h"
#include "usd_reader_xform.h"
#include <pxr/usd/usdGeom/gprim.h>
struct Mesh;
namespace blender::io::usd {
/*
* Read USDGeom primitive shapes as Blender Meshes. This class uses the same adapter functions
* as the GL viewport to generate geometry for each of the supported types.
*/
class USDShapeReader : public USDGeomReader {
private:
/* Template required to read mesh information out of Shape prims,
* as each prim type has a separate subclass. */
template<typename Adapter>
void read_values(double motionSampleTime,
pxr::VtVec3fArray &positions,
pxr::VtIntArray &face_indices,
pxr::VtIntArray &face_counts) const;
/* Wrapper for the templated method read_values, calling the correct template
* instantiation based on the introspected prim type. */
bool read_mesh_values(double motionSampleTime,
pxr::VtVec3fArray &positions,
pxr::VtIntArray &face_indices,
pxr::VtIntArray &face_counts) const;
/* Read the pxr:UsdGeomMesh values and convert them to a Blender Mesh,
CharlesWardlaw marked this conversation as resolved
Review

Clang format! Please configure your editor to format when you save the file, it will save everyone's time.

Clang format! Please configure your editor to format when you save the file, it will save everyone's time.
Review

Reformatted file.

Reformatted file.
* also returning face_indices and counts for further loop processing. */
Mesh *mesh_from_prim(Mesh *existing_mesh,
double motionSampleTime,
pxr::VtIntArray &face_indices,
pxr::VtIntArray &face_counts) const;
public:
USDShapeReader(const pxr::UsdPrim &prim,
const USDImportParams &import_params,
const ImportSettings &settings);
CharlesWardlaw marked this conversation as resolved
Review

No need for these struct keywords in C++ code, even headers.

No need for these struct keywords in C++ code, even headers.
Review

Removed.

Removed.
Review

Removed.

Removed.
void create_object(Main *bmain, double /*motionSampleTime*/) override;
void read_object_data(Main *bmain, double motionSampleTime) override;
Mesh *read_mesh(Mesh *existing_mesh,
CharlesWardlaw marked this conversation as resolved
Review

struct Mesh -> Mesh

`struct Mesh` -> `Mesh`
double motionSampleTime,
int /*read_flag*/,
CharlesWardlaw marked this conversation as resolved
Review

Consistency: /* existing_mesh */ -> /*existing_mesh*/, etc.

Consistency: `/* existing_mesh */` -> `/*existing_mesh*/`, etc.
Review

Changed.

Changed.
const char ** /*err_str*/) override;
bool is_time_varying();
virtual bool topology_changed(const Mesh * /*existing_mesh*/, double /*motionSampleTime*/)
{
return false;
};
};
} // namespace blender::io::usd

View File

@ -9,6 +9,7 @@
#include "usd_reader_mesh.h"
#include "usd_reader_nurbs.h"
#include "usd_reader_prim.h"
#include "usd_reader_shape.h"
#include "usd_reader_volume.h"
#include "usd_reader_xform.h"
@ -16,9 +17,14 @@
#include <pxr/usd/usd/primRange.h>
#include <pxr/usd/usdGeom/camera.h>
#include <pxr/usd/usdGeom/curves.h>
#include <pxr/usd/usdGeom/capsule.h>
#include <pxr/usd/usdGeom/cone.h>
#include <pxr/usd/usdGeom/cube.h>
#include <pxr/usd/usdGeom/cylinder.h>
#include <pxr/usd/usdGeom/mesh.h>
#include <pxr/usd/usdGeom/nurbsCurves.h>
#include <pxr/usd/usdGeom/scope.h>
#include <pxr/usd/usdGeom/sphere.h>
#include <pxr/usd/usdGeom/xform.h>
#include <pxr/usd/usdShade/material.h>
@ -57,8 +63,18 @@ bool USDStageReader::valid() const
return stage_;
}
bool USDStageReader::is_primitive_prim(const pxr::UsdPrim &prim) const
{
return (prim.IsA<pxr::UsdGeomCapsule>() || prim.IsA<pxr::UsdGeomCylinder>() ||
prim.IsA<pxr::UsdGeomCone>() || prim.IsA<pxr::UsdGeomCube>() ||
prim.IsA<pxr::UsdGeomSphere>());
}
USDPrimReader *USDStageReader::create_reader_if_allowed(const pxr::UsdPrim &prim)
{
if (params_.import_shapes && is_primitive_prim(prim)) {
return new USDShapeReader(prim, params_, settings_);
}
if (params_.import_cameras && prim.IsA<pxr::UsdGeomCamera>()) {
return new USDCameraReader(prim, params_, settings_);
}
@ -91,6 +107,9 @@ USDPrimReader *USDStageReader::create_reader_if_allowed(const pxr::UsdPrim &prim
USDPrimReader *USDStageReader::create_reader(const pxr::UsdPrim &prim)
{
if (is_primitive_prim(prim)) {
return new USDShapeReader(prim, params_, settings_);
}
if (prim.IsA<pxr::UsdGeomCamera>()) {
return new USDCameraReader(prim, params_, settings_);
}

View File

@ -100,6 +100,12 @@ class USDStageReader {
* toggled off.
*/
bool include_by_purpose(const pxr::UsdGeomImageable &imageable) const;
/*
* Returns true if the specified UsdPrim is a UsdGeom primitive,
* procedural shape, such as UsdGeomCube.
*/
bool is_primitive_prim(const pxr::UsdPrim &prim) const;
};
}; // namespace blender::io::usd

View File

@ -66,6 +66,7 @@ struct USDImportParams {
bool import_materials;
bool import_meshes;
bool import_volumes;
bool import_shapes;
char prim_path_mask[1024];
bool import_subdiv;
bool import_instance_proxies;