altium-toolkit 1.0.9 → 1.0.10

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 (30) hide show
  1. package/docs/schemas/altium_toolkit/ci_artifact_bundle_a1.schema.json +76 -0
  2. package/docs/schemas/altium_toolkit/draftsman_digest_a1.schema.json +35 -0
  3. package/docs/schemas/altium_toolkit/netlist_a1.schema.json +6 -0
  4. package/docs/schemas/altium_toolkit/normalized_model_a1.schema.json +160 -1
  5. package/docs/schemas/altium_toolkit/parser_compatibility_fuzz_a1.schema.json +25 -0
  6. package/docs/schemas/altium_toolkit/pcb_svg_semantics_a1.schema.json +27 -0
  7. package/docs/schemas/altium_toolkit/project_bundle_a1.schema.json +6 -0
  8. package/docs/schemas/altium_toolkit/project_document_graph_a1.schema.json +33 -0
  9. package/docs/schemas/altium_toolkit/svg_model_cross_link_a1.schema.json +39 -0
  10. package/package.json +1 -1
  11. package/src/core/altium/AltiumParser.mjs +7 -2
  12. package/src/core/altium/CiArtifactBundleBuilder.mjs +202 -0
  13. package/src/core/altium/DraftsmanDigestParser.mjs +689 -0
  14. package/src/core/altium/ParserCompatibilityFuzzer.mjs +192 -0
  15. package/src/core/altium/PcbModelParser.mjs +29 -4
  16. package/src/core/altium/PcbPadStackParser.mjs +171 -2
  17. package/src/core/altium/PcbPickPlacePositionResolver.mjs +8 -1
  18. package/src/core/altium/PcbRegionPrimitiveParser.mjs +71 -2
  19. package/src/core/altium/PcbRouteAnalysisBuilder.mjs +730 -0
  20. package/src/core/altium/PcbStatisticsBuilder.mjs +9 -0
  21. package/src/core/altium/PrjPcbModelParser.mjs +24 -2
  22. package/src/core/altium/ProjectDesignBundleBuilder.mjs +15 -0
  23. package/src/core/altium/ProjectDocumentGraphBuilder.mjs +280 -0
  24. package/src/core/altium/ProjectNetlistExporter.mjs +5 -1
  25. package/src/core/altium/SvgModelCrossLinkValidator.mjs +402 -0
  26. package/src/core/circuit-json/CircuitJsonModelAdapter.mjs +136 -96
  27. package/src/core/circuit-json/CircuitJsonModelAdapterPcbElements.mjs +244 -0
  28. package/src/core/circuit-json/CircuitJsonModelSchema.mjs +1 -1
  29. package/src/parser.mjs +6 -0
  30. package/src/ui/PcbSvgRenderer.mjs +65 -0
