altium-toolkit 1.1.40 → 1.2.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 (64) hide show
  1. package/README.md +113 -19
  2. package/docs/api.md +224 -18
  3. package/docs/capabilities.md +65 -0
  4. package/docs/migration/legacy-001.md +309 -0
  5. package/docs/migration/legacy-002.md +309 -0
  6. package/docs/migration/legacy-003.md +309 -0
  7. package/docs/migration/legacy-004.md +309 -0
  8. package/docs/migration/legacy-005.md +111 -0
  9. package/docs/migration.md +20 -0
  10. package/docs/model-format.md +28 -3
  11. package/docs/release-notes-v1.2.0.md +147 -0
  12. package/docs/testing.md +43 -1
  13. package/examples/arduino-uno/PcbThreeSceneRenderer.mjs +1 -1
  14. package/examples/arduino-uno/example.mjs +1 -1
  15. package/examples/cli-utils.mjs +1 -1
  16. package/examples/corpus-smoke.mjs +1 -1
  17. package/examples/inspect-board.mjs +1 -1
  18. package/examples/library-catalog.mjs +1 -1
  19. package/examples/validate-library.mjs +1 -1
  20. package/package.json +20 -5
  21. package/spec/api-baseline-v1.1.41.json +1 -0
  22. package/spec/asset-baseline-v1.1.41.json +1 -0
  23. package/spec/feature-preservation.json +1 -0
  24. package/spec/library-scope.md +14 -4
  25. package/spec/native-source-manifest-v1.1.41.json +1 -0
  26. package/src/capabilities.mjs +4 -0
  27. package/src/convergence/AltiumCircuitJsonProjection.mjs +230 -0
  28. package/src/convergence/AltiumDocumentBuilder.mjs +167 -0
  29. package/src/convergence/AltiumExtensionResolver.mjs +98 -0
  30. package/src/convergence/AltiumProjectDocumentResolver.mjs +324 -0
  31. package/src/convergence/AltiumSchematicCoordinateProjection.mjs +130 -0
  32. package/src/convergence/AltiumWorkerClient.mjs +95 -0
  33. package/src/convergence/Parser.mjs +285 -0
  34. package/src/convergence/ParserInput.mjs +282 -0
  35. package/src/convergence/ProjectLoader.mjs +916 -0
  36. package/src/convergence/SchematicSvgRenderer.mjs +126 -0
  37. package/src/convergence/ToolkitCapabilities.mjs +49 -0
  38. package/src/core/altium/AltiumLayoutParser.mjs +9 -4
  39. package/src/core/altium/PcbLayerStackReadModelBuilder.mjs +1 -0
  40. package/src/core/circuit-json/CircuitJsonSchematicDocumentGraphicBuilder.mjs +785 -0
  41. package/src/core/circuit-json/CircuitJsonSchematicGraphicBuilder.mjs +909 -0
  42. package/src/core/circuit-json/CircuitJsonSchematicImageProjection.mjs +277 -0
  43. package/src/core/circuit-json/CircuitJsonSchematicStrokeStyle.mjs +47 -0
  44. package/src/extensions.mjs +11 -0
  45. package/src/index.mjs +20 -4
  46. package/src/interaction.mjs +7 -0
  47. package/src/legacy-netlist-query.mjs +11 -0
  48. package/src/legacy-parser.mjs +141 -0
  49. package/src/legacy-renderers.mjs +25 -0
  50. package/src/legacy-scene3d.mjs +10 -0
  51. package/src/manufacturing.mjs +4 -0
  52. package/src/parser.mjs +12 -138
  53. package/src/project.mjs +11 -0
  54. package/src/query.mjs +4 -0
  55. package/src/renderers.mjs +4 -22
  56. package/src/scene3d.mjs +5 -8
  57. package/src/simulation.mjs +4 -0
  58. package/src/styles/altium-renderers.css +67 -1
  59. package/src/styles/renderers.css +27 -0
  60. package/src/testing.mjs +8 -0
  61. package/src/ui/PcbInteractionLayerModel.mjs +6 -2
  62. package/src/ui/PcbPadMaskApertureRenderer.mjs +288 -0
  63. package/src/ui/PcbSvgRenderer.mjs +674 -38
  64. package/src/workers/parser.worker.mjs +37 -0
package/src/parser.mjs CHANGED
@@ -1,142 +1,16 @@
1
1
  // SPDX-FileCopyrightText: 2026 André Fiedler
2
- //
3
2
  // SPDX-License-Identifier: GPL-3.0-or-later
4
3
 
