Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ The PACKAGE file is a [YAML](http://www.yaml.org/) file providing information ab
| rv | version number | • | The minimum version of commercial RV which this package is compatible with |
| openrv | version number | • | The minimum version of Open RV which this package is compatible with |
| requires | zip file name list | | Any other packages (as zip file names) which are required in order to install/load this package |
| excludes | package base name list | | Any other packages (as base names, separated by spaces) which cannot be loaded at the same time as this package. Making this package loadable in the package manager will unload any excluded package that is currently loaded. The exclusion is mutual, so only one of the two packages needs to declare it |
| icon | PNG file name | | The name of an file with an icon for this package |
| imageio | file list | | List of files in package which implement Image I/O |
| movieio | file list | | List of files in package which implement Movie I/O |
Expand Down
2 changes: 1 addition & 1 deletion src/lib/app/RvCommon/RvBottomViewToolBar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ namespace Rv

void RvBottomViewToolBar::smActionTriggered(bool b) { m_session->userGenericEvent("mode-manager-toggle-mode", "session_manager"); }

void RvBottomViewToolBar::paintActionTriggered(bool) { m_session->userGenericEvent("mode-manager-toggle-mode", "annotate_mode"); }
void RvBottomViewToolBar::paintActionTriggered(bool) { m_session->userGenericEvent("toggle-annotate-toolbar", ""); }

void RvBottomViewToolBar::infoActionTriggered(bool) { m_session->userGenericEvent("toggle-hud-info-widget", ""); }

Expand Down
32 changes: 31 additions & 1 deletion src/lib/app/RvPackage/PackageManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,11 @@ namespace Rv
}
}

for (const int exclusion : package.exclusions)
{
allowLoading(m_packages[exclusion], false, depth + 1);
}

m_doNotLoadPackages.removeOne(package.file);

if (package.optional && !m_optLoadPackages.contains(package.file))
Expand Down Expand Up @@ -1301,6 +1306,8 @@ namespace Rv
package.contact = v;
else if (pname == "version")
package.version = v;
else if (pname == "excludes")
package.excludes = v;
else if (pname == "requires")
package.
requires
Expand Down Expand Up @@ -1546,7 +1553,7 @@ namespace Rv

void PackageManager::findPackageDependencies()
{
for (size_t i = 0; i < m_packages.size(); i++)
for (int i = 0; i < m_packages.size(); i++)
{
Package& package = m_packages[i];
QStringList deps = package.
Expand All @@ -1565,6 +1572,28 @@ namespace Rv
package.compatible = false;
}
}

const auto excludedPackages = package.excludes.split(" ", Qt::SkipEmptyParts);

for (const auto& excludedPackage : excludedPackages)
{
const int packageIndex = findPackageIndexByZip(excludedPackage);

if (packageIndex == -1 || packageIndex == i)
{
continue;
}

if (!package.exclusions.contains(packageIndex))
{
package.exclusions.push_back(packageIndex);
}

if (!m_packages[packageIndex].exclusions.contains(i))
{
m_packages[packageIndex].exclusions.push_back(i);
}
}
}
}

Expand Down Expand Up @@ -1873,6 +1902,7 @@ namespace Rv
{
m_packages[q].usedBy.clear();
m_packages[q].uses.clear();
m_packages[q].exclusions.clear();
}

findPackageDependencies();
Expand Down
2 changes: 2 additions & 0 deletions src/lib/app/RvPackage/RvPackage/PackageManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ namespace Rv
QString version;
QString url;
QString icon;
QString excludes;
QString
requires;
QStringList imageio;
Expand Down Expand Up @@ -143,6 +144,7 @@ namespace Rv

QList<int> uses;
QList<int> usedBy;
QList<int> exclusions;
};

struct ModeEntry
Expand Down
8 changes: 8 additions & 0 deletions src/lib/app/mu_rvui/mode_manager.mu
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,14 @@ class: ModeManagerMode : MinorMode
toggleModeEntry(ev, m, this);
});
}