@@ -0,0 +1,202 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ //
3
+ // SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ import { PcbSvgRenderer } from '../../ui/PcbSvgRenderer.mjs'
6
+ import { SchematicSvgRenderer } from '../../ui/SchematicSvgRenderer.mjs'
7
+ import { PcbStatisticsBuilder } from './PcbStatisticsBuilder.mjs'
8
+ import { ProjectDesignBundleBuilder } from './ProjectDesignBundleBuilder.mjs'
9
+ import { ProjectDocumentGraphBuilder } from './ProjectDocumentGraphBuilder.mjs'
10
+ import { ProjectNetlistExporter } from './ProjectNetlistExporter.mjs'
11
+
12
+ /**
13
+ * Builds one deterministic CI artifact package from parsed project documents.
14
+ */
15
+ export class CiArtifactBundleBuilder {
16
+ static SCHEMA = 'altium-toolkit.ci.artifact-bundle.a1'
17
+
18
+ static #UNITS = {
19
+ coordinate: 'mil',
20
+ length: 'mil',
21
+ board: 'mil',
22
+ pnp: 'mil',
23
+ angle: 'deg'
24
+ }
25
+
26
+ static #PNP_UNITS = {
27
+ coordinate: 'mil',
28
+ angle: 'deg'
29
+ }
30
+
31
+ /**
32
+ * Builds a deterministic bundle of normalized, rendered, and report outputs.
33
+ * @param {{ projectModel?: object, documentModels?: object[], designBundle?: object, annotationModels?: object[], variantName?: string, renderSchematicSvg?: boolean, renderPcbLayerSvgs?: boolean, schematicSvgOptions?: object }} options Bundle options.
34
+ * @returns {object}
35
+ */
36
+ static build(options = {}) {
37
+ const documentModels = Array.isArray(options.documentModels)
38
+ ? options.documentModels
39
+ : []
40
+ const designBundle =
41
+ options.designBundle ||
42
+ ProjectDesignBundleBuilder.build({
43
+ projectModel: options.projectModel,
44
+ documentModels,
45
+ annotationModels: options.annotationModels || [],
46
+ variantName: options.variantName
47
+ })
48
+ const activeBundle = designBundle.effectiveVariant || designBundle
49
+ const schematicSvgs =
50
+ options.renderSchematicSvg === false
51
+ ? []
52
+ : CiArtifactBundleBuilder.#schematicSvgs(
53
+ documentModels,
54
+ options.schematicSvgOptions || {}
55
+ )
56
+ const pcbLayerSvgs =
57
+ options.renderPcbLayerSvgs === false
58
+ ? []
59
+ : CiArtifactBundleBuilder.#pcbLayerSvgs(documentModels)
60
+ const statistics = CiArtifactBundleBuilder.#statistics(documentModels)
61
+ const diagnostics = CiArtifactBundleBuilder.#diagnostics(
62
+ designBundle,
63
+ documentModels
64
+ )
65
+ const netlistJson =
66
+ ProjectNetlistExporter.buildNetlistJson(activeBundle)
67
+ const documentGraph =
68
+ designBundle.project?.documentGraph ||
69
+ ProjectDocumentGraphBuilder.build(
70
+ options.projectModel?.project || designBundle.project || {}
71
+ )
72
+
73
+ return {
74
+ schema: CiArtifactBundleBuilder.SCHEMA,
75
+ summary: {
76
+ normalizedModelCount: documentModels.length,
77
+ schematicSvgCount: schematicSvgs.length,
78
+ pcbLayerSvgCount: pcbLayerSvgs.reduce(
79
+ (total, entry) => total + entry.layers.length,
80
+ 0
81
+ ),
82
+ netCount: netlistJson.nets.length,
83
+ bomRowCount: (activeBundle.bom || designBundle.bom || [])
84
+ .length,
85
+ pnpCount: (activeBundle.pnp?.entries || []).length,
86
+ diagnosticCount: diagnostics.length
87
+ },
88
+ units: designBundle.units || CiArtifactBundleBuilder.#UNITS,
89
+ designBundle,
90
+ documentGraph,
91
+ normalizedModels: documentModels,
92
+ netlist: {
93
+ json: netlistJson,
94
+ wirelist: ProjectNetlistExporter.buildWirelist(activeBundle)
95
+ },
96
+ bom: {
97
+ rows: activeBundle.bom || designBundle.bom || []
98
+ },
99
+ pnp: CiArtifactBundleBuilder.#pnp(activeBundle, designBundle),
100
+ schematicSvgs,
101
+ pcbLayerSvgs,
102
+ statistics,
103
+ diagnostics
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Resolves a PnP payload with explicit output units.
109
+ * @param {object} activeBundle Effective bundle or variant.
110
+ * @param {object} designBundle Source design bundle.
111
+ * @returns {object}
112
+ */
113
+ static #pnp(activeBundle, designBundle) {
114
+ const pnp = activeBundle.pnp || designBundle.pnp || { entries: [] }
115
+
116
+ return {
117
+ units: pnp.units || CiArtifactBundleBuilder.#PNP_UNITS,
118
+ ...pnp
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Renders schematic SVG entries.
124
+ * @param {object[]} documentModels Parsed document models.
125
+ * @param {object} renderOptions Schematic SVG render options.
126
+ * @returns {object[]}
127
+ */
128
+ static #schematicSvgs(documentModels, renderOptions) {
129
+ return documentModels
130
+ .filter((model) => model?.kind === 'schematic')
131
+ .map((model) => ({
132
+ fileName: model.fileName || '',
133
+ svg: SchematicSvgRenderer.render(model, renderOptions)
134
+ }))
135
+ }
136
+
137
+ /**
138
+ * Renders per-layer PCB SVG entries.
139
+ * @param {object[]} documentModels Parsed document models.
140
+ * @returns {object[]}
141
+ */
142
+ static #pcbLayerSvgs(documentModels) {
143
+ return documentModels
144
+ .filter((model) => model?.kind === 'pcb')
145
+ .map((model) => ({
146
+ fileName: model.fileName || '',
147
+ layers: PcbSvgRenderer.renderLayerSvgs(model)
148
+ }))
149
+ }
150
+
151
+ /**
152
+ * Builds statistics package entries.
153
+ * @param {object[]} documentModels Parsed document models.
154
+ * @returns {{ pcb: object[] }}
155
+ */
156
+ static #statistics(documentModels) {
157
+ return {
158
+ pcb: documentModels
159
+ .filter((model) => model?.kind === 'pcb')
160
+ .map((model) => ({
161
+ fileName: model.fileName || '',
162
+ statistics:
163
+ model.pcb?.statistics ||
164
+ PcbStatisticsBuilder.build(model.pcb || {})
165
+ }))
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Collects diagnostics from the bundle and source documents.
171
+ * @param {object} designBundle Composed design bundle.
172
+ * @param {object[]} documentModels Parsed document models.
173
+ * @returns {object[]}
174
+ */
175
+ static #diagnostics(designBundle, documentModels) {
176
+ return [
177
+ ...CiArtifactBundleBuilder.#sourceDiagnostics(
178
+ 'design-bundle',
179
+ designBundle.diagnostics || []
180
+ ),
181
+ ...documentModels.flatMap((model) =>
182
+ CiArtifactBundleBuilder.#sourceDiagnostics(
183
+ model.fileName || model.kind || 'document',
184
+ model.diagnostics || []
185
+ )
186
+ )
187
+ ]
188
+ }
189
+
190
+ /**
191
+ * Adds source labels to diagnostics without changing their codes.
192
+ * @param {string} source Diagnostic source label.
193
+ * @param {object[]} diagnostics Source diagnostics.
194
+ * @returns {object[]}
195
+ */
196
+ static #sourceDiagnostics(source, diagnostics) {
197
+ return (diagnostics || []).map((diagnostic) => ({
198
+ source,
199
+ ...diagnostic
200
+ }))
201
+ }
202
+ }