circuit-json-to-kicad 0.0.25 → 0.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -13,6 +13,10 @@ interface PaperDimensions {
13
13
  type SchematicPortId = string;
14
14
  type SchematicTraceId = string;
15
15
  type PcbPortId = string;
16
+ interface PcbNetInfo {
17
+ id: number;
18
+ name: string;
19
+ }
16
20
  interface ConverterContext {
17
21
  db: CircuitJsonUtilObjects;
18
22
  circuitJson: CircuitJson;
@@ -33,7 +37,7 @@ interface ConverterContext {
33
37
  x: number;
34
38
  y: number;
35
39
  }>;
36
- pcbNetMap?: Map<string, number>;
40
+ pcbNetMap?: Map<string, PcbNetInfo>;
37
41
  }
38
42
  declare abstract class ConverterStage<Input, Output> {
39
43
  MAX_ITERATIONS: number;
package/dist/index.js CHANGED
@@ -1347,27 +1347,49 @@ var AddNetsStage = class extends ConverterStage {
1347
1347
  if (!kicadPcb) {
1348
1348
  throw new Error("KicadPcb instance not initialized in context");
1349
1349
  }
1350
- if (!this.ctx.pcbNetMap) {
1351
- this.ctx.pcbNetMap = /* @__PURE__ */ new Map();
1352
- }
1353
- const pcbTraces = this.ctx.db.pcb_trace.list();
1354
- const netNames = /* @__PURE__ */ new Set();
1355
- netNames.add("GND");
1356
- for (const trace of pcbTraces) {
1357
- if (trace.route && trace.route.length > 0) {
1358
- const netName = `Net-${trace.pcb_trace_id}`;
1359
- netNames.add(netName);
1350
+ this.ctx.pcbNetMap = /* @__PURE__ */ new Map();
1351
+ const netNameByKey = /* @__PURE__ */ new Map();
1352
+ const sourceNets = this.ctx.db.source_net?.list() ?? [];
1353
+ for (const sourceNet of sourceNets) {
1354
+ const connectivityKey = sourceNet.subcircuit_connectivity_map_key || sourceNet.source_net_id;
1355
+ if (!connectivityKey) continue;
1356
+ const candidateName = sourceNet.name || sourceNet.source_net_id || "";
1357
+ const netName = candidateName && candidateName.trim().length > 0 ? candidateName : connectivityKey;
1358
+ netNameByKey.set(connectivityKey, netName);
1359
+ }
1360
+ const sourceTraces = this.ctx.db.source_trace?.list() ?? [];
1361
+ for (const sourceTrace of sourceTraces) {
1362
+ let connectivityKey = sourceTrace.subcircuit_connectivity_map_key;
1363
+ if (!connectivityKey && sourceTrace.connected_source_net_ids?.length) {
1364
+ for (const sourceNetId of sourceTrace.connected_source_net_ids) {
1365
+ const connectedNet = this.ctx.db.source_net?.get(sourceNetId);
1366
+ if (connectedNet?.subcircuit_connectivity_map_key && connectedNet.subcircuit_connectivity_map_key.length > 0) {
1367
+ connectivityKey = connectedNet.subcircuit_connectivity_map_key;
1368
+ break;
1369
+ }
1370
+ }
1371
+ }
1372
+ if (!connectivityKey) continue;
1373
+ if (!netNameByKey.has(connectivityKey)) {
1374
+ const candidateName = sourceTrace.display_name || sourceTrace.source_trace_id || "";
1375
+ const netName = candidateName && candidateName.trim().length > 0 ? candidateName : connectivityKey;
1376
+ netNameByKey.set(connectivityKey, netName);
1360
1377
  }
1361
1378
  }
1362
- let netNumber = 0;
1363
- for (const netName of Array.from(netNames).sort()) {
1364
- const net = new PcbNet(netNumber, netName);
1365
- const nets = kicadPcb.nets;
1366
- nets.push(net);
1367
- kicadPcb.nets = nets;
1368
- this.ctx.pcbNetMap.set(netName, netNumber);
1379
+ const sortedEntries = Array.from(netNameByKey.entries()).sort(
1380
+ (a, b) => a[0].localeCompare(b[0])
1381
+ );
1382
+ const nets = [];
1383
+ nets.push(new PcbNet(0, ""));
1384
+ let netNumber = 1;
1385
+ for (const [connectivityKey, netName] of sortedEntries) {
1386
+ const pcbNet = new PcbNet(netNumber, netName);
1387
+ nets.push(pcbNet);
1388
+ const netInfo = { id: netNumber, name: netName };
1389
+ this.ctx.pcbNetMap.set(connectivityKey, netInfo);
1369
1390
  netNumber++;
1370
1391
  }
1392
+ kicadPcb.nets = nets;
1371
1393
  this.finished = true;
1372
1394
  }
1373
1395
  getOutput() {
@@ -1386,7 +1408,8 @@ import {
1386
1408
  PadPrimitiveGrPoly,
1387
1409
  Pts as Pts3,
1388
1410
  Xy as Xy3,
1389
- PadOptions
1411
+ PadOptions,
1412
+ PadNet
1390
1413
  } from "kicadts";
1391
1414
  import {
1392
1415
  applyToPoint as applyToPoint5,
@@ -1395,11 +1418,39 @@ import {
1395
1418
  scale as scale2,
1396
1419
  rotate
1397
1420
  } from "transformation-matrix";
1421
+
1422
+ // lib/pcb/stages/utils/generateDeterministicUuid.ts
1423
+ function simpleHash(str) {
1424
+ let hash = 0;
1425
+ for (let i = 0; i < str.length; i++) {
1426
+ const char = str.charCodeAt(i);
1427
+ hash = (hash << 5) - hash + char;
1428
+ hash = hash & hash;
1429
+ }
1430
+ let result = "";
1431
+ for (let i = 0; i < 4; i++) {
1432
+ let h = hash;
1433
+ for (let j = 0; j < str.length; j++) {
1434
+ h = (h << 5) - h + str.charCodeAt(j) + i * 31;
1435
+ h = h & h;
1436
+ }
1437
+ result += Math.abs(h).toString(16).padStart(8, "0");
1438
+ }
1439
+ return result;
1440
+ }
1441
+ function generateDeterministicUuid(data) {
1442
+ const hash = simpleHash(data);
1443
+ return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
1444
+ }
1445
+
1446
+ // lib/pcb/stages/utils/CreateSmdPadFromCircuitJson.ts
1398
1447
  function createSmdPadFromCircuitJson({
1399
1448
  pcbPad,
1400
1449
  componentCenter,
1401
1450
  padNumber,
1402
- componentRotation = 0
1451
+ componentRotation = 0,
1452
+ netInfo,
1453
+ componentId
1403
1454
  }) {
1404
1455
  let padX;
1405
1456
  let padY;
@@ -1466,6 +1517,7 @@ function createSmdPadFromCircuitJson({
1466
1517
  "height" in pcbPad ? pcbPad.height : 0.5
1467
1518
  ];
1468
1519
  }
1520
+ const padData = `pad:${componentId}:${padNumber}:${rotatedPos.x},${rotatedPos.y}`;
1469
1521
  const pad = new FootprintPad({
1470
1522
  number: String(padNumber),
1471
1523
  padType: "smd",
@@ -1477,7 +1529,7 @@ function createSmdPadFromCircuitJson({
1477
1529
  `${padLayer === "F.Cu" ? "F" : "B"}.Paste`,
1478
1530
  `${padLayer === "F.Cu" ? "F" : "B"}.Mask`
1479
1531
  ],
1480
- uuid: crypto.randomUUID()
1532
+ uuid: generateDeterministicUuid(padData)
1481
1533
  });
1482
1534
  if (padOptions) {
1483
1535
  pad.options = padOptions;
@@ -1485,17 +1537,22 @@ function createSmdPadFromCircuitJson({
1485
1537
  if (padPrimitives) {
1486
1538
  pad.primitives = padPrimitives;
1487
1539
  }
1540
+ if (netInfo) {
1541
+ pad.net = new PadNet(netInfo.id, netInfo.name);
1542
+ }
1488
1543
  return pad;
1489
1544
  }
1490
1545
 
1491
1546
  // lib/pcb/stages/utils/CreateThruHolePadFromCircuitJson.ts
1492
- import { FootprintPad as FootprintPad2, PadDrill } from "kicadts";
1547
+ import { FootprintPad as FootprintPad2, PadDrill, PadNet as PadNet2 } from "kicadts";
1493
1548
  import { applyToPoint as applyToPoint6, rotate as rotate2, identity } from "transformation-matrix";
1494
1549
  function createThruHolePadFromCircuitJson({
1495
1550
  platedHole,
1496
1551
  componentCenter,
1497
1552
  padNumber,
1498
- componentRotation = 0
1553
+ componentRotation = 0,
1554
+ netInfo,
1555
+ componentId
1499
1556
  }) {
1500
1557
  if (!("x" in platedHole && "y" in platedHole)) {
1501
1558
  return null;
@@ -1585,7 +1642,8 @@ function createThruHolePadFromCircuitJson({
1585
1642
  padSize = [1.6, 1.6];
1586
1643
  drill = new PadDrill({ diameter: 0.8, offset: drillOffset });
1587
1644
  }
1588
- return new FootprintPad2({
1645
+ const padData = `thruhole:${componentId}:${padNumber}:${rotatedPos.x},${rotatedPos.y}`;
1646
+ const pad = new FootprintPad2({
1589
1647
  number: String(padNumber),
1590
1648
  padType: "thru_hole",
1591
1649
  shape: padShape,
@@ -1594,8 +1652,12 @@ function createThruHolePadFromCircuitJson({
1594
1652
  drill,
1595
1653
  layers: ["*.Cu", "*.Mask"],
1596
1654
  removeUnusedLayers: false,
1597
- uuid: crypto.randomUUID()
1655
+ uuid: generateDeterministicUuid(padData)
1598
1656
  });
1657
+ if (netInfo) {
1658
+ pad.net = new PadNet2(netInfo.id, netInfo.name);
1659
+ }
1660
+ return pad;
1599
1661
  }
1600
1662
 
1601
1663
  // lib/pcb/stages/utils/CreateNpthPadFromCircuitJson.ts
@@ -1707,6 +1769,18 @@ function createFpTextFromCircuitJson({
1707
1769
  var AddFootprintsStage = class extends ConverterStage {
1708
1770
  componentsProcessed = 0;
1709
1771
  pcbComponents = [];
1772
+ getNetInfoForPcbPort(pcbPortId) {
1773
+ if (!pcbPortId) return void 0;
1774
+ const pcbPort = this.ctx.db.pcb_port?.get(pcbPortId);
1775
+ if (!pcbPort) return void 0;
1776
+ const sourcePortId = pcbPort.source_port_id;
1777
+ if (!sourcePortId) return void 0;
1778
+ const sourcePort = this.ctx.db.source_port?.get(sourcePortId);
1779
+ if (!sourcePort) return void 0;
1780
+ const connectivityKey = sourcePort.subcircuit_connectivity_map_key;
1781
+ if (!connectivityKey) return void 0;
1782
+ return this.ctx.pcbNetMap?.get(connectivityKey);
1783
+ }
1710
1784
  constructor(input, ctx) {
1711
1785
  super(input, ctx);
1712
1786
  this.pcbComponents = this.ctx.db.pcb_component.list();
@@ -1730,11 +1804,12 @@ var AddFootprintsStage = class extends ConverterStage {
1730
1804
  x: component.center.x,
1731
1805
  y: component.center.y
1732
1806
  });
1807
+ const footprintData = `footprint:${component.pcb_component_id}:${transformedPos.x},${transformedPos.y}`;
1733
1808
  const footprint = new Footprint({
1734
1809
  libraryLink: `tscircuit:${footprintName}`,
1735
1810
  layer: "F.Cu",
1736
1811
  at: [transformedPos.x, transformedPos.y, component.rotation || 0],
1737
- uuid: crypto.randomUUID()
1812
+ uuid: generateDeterministicUuid(footprintData)
1738
1813
  });
1739
1814
  const fpTexts = footprint.fpTexts;
1740
1815
  const pcbSilkscreenTexts = this.ctx.db.pcb_silkscreen_text?.list().filter(
@@ -1757,11 +1832,14 @@ var AddFootprintsStage = class extends ConverterStage {
1757
1832
  const fpPads = footprint.fpPads;
1758
1833
  let padNumber = 1;
1759
1834
  for (const pcbPad of pcbPads) {
1835
+ const netInfo = this.getNetInfoForPcbPort(pcbPad.pcb_port_id);
1760
1836
  const pad = createSmdPadFromCircuitJson({
1761
1837
  pcbPad,
1762
1838
  componentCenter: component.center,
1763
1839
  padNumber,
1764
- componentRotation: component.rotation || 0
1840
+ componentRotation: component.rotation || 0,
1841
+ netInfo,
1842
+ componentId: component.pcb_component_id
1765
1843
  });
1766
1844
  fpPads.push(pad);
1767
1845
  padNumber++;
@@ -1770,11 +1848,14 @@ var AddFootprintsStage = class extends ConverterStage {
1770
1848
  (hole) => hole.pcb_component_id === component.pcb_component_id
1771
1849
  ) || [];
1772
1850
  for (const platedHole of pcbPlatedHoles) {
1851
+ const netInfo = this.getNetInfoForPcbPort(platedHole.pcb_port_id);
1773
1852
  const pad = createThruHolePadFromCircuitJson({
1774
1853
  platedHole,
1775
1854
  componentCenter: component.center,
1776
1855
  padNumber,
1777
- componentRotation: component.rotation || 0
1856
+ componentRotation: component.rotation || 0,
1857
+ netInfo,
1858
+ componentId: component.pcb_component_id
1778
1859
  });
1779
1860
  if (pad) {
1780
1861
  fpPads.push(pad);
@@ -1843,22 +1924,49 @@ var AddTracesStage = class extends ConverterStage {
1843
1924
  x: endPoint.x,
1844
1925
  y: endPoint.y
1845
1926
  });
1846
- let netNumber = 0;
1927
+ let netInfo;
1847
1928
  if (pcbNetMap) {
1848
- const netName = `Net-${trace.pcb_trace_id}`;
1849
- netNumber = pcbNetMap.get(netName) ?? 0;
1929
+ let connectivityKey = trace.subcircuit_connectivity_map_key;
1930
+ if (!connectivityKey && trace.source_trace_id) {
1931
+ const sourceTrace = this.ctx.db.source_trace?.get(
1932
+ trace.source_trace_id
1933
+ );
1934
+ if (sourceTrace) {
1935
+ connectivityKey = sourceTrace.subcircuit_connectivity_map_key;
1936
+ if (!connectivityKey && sourceTrace.connected_source_net_ids?.length) {
1937
+ for (const sourceNetId of sourceTrace.connected_source_net_ids) {
1938
+ const sourceNet = this.ctx.db.source_net?.get(sourceNetId);
1939
+ if (sourceNet?.subcircuit_connectivity_map_key) {
1940
+ connectivityKey = sourceNet.subcircuit_connectivity_map_key;
1941
+ break;
1942
+ }
1943
+ }
1944
+ }
1945
+ }
1946
+ }
1947
+ if (!connectivityKey && typeof trace.connection_name === "string") {
1948
+ const sourceNet = this.ctx.db.source_net?.get(trace.connection_name);
1949
+ if (sourceNet?.subcircuit_connectivity_map_key) {
1950
+ connectivityKey = sourceNet.subcircuit_connectivity_map_key;
1951
+ }
1952
+ }
1953
+ if (connectivityKey) {
1954
+ netInfo = pcbNetMap.get(connectivityKey);
1955
+ }
1850
1956
  }
1851
1957
  const layerMap = {
1852
1958
  top: "F.Cu",
1853
1959
  bottom: "B.Cu"
1854
1960
  };
1855
1961
  const kicadLayer = layerMap[startPoint.layer] || startPoint.layer || "F.Cu";
1962
+ const segmentData = `segment:${transformedStart.x},${transformedStart.y}:${transformedEnd.x},${transformedEnd.y}:${kicadLayer}:${netInfo?.id ?? 0}`;
1856
1963
  const segment = new Segment({
1857
1964
  start: { x: transformedStart.x, y: transformedStart.y },
1858
1965
  end: { x: transformedEnd.x, y: transformedEnd.y },
1859
1966
  layer: kicadLayer,
1860
1967
  width: trace.width || 0.25,
1861
- net: new SegmentNet(netNumber)
1968
+ net: new SegmentNet(netInfo?.id ?? 0),
1969
+ uuid: generateDeterministicUuid(segmentData)
1862
1970
  });
1863
1971
  const segments = kicadPcb.segments;
1864
1972
  segments.push(segment);
@@ -1898,16 +2006,54 @@ var AddViasStage = class extends ConverterStage {
1898
2006
  x: via.x,
1899
2007
  y: via.y
1900
2008
  });
1901
- let netNumber = 0;
1902
- if (pcbNetMap && via.net_name) {
1903
- netNumber = pcbNetMap.get(via.net_name) ?? 0;
2009
+ let netInfo;
2010
+ if (pcbNetMap) {
2011
+ let connectivityKey = via.subcircuit_connectivity_map_key;
2012
+ if (!connectivityKey && via.pcb_trace_id) {
2013
+ const pcbTrace = this.ctx.db.pcb_trace?.get(via.pcb_trace_id);
2014
+ if (pcbTrace) {
2015
+ if ("subcircuit_connectivity_map_key" in pcbTrace) {
2016
+ connectivityKey = pcbTrace.subcircuit_connectivity_map_key;
2017
+ }
2018
+ if (!connectivityKey && pcbTrace.source_trace_id) {
2019
+ const sourceTrace = this.ctx.db.source_trace?.get(
2020
+ pcbTrace.source_trace_id
2021
+ );
2022
+ if (sourceTrace) {
2023
+ if ("subcircuit_connectivity_map_key" in sourceTrace) {
2024
+ connectivityKey = sourceTrace.subcircuit_connectivity_map_key;
2025
+ }
2026
+ if (!connectivityKey && sourceTrace.connected_source_net_ids?.length) {
2027
+ for (const sourceNetId of sourceTrace.connected_source_net_ids) {
2028
+ const sourceNet = this.ctx.db.source_net?.get(sourceNetId);
2029
+ if (sourceNet?.subcircuit_connectivity_map_key) {
2030
+ connectivityKey = sourceNet.subcircuit_connectivity_map_key;
2031
+ break;
2032
+ }
2033
+ }
2034
+ }
2035
+ }
2036
+ }
2037
+ }
2038
+ }
2039
+ if (!connectivityKey && via.connection_name) {
2040
+ const sourceNet = this.ctx.db.source_net?.get(via.connection_name);
2041
+ if (sourceNet?.subcircuit_connectivity_map_key) {
2042
+ connectivityKey = sourceNet.subcircuit_connectivity_map_key;
2043
+ }
2044
+ }
2045
+ if (connectivityKey) {
2046
+ netInfo = pcbNetMap.get(connectivityKey);
2047
+ }
1904
2048
  }
2049
+ const viaData = `via:${transformedPos.x},${transformedPos.y}:${via.outer_diameter || 0.8}:${via.hole_diameter || 0.4}:${netInfo?.id ?? 0}`;
1905
2050
  const kicadVia = new Via({
1906
2051
  at: [transformedPos.x, transformedPos.y],
1907
2052
  size: via.outer_diameter || 0.8,
1908
2053
  drill: via.hole_diameter || 0.4,
1909
2054
  layers: ["F.Cu", "B.Cu"],
1910
- net: new ViaNet(netNumber)
2055
+ net: new ViaNet(netInfo?.id ?? 0),
2056
+ uuid: generateDeterministicUuid(viaData)
1911
2057
  });
1912
2058
  const vias = kicadPcb.vias;
1913
2059
  vias.push(kicadVia);
package/package.json CHANGED
@@ -1,13 +1,14 @@
1
1
  {
2
2
  "name": "circuit-json-to-kicad",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.25",
4
+ "version": "0.0.27",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist"
8
8
  ],
9
9
  "scripts": {
10
10
  "build": "tsup-node lib/index.ts --format esm --dts",
11
+ "build:site": "bun build ./site/index.html --outdir site-build",
11
12
  "download-demos": "git clone --depth 1 --filter=blob:none --sparse https://gitlab.com/kicad/code/kicad.git kicad-demos && cd kicad-demos && git sparse-checkout set demos",
12
13
  "typecheck": "bunx tsc --noEmit",
13
14
  "format": "biome format --write .",