Skip to content

Delta models become unpickable after editor.edit() because FastModelPicker hides parent model root #746

@rihokirss

Description

@rihokirss

Summary

After editing an element with editor.edit(), the edited geometry lives in a generated delta model attached as a child of the parent model. FastModelPicker.renderPickPass() hides each model root before rendering it individually, which means the delta model never gets rendered into the picker buffer (Three.js will not render a child whose ancestor has visible = false). This causes picking/highlighting to fail on edited elements until editor.save(modelId) is called.

Forum thread (Antonio González Viegas confirmed it as a bug and asked me to file here):
https://people.thatopen.com/c/ask-the-community/edited-element-stays-in-delta-model-after-transform-edit-causing-picking-highlight-issues-unless-editor-save-is-called

Versions

  • @thatopen/fragments@3.4.5
  • @thatopen/components@3.4.5
  • @thatopen/components-front@3.4.3
  • three@0.183.2

Steps to reproduce

  1. Load a Fragment model.
  2. Select an element via editor.getElements(modelId, [localId]).
  3. Move it with TransformControls and apply through element.setMeshes(mesh)element.getRequests()fragments.editor.edit(modelId, requests).
  4. Call fragments.update(true).
  5. Try to click/pick the edited element.

Expected

Edited element should remain pickable and highlight correctly without calling editor.save().

Actual

  • Edited element is rendered through the delta model but appears visually inconsistent ("multicolored").
  • Picking/highlighting passes through the edited element or behaves incorrectly.
  • Calling editor.save(modelId) fixes it (delta is committed back), but causes a model reload and visible flicker.

Root cause analysis

In @thatopen/fragments, the delta model is attached under the parent model object:

parentModel.object.add(deltaModel.object)

In @thatopen/components, FastModelPicker.renderPickPass() builds a map of fragments.list model objects, hides every model root, and then renders each one individually:

for (const obj of objectsByModel.values()) {
  obj.visible = false
}

for (const [byte, modelId] of byteToModel) {
  const obj = objectsByModel.get(modelId)
  obj.visible = true
  renderer.render(scene, camera)
  obj.visible = false
}

When the picker reaches the delta model, the parent model root is still hidden. Three.js does not render children whose ancestor is hidden, so the delta model is not drawn into the picker buffer.

Workaround

Re-parent the delta model directly to the scene whenever it's added:

function keepDeltaModelPickable(model, scene) {
  if (!model.isDeltaModel || !model.object) return
  const attach = () => {
    if (model.object.parent !== scene) scene.add(model.object)
  }
  attach()
  window.setTimeout(attach, 0)
  window.setTimeout(attach, 50)
}

// hook on fragments.list.onItemSet and on each model.tiles.onItemSet

After applying this, picking/highlighting works correctly without needing editor.save().

Suggested fixes (either should work)

  1. Attach delta models to the scene root instead of under the parent model object in @thatopen/fragments.
  2. Account for hidden ancestors in FastModelPicker.renderPickPass() — temporarily unhide the chain to the picked model, or render delta models in their own pass.

Maintainers are better placed to pick the right path.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions