@tscircuit/capacity-autorouter 0.0.2 → 0.0.4
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/README.md +5 -5
- package/dist/index.d.ts +823 -0
- package/dist/index.js +4188 -0
- package/dist/index.js.map +1 -0
- package/package.json +1 -1
package/dist/index.js
ADDED
|
@@ -0,0 +1,4188 @@
|
|
|
1
|
+
// lib/utils/combineVisualizations.ts
|
|
2
|
+
var combineVisualizations = (...visualizations) => {
|
|
3
|
+
const combined = {
|
|
4
|
+
points: [],
|
|
5
|
+
lines: [],
|
|
6
|
+
circles: [],
|
|
7
|
+
rects: []
|
|
8
|
+
};
|
|
9
|
+
visualizations.forEach((viz, i) => {
|
|
10
|
+
if (viz.lines) {
|
|
11
|
+
combined.lines = [
|
|
12
|
+
...combined.lines || [],
|
|
13
|
+
...viz.lines.map((l) => ({ ...l, step: i }))
|
|
14
|
+
];
|
|
15
|
+
}
|
|
16
|
+
if (viz.points) {
|
|
17
|
+
combined.points = [
|
|
18
|
+
...combined.points || [],
|
|
19
|
+
...viz.points.map((p) => ({ ...p, step: i }))
|
|
20
|
+
];
|
|
21
|
+
}
|
|
22
|
+
if (viz.circles) {
|
|
23
|
+
combined.circles = [
|
|
24
|
+
...combined.circles || [],
|
|
25
|
+
...viz.circles.map((c) => ({ ...c, step: i }))
|
|
26
|
+
];
|
|
27
|
+
}
|
|
28
|
+
if (viz.rects) {
|
|
29
|
+
combined.rects = [
|
|
30
|
+
...combined.rects || [],
|
|
31
|
+
...viz.rects.map((r) => ({ ...r, step: i }))
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
return combined;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// lib/solvers/BaseSolver.ts
|
|
39
|
+
var BaseSolver = class {
|
|
40
|
+
MAX_ITERATIONS = 1e3;
|
|
41
|
+
solved = false;
|
|
42
|
+
failed = false;
|
|
43
|
+
iterations = 0;
|
|
44
|
+
progress = 0;
|
|
45
|
+
error = null;
|
|
46
|
+
/** DO NOT OVERRIDE! Override _step() instead */
|
|
47
|
+
step() {
|
|
48
|
+
if (this.solved) return;
|
|
49
|
+
if (this.failed) return;
|
|
50
|
+
this.iterations++;
|
|
51
|
+
try {
|
|
52
|
+
this._step();
|
|
53
|
+
} catch (e) {
|
|
54
|
+
this.error = `${this.constructor.name} error: ${e}`;
|
|
55
|
+
console.error(this.error);
|
|
56
|
+
this.failed = true;
|
|
57
|
+
throw e;
|
|
58
|
+
}
|
|
59
|
+
if (!this.solved && this.iterations > this.MAX_ITERATIONS) {
|
|
60
|
+
this.error = `${this.constructor.name} did not converge`;
|
|
61
|
+
console.error(this.error);
|
|
62
|
+
this.failed = true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
_step() {
|
|
66
|
+
}
|
|
67
|
+
solve() {
|
|
68
|
+
while (!this.solved && !this.failed) {
|
|
69
|
+
this.step();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
visualize() {
|
|
73
|
+
return {
|
|
74
|
+
lines: [],
|
|
75
|
+
points: [],
|
|
76
|
+
rects: [],
|
|
77
|
+
circles: []
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// node_modules/@tscircuit/math-utils/dist/chunk-CHQOCSFB.js
|
|
83
|
+
function doSegmentsIntersect(p1, q1, p2, q2) {
|
|
84
|
+
const o1 = orientation(p1, q1, p2);
|
|
85
|
+
const o2 = orientation(p1, q1, q2);
|
|
86
|
+
const o3 = orientation(p2, q2, p1);
|
|
87
|
+
const o4 = orientation(p2, q2, q1);
|
|
88
|
+
if (o1 !== o2 && o3 !== o4) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
if (o1 === 0 && onSegment(p1, p2, q1)) return true;
|
|
92
|
+
if (o2 === 0 && onSegment(p1, q2, q1)) return true;
|
|
93
|
+
if (o3 === 0 && onSegment(p2, p1, q2)) return true;
|
|
94
|
+
if (o4 === 0 && onSegment(p2, q1, q2)) return true;
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
function orientation(p, q, r) {
|
|
98
|
+
const val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
|
|
99
|
+
if (val === 0) return 0;
|
|
100
|
+
return val > 0 ? 1 : 2;
|
|
101
|
+
}
|
|
102
|
+
function onSegment(p, q, r) {
|
|
103
|
+
return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y);
|
|
104
|
+
}
|
|
105
|
+
function pointToSegmentDistance(p, v, w) {
|
|
106
|
+
const l2 = (w.x - v.x) ** 2 + (w.y - v.y) ** 2;
|
|
107
|
+
if (l2 === 0) return distance(p, v);
|
|
108
|
+
let t = ((p.x - v.x) * (w.x - v.x) + (p.y - v.y) * (w.y - v.y)) / l2;
|
|
109
|
+
t = Math.max(0, Math.min(1, t));
|
|
110
|
+
const projection = {
|
|
111
|
+
x: v.x + t * (w.x - v.x),
|
|
112
|
+
y: v.y + t * (w.y - v.y)
|
|
113
|
+
};
|
|
114
|
+
return distance(p, projection);
|
|
115
|
+
}
|
|
116
|
+
function distance(p1, p2) {
|
|
117
|
+
const dx = p1.x - p2.x;
|
|
118
|
+
const dy = p1.y - p2.y;
|
|
119
|
+
return Math.sqrt(dx * dx + dy * dy);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// lib/solvers/CapacityMeshSolver/CapacityMeshEdgeSolver.ts
|
|
123
|
+
var CapacityMeshEdgeSolver = class extends BaseSolver {
|
|
124
|
+
constructor(nodes) {
|
|
125
|
+
super();
|
|
126
|
+
this.nodes = nodes;
|
|
127
|
+
this.edges = [];
|
|
128
|
+
}
|
|
129
|
+
edges;
|
|
130
|
+
getNextCapacityMeshEdgeId() {
|
|
131
|
+
return `ce${this.edges.length}`;
|
|
132
|
+
}
|
|
133
|
+
step() {
|
|
134
|
+
this.edges = [];
|
|
135
|
+
for (let i = 0; i < this.nodes.length; i++) {
|
|
136
|
+
for (let j = i + 1; j < this.nodes.length; j++) {
|
|
137
|
+
if (this.areNodesBordering(this.nodes[i], this.nodes[j])) {
|
|
138
|
+
this.edges.push({
|
|
139
|
+
capacityMeshEdgeId: this.getNextCapacityMeshEdgeId(),
|
|
140
|
+
nodeIds: [
|
|
141
|
+
this.nodes[i].capacityMeshNodeId,
|
|
142
|
+
this.nodes[j].capacityMeshNodeId
|
|
143
|
+
]
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
const targetNodes = this.nodes.filter((node) => node._containsTarget);
|
|
149
|
+
for (const targetNode of targetNodes) {
|
|
150
|
+
const hasEdge = this.edges.some(
|
|
151
|
+
(edge) => edge.nodeIds.includes(targetNode.capacityMeshNodeId)
|
|
152
|
+
);
|
|
153
|
+
if (hasEdge) continue;
|
|
154
|
+
let nearestNode = null;
|
|
155
|
+
let nearestDistance = Infinity;
|
|
156
|
+
for (const node of this.nodes) {
|
|
157
|
+
if (node._containsObstacle) continue;
|
|
158
|
+
if (node._containsTarget) continue;
|
|
159
|
+
const dist = distance(targetNode.center, node.center);
|
|
160
|
+
if (dist < nearestDistance) {
|
|
161
|
+
nearestDistance = dist;
|
|
162
|
+
nearestNode = node;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (nearestNode) {
|
|
166
|
+
this.edges.push({
|
|
167
|
+
capacityMeshEdgeId: this.getNextCapacityMeshEdgeId(),
|
|
168
|
+
nodeIds: [
|
|
169
|
+
targetNode.capacityMeshNodeId,
|
|
170
|
+
nearestNode.capacityMeshNodeId
|
|
171
|
+
]
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
this.solved = true;
|
|
176
|
+
}
|
|
177
|
+
areNodesBordering(node1, node2) {
|
|
178
|
+
const n1Left = node1.center.x - node1.width / 2;
|
|
179
|
+
const n1Right = node1.center.x + node1.width / 2;
|
|
180
|
+
const n1Top = node1.center.y - node1.height / 2;
|
|
181
|
+
const n1Bottom = node1.center.y + node1.height / 2;
|
|
182
|
+
const n2Left = node2.center.x - node2.width / 2;
|
|
183
|
+
const n2Right = node2.center.x + node2.width / 2;
|
|
184
|
+
const n2Top = node2.center.y - node2.height / 2;
|
|
185
|
+
const n2Bottom = node2.center.y + node2.height / 2;
|
|
186
|
+
const epsilon = 1e-3;
|
|
187
|
+
const shareVerticalBorder = (Math.abs(n1Right - n2Left) < epsilon || Math.abs(n1Left - n2Right) < epsilon) && Math.min(n1Bottom, n2Bottom) - Math.max(n1Top, n2Top) >= epsilon;
|
|
188
|
+
const shareHorizontalBorder = (Math.abs(n1Bottom - n2Top) < epsilon || Math.abs(n1Top - n2Bottom) < epsilon) && Math.min(n1Right, n2Right) - Math.max(n1Left, n2Left) >= epsilon;
|
|
189
|
+
return shareVerticalBorder || shareHorizontalBorder;
|
|
190
|
+
}
|
|
191
|
+
visualize() {
|
|
192
|
+
const graphics = {
|
|
193
|
+
lines: [],
|
|
194
|
+
points: [],
|
|
195
|
+
rects: this.nodes.map((node) => ({
|
|
196
|
+
width: Math.max(node.width - 2, node.width * 0.8),
|
|
197
|
+
height: Math.max(node.height - 2, node.height * 0.8),
|
|
198
|
+
center: node.center,
|
|
199
|
+
fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : node._containsTarget ? "rgba(0,255,0,0.2)" : "rgba(0,0,0,0.1)"
|
|
200
|
+
})),
|
|
201
|
+
circles: []
|
|
202
|
+
};
|
|
203
|
+
for (const edge of this.edges) {
|
|
204
|
+
const node1 = this.nodes.find(
|
|
205
|
+
(node) => node.capacityMeshNodeId === edge.nodeIds[0]
|
|
206
|
+
);
|
|
207
|
+
const node2 = this.nodes.find(
|
|
208
|
+
(node) => node.capacityMeshNodeId === edge.nodeIds[1]
|
|
209
|
+
);
|
|
210
|
+
if (node1?.center && node2?.center) {
|
|
211
|
+
graphics.lines.push({
|
|
212
|
+
points: [node1.center, node2.center]
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return graphics;
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
// node_modules/@babel/runtime/helpers/esm/extends.js
|
|
221
|
+
function _extends() {
|
|
222
|
+
return _extends = Object.assign ? Object.assign.bind() : function(n) {
|
|
223
|
+
for (var e = 1; e < arguments.length; e++) {
|
|
224
|
+
var t = arguments[e];
|
|
225
|
+
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
|
|
226
|
+
}
|
|
227
|
+
return n;
|
|
228
|
+
}, _extends.apply(null, arguments);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// node_modules/@babel/runtime/helpers/esm/assertThisInitialized.js
|
|
232
|
+
function _assertThisInitialized(e) {
|
|
233
|
+
if (void 0 === e) throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
|
|
234
|
+
return e;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// node_modules/@babel/runtime/helpers/esm/setPrototypeOf.js
|
|
238
|
+
function _setPrototypeOf(t, e) {
|
|
239
|
+
return _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function(t2, e2) {
|
|
240
|
+
return t2.__proto__ = e2, t2;
|
|
241
|
+
}, _setPrototypeOf(t, e);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// node_modules/@babel/runtime/helpers/esm/inheritsLoose.js
|
|
245
|
+
function _inheritsLoose(t, o) {
|
|
246
|
+
t.prototype = Object.create(o.prototype), t.prototype.constructor = t, _setPrototypeOf(t, o);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// node_modules/@babel/runtime/helpers/esm/getPrototypeOf.js
|
|
250
|
+
function _getPrototypeOf(t) {
|
|
251
|
+
return _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function(t2) {
|
|
252
|
+
return t2.__proto__ || Object.getPrototypeOf(t2);
|
|
253
|
+
}, _getPrototypeOf(t);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// node_modules/@babel/runtime/helpers/esm/isNativeFunction.js
|
|
257
|
+
function _isNativeFunction(t) {
|
|
258
|
+
try {
|
|
259
|
+
return -1 !== Function.toString.call(t).indexOf("[native code]");
|
|
260
|
+
} catch (n) {
|
|
261
|
+
return "function" == typeof t;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// node_modules/@babel/runtime/helpers/esm/isNativeReflectConstruct.js
|
|
266
|
+
function _isNativeReflectConstruct() {
|
|
267
|
+
try {
|
|
268
|
+
var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function() {
|
|
269
|
+
}));
|
|
270
|
+
} catch (t2) {
|
|
271
|
+
}
|
|
272
|
+
return (_isNativeReflectConstruct = function _isNativeReflectConstruct2() {
|
|
273
|
+
return !!t;
|
|
274
|
+
})();
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// node_modules/@babel/runtime/helpers/esm/construct.js
|
|
278
|
+
function _construct(t, e, r) {
|
|
279
|
+
if (_isNativeReflectConstruct()) return Reflect.construct.apply(null, arguments);
|
|
280
|
+
var o = [null];
|
|
281
|
+
o.push.apply(o, e);
|
|
282
|
+
var p = new (t.bind.apply(t, o))();
|
|
283
|
+
return r && _setPrototypeOf(p, r.prototype), p;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// node_modules/@babel/runtime/helpers/esm/wrapNativeSuper.js
|
|
287
|
+
function _wrapNativeSuper(t) {
|
|
288
|
+
var r = "function" == typeof Map ? /* @__PURE__ */ new Map() : void 0;
|
|
289
|
+
return _wrapNativeSuper = function _wrapNativeSuper2(t2) {
|
|
290
|
+
if (null === t2 || !_isNativeFunction(t2)) return t2;
|
|
291
|
+
if ("function" != typeof t2) throw new TypeError("Super expression must either be null or a function");
|
|
292
|
+
if (void 0 !== r) {
|
|
293
|
+
if (r.has(t2)) return r.get(t2);
|
|
294
|
+
r.set(t2, Wrapper);
|
|
295
|
+
}
|
|
296
|
+
function Wrapper() {
|
|
297
|
+
return _construct(t2, arguments, _getPrototypeOf(this).constructor);
|
|
298
|
+
}
|
|
299
|
+
return Wrapper.prototype = Object.create(t2.prototype, {
|
|
300
|
+
constructor: {
|
|
301
|
+
value: Wrapper,
|
|
302
|
+
enumerable: false,
|
|
303
|
+
writable: true,
|
|
304
|
+
configurable: true
|
|
305
|
+
}
|
|
306
|
+
}), _setPrototypeOf(Wrapper, t2);
|
|
307
|
+
}, _wrapNativeSuper(t);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// node_modules/polished/dist/polished.esm.js
|
|
311
|
+
var ERRORS = {
|
|
312
|
+
"1": "Passed invalid arguments to hsl, please pass multiple numbers e.g. hsl(360, 0.75, 0.4) or an object e.g. rgb({ hue: 255, saturation: 0.4, lightness: 0.75 }).\n\n",
|
|
313
|
+
"2": "Passed invalid arguments to hsla, please pass multiple numbers e.g. hsla(360, 0.75, 0.4, 0.7) or an object e.g. rgb({ hue: 255, saturation: 0.4, lightness: 0.75, alpha: 0.7 }).\n\n",
|
|
314
|
+
"3": "Passed an incorrect argument to a color function, please pass a string representation of a color.\n\n",
|
|
315
|
+
"4": "Couldn't generate valid rgb string from %s, it returned %s.\n\n",
|
|
316
|
+
"5": "Couldn't parse the color string. Please provide the color as a string in hex, rgb, rgba, hsl or hsla notation.\n\n",
|
|
317
|
+
"6": "Passed invalid arguments to rgb, please pass multiple numbers e.g. rgb(255, 205, 100) or an object e.g. rgb({ red: 255, green: 205, blue: 100 }).\n\n",
|
|
318
|
+
"7": "Passed invalid arguments to rgba, please pass multiple numbers e.g. rgb(255, 205, 100, 0.75) or an object e.g. rgb({ red: 255, green: 205, blue: 100, alpha: 0.75 }).\n\n",
|
|
319
|
+
"8": "Passed invalid argument to toColorString, please pass a RgbColor, RgbaColor, HslColor or HslaColor object.\n\n",
|
|
320
|
+
"9": "Please provide a number of steps to the modularScale helper.\n\n",
|
|
321
|
+
"10": "Please pass a number or one of the predefined scales to the modularScale helper as the ratio.\n\n",
|
|
322
|
+
"11": 'Invalid value passed as base to modularScale, expected number or em string but got "%s"\n\n',
|
|
323
|
+
"12": 'Expected a string ending in "px" or a number passed as the first argument to %s(), got "%s" instead.\n\n',
|
|
324
|
+
"13": 'Expected a string ending in "px" or a number passed as the second argument to %s(), got "%s" instead.\n\n',
|
|
325
|
+
"14": 'Passed invalid pixel value ("%s") to %s(), please pass a value like "12px" or 12.\n\n',
|
|
326
|
+
"15": 'Passed invalid base value ("%s") to %s(), please pass a value like "12px" or 12.\n\n',
|
|
327
|
+
"16": "You must provide a template to this method.\n\n",
|
|
328
|
+
"17": "You passed an unsupported selector state to this method.\n\n",
|
|
329
|
+
"18": "minScreen and maxScreen must be provided as stringified numbers with the same units.\n\n",
|
|
330
|
+
"19": "fromSize and toSize must be provided as stringified numbers with the same units.\n\n",
|
|
331
|
+
"20": "expects either an array of objects or a single object with the properties prop, fromSize, and toSize.\n\n",
|
|
332
|
+
"21": "expects the objects in the first argument array to have the properties `prop`, `fromSize`, and `toSize`.\n\n",
|
|
333
|
+
"22": "expects the first argument object to have the properties `prop`, `fromSize`, and `toSize`.\n\n",
|
|
334
|
+
"23": "fontFace expects a name of a font-family.\n\n",
|
|
335
|
+
"24": "fontFace expects either the path to the font file(s) or a name of a local copy.\n\n",
|
|
336
|
+
"25": "fontFace expects localFonts to be an array.\n\n",
|
|
337
|
+
"26": "fontFace expects fileFormats to be an array.\n\n",
|
|
338
|
+
"27": "radialGradient requries at least 2 color-stops to properly render.\n\n",
|
|
339
|
+
"28": "Please supply a filename to retinaImage() as the first argument.\n\n",
|
|
340
|
+
"29": "Passed invalid argument to triangle, please pass correct pointingDirection e.g. 'right'.\n\n",
|
|
341
|
+
"30": "Passed an invalid value to `height` or `width`. Please provide a pixel based unit.\n\n",
|
|
342
|
+
"31": "The animation shorthand only takes 8 arguments. See the specification for more information: http://mdn.io/animation\n\n",
|
|
343
|
+
"32": "To pass multiple animations please supply them in arrays, e.g. animation(['rotate', '2s'], ['move', '1s'])\nTo pass a single animation please supply them in simple values, e.g. animation('rotate', '2s')\n\n",
|
|
344
|
+
"33": "The animation shorthand arrays can only have 8 elements. See the specification for more information: http://mdn.io/animation\n\n",
|
|
345
|
+
"34": "borderRadius expects a radius value as a string or number as the second argument.\n\n",
|
|
346
|
+
"35": 'borderRadius expects one of "top", "bottom", "left" or "right" as the first argument.\n\n',
|
|
347
|
+
"36": "Property must be a string value.\n\n",
|
|
348
|
+
"37": "Syntax Error at %s.\n\n",
|
|
349
|
+
"38": "Formula contains a function that needs parentheses at %s.\n\n",
|
|
350
|
+
"39": "Formula is missing closing parenthesis at %s.\n\n",
|
|
351
|
+
"40": "Formula has too many closing parentheses at %s.\n\n",
|
|
352
|
+
"41": "All values in a formula must have the same unit or be unitless.\n\n",
|
|
353
|
+
"42": "Please provide a number of steps to the modularScale helper.\n\n",
|
|
354
|
+
"43": "Please pass a number or one of the predefined scales to the modularScale helper as the ratio.\n\n",
|
|
355
|
+
"44": "Invalid value passed as base to modularScale, expected number or em/rem string but got %s.\n\n",
|
|
356
|
+
"45": "Passed invalid argument to hslToColorString, please pass a HslColor or HslaColor object.\n\n",
|
|
357
|
+
"46": "Passed invalid argument to rgbToColorString, please pass a RgbColor or RgbaColor object.\n\n",
|
|
358
|
+
"47": "minScreen and maxScreen must be provided as stringified numbers with the same units.\n\n",
|
|
359
|
+
"48": "fromSize and toSize must be provided as stringified numbers with the same units.\n\n",
|
|
360
|
+
"49": "Expects either an array of objects or a single object with the properties prop, fromSize, and toSize.\n\n",
|
|
361
|
+
"50": "Expects the objects in the first argument array to have the properties prop, fromSize, and toSize.\n\n",
|
|
362
|
+
"51": "Expects the first argument object to have the properties prop, fromSize, and toSize.\n\n",
|
|
363
|
+
"52": "fontFace expects either the path to the font file(s) or a name of a local copy.\n\n",
|
|
364
|
+
"53": "fontFace expects localFonts to be an array.\n\n",
|
|
365
|
+
"54": "fontFace expects fileFormats to be an array.\n\n",
|
|
366
|
+
"55": "fontFace expects a name of a font-family.\n\n",
|
|
367
|
+
"56": "linearGradient requries at least 2 color-stops to properly render.\n\n",
|
|
368
|
+
"57": "radialGradient requries at least 2 color-stops to properly render.\n\n",
|
|
369
|
+
"58": "Please supply a filename to retinaImage() as the first argument.\n\n",
|
|
370
|
+
"59": "Passed invalid argument to triangle, please pass correct pointingDirection e.g. 'right'.\n\n",
|
|
371
|
+
"60": "Passed an invalid value to `height` or `width`. Please provide a pixel based unit.\n\n",
|
|
372
|
+
"61": "Property must be a string value.\n\n",
|
|
373
|
+
"62": "borderRadius expects a radius value as a string or number as the second argument.\n\n",
|
|
374
|
+
"63": 'borderRadius expects one of "top", "bottom", "left" or "right" as the first argument.\n\n',
|
|
375
|
+
"64": "The animation shorthand only takes 8 arguments. See the specification for more information: http://mdn.io/animation.\n\n",
|
|
376
|
+
"65": "To pass multiple animations please supply them in arrays, e.g. animation(['rotate', '2s'], ['move', '1s'])\\nTo pass a single animation please supply them in simple values, e.g. animation('rotate', '2s').\n\n",
|
|
377
|
+
"66": "The animation shorthand arrays can only have 8 elements. See the specification for more information: http://mdn.io/animation.\n\n",
|
|
378
|
+
"67": "You must provide a template to this method.\n\n",
|
|
379
|
+
"68": "You passed an unsupported selector state to this method.\n\n",
|
|
380
|
+
"69": 'Expected a string ending in "px" or a number passed as the first argument to %s(), got %s instead.\n\n',
|
|
381
|
+
"70": 'Expected a string ending in "px" or a number passed as the second argument to %s(), got %s instead.\n\n',
|
|
382
|
+
"71": 'Passed invalid pixel value %s to %s(), please pass a value like "12px" or 12.\n\n',
|
|
383
|
+
"72": 'Passed invalid base value %s to %s(), please pass a value like "12px" or 12.\n\n',
|
|
384
|
+
"73": "Please provide a valid CSS variable.\n\n",
|
|
385
|
+
"74": "CSS variable not found and no default was provided.\n\n",
|
|
386
|
+
"75": "important requires a valid style object, got a %s instead.\n\n",
|
|
387
|
+
"76": "fromSize and toSize must be provided as stringified numbers with the same units as minScreen and maxScreen.\n\n",
|
|
388
|
+
"77": 'remToPx expects a value in "rem" but you provided it in "%s".\n\n',
|
|
389
|
+
"78": 'base must be set in "px" or "%" but you set it in "%s".\n'
|
|
390
|
+
};
|
|
391
|
+
function format() {
|
|
392
|
+
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
|
|
393
|
+
args[_key] = arguments[_key];
|
|
394
|
+
}
|
|
395
|
+
var a = args[0];
|
|
396
|
+
var b = [];
|
|
397
|
+
var c;
|
|
398
|
+
for (c = 1; c < args.length; c += 1) {
|
|
399
|
+
b.push(args[c]);
|
|
400
|
+
}
|
|
401
|
+
b.forEach(function(d) {
|
|
402
|
+
a = a.replace(/%[a-z]/, d);
|
|
403
|
+
});
|
|
404
|
+
return a;
|
|
405
|
+
}
|
|
406
|
+
var PolishedError = /* @__PURE__ */ function(_Error) {
|
|
407
|
+
_inheritsLoose(PolishedError2, _Error);
|
|
408
|
+
function PolishedError2(code) {
|
|
409
|
+
var _this;
|
|
410
|
+
if (process.env.NODE_ENV === "production") {
|
|
411
|
+
_this = _Error.call(this, "An error occurred. See https://github.com/styled-components/polished/blob/main/src/internalHelpers/errors.md#" + code + " for more information.") || this;
|
|
412
|
+
} else {
|
|
413
|
+
for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
|
|
414
|
+
args[_key2 - 1] = arguments[_key2];
|
|
415
|
+
}
|
|
416
|
+
_this = _Error.call(this, format.apply(void 0, [ERRORS[code]].concat(args))) || this;
|
|
417
|
+
}
|
|
418
|
+
return _assertThisInitialized(_this);
|
|
419
|
+
}
|
|
420
|
+
return PolishedError2;
|
|
421
|
+
}(/* @__PURE__ */ _wrapNativeSuper(Error));
|
|
422
|
+
function endsWith(string, suffix) {
|
|
423
|
+
return string.substr(-suffix.length) === suffix;
|
|
424
|
+
}
|
|
425
|
+
var cssRegex$1 = /^([+-]?(?:\d+|\d*\.\d+))([a-z]*|%)$/;
|
|
426
|
+
function stripUnit(value) {
|
|
427
|
+
if (typeof value !== "string") return value;
|
|
428
|
+
var matchedValue = value.match(cssRegex$1);
|
|
429
|
+
return matchedValue ? parseFloat(value) : value;
|
|
430
|
+
}
|
|
431
|
+
var pxtoFactory = function pxtoFactory2(to) {
|
|
432
|
+
return function(pxval, base) {
|
|
433
|
+
if (base === void 0) {
|
|
434
|
+
base = "16px";
|
|
435
|
+
}
|
|
436
|
+
var newPxval = pxval;
|
|
437
|
+
var newBase = base;
|
|
438
|
+
if (typeof pxval === "string") {
|
|
439
|
+
if (!endsWith(pxval, "px")) {
|
|
440
|
+
throw new PolishedError(69, to, pxval);
|
|
441
|
+
}
|
|
442
|
+
newPxval = stripUnit(pxval);
|
|
443
|
+
}
|
|
444
|
+
if (typeof base === "string") {
|
|
445
|
+
if (!endsWith(base, "px")) {
|
|
446
|
+
throw new PolishedError(70, to, base);
|
|
447
|
+
}
|
|
448
|
+
newBase = stripUnit(base);
|
|
449
|
+
}
|
|
450
|
+
if (typeof newPxval === "string") {
|
|
451
|
+
throw new PolishedError(71, pxval, to);
|
|
452
|
+
}
|
|
453
|
+
if (typeof newBase === "string") {
|
|
454
|
+
throw new PolishedError(72, base, to);
|
|
455
|
+
}
|
|
456
|
+
return "" + newPxval / newBase + to;
|
|
457
|
+
};
|
|
458
|
+
};
|
|
459
|
+
var pixelsto = pxtoFactory;
|
|
460
|
+
var em = pixelsto("em");
|
|
461
|
+
var rem = pixelsto("rem");
|
|
462
|
+
function colorToInt(color) {
|
|
463
|
+
return Math.round(color * 255);
|
|
464
|
+
}
|
|
465
|
+
function convertToInt(red, green, blue) {
|
|
466
|
+
return colorToInt(red) + "," + colorToInt(green) + "," + colorToInt(blue);
|
|
467
|
+
}
|
|
468
|
+
function hslToRgb(hue, saturation, lightness, convert) {
|
|
469
|
+
if (convert === void 0) {
|
|
470
|
+
convert = convertToInt;
|
|
471
|
+
}
|
|
472
|
+
if (saturation === 0) {
|
|
473
|
+
return convert(lightness, lightness, lightness);
|
|
474
|
+
}
|
|
475
|
+
var huePrime = (hue % 360 + 360) % 360 / 60;
|
|
476
|
+
var chroma = (1 - Math.abs(2 * lightness - 1)) * saturation;
|
|
477
|
+
var secondComponent = chroma * (1 - Math.abs(huePrime % 2 - 1));
|
|
478
|
+
var red = 0;
|
|
479
|
+
var green = 0;
|
|
480
|
+
var blue = 0;
|
|
481
|
+
if (huePrime >= 0 && huePrime < 1) {
|
|
482
|
+
red = chroma;
|
|
483
|
+
green = secondComponent;
|
|
484
|
+
} else if (huePrime >= 1 && huePrime < 2) {
|
|
485
|
+
red = secondComponent;
|
|
486
|
+
green = chroma;
|
|
487
|
+
} else if (huePrime >= 2 && huePrime < 3) {
|
|
488
|
+
green = chroma;
|
|
489
|
+
blue = secondComponent;
|
|
490
|
+
} else if (huePrime >= 3 && huePrime < 4) {
|
|
491
|
+
green = secondComponent;
|
|
492
|
+
blue = chroma;
|
|
493
|
+
} else if (huePrime >= 4 && huePrime < 5) {
|
|
494
|
+
red = secondComponent;
|
|
495
|
+
blue = chroma;
|
|
496
|
+
} else if (huePrime >= 5 && huePrime < 6) {
|
|
497
|
+
red = chroma;
|
|
498
|
+
blue = secondComponent;
|
|
499
|
+
}
|
|
500
|
+
var lightnessModification = lightness - chroma / 2;
|
|
501
|
+
var finalRed = red + lightnessModification;
|
|
502
|
+
var finalGreen = green + lightnessModification;
|
|
503
|
+
var finalBlue = blue + lightnessModification;
|
|
504
|
+
return convert(finalRed, finalGreen, finalBlue);
|
|
505
|
+
}
|
|
506
|
+
var namedColorMap = {
|
|
507
|
+
aliceblue: "f0f8ff",
|
|
508
|
+
antiquewhite: "faebd7",
|
|
509
|
+
aqua: "00ffff",
|
|
510
|
+
aquamarine: "7fffd4",
|
|
511
|
+
azure: "f0ffff",
|
|
512
|
+
beige: "f5f5dc",
|
|
513
|
+
bisque: "ffe4c4",
|
|
514
|
+
black: "000",
|
|
515
|
+
blanchedalmond: "ffebcd",
|
|
516
|
+
blue: "0000ff",
|
|
517
|
+
blueviolet: "8a2be2",
|
|
518
|
+
brown: "a52a2a",
|
|
519
|
+
burlywood: "deb887",
|
|
520
|
+
cadetblue: "5f9ea0",
|
|
521
|
+
chartreuse: "7fff00",
|
|
522
|
+
chocolate: "d2691e",
|
|
523
|
+
coral: "ff7f50",
|
|
524
|
+
cornflowerblue: "6495ed",
|
|
525
|
+
cornsilk: "fff8dc",
|
|
526
|
+
crimson: "dc143c",
|
|
527
|
+
cyan: "00ffff",
|
|
528
|
+
darkblue: "00008b",
|
|
529
|
+
darkcyan: "008b8b",
|
|
530
|
+
darkgoldenrod: "b8860b",
|
|
531
|
+
darkgray: "a9a9a9",
|
|
532
|
+
darkgreen: "006400",
|
|
533
|
+
darkgrey: "a9a9a9",
|
|
534
|
+
darkkhaki: "bdb76b",
|
|
535
|
+
darkmagenta: "8b008b",
|
|
536
|
+
darkolivegreen: "556b2f",
|
|
537
|
+
darkorange: "ff8c00",
|
|
538
|
+
darkorchid: "9932cc",
|
|
539
|
+
darkred: "8b0000",
|
|
540
|
+
darksalmon: "e9967a",
|
|
541
|
+
darkseagreen: "8fbc8f",
|
|
542
|
+
darkslateblue: "483d8b",
|
|
543
|
+
darkslategray: "2f4f4f",
|
|
544
|
+
darkslategrey: "2f4f4f",
|
|
545
|
+
darkturquoise: "00ced1",
|
|
546
|
+
darkviolet: "9400d3",
|
|
547
|
+
deeppink: "ff1493",
|
|
548
|
+
deepskyblue: "00bfff",
|
|
549
|
+
dimgray: "696969",
|
|
550
|
+
dimgrey: "696969",
|
|
551
|
+
dodgerblue: "1e90ff",
|
|
552
|
+
firebrick: "b22222",
|
|
553
|
+
floralwhite: "fffaf0",
|
|
554
|
+
forestgreen: "228b22",
|
|
555
|
+
fuchsia: "ff00ff",
|
|
556
|
+
gainsboro: "dcdcdc",
|
|
557
|
+
ghostwhite: "f8f8ff",
|
|
558
|
+
gold: "ffd700",
|
|
559
|
+
goldenrod: "daa520",
|
|
560
|
+
gray: "808080",
|
|
561
|
+
green: "008000",
|
|
562
|
+
greenyellow: "adff2f",
|
|
563
|
+
grey: "808080",
|
|
564
|
+
honeydew: "f0fff0",
|
|
565
|
+
hotpink: "ff69b4",
|
|
566
|
+
indianred: "cd5c5c",
|
|
567
|
+
indigo: "4b0082",
|
|
568
|
+
ivory: "fffff0",
|
|
569
|
+
khaki: "f0e68c",
|
|
570
|
+
lavender: "e6e6fa",
|
|
571
|
+
lavenderblush: "fff0f5",
|
|
572
|
+
lawngreen: "7cfc00",
|
|
573
|
+
lemonchiffon: "fffacd",
|
|
574
|
+
lightblue: "add8e6",
|
|
575
|
+
lightcoral: "f08080",
|
|
576
|
+
lightcyan: "e0ffff",
|
|
577
|
+
lightgoldenrodyellow: "fafad2",
|
|
578
|
+
lightgray: "d3d3d3",
|
|
579
|
+
lightgreen: "90ee90",
|
|
580
|
+
lightgrey: "d3d3d3",
|
|
581
|
+
lightpink: "ffb6c1",
|
|
582
|
+
lightsalmon: "ffa07a",
|
|
583
|
+
lightseagreen: "20b2aa",
|
|
584
|
+
lightskyblue: "87cefa",
|
|
585
|
+
lightslategray: "789",
|
|
586
|
+
lightslategrey: "789",
|
|
587
|
+
lightsteelblue: "b0c4de",
|
|
588
|
+
lightyellow: "ffffe0",
|
|
589
|
+
lime: "0f0",
|
|
590
|
+
limegreen: "32cd32",
|
|
591
|
+
linen: "faf0e6",
|
|
592
|
+
magenta: "f0f",
|
|
593
|
+
maroon: "800000",
|
|
594
|
+
mediumaquamarine: "66cdaa",
|
|
595
|
+
mediumblue: "0000cd",
|
|
596
|
+
mediumorchid: "ba55d3",
|
|
597
|
+
mediumpurple: "9370db",
|
|
598
|
+
mediumseagreen: "3cb371",
|
|
599
|
+
mediumslateblue: "7b68ee",
|
|
600
|
+
mediumspringgreen: "00fa9a",
|
|
601
|
+
mediumturquoise: "48d1cc",
|
|
602
|
+
mediumvioletred: "c71585",
|
|
603
|
+
midnightblue: "191970",
|
|
604
|
+
mintcream: "f5fffa",
|
|
605
|
+
mistyrose: "ffe4e1",
|
|
606
|
+
moccasin: "ffe4b5",
|
|
607
|
+
navajowhite: "ffdead",
|
|
608
|
+
navy: "000080",
|
|
609
|
+
oldlace: "fdf5e6",
|
|
610
|
+
olive: "808000",
|
|
611
|
+
olivedrab: "6b8e23",
|
|
612
|
+
orange: "ffa500",
|
|
613
|
+
orangered: "ff4500",
|
|
614
|
+
orchid: "da70d6",
|
|
615
|
+
palegoldenrod: "eee8aa",
|
|
616
|
+
palegreen: "98fb98",
|
|
617
|
+
paleturquoise: "afeeee",
|
|
618
|
+
palevioletred: "db7093",
|
|
619
|
+
papayawhip: "ffefd5",
|
|
620
|
+
peachpuff: "ffdab9",
|
|
621
|
+
peru: "cd853f",
|
|
622
|
+
pink: "ffc0cb",
|
|
623
|
+
plum: "dda0dd",
|
|
624
|
+
powderblue: "b0e0e6",
|
|
625
|
+
purple: "800080",
|
|
626
|
+
rebeccapurple: "639",
|
|
627
|
+
red: "f00",
|
|
628
|
+
rosybrown: "bc8f8f",
|
|
629
|
+
royalblue: "4169e1",
|
|
630
|
+
saddlebrown: "8b4513",
|
|
631
|
+
salmon: "fa8072",
|
|
632
|
+
sandybrown: "f4a460",
|
|
633
|
+
seagreen: "2e8b57",
|
|
634
|
+
seashell: "fff5ee",
|
|
635
|
+
sienna: "a0522d",
|
|
636
|
+
silver: "c0c0c0",
|
|
637
|
+
skyblue: "87ceeb",
|
|
638
|
+
slateblue: "6a5acd",
|
|
639
|
+
slategray: "708090",
|
|
640
|
+
slategrey: "708090",
|
|
641
|
+
snow: "fffafa",
|
|
642
|
+
springgreen: "00ff7f",
|
|
643
|
+
steelblue: "4682b4",
|
|
644
|
+
tan: "d2b48c",
|
|
645
|
+
teal: "008080",
|
|
646
|
+
thistle: "d8bfd8",
|
|
647
|
+
tomato: "ff6347",
|
|
648
|
+
turquoise: "40e0d0",
|
|
649
|
+
violet: "ee82ee",
|
|
650
|
+
wheat: "f5deb3",
|
|
651
|
+
white: "fff",
|
|
652
|
+
whitesmoke: "f5f5f5",
|
|
653
|
+
yellow: "ff0",
|
|
654
|
+
yellowgreen: "9acd32"
|
|
655
|
+
};
|
|
656
|
+
function nameToHex(color) {
|
|
657
|
+
if (typeof color !== "string") return color;
|
|
658
|
+
var normalizedColorName = color.toLowerCase();
|
|
659
|
+
return namedColorMap[normalizedColorName] ? "#" + namedColorMap[normalizedColorName] : color;
|
|
660
|
+
}
|
|
661
|
+
var hexRegex = /^#[a-fA-F0-9]{6}$/;
|
|
662
|
+
var hexRgbaRegex = /^#[a-fA-F0-9]{8}$/;
|
|
663
|
+
var reducedHexRegex = /^#[a-fA-F0-9]{3}$/;
|
|
664
|
+
var reducedRgbaHexRegex = /^#[a-fA-F0-9]{4}$/;
|
|
665
|
+
var rgbRegex = /^rgb\(\s*(\d{1,3})\s*(?:,)?\s*(\d{1,3})\s*(?:,)?\s*(\d{1,3})\s*\)$/i;
|
|
666
|
+
var rgbaRegex = /^rgb(?:a)?\(\s*(\d{1,3})\s*(?:,)?\s*(\d{1,3})\s*(?:,)?\s*(\d{1,3})\s*(?:,|\/)\s*([-+]?\d*[.]?\d+[%]?)\s*\)$/i;
|
|
667
|
+
var hslRegex = /^hsl\(\s*(\d{0,3}[.]?[0-9]+(?:deg)?)\s*(?:,)?\s*(\d{1,3}[.]?[0-9]?)%\s*(?:,)?\s*(\d{1,3}[.]?[0-9]?)%\s*\)$/i;
|
|
668
|
+
var hslaRegex = /^hsl(?:a)?\(\s*(\d{0,3}[.]?[0-9]+(?:deg)?)\s*(?:,)?\s*(\d{1,3}[.]?[0-9]?)%\s*(?:,)?\s*(\d{1,3}[.]?[0-9]?)%\s*(?:,|\/)\s*([-+]?\d*[.]?\d+[%]?)\s*\)$/i;
|
|
669
|
+
function parseToRgb(color) {
|
|
670
|
+
if (typeof color !== "string") {
|
|
671
|
+
throw new PolishedError(3);
|
|
672
|
+
}
|
|
673
|
+
var normalizedColor = nameToHex(color);
|
|
674
|
+
if (normalizedColor.match(hexRegex)) {
|
|
675
|
+
return {
|
|
676
|
+
red: parseInt("" + normalizedColor[1] + normalizedColor[2], 16),
|
|
677
|
+
green: parseInt("" + normalizedColor[3] + normalizedColor[4], 16),
|
|
678
|
+
blue: parseInt("" + normalizedColor[5] + normalizedColor[6], 16)
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
if (normalizedColor.match(hexRgbaRegex)) {
|
|
682
|
+
var alpha = parseFloat((parseInt("" + normalizedColor[7] + normalizedColor[8], 16) / 255).toFixed(2));
|
|
683
|
+
return {
|
|
684
|
+
red: parseInt("" + normalizedColor[1] + normalizedColor[2], 16),
|
|
685
|
+
green: parseInt("" + normalizedColor[3] + normalizedColor[4], 16),
|
|
686
|
+
blue: parseInt("" + normalizedColor[5] + normalizedColor[6], 16),
|
|
687
|
+
alpha
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
if (normalizedColor.match(reducedHexRegex)) {
|
|
691
|
+
return {
|
|
692
|
+
red: parseInt("" + normalizedColor[1] + normalizedColor[1], 16),
|
|
693
|
+
green: parseInt("" + normalizedColor[2] + normalizedColor[2], 16),
|
|
694
|
+
blue: parseInt("" + normalizedColor[3] + normalizedColor[3], 16)
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
if (normalizedColor.match(reducedRgbaHexRegex)) {
|
|
698
|
+
var _alpha = parseFloat((parseInt("" + normalizedColor[4] + normalizedColor[4], 16) / 255).toFixed(2));
|
|
699
|
+
return {
|
|
700
|
+
red: parseInt("" + normalizedColor[1] + normalizedColor[1], 16),
|
|
701
|
+
green: parseInt("" + normalizedColor[2] + normalizedColor[2], 16),
|
|
702
|
+
blue: parseInt("" + normalizedColor[3] + normalizedColor[3], 16),
|
|
703
|
+
alpha: _alpha
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
var rgbMatched = rgbRegex.exec(normalizedColor);
|
|
707
|
+
if (rgbMatched) {
|
|
708
|
+
return {
|
|
709
|
+
red: parseInt("" + rgbMatched[1], 10),
|
|
710
|
+
green: parseInt("" + rgbMatched[2], 10),
|
|
711
|
+
blue: parseInt("" + rgbMatched[3], 10)
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
var rgbaMatched = rgbaRegex.exec(normalizedColor.substring(0, 50));
|
|
715
|
+
if (rgbaMatched) {
|
|
716
|
+
return {
|
|
717
|
+
red: parseInt("" + rgbaMatched[1], 10),
|
|
718
|
+
green: parseInt("" + rgbaMatched[2], 10),
|
|
719
|
+
blue: parseInt("" + rgbaMatched[3], 10),
|
|
720
|
+
alpha: parseFloat("" + rgbaMatched[4]) > 1 ? parseFloat("" + rgbaMatched[4]) / 100 : parseFloat("" + rgbaMatched[4])
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
var hslMatched = hslRegex.exec(normalizedColor);
|
|
724
|
+
if (hslMatched) {
|
|
725
|
+
var hue = parseInt("" + hslMatched[1], 10);
|
|
726
|
+
var saturation = parseInt("" + hslMatched[2], 10) / 100;
|
|
727
|
+
var lightness = parseInt("" + hslMatched[3], 10) / 100;
|
|
728
|
+
var rgbColorString = "rgb(" + hslToRgb(hue, saturation, lightness) + ")";
|
|
729
|
+
var hslRgbMatched = rgbRegex.exec(rgbColorString);
|
|
730
|
+
if (!hslRgbMatched) {
|
|
731
|
+
throw new PolishedError(4, normalizedColor, rgbColorString);
|
|
732
|
+
}
|
|
733
|
+
return {
|
|
734
|
+
red: parseInt("" + hslRgbMatched[1], 10),
|
|
735
|
+
green: parseInt("" + hslRgbMatched[2], 10),
|
|
736
|
+
blue: parseInt("" + hslRgbMatched[3], 10)
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
var hslaMatched = hslaRegex.exec(normalizedColor.substring(0, 50));
|
|
740
|
+
if (hslaMatched) {
|
|
741
|
+
var _hue = parseInt("" + hslaMatched[1], 10);
|
|
742
|
+
var _saturation = parseInt("" + hslaMatched[2], 10) / 100;
|
|
743
|
+
var _lightness = parseInt("" + hslaMatched[3], 10) / 100;
|
|
744
|
+
var _rgbColorString = "rgb(" + hslToRgb(_hue, _saturation, _lightness) + ")";
|
|
745
|
+
var _hslRgbMatched = rgbRegex.exec(_rgbColorString);
|
|
746
|
+
if (!_hslRgbMatched) {
|
|
747
|
+
throw new PolishedError(4, normalizedColor, _rgbColorString);
|
|
748
|
+
}
|
|
749
|
+
return {
|
|
750
|
+
red: parseInt("" + _hslRgbMatched[1], 10),
|
|
751
|
+
green: parseInt("" + _hslRgbMatched[2], 10),
|
|
752
|
+
blue: parseInt("" + _hslRgbMatched[3], 10),
|
|
753
|
+
alpha: parseFloat("" + hslaMatched[4]) > 1 ? parseFloat("" + hslaMatched[4]) / 100 : parseFloat("" + hslaMatched[4])
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
throw new PolishedError(5);
|
|
757
|
+
}
|
|
758
|
+
function rgbToHsl(color) {
|
|
759
|
+
var red = color.red / 255;
|
|
760
|
+
var green = color.green / 255;
|
|
761
|
+
var blue = color.blue / 255;
|
|
762
|
+
var max = Math.max(red, green, blue);
|
|
763
|
+
var min = Math.min(red, green, blue);
|
|
764
|
+
var lightness = (max + min) / 2;
|
|
765
|
+
if (max === min) {
|
|
766
|
+
if (color.alpha !== void 0) {
|
|
767
|
+
return {
|
|
768
|
+
hue: 0,
|
|
769
|
+
saturation: 0,
|
|
770
|
+
lightness,
|
|
771
|
+
alpha: color.alpha
|
|
772
|
+
};
|
|
773
|
+
} else {
|
|
774
|
+
return {
|
|
775
|
+
hue: 0,
|
|
776
|
+
saturation: 0,
|
|
777
|
+
lightness
|
|
778
|
+
};
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
var hue;
|
|
782
|
+
var delta = max - min;
|
|
783
|
+
var saturation = lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min);
|
|
784
|
+
switch (max) {
|
|
785
|
+
case red:
|
|
786
|
+
hue = (green - blue) / delta + (green < blue ? 6 : 0);
|
|
787
|
+
break;
|
|
788
|
+
case green:
|
|
789
|
+
hue = (blue - red) / delta + 2;
|
|
790
|
+
break;
|
|
791
|
+
default:
|
|
792
|
+
hue = (red - green) / delta + 4;
|
|
793
|
+
break;
|
|
794
|
+
}
|
|
795
|
+
hue *= 60;
|
|
796
|
+
if (color.alpha !== void 0) {
|
|
797
|
+
return {
|
|
798
|
+
hue,
|
|
799
|
+
saturation,
|
|
800
|
+
lightness,
|
|
801
|
+
alpha: color.alpha
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
return {
|
|
805
|
+
hue,
|
|
806
|
+
saturation,
|
|
807
|
+
lightness
|
|
808
|
+
};
|
|
809
|
+
}
|
|
810
|
+
function parseToHsl(color) {
|
|
811
|
+
return rgbToHsl(parseToRgb(color));
|
|
812
|
+
}
|
|
813
|
+
var reduceHexValue = function reduceHexValue2(value) {
|
|
814
|
+
if (value.length === 7 && value[1] === value[2] && value[3] === value[4] && value[5] === value[6]) {
|
|
815
|
+
return "#" + value[1] + value[3] + value[5];
|
|
816
|
+
}
|
|
817
|
+
return value;
|
|
818
|
+
};
|
|
819
|
+
var reduceHexValue$1 = reduceHexValue;
|
|
820
|
+
function numberToHex(value) {
|
|
821
|
+
var hex = value.toString(16);
|
|
822
|
+
return hex.length === 1 ? "0" + hex : hex;
|
|
823
|
+
}
|
|
824
|
+
function colorToHex(color) {
|
|
825
|
+
return numberToHex(Math.round(color * 255));
|
|
826
|
+
}
|
|
827
|
+
function convertToHex(red, green, blue) {
|
|
828
|
+
return reduceHexValue$1("#" + colorToHex(red) + colorToHex(green) + colorToHex(blue));
|
|
829
|
+
}
|
|
830
|
+
function hslToHex(hue, saturation, lightness) {
|
|
831
|
+
return hslToRgb(hue, saturation, lightness, convertToHex);
|
|
832
|
+
}
|
|
833
|
+
function hsl(value, saturation, lightness) {
|
|
834
|
+
if (typeof value === "number" && typeof saturation === "number" && typeof lightness === "number") {
|
|
835
|
+
return hslToHex(value, saturation, lightness);
|
|
836
|
+
} else if (typeof value === "object" && saturation === void 0 && lightness === void 0) {
|
|
837
|
+
return hslToHex(value.hue, value.saturation, value.lightness);
|
|
838
|
+
}
|
|
839
|
+
throw new PolishedError(1);
|
|
840
|
+
}
|
|
841
|
+
function hsla(value, saturation, lightness, alpha) {
|
|
842
|
+
if (typeof value === "number" && typeof saturation === "number" && typeof lightness === "number" && typeof alpha === "number") {
|
|
843
|
+
return alpha >= 1 ? hslToHex(value, saturation, lightness) : "rgba(" + hslToRgb(value, saturation, lightness) + "," + alpha + ")";
|
|
844
|
+
} else if (typeof value === "object" && saturation === void 0 && lightness === void 0 && alpha === void 0) {
|
|
845
|
+
return value.alpha >= 1 ? hslToHex(value.hue, value.saturation, value.lightness) : "rgba(" + hslToRgb(value.hue, value.saturation, value.lightness) + "," + value.alpha + ")";
|
|
846
|
+
}
|
|
847
|
+
throw new PolishedError(2);
|
|
848
|
+
}
|
|
849
|
+
function rgb(value, green, blue) {
|
|
850
|
+
if (typeof value === "number" && typeof green === "number" && typeof blue === "number") {
|
|
851
|
+
return reduceHexValue$1("#" + numberToHex(value) + numberToHex(green) + numberToHex(blue));
|
|
852
|
+
} else if (typeof value === "object" && green === void 0 && blue === void 0) {
|
|
853
|
+
return reduceHexValue$1("#" + numberToHex(value.red) + numberToHex(value.green) + numberToHex(value.blue));
|
|
854
|
+
}
|
|
855
|
+
throw new PolishedError(6);
|
|
856
|
+
}
|
|
857
|
+
function rgba(firstValue, secondValue, thirdValue, fourthValue) {
|
|
858
|
+
if (typeof firstValue === "string" && typeof secondValue === "number") {
|
|
859
|
+
var rgbValue = parseToRgb(firstValue);
|
|
860
|
+
return "rgba(" + rgbValue.red + "," + rgbValue.green + "," + rgbValue.blue + "," + secondValue + ")";
|
|
861
|
+
} else if (typeof firstValue === "number" && typeof secondValue === "number" && typeof thirdValue === "number" && typeof fourthValue === "number") {
|
|
862
|
+
return fourthValue >= 1 ? rgb(firstValue, secondValue, thirdValue) : "rgba(" + firstValue + "," + secondValue + "," + thirdValue + "," + fourthValue + ")";
|
|
863
|
+
} else if (typeof firstValue === "object" && secondValue === void 0 && thirdValue === void 0 && fourthValue === void 0) {
|
|
864
|
+
return firstValue.alpha >= 1 ? rgb(firstValue.red, firstValue.green, firstValue.blue) : "rgba(" + firstValue.red + "," + firstValue.green + "," + firstValue.blue + "," + firstValue.alpha + ")";
|
|
865
|
+
}
|
|
866
|
+
throw new PolishedError(7);
|
|
867
|
+
}
|
|
868
|
+
var isRgb = function isRgb2(color) {
|
|
869
|
+
return typeof color.red === "number" && typeof color.green === "number" && typeof color.blue === "number" && (typeof color.alpha !== "number" || typeof color.alpha === "undefined");
|
|
870
|
+
};
|
|
871
|
+
var isRgba = function isRgba2(color) {
|
|
872
|
+
return typeof color.red === "number" && typeof color.green === "number" && typeof color.blue === "number" && typeof color.alpha === "number";
|
|
873
|
+
};
|
|
874
|
+
var isHsl = function isHsl2(color) {
|
|
875
|
+
return typeof color.hue === "number" && typeof color.saturation === "number" && typeof color.lightness === "number" && (typeof color.alpha !== "number" || typeof color.alpha === "undefined");
|
|
876
|
+
};
|
|
877
|
+
var isHsla = function isHsla2(color) {
|
|
878
|
+
return typeof color.hue === "number" && typeof color.saturation === "number" && typeof color.lightness === "number" && typeof color.alpha === "number";
|
|
879
|
+
};
|
|
880
|
+
function toColorString(color) {
|
|
881
|
+
if (typeof color !== "object") throw new PolishedError(8);
|
|
882
|
+
if (isRgba(color)) return rgba(color);
|
|
883
|
+
if (isRgb(color)) return rgb(color);
|
|
884
|
+
if (isHsla(color)) return hsla(color);
|
|
885
|
+
if (isHsl(color)) return hsl(color);
|
|
886
|
+
throw new PolishedError(8);
|
|
887
|
+
}
|
|
888
|
+
function curried(f, length, acc) {
|
|
889
|
+
return function fn() {
|
|
890
|
+
var combined = acc.concat(Array.prototype.slice.call(arguments));
|
|
891
|
+
return combined.length >= length ? f.apply(this, combined) : curried(f, length, combined);
|
|
892
|
+
};
|
|
893
|
+
}
|
|
894
|
+
function curry(f) {
|
|
895
|
+
return curried(f, f.length, []);
|
|
896
|
+
}
|
|
897
|
+
function adjustHue(degree, color) {
|
|
898
|
+
if (color === "transparent") return color;
|
|
899
|
+
var hslColor = parseToHsl(color);
|
|
900
|
+
return toColorString(_extends({}, hslColor, {
|
|
901
|
+
hue: hslColor.hue + parseFloat(degree)
|
|
902
|
+
}));
|
|
903
|
+
}
|
|
904
|
+
var curriedAdjustHue = curry(adjustHue);
|
|
905
|
+
function guard(lowerBoundary, upperBoundary, value) {
|
|
906
|
+
return Math.max(lowerBoundary, Math.min(upperBoundary, value));
|
|
907
|
+
}
|
|
908
|
+
function darken(amount, color) {
|
|
909
|
+
if (color === "transparent") return color;
|
|
910
|
+
var hslColor = parseToHsl(color);
|
|
911
|
+
return toColorString(_extends({}, hslColor, {
|
|
912
|
+
lightness: guard(0, 1, hslColor.lightness - parseFloat(amount))
|
|
913
|
+
}));
|
|
914
|
+
}
|
|
915
|
+
var curriedDarken = curry(darken);
|
|
916
|
+
function desaturate(amount, color) {
|
|
917
|
+
if (color === "transparent") return color;
|
|
918
|
+
var hslColor = parseToHsl(color);
|
|
919
|
+
return toColorString(_extends({}, hslColor, {
|
|
920
|
+
saturation: guard(0, 1, hslColor.saturation - parseFloat(amount))
|
|
921
|
+
}));
|
|
922
|
+
}
|
|
923
|
+
var curriedDesaturate = curry(desaturate);
|
|
924
|
+
function lighten(amount, color) {
|
|
925
|
+
if (color === "transparent") return color;
|
|
926
|
+
var hslColor = parseToHsl(color);
|
|
927
|
+
return toColorString(_extends({}, hslColor, {
|
|
928
|
+
lightness: guard(0, 1, hslColor.lightness + parseFloat(amount))
|
|
929
|
+
}));
|
|
930
|
+
}
|
|
931
|
+
var curriedLighten = curry(lighten);
|
|
932
|
+
function mix(weight, color, otherColor) {
|
|
933
|
+
if (color === "transparent") return otherColor;
|
|
934
|
+
if (otherColor === "transparent") return color;
|
|
935
|
+
if (weight === 0) return otherColor;
|
|
936
|
+
var parsedColor1 = parseToRgb(color);
|
|
937
|
+
var color1 = _extends({}, parsedColor1, {
|
|
938
|
+
alpha: typeof parsedColor1.alpha === "number" ? parsedColor1.alpha : 1
|
|
939
|
+
});
|
|
940
|
+
var parsedColor2 = parseToRgb(otherColor);
|
|
941
|
+
var color2 = _extends({}, parsedColor2, {
|
|
942
|
+
alpha: typeof parsedColor2.alpha === "number" ? parsedColor2.alpha : 1
|
|
943
|
+
});
|
|
944
|
+
var alphaDelta = color1.alpha - color2.alpha;
|
|
945
|
+
var x = parseFloat(weight) * 2 - 1;
|
|
946
|
+
var y = x * alphaDelta === -1 ? x : x + alphaDelta;
|
|
947
|
+
var z = 1 + x * alphaDelta;
|
|
948
|
+
var weight1 = (y / z + 1) / 2;
|
|
949
|
+
var weight2 = 1 - weight1;
|
|
950
|
+
var mixedColor = {
|
|
951
|
+
red: Math.floor(color1.red * weight1 + color2.red * weight2),
|
|
952
|
+
green: Math.floor(color1.green * weight1 + color2.green * weight2),
|
|
953
|
+
blue: Math.floor(color1.blue * weight1 + color2.blue * weight2),
|
|
954
|
+
alpha: color1.alpha * parseFloat(weight) + color2.alpha * (1 - parseFloat(weight))
|
|
955
|
+
};
|
|
956
|
+
return rgba(mixedColor);
|
|
957
|
+
}
|
|
958
|
+
var curriedMix = curry(mix);
|
|
959
|
+
var mix$1 = curriedMix;
|
|
960
|
+
function opacify(amount, color) {
|
|
961
|
+
if (color === "transparent") return color;
|
|
962
|
+
var parsedColor = parseToRgb(color);
|
|
963
|
+
var alpha = typeof parsedColor.alpha === "number" ? parsedColor.alpha : 1;
|
|
964
|
+
var colorWithAlpha = _extends({}, parsedColor, {
|
|
965
|
+
alpha: guard(0, 1, (alpha * 100 + parseFloat(amount) * 100) / 100)
|
|
966
|
+
});
|
|
967
|
+
return rgba(colorWithAlpha);
|
|
968
|
+
}
|
|
969
|
+
var curriedOpacify = curry(opacify);
|
|
970
|
+
function saturate(amount, color) {
|
|
971
|
+
if (color === "transparent") return color;
|
|
972
|
+
var hslColor = parseToHsl(color);
|
|
973
|
+
return toColorString(_extends({}, hslColor, {
|
|
974
|
+
saturation: guard(0, 1, hslColor.saturation + parseFloat(amount))
|
|
975
|
+
}));
|
|
976
|
+
}
|
|
977
|
+
var curriedSaturate = curry(saturate);
|
|
978
|
+
function setHue(hue, color) {
|
|
979
|
+
if (color === "transparent") return color;
|
|
980
|
+
return toColorString(_extends({}, parseToHsl(color), {
|
|
981
|
+
hue: parseFloat(hue)
|
|
982
|
+
}));
|
|
983
|
+
}
|
|
984
|
+
var curriedSetHue = curry(setHue);
|
|
985
|
+
function setLightness(lightness, color) {
|
|
986
|
+
if (color === "transparent") return color;
|
|
987
|
+
return toColorString(_extends({}, parseToHsl(color), {
|
|
988
|
+
lightness: parseFloat(lightness)
|
|
989
|
+
}));
|
|
990
|
+
}
|
|
991
|
+
var curriedSetLightness = curry(setLightness);
|
|
992
|
+
function setSaturation(saturation, color) {
|
|
993
|
+
if (color === "transparent") return color;
|
|
994
|
+
return toColorString(_extends({}, parseToHsl(color), {
|
|
995
|
+
saturation: parseFloat(saturation)
|
|
996
|
+
}));
|
|
997
|
+
}
|
|
998
|
+
var curriedSetSaturation = curry(setSaturation);
|
|
999
|
+
function shade(percentage, color) {
|
|
1000
|
+
if (color === "transparent") return color;
|
|
1001
|
+
return mix$1(parseFloat(percentage), "rgb(0, 0, 0)", color);
|
|
1002
|
+
}
|
|
1003
|
+
var curriedShade = curry(shade);
|
|
1004
|
+
function tint(percentage, color) {
|
|
1005
|
+
if (color === "transparent") return color;
|
|
1006
|
+
return mix$1(parseFloat(percentage), "rgb(255, 255, 255)", color);
|
|
1007
|
+
}
|
|
1008
|
+
var curriedTint = curry(tint);
|
|
1009
|
+
function transparentize(amount, color) {
|
|
1010
|
+
if (color === "transparent") return color;
|
|
1011
|
+
var parsedColor = parseToRgb(color);
|
|
1012
|
+
var alpha = typeof parsedColor.alpha === "number" ? parsedColor.alpha : 1;
|
|
1013
|
+
var colorWithAlpha = _extends({}, parsedColor, {
|
|
1014
|
+
alpha: guard(0, 1, +(alpha * 100 - parseFloat(amount) * 100).toFixed(2) / 100)
|
|
1015
|
+
});
|
|
1016
|
+
return rgba(colorWithAlpha);
|
|
1017
|
+
}
|
|
1018
|
+
var curriedTransparentize = curry(transparentize);
|
|
1019
|
+
var curriedTransparentize$1 = curriedTransparentize;
|
|
1020
|
+
|
|
1021
|
+
// lib/solvers/colors.ts
|
|
1022
|
+
var COLORS = [
|
|
1023
|
+
"blue",
|
|
1024
|
+
"orange",
|
|
1025
|
+
"purple",
|
|
1026
|
+
"cyan",
|
|
1027
|
+
"magenta",
|
|
1028
|
+
"yellowgreen",
|
|
1029
|
+
"darkgoldenrod",
|
|
1030
|
+
"deeppink"
|
|
1031
|
+
];
|
|
1032
|
+
var getColorMap = (srj, connMap) => {
|
|
1033
|
+
const colorMap = {};
|
|
1034
|
+
for (let i = 0; i < srj.connections.length; i++) {
|
|
1035
|
+
const connection = srj.connections[i];
|
|
1036
|
+
const netName = connMap?.getNetConnectedToId(connection.name);
|
|
1037
|
+
if (netName && !colorMap[netName]) {
|
|
1038
|
+
colorMap[netName] = `hsl(${i * 300 / srj.connections.length}, 100%, 50%)`;
|
|
1039
|
+
}
|
|
1040
|
+
colorMap[connection.name] = (netName ? colorMap[netName] : null) ?? `hsl(${i * 340 / srj.connections.length}, 100%, 50%)`;
|
|
1041
|
+
}
|
|
1042
|
+
return colorMap;
|
|
1043
|
+
};
|
|
1044
|
+
var safeTransparentize = (color, amount) => {
|
|
1045
|
+
try {
|
|
1046
|
+
return curriedTransparentize$1(amount, color);
|
|
1047
|
+
} catch (e) {
|
|
1048
|
+
console.error(e);
|
|
1049
|
+
return color;
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
|
|
1053
|
+
// lib/utils/isPointInRect.ts
|
|
1054
|
+
function isPointInRect(point, rect) {
|
|
1055
|
+
return point.x >= rect.center.x - rect.width / 2 && point.x <= rect.center.x + rect.width / 2 && point.y >= rect.center.y - rect.height / 2 && point.y <= rect.center.y + rect.height / 2;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// lib/utils/doRectsOverlap.ts
|
|
1059
|
+
function doRectsOverlap(rect1, rect2) {
|
|
1060
|
+
const rect1Left = rect1.center.x - rect1.width / 2;
|
|
1061
|
+
const rect1Right = rect1.center.x + rect1.width / 2;
|
|
1062
|
+
const rect1Top = rect1.center.y - rect1.height / 2;
|
|
1063
|
+
const rect1Bottom = rect1.center.y + rect1.height / 2;
|
|
1064
|
+
const rect2Left = rect2.center.x - rect2.width / 2;
|
|
1065
|
+
const rect2Right = rect2.center.x + rect2.width / 2;
|
|
1066
|
+
const rect2Top = rect2.center.y - rect2.height / 2;
|
|
1067
|
+
const rect2Bottom = rect2.center.y + rect2.height / 2;
|
|
1068
|
+
return rect1Left <= rect2Right && rect1Right >= rect2Left && rect1Top <= rect2Bottom && rect1Bottom >= rect2Top;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// lib/solvers/CapacityMeshSolver/CapacityMeshNodeSolver.ts
|
|
1072
|
+
var CapacityMeshNodeSolver = class extends BaseSolver {
|
|
1073
|
+
constructor(srj, opts = {}) {
|
|
1074
|
+
super();
|
|
1075
|
+
this.srj = srj;
|
|
1076
|
+
this.opts = opts;
|
|
1077
|
+
this.MAX_DEPTH = opts?.capacityDepth ?? this.MAX_DEPTH;
|
|
1078
|
+
this.MAX_ITERATIONS = 1e5;
|
|
1079
|
+
const boundsCenter = {
|
|
1080
|
+
x: (srj.bounds.minX + srj.bounds.maxX) / 2,
|
|
1081
|
+
y: (srj.bounds.minY + srj.bounds.maxY) / 2
|
|
1082
|
+
};
|
|
1083
|
+
const boundsSize = {
|
|
1084
|
+
width: srj.bounds.maxX - srj.bounds.minX,
|
|
1085
|
+
height: srj.bounds.maxY - srj.bounds.minY
|
|
1086
|
+
};
|
|
1087
|
+
const maxWidthHeight = Math.max(boundsSize.width, boundsSize.height);
|
|
1088
|
+
this.unfinishedNodes = [
|
|
1089
|
+
{
|
|
1090
|
+
capacityMeshNodeId: this.getNextNodeId(),
|
|
1091
|
+
center: boundsCenter,
|
|
1092
|
+
width: maxWidthHeight,
|
|
1093
|
+
height: maxWidthHeight,
|
|
1094
|
+
layer: "top",
|
|
1095
|
+
_depth: 0,
|
|
1096
|
+
_containsTarget: true,
|
|
1097
|
+
_containsObstacle: true,
|
|
1098
|
+
_completelyInsideObstacle: false
|
|
1099
|
+
}
|
|
1100
|
+
];
|
|
1101
|
+
this.finishedNodes = [];
|
|
1102
|
+
this.nodeToOverlappingObstaclesMap = /* @__PURE__ */ new Map();
|
|
1103
|
+
this.targets = this.srj.connections.flatMap(
|
|
1104
|
+
(c) => c.pointsToConnect.map((p) => ({ ...p, connectionName: c.name }))
|
|
1105
|
+
);
|
|
1106
|
+
}
|
|
1107
|
+
unfinishedNodes;
|
|
1108
|
+
finishedNodes;
|
|
1109
|
+
nodeToOverlappingObstaclesMap;
|
|
1110
|
+
// targetObstacleMap: Record<string, { obstacle: Obstacle, node: CapacityMeshNode }>
|
|
1111
|
+
MAX_DEPTH = 4;
|
|
1112
|
+
targets;
|
|
1113
|
+
_nextNodeCounter = 0;
|
|
1114
|
+
getNextNodeId() {
|
|
1115
|
+
return `cn${this._nextNodeCounter++}`;
|
|
1116
|
+
}
|
|
1117
|
+
getCapacityFromDepth(depth) {
|
|
1118
|
+
return (this.MAX_DEPTH - depth + 1) ** 2;
|
|
1119
|
+
}
|
|
1120
|
+
getTargetNameIfNodeContainsTarget(node) {
|
|
1121
|
+
const overlappingObstacles = this.getOverlappingObstacles(node);
|
|
1122
|
+
for (const target of this.targets) {
|
|
1123
|
+
const targetObstacle = overlappingObstacles.find(
|
|
1124
|
+
(o) => isPointInRect(target, o)
|
|
1125
|
+
);
|
|
1126
|
+
if (targetObstacle) {
|
|
1127
|
+
if (doRectsOverlap(node, targetObstacle)) {
|
|
1128
|
+
return target.connectionName;
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
if (target.x >= node.center.x - node.width / 2 && target.x <= node.center.x + node.width / 2 && target.y >= node.center.y - node.height / 2 && target.y <= node.center.y + node.height / 2) {
|
|
1132
|
+
return target.connectionName;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
return null;
|
|
1136
|
+
}
|
|
1137
|
+
getOverlappingObstacles(node) {
|
|
1138
|
+
const cachedObstacles = this.nodeToOverlappingObstaclesMap.get(
|
|
1139
|
+
node.capacityMeshNodeId
|
|
1140
|
+
);
|
|
1141
|
+
if (cachedObstacles) {
|
|
1142
|
+
return cachedObstacles;
|
|
1143
|
+
}
|
|
1144
|
+
const overlappingObstacles = [];
|
|
1145
|
+
const nodeLeft = node.center.x - node.width / 2;
|
|
1146
|
+
const nodeRight = node.center.x + node.width / 2;
|
|
1147
|
+
const nodeTop = node.center.y - node.height / 2;
|
|
1148
|
+
const nodeBottom = node.center.y + node.height / 2;
|
|
1149
|
+
const obstacles = node._parent ? this.getOverlappingObstacles(node._parent) : this.srj.obstacles;
|
|
1150
|
+
for (const obstacle of obstacles) {
|
|
1151
|
+
const obsLeft = obstacle.center.x - obstacle.width / 2;
|
|
1152
|
+
const obsRight = obstacle.center.x + obstacle.width / 2;
|
|
1153
|
+
const obsTop = obstacle.center.y - obstacle.height / 2;
|
|
1154
|
+
const obsBottom = obstacle.center.y + obstacle.height / 2;
|
|
1155
|
+
if (nodeRight >= obsLeft && nodeLeft <= obsRight && nodeBottom >= obsTop && nodeTop <= obsBottom) {
|
|
1156
|
+
overlappingObstacles.push(obstacle);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
this.nodeToOverlappingObstaclesMap.set(
|
|
1160
|
+
node.capacityMeshNodeId,
|
|
1161
|
+
overlappingObstacles
|
|
1162
|
+
);
|
|
1163
|
+
return overlappingObstacles;
|
|
1164
|
+
}
|
|
1165
|
+
/**
|
|
1166
|
+
* Checks if the given mesh node overlaps with any obstacle.
|
|
1167
|
+
* We treat both obstacles and nodes as axis‐aligned rectangles.
|
|
1168
|
+
*/
|
|
1169
|
+
doesNodeOverlapObstacle(node) {
|
|
1170
|
+
const overlappingObstacles = this.getOverlappingObstacles(node);
|
|
1171
|
+
if (overlappingObstacles.length > 0) {
|
|
1172
|
+
return true;
|
|
1173
|
+
}
|
|
1174
|
+
const nodeLeft = node.center.x - node.width / 2;
|
|
1175
|
+
const nodeRight = node.center.x + node.width / 2;
|
|
1176
|
+
const nodeTop = node.center.y - node.height / 2;
|
|
1177
|
+
const nodeBottom = node.center.y + node.height / 2;
|
|
1178
|
+
if (nodeLeft < this.srj.bounds.minX || nodeRight > this.srj.bounds.maxX || nodeTop < this.srj.bounds.minY || nodeBottom > this.srj.bounds.maxY) {
|
|
1179
|
+
return true;
|
|
1180
|
+
}
|
|
1181
|
+
return false;
|
|
1182
|
+
}
|
|
1183
|
+
/**
|
|
1184
|
+
* Checks if the entire node is contained within any obstacle.
|
|
1185
|
+
*/
|
|
1186
|
+
isNodeCompletelyInsideObstacle(node) {
|
|
1187
|
+
const overlappingObstacles = this.getOverlappingObstacles(node);
|
|
1188
|
+
const nodeLeft = node.center.x - node.width / 2;
|
|
1189
|
+
const nodeRight = node.center.x + node.width / 2;
|
|
1190
|
+
const nodeTop = node.center.y - node.height / 2;
|
|
1191
|
+
const nodeBottom = node.center.y + node.height / 2;
|
|
1192
|
+
for (const obstacle of overlappingObstacles) {
|
|
1193
|
+
const obsLeft = obstacle.center.x - obstacle.width / 2;
|
|
1194
|
+
const obsRight = obstacle.center.x + obstacle.width / 2;
|
|
1195
|
+
const obsTop = obstacle.center.y - obstacle.height / 2;
|
|
1196
|
+
const obsBottom = obstacle.center.y + obstacle.height / 2;
|
|
1197
|
+
if (nodeLeft >= obsLeft && nodeRight <= obsRight && nodeTop >= obsTop && nodeBottom <= obsBottom) {
|
|
1198
|
+
return true;
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
if (nodeRight < this.srj.bounds.minX || nodeLeft > this.srj.bounds.maxX || nodeBottom < this.srj.bounds.minY || nodeTop > this.srj.bounds.maxY) {
|
|
1202
|
+
return true;
|
|
1203
|
+
}
|
|
1204
|
+
return false;
|
|
1205
|
+
}
|
|
1206
|
+
getChildNodes(parent) {
|
|
1207
|
+
if (parent._depth === this.MAX_DEPTH) return [];
|
|
1208
|
+
const childNodes = [];
|
|
1209
|
+
const childNodeSize = { width: parent.width / 2, height: parent.height / 2 };
|
|
1210
|
+
const childNodePositions = [
|
|
1211
|
+
{
|
|
1212
|
+
x: parent.center.x - childNodeSize.width / 2,
|
|
1213
|
+
y: parent.center.y - childNodeSize.height / 2
|
|
1214
|
+
},
|
|
1215
|
+
{
|
|
1216
|
+
x: parent.center.x + childNodeSize.width / 2,
|
|
1217
|
+
y: parent.center.y - childNodeSize.height / 2
|
|
1218
|
+
},
|
|
1219
|
+
{
|
|
1220
|
+
x: parent.center.x - childNodeSize.width / 2,
|
|
1221
|
+
y: parent.center.y + childNodeSize.height / 2
|
|
1222
|
+
},
|
|
1223
|
+
{
|
|
1224
|
+
x: parent.center.x + childNodeSize.width / 2,
|
|
1225
|
+
y: parent.center.y + childNodeSize.height / 2
|
|
1226
|
+
}
|
|
1227
|
+
];
|
|
1228
|
+
for (const position of childNodePositions) {
|
|
1229
|
+
const childNode = {
|
|
1230
|
+
capacityMeshNodeId: this.getNextNodeId(),
|
|
1231
|
+
center: position,
|
|
1232
|
+
width: childNodeSize.width,
|
|
1233
|
+
height: childNodeSize.height,
|
|
1234
|
+
layer: parent.layer,
|
|
1235
|
+
_depth: (parent._depth ?? 0) + 1,
|
|
1236
|
+
_parent: parent
|
|
1237
|
+
};
|
|
1238
|
+
childNode._containsObstacle = this.doesNodeOverlapObstacle(childNode);
|
|
1239
|
+
childNode._targetConnectionName = this.getTargetNameIfNodeContainsTarget(childNode) ?? void 0;
|
|
1240
|
+
childNode._containsTarget = Boolean(childNode._targetConnectionName);
|
|
1241
|
+
if (childNode._containsObstacle) {
|
|
1242
|
+
childNode._completelyInsideObstacle = this.isNodeCompletelyInsideObstacle(childNode);
|
|
1243
|
+
}
|
|
1244
|
+
if (childNode._completelyInsideObstacle && !childNode._containsTarget)
|
|
1245
|
+
continue;
|
|
1246
|
+
childNodes.push(childNode);
|
|
1247
|
+
}
|
|
1248
|
+
return childNodes;
|
|
1249
|
+
}
|
|
1250
|
+
shouldNodeBeSubdivided(node) {
|
|
1251
|
+
if (node._depth >= this.MAX_DEPTH) return false;
|
|
1252
|
+
if (node._containsTarget) return true;
|
|
1253
|
+
if (node._containsObstacle && !node._completelyInsideObstacle) return true;
|
|
1254
|
+
return false;
|
|
1255
|
+
}
|
|
1256
|
+
_step() {
|
|
1257
|
+
const nextNode = this.unfinishedNodes.pop();
|
|
1258
|
+
if (!nextNode) {
|
|
1259
|
+
this.solved = true;
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
const newNodes = this.getChildNodes(nextNode);
|
|
1263
|
+
const finishedNewNodes = [];
|
|
1264
|
+
const unfinishedNewNodes = [];
|
|
1265
|
+
for (const newNode of newNodes) {
|
|
1266
|
+
const shouldBeSubdivided = this.shouldNodeBeSubdivided(newNode);
|
|
1267
|
+
if (shouldBeSubdivided) {
|
|
1268
|
+
unfinishedNewNodes.push(newNode);
|
|
1269
|
+
} else if (!shouldBeSubdivided && !newNode._containsObstacle) {
|
|
1270
|
+
finishedNewNodes.push(newNode);
|
|
1271
|
+
} else if (!shouldBeSubdivided && newNode._containsTarget) {
|
|
1272
|
+
finishedNewNodes.push(newNode);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
this.unfinishedNodes.push(...unfinishedNewNodes);
|
|
1276
|
+
this.finishedNodes.push(...finishedNewNodes);
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Creates a GraphicsObject to visualize the mesh, its nodes, obstacles, and connection points.
|
|
1280
|
+
*
|
|
1281
|
+
* - Mesh nodes are rendered as rectangles.
|
|
1282
|
+
* - Nodes that have an obstacle intersection are outlined in red.
|
|
1283
|
+
* - Other nodes are outlined in green.
|
|
1284
|
+
* - Lines are drawn from a node to its parent.
|
|
1285
|
+
* - Obstacles are drawn as semi-transparent red rectangles.
|
|
1286
|
+
* - Points for each connection’s pointsToConnect are drawn in a unique color.
|
|
1287
|
+
*/
|
|
1288
|
+
visualize() {
|
|
1289
|
+
const graphics = {
|
|
1290
|
+
lines: [],
|
|
1291
|
+
points: [],
|
|
1292
|
+
rects: [],
|
|
1293
|
+
circles: [],
|
|
1294
|
+
coordinateSystem: "cartesian",
|
|
1295
|
+
title: "Capacity Mesh Visualization"
|
|
1296
|
+
};
|
|
1297
|
+
const allNodes = [...this.finishedNodes, ...this.unfinishedNodes];
|
|
1298
|
+
for (const node of allNodes) {
|
|
1299
|
+
graphics.rects.push({
|
|
1300
|
+
center: node.center,
|
|
1301
|
+
width: Math.max(node.width - 2, node.width * 0.8),
|
|
1302
|
+
height: Math.max(node.height - 2, node.height * 0.8),
|
|
1303
|
+
fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : "rgba(0,0,0,0.1)",
|
|
1304
|
+
label: node.capacityMeshNodeId
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
for (const obstacle of this.srj.obstacles) {
|
|
1308
|
+
graphics.rects.push({
|
|
1309
|
+
center: obstacle.center,
|
|
1310
|
+
width: obstacle.width,
|
|
1311
|
+
height: obstacle.height,
|
|
1312
|
+
fill: "rgba(255,0,0,0.3)",
|
|
1313
|
+
stroke: "red",
|
|
1314
|
+
label: "obstacle"
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
this.srj.connections.forEach((connection, index) => {
|
|
1318
|
+
const color = COLORS[index % COLORS.length];
|
|
1319
|
+
for (const pt of connection.pointsToConnect) {
|
|
1320
|
+
graphics.points.push({
|
|
1321
|
+
x: pt.x,
|
|
1322
|
+
y: pt.y,
|
|
1323
|
+
label: `conn-${index}`,
|
|
1324
|
+
color
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
});
|
|
1328
|
+
return graphics;
|
|
1329
|
+
}
|
|
1330
|
+
};
|
|
1331
|
+
|
|
1332
|
+
// lib/solvers/CapacityMeshSolver/getNodeEdgeMap.ts
|
|
1333
|
+
function getNodeEdgeMap(edges) {
|
|
1334
|
+
const nodeEdgeMap = /* @__PURE__ */ new Map();
|
|
1335
|
+
for (const edge of edges) {
|
|
1336
|
+
for (const nodeId of edge.nodeIds) {
|
|
1337
|
+
nodeEdgeMap.set(nodeId, [...nodeEdgeMap.get(nodeId) ?? [], edge]);
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
return nodeEdgeMap;
|
|
1341
|
+
}
|
|
1342
|
+
|
|
1343
|
+
// lib/solvers/CapacityMeshSolver/CapacityEdgeToPortSegmentSolver.ts
|
|
1344
|
+
var CapacityEdgeToPortSegmentSolver = class extends BaseSolver {
|
|
1345
|
+
nodes;
|
|
1346
|
+
edges;
|
|
1347
|
+
capacityPaths;
|
|
1348
|
+
nodeMap;
|
|
1349
|
+
nodeEdgeMap;
|
|
1350
|
+
unprocessedNodeIds;
|
|
1351
|
+
nodePortSegments;
|
|
1352
|
+
colorMap;
|
|
1353
|
+
constructor({
|
|
1354
|
+
nodes,
|
|
1355
|
+
edges,
|
|
1356
|
+
capacityPaths,
|
|
1357
|
+
colorMap
|
|
1358
|
+
}) {
|
|
1359
|
+
super();
|
|
1360
|
+
this.nodes = nodes;
|
|
1361
|
+
this.edges = edges;
|
|
1362
|
+
this.nodeMap = new Map(nodes.map((node) => [node.capacityMeshNodeId, node]));
|
|
1363
|
+
this.nodeEdgeMap = getNodeEdgeMap(edges);
|
|
1364
|
+
this.capacityPaths = capacityPaths;
|
|
1365
|
+
this.colorMap = colorMap ?? {};
|
|
1366
|
+
this.unprocessedNodeIds = [
|
|
1367
|
+
...new Set(capacityPaths.flatMap((path) => path.nodeIds))
|
|
1368
|
+
];
|
|
1369
|
+
this.nodePortSegments = /* @__PURE__ */ new Map();
|
|
1370
|
+
}
|
|
1371
|
+
step() {
|
|
1372
|
+
const nodeId = this.unprocessedNodeIds.pop();
|
|
1373
|
+
if (!nodeId) {
|
|
1374
|
+
this.solved = true;
|
|
1375
|
+
return;
|
|
1376
|
+
}
|
|
1377
|
+
const pathsGoingThroughNode = [];
|
|
1378
|
+
for (const path of this.capacityPaths) {
|
|
1379
|
+
const indexOfNodeInPath = path.nodeIds.indexOf(nodeId);
|
|
1380
|
+
if (indexOfNodeInPath !== -1) {
|
|
1381
|
+
pathsGoingThroughNode.push({ path, indexOfNodeInPath });
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
const node = this.nodeMap.get(nodeId);
|
|
1385
|
+
const nodePortSegments = [];
|
|
1386
|
+
for (const { path, indexOfNodeInPath } of pathsGoingThroughNode) {
|
|
1387
|
+
const entryNodeId = path.nodeIds[indexOfNodeInPath - 1];
|
|
1388
|
+
const exitNodeId = path.nodeIds[indexOfNodeInPath + 1];
|
|
1389
|
+
for (const adjNodeId of [entryNodeId, exitNodeId]) {
|
|
1390
|
+
const adjNode = this.nodeMap.get(adjNodeId);
|
|
1391
|
+
if (!adjNode) continue;
|
|
1392
|
+
const segment = findOverlappingSegment(node, adjNode);
|
|
1393
|
+
const portSegment = {
|
|
1394
|
+
capacityMeshNodeId: nodeId,
|
|
1395
|
+
start: segment.start,
|
|
1396
|
+
end: segment.end,
|
|
1397
|
+
connectionNames: [path.connectionName]
|
|
1398
|
+
};
|
|
1399
|
+
nodePortSegments.push(portSegment);
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
const combinedSegments = combineSegments(nodePortSegments);
|
|
1403
|
+
this.nodePortSegments.set(nodeId, combinedSegments);
|
|
1404
|
+
}
|
|
1405
|
+
visualize() {
|
|
1406
|
+
const graphics = {
|
|
1407
|
+
lines: [],
|
|
1408
|
+
points: [],
|
|
1409
|
+
rects: [],
|
|
1410
|
+
circles: []
|
|
1411
|
+
};
|
|
1412
|
+
this.nodePortSegments.forEach((segments, nodeId) => {
|
|
1413
|
+
const node = this.nodeMap.get(nodeId);
|
|
1414
|
+
segments.forEach((segment) => {
|
|
1415
|
+
const isVertical = segment.start.x === segment.end.x;
|
|
1416
|
+
const THICKNESS = 0.5 / segment.connectionNames.length;
|
|
1417
|
+
for (let i = 0; i < segment.connectionNames.length; i++) {
|
|
1418
|
+
const offsetAmount = (i / (segment.connectionNames.length - 1 + 1e-6) - 0.5) * THICKNESS;
|
|
1419
|
+
const offset = {
|
|
1420
|
+
x: isVertical ? offsetAmount : 0,
|
|
1421
|
+
y: isVertical ? 0 : offsetAmount
|
|
1422
|
+
};
|
|
1423
|
+
const trueSegmentCenter = {
|
|
1424
|
+
x: (segment.start.x + segment.end.x) / 2 + offset.x,
|
|
1425
|
+
y: (segment.start.y + segment.end.y) / 2 + offset.y
|
|
1426
|
+
};
|
|
1427
|
+
graphics.rects.push({
|
|
1428
|
+
center: {
|
|
1429
|
+
x: (trueSegmentCenter.x * 6 + node.center.x) / 7,
|
|
1430
|
+
y: (trueSegmentCenter.y * 6 + node.center.y) / 7
|
|
1431
|
+
},
|
|
1432
|
+
width: isVertical ? THICKNESS : Math.abs(segment.end.x - segment.start.x),
|
|
1433
|
+
height: isVertical ? Math.abs(segment.end.y - segment.start.y) : THICKNESS,
|
|
1434
|
+
fill: safeTransparentize(
|
|
1435
|
+
this.colorMap[segment.connectionNames[i]],
|
|
1436
|
+
0.6
|
|
1437
|
+
),
|
|
1438
|
+
label: `${nodeId}: ${segment.connectionNames.join(", ")}`
|
|
1439
|
+
});
|
|
1440
|
+
}
|
|
1441
|
+
});
|
|
1442
|
+
});
|
|
1443
|
+
return graphics;
|
|
1444
|
+
}
|
|
1445
|
+
};
|
|
1446
|
+
function findOverlappingSegment(node, adjNode) {
|
|
1447
|
+
const xOverlap = {
|
|
1448
|
+
start: Math.max(
|
|
1449
|
+
node.center.x - node.width / 2,
|
|
1450
|
+
adjNode.center.x - adjNode.width / 2
|
|
1451
|
+
),
|
|
1452
|
+
end: Math.min(
|
|
1453
|
+
node.center.x + node.width / 2,
|
|
1454
|
+
adjNode.center.x + adjNode.width / 2
|
|
1455
|
+
)
|
|
1456
|
+
};
|
|
1457
|
+
const yOverlap = {
|
|
1458
|
+
start: Math.max(
|
|
1459
|
+
node.center.y - node.height / 2,
|
|
1460
|
+
adjNode.center.y - adjNode.height / 2
|
|
1461
|
+
),
|
|
1462
|
+
end: Math.min(
|
|
1463
|
+
node.center.y + node.height / 2,
|
|
1464
|
+
adjNode.center.y + adjNode.height / 2
|
|
1465
|
+
)
|
|
1466
|
+
};
|
|
1467
|
+
const xRange = xOverlap.end - xOverlap.start;
|
|
1468
|
+
const yRange = yOverlap.end - yOverlap.start;
|
|
1469
|
+
if (xRange < yRange) {
|
|
1470
|
+
const x = (xOverlap.start + xOverlap.end) / 2;
|
|
1471
|
+
return {
|
|
1472
|
+
start: { x, y: yOverlap.start },
|
|
1473
|
+
end: { x, y: yOverlap.end }
|
|
1474
|
+
};
|
|
1475
|
+
} else {
|
|
1476
|
+
const y = (yOverlap.start + yOverlap.end) / 2;
|
|
1477
|
+
return {
|
|
1478
|
+
start: { x: xOverlap.start, y },
|
|
1479
|
+
end: { x: xOverlap.end, y }
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
function combineSegments(segments) {
|
|
1484
|
+
const mergedSegments = [];
|
|
1485
|
+
const remainingSegments = [...segments];
|
|
1486
|
+
while (remainingSegments.length > 0) {
|
|
1487
|
+
const segmentUnderTest = remainingSegments.pop();
|
|
1488
|
+
const overlappingMergedSegment = mergedSegments.find((segment) => {
|
|
1489
|
+
return segment.start.x === segmentUnderTest.start.x && segment.start.y === segmentUnderTest.start.y && segment.end.x === segmentUnderTest.end.x && segment.end.y === segmentUnderTest.end.y;
|
|
1490
|
+
});
|
|
1491
|
+
if (overlappingMergedSegment) {
|
|
1492
|
+
overlappingMergedSegment.connectionNames.push(
|
|
1493
|
+
...segmentUnderTest.connectionNames
|
|
1494
|
+
);
|
|
1495
|
+
} else {
|
|
1496
|
+
mergedSegments.push(segmentUnderTest);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
return mergedSegments;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
// lib/solvers/CapacityMeshSolver/CapacitySegmentToPointSolver.ts
|
|
1503
|
+
var CapacitySegmentToPointSolver = class extends BaseSolver {
|
|
1504
|
+
unsolvedSegments;
|
|
1505
|
+
solvedSegments;
|
|
1506
|
+
nodeMap;
|
|
1507
|
+
colorMap;
|
|
1508
|
+
// We use an extra property on segments to remember assigned points.
|
|
1509
|
+
// Each segment will get an added property "assignedPoints" which is an array of:
|
|
1510
|
+
// { connectionName: string, point: {x: number, y: number } }
|
|
1511
|
+
// This is a temporary extension used by the solver.
|
|
1512
|
+
constructor({
|
|
1513
|
+
segments,
|
|
1514
|
+
colorMap,
|
|
1515
|
+
nodes
|
|
1516
|
+
}) {
|
|
1517
|
+
super();
|
|
1518
|
+
this.MAX_ITERATIONS = 1e5;
|
|
1519
|
+
this.unsolvedSegments = segments;
|
|
1520
|
+
this.solvedSegments = [];
|
|
1521
|
+
this.colorMap = colorMap ?? {};
|
|
1522
|
+
this.nodeMap = Object.fromEntries(
|
|
1523
|
+
nodes.map((node) => [node.capacityMeshNodeId, node])
|
|
1524
|
+
);
|
|
1525
|
+
}
|
|
1526
|
+
/**
|
|
1527
|
+
* Perform one iteration step.
|
|
1528
|
+
*/
|
|
1529
|
+
_step() {
|
|
1530
|
+
let updated = false;
|
|
1531
|
+
const unsolved = [...this.unsolvedSegments];
|
|
1532
|
+
for (const seg of unsolved) {
|
|
1533
|
+
const n = seg.connectionNames.length;
|
|
1534
|
+
if ("assignedPoints" in seg && seg.assignedPoints?.length === n) continue;
|
|
1535
|
+
if (n === 1) {
|
|
1536
|
+
const center = {
|
|
1537
|
+
x: (seg.start.x + seg.end.x) / 2,
|
|
1538
|
+
y: (seg.start.y + seg.end.y) / 2,
|
|
1539
|
+
z: 0
|
|
1540
|
+
};
|
|
1541
|
+
seg.assignedPoints = [
|
|
1542
|
+
{ connectionName: seg.connectionNames[0], point: center }
|
|
1543
|
+
];
|
|
1544
|
+
this.unsolvedSegments.splice(this.unsolvedSegments.indexOf(seg), 1);
|
|
1545
|
+
this.solvedSegments.push(seg);
|
|
1546
|
+
updated = true;
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
if (!updated && unsolved.length > 0) {
|
|
1550
|
+
let candidate = unsolved[0];
|
|
1551
|
+
for (const seg of unsolved) {
|
|
1552
|
+
if (seg.connectionNames.length < candidate.connectionNames.length) {
|
|
1553
|
+
candidate = seg;
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
const sortedConnections = [...candidate.connectionNames].sort();
|
|
1557
|
+
const dx = candidate.end.x - candidate.start.x;
|
|
1558
|
+
const dy = candidate.end.y - candidate.start.y;
|
|
1559
|
+
const n = sortedConnections.length;
|
|
1560
|
+
const points = [];
|
|
1561
|
+
for (let i = 1; i <= n; i++) {
|
|
1562
|
+
const fraction = i / (n + 1);
|
|
1563
|
+
points.push({
|
|
1564
|
+
x: candidate.start.x + dx * fraction,
|
|
1565
|
+
y: candidate.start.y + dy * fraction,
|
|
1566
|
+
z: 0
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
;
|
|
1570
|
+
candidate.assignedPoints = sortedConnections.map(
|
|
1571
|
+
(conn, idx) => ({
|
|
1572
|
+
connectionName: conn,
|
|
1573
|
+
point: points[idx]
|
|
1574
|
+
})
|
|
1575
|
+
);
|
|
1576
|
+
this.unsolvedSegments.splice(this.unsolvedSegments.indexOf(candidate), 1);
|
|
1577
|
+
this.solvedSegments.push(candidate);
|
|
1578
|
+
updated = true;
|
|
1579
|
+
}
|
|
1580
|
+
if (this.unsolvedSegments.length === 0) {
|
|
1581
|
+
this.solved = true;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
/**
|
|
1585
|
+
* Return the assigned points for each segment.
|
|
1586
|
+
*/
|
|
1587
|
+
getNodesWithPortPoints() {
|
|
1588
|
+
if (!this.solved) {
|
|
1589
|
+
throw new Error(
|
|
1590
|
+
"CapacitySegmentToPointSolver not solved, can't give port points yet"
|
|
1591
|
+
);
|
|
1592
|
+
}
|
|
1593
|
+
const map = /* @__PURE__ */ new Map();
|
|
1594
|
+
for (const seg of this.solvedSegments) {
|
|
1595
|
+
const nodeId = seg.capacityMeshNodeId;
|
|
1596
|
+
const node = this.nodeMap[nodeId];
|
|
1597
|
+
if (!map.has(nodeId)) {
|
|
1598
|
+
map.set(nodeId, {
|
|
1599
|
+
capacityMeshNodeId: nodeId,
|
|
1600
|
+
portPoints: [],
|
|
1601
|
+
center: node.center,
|
|
1602
|
+
width: node.width,
|
|
1603
|
+
height: node.height
|
|
1604
|
+
});
|
|
1605
|
+
}
|
|
1606
|
+
map.get(nodeId).portPoints.push(
|
|
1607
|
+
...seg.assignedPoints.map((ap) => ({
|
|
1608
|
+
...ap.point,
|
|
1609
|
+
connectionName: ap.connectionName
|
|
1610
|
+
}))
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
return Array.from(map.values());
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* Return a GraphicsObject that visualizes the segments with assigned points.
|
|
1617
|
+
*/
|
|
1618
|
+
visualize() {
|
|
1619
|
+
const graphics = {
|
|
1620
|
+
points: this.solvedSegments.flatMap(
|
|
1621
|
+
(seg) => seg.assignedPoints.map((ap) => ({
|
|
1622
|
+
x: ap.point.x,
|
|
1623
|
+
y: ap.point.y,
|
|
1624
|
+
label: `${seg.capacityMeshNodeId}-${ap.connectionName}`,
|
|
1625
|
+
color: this.colorMap[ap.connectionName],
|
|
1626
|
+
step: 4
|
|
1627
|
+
}))
|
|
1628
|
+
),
|
|
1629
|
+
lines: this.solvedSegments.map((seg) => ({
|
|
1630
|
+
points: [seg.start, seg.end],
|
|
1631
|
+
step: 4
|
|
1632
|
+
})),
|
|
1633
|
+
rects: [],
|
|
1634
|
+
circles: []
|
|
1635
|
+
};
|
|
1636
|
+
const dashedLines = [];
|
|
1637
|
+
const nodeConnections = {};
|
|
1638
|
+
for (const seg of this.solvedSegments) {
|
|
1639
|
+
const nodeId = seg.capacityMeshNodeId;
|
|
1640
|
+
if (!nodeConnections[nodeId]) {
|
|
1641
|
+
nodeConnections[nodeId] = {};
|
|
1642
|
+
}
|
|
1643
|
+
for (const ap of seg.assignedPoints) {
|
|
1644
|
+
if (!nodeConnections[nodeId][ap.connectionName]) {
|
|
1645
|
+
nodeConnections[nodeId][ap.connectionName] = [];
|
|
1646
|
+
}
|
|
1647
|
+
nodeConnections[nodeId][ap.connectionName].push({
|
|
1648
|
+
x: ap.point.x,
|
|
1649
|
+
y: ap.point.y
|
|
1650
|
+
});
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
for (const nodeId in nodeConnections) {
|
|
1654
|
+
for (const conn in nodeConnections[nodeId]) {
|
|
1655
|
+
const points = nodeConnections[nodeId][conn];
|
|
1656
|
+
if (points.length > 1) {
|
|
1657
|
+
dashedLines.push({
|
|
1658
|
+
points,
|
|
1659
|
+
step: 4,
|
|
1660
|
+
strokeDash: "5 5",
|
|
1661
|
+
strokeColor: this.colorMap[conn] || "#000"
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
graphics.lines.push(...dashedLines);
|
|
1667
|
+
return graphics;
|
|
1668
|
+
}
|
|
1669
|
+
};
|
|
1670
|
+
|
|
1671
|
+
// lib/solvers/HighDensitySolver/SingleHighDensityRouteSolver.ts
|
|
1672
|
+
var SingleHighDensityRouteSolver = class extends BaseSolver {
|
|
1673
|
+
obstacleRoutes;
|
|
1674
|
+
bounds;
|
|
1675
|
+
boundsSize;
|
|
1676
|
+
boundsCenter;
|
|
1677
|
+
A;
|
|
1678
|
+
B;
|
|
1679
|
+
straightLineDistance;
|
|
1680
|
+
viaDiameter;
|
|
1681
|
+
traceThickness;
|
|
1682
|
+
obstacleMargin;
|
|
1683
|
+
layerCount;
|
|
1684
|
+
minCellSize = 0.05;
|
|
1685
|
+
cellStep = 0.05;
|
|
1686
|
+
GREEDY_MULTIPLER = 1.1;
|
|
1687
|
+
numRoutes;
|
|
1688
|
+
VIA_PENALTY_FACTOR = 0.3;
|
|
1689
|
+
CELL_SIZE_FACTOR;
|
|
1690
|
+
exploredNodes;
|
|
1691
|
+
candidates;
|
|
1692
|
+
connectionName;
|
|
1693
|
+
solvedPath = null;
|
|
1694
|
+
futureConnections;
|
|
1695
|
+
hyperParameters;
|
|
1696
|
+
connMap;
|
|
1697
|
+
/** For debugging/animating the exploration */
|
|
1698
|
+
debug_exploredNodesOrdered;
|
|
1699
|
+
debug_nodesTooCloseToObstacle;
|
|
1700
|
+
debug_nodePathToParentIntersectsObstacle;
|
|
1701
|
+
debugEnabled = true;
|
|
1702
|
+
constructor(opts) {
|
|
1703
|
+
super();
|
|
1704
|
+
this.bounds = opts.bounds;
|
|
1705
|
+
this.connMap = opts.connMap;
|
|
1706
|
+
this.hyperParameters = opts.hyperParameters ?? {};
|
|
1707
|
+
this.CELL_SIZE_FACTOR = this.hyperParameters.CELL_SIZE_FACTOR ?? 1;
|
|
1708
|
+
this.boundsSize = {
|
|
1709
|
+
width: this.bounds.maxX - this.bounds.minX,
|
|
1710
|
+
height: this.bounds.maxY - this.bounds.minY
|
|
1711
|
+
};
|
|
1712
|
+
this.boundsCenter = {
|
|
1713
|
+
x: (this.bounds.minX + this.bounds.maxX) / 2,
|
|
1714
|
+
y: (this.bounds.minY + this.bounds.maxY) / 2
|
|
1715
|
+
};
|
|
1716
|
+
this.connectionName = opts.connectionName;
|
|
1717
|
+
this.obstacleRoutes = opts.obstacleRoutes;
|
|
1718
|
+
this.A = opts.A;
|
|
1719
|
+
this.B = opts.B;
|
|
1720
|
+
this.viaDiameter = opts.viaDiameter ?? 0.6;
|
|
1721
|
+
this.traceThickness = opts.traceThickness ?? 0.15;
|
|
1722
|
+
this.obstacleMargin = opts.obstacleMargin ?? 0.2;
|
|
1723
|
+
this.layerCount = opts.layerCount ?? 2;
|
|
1724
|
+
this.exploredNodes = /* @__PURE__ */ new Set();
|
|
1725
|
+
this.candidates = [
|
|
1726
|
+
{
|
|
1727
|
+
...opts.A,
|
|
1728
|
+
z: opts.A.z ?? 0,
|
|
1729
|
+
g: 0,
|
|
1730
|
+
h: 0,
|
|
1731
|
+
f: 0,
|
|
1732
|
+
parent: null
|
|
1733
|
+
}
|
|
1734
|
+
];
|
|
1735
|
+
this.straightLineDistance = distance(this.A, this.B);
|
|
1736
|
+
this.futureConnections = opts.futureConnections ?? [];
|
|
1737
|
+
this.MAX_ITERATIONS = 5e3;
|
|
1738
|
+
this.debug_exploredNodesOrdered = [];
|
|
1739
|
+
this.debug_nodesTooCloseToObstacle = /* @__PURE__ */ new Set();
|
|
1740
|
+
this.debug_nodePathToParentIntersectsObstacle = /* @__PURE__ */ new Set();
|
|
1741
|
+
this.numRoutes = this.obstacleRoutes.length + this.futureConnections.length;
|
|
1742
|
+
const bestRowOrColumnCount = Math.ceil(5 * (this.numRoutes + 1));
|
|
1743
|
+
let numXCells = this.boundsSize.width / this.cellStep;
|
|
1744
|
+
let numYCells = this.boundsSize.height / this.cellStep;
|
|
1745
|
+
while (numXCells * numYCells > bestRowOrColumnCount ** 2) {
|
|
1746
|
+
if (this.cellStep * 2 > opts.minDistBetweenEnteringPoints) {
|
|
1747
|
+
break;
|
|
1748
|
+
}
|
|
1749
|
+
this.cellStep *= 2;
|
|
1750
|
+
numXCells = this.boundsSize.width / this.cellStep;
|
|
1751
|
+
numYCells = this.boundsSize.height / this.cellStep;
|
|
1752
|
+
}
|
|
1753
|
+
this.cellStep *= this.CELL_SIZE_FACTOR;
|
|
1754
|
+
if (this.futureConnections && this.futureConnections.length === 0 && this.obstacleRoutes.length === 0) {
|
|
1755
|
+
this.handleSimpleCases();
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1758
|
+
handleSimpleCases() {
|
|
1759
|
+
this.solved = true;
|
|
1760
|
+
this.solvedPath = {
|
|
1761
|
+
connectionName: this.connectionName,
|
|
1762
|
+
route: [
|
|
1763
|
+
this.A,
|
|
1764
|
+
{ ...this.boundsCenter, z: this.A.z },
|
|
1765
|
+
{
|
|
1766
|
+
...this.boundsCenter,
|
|
1767
|
+
z: this.B.z
|
|
1768
|
+
},
|
|
1769
|
+
this.B
|
|
1770
|
+
],
|
|
1771
|
+
traceThickness: this.traceThickness,
|
|
1772
|
+
viaDiameter: this.viaDiameter,
|
|
1773
|
+
vias: this.A.z === this.B.z ? [] : [this.boundsCenter]
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
get viaPenaltyDistance() {
|
|
1777
|
+
return this.cellStep + this.straightLineDistance * this.VIA_PENALTY_FACTOR;
|
|
1778
|
+
}
|
|
1779
|
+
isNodeTooCloseToObstacle(node, margin, isVia) {
|
|
1780
|
+
margin ??= this.obstacleMargin;
|
|
1781
|
+
if (isVia && node.parent) {
|
|
1782
|
+
const viasInMyRoute = this.getViasInNodePath(node.parent);
|
|
1783
|
+
for (const via of viasInMyRoute) {
|
|
1784
|
+
if (distance(node, via) < this.viaDiameter / 2 + margin) {
|
|
1785
|
+
return true;
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
for (const route of this.obstacleRoutes) {
|
|
1790
|
+
const connectedToObstacle = this.connMap?.areIdsConnected?.(
|
|
1791
|
+
this.connectionName,
|
|
1792
|
+
route.connectionName
|
|
1793
|
+
);
|
|
1794
|
+
if (!connectedToObstacle) {
|
|
1795
|
+
const pointPairs = getSameLayerPointPairs(route);
|
|
1796
|
+
for (const pointPair of pointPairs) {
|
|
1797
|
+
if ((isVia || pointPair.z === node.z) && pointToSegmentDistance(node, pointPair.A, pointPair.B) < this.traceThickness + margin) {
|
|
1798
|
+
return true;
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
for (const via of route.vias) {
|
|
1803
|
+
if (distance(node, via) < this.viaDiameter / 2 + margin) {
|
|
1804
|
+
return true;
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
return false;
|
|
1809
|
+
}
|
|
1810
|
+
isNodeTooCloseToEdge(node) {
|
|
1811
|
+
const viaRadius = this.viaDiameter / 2;
|
|
1812
|
+
return node.x < this.bounds.minX + viaRadius || node.x > this.bounds.maxX - viaRadius || node.y < this.bounds.minY + viaRadius || node.y > this.bounds.maxY - viaRadius;
|
|
1813
|
+
}
|
|
1814
|
+
doesPathToParentIntersectObstacle(node) {
|
|
1815
|
+
const parent = node.parent;
|
|
1816
|
+
if (!parent) return false;
|
|
1817
|
+
for (const route of this.obstacleRoutes) {
|
|
1818
|
+
const obstacleIsConnectedToNewPath = this.connMap?.areIdsConnected?.(
|
|
1819
|
+
this.connectionName,
|
|
1820
|
+
route.connectionName
|
|
1821
|
+
);
|
|
1822
|
+
if (obstacleIsConnectedToNewPath) continue;
|
|
1823
|
+
for (const pointPair of getSameLayerPointPairs(route)) {
|
|
1824
|
+
if (pointPair.z !== node.z) continue;
|
|
1825
|
+
if (doSegmentsIntersect(node, parent, pointPair.A, pointPair.B)) {
|
|
1826
|
+
return true;
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
return false;
|
|
1831
|
+
}
|
|
1832
|
+
computeH(node) {
|
|
1833
|
+
return distance(node, this.B) + // via penalty
|
|
1834
|
+
Math.abs(node.z - this.B.z) * this.viaPenaltyDistance;
|
|
1835
|
+
}
|
|
1836
|
+
computeG(node) {
|
|
1837
|
+
return (node.parent?.g ?? 0) + (node.z === 0 ? 0 : this.viaPenaltyDistance) + distance(node, node.parent);
|
|
1838
|
+
}
|
|
1839
|
+
computeF(g, h) {
|
|
1840
|
+
return g + h * this.GREEDY_MULTIPLER;
|
|
1841
|
+
}
|
|
1842
|
+
getNodeKey(node) {
|
|
1843
|
+
return `${Math.round(node.x / this.cellStep) * this.cellStep},${Math.round(node.y / this.cellStep) * this.cellStep},${node.z}`;
|
|
1844
|
+
}
|
|
1845
|
+
getNeighbors(node) {
|
|
1846
|
+
const neighbors = [];
|
|
1847
|
+
const { maxX, minX, maxY, minY } = this.bounds;
|
|
1848
|
+
for (let x = -1; x <= 1; x++) {
|
|
1849
|
+
for (let y = -1; y <= 1; y++) {
|
|
1850
|
+
if (x === 0 && y === 0) continue;
|
|
1851
|
+
const neighbor = {
|
|
1852
|
+
...node,
|
|
1853
|
+
parent: node,
|
|
1854
|
+
x: clamp2(node.x + x * this.cellStep, minX, maxX),
|
|
1855
|
+
y: clamp2(node.y + y * this.cellStep, minY, maxY)
|
|
1856
|
+
};
|
|
1857
|
+
const neighborKey = this.getNodeKey(neighbor);
|
|
1858
|
+
if (this.exploredNodes.has(neighborKey)) {
|
|
1859
|
+
continue;
|
|
1860
|
+
}
|
|
1861
|
+
if (this.isNodeTooCloseToObstacle(neighbor)) {
|
|
1862
|
+
this.debug_nodesTooCloseToObstacle.add(neighborKey);
|
|
1863
|
+
this.exploredNodes.add(neighborKey);
|
|
1864
|
+
continue;
|
|
1865
|
+
}
|
|
1866
|
+
if (this.doesPathToParentIntersectObstacle(neighbor)) {
|
|
1867
|
+
this.debug_nodePathToParentIntersectsObstacle.add(neighborKey);
|
|
1868
|
+
this.exploredNodes.add(neighborKey);
|
|
1869
|
+
continue;
|
|
1870
|
+
}
|
|
1871
|
+
neighbor.g = this.computeG(neighbor);
|
|
1872
|
+
neighbor.h = this.computeH(neighbor);
|
|
1873
|
+
neighbor.f = this.computeF(neighbor.g, neighbor.h);
|
|
1874
|
+
neighbors.push(neighbor);
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
const viaNeighbor = {
|
|
1878
|
+
...node,
|
|
1879
|
+
parent: node,
|
|
1880
|
+
z: node.z === 0 ? this.layerCount - 1 : 0
|
|
1881
|
+
};
|
|
1882
|
+
if (!this.exploredNodes.has(this.getNodeKey(viaNeighbor)) && !this.isNodeTooCloseToObstacle(
|
|
1883
|
+
viaNeighbor,
|
|
1884
|
+
this.viaDiameter / 2 + this.obstacleMargin,
|
|
1885
|
+
true
|
|
1886
|
+
) && !this.isNodeTooCloseToEdge(viaNeighbor)) {
|
|
1887
|
+
viaNeighbor.g = this.computeG(viaNeighbor);
|
|
1888
|
+
viaNeighbor.h = this.computeH(viaNeighbor);
|
|
1889
|
+
viaNeighbor.f = this.computeF(viaNeighbor.g, viaNeighbor.h);
|
|
1890
|
+
neighbors.push(viaNeighbor);
|
|
1891
|
+
}
|
|
1892
|
+
return neighbors;
|
|
1893
|
+
}
|
|
1894
|
+
getNodePath(node) {
|
|
1895
|
+
const path = [];
|
|
1896
|
+
while (node) {
|
|
1897
|
+
path.push(node);
|
|
1898
|
+
node = node.parent;
|
|
1899
|
+
}
|
|
1900
|
+
return path;
|
|
1901
|
+
}
|
|
1902
|
+
getViasInNodePath(node) {
|
|
1903
|
+
const path = this.getNodePath(node);
|
|
1904
|
+
const vias = [];
|
|
1905
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
1906
|
+
if (path[i].z !== path[i + 1].z) {
|
|
1907
|
+
vias.push({ x: path[i].x, y: path[i].y });
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
return vias;
|
|
1911
|
+
}
|
|
1912
|
+
setSolvedPath(node) {
|
|
1913
|
+
const path = this.getNodePath(node);
|
|
1914
|
+
path.reverse();
|
|
1915
|
+
const vias = [];
|
|
1916
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
1917
|
+
if (path[i].z !== path[i + 1].z) {
|
|
1918
|
+
vias.push({ x: path[i].x, y: path[i].y });
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
this.solvedPath = {
|
|
1922
|
+
connectionName: this.connectionName,
|
|
1923
|
+
traceThickness: this.traceThickness,
|
|
1924
|
+
viaDiameter: this.viaDiameter,
|
|
1925
|
+
route: path.map((node2) => ({ x: node2.x, y: node2.y, z: node2.z })).concat([this.B]),
|
|
1926
|
+
vias
|
|
1927
|
+
};
|
|
1928
|
+
}
|
|
1929
|
+
computeProgress(currentNode, goalDist, isOnLayer) {
|
|
1930
|
+
if (!isOnLayer) goalDist += this.viaPenaltyDistance;
|
|
1931
|
+
const goalDistPercent = 1 - goalDist / this.straightLineDistance;
|
|
1932
|
+
return Math.max(
|
|
1933
|
+
this.progress || 0,
|
|
1934
|
+
// 0.112 = ~90% -> 50%
|
|
1935
|
+
// ~25% -> 2%
|
|
1936
|
+
// ~99% -> 94%
|
|
1937
|
+
// ~95% -> 72%
|
|
1938
|
+
2 / Math.PI * Math.atan(0.112 * goalDistPercent / (1 - goalDistPercent))
|
|
1939
|
+
);
|
|
1940
|
+
}
|
|
1941
|
+
_step() {
|
|
1942
|
+
this.candidates.sort((a, b) => b.f - a.f);
|
|
1943
|
+
let currentNode = this.candidates.pop();
|
|
1944
|
+
while (currentNode && this.exploredNodes.has(this.getNodeKey(currentNode))) {
|
|
1945
|
+
currentNode = this.candidates.pop();
|
|
1946
|
+
}
|
|
1947
|
+
if (!currentNode) {
|
|
1948
|
+
this.failed = true;
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
this.exploredNodes.add(this.getNodeKey(currentNode));
|
|
1952
|
+
this.debug_exploredNodesOrdered.push(this.getNodeKey(currentNode));
|
|
1953
|
+
const goalDist = distance(currentNode, this.B);
|
|
1954
|
+
this.progress = this.computeProgress(
|
|
1955
|
+
currentNode,
|
|
1956
|
+
goalDist,
|
|
1957
|
+
currentNode.z === this.B.z
|
|
1958
|
+
);
|
|
1959
|
+
if (goalDist <= this.cellStep && currentNode.z === this.B.z) {
|
|
1960
|
+
this.solved = true;
|
|
1961
|
+
this.setSolvedPath(currentNode);
|
|
1962
|
+
}
|
|
1963
|
+
const neighbors = this.getNeighbors(currentNode);
|
|
1964
|
+
for (const neighbor of neighbors) {
|
|
1965
|
+
this.candidates.push(neighbor);
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
visualize() {
|
|
1969
|
+
const graphics = {
|
|
1970
|
+
lines: [],
|
|
1971
|
+
points: [],
|
|
1972
|
+
rects: [],
|
|
1973
|
+
circles: []
|
|
1974
|
+
};
|
|
1975
|
+
graphics.points.push({
|
|
1976
|
+
x: this.A.x,
|
|
1977
|
+
y: this.A.y,
|
|
1978
|
+
label: "Input A",
|
|
1979
|
+
color: "orange"
|
|
1980
|
+
});
|
|
1981
|
+
graphics.points.push({
|
|
1982
|
+
x: this.B.x,
|
|
1983
|
+
y: this.B.y,
|
|
1984
|
+
label: "Input B",
|
|
1985
|
+
color: "orange"
|
|
1986
|
+
});
|
|
1987
|
+
graphics.lines.push({
|
|
1988
|
+
points: [this.A, this.B],
|
|
1989
|
+
strokeColor: "rgba(255, 0, 0, 0.5)",
|
|
1990
|
+
label: "Direct Input Connection"
|
|
1991
|
+
});
|
|
1992
|
+
for (let routeIndex = 0; routeIndex < this.obstacleRoutes.length; routeIndex++) {
|
|
1993
|
+
const route = this.obstacleRoutes[routeIndex];
|
|
1994
|
+
for (let i = 0; i < route.route.length - 1; i++) {
|
|
1995
|
+
const z = route.route[i].z;
|
|
1996
|
+
graphics.lines.push({
|
|
1997
|
+
points: [route.route[i], route.route[i + 1]],
|
|
1998
|
+
strokeColor: z === 0 ? "rgba(255, 0, 0, 0.75)" : "rgba(255, 128, 0, 0.25)",
|
|
1999
|
+
strokeWidth: route.traceThickness,
|
|
2000
|
+
label: "Obstacle Route",
|
|
2001
|
+
layer: `obstacle${routeIndex.toString()}`
|
|
2002
|
+
});
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
for (let i = 0; i < this.debug_exploredNodesOrdered.length; i++) {
|
|
2006
|
+
const nodeKey = this.debug_exploredNodesOrdered[i];
|
|
2007
|
+
const [x, y, z] = nodeKey.split(",").map(Number);
|
|
2008
|
+
if (this.debug_nodesTooCloseToObstacle.has(nodeKey)) continue;
|
|
2009
|
+
if (this.debug_nodePathToParentIntersectsObstacle.has(nodeKey)) continue;
|
|
2010
|
+
graphics.rects.push({
|
|
2011
|
+
center: {
|
|
2012
|
+
x: x + z * this.cellStep / 20,
|
|
2013
|
+
y: y + z * this.cellStep / 20
|
|
2014
|
+
},
|
|
2015
|
+
fill: z === 0 ? `rgba(255,0,255,${0.3 - i / this.debug_exploredNodesOrdered.length * 0.2})` : `rgba(0,0,255,${0.3 - i / this.debug_exploredNodesOrdered.length * 0.2})`,
|
|
2016
|
+
width: this.cellStep * 0.9,
|
|
2017
|
+
height: this.cellStep * 0.9,
|
|
2018
|
+
label: `Explored (z=${z})`
|
|
2019
|
+
});
|
|
2020
|
+
}
|
|
2021
|
+
for (const route of this.obstacleRoutes) {
|
|
2022
|
+
for (const via of route.vias) {
|
|
2023
|
+
graphics.circles.push({
|
|
2024
|
+
center: {
|
|
2025
|
+
x: via.x,
|
|
2026
|
+
y: via.y
|
|
2027
|
+
},
|
|
2028
|
+
radius: this.viaDiameter / 2,
|
|
2029
|
+
fill: "rgba(255, 0, 0, 0.5)",
|
|
2030
|
+
label: "Via"
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
if (this.solvedPath) {
|
|
2035
|
+
graphics.lines.push({
|
|
2036
|
+
points: this.solvedPath.route,
|
|
2037
|
+
strokeColor: "green",
|
|
2038
|
+
label: "Solved Route"
|
|
2039
|
+
});
|
|
2040
|
+
for (const via of this.solvedPath.vias) {
|
|
2041
|
+
graphics.circles.push({
|
|
2042
|
+
center: via,
|
|
2043
|
+
radius: this.viaDiameter / 2,
|
|
2044
|
+
fill: "green",
|
|
2045
|
+
label: "Via"
|
|
2046
|
+
});
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
2049
|
+
return graphics;
|
|
2050
|
+
}
|
|
2051
|
+
};
|
|
2052
|
+
function getSameLayerPointPairs(route) {
|
|
2053
|
+
const pointPairs = [];
|
|
2054
|
+
for (let i = 0; i < route.route.length - 1; i++) {
|
|
2055
|
+
if (route.route[i].z === route.route[i + 1].z) {
|
|
2056
|
+
pointPairs.push({
|
|
2057
|
+
z: route.route[i].z,
|
|
2058
|
+
A: route.route[i],
|
|
2059
|
+
B: route.route[i + 1]
|
|
2060
|
+
});
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
return pointPairs;
|
|
2064
|
+
}
|
|
2065
|
+
function clamp2(value, min, max) {
|
|
2066
|
+
return Math.max(min, Math.min(value, max));
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
// lib/solvers/HighDensitySolver/SingleHighDensityRouteSolver6_VertHorzLayer_FutureCost.ts
|
|
2070
|
+
var SingleHighDensityRouteSolver6_VertHorzLayer_FutureCost = class extends SingleHighDensityRouteSolver {
|
|
2071
|
+
FUTURE_CONNECTION_PROX_TRACE_PENALTY_FACTOR = 2;
|
|
2072
|
+
FUTURE_CONNECTION_PROX_VIA_PENALTY_FACTOR = 1;
|
|
2073
|
+
FUTURE_CONNECTION_PROXIMITY_VD = 10;
|
|
2074
|
+
MISALIGNED_DIST_PENALTY_FACTOR = 5;
|
|
2075
|
+
VIA_PENALTY_FACTOR_2 = 1;
|
|
2076
|
+
FLIP_TRACE_ALIGNMENT_DIRECTION = false;
|
|
2077
|
+
constructor(opts) {
|
|
2078
|
+
super(opts);
|
|
2079
|
+
for (const key in opts.hyperParameters) {
|
|
2080
|
+
this[key] = opts.hyperParameters[key];
|
|
2081
|
+
}
|
|
2082
|
+
const viasThatCanFitHorz = this.boundsSize.width / this.viaDiameter;
|
|
2083
|
+
this.VIA_PENALTY_FACTOR = 0.3 * (viasThatCanFitHorz / this.numRoutes) * this.VIA_PENALTY_FACTOR_2;
|
|
2084
|
+
}
|
|
2085
|
+
getClosestFutureConnectionPoint(node) {
|
|
2086
|
+
let minDist = Infinity;
|
|
2087
|
+
let closestPoint = null;
|
|
2088
|
+
for (const futureConnection of this.futureConnections) {
|
|
2089
|
+
for (const point of futureConnection.points) {
|
|
2090
|
+
const dist = distance(node, point);
|
|
2091
|
+
if (dist < minDist) {
|
|
2092
|
+
minDist = dist;
|
|
2093
|
+
closestPoint = point;
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
return closestPoint;
|
|
2098
|
+
}
|
|
2099
|
+
/**
|
|
2100
|
+
* Rapidly approaches 0 as the goal distance approaches 0
|
|
2101
|
+
*/
|
|
2102
|
+
diminishCloseToGoal(node) {
|
|
2103
|
+
const goalDist = distance(node, this.B);
|
|
2104
|
+
return 1 - Math.exp(-goalDist / this.straightLineDistance * 5);
|
|
2105
|
+
}
|
|
2106
|
+
getFutureConnectionPenalty(node, isVia) {
|
|
2107
|
+
let futureConnectionPenalty = 0;
|
|
2108
|
+
const closestFuturePoint = this.getClosestFutureConnectionPoint(node);
|
|
2109
|
+
const goalDist = distance(node, this.B);
|
|
2110
|
+
if (closestFuturePoint) {
|
|
2111
|
+
const distToFuturePoint = distance(node, closestFuturePoint);
|
|
2112
|
+
if (goalDist <= distToFuturePoint) return 0;
|
|
2113
|
+
const maxDist = this.viaDiameter * this.FUTURE_CONNECTION_PROXIMITY_VD;
|
|
2114
|
+
const distRatio = distToFuturePoint / maxDist;
|
|
2115
|
+
const maxPenalty = isVia ? this.straightLineDistance * this.FUTURE_CONNECTION_PROX_VIA_PENALTY_FACTOR : this.straightLineDistance * this.FUTURE_CONNECTION_PROX_TRACE_PENALTY_FACTOR;
|
|
2116
|
+
futureConnectionPenalty = maxPenalty * Math.exp(-distRatio * 5);
|
|
2117
|
+
}
|
|
2118
|
+
return futureConnectionPenalty;
|
|
2119
|
+
}
|
|
2120
|
+
computeH(node) {
|
|
2121
|
+
const goalDist = distance(node, this.B) ** 1.6;
|
|
2122
|
+
const goalDistRatio = goalDist / this.straightLineDistance;
|
|
2123
|
+
const baseCost = goalDist + (node.z !== this.B.z ? this.viaPenaltyDistance : 0);
|
|
2124
|
+
return baseCost + this.getFutureConnectionPenalty(node, node.z !== node.parent?.z);
|
|
2125
|
+
}
|
|
2126
|
+
computeG(node) {
|
|
2127
|
+
const dx = Math.abs(node.x - node.parent.x);
|
|
2128
|
+
const dy = Math.abs(node.y - node.parent.y);
|
|
2129
|
+
const dist = Math.sqrt(dx ** 2 + dy ** 2);
|
|
2130
|
+
const misalignedDist = !this.FLIP_TRACE_ALIGNMENT_DIRECTION ? node.z === 0 ? dy : dx : node.z === 0 ? dx : dy;
|
|
2131
|
+
const baseCost = (node.parent?.g ?? 0) + (node.z === node.parent?.z ? 0 : this.viaPenaltyDistance) + dist + misalignedDist * this.MISALIGNED_DIST_PENALTY_FACTOR;
|
|
2132
|
+
return baseCost + this.getFutureConnectionPenalty(node, node.z !== node.parent?.z);
|
|
2133
|
+
}
|
|
2134
|
+
};
|
|
2135
|
+
|
|
2136
|
+
// lib/utils/cloneAndShuffleArray.ts
|
|
2137
|
+
function seededRandom(seed) {
|
|
2138
|
+
let s = seed;
|
|
2139
|
+
for (let i = 0; i < 10; i++) {
|
|
2140
|
+
s = s * 16807 % 2147483647;
|
|
2141
|
+
}
|
|
2142
|
+
let state0 = s;
|
|
2143
|
+
s = (seed * 69069 + 1) % 2147483647;
|
|
2144
|
+
for (let i = 0; i < 10; i++) {
|
|
2145
|
+
s = s * 48271 % 2147483647;
|
|
2146
|
+
}
|
|
2147
|
+
let state1 = s;
|
|
2148
|
+
return () => {
|
|
2149
|
+
let s1 = state0;
|
|
2150
|
+
const s0 = state1;
|
|
2151
|
+
state0 = s0;
|
|
2152
|
+
s1 ^= s1 << 23;
|
|
2153
|
+
s1 ^= s1 >>> 17;
|
|
2154
|
+
s1 ^= s0;
|
|
2155
|
+
s1 ^= s0 >>> 26;
|
|
2156
|
+
state1 = s1;
|
|
2157
|
+
const result = (state0 + state1) / 4294967296;
|
|
2158
|
+
return result - Math.floor(result);
|
|
2159
|
+
};
|
|
2160
|
+
}
|
|
2161
|
+
function cloneAndShuffleArray(arr, seed) {
|
|
2162
|
+
if (seed === 0) return arr;
|
|
2163
|
+
const random = seededRandom(seed);
|
|
2164
|
+
const shuffled = arr.slice();
|
|
2165
|
+
for (let i = 0; i < shuffled.length; i++) {
|
|
2166
|
+
const i1 = Math.floor(random() * shuffled.length);
|
|
2167
|
+
const i2 = Math.floor(random() * (i + 1));
|
|
2168
|
+
[shuffled[i1], shuffled[i2]] = [shuffled[i2], shuffled[i1]];
|
|
2169
|
+
}
|
|
2170
|
+
return shuffled;
|
|
2171
|
+
}
|
|
2172
|
+
|
|
2173
|
+
// lib/utils/getBoundsFromNodeWithPortPoints.ts
|
|
2174
|
+
function getBoundsFromNodeWithPortPoints(nodeWithPortPoints) {
|
|
2175
|
+
const bounds = {
|
|
2176
|
+
minX: nodeWithPortPoints.center.x - nodeWithPortPoints.width / 2,
|
|
2177
|
+
maxX: nodeWithPortPoints.center.x + nodeWithPortPoints.width / 2,
|
|
2178
|
+
minY: nodeWithPortPoints.center.y - nodeWithPortPoints.height / 2,
|
|
2179
|
+
maxY: nodeWithPortPoints.center.y + nodeWithPortPoints.height / 2
|
|
2180
|
+
};
|
|
2181
|
+
for (const pt of nodeWithPortPoints.portPoints) {
|
|
2182
|
+
if (pt.x < bounds.minX) {
|
|
2183
|
+
bounds.minX = pt.x;
|
|
2184
|
+
}
|
|
2185
|
+
if (pt.x > bounds.maxX) {
|
|
2186
|
+
bounds.maxX = pt.x;
|
|
2187
|
+
}
|
|
2188
|
+
if (pt.y < bounds.minY) {
|
|
2189
|
+
bounds.minY = pt.y;
|
|
2190
|
+
}
|
|
2191
|
+
if (pt.y > bounds.maxY) {
|
|
2192
|
+
bounds.maxY = pt.y;
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
return bounds;
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
// lib/utils/getMinDistBetweenEnteringPoints.ts
|
|
2199
|
+
var getMinDistBetweenEnteringPoints = (node) => {
|
|
2200
|
+
let minDist = Infinity;
|
|
2201
|
+
const points = node.portPoints;
|
|
2202
|
+
for (let i = 0; i < points.length; i++) {
|
|
2203
|
+
for (let j = i + 1; j < points.length; j++) {
|
|
2204
|
+
const p1 = points[i];
|
|
2205
|
+
const p2 = points[j];
|
|
2206
|
+
const dist = Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
|
|
2207
|
+
minDist = Math.min(minDist, dist);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
return minDist === Infinity ? 0 : minDist;
|
|
2211
|
+
};
|
|
2212
|
+
|
|
2213
|
+
// lib/solvers/HighDensitySolver/SingleIntraNodeRouteSolver.ts
|
|
2214
|
+
var SingleIntraNodeRouteSolver = class extends BaseSolver {
|
|
2215
|
+
nodeWithPortPoints;
|
|
2216
|
+
colorMap;
|
|
2217
|
+
unsolvedConnections;
|
|
2218
|
+
totalConnections;
|
|
2219
|
+
solvedRoutes;
|
|
2220
|
+
failedSolvers;
|
|
2221
|
+
hyperParameters;
|
|
2222
|
+
minDistBetweenEnteringPoints;
|
|
2223
|
+
activeSolver = null;
|
|
2224
|
+
connMap;
|
|
2225
|
+
constructor(params) {
|
|
2226
|
+
const { nodeWithPortPoints, colorMap } = params;
|
|
2227
|
+
super();
|
|
2228
|
+
this.nodeWithPortPoints = nodeWithPortPoints;
|
|
2229
|
+
this.colorMap = colorMap ?? {};
|
|
2230
|
+
this.solvedRoutes = [];
|
|
2231
|
+
this.hyperParameters = params.hyperParameters ?? {};
|
|
2232
|
+
this.failedSolvers = [];
|
|
2233
|
+
this.connMap = params.connMap;
|
|
2234
|
+
const unsolvedConnectionsMap = /* @__PURE__ */ new Map();
|
|
2235
|
+
for (const { connectionName, x, y, z } of nodeWithPortPoints.portPoints) {
|
|
2236
|
+
unsolvedConnectionsMap.set(connectionName, [
|
|
2237
|
+
...unsolvedConnectionsMap.get(connectionName) ?? [],
|
|
2238
|
+
{ x, y, z: z ?? 0 }
|
|
2239
|
+
]);
|
|
2240
|
+
}
|
|
2241
|
+
this.unsolvedConnections = Array.from(
|
|
2242
|
+
unsolvedConnectionsMap.entries().map(([connectionName, points]) => ({
|
|
2243
|
+
connectionName,
|
|
2244
|
+
points
|
|
2245
|
+
}))
|
|
2246
|
+
);
|
|
2247
|
+
if (this.hyperParameters.SHUFFLE_SEED) {
|
|
2248
|
+
this.unsolvedConnections = cloneAndShuffleArray(
|
|
2249
|
+
this.unsolvedConnections,
|
|
2250
|
+
this.hyperParameters.SHUFFLE_SEED ?? 0
|
|
2251
|
+
);
|
|
2252
|
+
this.unsolvedConnections = this.unsolvedConnections.map(
|
|
2253
|
+
({ points, ...rest }, i) => ({
|
|
2254
|
+
...rest,
|
|
2255
|
+
points: cloneAndShuffleArray(
|
|
2256
|
+
points,
|
|
2257
|
+
i * 7117 + (this.hyperParameters.SHUFFLE_SEED ?? 0)
|
|
2258
|
+
)
|
|
2259
|
+
})
|
|
2260
|
+
);
|
|
2261
|
+
}
|
|
2262
|
+
this.totalConnections = this.unsolvedConnections.length;
|
|
2263
|
+
this.MAX_ITERATIONS = 1e3 * this.totalConnections ** 1.5;
|
|
2264
|
+
this.minDistBetweenEnteringPoints = getMinDistBetweenEnteringPoints(
|
|
2265
|
+
this.nodeWithPortPoints
|
|
2266
|
+
);
|
|
2267
|
+
}
|
|
2268
|
+
// handleSimpleNoCrossingsCase() {
|
|
2269
|
+
// // TODO check to make sure there are no crossings due to trace width
|
|
2270
|
+
// this.solved = true
|
|
2271
|
+
// this.solvedRoutes = this.unsolvedConnections.map(
|
|
2272
|
+
// ({ connectionName, points }) => ({
|
|
2273
|
+
// connectionName,
|
|
2274
|
+
// route: points,
|
|
2275
|
+
// traceThickness: 0.1, // TODO load from hyperParameters
|
|
2276
|
+
// viaDiameter: 0.6,
|
|
2277
|
+
// vias: [],
|
|
2278
|
+
// }),
|
|
2279
|
+
// )
|
|
2280
|
+
// this.unsolvedConnections = []
|
|
2281
|
+
// }
|
|
2282
|
+
computeProgress() {
|
|
2283
|
+
return (this.solvedRoutes.length + (this.activeSolver?.progress || 0)) / this.totalConnections;
|
|
2284
|
+
}
|
|
2285
|
+
_step() {
|
|
2286
|
+
if (this.activeSolver) {
|
|
2287
|
+
this.activeSolver.step();
|
|
2288
|
+
this.progress = this.computeProgress();
|
|
2289
|
+
if (this.activeSolver.solved) {
|
|
2290
|
+
this.solvedRoutes.push(this.activeSolver.solvedPath);
|
|
2291
|
+
this.activeSolver = null;
|
|
2292
|
+
} else if (this.activeSolver.failed) {
|
|
2293
|
+
this.failedSolvers.push(this.activeSolver);
|
|
2294
|
+
this.activeSolver = null;
|
|
2295
|
+
this.error = this.failedSolvers.map((s) => s.error).join("\n");
|
|
2296
|
+
this.failed = true;
|
|
2297
|
+
}
|
|
2298
|
+
return;
|
|
2299
|
+
}
|
|
2300
|
+
const unsolvedConnection = this.unsolvedConnections.pop();
|
|
2301
|
+
this.progress = this.computeProgress();
|
|
2302
|
+
if (!unsolvedConnection) {
|
|
2303
|
+
this.solved = this.failedSolvers.length === 0;
|
|
2304
|
+
return;
|
|
2305
|
+
}
|
|
2306
|
+
const { connectionName, points } = unsolvedConnection;
|
|
2307
|
+
this.activeSolver = new SingleHighDensityRouteSolver6_VertHorzLayer_FutureCost({
|
|
2308
|
+
connectionName,
|
|
2309
|
+
minDistBetweenEnteringPoints: this.minDistBetweenEnteringPoints,
|
|
2310
|
+
bounds: getBoundsFromNodeWithPortPoints(this.nodeWithPortPoints),
|
|
2311
|
+
A: { x: points[0].x, y: points[0].y, z: points[0].z },
|
|
2312
|
+
B: {
|
|
2313
|
+
x: points[points.length - 1].x,
|
|
2314
|
+
y: points[points.length - 1].y,
|
|
2315
|
+
z: points[points.length - 1].z
|
|
2316
|
+
},
|
|
2317
|
+
obstacleRoutes: this.solvedRoutes,
|
|
2318
|
+
futureConnections: this.unsolvedConnections,
|
|
2319
|
+
layerCount: 2,
|
|
2320
|
+
hyperParameters: this.hyperParameters,
|
|
2321
|
+
connMap: this.connMap
|
|
2322
|
+
});
|
|
2323
|
+
}
|
|
2324
|
+
visualize() {
|
|
2325
|
+
const graphics = {
|
|
2326
|
+
lines: [],
|
|
2327
|
+
points: [],
|
|
2328
|
+
rects: [],
|
|
2329
|
+
circles: []
|
|
2330
|
+
};
|
|
2331
|
+
for (const pt of this.nodeWithPortPoints.portPoints) {
|
|
2332
|
+
graphics.points.push({
|
|
2333
|
+
x: pt.x,
|
|
2334
|
+
y: pt.y,
|
|
2335
|
+
label: [pt.connectionName, `layer: ${pt.z}`].join("\n"),
|
|
2336
|
+
color: this.colorMap[pt.connectionName] ?? "blue"
|
|
2337
|
+
});
|
|
2338
|
+
}
|
|
2339
|
+
for (let routeIndex = 0; routeIndex < this.solvedRoutes.length; routeIndex++) {
|
|
2340
|
+
const route = this.solvedRoutes[routeIndex];
|
|
2341
|
+
if (route.route.length > 0) {
|
|
2342
|
+
const routeColor = this.colorMap[route.connectionName] ?? "blue";
|
|
2343
|
+
for (let i = 0; i < route.route.length - 1; i++) {
|
|
2344
|
+
const p1 = route.route[i];
|
|
2345
|
+
const p2 = route.route[i + 1];
|
|
2346
|
+
graphics.lines.push({
|
|
2347
|
+
points: [p1, p2],
|
|
2348
|
+
strokeColor: p1.z === 0 ? safeTransparentize(routeColor, 0.2) : safeTransparentize(routeColor, 0.8),
|
|
2349
|
+
layer: `route-layer-${p1.z}`,
|
|
2350
|
+
step: routeIndex,
|
|
2351
|
+
strokeWidth: route.traceThickness
|
|
2352
|
+
});
|
|
2353
|
+
}
|
|
2354
|
+
for (const via of route.vias) {
|
|
2355
|
+
graphics.circles.push({
|
|
2356
|
+
center: { x: via.x, y: via.y },
|
|
2357
|
+
radius: route.viaDiameter / 2,
|
|
2358
|
+
fill: safeTransparentize(routeColor, 0.5),
|
|
2359
|
+
layer: "via",
|
|
2360
|
+
step: routeIndex
|
|
2361
|
+
});
|
|
2362
|
+
}
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
return graphics;
|
|
2366
|
+
}
|
|
2367
|
+
};
|
|
2368
|
+
|
|
2369
|
+
// lib/solvers/HyperParameterSupervisorSolver.ts
|
|
2370
|
+
var HyperParameterSupervisorSolver = class extends BaseSolver {
|
|
2371
|
+
GREEDY_MULTIPLIER = 1.2;
|
|
2372
|
+
MIN_SUBSTEPS = 1;
|
|
2373
|
+
supervisedSolvers;
|
|
2374
|
+
getHyperParameterDefs() {
|
|
2375
|
+
throw new Error("Not implemented");
|
|
2376
|
+
}
|
|
2377
|
+
getCombinationDefs() {
|
|
2378
|
+
return null;
|
|
2379
|
+
}
|
|
2380
|
+
getHyperParameterCombinations(hyperParameterDefs) {
|
|
2381
|
+
if (!hyperParameterDefs) {
|
|
2382
|
+
hyperParameterDefs = this.getHyperParameterDefs();
|
|
2383
|
+
}
|
|
2384
|
+
const combinations = [];
|
|
2385
|
+
if (hyperParameterDefs.length === 0) {
|
|
2386
|
+
return [{}];
|
|
2387
|
+
}
|
|
2388
|
+
const [currentDef, ...remainingDefs] = hyperParameterDefs;
|
|
2389
|
+
const subCombinations = this.getHyperParameterCombinations(remainingDefs);
|
|
2390
|
+
currentDef.possibleValues.forEach((value) => {
|
|
2391
|
+
subCombinations.forEach((subCombo) => {
|
|
2392
|
+
combinations.push({
|
|
2393
|
+
...subCombo,
|
|
2394
|
+
...value
|
|
2395
|
+
});
|
|
2396
|
+
});
|
|
2397
|
+
});
|
|
2398
|
+
return combinations;
|
|
2399
|
+
}
|
|
2400
|
+
initializeSolvers() {
|
|
2401
|
+
const hyperParameterDefs = this.getHyperParameterDefs();
|
|
2402
|
+
const combinationDefs = this.getCombinationDefs() ?? [
|
|
2403
|
+
hyperParameterDefs.map((def) => def.name)
|
|
2404
|
+
];
|
|
2405
|
+
this.supervisedSolvers = [];
|
|
2406
|
+
for (const combinationDef of combinationDefs) {
|
|
2407
|
+
const hyperParameterCombinations = this.getHyperParameterCombinations(
|
|
2408
|
+
hyperParameterDefs.filter((hpd) => combinationDef.includes(hpd.name))
|
|
2409
|
+
);
|
|
2410
|
+
for (const hyperParameters of hyperParameterCombinations) {
|
|
2411
|
+
const solver = this.generateSolver(hyperParameters);
|
|
2412
|
+
this.supervisedSolvers.push({
|
|
2413
|
+
hyperParameters,
|
|
2414
|
+
solver,
|
|
2415
|
+
h: 0,
|
|
2416
|
+
g: 0,
|
|
2417
|
+
f: 0
|
|
2418
|
+
});
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
generateSolver(hyperParameters) {
|
|
2423
|
+
throw new Error("Not implemented");
|
|
2424
|
+
}
|
|
2425
|
+
computeG(solver) {
|
|
2426
|
+
return solver.iterations / solver.MAX_ITERATIONS;
|
|
2427
|
+
}
|
|
2428
|
+
computeH(solver) {
|
|
2429
|
+
return 1 - (solver.progress || 0);
|
|
2430
|
+
}
|
|
2431
|
+
computeF(g, h) {
|
|
2432
|
+
return g + h * this.GREEDY_MULTIPLIER;
|
|
2433
|
+
}
|
|
2434
|
+
getSupervisedSolverWithBestFitness() {
|
|
2435
|
+
let bestFitness = Infinity;
|
|
2436
|
+
let bestSolver = null;
|
|
2437
|
+
for (const supervisedSolver of this.supervisedSolvers ?? []) {
|
|
2438
|
+
if (supervisedSolver.solver.solved) {
|
|
2439
|
+
return supervisedSolver;
|
|
2440
|
+
}
|
|
2441
|
+
if (supervisedSolver.solver.failed) {
|
|
2442
|
+
continue;
|
|
2443
|
+
}
|
|
2444
|
+
const fitness = supervisedSolver.f;
|
|
2445
|
+
if (fitness < bestFitness) {
|
|
2446
|
+
bestFitness = fitness;
|
|
2447
|
+
bestSolver = supervisedSolver;
|
|
2448
|
+
}
|
|
2449
|
+
}
|
|
2450
|
+
return bestSolver;
|
|
2451
|
+
}
|
|
2452
|
+
_step() {
|
|
2453
|
+
if (!this.supervisedSolvers) this.initializeSolvers();
|
|
2454
|
+
const supervisedSolver = this.getSupervisedSolverWithBestFitness();
|
|
2455
|
+
if (!supervisedSolver) {
|
|
2456
|
+
this.failed = true;
|
|
2457
|
+
this.error = "All solvers failed";
|
|
2458
|
+
return;
|
|
2459
|
+
}
|
|
2460
|
+
for (let i = 0; i < this.MIN_SUBSTEPS; i++) {
|
|
2461
|
+
supervisedSolver.solver.step();
|
|
2462
|
+
}
|
|
2463
|
+
supervisedSolver.g = this.computeG(supervisedSolver.solver);
|
|
2464
|
+
supervisedSolver.h = this.computeH(supervisedSolver.solver);
|
|
2465
|
+
supervisedSolver.f = this.computeF(supervisedSolver.g, supervisedSolver.h);
|
|
2466
|
+
if (supervisedSolver.solver.solved) {
|
|
2467
|
+
this.solved = true;
|
|
2468
|
+
this.onSolve?.(supervisedSolver);
|
|
2469
|
+
}
|
|
2470
|
+
}
|
|
2471
|
+
onSolve(solver) {
|
|
2472
|
+
}
|
|
2473
|
+
visualize() {
|
|
2474
|
+
const bestSupervisedSolver = this.getSupervisedSolverWithBestFitness();
|
|
2475
|
+
let graphics = {
|
|
2476
|
+
lines: [],
|
|
2477
|
+
circles: [],
|
|
2478
|
+
points: [],
|
|
2479
|
+
rects: []
|
|
2480
|
+
};
|
|
2481
|
+
if (bestSupervisedSolver) {
|
|
2482
|
+
graphics = bestSupervisedSolver.solver.visualize();
|
|
2483
|
+
}
|
|
2484
|
+
return graphics;
|
|
2485
|
+
}
|
|
2486
|
+
};
|
|
2487
|
+
|
|
2488
|
+
// lib/solvers/HyperHighDensitySolver/HyperSingleIntraNodeSolver.ts
|
|
2489
|
+
var HyperSingleIntraNodeSolver = class extends HyperParameterSupervisorSolver {
|
|
2490
|
+
constructorParams;
|
|
2491
|
+
solvedRoutes = [];
|
|
2492
|
+
nodeWithPortPoints;
|
|
2493
|
+
constructor(opts) {
|
|
2494
|
+
super();
|
|
2495
|
+
this.nodeWithPortPoints = opts.nodeWithPortPoints;
|
|
2496
|
+
this.constructorParams = opts;
|
|
2497
|
+
this.MAX_ITERATIONS = 25e4;
|
|
2498
|
+
this.GREEDY_MULTIPLIER = 5;
|
|
2499
|
+
this.MIN_SUBSTEPS = 100;
|
|
2500
|
+
}
|
|
2501
|
+
getCombinationDefs() {
|
|
2502
|
+
return [
|
|
2503
|
+
["majorCombinations", "orderings6", "cellSizeFactor"],
|
|
2504
|
+
["noVias"],
|
|
2505
|
+
["orderings50"],
|
|
2506
|
+
["flipTraceAlignmentDirection", "orderings6"]
|
|
2507
|
+
];
|
|
2508
|
+
}
|
|
2509
|
+
getHyperParameterDefs() {
|
|
2510
|
+
return [
|
|
2511
|
+
{
|
|
2512
|
+
name: "majorCombinations",
|
|
2513
|
+
possibleValues: [
|
|
2514
|
+
{
|
|
2515
|
+
FUTURE_CONNECTION_PROX_TRACE_PENALTY_FACTOR: 2,
|
|
2516
|
+
FUTURE_CONNECTION_PROX_VIA_PENALTY_FACTOR: 1,
|
|
2517
|
+
FUTURE_CONNECTION_PROXIMITY_VD: 10,
|
|
2518
|
+
MISALIGNED_DIST_PENALTY_FACTOR: 5
|
|
2519
|
+
},
|
|
2520
|
+
{
|
|
2521
|
+
FUTURE_CONNECTION_PROX_TRACE_PENALTY_FACTOR: 1,
|
|
2522
|
+
FUTURE_CONNECTION_PROX_VIA_PENALTY_FACTOR: 0.5,
|
|
2523
|
+
FUTURE_CONNECTION_PROXIMITY_VD: 5,
|
|
2524
|
+
MISALIGNED_DIST_PENALTY_FACTOR: 2
|
|
2525
|
+
},
|
|
2526
|
+
{
|
|
2527
|
+
FUTURE_CONNECTION_PROX_TRACE_PENALTY_FACTOR: 10,
|
|
2528
|
+
FUTURE_CONNECTION_PROX_VIA_PENALTY_FACTOR: 1,
|
|
2529
|
+
FUTURE_CONNECTION_PROXIMITY_VD: 5,
|
|
2530
|
+
MISALIGNED_DIST_PENALTY_FACTOR: 10,
|
|
2531
|
+
VIA_PENALTY_FACTOR_2: 1
|
|
2532
|
+
}
|
|
2533
|
+
]
|
|
2534
|
+
},
|
|
2535
|
+
{
|
|
2536
|
+
name: "orderings6",
|
|
2537
|
+
possibleValues: [
|
|
2538
|
+
{
|
|
2539
|
+
SHUFFLE_SEED: 0
|
|
2540
|
+
},
|
|
2541
|
+
{
|
|
2542
|
+
SHUFFLE_SEED: 1
|
|
2543
|
+
},
|
|
2544
|
+
{
|
|
2545
|
+
SHUFFLE_SEED: 2
|
|
2546
|
+
},
|
|
2547
|
+
{
|
|
2548
|
+
SHUFFLE_SEED: 3
|
|
2549
|
+
},
|
|
2550
|
+
{
|
|
2551
|
+
SHUFFLE_SEED: 4
|
|
2552
|
+
},
|
|
2553
|
+
{
|
|
2554
|
+
SHUFFLE_SEED: 5
|
|
2555
|
+
}
|
|
2556
|
+
]
|
|
2557
|
+
},
|
|
2558
|
+
{
|
|
2559
|
+
name: "cellSizeFactor",
|
|
2560
|
+
possibleValues: [
|
|
2561
|
+
{
|
|
2562
|
+
CELL_SIZE_FACTOR: 0.5
|
|
2563
|
+
},
|
|
2564
|
+
{
|
|
2565
|
+
CELL_SIZE_FACTOR: 1
|
|
2566
|
+
}
|
|
2567
|
+
]
|
|
2568
|
+
},
|
|
2569
|
+
{
|
|
2570
|
+
name: "flipTraceAlignmentDirection",
|
|
2571
|
+
possibleValues: [
|
|
2572
|
+
{
|
|
2573
|
+
FLIP_TRACE_ALIGNMENT_DIRECTION: true
|
|
2574
|
+
}
|
|
2575
|
+
]
|
|
2576
|
+
},
|
|
2577
|
+
{
|
|
2578
|
+
name: "noVias",
|
|
2579
|
+
possibleValues: [
|
|
2580
|
+
{
|
|
2581
|
+
CELL_SIZE_FACTOR: 2,
|
|
2582
|
+
VIA_PENALTY_FACTOR_2: 10
|
|
2583
|
+
}
|
|
2584
|
+
]
|
|
2585
|
+
},
|
|
2586
|
+
{
|
|
2587
|
+
name: "orderings50",
|
|
2588
|
+
possibleValues: Array.from({ length: 50 }, (_, i) => ({
|
|
2589
|
+
SHUFFLE_SEED: 100 + i
|
|
2590
|
+
}))
|
|
2591
|
+
}
|
|
2592
|
+
];
|
|
2593
|
+
}
|
|
2594
|
+
computeG(solver) {
|
|
2595
|
+
return solver.iterations / 1e4;
|
|
2596
|
+
}
|
|
2597
|
+
computeH(solver) {
|
|
2598
|
+
return 1 - (solver.progress || 0);
|
|
2599
|
+
}
|
|
2600
|
+
generateSolver(hyperParameters) {
|
|
2601
|
+
return new SingleIntraNodeRouteSolver({
|
|
2602
|
+
...this.constructorParams,
|
|
2603
|
+
hyperParameters
|
|
2604
|
+
});
|
|
2605
|
+
}
|
|
2606
|
+
onSolve(solver) {
|
|
2607
|
+
this.solvedRoutes = solver.solver.solvedRoutes;
|
|
2608
|
+
}
|
|
2609
|
+
};
|
|
2610
|
+
|
|
2611
|
+
// lib/utils/mergeRouteSegments.ts
|
|
2612
|
+
function mergeRouteSegments(route, connectionName, color) {
|
|
2613
|
+
const segments = [];
|
|
2614
|
+
let currentSegment = null;
|
|
2615
|
+
for (let i = 0; i < route.length; i++) {
|
|
2616
|
+
const point = route[i];
|
|
2617
|
+
if (!currentSegment) {
|
|
2618
|
+
currentSegment = {
|
|
2619
|
+
points: [{ x: point.x, y: point.y }],
|
|
2620
|
+
z: point.z,
|
|
2621
|
+
connectionName,
|
|
2622
|
+
color
|
|
2623
|
+
};
|
|
2624
|
+
} else if (currentSegment.z === point.z) {
|
|
2625
|
+
currentSegment.points.push({ x: point.x, y: point.y });
|
|
2626
|
+
} else {
|
|
2627
|
+
segments.push(currentSegment);
|
|
2628
|
+
currentSegment = {
|
|
2629
|
+
points: [{ x: point.x, y: point.y }],
|
|
2630
|
+
z: point.z,
|
|
2631
|
+
connectionName,
|
|
2632
|
+
color
|
|
2633
|
+
};
|
|
2634
|
+
}
|
|
2635
|
+
if (i === route.length - 1 && currentSegment) {
|
|
2636
|
+
segments.push(currentSegment);
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
return segments;
|
|
2640
|
+
}
|
|
2641
|
+
|
|
2642
|
+
// lib/solvers/HighDensitySolver/HighDensityRouteSolver.ts
|
|
2643
|
+
var HighDensityRouteSolver = class extends BaseSolver {
|
|
2644
|
+
unsolvedNodePortPoints;
|
|
2645
|
+
routes;
|
|
2646
|
+
colorMap;
|
|
2647
|
+
// Defaults as specified: viaDiameter of 0.6 and traceThickness of 0.15
|
|
2648
|
+
defaultViaDiameter = 0.6;
|
|
2649
|
+
defaultTraceThickness = 0.15;
|
|
2650
|
+
failedSolvers;
|
|
2651
|
+
activeSubSolver = null;
|
|
2652
|
+
connMap;
|
|
2653
|
+
constructor({
|
|
2654
|
+
nodePortPoints,
|
|
2655
|
+
colorMap,
|
|
2656
|
+
connMap
|
|
2657
|
+
}) {
|
|
2658
|
+
super();
|
|
2659
|
+
this.unsolvedNodePortPoints = nodePortPoints;
|
|
2660
|
+
this.colorMap = colorMap ?? {};
|
|
2661
|
+
this.connMap = connMap;
|
|
2662
|
+
this.routes = [];
|
|
2663
|
+
this.failedSolvers = [];
|
|
2664
|
+
this.MAX_ITERATIONS = 1e6;
|
|
2665
|
+
}
|
|
2666
|
+
/**
|
|
2667
|
+
* Each iteration, pop an unsolved node and attempt to find the routes inside
|
|
2668
|
+
* of it.
|
|
2669
|
+
*/
|
|
2670
|
+
_step() {
|
|
2671
|
+
if (this.activeSubSolver) {
|
|
2672
|
+
this.activeSubSolver.step();
|
|
2673
|
+
if (this.activeSubSolver.solved) {
|
|
2674
|
+
this.routes.push(...this.activeSubSolver.solvedRoutes);
|
|
2675
|
+
this.activeSubSolver = null;
|
|
2676
|
+
} else if (this.activeSubSolver.failed) {
|
|
2677
|
+
this.failedSolvers.push(this.activeSubSolver);
|
|
2678
|
+
this.activeSubSolver = null;
|
|
2679
|
+
}
|
|
2680
|
+
return;
|
|
2681
|
+
}
|
|
2682
|
+
if (this.unsolvedNodePortPoints.length === 0) {
|
|
2683
|
+
this.solved = true;
|
|
2684
|
+
return;
|
|
2685
|
+
}
|
|
2686
|
+
const node = this.unsolvedNodePortPoints.pop();
|
|
2687
|
+
this.activeSubSolver = new HyperSingleIntraNodeSolver({
|
|
2688
|
+
nodeWithPortPoints: node,
|
|
2689
|
+
colorMap: this.colorMap,
|
|
2690
|
+
connMap: this.connMap
|
|
2691
|
+
});
|
|
2692
|
+
}
|
|
2693
|
+
visualize() {
|
|
2694
|
+
let graphics = {
|
|
2695
|
+
lines: [],
|
|
2696
|
+
points: [],
|
|
2697
|
+
rects: [],
|
|
2698
|
+
circles: []
|
|
2699
|
+
};
|
|
2700
|
+
for (const route of this.routes) {
|
|
2701
|
+
const mergedSegments = mergeRouteSegments(
|
|
2702
|
+
route.route,
|
|
2703
|
+
route.connectionName,
|
|
2704
|
+
this.colorMap[route.connectionName]
|
|
2705
|
+
);
|
|
2706
|
+
for (const segment of mergedSegments) {
|
|
2707
|
+
graphics.lines.push({
|
|
2708
|
+
points: segment.points,
|
|
2709
|
+
label: segment.connectionName,
|
|
2710
|
+
strokeColor: segment.z === 0 ? segment.color : safeTransparentize(segment.color, 0.75),
|
|
2711
|
+
strokeWidth: route.traceThickness,
|
|
2712
|
+
strokeDash: segment.z !== 0 ? "10, 5" : void 0
|
|
2713
|
+
});
|
|
2714
|
+
}
|
|
2715
|
+
for (const via of route.vias) {
|
|
2716
|
+
graphics.circles.push({
|
|
2717
|
+
center: via,
|
|
2718
|
+
radius: route.viaDiameter / 2,
|
|
2719
|
+
fill: this.colorMap[route.connectionName],
|
|
2720
|
+
label: `${route.connectionName} via`
|
|
2721
|
+
});
|
|
2722
|
+
}
|
|
2723
|
+
}
|
|
2724
|
+
for (const solver of this.failedSolvers) {
|
|
2725
|
+
const node = solver.nodeWithPortPoints;
|
|
2726
|
+
const connectionGroups = {};
|
|
2727
|
+
for (const pt of node.portPoints) {
|
|
2728
|
+
if (!connectionGroups[pt.connectionName]) {
|
|
2729
|
+
connectionGroups[pt.connectionName] = [];
|
|
2730
|
+
}
|
|
2731
|
+
connectionGroups[pt.connectionName].push({ x: pt.x, y: pt.y, z: pt.z });
|
|
2732
|
+
}
|
|
2733
|
+
for (const [connectionName, points] of Object.entries(connectionGroups)) {
|
|
2734
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
2735
|
+
const start = points[i];
|
|
2736
|
+
const end = points[i + 1];
|
|
2737
|
+
graphics.lines.push({
|
|
2738
|
+
points: [start, end],
|
|
2739
|
+
strokeColor: "red",
|
|
2740
|
+
strokeDash: "10, 5"
|
|
2741
|
+
});
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
if (this.activeSubSolver) {
|
|
2746
|
+
graphics = combineVisualizations(
|
|
2747
|
+
graphics,
|
|
2748
|
+
this.activeSubSolver.visualize()
|
|
2749
|
+
);
|
|
2750
|
+
}
|
|
2751
|
+
return graphics;
|
|
2752
|
+
}
|
|
2753
|
+
};
|
|
2754
|
+
|
|
2755
|
+
// lib/solvers/CapacityPathingSolver/CapacityPathingSolver.ts
|
|
2756
|
+
var CapacityPathingSolver = class extends BaseSolver {
|
|
2757
|
+
connectionsWithNodes;
|
|
2758
|
+
usedNodeCapacityMap;
|
|
2759
|
+
simpleRouteJson;
|
|
2760
|
+
nodes;
|
|
2761
|
+
edges;
|
|
2762
|
+
GREEDY_MULTIPLIER = 1.1;
|
|
2763
|
+
nodeMap;
|
|
2764
|
+
nodeEdgeMap;
|
|
2765
|
+
connectionNameToGoalNodeIds;
|
|
2766
|
+
colorMap;
|
|
2767
|
+
maxDepthOfNodes;
|
|
2768
|
+
activeCandidateStraightLineDistance;
|
|
2769
|
+
hyperParameters;
|
|
2770
|
+
constructor({
|
|
2771
|
+
simpleRouteJson,
|
|
2772
|
+
nodes,
|
|
2773
|
+
edges,
|
|
2774
|
+
colorMap,
|
|
2775
|
+
MAX_ITERATIONS = 1e6,
|
|
2776
|
+
hyperParameters = {}
|
|
2777
|
+
}) {
|
|
2778
|
+
super();
|
|
2779
|
+
this.MAX_ITERATIONS = MAX_ITERATIONS;
|
|
2780
|
+
this.simpleRouteJson = simpleRouteJson;
|
|
2781
|
+
this.nodes = nodes;
|
|
2782
|
+
this.edges = edges;
|
|
2783
|
+
this.colorMap = colorMap ?? {};
|
|
2784
|
+
const { connectionsWithNodes, connectionNameToGoalNodeIds } = this.getConnectionsWithNodes();
|
|
2785
|
+
this.connectionsWithNodes = connectionsWithNodes;
|
|
2786
|
+
this.connectionNameToGoalNodeIds = connectionNameToGoalNodeIds;
|
|
2787
|
+
this.hyperParameters = hyperParameters;
|
|
2788
|
+
this.usedNodeCapacityMap = new Map(
|
|
2789
|
+
this.nodes.map((node) => [node.capacityMeshNodeId, 0])
|
|
2790
|
+
);
|
|
2791
|
+
this.nodeMap = new Map(
|
|
2792
|
+
this.nodes.map((node) => [node.capacityMeshNodeId, node])
|
|
2793
|
+
);
|
|
2794
|
+
this.nodeEdgeMap = getNodeEdgeMap(this.edges);
|
|
2795
|
+
this.maxDepthOfNodes = Math.max(
|
|
2796
|
+
...this.nodes.map((node) => node._depth ?? 0)
|
|
2797
|
+
);
|
|
2798
|
+
}
|
|
2799
|
+
getTotalCapacity(node) {
|
|
2800
|
+
const depth = node._depth ?? 0;
|
|
2801
|
+
return (this.maxDepthOfNodes - depth + 1) ** 2;
|
|
2802
|
+
}
|
|
2803
|
+
getConnectionsWithNodes() {
|
|
2804
|
+
const connectionsWithNodes = [];
|
|
2805
|
+
const nodesWithTargets = this.nodes.filter((node) => node._containsTarget);
|
|
2806
|
+
const connectionNameToGoalNodeIds = /* @__PURE__ */ new Map();
|
|
2807
|
+
for (const connection of this.simpleRouteJson.connections) {
|
|
2808
|
+
const nodesForConnection = [];
|
|
2809
|
+
for (const point of connection.pointsToConnect) {
|
|
2810
|
+
let closestNode = this.nodes[0];
|
|
2811
|
+
let minDistance = Number.MAX_VALUE;
|
|
2812
|
+
for (const node of nodesWithTargets) {
|
|
2813
|
+
const distance3 = Math.sqrt(
|
|
2814
|
+
(node.center.x - point.x) ** 2 + (node.center.y - point.y) ** 2
|
|
2815
|
+
);
|
|
2816
|
+
if (distance3 < minDistance) {
|
|
2817
|
+
minDistance = distance3;
|
|
2818
|
+
closestNode = node;
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
nodesForConnection.push(closestNode);
|
|
2822
|
+
}
|
|
2823
|
+
if (nodesForConnection.length < 2) {
|
|
2824
|
+
throw new Error(
|
|
2825
|
+
`Not enough nodes for connection "${connection.name}", only ${nodesForConnection.length} found`
|
|
2826
|
+
);
|
|
2827
|
+
}
|
|
2828
|
+
connectionNameToGoalNodeIds.set(
|
|
2829
|
+
connection.name,
|
|
2830
|
+
nodesForConnection.map((n) => n.capacityMeshNodeId)
|
|
2831
|
+
);
|
|
2832
|
+
connectionsWithNodes.push({
|
|
2833
|
+
connection,
|
|
2834
|
+
nodes: nodesForConnection,
|
|
2835
|
+
pathFound: false
|
|
2836
|
+
});
|
|
2837
|
+
}
|
|
2838
|
+
return { connectionsWithNodes, connectionNameToGoalNodeIds };
|
|
2839
|
+
}
|
|
2840
|
+
currentConnectionIndex = 0;
|
|
2841
|
+
candidates;
|
|
2842
|
+
visitedNodes;
|
|
2843
|
+
computeG(prevCandidate, node, endGoal) {
|
|
2844
|
+
return prevCandidate.g + this.getDistanceBetweenNodes(prevCandidate.node, node);
|
|
2845
|
+
}
|
|
2846
|
+
computeH(prevCandidate, node, endGoal) {
|
|
2847
|
+
return this.getDistanceBetweenNodes(node, endGoal);
|
|
2848
|
+
}
|
|
2849
|
+
getBacktrackedPath(candidate) {
|
|
2850
|
+
const path = [];
|
|
2851
|
+
let currentCandidate = candidate;
|
|
2852
|
+
while (currentCandidate) {
|
|
2853
|
+
path.push(currentCandidate.node);
|
|
2854
|
+
currentCandidate = currentCandidate.prevCandidate;
|
|
2855
|
+
}
|
|
2856
|
+
return path;
|
|
2857
|
+
}
|
|
2858
|
+
getNeighboringNodes(node) {
|
|
2859
|
+
return this.nodeEdgeMap.get(node.capacityMeshNodeId).flatMap(
|
|
2860
|
+
(edge) => edge.nodeIds.filter((n) => n !== node.capacityMeshNodeId)
|
|
2861
|
+
).map((n) => this.nodeMap.get(n));
|
|
2862
|
+
}
|
|
2863
|
+
getCapacityPaths() {
|
|
2864
|
+
const capacityPaths = [];
|
|
2865
|
+
for (const connection of this.connectionsWithNodes) {
|
|
2866
|
+
const path = connection.path;
|
|
2867
|
+
if (path) {
|
|
2868
|
+
capacityPaths.push({
|
|
2869
|
+
capacityPathId: connection.connection.name,
|
|
2870
|
+
connectionName: connection.connection.name,
|
|
2871
|
+
nodeIds: path.map((node) => node.capacityMeshNodeId)
|
|
2872
|
+
});
|
|
2873
|
+
}
|
|
2874
|
+
}
|
|
2875
|
+
return capacityPaths;
|
|
2876
|
+
}
|
|
2877
|
+
doesNodeHaveCapacityForTrace(node) {
|
|
2878
|
+
const usedCapacity = this.usedNodeCapacityMap.get(node.capacityMeshNodeId) ?? 0;
|
|
2879
|
+
const totalCapacity = this.getTotalCapacity(node);
|
|
2880
|
+
return usedCapacity < totalCapacity;
|
|
2881
|
+
}
|
|
2882
|
+
canTravelThroughObstacle(node, connectionName) {
|
|
2883
|
+
const goalNodeIds = this.connectionNameToGoalNodeIds.get(connectionName);
|
|
2884
|
+
return goalNodeIds?.includes(node.capacityMeshNodeId) ?? false;
|
|
2885
|
+
}
|
|
2886
|
+
getDistanceBetweenNodes(A, B) {
|
|
2887
|
+
return Math.sqrt(
|
|
2888
|
+
(A.center.x - B.center.x) ** 2 + (A.center.y - B.center.y) ** 2
|
|
2889
|
+
);
|
|
2890
|
+
}
|
|
2891
|
+
reduceCapacityAlongPath(nextConnection) {
|
|
2892
|
+
for (const node of nextConnection.path ?? []) {
|
|
2893
|
+
this.usedNodeCapacityMap.set(
|
|
2894
|
+
node.capacityMeshNodeId,
|
|
2895
|
+
this.usedNodeCapacityMap.get(node.capacityMeshNodeId) + 1
|
|
2896
|
+
);
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
_step() {
|
|
2900
|
+
const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
|
|
2901
|
+
if (!nextConnection) {
|
|
2902
|
+
this.solved = true;
|
|
2903
|
+
return;
|
|
2904
|
+
}
|
|
2905
|
+
const [start, end] = nextConnection.nodes;
|
|
2906
|
+
if (!this.candidates) {
|
|
2907
|
+
this.candidates = [{ prevCandidate: null, node: start, f: 0, g: 0, h: 0 }];
|
|
2908
|
+
this.visitedNodes = /* @__PURE__ */ new Set([start.capacityMeshNodeId]);
|
|
2909
|
+
this.activeCandidateStraightLineDistance = distance(
|
|
2910
|
+
start.center,
|
|
2911
|
+
end.center
|
|
2912
|
+
);
|
|
2913
|
+
}
|
|
2914
|
+
this.candidates.sort((a, b) => a.f - b.f);
|
|
2915
|
+
const currentCandidate = this.candidates.shift();
|
|
2916
|
+
if (!currentCandidate) {
|
|
2917
|
+
console.error(
|
|
2918
|
+
`Ran out of candidates on connection ${nextConnection.connection.name}`
|
|
2919
|
+
);
|
|
2920
|
+
this.currentConnectionIndex++;
|
|
2921
|
+
this.candidates = null;
|
|
2922
|
+
this.visitedNodes = null;
|
|
2923
|
+
return;
|
|
2924
|
+
}
|
|
2925
|
+
if (currentCandidate.node.capacityMeshNodeId === end.capacityMeshNodeId) {
|
|
2926
|
+
nextConnection.path = this.getBacktrackedPath(currentCandidate);
|
|
2927
|
+
this.reduceCapacityAlongPath(nextConnection);
|
|
2928
|
+
this.currentConnectionIndex++;
|
|
2929
|
+
this.candidates = null;
|
|
2930
|
+
this.visitedNodes = null;
|
|
2931
|
+
return;
|
|
2932
|
+
}
|
|
2933
|
+
const neighborNodes = this.getNeighboringNodes(currentCandidate.node);
|
|
2934
|
+
for (const neighborNode of neighborNodes) {
|
|
2935
|
+
if (this.visitedNodes?.has(neighborNode.capacityMeshNodeId)) {
|
|
2936
|
+
continue;
|
|
2937
|
+
}
|
|
2938
|
+
if (!this.doesNodeHaveCapacityForTrace(neighborNode)) {
|
|
2939
|
+
continue;
|
|
2940
|
+
}
|
|
2941
|
+
const connectionName = this.connectionsWithNodes[this.currentConnectionIndex].connection.name;
|
|
2942
|
+
if (neighborNode._containsObstacle && !this.canTravelThroughObstacle(neighborNode, connectionName)) {
|
|
2943
|
+
continue;
|
|
2944
|
+
}
|
|
2945
|
+
const g = this.computeG(currentCandidate, neighborNode, end);
|
|
2946
|
+
const h = this.computeH(currentCandidate, neighborNode, end);
|
|
2947
|
+
const f = g + h * this.GREEDY_MULTIPLIER;
|
|
2948
|
+
const newCandidate = {
|
|
2949
|
+
prevCandidate: currentCandidate,
|
|
2950
|
+
node: neighborNode,
|
|
2951
|
+
f,
|
|
2952
|
+
g,
|
|
2953
|
+
h
|
|
2954
|
+
};
|
|
2955
|
+
this.candidates.push(newCandidate);
|
|
2956
|
+
}
|
|
2957
|
+
this.visitedNodes.add(currentCandidate.node.capacityMeshNodeId);
|
|
2958
|
+
}
|
|
2959
|
+
visualize() {
|
|
2960
|
+
const graphics = {
|
|
2961
|
+
lines: [],
|
|
2962
|
+
points: [],
|
|
2963
|
+
rects: [],
|
|
2964
|
+
circles: []
|
|
2965
|
+
};
|
|
2966
|
+
if (this.connectionsWithNodes) {
|
|
2967
|
+
for (let i = 0; i < this.connectionsWithNodes.length; i++) {
|
|
2968
|
+
const conn = this.connectionsWithNodes[i];
|
|
2969
|
+
if (conn.path && conn.path.length > 0) {
|
|
2970
|
+
const pathPoints = conn.path.map(({ center: { x, y }, width }) => ({
|
|
2971
|
+
// slight offset to allow viewing overlapping paths
|
|
2972
|
+
x: x + (i % 10 + i % 19) * (0.01 * width),
|
|
2973
|
+
y: y + (i % 10 + i % 19) * (0.01 * width)
|
|
2974
|
+
}));
|
|
2975
|
+
graphics.lines.push({
|
|
2976
|
+
points: pathPoints,
|
|
2977
|
+
strokeColor: this.colorMap[conn.connection.name]
|
|
2978
|
+
});
|
|
2979
|
+
}
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
for (const node of this.nodes) {
|
|
2983
|
+
graphics.rects.push({
|
|
2984
|
+
center: node.center,
|
|
2985
|
+
width: Math.max(node.width - 2, node.width * 0.8),
|
|
2986
|
+
height: Math.max(node.height - 2, node.height * 0.8),
|
|
2987
|
+
fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : "rgba(0,0,0,0.1)",
|
|
2988
|
+
label: `${node.capacityMeshNodeId}
|
|
2989
|
+
${this.usedNodeCapacityMap.get(node.capacityMeshNodeId)}/${this.getTotalCapacity(node).toFixed(2)}
|
|
2990
|
+
${node.width.toFixed(2)}x${node.height.toFixed(2)}`
|
|
2991
|
+
});
|
|
2992
|
+
}
|
|
2993
|
+
if (this.connectionsWithNodes) {
|
|
2994
|
+
for (const conn of this.connectionsWithNodes) {
|
|
2995
|
+
if (conn.connection?.pointsToConnect) {
|
|
2996
|
+
for (const point of conn.connection.pointsToConnect) {
|
|
2997
|
+
graphics.points.push({
|
|
2998
|
+
x: point.x,
|
|
2999
|
+
y: point.y
|
|
3000
|
+
});
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
if (this.candidates) {
|
|
3006
|
+
const topCandidates = this.candidates.slice(0, 50);
|
|
3007
|
+
const connectionName = this.connectionsWithNodes[this.currentConnectionIndex].connection.name;
|
|
3008
|
+
topCandidates.forEach((candidate, index) => {
|
|
3009
|
+
const opacity = 0.05 * (1 - index / 50);
|
|
3010
|
+
const backtrackedPath = this.getBacktrackedPath(candidate);
|
|
3011
|
+
graphics.lines.push({
|
|
3012
|
+
points: backtrackedPath.map(({ center: { x, y } }) => ({ x, y })),
|
|
3013
|
+
strokeColor: safeTransparentize(
|
|
3014
|
+
this.colorMap[connectionName] ?? "red",
|
|
3015
|
+
1 - opacity
|
|
3016
|
+
),
|
|
3017
|
+
strokeWidth: 0.5
|
|
3018
|
+
});
|
|
3019
|
+
});
|
|
3020
|
+
}
|
|
3021
|
+
return graphics;
|
|
3022
|
+
}
|
|
3023
|
+
};
|
|
3024
|
+
|
|
3025
|
+
// lib/utils/getTunedTotalCapacity1.ts
|
|
3026
|
+
var getTunedTotalCapacity1 = (nodeOrWidth, maxCapacityFactor = 1) => {
|
|
3027
|
+
const VIA_DIAMETER = 0.6;
|
|
3028
|
+
const TRACE_WIDTH = 0.15;
|
|
3029
|
+
const obstacleMargin = 0.2;
|
|
3030
|
+
const width = "width" in nodeOrWidth ? nodeOrWidth.width : nodeOrWidth;
|
|
3031
|
+
const viaLengthAcross = width / (VIA_DIAMETER / 2 + obstacleMargin);
|
|
3032
|
+
return (viaLengthAcross / 2) ** 1.1 * maxCapacityFactor;
|
|
3033
|
+
};
|
|
3034
|
+
var calculateOptimalCapacityDepth = (initialWidth, targetMinCapacity = 0.5, maxDepth = 16) => {
|
|
3035
|
+
let depth = 0;
|
|
3036
|
+
let width = initialWidth;
|
|
3037
|
+
while (depth < maxDepth) {
|
|
3038
|
+
const capacity = getTunedTotalCapacity1({ width });
|
|
3039
|
+
if (capacity <= targetMinCapacity) {
|
|
3040
|
+
break;
|
|
3041
|
+
}
|
|
3042
|
+
width /= 2;
|
|
3043
|
+
depth++;
|
|
3044
|
+
}
|
|
3045
|
+
return Math.max(1, depth);
|
|
3046
|
+
};
|
|
3047
|
+
|
|
3048
|
+
// lib/solvers/CapacityPathingSolver/CapacityPathingSolver4_FlexibleNegativeCapacity_AvoidLowCapacity_FixedDistanceCost.ts
|
|
3049
|
+
var CapacityPathingSolver4_FlexibleNegativeCapacity = class extends CapacityPathingSolver {
|
|
3050
|
+
NEGATIVE_CAPACITY_PENALTY_FACTOR = 1;
|
|
3051
|
+
REDUCED_CAPACITY_PENALTY_FACTOR = 1;
|
|
3052
|
+
get maxCapacityFactor() {
|
|
3053
|
+
return this.hyperParameters.MAX_CAPACITY_FACTOR ?? 1;
|
|
3054
|
+
}
|
|
3055
|
+
/**
|
|
3056
|
+
* In the FlexibleNegativeCapacity mode, we allow negative capacity
|
|
3057
|
+
*/
|
|
3058
|
+
doesNodeHaveCapacityForTrace(node) {
|
|
3059
|
+
return true;
|
|
3060
|
+
}
|
|
3061
|
+
getTotalCapacity(node) {
|
|
3062
|
+
return getTunedTotalCapacity1(node, this.maxCapacityFactor);
|
|
3063
|
+
}
|
|
3064
|
+
/**
|
|
3065
|
+
* Penalty you pay for using this node
|
|
3066
|
+
*/
|
|
3067
|
+
getNodeCapacityPenalty(node) {
|
|
3068
|
+
const totalCapacity = this.getTotalCapacity(node);
|
|
3069
|
+
const usedCapacity = this.usedNodeCapacityMap.get(node.capacityMeshNodeId) ?? 0;
|
|
3070
|
+
const remainingCapacity = totalCapacity - usedCapacity;
|
|
3071
|
+
const dist = this.activeCandidateStraightLineDistance;
|
|
3072
|
+
if (remainingCapacity <= 0) {
|
|
3073
|
+
const penalty = (-remainingCapacity + 1) / totalCapacity * dist * (this.NEGATIVE_CAPACITY_PENALTY_FACTOR / 4);
|
|
3074
|
+
return penalty ** 2;
|
|
3075
|
+
}
|
|
3076
|
+
return 1 / remainingCapacity * dist * this.REDUCED_CAPACITY_PENALTY_FACTOR / 8;
|
|
3077
|
+
}
|
|
3078
|
+
/**
|
|
3079
|
+
* We're rewarding travel into big nodes.
|
|
3080
|
+
*
|
|
3081
|
+
* To minimize shortest path, you'd want to comment this out.
|
|
3082
|
+
*/
|
|
3083
|
+
getDistanceBetweenNodes(A, B) {
|
|
3084
|
+
const dx = A.center.x - B.center.x;
|
|
3085
|
+
const dy = A.center.y - B.center.y;
|
|
3086
|
+
const szx = Math.max(A.width, B.width);
|
|
3087
|
+
const szy = Math.max(A.height, B.height);
|
|
3088
|
+
const dist = Math.sqrt(dx ** 2 + dy ** 2) / (szx * szy);
|
|
3089
|
+
return dist;
|
|
3090
|
+
}
|
|
3091
|
+
computeG(prevCandidate, node, endGoal) {
|
|
3092
|
+
return prevCandidate.g + this.getDistanceBetweenNodes(prevCandidate.node, node) + this.getNodeCapacityPenalty(node);
|
|
3093
|
+
}
|
|
3094
|
+
computeH(prevCandidate, node, endGoal) {
|
|
3095
|
+
return this.getDistanceBetweenNodes(node, endGoal) + this.getNodeCapacityPenalty(node);
|
|
3096
|
+
}
|
|
3097
|
+
};
|
|
3098
|
+
|
|
3099
|
+
// node_modules/circuit-json-to-connectivity-map/dist/index.js
|
|
3100
|
+
var ConnectivityMap = class {
|
|
3101
|
+
netMap;
|
|
3102
|
+
idToNetMap;
|
|
3103
|
+
constructor(netMap) {
|
|
3104
|
+
this.netMap = netMap;
|
|
3105
|
+
this.idToNetMap = {};
|
|
3106
|
+
for (const [netId, ids] of Object.entries(netMap)) {
|
|
3107
|
+
for (const id of ids) {
|
|
3108
|
+
this.idToNetMap[id] = netId;
|
|
3109
|
+
}
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
addConnections(connections) {
|
|
3113
|
+
for (const connection of connections) {
|
|
3114
|
+
const existingNets = /* @__PURE__ */ new Set();
|
|
3115
|
+
for (const id of connection) {
|
|
3116
|
+
const existingNetId = this.idToNetMap[id];
|
|
3117
|
+
if (existingNetId) {
|
|
3118
|
+
existingNets.add(existingNetId);
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
let targetNetId;
|
|
3122
|
+
if (existingNets.size === 0) {
|
|
3123
|
+
targetNetId = `connectivity_net${Object.keys(this.netMap).length}`;
|
|
3124
|
+
this.netMap[targetNetId] = [];
|
|
3125
|
+
} else if (existingNets.size === 1) {
|
|
3126
|
+
targetNetId = existingNets.values().next().value ?? `connectivity_net${Object.keys(this.netMap).length}`;
|
|
3127
|
+
} else {
|
|
3128
|
+
targetNetId = existingNets.values().next().value ?? `connectivity_net${Object.keys(this.netMap).length}`;
|
|
3129
|
+
for (const netId of existingNets) {
|
|
3130
|
+
if (netId !== targetNetId) {
|
|
3131
|
+
this.netMap[targetNetId].push(...this.netMap[netId]);
|
|
3132
|
+
this.netMap[netId] = this.netMap[targetNetId];
|
|
3133
|
+
for (const id of this.netMap[targetNetId]) {
|
|
3134
|
+
this.idToNetMap[id] = targetNetId;
|
|
3135
|
+
}
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
for (const id of connection) {
|
|
3140
|
+
if (!this.netMap[targetNetId].includes(id)) {
|
|
3141
|
+
this.netMap[targetNetId].push(id);
|
|
3142
|
+
}
|
|
3143
|
+
this.idToNetMap[id] = targetNetId;
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
getIdsConnectedToNet(netId) {
|
|
3148
|
+
return this.netMap[netId] || [];
|
|
3149
|
+
}
|
|
3150
|
+
getNetConnectedToId(id) {
|
|
3151
|
+
return this.idToNetMap[id];
|
|
3152
|
+
}
|
|
3153
|
+
areIdsConnected(id1, id2) {
|
|
3154
|
+
if (id1 === id2) return true;
|
|
3155
|
+
const netId1 = this.getNetConnectedToId(id1);
|
|
3156
|
+
if (!netId1) return false;
|
|
3157
|
+
const netId2 = this.getNetConnectedToId(id2);
|
|
3158
|
+
if (!netId2) return false;
|
|
3159
|
+
return netId1 === netId2 || netId2 === id1 || netId2 === id1;
|
|
3160
|
+
}
|
|
3161
|
+
areAllIdsConnected(ids) {
|
|
3162
|
+
const netId = this.getNetConnectedToId(ids[0]);
|
|
3163
|
+
for (const id of ids) {
|
|
3164
|
+
const nextNetId = this.getNetConnectedToId(id);
|
|
3165
|
+
if (nextNetId === void 0) {
|
|
3166
|
+
return false;
|
|
3167
|
+
}
|
|
3168
|
+
if (nextNetId !== netId) {
|
|
3169
|
+
return false;
|
|
3170
|
+
}
|
|
3171
|
+
}
|
|
3172
|
+
return true;
|
|
3173
|
+
}
|
|
3174
|
+
};
|
|
3175
|
+
|
|
3176
|
+
// lib/utils/getConnectivityMapFromSimpleRouteJson.ts
|
|
3177
|
+
var getConnectivityMapFromSimpleRouteJson = (srj) => {
|
|
3178
|
+
const connMap = new ConnectivityMap({});
|
|
3179
|
+
for (const connection of srj.connections) {
|
|
3180
|
+
for (const point of connection.pointsToConnect) {
|
|
3181
|
+
if ("pcb_port_id" in point && point.pcb_port_id) {
|
|
3182
|
+
connMap.addConnections([[connection.name, point.pcb_port_id]]);
|
|
3183
|
+
}
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
return connMap;
|
|
3187
|
+
};
|
|
3188
|
+
|
|
3189
|
+
// lib/solvers/CapacityMeshSolver/CapacityNodeTargetMerger.ts
|
|
3190
|
+
var CapacityNodeTargetMerger = class extends BaseSolver {
|
|
3191
|
+
constructor(nodes, obstacles, connMap) {
|
|
3192
|
+
super();
|
|
3193
|
+
this.nodes = nodes;
|
|
3194
|
+
this.connMap = connMap;
|
|
3195
|
+
this.MAX_ITERATIONS = 1e5;
|
|
3196
|
+
this.unprocessedObstacles = [...obstacles];
|
|
3197
|
+
this.newNodes = [];
|
|
3198
|
+
this.removedNodeIds = /* @__PURE__ */ new Set();
|
|
3199
|
+
}
|
|
3200
|
+
unprocessedObstacles;
|
|
3201
|
+
newNodes;
|
|
3202
|
+
removedNodeIds;
|
|
3203
|
+
_step() {
|
|
3204
|
+
const obstacle = this.unprocessedObstacles.pop();
|
|
3205
|
+
if (!obstacle) {
|
|
3206
|
+
for (const node of this.nodes) {
|
|
3207
|
+
if (this.removedNodeIds.has(node.capacityMeshNodeId)) continue;
|
|
3208
|
+
this.newNodes.push(node);
|
|
3209
|
+
}
|
|
3210
|
+
this.solved = true;
|
|
3211
|
+
return;
|
|
3212
|
+
}
|
|
3213
|
+
const connectedNodes = this.nodes.filter((n) => {
|
|
3214
|
+
if (!n._targetConnectionName) return false;
|
|
3215
|
+
const implicitlyConnected = doRectsOverlap(n, obstacle);
|
|
3216
|
+
return implicitlyConnected;
|
|
3217
|
+
});
|
|
3218
|
+
if (connectedNodes.length === 0) return;
|
|
3219
|
+
const connectionName = connectedNodes[0]._targetConnectionName;
|
|
3220
|
+
const bounds = {
|
|
3221
|
+
minX: Infinity,
|
|
3222
|
+
minY: Infinity,
|
|
3223
|
+
maxX: -Infinity,
|
|
3224
|
+
maxY: -Infinity
|
|
3225
|
+
};
|
|
3226
|
+
for (const node of connectedNodes) {
|
|
3227
|
+
bounds.minX = Math.min(bounds.minX, node.center.x - node.width / 2);
|
|
3228
|
+
bounds.minY = Math.min(bounds.minY, node.center.y - node.height / 2);
|
|
3229
|
+
bounds.maxX = Math.max(bounds.maxX, node.center.x + node.width / 2);
|
|
3230
|
+
bounds.maxY = Math.max(bounds.maxY, node.center.y + node.height / 2);
|
|
3231
|
+
}
|
|
3232
|
+
const newNode = {
|
|
3233
|
+
capacityMeshNodeId: connectedNodes[0].capacityMeshNodeId,
|
|
3234
|
+
center: {
|
|
3235
|
+
x: (bounds.minX + bounds.maxX) / 2,
|
|
3236
|
+
y: (bounds.minY + bounds.maxY) / 2
|
|
3237
|
+
},
|
|
3238
|
+
width: bounds.maxX - bounds.minX,
|
|
3239
|
+
height: bounds.maxY - bounds.minY,
|
|
3240
|
+
layer: connectedNodes[0].layer,
|
|
3241
|
+
_completelyInsideObstacle: false,
|
|
3242
|
+
_containsObstacle: true,
|
|
3243
|
+
_containsTarget: true,
|
|
3244
|
+
_targetConnectionName: connectionName,
|
|
3245
|
+
_depth: connectedNodes[0]._depth,
|
|
3246
|
+
_parent: connectedNodes[0]._parent
|
|
3247
|
+
};
|
|
3248
|
+
this.newNodes.push(newNode);
|
|
3249
|
+
for (const node of connectedNodes) {
|
|
3250
|
+
this.removedNodeIds.add(node.capacityMeshNodeId);
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
visualize() {
|
|
3254
|
+
const graphics = {
|
|
3255
|
+
rects: []
|
|
3256
|
+
};
|
|
3257
|
+
for (const node of this.newNodes) {
|
|
3258
|
+
graphics.rects.push({
|
|
3259
|
+
center: node.center,
|
|
3260
|
+
width: Math.max(node.width - 2, node.width * 0.8),
|
|
3261
|
+
height: Math.max(node.height - 2, node.height * 0.8),
|
|
3262
|
+
fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : "rgba(0,0,0,0.1)",
|
|
3263
|
+
label: node.capacityMeshNodeId
|
|
3264
|
+
});
|
|
3265
|
+
}
|
|
3266
|
+
return graphics;
|
|
3267
|
+
}
|
|
3268
|
+
};
|
|
3269
|
+
|
|
3270
|
+
// lib/utils/getIntraNodeCrossingsFromSegments.ts
|
|
3271
|
+
var getIntraNodeCrossingsFromSegments = (segments) => {
|
|
3272
|
+
let numSameLayerCrossings = 0;
|
|
3273
|
+
const pointPairs = [];
|
|
3274
|
+
const transitionPairPoints = [];
|
|
3275
|
+
let numEntryExitLayerChanges = 0;
|
|
3276
|
+
const portPoints = segments.flatMap((seg) => seg.assignedPoints);
|
|
3277
|
+
for (const { connectionName: aConnName, point: A } of portPoints) {
|
|
3278
|
+
if (pointPairs.some((p) => p.connectionName === aConnName)) {
|
|
3279
|
+
continue;
|
|
3280
|
+
}
|
|
3281
|
+
if (transitionPairPoints.some((p) => p.connectionName === aConnName)) {
|
|
3282
|
+
continue;
|
|
3283
|
+
}
|
|
3284
|
+
const pointPair = {
|
|
3285
|
+
connectionName: aConnName,
|
|
3286
|
+
z: A.z,
|
|
3287
|
+
points: [A]
|
|
3288
|
+
};
|
|
3289
|
+
for (const { connectionName: bConnName, point: B } of portPoints) {
|
|
3290
|
+
if (aConnName !== bConnName) continue;
|
|
3291
|
+
if (A === B) continue;
|
|
3292
|
+
pointPair.points.push(B);
|
|
3293
|
+
}
|
|
3294
|
+
if (pointPair.points.some((p) => p.z !== pointPair.z)) {
|
|
3295
|
+
numEntryExitLayerChanges++;
|
|
3296
|
+
transitionPairPoints.push(pointPair);
|
|
3297
|
+
continue;
|
|
3298
|
+
}
|
|
3299
|
+
pointPairs.push(pointPair);
|
|
3300
|
+
}
|
|
3301
|
+
for (let i = 0; i < pointPairs.length; i++) {
|
|
3302
|
+
for (let j = i + 1; j < pointPairs.length; j++) {
|
|
3303
|
+
const pair1 = pointPairs[i];
|
|
3304
|
+
const pair2 = pointPairs[j];
|
|
3305
|
+
if (pair1.z === pair2.z && doSegmentsIntersect(
|
|
3306
|
+
pair1.points[0],
|
|
3307
|
+
pair1.points[1],
|
|
3308
|
+
pair2.points[0],
|
|
3309
|
+
pair2.points[1]
|
|
3310
|
+
)) {
|
|
3311
|
+
numSameLayerCrossings++;
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
let numTransitionCrossings = 0;
|
|
3316
|
+
for (let i = 0; i < transitionPairPoints.length; i++) {
|
|
3317
|
+
for (let j = i + 1; j < transitionPairPoints.length; j++) {
|
|
3318
|
+
const pair1 = transitionPairPoints[i];
|
|
3319
|
+
const pair2 = transitionPairPoints[j];
|
|
3320
|
+
if (doSegmentsIntersect(
|
|
3321
|
+
pair1.points[0],
|
|
3322
|
+
pair1.points[1],
|
|
3323
|
+
pair2.points[0],
|
|
3324
|
+
pair2.points[1]
|
|
3325
|
+
)) {
|
|
3326
|
+
numTransitionCrossings++;
|
|
3327
|
+
}
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
3330
|
+
for (let i = 0; i < transitionPairPoints.length; i++) {
|
|
3331
|
+
for (let j = 0; j < pointPairs.length; j++) {
|
|
3332
|
+
const pair1 = transitionPairPoints[i];
|
|
3333
|
+
const pair2 = pointPairs[j];
|
|
3334
|
+
if (doSegmentsIntersect(
|
|
3335
|
+
pair1.points[0],
|
|
3336
|
+
pair1.points[1],
|
|
3337
|
+
pair2.points[0],
|
|
3338
|
+
pair2.points[1]
|
|
3339
|
+
)) {
|
|
3340
|
+
numTransitionCrossings++;
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
return {
|
|
3345
|
+
numSameLayerCrossings,
|
|
3346
|
+
numEntryExitLayerChanges,
|
|
3347
|
+
numTransitionCrossings
|
|
3348
|
+
};
|
|
3349
|
+
};
|
|
3350
|
+
|
|
3351
|
+
// lib/solvers/CapacitySegmentPointOptimizer/CapacitySegmentPointOptimizer.ts
|
|
3352
|
+
var CapacitySegmentPointOptimizer = class extends BaseSolver {
|
|
3353
|
+
assignedSegments;
|
|
3354
|
+
colorMap;
|
|
3355
|
+
nodeMap;
|
|
3356
|
+
nodeIdToSegmentIds;
|
|
3357
|
+
segmentIdToNodeIds;
|
|
3358
|
+
currentMutatedSegments;
|
|
3359
|
+
allSegmentIds;
|
|
3360
|
+
lastAppliedOperation = null;
|
|
3361
|
+
lastCreatedOperation = null;
|
|
3362
|
+
currentNodeCosts;
|
|
3363
|
+
lastAcceptedIteration = 0;
|
|
3364
|
+
currentCost;
|
|
3365
|
+
randomSeed;
|
|
3366
|
+
numNodes;
|
|
3367
|
+
probabilityOfFailure;
|
|
3368
|
+
nodesThatCantFitVias;
|
|
3369
|
+
mutableSegments;
|
|
3370
|
+
VIA_DIAMETER = 0.6;
|
|
3371
|
+
OBSTACLE_MARGIN = 0.15;
|
|
3372
|
+
MAX_OPERATIONS_PER_MUTATION = 5;
|
|
3373
|
+
MAX_NODE_CHAIN_PER_MUTATION = 2;
|
|
3374
|
+
NOOP_ITERATIONS_BEFORE_EARLY_STOP = 2e4;
|
|
3375
|
+
// We use an extra property on segments to remember assigned points.
|
|
3376
|
+
// Each segment will get an added property "assignedPoints" which is an array of:
|
|
3377
|
+
// { connectionName: string, point: {x: number, y: number } }
|
|
3378
|
+
// This is a temporary extension used by the solver.
|
|
3379
|
+
constructor({
|
|
3380
|
+
assignedSegments,
|
|
3381
|
+
colorMap,
|
|
3382
|
+
nodes
|
|
3383
|
+
}) {
|
|
3384
|
+
super();
|
|
3385
|
+
this.MAX_ITERATIONS = 5e5;
|
|
3386
|
+
this.assignedSegments = assignedSegments;
|
|
3387
|
+
const dedupedSegments = [];
|
|
3388
|
+
const dedupedSegPointMap = /* @__PURE__ */ new Map();
|
|
3389
|
+
let highestSegmentId = -1;
|
|
3390
|
+
for (const seg of this.assignedSegments) {
|
|
3391
|
+
const segKey = `${seg.start.x}-${seg.start.y}-${seg.end.x}-${seg.end.y}`;
|
|
3392
|
+
const existingSeg = dedupedSegPointMap.get(segKey);
|
|
3393
|
+
if (!existingSeg) {
|
|
3394
|
+
highestSegmentId++;
|
|
3395
|
+
seg.nodePortSegmentId = `SEG${highestSegmentId}`;
|
|
3396
|
+
dedupedSegPointMap.set(segKey, seg);
|
|
3397
|
+
dedupedSegments.push(seg);
|
|
3398
|
+
continue;
|
|
3399
|
+
}
|
|
3400
|
+
seg.nodePortSegmentId = existingSeg.nodePortSegmentId;
|
|
3401
|
+
}
|
|
3402
|
+
this.currentMutatedSegments = /* @__PURE__ */ new Map();
|
|
3403
|
+
for (const seg of dedupedSegments) {
|
|
3404
|
+
this.currentMutatedSegments.set(seg.nodePortSegmentId, {
|
|
3405
|
+
...seg,
|
|
3406
|
+
assignedPoints: seg.assignedPoints?.map((p) => ({
|
|
3407
|
+
...p,
|
|
3408
|
+
point: { x: p.point.x, y: p.point.y, z: p.point.z }
|
|
3409
|
+
}))
|
|
3410
|
+
});
|
|
3411
|
+
}
|
|
3412
|
+
this.nodeIdToSegmentIds = /* @__PURE__ */ new Map();
|
|
3413
|
+
this.segmentIdToNodeIds = /* @__PURE__ */ new Map();
|
|
3414
|
+
for (const segment of this.assignedSegments) {
|
|
3415
|
+
this.segmentIdToNodeIds.set(segment.nodePortSegmentId, [
|
|
3416
|
+
...this.segmentIdToNodeIds.get(segment.nodePortSegmentId) ?? [],
|
|
3417
|
+
segment.capacityMeshNodeId
|
|
3418
|
+
]);
|
|
3419
|
+
this.nodeIdToSegmentIds.set(segment.capacityMeshNodeId, [
|
|
3420
|
+
...this.nodeIdToSegmentIds.get(segment.capacityMeshNodeId) ?? [],
|
|
3421
|
+
segment.nodePortSegmentId
|
|
3422
|
+
]);
|
|
3423
|
+
}
|
|
3424
|
+
this.colorMap = colorMap ?? {};
|
|
3425
|
+
this.nodeMap = /* @__PURE__ */ new Map();
|
|
3426
|
+
for (const node of nodes) {
|
|
3427
|
+
this.nodeMap.set(node.capacityMeshNodeId, node);
|
|
3428
|
+
}
|
|
3429
|
+
this.numNodes = this.segmentIdToNodeIds.size;
|
|
3430
|
+
const { cost, nodeCosts, probabilityOfFailure } = this.computeCurrentCost();
|
|
3431
|
+
this.currentCost = cost;
|
|
3432
|
+
this.currentNodeCosts = nodeCosts;
|
|
3433
|
+
this.probabilityOfFailure = probabilityOfFailure;
|
|
3434
|
+
this.randomSeed = 1;
|
|
3435
|
+
this.allSegmentIds = Array.from(this.currentMutatedSegments.keys());
|
|
3436
|
+
this.nodesThatCantFitVias = /* @__PURE__ */ new Set();
|
|
3437
|
+
for (const nodeId of this.nodeIdToSegmentIds.keys()) {
|
|
3438
|
+
const node = this.nodeMap.get(nodeId);
|
|
3439
|
+
if (node.width < this.VIA_DIAMETER + this.OBSTACLE_MARGIN) {
|
|
3440
|
+
this.nodesThatCantFitVias.add(nodeId);
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
this.mutableSegments = this.getMutableSegments();
|
|
3444
|
+
}
|
|
3445
|
+
random() {
|
|
3446
|
+
this.randomSeed = this.randomSeed * 16807 % 2147483647;
|
|
3447
|
+
return (this.randomSeed - 1) / 2147483646;
|
|
3448
|
+
}
|
|
3449
|
+
/**
|
|
3450
|
+
* The cost is the "probability of failure" of the node.
|
|
3451
|
+
*/
|
|
3452
|
+
computeNodeCost(nodeId) {
|
|
3453
|
+
const node = this.nodeMap.get(nodeId);
|
|
3454
|
+
if (node?._containsTarget) return 0;
|
|
3455
|
+
const totalCapacity = getTunedTotalCapacity1(node);
|
|
3456
|
+
const usedViaCapacity = this.getUsedViaCapacity(nodeId);
|
|
3457
|
+
const usedTraceCapacity = this.getUsedTraceCapacity(nodeId);
|
|
3458
|
+
const approxProb = usedViaCapacity * usedTraceCapacity / totalCapacity ** 2;
|
|
3459
|
+
const K = -2.3;
|
|
3460
|
+
return 1 - Math.exp(approxProb * K);
|
|
3461
|
+
}
|
|
3462
|
+
/**
|
|
3463
|
+
* Number of traces that can go through this node if they are completely
|
|
3464
|
+
* straight without crossings
|
|
3465
|
+
*/
|
|
3466
|
+
getUsedTraceCapacity(nodeId) {
|
|
3467
|
+
const segmentIds = this.nodeIdToSegmentIds.get(nodeId);
|
|
3468
|
+
const segments = segmentIds.map(
|
|
3469
|
+
(segmentId) => this.currentMutatedSegments.get(segmentId)
|
|
3470
|
+
);
|
|
3471
|
+
const points = segments.flatMap((s) => s.assignedPoints);
|
|
3472
|
+
const numTracesThroughNode = points.length / 2;
|
|
3473
|
+
const numLayers = 2;
|
|
3474
|
+
return numTracesThroughNode / numLayers;
|
|
3475
|
+
}
|
|
3476
|
+
/**
|
|
3477
|
+
* Granular via capacity is a consideration of capacity that includes...
|
|
3478
|
+
* - The number of traces
|
|
3479
|
+
* - The number of trace crossings (0-2 vias per trace crossing)
|
|
3480
|
+
* - Empirically, each crossing typically results in 0.82 vias
|
|
3481
|
+
* - e.g. 17 traces would typically have 51 crossings & 42 vias
|
|
3482
|
+
* - The number of layer changes (at least 1 via per layer change)
|
|
3483
|
+
* - We don't know how a entry/exit being on separated layers effects
|
|
3484
|
+
* the capacity/number of vias yet
|
|
3485
|
+
*
|
|
3486
|
+
* - Generally minimizing the number of crossings is pretty good, if there
|
|
3487
|
+
* is no trace crossing you basically don't have any used capacity
|
|
3488
|
+
* - If the entry/exit layer is different, you're guaranteed to have at least
|
|
3489
|
+
* one via
|
|
3490
|
+
*
|
|
3491
|
+
* - Total capacity is computed by estimating the number of vias that could
|
|
3492
|
+
* be created using the formula (viaFitAcross / 2) ** 1.1
|
|
3493
|
+
*/
|
|
3494
|
+
getUsedViaCapacity(nodeId) {
|
|
3495
|
+
const segmentIds = this.nodeIdToSegmentIds.get(nodeId);
|
|
3496
|
+
const segments = segmentIds.map(
|
|
3497
|
+
(segmentId) => this.currentMutatedSegments.get(segmentId)
|
|
3498
|
+
);
|
|
3499
|
+
const points = segments.flatMap((s) => s.assignedPoints);
|
|
3500
|
+
if (points.length <= 2) {
|
|
3501
|
+
if (points.length <= 1) return 0;
|
|
3502
|
+
return 0;
|
|
3503
|
+
}
|
|
3504
|
+
const {
|
|
3505
|
+
numEntryExitLayerChanges,
|
|
3506
|
+
numSameLayerCrossings,
|
|
3507
|
+
numTransitionCrossings
|
|
3508
|
+
} = getIntraNodeCrossingsFromSegments(segments);
|
|
3509
|
+
const estNumVias = numSameLayerCrossings * 0.82 + numEntryExitLayerChanges * 0.41 + numTransitionCrossings * 0.2;
|
|
3510
|
+
const estUsedCapacity = (estNumVias / 2) ** 1.1;
|
|
3511
|
+
return estUsedCapacity;
|
|
3512
|
+
}
|
|
3513
|
+
getRandomWeightedNodeId() {
|
|
3514
|
+
const nodeIdsWithCosts = [...this.currentNodeCosts.entries()].filter(([nodeId, cost]) => cost > 1e-5).filter(([nodeId]) => !this.nodeMap.get(nodeId)?._containsTarget);
|
|
3515
|
+
if (nodeIdsWithCosts.length === 0) {
|
|
3516
|
+
console.error(
|
|
3517
|
+
"No nodes with cost > 0.00001 (why are you even running this solver)"
|
|
3518
|
+
);
|
|
3519
|
+
return this.currentNodeCosts.keys().next().value;
|
|
3520
|
+
}
|
|
3521
|
+
const totalCost = nodeIdsWithCosts.reduce((acc, [, cost]) => acc + cost, 0);
|
|
3522
|
+
const randomValue = this.random() * totalCost;
|
|
3523
|
+
let cumulativeCost = 0;
|
|
3524
|
+
for (let i = 0; i < nodeIdsWithCosts.length; i++) {
|
|
3525
|
+
const [nodeId, cost] = nodeIdsWithCosts[i];
|
|
3526
|
+
cumulativeCost += cost;
|
|
3527
|
+
if (cumulativeCost >= randomValue) {
|
|
3528
|
+
return nodeId;
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
throw new Error("RANDOM SELECTION FAILURE FOR NODES (this is a bug)");
|
|
3532
|
+
}
|
|
3533
|
+
getRandomWeightedSegmentId() {
|
|
3534
|
+
const nodeId = this.getRandomWeightedNodeId();
|
|
3535
|
+
const segmentsIds = this.nodeIdToSegmentIds.get(nodeId).filter((s) => this.isSegmentMutable(s));
|
|
3536
|
+
return segmentsIds[Math.floor(this.random() * segmentsIds.length)];
|
|
3537
|
+
}
|
|
3538
|
+
getMutableSegments() {
|
|
3539
|
+
const mutableSegments = /* @__PURE__ */ new Set();
|
|
3540
|
+
for (const segmentId of this.currentMutatedSegments.keys()) {
|
|
3541
|
+
const segment = this.currentMutatedSegments.get(segmentId);
|
|
3542
|
+
const nodes = this.segmentIdToNodeIds.get(segmentId);
|
|
3543
|
+
const isMutable = nodes.every(
|
|
3544
|
+
(nodeId) => !this.nodeMap.get(nodeId)?._containsTarget
|
|
3545
|
+
);
|
|
3546
|
+
if (isMutable) {
|
|
3547
|
+
mutableSegments.add(segmentId);
|
|
3548
|
+
}
|
|
3549
|
+
}
|
|
3550
|
+
return mutableSegments;
|
|
3551
|
+
}
|
|
3552
|
+
isSegmentMutable(segmentId) {
|
|
3553
|
+
return this.mutableSegments.has(segmentId);
|
|
3554
|
+
}
|
|
3555
|
+
getRandomOperationForSegment(randomSegmentId) {
|
|
3556
|
+
const segment = this.currentMutatedSegments.get(randomSegmentId);
|
|
3557
|
+
let operationType = this.random() < 0.5 ? "switch" : "changeLayer";
|
|
3558
|
+
if (segment.assignedPoints.length <= 1) {
|
|
3559
|
+
operationType = "changeLayer";
|
|
3560
|
+
}
|
|
3561
|
+
if (operationType === "switch") {
|
|
3562
|
+
const randomPointIndex1 = Math.floor(
|
|
3563
|
+
this.random() * segment.assignedPoints.length
|
|
3564
|
+
);
|
|
3565
|
+
let randomPointIndex2 = randomPointIndex1;
|
|
3566
|
+
while (randomPointIndex1 === randomPointIndex2) {
|
|
3567
|
+
randomPointIndex2 = Math.floor(
|
|
3568
|
+
this.random() * segment.assignedPoints.length
|
|
3569
|
+
);
|
|
3570
|
+
}
|
|
3571
|
+
return {
|
|
3572
|
+
op: "switch",
|
|
3573
|
+
segmentId: randomSegmentId,
|
|
3574
|
+
point1Index: randomPointIndex1,
|
|
3575
|
+
point2Index: randomPointIndex2
|
|
3576
|
+
};
|
|
3577
|
+
}
|
|
3578
|
+
const nodeIds = this.segmentIdToNodeIds.get(segment.nodePortSegmentId);
|
|
3579
|
+
if (nodeIds?.some((nodeId) => this.nodesThatCantFitVias.has(nodeId))) {
|
|
3580
|
+
return null;
|
|
3581
|
+
}
|
|
3582
|
+
const randomPointIndex = Math.floor(
|
|
3583
|
+
this.random() * segment.assignedPoints.length
|
|
3584
|
+
);
|
|
3585
|
+
const point = segment.assignedPoints[randomPointIndex];
|
|
3586
|
+
return {
|
|
3587
|
+
op: "changeLayer",
|
|
3588
|
+
segmentId: randomSegmentId,
|
|
3589
|
+
pointIndex: randomPointIndex,
|
|
3590
|
+
newLayer: point.point.y > 0 ? 0 : 1
|
|
3591
|
+
};
|
|
3592
|
+
}
|
|
3593
|
+
getNodesNearNode(nodeId, hops = 1) {
|
|
3594
|
+
if (hops === 0) return [nodeId];
|
|
3595
|
+
const segments = this.nodeIdToSegmentIds.get(nodeId);
|
|
3596
|
+
const nodes = /* @__PURE__ */ new Set();
|
|
3597
|
+
for (const segmentId of segments) {
|
|
3598
|
+
const adjacentNodeIds = this.segmentIdToNodeIds.get(segmentId);
|
|
3599
|
+
for (const adjacentNodeId of adjacentNodeIds) {
|
|
3600
|
+
const ancestors = this.getNodesNearNode(adjacentNodeId, hops - 1);
|
|
3601
|
+
for (const ancestor of ancestors) {
|
|
3602
|
+
nodes.add(ancestor);
|
|
3603
|
+
}
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
return Array.from(nodes);
|
|
3607
|
+
}
|
|
3608
|
+
getRandomCombinedOperationNearNode(nodeId) {
|
|
3609
|
+
const adjacentNodeIds = this.getNodesNearNode(
|
|
3610
|
+
nodeId,
|
|
3611
|
+
this.MAX_NODE_CHAIN_PER_MUTATION
|
|
3612
|
+
);
|
|
3613
|
+
const subOperations = [];
|
|
3614
|
+
const adjacentSegments = adjacentNodeIds.flatMap((nodeId2) => this.nodeIdToSegmentIds.get(nodeId2)).filter((s) => this.isSegmentMutable(s));
|
|
3615
|
+
const numOperations = Math.floor(this.random() * this.MAX_OPERATIONS_PER_MUTATION) + 1;
|
|
3616
|
+
for (let i = 0; i < numOperations; i++) {
|
|
3617
|
+
const randomSegmentId = adjacentSegments[Math.floor(this.random() * adjacentSegments.length)];
|
|
3618
|
+
const newOp = this.getRandomOperationForSegment(randomSegmentId);
|
|
3619
|
+
if (newOp) {
|
|
3620
|
+
subOperations.push(newOp);
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
return {
|
|
3624
|
+
op: "combined",
|
|
3625
|
+
subOperations
|
|
3626
|
+
};
|
|
3627
|
+
}
|
|
3628
|
+
/**
|
|
3629
|
+
* A combined operation can perform multiple operations on a single node, this
|
|
3630
|
+
* allows it to reach outcomes that may not be beneficial with since
|
|
3631
|
+
* operations
|
|
3632
|
+
*/
|
|
3633
|
+
getRandomCombinedOperationOnSingleNode(max = 7) {
|
|
3634
|
+
const numSubOperations = max === 1 ? 1 : Math.floor(this.random() * max) + 1;
|
|
3635
|
+
const subOperations = [];
|
|
3636
|
+
const nodeId = this.getRandomWeightedNodeId();
|
|
3637
|
+
const segmentsIds = this.nodeIdToSegmentIds.get(nodeId).filter((s) => this.isSegmentMutable(s));
|
|
3638
|
+
for (let i = 0; i < numSubOperations; i++) {
|
|
3639
|
+
const randomSegmentId = segmentsIds[Math.floor(this.random() * segmentsIds.length)];
|
|
3640
|
+
const newOp = this.getRandomOperationForSegment(randomSegmentId);
|
|
3641
|
+
if (newOp) {
|
|
3642
|
+
subOperations.push(newOp);
|
|
3643
|
+
}
|
|
3644
|
+
}
|
|
3645
|
+
return {
|
|
3646
|
+
op: "combined",
|
|
3647
|
+
subOperations
|
|
3648
|
+
};
|
|
3649
|
+
}
|
|
3650
|
+
getRandomOperation() {
|
|
3651
|
+
const randomSegmentId = this.getRandomWeightedSegmentId();
|
|
3652
|
+
const newOp = this.getRandomOperationForSegment(randomSegmentId);
|
|
3653
|
+
if (newOp) {
|
|
3654
|
+
return newOp;
|
|
3655
|
+
}
|
|
3656
|
+
return this.getRandomOperation();
|
|
3657
|
+
}
|
|
3658
|
+
/**
|
|
3659
|
+
* We compute "overall probability of failure" as our overall cost, then
|
|
3660
|
+
* linearize it to make it easier to work with
|
|
3661
|
+
*/
|
|
3662
|
+
computeCurrentCost() {
|
|
3663
|
+
let logProbabilityOfSuccess = 0;
|
|
3664
|
+
let costSum = 0;
|
|
3665
|
+
const nodeCosts = /* @__PURE__ */ new Map();
|
|
3666
|
+
for (const nodeId of this.nodeIdToSegmentIds.keys()) {
|
|
3667
|
+
const nodeProbOfFailure = this.computeNodeCost(nodeId);
|
|
3668
|
+
nodeCosts.set(nodeId, nodeProbOfFailure);
|
|
3669
|
+
costSum += nodeProbOfFailure;
|
|
3670
|
+
if (nodeProbOfFailure < 1) {
|
|
3671
|
+
logProbabilityOfSuccess += Math.log(1 - nodeProbOfFailure);
|
|
3672
|
+
} else {
|
|
3673
|
+
logProbabilityOfSuccess = -Infinity;
|
|
3674
|
+
}
|
|
3675
|
+
}
|
|
3676
|
+
const probabilityOfSuccess = Math.exp(logProbabilityOfSuccess);
|
|
3677
|
+
const probabilityOfFailure = 1 - probabilityOfSuccess;
|
|
3678
|
+
const numNodes = this.nodeIdToSegmentIds.size;
|
|
3679
|
+
const linearizedCost = numNodes > 0 ? -logProbabilityOfSuccess / numNodes : 0;
|
|
3680
|
+
return {
|
|
3681
|
+
cost: linearizedCost,
|
|
3682
|
+
// Replace cost with linearized version
|
|
3683
|
+
nodeCosts,
|
|
3684
|
+
probabilityOfFailure,
|
|
3685
|
+
linearizedCost
|
|
3686
|
+
// Also return as separate value if you need original cost sum
|
|
3687
|
+
};
|
|
3688
|
+
}
|
|
3689
|
+
applyOperation(op) {
|
|
3690
|
+
if (op.op === "combined") {
|
|
3691
|
+
for (const subOp of op.subOperations) {
|
|
3692
|
+
this.applyOperation(subOp);
|
|
3693
|
+
}
|
|
3694
|
+
return;
|
|
3695
|
+
}
|
|
3696
|
+
const segment = this.currentMutatedSegments.get(op.segmentId);
|
|
3697
|
+
if (!segment || !segment.assignedPoints) return;
|
|
3698
|
+
if (op.op === "changeLayer") {
|
|
3699
|
+
op.oldLayer = segment.assignedPoints[op.pointIndex].point.z;
|
|
3700
|
+
segment.assignedPoints[op.pointIndex].point.z = op.newLayer;
|
|
3701
|
+
} else if (op.op === "switch") {
|
|
3702
|
+
const point1 = segment.assignedPoints[op.point1Index].point;
|
|
3703
|
+
const point2 = segment.assignedPoints[op.point2Index].point;
|
|
3704
|
+
const tempX = point1.x;
|
|
3705
|
+
const tempY = point1.y;
|
|
3706
|
+
const tempZ = point1.z;
|
|
3707
|
+
point1.x = point2.x;
|
|
3708
|
+
point1.y = point2.y;
|
|
3709
|
+
point1.z = point2.z;
|
|
3710
|
+
point2.x = tempX;
|
|
3711
|
+
point2.y = tempY;
|
|
3712
|
+
point2.z = tempZ;
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
reverseOperation(op) {
|
|
3716
|
+
if (op.op === "combined") {
|
|
3717
|
+
for (const subOp of [...op.subOperations].reverse()) {
|
|
3718
|
+
this.reverseOperation(subOp);
|
|
3719
|
+
}
|
|
3720
|
+
return;
|
|
3721
|
+
}
|
|
3722
|
+
const segment = this.currentMutatedSegments.get(op.segmentId);
|
|
3723
|
+
if (!segment || !segment.assignedPoints) return;
|
|
3724
|
+
if (op.op === "changeLayer") {
|
|
3725
|
+
const oldLayer = op.oldLayer;
|
|
3726
|
+
if (oldLayer === void 0) return;
|
|
3727
|
+
segment.assignedPoints[op.pointIndex].point.z = oldLayer;
|
|
3728
|
+
} else if (op.op === "switch") {
|
|
3729
|
+
const point1 = segment.assignedPoints[op.point1Index].point;
|
|
3730
|
+
const point2 = segment.assignedPoints[op.point2Index].point;
|
|
3731
|
+
const tempX = point1.x;
|
|
3732
|
+
const tempY = point1.y;
|
|
3733
|
+
const tempZ = point1.z;
|
|
3734
|
+
point1.x = point2.x;
|
|
3735
|
+
point1.y = point2.y;
|
|
3736
|
+
point1.z = point2.z;
|
|
3737
|
+
point2.x = tempX;
|
|
3738
|
+
point2.y = tempY;
|
|
3739
|
+
point2.z = tempZ;
|
|
3740
|
+
}
|
|
3741
|
+
}
|
|
3742
|
+
isNewCostAcceptable(oldPf, newPf) {
|
|
3743
|
+
if (newPf < oldPf) return true;
|
|
3744
|
+
return false;
|
|
3745
|
+
}
|
|
3746
|
+
/**
|
|
3747
|
+
* FOR OUTPUT: Return the assigned points for each segment.
|
|
3748
|
+
*/
|
|
3749
|
+
getNodesWithPortPoints() {
|
|
3750
|
+
if (!this.solved) {
|
|
3751
|
+
throw new Error(
|
|
3752
|
+
"CapacitySegmentToPointSolver not solved, can't give port points yet"
|
|
3753
|
+
);
|
|
3754
|
+
}
|
|
3755
|
+
const map = /* @__PURE__ */ new Map();
|
|
3756
|
+
for (const segId of this.allSegmentIds) {
|
|
3757
|
+
for (const nodeId of this.segmentIdToNodeIds.get(segId)) {
|
|
3758
|
+
const node = this.nodeMap.get(nodeId);
|
|
3759
|
+
if (!map.has(nodeId)) {
|
|
3760
|
+
map.set(nodeId, {
|
|
3761
|
+
capacityMeshNodeId: nodeId,
|
|
3762
|
+
portPoints: [],
|
|
3763
|
+
center: node.center,
|
|
3764
|
+
width: node.width,
|
|
3765
|
+
height: node.height
|
|
3766
|
+
});
|
|
3767
|
+
}
|
|
3768
|
+
map.get(nodeId).portPoints.push(
|
|
3769
|
+
...this.currentMutatedSegments.get(segId).assignedPoints.map((ap) => ({
|
|
3770
|
+
...ap.point,
|
|
3771
|
+
connectionName: ap.connectionName
|
|
3772
|
+
}))
|
|
3773
|
+
);
|
|
3774
|
+
}
|
|
3775
|
+
}
|
|
3776
|
+
return Array.from(map.values());
|
|
3777
|
+
}
|
|
3778
|
+
_step() {
|
|
3779
|
+
if (this.iterations === this.MAX_ITERATIONS - 1) {
|
|
3780
|
+
this.solved = true;
|
|
3781
|
+
return;
|
|
3782
|
+
}
|
|
3783
|
+
if (this.currentCost < 1e-3) {
|
|
3784
|
+
this.solved = true;
|
|
3785
|
+
return;
|
|
3786
|
+
}
|
|
3787
|
+
const op = this.getRandomCombinedOperationNearNode(
|
|
3788
|
+
this.getRandomWeightedNodeId()
|
|
3789
|
+
);
|
|
3790
|
+
this.lastCreatedOperation = op;
|
|
3791
|
+
this.applyOperation(op);
|
|
3792
|
+
const {
|
|
3793
|
+
cost: newCost,
|
|
3794
|
+
nodeCosts: newNodeCosts,
|
|
3795
|
+
probabilityOfFailure: newProbabilityOfFailure
|
|
3796
|
+
} = this.computeCurrentCost();
|
|
3797
|
+
op.cost = newCost;
|
|
3798
|
+
const keepChange = this.isNewCostAcceptable(this.currentCost, newCost);
|
|
3799
|
+
if (!keepChange) {
|
|
3800
|
+
this.reverseOperation(op);
|
|
3801
|
+
if (this.iterations - this.lastAcceptedIteration > this.NOOP_ITERATIONS_BEFORE_EARLY_STOP) {
|
|
3802
|
+
this.solved = true;
|
|
3803
|
+
}
|
|
3804
|
+
return;
|
|
3805
|
+
}
|
|
3806
|
+
this.lastAcceptedIteration = this.iterations;
|
|
3807
|
+
this.currentCost = newCost;
|
|
3808
|
+
this.currentNodeCosts = newNodeCosts;
|
|
3809
|
+
this.lastAppliedOperation = op;
|
|
3810
|
+
this.probabilityOfFailure = newProbabilityOfFailure;
|
|
3811
|
+
}
|
|
3812
|
+
visualize() {
|
|
3813
|
+
const immutableSegments = new Set(
|
|
3814
|
+
[...this.currentMutatedSegments.values()].filter(
|
|
3815
|
+
(seg) => !this.isSegmentMutable(seg.nodePortSegmentId)
|
|
3816
|
+
)
|
|
3817
|
+
);
|
|
3818
|
+
const graphics = {
|
|
3819
|
+
points: [...this.currentMutatedSegments.values()].flatMap(
|
|
3820
|
+
(seg, i) => seg.assignedPoints.map((ap) => ({
|
|
3821
|
+
x: ap.point.x,
|
|
3822
|
+
y: ap.point.y,
|
|
3823
|
+
label: `${seg.nodePortSegmentId}
|
|
3824
|
+
layer: ${ap.point.z}
|
|
3825
|
+
${ap.connectionName}
|
|
3826
|
+
${immutableSegments.has(seg) ? "(IMMUTABLE)" : ""}`,
|
|
3827
|
+
color: this.colorMap[ap.connectionName]
|
|
3828
|
+
}))
|
|
3829
|
+
),
|
|
3830
|
+
lines: [...this.currentMutatedSegments.values()].map((seg) => ({
|
|
3831
|
+
points: [seg.start, seg.end]
|
|
3832
|
+
})),
|
|
3833
|
+
rects: [
|
|
3834
|
+
...[...this.nodeMap.values()].map((node) => {
|
|
3835
|
+
const segmentIds = this.nodeIdToSegmentIds.get(
|
|
3836
|
+
node.capacityMeshNodeId
|
|
3837
|
+
);
|
|
3838
|
+
if (!segmentIds) return null;
|
|
3839
|
+
const segments = segmentIds.map(
|
|
3840
|
+
(segmentId) => this.currentMutatedSegments.get(segmentId)
|
|
3841
|
+
);
|
|
3842
|
+
let label;
|
|
3843
|
+
if (node._containsTarget) {
|
|
3844
|
+
label = `${node.capacityMeshNodeId}
|
|
3845
|
+
${node.width.toFixed(2)}x${node.height.toFixed(2)}`;
|
|
3846
|
+
} else {
|
|
3847
|
+
const intraNodeCrossings = getIntraNodeCrossingsFromSegments(segments);
|
|
3848
|
+
label = `${node.capacityMeshNodeId}
|
|
3849
|
+
${this.computeNodeCost(node.capacityMeshNodeId).toFixed(2)}/${getTunedTotalCapacity1(node).toFixed(2)}
|
|
3850
|
+
Trace Capacity: ${this.getUsedTraceCapacity(node.capacityMeshNodeId).toFixed(2)}
|
|
3851
|
+
X'ings: ${intraNodeCrossings.numSameLayerCrossings}
|
|
3852
|
+
Ent/Ex LC: ${intraNodeCrossings.numEntryExitLayerChanges}
|
|
3853
|
+
T X'ings: ${intraNodeCrossings.numTransitionCrossings}
|
|
3854
|
+
${node.width.toFixed(2)}x${node.height.toFixed(2)}`;
|
|
3855
|
+
}
|
|
3856
|
+
return {
|
|
3857
|
+
center: node.center,
|
|
3858
|
+
label,
|
|
3859
|
+
color: "red",
|
|
3860
|
+
width: node.width / 8,
|
|
3861
|
+
height: node.height / 8
|
|
3862
|
+
};
|
|
3863
|
+
}).filter((r) => r !== null)
|
|
3864
|
+
],
|
|
3865
|
+
circles: [],
|
|
3866
|
+
coordinateSystem: "cartesian",
|
|
3867
|
+
title: "Capacity Segment Point Optimizer"
|
|
3868
|
+
};
|
|
3869
|
+
const dashedLines = [];
|
|
3870
|
+
const nodeConnections = {};
|
|
3871
|
+
for (const seg of this.currentMutatedSegments.values()) {
|
|
3872
|
+
const nodeIds = this.segmentIdToNodeIds.get(seg.nodePortSegmentId);
|
|
3873
|
+
for (const nodeId of nodeIds) {
|
|
3874
|
+
if (!nodeConnections[nodeId]) {
|
|
3875
|
+
nodeConnections[nodeId] = {};
|
|
3876
|
+
}
|
|
3877
|
+
for (const ap of seg.assignedPoints) {
|
|
3878
|
+
if (!nodeConnections[nodeId][ap.connectionName]) {
|
|
3879
|
+
nodeConnections[nodeId][ap.connectionName] = [];
|
|
3880
|
+
}
|
|
3881
|
+
nodeConnections[nodeId][ap.connectionName].push(ap.point);
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
for (const nodeId in nodeConnections) {
|
|
3886
|
+
for (const conn in nodeConnections[nodeId]) {
|
|
3887
|
+
const points = nodeConnections[nodeId][conn];
|
|
3888
|
+
if (points.length <= 1) continue;
|
|
3889
|
+
const sameLayer = points[0].z === points[1].z;
|
|
3890
|
+
const commonLayer = points[0].z;
|
|
3891
|
+
const type = sameLayer ? commonLayer === 0 ? "top" : "bottom" : "transition";
|
|
3892
|
+
dashedLines.push({
|
|
3893
|
+
points,
|
|
3894
|
+
strokeDash: type === "top" ? void 0 : type === "bottom" ? "10 5" : "3 3 10",
|
|
3895
|
+
strokeColor: this.colorMap[conn] || "#000"
|
|
3896
|
+
});
|
|
3897
|
+
}
|
|
3898
|
+
}
|
|
3899
|
+
graphics.lines.push(...dashedLines);
|
|
3900
|
+
const operationsToShow = [];
|
|
3901
|
+
if (this.lastCreatedOperation?.op === "combined") {
|
|
3902
|
+
operationsToShow.push(...this.lastCreatedOperation.subOperations);
|
|
3903
|
+
} else if (this.lastCreatedOperation) {
|
|
3904
|
+
operationsToShow.push(this.lastCreatedOperation);
|
|
3905
|
+
}
|
|
3906
|
+
for (const op of operationsToShow) {
|
|
3907
|
+
const segment = this.currentMutatedSegments.get(op.segmentId);
|
|
3908
|
+
const node = this.nodeMap.get(segment.capacityMeshNodeId);
|
|
3909
|
+
graphics.circles.push({
|
|
3910
|
+
center: { x: node.center.x, y: node.center.y },
|
|
3911
|
+
radius: node.width / 4,
|
|
3912
|
+
stroke: "#0000ff",
|
|
3913
|
+
fill: "rgba(0, 0, 255, 0.2)",
|
|
3914
|
+
label: `LAST OPERATION: ${op.op}
|
|
3915
|
+
Cost: ${op.cost?.toString()}
|
|
3916
|
+
${node.capacityMeshNodeId}
|
|
3917
|
+
${this.currentNodeCosts.get(node.capacityMeshNodeId)}/${getTunedTotalCapacity1(node)}
|
|
3918
|
+
${node.width.toFixed(2)}x${node.height.toFixed(2)}`
|
|
3919
|
+
});
|
|
3920
|
+
if (op.op === "changeLayer") {
|
|
3921
|
+
const point = segment.assignedPoints[op.pointIndex];
|
|
3922
|
+
graphics.circles.push({
|
|
3923
|
+
center: { x: point.point.x, y: point.point.y },
|
|
3924
|
+
radius: this.nodeMap.get(segment.capacityMeshNodeId).width / 8,
|
|
3925
|
+
stroke: "#ff0000",
|
|
3926
|
+
fill: "rgba(255, 0, 0, 0.2)",
|
|
3927
|
+
label: `Layer Changed
|
|
3928
|
+
oldLayer: ${op.oldLayer}
|
|
3929
|
+
newLayer: ${op.newLayer}`
|
|
3930
|
+
});
|
|
3931
|
+
} else if (op.op === "switch") {
|
|
3932
|
+
const point1 = segment.assignedPoints[op.point1Index];
|
|
3933
|
+
const point2 = segment.assignedPoints[op.point2Index];
|
|
3934
|
+
graphics.circles.push(
|
|
3935
|
+
{
|
|
3936
|
+
center: { x: point1.point.x, y: point1.point.y },
|
|
3937
|
+
radius: node.width / 16,
|
|
3938
|
+
stroke: "#00ff00",
|
|
3939
|
+
fill: "rgba(0, 255, 0, 0.2)",
|
|
3940
|
+
label: `Swapped 1
|
|
3941
|
+
${segment.nodePortSegmentId}`
|
|
3942
|
+
},
|
|
3943
|
+
{
|
|
3944
|
+
center: { x: point2.point.x, y: point2.point.y },
|
|
3945
|
+
radius: node.width / 16,
|
|
3946
|
+
stroke: "#00ff00",
|
|
3947
|
+
fill: "rgba(0, 255, 0, 0.2)",
|
|
3948
|
+
label: `Swapped 2
|
|
3949
|
+
${segment.nodePortSegmentId}`
|
|
3950
|
+
}
|
|
3951
|
+
);
|
|
3952
|
+
graphics.lines.push({
|
|
3953
|
+
points: [
|
|
3954
|
+
{ x: point1.point.x, y: point1.point.y },
|
|
3955
|
+
{ x: point2.point.x, y: point2.point.y }
|
|
3956
|
+
],
|
|
3957
|
+
strokeColor: "#00ff00",
|
|
3958
|
+
strokeDash: "3 3",
|
|
3959
|
+
strokeWidth: node.width / 32
|
|
3960
|
+
});
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
return graphics;
|
|
3964
|
+
}
|
|
3965
|
+
};
|
|
3966
|
+
|
|
3967
|
+
// lib/solvers/CapacityMeshSolver/CapacityMeshSolver.ts
|
|
3968
|
+
var CapacityMeshSolver = class extends BaseSolver {
|
|
3969
|
+
constructor(srj, opts = {}) {
|
|
3970
|
+
super();
|
|
3971
|
+
this.srj = srj;
|
|
3972
|
+
this.opts = opts;
|
|
3973
|
+
this.MAX_ITERATIONS = 1e6;
|
|
3974
|
+
if (opts.capacityDepth === void 0) {
|
|
3975
|
+
const boundsWidth = srj.bounds.maxX - srj.bounds.minX;
|
|
3976
|
+
const boundsHeight = srj.bounds.maxY - srj.bounds.minY;
|
|
3977
|
+
const maxWidthHeight = Math.max(boundsWidth, boundsHeight);
|
|
3978
|
+
const targetMinCapacity = opts.targetMinCapacity ?? 0.5;
|
|
3979
|
+
opts.capacityDepth = calculateOptimalCapacityDepth(
|
|
3980
|
+
maxWidthHeight,
|
|
3981
|
+
targetMinCapacity
|
|
3982
|
+
);
|
|
3983
|
+
}
|
|
3984
|
+
this.nodeSolver = new CapacityMeshNodeSolver(srj, this.opts);
|
|
3985
|
+
this.connMap = getConnectivityMapFromSimpleRouteJson(srj);
|
|
3986
|
+
this.colorMap = getColorMap(srj, this.connMap);
|
|
3987
|
+
}
|
|
3988
|
+
nodeSolver;
|
|
3989
|
+
nodeTargetMerger;
|
|
3990
|
+
edgeSolver;
|
|
3991
|
+
pathingSolver;
|
|
3992
|
+
edgeToPortSegmentSolver;
|
|
3993
|
+
colorMap;
|
|
3994
|
+
segmentToPointSolver;
|
|
3995
|
+
segmentToPointOptimizer;
|
|
3996
|
+
highDensityRouteSolver;
|
|
3997
|
+
activeSolver = null;
|
|
3998
|
+
connMap;
|
|
3999
|
+
_step() {
|
|
4000
|
+
if (this.activeSolver) {
|
|
4001
|
+
this.activeSolver.step();
|
|
4002
|
+
if (this.activeSolver.solved) {
|
|
4003
|
+
this.activeSolver = null;
|
|
4004
|
+
} else if (this.activeSolver.failed) {
|
|
4005
|
+
this.error = this.activeSolver?.error;
|
|
4006
|
+
this.failed = true;
|
|
4007
|
+
this.activeSolver = null;
|
|
4008
|
+
}
|
|
4009
|
+
return;
|
|
4010
|
+
}
|
|
4011
|
+
if (!this.nodeSolver.solved) {
|
|
4012
|
+
this.activeSolver = this.nodeSolver;
|
|
4013
|
+
return;
|
|
4014
|
+
}
|
|
4015
|
+
if (!this.nodeTargetMerger) {
|
|
4016
|
+
this.nodeTargetMerger = new CapacityNodeTargetMerger(
|
|
4017
|
+
this.nodeSolver.finishedNodes,
|
|
4018
|
+
this.srj.obstacles,
|
|
4019
|
+
this.connMap
|
|
4020
|
+
);
|
|
4021
|
+
this.activeSolver = this.nodeTargetMerger;
|
|
4022
|
+
return;
|
|
4023
|
+
}
|
|
4024
|
+
const nodes = this.nodeTargetMerger.newNodes;
|
|
4025
|
+
if (!this.edgeSolver) {
|
|
4026
|
+
this.edgeSolver = new CapacityMeshEdgeSolver(nodes);
|
|
4027
|
+
this.activeSolver = this.edgeSolver;
|
|
4028
|
+
return;
|
|
4029
|
+
}
|
|
4030
|
+
if (!this.pathingSolver) {
|
|
4031
|
+
this.pathingSolver = new CapacityPathingSolver4_FlexibleNegativeCapacity({
|
|
4032
|
+
simpleRouteJson: this.srj,
|
|
4033
|
+
nodes,
|
|
4034
|
+
edges: this.edgeSolver.edges,
|
|
4035
|
+
colorMap: this.colorMap,
|
|
4036
|
+
hyperParameters: {
|
|
4037
|
+
MAX_CAPACITY_FACTOR: 1
|
|
4038
|
+
}
|
|
4039
|
+
});
|
|
4040
|
+
this.activeSolver = this.pathingSolver;
|
|
4041
|
+
return;
|
|
4042
|
+
}
|
|
4043
|
+
if (!this.edgeToPortSegmentSolver) {
|
|
4044
|
+
this.edgeToPortSegmentSolver = new CapacityEdgeToPortSegmentSolver({
|
|
4045
|
+
nodes,
|
|
4046
|
+
edges: this.edgeSolver.edges,
|
|
4047
|
+
capacityPaths: this.pathingSolver.getCapacityPaths(),
|
|
4048
|
+
colorMap: this.colorMap
|
|
4049
|
+
});
|
|
4050
|
+
this.activeSolver = this.edgeToPortSegmentSolver;
|
|
4051
|
+
return;
|
|
4052
|
+
}
|
|
4053
|
+
if (!this.segmentToPointSolver) {
|
|
4054
|
+
const allSegments = [];
|
|
4055
|
+
this.edgeToPortSegmentSolver.nodePortSegments.forEach((segs) => {
|
|
4056
|
+
allSegments.push(...segs);
|
|
4057
|
+
});
|
|
4058
|
+
this.segmentToPointSolver = new CapacitySegmentToPointSolver({
|
|
4059
|
+
segments: allSegments,
|
|
4060
|
+
colorMap: this.colorMap,
|
|
4061
|
+
nodes
|
|
4062
|
+
});
|
|
4063
|
+
this.activeSolver = this.segmentToPointSolver;
|
|
4064
|
+
return;
|
|
4065
|
+
}
|
|
4066
|
+
if (!this.segmentToPointOptimizer) {
|
|
4067
|
+
this.segmentToPointOptimizer = new CapacitySegmentPointOptimizer({
|
|
4068
|
+
assignedSegments: this.segmentToPointSolver.solvedSegments,
|
|
4069
|
+
colorMap: this.colorMap,
|
|
4070
|
+
nodes
|
|
4071
|
+
});
|
|
4072
|
+
this.activeSolver = this.segmentToPointOptimizer;
|
|
4073
|
+
return;
|
|
4074
|
+
}
|
|
4075
|
+
if (!this.highDensityRouteSolver) {
|
|
4076
|
+
const nodesWithPortPoints = this.segmentToPointOptimizer.getNodesWithPortPoints();
|
|
4077
|
+
this.highDensityRouteSolver = new HighDensityRouteSolver({
|
|
4078
|
+
nodePortPoints: nodesWithPortPoints,
|
|
4079
|
+
colorMap: this.colorMap,
|
|
4080
|
+
connMap: this.connMap
|
|
4081
|
+
});
|
|
4082
|
+
this.activeSolver = this.highDensityRouteSolver;
|
|
4083
|
+
return;
|
|
4084
|
+
}
|
|
4085
|
+
this.solved = true;
|
|
4086
|
+
}
|
|
4087
|
+
visualize() {
|
|
4088
|
+
if (!this.solved && this.activeSolver) return this.activeSolver.visualize();
|
|
4089
|
+
const nodeViz = this.nodeSolver.visualize();
|
|
4090
|
+
const edgeViz = this.edgeSolver?.visualize();
|
|
4091
|
+
const pathingViz = this.pathingSolver?.visualize();
|
|
4092
|
+
const edgeToPortSegmentViz = this.edgeToPortSegmentSolver?.visualize();
|
|
4093
|
+
const segmentToPointViz = this.segmentToPointSolver?.visualize();
|
|
4094
|
+
const segmentOptimizationViz = this.segmentToPointOptimizer?.visualize();
|
|
4095
|
+
const highDensityViz = this.highDensityRouteSolver?.visualize();
|
|
4096
|
+
const problemViz = {
|
|
4097
|
+
points: [...nodeViz.points],
|
|
4098
|
+
rects: [...nodeViz.rects?.filter((r) => r.label?.includes("obstacle"))]
|
|
4099
|
+
};
|
|
4100
|
+
const visualizations = [
|
|
4101
|
+
problemViz,
|
|
4102
|
+
nodeViz,
|
|
4103
|
+
edgeViz,
|
|
4104
|
+
pathingViz,
|
|
4105
|
+
edgeToPortSegmentViz,
|
|
4106
|
+
segmentToPointViz,
|
|
4107
|
+
segmentOptimizationViz,
|
|
4108
|
+
highDensityViz ? combineVisualizations(problemViz, highDensityViz) : null
|
|
4109
|
+
].filter(Boolean);
|
|
4110
|
+
return combineVisualizations(...visualizations);
|
|
4111
|
+
}
|
|
4112
|
+
/**
|
|
4113
|
+
* Simplifies a route by merging consecutive points along the same line
|
|
4114
|
+
*/
|
|
4115
|
+
simplifyRoute(points) {
|
|
4116
|
+
if (points.length <= 2) return points;
|
|
4117
|
+
const result = [points[0]];
|
|
4118
|
+
for (let i = 1; i < points.length - 1; i++) {
|
|
4119
|
+
const prev = points[i - 1];
|
|
4120
|
+
const curr = points[i];
|
|
4121
|
+
const next = points[i + 1];
|
|
4122
|
+
if (curr.z === prev.z && curr.z === next.z) {
|
|
4123
|
+
const dx1 = curr.x - prev.x;
|
|
4124
|
+
const dy1 = curr.y - prev.y;
|
|
4125
|
+
const dx2 = next.x - curr.x;
|
|
4126
|
+
const dy2 = next.y - curr.y;
|
|
4127
|
+
const crossProduct = dx1 * dy2 - dy1 * dx2;
|
|
4128
|
+
const dotProduct = dx1 * dx2 + dy1 * dy2;
|
|
4129
|
+
if (Math.abs(crossProduct) < 1e-3 && dotProduct > 0) {
|
|
4130
|
+
continue;
|
|
4131
|
+
}
|
|
4132
|
+
}
|
|
4133
|
+
result.push(curr);
|
|
4134
|
+
}
|
|
4135
|
+
result.push(points[points.length - 1]);
|
|
4136
|
+
return result;
|
|
4137
|
+
}
|
|
4138
|
+
/**
|
|
4139
|
+
* Returns the SimpleRouteJson with routes converted to SimplifiedPcbTraces
|
|
4140
|
+
*/
|
|
4141
|
+
getOutputSimpleRouteJson() {
|
|
4142
|
+
if (!this.solved || !this.highDensityRouteSolver) {
|
|
4143
|
+
throw new Error("Cannot get output before solving is complete");
|
|
4144
|
+
}
|
|
4145
|
+
const traces = [];
|
|
4146
|
+
for (const route of this.highDensityRouteSolver.routes) {
|
|
4147
|
+
const simplifiedRoute = this.simplifyRoute(route.route);
|
|
4148
|
+
const trace = {
|
|
4149
|
+
type: "pcb_trace",
|
|
4150
|
+
pcb_trace_id: route.connectionName,
|
|
4151
|
+
route: []
|
|
4152
|
+
};
|
|
4153
|
+
let currentLayer = simplifiedRoute[0]?.z.toString() || "0";
|
|
4154
|
+
for (let i = 0; i < simplifiedRoute.length; i++) {
|
|
4155
|
+
const point = simplifiedRoute[i];
|
|
4156
|
+
const nextLayerStr = point.z.toString();
|
|
4157
|
+
if (nextLayerStr !== currentLayer) {
|
|
4158
|
+
trace.route.push({
|
|
4159
|
+
route_type: "via",
|
|
4160
|
+
x: point.x,
|
|
4161
|
+
y: point.y,
|
|
4162
|
+
from_layer: currentLayer,
|
|
4163
|
+
to_layer: nextLayerStr
|
|
4164
|
+
});
|
|
4165
|
+
currentLayer = nextLayerStr;
|
|
4166
|
+
}
|
|
4167
|
+
trace.route.push({
|
|
4168
|
+
route_type: "wire",
|
|
4169
|
+
x: point.x,
|
|
4170
|
+
y: point.y,
|
|
4171
|
+
width: route.traceThickness || this.highDensityRouteSolver.defaultTraceThickness,
|
|
4172
|
+
layer: currentLayer
|
|
4173
|
+
});
|
|
4174
|
+
}
|
|
4175
|
+
traces.push(trace);
|
|
4176
|
+
}
|
|
4177
|
+
return {
|
|
4178
|
+
...this.srj,
|
|
4179
|
+
traces
|
|
4180
|
+
};
|
|
4181
|
+
}
|
|
4182
|
+
};
|
|
4183
|
+
export {
|
|
4184
|
+
CapacityMeshSolver,
|
|
4185
|
+
calculateOptimalCapacityDepth,
|
|
4186
|
+
getTunedTotalCapacity1
|
|
4187
|
+
};
|
|
4188
|
+
//# sourceMappingURL=index.js.map
|