altium-toolkit 1.1.35 → 1.1.36
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/AGENTS.md +2 -2
- package/package.json +1 -1
- package/src/core/altium/PrintableTextDecoder.mjs +20 -1
- package/src/core/altium/SchematicMultipartOwnerMatcher.mjs +288 -7
- package/src/core/altium/SchematicTextParser.mjs +18 -253
- package/src/core/altium/SchematicTitleBlockParser.mjs +410 -0
- package/src/core/altium/SelectedPartAltiumExportAdapter.mjs +215 -0
- package/src/parser.mjs +1 -0
- package/src/ui/AltiumScene3dShapeStackOwnerAdapter.mjs +149 -20
- package/src/ui/PcbScene3dPackages.mjs +16 -1
- package/src/ui/PcbScene3dStaticBodyOwnerPromotion.mjs +899 -0
- package/src/ui/PcbScene3dStaticBodyPlacementBuilder.mjs +138 -8
- package/src/ui/PcbScene3dStaticBodyPrototypeRecovery.mjs +445 -0
- package/src/ui/PcbScene3dStaticBodyRecovery.mjs +7 -1
- package/src/ui/PcbScene3dStaticBodySelectionKeyBuilder.mjs +199 -2
- package/src/ui/PcbScene3dStaticBodySymmetricOwnerPromotion.mjs +438 -0
- package/src/ui/PcbScene3dStaticBodySymmetryRecovery.mjs +23 -2
- package/src/ui/SchematicColorResolver.mjs +44 -0
- package/src/ui/SchematicLineColorResolver.mjs +1 -1
- package/src/ui/SchematicShapeRenderer.mjs +13 -13
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
4
4
|
|
|
5
5
|
import { ParserUtils } from './ParserUtils.mjs'
|
|
6
|
+
import { SchematicTitleBlockParser } from './SchematicTitleBlockParser.mjs'
|
|
6
7
|
import { SchematicTextRunParser } from './SchematicTextRunParser.mjs'
|
|
7
8
|
import { SchematicTextOrientationResolver } from './SchematicTextOrientationResolver.mjs'
|
|
8
9
|
|
|
@@ -152,13 +153,19 @@ export class SchematicTextParser {
|
|
|
152
153
|
const rawText = ParserUtils.getDisplayText(fields)
|
|
153
154
|
const recordType = ParserUtils.getField(fields, 'RECORD')
|
|
154
155
|
const ownerIndex = ParserUtils.getField(fields, 'OwnerIndex')
|
|
156
|
+
const textMetadata = SchematicTextParser.isTitleBlockFooterRecord(
|
|
157
|
+
fields,
|
|
158
|
+
sheet.width
|
|
159
|
+
)
|
|
160
|
+
? metadata
|
|
161
|
+
: SchematicTextParser.#resolveSchematicTextMetadata(
|
|
162
|
+
ownerIndex,
|
|
163
|
+
metadata,
|
|
164
|
+
ownerMetadata
|
|
165
|
+
)
|
|
155
166
|
const text = SchematicTextParser.#resolveSchematicTemplateText(
|
|
156
167
|
rawText,
|
|
157
|
-
|
|
158
|
-
ownerIndex,
|
|
159
|
-
metadata,
|
|
160
|
-
ownerMetadata
|
|
161
|
-
)
|
|
168
|
+
textMetadata
|
|
162
169
|
)
|
|
163
170
|
const textRuns = SchematicTextRunParser.parseOptionalOverlineRuns(text)
|
|
164
171
|
|
|
@@ -267,70 +274,12 @@ export class SchematicTextParser {
|
|
|
267
274
|
* @returns {{ title: string, revision: string, documentNumber: string, sheetNumber: string, sheetTotal: string, date: string, drawnBy: string, footerHints: Partial<Record<'title' | 'documentNumber' | 'revision' | 'sheetNumber' | 'sheetTotal', { x: number, y: number, color: string, fontSize: number, fontFamily: string, fontWeight: number, fontStyle?: string }>> }}
|
|
268
275
|
*/
|
|
269
276
|
static extractSchematicTitleBlock(records, metadata, sheetWidth, fonts) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
)
|
|
276
|
-
)
|
|
277
|
-
.map((record) =>
|
|
278
|
-
SchematicTextParser.#normalizeTitleBlockFooterRecord(
|
|
279
|
-
record.fields,
|
|
280
|
-
fonts
|
|
281
|
-
)
|
|
282
|
-
)
|
|
283
|
-
.filter(Boolean)
|
|
284
|
-
.sort((left, right) => right.y - left.y || left.x - right.x)
|
|
285
|
-
const footerHints =
|
|
286
|
-
SchematicTextParser.#collectSchematicTitleBlockFooterHints(
|
|
287
|
-
footerTexts
|
|
288
|
-
)
|
|
289
|
-
const numericFooterTexts = footerTexts.filter((record) =>
|
|
290
|
-
/^\d+$/.test(record.text)
|
|
277
|
+
return SchematicTitleBlockParser.extract(
|
|
278
|
+
records,
|
|
279
|
+
metadata,
|
|
280
|
+
sheetWidth,
|
|
281
|
+
fonts
|
|
291
282
|
)
|
|
292
|
-
const footerDrawnBy =
|
|
293
|
-
SchematicTextParser.#extractSchematicTitleBlockFooterDrawnBy(
|
|
294
|
-
footerTexts,
|
|
295
|
-
metadata
|
|
296
|
-
)
|
|
297
|
-
|
|
298
|
-
return {
|
|
299
|
-
title:
|
|
300
|
-
SchematicTextParser.#resolveTitleBlockFooterValue(
|
|
301
|
-
footerHints.title?.text,
|
|
302
|
-
metadata
|
|
303
|
-
) || SchematicTextParser.#cleanMetadataValue(metadata.title),
|
|
304
|
-
revision:
|
|
305
|
-
SchematicTextParser.#resolveTitleBlockFooterValue(
|
|
306
|
-
footerHints.revision?.text,
|
|
307
|
-
metadata
|
|
308
|
-
) || SchematicTextParser.#cleanMetadataValue(metadata.revision),
|
|
309
|
-
documentNumber: SchematicTextParser.#cleanMetadataValue(
|
|
310
|
-
SchematicTextParser.#resolveTitleBlockFooterValue(
|
|
311
|
-
footerHints.documentNumber?.text,
|
|
312
|
-
metadata
|
|
313
|
-
) || metadata.documentnumber
|
|
314
|
-
),
|
|
315
|
-
sheetNumber:
|
|
316
|
-
footerHints.sheetNumber?.text ||
|
|
317
|
-
numericFooterTexts[0]?.text ||
|
|
318
|
-
SchematicTextParser.#cleanMetadataValue(metadata.sheetnumber),
|
|
319
|
-
sheetTotal:
|
|
320
|
-
footerHints.sheetTotal?.text ||
|
|
321
|
-
numericFooterTexts[1]?.text ||
|
|
322
|
-
SchematicTextParser.#cleanMetadataValue(metadata.sheettotal),
|
|
323
|
-
date: SchematicTextParser.#cleanMetadataValue(
|
|
324
|
-
metadata.currentdate || metadata.date
|
|
325
|
-
),
|
|
326
|
-
drawnBy:
|
|
327
|
-
SchematicTextParser.#cleanMetadataValue(metadata.drawnby) ||
|
|
328
|
-
footerDrawnBy,
|
|
329
|
-
footerHints:
|
|
330
|
-
SchematicTextParser.#stripSchematicTitleBlockHintText(
|
|
331
|
-
footerHints
|
|
332
|
-
)
|
|
333
|
-
}
|
|
334
283
|
}
|
|
335
284
|
|
|
336
285
|
/**
|
|
@@ -340,182 +289,7 @@ export class SchematicTextParser {
|
|
|
340
289
|
* @returns {boolean}
|
|
341
290
|
*/
|
|
342
291
|
static isTitleBlockFooterRecord(fields, sheetWidth) {
|
|
343
|
-
|
|
344
|
-
const x = ParserUtils.parseNumericField(fields, 'Location.X')
|
|
345
|
-
const y = ParserUtils.parseNumericField(fields, 'Location.Y')
|
|
346
|
-
|
|
347
|
-
return (
|
|
348
|
-
recordType === '4' &&
|
|
349
|
-
x !== null &&
|
|
350
|
-
y !== null &&
|
|
351
|
-
x >= sheetWidth * 0.55 &&
|
|
352
|
-
y <= 100
|
|
353
|
-
)
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Normalizes one visible footer text record into a title-block layout hint.
|
|
358
|
-
* @param {Record<string, string | string[]>} fields
|
|
359
|
-
* @param {Record<string, { size: number, family: string, bold: boolean, italic?: boolean, rotation: number }>} fonts
|
|
360
|
-
* @returns {{ text: string, x: number, y: number, color: string, fontSize: number, fontFamily: string, fontWeight: number, fontStyle?: string } | null}
|
|
361
|
-
*/
|
|
362
|
-
static #normalizeTitleBlockFooterRecord(fields, fonts) {
|
|
363
|
-
const text = ParserUtils.getDisplayText(fields)
|
|
364
|
-
const x = ParserUtils.parseNumericField(fields, 'Location.X')
|
|
365
|
-
const y = ParserUtils.parseNumericField(fields, 'Location.Y')
|
|
366
|
-
|
|
367
|
-
if (!text || x === null || y === null) {
|
|
368
|
-
return null
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
const font =
|
|
372
|
-
fonts[ParserUtils.getField(fields, 'FontID')] ||
|
|
373
|
-
SchematicTextParser.#defaultSchematicFont()
|
|
374
|
-
|
|
375
|
-
return {
|
|
376
|
-
text,
|
|
377
|
-
x,
|
|
378
|
-
y,
|
|
379
|
-
color: SchematicTextParser.#resolveSchematicTextColor(
|
|
380
|
-
fields,
|
|
381
|
-
ParserUtils.getField(fields, 'RECORD')
|
|
382
|
-
),
|
|
383
|
-
fontSize: font.size,
|
|
384
|
-
fontFamily: font.family,
|
|
385
|
-
fontWeight: font.bold ? 700 : 400,
|
|
386
|
-
...(font.italic ? { fontStyle: 'italic' } : {})
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* Maps visible footer rows onto title-block fields.
|
|
392
|
-
* @param {{ text: string, x: number, y: number, color: string, fontSize: number, fontFamily: string, fontWeight: number, fontStyle?: string }[]} footerTexts
|
|
393
|
-
* @returns {Partial<Record<'title' | 'documentNumber' | 'revision' | 'sheetNumber' | 'sheetTotal', { text: string, x: number, y: number, color: string, fontSize: number, fontFamily: string, fontWeight: number, fontStyle?: string }>>}
|
|
394
|
-
*/
|
|
395
|
-
static #collectSchematicTitleBlockFooterHints(footerTexts) {
|
|
396
|
-
const rows = SchematicTextParser.#groupTitleBlockFooterRows(footerTexts)
|
|
397
|
-
const topRow = rows[0] || []
|
|
398
|
-
const middleRow = rows.length > 2 ? rows[1] || [] : []
|
|
399
|
-
const sheetRow =
|
|
400
|
-
[...rows]
|
|
401
|
-
.reverse()
|
|
402
|
-
.find(
|
|
403
|
-
(row) =>
|
|
404
|
-
row.filter((record) => /^\d+$/.test(record.text))
|
|
405
|
-
.length >= 2
|
|
406
|
-
) || []
|
|
407
|
-
const numericSheetRow = sheetRow.filter((record) =>
|
|
408
|
-
/^\d+$/.test(record.text)
|
|
409
|
-
)
|
|
410
|
-
const topRowHasVisibleTitleText = topRow.some(
|
|
411
|
-
(record) => /^\d+$/.test(record.text) === false
|
|
412
|
-
)
|
|
413
|
-
const hints = {}
|
|
414
|
-
|
|
415
|
-
if (topRow.length && topRowHasVisibleTitleText) {
|
|
416
|
-
hints.title = topRow[0]
|
|
417
|
-
|
|
418
|
-
if (topRow.length > 1) {
|
|
419
|
-
hints.documentNumber = topRow.at(-1)
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
if (middleRow.length) {
|
|
424
|
-
hints.revision = middleRow.at(-1)
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
if (numericSheetRow.length) {
|
|
428
|
-
hints.sheetNumber = numericSheetRow[0]
|
|
429
|
-
hints.sheetTotal = numericSheetRow.at(-1)
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
return hints
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
/**
|
|
436
|
-
* Groups footer texts by their shared baseline row.
|
|
437
|
-
* @param {{ text: string, x: number, y: number, color: string, fontSize: number, fontFamily: string, fontWeight: number, fontStyle?: string }[]} footerTexts
|
|
438
|
-
* @returns {Array<{ text: string, x: number, y: number, color: string, fontSize: number, fontFamily: string, fontWeight: number, fontStyle?: string }[]>}
|
|
439
|
-
*/
|
|
440
|
-
static #groupTitleBlockFooterRows(footerTexts) {
|
|
441
|
-
const tolerance = 8
|
|
442
|
-
const rows = []
|
|
443
|
-
|
|
444
|
-
for (const record of footerTexts) {
|
|
445
|
-
const currentRow = rows.at(-1)
|
|
446
|
-
|
|
447
|
-
if (
|
|
448
|
-
currentRow &&
|
|
449
|
-
Math.abs(currentRow[0].y - record.y) <= tolerance
|
|
450
|
-
) {
|
|
451
|
-
currentRow.push(record)
|
|
452
|
-
currentRow.sort((left, right) => left.x - right.x)
|
|
453
|
-
continue
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
rows.push([record])
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
return rows
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
/**
|
|
463
|
-
* Resolves one visible footer placeholder against hidden sheet metadata.
|
|
464
|
-
* @param {string | undefined} text
|
|
465
|
-
* @param {Record<string, string>} metadata
|
|
466
|
-
* @returns {string}
|
|
467
|
-
*/
|
|
468
|
-
static #resolveTitleBlockFooterValue(text, metadata) {
|
|
469
|
-
const resolved = SchematicTextParser.#resolveSchematicTemplateText(
|
|
470
|
-
text,
|
|
471
|
-
metadata
|
|
472
|
-
)
|
|
473
|
-
|
|
474
|
-
if (String(resolved || '').startsWith('=')) {
|
|
475
|
-
return ''
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
return SchematicTextParser.#cleanMetadataValue(resolved)
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
/**
|
|
482
|
-
* Extracts a visible footer `Drawn By` value from the bottom-most footer
|
|
483
|
-
* row when hidden metadata does not provide one.
|
|
484
|
-
* @param {{ text: string, x: number, y: number, color: string, fontSize: number, fontFamily: string, fontWeight: number, fontStyle?: string }[]} footerTexts
|
|
485
|
-
* @param {Record<string, string>} metadata
|
|
486
|
-
* @returns {string}
|
|
487
|
-
*/
|
|
488
|
-
static #extractSchematicTitleBlockFooterDrawnBy(footerTexts, metadata) {
|
|
489
|
-
const bottomRow =
|
|
490
|
-
SchematicTextParser.#groupTitleBlockFooterRows(footerTexts).at(
|
|
491
|
-
-1
|
|
492
|
-
) || []
|
|
493
|
-
const candidates = bottomRow
|
|
494
|
-
.map((record) =>
|
|
495
|
-
SchematicTextParser.#resolveTitleBlockFooterValue(
|
|
496
|
-
record.text,
|
|
497
|
-
metadata
|
|
498
|
-
)
|
|
499
|
-
)
|
|
500
|
-
.filter(
|
|
501
|
-
(value) => value && /^\d+$/.test(String(value).trim()) === false
|
|
502
|
-
)
|
|
503
|
-
|
|
504
|
-
return candidates.at(-1) || ''
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
/**
|
|
508
|
-
* Removes the non-rendered text payload from stored footer hints.
|
|
509
|
-
* @param {Partial<Record<'title' | 'documentNumber' | 'revision' | 'sheetNumber' | 'sheetTotal', { text: string, x: number, y: number, color: string, fontSize: number, fontFamily: string, fontWeight: number, fontStyle?: string }>>} footerHints
|
|
510
|
-
* @returns {Partial<Record<'title' | 'documentNumber' | 'revision' | 'sheetNumber' | 'sheetTotal', { x: number, y: number, color: string, fontSize: number, fontFamily: string, fontWeight: number, fontStyle?: string }>>}
|
|
511
|
-
*/
|
|
512
|
-
static #stripSchematicTitleBlockHintText(footerHints) {
|
|
513
|
-
return Object.fromEntries(
|
|
514
|
-
Object.entries(footerHints).map(([key, value]) => {
|
|
515
|
-
const { text: _text, ...hint } = value
|
|
516
|
-
return [key, hint]
|
|
517
|
-
})
|
|
518
|
-
)
|
|
292
|
+
return SchematicTitleBlockParser.isFooterRecord(fields, sheetWidth)
|
|
519
293
|
}
|
|
520
294
|
|
|
521
295
|
/**
|
|
@@ -853,15 +627,6 @@ export class SchematicTextParser {
|
|
|
853
627
|
return Number(size || 10)
|
|
854
628
|
}
|
|
855
629
|
|
|
856
|
-
/**
|
|
857
|
-
* Normalizes placeholder metadata values.
|
|
858
|
-
* @param {string | undefined} value
|
|
859
|
-
* @returns {string}
|
|
860
|
-
*/
|
|
861
|
-
static #cleanMetadataValue(value) {
|
|
862
|
-
return value && value !== '*' ? value : ''
|
|
863
|
-
}
|
|
864
|
-
|
|
865
630
|
/**
|
|
866
631
|
* Adds note box metadata to one decoded schematic note record.
|
|
867
632
|
* @param {{ x: number, y: number, text: string, color: string, hidden: boolean, name: string, ownerIndex?: string, recordType: string, style: number, fontSize: number, fontFamily: string, fontWeight: number, fontStyle?: string, rotation: number, sourceOrientation?: number, isMirrored?: boolean, anchor: 'start' | 'middle' | 'end' }} textRecord
|