Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/components/canvas/WireEdge.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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' };
Expand Down Expand Up @@ -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
Expand Down
11 changes: 5 additions & 6 deletions src/utils/geometry.js
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -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
Expand Down
Loading