@tscircuit/schematic-trace-solver 0.0.130 → 0.0.131

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.
@@ -10,6 +10,7 @@ import {
10
10
  } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
11
11
  import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
12
12
  import type {
13
+ ChipId,
13
14
  InputNetConnection,
14
15
  InputPin,
15
16
  InputProblem,
@@ -54,6 +55,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
54
55
  currentLabelIndex: number | null = null
55
56
  currentLabel: NetLabelPlacement | null = null
56
57
  currentCandidateResults: EvaluatedCandidate[] = []
58
+ private crowdedPortOnlyLabelIndices = new Set<number>()
57
59
 
58
60
  blockedStandaloneLabelIndex: number | null = null
59
61
  blockedStandaloneLabelTargetAnchorX: number | null = null
@@ -76,6 +78,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
76
78
  params.inputProblem._chipObstacleSpatialIndex ??
77
79
  new ChipObstacleSpatialIndex(params.inputProblem.chips)
78
80
  this.maxSearchDistance = getMaxSearchDistance(params.inputProblem)
81
+ this.crowdedPortOnlyLabelIndices = this.getCrowdedPortOnlyLabelIndices()
79
82
  this.queuedLabelIndices = this.getProcessableLabelIndices()
80
83
  this.setCurrentLabel(this.queuedLabelIndices[0] ?? null)
81
84
  }
