diff --git a/common/lc_application.cpp b/common/lc_application.cpp index 328bdcc2..e7a08735 100644 --- a/common/lc_application.cpp +++ b/common/lc_application.cpp @@ -1148,15 +1148,28 @@ lcStartupMode lcApplication::Initialize(const QList>& Libra return lcStartupMode::Error; } - if (Options.CameraName.isEmpty() && !Options.SetCameraPosition) - ActiveView->ZoomExtents(); + const bool UsesCameraPosition = Options.SetCameraPosition && Options.Viewpoint == lcViewpoint::Count && !Options.SetCameraAngles; + + if (Options.CameraName.isEmpty() && !UsesCameraPosition) + { + const lcStep FitStep = Options.ImageStart == Options.ImageEnd ? Options.ImageStart : ActiveModel->GetLastStep(); + + ActiveModel->ZoomExtentsAtStep(ActiveView->GetCamera(), (float)ActiveView->GetWidth() / (float)ActiveView->GetHeight(), FitStep); + } auto ProgressCallback = [&StdOut](const QString& FileName) { StdOut << tr("Saved '%1'.\n").arg(FileName); }; - ActiveView->SaveStepImages(Frame, Options.ImageStart != Options.ImageEnd, Options.ImageStart, Options.ImageEnd, ProgressCallback); + const lcResult ImagesSaved = ActiveView->SaveStepImages(Frame, Options.ImageStart != Options.ImageEnd, Options.ImageStart, Options.ImageEnd, ProgressCallback); + + if (!ImagesSaved) + { + StdErr << ImagesSaved.error() << '\n'; + + return lcStartupMode::Error; + } } if (Options.SaveWavefront) diff --git a/common/lc_blenderpreferences.cpp b/common/lc_blenderpreferences.cpp index 9674cf3f..4ca75d5d 100644 --- a/common/lc_blenderpreferences.cpp +++ b/common/lc_blenderpreferences.cpp @@ -3060,6 +3060,9 @@ void lcBlenderPreferences::SetModelSize(bool Update) NativeImage& Image = Images.back(); Image.RenderedImage = Model->GetStepImage(false, ImageWidth, ImageHeight, Model->GetCurrentStep()); + if (Image.RenderedImage.isNull()) + return; + auto CalculateImageBounds = [](NativeImage& Image) { QImage& RenderedImage = Image.RenderedImage; diff --git a/common/lc_instructionsdialog.cpp b/common/lc_instructionsdialog.cpp index dfd57ed7..2dc66300 100644 --- a/common/lc_instructionsdialog.cpp +++ b/common/lc_instructionsdialog.cpp @@ -25,6 +25,8 @@ void lcInstructionsStepImageItem::Update() if (!Images.empty()) setPixmap(QPixmap::fromImage(Images.front())); + else + setPixmap(QPixmap()); } lcInstructionsStepNumberItem::lcInstructionsStepNumberItem(QGraphicsItem* Parent, lcInstructions* Instructions, lcModel* Model, lcStep Step) diff --git a/common/lc_library.cpp b/common/lc_library.cpp index 6990ca25..e09dc50a 100644 --- a/common/lc_library.cpp +++ b/common/lc_library.cpp @@ -401,7 +401,7 @@ PieceInfo* lcPiecesLibrary::FindPiece(const char* PieceName, Project* CurrentPro if (ProjectFile.isFile()) { Project* NewProject = new Project(CurrentProject && CurrentProject->IsPreview()); - NewProject->SetStreamMeshes(CurrentProject && CurrentProject->UsesStreamingMeshes()); + NewProject->SetDeferModelMeshRequests(CurrentProject && CurrentProject->DefersModelMeshRequests()); if (NewProject->Load(ProjectFile.absoluteFilePath(), false)) { @@ -454,7 +454,7 @@ bool lcPiecesLibrary::RemapProjectPiece(PieceInfo* Info, const QString& ProjectD if (Candidate.isFile()) { Project* LocalProject = new Project(IsPreview); - LocalProject->SetStreamMeshes(true); + LocalProject->SetDeferModelMeshRequests(true); if (LocalProject->Load(Candidate.absoluteFilePath(), false)) Info->CreateProject(LocalProject, PieceName.constData()); @@ -1771,7 +1771,7 @@ lcPartBuildResult lcPiecesLibrary::BuildPieceData(const lcPartSourceSnapshot& So PieceFile.Seek(0, SEEK_SET); - lcMeshLoader MeshLoader(*Result.MeshData, true, nullptr, false); + lcMeshLoader MeshLoader(*Result.MeshData, nullptr, lcMeshLoaderFlag::Optimize); if (!MeshLoader.LoadMesh(PieceFile, LC_MESHDATA_SHARED)) { @@ -1812,7 +1812,7 @@ lcPartBuildResult lcPiecesLibrary::BuildPieceData(const lcPartSourceSnapshot& So Result.MeshData.reset(new lcLibraryMeshData); - lcMeshLoader MeshLoader(*Result.MeshData, true, nullptr, false); + lcMeshLoader MeshLoader(*Result.MeshData, nullptr, lcMeshLoaderFlag::Optimize); bool Loaded = false; @@ -2337,7 +2337,7 @@ bool lcPiecesLibrary::LoadPrimitive(lcLibraryPrimitive* Primitive) return false; }; - lcMeshLoader MeshLoader(Primitive->mMeshData, true, nullptr, false); + lcMeshLoader MeshLoader(Primitive->mMeshData, nullptr, lcMeshLoaderFlag::Optimize | lcMeshLoaderFlag::RequireAllIncludes); if (mZipFiles[static_cast(lcZipFileType::Official)]) { diff --git a/common/lc_mainwindow.cpp b/common/lc_mainwindow.cpp index d9f8480d..712e1595 100644 --- a/common/lc_mainwindow.cpp +++ b/common/lc_mainwindow.cpp @@ -990,7 +990,6 @@ void lcMainWindow::UpdateAssets() if (mStatusProject != Project) { mStatusProject = Project; - mHadPendingAssets = false; mStatusRequiredAssets.clear(); mStatusLoadProgress->reset(); mStatusLoadProgress->setFormat(QString()); @@ -1016,7 +1015,6 @@ void lcMainWindow::UpdateAssets() if (Required.empty()) { - mHadPendingAssets = false; mStatusLoadProgress->reset(); mStatusLoadProgress->hide(); return; @@ -1044,33 +1042,21 @@ void lcMainWindow::UpdateAssets() if (Failed) { - mHadPendingAssets = false; mStatusLoadProgress->setFormat(tr("%n failed (%v/%m)", nullptr, Failed)); mStatusLoadProgress->setToolTip(FailureDetails); } else if (Pending) { - mHadPendingAssets = true; mStatusLoadProgress->setFormat(tr("Loading %v/%m")); } - else if (mHadPendingAssets) - { - mHadPendingAssets = false; - mStatusLoadProgress->setFormat(tr("All pieces loaded")); - QTimer::singleShot(2500, this, [this, Project]() - { - if (mStatusProject == Project && !mHadPendingAssets && mStatusLoadProgress->format() == tr("All pieces loaded")) - { - mStatusLoadProgress->setFormat(QString()); - mStatusLoadProgress->hide(); - } - }); - } else + { + mStatusLoadProgress->setFormat(QString()); mStatusLoadProgress->hide(); + return; + } - if (Failed || Pending || mStatusLoadProgress->format() == tr("All pieces loaded")) - mStatusLoadProgress->show(); + mStatusLoadProgress->show(); } void lcMainWindow::closeEvent(QCloseEvent* Event) @@ -1389,6 +1375,12 @@ void lcMainWindow::Print(QPrinter* Printer) lcStep Step = PageLayout.Steps[0].Step; QImage Image = Model->GetStepImage(false, StepWidth, StepHeight, Step); + if (Image.isNull()) + { + Printer->abort(); + return; + } + Painter.drawImage(MarginRect.left(), MarginRect.top(), Image); // TODO: add print options somewhere but Qt doesn't allow changes to the page setup dialog @@ -1532,7 +1524,10 @@ void lcMainWindow::ShowImageDialog() lcSetProfileInt(LC_PROFILE_IMAGE_WIDTH, Options.Width); lcSetProfileInt(LC_PROFILE_IMAGE_HEIGHT, Options.Height); - lcGetActiveProject()->SaveImage(Options); + const lcResult Saved = lcGetActiveProject()->SaveImage(Options); + + if (!Saved) + QMessageBox::warning(this, tr("LeoCAD"), Saved.error()); } void lcMainWindow::ShowSelectDialog() diff --git a/common/lc_mainwindow.h b/common/lc_mainwindow.h index 37041691..3ffbfd4a 100644 --- a/common/lc_mainwindow.h +++ b/common/lc_mainwindow.h @@ -380,7 +380,6 @@ protected: const Project* mStatusProject = nullptr; std::unordered_set mStatusRequiredAssets; bool mAssetUpdateScheduled = false; - bool mHadPendingAssets = false; QMenu* mTransformMenu = nullptr; QMenu* mToolsMenu = nullptr; diff --git a/common/lc_meshloader.cpp b/common/lc_meshloader.cpp index e6fceaff..017473e3 100644 --- a/common/lc_meshloader.cpp +++ b/common/lc_meshloader.cpp @@ -1052,7 +1052,7 @@ bool lcLibraryMeshData::WriteSections(lcMesh* Mesh, const std::vectorFindTexture(FinalSection.Name, mMeshLoader->mCurrentProject, mMeshLoader->mSearchProjectFolder); + DstSection.Texture = lcGetPiecesLibrary()->FindTexture(FinalSection.Name, mMeshLoader->mCurrentProject, mMeshLoader->SearchProjectFolder()); else DstSection.Texture = lcGetPiecesLibrary()->FindTexture(FinalSection.Name, nullptr, false); } @@ -1226,8 +1226,8 @@ void lcLibraryMeshData::UpdateMeshSectionBoundingBox(const lcMesh* Mesh, const l } } -lcMeshLoader::lcMeshLoader(lcLibraryMeshData& MeshData, bool Optimize, Project* CurrentProject, bool SearchProjectFolder) - : mCurrentProject(CurrentProject), mSearchProjectFolder(SearchProjectFolder), mMeshData(MeshData), mOptimize(Optimize) +lcMeshLoader::lcMeshLoader(lcLibraryMeshData& MeshData, Project* CurrentProject, lcMeshLoaderFlags Flags) + : mCurrentProject(CurrentProject), mMeshData(MeshData), mFlags(Flags) { MeshData.SetMeshLoader(this); } @@ -1247,6 +1247,7 @@ bool lcMeshLoader::ReadMeshData(lcFile& File, const lcMatrix44& CurrentTransform bool InvertNext = false; bool WindingCCW = !InvertWinding; lcPiecesLibrary* Library = lcGetPiecesLibrary(); + const bool Optimize = mFlags.testFlag(lcMeshLoaderFlag::Optimize); while (File.ReadLine(Buffer, sizeof(Buffer))) { @@ -1539,18 +1540,28 @@ bool lcMeshLoader::ReadMeshData(lcFile& File, const lcMatrix44& CurrentTransform mMeshData.AddMeshDataNoDuplicateCheck(Primitive->mMeshData, IncludeTransform, ColorCode, Mirror ^ InvertNext, InvertNext, TextureMap, MeshDataType); else if (!Primitive->mSubFile) { - if (mOptimize) + if (Optimize) mMeshData.AddMeshData(Primitive->mMeshData, IncludeTransform, ColorCode, Mirror ^ InvertNext, InvertNext, TextureMap, MeshDataType); else mMeshData.AddMeshDataNoDuplicateCheck(Primitive->mMeshData, IncludeTransform, ColorCode, Mirror ^ InvertNext, InvertNext, TextureMap, MeshDataType); } else + { + if (mFlags.testFlag(lcMeshLoaderFlag::RequireAllIncludes)) + IncludeLoaded = false; + Library->GetPrimitiveFile(Primitive, FileCallback); + } mMeshData.mHasStyleStud |= Primitive->mStudStyle | Primitive->mMeshData.mHasStyleStud; } else + { + if (mFlags.testFlag(lcMeshLoaderFlag::RequireAllIncludes)) + IncludeLoaded = false; + Library->GetPieceFile(FileName, FileCallback); + } if (!IncludeLoaded) return false; @@ -1563,7 +1574,7 @@ bool lcMeshLoader::ReadMeshData(lcFile& File, const lcMatrix44& CurrentTransform Points[0] = lcMul31(Points[0], CurrentTransform); Points[1] = lcMul31(Points[1], CurrentTransform); - mMeshData.mData[MeshDataType].ProcessLine(LineType, mMeshData.GetMaterial(ColorCode), WindingCCW, Points, mOptimize); + mMeshData.mData[MeshDataType].ProcessLine(LineType, mMeshData.GetMaterial(ColorCode), WindingCCW, Points, Optimize); break; case 3: @@ -1576,11 +1587,11 @@ bool lcMeshLoader::ReadMeshData(lcFile& File, const lcMatrix44& CurrentTransform Points[2] = lcMul31(Points[2], CurrentTransform); if (!TextureMap) - mMeshData.mData[MeshDataType].ProcessLine(LineType, mMeshData.GetMaterial(ColorCode), WindingCCW, Points, mOptimize); + mMeshData.mData[MeshDataType].ProcessLine(LineType, mMeshData.GetMaterial(ColorCode), WindingCCW, Points, Optimize); else { mMeshData.mHasTextures = true; - mMeshData.mData[MeshDataType].ProcessLine(LineType, mMeshData.GetTexturedMaterial(ColorCode, *TextureMap), WindingCCW, Points, mOptimize); + mMeshData.mData[MeshDataType].ProcessLine(LineType, mMeshData.GetTexturedMaterial(ColorCode, *TextureMap), WindingCCW, Points, Optimize); } break; @@ -1596,11 +1607,11 @@ bool lcMeshLoader::ReadMeshData(lcFile& File, const lcMatrix44& CurrentTransform Points[3] = lcMul31(Points[3], CurrentTransform); if (!TextureMap) - mMeshData.mData[MeshDataType].ProcessLine(LineType, mMeshData.GetMaterial(ColorCode), WindingCCW, Points, mOptimize); + mMeshData.mData[MeshDataType].ProcessLine(LineType, mMeshData.GetMaterial(ColorCode), WindingCCW, Points, Optimize); else { mMeshData.mHasTextures = true; - mMeshData.mData[MeshDataType].ProcessLine(LineType, mMeshData.GetTexturedMaterial(ColorCode, *TextureMap), WindingCCW, Points, mOptimize); + mMeshData.mData[MeshDataType].ProcessLine(LineType, mMeshData.GetTexturedMaterial(ColorCode, *TextureMap), WindingCCW, Points, Optimize); } break; @@ -1615,7 +1626,7 @@ bool lcMeshLoader::ReadMeshData(lcFile& File, const lcMatrix44& CurrentTransform Points[2] = lcMul31(Points[2], CurrentTransform); Points[3] = lcMul31(Points[3], CurrentTransform); - mMeshData.mData[MeshDataType].ProcessLine(LineType, mMeshData.GetMaterial(ColorCode), WindingCCW, Points, mOptimize); + mMeshData.mData[MeshDataType].ProcessLine(LineType, mMeshData.GetMaterial(ColorCode), WindingCCW, Points, Optimize); break; } diff --git a/common/lc_meshloader.h b/common/lc_meshloader.h index f4b276e8..7f0cb506 100644 --- a/common/lc_meshloader.h +++ b/common/lc_meshloader.h @@ -201,15 +201,26 @@ protected: static void UpdateMeshSectionBoundingBox(const lcMesh* Mesh, const lcMeshSection& Section, lcVector3& SectionMin, lcVector3& SectionMax); }; +enum class lcMeshLoaderFlag +{ + None = 0, + Optimize = 1 << 0, + SearchProjectFolder = 1 << 1, + RequireAllIncludes = 1 << 2 +}; + +Q_DECLARE_FLAGS(lcMeshLoaderFlags, lcMeshLoaderFlag) +Q_DECLARE_OPERATORS_FOR_FLAGS(lcMeshLoaderFlags) + class lcMeshLoader { public: - lcMeshLoader(lcLibraryMeshData& MeshData, bool Optimize, Project* CurrentProject, bool SearchProjectFolder); + lcMeshLoader(lcLibraryMeshData& MeshData, Project* CurrentProject, lcMeshLoaderFlags Flags); bool LoadMesh(lcFile& File, lcMeshDataType MeshDataType); + bool SearchProjectFolder() const { return mFlags.testFlag(lcMeshLoaderFlag::SearchProjectFolder); } Project* mCurrentProject; - bool mSearchProjectFolder; protected: bool ReadMeshData(lcFile& File, const lcMatrix44& CurrentTransform, quint32 CurrentColorCode, bool InvertWinding, lcMeshDataType MeshDataType); @@ -217,5 +228,5 @@ protected: std::vector mTextureStack; lcLibraryMeshData& mMeshData; - bool mOptimize; + lcMeshLoaderFlags mFlags; }; diff --git a/common/lc_model.cpp b/common/lc_model.cpp index 6cf1d839..5ae4446c 100644 --- a/common/lc_model.cpp +++ b/common/lc_model.cpp @@ -388,9 +388,9 @@ void lcModel::CreatePieceInfo(Project* Project) }), mPieces.end()); } - // A streamed external model retains its initial CreateModelPiece reference + // A model with deferred mesh requests retains its initial CreateModelPiece reference // until its source lines have been parsed and QueueMeshBuild() runs. - if (!Project->UsesStreamingMeshes()) + if (!Project->DefersModelMeshRequests()) { Library->LoadPieceInfo(mPieceInfo, mIsPreview ? lcPieceLoadFlag::Visible : lcPieceLoadFlag::Wait | lcPieceLoadFlag::Visible); Library->ReleasePieceInfo(mPieceInfo); @@ -400,7 +400,7 @@ void lcModel::CreatePieceInfo(Project* Project) { UpdateAllViews(); - if (!Project->UsesStreamingMeshes()) + if (!Project->DefersModelMeshRequests()) RefreshPreview(); } } @@ -1657,25 +1657,51 @@ QImage lcModel::GetStepImage(bool Zoom, int Width, int Height, lcStep Step) return QImage(); } - const lcView* ActiveView = gMainWindow->GetActiveView(); - const lcStep CurrentStep = mCurrentStep; - lcCamera* Camera = ActiveView->GetCamera(); + const lcResult Image = RenderStepImageWithReadyAssets(Zoom, Width, Height, Step, nullptr); - lcView View(lcViewType::View, this); - View.SetCamera(Camera, true); - View.SetOffscreenContext(); - View.MakeCurrent(); - - if (!View.BeginRenderToImage(Width, Height)) + if (!Image) { - QMessageBox::warning(gMainWindow, tr("LeoCAD"), tr("Error creating images.")); + if (gMainWindow) + QMessageBox::warning(gMainWindow, tr("LeoCAD"), Image.error()); + return QImage(); } + return Image.value(); +} + +lcResult lcModel::RenderStepImageWithReadyAssets(bool Zoom, int Width, int Height, lcStep Step, lcCamera* OutputCamera) +{ + const lcView* ActiveView = gMainWindow ? gMainWindow->GetActiveView() : nullptr; + const lcStep CurrentStep = mCurrentStep; + + lcView View(lcViewType::View, this); + + if (OutputCamera) + View.SetCamera(OutputCamera, true); + else if (ActiveView) + View.SetCamera(ActiveView->GetCamera(), true); + + View.SetOffscreenContext(); + View.MakeCurrent(); + + if (!OutputCamera && !ActiveView) + ZoomExtentsAtStep(View.GetCamera(), (float)Width / (float)Height, GetLastStep()); + SetTemporaryStep(Step); - if (Zoom) - ZoomExtents(Camera, (float)Width / (float)Height, lcMatrix44Identity()); + if (Zoom && ActiveView && !OutputCamera) + ZoomExtents(View.GetCamera(), (float)Width / (float)Height, lcMatrix44Identity()); + + if (!View.BeginRenderToImage(Width, Height)) + { + SetTemporaryStep(CurrentStep); + + if (!mActive) + CalculateStep(LC_STEP_MAX); + + return lcUnexpected(tr("Error creating images.")); + } View.OnDraw(); @@ -1693,8 +1719,8 @@ QImage lcModel::GetStepImage(bool Zoom, int Width, int Height, lcStep Step) if (!mActive) CalculateStep(LC_STEP_MAX); - if (MissingAssets && gMainWindow) - QMessageBox::warning(gMainWindow, tr("LeoCAD"), tr("Could not render all required pieces.")); + if (MissingAssets || Image.isNull()) + return lcUnexpected(tr("Could not render all required pieces.")); return Image; } @@ -1765,7 +1791,9 @@ QImage lcModel::GetPartsListImage(int MaxWidth, lcStep Step, quint32 BackgroundC if (!View.BeginRenderToImage(ThumbnailSize, ThumbnailSize)) { - QMessageBox::warning(gMainWindow, tr("LeoCAD"), tr("Error creating images.")); + if (gMainWindow) + QMessageBox::warning(gMainWindow, tr("LeoCAD"), tr("Error creating images.")); + return QImage(); } @@ -1946,16 +1974,21 @@ QImage lcModel::GetPartsListImage(int MaxWidth, lcStep Step, quint32 BackgroundC return PainterImage; } -void lcModel::SaveStepImages(const QString& BaseName, bool AddStepSuffix, bool Zoom, int Width, int Height, lcStep Start, lcStep End) +lcResult lcModel::SaveStepImages(const QString& BaseName, bool AddStepSuffix, bool Zoom, int Width, int Height, lcStep Start, lcStep End) { const lcResult Ready = EnsureAssetsReady(); if (!Ready) - { - if (gMainWindow) - QMessageBox::warning(gMainWindow, tr("LeoCAD"), Ready.error()); + return Ready; - return; + lcCamera OutputCamera(true); + lcCamera* Camera = nullptr; + + if (!gMainWindow || !gMainWindow->GetActiveView()) + { + OutputCamera.SetViewpoint(lcViewpoint::Home); + ZoomExtentsAtStep(&OutputCamera, (float)Width / (float)Height, GetLastStep()); + Camera = &OutputCamera; } for (lcStep Step = Start; Step <= End; Step++) @@ -1967,18 +2000,21 @@ void lcModel::SaveStepImages(const QString& BaseName, bool AddStepSuffix, bool Z else FileName = BaseName; + const lcResult Image = RenderStepImageWithReadyAssets(Zoom, Width, Height, Step, Camera); + + if (!Image) + return lcUnexpected(Image.error()); + QImageWriter Writer(FileName); if (Writer.format().isEmpty()) Writer.setFormat("png"); - QImage Image = GetStepImage(Zoom, Width, Height, Step); - if (!Writer.write(Image)) - { - QMessageBox::information(gMainWindow, tr("Error"), tr("Error writing to file '%1':\n%2").arg(FileName, Writer.errorString())); - break; - } + if (!Writer.write(Image.value())) + return lcUnexpected(tr("Error writing to file '%1':\n%2").arg(FileName, Writer.errorString())); } + + return lcResult(); } void lcModel::RayTest(lcObjectRayTest& ObjectRayTest) const @@ -2026,7 +2062,7 @@ bool lcModel::SubModelMinIntersectDist(const lcVector3& WorldStart, const lcVect if (Piece->IsVisibleInSubModel()) { - if (Piece->mPieceInfo->MinIntersectDist(Start, End, MinDistance, PieceInfoRayTest)) // todo: this should check for piece->mMesh first + if (Piece->MinIntersectDist(Start, End, MinDistance, PieceInfoRayTest)) { MinIntersect = true; PieceInfoRayTest.Transform = lcMul(PieceInfoRayTest.Transform, Piece->mModelWorld); @@ -2040,7 +2076,7 @@ bool lcModel::SubModelMinIntersectDist(const lcVector3& WorldStart, const lcVect bool lcModel::SubModelBoxTest(const lcVector4 Planes[6]) const { for (const std::unique_ptr& Piece : mPieces) - if (Piece->IsVisibleInSubModel() && Piece->mPieceInfo->BoxTest(Piece->mModelWorld, Planes)) + if (Piece->IsVisibleInSubModel() && Piece->IntersectsPlanes(Piece->mModelWorld, Planes)) return true; return false; @@ -5964,6 +6000,18 @@ void lcModel::ZoomExtents(lcCamera* Camera, float Aspect, const lcMatrix44& Worl } } +void lcModel::ZoomExtentsAtStep(lcCamera* Camera, float Aspect, lcStep Step) +{ + const lcStep CurrentStep = mCurrentStep; + + SetTemporaryStep(Step); + ZoomExtents(Camera, Aspect, lcMatrix44Identity()); + SetTemporaryStep(CurrentStep); + + if (!mActive) + CalculateStep(LC_STEP_MAX); +} + void lcModel::Zoom(lcCamera* Camera, float Amount) { if (!Camera->IsSimple()) diff --git a/common/lc_model.h b/common/lc_model.h index 70754e61..6f24cd59 100644 --- a/common/lc_model.h +++ b/common/lc_model.h @@ -331,7 +331,7 @@ public: void AddSubModelRenderMeshes(lcScene* Scene, const lcMatrix44& WorldMatrix, int DefaultColorIndex, lcRenderMeshState RenderMeshState, bool ParentActive) const; QImage GetStepImage(bool Zoom, int Width, int Height, lcStep Step); QImage GetPartsListImage(int MaxWidth, lcStep Step, quint32 BackgroundColor, QFont Font, QColor TextColor) const; - void SaveStepImages(const QString& BaseName, bool AddStepSuffix, bool Zoom, int Width, int Height, lcStep Start, lcStep End); + lcResult SaveStepImages(const QString& BaseName, bool AddStepSuffix, bool Zoom, int Width, int Height, lcStep Start, lcStep End); void RayTest(lcObjectRayTest& ObjectRayTest) const; void BoxTest(lcObjectBoxTest& ObjectBoxTest) const; @@ -423,6 +423,7 @@ public: void LookAt(lcCamera* Camera); void MoveCamera(lcCamera* Camera, const lcVector3& Direction); void ZoomExtents(lcCamera* Camera, float Aspect, const lcMatrix44& WorldMatrix); + void ZoomExtentsAtStep(lcCamera* Camera, float Aspect, lcStep Step); void Zoom(lcCamera* Camera, float Amount); void MoveSelectedObjects(const lcVector3& Distance, lcModelTransformFlags Flags, lcModelHistoryEditMerge ModelHistoryEditMerge) @@ -452,6 +453,8 @@ public: void UpdateInterface(); protected: + lcResult RenderStepImageWithReadyAssets(bool Zoom, int Width, int Height, lcStep Step, lcCamera* OutputCamera); + void DeleteModel(); void AddSelectionHistory(std::function Callback); diff --git a/common/lc_scene.h b/common/lc_scene.h index 15dc0e4c..4ecd2c6f 100644 --- a/common/lc_scene.h +++ b/common/lc_scene.h @@ -68,6 +68,16 @@ public: return mRequireCompleteAssets; } + void SetRequireGeneratedMeshes(bool RequireGeneratedMeshes) + { + mRequireGeneratedMeshes = RequireGeneratedMeshes; + } + + bool GetRequireGeneratedMeshes() const + { + return mRequireGeneratedMeshes; + } + void MarkMissingAssets() { mMissingAssets = true; @@ -136,6 +146,7 @@ protected: lcShadingMode mShadingMode; bool mDrawInterface = false; bool mRequireCompleteAssets = false; + bool mRequireGeneratedMeshes = false; bool mMissingAssets = false; bool mDrawInsertPreview = false; bool mAllowLOD = true; diff --git a/common/lc_synth.cpp b/common/lc_synth.cpp index 86c17e5b..3f776c8f 100644 --- a/common/lc_synth.cpp +++ b/common/lc_synth.cpp @@ -1526,8 +1526,8 @@ lcMesh* lcSynthInfo::CreateMesh(const std::vector& ControlP File.WriteU8(0); File.Seek(0, SEEK_SET); - lcMeshLoader MeshLoader(MeshData, false, nullptr, false); - if (MeshLoader.LoadMesh(File, LC_MESHDATA_SHARED)) + lcMeshLoader MeshLoader(MeshData, nullptr, lcMeshLoaderFlag::RequireAllIncludes); + if (MeshLoader.LoadMesh(File, LC_MESHDATA_SHARED) && !MeshData.IsEmpty()) return MeshData.CreateMesh(); return nullptr; diff --git a/common/lc_thumbnailmanager.cpp b/common/lc_thumbnailmanager.cpp index 78eec19d..341636f2 100644 --- a/common/lc_thumbnailmanager.cpp +++ b/common/lc_thumbnailmanager.cpp @@ -256,6 +256,7 @@ void lcThumbnailManager::DrawThumbnail(lcPartThumbnailId ThumbnailId, lcPartThum if (!mView->BeginRenderToImage(Width, Height)) { mView.reset(); + DrawFailedThumbnail(ThumbnailId, Thumbnail); return; } } @@ -293,6 +294,13 @@ void lcThumbnailManager::DrawThumbnail(lcPartThumbnailId ThumbnailId, lcPartThum } QImage Image = mView->GetRenderImage().convertToFormat(QImage::Format_ARGB32); + + if (Image.isNull()) + { + DrawFailedThumbnail(ThumbnailId, Thumbnail); + return; + } + const char* IconName = nullptr; if (Info->GetSynthInfo()) diff --git a/common/lc_view.cpp b/common/lc_view.cpp index f821eea6..e4bf75bf 100644 --- a/common/lc_view.cpp +++ b/common/lc_view.cpp @@ -734,7 +734,9 @@ std::vector lcView::GetStepImages(lcStep Start, lcStep End) if (!BeginRenderToImage(mWidth, mHeight)) { - QMessageBox::warning(gMainWindow, tr("LeoCAD"), tr("Error creating images.")); + if (gMainWindow) + QMessageBox::warning(gMainWindow, tr("LeoCAD"), tr("Error creating images.")); + return Images; } @@ -754,7 +756,16 @@ std::vector lcView::GetStepImages(lcStep Start, lcStep End) break; } - Images.emplace_back(GetRenderImage()); + QImage Image = GetRenderImage(); + + if (Image.isNull()) + { + MissingAssets = true; + Images.clear(); + break; + } + + Images.emplace_back(std::move(Image)); } EndRenderToImage(); @@ -770,12 +781,12 @@ std::vector lcView::GetStepImages(lcStep Start, lcStep End) return Images; } -void lcView::SaveStepImages(const QString& BaseName, bool AddStepSuffix, lcStep Start, lcStep End, std::function ProgressCallback) +lcResult lcView::SaveStepImages(const QString& BaseName, bool AddStepSuffix, lcStep Start, lcStep End, std::function ProgressCallback) { std::vector Images = GetStepImages(Start, End); if (Images.size() != static_cast(End - Start + 1)) - return; + return lcUnexpected(tr("Could not render all required pieces.")); for (lcStep Step = Start; Step <= End; Step++) { @@ -792,14 +803,13 @@ void lcView::SaveStepImages(const QString& BaseName, bool AddStepSuffix, lcStep Writer.setFormat("png"); if (!Writer.write(Images[Step - Start])) - { - QMessageBox::information(gMainWindow, tr("Error"), tr("Error writing to file '%1':\n%2").arg(FileName, Writer.errorString())); - break; - } + return lcUnexpected(tr("Error writing to file '%1':\n%2").arg(FileName, Writer.errorString())); if (ProgressCallback) ProgressCallback(FileName); } + + return lcResult(); } bool lcView::BeginRenderToImage(int Width, int Height) @@ -896,6 +906,7 @@ void lcView::OnDraw() mScene->SetShadingMode(ShadingMode); mScene->SetAllowLOD(Preferences.mAllowLOD && mWidget != nullptr); mScene->SetRequireCompleteAssets(static_cast(mRenderFramebuffer)); + mScene->SetRequireGeneratedMeshes(mRenderFramebuffer && mViewType != lcViewType::PartsList); mScene->SetLODDistance(Preferences.mMeshLODDistance); mScene->Begin(mCamera->mWorldView); diff --git a/common/lc_view.h b/common/lc_view.h index 77d8836e..35d782d0 100644 --- a/common/lc_view.h +++ b/common/lc_view.h @@ -3,6 +3,7 @@ #include "lc_context.h" #include "lc_math.h" #include "lc_commands.h" +#include "lc_result.h" struct lcInsertPieceInfo; enum class lcCameraProjection; @@ -304,7 +305,7 @@ public: void UnbindRenderFramebuffer(); QImage GetRenderFramebufferImage() const; std::vector GetStepImages(lcStep Start, lcStep End); - void SaveStepImages(const QString& BaseName, bool AddStepSuffix, lcStep Start, lcStep End, std::function ProgressCallback); + lcResult SaveStepImages(const QString& BaseName, bool AddStepSuffix, lcStep Start, lcStep End, std::function ProgressCallback); lcContext* mContext = nullptr; diff --git a/common/piece.cpp b/common/piece.cpp index f28844c9..4c3e1d02 100644 --- a/common/piece.cpp +++ b/common/piece.cpp @@ -480,23 +480,11 @@ void lcPiece::RayTest(lcObjectRayTest& ObjectRayTest) const const lcVector3 Start = lcMul31(ObjectRayTest.Start, InverseWorldMatrix); const lcVector3 End = lcMul31(ObjectRayTest.End, InverseWorldMatrix); - if (mMesh && !mPieceInfo->IsLoading()) + if (MinIntersectDist(Start, End, ObjectRayTest.Distance, ObjectRayTest.PieceInfoRayTest)) { - if (mMesh->MinIntersectDist(Start, End, ObjectRayTest.Distance, ObjectRayTest.PieceInfoRayTest.Plane)) - { - ObjectRayTest.ObjectSection.Object = const_cast(this); - ObjectRayTest.ObjectSection.Section = LC_PIECE_SECTION_POSITION; - ObjectRayTest.PieceInfoRayTest.Transform = mModelWorld; - } - } - else - { - if (mPieceInfo->MinIntersectDist(Start, End, ObjectRayTest.Distance, ObjectRayTest.PieceInfoRayTest)) - { - ObjectRayTest.ObjectSection.Object = const_cast(this); - ObjectRayTest.ObjectSection.Section = LC_PIECE_SECTION_POSITION; - ObjectRayTest.PieceInfoRayTest.Transform = lcMul(ObjectRayTest.PieceInfoRayTest.Transform, mModelWorld); - } + ObjectRayTest.ObjectSection.Object = const_cast(this); + ObjectRayTest.ObjectSection.Section = LC_PIECE_SECTION_POSITION; + ObjectRayTest.PieceInfoRayTest.Transform = lcMul(ObjectRayTest.PieceInfoRayTest.Transform, mModelWorld); } if (mPieceInfo->GetSynthInfo() && AreControlPointsVisible()) @@ -524,12 +512,67 @@ void lcPiece::RayTest(lcObjectRayTest& ObjectRayTest) const } } +bool lcPiece::MinIntersectDist(const lcVector3& Start, const lcVector3& End, float& MinDistance, lcPieceInfoRayTest& PieceInfoRayTest) const +{ + if (!mMesh || mPieceInfo->IsLoading()) + return mPieceInfo->MinIntersectDist(Start, End, MinDistance, PieceInfoRayTest); + + if (!mMesh->MinIntersectDist(Start, End, MinDistance, PieceInfoRayTest.Plane)) + return false; + + PieceInfoRayTest.Info = mPieceInfo; + PieceInfoRayTest.Transform = lcMatrix44Identity(); + + return true; +} + void lcPiece::BoxTest(lcObjectBoxTest& ObjectBoxTest) const { - if (mPieceInfo->BoxTest(mModelWorld, ObjectBoxTest.Planes)) + if (IntersectsPlanes(mModelWorld, ObjectBoxTest.Planes)) ObjectBoxTest.Objects.emplace_back(const_cast(this)); } +bool lcPiece::IntersectsPlanes(const lcMatrix44& WorldMatrix, const lcVector4 Planes[6]) const +{ + if (!mMesh || mPieceInfo->IsLoading()) + return mPieceInfo->BoxTest(WorldMatrix, Planes); + + const lcMatrix44 InverseWorldMatrix = lcMatrix44AffineInverse(WorldMatrix); + lcVector4 LocalPlanes[6]; + + for (int PlaneIdx = 0; PlaneIdx < 6; PlaneIdx++) + { + const lcVector3 PlaneNormal = lcMul30(Planes[PlaneIdx], InverseWorldMatrix); + LocalPlanes[PlaneIdx] = lcVector4(PlaneNormal, Planes[PlaneIdx][3] - lcDot3(InverseWorldMatrix[3], PlaneNormal)); + } + + lcVector3 Corners[8]; + lcGetBoxCorners(mMesh->mBoundingBox, Corners); + + int OutcodesOR = 0; + int OutcodesAND = 0x3f; + + for (const lcVector3& Corner : Corners) + { + int Outcode = 0; + + for (int PlaneIdx = 0; PlaneIdx < 6; PlaneIdx++) + if (lcDot3(Corner, LocalPlanes[PlaneIdx]) + LocalPlanes[PlaneIdx][3] > 0) + Outcode |= 1 << PlaneIdx; + + OutcodesOR |= Outcode; + OutcodesAND &= Outcode; + } + + if (OutcodesAND != 0) + return false; + + if (OutcodesOR == 0) + return true; + + return mMesh->IntersectsPlanes(LocalPlanes); +} + void lcPiece::DrawInterface(lcContext* Context, const lcScene& Scene) const { const lcPreferences& Preferences = lcGetPreferences(); @@ -1012,7 +1055,12 @@ void lcPiece::AddMainModelRenderMeshes(lcScene* Scene, bool Highlight, bool Fade } if (!mMesh || mPieceInfo->IsLoading()) + { + if (!mMesh && mPieceInfo->GetSynthInfo() && Scene->GetRequireGeneratedMeshes()) + Scene->MarkMissingAssets(); + mPieceInfo->AddRenderMeshes(Scene, mModelWorld, mColorIndex, RenderMeshState, ParentActive); + } else Scene->AddMesh(mMesh, mModelWorld, mColorIndex, RenderMeshState); @@ -1035,7 +1083,12 @@ void lcPiece::AddSubModelRenderMeshes(lcScene* Scene, const lcMatrix44& WorldMat RenderMeshState = IsFocused() ? lcRenderMeshState::Focused : (IsSelected() ? lcRenderMeshState::Selected : lcRenderMeshState::Default); if (!mMesh || mPieceInfo->IsLoading()) + { + if (!mMesh && mPieceInfo->GetSynthInfo() && Scene->GetRequireGeneratedMeshes()) + Scene->MarkMissingAssets(); + mPieceInfo->AddRenderMeshes(Scene, lcMul(mModelWorld, WorldMatrix), ColorIndex, RenderMeshState, ActiveSubmodelInstance == this); + } else Scene->AddMesh(mMesh, lcMul(mModelWorld, WorldMatrix), ColorIndex, RenderMeshState); diff --git a/common/piece.h b/common/piece.h index 9dafbf69..b6881495 100644 --- a/common/piece.h +++ b/common/piece.h @@ -85,6 +85,8 @@ public: void RayTest(lcObjectRayTest& ObjectRayTest) const override; void BoxTest(lcObjectBoxTest& ObjectBoxTest) const override; + bool MinIntersectDist(const lcVector3& Start, const lcVector3& End, float& MinDistance, lcPieceInfoRayTest& PieceInfoRayTest) const; + bool IntersectsPlanes(const lcMatrix44& WorldMatrix, const lcVector4 Planes[6]) const; void DrawInterface(lcContext* Context, const lcScene& Scene) const override; QVariant GetPropertyValue(lcObjectPropertyId PropertyId) const override; bool SetPropertyValue(lcObjectPropertyId PropertyId, lcStep Step, bool AddKey, QVariant Value) override; diff --git a/common/project.cpp b/common/project.cpp index 98f338ad..a6144c6f 100644 --- a/common/project.cpp +++ b/common/project.cpp @@ -710,7 +710,7 @@ bool Project::Load(const QString& FileName, bool ShowErrors) } else { - if (mStreamMeshes) + if (mDeferModelMeshRequests) lcGetPiecesLibrary()->InvalidatePiece(Model->GetPieceInfo()); Model->QueueMeshBuild(); @@ -1118,7 +1118,7 @@ bool Project::ImportInventory(const std::vector& SetInventor return true; } -std::vector Project::GetModelParts() +lcResult> Project::GetModelParts() { std::vector ModelParts; @@ -1132,7 +1132,13 @@ std::vector Project::GetModelParts() SetActiveModel(mActiveModel, false); - return ModelParts; + for (const lcModelPartsEntry& ModelPart : ModelParts) + { + if (ModelPart.Info->GetSynthInfo() && !ModelPart.Mesh) + return lcUnexpected(tr("Could not generate geometry for piece '%1'.").arg(QString::fromLatin1(ModelPart.Info->mFileName))); + } + + return std::move(ModelParts); } std::vector Project::GetRequiredPieces() const @@ -1304,7 +1310,12 @@ lcResult Project::Export3DStudio(const QString& FileName) if (!Ready) return Ready; - std::vector ModelParts = GetModelParts(); + const lcResult> ModelPartsResult = GetModelParts(); + + if (!ModelPartsResult) + return lcUnexpected(ModelPartsResult.error()); + + const std::vector& ModelParts = ModelPartsResult.value(); if (ModelParts.empty()) return lcUnexpected(tr("Nothing to export.")); @@ -1762,7 +1773,12 @@ lcResult Project::ExportCOLLADA(const QString& FileName) if (!Ready) return Ready; - std::vector ModelParts = GetModelParts(); + const lcResult> ModelPartsResult = GetModelParts(); + + if (!ModelPartsResult) + return lcUnexpected(ModelPartsResult.error()); + + const std::vector& ModelParts = ModelPartsResult.value(); if (ModelParts.empty()) return lcUnexpected(tr("Nothing to export.")); @@ -2104,19 +2120,33 @@ lcResult Project::ExportHTML(const lcHTMLExportOptions& Options) QString ProjectTitle = GetTitle(); - auto AddPartsListImage = [&Dir](QTextStream& Stream, lcModel* Model, lcStep Step, const QString& BaseName) + auto AddPartsListImage = [&Dir](QTextStream& Stream, lcModel* Model, lcStep Step, const QString& BaseName) -> lcResult { + lcPartsList PartsList; + + if (Step == 0) + Model->GetPartsList(gDefaultColor, true, false, PartsList); + else + Model->GetPartsListForStep(Step, gDefaultColor, PartsList, false); + + if (PartsList.empty()) + return lcResult(); + QImage Image = Model->GetPartsListImage(1024, Step, LC_RGBA(255, 255, 255, 0), QFont("Arial", 16, QFont::Bold), Qt::black); - if (!Image.isNull()) - { - QString ImageName = BaseName + QLatin1String("-parts.png"); - QString FileName = QFileInfo(Dir, ImageName).absoluteFilePath(); + if (Image.isNull()) + return lcUnexpected(tr("Could not render the pieces list image.")); - Image.save(FileName); + QString ImageName = BaseName + QLatin1String("-parts.png"); + QString FileName = QFileInfo(Dir, ImageName).absoluteFilePath(); + QImageWriter Writer(FileName); - Stream << QString::fromLatin1("



\r\n").arg(ImageName); - } + if (!Writer.write(Image)) + return lcUnexpected(tr("Error writing to file '%1':\n%2").arg(FileName, Writer.errorString())); + + Stream << QString::fromLatin1("



\r\n").arg(ImageName); + + return lcResult(); }; for (lcModel* Model : Models) @@ -2152,11 +2182,21 @@ lcResult Project::ExportHTML(const lcHTMLExportOptions& Options) Stream << QString::fromLatin1("

\"Step



\r\n").arg(BaseName, StepString, StepString, QString::number(Options.StepImagesWidth), QString::number(Options.StepImagesHeight)); if (Options.PartsListStep) - AddPartsListImage(Stream, Model, Step, QString("%1-%2").arg(BaseName, StepString)); + { + const lcResult PartsImageSaved = AddPartsListImage(Stream, Model, Step, QString("%1-%2").arg(BaseName, StepString)); + + if (!PartsImageSaved) + return PartsImageSaved; + } } if (Options.PartsListEnd) - AddPartsListImage(Stream, Model, 0, BaseName); + { + const lcResult PartsImageSaved = AddPartsListImage(Stream, Model, 0, BaseName); + + if (!PartsImageSaved) + return PartsImageSaved; + } Stream << QLatin1String("\r\n


Created by LeoCAD
\r\n"); } @@ -2198,7 +2238,12 @@ lcResult Project::ExportHTML(const lcHTMLExportOptions& Options) Stream << QString::fromLatin1("\"Step

\r\n").arg(BaseName, StepString, StepString, QString::number(Options.StepImagesWidth), QString::number(Options.StepImagesHeight)); if (Options.PartsListStep) - AddPartsListImage(Stream, Model, Step, QString("%1-%2").arg(BaseName, StepString)); + { + const lcResult PartsImageSaved = AddPartsListImage(Stream, Model, Step, QString("%1-%2").arg(BaseName, StepString)); + + if (!PartsImageSaved) + return PartsImageSaved; + } Stream << QLatin1String("\r\n


"); if (Step != 1) @@ -2227,7 +2272,10 @@ lcResult Project::ExportHTML(const lcHTMLExportOptions& Options) Stream << QString::fromLatin1("\r\n\r\nPieces used by %1\r\n\r\n
\r\n
\n").arg(PageTitle); - AddPartsListImage(Stream, Model, 0, BaseName); + const lcResult PartsImageSaved = AddPartsListImage(Stream, Model, 0, BaseName); + + if (!PartsImageSaved) + return PartsImageSaved; Stream << QLatin1String("
\r\n


"); Stream << QString::fromLatin1("Previous ").arg(BaseName, QString("%1").arg(LastStep, 2, 10, QLatin1Char('0'))); @@ -2240,7 +2288,10 @@ lcResult Project::ExportHTML(const lcHTMLExportOptions& Options) } QString StepImageBaseName = QFileInfo(Dir, BaseName + QLatin1String("-%1.png")).absoluteFilePath(); - Model->SaveStepImages(StepImageBaseName, true, false, Options.StepImagesWidth, Options.StepImagesHeight, 1, LastStep); + const lcResult ImagesSaved = Model->SaveStepImages(StepImageBaseName, true, false, Options.StepImagesWidth, Options.StepImagesHeight, 1, LastStep); + + if (!ImagesSaved) + return ImagesSaved; } if (Models.size() > 1) @@ -2298,7 +2349,12 @@ lcResult Project::ExportPOVRay(const QString& FileName) if (!Ready) return Ready; - std::vector ModelParts = GetModelParts(); + const lcResult> ModelPartsResult = GetModelParts(); + + if (!ModelPartsResult) + return lcUnexpected(ModelPartsResult.error()); + + const std::vector& ModelParts = ModelPartsResult.value(); if (ModelParts.empty()) return lcUnexpected(tr("Nothing to export.")); @@ -2989,7 +3045,12 @@ lcResult Project::ExportWavefront(const QString& FileName) if (!Ready) return Ready; - std::vector ModelParts = GetModelParts(); + const lcResult> ModelPartsResult = GetModelParts(); + + if (!ModelPartsResult) + return lcUnexpected(ModelPartsResult.error()); + + const std::vector& ModelParts = ModelPartsResult.value(); if (ModelParts.empty()) return lcUnexpected(tr("Nothing to export.")); @@ -3089,7 +3150,7 @@ lcResult Project::ExportWavefront(const QString& FileName) return lcResult(); } -void Project::SaveImage(const lcImageDialogOptions& Options) +lcResult Project::SaveImage(const lcImageDialogOptions& Options) { QString FilePath = Options.FilePath; QString Extension = QFileInfo(FilePath).suffix(); @@ -3100,7 +3161,7 @@ void Project::SaveImage(const lcImageDialogOptions& Options) if (Options.Start != Options.End) FilePath = FilePath.insert(FilePath.length() - Extension.length() - 1, QLatin1String("%1")); - mActiveModel->SaveStepImages(FilePath, Options.Start != Options.End, false, Options.Width, Options.Height, Options.Start, Options.End); + return mActiveModel->SaveStepImages(FilePath, Options.Start != Options.End, false, Options.Width, Options.Height, Options.Start, Options.End); } void Project::UpdatePieceInfo(PieceInfo* Info) const diff --git a/common/project.h b/common/project.h index b57e8b3a..3edcf0cb 100644 --- a/common/project.h +++ b/common/project.h @@ -82,14 +82,14 @@ public: return mIsPreview; } - bool UsesStreamingMeshes() const + bool DefersModelMeshRequests() const { - return mStreamMeshes; + return mDeferModelMeshRequests; } - void SetStreamMeshes(bool StreamMeshes) + void SetDeferModelMeshRequests(bool Defer) { - mStreamMeshes = StreamMeshes; + mDeferModelMeshRequests = Defer; } bool IsModified() const; @@ -120,7 +120,7 @@ public: bool ImportLDD(const QString& FileName); bool ImportInventory(const std::vector& SetInventory, const QString& Name, const QString& Description); - void SaveImage(const lcImageDialogOptions& Options); + lcResult SaveImage(const lcImageDialogOptions& Options); std::vector GetRequiredPieces() const; lcResult EnsureAssetsReady() const; lcResult ExportCurrentStep(const QString& FileName); @@ -139,12 +139,12 @@ protected: static bool CanShareMergePiece(const PieceInfo* Existing, const PieceInfo* Incoming); QString GetExportFileName(const QString& FileName, const QString& DefaultExtension, const QString& DialogTitle, const QString& DialogFilter) const; - std::vector GetModelParts(); + lcResult> GetModelParts(); void SetFileName(const QString& FileName); bool mIsPreview; // External projects can parse their models before queuing direct meshes. - bool mStreamMeshes = false; + bool mDeferModelMeshRequests = false; bool mModified; QString mFileName; QFileSystemWatcher mFileWatcher;