@tscircuit/cli 0.1.1175 → 0.1.1176
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 +700 -29
- package/dist/lib/index.js +2 -2
- package/package.json +2 -2
package/dist/cli/main.js
CHANGED
|
@@ -98409,7 +98409,7 @@ var import_perfect_cli = __toESM2(require_dist2(), 1);
|
|
|
98409
98409
|
// lib/getVersion.ts
|
|
98410
98410
|
import { createRequire as createRequire2 } from "node:module";
|
|
98411
98411
|
// package.json
|
|
98412
|
-
var version = "0.1.
|
|
98412
|
+
var version = "0.1.1175";
|
|
98413
98413
|
var package_default = {
|
|
98414
98414
|
name: "@tscircuit/cli",
|
|
98415
98415
|
version,
|
|
@@ -98421,7 +98421,7 @@ var package_default = {
|
|
|
98421
98421
|
devDependencies: {
|
|
98422
98422
|
"@babel/standalone": "^7.26.9",
|
|
98423
98423
|
"@biomejs/biome": "^1.9.4",
|
|
98424
|
-
"@tscircuit/circuit-json-placement-analysis": "^0.0.
|
|
98424
|
+
"@tscircuit/circuit-json-placement-analysis": "^0.0.5",
|
|
98425
98425
|
"@tscircuit/circuit-json-routing-analysis": "^0.0.1",
|
|
98426
98426
|
"@tscircuit/fake-snippets": "^0.0.182",
|
|
98427
98427
|
"@tscircuit/file-server": "^0.0.32",
|
|
@@ -110751,6 +110751,673 @@ var analyzeComponentPlacement = (circuitJson, componentName) => {
|
|
|
110751
110751
|
`)
|
|
110752
110752
|
};
|
|
110753
110753
|
};
|
|
110754
|
+
var TOP_ISSUE_LIMIT = 5;
|
|
110755
|
+
var CENTER_ANCHOR2 = "center";
|
|
110756
|
+
var ISSUE_TYPE_ORDER = [
|
|
110757
|
+
"pad_overlap",
|
|
110758
|
+
"off_board",
|
|
110759
|
+
"courtyard_collision",
|
|
110760
|
+
"connector_body_intrusion",
|
|
110761
|
+
"footprint_intrusion"
|
|
110762
|
+
];
|
|
110763
|
+
var toNumber22 = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
110764
|
+
var fmtNumber2 = (value) => {
|
|
110765
|
+
if (Number.isInteger(value))
|
|
110766
|
+
return String(value);
|
|
110767
|
+
return value.toFixed(3).replace(/\.0+$/, "").replace(/(\.\d*?)0+$/, "$1");
|
|
110768
|
+
};
|
|
110769
|
+
var fmtMm2 = (value) => `${fmtNumber2(value)}mm`;
|
|
110770
|
+
var getBoundsFromCenterAndSize = (centerX, centerY, width, height) => ({
|
|
110771
|
+
min_x: centerX - width / 2,
|
|
110772
|
+
max_x: centerX + width / 2,
|
|
110773
|
+
min_y: centerY - height / 2,
|
|
110774
|
+
max_y: centerY + height / 2,
|
|
110775
|
+
width,
|
|
110776
|
+
height
|
|
110777
|
+
});
|
|
110778
|
+
var getBoundsFromPoints = (points) => {
|
|
110779
|
+
const xs = [];
|
|
110780
|
+
const ys = [];
|
|
110781
|
+
for (const point of points) {
|
|
110782
|
+
const x = toNumber22(point.x);
|
|
110783
|
+
const y = toNumber22(point.y);
|
|
110784
|
+
if (x === null || y === null)
|
|
110785
|
+
continue;
|
|
110786
|
+
xs.push(x);
|
|
110787
|
+
ys.push(y);
|
|
110788
|
+
}
|
|
110789
|
+
if (xs.length === 0 || ys.length === 0)
|
|
110790
|
+
return null;
|
|
110791
|
+
const min_x = Math.min(...xs);
|
|
110792
|
+
const max_x = Math.max(...xs);
|
|
110793
|
+
const min_y = Math.min(...ys);
|
|
110794
|
+
const max_y = Math.max(...ys);
|
|
110795
|
+
return {
|
|
110796
|
+
min_x,
|
|
110797
|
+
max_x,
|
|
110798
|
+
min_y,
|
|
110799
|
+
max_y,
|
|
110800
|
+
width: max_x - min_x,
|
|
110801
|
+
height: max_y - min_y
|
|
110802
|
+
};
|
|
110803
|
+
};
|
|
110804
|
+
var getOverlap = (a, b) => {
|
|
110805
|
+
const overlapX = Math.min(a.max_x, b.max_x) - Math.max(a.min_x, b.min_x);
|
|
110806
|
+
const overlapY = Math.min(a.max_y, b.max_y) - Math.max(a.min_y, b.min_y);
|
|
110807
|
+
if (overlapX <= 0 || overlapY <= 0)
|
|
110808
|
+
return null;
|
|
110809
|
+
return {
|
|
110810
|
+
overlapX,
|
|
110811
|
+
overlapY,
|
|
110812
|
+
clearance: -Math.min(overlapX, overlapY)
|
|
110813
|
+
};
|
|
110814
|
+
};
|
|
110815
|
+
var layersIntersect = (a, b) => {
|
|
110816
|
+
const bSet = new Set(b);
|
|
110817
|
+
return a.some((layer) => bSet.has(layer));
|
|
110818
|
+
};
|
|
110819
|
+
var getArea = (bounds) => {
|
|
110820
|
+
if (!bounds)
|
|
110821
|
+
return Number.POSITIVE_INFINITY;
|
|
110822
|
+
return bounds.width * bounds.height;
|
|
110823
|
+
};
|
|
110824
|
+
var stripNumericSuffix = (name) => {
|
|
110825
|
+
const stripped = name.replace(/\d+$/, "");
|
|
110826
|
+
return stripped.length >= 2 ? stripped : name;
|
|
110827
|
+
};
|
|
110828
|
+
var getSummaryLabel = (type, count) => {
|
|
110829
|
+
if (count === 0)
|
|
110830
|
+
return null;
|
|
110831
|
+
switch (type) {
|
|
110832
|
+
case "pad_overlap":
|
|
110833
|
+
return `${count} pad overlap${count === 1 ? "" : "s"}`;
|
|
110834
|
+
case "off_board":
|
|
110835
|
+
return `${count} off-board`;
|
|
110836
|
+
case "courtyard_collision":
|
|
110837
|
+
return `${count} courtyard collision${count === 1 ? "" : "s"}`;
|
|
110838
|
+
case "connector_body_intrusion":
|
|
110839
|
+
return `${count} connector-body intrusion${count === 1 ? "" : "s"}`;
|
|
110840
|
+
case "footprint_intrusion":
|
|
110841
|
+
return `${count} footprint intrusion${count === 1 ? "" : "s"}`;
|
|
110842
|
+
}
|
|
110843
|
+
};
|
|
110844
|
+
var getMoveDirectionForEdge = (edge) => {
|
|
110845
|
+
switch (edge) {
|
|
110846
|
+
case "left":
|
|
110847
|
+
return "right";
|
|
110848
|
+
case "right":
|
|
110849
|
+
return "left";
|
|
110850
|
+
case "top":
|
|
110851
|
+
return "down";
|
|
110852
|
+
case "bottom":
|
|
110853
|
+
return "up";
|
|
110854
|
+
}
|
|
110855
|
+
};
|
|
110856
|
+
var getBoardEdgeStatus = (bounds, boardBounds, componentName) => {
|
|
110857
|
+
if (!bounds || !boardBounds)
|
|
110858
|
+
return null;
|
|
110859
|
+
const clearances = [
|
|
110860
|
+
{ edge: "left", clearance: bounds.min_x - boardBounds.min_x },
|
|
110861
|
+
{ edge: "right", clearance: boardBounds.max_x - bounds.max_x },
|
|
110862
|
+
{ edge: "top", clearance: bounds.min_y - boardBounds.min_y },
|
|
110863
|
+
{
|
|
110864
|
+
edge: "bottom",
|
|
110865
|
+
clearance: boardBounds.max_y - bounds.max_y
|
|
110866
|
+
}
|
|
110867
|
+
];
|
|
110868
|
+
const outside = clearances.filter((entry) => entry.clearance < 0).sort((a, b) => a.clearance - b.clearance);
|
|
110869
|
+
const selected = outside[0] ?? [...clearances].sort((a, b) => a.clearance - b.clearance)[0] ?? null;
|
|
110870
|
+
if (!selected)
|
|
110871
|
+
return null;
|
|
110872
|
+
return {
|
|
110873
|
+
component_name: componentName,
|
|
110874
|
+
edge: selected.edge,
|
|
110875
|
+
status: selected.clearance < 0 ? "outside" : "inside",
|
|
110876
|
+
distance: Math.abs(selected.clearance)
|
|
110877
|
+
};
|
|
110878
|
+
};
|
|
110879
|
+
var formatBoardEdgeStatus = (status) => `${fmtMm2(status.distance)} ${status.status} ${status.edge} edge`;
|
|
110880
|
+
var chooseMover = (a, b, preferredMover) => {
|
|
110881
|
+
if (preferredMover === a.name)
|
|
110882
|
+
return { mover: a, anchor: b };
|
|
110883
|
+
if (preferredMover === b.name)
|
|
110884
|
+
return { mover: b, anchor: a };
|
|
110885
|
+
if (a.isConnectorLike && !b.isConnectorLike)
|
|
110886
|
+
return { mover: b, anchor: a };
|
|
110887
|
+
if (b.isConnectorLike && !a.isConnectorLike)
|
|
110888
|
+
return { mover: a, anchor: b };
|
|
110889
|
+
const areaA = getArea(a.bounds);
|
|
110890
|
+
const areaB = getArea(b.bounds);
|
|
110891
|
+
if (areaA < areaB)
|
|
110892
|
+
return { mover: a, anchor: b };
|
|
110893
|
+
if (areaB < areaA)
|
|
110894
|
+
return { mover: b, anchor: a };
|
|
110895
|
+
return a.order > b.order ? { mover: a, anchor: b } : { mover: b, anchor: a };
|
|
110896
|
+
};
|
|
110897
|
+
var getMoveDirectionBetweenComponents = (mover, anchor, axis) => {
|
|
110898
|
+
if (axis === "x") {
|
|
110899
|
+
if ((mover.centerX ?? 0) >= (anchor.centerX ?? 0))
|
|
110900
|
+
return "right";
|
|
110901
|
+
return "left";
|
|
110902
|
+
}
|
|
110903
|
+
if ((mover.centerY ?? 0) >= (anchor.centerY ?? 0))
|
|
110904
|
+
return "down";
|
|
110905
|
+
return "up";
|
|
110906
|
+
};
|
|
110907
|
+
var getSeparationSuggestion = (a, b, overlapX, overlapY, preferredMover) => {
|
|
110908
|
+
if (a.centerX === null || a.centerY === null || b.centerX === null || b.centerY === null) {
|
|
110909
|
+
return;
|
|
110910
|
+
}
|
|
110911
|
+
const { mover, anchor } = chooseMover(a, b, preferredMover);
|
|
110912
|
+
const axis = overlapX <= overlapY ? "x" : "y";
|
|
110913
|
+
const distance = axis === "x" ? overlapX : overlapY;
|
|
110914
|
+
const direction = getMoveDirectionBetweenComponents(mover, anchor, axis);
|
|
110915
|
+
return `move ${mover.name} ${fmtMm2(distance)} ${direction}`;
|
|
110916
|
+
};
|
|
110917
|
+
var getCountainmentBonus = (a, b) => {
|
|
110918
|
+
if (!a.bounds || !b.bounds || a.centerX === null || a.centerY === null)
|
|
110919
|
+
return 0;
|
|
110920
|
+
const centerInsideB = a.centerX >= b.bounds.min_x && a.centerX <= b.bounds.max_x && a.centerY >= b.bounds.min_y && a.centerY <= b.bounds.max_y;
|
|
110921
|
+
return centerInsideB ? 25 : 0;
|
|
110922
|
+
};
|
|
110923
|
+
var createIssue = (issue) => issue;
|
|
110924
|
+
var getComponentByPcbId = (componentsByPcbId, pcbComponentId) => {
|
|
110925
|
+
if (typeof pcbComponentId !== "string")
|
|
110926
|
+
return null;
|
|
110927
|
+
return componentsByPcbId.get(pcbComponentId) ?? null;
|
|
110928
|
+
};
|
|
110929
|
+
var getLayers = (value) => {
|
|
110930
|
+
if (!Array.isArray(value))
|
|
110931
|
+
return [];
|
|
110932
|
+
return value.filter((layer) => typeof layer === "string");
|
|
110933
|
+
};
|
|
110934
|
+
var buildComponentContexts = (circuitJson) => {
|
|
110935
|
+
const components = [];
|
|
110936
|
+
const sourceComponentsById = /* @__PURE__ */ new Map;
|
|
110937
|
+
const componentByName = /* @__PURE__ */ new Map;
|
|
110938
|
+
const componentsByPcbId = /* @__PURE__ */ new Map;
|
|
110939
|
+
let order = 0;
|
|
110940
|
+
for (const el of circuitJson) {
|
|
110941
|
+
if (el.type !== "source_component" || typeof el.source_component_id !== "string" || typeof el.name !== "string") {
|
|
110942
|
+
continue;
|
|
110943
|
+
}
|
|
110944
|
+
if (componentByName.has(el.name))
|
|
110945
|
+
continue;
|
|
110946
|
+
const context = {
|
|
110947
|
+
name: el.name,
|
|
110948
|
+
sourceComponent: el,
|
|
110949
|
+
sourceComponentId: el.source_component_id,
|
|
110950
|
+
pcbComponent: null,
|
|
110951
|
+
pcbComponentId: null,
|
|
110952
|
+
centerX: null,
|
|
110953
|
+
centerY: null,
|
|
110954
|
+
layer: null,
|
|
110955
|
+
width: null,
|
|
110956
|
+
height: null,
|
|
110957
|
+
bounds: null,
|
|
110958
|
+
anchorAlignment: CENTER_ANCHOR2,
|
|
110959
|
+
placementMode: "none",
|
|
110960
|
+
pads: [],
|
|
110961
|
+
courtyards: [],
|
|
110962
|
+
isConnectorLike: false,
|
|
110963
|
+
order: order++
|
|
110964
|
+
};
|
|
110965
|
+
const xDefinitionRaw = el.pcbX ?? el.pcb_x ?? el.x;
|
|
110966
|
+
const yDefinitionRaw = el.pcbY ?? el.pcb_y ?? el.y;
|
|
110967
|
+
context.xDefinition = xDefinitionRaw === undefined ? undefined : String(xDefinitionRaw);
|
|
110968
|
+
context.yDefinition = yDefinitionRaw === undefined ? undefined : String(yDefinitionRaw);
|
|
110969
|
+
if (context.xDefinition !== undefined || context.yDefinition !== undefined) {
|
|
110970
|
+
context.placementMode = "props_set";
|
|
110971
|
+
} else if (el.placement_mode === "auto") {
|
|
110972
|
+
context.placementMode = "auto";
|
|
110973
|
+
}
|
|
110974
|
+
components.push(context);
|
|
110975
|
+
sourceComponentsById.set(context.sourceComponentId, context);
|
|
110976
|
+
componentByName.set(context.name, context);
|
|
110977
|
+
}
|
|
110978
|
+
for (const el of circuitJson) {
|
|
110979
|
+
if (el.type !== "pcb_component" || typeof el.source_component_id !== "string" || typeof el.pcb_component_id !== "string") {
|
|
110980
|
+
continue;
|
|
110981
|
+
}
|
|
110982
|
+
const context = sourceComponentsById.get(el.source_component_id);
|
|
110983
|
+
if (!context)
|
|
110984
|
+
continue;
|
|
110985
|
+
context.pcbComponent = el;
|
|
110986
|
+
context.pcbComponentId = el.pcb_component_id;
|
|
110987
|
+
componentsByPcbId.set(el.pcb_component_id, context);
|
|
110988
|
+
const center = typeof el.center === "object" && el.center ? el.center : null;
|
|
110989
|
+
context.centerX = center ? toNumber22(center.x) : null;
|
|
110990
|
+
context.centerY = center ? toNumber22(center.y) : null;
|
|
110991
|
+
context.layer = typeof el.layer === "string" ? el.layer : null;
|
|
110992
|
+
context.width = toNumber22(el.width);
|
|
110993
|
+
context.height = toNumber22(el.height);
|
|
110994
|
+
if (context.centerX !== null && context.centerY !== null && context.width !== null && context.height !== null) {
|
|
110995
|
+
context.bounds = getBoundsFromCenterAndSize(context.centerX, context.centerY, context.width, context.height);
|
|
110996
|
+
}
|
|
110997
|
+
if (context.placementMode === "none" && el.position_mode === "auto") {
|
|
110998
|
+
context.placementMode = "auto";
|
|
110999
|
+
}
|
|
111000
|
+
if (context.sourceComponent.ftype === "simple_pin_header" && context.width !== null && context.height !== null) {
|
|
111001
|
+
context.orientation = context.width >= context.height ? "horizontal" : "vertical";
|
|
111002
|
+
}
|
|
111003
|
+
}
|
|
111004
|
+
for (const el of circuitJson) {
|
|
111005
|
+
if (el.type === "pcb_silkscreen_text" && typeof el.anchor_alignment === "string") {
|
|
111006
|
+
const context = getComponentByPcbId(componentsByPcbId, el.pcb_component_id);
|
|
111007
|
+
if (!context)
|
|
111008
|
+
continue;
|
|
111009
|
+
context.anchorAlignment = el.anchor_alignment;
|
|
111010
|
+
continue;
|
|
111011
|
+
}
|
|
111012
|
+
if (el.type === "pcb_smtpad") {
|
|
111013
|
+
const context = getComponentByPcbId(componentsByPcbId, el.pcb_component_id);
|
|
111014
|
+
if (!context)
|
|
111015
|
+
continue;
|
|
111016
|
+
const x = toNumber22(el.x);
|
|
111017
|
+
const y = toNumber22(el.y);
|
|
111018
|
+
const width = toNumber22(el.width);
|
|
111019
|
+
const height = toNumber22(el.height);
|
|
111020
|
+
const layer = typeof el.layer === "string" ? el.layer : null;
|
|
111021
|
+
if (x === null || y === null || width === null || height === null || !layer)
|
|
111022
|
+
continue;
|
|
111023
|
+
context.pads.push({
|
|
111024
|
+
bounds: getBoundsFromCenterAndSize(x, y, width, height),
|
|
111025
|
+
layers: [layer]
|
|
111026
|
+
});
|
|
111027
|
+
continue;
|
|
111028
|
+
}
|
|
111029
|
+
if (el.type === "pcb_plated_hole") {
|
|
111030
|
+
const context = getComponentByPcbId(componentsByPcbId, el.pcb_component_id);
|
|
111031
|
+
if (!context)
|
|
111032
|
+
continue;
|
|
111033
|
+
const x = toNumber22(el.x);
|
|
111034
|
+
const y = toNumber22(el.y);
|
|
111035
|
+
const layers = getLayers(el.layers);
|
|
111036
|
+
if (x === null || y === null || layers.length === 0)
|
|
111037
|
+
continue;
|
|
111038
|
+
let width = null;
|
|
111039
|
+
let height = null;
|
|
111040
|
+
if (el.shape === "circle") {
|
|
111041
|
+
const diameter = toNumber22(el.outer_diameter) ?? toNumber22(el.hole_diameter);
|
|
111042
|
+
width = diameter;
|
|
111043
|
+
height = diameter;
|
|
111044
|
+
} else if (el.shape === "circular_hole_with_rect_pad" || el.shape === "pill_hole_with_rect_pad" || el.shape === "rotated_pill_hole_with_rect_pad") {
|
|
111045
|
+
width = toNumber22(el.rect_pad_width);
|
|
111046
|
+
height = toNumber22(el.rect_pad_height);
|
|
111047
|
+
} else if (el.shape === "hole_with_polygon_pad") {
|
|
111048
|
+
const padOutline = Array.isArray(el.pad_outline) ? el.pad_outline : [];
|
|
111049
|
+
const padBounds = getBoundsFromPoints(padOutline.map((point) => ({
|
|
111050
|
+
x: (toNumber22(point.x) ?? 0) + x,
|
|
111051
|
+
y: (toNumber22(point.y) ?? 0) + y
|
|
111052
|
+
})));
|
|
111053
|
+
if (padBounds) {
|
|
111054
|
+
context.pads.push({
|
|
111055
|
+
bounds: padBounds,
|
|
111056
|
+
layers
|
|
111057
|
+
});
|
|
111058
|
+
}
|
|
111059
|
+
continue;
|
|
111060
|
+
}
|
|
111061
|
+
if (width === null || height === null)
|
|
111062
|
+
continue;
|
|
111063
|
+
context.pads.push({
|
|
111064
|
+
bounds: getBoundsFromCenterAndSize(x, y, width, height),
|
|
111065
|
+
layers
|
|
111066
|
+
});
|
|
111067
|
+
continue;
|
|
111068
|
+
}
|
|
111069
|
+
if (el.type === "pcb_courtyard_rect") {
|
|
111070
|
+
const context = getComponentByPcbId(componentsByPcbId, el.pcb_component_id);
|
|
111071
|
+
if (!context)
|
|
111072
|
+
continue;
|
|
111073
|
+
const center = typeof el.center === "object" && el.center ? el.center : null;
|
|
111074
|
+
const centerX = center ? toNumber22(center.x) : null;
|
|
111075
|
+
const centerY = center ? toNumber22(center.y) : null;
|
|
111076
|
+
const width = toNumber22(el.width);
|
|
111077
|
+
const height = toNumber22(el.height);
|
|
111078
|
+
if (centerX === null || centerY === null || width === null || height === null)
|
|
111079
|
+
continue;
|
|
111080
|
+
context.courtyards.push(getBoundsFromCenterAndSize(centerX, centerY, width, height));
|
|
111081
|
+
continue;
|
|
111082
|
+
}
|
|
111083
|
+
if (el.type === "pcb_courtyard_outline") {
|
|
111084
|
+
const context = getComponentByPcbId(componentsByPcbId, el.pcb_component_id);
|
|
111085
|
+
if (!context)
|
|
111086
|
+
continue;
|
|
111087
|
+
const outline = Array.isArray(el.outline) ? el.outline : [];
|
|
111088
|
+
const bounds = getBoundsFromPoints(outline);
|
|
111089
|
+
if (bounds)
|
|
111090
|
+
context.courtyards.push(bounds);
|
|
111091
|
+
continue;
|
|
111092
|
+
}
|
|
111093
|
+
if (el.type === "pcb_courtyard_polygon") {
|
|
111094
|
+
const context = getComponentByPcbId(componentsByPcbId, el.pcb_component_id);
|
|
111095
|
+
if (!context)
|
|
111096
|
+
continue;
|
|
111097
|
+
const points = Array.isArray(el.points) ? el.points : [];
|
|
111098
|
+
const bounds = getBoundsFromPoints(points);
|
|
111099
|
+
if (bounds)
|
|
111100
|
+
context.courtyards.push(bounds);
|
|
111101
|
+
}
|
|
111102
|
+
}
|
|
111103
|
+
for (const context of components) {
|
|
111104
|
+
const nameUpper = context.name.toUpperCase();
|
|
111105
|
+
const ftype = String(context.sourceComponent.ftype ?? "").toUpperCase();
|
|
111106
|
+
const manufacturerPartNumber = String(context.sourceComponent.manufacturer_part_number ?? context.sourceComponent.manufacturerPartNumber ?? "").toUpperCase();
|
|
111107
|
+
const platedHoleCount = context.pads.filter((pad) => pad.layers.includes("bottom")).length;
|
|
111108
|
+
context.isConnectorLike = ftype === "SIMPLE_PIN_HEADER" || nameUpper.startsWith("USB") || /^J[A-Z0-9_]*\d*$/.test(nameUpper) || /USB|CONN|CONNECTOR|HEADER|SOCKET|JST|HDMI|RJ|BARREL/.test(manufacturerPartNumber) || platedHoleCount >= 4 && context.bounds !== null && Math.max(context.bounds.width, context.bounds.height) >= 4;
|
|
111109
|
+
}
|
|
111110
|
+
const pcbBoard = circuitJson.find((el) => el.type === "pcb_board");
|
|
111111
|
+
const boardCenter = pcbBoard && typeof pcbBoard.center === "object" && pcbBoard.center ? pcbBoard.center : null;
|
|
111112
|
+
const boardCenterX = boardCenter ? toNumber22(boardCenter.x) : null;
|
|
111113
|
+
const boardCenterY = boardCenter ? toNumber22(boardCenter.y) : null;
|
|
111114
|
+
const boardWidth = toNumber22(pcbBoard?.width);
|
|
111115
|
+
const boardHeight = toNumber22(pcbBoard?.height);
|
|
111116
|
+
const boardBounds = boardCenterX !== null && boardCenterY !== null && boardWidth !== null && boardHeight !== null ? getBoundsFromCenterAndSize(boardCenterX, boardCenterY, boardWidth, boardHeight) : null;
|
|
111117
|
+
return {
|
|
111118
|
+
components,
|
|
111119
|
+
componentByName,
|
|
111120
|
+
boardBounds
|
|
111121
|
+
};
|
|
111122
|
+
};
|
|
111123
|
+
var buildIssues = (components, boardBounds) => {
|
|
111124
|
+
const issues = [];
|
|
111125
|
+
for (const component of components) {
|
|
111126
|
+
const boardEdgeStatus = getBoardEdgeStatus(component.bounds, boardBounds, component.name);
|
|
111127
|
+
if (!boardEdgeStatus || boardEdgeStatus.status !== "outside")
|
|
111128
|
+
continue;
|
|
111129
|
+
const moveDirection = getMoveDirectionForEdge(boardEdgeStatus.edge);
|
|
111130
|
+
issues.push(createIssue({
|
|
111131
|
+
type: "off_board",
|
|
111132
|
+
componentA: component.name,
|
|
111133
|
+
clearance: -boardEdgeStatus.distance,
|
|
111134
|
+
severity: 220 + boardEdgeStatus.distance * 100,
|
|
111135
|
+
summary: `${component.name} is ${fmtMm2(boardEdgeStatus.distance)} outside ${boardEdgeStatus.edge} edge`,
|
|
111136
|
+
suggested_move: `move ${component.name} ${fmtMm2(boardEdgeStatus.distance)} ${moveDirection} to clear ${boardEdgeStatus.edge} edge`
|
|
111137
|
+
}));
|
|
111138
|
+
}
|
|
111139
|
+
for (let i = 0;i < components.length; i += 1) {
|
|
111140
|
+
const a = components[i];
|
|
111141
|
+
if (!a)
|
|
111142
|
+
continue;
|
|
111143
|
+
for (let j = i + 1;j < components.length; j += 1) {
|
|
111144
|
+
const b = components[j];
|
|
111145
|
+
if (!b)
|
|
111146
|
+
continue;
|
|
111147
|
+
if (!a.bounds || !b.bounds)
|
|
111148
|
+
continue;
|
|
111149
|
+
if (a.layer !== null && b.layer !== null && a.layer !== b.layer)
|
|
111150
|
+
continue;
|
|
111151
|
+
let strongestPadOverlap = null;
|
|
111152
|
+
for (const padA of a.pads) {
|
|
111153
|
+
for (const padB of b.pads) {
|
|
111154
|
+
if (!layersIntersect(padA.layers, padB.layers))
|
|
111155
|
+
continue;
|
|
111156
|
+
const overlap = getOverlap(padA.bounds, padB.bounds);
|
|
111157
|
+
if (!overlap)
|
|
111158
|
+
continue;
|
|
111159
|
+
if (!strongestPadOverlap || overlap.clearance < strongestPadOverlap.clearance) {
|
|
111160
|
+
strongestPadOverlap = overlap;
|
|
111161
|
+
}
|
|
111162
|
+
}
|
|
111163
|
+
}
|
|
111164
|
+
const bodyOverlap = getOverlap(a.bounds, b.bounds);
|
|
111165
|
+
let strongestCourtyardOverlap = null;
|
|
111166
|
+
if (!strongestPadOverlap) {
|
|
111167
|
+
for (const courtyardA of a.courtyards) {
|
|
111168
|
+
for (const courtyardB of b.courtyards) {
|
|
111169
|
+
const overlap = getOverlap(courtyardA, courtyardB);
|
|
111170
|
+
if (!overlap)
|
|
111171
|
+
continue;
|
|
111172
|
+
if (!strongestCourtyardOverlap || overlap.clearance < strongestCourtyardOverlap.clearance) {
|
|
111173
|
+
strongestCourtyardOverlap = overlap;
|
|
111174
|
+
}
|
|
111175
|
+
}
|
|
111176
|
+
}
|
|
111177
|
+
}
|
|
111178
|
+
if (strongestPadOverlap) {
|
|
111179
|
+
issues.push(createIssue({
|
|
111180
|
+
type: "pad_overlap",
|
|
111181
|
+
componentA: a.name,
|
|
111182
|
+
componentB: b.name,
|
|
111183
|
+
clearance: strongestPadOverlap.clearance,
|
|
111184
|
+
severity: 300 + Math.abs(strongestPadOverlap.clearance) * 120,
|
|
111185
|
+
summary: `${a.name} and ${b.name} pad overlap by ${fmtMm2(Math.abs(strongestPadOverlap.clearance))}`,
|
|
111186
|
+
suggested_move: getSeparationSuggestion(a, b, strongestPadOverlap.overlapX, strongestPadOverlap.overlapY)
|
|
111187
|
+
}));
|
|
111188
|
+
}
|
|
111189
|
+
if (bodyOverlap) {
|
|
111190
|
+
if (a.isConnectorLike || b.isConnectorLike) {
|
|
111191
|
+
const connector = a.isConnectorLike ? a : b;
|
|
111192
|
+
const intruder = connector === a ? b : a;
|
|
111193
|
+
const overlapX = bodyOverlap.overlapX;
|
|
111194
|
+
const overlapY = bodyOverlap.overlapY;
|
|
111195
|
+
const containmentBonus = getCountainmentBonus(intruder, connector);
|
|
111196
|
+
issues.push(createIssue({
|
|
111197
|
+
type: "connector_body_intrusion",
|
|
111198
|
+
componentA: intruder.name,
|
|
111199
|
+
componentB: connector.name,
|
|
111200
|
+
clearance: bodyOverlap.clearance,
|
|
111201
|
+
severity: 260 + Math.abs(bodyOverlap.clearance) * 100 + containmentBonus,
|
|
111202
|
+
summary: `${intruder.name} intrudes ${fmtMm2(Math.abs(bodyOverlap.clearance))} into ${connector.name} connector body`,
|
|
111203
|
+
suggested_move: getSeparationSuggestion(a, b, overlapX, overlapY, intruder.name)
|
|
111204
|
+
}));
|
|
111205
|
+
} else if (!strongestPadOverlap) {
|
|
111206
|
+
const containmentBonus = getCountainmentBonus(a, b) + getCountainmentBonus(b, a);
|
|
111207
|
+
issues.push(createIssue({
|
|
111208
|
+
type: "footprint_intrusion",
|
|
111209
|
+
componentA: a.name,
|
|
111210
|
+
componentB: b.name,
|
|
111211
|
+
clearance: bodyOverlap.clearance,
|
|
111212
|
+
severity: 180 + Math.abs(bodyOverlap.clearance) * 80 + containmentBonus,
|
|
111213
|
+
summary: `${a.name} and ${b.name} footprint intrusion by ${fmtMm2(Math.abs(bodyOverlap.clearance))}`,
|
|
111214
|
+
suggested_move: getSeparationSuggestion(a, b, bodyOverlap.overlapX, bodyOverlap.overlapY)
|
|
111215
|
+
}));
|
|
111216
|
+
}
|
|
111217
|
+
} else if (strongestCourtyardOverlap) {
|
|
111218
|
+
issues.push(createIssue({
|
|
111219
|
+
type: "courtyard_collision",
|
|
111220
|
+
componentA: a.name,
|
|
111221
|
+
componentB: b.name,
|
|
111222
|
+
clearance: strongestCourtyardOverlap.clearance,
|
|
111223
|
+
severity: 120 + Math.abs(strongestCourtyardOverlap.clearance) * 80,
|
|
111224
|
+
summary: `${a.name} and ${b.name} courtyard collision by ${fmtMm2(Math.abs(strongestCourtyardOverlap.clearance))}`,
|
|
111225
|
+
suggested_move: getSeparationSuggestion(a, b, strongestCourtyardOverlap.overlapX, strongestCourtyardOverlap.overlapY)
|
|
111226
|
+
}));
|
|
111227
|
+
}
|
|
111228
|
+
}
|
|
111229
|
+
}
|
|
111230
|
+
return issues.sort((a, b) => b.severity - a.severity);
|
|
111231
|
+
};
|
|
111232
|
+
var buildClusters = (components, issues) => {
|
|
111233
|
+
const componentsByName = new Map(components.map((component) => [component.name, component]));
|
|
111234
|
+
const adjacency = /* @__PURE__ */ new Map;
|
|
111235
|
+
const incidentSeverity = /* @__PURE__ */ new Map;
|
|
111236
|
+
for (const component of components) {
|
|
111237
|
+
adjacency.set(component.name, /* @__PURE__ */ new Set);
|
|
111238
|
+
incidentSeverity.set(component.name, 0);
|
|
111239
|
+
}
|
|
111240
|
+
for (const issue of issues) {
|
|
111241
|
+
if (!issue.componentB)
|
|
111242
|
+
continue;
|
|
111243
|
+
adjacency.get(issue.componentA)?.add(issue.componentB);
|
|
111244
|
+
adjacency.get(issue.componentB)?.add(issue.componentA);
|
|
111245
|
+
incidentSeverity.set(issue.componentA, (incidentSeverity.get(issue.componentA) ?? 0) + issue.severity);
|
|
111246
|
+
incidentSeverity.set(issue.componentB, (incidentSeverity.get(issue.componentB) ?? 0) + issue.severity);
|
|
111247
|
+
}
|
|
111248
|
+
const visited = /* @__PURE__ */ new Set;
|
|
111249
|
+
const clusters = [];
|
|
111250
|
+
for (const component of components) {
|
|
111251
|
+
if (visited.has(component.name))
|
|
111252
|
+
continue;
|
|
111253
|
+
const queue = [component.name];
|
|
111254
|
+
const members = [];
|
|
111255
|
+
while (queue.length > 0) {
|
|
111256
|
+
const current = queue.shift();
|
|
111257
|
+
if (visited.has(current))
|
|
111258
|
+
continue;
|
|
111259
|
+
visited.add(current);
|
|
111260
|
+
members.push(current);
|
|
111261
|
+
for (const neighbor of adjacency.get(current) ?? []) {
|
|
111262
|
+
if (!visited.has(neighbor))
|
|
111263
|
+
queue.push(neighbor);
|
|
111264
|
+
}
|
|
111265
|
+
}
|
|
111266
|
+
if (members.length < 3)
|
|
111267
|
+
continue;
|
|
111268
|
+
const memberSet = new Set(members);
|
|
111269
|
+
const severity = issues.filter((issue) => issue.componentB && memberSet.has(issue.componentA) && memberSet.has(issue.componentB)).reduce((sum, issue) => sum + issue.severity, 0);
|
|
111270
|
+
if (severity === 0)
|
|
111271
|
+
continue;
|
|
111272
|
+
const sortedMembers = [...members].sort((a, b) => {
|
|
111273
|
+
const severityA = incidentSeverity.get(a) ?? 0;
|
|
111274
|
+
const severityB = incidentSeverity.get(b) ?? 0;
|
|
111275
|
+
if (severityA !== severityB)
|
|
111276
|
+
return severityB - severityA;
|
|
111277
|
+
const orderA = componentsByName.get(a)?.order ?? 0;
|
|
111278
|
+
const orderB = componentsByName.get(b)?.order ?? 0;
|
|
111279
|
+
return orderA - orderB;
|
|
111280
|
+
});
|
|
111281
|
+
const labelComponent = sortedMembers.find((name) => componentsByName.get(name)?.isConnectorLike) ?? sortedMembers[0];
|
|
111282
|
+
if (!labelComponent)
|
|
111283
|
+
continue;
|
|
111284
|
+
clusters.push({
|
|
111285
|
+
clusterName: `${stripNumericSuffix(labelComponent)} cluster`,
|
|
111286
|
+
componentNames: sortedMembers,
|
|
111287
|
+
severity
|
|
111288
|
+
});
|
|
111289
|
+
}
|
|
111290
|
+
return clusters.sort((a, b) => b.severity - a.severity);
|
|
111291
|
+
};
|
|
111292
|
+
var buildCountsByType = (issues) => {
|
|
111293
|
+
const counts = {};
|
|
111294
|
+
for (const issue of issues) {
|
|
111295
|
+
counts[issue.type] = (counts[issue.type] ?? 0) + 1;
|
|
111296
|
+
}
|
|
111297
|
+
return counts;
|
|
111298
|
+
};
|
|
111299
|
+
var buildComponentStatuses = (components, boardBounds, issues) => components.map((component) => {
|
|
111300
|
+
const componentIssues = issues.filter((issue) => issue.componentA === component.name || issue.componentB === component.name);
|
|
111301
|
+
return {
|
|
111302
|
+
componentName: component.name,
|
|
111303
|
+
placementMode: component.placementMode,
|
|
111304
|
+
sourcePlacement: {
|
|
111305
|
+
xDefinition: component.xDefinition,
|
|
111306
|
+
yDefinition: component.yDefinition
|
|
111307
|
+
},
|
|
111308
|
+
resolvedPlacement: {
|
|
111309
|
+
center: component.centerX !== null && component.centerY !== null && component.layer !== null ? {
|
|
111310
|
+
x: component.centerX,
|
|
111311
|
+
y: component.centerY,
|
|
111312
|
+
layer: component.layer
|
|
111313
|
+
} : undefined,
|
|
111314
|
+
bounds: component.bounds ? {
|
|
111315
|
+
width: component.bounds.width,
|
|
111316
|
+
height: component.bounds.height,
|
|
111317
|
+
min_x: component.bounds.min_x,
|
|
111318
|
+
max_x: component.bounds.max_x,
|
|
111319
|
+
min_y: component.bounds.min_y,
|
|
111320
|
+
max_y: component.bounds.max_y
|
|
111321
|
+
} : undefined,
|
|
111322
|
+
anchorAlignment: component.anchorAlignment,
|
|
111323
|
+
orientation: component.orientation
|
|
111324
|
+
},
|
|
111325
|
+
boardEdgeStatus: getBoardEdgeStatus(component.bounds, boardBounds, component.name),
|
|
111326
|
+
issues: componentIssues
|
|
111327
|
+
};
|
|
111328
|
+
});
|
|
111329
|
+
var formatSourcePlacement = (component) => {
|
|
111330
|
+
const bits = [`placement_mode=${component.placementMode}`];
|
|
111331
|
+
if (component.sourcePlacement.xDefinition !== undefined) {
|
|
111332
|
+
bits.push(`x=${component.sourcePlacement.xDefinition}`);
|
|
111333
|
+
}
|
|
111334
|
+
if (component.sourcePlacement.yDefinition !== undefined) {
|
|
111335
|
+
bits.push(`y=${component.sourcePlacement.yDefinition}`);
|
|
111336
|
+
}
|
|
111337
|
+
return bits.join(", ");
|
|
111338
|
+
};
|
|
111339
|
+
var formatResolvedPlacement = (component) => {
|
|
111340
|
+
const bits = [];
|
|
111341
|
+
const center = component.resolvedPlacement.center;
|
|
111342
|
+
const bounds = component.resolvedPlacement.bounds;
|
|
111343
|
+
if (center) {
|
|
111344
|
+
bits.push(`center=(${fmtMm2(center.x)}, ${fmtMm2(center.y)}) on ${center.layer}`);
|
|
111345
|
+
}
|
|
111346
|
+
if (bounds) {
|
|
111347
|
+
bits.push(`bounds=(minX=${fmtMm2(bounds.min_x)}, maxX=${fmtMm2(bounds.max_x)}, minY=${fmtMm2(bounds.min_y)}, maxY=${fmtMm2(bounds.max_y)})`);
|
|
111348
|
+
bits.push(`size=(width=${fmtMm2(bounds.width)}, height=${fmtMm2(bounds.height)})`);
|
|
111349
|
+
}
|
|
111350
|
+
bits.push(`anchor_alignment="${component.resolvedPlacement.anchorAlignment}"`);
|
|
111351
|
+
if (component.resolvedPlacement.orientation) {
|
|
111352
|
+
bits.push(`orientation=${component.resolvedPlacement.orientation}`);
|
|
111353
|
+
}
|
|
111354
|
+
return bits.join("; ");
|
|
111355
|
+
};
|
|
111356
|
+
var formatIssue = (issue) => {
|
|
111357
|
+
const suffix = issue.suggested_move ? ` Suggested move: ${issue.suggested_move}.` : "";
|
|
111358
|
+
return `${issue.summary}.${suffix}`;
|
|
111359
|
+
};
|
|
111360
|
+
var formatPlacementAnalysisReport = (report) => {
|
|
111361
|
+
const lines = [];
|
|
111362
|
+
const summaryBits = ISSUE_TYPE_ORDER.map((type) => getSummaryLabel(type, report.summary.countsByType[type] ?? 0)).filter((entry) => Boolean(entry));
|
|
111363
|
+
lines.push(summaryBits.length > 0 ? `placement summary: ${summaryBits.join(", ")}` : "placement summary: no placement issues");
|
|
111364
|
+
if (report.summary.topIssues.length > 0) {
|
|
111365
|
+
lines.push("");
|
|
111366
|
+
lines.push("worst issues:");
|
|
111367
|
+
report.summary.topIssues.forEach((issue, index) => {
|
|
111368
|
+
lines.push(`${index + 1}. ${formatIssue(issue)}`);
|
|
111369
|
+
});
|
|
111370
|
+
}
|
|
111371
|
+
if (report.summary.likelyBadClusters.length > 0) {
|
|
111372
|
+
lines.push("");
|
|
111373
|
+
lines.push("likely bad clusters:");
|
|
111374
|
+
for (const cluster of report.summary.likelyBadClusters) {
|
|
111375
|
+
lines.push(`- ${cluster.clusterName}: ${cluster.componentNames.join(", ")}`);
|
|
111376
|
+
}
|
|
111377
|
+
}
|
|
111378
|
+
lines.push("");
|
|
111379
|
+
lines.push("board-edge status:");
|
|
111380
|
+
for (const component of report.components) {
|
|
111381
|
+
if (!component.boardEdgeStatus)
|
|
111382
|
+
continue;
|
|
111383
|
+
lines.push(`- ${component.componentName}: ${formatBoardEdgeStatus(component.boardEdgeStatus)}`);
|
|
111384
|
+
}
|
|
111385
|
+
const flaggedComponents = report.components.filter((component) => component.issues.length > 0);
|
|
111386
|
+
if (flaggedComponents.length > 0) {
|
|
111387
|
+
lines.push("");
|
|
111388
|
+
lines.push("flagged components:");
|
|
111389
|
+
for (const component of flaggedComponents) {
|
|
111390
|
+
lines.push(`- ${component.componentName}`);
|
|
111391
|
+
lines.push(` source placement: ${formatSourcePlacement(component)}`);
|
|
111392
|
+
lines.push(` resolved placement: ${formatResolvedPlacement(component)}`);
|
|
111393
|
+
if (component.boardEdgeStatus) {
|
|
111394
|
+
lines.push(` board edge status: ${formatBoardEdgeStatus(component.boardEdgeStatus)}`);
|
|
111395
|
+
}
|
|
111396
|
+
lines.push(" issues:");
|
|
111397
|
+
for (const issue of component.issues) {
|
|
111398
|
+
lines.push(` - ${formatIssue(issue)}`);
|
|
111399
|
+
}
|
|
111400
|
+
}
|
|
111401
|
+
}
|
|
111402
|
+
return lines.join(`
|
|
111403
|
+
`);
|
|
111404
|
+
};
|
|
111405
|
+
var buildPlacementAnalysisReport = (circuitJson) => {
|
|
111406
|
+
const { components, boardBounds } = buildComponentContexts(circuitJson);
|
|
111407
|
+
const issues = buildIssues(components, boardBounds);
|
|
111408
|
+
const clusters = buildClusters(components, issues);
|
|
111409
|
+
const countsByType = buildCountsByType(issues);
|
|
111410
|
+
return {
|
|
111411
|
+
summary: {
|
|
111412
|
+
totalIssueCount: issues.length,
|
|
111413
|
+
countsByType,
|
|
111414
|
+
topIssues: issues.slice(0, TOP_ISSUE_LIMIT),
|
|
111415
|
+
likelyBadClusters: clusters
|
|
111416
|
+
},
|
|
111417
|
+
components: buildComponentStatuses(components, boardBounds, issues),
|
|
111418
|
+
issues
|
|
111419
|
+
};
|
|
111420
|
+
};
|
|
110754
111421
|
var analyzeAllPlacements = (circuitJson) => {
|
|
110755
111422
|
const componentNames = [];
|
|
110756
111423
|
const seenNames = /* @__PURE__ */ new Set;
|
|
@@ -110767,10 +111434,12 @@ var analyzeAllPlacements = (circuitJson) => {
|
|
|
110767
111434
|
analysis: analyzeComponentPlacement(circuitJson, componentName)
|
|
110768
111435
|
}));
|
|
110769
111436
|
const lineItems = analyses.flatMap(({ analysis }) => analysis.getLineItems());
|
|
111437
|
+
const report = buildPlacementAnalysisReport(circuitJson);
|
|
110770
111438
|
return {
|
|
110771
111439
|
getLineItems: () => lineItems,
|
|
110772
|
-
getString: () =>
|
|
110773
|
-
|
|
111440
|
+
getString: () => formatPlacementAnalysisReport(report),
|
|
111441
|
+
getIssues: () => report.issues,
|
|
111442
|
+
getReport: () => report
|
|
110774
111443
|
};
|
|
110775
111444
|
};
|
|
110776
111445
|
|
|
@@ -110785,8 +111454,10 @@ var checkPlacement = async (file, refdes) => {
|
|
|
110785
111454
|
},
|
|
110786
111455
|
allowPrebuiltCircuitJson: true
|
|
110787
111456
|
});
|
|
110788
|
-
|
|
110789
|
-
|
|
111457
|
+
if (refdes) {
|
|
111458
|
+
return analyzeComponentPlacement(circuitJson, refdes).getString();
|
|
111459
|
+
}
|
|
111460
|
+
return analyzeAllPlacements(circuitJson).getString();
|
|
110790
111461
|
};
|
|
110791
111462
|
var registerCheckPlacement = (program2) => {
|
|
110792
111463
|
program2.commands.find((c) => c.name() === "check").command("placement").description("Partially build and validate the placement").argument("[file]", "Path to the entry file").argument("[refdes]", "Optional refdes to scope the check").action(async (file, refdes) => {
|
|
@@ -133568,7 +134239,7 @@ var getDirectionalFreeSpace = (spatialIndex, componentIndex, directions) => {
|
|
|
133568
134239
|
}
|
|
133569
134240
|
return freeSpaceByDirection;
|
|
133570
134241
|
};
|
|
133571
|
-
var
|
|
134242
|
+
var fmtMm3 = (value) => `${value.toFixed(1)}mm`;
|
|
133572
134243
|
var roundsToZeroMm = (value) => Math.abs(value) < 0.05;
|
|
133573
134244
|
var toNumber3 = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
133574
134245
|
var getPcbComponentBounds = (element) => {
|
|
@@ -133612,10 +134283,10 @@ var getPlacedComponents = (circuitJson) => {
|
|
|
133612
134283
|
}
|
|
133613
134284
|
return placedComponents;
|
|
133614
134285
|
};
|
|
133615
|
-
var getOffsetFromTopOfRegion = (componentBounds, regionBounds) =>
|
|
133616
|
-
var getOffsetFromBottomOfRegion = (componentBounds, regionBounds) =>
|
|
133617
|
-
var getOffsetFromLeftOfRegion = (componentBounds, regionBounds) =>
|
|
133618
|
-
var getOffsetFromRightOfRegion = (componentBounds, regionBounds) =>
|
|
134286
|
+
var getOffsetFromTopOfRegion = (componentBounds, regionBounds) => fmtMm3(regionBounds.maxY - componentBounds.maxY);
|
|
134287
|
+
var getOffsetFromBottomOfRegion = (componentBounds, regionBounds) => fmtMm3(componentBounds.minY - regionBounds.minY);
|
|
134288
|
+
var getOffsetFromLeftOfRegion = (componentBounds, regionBounds) => fmtMm3(componentBounds.minX - regionBounds.minX);
|
|
134289
|
+
var getOffsetFromRightOfRegion = (componentBounds, regionBounds) => fmtMm3(regionBounds.maxX - componentBounds.maxX);
|
|
133619
134290
|
var addEdgeAlignedOffsets = (nearbyComponent, componentBounds, regionBounds) => {
|
|
133620
134291
|
if (nearbyComponent.onLeftEdgeOfRegion || nearbyComponent.onRightEdgeOfRegion) {
|
|
133621
134292
|
nearbyComponent.distToTopOfRegion = getOffsetFromTopOfRegion(componentBounds, regionBounds);
|
|
@@ -133747,28 +134418,28 @@ var getOutsideComponentCandidate = (component, componentIndex, regionBounds) =>
|
|
|
133747
134418
|
if (roundsToZeroMm(candidate.distance)) {
|
|
133748
134419
|
nearbyComponentCandidate.onLeftEdgeOfRegion = true;
|
|
133749
134420
|
} else {
|
|
133750
|
-
nearbyComponentCandidate.distToLeftEdgeOfRegion =
|
|
134421
|
+
nearbyComponentCandidate.distToLeftEdgeOfRegion = fmtMm3(candidate.distance);
|
|
133751
134422
|
}
|
|
133752
134423
|
break;
|
|
133753
134424
|
case "right":
|
|
133754
134425
|
if (roundsToZeroMm(candidate.distance)) {
|
|
133755
134426
|
nearbyComponentCandidate.onRightEdgeOfRegion = true;
|
|
133756
134427
|
} else {
|
|
133757
|
-
nearbyComponentCandidate.distToRightEdgeOfRegion =
|
|
134428
|
+
nearbyComponentCandidate.distToRightEdgeOfRegion = fmtMm3(candidate.distance);
|
|
133758
134429
|
}
|
|
133759
134430
|
break;
|
|
133760
134431
|
case "top":
|
|
133761
134432
|
if (roundsToZeroMm(candidate.distance)) {
|
|
133762
134433
|
nearbyComponentCandidate.onTopEdgeOfRegion = true;
|
|
133763
134434
|
} else {
|
|
133764
|
-
nearbyComponentCandidate.distToTopOfRegion =
|
|
134435
|
+
nearbyComponentCandidate.distToTopOfRegion = fmtMm3(candidate.distance);
|
|
133765
134436
|
}
|
|
133766
134437
|
break;
|
|
133767
134438
|
case "bottom":
|
|
133768
134439
|
if (roundsToZeroMm(candidate.distance)) {
|
|
133769
134440
|
nearbyComponentCandidate.onBottomEdgeOfRegion = true;
|
|
133770
134441
|
} else {
|
|
133771
|
-
nearbyComponentCandidate.distToBottomOfRegion =
|
|
134442
|
+
nearbyComponentCandidate.distToBottomOfRegion = fmtMm3(candidate.distance);
|
|
133772
134443
|
}
|
|
133773
134444
|
break;
|
|
133774
134445
|
}
|
|
@@ -168821,7 +169492,7 @@ function normalizeDegrees(angle) {
|
|
|
168821
169492
|
var doBoundsOverlap = (bounds1, bounds2) => {
|
|
168822
169493
|
return !(bounds1.maxX < bounds2.minX || bounds2.maxX < bounds1.minX || bounds1.maxY < bounds2.minY || bounds2.maxY < bounds1.minY);
|
|
168823
169494
|
};
|
|
168824
|
-
var
|
|
169495
|
+
var getBoundsFromPoints2 = (points) => {
|
|
168825
169496
|
if (points.length === 0) {
|
|
168826
169497
|
return null;
|
|
168827
169498
|
}
|
|
@@ -198646,8 +199317,8 @@ function checkCourtyardOverlap(circuitJson) {
|
|
|
198646
199317
|
for (const b22 of byComponent.get(idB)) {
|
|
198647
199318
|
const polyA = getCourtyardPolygon(a2);
|
|
198648
199319
|
const polyB = getCourtyardPolygon(b22);
|
|
198649
|
-
const boundsA =
|
|
198650
|
-
const boundsB =
|
|
199320
|
+
const boundsA = getBoundsFromPoints2(polyA);
|
|
199321
|
+
const boundsB = getBoundsFromPoints2(polyB);
|
|
198651
199322
|
if (!boundsA || !boundsB)
|
|
198652
199323
|
continue;
|
|
198653
199324
|
if (areBoundsOverlappingPolygon(boundsA, polyB) || areBoundsOverlappingPolygon(boundsB, polyA)) {
|
|
@@ -219679,7 +220350,7 @@ var CourtyardOutline = class extends PrimitiveComponent2 {
|
|
|
219679
220350
|
if (!props.outline || props.outline.length === 0) {
|
|
219680
220351
|
return { width: 0, height: 0 };
|
|
219681
220352
|
}
|
|
219682
|
-
const bounds =
|
|
220353
|
+
const bounds = getBoundsFromPoints2(props.outline);
|
|
219683
220354
|
if (!bounds) {
|
|
219684
220355
|
return { width: 0, height: 0 };
|
|
219685
220356
|
}
|
|
@@ -223588,7 +224259,7 @@ var NormalComponent__getMinimumFlexContainerSize = (component) => {
|
|
|
223588
224259
|
if (!pcbGroup)
|
|
223589
224260
|
return null;
|
|
223590
224261
|
if (pcbGroup.outline && pcbGroup.outline.length > 0) {
|
|
223591
|
-
const bounds =
|
|
224262
|
+
const bounds = getBoundsFromPoints2(pcbGroup.outline);
|
|
223592
224263
|
if (!bounds)
|
|
223593
224264
|
return null;
|
|
223594
224265
|
return {
|
|
@@ -226857,7 +227528,7 @@ function Group_doInitialPcbComponentAnchorAlignment(group) {
|
|
|
226857
227528
|
let height = pcbGroup.height;
|
|
226858
227529
|
const { center: center2 } = pcbGroup;
|
|
226859
227530
|
if (pcbGroup.outline && pcbGroup.outline.length > 0) {
|
|
226860
|
-
const bounds2 =
|
|
227531
|
+
const bounds2 = getBoundsFromPoints2(pcbGroup.outline);
|
|
226861
227532
|
if (bounds2) {
|
|
226862
227533
|
width = bounds2.maxX - bounds2.minX;
|
|
226863
227534
|
height = bounds2.maxY - bounds2.minY;
|
|
@@ -230167,7 +230838,7 @@ var Group6 = class extends NormalComponent3 {
|
|
|
230167
230838
|
x: distance.parse(point6.x),
|
|
230168
230839
|
y: distance.parse(point6.y)
|
|
230169
230840
|
}));
|
|
230170
|
-
const outlineBounds =
|
|
230841
|
+
const outlineBounds = getBoundsFromPoints2(numericOutline);
|
|
230171
230842
|
if (!outlineBounds)
|
|
230172
230843
|
return;
|
|
230173
230844
|
const centerX2 = (outlineBounds.minX + outlineBounds.maxX) / 2;
|
|
@@ -232099,7 +232770,7 @@ var Board = class extends Group6 {
|
|
|
232099
232770
|
let width = dbBoard?.width ?? props.width;
|
|
232100
232771
|
let height = dbBoard?.height ?? props.height;
|
|
232101
232772
|
if ((width == null || height == null) && props.outline?.length) {
|
|
232102
|
-
const outlineBounds =
|
|
232773
|
+
const outlineBounds = getBoundsFromPoints2(props.outline);
|
|
232103
232774
|
if (outlineBounds) {
|
|
232104
232775
|
width ??= outlineBounds.maxX - outlineBounds.minX;
|
|
232105
232776
|
height ??= outlineBounds.maxY - outlineBounds.minY;
|
|
@@ -232155,7 +232826,7 @@ var Board = class extends Group6 {
|
|
|
232155
232826
|
let width = pcbGroup.width ?? 0;
|
|
232156
232827
|
let height = pcbGroup.height ?? 0;
|
|
232157
232828
|
if (pcbGroup.outline && pcbGroup.outline.length > 0) {
|
|
232158
|
-
const bounds =
|
|
232829
|
+
const bounds = getBoundsFromPoints2(pcbGroup.outline);
|
|
232159
232830
|
if (bounds) {
|
|
232160
232831
|
width = bounds.maxX - bounds.minX;
|
|
232161
232832
|
height = bounds.maxY - bounds.minY;
|
|
@@ -232306,7 +232977,7 @@ var Board = class extends Group6 {
|
|
|
232306
232977
|
}
|
|
232307
232978
|
let outlineTranslation = { x: 0, y: 0 };
|
|
232308
232979
|
if (outline && outline.length > 0 && (this.parent?.lowercaseComponentName === "panel" || this.parent?.lowercaseComponentName === "subpanel")) {
|
|
232309
|
-
const outlineBounds =
|
|
232980
|
+
const outlineBounds = getBoundsFromPoints2(outline);
|
|
232310
232981
|
if (outlineBounds) {
|
|
232311
232982
|
const outlineCenterX = (outlineBounds.minX + outlineBounds.maxX) / 2;
|
|
232312
232983
|
const outlineCenterY = (outlineBounds.minY + outlineBounds.maxY) / 2;
|
|
@@ -232421,7 +233092,7 @@ var Board = class extends Group6 {
|
|
|
232421
233092
|
if (this.pcb_board_id) {
|
|
232422
233093
|
db.pcb_board.update(this.pcb_board_id, { center: position2 });
|
|
232423
233094
|
if (pcbBoard?.outline) {
|
|
232424
|
-
const outlineBounds =
|
|
233095
|
+
const outlineBounds = getBoundsFromPoints2(pcbBoard.outline);
|
|
232425
233096
|
if (outlineBounds) {
|
|
232426
233097
|
const oldOutlineCenter = {
|
|
232427
233098
|
x: (outlineBounds.minX + outlineBounds.maxX) / 2,
|
|
@@ -232724,7 +233395,7 @@ var getBoardDimensionsFromProps = (board) => {
|
|
|
232724
233395
|
let width = props.width != null ? distance.parse(props.width) : undefined;
|
|
232725
233396
|
let height = props.height != null ? distance.parse(props.height) : undefined;
|
|
232726
233397
|
if ((width === undefined || height === undefined) && props.outline?.length) {
|
|
232727
|
-
const outlineBounds =
|
|
233398
|
+
const outlineBounds = getBoundsFromPoints2(props.outline);
|
|
232728
233399
|
if (outlineBounds) {
|
|
232729
233400
|
width ??= outlineBounds.maxX - outlineBounds.minX;
|
|
232730
233401
|
height ??= outlineBounds.maxY - outlineBounds.minY;
|
|
@@ -236890,13 +237561,13 @@ var solveForGlobalCapacityNodes = async (circuitJson) => {
|
|
|
236890
237561
|
await solver.solveUntilPhase("highDensityRouteSolver");
|
|
236891
237562
|
return solver.uniformPortDistributionSolver?.getOutput() ?? [];
|
|
236892
237563
|
};
|
|
236893
|
-
var
|
|
237564
|
+
var fmtNumber3 = (value) => {
|
|
236894
237565
|
if (Number.isInteger(value))
|
|
236895
237566
|
return String(value);
|
|
236896
237567
|
return value.toFixed(3).replace(/\.0+$/, "").replace(/(\.\d*?)0+$/, "$1");
|
|
236897
237568
|
};
|
|
236898
237569
|
var fmtMm22 = (value) => `${value.toFixed(1)}mm`;
|
|
236899
|
-
var fmtPercent = (value) => `${
|
|
237570
|
+
var fmtPercent = (value) => `${fmtNumber3(value * 100)}%`;
|
|
236900
237571
|
var isCrampedPortPoint = (portPointId) => portPointId?.includes("_cramped") ?? false;
|
|
236901
237572
|
var clamp012 = (value) => Math.max(0, Math.min(1, value));
|
|
236902
237573
|
var roundProbability = (value) => Number.parseFloat(value.toFixed(3));
|
package/dist/lib/index.js
CHANGED
|
@@ -60678,7 +60678,7 @@ var getNodeHandler = (winterSpec, { port, middleware = [] }) => {
|
|
|
60678
60678
|
}));
|
|
60679
60679
|
};
|
|
60680
60680
|
// package.json
|
|
60681
|
-
var version = "0.1.
|
|
60681
|
+
var version = "0.1.1175";
|
|
60682
60682
|
var package_default = {
|
|
60683
60683
|
name: "@tscircuit/cli",
|
|
60684
60684
|
version,
|
|
@@ -60690,7 +60690,7 @@ var package_default = {
|
|
|
60690
60690
|
devDependencies: {
|
|
60691
60691
|
"@babel/standalone": "^7.26.9",
|
|
60692
60692
|
"@biomejs/biome": "^1.9.4",
|
|
60693
|
-
"@tscircuit/circuit-json-placement-analysis": "^0.0.
|
|
60693
|
+
"@tscircuit/circuit-json-placement-analysis": "^0.0.5",
|
|
60694
60694
|
"@tscircuit/circuit-json-routing-analysis": "^0.0.1",
|
|
60695
60695
|
"@tscircuit/fake-snippets": "^0.0.182",
|
|
60696
60696
|
"@tscircuit/file-server": "^0.0.32",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1176",
|
|
4
4
|
"main": "dist/cli/main.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dist/cli/main.js",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"devDependencies": {
|
|
10
10
|
"@babel/standalone": "^7.26.9",
|
|
11
11
|
"@biomejs/biome": "^1.9.4",
|
|
12
|
-
"@tscircuit/circuit-json-placement-analysis": "^0.0.
|
|
12
|
+
"@tscircuit/circuit-json-placement-analysis": "^0.0.5",
|
|
13
13
|
"@tscircuit/circuit-json-routing-analysis": "^0.0.1",
|
|
14
14
|
"@tscircuit/fake-snippets": "^0.0.182",
|
|
15
15
|
"@tscircuit/file-server": "^0.0.32",
|