// Copyright (c) 2021 The Foundry Visionmongers Ltd. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // * Neither the name of Foundry nor the names of its // contributors may be used to endorse or promote products derived from this // software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // This work is a C++ implementation compatible with version 1.2.8 of the Python-based Nuke gizmo // authored at Psyop by Jonah Friedman and Andy Jones (see https://github.com/Psyop/Cryptomatte). #include "CryptomattePlugin.h" #include "CryptomatteManifest.h" #include "CryptomatteMatteList.h" #include "Build/fnBuild.h" #include "DDImage/Row.h" #include "DDImage/Knobs.h" #include "DDImage/Enumeration_KnobI.h" #include namespace { const char* const kLegacyManifestSourceOptions[] = { "Metadata", "Sidecar", nullptr }; constexpr const char* kLegacyInsideManifestSourceProceduralModeficatorKnob = "insideManifestSourceProceduralModeficatorKnob"; // The helper type for distinguishing the procedural modification of the knob from the real user input. // The class operates via a dedicated numeric knob. Zero value of the knob means the procedural // modification session is closed. A non-zero value of the counter knob determines an enclosure level // of the open session. // This is implemented through the memory management mechanism. When an instance of the class is created, // the value of the counter knob is increased. Similarly, the value of the counter knob is decreased, // when the instance gets destructed. class LegacyKnobProceduralModificationSession { DD::Image::Knob* _counterKnob; public: LegacyKnobProceduralModificationSession(DD::Image::Op* op, const char* counterKnobName) { _counterKnob = op->knob(counterKnobName); if (_counterKnob != nullptr) { double counter = _counterKnob->get_value(); ++counter; _counterKnob->set_value(counter); } } ~LegacyKnobProceduralModificationSession() { if (_counterKnob != nullptr) { double counter = _counterKnob->get_value(); --counter; _counterKnob->set_value(counter); mFnAssert(counter >= 0); } } static bool AtProceduralModification(DD::Image::Op* op, const char* counterKnobName) { if (auto couterKnob = op->knob(counterKnobName)) { return couterKnob->get_value() > 0; } return false; } }; } namespace Foundry { namespace NukePlugins { using namespace DD::Image; using namespace Cryptomatte; enum LegacyManifestSource : size_t { eEmbeddedManifest = 0, eSidecarManifest }; // Knobs // Note: to avoid knob names mismatch with the original gizmo, // don't change the name of knobs followed by "Name from gizmo" comment const char* CryptomattePlugin::kCryptoLayerChoiceKnob = "cryptoLayerChoice"; // Name from gizmo const char* CryptomattePlugin::kManifestSourceKnob = "manifestSourceExtended"; const char* const CryptomattePlugin::kManifestSourceOptions[] = { "Metadata Embedded", "Metadata Sidecar", "Metadata Auto", "Custom Sidecar", nullptr }; const char* CryptomattePlugin::kLegacyManifestSourceKnob = "manifestSource"; const char* CryptomattePlugin::kSidecarFilepathKnob = "sidecarFilepath"; const char* CryptomattePlugin::kCustomSidecarKnob = "customSidecar"; const char* CryptomattePlugin::kMatteOutputKnob = "matteOutput"; // Name from gizmo const char* CryptomattePlugin::kUnpremultiply = "unpremultiply"; // Name from gizmo const char* CryptomattePlugin::kRemoveChannelsKnob = "removeChannels"; const char* CryptomattePlugin::kLegacyManifestSourceModifiedKnob = "manifestSourceModified"; const char* CryptomattePlugin::kLastSelectedCryptoLayerNameKnob = "lastSelectedCryptoLayerName"; CryptomattePlugin::CryptomattePlugin(Node* node) : PixelIop(node) , _picker(this) { } const char* CryptomattePlugin::Class() const { return kDescription.name; } const char* CryptomattePlugin::node_help() const { return "

Cryptomatte: Generates a matte that allows modification of parts of a 3D scene in a 2D render.

" "

To use, place the node on a stream that contains Cryptomatte layers (e.g. provided by an exr file, " "with Cryptomatte AOVs included). Then tick the Picker Add knob and use " FN_CTRLKEYNAME "+click or " FN_CTRLKEYNAME "+Shift+drag to select ID mattes from the viewer. These are then used to generate a matte " "in the target channel, specified by the Matte Output knob. Once an ID matte is selected, its name gets " "added to the Matte List knob. Similarly to Picker Add, you can use the Picker Remove knob to remove " "selected ID mattes. You can also manipulate selected ID mattes by modifying the text in Matte List.

