Skip to content

Commit 9956e0e

Browse files
authored
Merge pull request #4690 from Autodesk/bailp/EMSUSD-3530/multi-variants-maya-save
EMSUSD-3530 save proxy shape of component as component
2 parents 309a330 + cf9820c commit 9956e0e

15 files changed

Lines changed: 946 additions & 33 deletions

lib/mayaUsd/nodes/layerManager.cpp

Lines changed: 53 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <mayaUsd/undo/OpUndoItems.h>
2626
#include <mayaUsd/utils/layerMuting.h>
2727
#include <mayaUsd/utils/util.h>
28+
#include <mayaUsd/utils/utilComponentCreator.h>
2829
#include <mayaUsd/utils/utilFileSystem.h>
2930
#include <mayaUsd/utils/utilSerialization.h>
3031

@@ -1137,19 +1138,29 @@ BatchSaveResult LayerDatabase::saveUsdToMayaFile()
11371138
const StageSavingInfo& info = i < _proxiesToSave.size()
11381139
? _proxiesToSave[i]
11391140
: _internalProxiesToSave[i - _proxiesToSave.size()];
1141+
1142+
std::string proxyPath = info.dagPath.fullPathName().asChar();
1143+
if (MayaUsd::ComponentUtils::isAdskUsdComponent(proxyPath)) {
1144+
MayaUsd::ComponentUtils::saveAdskUsdComponent(proxyPath);
1145+
continue;
1146+
}
1147+
11401148
MObject mobj = info.dagPath.node();
11411149
fn.setObject(mobj);
1142-
if (!fn.isFromReferencedFile()
1143-
&& LayerDatabase::instance().supportedNodeType(fn.typeId())) {
1144-
1145-
// Here if its unshared or not an incoming connection we save otherwise skip
1146-
if (!info.shareable || !info.isIncoming) {
1147-
auto result = saveStageToMayaFile(lm, builder, mobj, info.stage);
1148-
if (result._stageHasDirtyLayers) {
1149-
atLeastOneDirty = true;
1150-
}
1151-
layersHandle.set(builder);
1150+
1151+
if (fn.isFromReferencedFile())
1152+
continue;
1153+
1154+
if (!LayerDatabase::instance().supportedNodeType(fn.typeId()))
1155+
continue;
1156+
1157+
// Here if its unshared or not an incoming connection we save otherwise skip
1158+
if (!info.shareable || !info.isIncoming) {
1159+
auto result = saveStageToMayaFile(lm, builder, mobj, info.stage);
1160+
if (result._stageHasDirtyLayers) {
1161+
atLeastOneDirty = true;
11521162
}
1163+
layersHandle.set(builder);
11531164
}
11541165
}
11551166

@@ -1174,32 +1185,41 @@ BatchSaveResult LayerDatabase::saveUsdToUsdFiles()
11741185
? _proxiesToSave[i]
11751186
: _internalProxiesToSave[i - _proxiesToSave.size()];
11761187

1188+
std::string proxyPath = info.dagPath.fullPathName().asChar();
1189+
if (MayaUsd::ComponentUtils::isAdskUsdComponent(proxyPath)) {
1190+
MayaUsd::ComponentUtils::saveAdskUsdComponent(proxyPath);
1191+
continue;
1192+
}
1193+
11771194
MObject mobj = info.dagPath.node();
11781195
fn.setObject(mobj);
1179-
if (!fn.isFromReferencedFile()
1180-
&& LayerDatabase::instance().supportedNodeType(fn.typeId())) {
1181-
MayaUsdProxyShapeBase* pShape = static_cast<MayaUsdProxyShapeBase*>(fn.userNode());
11821196

1183-
// Unshared Composition Saves to MayaFile Always
1184-
if (!info.shareable) {
1185-
saveStageToMayaFile(mobj, info.stage);
1186-
} else {
1187-
// No need to save stages from external sources
1188-
if (info.isIncoming) {
1189-
continue;
1190-
}
1191-
convertAnonymousLayers(pShape, mobj, info.stage);
1192-
const auto& sessionLayer = info.stage->GetSessionLayer();
1193-
for (const auto& layer : getSaveCandidateLayers(*info.stage)) {
1194-
if (TF_VERIFY(layer)) {
1195-
if (layer != sessionLayer && layer->PermissionToSave()
1196-
&& layer->IsDirty()) {
1197-
if (!MayaUsd::utils::saveLayerWithFormat(layer)) {
1198-
MString errMsg;
1199-
MString layerName(layer->GetDisplayName().c_str());
1200-
errMsg.format("Could not save layer ^1s.", layerName);
1201-
MGlobal::displayError(errMsg);
1202-
}
1197+
if (fn.isFromReferencedFile())
1198+
continue;
1199+
1200+
if (!LayerDatabase::instance().supportedNodeType(fn.typeId()))
1201+
continue;
1202+
1203+
MayaUsdProxyShapeBase* pShape = static_cast<MayaUsdProxyShapeBase*>(fn.userNode());
1204+
1205+
// Unshared Composition Saves to MayaFile Always
1206+
if (!info.shareable) {
1207+
saveStageToMayaFile(mobj, info.stage);
1208+
} else {
1209+
// No need to save stages from external sources
1210+
if (info.isIncoming) {
1211+
continue;
1212+
}
1213+
convertAnonymousLayers(pShape, mobj, info.stage);
1214+
const auto& sessionLayer = info.stage->GetSessionLayer();
1215+
for (const auto& layer : getSaveCandidateLayers(*info.stage)) {
1216+
if (TF_VERIFY(layer)) {
1217+
if (layer != sessionLayer && layer->PermissionToSave() && layer->IsDirty()) {
1218+
if (!MayaUsd::utils::saveLayerWithFormat(layer)) {
1219+
MString errMsg;
1220+
MString layerName(layer->GetDisplayName().c_str());
1221+
errMsg.format("Could not save layer ^1s.", layerName);
1222+
MGlobal::displayError(errMsg);
12031223
}
12041224
}
12051225
}

test/lib/componentCreator/CMakeLists.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ if (AdskUsdComponentCreator_FOUND)
1717
testComponentCreatorAddPrimInComponent.py
1818
)
1919

20+
set(COMPONENT_CREATOR_BATCH_TEST_SCRIPT_FILES
21+
testComponentCreatorSave.py
22+
)
23+
2024
foreach(script ${COMPONENT_CREATOR_TEST_SCRIPT_FILES})
2125
mayaUsd_get_unittest_target(target ${script})
2226
mayaUsd_add_test(${target}
@@ -35,6 +39,20 @@ if (AdskUsdComponentCreator_FOUND)
3539
)
3640
set_property(TEST ${target} APPEND PROPERTY LABELS SharedComponents)
3741
endforeach()
42+
43+
foreach(script ${COMPONENT_CREATOR_BATCH_TEST_SCRIPT_FILES})
44+
mayaUsd_get_unittest_target(target ${script})
45+
mayaUsd_add_test(${target}
46+
PYTHON_MODULE ${target}
47+
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
48+
ENV
49+
"USD_FORCE_DEFAULT_MATERIALS_SCOPE_NAME=1"
50+
"LD_LIBRARY_PATH=${ADDITIONAL_LD_LIBRARY_PATH}"
51+
"MAYA_MODULE_PATH=${CMAKE_INSTALL_PREFIX}/../AdskUsdComponentCreator"
52+
"PXR_OVERRIDE_PLUGINPATH_NAME=${CMAKE_INSTALL_PREFIX}/lib/usd"
53+
)
54+
set_property(TEST ${target} APPEND PROPERTY LABELS SharedComponents)
55+
endforeach()
3856
else()
3957
# If MAYAUSD_FORCE_CC_TEST is set to 1, fail the build when component creator is not found
4058
if (DEFINED ENV{MAYAUSD_FORCE_CC_TEST} AND "$ENV{MAYAUSD_FORCE_CC_TEST}" STREQUAL "1")
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import unittest
2+
3+
import os.path
4+
import tempfile
5+
from distutils.dir_util import copy_tree
6+
7+
import fixturesUtils
8+
import testUtils
9+
import mayaUsd.lib
10+
import mayaUsd.ufe
11+
from maya import cmds
12+
from pxr import Sdf, Usd
13+
14+
from testComponentCreatorBase import _ComponentCreatorTestBase
15+
16+
class SaveToComponentTestCase(_ComponentCreatorTestBase, unittest.TestCase):
17+
"""
18+
Tests for usd_component_creator_plugin.create_component.add_to_component_from_nodes
19+
and then saving and reloading.
20+
"""
21+
22+
_tempFolder = None
23+
24+
@classmethod
25+
def setUpClass(cls):
26+
fixturesUtils.readOnlySetUpClass(__file__, initializeStandalone=False)
27+
28+
testFolderName = "component3Variants"
29+
fromDirectory = testUtils.getTestScene(testFolderName)
30+
toDirectory = os.path.join(tempfile.gettempdir(), 'SaveToComponentTestCase')
31+
copy_tree(fromDirectory, toDirectory)
32+
SaveToComponentTestCase._tempFolder = toDirectory
33+
34+
@classmethod
35+
def tearDownClass(cls):
36+
if SaveToComponentTestCase._tempFolder:
37+
if os.path.exists(SaveToComponentTestCase._tempFolder):
38+
import shutil
39+
shutil.rmtree(SaveToComponentTestCase._tempFolder)
40+
cls._resetDefaultTemplate()
41+
return super().tearDownClass()
42+
43+
def _findVariantSet(self, desc, withName):
44+
variantSetMap = desc.GetVariantSets()
45+
self.assertGreaterEqual(len(variantSetMap), 1, "There must be at least one variant set")
46+
for variantSetName, variantSet in variantSetMap.items():
47+
if withName not in variantSetName:
48+
continue
49+
return variantSet
50+
return None
51+
52+
def setUp(self):
53+
self._setUpCC()
54+
# Clear the variant editor state so there is no lingering component from a
55+
# previous test.
56+
self._resetDefaultTemplate()
57+
return super().setUp()
58+
59+
def testSaveAndReload(self):
60+
"""
61+
Open a Maya scene containing a USD stage of a component.
62+
Modify all variants of the component by replacing its data with a new node.
63+
Save the scene. Re-open the scene and verify that the component is still
64+
valid and has the new data.
65+
"""
66+
before = self._snapshotProxyShapes()
67+
68+
mayaSceneFilePath = os.path.join(SaveToComponentTestCase._tempFolder, "repro-3530.ma")
69+
cmds.file(mayaSceneFilePath, open=True)
70+
71+
proxy = self._findNewProxyShape(before)
72+
self.assertIsNotNone(proxy)
73+
stage = mayaUsd.ufe.getStage(proxy)
74+
self.assertIsNotNone(stage)
75+
desc = self._getDescFromStage(stage)
76+
self.assertIsNotNone(desc, 'Could not get ComponentDescription from the Maya scene')
77+
78+
first_vs = self._findVariantSet(desc, 'variant_set_1')
79+
self.assertTrue(first_vs)
80+
self.assertGreaterEqual(len(first_vs.GetVariants()), 3, "There must be at least three variants in the set {}".format(first_vs.GetName()))
81+
82+
variantsMap = first_vs.GetVariants()
83+
self.assertGreaterEqual(len(variantsMap), 3, "There must be at least three variants")
84+
85+
for variantName in variantsMap.keys():
86+
polyCubeName = cmds.polyCube(name='pCubeExtra')[0]
87+
self.assertIn('pCubeExtra', polyCubeName)
88+
89+
# The following code is equivalent to add_to_component_from_nodes,
90+
# but that function does not work in maya batch mode and we need to run this test in batch mode
91+
# because the Maya save would pop-up a dialog in interactive mode.
92+
#
93+
# from usd_component_creator_plugin import add_to_component_from_nodes
94+
# result = add_to_component_from_nodes(
95+
# [polyCubeName],
96+
# [(first_vs.name, variantName)],
97+
# is_replacing=True,
98+
# component_desc=desc)
99+
# self.assertTrue(result)
100+
101+
from AdskUsdComponentCreator import CreateFromFileCommand
102+
from usd_component_creator_plugin import ExportNodesCommand, execute_ufe_command, AddComponentToManagerCommand, UfeCommandWrapper
103+
from usd_component_creator_plugin.create_component import _generate_unique_temp_filename, _update_options_for_node, _update_options_for_purpose_from_nodes
104+
105+
component_desc = desc
106+
nodes = [polyCubeName]
107+
variant_selections = [(first_vs.name, variantName)]
108+
is_replacing = True
109+
export_options = None
110+
purpose = None
111+
112+
options = component_desc.GetOptions().Clone()
113+
_update_options_for_node(options, nodes[0], nodes)
114+
options.component_variants = variant_selections if variant_selections else []
115+
options.replace_variant_content = is_replacing
116+
# Never change the default variant when appending to an existing component.
117+
options.is_default_variant = False
118+
119+
input_usd_filename = _generate_unique_temp_filename(options)
120+
export_cmd = ExportNodesCommand(nodes, input_usd_filename, export_options)
121+
execute_ufe_command(export_cmd)
122+
123+
creation_options = _update_options_for_purpose_from_nodes(options, purpose, input_usd_filename)
124+
creation_options.Validate()
125+
126+
delete_input_file = False
127+
create_cmd = CreateFromFileCommand(component_desc, input_usd_filename, creation_options, delete_input_file)
128+
self.assertTrue(UfeCommandWrapper.executeWithUndo(create_cmd))
129+
130+
add_cmd = AddComponentToManagerCommand(create_cmd)
131+
execute_ufe_command(add_cmd)
132+
133+
134+
updated_desc = self._getDescFromStage(stage)
135+
self.assertIsNotNone(updated_desc, 'Could not get updated ComponentDescription')
136+
137+
# Save the file. Make sure the USD edits will go to a USD file.
138+
cmds.optionVar(intValue=('mayaUsd_ConfirmExistingFileSave', 0))
139+
saveLocation = 1
140+
cmds.optionVar(intValue=(mayaUsd.lib.OptionVarTokens.SerializedUsdEditsLocation, saveLocation))
141+
cmds.file(save=True, force=True)
142+
143+
cmds.file(new=True, force=True)
144+
cmds.file(mayaSceneFilePath, open=True)
145+
146+
proxy = self._findNewProxyShape(before)
147+
self.assertIsNotNone(proxy)
148+
stage = mayaUsd.ufe.getStage(proxy)
149+
self.assertIsNotNone(stage)
150+
desc = self._getDescFromStage(stage)
151+
self.assertIsNotNone(desc, 'Could not get ComponentDescription from the Maya scene')
152+
153+
first_vs = self._findVariantSet(desc, 'variant_set_1')
154+
self.assertTrue(first_vs)
155+
156+
variantsMap = first_vs.GetVariants()
157+
self.assertGreaterEqual(len(variantsMap), 3, "There must be at least three variants")
158+
159+
variant_to_cube_map = {
160+
'pPlane1': 'pCubeExtra',
161+
'pPlane2': 'pCubeExtra1',
162+
'pPlane3': 'pCubeExtra2',
163+
}
164+
165+
found_cubes = set()
166+
167+
for variant in variantsMap.values():
168+
primPath = desc.root_prim_path
169+
prim = stage.GetPrimAtPath(primPath)
170+
self.assertTrue(prim)
171+
prim.GetVariantSet(first_vs.GetName()).SetVariantSelection(variant.GetName())
172+
173+
stage.Reload()
174+
175+
expected_cube_name = variant_to_cube_map.get(variant.GetName())
176+
self.assertIsNotNone(expected_cube_name, "No expected cube name for variant {}".format(variant.GetName()))
177+
178+
geoPrim = stage.GetPrimAtPath(Sdf.Path('/root/geo'))
179+
for child in geoPrim.GetChildren():
180+
if child.GetTypeName() == 'Mesh':
181+
self.assertEqual(expected_cube_name, child.GetName(), f"The variant {variant.GetName()} should have a cube named {expected_cube_name}")
182+
found_cubes.add(child.GetName())
183+
184+
self.assertEqual(len(found_cubes), len(variantsMap), "There should be one cube for each variant")
185+
186+
187+
if __name__ == '__main__':
188+
fixturesUtils.runTests(globals())
189+

0 commit comments

Comments
 (0)