Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
"type": "node",
"request": "launch",
"name": "Launch Program",
"program": "${workspaceFolder}/examples/bus.ts",
"program": "${workspaceFolder}/examples/easyeda.ts",
"outFiles": [
"${workspaceFolder}/dist/bus.js"
"${workspaceFolder}/dist/easyeda.js"
]
}
]
Expand Down
179 changes: 179 additions & 0 deletions examples/easyeda.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { and, Circuit, createCache, defineModule, isolateGates, Iter, metadata, ModuleId, Net, pinHeaders1x4, pinMapping, reversePinMapping, Rewire } from "../src";
import { EasyEDA, fetchPart, getSymbol, LCSCPartNumber } from "../src/export/easyeda/api";
import { Schematic } from "../src/export/easyeda/parse";
import { writeFile } from 'fs/promises';

/**
* name: 74HC08D,653
* package: SOIC-14_L8.7-W3.9-P1.27-LS6.0-BL
*/
const C5593 = defineModule({
name: 'C5593',
inputs: { a: 4, b: 4, gnd: 1, vcc: 1 },
outputs: { y: 4 },
lcsc: { partNumber: 'C5593' },
simulate() {
throw new Error('auto-generated module definition for C5593');
}
});

const top = defineModule({
name: 'top',
inputs: {},
outputs: {},
connect() {
const a = pinHeaders1x4();
const b = pinHeaders1x4();
const y = pinHeaders1x4();

const [and1, and2, and3, and4] = isolateGates(C5593());

y.out.pins = [
and4(a.out.pins[3], b.out.pins[3]),
and3(a.out.pins[2], b.out.pins[2]),
and2(a.out.pins[1], b.out.pins[1]),
and1(a.out.pins[0], b.out.pins[0]),
];
},
})();

const generateSchematic = async (circuit: Circuit) => {
const partCache = createCache<LCSCPartNumber, any>();

const symbols = new Map<ModuleId, Schematic['Symbol']>();
const pins = createCache<ModuleId, Map<string, { pinNumber: number, x: number, y: number }>>();

const MAX_WIDTH = 2000; // px
const MARGIN_X = 20; // px
const MARGIN_Y = 20; // px

let totalWidth = 0;
let maxHeight = 0;

const currentPos = { x: 0, y: 0 }; // top left

for (const [id, node] of circuit.modules) {
const sig = circuit.signatures.get(node.name)!;

if (sig.lcsc == null) {
throw new Error(`Unspecified LCSC part number for module '${node.name}'`);
}

const data = await partCache.keyAsync(sig.lcsc!.partNumber, () => fetchPart(sig.lcsc!.partNumber));
const symbol = getSymbol(data, data.dataStr.head.c_para.pre.replace('?', id))

const width = data.dataStr.BBox.width;
const height = data.dataStr.BBox.height;

totalWidth += width;
maxHeight = Math.max(maxHeight, height);

symbols.set(id, EasyEDA.Schematic.Symbol.moveTo(
symbol,
currentPos.x + width / 2,
currentPos.y + height / 2,
));

currentPos.x += width + MARGIN_X;
}

const nets: string[] = [];

const getNetPosition = (net: Net) => {
const [pin, modId] = Net.decompose(net);
const node = circuit.modules.get(modId)!;
const sig = circuit.signatures.get(node.name)!;
const mapping = pins.key(modId, () => {
const symb = symbols.get(modId);

if (symb == null) {
throw new Error(`Unspecified LCSC part number for module ${modId}`);
}

const pinShapes = symb.shapes.filter(s => s.command === 'P') as Schematic['Shape']['Pin'][];

const defaultPinMapping = Object.fromEntries(
pinShapes.map(pin => [pin.pinNumber, pin.name.text])
);

const nameMap = pinMapping(
node.name, { ...sig.inputs, ...sig.outputs },
sig.lcsc?.pins ?? defaultPinMapping,
);

return new Map(pinShapes.map(pin => [
nameMap[pin.pinNumber],
{
pinNumber: pin.pinNumber,
x: pin.pinDot.x,
y: pin.pinDot.y,
}]));
});

if (!mapping.has(pin)) {
throw new Error(`Could not get position of pin '${pin}' in module ${modId}`);
}

return mapping.get(pin)!;
};

for (const [net, { out }] of circuit.nets) {
for (const targetNet of [net, ...out]) {
const { x, y } = getNetPosition(targetNet);
nets.push(`F~part_netLabel_netPort~${x}~${y}~180~${net}_${targetNet}F~~0^^${x}~${y}^^${net}~#235789~${x + 10}~${y}~0~~1~Times New Roman~5pt~${net}_${targetNet}NL^^R~${x - 2}~${y - 2}~~~4~4~#235789~1~0~none~${net}_${targetNet}R~0~`);
}
}

return {
"editorVersion": "6.5.5",
"docType": "5",
"title": "Test",
"description": "",
"colors": {},
"schematics": [
{
"docType": "1",
"title": "Sheet_1",
"description": "",
"dataStr": {
"head": {
"docType": "1",
"editorVersion": "6.5.5",
"newgId": true,
"c_para": {
"Prefix Start": "1"
},
"c_spiceCmd": "null",
"hasIdFlag": true,
"uuid": "ab9e97cb51514c1c9ebdf63b8cb4d94e",
"x": "0",
"y": "0",
"portOfADImportHack": "",
"importFlag": 0,
"transformList": ""
},
"canvas": "CA~1000~1000~#FFFFFF~yes~#CCCCCC~5~1000~1000~line~5~pixel~5~0~0",
"shape": [...Iter.map(symbols.values(), EasyEDA.Schematic.Symbol.show), ...nets],
"BBox": {
"x": 0,
"y": 0,
"width": totalWidth,
"height": maxHeight,
},
"colors": {}
}
}
]
};
};

