Skip to content

Instantly share code, notes, and snippets.

@herronelou
Created December 21, 2024 23:04
Show Gist options
  • Save herronelou/88c656fd449f14ccfe9f7658686dd8fd to your computer and use it in GitHub Desktop.
Save herronelou/88c656fd449f14ccfe9f7658686dd8fd to your computer and use it in GitHub Desktop.
Custom (Useless) plugin for Nuke with Custom Qt Knob and Python API
cmake_minimum_required(VERSION 3.10 FATAL_ERROR)
project(MyPlugin)
set(CMAKE_MODULE_PATH "CMake;${CMAKE_MODULE_PATH}")
set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
# Set the Qt5_DIR path to the correct location
set(Qt5_DIR "C:/Users/herro/Documents/NDK/Qt/nuke14.1/lib/cmake/Qt5")
find_package(Python3 COMPONENTS Interpreter Development)
if (Python3_FOUND)
message(STATUS "Python found")
else()
message(STATUS "Python Not found")
endif()
find_package(Nuke REQUIRED)
find_package(Qt5 COMPONENTS Core Gui Widgets)
set(CMAKE_AUTOMOC ON)
add_nuke_plugin(MyPlugin MyPlugin.cpp)
target_sources(MyPlugin PRIVATE MyPlugin.moc.h CryptomatteLayerWidget.moc.h)
target_link_libraries(MyPlugin PRIVATE Python3::Module Qt5::Core Qt5::Gui Qt5::Widgets)
static const char* const HELP = "MyPlugins a constant to a set of channels";
#include "Python.h"
#include "MyPlugin.moc.h"
#include "DDImage/PixelIop.h"
#include "DDImage/Row.h"
#include "DDImage/Knobs.h"
#include <QtCore/QObject>
#include <QtWidgets/QWidget>
#include <QtWidgets/QTabWidget>
#include <QtGui/QPainter>
#include <QtGui/QMouseEvent>
#include <sstream>
#include <iomanip>
using namespace DD::Image;
// Forward declare classes
class MyKnob;
class MyWidget;
// Update the Python object structure to match Foundry's pattern
struct MyKnobObject {
PyObject_HEAD
MyKnob* _knob;
};
static PyObject* MyKnobNew(PyTypeObject* type, PyObject* args, PyObject* kwargs)
{
Py_RETURN_NONE;
}
static void MyKnobDealloc(PyObject* self)
{
}
// Python docstrings
PyDoc_STRVAR(MyKnobSetValue__doc__,
"self.setValue(value=False) -> None\n\n"
"Set the toggle value of the knob.\n"
"@param value: Boolean parameter for specifying the toggle state.\n"
"@return: None");
PyDoc_STRVAR(MyKnobGetValue__doc__,
"self.getValue() -> bool\n\n"
"Get the current toggle state of this knob.\n"
"@return: Boolean value.");
PyDoc_STRVAR(MyKnobToggle__doc__,
"self.toggle() -> None\n\n"
"Toggle the value of the knob: True if current value was False, and vice-versa.\n"
"@return: None");
// Forward declare the Python methods (will be implemented after MyKnob class definition)
static PyObject* MyKnobSetValue(MyKnobObject* self, PyObject* args, PyObject* kwds);
static PyObject* MyKnobGetValue(MyKnobObject* self, PyObject* args, PyObject* kwds);
static PyObject* MyKnobToggle(MyKnobObject* self, PyObject* args, PyObject* kwds); // This one just to show we're not limited to methods on default knobs
// Python method definitions
static PyMethodDef MyKnobMethods[] = {
{"setValue", reinterpret_cast<PyCFunction>(MyKnobSetValue), METH_VARARGS, MyKnobSetValue__doc__},
{"getValue", reinterpret_cast<PyCFunction>(MyKnobGetValue), METH_NOARGS, MyKnobGetValue__doc__},
{"value", reinterpret_cast<PyCFunction>(MyKnobGetValue), METH_NOARGS, MyKnobGetValue__doc__},
{"toggle", reinterpret_cast<PyCFunction>(MyKnobToggle), METH_NOARGS, MyKnobToggle__doc__},
{nullptr, nullptr, 0, nullptr}
};
// Initialize the Python type object
// Python type object
PyTypeObject MyKnobPythonType = []() {
PyTypeObject result;
memset(&result, 0, sizeof(PyTypeObject));
result.tp_name = "MyKnob";
result.tp_basicsize = sizeof(MyKnobObject);
result.tp_itemsize = 0;
result.tp_dealloc = (destructor)MyKnobDealloc;
result.tp_getattro = PyObject_GenericGetAttr;
result.tp_setattro = PyObject_GenericSetAttr;
result.tp_flags = Py_TPFLAGS_DEFAULT;
//result.tp_doc = "Custom toggle knob type";
result.tp_methods = MyKnobMethods;
result.tp_alloc = PyType_GenericAlloc;
result.tp_new = MyKnobNew;
result.tp_free = PyObject_Del;
return result;
}();
class MyKnob : public DD::Image::Knob,
public DD::Image::PluginPython_KnobI
{
friend class MyWidget;
friend PyObject* MyKnobGetValue(MyKnobObject*, PyObject*, PyObject*);
friend PyObject* MyKnobToggle(MyKnobObject*, PyObject*, PyObject*);
bool _data;
public:
MyKnob(DD::Image::Knob_Closure* kc, bool* data, const char* n) : Knob(kc, n)
{
_data = data ? *data : false;
setPythonType(&MyKnobPythonType);
}
// Required override for Python integration
DD::Image::PluginPython_KnobI* pluginPythonKnob() override
{
return this;
}
virtual const char* Class() const { return "MyKnob"; }
virtual bool not_default() const { return _data != false; }
virtual void to_script(std::ostream& os, const OutputContext*, bool quote) const
{
os << (_data ? "true" : "false");
}
virtual bool from_script(const char* v)
{
_data = (strcmp(v, "true") == 0);
changed();
return true;
}
void store(StoreType type, void* data, Hash& hash, const OutputContext& oc)
{
bool* destData = (bool*)data;
*destData = _data;
hash.append(_data);
}
void setValue(bool value)
{
new_undo("setValue");
_data = value;
changed();
}
virtual WidgetPointer make_widget(const DD::Image::WidgetContext& context)
{
MyWidget* widget = new MyWidget(this);
return widget;
}
};
// Now implement the previously forward-declared Python methods
static PyObject* MyKnobSetValue(MyKnobObject* self, PyObject* args, PyObject* kwds)
{
PyObject* valueObj = nullptr;
if (!PyArg_ParseTuple(args, "|O:setValue", &valueObj)) {
return nullptr;
}
bool value = false;
if (valueObj != nullptr) {
value = PyObject_IsTrue(valueObj);
if (value == -1) {
PyErr_SetString(PyExc_ValueError, "Invalid boolean value");
return nullptr;
}
}
MyKnob* myKnob = self->_knob;
if (myKnob != nullptr) {
myKnob->setValue(value);
}
Py_RETURN_NONE;
}
static PyObject* MyKnobGetValue(MyKnobObject* self, PyObject* args, PyObject* kwds)
{
const MyKnob* myKnob = self->_knob;
bool value = false;
if (myKnob != nullptr) {
value = myKnob->_data;
}
if (value) {
Py_RETURN_TRUE;
}
else {
Py_RETURN_FALSE;
}
}
static PyObject* MyKnobToggle(MyKnobObject* self, PyObject* args, PyObject* kwds)
{
MyKnob* myKnob = self->_knob;
if (myKnob != nullptr) {
myKnob->setValue(!myKnob->_data);
}
if (myKnob->_data) {
Py_RETURN_TRUE;
}
else {
Py_RETURN_FALSE;
}
}
MyWidget::MyWidget(MyKnob* knob) : _knob(knob), _value(knob->_data), _dragging(false)
{
setFixedSize(60, 30);
_switchRect = rect();
_knob->addCallback(WidgetCallback, this);
// Set the tooltip based on the knob's tooltip and the knob name
// Sadly Nuke doesn't handle this automatically
setToolTip(QString::fromStdString(("<b>" + knob->name() + "</b><br />" + knob->tooltip())));
}
MyWidget::~MyWidget()
{
if (_knob)
_knob->removeCallback(WidgetCallback, this);
}
void MyWidget::paintEvent(QPaintEvent* event)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
// Draw background
painter.setPen(Qt::NoPen);
painter.setBrush(_value ? QColor(0, 150, 136) : QColor(120, 120, 120));
painter.drawRoundedRect(_switchRect, 15, 15);
// Draw switch
painter.setBrush(Qt::white);
int position = _value ? width() - 28 : 2;
painter.drawEllipse(position, 2, 26, 26);
}
void MyWidget::mousePressEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton)
{
_dragging = true;
event->accept();
}
}
void MyWidget::mouseMoveEvent(QMouseEvent* event)
{
if (_dragging)
{
bool newValue = event->pos().x() > width() / 2;
if (newValue != _value)
{
_value = newValue;
_knob->setValue(_value);
update();
}
event->accept();
}
}
void MyWidget::mouseReleaseEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton)
{
_dragging = false;
bool newValue = event->pos().x() > width() / 2;
_value = newValue;
_knob->setValue(_value);
update();
event->accept();
}
}
void MyWidget::update()
{
_value = _knob->_data;
QWidget::update();
}
void MyWidget::destroy()
{
_knob = 0;
}
int MyWidget::WidgetCallback(void* closure, Knob::CallbackReason reason)
{
MyWidget* widget = (MyWidget*)closure;
assert(widget);
switch (reason) {
case Knob::kIsVisible:
{
for (QWidget* w = widget->parentWidget(); w; w = w->parentWidget())
if (qobject_cast<QTabWidget*>(w))
return widget->isVisibleTo(w);
return widget->isVisible();
}
case Knob::kUpdateWidgets:
widget->update();
return 0;
case Knob::kDestroying:
widget->destroy();
return 0;
default:
return 0;
}
}
using namespace DD::Image;
class MyPlugin : public PixelIop
{
float value[4];
bool _value;
std::string _cryptoLayerName;
public:
void in_channels(int input, ChannelSet& mask) const override;
MyPlugin(Node* node) : PixelIop(node)
{
value[0] = value[1] = value[2] = value[3] = 0;
_value = false;
}
bool pass_transform() const override { return true; }
void pixel_engine(const Row& in, int y, int x, int r, ChannelMask, Row& out) override;
void knobs(Knob_Callback) override;
static const Iop::Description d;
const char* Class() const override { return d.name; }
const char* node_help() const override { return HELP; }
void _validate(bool) override;
};
void MyPlugin::_validate(bool for_real)
{
copy_info();
Knob* test = this->knob("override");
// Check for null pointer
if (test) {
// load the values from the knob into the value array
for (unsigned i = 0; i < 4; i++) {
value[i] = test->get_value(i);
}
}
for (unsigned i = 0; i < 4; i++) {
if (value[i]) {
set_out_channels(Mask_All);
info_.black_outside(false);
return;
}
}
set_out_channels(Mask_None);
}
void MyPlugin::in_channels(int input, ChannelSet& mask) const
{
// mask is unchanged
}
void MyPlugin::pixel_engine(const Row& in, int y, int x, int r,
ChannelMask channels, Row& out)
{
foreach(z, channels) {
if (_value) {
const float c = 0.5f;
const float* inptr = in[z] + x;
const float* END = inptr + (r - x);
float* outptr = out.writable(z) + x;
while (inptr < END)
*outptr++ = *inptr++ + c;
continue;
}
const float c = value[colourIndex(z)];
const float* inptr = in[z] + x;
const float* END = inptr + (r - x);
float* outptr = out.writable(z) + x;
while (inptr < END)
*outptr++ = *inptr++ + c;
}
}
void MyPlugin::knobs(Knob_Callback f)
{
AColor_knob(f, value, IRange(0, 4), "value");
Knob* MyPluginKnob = CustomKnob1(MyKnob, f, &_value, "toggle");
if (f.makeKnobs()) {
f(PLUGIN_PYTHON_KNOB, Custom, MyPluginKnob, nullptr, nullptr, nullptr); // BoolPtr instead of Custom?
}
// Set the tooltip for the knob
Tooltip(f, "This is a toggle knob");
}
#include "DDImage/NukeWrapper.h"
static Iop* build(Node* node) { return new NukeWrapper(new MyPlugin(node)); }
const Iop::Description MyPlugin::d("MyPlugin", "Color/Math/MyPlugin", build);
#ifndef HAVE_MYPLUGIN_MOC_H
#define HAVE_MYPLUGIN_MOC_H
#include "DDImage/Knobs.h"
#include <QtCore/QObject>
#include <QtWidgets/QDial>
class MyKnob;
class MyWidget : public QWidget
{
Q_OBJECT
public:
MyWidget(MyKnob* knob);
~MyWidget();
void update();
void destroy();
static int WidgetCallback(void* closure, DD::Image::Knob::CallbackReason reason);
protected:
void paintEvent(QPaintEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
private:
MyKnob* _knob;
bool _value;
bool _dragging;
QRect _switchRect;
};
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment