altium-toolkit 1.1.41 → 1.2.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/README.md +118 -19
- package/docs/api.md +224 -18
- package/docs/capabilities.md +65 -0
- package/docs/migration/legacy-001.md +309 -0
- package/docs/migration/legacy-002.md +309 -0
- package/docs/migration/legacy-003.md +309 -0
- package/docs/migration/legacy-004.md +309 -0
- package/docs/migration/legacy-005.md +111 -0
- package/docs/migration.md +20 -0
- package/docs/model-format.md +28 -3
- package/docs/release-notes-v1.2.0.md +147 -0
- package/docs/release-notes-v1.2.1.md +15 -0
- package/docs/testing.md +43 -1
- package/examples/arduino-uno/PcbThreeSceneRenderer.mjs +1 -1
- package/examples/arduino-uno/example.mjs +1 -1
- package/examples/cli-utils.mjs +1 -1
- package/examples/corpus-smoke.mjs +1 -1
- package/examples/inspect-board.mjs +1 -1
- package/examples/library-catalog.mjs +1 -1
- package/examples/validate-library.mjs +1 -1
- package/package.json +23 -8
- package/spec/api-baseline-v1.1.41.json +1 -0
- package/spec/asset-baseline-v1.1.41.json +1 -0
- package/spec/feature-preservation.json +1 -0
- package/spec/library-scope.md +14 -4
- package/spec/native-source-manifest-v1.1.41.json +1 -0
- package/src/capabilities.mjs +4 -0
- package/src/convergence/AltiumCircuitJsonProjection.mjs +230 -0
- package/src/convergence/AltiumDocumentBuilder.mjs +167 -0
- package/src/convergence/AltiumExtensionResolver.mjs +98 -0
- package/src/convergence/AltiumProjectDocumentResolver.mjs +324 -0
- package/src/convergence/AltiumSchematicCoordinateProjection.mjs +130 -0
- package/src/convergence/AltiumWorkerClient.mjs +95 -0
- package/src/convergence/Parser.mjs +285 -0
- package/src/convergence/ParserInput.mjs +282 -0
- package/src/convergence/ProjectLoader.mjs +916 -0
- package/src/convergence/SchematicSvgRenderer.mjs +126 -0
- package/src/convergence/ToolkitCapabilities.mjs +49 -0
- package/src/core/circuit-json/CircuitJsonSchematicDocumentGraphicBuilder.mjs +785 -0
- package/src/core/circuit-json/CircuitJsonSchematicGraphicBuilder.mjs +909 -0
- package/src/core/circuit-json/CircuitJsonSchematicImageProjection.mjs +277 -0
- package/src/core/circuit-json/CircuitJsonSchematicStrokeStyle.mjs +47 -0
- package/src/extensions.mjs +11 -0
- package/src/index.mjs +20 -4
- package/src/interaction.mjs +7 -0
- package/src/legacy-netlist-query.mjs +11 -0
- package/src/legacy-parser.mjs +141 -0
- package/src/legacy-renderers.mjs +25 -0
- package/src/legacy-scene3d.mjs +10 -0
- package/src/manufacturing.mjs +4 -0
- package/src/parser.mjs +12 -138
- package/src/project.mjs +11 -0
- package/src/query.mjs +4 -0
- package/src/renderers.mjs +4 -22
- package/src/scene3d.mjs +5 -8
- package/src/simulation.mjs +4 -0
- package/src/styles/renderers.css +27 -0
- package/src/testing.mjs +8 -0
- package/src/workers/parser.worker.mjs +37 -0
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: 2026 André Fiedler
|
|
2
|
+
//
|
|
3
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
|
+
|
|
5
|
+
import { CircuitJsonModelAdapterPrimitives } from './CircuitJsonModelAdapterPrimitives.mjs'
|
|
6
|
+
import { CircuitJsonSchematicImageProjection } from './CircuitJsonSchematicImageProjection.mjs'
|
|
7
|
+
import { CircuitJsonSchematicStrokeStyle } from './CircuitJsonSchematicStrokeStyle.mjs'
|
|
8
|
+
|
|
9
|
+
const Primitives = CircuitJsonModelAdapterPrimitives
|
|
10
|
+
const FAMILY_ORDER = Object.freeze({
|
|
11
|
+
textBox: 7,
|
|
12
|
+
table: 8,
|
|
13
|
+
sheet: 9,
|
|
14
|
+
image: 10,
|
|
15
|
+
text: 11
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Projects document-level schematic graphics onto shared CircuitJSON rows.
|
|
20
|
+
*/
|
|
21
|
+
export class CircuitJsonSchematicDocumentGraphicBuilder {
|
|
22
|
+
/**
|
|
23
|
+
* Appends text boxes, tables, hierarchical sheets, and ordinary text.
|
|
24
|
+
* @param {object[]} groups Destination render groups.
|
|
25
|
+
* @param {Record<string, unknown>} schematic Native schematic read model.
|
|
26
|
+
* @param {string} idScope Deterministic document id scope.
|
|
27
|
+
* @param {Map<string, string>} ownerIds Owner-to-component lookup.
|
|
28
|
+
* @returns {void}
|
|
29
|
+
*/
|
|
30
|
+
static append(groups, schematic, idScope, ownerIds) {
|
|
31
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#appendTextFrames(
|
|
32
|
+
groups,
|
|
33
|
+
schematic,
|
|
34
|
+
idScope,
|
|
35
|
+
ownerIds
|
|
36
|
+
)
|
|
37
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#appendTables(
|
|
38
|
+
groups,
|
|
39
|
+
schematic,
|
|
40
|
+
idScope,
|
|
41
|
+
ownerIds
|
|
42
|
+
)
|
|
43
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#appendSheets(
|
|
44
|
+
groups,
|
|
45
|
+
schematic,
|
|
46
|
+
idScope,
|
|
47
|
+
ownerIds
|
|
48
|
+
)
|
|
49
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#appendImages(
|
|
50
|
+
groups,
|
|
51
|
+
schematic,
|
|
52
|
+
idScope,
|
|
53
|
+
ownerIds
|
|
54
|
+
)
|
|
55
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#appendTexts(
|
|
56
|
+
groups,
|
|
57
|
+
schematic,
|
|
58
|
+
idScope,
|
|
59
|
+
ownerIds
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Appends boxed notes as one outline and one positioned text row.
|
|
65
|
+
* @param {object[]} groups Render groups.
|
|
66
|
+
* @param {Record<string, unknown>} schematic Native schematic.
|
|
67
|
+
* @param {string} idScope Document id scope.
|
|
68
|
+
* @param {Map<string, string>} ownerIds Owner-to-component lookup.
|
|
69
|
+
* @returns {void}
|
|
70
|
+
*/
|
|
71
|
+
static #appendTextFrames(groups, schematic, idScope, ownerIds) {
|
|
72
|
+
for (const [index, frame] of Primitives.array(
|
|
73
|
+
schematic.textFrames
|
|
74
|
+
).entries()) {
|
|
75
|
+
const left = Math.min(
|
|
76
|
+
Primitives.number(frame?.x, 0) || 0,
|
|
77
|
+
Primitives.number(frame?.cornerX, frame?.x) || 0
|
|
78
|
+
)
|
|
79
|
+
const firstY = Primitives.number(frame?.y, 0) || 0
|
|
80
|
+
const secondY =
|
|
81
|
+
Primitives.number(frame?.cornerY, frame?.y) || firstY
|
|
82
|
+
const bottom = Math.min(firstY, secondY)
|
|
83
|
+
const top = Math.max(firstY, secondY)
|
|
84
|
+
const width = Math.abs(
|
|
85
|
+
Primitives.number(
|
|
86
|
+
frame?.width,
|
|
87
|
+
(Primitives.number(frame?.cornerX, 0) || 0) - left
|
|
88
|
+
) || 0
|
|
89
|
+
)
|
|
90
|
+
const height = Math.abs(
|
|
91
|
+
Primitives.number(frame?.height, top - bottom) || 0
|
|
92
|
+
)
|
|
93
|
+
const margin = Math.max(
|
|
94
|
+
Primitives.number(frame?.textMargin, 0) || 0,
|
|
95
|
+
0
|
|
96
|
+
)
|
|
97
|
+
const identity =
|
|
98
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#identity(
|
|
99
|
+
frame,
|
|
100
|
+
index
|
|
101
|
+
)
|
|
102
|
+
const owner =
|
|
103
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#ownerField(
|
|
104
|
+
frame,
|
|
105
|
+
ownerIds
|
|
106
|
+
)
|
|
107
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#group(
|
|
108
|
+
groups,
|
|
109
|
+
frame,
|
|
110
|
+
FAMILY_ORDER.textBox,
|
|
111
|
+
index,
|
|
112
|
+
[
|
|
113
|
+
{
|
|
114
|
+
type: 'schematic_rect',
|
|
115
|
+
schematic_rect_id: Primitives.id(idScope, [
|
|
116
|
+
'schematic_rect',
|
|
117
|
+
'text_box',
|
|
118
|
+
identity
|
|
119
|
+
]),
|
|
120
|
+
center: Primitives.point(
|
|
121
|
+
left + width / 2,
|
|
122
|
+
bottom + height / 2
|
|
123
|
+
),
|
|
124
|
+
width,
|
|
125
|
+
height,
|
|
126
|
+
stroke_width:
|
|
127
|
+
frame?.showBorder === false
|
|
128
|
+
? 0
|
|
129
|
+
: Primitives.number(frame?.borderWidth, 1),
|
|
130
|
+
color: Primitives.string(
|
|
131
|
+
frame?.borderColor || frame?.color,
|
|
132
|
+
'#000000'
|
|
133
|
+
),
|
|
134
|
+
fill_color: Primitives.string(frame?.fill, '#ffffff'),
|
|
135
|
+
...CircuitJsonSchematicStrokeStyle.fields(
|
|
136
|
+
frame,
|
|
137
|
+
frame?.borderWidth ?? frame?.lineWidth
|
|
138
|
+
),
|
|
139
|
+
is_filled: frame?.isSolid === true,
|
|
140
|
+
...owner
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
type: 'schematic_text',
|
|
144
|
+
schematic_text_id: Primitives.id(idScope, [
|
|
145
|
+
'schematic_text',
|
|
146
|
+
'text_box',
|
|
147
|
+
identity
|
|
148
|
+
]),
|
|
149
|
+
text: Primitives.string(frame?.text, ''),
|
|
150
|
+
position: Primitives.point(left + margin, top - margin),
|
|
151
|
+
font_size: Primitives.number(frame?.font?.size, 0.18),
|
|
152
|
+
rotation:
|
|
153
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#textRotation(
|
|
154
|
+
frame
|
|
155
|
+
),
|
|
156
|
+
anchor: 'top_left',
|
|
157
|
+
color: Primitives.string(frame?.color, '#000000'),
|
|
158
|
+
...owner
|
|
159
|
+
}
|
|
160
|
+
]
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Appends generic native table read models when supplied by a parser.
|
|
167
|
+
* @param {object[]} groups Render groups.
|
|
168
|
+
* @param {Record<string, unknown>} schematic Native schematic.
|
|
169
|
+
* @param {string} idScope Document id scope.
|
|
170
|
+
* @param {Map<string, string>} ownerIds Owner-to-component lookup.
|
|
171
|
+
* @returns {void}
|
|
172
|
+
*/
|
|
173
|
+
static #appendTables(groups, schematic, idScope, ownerIds) {
|
|
174
|
+
for (const [index, table] of Primitives.array(
|
|
175
|
+
schematic.tables
|
|
176
|
+
).entries()) {
|
|
177
|
+
const tableId = Primitives.id(idScope, [
|
|
178
|
+
'schematic_table',
|
|
179
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#identity(
|
|
180
|
+
table,
|
|
181
|
+
index
|
|
182
|
+
)
|
|
183
|
+
])
|
|
184
|
+
const rows = [
|
|
185
|
+
{
|
|
186
|
+
type: 'schematic_table',
|
|
187
|
+
schematic_table_id: tableId,
|
|
188
|
+
anchor_position: Primitives.point(
|
|
189
|
+
table?.anchorPosition?.x ?? table?.x,
|
|
190
|
+
table?.anchorPosition?.y ?? table?.y
|
|
191
|
+
),
|
|
192
|
+
anchor: CircuitJsonSchematicDocumentGraphicBuilder.#tableAnchor(
|
|
193
|
+
table?.anchor
|
|
194
|
+
),
|
|
195
|
+
column_widths: Primitives.array(
|
|
196
|
+
table?.columnWidths ?? table?.column_widths
|
|
197
|
+
).map((width) => Primitives.number(width, 0)),
|
|
198
|
+
row_heights: Primitives.array(
|
|
199
|
+
table?.rowHeights ?? table?.row_heights
|
|
200
|
+
).map((height) => Primitives.number(height, 0)),
|
|
201
|
+
border_width: Primitives.number(
|
|
202
|
+
table?.borderWidth ?? table?.border_width,
|
|
203
|
+
0
|
|
204
|
+
),
|
|
205
|
+
...CircuitJsonSchematicDocumentGraphicBuilder.#ownerField(
|
|
206
|
+
table,
|
|
207
|
+
ownerIds
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
for (const [cellIndex, cell] of Primitives.array(
|
|
212
|
+
table?.cells
|
|
213
|
+
).entries()) {
|
|
214
|
+
rows.push(
|
|
215
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#tableCell(
|
|
216
|
+
cell,
|
|
217
|
+
cellIndex,
|
|
218
|
+
table,
|
|
219
|
+
tableId,
|
|
220
|
+
idScope
|
|
221
|
+
)
|
|
222
|
+
)
|
|
223
|
+
}
|
|
224
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#group(
|
|
225
|
+
groups,
|
|
226
|
+
table,
|
|
227
|
+
FAMILY_ORDER.table,
|
|
228
|
+
index,
|
|
229
|
+
rows
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Appends hierarchical sheet boxes and entry ports.
|
|
236
|
+
* @param {object[]} groups Render groups.
|
|
237
|
+
* @param {Record<string, unknown>} schematic Native schematic.
|
|
238
|
+
* @param {string} idScope Document id scope.
|
|
239
|
+
* @param {Map<string, string>} ownerIds Owner-to-component lookup.
|
|
240
|
+
* @returns {void}
|
|
241
|
+
*/
|
|
242
|
+
static #appendSheets(groups, schematic, idScope, ownerIds) {
|
|
243
|
+
const sheets = Primitives.array(schematic.sheetSymbols)
|
|
244
|
+
const sheetRows = new Map()
|
|
245
|
+
for (const [index, sheet] of sheets.entries()) {
|
|
246
|
+
const identity =
|
|
247
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#identity(
|
|
248
|
+
sheet,
|
|
249
|
+
index
|
|
250
|
+
)
|
|
251
|
+
const sheetSymbolId = Primitives.id(idScope, [
|
|
252
|
+
'schematic_sheet_symbol',
|
|
253
|
+
identity
|
|
254
|
+
])
|
|
255
|
+
const sourceComponentId = Primitives.id(idScope, [
|
|
256
|
+
'source_component',
|
|
257
|
+
'sheet',
|
|
258
|
+
identity
|
|
259
|
+
])
|
|
260
|
+
const height = Math.abs(Primitives.number(sheet?.height, 0) || 0)
|
|
261
|
+
const y = Primitives.number(sheet?.y, 0) || 0
|
|
262
|
+
const width = Math.abs(Primitives.number(sheet?.width, 0) || 0)
|
|
263
|
+
const x = Primitives.number(sheet?.x, 0) || 0
|
|
264
|
+
sheetRows.set(sheet, { sheetSymbolId, sourceComponentId })
|
|
265
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#group(
|
|
266
|
+
groups,
|
|
267
|
+
sheet,
|
|
268
|
+
FAMILY_ORDER.sheet,
|
|
269
|
+
index,
|
|
270
|
+
[
|
|
271
|
+
{
|
|
272
|
+
type: 'source_component',
|
|
273
|
+
source_component_id: sourceComponentId,
|
|
274
|
+
name: Primitives.string(
|
|
275
|
+
sheet?.name || sheet?.fileName,
|
|
276
|
+
`Sheet ${index + 1}`
|
|
277
|
+
),
|
|
278
|
+
ftype: 'simple_chip'
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
type: 'schematic_sheet_symbol',
|
|
282
|
+
schematic_sheet_symbol_id: sheetSymbolId,
|
|
283
|
+
name: Primitives.string(
|
|
284
|
+
sheet?.name || sheet?.fileName,
|
|
285
|
+
`Sheet ${index + 1}`
|
|
286
|
+
),
|
|
287
|
+
...(sheet?.fileName
|
|
288
|
+
? { source_file_name: String(sheet.fileName) }
|
|
289
|
+
: {}),
|
|
290
|
+
center: Primitives.point(x + width / 2, y - height / 2),
|
|
291
|
+
width,
|
|
292
|
+
height,
|
|
293
|
+
stroke_width: Primitives.number(sheet?.lineWidth, 1),
|
|
294
|
+
color: Primitives.string(sheet?.color, '#000000'),
|
|
295
|
+
fill_color: Primitives.string(sheet?.fill, '#ffffff'),
|
|
296
|
+
...CircuitJsonSchematicStrokeStyle.fields(
|
|
297
|
+
sheet,
|
|
298
|
+
sheet?.lineWidth
|
|
299
|
+
),
|
|
300
|
+
is_filled:
|
|
301
|
+
sheet?.isSolid === true &&
|
|
302
|
+
sheet?.transparent !== true,
|
|
303
|
+
render_order:
|
|
304
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#renderOrder(
|
|
305
|
+
sheet,
|
|
306
|
+
index
|
|
307
|
+
),
|
|
308
|
+
...CircuitJsonSchematicDocumentGraphicBuilder.#ownerField(
|
|
309
|
+
sheet,
|
|
310
|
+
ownerIds
|
|
311
|
+
)
|
|
312
|
+
}
|
|
313
|
+
]
|
|
314
|
+
)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
for (const [index, entry] of Primitives.array(
|
|
318
|
+
schematic.sheetEntries
|
|
319
|
+
).entries()) {
|
|
320
|
+
const sheet =
|
|
321
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#sheetForEntry(
|
|
322
|
+
entry,
|
|
323
|
+
sheets
|
|
324
|
+
)
|
|
325
|
+
const parent = sheetRows.get(sheet)
|
|
326
|
+
if (!parent) continue
|
|
327
|
+
const identity =
|
|
328
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#identity(
|
|
329
|
+
entry,
|
|
330
|
+
index
|
|
331
|
+
)
|
|
332
|
+
const sourcePortId = Primitives.id(idScope, [
|
|
333
|
+
'source_port',
|
|
334
|
+
'sheet',
|
|
335
|
+
parent.sheetSymbolId,
|
|
336
|
+
identity
|
|
337
|
+
])
|
|
338
|
+
const side = CircuitJsonSchematicDocumentGraphicBuilder.#sheetSide(
|
|
339
|
+
entry?.side
|
|
340
|
+
)
|
|
341
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#group(
|
|
342
|
+
groups,
|
|
343
|
+
entry,
|
|
344
|
+
FAMILY_ORDER.sheet,
|
|
345
|
+
sheets.length + index,
|
|
346
|
+
[
|
|
347
|
+
{
|
|
348
|
+
type: 'source_port',
|
|
349
|
+
source_port_id: sourcePortId,
|
|
350
|
+
source_component_id: parent.sourceComponentId,
|
|
351
|
+
name: Primitives.string(
|
|
352
|
+
entry?.name,
|
|
353
|
+
`PORT_${index + 1}`
|
|
354
|
+
)
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
type: 'schematic_port',
|
|
358
|
+
schematic_port_id: Primitives.id(idScope, [
|
|
359
|
+
'schematic_port',
|
|
360
|
+
'sheet',
|
|
361
|
+
parent.sheetSymbolId,
|
|
362
|
+
identity
|
|
363
|
+
]),
|
|
364
|
+
source_port_id: sourcePortId,
|
|
365
|
+
schematic_sheet_symbol_id: parent.sheetSymbolId,
|
|
366
|
+
display_pin_label: Primitives.string(entry?.name, ''),
|
|
367
|
+
center: Primitives.point(entry?.x, entry?.y),
|
|
368
|
+
side_of_component: side,
|
|
369
|
+
facing_direction:
|
|
370
|
+
side === 'top'
|
|
371
|
+
? 'up'
|
|
372
|
+
: side === 'bottom'
|
|
373
|
+
? 'down'
|
|
374
|
+
: side,
|
|
375
|
+
has_input_arrow:
|
|
376
|
+
entry?.direction === 'input' ||
|
|
377
|
+
entry?.direction === 'bidirectional',
|
|
378
|
+
has_output_arrow:
|
|
379
|
+
entry?.direction === 'output' ||
|
|
380
|
+
entry?.direction === 'bidirectional'
|
|
381
|
+
}
|
|
382
|
+
]
|
|
383
|
+
)
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Appends asset-backed schematic image placements.
|
|
389
|
+
* @param {object[]} groups Render groups.
|
|
390
|
+
* @param {Record<string, unknown>} schematic Native schematic.
|
|
391
|
+
* @param {string} idScope Document id scope.
|
|
392
|
+
* @param {Map<string, string>} ownerIds Owner-to-component lookup.
|
|
393
|
+
* @returns {void}
|
|
394
|
+
*/
|
|
395
|
+
static #appendImages(groups, schematic, idScope, ownerIds) {
|
|
396
|
+
for (const [index, image] of Primitives.array(
|
|
397
|
+
schematic.images
|
|
398
|
+
).entries()) {
|
|
399
|
+
const row = CircuitJsonSchematicImageProjection.element(
|
|
400
|
+
image,
|
|
401
|
+
index,
|
|
402
|
+
idScope,
|
|
403
|
+
ownerIds
|
|
404
|
+
)
|
|
405
|
+
if (!row) continue
|
|
406
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#group(
|
|
407
|
+
groups,
|
|
408
|
+
image,
|
|
409
|
+
FAMILY_ORDER.image,
|
|
410
|
+
index,
|
|
411
|
+
[row]
|
|
412
|
+
)
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Appends non-frame schematic text and net labels.
|
|
418
|
+
* @param {object[]} groups Render groups.
|
|
419
|
+
* @param {Record<string, unknown>} schematic Native schematic.
|
|
420
|
+
* @param {string} idScope Document id scope.
|
|
421
|
+
* @param {Map<string, string>} ownerIds Owner-to-component lookup.
|
|
422
|
+
* @returns {void}
|
|
423
|
+
*/
|
|
424
|
+
static #appendTexts(groups, schematic, idScope, ownerIds) {
|
|
425
|
+
const hasTextFrames = Primitives.array(schematic.textFrames).length > 0
|
|
426
|
+
for (const [index, text] of Primitives.array(
|
|
427
|
+
schematic.texts
|
|
428
|
+
).entries()) {
|
|
429
|
+
if (hasTextFrames && String(text?.recordType || '') === '28') {
|
|
430
|
+
continue
|
|
431
|
+
}
|
|
432
|
+
const textValue = Primitives.string(
|
|
433
|
+
text?.text || text?.value || text?.name,
|
|
434
|
+
''
|
|
435
|
+
)
|
|
436
|
+
const center = Primitives.point(text?.x, text?.y)
|
|
437
|
+
const row = Primitives.isNetLabel(text)
|
|
438
|
+
? {
|
|
439
|
+
type: 'schematic_net_label',
|
|
440
|
+
schematic_net_label_id: Primitives.id(idScope, [
|
|
441
|
+
'schematic_net_label',
|
|
442
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#identity(
|
|
443
|
+
text,
|
|
444
|
+
index
|
|
445
|
+
)
|
|
446
|
+
]),
|
|
447
|
+
source_net_id: Primitives.sourceNetId(
|
|
448
|
+
idScope,
|
|
449
|
+
textValue || index
|
|
450
|
+
),
|
|
451
|
+
text: textValue,
|
|
452
|
+
center,
|
|
453
|
+
anchor_position: center,
|
|
454
|
+
anchor_side: 'top',
|
|
455
|
+
rotation:
|
|
456
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#textRotation(
|
|
457
|
+
text
|
|
458
|
+
)
|
|
459
|
+
}
|
|
460
|
+
: {
|
|
461
|
+
type: 'schematic_text',
|
|
462
|
+
schematic_text_id: Primitives.id(idScope, [
|
|
463
|
+
'schematic_text',
|
|
464
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#identity(
|
|
465
|
+
text,
|
|
466
|
+
index
|
|
467
|
+
)
|
|
468
|
+
]),
|
|
469
|
+
text: textValue,
|
|
470
|
+
position: center,
|
|
471
|
+
font_size: Primitives.number(
|
|
472
|
+
text?.fontSize || text?.size,
|
|
473
|
+
0.18
|
|
474
|
+
),
|
|
475
|
+
rotation:
|
|
476
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#textRotation(
|
|
477
|
+
text
|
|
478
|
+
),
|
|
479
|
+
anchor: CircuitJsonSchematicDocumentGraphicBuilder.#textAnchor(
|
|
480
|
+
text
|
|
481
|
+
),
|
|
482
|
+
color: Primitives.string(text?.color, '#000000'),
|
|
483
|
+
...CircuitJsonSchematicDocumentGraphicBuilder.#ownerField(
|
|
484
|
+
text,
|
|
485
|
+
ownerIds
|
|
486
|
+
)
|
|
487
|
+
}
|
|
488
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#group(
|
|
489
|
+
groups,
|
|
490
|
+
text,
|
|
491
|
+
FAMILY_ORDER.text,
|
|
492
|
+
index,
|
|
493
|
+
[row]
|
|
494
|
+
)
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Builds one table cell from explicit or derived geometry.
|
|
500
|
+
* @param {Record<string, unknown>} cell Native table cell.
|
|
501
|
+
* @param {number} index Cell index.
|
|
502
|
+
* @param {Record<string, unknown>} table Native table.
|
|
503
|
+
* @param {string} tableId Canonical table id.
|
|
504
|
+
* @param {string} idScope Document id scope.
|
|
505
|
+
* @returns {object}
|
|
506
|
+
*/
|
|
507
|
+
static #tableCell(cell, index, table, tableId, idScope) {
|
|
508
|
+
const startRow = Math.max(
|
|
509
|
+
Math.trunc(
|
|
510
|
+
Primitives.number(
|
|
511
|
+
cell?.startRowIndex ?? cell?.row ?? cell?.start_row_index,
|
|
512
|
+
0
|
|
513
|
+
) || 0
|
|
514
|
+
),
|
|
515
|
+
0
|
|
516
|
+
)
|
|
517
|
+
const endRow = Math.max(
|
|
518
|
+
Math.trunc(
|
|
519
|
+
Primitives.number(
|
|
520
|
+
cell?.endRowIndex ?? cell?.end_row_index,
|
|
521
|
+
startRow
|
|
522
|
+
) || startRow
|
|
523
|
+
),
|
|
524
|
+
startRow
|
|
525
|
+
)
|
|
526
|
+
const startColumn = Math.max(
|
|
527
|
+
Math.trunc(
|
|
528
|
+
Primitives.number(
|
|
529
|
+
cell?.startColumnIndex ??
|
|
530
|
+
cell?.column ??
|
|
531
|
+
cell?.start_column_index,
|
|
532
|
+
0
|
|
533
|
+
) || 0
|
|
534
|
+
),
|
|
535
|
+
0
|
|
536
|
+
)
|
|
537
|
+
const endColumn = Math.max(
|
|
538
|
+
Math.trunc(
|
|
539
|
+
Primitives.number(
|
|
540
|
+
cell?.endColumnIndex ?? cell?.end_column_index,
|
|
541
|
+
startColumn
|
|
542
|
+
) || startColumn
|
|
543
|
+
),
|
|
544
|
+
startColumn
|
|
545
|
+
)
|
|
546
|
+
const columnWidths = Primitives.array(
|
|
547
|
+
table?.columnWidths ?? table?.column_widths
|
|
548
|
+
)
|
|
549
|
+
const rowHeights = Primitives.array(
|
|
550
|
+
table?.rowHeights ?? table?.row_heights
|
|
551
|
+
)
|
|
552
|
+
const anchor = Primitives.point(
|
|
553
|
+
table?.anchorPosition?.x ?? table?.x,
|
|
554
|
+
table?.anchorPosition?.y ?? table?.y
|
|
555
|
+
)
|
|
556
|
+
const beforeX = CircuitJsonSchematicDocumentGraphicBuilder.#sum(
|
|
557
|
+
columnWidths.slice(0, startColumn)
|
|
558
|
+
)
|
|
559
|
+
const width = CircuitJsonSchematicDocumentGraphicBuilder.#sum(
|
|
560
|
+
columnWidths.slice(startColumn, endColumn + 1)
|
|
561
|
+
)
|
|
562
|
+
const beforeY = CircuitJsonSchematicDocumentGraphicBuilder.#sum(
|
|
563
|
+
rowHeights.slice(0, startRow)
|
|
564
|
+
)
|
|
565
|
+
const height = CircuitJsonSchematicDocumentGraphicBuilder.#sum(
|
|
566
|
+
rowHeights.slice(startRow, endRow + 1)
|
|
567
|
+
)
|
|
568
|
+
return {
|
|
569
|
+
type: 'schematic_table_cell',
|
|
570
|
+
schematic_table_cell_id: Primitives.id(idScope, [
|
|
571
|
+
'schematic_table_cell',
|
|
572
|
+
tableId,
|
|
573
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#identity(
|
|
574
|
+
cell,
|
|
575
|
+
index
|
|
576
|
+
)
|
|
577
|
+
]),
|
|
578
|
+
schematic_table_id: tableId,
|
|
579
|
+
text: Primitives.string(cell?.text, ''),
|
|
580
|
+
center: Primitives.point(
|
|
581
|
+
cell?.center?.x ?? anchor.x + beforeX + width / 2,
|
|
582
|
+
cell?.center?.y ?? anchor.y + beforeY + height / 2
|
|
583
|
+
),
|
|
584
|
+
width: Primitives.number(cell?.width, width),
|
|
585
|
+
height: Primitives.number(cell?.height, height),
|
|
586
|
+
start_column_index: startColumn,
|
|
587
|
+
end_column_index: endColumn,
|
|
588
|
+
start_row_index: startRow,
|
|
589
|
+
end_row_index: endRow,
|
|
590
|
+
horizontal_align:
|
|
591
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#horizontalAlign(
|
|
592
|
+
cell?.horizontalAlign ?? cell?.alignment
|
|
593
|
+
),
|
|
594
|
+
vertical_align:
|
|
595
|
+
CircuitJsonSchematicDocumentGraphicBuilder.#verticalAlign(
|
|
596
|
+
cell?.verticalAlign
|
|
597
|
+
)
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Sums numeric size values.
|
|
603
|
+
* @param {unknown[]} values Size values.
|
|
604
|
+
* @returns {number}
|
|
605
|
+
*/
|
|
606
|
+
static #sum(values) {
|
|
607
|
+
return values.reduce(
|
|
608
|
+
(sum, value) => sum + (Primitives.number(value, 0) || 0),
|
|
609
|
+
0
|
|
610
|
+
)
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Adds one sortable group descriptor.
|
|
615
|
+
* @param {object[]} groups Group list.
|
|
616
|
+
* @param {Record<string, unknown>} source Native source row.
|
|
617
|
+
* @param {number} familyOrder Graphic family order.
|
|
618
|
+
* @param {number} index Family-local index.
|
|
619
|
+
* @param {object[]} rows Canonical rows.
|
|
620
|
+
* @returns {void}
|
|
621
|
+
*/
|
|
622
|
+
static #group(groups, source, familyOrder, index, rows) {
|
|
623
|
+
const renderOrder = Primitives.number(source?.renderOrder, index)
|
|
624
|
+
groups.push({
|
|
625
|
+
order: Number.isFinite(renderOrder) ? renderOrder : index,
|
|
626
|
+
familyOrder,
|
|
627
|
+
index,
|
|
628
|
+
rows
|
|
629
|
+
})
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Returns a deterministic native identity.
|
|
634
|
+
* @param {Record<string, unknown>} source Native row.
|
|
635
|
+
* @param {number} index Family-local index.
|
|
636
|
+
* @returns {unknown}
|
|
637
|
+
*/
|
|
638
|
+
static #identity(source, index) {
|
|
639
|
+
return (
|
|
640
|
+
source?.recordId ||
|
|
641
|
+
source?.uniqueId ||
|
|
642
|
+
source?.uuid ||
|
|
643
|
+
source?.id ||
|
|
644
|
+
source?.renderOrder ||
|
|
645
|
+
index
|
|
646
|
+
)
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Resolves one safe authored render order.
|
|
651
|
+
* @param {Record<string, unknown>} source Native row.
|
|
652
|
+
* @param {number} fallback Fallback order.
|
|
653
|
+
* @returns {number}
|
|
654
|
+
*/
|
|
655
|
+
static #renderOrder(source, fallback) {
|
|
656
|
+
const order = Primitives.number(source?.renderOrder, fallback)
|
|
657
|
+
return Number.isSafeInteger(order) ? order : fallback
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Returns optional component ownership.
|
|
662
|
+
* @param {Record<string, unknown>} source Native row.
|
|
663
|
+
* @param {Map<string, string>} ownerIds Owner lookup.
|
|
664
|
+
* @returns {{ schematic_component_id?: string }}
|
|
665
|
+
*/
|
|
666
|
+
static #ownerField(source, ownerIds) {
|
|
667
|
+
const ownerId = ownerIds.get(String(source?.ownerIndex || ''))
|
|
668
|
+
return ownerId ? { schematic_component_id: ownerId } : {}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Finds the parent sheet whose perimeter contains an entry.
|
|
673
|
+
* @param {Record<string, unknown>} entry Native sheet entry.
|
|
674
|
+
* @param {Record<string, unknown>[]} sheets Native sheets.
|
|
675
|
+
* @returns {Record<string, unknown> | undefined}
|
|
676
|
+
*/
|
|
677
|
+
static #sheetForEntry(entry, sheets) {
|
|
678
|
+
const x = Primitives.number(entry?.x, 0) || 0
|
|
679
|
+
const y = Primitives.number(entry?.y, 0) || 0
|
|
680
|
+
const epsilon = 0.000001
|
|
681
|
+
return sheets.find((sheet) => {
|
|
682
|
+
const left = Primitives.number(sheet?.x, 0) || 0
|
|
683
|
+
const top = Primitives.number(sheet?.y, 0) || 0
|
|
684
|
+
const right = left + (Primitives.number(sheet?.width, 0) || 0)
|
|
685
|
+
const bottom = top - (Primitives.number(sheet?.height, 0) || 0)
|
|
686
|
+
const withinX = x >= left - epsilon && x <= right + epsilon
|
|
687
|
+
const withinY = y >= bottom - epsilon && y <= top + epsilon
|
|
688
|
+
const onVertical =
|
|
689
|
+
Math.abs(x - left) <= epsilon || Math.abs(x - right) <= epsilon
|
|
690
|
+
const onHorizontal =
|
|
691
|
+
Math.abs(y - top) <= epsilon || Math.abs(y - bottom) <= epsilon
|
|
692
|
+
return withinX && withinY && (onVertical || onHorizontal)
|
|
693
|
+
})
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Normalizes a sheet-entry side.
|
|
698
|
+
* @param {unknown} side Native side.
|
|
699
|
+
* @returns {'left' | 'right' | 'top' | 'bottom'}
|
|
700
|
+
*/
|
|
701
|
+
static #sheetSide(side) {
|
|
702
|
+
const normalized = String(side || '').toLowerCase()
|
|
703
|
+
return ['left', 'right', 'top', 'bottom'].includes(normalized)
|
|
704
|
+
? normalized
|
|
705
|
+
: 'left'
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Normalizes one text anchor.
|
|
710
|
+
* @param {Record<string, unknown>} text Native text.
|
|
711
|
+
* @returns {string}
|
|
712
|
+
*/
|
|
713
|
+
static #textAnchor(text) {
|
|
714
|
+
if (text?.verticalAnchor === 'top') {
|
|
715
|
+
if (text?.anchor === 'end') return 'top_right'
|
|
716
|
+
if (text?.anchor === 'middle') return 'top_center'
|
|
717
|
+
return 'top_left'
|
|
718
|
+
}
|
|
719
|
+
if (text?.anchor === 'end') return 'right'
|
|
720
|
+
if (text?.anchor === 'start') return 'left'
|
|
721
|
+
return 'center'
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Preserves the historical signed SVG rotation for Altium text.
|
|
726
|
+
* @param {Record<string, unknown>} text Native text.
|
|
727
|
+
* @returns {number} Signed canonical rotation.
|
|
728
|
+
*/
|
|
729
|
+
static #textRotation(text) {
|
|
730
|
+
const rotation = Primitives.number(text?.rotation, 0) || 0
|
|
731
|
+
if (!rotation) return 0
|
|
732
|
+
const signed =
|
|
733
|
+
Number(text?.sourceOrientation || 0) === 3 ? rotation : -rotation
|
|
734
|
+
return text?.isMirrored === true ? -signed : signed
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Normalizes one table anchor.
|
|
739
|
+
* @param {unknown} anchor Native anchor.
|
|
740
|
+
* @returns {string}
|
|
741
|
+
*/
|
|
742
|
+
static #tableAnchor(anchor) {
|
|
743
|
+
const normalized = String(anchor || '').toLowerCase()
|
|
744
|
+
return [
|
|
745
|
+
'top_left',
|
|
746
|
+
'top_center',
|
|
747
|
+
'top_right',
|
|
748
|
+
'center_left',
|
|
749
|
+
'center',
|
|
750
|
+
'center_right',
|
|
751
|
+
'bottom_left',
|
|
752
|
+
'bottom_center',
|
|
753
|
+
'bottom_right'
|
|
754
|
+
].includes(normalized)
|
|
755
|
+
? normalized
|
|
756
|
+
: 'top_left'
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Normalizes horizontal table-cell alignment.
|
|
761
|
+
* @param {unknown} alignment Native alignment.
|
|
762
|
+
* @returns {'left' | 'center' | 'right'}
|
|
763
|
+
*/
|
|
764
|
+
static #horizontalAlign(alignment) {
|
|
765
|
+
const normalized = String(alignment || '').toLowerCase()
|
|
766
|
+
return ['left', 'center', 'right'].includes(normalized)
|
|
767
|
+
? normalized
|
|
768
|
+
: 'left'
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* Normalizes vertical table-cell alignment.
|
|
773
|
+
* @param {unknown} alignment Native alignment.
|
|
774
|
+
* @returns {'top' | 'middle' | 'bottom'}
|
|
775
|
+
*/
|
|
776
|
+
static #verticalAlign(alignment) {
|
|
777
|
+
const normalized = String(alignment || '').toLowerCase()
|
|
778
|
+
return ['top', 'middle', 'bottom'].includes(normalized)
|
|
779
|
+
? normalized
|
|
780
|
+
: 'middle'
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
Object.freeze(CircuitJsonSchematicDocumentGraphicBuilder.prototype)
|
|
785
|
+
Object.freeze(CircuitJsonSchematicDocumentGraphicBuilder)
|