// 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 { constexpr int kMantissaScaleRed = 1; constexpr int kMantissaScaleGreen = 16; constexpr int kMantissaScaleBlue = 64; const char* const kManifestSourceOptions[] = { "Metadata", "Sidecar", nullptr }; constexpr float Unpremultiply(float coverage, float alpha) { return coverage / (alpha > 0.0f ? alpha : 1.0f); } } namespace Foundry { namespace NukePlugins { using namespace DD::Image; using namespace Cryptomatte; enum ManifestSource : 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 = "manifestSource"; const char* CryptomattePlugin::kSidecarFilepathKnob = "sidecarFilepath"; const char* CryptomattePlugin::kPreviewEnabled = "previewEnabled"; // Name from gizmo const char* CryptomattePlugin::kPickerAddKnob = "pickerAdd"; // Name from gizmo const char* CryptomattePlugin::kPickerRemoveKnob = "pickerRemove"; // Name from gizmo const char* CryptomattePlugin::kMatteListKnob = "matteList"; // Name from gizmo const char* CryptomattePlugin::kClearKnob = "clear"; // Name from gizmo const char* CryptomattePlugin::kMatteOutputKnob = "matteOutput"; // Name from gizmo const char* CryptomattePlugin::kUnpremultiply = "unpremultiply"; // Name from gizmo const char* CryptomattePlugin::kRemoveChannelsKnob = "removeChannels"; const char* CryptomattePlugin::kManifestSourceModifiedKnob = "manifestSourceModified"; const char* CryptomattePlugin::kLastSelectedCryptoLayerNameKnob = "lastSelectedCryptoLayerName"; CryptomattePlugin::CryptomattePlugin(Node* node) : PixelIop(node) { } 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 (_previewEnabled) { 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(). // Note, we don't use _matteList directly to avoid the issue, where // backslashes get removed for a baked value of Multiline_String_knob. const Knob* matteListKnob = knob(kMatteListKnob); if (matteListKnob != nullptr) { const std::string strMatteList(_matteList == nullptr ? "" : _matteList); // Resolve IDs from Matte List for further use in pixel_engine() const MatteList matteList(strMatteList); _resolvedIDs = matteList.resolveIDs(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); Enumeration_knob(callback, &_manifestSourceIndex, kManifestSourceOptions, kManifestSourceKnob, "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.

"); File_knob(callback, &_sidecarFilePath, kSidecarFilepathKnob, ""); ClearFlags(callback, Knob::STARTLINE); Tooltip(callback, "When the Sidecar option is selected as a Manifest Source, use this " "knob to set the path to the sidecar file." "

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

"); Divider(callback); Bool_knob(callback, &_previewEnabled, kPreviewEnabled, "Preview Mattes"); Tooltip(callback, "Preview available and selected mattes. Turn off for RGB pass-through."); Divider(callback); Eyedropper_knob(callback, _pickerAddValue, kPickerAddKnob, "Picker Add"); Tooltip(callback, "Sample objects in a viewer to add to the Matte List."); Text_knob(callback, "Picker Add"); Eyedropper_knob(callback, _pickerRemoveValue, kPickerRemoveKnob, "Picker Remove"); ClearFlags(callback, Knob::CHECKED); Tooltip(callback, "Sample objects in a viewer to remove from the Matte List."); Text_knob(callback, "Picker Remove"); Multiline_String_knob(callback, &_matteList, kMatteListKnob, "Matte List"); Tooltip(callback, "The list of expressions for selected object names." "

Type in object names directly to add their corresponding ID mattes to the output. Use commas " "to create a list of object names to pick in a single line.

" "

Use the wilcard character '*' to select all of the objects matching a given wildcard expression " "throughout the sequence (e.g. flower* can be used to select objects named flower1, flower2 and flower3).

" "

Escape special characters with '\\\\' if they appear in a matte name (e.g. flower\\\\* would allow an object " "named 'flower*' to be selected exactly instead of being resolved as a wildcard expression).

