@tscircuit/hypergraph 0.0.44 → 0.0.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1081,10 +1081,13 @@ type Bounds = {
1081
1081
  /**
1082
1082
  * Generates a via topology using convex regions computed by ConvexRegionsSolver.
1083
1083
  *
1084
- * 1. Via tiles are placed on a grid (5mm tiles by default)
1085
- * 2. Per-net via region polygons are created within each tile
1086
- * 3. Convex regions are computed globally with via region polygons as obstacles
1087
- * 4. Ports are created between adjacent convex regions and between convex/via regions
1084
+ * New tiled approach:
1085
+ * 1. Compute convex regions for a single unit tile (centered at origin)
1086
+ * 2. Replicate the tile's regions across the grid by translation
1087
+ * 3. Create rectangular filler regions for outer areas:
1088
+ * - Top/bottom regions extend horizontally across full bounds width
1089
+ * - Left/right regions extend vertically between top/bottom regions
1090
+ * 4. Create ports between adjacent tiles and between tiles and filler regions
1088
1091
  */
1089
1092
  declare function generateConvexViaTopologyRegions(opts: {
1090
1093
  viaTile: ViaTile;
package/dist/index.js CHANGED
@@ -5778,6 +5778,32 @@ function generateDefaultViaTopologyRegions(opts) {
5778
5778
  }
5779
5779
 
5780
5780
  // lib/ViaGraphSolver/polygonPerimeterUtils.ts
5781
+ function lineSegmentsIntersect(p1, p2, p3, p4, eps = 1e-9) {
5782
+ const pointsCoincident = (a, b) => Math.abs(a.x - b.x) < eps && Math.abs(a.y - b.y) < eps;
5783
+ if (pointsCoincident(p1, p3) || pointsCoincident(p1, p4) || pointsCoincident(p2, p3) || pointsCoincident(p2, p4)) {
5784
+ return false;
5785
+ }
5786
+ const cross2 = (o, a, b) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
5787
+ const d1 = cross2(p3, p4, p1);
5788
+ const d2 = cross2(p3, p4, p2);
5789
+ const d3 = cross2(p1, p2, p3);
5790
+ const d4 = cross2(p1, p2, p4);
5791
+ if (d1 * d2 < 0 && d3 * d4 < 0) {
5792
+ return true;
5793
+ }
5794
+ return false;
5795
+ }
5796
+ function chordsIntersect(newChord, existingChord, perimeter, newPort1, newPort2, existingPort1, existingPort2) {
5797
+ if (chordsCross(newChord, existingChord, perimeter)) {
5798
+ return true;
5799
+ }
5800
+ return lineSegmentsIntersect(
5801
+ newPort1.d,
5802
+ newPort2.d,
5803
+ existingPort1.d,
5804
+ existingPort2.d
5805
+ );
5806
+ }
5781
5807
  function computeDifferentNetCrossingsForPolygon(region, port1, port2) {
5782
5808
  const polygon2 = region.d.polygon;
5783
5809
  if (!polygon2 || polygon2.length < 3) {
@@ -5790,16 +5816,20 @@ function computeDifferentNetCrossingsForPolygon(region, port1, port2) {
5790
5816
  let crossings = 0;
5791
5817
  const assignments = region.assignments ?? [];
5792
5818
  for (const assignment of assignments) {
5793
- const existingT1 = getPortPerimeterTInRegion(
5794
- assignment.regionPort1,
5795
- region
5796
- );
5797
- const existingT2 = getPortPerimeterTInRegion(
5798
- assignment.regionPort2,
5799
- region
5800
- );
5819
+ const existingPort1 = assignment.regionPort1;
5820
+ const existingPort2 = assignment.regionPort2;
5821
+ const existingT1 = getPortPerimeterTInRegion(existingPort1, region);
5822
+ const existingT2 = getPortPerimeterTInRegion(existingPort2, region);
5801
5823
  const existingChord = [existingT1, existingT2];
5802
- if (chordsCross(newChord, existingChord, perimeter)) {
5824
+ if (chordsIntersect(
5825
+ newChord,
5826
+ existingChord,
5827
+ perimeter,
5828
+ port1,
5829
+ port2,
5830
+ existingPort1,
5831
+ existingPort2
5832
+ )) {
5803
5833
  crossings++;
5804
5834
  }
5805
5835
  }
@@ -5817,16 +5847,20 @@ function computeCrossingAssignmentsForPolygon(region, port1, port2) {
5817
5847
  const crossingAssignments = [];
5818
5848
  const assignments = region.assignments ?? [];
5819
5849
  for (const assignment of assignments) {
5820
- const existingT1 = getPortPerimeterTInRegion(
5821
- assignment.regionPort1,
5822
- region
5823
- );
5824
- const existingT2 = getPortPerimeterTInRegion(
5825
- assignment.regionPort2,
5826
- region
5827
- );
5850
+ const existingPort1 = assignment.regionPort1;
5851
+ const existingPort2 = assignment.regionPort2;
5852
+ const existingT1 = getPortPerimeterTInRegion(existingPort1, region);
5853
+ const existingT2 = getPortPerimeterTInRegion(existingPort2, region);
5828
5854
  const existingChord = [existingT1, existingT2];
5829
- if (chordsCross(newChord, existingChord, perimeter)) {
5855
+ if (chordsIntersect(
5856
+ newChord,
5857
+ existingChord,
5858
+ perimeter,
5859
+ port1,
5860
+ port2,
5861
+ existingPort1,
5862
+ existingPort2
5863
+ )) {
5830
5864
  crossingAssignments.push(assignment);
5831
5865
  }
5832
5866
  }
@@ -15697,10 +15731,7 @@ function findSharedEdges(polygon1, polygon2, tolerance = 0.01) {
15697
15731
  x: a1.x + overlap.to * dx,
15698
15732
  y: a1.y + overlap.to * dy
15699
15733
  };
15700
- const edgeLen = Math.sqrt((to.x - from.x) ** 2 + (to.y - from.y) ** 2);
15701
- if (edgeLen > tolerance) {
15702
- sharedEdges.push({ from, to });
15703
- }
15734
+ sharedEdges.push({ from, to });
15704
15735
  }
15705
15736
  }
15706
15737
  return sharedEdges;
@@ -15768,35 +15799,6 @@ function centroid(points) {
15768
15799
  }
15769
15800
  return { x: cx / points.length, y: cy / points.length };
15770
15801
  }
15771
- function classifySideFromBounds(point2, bounds) {
15772
- const distances = {
15773
- left: Math.abs(point2.x - bounds.minX),
15774
- right: Math.abs(point2.x - bounds.maxX),
15775
- bottom: Math.abs(point2.y - bounds.minY),
15776
- top: Math.abs(point2.y - bounds.maxY)
15777
- };
15778
- let bestSide = "left";
15779
- let bestDistance = distances.left;
15780
- for (const side of ["right", "bottom", "top"]) {
15781
- if (distances[side] < bestDistance) {
15782
- bestSide = side;
15783
- bestDistance = distances[side];
15784
- }
15785
- }
15786
- return bestSide;
15787
- }
15788
- function toCandidateKey(regionId, point2) {
15789
- return `${regionId}:${point2.x.toFixed(6)},${point2.y.toFixed(6)}`;
15790
- }
15791
- function compareCandidateQuality(a, b) {
15792
- if (Math.abs(a.primaryDistance - b.primaryDistance) > 1e-6) {
15793
- return b.primaryDistance - a.primaryDistance;
15794
- }
15795
- if (Math.abs(a.orthDistance - b.orthDistance) > 1e-6) {
15796
- return a.orthDistance - b.orthDistance;
15797
- }
15798
- return a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
15799
- }
15800
15802
  function createRegionFromPolygon(regionId, polygon2, opts) {
15801
15803
  const bounds = boundsFromPolygon(polygon2);
15802
15804
  return {
@@ -15879,6 +15881,124 @@ function translateRouteSegments(routeSegments, dx, dy, prefix) {
15879
15881
  }))
15880
15882
  }));
15881
15883
  }
15884
+ function translatePolygon(polygon2, dx, dy) {
15885
+ return polygon2.map((p) => ({ x: p.x + dx, y: p.y + dy }));
15886
+ }
15887
+ function rectPolygonFromBounds(b) {
15888
+ return [
15889
+ { x: b.minX, y: b.minY },
15890
+ { x: b.maxX, y: b.minY },
15891
+ { x: b.maxX, y: b.maxY },
15892
+ { x: b.minX, y: b.maxY }
15893
+ ];
15894
+ }
15895
+ function extendViaRegionToTileEdge(polygon2, tileBounds, threshold = 0.1) {
15896
+ if (polygon2.length === 0) return polygon2;
15897
+ const polyBounds = boundsFromPolygon(polygon2);
15898
+ const distToLeft = polyBounds.minX - tileBounds.minX;
15899
+ const distToRight = tileBounds.maxX - polyBounds.maxX;
15900
+ const distToBottom = polyBounds.minY - tileBounds.minY;
15901
+ const distToTop = tileBounds.maxY - polyBounds.maxY;
15902
+ const extendLeft = distToLeft > 0 && distToLeft < threshold;
15903
+ const extendRight = distToRight > 0 && distToRight < threshold;
15904
+ const extendBottom = distToBottom > 0 && distToBottom < threshold;
15905
+ const extendTop = distToTop > 0 && distToTop < threshold;
15906
+ if (!extendLeft && !extendRight && !extendBottom && !extendTop) {
15907
+ return polygon2;
15908
+ }
15909
+ const result = polygon2.map((p) => {
15910
+ let x = p.x;
15911
+ let y = p.y;
15912
+ if (extendLeft && Math.abs(p.x - polyBounds.minX) < 1e-3) {
15913
+ x = tileBounds.minX;
15914
+ }
15915
+ if (extendRight && Math.abs(p.x - polyBounds.maxX) < 1e-3) {
15916
+ x = tileBounds.maxX;
15917
+ }
15918
+ if (extendBottom && Math.abs(p.y - polyBounds.minY) < 1e-3) {
15919
+ y = tileBounds.minY;
15920
+ }
15921
+ if (extendTop && Math.abs(p.y - polyBounds.maxY) < 1e-3) {
15922
+ y = tileBounds.maxY;
15923
+ }
15924
+ return { x, y };
15925
+ });
15926
+ return deduplicateConsecutivePoints(result);
15927
+ }
15928
+ function pointInPolygon(point2, polygon2) {
15929
+ let inside2 = false;
15930
+ const n = polygon2.length;
15931
+ for (let i = 0, j = n - 1; i < n; j = i++) {
15932
+ const xi = polygon2[i].x;
15933
+ const yi = polygon2[i].y;
15934
+ const xj = polygon2[j].x;
15935
+ const yj = polygon2[j].y;
15936
+ const onEdge = Math.abs((point2.y - yi) * (xj - xi) - (point2.x - xi) * (yj - yi)) < 1e-3 && point2.x >= Math.min(xi, xj) - 1e-3 && point2.x <= Math.max(xi, xj) + 1e-3 && point2.y >= Math.min(yi, yj) - 1e-3 && point2.y <= Math.max(yi, yj) + 1e-3;
15937
+ if (onEdge) return true;
15938
+ if (yi > point2.y !== yj > point2.y) {
15939
+ const intersectX = (xj - xi) * (point2.y - yi) / (yj - yi) + xi;
15940
+ if (point2.x < intersectX) {
15941
+ inside2 = !inside2;
15942
+ }
15943
+ }
15944
+ }
15945
+ return inside2;
15946
+ }
15947
+ function findRegionContainingPoint(point2, regions) {
15948
+ for (const region of regions) {
15949
+ if (region.d.polygon && pointInPolygon(point2, region.d.polygon)) {
15950
+ return region;
15951
+ }
15952
+ }
15953
+ return null;
15954
+ }
15955
+ function computeUnitTileTemplate(viaTile, tileWidth, tileHeight, clearance, concavityTolerance) {
15956
+ const halfWidth = tileWidth / 2;
15957
+ const halfHeight = tileHeight / 2;
15958
+ const tileBounds = {
15959
+ minX: -halfWidth,
15960
+ maxX: halfWidth,
15961
+ minY: -halfHeight,
15962
+ maxY: halfHeight
15963
+ };
15964
+ const viaRegions = [];
15965
+ for (const [netName, vias] of Object.entries(viaTile.viasByNet)) {
15966
+ if (vias.length === 0) continue;
15967
+ const polygon2 = generateViaRegionPolygon(vias);
15968
+ if (polygon2.length === 0) continue;
15969
+ viaRegions.push({
15970
+ netName,
15971
+ polygon: polygon2,
15972
+ bounds: boundsFromPolygon(polygon2),
15973
+ center: centroid(polygon2)
15974
+ });
15975
+ }
15976
+ const obstaclePolygons = viaRegions.map((r) => ({
15977
+ points: extendViaRegionToTileEdge(r.polygon, tileBounds)
15978
+ }));
15979
+ const solver = new ConvexRegionsSolver({
15980
+ bounds: tileBounds,
15981
+ polygons: obstaclePolygons,
15982
+ clearance,
15983
+ concavityTolerance
15984
+ });
15985
+ solver.solve();
15986
+ const solverOutput = solver.getOutput();
15987
+ if (!solverOutput) {
15988
+ throw new Error("ConvexRegionsSolver failed to compute unit tile regions");
15989
+ }
15990
+ const convexRegions = solverOutput.regions.map((polygon2) => ({
15991
+ polygon: polygon2,
15992
+ bounds: boundsFromPolygon(polygon2),
15993
+ center: centroid(polygon2)
15994
+ }));
15995
+ return {
15996
+ viaRegions,
15997
+ convexRegions,
15998
+ tileWidth,
15999
+ tileHeight
16000
+ };
16001
+ }
15882
16002
  function generateConvexViaTopologyRegions(opts) {
15883
16003
  const tileWidth = opts.tileWidth ?? opts.tileSize ?? opts.viaTile.tileWidth;
15884
16004
  const tileHeight = opts.tileHeight ?? opts.tileSize ?? opts.viaTile.tileHeight;
@@ -15900,18 +16020,79 @@ function generateConvexViaTopologyRegions(opts) {
15900
16020
  const allPorts = [];
15901
16021
  const viaTile = { viasByNet: {}, routeSegments: [] };
15902
16022
  const viaRegions = [];
16023
+ const convexRegions = [];
15903
16024
  const gridWidth = cols * tileWidth;
15904
16025
  const gridHeight = rows * tileHeight;
15905
16026
  const gridMinX = bounds.minX + (width - gridWidth) / 2;
15906
16027
  const gridMinY = bounds.minY + (height - gridHeight) / 2;
16028
+ const gridMaxX = gridMinX + gridWidth;
16029
+ const gridMaxY = gridMinY + gridHeight;
15907
16030
  const halfWidth = tileWidth / 2;
15908
16031
  const halfHeight = tileHeight / 2;
16032
+ let portIdCounter = 0;
16033
+ const usedPortPositions = /* @__PURE__ */ new Set();
16034
+ const getPortPosKey = (x, y) => `${x.toFixed(4)},${y.toFixed(4)}`;
16035
+ const createPort = (portId, region1, region2, pos) => {
16036
+ const posKey = getPortPosKey(pos.x, pos.y);
16037
+ if (usedPortPositions.has(posKey)) {
16038
+ return null;
16039
+ }
16040
+ usedPortPositions.add(posKey);
16041
+ const port = {
16042
+ portId,
16043
+ region1,
16044
+ region2,
16045
+ d: { x: pos.x, y: pos.y }
16046
+ };
16047
+ region1.ports.push(port);
16048
+ region2.ports.push(port);
16049
+ allPorts.push(port);
16050
+ return port;
16051
+ };
16052
+ let unitTileTemplate = null;
15909
16053
  if (rows > 0 && cols > 0) {
16054
+ unitTileTemplate = computeUnitTileTemplate(
16055
+ inputViaTile,
16056
+ tileWidth,
16057
+ tileHeight,
16058
+ clearance,
16059
+ concavityTolerance
16060
+ );
16061
+ }
16062
+ if (rows > 0 && cols > 0 && unitTileTemplate) {
15910
16063
  for (let row = 0; row < rows; row++) {
15911
16064
  for (let col = 0; col < cols; col++) {
15912
16065
  const tileCenterX = gridMinX + col * tileWidth + halfWidth;
15913
16066
  const tileCenterY = gridMinY + row * tileHeight + halfHeight;
15914
16067
  const prefix = `t${row}_${col}`;
16068
+ for (const templateViaRegion of unitTileTemplate.viaRegions) {
16069
+ const translatedPolygon = translatePolygon(
16070
+ templateViaRegion.polygon,
16071
+ tileCenterX,
16072
+ tileCenterY
16073
+ );
16074
+ const viaRegion = createRegionFromPolygon(
16075
+ `${prefix}:v:${templateViaRegion.netName}`,
16076
+ translatedPolygon,
16077
+ { isViaRegion: true }
16078
+ );
16079
+ viaRegions.push(viaRegion);
16080
+ allRegions.push(viaRegion);
16081
+ }
16082
+ for (let i = 0; i < unitTileTemplate.convexRegions.length; i++) {
16083
+ const templateConvexRegion = unitTileTemplate.convexRegions[i];
16084
+ const translatedPolygon = translatePolygon(
16085
+ templateConvexRegion.polygon,
16086
+ tileCenterX,
16087
+ tileCenterY
16088
+ );
16089
+ const convexRegion = createRegionFromPolygon(
16090
+ `${prefix}:convex:${i}`,
16091
+ translatedPolygon
16092
+ );
16093
+ convexRegions.push(convexRegion);
16094
+ allRegions.push(convexRegion);
16095
+ }
15915
16096
  for (const [netName, vias] of Object.entries(viasByNet)) {
15916
16097
  if (vias.length === 0) continue;
15917
16098
  const translatedVias = translateVias(
@@ -15924,15 +16105,6 @@ function generateConvexViaTopologyRegions(opts) {
15924
16105
  viaTile.viasByNet[netName] = [];
15925
16106
  }
15926
16107
  viaTile.viasByNet[netName].push(...translatedVias);
15927
- const polygon2 = generateViaRegionPolygon(translatedVias);
15928
- if (polygon2.length === 0) continue;
15929
- const viaRegion = createRegionFromPolygon(
15930
- `${prefix}:v:${netName}`,
15931
- polygon2,
15932
- { isViaRegion: true }
15933
- );
15934
- viaRegions.push(viaRegion);
15935
- allRegions.push(viaRegion);
15936
16108
  }
15937
16109
  viaTile.routeSegments.push(
15938
16110
  ...translateRouteSegments(
@@ -15945,55 +16117,306 @@ function generateConvexViaTopologyRegions(opts) {
15945
16117
  }
15946
16118
  }
15947
16119
  }
15948
- const obstaclePolygons = viaRegions.map((r) => ({
15949
- points: r.d.polygon
15950
- }));
15951
- const solverInput = {
15952
- bounds,
15953
- polygons: obstaclePolygons,
15954
- clearance,
15955
- concavityTolerance
15956
- };
15957
- const solver = new ConvexRegionsSolver(solverInput);
15958
- solver.solve();
15959
- const solverOutput = solver.getOutput();
15960
- if (!solverOutput) {
15961
- throw new Error("ConvexRegionsSolver failed to compute regions");
16120
+ const fillerRegions = [];
16121
+ const topMargin = bounds.maxY - gridMaxY;
16122
+ const bottomMargin = gridMinY - bounds.minY;
16123
+ const leftMargin = gridMinX - bounds.minX;
16124
+ const rightMargin = bounds.maxX - gridMaxX;
16125
+ const verticalMargin = Math.max(topMargin, bottomMargin);
16126
+ const horizontalMargin = Math.max(leftMargin, rightMargin);
16127
+ const topBottomGetCorners = verticalMargin >= horizontalMargin;
16128
+ const topMinX = topBottomGetCorners ? bounds.minX : gridMinX;
16129
+ const topMaxX = topBottomGetCorners ? bounds.maxX : gridMaxX;
16130
+ const bottomMinX = topBottomGetCorners ? bounds.minX : gridMinX;
16131
+ const bottomMaxX = topBottomGetCorners ? bounds.maxX : gridMaxX;
16132
+ const leftMinY = topBottomGetCorners ? gridMinY : bounds.minY;
16133
+ const leftMaxY = topBottomGetCorners ? gridMaxY : bounds.maxY;
16134
+ const rightMinY = topBottomGetCorners ? gridMinY : bounds.minY;
16135
+ const rightMaxY = topBottomGetCorners ? gridMaxY : bounds.maxY;
16136
+ if (topMargin > 1e-3) {
16137
+ const topWidth = topMaxX - topMinX;
16138
+ const targetStripWidth = Math.max(topMargin, portPitch);
16139
+ const numTopStrips = Math.max(1, Math.floor(topWidth / targetStripWidth));
16140
+ const stripWidth = topWidth / numTopStrips;
16141
+ for (let i = 0; i < numTopStrips; i++) {
16142
+ const fillerBounds = {
16143
+ minX: topMinX + i * stripWidth,
16144
+ maxX: topMinX + (i + 1) * stripWidth,
16145
+ minY: gridMaxY,
16146
+ maxY: bounds.maxY
16147
+ };
16148
+ const regionId = `filler:top:${i}`;
16149
+ const filler = createRegionFromPolygon(
16150
+ regionId,
16151
+ rectPolygonFromBounds(fillerBounds)
16152
+ );
16153
+ fillerRegions.push(filler);
16154
+ allRegions.push(filler);
16155
+ }
15962
16156
  }
15963
- const convexRegions = solverOutput.regions.map(
15964
- (polygon2, i) => createRegionFromPolygon(`convex:${i}`, polygon2)
15965
- );
15966
- allRegions.push(...convexRegions);
15967
- let portIdCounter = 0;
15968
- for (let i = 0; i < convexRegions.length; i++) {
15969
- for (let j = i + 1; j < convexRegions.length; j++) {
15970
- const region1 = convexRegions[i];
15971
- const region2 = convexRegions[j];
16157
+ if (bottomMargin > 1e-3) {
16158
+ const bottomWidth = bottomMaxX - bottomMinX;
16159
+ const targetStripWidth = Math.max(bottomMargin, portPitch);
16160
+ const numBottomStrips = Math.max(
16161
+ 1,
16162
+ Math.floor(bottomWidth / targetStripWidth)
16163
+ );
16164
+ const stripWidth = bottomWidth / numBottomStrips;
16165
+ for (let i = 0; i < numBottomStrips; i++) {
16166
+ const fillerBounds = {
16167
+ minX: bottomMinX + i * stripWidth,
16168
+ maxX: bottomMinX + (i + 1) * stripWidth,
16169
+ minY: bounds.minY,
16170
+ maxY: gridMinY
16171
+ };
16172
+ const regionId = `filler:bottom:${i}`;
16173
+ const filler = createRegionFromPolygon(
16174
+ regionId,
16175
+ rectPolygonFromBounds(fillerBounds)
16176
+ );
16177
+ fillerRegions.push(filler);
16178
+ allRegions.push(filler);
16179
+ }
16180
+ }
16181
+ if (leftMargin > 1e-3) {
16182
+ const leftHeight = leftMaxY - leftMinY;
16183
+ const targetStripHeight = Math.max(leftMargin, portPitch);
16184
+ const numLeftStrips = Math.max(
16185
+ 1,
16186
+ Math.floor(leftHeight / targetStripHeight)
16187
+ );
16188
+ const stripHeight = leftHeight / numLeftStrips;
16189
+ for (let i = 0; i < numLeftStrips; i++) {
16190
+ const fillerBounds = {
16191
+ minX: bounds.minX,
16192
+ maxX: gridMinX,
16193
+ minY: leftMinY + i * stripHeight,
16194
+ maxY: leftMinY + (i + 1) * stripHeight
16195
+ };
16196
+ const regionId = `filler:left:${i}`;
16197
+ const filler = createRegionFromPolygon(
16198
+ regionId,
16199
+ rectPolygonFromBounds(fillerBounds)
16200
+ );
16201
+ fillerRegions.push(filler);
16202
+ allRegions.push(filler);
16203
+ }
16204
+ }
16205
+ if (rightMargin > 1e-3) {
16206
+ const rightHeight = rightMaxY - rightMinY;
16207
+ const targetStripHeight = Math.max(rightMargin, portPitch);
16208
+ const numRightStrips = Math.max(
16209
+ 1,
16210
+ Math.floor(rightHeight / targetStripHeight)
16211
+ );
16212
+ const stripHeight = rightHeight / numRightStrips;
16213
+ for (let i = 0; i < numRightStrips; i++) {
16214
+ const fillerBounds = {
16215
+ minX: gridMaxX,
16216
+ maxX: bounds.maxX,
16217
+ minY: rightMinY + i * stripHeight,
16218
+ maxY: rightMinY + (i + 1) * stripHeight
16219
+ };
16220
+ const regionId = `filler:right:${i}`;
16221
+ const filler = createRegionFromPolygon(
16222
+ regionId,
16223
+ rectPolygonFromBounds(fillerBounds)
16224
+ );
16225
+ fillerRegions.push(filler);
16226
+ allRegions.push(filler);
16227
+ }
16228
+ }
16229
+ if (unitTileTemplate && rows > 0 && cols > 0) {
16230
+ const regionsPerTile = unitTileTemplate.convexRegions.length;
16231
+ for (let row = 0; row < rows; row++) {
16232
+ for (let col = 0; col < cols; col++) {
16233
+ const tileIndex = row * cols + col;
16234
+ const tileStartIdx = tileIndex * regionsPerTile;
16235
+ for (let i = 0; i < regionsPerTile; i++) {
16236
+ for (let j = i + 1; j < regionsPerTile; j++) {
16237
+ const region1 = convexRegions[tileStartIdx + i];
16238
+ const region2 = convexRegions[tileStartIdx + j];
16239
+ const sharedEdges = findSharedEdges(
16240
+ region1.d.polygon,
16241
+ region2.d.polygon,
16242
+ clearance * 2
16243
+ );
16244
+ for (const edge of sharedEdges) {
16245
+ const portPositions = createPortsAlongEdge(edge, portPitch);
16246
+ for (const pos of portPositions) {
16247
+ createPort(
16248
+ `t${row}_${col}:convex:${i}-${j}:${portIdCounter++}`,
16249
+ region1,
16250
+ region2,
16251
+ pos
16252
+ );
16253
+ }
16254
+ }
16255
+ }
16256
+ }
16257
+ }
16258
+ }
16259
+ }
16260
+ if (unitTileTemplate && rows > 0 && cols > 0) {
16261
+ const convexPerTile = unitTileTemplate.convexRegions.length;
16262
+ const viasPerTile = unitTileTemplate.viaRegions.length;
16263
+ const numVerticalPorts = Math.floor(tileHeight / portPitch);
16264
+ const verticalPortYOffsets = [];
16265
+ for (let i = 0; i < numVerticalPorts; i++) {
16266
+ verticalPortYOffsets.push(-halfHeight + (i + 0.5) * portPitch);
16267
+ }
16268
+ const numHorizontalPorts = Math.floor(tileWidth / portPitch);
16269
+ const horizontalPortXOffsets = [];
16270
+ for (let i = 0; i < numHorizontalPorts; i++) {
16271
+ horizontalPortXOffsets.push(-halfWidth + (i + 0.5) * portPitch);
16272
+ }
16273
+ for (let row = 0; row < rows; row++) {
16274
+ for (let col = 0; col < cols; col++) {
16275
+ const tileIndex = row * cols + col;
16276
+ const convexStartIdx = tileIndex * convexPerTile;
16277
+ const viaStartIdx = tileIndex * viasPerTile;
16278
+ const tileCenterX = gridMinX + col * tileWidth + halfWidth;
16279
+ const tileCenterY = gridMinY + row * tileHeight + halfHeight;
16280
+ const tileConvexRegions = convexRegions.slice(
16281
+ convexStartIdx,
16282
+ convexStartIdx + convexPerTile
16283
+ );
16284
+ const tileViaRegions = viaRegions.slice(
16285
+ viaStartIdx,
16286
+ viaStartIdx + viasPerTile
16287
+ );
16288
+ const tileAllRegions = [...tileConvexRegions, ...tileViaRegions];
16289
+ if (col + 1 < cols) {
16290
+ const rightTileIndex = row * cols + (col + 1);
16291
+ const rightConvexStartIdx = rightTileIndex * convexPerTile;
16292
+ const rightViaStartIdx = rightTileIndex * viasPerTile;
16293
+ const rightTileConvexRegions = convexRegions.slice(
16294
+ rightConvexStartIdx,
16295
+ rightConvexStartIdx + convexPerTile
16296
+ );
16297
+ const rightTileViaRegions = viaRegions.slice(
16298
+ rightViaStartIdx,
16299
+ rightViaStartIdx + viasPerTile
16300
+ );
16301
+ const rightTileAllRegions = [
16302
+ ...rightTileConvexRegions,
16303
+ ...rightTileViaRegions
16304
+ ];
16305
+ const boundaryX = tileCenterX + halfWidth;
16306
+ for (const yOffset of verticalPortYOffsets) {
16307
+ const portY = tileCenterY + yOffset;
16308
+ const pointInCurrentTile = { x: boundaryX - 0.01, y: portY };
16309
+ const pointInRightTile = { x: boundaryX + 0.01, y: portY };
16310
+ const region1 = findRegionContainingPoint(
16311
+ pointInCurrentTile,
16312
+ tileAllRegions
16313
+ );
16314
+ const region2 = findRegionContainingPoint(
16315
+ pointInRightTile,
16316
+ rightTileAllRegions
16317
+ );
16318
+ if (region1 && region2) {
16319
+ createPort(
16320
+ `tile:${row}_${col}-${row}_${col + 1}:${portIdCounter++}`,
16321
+ region1,
16322
+ region2,
16323
+ { x: boundaryX, y: portY }
16324
+ );
16325
+ }
16326
+ }
16327
+ }
16328
+ if (row + 1 < rows) {
16329
+ const topTileIndex = (row + 1) * cols + col;
16330
+ const topConvexStartIdx = topTileIndex * convexPerTile;
16331
+ const topViaStartIdx = topTileIndex * viasPerTile;
16332
+ const topTileConvexRegions = convexRegions.slice(
16333
+ topConvexStartIdx,
16334
+ topConvexStartIdx + convexPerTile
16335
+ );
16336
+ const topTileViaRegions = viaRegions.slice(
16337
+ topViaStartIdx,
16338
+ topViaStartIdx + viasPerTile
16339
+ );
16340
+ const topTileAllRegions = [
16341
+ ...topTileConvexRegions,
16342
+ ...topTileViaRegions
16343
+ ];
16344
+ const boundaryY = tileCenterY + halfHeight;
16345
+ for (const xOffset of horizontalPortXOffsets) {
16346
+ const portX = tileCenterX + xOffset;
16347
+ const pointInCurrentTile = { x: portX, y: boundaryY - 0.01 };
16348
+ const pointInTopTile = { x: portX, y: boundaryY + 0.01 };
16349
+ const region1 = findRegionContainingPoint(
16350
+ pointInCurrentTile,
16351
+ tileAllRegions
16352
+ );
16353
+ const region2 = findRegionContainingPoint(
16354
+ pointInTopTile,
16355
+ topTileAllRegions
16356
+ );
16357
+ if (region1 && region2) {
16358
+ createPort(
16359
+ `tile:${row}_${col}-${row + 1}_${col}:${portIdCounter++}`,
16360
+ region1,
16361
+ region2,
16362
+ { x: portX, y: boundaryY }
16363
+ );
16364
+ }
16365
+ }
16366
+ }
16367
+ }
16368
+ }
16369
+ }
16370
+ for (const fillerRegion of fillerRegions) {
16371
+ const tileRegions = [...convexRegions, ...viaRegions];
16372
+ for (const tileRegion of tileRegions) {
16373
+ const sharedEdges = findSharedEdges(
16374
+ tileRegion.d.polygon,
16375
+ fillerRegion.d.polygon,
16376
+ clearance * 2
16377
+ );
16378
+ for (const edge of sharedEdges) {
16379
+ const portPositions = createPortsAlongEdge(edge, portPitch);
16380
+ for (const pos of portPositions) {
16381
+ createPort(
16382
+ `filler:${tileRegion.regionId}-${fillerRegion.regionId}:${portIdCounter++}`,
16383
+ tileRegion,
16384
+ fillerRegion,
16385
+ pos
16386
+ );
16387
+ }
16388
+ }
16389
+ }
16390
+ }
16391
+ for (let i = 0; i < fillerRegions.length; i++) {
16392
+ for (let j = i + 1; j < fillerRegions.length; j++) {
16393
+ const region1 = fillerRegions[i];
16394
+ const region2 = fillerRegions[j];
15972
16395
  const sharedEdges = findSharedEdges(
15973
16396
  region1.d.polygon,
15974
16397
  region2.d.polygon,
15975
- clearance * 2
15976
- // tolerance slightly larger than clearance
16398
+ 0.01
15977
16399
  );
15978
16400
  for (const edge of sharedEdges) {
16401
+ const edgeLength = Math.sqrt(
16402
+ (edge.to.x - edge.from.x) ** 2 + (edge.to.y - edge.from.y) ** 2
16403
+ );
16404
+ if (edgeLength < portPitch) {
16405
+ continue;
16406
+ }
15979
16407
  const portPositions = createPortsAlongEdge(edge, portPitch);
15980
16408
  for (const pos of portPositions) {
15981
- const port = {
15982
- portId: `convex:${i}-${j}:${portIdCounter++}`,
16409
+ createPort(
16410
+ `filler:${region1.regionId}-${region2.regionId}:${portIdCounter++}`,
15983
16411
  region1,
15984
16412
  region2,
15985
- d: { x: pos.x, y: pos.y }
15986
- };
15987
- region1.ports.push(port);
15988
- region2.ports.push(port);
15989
- allPorts.push(port);
16413
+ pos
16414
+ );
15990
16415
  }
15991
16416
  }
15992
16417
  }
15993
16418
  }
15994
16419
  for (const viaRegion of viaRegions) {
15995
- const viaCenter = viaRegion.d.center;
15996
- const candidates = [];
15997
16420
  for (const convexRegion of convexRegions) {
15998
16421
  const sharedEdges = findSharedEdges(
15999
16422
  viaRegion.d.polygon,
@@ -16003,55 +16426,15 @@ function generateConvexViaTopologyRegions(opts) {
16003
16426
  for (const edge of sharedEdges) {
16004
16427
  const portPositions = createPortsAlongEdge(edge, portPitch);
16005
16428
  for (const pos of portPositions) {
16006
- const dx = pos.x - viaCenter.x;
16007
- const dy = pos.y - viaCenter.y;
16008
- const side = classifySideFromBounds(pos, viaRegion.d.bounds);
16009
- const primaryDistance = side === "left" || side === "right" ? Math.abs(dx) : Math.abs(dy);
16010
- const orthDistance = side === "left" || side === "right" ? Math.abs(dy) : Math.abs(dx);
16011
- candidates.push({
16429
+ createPort(
16430
+ `via-convex:${viaRegion.regionId}-${convexRegion.regionId}:${portIdCounter++}`,
16431
+ viaRegion,
16012
16432
  convexRegion,
16013
- position: pos,
16014
- side,
16015
- primaryDistance,
16016
- orthDistance,
16017
- key: toCandidateKey(convexRegion.regionId, pos)
16018
- });
16433
+ pos
16434
+ );
16019
16435
  }
16020
16436
  }
16021
16437
  }
16022
- if (candidates.length === 0) continue;
16023
- const selectedCandidates = [];
16024
- const selectedKeys = /* @__PURE__ */ new Set();
16025
- const addCandidate = (candidate) => {
16026
- if (!candidate) return;
16027
- if (selectedKeys.has(candidate.key)) return;
16028
- selectedCandidates.push(candidate);
16029
- selectedKeys.add(candidate.key);
16030
- };
16031
- for (const side of ["top", "bottom", "left", "right"]) {
16032
- const sideCandidate = [...candidates].filter((candidate) => candidate.side === side).sort(compareCandidateQuality)[0];
16033
- addCandidate(sideCandidate);
16034
- }
16035
- if (selectedCandidates.length < 4) {
16036
- for (const candidate of [...candidates].sort(compareCandidateQuality)) {
16037
- addCandidate(candidate);
16038
- if (selectedCandidates.length >= 4) break;
16039
- }
16040
- }
16041
- for (const selectedCandidate of selectedCandidates.slice(0, 4)) {
16042
- const port = {
16043
- portId: `via-convex:${viaRegion.regionId}-${selectedCandidate.convexRegion.regionId}:${portIdCounter++}`,
16044
- region1: viaRegion,
16045
- region2: selectedCandidate.convexRegion,
16046
- d: {
16047
- x: selectedCandidate.position.x,
16048
- y: selectedCandidate.position.y
16049
- }
16050
- };
16051
- viaRegion.ports.push(port);
16052
- selectedCandidate.convexRegion.ports.push(port);
16053
- allPorts.push(port);
16054
- }
16055
16438
  }
16056
16439
  return {
16057
16440
  regions: allRegions,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/hypergraph",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.44",
4
+ "version": "0.0.45",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",