@tscircuit/cli 0.1.2049 → 0.1.2050

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/cli/main.js CHANGED
@@ -153260,7 +153260,7 @@ var package_default = {
153260
153260
  "@babel/standalone": "^7.26.9",
153261
153261
  "@biomejs/biome": "^1.9.4",
153262
153262
  "@tscircuit/check-shorts": "https://jscdn.tscircuit.com/@tscircuit/check-shorts/0.0.19.tgz",
153263
- "@tscircuit/circuit-json-placement-analysis": "^0.0.9",
153263
+ "@tscircuit/circuit-json-placement-analysis": "^0.0.15",
153264
153264
  "@tscircuit/circuit-json-routing-analysis": "^0.0.8",
153265
153265
  "@tscircuit/circuit-json-schematic-placement-analysis": "github:tscircuit/circuit-json-schematic-placement-analysis#cb6059c8e2561967afad591b30251a915cab42e5",
153266
153266
  "@tscircuit/circuit-json-util": "^0.0.113",
@@ -172599,17 +172599,50 @@ var analyzeComponentPlacement = (circuitJson, componentName) => {
172599
172599
  `)
172600
172600
  };
172601
172601
  };
172602
+ var isPcbPort = (circuitElement) => {
172603
+ if (circuitElement.type !== "pcb_port")
172604
+ return false;
172605
+ if (typeof circuitElement.pcb_port_id !== "string")
172606
+ return false;
172607
+ if (typeof circuitElement.source_port_id !== "string")
172608
+ return false;
172609
+ if (typeof circuitElement.x !== "number")
172610
+ return false;
172611
+ if (!Number.isFinite(circuitElement.x))
172612
+ return false;
172613
+ if (typeof circuitElement.y !== "number")
172614
+ return false;
172615
+ if (!Number.isFinite(circuitElement.y))
172616
+ return false;
172617
+ if (!Array.isArray(circuitElement.layers))
172618
+ return false;
172619
+ for (const layer of circuitElement.layers) {
172620
+ if (typeof layer !== "string")
172621
+ return false;
172622
+ }
172623
+ return true;
172624
+ };
172602
172625
  var TOP_ISSUE_LIMIT = 5;
172603
172626
  var CENTER_ANCHOR2 = "center";
172604
172627
  var LARGE_EMPTY_SPACE_THRESHOLD_RATIO = 0.05;
172605
172628
  var EMPTY_SPACE_SAMPLE_STEP_MM = 5;
172606
172629
  var GEOMETRY_EPSILON = 0.000001;
172630
+ var SUBOPTIMAL_ORIENTATION_SEVERITY = 100;
172631
+ var ORIENTATION_ANALYSIS_COMPONENT_TYPES = /* @__PURE__ */ new Set([
172632
+ "simple_resistor",
172633
+ "simple_capacitor",
172634
+ "simple_inductor",
172635
+ "simple_crystal",
172636
+ "simple_diode",
172637
+ "simple_chip"
172638
+ ]);
172607
172639
  var ISSUE_TYPE_ORDER = [
172608
172640
  "pad_overlap",
172609
172641
  "off_board",
172610
172642
  "courtyard_collision",
172611
172643
  "connector_body_intrusion",
172612
- "footprint_intrusion"
172644
+ "footprint_intrusion",
172645
+ "suboptimal_orientation"
172613
172646
  ];
172614
172647
  var toNumber22 = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
172615
172648
  var getLayer = (value) => typeof value === "string" ? value : null;
@@ -172734,6 +172767,10 @@ var getSummaryLabel = (type, count) => {
172734
172767
  return `${count} connector-body intrusion${count === 1 ? "" : "s"}`;
172735
172768
  case "footprint_intrusion":
172736
172769
  return `${count} footprint intrusion${count === 1 ? "" : "s"}`;
172770
+ case "suboptimal_orientation":
172771
+ if (count === 1)
172772
+ return "1 suboptimal orientation";
172773
+ return `${count} suboptimal orientations`;
172737
172774
  }
172738
172775
  };
172739
172776
  var getMoveDirectionForEdge = (edge) => {
@@ -173303,7 +173340,174 @@ var buildBoardTopLayerReport = (components, boardBounds) => {
173303
173340
  largeEmptySpaces
173304
173341
  };
173305
173342
  };
173306
- var buildIssues = (components, boardBounds) => {
173343
+ var buildConnectedSourcePortIdsBySourcePortId = (circuitJson) => {
173344
+ const connectionGroupById = /* @__PURE__ */ new Map;
173345
+ const sourcePortIds = /* @__PURE__ */ new Set;
173346
+ for (const element of circuitJson) {
173347
+ if (element.type !== "source_trace")
173348
+ continue;
173349
+ if (!Array.isArray(element.connected_source_port_ids))
173350
+ continue;
173351
+ const portIds = element.connected_source_port_ids.filter((id) => typeof id === "string");
173352
+ for (const id of portIds)
173353
+ sourcePortIds.add(id);
173354
+ const netIds = Array.isArray(element.connected_source_net_ids) ? element.connected_source_net_ids.filter((id) => typeof id === "string") : [];
173355
+ const connectionIds = [...portIds, ...netIds];
173356
+ const group = new Set(connectionIds);
173357
+ for (const id of connectionIds) {
173358
+ const existingGroup = connectionGroupById.get(id);
173359
+ if (!existingGroup)
173360
+ continue;
173361
+ for (const connectedId of existingGroup)
173362
+ group.add(connectedId);
173363
+ }
173364
+ for (const id of group)
173365
+ connectionGroupById.set(id, group);
173366
+ }
173367
+ const connectedPortIdsByPortId = /* @__PURE__ */ new Map;
173368
+ for (const group of new Set(connectionGroupById.values())) {
173369
+ const portIds = new Set([...group].filter((id) => sourcePortIds.has(id)));
173370
+ for (const id of portIds)
173371
+ connectedPortIdsByPortId.set(id, portIds);
173372
+ }
173373
+ return connectedPortIdsByPortId;
173374
+ };
173375
+ var buildPcbPortIndexes = (circuitJson) => {
173376
+ const sourceComponentIdBySourcePortId = /* @__PURE__ */ new Map;
173377
+ const pcbPortBySourcePortId = /* @__PURE__ */ new Map;
173378
+ const pcbPortsBySourceComponentId = /* @__PURE__ */ new Map;
173379
+ for (const element of circuitJson) {
173380
+ if (element.type === "source_port" && typeof element.source_port_id === "string" && typeof element.source_component_id === "string") {
173381
+ sourceComponentIdBySourcePortId.set(element.source_port_id, element.source_component_id);
173382
+ }
173383
+ }
173384
+ for (const element of circuitJson) {
173385
+ if (!isPcbPort(element))
173386
+ continue;
173387
+ const pcbPort = element;
173388
+ const sourceComponentId = sourceComponentIdBySourcePortId.get(pcbPort.source_port_id);
173389
+ if (!sourceComponentId)
173390
+ continue;
173391
+ pcbPortBySourcePortId.set(pcbPort.source_port_id, pcbPort);
173392
+ let componentPcbPorts = pcbPortsBySourceComponentId.get(sourceComponentId);
173393
+ if (!componentPcbPorts) {
173394
+ componentPcbPorts = [];
173395
+ pcbPortsBySourceComponentId.set(sourceComponentId, componentPcbPorts);
173396
+ }
173397
+ componentPcbPorts.push(pcbPort);
173398
+ }
173399
+ return {
173400
+ pcbPortBySourcePortId,
173401
+ pcbPortsBySourceComponentId,
173402
+ sourceComponentIdBySourcePortId
173403
+ };
173404
+ };
173405
+ var doesConnectionCrossPadCenterline = ({
173406
+ pcbPort,
173407
+ connectedPcbPort,
173408
+ firstComponentPcbPort,
173409
+ secondComponentPcbPort
173410
+ }) => {
173411
+ const padCenterlineX = (firstComponentPcbPort.x + secondComponentPcbPort.x) / 2;
173412
+ const padCenterlineY = (firstComponentPcbPort.y + secondComponentPcbPort.y) / 2;
173413
+ const padAxisX = secondComponentPcbPort.x - firstComponentPcbPort.x;
173414
+ const padAxisY = secondComponentPcbPort.y - firstComponentPcbPort.y;
173415
+ const pcbPortSide = (pcbPort.x - padCenterlineX) * padAxisX + (pcbPort.y - padCenterlineY) * padAxisY;
173416
+ const connectedPcbPortSide = (connectedPcbPort.x - padCenterlineX) * padAxisX + (connectedPcbPort.y - padCenterlineY) * padAxisY;
173417
+ if (pcbPortSide > GEOMETRY_EPSILON) {
173418
+ return connectedPcbPortSide <= GEOMETRY_EPSILON;
173419
+ }
173420
+ if (pcbPortSide < -GEOMETRY_EPSILON) {
173421
+ return connectedPcbPortSide >= -GEOMETRY_EPSILON;
173422
+ }
173423
+ return false;
173424
+ };
173425
+ var buildSuboptimalOrientationIssues = (circuitJson, components) => {
173426
+ const issues = [];
173427
+ const pcbPortIndexes = buildPcbPortIndexes(circuitJson);
173428
+ const connectedSourcePortIdsBySourcePortId = buildConnectedSourcePortIdsBySourcePortId(circuitJson);
173429
+ for (const component of components) {
173430
+ if (!ORIENTATION_ANALYSIS_COMPONENT_TYPES.has(String(component.sourceComponent.ftype))) {
173431
+ continue;
173432
+ }
173433
+ if (component.centerX === null || component.centerY === null)
173434
+ continue;
173435
+ const componentPcbPorts = pcbPortIndexes.pcbPortsBySourceComponentId.get(component.sourceComponentId);
173436
+ if (!componentPcbPorts || componentPcbPorts.length < 2)
173437
+ continue;
173438
+ const [firstComponentPcbPort, secondComponentPcbPort] = componentPcbPorts;
173439
+ if (!firstComponentPcbPort || !secondComponentPcbPort)
173440
+ continue;
173441
+ const hasMultiplePins = componentPcbPorts.length > 2;
173442
+ const centerX = component.centerX;
173443
+ const centerY = component.centerY;
173444
+ const movingPcbPorts = componentPcbPorts.filter((port) => !hasMultiplePins || Math.hypot(port.x - centerX, port.y - centerY) > GEOMETRY_EPSILON);
173445
+ if (movingPcbPorts.length < 2)
173446
+ continue;
173447
+ let crossingConnectionCount = 0;
173448
+ for (const pcbPort of movingPcbPorts) {
173449
+ const connectedSourcePortIds = connectedSourcePortIdsBySourcePortId.get(pcbPort.source_port_id);
173450
+ if (!connectedSourcePortIds)
173451
+ break;
173452
+ if (componentPcbPorts.every((port) => connectedSourcePortIds.has(port.source_port_id))) {
173453
+ break;
173454
+ }
173455
+ const rotatedPosition = {
173456
+ x: 2 * component.centerX - pcbPort.x,
173457
+ y: 2 * component.centerY - pcbPort.y
173458
+ };
173459
+ let connectedPcbPort;
173460
+ let nearestDistance = Infinity;
173461
+ let rotatedNearestDistance = Infinity;
173462
+ for (const connectedSourcePortId of connectedSourcePortIds) {
173463
+ const candidate = pcbPortIndexes.pcbPortBySourcePortId.get(connectedSourcePortId);
173464
+ if (!candidate)
173465
+ continue;
173466
+ if (pcbPortIndexes.sourceComponentIdBySourcePortId.get(connectedSourcePortId) === component.sourceComponentId) {
173467
+ continue;
173468
+ }
173469
+ if (!pcbPort.layers.some((layer) => candidate.layers.includes(layer))) {
173470
+ continue;
173471
+ }
173472
+ const distance = Math.hypot(candidate.x - pcbPort.x, candidate.y - pcbPort.y);
173473
+ if (distance < nearestDistance) {
173474
+ connectedPcbPort = candidate;
173475
+ nearestDistance = distance;
173476
+ }
173477
+ rotatedNearestDistance = Math.min(rotatedNearestDistance, Math.hypot(candidate.x - rotatedPosition.x, candidate.y - rotatedPosition.y));
173478
+ }
173479
+ if (!connectedPcbPort)
173480
+ break;
173481
+ if (rotatedNearestDistance >= nearestDistance - GEOMETRY_EPSILON)
173482
+ break;
173483
+ const crossesPadCenterline = doesConnectionCrossPadCenterline({
173484
+ pcbPort,
173485
+ connectedPcbPort,
173486
+ firstComponentPcbPort: hasMultiplePins ? pcbPort : firstComponentPcbPort,
173487
+ secondComponentPcbPort: hasMultiplePins ? rotatedPosition : secondComponentPcbPort
173488
+ });
173489
+ if (!crossesPadCenterline)
173490
+ break;
173491
+ crossingConnectionCount += 1;
173492
+ }
173493
+ if (crossingConnectionCount !== movingPcbPorts.length)
173494
+ continue;
173495
+ issues.push(createIssue({
173496
+ type: "suboptimal_orientation",
173497
+ componentA: component.name,
173498
+ clearance: 0,
173499
+ severity: SUBOPTIMAL_ORIENTATION_SEVERITY,
173500
+ summary: hasMultiplePins ? `${component.name} connections favor the opposite orientation` : `${component.name} connections cross the routing path between its pads`,
173501
+ suggested_move: `rotate ${component.name} 180 degrees`
173502
+ }));
173503
+ }
173504
+ return issues;
173505
+ };
173506
+ var buildIssues = ({
173507
+ circuitJson,
173508
+ components,
173509
+ boardBounds
173510
+ }) => {
173307
173511
  const issues = [];
173308
173512
  for (const component of components) {
173309
173513
  const boardEdgeStatus = getBoardEdgeStatus(component.bounds, boardBounds, component.name);
@@ -173414,6 +173618,7 @@ var buildIssues = (components, boardBounds) => {
173414
173618
  }
173415
173619
  }
173416
173620
  }
173621
+ issues.push(...buildSuboptimalOrientationIssues(circuitJson, components));
173417
173622
  return issues.sort((a, b) => b.severity - a.severity);
173418
173623
  };
173419
173624
  var buildClusters = (components, issues) => {
@@ -173606,7 +173811,7 @@ var formatPlacementAnalysisReport = (report) => {
173606
173811
  };
173607
173812
  var buildPlacementAnalysisReport = (circuitJson) => {
173608
173813
  const { components, boardBounds } = buildComponentContexts(circuitJson);
173609
- const issues = buildIssues(components, boardBounds);
173814
+ const issues = buildIssues({ circuitJson, components, boardBounds });
173610
173815
  const clusters = buildClusters(components, issues);
173611
173816
  const countsByType = buildCountsByType(issues);
173612
173817
  const boardTopLayer = buildBoardTopLayerReport(components, boardBounds);
package/dist/lib/index.js CHANGED
@@ -69126,7 +69126,7 @@ var package_default = {
69126
69126
  "@babel/standalone": "^7.26.9",
69127
69127
  "@biomejs/biome": "^1.9.4",
69128
69128
  "@tscircuit/check-shorts": "https://jscdn.tscircuit.com/@tscircuit/check-shorts/0.0.19.tgz",
69129
- "@tscircuit/circuit-json-placement-analysis": "^0.0.9",
69129
+ "@tscircuit/circuit-json-placement-analysis": "^0.0.15",
69130
69130
  "@tscircuit/circuit-json-routing-analysis": "^0.0.8",
69131
69131
  "@tscircuit/circuit-json-schematic-placement-analysis": "github:tscircuit/circuit-json-schematic-placement-analysis#cb6059c8e2561967afad591b30251a915cab42e5",
69132
69132
  "@tscircuit/circuit-json-util": "^0.0.113",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/cli",
3
- "version": "0.1.2049",
3
+ "version": "0.1.2050",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/tscircuit/cli"
@@ -14,7 +14,7 @@
14
14
  "@babel/standalone": "^7.26.9",
15
15
  "@biomejs/biome": "^1.9.4",
16
16
  "@tscircuit/check-shorts": "https://jscdn.tscircuit.com/@tscircuit/check-shorts/0.0.19.tgz",
17
- "@tscircuit/circuit-json-placement-analysis": "^0.0.9",
17
+ "@tscircuit/circuit-json-placement-analysis": "^0.0.15",
18
18
  "@tscircuit/circuit-json-routing-analysis": "^0.0.8",
19
19
  "@tscircuit/circuit-json-schematic-placement-analysis": "github:tscircuit/circuit-json-schematic-placement-analysis#cb6059c8e2561967afad591b30251a915cab42e5",
20
20
  "@tscircuit/circuit-json-util": "^0.0.113",