Files
leocad/common/lc_previewwidget.cpp
T
Leonardo Zide 61be531444 Manage piece data ownership with scoped asset references
Replace manual piece-data retain/release bookkeeping with shared copyable
asset-reference handles. Pieces, models, selection, minifigs, thumbnails,
and stored insertion previews retain their dependencies explicitly, while
transient queries continue to borrow pointers.

Separate registered lookup and acquisition from asynchronous load requests
and explicit readiness waits. Keep loader-held references through work and
notification delivery, resetting shared request handles on the UI thread
and guarding callback-triggered library teardown.

Validated with the Qt 6.10 debug build and 26 offscreen lifecycle checks,
including cancellation, stale requests, copy/move operations, model/preview
teardown, undo, project merge/export, and loader shutdown.
2026-10-19 10:02:26 -07:00

380 lines
8.9 KiB
C++

#include "lc_global.h"
#include "lc_previewwidget.h"
#include "lc_piecedata.h"
#include "piece.h"
#include "project.h"
#include "lc_model.h"
#include "lc_library.h"
#include "lc_application.h"
#include "lc_viewwidget.h"
#include "lc_view.h"
lcPreviewDockWidget::lcPreviewDockWidget(QMainWindow* Parent)
: QMainWindow(Parent)
{
mPreview = new lcPreview();
mViewWidget = new lcViewWidget(nullptr, mPreview);
setCentralWidget(mViewWidget);
setMinimumSize(200, 200);
mLockAction = new QAction(QIcon(":/resources/action_preview_unlocked.png"),tr("Lock Preview"), this);
mLockAction->setCheckable(true);
mLockAction->setChecked(false);
mLockAction->setShortcut(tr("Ctrl+L"));
connect(mLockAction, &QAction::triggered, this, &lcPreviewDockWidget::SetPreviewLock);
SetPreviewLock();
mLabel = new QLabel();
lcPiecesLibrary* Library = lcGetPiecesLibrary();
connect(Library, &lcPiecesLibrary::PartLoaded, this, [this](lcPieceData*)
{
if (mAssetRefreshPending)
RefreshPendingAssets();
});
connect(Library, &lcPiecesLibrary::PartLoadFailed, this, [this](lcPieceData*, const QString&)
{
if (mAssetRefreshPending)
RefreshPendingAssets();
});
connect(Library, &lcPiecesLibrary::GeneratedMeshSettled, this, [this](lcPieceData*)
{
if (mAssetRefreshPending)
RefreshPendingAssets();
});
mToolBar = addToolBar(tr("Toolbar"));
mToolBar->setObjectName("Toolbar");
mToolBar->setStatusTip(tr("Preview Toolbar"));
mToolBar->setMovable(false);
mToolBar->addAction(mLockAction);
mToolBar->addSeparator();
mToolBar->addWidget(mLabel);
if (mToolBar->isHidden())
mToolBar->show();
}
bool lcPreviewDockWidget::SetCurrentPiece(lcPieceData* PieceData, int ColorCode)
{
if (mLockAction->isChecked())
return true;
mAssetRefreshPending = false;
mRefitAfterAssetLoad = true;
mLabel->setText(tr("Loading..."));
if (mPreview->SetCurrentPiece(PieceData, ColorCode))
{
RefreshPendingAssets();
return true;
}
mViewWidget->show();
mLabel->setText(tr("Preview unavailable"));
return false;
}
void lcPreviewDockWidget::UpdatePreview()
{
mAssetRefreshPending = false;
mRefitAfterAssetLoad = true;
mPreview->UpdatePreview();
RefreshPendingAssets();
}
void lcPreviewDockWidget::RefreshDescription()
{
mPreview->RefreshDescription();
mLabel->setText(mPreview->GetDescription());
}
void lcPreviewDockWidget::RefreshModel(const lcModel* Model)
{
if (mLockAction->isChecked() || mRefreshingModel)
return;
const std::vector<lcPieceDataRef> RequiredPieceData = mPreview->GetModel()->GetRequiredPieceData();
if (std::find(RequiredPieceData.begin(), RequiredPieceData.end(), Model->GetPieceData()) == RequiredPieceData.end())
return;
mRefreshingModel = true;
if (!mAssetRefreshPending)
mRefitAfterAssetLoad = false;
lcPiecesLibrary* Library = lcGetPiecesLibrary();
for (const lcPieceDataRef& RequiredData : RequiredPieceData)
{
if (RequiredData->mState != lcAssetState::Unloaded)
continue;
Library->RequestPieceDataLoad(RequiredData.Get(), lcLoadPriority::Normal);
}
std::vector<lcModel*> UpdatedModels;
mPreview->GetModel()->UpdatePieceData(UpdatedModels);
RefreshPendingAssets();
mRefreshingModel = false;
}
void lcPreviewDockWidget::RefreshAfterAssetChange(const std::vector<lcPieceData*>& ChangedPieces)
{
if (mPreview->GetModel()->GetPieces().empty())
return;
const std::vector<lcPieceDataRef> RequiredPieceData = mPreview->GetModel()->GetRequiredPieceData();
bool Affected = std::any_of(RequiredPieceData.begin(), RequiredPieceData.end(), [&ChangedPieces](const lcPieceDataRef& RequiredData)
{
return std::find(ChangedPieces.begin(), ChangedPieces.end(), RequiredData) != ChangedPieces.end();
});
if (!Affected)
{
for (const std::unique_ptr<lcPiece>& Piece : mPreview->GetModel()->GetPieces())
{
if (std::find(ChangedPieces.begin(), ChangedPieces.end(), Piece->mPieceData) != ChangedPieces.end())
{
Affected = true;
break;
}
}
}
if (!Affected)
return;
RefreshPendingAssets();
}
void lcPreviewDockWidget::RefreshPendingAssets()
{
const std::vector<lcPieceDataRef> RequiredPieceData = mPreview->GetModel()->GetRequiredPieceData();
for (const lcPieceDataRef& RequiredData : RequiredPieceData)
{
if (RequiredData->mState == lcAssetState::Failed || RequiredData->mState == lcAssetState::Unloaded)
{
mAssetRefreshPending = false;
mViewWidget->hide();
mLabel->setText(tr("Preview unavailable"));
return;
}
if (RequiredData->mState != lcAssetState::Loaded)
{
mAssetRefreshPending = true;
mViewWidget->hide();
mLabel->setText(tr("Loading..."));
return;
}
}
for (const lcPiece* Piece : mPreview->GetModel()->GetRequiredSynthPieces())
{
if (Piece->IsGeneratedMeshPending())
{
mAssetRefreshPending = true;
mViewWidget->hide();
mLabel->setText(tr("Loading..."));
return;
}
if (!Piece->HasGeneratedMesh())
{
mAssetRefreshPending = false;
mViewWidget->hide();
mLabel->setText(tr("Preview unavailable"));
return;
}
}
if (mAssetRefreshPending && mRefitAfterAssetLoad)
mPreview->ZoomExtents(lcGeometryBoundsMode::IncludeFallback, false);
mAssetRefreshPending = false;
mRefitAfterAssetLoad = true;
mViewWidget->show();
mPreview->RefreshDescription();
mPreview->Redraw();
mLabel->setText(mPreview->GetDescription());
}
void lcPreviewDockWidget::RebindPieceData(lcPieceData* Previous, lcPieceData* Replacement)
{
int ColorCode = -1;
for (const std::unique_ptr<lcPiece>& Piece : mPreview->GetModel()->GetPieces())
{
if (Piece->mPieceData == Previous)
{
ColorCode = Piece->GetColorCode();
break;
}
}
if (ColorCode == -1)
return;
mAssetRefreshPending = false;
mRefitAfterAssetLoad = true;
if (mPreview->SetCurrentPiece(Replacement, ColorCode))
RefreshPendingAssets();
else
{
mViewWidget->show();
mLabel->setText(tr("Preview unavailable"));
}
}
void lcPreviewDockWidget::ClearPreview()
{
mAssetRefreshPending = false;
mRefitAfterAssetLoad = true;
mViewWidget->show();
if (mPreview->GetModel()->GetPieces().size())
mPreview->ClearPreview();
mLabel->setText(QString());
}
void lcPreviewDockWidget::SetPreviewLock()
{
bool Locked = mLockAction->isChecked();
if (Locked && mPreview->GetModel()->GetPieces().empty())
{
mLockAction->setChecked(false);
return;
}
QIcon LockIcon(Locked ? ":/resources/action_preview_locked.png" : ":/resources/action_preview_unlocked.png");
QString StatusTip(Locked
? tr("Unlock the preview display to enable updates")
: tr("Lock the preview display to disable updates"));
mLockAction->setToolTip(Locked ? tr("Unlock Preview") : tr("Lock Preview"));
mLockAction->setIcon(LockIcon);
mLockAction->setStatusTip(StatusTip);
}
lcPreview::lcPreview()
: lcView(lcViewType::Preview, nullptr), mLoader(new Project(true))
{
mLoader->SetActiveModel(0, false);
mModel = mLoader->GetActiveModel();
}
bool lcPreview::SetCurrentPiece(lcPieceData* PieceData, int ColorCode)
{
if (!PieceData)
{
ClearPreview();
return false;
}
for (const std::unique_ptr<lcPiece>& ModelPiece : mModel->GetPieces())
{
if (PieceData == ModelPiece->mPieceData)
{
int ModelColorCode = ModelPiece->GetColorCode();
if (ModelColorCode == ColorCode)
{
RefreshDescription();
return true;
}
}
}
lcPiecesLibrary* Library = lcGetPiecesLibrary();
const lcPieceDataRef PieceDataRef(PieceData);
bool Ready = Library->WaitForPieceData(PieceDataRef);
if (Ready && PieceData->IsModel())
Ready = static_cast<bool>(PieceData->GetModel()->EnsureAssetsReady());
else if (Ready && PieceData->IsProject())
Ready = static_cast<bool>(PieceData->GetProject()->EnsureAssetsReady());
if (!Ready)
{
ClearPreview();
return false;
}
mIsModel = PieceData->IsModel();
mDescription = PieceData->m_strDescription;
mModel->SelectAllPiecesAction();
mModel->DeleteSelectedObjects();
mModel->SetPreviewPieceData(PieceData, lcGetColorIndex(ColorCode));
std::vector<lcModel*> UpdatedModels;
mModel->UpdatePieceData(UpdatedModels);
ZoomExtents(lcGeometryBoundsMode::IncludeFallback, true);
return true;
}
void lcPreview::ClearPreview()
{
mDescription.clear();
mIsModel = false;
mLoader = std::unique_ptr<Project>(new Project(true/*IsPreview*/));
mLoader->SetActiveModel(0, false);
mModel = mLoader->GetActiveModel();
lcPiecesLibrary* Library = lcGetPiecesLibrary();
Library->RemoveTemporaryPieceData();
Library->UnloadUnusedParts();
Redraw();
}
void lcPreview::UpdatePreview()
{
lcPieceData* PieceData = nullptr;
int ColorCode = -1;
for (const std::unique_ptr<lcPiece>& ModelPiece : mModel->GetPieces())
{
if (ModelPiece->mPieceData)
{
PieceData = ModelPiece->mPieceData.Get();
ColorCode = ModelPiece->GetColorCode();
break;
}
}
lcPieceDataRef PreviousPieceData(PieceData);
ClearPreview();
if (PieceData && ColorCode > -1)
SetCurrentPiece(PieceData, ColorCode);
}
void lcPreview::RefreshDescription()
{
for (const std::unique_ptr<lcPiece>& Piece : mModel->GetPieces())
{
if (!Piece->mPieceData)
continue;
mDescription = QString::fromLatin1(Piece->mPieceData->m_strDescription);
return;
}
mDescription.clear();
}