UNPKG

@typecad/typecad

Version:

🤖programmatically 💥create 🛰️hardware

256 lines (255 loc) 11.5 kB
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var _Component_footprint_file, _Component_isInitialized; import fsexp from "fast-sexpr"; import { execSync } from "node:child_process"; import { randomUUID } from "node:crypto"; import fs from "node:fs"; import { platform } from 'node:os'; import SExpr from "s-expression.js"; import { kicad_cli_path, kicad_path } from "./kicad"; import { Pin } from "./pin"; import chalk from 'chalk'; import { ReferenceCounter } from "./reference_counter"; // Remove static import to prevent circular dependency // import { ComponentRegistry } from "./component_registry"; const referenceCounter = new ReferenceCounter(); const S = new SExpr(); /** * Represents a component in an electronic circuit. * @property reference - Reference designator (e.g., R1). * @property value - Component value (e.g., 1 kOhm). * @property footprint - Component footprint (e.g., Resistor_SMD:R_0603_1608Metric). * @property datasheet - Link to component datasheet. * @property description - Description of the component. * @property mpn - Manufacturer Part Number. * @property pcb - PCB placement details (x, y, rotation). * @property dnp - True if Do Not Place. * @property uuid - Unique identifier. * @property pins - Array of component pins. * @property via - True if the component is a via. * @property simulation - Simulation details. */ export class Component { /** * Creates an instance of Component. * @param {IComponent} [options={}] - The component options. */ constructor({ reference, value, footprint, prefix, datasheet, description, voltage, wattage, mpn, via, uuid, simulation, symbol, sch, pcb, viaData } = {}) { this.reference = ''; this.value = ''; this.footprint = ''; this.datasheet = ''; this.description = ''; this.voltage = ''; this.wattage = ''; this.mpn = ''; this.pcb = { x: 0, y: 0, rotation: 0, side: 'front' }; this.dnp = false; _Component_footprint_file.set(this, ''); this.pins = []; this.via = false; this.simulation = { include: false, model: '' }; this.symbol = ''; this.sch = { x: 0, y: 0, rotation: 0 }; this.groups = []; // Track which groups this component belongs to _Component_isInitialized.set(this, false); // Private backing field for uuid this._uuid = ''; // Private flag to prevent recursion this._gettingUuid = false; if (reference != undefined) { this.reference = reference; const referencePattern = /^[A-Za-z]+\d+$/; if (!referencePattern.test(reference)) { process.stdout.write(chalk.bgRed(`👺 Error:`) + chalk.bold(` [${reference}, ${value}, ${footprint}] Pin number must be a number or a string` + '\n')); process.exit(1); } if (!referenceCounter.setReference(reference)) { this.reference = referenceCounter.getNextReference(prefix || 'U'); //console.log(` 🚩 renaming ${reference} to ${this.reference}`); process.stdout.write(chalk.whiteBright.bgYellow(`🚩 renaming ${reference} to ${this.reference}` + '\n')); } } else { this.reference = referenceCounter.getNextReference(prefix || 'U'); } if (value != undefined) this.value = value; if (footprint != undefined) this.footprint = footprint; if (datasheet != undefined) this.datasheet = datasheet; if (description != undefined) this.description = description; if (voltage != undefined) this.voltage = voltage; if (wattage != undefined) this.wattage = wattage; if (symbol != undefined) this.symbol = symbol; if (sch != undefined) this.sch = sch; if (pcb != undefined) this.pcb = pcb; if (mpn != undefined) this.mpn = mpn; if (simulation != undefined) this.simulation = { include: simulation.include, model: simulation.model || '' }; this.via = via || false; if (viaData) { // If viaData is provided, store it this.viaData = viaData; } // Allow explicitly set UUID to override (for backward compatibility) if (uuid != undefined) { this._uuid = uuid; } if (this.via) { // For vias, the concept of a component "reference" like U1, R1 doesn't directly apply in the same way. // We log based on its UUID, which is its primary identifier from pcb.ts context. // The this.uuid getter will provide the UUID (either passed in or from registry). // process.stdout.write(chalk.cyan.bold(`Via`) + ' created\n'); } else { // process.stdout.write(chalk.blue.bold(this.reference) + ' created\n'); } __classPrivateFieldSet(this, _Component_isInitialized, true, "f"); } /** * Get the UUID for this component. If not explicitly set, it will be generated * consistently based on component properties. */ get uuid() { // If uuid is not set yet and the component is fully initialized, // get it from the registry if (this._uuid === '' && __classPrivateFieldGet(this, _Component_isInitialized, "f")) { // Prevent circular dependency by checking if we're already in the process of getting a UUID if (this._gettingUuid) { return randomUUID(); // Fallback to random UUID to break recursion } try { this._gettingUuid = true; // Fallback to a random UUID if component registry is not available this._uuid = randomUUID(); } finally { this._gettingUuid = false; } } return this._uuid; } /** * Set the UUID for this component */ set uuid(value) { this._uuid = value; } /** * Returns a {@link Pin} object from the component. * @param number - The pin number or identifier. * @returns The pin object. */ pin(number) { if (typeof number !== 'number' && typeof number !== 'string') { process.stdout.write(chalk.bgRed(`👺 Error:`) + chalk.bold(` Pin number must be a number or a string` + '\n')); process.exit(1); } // Ensure pin number is a string for consistent comparisons later if needed const pinNumberStr = String(number); const existingPin = this.pins.find(pin => pin.number === pinNumberStr); if (existingPin) { // Ensure existing pin also has the uuid if it was added before this logic if (!existingPin.uuid) { existingPin.uuid = this.uuid; } return existingPin; } const newPin = new Pin(this.reference, pinNumberStr, undefined, this); // Add the component's UUID directly to the Pin object // This makes it accessible for lookups like p.uuid in pcb.ts newPin.uuid = this.uuid; this.pins.push(newPin); return newPin; } /** * Check if this component belongs to a specific group. * @param groupName - The name of the group to check. * @returns True if the component is in the specified group. */ isInGroup(groupName) { return this.groups.includes(groupName); } /** * Get all groups this component belongs to. * @returns Array of group names this component is a member of. */ getGroups() { return [...this.groups]; // Return a copy to prevent external modification } /** * Retrieves the footprint library for the component. * Used internally to manage footprint files. * * @param {string} footprint - The footprint identifier * @returns {string} - The serialized footprint data * @ignore */ footprint_lib(footprint) { const runCommand = (command) => { try { execSync(`${command}`, { stdio: 'ignore' }); } catch (e) { console.log(e); } return true; }; // if we already found this symbol, return it before doing anything if (__classPrivateFieldGet(this, _Component_footprint_file, "f") != '') return __classPrivateFieldGet(this, _Component_footprint_file, "f"); let footprint_file_contents = ""; let footprint_file_name = footprint.split(":"); let kicad_footprint = ''; if (platform() == 'win32') { kicad_footprint = kicad_path + 'share/kicad/footprints'; } else { kicad_footprint = kicad_path + 'footprints'; } try { if (fs.existsSync(`${kicad_footprint}/${footprint_file_name[0]}.pretty/${footprint_file_name[1]}.kicad_mod`)) { footprint_file_contents = fs.readFileSync(`${kicad_footprint}/${footprint_file_name[0]}.pretty/${footprint_file_name[1]}.kicad_mod`, "utf8"); } else { // if the footprint is in this folder, it came from elsewhere and might be an older format // run kicad-cli to fix/upgrade the footprint runCommand(`"${kicad_cli_path}" fp upgrade ./build/lib/footprints/`); footprint_file_contents = fs.readFileSync(`./build/lib/footprints/${footprint_file_name[1]}.kicad_mod`, "utf8"); } footprint_file_contents = footprint_file_contents.replaceAll('"', "`"); const l = fsexp(footprint_file_contents).pop(); // some footprints have a 'module' instead of 'footprint' if (l[0] == 'module') { l[0] = 'footprint'; } __classPrivateFieldSet(this, _Component_footprint_file, S.serialize(l).replaceAll("`", '"'), "f"); } catch (err) { console.error(err); } if (__classPrivateFieldGet(this, _Component_footprint_file, "f") == '') { // Error for missing footprint is handled by the calling context (e.g. pcb.create) } return __classPrivateFieldGet(this, _Component_footprint_file, "f"); } } _Component_footprint_file = new WeakMap(), _Component_isInitialized = new WeakMap();