@tscircuit/cli 0.1.1981 → 0.1.1982

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
@@ -136163,7 +136163,7 @@ var package_default = {
136163
136163
  "@tscircuit/check-shorts": "https://jscdn.tscircuit.com/@tscircuit/check-shorts/0.0.19.tgz",
136164
136164
  "@tscircuit/circuit-json-placement-analysis": "^0.0.9",
136165
136165
  "@tscircuit/circuit-json-routing-analysis": "^0.0.8",
136166
- "@tscircuit/circuit-json-schematic-placement-analysis": "github:tscircuit/circuit-json-schematic-placement-analysis#2bfa85c47dd0bd0d360b85083980170c01d3afca",
136166
+ "@tscircuit/circuit-json-schematic-placement-analysis": "github:tscircuit/circuit-json-schematic-placement-analysis#db2cd1ca2ce51490e3230e53c3d7b361eeb14446",
136167
136167
  "@tscircuit/circuit-json-util": "^0.0.105",
136168
136168
  "@tscircuit/eval": "^0.0.1016",
136169
136169
  "@tscircuit/fake-snippets": "^0.0.182",
@@ -301798,118 +301798,27 @@ var registerCheckRoutingDifficulty = (program2) => {
301798
301798
  });
301799
301799
  };
301800
301800
 
301801
- // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/solvers/SchematicPlacementPipeline/SchematicPlacementPipeline.ts
301802
- import {
301803
- BasePipelineSolver as BasePipelineSolver2,
301804
- definePipelineStep as definePipelineStep3
301805
- } from "@tscircuit/solver-utils";
301806
-
301807
- // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/utils/placements.ts
301808
- import { cju as cju7 } from "@tscircuit/circuit-json-util";
301809
-
301810
- // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/utils/schematic-sheets.ts
301811
- var getSchematicSheetNamesById = (circuitJson) => {
301812
- const map = new Map;
301813
- for (const element of circuitJson) {
301814
- if (element.type === "schematic_sheet" && element.name) {
301815
- map.set(element.schematic_sheet_id, element.name);
301816
- }
301817
- }
301818
- return map;
301819
- };
301801
+ // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/solvers/CapacitorOrientationSolver/CapacitorOrientationSolver.ts
301802
+ import { BaseSolver as BaseSolver5 } from "@tscircuit/solver-utils";
301820
301803
 
