UNPKG

@typecad/typecad

Version:

🤖programmatically 💥create 🛰️hardware

183 lines (182 loc) • 6.7 kB
import { randomUUID } from 'node:crypto'; import fs from 'node:fs'; import path from 'node:path'; import chalk from 'chalk'; /** * Registry for managing component UUIDs and reusing them across builds */ export class ComponentRegistry { /** * Get the number of components in the registry */ getComponentCount() { return this.components.size; } /** * Get the number of UUID to hash mappings in the registry */ getUuidToHashCount() { return this.uuidToHash.size; } constructor() { this.components = new Map(); // Map of component hash to UUID this.uuidToHash = new Map(); // Reverse lookup (UUID to hash) this.cachePath = path.join(process.cwd(), 'build', 'cache', 'component_registry.json'); this.loadFromCache(); } /** * Get the singleton instance of the registry */ static getInstance() { if (!ComponentRegistry.instance) { ComponentRegistry.instance = new ComponentRegistry(); } return ComponentRegistry.instance; } /** * Load existing component registry from cache */ loadFromCache() { try { if (fs.existsSync(this.cachePath)) { const data = JSON.parse(fs.readFileSync(this.cachePath, 'utf8')); // Process each entry and build both maps if (data.components) { Object.entries(data.components).forEach(([hash, uuid]) => { this.components.set(hash, uuid); this.uuidToHash.set(uuid, hash); }); console.log(chalk.green(`📋 Loaded ${this.components.size} component UUIDs from cache`)); } } } catch (error) { console.warn(chalk.yellow('⚠️ Failed to load component registry from cache'), error); // Create a new registry if loading fails this.components = new Map(); this.uuidToHash = new Map(); } } /** * Save the current registry to cache */ saveToCache() { try { // Ensure directory exists const dir = path.dirname(this.cachePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } // Save the cache const data = { components: Object.fromEntries(this.components), meta: { lastUpdated: new Date().toISOString(), componentCount: this.components.size } }; fs.writeFileSync(this.cachePath, JSON.stringify(data, null, 2)); } catch (error) { console.warn(chalk.yellow('⚠️ Failed to save component registry to cache'), error); } } /** * Generate a positional hash for PCB coordinates with tolerance * This ensures components in roughly the same position get the same hash * even with minor position adjustments */ getPCBPositionHash(pcb) { if (!pcb || pcb.x === undefined || pcb.y === undefined) { return 'no-position'; } // Round to nearest millimeter to provide some tolerance for small changes // Adjust tolerance as needed for your application const tolerance = 1; // 1mm tolerance const x = Math.round(pcb.x / tolerance) * tolerance; const y = Math.round(pcb.y / tolerance) * tolerance; const rotation = pcb.rotation !== undefined ? Math.round(pcb.rotation / 45) * 45 : // Round to nearest 45 degrees 0; const side = pcb.side || 'front'; // Default to 'front' if side is not specified return `pos:${x},${y},${rotation},${side}`; } /** * Generate a unique hash for a component based on its properties * Prioritizes stable properties that won't change between builds * Avoids using reference designator which might change * * @param component The component to generate a hash for * @returns A string hash representing the component's unique properties */ getComponentHash(component) { // Create a hash based on stable component properties that identify it uniquely // Using component as any to avoid circular dependencies with property access // Explicitly NOT using reference designator since it can change const hashParts = [ // Type info component.symbol || 'no-symbol', component.value || 'no-value', component.footprint || 'no-footprint', // Manufacturer info (if available) component.mpn || 'no-mpn', // Physical positioning - crucial for identifying components this.getPCBPositionHash(component.pcb), // Additional identifiers component.voltage || '', component.wattage || '', component.description?.substring(0, 50) || '' // Truncate long descriptions ]; return hashParts.filter(part => part !== '').join('::'); } /** * Get or create a UUID for the given component * Will reuse existing UUID if a similar component is found * * @param component The component to get a UUID for * @returns A UUID string */ getUUID(component) { // Check early if component already has a uuid to avoid recursion if (component._uuid && component._uuid !== '') { return component._uuid; } const hash = this.getComponentHash(component); // If we already have a UUID for this component hash, return it if (this.components.has(hash)) { return this.components.get(hash); } // Create a new UUID for this component const newUuid = randomUUID(); this.components.set(hash, newUuid); this.uuidToHash.set(newUuid, hash); this.saveToCache(); return newUuid; } /** * Find a component by UUID * Useful for cross-referencing components * * @param uuid UUID to lookup * @returns The component hash or undefined if not found */ findComponentHashByUUID(uuid) { return this.uuidToHash.get(uuid); } /** * Get all registered UUIDs * @returns Array of UUIDs */ getAllUUIDs() { return Array.from(this.uuidToHash.keys()); } /** * Clear the registry * Use with caution as this will invalidate all component UUIDs */ clear() { this.components.clear(); this.uuidToHash.clear(); this.saveToCache(); } }