5
- export { BinaryReader, BinaryReaderCursor } from './core/BinaryReader.mjs'
6
- export { OleCompoundDocument } from './core/ole/OleCompoundDocument.mjs'
7
- export { OleConstants } from './core/ole/OleConstants.mjs'
8
- export { OleDirectoryEntry } from './core/ole/OleDirectoryEntry.mjs'
9
- export { OleCompoundDocumentWriter } from './core/ole/OleCompoundDocumentWriter.mjs'
10
- export { AltiumLibraryBatchExporter } from './core/altium/AltiumLibraryBatchExporter.mjs'
11
- export { AltiumLibraryRecordBuilder } from './core/altium/AltiumLibraryRecordBuilder.mjs'
12
- export { AltiumPcbLibExporter } from './core/altium/AltiumPcbLibExporter.mjs'
13
- export { AltiumSchLibExporter } from './core/altium/AltiumSchLibExporter.mjs'
14
- export { SelectedPartAltiumExportAdapter } from './core/altium/SelectedPartAltiumExportAdapter.mjs'
15
- export { AltiumUnits } from './core/altium/AltiumUnits.mjs'
16
- export { SourceBundleExporter } from './core/altium/SourceBundleExporter.mjs'
17
- export { SourceComponentBundleNormalizer } from './core/altium/SourceComponentBundleNormalizer.mjs'
18
- export { SourceComponentClient } from './core/altium/SourceComponentClient.mjs'
19
- export { AltiumParser } from './core/altium/AltiumParser.mjs'
20
- export { AltiumSchematicArcAngleNormalizer } from './core/altium/AltiumSchematicArcAngleNormalizer.mjs'
21
- export { AltiumSchematicFreeGraphicStrokeNormalizer } from './core/altium/AltiumSchematicFreeGraphicStrokeNormalizer.mjs'
22
- export { AltiumSchematicHiddenDesignatorResolver } from './core/altium/AltiumSchematicHiddenDesignatorResolver.mjs'
23
- export { AltiumSchematicPackedImageResolver } from './core/altium/AltiumSchematicPackedImageResolver.mjs'
24
- export { AltiumSchematicSheetBoundsNormalizer } from './core/altium/AltiumSchematicSheetBoundsNormalizer.mjs'
25
- export { AltiumLayoutParser } from './core/altium/AltiumLayoutParser.mjs'
26
- export { AsciiRecordParser } from './core/altium/AsciiRecordParser.mjs'
27
- export { HostCapabilityDiagnosticsBuilder } from './core/altium/HostCapabilityDiagnosticsBuilder.mjs'
28
- export { IntLibModelParser } from './core/altium/IntLibModelParser.mjs'
29
- export { IntLibStreamExtractor } from './core/altium/IntLibStreamExtractor.mjs'
30
- export { EmbeddedAssetReportBuilder } from './core/altium/EmbeddedAssetReportBuilder.mjs'
31
- export { EmbeddedFileInventoryBuilder } from './core/altium/EmbeddedFileInventoryBuilder.mjs'
32
- export { FixtureCoverageMatrixBuilder } from './core/altium/FixtureCoverageMatrixBuilder.mjs'
33
- export { GeometryBoundsReportBuilder } from './core/altium/GeometryBoundsReportBuilder.mjs'
34
4
  export {
35
- ParameterCollection,
36
- ParameterValue
37
- } from './core/altium/ParameterCollection.mjs'
38
- export {
39
- AltiumFileError,
40
- AltiumParseError,
41
- AltiumCorruptFileError,
42
- AltiumUnsupportedFeatureError
43
- } from './core/altium/ParserErrors.mjs'
44
- export { CiArtifactBundleBuilder } from './core/altium/CiArtifactBundleBuilder.mjs'
45
- export { ContractGateReportBuilder } from './core/altium/ContractGateReportBuilder.mjs'
46
- export { DraftsmanBoardViewMetadataBuilder } from './core/altium/DraftsmanBoardViewMetadataBuilder.mjs'
47
- export { DraftsmanDigestParser } from './core/altium/DraftsmanDigestParser.mjs'
48
- export { LibraryRenderManifestBuilder } from './core/altium/LibraryRenderManifestBuilder.mjs'
49
- export { LibraryCatalogArtifactBuilder } from './core/altium/LibraryCatalogArtifactBuilder.mjs'
50
- export { LibraryCompatibilityReportBuilder } from './core/altium/LibraryCompatibilityReportBuilder.mjs'
51
- export { LibraryDiffReportBuilder } from './core/altium/LibraryDiffReportBuilder.mjs'
52
- export { LibraryInspectionReportBuilder } from './core/altium/LibraryInspectionReportBuilder.mjs'
53
- export { LibraryQaReportBuilder } from './core/altium/LibraryQaReportBuilder.mjs'
54
- export { LibrarySearchIndex } from './core/altium/LibrarySearchIndex.mjs'
55
- export { NativeStreamInventoryBuilder } from './core/altium/NativeStreamInventoryBuilder.mjs'
56
- export { ParserUtils } from './core/altium/ParserUtils.mjs'
57
- export { ParameterRecordInventoryBuilder } from './core/altium/ParameterRecordInventoryBuilder.mjs'
58
- export { ParserCompatibilityFuzzer } from './core/altium/ParserCompatibilityFuzzer.mjs'
59
- export { ParserDiagnosticNormalizer } from './core/altium/ParserDiagnosticNormalizer.mjs'
60
- export { ParserFieldCoverageReportBuilder } from './core/altium/ParserFieldCoverageReportBuilder.mjs'
61
- export { ParserValueVerificationReportBuilder } from './core/altium/ParserValueVerificationReportBuilder.mjs'
62
- export { NormalizedModelSchema } from './core/altium/NormalizedModelSchema.mjs'
63
- export { ProjectDesignBundleBuilder } from './core/altium/ProjectDesignBundleBuilder.mjs'
64
- export { ProjectDocumentGraphBuilder } from './core/altium/ProjectDocumentGraphBuilder.mjs'
65
- export { ProjectHierarchyReportBuilder } from './core/altium/ProjectHierarchyReportBuilder.mjs'
66
- export { ProjectAnnotationParser } from './core/altium/ProjectAnnotationParser.mjs'
67
- export { ProjectNetlistExporter } from './core/altium/ProjectNetlistExporter.mjs'
68
- export { ProjectVariantViewBuilder } from './core/altium/ProjectVariantViewBuilder.mjs'
69
- export { CircuitJsonModelSchema } from './core/circuit-json/CircuitJsonModelSchema.mjs'
70
- export { CircuitJsonModelAdapter } from './core/circuit-json/CircuitJsonModelAdapter.mjs'
71
- export { PcbBinaryPrimitiveParser } from './core/altium/PcbBinaryPrimitiveParser.mjs'
72
- export { PcbBomProfileBuilder } from './core/altium/PcbBomProfileBuilder.mjs'
73
- export { PcbBoardRegionSemanticsParser } from './core/altium/PcbBoardRegionSemanticsParser.mjs'
74
- export { PcbComponentKindPolicy } from './core/altium/PcbComponentKindPolicy.mjs'
75
- export { PcbComponentPrimitiveIndexer } from './core/altium/PcbComponentPrimitiveIndexer.mjs'
76
- export { PcbCustomPadShapeParser } from './core/altium/PcbCustomPadShapeParser.mjs'
77
- export { PcbDimensionParser } from './core/altium/PcbDimensionParser.mjs'
78
- export { PcbDimensionReportBuilder } from './core/altium/PcbDimensionReportBuilder.mjs'
79
- export { PcbEmbeddedFontExtractor } from './core/altium/PcbEmbeddedFontExtractor.mjs'
80
- export { PcbEmbeddedModelExtractor } from './core/altium/PcbEmbeddedModelExtractor.mjs'
81
- export { PcbExtendedPrimitiveInformationParser } from './core/altium/PcbExtendedPrimitiveInformationParser.mjs'
82
- export { PcbFabricationReadinessReportBuilder } from './core/altium/PcbFabricationReadinessReportBuilder.mjs'
83
- export { PcbFontMetricsParser } from './core/altium/PcbFontMetricsParser.mjs'
84
- export { PcbLayerIdCodec } from './core/altium/PcbLayerIdCodec.mjs'
85
- export { PcbLayerGroups } from './core/altium/PcbLayerGroups.mjs'
86
- export { PcbLayerStackFidelityReportBuilder } from './core/altium/PcbLayerStackFidelityReportBuilder.mjs'
87
- export { PcbLayerStackInterchangeParser } from './core/altium/PcbLayerStackInterchangeParser.mjs'
88
- export { PcbLayerStackQueryHelper } from './core/altium/PcbLayerStackQueryHelper.mjs'
89
- export { PcbLibModelParser } from './core/altium/PcbLibModelParser.mjs'
90
- export { PcbLibParityReportBuilder } from './core/altium/PcbLibParityReportBuilder.mjs'
91
- export { PcbLibStreamExtractor } from './core/altium/PcbLibStreamExtractor.mjs'
92
- export { PcbMechanicalLayerPairParser } from './core/altium/PcbMechanicalLayerPairParser.mjs'
93
- export { PcbModelParser } from './core/altium/PcbModelParser.mjs'
94
- export { PcbOutlineRasterizer } from './core/altium/PcbOutlineRasterizer.mjs'
95
- export { PcbOutlineRecovery } from './core/altium/PcbOutlineRecovery.mjs'
96
- export { PcbOwnershipGraphBuilder } from './core/altium/PcbOwnershipGraphBuilder.mjs'
97
- export { PcbPadStackParser } from './core/altium/PcbPadStackParser.mjs'
98
- export { PcbPlacedFootprintManifestBuilder } from './core/altium/PcbPlacedFootprintManifestBuilder.mjs'
99
- export { PcbPickPlacePositionResolver } from './core/altium/PcbPickPlacePositionResolver.mjs'
100
- export { PcbPolygonRecordParser } from './core/altium/PcbPolygonRecordParser.mjs'
101
- export { PcbPrimitiveFieldOffsetReportBuilder } from './core/altium/PcbPrimitiveFieldOffsetReportBuilder.mjs'
102
- export { PcbRawRecordRegistry } from './core/altium/PcbRawRecordRegistry.mjs'
103
- export { PcbReviewMetadataBuilder } from './core/altium/PcbReviewMetadataBuilder.mjs'
104
- export { PcbReviewPolygonRealizationBuilder } from './core/altium/PcbReviewPolygonRealizationBuilder.mjs'
105
- export { PcbClassReportBuilder } from './core/altium/PcbClassReportBuilder.mjs'
106
- export { PcbInspectionReportBuilder } from './core/altium/PcbInspectionReportBuilder.mjs'
107
- export { PcbNetMembershipReportBuilder } from './core/altium/PcbNetMembershipReportBuilder.mjs'
108
- export { PcbRouteAnalysisBuilder } from './core/altium/PcbRouteAnalysisBuilder.mjs'
109
- export { PcbRuleImpactReportBuilder } from './core/altium/PcbRuleImpactReportBuilder.mjs'
110
- export { PcbRuleParser } from './core/altium/PcbRuleParser.mjs'
111
- export { PcbSidecarRecordParser } from './core/altium/PcbSidecarRecordParser.mjs'
112
- export { PcbSpecialStringResolver } from './core/altium/PcbSpecialStringResolver.mjs'
113
- export { PcbStatisticsBuilder } from './core/altium/PcbStatisticsBuilder.mjs'
114
- export { PcbStreamExtractor } from './core/altium/PcbStreamExtractor.mjs'
115
- export { PcbUnionParser } from './core/altium/PcbUnionParser.mjs'
116
- export { PcbViaStackParser } from './core/altium/PcbViaStackParser.mjs'
117
- export { PcbViaStructureParser } from './core/altium/PcbViaStructureParser.mjs'
118
- export { PrintableTextDecoder } from './core/altium/PrintableTextDecoder.mjs'
119
- export { PrjPcbModelParser } from './core/altium/PrjPcbModelParser.mjs'
120
- export { PrjScrModelParser } from './core/altium/PrjScrModelParser.mjs'
121
- export { ProjectBomPnpReconciliationBuilder } from './core/altium/ProjectBomPnpReconciliationBuilder.mjs'
122
- export { RawDataPreservationReportBuilder } from './core/altium/RawDataPreservationReportBuilder.mjs'
123
- export { SchLibModelParser } from './core/altium/SchLibModelParser.mjs'
124
- export { SchLibStreamExtractor } from './core/altium/SchLibStreamExtractor.mjs'
125
- export { SchematicMultipartOwnerMatcher } from './core/altium/SchematicMultipartOwnerMatcher.mjs'
126
- export { SchematicCrossSheetConnectorParser } from './core/altium/SchematicCrossSheetConnectorParser.mjs'
127
- export { SchematicCodeSymbolParser } from './core/altium/SchematicCodeSymbolParser.mjs'
128
- export { SchematicConnectivityQaBuilder } from './core/altium/SchematicConnectivityQaBuilder.mjs'
129
- export { SchematicHarnessParser } from './core/altium/SchematicHarnessParser.mjs'
130
- export { SchematicImplementationParser } from './core/altium/SchematicImplementationParser.mjs'
131
- export { SchematicImageDiagnosticsBuilder } from './core/altium/SchematicImageDiagnosticsBuilder.mjs'
132
- export { SchematicOwnershipGraphParser } from './core/altium/SchematicOwnershipGraphParser.mjs'
133
- export { SchematicProjectParameterResolver } from './core/altium/SchematicProjectParameterResolver.mjs'
134
- export { SchematicQaReportBuilder } from './core/altium/SchematicQaReportBuilder.mjs'
135
- export { SchematicRecordStreamParser } from './core/altium/SchematicRecordStreamParser.mjs'
136
- export { SchematicRecordTypeRegistry } from './core/altium/SchematicRecordTypeRegistry.mjs'
137
- export { SchematicRepeatedChannelParser } from './core/altium/SchematicRepeatedChannelParser.mjs'
138
- export { SchematicTemplateParser } from './core/altium/SchematicTemplateParser.mjs'
139
- export { UnsupportedFeatureReportBuilder } from './core/altium/UnsupportedFeatureReportBuilder.mjs'
140
- export { SchematicTextRunParser } from './core/altium/SchematicTextRunParser.mjs'
141
- export { SchematicStandaloneCalloutNormalizer } from './core/altium/SchematicStandaloneCalloutNormalizer.mjs'
142
- export { SvgModelCrossLinkValidator } from './core/altium/SvgModelCrossLinkValidator.mjs'
5
+ CircuitJsonDocumentContext,
6
+ DocumentResult,
7
+ ParserWorkerClient,
8
+ ToolkitAsset,
9
+ ToolkitDiagnostic,
10
+ ToolkitError,
11
+ ToolkitProgress,
12
+ TOOLKIT_WORKER_PROTOCOL,
13
+ ToolkitWorkerProtocol
14
+ } from 'circuitjson-toolkit/parser'
15
+
16
+ export { Parser } from './convergence/Parser.mjs'
@@ -0,0 +1,11 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ // SPDX-License-Identifier: GPL-3.0-or-later
3
+
4
+ export {
5
+ ArchiveEntryPath,
6
+ ArchiveLimits,
7
+ ProjectResult,
8
+ ZipArchiveInspector
9
+ } from 'circuitjson-toolkit/project'
10
+
11
+ export { ProjectLoader } from './convergence/ProjectLoader.mjs'
package/src/query.mjs ADDED
@@ -0,0 +1,4 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ // SPDX-License-Identifier: GPL-3.0-or-later
3
+
4
+ export { QueryService } from 'circuitjson-toolkit/query'
package/src/renderers.mjs CHANGED
@@ -1,26 +1,8 @@
1
1
  // SPDX-FileCopyrightText: 2026 André Fiedler
