Skip to content

SceneOps: Slice D — Split submesh (by material / by selection / by connected component) #627

Description

@fernandotonon

Parent epic: #623

Goal

Split one submesh into multiple — three modes covering the realistic use-cases:

  1. By material — a submesh with per-face material tags becomes one submesh per material. (Edge case but useful for some FBX imports.)
  2. 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").
  3. 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

  • By-material split correctly separates multi-material submeshes; no-op on single-material.
  • By-selection split produces a new submesh containing exactly the selected faces; the source submesh remains valid.
  • By-connected-component split produces N submeshes that, when joined, are byte-identical to the original (round-trip).
  • Boundary vertex duplication is correct (no shared vertices across the split).
  • Bone assignments survive on a skinned fixture.
  • Undo restores the original submesh byte-exactly.
  • glTF + FBX round-trip preserves the new submeshes.
  • Sentry breadcrumbs mesh.submesh.split with {mode, srcIndex, newIndices}.
  • Headless-CI tests for each of the three modes.

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestscene-treeScene tree, submeshes, hierarchy operations

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions