mirror of
https://github.com/leozide/leocad.git
synced 2026-10-26 22:10:32 +00:00
Require complete geometry for image and 3D exports
Report missing generated meshes and failed image writes, keep thumbnails and interactive geometry using their fallback behavior, and frame command-line images at the requested step or full range. Use explicit mesh loader flags and reject incomplete synthesized meshes. Keep selection and ray tests aligned with the rendered mesh.
This commit is contained in:
1 parent
50d4e15c53
commit
bd6bd50ecc
19 files changed
+363
-131
No files matched your search
@@ -1148,15 +1148,28 @@ lcStartupMode lcApplication::Initialize(const QList<QPair<QString, bool>>& 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<void> 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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<int>(lcZipFileType::Official)])
|
||||
{
|
||||
|
||||
+15
-20
@@ -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<void> Saved = lcGetActiveProject()->SaveImage(Options);
|
||||
|
||||
if (!Saved)
|
||||
QMessageBox::warning(this, tr("LeoCAD"), Saved.error());
|
||||
}
|
||||
|
||||
void lcMainWindow::ShowSelectDialog()
|
||||
|
||||
@@ -380,7 +380,6 @@ protected:
|
||||
const Project* mStatusProject = nullptr;
|
||||
std::unordered_set<const PieceInfo*> mStatusRequiredAssets;
|
||||
bool mAssetUpdateScheduled = false;
|
||||
bool mHadPendingAssets = false;
|
||||
|
||||
QMenu* mTransformMenu = nullptr;
|
||||
QMenu* mToolsMenu = nullptr;
|
||||
|
||||
+21
-10
@@ -1052,7 +1052,7 @@ bool lcLibraryMeshData::WriteSections(lcMesh* Mesh, const std::vector<lcMeshLoad
|
||||
if (TextureLookup)
|
||||
DstSection.Texture = TextureLookup(FinalSection.Name);
|
||||
else if (mMeshLoader)
|
||||
DstSection.Texture = lcGetPiecesLibrary()->FindTexture(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;
|
||||
}
|
||||
|
||||
|
||||
+14
-3
@@ -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<lcMeshLoaderTextureMap> mTextureStack;
|
||||
|
||||
lcLibraryMeshData& mMeshData;
|
||||
bool mOptimize;
|
||||
lcMeshLoaderFlags mFlags;
|
||||
};
|
||||
+79
-31
@@ -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<QImage> 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<QImage> 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<void> lcModel::SaveStepImages(const QString& BaseName, bool AddStepSuffix, bool Zoom, int Width, int Height, lcStep Start, lcStep End)
|
||||
{
|
||||
const lcResult<void> 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<QImage> 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>();
|
||||
}
|
||||
|
||||
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<lcPiece>& 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())
|
||||
|
||||
+4
-1
@@ -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<void> 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<QImage> RenderStepImageWithReadyAssets(bool Zoom, int Width, int Height, lcStep Step, lcCamera* OutputCamera);
|
||||
|
||||
void DeleteModel();
|
||||
|
||||
void AddSelectionHistory(std::function<void()> Callback);
|
||||
|
||||
@@ -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;
|
||||
|
||||
+2
-2
@@ -1526,8 +1526,8 @@ lcMesh* lcSynthInfo::CreateMesh(const std::vector<lcPieceControlPoint>& 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;
|
||||
|
||||
@@ -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())
|
||||
|
||||
+19
-8
@@ -734,7 +734,9 @@ std::vector<QImage> 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<QImage> 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<QImage> lcView::GetStepImages(lcStep Start, lcStep End)
|
||||
return Images;
|
||||
}
|
||||
|
||||
void lcView::SaveStepImages(const QString& BaseName, bool AddStepSuffix, lcStep Start, lcStep End, std::function<void(const QString&)> ProgressCallback)
|
||||
lcResult<void> lcView::SaveStepImages(const QString& BaseName, bool AddStepSuffix, lcStep Start, lcStep End, std::function<void(const QString&)> ProgressCallback)
|
||||
{
|
||||
std::vector<QImage> Images = GetStepImages(Start, End);
|
||||
|
||||
if (Images.size() != static_cast<size_t>(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<void>();
|
||||
}
|
||||
|
||||
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<bool>(mRenderFramebuffer));
|
||||
mScene->SetRequireGeneratedMeshes(mRenderFramebuffer && mViewType != lcViewType::PartsList);
|
||||
mScene->SetLODDistance(Preferences.mMeshLODDistance);
|
||||
|
||||
mScene->Begin(mCamera->mWorldView);
|
||||
|
||||
+2
-1
@@ -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<QImage> GetStepImages(lcStep Start, lcStep End);
|
||||
void SaveStepImages(const QString& BaseName, bool AddStepSuffix, lcStep Start, lcStep End, std::function<void(const QString&)> ProgressCallback);
|
||||
lcResult<void> SaveStepImages(const QString& BaseName, bool AddStepSuffix, lcStep Start, lcStep End, std::function<void(const QString&)> ProgressCallback);
|
||||
|
||||
lcContext* mContext = nullptr;
|
||||
|
||||
|
||||
+70
-17
@@ -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<lcPiece*>(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<lcPiece*>(this);
|
||||
ObjectRayTest.ObjectSection.Section = LC_PIECE_SECTION_POSITION;
|
||||
ObjectRayTest.PieceInfoRayTest.Transform = lcMul(ObjectRayTest.PieceInfoRayTest.Transform, mModelWorld);
|
||||
}
|
||||
ObjectRayTest.ObjectSection.Object = const_cast<lcPiece*>(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<lcPiece*>(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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
+83
-22
@@ -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<lcSetInventoryItem>& SetInventor
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<lcModelPartsEntry> Project::GetModelParts()
|
||||
lcResult<std::vector<lcModelPartsEntry>> Project::GetModelParts()
|
||||
{
|
||||
std::vector<lcModelPartsEntry> ModelParts;
|
||||
|
||||
@@ -1132,7 +1132,13 @@ std::vector<lcModelPartsEntry> 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<PieceInfo*> Project::GetRequiredPieces() const
|
||||
@@ -1304,7 +1310,12 @@ lcResult<void> Project::Export3DStudio(const QString& FileName)
|
||||
if (!Ready)
|
||||
return Ready;
|
||||
|
||||
std::vector<lcModelPartsEntry> ModelParts = GetModelParts();
|
||||
const lcResult<std::vector<lcModelPartsEntry>> ModelPartsResult = GetModelParts();
|
||||
|
||||
if (!ModelPartsResult)
|
||||
return lcUnexpected(ModelPartsResult.error());
|
||||
|
||||
const std::vector<lcModelPartsEntry>& ModelParts = ModelPartsResult.value();
|
||||
|
||||
if (ModelParts.empty())
|
||||
return lcUnexpected(tr("Nothing to export."));
|
||||
@@ -1762,7 +1773,12 @@ lcResult<void> Project::ExportCOLLADA(const QString& FileName)
|
||||
if (!Ready)
|
||||
return Ready;
|
||||
|
||||
std::vector<lcModelPartsEntry> ModelParts = GetModelParts();
|
||||
const lcResult<std::vector<lcModelPartsEntry>> ModelPartsResult = GetModelParts();
|
||||
|
||||
if (!ModelPartsResult)
|
||||
return lcUnexpected(ModelPartsResult.error());
|
||||
|
||||
const std::vector<lcModelPartsEntry>& ModelParts = ModelPartsResult.value();
|
||||
|
||||
if (ModelParts.empty())
|
||||
return lcUnexpected(tr("Nothing to export."));
|
||||
@@ -2104,19 +2120,33 @@ lcResult<void> 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<void>
|
||||
{
|
||||
lcPartsList PartsList;
|
||||
|
||||
if (Step == 0)
|
||||
Model->GetPartsList(gDefaultColor, true, false, PartsList);
|
||||
else
|
||||
Model->GetPartsListForStep(Step, gDefaultColor, PartsList, false);
|
||||
|
||||
if (PartsList.empty())
|
||||
return lcResult<void>();
|
||||
|
||||
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("<p><IMG SRC=\"%1\" /></p><br><br>\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("<p><IMG SRC=\"%1\" /></p><br><br>\r\n").arg(ImageName);
|
||||
|
||||
return lcResult<void>();
|
||||
};
|
||||
|
||||
for (lcModel* Model : Models)
|
||||
@@ -2152,11 +2182,21 @@ lcResult<void> Project::ExportHTML(const lcHTMLExportOptions& Options)
|
||||
Stream << QString::fromLatin1("<p><IMG SRC=\"%1-%2.png\" ALT=\"Step %3\" WIDTH=%4 HEIGHT=%5></p><BR><BR>\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<void> 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<void> PartsImageSaved = AddPartsListImage(Stream, Model, 0, BaseName);
|
||||
|
||||
if (!PartsImageSaved)
|
||||
return PartsImageSaved;
|
||||
}
|
||||
|
||||
Stream << QLatin1String("</CENTER>\r\n<BR><HR><BR><B><I>Created by <A HREF=\"https://www.leocad.org\">LeoCAD</A></I></B><BR></HTML>\r\n");
|
||||
}
|
||||
@@ -2198,7 +2238,12 @@ lcResult<void> Project::ExportHTML(const lcHTMLExportOptions& Options)
|
||||
Stream << QString::fromLatin1("<IMG SRC=\"%1-%2.png\" ALT=\"Step %3\" WIDTH=%4 HEIGHT=%5><BR><BR>\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<void> PartsImageSaved = AddPartsListImage(Stream, Model, Step, QString("%1-%2").arg(BaseName, StepString));
|
||||
|
||||
if (!PartsImageSaved)
|
||||
return PartsImageSaved;
|
||||
}
|
||||
|
||||
Stream << QLatin1String("</CENTER>\r\n<BR><HR><BR>");
|
||||
if (Step != 1)
|
||||
@@ -2227,7 +2272,10 @@ lcResult<void> Project::ExportHTML(const lcHTMLExportOptions& Options)
|
||||
|
||||
Stream << QString::fromLatin1("<HTML>\r\n<HEAD>\r\n<TITLE>Pieces used by %1</TITLE>\r\n</HEAD>\r\n<BR>\r\n<CENTER>\n").arg(PageTitle);
|
||||
|
||||
AddPartsListImage(Stream, Model, 0, BaseName);
|
||||
const lcResult<void> PartsImageSaved = AddPartsListImage(Stream, Model, 0, BaseName);
|
||||
|
||||
if (!PartsImageSaved)
|
||||
return PartsImageSaved;
|
||||
|
||||
Stream << QLatin1String("</CENTER>\r\n<BR><HR><BR>");
|
||||
Stream << QString::fromLatin1("<A HREF=\"%1-%2.html\">Previous</A> ").arg(BaseName, QString("%1").arg(LastStep, 2, 10, QLatin1Char('0')));
|
||||
@@ -2240,7 +2288,10 @@ lcResult<void> 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<void> 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<void> Project::ExportPOVRay(const QString& FileName)
|
||||
if (!Ready)
|
||||
return Ready;
|
||||
|
||||
std::vector<lcModelPartsEntry> ModelParts = GetModelParts();
|
||||
const lcResult<std::vector<lcModelPartsEntry>> ModelPartsResult = GetModelParts();
|
||||
|
||||
if (!ModelPartsResult)
|
||||
return lcUnexpected(ModelPartsResult.error());
|
||||
|
||||
const std::vector<lcModelPartsEntry>& ModelParts = ModelPartsResult.value();
|
||||
|
||||
if (ModelParts.empty())
|
||||
return lcUnexpected(tr("Nothing to export."));
|
||||
@@ -2989,7 +3045,12 @@ lcResult<void> Project::ExportWavefront(const QString& FileName)
|
||||
if (!Ready)
|
||||
return Ready;
|
||||
|
||||
std::vector<lcModelPartsEntry> ModelParts = GetModelParts();
|
||||
const lcResult<std::vector<lcModelPartsEntry>> ModelPartsResult = GetModelParts();
|
||||
|
||||
if (!ModelPartsResult)
|
||||
return lcUnexpected(ModelPartsResult.error());
|
||||
|
||||
const std::vector<lcModelPartsEntry>& ModelParts = ModelPartsResult.value();
|
||||
|
||||
if (ModelParts.empty())
|
||||
return lcUnexpected(tr("Nothing to export."));
|
||||
@@ -3089,7 +3150,7 @@ lcResult<void> Project::ExportWavefront(const QString& FileName)
|
||||
return lcResult<void>();
|
||||
}
|
||||
|
||||
void Project::SaveImage(const lcImageDialogOptions& Options)
|
||||
lcResult<void> 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
|
||||
|
||||
+7
-7
@@ -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<lcSetInventoryItem>& SetInventory, const QString& Name, const QString& Description);
|
||||
|
||||
void SaveImage(const lcImageDialogOptions& Options);
|
||||
lcResult<void> SaveImage(const lcImageDialogOptions& Options);
|
||||
std::vector<PieceInfo*> GetRequiredPieces() const;
|
||||
lcResult<void> EnsureAssetsReady() const;
|
||||
lcResult<void> 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<lcModelPartsEntry> GetModelParts();
|
||||
lcResult<std::vector<lcModelPartsEntry>> 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;
|
||||
|
||||
Reference in new issue
Block a user