Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add UFE path handling and utilities to support paths that identify point instances of a PointInstancer #1027

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions lib/mayaUsd/ufe/UsdHierarchy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,14 @@
#include <pxr/usd/usdGeom/xform.h>

#include <ufe/log.h>
#include <ufe/path.h>
#include <ufe/pathComponent.h>
#include <ufe/scene.h>
#include <ufe/sceneNotification.h>

#include <cassert>
#include <stdexcept>
#include <string>

#ifdef UFE_V2_FEATURES_AVAILABLE
#include <mayaUsd/ufe/UsdUndoCreateGroupCommand.h>
Expand All @@ -43,9 +46,21 @@

namespace {
UsdPrimSiblingRange getUSDFilteredChildren(
const UsdPrim& prim,
const Usd_PrimFlagsPredicate pred = UsdPrimDefaultPredicate)
const MayaUsd::ufe::UsdSceneItem::Ptr usdSceneItem,
const Usd_PrimFlagsPredicate pred = UsdPrimDefaultPredicate)
{
// If the scene item represents a point instance of a PointInstancer prim,
// we consider it child-less. The namespace children of a PointInstancer
// can only be accessed directly through the PointInstancer prim and not
// through one of its point instances. Any authoring that would affect the
// point instance should be done either to the PointInstancer or to the
// prototype that is being instanced.
if (usdSceneItem->isPointInstance()) {
return UsdPrimSiblingRange();
}

const UsdPrim& prim = usdSceneItem->prim();

// We need to be able to traverse down to instance proxies, so turn
// on that part of the predicate, since by default, it is off. Since
// the equivalent of GetChildren is
Expand Down Expand Up @@ -85,11 +100,11 @@ UsdSceneItem::Ptr UsdHierarchy::usdSceneItem() const { return fItem; }

Ufe::SceneItem::Ptr UsdHierarchy::sceneItem() const { return fItem; }

bool UsdHierarchy::hasChildren() const { return !getUSDFilteredChildren(prim()).empty(); }
bool UsdHierarchy::hasChildren() const { return !getUSDFilteredChildren(fItem).empty(); }

Ufe::SceneItemList UsdHierarchy::children() const
{
return createUFEChildList(getUSDFilteredChildren(prim()));
return createUFEChildList(getUSDFilteredChildren(fItem));
}

#ifdef UFE_V2_FEATURES_AVAILABLE
Expand All @@ -103,7 +118,7 @@ Ufe::SceneItemList UsdHierarchy::filteredChildren(const ChildFilter& childFilter
Usd_PrimFlagsPredicate flags = childFilter.front().value
? UsdPrimIsDefined && !UsdPrimIsAbstract
: UsdPrimDefaultPredicate;
return createUFEChildList(getUSDFilteredChildren(prim(), flags));
return createUFEChildList(getUSDFilteredChildren(fItem, flags));
}

UFE_LOG("Unknown child filter");
Expand All @@ -115,6 +130,11 @@ Ufe::SceneItemList UsdHierarchy::filteredChildren(const ChildFilter& childFilter
Ufe::SceneItemList UsdHierarchy::createUFEChildList(const UsdPrimSiblingRange& range) const
{
// Return UFE child list from input USD child list.
// Note that the calls to this function are given a range from
// getUSDFilteredChildren() above, which ensures that when fItem is a
// point instance of a PointInstancer, it will be child-less. As a result,
// we expect to receieve an empty range in that case, and will return an
// empty scene item list as a result.
Ufe::SceneItemList children;
for (const auto& child : range) {
children.emplace_back(UsdSceneItem::create(fItem->path() + child.GetName(), child));
ppt-adsk marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -124,6 +144,11 @@ Ufe::SceneItemList UsdHierarchy::createUFEChildList(const UsdPrimSiblingRange& r

Ufe::SceneItem::Ptr UsdHierarchy::parent() const
{
// We do not have a special case for point instances here. If fItem
// represents a point instance of a PointInstancer, we consider the
// PointInstancer prim to be the "parent" of the point instance, even
// though this isn't really true in the USD sense. This allows pick-walking
// from point instances up to their PointInstancer.
return UsdSceneItem::create(fItem->path().pop(), prim().GetParent());
}

Expand Down
3 changes: 2 additions & 1 deletion lib/mayaUsd/ufe/UsdHierarchyHandler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ Ufe::Hierarchy::Ptr UsdHierarchyHandler::hierarchy(const Ufe::SceneItem::Ptr& it
Ufe::SceneItem::Ptr UsdHierarchyHandler::createItem(const Ufe::Path& path) const
{
const UsdPrim prim = ufePathToPrim(path);
return prim.IsValid() ? UsdSceneItem::create(path, prim) : nullptr;
const int instanceIndex = ufePathToInstanceIndex(path);
return prim.IsValid() ? UsdSceneItem::create(path, prim, instanceIndex) : nullptr;
}

#ifdef UFE_V2_FEATURES_AVAILABLE
Expand Down
8 changes: 5 additions & 3 deletions lib/mayaUsd/ufe/UsdSceneItem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,18 @@
namespace MAYAUSD_NS_DEF {
namespace ufe {

UsdSceneItem::UsdSceneItem(const Ufe::Path& path, const UsdPrim& prim)
UsdSceneItem::UsdSceneItem(const Ufe::Path& path, const UsdPrim& prim, int instanceIndex)
: Ufe::SceneItem(path)
, fPrim(prim)
, _instanceIndex(instanceIndex)
{
}

/*static*/
UsdSceneItem::Ptr UsdSceneItem::create(const Ufe::Path& path, const UsdPrim& prim)
UsdSceneItem::Ptr
UsdSceneItem::create(const Ufe::Path& path, const UsdPrim& prim, int instanceIndex)
{
return std::make_shared<UsdSceneItem>(path, prim);
return std::make_shared<UsdSceneItem>(path, prim, instanceIndex);
}

//------------------------------------------------------------------------------
Expand Down
79 changes: 76 additions & 3 deletions lib/mayaUsd/ufe/UsdSceneItem.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
#include <mayaUsd/base/api.h>

#include <pxr/usd/usd/prim.h>
#include <pxr/usd/usdGeom/pointInstancer.h>
#include <pxr/usdImaging/usdImaging/delegate.h>

#include <ufe/path.h>
#include <ufe/sceneItem.h>

PXR_NAMESPACE_USING_DIRECTIVE
Expand All @@ -27,12 +30,64 @@ namespace MAYAUSD_NS_DEF {
namespace ufe {

//! \brief USD run-time scene item interface
//
// UsdSceneItem implements the Ufe::SceneItem interface for UsdPrims.
// Typically there will be a direct mapping between a location in the UFE scene
// item hierarchy identified by a Ufe::Path and a location in the USD namespace
// identified by an SdfPath and represented by a UsdPrim. UFE and USD are
// consistent in that way such that parent scene items/prims can be found by
// popping components off of the path and child scene items/prims can be found
// by appending components to the path.
//
// The above is also true of USD PointInstancer schema prims
// (UsdGeomPointInstancer). PointInstancer prims are treated the same as any
// other UsdPrim and their definitions of parent and child prims are the same
// as well. PointInstancer prims will often have a single prototypes scope as a
// child under which all of the PointInstancer's prototypes are defined.
//
// The point instances generated by PointInstancer prims, however, are handled
// a bit differently, and there are subtle differences in semantics between USD
// and UFE.
//
// Point instances are generated based on data members of a PointInstancer prim
// and a "prototype" prim (or a hierarchy of prims below a prototype prim) that
// is being instanced. As a result, point instances themselves do not occupy a
// location in USD namespace and are instead uniquely identified by the
// combination of the PointInstancer that generated the point instance and an
// instance index used to access per-instance values in the PointInstancer's
// array attributes. See additional detail on PointInstancer prims here:
//
// https://graphics.pixar.com/usd/docs/api/class_usd_geom_point_instancer.html
//
// This means that in a USD namespace sense, a point instance does not really
// have either a parent or children. Similarly, the point instances generated
// by a PointInstancer prim would not be considered the PointInstancer's
// children, nor would the PointInstancer prim be considered the parent of any
// point instances that it generates.
//
// In UFE, however, there is utility in treating a PointInstancer as the parent
// of the point instances it generates. For example, this is used to enable
// pick-walking from a point instance up to the PointInstancer that generated
// it, and then further up the scene hierarchy if desired. As in USD though,
// point instances in UFE still do not have any children. This handling in UFE
// ensures that individual point instances can be selected and have their
// transformation manipulated (which is authored as edits to the positions,
// orientations, scales, etc. attributes on the PointInstancer prim), but no
// other per-instance data access or manipulation is allowed, since no such
// authoring is possible in USD. Any authoring intended to affect point
// instances must be done either to the PointInstancer prim that generates that
// point instance, or to the prim (or hierarchy of prims) representing the
// prototype being instanced.
//
class MAYAUSD_CORE_PUBLIC UsdSceneItem : public Ufe::SceneItem
{
public:
typedef std::shared_ptr<UsdSceneItem> Ptr;

UsdSceneItem(const Ufe::Path& path, const UsdPrim& prim);
UsdSceneItem(
const Ufe::Path& path,
const UsdPrim& prim,
int instanceIndex = UsdImagingDelegate::ALL_INSTANCES);
~UsdSceneItem() override = default;

// Delete the copy/move constructors assignment operators.
Expand All @@ -42,18 +97,36 @@ class MAYAUSD_CORE_PUBLIC UsdSceneItem : public Ufe::SceneItem
UsdSceneItem& operator=(UsdSceneItem&&) = delete;

//! Create a UsdSceneItem from a UFE path and a USD prim.
static UsdSceneItem::Ptr create(const Ufe::Path& path, const UsdPrim& prim);
//
// A non-negative instanceIndex should be provided if the scene item is
// intended to represent an individual instance of a PointInstancer.
static UsdSceneItem::Ptr create(
const Ufe::Path& path,
const UsdPrim& prim,
int instanceIndex = UsdImagingDelegate::ALL_INSTANCES);

const UsdPrim& prim() const { return fPrim; }

int instanceIndex() const { return _instanceIndex; }

//! Returns true if the UsdSceneItem represents a point instance.
//
// The scene item represents a point instance if its prim is a
// PointInstancer and its instanceIndex is non-negative.
bool isPointInstance() const
{
return (fPrim && fPrim.IsA<UsdGeomPointInstancer>() && _instanceIndex >= 0);
}

// Ufe::SceneItem overrides
std::string nodeType() const override;
#ifdef UFE_V2_FEATURES_AVAILABLE
std::vector<std::string> ancestorNodeTypes() const override;
#endif

private:
UsdPrim fPrim;
UsdPrim fPrim;
const int _instanceIndex;
}; // UsdSceneItem

} // namespace ufe
Expand Down
98 changes: 95 additions & 3 deletions lib/mayaUsd/ufe/Utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,28 @@
#include "private/Utils.h"

#include <mayaUsd/nodes/proxyShapeBase.h>
#include <mayaUsd/ufe/Global.h>
#include <mayaUsd/ufe/ProxyShapeHandler.h>
#include <mayaUsd/ufe/UsdStageMap.h>
#include <mayaUsd/utils/util.h>

#include <pxr/base/tf/hashset.h>
#include <pxr/base/tf/stringUtils.h>
#include <pxr/usd/sdf/path.h>
#include <pxr/usd/sdf/tokens.h>
#include <pxr/usd/usd/prim.h>
#include <pxr/usd/usd/stage.h>
#include <pxr/usd/usdGeom/pointInstancer.h>
#include <pxr/usdImaging/usdImaging/delegate.h>

#include <maya/MFnDependencyNode.h>
#include <maya/MGlobal.h>
#include <maya/MObjectHandle.h>
#include <ufe/pathSegment.h>
#include <ufe/rtid.h>

#include <cassert>
#include <cctype>
#include <memory>
#include <regex>
#include <stdexcept>
Expand All @@ -51,9 +60,25 @@ template <> struct iterator_traits<MStringArray::Iterator>
#endif

namespace {

constexpr auto kIllegalUSDPath = "Illegal USD run-time path %s.";

bool stringBeginsWithDigit(const std::string& inputString)
{
if (inputString.empty()) {
return false;
}

const char& firstChar = inputString.front();
if (std::isdigit(static_cast<unsigned char>(firstChar))) {
return true;
}

return false;
}

} // anonymous namespace

namespace MAYAUSD_NS_DEF {
namespace ufe {

Expand All @@ -76,25 +101,92 @@ UsdStageWeakPtr getStage(const Ufe::Path& path) { return g_StageMap.stage(path);

Ufe::Path stagePath(UsdStageWeakPtr stage) { return g_StageMap.path(stage); }

Ufe::PathSegment usdPathToUfePathSegment(const SdfPath& usdPath, int instanceIndex)
{
const Ufe::Rtid usdRuntimeId = getUsdRunTimeId();
static const char separator = SdfPathTokens->childDelimiter.GetText()[0u];

if (usdPath.IsEmpty()) {
// Return an empty segment.
return Ufe::PathSegment(Ufe::PathSegment::Components(), usdRuntimeId, separator);
}

std::string pathString = usdPath.GetString();

if (instanceIndex >= 0) {
// Note here that we're taking advantage of the fact that identifiers
// in SdfPaths must be C/Python identifiers; that is, they must *not*
// begin with a digit. This means that when we see a path component at
// the end of a USD path segment that does begin with a digit, we can
// be sure that it represents an instance index and not a prim or other
// USD entity.
pathString += TfStringPrintf("%c%d", separator, instanceIndex);
}

return Ufe::PathSegment(pathString, usdRuntimeId, separator);
}

Ufe::Path stripInstanceIndexFromUfePath(const Ufe::Path& path)
{
if (path.empty()) {
return path;
}

// As with usdPathToUfePathSegment() above, we're taking advantage of the
// fact that identifiers in SdfPaths must be C/Python identifiers; that is,
// they must *not* begin with a digit. This means that when we see a path
// component at the end of a USD path segment that does begin with a digit,
// we can be sure that it represents an instance index and not a prim or
// other USD entity.
if (stringBeginsWithDigit(path.back().string())) {
return path.pop();
}

return path;
}

UsdPrim ufePathToPrim(const Ufe::Path& path)
{
const Ufe::Path ufePrimPath = stripInstanceIndexFromUfePath(path);

// Assume that there are only two segments in the path, the first a Maya
// Dag path segment to the proxy shape, which identifies the stage, and
// the second the USD segment.
// When called we do not make any assumption on whether or not the
// input path is valid.
const Ufe::Path::Segments& segments = path.getSegments();
if (!TF_VERIFY(segments.size() == 2, kIllegalUSDPath, path.string().c_str())) {
const Ufe::Path::Segments& segments = ufePrimPath.getSegments();
if (!TF_VERIFY(segments.size() == 2u, kIllegalUSDPath, path.string().c_str())) {
return UsdPrim();
}

UsdPrim prim;
if (auto stage = getStage(Ufe::Path(segments[0]))) {
prim = stage->GetPrimAtPath(SdfPath(segments[1].string()));
const SdfPath usdPath = SdfPath(segments[1].string());
prim = stage->GetPrimAtPath(usdPath.GetPrimPath());
}
return prim;
}

int ufePathToInstanceIndex(const Ufe::Path& path)
{
int instanceIndex = UsdImagingDelegate::ALL_INSTANCES;

const UsdPrim usdPrim = ufePathToPrim(path);
if (!usdPrim || !usdPrim.IsA<UsdGeomPointInstancer>()) {
return instanceIndex;
}

// Once more as above in usdPathToUfePathSegment() and
// stripInstanceIndexFromUfePath(), a path component at the tail of the
// path that begins with a digit is assumed to represent an instance index.
const std::string& tailComponentString = path.back().string();
mattyjams marked this conversation as resolved.
Show resolved Hide resolved
if (stringBeginsWithDigit(path.back().string())) {
instanceIndex = std::stoi(tailComponentString);
}

return instanceIndex;
}

bool isRootChild(const Ufe::Path& path)
{
// When called we make the assumption that we are given a valid
Expand Down
Loading