139 lines
4.6 KiB
TypeScript
139 lines
4.6 KiB
TypeScript
/**
|
|
* Pin Position Calculator
|
|
*
|
|
* Converts pin coordinates from element space to canvas space (pixels).
|
|
* This is the CRITICAL piece for wire system - without accurate pin positions,
|
|
* wires cannot connect properly to components.
|
|
*
|
|
* Coordinate Systems:
|
|
* 1. Element Space: Pin positions in pinInfo are in CSS pixels relative to element origin
|
|
* 2. Canvas Space: Absolute positioning in pixels on the canvas
|
|
*
|
|
* Note: wokwi-elements pinInfo x/y are already in CSS pixels.
|
|
*/
|
|
|
|
/**
|
|
* Calculates the absolute canvas position of a specific pin.
|
|
*
|
|
* @param componentId - The DOM ID of the component element
|
|
* @param pinName - The name of the pin (e.g., 'A', 'C', 'GND.1', '13')
|
|
* @param componentX - Component's X position on canvas (pixels)
|
|
* @param componentY - Component's Y position on canvas (pixels)
|
|
* @returns Absolute canvas coordinates { x, y } or null if pin not found
|
|
*/
|
|
export function calculatePinPosition(
|
|
componentId: string,
|
|
pinName: string,
|
|
componentX: number,
|
|
componentY: number
|
|
): { x: number; y: number } | null {
|
|
// Get the DOM element
|
|
const element = document.getElementById(componentId);
|
|
if (!element) {
|
|
console.warn(`[pinPositionCalculator] Component ${componentId} not found in DOM`);
|
|
return null;
|
|
}
|
|
|
|
// Access the pinInfo property (all wokwi-elements expose this)
|
|
const pinInfo = (element as any).pinInfo;
|
|
if (!pinInfo || !Array.isArray(pinInfo)) {
|
|
console.warn(`[pinPositionCalculator] Component ${componentId} does not have pinInfo`);
|
|
return null;
|
|
}
|
|
|
|
// Find the specific pin
|
|
let pin = pinInfo.find((p: any) => p.name === pinName);
|
|
// Fallback: try numbered variant (e.g. GND → GND.1) for pins that have suffix variants
|
|
if (!pin && !pinName.includes('.')) {
|
|
pin = pinInfo.find((p: any) => p.name === `${pinName}.1`);
|
|
}
|
|
// Fallback: GP-prefix → match description field (e.g. 'GP15' → description 'GPIO15')
|
|
// Needed for Nano RP2040 Connect which uses D-prefix pin names but GPIO descriptions
|
|
if (!pin && pinName.startsWith('GP')) {
|
|
const gpioNum = parseInt(pinName.substring(2), 10);
|
|
if (!isNaN(gpioNum)) {
|
|
pin = pinInfo.find((p: any) => p.description === `GPIO${gpioNum}`);
|
|
}
|
|
}
|
|
if (!pin) {
|
|
console.warn(`[pinPositionCalculator] Pin ${pinName} not found on component ${componentId}`);
|
|
console.warn(`Available pins:`, pinInfo.map((p: any) => p.name));
|
|
return null;
|
|
}
|
|
|
|
// Pin coordinates are already in CSS pixels, just add component position
|
|
const pinX = componentX + pin.x;
|
|
const pinY = componentY + pin.y;
|
|
|
|
return { x: pinX, y: pinY };
|
|
}
|
|
|
|
/**
|
|
* Gets all pins for a component with their absolute canvas positions.
|
|
* Useful for rendering pin overlays and finding nearby pins.
|
|
*
|
|
* @param componentId - The DOM ID of the component element
|
|
* @param componentX - Component's X position on canvas
|
|
* @param componentY - Component's Y position on canvas
|
|
* @returns Array of pins with absolute positions and signal info
|
|
*/
|
|
export function getAllPinPositions(
|
|
componentId: string,
|
|
componentX: number,
|
|
componentY: number
|
|
): Array<{ name: string; x: number; y: number; signals: any[] }> {
|
|
const element = document.getElementById(componentId);
|
|
if (!element) return [];
|
|
|
|
const pinInfo = (element as any).pinInfo;
|
|
if (!pinInfo || !Array.isArray(pinInfo)) return [];
|
|
|
|
return pinInfo.map((pin: any) => ({
|
|
name: pin.name,
|
|
x: componentX + pin.x,
|
|
y: componentY + pin.y,
|
|
signals: pin.signals || [],
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Finds the closest pin to a given canvas position.
|
|
* Useful for snapping wire endpoints to nearby pins.
|
|
*
|
|
* @param componentId - The component to search
|
|
* @param componentX - Component's X position
|
|
* @param componentY - Component's Y position
|
|
* @param targetX - Target X coordinate to find nearest pin
|
|
* @param targetY - Target Y coordinate to find nearest pin
|
|
* @param maxDistance - Maximum distance in pixels to consider (default 20)
|
|
* @returns Closest pin info or null if none within maxDistance
|
|
*/
|
|
export function findClosestPin(
|
|
componentId: string,
|
|
componentX: number,
|
|
componentY: number,
|
|
targetX: number,
|
|
targetY: number,
|
|
maxDistance: number = 20
|
|
): { name: string; x: number; y: number; signals: any[] } | null {
|
|
const pins = getAllPinPositions(componentId, componentX, componentY);
|
|
|
|
let closestPin: { name: string; x: number; y: number; signals: any[] } | null = null;
|
|
let minDistance = maxDistance;
|
|
|
|
for (const pin of pins) {
|
|
const distance = Math.sqrt(
|
|
Math.pow(pin.x - targetX, 2) + Math.pow(pin.y - targetY, 2)
|
|
);
|
|
|
|
if (distance < minDistance) {
|
|
minDistance = distance;
|
|
closestPin = pin;
|
|
}
|
|
}
|
|
|
|
return closestPin;
|
|
}
|
|
|
|
|