@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/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