altium-toolkit 1.0.10 → 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.
Files changed (63) hide show
  1. package/docs/api.md +6 -2
  2. package/docs/model-format.md +29 -4
  3. package/docs/schemas/altium_toolkit/ci_artifact_bundle_a1.schema.json +4 -0
  4. package/docs/schemas/altium_toolkit/contract_gate_a1.schema.json +34 -0
  5. package/docs/schemas/altium_toolkit/draftsman_board_view_cache_a1.schema.json +115 -0
  6. package/docs/schemas/altium_toolkit/draftsman_digest_a1.schema.json +132 -1
  7. package/docs/schemas/altium_toolkit/host_capabilities_a1.schema.json +39 -0
  8. package/docs/schemas/altium_toolkit/library_merge_plan_a1.schema.json +56 -0
  9. package/docs/schemas/altium_toolkit/library_qa_a1.schema.json +70 -0
  10. package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +692 -2
  11. package/docs/schemas/altium_toolkit/pcb_bom_profile_a1.schema.json +48 -0
  12. package/docs/schemas/altium_toolkit/pcb_layer_stack_a1.schema.json +98 -0
  13. package/docs/schemas/altium_toolkit/pcb_layer_stack_fidelity_a1.schema.json +66 -0
  14. package/docs/schemas/altium_toolkit/pcb_placed_footprint_extraction_a1.schema.json +31 -0
  15. package/docs/schemas/altium_toolkit/pcb_review_metadata_a1.schema.json +62 -0
  16. package/docs/schemas/altium_toolkit/pcb_rigid_flex_topology_a1.schema.json +52 -0
  17. package/docs/schemas/altium_toolkit/pcblib_parity_a1.schema.json +24 -0
  18. package/docs/schemas/altium_toolkit/project_bom_pnp_reconciliation_a1.schema.json +63 -0
  19. package/docs/schemas/altium_toolkit/project_outjob_digest_a1.schema.json +46 -0
  20. package/docs/schemas/altium_toolkit/project_script_a1.schema.json +50 -0
  21. package/docs/schemas/altium_toolkit/schematic_render_ops_a1.schema.json +55 -0
  22. package/docs/schemas/altium_toolkit/schematic_template_extraction_a1.schema.json +37 -0
  23. package/package.json +1 -1
  24. package/src/core/altium/AltiumParser.mjs +7 -2
  25. package/src/core/altium/CiArtifactBundleBuilder.mjs +16 -5
  26. package/src/core/altium/ContractGateReportBuilder.mjs +351 -0
  27. package/src/core/altium/DraftsmanBoardViewMetadataBuilder.mjs +653 -0
  28. package/src/core/altium/DraftsmanDigestParser.mjs +246 -7
  29. package/src/core/altium/DraftsmanImagePayloadManifestBuilder.mjs +178 -0
  30. package/src/core/altium/HostCapabilityDiagnosticsBuilder.mjs +271 -0
  31. package/src/core/altium/LibraryQaReportBuilder.mjs +504 -0
  32. package/src/core/altium/LibraryRenderManifestBuilder.mjs +172 -2
  33. package/src/core/altium/PcbBomProfileBuilder.mjs +263 -0
  34. package/src/core/altium/PcbComponentKindPolicy.mjs +146 -0
  35. package/src/core/altium/PcbLayerStackFidelityReportBuilder.mjs +141 -0
  36. package/src/core/altium/PcbLayerStackInterchangeParser.mjs +453 -0
  37. package/src/core/altium/PcbLayerStackQueryHelper.mjs +195 -0
  38. package/src/core/altium/PcbLayerStackReadModelBuilder.mjs +906 -0
  39. package/src/core/altium/PcbLayerStackSourceMetadataParser.mjs +488 -0
  40. package/src/core/altium/PcbLibModelParser.mjs +2 -0
  41. package/src/core/altium/PcbLibParityReportBuilder.mjs +242 -0
  42. package/src/core/altium/PcbModelParser.mjs +182 -18
  43. package/src/core/altium/PcbPickPlacePositionResolver.mjs +3 -0
  44. package/src/core/altium/PcbPlacedFootprintManifestBuilder.mjs +338 -0
  45. package/src/core/altium/PcbPolygonRecordParser.mjs +120 -0
  46. package/src/core/altium/PcbReviewDrillMetadataBuilder.mjs +301 -0
  47. package/src/core/altium/PcbReviewMetadataBuilder.mjs +373 -0
  48. package/src/core/altium/PcbReviewPolygonRealizationBuilder.mjs +269 -0
  49. package/src/core/altium/PcbReviewRouteHighlightProfileBuilder.mjs +298 -0
  50. package/src/core/altium/PcbRigidFlexTopologyBuilder.mjs +171 -0
  51. package/src/core/altium/PrintableTextDecoder.mjs +70 -6
  52. package/src/core/altium/PrjPcbModelParser.mjs +45 -0
  53. package/src/core/altium/PrjScrModelParser.mjs +386 -0
  54. package/src/core/altium/ProjectBomPnpReconciliationBuilder.mjs +237 -0
  55. package/src/core/altium/ProjectDesignBundleBuilder.mjs +61 -2
  56. package/src/core/altium/ProjectOutJobDigestBuilder.mjs +424 -13
  57. package/src/core/altium/SvgModelCrossLinkValidator.mjs +35 -2
  58. package/src/core/circuit-json/CircuitJsonModelAdapter.mjs +164 -0
  59. package/src/parser.mjs +15 -0
  60. package/src/ui/PcbFootprintPrimitiveSelector.mjs +13 -1
  61. package/src/ui/PcbScene3dBuilder.mjs +26 -4
  62. package/src/ui/SchematicRenderOpsSidecarBuilder.mjs +554 -0
  63. package/src/ui/SchematicSvgRenderer.mjs +48 -2
