diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ce373719f..efb80ce273 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Nodes group to have cleaner graphs with hidden sub-nodegraphs - PyPlug: You can export a group as a Python plug-in and it be re-used in any other project as a single node as you would use any other plug-in - SeExpr integration within a node: http://www.disneyanimation.com/technology/seexpr.html +- New SeNoise and SeGrain nodes based on SeExpr - RotoPaint node with Wacom tablets support - DopeSheet editor: This is where you can control easily keyframes and clips in time for motion graphics purposes - Render statistics: Available in the Render menu, use this to debug complex compositions diff --git a/Engine/DiskCacheNode.cpp b/Engine/DiskCacheNode.cpp index eadd033639..2a71b1a5fa 100644 --- a/Engine/DiskCacheNode.cpp +++ b/Engine/DiskCacheNode.cpp @@ -69,7 +69,7 @@ DiskCacheNode::addSupportedBitDepth(std::list* depths } bool -DiskCacheNode::shouldCacheOutput(bool /*isFrameVaryingOrAnimated*/) const +DiskCacheNode::shouldCacheOutput(bool /*isFrameVaryingOrAnimated*/,double /*time*/, int /*view*/) const { return true; } diff --git a/Engine/DiskCacheNode.h b/Engine/DiskCacheNode.h index 7a62a86438..967331ff91 100644 --- a/Engine/DiskCacheNode.h +++ b/Engine/DiskCacheNode.h @@ -135,7 +135,7 @@ class DiskCacheNode : public Natron::OutputEffectInstance virtual Natron::StatusEnum render(const RenderActionArgs& args) OVERRIDE WARN_UNUSED_RETURN; - virtual bool shouldCacheOutput(bool isFrameVaryingOrAnimated) const OVERRIDE FINAL WARN_UNUSED_RETURN; + virtual bool shouldCacheOutput(bool isFrameVaryingOrAnimated, double time, int view) const OVERRIDE FINAL WARN_UNUSED_RETURN; boost::scoped_ptr _imp; }; diff --git a/Engine/EffectInstance.cpp b/Engine/EffectInstance.cpp index 1cac7dff2d..4492f15648 100644 --- a/Engine/EffectInstance.cpp +++ b/Engine/EffectInstance.cpp @@ -369,11 +369,11 @@ EffectInstance::aborted() const } // EffectInstance::aborted bool -EffectInstance::shouldCacheOutput(bool isFrameVaryingOrAnimated) const +EffectInstance::shouldCacheOutput(bool isFrameVaryingOrAnimated,double time, int view) const { boost::shared_ptr n = _node.lock(); - return n->shouldCacheOutput(isFrameVaryingOrAnimated); + return n->shouldCacheOutput(isFrameVaryingOrAnimated, time ,view); } U64 @@ -880,7 +880,7 @@ EffectInstance::getImage(int inputNb, getPreferredDepthAndComponents(inputNb, &prefComps, &prefDepth); assert(!prefComps.empty()); - inputImg = convertPlanesFormatsIfNeeded(getApp(), inputImg, pixelRoI, prefComps.front(), prefDepth, getNode()->usesAlpha0ToConvertFromRGBToRGBA(), outputPremult); + inputImg = convertPlanesFormatsIfNeeded(getApp(), inputImg, pixelRoI, prefComps.front(), prefDepth, getNode()->usesAlpha0ToConvertFromRGBToRGBA(), outputPremult, channelForMask); if (inputImagesThreadLocal.empty()) { ///If the effect is analysis (e.g: Tracker) there's no input images in the tread local storage, hence add it @@ -4209,7 +4209,17 @@ EffectInstance::abortAnyEvaluation() assert(node); node->incrementKnobsAge(); std::list outputNodes; - node->hasOutputNodesConnected(&outputNodes); + + NodeGroup* isGroup = dynamic_cast(this); + if (isGroup) { + std::list inputOutputs; + isGroup->getInputsOutputs(&inputOutputs); + for (std::list::iterator it = inputOutputs.begin(); it!=inputOutputs.end();++it) { + (*it)->hasOutputNodesConnected(&outputNodes); + } + } else { + node->hasOutputNodesConnected(&outputNodes); + } for (std::list::const_iterator it = outputNodes.begin(); it != outputNodes.end(); ++it) { ViewerInstance* isViewer = dynamic_cast(*it); if (isViewer) { diff --git a/Engine/EffectInstance.h b/Engine/EffectInstance.h index 89f6563f1f..fbb198335c 100644 --- a/Engine/EffectInstance.h +++ b/Engine/EffectInstance.h @@ -909,7 +909,7 @@ class EffectInstance const bool dontUpscale, RectI* roiPixel) WARN_UNUSED_RETURN; virtual void aboutToRestoreDefaultValues() OVERRIDE FINAL; - virtual bool shouldCacheOutput(bool isFrameVaryingOrAnimated) const; + virtual bool shouldCacheOutput(bool isFrameVaryingOrAnimated, double time, int view) const; /** * @brief Can be derived to get the region that the plugin is capable of filling. @@ -1684,7 +1684,8 @@ class EffectInstance const ImageComponents& targetComponents, ImageBitDepthEnum targetDepth, bool useAlpha0ForRGBToRGBAConversion, - ImagePremultiplicationEnum outputPremult); + ImagePremultiplicationEnum outputPremult, + int channelForAlpha); /** diff --git a/Engine/EffectInstanceRenderRoI.cpp b/Engine/EffectInstanceRenderRoI.cpp index a325b2352d..19e25e1a5c 100644 --- a/Engine/EffectInstanceRenderRoI.cpp +++ b/Engine/EffectInstanceRenderRoI.cpp @@ -193,7 +193,8 @@ EffectInstance::convertPlanesFormatsIfNeeded(const AppInstance* app, const ImageComponents& targetComponents, ImageBitDepthEnum targetDepth, bool useAlpha0ForRGBToRGBAConversion, - ImagePremultiplicationEnum outputPremult) + ImagePremultiplicationEnum outputPremult, + int channelForAlpha) { bool imageConversionNeeded = targetComponents.getNumComponents() != inputImage->getComponents().getNumComponents() || targetDepth != inputImage->getBitDepth(); if (!imageConversionNeeded) { @@ -216,12 +217,12 @@ EffectInstance::convertPlanesFormatsIfNeeded(const AppInstance* app, inputImage->convertToFormatAlpha0( clippedRoi, app->getDefaultColorSpaceForBitDepth(inputImage->getBitDepth()), app->getDefaultColorSpaceForBitDepth(targetDepth), - -1, false, unPremultIfNeeded, tmp.get() ); + channelForAlpha, false, unPremultIfNeeded, tmp.get() ); } else { inputImage->convertToFormat( clippedRoi, app->getDefaultColorSpaceForBitDepth(inputImage->getBitDepth()), app->getDefaultColorSpaceForBitDepth(targetDepth), - -1, false, unPremultIfNeeded, tmp.get() ); + channelForAlpha, false, unPremultIfNeeded, tmp.get() ); } return tmp; @@ -576,7 +577,7 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, premult = eImagePremultiplicationOpaque; } - ImagePtr tmp = convertPlanesFormatsIfNeeded(app, *it, args.roi, *compIt, inputArgs.bitdepth, useAlpha0ForRGBToRGBAConversion, premult); + ImagePtr tmp = convertPlanesFormatsIfNeeded(app, *it, args.roi, *compIt, inputArgs.bitdepth, useAlpha0ForRGBToRGBAConversion, premult, -1); assert(tmp); convertedPlanes.push_back(tmp); } @@ -678,7 +679,7 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, bool draftModeSupported = getNode()->isDraftModeUsed(); bool isFrameVaryingOrAnimated = isFrameVaryingOrAnimated_Recursive(); - bool createInCache = shouldCacheOutput(isFrameVaryingOrAnimated); + bool createInCache = shouldCacheOutput(isFrameVaryingOrAnimated, args.time, args.view); Natron::ImageKey key(getNode().get(), nodeHash, isFrameVaryingOrAnimated, @@ -1536,7 +1537,7 @@ EffectInstance::renderRoI(const RenderRoIArgs & args, } ///The image might need to be converted to fit the original requested format - it->second.downscaleImage = convertPlanesFormatsIfNeeded(getApp(), it->second.downscaleImage, roi, it->first, args.bitdepth, useAlpha0ForRGBToRGBAConversion, planesToRender.outputPremult); + it->second.downscaleImage = convertPlanesFormatsIfNeeded(getApp(), it->second.downscaleImage, roi, it->first, args.bitdepth, useAlpha0ForRGBToRGBAConversion, planesToRender.outputPremult, -1); assert(it->second.downscaleImage->getComponents() == it->first && it->second.downscaleImage->getBitDepth() == args.bitdepth); outputPlanes->push_back(it->second.downscaleImage); diff --git a/Engine/Knob.cpp b/Engine/Knob.cpp index 3f4427e6b6..cbef12fbf7 100644 --- a/Engine/Knob.cpp +++ b/Engine/Knob.cpp @@ -3692,6 +3692,13 @@ KnobHolder::onDoEndChangesOnMainThreadTriggered() endChanges(); } +ChangesList +KnobHolder::getKnobChanges() const +{ + QMutexLocker l(&_imp->evaluationBlockedMutex); + return _imp->knobChanged; +} + void KnobHolder::endChanges(bool discardEverything) { diff --git a/Engine/Knob.h b/Engine/Knob.h index 9c8253c495..2d8ebd0b9c 100644 --- a/Engine/Knob.h +++ b/Engine/Knob.h @@ -2074,6 +2074,8 @@ class KnobHolder : public QObject **/ void beginChanges(); void endChanges(bool discardEverything = false); + + ChangesList getKnobChanges() const; /** * @brief The virtual portion of notifyProjectBeginValuesChanged(). This is called by the project diff --git a/Engine/Node.cpp b/Engine/Node.cpp index 66e011a44c..f4b8394217 100644 --- a/Engine/Node.cpp +++ b/Engine/Node.cpp @@ -2538,81 +2538,6 @@ Node::initializeKnobs(int renderScaleSupportPref) _imp->nodeSettingsPage = Natron::createKnob(_imp->liveInstance.get(), NATRON_PARAMETER_PAGE_NAME_EXTRA,1,false); if (!isBd) { - - std::vector inputLabels(inputsCount); - std::vector hasMaskChannelSelector(inputsCount); - - for (int i = 0; i < inputsCount; ++i) { - - inputLabels[i] = _imp->liveInstance->getInputLabel(i); - - assert(i < (int)_imp->inputsComponents.size()); - const std::list& inputSupportedComps = _imp->inputsComponents[i]; - - bool isMask = _imp->liveInstance->isInputMask(i); - bool supportsOnlyAlpha = inputSupportedComps.size() == 1 && inputSupportedComps.front().getNumComponents() == 1; - - hasMaskChannelSelector[i] = false; - - if ((isMask || supportsOnlyAlpha) && - !_imp->liveInstance->isInputRotoBrush(i) ) { - - hasMaskChannelSelector[i] = true; - - MaskSelector sel; - boost::shared_ptr enabled = Natron::createKnob(_imp->liveInstance.get(), inputLabels[i],1,false); - - enabled->setDefaultValue(false, 0); - enabled->setAddNewLine(false); - if (isMask) { - std::string enableMaskName(std::string(kEnableMaskKnobName) + "_" + inputLabels[i]); - enabled->setName(enableMaskName); - enabled->setHintToolTip(tr("Enable the mask to come from the channel named by the choice parameter on the right. " - "Turning this off will act as though the mask was disconnected.").toStdString()); - } else { - std::string enableMaskName(std::string(kEnableInputKnobName) + "_" + inputLabels[i]); - enabled->setName(enableMaskName); - enabled->setHintToolTip(tr("Enable the image to come from the channel named by the choice parameter on the right. " - "Turning this off will act as though the input was disconnected.").toStdString()); - } - enabled->setAnimationEnabled(false); - - - sel.enabled = enabled; - - boost::shared_ptr channel = Natron::createKnob(_imp->liveInstance.get(), "",1,false); - - std::vector choices; - choices.push_back("None"); - const ImageComponents& rgba = ImageComponents::getRGBAComponents(); - const std::vector& channels = rgba.getComponentsNames(); - const std::string& layerName = rgba.getComponentsGlobalName(); - for (std::size_t c = 0; c < channels.size(); ++c) { - choices.push_back(layerName + "." + channels[c]); - } - - channel->populateChoices(choices); - channel->setDefaultValue(choices.size() - 1, 0); - channel->setAnimationEnabled(false); - channel->setHintToolTip(tr("Use this channel from the original input to mix the output with the original input. " - "Setting this to None is the same as disconnecting the input.").toStdString()); - if (isMask) { - std::string channelMaskName(std::string(kMaskChannelKnobName) + "_" + inputLabels[i]); - channel->setName(channelMaskName); - } else { - std::string channelMaskName(std::string(kInputChannelKnobName) + "_" + inputLabels[i]); - channel->setName(channelMaskName); - } - sel.channel = channel; - - boost::shared_ptr channelName = Natron::createKnob(_imp->liveInstance.get(), "",1,false); - channelName->setSecretByDefault(true); - channelName->setEvaluateOnChange(false); - sel.channelName = channelName; - _imp->maskSelectors[i] = sel; - - } - } // for (int i = 0; i < inputsCount; ++i) { bool isReaderOrWriterOrTrackerOrGroup = _imp->liveInstance->isReader() || _imp->liveInstance->isWriter() || _imp->liveInstance->isTrackerNode() || dynamic_cast(_imp->liveInstance.get()); @@ -2622,7 +2547,7 @@ Node::initializeKnobs(int renderScaleSupportPref) ///find in all knobs a page param to set this param into boost::shared_ptr mainPage; const std::vector< boost::shared_ptr > & knobs = _imp->liveInstance->getKnobs(); - + if (!isReaderOrWriterOrTrackerOrGroup) { for (U32 i = 0; i < knobs.size(); ++i) { boost::shared_ptr p = boost::dynamic_pointer_cast(knobs[i]); @@ -2637,29 +2562,61 @@ Node::initializeKnobs(int renderScaleSupportPref) } assert(mainPage); } + + ///Pair hasMaskChannelSelector, isMask + std::vector > hasMaskChannelSelector(inputsCount); + std::vector inputLabels(inputsCount); + for (int i = 0; i < inputsCount; ++i) { + inputLabels[i] = _imp->liveInstance->getInputLabel(i); + + assert(i < (int)_imp->inputsComponents.size()); + const std::list& inputSupportedComps = _imp->inputsComponents[i]; + + bool isMask = _imp->liveInstance->isInputMask(i); + bool supportsOnlyAlpha = inputSupportedComps.size() == 1 && inputSupportedComps.front().getNumComponents() == 1; + + hasMaskChannelSelector[i].first = false; + hasMaskChannelSelector[i].second = isMask; + + if ((isMask || supportsOnlyAlpha) && + !_imp->liveInstance->isInputRotoBrush(i) ) { + hasMaskChannelSelector[i].first = true; + } + } + + if (useChannels) { + + bool useSelectors = !dynamic_cast(_imp->liveInstance.get()); + if (useSelectors) { - //There are a A and B inputs and the plug-in is not multi-planar, propose 2 layer selectors for the inputs. + boost::shared_ptr sep = Natron::createKnob(_imp->liveInstance.get(), "Advanced", 1, false); + mainPage->addKnob(sep); + + ///Create input layer selectors for (int i = 0; i < inputsCount; ++i) { - if (!hasMaskChannelSelector[i]) { + if (!hasMaskChannelSelector[i].first) { _imp->createChannelSelector(i,inputLabels[i], false, mainPage); } } + ///Create output layer selectors _imp->createChannelSelector(-1, "Output", true, mainPage); } - - //Try to find R,G,B,A parameters on the plug-in, if found, use them, otherwise create them - std::string channelLabels[4] = {kNatronOfxParamProcessRLabel, kNatronOfxParamProcessGLabel, kNatronOfxParamProcessBLabel, kNatronOfxParamProcessALabel}; - std::string channelNames[4] = {kNatronOfxParamProcessR, kNatronOfxParamProcessG, kNatronOfxParamProcessB, kNatronOfxParamProcessA}; - std::string channelHints[4] = {kNatronOfxParamProcessRHint, kNatronOfxParamProcessGHint, kNatronOfxParamProcessBHint, kNatronOfxParamProcessAHint}; - - - bool pluginDefaultPref[4]; + + + + //Try to find R,G,B,A parameters on the plug-in, if found, use them, otherwise create them + std::string channelLabels[4] = {kNatronOfxParamProcessRLabel, kNatronOfxParamProcessGLabel, kNatronOfxParamProcessBLabel, kNatronOfxParamProcessALabel}; + std::string channelNames[4] = {kNatronOfxParamProcessR, kNatronOfxParamProcessG, kNatronOfxParamProcessB, kNatronOfxParamProcessA}; + std::string channelHints[4] = {kNatronOfxParamProcessRHint, kNatronOfxParamProcessGHint, kNatronOfxParamProcessBHint, kNatronOfxParamProcessAHint}; + + + bool pluginDefaultPref[4]; bool useRGBACheckbox = _imp->liveInstance->isHostChannelSelectorSupported(&pluginDefaultPref[0], &pluginDefaultPref[1], &pluginDefaultPref[2], &pluginDefaultPref[3]); boost::shared_ptr foundEnabled[4]; for (int i = 0; i < 4; ++i) { @@ -2671,11 +2628,15 @@ Node::initializeKnobs(int renderScaleSupportPref) } } } + if (foundEnabled[0] && foundEnabled[1] && foundEnabled[2] && foundEnabled[3]) { - _imp->enabledChan[0] = foundEnabled[0]; - _imp->enabledChan[1] = foundEnabled[1]; - _imp->enabledChan[2] = foundEnabled[2]; - _imp->enabledChan[3] = foundEnabled[3]; + for (int i = 0; i < 4; ++i) { + if (foundEnabled[i]->getParentKnob() == mainPage) { + mainPage->removeKnob(foundEnabled[i].get()); + mainPage->addKnob(foundEnabled[i]); + } + _imp->enabledChan[i] = foundEnabled[i]; + } } #ifdef DEBUG if (foundEnabled[0] && foundEnabled[1] && foundEnabled[2] && foundEnabled[3] && useRGBACheckbox) { @@ -2690,13 +2651,110 @@ Node::initializeKnobs(int renderScaleSupportPref) foundEnabled[i]->setAddNewLine(i == 3); foundEnabled[i]->setDefaultValue(pluginDefaultPref[i]); foundEnabled[i]->setHintToolTip(channelHints[i]); - mainPage->insertKnob(i,foundEnabled[i]); + mainPage->addKnob(foundEnabled[i]); _imp->enabledChan[i] = foundEnabled[i]; } } + } // useChannels - //Create the mix + ///Find in the plug-in the Mask/Mix related parameter to re-order them so it is consistent across nodes + std::vector > > foundPluginDefaultKnobsToReorder; + foundPluginDefaultKnobsToReorder.push_back(std::make_pair(kOfxMaskInvertParamName, boost::shared_ptr())); + foundPluginDefaultKnobsToReorder.push_back(std::make_pair(kOfxMixParamName, boost::shared_ptr())); + if (mainPage) { + ///Insert auto-added knobs before mask invert if found + for (std::size_t i = 0; i < knobs.size(); ++i) { + for (std::size_t j = 0; j < foundPluginDefaultKnobsToReorder.size(); ++j) { + if (knobs[i]->getName() == foundPluginDefaultKnobsToReorder[j].first) { + foundPluginDefaultKnobsToReorder[j].second = knobs[i]; + } + } + } + } + + ///Create mask selectors + for (int i = 0; i < inputsCount; ++i) { + + if (!hasMaskChannelSelector[i].first) { + continue; + } + + + MaskSelector sel; + boost::shared_ptr enabled = Natron::createKnob(_imp->liveInstance.get(), inputLabels[i],1,false); + + enabled->setDefaultValue(false, 0); + enabled->setAddNewLine(false); + if (hasMaskChannelSelector[i].second) { + std::string enableMaskName(std::string(kEnableMaskKnobName) + "_" + inputLabels[i]); + enabled->setName(enableMaskName); + enabled->setHintToolTip(tr("Enable the mask to come from the channel named by the choice parameter on the right. " + "Turning this off will act as though the mask was disconnected.").toStdString()); + } else { + std::string enableMaskName(std::string(kEnableInputKnobName) + "_" + inputLabels[i]); + enabled->setName(enableMaskName); + enabled->setHintToolTip(tr("Enable the image to come from the channel named by the choice parameter on the right. " + "Turning this off will act as though the input was disconnected.").toStdString()); + } + enabled->setAnimationEnabled(false); + if (mainPage) { + mainPage->addKnob(enabled); + } + + + sel.enabled = enabled; + + boost::shared_ptr channel = Natron::createKnob(_imp->liveInstance.get(), "",1,false); + + std::vector choices; + choices.push_back("None"); + const ImageComponents& rgba = ImageComponents::getRGBAComponents(); + const std::vector& channels = rgba.getComponentsNames(); + const std::string& layerName = rgba.getComponentsGlobalName(); + for (std::size_t c = 0; c < channels.size(); ++c) { + choices.push_back(layerName + "." + channels[c]); + } + + channel->populateChoices(choices); + channel->setDefaultValue(choices.size() - 1, 0); + channel->setAnimationEnabled(false); + channel->setHintToolTip(tr("Use this channel from the original input to mix the output with the original input. " + "Setting this to None is the same as disconnecting the input.").toStdString()); + if (hasMaskChannelSelector[i].second) { + std::string channelMaskName(std::string(kMaskChannelKnobName) + "_" + inputLabels[i]); + channel->setName(channelMaskName); + } else { + std::string channelMaskName(std::string(kInputChannelKnobName) + "_" + inputLabels[i]); + channel->setName(channelMaskName); + } + sel.channel = channel; + channel->setAddNewLine(false); + if (mainPage) { + mainPage->addKnob(channel); + } + + boost::shared_ptr channelName = Natron::createKnob(_imp->liveInstance.get(), "",1,false); + channelName->setSecretByDefault(true); + channelName->setEvaluateOnChange(false); + if (mainPage) { + mainPage->addKnob(channelName); + } + sel.channelName = channelName; + + //Make sure the first default param in the vector is MaskInvert + assert(foundPluginDefaultKnobsToReorder.size() > 0 && foundPluginDefaultKnobsToReorder[0].first == kOfxMaskInvertParamName); + if (foundPluginDefaultKnobsToReorder[0].second) { + //If there is a MaskInvert parameter, make it on the same line as the Mask channel parameter + channelName->setAddNewLine(false); + } + + + _imp->maskSelectors[i] = sel; + + } // for (int i = 0; i < inputsCount; ++i) { + + //Create the host mix if needed if (!isReaderOrWriterOrTrackerOrGroup && _imp->liveInstance->isHostMixingEnabled()) { boost::shared_ptr mixKnob = Natron::createKnob(_imp->liveInstance.get(), "Mix", 1, false); mixKnob->setName("hostMix"); @@ -2704,9 +2762,25 @@ Node::initializeKnobs(int renderScaleSupportPref) mixKnob->setMinimum(0.); mixKnob->setMaximum(1.); mixKnob->setDefaultValue(1.); - mainPage->addKnob(mixKnob); + if (mainPage) { + mainPage->addKnob(mixKnob); + } _imp->mixWithSource = mixKnob; } + + + /* + * Reposition the MaskInvert and Mix parameters declared by the plug-in + */ + if (mainPage) { + for (std::size_t i = 0; i < foundPluginDefaultKnobsToReorder.size(); ++i) { + if (foundPluginDefaultKnobsToReorder[i].second && foundPluginDefaultKnobsToReorder[i].second->getParentKnob() == mainPage) { + mainPage->removeKnob(foundPluginDefaultKnobsToReorder[i].second.get()); + mainPage->addKnob(foundPluginDefaultKnobsToReorder[i].second); + } + } + } + } // !isBd boost::shared_ptr nodeLabel = Natron::createKnob(_imp->liveInstance.get(), isBd ? tr("Name label").toStdString() : tr("Label").toStdString(),1,false); @@ -3115,7 +3189,7 @@ Node::hasOutputNodesConnected(std::list* writers { Natron::OutputEffectInstance* thisWriter = dynamic_cast(_imp->liveInstance.get()); - if (thisWriter && thisWriter->isOutput()) { + if (thisWriter && thisWriter->isOutput() && !dynamic_cast(thisWriter)) { std::list::const_iterator alreadyExists = std::find(writers->begin(), writers->end(), thisWriter); if ( alreadyExists == writers->end() ) { writers->push_back(thisWriter); @@ -5880,7 +5954,11 @@ Node::onEffectKnobValueChanged(KnobI* what, KnobString* strKnob = dynamic_cast(what); if (strKnob) { QString operation = strKnob->getValue().c_str(); - replaceCustomDataInlabel('(' + operation + ')'); + if (!operation.isEmpty()) { + operation.prepend("("); + operation.append(")"); + } + replaceCustomDataInlabel(operation); } } else if ( (what->getName() == kOfxImageEffectFileParamName) && _imp->liveInstance->isReader() ) { ///Refresh the preview automatically if the filename changed @@ -6104,7 +6182,10 @@ Node::Implementation::onMaskSelectorChanged(int inputNb,const MaskSelector& sele std::vector entries = channel->getEntries_mt_safe(); int curChan_i = channel->getValue(); - assert(curChan_i >= 0 && curChan_i < (int)entries.size()); + if (curChan_i < 0 || curChan_i >= (int)entries.size()) { + _publicInterface->refreshChannelSelectors(true); + return; + } selector.channelName.lock()->setValue(entries[curChan_i], 0); { ///Clip preferences have changed @@ -6627,31 +6708,69 @@ Node::dequeueActions() } } -bool -Node::shouldCacheOutput(bool isFrameVaryingOrAnimated) const +static void addIdentityNodesRecursively(const Node* caller, + const Node* node, + double time, + int view, + std::list* outputs, + std::list* markedNodes) { - /* - * Here is a list of reasons when caching is enabled for a node: - * - It is references multiple times below in the graph - * - Its single output has its settings panel opened, meaning the user is actively editing the output - * - The force caching parameter in the "Node" tab is checked - * - The aggressive caching preference of Natron is checked - * - We are in a recursive action (such as an analysis) - * - The plug-in does temporal clip access - * - Preview image is enabled (and Natron is not running in background) - * - The node is a direct input of a viewer, this is to overcome linear graphs where all nodes would not be cached - * - The node is not frame varying, meaning it will always produce the same image at any time - * - The node is a roto node and it is being edited - * - The node does not support tiles - */ + if (std::find(markedNodes->begin(), markedNodes->end(), node) != markedNodes->end()) { + return; + } + + markedNodes->push_back(node); - std::list outputs; - { - QMutexLocker k(&_imp->outputsMutex); - outputs = _imp->outputs; + + if (caller != node) { + + const ParallelRenderArgs* inputFrameArgs = node->getLiveInstance()->getParallelRenderArgsTLS(); + const FrameViewRequest* request = 0; + bool isIdentity = false; + if (inputFrameArgs && inputFrameArgs->request) { + request = inputFrameArgs->request->getFrameViewRequest(time, view); + if (request) { + isIdentity = request->globalData.identityInputNb != -1; + } + } + + if (!request) { + + /* + Very unlikely that there's no request pass. But we still check + */ + RenderScale scale; + scale.x = scale.y = 1; + double inputTimeId; + int inputNbId; + U64 renderHash; + + renderHash = node->getLiveInstance()->getRenderHash(); + + RectD rod; + bool isProj; + Natron::StatusEnum stat = node->getLiveInstance()->getRegionOfDefinition_public(renderHash, time, scale, view, &rod, &isProj); + if (stat == eStatusFailed) { + isIdentity = false; + } else { + RectI pixelRod; + rod.toPixelEnclosing(scale, node->getLiveInstance()->getPreferredAspectRatio(), &pixelRod); + isIdentity = node->getLiveInstance()->isIdentity_public(true, renderHash, time, scale, pixelRod, view, &inputTimeId, &inputNbId); + } + } + + + if (!isIdentity) { + outputs->push_back(node); + return; + } } + + ///Append outputs of this node instead + std::list nodeOutputs; + node->getOutputs_mt_safe(nodeOutputs); std::list outputsToAdd; - for (std::list::iterator it = outputs.begin(); it != outputs.end(); ++it) { + for (std::list::iterator it = nodeOutputs.begin(); it != nodeOutputs.end(); ++it) { GroupOutput* isOutputNode = dynamic_cast((*it)->getLiveInstance()); //If the node is an output node, add all the outputs of the group node instead if (isOutputNode) { @@ -6667,7 +6786,36 @@ Node::shouldCacheOutput(bool isFrameVaryingOrAnimated) const } } } - outputs.insert(outputs.end(), outputsToAdd.begin(),outputsToAdd.end()); + nodeOutputs.insert(nodeOutputs.end(), outputsToAdd.begin(),outputsToAdd.end()); + for (std::list::iterator it = nodeOutputs.begin(); it!=nodeOutputs.end(); ++it) { + addIdentityNodesRecursively(caller,*it,time,view,outputs, markedNodes); + } +} + +bool +Node::shouldCacheOutput(bool isFrameVaryingOrAnimated, double time, int view) const +{ + /* + * Here is a list of reasons when caching is enabled for a node: + * - It is references multiple times below in the graph + * - Its single output has its settings panel opened, meaning the user is actively editing the output + * - The force caching parameter in the "Node" tab is checked + * - The aggressive caching preference of Natron is checked + * - We are in a recursive action (such as an analysis) + * - The plug-in does temporal clip access + * - Preview image is enabled (and Natron is not running in background) + * - The node is a direct input of a viewer, this is to overcome linear graphs where all nodes would not be cached + * - The node is not frame varying, meaning it will always produce the same image at any time + * - The node is a roto node and it is being edited + * - The node does not support tiles + */ + + std::list outputs; + { + std::list markedNodes; + addIdentityNodesRecursively(this, this, time, view,&outputs,&markedNodes); + } + std::size_t sz = outputs.size(); if (sz > 1) { @@ -6676,7 +6824,7 @@ Node::shouldCacheOutput(bool isFrameVaryingOrAnimated) const } else { if (sz == 1) { - Node* output = outputs.front(); + const Node* output = outputs.front(); ViewerInstance* isViewer = dynamic_cast(output->getLiveInstance()); if (isViewer) { @@ -7001,6 +7149,26 @@ Node::refreshInputRelatedDataRecursiveInternal(std::list& markedN } } + /* + If this node is the bottom node of the rotopaint tree, forward directly to the outputs of the Rotopaint node + */ + boost::shared_ptr attachedItem = _imp->paintStroke.lock(); + if (attachedItem) { + NodePtr rotoPaintNode = attachedItem->getContext()->getNode(); + assert(rotoPaintNode); + boost::shared_ptr context = rotoPaintNode->getRotoContext(); + assert(context); + NodePtr bottomMerge = context->getRotoPaintBottomMergeNode(); + if (bottomMerge.get() == this) { + std::list outputs; + rotoPaintNode->getOutputsWithGroupRedirection(outputs); + for (std::list::const_iterator it = outputs.begin(); it != outputs.end(); ++it) { + (*it)->refreshInputRelatedDataRecursiveInternal( markedNodes ); + } + return; + } + } + ///Now notify outputs we have changed std::list outputs; getOutputsWithGroupRedirection(outputs); diff --git a/Engine/Node.h b/Engine/Node.h index 4bd8a1c97e..d65bc43343 100644 --- a/Engine/Node.h +++ b/Engine/Node.h @@ -60,6 +60,9 @@ CLANG_DIAG_ON(deprecated) #define kEnablePreviewKnobName "enablePreview" #define kOutputChannelsKnobName "channels" +#define kOfxMaskInvertParamName "maskInvert" +#define kOfxMixParamName "mix" + class AppInstance; class NodeSettingsPanel; class KnobI; @@ -543,7 +546,7 @@ GCC_DIAG_SUGGEST_OVERRIDE_ON bool isUserSelected() const; - bool shouldCacheOutput(bool isFrameVaryingOrAnimated) const; + bool shouldCacheOutput(bool isFrameVaryingOrAnimated, double time, int view) const; /** * @brief If the session is a GUI session, then this function sets the position of the node on the nodegraph. diff --git a/Engine/OfxClipInstance.cpp b/Engine/OfxClipInstance.cpp index 79ab463124..c38d8d44a7 100644 --- a/Engine/OfxClipInstance.cpp +++ b/Engine/OfxClipInstance.cpp @@ -132,9 +132,9 @@ OfxClipInstance::getUnmappedComponents() const static const std::string alphaStr(kOfxImageComponentAlpha); EffectInstance* inputNode = getAssociatedNode(); - if (!isOutput() && inputNode) { + /*if (!isOutput() && inputNode) { inputNode = inputNode->getNearestNonIdentity(_nodeInstance->getApp()->getTimeLine()->currentFrame()); - } + }*/ if (inputNode) { ///Get the input node's output preferred bit depth and componentns std::list comps; diff --git a/Engine/OfxEffectInstance.cpp b/Engine/OfxEffectInstance.cpp index eff756deed..5e36fc9fb4 100644 --- a/Engine/OfxEffectInstance.cpp +++ b/Engine/OfxEffectInstance.cpp @@ -2435,11 +2435,11 @@ OfxEffectInstance::natronValueChangedReasonToOfxValueChangedReason(Natron::Value switch (reason) { case Natron::eValueChangedReasonUserEdited: case Natron::eValueChangedReasonNatronGuiEdited: + case Natron::eValueChangedReasonSlaveRefresh: + case Natron::eValueChangedReasonRestoreDefault: return kOfxChangeUserEdited; case Natron::eValueChangedReasonPluginEdited: case Natron::eValueChangedReasonNatronInternalEdited: - case Natron::eValueChangedReasonSlaveRefresh: - case Natron::eValueChangedReasonRestoreDefault: return kOfxChangePluginEdited; case Natron::eValueChangedReasonTimeChanged: return kOfxChangeTime; diff --git a/Gui/KnobUndoCommand.cpp b/Gui/KnobUndoCommand.cpp index 43489118d4..67c1ca6ea8 100644 --- a/Gui/KnobUndoCommand.cpp +++ b/Gui/KnobUndoCommand.cpp @@ -531,6 +531,7 @@ RestoreDefaultsCommand::redo() KnobHolder* holder = first->getHolder(); if (holder && holder->getApp()) { timeline = holder->getApp()->getTimeLine(); + holder->beginChanges(); } for (std::list >::iterator it = _knobs.begin(); it != _knobs.end(); ++it) { @@ -550,13 +551,39 @@ RestoreDefaultsCommand::redo() if ((*it)->getHolder()) { (*it)->getHolder()->beginChanges(); } + (*it)->blockValueChanges(); + for (int d = 0; d < (*it)->getDimension(); ++d) { (*it)->resetToDefaultValue(d); } + + (*it)->unblockValueChanges(); + if ((*it)->getHolder()) { (*it)->getHolder()->endChanges(true); } + + } + + /* + Block value changes and call instanceChange on all knobs afterwards to put back the plug-in + in a correct state + */ + int time = 0; + if (timeline) { + time = timeline->currentFrame(); + } + for (std::list >::iterator it = _knobs.begin(); it != _knobs.end(); ++it) { + if ((*it)->getHolder()) { + (*it)->getHolder()->onKnobValueChanged_public(it->get(), Natron::eValueChangedReasonRestoreDefault, time, true); + } + } + + if (holder && holder->getApp()) { + holder->endChanges(); } + + if (timeline) { timeline->removeMultipleKeyframeIndicator(times,true); } diff --git a/Gui/NodeGraph25.cpp b/Gui/NodeGraph25.cpp index c62e2ebc6d..9cb0f6cec1 100644 --- a/Gui/NodeGraph25.cpp +++ b/Gui/NodeGraph25.cpp @@ -285,7 +285,7 @@ NodeGraph::keyPressEvent(QKeyEvent* e) createGroupFromSelection(); } else if ( isKeybind(kShortcutGroupNodegraph, kShortcutIDActionGraphExpandGroup, modifiers, key) ) { expandSelectedGroups(); - } else if (key == Qt::Key_Control && modCASIsNone(e)) { + } else if (key == Qt::Key_Control && e->modifiers() == Qt::ControlModifier) { _imp->setNodesBendPointsVisible(true); accept = false; } else if ( isKeybind(kShortcutGroupNodegraph, kShortcutIDActionGraphSelectUp, modifiers, key) || diff --git a/Gui/NodeGraph30.cpp b/Gui/NodeGraph30.cpp index 88eed4fdf3..d45768ea1c 100644 --- a/Gui/NodeGraph30.cpp +++ b/Gui/NodeGraph30.cpp @@ -83,8 +83,12 @@ NodeGraph::connectCurrentViewerToSelection(int inputNB) } - if ( !lastUsedViewer ) { - getGui()->getApp()->createNode( CreateNodeArgs(PLUGINID_NATRON_VIEWER, + boost::shared_ptr v; + if ( lastUsedViewer ) { + v = boost::dynamic_pointer_cast( lastUsedViewer-> + getInternalNode()->getNode() ); + } else { + NodePtr viewerNode = getGui()->getApp()->createNode( CreateNodeArgs(PLUGINID_NATRON_VIEWER, "", -1,-1, true, @@ -95,12 +99,16 @@ NodeGraph::connectCurrentViewerToSelection(int inputNB) QString(), CreateNodeArgs::DefaultValuesList(), getGroup()) ); + if (!viewerNode) { + return; + } + v = boost::dynamic_pointer_cast(viewerNode); } - ///get a pointer to the last user selected viewer - boost::shared_ptr v = boost::dynamic_pointer_cast( lastUsedViewer-> - getInternalNode()->getNode() ); - + if (!v) { + return; + } + ///if the node is no longer active (i.e: it was deleted by the user), don't do anything. if ( !v->isActivated() ) { return;