(async () => {
const { circuit } = metadata(top);
const lcscCircuit = Rewire.keepLCSCModules(circuit);

const sch = await generateSchematic(lcscCircuit);

await writeFile('./out/sch.json', JSON.stringify(sch, null, 2));

// console.log(await EasyEDA.api.generateModuleDefs(['C358684']));
})();
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"typeCheck": "tsc --noEmit",
"esbuild": "esbuild examples/cpu.ts --bundle --outfile=dist/cpu.js --platform=node",
"build": "tsc",
"watch": "esbuild examples/bus.ts --sourcemap --bundle --outfile=dist/bus.js --platform=node --watch"
"watch": "esbuild examples/easyeda.ts --sourcemap --bundle --outfile=dist/easyeda.js --platform=node --watch"
},
"keywords": [
"hardware-description",
Expand Down
10 changes: 10 additions & 0 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type CircuitSignatures = Map<string, {
inputs: Record<string, Num>,
outputs: Record<string, Num>,
kicad?: KiCadConfig<any, any>,
lcsc?: LCSCConfig<any, any>,
}>;

export type CircuitModules = Map<ModuleId, ModuleNode>;
Expand Down Expand Up @@ -428,6 +429,7 @@ export function defineModule<In extends Record<string, Num>, Out extends Record<
inputs: mod.inputs,
outputs: mod.outputs,
kicad: mod.kicad,
lcsc: mod.lcsc,
});
} else {
const prevSig = circuit.signatures.get(mod.name)!;
Expand Down Expand Up @@ -578,11 +580,19 @@ export type KiCadConfig<In extends Record<string, Num>, Out extends Record<strin
pins?: Record<number, LinearizePins<In & Out>>,
};

export type LCSCPartNumber = `C${number}`;

export type LCSCConfig<In extends Record<string, Num>, Out extends Record<string, Num>> = {
partNumber: LCSCPartNumber,
pins?: Record<number, LinearizePins<In & Out>>,
};