2
- //
3
2
  // SPDX-License-Identifier: GPL-3.0-or-later
4
3
 
5
- export { BomTableRenderer } from './ui/BomTableRenderer.mjs'
6
- export { AltiumPcbBottomViewMirror } from './ui/AltiumPcbBottomViewMirror.mjs'
7
- export { PcbArcUtils } from './ui/PcbArcUtils.mjs'
8
- export { PcbEdgeFacingGlyphNormalizer } from './ui/PcbEdgeFacingGlyphNormalizer.mjs'
9
- export { PcbFootprintPrimitiveSelector } from './ui/PcbFootprintPrimitiveSelector.mjs'
10
- export { PcbFootprintPadAxisNormalizer } from './ui/PcbFootprintPadAxisNormalizer.mjs'
11
- export { PcbInteractionIndex } from './ui/PcbInteractionIndex.mjs'
12
- export { PcbInteractionItemRegistry } from './ui/PcbInteractionItemRegistry.mjs'
13
- export { PcbInteractionLayerModel } from './ui/PcbInteractionLayerModel.mjs'
14
- export { PcbLayerGroups } from './core/altium/PcbLayerGroups.mjs'
15
4
  export {
16
- PcbSideResolvedRenderModel,
17
- isCopperPrimitive,
18
- preparePcbSideResolvedRenderModel
19
- } from './ui/PcbSideResolvedRenderModel.mjs'
20
- export { PcbSvgRenderer } from './ui/PcbSvgRenderer.mjs'
21
- export { SchematicColorResolver } from './ui/SchematicColorResolver.mjs'
22
- export { SchematicContentLayout } from './ui/SchematicContentLayout.mjs'
23
- export { SchematicOwnerPinLabelLayout } from './ui/SchematicOwnerPinLabelLayout.mjs'
24
- export { SchematicSvgRenderer } from './ui/SchematicSvgRenderer.mjs'
25
- export { SchematicSvgUtils } from './ui/SchematicSvgUtils.mjs'
26
- export { SchematicTypography } from './ui/SchematicTypography.mjs'
5
+ BomTableRenderer,
6
+ PcbSvgRenderer,
7
+ SchematicSvgRenderer
8
+ } from 'circuitjson-toolkit/renderers'
package/src/scene3d.mjs CHANGED
@@ -1,11 +1,8 @@
1
1
  // SPDX-FileCopyrightText: 2026 André Fiedler
