@tscircuit/core 0.0.133 → 0.0.135
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +87 -2
- package/dist/index.js +201 -11
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ type RenderPhase = (typeof orderedRenderPhases)[number];
|
|
|
13
13
|
type RenderPhaseFn<K extends RenderPhase = RenderPhase> = `doInitial${K}` | `update${K}` | `remove${K}`;
|
|
14
14
|
type RenderPhaseStates = Record<RenderPhase, {
|
|
15
15
|
initialized: boolean;
|
|
16
|
+
dirty: boolean;
|
|
16
17
|
}>;
|
|
17
18
|
type RenderPhaseFunctions = {
|
|
18
19
|
[T in RenderPhaseFn]?: () => void;
|
|
@@ -34,7 +35,12 @@ declare abstract class Renderable implements IRenderable {
|
|
|
34
35
|
/** Schematic-only, lines, boxes, indicators etc. */
|
|
35
36
|
isSchematicPrimitive: boolean;
|
|
36
37
|
_renderId: string;
|
|
38
|
+
_currentRenderPhase: RenderPhase | null;
|
|
39
|
+
private _asyncEffects;
|
|
37
40
|
constructor(props: any);
|
|
41
|
+
protected _markDirty(phase: RenderPhase): void;
|
|
42
|
+
protected _queueAsyncEffect(effect: () => Promise<void>): void;
|
|
43
|
+
_hasIncompleteAsyncEffects(): boolean;
|
|
38
44
|
runRenderCycle(): void;
|
|
39
45
|
/**
|
|
40
46
|
* This runs all the render methods for a given phase, calling one of:
|
|
@@ -53,6 +59,7 @@ type ReactSubtree = {
|
|
|
53
59
|
component: NormalComponent;
|
|
54
60
|
};
|
|
55
61
|
|
|
62
|
+
type RootCircuitEventName = "asyncEffectComplete";
|
|
56
63
|
declare class Circuit {
|
|
57
64
|
firstChild: PrimitiveComponent | null;
|
|
58
65
|
children: PrimitiveComponent[];
|
|
@@ -71,6 +78,8 @@ declare class Circuit {
|
|
|
71
78
|
};
|
|
72
79
|
_guessRootComponent(): void;
|
|
73
80
|
render(): void;
|
|
81
|
+
renderUntilSettled(): Promise<void>;
|
|
82
|
+
private _hasIncompleteAsyncEffects;
|
|
74
83
|
getSoup(): AnyCircuitElement[];
|
|
75
84
|
getCircuitJson(): AnyCircuitElement[];
|
|
76
85
|
toJson(): AnyCircuitElement[];
|
|
@@ -88,12 +97,19 @@ declare class Circuit {
|
|
|
88
97
|
selectOne(selector: string, opts?: {
|
|
89
98
|
type?: "component" | "port";
|
|
90
99
|
}): PrimitiveComponent | null;
|
|
100
|
+
_eventListeners: Record<RootCircuitEventName, Array<(...args: any[]) => void>>;
|
|
101
|
+
emit(event: RootCircuitEventName, ...args: any[]): void;
|
|
102
|
+
on(event: RootCircuitEventName, listener: (...args: any[]) => void): void;
|
|
91
103
|
}
|
|
92
104
|
/**
|
|
93
105
|
* @deprecated
|
|
94
106
|
*/
|
|
95
107
|
declare const Project: typeof Circuit;
|
|
96
108
|
|
|
109
|
+
interface ISubcircuit extends PrimitiveComponent {
|
|
110
|
+
_shouldUseTraceByTraceRouting(): boolean;
|
|
111
|
+
}
|
|
112
|
+
|
|
97
113
|
interface BaseComponentConfig {
|
|
98
114
|
componentName: string;
|
|
99
115
|
schematicSymbolName?: BaseSymbolName | null;
|
|
@@ -244,7 +260,7 @@ declare abstract class PrimitiveComponent<ZodProps extends ZodType = any> extend
|
|
|
244
260
|
height: number;
|
|
245
261
|
};
|
|
246
262
|
doesSelectorMatch(selector: string): boolean;
|
|
247
|
-
getSubcircuit():
|
|
263
|
+
getSubcircuit(): ISubcircuit;
|
|
248
264
|
selectAll(selector: string): PrimitiveComponent[];
|
|
249
265
|
selectOne(selector: string, options?: {
|
|
250
266
|
type?: string;
|
|
@@ -297,6 +313,10 @@ declare class Net extends PrimitiveComponent<typeof netProps> {
|
|
|
297
313
|
* ports that are connected to each other. If a there are multiple net islands
|
|
298
314
|
* that means that the net is not fully connected and we need to add traces
|
|
299
315
|
* such that the nets are fully connected
|
|
316
|
+
*
|
|
317
|
+
* Sometimes this phase doesn't find any net islands if the autorouter did
|
|
318
|
+
* a good job and connected the islands. In some sense this is a "backup"
|
|
319
|
+
* routing phase for autorouters that don't care about connecting nets.
|
|
300
320
|
*/
|
|
301
321
|
doInitialPcbRouteNetIslands(): void;
|
|
302
322
|
}
|
|
@@ -718,13 +738,78 @@ declare class NormalComponent<ZodProps extends ZodType = any, PortNames extends
|
|
|
718
738
|
doInitialCadModelRender(): void;
|
|
719
739
|
}
|
|
720
740
|
|
|
721
|
-
|
|
741
|
+
type SimplifiedPcbTrace = {
|
|
742
|
+
type: "pcb_trace";
|
|
743
|
+
pcb_trace_id: string;
|
|
744
|
+
route: Array<{
|
|
745
|
+
route_type: "wire";
|
|
746
|
+
x: number;
|
|
747
|
+
y: number;
|
|
748
|
+
width: number;
|
|
749
|
+
layer: string;
|
|
750
|
+
} | {
|
|
751
|
+
route_type: "via";
|
|
752
|
+
x: number;
|
|
753
|
+
y: number;
|
|
754
|
+
to_layer: string;
|
|
755
|
+
from_layer: string;
|
|
756
|
+
}>;
|
|
757
|
+
};
|
|
758
|
+
type Obstacle = {
|
|
759
|
+
type: "rect";
|
|
760
|
+
layers: string[];
|
|
761
|
+
center: {
|
|
762
|
+
x: number;
|
|
763
|
+
y: number;
|
|
764
|
+
};
|
|
765
|
+
width: number;
|
|
766
|
+
height: number;
|
|
767
|
+
connectedTo: string[];
|
|
768
|
+
};
|
|
769
|
+
interface SimpleRouteConnection {
|
|
770
|
+
name: string;
|
|
771
|
+
pointsToConnect: Array<{
|
|
772
|
+
x: number;
|
|
773
|
+
y: number;
|
|
774
|
+
layer: string;
|
|
775
|
+
}>;
|
|
776
|
+
}
|
|
777
|
+
interface SimpleRouteJson {
|
|
778
|
+
layerCount: number;
|
|
779
|
+
minTraceWidth: number;
|
|
780
|
+
obstacles: Obstacle[];
|
|
781
|
+
connections: Array<SimpleRouteConnection>;
|
|
782
|
+
bounds: {
|
|
783
|
+
minX: number;
|
|
784
|
+
maxX: number;
|
|
785
|
+
minY: number;
|
|
786
|
+
maxY: number;
|
|
787
|
+
};
|
|
788
|
+
traces?: SimplifiedPcbTrace[];
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
declare class Group<Props extends z.ZodType<any, any, any> = typeof groupProps> extends NormalComponent<Props> implements ISubcircuit {
|
|
792
|
+
_asyncAutoroutingResult: {
|
|
793
|
+
output_simple_route_json: SimpleRouteJson;
|
|
794
|
+
} | null;
|
|
722
795
|
get config(): {
|
|
723
796
|
zodProps: Props;
|
|
724
797
|
componentName: string;
|
|
725
798
|
};
|
|
726
799
|
doInitialCreateTraceHintsFromProps(): void;
|
|
800
|
+
_getSimpleRouteJsonFromPcbTraces(): SimpleRouteJson;
|
|
801
|
+
doInitialPcbTraceRender(): void;
|
|
802
|
+
updatePcbTraceRender(): void;
|
|
727
803
|
doInitialSchematicLayout(): void;
|
|
804
|
+
/**
|
|
805
|
+
* Trace-by-trace autorouting is where each trace routes itself in a well-known
|
|
806
|
+
* order. It's the most deterministic way to autoroute, because a new trace
|
|
807
|
+
* is generally ordered last.
|
|
808
|
+
*
|
|
809
|
+
* This method will return false if using an external service for autorouting
|
|
810
|
+
* or if using a "fullview" or "rip and replace" autorouting mode
|
|
811
|
+
*/
|
|
812
|
+
_shouldUseTraceByTraceRouting(): boolean;
|
|
728
813
|
}
|
|
729
814
|
|
|
730
815
|
declare class Board extends Group<typeof boardProps> {
|
package/dist/index.js
CHANGED
|
@@ -87,14 +87,61 @@ var Renderable = class {
|
|
|
87
87
|
/** Schematic-only, lines, boxes, indicators etc. */
|
|
88
88
|
isSchematicPrimitive = false;
|
|
89
89
|
_renderId;
|
|
90
|
+
_currentRenderPhase = null;
|
|
91
|
+
_asyncEffects = [];
|
|
90
92
|
constructor(props) {
|
|
91
93
|
this._renderId = `${globalRenderCounter++}`;
|
|
92
94
|
this.children = [];
|
|
93
95
|
this.renderPhaseStates = {};
|
|
94
96
|
for (const phase of orderedRenderPhases) {
|
|
95
|
-
this.renderPhaseStates[phase] = {
|
|
97
|
+
this.renderPhaseStates[phase] = {
|
|
98
|
+
initialized: false,
|
|
99
|
+
dirty: false
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
_markDirty(phase) {
|
|
104
|
+
this.renderPhaseStates[phase].dirty = true;
|
|
105
|
+
const phaseIndex = orderedRenderPhases.indexOf(phase);
|
|
106
|
+
for (let i = phaseIndex + 1; i < orderedRenderPhases.length; i++) {
|
|
107
|
+
this.renderPhaseStates[orderedRenderPhases[i]].dirty = true;
|
|
96
108
|
}
|
|
97
109
|
}
|
|
110
|
+
_queueAsyncEffect(effect) {
|
|
111
|
+
const asyncEffect = {
|
|
112
|
+
promise: effect(),
|
|
113
|
+
// TODO don't start effects until end of render cycle
|
|
114
|
+
phase: this._currentRenderPhase,
|
|
115
|
+
complete: false
|
|
116
|
+
};
|
|
117
|
+
this._asyncEffects.push(asyncEffect);
|
|
118
|
+
asyncEffect.promise.then(() => {
|
|
119
|
+
asyncEffect.complete = true;
|
|
120
|
+
if ("root" in this && this.root) {
|
|
121
|
+
;
|
|
122
|
+
this.root.emit("asyncEffectComplete", {
|
|
123
|
+
component: this,
|
|
124
|
+
asyncEffect
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}).catch((error) => {
|
|
128
|
+
console.error(
|
|
129
|
+
`Async effect error in ${this._currentRenderPhase}:`,
|
|
130
|
+
error
|
|
131
|
+
);
|
|
132
|
+
asyncEffect.complete = true;
|
|
133
|
+
if ("root" in this && this.root) {
|
|
134
|
+
;
|
|
135
|
+
this.root.emit("asyncEffectComplete", {
|
|
136
|
+
component: this,
|
|
137
|
+
asyncEffect
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
_hasIncompleteAsyncEffects() {
|
|
143
|
+
return this._asyncEffects.some((effect) => !effect.complete);
|
|
144
|
+
}
|
|
98
145
|
runRenderCycle() {
|
|
99
146
|
for (const renderPhase of orderedRenderPhases) {
|
|
100
147
|
this.runRenderPhaseForChildren(renderPhase);
|
|
@@ -109,22 +156,35 @@ var Renderable = class {
|
|
|
109
156
|
* ...depending on the current state of the component.
|
|
110
157
|
*/
|
|
111
158
|
runRenderPhase(phase) {
|
|
112
|
-
|
|
159
|
+
this._currentRenderPhase = phase;
|
|
160
|
+
const phaseState = this.renderPhaseStates[phase];
|
|
161
|
+
const isInitialized = phaseState.initialized;
|
|
162
|
+
const isDirty = phaseState.dirty;
|
|
113
163
|
if (!isInitialized && this.shouldBeRemoved) return;
|
|
114
164
|
if (this.shouldBeRemoved && isInitialized) {
|
|
115
165
|
;
|
|
116
166
|
this?.[`remove${phase}`]?.();
|
|
117
|
-
|
|
167
|
+
phaseState.initialized = false;
|
|
168
|
+
phaseState.dirty = false;
|
|
118
169
|
return;
|
|
119
170
|
}
|
|
171
|
+
const prevPhaseIndex = orderedRenderPhases.indexOf(phase) - 1;
|
|
172
|
+
if (prevPhaseIndex >= 0) {
|
|
173
|
+
const prevPhase = orderedRenderPhases[prevPhaseIndex];
|
|
174
|
+
const hasIncompleteEffects = this._asyncEffects.filter((e) => e.phase === prevPhase).some((e) => !e.complete);
|
|
175
|
+
if (hasIncompleteEffects) return;
|
|
176
|
+
}
|
|
120
177
|
if (isInitialized) {
|
|
121
|
-
|
|
122
|
-
|
|
178
|
+
if (isDirty) {
|
|
179
|
+
;
|
|
180
|
+
this?.[`update${phase}`]?.();
|
|
181
|
+
phaseState.dirty = false;
|
|
182
|
+
}
|
|
123
183
|
return;
|
|
124
184
|
}
|
|
125
|
-
;
|
|
185
|
+
phaseState.dirty = false;
|
|
126
186
|
this?.[`doInitial${phase}`]?.();
|
|
127
|
-
|
|
187
|
+
phaseState.initialized = true;
|
|
128
188
|
}
|
|
129
189
|
runRenderPhaseForChildren(phase) {
|
|
130
190
|
for (const child of this.children) {
|
|
@@ -843,6 +903,10 @@ var Net = class extends PrimitiveComponent {
|
|
|
843
903
|
* ports that are connected to each other. If a there are multiple net islands
|
|
844
904
|
* that means that the net is not fully connected and we need to add traces
|
|
845
905
|
* such that the nets are fully connected
|
|
906
|
+
*
|
|
907
|
+
* Sometimes this phase doesn't find any net islands if the autorouter did
|
|
908
|
+
* a good job and connected the islands. In some sense this is a "backup"
|
|
909
|
+
* routing phase for autorouters that don't care about connecting nets.
|
|
846
910
|
*/
|
|
847
911
|
doInitialPcbRouteNetIslands() {
|
|
848
912
|
const { db } = this.root;
|
|
@@ -2373,7 +2437,7 @@ var NormalComponent = class extends PrimitiveComponent {
|
|
|
2373
2437
|
import { boardProps } from "@tscircuit/props";
|
|
2374
2438
|
import { identity as identity4 } from "transformation-matrix";
|
|
2375
2439
|
|
|
2376
|
-
// lib/components/primitive-components/Group.ts
|
|
2440
|
+
// lib/components/primitive-components/Group/Group.ts
|
|
2377
2441
|
import {
|
|
2378
2442
|
groupProps
|
|
2379
2443
|
} from "@tscircuit/props";
|
|
@@ -2440,9 +2504,11 @@ var TraceHint = class extends PrimitiveComponent {
|
|
|
2440
2504
|
}
|
|
2441
2505
|
};
|
|
2442
2506
|
|
|
2443
|
-
// lib/components/primitive-components/Group.ts
|
|
2507
|
+
// lib/components/primitive-components/Group/Group.ts
|
|
2444
2508
|
import * as SAL from "@tscircuit/schematic-autolayout";
|
|
2509
|
+
import { getObstaclesFromSoup } from "@tscircuit/infgrid-ijump-astar";
|
|
2445
2510
|
var Group = class extends NormalComponent {
|
|
2511
|
+
_asyncAutoroutingResult = null;
|
|
2446
2512
|
get config() {
|
|
2447
2513
|
return {
|
|
2448
2514
|
zodProps: groupProps,
|
|
@@ -2465,6 +2531,87 @@ var Group = class extends NormalComponent {
|
|
|
2465
2531
|
);
|
|
2466
2532
|
}
|
|
2467
2533
|
}
|
|
2534
|
+
_getSimpleRouteJsonFromPcbTraces() {
|
|
2535
|
+
const traces = this.selectAll("trace");
|
|
2536
|
+
const { db } = this.root;
|
|
2537
|
+
const obstacles = getObstaclesFromSoup([
|
|
2538
|
+
...db.pcb_component.list(),
|
|
2539
|
+
...db.pcb_smtpad.list(),
|
|
2540
|
+
...db.pcb_plated_hole.list()
|
|
2541
|
+
]);
|
|
2542
|
+
const allPoints = obstacles.flatMap((o) => [
|
|
2543
|
+
{
|
|
2544
|
+
x: o.center.x - o.width / 2,
|
|
2545
|
+
y: o.center.y - o.height / 2
|
|
2546
|
+
},
|
|
2547
|
+
{
|
|
2548
|
+
x: o.center.x + o.width / 2,
|
|
2549
|
+
y: o.center.y + o.height / 2
|
|
2550
|
+
}
|
|
2551
|
+
]);
|
|
2552
|
+
const bounds = {
|
|
2553
|
+
minX: Math.min(...allPoints.map((p) => p.x)) - 1,
|
|
2554
|
+
maxX: Math.max(...allPoints.map((p) => p.x)) + 1,
|
|
2555
|
+
minY: Math.min(...allPoints.map((p) => p.y)) - 1,
|
|
2556
|
+
maxY: Math.max(...allPoints.map((p) => p.y)) + 1
|
|
2557
|
+
};
|
|
2558
|
+
const connections = traces.map((trace) => {
|
|
2559
|
+
const connectedPorts = trace._findConnectedPorts();
|
|
2560
|
+
if (!connectedPorts.allPortsFound || connectedPorts.ports.length < 2)
|
|
2561
|
+
return null;
|
|
2562
|
+
return {
|
|
2563
|
+
name: trace.source_trace_id ?? "",
|
|
2564
|
+
pointsToConnect: connectedPorts.ports.map((port) => {
|
|
2565
|
+
const pos = port._getGlobalPcbPositionBeforeLayout();
|
|
2566
|
+
return {
|
|
2567
|
+
x: pos.x,
|
|
2568
|
+
y: pos.y,
|
|
2569
|
+
layer: port.getAvailablePcbLayers()[0] ?? "top"
|
|
2570
|
+
};
|
|
2571
|
+
})
|
|
2572
|
+
};
|
|
2573
|
+
}).filter((c) => c !== null);
|
|
2574
|
+
return {
|
|
2575
|
+
bounds,
|
|
2576
|
+
obstacles: [],
|
|
2577
|
+
connections,
|
|
2578
|
+
layerCount: 2,
|
|
2579
|
+
minTraceWidth: this._parsedProps.minTraceWidth ?? 0.1
|
|
2580
|
+
};
|
|
2581
|
+
}
|
|
2582
|
+
doInitialPcbTraceRender() {
|
|
2583
|
+
if (this._shouldUseTraceByTraceRouting()) return;
|
|
2584
|
+
if (this.props.autorouter?.serverUrl) {
|
|
2585
|
+
this._queueAsyncEffect(async () => {
|
|
2586
|
+
const { autorouting_result } = await fetch(
|
|
2587
|
+
this.props.autorouter.serverUrl,
|
|
2588
|
+
{
|
|
2589
|
+
method: "POST",
|
|
2590
|
+
body: JSON.stringify({
|
|
2591
|
+
input_simple_route_json: this._getSimpleRouteJsonFromPcbTraces()
|
|
2592
|
+
})
|
|
2593
|
+
}
|
|
2594
|
+
).then((r) => r.json());
|
|
2595
|
+
const { output_simple_route_json } = autorouting_result;
|
|
2596
|
+
this._asyncAutoroutingResult = autorouting_result;
|
|
2597
|
+
this._markDirty("PcbTraceRender");
|
|
2598
|
+
});
|
|
2599
|
+
}
|
|
2600
|
+
}
|
|
2601
|
+
updatePcbTraceRender() {
|
|
2602
|
+
if (!this._asyncAutoroutingResult) return;
|
|
2603
|
+
if (this._shouldUseTraceByTraceRouting()) return;
|
|
2604
|
+
const { db } = this.root;
|
|
2605
|
+
const { traces: routedTraces } = this._asyncAutoroutingResult.output_simple_route_json;
|
|
2606
|
+
if (!routedTraces) return;
|
|
2607
|
+
const circuitTraces = this.selectAll("trace");
|
|
2608
|
+
for (const routedTrace of routedTraces) {
|
|
2609
|
+
const pcb_trace = db.pcb_trace.insert({
|
|
2610
|
+
route: routedTrace.route
|
|
2611
|
+
// source_trace_id: circuitTrace.source_trace_id!,
|
|
2612
|
+
});
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2468
2615
|
doInitialSchematicLayout() {
|
|
2469
2616
|
if (!this.isSubcircuit) return;
|
|
2470
2617
|
const props = this._parsedProps;
|
|
@@ -2491,6 +2638,18 @@ var Group = class extends NormalComponent {
|
|
|
2491
2638
|
const laidOutScene = SAL.ascendingCentralLrBug1(scene);
|
|
2492
2639
|
SAL.mutateSoupForScene(db.toArray(), laidOutScene);
|
|
2493
2640
|
}
|
|
2641
|
+
/**
|
|
2642
|
+
* Trace-by-trace autorouting is where each trace routes itself in a well-known
|
|
2643
|
+
* order. It's the most deterministic way to autoroute, because a new trace
|
|
2644
|
+
* is generally ordered last.
|
|
2645
|
+
*
|
|
2646
|
+
* This method will return false if using an external service for autorouting
|
|
2647
|
+
* or if using a "fullview" or "rip and replace" autorouting mode
|
|
2648
|
+
*/
|
|
2649
|
+
_shouldUseTraceByTraceRouting() {
|
|
2650
|
+
if (this.props.autorouter) return false;
|
|
2651
|
+
return true;
|
|
2652
|
+
}
|
|
2494
2653
|
};
|
|
2495
2654
|
|
|
2496
2655
|
// lib/components/normal-components/Board.ts
|
|
@@ -2557,7 +2716,7 @@ var FTYPE = stringProxy;
|
|
|
2557
2716
|
// lib/components/primitive-components/Trace.ts
|
|
2558
2717
|
import {
|
|
2559
2718
|
MultilayerIjump,
|
|
2560
|
-
getObstaclesFromSoup
|
|
2719
|
+
getObstaclesFromSoup as getObstaclesFromSoup2
|
|
2561
2720
|
} from "@tscircuit/infgrid-ijump-astar";
|
|
2562
2721
|
import { traceProps } from "@tscircuit/props";
|
|
2563
2722
|
import { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map";
|
|
@@ -2938,6 +3097,9 @@ searched component ${targetComponent.getString()}, which has ports: ${targetComp
|
|
|
2938
3097
|
if (this.getSubcircuit()._parsedProps.routingDisabled) {
|
|
2939
3098
|
return;
|
|
2940
3099
|
}
|
|
3100
|
+
if (!this.getSubcircuit()._shouldUseTraceByTraceRouting()) {
|
|
3101
|
+
return;
|
|
3102
|
+
}
|
|
2941
3103
|
const { allPortsFound, ports } = this._findConnectedPorts();
|
|
2942
3104
|
const portsConnectedOnPcbViaNet = [];
|
|
2943
3105
|
if (!allPortsFound) return;
|
|
@@ -3034,7 +3196,7 @@ searched component ${targetComponent.getString()}, which has ports: ${targetComp
|
|
|
3034
3196
|
this.root.db.toArray()
|
|
3035
3197
|
);
|
|
3036
3198
|
const [obstacles, errGettingObstacles] = tryNow(
|
|
3037
|
-
() =>
|
|
3199
|
+
() => getObstaclesFromSoup2(this.root.db.toArray())
|
|
3038
3200
|
// Remove as any when autorouting-dataset gets updated
|
|
3039
3201
|
);
|
|
3040
3202
|
if (errGettingObstacles) {
|
|
@@ -4396,6 +4558,21 @@ var Circuit = class {
|
|
|
4396
4558
|
firstChild.runRenderCycle();
|
|
4397
4559
|
this._hasRenderedAtleastOnce = true;
|
|
4398
4560
|
}
|
|
4561
|
+
async renderUntilSettled() {
|
|
4562
|
+
this.render();
|
|
4563
|
+
while (this._hasIncompleteAsyncEffects()) {
|
|
4564
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
4565
|
+
this.render();
|
|
4566
|
+
}
|
|
4567
|
+
}
|
|
4568
|
+
_hasIncompleteAsyncEffects() {
|
|
4569
|
+
return this.children.some((child) => {
|
|
4570
|
+
if (child._hasIncompleteAsyncEffects()) return true;
|
|
4571
|
+
return child.children.some(
|
|
4572
|
+
(grandchild) => grandchild._hasIncompleteAsyncEffects()
|
|
4573
|
+
);
|
|
4574
|
+
});
|
|
4575
|
+
}
|
|
4399
4576
|
getSoup() {
|
|
4400
4577
|
if (!this._hasRenderedAtleastOnce) this.render();
|
|
4401
4578
|
return this.db.toArray();
|
|
@@ -4432,6 +4609,19 @@ var Circuit = class {
|
|
|
4432
4609
|
selectOne(selector, opts) {
|
|
4433
4610
|
return this.firstChild?.selectOne(selector, opts) ?? null;
|
|
4434
4611
|
}
|
|
4612
|
+
_eventListeners = { asyncEffectComplete: [] };
|
|
4613
|
+
emit(event, ...args) {
|
|
4614
|
+
if (!this._eventListeners[event]) return;
|
|
4615
|
+
for (const listener of this._eventListeners[event]) {
|
|
4616
|
+
listener(...args);
|
|
4617
|
+
}
|
|
4618
|
+
}
|
|
4619
|
+
on(event, listener) {
|
|
4620
|
+
if (!this._eventListeners[event]) {
|
|
4621
|
+
this._eventListeners[event] = [];
|
|
4622
|
+
}
|
|
4623
|
+
this._eventListeners[event].push(listener);
|
|
4624
|
+
}
|
|
4435
4625
|
};
|
|
4436
4626
|
var Project = Circuit;
|
|
4437
4627
|
|