@tscircuit/core 0.0.2300 → 0.0.2302

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.
Files changed (2) hide show
  1. package/dist/index.js +885 -124
  2. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -11356,6 +11356,42 @@ var underscorifyPortArrangement = (portArrangement, pinLabels) => {
11356
11356
  return void 0;
11357
11357
  };
11358
11358
 
11359
+ // lib/utils/join-imported-courtyard-outlines.ts
11360
+ function joinImportedCourtyardOutlines(courtyards, componentName) {
11361
+ const polygons = /* @__PURE__ */ new Map();
11362
+ const pending = courtyards.filter(
11363
+ (courtyard) => courtyard.outline.length === 2
11364
+ );
11365
+ const samePoint2 = (a2, b2) => Math.hypot(a2.x - b2.x, a2.y - b2.y) < 1e-6;
11366
+ while (pending.length) {
11367
+ const first = pending.shift();
11368
+ const outline = [...first.outline];
11369
+ while (!samePoint2(outline[0], outline.at(-1))) {
11370
+ const end = outline.at(-1);
11371
+ const nextIndex = pending.findIndex(
11372
+ (candidate) => candidate.pcb_component_id === first.pcb_component_id && candidate.layer === first.layer && candidate.outline.some((point8) => samePoint2(point8, end))
11373
+ );
11374
+ if (nextIndex === -1) {
11375
+ throw new Error(
11376
+ `Imported courtyard for "${componentName}" has disconnected line segments; provide a complete closed polygon.`
11377
+ );
11378
+ }
11379
+ const [next] = pending.splice(nextIndex, 1);
11380
+ outline.push(
11381
+ samePoint2(next.outline[0], end) ? next.outline[1] : next.outline[0]
11382
+ );
11383
+ }
11384
+ outline.pop();
11385
+ if (outline.length < 3) {
11386
+ throw new Error(
11387
+ `Imported courtyard for "${componentName}" needs at least three polygon vertices.`
11388
+ );
11389
+ }
11390
+ polygons.set(first, outline);
11391
+ }
11392
+ return polygons;
11393
+ }
11394
+
11359
11395
  // lib/utils/createComponentsFromCircuitJson.ts
11360
11396
  import { getUnitVectorFromDirection as getUnitVectorFromDirection2 } from "@tscircuit/math-utils";
