mirror of
https://github.com/leozide/leocad.git
synced 2026-10-27 06:20:55 +00:00
Propagate parts-list rendering failures with lcResult
A null framebuffer readback was treated as empty geometry, so a parts list could retain a blank entry or silently omit it after rendering failed. The QImage return type also conflated an empty inventory with an error. Return lcResult<QImage> and abort on failed thumbnail readback after framebuffer and renderer cleanup. Report framebuffer creation and image allocation failures explicitly while preserving successful empty inventory and unavailable-geometry behavior. HTML export propagates the error text; the instructions UI clears its stale image and displays a warning parented to the scene's view. Validation: full debug build and whitespace checks passed. A temporary OpenGL fault-injection harness covered first and later readback failures with both missing-parts policies, framebuffer creation failure, successful empty results, and restoration of caller context, surface, and framebuffer bindings. The warning dialog was not exercised interactively.
This commit is contained in:
1 parent
a152d3442c
commit
cd6cf53c7b
4 files changed
+48
-14
No files matched your search
@@ -65,8 +65,19 @@ void lcInstructionsPartsListItem::Update()
|
||||
QFont Font = mInstructions->GetFontProperty(lcInstructionsPropertyType::PLIFont, mModel, mStep);
|
||||
QColor TextColor = mInstructions->GetColorProperty(lcInstructionsPropertyType::PLITextColor, mModel, mStep);
|
||||
|
||||
QImage PartsImage = mModel->GetPartsListImage(300, mStep, lcRGBAFromQColor(BackgroundColor), Font, TextColor, false);
|
||||
setPixmap(QPixmap::fromImage(PartsImage));
|
||||
const lcResult<QImage> PartsImage = mModel->GetPartsListImage(300, mStep, lcRGBAFromQColor(BackgroundColor), Font, TextColor, false);
|
||||
|
||||
if (!PartsImage)
|
||||
{
|
||||
setPixmap(QPixmap());
|
||||
|
||||
QWidget* Parent = scene() && !scene()->views().isEmpty() ? scene()->views().front() : nullptr;
|
||||
QMessageBox::warning(Parent, QObject::tr("LeoCAD"), PartsImage.error());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
setPixmap(QPixmap::fromImage(PartsImage.value()));
|
||||
}
|
||||
|
||||
lcInstructionsPageWidget::lcInstructionsPageWidget(QWidget* Parent, lcInstructions* Instructions, lcInstructionsPropertiesWidget* PropertiesWidget)
|
||||
|
||||
+29
-7
@@ -1764,7 +1764,7 @@ lcResult<QImage> lcModel::RenderStepImageWithReadyAssets(bool Zoom, int Width, i
|
||||
return Image;
|
||||
}
|
||||
|
||||
QImage lcModel::GetPartsListImage(int MaxWidth, lcStep Step, quint32 BackgroundColor, QFont Font, QColor TextColor, bool KeepMissingParts) const
|
||||
lcResult<QImage> lcModel::GetPartsListImage(int MaxWidth, lcStep Step, quint32 BackgroundColor, QFont Font, QColor TextColor, bool KeepMissingParts) const
|
||||
{
|
||||
lcScopedGLContextState GLState;
|
||||
|
||||
@@ -1812,6 +1812,10 @@ QImage lcModel::GetPartsListImage(int MaxWidth, lcStep Step, quint32 BackgroundC
|
||||
if (Images.empty())
|
||||
{
|
||||
QImage Image(1, 1, QImage::Format_ARGB32);
|
||||
|
||||
if (Image.isNull())
|
||||
return lcUnexpected(tr("Could not allocate the pieces list image."));
|
||||
|
||||
Image.fill(lcQColorFromRGBA(BackgroundColor));
|
||||
return Image;
|
||||
}
|
||||
@@ -1835,12 +1839,7 @@ QImage lcModel::GetPartsListImage(int MaxWidth, lcStep Step, quint32 BackgroundC
|
||||
View.SetSize(ThumbnailSize, ThumbnailSize);
|
||||
|
||||
if (!View.BeginRenderToImage(ThumbnailSize, ThumbnailSize))
|
||||
{
|
||||
if (gMainWindow)
|
||||
QMessageBox::warning(gMainWindow, tr("LeoCAD"), tr("Error creating images."));
|
||||
|
||||
return QImage();
|
||||
}
|
||||
return lcUnexpected(tr("Could not create the pieces list framebuffer."));
|
||||
|
||||
float OrthoSize = 200.0f;
|
||||
|
||||
@@ -1879,6 +1878,8 @@ QImage lcModel::GetPartsListImage(int MaxWidth, lcStep Step, quint32 BackgroundC
|
||||
Context->SetDefaultState();
|
||||
Context->SetProjectionMatrix(ProjectionMatrix);
|
||||
|
||||
bool RenderFailed = false;
|
||||
|
||||
for (lcPartsListImage& Image : Images)
|
||||
{
|
||||
View.BindRenderFramebuffer();
|
||||
@@ -1904,11 +1905,20 @@ QImage lcModel::GetPartsListImage(int MaxWidth, lcStep Step, quint32 BackgroundC
|
||||
|
||||
View.UnbindRenderFramebuffer();
|
||||
Image.Thumbnail = View.GetRenderFramebufferImage().convertToFormat(QImage::Format_ARGB32);
|
||||
|
||||
if (Image.Thumbnail.isNull())
|
||||
{
|
||||
RenderFailed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
View.EndRenderToImage();
|
||||
Context->ClearResources();
|
||||
|
||||
if (RenderFailed)
|
||||
return lcUnexpected(tr("Could not read the pieces list image."));
|
||||
|
||||
auto CalculateImageBounds = [](lcPartsListImage& Image)
|
||||
{
|
||||
const QImage& Thumbnail = Image.Thumbnail;
|
||||
@@ -1956,11 +1966,19 @@ QImage lcModel::GetPartsListImage(int MaxWidth, lcStep Step, quint32 BackgroundC
|
||||
if (Images.empty())
|
||||
{
|
||||
QImage Image(1, 1, QImage::Format_ARGB32);
|
||||
|
||||
if (Image.isNull())
|
||||
return lcUnexpected(tr("Could not allocate the pieces list image."));
|
||||
|
||||
Image.fill(lcQColorFromRGBA(BackgroundColor));
|
||||
return Image;
|
||||
}
|
||||
|
||||
QImage DummyImage(16, 16, QImage::Format_ARGB32);
|
||||
|
||||
if (DummyImage.isNull())
|
||||
return lcUnexpected(tr("Could not allocate the pieces list image."));
|
||||
|
||||
QPainter DummyPainter(&DummyImage);
|
||||
|
||||
DummyPainter.setFont(Font);
|
||||
@@ -2010,6 +2028,10 @@ QImage lcModel::GetPartsListImage(int MaxWidth, lcStep Step, quint32 BackgroundC
|
||||
}
|
||||
|
||||
QImage PainterImage(ImageWidth + 40, CurrentHeight + 40, QImage::Format_ARGB32);
|
||||
|
||||
if (PainterImage.isNull())
|
||||
return lcUnexpected(tr("Could not allocate the pieces list image."));
|
||||
|
||||
PainterImage.fill(lcQColorFromRGBA(BackgroundColor));
|
||||
|
||||
QPainter Painter(&PainterImage);
|
||||
|
||||
+2
-1
@@ -332,7 +332,8 @@ public:
|
||||
void GetScene(lcScene* Scene, const lcCamera* ViewCamera, bool AllowHighlight, bool AllowFade) const;
|
||||
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, bool KeepMissingParts) const;
|
||||
// Empty inventory succeeds with a null image; rendering failures return an error.
|
||||
lcResult<QImage> GetPartsListImage(int MaxWidth, lcStep Step, quint32 BackgroundColor, QFont Font, QColor TextColor, bool KeepMissingParts) const;
|
||||
lcResult<void> SaveStepImages(const QString& BaseName, bool AddStepSuffix, bool Zoom, int Width, int Height, lcStep Start, lcStep End);
|
||||
|
||||
void RayTest(lcObjectRayTest& ObjectRayTest) const;
|
||||
|
||||
+4
-4
@@ -2257,16 +2257,16 @@ lcResult<void> Project::ExportHTML(const lcHTMLExportOptions& Options)
|
||||
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, true);
|
||||
const lcResult<QImage> Image = Model->GetPartsListImage(1024, Step, LC_RGBA(255, 255, 255, 0), QFont("Arial", 16, QFont::Bold), Qt::black, true);
|
||||
|
||||
if (Image.isNull())
|
||||
return lcUnexpected(tr("Could not render the pieces list image."));
|
||||
if (!Image)
|
||||
return lcUnexpected(Image.error());
|
||||
|
||||
QString ImageName = BaseName + QLatin1String("-parts.png");
|
||||
QString FileName = QFileInfo(Dir, ImageName).absoluteFilePath();
|
||||
QImageWriter Writer(FileName);
|
||||
|
||||
if (!Writer.write(Image))
|
||||
if (!Writer.write(Image.value()))
|
||||
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.toHtmlEscaped());
|
||||
|
||||
Reference in new issue
Block a user