if (m.name == "annotate_mode")
{
bind("toggle-annotate-toolbar", \: (void; Event event)
{
toggleModeEntry(event, m, this);
});
}
}

defineModeMenu(name(), top);
Expand Down
20 changes: 16 additions & 4 deletions src/lib/ip/IPBaseNodes/PaintIPNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
// SPDX-License-Identifier: Apache-2.0
//
#include <IPBaseNodes/PaintIPNode.h>
#include <IPCore/BrushTextureManager.h>
#include <IPCore/PaintCommand.h>
#include <IPCore/SessionIPNode.h>
#include <IPCore/Exception.h>
Expand Down Expand Up @@ -335,6 +334,10 @@ namespace IPCore
const IntProperty* startFrameP = c->property<IntProperty>("startFrame");
const IntProperty* durationP = c->property<IntProperty>("duration");

const FloatProperty* hardnessP = c->property<FloatProperty>("hardness");
const StringProperty* tipTextureP = c->property<StringProperty>("tipTexture");
const IntProperty* blendModeP = c->property<IntProperty>("blendMode");

const float width = widthP && widthP->size() ? widthP->front() : 0.01f;
const Vec4f color = colorP && colorP->size() ? colorP->front() : Vec4f(1.0, 1.0, 1.0, 1.0);
const string brush = brushP && brushP->size() ? brushP->front() : string("circle");
Expand All @@ -349,6 +352,10 @@ namespace IPCore
const int startFrame = (startFrameP != nullptr && startFrameP->size() != 0) ? startFrameP->front() : getStartFrame(*c);
const int duration = (durationP != nullptr && durationP->size() != 0) ? durationP->front() : 1;

const float hardness = hardnessP && hardnessP->size() ? hardnessP->front() : 100.0f;
const string tipTexture = tipTextureP && tipTextureP->size() ? tipTextureP->front() : string();
const unsigned int blendMode = blendModeP && blendModeP->size() ? blendModeP->front() : Paint::PolyLine::BlendNormal;

p.width = width;
p.color = color;
p.brush = brush;
Expand All @@ -362,9 +369,14 @@ namespace IPCore
p.eye = eye;
p.startFrame = startFrame;
p.duration = duration;

// Classify the brush type once — drives all downstream data paths.
const bool isStampBrush = Paint::BrushTextureManager::instance().get(brush).isStamp;
p.hardness = hardness;
p.tipTexture = tipTexture;
p.stampBlendMode = (Paint::PolyLine::StampBlendMode)blendMode;

// Classify the brush type once — drives all downstream data paths. The
// legacy "circle"/"gauss" names stay on the ribbon (non-stamp) path;
// everything else (including future UI-driven brushes) is stamp-based.
const bool isStampBrush = (brush != "circle" && brush != "gauss");

// Per-point widths are present when widthP has one entry per point.
// Allow widthP to lag pointsP by one: the Mu layer inserts the point and
Expand Down
113 changes: 32 additions & 81 deletions src/lib/ip/IPCore/BrushTextureManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,14 @@
#include <IPCore/BrushTextureManager.h>
#include <TwkApp/Bundle.h>
#include <TwkGLF/GL.h>
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QDir>
#include <QFileInfo>
#include <QString>
#include <QStringList>
#include <png.h>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <mutex>
#include <vector>

namespace IPCore
Expand All @@ -24,17 +23,6 @@ namespace IPCore

// ── helpers ───────────────────────────────────────────────────────────────────

static PolyLine::StampBlendMode blendFromString(const QString& s)
{
if (s == "additive")
return PolyLine::BlendAdditive;
if (s == "marker")
return PolyLine::BlendMarker;
if (s == "eraser")
return PolyLine::BlendEraser;
return PolyLine::BlendNormal;
}

/// Load an 8-bit grayscale PNG and upload it as a GL_LUMINANCE texture.
/// Returns the GL texture name, or 0 on failure.
static unsigned int uploadGrayscalePng(const std::string& path)
Expand Down Expand Up @@ -117,7 +105,7 @@ namespace IPCore
return s;
}