@@ -0,0 +1,271 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ //
3
+ // SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ /**
6
+ * Builds deterministic host capability and fallback diagnostics.
7
+ */
8
+ export class HostCapabilityDiagnosticsBuilder {
9
+ static SCHEMA = 'altium-toolkit.host-capabilities.a1'
10
+
11
+ /**
12
+ * Builds a host capability diagnostics report.
13
+ * @param {{ host?: object, capabilities?: Record<string, boolean>, fallbacks?: object[] }} options Diagnostics options.
14
+ * @returns {object}
15
+ */
16
+ static build(options = {}) {
17
+ const capabilities = HostCapabilityDiagnosticsBuilder.#capabilityRows(
18
+ options.capabilities || {}
19
+ )
20
+ const diagnostics = [
21
+ ...HostCapabilityDiagnosticsBuilder.#capabilityDiagnostics(
22
+ capabilities
23
+ ),
24
+ ...HostCapabilityDiagnosticsBuilder.#fallbackDiagnostics(
25
+ options.fallbacks || []
26
+ )
27
+ ]
28
+ const readiness = HostCapabilityDiagnosticsBuilder.#readiness(
29
+ options.readinessCategories || [],
30
+ capabilities,
31
+ diagnostics
32
+ )
33
+
34
+ return HostCapabilityDiagnosticsBuilder.#stripUndefined({
35
+ schema: HostCapabilityDiagnosticsBuilder.SCHEMA,
36
+ host: options.host || {},
37
+ summary: HostCapabilityDiagnosticsBuilder.#stripUndefined({
38
+ capabilityCount: capabilities.length,
39
+ unsupportedCapabilityCount: capabilities.filter(
40
+ (capability) => !capability.supported
41
+ ).length,
42
+ fallbackCount: (options.fallbacks || []).length,
43
+ readinessStatus: readiness?.status,
44
+ readinessCategoryCount: readiness?.categories.length,
45
+ warningCount: diagnostics.filter(
46
+ (diagnostic) => diagnostic.severity === 'warning'
47
+ ).length
48
+ }),
49
+ capabilities,
50
+ readiness,
51
+ diagnostics
52
+ })
53
+ }
54
+
55
+ /**
56
+ * Builds sorted capability rows.
57
+ * @param {Record<string, boolean>} capabilities Capability map.
58
+ * @returns {object[]}
59
+ */
60
+ static #capabilityRows(capabilities) {
61
+ return Object.keys(capabilities || {})
62
+ .sort((left, right) =>
63
+ left.localeCompare(right, undefined, { numeric: true })
64
+ )
65
+ .map((key) => {
66
+ const supported = capabilities[key] === true
67
+ return HostCapabilityDiagnosticsBuilder.#stripUndefined({
68
+ key,
69
+ supported,
70
+ diagnosticCode: supported
71
+ ? undefined
72
+ : HostCapabilityDiagnosticsBuilder.#capabilityCode(key)
73
+ })
74
+ })
75
+ }
76
+
77
+ /**
78
+ * Builds diagnostics for unsupported capabilities.
79
+ * @param {object[]} capabilities Capability rows.
80
+ * @returns {object[]}
81
+ */
82
+ static #capabilityDiagnostics(capabilities) {
83
+ return capabilities
84
+ .filter((capability) => !capability.supported)
85
+ .map((capability) => ({
86
+ code: capability.diagnosticCode,
87
+ severity: 'warning',
88
+ capability: capability.key,
89
+ message:
90
+ 'Host capability ' + capability.key + ' is unavailable.'
91
+ }))
92
+ }
93
+
94
+ /**
95
+ * Builds diagnostics for caller-supplied fallback decisions.
96
+ * @param {object[]} fallbacks Fallback rows.
97
+ * @returns {object[]}
98
+ */
99
+ static #fallbackDiagnostics(fallbacks) {
100
+ return (fallbacks || []).map((fallback) =>
101
+ HostCapabilityDiagnosticsBuilder.#stripUndefined({
102
+ ...fallback,
103
+ code: fallback.code || 'host.fallback.used',
104
+ severity: fallback.severity || 'info',
105
+ message:
106
+ fallback.message ||
107
+ 'Host fallback ' +
108
+ (fallback.code || 'host.fallback.used') +
109
+ ' was used.'
110
+ })
111
+ )
112
+ }
113
+
114
+ /**
115
+ * Builds host support readiness groups.
116
+ * @param {object[]} categories Readiness category descriptors.
117
+ * @param {object[]} capabilities Capability rows.
118
+ * @param {object[]} diagnostics Diagnostic rows.
119
+ * @returns {object | undefined}
120
+ */
121
+ static #readiness(categories, capabilities, diagnostics) {
122
+ if (!categories.length) {
123
+ return undefined
124
+ }
125
+
126
+ const capabilityByKey = new Map(
127
+ capabilities.map((capability) => [capability.key, capability])
128
+ )
129
+ const categoryRows = categories.map((category) =>
130
+ HostCapabilityDiagnosticsBuilder.#readinessCategory(
131
+ category,
132
+ capabilityByKey,
133
+ diagnostics
134
+ )
135
+ )
136
+
137
+ return {
138
+ status: HostCapabilityDiagnosticsBuilder.#readinessStatus(
139
+ categoryRows
140
+ ),
141
+ categories: categoryRows
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Builds one readiness category.
147
+ * @param {object} category Category descriptor.
148
+ * @param {Map<string, object>} capabilityByKey Capability lookup.
149
+ * @param {object[]} diagnostics Diagnostic rows.
150
+ * @returns {object}
151
+ */
152
+ static #readinessCategory(category, capabilityByKey, diagnostics) {
153
+ const capabilityKeys = [...(category.capabilityKeys || [])]
154
+ const capabilityRows = capabilityKeys
155
+ .map((key) => capabilityByKey.get(key))
156
+ .filter(Boolean)
157
+ const unsupportedCapabilities = capabilityRows.filter(
158
+ (capability) => !capability.supported
159
+ )
160
+ const categoryDiagnostics =
161
+ HostCapabilityDiagnosticsBuilder.#categoryDiagnostics(
162
+ category.key,
163
+ unsupportedCapabilities,
164
+ diagnostics
165
+ )
166
+ const fallbackCount = categoryDiagnostics.filter(
167
+ (diagnostic) => diagnostic.category === category.key
168
+ ).length
169
+
170
+ return {
171
+ key: category.key,
172
+ displayName: category.displayName || category.key,
173
+ status: HostCapabilityDiagnosticsBuilder.#categoryStatus(
174
+ capabilityRows,
175
+ fallbackCount
176
+ ),
177
+ capabilityKeys,
178
+ supportedCapabilityCount: capabilityRows.filter(
179
+ (capability) => capability.supported
180
+ ).length,
181
+ unsupportedCapabilityCount: unsupportedCapabilities.length,
182
+ fallbackCount,
183
+ diagnosticCodes: categoryDiagnostics.map(
184
+ (diagnostic) => diagnostic.code
185
+ )
186
+ }
187
+ }
188
+
189
+ /**
190
+ * Returns diagnostics related to one readiness category.
191
+ * @param {string} categoryKey Category key.
192
+ * @param {object[]} unsupportedCapabilities Unsupported capabilities.
193
+ * @param {object[]} diagnostics Diagnostic rows.
194
+ * @returns {object[]}
195
+ */
196
+ static #categoryDiagnostics(
197
+ categoryKey,
198
+ unsupportedCapabilities,
199
+ diagnostics
200
+ ) {
201
+ const unsupportedCodes = new Set(
202
+ unsupportedCapabilities.map(
203
+ (capability) => capability.diagnosticCode
204
+ )
205
+ )
206
+
207
+ return diagnostics.filter(
208
+ (diagnostic) =>
209
+ unsupportedCodes.has(diagnostic.code) ||
210
+ diagnostic.category === categoryKey
211
+ )
212
+ }
213
+
214
+ /**
215
+ * Resolves one readiness category status.
216
+ * @param {object[]} capabilityRows Capability rows.
217
+ * @param {number} fallbackCount Fallback count.
218
+ * @returns {'supported' | 'limited' | 'unsupported'}
219
+ */
220
+ static #categoryStatus(capabilityRows, fallbackCount) {
221
+ const supportedCount = capabilityRows.filter(
222
+ (capability) => capability.supported
223
+ ).length
224
+ const unsupportedCount = capabilityRows.length - supportedCount
225
+
226
+ if (capabilityRows.length > 0 && supportedCount === 0) {
227
+ return 'unsupported'
228
+ }
229
+ if (unsupportedCount > 0 || fallbackCount > 0) {
230
+ return 'limited'
231
+ }
232
+ return 'supported'
233
+ }
234
+
235
+ /**
236
+ * Resolves the aggregate readiness status.
237
+ * @param {object[]} categories Readiness category rows.
238
+ * @returns {'supported' | 'limited' | 'unsupported'}
239
+ */
240
+ static #readinessStatus(categories) {
241
+ if (categories.every((category) => category.status === 'supported')) {
242
+ return 'supported'
243
+ }
244
+ if (categories.every((category) => category.status === 'unsupported')) {
245
+ return 'unsupported'
246
+ }
247
+ return 'limited'
248
+ }
249
+
250
+ /**
251
+ * Builds the diagnostic code for an unsupported capability.
252
+ * @param {string} key Capability key.
253
+ * @returns {string}
254
+ */
255
+ static #capabilityCode(key) {
256
+ return 'host.capability.' + key + '.unsupported'
257
+ }
258
+
259
+ /**
260
+ * Removes undefined fields.
261
+ * @param {Record<string, unknown>} value Candidate object.
262
+ * @returns {Record<string, unknown>}
263
+ */
264
+ static #stripUndefined(value) {
265
+ return Object.fromEntries(
266
+ Object.entries(value || {}).filter(
267
+ ([, entryValue]) => entryValue !== undefined
268
+ )
269
+ )
270
+ }
271
+ }