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
11 changed files with 350 additions and 4 deletions
Showing only changes of commit a2466c7be8 - Show all commits

@ -1 +1 @@
Subproject commit 4331c8e76c2f42b9fd903716c333d6cdeaa5cebd
Subproject commit 8d04896a130c12eb440b7b9ac33bf9dc152506b5
CharlesWardlaw marked this conversation as resolved
Review

These commit hashes shouldn't be included in the PR.

These commit hashes shouldn't be included in the PR.

@ -1 +1 @@
Subproject commit b3f0ffc587d197b37eac9a1566d1d24b7bee7d9a
Subproject commit 35d72c2da051a24bf0fc5fe1dbd2fec3f62d95d6

@ -1 +1 @@
Subproject commit 14ab9273409ea0231d08ba6e86fdc73d4e459e99
Subproject commit 65ff08e325d54a58b47fb3219ec7dbf417f20f18

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,256 @@
CharlesWardlaw marked this conversation as resolved
Review

Missing license header

Missing license header
Review

Added.

Added.
#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"
#include "DNA_object_types.h"
#include "DNA_space_types.h" /* for FILE_MAX */
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_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)
{
UNUSED_VARS(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) {
/* FIXME: after 2.80; `mesh->flag` isn't copied by #BKE_mesh_nomain_to_mesh() */
/* read_mesh can be freed by BKE_mesh_nomain_to_mesh(), so get the flag before that happens. */
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.
const uint16_t autosmooth = (read_mesh->flag & ME_AUTOSMOOTH);
BKE_mesh_nomain_to_mesh(read_mesh, mesh, object_);
mesh->flag |= autosmooth;
if (is_time_varying()) {
USDGeomReader::add_cache_modifier();
}
}
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,
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.
pxr::VtIntArray &face_indices,
pxr::VtIntArray &face_counts) const
{
const bool is_capsule = prim_.IsA<pxr::UsdGeomCapsule>();
const bool is_cylinder = prim_.IsA<pxr::UsdGeomCylinder>();
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.
const bool is_cone = prim_.IsA<pxr::UsdGeomCone>();
const bool is_cube = prim_.IsA<pxr::UsdGeomCube>();
const bool is_sphere = prim_.IsA<pxr::UsdGeomSphere>();
if (is_capsule) {
read_values<pxr::UsdImagingCapsuleAdapter>(motionSampleTime, positions, face_indices, face_counts);
return true;
}
if (is_cylinder) {
read_values<pxr::UsdImagingCylinderAdapter>(motionSampleTime, positions, face_indices, face_counts);
return true;
}
if (is_cone) {
read_values<pxr::UsdImagingConeAdapter>(motionSampleTime, positions, face_indices, face_counts);
return true;
}
if (is_cube) {
read_values<pxr::UsdImagingCubeAdapter>(motionSampleTime, positions, face_indices, face_counts);
return true;
}
if (is_sphere) {
read_values<pxr::UsdImagingSphereAdapter>(motionSampleTime, positions, face_indices, face_counts);
return true;
}
WM_reportf(RPT_ERROR,
Review

struct Mesh -> Mesh

`struct Mesh` -> `Mesh`
"Unhandled Gprim type: %s (%s)",
prim_.GetTypeName().GetText(),
prim_.GetPath().GetText());
return false;
}
struct Mesh *USDShapeReader::read_mesh(struct Mesh *existing_mesh,
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.
double motionSampleTime,
int read_flag,
const char **err_str)
{
pxr::VtIntArray face_indices;
pxr::VtIntArray face_counts;
if (!prim_) {
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; ```
return existing_mesh;
}
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.
UNUSED_VARS(read_flag);
CharlesWardlaw marked this conversation as resolved
Review

Same here with the unused arguments

Same here with the unused arguments
Review

Changed.

Changed.
UNUSED_VARS(err_str);
/* 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);
MutableSpan<MPoly> polys = active_mesh->polys_for_write();
MutableSpan<MLoop> loops = active_mesh->loops_for_write();
int loop_index = 0;
const char should_smooth = prim_.IsA<pxr::UsdGeomCube>() ? 0 : ME_SMOOTH;
if (active_mesh != existing_mesh) {
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);
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.
BKE_mesh_normals_tag_dirty(active_mesh);
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.
return active_mesh;
}
Mesh *USDShapeReader::mesh_from_prim(Mesh *existing_mesh, double motionSampleTime,
CharlesWardlaw marked this conversation as resolved
Review

Clang format!

Clang format!
Review

Reformatted file.

Reformatted file.
pxr::VtIntArray& face_indices, pxr::VtIntArray& face_counts) const
{
pxr::VtVec3fArray positions;
Mesh *active_mesh = existing_mesh;
if (!read_mesh_values(motionSampleTime, positions, face_indices, face_counts)) {
return active_mesh;
}
const bool poly_counts_match = existing_mesh ? face_counts.size() == existing_mesh->totpoly : false;
const bool position_counts_match = existing_mesh ? positions.size() == existing_mesh->totvert : false;
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());
}
MutableSpan<float3> verts = active_mesh->vert_positions_for_write();
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.
for (int i = 0; i < positions.size(); i++) {
verts[i][0] = positions[i][0];
verts[i][1] = positions[i][1];
verts[i][2] = positions[i][2];
}
return active_mesh;
}
bool USDShapeReader::is_time_varying()
{
const bool is_capsule = prim_.IsA<pxr::UsdGeomCapsule>();
const bool is_cylinder = prim_.IsA<pxr::UsdGeomCylinder>();
const bool is_cone = prim_.IsA<pxr::UsdGeomCone>();
const bool is_cube = prim_.IsA<pxr::UsdGeomCube>();
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.
const bool is_sphere = prim_.IsA<pxr::UsdGeomSphere>();
if (is_capsule) {
pxr::UsdGeomCapsule geom(prim_);
return (geom.GetAxisAttr().ValueMightBeTimeVarying() ||
geom.GetHeightAttr().ValueMightBeTimeVarying() ||
geom.GetRadiusAttr().ValueMightBeTimeVarying());
}
if (is_cylinder) {
pxr::UsdGeomCylinder geom(prim_);
return (geom.GetAxisAttr().ValueMightBeTimeVarying() ||
geom.GetHeightAttr().ValueMightBeTimeVarying() ||
geom.GetRadiusAttr().ValueMightBeTimeVarying());
}
if (is_cone) {
pxr::UsdGeomCone geom(prim_);
return (geom.GetAxisAttr().ValueMightBeTimeVarying() ||
geom.GetHeightAttr().ValueMightBeTimeVarying() ||
geom.GetRadiusAttr().ValueMightBeTimeVarying());
}
if (is_cube) {
pxr::UsdGeomCube geom(prim_);
return geom.GetSizeAttr().ValueMightBeTimeVarying();
}
if (is_sphere) {
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,58 @@
CharlesWardlaw marked this conversation as resolved
Review

Missing license header

Missing license header
Review

Added.

Added.
#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,
* also returning face_indices and counts for further loop processing. */
Mesh *mesh_from_prim(Mesh *existing_mesh, double motionSampleTime,
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.
pxr::VtIntArray& face_indices, pxr::VtIntArray& face_counts) const;
public:
USDShapeReader(const pxr::UsdPrim &prim,
const USDImportParams &import_params,
const ImportSettings &settings);
void create_object(Main *bmain, double motionSampleTime) override;
void read_object_data(Main *bmain, double motionSampleTime) override;
struct Mesh *read_mesh(struct Mesh *existing_mesh,
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.
double motionSampleTime,
int read_flag,
const char **err_str) override;
bool is_time_varying();
CharlesWardlaw marked this conversation as resolved
Review

struct Mesh -> Mesh

`struct Mesh` -> `Mesh`
virtual bool topology_changed(const Mesh * /* existing_mesh */, double /* motionSampleTime */)
CharlesWardlaw marked this conversation as resolved
Review

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

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

Changed.

Changed.
{
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;

@ -1 +1 @@
Subproject commit e133fc08cd3254bb3d3bd1345028c8486700bca4
Subproject commit d50df97812e481c36ccae965e8fa3101ab8ab320