Skip to content

Commit c43de2b

Browse files
authored
Merge pull request #4386 from Autodesk/deboisj/comp_mgr
Integrate the component manager
2 parents 6953003 + df85d8a commit c43de2b

File tree

6 files changed

+339
-135
lines changed

6 files changed

+339
-135
lines changed

lib/mayaUsd/utils/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ target_sources(${PROJECT_NAME}
2828
targetLayer.cpp
2929
traverseLayer.cpp
3030
undoHelperCommand.cpp
31+
utilComponentCreator.cpp
3132
util.cpp
3233
utilDictionary.cpp
3334
utilFileSystem.cpp
@@ -65,6 +66,7 @@ set(HEADERS
6566
traverseLayer.h
6667
trieVisitor.h
6768
undoHelperCommand.h
69+
utilComponentCreator.h
6870
util.h
6971
utilDictionary.h
7072
utilFileSystem.h
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
//
2+
// Copyright 2025 Autodesk
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
#include <mayaUsd/utils/utilComponentCreator.h>
18+
19+
#include <pxr/base/tf/diagnostic.h>
20+
#include <pxr/pxr.h>
21+
22+
#include <maya/MGlobal.h>
23+
#include <maya/MString.h>
24+
25+
#include <vector>
26+
27+
PXR_NAMESPACE_USING_DIRECTIVE
28+
29+
namespace MAYAUSD_NS_DEF {
30+
namespace ComponentUtils {
31+
32+
std::vector<std::string> getAdskUsdComponentLayersToSave(const std::string& proxyPath)
33+
{
34+
// Ask via python what layers need to be saved for the component.
35+
// With the maya api we can only return a string, so we concat the ids.
36+
MString getLayersFromComponent;
37+
getLayersFromComponent.format(
38+
"def usd_component_creator_get_layers_to_save():\n"
39+
" import mayaUsd\n"
40+
" import mayaUsd.ufe\n"
41+
" from usd_component_creator_plugin import MayaComponentManager\n"
42+
" stage = mayaUsd.ufe.getStage('^1s')\n"
43+
" if stage is None:\n"
44+
" return ''\n"
45+
" ids = MayaComponentManager.GetInstance().GetSaveInfo(stage)\n"
46+
" result = ''\n"
47+
" first = True\n"
48+
" for id in ids:\n"
49+
" if not first:\n"
50+
" result += '\\n'\n"
51+
" result += id\n"
52+
" first = False\n"
53+
" return result\n",
54+
proxyPath.c_str());
55+
56+
if (MGlobal::executePythonCommand(getLayersFromComponent)) {
57+
auto resultString = MGlobal::executePythonCommandStringResult(
58+
"usd_component_creator_get_layers_to_save()");
59+
MStringArray layerIds;
60+
resultString.split('\n', layerIds);
61+
std::vector<std::string> toSave;
62+
for (const auto& id : layerIds) {
63+
toSave.push_back(id.asUTF8());
64+
}
65+
return toSave;
66+
}
67+
return {};
68+
}
69+
70+
bool isAdskUsdComponent(const std::string& proxyShapePath)
71+
{
72+
MString defineIsComponentCmd;
73+
defineIsComponentCmd.format(
74+
"def usd_component_creator_is_proxy_shape_a_component():\n"
75+
" from pxr import Sdf, Usd, UsdUtils\n"
76+
" import mayaUsd\n"
77+
" import mayaUsd.ufe\n"
78+
" try:\n"
79+
" from AdskUsdComponentCreator import ComponentDescription\n"
80+
" except ImportError:\n"
81+
" return -1\n"
82+
" proxyStage = mayaUsd.ufe.getStage('^1s')\n"
83+
" component_description = ComponentDescription.CreateFromStageMetadata(proxyStage)\n"
84+
" if component_description:\n"
85+
" return 1\n"
86+
" else:\n"
87+
" return 0",
88+
proxyShapePath.c_str());
89+
90+
int isStageAComponent = 0;
91+
MStatus success;
92+
if (MS::kSuccess
93+
== (success = MGlobal::executePythonCommand(defineIsComponentCmd, false, false))) {
94+
MString runIsComponentCmd = "usd_component_creator_is_proxy_shape_a_component()";
95+
success = MGlobal::executePythonCommand(runIsComponentCmd, isStageAComponent);
96+
}
97+
98+
if (success != MS::kSuccess) {
99+
TF_RUNTIME_ERROR(
100+
"Error occurred when testing stage '%s' for component.", proxyShapePath.c_str());
101+
}
102+
103+
return isStageAComponent == 1;
104+
}
105+
106+
void saveAdskUsdComponent(const std::string& proxyPath)
107+
{
108+
MString saveComponent;
109+
saveComponent.format(
110+
"from pxr import Sdf, Usd, UsdUtils\n"
111+
"import mayaUsd\n"
112+
"import mayaUsd.ufe\n"
113+
"from usd_component_creator_plugin import MayaComponentManager\n"
114+
"proxyStage = mayaUsd.ufe.getStage('^1s')\n"
115+
"MayaComponentManager.GetInstance().SaveComponent(proxyStage)",
116+
proxyPath.c_str());
117+
118+
if (!MGlobal::executePythonCommand(saveComponent)) {
119+
TF_RUNTIME_ERROR("Error while saving USD component '%s'", proxyPath.c_str());
120+
}
121+
}
122+
123+
std::string previewSaveAdskUsdComponent(
124+
const std::string& saveLocation,
125+
const std::string& componentName,
126+
const std::string& proxyPath)
127+
{
128+
MString defMoveComponentPreviewCmd;
129+
defMoveComponentPreviewCmd.format(
130+
"def usd_component_creator_move_component_preview():\n"
131+
" import json\n"
132+
" from pxr import Sdf, Usd, UsdUtils\n"
133+
" import mayaUsd\n"
134+
" import mayaUsd.ufe\n"
135+
" try:\n"
136+
" from AdskUsdComponentCreator import ComponentDescription, "
137+
"PreviewMoveComponentHierarchy\n"
138+
" except ImportError:\n"
139+
" return None\n"
140+
" proxyStage = mayaUsd.ufe.getStage('^1s')\n"
141+
" component_description = "
142+
" ComponentDescription.CreateFromStageMetadata(proxyStage)\n"
143+
" if component_description:\n"
144+
" move_comp_preview = PreviewMoveComponentHierarchy(component_description, '^2s', "
145+
"'^3s')\n"
146+
" return json.dumps(move_comp_preview)\n"
147+
" else:\n"
148+
" return \"\"",
149+
proxyPath.c_str(),
150+
saveLocation.c_str(),
151+
componentName.c_str());
152+
153+
if (MS::kSuccess == MGlobal::executePythonCommand(defMoveComponentPreviewCmd)) {
154+
MString result;
155+
MString runComponentMovePreviewCmd = "usd_component_creator_move_component_preview()";
156+
if (MS::kSuccess == MGlobal::executePythonCommand(runComponentMovePreviewCmd, result)) {
157+
return result.asChar();
158+
}
159+
}
160+
return {};
161+
}
162+
163+
} // namespace ComponentUtils
164+
} // namespace MAYAUSD_NS_DEF
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//
2+
// Copyright 2025 Autodesk
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
#ifndef MAYAUSD_UTILS_UTILCOMPONENTCREATOR_H
18+
#define MAYAUSD_UTILS_UTILCOMPONENTCREATOR_H
19+
20+
#include "mayaUsd/mayaUsd.h"
21+
22+
#include <mayaUsd/base/api.h>
23+
24+
#include <string>
25+
#include <vector>
26+
27+
namespace MAYAUSD_NS_DEF {
28+
namespace ComponentUtils {
29+
30+
/*! \brief Returns whether the proxy shape at the given path identifies an Autodesk USD Component.
31+
*/
32+
MAYAUSD_CORE_PUBLIC
33+
bool isAdskUsdComponent(const std::string& proxyPath);
34+
35+
/*! \brief Returns the ids of the USD layers that should be saved for the Autodesk USD Component.
36+
*
37+
* \note Expects \p proxyPath to be a valid component path.
38+
*/
39+
MAYAUSD_CORE_PUBLIC
40+
std::vector<std::string> getAdskUsdComponentLayersToSave(const std::string& proxyPath);
41+
42+
/*! \brief Saves the Autodesk USD Component identified by \p proxyPath.
43+
*
44+
* \note Expects \p proxyPath to be a valid component path.
45+
*/
46+
MAYAUSD_CORE_PUBLIC
47+
void saveAdskUsdComponent(const std::string& proxyPath);
48+
49+
/*! \brief Previews the structure of the Autodesk USD Component identified by \p proxyPath,
50+
* when saved at the given location with the given name.
51+
* \return Returns the expected component hierarchy, formatted in json.
52+
*/
53+
MAYAUSD_CORE_PUBLIC
54+
std::string previewSaveAdskUsdComponent(
55+
const std::string& saveLocation,
56+
const std::string& componentName,
57+
const std::string& proxyPath);
58+
59+
} // namespace ComponentUtils
60+
} // namespace MAYAUSD_NS_DEF
61+
62+
#endif // MAYAUSD_UTILS_UTILCOMPONENTCREATOR_H

lib/mayaUsd/utils/utilSerialization.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
#include <pxr/usd/sdf/usdaFileFormat.h>
3737
#include <pxr/usd/sdf/usdcFileFormat.h>
3838
#endif
39+
#include "utilComponentCreator.h"
40+
3941
#include <pxr/usd/usdGeom/tokens.h>
4042

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

626+
// Special case for components created by the component creator. Non-local layers,
627+
// non-active layers, and non-dirty but to be renamed layers, can be impacted when saving a
628+
// component. Only the component creator knows how to save a component properly.
629+
if (ComponentUtils::isAdskUsdComponent(proxyPath)) {
630+
const auto layerIds = ComponentUtils::getAdskUsdComponentLayersToSave(proxyPath);
631+
for (const auto& ccLayerId : layerIds) {
632+
auto ccLayer = pxr::SdfLayer::FindOrOpen(ccLayerId);
633+
if (!ccLayer || ccLayer->IsAnonymous()) {
634+
continue;
635+
}
636+
layersInfo._dirtyFileBackedLayers.push_back(ccLayer);
637+
}
638+
return;
639+
}
640+
624641
auto root = stage->GetRootLayer();
625642
populateChildren(
626643
proxyPath, stage, root, nullptr, layersInfo._anonLayers, layersInfo._dirtyFileBackedLayers);

lib/usd/ui/layerEditor/componentSaveDialog.cpp

Lines changed: 35 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
#include "generatedIconButton.h"
2020
#include "qtUtils.h"
2121

22+
#include <mayaUsd/utils/utilComponentCreator.h>
23+
2224
#include <maya/MGlobal.h>
2325
#include <maya/MString.h>
2426

@@ -378,70 +380,41 @@ void ComponentSaveDialog::updateTreeView()
378380
std::string saveLocation(_locationEdit->text().toStdString());
379381
std::string componentName(_nameEdit->text().toStdString());
380382

381-
MString defMoveComponentPreviewCmd;
382-
defMoveComponentPreviewCmd.format(
383-
"def usd_component_creator_move_component_preview():\n"
384-
" import json\n"
385-
" from pxr import Sdf, Usd, UsdUtils\n"
386-
" import mayaUsd\n"
387-
" import mayaUsd.ufe\n"
388-
" try:\n"
389-
" from AdskUsdComponentCreator import ComponentDescription, "
390-
"PreviewMoveComponentHierarchy\n"
391-
" except ImportError:\n"
392-
" return None\n"
393-
" proxyStage = mayaUsd.ufe.getStage(\"^1s\")\n"
394-
" component_description = "
395-
" ComponentDescription.CreateFromStageMetadata(proxyStage)\n"
396-
" if component_description:\n"
397-
" move_comp_preview = PreviewMoveComponentHierarchy(component_description, \"^2s\", "
398-
"\"^3s\")\n"
399-
" return json.dumps(move_comp_preview)\n"
400-
" else:\n"
401-
" return \"\"",
402-
_proxyShapePath.c_str(),
403-
saveLocation.c_str(),
404-
componentName.c_str());
405-
406-
if (MS::kSuccess == MGlobal::executePythonCommand(defMoveComponentPreviewCmd)) {
407-
MString result;
408-
MString runComponentMovePreviewCmd = "usd_component_creator_move_component_preview()";
409-
if (MS::kSuccess == MGlobal::executePythonCommand(runComponentMovePreviewCmd, result)) {
410-
QString jsonStr = QString::fromStdWString(result.asWChar());
411-
412-
// Clear existing tree first
413-
_treeWidget->clear();
414-
415-
QStackedWidget* stackedWidget
416-
= qobject_cast<QStackedWidget*>(_treeScrollArea->widget());
417-
418-
QJsonParseError parseError;
419-
QJsonDocument doc = QJsonDocument::fromJson(jsonStr.toUtf8(), &parseError);
420-
QJsonObject jsonObj;
421-
bool hasData = false;
422-
423-
if (parseError.error == QJsonParseError::NoError && doc.isObject() && !doc.isEmpty()) {
424-
jsonObj = doc.object();
425-
hasData = !jsonObj.isEmpty();
426-
}
427-
428-
if (hasData) {
429-
// Populate tree view with JSON data and show tree
430-
populateTreeView(jsonObj);
431-
if (stackedWidget) {
432-
stackedWidget->setCurrentIndex(0);
433-
}
434-
} else {
435-
// Show "Nothing to preview" message
436-
if (stackedWidget) {
437-
stackedWidget->setCurrentIndex(1);
438-
}
439-
}
440-
441-
// Update last component name
442-
_lastComponentName = _nameEdit->text();
383+
const auto result = MayaUsd::ComponentUtils::previewSaveAdskUsdComponent(
384+
saveLocation, componentName, _proxyShapePath.c_str());
385+
386+
QString jsonStr = QString::fromStdString(result);
387+
388+
// Clear existing tree first
389+
_treeWidget->clear();
390+
391+
QStackedWidget* stackedWidget = qobject_cast<QStackedWidget*>(_treeScrollArea->widget());
392+
393+
QJsonParseError parseError;
394+
QJsonDocument doc = QJsonDocument::fromJson(jsonStr.toUtf8(), &parseError);
395+
QJsonObject jsonObj;
396+
bool hasData = false;
397+
398+
if (parseError.error == QJsonParseError::NoError && doc.isObject() && !doc.isEmpty()) {
399+
jsonObj = doc.object();
400+
hasData = !jsonObj.isEmpty();
401+
}
402+
403+
if (hasData) {
404+
// Populate tree view with JSON data and show tree
405+
populateTreeView(jsonObj);
406+
if (stackedWidget) {
407+
stackedWidget->setCurrentIndex(0);
408+
}
409+
} else {
410+
// Show "Nothing to preview" message
411+
if (stackedWidget) {
412+
stackedWidget->setCurrentIndex(1);
443413
}
444414
}
415+
416+
// Update last component name
417+
_lastComponentName = _nameEdit->text();
445418
}
446419

447420
void ComponentSaveDialog::onShowMore()

0 commit comments

Comments
 (0)