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

552 lines
19 KiB
C++

#pragma once
#include "lc_math.h"
#include "lc_assettypes.h"
#include "lc_commands.h"
#include "lc_objectproperty.h"
#include "lc_result.h"
enum class lcGeometryBoundsMode
{
IncludeFallback,
AvailableOnly
};
enum class lcCameraProjection;
struct lcSetInventoryItem;
struct lcModelHistoryEditState;
class lcModelHistory;
class lcModelHistorySelect;
class lcModelHistoryEdit;
class lcModelHistoryProperties;
enum class lcModelHistoryEditMerge;
#define LC_SEL_NO_PIECES 0x0001 // No pieces in model
#define LC_SEL_PIECE 0x0002 // At least 1 piece selected
#define LC_SEL_CAMERA 0x0004 // At least 1 camera selected
#define LC_SEL_LIGHT 0x0008 // At least 1 light selected
#define LC_SEL_SELECTED 0x0010 // At least 1 object selected
#define LC_SEL_UNSELECTED 0x0020 // At least 1 piece unselected
#define LC_SEL_HIDDEN 0x0040 // At least one piece hidden
#define LC_SEL_HIDDEN_SELECTED 0x0080 // At least one piece selected is hidden
#define LC_SEL_VISIBLE_SELECTED 0x0100 // At least one piece selected is not hidden
#define LC_SEL_GROUPED 0x0200 // At least one piece selected is grouped
#define LC_SEL_FOCUS_GROUPED 0x0400 // Focused piece is grouped
#define LC_SEL_CAN_GROUP 0x0800 // Can make a new group
#define LC_SEL_MODEL_SELECTED 0x1000 // At least one model reference is selected
#define LC_SEL_CAN_ADD_CONTROL_POINT 0x2000 // Can add control points to focused piece
#define LC_SEL_CAN_REMOVE_CONTROL_POINT 0x4000 // Can remove control points from focused piece
#define LC_SEL_TRAIN_TRACK_VISIBLE 0x8000 // Focused piece has train track connections
enum class lcSelectionMode
{
Single,
Piece,
Color,
PieceColor
};
enum class lcTransformType
{
First,
AbsoluteTranslation = First,
RelativeTranslation,
AbsoluteRotation,
RelativeRotation,
Count
};
enum class lcModelTransformFlag
{
None = 0,
Relative = 1 << 0,
RotatePivotPoint = 1 << 1,
Checkpoint = 1 << 2,
FirstMove = 1 << 3
};
Q_DECLARE_FLAGS(lcModelTransformFlags, lcModelTransformFlag)
Q_DECLARE_OPERATORS_FOR_FLAGS(lcModelTransformFlags)
struct lcInsertPieceInfo
{
lcPieceData* PieceData;
lcMatrix44 Transform;
int ColorIndex;
};
// Stored insertion previews own their definitions; lcInsertPieceInfo remains borrowed.
struct lcPreviewInsertPieceInfo
{
explicit lcPreviewInsertPieceInfo(const lcInsertPieceInfo& Info)
: PieceData(Info.PieceData), Transform(Info.Transform), ColorIndex(Info.ColorIndex)
{
}
lcPieceDataRef PieceData;
lcMatrix44 Transform;
int ColorIndex;
};
struct lcModelPieceRename
{
lcPieceData* PieceData;
QString OldName;
QString NewName;
};
struct lcModelAssetSourceState
{
std::vector<QString> Directories;
bool PreferCurrentDirectory;
};
class lcModelProperties
{
public:
void LoadDefaults();
void SaveDefaults();
bool operator==(const lcModelProperties& Properties) const
{
if (mFileName != Properties.mFileName || mModelName != Properties.mModelName || mAuthor != Properties.mAuthor ||
mDescription != Properties.mDescription || mComments != Properties.mComments)
return false;
if (mAmbientColor != Properties.mAmbientColor)
return false;
return true;
}
bool operator!=(const lcModelProperties& Properties) const
{
return !(*this == Properties);
}
void SaveLDraw(QTextStream& Stream) const;
bool ParseLDrawHeader(QString Line, bool FirstLine);
void ParseLDrawLine(QTextStream& Stream);
QString mFileName;
QString mDescription;
QString mModelName;
QString mAuthor;
QString mComments;
lcVector3 mAmbientColor;
};
class lcPOVRayOptions
{
public:
void ParseLDrawLine(QTextStream& LineStream);
void SaveLDraw(QTextStream& Stream) const;
bool UseLGEO = false;
bool ExcludeFloor = false;
bool ExcludeBackground = false;
bool NoReflection = false;
bool NoShadow = false;
int FloorAxis = 1;
float FloorAmbient = 0.4f;
float FloorDiffuse = 0.4f;
lcVector3 FloorColor = lcVector3(0.8f,0.8f,0.8f);
QString HeaderIncludeFile;
QString FooterIncludeFile;
};
struct lcModelHistoryEntry
{
std::vector<std::unique_ptr<lcModelHistory>> HistorySequence;
QString Description;
quint64 Revision = 0;
};
class lcModel
{
public:
lcModel(const QString& FileName, Project* Project, bool Preview);
~lcModel();
lcModel(const lcModel&) = delete;
lcModel(lcModel&&) = delete;
lcModel& operator=(const lcModel&) = delete;
lcModel& operator=(lcModel&&) = delete;
Project* GetProject() const
{
return mProject;
}
void SetProject(Project* Project);
void PreserveAssetSourceDirectory(const QString& Directory, bool PreferCurrentDirectory);
lcModelAssetSourceState GetAssetSourceState() const;
void RestoreAssetSourceState(const lcModelAssetSourceState& State);
std::vector<QString> GetAssetSearchDirectories() const;
bool IsActive() const
{
return mActive;
}
bool IsModified() const;
void SetSaved();
bool GetPieceWorldMatrix(lcPiece* Piece, lcMatrix44& ParentWorldMatrix) const;
bool IncludesModel(const lcModel* Model) const;
void CreatePieceData(Project* Project);
bool RebindPieceData(lcPieceData* Previous, lcPieceData* Replacement);
void UpdatePieceData(std::vector<lcModel*>& UpdatedModels);
void QueueMeshBuild();
void UpdateAllViews() const;
void RefreshPreview() const;
std::vector<lcPieceDataRef> GetRequiredPieceData() const;
std::vector<lcPiece*> GetRequiredSynthPieces() const;
void WaitForAssets() const;
lcResult<void> EnsureAssetsReady() const;
lcPieceData* GetPieceData() const
{
return mPieceData.Get();
}
const std::vector<std::unique_ptr<lcPiece>>& GetPieces() const
{
return mPieces;
}
const std::vector<std::unique_ptr<lcCamera>>& GetCameras() const
{
return mCameras;
}
const std::vector<std::unique_ptr<lcLight>>& GetLights() const
{
return mLights;
}
const std::vector<std::unique_ptr<lcGroup>>& GetGroups() const
{
return mGroups;
}
const lcModelProperties& GetProperties() const
{
return mProperties;
}
const lcPOVRayOptions& GetPOVRayOptions() const
{
return mPOVRayOptions;
}
void SetFileName(const QString& FileName)
{
if (mProperties.mModelName == mProperties.mFileName)
mProperties.mModelName = FileName;
mProperties.mFileName = FileName;
}
const QString& GetFileName() const
{
return mProperties.mFileName;
}
void SetDescription(const QString& Description)
{
mProperties.mDescription = Description;
}
const QStringList& GetFileLines() const
{
return mFileLines;
}
bool HasDirectGeometry() const
{
return mHasDirectGeometry;
}
lcStep GetLastStep() const;
lcStep GetCurrentStep() const
{
return mCurrentStep;
}
void SetActive(bool Active);
void CalculateStep(lcStep Step);
void SetCurrentStep(lcStep Step);
void SetTemporaryStep(lcStep Step)
{
mCurrentStep = Step;
CalculateStep(Step);
}
void ShowFirstStep();
void ShowLastStep();
void ShowPreviousStep();
void ShowNextStep();
void InsertStepAction(lcStep Step);
void RemoveStepAction(lcStep Step);
template<typename StateType, typename ObjectType>
void LoadObjectHistoryState(const std::vector<StateType>& ObjectStates, std::vector<std::unique_ptr<ObjectType>>& Objects);
void LoadEditHistoryState(const lcModelHistoryEditState& HistoryState);
void LoadModelPropertiesState(const lcModelProperties& ModelProperties);
lcPiece* AddPiece(lcPieceData* PieceData, quint32 Section);
void AddPiece(std::unique_ptr<lcPiece> Piece, size_t PieceIndex);
void DeleteSelectedObjects();
void ResetSelectedPiecesPivotPoint();
void RemoveSelectedObjectsKeyFrames();
void InsertControlPoint();
void RemoveFocusedControlPoint();
void FocusNextTrainTrack();
void FocusPreviousTrainTrack();
void RotateFocusedTrainTrack(int Direction);
void ShowSelectedPiecesEarlier();
void ShowSelectedPiecesLater();
void InsertStepAndMoveSelectedPieces(lcStep Step);
void SetPieceSteps(const std::vector<std::pair<lcPiece*, lcStep>>& PieceSteps);
void RenamePieces(const std::vector<lcModelPieceRename>& Renames);
void MoveSelectionToModel(lcModel* Model);
void InlineSelectedModels();
lcGroup* AddGroup(const QString& Prefix, lcGroup* Parent);
lcGroup* GetGroup(const QString& Name, bool CreateIfMissing);
void RemoveGroup(lcGroup* Group);
void GroupSelection();
void UngroupSelection();
void AddSelectedPiecesToGroup();
void RemoveFocusPieceFromGroup();
void ShowEditGroupsDialog();
void SaveLDraw(QTextStream& Stream, bool SelectedOnly, lcStep LastStep) const;
void LoadLDraw(QIODevice& Device, Project* Project);
bool LoadBinary(lcFile* File);
bool LoadLDD(const QString& FileData);
bool LoadInventory(const std::vector<lcSetInventoryItem>& SetInventory);
void Merge(std::unique_ptr<lcModel> Other);
void SetMinifig(const lcMinifig& Minifig);
void SetPreviewPieceData(lcPieceData* PieceData, int ColorIndex);
lcResult<void> Cut();
lcResult<void> Copy();
lcResult<void> Paste(bool PasteToCurrentStep);
void DuplicateSelectedPieces();
void PaintSelectedPieces();
void UpdateTrainTrackConnections(lcPiece* TrackPiece, bool IgnoreSelected) const;
void UpdateSelectedPiecesTrainTrackConnections();
void SetPreviewInsertPieceInfo(std::vector<lcInsertPieceInfo>&& PreviewInsertPieceInfo);
const std::vector<lcPreviewInsertPieceInfo>& GetPreviewInsertPieceInfo() const
{
return mPreviewInsertPieceInfo;
}
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);
// 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;
void BoxTest(lcObjectBoxTest& ObjectBoxTest) const;
bool SubModelMinIntersectDist(const lcVector3& WorldStart, const lcVector3& WorldEnd, float& MinDistance, lcPieceRayTest& PieceRayTest) const;
bool SubModelBoxTest(const lcVector4 Planes[6]) const;
void SubModelCompareBoundingBox(const lcMatrix44& WorldMatrix, lcVector3& Min, lcVector3& Max) const;
void SubModelAddBoundingBoxPoints(const lcMatrix44& WorldMatrix, std::vector<lcVector3>& Points, lcGeometryBoundsMode Mode) const;
bool HasPieces() const
{
return !mPieces.empty();
}
bool AnyPiecesSelected() const;
bool AnyObjectsSelected() const;
lcModel* GetFirstSelectedSubmodel() const;
void GetSubModels(std::set<lcModel*>& SubModels) const;
bool GetMoveRotateTransform(lcVector3& Center, lcMatrix33& RelativeRotation) const;
bool CanRotateSelection() const;
bool GetPieceFocusOrSelectionCenter(lcVector3& Center) const;
lcVector3 GetSelectionOrModelCenter() const;
bool GetFocusPosition(lcVector3& Position) const;
lcObject* GetFocusObject() const;
bool GetSelectionCenter(lcVector3& Center) const;
lcBoundingBox GetAllPiecesBoundingBox() const;
bool GetVisiblePiecesBoundingBox(lcVector3& Min, lcVector3& Max) const;
std::vector<lcVector3> GetPiecesBoundingBoxPoints(lcGeometryBoundsMode Mode) const;
void GetPartsList(int DefaultColorIndex, bool ScanSubModels, bool AddSubModels, lcPartsList& PartsList) const;
void GetPartsListForStep(lcStep Step, int DefaultColorIndex, lcPartsList& PartsList, bool Cumulative) const;
void GetModelParts(const lcMatrix44& WorldMatrix, int DefaultColorIndex, std::vector<lcModelPartsEntry>& ModelParts) const;
void GetSelectionInformation(int* Flags, std::vector<lcObject*>& Selection, lcObject** Focus) const;
void ClearSelectionAction();
void SetSelectionAndFocusAction(const std::vector<lcObject*>& Selection, lcObject* Focus, quint32 Section, lcSelectionMode SelectionMode);
void FocusOrDeselectObjectAction(lcObject* Object, uint32_t Section, lcSelectionMode SelectionMode);
void SelectAllPiecesAction();
void InvertPieceSelectionAction();
void AddToSelectionAction(const std::vector<lcObject*>& Objects);
void RemoveFromSelectionAction(const std::vector<lcObject*>& Objects);
void HideSelectedPieces();
void HideUnselectedPieces();
void UnhideSelectedPieces();
void UnhideAllPieces();
void FindReplacePiece(bool SearchForward, bool FindAll, bool Replace);
void UndoAction();
void RedoAction();
lcVector3 SnapPosition(const lcVector3& Delta) const;
lcVector3 SnapRotation(const lcVector3& Delta) const;
lcMatrix33 GetRelativeRotation() const;
const lcVector3& GetMouseToolDistance() const
{
return mMouseToolDistance;
}
bool GetMouseToolMovePositions(lcVector3& Start, lcVector3& End) const
{
if (!mMouseToolMovePositionValid)
return false;
Start = mMouseToolMoveStartPosition;
End = mMouseToolMoveEndPosition;
return true;
}
void BeginMouseTool(lcTool Tool, lcView* View);
void EndMouseTool(lcTool Tool, lcView* View, bool Accept);
bool InsertPieceToolClicked();
void InsertCameraToolClicked(const lcVector3& Position);
void InsertLightToolClicked(const lcVector3& Position, lcLightType LightType);
void UpdateMoveTool(const lcVector3& Distance, bool AllowRelative, bool AlternateButtonDrag);
void UpdateFreeMoveTool(lcPiece* MousePiece, const lcMatrix44& StartTransform, const lcMatrix44& NewTransform, bool IsConnection, bool AlternateButtonDrag);
void UpdateRotateTool(const lcVector3& Axis, float Angle, bool AlternateButtonDrag, bool Relative);
void UpdateRotateTool(const lcMatrix33& RotationMatrix, bool AlternateButtonDrag, bool Relative);
void UpdateScaleTool(const float Scale);
void EraserToolClicked(lcObject* Object);
void PaintToolClicked(lcObject* Object);
void ColorPickerToolClicked(const lcObject* Object);
void UpdateZoomTool(lcCamera* Camera, float Mouse);
void UpdatePanTool(lcCamera* Camera, const lcVector3& Distance);
void UpdateOrbitTool(lcCamera* Camera, float MouseX, float MouseY);
void UpdateRollTool(lcCamera* Camera, float Mouse);
void ZoomRegionToolClicked(lcView* View, float AspectRatio, const lcVector3& Position, const lcVector3& TargetPosition, const lcVector3* Corners);
void LookAt(lcCamera* Camera);
void MoveCamera(lcCamera* Camera, const lcVector3& Direction);
void ZoomExtents(lcCamera* Camera, float Aspect, const lcMatrix44& WorldMatrix, lcGeometryBoundsMode Mode, bool AdjustFarPlane);
void Zoom(lcCamera* Camera, float Amount);
void MoveSelectedObjects(const lcVector3& Distance, lcModelTransformFlags Flags, lcModelHistoryEditMerge ModelHistoryEditMerge)
{
MoveSelectedObjects(Distance, Distance, Flags, ModelHistoryEditMerge);
}
void MoveSelectedObjects(const lcVector3& PieceDistance, const lcVector3& ObjectDistance, lcModelTransformFlags Flags, lcModelHistoryEditMerge ModelHistoryEditMerge);
void RotateSelectedObjects(const lcVector3& Angles, lcModelTransformFlags Flags, lcModelHistoryEditMerge ModelHistoryEditMerge);
void RotateSelectedObjects(const lcMatrix33& RotationMatrix, lcModelTransformFlags Flags, lcModelHistoryEditMerge ModelHistoryEditMerge);
void ScaleSelectedPieces(const float Scale);
void TransformSelectedObjects(lcTransformType TransformType, const lcVector3& Transform);
void SetObjectsKeyFrame(const std::vector<lcObject*>& Objects, lcObjectPropertyId PropertyId, bool KeyFrame);
void SetSelectedPiecesColorIndex(int ColorIndex);
void SetSelectedPiecesStepShow(lcStep Step);
void SetSelectedPiecesStepHide(lcStep Step);
void SetObjectsProperty(const std::vector<lcObject*>& Objects, lcObjectPropertyId PropertyId, QVariant Value);
void BeginPropertyEdit(lcObjectPropertyId PropertyId);
void EndPropertyEdit(lcObjectPropertyId PropertyId, bool Accept);
void SetCameraProjection(lcCamera* Camera, lcCameraProjection CameraProjection);
void ShowPropertiesDialog();
void ShowArrayDialog();
void ShowMinifigDialog();
void UpdateInterface();
protected:
lcResult<QImage> RenderStepImageWithReadyAssets(bool Zoom, int Width, int Height, lcStep Step, lcCamera* OutputCamera);
void DeleteModel();
void AddSelectionHistory(std::function<void()> Callback);
void ClearSelection();
void SetFocus(lcObject* FocusObject, uint32_t FocusSection, lcSelectionMode SelectionMode);
void SetSelectionAndFocus(const std::vector<lcObject*>& Objects, lcObject* FocusObject, uint32_t FocusSection, lcSelectionMode SelectionMode);
void SelectAllPieces();
void InvertPieceSelection();
void AddToSelection(const std::vector<lcObject*>& Objects);
void RemoveFromSelection(const std::vector<lcObject*>& Objects);
void BeginEditHistory();
void BeginMergeableEditHistory(lcModelHistoryEditMerge ModelHistoryEditMerge, lcObjectPropertyId PropertyId);
void EndEditHistory();
void SetModelProperties(const lcModelProperties& ModelProperties);
void RunHistorySequence(const std::vector<std::unique_ptr<lcModelHistory>>& HistorySequence, bool Apply);
void BeginHistorySequence();
void EndHistorySequence(const QString& Description);
void DiscardHistorySequence();
void RevertHistorySequence();
void RemoveFirstUndoIfUnchanged();
quint64 GetCurrentHistoryRevision() const;
QString GetGroupName(const QString& Prefix);
void RemoveEmptyGroups();
bool RemoveSelectedObjects();
void RemoveCameraFromViews(lcCamera* Camera);
std::vector<lcObject*> GetSelectionModePieces(lcSelectionMode SelectionMode, const lcPiece* SelectedPiece) const;
void DeselectAllObjects();
void SetFocusedObject(lcObject* FocusObject, uint32_t FocusSection, lcSelectionMode SelectionMode);
void SetObjectsSelected(const std::vector<lcObject*>& Objects, bool Selected);
void SelectGroup(lcGroup* TopGroup, bool Select);
size_t AddPiece(lcPiece* Piece);
void InsertStep(lcStep Step);
lcPOVRayOptions mPOVRayOptions;
lcModelProperties mProperties;
Project* mProject;
// Search roots retained when a model moves between project directories.
// Their order and the current-directory preference are saved with the model.
std::vector<QString> mAssetSourceDirectories;
bool mPreferCurrentAssetDirectory = true;
lcPieceDataRef mPieceData;
bool mIsPreview;
bool mActive;
lcStep mCurrentStep;
lcVector3 mMouseToolDistance;
lcVector3 mMouseToolMoveStartPosition;
lcVector3 mMouseToolMoveEndPosition;
bool mMouseToolMovePositionValid = false;
bool mMouseToolMoveUsesFocus = false;
bool mMouseToolFirstMove;
std::vector<lcPreviewInsertPieceInfo> mPreviewInsertPieceInfo;
std::vector<std::unique_ptr<lcPiece>> mPieces;
std::vector<std::unique_ptr<lcCamera>> mCameras;
std::vector<std::unique_ptr<lcLight>> mLights;
std::vector<std::unique_ptr<lcGroup>> mGroups;
QStringList mFileLines;
bool mHasDirectGeometry = false;
std::vector<std::unique_ptr<lcModelHistory>> mHistorySequence;
lcObjectPropertyId mActivePropertyEdit = lcObjectPropertyId::Count;
quint64 mSavedHistoryRevision = 0;
quint64 mNextHistoryRevision = 1;
std::vector<std::unique_ptr<lcModelHistoryEntry>> mUndoHistory;
std::vector<std::unique_ptr<lcModelHistoryEntry>> mRedoHistory;
Q_DECLARE_TR_FUNCTIONS(lcModel);
};