type BaseModuleDef<In extends Record<string, Num>, Out extends Record<string, Num>> = {
name: string,
inputs: In,
outputs: Out,
kicad?: KiCadConfig<In, Out>,
lcsc?: LCSCConfig<In, Out>,
};

export type SimulatedModuleDef<
Expand Down
109 changes: 109 additions & 0 deletions src/export/easyeda/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { Schematic } from "./parse";

export type LCSCPartNumber = `C${number}`;

export const fetchPart = async (lcsc: LCSCPartNumber) => {
const res = await fetch(`https://easyeda.com/api/products/${lcsc}/components`);
return (await res.json())?.result;
};

export type LCSCPartData = any;

export const getSymbol = (part: LCSCPartData, designator: string): Schematic['Symbol'] => {
return EasyEDA.Schematic.Symbol.moveTo({
command: 'LIB',
x: part.dataStr.head.x,
y: part.dataStr.head.y,
attributes: part.dataStr.head.c_para,
importFlag: 0,
rotation: 0,
packageUuid: part.dataStr.head.puuid,
datastrid: part.datastrid,
updateTime: part.updateTime,
packageDetailDatastrid: part.packageDetail.datastrid,
shapes: part.dataStr.shape.map(EasyEDA.Schematic.Shape.parse),
designator,
id: 'nath00',
}, 0, 0);
};

export const getSymbolString = (part: LCSCPartData, designator: string): string => {
const symb = getSymbol(part, designator);

return [
EasyEDA.Schematic.Symbol.show(symb),
...symb.shapes.map(EasyEDA.Schematic.Shape.show),
].join('#@$');
};

export const generateModuleDef = async (lcsc: LCSCPartNumber): Promise<string> => {
const part = await fetchPart(lcsc);

const rawShapes: string[] = part.dataStr.shape;
const shapes = rawShapes.map(EasyEDA.Schematic.Shape.parse);

const pinBaseName = (name: string) => {
if (/^\d+$/.test(name)) {
return '*';
}

if (/\d+$/.test(name)) {
return name.split(/\d+$/)[0].toLowerCase();
}

if (/^\d+/.test(name)) {
return name.split(/^\d+/)[1].toLowerCase();
}

return name.toLowerCase();
};

const pins = new Map(
(shapes.filter((s: Schematic['Shape']['ANY']) => s.command === 'P') as Schematic['Shape']['Pin'][])
.map(pin => [
pin.pinNumber,
{
name: pin.name.text.toLowerCase(),
baseName: pinBaseName(pin.name.text),
x: pin.pinDot.x,
y: pin.pinDot.y,
}
])
);

const baseNames = [...pins.values()].map(p => p.baseName);
const baseNamesWidth = [...new Set(baseNames)].map(baseName => [
baseName,
baseNames.reduce((count, name) => (name === baseName) ? count + 1 : count, 0)
] as const);

const moduleDef = `
/**
* name: ${part.dataStr.head.c_para.name}
* package: ${part.dataStr.head.c_para.package}${part.description ? `\ndescription: ${part.description}\n` : ''}
*/
const ${lcsc} = defineModule({
name: '${lcsc}',
// pins: { ${baseNamesWidth.map(([name, width]) => `${/[a-zA-Z_]/.test(name[0]) ? name : `'${name}'`}: ${width}`).join(', ')} },
inputs: {},
outputs: {},
lcsc: '${lcsc}',
simulate() {
throw new Error('Missing simulation for auto-generated module definition of ${lcsc}');
}
});
`.trim();

return moduleDef;
};

export const EasyEDA = {
Schematic,
api: {
fetchPart,
async generateModuleDefs(parts: LCSCPartNumber[]): Promise<string> {
const defs = await Promise.all(parts.map(generateModuleDef));
return defs.join('\n\n');
},
},
};
Loading