altium-toolkit 1.0.9 → 1.1.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/docs/api.md +6 -2
- package/docs/model-format.md +29 -4
- package/docs/schemas/altium_toolkit/ci_artifact_bundle_a1.schema.json +80 -0
- package/docs/schemas/altium_toolkit/contract_gate_a1.schema.json +34 -0
- package/docs/schemas/altium_toolkit/draftsman_board_view_cache_a1.schema.json +115 -0
- package/docs/schemas/altium_toolkit/draftsman_digest_a1.schema.json +166 -0
- package/docs/schemas/altium_toolkit/host_capabilities_a1.schema.json +39 -0
- package/docs/schemas/altium_toolkit/library_merge_plan_a1.schema.json +56 -0
- package/docs/schemas/altium_toolkit/library_qa_a1.schema.json +70 -0
- package/docs/schemas/altium_toolkit/netlist_a1.schema.json +6 -0
- package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +856 -7
- package/docs/schemas/altium_toolkit/parser_compatibility_fuzz_a1.schema.json +25 -0
- package/docs/schemas/altium_toolkit/pcb_bom_profile_a1.schema.json +48 -0
- package/docs/schemas/altium_toolkit/pcb_layer_stack_a1.schema.json +98 -0
- package/docs/schemas/altium_toolkit/pcb_layer_stack_fidelity_a1.schema.json +66 -0
- package/docs/schemas/altium_toolkit/pcb_placed_footprint_extraction_a1.schema.json +31 -0
- package/docs/schemas/altium_toolkit/pcb_review_metadata_a1.schema.json +62 -0
- package/docs/schemas/altium_toolkit/pcb_rigid_flex_topology_a1.schema.json +52 -0
- package/docs/schemas/altium_toolkit/pcb_svg_semantics_a1.schema.json +27 -0
- package/docs/schemas/altium_toolkit/pcblib_parity_a1.schema.json +24 -0
- package/docs/schemas/altium_toolkit/project_bom_pnp_reconciliation_a1.schema.json +63 -0
- package/docs/schemas/altium_toolkit/project_bundle_a1.schema.json +6 -0
- package/docs/schemas/altium_toolkit/project_document_graph_a1.schema.json +33 -0
- package/docs/schemas/altium_toolkit/project_outjob_digest_a1.schema.json +46 -0
- package/docs/schemas/altium_toolkit/project_script_a1.schema.json +50 -0
- package/docs/schemas/altium_toolkit/schematic_render_ops_a1.schema.json +55 -0
- package/docs/schemas/altium_toolkit/schematic_template_extraction_a1.schema.json +37 -0
- package/docs/schemas/altium_toolkit/svg_model_cross_link_a1.schema.json +39 -0
- package/package.json +1 -1
- package/src/core/altium/AltiumParser.mjs +12 -2
- package/src/core/altium/CiArtifactBundleBuilder.mjs +213 -0
- package/src/core/altium/ContractGateReportBuilder.mjs +351 -0
- package/src/core/altium/DraftsmanBoardViewMetadataBuilder.mjs +653 -0
- package/src/core/altium/DraftsmanDigestParser.mjs +928 -0
- package/src/core/altium/DraftsmanImagePayloadManifestBuilder.mjs +178 -0
- package/src/core/altium/HostCapabilityDiagnosticsBuilder.mjs +271 -0
- package/src/core/altium/LibraryQaReportBuilder.mjs +504 -0
- package/src/core/altium/LibraryRenderManifestBuilder.mjs +172 -2
- package/src/core/altium/ParserCompatibilityFuzzer.mjs +192 -0
- package/src/core/altium/PcbBomProfileBuilder.mjs +263 -0
- package/src/core/altium/PcbComponentKindPolicy.mjs +146 -0
- package/src/core/altium/PcbLayerStackFidelityReportBuilder.mjs +141 -0
- package/src/core/altium/PcbLayerStackInterchangeParser.mjs +453 -0
- package/src/core/altium/PcbLayerStackQueryHelper.mjs +195 -0
- package/src/core/altium/PcbLayerStackReadModelBuilder.mjs +906 -0
- package/src/core/altium/PcbLayerStackSourceMetadataParser.mjs +488 -0
- package/src/core/altium/PcbLibModelParser.mjs +2 -0
- package/src/core/altium/PcbLibParityReportBuilder.mjs +242 -0
- package/src/core/altium/PcbModelParser.mjs +211 -22
- package/src/core/altium/PcbPadStackParser.mjs +171 -2
- package/src/core/altium/PcbPickPlacePositionResolver.mjs +11 -1
- package/src/core/altium/PcbPlacedFootprintManifestBuilder.mjs +338 -0
- package/src/core/altium/PcbPolygonRecordParser.mjs +120 -0
- package/src/core/altium/PcbRegionPrimitiveParser.mjs +71 -2
- package/src/core/altium/PcbReviewDrillMetadataBuilder.mjs +301 -0
- package/src/core/altium/PcbReviewMetadataBuilder.mjs +373 -0
- package/src/core/altium/PcbReviewPolygonRealizationBuilder.mjs +269 -0
- package/src/core/altium/PcbReviewRouteHighlightProfileBuilder.mjs +298 -0
- package/src/core/altium/PcbRigidFlexTopologyBuilder.mjs +171 -0
- package/src/core/altium/PcbRouteAnalysisBuilder.mjs +730 -0
- package/src/core/altium/PcbStatisticsBuilder.mjs +9 -0
- package/src/core/altium/PrintableTextDecoder.mjs +70 -6
- package/src/core/altium/PrjPcbModelParser.mjs +69 -2
- package/src/core/altium/PrjScrModelParser.mjs +386 -0
- package/src/core/altium/ProjectBomPnpReconciliationBuilder.mjs +237 -0
- package/src/core/altium/ProjectDesignBundleBuilder.mjs +76 -2
- package/src/core/altium/ProjectDocumentGraphBuilder.mjs +280 -0
- package/src/core/altium/ProjectNetlistExporter.mjs +5 -1
- package/src/core/altium/ProjectOutJobDigestBuilder.mjs +424 -13
- package/src/core/altium/SvgModelCrossLinkValidator.mjs +435 -0
- package/src/core/circuit-json/CircuitJsonModelAdapter.mjs +300 -96
- package/src/core/circuit-json/CircuitJsonModelAdapterPcbElements.mjs +244 -0
- package/src/core/circuit-json/CircuitJsonModelSchema.mjs +1 -1
- package/src/parser.mjs +21 -0
- package/src/ui/PcbFootprintPrimitiveSelector.mjs +13 -1
- package/src/ui/PcbScene3dBuilder.mjs +26 -4
- package/src/ui/PcbSvgRenderer.mjs +65 -0
- package/src/ui/SchematicRenderOpsSidecarBuilder.mjs +554 -0
- package/src/ui/SchematicSvgRenderer.mjs +48 -2
|
@@ -14,7 +14,8 @@ export class ProjectOutJobDigestBuilder {
|
|
|
14
14
|
* @returns {object}
|
|
15
15
|
*/
|
|
16
16
|
static build(project) {
|
|
17
|
-
const
|
|
17
|
+
const projectDocuments = project?.documents || []
|
|
18
|
+
const documents = projectDocuments
|
|
18
19
|
.filter((document) => document.kind === 'output-job')
|
|
19
20
|
.map((document) => ({
|
|
20
21
|
documentIndex: document.index,
|
|
@@ -22,23 +23,39 @@ export class ProjectOutJobDigestBuilder {
|
|
|
22
23
|
normalizedPath: document.normalizedPath,
|
|
23
24
|
fileName: document.fileName
|
|
24
25
|
}))
|
|
26
|
+
const context = {
|
|
27
|
+
defaultPcbDocumentPath:
|
|
28
|
+
projectDocuments.find((document) => document.kind === 'pcb')
|
|
29
|
+
?.path || ''
|
|
30
|
+
}
|
|
25
31
|
const outputGroups = (project?.outputGroups || []).map((group) =>
|
|
26
|
-
ProjectOutJobDigestBuilder.#outputGroup(group)
|
|
32
|
+
ProjectOutJobDigestBuilder.#outputGroup(group, context)
|
|
27
33
|
)
|
|
28
34
|
const outputCount = outputGroups.reduce(
|
|
29
35
|
(sum, group) => sum + group.outputCount,
|
|
30
36
|
0
|
|
31
37
|
)
|
|
38
|
+
const outputs = outputGroups.flatMap((group) => group.outputs)
|
|
39
|
+
const expectedArtifacts =
|
|
40
|
+
ProjectOutJobDigestBuilder.#expectedArtifacts(outputGroups)
|
|
32
41
|
|
|
33
42
|
return {
|
|
34
43
|
schema: ProjectOutJobDigestBuilder.SCHEMA_ID,
|
|
35
44
|
summary: {
|
|
36
45
|
outJobDocumentCount: documents.length,
|
|
37
46
|
outputGroupCount: outputGroups.length,
|
|
38
|
-
outputCount
|
|
47
|
+
outputCount,
|
|
48
|
+
typedOutputCount: outputs.filter(
|
|
49
|
+
(output) => output.normalizedType !== 'unsupported'
|
|
50
|
+
).length,
|
|
51
|
+
unsupportedOutputCount: outputs.filter(
|
|
52
|
+
(output) => output.normalizedType === 'unsupported'
|
|
53
|
+
).length,
|
|
54
|
+
expectedArtifactCount: expectedArtifacts.manifest.outputs.length
|
|
39
55
|
},
|
|
40
56
|
documents,
|
|
41
57
|
outputGroups,
|
|
58
|
+
expectedArtifacts,
|
|
42
59
|
outputsByDocumentPath:
|
|
43
60
|
ProjectOutJobDigestBuilder.#outputsByDocumentPath(outputGroups)
|
|
44
61
|
}
|
|
@@ -47,19 +64,12 @@ export class ProjectOutJobDigestBuilder {
|
|
|
47
64
|
/**
|
|
48
65
|
* Normalizes one output group.
|
|
49
66
|
* @param {object} group Project output group.
|
|
67
|
+
* @param {{ defaultPcbDocumentPath: string }} context Project context.
|
|
50
68
|
* @returns {object}
|
|
51
69
|
*/
|
|
52
|
-
static #outputGroup(group) {
|
|
70
|
+
static #outputGroup(group, context) {
|
|
53
71
|
const outputs = (group.outputs || []).map((output) => ({
|
|
54
|
-
|
|
55
|
-
type: output.type,
|
|
56
|
-
name: output.name,
|
|
57
|
-
documentPath: output.documentPath,
|
|
58
|
-
normalizedDocumentPath: ProjectOutJobDigestBuilder.#normalizePath(
|
|
59
|
-
output.documentPath
|
|
60
|
-
),
|
|
61
|
-
variantName: output.variantName,
|
|
62
|
-
isDefault: output.isDefault
|
|
72
|
+
...ProjectOutJobDigestBuilder.#typedOutput(output, group, context)
|
|
63
73
|
}))
|
|
64
74
|
|
|
65
75
|
return {
|
|
@@ -88,6 +98,7 @@ export class ProjectOutJobDigestBuilder {
|
|
|
88
98
|
outputGroupIndex: group.index,
|
|
89
99
|
outputIndex: output.index,
|
|
90
100
|
type: output.type,
|
|
101
|
+
normalizedType: output.normalizedType,
|
|
91
102
|
name: output.name,
|
|
92
103
|
variantName: output.variantName,
|
|
93
104
|
isDefault: output.isDefault
|
|
@@ -106,4 +117,404 @@ export class ProjectOutJobDigestBuilder {
|
|
|
106
117
|
static #normalizePath(path) {
|
|
107
118
|
return String(path || '').replace(/\\/g, '/')
|
|
108
119
|
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Builds one typed output row.
|
|
123
|
+
* @param {object} output Raw output row.
|
|
124
|
+
* @param {object} group Owning output group.
|
|
125
|
+
* @param {{ defaultPcbDocumentPath: string }} context Project context.
|
|
126
|
+
* @returns {object}
|
|
127
|
+
*/
|
|
128
|
+
static #typedOutput(output, group, context) {
|
|
129
|
+
const config = ProjectOutJobDigestBuilder.#mergedConfig(output)
|
|
130
|
+
const normalizedType = ProjectOutJobDigestBuilder.#normalizedType(
|
|
131
|
+
output.type
|
|
132
|
+
)
|
|
133
|
+
const category = ProjectOutJobDigestBuilder.#category(normalizedType)
|
|
134
|
+
const documentPath = ProjectOutJobDigestBuilder.#documentPath(
|
|
135
|
+
normalizedType,
|
|
136
|
+
output,
|
|
137
|
+
config,
|
|
138
|
+
context
|
|
139
|
+
)
|
|
140
|
+
const normalizedDocumentPath =
|
|
141
|
+
ProjectOutJobDigestBuilder.#normalizePath(documentPath)
|
|
142
|
+
const settings = ProjectOutJobDigestBuilder.#settings(
|
|
143
|
+
normalizedType,
|
|
144
|
+
output,
|
|
145
|
+
config,
|
|
146
|
+
documentPath
|
|
147
|
+
)
|
|
148
|
+
const base = {
|
|
149
|
+
index: output.index,
|
|
150
|
+
type: output.type,
|
|
151
|
+
normalizedType,
|
|
152
|
+
name: output.name,
|
|
153
|
+
documentPath: output.documentPath || '',
|
|
154
|
+
normalizedDocumentPath,
|
|
155
|
+
variantName: output.variantName || config.VariantName || '',
|
|
156
|
+
isDefault: output.isDefault,
|
|
157
|
+
category,
|
|
158
|
+
settings
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
...base,
|
|
163
|
+
expectedArtifact: ProjectOutJobDigestBuilder.#expectedArtifact(
|
|
164
|
+
base,
|
|
165
|
+
group
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Resolves the source document path for one output row.
|
|
172
|
+
* @param {string} normalizedType Stable output type.
|
|
173
|
+
* @param {object} output Raw output row.
|
|
174
|
+
* @param {Record<string, string>} config Merged config fields.
|
|
175
|
+
* @param {{ defaultPcbDocumentPath: string }} context Project context.
|
|
176
|
+
* @returns {string}
|
|
177
|
+
*/
|
|
178
|
+
static #documentPath(normalizedType, output, config, context) {
|
|
179
|
+
const explicit = output.documentPath || config.DocumentPath || ''
|
|
180
|
+
if (explicit) return explicit
|
|
181
|
+
|
|
182
|
+
if (
|
|
183
|
+
normalizedType === 'bom' &&
|
|
184
|
+
ProjectOutJobDigestBuilder.#boolean(config.IncludePcbData) === true
|
|
185
|
+
) {
|
|
186
|
+
return context.defaultPcbDocumentPath || ''
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return ''
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Merges parsed configuration rows into one field lookup.
|
|
194
|
+
* @param {object} output Output row.
|
|
195
|
+
* @returns {Record<string, string>}
|
|
196
|
+
*/
|
|
197
|
+
static #mergedConfig(output) {
|
|
198
|
+
const config = {}
|
|
199
|
+
|
|
200
|
+
for (const row of output.configRows || []) {
|
|
201
|
+
if (row.record && !config.Record) {
|
|
202
|
+
config.Record = row.record
|
|
203
|
+
}
|
|
204
|
+
Object.assign(config, row.fields || {})
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return config
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Resolves a stable output type token.
|
|
212
|
+
* @param {string} type Native output type.
|
|
213
|
+
* @returns {string}
|
|
214
|
+
*/
|
|
215
|
+
static #normalizedType(type) {
|
|
216
|
+
const normalized = String(type || '')
|
|
217
|
+
.trim()
|
|
218
|
+
.toLowerCase()
|
|
219
|
+
.replace(/[\s_]+/gu, '-')
|
|
220
|
+
|
|
221
|
+
if (normalized.includes('gerber')) return 'gerber'
|
|
222
|
+
if (normalized.includes('ncdrill') || normalized.includes('nc-drill')) {
|
|
223
|
+
return 'nc-drill'
|
|
224
|
+
}
|
|
225
|
+
if (normalized.includes('odb')) return 'odb'
|
|
226
|
+
if (
|
|
227
|
+
normalized.includes('pickplace') ||
|
|
228
|
+
normalized.includes('pick-place')
|
|
229
|
+
) {
|
|
230
|
+
return 'pick-place'
|
|
231
|
+
}
|
|
232
|
+
if (normalized.includes('wirelist')) return 'wirelist'
|
|
233
|
+
if (normalized.includes('bom')) return 'bom'
|
|
234
|
+
if (normalized.includes('step')) return 'step'
|
|
235
|
+
if (normalized.includes('schematicprint')) return 'schematic-print'
|
|
236
|
+
if (
|
|
237
|
+
normalized.includes('pcbdrawing') ||
|
|
238
|
+
normalized.includes('draftsman')
|
|
239
|
+
) {
|
|
240
|
+
return 'pcb-drawing'
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
return 'unsupported'
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Resolves an output category.
|
|
248
|
+
* @param {string} normalizedType Stable output type.
|
|
249
|
+
* @returns {string}
|
|
250
|
+
*/
|
|
251
|
+
static #category(normalizedType) {
|
|
252
|
+
return (
|
|
253
|
+
{
|
|
254
|
+
gerber: 'fabrication',
|
|
255
|
+
'nc-drill': 'fabrication',
|
|
256
|
+
odb: 'fabrication',
|
|
257
|
+
'pick-place': 'assembly',
|
|
258
|
+
wirelist: 'netlist',
|
|
259
|
+
bom: 'report',
|
|
260
|
+
step: 'export',
|
|
261
|
+
'schematic-print': 'documentation',
|
|
262
|
+
'pcb-drawing': 'documentation'
|
|
263
|
+
}[normalizedType] || 'unsupported'
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Builds typed settings for one output row.
|
|
269
|
+
* @param {string} normalizedType Stable output type.
|
|
270
|
+
* @param {object} output Output row.
|
|
271
|
+
* @param {Record<string, string>} config Merged config fields.
|
|
272
|
+
* @param {string} documentPath Resolved output document path.
|
|
273
|
+
* @returns {object}
|
|
274
|
+
*/
|
|
275
|
+
static #settings(normalizedType, output, config, documentPath) {
|
|
276
|
+
const common = ProjectOutJobDigestBuilder.#stripEmpty({
|
|
277
|
+
record: config.Record || '',
|
|
278
|
+
documentPath
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
switch (normalizedType) {
|
|
282
|
+
case 'gerber':
|
|
283
|
+
return ProjectOutJobDigestBuilder.#stripEmpty({
|
|
284
|
+
...common,
|
|
285
|
+
units: config.GerberUnit || config.Units || '',
|
|
286
|
+
decimals: ProjectOutJobDigestBuilder.#number(
|
|
287
|
+
config.NumberOfDecimals
|
|
288
|
+
),
|
|
289
|
+
plotLayers: ProjectOutJobDigestBuilder.#plotLayers(
|
|
290
|
+
config['Plot.Set']
|
|
291
|
+
)
|
|
292
|
+
})
|
|
293
|
+
case 'nc-drill':
|
|
294
|
+
return ProjectOutJobDigestBuilder.#stripEmpty({
|
|
295
|
+
...common,
|
|
296
|
+
units: config.Units || '',
|
|
297
|
+
separatePlated:
|
|
298
|
+
ProjectOutJobDigestBuilder.#boolean(
|
|
299
|
+
config.SeparatePlated
|
|
300
|
+
) ??
|
|
301
|
+
ProjectOutJobDigestBuilder.#boolean(
|
|
302
|
+
config.GenerateSeparatePlatedNonPlatedFiles
|
|
303
|
+
)
|
|
304
|
+
})
|
|
305
|
+
case 'pick-place':
|
|
306
|
+
return ProjectOutJobDigestBuilder.#stripEmpty({
|
|
307
|
+
...common,
|
|
308
|
+
units: config.Units || '',
|
|
309
|
+
generateCsv: ProjectOutJobDigestBuilder.#boolean(
|
|
310
|
+
config.GenerateCSVFormat
|
|
311
|
+
),
|
|
312
|
+
includeStandardNoBom: ProjectOutJobDigestBuilder.#boolean(
|
|
313
|
+
config.IncludeStandardNoBOM
|
|
314
|
+
)
|
|
315
|
+
})
|
|
316
|
+
case 'wirelist':
|
|
317
|
+
return ProjectOutJobDigestBuilder.#stripEmpty({
|
|
318
|
+
...common,
|
|
319
|
+
units: config.Units || '',
|
|
320
|
+
generateText: ProjectOutJobDigestBuilder.#boolean(
|
|
321
|
+
config.GenerateTextFormat
|
|
322
|
+
),
|
|
323
|
+
includeVariations: ProjectOutJobDigestBuilder.#boolean(
|
|
324
|
+
config.IncludeVariations
|
|
325
|
+
)
|
|
326
|
+
})
|
|
327
|
+
case 'bom':
|
|
328
|
+
return ProjectOutJobDigestBuilder.#stripEmpty({
|
|
329
|
+
...common,
|
|
330
|
+
includePcbData: ProjectOutJobDigestBuilder.#boolean(
|
|
331
|
+
config.IncludePcbData
|
|
332
|
+
),
|
|
333
|
+
includeAlternatives: ProjectOutJobDigestBuilder.#boolean(
|
|
334
|
+
config.IncludeAlternatives
|
|
335
|
+
),
|
|
336
|
+
batchMode: ProjectOutJobDigestBuilder.#number(
|
|
337
|
+
config.BatchMode
|
|
338
|
+
),
|
|
339
|
+
viewType: ProjectOutJobDigestBuilder.#number(
|
|
340
|
+
config.ViewType
|
|
341
|
+
)
|
|
342
|
+
})
|
|
343
|
+
case 'step':
|
|
344
|
+
return ProjectOutJobDigestBuilder.#stripEmpty({
|
|
345
|
+
...common,
|
|
346
|
+
exportModelsOption: ProjectOutJobDigestBuilder.#number(
|
|
347
|
+
config.ExportModelsOption
|
|
348
|
+
),
|
|
349
|
+
exportHolesOption: ProjectOutJobDigestBuilder.#number(
|
|
350
|
+
config.ExportHolesOption
|
|
351
|
+
)
|
|
352
|
+
})
|
|
353
|
+
case 'schematic-print':
|
|
354
|
+
return ProjectOutJobDigestBuilder.#stripEmpty({
|
|
355
|
+
...common,
|
|
356
|
+
paperKind: config.PaperKind || '',
|
|
357
|
+
printScale: ProjectOutJobDigestBuilder.#number(
|
|
358
|
+
config.PrintScale
|
|
359
|
+
)
|
|
360
|
+
})
|
|
361
|
+
case 'pcb-drawing':
|
|
362
|
+
return ProjectOutJobDigestBuilder.#stripEmpty({
|
|
363
|
+
...common,
|
|
364
|
+
variantName: config.VariantName || ''
|
|
365
|
+
})
|
|
366
|
+
default:
|
|
367
|
+
return common
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Builds expected artifact metadata for all output rows.
|
|
373
|
+
* @param {object[]} outputGroups Output groups.
|
|
374
|
+
* @returns {object}
|
|
375
|
+
*/
|
|
376
|
+
static #expectedArtifacts(outputGroups) {
|
|
377
|
+
const outputs = outputGroups.flatMap((group) =>
|
|
378
|
+
group.outputs.map((output) => output.expectedArtifact)
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
return {
|
|
382
|
+
schema: 'altium-toolkit.project.expected-artifacts.a1',
|
|
383
|
+
summary: {
|
|
384
|
+
outputCount: outputs.length,
|
|
385
|
+
unsupportedOutputCount: outputs.filter(
|
|
386
|
+
(output) => output.unsupported
|
|
387
|
+
).length
|
|
388
|
+
},
|
|
389
|
+
manifest: {
|
|
390
|
+
outputs
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Builds one expected artifact row.
|
|
397
|
+
* @param {object} output Typed output row.
|
|
398
|
+
* @param {object} group Owning group.
|
|
399
|
+
* @returns {object}
|
|
400
|
+
*/
|
|
401
|
+
static #expectedArtifact(output, group) {
|
|
402
|
+
const artifact = ProjectOutJobDigestBuilder.#stripEmpty({
|
|
403
|
+
key:
|
|
404
|
+
ProjectOutJobDigestBuilder.#slug(group.name || 'outputs') +
|
|
405
|
+
'/' +
|
|
406
|
+
String(output.index).padStart(2, '0') +
|
|
407
|
+
'-' +
|
|
408
|
+
ProjectOutJobDigestBuilder.#slug(
|
|
409
|
+
output.name || output.normalizedType
|
|
410
|
+
),
|
|
411
|
+
outputGroupName: group.name || '',
|
|
412
|
+
outputName: output.name || '',
|
|
413
|
+
outputType: output.normalizedType,
|
|
414
|
+
category: output.category,
|
|
415
|
+
documentPath:
|
|
416
|
+
output.settings.documentPath || output.documentPath || '',
|
|
417
|
+
normalizedDocumentPath: output.normalizedDocumentPath,
|
|
418
|
+
variantName: output.variantName,
|
|
419
|
+
format: ProjectOutJobDigestBuilder.#format(output),
|
|
420
|
+
units: output.settings.units,
|
|
421
|
+
unsupported:
|
|
422
|
+
output.normalizedType === 'unsupported' ? true : undefined
|
|
423
|
+
})
|
|
424
|
+
artifact.variantName = output.variantName || ''
|
|
425
|
+
return artifact
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Resolves the expected artifact format token.
|
|
430
|
+
* @param {object} output Typed output row.
|
|
431
|
+
* @returns {string}
|
|
432
|
+
*/
|
|
433
|
+
static #format(output) {
|
|
434
|
+
if (output.normalizedType === 'pick-place') {
|
|
435
|
+
return output.settings.generateCsv === false
|
|
436
|
+
? 'pick-place-text'
|
|
437
|
+
: 'pick-place-csv'
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return (
|
|
441
|
+
{
|
|
442
|
+
gerber: 'gerber',
|
|
443
|
+
'nc-drill': 'nc-drill',
|
|
444
|
+
odb: 'odb',
|
|
445
|
+
wirelist: 'wirelist',
|
|
446
|
+
bom: 'bom',
|
|
447
|
+
step: 'step',
|
|
448
|
+
'schematic-print': 'pdf',
|
|
449
|
+
'pcb-drawing': 'pcbdwf'
|
|
450
|
+
}[output.normalizedType] || 'unknown'
|
|
451
|
+
)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Parses plot-layer tokens from Altium plot-layer state strings.
|
|
456
|
+
* @param {string | undefined} value Plot-layer state.
|
|
457
|
+
* @returns {string[] | undefined}
|
|
458
|
+
*/
|
|
459
|
+
static #plotLayers(value) {
|
|
460
|
+
const layers = String(value || '')
|
|
461
|
+
.split(',')
|
|
462
|
+
.map((segment) => segment.trim().split('~')[0])
|
|
463
|
+
.filter(Boolean)
|
|
464
|
+
|
|
465
|
+
return layers.length ? layers : undefined
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Parses one numeric field.
|
|
470
|
+
* @param {unknown} value Raw value.
|
|
471
|
+
* @returns {number | undefined}
|
|
472
|
+
*/
|
|
473
|
+
static #number(value) {
|
|
474
|
+
const parsed = Number.parseFloat(String(value ?? '').trim())
|
|
475
|
+
return Number.isFinite(parsed) ? parsed : undefined
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Parses one optional boolean field.
|
|
480
|
+
* @param {unknown} value Raw value.
|
|
481
|
+
* @returns {boolean | undefined}
|
|
482
|
+
*/
|
|
483
|
+
static #boolean(value) {
|
|
484
|
+
const raw = String(value ?? '')
|
|
485
|
+
.trim()
|
|
486
|
+
.toLowerCase()
|
|
487
|
+
if (!raw) return undefined
|
|
488
|
+
return ['1', 't', 'true', 'yes'].includes(raw)
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Removes empty values while preserving booleans and zeroes.
|
|
493
|
+
* @param {object} value Source object.
|
|
494
|
+
* @returns {object}
|
|
495
|
+
*/
|
|
496
|
+
static #stripEmpty(value) {
|
|
497
|
+
return Object.fromEntries(
|
|
498
|
+
Object.entries(value || {}).filter(([, entryValue]) => {
|
|
499
|
+
if (Array.isArray(entryValue)) return entryValue.length > 0
|
|
500
|
+
if (typeof entryValue === 'string') return entryValue.length > 0
|
|
501
|
+
return entryValue !== null && entryValue !== undefined
|
|
502
|
+
})
|
|
503
|
+
)
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Builds a stable slug for output manifest keys.
|
|
508
|
+
* @param {string} value Raw value.
|
|
509
|
+
* @returns {string}
|
|
510
|
+
*/
|
|
511
|
+
static #slug(value) {
|
|
512
|
+
return (
|
|
513
|
+
String(value || '')
|
|
514
|
+
.trim()
|
|
515
|
+
.toLowerCase()
|
|
516
|
+
.replace(/[^a-z0-9]+/gu, '-')
|
|
517
|
+
.replace(/^-|-$/gu, '') || 'output'
|
|
518
|
+
)
|
|
519
|
+
}
|
|
109
520
|
}
|