@tscircuit/hypergraph 0.0.19 → 0.0.21

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
@@ -110,6 +110,10 @@ interface JRegion extends Region {
110
110
  x: number;
111
111
  y: number;
112
112
  };
113
+ polygon?: {
114
+ x: number;
115
+ y: number;
116
+ }[];
113
117
  isPad: boolean;
114
118
  isThroughJumper?: boolean;
115
119
  isConnectionRegion?: boolean;
@@ -345,6 +349,23 @@ declare class HyperGraphSolver<RegionType extends Region = Region, RegionPortTyp
345
349
  * redundant.
346
350
  */
347
351
  selectCandidatesForEnteringRegion(candidates: Candidate[]): Candidate[];
352
+ /**
353
+ * OPTIONALLY OVERRIDE THIS
354
+ *
355
+ * Compute the full set of solved routes that must be ripped to accept
356
+ * `newlySolvedRoute`. By default this returns all conflicting routes
357
+ * (always-rip behavior)
358
+ *
359
+ * Override this to implement partial ripping, where only a subset of
360
+ * conflicting routes are removed.
361
+ */
362
+ computeRoutesToRip(newlySolvedRoute: SolvedRoute): Set<SolvedRoute>;
363
+ /**
364
+ * Returns solved routes that overlap ports with the newly solved route.
365
+ * Use this in computeRoutesToRip overrides to include port reuse rips.
366
+ */
367
+ computePortOverlapRoutes(newlySolvedRoute: SolvedRoute): Set<SolvedRoute>;
368
+ computeCrossingRoutes(newlySolvedRoute: SolvedRoute): Set<SolvedRoute>;
348
369
  getNextCandidates(currentCandidate: CandidateType): CandidateType[];
349
370
  processSolvedRoute(finalCandidate: CandidateType): void;