11361
11397
  import {
@@ -15829,6 +15865,10 @@ var createComponentsFromCircuitJson = ({
15829
15865
  pcbPinLabels,
15830
15866
  preserveSolderPaste = false
15831
15867
  }, circuitJson) => {
15868
+ const joinedCourtyards = joinImportedCourtyardOutlines(
15869
+ circuitJson.filter((elm) => elm.type === "pcb_courtyard_outline"),
15870
+ componentName
15871
+ );
15832
15872
  const importedSourcePorts = circuitJson.filter(
15833
15873
  (elm) => elm.type === "source_port"
15834
15874
  );
@@ -16540,9 +16580,11 @@ var createComponentsFromCircuitJson = ({
16540
16580
  })
16541
16581
  );
16542
16582
  } else if (elm.type === "pcb_courtyard_outline") {
16583
+ const outline = elm.outline.length === 2 ? joinedCourtyards.get(elm) : elm.outline;
16584
+ if (!outline) continue;
16543
16585
  components.push(
16544
16586
  new CourtyardOutline({
16545
- outline: elm.outline,
16587
+ outline,
16546
16588
  layer: elm.layer
16547
16589
  })
16548
16590
  );
@@ -22331,7 +22373,7 @@ function intersectsObstacle(points, obstacle, depth = 0) {
22331
22373
  // lib/utils/system-blocks/round-system-block-route.ts
22332
22374
  import { applyToPoint as applyToPoint33, compose as compose9, rotate as rotate4, translate as translate7 } from "transformation-matrix";
22333
22375
  var EPSILON = 1e-8;
22334
- function roundSystemBlockRoute(route, obstacles) {
22376
+ function roundSystemBlockRoute(route, obstacles, wireObstacles = []) {
22335
22377
  const points = [];
22336
22378
  for (const point8 of route) {
22337
22379
  if (points.length && distance9(points.at(-1), point8) < EPSILON) continue;
@@ -22356,6 +22398,27 @@ function roundSystemBlockRoute(route, obstacles) {
22356
22398
  const previous = points[index2 - 1];
22357
22399
  const corner = points[index2];
22358
22400
  const next = points[index2 + 1];
22401
+ const cornerObstacles = [
22402
+ ...obstacles,
22403
+ ...wireObstacles.map((wire) => {
22404
+ const clearance = Math.min(
22405
+ 3,
22406
+ Math.max(
22407
+ 0,
22408
+ wire.minX - corner.x,
22409
+ corner.x - wire.maxX,
22410
+ wire.minY - corner.y,
22411
+ corner.y - wire.maxY
22412
+ )
22413
+ );
22414
+ return {
22415
+ minX: wire.minX - clearance,
22416
+ maxX: wire.maxX + clearance,
22417
+ minY: wire.minY - clearance,
22418
+ maxY: wire.maxY + clearance
22419
+ };
22420
+ })
22421
+ ];
22359
22422
  const incoming = unitDirection(previous, corner);
22360
22423
  const outgoing = unitDirection(corner, next);
22361
22424
  const turn = Math.atan2(
@@ -22384,7 +22447,7 @@ function roundSystemBlockRoute(route, obstacles) {
22384
22447
  const maxX = Math.max(start.x, corner.x, end.x);
22385
22448
  const minY = Math.min(start.y, corner.y, end.y);
22386
22449
  const maxY = Math.max(start.y, corner.y, end.y);
22387
- const blocked = obstacles.some(
22450
+ const blocked = cornerObstacles.some(
22388
22451
  (obstacle) => minX < obstacle.maxX && maxX > obstacle.minX && minY < obstacle.maxY && maxY > obstacle.minY
22389
22452
  );
22390
22453
  if (!blocked || trim < 1e-3) break;
@@ -22439,103 +22502,744 @@ function unitDirection(a2, b2) {
22439
22502
  return { x: (b2.x - a2.x) / length9, y: (b2.y - a2.y) / length9 };
22440
22503
  }
22441
22504
 
22442
- // lib/utils/system-blocks/layout-system-blocks.ts
22443
- function layoutSystemBlocks(tree, direction) {
22444
- const graph = new dagre.graphlib.Graph({
22445
- compound: true,
22446
- multigraph: true
22447
- });
22448
- graph.setGraph({
22449
- rankdir: { right: "LR", left: "RL", down: "TB", up: "BT" }[direction ?? "right"],
22450
- // Compound borders need room for the renderer's 12 mm heading area.
22451
- nodesep: 24,
22452
- ranksep: 28,
22453
- edgesep: 12,
22454
- marginx: 6,
22455
- marginy: 6
22456
- });
22457
- const blocks = [...tree.groups, ...tree.components];
22458
- for (const block of blocks) {
22459
- const { name, displayName, description } = block._parsedProps;
22460
- const width2 = Math.max(
22461
- 48,
22462
- Math.min(76, (displayName ?? name).length * 1.7 + 8)
22463
- );
22464
- const descriptionLines = description ? Math.ceil(description.length / ((width2 - 8) / 1.3)) : 0;
22465
- graph.setNode(name, {
22466
- width: width2,
22467
- height: Math.max(24, 12 + descriptionLines * 3.5)
22468
- });
22505
+ // lib/utils/system-blocks/measure-system-route-quality.ts
22506
+ function measureSystemRouteQuality(routes) {
22507
+ let crossings = 0;
22508
+ let sharedLength = 0;
22509
+ let closeParallelLength = 0;
22510
+ let length9 = 0;
22511
+ let bends = 0;
22512
+ const segments = routes.map(
22513
+ (route) => route.slice(1).map((end, index2) => {
22514
+ const start = route[index2];
22515
+ length9 += Math.abs(end.x - start.x) + Math.abs(end.y - start.y);
22516
+ return { start, end, horizontal: start.y === end.y };
22517
+ })
22518
+ );
22519
+ for (const route of routes) bends += Math.max(0, route.length - 2);
22520
+ for (let i2 = 0; i2 < segments.length; i2++) {
22521
+ for (let j2 = i2 + 1; j2 < segments.length; j2++) {
22522
+ const intersections2 = /* @__PURE__ */ new Set();
22523
+ for (const a2 of segments[i2])
22524
+ for (const b2 of segments[j2]) {
22525
+ if (a2.horizontal === b2.horizontal) {
22526
+ const along2 = a2.horizontal ? "x" : "y";
22527
+ const across = a2.horizontal ? "y" : "x";
22528
+ const overlap = Math.min(
22529
+ Math.max(a2.start[along2], a2.end[along2]),
22530
+ Math.max(b2.start[along2], b2.end[along2])
22531
+ ) - Math.max(
22532
+ Math.min(a2.start[along2], a2.end[along2]),
22533
+ Math.min(b2.start[along2], b2.end[along2])
22534
+ );
22535
+ if (overlap <= 1e-6) continue;
22536
+ const separation = Math.abs(a2.start[across] - b2.start[across]);
22537
+ if (separation < 1e-6) sharedLength += overlap;
22538
+ if (separation < 3 - 1e-6)
22539
+ closeParallelLength += overlap * (3 - separation) / 3;
22540
+ } else {
22541
+ const h2 = a2.horizontal ? a2 : b2, v2 = a2.horizontal ? b2 : a2;
22542
+ const x2 = v2.start.x, y2 = h2.start.y;
22543
+ if (x2 >= Math.min(h2.start.x, h2.end.x) - 1e-6 && x2 <= Math.max(h2.start.x, h2.end.x) + 1e-6 && y2 >= Math.min(v2.start.y, v2.end.y) - 1e-6 && y2 <= Math.max(v2.start.y, v2.end.y) + 1e-6) {
22544
+ intersections2.add(`${x2.toFixed(6)},${y2.toFixed(6)}`);
22545
+ }
22546
+ }
22547
+ }
22548
+ crossings += intersections2.size;
22549
+ }
22469
22550
  }
22470
- for (const [block, parent] of tree.parents)
22471
- graph.setParent(block.name, parent.name);
22472
- const edges = tree.connections.map((connection, index2) => {
22473
- const { from, to, label } = connection._parsedProps;
22474
- const edge = { v: from, w: to, name: `connection_${index2}` };
22475
- graph.setEdge(edge, {
22476
- width: label === void 0 ? 0 : Math.max(1, label.length * 1.5),
22477
- height: label === void 0 ? 0 : 4,
22478
- labelpos: "l",
22479
- labeloffset: 3
22480
- });
22481
- return edge;
22482
- });
22483
- if (blocks.length) dagre.layout(graph);
22484
- const width = Math.max(32, graph.graph().width ?? 0);
22485
- const height = Math.max(24, graph.graph().height ?? 0);
22486
- const toPage = compose10(translate8(-width / 2, height / 2), scale3(1, -1));
22487
- const bounds = new Map(
22488
- blocks.map((block) => {
22489
- const node = graph.node(block.name);
22490
- return [
22491
- block,
22492
- {
22493
- center: applyToPoint34(toPage, { x: node.x, y: node.y }),
22494
- width: node.width,
22495
- height: node.height
22551
+ return {
22552
+ crossings,
22553
+ sharedLength,
22554
+ closeParallelLength,
22555
+ bends,
22556
+ length: length9,
22557
+ score: crossings * 200 + sharedLength * 100 + closeParallelLength * 10 + bends * 3 + length9 * 0.05
22558
+ };
22559
+ }
22560
+
22561
+ // lib/utils/system-blocks/find-system-orthogonal-path.ts
22562
+ function findSystemOrthogonalPath(start, end, obstacles, routedSegments) {
22563
+ const xs = [
22564
+ .../* @__PURE__ */ new Set([
22565
+ start.x,
22566
+ end.x,
22567
+ ...obstacles.flatMap((r2) => [r2.left, r2.right]),
22568
+ ...routedSegments.filter(([a2, b2]) => a2.x === b2.x).flatMap(([a2]) => [a2.x - 3, a2.x + 3])
22569
+ ])
22570
+ ].sort((a2, b2) => a2 - b2);
22571
+ const ys = [
22572
+ .../* @__PURE__ */ new Set([
22573
+ start.y,
22574
+ end.y,
22575
+ ...obstacles.flatMap((r2) => [r2.bottom, r2.top]),
22576
+ ...routedSegments.filter(([a2, b2]) => a2.y === b2.y).flatMap(([a2]) => [a2.y - 3, a2.y + 3])
22577
+ ])
22578
+ ].sort((a2, b2) => a2 - b2);
22579
+ xs.unshift(xs[0] - 5);
22580
+ xs.push(xs.at(-1) + 5);
22581
+ ys.unshift(ys[0] - 5);
22582
+ ys.push(ys.at(-1) + 5);
22583
+ const columns = xs.length;
22584
+ const pointAt2 = (node) => ({
22585
+ x: xs[node % columns],
22586
+ y: ys[Math.floor(node / columns)]
22587
+ });
22588
+ const startNode = ys.indexOf(start.y) * columns + xs.indexOf(start.x);
22589
+ const endNode = ys.indexOf(end.y) * columns + xs.indexOf(end.x);
22590
+ const costs = new Float64Array(xs.length * ys.length * 3).fill(Infinity);
22591
+ const previous = new Int32Array(costs.length).fill(-1);
22592
+ const horizontalCosts = new Float64Array(xs.length * ys.length).fill(NaN);
22593
+ const verticalCosts = new Float64Array(horizontalCosts.length).fill(NaN);
22594
+ const queue = [];
22595
+ const enqueue = (state, cost2, priority) => {
22596
+ const entry = { state, cost: cost2, priority };
22597
+ queue.push(entry);
22598
+ let i2 = queue.length - 1;
22599
+ while (i2 > 0) {
22600
+ const parent = i2 - 1 >> 1;
22601
+ if (queue[parent].priority <= priority) break;
22602
+ queue[i2] = queue[parent];
22603
+ i2 = parent;
22604
+ }
22605
+ queue[i2] = entry;
22606
+ };
22607
+ costs[startNode * 3 + 2] = 0;
22608
+ enqueue(startNode * 3 + 2, 0, 0);
22609
+ while (queue.length) {
22610
+ const current2 = queue[0];
22611
+ const last = queue.pop();
22612
+ if (queue.length) {
22613
+ let i2 = 0;
22614
+ while (2 * i2 + 1 < queue.length) {
22615
+ let child = 2 * i2 + 1;
22616
+ if (child + 1 < queue.length && queue[child + 1].priority < queue[child].priority)
22617
+ child++;
22618
+ if (last.priority <= queue[child].priority) break;
22619
+ queue[i2] = queue[child];
22620
+ i2 = child;
22621
+ }
22622
+ queue[i2] = last;
22623
+ }
22624
+ if (current2.cost !== costs[current2.state]) continue;
22625
+ const node = Math.floor(current2.state / 3);
22626
+ if (node === endNode) {
22627
+ const path = [];
22628
+ for (let state = current2.state; state !== -1; state = previous[state])
22629
+ path.push(pointAt2(Math.floor(state / 3)));
22630
+ return path.reverse();
22631
+ }
22632
+ const from = pointAt2(node);
22633
+ const col = node % columns;
22634
+ const row = Math.floor(node / columns);
22635
+ const neighbors = [
22636
+ ...col > 0 ? [node - 1] : [],
22637
+ ...col + 1 < columns ? [node + 1] : [],
22638
+ ...row > 0 ? [node - columns] : [],
22639
+ ...row + 1 < ys.length ? [node + columns] : []
22640
+ ];
22641
+ for (const neighbor of neighbors) {
22642
+ const to = pointAt2(neighbor);
22643
+ const horizontal = from.y === to.y;
22644
+ const left = Math.min(from.x, to.x), right = Math.max(from.x, to.x);
22645
+ const bottom = Math.min(from.y, to.y), top = Math.max(from.y, to.y);
22646
+ const edgeCosts = horizontal ? horizontalCosts : verticalCosts;
22647
+ const edgeIndex = Math.min(node, neighbor);
22648
+ let travelCost = edgeCosts[edgeIndex];
22649
+ if (Number.isNaN(travelCost)) {
22650
+ if (obstacles.some(
22651
+ (r2) => horizontal ? from.y > r2.bottom + 1e-6 && from.y < r2.top - 1e-6 && right > r2.left + 1e-6 && left < r2.right - 1e-6 : from.x > r2.left + 1e-6 && from.x < r2.right - 1e-6 && top > r2.bottom + 1e-6 && bottom < r2.top - 1e-6
22652
+ )) {
22653
+ edgeCosts[edgeIndex] = Infinity;
22654
+ continue;
22496
22655
  }
22497
- ];
22656
+ let congestion = 0;
22657
+ for (const [a2, b2] of routedSegments) {
22658
+ const sameAxis = a2.y === b2.y === horizontal;
22659
+ if (sameAxis) {
22660
+ const separation = horizontal ? Math.abs(a2.y - from.y) : Math.abs(a2.x - from.x);
22661
+ if (horizontal && separation < 3)
22662
+ congestion += Math.max(
22663
+ 0,
22664
+ Math.min(right, Math.max(a2.x, b2.x)) - Math.max(left, Math.min(a2.x, b2.x))
22665
+ ) * (separation < 1e-6 ? 40 : (3 - separation) * 8);
22666
+ if (!horizontal && separation < 3)
22667
+ congestion += Math.max(
22668
+ 0,
22669
+ Math.min(top, Math.max(a2.y, b2.y)) - Math.max(bottom, Math.min(a2.y, b2.y))
22670
+ ) * (separation < 1e-6 ? 40 : (3 - separation) * 8);
22671
+ } else if (horizontal ? a2.x >= left && a2.x <= right && from.y >= Math.min(a2.y, b2.y) && from.y <= Math.max(a2.y, b2.y) : a2.y >= bottom && a2.y <= top && from.x >= Math.min(a2.x, b2.x) && from.x <= Math.max(a2.x, b2.x))
22672
+ congestion += 80;
22673
+ }
22674
+ travelCost = right - left + top - bottom + congestion;
22675
+ edgeCosts[edgeIndex] = travelCost;
22676
+ }
22677
+ if (travelCost === Infinity) continue;
22678
+ const axis = horizontal ? 0 : 1;
22679
+ const state = neighbor * 3 + axis;
22680
+ const bend = current2.state % 3 === 2 || current2.state % 3 === axis ? 0 : 6;
22681
+ const cost2 = current2.cost + travelCost + bend;
22682
+ if (cost2 >= costs[state]) continue;
22683
+ costs[state] = cost2;
22684
+ previous[state] = current2.state;
22685
+ enqueue(
22686
+ state,
22687
+ cost2,
22688
+ cost2 + Math.abs(to.x - end.x) + Math.abs(to.y - end.y)
22689
+ );
22690
+ }
22691
+ }
22692
+ }
22693
+
22694
+ // lib/utils/system-blocks/route-system-blocks.ts
22695
+ var SYSTEM_CONNECTION_LABEL_HORIZONTAL_PADDING = 3;
22696
+ function getSystemConnectionLabelWidth(label) {
22697
+ let width = SYSTEM_CONNECTION_LABEL_HORIZONTAL_PADDING;
22698
+ for (const character of label) {
22699
+ width += /[MW@%#&]|[^\x00-\x7f]/.test(character) ? 2.5 : /[A-Z0-9]/.test(character) ? 1.85 : /[il.,:;!| ']/.test(character) ? 0.85 : 1.5;
22700
+ }
22701
+ return width;
22702
+ }
22703
+ function routeSystemBlocks(tree, bounds, initialRoutes) {
22704
+ const obstacles = [...bounds].map(
22705
+ ([block, box3]) => ({
22706
+ left: box3.center.x - box3.width / 2 - 2,
22707
+ right: tree.groups.includes(block) ? box3.center.x - box3.width / 2 + Math.min(
22708
+ box3.width,
22709
+ block._parsedProps.description ? box3.width : (block._parsedProps.displayName ?? block.name).length * 1.8 + 6
22710
+ ) : box3.center.x + box3.width / 2 + 2,
22711
+ bottom: tree.groups.includes(block) ? box3.center.y + box3.height / 2 - (block._parsedProps.description ? 12 : 7) : box3.center.y - box3.height / 2 - 2,
22712
+ top: box3.center.y + box3.height / 2 + 2
22498
22713
  })
22499
22714
  );
22500
- const obstacles = blocks.map((block) => {
22501
- const { center, width: width2, height: height2 } = bounds.get(block);
22502
- const top = center.y + height2 / 2;
22715
+ const portsByBlock = /* @__PURE__ */ new Map();
22716
+ const portsByConnection = /* @__PURE__ */ new Map();
22717
+ for (const connection of tree.connections) {
22718
+ const endpoints = tree.endpoints.get(connection);
22719
+ const route = initialRoutes.get(connection).route;
22720
+ const ports = [endpoints.from, endpoints.to].map((block, end) => {
22721
+ const box3 = bounds.get(block);
22722
+ const target = bounds.get(
22723
+ end === 0 ? endpoints.to : endpoints.from
22724
+ ).center;
22725
+ const guide = initialRoutes.get(connection).label_anchor_center ?? target;
22726
+ const point8 = endpoints.from === endpoints.to ? end === 0 ? route[0] : route.at(-1) : guide;
22727
+ const dx2 = (point8.x - box3.center.x) / box3.width;
22728
+ const dy2 = (point8.y - box3.center.y) / box3.height;
22729
+ const side = Math.abs(dx2) >= Math.abs(dy2) ? dx2 >= 0 ? 0 : 2 : dy2 >= 0 ? 1 : 3;
22730
+ const port = {
22731
+ point: { ...point8 },
22732
+ escape: { ...point8 },
22733
+ side,
22734
+ target: guide,
22735
+ connection
22736
+ };
22737
+ const ports2 = portsByBlock.get(block) ?? [];
22738
+ ports2.push(port);
22739
+ portsByBlock.set(block, ports2);
22740
+ return port;
22741
+ });
22742
+ portsByConnection.set(connection, ports);
22743
+ }
22744
+ for (const [block, ports] of portsByBlock) {
22745
+ const box3 = bounds.get(block);
22746
+ for (const side of [0, 1, 2, 3]) {
22747
+ const horizontal = side % 2 === 0;
22748
+ const facePorts = ports.filter((p2) => p2.side === side).sort(
22749
+ (a2, b2) => horizontal ? (a2.target.y - box3.center.y) / Math.max(1, Math.abs(a2.target.x - box3.center.x)) - (b2.target.y - box3.center.y) / Math.max(1, Math.abs(b2.target.x - box3.center.x)) : (a2.target.x - box3.center.x) / Math.max(1, Math.abs(a2.target.y - box3.center.y)) - (b2.target.x - box3.center.x) / Math.max(1, Math.abs(b2.target.y - box3.center.y))
22750
+ );
22751
+ const span = horizontal ? box3.height : box3.width;
22752
+ const spacing = Math.min(
22753
+ 4,
22754
+ (span - 4) / Math.max(1, facePorts.length - 1)
22755
+ );
22756
+ facePorts.forEach((port, index2) => {
22757
+ const offset = (index2 - (facePorts.length - 1) / 2) * spacing;
22758
+ const sign = side < 2 ? 1 : -1;
22759
+ port.point = horizontal ? {
22760
+ x: box3.center.x + sign * box3.width / 2,
22761
+ y: box3.center.y + offset
22762
+ } : {
22763
+ x: box3.center.x + offset,
22764
+ y: box3.center.y + sign * box3.height / 2
22765
+ };
22766
+ port.escape = horizontal ? { x: port.point.x + sign * 4, y: port.point.y } : { x: port.point.x, y: port.point.y + sign * 4 };
22767
+ });
22768
+ }
22769
+ }
22770
+ const labels = /* @__PURE__ */ new Map();
22771
+ const labelCenters = /* @__PURE__ */ new Map();
22772
+ const labelAttachments = /* @__PURE__ */ new Map();
22773
+ const portObstacles = [...portsByConnection.values()].flat().map(({ escape }) => ({
22774
+ left: escape.x - 1,
22775
+ right: escape.x + 1,
22776
+ bottom: escape.y - 1,
22777
+ top: escape.y + 1
22778
+ }));
22779
+ for (const connection of tree.connections) {
22780
+ const label = connection._parsedProps.label;
22781
+ const center = initialRoutes.get(connection).label_anchor_center;
22782
+ if (label !== void 0 && center) {
22783
+ const halfWidth = getSystemConnectionLabelWidth(label) / 2;
22784
+ const occupied = [...obstacles, ...labels.values(), ...portObstacles];
22785
+ const fits = (rectangle) => !occupied.some(
22786
+ (r2) => rectangle.left < r2.right + 1 && rectangle.right > r2.left - 1 && rectangle.bottom < r2.top + 1 && rectangle.top > r2.bottom - 1
22787
+ );
22788
+ const [start, end] = portsByConnection.get(connection);
22789
+ const endpoints = tree.endpoints.get(connection);
22790
+ const selfLoop = endpoints.from === endpoints.to;
22791
+ const vertical = start.escape.x === end.escape.x;
22792
+ const horizontal = start.escape.y === end.escape.y;
22793
+ const oppositeFaces = (start.side + 2) % 4 === end.side;
22794
+ const straightAxis = vertical && start.side % 2 === 1 ? "y" : horizontal && start.side % 2 === 0 ? "x" : void 0;
22795
+ const straight = !selfLoop && oppositeFaces && straightAxis !== void 0 && (end.escape[straightAxis] - start.escape[straightAxis]) * (start.escape[straightAxis] - start.point[straightAxis]) >= 0;
22796
+ const sameFaceReturn = selfLoop && start.side === end.side;
22797
+ const textFitsHorizontalSpan = horizontal && !selfLoop && Math.abs(start.escape.x - end.escape.x) >= halfWidth * 2;
22798
+ if ((straight || sameFaceReturn) && !textFitsHorizontalSpan && !obstacles.some(
22799
+ (r2) => vertical ? start.escape.x > r2.left && start.escape.x < r2.right && Math.max(start.escape.y, end.escape.y) > r2.bottom && Math.min(start.escape.y, end.escape.y) < r2.top : start.escape.y > r2.bottom && start.escape.y < r2.top && Math.max(start.escape.x, end.escape.x) > r2.left && Math.min(start.escape.x, end.escape.x) < r2.right
22800
+ )) {
22801
+ const attachment = {
22802
+ x: (start.escape.x + end.escape.x) / 2,
22803
+ y: (start.escape.y + end.escape.y) / 2
22804
+ };
22805
+ const signs = selfLoop ? [start.side < 2 ? 1 : -1] : [1, -1];
22806
+ for (const sign of signs) {
22807
+ const candidate = vertical ? { x: attachment.x + sign * (halfWidth + 2), y: attachment.y } : { x: attachment.x, y: attachment.y + sign * 4.5 };
22808
+ const rectangle = {
22809
+ left: candidate.x - halfWidth,
22810
+ right: candidate.x + halfWidth,
22811
+ bottom: candidate.y - 2.5,
22812
+ top: candidate.y + 2.5
22813
+ };
22814
+ if (!fits(rectangle)) continue;
22815
+ labels.set(connection, rectangle);
22816
+ labelCenters.set(connection, candidate);
22817
+ labelAttachments.set(connection, attachment);
22818
+ break;
22819
+ }
22820
+ if (labelAttachments.has(connection)) continue;
22821
+ }
22822
+ let placed = false;
22823
+ for (let radius = 0; !placed; radius++) {
22824
+ const offsets = radius === 0 ? [[0, 0]] : [
22825
+ [0, radius * 6],
22826
+ [0, -radius * 6],
22827
+ [radius * 6, 0],
22828
+ [-radius * 6, 0]
22829
+ ];
22830
+ for (const [dx2, dy2] of offsets) {
22831
+ const candidate = { x: center.x + dx2, y: center.y + dy2 };
22832
+ const rectangle = {
22833
+ left: candidate.x - halfWidth,
22834
+ right: candidate.x + halfWidth,
22835
+ bottom: candidate.y - 2.5,
22836
+ top: candidate.y + 2.5
22837
+ };
22838
+ if (!fits(rectangle)) continue;
22839
+ labels.set(connection, rectangle);
22840
+ labelCenters.set(connection, candidate);
22841
+ placed = true;
22842
+ break;
22843
+ }
22844
+ }
22845
+ }
22846
+ }
22847
+ const routeConnection = (connection, routedSegments) => {
22848
+ const [start, end] = portsByConnection.get(connection);
22849
+ const label = labels.get(connection);
22850
+ const attachment = labelAttachments.get(connection);
22851
+ const blocked = [
22852
+ ...obstacles,
22853
+ ...[...labels].filter(([c2]) => c2 !== connection).map(([, r2]) => r2)
22854
+ ];
22855
+ const waypoints = [start.escape];
22856
+ if (attachment) {
22857
+ waypoints.push(attachment);
22858
+ } else if (label) {
22859
+ const y2 = labelCenters.get(connection).y;
22860
+ const labelPorts = [
22861
+ { x: label.left, y: y2 },
22862
+ { x: label.right, y: y2 }
22863
+ ];
22864
+ if (start.escape.x > end.escape.x) labelPorts.reverse();
22865
+ waypoints.push(...labelPorts);
22866
+ }
22867
+ waypoints.push(end.escape);
22868
+ const route = [start.point];
22869
+ for (let i2 = 1; i2 < waypoints.length; i2++) {
22870
+ const legObstacles = label && (attachment || i2 !== 2) ? [...blocked, label] : blocked;
22871
+ const leg = findSystemOrthogonalPath(
22872
+ waypoints[i2 - 1],
22873
+ waypoints[i2],
22874
+ legObstacles,
22875
+ routedSegments
22876
+ );
22877
+ if (!leg) {
22878
+ throw new Error(
22879
+ `Cannot route system connection from "${connection._parsedProps.from}" to "${connection._parsedProps.to}" around blocks and labels`
22880
+ );
22881
+ }
22882
+ route.push(...leg);
22883
+ }
22884
+ route.push(end.point);
22885
+ const simplified = [];
22886
+ for (const point8 of route) {
22887
+ const previous = simplified.at(-1);
22888
+ if (previous?.x === point8.x && previous.y === point8.y) continue;
22889
+ const before = simplified.at(-2);
22890
+ if (before && previous && (before.x === previous.x && previous.x === point8.x || before.y === previous.y && previous.y === point8.y))
22891
+ simplified.pop();
22892
+ simplified.push(point8);
22893
+ }
22503
22894
  return {
22504
- minX: center.x - width2 / 2,
22505
- maxX: center.x + width2 / 2,
22506
- minY: tree.groups.some((group) => group === block) ? top - 12 : center.y - height2 / 2,
22507
- maxY: top
22895
+ route: simplified,
22896
+ label_anchor_center: labelCenters.get(connection)
22508
22897
  };
22898
+ };
22899
+ const connections = [...tree.connections].sort((a2, b2) => {
22900
+ const distance29 = (connection) => {
22901
+ const [from, to] = portsByConnection.get(connection);
22902
+ return Math.abs(from.point.x - to.point.x) + Math.abs(from.point.y - to.point.y);
22903
+ };
22904
+ return distance29(a2) - distance29(b2) || a2._parsedProps.from.localeCompare(b2._parsedProps.from) || a2._parsedProps.to.localeCompare(b2._parsedProps.to) || (a2._parsedProps.label ?? "").localeCompare(b2._parsedProps.label ?? "");
22509
22905
  });
22510
- for (const [index2, connection] of tree.connections.entries()) {
22511
- if (connection._parsedProps.label === void 0) continue;
22512
- const edge = graph.edge(edges[index2]);
22513
- const label = applyToPoint34(toPage, { x: edge.x, y: edge.y });
22906
+ const segmentsOf = (resolvedRoutes) => [...resolvedRoutes].flatMap(
22907
+ ({ route }) => route.slice(1).map((point8, index2) => [route[index2], point8])
22908
+ );
22909
+ let routes = /* @__PURE__ */ new Map();
22910
+ for (const connection of connections)
22911
+ routes.set(
22912
+ connection,
22913
+ routeConnection(connection, segmentsOf(routes.values()))
22914
+ );
22915
+ let score = measureSystemRouteQuality(
22916
+ [...routes.values()].map((r2) => r2.route)
22917
+ ).score;
22918
+ const reroute = (affected) => {
22919
+ const candidate = new Map(
22920
+ [...routes].filter(([connection]) => !affected.has(connection))
22921
+ );
22922
+ for (const connection of connections.filter((c2) => affected.has(c2)))
22923
+ candidate.set(
22924
+ connection,
22925
+ routeConnection(connection, segmentsOf(candidate.values()))
22926
+ );
22927
+ const candidateScore = measureSystemRouteQuality(
22928
+ [...candidate.values()].map((r2) => r2.route)
22929
+ ).score;
22930
+ if (candidateScore >= score - 1e-6) return false;
22931
+ routes = candidate;
22932
+ score = candidateScore;
22933
+ return true;
22934
+ };
22935
+ let proposals = 0;
22936
+ const proposalLimit = Math.min(64, connections.length * 4);
22937
+ for (let pass = 0; pass < 4 && proposals < proposalLimit; pass++) {
22938
+ let improved = false;
22939
+ for (const [block, ports] of portsByBlock) {
22940
+ if (ports.length < 2) continue;
22941
+ const center = bounds.get(block).center;
22942
+ const perimeter = [...ports].sort(
22943
+ (a2, b2) => Math.atan2(a2.point.y - center.y, a2.point.x - center.x) - Math.atan2(b2.point.y - center.y, b2.point.x - center.x)
22944
+ );
22945
+ for (let i2 = 0; i2 < perimeter.length && proposals < proposalLimit; i2++) {
22946
+ proposals++;
22947
+ const a2 = perimeter[i2], b2 = perimeter[(i2 + 1) % perimeter.length];
22948
+ const swap2 = () => {
22949
+ ;
22950
+ [a2.point, b2.point] = [b2.point, a2.point];
22951
+ [a2.escape, b2.escape] = [b2.escape, a2.escape];
22952
+ [a2.side, b2.side] = [b2.side, a2.side];
22953
+ };
22954
+ swap2();
22955
+ if (reroute(/* @__PURE__ */ new Set([a2.connection, b2.connection]))) {
22956
+ improved = true;
22957
+ perimeter[i2] = b2;
22958
+ perimeter[(i2 + 1) % perimeter.length] = a2;
22959
+ } else swap2();
22960
+ }
22961
+ }
22962
+ if (!improved) break;
22963
+ const quality = measureSystemRouteQuality(
22964
+ [...routes.values()].map((r2) => r2.route)
22965
+ );
22966
+ if (quality.crossings === 0 && quality.sharedLength === 0 && quality.closeParallelLength === 0)
22967
+ break;
22968
+ }
22969
+ for (const connection of connections) reroute(/* @__PURE__ */ new Set([connection]));
22970
+ return routes;
22971
+ }
22972
+
22973
+ // lib/utils/system-blocks/round-system-block-connections.ts
22974
+ function roundSystemBlockConnections(tree, bounds, routes) {
22975
+ const obstacles = [...bounds].map(
22976
+ ([block, box3]) => ({
22977
+ minX: box3.center.x - box3.width / 2,
22978
+ maxX: box3.center.x + box3.width / 2,
22979
+ minY: tree.groups.includes(block) ? box3.center.y + box3.height / 2 - (block._parsedProps.description ? 12 : 7) : box3.center.y - box3.height / 2,
22980
+ maxY: box3.center.y + box3.height / 2
22981
+ })
22982
+ );
22983
+ for (const [connection, resolved] of routes) {
22984
+ const label = resolved.label_anchor_center;
22985
+ if (!label) continue;
22986
+ const halfWidth = (getSystemConnectionLabelWidth(connection._parsedProps.label) - SYSTEM_CONNECTION_LABEL_HORIZONTAL_PADDING) / 2;
22514
22987
  obstacles.push({
22515
- minX: label.x - edge.width / 2 - 0.5,
22516
- maxX: label.x + edge.width / 2 + 0.5,
22517
- minY: label.y - edge.height / 2 - 0.5,
22518
- maxY: label.y + edge.height / 2 + 0.5
22988
+ minX: label.x - halfWidth,
22989
+ maxX: label.x + halfWidth,
22990
+ minY: label.y - 2.5,
22991
+ maxY: label.y + 2.5
22992
+ });
22993
+ }
22994
+ const rounded = new Map(routes);
22995
+ for (const [connection, resolved] of routes) {
22996
+ const wireObstacles = [];
22997
+ for (const [other, { route }] of rounded) {
22998
+ if (other === connection) continue;
22999
+ for (let index2 = 1; index2 < route.length; index2++) {
23000
+ const end = route[index2];
23001
+ const points = [
23002
+ route[index2 - 1],
23003
+ ...end.cubic_bezier_control_points ?? [],
23004
+ end
23005
+ ];
23006
+ wireObstacles.push({
23007
+ minX: Math.min(...points.map((p2) => p2.x)),
23008
+ maxX: Math.max(...points.map((p2) => p2.x)),
23009
+ minY: Math.min(...points.map((p2) => p2.y)),
23010
+ maxY: Math.max(...points.map((p2) => p2.y))
23011
+ });
23012
+ }
23013
+ }
23014
+ const blocked = [
23015
+ ...obstacles,
23016
+ ...wireObstacles.map((wire) => ({
23017
+ minX: wire.minX - 3,
23018
+ maxX: wire.maxX + 3,
23019
+ minY: wire.minY - 3,
23020
+ maxY: wire.maxY + 3
23021
+ }))
23022
+ ];
23023
+ const { from, to } = tree.endpoints.get(connection);
23024
+ const selfLoop = from === to ? createSystemBlockSelfLoop(resolved.route, bounds.get(from), blocked) : void 0;
23025
+ rounded.set(connection, {
23026
+ ...resolved,
23027
+ route: selfLoop ?? roundSystemBlockRoute(resolved.route, obstacles, wireObstacles)
22519
23028
  });
22520
23029
  }
22521
- const routes = new Map(
22522
- tree.connections.map((connection, index2) => {
22523
- const edge = graph.edge(edges[index2]);
22524
- const route = edge.points.map((point8) => applyToPoint34(toPage, point8)).filter(
22525
- (point8, index3, points) => index3 === 0 || point8.x !== points[index3 - 1].x || point8.y !== points[index3 - 1].y
23030
+ return rounded;
23031
+ }
23032
+
23033
+ // lib/utils/system-blocks/layout-system-blocks.ts
23034
+ function layoutSystemBlocks(tree, direction) {
23035
+ const degrees2 = /* @__PURE__ */ new Map();
23036
+ for (const {
23037
+ _parsedProps: { from, to }
23038
+ } of tree.connections) {
23039
+ degrees2.set(from, (degrees2.get(from) ?? 0) + 1);
23040
+ degrees2.set(to, (degrees2.get(to) ?? 0) + 1);
23041
+ }
23042
+ const layout = layoutContainer(tree, void 0, direction ?? "right", degrees2);
23043
+ const toPage = compose10(
23044
+ translate8(-layout.width / 2, layout.height / 2),
23045
+ scale3(1, -1)
23046
+ );
23047
+ for (const box3 of layout.bounds.values())
23048
+ box3.center = applyToPoint34(toPage, box3.center);
23049
+ for (const [connection, resolved] of layout.routes) {
23050
+ resolved.route = resolved.route.map((point8) => applyToPoint34(toPage, point8));
23051
+ if (resolved.label_anchor_center)
23052
+ resolved.label_anchor_center = applyToPoint34(
23053
+ toPage,
23054
+ resolved.label_anchor_center
23055
+ );
23056
+ const endpoints = tree.endpoints.get(connection);
23057
+ for (const [block, index2] of [
23058
+ [endpoints.from, 0],
23059
+ [endpoints.to, resolved.route.length - 1]
23060
+ ]) {
23061
+ const box3 = layout.bounds.get(block);
23062
+ const point8 = resolved.route[index2];
23063
+ let dx2 = point8.x - box3.center.x, dy2 = point8.y - box3.center.y;
23064
+ if (dx2 === 0 && dy2 === 0) {
23065
+ const neighbor = resolved.route[index2 === 0 ? 1 : index2 - 1];
23066
+ dx2 = neighbor.x - point8.x;
23067
+ dy2 = neighbor.y - point8.y;
23068
+ }
23069
+ const factor = Math.max(
23070
+ Math.abs(dx2) / (box3.width / 2),
23071
+ Math.abs(dy2) / (box3.height / 2)
22526
23072
  );
22527
- const label_anchor_center = connection._parsedProps.label === void 0 ? void 0 : applyToPoint34(toPage, { x: edge.x, y: edge.y });
22528
- const { from, to } = tree.endpoints.get(connection);
22529
- const selfLoop = from === to ? createSystemBlockSelfLoop(route, bounds.get(from), obstacles) : void 0;
22530
- return [
22531
- connection,
22532
- {
22533
- route: selfLoop ?? roundSystemBlockRoute(route, obstacles),
22534
- label_anchor_center
22535
- }
22536
- ];
22537
- })
23073
+ resolved.route[index2] = {
23074
+ x: box3.center.x + dx2 / factor,
23075
+ y: box3.center.y + dy2 / factor
23076
+ };
23077
+ }
23078
+ }
23079
+ const routes = roundSystemBlockConnections(
23080
+ tree,
23081
+ layout.bounds,
23082
+ routeSystemBlocks(tree, layout.bounds, layout.routes)
22538
23083
  );
23084
+ let width = layout.width, height = layout.height;
23085
+ for (const [connection, resolved] of routes) {
23086
+ for (const point8 of resolved.route) {
23087
+ width = Math.max(width, 2 * (Math.abs(point8.x) + 6));
23088
+ height = Math.max(height, 2 * (Math.abs(point8.y) + 6));
23089
+ }
23090
+ if (resolved.label_anchor_center) {
23091
+ width = Math.max(
23092
+ width,
23093
+ 2 * (Math.abs(resolved.label_anchor_center.x) + getSystemConnectionLabelWidth(connection._parsedProps.label) / 2 + 6)
23094
+ );
23095
+ height = Math.max(
23096
+ height,
23097
+ 2 * (Math.abs(resolved.label_anchor_center.y) + 8)
23098
+ );
23099
+ }
23100
+ }
23101
+ return { width, height, bounds: layout.bounds, routes };
23102
+ }
23103
+ function layoutContainer(tree, parent, direction, degrees2) {
23104
+ const children = [
23105
+ ...tree.groups,
23106
+ ...tree.components
23107
+ ].filter((block) => tree.parents.get(block) === parent);
23108
+ const groupLayouts = /* @__PURE__ */ new Map();
23109
+ const graph = new dagre.graphlib.Graph({ multigraph: true });
23110
+ graph.setGraph({
23111
+ rankdir: { right: "LR", left: "RL", down: "TB", up: "BT" }[direction],
23112
+ nodesep: parent ? 12 : 18,
23113
+ ranksep: 18,
23114
+ edgesep: 8,
23115
+ marginx: 8,
23116
+ marginy: 8
23117
+ });
23118
+ for (const block of children) {
23119
+ if (tree.groups.includes(block)) {
23120
+ const child = layoutContainer(
23121
+ tree,
23122
+ block,
23123
+ direction,
23124
+ degrees2
23125
+ );
23126
+ groupLayouts.set(block, child);
23127
+ graph.setNode(block.name, { width: child.width, height: child.height });
23128
+ } else {
23129
+ const { name, displayName, description } = block._parsedProps;
23130
+ const width2 = Math.max(
23131
+ 32,
23132
+ Math.min(62, (displayName ?? name).length * 1.5 + 8)
23133
+ );
23134
+ const descriptionLines = description ? Math.ceil(description.length / ((width2 - 8) / 1.3)) : 0;
23135
+ graph.setNode(name, {
23136
+ width: width2,
23137
+ height: Math.max(18, 12 + descriptionLines * 3.5)
23138
+ });
23139
+ }
23140
+ }
23141
+ const childFor = (block) => {
23142
+ let current2 = block;
23143
+ while (current2 && tree.parents.get(current2) !== parent)
23144
+ current2 = tree.parents.get(current2);
23145
+ return current2;
23146
+ };
23147
+ const edges = /* @__PURE__ */ new Map();
23148
+ for (const [index2, connection] of tree.connections.entries()) {
23149
+ const endpoints = tree.endpoints.get(connection);
23150
+ const from = childFor(endpoints.from), to = childFor(endpoints.to);
23151
+ if (!from || !to || from === to && (from !== endpoints.from || to !== endpoints.to))
23152
+ continue;
23153
+ const props = connection._parsedProps;
23154
+ const reverse4 = props.direction === "both" && degrees2.get(props.to) > degrees2.get(props.from);
23155
+ const edge = {
23156
+ v: (reverse4 ? to : from).name,
23157
+ w: (reverse4 ? from : to).name,
23158
+ name: `connection_${index2}`
23159
+ };
23160
+ graph.setEdge(edge, {
23161
+ width: props.label === void 0 ? 0 : getSystemConnectionLabelWidth(props.label),
23162
+ height: props.label === void 0 ? 0 : 5,
23163
+ labelpos: "c",
23164
+ labeloffset: 0
23165
+ });
23166
+ edges.set(connection, edge);
23167
+ }
23168
+ if (children.length) dagre.layout(graph);
23169
+ const heading = parent ? parent._parsedProps.description ? 14 : 8 : 0;
23170
+ const minimumWidth = parent ? Math.min(
23171
+ 90,
23172
+ (parent._parsedProps.displayName ?? parent.name).length * 1.8 + 8
23173
+ ) : 32;
23174
+ const width = Math.max(minimumWidth, graph.graph().width ?? 0);
23175
+ const height = Math.max(24, (graph.graph().height ?? 0) + heading);
23176
+ const bounds = /* @__PURE__ */ new Map();
23177
+ const routes = /* @__PURE__ */ new Map();
23178
+ for (const block of children) {
23179
+ const node = graph.node(block.name);
23180
+ bounds.set(block, {
23181
+ center: { x: node.x, y: node.y + heading },
23182
+ width: node.width,
23183
+ height: node.height
23184
+ });
23185
+ const child = groupLayouts.get(block);
23186
+ if (!child) continue;
23187
+ const offset = {
23188
+ x: node.x - child.width / 2,
23189
+ y: node.y + heading - child.height / 2
23190
+ };
23191
+ const shift = (point8) => ({
23192
+ x: point8.x + offset.x,
23193
+ y: point8.y + offset.y
23194
+ });
23195
+ for (const [descendant, box3] of child.bounds)
23196
+ bounds.set(descendant, { ...box3, center: shift(box3.center) });
23197
+ for (const [connection, resolved] of child.routes)
23198
+ routes.set(connection, {
23199
+ route: resolved.route.map(shift),
23200
+ label_anchor_center: resolved.label_anchor_center ? shift(resolved.label_anchor_center) : void 0
23201
+ });
23202
+ }
23203
+ for (const [connection, edgeKey] of edges) {
23204
+ const edge = graph.edge(edgeKey);
23205
+ const route = edge.points.map((point8) => ({
23206
+ x: point8.x,
23207
+ y: point8.y + heading
23208
+ }));
23209
+ const from = childFor(tree.endpoints.get(connection).from);
23210
+ if (edgeKey.v !== from.name) route.reverse();
23211
+ routes.set(connection, {
23212
+ route,
23213
+ label_anchor_center: connection._parsedProps.label === void 0 ? void 0 : { x: edge.x, y: edge.y + heading }
23214
+ });
23215
+ }
23216
+ const bundles = /* @__PURE__ */ new Map();
23217
+ for (const [connection, edge] of edges) {
23218
+ if (connection._parsedProps.label === void 0) continue;
23219
+ const targets = bundles.get(edge.v) ?? /* @__PURE__ */ new Map();
23220
+ const connections = targets.get(edge.w) ?? [];
23221
+ connections.push(connection);
23222
+ targets.set(edge.w, connections);
23223
+ bundles.set(edge.v, targets);
23224
+ }
23225
+ const crossAxis = direction === "right" || direction === "left" ? "y" : "x";
23226
+ for (const targets of bundles.values()) {
23227
+ for (const connections of targets.values()) {
23228
+ const slots = connections.map((c2) => routes.get(c2).label_anchor_center).sort((a2, b2) => a2[crossAxis] - b2[crossAxis]);
23229
+ const destinationPosition = (connection) => {
23230
+ const endpoints = tree.endpoints.get(connection);
23231
+ const targetChild = edges.get(connection).w;
23232
+ const endpoint = childFor(endpoints.to).name === targetChild ? endpoints.to : endpoints.from;
23233
+ return bounds.get(endpoint).center[crossAxis];
23234
+ };
23235
+ connections.sort(
23236
+ (a2, b2) => destinationPosition(a2) - destinationPosition(b2)
23237
+ );
23238
+ connections.forEach((connection, index2) => {
23239
+ routes.get(connection).label_anchor_center = slots[index2];
23240
+ });
23241
+ }
23242
+ }
22539
23243
  return { width, height, bounds, routes };
22540
23244
  }
22541
23245
 
@@ -22634,7 +23338,7 @@ function SystemBlockPage_doInitialSystemBlockRender(page) {
22634
23338
  }
22635
23339
  for (const connection of tree.connections) {
22636
23340
  const { from, to } = tree.endpoints.get(connection);
22637
- const { name: name2, kind, direction: direction2, label } = connection._parsedProps;
23341
+ const { name: name2, kind, direction: direction2, label, color: color3, lineStyle } = connection._parsedProps;
22638
23342
  db2.system_block_connection.insert({
22639
23343
  system_block_page_id: page.system_block_page_id,
22640
23344
  from_system_block_component_id: componentIds.get(from),
@@ -22642,6 +23346,8 @@ function SystemBlockPage_doInitialSystemBlockRender(page) {
22642
23346
  name: name2,
22643
23347
  label,
22644
23348
  direction: direction2,
23349
+ color: color3,
23350
+ line_style: lineStyle,
22645
23351
  is_signal: kind === "signal",
22646
23352
  is_power: kind === "power",
22647
23353
  ...layout.routes.get(connection)
@@ -69965,11 +70671,14 @@ function extractTraceCopper3(trace, layerNames) {
69965
70671
  type: "via",
69966
70672
  center: { x: routePoint.x, y: routePoint.y },
69967
70673
  diameter: routePoint.via_diameter ?? 0,
69968
- layers: getLayerSpan3(
69969
- routePoint.from_layer,
69970
- routePoint.to_layer,
69971
- layerNames
69972
- )
70674
+ layers: getRouteViaSpanLayers3({
70675
+ fromLayer: routePoint.from_layer,
70676
+ toLayer: routePoint.to_layer,
70677
+ layers: "layers" in routePoint && Array.isArray(routePoint.layers) ? routePoint.layers : void 0,
70678
+ layerNames,
70679
+ // Preserve endpoint-derived spans when physical metadata is absent.
70680
+ allowBlindAndBuriedVias: true
70681
+ })
69973
70682
  });
69974
70683
  previousWire = void 0;
69975
70684
  continue;
@@ -84818,7 +85527,12 @@ function routeAlternateSignalDogbones(input, options, attempt) {
84818
85527
  },
84819
85528
  connections: input.connections.map((c2) => ({
84820
85529
  ...c2,
84821
- pointsToConnect: c2.pointsToConnect.map((p2) => rotate22(p2, turns))
85530
+ pointsToConnect: c2.pointsToConnect.map((p2) => {
85531
+ const point52 = rotate22(p2, turns);
85532
+ if (!point52.layers) return point52;
85533
+ const { layer: _layer, ...handoff } = point52;
85534
+ return handoff;
85535
+ })
84822
85536
  })),
84823
85537
  obstacles: input.obstacles.map((o2) => ({
84824
85538
  ...o2,
@@ -87589,7 +88303,7 @@ function initialSignalDogbones(input, options, attempt) {
87589
88303
  throw error;
87590
88304
  const targets = new Map(options.targetLayers);
87591
88305
  const available = (c2, layer) => (!input.allowedLayers || input.allowedLayers.includes(layer)) && c2.pointsToConnect.every(
87592
- (p2) => p2.layer === layer && isUnroutedComponentPad(input, c2, p2)
88306
+ (p2) => (p2.layers ?? [p2.layer]).includes(layer) && (p2.layers !== void 0 || isUnroutedComponentPad(input, c2, p2))
87593
88307
  ) && (input.buses ?? []).every(
87594
88308
  (b2) => !b2.connectionNames.includes(c2.name) || !b2.allowedLayers || b2.allowedLayers.includes(layer)
87595
88309
  );
@@ -87732,6 +88446,7 @@ function* routeMultiTerminalBuses(input, options) {
87732
88446
  }).sort((a2, b2) => b2.padCount - a2.padCount).map(({ terminal }) => terminal)
87733
88447
  }));
87734
88448
  const completed = [];
88449
+ const physicalLayers = getCopperLayerNames3(input.layerCount);
87735
88450
  let groups = [
87736
88451
  connections.map((connection) => ({
87737
88452
  ...connection,
@@ -87758,8 +88473,6 @@ function* routeMultiTerminalBuses(input, options) {
87758
88473
  (pair) => pair.connectionNames.every((name) => names.has(name))
87759
88474
  )
87760
88475
  };
87761
- if (terminalIndex > 1)
87762
- request.allowedLayers = [group[0].pointsToConnect[1].layer];
87763
88476
  const solver = new BusLanesPipelineSolver(request, options);
87764
88477
  while (!solver.solved && !solver.failed) {
87765
88478
  solver.step();
@@ -87780,18 +88493,36 @@ function* routeMultiTerminalBuses(input, options) {
87780
88493
  if (!terminal) continue;
87781
88494
  if (!effectiveCarrierLayers(input, connection).includes(terminal.layer))
87782
88495
  throw Error("No permitted surface layer for a multi-terminal branch");
87783
- const attachments = completed.filter((trace) => trace.connection_name === connection.name).flatMap(
87784
- (trace) => trace.route.flatMap((point52) => {
87785
- if (point52.route_type === "wire" && point52.layer === terminal.layer)
87786
- return [point52];
87787
- if (point52.route_type === "via" && (point52.layers?.includes(terminal.layer) || point52.from_layer === terminal.layer || point52.to_layer === terminal.layer))
87788
- return [{ x: point52.x, y: point52.y, layer: terminal.layer }];
87789
- return [];
87790
- })
87791
- );
87792
- attachments.sort(
87793
- (a2, b2) => Math.hypot(a2.x - terminal.x, a2.y - terminal.y) - Math.hypot(b2.x - terminal.x, b2.y - terminal.y)
87794
- );
88496
+ const attachments = completed.filter((trace) => trace.connection_name === connection.name).flatMap((trace) => {
88497
+ const vias = trace.route.flatMap((point52) => {
88498
+ if (point52.route_type !== "via") return [];
88499
+ const layers = getRouteViaSpanLayers3({
88500
+ fromLayer: point52.from_layer,
88501
+ toLayer: point52.to_layer,
88502
+ layers: point52.layers,
88503
+ layerNames: physicalLayers,
88504
+ allowBlindAndBuriedVias: input.allowBlindAndBuriedVias ?? false
88505
+ });
88506
+ if (!layers.includes(terminal.layer)) return [];
88507
+ return [{ x: point52.x, y: point52.y, layer: terminal.layer, layers }];
88508
+ });
88509
+ return [
88510
+ ...vias,
88511
+ ...trace.route.flatMap(
88512
+ (point52) => point52.route_type === "wire" && point52.layer === terminal.layer ? [point52] : []
88513
+ )
88514
+ ];
88515
+ });
88516
+ const carrierLayers = effectiveCarrierLayers(input, connection);
88517
+ attachments.sort((a2, b2) => {
88518
+ const reachesAnotherCarrierA = !!a2.layers?.some(
88519
+ (layer) => layer !== terminal.layer && carrierLayers.includes(layer)
88520
+ );
88521
+ const reachesAnotherCarrierB = !!b2.layers?.some(
88522
+ (layer) => layer !== terminal.layer && carrierLayers.includes(layer)
88523
+ );
88524
+ return Number(reachesAnotherCarrierB) - Number(reachesAnotherCarrierA) || Math.hypot(a2.x - terminal.x, a2.y - terminal.y) - Math.hypot(b2.x - terminal.x, b2.y - terminal.y);
88525
+ });
87795
88526
  if (!attachments.length)
87796
88527
  throw Error("No routed copper for a multi-terminal branch");
87797
88528
  const branch = {
@@ -114658,16 +115389,18 @@ function* expandedSignalSites({
114658
115389
  );
114659
115390
  for (const end of [0, 1]) {
114660
115391
  const pad = connection.pointsToConnect[end];
114661
- if (pad.layer === targetLayer2) {
115392
+ if ((pad.layers ?? [pad.layer]).includes(targetLayer2)) {
115393
+ const { layers: _layers, ...fixedPad } = pad;
115394
+ const point52 = { ...fixedPad, layer: targetLayer2 };
114662
115395
  ends[end].push({
114663
- point: pad,
115396
+ point: point52,
114664
115397
  length: 0,
114665
115398
  escape: {
114666
115399
  type: "pcb_trace",
114667
115400
  pcb_trace_id: `expanded_surface_${connection.name}_${end}`,
114668
115401
  connection_name: connection.name,
114669
115402
  source_trace_id: connection.source_trace_id ?? connection.name,
114670
- route: [{ ...pad, route_type: "wire", width }]
115403
+ route: [{ ...point52, route_type: "wire", width }]
114671
115404
  }
114672
115405
  });
114673
115406
  continue;
@@ -116208,9 +116941,12 @@ function* negotiateSignalSites(state, remove, stopWithOneRemaining = false, expa
116208
116941
  const carrierLayers = signalLayers(native, connection);
116209
116942
  for (let end = 0; end < 2; end++) {
116210
116943
  const point52 = connection.pointsToConnect[end];
116211
- if (carrierLayers.includes(point52.layer))
116944
+ for (const layer of carrierLayers) {
116945
+ if (!(point52.layers ?? [point52.layer]).includes(layer)) continue;
116946
+ const { layers: _layers, ...fixedPoint } = point52;
116947
+ const handoff = { ...fixedPoint, layer };
116212
116948
  ends[end].push({
116213
- point: point52,
116949
+ point: handoff,
116214
116950
  escape: {
116215
116951
  type: "pcb_trace",
116216
116952
  pcb_trace_id: `local_surface_${connection.name}_${end}`,
@@ -116218,14 +116954,14 @@ function* negotiateSignalSites(state, remove, stopWithOneRemaining = false, expa
116218
116954
  source_trace_id: connection.source_trace_id ?? connection.name,
116219
116955
  route: [
116220
116956
  {
116221
- ...point52,
116957
+ ...handoff,
116222
116958
  route_type: "wire",
116223
- layer: point52.layer,
116224
116959
  width: signalWidth(native, connection)
116225
116960
  }
116226
116961
  ]
116227
116962
  }
116228
116963
  });
116964
+ }
116229
116965
  }
116230
116966
  for (let variant = 0; variant < 4; variant++) {
116231
116967
  try {
@@ -122123,6 +122859,29 @@ var BusLanesPipelineSolver = class extends BaseSolver9 {
122123
122859
  this.phase = "native_pair_approaches";
122124
122860
  }
122125
122861
  }
122862
+ isNegotiableTerminal(connection, point52) {
122863
+ if (isUnroutedComponentPad(this.routingInput, connection, point52))
122864
+ return true;
122865
+ if (!point52.layers?.length || !point52.layers.includes(point52.layer))
122866
+ return false;
122867
+ const physical = getCopperLayerNames3(this.routingInput.layerCount);
122868
+ return !!this.routingInput.traces?.some(
122869
+ (trace) => (trace.connection_name === connection.name || connection.source_trace_id !== void 0 && trace.source_trace_id === connection.source_trace_id) && trace.route.some((via) => {
122870
+ if (via.route_type !== "via" || Math.hypot(via.x - point52.x, via.y - point52.y) > 1e-8)
122871
+ return false;
122872
+ const span = getRouteViaSpanLayers3({
122873
+ fromLayer: via.from_layer,
122874
+ toLayer: via.to_layer,
122875
+ layers: via.layers,
122876
+ layerNames: physical,
122877
+ allowBlindAndBuriedVias: this.routingInput.allowBlindAndBuriedVias ?? false
122878
+ });
122879
+ return point52.layers.every(
122880
+ (layer) => physical.includes(layer) && span.includes(layer)
122881
+ );
122882
+ })
122883
+ );
122884
+ }
122126
122885
  getConstructorParams() {
122127
122886
  return [this.input, this.options];
122128
122887
  }
@@ -122442,8 +123201,10 @@ var BusLanesPipelineSolver = class extends BaseSolver9 {
122442
123201
  )
122443
123202
  );
122444
123203
  this.canNegotiateGeneratedSites = !!(usedNativeFallback && this.routingInput.traces?.length && this.options.denseSearch && this.options.smoothTuning && !this.routingInput.differentialPairs?.length && this.routingInput.connections.every(
122445
- (connection) => connection.pointsToConnect.every(
123204
+ (connection) => connection.pointsToConnect.some(
122446
123205
  (point52) => isUnroutedComponentPad(this.routingInput, connection, point52)
123206
+ ) && connection.pointsToConnect.every(
123207
+ (point52) => this.isNegotiableTerminal(connection, point52)
122447
123208
  )
122448
123209
  ));
122449
123210
  const terminalLayers = /* @__PURE__ */ new Map();
@@ -158599,7 +159360,7 @@ var RoutingPhasePreparationError = class extends Error {
158599
159360
  var package_default = {
158600
159361
  name: "@tscircuit/core",
158601
159362
  type: "module",
158602
- version: "0.0.2299",
159363
+ version: "0.0.2301",
158603
159364
  types: "dist/index.d.ts",
158604
159365
  main: "dist/index.js",
158605
159366
  module: "dist/index.js",
@@ -158637,9 +159398,9 @@ var package_default = {
158637
159398
  "@tsci/tscircuit.ti": "github:tscircuit/ti#57e314be4b2b06bc546d4def7453e619604d1157",
158638
159399
  "@tscircuit/alphabet": "0.0.25",
158639
159400
  "@tscircuit/bga-fanout-solver": "github:tscircuit/bga-fanout-solver#247c835cb8d69b65e53891dcf869731079415b39",
158640
- "@tscircuit/bus-lanes-solver": "https://jscdn.tscircuit.com/@tscircuit/bus-lanes-solver/0.0.48.tgz",
159401
+ "@tscircuit/bus-lanes-solver": "https://jscdn.tscircuit.com/@tscircuit/bus-lanes-solver/0.0.49.tgz",
158641
159402
  "@tscircuit/cableprinter": "^0.0.6",
158642
- "@tscircuit/capacity-autorouter": "^0.0.1059",
159403
+ "@tscircuit/capacity-autorouter": "^0.0.1060",
158643
159404
  "@tscircuit/checks": "^0.0.270",
158644
159405
  "@tscircuit/circuit-json-util": "^0.0.128",
158645
159406
  "@tscircuit/common": "^0.0.20",
@@ -158677,12 +159438,12 @@ var package_default = {
158677
159438
  "bun-match-svg": "0.0.12",
158678
159439
  "calculate-elbow": "^0.0.12",
158679
159440
  "chokidar-cli": "^3.0.0",
158680
- "circuit-json": "^0.0.547",
159441
+ "circuit-json": "^0.0.548",
158681
159442
  "circuit-json-to-bom-csv": "^0.0.28",
158682
159443
  "circuit-json-to-bpc": "^0.0.13",
158683
159444
  "circuit-json-to-connectivity-map": "^0.0.33",
158684
159445
  "circuit-json-to-spice": "^0.0.45",
158685
- "circuit-to-svg": "^0.0.456",
159446
+ "circuit-to-svg": "^0.0.457",
158686
159447
  concurrently: "^9.1.2",
158687
159448
  "connectivity-map": "^1.0.1",
158688
159449
  debug: "^4.3.6",
@@ -158769,7 +159530,7 @@ var package_default = {
158769
159530
  overrides: {
158770
159531
  "@tscircuit/graphics-debug-3d": "https://jscdn.tscircuit.com/@tscircuit/graphics-debug-3d/0.0.1.tgz",
158771
159532
  "@tscircuit/props": "^0.0.721",
158772
- "circuit-json": "^0.0.547",
159533
+ "circuit-json": "^0.0.548",
158773
159534
  "@tscircuit/cableprinter": "^0.0.6"
158774
159535
  },
158775
159536
  browser: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/core",
3
3
  "type": "module",
4
- "version": "0.0.2300",
4
+ "version": "0.0.2302",
5
5
  "types": "dist/index.d.ts",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.js",
@@ -39,9 +39,9 @@
39
39
  "@tsci/tscircuit.ti": "github:tscircuit/ti#57e314be4b2b06bc546d4def7453e619604d1157",
40
40
  "@tscircuit/alphabet": "0.0.25",
41
41
  "@tscircuit/bga-fanout-solver": "github:tscircuit/bga-fanout-solver#247c835cb8d69b65e53891dcf869731079415b39",
42
- "@tscircuit/bus-lanes-solver": "https://jscdn.tscircuit.com/@tscircuit/bus-lanes-solver/0.0.48.tgz",
42
+ "@tscircuit/bus-lanes-solver": "https://jscdn.tscircuit.com/@tscircuit/bus-lanes-solver/0.0.49.tgz",
43
43
  "@tscircuit/cableprinter": "^0.0.6",
44
- "@tscircuit/capacity-autorouter": "^0.0.1059",
44
+ "@tscircuit/capacity-autorouter": "^0.0.1060",
45
45
  "@tscircuit/checks": "^0.0.270",
46
46
  "@tscircuit/circuit-json-util": "^0.0.128",
47
47
  "@tscircuit/common": "^0.0.20",
@@ -79,12 +79,12 @@
79
79
  "bun-match-svg": "0.0.12",
80
80
  "calculate-elbow": "^0.0.12",
81
81
  "chokidar-cli": "^3.0.0",
82
- "circuit-json": "^0.0.547",
82
+ "circuit-json": "^0.0.548",
83
83
  "circuit-json-to-bom-csv": "^0.0.28",
84
84
  "circuit-json-to-bpc": "^0.0.13",
85
85
  "circuit-json-to-connectivity-map": "^0.0.33",
86
86
  "circuit-json-to-spice": "^0.0.45",
87
- "circuit-to-svg": "^0.0.456",
87
+ "circuit-to-svg": "^0.0.457",
88
88
  "concurrently": "^9.1.2",
89
89
  "connectivity-map": "^1.0.1",
90
90
  "debug": "^4.3.6",
@@ -171,7 +171,7 @@
171
171
  "overrides": {
172
172
  "@tscircuit/graphics-debug-3d": "https://jscdn.tscircuit.com/@tscircuit/graphics-debug-3d/0.0.1.tgz",
173
173
  "@tscircuit/props": "^0.0.721",
174
- "circuit-json": "^0.0.547",
174
+ "circuit-json": "^0.0.548",
175
175
  "@tscircuit/cableprinter": "^0.0.6"
176
176
  },
177
177
  "browser": {