Files
leocad/qt/lc_renderdialog.cpp
T
Leonardo Zide 75dc3883da Store profile enums as stable strings and harden settings reads
Persist enum preferences through named typed accessors using stable PascalCase tokens. Keep explicit legacy integer mappings so existing settings continue to load when enum members are reordered, and fall back to declared defaults for invalid values.

Validate scalar conversions and reject non-finite floats while preserving unsigned color bits returned by signed native settings backends. Read fixed part colors as unsigned values and remove their override when unlocking so the unlocked state survives restart.

Save both view-sphere enabled flags directly, preserving disabled spheres even when their remembered sizes are nonzero.

Validation: macOS application build and temporary enum and persistence regression checks passed. Windows and Linux execution was not performed.
2026-10-10 23:32:40 -07:00

942 lines
26 KiB
C++

#include "lc_global.h"
#include "lc_renderdialog.h"
#include "ui_lc_renderdialog.h"
#include "project.h"
#include "lc_application.h"
#include "lc_profile.h"
#include "lc_blenderpreferences.h"
#include "lc_model.h"
#include "lc_qutils.h"
#ifndef LC_DISABLE_RENDER_DIALOG
#define LC_POVRAY_PREVIEW_WIDTH 768
#define LC_POVRAY_PREVIEW_HEIGHT 432
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
#define LC_POVRAY_MEMORY_MAPPED_FILE 1
#endif
void lcRenderPreviewWidget::resizeEvent(QResizeEvent* Event)
{
mScaledImage = QImage();
QWidget::resizeEvent(Event);
}
void lcRenderPreviewWidget::paintEvent(QPaintEvent* PaintEvent)
{
Q_UNUSED(PaintEvent);
QPainter Painter(this);
if (!mImage.isNull())
{
QSize Size = size();
if (mScaledImage.isNull())
mScaledImage = mImage.scaled(Size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
Painter.drawImage((Size.width() - mScaledImage.width()) / 2, (Size.height() - mScaledImage.height()) / 2, mScaledImage);
}
else
Painter.fillRect(rect(), Qt::transparent);
}
lcRenderDialog::lcRenderDialog(QWidget* Parent, lcRenderDialogMode RenderDialogMode)
: QDialog(Parent), mDialogMode(RenderDialogMode), ui(new Ui::lcRenderDialog)
{
ui->setupUi(this);
mWidth = lcGetProfileInt(LC_PROFILE_RENDER_WIDTH);
mHeight = lcGetProfileInt(LC_PROFILE_RENDER_HEIGHT);
mQuality = lcGetProfileRenderQuality();
mScale = 1.0f;
ui->OutputEdit->setText(lcGetActiveProject()->GetImageFileName(false));
mRenderButton = new QPushButton(tr("Render"), ui->buttonBox);
ui->buttonBox->addButton(mRenderButton, QDialogButtonBox::ActionRole);
mSettingsButton = new QPushButton(tr("Settings..."), ui->buttonBox);
ui->buttonBox->addButton(mSettingsButton, QDialogButtonBox::ResetRole);
mLogButton = new QPushButton(tr("View Log..."), ui->buttonBox);
ui->buttonBox->addButton(mLogButton, QDialogButtonBox::ResetRole);
mLogButton->setEnabled(false);
connect(mRenderButton, &QPushButton::clicked, this, &lcRenderDialog::RenderButtonClicked);
connect(mSettingsButton, &QPushButton::clicked, this, &lcRenderDialog::SettingsButtonClicked);
connect(mLogButton, &QPushButton::clicked, this, &lcRenderDialog::LogButtonClicked);
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &lcRenderDialog::reject);
connect(ui->OutputBrowseButton, &QPushButton::clicked, this, &lcRenderDialog::OutputBrowseButtonClicked);
if (mDialogMode == lcRenderDialogMode::RenderPOVRay)
{
setWindowTitle(tr("POV-Ray Image Render"));
mRenderButton->setToolTip(tr("Render Model"));
}
else
{
setWindowTitle(mDialogMode == lcRenderDialogMode::OpenInBlender ? tr("Blender LDraw Import") : tr("Blender Image Render"));
bool BlenderConfigured = !lcGetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE).isEmpty();
QStringList const& DataPathList = QStandardPaths::standardLocations(QStandardPaths::AppLocalDataLocation);
if (!QDir(QString("%1/Blender/addons/%2").arg(DataPathList.first(), LC_BLENDER_ADDON_RENDER_FOLDER)).isReadable())
{
BlenderConfigured = false;
lcSetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE, QString());
}
mRenderButton->setToolTip(BlenderConfigured ? tr("Render Model") : tr("Blender not configured. Use Settings to configure."));
mRenderButton->setEnabled(BlenderConfigured);
mSettingsButton->setToolTip(tr("Blender render settings"));
if (mDialogMode == lcRenderDialogMode::OpenInBlender)
{
mSettingsButton->setToolTip(tr("Blender import settings"));
mRenderButton->setText(tr("Open in Blender"));
if (BlenderConfigured)
mRenderButton->setToolTip(tr("Import and open LDraw model in Blender"));
ui->outputLabel->hide();
ui->OutputEdit->hide();
ui->RenderProgress->hide();
ui->OutputBrowseButton->hide();
mLogButton->hide();
adjustSize();
setMinimumWidth(ui->preview->geometry().width());
ui->preview->hide();
}
else
{
QImage Image(QPixmap(":/resources/file_render_blender_logo_1280x720.png").toImage());
Image = Image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
ui->preview->SetImage(Image);
}
}
connect(&mUpdateTimer, &QTimer::timeout, this, &lcRenderDialog::Update);
mUpdateTimer.start(500);
}
lcRenderDialog::~lcRenderDialog()
{
delete ui;
}
QString lcRenderDialog::GetStdOutFileName() const
{
QString LogFile = mDialogMode == lcRenderDialogMode::RenderPOVRay ? QLatin1String("leocad-povray-render.out") : QLatin1String("leocad-blender-render.out");
return QDir(QDir::tempPath()).absoluteFilePath(LogFile);
}
QString lcRenderDialog::GetStdErrFileName() const
{
QString LogFile = mDialogMode == lcRenderDialogMode::RenderPOVRay ? QLatin1String("leocad-povray-render.err") : QLatin1String("leocad-blender-render.err");
return QDir(QDir::tempPath()).absoluteFilePath(LogFile);
}
QString lcRenderDialog::GetPOVFileName() const
{
return QDir(QDir::tempPath()).absoluteFilePath("leocad-render.pov");
}
void lcRenderDialog::CloseProcess()
{
delete mProcess;
mProcess = nullptr;
if (mDialogMode == lcRenderDialogMode::RenderPOVRay)
{
#if LC_POVRAY_MEMORY_MAPPED_FILE
mOutputFile.unmap((uchar*)mOutputBuffer);
mOutputBuffer = nullptr;
mOutputFile.close();
QFile::remove(GetStdOutFileName());
#endif
QFile::remove(GetPOVFileName());
}
if (mDialogMode != lcRenderDialogMode::OpenInBlender)
mRenderButton->setText(tr("Render"));
}
bool lcRenderDialog::PromptCancel()
{
if (!mProcess)
return true;
if (QMessageBox::question(this, tr("Cancel Render"), tr("Are you sure you want to cancel the current render?"), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
return false;
if (mProcess)
{
#ifdef Q_OS_WIN
lcTerminateChildProcess(this, mProcess->processId(), QCoreApplication::applicationPid());
#endif
mProcess->kill();
}
CloseProcess();
if (mStdOutList.size())
{
WriteStdOut();
mLogButton->setEnabled(true);
}
return true;
}
void lcRenderDialog::RenderButtonClicked()
{
if (mProcess)
{
PromptCancel();
return;
}
mLogButton->setEnabled(false);
mPreviewWidth = ui->preview->width();
mPreviewHeight = ui->preview->height();
mRenderTime.start();
if (mDialogMode == lcRenderDialogMode::RenderPOVRay)
RenderPOVRay();
else
RenderBlender();
}
void lcRenderDialog::reject()
{
if (PromptCancel())
QDialog::reject();
}
void lcRenderDialog::RenderPOVRay()
{
QString FileName = GetPOVFileName();
ui->preview->SetImage(QImage());
ui->RenderProgress->setValue(ui->RenderProgress->minimum());
ui->RenderProgress->setFormat(tr("Exporting Model"));
QApplication::setOverrideCursor(Qt::WaitCursor);
lcResult<void> ExportResult = lcGetActiveProject()->ExportPOVRay(FileName);
QGuiApplication::restoreOverrideCursor();
ui->RenderProgress->setFormat("%p%");
if (!ExportResult)
{
if (!ExportResult.error().isEmpty())
QMessageBox::information(this, tr("Render Error"), ExportResult.error());
return;
}
QStringList Arguments;
Arguments.append(QString::fromLatin1("+I\"%1\"").arg(FileName));
Arguments.append(QString::fromLatin1("+W%1").arg(QString::number(mWidth)));
Arguments.append(QString::fromLatin1("+H%1").arg(QString::number(mHeight)));
Arguments.append("-O-");
#if LC_POVRAY_MEMORY_MAPPED_FILE
Arguments.append(QString::fromLatin1("+SM\"%1\"").arg(GetStdOutFileName()));
#endif
switch (mQuality)
{
case lcPOVRayRenderQuality::High:
Arguments.append("+Q11");
Arguments.append("+R3");
Arguments.append("+A0.1");
Arguments.append("+J0.5");
break;
case lcPOVRayRenderQuality::Medium:
Arguments.append("+Q5");
Arguments.append("+A0.1");
break;
case lcPOVRayRenderQuality::Low:
break;
}
QString POVRayPath;
#ifdef Q_OS_WIN
POVRayPath = QDir::cleanPath(QCoreApplication::applicationDirPath() + QLatin1String("/povconsole32-sse2.exe"));
#endif
#ifdef Q_OS_LINUX
POVRayPath = lcGetProfileString(LC_PROFILE_POVRAY_PATH);
Arguments.append("+FN");
Arguments.append("-D");
#endif
#ifdef Q_OS_MACOS
POVRayPath = QDir::cleanPath(QCoreApplication::applicationDirPath() + QLatin1String("/povray"));
#endif
const QString POVRayDir = QFileInfo(POVRayPath).absolutePath();
const QString IncludePath = QDir::cleanPath(POVRayDir + "/include");
if (QFileInfo::exists(IncludePath))
Arguments.append(QString("+L\"%1\"").arg(IncludePath));
const QString IniPath = QDir::cleanPath(POVRayDir + "/ini");
if (QFileInfo::exists(IniPath))
Arguments.append(QString("+L\"%1\"").arg(IniPath));
if (lcGetActiveProject()->GetModels()[0]->GetPOVRayOptions().UseLGEO)
{
const QString LGEOPath = lcGetProfileString(LC_PROFILE_POVRAY_LGEO_PATH);
if (QFileInfo::exists(LGEOPath))
{
const QString LgPath = QDir::cleanPath(LGEOPath + "/lg");
if (QFileInfo::exists(LgPath))
Arguments.append(QString("+L\"%1\"").arg(LgPath));
const QString ArPath = QDir::cleanPath(LGEOPath + "/ar");
if (QFileInfo::exists(ArPath))
Arguments.append(QString("+L\"%1\"").arg(ArPath));
const QString StlPath = QDir::cleanPath(LGEOPath + "/stl");
if (QFileInfo::exists(StlPath))
Arguments.append(QString("+L\"%1\"").arg(StlPath));
}
}
lcRenderProcess* Process = new lcRenderProcess(this);
#ifdef Q_OS_LINUX
connect(Process, &QProcess::readyReadStandardError, this, [this]() { bool Error; ReadStdErr(Error); });
#endif
QStringList POVEnv = QProcess::systemEnvironment();
POVEnv.prepend("POV_IGNORE_SYSCONF_MSG=1");
Process->setEnvironment(POVEnv);
Process->setStandardErrorFile(GetStdErrFileName());
Process->start(POVRayPath, Arguments);
mImage = QImage(mWidth, mHeight, QImage::Format_ARGB32);
mImage.fill(Qt::transparent);
ui->preview->SetImage(mImage);
if (Process->waitForStarted())
{
mProcess = Process;
mRenderButton->setText(tr("Cancel"));
ui->RenderProgress->setValue(ui->RenderProgress->minimum());
mStdErrList.clear();
}
else
{
delete Process;
QMessageBox::information(this, tr("Render Error"), tr("Error starting POV-Ray."));
CloseProcess();
}
}
void lcRenderDialog::RenderBlender()
{
const QString BlenderLDrawConfigFile = lcGetProfileString(LC_PROFILE_BLENDER_LDRAW_CONFIG_PATH);
const QString BlenderImportModule = lcGetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE);
if (!QFileInfo(BlenderLDrawConfigFile).isReadable() && !BlenderImportModule.isEmpty())
lcBlenderPreferences::SaveSettings();
ui->RenderProgress->setFormat(tr("Saving Blender Model"));
QApplication::processEvents();
mBlendProgValue = 0;
mBlendProgMax = 0;
const QStringList DataPathList = QStandardPaths::standardLocations(QStandardPaths::AppLocalDataLocation);
mDataPath = DataPathList.first();
const QString DefaultBlendFile = QString("%1/blender/config/%2").arg(mDataPath, LC_BLENDER_ADDON_BLEND_FILE);
lcModel* Model = lcGetActiveProject()->GetActiveModel();
const QString ModelFileName = QFileInfo(QDir(lcGetProfileString(LC_PROFILE_PROJECTS_PATH)), QString("%1_Step_%2.ldr").arg(QFileInfo(Model->GetProperties().mFileName).baseName()).arg(Model->GetCurrentStep())).absoluteFilePath();
lcResult<void> ExportResult = lcGetActiveProject()->ExportCurrentStep(ModelFileName);
if (!ExportResult)
{
QMessageBox::warning(this, tr("Error"), ExportResult.error());
return;
}
ui->RenderProgress->setFormat("%p%");
QApplication::processEvents();
if (!QFileInfo(ModelFileName).isReadable())
return;
bool SearchCustomDir = true;
QString Message;
QStringList Arguments;
QString PythonExpression = QString("\"import bpy; bpy.ops.render_scene.lpub3d_render_ldraw("
"'EXEC_DEFAULT', "
"resolution_width=%1, resolution_height=%2, "
"render_percentage=%3, model_file=r'%4', "
"image_file=r'%5'")
.arg(mWidth).arg(mHeight)
.arg(mScale * 100)
.arg(QDir::toNativeSeparators(ModelFileName).replace("\\","\\\\"),
QDir::toNativeSeparators(ui->OutputEdit->text()).replace("\\","\\\\"));
if (BlenderImportModule == QLatin1String("MM"))
PythonExpression.append(", use_ldraw_import_mm=True");
if (SearchCustomDir)
PythonExpression.append(", search_additional_paths=True");
if (mDialogMode == lcRenderDialogMode::OpenInBlender)
{
PythonExpression.append(", import_only=True");
Arguments << QLatin1String("--window-geometry");
Arguments << QLatin1String("200 100 1440 900");
}
else
{
Arguments << QLatin1String("--background");
}
PythonExpression.append(", cli_render=True)\"");
if (QFileInfo::exists(DefaultBlendFile))
Arguments << QDir::toNativeSeparators(DefaultBlendFile);
Arguments << QString("--python-expr");
Arguments << PythonExpression;
QString ScriptName, ScriptCommand, ShellProgram;
#ifdef Q_OS_WIN
ScriptName = QLatin1String("render_ldraw_model.bat");
#else
ScriptName = QLatin1String("render_ldraw_model.sh");
#endif
ScriptCommand = QString("\"%1\" %2").arg(lcGetProfileString(LC_PROFILE_BLENDER_PATH), Arguments.join(" "));
if (mDialogMode == lcRenderDialogMode::OpenInBlender)
ScriptCommand.append(QString(" > %1").arg(QDir::toNativeSeparators(GetStdOutFileName())));
const QLatin1String LineEnding("\r\n");
QFile ScriptFile(QString("%1/%2").arg(QDir::tempPath(), ScriptName));
if (ScriptFile.open(QIODevice::WriteOnly | QIODevice::Text))
{
QTextStream Stream(&ScriptFile);
#ifdef Q_OS_WIN
Stream << QLatin1String("@ECHO OFF &SETLOCAL") << LineEnding;
#else
Stream << QLatin1String("#!/bin/bash") << LineEnding;
#endif
Stream << ScriptCommand << LineEnding;
ScriptFile.close();
}
else
{
QMessageBox::information(this, tr("Render Error"), tr("Cannot write Blender render script file '%1':\n%2.").arg(ScriptFile.fileName(), ScriptFile.errorString()));
return;
}
QThread::sleep(2);
#ifdef Q_OS_WIN
ShellProgram = QLatin1String(LC_WINDOWS_SHELL);
#else
ShellProgram = QLatin1String(LC_UNIX_SHELL);
#endif
mProcess = new lcRenderProcess(this);
connect(mProcess, &QProcess::readyReadStandardOutput, this, &lcRenderDialog::ReadStdOut);
const QString LDrawLibPath = QFileInfo(lcGetProfileString(LC_PROFILE_PARTS_LIBRARY)).absolutePath();
QStringList SystemEnvironment = QProcess::systemEnvironment();
SystemEnvironment.prepend("LDRAW_DIRECTORY=" + LDrawLibPath);
mProcess->setEnvironment(SystemEnvironment);
mProcess->setWorkingDirectory(QDir::toNativeSeparators(QString("%1/blender").arg(mDataPath)));
mProcess->setStandardErrorFile(GetStdErrFileName());
if (mDialogMode == lcRenderDialogMode::OpenInBlender)
{
QFileInfo Info(GetStdOutFileName());
if (Info.exists())
QFile::remove(Info.absoluteFilePath());
#ifdef Q_OS_WIN
mProcess->startDetached(ShellProgram, QStringList() << "/C" << ScriptFile.fileName());
#else
mProcess->startDetached(ShellProgram, QStringList() << ScriptFile.fileName());
#endif
if (mProcess)
{
mProcess->kill();
CloseProcess();
if (mStdOutList.size())
WriteStdOut();
if (Info.exists())
{
QFile Log(Info.absoluteFilePath());
QTime Wait = QTime::currentTime().addSecs(3);
while (!Log.size() || QTime::currentTime() < Wait)
QCoreApplication::processEvents(QEventLoop::AllEvents, 100);
if (Log.size()) {
if (Log.open(QFile::ReadOnly | QFile::Text))
{
QByteArray Ba = Log.readAll();
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
const bool Error = QString(Ba).contains(QRegularExpression("(?:\\w)*ERROR: ", QRegularExpression::CaseInsensitiveOption));
const bool Warning = QString(Ba).contains(QRegularExpression("(?:\\w)*WARNING: ", QRegularExpression::CaseInsensitiveOption));
#else
const bool Error = QString(Ba).contains(QRegExp("(?:\\w)*ERROR: ", Qt::CaseInsensitive));
const bool Warning = QString(Ba).contains(QRegExp("(?:\\w)*WARNING: ", Qt::CaseInsensitive));
#endif
if (Error || Warning)
{
QMessageBox::Icon Icon = QMessageBox::Information;
const QString Items = Error ? tr("errors%1").arg(Warning ? tr(" and warnings") : "") : Warning ? tr("warnings") : "";
const QString Title = tr("Open in Blender output");
const QString Body = tr("Open in Blender encountered %1. See Show Details...").arg(Items);
lcBlenderPreferences::ShowMessage(this, Body, Title, QString(), QString(Ba), 0, Icon);
}
}
}
}
close();
return;
}
}
else
{
ui->RenderProgress->setRange(mBlendProgValue, mBlendProgMax);
ui->RenderProgress->setValue(1);
#ifdef Q_OS_WIN
mProcess->start(ShellProgram, QStringList() << "/C" << ScriptFile.fileName());
#else
mProcess->start(ShellProgram, QStringList() << ScriptFile.fileName());
#endif
}
if (mProcess->waitForStarted())
{
mRenderButton->setText(tr("Cancel"));
ui->RenderProgress->setValue(ui->RenderProgress->minimum());
QApplication::processEvents();
}
else
{
Message = tr("Error starting Blender render process");
QMessageBox::information(this, tr("Render Error"), Message);
CloseProcess();
}
}
void lcRenderDialog::ReadStdOut()
{
if (mDialogMode == lcRenderDialogMode::RenderPOVRay)
return;
QString StdOut = QString(mProcess->readAllStandardOutput());
mStdOutList.append(StdOut);
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
QRegularExpression RxRenderProgress;
RxRenderProgress.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
#else
QRegExp RxRenderProgress;
RxRenderProgress.setCaseSensitivity(Qt::CaseInsensitive);
#endif
int BlenderVersionNum = QString(lcGetProfileString(LC_PROFILE_BLENDER_VERSION).mid(1, 1)).toInt();
bool BlenderVersion3OrGreater = BlenderVersionNum > 2;
if (BlenderVersion3OrGreater)
RxRenderProgress.setPattern("Sample (\\d+)\\/(\\d+)");
else
RxRenderProgress.setPattern("(\\d+)\\/(\\d+) Tiles");
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
QRegularExpressionMatch Match = RxRenderProgress.match(StdOut);
if (Match.hasMatch())
{
mBlendProgValue = Match.captured(1).toInt();
mBlendProgMax = Match.captured(2).toInt();
ui->RenderProgress->setMaximum(mBlendProgMax);
ui->RenderProgress->setValue(mBlendProgValue);
}
#else
if (StdOut.contains(RxRenderProgress))
{
mBlendProgValue = RxRenderProgress.cap(1).toInt();
mBlendProgMax = RxRenderProgress.cap(2).toInt();
ui->RenderProgress->setMaximum(mBlendProgMax);
ui->RenderProgress->setValue(mBlendProgValue);
}
#endif
}
QString lcRenderDialog::ReadStdErr(bool& HasError) const
{
HasError = mDialogMode == lcRenderDialogMode::RenderBlender ? false : true;
QFile File;
QStringList ReturnLines;
File.setFileName(GetStdErrFileName());
if (!File.open(QFile::ReadOnly | QFile::Text))
{
const QString Message = tr("Failed to open log file: %1:\n%2").arg(File.fileName(), File.errorString());
return Message;
}
QTextStream In(&File);
while (!In.atEnd())
{
QString Line = In.readLine(0);
ReturnLines << Line.trimmed() + "<br>";
if (mDialogMode == lcRenderDialogMode::RenderPOVRay)
{
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
if (Line.contains(QRegularExpression("^POV-Ray finished$", QRegularExpression::CaseInsensitiveOption)))
HasError = false;
#else
if (Line.contains(QRegExp("^POV-Ray finished$", Qt::CaseSensitive)))
HasError = false;
#endif
}
else if (mDialogMode == lcRenderDialogMode::RenderBlender)
{
if (!HasError && !Line.isEmpty())
HasError = true;
}
}
return ReturnLines.join(" ");
}
void lcRenderDialog::WriteStdOut()
{
QFile File(GetStdOutFileName());
if (File.open(QFile::WriteOnly | QIODevice::Truncate | QFile::Text))
{
QTextStream Out(&File);
for (const QString& Line : std::as_const(mStdOutList))
Out << Line;
File.close();
mLogButton->setEnabled(true);
}
}
void lcRenderDialog::Update()
{
if (!mProcess)
return;
const bool ProcessFinished = mProcess->state() == QProcess::NotRunning;
if (mDialogMode == lcRenderDialogMode::RenderPOVRay)
{
#if LC_POVRAY_MEMORY_MAPPED_FILE
if (!mOutputBuffer)
{
mOutputFile.setFileName(GetStdOutFileName());
if (!mOutputFile.open(QFile::ReadWrite))
{
if (ProcessFinished)
{
ShowResult();
CloseProcess();
}
return;
}
mOutputBuffer = mOutputFile.map(0, mOutputFile.size());
if (!mOutputBuffer)
{
mOutputFile.close();
if (ProcessFinished)
{
ShowResult();
CloseProcess();
}
return;
}
}
struct lcSharedMemoryHeader
{
quint32 Version;
quint32 Width;
quint32 Height;
quint32 PixelsWritten;
quint32 PixelsRead;
};
lcSharedMemoryHeader* Header = (lcSharedMemoryHeader*)mOutputBuffer;
if (Header->PixelsWritten == Header->PixelsRead && !ProcessFinished)
return;
int Width = Header->Width;
int Height = Header->Height;
int PixelsWritten = Header->PixelsWritten;
if (!Header->PixelsRead)
mImage = QImage(Width, Height, QImage::Format_ARGB32);
quint8* Pixels = (quint8*)(Header + 1);
for (int y = 0; y < Height; y++)
{
for (int x = 0; x < Width; x++)
{
mImage.setPixel(x, y, qRgba(Pixels[0], Pixels[1], Pixels[2], Pixels[3]));
Pixels += 4;
}
}
Header->PixelsRead = PixelsWritten;
ui->RenderProgress->setMaximum(mImage.width() * mImage.height());
ui->RenderProgress->setValue(int(Header->PixelsRead));
if (PixelsWritten == Width * Height)
ui->RenderProgress->setValue(ui->RenderProgress->maximum());
ui->preview->SetImage(mImage.scaled(mPreviewWidth, mPreviewHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation));
#endif
}
if (ProcessFinished)
{
#ifdef Q_OS_LINUX
QByteArray Output = mProcess->readAllStandardOutput();
mImage = QImage::fromData(Output);
#endif
ShowResult();
CloseProcess();
}
}
void lcRenderDialog::ShowResult()
{
bool Error;
const QString StdErrLog = ReadStdErr(Error);
if (mProcess->exitStatus() != QProcess::NormalExit || mProcess->exitCode() != 0 || Error)
{
ui->RenderProgress->setRange(0, 1);
ui->RenderProgress->setValue(0);
const QString Body = tr("An error occurred while rendering.");
lcBlenderPreferences::ShowMessage(this, Body, tr("Render Error"), QString(), StdErrLog, 0, QMessageBox::Information);
return;
}
else
{
ui->RenderProgress->setValue(ui->RenderProgress->maximum());
}
bool Success = false;
QString FileName = ui->OutputEdit->text();
if (mDialogMode == lcRenderDialogMode::RenderPOVRay)
{
ui->preview->SetImage(mImage.scaled(mPreviewWidth, mPreviewHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation));
if (!FileName.isEmpty())
{
QImageWriter Writer(FileName);
Success = Writer.write(mImage);
if (!Success)
QMessageBox::information(this, tr("Render Error"), tr("Error writing to file '%1':\n%2").arg(FileName, Writer.errorString()));
}
}
else if (mDialogMode == lcRenderDialogMode::RenderBlender)
{
Success = QFileInfo::exists(FileName);
if (Success)
{
setMinimumSize(100, 100);
QImageReader Reader(FileName);
mImage = Reader.read();
mImage = mImage.convertToFormat(QImage::Format_ARGB32_Premultiplied);
mPreviewWidth = mImage.width();
mPreviewHeight = mImage.height();
ui->preview->SetImage(mImage.scaled(mPreviewWidth, mPreviewHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation));
}
}
if (!Success)
QMessageBox::information(this, tr("Render Error"), tr("Render failed."));
WriteStdOut();
}
void lcRenderDialog::OutputBrowseButtonClicked()
{
const QString Result = QFileDialog::getSaveFileName(this, tr("Select Output File"), ui->OutputEdit->text(), tr("Supported Image Files (*.bmp *.png *.jpg);;BMP Files (*.bmp);;PNG Files (*.png);;JPEG Files (*.jpg);;All Files (*.*)"));
if (!Result.isEmpty())
ui->OutputEdit->setText(QDir::toNativeSeparators(Result));
}
void lcRenderDialog::SettingsButtonClicked()
{
if (mDialogMode == lcRenderDialogMode::RenderPOVRay)
{
QDialog Dialog(this);
Dialog.setWindowTitle(tr("POV-Ray Settings"));
QFormLayout* Layout = new QFormLayout(&Dialog);
QLineEdit* WidthEdit = new QLineEdit(&Dialog);
Layout->addRow(tr("Width:"), WidthEdit);
WidthEdit->setText(QString::number(mWidth));
WidthEdit->setValidator(new QIntValidator(16, INT_MAX, this));
QLineEdit* HeightEdit = new QLineEdit(&Dialog);
Layout->addRow(tr("Height:"), HeightEdit);
HeightEdit->setText(QString::number(mHeight));
HeightEdit->setValidator(new QIntValidator(16, INT_MAX, this));
QComboBox* QualityComboBox = new QComboBox(&Dialog);
Layout->addRow(tr("Quality:"), QualityComboBox);
QualityComboBox->addItems({ tr("Low"), tr("Medium"), tr("High") });
QualityComboBox->setCurrentIndex(static_cast<int>(mQuality));
QDialogButtonBox* ButtonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, &Dialog);
Layout->addWidget(ButtonBox);
QObject::connect(ButtonBox, &QDialogButtonBox::rejected, &Dialog, &QDialog::reject);
QObject::connect(ButtonBox, &QDialogButtonBox::accepted, &Dialog, &QDialog::accept);
if (Dialog.exec() != QDialog::Accepted)
return;
mWidth = std::clamp(WidthEdit->text().toInt(), 16, INT_MAX);
mHeight = std::clamp(HeightEdit->text().toInt(), 16, INT_MAX);
mQuality = static_cast<lcPOVRayRenderQuality>(QualityComboBox->currentIndex());
lcSetProfileInt(LC_PROFILE_RENDER_WIDTH, mWidth);
lcSetProfileInt(LC_PROFILE_RENDER_HEIGHT, mHeight);
lcSetProfileRenderQuality(mQuality);
}
else
{
lcBlenderPreferencesDialog::GetBlenderPreferences(mWidth, mHeight, mScale, this);
if (lcGetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE).isEmpty())
mRenderButton->setToolTip(tr("Blender not configured. Use Settings to configure."));
else
mRenderButton->setEnabled(true);
}
}
void lcRenderDialog::LogButtonClicked()
{
QFileInfo FileInfo;
QString Message ;
if (mDialogMode == lcRenderDialogMode::RenderPOVRay)
{
FileInfo.setFile(GetStdErrFileName());
Message = tr("POV-Ray standard error file not found: %1.").arg(FileInfo.absoluteFilePath());
}
else
{
FileInfo.setFile(GetStdOutFileName());
Message = tr("Blender standard output file not found: %1.").arg(FileInfo.absoluteFilePath());
}
if (!FileInfo.exists())
{
QMessageBox::information(this, tr("Render Log"), Message);
return;
}
QFile File(FileInfo.absoluteFilePath());
if (!File.open(QFile::ReadOnly | QFile::Text))
{
QMessageBox::information(this, tr("Render Log"), tr("Error opening log file '%1':\n%2.").arg(FileInfo.absoluteFilePath(), File.errorString()));
return;
}
QString Text = File.readAll();
File.close();
QDialog Dialog(this);
Dialog.setWindowTitle(tr("Render Log"));
Dialog.resize(800, 600);
QVBoxLayout* Layout = new QVBoxLayout(&Dialog);
QTextBrowser* TextBrowser = new QTextBrowser(&Dialog);
Layout->addWidget(TextBrowser);
TextBrowser->setText(Text);
QDialogButtonBox* ButtonBox = new QDialogButtonBox(QDialogButtonBox::Close, Qt::Horizontal, &Dialog);
Layout->addWidget(ButtonBox);
QObject::connect(ButtonBox, &QDialogButtonBox::rejected, &Dialog, &QDialog::reject);
QObject::connect(ButtonBox, &QDialogButtonBox::accepted, &Dialog, &QDialog::accept);
Dialog.exec();
}
lcRenderProcess::~lcRenderProcess()
{
if (state() == QProcess::Running || state() == QProcess::Starting)
{
terminate();
waitForFinished();
}
}
#endif // LC_DISABLE_RENDER_DIALOG