-
-
Save herronelou/88c656fd449f14ccfe9f7658686dd8fd to your computer and use it in GitHub Desktop.
Custom (Useless) plugin for Nuke with Custom Qt Knob and Python API
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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