" "

The Cryptomatte specifications were created at Psyop by Jonah Friedman and Andy Jones. They define " "a fully automatic method for creating and encoding ID mattes. 3D scenes consist of an organised hierarchy " "of objects and relationships, and when 2D images are rendered, that information is not preserved. " "ID mattes attempt to preserve organisational information by establishing a correspondence between items in " "the 3D scene and particular pixels in the 2D image plane.

"; } void CryptomattePlugin::_validate(bool forReal) { copy_info(); // Remove unused channels if requested. if (_removeChannels) { info_.channels() &= Mask_RGBA; } // Determine out channels. ChannelSet outChannels = _matteOutChannel; // If Preview Mode is enabled, add RGB to out channels. if (_picker.previewIsEnabled()) { outChannels += Mask_RGB; } info_.turn_on(outChannels); set_out_channels(outChannels); // Fetch available cryptomatte objects from the input 0. const auto availableObjects = GetAvailableCryptomatteObjects(this); // Pick user-selected cryptomatte among the available items. // List of available cryptomattes may be empty, when the input 0 isn't present. if (availableObjects.empty()) { _selectedCryptomatte = CryptomatteObject(); } else { // updateUI() tracks cryptomatte data change on the input and updates the selected // index accordingly. When the plugin's UI isn't present on the Properties pane, // cryptomatte data change may not be detected, therefore the selected index could // be invalid. This could make the selected index being out of bounds of actual // cryptomatte objects. Below we use the value of the select index if the index is // in bounds; otherwise 0. const auto safeSelectedIndex = (_selectedCryptomatteIndex >= availableObjects.size()) ? 0 : static_cast(_selectedCryptomatteIndex); _selectedCryptomatte = availableObjects[safeSelectedIndex]; // Restore the last selected cryptomatte layer name if found. if (_selectedCryptomatte.name != _lastSelectedCryptoLayerName) { for (const auto& cryptoObject : availableObjects) { if (_lastSelectedCryptoLayerName == cryptoObject.name) { _selectedCryptomatte = cryptoObject; break; } } } HandleCryptomatteObjectErrors(_selectedCryptomatte, this); } // Get Manifest to allow pattern matching when using wildcards expressions. const auto manifest = getManifestFromSelectedSource(_selectedCryptomatte); // Resolve IDs from Matte List for further use in pixel_engine(). _resolvedIDs = _picker.getResolvedIDs(manifest); } void CryptomattePlugin::in_channels(int input, ChannelSet& mask) const { // Out channels will be completely overwritten. // We don't need anything from input on them. mask -= out_channels(); // Add layers from user-selected cryptomatte to input channels, if any exists for (const auto& layer : _selectedCryptomatte.layers) { mask += layer; } if (_unpremultiplyEnabled) { mask += Chan_Alpha; } } void CryptomattePlugin::knobs(Knob_Callback callback) { Enumeration_knob(callback, &_selectedCryptomatteIndex, _cryptoLayersMenu, kCryptoLayerChoiceKnob, "Layer Selection"); SetFlags(callback, Knob::SAVE_MENU | Knob::EXPAND_TO_CONTENTS); Tooltip(callback, "Choose which Cryptomatte layer to generate the output matte from."); Newline(callback); switch (GetCompatibility()) { case Compatibility::To_v14_0: Enumeration_knob(callback, &_legacyManifestSourceIndex, kLegacyManifestSourceOptions, kLegacyManifestSourceKnob, "Manifest Source"); Tooltip(callback, "Choose the source of the manifest for the selected Cryptomatte layer." "

Note: the value will be reset to the default when the Layer Selection knob is changed.

"); break; case Compatibility::From_v14_1: Enumeration_knob(callback, &_manifestSourceIndex, kManifestSourceOptions, kManifestSourceKnob, "Manifest Source"); Tooltip(callback, "Choose the source of the Cryptomatte Manifest." ""); break; } File_knob(callback, &_sidecarFilePath, kSidecarFilepathKnob, ""); if (GetCompatibility() == Compatibility::From_v14_1) { SetFlags(callback, Knob::DISABLED | Knob::NO_UNDO | Knob::DO_NOT_WRITE | Knob::DO_NOT_READ); } ClearFlags(callback, Knob::STARTLINE); switch (GetCompatibility()) { case Compatibility::To_v14_0: Tooltip(callback, "When the Sidecar option is selected as a Manifest Source, use this " "knob to set the path to the sidecar file." "

