@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 +209 -4
- package/dist/lib/index.js +1 -1
- package/package.json +2 -2
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.
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|