2
- //
3
2
  // SPDX-License-Identifier: GPL-3.0-or-later
4
3
 
5
- export { PcbScene3dBuilder } from './ui/PcbScene3dBuilder.mjs'
6
- export { AltiumScene3dAuthoredBodyAnchorAdapter } from './ui/AltiumScene3dAuthoredBodyAnchorAdapter.mjs'
7
- export { PcbScene3dModelRegistry } from './ui/PcbScene3dModelRegistry.mjs'
8
- export { PcbScene3dPackages } from './ui/PcbScene3dPackages.mjs'
9
- export { PcbScene3dScenePreparator } from './ui/PcbScene3dScenePreparator.mjs'
10
- export { PcbScene3dSummaryRenderer } from './ui/PcbScene3dSummaryRenderer.mjs'
11
- export { PcbScene3dTextBoxLayoutResolver } from './ui/PcbScene3dTextBoxLayoutResolver.mjs'
4
+ export {
5
+ PcbScene3dBuilder,
6
+ PcbScene3dPreparator,
7
+ SceneAssetResolver
8
+ } from 'circuitjson-toolkit/scene3d'
@@ -0,0 +1,4 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ // SPDX-License-Identifier: GPL-3.0-or-later
3
+
4
+ export { SimulationService } from 'circuitjson-toolkit/simulation'
@@ -142,6 +142,16 @@
142
142
  --pcb-via-hole-fill: #0f746c;
