@tscircuit/core 0.0.1280 → 0.0.1282

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -78583,6 +78583,7 @@ declare class SmtPad extends PrimitiveComponent<typeof smtPadProps> {
78583
78583
  doInitialPortMatching(): void;
78584
78584
  doInitialPcbPrimitiveRender(): void;
78585
78585
  doInitialPcbPortAttachment(): void;
78586
+ getAvailablePcbLayers(): string[];
78586
78587
  _getPcbCircuitJsonBounds(): {
78587
78588
  center: {
78588
78589
  x: number;
package/dist/index.js CHANGED
@@ -3843,7 +3843,7 @@ var Trace_doInitialSchematicTraceRender = (trace) => {
3843
3843
  elbowEdges.push({ from: path[j], to: path[j + 1] });
3844
3844
  }
3845
3845
  }
3846
- const doesSegmentIntersectRect = (edge, rect) => {
3846
+ const doesSegmentIntersectRect2 = (edge, rect) => {
3847
3847
  const halfW = rect.width / 2;
3848
3848
  const halfH = rect.height / 2;
3849
3849
  const left = rect.center.x - halfW;
@@ -3876,7 +3876,7 @@ var Trace_doInitialSchematicTraceRender = (trace) => {
3876
3876
  };
3877
3877
  for (const edge of elbowEdges) {
3878
3878
  for (const obstacle of obstacles) {
3879
- if (doesSegmentIntersectRect(edge, obstacle)) {
3879
+ if (doesSegmentIntersectRect2(edge, obstacle)) {
3880
3880
  return null;
3881
3881
  }
3882
3882
  }
@@ -8191,7 +8191,10 @@ var SmtPad = class extends PrimitiveComponent2 {
8191
8191
  const isRotated90Degrees = Math.abs(normalizedRotationDegrees - 90) < rotationTolerance || Math.abs(normalizedRotationDegrees - 270) < rotationTolerance;
8192
8192
  let finalRotationDegrees = Math.abs(normalizedRotationDegrees - 360) < rotationTolerance ? 0 : normalizedRotationDegrees;
8193
8193
  const transformRotationBeforeFlip = finalRotationDegrees;
8194
- const { maybeFlipLayer, isFlipped } = this._getPcbPrimitiveFlippedHelpers();
8194
+ const { maybeFlipLayer: maybeFlipDefaultLayer, isFlipped } = this._getPcbPrimitiveFlippedHelpers();
8195
+ const primitiveContainer = this.getPrimitiveContainer();
8196
+ const primitiveContainerLayer = primitiveContainer?.props?.layer !== void 0 ? primitiveContainer?._parsedProps.layer : void 0;
8197
+ const maybeFlipLayer = (layer) => primitiveContainerLayer ?? maybeFlipDefaultLayer(layer);
8195
8198
  if (isFlipped) {
8196
8199
  finalRotationDegrees = (360 - finalRotationDegrees + 360) % 360;
8197
8200
  }
@@ -8403,6 +8406,14 @@ var SmtPad = class extends PrimitiveComponent2 {
8403
8406
  pcb_port_id: this.matchedPort?.pcb_port_id
8404
8407
  });
8405
8408
  }
8409
+ getAvailablePcbLayers() {
8410
+ const { maybeFlipLayer } = this._getPcbPrimitiveFlippedHelpers();
8411
+ const primitiveContainer = this.getPrimitiveContainer();
8412
+ const primitiveContainerLayer = primitiveContainer?.props?.layer !== void 0 ? primitiveContainer?._parsedProps.layer : void 0;
8413
+ return [
8414
+ primitiveContainerLayer ?? maybeFlipLayer(this._parsedProps.layer ?? "top")
8415
+ ];
8416
+ }
8406
8417
  _getPcbCircuitJsonBounds() {
8407
8418
  const { db } = this.root;
8408
8419
  const smtpad = db.pcb_smtpad.get(this.pcb_smtpad_id);
@@ -21856,7 +21867,7 @@ import { identity as identity5 } from "transformation-matrix";
21856
21867
  var package_default = {
21857
21868
  name: "@tscircuit/core",
21858
21869
  type: "module",
21859
- version: "0.0.1279",
21870
+ version: "0.0.1281",
21860
21871
  types: "dist/index.d.ts",
21861
21872
  main: "dist/index.js",
21862
21873
  module: "dist/index.js",
@@ -24855,7 +24866,733 @@ var PcbNoteDimension = class extends PrimitiveComponent2 {
24855
24866
 
24856
24867
  // lib/components/primitive-components/Breakout/Breakout.ts
24857
24868
  import "@tscircuit/props";
24858
- import { BreakoutPointSolver } from "@tscircuit/breakout-point-solver";
24869
+
24870
+ // node_modules/@tscircuit/breakout-point-solver/lib/BreakoutPointSolver.ts
24871
+ import { BaseSolver } from "@tscircuit/solver-utils";
24872
+
24873
+ // node_modules/@tscircuit/breakout-point-solver/lib/boundary/get-breakout-boundary-intersection.ts
24874
+ import {
24875
+ distance as distance14,
24876
+ getSegmentIntersection
24877
+ } from "@tscircuit/math-utils";
24878
+ var getBreakoutBoundaryIntersection = ({
24879
+ from,
24880
+ to,
24881
+ bounds
24882
+ }) => {
24883
+ if (from.x === to.x && from.y === to.y) return null;
24884
+ const boundarySegments = [
24885
+ [
24886
+ { x: bounds.minX, y: bounds.minY },
24887
+ { x: bounds.maxX, y: bounds.minY }
24888
+ ],
24889
+ [
24890
+ { x: bounds.maxX, y: bounds.minY },
24891
+ { x: bounds.maxX, y: bounds.maxY }
24892
+ ],
24893
+ [
24894
+ { x: bounds.maxX, y: bounds.maxY },
24895
+ { x: bounds.minX, y: bounds.maxY }
24896
+ ],
24897
+ [
24898
+ { x: bounds.minX, y: bounds.maxY },
24899
+ { x: bounds.minX, y: bounds.minY }
24900
+ ]
24901
+ ];
24902
+ const candidates = boundarySegments.map(([start, end]) => getSegmentIntersection(from, to, start, end)).filter((point6) => point6 !== null);
24903
+ candidates.sort((a, b) => distance14(from, a) - distance14(from, b));
24904
+ return candidates[0] ?? null;
24905
+ };
24906
+
24907
+ // node_modules/@tscircuit/breakout-point-solver/lib/boundary/get-available-breakout-boundary-point.ts
24908
+ import { distance as distance15 } from "@tscircuit/math-utils";
24909
+
24910
+ // node_modules/@tscircuit/breakout-point-solver/lib/pad/breakout-pad-collisions.ts
24911
+ import {
24912
+ doesSegmentIntersectRect,
24913
+ getBoundFromCenteredRect
24914
+ } from "@tscircuit/math-utils";
24915
+ var degreesToRadians = (degrees) => degrees * Math.PI / 180;
24916
+ var rotatePoint = (point6, radians) => {
24917
+ const cos = Math.cos(radians);
24918
+ const sin = Math.sin(radians);
24919
+ return {
24920
+ x: point6.x * cos - point6.y * sin,
24921
+ y: point6.x * sin + point6.y * cos
24922
+ };
24923
+ };
24924
+ var getLocalPadPoint = (point6, pad) => {
24925
+ const translatedPoint = {
24926
+ x: point6.x - pad.center.x,
24927
+ y: point6.y - pad.center.y
24928
+ };
24929
+ return rotatePoint(
24930
+ translatedPoint,
24931
+ -degreesToRadians(pad.ccwRotationDegrees ?? 0)
24932
+ );
24933
+ };
24934
+ var getInflatedPadRect = (pad) => {
24935
+ const clearance = pad.clearance ?? 0;
24936
+ return getBoundFromCenteredRect({
24937
+ center: { x: 0, y: 0 },
24938
+ width: pad.width + clearance * 2,
24939
+ height: pad.height + clearance * 2
24940
+ });
24941
+ };
24942
+ var isBreakoutPadIgnoredForSourcePort = ({
24943
+ pad,
24944
+ sourcePortId
24945
+ }) => {
24946
+ if (!pad.sourcePortIds) return false;
24947
+ return pad.sourcePortIds.includes(sourcePortId);
24948
+ };
24949
+ var isBreakoutPadIgnoredForLayer = ({
24950
+ pad,
24951
+ layer
24952
+ }) => {
24953
+ if (!pad.layer || !layer) return false;
24954
+ return pad.layer !== layer;
24955
+ };
24956
+ var doesBreakoutSegmentIntersectPad = ({
24957
+ from,
24958
+ to,
24959
+ pad
24960
+ }) => doesSegmentIntersectRect(
24961
+ getLocalPadPoint(from, pad),
24962
+ getLocalPadPoint(to, pad),
24963
+ getInflatedPadRect(pad)
24964
+ );
24965
+ var doesBreakoutSegmentIntersectNonIgnoredPads = ({
24966
+ from,
24967
+ to,
24968
+ pads,
24969
+ ignoredSourcePortIds,
24970
+ layer
24971
+ }) => {
24972
+ for (const pad of pads) {
24973
+ if (ignoredSourcePortIds.some(
24974
+ (sourcePortId) => isBreakoutPadIgnoredForSourcePort({
24975
+ pad,
24976
+ sourcePortId
24977
+ })
24978
+ ) || isBreakoutPadIgnoredForLayer({
24979
+ pad,
24980
+ layer
24981
+ })) {
24982
+ continue;
24983
+ }
24984
+ if (doesBreakoutSegmentIntersectPad({ from, to, pad })) {
24985
+ return true;
24986
+ }
24987
+ }
24988
+ return false;
24989
+ };
24990
+ var doesBreakoutSegmentIntersectPads = ({
24991
+ from,
24992
+ to,
24993
+ pads,
24994
+ sourcePortId,
24995
+ layer
24996
+ }) => doesBreakoutSegmentIntersectNonIgnoredPads({
24997
+ from,
24998
+ to,
24999
+ pads,
25000
+ ignoredSourcePortIds: [sourcePortId],
25001
+ layer
25002
+ });
25003
+
25004
+ // node_modules/@tscircuit/breakout-point-solver/lib/boundary/get-available-breakout-boundary-point.ts
25005
+ var BOUNDARY_POINT_DISTANCE_TOLERANCE = 1e-6;
25006
+ var isInsideRequiredSpacing = ({
25007
+ candidate,
25008
+ usedPoint,
25009
+ boundaryPointSpacing
25010
+ }) => distance15(usedPoint, candidate) < boundaryPointSpacing - BOUNDARY_POINT_DISTANCE_TOLERANCE;
25011
+ var getBoundsEdge = (point6, bounds) => {
25012
+ if (Math.abs(point6.x - bounds.minX) < BOUNDARY_POINT_DISTANCE_TOLERANCE)
25013
+ return "left";
25014
+ if (Math.abs(point6.x - bounds.maxX) < BOUNDARY_POINT_DISTANCE_TOLERANCE)
25015
+ return "right";
25016
+ if (Math.abs(point6.y - bounds.minY) < BOUNDARY_POINT_DISTANCE_TOLERANCE)
25017
+ return "bottom";
25018
+ if (Math.abs(point6.y - bounds.maxY) < BOUNDARY_POINT_DISTANCE_TOLERANCE)
25019
+ return "top";
25020
+ return null;
25021
+ };
25022
+ var getBoundsEdgeCandidates = ({
25023
+ edge,
25024
+ bounds,
25025
+ step
25026
+ }) => {
25027
+ const candidates = [];
25028
+ if (edge === "left" || edge === "right") {
25029
+ const x = edge === "left" ? bounds.minX : bounds.maxX;
25030
+ for (let y = bounds.minY; y <= bounds.maxY + step / 2; y += step) {
25031
+ candidates.push({ x, y: Math.min(y, bounds.maxY) });
25032
+ }
25033
+ } else {
25034
+ const y = edge === "bottom" ? bounds.minY : bounds.maxY;
25035
+ for (let x = bounds.minX; x <= bounds.maxX + step / 2; x += step) {
25036
+ candidates.push({ x: Math.min(x, bounds.maxX), y });
25037
+ }
25038
+ }
25039
+ return candidates;
25040
+ };
25041
+ var getAllBoundsCandidates = ({
25042
+ bounds,
25043
+ step
25044
+ }) => {
25045
+ const candidatesByCoordinate = /* @__PURE__ */ new Map();
25046
+ for (const edge of ["left", "right", "bottom", "top"]) {
25047
+ for (const candidate of getBoundsEdgeCandidates({ edge, bounds, step })) {
25048
+ candidatesByCoordinate.set(`${candidate.x}:${candidate.y}`, candidate);
25049
+ }
25050
+ }
25051
+ return [...candidatesByCoordinate.values()];
25052
+ };
25053
+ var getBoundaryCandidateSearchStep = ({
25054
+ bounds,
25055
+ boundaryPointSpacing
25056
+ }) => {
25057
+ if (boundaryPointSpacing > 0) return boundaryPointSpacing;
25058
+ return Math.min(bounds.maxX - bounds.minX, bounds.maxY - bounds.minY) / 40;
25059
+ };
25060
+ var hasBoundarySpacingConflict = ({
25061
+ candidate,
25062
+ usedBoundaryPoints,
25063
+ boundaryPointSpacing
25064
+ }) => {
25065
+ for (const usedPoint of usedBoundaryPoints) {
25066
+ if (isInsideRequiredSpacing({
25067
+ candidate,
25068
+ usedPoint,
25069
+ boundaryPointSpacing
25070
+ })) {
25071
+ return true;
25072
+ }
25073
+ }
25074
+ return false;
25075
+ };
25076
+ var isBoundaryCandidateBlocked = ({
25077
+ candidate,
25078
+ routeFrom,
25079
+ pads,
25080
+ sourcePortId,
25081
+ layer
25082
+ }) => {
25083
+ if (!routeFrom || !pads || !sourcePortId) return false;
25084
+ return doesBreakoutSegmentIntersectPads({
25085
+ from: routeFrom,
25086
+ to: candidate,
25087
+ pads,
25088
+ sourcePortId,
25089
+ layer
25090
+ });
25091
+ };
25092
+ var isCandidateAvailable = ({
25093
+ candidate,
25094
+ usedBoundaryPoints,
25095
+ boundaryPointSpacing,
25096
+ routeFrom,
25097
+ pads,
25098
+ sourcePortId,
25099
+ layer
25100
+ }) => {
25101
+ if (hasBoundarySpacingConflict({
25102
+ candidate,
25103
+ usedBoundaryPoints,
25104
+ boundaryPointSpacing
25105
+ })) {
25106
+ return false;
25107
+ }
25108
+ return !isBoundaryCandidateBlocked({
25109
+ candidate,
25110
+ routeFrom,
25111
+ pads,
25112
+ sourcePortId,
25113
+ layer
25114
+ });
25115
+ };
25116
+ var hasOutsideAccessConflict = ({
25117
+ candidate,
25118
+ outsidePorts,
25119
+ pads,
25120
+ sourcePortId,
25121
+ layer
25122
+ }) => {
25123
+ if (!outsidePorts || !pads || !sourcePortId) return false;
25124
+ for (const outsidePort of outsidePorts) {
25125
+ if (doesBreakoutSegmentIntersectNonIgnoredPads({
25126
+ from: candidate,
25127
+ to: outsidePort.position,
25128
+ pads,
25129
+ ignoredSourcePortIds: [sourcePortId, outsidePort.sourcePortId],
25130
+ layer
25131
+ })) {
25132
+ return true;
25133
+ }
25134
+ }
25135
+ return false;
25136
+ };
25137
+ var isCandidateAvailableForOutsidePorts = ({
25138
+ candidate,
25139
+ bounds,
25140
+ usedBoundaryPoints,
25141
+ boundaryPointSpacing,
25142
+ routeFrom,
25143
+ pads,
25144
+ sourcePortId,
25145
+ outsidePorts,
25146
+ layer
25147
+ }) => {
25148
+ if (!isCandidateAvailable({
25149
+ candidate,
25150
+ usedBoundaryPoints,
25151
+ boundaryPointSpacing,
25152
+ routeFrom,
25153
+ pads,
25154
+ sourcePortId,
25155
+ layer
25156
+ })) {
25157
+ return false;
25158
+ }
25159
+ return !hasOutsideAccessConflict({
25160
+ candidate,
25161
+ outsidePorts,
25162
+ pads,
25163
+ sourcePortId,
25164
+ layer
25165
+ });
25166
+ };
25167
+ var getAvailableBreakoutBoundaryPoint = ({
25168
+ idealPoint,
25169
+ bounds,
25170
+ usedBoundaryPoints,
25171
+ boundaryPointSpacing,
25172
+ routeFrom,
25173
+ pads,
25174
+ sourcePortId,
25175
+ layer
25176
+ }) => {
25177
+ if (isCandidateAvailable({
25178
+ candidate: idealPoint,
25179
+ usedBoundaryPoints,
25180
+ boundaryPointSpacing,
25181
+ routeFrom,
25182
+ pads,
25183
+ sourcePortId,
25184
+ layer
25185
+ })) {
25186
+ return idealPoint;
25187
+ }
25188
+ const edge = getBoundsEdge(idealPoint, bounds);
25189
+ if (!edge) return null;
25190
+ const step = getBoundaryCandidateSearchStep({
25191
+ bounds,
25192
+ boundaryPointSpacing
25193
+ });
25194
+ if (step <= 0) return null;
25195
+ const edgeCandidates = getBoundsEdgeCandidates({
25196
+ edge,
25197
+ bounds,
25198
+ step
25199
+ });
25200
+ edgeCandidates.sort(
25201
+ (a, b) => distance15(a, idealPoint) - distance15(b, idealPoint)
25202
+ );
25203
+ for (const candidate of edgeCandidates) {
25204
+ if (isCandidateAvailable({
25205
+ candidate,
25206
+ usedBoundaryPoints,
25207
+ boundaryPointSpacing,
25208
+ routeFrom,
25209
+ pads,
25210
+ sourcePortId,
25211
+ layer
25212
+ })) {
25213
+ return candidate;
25214
+ }
25215
+ }
25216
+ const candidates = getAllBoundsCandidates({ bounds, step });
25217
+ candidates.sort((a, b) => distance15(a, idealPoint) - distance15(b, idealPoint));
25218
+ for (const candidate of candidates) {
25219
+ if (isCandidateAvailable({
25220
+ candidate,
25221
+ usedBoundaryPoints,
25222
+ boundaryPointSpacing,
25223
+ routeFrom,
25224
+ pads,
25225
+ sourcePortId,
25226
+ layer
25227
+ })) {
25228
+ return candidate;
25229
+ }
25230
+ }
25231
+ return null;
25232
+ };
25233
+ var getAvailableBreakoutBoundaryPointForOutsidePorts = ({
25234
+ idealPoints,
25235
+ bounds,
25236
+ usedBoundaryPoints,
25237
+ boundaryPointSpacing,
25238
+ routeFrom,
25239
+ pads,
25240
+ sourcePortId,
25241
+ outsidePorts,
25242
+ layer
25243
+ }) => {
25244
+ const step = getBoundaryCandidateSearchStep({
25245
+ bounds,
25246
+ boundaryPointSpacing
25247
+ });
25248
+ if (step <= 0) return null;
25249
+ for (const idealPoint of idealPoints) {
25250
+ if (isCandidateAvailableForOutsidePorts({
25251
+ candidate: idealPoint,
25252
+ bounds,
25253
+ usedBoundaryPoints,
25254
+ boundaryPointSpacing,
25255
+ routeFrom,
25256
+ pads,
25257
+ sourcePortId,
25258
+ outsidePorts,
25259
+ layer
25260
+ })) {
25261
+ return idealPoint;
25262
+ }
25263
+ const edge = getBoundsEdge(idealPoint, bounds);
25264
+ if (!edge) continue;
25265
+ const edgeCandidates = getBoundsEdgeCandidates({ edge, bounds, step });
25266
+ edgeCandidates.sort(
25267
+ (a, b) => distance15(a, idealPoint) - distance15(b, idealPoint)
25268
+ );
25269
+ for (const candidate of edgeCandidates) {
25270
+ if (isCandidateAvailableForOutsidePorts({
25271
+ candidate,
25272
+ bounds,
25273
+ usedBoundaryPoints,
25274
+ boundaryPointSpacing,
25275
+ routeFrom,
25276
+ pads,
25277
+ sourcePortId,
25278
+ outsidePorts,
25279
+ layer
25280
+ })) {
25281
+ return candidate;
25282
+ }
25283
+ }
25284
+ }
25285
+ for (const idealPoint of idealPoints) {
25286
+ const candidates = getAllBoundsCandidates({ bounds, step });
25287
+ candidates.sort((a, b) => distance15(a, idealPoint) - distance15(b, idealPoint));
25288
+ for (const candidate of candidates) {
25289
+ if (isCandidateAvailableForOutsidePorts({
25290
+ candidate,
25291
+ bounds,
25292
+ usedBoundaryPoints,
25293
+ boundaryPointSpacing,
25294
+ routeFrom,
25295
+ pads,
25296
+ sourcePortId,
25297
+ outsidePorts,
25298
+ layer
25299
+ })) {
25300
+ return candidate;
25301
+ }
25302
+ }
25303
+ }
25304
+ return null;
25305
+ };
25306
+
25307
+ // node_modules/@tscircuit/breakout-point-solver/lib/BreakoutPointSolver.ts
25308
+ var getLayerVisualStyle = (layer) => {
25309
+ if (layer === "bottom") {
25310
+ return {
25311
+ insidePadFill: "rgba(2, 132, 199, 0.28)",
25312
+ insidePadStroke: "#0369a1",
25313
+ insidePointColor: "#0369a1",
25314
+ outsidePadFill: "rgba(30, 64, 175, 0.2)",
25315
+ outsidePadStroke: "#1e40af",
25316
+ outsidePointColor: "#1e40af",
25317
+ padFill: "rgba(37, 99, 235, 0.18)",
25318
+ padStroke: "#1d4ed8",
25319
+ componentFill: "rgba(245, 158, 11, 0.1)",
25320
+ componentStroke: "#b45309",
25321
+ traceStroke: "#2563eb",
25322
+ breakoutPointColor: "#0f766e"
25323
+ };
25324
+ }
25325
+ return {
25326
+ insidePadFill: "rgba(46, 125, 50, 0.28)",
25327
+ insidePadStroke: "#1b5e20",
25328
+ insidePointColor: "#1b5e20",
25329
+ outsidePadFill: "rgba(106, 27, 154, 0.2)",
25330
+ outsidePadStroke: "#6a1b9a",
25331
+ outsidePointColor: "#6a1b9a",
25332
+ padFill: "rgba(220, 38, 38, 0.22)",
25333
+ padStroke: "#b91c1c",
25334
+ componentFill: "rgba(245, 158, 11, 0.1)",
25335
+ componentStroke: "#b45309",
25336
+ traceStroke: "#7e8794",
25337
+ breakoutPointColor: "#0d47a1"
25338
+ };
25339
+ };
25340
+ var getPortPadRect = ({
25341
+ port,
25342
+ fill,
25343
+ stroke,
25344
+ fallbackLabel
25345
+ }) => {
25346
+ if (port.width === void 0 || port.height === void 0) return null;
25347
+ return {
25348
+ center: port.position,
25349
+ width: port.width,
25350
+ height: port.height,
25351
+ ccwRotationDegrees: port.ccwRotationDegrees,
25352
+ fill,
25353
+ stroke,
25354
+ label: port.label ?? fallbackLabel
25355
+ };
25356
+ };
25357
+ var getAveragePortPosition = (ports) => {
25358
+ if (ports.length === 0) return null;
25359
+ const total = ports.reduce(
25360
+ (sum, port) => ({
25361
+ x: sum.x + port.position.x,
25362
+ y: sum.y + port.position.y
25363
+ }),
25364
+ { x: 0, y: 0 }
25365
+ );
25366
+ return {
25367
+ x: total.x / ports.length,
25368
+ y: total.y / ports.length
25369
+ };
25370
+ };
25371
+ var getOutsideTarget = (trace) => getAveragePortPosition(trace.outsidePorts);
25372
+ var getInsidePortKey = (port) => `${port.sourcePortId}:${port.layer ?? "top"}`;
25373
+ var getOutsidePortsForInsidePort = ({
25374
+ traces,
25375
+ insidePort
25376
+ }) => {
25377
+ const outsidePorts = [];
25378
+ const insidePortKey = getInsidePortKey(insidePort);
25379
+ for (const trace of traces) {
25380
+ const hasMatchingInsidePort = trace.insidePorts.some(
25381
+ (port) => getInsidePortKey(port) === insidePortKey
25382
+ );
25383
+ if (!hasMatchingInsidePort) continue;
25384
+ outsidePorts.push(...trace.outsidePorts);
25385
+ }
25386
+ return outsidePorts;
25387
+ };
25388
+ var getIdealBoundaryPoints = ({
25389
+ insidePort,
25390
+ outsidePorts,
25391
+ bounds
25392
+ }) => {
25393
+ const idealBoundaryPoints = [];
25394
+ for (const outsidePort of outsidePorts) {
25395
+ const idealBoundaryPoint = getBreakoutBoundaryIntersection({
25396
+ from: insidePort.position,
25397
+ to: outsidePort.position,
25398
+ bounds
25399
+ });
25400
+ if (idealBoundaryPoint) idealBoundaryPoints.push(idealBoundaryPoint);
25401
+ }
25402
+ return idealBoundaryPoints;
25403
+ };
25404
+ var BreakoutPointSolver = class extends BaseSolver {
25405
+ input;
25406
+ output = { breakoutPoints: [] };
25407
+ constructor(input) {
25408
+ super();
25409
+ this.input = input;
25410
+ }
25411
+ _step() {
25412
+ const breakoutPoints = [];
25413
+ const boundaryPointsByInsidePort = /* @__PURE__ */ new Map();
25414
+ for (const trace of this.input.traces) {
25415
+ for (const insidePort of trace.insidePorts) {
25416
+ const insidePortKey = getInsidePortKey(insidePort);
25417
+ let boundaryPoint = boundaryPointsByInsidePort.get(insidePortKey) ?? null;
25418
+ if (!boundaryPoint) {
25419
+ const outsidePorts = getOutsidePortsForInsidePort({
25420
+ traces: this.input.traces,
25421
+ insidePort
25422
+ });
25423
+ const idealBoundaryPoints = getIdealBoundaryPoints({
25424
+ insidePort,
25425
+ outsidePorts,
25426
+ bounds: this.input.bounds
25427
+ });
25428
+ if (idealBoundaryPoints.length === 0) continue;
25429
+ const usedBoundaryPoints = [
25430
+ ...this.input.usedBoundaryPoints ?? [],
25431
+ ...boundaryPointsByInsidePort.values()
25432
+ ];
25433
+ if (outsidePorts.length > 1) {
25434
+ boundaryPoint = getAvailableBreakoutBoundaryPointForOutsidePorts({
25435
+ idealPoints: idealBoundaryPoints,
25436
+ bounds: this.input.bounds,
25437
+ usedBoundaryPoints,
25438
+ boundaryPointSpacing: this.input.boundaryPointSpacing ?? 0,
25439
+ routeFrom: insidePort.position,
25440
+ pads: this.input.pads,
25441
+ sourcePortId: insidePort.sourcePortId,
25442
+ outsidePorts,
25443
+ layer: insidePort.layer
25444
+ });
25445
+ }
25446
+ boundaryPoint ??= getAvailableBreakoutBoundaryPoint({
25447
+ idealPoint: idealBoundaryPoints[0],
25448
+ bounds: this.input.bounds,
25449
+ usedBoundaryPoints,
25450
+ boundaryPointSpacing: this.input.boundaryPointSpacing ?? 0,
25451
+ routeFrom: insidePort.position,
25452
+ pads: this.input.pads,
25453
+ sourcePortId: insidePort.sourcePortId,
25454
+ layer: insidePort.layer
25455
+ });
25456
+ if (!boundaryPoint) continue;
25457
+ boundaryPointsByInsidePort.set(insidePortKey, boundaryPoint);
25458
+ }
25459
+ breakoutPoints.push({
25460
+ sourcePortId: insidePort.sourcePortId,
25461
+ sourceTraceId: trace.sourceTraceId,
25462
+ x: boundaryPoint.x,
25463
+ y: boundaryPoint.y,
25464
+ ...insidePort.layer ? { layer: insidePort.layer } : {}
25465
+ });
25466
+ }
25467
+ }
25468
+ this.output = { breakoutPoints };
25469
+ this.solved = true;
25470
+ }
25471
+ getConstructorParams() {
25472
+ return [this.input];
25473
+ }
25474
+ getOutput() {
25475
+ return this.output;
25476
+ }
25477
+ visualize() {
25478
+ const { bounds } = this.input;
25479
+ const width = bounds.maxX - bounds.minX;
25480
+ const height = bounds.maxY - bounds.minY;
25481
+ const center = {
25482
+ x: bounds.minX + width / 2,
25483
+ y: bounds.minY + height / 2
25484
+ };
25485
+ return {
25486
+ title: "BreakoutPointSolver - generated breakout points",
25487
+ rects: [
25488
+ {
25489
+ center,
25490
+ width,
25491
+ height,
25492
+ fill: "rgba(210, 225, 255, 0.25)",
25493
+ stroke: "#315fba",
25494
+ label: "breakout bounds"
25495
+ },
25496
+ ...(this.input.components ?? []).map((component) => ({
25497
+ center: component.center,
25498
+ width: component.width,
25499
+ height: component.height,
25500
+ ccwRotationDegrees: component.ccwRotationDegrees,
25501
+ fill: getLayerVisualStyle(component.layer).componentFill,
25502
+ stroke: getLayerVisualStyle(component.layer).componentStroke,
25503
+ label: component.label ?? "component"
25504
+ })),
25505
+ ...(this.input.pads ?? []).map((pad) => ({
25506
+ center: pad.center,
25507
+ width: pad.width + (pad.clearance ?? 0) * 2,
25508
+ height: pad.height + (pad.clearance ?? 0) * 2,
25509
+ ccwRotationDegrees: pad.ccwRotationDegrees,
25510
+ fill: getLayerVisualStyle(pad.layer).padFill,
25511
+ stroke: getLayerVisualStyle(pad.layer).padStroke,
25512
+ label: pad.label ?? "pad"
25513
+ })),
25514
+ ...this.input.traces.flatMap(
25515
+ (trace) => trace.insidePorts.flatMap((port) => {
25516
+ const rect = getPortPadRect({
25517
+ port,
25518
+ fill: getLayerVisualStyle(port.layer).insidePadFill,
25519
+ stroke: getLayerVisualStyle(port.layer).insidePadStroke,
25520
+ fallbackLabel: `inside pad ${port.sourcePortId}`
25521
+ });
25522
+ return rect ? [rect] : [];
25523
+ })
25524
+ ),
25525
+ ...this.input.traces.flatMap(
25526
+ (trace) => trace.outsidePorts.flatMap((port) => {
25527
+ const rect = getPortPadRect({
25528
+ port,
25529
+ fill: getLayerVisualStyle(port.layer).outsidePadFill,
25530
+ stroke: getLayerVisualStyle(port.layer).outsidePadStroke,
25531
+ fallbackLabel: `outside pad ${port.sourcePortId}`
25532
+ });
25533
+ return rect ? [rect] : [];
25534
+ })
25535
+ )
25536
+ ],
25537
+ lines: this.input.traces.flatMap(
25538
+ (trace) => trace.insidePorts.flatMap((insidePort) => {
25539
+ const outsideTarget = getOutsideTarget(trace);
25540
+ if (!outsideTarget) return [];
25541
+ const breakoutPoint = this.output.breakoutPoints.find(
25542
+ (point6) => point6.sourceTraceId === trace.sourceTraceId && point6.sourcePortId === insidePort.sourcePortId
25543
+ );
25544
+ if (!breakoutPoint) return [];
25545
+ return [
25546
+ {
25547
+ points: [
25548
+ insidePort.position,
25549
+ { x: breakoutPoint.x, y: breakoutPoint.y }
25550
+ ],
25551
+ strokeColor: getLayerVisualStyle(insidePort.layer).traceStroke,
25552
+ label: `breakout segment ${trace.sourceTraceId}`
25553
+ },
25554
+ {
25555
+ points: [
25556
+ { x: breakoutPoint.x, y: breakoutPoint.y },
25557
+ outsideTarget
25558
+ ],
25559
+ strokeColor: getLayerVisualStyle(insidePort.layer).traceStroke,
25560
+ strokeDash: "0.15 0.15",
25561
+ label: `target guide ${trace.sourceTraceId}`
25562
+ }
25563
+ ];
25564
+ })
25565
+ ),
25566
+ points: [
25567
+ ...this.input.traces.flatMap(
25568
+ (trace) => trace.insidePorts.map((port) => ({
25569
+ ...port.position,
25570
+ color: getLayerVisualStyle(port.layer).insidePointColor,
25571
+ label: `inside pad ${port.sourcePortId}`
25572
+ }))
25573
+ ),
25574
+ ...this.input.traces.flatMap(
25575
+ (trace) => trace.outsidePorts.map((port) => ({
25576
+ ...port.position,
25577
+ color: getLayerVisualStyle(port.layer).outsidePointColor,
25578
+ label: `outside target ${port.sourcePortId}`
25579
+ }))
25580
+ ),
25581
+ ...this.output.breakoutPoints.map((point6) => ({
25582
+ x: point6.x,
25583
+ y: point6.y,
25584
+ color: getLayerVisualStyle(point6.layer).breakoutPointColor,
25585
+ label: `selected breakout ${point6.sourcePortId}`
25586
+ })),
25587
+ ...(this.input.usedBoundaryPoints ?? []).map((point6) => ({
25588
+ ...point6,
25589
+ color: "#ef6c00",
25590
+ label: "pre-existing breakout point"
25591
+ }))
25592
+ ]
25593
+ };
25594
+ }
25595
+ };
24859
25596
 
24860
25597
  // lib/components/primitive-components/BaseBreakoutPoint.ts
24861
25598
  import { pcbLayoutProps } from "@tscircuit/props";
@@ -25369,7 +26106,7 @@ var NetLabel = class extends PrimitiveComponent2 {
25369
26106
 
25370
26107
  // lib/components/primitive-components/Fiducial.ts
25371
26108
  import "zod";
25372
- import { distance as distance14 } from "circuit-json";
26109
+ import { distance as distance16 } from "circuit-json";
25373
26110
  import { fiducialProps } from "@tscircuit/props";
25374
26111
  var Fiducial = class extends PrimitiveComponent2 {
25375
26112
  pcb_smtpad_id = null;
@@ -25394,15 +26131,15 @@ var Fiducial = class extends PrimitiveComponent2 {
25394
26131
  shape: "circle",
25395
26132
  x: position.x,
25396
26133
  y: position.y,
25397
- radius: distance14.parse(props.padDiameter) / 2,
25398
- soldermask_margin: props.soldermaskPullback ? distance14.parse(props.soldermaskPullback) : distance14.parse(props.padDiameter) / 2,
26134
+ radius: distance16.parse(props.padDiameter) / 2,
26135
+ soldermask_margin: props.soldermaskPullback ? distance16.parse(props.soldermaskPullback) : distance16.parse(props.padDiameter) / 2,
25399
26136
  is_covered_with_solder_mask: true
25400
26137
  });
25401
26138
  this.pcb_smtpad_id = pcb_smtpad.pcb_smtpad_id;
25402
26139
  }
25403
26140
  getPcbSize() {
25404
26141
  const { _parsedProps: props } = this;
25405
- const d = distance14.parse(props.padDiameter);
26142
+ const d = distance16.parse(props.padDiameter);
25406
26143
  return { width: d, height: d };
25407
26144
  }
25408
26145
  _setPositionFromLayout(newCenter) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/core",
3
3
  "type": "module",
4
- "version": "0.0.1280",
4
+ "version": "0.0.1282",
5
5
  "types": "dist/index.d.ts",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",