"); Newline(callback); Button(callback, kClearKnob, "Clear"); Tooltip(callback, "Clears all the expressions in the Matte List."); 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."); // This invisible knob is used as a state flag, that determines if manifest source knobs // have been modified by the user. Bool_knob(callback, &_manifestSourceModified, kManifestSourceModifiedKnob); 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); addObsoleteKnobs(callback); } int CryptomattePlugin::knob_changed(Knob* k) { if (k == &Knob::showPanel) { const auto cryptomatteContext = loadCryptomatteContext(); // Update the layer selection knob with list of available cryptomattes updateCryptoLayerChoiceKnob(cryptomatteContext); // Update manifest source knob and sidecar file knob updateManifestSourceKnobs(cryptomatteContext.objects); return 1; } if (k->is(kCryptoLayerChoiceKnob)) { // Each Crypto Layer has its own manifest source properties. When Crypto Layer is changing, // we don't want user's modifications for the current layer being applied to a new layer. // Therefore we reset them to their default values. setManifestSourceModified(false); const auto cryptomatteContext = loadCryptomatteContext(); // Update manifest source knob and sidecar file knob updateManifestSourceKnobs(cryptomatteContext.objects); // Save last user-selected Crypto Layer saveLastSelectedCryptoLayerName(cryptomatteContext); return 1; } if (k->is(kManifestSourceKnob)) { setManifestSourceModified(true); updateSidecarFilenameKnobEnabled(); return 1; } if (k->is(kSidecarFilepathKnob)) { setManifestSourceModified(true); return 1; } if (k->is(kPickerAddKnob) || k->is(kPickerRemoveKnob)) { // Get a bbox of the selected area const Box selectionArea( k->get_value(4), k->get_value(5), k->get_value(6), k->get_value(7) ); // Determine whether Add or Remove const auto selectionMode = k->is(kPickerAddKnob) ? eSelectByTopCoverage : eUnselectByTopCoverage; // Select IDs by the bbox updateMatteListBySelectionArea(selectionArea, selectionMode); return 1; } if (k->is(kClearKnob)) { auto matteListKnob = knob(kMatteListKnob); if (matteListKnob != nullptr) { matteListKnob->set_text(""); 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.uiUpToDate) { updateCryptoLayerChoiceKnob(cryptomatteContext); } 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 (_previewEnabled) { outChannels += Mask_RGB; } out.erase(outChannels); return; } const size_t width = static_cast(r - x); // Stores input channel pointers in a convenient way for Cryptomatte // data processing. std::vector rgbaCoverage[4]; for (const auto& layer : _selectedCryptomatte.layers) { for (const Channel& channel : layer) { rgbaCoverage[colourIndex(channel)].push_back(in[channel] + x); } } // Evaluate total coverage. std::vector totalCoverage(width); for (auto colour : {Chan_Red - 1, Chan_Blue - 1}) { for (size_t i = 0; i < rgbaCoverage[colour].size(); ++i) { for (size_t pixel = 0; pixel < width; ++pixel) { const float id = *(rgbaCoverage[colour][i] + pixel); const float value = *(rgbaCoverage[colour + 1][i] + pixel); if (_resolvedIDs.find(id) != _resolvedIDs.end()) { totalCoverage[pixel] += value; } } } } const float* inAlpha = in[Chan_Alpha] + x; if (_unpremultiplyEnabled) { for (size_t pixel = 0; pixel < width; ++pixel) { totalCoverage[pixel] = Unpremultiply(totalCoverage[pixel], inAlpha[pixel]); } } float* outMatte = out.writable(_matteOutChannel) + x; for (size_t pixel = 0; pixel < width; ++pixel) { outMatte[pixel] = totalCoverage[pixel]; } // Convert obtained pairs into their visual representation (RGB Preview), and output it to RGB channels. // Furthermore, by adding the total coverage to R and G channels the selected mattes get // a bright yellow overlay, so that the user can see what is currently selected. if (_previewEnabled) { // Get pointers to RGB channels. float* outRed = out.writable(Chan_Red) + x; float* outGreen = out.writable(Chan_Green) + x; float* outBlue = out.writable(Chan_Blue) + x; for (size_t pixel = 0; pixel < width; ++pixel) { outRed[pixel] = totalCoverage[pixel]; outGreen[pixel] = totalCoverage[pixel]; outBlue[pixel] = 0; for (auto colour : {Chan_Red - 1, Chan_Blue - 1}) { for (size_t i = 0; i < rgbaCoverage[colour].size(); ++i) { const float id = *(rgbaCoverage[colour][i] + pixel); const float value = *(rgbaCoverage[colour + 1][i] + pixel); outRed[pixel] += ChannelPreview(id, value, kMantissaScaleRed); outGreen[pixel] += ChannelPreview(id, value, kMantissaScaleGreen); outBlue[pixel] += ChannelPreview(id, value, kMantissaScaleBlue); } } } } } void CryptomattePlugin::updateMatteListBySelectionArea(const Box& selectionArea, MatteSelectionMode selectionMode) { const auto cryptoLayerChoiceKnob = knob(kCryptoLayerChoiceKnob); auto matteListKnob = knob(kMatteListKnob); if (cryptoLayerChoiceKnob == nullptr || matteListKnob == nullptr) { return; } 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; } // Process pixels of the Cryptomatte Layer that lay inside the selection are. // For each pixel, read ID-Coverage pairs and find the pair with the highest coverage value. // The found pair would contain the ID that we are aiming to manipulate (select or unselect). const auto& cryptomatte = availableCryptomattes[selectedCryptomatteIndex]; std::set selectedIDs; for (auto it = selectionArea.begin(); it != selectionArea.end(); ++it) { const IDCoveragePair topIDCoverage = GetTopIDCoveragePair(GetIDCoveragePairs(input(0), cryptomatte, it.x, it.y)); selectedIDs.insert(topIDCoverage.id); } // Skip zero value, as this doesn't represent any object ID. // Zero value means that the ID-Coverage pair has no associated object. selectedIDs.erase(0.0f); // Load the manifest from the selected source. const Manifest manifest = getManifestFromSelectedSource(cryptomatte); // Deserialize a MatteList object from a string value of the matte list knob. const char* matteListText = matteListKnob->get_text(&uiContext()); const std::string strOldMatteList = matteListText == nullptr ? "" : matteListText; MatteList matteList(strOldMatteList); // According to the selection mode, selected IDs are either added to // or removed from the MatteList object. if (selectionMode == eSelectByTopCoverage) { matteList.selectByID(selectedIDs.begin(), selectedIDs.end(), manifest); } else { matteList.unselectByID(selectedIDs.begin(), selectedIDs.end(), manifest); } // Serialize modified MatteList object into a string, and // set the string as a new value to the matte list knob. const std::string strNewMatteList = matteList.str(); matteListKnob->set_text(strNewMatteList.c_str()); } CryptomattePlugin::CryptomatteContext CryptomattePlugin::loadCryptomatteContext() const { CryptomatteContext cryptomatteContext; const auto layerChoiceKnob = knob(kCryptoLayerChoiceKnob); if (layerChoiceKnob == nullptr) { return cryptomatteContext; } const Enumeration_KnobI* layerChoiceEnumerationKnob = layerChoiceKnob->enumerationKnob(); if (layerChoiceEnumerationKnob == nullptr) { return cryptomatteContext; } const auto selectedCryptoLayerNameKnob = knob(kLastSelectedCryptoLayerNameKnob); if (selectedCryptoLayerNameKnob == nullptr) { return cryptomatteContext; } // Fetch available Cryptomatte objects from the input 0 cryptomatteContext.objects = GetAvailableCryptomatteObjects(this); // Determine if Cryptomatte objects have been changed since the last update // by comparing those against items from the Layer Selection. auto cryptoLayerMenuItems = layerChoiceEnumerationKnob->menu(); cryptomatteContext.uiUpToDate = cryptoLayerMenuItems.size() == cryptomatteContext.objects.size(); for (size_t i = 0; cryptomatteContext.uiUpToDate && i < cryptoLayerMenuItems.size(); ++i) { cryptomatteContext.uiUpToDate = cryptoLayerMenuItems[i] == cryptomatteContext.objects[i].name; } if (cryptomatteContext.uiUpToDate) { // If the UI is up to date, get the selected index from the corresponding knob const size_t lastIndex = static_cast(layerChoiceKnob->get_value()); cryptomatteContext.selectedIndex = lastIndex; } 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; } } } } return cryptomatteContext; } void CryptomattePlugin::saveLastSelectedCryptoLayerName(const CryptomatteContext& objectsAndState) const { // 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"); } 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::updateManifestSourceKnobs(const std::vector& cryptomatteObjects) { if (cryptomatteObjects.empty()) { return; } const auto layerChoiceKnob = knob(kCryptoLayerChoiceKnob); auto manifestSourceKnob = knob(kManifestSourceKnob); auto sidecarFilenameKnob = knob(kSidecarFilepathKnob); if (layerChoiceKnob == nullptr || manifestSourceKnob == nullptr || sidecarFilenameKnob == nullptr) { return; } // Reset manifest source knobs to their default values, if they haven't been changed by the user yet. if (!getManifestSourceModified()) { const size_t selectedLayerIndex = static_cast(layerChoiceKnob->get_value()); if (selectedLayerIndex < cryptomatteObjects.size()) { const auto& selectedCryptomatte = cryptomatteObjects[selectedLayerIndex]; if (selectedCryptomatte.manifestFile.empty()) { manifestSourceKnob->set_value(static_cast(eEmbeddedManifest)); sidecarFilenameKnob->set_text(""); } else { manifestSourceKnob->set_value(static_cast(eSidecarManifest)); const std::string sidecarFilePath = GetAbsoluteFilePathToSidecarManifest(input(0), selectedCryptomatte); sidecarFilenameKnob->set_text(sidecarFilePath.c_str()); } } } updateSidecarFilenameKnobEnabled(); } void CryptomattePlugin::updateSidecarFilenameKnobEnabled() { const auto manifestSourceKnob = knob(kManifestSourceKnob); if (manifestSourceKnob == nullptr) { return; } auto sidecarFilenameKnob = knob(kSidecarFilepathKnob); if (sidecarFilenameKnob == nullptr) { return; } // Disable the sidecar filename knob by when the manifest is embedded; otherwise enable it. const bool isManifestSidecar = static_cast(manifestSourceKnob->get_value()) == eSidecarManifest; sidecarFilenameKnob->enable(isManifestSidecar); } Manifest CryptomattePlugin::getManifestFromSelectedSource(const CryptomatteObject& cryptomatte) { const auto manifestSourceKnob = knob(kManifestSourceKnob); const auto sidecarFilenameKnob = knob(kSidecarFilepathKnob); if (manifestSourceKnob == nullptr || sidecarFilenameKnob == nullptr) { return Manifest(); } const auto selectedManifestSource = static_cast(manifestSourceKnob->get_value()); const auto manifestSource = static_cast(selectedManifestSource); const char* arrSidecarFilename = sidecarFilenameKnob->get_text(); Manifest manifest; if (manifestSource == eEmbeddedManifest) { manifest = GetManifestEmbedded(input(0), cryptomatte); } else { const std::string sidecarFilename = std::string(arrSidecarFilename == nullptr ? "" : arrSidecarFilename); manifest = GetManifestSidecar(sidecarFilename); if (manifest.empty()) { const std::string errorMessage = R"(")" + sidecarFilename + R"(" is not a valid sidecar manifest file path)"; error(errorMessage.c_str()); } } return manifest; } bool CryptomattePlugin::getManifestSourceModified() const { const auto manifestSourceModifiedKnob = knob(kManifestSourceModifiedKnob); if (manifestSourceModifiedKnob != nullptr) { return static_cast(manifestSourceModifiedKnob->get_value()); } return false; } void CryptomattePlugin::setManifestSourceModified(bool value) { auto manifestSourceModifiedKnob = knob(kManifestSourceModifiedKnob); if (manifestSourceModifiedKnob != nullptr) { manifestSourceModifiedKnob->set_value(static_cast(value)); } } static Iop* build(Node* node) { return new CryptomattePlugin(node); } const Iop::Description CryptomattePlugin::kDescription("Cryptomatte", nullptr, build); } }