Skip to content
Open
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
49 changes: 49 additions & 0 deletions src/FileCache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include "editors/source/SourceEditor.h"
#include "editors/texture/TextureEditor.h"
#include "session/SessionModel.h"
#include <QFileInfo>
#include <QRegularExpression>
#include <QTextStream>
#include <QThread>

Expand Down Expand Up @@ -467,4 +469,51 @@ void FileCache::handleReloadingFailed(const QString &fileName)
mUpdateFileSystemWatchesTimer.start();
}

bool FileCache::getSequenceTexture(const QString &baseFileName,
const QString &pattern,
int frameNumber,
bool flipVertically,
TextureData *texture) const
{
if (!texture)
return false;

const QString actualFileName = buildSequenceFileName(baseFileName, pattern, frameNumber);
return getTexture(actualFileName, flipVertically, texture);
}

QString FileCache::buildSequenceFileName(const QString &baseFileName,
const QString &pattern,
int frameNumber) const
{
if (baseFileName.isEmpty() || pattern.isEmpty())
return baseFileName;

QFileInfo fileInfo(baseFileName);
QString dir = fileInfo.absolutePath();
QString originalExtension = fileInfo.suffix();

// Process the pattern to replace %0Xd with zero-padded frame number
QString processedPattern = pattern;
QRegularExpression regex(R"(%0?(\d+)d)");
QRegularExpressionMatch match = regex.match(pattern);

if (match.hasMatch()) {
int padding = match.captured(1).toInt();
QString frameStr = QString("%1").arg(frameNumber, padding, 10, QChar('0'));
processedPattern.replace(regex, frameStr);
} else {
// Fallback: replace any %d with frame number
processedPattern.replace("%d", QString::number(frameNumber));
}

// If pattern already has an extension, use it as-is
// Otherwise, append the original file's extension
if (processedPattern.contains('.')) {
return QString("%1/%2").arg(dir, processedPattern);
} else {
return QString("%1/%2.%3").arg(dir, processedPattern, originalExtension);
}
}

#include "FileCache.moc"
10 changes: 10 additions & 0 deletions src/FileCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <QMap>
#include <QMutex>
#include <QObject>
#include <QQueue>
#include <QSet>
#include <QThread>
#include <QTimer>
Expand All @@ -19,10 +20,19 @@ class FileCache final : public QObject
bool getSource(const QString &fileName, QString *source) const;
bool getTexture(const QString &fileName, bool flipVertically,
TextureData *texture) const;
bool getSequenceTexture(const QString &baseFileName,
const QString &pattern,
int frameNumber,
bool flipVertically,
TextureData *texture) const;
bool getBinary(const QString &fileName, QByteArray *binary) const;
bool updateTexture(const QString &fileName, bool flippedVertically,
TextureData texture);

QString buildSequenceFileName(const QString &baseFileName,
const QString &pattern,
int frameNumber) const;

// only call from main thread
void unloadAll();
void invalidateFile(const QString &fileName);
Expand Down
77 changes: 76 additions & 1 deletion src/SynchronizeLogic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ void SynchronizeLogic::manualEvaluation()
evaluate(EvaluationType::Manual);
}

void SynchronizeLogic::automaticEvaluation()
{
evaluate(EvaluationType::Automatic);
}