std::string BrushTextureManager::catalogueDir()
std::string BrushTextureManager::tipDir()
{
if (const char* env = std::getenv("RV_BRUSH_DIR"))
return env;
Expand All @@ -133,83 +121,46 @@ namespace IPCore
return "";
}

void BrushTextureManager::parseCatalogue()
unsigned int BrushTextureManager::getTexture(const std::string& tipFile)
{
std::call_once(m_parseOnce,
[this]()
{
const QString qdir = QString::fromStdString(catalogueDir());
const QString catPath = qdir + "/catalogue.json";

QFile f(catPath);
if (!f.open(QIODevice::ReadOnly))
{
std::cerr << "[BrushTextureManager] catalogue not found: " << catPath.toStdString() << "\n";
return;
}

QJsonParseError err;
const QJsonDocument doc = QJsonDocument::fromJson(f.readAll(), &err);
if (doc.isNull())
{
std::cerr << "[BrushTextureManager] JSON parse error: " << err.errorString().toStdString() << "\n";
return;
}

for (const QJsonValue& v : doc.object().value("brushes").toArray())
{
const QJsonObject entry = v.toObject();
const std::string name = entry.value("name").toString().toStdString();
const QString tip = entry.value("tip").toString();
const QString blend = entry.value("blend").toString("normal");
const bool soft = entry.value("soft").toBool(false);

BrushInfo& info = m_brushes[name];
info.blendMode = blendFromString(blend);
info.softShader = soft;
info.isStamp = !tip.isEmpty();
info.tipFile = tip.toStdString();
}
}); // call_once
}
if (tipFile.empty())
return 0;

void BrushTextureManager::load()
{
if (m_texturesLoaded)
return;
m_texturesLoaded = true;
std::lock_guard<std::mutex> lock(m_mutex);
const auto it = m_textures.find(tipFile);
if (it != m_textures.end())
return it->second;

parseCatalogue();
const QString path = QString::fromStdString(tipDir()) + "/" + QString::fromStdString(tipFile);
const unsigned int texId = uploadGrayscalePng(path.toStdString());
if (!texId)
std::cerr << "[BrushTextureManager] failed to load tip: " << path.toStdString() << "\n";

const QString qdir = QString::fromStdString(catalogueDir());
for (auto& kv : m_brushes)
{
if (kv.second.isStamp && !kv.second.tipFile.empty() && !kv.second.textureId)
{
const std::string tipPath = (qdir + "/" + QString::fromStdString(kv.second.tipFile)).toStdString();
kv.second.textureId = uploadGrayscalePng(tipPath);
if (!kv.second.textureId)
std::cerr << "[BrushTextureManager] failed to load tip: " << tipPath << "\n";
}
}
m_textures[tipFile] = texId; // cache failures too, so a bad path isn't retried every frame
return texId;
}

BrushInfo BrushTextureManager::get(const std::string& name)
std::vector<std::string> BrushTextureManager::listTipFiles() const
{
parseCatalogue();
const auto it = m_brushes.find(name);
return (it != m_brushes.end()) ? it->second : BrushInfo{};
std::vector<std::string> files;
const QDir dir(QString::fromStdString(tipDir()));
for (const QFileInfo& fi : dir.entryInfoList(QStringList() << "*.png", QDir::Files, QDir::Name))
files.push_back(fi.fileName().toStdString());
return files;
}

void BrushTextureManager::clear()
{
for (auto& kv : m_brushes)
std::lock_guard<std::mutex> lock(m_mutex);
for (auto& kv : m_textures)
{
if (kv.second.textureId)
glDeleteTextures(1, &kv.second.textureId);
kv.second.textureId = 0;
if (kv.second)
{
GLuint texId = kv.second;
glDeleteTextures(1, &texId);
}
}
m_texturesLoaded = false;
m_textures.clear();
}

} // namespace Paint
Expand Down
Loading
Loading