Files
leocad/common/minifig.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

517 lines
15 KiB
C++

#include "lc_global.h"
#include "minifig.h"
#include "lc_colors.h"
#include "lc_piecedata.h"
#include "lc_model.h"
#include "lc_library.h"
#include "lc_application.h"
#include "lc_file.h"
#include "lc_profile.h"
const char* MinifigWizard::mSectionNames[LC_MFW_NUMITEMS] =
{
"HATS", // LC_MFW_HATS
"HATS2", // LC_MFW_HATS2
"HEAD", // LC_MFW_HEAD
"NECK", // LC_MFW_NECK
"BODY", // LC_MFW_BODY
"BODY2", // LC_MFW_BODY2
"BODY3", // LC_MFW_BODY3
"RARM", // LC_MFW_RARM
"LARM", // LC_MFW_LARM
"RHAND", // LC_MFW_RHAND
"LHAND", // LC_MFW_LHAND
"RHANDA", // LC_MFW_RHANDA
"LHANDA", // LC_MFW_LHANDA
"RLEG", // LC_MFW_RLEG
"LLEG", // LC_MFW_LLEG
"RLEGA", // LC_MFW_RLEGA
"LLEGA", // LC_MFW_LLEGA
};
MinifigWizard::MinifigWizard()
: mModel(new lcModel(QString(), nullptr, false))
{
LoadSettings();
LoadTemplates();
lcMinifigTemplate& Template = mTemplates[GetDefaultTemplateName()];
const int ColorCodes[LC_MFW_NUMITEMS] = { 4, 7, 14, 7, 1, 0, 7, 4, 4, 14, 14, 7, 7, 0, 0, 7, 7 };
const char* const Pieces[LC_MFW_NUMITEMS] = { "3624.dat", "", "3626bp01.dat", "", "973.dat", "3815.dat", "", "3819.dat", "3818.dat", "3820.dat", "3820.dat", "", "", "3817.dat", "3816.dat", "", "" };
for (int PartIndex = 0; PartIndex < LC_MFW_NUMITEMS; PartIndex++)
{
Template.Parts[PartIndex] = Pieces[PartIndex];
Template.ColorCodes[PartIndex] = ColorCodes[PartIndex];
Template.Angles[PartIndex] = 0.0f;
}
for (int PartIndex = 0; PartIndex < LC_MFW_NUMITEMS; PartIndex++)
{
mMinifig.ColorIndices[PartIndex] = gDefaultColor;
mMinifig.Angles[PartIndex] = 0.0f;
mMinifig.Matrices[PartIndex] = lcMatrix44Identity();
}
}
MinifigWizard::~MinifigWizard()
{
SaveTemplates();
}
QString MinifigWizard::GetDefaultTemplateName()
{
return QCoreApplication::translate("MinifigWizard", "Default");
}
void MinifigWizard::LoadSettings()
{
QString CustomSettingsPath = lcGetProfileString(LC_PROFILE_MINIFIG_SETTINGS);
if (!CustomSettingsPath.isEmpty())
{
lcDiskFile DiskSettings(CustomSettingsPath);
if (DiskSettings.Open(QIODevice::ReadOnly))
{
ParseSettings(DiskSettings);
return;
}
}
lcDiskFile MinifigFile(":/resources/minifig.ini");
if (MinifigFile.Open(QIODevice::ReadOnly))
ParseSettings(MinifigFile);
}
void MinifigWizard::ParseSettings(lcFile& Settings)
{
for (int SectionIndex = 0; SectionIndex < LC_MFW_NUMITEMS; SectionIndex++)
{
std::vector<lcMinifigPieceInfo>& InfoArray = mSettings[SectionIndex];
InfoArray.clear();
Settings.Seek(0, SEEK_SET);
char Line[1024];
bool FoundSection = false;
const char* SectionName = mSectionNames[SectionIndex];
const size_t SectionNameLength = strlen(SectionName);
while (Settings.ReadLine(Line, sizeof(Line)))
{
if (Line[0] == '[' && !strncmp(Line + 1, SectionName, SectionNameLength) && Line[SectionNameLength + 1] == ']')
{
FoundSection = true;
break;
}
}
if (!FoundSection)
{
lcMinifigPieceInfo MinifigInfo;
strncpy(MinifigInfo.Description, "None", sizeof(MinifigInfo.Description));
MinifigInfo.Description[sizeof(MinifigInfo.Description)-1] = 0;
MinifigInfo.Offset = lcMatrix44Identity();
MinifigInfo.PieceData = nullptr;
InfoArray.emplace_back(std::move(MinifigInfo));
continue;
}
while (Settings.ReadLine(Line, sizeof(Line)))
{
if (Line[0] == '[')
break;
char* DescriptionStart = strchr(Line, '"');
if (!DescriptionStart)
continue;
DescriptionStart++;
char* DescriptionEnd = strchr(DescriptionStart, '"');
if (!DescriptionEnd)
continue;
*DescriptionEnd = 0;
DescriptionEnd++;
char* NameStart = strchr(DescriptionEnd, '"');
if (!NameStart)
continue;
NameStart++;
char* NameEnd = strchr(NameStart, '"');
if (!NameEnd)
continue;
*NameEnd = 0;
NameEnd++;
lcPieceData* PieceData = lcGetPiecesLibrary()->FindPieceData(NameStart, nullptr);
if (!PieceData && *NameStart)
continue;
float Mat[12];
int Flags;
if (sscanf(NameEnd, "%d %g %g %g %g %g %g %g %g %g %g %g %g",
&Flags, &Mat[0], &Mat[1], &Mat[2], &Mat[3], &Mat[4], &Mat[5], &Mat[6],
&Mat[7], &Mat[8], &Mat[9], &Mat[10], &Mat[11]) != 13)
continue;
lcMatrix44 Offset = lcMatrix44Identity();
float* OffsetMatrix = &Offset[0][0];
OffsetMatrix[0] = Mat[0];
OffsetMatrix[8] = -Mat[1];
OffsetMatrix[4] = Mat[2];
OffsetMatrix[2] = -Mat[3];
OffsetMatrix[10] = Mat[4];
OffsetMatrix[6] = -Mat[5];
OffsetMatrix[1] = Mat[6];
OffsetMatrix[9] = -Mat[7];
OffsetMatrix[5] = Mat[8];
OffsetMatrix[12] = Mat[9];
OffsetMatrix[14] = -Mat[10];
OffsetMatrix[13] = Mat[11];
lcMinifigPieceInfo MinifigInfo;
strncpy(MinifigInfo.Description, DescriptionStart, sizeof(MinifigInfo.Description));
MinifigInfo.Description[sizeof(MinifigInfo.Description)-1] = 0;
MinifigInfo.Offset = Offset;
MinifigInfo.PieceData = PieceData;
InfoArray.emplace_back(std::move(MinifigInfo));
}
}
}
void MinifigWizard::LoadTemplate(const QString& TemplateName)
{
const std::map<QString, lcMinifigTemplate>::iterator& TemplateIt = mTemplates.find(TemplateName);
if (TemplateIt == mTemplates.end())
return;
const lcMinifigTemplate& Template = TemplateIt->second;
lcPiecesLibrary* Library = lcGetPiecesLibrary();
for (int PartIndex = 0; PartIndex < LC_MFW_NUMITEMS; PartIndex++)
{
mMinifig.ColorIndices[PartIndex] = lcGetColorIndex(Template.ColorCodes[PartIndex]);
mMinifig.Angles[PartIndex] = Template.Angles[PartIndex];
mMinifig.Matrices[PartIndex] = lcMatrix44Identity();
mMinifig.PieceData[PartIndex] = Library->AcquirePieceData(Template.Parts[PartIndex].toLatin1(), nullptr, false, false);
if (mMinifig.PieceData[PartIndex])
Library->RequestPieceDataLoad(mMinifig.PieceData[PartIndex].Get(), lcLoadPriority::Normal);
}
std::vector<lcPieceDataRef> RequiredPieceData;
for (const lcPieceDataRef& PieceData : mMinifig.PieceData)
if (PieceData)
RequiredPieceData.push_back(PieceData);
Library->WaitForPieceData(RequiredPieceData);
Calculate();
}
void MinifigWizard::SaveTemplate(const QString& TemplateName, const lcMinifigTemplate& Template)
{
mTemplates[TemplateName] = Template;
}
void MinifigWizard::DeleteTemplate(const QString& TemplateName)
{
mTemplates.erase(TemplateName);
}
void MinifigWizard::AddTemplatesJson(const QByteArray& TemplateData)
{
QJsonDocument Document = QJsonDocument::fromJson(TemplateData);
QJsonObject RootObject = Document.object();
int Version = RootObject["Version"].toInt(0);
QJsonObject TemplatesObject;
if (Version > 0)
TemplatesObject = RootObject["Templates"].toObject();
else
TemplatesObject = RootObject;
for (QJsonObject::const_iterator ElementIt = TemplatesObject.constBegin(); ElementIt != TemplatesObject.constEnd(); ElementIt++)
{
if (!ElementIt.value().isObject())
continue;
QJsonObject TemplateObject = ElementIt.value().toObject();
lcMinifigTemplate Template;
for (int PartIdx = 0; PartIdx < LC_MFW_NUMITEMS; PartIdx++)
{
QJsonObject PartObject = TemplateObject.value(QLatin1String(mSectionNames[PartIdx])).toObject();
Template.Parts[PartIdx] = PartObject["Id"].toString();
Template.ColorCodes[PartIdx] = PartObject["Color"].toInt();
Template.Angles[PartIdx] = PartObject["Angle"].toDouble();
}
mTemplates.emplace(ElementIt.key(), std::move(Template));
}
}
QByteArray MinifigWizard::GetTemplatesJson() const
{
QJsonObject TemplatesObject;
for (const auto& TemplateEntry : mTemplates)
{
const lcMinifigTemplate& Template = TemplateEntry.second;
QJsonObject TemplateObject;
for (int PartIdx = 0; PartIdx < LC_MFW_NUMITEMS; PartIdx++)
{
QJsonObject PartObject;
PartObject["Id"] = Template.Parts[PartIdx];
PartObject["Color"] = Template.ColorCodes[PartIdx];
PartObject["Angle"] = Template.Angles[PartIdx];
TemplateObject[QLatin1String(mSectionNames[PartIdx])] = PartObject;
}
TemplatesObject[TemplateEntry.first] = TemplateObject;
}
QJsonObject RootObject;
RootObject["Templates"] = TemplatesObject;
RootObject["Version"] = 1;
return QJsonDocument(RootObject).toJson();
}
void MinifigWizard::LoadTemplates()
{
mTemplates.clear();
QSettings Settings;
Settings.beginGroup("Minifig");
QByteArray TemplateData = Settings.value("Templates").toByteArray();
AddTemplatesJson(TemplateData);
}
void MinifigWizard::SaveTemplates()
{
QSettings Settings;
Settings.beginGroup("Minifig");
Settings.setValue("Templates", GetTemplatesJson());
}
void MinifigWizard::Calculate()
{
float HeadOffset = 0.0f;
lcMatrix44 Root, Mat, Mat2;
const lcPieceDataRef* PieceData = mMinifig.PieceData;
const float* Angles = mMinifig.Angles;
lcMatrix44* Matrices = mMinifig.Matrices;
const bool DroidTorso = PieceData[LC_MFW_BODY] && !qstricmp(PieceData[LC_MFW_BODY]->mFileName, "30375.dat");
const bool SkeletonTorso = PieceData[LC_MFW_BODY] && !qstricmp(PieceData[LC_MFW_BODY]->mFileName, "6260.dat");
if (PieceData[LC_MFW_BODY3])
Root = lcMatrix44Translation(lcVector3(0, 0, 74.0f));
else
Root = lcMatrix44Translation(lcVector3(0, 0, 72.0f));
Matrices[LC_MFW_BODY] = lcMul(mSettings[LC_MFW_BODY][GetSelectionIndex(LC_MFW_BODY)].Offset, Root);
if (PieceData[LC_MFW_NECK])
{
Matrices[LC_MFW_NECK] = lcMul(mSettings[LC_MFW_NECK][GetSelectionIndex(LC_MFW_NECK)].Offset, Root);
HeadOffset = 0.08f;
}
if (PieceData[LC_MFW_HEAD])
{
Mat = lcMatrix44RotationZ(-LC_DTOR * Angles[LC_MFW_HEAD]);
Mat.SetTranslation(lcVector3(0.0f, 0.0f, 24.0f + HeadOffset));
Mat = lcMul(mSettings[LC_MFW_HEAD][GetSelectionIndex(LC_MFW_HEAD)].Offset, Mat);
Matrices[LC_MFW_HEAD] = lcMul(Mat, Root);
}
if (PieceData[LC_MFW_HATS])
{
Mat = lcMatrix44RotationZ(-LC_DTOR * Angles[LC_MFW_HATS]);
Mat = lcMul(mSettings[LC_MFW_HATS][GetSelectionIndex(LC_MFW_HATS)].Offset, Mat);
Matrices[LC_MFW_HATS] = lcMul(Mat, Matrices[LC_MFW_HEAD]);
}
if (PieceData[LC_MFW_HATS2])
{
Mat = lcMatrix44RotationX(-LC_DTOR * Angles[LC_MFW_HATS2]);
Mat = lcMul(mSettings[LC_MFW_HATS2][GetSelectionIndex(LC_MFW_HATS2)].Offset, Mat);
Matrices[LC_MFW_HATS2] = lcMul(Mat, Matrices[LC_MFW_HATS]);
}
if (PieceData[LC_MFW_RARM])
{
Mat = lcMatrix44RotationX(-LC_DTOR * Angles[LC_MFW_RARM]);
if (DroidTorso || SkeletonTorso)
Mat2 = lcMatrix44Identity();
else
Mat2 = lcMatrix44RotationY(-LC_DTOR * 9.791f);
Mat2.SetTranslation(lcVector3(15.552f, 0, -8.88f));
Mat = lcMul(mSettings[LC_MFW_RARM][GetSelectionIndex(LC_MFW_RARM)].Offset, Mat);
Mat = lcMul(Mat, Mat2);
Matrices[LC_MFW_RARM] = lcMul(Mat, Root);
}
if (PieceData[LC_MFW_RHAND])
{
Mat = lcMatrix44RotationY(-LC_DTOR * Angles[LC_MFW_RHAND]);
Mat2 = lcMatrix44RotationX(LC_DTOR * 45);
Mat = lcMul(mSettings[LC_MFW_RHAND][GetSelectionIndex(LC_MFW_RHAND)].Offset, Mat);
Mat = lcMul(Mat, Mat2);
Mat.SetTranslation(lcVector3(5.0f, -10.0f, -19.0f));
Matrices[LC_MFW_RHAND] = lcMul(Mat, Matrices[LC_MFW_RARM]);
}
if (PieceData[LC_MFW_RHANDA])
{
Mat = lcMatrix44RotationZ(LC_DTOR * Angles[LC_MFW_RHANDA]);
Mat.SetTranslation(lcVector3(0, -9.25f, 0));
Mat = lcMul(mSettings[LC_MFW_RHANDA][GetSelectionIndex(LC_MFW_RHANDA)].Offset, Mat);
Mat = lcMul(Mat, lcMatrix44RotationX(LC_DTOR * 15.0f));
Matrices[LC_MFW_RHANDA] = lcMul(Mat, Matrices[LC_MFW_RHAND]);
}
if (PieceData[LC_MFW_LARM])
{
Mat = lcMatrix44RotationX(-LC_DTOR * Angles[LC_MFW_LARM]);
if (DroidTorso || SkeletonTorso)
Mat2 = lcMatrix44Identity();
else
Mat2 = lcMatrix44RotationY(LC_DTOR * 9.791f);
Mat2.SetTranslation(lcVector3(-15.552f, 0.0f, -8.88f));
Mat = lcMul(mSettings[LC_MFW_LARM][GetSelectionIndex(LC_MFW_LARM)].Offset, Mat);
Mat = lcMul(Mat, Mat2);
Matrices[LC_MFW_LARM] = lcMul(Mat, Root);
}
if (PieceData[LC_MFW_LHAND])
{
Mat = lcMatrix44RotationY(-LC_DTOR * Angles[LC_MFW_LHAND]);
Mat2 = lcMatrix44RotationX(LC_DTOR * 45);
Mat = lcMul(mSettings[LC_MFW_LHAND][GetSelectionIndex(LC_MFW_LHAND)].Offset, Mat);
Mat = lcMul(Mat, Mat2);
Mat.SetTranslation(lcVector3(-5.0f, -10.0f, -19.0f));
Matrices[LC_MFW_LHAND] = lcMul(Mat, Matrices[LC_MFW_LARM]);
}
if (PieceData[LC_MFW_LHANDA])
{
Mat = lcMatrix44RotationZ(LC_DTOR * Angles[LC_MFW_LHANDA]);
Mat.SetTranslation(lcVector3(0, -9.25f, 0));
Mat = lcMul(mSettings[LC_MFW_LHANDA][GetSelectionIndex(LC_MFW_LHANDA)].Offset, Mat);
Mat = lcMul(Mat, lcMatrix44RotationX(LC_DTOR * 15.0f));
Matrices[LC_MFW_LHANDA] = lcMul(Mat, Matrices[LC_MFW_LHAND]);
}
if (PieceData[LC_MFW_BODY2])
{
Mat = lcMatrix44Identity();
Mat.SetTranslation(lcVector3(0, 0, -32.0f));
Mat = lcMul(mSettings[LC_MFW_BODY2][GetSelectionIndex(LC_MFW_BODY2)].Offset, Mat);
Matrices[LC_MFW_BODY2] = lcMul(Mat, Root);
}
if (PieceData[LC_MFW_BODY3])
{
Mat = lcMatrix44Identity();
Mat.SetTranslation(lcVector3(0, 0, -32.0f));
Mat = lcMul(mSettings[LC_MFW_BODY3][GetSelectionIndex(LC_MFW_BODY3)].Offset, Mat);
Matrices[LC_MFW_BODY3] = lcMul(Mat, Root);
}
if (PieceData[LC_MFW_RLEG])
{
Mat = lcMatrix44RotationX(-LC_DTOR * Angles[LC_MFW_RLEG]);
Mat.SetTranslation(lcVector3(0, 0, -44.0f));
Mat = lcMul(mSettings[LC_MFW_RLEG][GetSelectionIndex(LC_MFW_RLEG)].Offset, Mat);
Matrices[LC_MFW_RLEG] = lcMul(Mat, Root);
}
if (PieceData[LC_MFW_RLEGA])
{
const lcVector3 Center(-10.0f, -1.0f, -28.0f);
Mat = lcMatrix44RotationZ(LC_DTOR * Angles[LC_MFW_RLEGA]);
Mat2 = mSettings[LC_MFW_RLEGA][GetSelectionIndex(LC_MFW_RLEGA)].Offset;
Mat2.SetTranslation(lcMul31(-Center, Mat2));
Mat = lcMul(Mat2, Mat);
Mat.SetTranslation(lcMul31(Center, Mat2));
Matrices[LC_MFW_RLEGA] = lcMul(Mat, Matrices[LC_MFW_RLEG]);
}
if (PieceData[LC_MFW_LLEG])
{
Mat = lcMatrix44RotationX(-LC_DTOR * Angles[LC_MFW_LLEG]);
Mat.SetTranslation(lcVector3(0, 0, -44.0f));
Mat = lcMul(mSettings[LC_MFW_LLEG][GetSelectionIndex(LC_MFW_LLEG)].Offset, Mat);
Matrices[LC_MFW_LLEG] = lcMul(Mat, Root);
}
if (PieceData[LC_MFW_LLEGA])
{
const lcVector3 Center(10.0f, -1.0f, -28.0f);
Mat = lcMatrix44RotationZ(LC_DTOR * Angles[LC_MFW_LLEGA]);
Mat2 = mSettings[LC_MFW_LLEGA][GetSelectionIndex(LC_MFW_LLEGA)].Offset;
Mat2.SetTranslation(lcMul31(-Center, Mat2));
Mat = lcMul(Mat2, Mat);
Mat.SetTranslation(lcMul31(Center, Mat2));
Matrices[LC_MFW_LLEGA] = lcMul(Mat, Matrices[LC_MFW_LLEG]);
}
mModel->SetMinifig(mMinifig);
}
int MinifigWizard::GetSelectionIndex(int Type) const
{
const std::vector<lcMinifigPieceInfo>& InfoArray = mSettings[Type];
for (size_t Index = 0; Index < InfoArray.size(); Index++)
if (InfoArray[Index].PieceData == mMinifig.PieceData[Type])
return (int)Index;
return 0;
}
void MinifigWizard::SetPieceData(int Type, lcPieceData* PieceData)
{
mMinifig.PieceData[Type] = lcPieceDataRef(PieceData);
if (mMinifig.PieceData[Type])
lcGetPiecesLibrary()->WaitForPieceData(mMinifig.PieceData[Type]);
Calculate();
}
void MinifigWizard::SetColorIndex(int Type, int ColorIndex)
{
mMinifig.ColorIndices[Type] = ColorIndex;
Calculate();
}
void MinifigWizard::SetAngle(int Type, float Angle)
{
mMinifig.Angles[Type] = Angle;
Calculate();
}