Note: unless any modification to this knob has been done manually, " "the value will be reset to the default when the Layer Selection knob is changed.

"); break; case Compatibility::From_v14_1: Tooltip(callback, "When the Custom Sidecar option is selected as a Manifest Source, use this " "knob to set the path to the sidecar file." "

For other options the knob is disabled, but for options Metadata Sidecar and Custom Sidecar " "it shows the metadata sourced path to the sidecar file, if any exists.

"); String_knob(callback, &_customSidecar, kCustomSidecarKnob); SetFlags(callback, Knob::INVISIBLE); break; } Divider(callback); _picker.addKnobs(callback); Divider(callback); Channel_knob(callback, &_matteOutChannel, 1, kMatteOutputKnob, "Matte Output"); Tooltip(callback, "Set the channel the matte will be written to."); Bool_knob(callback, &_unpremultiplyEnabled, kUnpremultiply, "Unpremultiply"); Tooltip(callback, "Unpremultiply the output matte by the input alpha channel."); Newline(callback); Bool_knob(callback, &_removeChannels, kRemoveChannelsKnob, "Remove Channels"); Tooltip(callback, "Removes all the channels for the output, except for RGBA channels and the matte output channel."); if (GetCompatibility() == Compatibility::To_v14_0) { // This invisible knob is used as a state flag, that determines if manifest source knobs // have been modified by the user. Bool_knob(callback, &_legacyManifestSourceModified, kLegacyManifestSourceModifiedKnob); SetFlags(callback, Knob::INVISIBLE); } // This invisible knob is used for preserving the name of last user-selected cryptomatte. // When the input is changed, a Cryptomatte object with the same name is selected by default, // if any exists upstream. String_knob(callback, &_lastSelectedCryptoLayerName, kLastSelectedCryptoLayerNameKnob); SetFlags(callback, Knob::INVISIBLE | Knob::NO_UNDO); if (GetCompatibility() == Compatibility::To_v14_0) { Int_knob(callback, &_legacyInsideManifestSourceProceduralModeficator, kLegacyInsideManifestSourceProceduralModeficatorKnob); SetFlags(callback, Knob::INVISIBLE | Knob::NO_UNDO | Knob::DO_NOT_WRITE); } addObsoleteKnobs(callback); } int CryptomattePlugin::knob_changed(Knob* k) { if (k == &Knob::showPanel) { auto cryptomatteContext = loadCryptomatteContext(); // Update the layer selection knob with list of available cryptomattes updateCryptoLayerChoiceKnob(cryptomatteContext); switch (GetCompatibility()) { case Compatibility::To_v14_0: // Update manifest source knob and sidecar file knob legacyUpdateManifestSourceKnobs(cryptomatteContext); break; case Compatibility::From_v14_1: // Force update the state of the sidecar filepath knob, depending on // the cryptomatte context and the selected manifest source. cryptomatteContext.manifestSourceUiInvalid = true; updateSidecarFilepathKnob(cryptomatteContext); break; } return 1; } if (k->is(kCryptoLayerChoiceKnob)) { const auto cryptomatteContext = loadCryptomatteContext(); switch (GetCompatibility()) { case Compatibility::To_v14_0: // Update manifest source knob and sidecar file knob legacyUpdateManifestSourceKnobs(cryptomatteContext); break; case Compatibility::From_v14_1: // Update the state of the sidecar filepath knob, if required. // The flag `cryptomatteContext.manifestSourceUiInvalid` determines that. updateSidecarFilepathKnob(cryptomatteContext); break; } // Save last user-selected Crypto Layer saveLastSelectedCryptoLayerName(cryptomatteContext); return 1; } if (k->is(kManifestSourceKnob)) { auto cryptomatteContext = loadCryptomatteContext(); switch (GetCompatibility()) { case Compatibility::To_v14_0: legacyCheckManifestSourceModified(cryptomatteContext); legacyUpdateSidecarFilenameKnobEnabled(); break; case Compatibility::From_v14_1: // The manifest source is changed, force update the state of the sidecar filepath knob. cryptomatteContext.manifestSourceUiInvalid = true; updateSidecarFilepathKnob(cryptomatteContext); break; } return 1; } if (k->is(kSidecarFilepathKnob)) { switch (GetCompatibility()) { case Compatibility::To_v14_0: { const auto cryptomatteContext = loadCryptomatteContext(); legacyCheckManifestSourceModified(cryptomatteContext); break; } case Compatibility::From_v14_1: saveCustomSidecar(); break; } return 1; } if (_picker.knob_changed(k)) { return 1; } return 0; } bool CryptomattePlugin::updateUI(const OutputContext& context) { // Fetch available Cryptomatte objects from the metadata and update the UI // in regards to them, if the UI is outdated. const auto cryptomatteContext = loadCryptomatteContext(); if (cryptomatteContext.cryptoLayersUiInvalid) { updateCryptoLayerChoiceKnob(cryptomatteContext); } switch (GetCompatibility()) { case Compatibility::To_v14_0: if (cryptomatteContext.legacySidecarFilenameUiInvalid) { legacyUpdateManifestSourceKnobs(cryptomatteContext); } break; case Compatibility::From_v14_1: if (cryptomatteContext.manifestSourceUiInvalid) { updateSidecarFilepathKnob(cryptomatteContext); } break; } return true; } void CryptomattePlugin::pixel_engine(const Row& in, int y, int x, int r, ChannelMask mask, Row& out) { if (r < x) { // Return, if we are in the degenerate case return; } // If for some reason the selected cryptomatte is invalid (e.g. the Input doesn't exist), // just clear the Matte Out Channel. If the Preview Mode is enabled, clear RGB as well. if (_selectedCryptomatte.layers.empty()) { ChannelSet outChannels(_matteOutChannel); if (_picker.previewIsEnabled()) { outChannels += Mask_RGB; } out.erase(outChannels); return; } // Render the preview and matte channel _picker.renderPreview(this, in, y, x, r, _selectedCryptomatte.layers, _resolvedIDs, &_matteOutChannel, _unpremultiplyEnabled, out); } CryptomatteObject CryptomattePlugin::getSelectedCryptomatteObject() const { const auto cryptoLayerChoiceKnob = knob(kCryptoLayerChoiceKnob); if (cryptoLayerChoiceKnob == nullptr) { return CryptomatteObject(); } const auto availableCryptomattes = GetAvailableCryptomatteObjects(this); // Get the index of selected cryptomatte and make sure the value is within // the range of available cryptomattes. const auto selectedCryptomatteIndex = static_cast(cryptoLayerChoiceKnob->get_value()); if (selectedCryptomatteIndex >= availableCryptomattes.size()) { return CryptomatteObject(); } return availableCryptomattes[selectedCryptomatteIndex]; } PickingInterface::PixelRowFetcher CryptomattePlugin::getCryptomattePixelRowFetcher() const { const auto& outputContext = uiContext(); auto input = op_cast(node_input(0, Op::EXECUTABLE_INPUT, &outputContext)); if (input == nullptr) { return[](int y, int x, int r, ChannelSet m, Row& row) { }; } input->validate(); input->request(input->info().box(), input->info().channels(), 0); return [input](int y, int x, int r, ChannelSet m, Row& row) { input->get(y, x, r, m, row); }; } CryptomattePlugin::CryptomatteContext CryptomattePlugin::loadCryptomatteContext() const { CryptomatteContext cryptomatteContext; const auto layerChoiceKnob = enumerationKnobI(kCryptoLayerChoiceKnob); const auto manifestSourceKnob = enumerationKnobI(kManifestSourceKnob); const auto sidecarFilepathKnob = knob(kSidecarFilepathKnob); const auto selectedCryptoLayerNameKnob = knob(kLastSelectedCryptoLayerNameKnob); if (GetCompatibility() == Compatibility::From_v14_1) { if (manifestSourceKnob == nullptr) { return cryptomatteContext; } } if (layerChoiceKnob == nullptr || sidecarFilepathKnob == nullptr || selectedCryptoLayerNameKnob == nullptr) { return cryptomatteContext; } // Fetch available Cryptomatte objects from the input 0 cryptomatteContext.objects = GetAvailableCryptomatteObjects(this); if (GetCompatibility() == Compatibility::From_v14_1) { if (cryptomatteContext.objects.empty()) { return cryptomatteContext; } } // Determine if Cryptomatte objects have been changed since the last update // by comparing those against items from the Layer Selection. auto cryptoLayerMenuItems = layerChoiceKnob->menu(); cryptomatteContext.cryptoLayersUiInvalid = cryptoLayerMenuItems.size() != cryptomatteContext.objects.size(); for (size_t i = 0; !cryptomatteContext.cryptoLayersUiInvalid && i < cryptoLayerMenuItems.size(); ++i) { cryptomatteContext.cryptoLayersUiInvalid = cryptoLayerMenuItems[i] != cryptomatteContext.objects[i].name; } if (!cryptomatteContext.cryptoLayersUiInvalid) { switch (GetCompatibility()) { case Compatibility::To_v14_0: { // If the UI is up to date, get the selected index from the corresponding knob const size_t lastIndex = layerChoiceKnob->getSelectedItemIndex(); cryptomatteContext.selectedIndex = lastIndex; // If the manifest sidecar file path isn't custom, check whether it has been changed if (cryptomatteContext.objects.empty() || legacyGetManifestSourceModified()) { cryptomatteContext.legacySidecarFilenameUiInvalid = false; } else { const auto& selectedCryptomatte = cryptomatteContext.objects[cryptomatteContext.selectedIndex]; const auto& actualSidecarFilepath = LegacyGetAbsoluteFilePathToSidecarManifest(input(0), selectedCryptomatte.manifestFile); const auto sidecarFilepathFromUi = sidecarFilepathKnob->get_text(); cryptomatteContext.legacySidecarFilenameUiInvalid = sidecarFilepathFromUi == nullptr ? !actualSidecarFilepath.empty() : actualSidecarFilepath != sidecarFilepathFromUi; } break; } case Compatibility::From_v14_1: // If the UI is up to date, get the selected index from the corresponding knob cryptomatteContext.selectedIndex = static_cast(layerChoiceKnob->getSelectedItemIndex()); break; } } else { // Otherwise, try find a Cryptomatte object among available that has the same name // as the name of last user-selected Cryptomatte object. const char* lastSelectedName = selectedCryptoLayerNameKnob->get_text(); if (lastSelectedName != nullptr) { for (size_t i = 0; i < cryptomatteContext.objects.size(); ++i) { if (lastSelectedName == cryptomatteContext.objects[i].name) { cryptomatteContext.selectedIndex = i; break; } } } } if (GetCompatibility() == Compatibility::From_v14_1) { // If the manifest source comes from manifest, check whether it has been changed. switch (static_cast(manifestSourceKnob->getSelectedItemIndex())) { case ManifestSource::MetadataAuto: case ManifestSource::MetadataSidecar: { std::string sidecarFilepathFromUi; if (const auto sidecarFilepathFromUiCStr = sidecarFilepathKnob->get_text()) { sidecarFilepathFromUi = std::string{sidecarFilepathFromUiCStr}; } const auto& selectedCryptomatte = cryptomatteContext.objects[cryptomatteContext.selectedIndex]; cryptomatteContext.manifestSourceUiInvalid = sidecarFilepathFromUi != selectedCryptomatte.manifestFile; break; } case ManifestSource::MetadataEmbedded: case ManifestSource::CustomSidecar: cryptomatteContext.manifestSourceUiInvalid = false; break; } } return cryptomatteContext; } void CryptomattePlugin::saveLastSelectedCryptoLayerName(const CryptomatteContext& objectsAndState) { // Don't reset the name of last user-selected Cryptomatte object, if there is no more available objects. if (objectsAndState.objects.empty()) { return; } auto lastSelectedCryptoLayerNameKnob = knob(kLastSelectedCryptoLayerNameKnob); if (lastSelectedCryptoLayerNameKnob != nullptr) { lastSelectedCryptoLayerNameKnob->set_text(objectsAndState.objects[objectsAndState.selectedIndex].name.c_str()); } } void CryptomattePlugin::addObsoleteKnobs(Knob_Callback callback) const { // The following knob is supported by the plugin Obsolete_knob(callback, kRemoveChannelsKnob, "RemoveChannels", nullptr); // Helper lambda to create obsolete knobs with zero-padded names auto addPaddedObsoleteKnobs = [&callback](const std::string& baseKnobName, size_t numberOfKnobs) { if (numberOfKnobs != 0) { const size_t paddingWidth = std::to_string(numberOfKnobs - 1).size(); for (size_t i = 0; i < numberOfKnobs; i++) { std::stringstream indexStream; indexStream << std::setw(paddingWidth) << std::setfill('0') << i; const std::string knobName = baseKnobName + indexStream.str(); Obsolete_knob(callback, knobName.c_str(), nullptr); } } }; // The following knobs are not needed to match the gizmo's functionality Obsolete_knob(callback, kLastSelectedCryptoLayerNameKnob, "cryptoLayer", nullptr); Obsolete_knob(callback, "cryptoLayerLock", nullptr); Obsolete_knob(callback, "expression", nullptr); Obsolete_knob(callback, "keyedName", nullptr); Obsolete_knob(callback, "manifestKey", nullptr); Obsolete_knob(callback, "previewChannel", nullptr); Obsolete_knob(callback, "stopAutoUpdate", nullptr); addPaddedObsoleteKnobs("in", 12); addPaddedObsoleteKnobs("previewExpression", 4); // Helper lambda to generate an obsolete knob with a warning attached auto addUnsupportedKnob = [&callback](const std::string& knobName) { const std::string warningMessage = "message \"Warning: detected use of unsupported knob '" + knobName + "' from the Cryptomatte gizmo.\""; Obsolete_knob(callback, knobName.c_str(), warningMessage.c_str()); }; // The following knobs are not currently supported addUnsupportedKnob("matteOnly"); addUnsupportedKnob("previewMode"); addUnsupportedKnob("singleSelection"); if (GetCompatibility() == Compatibility::From_v14_1) { // The following knobs are obsolete due to the manifest source workflow change (TP-514325, TP-514323). // Backward compatibility is provided via the obsolete stript in `manifestSourceModified`. Obsolete_knob(callback, "manifestSource", nullptr); Obsolete_knob(callback, "manifestSourceModified", "if $value {knob manifestSourceExtended 3; knob customSidecar [knob sidecarFilepath]} else {knob manifestSourceExtended 2}"); Obsolete_knob(callback, "insideManifestSourceProceduralModeficatorKnob", nullptr); } } void CryptomattePlugin::updateCryptoLayerChoiceKnob(const CryptomatteContext& objectsAndState) { auto layerChoiceKnob = knob(kCryptoLayerChoiceKnob); if (layerChoiceKnob == nullptr) { return; } Enumeration_KnobI* layerChoiceEnumerationKnob = layerChoiceKnob->enumerationKnob(); if (layerChoiceEnumerationKnob == nullptr) { return; } // Save the name of current user-selected Cryptomatte object. saveLastSelectedCryptoLayerName(objectsAndState); // If layerChoiceKnob has already been changed by an undo, but this undo is not valid e.g. // in the case the undo layer no longer exists in the CryptomatteContext, set_value() and // menu() will correct the layer selection but also create the same undo at the top of the // stack, causing an undo loop. To prevent this, guard against these functions creating undos. layerChoiceKnob->undoless(true); // Update selected index layerChoiceKnob->set_value(static_cast(objectsAndState.selectedIndex)); // Update menu items std::vector names; names.reserve(objectsAndState.objects.size()); for (const auto& cryptomatteObject : objectsAndState.objects) { names.push_back(cryptomatteObject.name); } layerChoiceEnumerationKnob->menu(names); // stop guarding against undos layerChoiceKnob->undoless(false); } void CryptomattePlugin::updateSidecarFilepathKnob(const CryptomatteContext& cryptomatteContext) { if (!cryptomatteContext.manifestSourceUiInvalid || cryptomatteContext.objects.empty()) { return; } const auto manifestSourceKnob = enumerationKnobI(kManifestSourceKnob); auto sidecarFilenameKnob = knob(kSidecarFilepathKnob); const auto customSidecarKnob = knob(kCustomSidecarKnob); if (manifestSourceKnob == nullptr || sidecarFilenameKnob == nullptr || customSidecarKnob == nullptr) { return; } switch (static_cast(manifestSourceKnob->getSelectedItemIndex())) { case ManifestSource::MetadataAuto: case ManifestSource::MetadataSidecar: { const auto& selectedCryptomatte = cryptomatteContext.objects[cryptomatteContext.selectedIndex]; sidecarFilenameKnob->set_text(selectedCryptomatte.manifestFile.c_str()); sidecarFilenameKnob->enable(false); break; } case ManifestSource::MetadataEmbedded: sidecarFilenameKnob->set_text(""); sidecarFilenameKnob->enable(false); break; case ManifestSource::CustomSidecar: sidecarFilenameKnob->set_text(customSidecarKnob->get_text()); sidecarFilenameKnob->enable(true); break; } } void CryptomattePlugin::saveCustomSidecar() { const auto manifestSourceKnob = enumerationKnobI(kManifestSourceKnob); const auto sidecarFilenameKnob = knob(kSidecarFilepathKnob); auto customSidecarKnob = knob(kCustomSidecarKnob); if (manifestSourceKnob == nullptr || sidecarFilenameKnob == nullptr || customSidecarKnob == nullptr) { return; } const auto manifestSource = static_cast(manifestSourceKnob->getSelectedItemIndex()); if (manifestSource == ManifestSource::CustomSidecar) { customSidecarKnob->set_text(sidecarFilenameKnob->get_text()); } } void CryptomattePlugin::legacyUpdateManifestSourceKnobs(const CryptomatteContext& cryptomatteContext) { if (cryptomatteContext.objects.empty()) { return; } const auto layerChoiceKnob = knob(kCryptoLayerChoiceKnob); auto legacyManifestSourceKnob = knob(kLegacyManifestSourceKnob); auto sidecarFilenameKnob = knob(kSidecarFilepathKnob); if (layerChoiceKnob == nullptr || legacyManifestSourceKnob == nullptr || sidecarFilenameKnob == nullptr) { return; } LegacyKnobProceduralModificationSession session(this, kLegacyInsideManifestSourceProceduralModeficatorKnob); // Reset manifest source knobs to their default values, if they haven't been changed by the user yet. if (!legacyGetManifestSourceModified()) { const size_t selectedLayerIndex = static_cast(layerChoiceKnob->get_value()); if (selectedLayerIndex < cryptomatteContext.objects.size()) { const auto& selectedCryptomatte = cryptomatteContext.objects[selectedLayerIndex]; if (selectedCryptomatte.manifestFile.empty()) { legacyManifestSourceKnob->set_value(static_cast(eEmbeddedManifest)); sidecarFilenameKnob->set_text(""); } else { legacyManifestSourceKnob->set_value(static_cast(eSidecarManifest)); const std::string sidecarFilePath = LegacyGetAbsoluteFilePathToSidecarManifest(input(0), selectedCryptomatte.manifestFile); sidecarFilenameKnob->set_text(sidecarFilePath.c_str()); } } } legacyUpdateSidecarFilenameKnobEnabled(); } void CryptomattePlugin::legacyUpdateSidecarFilenameKnobEnabled() { const auto legacyManifestSourceKnob = knob(kLegacyManifestSourceKnob); if (legacyManifestSourceKnob == nullptr) { return; } auto sidecarFilenameKnob = knob(kSidecarFilepathKnob); if (sidecarFilenameKnob == nullptr) { return; } LegacyKnobProceduralModificationSession session(this, kLegacyInsideManifestSourceProceduralModeficatorKnob); // Disable the sidecar filename knob by when the manifest is embedded; otherwise enable it. const bool isManifestSidecar = static_cast(legacyManifestSourceKnob->get_value()) == eSidecarManifest; sidecarFilenameKnob->enable(isManifestSidecar); } Manifest CryptomattePlugin::legacyGetManifestFromSelectedSource(const CryptomatteObject& cryptomatte) { const auto legacyManifestSourceKnob = knob(kLegacyManifestSourceKnob); const auto sidecarFilenameKnob = knob(kSidecarFilepathKnob); if (legacyManifestSourceKnob == nullptr || sidecarFilenameKnob == nullptr) { return Manifest(); } const auto selectedManifestSource = static_cast(legacyManifestSourceKnob->get_value()); const auto manifestSource = static_cast(selectedManifestSource); const char* sidecarFilename = sidecarFilenameKnob->get_text(&uiContext()); Manifest manifest; if (manifestSource == eEmbeddedManifest) { manifest = GetManifestEmbedded(input(0), cryptomatte); } else if (sidecarFilename != nullptr) { const std::string sidecarFilenameString(sidecarFilename); manifest = GetManifestSidecar(sidecarFilename); if (manifest.empty()) { const std::string errorMessage = R"(")" + sidecarFilenameString + R"(" is not a valid sidecar manifest file path)"; error(errorMessage.c_str()); } } return manifest; } bool CryptomattePlugin::legacyGetManifestSourceModified() const { const auto legacyManifestSourceModifiedKnob = knob(kLegacyManifestSourceModifiedKnob); if (legacyManifestSourceModifiedKnob != nullptr) { return static_cast(legacyManifestSourceModifiedKnob->get_value()); } return false; } void CryptomattePlugin::legacyCheckManifestSourceModified(CryptomattePlugin::CryptomatteContext context) { if (context.objects.empty()) { return; } const auto sidecarFilepathKnob = knob(kSidecarFilepathKnob); const auto legacyManifestSourceKnob = knob(kLegacyManifestSourceKnob); auto legacyManifestSourceModifiedKnob = knob(kLegacyManifestSourceModifiedKnob); if (sidecarFilepathKnob == nullptr || legacyManifestSourceKnob == nullptr || legacyManifestSourceModifiedKnob == nullptr) { return; } bool manifestSourceModified = legacyManifestSourceModifiedKnob->get_value() != 0.0; if (manifestSourceModified) { // Nothing more to do here, when the flag is already set. return; } if (LegacyKnobProceduralModificationSession::AtProceduralModification(this, kLegacyInsideManifestSourceProceduralModeficatorKnob)) { // Skip procedural modifications of the manifest source knobs. return; } const auto& selectedObject = context.objects[context.selectedIndex]; const auto manifestSource = static_cast(static_cast((legacyManifestSourceKnob->get_value()))); if (selectedObject.manifestFile.empty() && manifestSource != eSidecarManifest) { legacyManifestSourceModifiedKnob->set_value(1.0); return; } const auto& uiManifestFilename = sidecarFilepathKnob->get_text(); const auto& metadataManifestFilename = LegacyGetAbsoluteFilePathToSidecarManifest(input(0), selectedObject.manifestFile); manifestSourceModified = uiManifestFilename == nullptr ? !metadataManifestFilename.empty() : uiManifestFilename != metadataManifestFilename; legacyManifestSourceModifiedKnob->set_value(static_cast(manifestSourceModified)); } Manifest CryptomattePlugin::getManifestFromSelectedSource(const CryptomatteObject& cryptomatte) { // Legacy behaviour if (GetCompatibility() == Compatibility::To_v14_0) { return legacyGetManifestFromSelectedSource(cryptomatte); } Manifest manifest; std::string sidecarFilename; const auto manifestSourceKnob = enumerationKnobI(kManifestSourceKnob); if (manifestSourceKnob == nullptr) { return manifest; } switch (static_cast(manifestSourceKnob->getSelectedItemIndex())) { case ManifestSource::MetadataEmbedded: manifest = GetManifestEmbedded(input(0), cryptomatte); break; case ManifestSource::MetadataAuto: if (cryptomatte.manifestFile.empty()) { manifest = GetManifestEmbedded(input(0), cryptomatte); break; } // [[fallthrough]] case ManifestSource::MetadataSidecar: sidecarFilename = GetAbsoluteFilePathToSidecarManifest(this, cryptomatte.manifestFile); manifest = GetManifestSidecar(sidecarFilename); break; case ManifestSource::CustomSidecar: { const auto customSidecarKnob = knob(kCustomSidecarKnob); if (customSidecarKnob == nullptr) { return manifest; } if (const char* sidecarFilenameSCtr = customSidecarKnob->get_text(&uiContext())) { sidecarFilename = std::string(sidecarFilenameSCtr); manifest = GetManifestSidecar(sidecarFilename); } break; } } if (!sidecarFilename.empty() && manifest.empty()) { const std::string errorMessage = R"(")" + sidecarFilename + R"(" is not a valid sidecar manifest file path)"; error(errorMessage.c_str()); } return manifest; } Enumeration_KnobI* CryptomattePlugin::enumerationKnobI(const char* name) const { if (const auto baseKnob = knob(name)) { return baseKnob->enumerationKnob(); } return nullptr; } static Iop* build(Node* node) { return new CryptomattePlugin(node); } const Iop::Description CryptomattePlugin::kDescription("Cryptomatte", nullptr, build); } }