Demo Plugin¶
This section provides an example of a demo plugin for VRED. The demo plugin demonstrates how to create a user plugin by inheriting from the VREDUserPluginInterface class and implementing its methods.
Header¶
#pragma once
#include <VREDUserPluginInterface.h>
class vrUserPluginDemo : public VREDUserPluginInterface
{
public:
vrUserPluginDemo(VREDUserKernelInterface *kernelInterface);
~vrUserPluginDemo();
void init() override;
void update();
void message(const VREDMessage &msg);
bool frameBufferChanged(const VREDGLBuffer &context);
private:
VREDUserKernelInterface *m_ki;
bool m_readyToRender;
std::size_t m_updateCount;
};
Implementation¶
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <vector>
#include <array>
#include <thread>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <dlfcn.h>
#endif
#include "vrUserPluginDemo.h"
// *********************************
// OpenGL related stuff
// *********************************
#define GL_BGR 0x80E0
#define GL_PACK_ALIGNMENT 0x0D05
#define GL_UNSIGNED_BYTE 0x1401
using GLenum = unsigned int;
using GLint = int;
using GLsizei = int;
using GLvoid = void;
using GLReadPixelsFunc = void (*)(GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, GLvoid *);
static GLReadPixelsFunc glReadPixels = nullptr;
using GLPixelStoreiFunc = void (*)(GLenum, GLint);
static GLPixelStoreiFunc glPixelStorei = nullptr;
static void initGL()
{
#ifdef _WIN32
auto glLibrary = LoadLibrary("opengl32.dll");
if(glLibrary == nullptr)
return;
glReadPixels = reinterpret_cast<GLReadPixelsFunc>(GetProcAddress(glLibrary, "glReadPixels"));
glPixelStorei = reinterpret_cast<GLPixelStoreiFunc>(GetProcAddress(glLibrary, "glPixelStorei"));
#else
auto glLibrary = dlopen("/usr/lib64/libGL.so.1", RTLD_LAZY);
if(glLibrary == nullptr)
return;
glReadPixels = reinterpret_cast<GLReadPixelsFunc>(dlsym(glLibrary, "glReadPixels"));
glPixelStorei = reinterpret_cast<GLPixelStoreiFunc>(dlsym(glLibrary, "glPixelStorei"));
#endif
}
static bool writeBMP(const std::string &filename, std::uint32_t w, std::uint32_t h, const char *rgb)
{
std::ofstream out(filename, std::ios_base::out | std::ios_base::binary);
if(!out)
return false;
std::uint32_t pad = w * -3 & 3;
std::uint32_t total = 54 + 3*w*h + pad*h;
std::array<uint32_t, 13> head = {total, 0u, 54u, 40u, w, h, (24u<<16u)|1u};
out.write("BM", 2);
out.write(reinterpret_cast<const char *>(head.data()), 52);
for(uint32_t i=0 ; i<h ; i++)
{ out.write(rgb + (3 * w * i), 3 * w);
out.write(reinterpret_cast<const char *>(&pad), pad);
}
return true;
}
static void writeFrameBuffer(std::uint32_t fboId, std::uint32_t width, std::uint32_t height)
{
if(glReadPixels == nullptr || glPixelStorei == nullptr)
return;
std::vector<char> img;
img.resize(width * height * 3);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(0, 0, width, height, GL_BGR, GL_UNSIGNED_BYTE, img.data());
#ifdef _WIN32
static int count = 0;
std::string path = "image" + std::to_string(count++) + ".bmp";
auto appdata = getenv("APPDATA");
if(appdata != nullptr)
path = std::string(appdata) + "\\" + path;
#else
std::string path = "/tmp/image.bmp";
#endif
if(writeBMP(path, width, height, img.data()))
std::cout << "*** vrUserPluginDemo::writeFrameBuffer: wrote image to: " << path << std::endl;
}
// *********************************
// Plugin implementation
// *********************************
vrUserPluginDemo::vrUserPluginDemo(VREDUserKernelInterface *kernelInterface) :
m_ki(kernelInterface),
m_readyToRender(false),
m_updateCount(0)
{
std::cout << "*** vrUserPluginDemo::vrUserPluginDemo" << std::endl;
m_ki->registerUpdate([this]()
{
update();
});
m_ki->registerMessage([this](const VREDMessage &msg)
{
message(msg);
});
m_ki->registerFrameBufferChanged([this](const VREDGLBuffer &context) -> bool
{
return frameBufferChanged(context);
});
}
vrUserPluginDemo::~vrUserPluginDemo()
{
std::cout << "*** vrUserPluginDemo::~vrUserPluginDemo" << std::endl;
}
void vrUserPluginDemo::init()
{
initGL();
std::string cmd = "42 * 12";
auto r = m_ki->runPythonR(cmd.c_str());
std::cout << "*** vrUserPluginDemo::init: runPython: '" << cmd << "' returns: " << r << std::endl;
//m_ki->setFrameSize(1920, 1080);
std::thread t([this]()
{
std::this_thread::sleep_for(std::chrono::seconds(5));
std::string cmd = "42 * 12";
auto r = m_ki->runPythonR(cmd.c_str());
std::cout << "*** vrUserPluginDemo::init: runPython from a thread: '" << cmd << "' returns: " << r << std::endl;
});
t.detach();
}
void vrUserPluginDemo::update()
{
// just print the first 10 times
if(m_updateCount < 10)
{
std::cout << "*** vrUserPluginDemo::update" << std::endl;
++m_updateCount;
}
}
void vrUserPluginDemo::message(const VREDMessage &msg)
{
//std::cout << "*** vrUserPluginDemo::message: " << msg.type << std::endl;
if(msg.type == VREDMessageTypes::NewScene)
{
m_readyToRender = false;
}
else if(msg.type == VREDMessageTypes::ProjectReadyToRender)
{
std::cout << "*** vrUserPluginDemo::message: ProjectReadyToRender" << std::endl;
m_readyToRender = true;
}
}
bool vrUserPluginDemo::frameBufferChanged(const VREDGLBuffer &context)
{
if(!m_readyToRender)
return false;
writeFrameBuffer(context.fboId, context.width, context.height);
// return true means don't blit to the vred render window.
return false;
}
VRED_REGISTER_USER_PLUGIN(vrUserPluginDemo)
Viewer Plugin¶
The implementation of the vrUserPluginViewer class includes methods for initialization, updating, handling messages, and handling frame buffer changes. These methods are used to manage the display and interaction with images within VRED. The viewer plugin opens a view of the render result from VRED, allowing for mouse navigation and keyboard interaction.
Header¶
#pragma once
#include <VREDUserPluginInterface.h>
class vrImageWidget;
class vrUserPluginViewer : public VREDUserPluginInterface
{
public:
vrUserPluginViewer(VREDUserKernelInterface *kernelInterface);
~vrUserPluginViewer();
void init() override;
bool frameBufferChanged(const VREDGLBuffer &context);
private:
VREDUserKernelInterface *m_ki;
bool m_enabled;
vrImageWidget *m_viewer;
};
Implementation¶
#include <cstdlib>
#include <iostream>
#include <fstream>
#include <vector>
#include <array>
#include <format>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <dlfcn.h>
#endif
#include <QWidget>
#include <QPainter>
#include <QMouseEvent>
#include <QKeyEvent>
#include <QWheelEvent>
#include <QTimer>
#include "vrUserPluginViewer.h"
// *********************************
// OpenGL related stuff
// *********************************
#define GL_BGR 0x80E0
#define GL_PACK_ALIGNMENT 0x0D05
#define GL_UNSIGNED_BYTE 0x1401
using GLenum = unsigned int;
using GLint = int;
using GLsizei = int;
using GLvoid = void;
using GLReadPixelsFunc = void (*)(GLint, GLint, GLsizei, GLsizei, GLenum, GLenum, GLvoid *);
static GLReadPixelsFunc glReadPixels = nullptr;
using GLPixelStoreiFunc = void (*)(GLenum, GLint);
static GLPixelStoreiFunc glPixelStorei = nullptr;
static void initGL()
{
#ifdef _WIN32
auto glLibrary = LoadLibrary("opengl32.dll");
if(glLibrary == nullptr)
return;
glReadPixels = reinterpret_cast<GLReadPixelsFunc>(GetProcAddress(glLibrary, "glReadPixels"));
glPixelStorei = reinterpret_cast<GLPixelStoreiFunc>(GetProcAddress(glLibrary, "glPixelStorei"));
#else
auto glLibrary = dlopen("/usr/lib64/libGL.so.1", RTLD_LAZY);
if(glLibrary == nullptr)
return;
glReadPixels = reinterpret_cast<GLReadPixelsFunc>(dlsym(glLibrary, "glReadPixels"));
glPixelStorei = reinterpret_cast<GLPixelStoreiFunc>(dlsym(glLibrary, "glPixelStorei"));
#endif
}
// *******
class vrImageWidget : public QWidget
{
public:
vrImageWidget(VREDUserKernelInterface *ki, QWidget *parent = nullptr) :
QWidget(parent),
m_ki(ki),
m_initScript(),
m_image()
{
setAttribute(Qt::WA_AcceptTouchEvents);
// execute python code to get the render window
// we actually have two widgets with the name "Render". One is a wrapper and the other one is the actual render window
// the wrapper has the real render window as a child.
// with VRED 2024.2 you can use directly the new pre-defined python variable VREDVREDRenderWindow
// a new scene in VRED will delete all user variables and imports so we need to call this script every time we want to use Qt APIs
m_initScript =
R"(
try:
viewerScriptInitialized
except NameError:
from PySide6 import QtCore, QtGui, QtWidgets
viewerScriptInitialized = True
try:
VREDRenderWindow
except NameError:
VREDRenderWindowTmp = VREDMainWindow.findChild(QtWidgets.QWidget, "Render")
VREDRenderWindow = VREDRenderWindowTmp.findChild(QtWidgets.QWidget, "Render")
if VREDRenderWindow is None:
VREDRenderWindow = VREDRenderWindowTmp
del VREDRenderWindowTmp)";
m_ki->runPython(m_initScript);
}
void setImage(const QImage &image)
{
m_image = image;
update();
}
protected:
void paintEvent(QPaintEvent *event) override
{
QPainter p(this);
p.drawImage(rect(), m_image);
}
void wheelEvent(QWheelEvent *we) override
{
QWidget::wheelEvent(we);
m_ki->runPython(m_initScript);
m_ki->runPython(std::format("QtCore.QCoreApplication.sendEvent(VREDRenderWindow, QtGui.QWheelEvent(QtCore.QPoint({}, {}), QtCore.QPoint({}, {}), QtCore.QPoint({}, {}), QtCore.QPoint({}, {}), QtCore.Qt.MouseButtons({}), QtCore.Qt.KeyboardModifiers({}), QtCore.Qt.ScrollPhase({}), {}))",
we->position().x(), we->position().y(), we->globalPosition().x(), we->globalPosition().y(), we->pixelDelta().x(), we->pixelDelta().y(),
we->angleDelta().x(), we->angleDelta().y(), static_cast<int>(we->buttons()), static_cast<int>(we->modifiers()), static_cast<int>(we->phase()), we->inverted() ? "True" : "False"));
}
#if 0
QMouseEvent(QEvent::Type type, const QPointF &localPos, Qt::MouseButton button, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, const QPointingDevice *device = QPointingDevice::primaryPointingDevice())
QMouseEvent(QEvent::Type type, const QPointF &localPos, const QPointF &globalPos, Qt::MouseButton button, Qt::MouseButtons buttons, Qt::KeyboardModifiers modifiers, const QPointingDevice *device = QPointingDevice::primaryPointingDevice())
#endif
void mousePressEvent(QMouseEvent *me) override
{
QWidget::mousePressEvent(me);
m_ki->runPython(m_initScript);
m_ki->runPython(std::format("QtCore.QCoreApplication.sendEvent(VREDRenderWindow, QtGui.QMouseEvent(QtCore.QEvent.MouseButtonPress, QtCore.QPoint({}, {}), QtCore.QPoint({}, {}), QtCore.Qt.MouseButton({}), QtCore.Qt.MouseButtons({}), QtCore.Qt.KeyboardModifiers({})))", me->position().x(), me->position().y(), me->globalPosition().x(), me->globalPosition().y(), static_cast<int>(me->button()), static_cast<int>(me->buttons()), static_cast<int>(me->modifiers())));
}
void mouseReleaseEvent(QMouseEvent *me) override
{
QWidget::mouseReleaseEvent(me);
m_ki->runPython(m_initScript);
m_ki->runPython(std::format("QtCore.QCoreApplication.sendEvent(VREDRenderWindow, QtGui.QMouseEvent(QtCore.QEvent.MouseButtonRelease, QtCore.QPoint({}, {}), QtCore.QPoint({}, {}), QtCore.Qt.MouseButton({}), QtCore.Qt.MouseButtons({}), QtCore.Qt.KeyboardModifiers({})))", me->position().x(), me->position().y(), me->globalPosition().x(), me->globalPosition().y(), static_cast<int>(me->button()), static_cast<int>(me->buttons()), static_cast<int>(me->modifiers())));
}
void mouseMoveEvent(QMouseEvent *me) override
{
QWidget::mouseMoveEvent(me);
m_ki->runPython(m_initScript);
m_ki->runPython(std::format("QtCore.QCoreApplication.sendEvent(VREDRenderWindow, QtGui.QMouseEvent(QtCore.QEvent.MouseMove, QtCore.QPoint({}, {}), QtCore.QPoint({}, {}), QtCore.Qt.MouseButton({}), QtCore.Qt.MouseButtons({}), QtCore.Qt.KeyboardModifiers({})))", me->position().x(), me->position().y(), me->globalPosition().x(), me->globalPosition().y(), static_cast<int>(me->button()), static_cast<int>(me->buttons()), static_cast<int>(me->modifiers())));
}
void keyPressEvent(QKeyEvent *ke) override
{
QWidget::keyPressEvent(ke);
m_ki->runPython(m_initScript);
m_ki->runPython(std::format("QtCore.QCoreApplication.sendEvent(VREDRenderWindow, QtGui.QKeyEvent(QtCore.QEvent.KeyPress, {}, QtCore.Qt.KeyboardModifiers({})))", ke->key(), static_cast<int>(ke->modifiers())));
}
void keyReleaseEvent(QKeyEvent *ke) override
{
QWidget::keyReleaseEvent(ke);
m_ki->runPython(m_initScript);
m_ki->runPython(std::format("QtCore.QCoreApplication.sendEvent(VREDRenderWindow, QtGui.QKeyEvent(QtCore.QEvent.KeyRelease, {}, QtCore.Qt.KeyboardModifiers({})))", ke->key(), static_cast<int>(ke->modifiers())));
}
private:
VREDUserKernelInterface *m_ki;
std::string m_initScript;
QImage m_image;
};
// *********************************
// Plugin implementation
// *********************************
vrUserPluginViewer::vrUserPluginViewer(VREDUserKernelInterface *kernelInterface) :
m_ki(kernelInterface),
m_enabled(false),
m_viewer(nullptr)
{
std::cout << "*** vrUserPluginViewer::vrUserPluginViewer" << std::endl;
m_ki->registerFrameBufferChanged([this](const VREDGLBuffer &context) -> bool
{
return frameBufferChanged(context);
});
}
vrUserPluginViewer::~vrUserPluginViewer()
{
std::cout << "*** vrUserPluginViewer::~vrUserPluginViewer" << std::endl;
}
void vrUserPluginViewer::init()
{
m_enabled = (qgetenv("VRED_PLUGIN_VIEWER").toInt() == 1);
if(!m_enabled)
return;
initGL();
m_viewer = new vrImageWidget(m_ki);
m_viewer->setWindowTitle("Plugin Viewer");
QTimer::singleShot(1000, [this] { m_viewer->show(); m_viewer->raise(); });
}
bool vrUserPluginViewer::frameBufferChanged(const VREDGLBuffer &context)
{
if(!m_enabled)
return false;
if(glReadPixels == nullptr || glPixelStorei == nullptr)
return false;
const auto w = context.width;
const auto h = context.height;
QImage img(w, h, QImage::Format_BGR888);
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(0, 0, w, h, GL_BGR, GL_UNSIGNED_BYTE, img.bits());
m_viewer->resize(w, h);
m_viewer->setImage(img.mirrored());
// return true means don't blit to the vred render window.
return false;
}
VRED_REGISTER_USER_PLUGIN(vrUserPluginViewer)