Skip to content

Commit

Permalink
Merge pull request #1027 from mattyjams/pr/ufe_path_handling_and_util…
Browse files Browse the repository at this point in the history
…ities_for_point_instances

Add UFE path handling and utilities to support paths that identify point instances of a PointInstancer
  • Loading branch information
Krystian Ligenza authored Jan 21, 2021
2 parents 07771c9 + e9de85a commit 8510159
Show file tree
Hide file tree
Showing 17 changed files with 1,634 additions and 30 deletions.
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));
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();
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

0 comments on commit 8510159

Please sign in to comment.