#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 = static_cast(lcGetProfileInt(LC_PROFILE_RENDER_QUALITY)); 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")); QFuture> exportThread = QtConcurrent::run([FileName]() { return lcGetActiveProject()->ExportPOVRay(FileName); }); QApplication::setOverrideCursor(Qt::WaitCursor); while (!exportThread.isFinished()) QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); QGuiApplication::restoreOverrideCursor(); ui->RenderProgress->setFormat("%p%"); auto [Success, ErrorMessage] = exportThread.result(); if (!Success) { if (!ErrorMessage.isEmpty()) QMessageBox::information(this, tr("Render Error"), ErrorMessage); 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(); lcGetActiveProject()->ExportCurrentStep(ModelFileName); 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() + "
"; 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; if (mProcess->state() == QProcess::NotRunning) { #ifdef Q_OS_LINUX QByteArray Output = mProcess->readAllStandardOutput(); mImage = QImage::fromData(Output); #endif ShowResult(); CloseProcess(); } if (mDialogMode == lcRenderDialogMode::RenderPOVRay) { #if LC_POVRAY_MEMORY_MAPPED_FILE if (!mOutputBuffer) { mOutputFile.setFileName(GetStdOutFileName()); if (!mOutputFile.open(QFile::ReadWrite)) return; mOutputBuffer = mOutputFile.map(0, mOutputFile.size()); if (!mOutputBuffer) { mOutputFile.close(); return; } } struct lcSharedMemoryHeader { quint32 Version; quint32 Width; quint32 Height; quint32 PixelsWritten; quint32 PixelsRead; }; lcSharedMemoryHeader* Header = (lcSharedMemoryHeader*)mOutputBuffer; if (Header->PixelsWritten == Header->PixelsRead) 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 } } 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(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(QualityComboBox->currentIndex()); lcSetProfileInt(LC_PROFILE_RENDER_WIDTH, mWidth); lcSetProfileInt(LC_PROFILE_RENDER_HEIGHT, mHeight); lcSetProfileInt(LC_PROFILE_RENDER_QUALITY, static_cast(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