feat(editor): MEP move-tools — mode-driven snapping (drop Shift=bypass)

The 5 bespoke MEP movers (duct/pipe-segment, liquid-line, lineset, duct-fitting)
now read the active snapping mode (isGridSnapActive / isMagneticSnapActive)
instead of shiftKey=bypass. The moving scope already carries the node
(setMovingNode → begin('moving')), so the per-kind context resolves with no
extra wiring. Grid and alignment are now independent reads.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-06-25 12:06:31 -04:00
co-authored by Claude Opus 4.8
parent 2fdbcf03c3
commit 8e9a45a9d5
5 changed files with 44 additions and 30 deletions
+12 -6
View File
@@ -13,6 +13,8 @@ import {
import { import {
DragBoundingBox, DragBoundingBox,
EDITOR_LAYER, EDITOR_LAYER,
isGridSnapActive,
isMagneticSnapActive,
markToolCancelConsumed, markToolCancelConsumed,
stripPlacementMetadataFlags, stripPlacementMetadataFlags,
triggerSFX, triggerSFX,
@@ -177,14 +179,14 @@ export const MoveDuctFittingTool: React.FC<{ node: AnyNode }> = ({ node }) => {
let lastPos: Vec3 = originalPosition let lastPos: Vec3 = originalPosition
const onMove = (event: GridEvent) => { const onMove = (event: GridEvent) => {
const bypass = event.nativeEvent?.shiftKey === true const snap = isGridSnapActive() ? snapToGridStep : (v: number) => v
const snap = bypass ? (v: number) => v : snapToGridStep
let x = snap(event.localPosition[0]) let x = snap(event.localPosition[0])
let z = snap(event.localPosition[2]) let z = snap(event.localPosition[2])
// Alignment: snap the footprint box edges onto nearby geometry and // Magnetic alignment: snap the footprint box edges onto nearby geometry
// publish guides (Alt / Shift bypass). // and publish guides. Grid follows the snapping mode; lines follow
if (!bypass) { // magnetic alignment — the two are independent.
if (isMagneticSnapActive()) {
const proposed: Aabb2D = { const proposed: Aabb2D = {
minX: x + ox - hx, minX: x + ox - hx,
maxX: x + ox + hx, maxX: x + ox + hx,
@@ -200,7 +202,11 @@ export const MoveDuctFittingTool: React.FC<{ node: AnyNode }> = ({ node }) => {
} }
const next: Vec3 = [x, originalPosition[1], z] const next: Vec3 = [x, originalPosition[1], z]
if (next[0] !== lastPos[0] || next[2] !== lastPos[2]) triggerSFX('sfx:grid-snap') if (
(isGridSnapActive() || isMagneticSnapActive()) &&
(next[0] !== lastPos[0] || next[2] !== lastPos[2])
)
triggerSFX('sfx:grid-snap')
lastPos = next lastPos = next
hasMoved = true hasMoved = true
setCursorPos(next) setCursorPos(next)
@@ -13,6 +13,8 @@ import {
import { import {
DragBoundingBox, DragBoundingBox,
EDITOR_LAYER, EDITOR_LAYER,
isGridSnapActive,
isMagneticSnapActive,
markToolCancelConsumed, markToolCancelConsumed,
stripPlacementMetadataFlags, stripPlacementMetadataFlags,
triggerSFX, triggerSFX,
@@ -144,14 +146,14 @@ export const MoveDuctSegmentTool: React.FC<{ node: AnyNode }> = ({ node }) => {
} }
const onMove = (event: GridEvent) => { const onMove = (event: GridEvent) => {
const bypass = event.nativeEvent?.shiftKey === true const snap = isGridSnapActive() ? snapToGridStep : (v: number) => v
const snap = bypass ? (v: number) => v : snapToGridStep
let dx = snap(event.localPosition[0] - centerX) let dx = snap(event.localPosition[0] - centerX)
let dz = snap(event.localPosition[2] - centerZ) let dz = snap(event.localPosition[2] - centerZ)
// Figma-style alignment: snap the run's footprint box edges onto // Figma-style magnetic alignment: snap the run's footprint box edges onto
// nearby geometry and publish the guides (Alt / Shift bypass). // nearby geometry and publish the guides. Grid follows the snapping mode;
if (!bypass) { // lines follow magnetic alignment — the two are independent.
if (isMagneticSnapActive()) {
const proposed: Aabb2D = { const proposed: Aabb2D = {
minX: baseAabb.minX + dx, minX: baseAabb.minX + dx,
maxX: baseAabb.maxX + dx, maxX: baseAabb.maxX + dx,
@@ -168,7 +170,7 @@ export const MoveDuctSegmentTool: React.FC<{ node: AnyNode }> = ({ node }) => {
const cur: [number, number] = [centerX + dx, centerZ + dz] const cur: [number, number] = [centerX + dx, centerZ + dz]
if ( if (
!bypass && (isGridSnapActive() || isMagneticSnapActive()) &&
(!prevSnapRef.current || (!prevSnapRef.current ||
prevSnapRef.current[0] !== cur[0] || prevSnapRef.current[0] !== cur[0] ||
prevSnapRef.current[1] !== cur[1]) prevSnapRef.current[1] !== cur[1])
+8 -6
View File
@@ -13,6 +13,8 @@ import {
import { import {
DragBoundingBox, DragBoundingBox,
EDITOR_LAYER, EDITOR_LAYER,
isGridSnapActive,
isMagneticSnapActive,
markToolCancelConsumed, markToolCancelConsumed,
stripPlacementMetadataFlags, stripPlacementMetadataFlags,
triggerSFX, triggerSFX,
@@ -143,14 +145,14 @@ export const MoveLinesetTool: React.FC<{ node: AnyNode }> = ({ node }) => {
} }
const onMove = (event: GridEvent) => { const onMove = (event: GridEvent) => {
const bypass = event.nativeEvent?.shiftKey === true const snap = isGridSnapActive() ? snapToGridStep : (v: number) => v
const snap = bypass ? (v: number) => v : snapToGridStep
let dx = snap(event.localPosition[0] - centerX) let dx = snap(event.localPosition[0] - centerX)
let dz = snap(event.localPosition[2] - centerZ) let dz = snap(event.localPosition[2] - centerZ)
// Figma-style alignment: snap the run's footprint box edges onto // Figma-style magnetic alignment: snap the run's footprint box edges onto
// nearby geometry and publish the guides (Shift bypass). // nearby geometry and publish the guides. Grid follows the snapping mode;
if (!bypass) { // lines follow magnetic alignment — the two are independent.
if (isMagneticSnapActive()) {
const proposed: Aabb2D = { const proposed: Aabb2D = {
minX: baseAabb.minX + dx, minX: baseAabb.minX + dx,
maxX: baseAabb.maxX + dx, maxX: baseAabb.maxX + dx,
@@ -167,7 +169,7 @@ export const MoveLinesetTool: React.FC<{ node: AnyNode }> = ({ node }) => {
const cur: [number, number] = [centerX + dx, centerZ + dz] const cur: [number, number] = [centerX + dx, centerZ + dz]
if ( if (
!bypass && (isGridSnapActive() || isMagneticSnapActive()) &&
(!prevSnapRef.current || (!prevSnapRef.current ||
prevSnapRef.current[0] !== cur[0] || prevSnapRef.current[0] !== cur[0] ||
prevSnapRef.current[1] !== cur[1]) prevSnapRef.current[1] !== cur[1])
+8 -6
View File
@@ -13,6 +13,8 @@ import {
import { import {
DragBoundingBox, DragBoundingBox,
EDITOR_LAYER, EDITOR_LAYER,
isGridSnapActive,
isMagneticSnapActive,
markToolCancelConsumed, markToolCancelConsumed,
stripPlacementMetadataFlags, stripPlacementMetadataFlags,
triggerSFX, triggerSFX,
@@ -139,14 +141,14 @@ export const MoveLiquidLineTool: React.FC<{ node: AnyNode }> = ({ node }) => {
} }
const onMove = (event: GridEvent) => { const onMove = (event: GridEvent) => {
const bypass = event.nativeEvent?.shiftKey === true const snap = isGridSnapActive() ? snapToGridStep : (v: number) => v
const snap = bypass ? (v: number) => v : snapToGridStep
let dx = snap(event.localPosition[0] - centerX) let dx = snap(event.localPosition[0] - centerX)
let dz = snap(event.localPosition[2] - centerZ) let dz = snap(event.localPosition[2] - centerZ)
// Figma-style alignment: snap the run's footprint box edges onto nearby // Figma-style magnetic alignment: snap the run's footprint box edges onto
// geometry and publish the guides (Shift bypass). // nearby geometry and publish the guides. Grid follows the snapping mode;
if (!bypass) { // lines follow magnetic alignment — the two are independent.
if (isMagneticSnapActive()) {
const proposed: Aabb2D = { const proposed: Aabb2D = {
minX: baseAabb.minX + dx, minX: baseAabb.minX + dx,
maxX: baseAabb.maxX + dx, maxX: baseAabb.maxX + dx,
@@ -163,7 +165,7 @@ export const MoveLiquidLineTool: React.FC<{ node: AnyNode }> = ({ node }) => {
const cur: [number, number] = [centerX + dx, centerZ + dz] const cur: [number, number] = [centerX + dx, centerZ + dz]
if ( if (
!bypass && (isGridSnapActive() || isMagneticSnapActive()) &&
(!prevSnapRef.current || (!prevSnapRef.current ||
prevSnapRef.current[0] !== cur[0] || prevSnapRef.current[0] !== cur[0] ||
prevSnapRef.current[1] !== cur[1]) prevSnapRef.current[1] !== cur[1])
@@ -13,6 +13,8 @@ import {
import { import {
DragBoundingBox, DragBoundingBox,
EDITOR_LAYER, EDITOR_LAYER,
isGridSnapActive,
isMagneticSnapActive,
markToolCancelConsumed, markToolCancelConsumed,
stripPlacementMetadataFlags, stripPlacementMetadataFlags,
triggerSFX, triggerSFX,
@@ -141,14 +143,14 @@ export const MovePipeSegmentTool: React.FC<{ node: AnyNode }> = ({ node }) => {
} }
const onMove = (event: GridEvent) => { const onMove = (event: GridEvent) => {
const bypass = event.nativeEvent?.shiftKey === true const snap = isGridSnapActive() ? snapToGridStep : (v: number) => v
const snap = bypass ? (v: number) => v : snapToGridStep
let dx = snap(event.localPosition[0] - centerX) let dx = snap(event.localPosition[0] - centerX)
let dz = snap(event.localPosition[2] - centerZ) let dz = snap(event.localPosition[2] - centerZ)
// Figma-style alignment: snap the run's footprint box edges onto // Figma-style magnetic alignment: snap the run's footprint box edges onto
// nearby geometry and publish the guides (Shift bypass). // nearby geometry and publish the guides. Grid follows the snapping mode;
if (!bypass) { // lines follow magnetic alignment — the two are independent.
if (isMagneticSnapActive()) {
const proposed: Aabb2D = { const proposed: Aabb2D = {
minX: baseAabb.minX + dx, minX: baseAabb.minX + dx,
maxX: baseAabb.maxX + dx, maxX: baseAabb.maxX + dx,
@@ -165,7 +167,7 @@ export const MovePipeSegmentTool: React.FC<{ node: AnyNode }> = ({ node }) => {
const cur: [number, number] = [centerX + dx, centerZ + dz] const cur: [number, number] = [centerX + dx, centerZ + dz]
if ( if (
!bypass && (isGridSnapActive() || isMagneticSnapActive()) &&
(!prevSnapRef.current || (!prevSnapRef.current ||
prevSnapRef.current[0] !== cur[0] || prevSnapRef.current[0] !== cur[0] ||
prevSnapRef.current[1] !== cur[1]) prevSnapRef.current[1] !== cur[1])