diff --git a/src/components/canvas/WireEdge.jsx b/src/components/canvas/WireEdge.jsx index 6399730..5fd7f45 100644 --- a/src/components/canvas/WireEdge.jsx +++ b/src/components/canvas/WireEdge.jsx @@ -8,6 +8,8 @@ import { useRouting } from './RoutingContext'; import { useDrcMarks } from './DrcContext'; import useDiagramStore from '../../store/diagramStore'; import { buildSvgPath, STEP } from '../../utils/routingUtils'; +import { portPosition } from '../../utils/geometry'; +import { getDefinition } from '../../data/componentLibrary'; const MIN_SEG = 26; // only show an insert dot on segments at least this long (px) const DRC_HALO = { error: '#f87171', warning: '#fbbf24', info: '#38bdf8' }; @@ -73,7 +75,16 @@ function WireEdge({ id, selected }) { ev.preventDefault(); dragRef.current = { idx, pts: base }; setDraft(base); - const others = base.filter((_, k) => k !== idx); + // Axis-lock candidates: this wire's other control points + every port in the + // diagram. Collected once at drag-start (getState, non-reactive) so the per- + // frame move handler can run without subscribing to the full node list. + const { nodes: allNodes, customDefinitions: allDefs } = useDiagramStore.getState(); + const allPortPts = allNodes.flatMap((n) => { + const d = getDefinition(n.data.definitionId, allDefs); + if (!d) return []; + return (n.data.ports ?? []).map((p) => portPosition(n, p, d)); + }); + const others = [...base.filter((_, k) => k !== idx), ...allPortPts]; const onMove = (e) => { // Work from the RAW cursor position so axis-locking onto a port/waypoint // wins over the grid: with a capture radius wider than the grid step, a diff --git a/src/utils/geometry.js b/src/utils/geometry.js index e987cc8..81fe244 100644 --- a/src/utils/geometry.js +++ b/src/utils/geometry.js @@ -1,6 +1,5 @@ // Shared geometry for port placement — used by ComponentNode (handle CSS %) // and routingUtils (absolute flow coordinates). Both MUST agree. -import { STEP } from './routingUtils'; export function portsOnSide(ports, side) { return ports.filter((p) => p.side === side).sort((a, b) => a.order - b.order); @@ -14,13 +13,13 @@ export function portFraction(ports, port) { return (idx + 1) / (n + 1); } -// Offset in px from the edge's start for a port, snapped to the routing lattice -// so wires leave the port aligned to the grid (no off-axis jog). `edgeLen` is -// the side length (height for left/right, width for top/bottom). +// Offset in px from the edge's start for a port. Uses the exact fractional +// position (rounded to nearest pixel) so ports are always evenly spaced +// regardless of the routing grid step. The routing engine handles sub-grid +// alignment via the exit stub, so no grid-snap is needed here. export function portOffsetPx(ports, port, edgeLen) { const raw = edgeLen * portFraction(ports, port); - const snapped = Math.round(raw / STEP) * STEP; - return Math.min(edgeLen - 1, Math.max(1, snapped)); + return Math.min(edgeLen - 1, Math.max(1, Math.round(raw))); } // Snapped fraction (0..1) used for the visual handle/label so the dot sits