@schemd/core 0.2.0 → 0.3.0
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/CHANGELOG.md +46 -0
- package/README.md +91 -27
- package/dist/compiler.d.ts +39 -0
- package/dist/compiler.js +17 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -3
- package/dist/layout.d.ts +49 -2
- package/dist/layout.js +556 -73
- package/dist/math-label.js +1 -22
- package/dist/parser.js +417 -42
- package/dist/renderer.js +377 -65
- package/dist/route-cache.d.ts +6 -0
- package/dist/route-cache.js +10 -0
- package/dist/types.d.ts +124 -19
- package/dist/types.js +167 -4
- package/dist/xml.d.ts +6 -0
- package/dist/xml.js +38 -0
- package/package.json +83 -74
package/dist/layout.js
CHANGED
|
@@ -1,12 +1,68 @@
|
|
|
1
|
-
import { CLASSICAL_GATE_KINDS, UML_COMPONENT_KINDS, SchematicSyntaxError } from './types.js';
|
|
1
|
+
import { CLASSICAL_GATE_KINDS, DIGITAL_COMPONENT_KINDS, QUANTUM_SPECIAL_KINDS, UML_COMPONENT_KINDS, SchematicSyntaxError } from './types.js';
|
|
2
2
|
import { mathLabelTextWidth } from './math-label.js';
|
|
3
3
|
import { MAX_SCHEMATIC_WIRE_CROSSINGS } from './limits.js';
|
|
4
4
|
export const PORT_HOTSPOT_RADIUS = 4;
|
|
5
5
|
export const SCHEMATIC_OBSTACLE_CLEARANCE = 12;
|
|
6
6
|
export const SCHEMATIC_BRIDGE_RADIUS = 5;
|
|
7
|
+
export function orientationQuarterTurn(orientation) {
|
|
8
|
+
switch (orientation) {
|
|
9
|
+
case 'right':
|
|
10
|
+
return 0;
|
|
11
|
+
case 'down':
|
|
12
|
+
return 1;
|
|
13
|
+
case 'left':
|
|
14
|
+
return 2;
|
|
15
|
+
case 'up':
|
|
16
|
+
return 3;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function normalizedQuarterTurn(value) {
|
|
20
|
+
switch (value & 3) {
|
|
21
|
+
case 0:
|
|
22
|
+
return 0;
|
|
23
|
+
case 1:
|
|
24
|
+
return 1;
|
|
25
|
+
case 2:
|
|
26
|
+
return 2;
|
|
27
|
+
default:
|
|
28
|
+
return 3;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export function composeQuarterTurns(first, second) {
|
|
32
|
+
return normalizedQuarterTurn(first + second);
|
|
33
|
+
}
|
|
34
|
+
export function inverseQuarterTurn(turn) {
|
|
35
|
+
return normalizedQuarterTurn(4 - turn);
|
|
36
|
+
}
|
|
37
|
+
function negatedCoordinate(value) {
|
|
38
|
+
return value === 0 ? 0 : -value;
|
|
39
|
+
}
|
|
40
|
+
function canonicalCoordinate(value) {
|
|
41
|
+
return value === 0 ? 0 : value;
|
|
42
|
+
}
|
|
43
|
+
export function rotateQuarterPoint(point, turn) {
|
|
44
|
+
switch (turn) {
|
|
45
|
+
case 0:
|
|
46
|
+
return { x: canonicalCoordinate(point.x), y: canonicalCoordinate(point.y) };
|
|
47
|
+
case 1:
|
|
48
|
+
return { x: negatedCoordinate(point.y), y: canonicalCoordinate(point.x) };
|
|
49
|
+
case 2:
|
|
50
|
+
return { x: negatedCoordinate(point.x), y: negatedCoordinate(point.y) };
|
|
51
|
+
case 3:
|
|
52
|
+
return { x: canonicalCoordinate(point.y), y: negatedCoordinate(point.x) };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function rotateQuarterExtents(extents, turn) {
|
|
56
|
+
const halfWidth = canonicalCoordinate(extents.halfWidth);
|
|
57
|
+
const halfHeight = canonicalCoordinate(extents.halfHeight);
|
|
58
|
+
return turn === 1 || turn === 3
|
|
59
|
+
? { halfWidth: halfHeight, halfHeight: halfWidth }
|
|
60
|
+
: { halfWidth, halfHeight };
|
|
61
|
+
}
|
|
7
62
|
const MAX_ROUTER_STATES = 40_000;
|
|
8
63
|
const ROUTER_BEND_PENALTY = 0.25;
|
|
9
64
|
const CROSSING_BUCKET_SIZE = 64;
|
|
65
|
+
const MIN_RENDERABLE_BRIDGE_RADIUS = 0.001;
|
|
10
66
|
const DESIGNATOR_BASELINE_GAP = 10;
|
|
11
67
|
const LABEL_BASELINE_GAP = 19;
|
|
12
68
|
const TEXT_ASCENT_GUTTER = 12;
|
|
@@ -30,26 +86,96 @@ export function distributedCoordinate(index, count, span) {
|
|
|
30
86
|
export function classicalGateHeight(component) {
|
|
31
87
|
return Math.max(48, Math.max(component.inputs, component.outputs) * 16);
|
|
32
88
|
}
|
|
89
|
+
export function quantumGateDimensions(component) {
|
|
90
|
+
const details = [component.parameter, component.phase, component.matrix].filter((value) => value !== undefined);
|
|
91
|
+
let widest = 16;
|
|
92
|
+
if (details.length > 0 && component.kind === 'qgate')
|
|
93
|
+
widest = mathLabelTextWidth(component.label, 8);
|
|
94
|
+
for (const detail of details)
|
|
95
|
+
widest = Math.max(widest, mathLabelTextWidth(detail, 7));
|
|
96
|
+
const bodyWidth = Math.max(50, Math.min(104, widest + 20));
|
|
97
|
+
const bodyHeight = 50 + details.length * 15;
|
|
98
|
+
return { bodyWidth, bodyHeight, stubExtent: bodyWidth / 2 + 23 };
|
|
99
|
+
}
|
|
33
100
|
function isClassicalGate(component) {
|
|
34
101
|
return CLASSICAL_GATE_KINDS.includes(component.kind);
|
|
35
102
|
}
|
|
103
|
+
function isDigitalComponent(component) {
|
|
104
|
+
return DIGITAL_COMPONENT_KINDS.includes(component.kind);
|
|
105
|
+
}
|
|
106
|
+
function isQuantumSpecial(component) {
|
|
107
|
+
return QUANTUM_SPECIAL_KINDS.includes(component.kind);
|
|
108
|
+
}
|
|
36
109
|
function isUmlComponent(component) {
|
|
37
110
|
return UML_COMPONENT_KINDS.includes(component.kind);
|
|
38
111
|
}
|
|
112
|
+
function componentTurn(component) {
|
|
113
|
+
return 'orientation' in component && component.orientation !== undefined
|
|
114
|
+
? orientationQuarterTurn(component.orientation)
|
|
115
|
+
: 0;
|
|
116
|
+
}
|
|
117
|
+
function positionedQuarterPoint(component, local) {
|
|
118
|
+
const rotated = rotateQuarterPoint(local, componentTurn(component));
|
|
119
|
+
return { x: component.x + rotated.x, y: component.y + rotated.y };
|
|
120
|
+
}
|
|
121
|
+
function positionedQuarterNormal(component, normal) {
|
|
122
|
+
return rotateQuarterPoint(normal, componentTurn(component));
|
|
123
|
+
}
|
|
39
124
|
function umlHalfExtents(component) {
|
|
40
125
|
switch (component.kind) {
|
|
41
126
|
case 'class':
|
|
127
|
+
case 'interface':
|
|
128
|
+
case 'enumeration':
|
|
129
|
+
case 'datatype':
|
|
130
|
+
case 'object':
|
|
42
131
|
case 'state':
|
|
43
132
|
case 'usecase':
|
|
44
133
|
case 'lifeline':
|
|
45
134
|
case 'note':
|
|
46
135
|
case 'package':
|
|
136
|
+
case 'component':
|
|
137
|
+
case 'artifact':
|
|
138
|
+
case 'node':
|
|
139
|
+
case 'device':
|
|
140
|
+
case 'execution':
|
|
141
|
+
case 'system':
|
|
142
|
+
case 'action':
|
|
143
|
+
case 'object-node':
|
|
144
|
+
case 'partition':
|
|
145
|
+
case 'activation':
|
|
146
|
+
case 'fragment':
|
|
147
|
+
case 'interaction':
|
|
148
|
+
case 'region':
|
|
47
149
|
return { halfWidth: component.bodyWidth / 2, halfHeight: component.bodyHeight / 2 };
|
|
48
150
|
case 'actor':
|
|
49
151
|
return { halfWidth: 24, halfHeight: 50 };
|
|
50
152
|
case 'initial':
|
|
51
153
|
case 'final':
|
|
154
|
+
case 'activity-final':
|
|
155
|
+
case 'flow-final':
|
|
156
|
+
case 'provided-interface':
|
|
157
|
+
case 'required-interface':
|
|
158
|
+
case 'component-port':
|
|
159
|
+
case 'destruction':
|
|
160
|
+
case 'gate':
|
|
161
|
+
case 'found':
|
|
162
|
+
case 'lost':
|
|
163
|
+
case 'state-junction':
|
|
164
|
+
case 'history':
|
|
165
|
+
case 'entry':
|
|
166
|
+
case 'exit':
|
|
167
|
+
case 'terminate':
|
|
52
168
|
return { halfWidth: 12, halfHeight: 12 };
|
|
169
|
+
case 'decision':
|
|
170
|
+
case 'merge':
|
|
171
|
+
case 'choice':
|
|
172
|
+
return { halfWidth: 18, halfHeight: 18 };
|
|
173
|
+
case 'fork':
|
|
174
|
+
case 'join':
|
|
175
|
+
return { halfWidth: 38, halfHeight: 5 };
|
|
176
|
+
case 'send-signal':
|
|
177
|
+
case 'receive-signal':
|
|
178
|
+
return { halfWidth: 22, halfHeight: 15 };
|
|
53
179
|
}
|
|
54
180
|
}
|
|
55
181
|
function umlPortPoint(component, port) {
|
|
@@ -81,28 +207,30 @@ function indexedPortPoint(component, port) {
|
|
|
81
207
|
const count = output ? component.outputs : component.inputs;
|
|
82
208
|
const suffix = port.match(/\d+$/)?.[0];
|
|
83
209
|
const index = suffix === undefined ? 0 : Number(suffix) - 1;
|
|
84
|
-
return {
|
|
85
|
-
x:
|
|
86
|
-
y:
|
|
87
|
-
};
|
|
210
|
+
return positionedQuarterPoint(component, {
|
|
211
|
+
x: output ? 48 : -48,
|
|
212
|
+
y: distributedCoordinate(index, count, classicalGateHeight(component))
|
|
213
|
+
});
|
|
88
214
|
}
|
|
89
215
|
export function positionIcPin(component, side, index) {
|
|
90
216
|
const pins = component.pins[side];
|
|
91
217
|
if (!Number.isInteger(index) || index < 0 || index >= pins.length) {
|
|
92
218
|
throw new RangeError(`IC pin index ${index} is outside ${component.id}.${side}.`);
|
|
93
219
|
}
|
|
220
|
+
let local;
|
|
94
221
|
if (side === 'left' || side === 'right') {
|
|
95
|
-
|
|
96
|
-
x: component.
|
|
97
|
-
|
|
98
|
-
y: component.y + distributedCoordinate(index, pins.length, component.bodyHeight)
|
|
222
|
+
local = {
|
|
223
|
+
x: side === 'left' ? -component.bodyWidth / 2 - 16 : component.bodyWidth / 2 + 16,
|
|
224
|
+
y: distributedCoordinate(index, pins.length, component.bodyHeight)
|
|
99
225
|
};
|
|
100
226
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
227
|
+
else {
|
|
228
|
+
local = {
|
|
229
|
+
x: distributedCoordinate(index, pins.length, component.bodyWidth),
|
|
230
|
+
y: side === 'top' ? -component.bodyHeight / 2 - 16 : component.bodyHeight / 2 + 16
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
return positionedQuarterPoint(component, local);
|
|
106
234
|
}
|
|
107
235
|
function findIcPinLocation(component, port) {
|
|
108
236
|
const sides = ['left', 'right', 'top', 'bottom'];
|
|
@@ -149,73 +277,205 @@ export function resolvePortPoint(component, port) {
|
|
|
149
277
|
return point;
|
|
150
278
|
throw new Error(`Validated port ${component.id}.${port} is missing.`);
|
|
151
279
|
}
|
|
152
|
-
|
|
153
|
-
|
|
280
|
+
if (component.kind === 'ic') {
|
|
281
|
+
const location = icPinLocation(component, port);
|
|
282
|
+
if (location !== undefined)
|
|
283
|
+
return location.point;
|
|
284
|
+
throw new Error(`Validated port ${component.id}.${port} is missing.`);
|
|
285
|
+
}
|
|
286
|
+
let local;
|
|
287
|
+
const left = { x: -42, y: 0 };
|
|
288
|
+
const right = { x: 42, y: 0 };
|
|
154
289
|
switch (component.kind) {
|
|
155
290
|
case 'resistor':
|
|
156
291
|
case 'capacitor':
|
|
157
292
|
case 'inductor':
|
|
158
293
|
if (PASSIVE_INPUT_PORTS.has(port))
|
|
159
|
-
|
|
294
|
+
local = left;
|
|
160
295
|
if (PASSIVE_OUTPUT_PORTS.has(port))
|
|
161
|
-
|
|
296
|
+
local = right;
|
|
162
297
|
break;
|
|
163
298
|
case 'diode':
|
|
164
299
|
if (DIODE_ANODE_PORTS.has(port))
|
|
165
|
-
|
|
300
|
+
local = left;
|
|
166
301
|
if (DIODE_CATHODE_PORTS.has(port))
|
|
167
|
-
|
|
302
|
+
local = right;
|
|
168
303
|
break;
|
|
169
304
|
case 'transistor':
|
|
170
305
|
if (TRANSISTOR_CONTROL_PORTS.has(port))
|
|
171
|
-
|
|
172
|
-
if (TRANSISTOR_UPPER_PORTS.has(port))
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
return { x: component.x + 42, y: component.y + 22 };
|
|
177
|
-
}
|
|
306
|
+
local = left;
|
|
307
|
+
if (TRANSISTOR_UPPER_PORTS.has(port))
|
|
308
|
+
local = { x: 42, y: -22 };
|
|
309
|
+
if (TRANSISTOR_LOWER_PORTS.has(port))
|
|
310
|
+
local = { x: 42, y: 22 };
|
|
178
311
|
break;
|
|
179
312
|
case 'port':
|
|
180
313
|
if (port === 'in')
|
|
181
|
-
|
|
314
|
+
local = left;
|
|
182
315
|
if (port === 'out')
|
|
183
|
-
|
|
316
|
+
local = right;
|
|
184
317
|
break;
|
|
185
318
|
case 'ground':
|
|
186
319
|
if (port === 'in')
|
|
187
|
-
|
|
320
|
+
local = { x: 0, y: -42 };
|
|
188
321
|
break;
|
|
189
322
|
case 'hadamard':
|
|
190
323
|
case 'qgate':
|
|
324
|
+
case 'xgate':
|
|
325
|
+
case 'ygate':
|
|
326
|
+
case 'zgate':
|
|
327
|
+
case 'sgate':
|
|
328
|
+
case 'sdg':
|
|
329
|
+
case 'tgate':
|
|
330
|
+
case 'tdg':
|
|
331
|
+
case 'sx':
|
|
332
|
+
case 'phase':
|
|
333
|
+
case 'rx':
|
|
334
|
+
case 'ry':
|
|
335
|
+
case 'rz':
|
|
336
|
+
case 'ugate':
|
|
191
337
|
if (port === 'in')
|
|
192
|
-
|
|
338
|
+
local = { x: -quantumGateDimensions(component).stubExtent, y: 0 };
|
|
193
339
|
if (port === 'out')
|
|
194
|
-
|
|
340
|
+
local = { x: quantumGateDimensions(component).stubExtent, y: 0 };
|
|
195
341
|
break;
|
|
196
342
|
case 'cnot':
|
|
197
343
|
if (port === 'in')
|
|
198
|
-
|
|
344
|
+
local = left;
|
|
199
345
|
if (port === 'out')
|
|
200
|
-
|
|
346
|
+
local = right;
|
|
201
347
|
if (port === 'control')
|
|
202
|
-
|
|
348
|
+
local = { x: 0, y: -16 };
|
|
203
349
|
if (port === 'target')
|
|
204
|
-
|
|
350
|
+
local = { x: 0, y: 16 };
|
|
205
351
|
break;
|
|
206
|
-
case '
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
352
|
+
case 'junction':
|
|
353
|
+
case 'testpoint':
|
|
354
|
+
local = { x: 0, y: 0 };
|
|
355
|
+
break;
|
|
356
|
+
case 'connector':
|
|
357
|
+
local = port === 'out' ? { x: 32, y: 0 } : { x: -32, y: 0 };
|
|
358
|
+
break;
|
|
359
|
+
case 'source':
|
|
360
|
+
if (port === 'in' || port === 'negative')
|
|
361
|
+
local = left;
|
|
362
|
+
else if (port === 'out' || port === 'positive')
|
|
363
|
+
local = right;
|
|
364
|
+
else
|
|
365
|
+
local = { x: 0, y: port === 'control-positive' ? -34 : 34 };
|
|
366
|
+
break;
|
|
367
|
+
case 'power':
|
|
368
|
+
local = { x: -32, y: 0 };
|
|
369
|
+
break;
|
|
370
|
+
case 'switch':
|
|
371
|
+
if (port === 'in' || port === 'common')
|
|
372
|
+
local = left;
|
|
373
|
+
else if (port === 'normally-open')
|
|
374
|
+
local = { x: 42, y: -12 };
|
|
375
|
+
else if (port === 'normally-closed')
|
|
376
|
+
local = { x: 42, y: 12 };
|
|
377
|
+
else if (port === 'coil1')
|
|
378
|
+
local = { x: -12, y: 34 };
|
|
379
|
+
else if (port === 'coil2')
|
|
380
|
+
local = { x: 12, y: 34 };
|
|
381
|
+
else
|
|
382
|
+
local = right;
|
|
383
|
+
break;
|
|
384
|
+
case 'amplifier':
|
|
385
|
+
if (port === 'out')
|
|
386
|
+
local = { x: 48, y: 0 };
|
|
387
|
+
else if (port === 'v+')
|
|
388
|
+
local = { x: 0, y: -36 };
|
|
389
|
+
else if (port === 'v-')
|
|
390
|
+
local = { x: 0, y: 36 };
|
|
391
|
+
else
|
|
392
|
+
local = { x: -48, y: port === 'negative' ? 13 : -13 };
|
|
393
|
+
break;
|
|
394
|
+
case 'protection':
|
|
395
|
+
case 'resonator':
|
|
396
|
+
case 'meter':
|
|
397
|
+
case 'load':
|
|
398
|
+
local = port === 'out' ? right : left;
|
|
399
|
+
break;
|
|
400
|
+
case 'buffer':
|
|
401
|
+
case 'logic':
|
|
402
|
+
case 'clock':
|
|
403
|
+
case 'flipflop':
|
|
404
|
+
case 'mux':
|
|
405
|
+
case 'encoder':
|
|
406
|
+
case 'decoder':
|
|
407
|
+
case 'register':
|
|
408
|
+
case 'counter':
|
|
409
|
+
case 'adder':
|
|
410
|
+
case 'comparator':
|
|
411
|
+
case 'bus': {
|
|
412
|
+
const output = port === 'out' || port.startsWith('out') || ['q', 'nq', 'gt', 'eq', 'lt', 'tap'].includes(port) || (component.kind === 'bus' && component.variant === 'joiner' && port === 'bus');
|
|
413
|
+
const match = port.match(/\d+$/);
|
|
414
|
+
const count = output ? component.outputs : component.inputs;
|
|
415
|
+
const index = match === null ? 0 : Number(match[0]) - 1;
|
|
416
|
+
if (port === 'clock' || port === 'enable' || port === 'select')
|
|
417
|
+
local = { x: 0, y: component.bodyHeight / 2 + 16 };
|
|
418
|
+
else if (port === 'preset' || port === 'clear')
|
|
419
|
+
local = { x: 0, y: -component.bodyHeight / 2 - 16 };
|
|
420
|
+
else
|
|
421
|
+
local = {
|
|
422
|
+
x: (output ? 1 : -1) * (component.bodyWidth / 2 + 16),
|
|
423
|
+
y: distributedCoordinate(index, Math.max(1, count), component.bodyHeight)
|
|
424
|
+
};
|
|
425
|
+
break;
|
|
426
|
+
}
|
|
427
|
+
case 'measure':
|
|
428
|
+
if (port === 'classical')
|
|
429
|
+
local = { x: 0, y: 34 };
|
|
430
|
+
else
|
|
431
|
+
local = port === 'out' ? { x: 40, y: 0 } : { x: -40, y: 0 };
|
|
432
|
+
break;
|
|
433
|
+
case 'prepare':
|
|
434
|
+
local = { x: 40, y: 0 };
|
|
435
|
+
break;
|
|
436
|
+
case 'control':
|
|
437
|
+
local = port === 'control' ? { x: 0, y: 0 } : port === 'out' ? right : left;
|
|
438
|
+
break;
|
|
439
|
+
case 'reset':
|
|
440
|
+
case 'classical-bit':
|
|
441
|
+
case 'classical-register':
|
|
442
|
+
local = port === 'out' ? { x: 40, y: 0 } : { x: -40, y: 0 };
|
|
443
|
+
break;
|
|
444
|
+
case 'swap':
|
|
445
|
+
case 'cz':
|
|
446
|
+
case 'cphase':
|
|
447
|
+
case 'toffoli':
|
|
448
|
+
case 'controlled':
|
|
449
|
+
case 'barrier':
|
|
450
|
+
case 'delay': {
|
|
451
|
+
const indexed = port.match(/^(?:in|out|control|target)([1-9]\d*)$/);
|
|
452
|
+
let index = indexed === null ? 0 : Number(indexed[1]) - 1;
|
|
453
|
+
if (port.startsWith('target'))
|
|
454
|
+
index += component.controls;
|
|
455
|
+
const tracks = Math.max(component.wires, component.controls + component.targets);
|
|
456
|
+
if (port.startsWith('control') || port.startsWith('target')) {
|
|
457
|
+
local = { x: 0, y: distributedCoordinate(index, Math.max(1, tracks), Math.max(16, (tracks - 1) * 18)) };
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
local = {
|
|
461
|
+
x: port.startsWith('out') ? 42 : -42,
|
|
462
|
+
y: distributedCoordinate(index, Math.max(1, tracks), Math.max(16, (tracks - 1) * 18))
|
|
463
|
+
};
|
|
464
|
+
}
|
|
210
465
|
break;
|
|
211
466
|
}
|
|
212
467
|
}
|
|
468
|
+
if (local !== undefined)
|
|
469
|
+
return positionedQuarterPoint(component, local);
|
|
213
470
|
throw new Error(`Validated port ${component.id}.${port} is missing.`);
|
|
214
471
|
}
|
|
215
472
|
export function resolvePortGeometry(component, port) {
|
|
216
473
|
const point = resolvePortPoint(component, port);
|
|
217
474
|
if (isClassicalGate(component)) {
|
|
218
|
-
return {
|
|
475
|
+
return {
|
|
476
|
+
point,
|
|
477
|
+
normal: positionedQuarterNormal(component, port.startsWith('out') ? { x: 1, y: 0 } : { x: -1, y: 0 })
|
|
478
|
+
};
|
|
219
479
|
}
|
|
220
480
|
if (isUmlComponent(component)) {
|
|
221
481
|
if (port === 'left' || port === 'in' || port.startsWith('left')) {
|
|
@@ -226,40 +486,103 @@ export function resolvePortGeometry(component, port) {
|
|
|
226
486
|
}
|
|
227
487
|
return { point, normal: port === 'top' ? { x: 0, y: -1 } : { x: 0, y: 1 } };
|
|
228
488
|
}
|
|
489
|
+
const geometry = (normal) => ({
|
|
490
|
+
point,
|
|
491
|
+
normal: positionedQuarterNormal(component, normal)
|
|
492
|
+
});
|
|
229
493
|
switch (component.kind) {
|
|
230
494
|
case 'resistor':
|
|
231
495
|
case 'capacitor':
|
|
232
496
|
case 'inductor':
|
|
233
|
-
return {
|
|
234
|
-
point,
|
|
235
|
-
normal: PASSIVE_OUTPUT_PORTS.has(port) ? { x: 1, y: 0 } : { x: -1, y: 0 }
|
|
236
|
-
};
|
|
497
|
+
return geometry(PASSIVE_OUTPUT_PORTS.has(port) ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
237
498
|
case 'diode':
|
|
238
|
-
return {
|
|
239
|
-
point,
|
|
240
|
-
normal: DIODE_CATHODE_PORTS.has(port) ? { x: 1, y: 0 } : { x: -1, y: 0 }
|
|
241
|
-
};
|
|
499
|
+
return geometry(DIODE_CATHODE_PORTS.has(port) ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
242
500
|
case 'transistor':
|
|
243
|
-
return {
|
|
244
|
-
point,
|
|
245
|
-
normal: TRANSISTOR_CONTROL_PORTS.has(port) ? { x: -1, y: 0 } : { x: 1, y: 0 }
|
|
246
|
-
};
|
|
501
|
+
return geometry(TRANSISTOR_CONTROL_PORTS.has(port) ? { x: -1, y: 0 } : { x: 1, y: 0 });
|
|
247
502
|
case 'port':
|
|
248
503
|
case 'hadamard':
|
|
249
504
|
case 'qgate':
|
|
250
|
-
|
|
505
|
+
case 'xgate':
|
|
506
|
+
case 'ygate':
|
|
507
|
+
case 'zgate':
|
|
508
|
+
case 'sgate':
|
|
509
|
+
case 'sdg':
|
|
510
|
+
case 'tgate':
|
|
511
|
+
case 'tdg':
|
|
512
|
+
case 'sx':
|
|
513
|
+
case 'phase':
|
|
514
|
+
case 'rx':
|
|
515
|
+
case 'ry':
|
|
516
|
+
case 'rz':
|
|
517
|
+
case 'ugate':
|
|
518
|
+
case 'connector':
|
|
519
|
+
case 'protection':
|
|
520
|
+
case 'resonator':
|
|
521
|
+
case 'meter':
|
|
522
|
+
case 'load':
|
|
523
|
+
case 'reset':
|
|
524
|
+
case 'prepare':
|
|
525
|
+
case 'classical-bit':
|
|
526
|
+
case 'classical-register':
|
|
527
|
+
return geometry(port === 'out' ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
251
528
|
case 'ground':
|
|
252
|
-
return {
|
|
529
|
+
return geometry({ x: 0, y: -1 });
|
|
253
530
|
case 'cnot':
|
|
254
531
|
if (port === 'control')
|
|
255
|
-
return {
|
|
532
|
+
return geometry({ x: 0, y: -1 });
|
|
256
533
|
if (port === 'target')
|
|
257
|
-
return {
|
|
258
|
-
return
|
|
534
|
+
return geometry({ x: 0, y: 1 });
|
|
535
|
+
return geometry(port === 'out' ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
259
536
|
case 'ic': {
|
|
260
537
|
const location = icPinLocation(component, port);
|
|
261
|
-
return
|
|
538
|
+
return geometry(sideNormal(location.side));
|
|
262
539
|
}
|
|
540
|
+
case 'junction':
|
|
541
|
+
case 'testpoint':
|
|
542
|
+
return geometry(port === 'out' ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
543
|
+
case 'source':
|
|
544
|
+
if (port.startsWith('control-'))
|
|
545
|
+
return geometry({ x: 0, y: port.endsWith('positive') ? -1 : 1 });
|
|
546
|
+
return geometry(port === 'out' || port === 'positive' ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
547
|
+
case 'power':
|
|
548
|
+
return geometry({ x: -1, y: 0 });
|
|
549
|
+
case 'switch':
|
|
550
|
+
if (port.startsWith('coil'))
|
|
551
|
+
return geometry({ x: 0, y: 1 });
|
|
552
|
+
return geometry(port === 'in' || port === 'common' ? { x: -1, y: 0 } : { x: 1, y: 0 });
|
|
553
|
+
case 'amplifier':
|
|
554
|
+
if (port === 'v+' || port === 'v-')
|
|
555
|
+
return geometry({ x: 0, y: port === 'v+' ? -1 : 1 });
|
|
556
|
+
return geometry(port === 'out' ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
557
|
+
case 'buffer':
|
|
558
|
+
case 'logic':
|
|
559
|
+
case 'clock':
|
|
560
|
+
case 'flipflop':
|
|
561
|
+
case 'mux':
|
|
562
|
+
case 'encoder':
|
|
563
|
+
case 'decoder':
|
|
564
|
+
case 'register':
|
|
565
|
+
case 'counter':
|
|
566
|
+
case 'adder':
|
|
567
|
+
case 'comparator':
|
|
568
|
+
case 'bus':
|
|
569
|
+
if (['clock', 'enable', 'select'].includes(port))
|
|
570
|
+
return geometry({ x: 0, y: 1 });
|
|
571
|
+
if (['preset', 'clear'].includes(port))
|
|
572
|
+
return geometry({ x: 0, y: -1 });
|
|
573
|
+
return geometry(port === 'out' || port.startsWith('out') || ['q', 'nq', 'gt', 'eq', 'lt', 'tap'].includes(port) || (component.kind === 'bus' && component.variant === 'joiner' && port === 'bus') ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
574
|
+
case 'measure':
|
|
575
|
+
return geometry(port === 'classical' ? { x: 0, y: 1 } : port === 'out' ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
576
|
+
case 'control':
|
|
577
|
+
return geometry(port === 'control' ? { x: 0, y: -1 } : port === 'out' ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
578
|
+
case 'swap':
|
|
579
|
+
case 'cz':
|
|
580
|
+
case 'cphase':
|
|
581
|
+
case 'toffoli':
|
|
582
|
+
case 'controlled':
|
|
583
|
+
case 'barrier':
|
|
584
|
+
case 'delay':
|
|
585
|
+
return geometry(port.startsWith('out') ? { x: 1, y: 0 } : port.startsWith('in') ? { x: -1, y: 0 } : { x: 0, y: -1 });
|
|
263
586
|
}
|
|
264
587
|
throw new Error(`Validated port ${port} is missing.`);
|
|
265
588
|
}
|
|
@@ -273,6 +596,50 @@ export function enumerateComponentPorts(component) {
|
|
|
273
596
|
...Array.from({ length: component.outputs }, (_, index) => namedPort(component, `out${index + 1}`))
|
|
274
597
|
];
|
|
275
598
|
}
|
|
599
|
+
if (isDigitalComponent(component)) {
|
|
600
|
+
let ports = [
|
|
601
|
+
...Array.from({ length: component.inputs }, (_, index) => `in${index + 1}`),
|
|
602
|
+
...Array.from({ length: component.outputs }, (_, index) => `out${index + 1}`)
|
|
603
|
+
];
|
|
604
|
+
if (component.kind === 'logic' || component.kind === 'clock')
|
|
605
|
+
ports = ['out'];
|
|
606
|
+
else if (component.kind === 'flipflop') {
|
|
607
|
+
const inputs = component.variant === 'jk' ? ['j', 'k', 'clock'] : component.variant === 't' ? ['t', 'clock', 'clear'] : component.variant === 'sr-latch' ? ['s', 'r', 'enable'] : ['d', 'clock', 'clear'];
|
|
608
|
+
ports = [...inputs, 'q', 'nq', 'preset'];
|
|
609
|
+
}
|
|
610
|
+
else if (component.kind === 'register')
|
|
611
|
+
ports = ['in', 'out', 'clock', 'enable', 'clear'];
|
|
612
|
+
else if (component.kind === 'counter')
|
|
613
|
+
ports.push('clock', 'enable', 'clear');
|
|
614
|
+
else if (component.kind === 'mux')
|
|
615
|
+
ports.push('select', 'enable');
|
|
616
|
+
else if (component.kind === 'comparator')
|
|
617
|
+
ports = ['in1', 'in2', 'gt', 'eq', 'lt'];
|
|
618
|
+
else if (component.kind === 'bus') {
|
|
619
|
+
ports = component.variant === 'tap' ? ['bus', 'tap'] : component.variant === 'joiner' ? [...Array.from({ length: component.inputs }, (_, index) => `in${index + 1}`), 'bus'] : ['bus', ...Array.from({ length: component.outputs }, (_, index) => `out${index + 1}`)];
|
|
620
|
+
}
|
|
621
|
+
else if (component.kind === 'buffer' && component.variant?.startsWith('tristate') === true)
|
|
622
|
+
ports.push('enable');
|
|
623
|
+
return ports.map((port) => namedPort(component, port));
|
|
624
|
+
}
|
|
625
|
+
if (isQuantumSpecial(component)) {
|
|
626
|
+
if (component.kind === 'prepare')
|
|
627
|
+
return [namedPort(component, 'out')];
|
|
628
|
+
if (component.kind === 'measure')
|
|
629
|
+
return ['in', 'out', 'classical'].map((port) => namedPort(component, port));
|
|
630
|
+
if (component.kind === 'control')
|
|
631
|
+
return ['in', 'out', 'control'].map((port) => namedPort(component, port));
|
|
632
|
+
if (component.kind === 'reset' || component.kind === 'classical-bit' || component.kind === 'classical-register') {
|
|
633
|
+
return ['in', 'out'].map((port) => namedPort(component, port));
|
|
634
|
+
}
|
|
635
|
+
const tracks = Math.max(component.wires, component.controls + component.targets);
|
|
636
|
+
return [
|
|
637
|
+
...Array.from({ length: tracks }, (_, index) => namedPort(component, `in${index + 1}`)),
|
|
638
|
+
...Array.from({ length: tracks }, (_, index) => namedPort(component, `out${index + 1}`)),
|
|
639
|
+
...Array.from({ length: component.controls }, (_, index) => namedPort(component, `control${index + 1}`)),
|
|
640
|
+
...Array.from({ length: component.targets }, (_, index) => namedPort(component, `target${index + 1}`))
|
|
641
|
+
];
|
|
642
|
+
}
|
|
276
643
|
if (isUmlComponent(component)) {
|
|
277
644
|
return ['left', 'right', 'top', 'bottom'].map((port) => namedPort(component, port));
|
|
278
645
|
}
|
|
@@ -283,6 +650,24 @@ export function enumerateComponentPorts(component) {
|
|
|
283
650
|
case 'port':
|
|
284
651
|
case 'hadamard':
|
|
285
652
|
case 'qgate':
|
|
653
|
+
case 'xgate':
|
|
654
|
+
case 'ygate':
|
|
655
|
+
case 'zgate':
|
|
656
|
+
case 'sgate':
|
|
657
|
+
case 'sdg':
|
|
658
|
+
case 'tgate':
|
|
659
|
+
case 'tdg':
|
|
660
|
+
case 'sx':
|
|
661
|
+
case 'phase':
|
|
662
|
+
case 'rx':
|
|
663
|
+
case 'ry':
|
|
664
|
+
case 'rz':
|
|
665
|
+
case 'ugate':
|
|
666
|
+
case 'connector':
|
|
667
|
+
case 'protection':
|
|
668
|
+
case 'resonator':
|
|
669
|
+
case 'meter':
|
|
670
|
+
case 'load':
|
|
286
671
|
return [namedPort(component, 'in'), namedPort(component, 'out')];
|
|
287
672
|
case 'diode':
|
|
288
673
|
return [namedPort(component, 'anode'), namedPort(component, 'cathode')];
|
|
@@ -309,12 +694,34 @@ export function enumerateComponentPorts(component) {
|
|
|
309
694
|
namedPort(component, 'control'),
|
|
310
695
|
namedPort(component, 'target')
|
|
311
696
|
];
|
|
697
|
+
case 'junction':
|
|
698
|
+
case 'testpoint':
|
|
699
|
+
return [namedPort(component, 'node')];
|
|
700
|
+
case 'source': {
|
|
701
|
+
const ports = ['negative', 'positive'];
|
|
702
|
+
if (component.variant !== undefined && ['vcvs', 'vccs', 'ccvs', 'cccs'].includes(component.variant)) {
|
|
703
|
+
ports.push('control-positive', 'control-negative');
|
|
704
|
+
}
|
|
705
|
+
return ports.map((port) => namedPort(component, port));
|
|
706
|
+
}
|
|
707
|
+
case 'power':
|
|
708
|
+
return [namedPort(component, 'in')];
|
|
709
|
+
case 'switch': {
|
|
710
|
+
const ports = component.variant === 'spdt'
|
|
711
|
+
? ['common', 'normally-open', 'normally-closed']
|
|
712
|
+
: component.variant === 'relay'
|
|
713
|
+
? ['in', 'out', 'coil1', 'coil2']
|
|
714
|
+
: ['in', 'out'];
|
|
715
|
+
return ports.map((port) => namedPort(component, port));
|
|
716
|
+
}
|
|
717
|
+
case 'amplifier':
|
|
718
|
+
return ['positive', 'negative', 'out', 'v+', 'v-'].map((port) => namedPort(component, port));
|
|
312
719
|
case 'ic': {
|
|
313
720
|
const sides = ['left', 'right', 'top', 'bottom'];
|
|
314
721
|
return sides.flatMap((side) => component.pins[side].map((id, index) => ({
|
|
315
722
|
id,
|
|
316
723
|
point: positionIcPin(component, side, index),
|
|
317
|
-
normal: sideNormal(side)
|
|
724
|
+
normal: positionedQuarterNormal(component, sideNormal(side))
|
|
318
725
|
})));
|
|
319
726
|
}
|
|
320
727
|
}
|
|
@@ -609,6 +1016,19 @@ function routeBetweenEscapes(start, end, obstacles, bounds, line) {
|
|
|
609
1016
|
}
|
|
610
1017
|
return best ?? searchOrthogonalRoute(start, end, obstacles, bounds, line);
|
|
611
1018
|
}
|
|
1019
|
+
function endpointsFaceEachOther(from, to) {
|
|
1020
|
+
const dx = to.point.x - from.point.x;
|
|
1021
|
+
const dy = to.point.y - from.point.y;
|
|
1022
|
+
if (dy === 0 && dx !== 0) {
|
|
1023
|
+
const direction = Math.sign(dx);
|
|
1024
|
+
return from.normal.x === direction && to.normal.x === -direction;
|
|
1025
|
+
}
|
|
1026
|
+
if (dx === 0 && dy !== 0) {
|
|
1027
|
+
const direction = Math.sign(dy);
|
|
1028
|
+
return from.normal.y === direction && to.normal.y === -direction;
|
|
1029
|
+
}
|
|
1030
|
+
return false;
|
|
1031
|
+
}
|
|
612
1032
|
function routeConnectionInternal(connection, components, bounds, cachedObstacles) {
|
|
613
1033
|
const from = endpointGeometry(connection.from, components);
|
|
614
1034
|
const to = endpointGeometry(connection.to, components);
|
|
@@ -635,6 +1055,13 @@ function routeConnectionInternal(connection, components, bounds, cachedObstacles
|
|
|
635
1055
|
expanded: componentObstacleRectangle(component),
|
|
636
1056
|
body: componentObstacleRectangle(component, 0)
|
|
637
1057
|
}));
|
|
1058
|
+
if (from.component.id !== to.component.id &&
|
|
1059
|
+
endpointsFaceEachOther(from, to) &&
|
|
1060
|
+
!obstacleCache.some((entry) => entry.id !== from.component.id &&
|
|
1061
|
+
entry.id !== to.component.id &&
|
|
1062
|
+
segmentIntersectsRectangle(start, end, entry.expanded))) {
|
|
1063
|
+
return { curve: 'ortho', d: orthogonalPath([start, end]), points: [start, end] };
|
|
1064
|
+
}
|
|
638
1065
|
const fromObstacle = componentObstacleRectangle(from.component);
|
|
639
1066
|
const toObstacle = componentObstacleRectangle(to.component);
|
|
640
1067
|
const escape = (geometry, obstacle) => ({
|
|
@@ -651,13 +1078,8 @@ function routeConnectionInternal(connection, components, bounds, cachedObstacles
|
|
|
651
1078
|
});
|
|
652
1079
|
const startEscape = escape(from, fromObstacle);
|
|
653
1080
|
const endEscape = escape(to, toObstacle);
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
rectangle: entry.id === from.component.id || entry.id === to.component.id
|
|
657
|
-
? entry.body
|
|
658
|
-
: entry.expanded
|
|
659
|
-
}));
|
|
660
|
-
const obstacleRectangles = obstacleEntries.map((entry) => entry.rectangle);
|
|
1081
|
+
const obstacleRectangle = (entry) => entry.id === from.component.id || entry.id === to.component.id ? entry.body : entry.expanded;
|
|
1082
|
+
const obstacleRectangles = obstacleCache.map(obstacleRectangle);
|
|
661
1083
|
const middle = routeBetweenEscapes(startEscape, endEscape, obstacleRectangles, bounds, connection.line);
|
|
662
1084
|
if (middle === undefined) {
|
|
663
1085
|
throw new SchematicSyntaxError('No collision-free orthogonal route exists.', connection.line);
|
|
@@ -666,10 +1088,10 @@ function routeConnectionInternal(connection, components, bounds, cachedObstacles
|
|
|
666
1088
|
for (let index = 1; index < points.length; index += 1) {
|
|
667
1089
|
const allowFrom = index === 1;
|
|
668
1090
|
const allowTo = index === points.length - 1;
|
|
669
|
-
for (const obstacle of
|
|
1091
|
+
for (const obstacle of obstacleCache) {
|
|
670
1092
|
if (!(allowFrom && obstacle.id === from.component.id) &&
|
|
671
1093
|
!(allowTo && obstacle.id === to.component.id) &&
|
|
672
|
-
segmentIntersectsRectangle(points[index - 1], points[index], obstacle
|
|
1094
|
+
segmentIntersectsRectangle(points[index - 1], points[index], obstacleRectangle(obstacle))) {
|
|
673
1095
|
throw new SchematicSyntaxError(`Orthogonal route intersects ${obstacle.id} after routing.`, connection.line);
|
|
674
1096
|
}
|
|
675
1097
|
}
|
|
@@ -743,6 +1165,8 @@ function bridgedOrthogonalPath(route, crossings) {
|
|
|
743
1165
|
let cursorX = start.x;
|
|
744
1166
|
for (const [crossingIndex, crossing] of segmentCrossings.entries()) {
|
|
745
1167
|
const radius = radii[crossingIndex];
|
|
1168
|
+
if (radius < MIN_RENDERABLE_BRIDGE_RADIUS)
|
|
1169
|
+
continue;
|
|
746
1170
|
const before = crossing.x - direction * radius;
|
|
747
1171
|
const after = crossing.x + direction * radius;
|
|
748
1172
|
if (before !== cursorX)
|
|
@@ -758,6 +1182,8 @@ function bridgedOrthogonalPath(route, crossings) {
|
|
|
758
1182
|
let cursorY = start.y;
|
|
759
1183
|
for (const [crossingIndex, crossing] of segmentCrossings.entries()) {
|
|
760
1184
|
const radius = radii[crossingIndex];
|
|
1185
|
+
if (radius < MIN_RENDERABLE_BRIDGE_RADIUS)
|
|
1186
|
+
continue;
|
|
761
1187
|
const before = crossing.y - direction * radius;
|
|
762
1188
|
const after = crossing.y + direction * radius;
|
|
763
1189
|
if (before !== cursorY)
|
|
@@ -851,6 +1277,17 @@ function componentHalfExtents(component) {
|
|
|
851
1277
|
else if (isUmlComponent(component)) {
|
|
852
1278
|
return umlHalfExtents(component);
|
|
853
1279
|
}
|
|
1280
|
+
else if (isDigitalComponent(component)) {
|
|
1281
|
+
halfWidth = component.bodyWidth / 2 + 16;
|
|
1282
|
+
halfHeight = component.bodyHeight / 2 + 16;
|
|
1283
|
+
}
|
|
1284
|
+
else if (isQuantumSpecial(component)) {
|
|
1285
|
+
const tracks = Math.max(component.wires, component.controls + component.targets);
|
|
1286
|
+
halfWidth = 42;
|
|
1287
|
+
halfHeight = Math.max(24, (tracks - 1) * 9 + 14);
|
|
1288
|
+
if (component.kind === 'measure')
|
|
1289
|
+
halfHeight = 34;
|
|
1290
|
+
}
|
|
854
1291
|
else {
|
|
855
1292
|
switch (component.kind) {
|
|
856
1293
|
case 'resistor':
|
|
@@ -861,7 +1298,7 @@ function componentHalfExtents(component) {
|
|
|
861
1298
|
break;
|
|
862
1299
|
case 'diode':
|
|
863
1300
|
halfWidth = 42;
|
|
864
|
-
halfHeight = component.diodeType === 'led' ? 30 : 20;
|
|
1301
|
+
halfHeight = component.diodeType === 'led' || component.diodeType === 'photodiode' ? 30 : 20;
|
|
865
1302
|
break;
|
|
866
1303
|
case 'transistor':
|
|
867
1304
|
halfWidth = 42;
|
|
@@ -877,9 +1314,24 @@ function componentHalfExtents(component) {
|
|
|
877
1314
|
break;
|
|
878
1315
|
case 'hadamard':
|
|
879
1316
|
case 'qgate':
|
|
880
|
-
|
|
881
|
-
|
|
1317
|
+
case 'xgate':
|
|
1318
|
+
case 'ygate':
|
|
1319
|
+
case 'zgate':
|
|
1320
|
+
case 'sgate':
|
|
1321
|
+
case 'sdg':
|
|
1322
|
+
case 'tgate':
|
|
1323
|
+
case 'tdg':
|
|
1324
|
+
case 'sx':
|
|
1325
|
+
case 'phase':
|
|
1326
|
+
case 'rx':
|
|
1327
|
+
case 'ry':
|
|
1328
|
+
case 'rz':
|
|
1329
|
+
case 'ugate': {
|
|
1330
|
+
const dimensions = quantumGateDimensions(component);
|
|
1331
|
+
halfWidth = dimensions.stubExtent;
|
|
1332
|
+
halfHeight = dimensions.bodyHeight / 2;
|
|
882
1333
|
break;
|
|
1334
|
+
}
|
|
883
1335
|
case 'cnot':
|
|
884
1336
|
halfWidth = 42;
|
|
885
1337
|
halfHeight = 26;
|
|
@@ -888,9 +1340,39 @@ function componentHalfExtents(component) {
|
|
|
888
1340
|
halfWidth = component.bodyWidth / 2 + 16;
|
|
889
1341
|
halfHeight = component.bodyHeight / 2 + 16;
|
|
890
1342
|
break;
|
|
1343
|
+
case 'source':
|
|
1344
|
+
halfWidth = 42;
|
|
1345
|
+
halfHeight = 34;
|
|
1346
|
+
break;
|
|
1347
|
+
case 'junction':
|
|
1348
|
+
halfWidth = halfHeight = 5;
|
|
1349
|
+
break;
|
|
1350
|
+
case 'testpoint':
|
|
1351
|
+
halfWidth = halfHeight = 12;
|
|
1352
|
+
break;
|
|
1353
|
+
case 'connector':
|
|
1354
|
+
case 'power':
|
|
1355
|
+
halfWidth = 32;
|
|
1356
|
+
halfHeight = 18;
|
|
1357
|
+
break;
|
|
1358
|
+
case 'switch':
|
|
1359
|
+
halfWidth = 42;
|
|
1360
|
+
halfHeight = component.variant === 'relay' ? 34 : 20;
|
|
1361
|
+
break;
|
|
1362
|
+
case 'amplifier':
|
|
1363
|
+
halfWidth = 48;
|
|
1364
|
+
halfHeight = 36;
|
|
1365
|
+
break;
|
|
1366
|
+
case 'protection':
|
|
1367
|
+
case 'resonator':
|
|
1368
|
+
case 'meter':
|
|
1369
|
+
case 'load':
|
|
1370
|
+
halfWidth = 42;
|
|
1371
|
+
halfHeight = 24;
|
|
1372
|
+
break;
|
|
891
1373
|
}
|
|
892
1374
|
}
|
|
893
|
-
return { halfWidth, halfHeight };
|
|
1375
|
+
return rotateQuarterExtents({ halfWidth, halfHeight }, componentTurn(component));
|
|
894
1376
|
}
|
|
895
1377
|
export function componentTextAnchors(component) {
|
|
896
1378
|
const { halfHeight } = componentHalfExtents(component);
|
|
@@ -961,4 +1443,5 @@ export function validateDocumentGeometry(document, fence, routedConnections) {
|
|
|
961
1443
|
throw new SchematicSyntaxError('Connection trace exceeds the declared schematic bounds.', connection.line);
|
|
962
1444
|
}
|
|
963
1445
|
}
|
|
1446
|
+
return routes;
|
|
964
1447
|
}
|