@tscircuit/capacity-autorouter 0.0.3 → 0.0.5

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
@@ -115,6 +115,11 @@ declare class CapacityMeshNodeSolver extends BaseSolver {
115
115
  finishedNodes: CapacityMeshNode[];
116
116
  nodeToOverlappingObstaclesMap: Map<CapacityMeshNodeId, Obstacle[]>;
117
117
  MAX_DEPTH: number;
118
+ targets: Array<{
119
+ x: number;
120
+ y: number;
121
+ connectionName: string;
122
+ }>;
118
123
  constructor(srj: SimpleRouteJson, opts?: CapacityMeshNodeSolverOptions);
119
124
  _nextNodeCounter: number;
120
125
  getNextNodeId(): string;
@@ -208,6 +213,7 @@ declare class CapacityPathingSolver extends BaseSolver {
208
213
  reduceCapacityAlongPath(nextConnection: {
209
214
  path?: CapacityMeshNode[];
210
215
  }): void;
216
+ isConnectedToEndGoal(node: CapacityMeshNode, endGoal: CapacityMeshNode): boolean;
211
217
  _step(): void;
212
218
  visualize(): GraphicsObject;
213
219
  }
@@ -766,6 +772,30 @@ declare class CapacitySegmentPointOptimizer extends BaseSolver {
766
772
  visualize(): GraphicsObject;
767
773
  }
768
774
 
775
+ /**
776
+ * Converts a net containing many points to connect into an array of point pair
777
+ * connections.
778
+ *
779
+ * For example, a connection with 3 pointsToConnect could be turned into 2
780
+ * connections of 2 points each.
781
+ *
782
+ * Where we create the minimum number of pairs, we're using a minimum spanning
783
+ * tree (MST).
784
+ *
785
+ * Sometimes it can be used to add additional traces to help make sure we
786
+ * distribute load effectively. In this version we don't do that!
787
+ */
788
+ declare class NetToPointPairsSolver extends BaseSolver {
789
+ ogSrj: SimpleRouteJson;
790
+ colorMap: Record<string, string>;
791
+ unprocessedConnections: Array<SimpleRouteConnection>;
792
+ newConnections: Array<SimpleRouteConnection>;
793
+ constructor(ogSrj: SimpleRouteJson, colorMap?: Record<string, string>);
794
+ _step(): void;
795
+ getNewSimpleRouteJson(): SimpleRouteJson;
796
+ visualize(): GraphicsObject;
797
+ }
798
+
769
799
  interface CapacityMeshSolverOptions {
770
800
  capacityDepth?: number;
771
801
  targetMinCapacity?: number;
@@ -773,7 +803,8 @@ interface CapacityMeshSolverOptions {
773
803
  declare class CapacityMeshSolver extends BaseSolver {
774
804
  srj: SimpleRouteJson;
775
805
  opts: CapacityMeshSolverOptions;
776
- nodeSolver: CapacityMeshNodeSolver;
806
+ netToPointPairsSolver?: NetToPointPairsSolver;
807
+ nodeSolver?: CapacityMeshNodeSolver;
777
808
  nodeTargetMerger?: CapacityNodeTargetMerger;
778
809
  edgeSolver?: CapacityMeshEdgeSolver;
779
810
  pathingSolver?: CapacityPathingSolver;
package/dist/index.js CHANGED
@@ -1092,17 +1092,24 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1092
1092
  width: maxWidthHeight,
1093
1093
  height: maxWidthHeight,
1094
1094
  layer: "top",
1095
- _depth: 0
1095
+ _depth: 0,
1096
+ _containsTarget: true,
1097
+ _containsObstacle: true,
1098
+ _completelyInsideObstacle: false
1096
1099
  }
1097
1100
  ];
1098
1101
  this.finishedNodes = [];
1099
1102
  this.nodeToOverlappingObstaclesMap = /* @__PURE__ */ new Map();
1103
+ this.targets = this.srj.connections.flatMap(
1104
+ (c) => c.pointsToConnect.map((p) => ({ ...p, connectionName: c.name }))
1105
+ );
1100
1106
  }
1101
1107
  unfinishedNodes;
1102
1108
  finishedNodes;
1103
1109
  nodeToOverlappingObstaclesMap;
1104
1110
  // targetObstacleMap: Record<string, { obstacle: Obstacle, node: CapacityMeshNode }>
1105
1111
  MAX_DEPTH = 4;
1112
+ targets;
1106
1113
  _nextNodeCounter = 0;
1107
1114
  getNextNodeId() {
1108
1115
  return `cn${this._nextNodeCounter++}`;
@@ -1112,10 +1119,7 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1112
1119
  }
1113
1120
  getTargetNameIfNodeContainsTarget(node) {
1114
1121
  const overlappingObstacles = this.getOverlappingObstacles(node);
1115
- const targets = this.srj.connections.flatMap(
1116
- (c) => c.pointsToConnect.map((p) => ({ ...p, connectionName: c.name }))
1117
- );
1118
- for (const target of targets) {
1122
+ for (const target of this.targets) {
1119
1123
  const targetObstacle = overlappingObstacles.find(
1120
1124
  (o) => isPointInRect(target, o)
1121
1125
  );
@@ -1232,9 +1236,9 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1232
1236
  _parent: parent
1233
1237
  };
1234
1238
  childNode._containsObstacle = this.doesNodeOverlapObstacle(childNode);
1239
+ childNode._targetConnectionName = this.getTargetNameIfNodeContainsTarget(childNode) ?? void 0;
1240
+ childNode._containsTarget = Boolean(childNode._targetConnectionName);
1235
1241
  if (childNode._containsObstacle) {
1236
- childNode._targetConnectionName = this.getTargetNameIfNodeContainsTarget(childNode) ?? void 0;
1237
- childNode._containsTarget = Boolean(childNode._targetConnectionName);
1238
1242
  childNode._completelyInsideObstacle = this.isNodeCompletelyInsideObstacle(childNode);
1239
1243
  }
1240
1244
  if (childNode._completelyInsideObstacle && !childNode._containsTarget)
@@ -2892,6 +2896,9 @@ var CapacityPathingSolver = class extends BaseSolver {
2892
2896
  );
2893
2897
  }
2894
2898
  }
2899
+ isConnectedToEndGoal(node, endGoal) {
2900
+ return this.nodeEdgeMap.get(node.capacityMeshNodeId).some((edge) => edge.nodeIds.includes(endGoal.capacityMeshNodeId));
2901
+ }
2895
2902
  _step() {
2896
2903
  const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
2897
2904
  if (!nextConnection) {
@@ -2918,8 +2925,14 @@ var CapacityPathingSolver = class extends BaseSolver {
2918
2925
  this.visitedNodes = null;
2919
2926
  return;
2920
2927
  }
2921
- if (currentCandidate.node.capacityMeshNodeId === end.capacityMeshNodeId) {
2922
- nextConnection.path = this.getBacktrackedPath(currentCandidate);
2928
+ if (this.isConnectedToEndGoal(currentCandidate.node, end)) {
2929
+ nextConnection.path = this.getBacktrackedPath({
2930
+ prevCandidate: currentCandidate,
2931
+ node: end,
2932
+ f: 0,
2933
+ g: 0,
2934
+ h: 0
2935
+ });
2923
2936
  this.reduceCapacityAlongPath(nextConnection);
2924
2937
  this.currentConnectionIndex++;
2925
2938
  this.candidates = null;
@@ -2998,19 +3011,30 @@ ${node.width.toFixed(2)}x${node.height.toFixed(2)}`
2998
3011
  }
2999
3012
  }
3000
3013
  }
3014
+ const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
3015
+ if (nextConnection) {
3016
+ const [start, end] = nextConnection.connection.pointsToConnect;
3017
+ graphics.lines.push({
3018
+ points: [
3019
+ { x: start.x, y: start.y },
3020
+ { x: end.x, y: end.y }
3021
+ ],
3022
+ strokeColor: "red",
3023
+ strokeDash: "10 5"
3024
+ });
3025
+ }
3001
3026
  if (this.candidates) {
3002
- const topCandidates = this.candidates.slice(0, 50);
3027
+ const topCandidates = this.candidates.slice(0, 5);
3003
3028
  const connectionName = this.connectionsWithNodes[this.currentConnectionIndex].connection.name;
3004
3029
  topCandidates.forEach((candidate, index) => {
3005
- const opacity = 0.05 * (1 - index / 50);
3030
+ const opacity = 0.5 * (1 - index / 5);
3006
3031
  const backtrackedPath = this.getBacktrackedPath(candidate);
3007
3032
  graphics.lines.push({
3008
3033
  points: backtrackedPath.map(({ center: { x, y } }) => ({ x, y })),
3009
3034
  strokeColor: safeTransparentize(
3010
3035
  this.colorMap[connectionName] ?? "red",
3011
3036
  1 - opacity
3012
- ),
3013
- strokeWidth: 0.5
3037
+ )
3014
3038
  });
3015
3039
  });
3016
3040
  }
@@ -3960,6 +3984,308 @@ ${segment.nodePortSegmentId}`
3960
3984
  }
3961
3985
  };
3962
3986
 
3987
+ // lib/solvers/NetToPointPairsSolver/buildMinimumSpanningTree.ts
3988
+ var KDNode = class {
3989
+ point;
3990
+ left = null;
3991
+ right = null;
3992
+ constructor(point) {
3993
+ this.point = point;
3994
+ }
3995
+ };
3996
+ var KDTree = class {
3997
+ root = null;
3998
+ constructor(points) {
3999
+ if (points.length > 0) {
4000
+ this.root = this.buildTree(points, 0);
4001
+ }
4002
+ }
4003
+ buildTree(points, depth) {
4004
+ const axis = depth % 2 === 0 ? "x" : "y";
4005
+ points.sort((a, b) => a[axis] - b[axis]);
4006
+ const medianIndex = Math.floor(points.length / 2);
4007
+ const node = new KDNode(points[medianIndex]);
4008
+ if (medianIndex > 0) {
4009
+ node.left = this.buildTree(points.slice(0, medianIndex), depth + 1);
4010
+ }
4011
+ if (medianIndex < points.length - 1) {
4012
+ node.right = this.buildTree(points.slice(medianIndex + 1), depth + 1);
4013
+ }
4014
+ return node;
4015
+ }
4016
+ // Find the nearest neighbor to a query point
4017
+ findNearestNeighbor(queryPoint) {
4018
+ if (!this.root) {
4019
+ throw new Error("Tree is empty");
4020
+ }
4021
+ const best = this.root.point;
4022
+ const bestDistance = this.distance(queryPoint, best);
4023
+ this.nearestNeighborSearch(this.root, queryPoint, 0, best, bestDistance);
4024
+ return best;
4025
+ }
4026
+ nearestNeighborSearch(node, queryPoint, depth, best, bestDistance) {
4027
+ if (!node) {
4028
+ return best;
4029
+ }
4030
+ const axis = depth % 2 ? "x" : "y";
4031
+ const currentDistance = this.distance(queryPoint, node.point);
4032
+ if (currentDistance < bestDistance) {
4033
+ best = node.point;
4034
+ bestDistance = currentDistance;
4035
+ }
4036
+ const axisDiff = queryPoint[axis] - node.point[axis];
4037
+ const firstBranch = axisDiff <= 0 ? node.left : node.right;
4038
+ const secondBranch = axisDiff <= 0 ? node.right : node.left;
4039
+ best = this.nearestNeighborSearch(
4040
+ firstBranch,
4041
+ queryPoint,
4042
+ depth + 1,
4043
+ best,
4044
+ bestDistance
4045
+ );
4046
+ bestDistance = this.distance(queryPoint, best);
4047
+ if (Math.abs(axisDiff) < bestDistance) {
4048
+ best = this.nearestNeighborSearch(
4049
+ secondBranch,
4050
+ queryPoint,
4051
+ depth + 1,
4052
+ best,
4053
+ bestDistance
4054
+ );
4055
+ }
4056
+ return best;
4057
+ }
4058
+ // Find k nearest neighbors
4059
+ findKNearestNeighbors(queryPoint, k) {
4060
+ if (!this.root) {
4061
+ return [];
4062
+ }
4063
+ const neighbors = [];
4064
+ this.kNearestNeighborSearch(this.root, queryPoint, 0, neighbors, k);
4065
+ return neighbors.sort((a, b) => a.distance - b.distance).slice(0, k).map((n) => n.point);
4066
+ }
4067
+ kNearestNeighborSearch(node, queryPoint, depth, neighbors, k) {
4068
+ if (!node) {
4069
+ return;
4070
+ }
4071
+ const axis = depth % 2 ? "x" : "y";
4072
+ const currentDistance = this.distance(queryPoint, node.point);
4073
+ neighbors.push({ point: node.point, distance: currentDistance });
4074
+ const axisDiff = queryPoint[axis] - node.point[axis];
4075
+ const firstBranch = axisDiff <= 0 ? node.left : node.right;
4076
+ const secondBranch = axisDiff <= 0 ? node.right : node.left;
4077
+ this.kNearestNeighborSearch(
4078
+ firstBranch,
4079
+ queryPoint,
4080
+ depth + 1,
4081
+ neighbors,
4082
+ k
4083
+ );
4084
+ let kthDistance = Infinity;
4085
+ if (neighbors.length >= k) {
4086
+ neighbors.sort((a, b) => a.distance - b.distance);
4087
+ kthDistance = neighbors[k - 1]?.distance || Infinity;
4088
+ }
4089
+ if (Math.abs(axisDiff) < kthDistance || neighbors.length < k) {
4090
+ this.kNearestNeighborSearch(
4091
+ secondBranch,
4092
+ queryPoint,
4093
+ depth + 1,
4094
+ neighbors,
4095
+ k
4096
+ );
4097
+ }
4098
+ }
4099
+ // Calculate Euclidean distance between two points
4100
+ distance(a, b) {
4101
+ return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
4102
+ }
4103
+ };
4104
+ var DisjointSet = class {
4105
+ parent = /* @__PURE__ */ new Map();
4106
+ rank = /* @__PURE__ */ new Map();
4107
+ constructor(points) {
4108
+ for (const point of points) {
4109
+ const key = this.pointToKey(point);
4110
+ this.parent.set(key, key);
4111
+ this.rank.set(key, 0);
4112
+ }
4113
+ }
4114
+ pointToKey(point) {
4115
+ return `${point.x},${point.y}`;
4116
+ }
4117
+ find(point) {
4118
+ const key = this.pointToKey(point);
4119
+ if (!this.parent.has(key)) {
4120
+ throw new Error(`Point ${key} not found in DisjointSet`);
4121
+ }
4122
+ let root = key;
4123
+ while (root !== this.parent.get(root)) {
4124
+ root = this.parent.get(root);
4125
+ }
4126
+ let current = key;
4127
+ while (current !== root) {
4128
+ const next = this.parent.get(current);
4129
+ this.parent.set(current, root);
4130
+ current = next;
4131
+ }
4132
+ return root;
4133
+ }
4134
+ union(pointA, pointB) {
4135
+ const rootA = this.find(pointA);
4136
+ const rootB = this.find(pointB);
4137
+ if (rootA === rootB) {
4138
+ return false;
4139
+ }
4140
+ const rankA = this.rank.get(rootA) || 0;
4141
+ const rankB = this.rank.get(rootB) || 0;
4142
+ if (rankA < rankB) {
4143
+ this.parent.set(rootA, rootB);
4144
+ } else if (rankA > rankB) {
4145
+ this.parent.set(rootB, rootA);
4146
+ } else {
4147
+ this.parent.set(rootB, rootA);
4148
+ this.rank.set(rootA, rankA + 1);
4149
+ }
4150
+ return true;
4151
+ }
4152
+ };
4153
+ function buildMinimumSpanningTree(points) {
4154
+ if (points.length <= 1) {
4155
+ return [];
4156
+ }
4157
+ const kdTree = new KDTree(points);
4158
+ const edges = [];
4159
+ const k = Math.min(10, points.length - 1);
4160
+ for (const point of points) {
4161
+ const neighbors = kdTree.findKNearestNeighbors(point, k + 1);
4162
+ for (const neighbor of neighbors) {
4163
+ if (point.x === neighbor.x && point.y === neighbor.y) {
4164
+ continue;
4165
+ }
4166
+ const distance3 = Math.sqrt(
4167
+ (point.x - neighbor.x) ** 2 + (point.y - neighbor.y) ** 2
4168
+ );
4169
+ edges.push({
4170
+ from: point,
4171
+ to: neighbor,
4172
+ weight: distance3
4173
+ });
4174
+ }
4175
+ }
4176
+ edges.sort((a, b) => a.weight - b.weight);
4177
+ const disjointSet = new DisjointSet(points);
4178
+ const mstEdges = [];
4179
+ for (const edge of edges) {
4180
+ if (disjointSet.union(edge.from, edge.to)) {
4181
+ mstEdges.push(edge);
4182
+ if (mstEdges.length === points.length - 1) {
4183
+ break;
4184
+ }
4185
+ }
4186
+ }
4187
+ return mstEdges;
4188
+ }
4189
+
4190
+ // lib/solvers/NetToPointPairsSolver/NetToPointPairsSolver.ts
4191
+ var NetToPointPairsSolver = class extends BaseSolver {
4192
+ constructor(ogSrj, colorMap = {}) {
4193
+ super();
4194
+ this.ogSrj = ogSrj;
4195
+ this.colorMap = colorMap;
4196
+ this.unprocessedConnections = ogSrj.connections;
4197
+ this.newConnections = [];
4198
+ }
4199
+ unprocessedConnections;
4200
+ newConnections;
4201
+ _step() {
4202
+ if (this.unprocessedConnections.length === 0) {
4203
+ this.solved = true;
4204
+ return;
4205
+ }
4206
+ const connection = this.unprocessedConnections.pop();
4207
+ if (connection.pointsToConnect.length === 2) {
4208
+ this.newConnections.push(connection);
4209
+ return;
4210
+ }
4211
+ const edges = buildMinimumSpanningTree(connection.pointsToConnect);
4212
+ for (const edge of edges) {
4213
+ this.newConnections.push({
4214
+ pointsToConnect: [edge.from, edge.to],
4215
+ name: connection.name
4216
+ });
4217
+ }
4218
+ }
4219
+ getNewSimpleRouteJson() {
4220
+ return {
4221
+ ...this.ogSrj,
4222
+ connections: this.newConnections
4223
+ };
4224
+ }
4225
+ visualize() {
4226
+ const graphics = {
4227
+ lines: [],
4228
+ points: [],
4229
+ rects: [],
4230
+ circles: [],
4231
+ coordinateSystem: "cartesian",
4232
+ title: "Net To Point Pairs Visualization"
4233
+ };
4234
+ this.unprocessedConnections.forEach((connection) => {
4235
+ connection.pointsToConnect.forEach((point) => {
4236
+ graphics.points.push({
4237
+ x: point.x,
4238
+ y: point.y,
4239
+ color: "red",
4240
+ label: connection.name
4241
+ });
4242
+ });
4243
+ const fullyConnectedEdgeCount = connection.pointsToConnect.length ** 2;
4244
+ const random = seededRandom(0);
4245
+ const alreadyPlacedEdges = /* @__PURE__ */ new Set();
4246
+ for (let i = 0; i < Math.max(
4247
+ fullyConnectedEdgeCount,
4248
+ connection.pointsToConnect.length * 2
4249
+ ); i++) {
4250
+ const a = Math.floor(random() * connection.pointsToConnect.length);
4251
+ const b = Math.floor(random() * connection.pointsToConnect.length);
4252
+ if (alreadyPlacedEdges.has(`${a}-${b}`)) continue;
4253
+ alreadyPlacedEdges.add(`${a}-${b}`);
4254
+ graphics.lines.push({
4255
+ points: [
4256
+ connection.pointsToConnect[a],
4257
+ connection.pointsToConnect[b]
4258
+ ],
4259
+ strokeColor: "rgba(255,0,0,0.25)"
4260
+ });
4261
+ }
4262
+ });
4263
+ this.newConnections.forEach((connection) => {
4264
+ const color = this.colorMap?.[connection.name] || "blue";
4265
+ connection.pointsToConnect.forEach((point) => {
4266
+ graphics.points.push({
4267
+ x: point.x,
4268
+ y: point.y,
4269
+ color,
4270
+ label: connection.name
4271
+ });
4272
+ });
4273
+ for (let i = 0; i < connection.pointsToConnect.length - 1; i++) {
4274
+ for (let j = i + 1; j < connection.pointsToConnect.length; j++) {
4275
+ graphics.lines.push({
4276
+ points: [
4277
+ connection.pointsToConnect[i],
4278
+ connection.pointsToConnect[j]
4279
+ ],
4280
+ strokeColor: color
4281
+ });
4282
+ }
4283
+ }
4284
+ });
4285
+ return graphics;
4286
+ }
4287
+ };
4288
+
3963
4289
  // lib/solvers/CapacityMeshSolver/CapacityMeshSolver.ts
3964
4290
  var CapacityMeshSolver = class extends BaseSolver {
3965
4291
  constructor(srj, opts = {}) {
@@ -3977,10 +4303,10 @@ var CapacityMeshSolver = class extends BaseSolver {
3977
4303
  targetMinCapacity
3978
4304
  );
3979
4305
  }
3980
- this.nodeSolver = new CapacityMeshNodeSolver(srj, this.opts);
3981
4306
  this.connMap = getConnectivityMapFromSimpleRouteJson(srj);
3982
4307
  this.colorMap = getColorMap(srj, this.connMap);
3983
4308
  }
4309
+ netToPointPairsSolver;
3984
4310
  nodeSolver;
3985
4311
  nodeTargetMerger;
3986
4312
  edgeSolver;
@@ -4004,7 +4330,19 @@ var CapacityMeshSolver = class extends BaseSolver {
4004
4330
  }
4005
4331
  return;
4006
4332
  }
4007
- if (!this.nodeSolver.solved) {
4333
+ if (!this.netToPointPairsSolver) {
4334
+ this.netToPointPairsSolver = new NetToPointPairsSolver(
4335
+ this.srj,
4336
+ this.colorMap
4337
+ );
4338
+ this.activeSolver = this.netToPointPairsSolver;
4339
+ return;
4340
+ }
4341
+ if (!this.nodeSolver) {
4342
+ this.nodeSolver = new CapacityMeshNodeSolver(
4343
+ this.netToPointPairsSolver.getNewSimpleRouteJson(),
4344
+ this.opts
4345
+ );
4008
4346
  this.activeSolver = this.nodeSolver;
4009
4347
  return;
4010
4348
  }
@@ -4025,7 +4363,7 @@ var CapacityMeshSolver = class extends BaseSolver {
4025
4363
  }
4026
4364
  if (!this.pathingSolver) {
4027
4365
  this.pathingSolver = new CapacityPathingSolver4_FlexibleNegativeCapacity({
4028
- simpleRouteJson: this.srj,
4366
+ simpleRouteJson: this.netToPointPairsSolver.getNewSimpleRouteJson(),
4029
4367
  nodes,
4030
4368
  edges: this.edgeSolver.edges,
4031
4369
  colorMap: this.colorMap,
@@ -4082,7 +4420,8 @@ var CapacityMeshSolver = class extends BaseSolver {
4082
4420
  }
4083
4421
  visualize() {
4084
4422
  if (!this.solved && this.activeSolver) return this.activeSolver.visualize();
4085
- const nodeViz = this.nodeSolver.visualize();
4423
+ const netToPPSolver = this.netToPointPairsSolver?.visualize();
4424
+ const nodeViz = this.nodeSolver?.visualize();
4086
4425
  const edgeViz = this.edgeSolver?.visualize();
4087
4426
  const pathingViz = this.pathingSolver?.visualize();
4088
4427
  const edgeToPortSegmentViz = this.edgeToPortSegmentSolver?.visualize();
@@ -4090,11 +4429,17 @@ var CapacityMeshSolver = class extends BaseSolver {
4090
4429
  const segmentOptimizationViz = this.segmentToPointOptimizer?.visualize();
4091
4430
  const highDensityViz = this.highDensityRouteSolver?.visualize();
4092
4431
  const problemViz = {
4093
- points: [...nodeViz.points],
4094
- rects: [...nodeViz.rects?.filter((r) => r.label?.includes("obstacle"))]
4432
+ points: [...this.srj.connections.flatMap((c) => c.pointsToConnect)],
4433
+ rects: [
4434
+ ...(this.srj.obstacles ?? []).map((o) => ({
4435
+ ...o,
4436
+ fill: "rgba(255,0,0,0.25)"
4437
+ }))
4438
+ ]
4095
4439
  };
4096
4440
  const visualizations = [
4097
4441
  problemViz,
4442
+ netToPPSolver,
4098
4443
  nodeViz,
4099
4444
  edgeViz,
4100
4445
  pathingViz,