void SynchronizeLogic::setEvaluationMode(EvaluationMode mode)
{
if (mEvaluationMode == mode)
Expand Down Expand Up @@ -304,7 +309,16 @@ void SynchronizeLogic::handleEditorFileRenamed(const QString &prevFileName,
void SynchronizeLogic::handleFileItemFileChanged(const FileItem &item)
{
// update item name
auto name = FileDialog::getFileTitle(item.fileName);
QString displayFileName = item.fileName;

// For textures, use baseName for display (baseName = fileName for single textures, baseName for sequences)
if (auto texture = castItem<Texture>(&item)) {
if (!texture->baseName.isEmpty()) {
displayFileName = texture->baseName;
}
}

auto name = FileDialog::getFileTitle(displayFileName);
if (name.isEmpty()) {
// only reset to type name when it currently has a filename
if (FileDialog::getFileExtension(item.name).isEmpty())
Expand Down Expand Up @@ -386,10 +400,71 @@ void SynchronizeLogic::handleEvaluateTimout()

void SynchronizeLogic::evaluate(EvaluationType evaluationType)
{
// Reset sequence frames when evaluation is reset (F5)
if (evaluationType == EvaluationType::Reset) {
mModel.forEachItem<Texture>([&](const Texture &texture) {
if (texture.isSequence) {
const_cast<Texture&>(texture).currentFrame = 0;
}
});
}

// Advance sequence frames for manual evaluation (F6)
if (evaluationType == EvaluationType::Manual) {
mModel.forEachItem<Texture>([&](const Texture &texture) {
if (texture.isSequence) {
auto &mutableTexture = const_cast<Texture&>(texture);

// Advance zero-based frame index
mutableTexture.currentFrame++;

// Calculate max zero-based index (frameEnd - frameStart)
int maxFrameIndex = texture.frameEnd - texture.frameStart;

if (mutableTexture.currentFrame > maxFrameIndex) {
if (texture.loopSequence) {
mutableTexture.currentFrame = 0; // Reset to zero index
} else {
mutableTexture.currentFrame = maxFrameIndex; // Clamp to last zero-based index
}
}
}
});
}

// Advance sequence frames for steady evaluation (F8 auto-advancing)
if (evaluationType == EvaluationType::Steady) {
mModel.forEachItem<Texture>([&](const Texture &texture) {
if (texture.isSequence) {
auto &mutableTexture = const_cast<Texture&>(texture);

// Advance zero-based frame index
mutableTexture.currentFrame++;

// Calculate max zero-based index (frameEnd - frameStart)
int maxFrameIndex = texture.frameEnd - texture.frameStart;

if (mutableTexture.currentFrame > maxFrameIndex) {
if (texture.loopSequence) {
mutableTexture.currentFrame = 0; // Reset to zero index
} else {
mutableTexture.currentFrame = maxFrameIndex; // Clamp to last zero-based index
}
}
}
});
}


Singletons::fileCache().updateFromEditors();
const auto itemsChanged = std::exchange(mRenderSessionInvalidated, false);
initializeRenderSession();
mRenderSession->update(itemsChanged, evaluationType);

// No frame state restoration needed

// Notify texture editors about evaluation updates
Q_EMIT evaluationUpdated();
}

void SynchronizeLogic::updateEditors()
Expand Down
2 changes: 2 additions & 0 deletions src/SynchronizeLogic.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class SynchronizeLogic final : public QObject
EvaluationMode evaluationMode() const { return mEvaluationMode; }
void resetEvaluation();
void manualEvaluation();
void automaticEvaluation();
bool resetRenderSessionInvalidationState();
void updateEditor(ItemId itemId, bool activated);

Expand All @@ -50,6 +51,7 @@ class SynchronizeLogic final : public QObject

Q_SIGNALS:
void outputChanged(QVariant output);
void evaluationUpdated();

private:
void invalidateRenderSession();
Expand Down
11 changes: 10 additions & 1 deletion src/TextureData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -956,7 +956,16 @@ bool TextureData::saveOpenImageIO(const QString &fileName,
return false;

const auto channelCount = getTextureComponentCount(format());
const auto spec = ImageSpec(width(), height(), channelCount, typeDesc);
auto spec = ImageSpec(width(), height(), channelCount, typeDesc);

// TODO: Expose in Preferences UI
spec.attribute("Compression", "jpeg:98");
spec.attribute("pnm:binary", 1);
spec.attribute("pnm:pfmflip", 0);
spec.attribute("oiio:UnassociatedAlpha", 1);
spec.attribute("jpeg:subsampling", "4:4:4");
spec.attribute("png:compressionLevel", 4);

if (!output->open(fileName.toStdWString(), spec))
return false;
if (!output->write_image(typeDesc, getData(0, 0, 0)))
Expand Down
45 changes: 42 additions & 3 deletions src/editors/EditorManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "FileDialog.h"
#include "Singletons.h"
#include "SynchronizeLogic.h"
#include "../session/SessionModel.h"
#include "binary/BinaryEditor.h"
#include "binary/BinaryEditorToolBar.h"
#include "qml/QmlView.h"
Expand Down Expand Up @@ -406,9 +407,14 @@ BinaryEditor *EditorManager::getBinaryEditor(const QString &fileName)

TextureEditor *EditorManager::getTextureEditor(const QString &fileName)
{
for (TextureEditor *editor : std::as_const(mTextureEditors))
if (editor->fileName() == fileName)
// Get the editor key for this fileName
QString editorKey = getTextureEditorKey(fileName);

for (TextureEditor *editor : std::as_const(mTextureEditors)) {
QString existingKey = getTextureEditorKey(editor->fileName());
if (existingKey == editorKey)
return editor;
}
return nullptr;
}

Expand Down Expand Up @@ -726,11 +732,44 @@ void EditorManager::handleEditorFilenameChanged(QDockWidget *dock)
void EditorManager::setDockWindowTitle(QDockWidget *dock,
const QString &fileName)
{
dock->setWindowTitle(FileDialog::getWindowTitle(fileName));
QString displayFileName = fileName;

// For textures, show baseName for display (baseName = fileName for single textures, baseName for sequences)
auto &model = Singletons::sessionModel();
model.forEachItem([&](const Item &item) {
if (auto texture = castItem<Texture>(&item)) {
if (texture->fileName == fileName && !texture->baseName.isEmpty()) {
displayFileName = texture->baseName;
return false; // stop iteration
}
}
return true; // continue iteration
});

dock->setWindowTitle(FileDialog::getWindowTitle(displayFileName));
if (!FileDialog::isEmptyOrUntitled(fileName))
dock->setStatusTip(fileName);
}

QString EditorManager::getTextureEditorKey(const QString &fileName) const
{
// Always use baseName as editor key (baseName = fileName for single textures, baseName for sequences)
auto &model = Singletons::sessionModel();
QString editorKey = fileName; // fallback if texture not found

model.forEachItem([&](const Item &item) {
if (auto texture = castItem<Texture>(&item)) {
if (texture->fileName == fileName && !texture->baseName.isEmpty()) {
editorKey = texture->baseName;
return false; // stop iteration
}
}
return true; // continue iteration
});

return editorKey;
}

bool EditorManager::saveDock(QDockWidget *dock)
{
auto currentDock = mCurrentDock;
Expand Down
1 change: 1 addition & 0 deletions src/editors/EditorManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ class EditorManager final : public DockWindow
void clearNavigationStack();
void addNavigationPosition(const QString &position, bool update);
bool restoreNavigationPosition(int index);
QString getTextureEditorKey(const QString &fileName) const;

QList<SourceEditor *> mSourceEditors;
QList<BinaryEditor *> mBinaryEditors;
Expand Down
Loading