Parent epic: #623
Goal
Split one submesh into multiple — three modes covering the realistic use-cases:
- By material — a submesh with per-face material tags becomes one submesh per material. (Edge case but useful for some FBX imports.)
- By selection — the user selects faces in Edit Mode; "Split selection to new submesh" extracts them. The primary user-requested flow ("split the body parts").
- By connected component — flood-fill mesh adjacency via
HalfEdgeMesh; produces one submesh per topologically-connected island. Useful when the imported geometry has multiple discrete parts baked into one submesh.
Scope
- New helpers:
SubMeshOps::splitSubMeshByMaterial(mesh, subIndex) -> std::vector<int> newIndices.
SubMeshOps::splitSubMeshBySelection(mesh, subIndex, faceIndices, newName?) -> int newSubIndex.
SubMeshOps::splitSubMeshByConnectedComponent(mesh, subIndex) -> std::vector<int> newIndices.
- By material:
- Requires submeshes that have per-face material tags (rare in practice; common in some Assimp re-imports). No-op if all faces share one material.
- By selection (the primary flow):
- Inputs: a list of face indices (
uint32_t triangle indices in the source submesh).
- Output: a new submesh containing only those triangles, with vertex deduplication on the boundary (interior-only vertices stay; boundary vertices are duplicated so the original submesh remains a valid closed mesh).
- Inherits the source submesh's material.
- Plumbs through
EditModeController's face selection → qml/SceneTreeNode.qml context-menu command "Split selection to new submesh".
- By connected component:
- Build a
HalfEdgeMesh over the source submesh.
- Flood-fill from each face to find connected islands.
- Produce one submesh per island.
- Discard the source submesh.
- Naming:
- By material: appended with material name (
Body.Skin, Body.Cloth).
- By selection: user-prompted (default
Body.001).
- By connected component: numeric suffix (
Body.001, Body.002, …).
- Bone assignments: copied to whichever new submesh contains each affected vertex.
- Undo:
SplitSubMeshCommand captures pre-split state.
Acceptance Criteria
Effort
~10 days. By-selection is the highest-value mode; ship it first; by-material and by-component can land in follow-up PRs under this slice.
Parent epic: #623
Goal
Split one submesh into multiple — three modes covering the realistic use-cases:
HalfEdgeMesh; produces one submesh per topologically-connected island. Useful when the imported geometry has multiple discrete parts baked into one submesh.Scope
SubMeshOps::splitSubMeshByMaterial(mesh, subIndex) -> std::vector<int> newIndices.SubMeshOps::splitSubMeshBySelection(mesh, subIndex, faceIndices, newName?) -> int newSubIndex.SubMeshOps::splitSubMeshByConnectedComponent(mesh, subIndex) -> std::vector<int> newIndices.uint32_ttriangle indices in the source submesh).EditModeController's face selection →qml/SceneTreeNode.qmlcontext-menu command "Split selection to new submesh".HalfEdgeMeshover the source submesh.Body.Skin,Body.Cloth).Body.001).Body.001,Body.002, …).SplitSubMeshCommandcaptures pre-split state.Acceptance Criteria
mesh.submesh.splitwith{mode, srcIndex, newIndices}.Effort
~10 days. By-selection is the highest-value mode; ship it first; by-material and by-component can land in follow-up PRs under this slice.