Preserve ancestor steps while editing nested submodels

Drilling into a child deactivated its parent and evaluated the parent at
its final step. After navigating to an earlier step, this moved surrounding
geometry and separated the child's editing transform from its placement.

Keep the complete editing path active at its chosen steps, and use that
path when filtering ancestor geometry for rendering. Rebuild the transform
from the selected instances when restoring a tab so changes in an ancestor's
own tab also keep the editing target aligned. Returning through a breadcrumb
deactivates only departed descendants; ending editing restores completed
assembly poses while retaining stored step choices.

Also route Submodel Properties to the model currently being edited rather
than the tab model.

Validated the macOS Qt 6 application build, all 8 Qt Test cases, and 106
focused compiled-application checks covering ancestor poses and visibility,
properties edits, tab switching, breadcrumbs, focus status, and exports.
This commit is contained in:
Leonardo Zide committed 2026-10-24 13:16:12 -07:00
1 parent 688e2f70f1
commit 3db4b8e70b
7 files changed
+47 -35

No files matched your search

+2 -1
View File
@@ -3611,7 +3611,8 @@ void lcMainWindow::HandleCommand(lcCommandId CommandId)
break;
case LC_MODEL_PROPERTIES:
lcGetActiveModel()->ShowPropertiesDialog();
if (ActiveModel)
ActiveModel->ShowPropertiesDialog();
break;
case LC_MODEL_LIST:
+3 -1
View File
@@ -1660,9 +1660,11 @@ void lcModel::GetScene(lcScene* Scene, const lcCamera* ViewCamera, bool AllowHig
void lcModel::AddSubModelRenderMeshes(lcScene* Scene, const lcMatrix44& WorldMatrix, int DefaultColorIndex, lcRenderMeshState RenderMeshState, bool ParentActive) const
{
const bool Editing = ParentActive || Scene->IsModelEditing(this);
for (const std::unique_ptr<lcPiece>& Piece : mPieces)
{
const bool Visible = ParentActive ? (Piece->IsSelected() || Piece->IsVisible(mCurrentStep)) : Piece->IsVisibleInSubModel();
const bool Visible = Editing ? (Piece->IsSelected() || Piece->IsVisible(mCurrentStep)) : Piece->IsVisibleInSubModel();
if (Visible)
{
+1
View File
@@ -21,6 +21,7 @@ void lcScene::Begin(const lcMatrix44& ViewMatrix)
mHighlightRenderMode = lcHighlightRenderMode::Tint;
mViewMatrix = ViewMatrix;
mActiveSubmodelInstance = nullptr;
mModelEditingPath.clear();
mRenderMeshes.clear();
mOpaqueMeshes.clear();
mTranslucentMeshes.clear();
+8 -1
View File
@@ -35,10 +35,16 @@ class lcScene
public:
lcScene();
void SetActiveSubmodelInstance(lcPiece* ActiveSubmodelInstance, const lcMatrix44& ActiveSubmodelTransform)
void SetActiveSubmodelInstance(lcPiece* ActiveSubmodelInstance, const lcMatrix44& ActiveSubmodelTransform, const std::vector<lcModel*>& ModelEditingPath)
{
mActiveSubmodelInstance = ActiveSubmodelInstance;
mActiveSubmodelTransform = ActiveSubmodelTransform;
mModelEditingPath = ModelEditingPath;
}
bool IsModelEditing(const lcModel* Model) const
{
return mDrawInterface && mActiveSubmodelInstance && std::find(mModelEditingPath.begin(), mModelEditingPath.end(), Model) != mModelEditingPath.end();
}
lcPiece* GetActiveSubmodelInstance() const
@@ -165,6 +171,7 @@ protected:
lcMatrix44 mViewMatrix;
lcMatrix44 mActiveSubmodelTransform;
lcPiece* mActiveSubmodelInstance = nullptr;
std::vector<lcModel*> mModelEditingPath;
lcShadingMode mShadingMode;
bool mDrawInterface = false;
lcHighlightRenderMode mHighlightRenderMode;
+25 -26
View File
@@ -152,12 +152,24 @@ std::vector<lcModel*> lcView::GetModelEditingPath() const
{
std::vector<lcModel*> Models = { mModel };
for (const std::pair<lcPiece*, lcMatrix44>& Entry : mSubmodelEditingPath)
Models.push_back(Entry.first->mPieceData->GetModel());
for (const lcPiece* Piece : mSubmodelEditingPath)
Models.push_back(Piece->mPieceData->GetModel());
return Models;
}
void lcView::RestoreModelEditingContext()
{
mModel->SetActive(true);
mActiveSubmodelTransform = lcMatrix44Identity();
for (lcPiece* Piece : mSubmodelEditingPath)
{
mActiveSubmodelTransform = lcMul(Piece->mModelWorld, mActiveSubmodelTransform);
Piece->mPieceData->GetModel()->SetActive(true);
}
}
void lcView::SetSubmodelActive(size_t Depth)
{
if (Depth > mSubmodelEditingPath.size())
@@ -165,26 +177,22 @@ void lcView::SetSubmodelActive(size_t Depth)
StopTracking(true);
lcModel* ActiveModel = GetActiveModel();
const std::vector<lcModel*> EditingModels = GetModelEditingPath();
if (mActiveSubmodelInstance)
{
ActiveModel->SetActive(false);
for (size_t Index = EditingModels.size(); Index > Depth + 1; Index--)
EditingModels[Index - 1]->SetActive(false);
std::vector<lcModel*> UpdatedModels;
ActiveModel->UpdatePieceData(UpdatedModels);
}
for (size_t Index = EditingModels.size(); Index > Depth + 1; Index--)
EditingModels[Index - 1]->UpdatePieceData(UpdatedModels);
mSubmodelEditingPath.resize(Depth);
mActiveSubmodelInstance = nullptr;
if (!mSubmodelEditingPath.empty())
{
mActiveSubmodelInstance = mSubmodelEditingPath.back().first;
mActiveSubmodelTransform = mSubmodelEditingPath.back().second;
}
mActiveSubmodelInstance = mSubmodelEditingPath.back();
GetActiveModel()->SetActive(true);
RestoreModelEditingContext();
GetActiveModel()->UpdateInterface();
}
@@ -206,22 +214,13 @@ void lcView::SetSelectedSubmodelActive()
if (!Piece->mPieceData->IsModel())
return;
// Preserve the selected instance path and capture its transform before SetActive(false) changes the step.
lcMatrix44 SubmodelTransform = Piece->mModelWorld;
if (mActiveSubmodelInstance)
SubmodelTransform = lcMul(SubmodelTransform, mActiveSubmodelTransform);
if (mActiveSubmodelInstance)
ActiveModel->SetActive(false);
mSubmodelEditingPath.emplace_back(Piece, SubmodelTransform);
mActiveSubmodelTransform = SubmodelTransform;
// Keep ancestor models at their chosen steps so the selected instance stays in place.
mSubmodelEditingPath.push_back(Piece);
mActiveSubmodelInstance = Piece;
ActiveModel = mActiveSubmodelInstance->mPieceData->GetModel();
ActiveModel->SetTemporaryStep(ActiveModel->GetLastStep());
ActiveModel->SetActive(true);
RestoreModelEditingContext();
RemoveCamera();
@@ -1048,7 +1047,7 @@ void lcView::OnDraw()
mScene->Begin(mCamera->mWorldView);
mScene->SetActiveSubmodelInstance(mActiveSubmodelInstance, mActiveSubmodelTransform);
mScene->SetActiveSubmodelInstance(mActiveSubmodelInstance, mActiveSubmodelTransform, GetModelEditingPath());
mScene->SetDrawInterface(DrawInterface);
bool DrawInsertPreview = DrawInterface && std::any_of(mViews.begin(), mViews.end(), [](lcView* View){ return View->mTrackTool == lcTrackTool::Insert; });
+2 -1
View File
@@ -236,6 +236,7 @@ public:
lcModel* GetActiveModel() const;
std::vector<lcModel*> GetModelEditingPath() const;
void RestoreModelEditingContext();
void SetSubmodelActive(size_t Depth);
void SetTopSubmodelActive();
void SetSelectedSubmodelActive();
@@ -395,7 +396,7 @@ protected:
lcModel* mModel = nullptr;
lcPiece* mActiveSubmodelInstance = nullptr;
lcMatrix44 mActiveSubmodelTransform;
std::vector<std::pair<lcPiece*, lcMatrix44>> mSubmodelEditingPath;
std::vector<lcPiece*> mSubmodelEditingPath;
lcCamera* mCamera = nullptr;
+6 -5
View File
@@ -234,12 +234,13 @@ void Project::SetActiveModel(lcModel* ActiveModel, bool UpdateInterface)
{
gMainWindow->SetCurrentModelTab(mActiveModel, true);
// The tab may still be editing a nested submodel at its current step.
lcModel* EditingModel = gMainWindow->GetActiveModel();
if (EditingModel)
// Restore the current steps of the editing target and its surrounding ancestors.
lcView* EditingView = gMainWindow->GetActiveView();
if (EditingView)
{
EditingModel->SetActive(true);
EditingModel->UpdateInterface();
EditingView->RestoreModelEditingContext();
EditingView->GetActiveModel()->UpdateInterface();
}
}
}