From 4aa4d59770ace0f11e9e3a99379fb9697b45e061 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Thu, 14 May 2026 21:54:18 +0800 Subject: [PATCH] feat: hide non-texture OFX params from node graph + host coordinate standardization Hide non-texture OFX params from node graph -------------------------------------------- OFX plugins like ColorCorrect expose dozens of scalar parameters as node inputs, making nodes extremely tall and pushing Source/Mask far down. Previously attempted via kInputFlagHidden, but that also hid them from the parameter panel. Fix: move the filter to NodeViewItem::IsInputValid() instead. For OFX plugin nodes (getPluginInstance() != nullptr), only kTexture inputs are rendered as ports. Scalar parameters remain fully visible in the parameter panel. Files: app/widget/nodeview/nodeviewitem.cpp app/node/plugins/Plugin.cpp Standardize OFX host coordinate system -------------------------------------- Olive's OFX host had partial and inconsistent coordinate handling. 1. Fix Project coordinate methods - getProjectSize() / getProjectExtent() / getProjectOffset() now multiply X by pixel_aspect_ratio(), returning canonical coordinates per the OFX spec. 2. Fix Clip default RoD - OliveClipInstance::getRegionOfDefinition() default now returns {0, 0, width*PAR, height} instead of raw pixel coords. 3. Add parameter coordinate system conversion - DoubleInstance / Double2DInstance / Double3DInstance now check _descriptor.getDefaultCoordinateSystem(). - For kOfxParamCoordinatesNormalised: get: internal pixel value -> normalised (divide by extent) set: normalised plugin value -> pixel (multiply by extent) - DefaultValueForParam() also converts normalised defaults to canonical before storing in Node, keeping Olive internal/UI values consistently in pixel space. Files: app/pluginSupport/OlivePluginInstance.cpp app/pluginSupport/OliveClip.cpp app/pluginSupport/paraminstance.h app/node/plugins/Plugin.cpp --- app/node/plugins/Plugin.cpp | 36 ++++++- app/pluginSupport/OliveClip.cpp | 3 +- app/pluginSupport/OlivePluginInstance.cpp | 15 +-- app/pluginSupport/paraminstance.h | 122 ++++++++++++++++++++-- app/widget/nodeview/nodeviewitem.cpp | 13 ++- 5 files changed, 171 insertions(+), 18 deletions(-) diff --git a/app/node/plugins/Plugin.cpp b/app/node/plugins/Plugin.cpp index 041d0dab7..7cc49ad77 100644 --- a/app/node/plugins/Plugin.cpp +++ b/app/node/plugins/Plugin.cpp @@ -21,6 +21,7 @@ #include "render/rendermanager.h" #include "render/job/pluginjob.h" #include "pluginSupport/OlivePluginInstance.h" +#include "common/Current.h" #include #include @@ -34,6 +35,24 @@ namespace { QHash> g_plugin_param_defaults; +static bool IsNormalisedCoordSystem(const OFX::Host::Param::Base *param) +{ + return param->getDefaultCoordinateSystem() == + kOfxParamCoordinatesNormalised; +} + +static void GetProjectExtent(double &xSize, double &ySize) +{ + auto &vp = Current::getInstance().currentVideoParams(); + xSize = vp.width() * vp.pixel_aspect_ratio().toDouble(); + ySize = vp.height(); +} + +static double ToCanonical(double normalised, double extent) +{ + return extent > 0 ? normalised * extent : normalised; +} + QVariant DefaultValueForParam(const OFX::Host::Param::Base *param) { if (!param) { @@ -50,7 +69,13 @@ QVariant DefaultValueForParam(const OFX::Host::Param::Base *param) return props.getIntProperty(kOfxParamPropDefault) != 0; } if (ofxType == kOfxParamTypeDouble) { - return props.getDoubleProperty(kOfxParamPropDefault); + double val = props.getDoubleProperty(kOfxParamPropDefault); + if (IsNormalisedCoordSystem(param)) { + double xSize, ySize; + GetProjectExtent(xSize, ySize); + val = ToCanonical(val, xSize); + } + return val; } if (ofxType == kOfxParamTypeString || ofxType == kOfxParamTypeStrChoice || @@ -81,6 +106,15 @@ QVariant DefaultValueForParam(const OFX::Host::Param::Base *param) if (is_double) { double values[3] = {0.0, 0.0, 0.0}; props.getDoublePropertyN(kOfxParamPropDefault, values, count); + if (IsNormalisedCoordSystem(param)) { + double xSize, ySize; + GetProjectExtent(xSize, ySize); + values[0] = ToCanonical(values[0], xSize); + values[1] = ToCanonical(values[1], ySize); + if (count == 3) { + values[2] = ToCanonical(values[2], xSize); + } + } if (count == 2) { return QVector2D(values[0], values[1]); } diff --git a/app/pluginSupport/OliveClip.cpp b/app/pluginSupport/OliveClip.cpp index 491299a4b..daeb4cf15 100644 --- a/app/pluginSupport/OliveClip.cpp +++ b/app/pluginSupport/OliveClip.cpp @@ -538,7 +538,8 @@ olive::plugin::OliveClipInstance::getRegionOfDefinition(OfxTime time) const } OfxRectD regionOfDefinition; regionOfDefinition.x1 = regionOfDefinition.y1 = 0; - regionOfDefinition.x2 = params_.width(); + double par = params_.pixel_aspect_ratio().toDouble(); + regionOfDefinition.x2 = params_.width() * par; regionOfDefinition.y2 = params_.height(); return regionOfDefinition; } diff --git a/app/pluginSupport/OlivePluginInstance.cpp b/app/pluginSupport/OlivePluginInstance.cpp index d998d029c..5288a5ce4 100644 --- a/app/pluginSupport/OlivePluginInstance.cpp +++ b/app/pluginSupport/OlivePluginInstance.cpp @@ -276,18 +276,21 @@ OfxStatus OlivePluginInstance::clearPersistentMessage() } void OlivePluginInstance::getProjectSize(double &xSize, double &ySize) const { - xSize =params_.width(); - ySize =params_.height(); + double par = params_.pixel_aspect_ratio().toDouble(); + xSize = params_.width() * par; + ySize = params_.height(); } void OlivePluginInstance::getProjectOffset(double &xOffset, double &yOffset) const { - xOffset =params_.x(); - yOffset =params_.y(); + double par = params_.pixel_aspect_ratio().toDouble(); + xOffset = params_.x() * par; + yOffset = params_.y(); } void OlivePluginInstance::getProjectExtent(double &xSize, double &ySize) const { - xSize =params_.width(); - ySize =params_.height(); + double par = params_.pixel_aspect_ratio().toDouble(); + xSize = params_.width() * par; + ySize = params_.height(); } double OlivePluginInstance::getProjectPixelAspectRatio() const { diff --git a/app/pluginSupport/paraminstance.h b/app/pluginSupport/paraminstance.h index e69aee287..fafa7e372 100644 --- a/app/pluginSupport/paraminstance.h +++ b/app/pluginSupport/paraminstance.h @@ -32,12 +32,38 @@ #include "node/plugins/Plugin.h" #include "core.h" #include "undo/undocommand.h" +#include "common/Current.h" #include #include namespace olive { namespace plugin { + +inline bool IsNormalisedCoordinateSystem( + const OFX::Host::Param::Descriptor &descriptor) +{ + return descriptor.getDefaultCoordinateSystem() == + kOfxParamCoordinatesNormalised; +} + +inline void GetProjectExtent(double &xSize, double &ySize) +{ + auto &vp = Current::getInstance().currentVideoParams(); + xSize = vp.width() * vp.pixel_aspect_ratio().toDouble(); + ySize = vp.height(); +} + +inline double ToNormalised(double canonical, double extent) +{ + return extent > 0 ? canonical / extent : canonical; +} + +inline double ToCanonical(double normalised, double extent) +{ + return extent > 0 ? normalised * extent : normalised; +} + inline QString ParamChangeLabel(const OFX::Host::Param::Descriptor &descriptor) { return QStringLiteral("Change %1") @@ -185,6 +211,11 @@ public: QVariant variant = node->GetStandardValue(_descriptor.getName().c_str()); if (variant.canConvert()) { data = variant.toDouble(); + if (IsNormalisedCoordinateSystem(_descriptor)) { + double xSize, ySize; + GetProjectExtent(xSize, ySize); + data = ToNormalised(data, xSize); + } return kOfxStatOK; } data = 0.0; @@ -201,6 +232,11 @@ public: rational::fromDouble(time)); if (variant.canConvert()) { data = variant.toDouble(); + if (IsNormalisedCoordinateSystem(_descriptor)) { + double xSize, ySize; + GetProjectExtent(xSize, ySize); + data = ToNormalised(data, xSize); + } return kOfxStatOK; } data = 0.0; @@ -213,8 +249,14 @@ public: has_value_ = true; return kOfxStatOK; } + double val = data; + if (IsNormalisedCoordinateSystem(_descriptor)) { + double xSize, ySize; + GetProjectExtent(xSize, ySize); + val = ToCanonical(val, xSize); + } SplitValue split = NodeValue::split_normal_value_into_track_values( - NodeValue::kFloat, data); + NodeValue::kFloat, val); auto command = new NodeParamSetSplitStandardValueCommand( NodeInput(node.get(), _descriptor.getName().c_str()), split); SubmitUndoCommand(node, command, ParamChangeLabel(_descriptor)); @@ -227,10 +269,16 @@ public: has_value_ = true; return kOfxStatOK; } + double val = data; + if (IsNormalisedCoordinateSystem(_descriptor)) { + double xSize, ySize; + GetProjectExtent(xSize, ySize); + val = ToCanonical(val, xSize); + } auto command = new MultiUndoCommand(); Node::SetValueAtTime( NodeInput(node.get(), _descriptor.getName().c_str()), - rational::fromDouble(time), data, 0, command, true); + rational::fromDouble(time), val, 0, command, true); SubmitUndoCommand(node, command, ParamChangeLabel(_descriptor)); return kOfxStatOK; } @@ -661,6 +709,12 @@ public: .value(); x = static_cast(vec.x()); y = static_cast(vec.y()); + if (IsNormalisedCoordinateSystem(_descriptor)) { + double xSize, ySize; + GetProjectExtent(xSize, ySize); + x = ToNormalised(x, xSize); + y = ToNormalised(y, ySize); + } return kOfxStatOK; } OfxStatus get(OfxTime time,double& x,double& y) @@ -680,6 +734,12 @@ public: .value(); x = static_cast(vec.x()); y = static_cast(vec.y()); + if (IsNormalisedCoordinateSystem(_descriptor)) { + double xSize, ySize; + GetProjectExtent(xSize, ySize); + x = ToNormalised(x, xSize); + y = ToNormalised(y, ySize); + } return kOfxStatOK; } OfxStatus set(double x,double y) @@ -690,8 +750,15 @@ public: has_value_ = true; return kOfxStatOK; } + double xv = x, yv = y; + if (IsNormalisedCoordinateSystem(_descriptor)) { + double xSize, ySize; + GetProjectExtent(xSize, ySize); + xv = ToCanonical(xv, xSize); + yv = ToCanonical(yv, ySize); + } SplitValue split = NodeValue::split_normal_value_into_track_values( - NodeValue::kVec2, QVector2D(x, y)); + NodeValue::kVec2, QVector2D(xv, yv)); auto command = new NodeParamSetSplitStandardValueCommand( NodeInput(node.get(), _descriptor.getName().c_str()), split); SubmitUndoCommand(node, command, ParamChangeLabel(_descriptor)); @@ -705,12 +772,19 @@ public: has_value_ = true; return kOfxStatOK; } + double xv = x, yv = y; + if (IsNormalisedCoordinateSystem(_descriptor)) { + double xSize, ySize; + GetProjectExtent(xSize, ySize); + xv = ToCanonical(xv, xSize); + yv = ToCanonical(yv, ySize); + } auto command = new MultiUndoCommand(); const QString name = _descriptor.getName().c_str(); Node::SetValueAtTime(NodeInput(node.get(), name), rational::fromDouble(time), - x, 0, command, true); + xv, 0, command, true); Node::SetValueAtTime(NodeInput(node.get(), name), rational::fromDouble(time), - y, 1, command, true); + yv, 1, command, true); SubmitUndoCommand(node, command, ParamChangeLabel(_descriptor)); return kOfxStatOK; } @@ -844,6 +918,13 @@ public: x = static_cast(vec.x()); y = static_cast(vec.y()); z = static_cast(vec.z()); + if (IsNormalisedCoordinateSystem(_descriptor)) { + double xSize, ySize; + GetProjectExtent(xSize, ySize); + x = ToNormalised(x, xSize); + y = ToNormalised(y, ySize); + z = ToNormalised(z, xSize); + } return kOfxStatOK; } OfxStatus get(OfxTime time,double& x,double& y,double& z) @@ -865,6 +946,13 @@ public: x = static_cast(vec.x()); y = static_cast(vec.y()); z = static_cast(vec.z()); + if (IsNormalisedCoordinateSystem(_descriptor)) { + double xSize, ySize; + GetProjectExtent(xSize, ySize); + x = ToNormalised(x, xSize); + y = ToNormalised(y, ySize); + z = ToNormalised(z, xSize); + } return kOfxStatOK; } OfxStatus set(double x,double y,double z) @@ -876,8 +964,16 @@ public: has_value_ = true; return kOfxStatOK; } + double xv = x, yv = y, zv = z; + if (IsNormalisedCoordinateSystem(_descriptor)) { + double xSize, ySize; + GetProjectExtent(xSize, ySize); + xv = ToCanonical(xv, xSize); + yv = ToCanonical(yv, ySize); + zv = ToCanonical(zv, xSize); + } SplitValue split = NodeValue::split_normal_value_into_track_values( - NodeValue::kVec3, QVector3D(x, y, z)); + NodeValue::kVec3, QVector3D(xv, yv, zv)); auto command = new NodeParamSetSplitStandardValueCommand( NodeInput(node.get(), _descriptor.getName().c_str()), split); SubmitUndoCommand(node, command, ParamChangeLabel(_descriptor)); @@ -892,14 +988,22 @@ public: has_value_ = true; return kOfxStatOK; } + double xv = x, yv = y, zv = z; + if (IsNormalisedCoordinateSystem(_descriptor)) { + double xSize, ySize; + GetProjectExtent(xSize, ySize); + xv = ToCanonical(xv, xSize); + yv = ToCanonical(yv, ySize); + zv = ToCanonical(zv, xSize); + } auto command = new MultiUndoCommand(); const QString name = _descriptor.getName().c_str(); Node::SetValueAtTime(NodeInput(node.get(), name), - rational::fromDouble(time), x, 0, command, true); + rational::fromDouble(time), xv, 0, command, true); Node::SetValueAtTime(NodeInput(node.get(), name), - rational::fromDouble(time), y, 1, command, true); + rational::fromDouble(time), yv, 1, command, true); Node::SetValueAtTime(NodeInput(node.get(), name), - rational::fromDouble(time), z, 2, command, true); + rational::fromDouble(time), zv, 2, command, true); SubmitUndoCommand(node, command, ParamChangeLabel(_descriptor)); return kOfxStatOK; } diff --git a/app/widget/nodeview/nodeviewitem.cpp b/app/widget/nodeview/nodeviewitem.cpp index c086c40b2..533be911c 100644 --- a/app/widget/nodeview/nodeviewitem.cpp +++ b/app/widget/nodeview/nodeviewitem.cpp @@ -31,6 +31,7 @@ #include "config/config.h" #include "core.h" #include "node/nodeundo.h" +#include "node/value.h" #include "pluginSupport/OlivePluginInstance.h" #include "nodeview.h" #include "nodeviewscene.h" @@ -753,7 +754,17 @@ void NodeViewItem::UpdateOutputConnectorPosition() bool NodeViewItem::IsInputValid(const QString &input) { - return node_->IsInputConnectable(input) && !node_->IsInputHidden(input); + if (!node_->IsInputConnectable(input) || node_->IsInputHidden(input)) { + return false; + } + // For OFX plugin nodes, only show texture inputs in the node graph + // to avoid excessively tall nodes with dozens of scalar parameters. + // Scalar parameters are still visible in the parameter panel. + if (node_->getPluginInstance() != nullptr && + node_->GetInputDataType(input) != NodeValue::kTexture) { + return false; + } + return true; } void NodeViewItem::SetRectSize(int height_units)