@@ -111,10 +114,11 @@ export class AvailableNetOrientationSolver extends BaseSolver {
111
114
  const indices: number[] = []
112
115
 
113
116
  for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
114
- if (this.shouldProcessLabel(this.outputNetLabelPlacements[i]!)) {
117
+ if (this.shouldProcessLabel(this.outputNetLabelPlacements[i]!, i)) {
115
118
  indices.push(i)
116
119
  }
117
120
  }
121
+ this.sortCrowdedTopBankByPin(indices)
118
122
 
119
123
  const blockedStandaloneLabelIndex = indices.find((index) => {
120
124
  const label = this.outputNetLabelPlacements[index]!
@@ -184,9 +188,98 @@ export class AvailableNetOrientationSolver extends BaseSolver {
184
188
  return indices
185
189
  }
186
190
 
187
- private shouldProcessLabel(label: NetLabelPlacement) {
191
+ private sortCrowdedTopBankByPin(indices: number[]) {
192
+ const slotsByChip = new Map<ChipId, number[]>()
193
+
194
+ for (let slot = 0; slot < indices.length; slot++) {
195
+ const labelIndex = indices[slot]!
196
+ if (!this.crowdedPortOnlyLabelIndices.has(labelIndex)) continue
197
+ const label = this.outputNetLabelPlacements[labelIndex]!
198
+ if (this.getChipSideForPoint(label.anchorPoint) !== "top") continue
199
+ const pin = this.pinMap[label.pinIds[0]!]
200
+ if (!pin) continue
201
+ const slots = slotsByChip.get(pin.chipId) ?? []
202
+ slots.push(slot)
203
+ slotsByChip.set(pin.chipId, slots)
204
+ }
205
+
206
+ for (const slots of slotsByChip.values()) {
207
+ const labelIndices = slots
208
+ .map((slot) => indices[slot]!)
209
+ .sort((a, b) => {
210
+ const aPin = this.pinMap[this.outputNetLabelPlacements[a]!.pinIds[0]!]
211
+ const bPin = this.pinMap[this.outputNetLabelPlacements[b]!.pinIds[0]!]
212
+ return aPin!.x - bPin!.x
213
+ })
214
+ for (let position = 0; position < slots.length; position++) {
215
+ indices[slots[position]!] = labelIndices[position]!
216
+ }
217
+ }
218
+ }
219
+
220
+ private shouldProcessLabel(label: NetLabelPlacement, labelIndex: number) {
188
221
  const orientations = this.getAvailableOrientations(label)
189
- return orientations.length > 0 && !orientations.includes(label.orientation)
222
+ if (orientations.length === 0) return false
223
+ if (!orientations.includes(label.orientation)) return true
224
+ if (!this.crowdedPortOnlyLabelIndices.has(labelIndex)) return false
225
+
226
+ const bounds = getRectBounds(label.center, label.width, label.height)
227
+ return this.outputNetLabelPlacements.some((otherLabel, otherIndex) => {
228
+ if (otherIndex === labelIndex) return false
229
+ if (otherLabel.globalConnNetId === label.globalConnNetId) return false
230
+ return rectsOverlap(
231
+ bounds,
232
+ getRectBounds(otherLabel.center, otherLabel.width, otherLabel.height),
233
+ )
234
+ })
235
+ }
236
+
237
+ private getCrowdedPortOnlyLabelIndices() {
238
+ const labelIndicesByChipAndSide = new Map<ChipId, Map<ChipSide, number[]>>()
239
+ for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
240
+ const label = this.outputNetLabelPlacements[i]!
241
+ if (!this.isPortOnlyLabel(label)) continue
242
+ if (!this.isStandaloneSinglePinNetLabel(label)) continue
243
+ if (this.getAvailableOrientations(label).length === 0) continue
244
+ const pin = this.pinMap[label.pinIds[0]!]
245
+ if (!pin) continue
246
+ const chipSide = this.getChipSideForPoint(label.anchorPoint)
247
+ if (!chipSide) continue
248
+ const labelIndicesBySide =
249
+ labelIndicesByChipAndSide.get(pin.chipId) ??
250
+ new Map<ChipSide, number[]>()
251
+ const labelIndices = labelIndicesBySide.get(chipSide) ?? []
252
+ labelIndices.push(i)
253
+ labelIndicesBySide.set(chipSide, labelIndices)
254
+ labelIndicesByChipAndSide.set(pin.chipId, labelIndicesBySide)
255
+ }
256
+
257
+ const crowdedIndices = new Set<number>()
258
+ for (const labelIndicesBySide of labelIndicesByChipAndSide.values()) {
259
+ for (const labelIndices of labelIndicesBySide.values()) {
260
+ const hasWrongOrientation = labelIndices.some((index) => {
261
+ const label = this.outputNetLabelPlacements[index]!
262
+ return !this.getAvailableOrientations(label).includes(
263
+ label.orientation,
264
+ )
265
+ })
266
+ const hasOverlap = labelIndices.some((index, position) => {
267
+ const label = this.outputNetLabelPlacements[index]!
268
+ const bounds = getRectBounds(label.center, label.width, label.height)
269
+ return labelIndices.slice(position + 1).some((otherIndex) => {
270
+ const other = this.outputNetLabelPlacements[otherIndex]!
271
+ return rectsOverlap(
272
+ bounds,
273
+ getRectBounds(other.center, other.width, other.height),
274
+ )
275
+ })
276
+ })
277
+ if (!hasWrongOrientation || !hasOverlap) continue
278
+ for (const index of labelIndices) crowdedIndices.add(index)
279
+ }
280
+ }
281
+
282
+ return crowdedIndices
190
283
  }
191
284
 
192
285
  private processLabel(labelIndex: number) {
@@ -219,7 +312,11 @@ export class AvailableNetOrientationSolver extends BaseSolver {
219
312
  ) {
220
313
  if (candidate.phase === "trace-anchor") return
221
314
 
222
- const tracePath = this.getCandidateConnectorTrace(label, candidate)
315
+ const tracePath = this.getCandidateConnectorTrace(
316
+ label,
317
+ candidate,
318
+ labelIndex,
319
+ )
223
320
  if (tracePath.length < 2) return
224
321
 
225
322
  const mspPairId = `available-net-orientation-${labelIndex}-${label.netId ?? label.globalConnNetId}`
@@ -254,6 +351,21 @@ export class AvailableNetOrientationSolver extends BaseSolver {
254
351
  private findCorrectedCandidate(label: NetLabelPlacement, labelIndex: number) {
255
352
  const orientations = this.getAvailableOrientations(label)
256
353
  const requiredOrientation = orientations[0]!
354
+
355
+ if (
356
+ this.crowdedPortOnlyLabelIndices.has(labelIndex) &&
357
+ this.getChipSideForPoint(label.anchorPoint) === "top" &&
358
+ isYOrientation(requiredOrientation)
359
+ ) {
360
+ const topFanoutCandidate =
361
+ this.findValidCrowdedTopVerticalFanoutCandidate(
362
+ label,
363
+ labelIndex,
364
+ requiredOrientation,
365
+ )
366
+ if (topFanoutCandidate) return topFanoutCandidate
367
+ }
368
+
257
369
  const netConnection = this.inputProblem.netConnections.find(
258
370
  (connection) => connection.netId === label.netId,
259
371
  )
@@ -334,6 +446,117 @@ export class AvailableNetOrientationSolver extends BaseSolver {
334
446
  )
335
447
  }
336
448
 
449
+ private findValidCrowdedTopVerticalFanoutCandidate(
450
+ label: NetLabelPlacement,
451
+ labelIndex: number,
452
+ orientation: "y+" | "y-",
453
+ ) {
454
+ const pin = this.pinMap[label.pinIds[0]!]
455
+ const chip = pin
456
+ ? this.chipObstacleSpatialIndex.chips.find(
457
+ (candidate) => candidate.chipId === pin.chipId,
458
+ )
459
+ : undefined
460
+ if (!chip) return null
461
+
462
+ const { width, height } = getDimsForOrientation({
463
+ orientation,
464
+ netLabelWidth: this.getNetLabelWidth(label),
465
+ netLabelHeight: this.getNetLabelHeight(label),
466
+ })
467
+
468
+ const nextPinToRight = this.outputNetLabelPlacements
469
+ .map((otherLabel, index) => ({
470
+ index,
471
+ pin: this.pinMap[otherLabel.pinIds[0]!],
472
+ }))
473
+ .filter(
474
+ ({ index, pin: otherPin }) =>
475
+ this.crowdedPortOnlyLabelIndices.has(index) &&
476
+ otherPin?.chipId === pin.chipId &&
477
+ otherPin.x > pin.x + EPS,
478
+ )
479
+ .sort((a, b) => a.pin!.x - b.pin!.x)[0]?.pin
480
+
481
+ // A down-facing label uses the terminal right-hand escape. Interior
482
+ // down-facing labels fall back to the general search instead of crossing
483
+ // the remaining pin corridors.
484
+ if (orientation === "y-" && nextPinToRight) return null
485
+
486
+ const anchorX =
487
+ orientation === "y-"
488
+ ? Math.max(
489
+ label.anchorPoint.x + LABEL_SEARCH_STEP,
490
+ chip.bounds.maxX + width / 2 + LABEL_TRACE_CLEARANCE,
491
+ )
492
+ : Math.min(
493
+ label.anchorPoint.x,
494
+ (nextPinToRight?.x ?? chip.bounds.maxX) -
495
+ LABEL_SEARCH_STEP -
496
+ width / 2,
497
+ )
498
+
499
+ let highestPlacedLabelY = chip.bounds.maxY
500
+ let lowestUpFacingLabelY = Number.POSITIVE_INFINITY
501
+ let highestUpFacingLabelY = Number.NEGATIVE_INFINITY
502
+ for (let index = 0; index < this.outputNetLabelPlacements.length; index++) {
503
+ if (index === labelIndex) continue
504
+ if (!this.crowdedPortOnlyLabelIndices.has(index)) continue
505
+ if (this.queuedLabelIndices.includes(index)) continue
506
+ const placedLabel = this.outputNetLabelPlacements[index]!
507
+ const placedBounds = getRectBounds(
508
+ placedLabel.center,
509
+ placedLabel.width,
510
+ placedLabel.height,
511
+ )
512
+ highestPlacedLabelY = Math.max(highestPlacedLabelY, placedBounds.maxY)
513
+ if (placedLabel.orientation !== "y+") continue
514
+ lowestUpFacingLabelY = Math.min(lowestUpFacingLabelY, placedBounds.minY)
515
+ highestUpFacingLabelY = Math.max(highestUpFacingLabelY, placedBounds.maxY)
516
+ }
517
+
518
+ const stackedAnchorY =
519
+ highestPlacedLabelY +
520
+ LABEL_SEARCH_STEP +
521
+ (orientation === "y-" ? height : 0)
522
+ const hasUpFacingStack =
523
+ Number.isFinite(lowestUpFacingLabelY) &&
524
+ Number.isFinite(highestUpFacingLabelY)
525
+ const centeredAnchorY = hasUpFacingStack
526
+ ? (lowestUpFacingLabelY + highestUpFacingLabelY) / 2
527
+ : stackedAnchorY
528
+ const anchorYs =
529
+ orientation === "y-"
530
+ ? [centeredAnchorY, stackedAnchorY]
531
+ : [stackedAnchorY]
532
+
533
+ for (const anchorY of anchorYs) {
534
+ if (anchorY - label.anchorPoint.y > this.maxSearchDistance + EPS) {
535
+ continue
536
+ }
537
+ const candidate = this.createCandidate(
538
+ label,
539
+ { x: anchorX, y: anchorY },
540
+ orientation,
541
+ )
542
+ const result = this.evaluateCandidate(
543
+ candidate,
544
+ label,
545
+ labelIndex,
546
+ "lateral-shift",
547
+ anchorY - label.anchorPoint.y,
548
+ anchorX - label.anchorPoint.x,
549
+ )
550
+ this.currentCandidateResults.push(result)
551
+ if (result.status !== "valid") continue
552
+
553
+ result.selected = true
554
+ return result
555
+ }
556
+
557
+ return null
558
+ }
559
+
337
560
  private hasTraceContinuingInOrientation(
338
561
  label: NetLabelPlacement,
339
562
  orientation: "y+" | "y-",
@@ -703,12 +926,35 @@ export class AvailableNetOrientationSolver extends BaseSolver {
703
926
  EvaluatedCandidate,
704
927
  "anchorPoint" | "connectorSource" | "orientation" | "phase"
705
928
  >,
929
+ labelIndex: number,
706
930
  ) {
931
+ if (
932
+ this.crowdedPortOnlyLabelIndices.has(labelIndex) &&
933
+ this.getChipSideForPoint(label.anchorPoint) === "top" &&
934
+ isYOrientation(candidate.orientation)
935
+ ) {
936
+ // Top-edge vertical labels use the target row as their bend row. This
937
+ // makes every connector leave the pin upward first, instead of creating
938
+ // a low horizontal bus that the overlap solver later pushes below the
939
+ // component. It also gives a y- ground label the desired up-then-right
940
+ // connector while preserving its required facing direction.
941
+ return simplifyOrthogonalPath([
942
+ label.anchorPoint,
943
+ { x: label.anchorPoint.x, y: candidate.anchorPoint.y },
944
+ candidate.anchorPoint,
945
+ ])
946
+ }
947
+
707
948
  if (candidate.phase === "lateral-shift") {
708
- const orientDir = dir(candidate.orientation)
949
+ const chipOutwardDir = this.crowdedPortOnlyLabelIndices.has(labelIndex)
950
+ ? this.getChipOutwardDirection(label.anchorPoint)
951
+ : (() => {
952
+ const orientDir = dir(candidate.orientation)
953
+ return { x: -orientDir.x, y: -orientDir.y }
954
+ })()
709
955
  const kickedSource = {
710
- x: label.anchorPoint.x - orientDir.x * LABEL_SEARCH_STEP,
711
- y: label.anchorPoint.y - orientDir.y * LABEL_SEARCH_STEP,
956
+ x: label.anchorPoint.x + chipOutwardDir.x * LABEL_SEARCH_STEP,
957
+ y: label.anchorPoint.y + chipOutwardDir.y * LABEL_SEARCH_STEP,
712
958
  }
713
959
  return simplifyOrthogonalPath([
714
960
  label.anchorPoint,
@@ -1007,11 +1253,15 @@ export class AvailableNetOrientationSolver extends BaseSolver {
1007
1253
  if (nonChipBoundsStatus !== "valid") return nonChipBoundsStatus
1008
1254
  }
1009
1255
 
1010
- const connectorTrace = this.getCandidateConnectorTrace(label, {
1011
- anchorPoint: candidate.anchorPoint,
1012
- orientation: candidate.orientation,
1013
- phase,
1014
- })
1256
+ const connectorTrace = this.getCandidateConnectorTrace(
1257
+ label,
1258
+ {
1259
+ anchorPoint: candidate.anchorPoint,
1260
+ orientation: candidate.orientation,
1261
+ phase,
1262
+ },
1263
+ labelIndex,
1264
+ )
1015
1265
 
1016
1266
  for (const chip of this.chipObstacleSpatialIndex.chips) {
1017
1267
  if (tracePathCrossesAnyBounds(connectorTrace, chip.bounds)) {
@@ -1021,6 +1271,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
1021
1271
 
1022
1272
  for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
1023
1273
  if (i === labelIndex) continue
1274
+ if (this.shouldIgnorePendingCrowdedLabel(labelIndex, i)) continue
1024
1275
  const otherLabel = this.outputNetLabelPlacements[i]!
1025
1276
  if (
1026
1277
  tracePathIntersectsBounds(
@@ -1227,9 +1478,21 @@ export class AvailableNetOrientationSolver extends BaseSolver {
1227
1478
  return label.mspConnectionPairIds.length === 0
1228
1479
  }
1229
1480
 
1481
+ private shouldIgnorePendingCrowdedLabel(
1482
+ labelIndex: number,
1483
+ otherLabelIndex: number,
1484
+ ) {
1485
+ return (
1486
+ this.crowdedPortOnlyLabelIndices.has(labelIndex) &&
1487
+ this.crowdedPortOnlyLabelIndices.has(otherLabelIndex) &&
1488
+ this.queuedLabelIndices.includes(otherLabelIndex)
1489
+ )
1490
+ }
1491
+
1230
1492
  private intersectsAnyOtherNetLabel(bounds: Bounds, labelIndex: number) {
1231
1493
  for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
1232
1494
  if (i === labelIndex) continue
1495
+ if (this.shouldIgnorePendingCrowdedLabel(labelIndex, i)) continue
1233
1496
  const label = this.outputNetLabelPlacements[i]!
1234
1497
  const otherBounds = getRectBounds(label.center, label.width, label.height)
1235
1498
  if (
@@ -1312,6 +1575,21 @@ export class AvailableNetOrientationSolver extends BaseSolver {
1312
1575
  return { x: 0, y: 0 }
1313
1576
  }
1314
1577
 
1578
+ private getChipOutwardDirection(point: Point) {
1579
+ switch (this.getChipSideForPoint(point)) {
1580
+ case "left":
1581
+ return { x: -1, y: 0 }
1582
+ case "right":
1583
+ return { x: 1, y: 0 }
1584
+ case "bottom":
1585
+ return { x: 0, y: -1 }
1586
+ case "top":
1587
+ return { x: 0, y: 1 }
1588
+ default:
1589
+ return { x: 0, y: 0 }
1590
+ }
1591
+ }
1592
+
1315
1593
  private getContainingChipSide(point: Point) {
1316
1594
  let nearestSide: ChipSide | null = null
1317
1595
  let nearestDistance = Number.POSITIVE_INFINITY
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/schematic-trace-solver",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.130",
4
+ "version": "0.0.131",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -1,34 +1,34 @@
1
- <svg width="640" height="640" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="white"/><g><polyline data-points="15.1,2.4000000000000004 15.1,2.5010000000000003 14.899000000000001,2.5010000000000003" data-type="line" data-label="" points="283.8178457402116,325.69316081330874 283.8178457402116,316.18887581919006 264.9033775835994,316.18887581919006" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="15.3,2.4000000000000004 15.3,2.6510000000000002 13.899000000000001,2.6510000000000002" data-type="line" data-label="" points="302.638212065199,325.69316081330874 302.638212065199,302.0736010754496 170.80154595866247,302.0736010754496" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="15.5,2.4000000000000004 15.5,2.7510000000000003 15.449,2.7510000000000003" data-type="line" data-label="" points="321.4585783901864,325.69316081330874 321.4585783901864,292.6634179129558 316.65938497731463,292.6634179129558" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="15.7,2.4000000000000004 15.7,2.9010000000000002 14.699,2.9010000000000002" data-type="line" data-label="" points="340.2789447151738,325.69316081330874 340.2789447151738,278.5481431692153 246.0830112586118,278.5481431692153" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="15.9,2.4000000000000004 15.9,3.051 13.949,3.051" data-type="line" data-label="" points="359.0993110401612,325.69316081330874 359.0993110401612,264.4328684254748 175.5066375399092,264.4328684254748" fill="none" stroke="purple" stroke-width="1px"/></g><g><rect data-type="rect" data-label="schematic_component_0" data-x="16" data-y="1.8" x="264.997479415224" y="325.69316081330874" width="207.02402957486174" height="112.92219794992434" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.010626785714285719"/></g><g><rect data-type="rect" data-label="Conn_North" data-x="17.800000000000004" data-y="2.1149999999999998" x="475.7855822550838" y="340.7494538732987" width="124.21441774491609" height="23.525457906234237" fill="rgba(160, 0, 220, 0.14)" stroke="black" stroke-width="0.010626785714285719"/></g><g><rect data-type="rect" data-label="netId: QSPI_CS
2
- globalConnNetId: connectivity_net0" data-x="14.419" data-y="2.5010000000000003" x="174.56561922365984" y="306.7786926566963" width="90.3377583599397" height="18.820366324987447" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.010626785714285719"/></g><g><rect data-type="rect" data-label="netId: RESET
3
- globalConnNetId: connectivity_net1" data-x="13.539000000000001" data-y="2.6510000000000002" x="103.04822718870787" y="292.6634179129559" width="67.7533187699546" height="18.820366324987447" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.010626785714285719"/></g><g><rect data-type="rect" data-label="netId: SWCLK
4
- globalConnNetId: connectivity_net2" data-x="15.089" data-y="2.7510000000000003" x="248.90606620736003" y="283.25323475046207" width="67.7533187699546" height="18.820366324987447" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.010626785714285719"/></g><g><rect data-type="rect" data-label="netId: SWDIO
5
- globalConnNetId: connectivity_net3" data-x="14.339" data-y="2.9010000000000002" x="178.32969248865754" y="269.1379600067215" width="67.75331876995438" height="18.820366324987447" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.010626785714285719"/></g><g><rect data-type="rect" data-label="netId: USB_D_MINUS
6
- globalConnNetId: connectivity_net4" data-x="13.229" data-y="3.051" x="39.999999999999886" y="255.02268526298107" width="135.50663753990943" height="18.820366324987447" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.010626785714285719"/></g><g><rect data-type="rect" data-label="netId: USB_D_PLUS
7
- globalConnNetId: connectivity_net5" data-x="16.1" data-y="3.0610000000000004" x="368.509494202655" y="201.38464123676695" width="18.820366324987617" height="124.21441774491683" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.010626785714285719"/></g><g><rect data-type="rect" data-label="netId: V3_3
8
- globalConnNetId: connectivity_net6" data-x="16.3" data-y="2.611" x="368.50949420265476" y="286.0762896992103" width="56.4610989749624" height="39.52276928247352" fill="#ef444466" stroke="#ef4444" stroke-width="0.010626785714285719"/></g><g><rect data-type="rect" data-label="netId: VBUS_5V
9
- globalConnNetId: connectivity_net7" data-x="16.5" data-y="2.611" x="370.3915308351536" y="286.0762896992103" width="90.33775835993947" height="39.52276928247352" fill="#ef444466" stroke="#ef4444" stroke-width="0.010626785714285719"/></g><g><rect data-type="rect" data-label="netId: VBAT
10
- globalConnNetId: connectivity_net8" data-x="16.7" data-y="2.611" x="406.15022685262954" y="286.0762896992103" width="56.4610989749624" height="39.52276928247352" fill="#ef444466" stroke="#ef4444" stroke-width="0.010626785714285719"/></g><g><rect data-type="rect" data-label="netId: GND
11
- globalConnNetId: connectivity_net9" data-x="16.9" data-y="2.611" x="443.7909595026041" y="286.0762896992103" width="18.820366324987617" height="39.52276928247352" fill="#00000066" stroke="#000000" stroke-width="0.010626785714285719"/></g><g><circle data-type="point" data-label="schematic_port_0
12
- y+" data-x="15.1" data-y="2.4000000000000004" cx="283.8178457402116" cy="325.69316081330874" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_1
13
- y+" data-x="15.3" data-y="2.4000000000000004" cx="302.638212065199" cy="325.69316081330874" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_2
14
- y+" data-x="15.5" data-y="2.4000000000000004" cx="321.4585783901864" cy="325.69316081330874" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_3
15
- y+" data-x="15.7" data-y="2.4000000000000004" cx="340.2789447151738" cy="325.69316081330874" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_4
16
- y+" data-x="15.9" data-y="2.4000000000000004" cx="359.0993110401612" cy="325.69316081330874" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_5
17
- y+" data-x="16.1" data-y="2.4000000000000004" cx="377.9196773651488" cy="325.69316081330874" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_6
18
- y+" data-x="16.3" data-y="2.4000000000000004" cx="396.74004369013596" cy="325.69316081330874" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_7
19
- y+" data-x="16.5" data-y="2.4000000000000004" cx="415.56041001512335" cy="325.69316081330874" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_8
20
- y+" data-x="16.7" data-y="2.4000000000000004" cx="434.38077634011074" cy="325.69316081330874" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_9
21
- y+" data-x="16.9" data-y="2.4000000000000004" cx="453.2011426650979" cy="325.69316081330874" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="anchorPoint
22
- orientation: x-" data-x="14.899000000000001" data-y="2.5010000000000003" cx="264.9033775835994" cy="316.18887581919006" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
23
- orientation: x-" data-x="13.899000000000001" data-y="2.6510000000000002" cx="170.80154595866247" cy="302.0736010754496" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
24
- orientation: x-" data-x="15.449" data-y="2.7510000000000003" cx="316.65938497731463" cy="292.6634179129558" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
25
- orientation: x-" data-x="14.699" data-y="2.9010000000000002" cx="246.0830112586118" cy="278.5481431692153" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
26
- orientation: x-" data-x="13.949" data-y="3.051" cx="175.5066375399092" cy="264.4328684254748" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
27
- orientation: y+" data-x="16.1" data-y="2.4000000000000004" cx="377.9196773651488" cy="325.69316081330874" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
28
- orientation: y+" data-x="16.3" data-y="2.4000000000000004" cx="396.74004369013596" cy="325.69316081330874" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
29
- orientation: y+" data-x="16.5" data-y="2.4000000000000004" cx="415.56041001512335" cy="325.69316081330874" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
30
- orientation: y+" data-x="16.7" data-y="2.4000000000000004" cx="434.38077634011074" cy="325.69316081330874" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
31
- orientation: y+" data-x="16.9" data-y="2.4000000000000004" cx="453.2011426650979" cy="325.69316081330874" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g id="crosshair" style="display: none"><line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5"/><line id="crosshair-v" x1="0" x2="640" stroke="#666" stroke-width="0.5"/><text id="coordinates" font-family="monospace" font-size="12" fill="#666"></text></g><script><![CDATA[
1
+ <svg width="640" height="640" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="white"/><g><polyline data-points="15.1,2.4000000000000004 15.1,2.5010000000000003 14.899000000000001,2.5010000000000003" data-type="line" data-label="" points="283.8178457402116,369.921021677029 283.8178457402116,360.4167366829103 264.9033775835994,360.4167366829103" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="15.3,2.4000000000000004 15.3,2.6510000000000002 13.899000000000001,2.6510000000000002" data-type="line" data-label="" points="302.638212065199,369.921021677029 302.638212065199,346.3014619391698 170.80154595866247,346.3014619391698" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="15.5,2.4000000000000004 15.5,2.7510000000000003 15.449,2.7510000000000003" data-type="line" data-label="" points="321.4585783901864,369.921021677029 321.4585783901864,336.8912787766761 316.65938497731463,336.8912787766761" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="15.7,2.4000000000000004 15.7,2.9010000000000002 14.699,2.9010000000000002" data-type="line" data-label="" points="340.2789447151738,369.921021677029 340.2789447151738,322.77600403293553 246.0830112586118,322.77600403293553" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="15.9,2.4000000000000004 15.9,3.051 13.949,3.051" data-type="line" data-label="" points="359.0993110401612,369.921021677029 359.0993110401612,308.66072928919505 175.5066375399092,308.66072928919505" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="16.1,2.4000000000000004 16.1,3.1510000000000002 16.049,3.1510000000000002" data-type="line" data-label="" points="377.9196773651488,369.921021677029 377.9196773651488,299.2505461267013 373.1204839522768,299.2505461267013" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="16.3,2.4000000000000004 16.3,3.301 16.15,3.301" data-type="line" data-label="" points="396.74004369013596,369.921021677029 396.74004369013596,285.1352713829608 382.6247689463953,285.1352713829608" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="16.5,2.4000000000000004 16.5,3.771 16.169999999999998,3.771" data-type="line" data-label="" points="415.56041001512335,369.921021677029 415.56041001512335,240.90741051924044 384.50680557889405,240.90741051924044" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="16.7,2.4000000000000004 16.7,4.241 16.549999999999997,4.241" data-type="line" data-label="" points="434.38077634011074,369.921021677029 434.38077634011074,196.67954965552008 420.26550159636986,196.67954965552008" fill="none" stroke="purple" stroke-width="1px"/></g><g><polyline data-points="16.9,2.4000000000000004 16.9,3.981 17.44,3.981" data-type="line" data-label="" points="453.2011426650979,369.921021677029 453.2011426650979,221.14602587800368 504.0161317425643,221.14602587800368" fill="none" stroke="purple" stroke-width="1px"/></g><g><rect data-type="rect" data-label="schematic_component_0" data-x="16" data-y="1.8" x="264.997479415224" y="369.921021677029" width="207.02402957486174" height="112.92219794992434" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.010626785714285719"/></g><g><rect data-type="rect" data-label="Conn_North" data-x="17.800000000000004" data-y="2.1149999999999998" x="475.7855822550838" y="384.97731473701896" width="124.21441774491609" height="23.525457906234237" fill="rgba(160, 0, 220, 0.14)" stroke="black" stroke-width="0.010626785714285719"/></g><g><rect data-type="rect" data-label="netId: QSPI_CS
2
+ globalConnNetId: connectivity_net0" data-x="14.419" data-y="2.5010000000000003" x="174.56561922365984" y="351.00655352041656" width="90.3377583599397" height="18.820366324987447" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.010626785714285719"/></g><g><rect data-type="rect" data-label="netId: RESET
3
+ globalConnNetId: connectivity_net1" data-x="13.539000000000001" data-y="2.6510000000000002" x="103.04822718870787" y="336.89127877667613" width="67.7533187699546" height="18.820366324987447" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.010626785714285719"/></g><g><rect data-type="rect" data-label="netId: SWCLK
4
+ globalConnNetId: connectivity_net2" data-x="15.089" data-y="2.7510000000000003" x="248.90606620736003" y="327.4810956141823" width="67.7533187699546" height="18.820366324987447" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.010626785714285719"/></g><g><rect data-type="rect" data-label="netId: SWDIO
5
+ globalConnNetId: connectivity_net3" data-x="14.339" data-y="2.9010000000000002" x="178.32969248865754" y="313.3658208704418" width="67.75331876995438" height="18.820366324987447" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.010626785714285719"/></g><g><rect data-type="rect" data-label="netId: USB_D_MINUS
6
+ globalConnNetId: connectivity_net4" data-x="13.229" data-y="3.051" x="39.999999999999886" y="299.25054612670135" width="135.50663753990943" height="18.820366324987447" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.010626785714285719"/></g><g><rect data-type="rect" data-label="netId: USB_D_PLUS
7
+ globalConnNetId: connectivity_net5" data-x="15.389" data-y="3.1510000000000002" x="248.90606620736003" y="289.84036296420754" width="124.21441774491677" height="18.820366324987447" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.010626785714285719"/></g><g><rect data-type="rect" data-label="netId: V3_3
8
+ globalConnNetId: connectivity_net6" data-x="16.15" data-y="3.511" x="354.3942194589142" y="245.6125021004873" width="56.46109897496217" height="39.52276928247352" fill="#ef444466" stroke="#ef4444" stroke-width="0.010626785714285719"/></g><g><rect data-type="rect" data-label="netId: VBUS_5V
9
+ globalConnNetId: connectivity_net7" data-x="16.169999999999998" data-y="3.981" x="339.3379263989243" y="201.38464123676692" width="90.33775835993947" height="39.52276928247352" fill="#ef444466" stroke="#ef4444" stroke-width="0.010626785714285719"/></g><g><rect data-type="rect" data-label="netId: VBAT
10
+ globalConnNetId: connectivity_net8" data-x="16.549999999999997" data-y="4.451" x="392.03495210888866" y="157.15678037304656" width="56.4610989749624" height="39.52276928247352" fill="#ef444466" stroke="#ef4444" stroke-width="0.010626785714285719"/></g><g><rect data-type="rect" data-label="netId: GND
11
+ globalConnNetId: connectivity_net9" data-x="17.44" data-y="3.771" x="481.43169215257956" y="221.14602587800368" width="45.16887917996951" height="39.52276928247352" fill="#00000066" stroke="#000000" stroke-width="0.010626785714285719"/></g><g><circle data-type="point" data-label="schematic_port_0
12
+ y+" data-x="15.1" data-y="2.4000000000000004" cx="283.8178457402116" cy="369.921021677029" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_1
13
+ y+" data-x="15.3" data-y="2.4000000000000004" cx="302.638212065199" cy="369.921021677029" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_2
14
+ y+" data-x="15.5" data-y="2.4000000000000004" cx="321.4585783901864" cy="369.921021677029" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_3
15
+ y+" data-x="15.7" data-y="2.4000000000000004" cx="340.2789447151738" cy="369.921021677029" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_4
16
+ y+" data-x="15.9" data-y="2.4000000000000004" cx="359.0993110401612" cy="369.921021677029" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_5
17
+ y+" data-x="16.1" data-y="2.4000000000000004" cx="377.9196773651488" cy="369.921021677029" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_6
18
+ y+" data-x="16.3" data-y="2.4000000000000004" cx="396.74004369013596" cy="369.921021677029" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_7
19
+ y+" data-x="16.5" data-y="2.4000000000000004" cx="415.56041001512335" cy="369.921021677029" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_8
20
+ y+" data-x="16.7" data-y="2.4000000000000004" cx="434.38077634011074" cy="369.921021677029" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="schematic_port_9
21
+ y+" data-x="16.9" data-y="2.4000000000000004" cx="453.2011426650979" cy="369.921021677029" r="3" fill="hsl(248, 100%, 50%, 0.8)"/></g><g><circle data-type="point" data-label="anchorPoint
22
+ orientation: x-" data-x="14.899000000000001" data-y="2.5010000000000003" cx="264.9033775835994" cy="360.4167366829103" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
23
+ orientation: x-" data-x="13.899000000000001" data-y="2.6510000000000002" cx="170.80154595866247" cy="346.3014619391698" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
24
+ orientation: x-" data-x="15.449" data-y="2.7510000000000003" cx="316.65938497731463" cy="336.8912787766761" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
25
+ orientation: x-" data-x="14.699" data-y="2.9010000000000002" cx="246.0830112586118" cy="322.77600403293553" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
26
+ orientation: x-" data-x="13.949" data-y="3.051" cx="175.5066375399092" cy="308.66072928919505" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
27
+ orientation: x-" data-x="16.049" data-y="3.1510000000000002" cx="373.1204839522768" cy="299.2505461267013" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
28
+ orientation: y+" data-x="16.15" data-y="3.301" cx="382.6247689463953" cy="285.1352713829608" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
29
+ orientation: y+" data-x="16.169999999999998" data-y="3.771" cx="384.50680557889405" cy="240.90741051924044" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
30
+ orientation: y+" data-x="16.549999999999997" data-y="4.241" cx="420.26550159636986" cy="196.67954965552008" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g><circle data-type="point" data-label="anchorPoint
31
+ orientation: y-" data-x="17.44" data-y="3.981" cx="504.0161317425643" cy="221.14602587800368" r="3" fill="hsl(40, 100%, 50%, 0.9)"/></g><g id="crosshair" style="display: none"><line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5"/><line id="crosshair-v" x1="0" x2="640" stroke="#666" stroke-width="0.5"/><text id="coordinates" font-family="monospace" font-size="12" fill="#666"></text></g><script><![CDATA[
32
32
  document.currentScript.parentElement.addEventListener('mousemove', (e) => {
33
33
  const svg = e.currentTarget;
34
34
  const rect = svg.getBoundingClientRect();
@@ -50,7 +50,7 @@ orientation: y+" data-x="16.9" data-y="2.4000000000000004" cx="453.2011426650979
50
50
  v.setAttribute('y2', '640');
51
51
 
52
52
  // Calculate real coordinates using inverse transformation
53
- const matrix = {"a":94.10183162493695,"c":0,"e":-1137.1198117963363,"b":0,"d":-94.10183162493695,"f":551.5375567131574};
53
+ const matrix = {"a":94.10183162493695,"c":0,"e":-1137.1198117963363,"b":0,"d":-94.10183162493695,"f":595.7654175768777};
54
54
  // Manually invert and apply the affine transform
55
55
  // Since we only use translate and scale, we can directly compute:
56
56
  // x' = (x - tx) / sx