Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/usd/ui/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ endif()
set(HEADERS
api.h
initStringResources.h
undoChunkUtils.h
)

mayaUsd_promoteHeaderList(
Expand Down
30 changes: 3 additions & 27 deletions lib/usd/ui/debugTools/CompositionEditorCmd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include <mayaUsd/ufe/Utils.h>
#include <mayaUsd/undo/MayaUsdUndoBlock.h>
#include <mayaUsdUI/ui/undoChunkUtils.h>

#include <usdUfe/undo/UsdUndoManager.h>

Expand Down Expand Up @@ -62,31 +63,6 @@ constexpr auto kReloadFlagLong = "-reload";

const MString WORKSPACE_CONTROL_NAME = "mayaUsdCompositionEditor";

// RAII guard that opens a named Maya undo chunk
class UndoChunkContext
{
private:
// Maya's undo chunk names cannot contain spaces (the name is split at the
// first space), so replace them with underscores before quoting.
MString cleanChunkName(const std::string& label)
{
std::string name = label.empty() ? "USD Composition Edit" : label;
std::replace(name.begin(), name.end(), ' ', '_');
return MString("\"") + name.c_str() + "\"";
}

public:
explicit UndoChunkContext(const std::string& label)
{
MGlobal::executeCommand(
MString("undoInfo -openChunk -chunkName ") + cleanChunkName(label), false, false);
}
~UndoChunkContext() { MGlobal::executeCommand("undoInfo -closeChunk", false, false); }

UndoChunkContext(const UndoChunkContext&) = delete;
UndoChunkContext& operator=(const UndoChunkContext&) = delete;
};

QPointer<Adsk::UsdDebug::CompositionEditorWidget> g_compositionEditorWidget;
Ufe::Observer::Ptr g_selectionObserver;

Expand Down Expand Up @@ -167,8 +143,8 @@ class MayaCompositionEditorHost : public Adsk::UsdDebug::ApplicationHost
UsdUfe::UsdUndoManager::instance().trackLayerStates(layer);
}

UndoChunkContext undoChunk(editLabel);
MayaUsdUndoBlock undoBlock;
MayaUsdUI::UndoChunkGuard undoChunk(editLabel);
MayaUsdUndoBlock undoBlock;
return edit();
}

Expand Down
8 changes: 4 additions & 4 deletions lib/usd/ui/layerEditor/mayaCommandHook.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#include "mayaCommandHook.h"

#include "../undoChunkUtils.h"
#include "abstractCommandHook.h"
#include "mayaSessionState.h"

