From 44de1f19fc6a3372179c6d5d89bfefb56b42ee20 Mon Sep 17 00:00:00 2001 From: Leonardo Zide Date: Sun, 3 Aug 2025 11:47:51 -0700 Subject: [PATCH] Reorganized the Render Dialog. Fixes #875. --- common/lc_application.cpp | 4 +- common/lc_blenderpreferences.cpp | 83 +-- common/lc_blenderpreferences.h | 6 +- common/lc_global.h | 8 + common/lc_mainwindow.cpp | 38 +- common/lc_mainwindow.h | 7 +- common/lc_previewwidget.cpp | 6 +- common/lc_profile.cpp | 2 + common/lc_profile.h | 1 + common/project.cpp | 46 +- common/project.h | 8 +- qt/lc_renderdialog.cpp | 941 ++++++++++++++++--------------- qt/lc_renderdialog.h | 55 +- qt/lc_renderdialog.ui | 136 +---- 14 files changed, 657 insertions(+), 684 deletions(-) diff --git a/common/lc_application.cpp b/common/lc_application.cpp index 3f4f4846..4f6b790d 100644 --- a/common/lc_application.cpp +++ b/common/lc_application.cpp @@ -294,7 +294,7 @@ void lcApplication::SetProject(Project* Project) delete mProject; mProject = Project; - Project->SetActiveModel(0); + Project->SetActiveModel(0, true); lcGetPiecesLibrary()->RemoveTemporaryPieces(); if (mProject && !mProject->GetFileName().isEmpty() && mPreferences.mRestoreTabLayout) @@ -1022,7 +1022,7 @@ lcStartupMode lcApplication::Initialize(const QList>& Libra if (ProjectLoaded) { if (!Options.ModelName.isEmpty()) - mProject->SetActiveModel(Options.ModelName); + mProject->SetActiveModel(Options.ModelName, true); std::unique_ptr ActiveView; diff --git a/common/lc_blenderpreferences.cpp b/common/lc_blenderpreferences.cpp index a7c14365..ca21684c 100644 --- a/common/lc_blenderpreferences.cpp +++ b/common/lc_blenderpreferences.cpp @@ -1087,7 +1087,7 @@ void lcBlenderPreferences::ConfigureBlenderAddon(bool TestBlender, bool AddonUpd #else ScriptName = QLatin1String("blender_test.sh"); #endif - ScriptCommand = QString("%1 %2").arg(BlenderExe).arg(Arguments.join(" ")); + ScriptCommand = QString("\"%1\" %2").arg(BlenderExe).arg(Arguments.join(" ")); Script.setFileName(QString("%1/%2").arg(QDir::tempPath()).arg(ScriptName)); if(Script.open(QIODevice::WriteOnly | QIODevice::Text)) @@ -1133,7 +1133,7 @@ void lcBlenderPreferences::ConfigureBlenderAddon(bool TestBlender, bool AddonUpd { const QString& Title = tr ("%1 Blender LDraw Addon").arg(LC_PRODUCTNAME_STR); const QString& Header = tr ("Blender test failed."); - ShowMessage(Header, Title, Message); + ShowMessage(this, Header, Title, Message); return; } } // Test Blender @@ -1143,7 +1143,7 @@ void lcBlenderPreferences::ConfigureBlenderAddon(bool TestBlender, bool AddonUpd const QString& Title = tr ("%1 Blender LDraw Addon Modules").arg(LC_PRODUCTNAME_STR); const QString& Header = tr ("No import module enabled. If you continue, the default import module (Import TN) will be used.
If you select No, all addon modules will be disabled."); const QString& Body = tr ("Continue with the default import module ?"); - int Exec = ShowMessage(Header, Title, Body, QString(), MBB_YES_NO, QMessageBox::NoIcon); + int Exec = ShowMessage(this, Header, Title, Body, QString(), MBB_YES_NO, QMessageBox::NoIcon); if (Exec != QMessageBox::Yes) { mRenderActBox->setChecked(false); @@ -1200,7 +1200,7 @@ void lcBlenderPreferences::ConfigureBlenderAddon(bool TestBlender, bool AddonUpd if (!QFileInfo(BlenderInstallFile).exists()) { - ShowMessage(tr ("Could not find addon install file: %1").arg(BlenderInstallFile)); + ShowMessage(this, tr("Could not find addon install file: %1").arg(BlenderInstallFile)); StatusUpdate(true, true, tr("Not found.")); return; } @@ -1248,7 +1248,7 @@ void lcBlenderPreferences::ConfigureBlenderAddon(bool TestBlender, bool AddonUpd QFile File(QFileInfo(List.at(LblIdx)).absoluteFilePath()); if (!File.open(QFile::ReadOnly | QFile::Text)) { - ShowMessage(tr("Cannot read addon file %1
%2").arg(List.at(LblIdx).fileName()).arg(File.errorString())); + ShowMessage(this, tr("Cannot read addon file %1
%2").arg(List.at(LblIdx).fileName()).arg(File.errorString())); break; } else @@ -1286,7 +1286,7 @@ void lcBlenderPreferences::ConfigureBlenderAddon(bool TestBlender, bool AddonUpd StatusUpdate(true, true, tr("Install failed.")); } else - ShowMessage(tr("Blender executable not found at [%1]").arg(BlenderExe), tr("Addon install failed.")); + ShowMessage(this, tr("Blender executable not found at [%1]").arg(BlenderExe), tr("Addon install failed.")); } bool lcBlenderPreferences::ExtractBlenderAddon(const QString& BlenderDir) @@ -1312,7 +1312,7 @@ bool lcBlenderPreferences::ExtractBlenderAddon(const QString& BlenderDir) if (Result.size()) Message.append(" "+Result); - ShowMessage(Message, tr("Extract addon")); + ShowMessage(this, Message, tr("Extract addon")); } } else if (AddonAction == ADDON_NO_ACTION) @@ -1374,7 +1374,7 @@ int lcBlenderPreferences::GetBlenderAddon(const QString& BlenderDir) } else { - ShowMessage(tr("Check latest addon version failed."), tr("Latest Addon"), QString(), QString(), MBB_OK, QMessageBox::Warning); + ShowMessage(this, tr("Check latest addon version failed."), tr("Latest Addon"), QString(), QString(), MBB_OK, QMessageBox::Warning); return true; // Reload existing archive } @@ -1387,7 +1387,7 @@ int lcBlenderPreferences::GetBlenderAddon(const QString& BlenderDir) if (!ZipFile.OpenRead(BlenderAddonFile)) { - ShowMessage(tr("Cannot open addon archive file: %1.").arg(BlenderAddonFile)); + ShowMessage(this, tr("Cannot open addon archive file: %1.").arg(BlenderAddonFile)); return false; } @@ -1395,14 +1395,14 @@ int lcBlenderPreferences::GetBlenderAddon(const QString& BlenderDir) if (!ZipFile.ExtractFile(VersionFile, File)) { - ShowMessage(tr("Cannot extract addon archive version file: %1.").arg(VersionFile)); + ShowMessage(this, tr("Cannot extract addon archive version file: %1.").arg(VersionFile)); return false; } Ba = QByteArray::fromRawData((const char*)File.mBuffer, (int)File.GetLength()); if (Ba.isEmpty()) { - ShowMessage(tr("Cannot read addon archive version file: %1.").arg(VersionFile)); + ShowMessage(this, tr("Cannot read addon archive version file: %1.").arg(VersionFile)); return false; // Download new archive } } @@ -1411,7 +1411,7 @@ int lcBlenderPreferences::GetBlenderAddon(const QString& BlenderDir) QFile File(AddonVersionFile); if (!File.open(QIODevice::ReadOnly)) { - ShowMessage(tr("Cannot read addon version file: [%1]
%2.").arg(AddonVersionFile).arg(File.errorString())); + ShowMessage(this, tr("Cannot read addon version file: [%1]
%2.").arg(AddonVersionFile).arg(File.errorString())); return false; // Download new archive } Ba = File.readAll(); @@ -1458,7 +1458,7 @@ int lcBlenderPreferences::GetBlenderAddon(const QString& BlenderDir) const QString& Title = tr ("%1 Blender LDraw Addon").arg(LC_PRODUCTNAME_STR); const QString& Header = tr ("Detected %1 Blender LDraw addon %2. A newer version %3 exists.").arg(LC_PRODUCTNAME_STR).arg(LocalVersion).arg(OnlineVersion); const QString& Body = tr ("Do you want to download version %1 ?").arg(OnlineVersion); - int Exec = ShowMessage(Header, Title, Body, QString(), MBB_YES, QMessageBox::NoIcon); + int Exec = ShowMessage(this, Header, Title, Body, QString(), MBB_YES, QMessageBox::NoIcon); if (Exec == QMessageBox::Cancel) { AddonAction = ADDON_CANCELED; @@ -1494,7 +1494,7 @@ int lcBlenderPreferences::GetBlenderAddon(const QString& BlenderDir) Result &= Dir.rmdir(BlenderAddonDir); if (!Result) - ShowMessage(tr("Failed to properly remove Blender addon: %1").arg(BlenderAddonDir), tr("Remove Existing Addon"), QString(), QString(), MBB_OK, QMessageBox::Warning); + ShowMessage(this, tr("Failed to properly remove Blender addon: %1").arg(BlenderAddonDir), tr("Remove Existing Addon"), QString(), QString(), MBB_OK, QMessageBox::Warning); } }; @@ -1510,7 +1510,7 @@ int lcBlenderPreferences::GetBlenderAddon(const QString& BlenderDir) if (QFileInfo(BlenderAddonFile).exists()) { if (!QFile::rename(BlenderAddonFile, OldBlenderAddonFile)) - ShowMessage(tr("Failed to rename existing Blender addon archive %1.").arg(BlenderAddonFile)); + ShowMessage(this, tr("Failed to rename existing Blender addon archive %1.").arg(BlenderAddonFile)); } QDir Dir(BlenderDir); @@ -1556,35 +1556,35 @@ int lcBlenderPreferences::GetBlenderAddon(const QString& BlenderDir) BlenderAddonValidated = true; } else - ShowMessage(tr("Failed to validate Blender addon file name
Downloaded:%1
Received:%2").arg(ArchiveFileName, ShaReceived.last())); + ShowMessage(this, tr("Failed to validate Blender addon file name
Downloaded:%1
Received:%2").arg(ArchiveFileName, ShaReceived.last())); } else - ShowMessage(tr("Failed to validate Blender addon SHA hash
Calculated:%1
Received:%2").arg(ShaCalculated, ShaReceived.first())); + ShowMessage(this, tr("Failed to validate Blender addon SHA hash
Calculated:%1
Received:%2").arg(ShaCalculated, ShaReceived.first())); gAddonPreferences->mData.clear(); } else - ShowMessage(tr("Failed to receive SHA hash for Blender addon %1.sha256").arg(LC_BLENDER_ADDON_FILE)); + ShowMessage(this, tr("Failed to receive SHA hash for Blender addon %1.sha256").arg(LC_BLENDER_ADDON_FILE)); } else - ShowMessage(tr("Failed to read Blender addon archive:
%1:
%2").arg(BlenderAddonFile).arg(File.errorString())); + ShowMessage(this, tr("Failed to read Blender addon archive:
%1:
%2").arg(BlenderAddonFile).arg(File.errorString())); } else - ShowMessage(tr("Failed to write Blender addon archive:
%1:
%2").arg(BlenderAddonFile).arg(File.errorString())); + ShowMessage(this, tr("Failed to write Blender addon archive:
%1:
%2").arg(BlenderAddonFile).arg(File.errorString())); if (!BlenderAddonValidated) { if (QFileInfo(BlenderAddonFile).exists()) if (!QFile::remove(BlenderAddonFile)) - ShowMessage(tr("Failed to remove invalid Blender addon archive:
%1").arg(BlenderAddonFile)); + ShowMessage(this, tr("Failed to remove invalid Blender addon archive:
%1").arg(BlenderAddonFile)); if (QFileInfo(OldBlenderAddonFile).exists()) if (!QFile::rename(OldBlenderAddonFile, BlenderAddonFile)) - ShowMessage(tr("Failed to restore Blender addon archive:
%1 from %2").arg(ArchiveFileName, OldArchiveFileName)); + ShowMessage(this, tr("Failed to restore Blender addon archive:
%1 from %2").arg(ArchiveFileName, OldArchiveFileName)); AddonAction = ADDON_FAIL; } } else { - ShowMessage(tr("Failed to download Blender addon archive:
%1").arg(BlenderAddonFile)); + ShowMessage(this, tr("Failed to download Blender addon archive:
%1").arg(BlenderAddonFile)); AddonAction = ADDON_FAIL; } @@ -1673,7 +1673,7 @@ void lcBlenderPreferences::ShowResult() const QString& Title = tr ("%1 Blender Addon Install").arg(LC_PRODUCTNAME_STR); const QString& Header = "" + tr ("Addon install failed.") + ""; const QString& Body = tr ("LDraw addon install encountered one or more errors. See Show Details..."); - ShowMessage(Header, Title, Body, StdErrLog, MBB_OK, QMessageBox::Warning); + ShowMessage(this, Header, Title, Body, StdErrLog, MBB_OK, QMessageBox::Warning); } else { @@ -1709,7 +1709,7 @@ void lcBlenderPreferences::ShowResult() mProcess = nullptr; if (Error) - ShowMessage(Message); + ShowMessage(this, Message); } void lcBlenderPreferences::SettingChanged(const QString& Value) @@ -1800,7 +1800,7 @@ void lcBlenderPreferences::GetStandardOutput() QFileInfo FileInfo(LogFile); if (!FileInfo.exists()) { - ShowMessage(tr("Blender addon standard output file not found: %1.").arg(FileInfo.absoluteFilePath())); + ShowMessage(this, tr("Blender addon standard output file not found: %1.").arg(FileInfo.absoluteFilePath())); return; } @@ -1957,7 +1957,7 @@ void lcBlenderPreferences::ReadStdOut() const QString& Title = tr ("%1 Blender Addon Install").arg(LC_PRODUCTNAME_STR); const QString& Header = "" + tr ("Addon install standard output.") + ""; const QString& Body = tr ("LDraw addon install encountered %1. See Show Details...").arg(Items); - ShowMessage(Header, Title, Body, ErrorsAndWarnings.append(StdOutLog), MBB_OK, Icon); + ShowMessage(this, Header, Title, Body, ErrorsAndWarnings.append(StdOutLog), MBB_OK, Icon); } } @@ -2002,7 +2002,7 @@ void lcBlenderPreferences::WriteStdOut() mAddonStdOutButton->setEnabled(true); } else - ShowMessage(tr("Error writing to %1 file '%2':\n%3").arg("stdout").arg(File.fileName(), File.errorString())); + ShowMessage(this, tr("Error writing to %1 file '%2':\n%3").arg("stdout").arg(File.fileName(), File.errorString())); } bool lcBlenderPreferences::PromptCancel() @@ -2012,7 +2012,7 @@ bool lcBlenderPreferences::PromptCancel() { const QString& Title = tr ("Cancel %1 Addon Install").arg(LC_PRODUCTNAME_STR); const QString& Header = "" + tr("Are you sure you want to cancel the add on install ?") + ""; - int Exec = ShowMessage(Header, Title, QString(), QString(), MBB_YES_NO, QMessageBox::Question); + int Exec = ShowMessage(this, Header, Title, QString(), QString(), MBB_YES_NO, QMessageBox::Question); if (Exec == QMessageBox::Yes) { mProcess->kill(); @@ -2623,11 +2623,11 @@ void lcBlenderPreferences::LoadSettings() gAddonPreferences->ReadStdOut(QString(Ba), Errors); if (!Errors.isEmpty()) - ShowMessage(Errors); + ShowMessage(nullptr, Errors); } else { - ShowMessage(tr("Blender config file was not found. " + ShowMessage(nullptr, tr("Blender config file was not found. " "Install log check failed:
%1:
%2") .arg(File.fileName()) .arg(File.errorString())); @@ -3079,7 +3079,7 @@ void lcBlenderPreferences::SetModelSize(bool Update) const QString& Title = tr ("LDraw Render Settings Conflict"); const QString& Header = "" + tr ("Crop image configuration settings conflict were resolved.") + ""; const QString& Body = QString("%1%2%3").arg(Conflict[0] ? tr("Keep aspect ratio set to false.
") : "").arg(Conflict[1] ? tr("Add environment (backdrop and base plane) set to false.
") : "").arg(Conflict[2] ? tr("Transparent background set to true.
") : ""); - ShowMessage(Header, Title, Body, QString(), MBB_OK, QMessageBox::Information); + ShowMessage(this, Header, Title, Body, QString(), MBB_OK, QMessageBox::Information); } } @@ -3117,7 +3117,7 @@ void lcBlenderPreferences::ValidateColourScheme(int Index) const QString& Title = tr ("Custom LDraw Colours"); const QString& Header = "" + tr ("Colour scheme 'custom' cannot be enabled. Custom LDConfig file not found.") + ""; const QString& Body = tr ("Colour scheme 'custom' selected but no LDConfig file was specified.
The default colour scheme '%1' will be used.
").arg(Settings[Color_Scheme].value); - ShowMessage(Header, Title, Body, QString(), MBB_OK, QMessageBox::Warning); + ShowMessage(this, Header, Title, Body, QString(), MBB_OK, QMessageBox::Warning); } else { @@ -3131,7 +3131,7 @@ bool lcBlenderPreferences::PromptAccept() { const QString& Title = tr ("Render Settings Modified"); const QString& Header = "" + tr("Do you want to accept the modified settings before quitting ?") + ""; - int Exec = ShowMessage(Header, Title, QString(), QString(), MBB_YES_NO, QMessageBox::Question); + int Exec = ShowMessage(this, Header, Title, QString(), QString(), MBB_YES_NO, QMessageBox::Question); if (Exec == QMessageBox::Yes) return true; @@ -3503,7 +3503,7 @@ bool lcBlenderPreferences::ExportParameterFile() Message = tr("Failed to open Blender parameter file: %1:
%2") .arg(File.fileName()) .arg(File.errorString()); - ShowMessage(Message); + ShowMessage(nullptr, Message); return false; } return true; @@ -3519,17 +3519,17 @@ bool lcBlenderPreferences::OverwriteFile(const QString& File) const QString& Title = tr ("Replace Existing File"); const QString Header = "" + QMessageBox::tr ("Existing file %1 detected.").arg(fileInfo.fileName()) + ""; const QString Body = QMessageBox::tr ("\"%1\"
This file already exists.
Replace existing file?").arg(fileInfo.fileName()); - int Exec = ShowMessage(Header, Title, Body, QString(), MBB_YES, QMessageBox::NoIcon); + int Exec = ShowMessage(nullptr, Header, Title, Body, QString(), MBB_YES, QMessageBox::NoIcon); return (Exec == QMessageBox::Yes); } -int lcBlenderPreferences::ShowMessage(const QString& Header, const QString& Title, const QString& Body, const QString& Detail, int const Buttons, int const Icon) +int lcBlenderPreferences::ShowMessage(QWidget* Parent, const QString& Header, const QString& Title, const QString& Body, const QString& Detail, int const Buttons, int const Icon) { if (!gMainWindow) return QMessageBox::Ok; - QMessageBox Box; + QMessageBox Box(Parent); Box.setWindowIcon(QIcon()); if (!Icon) { @@ -3593,7 +3593,8 @@ int lcBlenderPreferences::ShowMessage(const QString& Header, const QString& Tit const bool DownloadRequest = Body.startsWith(tr("Do you want to download version ")); - if (DownloadRequest){ + if (DownloadRequest) + { QCheckBox* AddonVersionCheck = new QCheckBox(tr("Do not show download new addon version message again.")); Box.setCheckBox(AddonVersionCheck); QObject::connect(AddonVersionCheck, &QCheckBox::stateChanged, [](int State) @@ -3613,7 +3614,7 @@ void lcBlenderPreferences::DownloadFinished(lcHttpReply* Reply) if (!Reply->error()) mData = Reply->readAll(); else - ShowMessage(tr("Addon download failed.")); + ShowMessage(this, tr("Addon download failed.")); mHttpReply = nullptr; @@ -3761,7 +3762,7 @@ bool lcBlenderPreferences::ExtractAddon(const QString FileName, QString& Result) FileInfo.permissions |= QFile::ExeOwner | QFile::ExeUser | QFile::ExeGroup | QFile::ExeOther; break; default: - ShowMessage(tr("ZipFile entry format (HostOS %1) at index %2 is not supported. Extract terminated.").arg(HostOS).arg(FileIdx), tr("Extract Addon"), QString(), QString(), MBB_OK, QMessageBox::Warning); + ShowMessage(this, tr("ZipFile entry format (HostOS %1) at index %2 is not supported. Extract terminated.").arg(HostOS).arg(FileIdx), tr("Extract Addon"), QString(), QString(), MBB_OK, QMessageBox::Warning); Ok = false; } diff --git a/common/lc_blenderpreferences.h b/common/lc_blenderpreferences.h index 5a765749..150bbb2f 100644 --- a/common/lc_blenderpreferences.h +++ b/common/lc_blenderpreferences.h @@ -64,7 +64,7 @@ public: static void SaveSettings(); static bool ExportParameterFile(); static bool SettingsModified(bool Update = true, QString const& Module = QString()); - static int ShowMessage(QString const& Header, QString const& Title = QString(), QString const& Body = QString(), QString const& Detail = QString(), const int Buttons = MBB_OK, const int Icon = 3 /*QMessageBox::Critical*/); + static int ShowMessage(QWidget* Parent, QString const& Header, QString const& Title = QString(), QString const& Body = QString(), QString const& Detail = QString(), const int Buttons = MBB_OK, const int Icon = 3 /*QMessageBox::Critical*/); void Apply(const int Response = QDialog::Accepted); @@ -76,8 +76,8 @@ protected: static int NumPaths(bool DefaultSettings = false); static int NumSettings(bool DefaultSettings = false); static int NumSettingsMM(bool DefaultSettings = false); - static int GetBlenderAddon(const QString& BlenderDir); - static bool ExtractBlenderAddon(const QString& BlenderDir); + int GetBlenderAddon(const QString& BlenderDir); + bool ExtractBlenderAddon(const QString& BlenderDir); static void LoadDefaultParameters(QByteArray& Buffer, int Which); static bool OverwriteFile(const QString& File); diff --git a/common/lc_global.h b/common/lc_global.h index 04966a76..91657573 100644 --- a/common/lc_global.h +++ b/common/lc_global.h @@ -28,6 +28,10 @@ #pragma warning(default : 5038) // data member 'A::y' will be initialized after data member 'A::x' #endif +#ifdef QT_NO_PRINTER +class QPrinter; +#endif + #ifndef Q_FALLTHROUGH #define Q_FALLTHROUGH(); #endif @@ -37,6 +41,10 @@ #define QT_STRINGIFY(x) QT_STRINGIFY2(x) #endif +#ifdef QT_NO_PROCESS +#define LC_DISABLE_RENDER_DIALOG +#endif + #define LC_ARRAY_COUNT(x) ((sizeof(x)/sizeof(0[x])) / ((size_t)(!(sizeof(x) % sizeof(0[x]))))) #define LC_ARRAY_SIZE_CHECK(a,s) static_assert(LC_ARRAY_COUNT(a) == static_cast(s), QT_STRINGIFY(a) " size mismatch.") diff --git a/common/lc_mainwindow.cpp b/common/lc_mainwindow.cpp index a5867687..0e650e56 100644 --- a/common/lc_mainwindow.cpp +++ b/common/lc_mainwindow.cpp @@ -474,10 +474,12 @@ void lcMainWindow::CreateMenus() ExportMenu->addAction(mActions[LC_FILE_EXPORT_POVRAY]); ExportMenu->addAction(mActions[LC_FILE_EXPORT_WAVEFRONT]); FileMenu->addSeparator(); +#ifndef LC_DISABLE_RENDER_DIALOG QMenu* RenderMenu = FileMenu->addMenu(tr("&Render")); RenderMenu->addAction(mActions[LC_FILE_RENDER_POVRAY]); RenderMenu->addAction(mActions[LC_FILE_RENDER_BLENDER]); RenderMenu->addAction(mActions[LC_FILE_RENDER_OPEN_IN_BLENDER]); +#endif FileMenu->addAction(mActions[LC_FILE_INSTRUCTIONS]); FileMenu->addAction(mActions[LC_FILE_PRINT]); FileMenu->addAction(mActions[LC_FILE_PRINT_PREVIEW]); @@ -1074,7 +1076,7 @@ void lcMainWindow::ModelTabChanged(int Index) Project* Project = lcGetActiveProject(); lcModelTabWidget* CurrentTab = (lcModelTabWidget*)mModelTabWidget->widget(Index); - Project->SetActiveModel(CurrentTab ? CurrentTab->GetModel() : nullptr); + Project->SetActiveModel(CurrentTab ? CurrentTab->GetModel() : nullptr, true); } void lcMainWindow::ClipboardChanged() @@ -1317,10 +1319,20 @@ void lcMainWindow::ShowHTMLDialog() lcGetActiveProject()->ExportHTML(Options); } -void lcMainWindow::ShowRenderDialog(int Command) +void lcMainWindow::ShowExportPOVRayDialog() { - lcRenderDialog Dialog(this, Command); + auto [Success, ErrorMessage] = lcGetActiveProject()->ExportPOVRay(QString()); + + if (!Success && !ErrorMessage.isEmpty()) + QMessageBox::warning(this, tr("POV-Ray Export" ), ErrorMessage); +} + +void lcMainWindow::ShowRenderDialog(lcRenderDialogMode RenderDialogMode) +{ +#ifndef LC_DISABLE_RENDER_DIALOG + lcRenderDialog Dialog(this, RenderDialogMode); Dialog.exec(); +#endif } void lcMainWindow::ShowInstructionsDialog() @@ -1586,9 +1598,9 @@ void lcMainWindow::RestoreTabLayout(const QByteArray& TabLayout) } if (!mModelTabWidget->count()) - lcGetActiveProject()->SetActiveModel(0); + lcGetActiveProject()->SetActiveModel(0, true); else - lcGetActiveProject()->SetActiveModel(CurrentTabName); + lcGetActiveProject()->SetActiveModel(CurrentTabName, true); } void lcMainWindow::RemoveAllModelTabs() @@ -2552,7 +2564,7 @@ bool lcMainWindow::SetModelFromFocus() if (Model) { Project* Project = lcGetActiveProject(); - Project->SetActiveModel(Model); + Project->SetActiveModel(Model, true); return true; } @@ -2569,7 +2581,7 @@ void lcMainWindow::SetModelFromSelection() if (Model) { Project* Project = lcGetActiveProject(); - Project->SetActiveModel(Model); + Project->SetActiveModel(Model, true); } } @@ -2656,7 +2668,7 @@ void lcMainWindow::HandleCommand(lcCommandId CommandId) break; case LC_FILE_EXPORT_POVRAY: - lcGetActiveProject()->ExportPOVRay(QString()); + ShowExportPOVRayDialog(); break; case LC_FILE_EXPORT_WAVEFRONT: @@ -2664,9 +2676,15 @@ void lcMainWindow::HandleCommand(lcCommandId CommandId) break; case LC_FILE_RENDER_POVRAY: + ShowRenderDialog(lcRenderDialogMode::RenderPOVRay); + break; + case LC_FILE_RENDER_BLENDER: + ShowRenderDialog(lcRenderDialogMode::RenderBlender); + break; + case LC_FILE_RENDER_OPEN_IN_BLENDER: - ShowRenderDialog(CommandId - LC_FILE_RENDER_POVRAY); + ShowRenderDialog(lcRenderDialogMode::OpenInBlender); break; case LC_FILE_INSTRUCTIONS: @@ -3298,7 +3316,7 @@ void lcMainWindow::HandleCommand(lcCommandId CommandId) case LC_MODEL_38: case LC_MODEL_39: case LC_MODEL_40: - lcGetActiveProject()->SetActiveModel(CommandId - LC_MODEL_01); + lcGetActiveProject()->SetActiveModel(CommandId - LC_MODEL_01, true); break; case LC_HELP_HOMEPAGE: diff --git a/common/lc_mainwindow.h b/common/lc_mainwindow.h index 5de0918c..baf8f477 100644 --- a/common/lc_mainwindow.h +++ b/common/lc_mainwindow.h @@ -13,9 +13,7 @@ class lcColorList; class lcPropertiesWidget; class lcTimelineWidget; class lcElidedLabel; -#ifdef QT_NO_PRINTER -class QPrinter; -#endif +enum class lcRenderDialogMode; #define LC_MAX_RECENT_FILES 4 @@ -326,7 +324,8 @@ protected: void ShowUpdatesDialog(); void ShowAboutDialog(); void ShowHTMLDialog(); - void ShowRenderDialog(int Command); + void ShowExportPOVRayDialog(); + void ShowRenderDialog(lcRenderDialogMode RenderDialogMode); void ShowInstructionsDialog(); void ShowPrintDialog(); void CreatePreviewWidget(); diff --git a/common/lc_previewwidget.cpp b/common/lc_previewwidget.cpp index f46219b3..4be36079 100644 --- a/common/lc_previewwidget.cpp +++ b/common/lc_previewwidget.cpp @@ -86,7 +86,7 @@ void lcPreviewDockWidget::SetPreviewLock() lcPreview::lcPreview() : lcView(lcViewType::Preview, nullptr), mLoader(new Project(true)) { - mLoader->SetActiveModel(0); + mLoader->SetActiveModel(0, false); mModel = mLoader->GetActiveModel(); } @@ -127,7 +127,7 @@ bool lcPreview::SetCurrentPiece(const QString& PartType, int ColorCode) if (!mLoader->Load(ModelPath, false)) return false; - mLoader->SetActiveModel(0); + mLoader->SetActiveModel(0, false); lcGetPiecesLibrary()->RemoveTemporaryPieces(); mModel = mLoader->GetActiveModel(); if (!mModel->GetProperties().mDescription.isEmpty()) @@ -145,7 +145,7 @@ bool lcPreview::SetCurrentPiece(const QString& PartType, int ColorCode) void lcPreview::ClearPreview() { mLoader = std::unique_ptr(new Project(true/*IsPreview*/)); - mLoader->SetActiveModel(0); + mLoader->SetActiveModel(0, false); mModel = mLoader->GetActiveModel(); lcGetPiecesLibrary()->UnloadUnusedParts(); Redraw(); diff --git a/common/lc_profile.cpp b/common/lc_profile.cpp index 0cd93a7a..1554df5e 100644 --- a/common/lc_profile.cpp +++ b/common/lc_profile.cpp @@ -5,6 +5,7 @@ #include "lc_model.h" #include "project.h" #include "lc_viewsphere.h" +#include "lc_renderdialog.h" lcProfileEntry::lcProfileEntry(const char* Section, const char* Key, int DefaultValue) { @@ -147,6 +148,7 @@ static lcProfileEntry gProfileEntries[LC_NUM_PROFILE_KEYS] = lcProfileEntry("Render", "Width", 1280), // LC_PROFILE_RENDER_WIDTH lcProfileEntry("Render", "Height", 720), // LC_PROFILE_RENDER_HEIGHT + lcProfileEntry("Render", "Height", (int)lcPOVRayRenderQuality::High), // LC_PROFILE_RENDER_QUALITY lcProfileEntry("Blender", "Path", ""), // LC_PROFILE_BLENDER_PATH lcProfileEntry("Blender", "LDrawConfigPath", ""), // LC_PROFILE_BLENDER_LDRAW_CONFIG_PATH diff --git a/common/lc_profile.h b/common/lc_profile.h index 135c79f2..ee6af744 100644 --- a/common/lc_profile.h +++ b/common/lc_profile.h @@ -94,6 +94,7 @@ enum LC_PROFILE_KEY LC_PROFILE_POVRAY_LGEO_PATH, LC_PROFILE_RENDER_WIDTH, LC_PROFILE_RENDER_HEIGHT, + LC_PROFILE_RENDER_QUALITY, LC_PROFILE_BLENDER_PATH, LC_PROFILE_BLENDER_LDRAW_CONFIG_PATH, LC_PROFILE_BLENDER_VERSION, diff --git a/common/project.cpp b/common/project.cpp index 383408c8..59a27734 100644 --- a/common/project.cpp +++ b/common/project.cpp @@ -134,7 +134,7 @@ QString Project::GetImageFileName(bool AllowCurrentFolder) const return QDir::toNativeSeparators(FileName) + lcGetProfileString(LC_PROFILE_IMAGE_EXTENSION); } -void Project::SetActiveModel(lcModel* ActiveModel) +void Project::SetActiveModel(lcModel* ActiveModel, bool UpdateInterface) { if (!ActiveModel) return; @@ -150,28 +150,28 @@ void Project::SetActiveModel(lcModel* ActiveModel) mActiveModel = ActiveModel; - if (!mIsPreview && gMainWindow) + if (!mIsPreview && gMainWindow && UpdateInterface) { gMainWindow->SetCurrentModelTab(mActiveModel); mActiveModel->UpdateInterface(); } } -void Project::SetActiveModel(int ModelIndex) +void Project::SetActiveModel(int ModelIndex, bool UpdateInterface) { if (ModelIndex < 0 || ModelIndex >= static_cast(mModels.size())) return; - SetActiveModel(mModels[ModelIndex].get()); + SetActiveModel(mModels[ModelIndex].get(), UpdateInterface); } -void Project::SetActiveModel(const QString& FileName) +void Project::SetActiveModel(const QString& FileName, bool UpdateInterface) { for (const std::unique_ptr& Model : mModels) { if (FileName.compare(Model->GetFileName(), Qt::CaseInsensitive) == 0) { - SetActiveModel(Model.get()); + SetActiveModel(Model.get(), UpdateInterface); return; } } @@ -264,7 +264,7 @@ lcModel* Project::CreateNewModel(bool ShowModel) if (ShowModel) { - SetActiveModel(mModels.back().get()); + SetActiveModel(mModels.back().get(), true); lcView* ActiveView = gMainWindow ? gMainWindow->GetActiveView() : nullptr; if (ActiveView) @@ -273,7 +273,7 @@ lcModel* Project::CreateNewModel(bool ShowModel) gMainWindow->UpdateTitle(); } else - SetActiveModel(mActiveModel); + SetActiveModel(mActiveModel, true); return Model; } @@ -352,7 +352,7 @@ void Project::ShowModelListDialog() int ModelIndex = Dialog.GetActiveModelIndex(); if (ModelIndex != -1) - SetActiveModel(ModelIndex); + SetActiveModel(ModelIndex, true); } void Project::SetFileName(const QString& FileName) @@ -643,7 +643,7 @@ std::vector Project::GetModelParts() mModels[0]->GetModelParts(lcMatrix44Identity(), gDefaultColor, ModelParts); - SetActiveModel(mActiveModel); + SetActiveModel(mActiveModel, false); return ModelParts; } @@ -1803,28 +1803,22 @@ void Project::ExportHTML(const lcHTMLExportOptions& Options) } } -bool Project::ExportPOVRay(const QString& FileName) +std::pair Project::ExportPOVRay(const QString& FileName) { std::vector ModelParts = GetModelParts(); if (ModelParts.empty()) - { - QMessageBox::information(gMainWindow, tr("LeoCAD"), tr("Nothing to export.")); - return false; - } + return { false, tr("Nothing to export.") }; QString SaveFileName = GetExportFileName(FileName, QLatin1String("pov"), tr("Export POV-Ray"), tr("POV-Ray Files (*.pov);;All Files (*.*)")); if (SaveFileName.isEmpty()) - return false; + return { false, QString() }; lcDiskFile POVFile(SaveFileName); if (!POVFile.Open(QIODevice::WriteOnly)) - { - QMessageBox::warning(gMainWindow, tr("LeoCAD"), tr("Could not open file '%1' for writing.").arg(SaveFileName)); - return false; - } + return { false, tr("Could not open file '%1' for writing.").arg(SaveFileName) }; enum { @@ -2226,10 +2220,7 @@ bool Project::ExportPOVRay(const QString& FileName) lcDiskFile TableFile(QFileInfo(QDir(LGEOPath), QLatin1String("lg_elements.lst")).absoluteFilePath()); if (!TableFile.Open(QIODevice::ReadOnly)) - { - QMessageBox::information(gMainWindow, tr("LeoCAD"), tr("Could not find LGEO files in folder '%1'.").arg(LGEOPath)); - return false; - } + return { false, tr("Could not find LGEO files in folder '%1'.").arg(LGEOPath) }; while (TableFile.ReadLine(Line, sizeof(Line))) { @@ -2275,10 +2266,7 @@ bool Project::ExportPOVRay(const QString& FileName) lcDiskFile LgeoColorFile(QFileInfo(QDir(LGEOPath), QLatin1String("lg_colors.lst")).absoluteFilePath()); if (!LgeoColorFile.Open(QIODevice::ReadOnly)) - { - QMessageBox::information(gMainWindow, tr("LeoCAD"), tr("Could not find LGEO files in folder '%1'.").arg(LGEOPath)); - return false; - } + return { false, tr("Could not find LGEO files in folder '%1'.").arg(LGEOPath) }; while (LgeoColorFile.ReadLine(Line, sizeof(Line))) { @@ -2510,7 +2498,7 @@ bool Project::ExportPOVRay(const QString& FileName) POVFile.WriteLine(Line); } - return true; + return { true, QString() }; } bool Project::ExportWavefront(const QString& FileName) diff --git a/common/project.h b/common/project.h index 2ee081eb..e7022d59 100644 --- a/common/project.h +++ b/common/project.h @@ -68,9 +68,9 @@ public: lcInstructions* GetInstructions(); - void SetActiveModel(lcModel* Model); - void SetActiveModel(int ModelIndex); - void SetActiveModel(const QString& FileName); + void SetActiveModel(lcModel* Model, bool UpdateInterface); + void SetActiveModel(int ModelIndex, bool UpdateInterface); + void SetActiveModel(const QString& FileName, bool UpdateInterface); lcModel* CreateNewModel(bool ShowModel); QString GetNewModelName(QWidget* ParentWidget, const QString& DialogTitle, const QString& CurrentName, const QStringList& ExistingModels) const; @@ -91,7 +91,7 @@ public: bool ExportCOLLADA(const QString& FileName); bool ExportCSV(const QString& FileName); void ExportHTML(const lcHTMLExportOptions& Options); - bool ExportPOVRay(const QString& FileName); + std::pair ExportPOVRay(const QString& FileName); bool ExportWavefront(const QString& FileName); void UpdatePieceInfo(PieceInfo* Info) const; diff --git a/qt/lc_renderdialog.cpp b/qt/lc_renderdialog.cpp index bb49a7ab..3db1c4c5 100644 --- a/qt/lc_renderdialog.cpp +++ b/qt/lc_renderdialog.cpp @@ -8,29 +8,14 @@ #include "lc_mainwindow.h" #include "lc_model.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 -static inline QString ElapsedTime(const qint64& Duration) -{ - 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(); @@ -54,54 +39,45 @@ void lcRenderPreviewWidget::paintEvent(QPaintEvent* PaintEvent) Painter.drawImage((Size.width() - mScaledImage.width()) / 2, (Size.height() - mScaledImage.height()) / 2, mScaledImage); } else - Painter.fillRect(rect(), Qt::white); + Painter.fillRect(rect(), Qt::transparent); } -lcRenderDialog::lcRenderDialog(QWidget* Parent, int Command) - : QDialog(Parent), - mCommand(Command), - ui(new Ui::lcRenderDialog) +lcRenderDialog::lcRenderDialog(QWidget* Parent, lcRenderDialogMode RenderDialogMode) + : QDialog(Parent), mDialogMode(RenderDialogMode), ui(new Ui::lcRenderDialog) { -#ifndef QT_NO_PROCESS - mProcess = nullptr; -#endif - mOutputBuffer = nullptr; - 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->WidthEdit->setText(QString::number(mWidth)); - ui->WidthEdit->setValidator(new QIntValidator(16, INT_MAX)); - ui->HeightEdit->setText(QString::number(mHeight)); - ui->HeightEdit->setValidator(new QIntValidator(16, INT_MAX)); ui->OutputEdit->setText(lcGetActiveProject()->GetImageFileName(false)); - lcModel* Model = lcGetActiveProject()->GetActiveModel(); - mLabelMessage = tr("Render image:"); - if (Model) - mLabelMessage = tr("Render STEP %1 image:").arg(Model->GetCurrentStep()); + mRenderButton = new QPushButton(tr("Render"), ui->buttonBox); + ui->buttonBox->addButton(mRenderButton, QDialogButtonBox::ActionRole); - ui->renderLabel->setText(mLabelMessage); - ui->RenderOutputButton->setEnabled(false); + mSettingsButton = new QPushButton(tr("Settings..."), ui->buttonBox); + ui->buttonBox->addButton(mSettingsButton, QDialogButtonBox::ResetRole); - if (mCommand == POVRAY_RENDER) + 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); + + if (mDialogMode == lcRenderDialogMode::RenderPOVRay) { 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(); + mRenderButton->setToolTip(tr("Render Model")); } else { - setWindowTitle(tr("Blender %1").arg(mCommand == OPEN_IN_BLENDER ? tr("LDraw Import") : tr("Image Render"))); + setWindowTitle(mDialogMode == lcRenderDialogMode::OpenInBlender ? tr("Blender LDraw Import") : tr("Blender Image Render")); bool BlenderConfigured = !lcGetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE).isEmpty(); @@ -112,38 +88,25 @@ lcRenderDialog::lcRenderDialog(QWidget* Parent, int Command) 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"); + mRenderButton->setToolTip(BlenderConfigured ? tr("Render Model") : tr("Blender not configured. Use Settings to configure.")); + mRenderButton->setEnabled(BlenderConfigured); - ui->RenderButton->setToolTip(BlenderConfigured ? tr("Render LDraw Model") : tr("Blender not configured. Use Settings... to configure.")); + mSettingsButton->setToolTip(tr("Blender render settings")); - ui->RenderButton->setEnabled(BlenderConfigured); - - ui->RenderSettingsButton->setToolTip(tr("Blender render settings")); - - ui->qualityLabel->hide(); - ui->QualityComboBox->hide(); - - if (mCommand == OPEN_IN_BLENDER) + if (mDialogMode == lcRenderDialogMode::OpenInBlender) { - mLabelMessage = tr("Open%1 in Blender using %2:") .arg(Model ? tr(" STEP %1").arg(Model->GetCurrentStep()) : ""); + mSettingsButton->setToolTip(tr("Blender import settings")); - ui->RenderSettingsButton->setToolTip(tr("Blender import settings")); - - ui->RenderButton->setText(tr("Open in Blender")); - ui->RenderButton->setFixedWidth(ui->RenderButton->sizeHint().width() + 20); + mRenderButton->setText(tr("Open in Blender")); if (BlenderConfigured) - ui->RenderButton->setToolTip(tr("Import and open LDraw model in Blender")); - - ui->renderLabel->setText(mLabelMessage.arg(mImportModule)); - ui->renderLabel->setAlignment(Qt::AlignTrailing | Qt::AlignVCenter); + mRenderButton->setToolTip(tr("Import and open LDraw model in Blender")); ui->outputLabel->hide(); ui->OutputEdit->hide(); ui->RenderProgress->hide(); ui->OutputBrowseButton->hide(); - ui->RenderOutputButton->hide(); + mLogButton->hide(); adjustSize(); setMinimumWidth(ui->preview->geometry().width()); @@ -155,15 +118,11 @@ lcRenderDialog::lcRenderDialog(QWidget* Parent, int Command) Image = Image.convertToFormat(QImage::Format_ARGB32_Premultiplied); ui->preview->SetImage(Image); } - - connect(&mUpdateTimer, SIGNAL(timeout()), this, SLOT(UpdateElapsedTime())); } - connect(&mUpdateTimer, SIGNAL(timeout()), this, SLOT(Update())); + connect(&mUpdateTimer, &QTimer::timeout, this, &lcRenderDialog::Update); mUpdateTimer.start(500); - - setSizeGripEnabled(true); } lcRenderDialog::~lcRenderDialog() @@ -173,14 +132,14 @@ lcRenderDialog::~lcRenderDialog() QString lcRenderDialog::GetStdOutFileName() const { - QString LogFile = mCommand == POVRAY_RENDER ? QLatin1String("leocad-povray-render.out") : QLatin1String("leocad-blender-render.out"); + 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 = mCommand == POVRAY_RENDER ? QLatin1String("leocad-povray-render.err") : QLatin1String("leocad-blender-render.err"); + QString LogFile = mDialogMode == lcRenderDialogMode::RenderPOVRay ? QLatin1String("leocad-povray-render.err") : QLatin1String("leocad-blender-render.err"); return QDir(QDir::tempPath()).absoluteFilePath(LogFile); } @@ -190,23 +149,12 @@ QString lcRenderDialog::GetPOVFileName() const return QDir(QDir::tempPath()).absoluteFilePath("leocad-render.pov"); } -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()))); - } -} - void lcRenderDialog::CloseProcess() { -#ifndef QT_NO_PROCESS delete mProcess; mProcess = nullptr; -#endif - if (mCommand == POVRAY_RENDER) + if (mDialogMode == lcRenderDialogMode::RenderPOVRay) { #if LC_POVRAY_MEMORY_MAPPED_FILE mOutputFile.unmap((uchar*)mOutputBuffer); @@ -219,399 +167,407 @@ void lcRenderDialog::CloseProcess() QFile::remove(GetPOVFileName()); } - if (mCommand != OPEN_IN_BLENDER) - ui->RenderButton->setText(tr("Render")); + if (mDialogMode != lcRenderDialogMode::OpenInBlender) + mRenderButton->setText(tr("Render")); } bool lcRenderDialog::PromptCancel() { -#ifndef QT_NO_PROCESS + 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 (mDialogMode == lcRenderDialogMode::RenderPOVRay) + gMainWindow->mActions[LC_FILE_RENDER_POVRAY]->setEnabled(true); + else if (mDialogMode == lcRenderDialogMode::RenderBlender) + gMainWindow->mActions[LC_FILE_RENDER_BLENDER]->setEnabled(true); + if (mProcess) { - if (QMessageBox::question(this, tr("Cancel Render"), tr("Are you sure you want to cancel the current render?"), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) - { - 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 - lcTerminateChildProcess(this, mProcess->processId(), QCoreApplication::applicationPid()); + lcTerminateChildProcess(this, mProcess->processId(), QCoreApplication::applicationPid()); #endif - mProcess->kill(); - CloseProcess(); - if (mStdOutList.size()) - { - WriteStdOut(); - ui->RenderOutputButton->setEnabled(true); - } - if (mCommand == BLENDER_RENDER) - ui->renderLabel->setText(tr("Tiles: %1/%2, Render Cancelled.") .arg(mBlendProgValue) .arg(mBlendProgMax)); - } - else - return false; + mProcess->kill(); + } + + CloseProcess(); + + if (mStdOutList.size()) + { + WriteStdOut(); + mLogButton->setEnabled(true); } -#endif 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::on_RenderSettingsButton_clicked() +void lcRenderDialog::RenderPOVRay() { - if (mCommand == POVRAY_RENDER) - return; + gMainWindow->mActions[LC_FILE_RENDER_POVRAY]->setEnabled(false); - lcBlenderPreferencesDialog::GetBlenderPreferences(mWidth, mHeight, mScale, this); + QString FileName = GetPOVFileName(); - if (lcGetProfileString(LC_PROFILE_BLENDER_IMPORT_MODULE).isEmpty()) - ui->RenderButton->setToolTip(tr("Blender not configured. Use Settings... to configure.")); - else + ui->preview->SetImage(QImage()); + ui->RenderProgress->setValue(ui->RenderProgress->minimum()); + ui->RenderProgress->setFormat(tr("Exporting Model")); + + QFuture> exportThread = QtConcurrent::run([FileName]() { - 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); - } + return lcGetActiveProject()->ExportPOVRay(FileName); + }); - ui->RenderButton->setEnabled(true); - } -} + QApplication::setOverrideCursor(Qt::WaitCursor); -void lcRenderDialog::on_RenderButton_clicked() -{ -#ifndef QT_NO_PROCESS - if (mProcess) + while (!exportThread.isFinished()) + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + + QGuiApplication::restoreOverrideCursor(); + + ui->RenderProgress->setFormat("%p%"); + + auto [Success, ErrorMessage] = exportThread.result(); + + if (!Success) { - PromptCancel(); + if (!ErrorMessage.isEmpty()) + QMessageBox::information(this, tr("Render Error"), ErrorMessage); + return; } - ui->RenderOutputButton->setEnabled(false); + QStringList Arguments; - 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; - - QStringList Arguments; - - 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-"); + 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())); + Arguments.append(QString::fromLatin1("+SM\"%1\"").arg(GetStdOutFileName())); #endif - int Quality = ui->QualityComboBox->currentIndex(); + switch (mQuality) + { + case lcPOVRayRenderQuality::High: + Arguments.append("+Q11"); + Arguments.append("+R3"); + Arguments.append("+A0.1"); + Arguments.append("+J0.5"); + break; - switch (Quality) - { - case 0: - 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 1: - Arguments.append("+Q5"); - Arguments.append("+A0.1"); - break; + case lcPOVRayRenderQuality::Low: + break; + } - case 2: - break; - } - - QString POVRayPath; + QString POVRayPath; #ifdef Q_OS_WIN - POVRayPath = QDir::cleanPath(QCoreApplication::applicationDirPath() + QLatin1String("/povconsole32-sse2.exe")); + 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"); + POVRayPath = lcGetProfileString(LC_PROFILE_POVRAY_PATH); + Arguments.append("+FN"); + Arguments.append("-D"); #endif #ifdef Q_OS_MACOS - POVRayPath = QDir::cleanPath(QCoreApplication::applicationDirPath() + QLatin1String("/povray")); + POVRayPath = QDir::cleanPath(QCoreApplication::applicationDirPath() + QLatin1String("/povray")); #endif - 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)); - } - } + const QString POVRayDir = QFileInfo(POVRayPath).absolutePath(); + const QString IncludePath = QDir::cleanPath(POVRayDir + "/include"); - lcRenderProcess* Process = new lcRenderProcess(this); + 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)); + } + } + + lcRenderProcess* Process = new lcRenderProcess(this); #ifdef Q_OS_LINUX - connect(Process, SIGNAL(readyReadStandardError()), this, SLOT(ReadStdErr())); + connect(Process, SIGNAL(readyReadStandardError()), this, SLOT(ReadStdErr())); #endif - QStringList POVEnv = QProcess::systemEnvironment(); - POVEnv.prepend("POV_IGNORE_SYSCONF_MSG=1"); - Process->setEnvironment(POVEnv); - Process->setStandardErrorFile(GetStdErrFileName()); - Process->start(POVRayPath, Arguments); + QStringList POVEnv = QProcess::systemEnvironment(); + POVEnv.prepend("POV_IGNORE_SYSCONF_MSG=1"); + Process->setEnvironment(POVEnv); + Process->setStandardErrorFile(GetStdErrFileName()); + Process->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); + mImage = QImage(mWidth, mHeight, QImage::Format_ARGB32); + mImage.fill(Qt::transparent); + ui->preview->SetImage(mImage); - if (Process->waitForStarted()) + if (Process->waitForStarted()) + { + mProcess = Process; + mRenderButton->setText(tr("Cancel")); + ui->RenderProgress->setValue(ui->RenderProgress->minimum()); + mStdErrList.clear(); + } + else + { + delete Process; + gMainWindow->mActions[LC_FILE_RENDER_POVRAY]->setEnabled(true); + 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).arg(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("\\","\\\\")) + .arg(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"); + 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 (mDialogMode == lcRenderDialogMode::OpenInBlender) + ScriptCommand.append(QString(" > %1").arg(QDir::toNativeSeparators(GetStdOutFileName()))); + + const QLatin1String LineEnding("\r\n"); + + QFile ScriptFile(QString("%1/%2").arg(QDir::tempPath()).arg(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())); + + 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 + + mProcess = new lcRenderProcess(this); + + connect(mProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(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() << Script.fileName()); +#endif + if (mProcess) { - mProcess = Process; - ui->RenderButton->setText(tr("Cancel")); - ui->RenderProgress->setValue(ui->RenderProgress->minimum()); - mStdErrList.clear(); - } - else - { - delete Process; - gMainWindow->mActions[LC_FILE_RENDER_POVRAY]->setEnabled(true); - QMessageBox::information(this, tr("Render"), tr("Error starting POV-Ray.")); + 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 { - 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(); - - const QString Option = mCommand == OPEN_IN_BLENDER ? tr("import") : tr("render"); - - ui->renderLabel->setText(tr("Saving Blender %1 model...").arg(Option)); + 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() << Script.fileName()); +#endif + } + if (mProcess->waitForStarted()) + { + mRenderButton->setText(tr("Cancel")); + ui->RenderProgress->setValue(ui->RenderProgress->minimum()); 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).arg(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); - - 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("\\","\\\\")) - .arg(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 (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) - ScriptCommand.append(QString(" > %1").arg(QDir::toNativeSeparators(GetStdOutFileName()))); - - 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())); - - 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 - - mProcess = new lcRenderProcess(this); - - connect(mProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(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 (mCommand == OPEN_IN_BLENDER) - { - QFileInfo Info(GetStdOutFileName()); - 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(); -#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::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); - lcBlenderPreferences::ShowMessage(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" << 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()))); - 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 + } + else + { + gMainWindow->mActions[LC_FILE_RENDER_BLENDER]->setEnabled(true); + Message = tr("Error starting Blender render process"); + QMessageBox::information(this, tr("Render Error"), Message); + CloseProcess(); + } } void lcRenderDialog::ReadStdOut() { - if (mCommand == POVRAY_RENDER) + if (mDialogMode == lcRenderDialogMode::RenderPOVRay) return; QString StdOut = QString(mProcess->readAllStandardOutput()); @@ -624,7 +580,7 @@ void lcRenderDialog::ReadStdOut() QRegExp RxRenderProgress; RxRenderProgress.setCaseSensitivity(Qt::CaseInsensitive); #endif - int BlenderVersionNum = QString(lcGetProfileString(LC_PROFILE_BLENDER_VERSION).at(1)).toInt(); + int BlenderVersionNum = QString(lcGetProfileString(LC_PROFILE_BLENDER_VERSION).mid(1, 1)).toInt(); bool BlenderVersion3OrGreater = BlenderVersionNum > 2; if (BlenderVersion3OrGreater) @@ -654,25 +610,26 @@ void lcRenderDialog::ReadStdOut() QString lcRenderDialog::ReadStdErr(bool& HasError) const { - HasError = mCommand == BLENDER_RENDER ? false : true; + HasError = mDialogMode == lcRenderDialogMode::RenderBlender ? false : true; QFile File; - QStringList returnLines; + QStringList ReturnLines; File.setFileName(GetStdErrFileName()); - if (! File.open(QFile::ReadOnly | QFile::Text)) + 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()); return message; } QTextStream In(&File); - while (! In.atEnd()) + while (!In.atEnd()) { QString Line = In.readLine(0); - returnLines << Line.trimmed() + "
"; - if (mCommand == POVRAY_RENDER) + 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))) @@ -682,14 +639,14 @@ QString lcRenderDialog::ReadStdErr(bool& HasError) const HasError = false; #endif } - else if (mCommand == BLENDER_RENDER) + else if (mDialogMode == lcRenderDialogMode::RenderBlender) { if (!HasError && !Line.isEmpty()) HasError = true; } } - return returnLines.join(" "); + return ReturnLines.join(" "); } void lcRenderDialog::WriteStdOut() @@ -705,13 +662,12 @@ void lcRenderDialog::WriteStdOut() File.close(); - ui->RenderOutputButton->setEnabled(true); + mLogButton->setEnabled(true); } } void lcRenderDialog::Update() { -#ifndef QT_NO_PROCESS if (!mProcess) return; @@ -725,9 +681,8 @@ void lcRenderDialog::Update() ShowResult(); CloseProcess(); } -#endif - if (mCommand == POVRAY_RENDER) + if (mDialogMode == lcRenderDialogMode::RenderPOVRay) { #if LC_POVRAY_MEMORY_MAPPED_FILE if (!mOutputBuffer) @@ -793,34 +748,27 @@ void lcRenderDialog::Update() void lcRenderDialog::ShowResult() { -#ifndef QT_NO_PROCESS bool Error; - const QString StdErrLog = ReadStdErr(Error); - const QString RenderLabel = mCommand == BLENDER_RENDER ? tr("Blender Render") : tr("POV-Ray Render"); if (mProcess->exitStatus() != QProcess::NormalExit || mProcess->exitCode() != 0 || Error) { - ui->renderLabel->setText(tr("Image generation failed.")); - ui->RenderProgress->setRange(0,1); + 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."); - lcBlenderPreferences::ShowMessage(Body, Title, QString(), StdErrLog, 0, QMessageBox::Warning); + lcBlenderPreferences::ShowMessage(this, Body, tr("Render Error"), QString(), StdErrLog, 0, QMessageBox::Information); return; } else { ui->RenderProgress->setValue(ui->RenderProgress->maximum()); } -#endif bool Success = false; - QString FileName = ui->OutputEdit->text(); - if (mCommand == POVRAY_RENDER) + if (mDialogMode == lcRenderDialogMode::RenderPOVRay) { gMainWindow->mActions[LC_FILE_RENDER_POVRAY]->setEnabled(true); @@ -833,11 +781,11 @@ void lcRenderDialog::ShowResult() Success = Writer.write(mImage); if (!Success) - QMessageBox::warning(this, tr("Error"), tr("Error writing to file '%1':\n%2").arg(FileName, Writer.errorString())); + QMessageBox::information(this, tr("Render Error"), tr("Error writing to file '%1':\n%2").arg(FileName, Writer.errorString())); } } - else if (mCommand == BLENDER_RENDER) + else if (mDialogMode == lcRenderDialogMode::RenderBlender) { gMainWindow->mActions[LC_FILE_RENDER_BLENDER]->setEnabled(true); @@ -857,12 +805,7 @@ void lcRenderDialog::ShowResult() } if (!Success) - { - 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())); - QMessageBox::warning(this, tr("Error"), Message); - } + QMessageBox::information(this, tr("Render Error"), tr("Render failed.")); WriteStdOut(); } @@ -875,22 +818,114 @@ void lcRenderDialog::on_OutputBrowseButton_clicked() ui->OutputEdit->setText(QDir::toNativeSeparators(Result)); } -void lcRenderDialog::on_RenderOutputButton_clicked() +void lcRenderDialog::SettingsButtonClicked() { - QFileInfo FileInfo(GetStdErrFileName()); - QString Message = tr("POV-Ray standard error file not found: %1.").arg(FileInfo.absoluteFilePath()); - if (mCommand == BLENDER_RENDER) + 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::warning(this, tr("Error"), Message); + QMessageBox::information(this, tr("Render Log"), Message); return; } - QDesktopServices::openUrl(QUrl("file:///"+FileInfo.absoluteFilePath(), QUrl::TolerantMode)); + 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() @@ -901,3 +936,5 @@ lcRenderProcess::~lcRenderProcess() waitForFinished(); } } + +#endif // LC_DISABLE_RENDER_DIALOG diff --git a/qt/lc_renderdialog.h b/qt/lc_renderdialog.h index 03cf0701..115c31c9 100644 --- a/qt/lc_renderdialog.h +++ b/qt/lc_renderdialog.h @@ -1,5 +1,7 @@ #pragma once +#ifndef LC_DISABLE_RENDER_DIALOG + #include namespace Ui { @@ -43,64 +45,75 @@ protected: QImage mScaledImage; }; +enum class lcRenderDialogMode +{ + RenderPOVRay, + RenderBlender, + OpenInBlender +}; + +enum class lcPOVRayRenderQuality +{ + Low, + Medium, + High +}; + class lcRenderDialog : public QDialog { Q_OBJECT public: - explicit lcRenderDialog(QWidget* Parent, int Command); - ~lcRenderDialog(); + explicit lcRenderDialog(QWidget* Parent, lcRenderDialogMode Mode); + virtual ~lcRenderDialog(); public slots: void reject() override; - void on_RenderButton_clicked(); void on_OutputBrowseButton_clicked(); - void on_RenderSettingsButton_clicked(); - void on_RenderOutputButton_clicked(); void Update(); protected slots: + void RenderButtonClicked(); + void SettingsButtonClicked(); + void LogButtonClicked(); void ReadStdOut(); void WriteStdOut(); - void UpdateElapsedTime() const; protected: QString GetStdOutFileName() const; QString GetStdErrFileName() const; QString GetPOVFileName() const; QString ReadStdErr(bool& Error) const; + void RenderPOVRay(); + void RenderBlender(); void CloseProcess(); bool PromptCancel(); void ShowResult(); -#ifndef QT_NO_PROCESS - lcRenderProcess* mProcess; -#endif - enum CommandType - { - POVRAY_RENDER, - BLENDER_RENDER, - OPEN_IN_BLENDER - }; QTimer mUpdateTimer; QElapsedTimer mRenderTime; QFile mOutputFile; - void* mOutputBuffer; + void* mOutputBuffer = nullptr; QImage mImage; QStringList mStdErrList; QStringList mStdOutList; + QPushButton* mRenderButton = nullptr; + QPushButton* mSettingsButton = nullptr; + QPushButton* mLogButton = nullptr; + lcRenderProcess* mProcess = nullptr; - int mWidth; - int mHeight; + int mWidth = 1280; + int mHeight = 720; + lcPOVRayRenderQuality mQuality = lcPOVRayRenderQuality::High; int mPreviewWidth; int mPreviewHeight; - int mCommand; + lcRenderDialogMode mDialogMode; int mBlendProgValue; int mBlendProgMax; double mScale; - QString mImportModule; - QString mLabelMessage; QString mDataPath; Ui::lcRenderDialog* ui; }; + +#endif // LC_DISABLE_RENDER_DIALOG diff --git a/qt/lc_renderdialog.ui b/qt/lc_renderdialog.ui index dc40a2be..01da88fa 100644 --- a/qt/lc_renderdialog.ui +++ b/qt/lc_renderdialog.ui @@ -15,78 +15,7 @@ - - - - - - 0 - 0 - - - - Settings - - - - - - Width: - - - - - - - - - - Height: - - - - - - - - - - Blender addon settings - - - Settings... - - - - - - - Quality: - - - - - - - - High - - - - - Medium - - - - - Low - - - - - - - + @@ -94,6 +23,9 @@ Output: + + OutputEdit + @@ -108,49 +40,28 @@ - - - - - - 1 - - - 0 - - - - - - - - - - - - - - Render - - - - - - - Open the standard output log - - - Output... - - - - - + + + + 1 + + + 0 + + + + + + + QDialogButtonBox::Close + + + @@ -162,13 +73,8 @@ - WidthEdit - HeightEdit - RenderSettingsButton - QualityComboBox OutputEdit OutputBrowseButton - RenderButton