143
143
  --pcb-footprint-fill: rgba(247, 230, 117, 0.14);
144
144
  --pcb-footprint-track-color: rgba(237, 172, 36, 1);
145
+ --pcb-detail-paste-fill: rgba(146, 146, 146, 0.94);
146
+ --pcb-detail-paste-stroke: rgba(154, 154, 154, 0.96);
147
+ --pcb-detail-mask-fill: rgba(137, 34, 132, 0.4);
148
+ --pcb-detail-mask-stroke: rgba(137, 34, 132, 0.82);
149
+ --pcb-detail-mechanical-fill: rgba(148, 160, 166, 0.32);
150
+ --pcb-detail-mechanical-stroke: rgba(96, 110, 118, 0.86);
151
+ --pcb-detail-drill-fill: rgba(79, 87, 93, 0.46);
152
+ --pcb-detail-drill-stroke: rgba(42, 50, 56, 0.88);
153
+ --pcb-detail-fill: rgba(150, 160, 166, 0.38);
154
+ --pcb-detail-stroke: rgba(80, 92, 100, 0.82);
145
155
  --pcb-dimension-color: rgba(39, 88, 111, 0.78);
146
156
  --pcb-dimension-text: #22343b;
147
157
  --pcb-text-knockout-fill: rgba(248, 246, 239, 0.96);
@@ -200,7 +210,9 @@
200
210
  .pcb-track,
201
211
  .pcb-arc,
202
212
  .pcb-footprint-track,