Expand Down Expand Up @@ -46,9 +47,6 @@ namespace {

std::string quote(const std::string& string) { return STR(" \"") + string + STR("\""); }

// maya doesn't support spaces in undo chunk names...
MString cleanChunkName(QString name) { return quote(name.replace(" ", "_").toStdString()).c_str(); }

std::string getProxyShapeName(const std::string& proxyShapePath)
{
std::size_t found = proxyShapePath.find_last_of("|");
Expand Down Expand Up @@ -102,7 +100,9 @@ void MayaCommandHook::setEditTarget(UsdLayer usdLayer)
void MayaCommandHook::openUndoBracket(const QString& name)
{
MGlobal::executeCommand(
MString("undoInfo -openChunk -chunkName ") + cleanChunkName(name), false, false);
MString("undoInfo -openChunk -chunkName ") + MayaUsdUI::cleanChunkName(name.toStdString()),
false,
false);
}

// closes a complex undo operation in the host app. Please use UndoContext class to safely
Expand Down
2 changes: 2 additions & 0 deletions lib/usd/ui/renderSetup/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@

target_sources(${PROJECT_NAME}
PRIVATE
mayaEditCommitter.cpp
renderSetupWindowCmd.cpp
)

mayaUsd_promoteHeaderList(
HEADERS
mayaEditCommitter.h
renderSetupWindowCmd.h
BASEDIR
${PROJECT_NAME}/ui
Expand Down
74 changes: 74 additions & 0 deletions lib/usd/ui/renderSetup/mayaEditCommitter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//
// Copyright 2026 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 "mayaEditCommitter.h"

#include "../undoChunkUtils.h"

#include <mayaUsd/undo/MayaUsdUndoBlock.h>

#include <usdUfe/undo/UsdUndoUtils.h>

#include <pxr/usd/sdf/changeBlock.h>

#include <QtCore/QTimer>

namespace MayaUsdRenderSetup {

MayaEditCommitter::MayaEditCommitter(QObject* parent)
: QObject(parent)
{
}

void MayaEditCommitter::setStages(const std::vector<Adsk::HostStage>& stages)
{
_stages.clear();
_stages.reserve(stages.size());
for (const auto& hostStage : stages) {
if (hostStage.stage) {
_stages.push_back(hostStage.stage);
}
}
}

void MayaEditCommitter::commit(const std::string& undoLabel, std::function<void()> doEdit)
{
if (!doEdit || _stages.empty()) {
return;
}

++_inFlightCount;
// Schedule the decrement before doEdit() so it fires even if doEdit() throws.
// The one-event-loop-cycle delay keeps isLocalEditInFlight() true through the
// USD-notice burst that follows the edit, so the notice bridge treats the
// resulting refresh as self-originated and skips re-entrancy.
QTimer::singleShot(0, this, [this]() {
if (_inFlightCount > 0) {
--_inFlightCount;
}
});

UsdUfe::trackStagesEditTargets(_stages);

const MayaUsdUI::UndoChunkGuard undoChunkGuard(undoLabel);
MayaUsd::MayaUsdUndoBlock block;
{
PXR_NS::SdfChangeBlock changeBlock;
doEdit();
}
}

} // namespace MayaUsdRenderSetup
55 changes: 55 additions & 0 deletions lib/usd/ui/renderSetup/mayaEditCommitter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// Copyright 2026 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 MAYAUSDUI_USD_RENDERSETUP_MAYAEDITCOMMITTER_H
#define MAYAUSDUI_USD_RENDERSETUP_MAYAEDITCOMMITTER_H

#include <pxr/usd/usd/stage.h>

#include <QtCore/QObject>
#include <RenderSetup/HostStage.h>
#include <RenderSetup/IEditCommitter.h>

#include <functional>
#include <string>
#include <vector>

namespace MayaUsdRenderSetup {

//! MayaUSD implementation of Adsk::IEditCommitter for the Render Setup UI.
//! Captures USD edits through UsdUfe and flushes them onto the Maya undo stack
//! via MayaUsdUndoBlock.
class MayaEditCommitter
: public QObject
, public Adsk::IEditCommitter
{
Q_OBJECT
public:
explicit MayaEditCommitter(QObject* parent = nullptr);

void setStages(const std::vector<Adsk::HostStage>& stages);

void commit(const std::string& undoLabel, std::function<void()> doEdit) override;
bool isLocalEditInFlight() const override { return _inFlightCount > 0; }

private:
std::vector<PXR_NS::UsdStageRefPtr> _stages;
int _inFlightCount { 0 };
};

} // namespace MayaUsdRenderSetup

#endif // MAYAUSDUI_USD_RENDERSETUP_MAYAEDITCOMMITTER_H
17 changes: 13 additions & 4 deletions lib/usd/ui/renderSetup/renderSetupWindowCmd.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
//
#include "renderSetupWindowCmd.h"

#include "mayaEditCommitter.h"

#include <mayaUsd/nodes/proxyShapeBase.h>
#include <mayaUsd/nodes/usdSceneSettingsManager.h>
#include <mayaUsd/ufe/Utils.h>
Expand Down Expand Up @@ -88,19 +90,26 @@ class RenderSetupWindow
static void onSceneChangedCB(void* clientData);

private:
void applyStages() { _tree->setStages(_hostStages); }
void applyStages()
{
_editCommitter->setStages(_hostStages);
_tree->setStages(_hostStages);
}

private:
Adsk::RenderSetupWidget* _tree;
std::vector<Adsk::HostStage> _hostStages;
std::vector<MCallbackId> _sceneCallbackIds;
Adsk::RenderSetupWidget* _tree;
MayaUsdRenderSetup::MayaEditCommitter* _editCommitter { nullptr };
std::vector<Adsk::HostStage> _hostStages;
std::vector<MCallbackId> _sceneCallbackIds;
};

RenderSetupWindow::RenderSetupWindow(QWidget* parent)
: PARENT_CLASS(parent)
{
// Create the render setup widget and set it as the central widget of the window.
_tree = new Adsk::RenderSetupWidget(this);
_editCommitter = new MayaUsdRenderSetup::MayaEditCommitter(nullptr);
_tree->setEditCommitter(std::unique_ptr<Adsk::IEditCommitter>(_editCommitter));
setCentralWidget(_tree);
_tree->show();

Expand Down
66 changes: 66 additions & 0 deletions lib/usd/ui/undoChunkUtils.h
Comment thread
seando-adsk marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// Copyright 2026 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 MAYAUSDUI_UNDO_CHUNK_UTILS_H
#define MAYAUSDUI_UNDO_CHUNK_UTILS_H

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

#include <QtCore/QLatin1String>
#include <QtCore/QString>

#include <string>

namespace MayaUsdUI {

//! Sanitizes a user-facing undo label for use as a Maya undo chunk name.
//! Maya's -chunkName flag does not accept spaces; backslash and double-quote
//! are escaped so the result is safe to embed in a MEL double-quoted string.
inline MString cleanChunkName(const std::string& label)
{
QString name = QString::fromStdString(label);
name.replace(QLatin1Char('\\'), QLatin1String("\\\\"));
name.replace(QLatin1Char('"'), QLatin1String("\\\""));
name.replace(QLatin1Char(' '), QLatin1Char('_'));
return MString(("\"" + name.toStdString() + "\"").c_str());
}

//! RAII guard that opens a named Maya undo chunk on construction and closes it
//! on destruction, ensuring the chunk is always balanced even if an exception
//! is thrown inside the guarded scope.
struct UndoChunkGuard
{
//! Opens the undo chunk. If \p label is empty, no chunk name is set.
explicit UndoChunkGuard(const std::string& label)
{
if (label.empty()) {
MGlobal::executeCommand("undoInfo -openChunk", false, false);
} else {
MGlobal::executeCommand(
MString("undoInfo -openChunk -chunkName ") + cleanChunkName(label), false, false);
}
}

~UndoChunkGuard() { MGlobal::executeCommand("undoInfo -closeChunk", false, false); }

UndoChunkGuard(const UndoChunkGuard&) = delete;
UndoChunkGuard& operator=(const UndoChunkGuard&) = delete;
};

} // namespace MayaUsdUI

#endif // MAYAUSDUI_UNDO_CHUNK_UTILS_H
2 changes: 2 additions & 0 deletions lib/usdUfe/ufe/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ target_sources(${PROJECT_NAME}
UsdUndoVisibleCommand.cpp
UsdUndoSetDefaultPrimCommand.cpp
UsdUndoClearDefaultPrimCommand.cpp
UsdLabeledEditUndoableCommand.cpp
UsdUndoableCommand.cpp
Utils.cpp
)
Expand Down Expand Up @@ -219,6 +220,7 @@ set(HEADERS
UsdUndoVisibleCommand.h
UsdUndoSetDefaultPrimCommand.h
UsdUndoClearDefaultPrimCommand.h
UsdLabeledEditUndoableCommand.h
UsdUndoableCommand.h
Utils.h
)
Expand Down
36 changes: 36 additions & 0 deletions lib/usdUfe/ufe/UsdLabeledEditUndoableCommand.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// Copyright 2026 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 "UsdLabeledEditUndoableCommand.h"

namespace USDUFE_NS_DEF {

UsdLabeledEditUndoableCommand::UsdLabeledEditUndoableCommand(
std::string label,
std::function<void()> edit)
: _label(std::move(label))
, _edit(std::move(edit))
{
}

void UsdLabeledEditUndoableCommand::executeImplementation()
{
if (_edit) {
_edit();
}
}

} // namespace USDUFE_NS_DEF
Loading
Loading