fix(wires): stop board-corner wire anchors — alias fallbacks for pin-name spellings

A wire endpoint naming a pin the board element does not expose silently
resolved to nothing and rendered anchored at the board corner. A full
audit (all gallery examples + every saved project on velxio.dev) found 15
examples and 28 user projects doing it — the same electrical pin spelled
differently per surface: GND.1 on the classic ESP32 (element exposes
GND/GND2), 3.3V vs 3V3, GND2 vs GND.2, D13 vs 13 vs GP13, A0-A2 vs
GP26-28 on the Pico, 36/39 vs VP/VN, bare 5V vs VBUS/VIN.

calculatePinPosition now tries those equivalences AFTER the exact match
and the two existing fallbacks miss (so no current resolution changes),
healing old saves, .vlx imports and agent-authored wires without a data
migration. The Pico element gains A0/A1/A2 alias pads on the ADC pins
(RX2/16 dual-entry precedent) and a GP25 pad at the onboard LED silk so
pico-blink-style wires have an anchor. The two examples that were
outright wrong (microsd-card-esp32 GND.1, pico-serial-echo TX) now use
the element's real pin names.

Netlist snapshots (166) untouched; verifier + builder suites green.
This commit is contained in:
David Montero 2026-08-15 20:08:40 +02:00
parent fe8ac7e4e3
commit aed1796a8f
4 changed files with 38 additions and 2 deletions

View File

@ -39,15 +39,26 @@ const PINS_PI_PICO_W = [
{ name: 'GP22', x: 97, y: 157 },
{ name: 'RUN', x: 97, y: 144 },
{ name: 'GP26', x: 97, y: 131 },
// Alias entries: examples and saved projects also reference the ADC pins
// as A0/A1/A2 (Arduino-style) — same physical pins, same coordinates.
// Without these the wire endpoint anchored at the board corner. Mirrors
// the RX2/16 dual-entry precedent in Esp32Element.
{ name: 'A0', x: 97, y: 131 },
{ name: 'GP27', x: 97, y: 119 },
{ name: 'A1', x: 97, y: 119 },
{ name: 'GND.7', x: 97, y: 106 },
{ name: 'GP28', x: 97, y: 93 },
{ name: 'A2', x: 97, y: 93 },
{ name: 'ADC_VREF', x: 97, y: 81 },
{ name: '3V3', x: 97, y: 68 },
{ name: '3V3_EN', x: 97, y: 55 },
{ name: 'GND.8', x: 97, y: 42 },
{ name: 'VSYS', x: 97, y: 30 },
{ name: 'VBUS', x: 97, y: 17 },
// GP25 is the onboard LED, not a header pin — but pico-blink-style
// examples wire an external LED to it for visibility. Anchor at the LED
// silk so those wires stop falling back to the board corner.
{ name: 'GP25', x: 40, y: 14 },
];
class PiPicoWElement extends HTMLElement {

View File

@ -194,7 +194,7 @@ export const microsdExamples: ExampleProject[] = [
{ id: 'w-miso', start: { componentId: 'esp32', pinName: '19' }, end: { componentId: 'sd1', pinName: 'MISO' }, color: '#22cc22' },
{ id: 'w-sck', start: { componentId: 'esp32', pinName: '18' }, end: { componentId: 'sd1', pinName: 'SCK' }, color: '#ffdd33' },
{ id: 'w-vcc', start: { componentId: 'esp32', pinName: '3V3' }, end: { componentId: 'sd1', pinName: 'VCC' }, color: '#ff4444' },
{ id: 'w-gnd', start: { componentId: 'esp32', pinName: 'GND.1' }, end: { componentId: 'sd1', pinName: 'GND' }, color: '#000000' },
{ id: 'w-gnd', start: { componentId: 'esp32', pinName: 'GND' }, end: { componentId: 'sd1', pinName: 'GND' }, color: '#000000' },
],
},
];

View File

@ -4484,7 +4484,7 @@ void loop() {
wires: [
{
id: 'w1',
start: { componentId: 'arduino-uno', pinName: 'TX' },
start: { componentId: 'arduino-uno', pinName: 'GP0' },
end: { componentId: 'r-led-rx-a', pinName: '1' },
color: '#ff8800',
},

View File

@ -71,6 +71,31 @@ export function calculatePinPosition(
pin = pinInfo.find((p: any) => p.description === `GPIO${gpioNum}`);
}
}
// Generic alias fallbacks — every unresolved endpoint used to silently
// anchor at the board CORNER (0,0 of the element), which users read as a
// broken circuit. The names below are electrically identical spellings
// that boards/examples/saved projects disagree on (a 2026-08 audit found
// 15 gallery examples and 28 saved projects wired to a spelling their
// board element doesn't expose). Only consulted after exact + the two
// legacy fallbacks above miss, so no existing resolution changes.
if (!pin) {
const candidates: string[] = [];
const dot = pinName.match(/^(.+)\.\d+$/);
if (dot) candidates.push(dot[1]); // GND.1 -> GND (esp32 classic)
if (pinName === '3.3V') candidates.push('3V3', '3V3.1', '3V');
if (pinName === '3V3') candidates.push('3V3.1', '3.3V', '3V');
if (pinName === '5V') candidates.push('5V.1', 'VBUS', 'VIN');
if (pinName === 'GND2') candidates.push('GND.2');
const dNum = pinName.match(/^D(\d+)$/);
if (dNum) candidates.push(dNum[1], `GP${dNum[1]}`); // D13 -> 13 (esp32) / GP13 (pico)
if (/^\d+$/.test(pinName)) candidates.push(`D${pinName}`, `GP${pinName}`);
if (pinName === '36') candidates.push('VP'); // ESP32 devkit silk for GPIO36
if (pinName === '39') candidates.push('VN'); // ESP32 devkit silk for GPIO39
for (const c of candidates) {
pin = pinInfo.find((p: any) => p.name === c);
if (pin) break;
}
}
if (!pin) {
console.warn(`[pinPositionCalculator] Pin ${pinName} not found on component ${componentId}`);
console.warn(