@typecad/typecad
Version:
🤖programmatically 💥create 🛰️hardware
256 lines (255 loc) • 11.5 kB
JavaScript
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();