203
- .pcb-footprint-arc {
213
+ .pcb-footprint-arc,
214
+ .pcb-detail-track,
215
+ .pcb-detail-arc {
204
216
  fill: none;
205
217
  stroke-linecap: round;
206
218
  stroke-linejoin: round;
@@ -271,6 +283,60 @@
271
283
  stroke: var(--pcb-footprint-track-color);
272
284
  }
273
285
 
286
+ .pcb-detail-fill,
287
+ .pcb-detail-region {
288
+ fill: var(--pcb-detail-fill);
289
+ }
290
+
291
+ .pcb-detail-track,
292
+ .pcb-detail-arc {
293
+ stroke: var(--pcb-detail-stroke);
294
+ }
295
+
296
+ .pcb-detail-fill--paste,
297
+ .pcb-detail-region--paste {
298
+ fill: var(--pcb-detail-paste-fill);
299
+ }
300
+
301
+ .pcb-detail-track--paste,
302
+ .pcb-detail-arc--paste {
303
+ stroke: var(--pcb-detail-paste-stroke);
304
+ }
305
+
306
+ .pcb-detail-fill--mask,
307
+ .pcb-detail-region--mask,
308
+ .pcb-detail-fill--solder-mask,
309
+ .pcb-detail-region--solder-mask {
310
+ fill: var(--pcb-detail-mask-fill);
311
+ }
312
+
313
+ .pcb-detail-track--mask,
314
+ .pcb-detail-arc--mask,
315
+ .pcb-detail-track--solder-mask,
316
+ .pcb-detail-arc--solder-mask {
317
+ stroke: var(--pcb-detail-mask-stroke);
318
+ }
319
+
320
+ .pcb-detail-fill--mechanical,
321
+ .pcb-detail-region--mechanical {
322
+ fill: var(--pcb-detail-mechanical-fill);
323
+ }
324
+
325
+ .pcb-detail-track--mechanical,
326
+ .pcb-detail-arc--mechanical {
327
+ stroke: var(--pcb-detail-mechanical-stroke);
328
+ }
329
+
330
+ .pcb-detail-fill--drill,
331
+ .pcb-detail-region--drill {
332
+ fill: var(--pcb-detail-drill-fill);
333
+ }
334
+
335
+ .pcb-detail-track--drill,
336
+ .pcb-detail-arc--drill {
337
+ stroke: var(--pcb-detail-drill-stroke);
338
+ }
339
+
274
340
  .pcb-dimensions {
275
341
  pointer-events: none;
276
342
  }
@@ -0,0 +1,27 @@
1
+ /* SPDX-FileCopyrightText: 2026 André Fiedler */
2
+ /* SPDX-License-Identifier: GPL-3.0-or-later */
3
+
4
+ .pcb-svg,
5
+ .schematic-svg {
6
+ display: block;
7
+ width: 100%;
8
+ max-width: 100%;
9
+ height: auto;
10
+ }
11
+
12
+ .pcb-svg text,
13
+ .schematic-svg text {
14
+ font-family: ui-sans-serif, system-ui, sans-serif;
15
+ }
16
+
17
+ .bom-table {
18
+ width: 100%;
19
+ border-collapse: collapse;
20
+ }
21
+
22
+ .bom-table th,
23
+ .bom-table td {
24
+ padding: 0.35rem 0.5rem;
25
+ border: 1px solid currentColor;
26
+ text-align: left;
27
+ }
@@ -0,0 +1,8 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ // SPDX-License-Identifier: GPL-3.0-or-later
3
+
4
+ export {
5
+ ToolkitContractFixtures,
6
+ ToolkitLoopbackWorker,
7
+ runToolkitContract
8
+ } from 'circuitjson-toolkit/testing'
@@ -56,13 +56,17 @@ export class PcbInteractionLayerModel {
56
56
  const key = String(layer?.name || layer?.layer || '').trim()
57
57
  if (!key || seen.has(key)) continue
58
58
  seen.add(key)
59
- layers.push({
59
+ const row = {
60
60
  key,
61
61
  label: key,
62
62
  layerId: Number.isFinite(Number(layer?.layerId))
63
63
  ? Number(layer.layerId)
64
64
  : null
65
- })
65
+ }
66
+ if (Number.isFinite(Number(layer?.legacyLayerId))) {
67
+ row.legacyLayerId = Number(layer.legacyLayerId)
68
+ }
69
+ layers.push(row)
66
70
  }
67
71
 
68
72
  return layers
@@ -0,0 +1,288 @@
1
+ // SPDX-FileCopyrightText: 2026 André Fiedler
2
+ //
3
+ // SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ import { SchematicSvgUtils } from './SchematicSvgUtils.mjs'
6
+
7
+ /**
8
+ * Renders pad-derived paste and solder-mask apertures as independent SVG layers.
9
+ */
10
+ export class PcbPadMaskApertureRenderer {
11
+ static #PAD_SHAPE_RECTANGULAR = 2
12
+ static #SPECS = [
13
+ {
14
+ flag: 'hasTopPasteMaskOpening',
15
+ kind: 'paste',
16
+ primitiveKind: 'pad-paste',
17
+ side: 'top',
18
+ layerId: 35,
19
+ layerName: 'Top Paste',
20
+ expansionKey: 'effectivePasteMaskExpansion'
21
+ },
22
+ {
23
+ flag: 'hasBottomPasteMaskOpening',
24
+ kind: 'paste',
25
+ primitiveKind: 'pad-paste',
26
+ side: 'bottom',
27
+ layerId: 36,
28
+ layerName: 'Bottom Paste',
29
+ expansionKey: 'effectivePasteMaskExpansion'
30
+ },
31
+ {
32
+ flag: 'hasTopSolderMaskOpening',
33
+ kind: 'solder-mask',
34
+ primitiveKind: 'pad-solder-mask',
35
+ side: 'top',
36
+ layerId: 37,
37
+ layerName: 'Top Solder',
38
+ expansionKey: 'effectiveSolderMaskExpansion'
39
+ },
40
+ {
41
+ flag: 'hasBottomSolderMaskOpening',
42
+ kind: 'solder-mask',
43
+ primitiveKind: 'pad-solder-mask',
44
+ side: 'bottom',
45
+ layerId: 38,
46
+ layerName: 'Bottom Solder',
47
+ expansionKey: 'effectiveSolderMaskExpansion'
48
+ }
49
+ ]
50
+
51
+ /**
52
+ * Builds one SVG group containing all derived pad mask apertures.
53
+ * @param {object[]} pads Normalized pad records.
54
+ * @param {{ attributes?: (aperture: object) => string, padIndex?: (pad: object, index: number) => number }} [options]
55
+ * @returns {string}
56
+ */
57
+ static render(pads, options = {}) {
58
+ const apertures = []
59
+
60
+ for (const [fallbackIndex, pad] of (pads || []).entries()) {
61
+ const padIndex = PcbPadMaskApertureRenderer.#padIndex(
62
+ pad,
63
+ fallbackIndex,
64
+ options
65
+ )
66
+ for (const [specIndex, spec] of this.#SPECS.entries()) {
67
+ if (!pad?.[spec.flag]) continue
68
+ const aperture = this.#apertureForPad(
69
+ pad,
70
+ padIndex,
71
+ specIndex,
72
+ spec
73
+ )
74
+ if (!aperture) continue
75
+ apertures.push(
76
+ this.#renderAperture(aperture, options.attributes)
77
+ )
78
+ }
79
+ }
80
+
81
+ if (!apertures.length) return ''
82
+
83
+ return '<g class="pcb-pad-mask-layers">' + apertures.join('') + '</g>'
84
+ }
85
+
86
+ /**
87
+ * Resolves one stable pad index.
88
+ * @param {object} pad Pad record.
89
+ * @param {number} fallbackIndex Array index.
90
+ * @param {{ padIndex?: (pad: object, index: number) => number }} options
91
+ * @returns {number}
92
+ */
93
+ static #padIndex(pad, fallbackIndex, options) {
94
+ const resolved = options.padIndex?.(pad, fallbackIndex)
95
+ return Number.isInteger(resolved) ? resolved : fallbackIndex
96
+ }
97
+
98
+ /**
99
+ * Builds one normalized aperture descriptor for a pad/spec pair.
100
+ * @param {object} pad Pad record.
101
+ * @param {number} padIndex Stable pad index.
102
+ * @param {number} specIndex Spec index.
103
+ * @param {object} spec Aperture layer spec.
104
+ * @returns {object | null}
105
+ */
106
+ static #apertureForPad(pad, padIndex, specIndex, spec) {
107
+ const size = this.#sizeForPad(pad, spec)
108
+ if (size.width <= 0 || size.height <= 0) return null
109
+
110
+ return {
111
+ pad,
112
+ padIndex,
113
+ index: padIndex * this.#SPECS.length + specIndex,
114
+ spec,
115
+ size,
116
+ primitive: {
117
+ ...pad,
118
+ layerId: spec.layerId,
119
+ layerCode: spec.layerId,
120
+ legacyLayerId: spec.layerId,
121
+ layerName: spec.layerName
122
+ }
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Resolves aperture dimensions after the parsed mask expansion.
128
+ * @param {object} pad Pad record.
129
+ * @param {object} spec Aperture layer spec.
130
+ * @returns {{ width: number, height: number }}
131
+ */
132
+ static #sizeForPad(pad, spec) {
133
+ const side = spec.side === 'bottom' ? 'Bottom' : 'Top'
134
+ const width = Number(pad?.['size' + side + 'X'] || pad?.sizeMidX || 0)
135
+ const height = Number(pad?.['size' + side + 'Y'] || pad?.sizeMidY || 0)
136
+ const expansion = Number(pad?.[spec.expansionKey] || 0)
137
+
138
+ return {
139
+ width: Math.max(width + expansion * 2, 0),
140
+ height: Math.max(height + expansion * 2, 0)
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Renders one aperture shape.
146
+ * @param {object} aperture Aperture descriptor.
147
+ * @param {(aperture: object) => string | undefined} attributes Attribute callback.
148
+ * @returns {string}
149
+ */
150
+ static #renderAperture(aperture, attributes) {
151
+ const className =
152
+ 'pcb-detail-fill pcb-detail-fill--' +
153
+ (aperture.spec.kind === 'solder-mask' ? 'mask' : 'paste') +
154
+ ' pcb-pad-mask-aperture pcb-pad-mask-aperture--' +
155
+ aperture.spec.kind
156
+ const renderedAttributes = attributes?.(aperture) || ''
157
+
158
+ return PcbPadMaskApertureRenderer.#isRoundAperture(aperture)
159
+ ? PcbPadMaskApertureRenderer.#renderCircle(
160
+ aperture,
161
+ className,
162
+ renderedAttributes
163
+ )
164
+ : PcbPadMaskApertureRenderer.#renderRect(
165
+ aperture,
166
+ className,
167
+ renderedAttributes
168
+ )
169
+ }
170
+
171
+ /**
172
+ * Renders one circular aperture.
173
+ * @param {object} aperture Aperture descriptor.
174
+ * @param {string} className SVG class list.
175
+ * @param {string} attributes SVG attributes.
176
+ * @returns {string}
177
+ */
178
+ static #renderCircle(aperture, className, attributes) {
179
+ const { pad, size } = aperture
180
+ const radius = Math.max(size.width, size.height) / 2
181
+
182
+ return (
183
+ '<circle class="' +
184
+ className +
185
+ '" cx="' +
186
+ SchematicSvgUtils.formatNumber(Number(pad.x || 0)) +
187
+ '" cy="' +
188
+ SchematicSvgUtils.formatNumber(Number(pad.y || 0)) +
189
+ '" r="' +
190
+ SchematicSvgUtils.formatNumber(radius) +
191
+ '"' +
192
+ attributes +
193
+ ' />'
194
+ )
195
+ }
196
+
197
+ /**
198
+ * Renders one rectangular aperture.
199
+ * @param {object} aperture Aperture descriptor.
200
+ * @param {string} className SVG class list.
201
+ * @param {string} attributes SVG attributes.
202
+ * @returns {string}
203
+ */
204
+ static #renderRect(aperture, className, attributes) {
205
+ const { pad, size } = aperture
206
+ const x = Number(pad.x || 0) - size.width / 2
207
+ const y = Number(pad.y || 0) - size.height / 2
208
+
209
+ return (
210
+ '<rect class="' +
211
+ className +
212
+ '" x="' +
213
+ SchematicSvgUtils.formatNumber(x) +
214
+ '" y="' +
215
+ SchematicSvgUtils.formatNumber(y) +
216
+ '" width="' +
217
+ SchematicSvgUtils.formatNumber(size.width) +
218
+ '" height="' +
219
+ SchematicSvgUtils.formatNumber(size.height) +
220
+ '" rx="' +
221
+ SchematicSvgUtils.formatNumber(
222
+ PcbPadMaskApertureRenderer.#cornerRadius(aperture)
223
+ ) +
224
+ '" transform="rotate(' +
225
+ SchematicSvgUtils.formatNumber(Number(pad.rotation || 0)) +
226
+ ' ' +
227
+ SchematicSvgUtils.formatNumber(Number(pad.x || 0)) +
228
+ ' ' +
229
+ SchematicSvgUtils.formatNumber(Number(pad.y || 0)) +
230
+ ')"' +
231
+ attributes +
232
+ ' />'
233
+ )
234
+ }
235
+
236
+ /**
237
+ * Returns true when one aperture should be rendered as a circle.
238
+ * @param {object} aperture Aperture descriptor.
239
+ * @returns {boolean}
240
+ */
241
+ static #isRoundAperture(aperture) {
242
+ const shape = PcbPadMaskApertureRenderer.#shapeForAperture(aperture)
243
+ return (
244
+ shape !== PcbPadMaskApertureRenderer.#PAD_SHAPE_RECTANGULAR &&
245
+ Math.abs(aperture.size.width - aperture.size.height) < 0.001
246
+ )
247
+ }
248
+
249
+ /**
250
+ * Resolves the side-specific pad shape code.
251
+ * @param {object} aperture Aperture descriptor.
252
+ * @returns {number}
253
+ */
254
+ static #shapeForAperture(aperture) {
255
+ const side = aperture.spec.side === 'bottom' ? 'Bottom' : 'Top'
256
+ if (
257
+ side === 'Top' &&
258
+ aperture.pad?.hasRoundedRect &&
259
+ Number.isInteger(aperture.pad.roundedRectShapeTop)
260
+ ) {
261
+ return Number(aperture.pad.roundedRectShapeTop)
262
+ }
263
+ return Number(aperture.pad?.['shape' + side] || 0)
264
+ }
265
+
266
+ /**
267
+ * Resolves rectangle corner radius for rounded pad shapes.
268
+ * @param {object} aperture Aperture descriptor.
269
+ * @returns {number}
270
+ */
271
+ static #cornerRadius(aperture) {
272
+ if (
273
+ aperture.spec.side === 'top' &&
274
+ aperture.pad?.hasRoundedRect &&
275
+ Number.isFinite(aperture.pad.cornerRadiusTop)
276
+ ) {
277
+ return (
278
+ Math.min(aperture.size.width, aperture.size.height) *
279
+ (Number(aperture.pad.cornerRadiusTop) / 100)
280
+ )
281
+ }
282
+ if (PcbPadMaskApertureRenderer.#shapeForAperture(aperture) === 1) {
283
+ return Math.min(aperture.size.width, aperture.size.height) / 2
284
+ }
285
+
286
+ return 0
287
+ }
288
+ }