fix(editor): improve guided manipulation and snap affordances
This commit is contained in:
@@ -12,7 +12,7 @@ Canonical rules for code that touches `packages/core`, `packages/viewer`, `packa
|
||||
| [node-definitions](node-definitions.md) | Three-checkbox composition model for registry-driven kinds (`geometry` / `renderer` / `system`) |
|
||||
| [materials-and-themes](materials-and-themes.md) | Surface colour: surface roles, colour presets, the textures axis, and scene themes (appearance / ground / clay tints) |
|
||||
| [plugin-authoring](plugin-authoring.md) | Public contract for external plugins — `Plugin` shape, `setPluginDiscovery`, lifecycle, what's in and out of v1 |
|
||||
| [tools](tools.md) | Editor tools structure in `apps/editor` |
|
||||
| [tools](tools.md) | Editor tools structure, manipulation constraints, and Shift bypass defaults |
|
||||
| [viewer-isolation](viewer-isolation.md) | Keeping `@pascal-app/viewer` editor-agnostic |
|
||||
| [selection-managers](selection-managers.md) | Two-layer selection (viewer + editor), events, outliner |
|
||||
| [scene-registry](scene-registry.md) | Global node ID → Object3D map and `useRegistry` |
|
||||
|
||||
@@ -184,6 +184,28 @@ If the system also handles cascades, animations, or material updates, keep `def.
|
||||
- **Dispose on rebuild.** The generic system disposes the previous children's geometry + material before swapping. Custom systems that imperatively add children must dispose what they replace, or accept the GPU-memory cost.
|
||||
- **`def.renderer` overrides the generic renderer.** Once you set it, you own the mount — `<ParametricNodeRenderer>` is not invoked. The generic geometry system still runs for the kind if `def.geometry` is set, so a custom renderer can register an empty group and let the system fill it.
|
||||
|
||||
## `toolHints`
|
||||
|
||||
`toolHints?: ToolHint[]` is the registry-owned source for the floating helper shown while
|
||||
a registered placement or draw tool is active.
|
||||
|
||||
```ts
|
||||
type ToolHint = {
|
||||
key: string
|
||||
label: string
|
||||
}
|
||||
```
|
||||
|
||||
Keep labels short and action-oriented. Prefer the default guided-building language:
|
||||
snapping, angle increments, guides, and validation are active unless the user holds Shift
|
||||
during the gesture. A `Shift` hint should describe the bypass in user terms, such as
|
||||
`Free angle`, `Free place`, or `Bypass guided constraints`.
|
||||
|
||||
`HelperManager` renders `def.toolHints` through `RegisteredToolHelper`, and active Shift
|
||||
state can update the row to show that guided constraints are currently bypassed. Select
|
||||
mode is not owned by a node definition, so its helper is derived separately from
|
||||
selection state, selected-node move/rotate capabilities, and held modifiers.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
### `<GeometrySystem>` must not mutate `group.position` / `group.rotation`
|
||||
|
||||
@@ -73,6 +73,21 @@ phase: 'furnish' → selectable: furniture items only
|
||||
|
||||
Clicking a node of a different phase auto-switches the phase. Double-click drills into a context level.
|
||||
|
||||
In Select mode, 3D and 2D canvas selection share the same modifier vocabulary:
|
||||
|
||||
- `Ctrl/Meta + click` toggles the clicked object in `selectedIds`.
|
||||
- `Shift + click` also toggles the clicked canvas object so users can multi-select from
|
||||
either viewport. The scene graph keeps file-browser semantics: `Shift + click` selects
|
||||
the visible range between the last selected row and the clicked row.
|
||||
- `Ctrl/Meta + left-drag` on a selected movable object starts direct move from the canvas.
|
||||
- `Ctrl/Meta + right-drag` on a selected rotatable object starts direct rotation from the
|
||||
canvas. Rotation snaps to the default angle increment unless Shift is held during the
|
||||
drag.
|
||||
|
||||
The floating helper in `packages/editor/src/components/ui/helpers/helper-manager.tsx`
|
||||
mirrors these rules from current selection state and held modifiers. Keep that helper and
|
||||
the shortcut dialog in sync when changing selection gestures.
|
||||
|
||||
---
|
||||
|
||||
## Rules
|
||||
|
||||
@@ -66,6 +66,27 @@ export function MyTool() {
|
||||
- The offset must be cleared on tool unmount, cancel, *and* commit — both `mesh.position.set(0, 0, 0)` and `useLiveTransforms.clear(id)`.
|
||||
- The tool must not generate or mutate geometry in this path — only transform writes. Geometry generation still belongs in a core system.
|
||||
- **No business logic in tools** — delegate geometry/constraint rules to core systems.
|
||||
- **Guided manipulation is the default.** Placement, move, rotate, resize, endpoint drag,
|
||||
and handle drag should behave as guided building mode: they help the user build quickly
|
||||
with fewer mistakes through grid/object snapping, canonical angle increments,
|
||||
alignment guides, and distance feedback. Holding Shift is the standard live bypass for
|
||||
those constraints: while Shift is held, tools should commit the raw pointer/angle
|
||||
proposal instead of applying sticky snap or angle corrections. Passive measurement
|
||||
guides may remain visible only when they do not alter the proposal. If an interaction
|
||||
cannot use Shift because of an established shortcut or topology rule, document the
|
||||
opt-out in its manipulation policy and explain the replacement behavior.
|
||||
- **Constraints and guides can be decoupled.** When a stronger constraint owns the
|
||||
proposal, such as a wall segment's 15° angle lock, the tool may still publish passive
|
||||
dashed alignment/proximity guides as long as it does not apply the guide snap delta.
|
||||
Use this for chained wall segments: users keep the fast constrained draft, but still see
|
||||
proximity feedback for later points. Shift remains the hard bypass for both correction
|
||||
and guide feedback.
|
||||
- **Help must mirror manipulation policy.** The shortcut dialog and floating helper panel
|
||||
are part of the interaction contract. Static shortcut docs should describe guided
|
||||
building as the default and Shift as the live bypass. Floating help should be contextual
|
||||
when enough state exists: Select mode can derive direct move, direct rotate,
|
||||
multi-select, and Shift-bypass tips from the selected nodes and active modifiers; active
|
||||
tools can highlight the Shift bypass row while the modifier is held.
|
||||
- **Preview geometry is local** — transient meshes shown while a tool is active live in the tool component, not in the scene store.
|
||||
- **Clean up on unmount** — remove any pending/incomplete nodes *and* any live transforms/mesh offsets when the tool unmounts.
|
||||
- **Tools must not import from `@pascal-app/viewer`** — use the scene store and core hooks only. `sceneRegistry` is exported from `@pascal-app/core` and is the allowed door into the Three.js graph for the narrow purposes above.
|
||||
|
||||
Reference in New Issue
Block a user