leocad/qt/lc_renderdialog.cpp

924 lines
27 KiB
C++
Raw Normal View History

2017-09-22 19:08:02 +02:00
#include "lc_global.h"
#include "lc_renderdialog.h"
#include "ui_lc_renderdialog.h"
#include "project.h"
#include "lc_application.h"
2017-11-03 01:35:12 +01:00
#include "lc_profile.h"
2023-06-02 00:02:53 +02:00
#include "lc_blenderpreferences.h"
#include "lc_mainwindow.h"
#include "lc_model.h"
#ifdef Q_OS_WIN
#include <TlHelp32.h>
#endif
2017-09-22 19:08:02 +02:00
2017-11-05 02:54:12 +01:00
#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
2017-11-05 02:54:12 +01:00
2023-06-19 04:06:44 +02:00
static inline QString ElapsedTime(const qint64& Duration)
2023-06-02 00:02:53 +02:00
{
qint64 Elapsed = Duration;
int Milliseconds = int(Elapsed % 1000);
Elapsed /= 1000;
int Seconds = int(Elapsed % 60);
Elapsed /= 60;
int Minutes = int(Elapsed % 60);
Elapsed /= 60;
int Hours = int(Elapsed % 24);
return QObject::tr("Elapsed time: %1%2%3")
.arg(Hours > 0 ? QString("%1 %2 ").arg(Hours).arg(Hours > 1 ? QObject::tr("hours") : QObject::tr("hour")) : QString())
.arg(Minutes > 0 ? QString("%1 %2 ").arg(Minutes).arg(Minutes > 1 ? QObject::tr("minutes") : QObject::tr("minute")) : QString())
.arg(QString("%1.%2 %3").arg(Seconds).arg(Milliseconds,3,10,QLatin1Char('0')).arg(Seconds > 1 ? QObject::tr("seconds") : QObject::tr("second")));
}
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::white);
}
2023-06-02 00:02:53 +02:00
lcRenderDialog::lcRenderDialog(QWidget* Parent, int Command)
2017-09-22 19:08:02 +02:00
: QDialog(Parent),
2023-06-02 00:02:53 +02:00
mCommand(Command),
ui(new Ui::lcRenderDialog)
2017-09-22 19:08:02 +02:00
{
2017-12-07 07:08:56 +01:00
#ifndef QT_NO_PROCESS
2017-09-22 19:08:02 +02:00
mProcess = nullptr;
2017-12-07 07:08:56 +01:00
#endif
2018-01-15 19:48:34 +01:00
mOutputBuffer = nullptr;
2017-09-22 19:08:02 +02:00
ui->setupUi(this);
2017-11-04 00:01:30 +01:00
2023-06-02 00:02:53 +02:00
mWidth = lcGetProfileInt(LC_PROFILE_RENDER_WIDTH);
mHeight = lcGetProfileInt(LC_PROFILE_RENDER_HEIGHT);
mScale = 1.0f;
ui->WidthEdit->setText(QString::number(mWidth));
2017-11-04 00:01:30 +01:00
ui->WidthEdit->setValidator(new QIntValidator(16, INT_MAX));
2023-06-02 00:02:53 +02:00
ui->HeightEdit->setText(QString::number(mHeight));
2017-11-04 00:01:30 +01:00
ui->HeightEdit->setValidator(new QIntValidator(16, INT_MAX));
ui->OutputEdit->setText(lcGetActiveProject()->GetImageFileName(false));
2017-11-04 00:01:30 +01:00
2023-06-02 00:02:53 +02:00
lcModel* Model = lcGetActiveProject()->GetActiveModel();
mLabelMessage = tr("Render image:");
2023-06-02 00:02:53 +02:00
if (Model)
mLabelMessage = tr("Render <b>STEP %1</b> image:").arg(Model->GetCurrentStep());
2023-06-02 00:02:53 +02:00
ui->renderLabel->setText(mLabelMessage);
2023-06-02 00:02:53 +02:00
ui->RenderOutputButton->setEnabled(false);
if (mCommand == POVRAY_RENDER)
{
setWindowTitle(tr("POV-Ray Image Render"));
ui->RenderButton->setToolTip(tr("Render LDraw model"));
QImage Image(LC_POVRAY_PREVIEW_WIDTH, LC_POVRAY_PREVIEW_HEIGHT, QImage::Format_RGB32);
Image.fill(QColor(255, 255, 255));
ui->preview->SetImage(Image);
ui->RenderSettingsButton->hide();
}
else
{
setWindowTitle(tr("Blender %1").arg(mCommand == OPEN_IN_BLENDER ? tr("LDraw Import") : tr("Image Render")));
bool BlenderConfigured = !lcGetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE).isEmpty();
QStringList const& DataPathList = QStandardPaths::standardLocations(QStandardPaths::DataLocation);
if (!QDir(QString("%1/Blender/addons/%2").arg(DataPathList.first()).arg(LC_BLENDER_ADDON_FOLDER_STR)).isReadable())
{
BlenderConfigured = false;
lcSetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE, QString());
}
if (BlenderConfigured)
mImportModule = lcGetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE) == QLatin1String("TN") ? tr("LDraw Import TN") : tr("LDraw Import MM");
2023-06-02 00:02:53 +02:00
ui->RenderButton->setToolTip(BlenderConfigured ? tr("Render LDraw Model") : tr("Blender not configured. Use Settings... to configure."));
2023-06-02 00:02:53 +02:00
ui->RenderButton->setEnabled(BlenderConfigured);
ui->RenderSettingsButton->setToolTip(tr("Blender render settings"));
ui->qualityLabel->hide();
ui->QualityComboBox->hide();
if (mCommand == OPEN_IN_BLENDER)
{
mLabelMessage = tr("Open%1 in Blender using %2:") .arg(Model ? tr(" <b>STEP %1</b>").arg(Model->GetCurrentStep()) : "");
2023-06-02 00:02:53 +02:00
ui->RenderSettingsButton->setToolTip(tr("Blender import settings"));
ui->RenderButton->setText(tr("Open in Blender"));
ui->RenderButton->setFixedWidth(ui->RenderButton->sizeHint().width() + 20);
2023-06-02 00:02:53 +02:00
if (BlenderConfigured)
ui->RenderButton->setToolTip(tr("Import and open LDraw model in Blender"));
ui->renderLabel->setText(mLabelMessage.arg(mImportModule));
2023-06-02 00:02:53 +02:00
ui->renderLabel->setAlignment(Qt::AlignTrailing | Qt::AlignVCenter);
ui->outputLabel->hide();
ui->OutputEdit->hide();
ui->RenderProgress->hide();
ui->OutputBrowseButton->hide();
ui->RenderOutputButton->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, SIGNAL(timeout()), this, SLOT(UpdateElapsedTime()));
}
2017-11-05 02:54:12 +01:00
2017-09-22 19:08:02 +02:00
connect(&mUpdateTimer, SIGNAL(timeout()), this, SLOT(Update()));
2023-06-02 00:02:53 +02:00
mUpdateTimer.start(500);
2023-06-02 00:02:53 +02:00
setSizeGripEnabled(true);
2017-09-22 19:08:02 +02:00
}
lcRenderDialog::~lcRenderDialog()
{
delete ui;
}
2023-06-19 04:06:44 +02:00
QString lcRenderDialog::GetStdOutFileName() const
{
2023-06-19 04:06:44 +02:00
QString LogFile = mCommand == POVRAY_RENDER ? QLatin1String("leocad-povray-render.out") : QLatin1String("leocad-blender-render.out");
return QDir(QDir::tempPath()).absoluteFilePath(LogFile);
}
QString lcRenderDialog::GetStdErrFileName() const
{
QString LogFile = mCommand == POVRAY_RENDER ? QLatin1String("leocad-povray-render.err") : QLatin1String("leocad-blender-render.err");
2023-06-02 00:02:53 +02:00
return QDir(QDir::tempPath()).absoluteFilePath(LogFile);
}
2017-11-03 01:35:12 +01:00
QString lcRenderDialog::GetPOVFileName() const
{
return QDir(QDir::tempPath()).absoluteFilePath("leocad-render.pov");
}
2023-06-02 00:02:53 +02:00
void lcRenderDialog::UpdateElapsedTime() const
{
if (mProcess && mCommand == BLENDER_RENDER)
{
const QString RenderType = lcGetProfileString(LC_PROFILE_BLENDER_VERSION).startsWith("v3") ? QLatin1String("Samples") : QLatin1String("Tiles");
ui->renderLabel->setText(tr("%1: %2/%3, %4") .arg(RenderType) .arg(mBlendProgValue) .arg(mBlendProgMax) .arg(ElapsedTime(mRenderTime.elapsed())));
2023-06-02 00:02:53 +02:00
}
}
2017-11-05 02:54:12 +01:00
void lcRenderDialog::CloseProcess()
2017-11-04 00:01:30 +01:00
{
2017-12-07 07:08:56 +01:00
#ifndef QT_NO_PROCESS
2017-11-05 02:54:12 +01:00
delete mProcess;
mProcess = nullptr;
2017-12-07 07:08:56 +01:00
#endif
2023-06-02 00:02:53 +02:00
if (mCommand == POVRAY_RENDER)
{
#if LC_POVRAY_MEMORY_MAPPED_FILE
2023-06-02 00:02:53 +02:00
mOutputFile.unmap((uchar*)mOutputBuffer);
mOutputBuffer = nullptr;
mOutputFile.close();
2023-06-19 04:06:44 +02:00
QFile::remove(GetStdOutFileName());
#endif
2023-06-02 00:02:53 +02:00
QFile::remove(GetPOVFileName());
}
2017-11-05 02:54:12 +01:00
2023-06-02 00:02:53 +02:00
if (mCommand != OPEN_IN_BLENDER)
ui->RenderButton->setText(tr("Render"));
2017-11-04 00:01:30 +01:00
}
2017-11-05 02:54:12 +01:00
bool lcRenderDialog::PromptCancel()
2017-09-22 19:08:02 +02:00
{
2017-12-07 07:08:56 +01:00
#ifndef QT_NO_PROCESS
2017-09-22 19:08:02 +02:00
if (mProcess)
{
2017-11-05 02:54:12 +01:00
if (QMessageBox::question(this, tr("Cancel Render"), tr("Are you sure you want to cancel the current render?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)
{
2023-06-02 00:02:53 +02:00
if (mCommand == POVRAY_RENDER)
gMainWindow->mActions[LC_FILE_RENDER_POVRAY]->setEnabled(true);
else if (mCommand == BLENDER_RENDER)
gMainWindow->mActions[LC_FILE_RENDER_BLENDER]->setEnabled(true);
#ifdef Q_OS_WIN
TerminateChildProcess(mProcess->processId(),
QCoreApplication::applicationPid());
#endif
mProcess->kill();
CloseProcess();
if (mStdOutList.size())
2017-11-05 02:54:12 +01:00
{
2023-06-02 00:02:53 +02:00
WriteStdOut();
ui->RenderOutputButton->setEnabled(true);
2017-11-05 02:54:12 +01:00
}
2023-06-02 00:02:53 +02:00
if (mCommand == BLENDER_RENDER)
ui->renderLabel->setText(tr("Tiles: %1/%2, Render Cancelled.") .arg(mBlendProgValue) .arg(mBlendProgMax));
2017-11-05 02:54:12 +01:00
}
else
return false;
2017-09-22 19:08:02 +02:00
}
2017-12-07 07:08:56 +01:00
#endif
2023-06-02 00:02:53 +02:00
2017-11-05 02:54:12 +01:00
return true;
}
void lcRenderDialog::reject()
{
if (PromptCancel())
QDialog::reject();
}
2023-06-02 00:02:53 +02:00
void lcRenderDialog::on_RenderSettingsButton_clicked()
{
if (mCommand == POVRAY_RENDER)
return;
lcBlenderPreferencesDialog::GetBlenderPreferences(mWidth, mHeight, mScale, this);
if (lcGetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE).isEmpty())
ui->RenderButton->setToolTip(tr("Blender not configured. Use Settings... to configure."));
else
{
if (mCommand == OPEN_IN_BLENDER)
{
mImportModule = lcGetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE) == QLatin1String("TN") ? tr("LDraw Import TN") : tr("LDraw Import MM");
ui->renderLabel->setText(mLabelMessage.arg(mImportModule));
ui->renderLabel->setAlignment(Qt::AlignTrailing | Qt::AlignVCenter);
}
2023-06-02 00:02:53 +02:00
ui->RenderButton->setEnabled(true);
}
2023-06-02 00:02:53 +02:00
}
2017-11-05 02:54:12 +01:00
void lcRenderDialog::on_RenderButton_clicked()
{
2017-12-07 07:08:56 +01:00
#ifndef QT_NO_PROCESS
if (mProcess)
{
PromptCancel();
2017-11-05 02:54:12 +01:00
return;
}
2017-11-05 02:54:12 +01:00
2023-06-02 00:02:53 +02:00
ui->RenderOutputButton->setEnabled(false);
2017-09-22 19:08:02 +02:00
2023-06-02 00:02:53 +02:00
mPreviewWidth = ui->preview->width();
mPreviewHeight = ui->preview->height();
mRenderTime.start();
if (mCommand == POVRAY_RENDER)
{
gMainWindow->mActions[LC_FILE_RENDER_POVRAY]->setEnabled(false);
QString FileName = GetPOVFileName();
QImage Image(mPreviewWidth, mPreviewHeight, QImage::Format_RGB32);
Image.fill(QColor(255, 255, 255));
ui->preview->SetImage(Image);
if (!lcGetActiveProject()->ExportPOVRay(FileName))
return;
2017-09-22 19:08:02 +02:00
2023-06-02 00:02:53 +02:00
QStringList Arguments;
2017-09-22 19:08:02 +02:00
2023-06-02 00:02:53 +02:00
Arguments.append(QString::fromLatin1("+I\"%1\"").arg(FileName));
Arguments.append(QString::fromLatin1("+W%1").arg(ui->WidthEdit->text()));
Arguments.append(QString::fromLatin1("+H%1").arg(ui->HeightEdit->text()));
Arguments.append("-O-");
2017-09-22 19:08:02 +02:00
#if LC_POVRAY_MEMORY_MAPPED_FILE
2023-06-19 04:06:44 +02:00
Arguments.append(QString::fromLatin1("+SM\"%1\"").arg(GetStdOutFileName()));
#endif
2023-06-02 00:02:53 +02:00
int Quality = ui->QualityComboBox->currentIndex();
2017-12-27 22:55:37 +01:00
2023-06-02 00:02:53 +02:00
switch (Quality)
{
case 0:
Arguments.append("+Q11");
Arguments.append("+R3");
Arguments.append("+A0.1");
Arguments.append("+J0.5");
break;
case 1:
Arguments.append("+Q5");
Arguments.append("+A0.1");
break;
case 2:
break;
}
2017-09-22 19:08:02 +02:00
2023-06-02 00:02:53 +02:00
QString POVRayPath;
2017-11-03 01:35:12 +01:00
#ifdef Q_OS_WIN
2023-06-02 00:02:53 +02:00
POVRayPath = QDir::cleanPath(QCoreApplication::applicationDirPath() + QLatin1String("/povconsole32-sse2.exe"));
2017-11-03 01:35:12 +01:00
#endif
#ifdef Q_OS_LINUX
2023-06-02 00:02:53 +02:00
POVRayPath = lcGetProfileString(LC_PROFILE_POVRAY_PATH);
Arguments.append("+FN");
Arguments.append("-D");
2017-11-03 01:35:12 +01:00
#endif
#ifdef Q_OS_MACOS
2023-06-02 00:02:53 +02:00
POVRayPath = QDir::cleanPath(QCoreApplication::applicationDirPath() + QLatin1String("/povray"));
2017-11-03 01:35:12 +01:00
#endif
2017-09-22 19:08:02 +02:00
2023-08-09 12:35:07 +02:00
const QString POVRayDir = QFileInfo(POVRayPath).absolutePath();
const QString IncludePath = QDir::cleanPath(POVRayDir + "/include");
if (QFileInfo(IncludePath).exists())
Arguments.append(QString("+L\"%1\"").arg(IncludePath));
const QString IniPath = QDir::cleanPath(POVRayDir + "/ini");
if (QFileInfo(IniPath).exists())
Arguments.append(QString("+L\"%1\"").arg(IniPath));
if (lcGetActiveProject()->GetModels()[0]->GetPOVRayOptions().UseLGEO) {
const QString LGEOPath = lcGetProfileString(LC_PROFILE_POVRAY_LGEO_PATH);
if (QFileInfo(LGEOPath).exists())
{
const QString LgPath = QDir::cleanPath(LGEOPath + "/lg");
if (QFileInfo(LgPath).exists())
Arguments.append(QString("+L\"%1\"").arg(LgPath));
const QString ArPath = QDir::cleanPath(LGEOPath + "/ar");
if (QFileInfo(ArPath).exists())
Arguments.append(QString("+L\"%1\"").arg(ArPath));
const QString StlPath = QDir::cleanPath(LGEOPath + "/stl");
if (QFileInfo(StlPath).exists())
Arguments.append(QString("+L\"%1\"").arg(StlPath));
}
}
2023-06-19 04:06:44 +02:00
mProcess = new lcRenderProcess(this);
#ifdef Q_OS_LINUX
2023-06-02 00:02:53 +02:00
connect(mProcess, SIGNAL(readyReadStandardError()), this, SLOT(ReadStdErr()));
#endif
QStringList POVEnv = QProcess::systemEnvironment();
POVEnv.prepend("POV_IGNORE_SYSCONF_MSG=1");
mProcess->setEnvironment(POVEnv);
2023-06-19 04:06:44 +02:00
mProcess->setStandardErrorFile(GetStdErrFileName());
2023-06-02 00:02:53 +02:00
mProcess->start(POVRayPath, Arguments);
mImage = QImage(ui->WidthEdit->text().toInt(), ui->HeightEdit->text().toInt(), QImage::Format_ARGB32);
mImage.fill(QColor(255, 255, 255));
ui->preview->SetImage(mImage);
if (mProcess->waitForStarted())
{
ui->RenderButton->setText(tr("Cancel"));
ui->RenderProgress->setValue(ui->RenderProgress->minimum());
mStdErrList.clear();
}
else
{
gMainWindow->mActions[LC_FILE_RENDER_POVRAY]->setEnabled(true);
QMessageBox::warning(this, tr("Error"), tr("Error starting POV-Ray."));
CloseProcess();
}
}
else
{
const QString BlenderLDrawConfigFile = lcGetProfileString(LC_PROFILE_BLENDER_LDRAW_CONFIG_PATH);
const QString BlenderImportModule = lcGetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE);
2023-06-02 00:02:53 +02:00
if (!QFileInfo(BlenderLDrawConfigFile).isReadable() && !BlenderImportModule.isEmpty())
lcBlenderPreferences::SaveSettings();
const QString Option = mCommand == OPEN_IN_BLENDER ? tr("import") : tr("render");
2023-06-02 00:02:53 +02:00
ui->renderLabel->setText(tr("Saving Blender %1 model...").arg(Option));
QApplication::processEvents();
mBlendProgValue = 0;
mBlendProgMax = 0;
2023-06-19 04:06:44 +02:00
const QStringList DataPathList = QStandardPaths::standardLocations(QStandardPaths::DataLocation);
2023-06-02 00:02:53 +02:00
mDataPath = DataPathList.first();
const QString DefaultBlendFile = QString("%1/blender/config/%2").arg(mDataPath).arg(LC_BLENDER_ADDON_BLEND_FILE);
2023-06-02 00:02:53 +02:00
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();
2023-06-02 00:02:53 +02:00
lcGetActiveProject()->ExportCurrentStep(ModelFileName);
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', preferences_file=r'%6'")
.arg(mWidth).arg(mHeight)
.arg(mScale * 100)
.arg(QDir::toNativeSeparators(ModelFileName).replace("\\","\\\\"))
.arg(QDir::toNativeSeparators(ui->OutputEdit->text()).replace("\\","\\\\"))
.arg(QDir::toNativeSeparators(BlenderLDrawConfigFile).replace("\\","\\\\"));
if (BlenderImportModule == QLatin1String("MM"))
PythonExpression.append(", use_ldraw_import_mm=True");
if (SearchCustomDir)
PythonExpression.append(", search_additional_paths=True");
if (mCommand == OPEN_IN_BLENDER)
{
PythonExpression.append(", import_only=True");
Arguments << QLatin1String("--window-geometry");
Arguments << QLatin1String("200 100 1440 900");
}
else
{
Arguments << QLatin1String("--background");
gMainWindow->mActions[LC_FILE_RENDER_BLENDER]->setEnabled(false);
}
PythonExpression.append(", cli_render=True)\"");
if (QFileInfo(DefaultBlendFile).exists())
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)).arg(Arguments.join(" "));
if (mCommand == OPEN_IN_BLENDER)
2023-06-19 04:06:44 +02:00
ScriptCommand.append(QString(" > %1").arg(QDir::toNativeSeparators(GetStdOutFileName())));
2023-06-02 00:02:53 +02:00
const QLatin1String LineEnding("\r\n");
QFile Script(QString("%1/%2").arg(QDir::tempPath()).arg(ScriptName));
if(Script.open(QIODevice::WriteOnly | QIODevice::Text))
{
QTextStream Stream(&Script);
#ifdef Q_OS_WIN
Stream << QLatin1String("@ECHO OFF &SETLOCAL") << LineEnding;
#else
Stream << QLatin1String("#!/bin/bash") << LineEnding;
#endif
Stream << ScriptCommand << LineEnding;
Script.close();
}
else
{
QMessageBox::warning(this, tr("Error"), tr("Cannot write Blender render script file [%1] %2.").arg(Script.fileName()).arg(Script.errorString()));
2023-06-02 00:02:53 +02:00
gMainWindow->mActions[LC_FILE_RENDER_BLENDER]->setEnabled(true);
return;
}
QThread::sleep(2);
#ifdef Q_OS_WIN
ShellProgram = QLatin1String(LC_WINDOWS_SHELL);
#else
ShellProgram = QLatin1String(LC_UNIX_SHELL);
#endif
2017-11-03 01:35:12 +01:00
2023-06-19 04:06:44 +02:00
mProcess = new lcRenderProcess(this);
2023-06-02 00:02:53 +02:00
connect(mProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(ReadStdOut()));
const QString LDrawLibPath = QFileInfo(lcGetProfileString(LC_PROFILE_PARTS_LIBRARY)).absolutePath();
2023-06-02 00:02:53 +02:00
QStringList SystemEnvironment = QProcess::systemEnvironment();
SystemEnvironment.prepend("LDRAW_DIRECTORY=" + LDrawLibPath);
mProcess->setEnvironment(SystemEnvironment);
mProcess->setWorkingDirectory(QDir::toNativeSeparators(QString("%1/blender").arg(mDataPath)));
2023-06-19 04:06:44 +02:00
mProcess->setStandardErrorFile(GetStdErrFileName());
2023-06-02 00:02:53 +02:00
if (mCommand == OPEN_IN_BLENDER)
{
2023-06-19 04:06:44 +02:00
QFileInfo Info(GetStdOutFileName());
2023-06-02 00:02:53 +02:00
if (Info.exists())
QFile::remove(Info.absoluteFilePath());
#ifdef Q_OS_WIN
mProcess->startDetached(ShellProgram, QStringList() << "/C" << Script.fileName());
#else
mProcess->startDetached(ShellProgram, QStringList() << Script.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();
const bool Error = QString(Ba).contains(QRegExp("(?:\\w)*ERROR: ", Qt::CaseInsensitive));
const bool Warning = QString(Ba).contains(QRegExp("(?:\\w)*WARNING: ", Qt::CaseInsensitive));
2023-06-02 00:02:53 +02:00
if (Error || Warning)
{
2023-06-19 04:06:44 +02:00
QMessageBox::Icon Icon = QMessageBox::Warning;
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);
2023-06-02 00:02:53 +02:00
lcBlenderPreferences::ShowMessage(Body, Title, QString(), QString(Ba), 0, Icon);
}
}
}
}
close();
return;
}
}
else
{
#ifdef Q_OS_WIN
mProcess->start(ShellProgram, QStringList() << "/C" << Script.fileName());
#else
mProcess->start(ShellProgram, QStringList() << Script.fileName());
#endif
}
if (mProcess->waitForStarted())
{
ui->RenderButton->setText(tr("Cancel"));
ui->RenderProgress->setValue(ui->RenderProgress->minimum());
ui->renderLabel->setText(tr("Loading LDraw model... %1").arg(ElapsedTime(mRenderTime.elapsed())));
2023-06-02 00:02:53 +02:00
QApplication::processEvents();
}
else
{
gMainWindow->mActions[LC_FILE_RENDER_BLENDER]->setEnabled(true);
Message = tr("Error starting Blender render process");
QMessageBox::warning(this, tr("Error"), Message);
CloseProcess();
}
} // BLENDER_RENDER
#endif
}
#ifdef Q_OS_WIN
int lcRenderDialog::TerminateChildProcess(const qint64 Pid, const qint64 Ppid)
{
DWORD pID = DWORD(Pid);
DWORD ppID = DWORD(Ppid);
HANDLE hSnapshot = INVALID_HANDLE_VALUE, hProcess = INVALID_HANDLE_VALUE;
PROCESSENTRY32 pe32;
if ((hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, ppID)) == INVALID_HANDLE_VALUE)
{
2023-06-02 00:02:53 +02:00
QMessageBox::warning(this, tr("Error"), QString("%1 failed: %1").arg("CreateToolhelp32Snapshot").arg(GetLastError()));
return -1;
}
2023-06-02 00:02:53 +02:00
pe32.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(hSnapshot, &pe32) == FALSE)
2017-11-03 01:35:12 +01:00
{
2023-06-02 00:02:53 +02:00
QMessageBox::warning(this, tr("Error"), QString("%1 failed: %2").arg("Process32First").arg(GetLastError()));
CloseHandle(hSnapshot);
return -2;
}
do
{
if (QString::fromWCharArray(pe32.szExeFile).contains(QRegExp("^(?:cmd\\.exe|conhost\\.exe|blender\\.exe)$", Qt::CaseInsensitive)))
{
if ((pe32.th32ProcessID == pID && pe32.th32ParentProcessID == ppID) || pe32.th32ParentProcessID == pID)
{
if ((hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, pe32.th32ProcessID)) == INVALID_HANDLE_VALUE)
{
QMessageBox::warning(this, tr("Error"), tr("%1 failed: %2").arg("OpenProcess").arg(GetLastError()));
return -3;
}
else
{
TerminateProcess(hProcess, 9);
CloseHandle(hProcess);
}
}
}
2017-11-03 01:35:12 +01:00
}
2023-06-02 00:02:53 +02:00
while (Process32Next(hSnapshot, &pe32));
CloseHandle(hSnapshot);
return 0;
}
2017-12-07 07:08:56 +01:00
#endif
2023-06-02 00:02:53 +02:00
void lcRenderDialog::ReadStdOut()
{
if (mCommand == POVRAY_RENDER)
return;
QString StdOut = QString(mProcess->readAllStandardOutput());
mStdOutList.append(StdOut);
QRegExp RxRenderProgress;
RxRenderProgress.setCaseSensitivity(Qt::CaseInsensitive);
bool BlenderVersion3 = lcGetProfileString(LC_PROFILE_BLENDER_VERSION).startsWith("v3");
if (BlenderVersion3)
RxRenderProgress.setPattern("Sample (\\d+)\\/(\\d+)");
else
RxRenderProgress.setPattern("(\\d+)\\/(\\d+) Tiles");
if (StdOut.contains(RxRenderProgress))
{
mBlendProgValue = RxRenderProgress.cap(1).toInt();
mBlendProgMax = RxRenderProgress.cap(2).toInt();
ui->RenderProgress->setMaximum(mBlendProgMax);
ui->RenderProgress->setValue(mBlendProgValue);
}
2017-09-22 19:08:02 +02:00
}
2023-06-19 04:06:44 +02:00
QString lcRenderDialog::ReadStdErr(bool& HasError) const
{
2023-06-02 00:02:53 +02:00
HasError = mCommand == BLENDER_RENDER ? false : true;
QFile File;
QStringList returnLines;
2023-06-19 04:06:44 +02:00
File.setFileName(GetStdErrFileName());
2023-06-02 00:02:53 +02:00
if (! File.open(QFile::ReadOnly | QFile::Text))
{
const QString message = tr("Failed to open log file: %1:\n%2").arg(File.fileName()).arg(File.errorString());
2023-06-02 00:02:53 +02:00
return message;
}
QTextStream In(&File);
while (! In.atEnd())
{
QString Line = In.readLine(0);
returnLines << Line.trimmed() + "<br>";
if (mCommand == POVRAY_RENDER)
{
if (Line.contains(QRegExp("^POV-Ray finished$", Qt::CaseSensitive)))
HasError = false;
}
else if (mCommand == BLENDER_RENDER)
{
if (!HasError && !Line.isEmpty())
HasError = true;
}
}
return returnLines.join(" ");
}
void lcRenderDialog::WriteStdOut()
{
2023-06-19 04:06:44 +02:00
QFile File(GetStdOutFileName());
2023-06-02 00:02:53 +02:00
if (File.open(QFile::WriteOnly | QIODevice::Truncate | QFile::Text))
{
QTextStream Out(&File);
for (const QString& Line : mStdOutList)
Out << Line;
File.close();
ui->RenderOutputButton->setEnabled(true);
}
}
2017-09-22 19:08:02 +02:00
void lcRenderDialog::Update()
{
2017-12-07 07:08:56 +01:00
#ifndef QT_NO_PROCESS
if (!mProcess)
return;
if (mProcess->state() == QProcess::NotRunning)
2017-09-22 19:08:02 +02:00
{
2017-11-03 01:35:12 +01:00
#ifdef Q_OS_LINUX
QByteArray Output = mProcess->readAllStandardOutput();
mImage = QImage::fromData(Output);
2017-11-03 01:35:12 +01:00
#endif
2019-09-08 21:19:00 +02:00
ShowResult();
CloseProcess();
2017-09-22 19:08:02 +02:00
}
2017-12-07 07:08:56 +01:00
#endif
2017-11-03 01:35:12 +01:00
2023-06-02 00:02:53 +02:00
if (mCommand == POVRAY_RENDER)
{
2023-06-02 00:02:53 +02:00
#if LC_POVRAY_MEMORY_MAPPED_FILE
if (!mOutputBuffer)
{
2023-06-19 04:06:44 +02:00
mOutputFile.setFileName(GetStdOutFileName());
2017-09-22 19:08:02 +02:00
2023-06-02 00:02:53 +02:00
if (!mOutputFile.open(QFile::ReadWrite))
return;
2017-09-22 19:08:02 +02:00
2023-06-02 00:02:53 +02:00
mOutputBuffer = mOutputFile.map(0, mOutputFile.size());
2023-06-02 00:02:53 +02:00
if (!mOutputBuffer)
{
mOutputFile.close();
return;
}
}
2017-09-22 19:08:02 +02:00
2023-06-02 00:02:53 +02:00
struct lcSharedMemoryHeader
{
quint32 Version;
quint32 Width;
quint32 Height;
quint32 PixelsWritten;
quint32 PixelsRead;
};
2017-09-22 19:08:02 +02:00
2023-06-02 00:02:53 +02:00
lcSharedMemoryHeader* Header = (lcSharedMemoryHeader*)mOutputBuffer;
2017-09-22 19:08:02 +02:00
2023-06-02 00:02:53 +02:00
if (Header->PixelsWritten == Header->PixelsRead)
return;
2023-06-02 00:02:53 +02:00
int Width = Header->Width;
int Height = Header->Height;
int PixelsWritten = Header->PixelsWritten;
2017-09-22 19:08:02 +02:00
2023-06-02 00:02:53 +02:00
if (!Header->PixelsRead)
mImage = QImage(Width, Height, QImage::Format_ARGB32);
2017-09-22 19:08:02 +02:00
2023-06-02 00:02:53 +02:00
quint8* Pixels = (quint8*)(Header + 1);
for (int y = 0; y < Height; y++)
2017-09-22 19:08:02 +02:00
{
2023-06-02 00:02:53 +02:00
for (int x = 0; x < Width; x++)
{
mImage.setPixel(x, y, qRgba(Pixels[0], Pixels[1], Pixels[2], Pixels[3]));
Pixels += 4;
}
2017-09-22 19:08:02 +02:00
}
2023-06-02 00:02:53 +02:00
Header->PixelsRead = PixelsWritten;
ui->RenderProgress->setMaximum(mImage.width() * mImage.height());
ui->RenderProgress->setValue(int(Header->PixelsRead));
2017-09-22 19:08:02 +02:00
2023-06-02 00:02:53 +02:00
if (PixelsWritten == Width * Height)
ui->RenderProgress->setValue(ui->RenderProgress->maximum());
2023-06-02 00:02:53 +02:00
ui->preview->SetImage(mImage.scaled(mPreviewWidth, mPreviewHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation));
#endif
2023-06-02 00:02:53 +02:00
}
}
2017-12-27 22:55:37 +01:00
2019-03-10 01:38:54 +01:00
void lcRenderDialog::ShowResult()
{
2023-06-02 00:02:53 +02:00
#ifndef QT_NO_PROCESS
bool Error;
const QString StdErrLog = ReadStdErr(Error);
2023-06-02 00:02:53 +02:00
const QString RenderLabel = mCommand == BLENDER_RENDER ? tr("Blender Render") : tr("POV-Ray Render");
if (mProcess->exitStatus() != QProcess::NormalExit || mProcess->exitCode() != 0 || Error)
2019-03-10 01:38:54 +01:00
{
2023-06-02 00:02:53 +02:00
ui->renderLabel->setText(tr("Image generation failed."));
ui->RenderProgress->setRange(0,1);
ui->RenderProgress->setValue(0);
const QString Title = mCommand == BLENDER_RENDER ? tr("Blender Render") : tr("POV-Ray Render");
const QString Body = tr ("An error occurred while rendering. See Show Details...");
2023-06-02 00:02:53 +02:00
lcBlenderPreferences::ShowMessage(Body, Title, QString(), StdErrLog, 0, QMessageBox::Warning);
return;
}
2023-06-02 00:02:53 +02:00
else
{
ui->RenderProgress->setValue(ui->RenderProgress->maximum());
}
#endif
2017-12-27 22:55:37 +01:00
2023-06-02 00:02:53 +02:00
bool Success = false;
2017-12-27 22:55:37 +01:00
QString FileName = ui->OutputEdit->text();
2023-06-02 00:02:53 +02:00
if (mCommand == POVRAY_RENDER)
{
2023-06-02 00:02:53 +02:00
gMainWindow->mActions[LC_FILE_RENDER_POVRAY]->setEnabled(true);
2023-06-02 00:02:53 +02:00
ui->preview->SetImage(mImage.scaled(mPreviewWidth, mPreviewHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation));
2023-06-02 00:02:53 +02:00
if (!FileName.isEmpty())
{
QImageWriter Writer(FileName);
2023-06-02 00:02:53 +02:00
Success = Writer.write(mImage);
2023-06-02 00:02:53 +02:00
if (!Success)
QMessageBox::warning(this, tr("Error"), tr("Error writing to file '%1':\n%2").arg(FileName, Writer.errorString()));
}
2023-06-02 00:02:53 +02:00
}
else if (mCommand == BLENDER_RENDER)
{
2023-06-02 00:02:53 +02:00
gMainWindow->mActions[LC_FILE_RENDER_BLENDER]->setEnabled(true);
Success = QFileInfo(FileName).exists();
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));
}
}
2023-06-02 00:02:53 +02:00
if (!Success)
{
2023-06-02 00:02:53 +02:00
ui->renderLabel->setStyleSheet("QLabel { color : red; }");
ui->renderLabel->setText(tr("Image render failed."));
const QString Message = QString("%1 %2. %3").arg(RenderLabel).arg(tr("failed (unknown reason)")).arg(ElapsedTime(mRenderTime.elapsed()));
2023-06-02 00:02:53 +02:00
QMessageBox::warning(this, tr("Error"), Message);
}
2023-06-02 00:02:53 +02:00
WriteStdOut();
2017-09-22 19:08:02 +02:00
}
2017-12-27 22:55:37 +01:00
void lcRenderDialog::on_OutputBrowseButton_clicked()
{
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 (*.*)"));
2017-12-27 22:55:37 +01:00
if (!Result.isEmpty())
ui->OutputEdit->setText(QDir::toNativeSeparators(Result));
2023-06-02 00:02:53 +02:00
}
2017-12-27 22:55:37 +01:00
2023-06-02 00:02:53 +02:00
void lcRenderDialog::on_RenderOutputButton_clicked()
{
QFileInfo FileInfo(GetStdErrFileName());
QString Message = tr("POV-Ray standard error file not found: %1.").arg(FileInfo.absoluteFilePath());
if (mCommand == BLENDER_RENDER)
{
FileInfo.setFile(GetStdOutFileName());
Message = tr("Blender standard output file not found: %1.").arg(FileInfo.absoluteFilePath());
}
2023-06-02 00:02:53 +02:00
if (!FileInfo.exists())
{
QMessageBox::warning(this, tr("Error"), Message);
2023-06-02 00:02:53 +02:00
return;
}
QDesktopServices::openUrl(QUrl("file:///"+FileInfo.absoluteFilePath(), QUrl::TolerantMode));
}
2023-06-19 04:06:44 +02:00
lcRenderProcess::~lcRenderProcess()
2023-06-02 00:02:53 +02:00
{
if(state() == QProcess::Running || state() == QProcess::Starting)
{
terminate();
waitForFinished();
}
2017-12-27 22:55:37 +01:00
}