Skip to content
2 changes: 2 additions & 0 deletions lib/mayaUsd/utils/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ target_sources(${PROJECT_NAME}
targetLayer.cpp
traverseLayer.cpp
undoHelperCommand.cpp
utilComponentCreator.cpp
util.cpp
utilDictionary.cpp
utilFileSystem.cpp
Expand Down Expand Up @@ -65,6 +66,7 @@ set(HEADERS
traverseLayer.h
trieVisitor.h
undoHelperCommand.h
utilComponentCreator.h
util.h
utilDictionary.h
utilFileSystem.h
Expand Down
124 changes: 124 additions & 0 deletions lib/mayaUsd/utils/utilComponentCreator.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//
// Copyright 2025 Autodesk
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#include <mayaUsd/utils/utilComponentCreator.h>

#include <pxr/base/tf/diagnostic.h>
#include <pxr/pxr.h>

#include <maya/MGlobal.h>
#include <maya/MString.h>

#include <vector>

PXR_NAMESPACE_USING_DIRECTIVE

namespace MAYAUSD_NS_DEF {
namespace ComponentUtils {

std::vector<std::string> getAdskUsdComponentLayersToSave(const std::string& proxyPath)
{
// Ask via python what layers need to be saved for the component.
// With the maya api we can only return a string, so we concat the ids.
MString getLayersFromComponent;
getLayersFromComponent.format(
"def usd_component_creator_get_layers_to_save():\n"
" import mayaUsd\n"
" import mayaUsd.ufe\n"
" from usd_component_creator_plugin import MayaComponentManager\n"
" stage = mayaUsd.ufe.getStage(\"^1s\")\n"
" if stage is None:\n"
" return \"\"\n"
" ids = MayaComponentManager.GetInstance().GetSaveInfo(stage)\n"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can this fail? if so maybe we need to add validation on the evaluation and reporting that error occured.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(answer offline) the only thing that can fail is if the CC isnt present or the path isnt a component path - but this function documents expecting a valid component path - and this is made sure of before calling into this one. So any such error would be reported from "above"

" result = \"\"\n"
" first = True\n"
" for id in ids:\n"
" if not first:\n"
" result += \"|\"\n"
" result += id\n"
" first = False\n"
" return result\n",
proxyPath.c_str());

if (MGlobal::executePythonCommand(getLayersFromComponent)) {
auto resultString = MGlobal::executePythonCommandStringResult(
"usd_component_creator_get_layers_to_save()");
MStringArray layerIds;
resultString.split('|', layerIds);
std::vector<std::string> toSave;
for (const auto& id : layerIds) {
toSave.push_back(id.asUTF8());
}
return toSave;
}
return {};
}

bool isAdskUsdComponent(const std::string& proxyShapePath)
{
MString defineIsComponentCmd;
defineIsComponentCmd.format(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for now, but I think we should eventually refactor all those Python functions into a "helper" sub-module of usd_component_creator and avoid redefining and reparsing these functions all the time. (Merged with Anthon's)

"def usd_component_creator_is_proxy_shape_a_component():\n"
" from pxr import Sdf, Usd, UsdUtils\n"
" import mayaUsd\n"
" import mayaUsd.ufe\n"
" try:\n"
" from AdskUsdComponentCreator import ComponentDescription\n"
" except ImportError:\n"
" return -1\n"
" proxyStage = mayaUsd.ufe.getStage(\"^1s\")\n"
" component_description = ComponentDescription.CreateFromStageMetadata(proxyStage)\n"
" if component_description:\n"
" return 1\n"
" else:\n"
" return 0",
proxyShapePath.c_str());

int isStageAComponent = 0;
MStatus success;
if (MS::kSuccess
== (success = MGlobal::executePythonCommand(defineIsComponentCmd, false, false))) {
MString runIsComponentCmd = "usd_component_creator_is_proxy_shape_a_component()";
success = MGlobal::executePythonCommand(runIsComponentCmd, isStageAComponent);
}

if (success != MS::kSuccess) {
TF_RUNTIME_ERROR(
"Error occurred when testing stage '%s' for component.", proxyShapePath.c_str());
}

return isStageAComponent == 1;
}

void saveAdskUsdComponent(const std::string& proxyPath)
{
MString saveComponent;
saveComponent.format(
"from pxr import Sdf, Usd, UsdUtils\n"
"import mayaUsd\n"
"import mayaUsd.ufe\n"
"from usd_component_creator_plugin import MayaComponentManager\n"
"proxyStage = mayaUsd.ufe.getStage(\"^1s\")\n"
"MayaComponentManager.GetInstance().SaveComponent(proxyStage)",
proxyPath.c_str());

if (!MGlobal::executePythonCommand(saveComponent)) {
TF_RUNTIME_ERROR("Error while saving USD component '%s'", proxyPath.c_str());
}
}

} // namespace ComponentUtils
} // namespace MAYAUSD_NS_DEF
52 changes: 52 additions & 0 deletions lib/mayaUsd/utils/utilComponentCreator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// Copyright 2025 Autodesk
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#ifndef MAYAUSD_UTILS_UTILCOMPONENTCREATOR_H
#define MAYAUSD_UTILS_UTILCOMPONENTCREATOR_H

#include "mayaUsd/mayaUsd.h"

#include <mayaUsd/base/api.h>

#include <string>
#include <vector>

namespace MAYAUSD_NS_DEF {
namespace ComponentUtils {

/*! \brief Returns whether the proxy shape at the given path identifies an Autodesk USD Component.
*/
MAYAUSD_CORE_PUBLIC
bool isAdskUsdComponent(const std::string& proxyPath);

/*! \brief Returns the ids of the USD layers that should be saved for the Autodesk USD Component.
*
* \note Expects \p proxyPath to be a valid component path.
*/
MAYAUSD_CORE_PUBLIC
std::vector<std::string> getAdskUsdComponentLayersToSave(const std::string& proxyPath);

/*! \brief Saves the Autodesk USD Component identified by \p proxyPath.
*
* \note Expects \p proxyPath to be a valid component path.
*/
MAYAUSD_CORE_PUBLIC
void saveAdskUsdComponent(const std::string& proxyPath);

} // namespace ComponentUtils
} // namespace MAYAUSD_NS_DEF

#endif // MAYAUSD_UTILS_UTILCOMPONENTCREATOR_H
17 changes: 17 additions & 0 deletions lib/mayaUsd/utils/utilSerialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
#include <pxr/usd/sdf/usdaFileFormat.h>
#include <pxr/usd/sdf/usdcFileFormat.h>
#endif
#include "utilComponentCreator.h"

#include <pxr/usd/usdGeom/tokens.h>

#include <maya/MGlobal.h>
Expand Down Expand Up @@ -621,6 +623,21 @@ void getLayersToSaveFromProxy(const std::string& proxyPath, StageLayersToSave& l
return;
}

// Special case for components created by the component creator. Non-local layers,
// non-active layers, and non-dirty but to be renamed layers, can be impacted when saving a
// component. Only the component creator knows how to save a component properly.
if (ComponentUtils::isAdskUsdComponent(proxyPath)) {
const auto layerIds = ComponentUtils::getAdskUsdComponentLayersToSave(proxyPath);
for (const auto& ccLayerId : layerIds) {
auto ccLayer = pxr::SdfLayer::FindOrOpen(ccLayerId);
if (!ccLayer || ccLayer->IsAnonymous()) {
continue;
}
layersInfo._dirtyFileBackedLayers.push_back(ccLayer);
}
return;
}

auto root = stage->GetRootLayer();
populateChildren(
proxyPath, stage, root, nullptr, layersInfo._anonLayers, layersInfo._dirtyFileBackedLayers);
Expand Down
101 changes: 44 additions & 57 deletions lib/usd/ui/layerEditor/layerTreeModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <mayaUsd/utils/customLayerData.h>
#include <mayaUsd/utils/layers.h>
#include <mayaUsd/utils/util.h>
#include <mayaUsd/utils/utilComponentCreator.h>
#include <mayaUsd/utils/utilSerialization.h>

#include <pxr/base/tf/notice.h>
Expand Down Expand Up @@ -70,46 +71,14 @@ bool shouldDisplayComponentInitialSaveDialog(
const UsdStageRefPtr stage,
const std::string& proxyShapePath)
{
MString defineIsComponentCmd;
defineIsComponentCmd.format(
"def usd_component_creator_is_proxy_shape_a_component():\n"
" from pxr import Sdf, Usd, UsdUtils\n"
" import mayaUsd\n"
" import mayaUsd.ufe\n"
" try:\n"
" from AdskUsdComponentCreator import ComponentDescription\n"
" except ImportError:\n"
" return -1\n"
" proxyStage = mayaUsd.ufe.getStage(\"^1s\")\n"
" component_description = ComponentDescription.CreateFromStageMetadata(proxyStage)\n"
" if component_description:\n"
" return 1\n"
" else:\n"
" return 0",
proxyShapePath.c_str());

int isStageAComponent = 0;
MStatus success;
if (MS::kSuccess
== (success = MGlobal::executePythonCommand(defineIsComponentCmd, false, false))) {
MString runIsComponentCmd = "usd_component_creator_is_proxy_shape_a_component()";
success = MGlobal::executePythonCommand(runIsComponentCmd, isStageAComponent);
}

if (success != MS::kSuccess) {
TF_RUNTIME_ERROR(
"Error occurred when testing stage '%s' for component.", proxyShapePath.c_str());
}

if (isStageAComponent != 1) {
if (!MayaUsd::ComponentUtils::isAdskUsdComponent(proxyShapePath)) {
return false;
}

MString tempDir;
MGlobal::executeCommand("internalVar -userTmpDir", tempDir);

return isStageAComponent == 1
&& isPathInside(UsdMayaUtil::convert(tempDir), stage->GetRootLayer()->GetRealPath());
return isPathInside(UsdMayaUtil::convert(tempDir), stage->GetRootLayer()->GetRealPath());
}

} // namespace
Expand Down Expand Up @@ -314,7 +283,7 @@ void LayerTreeModel::selectUsdLayerOnIdle(const SdfLayerRefPtr& usdLayer)
QTimer::singleShot(0, this, [this, usdLayer]() {
auto item = findUSDLayerItem(usdLayer);
if (item != nullptr) {
auto index = indexFromItem(item);
auto index = indexFromItem(item);
Q_EMIT selectLayerSignal(index);
}
});
Expand Down Expand Up @@ -571,6 +540,16 @@ LayerTreeModel::getAllAnonymousLayers(const LayerTreeItem* item /* = nullptr*/)
void LayerTreeModel::saveStage(QWidget* in_parent)
{
auto saveAllLayers = [this]() {

// Special case for components created by the component creator. Only the component creator
// knows how to save a component properly.
if (MayaUsd::ComponentUtils::isAdskUsdComponent(
_sessionState->stageEntry()._proxyShapePath)) {
MayaUsd::ComponentUtils::saveAdskUsdComponent(
_sessionState->stageEntry()._proxyShapePath);
return;
}

const auto layers = getAllNeedsSavingLayers();
for (auto layer : layers) {
if (!layer->isSystemLocked()) {
Expand Down Expand Up @@ -612,13 +591,19 @@ void LayerTreeModel::saveStage(QWidget* in_parent)
"from pxr import Sdf, Usd, UsdUtils\n"
"import mayaUsd\n"
"import mayaUsd.ufe\n"
"from AdskUsdComponentCreator import ComponentDescription, MoveComponent\n"
"from AdskUsdComponentCreator import ComponentDescription, MoveComponent, TheHost\n"
"from usd_component_creator_plugin import update_variant_editor_window, "
"MayaComponentManager\n"
"from AdskVariantEditor import ComponentData\n"
"def usd_component_creator_move_component():\n"
" proxyStage = mayaUsd.ufe.getStage(\"^1s\")\n"
" MayaComponentManager.GetInstance().SaveComponent(proxyStage)\n"
" component_description = "
" ComponentDescription.CreateFromStageMetadata(proxyStage)\n"
" moved_comp = MoveComponent(component_description, \"^2s\", \"^3s\", True, "
"False)\n"
" update_variant_editor_window(ComponentData(moved_comp[0]), "
"TheHost.GetHost())\n"
" return moved_comp[0].root_layer_filename",
_sessionState->stageEntry()._proxyShapePath.c_str(),
saveLocation.c_str(),
Expand All @@ -632,27 +617,29 @@ void LayerTreeModel::saveStage(QWidget* in_parent)
auto newRootLayer
= SdfLayer::FindOrOpen(UsdMayaUtil::convert(movedStageRootFilepath));

MayaUsd::utils::setNewProxyPath(
MString(_sessionState->stageEntry()._proxyShapePath.c_str()),
movedStageRootFilepath,
MayaUsd::utils::ProxyPathMode::kProxyPathAbsolute,
newRootLayer,
true);

MayaUsd::lockLayer(
_sessionState->stageEntry()._proxyShapePath,
newRootLayer,
MayaUsd::LayerLockType::LayerLock_Locked,
true);

// Rename Proxy Shape
MObject proxyNode;
UsdMayaUtil::GetMObjectByName(
_sessionState->stageEntry()._proxyShapePath, proxyNode);
MDagModifier dagMod;
MStatus status = dagMod.renameNode(proxyNode, componentName.c_str());
if (status == MStatus::kSuccess) {
dagMod.doIt();
if (newRootLayer) {
MayaUsd::utils::setNewProxyPath(
MString(_sessionState->stageEntry()._proxyShapePath.c_str()),
movedStageRootFilepath,
MayaUsd::utils::ProxyPathMode::kProxyPathAbsolute,
newRootLayer,
true);

MayaUsd::lockLayer(
_sessionState->stageEntry()._proxyShapePath,
newRootLayer,
MayaUsd::LayerLockType::LayerLock_Locked,
true);

// Rename Proxy Shape
MObject proxyNode;
UsdMayaUtil::GetMObjectByName(
_sessionState->stageEntry()._proxyShapePath, proxyNode);
MDagModifier dagMod;
MStatus status = dagMod.renameNode(proxyNode, componentName.c_str());
if (status == MStatus::kSuccess) {
dagMod.doIt();
}
}
}
}
Expand Down