350
371
  /**
@@ -458,6 +479,10 @@ type SharedBoundary = {
458
479
  type RegionData = {
459
480
  id: string;
460
481
  bounds: Bounds | null;
482
+ polygon: {
483
+ x: number;
484
+ y: number;
485
+ }[] | null;
461
486
  center: {
462
487
  x: number;
463
488
  y: number;
@@ -495,6 +520,10 @@ declare class RegionBuilder implements RegionRef {
495
520
  constructor(id: string);
496
521
  get id(): string;
497
522
  rect(b: Bounds): this;
523
+ polygon(points: {
524
+ x: number;
525
+ y: number;
526
+ }[]): this;
498
527
  center(x: number, y: number): this;
499
528
  size(w: number, h: number, anchor?: "center" | "min"): this;
500
529
  pad(isPad?: boolean): this;
package/dist/index.js CHANGED
@@ -1915,6 +1915,52 @@ var HyperGraphSolver = class extends BaseSolver {
1915
1915
  selectCandidatesForEnteringRegion(candidates) {
1916
1916
  return candidates;
1917
1917
  }
1918
+ /**
1919
+ * OPTIONALLY OVERRIDE THIS
1920
+ *
1921
+ * Compute the full set of solved routes that must be ripped to accept
1922
+ * `newlySolvedRoute`. By default this returns all conflicting routes
1923
+ * (always-rip behavior)
1924
+ *
1925
+ * Override this to implement partial ripping, where only a subset of
1926
+ * conflicting routes are removed.
1927
+ */
1928
+ computeRoutesToRip(newlySolvedRoute) {
1929
+ const crossingRoutesToRip = this.computeCrossingRoutes(newlySolvedRoute);
1930
+ const portReuseRoutesToRip = this.computePortOverlapRoutes(newlySolvedRoute);
1931
+ return /* @__PURE__ */ new Set([
1932
+ ...crossingRoutesToRip,
1933
+ ...portReuseRoutesToRip
1934
+ ]);
1935
+ }
1936
+ /**
1937
+ * Returns solved routes that overlap ports with the newly solved route.
1938
+ * Use this in computeRoutesToRip overrides to include port reuse rips.
1939
+ */
1940
+ computePortOverlapRoutes(newlySolvedRoute) {
1941
+ const portReuseRoutesToRip = /* @__PURE__ */ new Set();
1942
+ for (const candidate of newlySolvedRoute.path) {
1943
+ if (candidate.port.assignment && candidate.port.assignment.connection.mutuallyConnectedNetworkId !== newlySolvedRoute.connection.mutuallyConnectedNetworkId) {
1944
+ portReuseRoutesToRip.add(candidate.port.assignment.solvedRoute);
1945
+ }
1946
+ }
1947
+ return portReuseRoutesToRip;
1948
+ }
1949
+ computeCrossingRoutes(newlySolvedRoute) {
1950
+ const crossingRoutesToRip = /* @__PURE__ */ new Set();
1951
+ for (const candidate of newlySolvedRoute.path) {
1952
+ if (!candidate.lastPort || !candidate.lastRegion) continue;
1953
+ const ripsRequired = this.getRipsRequiredForPortUsage(
1954
+ candidate.lastRegion,
1955
+ candidate.lastPort,
1956
+ candidate.port
1957
+ );
1958
+ for (const assignment of ripsRequired) {
1959
+ crossingRoutesToRip.add(assignment.solvedRoute);
1960
+ }
1961
+ }
1962
+ return crossingRoutesToRip;
1963
+ }
1918
1964
  getNextCandidates(currentCandidate) {
1919
1965
  const currentRegion = currentCandidate.nextRegion;
1920
1966
  const currentPort = currentCandidate.port;
@@ -1966,29 +2012,13 @@ var HyperGraphSolver = class extends BaseSolver {
1966
2012
  solvedRoute.path.unshift(cursorCandidate);
1967
2013
  cursorCandidate = cursorCandidate.parent;
1968
2014
  }
1969
- const routesToRip = /* @__PURE__ */ new Set();
1970
2015
  if (anyRipsRequired) {
1971
2016
  solvedRoute.requiredRip = true;
1972
- for (const candidate of solvedRoute.path) {
1973
- if (candidate.port.assignment && candidate.port.assignment.connection.mutuallyConnectedNetworkId !== this.currentConnection.mutuallyConnectedNetworkId) {
1974
- routesToRip.add(candidate.port.assignment.solvedRoute);
1975
- }
1976
- }
1977
- }
1978
- for (const candidate of solvedRoute.path) {
1979
- if (!candidate.lastPort || !candidate.lastRegion) continue;
1980
- const ripsRequired = this.getRipsRequiredForPortUsage(
1981
- candidate.lastRegion,
1982
- candidate.lastPort,
1983
- candidate.port
1984
- );
1985
- for (const assignment of ripsRequired) {
1986
- routesToRip.add(assignment.solvedRoute);
1987
- }
1988
2017
  }
1989
- if (routesToRip.size > 0) {
2018
+ const allRoutesToRip = this.computeRoutesToRip(solvedRoute);
2019
+ if (allRoutesToRip.size > 0) {
1990
2020
  solvedRoute.requiredRip = true;
1991
- for (const route of routesToRip) {
2021
+ for (const route of allRoutesToRip) {
1992
2022
  this.ripSolvedRoute(route);
1993
2023
  }
1994
2024
  }
@@ -2117,10 +2147,11 @@ var visualizeJumperGraph = (graph, options) => {
2117
2147
  points: [],
2118
2148
  rects: [],
2119
2149
  texts: [],
2150
+ polygons: [],
2120
2151
  coordinateSystem: "cartesian"
2121
2152
  };
2122
2153
  for (const region of graph.regions) {
2123
- const { bounds, isPad, isThroughJumper, isConnectionRegion } = region.d;
2154
+ const { bounds, isPad, isThroughJumper, isConnectionRegion, polygon } = region.d;
2124
2155
  const centerX = (bounds.minX + bounds.maxX) / 2;
2125
2156
  const centerY = (bounds.minY + bounds.maxY) / 2;
2126
2157
  const width = bounds.maxX - bounds.minX;
@@ -2135,12 +2166,22 @@ var visualizeJumperGraph = (graph, options) => {
2135
2166
  } else {
2136
2167
  fill = "rgba(200, 200, 255, 0.1)";
2137
2168
  }
2138
- graphics.rects.push({
2139
- center: { x: centerX, y: centerY },
2140
- width: width - 0.1,
2141
- height: height - 0.1,
2142
- fill
2143
- });
2169
+ if (polygon && polygon.length >= 3) {
2170
+ const points = polygon;
2171
+ graphics.polygons.push({
2172
+ points,
2173
+ fill,
2174
+ stroke: "rgba(128, 128, 128, 0.5)",
2175
+ strokeWidth: 0.03
2176
+ });
2177
+ } else {
2178
+ graphics.rects.push({
2179
+ center: { x: centerX, y: centerY },
2180
+ width: width - 0.1,
2181
+ height: height - 0.1,
2182
+ fill
2183
+ });
2184
+ }
2144
2185
  }
2145
2186
  if (!options?.hidePortPoints) {
2146
2187
  for (const port of graph.ports) {
@@ -2590,6 +2631,7 @@ var RegionBuilder = class {
2590
2631
  this.data = {
2591
2632
  id,
2592
2633
  bounds: null,
2634
+ polygon: null,
2593
2635
  center: null,
2594
2636
  width: null,
2595
2637
  height: null,
@@ -2606,6 +2648,35 @@ var RegionBuilder = class {
2606
2648
  // Geometry methods
2607
2649
  rect(b) {
2608
2650
  this.data.bounds = { ...b };
2651
+ this.data.polygon = null;
2652
+ this.data.center = null;
2653
+ this.data.width = null;
2654
+ this.data.height = null;
2655
+ return this;
2656
+ }
2657
+ polygon(points) {
2658
+ if (points.length < 3) {
2659
+ throw new TopologyError(
2660
+ `Region "${this.data.id}" has invalid polygon: at least 3 points required`,
2661
+ {
2662
+ regionIds: [this.data.id],
2663
+ suggestion: "Provide at least three polygon vertices"
2664
+ }
2665
+ );
2666
+ }
2667
+ for (const point of points) {
2668
+ if (!Number.isFinite(point.x) || !Number.isFinite(point.y)) {
2669
+ throw new TopologyError(
2670
+ `Region "${this.data.id}" has invalid polygon point`,
2671
+ {
2672
+ regionIds: [this.data.id],
2673
+ suggestion: "Use finite numeric x/y values"
2674
+ }
2675
+ );
2676
+ }
2677
+ }
2678
+ this.data.polygon = points.map((p) => ({ x: p.x, y: p.y }));
2679
+ this.data.bounds = null;
2609
2680
  this.data.center = null;
2610
2681
  this.data.width = null;
2611
2682
  this.data.height = null;
@@ -2614,6 +2685,7 @@ var RegionBuilder = class {
2614
2685
  center(x, y) {
2615
2686
  this.data.center = { x, y };
2616
2687
  this.data.bounds = null;
2688
+ this.data.polygon = null;
2617
2689
  return this;
2618
2690
  }
2619
2691
  size(w, h, anchor = "center") {
@@ -2630,6 +2702,7 @@ var RegionBuilder = class {
2630
2702
  this.data.height = h;
2631
2703
  this.data.anchor = anchor;
2632
2704
  this.data.bounds = null;
2705
+ this.data.polygon = null;
2633
2706
  return this;
2634
2707
  }
2635
2708
  // Semantic methods
@@ -2665,6 +2738,20 @@ function computeBoundsFromRegionData(data) {
2665
2738
  if (data.bounds) {
2666
2739
  return data.bounds;
2667
2740
  }
2741
+ if (data.polygon && data.polygon.length > 0) {
2742
+ let minX = data.polygon[0].x;
2743
+ let maxX = data.polygon[0].x;
2744
+ let minY = data.polygon[0].y;
2745
+ let maxY = data.polygon[0].y;
2746
+ for (let i = 1; i < data.polygon.length; i++) {
2747
+ const point = data.polygon[i];
2748
+ minX = Math.min(minX, point.x);
2749
+ maxX = Math.max(maxX, point.x);
2750
+ minY = Math.min(minY, point.y);
2751
+ maxY = Math.max(maxY, point.y);
2752
+ }
2753
+ return { minX, maxX, minY, maxY };
2754
+ }
2668
2755
  if (data.center && data.width !== null && data.height !== null) {
2669
2756
  const halfW = data.width / 2;
2670
2757
  const halfH = data.height / 2;
@@ -3277,6 +3364,7 @@ ${errors.map((e) => ` - ${e}`).join("\n")}`
3277
3364
  bounds,
3278
3365
  center,
3279
3366
  isPad: data.isPad,
3367
+ ...data.polygon && { polygon: data.polygon },
3280
3368
  ...data.isThroughJumper && { isThroughJumper: true },
3281
3369
  ...data.isConnectionRegion && { isConnectionRegion: true },
3282
3370
  ...data.meta
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.19",
4
+ "version": "0.0.21",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -19,7 +19,7 @@
19
19
  "@tscircuit/math-utils": "^0.0.29",
20
20
  "@types/bun": "latest",
21
21
  "bun-match-svg": "^0.0.15",
22
- "graphics-debug": "^0.0.76",
22
+ "graphics-debug": "^0.0.83",
23
23
  "react-cosmos": "^7.1.0",
24
24
  "react-cosmos-plugin-vite": "^7.1.0",
25
25
  "transformation-matrix": "^3.1.0",