diff --git a/lib/mayaUsd/nodes/proxyShapeBase.cpp b/lib/mayaUsd/nodes/proxyShapeBase.cpp index 9f41e27a43..f811ba9c5c 100644 --- a/lib/mayaUsd/nodes/proxyShapeBase.cpp +++ b/lib/mayaUsd/nodes/proxyShapeBase.cpp @@ -38,13 +38,17 @@ #include #include #include +#include #include #include +#include #include #include #include #include #include +#include +#include #include #include #include @@ -74,6 +78,7 @@ #include #include #include +#include #include #include #include @@ -134,6 +139,18 @@ MObject MayaUsdProxyShapeBase::outTimeAttr; MObject MayaUsdProxyShapeBase::outStageDataAttr; MObject MayaUsdProxyShapeBase::outStageCacheIdAttr; +namespace { +//! Profiler category for proxy accessor events +const int _shapeBaseProfilerCategory = MProfiler::addCategory( +#if MAYA_API_VERSION >= 20190000 + "ProxyShapeBase", + "ProxyShapeBase events" +#else + "ProxyShapeBase" +#endif +); +} // namespace + /* static */ void* MayaUsdProxyShapeBase::creator() { return new MayaUsdProxyShapeBase(); } @@ -773,6 +790,9 @@ MBoundingBox MayaUsdProxyShapeBase::boundingBox() const { TRACE_FUNCTION(); + MProfilingScope profilingScope( + _shapeBaseProfilerCategory, MProfiler::kColorB_L1, "Get shape BoundingBox"); + MStatus status; // Make sure outStage is up to date @@ -1186,7 +1206,73 @@ void MayaUsdProxyShapeBase::_OnStageContentsChanged(const UsdNotice::StageConten void MayaUsdProxyShapeBase::_OnStageObjectsChanged(const UsdNotice::ObjectsChanged& notice) { + MProfilingScope profilingScope( + _shapeBaseProfilerCategory, MProfiler::kColorB_L1, "Process USD objects changed"); + ProxyAccessor::stageChanged(_usdAccessor, thisMObject(), notice); + + // Recompute the extents of any UsdGeomBoundable that has authored extents + const auto& stage = notice.GetStage(); + if (stage != getUsdStage()) { + TF_CODING_ERROR("We shouldn't be receiving notification for other stages than one " + "returned by stage provider"); + return; + } + + for (const auto& changedPath : notice.GetChangedInfoOnlyPaths()) { + if (!changedPath.IsPrimPropertyPath()) { + continue; + } + + const TfToken& changedPropertyToken = changedPath.GetNameToken(); + if (changedPropertyToken == UsdGeomTokens->extent) { + continue; + } + + SdfPath changedPrimPath = changedPath.GetPrimPath(); + const UsdPrim& changedPrim = stage->GetPrimAtPath(changedPrimPath); + UsdGeomBoundable boundableObj = UsdGeomBoundable(changedPrim); + if (!boundableObj) { + continue; + } + + // If the attribute is not part of the primitive schema, it does not affect extents +#if USD_VERSION_NUM > 2002 + auto attrDefn + = changedPrim.GetPrimDefinition().GetSchemaAttributeSpec(changedPropertyToken); +#else + auto attrDefn = PXR_NS::UsdSchemaRegistry::GetAttributeDefinition( + changedPrim.GetTypeName(), changedPropertyToken); +#endif + if (!attrDefn) { + continue; + } + + // Ignore all attributes known to GPrim and its base classes as they + // are guaranteed not to affect extents: + static const std::unordered_set ignoredAttributes( + UsdGeomGprim::GetSchemaAttributeNames(true).cbegin(), + UsdGeomGprim::GetSchemaAttributeNames(true).cend()); + if (ignoredAttributes.count(changedPropertyToken) > 0) { + continue; + } + + UsdAttribute extentsAttr = boundableObj.GetExtentAttr(); + if (extentsAttr.GetNumTimeSamples() > 0) { + TF_CODING_ERROR( + "Can not fix animated extents of %s made dirty by a change on %s.", + changedPrimPath.GetString().c_str(), + changedPropertyToken.GetText()); + continue; + } + if (extentsAttr && extentsAttr.HasValue()) { + VtVec3fArray extent(2); + if (UsdGeomBoundable::ComputeExtentFromPlugins( + boundableObj, UsdTimeCode::Default(), &extent)) { + extentsAttr.Set(extent); + } + } + } } bool MayaUsdProxyShapeBase::closestPoint( diff --git a/test/lib/ufe/testObject3d.py b/test/lib/ufe/testObject3d.py index c443bca274..5e88e70b24 100644 --- a/test/lib/ufe/testObject3d.py +++ b/test/lib/ufe/testObject3d.py @@ -415,6 +415,40 @@ def testMayaHideAndShowHiddenUndoCommands(self): self.assertTrue(bool(primSpecCapsule and UsdGeom.Tokens.visibility in primSpecCapsule.attributes)) self.assertTrue(bool(primSpecCylinder and UsdGeom.Tokens.visibility in primSpecCylinder.attributes)) + def testMayaGeomExtentsRecomputation(self): + ''' Verify the automatic extents computation in when geom attributes change ''' + + cmds.file(new=True, force=True) + + # create a Capsule via contextOps menu + import mayaUsd_createStageWithNewLayer + mayaUsd_createStageWithNewLayer.createStageWithNewLayer() + proxyShapePath = ufe.PathString.path('|stage1|stageShape1') + proxyShapeItem = ufe.Hierarchy.createItem(proxyShapePath) + proxyShapeContextOps = ufe.ContextOps.contextOps(proxyShapeItem) + proxyShapeContextOps.doOp(['Add New Prim', 'Capsule']) + + # capsule + capsulePath = ufe.PathString.path('|stage1|stageShape1,/Capsule1') + capsuleItem = ufe.Hierarchy.createItem(capsulePath) + capsulePrim = mayaUsd.ufe.ufePathToPrim(ufe.PathString.string(capsulePath)) + + # get the height and extent "attributes" + capsuleHeightAttr = capsulePrim.GetAttribute('height') + capsuleExtentAttr = capsulePrim.GetAttribute('extent') + + self.assertAlmostEqual(capsuleHeightAttr.Get(), 1.0) + capsuleExtent = capsuleExtentAttr.Get() + self.assertAlmostEqual(capsuleExtent[0][2], -1.0) + self.assertAlmostEqual(capsuleExtent[1][2], 1.0) + + capsuleHeightAttr.Set(10.0) + + self.assertAlmostEqual(capsuleHeightAttr.Get(), 10.0) + # Extent will have been recomputed: + capsuleExtent = capsuleExtentAttr.Get() + self.assertAlmostEqual(capsuleExtent[0][2], -5.5) + self.assertAlmostEqual(capsuleExtent[1][2], 5.5) if __name__ == '__main__': unittest.main(verbosity=2)