301821
- // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/utils/placements.ts
301822
- var isSchematicBox = (el3) => el3.type === "schematic_box";
301823
- var isSchematicComponent = (el3) => el3.type === "schematic_component";
301824
- var getSchematicSheetId = (el3) => el3.schematic_sheet_id;
301825
- var getSourceComponentName = (circuitJson, sourceComponentId) => {
301826
- if (!sourceComponentId)
301827
- return;
301828
- return cju7(circuitJson).source_component.get(sourceComponentId)?.name;
301829
- };
301830
- var getSourceComponentMetadata = (schematicBox, circuitJson) => {
301831
- if (!schematicBox.schematic_component_id)
301832
- return {};
301833
- const util2 = cju7(circuitJson);
301834
- const sc2 = util2.schematic_component.get(schematicBox.schematic_component_id);
301835
- if (!sc2?.source_component_id)
301836
- return {};
301837
- const sourceComponent = util2.source_component.get(sc2.source_component_id);
301838
- return {
301839
- sourceComponentId: sc2.source_component_id,
301840
- sourceComponentName: sourceComponent?.name
301841
- };
301842
- };
301843
- var schematicComponentToPlacement = (schematicComponent, circuitJson, schematicSheetNameById, schematicBox) => {
301844
- let schematicSheetId = getSchematicSheetId(schematicComponent);
301845
- if (schematicSheetId === undefined && schematicBox) {
301846
- schematicSheetId = getSchematicSheetId(schematicBox);
301847
- }
301848
- let schematicSheetName;
301849
- if (schematicSheetId) {
301850
- schematicSheetName = schematicSheetNameById.get(schematicSheetId);
301851
- }
301852
- const placement = {
301853
- lineItemType: "SchematicBoxPlacement",
301854
- positionAnchor: "center",
301855
- schX: schematicComponent.center.x,
301856
- schY: schematicComponent.center.y,
301857
- width: schematicBox?.width ?? schematicComponent.size.width,
301858
- height: schematicBox?.height ?? schematicComponent.size.height,
301859
- sourceComponentId: schematicComponent.source_component_id,
301860
- sourceComponentName: getSourceComponentName(circuitJson, schematicComponent.source_component_id),
301861
- schematicComponentId: schematicComponent.schematic_component_id,
301862
- schematicSymbolId: schematicBox?.schematic_symbol_id ?? schematicComponent.schematic_symbol_id,
301863
- subcircuitId: schematicComponent.subcircuit_id ?? schematicBox?.subcircuit_id
301864
- };
301865
- if (schematicSheetId) {
301866
- placement.schematicSheetId = schematicSheetId;
301867
- }
301868
- if (schematicSheetName) {
301869
- placement.schematicSheetName = schematicSheetName;
301870
- }
301871
- return placement;
301804
+ // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/utils/format.ts
301805
+ var fmtNumber5 = (value) => {
301806
+ if (Number.isInteger(value))
301807
+ return String(value);
301808
+ return value.toFixed(3).replace(/\.0+$/, "").replace(/(\.\d*?)0+$/, "$1");
301872
301809
  };
301873
- var schematicBoxToPlacement = (schematicBox, circuitJson, schematicSheetNameById) => {
301874
- const schematicSheetId = getSchematicSheetId(schematicBox);
301875
- let schematicSheetName;
301876
- if (schematicSheetId) {
301877
- schematicSheetName = schematicSheetNameById.get(schematicSheetId);
301878
- }
301879
- const placement = {
301880
- lineItemType: "SchematicBoxPlacement",
301881
- positionAnchor: "center",
301882
- schX: schematicBox.x,
301883
- schY: schematicBox.y,
301884
- width: schematicBox.width,
301885
- height: schematicBox.height,
301886
- ...getSourceComponentMetadata(schematicBox, circuitJson),
301887
- schematicComponentId: schematicBox.schematic_component_id,
301888
- schematicSymbolId: schematicBox.schematic_symbol_id,
301889
- subcircuitId: schematicBox.subcircuit_id
301890
- };
301891
- if (schematicSheetId) {
301892
- placement.schematicSheetId = schematicSheetId;
301893
- }
301894
- if (schematicSheetName) {
301895
- placement.schematicSheetName = schematicSheetName;
301896
- }
301897
- return placement;
301810
+ var fmtDelta = (value) => {
301811
+ const formatted = fmtNumber5(value);
301812
+ return value > 0 ? `+${formatted}` : formatted;
301898
301813
  };
301899
- var buildSolverContext = (circuitJson) => {
301900
- const schematicBoxes = circuitJson.filter(isSchematicBox);
301901
- const schematicSheetNameById = getSchematicSheetNamesById(circuitJson);
301902
- const schematicComponentIds = new Set(circuitJson.filter(isSchematicComponent).map((sc2) => sc2.schematic_component_id));
301903
- const componentPlacements = [
301904
- ...circuitJson.filter(isSchematicComponent).map((sc2) => schematicComponentToPlacement(sc2, circuitJson, schematicSheetNameById, schematicBoxes.find((sb3) => sb3.schematic_component_id === sc2.schematic_component_id))),
301905
- ...schematicBoxes.filter((sb3) => !sb3.schematic_component_id || !schematicComponentIds.has(sb3.schematic_component_id)).map((sb3) => schematicBoxToPlacement(sb3, circuitJson, schematicSheetNameById))
301906
- ];
301907
- return { circuitJson, componentPlacements };
301814
+ var escapeAttr3 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
301815
+ var addAttr = (attrs, key, value, options) => {
301816
+ if (value === undefined)
301817
+ return;
301818
+ const stringValue = typeof value === "number" ? options?.formatDelta ? fmtDelta(value) : fmtNumber5(value) : options?.escape === false ? value : escapeAttr3(value);
301819
+ attrs.push(`${key}="${stringValue}"`);
301908
301820
  };
301909
301821
 
301910
- // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/solvers/CapacitorOrientationSolver/CapacitorOrientationSolver.ts
301911
- import { BaseSolver as BaseSolver5 } from "@tscircuit/solver-utils";
301912
-
301913
301822
  // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/utils/graphics.ts
301914
301823
  import { mergeGraphics as mergeGraphics2 } from "graphics-debug";
301915
301824
  function mergeGraphicsObjects2(objects) {
@@ -301942,24 +301851,6 @@ function visualizeCircuitJson(circuitJson) {
301942
301851
  return { rects };
301943
301852
  }
301944
301853
 
301945
- // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/utils/format.ts
301946
- var fmtNumber5 = (value) => {
301947
- if (Number.isInteger(value))
301948
- return String(value);
301949
- return value.toFixed(3).replace(/\.0+$/, "").replace(/(\.\d*?)0+$/, "$1");
301950
- };
301951
- var fmtDelta = (value) => {
301952
- const formatted = fmtNumber5(value);
301953
- return value > 0 ? `+${formatted}` : formatted;
301954
- };
301955
- var escapeAttr3 = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
301956
- var addAttr = (attrs, key, value, options) => {
301957
- if (value === undefined)
301958
- return;
301959
- const stringValue = typeof value === "number" ? options?.formatDelta ? fmtDelta(value) : fmtNumber5(value) : options?.escape === false ? value : escapeAttr3(value);
301960
- attrs.push(`${key}="${stringValue}"`);
301961
- };
301962
-
301963
301854
  // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/solvers/CapacitorOrientationSolver/CapacitorOrientationSolver.ts
301964
301855
  class CapacitorOrientationSolver extends BaseSolver5 {
301965
301856
  static ORIENTATION_MESSAGE = 'Use schOrientation="vertical" on this capacitor to fix the symbol orientation';
@@ -301993,7 +301884,9 @@ class CapacitorOrientationSolver extends BaseSolver5 {
301993
301884
  height: p3.height,
301994
301885
  sourceComponentId: p3.sourceComponentId,
301995
301886
  sourceComponentName: p3.sourceComponentName,
301996
- schematicComponentId: p3.schematicComponentId
301887
+ schematicComponentId: p3.schematicComponentId,
301888
+ schematicSheetId: p3.schematicSheetId,
301889
+ schematicSheetName: p3.schematicSheetName
301997
301890
  }));
301998
301891
  }
301999
301892
  _step() {
@@ -302046,7 +301939,9 @@ class CapacitorOrientationSolver extends BaseSolver5 {
302046
301939
  height: placement.height,
302047
301940
  sourceComponentId: placement.sourceComponentId,
302048
301941
  sourceComponentName: placement.sourceComponentName,
302049
- schematicComponentId: placement.schematicComponentId
301942
+ schematicComponentId: placement.schematicComponentId,
301943
+ schematicSheetId: placement.schematicSheetId,
301944
+ schematicSheetName: placement.schematicSheetName
302050
301945
  };
302051
301946
  }
302052
301947
  getIssueForPlacement(placement) {
@@ -302075,129 +301970,672 @@ class CapacitorOrientationSolver extends BaseSolver5 {
302075
301970
  }
302076
301971
  }
302077
301972
 
302078
- // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/solvers/SchematicBoxOverlapSolver/SchematicBoxOverlapSolver.ts
301973
+ // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/solvers/ComponentNetLabelCollisionSolver/ComponentNetLabelCollisionSolver.ts
302079
301974
  import { BaseSolver as BaseSolver7 } from "@tscircuit/solver-utils";
302080
- class SchematicBoxOverlapSolver extends BaseSolver7 {
301975
+
301976
+ // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/utils/geometry.ts
301977
+ function centeredRect(cx3, cy3, w4, h4) {
301978
+ return {
301979
+ left: cx3 - w4 / 2,
301980
+ right: cx3 + w4 / 2,
301981
+ top: cy3 + h4 / 2,
301982
+ bottom: cy3 - h4 / 2
301983
+ };
301984
+ }
301985
+ function rectOverlap(a2, b3) {
301986
+ const ow2 = Math.min(a2.right, b3.right) - Math.max(a2.left, b3.left);
301987
+ const oh2 = Math.min(a2.top, b3.top) - Math.max(a2.bottom, b3.bottom);
301988
+ return ow2 > 0 && oh2 > 0 ? { ow: ow2, oh: oh2 } : null;
301989
+ }
301990
+
301991
+ // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/solvers/ComponentNetLabelCollisionSolver/ComponentNetLabelCollisionSolver.ts
301992
+ class ComponentNetLabelCollisionSolver extends BaseSolver7 {
302081
301993
  params;
301994
+ LABEL_HALF_HEIGHT = 0.1;
301995
+ LABEL_BOUNDS_SLACK = 0.1;
301996
+ SCH_CHAR_WIDTH = 0.13;
302082
301997
  placements;
301998
+ netLabelsByComponentId;
301999
+ rawCollisions = [];
302083
302000
  firstIndex = 0;
302084
302001
  secondIndex = 1;
302085
302002
  constructor(params2) {
302086
302003
  super();
302087
302004
  this.params = params2;
302088
302005
  this.placements = params2.ctx.componentPlacements;
302006
+ this.netLabelsByComponentId = this.buildNetLabelsByComponentId(params2.ctx.circuitJson);
302089
302007
  this.solved = this.placements.length < 2;
302090
302008
  }
302091
302009
  _step() {
302092
302010
  if (this.firstIndex >= this.placements.length - 1) {
302011
+ this.buildAndPushIssues();
302093
302012
  this.solved = true;
302094
302013
  return;
302095
302014
  }
302096
- const overlap = this.getComponentOverlap(this.placements[this.firstIndex], this.placements[this.secondIndex]);
302097
- if (overlap)
302098
- this.params.issues.push(overlap);
302015
+ const compA = this.placements[this.firstIndex];
302016
+ const compB = this.placements[this.secondIndex];
302017
+ this.detectPair(compA, compB);
302099
302018
  this.secondIndex++;
302100
302019
  if (this.secondIndex >= this.placements.length) {
302101
302020
  this.firstIndex++;
302102
302021
  this.secondIndex = this.firstIndex + 1;
302103
302022
  }
302104
- this.solved = this.firstIndex >= this.placements.length - 1;
302105
302023
  }
302106
- getCenteredRectBounds(box2) {
302107
- return {
302108
- left: box2.schX - box2.width / 2,
302109
- right: box2.schX + box2.width / 2,
302110
- top: box2.schY - box2.height / 2,
302111
- bottom: box2.schY + box2.height / 2
302024
+ detectPair(compA, compB) {
302025
+ if (compA.schematicSheetId !== compB.schematicSheetId)
302026
+ return;
302027
+ this.rawCollisions.push(...this.detectLabelLabel(compA, compB));
302028
+ this.rawCollisions.push(...this.detectBoxLabel(compA, compB));
302029
+ this.rawCollisions.push(...this.detectBoxLabel(compB, compA));
302030
+ }
302031
+ detectLabelLabel(firstComponent, secondComponent) {
302032
+ let leftComp = firstComponent;
302033
+ let rightComp = secondComponent;
302034
+ if (firstComponent.schX > secondComponent.schX) {
302035
+ leftComp = secondComponent;
302036
+ rightComp = firstComponent;
302037
+ }
302038
+ const leftId = leftComp.schematicComponentId;
302039
+ const rightId = rightComp.schematicComponentId;
302040
+ if (!leftId || !rightId)
302041
+ return [];
302042
+ const leftLabels = this.netLabelsByComponentId.get(leftId) ?? [];
302043
+ const rightLabels = this.netLabelsByComponentId.get(rightId) ?? [];
302044
+ if (leftLabels.length === 0 || rightLabels.length === 0)
302045
+ return [];
302046
+ const hits = [];
302047
+ for (const leftLabel of leftLabels) {
302048
+ for (const rightLabel of rightLabels) {
302049
+ const leftBounds = this.getNetLabelBounds(leftLabel);
302050
+ const rightBounds = this.getNetLabelBounds(rightLabel);
302051
+ if (rectOverlap(leftBounds, rightBounds)) {
302052
+ hits.push({
302053
+ type: "label-label",
302054
+ leftComp,
302055
+ rightComp,
302056
+ leftId,
302057
+ rightId,
302058
+ xSeparation: leftBounds.right - rightBounds.left + 0.1
302059
+ });
302060
+ }
302061
+ }
302062
+ }
302063
+ return hits;
302064
+ }
302065
+ detectBoxLabel(boxComp, labelComp) {
302066
+ const boxId = boxComp.schematicComponentId;
302067
+ const labelId = labelComp.schematicComponentId;
302068
+ if (!boxId || !labelId)
302069
+ return [];
302070
+ const labels = this.netLabelsByComponentId.get(labelId) ?? [];
302071
+ if (labels.length === 0)
302072
+ return [];
302073
+ const boxBounds = centeredRect(boxComp.schX, boxComp.schY, boxComp.width, boxComp.height);
302074
+ const boxIsLeft = boxComp.schX <= labelComp.schX;
302075
+ const hits = [];
302076
+ for (const label of labels) {
302077
+ const labelBounds = this.getNetLabelBounds(label);
302078
+ if (!rectOverlap(boxBounds, labelBounds))
302079
+ continue;
302080
+ let xSeparation;
302081
+ if (boxIsLeft) {
302082
+ xSeparation = boxBounds.right - labelBounds.left + 0.1;
302083
+ } else {
302084
+ xSeparation = labelBounds.right - boxBounds.left + 0.1;
302085
+ }
302086
+ hits.push({
302087
+ type: "box-label",
302088
+ boxComp,
302089
+ labelComp,
302090
+ boxId,
302091
+ labelId,
302092
+ xSeparation
302093
+ });
302094
+ }
302095
+ return hits;
302096
+ }
302097
+ buildAndPushIssues() {
302098
+ if (this.rawCollisions.length === 0)
302099
+ return;
302100
+ const collisionsBySheet = new Map;
302101
+ for (const collision of this.rawCollisions) {
302102
+ const placement = collision.type === "label-label" ? collision.leftComp : collision.boxComp;
302103
+ const sheetKey = placement.schematicSheetId ?? "";
302104
+ const sheetCollisions = collisionsBySheet.get(sheetKey);
302105
+ if (sheetCollisions)
302106
+ sheetCollisions.push(collision);
302107
+ else
302108
+ collisionsBySheet.set(sheetKey, [collision]);
302109
+ }
302110
+ for (const collisions of collisionsBySheet.values()) {
302111
+ this.buildAndPushIssueForSheet(collisions);
302112
+ }
302113
+ }
302114
+ buildAndPushIssueForSheet(collisions) {
302115
+ const globalFixes = this.computeGlobalFixes(collisions);
302116
+ if (globalFixes.size === 0)
302117
+ return;
302118
+ const seenPairs = new Set;
302119
+ const pairs3 = [];
302120
+ for (const collision of collisions) {
302121
+ let comp1Name;
302122
+ let comp2Name;
302123
+ if (collision.type === "label-label") {
302124
+ comp1Name = collision.leftComp.sourceComponentName ?? "";
302125
+ comp2Name = collision.rightComp.sourceComponentName ?? "";
302126
+ } else {
302127
+ comp1Name = collision.boxComp.sourceComponentName ?? "";
302128
+ comp2Name = collision.labelComp.sourceComponentName ?? "";
302129
+ }
302130
+ const key = `${comp1Name}/${comp2Name}`;
302131
+ if (!seenPairs.has(key)) {
302132
+ seenPairs.add(key);
302133
+ pairs3.push({ comp1Name, comp2Name });
302134
+ }
302135
+ }
302136
+ const firstCollision = collisions[0];
302137
+ const firstPlacement = firstCollision.type === "label-label" ? firstCollision.leftComp : firstCollision.boxComp;
302138
+ this.params.issues.push({
302139
+ lineItemType: "NetLabelCollision",
302140
+ schematicSheetId: firstPlacement.schematicSheetId,
302141
+ schematicSheetName: firstPlacement.schematicSheetName,
302142
+ pairs: pairs3,
302143
+ moves: Array.from(globalFixes.values())
302144
+ });
302145
+ }
302146
+ computeGlobalFixes(collisions) {
302147
+ const compById = new Map;
302148
+ for (const placement of this.placements) {
302149
+ if (placement.schematicComponentId)
302150
+ compById.set(placement.schematicComponentId, placement);
302151
+ }
302152
+ const constraintMap = new Map;
302153
+ const addConstraint = (leftId, rightId, xSeparation) => {
302154
+ const leftComp = compById.get(leftId);
302155
+ const rightComp = compById.get(rightId);
302156
+ if (!leftComp || !rightComp)
302157
+ return;
302158
+ const minSep = rightComp.schX - leftComp.schX + xSeparation;
302159
+ const key = `${leftId}|${rightId}`;
302160
+ constraintMap.set(key, Math.max(constraintMap.get(key) ?? 0, minSep));
302112
302161
  };
302162
+ for (const collision of collisions) {
302163
+ if (collision.type === "label-label") {
302164
+ addConstraint(collision.leftId, collision.rightId, collision.xSeparation);
302165
+ } else if (collision.boxComp.schX <= collision.labelComp.schX) {
302166
+ addConstraint(collision.boxId, collision.labelId, collision.xSeparation);
302167
+ } else {
302168
+ addConstraint(collision.labelId, collision.boxId, collision.xSeparation);
302169
+ }
302170
+ }
302171
+ const constraints = [...constraintMap.entries()].map(([key, minSep]) => {
302172
+ const pipeIndex = key.indexOf("|");
302173
+ return {
302174
+ leftId: key.slice(0, pipeIndex),
302175
+ rightId: key.slice(pipeIndex + 1),
302176
+ minSep
302177
+ };
302178
+ });
302179
+ const allIds = new Set;
302180
+ const adjacency = new Map;
302181
+ for (const { leftId, rightId } of constraints) {
302182
+ allIds.add(leftId);
302183
+ allIds.add(rightId);
302184
+ if (!adjacency.has(leftId))
302185
+ adjacency.set(leftId, new Set);
302186
+ if (!adjacency.has(rightId))
302187
+ adjacency.set(rightId, new Set);
302188
+ adjacency.get(leftId).add(rightId);
302189
+ adjacency.get(rightId).add(leftId);
302190
+ }
302191
+ const visited = new Set;
302192
+ const result = new Map;
302193
+ for (const startId of allIds) {
302194
+ if (visited.has(startId))
302195
+ continue;
302196
+ const group = [];
302197
+ const queue = [startId];
302198
+ visited.add(startId);
302199
+ while (queue.length) {
302200
+ const compId = queue.shift();
302201
+ group.push(compId);
302202
+ for (const neighbor of adjacency.get(compId) ?? []) {
302203
+ if (!visited.has(neighbor)) {
302204
+ visited.add(neighbor);
302205
+ queue.push(neighbor);
302206
+ }
302207
+ }
302208
+ }
302209
+ group.sort((idA, idB) => (compById.get(idA)?.schX ?? 0) - (compById.get(idB)?.schX ?? 0));
302210
+ const groupIds = new Set(group);
302211
+ const groupConstraints = constraints.filter((c3) => groupIds.has(c3.leftId) && groupIds.has(c3.rightId));
302212
+ const assigned = new Map;
302213
+ for (const compId of group) {
302214
+ let newX = compById.get(compId)?.schX ?? 0;
302215
+ for (const { leftId, rightId, minSep } of groupConstraints) {
302216
+ if (rightId === compId && assigned.has(leftId)) {
302217
+ newX = Math.max(newX, assigned.get(leftId) + minSep);
302218
+ }
302219
+ }
302220
+ assigned.set(compId, newX);
302221
+ }
302222
+ const totalPush = [...assigned.entries()].reduce((sum, [compId, newX]) => sum + (newX - (compById.get(compId)?.schX ?? 0)), 0);
302223
+ if (totalPush > 0.000000001) {
302224
+ const pullBack = totalPush / group.length;
302225
+ const shifted = new Map;
302226
+ for (const compId of group) {
302227
+ let newX = (compById.get(compId)?.schX ?? 0) - pullBack;
302228
+ for (const { leftId, rightId, minSep } of groupConstraints) {
302229
+ if (rightId === compId && shifted.has(leftId)) {
302230
+ newX = Math.max(newX, shifted.get(leftId) + minSep);
302231
+ }
302232
+ }
302233
+ shifted.set(compId, newX);
302234
+ }
302235
+ const maxDisplacement = (positions) => [...positions.entries()].reduce((currentMax, [compId, newX]) => Math.max(currentMax, Math.abs(newX - (compById.get(compId)?.schX ?? 0))), 0);
302236
+ if (maxDisplacement(shifted) < maxDisplacement(assigned)) {
302237
+ for (const [compId, newX] of shifted)
302238
+ assigned.set(compId, newX);
302239
+ }
302240
+ }
302241
+ for (const [compId, newX] of assigned) {
302242
+ const comp = compById.get(compId);
302243
+ if (!comp || Math.abs(newX - comp.schX) < 0.000000001)
302244
+ continue;
302245
+ result.set(compId, {
302246
+ componentName: comp.sourceComponentName ?? compId,
302247
+ newSchX: Math.round(newX * 100) / 100,
302248
+ newSchY: Math.round(comp.schY * 100) / 100
302249
+ });
302250
+ }
302251
+ }
302252
+ return result;
302113
302253
  }
302114
- getOverlapCorrectionSuggestions({
302115
- firstComponent,
302116
- secondComponent,
302117
- overlapWidth,
302118
- overlapHeight
302119
- }) {
302120
- const firstArea = firstComponent.width * firstComponent.height;
302121
- const secondArea = secondComponent.width * secondComponent.height;
302122
- const target = firstArea <= secondArea ? firstComponent : secondComponent;
302123
- const other = target === firstComponent ? secondComponent : firstComponent;
302124
- const deltaSchX = target.schX <= other.schX ? -overlapWidth : overlapWidth;
302125
- const deltaSchY = target.schY <= other.schY ? -overlapHeight : overlapHeight;
302126
- return [
302127
- {
302128
- targetComponentName: target.sourceComponentName,
302129
- deltaSchX,
302130
- deltaSchY: 0,
302131
- newSchX: target.schX + deltaSchX,
302132
- newSchY: target.schY
302133
- },
302134
- {
302135
- targetComponentName: target.sourceComponentName,
302136
- deltaSchX: 0,
302137
- deltaSchY,
302138
- newSchX: target.schX,
302139
- newSchY: target.schY + deltaSchY
302254
+ buildNetLabelsByComponentId(circuitJson) {
302255
+ const MATCH_EPSILON = 0.0001;
302256
+ const portPositions = [];
302257
+ for (const element of circuitJson) {
302258
+ if (element.type !== "schematic_port")
302259
+ continue;
302260
+ const port = element;
302261
+ if (!port.schematic_component_id)
302262
+ continue;
302263
+ portPositions.push({
302264
+ componentId: port.schematic_component_id,
302265
+ cx: port.center.x,
302266
+ cy: port.center.y,
302267
+ schematicSheetId: port.schematic_sheet_id
302268
+ });
302269
+ }
302270
+ const result = new Map;
302271
+ for (const element of circuitJson) {
302272
+ if (element.type !== "schematic_net_label")
302273
+ continue;
302274
+ const label = element;
302275
+ if (!label.anchor_position)
302276
+ continue;
302277
+ const { x: anchorX, y: anchorY } = label.anchor_position;
302278
+ const matches = portPositions.filter((port) => port.schematicSheetId === label.schematic_sheet_id && Math.hypot(port.cx - anchorX, port.cy - anchorY) < MATCH_EPSILON);
302279
+ if (matches.length === 0)
302280
+ continue;
302281
+ const componentIds = new Set(matches.map((match) => match.componentId));
302282
+ if (componentIds.size !== 1)
302283
+ continue;
302284
+ const componentId = matches[0].componentId;
302285
+ const labels = result.get(componentId) ?? [];
302286
+ labels.push(label);
302287
+ result.set(componentId, labels);
302288
+ }
302289
+ return result;
302290
+ }
302291
+ getNetLabelBounds(label) {
302292
+ const anchorSide = label.anchor_side;
302293
+ const isVertical3 = anchorSide === "top" || anchorSide === "bottom";
302294
+ if (isVertical3) {
302295
+ const anchorY = label.anchor_position?.y ?? label.center.y;
302296
+ const textHalfExtent = (label.text?.length ?? 8) * this.SCH_CHAR_WIDTH / 2 + this.LABEL_BOUNDS_SLACK;
302297
+ const left = label.center.x - this.LABEL_HALF_HEIGHT;
302298
+ const right = label.center.x + this.LABEL_HALF_HEIGHT;
302299
+ if (anchorSide === "top") {
302300
+ return {
302301
+ left,
302302
+ right,
302303
+ top: anchorY,
302304
+ bottom: anchorY - textHalfExtent * 2
302305
+ };
302140
302306
  }
302141
- ];
302307
+ return { left, right, top: anchorY + textHalfExtent * 2, bottom: anchorY };
302308
+ }
302309
+ const anchorX = label.anchor_position?.x ?? label.center.x;
302310
+ const halfWidth = Math.abs(label.center.x - anchorX);
302311
+ const farHalfWidth = halfWidth + this.LABEL_BOUNDS_SLACK;
302312
+ const top = label.center.y + this.LABEL_HALF_HEIGHT;
302313
+ const bottom = label.center.y - this.LABEL_HALF_HEIGHT;
302314
+ if (label.center.x >= anchorX) {
302315
+ return {
302316
+ left: anchorX,
302317
+ right: label.center.x + farHalfWidth,
302318
+ top,
302319
+ bottom
302320
+ };
302321
+ }
302322
+ return { left: label.center.x - farHalfWidth, right: anchorX, top, bottom };
302142
302323
  }
302143
- static issueToString(issue) {
302144
- const attrs = [];
302145
- addAttr(attrs, "component1Name", issue.firstComponent.sourceComponentName);
302146
- addAttr(attrs, "component2Name", issue.secondComponent.sourceComponentName);
302147
- addAttr(attrs, "component1SchX", issue.firstComponent.schX);
302148
- addAttr(attrs, "component1SchY", issue.firstComponent.schY);
302149
- addAttr(attrs, "component2SchX", issue.secondComponent.schX);
302150
- addAttr(attrs, "component2SchY", issue.secondComponent.schY);
302151
- addAttr(attrs, "overlapWidth", issue.overlapWidth);
302152
- addAttr(attrs, "overlapHeight", issue.overlapHeight);
302324
+ static netLabelCollisionToString(issue) {
302325
+ const pairAttrs = issue.pairs.map((pair, i2) => `pair${i2 + 1}="${pair.comp1Name}/${pair.comp2Name}"`).join(" ");
302326
+ const moves = issue.moves.map((move) => ` <Move componentName="${move.componentName}" newSchX="${move.newSchX}" newSchY="${move.newSchY}" />`);
302153
302327
  return [
302154
- `<ComponentOverlap ${attrs.join(" ")}>`,
302155
- ...issue.correctionSuggestions.map(SchematicBoxOverlapSolver.correctionSuggestionToString),
302156
- "</ComponentOverlap>"
302328
+ `<ComponentNetLabelCollision ${pairAttrs}>`,
302329
+ ` <SuggestedFix note="Apply all moves simultaneously. Set schAutoLayoutEnabled on your circuit.">`,
302330
+ ...moves,
302331
+ ` </SuggestedFix>`,
302332
+ `</ComponentNetLabelCollision>`
302157
302333
  ].join(`
302158
302334
  `);
302159
302335
  }
302160
- static correctionSuggestionToString(suggestion) {
302161
- const attrs = [];
302162
- addAttr(attrs, "target", suggestion.targetComponentName);
302163
- if (suggestion.deltaSchX !== 0) {
302164
- addAttr(attrs, "newSchX", suggestion.newSchX);
302165
- addAttr(attrs, "deltaSchX", suggestion.deltaSchX, { formatDelta: true });
302336
+ }
302337
+
302338
+ // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/solvers/ComponentPinAlignmentSolver/ComponentPinAlignmentSolver.ts
302339
+ import { BaseSolver as BaseSolver8 } from "@tscircuit/solver-utils";
302340
+ class ComponentPinAlignmentSolver extends BaseSolver8 {
302341
+ static ALIGNMENT_EPSILON = 0.01;
302342
+ ctx;
302343
+ out;
302344
+ constructor({
302345
+ ctx,
302346
+ issues
302347
+ }) {
302348
+ super();
302349
+ this.ctx = ctx;
302350
+ this.out = issues;
302351
+ }
302352
+ _step() {
302353
+ const portsById = new Map(this.ctx.circuitJson.filter((el3) => el3.type === "schematic_port").map((port) => [port.schematic_port_id, port]));
302354
+ const placementBySchematicComponentId = new Map(this.ctx.componentPlacements.flatMap((placement) => placement.schematicComponentId ? [[placement.schematicComponentId, placement]] : []));
302355
+ const pinPairsByComponentPair = new Map;
302356
+ for (const trace of this.ctx.circuitJson.filter((el3) => el3.type === "schematic_trace")) {
302357
+ const pinPair = this.getTracePinPair(trace, portsById);
302358
+ if (!pinPair)
302359
+ continue;
302360
+ const firstComponentId = pinPair.firstPort.schematic_component_id;
302361
+ const secondComponentId = pinPair.secondPort.schematic_component_id;
302362
+ if (!firstComponentId || !secondComponentId || firstComponentId === secondComponentId) {
302363
+ continue;
302364
+ }
302365
+ const orderedPair = firstComponentId < secondComponentId ? pinPair : {
302366
+ firstPort: pinPair.secondPort,
302367
+ secondPort: pinPair.firstPort
302368
+ };
302369
+ const key = [firstComponentId, secondComponentId].sort().join("\x00");
302370
+ const pairs3 = pinPairsByComponentPair.get(key) ?? [];
302371
+ const isDuplicate = pairs3.some((pair) => pair.firstPort.schematic_port_id === orderedPair.firstPort.schematic_port_id && pair.secondPort.schematic_port_id === orderedPair.secondPort.schematic_port_id);
302372
+ if (!isDuplicate)
302373
+ pairs3.push(orderedPair);
302374
+ pinPairsByComponentPair.set(key, pairs3);
302166
302375
  }
302167
- if (suggestion.deltaSchY !== 0) {
302168
- addAttr(attrs, "newSchY", suggestion.newSchY);
302169
- addAttr(attrs, "deltaSchY", suggestion.deltaSchY, { formatDelta: true });
302376
+ for (const pinPairs of pinPairsByComponentPair.values()) {
302377
+ const issue = this.findVerticalShiftIssue(pinPairs, placementBySchematicComponentId);
302378
+ if (issue)
302379
+ this.out.push(issue);
302170
302380
  }
302171
- return `<OverlapCorrectionSuggestion ${attrs.join(" ")} />`;
302381
+ this.solved = true;
302172
302382
  }
302173
- getComponentOverlap(firstComponent, secondComponent) {
302174
- if (firstComponent.schematicSheetId !== secondComponent.schematicSheetId)
302175
- return null;
302176
- const a2 = this.getCenteredRectBounds(firstComponent);
302177
- const b3 = this.getCenteredRectBounds(secondComponent);
302178
- const overlapWidth = Math.min(a2.right, b3.right) - Math.max(a2.left, b3.left);
302179
- const overlapHeight = Math.min(a2.bottom, b3.bottom) - Math.max(a2.top, b3.top);
302180
- if (overlapWidth <= 0 || overlapHeight <= 0)
302181
- return null;
302383
+ getTracePinPair(trace, portsById) {
302384
+ const firstEdge = trace.edges?.[0];
302385
+ const lastEdge = trace.edges?.at(-1);
302386
+ if (!firstEdge || !lastEdge)
302387
+ return;
302388
+ const firstPortId = firstEdge.from_schematic_port_id ?? firstEdge.to_schematic_port_id;
302389
+ const secondPortId = lastEdge.to_schematic_port_id ?? lastEdge.from_schematic_port_id;
302390
+ const ports = [...portsById.values()];
302391
+ const firstPort = firstPortId ? portsById.get(firstPortId) : this.findPortAtPoint(ports, firstEdge.from, trace.schematic_sheet_id);
302392
+ const secondPort = secondPortId ? portsById.get(secondPortId) : this.findPortAtPoint(ports, lastEdge.to, trace.schematic_sheet_id);
302393
+ if (!firstPort || !secondPort)
302394
+ return;
302395
+ if (firstPort.schematic_port_id === secondPort.schematic_port_id)
302396
+ return;
302397
+ if (firstPort.schematic_sheet_id !== secondPort.schematic_sheet_id)
302398
+ return;
302399
+ return { firstPort, secondPort };
302400
+ }
302401
+ findPortAtPoint(ports, point9, schematicSheetId) {
302402
+ const { ALIGNMENT_EPSILON } = ComponentPinAlignmentSolver;
302403
+ return ports.find((port) => port.schematic_sheet_id === schematicSheetId && Math.abs(port.center.x - point9.x) <= ALIGNMENT_EPSILON && Math.abs(port.center.y - point9.y) <= ALIGNMENT_EPSILON);
302404
+ }
302405
+ findVerticalShiftIssue(pinPairs, placementBySchematicComponentId) {
302406
+ const firstPair = pinPairs[0];
302407
+ if (!firstPair)
302408
+ return;
302409
+ const firstPlacement = placementBySchematicComponentId.get(firstPair.firstPort.schematic_component_id);
302410
+ const secondPlacement = placementBySchematicComponentId.get(firstPair.secondPort.schematic_component_id);
302411
+ if (!firstPlacement || !secondPlacement)
302412
+ return;
302413
+ const [leftPlacement, rightPlacement] = firstPlacement.schX <= secondPlacement.schX ? [firstPlacement, secondPlacement] : [secondPlacement, firstPlacement];
302414
+ const horizontalPairs = pinPairs.flatMap((pair) => {
302415
+ const [leftPort, rightPort] = pair.firstPort.center.x <= pair.secondPort.center.x ? [pair.firstPort, pair.secondPort] : [pair.secondPort, pair.firstPort];
302416
+ return leftPort.facing_direction === "right" && rightPort.facing_direction === "left" ? [{ leftPort, rightPort }] : [];
302417
+ });
302418
+ if (horizontalPairs.length < 2)
302419
+ return;
302420
+ const { ALIGNMENT_EPSILON } = ComponentPinAlignmentSolver;
302421
+ const currentlyAlignedPinCount = horizontalPairs.filter(({ leftPort, rightPort }) => Math.abs(leftPort.center.y - rightPort.center.y) <= ALIGNMENT_EPSILON).length;
302422
+ const candidateGroups = [];
302423
+ for (const pair of horizontalPairs) {
302424
+ const deltaSchY2 = pair.leftPort.center.y - pair.rightPort.center.y;
302425
+ if (Math.abs(deltaSchY2) <= ALIGNMENT_EPSILON)
302426
+ continue;
302427
+ const group = candidateGroups.find((candidate) => Math.abs(candidate.deltaSchY - deltaSchY2) <= ALIGNMENT_EPSILON);
302428
+ if (group)
302429
+ group.pairs.push(pair);
302430
+ else
302431
+ candidateGroups.push({ deltaSchY: deltaSchY2, pairs: [pair] });
302432
+ }
302433
+ const bestCandidate = candidateGroups.sort((a2, b3) => b3.pairs.length - a2.pairs.length || Math.abs(a2.deltaSchY) - Math.abs(b3.deltaSchY))[0];
302434
+ if (!bestCandidate || bestCandidate.pairs.length < 2 || bestCandidate.pairs.length <= currentlyAlignedPinCount) {
302435
+ return;
302436
+ }
302437
+ const targetName = rightPlacement.sourceComponentName ?? rightPlacement.schematicComponentId ?? "component";
302438
+ const deltaSchY = bestCandidate.deltaSchY;
302439
+ const newSchY = rightPlacement.schY + deltaSchY;
302182
302440
  return {
302183
- lineItemType: "ComponentOverlap",
302184
- firstComponent,
302185
- secondComponent,
302186
- overlapWidth,
302187
- overlapHeight,
302188
- correctionSuggestions: this.getOverlapCorrectionSuggestions({
302189
- firstComponent,
302190
- secondComponent,
302191
- overlapWidth,
302192
- overlapHeight
302193
- })
302441
+ lineItemType: "ComponentPinsWouldAlignWithVerticalShift",
302442
+ firstComponent: leftPlacement,
302443
+ secondComponent: rightPlacement,
302444
+ targetComponent: rightPlacement,
302445
+ deltaSchY,
302446
+ newSchY,
302447
+ currentlyAlignedPinCount,
302448
+ alignedPinCount: bestCandidate.pairs.length,
302449
+ alignedPinPairs: bestCandidate.pairs.map(({ leftPort, rightPort }) => ({
302450
+ firstPin: this.getPinLabel(leftPort),
302451
+ secondPin: this.getPinLabel(rightPort)
302452
+ })),
302453
+ message: `shift ${targetName} vertically by ${fmtDelta(deltaSchY)} to align ${bestCandidate.pairs.length} connected pin pairs`
302194
302454
  };
302195
302455
  }
302456
+ getPinLabel(port) {
302457
+ return port.display_pin_label ?? port.pin_number?.toString();
302458
+ }
302459
+ static issueToString(issue) {
302460
+ const attrs = [];
302461
+ addAttr(attrs, "firstComponentName", issue.firstComponent.sourceComponentName);
302462
+ addAttr(attrs, "secondComponentName", issue.secondComponent.sourceComponentName);
302463
+ addAttr(attrs, "targetComponentName", issue.targetComponent.sourceComponentName);
302464
+ addAttr(attrs, "deltaSchY", issue.deltaSchY);
302465
+ addAttr(attrs, "newSchY", issue.newSchY);
302466
+ addAttr(attrs, "currentlyAlignedPinCount", issue.currentlyAlignedPinCount);
302467
+ addAttr(attrs, "alignedPinCount", issue.alignedPinCount);
302468
+ addAttr(attrs, "message", issue.message);
302469
+ return `<ComponentPinsWouldAlignWithVerticalShift ${attrs.join(" ")} />`;
302470
+ }
302471
+ }
302472
+
302473
+ // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/solvers/DiodeResistorAlignmentSolver/DiodeResistorAlignmentSolver.ts
302474
+ import { BaseSolver as BaseSolver9 } from "@tscircuit/solver-utils";
302475
+ class DiodeResistorAlignmentSolver extends BaseSolver9 {
302476
+ static DIODE_FTYPES = new Set(["simple_led", "simple_diode"]);
302477
+ ctx;
302478
+ out;
302479
+ schematicTraces;
302480
+ currentIndex = 0;
302481
+ sourceComponentFtypeById;
302482
+ sourceComponentIdBySourcePortId;
302483
+ schematicPorts;
302484
+ schematicBoxBySourceComponentId;
302485
+ constructor({
302486
+ ctx,
302487
+ issues
302488
+ }) {
302489
+ super();
302490
+ this.ctx = ctx;
302491
+ this.out = issues;
302492
+ const { circuitJson } = ctx;
302493
+ this.sourceComponentFtypeById = this.buildSourceComponentFtypeById(circuitJson);
302494
+ this.sourceComponentIdBySourcePortId = this.buildSourceComponentIdBySourcePortId(circuitJson);
302495
+ this.schematicPorts = circuitJson.filter((el3) => el3.type === "schematic_port");
302496
+ this.schematicBoxBySourceComponentId = this.buildSchematicBoxBySourceComponentId();
302497
+ this.schematicTraces = circuitJson.filter((el3) => el3.type === "schematic_trace");
302498
+ this.solved = this.schematicTraces.length === 0;
302499
+ }
302500
+ _step() {
302501
+ const trace = this.schematicTraces[this.currentIndex];
302502
+ if (!trace) {
302503
+ this.solved = true;
302504
+ return;
302505
+ }
302506
+ this.currentIndex++;
302507
+ this.solved = this.currentIndex >= this.schematicTraces.length;
302508
+ if (!trace.edges || trace.edges.length === 0)
302509
+ return;
302510
+ const firstEdge = trace.edges[0];
302511
+ const lastEdge = trace.edges[trace.edges.length - 1];
302512
+ if (!firstEdge || !lastEdge)
302513
+ return;
302514
+ const start = firstEdge.from;
302515
+ const end = lastEdge.to;
302516
+ const schematicSheetId = trace.schematic_sheet_id;
302517
+ const startSourceCompId = this.findSourceComponentIdNearPoint(start, schematicSheetId);
302518
+ const endSourceCompId = this.findSourceComponentIdNearPoint(end, schematicSheetId);
302519
+ if (!startSourceCompId || !endSourceCompId)
302520
+ return;
302521
+ const startFtype = this.sourceComponentFtypeById.get(startSourceCompId);
302522
+ const endFtype = this.sourceComponentFtypeById.get(endSourceCompId);
302523
+ const { DIODE_FTYPES } = DiodeResistorAlignmentSolver;
302524
+ const isDiodeResistorPair = DIODE_FTYPES.has(startFtype) && endFtype === "simple_resistor" || startFtype === "simple_resistor" && DIODE_FTYPES.has(endFtype);
302525
+ if (!isDiodeResistorPair)
302526
+ return;
302527
+ const diodeCompId = DIODE_FTYPES.has(startFtype) ? startSourceCompId : endSourceCompId;
302528
+ const resistorCompId = startFtype === "simple_resistor" ? startSourceCompId : endSourceCompId;
302529
+ const diodeBox = this.schematicBoxBySourceComponentId.get(diodeCompId);
302530
+ const resistorBox = this.schematicBoxBySourceComponentId.get(resistorCompId);
302531
+ if (!diodeBox || !resistorBox)
302532
+ return;
302533
+ const diodePort = this.findNearestPort(DIODE_FTYPES.has(startFtype) ? start : end, schematicSheetId);
302534
+ const resistorPort = this.findNearestPort(startFtype === "simple_resistor" ? start : end, schematicSheetId);
302535
+ if (!diodePort?.center || !resistorPort?.center)
302536
+ return;
302537
+ const diodeName = diodeBox.sourceComponentName ?? diodeCompId;
302538
+ const resistorName = resistorBox.sourceComponentName ?? resistorCompId;
302539
+ const diodePin = diodePort?.display_pin_label ?? diodePort?.pin_number?.toString();
302540
+ const resistorPin = resistorPort?.display_pin_label ?? resistorPort?.pin_number?.toString();
302541
+ const diodeFacing = diodePort?.facing_direction;
302542
+ const resistorFacing = resistorPort?.facing_direction;
302543
+ const diodePinDesc = diodePin ? `${diodeName}.${diodePin}` : diodeName;
302544
+ const resistorPinDesc = resistorPin ? `${resistorName}.${resistorPin}` : resistorName;
302545
+ const makeIssue2 = (message) => ({
302546
+ lineItemType: "DiodeResistorNotAligned",
302547
+ diodeSchematicBox: diodeBox,
302548
+ resistorSchematicBox: resistorBox,
302549
+ diodePin,
302550
+ resistorPin,
302551
+ diodePinFacingDirection: diodeFacing,
302552
+ resistorPinFacingDirection: resistorFacing,
302553
+ message
302554
+ });
302555
+ if (!DiodeResistorAlignmentSolver.isCoLinear(diodePort.center, resistorPort.center)) {
302556
+ this.out.push(makeIssue2(`trace has corners — align ${diodeName} and ${resistorName} on same axis and rotate so ${diodePinDesc} faces ${resistorPinDesc}`));
302557
+ return;
302558
+ }
302559
+ if (diodeFacing && resistorFacing && !DiodeResistorAlignmentSolver.pinsFacingEachOther(diodePort.center, diodeFacing, resistorPort.center, resistorFacing)) {
302560
+ this.out.push(makeIssue2(`${diodePinDesc} and ${resistorPinDesc} face away from each other — rotate ${diodeName} so ${diodePinDesc} faces ${resistorPinDesc}`));
302561
+ }
302562
+ }
302563
+ static isCoLinear(a2, b3, epsilon = 0.01) {
302564
+ return Math.abs(a2.x - b3.x) < epsilon || Math.abs(a2.y - b3.y) < epsilon;
302565
+ }
302566
+ static pinsFacingEachOther(aCenter, aFacing, bCenter, bFacing) {
302567
+ const dx3 = bCenter.x - aCenter.x;
302568
+ const dy3 = bCenter.y - aCenter.y;
302569
+ const aToward = aFacing === "right" && dx3 > 0 || aFacing === "left" && dx3 < 0 || aFacing === "up" && dy3 > 0 || aFacing === "down" && dy3 < 0;
302570
+ const bToward = bFacing === "right" && dx3 < 0 || bFacing === "left" && dx3 > 0 || bFacing === "up" && dy3 < 0 || bFacing === "down" && dy3 > 0;
302571
+ return aToward && bToward;
302572
+ }
302573
+ static dist(a2, b3) {
302574
+ return Math.sqrt((a2.x - b3.x) ** 2 + (a2.y - b3.y) ** 2);
302575
+ }
302576
+ findNearestPort(point9, schematicSheetId) {
302577
+ let nearest;
302578
+ let minDist = Infinity;
302579
+ for (const port of this.schematicPorts) {
302580
+ if (port.schematic_sheet_id !== schematicSheetId)
302581
+ continue;
302582
+ if (!port.center)
302583
+ continue;
302584
+ const d3 = DiodeResistorAlignmentSolver.dist(point9, port.center);
302585
+ if (d3 < minDist) {
302586
+ minDist = d3;
302587
+ nearest = port;
302588
+ }
302589
+ }
302590
+ return nearest;
302591
+ }
302592
+ findSourceComponentIdNearPoint(point9, schematicSheetId) {
302593
+ const nearest = this.findNearestPort(point9, schematicSheetId);
302594
+ if (!nearest || !nearest.source_port_id)
302595
+ return;
302596
+ return this.sourceComponentIdBySourcePortId.get(nearest.source_port_id);
302597
+ }
302598
+ buildSourceComponentFtypeById(circuitJson) {
302599
+ const map = new Map;
302600
+ for (const el3 of circuitJson) {
302601
+ if (el3.type === "source_component" && "source_component_id" in el3 && "ftype" in el3 && typeof el3.ftype === "string") {
302602
+ map.set(el3.source_component_id, el3.ftype);
302603
+ }
302604
+ }
302605
+ return map;
302606
+ }
302607
+ buildSourceComponentIdBySourcePortId(circuitJson) {
302608
+ const map = new Map;
302609
+ for (const el3 of circuitJson) {
302610
+ if (el3.type === "source_port" && "source_port_id" in el3 && "source_component_id" in el3 && typeof el3.source_port_id === "string" && typeof el3.source_component_id === "string") {
302611
+ map.set(el3.source_port_id, el3.source_component_id);
302612
+ }
302613
+ }
302614
+ return map;
302615
+ }
302616
+ buildSchematicBoxBySourceComponentId() {
302617
+ const map = new Map;
302618
+ for (const placement of this.ctx.componentPlacements) {
302619
+ if (placement.sourceComponentId) {
302620
+ map.set(placement.sourceComponentId, placement);
302621
+ }
302622
+ }
302623
+ return map;
302624
+ }
302625
+ static issueToString(issue) {
302626
+ const attrs = [];
302627
+ addAttr(attrs, "diodeComponentName", issue.diodeSchematicBox.sourceComponentName);
302628
+ addAttr(attrs, "diodePin", issue.diodePin);
302629
+ addAttr(attrs, "resistorComponentName", issue.resistorSchematicBox.sourceComponentName);
302630
+ addAttr(attrs, "resistorPin", issue.resistorPin);
302631
+ addAttr(attrs, "message", issue.message);
302632
+ return `<DiodeResistorNotAligned ${attrs.join(" ")} />`;
302633
+ }
302196
302634
  }
302197
302635
 
302198
302636
  // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/solvers/SchematicBoxInnerLabelCollisionSolver/SchematicBoxInnerLabelCollisionSolver.ts
302199
- import { BaseSolver as BaseSolver8 } from "@tscircuit/solver-utils";
302200
- class SchematicBoxInnerLabelCollisionSolver extends BaseSolver8 {
302637
+ import { BaseSolver as BaseSolver10 } from "@tscircuit/solver-utils";
302638
+ class SchematicBoxInnerLabelCollisionSolver extends BaseSolver10 {
302201
302639
  params;
302202
302640
  MESSAGE = "Inner labels are colliding. Increase the schWidth or schHeight.";
302203
302641
  PIN_LABEL_EDGE_PADDING = 0.1;
@@ -302394,9 +302832,129 @@ class SchematicBoxInnerLabelCollisionSolver extends BaseSolver8 {
302394
302832
  }
302395
302833
  }
302396
302834
 
302835
+ // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/solvers/SchematicBoxOverlapSolver/SchematicBoxOverlapSolver.ts
302836
+ import { BaseSolver as BaseSolver11 } from "@tscircuit/solver-utils";
302837
+ class SchematicBoxOverlapSolver extends BaseSolver11 {
302838
+ params;
302839
+ placements;
302840
+ firstIndex = 0;
302841
+ secondIndex = 1;
302842
+ constructor(params2) {
302843
+ super();
302844
+ this.params = params2;
302845
+ this.placements = params2.ctx.componentPlacements;
302846
+ this.solved = this.placements.length < 2;
302847
+ }
302848
+ _step() {
302849
+ if (this.firstIndex >= this.placements.length - 1) {
302850
+ this.solved = true;
302851
+ return;
302852
+ }
302853
+ const overlap = this.getComponentOverlap(this.placements[this.firstIndex], this.placements[this.secondIndex]);
302854
+ if (overlap)
302855
+ this.params.issues.push(overlap);
302856
+ this.secondIndex++;
302857
+ if (this.secondIndex >= this.placements.length) {
302858
+ this.firstIndex++;
302859
+ this.secondIndex = this.firstIndex + 1;
302860
+ }
302861
+ this.solved = this.firstIndex >= this.placements.length - 1;
302862
+ }
302863
+ getCenteredRectBounds(box2) {
302864
+ return {
302865
+ left: box2.schX - box2.width / 2,
302866
+ right: box2.schX + box2.width / 2,
302867
+ top: box2.schY - box2.height / 2,
302868
+ bottom: box2.schY + box2.height / 2
302869
+ };
302870
+ }
302871
+ getOverlapCorrectionSuggestions({
302872
+ firstComponent,
302873
+ secondComponent,
302874
+ overlapWidth,
302875
+ overlapHeight
302876
+ }) {
302877
+ const firstArea = firstComponent.width * firstComponent.height;
302878
+ const secondArea = secondComponent.width * secondComponent.height;
302879
+ const target = firstArea <= secondArea ? firstComponent : secondComponent;
302880
+ const other = target === firstComponent ? secondComponent : firstComponent;
302881
+ const deltaSchX = target.schX <= other.schX ? -overlapWidth : overlapWidth;
302882
+ const deltaSchY = target.schY <= other.schY ? -overlapHeight : overlapHeight;
302883
+ return [
302884
+ {
302885
+ targetComponentName: target.sourceComponentName,
302886
+ deltaSchX,
302887
+ deltaSchY: 0,
302888
+ newSchX: target.schX + deltaSchX,
302889
+ newSchY: target.schY
302890
+ },
302891
+ {
302892
+ targetComponentName: target.sourceComponentName,
302893
+ deltaSchX: 0,
302894
+ deltaSchY,
302895
+ newSchX: target.schX,
302896
+ newSchY: target.schY + deltaSchY
302897
+ }
302898
+ ];
302899
+ }
302900
+ static issueToString(issue) {
302901
+ const attrs = [];
302902
+ addAttr(attrs, "component1Name", issue.firstComponent.sourceComponentName);
302903
+ addAttr(attrs, "component2Name", issue.secondComponent.sourceComponentName);
302904
+ addAttr(attrs, "component1SchX", issue.firstComponent.schX);
302905
+ addAttr(attrs, "component1SchY", issue.firstComponent.schY);
302906
+ addAttr(attrs, "component2SchX", issue.secondComponent.schX);
302907
+ addAttr(attrs, "component2SchY", issue.secondComponent.schY);
302908
+ addAttr(attrs, "overlapWidth", issue.overlapWidth);
302909
+ addAttr(attrs, "overlapHeight", issue.overlapHeight);
302910
+ return [
302911
+ `<ComponentOverlap ${attrs.join(" ")}>`,
302912
+ ...issue.correctionSuggestions.map(SchematicBoxOverlapSolver.correctionSuggestionToString),
302913
+ "</ComponentOverlap>"
302914
+ ].join(`
302915
+ `);
302916
+ }
302917
+ static correctionSuggestionToString(suggestion) {
302918
+ const attrs = [];
302919
+ addAttr(attrs, "target", suggestion.targetComponentName);
302920
+ if (suggestion.deltaSchX !== 0) {
302921
+ addAttr(attrs, "newSchX", suggestion.newSchX);
302922
+ addAttr(attrs, "deltaSchX", suggestion.deltaSchX, { formatDelta: true });
302923
+ }
302924
+ if (suggestion.deltaSchY !== 0) {
302925
+ addAttr(attrs, "newSchY", suggestion.newSchY);
302926
+ addAttr(attrs, "deltaSchY", suggestion.deltaSchY, { formatDelta: true });
302927
+ }
302928
+ return `<OverlapCorrectionSuggestion ${attrs.join(" ")} />`;
302929
+ }
302930
+ getComponentOverlap(firstComponent, secondComponent) {
302931
+ if (firstComponent.schematicSheetId !== secondComponent.schematicSheetId)
302932
+ return null;
302933
+ const a2 = this.getCenteredRectBounds(firstComponent);
302934
+ const b3 = this.getCenteredRectBounds(secondComponent);
302935
+ const overlapWidth = Math.min(a2.right, b3.right) - Math.max(a2.left, b3.left);
302936
+ const overlapHeight = Math.min(a2.bottom, b3.bottom) - Math.max(a2.top, b3.top);
302937
+ if (overlapWidth <= 0 || overlapHeight <= 0)
302938
+ return null;
302939
+ return {
302940
+ lineItemType: "ComponentOverlap",
302941
+ firstComponent,
302942
+ secondComponent,
302943
+ overlapWidth,
302944
+ overlapHeight,
302945
+ correctionSuggestions: this.getOverlapCorrectionSuggestions({
302946
+ firstComponent,
302947
+ secondComponent,
302948
+ overlapWidth,
302949
+ overlapHeight
302950
+ })
302951
+ };
302952
+ }
302953
+ }
302954
+
302397
302955
  // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/solvers/SchematicBoxTooWideSolver/SchematicBoxTooWideSolver.ts
302398
- import { BaseSolver as BaseSolver9 } from "@tscircuit/solver-utils";
302399
- class SchematicBoxTooWideSolver extends BaseSolver9 {
302956
+ import { BaseSolver as BaseSolver12 } from "@tscircuit/solver-utils";
302957
+ class SchematicBoxTooWideSolver extends BaseSolver12 {
302400
302958
  params;
302401
302959
  SCHEMATIC_BOX_TOO_WIDE_MESSAGE = "Shrink schematic box width";
302402
302960
  PIN_HEADER_MAX_ALLOWED_GAP = 0.1;
@@ -302571,8 +303129,8 @@ class SchematicBoxTooWideSolver extends BaseSolver9 {
302571
303129
  }
302572
303130
 
302573
303131
  // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/solvers/SchematicPinPaddingToEdgeSolver/SchematicPinPaddingToEdgeSolver.ts
302574
- import { BaseSolver as BaseSolver10 } from "@tscircuit/solver-utils";
302575
- class SchematicPinPaddingToEdgeSolver extends BaseSolver10 {
303132
+ import { BaseSolver as BaseSolver13 } from "@tscircuit/solver-utils";
303133
+ class SchematicPinPaddingToEdgeSolver extends BaseSolver13 {
302576
303134
  params;
302577
303135
  MESSAGE = "Move schematic pins closer to the box edge or change the schematic box";
302578
303136
  PIN_NAME_CHARACTER_WIDTH = 0.095;
@@ -302800,9 +303358,349 @@ class SchematicPinPaddingToEdgeSolver extends BaseSolver10 {
302800
303358
  }
302801
303359
  }
302802
303360
 
303361
+ // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/solvers/SchematicPlacementPipeline/SchematicPlacementPipeline.ts
303362
+ import {
303363
+ BasePipelineSolver as BasePipelineSolver2,
303364
+ definePipelineStep as definePipelineStep3
303365
+ } from "@tscircuit/solver-utils";
303366
+
303367
+ // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/utils/placements.ts
303368
+ import { cju as cju7 } from "@tscircuit/circuit-json-util";
303369
+
303370
+ // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/utils/schematic-sheets.ts
303371
+ var getSchematicSheetNamesById = (circuitJson) => {
303372
+ const map = new Map;
303373
+ for (const element of circuitJson) {
303374
+ if (element.type === "schematic_sheet" && element.name) {
303375
+ map.set(element.schematic_sheet_id, element.name);
303376
+ }
303377
+ }
303378
+ return map;
303379
+ };
303380
+
303381
+ // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/utils/placements.ts
303382
+ var isSchematicBox = (el3) => el3.type === "schematic_box";
303383
+ var isSchematicComponent = (el3) => el3.type === "schematic_component";
303384
+ var getSchematicSheetId = (el3) => el3.schematic_sheet_id;
303385
+ var getSourceComponentName = (circuitJson, sourceComponentId) => {
303386
+ if (!sourceComponentId)
303387
+ return;
303388
+ return cju7(circuitJson).source_component.get(sourceComponentId)?.name;
303389
+ };
303390
+ var getSourceComponentMetadata = (schematicBox, circuitJson) => {
303391
+ if (!schematicBox.schematic_component_id)
303392
+ return {};
303393
+ const util2 = cju7(circuitJson);
303394
+ const sc2 = util2.schematic_component.get(schematicBox.schematic_component_id);
303395
+ if (!sc2?.source_component_id)
303396
+ return {};
303397
+ const sourceComponent = util2.source_component.get(sc2.source_component_id);
303398
+ return {
303399
+ sourceComponentId: sc2.source_component_id,
303400
+ sourceComponentName: sourceComponent?.name
303401
+ };
303402
+ };
303403
+ var schematicComponentToPlacement = (schematicComponent, circuitJson, schematicSheetNameById, schematicBox) => {
303404
+ let schematicSheetId = getSchematicSheetId(schematicComponent);
303405
+ if (schematicSheetId === undefined && schematicBox) {
303406
+ schematicSheetId = getSchematicSheetId(schematicBox);
303407
+ }
303408
+ let schematicSheetName;
303409
+ if (schematicSheetId) {
303410
+ schematicSheetName = schematicSheetNameById.get(schematicSheetId);
303411
+ }
303412
+ const placement = {
303413
+ lineItemType: "SchematicBoxPlacement",
303414
+ positionAnchor: "center",
303415
+ schX: schematicComponent.center.x,
303416
+ schY: schematicComponent.center.y,
303417
+ width: schematicBox?.width ?? schematicComponent.size.width,
303418
+ height: schematicBox?.height ?? schematicComponent.size.height,
303419
+ sourceComponentId: schematicComponent.source_component_id,
303420
+ sourceComponentName: getSourceComponentName(circuitJson, schematicComponent.source_component_id),
303421
+ schematicComponentId: schematicComponent.schematic_component_id,
303422
+ schematicSymbolId: schematicBox?.schematic_symbol_id ?? schematicComponent.schematic_symbol_id,
303423
+ subcircuitId: schematicComponent.subcircuit_id ?? schematicBox?.subcircuit_id
303424
+ };
303425
+ if (schematicSheetId) {
303426
+ placement.schematicSheetId = schematicSheetId;
303427
+ }
303428
+ if (schematicSheetName) {
303429
+ placement.schematicSheetName = schematicSheetName;
303430
+ }
303431
+ return placement;
303432
+ };
303433
+ var schematicBoxToPlacement = (schematicBox, circuitJson, schematicSheetNameById) => {
303434
+ const schematicSheetId = getSchematicSheetId(schematicBox);
303435
+ let schematicSheetName;
303436
+ if (schematicSheetId) {
303437
+ schematicSheetName = schematicSheetNameById.get(schematicSheetId);
303438
+ }
303439
+ const placement = {
303440
+ lineItemType: "SchematicBoxPlacement",
303441
+ positionAnchor: "center",
303442
+ schX: schematicBox.x,
303443
+ schY: schematicBox.y,
303444
+ width: schematicBox.width,
303445
+ height: schematicBox.height,
303446
+ ...getSourceComponentMetadata(schematicBox, circuitJson),
303447
+ schematicComponentId: schematicBox.schematic_component_id,
303448
+ schematicSymbolId: schematicBox.schematic_symbol_id,
303449
+ subcircuitId: schematicBox.subcircuit_id
303450
+ };
303451
+ if (schematicSheetId) {
303452
+ placement.schematicSheetId = schematicSheetId;
303453
+ }
303454
+ if (schematicSheetName) {
303455
+ placement.schematicSheetName = schematicSheetName;
303456
+ }
303457
+ return placement;
303458
+ };
303459
+ var buildSolverContext = (circuitJson) => {
303460
+ const schematicBoxes = circuitJson.filter(isSchematicBox);
303461
+ const schematicSheetNameById = getSchematicSheetNamesById(circuitJson);
303462
+ const schematicComponentIds = new Set(circuitJson.filter(isSchematicComponent).map((sc2) => sc2.schematic_component_id));
303463
+ const componentPlacements = [
303464
+ ...circuitJson.filter(isSchematicComponent).map((sc2) => schematicComponentToPlacement(sc2, circuitJson, schematicSheetNameById, schematicBoxes.find((sb3) => sb3.schematic_component_id === sc2.schematic_component_id))),
303465
+ ...schematicBoxes.filter((sb3) => !sb3.schematic_component_id || !schematicComponentIds.has(sb3.schematic_component_id)).map((sb3) => schematicBoxToPlacement(sb3, circuitJson, schematicSheetNameById))
303466
+ ];
303467
+ return { circuitJson, componentPlacements };
303468
+ };
303469
+
303470
+ // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/solvers/TraceSimplificationSolver/TraceSimplificationSolver.ts
303471
+ import { BaseSolver as BaseSolver14 } from "@tscircuit/solver-utils";
303472
+ class TraceSimplificationSolver extends BaseSolver14 {
303473
+ static EPSILON = 0.01;
303474
+ ctx;
303475
+ out;
303476
+ constructor({
303477
+ ctx,
303478
+ issues
303479
+ }) {
303480
+ super();
303481
+ this.ctx = ctx;
303482
+ this.out = issues;
303483
+ }
303484
+ _step() {
303485
+ const ports = this.ctx.circuitJson.filter((element) => element.type === "schematic_port");
303486
+ const portsById = new Map(ports.map((port) => [port.schematic_port_id, port]));
303487
+ const placementsByComponentId = new Map(this.ctx.componentPlacements.flatMap((placement) => placement.schematicComponentId ? [[placement.schematicComponentId, placement]] : []));
303488
+ const emittedMoves = new Set;
303489
+ for (const trace of this.ctx.circuitJson.filter((element) => element.type === "schematic_trace")) {
303490
+ const points = this.getTracePoints(trace);
303491
+ const currentTurnCount = this.countTurns(points);
303492
+ if (currentTurnCount !== 3)
303493
+ continue;
303494
+ const candidates = [
303495
+ this.getCandidate({
303496
+ trace,
303497
+ points,
303498
+ atStart: true,
303499
+ currentTurnCount,
303500
+ ports,
303501
+ portsById,
303502
+ placementsByComponentId
303503
+ }),
303504
+ this.getCandidate({
303505
+ trace,
303506
+ points,
303507
+ atStart: false,
303508
+ currentTurnCount,
303509
+ ports,
303510
+ portsById,
303511
+ placementsByComponentId
303512
+ })
303513
+ ].filter((candidate) => Boolean(candidate));
303514
+ for (const candidate of candidates) {
303515
+ if (this.wouldOverlapAnotherComponent(candidate))
303516
+ continue;
303517
+ const moveKey = [
303518
+ candidate.target.schematicComponentId,
303519
+ candidate.deltaSchX.toFixed(3),
303520
+ candidate.deltaSchY.toFixed(3)
303521
+ ].join("\x00");
303522
+ if (emittedMoves.has(moveKey))
303523
+ continue;
303524
+ emittedMoves.add(moveKey);
303525
+ this.out.push(this.makeIssue(trace, candidate));
303526
+ }
303527
+ }
303528
+ this.solved = true;
303529
+ }
303530
+ getCandidate({
303531
+ trace,
303532
+ points,
303533
+ atStart,
303534
+ currentTurnCount,
303535
+ ports,
303536
+ portsById,
303537
+ placementsByComponentId
303538
+ }) {
303539
+ if (points.length < 4)
303540
+ return;
303541
+ const terminalIndex = atStart ? 0 : points.length - 1;
303542
+ const leadIndex = atStart ? 1 : points.length - 2;
303543
+ const acrossIndex = atStart ? 2 : points.length - 3;
303544
+ const retainedIndex = atStart ? 3 : points.length - 4;
303545
+ const terminalPoint = points[terminalIndex];
303546
+ const leadPoint = points[leadIndex];
303547
+ const acrossPoint = points[acrossIndex];
303548
+ const retainedPoint = points[retainedIndex];
303549
+ const terminalEdge = atStart ? trace.edges[0] : trace.edges.at(-1);
303550
+ if (!terminalEdge)
303551
+ return;
303552
+ const portId = atStart ? terminalEdge.from_schematic_port_id : terminalEdge.to_schematic_port_id;
303553
+ const port = portId ? portsById.get(portId) : this.findPortAtPoint(ports, terminalPoint, trace.schematic_sheet_id);
303554
+ if (!port?.schematic_component_id || !port.facing_direction)
303555
+ return;
303556
+ const leadAxis = this.getAxis(terminalPoint, leadPoint);
303557
+ const shiftAxis = this.getAxis(leadPoint, acrossPoint);
303558
+ const retainedAxis = this.getAxis(acrossPoint, retainedPoint);
303559
+ const portAxis = port.facing_direction === "left" || port.facing_direction === "right" ? "horizontal" : "vertical";
303560
+ if (!leadAxis || !shiftAxis || !retainedAxis)
303561
+ return;
303562
+ if (leadAxis !== portAxis || shiftAxis === portAxis || retainedAxis !== portAxis) {
303563
+ return;
303564
+ }
303565
+ if (!this.isPointInFacingDirection(terminalPoint, leadPoint, port.facing_direction) || !this.isPointInFacingDirection(acrossPoint, retainedPoint, port.facing_direction)) {
303566
+ return;
303567
+ }
303568
+ const target = placementsByComponentId.get(port.schematic_component_id);
303569
+ if (!target)
303570
+ return;
303571
+ const deltaSchX = acrossPoint.x - leadPoint.x;
303572
+ const deltaSchY = acrossPoint.y - leadPoint.y;
303573
+ if (Math.abs(deltaSchX) <= TraceSimplificationSolver.EPSILON && Math.abs(deltaSchY) <= TraceSimplificationSolver.EPSILON) {
303574
+ return;
303575
+ }
303576
+ const movedTerminalPoint = {
303577
+ x: terminalPoint.x + deltaSchX,
303578
+ y: terminalPoint.y + deltaSchY
303579
+ };
303580
+ const suggestedPoints = atStart ? [movedTerminalPoint, ...points.slice(2)] : [...points.slice(0, -2), movedTerminalPoint];
303581
+ const suggestedTurnCount = this.countTurns(suggestedPoints);
303582
+ if (suggestedTurnCount === undefined || suggestedTurnCount !== 1 || suggestedTurnCount >= currentTurnCount) {
303583
+ return;
303584
+ }
303585
+ return {
303586
+ target,
303587
+ deltaSchX,
303588
+ deltaSchY,
303589
+ currentTurnCount,
303590
+ suggestedTurnCount
303591
+ };
303592
+ }
303593
+ getTracePoints(trace) {
303594
+ const firstEdge = trace.edges[0];
303595
+ if (!firstEdge)
303596
+ return [];
303597
+ const points = [firstEdge.from];
303598
+ for (const edge of trace.edges) {
303599
+ const previousPoint = points.at(-1);
303600
+ if (!this.pointsEqual(previousPoint, edge.from))
303601
+ return [];
303602
+ points.push(edge.to);
303603
+ }
303604
+ return points;
303605
+ }
303606
+ countTurns(points) {
303607
+ const axes = [];
303608
+ for (const [index, point9] of points.slice(1).entries()) {
303609
+ const previousPoint = points[index];
303610
+ if (this.pointsEqual(previousPoint, point9))
303611
+ continue;
303612
+ const axis = this.getAxis(previousPoint, point9);
303613
+ if (!axis)
303614
+ return;
303615
+ axes.push(axis);
303616
+ }
303617
+ return axes.slice(1).filter((axis, index) => axis !== axes[index]).length;
303618
+ }
303619
+ pointsEqual(a2, b3) {
303620
+ const { EPSILON: EPSILON3 } = TraceSimplificationSolver;
303621
+ return Math.abs(a2.x - b3.x) <= EPSILON3 && Math.abs(a2.y - b3.y) <= EPSILON3;
303622
+ }
303623
+ getAxis(a2, b3) {
303624
+ const dx3 = Math.abs(b3.x - a2.x);
303625
+ const dy3 = Math.abs(b3.y - a2.y);
303626
+ const { EPSILON: EPSILON3 } = TraceSimplificationSolver;
303627
+ if (dx3 <= EPSILON3 && dy3 > EPSILON3)
303628
+ return "vertical";
303629
+ if (dy3 <= EPSILON3 && dx3 > EPSILON3)
303630
+ return "horizontal";
303631
+ return;
303632
+ }
303633
+ isPointInFacingDirection(origin, point9, facingDirection) {
303634
+ const { EPSILON: EPSILON3 } = TraceSimplificationSolver;
303635
+ switch (facingDirection) {
303636
+ case "left":
303637
+ return point9.x < origin.x - EPSILON3;
303638
+ case "right":
303639
+ return point9.x > origin.x + EPSILON3;
303640
+ case "up":
303641
+ return point9.y > origin.y + EPSILON3;
303642
+ case "down":
303643
+ return point9.y < origin.y - EPSILON3;
303644
+ }
303645
+ }
303646
+ findPortAtPoint(ports, point9, schematicSheetId) {
303647
+ const { EPSILON: EPSILON3 } = TraceSimplificationSolver;
303648
+ return ports.find((port) => port.schematic_sheet_id === schematicSheetId && Math.abs(port.center.x - point9.x) <= EPSILON3 && Math.abs(port.center.y - point9.y) <= EPSILON3);
303649
+ }
303650
+ wouldOverlapAnotherComponent(candidate) {
303651
+ const movedBounds = centeredRect(candidate.target.schX + candidate.deltaSchX, candidate.target.schY + candidate.deltaSchY, candidate.target.width, candidate.target.height);
303652
+ return this.ctx.componentPlacements.some((placement) => {
303653
+ if (placement === candidate.target)
303654
+ return false;
303655
+ if (placement.schematicSheetId !== candidate.target.schematicSheetId) {
303656
+ return false;
303657
+ }
303658
+ return Boolean(rectOverlap(movedBounds, centeredRect(placement.schX, placement.schY, placement.width, placement.height)));
303659
+ });
303660
+ }
303661
+ makeIssue(trace, candidate) {
303662
+ const targetName = candidate.target.sourceComponentName ?? candidate.target.schematicComponentId ?? "component";
303663
+ const direction2 = this.getMoveDirection(candidate.deltaSchX, candidate.deltaSchY);
303664
+ const distance49 = Math.abs(candidate.deltaSchX || candidate.deltaSchY);
303665
+ const newSchX = candidate.target.schX + candidate.deltaSchX;
303666
+ const newSchY = candidate.target.schY + candidate.deltaSchY;
303667
+ return {
303668
+ lineItemType: "TraceCanBeSimplifiedByMovingComponent",
303669
+ schematicTraceId: trace.schematic_trace_id,
303670
+ targetComponent: candidate.target,
303671
+ deltaSchX: candidate.deltaSchX,
303672
+ deltaSchY: candidate.deltaSchY,
303673
+ newSchX,
303674
+ newSchY,
303675
+ currentTurnCount: candidate.currentTurnCount,
303676
+ suggestedTurnCount: candidate.suggestedTurnCount,
303677
+ message: `move ${targetName} ${direction2} by ${fmtNumber5(distance49)} (to schX=${fmtNumber5(newSchX)}, schY=${fmtNumber5(newSchY)}) to reduce this trace from ${candidate.currentTurnCount} turns to ${candidate.suggestedTurnCount}`
303678
+ };
303679
+ }
303680
+ getMoveDirection(deltaSchX, deltaSchY) {
303681
+ if (Math.abs(deltaSchX) > TraceSimplificationSolver.EPSILON) {
303682
+ return deltaSchX > 0 ? "right" : "left";
303683
+ }
303684
+ return deltaSchY > 0 ? "up" : "down";
303685
+ }
303686
+ static issueToString(issue) {
303687
+ const attrs = [];
303688
+ addAttr(attrs, "schematicTraceId", issue.schematicTraceId);
303689
+ addAttr(attrs, "targetComponentName", issue.targetComponent.sourceComponentName);
303690
+ addAttr(attrs, "deltaSchX", issue.deltaSchX, { formatDelta: true });
303691
+ addAttr(attrs, "deltaSchY", issue.deltaSchY, { formatDelta: true });
303692
+ addAttr(attrs, "newSchX", issue.newSchX);
303693
+ addAttr(attrs, "newSchY", issue.newSchY);
303694
+ addAttr(attrs, "currentTurnCount", issue.currentTurnCount);
303695
+ addAttr(attrs, "suggestedTurnCount", issue.suggestedTurnCount);
303696
+ addAttr(attrs, "message", issue.message);
303697
+ return `<TraceCanBeSimplifiedByMovingComponent ${attrs.join(" ")} />`;
303698
+ }
303699
+ }
303700
+
302803
303701
  // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/solvers/VerboseNetLabelSolver/VerboseNetLabelSolver.ts
302804
- import { BaseSolver as BaseSolver11 } from "@tscircuit/solver-utils";
302805
- class VerboseNetLabelSolver extends BaseSolver11 {
303702
+ import { BaseSolver as BaseSolver15 } from "@tscircuit/solver-utils";
303703
+ class VerboseNetLabelSolver extends BaseSolver15 {
302806
303704
  params;
302807
303705
  VERBOSE_NET_LABEL_MESSAGE = "Create trace with schDisplayLabel";
302808
303706
  netLabels;
@@ -302910,650 +303808,6 @@ class VerboseNetLabelSolver extends BaseSolver11 {
302910
303808
  }
302911
303809
  }
302912
303810
 
302913
- // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/solvers/DiodeResistorAlignmentSolver/DiodeResistorAlignmentSolver.ts
302914
- import { BaseSolver as BaseSolver12 } from "@tscircuit/solver-utils";
302915
- class DiodeResistorAlignmentSolver extends BaseSolver12 {
302916
- static DIODE_FTYPES = new Set(["simple_led", "simple_diode"]);
302917
- ctx;
302918
- out;
302919
- schematicTraces;
302920
- currentIndex = 0;
302921
- sourceComponentFtypeById;
302922
- sourceComponentIdBySourcePortId;
302923
- schematicPorts;
302924
- schematicBoxBySourceComponentId;
302925
- constructor({
302926
- ctx,
302927
- issues
302928
- }) {
302929
- super();
302930
- this.ctx = ctx;
302931
- this.out = issues;
302932
- const { circuitJson } = ctx;
302933
- this.sourceComponentFtypeById = this.buildSourceComponentFtypeById(circuitJson);
302934
- this.sourceComponentIdBySourcePortId = this.buildSourceComponentIdBySourcePortId(circuitJson);
302935
- this.schematicPorts = circuitJson.filter((el3) => el3.type === "schematic_port");
302936
- this.schematicBoxBySourceComponentId = this.buildSchematicBoxBySourceComponentId();
302937
- this.schematicTraces = circuitJson.filter((el3) => el3.type === "schematic_trace");
302938
- this.solved = this.schematicTraces.length === 0;
302939
- }
302940
- _step() {
302941
- const trace = this.schematicTraces[this.currentIndex];
302942
- if (!trace) {
302943
- this.solved = true;
302944
- return;
302945
- }
302946
- this.currentIndex++;
302947
- this.solved = this.currentIndex >= this.schematicTraces.length;
302948
- if (!trace.edges || trace.edges.length === 0)
302949
- return;
302950
- const firstEdge = trace.edges[0];
302951
- const lastEdge = trace.edges[trace.edges.length - 1];
302952
- if (!firstEdge || !lastEdge)
302953
- return;
302954
- const start = firstEdge.from;
302955
- const end = lastEdge.to;
302956
- const schematicSheetId = trace.schematic_sheet_id;
302957
- const startSourceCompId = this.findSourceComponentIdNearPoint(start, schematicSheetId);
302958
- const endSourceCompId = this.findSourceComponentIdNearPoint(end, schematicSheetId);
302959
- if (!startSourceCompId || !endSourceCompId)
302960
- return;
302961
- const startFtype = this.sourceComponentFtypeById.get(startSourceCompId);
302962
- const endFtype = this.sourceComponentFtypeById.get(endSourceCompId);
302963
- const { DIODE_FTYPES } = DiodeResistorAlignmentSolver;
302964
- const isDiodeResistorPair = DIODE_FTYPES.has(startFtype) && endFtype === "simple_resistor" || startFtype === "simple_resistor" && DIODE_FTYPES.has(endFtype);
302965
- if (!isDiodeResistorPair)
302966
- return;
302967
- const diodeCompId = DIODE_FTYPES.has(startFtype) ? startSourceCompId : endSourceCompId;
302968
- const resistorCompId = startFtype === "simple_resistor" ? startSourceCompId : endSourceCompId;
302969
- const diodeBox = this.schematicBoxBySourceComponentId.get(diodeCompId);
302970
- const resistorBox = this.schematicBoxBySourceComponentId.get(resistorCompId);
302971
- if (!diodeBox || !resistorBox)
302972
- return;
302973
- const diodePort = this.findNearestPort(DIODE_FTYPES.has(startFtype) ? start : end, schematicSheetId);
302974
- const resistorPort = this.findNearestPort(startFtype === "simple_resistor" ? start : end, schematicSheetId);
302975
- if (!diodePort?.center || !resistorPort?.center)
302976
- return;
302977
- const diodeName = diodeBox.sourceComponentName ?? diodeCompId;
302978
- const resistorName = resistorBox.sourceComponentName ?? resistorCompId;
302979
- const diodePin = diodePort?.display_pin_label ?? diodePort?.pin_number?.toString();
302980
- const resistorPin = resistorPort?.display_pin_label ?? resistorPort?.pin_number?.toString();
302981
- const diodeFacing = diodePort?.facing_direction;
302982
- const resistorFacing = resistorPort?.facing_direction;
302983
- const diodePinDesc = diodePin ? `${diodeName}.${diodePin}` : diodeName;
302984
- const resistorPinDesc = resistorPin ? `${resistorName}.${resistorPin}` : resistorName;
302985
- const makeIssue2 = (message) => ({
302986
- lineItemType: "DiodeResistorNotAligned",
302987
- diodeSchematicBox: diodeBox,
302988
- resistorSchematicBox: resistorBox,
302989
- diodePin,
302990
- resistorPin,
302991
- diodePinFacingDirection: diodeFacing,
302992
- resistorPinFacingDirection: resistorFacing,
302993
- message
302994
- });
302995
- if (!DiodeResistorAlignmentSolver.isCoLinear(diodePort.center, resistorPort.center)) {
302996
- this.out.push(makeIssue2(`trace has corners — align ${diodeName} and ${resistorName} on same axis and rotate so ${diodePinDesc} faces ${resistorPinDesc}`));
302997
- return;
302998
- }
302999
- if (diodeFacing && resistorFacing && !DiodeResistorAlignmentSolver.pinsFacingEachOther(diodePort.center, diodeFacing, resistorPort.center, resistorFacing)) {
303000
- this.out.push(makeIssue2(`${diodePinDesc} and ${resistorPinDesc} face away from each other — rotate ${diodeName} so ${diodePinDesc} faces ${resistorPinDesc}`));
303001
- }
303002
- }
303003
- static isCoLinear(a2, b3, epsilon = 0.01) {
303004
- return Math.abs(a2.x - b3.x) < epsilon || Math.abs(a2.y - b3.y) < epsilon;
303005
- }
303006
- static pinsFacingEachOther(aCenter, aFacing, bCenter, bFacing) {
303007
- const dx3 = bCenter.x - aCenter.x;
303008
- const dy3 = bCenter.y - aCenter.y;
303009
- const aToward = aFacing === "right" && dx3 > 0 || aFacing === "left" && dx3 < 0 || aFacing === "up" && dy3 > 0 || aFacing === "down" && dy3 < 0;
303010
- const bToward = bFacing === "right" && dx3 < 0 || bFacing === "left" && dx3 > 0 || bFacing === "up" && dy3 < 0 || bFacing === "down" && dy3 > 0;
303011
- return aToward && bToward;
303012
- }
303013
- static dist(a2, b3) {
303014
- return Math.sqrt((a2.x - b3.x) ** 2 + (a2.y - b3.y) ** 2);
303015
- }
303016
- findNearestPort(point9, schematicSheetId) {
303017
- let nearest;
303018
- let minDist = Infinity;
303019
- for (const port of this.schematicPorts) {
303020
- if (port.schematic_sheet_id !== schematicSheetId)
303021
- continue;
303022
- if (!port.center)
303023
- continue;
303024
- const d3 = DiodeResistorAlignmentSolver.dist(point9, port.center);
303025
- if (d3 < minDist) {
303026
- minDist = d3;
303027
- nearest = port;
303028
- }
303029
- }
303030
- return nearest;
303031
- }
303032
- findSourceComponentIdNearPoint(point9, schematicSheetId) {
303033
- const nearest = this.findNearestPort(point9, schematicSheetId);
303034
- if (!nearest || !nearest.source_port_id)
303035
- return;
303036
- return this.sourceComponentIdBySourcePortId.get(nearest.source_port_id);
303037
- }
303038
- buildSourceComponentFtypeById(circuitJson) {
303039
- const map = new Map;
303040
- for (const el3 of circuitJson) {
303041
- if (el3.type === "source_component" && "source_component_id" in el3 && "ftype" in el3 && typeof el3.ftype === "string") {
303042
- map.set(el3.source_component_id, el3.ftype);
303043
- }
303044
- }
303045
- return map;
303046
- }
303047
- buildSourceComponentIdBySourcePortId(circuitJson) {
303048
- const map = new Map;
303049
- for (const el3 of circuitJson) {
303050
- if (el3.type === "source_port" && "source_port_id" in el3 && "source_component_id" in el3 && typeof el3.source_port_id === "string" && typeof el3.source_component_id === "string") {
303051
- map.set(el3.source_port_id, el3.source_component_id);
303052
- }
303053
- }
303054
- return map;
303055
- }
303056
- buildSchematicBoxBySourceComponentId() {
303057
- const map = new Map;
303058
- for (const placement of this.ctx.componentPlacements) {
303059
- if (placement.sourceComponentId) {
303060
- map.set(placement.sourceComponentId, placement);
303061
- }
303062
- }
303063
- return map;
303064
- }
303065
- static issueToString(issue) {
303066
- const attrs = [];
303067
- addAttr(attrs, "diodeComponentName", issue.diodeSchematicBox.sourceComponentName);
303068
- addAttr(attrs, "diodePin", issue.diodePin);
303069
- addAttr(attrs, "resistorComponentName", issue.resistorSchematicBox.sourceComponentName);
303070
- addAttr(attrs, "resistorPin", issue.resistorPin);
303071
- addAttr(attrs, "message", issue.message);
303072
- return `<DiodeResistorNotAligned ${attrs.join(" ")} />`;
303073
- }
303074
- }
303075
-
303076
- // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/solvers/ComponentNetLabelCollisionSolver/ComponentNetLabelCollisionSolver.ts
303077
- import { BaseSolver as BaseSolver13 } from "@tscircuit/solver-utils";
303078
-
303079
- // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/utils/geometry.ts
303080
- function centeredRect(cx3, cy3, w4, h4) {
303081
- return {
303082
- left: cx3 - w4 / 2,
303083
- right: cx3 + w4 / 2,
303084
- top: cy3 + h4 / 2,
303085
- bottom: cy3 - h4 / 2
303086
- };
303087
- }
303088
- function rectOverlap(a2, b3) {
303089
- const ow2 = Math.min(a2.right, b3.right) - Math.max(a2.left, b3.left);
303090
- const oh2 = Math.min(a2.top, b3.top) - Math.max(a2.bottom, b3.bottom);
303091
- return ow2 > 0 && oh2 > 0 ? { ow: ow2, oh: oh2 } : null;
303092
- }
303093
-
303094
- // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/solvers/ComponentNetLabelCollisionSolver/ComponentNetLabelCollisionSolver.ts
303095
- class ComponentNetLabelCollisionSolver extends BaseSolver13 {
303096
- params;
303097
- LABEL_HALF_HEIGHT = 0.1;
303098
- LABEL_BOUNDS_SLACK = 0.1;
303099
- SCH_CHAR_WIDTH = 0.13;
303100
- placements;
303101
- netLabelsByComponentId;
303102
- rawCollisions = [];
303103
- firstIndex = 0;
303104
- secondIndex = 1;
303105
- constructor(params2) {
303106
- super();
303107
- this.params = params2;
303108
- this.placements = params2.ctx.componentPlacements;
303109
- this.netLabelsByComponentId = this.buildNetLabelsByComponentId(params2.ctx.circuitJson);
303110
- this.solved = this.placements.length < 2;
303111
- }
303112
- _step() {
303113
- if (this.firstIndex >= this.placements.length - 1) {
303114
- this.buildAndPushIssues();
303115
- this.solved = true;
303116
- return;
303117
- }
303118
- const compA = this.placements[this.firstIndex];
303119
- const compB = this.placements[this.secondIndex];
303120
- this.detectPair(compA, compB);
303121
- this.secondIndex++;
303122
- if (this.secondIndex >= this.placements.length) {
303123
- this.firstIndex++;
303124
- this.secondIndex = this.firstIndex + 1;
303125
- }
303126
- }
303127
- detectPair(compA, compB) {
303128
- if (compA.schematicSheetId !== compB.schematicSheetId)
303129
- return;
303130
- this.rawCollisions.push(...this.detectLabelLabel(compA, compB));
303131
- this.rawCollisions.push(...this.detectBoxLabel(compA, compB));
303132
- this.rawCollisions.push(...this.detectBoxLabel(compB, compA));
303133
- }
303134
- detectLabelLabel(firstComponent, secondComponent) {
303135
- let leftComp = firstComponent;
303136
- let rightComp = secondComponent;
303137
- if (firstComponent.schX > secondComponent.schX) {
303138
- leftComp = secondComponent;
303139
- rightComp = firstComponent;
303140
- }
303141
- const leftId = leftComp.schematicComponentId;
303142
- const rightId = rightComp.schematicComponentId;
303143
- if (!leftId || !rightId)
303144
- return [];
303145
- const leftLabels = this.netLabelsByComponentId.get(leftId) ?? [];
303146
- const rightLabels = this.netLabelsByComponentId.get(rightId) ?? [];
303147
- if (leftLabels.length === 0 || rightLabels.length === 0)
303148
- return [];
303149
- const hits = [];
303150
- for (const leftLabel of leftLabels) {
303151
- for (const rightLabel of rightLabels) {
303152
- const leftBounds = this.getNetLabelBounds(leftLabel);
303153
- const rightBounds = this.getNetLabelBounds(rightLabel);
303154
- if (rectOverlap(leftBounds, rightBounds)) {
303155
- hits.push({
303156
- type: "label-label",
303157
- leftComp,
303158
- rightComp,
303159
- leftId,
303160
- rightId,
303161
- xSeparation: leftBounds.right - rightBounds.left + 0.1
303162
- });
303163
- }
303164
- }
303165
- }
303166
- return hits;
303167
- }
303168
- detectBoxLabel(boxComp, labelComp) {
303169
- const boxId = boxComp.schematicComponentId;
303170
- const labelId = labelComp.schematicComponentId;
303171
- if (!boxId || !labelId)
303172
- return [];
303173
- const labels = this.netLabelsByComponentId.get(labelId) ?? [];
303174
- if (labels.length === 0)
303175
- return [];
303176
- const boxBounds = centeredRect(boxComp.schX, boxComp.schY, boxComp.width, boxComp.height);
303177
- const boxIsLeft = boxComp.schX <= labelComp.schX;
303178
- const hits = [];
303179
- for (const label of labels) {
303180
- const labelBounds = this.getNetLabelBounds(label);
303181
- if (!rectOverlap(boxBounds, labelBounds))
303182
- continue;
303183
- let xSeparation;
303184
- if (boxIsLeft) {
303185
- xSeparation = boxBounds.right - labelBounds.left + 0.1;
303186
- } else {
303187
- xSeparation = labelBounds.right - boxBounds.left + 0.1;
303188
- }
303189
- hits.push({
303190
- type: "box-label",
303191
- boxComp,
303192
- labelComp,
303193
- boxId,
303194
- labelId,
303195
- xSeparation
303196
- });
303197
- }
303198
- return hits;
303199
- }
303200
- buildAndPushIssues() {
303201
- if (this.rawCollisions.length === 0)
303202
- return;
303203
- const globalFixes = this.computeGlobalFixes();
303204
- if (globalFixes.size === 0)
303205
- return;
303206
- const seenPairs = new Set;
303207
- const pairs3 = [];
303208
- for (const collision of this.rawCollisions) {
303209
- let comp1Name;
303210
- let comp2Name;
303211
- if (collision.type === "label-label") {
303212
- comp1Name = collision.leftComp.sourceComponentName ?? "";
303213
- comp2Name = collision.rightComp.sourceComponentName ?? "";
303214
- } else {
303215
- comp1Name = collision.boxComp.sourceComponentName ?? "";
303216
- comp2Name = collision.labelComp.sourceComponentName ?? "";
303217
- }
303218
- const key = `${comp1Name}/${comp2Name}`;
303219
- if (!seenPairs.has(key)) {
303220
- seenPairs.add(key);
303221
- pairs3.push({ comp1Name, comp2Name });
303222
- }
303223
- }
303224
- this.params.issues.push({
303225
- lineItemType: "NetLabelCollision",
303226
- pairs: pairs3,
303227
- moves: Array.from(globalFixes.values())
303228
- });
303229
- }
303230
- computeGlobalFixes() {
303231
- const compById = new Map;
303232
- for (const placement of this.placements) {
303233
- if (placement.schematicComponentId)
303234
- compById.set(placement.schematicComponentId, placement);
303235
- }
303236
- const constraintMap = new Map;
303237
- const addConstraint = (leftId, rightId, xSeparation) => {
303238
- const leftComp = compById.get(leftId);
303239
- const rightComp = compById.get(rightId);
303240
- if (!leftComp || !rightComp)
303241
- return;
303242
- const minSep = rightComp.schX - leftComp.schX + xSeparation;
303243
- const key = `${leftId}|${rightId}`;
303244
- constraintMap.set(key, Math.max(constraintMap.get(key) ?? 0, minSep));
303245
- };
303246
- for (const collision of this.rawCollisions) {
303247
- if (collision.type === "label-label") {
303248
- addConstraint(collision.leftId, collision.rightId, collision.xSeparation);
303249
- } else if (collision.boxComp.schX <= collision.labelComp.schX) {
303250
- addConstraint(collision.boxId, collision.labelId, collision.xSeparation);
303251
- } else {
303252
- addConstraint(collision.labelId, collision.boxId, collision.xSeparation);
303253
- }
303254
- }
303255
- const constraints = [...constraintMap.entries()].map(([key, minSep]) => {
303256
- const pipeIndex = key.indexOf("|");
303257
- return {
303258
- leftId: key.slice(0, pipeIndex),
303259
- rightId: key.slice(pipeIndex + 1),
303260
- minSep
303261
- };
303262
- });
303263
- const allIds = new Set;
303264
- const adjacency = new Map;
303265
- for (const { leftId, rightId } of constraints) {
303266
- allIds.add(leftId);
303267
- allIds.add(rightId);
303268
- if (!adjacency.has(leftId))
303269
- adjacency.set(leftId, new Set);
303270
- if (!adjacency.has(rightId))
303271
- adjacency.set(rightId, new Set);
303272
- adjacency.get(leftId).add(rightId);
303273
- adjacency.get(rightId).add(leftId);
303274
- }
303275
- const visited = new Set;
303276
- const result = new Map;
303277
- for (const startId of allIds) {
303278
- if (visited.has(startId))
303279
- continue;
303280
- const group = [];
303281
- const queue = [startId];
303282
- visited.add(startId);
303283
- while (queue.length) {
303284
- const compId = queue.shift();
303285
- group.push(compId);
303286
- for (const neighbor of adjacency.get(compId) ?? []) {
303287
- if (!visited.has(neighbor)) {
303288
- visited.add(neighbor);
303289
- queue.push(neighbor);
303290
- }
303291
- }
303292
- }
303293
- group.sort((idA, idB) => (compById.get(idA)?.schX ?? 0) - (compById.get(idB)?.schX ?? 0));
303294
- const groupIds = new Set(group);
303295
- const groupConstraints = constraints.filter((c3) => groupIds.has(c3.leftId) && groupIds.has(c3.rightId));
303296
- const assigned = new Map;
303297
- for (const compId of group) {
303298
- let newX = compById.get(compId)?.schX ?? 0;
303299
- for (const { leftId, rightId, minSep } of groupConstraints) {
303300
- if (rightId === compId && assigned.has(leftId)) {
303301
- newX = Math.max(newX, assigned.get(leftId) + minSep);
303302
- }
303303
- }
303304
- assigned.set(compId, newX);
303305
- }
303306
- const totalPush = [...assigned.entries()].reduce((sum, [compId, newX]) => sum + (newX - (compById.get(compId)?.schX ?? 0)), 0);
303307
- if (totalPush > 0.000000001) {
303308
- const pullBack = totalPush / group.length;
303309
- const shifted = new Map;
303310
- for (const compId of group) {
303311
- let newX = (compById.get(compId)?.schX ?? 0) - pullBack;
303312
- for (const { leftId, rightId, minSep } of groupConstraints) {
303313
- if (rightId === compId && shifted.has(leftId)) {
303314
- newX = Math.max(newX, shifted.get(leftId) + minSep);
303315
- }
303316
- }
303317
- shifted.set(compId, newX);
303318
- }
303319
- const maxDisplacement = (positions) => [...positions.entries()].reduce((currentMax, [compId, newX]) => Math.max(currentMax, Math.abs(newX - (compById.get(compId)?.schX ?? 0))), 0);
303320
- if (maxDisplacement(shifted) < maxDisplacement(assigned)) {
303321
- for (const [compId, newX] of shifted)
303322
- assigned.set(compId, newX);
303323
- }
303324
- }
303325
- for (const [compId, newX] of assigned) {
303326
- const comp = compById.get(compId);
303327
- if (!comp || Math.abs(newX - comp.schX) < 0.000000001)
303328
- continue;
303329
- result.set(compId, {
303330
- componentName: comp.sourceComponentName ?? compId,
303331
- newSchX: Math.round(newX * 100) / 100,
303332
- newSchY: Math.round(comp.schY * 100) / 100
303333
- });
303334
- }
303335
- }
303336
- return result;
303337
- }
303338
- buildNetLabelsByComponentId(circuitJson) {
303339
- const MATCH_EPSILON = 0.0001;
303340
- const portPositions = [];
303341
- for (const element of circuitJson) {
303342
- if (element.type !== "schematic_port")
303343
- continue;
303344
- const port = element;
303345
- if (!port.schematic_component_id)
303346
- continue;
303347
- portPositions.push({
303348
- componentId: port.schematic_component_id,
303349
- cx: port.center.x,
303350
- cy: port.center.y,
303351
- schematicSheetId: port.schematic_sheet_id
303352
- });
303353
- }
303354
- const result = new Map;
303355
- for (const element of circuitJson) {
303356
- if (element.type !== "schematic_net_label")
303357
- continue;
303358
- const label = element;
303359
- if (!label.anchor_position)
303360
- continue;
303361
- const { x: anchorX, y: anchorY } = label.anchor_position;
303362
- const matches = portPositions.filter((port) => port.schematicSheetId === label.schematic_sheet_id && Math.hypot(port.cx - anchorX, port.cy - anchorY) < MATCH_EPSILON);
303363
- if (matches.length === 0)
303364
- continue;
303365
- const componentIds = new Set(matches.map((match) => match.componentId));
303366
- if (componentIds.size !== 1)
303367
- continue;
303368
- const componentId = matches[0].componentId;
303369
- const labels = result.get(componentId) ?? [];
303370
- labels.push(label);
303371
- result.set(componentId, labels);
303372
- }
303373
- return result;
303374
- }
303375
- getNetLabelBounds(label) {
303376
- const anchorSide = label.anchor_side;
303377
- const isVertical3 = anchorSide === "top" || anchorSide === "bottom";
303378
- if (isVertical3) {
303379
- const anchorY = label.anchor_position?.y ?? label.center.y;
303380
- const textHalfExtent = (label.text?.length ?? 8) * this.SCH_CHAR_WIDTH / 2 + this.LABEL_BOUNDS_SLACK;
303381
- const left = label.center.x - this.LABEL_HALF_HEIGHT;
303382
- const right = label.center.x + this.LABEL_HALF_HEIGHT;
303383
- if (anchorSide === "top") {
303384
- return {
303385
- left,
303386
- right,
303387
- top: anchorY,
303388
- bottom: anchorY - textHalfExtent * 2
303389
- };
303390
- }
303391
- return { left, right, top: anchorY + textHalfExtent * 2, bottom: anchorY };
303392
- }
303393
- const anchorX = label.anchor_position?.x ?? label.center.x;
303394
- const halfWidth = Math.abs(label.center.x - anchorX);
303395
- const farHalfWidth = halfWidth + this.LABEL_BOUNDS_SLACK;
303396
- const top = label.center.y + this.LABEL_HALF_HEIGHT;
303397
- const bottom = label.center.y - this.LABEL_HALF_HEIGHT;
303398
- if (label.center.x >= anchorX) {
303399
- return {
303400
- left: anchorX,
303401
- right: label.center.x + farHalfWidth,
303402
- top,
303403
- bottom
303404
- };
303405
- }
303406
- return { left: label.center.x - farHalfWidth, right: anchorX, top, bottom };
303407
- }
303408
- static netLabelCollisionToString(issue) {
303409
- const pairAttrs = issue.pairs.map((pair, i2) => `pair${i2 + 1}="${pair.comp1Name}/${pair.comp2Name}"`).join(" ");
303410
- const moves = issue.moves.map((move) => ` <Move componentName="${move.componentName}" newSchX="${move.newSchX}" newSchY="${move.newSchY}" />`);
303411
- return [
303412
- `<ComponentNetLabelCollision ${pairAttrs}>`,
303413
- ` <SuggestedFix note="Apply all moves simultaneously. Set schAutoLayoutEnabled on your circuit.">`,
303414
- ...moves,
303415
- ` </SuggestedFix>`,
303416
- `</ComponentNetLabelCollision>`
303417
- ].join(`
303418
- `);
303419
- }
303420
- }
303421
-
303422
- // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/solvers/ComponentPinAlignmentSolver/ComponentPinAlignmentSolver.ts
303423
- import { BaseSolver as BaseSolver14 } from "@tscircuit/solver-utils";
303424
- class ComponentPinAlignmentSolver extends BaseSolver14 {
303425
- static ALIGNMENT_EPSILON = 0.01;
303426
- ctx;
303427
- out;
303428
- constructor({
303429
- ctx,
303430
- issues
303431
- }) {
303432
- super();
303433
- this.ctx = ctx;
303434
- this.out = issues;
303435
- }
303436
- _step() {
303437
- const portsById = new Map(this.ctx.circuitJson.filter((el3) => el3.type === "schematic_port").map((port) => [port.schematic_port_id, port]));
303438
- const placementBySchematicComponentId = new Map(this.ctx.componentPlacements.flatMap((placement) => placement.schematicComponentId ? [[placement.schematicComponentId, placement]] : []));
303439
- const pinPairsByComponentPair = new Map;
303440
- for (const trace of this.ctx.circuitJson.filter((el3) => el3.type === "schematic_trace")) {
303441
- const pinPair = this.getTracePinPair(trace, portsById);
303442
- if (!pinPair)
303443
- continue;
303444
- const firstComponentId = pinPair.firstPort.schematic_component_id;
303445
- const secondComponentId = pinPair.secondPort.schematic_component_id;
303446
- if (!firstComponentId || !secondComponentId || firstComponentId === secondComponentId) {
303447
- continue;
303448
- }
303449
- const orderedPair = firstComponentId < secondComponentId ? pinPair : {
303450
- firstPort: pinPair.secondPort,
303451
- secondPort: pinPair.firstPort
303452
- };
303453
- const key = [firstComponentId, secondComponentId].sort().join("\x00");
303454
- const pairs3 = pinPairsByComponentPair.get(key) ?? [];
303455
- const isDuplicate = pairs3.some((pair) => pair.firstPort.schematic_port_id === orderedPair.firstPort.schematic_port_id && pair.secondPort.schematic_port_id === orderedPair.secondPort.schematic_port_id);
303456
- if (!isDuplicate)
303457
- pairs3.push(orderedPair);
303458
- pinPairsByComponentPair.set(key, pairs3);
303459
- }
303460
- for (const pinPairs of pinPairsByComponentPair.values()) {
303461
- const issue = this.findVerticalShiftIssue(pinPairs, placementBySchematicComponentId);
303462
- if (issue)
303463
- this.out.push(issue);
303464
- }
303465
- this.solved = true;
303466
- }
303467
- getTracePinPair(trace, portsById) {
303468
- const firstEdge = trace.edges?.[0];
303469
- const lastEdge = trace.edges?.at(-1);
303470
- if (!firstEdge || !lastEdge)
303471
- return;
303472
- const firstPortId = firstEdge.from_schematic_port_id ?? firstEdge.to_schematic_port_id;
303473
- const secondPortId = lastEdge.to_schematic_port_id ?? lastEdge.from_schematic_port_id;
303474
- const ports = [...portsById.values()];
303475
- const firstPort = firstPortId ? portsById.get(firstPortId) : this.findPortAtPoint(ports, firstEdge.from, trace.schematic_sheet_id);
303476
- const secondPort = secondPortId ? portsById.get(secondPortId) : this.findPortAtPoint(ports, lastEdge.to, trace.schematic_sheet_id);
303477
- if (!firstPort || !secondPort)
303478
- return;
303479
- if (firstPort.schematic_port_id === secondPort.schematic_port_id)
303480
- return;
303481
- if (firstPort.schematic_sheet_id !== secondPort.schematic_sheet_id)
303482
- return;
303483
- return { firstPort, secondPort };
303484
- }
303485
- findPortAtPoint(ports, point9, schematicSheetId) {
303486
- const { ALIGNMENT_EPSILON } = ComponentPinAlignmentSolver;
303487
- return ports.find((port) => port.schematic_sheet_id === schematicSheetId && Math.abs(port.center.x - point9.x) <= ALIGNMENT_EPSILON && Math.abs(port.center.y - point9.y) <= ALIGNMENT_EPSILON);
303488
- }
303489
- findVerticalShiftIssue(pinPairs, placementBySchematicComponentId) {
303490
- const firstPair = pinPairs[0];
303491
- if (!firstPair)
303492
- return;
303493
- const firstPlacement = placementBySchematicComponentId.get(firstPair.firstPort.schematic_component_id);
303494
- const secondPlacement = placementBySchematicComponentId.get(firstPair.secondPort.schematic_component_id);
303495
- if (!firstPlacement || !secondPlacement)
303496
- return;
303497
- const [leftPlacement, rightPlacement] = firstPlacement.schX <= secondPlacement.schX ? [firstPlacement, secondPlacement] : [secondPlacement, firstPlacement];
303498
- const horizontalPairs = pinPairs.flatMap((pair) => {
303499
- const [leftPort, rightPort] = pair.firstPort.center.x <= pair.secondPort.center.x ? [pair.firstPort, pair.secondPort] : [pair.secondPort, pair.firstPort];
303500
- return leftPort.facing_direction === "right" && rightPort.facing_direction === "left" ? [{ leftPort, rightPort }] : [];
303501
- });
303502
- if (horizontalPairs.length < 2)
303503
- return;
303504
- const { ALIGNMENT_EPSILON } = ComponentPinAlignmentSolver;
303505
- const currentlyAlignedPinCount = horizontalPairs.filter(({ leftPort, rightPort }) => Math.abs(leftPort.center.y - rightPort.center.y) <= ALIGNMENT_EPSILON).length;
303506
- const candidateGroups = [];
303507
- for (const pair of horizontalPairs) {
303508
- const deltaSchY2 = pair.leftPort.center.y - pair.rightPort.center.y;
303509
- if (Math.abs(deltaSchY2) <= ALIGNMENT_EPSILON)
303510
- continue;
303511
- const group = candidateGroups.find((candidate) => Math.abs(candidate.deltaSchY - deltaSchY2) <= ALIGNMENT_EPSILON);
303512
- if (group)
303513
- group.pairs.push(pair);
303514
- else
303515
- candidateGroups.push({ deltaSchY: deltaSchY2, pairs: [pair] });
303516
- }
303517
- const bestCandidate = candidateGroups.sort((a2, b3) => b3.pairs.length - a2.pairs.length || Math.abs(a2.deltaSchY) - Math.abs(b3.deltaSchY))[0];
303518
- if (!bestCandidate || bestCandidate.pairs.length < 2 || bestCandidate.pairs.length <= currentlyAlignedPinCount) {
303519
- return;
303520
- }
303521
- const targetName = rightPlacement.sourceComponentName ?? rightPlacement.schematicComponentId ?? "component";
303522
- const deltaSchY = bestCandidate.deltaSchY;
303523
- const newSchY = rightPlacement.schY + deltaSchY;
303524
- return {
303525
- lineItemType: "ComponentPinsWouldAlignWithVerticalShift",
303526
- firstComponent: leftPlacement,
303527
- secondComponent: rightPlacement,
303528
- targetComponent: rightPlacement,
303529
- deltaSchY,
303530
- newSchY,
303531
- currentlyAlignedPinCount,
303532
- alignedPinCount: bestCandidate.pairs.length,
303533
- alignedPinPairs: bestCandidate.pairs.map(({ leftPort, rightPort }) => ({
303534
- firstPin: this.getPinLabel(leftPort),
303535
- secondPin: this.getPinLabel(rightPort)
303536
- })),
303537
- message: `shift ${targetName} vertically by ${fmtDelta(deltaSchY)} to align ${bestCandidate.pairs.length} connected pin pairs`
303538
- };
303539
- }
303540
- getPinLabel(port) {
303541
- return port.display_pin_label ?? port.pin_number?.toString();
303542
- }
303543
- static issueToString(issue) {
303544
- const attrs = [];
303545
- addAttr(attrs, "firstComponentName", issue.firstComponent.sourceComponentName);
303546
- addAttr(attrs, "secondComponentName", issue.secondComponent.sourceComponentName);
303547
- addAttr(attrs, "targetComponentName", issue.targetComponent.sourceComponentName);
303548
- addAttr(attrs, "deltaSchY", issue.deltaSchY);
303549
- addAttr(attrs, "newSchY", issue.newSchY);
303550
- addAttr(attrs, "currentlyAlignedPinCount", issue.currentlyAlignedPinCount);
303551
- addAttr(attrs, "alignedPinCount", issue.alignedPinCount);
303552
- addAttr(attrs, "message", issue.message);
303553
- return `<ComponentPinsWouldAlignWithVerticalShift ${attrs.join(" ")} />`;
303554
- }
303555
- }
303556
-
303557
303811
  // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/solvers/SchematicPlacementPipeline/SchematicPlacementPipeline.ts
303558
303812
  class SchematicPlacementPipeline extends BasePipelineSolver2 {
303559
303813
  ctx;
@@ -303583,6 +303837,9 @@ class SchematicPlacementPipeline extends BasePipelineSolver2 {
303583
303837
  definePipelineStep3("ComponentPinAlignmentSolver", ComponentPinAlignmentSolver, (p3) => [
303584
303838
  { ctx: p3.ctx, issues: p3.issues }
303585
303839
  ]),
303840
+ definePipelineStep3("TraceSimplificationSolver", TraceSimplificationSolver, (p3) => [
303841
+ { ctx: p3.ctx, issues: p3.issues }
303842
+ ]),
303586
303843
  definePipelineStep3("ComponentNetLabelCollisionSolver", ComponentNetLabelCollisionSolver, (p3) => [
303587
303844
  { ctx: p3.ctx, issues: p3.issues }
303588
303845
  ])
@@ -303598,11 +303855,153 @@ class SchematicPlacementPipeline extends BasePipelineSolver2 {
303598
303855
  }
303599
303856
  }
303600
303857
 
303858
+ // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/utils/issue-context.ts
303859
+ var POSITION_EPSILON = 0.01;
303860
+ var getRelevantPlacementsForIssues = ({
303861
+ issues,
303862
+ componentPlacements,
303863
+ circuitJson
303864
+ }) => {
303865
+ const relevantPlacements = new Set;
303866
+ const placementByComponentId = new Map(componentPlacements.flatMap((placement) => placement.schematicComponentId ? [[placement.schematicComponentId, placement]] : []));
303867
+ const placementsByName = new Map;
303868
+ for (const placement of componentPlacements) {
303869
+ if (!placement.sourceComponentName)
303870
+ continue;
303871
+ const namedPlacements = placementsByName.get(placement.sourceComponentName);
303872
+ if (namedPlacements)
303873
+ namedPlacements.push(placement);
303874
+ else
303875
+ placementsByName.set(placement.sourceComponentName, [placement]);
303876
+ }
303877
+ const addPlacement = (placement) => {
303878
+ if (!placement)
303879
+ return;
303880
+ const canonicalPlacement = placement.schematicComponentId ? placementByComponentId.get(placement.schematicComponentId) : componentPlacements.find((candidate) => candidate.sourceComponentId === placement.sourceComponentId && candidate.schX === placement.schX && candidate.schY === placement.schY);
303881
+ if (canonicalPlacement)
303882
+ relevantPlacements.add(canonicalPlacement);
303883
+ };
303884
+ const addComponentName = (componentName, schematicSheetId) => {
303885
+ if (!componentName)
303886
+ return;
303887
+ for (const placement of placementsByName.get(componentName) ?? []) {
303888
+ if (schematicSheetId === undefined || placement.schematicSheetId === schematicSheetId) {
303889
+ relevantPlacements.add(placement);
303890
+ }
303891
+ }
303892
+ };
303893
+ for (const issue of issues) {
303894
+ switch (issue.lineItemType) {
303895
+ case "ComponentOverlap":
303896
+ addPlacement(issue.firstComponent);
303897
+ addPlacement(issue.secondComponent);
303898
+ break;
303899
+ case "SchematicBoxHasALotOfSurroundingWhitespace":
303900
+ case "CapacitorSymbolHorizontal":
303901
+ case "PinHeaderSchematicBoxTooWide":
303902
+ case "GenericSchematicBoxTooWide":
303903
+ case "SchematicBoxInnerLabelCollision":
303904
+ case "SchematicPinPaddingToEdgeTooLarge":
303905
+ addPlacement(issue.schematicBox);
303906
+ break;
303907
+ case "VerboseSchematicNetLabel":
303908
+ for (const pin of issue.involvedPins) {
303909
+ addComponentName(getComponentNameFromPin(pin), issue.schematicSheetId);
303910
+ }
303911
+ break;
303912
+ case "DiodeResistorNotAligned":
303913
+ addPlacement(issue.diodeSchematicBox);
303914
+ addPlacement(issue.resistorSchematicBox);
303915
+ break;
303916
+ case "ComponentPinsWouldAlignWithVerticalShift":
303917
+ addPlacement(issue.firstComponent);
303918
+ addPlacement(issue.secondComponent);
303919
+ addPlacement(issue.targetComponent);
303920
+ break;
303921
+ case "TraceCanBeSimplifiedByMovingComponent":
303922
+ addPlacement(issue.targetComponent);
303923
+ for (const placement of getTraceEndpointPlacements({
303924
+ schematicTraceId: issue.schematicTraceId,
303925
+ circuitJson,
303926
+ placementByComponentId
303927
+ })) {
303928
+ relevantPlacements.add(placement);
303929
+ }
303930
+ break;
303931
+ case "ComponentNetLabelCollision":
303932
+ addPlacement(issue.firstComponent);
303933
+ addPlacement(issue.secondComponent);
303934
+ break;
303935
+ case "ComponentBoxNetLabelCollision":
303936
+ addPlacement(issue.boxComponent);
303937
+ addPlacement(issue.labelComponent);
303938
+ break;
303939
+ case "NetLabelCollision":
303940
+ for (const pair of issue.pairs) {
303941
+ addComponentName(pair.comp1Name, issue.schematicSheetId);
303942
+ addComponentName(pair.comp2Name, issue.schematicSheetId);
303943
+ }
303944
+ for (const move of issue.moves) {
303945
+ addComponentName(move.componentName, issue.schematicSheetId);
303946
+ }
303947
+ break;
303948
+ }
303949
+ }
303950
+ return componentPlacements.filter((placement) => relevantPlacements.has(placement));
303951
+ };
303952
+ var getIssueSchematicSheetContext = (issue) => {
303953
+ if ("schematicSheetId" in issue || "schematicSheetName" in issue) {
303954
+ const schematicSheetId = "schematicSheetId" in issue && typeof issue.schematicSheetId === "string" ? issue.schematicSheetId : undefined;
303955
+ const schematicSheetName = "schematicSheetName" in issue && typeof issue.schematicSheetName === "string" ? issue.schematicSheetName : undefined;
303956
+ if (schematicSheetId || schematicSheetName) {
303957
+ return { schematicSheetId, schematicSheetName };
303958
+ }
303959
+ }
303960
+ for (const value of Object.values(issue)) {
303961
+ if (isSchematicBoxPlacement(value)) {
303962
+ return {
303963
+ schematicSheetId: value.schematicSheetId,
303964
+ schematicSheetName: value.schematicSheetName
303965
+ };
303966
+ }
303967
+ }
303968
+ return {};
303969
+ };
303970
+ var getComponentNameFromPin = (pin) => {
303971
+ const separatorIndex = pin.lastIndexOf(".");
303972
+ return separatorIndex === -1 ? pin : pin.slice(0, separatorIndex);
303973
+ };
303974
+ var isSchematicBoxPlacement = (value) => Boolean(value && typeof value === "object" && "positionAnchor" in value && value.positionAnchor === "center" && "schX" in value && "schY" in value && "width" in value && "height" in value);
303975
+ var getTraceEndpointPlacements = ({
303976
+ schematicTraceId,
303977
+ circuitJson,
303978
+ placementByComponentId
303979
+ }) => {
303980
+ const trace = circuitJson.find((element) => element.type === "schematic_trace" && element.schematic_trace_id === schematicTraceId);
303981
+ const firstEdge = trace?.edges[0];
303982
+ const lastEdge = trace?.edges.at(-1);
303983
+ if (!trace || !firstEdge || !lastEdge)
303984
+ return [];
303985
+ const ports = circuitJson.filter((element) => element.type === "schematic_port");
303986
+ const portsById = new Map(ports.map((port) => [port.schematic_port_id, port]));
303987
+ const endpointPorts = [
303988
+ firstEdge.from_schematic_port_id ? portsById.get(firstEdge.from_schematic_port_id) : findPortAtPoint(ports, firstEdge.from, trace.schematic_sheet_id),
303989
+ lastEdge.to_schematic_port_id ? portsById.get(lastEdge.to_schematic_port_id) : findPortAtPoint(ports, lastEdge.to, trace.schematic_sheet_id)
303990
+ ];
303991
+ return endpointPorts.flatMap((port) => {
303992
+ const placement = port?.schematic_component_id ? placementByComponentId.get(port.schematic_component_id) : undefined;
303993
+ return placement ? [placement] : [];
303994
+ });
303995
+ };
303996
+ var findPortAtPoint = (ports, point9, schematicSheetId) => ports.find((port) => port.schematic_sheet_id === schematicSheetId && Math.abs(port.center.x - point9.x) <= POSITION_EPSILON && Math.abs(port.center.y - point9.y) <= POSITION_EPSILON);
303997
+
303601
303998
  // node_modules/@tscircuit/circuit-json-schematic-placement-analysis/lib/analyze-schematic-placement.ts
303602
303999
  class SchematicPlacementAnalysis {
303603
304000
  lineItems;
303604
- constructor(lineItems) {
304001
+ groupBySchematicSheet;
304002
+ constructor(lineItems, groupBySchematicSheet = false) {
303605
304003
  this.lineItems = lineItems;
304004
+ this.groupBySchematicSheet = groupBySchematicSheet;
303606
304005
  }
303607
304006
  getLineItems() {
303608
304007
  return this.lineItems;
@@ -303618,7 +304017,6 @@ class SchematicPlacementAnalysis {
303618
304017
  addAttr(attrs, "schY", lineItem.schY);
303619
304018
  addAttr(attrs, "width", lineItem.width);
303620
304019
  addAttr(attrs, "height", lineItem.height);
303621
- addAttr(attrs, "schSheetName", lineItem.schematicSheetName);
303622
304020
  return `<SchematicBoxPlacement ${attrs.join(" ")} />`;
303623
304021
  }
303624
304022
  schematicIssuesToString(issue) {
@@ -303640,6 +304038,8 @@ class SchematicPlacementAnalysis {
303640
304038
  return DiodeResistorAlignmentSolver.issueToString(issue);
303641
304039
  case "ComponentPinsWouldAlignWithVerticalShift":
303642
304040
  return ComponentPinAlignmentSolver.issueToString(issue);
304041
+ case "TraceCanBeSimplifiedByMovingComponent":
304042
+ return TraceSimplificationSolver.issueToString(issue);
303643
304043
  case "NetLabelCollision":
303644
304044
  return ComponentNetLabelCollisionSolver.netLabelCollisionToString(issue);
303645
304045
  default:
@@ -303649,28 +304049,71 @@ class SchematicPlacementAnalysis {
303649
304049
  toString() {
303650
304050
  const schematicBoxPlacements = this.lineItems.filter((lineItem) => lineItem.lineItemType === "SchematicBoxPlacement");
303651
304051
  const issues = this.lineItems.flatMap((lineItem) => lineItem.lineItemType === "SchematicPlacementIssues" ? lineItem.issues : []);
303652
- return [
303653
- "<SchematicBoxPositions>",
303654
- ...schematicBoxPlacements.map(this.schematicBoxPlacementsToString),
303655
- "</SchematicBoxPositions>",
303656
- ...issues.length > 0 ? [
303657
- "<SchematicPlacementIssues>",
303658
- ...issues.map(this.schematicIssuesToString),
303659
- "</SchematicPlacementIssues>"
303660
- ] : []
303661
- ].join(`
304052
+ if (issues.length === 0)
304053
+ return "";
304054
+ if (!this.groupBySchematicSheet) {
304055
+ return this.issueContextToLines(schematicBoxPlacements, issues).join(`
304056
+ `);
304057
+ }
304058
+ const groups = new Map;
304059
+ const getOrCreateGroup = (context) => {
304060
+ const key = context.schematicSheetId ?? context.schematicSheetName ?? "__default__";
304061
+ const existingGroup = groups.get(key);
304062
+ if (existingGroup)
304063
+ return existingGroup;
304064
+ const group = { ...context, placements: [], issues: [] };
304065
+ groups.set(key, group);
304066
+ return group;
304067
+ };
304068
+ for (const issue of issues) {
304069
+ getOrCreateGroup(getIssueSchematicSheetContext(issue)).issues.push(issue);
304070
+ }
304071
+ for (const placement of schematicBoxPlacements) {
304072
+ getOrCreateGroup({
304073
+ schematicSheetId: placement.schematicSheetId,
304074
+ schematicSheetName: placement.schematicSheetName
304075
+ }).placements.push(placement);
304076
+ }
304077
+ return [...groups.values()].flatMap((group) => {
304078
+ const sheetAttrs = [];
304079
+ addAttr(sheetAttrs, "name", group.schematicSheetName);
304080
+ addAttr(sheetAttrs, "id", group.schematicSheetId);
304081
+ return [
304082
+ `<SchematicSheet${sheetAttrs.length > 0 ? ` ${sheetAttrs.join(" ")}` : ""}>`,
304083
+ ...this.issueContextToLines(group.placements, group.issues),
304084
+ "</SchematicSheet>"
304085
+ ];
304086
+ }).join(`
303662
304087
  `);
303663
304088
  }
304089
+ issueContextToLines(placements, issues) {
304090
+ return [
304091
+ ...placements.length > 0 ? [
304092
+ "<SchematicBoxPositions>",
304093
+ ...placements.map(this.schematicBoxPlacementsToString),
304094
+ "</SchematicBoxPositions>"
304095
+ ] : [],
304096
+ "<SchematicPlacementIssues>",
304097
+ ...issues.map(this.schematicIssuesToString),
304098
+ "</SchematicPlacementIssues>"
304099
+ ];
304100
+ }
303664
304101
  }
303665
304102
  var analyzeSchematicPlacement = (circuitJson) => {
303666
304103
  const pipeline = new SchematicPlacementPipeline(circuitJson);
303667
304104
  pipeline.solve();
303668
304105
  const { issues, componentPlacements } = pipeline.getOutput();
304106
+ const relevantPlacements = getRelevantPlacementsForIssues({
304107
+ issues,
304108
+ componentPlacements,
304109
+ circuitJson
304110
+ });
303669
304111
  const lineItems = [
303670
- ...componentPlacements,
304112
+ ...relevantPlacements,
303671
304113
  ...issues.length > 0 ? [{ lineItemType: "SchematicPlacementIssues", issues }] : []
303672
304114
  ];
303673
- return new SchematicPlacementAnalysis(lineItems);
304115
+ const schematicSheetIds = new Set(circuitJson.flatMap((element) => ("schematic_sheet_id" in element) && typeof element.schematic_sheet_id === "string" ? [element.schematic_sheet_id] : []));
304116
+ return new SchematicPlacementAnalysis(lineItems, schematicSheetIds.size > 1);
303674
304117
  };
303675
304118
  // cli/check/schematic-placement/register.ts
303676
304119
  var checkSchematicPlacement = async (file) => {