From 8e9a45a9d587e3fa7df652550c242d3d616f8139 Mon Sep 17 00:00:00 2001 From: Wassim SAMAD Date: Thu, 25 Jun 2026 12:06:31 -0400 Subject: [PATCH] =?UTF-8?q?feat(editor):=20MEP=20move-tools=20=E2=80=94=20?= =?UTF-8?q?mode-driven=20snapping=20(drop=20Shift=3Dbypass)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- packages/nodes/src/duct-fitting/move-tool.tsx | 18 ++++++++++++------ packages/nodes/src/duct-segment/move-tool.tsx | 14 ++++++++------ packages/nodes/src/lineset/move-tool.tsx | 14 ++++++++------ packages/nodes/src/liquid-line/move-tool.tsx | 14 ++++++++------ packages/nodes/src/pipe-segment/move-tool.tsx | 14 ++++++++------ 5 files changed, 44 insertions(+), 30 deletions(-) diff --git a/packages/nodes/src/duct-fitting/move-tool.tsx b/packages/nodes/src/duct-fitting/move-tool.tsx index f378d622..fb800d43 100644 --- a/packages/nodes/src/duct-fitting/move-tool.tsx +++ b/packages/nodes/src/duct-fitting/move-tool.tsx @@ -13,6 +13,8 @@ import { import { DragBoundingBox, EDITOR_LAYER, + isGridSnapActive, + isMagneticSnapActive, markToolCancelConsumed, stripPlacementMetadataFlags, triggerSFX, @@ -177,14 +179,14 @@ export const MoveDuctFittingTool: React.FC<{ node: AnyNode }> = ({ node }) => { let lastPos: Vec3 = originalPosition const onMove = (event: GridEvent) => { - const bypass = event.nativeEvent?.shiftKey === true - const snap = bypass ? (v: number) => v : snapToGridStep + const snap = isGridSnapActive() ? snapToGridStep : (v: number) => v let x = snap(event.localPosition[0]) let z = snap(event.localPosition[2]) - // Alignment: snap the footprint box edges onto nearby geometry and - // publish guides (Alt / Shift bypass). - if (!bypass) { + // Magnetic alignment: snap the footprint box edges onto nearby geometry + // and publish guides. Grid follows the snapping mode; lines follow + // magnetic alignment — the two are independent. + if (isMagneticSnapActive()) { const proposed: Aabb2D = { minX: 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] - 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 hasMoved = true setCursorPos(next) diff --git a/packages/nodes/src/duct-segment/move-tool.tsx b/packages/nodes/src/duct-segment/move-tool.tsx index 8b81a516..0fda99fa 100644 --- a/packages/nodes/src/duct-segment/move-tool.tsx +++ b/packages/nodes/src/duct-segment/move-tool.tsx @@ -13,6 +13,8 @@ import { import { DragBoundingBox, EDITOR_LAYER, + isGridSnapActive, + isMagneticSnapActive, markToolCancelConsumed, stripPlacementMetadataFlags, triggerSFX, @@ -144,14 +146,14 @@ export const MoveDuctSegmentTool: React.FC<{ node: AnyNode }> = ({ node }) => { } const onMove = (event: GridEvent) => { - const bypass = event.nativeEvent?.shiftKey === true - const snap = bypass ? (v: number) => v : snapToGridStep + const snap = isGridSnapActive() ? snapToGridStep : (v: number) => v let dx = snap(event.localPosition[0] - centerX) let dz = snap(event.localPosition[2] - centerZ) - // Figma-style alignment: snap the run's footprint box edges onto - // nearby geometry and publish the guides (Alt / Shift bypass). - if (!bypass) { + // Figma-style magnetic alignment: snap the run's footprint box edges onto + // nearby geometry and publish the guides. Grid follows the snapping mode; + // lines follow magnetic alignment — the two are independent. + if (isMagneticSnapActive()) { const proposed: Aabb2D = { minX: baseAabb.minX + 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] if ( - !bypass && + (isGridSnapActive() || isMagneticSnapActive()) && (!prevSnapRef.current || prevSnapRef.current[0] !== cur[0] || prevSnapRef.current[1] !== cur[1]) diff --git a/packages/nodes/src/lineset/move-tool.tsx b/packages/nodes/src/lineset/move-tool.tsx index a025b534..7464ad87 100644 --- a/packages/nodes/src/lineset/move-tool.tsx +++ b/packages/nodes/src/lineset/move-tool.tsx @@ -13,6 +13,8 @@ import { import { DragBoundingBox, EDITOR_LAYER, + isGridSnapActive, + isMagneticSnapActive, markToolCancelConsumed, stripPlacementMetadataFlags, triggerSFX, @@ -143,14 +145,14 @@ export const MoveLinesetTool: React.FC<{ node: AnyNode }> = ({ node }) => { } const onMove = (event: GridEvent) => { - const bypass = event.nativeEvent?.shiftKey === true - const snap = bypass ? (v: number) => v : snapToGridStep + const snap = isGridSnapActive() ? snapToGridStep : (v: number) => v let dx = snap(event.localPosition[0] - centerX) let dz = snap(event.localPosition[2] - centerZ) - // Figma-style alignment: snap the run's footprint box edges onto - // nearby geometry and publish the guides (Shift bypass). - if (!bypass) { + // Figma-style magnetic alignment: snap the run's footprint box edges onto + // nearby geometry and publish the guides. Grid follows the snapping mode; + // lines follow magnetic alignment — the two are independent. + if (isMagneticSnapActive()) { const proposed: Aabb2D = { minX: baseAabb.minX + 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] if ( - !bypass && + (isGridSnapActive() || isMagneticSnapActive()) && (!prevSnapRef.current || prevSnapRef.current[0] !== cur[0] || prevSnapRef.current[1] !== cur[1]) diff --git a/packages/nodes/src/liquid-line/move-tool.tsx b/packages/nodes/src/liquid-line/move-tool.tsx index 3e5c8141..db664c5c 100644 --- a/packages/nodes/src/liquid-line/move-tool.tsx +++ b/packages/nodes/src/liquid-line/move-tool.tsx @@ -13,6 +13,8 @@ import { import { DragBoundingBox, EDITOR_LAYER, + isGridSnapActive, + isMagneticSnapActive, markToolCancelConsumed, stripPlacementMetadataFlags, triggerSFX, @@ -139,14 +141,14 @@ export const MoveLiquidLineTool: React.FC<{ node: AnyNode }> = ({ node }) => { } const onMove = (event: GridEvent) => { - const bypass = event.nativeEvent?.shiftKey === true - const snap = bypass ? (v: number) => v : snapToGridStep + const snap = isGridSnapActive() ? snapToGridStep : (v: number) => v let dx = snap(event.localPosition[0] - centerX) let dz = snap(event.localPosition[2] - centerZ) - // Figma-style alignment: snap the run's footprint box edges onto nearby - // geometry and publish the guides (Shift bypass). - if (!bypass) { + // Figma-style magnetic alignment: snap the run's footprint box edges onto + // nearby geometry and publish the guides. Grid follows the snapping mode; + // lines follow magnetic alignment — the two are independent. + if (isMagneticSnapActive()) { const proposed: Aabb2D = { minX: baseAabb.minX + 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] if ( - !bypass && + (isGridSnapActive() || isMagneticSnapActive()) && (!prevSnapRef.current || prevSnapRef.current[0] !== cur[0] || prevSnapRef.current[1] !== cur[1]) diff --git a/packages/nodes/src/pipe-segment/move-tool.tsx b/packages/nodes/src/pipe-segment/move-tool.tsx index cfa93e39..815d64de 100644 --- a/packages/nodes/src/pipe-segment/move-tool.tsx +++ b/packages/nodes/src/pipe-segment/move-tool.tsx @@ -13,6 +13,8 @@ import { import { DragBoundingBox, EDITOR_LAYER, + isGridSnapActive, + isMagneticSnapActive, markToolCancelConsumed, stripPlacementMetadataFlags, triggerSFX, @@ -141,14 +143,14 @@ export const MovePipeSegmentTool: React.FC<{ node: AnyNode }> = ({ node }) => { } const onMove = (event: GridEvent) => { - const bypass = event.nativeEvent?.shiftKey === true - const snap = bypass ? (v: number) => v : snapToGridStep + const snap = isGridSnapActive() ? snapToGridStep : (v: number) => v let dx = snap(event.localPosition[0] - centerX) let dz = snap(event.localPosition[2] - centerZ) - // Figma-style alignment: snap the run's footprint box edges onto - // nearby geometry and publish the guides (Shift bypass). - if (!bypass) { + // Figma-style magnetic alignment: snap the run's footprint box edges onto + // nearby geometry and publish the guides. Grid follows the snapping mode; + // lines follow magnetic alignment — the two are independent. + if (isMagneticSnapActive()) { const proposed: Aabb2D = { minX: baseAabb.minX + 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] if ( - !bypass && + (isGridSnapActive() || isMagneticSnapActive()) && (!prevSnapRef.current || prevSnapRef.current[0] !== cur[0] || prevSnapRef.current[1] !== cur[1])