@schemd/core 0.2.1 → 0.3.1
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 +64 -0
- package/README.md +84 -32
- 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 +60 -1
- package/dist/layout.js +536 -65
- package/dist/math-label.d.ts +5 -3
- package/dist/math-label.js +14 -28
- package/dist/parser.js +409 -40
- package/dist/renderer.js +377 -45
- package/dist/types.d.ts +124 -19
- package/dist/types.js +167 -4
- package/package.json +83 -78
- package/assets/brand/README.md +0 -6
- package/assets/brand/schemd-logo.svg +0 -29
- package/assets/brand/schemd-mark.svg +0 -23
package/dist/layout.js
CHANGED
|
@@ -1,9 +1,64 @@
|
|
|
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;
|
|
@@ -28,29 +83,108 @@ function formatNumber(value) {
|
|
|
28
83
|
export function distributedCoordinate(index, count, span) {
|
|
29
84
|
return ((index + 1) * span) / (count + 1) - span / 2;
|
|
30
85
|
}
|
|
86
|
+
export function quantumTrackSpan(tracks) {
|
|
87
|
+
return Math.max(16, (tracks - 1) * 18);
|
|
88
|
+
}
|
|
89
|
+
export function quantumTrackCoordinate(index, tracks) {
|
|
90
|
+
if (tracks <= 1)
|
|
91
|
+
return 0;
|
|
92
|
+
const span = quantumTrackSpan(tracks);
|
|
93
|
+
return -span / 2 + (index * span) / (tracks - 1);
|
|
94
|
+
}
|
|
31
95
|
export function classicalGateHeight(component) {
|
|
32
96
|
return Math.max(48, Math.max(component.inputs, component.outputs) * 16);
|
|
33
97
|
}
|
|
98
|
+
export function quantumGateDimensions(component) {
|
|
99
|
+
const details = [component.parameter, component.phase, component.matrix].filter((value) => value !== undefined && value !== '');
|
|
100
|
+
let widest = 16;
|
|
101
|
+
if (details.length > 0 && component.kind === 'qgate')
|
|
102
|
+
widest = mathLabelTextWidth(component.label, 8);
|
|
103
|
+
for (const detail of details)
|
|
104
|
+
widest = Math.max(widest, mathLabelTextWidth(detail, 7));
|
|
105
|
+
const bodyWidth = Math.max(50, Math.min(104, widest + 20));
|
|
106
|
+
const bodyHeight = 50 + details.length * 15;
|
|
107
|
+
return { bodyWidth, bodyHeight, stubExtent: bodyWidth / 2 + 23 };
|
|
108
|
+
}
|
|
34
109
|
function isClassicalGate(component) {
|
|
35
110
|
return CLASSICAL_GATE_KINDS.includes(component.kind);
|
|
36
111
|
}
|
|
112
|
+
function isDigitalComponent(component) {
|
|
113
|
+
return DIGITAL_COMPONENT_KINDS.includes(component.kind);
|
|
114
|
+
}
|
|
115
|
+
function isQuantumSpecial(component) {
|
|
116
|
+
return QUANTUM_SPECIAL_KINDS.includes(component.kind);
|
|
117
|
+
}
|
|
37
118
|
function isUmlComponent(component) {
|
|
38
119
|
return UML_COMPONENT_KINDS.includes(component.kind);
|
|
39
120
|
}
|
|
121
|
+
function componentTurn(component) {
|
|
122
|
+
return 'orientation' in component && component.orientation !== undefined
|
|
123
|
+
? orientationQuarterTurn(component.orientation)
|
|
124
|
+
: 0;
|
|
125
|
+
}
|
|
126
|
+
function positionedQuarterPoint(component, local) {
|
|
127
|
+
const rotated = rotateQuarterPoint(local, componentTurn(component));
|
|
128
|
+
return { x: component.x + rotated.x, y: component.y + rotated.y };
|
|
129
|
+
}
|
|
130
|
+
function positionedQuarterNormal(component, normal) {
|
|
131
|
+
return rotateQuarterPoint(normal, componentTurn(component));
|
|
132
|
+
}
|
|
40
133
|
function umlHalfExtents(component) {
|
|
41
134
|
switch (component.kind) {
|
|
42
135
|
case 'class':
|
|
136
|
+
case 'interface':
|
|
137
|
+
case 'enumeration':
|
|
138
|
+
case 'datatype':
|
|
139
|
+
case 'object':
|
|
43
140
|
case 'state':
|
|
44
141
|
case 'usecase':
|
|
45
142
|
case 'lifeline':
|
|
46
143
|
case 'note':
|
|
47
144
|
case 'package':
|
|
145
|
+
case 'component':
|
|
146
|
+
case 'artifact':
|
|
147
|
+
case 'node':
|
|
148
|
+
case 'device':
|
|
149
|
+
case 'execution':
|
|
150
|
+
case 'system':
|
|
151
|
+
case 'action':
|
|
152
|
+
case 'object-node':
|
|
153
|
+
case 'partition':
|
|
154
|
+
case 'activation':
|
|
155
|
+
case 'fragment':
|
|
156
|
+
case 'interaction':
|
|
157
|
+
case 'region':
|
|
48
158
|
return { halfWidth: component.bodyWidth / 2, halfHeight: component.bodyHeight / 2 };
|
|
49
159
|
case 'actor':
|
|
50
160
|
return { halfWidth: 24, halfHeight: 50 };
|
|
51
161
|
case 'initial':
|
|
52
162
|
case 'final':
|
|
163
|
+
case 'activity-final':
|
|
164
|
+
case 'flow-final':
|
|
165
|
+
case 'provided-interface':
|
|
166
|
+
case 'required-interface':
|
|
167
|
+
case 'component-port':
|
|
168
|
+
case 'destruction':
|
|
169
|
+
case 'gate':
|
|
170
|
+
case 'found':
|
|
171
|
+
case 'lost':
|
|
172
|
+
case 'state-junction':
|
|
173
|
+
case 'history':
|
|
174
|
+
case 'entry':
|
|
175
|
+
case 'exit':
|
|
176
|
+
case 'terminate':
|
|
53
177
|
return { halfWidth: 12, halfHeight: 12 };
|
|
178
|
+
case 'decision':
|
|
179
|
+
case 'merge':
|
|
180
|
+
case 'choice':
|
|
181
|
+
return { halfWidth: 18, halfHeight: 18 };
|
|
182
|
+
case 'fork':
|
|
183
|
+
case 'join':
|
|
184
|
+
return { halfWidth: 38, halfHeight: 5 };
|
|
185
|
+
case 'send-signal':
|
|
186
|
+
case 'receive-signal':
|
|
187
|
+
return { halfWidth: 22, halfHeight: 15 };
|
|
54
188
|
}
|
|
55
189
|
}
|
|
56
190
|
function umlPortPoint(component, port) {
|
|
@@ -82,28 +216,30 @@ function indexedPortPoint(component, port) {
|
|
|
82
216
|
const count = output ? component.outputs : component.inputs;
|
|
83
217
|
const suffix = port.match(/\d+$/)?.[0];
|
|
84
218
|
const index = suffix === undefined ? 0 : Number(suffix) - 1;
|
|
85
|
-
return {
|
|
86
|
-
x:
|
|
87
|
-
y:
|
|
88
|
-
};
|
|
219
|
+
return positionedQuarterPoint(component, {
|
|
220
|
+
x: output ? 48 : -48,
|
|
221
|
+
y: distributedCoordinate(index, count, classicalGateHeight(component))
|
|
222
|
+
});
|
|
89
223
|
}
|
|
90
224
|
export function positionIcPin(component, side, index) {
|
|
91
225
|
const pins = component.pins[side];
|
|
92
226
|
if (!Number.isInteger(index) || index < 0 || index >= pins.length) {
|
|
93
227
|
throw new RangeError(`IC pin index ${index} is outside ${component.id}.${side}.`);
|
|
94
228
|
}
|
|
229
|
+
let local;
|
|
95
230
|
if (side === 'left' || side === 'right') {
|
|
96
|
-
|
|
97
|
-
x: component.
|
|
98
|
-
|
|
99
|
-
y: component.y + distributedCoordinate(index, pins.length, component.bodyHeight)
|
|
231
|
+
local = {
|
|
232
|
+
x: side === 'left' ? -component.bodyWidth / 2 - 16 : component.bodyWidth / 2 + 16,
|
|
233
|
+
y: distributedCoordinate(index, pins.length, component.bodyHeight)
|
|
100
234
|
};
|
|
101
235
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
236
|
+
else {
|
|
237
|
+
local = {
|
|
238
|
+
x: distributedCoordinate(index, pins.length, component.bodyWidth),
|
|
239
|
+
y: side === 'top' ? -component.bodyHeight / 2 - 16 : component.bodyHeight / 2 + 16
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
return positionedQuarterPoint(component, local);
|
|
107
243
|
}
|
|
108
244
|
function findIcPinLocation(component, port) {
|
|
109
245
|
const sides = ['left', 'right', 'top', 'bottom'];
|
|
@@ -150,73 +286,205 @@ export function resolvePortPoint(component, port) {
|
|
|
150
286
|
return point;
|
|
151
287
|
throw new Error(`Validated port ${component.id}.${port} is missing.`);
|
|
152
288
|
}
|
|
153
|
-
|
|
154
|
-
|
|
289
|
+
if (component.kind === 'ic') {
|
|
290
|
+
const location = icPinLocation(component, port);
|
|
291
|
+
if (location !== undefined)
|
|
292
|
+
return location.point;
|
|
293
|
+
throw new Error(`Validated port ${component.id}.${port} is missing.`);
|
|
294
|
+
}
|
|
295
|
+
let local;
|
|
296
|
+
const left = { x: -42, y: 0 };
|
|
297
|
+
const right = { x: 42, y: 0 };
|
|
155
298
|
switch (component.kind) {
|
|
156
299
|
case 'resistor':
|
|
157
300
|
case 'capacitor':
|
|
158
301
|
case 'inductor':
|
|
159
302
|
if (PASSIVE_INPUT_PORTS.has(port))
|
|
160
|
-
|
|
303
|
+
local = left;
|
|
161
304
|
if (PASSIVE_OUTPUT_PORTS.has(port))
|
|
162
|
-
|
|
305
|
+
local = right;
|
|
163
306
|
break;
|
|
164
307
|
case 'diode':
|
|
165
308
|
if (DIODE_ANODE_PORTS.has(port))
|
|
166
|
-
|
|
309
|
+
local = left;
|
|
167
310
|
if (DIODE_CATHODE_PORTS.has(port))
|
|
168
|
-
|
|
311
|
+
local = right;
|
|
169
312
|
break;
|
|
170
313
|
case 'transistor':
|
|
171
314
|
if (TRANSISTOR_CONTROL_PORTS.has(port))
|
|
172
|
-
|
|
173
|
-
if (TRANSISTOR_UPPER_PORTS.has(port))
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
return { x: component.x + 42, y: component.y + 22 };
|
|
178
|
-
}
|
|
315
|
+
local = left;
|
|
316
|
+
if (TRANSISTOR_UPPER_PORTS.has(port))
|
|
317
|
+
local = { x: 42, y: -22 };
|
|
318
|
+
if (TRANSISTOR_LOWER_PORTS.has(port))
|
|
319
|
+
local = { x: 42, y: 22 };
|
|
179
320
|
break;
|
|
180
321
|
case 'port':
|
|
181
322
|
if (port === 'in')
|
|
182
|
-
|
|
323
|
+
local = left;
|
|
183
324
|
if (port === 'out')
|
|
184
|
-
|
|
325
|
+
local = right;
|
|
185
326
|
break;
|
|
186
327
|
case 'ground':
|
|
187
328
|
if (port === 'in')
|
|
188
|
-
|
|
329
|
+
local = { x: 0, y: -42 };
|
|
189
330
|
break;
|
|
190
331
|
case 'hadamard':
|
|
191
332
|
case 'qgate':
|
|
333
|
+
case 'xgate':
|
|
334
|
+
case 'ygate':
|
|
335
|
+
case 'zgate':
|
|
336
|
+
case 'sgate':
|
|
337
|
+
case 'sdg':
|
|
338
|
+
case 'tgate':
|
|
339
|
+
case 'tdg':
|
|
340
|
+
case 'sx':
|
|
341
|
+
case 'phase':
|
|
342
|
+
case 'rx':
|
|
343
|
+
case 'ry':
|
|
344
|
+
case 'rz':
|
|
345
|
+
case 'ugate':
|
|
192
346
|
if (port === 'in')
|
|
193
|
-
|
|
347
|
+
local = { x: -quantumGateDimensions(component).stubExtent, y: 0 };
|
|
194
348
|
if (port === 'out')
|
|
195
|
-
|
|
349
|
+
local = { x: quantumGateDimensions(component).stubExtent, y: 0 };
|
|
196
350
|
break;
|
|
197
351
|
case 'cnot':
|
|
198
352
|
if (port === 'in')
|
|
199
|
-
|
|
353
|
+
local = left;
|
|
200
354
|
if (port === 'out')
|
|
201
|
-
|
|
355
|
+
local = right;
|
|
202
356
|
if (port === 'control')
|
|
203
|
-
|
|
357
|
+
local = { x: 0, y: -16 };
|
|
204
358
|
if (port === 'target')
|
|
205
|
-
|
|
359
|
+
local = { x: 0, y: 16 };
|
|
206
360
|
break;
|
|
207
|
-
case '
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
361
|
+
case 'junction':
|
|
362
|
+
case 'testpoint':
|
|
363
|
+
local = { x: 0, y: 0 };
|
|
364
|
+
break;
|
|
365
|
+
case 'connector':
|
|
366
|
+
local = port === 'out' ? { x: 32, y: 0 } : { x: -32, y: 0 };
|
|
367
|
+
break;
|
|
368
|
+
case 'source':
|
|
369
|
+
if (port === 'in' || port === 'negative')
|
|
370
|
+
local = left;
|
|
371
|
+
else if (port === 'out' || port === 'positive')
|
|
372
|
+
local = right;
|
|
373
|
+
else
|
|
374
|
+
local = { x: 0, y: port === 'control-positive' ? -34 : 34 };
|
|
375
|
+
break;
|
|
376
|
+
case 'power':
|
|
377
|
+
local = { x: -32, y: 0 };
|
|
378
|
+
break;
|
|
379
|
+
case 'switch':
|
|
380
|
+
if (port === 'in' || port === 'common')
|
|
381
|
+
local = left;
|
|
382
|
+
else if (port === 'normally-open')
|
|
383
|
+
local = { x: 42, y: -12 };
|
|
384
|
+
else if (port === 'normally-closed')
|
|
385
|
+
local = { x: 42, y: 12 };
|
|
386
|
+
else if (port === 'coil1')
|
|
387
|
+
local = { x: -12, y: 34 };
|
|
388
|
+
else if (port === 'coil2')
|
|
389
|
+
local = { x: 12, y: 34 };
|
|
390
|
+
else
|
|
391
|
+
local = right;
|
|
392
|
+
break;
|
|
393
|
+
case 'amplifier':
|
|
394
|
+
if (port === 'out')
|
|
395
|
+
local = { x: 48, y: 0 };
|
|
396
|
+
else if (port === 'v+')
|
|
397
|
+
local = { x: 0, y: -36 };
|
|
398
|
+
else if (port === 'v-')
|
|
399
|
+
local = { x: 0, y: 36 };
|
|
400
|
+
else
|
|
401
|
+
local = { x: -48, y: port === 'negative' ? 13 : -13 };
|
|
402
|
+
break;
|
|
403
|
+
case 'protection':
|
|
404
|
+
case 'resonator':
|
|
405
|
+
case 'meter':
|
|
406
|
+
case 'load':
|
|
407
|
+
local = port === 'out' ? right : left;
|
|
408
|
+
break;
|
|
409
|
+
case 'buffer':
|
|
410
|
+
case 'logic':
|
|
411
|
+
case 'clock':
|
|
412
|
+
case 'flipflop':
|
|
413
|
+
case 'mux':
|
|
414
|
+
case 'encoder':
|
|
415
|
+
case 'decoder':
|
|
416
|
+
case 'register':
|
|
417
|
+
case 'counter':
|
|
418
|
+
case 'adder':
|
|
419
|
+
case 'comparator':
|
|
420
|
+
case 'bus': {
|
|
421
|
+
const output = port === 'out' || port.startsWith('out') || ['q', 'nq', 'gt', 'eq', 'lt', 'tap'].includes(port) || (component.kind === 'bus' && component.variant === 'joiner' && port === 'bus');
|
|
422
|
+
const match = port.match(/\d+$/);
|
|
423
|
+
const count = output ? component.outputs : component.inputs;
|
|
424
|
+
const index = match === null ? 0 : Number(match[0]) - 1;
|
|
425
|
+
if (port === 'clock' || port === 'enable' || port === 'select')
|
|
426
|
+
local = { x: 0, y: component.bodyHeight / 2 + 16 };
|
|
427
|
+
else if (port === 'preset' || port === 'clear')
|
|
428
|
+
local = { x: 0, y: -component.bodyHeight / 2 - 16 };
|
|
429
|
+
else
|
|
430
|
+
local = {
|
|
431
|
+
x: (output ? 1 : -1) * (component.bodyWidth / 2 + 16),
|
|
432
|
+
y: distributedCoordinate(index, Math.max(1, count), component.bodyHeight)
|
|
433
|
+
};
|
|
434
|
+
break;
|
|
435
|
+
}
|
|
436
|
+
case 'measure':
|
|
437
|
+
if (port === 'classical')
|
|
438
|
+
local = { x: 0, y: 34 };
|
|
439
|
+
else
|
|
440
|
+
local = port === 'out' ? { x: 40, y: 0 } : { x: -40, y: 0 };
|
|
441
|
+
break;
|
|
442
|
+
case 'prepare':
|
|
443
|
+
local = { x: 40, y: 0 };
|
|
444
|
+
break;
|
|
445
|
+
case 'control':
|
|
446
|
+
local = port === 'control' ? { x: 0, y: 0 } : port === 'out' ? right : left;
|
|
447
|
+
break;
|
|
448
|
+
case 'reset':
|
|
449
|
+
case 'classical-bit':
|
|
450
|
+
case 'classical-register':
|
|
451
|
+
local = port === 'out' ? { x: 40, y: 0 } : { x: -40, y: 0 };
|
|
452
|
+
break;
|
|
453
|
+
case 'swap':
|
|
454
|
+
case 'cz':
|
|
455
|
+
case 'cphase':
|
|
456
|
+
case 'toffoli':
|
|
457
|
+
case 'controlled':
|
|
458
|
+
case 'barrier':
|
|
459
|
+
case 'delay': {
|
|
460
|
+
const indexed = port.match(/^(?:in|out|control|target)([1-9]\d*)$/);
|
|
461
|
+
let index = indexed === null ? 0 : Number(indexed[1]) - 1;
|
|
462
|
+
if (port.startsWith('target'))
|
|
463
|
+
index += component.controls;
|
|
464
|
+
const tracks = Math.max(component.wires, component.controls + component.targets);
|
|
465
|
+
if (port.startsWith('control') || port.startsWith('target')) {
|
|
466
|
+
local = { x: 0, y: quantumTrackCoordinate(index, tracks) };
|
|
467
|
+
}
|
|
468
|
+
else {
|
|
469
|
+
local = {
|
|
470
|
+
x: port.startsWith('out') ? 42 : -42,
|
|
471
|
+
y: quantumTrackCoordinate(index, tracks)
|
|
472
|
+
};
|
|
473
|
+
}
|
|
211
474
|
break;
|
|
212
475
|
}
|
|
213
476
|
}
|
|
477
|
+
if (local !== undefined)
|
|
478
|
+
return positionedQuarterPoint(component, local);
|
|
214
479
|
throw new Error(`Validated port ${component.id}.${port} is missing.`);
|
|
215
480
|
}
|
|
216
481
|
export function resolvePortGeometry(component, port) {
|
|
217
482
|
const point = resolvePortPoint(component, port);
|
|
218
483
|
if (isClassicalGate(component)) {
|
|
219
|
-
return {
|
|
484
|
+
return {
|
|
485
|
+
point,
|
|
486
|
+
normal: positionedQuarterNormal(component, port.startsWith('out') ? { x: 1, y: 0 } : { x: -1, y: 0 })
|
|
487
|
+
};
|
|
220
488
|
}
|
|
221
489
|
if (isUmlComponent(component)) {
|
|
222
490
|
if (port === 'left' || port === 'in' || port.startsWith('left')) {
|
|
@@ -227,40 +495,103 @@ export function resolvePortGeometry(component, port) {
|
|
|
227
495
|
}
|
|
228
496
|
return { point, normal: port === 'top' ? { x: 0, y: -1 } : { x: 0, y: 1 } };
|
|
229
497
|
}
|
|
498
|
+
const geometry = (normal) => ({
|
|
499
|
+
point,
|
|
500
|
+
normal: positionedQuarterNormal(component, normal)
|
|
501
|
+
});
|
|
230
502
|
switch (component.kind) {
|
|
231
503
|
case 'resistor':
|
|
232
504
|
case 'capacitor':
|
|
233
505
|
case 'inductor':
|
|
234
|
-
return {
|
|
235
|
-
point,
|
|
236
|
-
normal: PASSIVE_OUTPUT_PORTS.has(port) ? { x: 1, y: 0 } : { x: -1, y: 0 }
|
|
237
|
-
};
|
|
506
|
+
return geometry(PASSIVE_OUTPUT_PORTS.has(port) ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
238
507
|
case 'diode':
|
|
239
|
-
return {
|
|
240
|
-
point,
|
|
241
|
-
normal: DIODE_CATHODE_PORTS.has(port) ? { x: 1, y: 0 } : { x: -1, y: 0 }
|
|
242
|
-
};
|
|
508
|
+
return geometry(DIODE_CATHODE_PORTS.has(port) ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
243
509
|
case 'transistor':
|
|
244
|
-
return {
|
|
245
|
-
point,
|
|
246
|
-
normal: TRANSISTOR_CONTROL_PORTS.has(port) ? { x: -1, y: 0 } : { x: 1, y: 0 }
|
|
247
|
-
};
|
|
510
|
+
return geometry(TRANSISTOR_CONTROL_PORTS.has(port) ? { x: -1, y: 0 } : { x: 1, y: 0 });
|
|
248
511
|
case 'port':
|
|
249
512
|
case 'hadamard':
|
|
250
513
|
case 'qgate':
|
|
251
|
-
|
|
514
|
+
case 'xgate':
|
|
515
|
+
case 'ygate':
|
|
516
|
+
case 'zgate':
|
|
517
|
+
case 'sgate':
|
|
518
|
+
case 'sdg':
|
|
519
|
+
case 'tgate':
|
|
520
|
+
case 'tdg':
|
|
521
|
+
case 'sx':
|
|
522
|
+
case 'phase':
|
|
523
|
+
case 'rx':
|
|
524
|
+
case 'ry':
|
|
525
|
+
case 'rz':
|
|
526
|
+
case 'ugate':
|
|
527
|
+
case 'connector':
|
|
528
|
+
case 'protection':
|
|
529
|
+
case 'resonator':
|
|
530
|
+
case 'meter':
|
|
531
|
+
case 'load':
|
|
532
|
+
case 'reset':
|
|
533
|
+
case 'prepare':
|
|
534
|
+
case 'classical-bit':
|
|
535
|
+
case 'classical-register':
|
|
536
|
+
return geometry(port === 'out' ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
252
537
|
case 'ground':
|
|
253
|
-
return {
|
|
538
|
+
return geometry({ x: 0, y: -1 });
|
|
254
539
|
case 'cnot':
|
|
255
540
|
if (port === 'control')
|
|
256
|
-
return {
|
|
541
|
+
return geometry({ x: 0, y: -1 });
|
|
257
542
|
if (port === 'target')
|
|
258
|
-
return {
|
|
259
|
-
return
|
|
543
|
+
return geometry({ x: 0, y: 1 });
|
|
544
|
+
return geometry(port === 'out' ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
260
545
|
case 'ic': {
|
|
261
546
|
const location = icPinLocation(component, port);
|
|
262
|
-
return
|
|
547
|
+
return geometry(sideNormal(location.side));
|
|
263
548
|
}
|
|
549
|
+
case 'junction':
|
|
550
|
+
case 'testpoint':
|
|
551
|
+
return geometry(port === 'out' ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
552
|
+
case 'source':
|
|
553
|
+
if (port.startsWith('control-'))
|
|
554
|
+
return geometry({ x: 0, y: port.endsWith('positive') ? -1 : 1 });
|
|
555
|
+
return geometry(port === 'out' || port === 'positive' ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
556
|
+
case 'power':
|
|
557
|
+
return geometry({ x: -1, y: 0 });
|
|
558
|
+
case 'switch':
|
|
559
|
+
if (port.startsWith('coil'))
|
|
560
|
+
return geometry({ x: 0, y: 1 });
|
|
561
|
+
return geometry(port === 'in' || port === 'common' ? { x: -1, y: 0 } : { x: 1, y: 0 });
|
|
562
|
+
case 'amplifier':
|
|
563
|
+
if (port === 'v+' || port === 'v-')
|
|
564
|
+
return geometry({ x: 0, y: port === 'v+' ? -1 : 1 });
|
|
565
|
+
return geometry(port === 'out' ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
566
|
+
case 'buffer':
|
|
567
|
+
case 'logic':
|
|
568
|
+
case 'clock':
|
|
569
|
+
case 'flipflop':
|
|
570
|
+
case 'mux':
|
|
571
|
+
case 'encoder':
|
|
572
|
+
case 'decoder':
|
|
573
|
+
case 'register':
|
|
574
|
+
case 'counter':
|
|
575
|
+
case 'adder':
|
|
576
|
+
case 'comparator':
|
|
577
|
+
case 'bus':
|
|
578
|
+
if (['clock', 'enable', 'select'].includes(port))
|
|
579
|
+
return geometry({ x: 0, y: 1 });
|
|
580
|
+
if (['preset', 'clear'].includes(port))
|
|
581
|
+
return geometry({ x: 0, y: -1 });
|
|
582
|
+
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 });
|
|
583
|
+
case 'measure':
|
|
584
|
+
return geometry(port === 'classical' ? { x: 0, y: 1 } : port === 'out' ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
585
|
+
case 'control':
|
|
586
|
+
return geometry(port === 'control' ? { x: 0, y: -1 } : port === 'out' ? { x: 1, y: 0 } : { x: -1, y: 0 });
|
|
587
|
+
case 'swap':
|
|
588
|
+
case 'cz':
|
|
589
|
+
case 'cphase':
|
|
590
|
+
case 'toffoli':
|
|
591
|
+
case 'controlled':
|
|
592
|
+
case 'barrier':
|
|
593
|
+
case 'delay':
|
|
594
|
+
return geometry(port.startsWith('out') ? { x: 1, y: 0 } : port.startsWith('in') ? { x: -1, y: 0 } : { x: 0, y: -1 });
|
|
264
595
|
}
|
|
265
596
|
throw new Error(`Validated port ${port} is missing.`);
|
|
266
597
|
}
|
|
@@ -274,6 +605,50 @@ export function enumerateComponentPorts(component) {
|
|
|
274
605
|
...Array.from({ length: component.outputs }, (_, index) => namedPort(component, `out${index + 1}`))
|
|
275
606
|
];
|
|
276
607
|
}
|
|
608
|
+
if (isDigitalComponent(component)) {
|
|
609
|
+
let ports = [
|
|
610
|
+
...Array.from({ length: component.inputs }, (_, index) => `in${index + 1}`),
|
|
611
|
+
...Array.from({ length: component.outputs }, (_, index) => `out${index + 1}`)
|
|
612
|
+
];
|
|
613
|
+
if (component.kind === 'logic' || component.kind === 'clock')
|
|
614
|
+
ports = ['out'];
|
|
615
|
+
else if (component.kind === 'flipflop') {
|
|
616
|
+
const inputs = component.variant === 'jk' ? ['j', 'k', 'clock'] : component.variant === 't' ? ['t', 'clock', 'clear'] : component.variant === 'sr-latch' ? ['s', 'r', 'enable'] : ['d', 'clock', 'clear'];
|
|
617
|
+
ports = [...inputs, 'q', 'nq', 'preset'];
|
|
618
|
+
}
|
|
619
|
+
else if (component.kind === 'register')
|
|
620
|
+
ports = ['in', 'out', 'clock', 'enable', 'clear'];
|
|
621
|
+
else if (component.kind === 'counter')
|
|
622
|
+
ports.push('clock', 'enable', 'clear');
|
|
623
|
+
else if (component.kind === 'mux')
|
|
624
|
+
ports.push('select', 'enable');
|
|
625
|
+
else if (component.kind === 'comparator')
|
|
626
|
+
ports = ['in1', 'in2', 'gt', 'eq', 'lt'];
|
|
627
|
+
else if (component.kind === 'bus') {
|
|
628
|
+
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}`)];
|
|
629
|
+
}
|
|
630
|
+
else if (component.kind === 'buffer' && component.variant?.startsWith('tristate') === true)
|
|
631
|
+
ports.push('enable');
|
|
632
|
+
return ports.map((port) => namedPort(component, port));
|
|
633
|
+
}
|
|
634
|
+
if (isQuantumSpecial(component)) {
|
|
635
|
+
if (component.kind === 'prepare')
|
|
636
|
+
return [namedPort(component, 'out')];
|
|
637
|
+
if (component.kind === 'measure')
|
|
638
|
+
return ['in', 'out', 'classical'].map((port) => namedPort(component, port));
|
|
639
|
+
if (component.kind === 'control')
|
|
640
|
+
return ['in', 'out', 'control'].map((port) => namedPort(component, port));
|
|
641
|
+
if (component.kind === 'reset' || component.kind === 'classical-bit' || component.kind === 'classical-register') {
|
|
642
|
+
return ['in', 'out'].map((port) => namedPort(component, port));
|
|
643
|
+
}
|
|
644
|
+
const tracks = Math.max(component.wires, component.controls + component.targets);
|
|
645
|
+
return [
|
|
646
|
+
...Array.from({ length: tracks }, (_, index) => namedPort(component, `in${index + 1}`)),
|
|
647
|
+
...Array.from({ length: tracks }, (_, index) => namedPort(component, `out${index + 1}`)),
|
|
648
|
+
...Array.from({ length: component.controls }, (_, index) => namedPort(component, `control${index + 1}`)),
|
|
649
|
+
...Array.from({ length: component.targets }, (_, index) => namedPort(component, `target${index + 1}`))
|
|
650
|
+
];
|
|
651
|
+
}
|
|
277
652
|
if (isUmlComponent(component)) {
|
|
278
653
|
return ['left', 'right', 'top', 'bottom'].map((port) => namedPort(component, port));
|
|
279
654
|
}
|
|
@@ -284,6 +659,24 @@ export function enumerateComponentPorts(component) {
|
|
|
284
659
|
case 'port':
|
|
285
660
|
case 'hadamard':
|
|
286
661
|
case 'qgate':
|
|
662
|
+
case 'xgate':
|
|
663
|
+
case 'ygate':
|
|
664
|
+
case 'zgate':
|
|
665
|
+
case 'sgate':
|
|
666
|
+
case 'sdg':
|
|
667
|
+
case 'tgate':
|
|
668
|
+
case 'tdg':
|
|
669
|
+
case 'sx':
|
|
670
|
+
case 'phase':
|
|
671
|
+
case 'rx':
|
|
672
|
+
case 'ry':
|
|
673
|
+
case 'rz':
|
|
674
|
+
case 'ugate':
|
|
675
|
+
case 'connector':
|
|
676
|
+
case 'protection':
|
|
677
|
+
case 'resonator':
|
|
678
|
+
case 'meter':
|
|
679
|
+
case 'load':
|
|
287
680
|
return [namedPort(component, 'in'), namedPort(component, 'out')];
|
|
288
681
|
case 'diode':
|
|
289
682
|
return [namedPort(component, 'anode'), namedPort(component, 'cathode')];
|
|
@@ -310,12 +703,34 @@ export function enumerateComponentPorts(component) {
|
|
|
310
703
|
namedPort(component, 'control'),
|
|
311
704
|
namedPort(component, 'target')
|
|
312
705
|
];
|
|
706
|
+
case 'junction':
|
|
707
|
+
case 'testpoint':
|
|
708
|
+
return [namedPort(component, 'node')];
|
|
709
|
+
case 'source': {
|
|
710
|
+
const ports = ['negative', 'positive'];
|
|
711
|
+
if (component.variant !== undefined && ['vcvs', 'vccs', 'ccvs', 'cccs'].includes(component.variant)) {
|
|
712
|
+
ports.push('control-positive', 'control-negative');
|
|
713
|
+
}
|
|
714
|
+
return ports.map((port) => namedPort(component, port));
|
|
715
|
+
}
|
|
716
|
+
case 'power':
|
|
717
|
+
return [namedPort(component, 'in')];
|
|
718
|
+
case 'switch': {
|
|
719
|
+
const ports = component.variant === 'spdt'
|
|
720
|
+
? ['common', 'normally-open', 'normally-closed']
|
|
721
|
+
: component.variant === 'relay'
|
|
722
|
+
? ['in', 'out', 'coil1', 'coil2']
|
|
723
|
+
: ['in', 'out'];
|
|
724
|
+
return ports.map((port) => namedPort(component, port));
|
|
725
|
+
}
|
|
726
|
+
case 'amplifier':
|
|
727
|
+
return ['positive', 'negative', 'out', 'v+', 'v-'].map((port) => namedPort(component, port));
|
|
313
728
|
case 'ic': {
|
|
314
729
|
const sides = ['left', 'right', 'top', 'bottom'];
|
|
315
730
|
return sides.flatMap((side) => component.pins[side].map((id, index) => ({
|
|
316
731
|
id,
|
|
317
732
|
point: positionIcPin(component, side, index),
|
|
318
|
-
normal: sideNormal(side)
|
|
733
|
+
normal: positionedQuarterNormal(component, sideNormal(side))
|
|
319
734
|
})));
|
|
320
735
|
}
|
|
321
736
|
}
|
|
@@ -685,7 +1100,7 @@ function routeConnectionInternal(connection, components, bounds, cachedObstacles
|
|
|
685
1100
|
for (const obstacle of obstacleCache) {
|
|
686
1101
|
if (!(allowFrom && obstacle.id === from.component.id) &&
|
|
687
1102
|
!(allowTo && obstacle.id === to.component.id) &&
|
|
688
|
-
segmentIntersectsRectangle(points[index - 1], points[index],
|
|
1103
|
+
segmentIntersectsRectangle(points[index - 1], points[index], obstacle.body)) {
|
|
689
1104
|
throw new SchematicSyntaxError(`Orthogonal route intersects ${obstacle.id} after routing.`, connection.line);
|
|
690
1105
|
}
|
|
691
1106
|
}
|
|
@@ -871,6 +1286,17 @@ function componentHalfExtents(component) {
|
|
|
871
1286
|
else if (isUmlComponent(component)) {
|
|
872
1287
|
return umlHalfExtents(component);
|
|
873
1288
|
}
|
|
1289
|
+
else if (isDigitalComponent(component)) {
|
|
1290
|
+
halfWidth = component.bodyWidth / 2 + 16;
|
|
1291
|
+
halfHeight = component.bodyHeight / 2 + 16;
|
|
1292
|
+
}
|
|
1293
|
+
else if (isQuantumSpecial(component)) {
|
|
1294
|
+
const tracks = Math.max(component.wires, component.controls + component.targets);
|
|
1295
|
+
halfWidth = 42;
|
|
1296
|
+
halfHeight = Math.max(24, (tracks - 1) * 9 + 14);
|
|
1297
|
+
if (component.kind === 'measure')
|
|
1298
|
+
halfHeight = 34;
|
|
1299
|
+
}
|
|
874
1300
|
else {
|
|
875
1301
|
switch (component.kind) {
|
|
876
1302
|
case 'resistor':
|
|
@@ -881,7 +1307,7 @@ function componentHalfExtents(component) {
|
|
|
881
1307
|
break;
|
|
882
1308
|
case 'diode':
|
|
883
1309
|
halfWidth = 42;
|
|
884
|
-
halfHeight = component.diodeType === 'led' ? 30 : 20;
|
|
1310
|
+
halfHeight = component.diodeType === 'led' || component.diodeType === 'photodiode' ? 30 : 20;
|
|
885
1311
|
break;
|
|
886
1312
|
case 'transistor':
|
|
887
1313
|
halfWidth = 42;
|
|
@@ -897,9 +1323,24 @@ function componentHalfExtents(component) {
|
|
|
897
1323
|
break;
|
|
898
1324
|
case 'hadamard':
|
|
899
1325
|
case 'qgate':
|
|
900
|
-
|
|
901
|
-
|
|
1326
|
+
case 'xgate':
|
|
1327
|
+
case 'ygate':
|
|
1328
|
+
case 'zgate':
|
|
1329
|
+
case 'sgate':
|
|
1330
|
+
case 'sdg':
|
|
1331
|
+
case 'tgate':
|
|
1332
|
+
case 'tdg':
|
|
1333
|
+
case 'sx':
|
|
1334
|
+
case 'phase':
|
|
1335
|
+
case 'rx':
|
|
1336
|
+
case 'ry':
|
|
1337
|
+
case 'rz':
|
|
1338
|
+
case 'ugate': {
|
|
1339
|
+
const dimensions = quantumGateDimensions(component);
|
|
1340
|
+
halfWidth = dimensions.stubExtent;
|
|
1341
|
+
halfHeight = dimensions.bodyHeight / 2;
|
|
902
1342
|
break;
|
|
1343
|
+
}
|
|
903
1344
|
case 'cnot':
|
|
904
1345
|
halfWidth = 42;
|
|
905
1346
|
halfHeight = 26;
|
|
@@ -908,9 +1349,39 @@ function componentHalfExtents(component) {
|
|
|
908
1349
|
halfWidth = component.bodyWidth / 2 + 16;
|
|
909
1350
|
halfHeight = component.bodyHeight / 2 + 16;
|
|
910
1351
|
break;
|
|
1352
|
+
case 'source':
|
|
1353
|
+
halfWidth = 42;
|
|
1354
|
+
halfHeight = 34;
|
|
1355
|
+
break;
|
|
1356
|
+
case 'junction':
|
|
1357
|
+
halfWidth = halfHeight = 5;
|
|
1358
|
+
break;
|
|
1359
|
+
case 'testpoint':
|
|
1360
|
+
halfWidth = halfHeight = 12;
|
|
1361
|
+
break;
|
|
1362
|
+
case 'connector':
|
|
1363
|
+
case 'power':
|
|
1364
|
+
halfWidth = 32;
|
|
1365
|
+
halfHeight = 18;
|
|
1366
|
+
break;
|
|
1367
|
+
case 'switch':
|
|
1368
|
+
halfWidth = 42;
|
|
1369
|
+
halfHeight = component.variant === 'relay' ? 34 : 20;
|
|
1370
|
+
break;
|
|
1371
|
+
case 'amplifier':
|
|
1372
|
+
halfWidth = 48;
|
|
1373
|
+
halfHeight = 36;
|
|
1374
|
+
break;
|
|
1375
|
+
case 'protection':
|
|
1376
|
+
case 'resonator':
|
|
1377
|
+
case 'meter':
|
|
1378
|
+
case 'load':
|
|
1379
|
+
halfWidth = 42;
|
|
1380
|
+
halfHeight = 24;
|
|
1381
|
+
break;
|
|
911
1382
|
}
|
|
912
1383
|
}
|
|
913
|
-
return { halfWidth, halfHeight };
|
|
1384
|
+
return rotateQuarterExtents({ halfWidth, halfHeight }, componentTurn(component));
|
|
914
1385
|
}
|
|
915
1386
|
export function componentTextAnchors(component) {
|
|
916
1387
|
const { halfHeight } = componentHalfExtents(component);
|