circuitjson-toolkit 1.0.16 → 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/AGENTS.md +15 -0
- package/NOTICE.md +31 -0
- package/README.md +267 -107
- package/docs/api.md +501 -96
- package/docs/capabilities.md +70 -0
- package/docs/migration/behaviors.md +45 -0
- package/docs/migration/parser.md +60 -0
- package/docs/migration/renderers.md +515 -0
- package/docs/migration/root.md +740 -0
- package/docs/migration.md +120 -0
- package/docs/model-format.md +175 -57
- package/docs/provenance.md +206 -0
- package/docs/release-notes-v1.1.0.md +154 -0
- package/docs/testing.md +117 -7
- package/package.json +31 -5
- package/spec/api-baseline-v1.0.17.json +1 -0
- package/spec/baseline-provenance-v1.0.17.json +7 -0
- package/spec/circuitjson-schema-snapshot.json +321 -0
- package/spec/circuitjson-schema-source.json +28 -0
- package/spec/feature-preservation.json +1 -0
- package/spec/library-scope.md +27 -20
- package/src/capabilities.mjs +1 -0
- package/src/core/ArchiveEntryPath.mjs +93 -0
- package/src/core/ArchiveLimits.mjs +31 -0
- package/src/core/ArchiveLimitsValidator.mjs +107 -0
- package/src/core/AsyncInputOwnership.mjs +56 -0
- package/src/core/AttachedValueLimits.mjs +67 -0
- package/src/core/CircuitJsonDiagnosticIndexer.mjs +184 -0
- package/src/core/CircuitJsonDocument.mjs +19 -61
- package/src/core/CircuitJsonElementTypes.mjs +10 -0
- package/src/core/CircuitJsonElementValidator.mjs +98 -847
- package/src/core/CircuitJsonIndexer.mjs +274 -194
- package/src/core/CircuitJsonManufacturingBuilder.mjs +167 -164
- package/src/core/CircuitJsonParser.mjs +75 -13
- package/src/core/CircuitJsonPcbClearanceDiagnostics.mjs +12 -6
- package/src/core/CircuitJsonPcbHolePrimitiveModel.mjs +108 -10
- package/src/core/CircuitJsonPcbPadPrimitiveModel.mjs +1 -1
- package/src/core/CircuitJsonPcbPrimitiveArtwork.mjs +44 -38
- package/src/core/CircuitJsonPcbPrimitiveBuilder.mjs +146 -28
- package/src/core/CircuitJsonPcbPrimitiveFields.mjs +70 -4
- package/src/core/CircuitJsonPcbPrimitiveIndex.mjs +18 -2
- package/src/core/CircuitJsonPcbPrimitiveOverlays.mjs +26 -9
- package/src/core/CircuitJsonPcbZonePrimitiveBuilder.mjs +7 -6
- package/src/core/CircuitJsonSerializedInputAudit.mjs +87 -0
- package/src/core/CircuitJsonSourceMetadata.mjs +5 -1
- package/src/core/CircuitJsonSupportMatrixBuilder.mjs +3 -1
- package/src/core/CircuitJsonToolkitElementSchema.mjs +218 -0
- package/src/core/CircuitJsonUnitParsers.mjs +101 -0
- package/src/core/CircuitJsonUnits.mjs +13 -87
- package/src/core/CircuitJsonUpstreamSchema.mjs +9 -0
- package/src/core/CircuitJsonUpstreamValidator.mjs +418 -0
- package/src/core/CircuitJsonValidationUnits.mjs +6 -0
- package/src/core/ManufacturingService.mjs +323 -0
- package/src/core/Parser.mjs +343 -0
- package/src/core/ParserOptions.mjs +333 -0
- package/src/core/PcbBoundsSelectionModel.mjs +55 -19
- package/src/core/PcbDiagnosticFocusModel.mjs +42 -11
- package/src/core/PcbInteractionIndex.mjs +368 -0
- package/src/core/PcbInteractionPrimitiveModel.mjs +393 -62
- package/src/core/ProjectAsyncInputOwner.mjs +70 -0
- package/src/core/ProjectLoader.mjs +975 -0
- package/src/core/SimulationService.mjs +790 -0
- package/src/core/ToolkitCapabilities.mjs +130 -0
- package/src/core/ZipArchiveInspector.mjs +649 -0
- package/src/core/context/BinaryDataSnapshot.mjs +217 -0
- package/src/core/context/CircuitJsonContextIndexes.mjs +96 -0
- package/src/core/context/CircuitJsonDerivedCache.mjs +114 -0
- package/src/core/context/CircuitJsonDocumentContext.mjs +353 -0
- package/src/core/context/CircuitJsonLegacyModel.mjs +147 -0
- package/src/core/context/CircuitJsonLegacyNormalizer.mjs +847 -0
- package/src/core/context/CircuitJsonMetadataBoundary.mjs +76 -0
- package/src/core/context/CircuitJsonModelFreezeTraversal.mjs +179 -0
- package/src/core/context/CircuitJsonReadOnlyDocument.mjs +920 -0
- package/src/core/context/CircuitJsonSchematicTableNormalizer.mjs +314 -0
- package/src/core/context/CircuitJsonValidationAuthority.mjs +39 -0
- package/src/core/context/CircuitJsonValidationProof.mjs +217 -0
- package/src/core/context/PcbPrimitivePreparation.mjs +198 -0
- package/src/core/context/PcbSpatialIndex.mjs +701 -0
- package/src/core/context/ProtectedExtensionBinaryBoundary.mjs +128 -0
- package/src/core/context/StructuredDataSnapshot.mjs +683 -0
- package/src/core/contracts/DocumentResult.mjs +198 -0
- package/src/core/contracts/ProjectResult.mjs +96 -0
- package/src/core/contracts/RuntimeProxyBoundary.mjs +48 -0
- package/src/core/contracts/ToolkitAsset.mjs +493 -0
- package/src/core/contracts/ToolkitDiagnostic.mjs +38 -0
- package/src/core/contracts/ToolkitError.mjs +176 -0
- package/src/core/contracts/ToolkitProgress.mjs +89 -0
- package/src/core/interaction/CanonicalInteractionOptions.mjs +246 -0
- package/src/core/interaction/PcbInteractionBounds.mjs +167 -0
- package/src/core/query/CircuitTraversal.mjs +343 -0
- package/src/core/query/ComponentGrouping.mjs +275 -0
- package/src/core/query/QueryNetlistBuilder.mjs +306 -0
- package/src/core/query/QueryService.mjs +435 -0
- package/src/core/query/RegexPattern.mjs +75 -0
- package/src/core/rendering/CanonicalBomOrder.mjs +81 -0
- package/src/core/rendering/CanonicalBomRows.mjs +92 -0
- package/src/core/rendering/CanonicalRenderOptions.mjs +498 -0
- package/src/core/rendering/CanonicalSvgDocument.mjs +102 -0
- package/src/core/rendering/PcbRenderPlan.mjs +429 -0
- package/src/core/rendering/SchematicSheetSelector.mjs +335 -0
- package/src/core/scene3d/PcbScene3dBuilder.mjs +906 -0
- package/src/core/scene3d/PcbScene3dPreparator.mjs +47 -0
- package/src/core/scene3d/Scene3dAssetIndex.mjs +284 -0
- package/src/core/scene3d/Scene3dBoardModel.mjs +596 -0
- package/src/core/scene3d/Scene3dDocumentMetadata.mjs +167 -0
- package/src/core/scene3d/Scene3dFreeze.mjs +37 -0
- package/src/core/scene3d/Scene3dIdRegistry.mjs +34 -0
- package/src/core/scene3d/Scene3dInputPreflight.mjs +193 -0
- package/src/core/scene3d/Scene3dMaterials.mjs +58 -0
- package/src/core/scene3d/Scene3dModelReference.mjs +134 -0
- package/src/core/scene3d/Scene3dOptions.mjs +363 -0
- package/src/core/scene3d/SceneAssetResolver.mjs +441 -0
- package/src/core/simulation/SimulationParameterCloner.mjs +543 -0
- package/src/core/worker/ParserWorkerClient.mjs +997 -0
- package/src/core/worker/ToolkitWorkerProtocol.mjs +412 -0
- package/src/core/worker/WorkerRequestData.mjs +623 -0
- package/src/core/worker/WorkerResponseData.mjs +905 -0
- package/src/extensions.mjs +37 -0
- package/src/index.mjs +14 -9
- package/src/interaction.mjs +2 -0
- package/src/manufacturing.mjs +1 -0
- package/src/parser.mjs +12 -2
- package/src/project.mjs +5 -0
- package/src/query.mjs +1 -0
- package/src/renderers.mjs +3 -29
- package/src/scene3d.mjs +3 -0
- package/src/simulation.mjs +1 -0
- package/src/styles/renderers.css +24 -0
- package/src/testing/ToolkitContractFixtures.mjs +124 -0
- package/src/testing/ToolkitLoopbackWorker.mjs +174 -0
- package/src/testing/runToolkitContract.mjs +705 -0
- package/src/testing.mjs +3 -0
- package/src/ui/BomTableRenderer.mjs +304 -0
- package/src/ui/CircuitJsonPcbBoardSvgRenderer.mjs +80 -0
- package/src/ui/CircuitJsonPcbPrimitiveAttributeRenderer.mjs +3 -5
- package/src/ui/CircuitJsonPcbSvgRenderer.mjs +63 -43
- package/src/ui/CircuitJsonPcbViaSvgRenderer.mjs +3 -5
- package/src/ui/CircuitJsonSchematicDebugRenderer.mjs +164 -0
- package/src/ui/CircuitJsonSchematicImageSvgRenderer.mjs +210 -0
- package/src/ui/CircuitJsonSchematicLineRenderer.mjs +86 -0
- package/src/ui/CircuitJsonSchematicSheetSymbolSvgRenderer.mjs +98 -0
- package/src/ui/CircuitJsonSchematicSvgArcPath.mjs +117 -17
- package/src/ui/CircuitJsonSchematicSvgPortMetadata.mjs +67 -20
- package/src/ui/CircuitJsonSchematicSvgPrimitiveAttributes.mjs +45 -9
- package/src/ui/CircuitJsonSchematicSvgRenderer.mjs +151 -148
- package/src/ui/CircuitJsonSchematicTableSvgRenderer.mjs +4 -292
- package/src/ui/PcbSvgRenderer.mjs +41 -0
- package/src/ui/SafeSvgPaint.mjs +26 -0
- package/src/ui/SafeXmlText.mjs +60 -0
- package/src/ui/SchematicGeometryBounds.mjs +540 -0
- package/src/ui/SchematicSvgRenderer.mjs +110 -0
- package/src/ui/SchematicTableGeometry.mjs +319 -0
- package/src/ui/SchematicTextAnchor.mjs +55 -0
- package/src/ui/SchematicTextBounds.mjs +98 -0
- package/src/workers/parser.worker.mjs +59 -0
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { CircuitJsonDocument } from './CircuitJsonDocument.mjs'
|
|
2
|
+
import { CircuitJsonDiagnosticIndexer } from './CircuitJsonDiagnosticIndexer.mjs'
|
|
3
|
+
import { CircuitJsonLegacyModel } from './context/CircuitJsonLegacyModel.mjs'
|
|
4
|
+
import { CircuitJsonValidationProof } from './context/CircuitJsonValidationProof.mjs'
|
|
2
5
|
|
|
3
6
|
const ID_FIELDS_BY_TYPE = {
|
|
4
7
|
pcb_board: 'pcb_board_id',
|
|
@@ -19,14 +22,31 @@ const ID_FIELDS_BY_TYPE = {
|
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
const GROUP_TYPES = new Set(['source_group', 'pcb_group', 'schematic_group'])
|
|
25
|
+
const GROUP_RELATION_FIELDS = [
|
|
26
|
+
'source_group_id',
|
|
27
|
+
'pcb_group_id',
|
|
28
|
+
'schematic_group_id',
|
|
29
|
+
'group_id',
|
|
30
|
+
'member_source_group_id',
|
|
31
|
+
'member_pcb_group_id',
|
|
32
|
+
'member_schematic_group_id',
|
|
33
|
+
'member_group_id',
|
|
34
|
+
'group_ids',
|
|
35
|
+
'member_source_group_ids',
|
|
36
|
+
'member_pcb_group_ids',
|
|
37
|
+
'member_schematic_group_ids',
|
|
38
|
+
'member_group_ids'
|
|
39
|
+
]
|
|
22
40
|
const SCHEMATIC_PRIMITIVE_TYPES = new Set([
|
|
23
41
|
'schematic_arc',
|
|
24
42
|
'schematic_box',
|
|
25
43
|
'schematic_circle',
|
|
44
|
+
'schematic_image',
|
|
26
45
|
'schematic_line',
|
|
27
46
|
'schematic_net_label',
|
|
28
47
|
'schematic_path',
|
|
29
48
|
'schematic_rect',
|
|
49
|
+
'schematic_sheet_symbol',
|
|
30
50
|
'schematic_table',
|
|
31
51
|
'schematic_table_cell',
|
|
32
52
|
'schematic_text',
|
|
@@ -41,69 +61,145 @@ export class CircuitJsonIndexer {
|
|
|
41
61
|
/**
|
|
42
62
|
* Indexes a CircuitJSON model.
|
|
43
63
|
* @param {object[]} circuitJson CircuitJSON model.
|
|
44
|
-
* @
|
|
64
|
+
* @param {{ validated?: boolean, families?: string[] | null }} [options] Internal validation options.
|
|
65
|
+
* @returns {Record<string, any>} Full legacy index or requested index families.
|
|
45
66
|
*/
|
|
46
|
-
static index(circuitJson) {
|
|
47
|
-
|
|
67
|
+
static index(circuitJson, options = {}) {
|
|
68
|
+
const prepared = CircuitJsonLegacyModel.takePreparedIndex(circuitJson)
|
|
69
|
+
if (prepared) return prepared
|
|
70
|
+
const trusted = CircuitJsonValidationProof.permitsIndex(
|
|
71
|
+
circuitJson,
|
|
72
|
+
options
|
|
73
|
+
)
|
|
74
|
+
const elementSlots = trusted
|
|
75
|
+
? circuitJson
|
|
76
|
+
: CircuitJsonIndexer.#captureElementSlots(circuitJson)
|
|
77
|
+
const trustedLegacy =
|
|
78
|
+
!trusted && CircuitJsonLegacyModel.permitsIndex(circuitJson)
|
|
79
|
+
if (!trusted && !trustedLegacy) {
|
|
80
|
+
CircuitJsonDocument.assertModel(elementSlots)
|
|
81
|
+
}
|
|
82
|
+
const families = trusted
|
|
83
|
+
? CircuitJsonValidationProof.indexFamilies(circuitJson, options)
|
|
84
|
+
: null
|
|
85
|
+
const requested = new Set(families || [])
|
|
86
|
+
const full = families === null
|
|
87
|
+
const buildElements = full || requested.has('elements')
|
|
88
|
+
const buildIdentifiers = !full && requested.has('identifiers')
|
|
89
|
+
const buildRelations = full || requested.has('relations')
|
|
90
|
+
const buildConnectivity = full || requested.has('connectivity')
|
|
91
|
+
const buildSpatial = full || requested.has('spatial')
|
|
48
92
|
const elementsByType = new Map()
|
|
49
93
|
const elementsById = new Map()
|
|
94
|
+
const elementIds = new Set()
|
|
50
95
|
const relationsByField = new Map()
|
|
51
96
|
const sourceComponentById = new Map()
|
|
52
97
|
const pcbComponentById = new Map()
|
|
53
98
|
const sourceTraceById = new Map()
|
|
99
|
+
const groupElements = []
|
|
54
100
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
101
|
+
if (
|
|
102
|
+
buildElements ||
|
|
103
|
+
buildIdentifiers ||
|
|
104
|
+
buildRelations ||
|
|
105
|
+
buildConnectivity
|
|
106
|
+
) {
|
|
107
|
+
for (let slot = 0; slot < elementSlots.length; slot += 1) {
|
|
108
|
+
const element = elementSlots[slot]
|
|
109
|
+
const type = String(element?.type || '')
|
|
110
|
+
const id = CircuitJsonIndexer.getElementId(element)
|
|
111
|
+
if (buildElements || buildConnectivity) {
|
|
112
|
+
if (!elementsByType.has(type)) {
|
|
113
|
+
elementsByType.set(type, [])
|
|
114
|
+
}
|
|
115
|
+
elementsByType.get(type).push(element)
|
|
116
|
+
}
|
|
117
|
+
if (buildElements && id) {
|
|
118
|
+
elementsById.set(`${type}:${id}`, element)
|
|
119
|
+
}
|
|
120
|
+
if (buildIdentifiers && id) {
|
|
121
|
+
elementIds.add(`${type}:${id}`)
|
|
122
|
+
}
|
|
123
|
+
if ((buildElements || buildRelations) && id) {
|
|
124
|
+
if (type === 'source_component') {
|
|
125
|
+
sourceComponentById.set(id, element)
|
|
126
|
+
}
|
|
127
|
+
if (buildElements && type === 'pcb_component') {
|
|
128
|
+
pcbComponentById.set(id, element)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if ((buildElements || buildConnectivity) && id) {
|
|
132
|
+
if (type === 'source_trace') {
|
|
133
|
+
sourceTraceById.set(id, element)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (buildRelations) {
|
|
137
|
+
if (GROUP_TYPES.has(type)) groupElements.push(element)
|
|
138
|
+
CircuitJsonIndexer.#indexRelations(
|
|
139
|
+
element,
|
|
140
|
+
relationsByField
|
|
141
|
+
)
|
|
142
|
+
}
|
|
74
143
|
}
|
|
75
|
-
|
|
76
|
-
})
|
|
77
|
-
const sourceTraceConnectivity =
|
|
78
|
-
CircuitJsonIndexer.#sourceTraceConnectivity(sourceTraceById)
|
|
144
|
+
}
|
|
79
145
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
sourceTraceConnectivity,
|
|
89
|
-
componentsBySourceId: CircuitJsonIndexer.#componentsBySourceId(
|
|
146
|
+
const result = {}
|
|
147
|
+
if (buildElements || buildSpatial) {
|
|
148
|
+
result.elements = circuitJson
|
|
149
|
+
}
|
|
150
|
+
if (buildElements) {
|
|
151
|
+
Object.assign(result, {
|
|
152
|
+
elementsByType,
|
|
153
|
+
elementsById,
|
|
90
154
|
sourceComponentById,
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
elementsByGroupId:
|
|
95
|
-
CircuitJsonIndexer.#elementsByGroupId(circuitJson),
|
|
96
|
-
elementsBySubcircuitId:
|
|
97
|
-
CircuitJsonIndexer.#elementsBySubcircuitId(circuitJson),
|
|
98
|
-
diagnostics: [
|
|
99
|
-
...CircuitJsonIndexer.collectDiagnostics(circuitJson),
|
|
100
|
-
...CircuitJsonIndexer.#referenceDiagnostics(
|
|
101
|
-
elementsByType,
|
|
102
|
-
sourceTraceById,
|
|
103
|
-
sourceTraceConnectivity
|
|
104
|
-
)
|
|
105
|
-
]
|
|
155
|
+
pcbComponentById,
|
|
156
|
+
sourceTraceById
|
|
157
|
+
})
|
|
106
158
|
}
|
|
159
|
+
if (buildIdentifiers) {
|
|
160
|
+
result.elementsById = elementIds
|
|
161
|
+
}
|
|
162
|
+
if (buildRelations) {
|
|
163
|
+
const hasGroupRelations = GROUP_RELATION_FIELDS.some((field) =>
|
|
164
|
+
relationsByField.has(field)
|
|
165
|
+
)
|
|
166
|
+
const elementsByGroupId = hasGroupRelations
|
|
167
|
+
? CircuitJsonIndexer.#elementsByGroupId(elementSlots)
|
|
168
|
+
: new Map()
|
|
169
|
+
Object.assign(result, {
|
|
170
|
+
relationsByField,
|
|
171
|
+
componentsBySourceId: CircuitJsonIndexer.#componentsBySourceId(
|
|
172
|
+
sourceComponentById,
|
|
173
|
+
relationsByField
|
|
174
|
+
),
|
|
175
|
+
groupsById: CircuitJsonIndexer.#groupsById(
|
|
176
|
+
groupElements,
|
|
177
|
+
elementsByGroupId
|
|
178
|
+
),
|
|
179
|
+
elementsByGroupId,
|
|
180
|
+
elementsBySubcircuitId:
|
|
181
|
+
relationsByField.get('subcircuit_id') || new Map()
|
|
182
|
+
})
|
|
183
|
+
}
|
|
184
|
+
if (buildConnectivity) {
|
|
185
|
+
const sourceTraceConnectivity =
|
|
186
|
+
CircuitJsonIndexer.#sourceTraceConnectivity(sourceTraceById)
|
|
187
|
+
Object.assign(result, {
|
|
188
|
+
sourceTraceById,
|
|
189
|
+
sourceTraceConnectivity,
|
|
190
|
+
diagnostics: [
|
|
191
|
+
...CircuitJsonIndexer.#collectIndexedDiagnostics(
|
|
192
|
+
elementsByType
|
|
193
|
+
),
|
|
194
|
+
...CircuitJsonIndexer.#referenceDiagnostics(
|
|
195
|
+
elementsByType,
|
|
196
|
+
sourceTraceById,
|
|
197
|
+
sourceTraceConnectivity
|
|
198
|
+
)
|
|
199
|
+
]
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
return result
|
|
107
203
|
}
|
|
108
204
|
|
|
109
205
|
/**
|
|
@@ -123,10 +219,107 @@ export class CircuitJsonIndexer {
|
|
|
123
219
|
* @returns {object[]}
|
|
124
220
|
*/
|
|
125
221
|
static collectDiagnostics(circuitJson) {
|
|
126
|
-
|
|
222
|
+
const elementSlots =
|
|
223
|
+
CircuitJsonIndexer.#captureElementSlots(circuitJson)
|
|
224
|
+
CircuitJsonDocument.assertModel(elementSlots)
|
|
225
|
+
return CircuitJsonIndexer.#collectDiagnostics(elementSlots)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Captures dense element data slots without caller-dispatchable iteration.
|
|
230
|
+
* @param {unknown} circuitJson CircuitJSON array candidate.
|
|
231
|
+
* @returns {object[]} Plain intrinsic array containing the captured slots.
|
|
232
|
+
*/
|
|
233
|
+
static #captureElementSlots(circuitJson) {
|
|
234
|
+
if (!Array.isArray(circuitJson)) return circuitJson
|
|
235
|
+
let prototype
|
|
236
|
+
let lengthDescriptor
|
|
237
|
+
try {
|
|
238
|
+
prototype = Object.getPrototypeOf(circuitJson)
|
|
239
|
+
lengthDescriptor = Object.getOwnPropertyDescriptor(
|
|
240
|
+
circuitJson,
|
|
241
|
+
'length'
|
|
242
|
+
)
|
|
243
|
+
} catch {
|
|
244
|
+
throw new TypeError(
|
|
245
|
+
'CircuitJSON arrays must expose dense own data properties.'
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
const length = lengthDescriptor?.value
|
|
249
|
+
if (
|
|
250
|
+
prototype !== Array.prototype ||
|
|
251
|
+
!Number.isSafeInteger(length) ||
|
|
252
|
+
length < 0
|
|
253
|
+
) {
|
|
254
|
+
throw new TypeError(
|
|
255
|
+
'CircuitJSON arrays must expose dense own data properties.'
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
const elements = new Array(length)
|
|
259
|
+
for (let index = 0; index < length; index += 1) {
|
|
260
|
+
let descriptor
|
|
261
|
+
try {
|
|
262
|
+
descriptor = Object.getOwnPropertyDescriptor(
|
|
263
|
+
circuitJson,
|
|
264
|
+
String(index)
|
|
265
|
+
)
|
|
266
|
+
} catch {
|
|
267
|
+
throw new TypeError(
|
|
268
|
+
'CircuitJSON arrays must expose dense own data properties.'
|
|
269
|
+
)
|
|
270
|
+
}
|
|
271
|
+
if (
|
|
272
|
+
!descriptor ||
|
|
273
|
+
!Object.hasOwn(descriptor, 'value') ||
|
|
274
|
+
descriptor.enumerable !== true
|
|
275
|
+
) {
|
|
276
|
+
throw new TypeError(
|
|
277
|
+
'CircuitJSON arrays must expose dense own data properties.'
|
|
278
|
+
)
|
|
279
|
+
}
|
|
280
|
+
elements[index] = descriptor.value
|
|
281
|
+
}
|
|
282
|
+
return elements
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Collects normalized diagnostic rows from an already validated model.
|
|
287
|
+
* @param {object[]} circuitJson CircuitJSON model.
|
|
288
|
+
* @returns {object[]}
|
|
289
|
+
*/
|
|
290
|
+
static #collectDiagnostics(circuitJson) {
|
|
127
291
|
return circuitJson
|
|
128
|
-
.filter((element) =>
|
|
129
|
-
|
|
292
|
+
.filter((element) =>
|
|
293
|
+
CircuitJsonDiagnosticIndexer.isElement(element)
|
|
294
|
+
)
|
|
295
|
+
.map((element) =>
|
|
296
|
+
CircuitJsonDiagnosticIndexer.fromElement(
|
|
297
|
+
element,
|
|
298
|
+
CircuitJsonIndexer.getElementId(element)
|
|
299
|
+
)
|
|
300
|
+
)
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Collects diagnostics from validated type buckets without rescanning every
|
|
305
|
+
* non-diagnostic row in large models.
|
|
306
|
+
* @param {Map<string, object[]>} elementsByType Element rows by type.
|
|
307
|
+
* @returns {object[]}
|
|
308
|
+
*/
|
|
309
|
+
static #collectIndexedDiagnostics(elementsByType) {
|
|
310
|
+
const diagnostics = []
|
|
311
|
+
for (const [type, elements] of elementsByType) {
|
|
312
|
+
if (!CircuitJsonDiagnosticIndexer.isType(type)) continue
|
|
313
|
+
for (const element of elements) {
|
|
314
|
+
diagnostics.push(
|
|
315
|
+
CircuitJsonDiagnosticIndexer.fromElement(
|
|
316
|
+
element,
|
|
317
|
+
CircuitJsonIndexer.getElementId(element)
|
|
318
|
+
)
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return diagnostics
|
|
130
323
|
}
|
|
131
324
|
|
|
132
325
|
/**
|
|
@@ -139,17 +332,21 @@ export class CircuitJsonIndexer {
|
|
|
139
332
|
const primaryIdField =
|
|
140
333
|
ID_FIELDS_BY_TYPE[String(element?.type || '')] ||
|
|
141
334
|
String(element?.type || '') + '_id'
|
|
142
|
-
for (const
|
|
335
|
+
for (const field in element || {}) {
|
|
336
|
+
if (!Object.hasOwn(element, field)) continue
|
|
143
337
|
if (!field.endsWith('_id') && !field.endsWith('_ids')) continue
|
|
144
338
|
if (field === primaryIdField) continue
|
|
145
|
-
|
|
146
|
-
if (
|
|
147
|
-
|
|
339
|
+
const value = element[field]
|
|
340
|
+
if (value == null || value === '') continue
|
|
341
|
+
if (Array.isArray(value) && value.length === 0) continue
|
|
342
|
+
const relationValues = CircuitJsonIndexer.#relationValues(value)
|
|
343
|
+
if (relationValues.length === 0) continue
|
|
344
|
+
let byValue = relationsByField.get(field)
|
|
345
|
+
if (!byValue) {
|
|
346
|
+
byValue = new Map()
|
|
347
|
+
relationsByField.set(field, byValue)
|
|
148
348
|
}
|
|
149
|
-
const
|
|
150
|
-
for (const relationValue of CircuitJsonIndexer.#relationValues(
|
|
151
|
-
value
|
|
152
|
-
)) {
|
|
349
|
+
for (const relationValue of relationValues) {
|
|
153
350
|
if (!byValue.has(relationValue)) {
|
|
154
351
|
byValue.set(relationValue, [])
|
|
155
352
|
}
|
|
@@ -239,6 +436,17 @@ export class CircuitJsonIndexer {
|
|
|
239
436
|
sourceTraceById,
|
|
240
437
|
sourceTraceConnectivity
|
|
241
438
|
) {
|
|
439
|
+
if (
|
|
440
|
+
sourceTraceConnectivity.size === 0 &&
|
|
441
|
+
!elementsByType.has('pcb_trace') &&
|
|
442
|
+
!elementsByType.has('schematic_component') &&
|
|
443
|
+
!elementsByType.has('schematic_port') &&
|
|
444
|
+
![...SCHEMATIC_PRIMITIVE_TYPES].some((type) =>
|
|
445
|
+
elementsByType.has(type)
|
|
446
|
+
)
|
|
447
|
+
) {
|
|
448
|
+
return []
|
|
449
|
+
}
|
|
242
450
|
const sourcePortIds = new Set(
|
|
243
451
|
CircuitJsonIndexer.#all(elementsByType, 'source_port')
|
|
244
452
|
.map((port) => String(port.source_port_id || '').trim())
|
|
@@ -609,15 +817,13 @@ export class CircuitJsonIndexer {
|
|
|
609
817
|
|
|
610
818
|
/**
|
|
611
819
|
* Builds group rows keyed by group id.
|
|
612
|
-
* @param {object[]}
|
|
820
|
+
* @param {object[]} groupElements Group element rows.
|
|
821
|
+
* @param {Map<string, object[]>} elementsByGroupId Element membership.
|
|
613
822
|
* @returns {Map<string, object>}
|
|
614
823
|
*/
|
|
615
|
-
static #groupsById(
|
|
616
|
-
const elementsByGroupId =
|
|
617
|
-
CircuitJsonIndexer.#elementsByGroupId(elements)
|
|
824
|
+
static #groupsById(groupElements, elementsByGroupId) {
|
|
618
825
|
const groups = new Map()
|
|
619
|
-
for (const element of
|
|
620
|
-
if (!GROUP_TYPES.has(String(element?.type || ''))) continue
|
|
826
|
+
for (const element of groupElements) {
|
|
621
827
|
const id = CircuitJsonIndexer.getElementId(element)
|
|
622
828
|
if (!id) continue
|
|
623
829
|
groups.set(id, {
|
|
@@ -648,22 +854,6 @@ export class CircuitJsonIndexer {
|
|
|
648
854
|
return byGroupId
|
|
649
855
|
}
|
|
650
856
|
|
|
651
|
-
/**
|
|
652
|
-
* Builds elements by subcircuit id.
|
|
653
|
-
* @param {object[]} elements Element rows.
|
|
654
|
-
* @returns {Map<string, object[]>}
|
|
655
|
-
*/
|
|
656
|
-
static #elementsBySubcircuitId(elements) {
|
|
657
|
-
const bySubcircuitId = new Map()
|
|
658
|
-
for (const element of elements) {
|
|
659
|
-
const id = String(element?.subcircuit_id || '').trim()
|
|
660
|
-
if (!id) continue
|
|
661
|
-
if (!bySubcircuitId.has(id)) bySubcircuitId.set(id, [])
|
|
662
|
-
bySubcircuitId.get(id).push(element)
|
|
663
|
-
}
|
|
664
|
-
return bySubcircuitId
|
|
665
|
-
}
|
|
666
|
-
|
|
667
857
|
/**
|
|
668
858
|
* Builds a set of known element ids for one type.
|
|
669
859
|
* @param {Map<string, object[]>} elementsByType Element rows by type.
|
|
@@ -718,114 +908,4 @@ export class CircuitJsonIndexer {
|
|
|
718
908
|
static #all(elementsByType, type) {
|
|
719
909
|
return elementsByType.get(type) || []
|
|
720
910
|
}
|
|
721
|
-
|
|
722
|
-
/**
|
|
723
|
-
* Returns true when an element is a warning or error row.
|
|
724
|
-
* @param {object} element Element.
|
|
725
|
-
* @returns {boolean}
|
|
726
|
-
*/
|
|
727
|
-
static #isDiagnostic(element) {
|
|
728
|
-
const type = String(element?.type || '')
|
|
729
|
-
return (
|
|
730
|
-
type.endsWith('_error') ||
|
|
731
|
-
type.endsWith('_warning') ||
|
|
732
|
-
Boolean(element?.error_type || element?.warning_type)
|
|
733
|
-
)
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
/**
|
|
737
|
-
* Builds one normalized diagnostic.
|
|
738
|
-
* @param {object} element Diagnostic element.
|
|
739
|
-
* @returns {object}
|
|
740
|
-
*/
|
|
741
|
-
static #diagnostic(element) {
|
|
742
|
-
const type = String(
|
|
743
|
-
element?.error_type || element?.warning_type || element?.type || ''
|
|
744
|
-
)
|
|
745
|
-
return {
|
|
746
|
-
severity: element?.warning_type ? 'warning' : 'error',
|
|
747
|
-
sourceFormat: 'circuitjson',
|
|
748
|
-
type,
|
|
749
|
-
category: CircuitJsonIndexer.#diagnosticCategory(type),
|
|
750
|
-
message: String(element?.message || type || 'CircuitJSON issue'),
|
|
751
|
-
elementId: CircuitJsonIndexer.getElementId(element),
|
|
752
|
-
...CircuitJsonIndexer.#diagnosticRelations(element)
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
/**
|
|
757
|
-
* Extracts optional relation ids from one diagnostic element.
|
|
758
|
-
* @param {object} element Diagnostic element.
|
|
759
|
-
* @returns {object}
|
|
760
|
-
*/
|
|
761
|
-
static #diagnosticRelations(element) {
|
|
762
|
-
return Object.fromEntries(
|
|
763
|
-
[
|
|
764
|
-
['sourceComponentId', element?.source_component_id],
|
|
765
|
-
['sourcePortId', element?.source_port_id],
|
|
766
|
-
['sourceNetId', element?.source_net_id],
|
|
767
|
-
['sourceTraceId', element?.source_trace_id],
|
|
768
|
-
['pcbComponentId', element?.pcb_component_id],
|
|
769
|
-
['pcbPortId', element?.pcb_port_id],
|
|
770
|
-
['pcbTraceId', element?.pcb_trace_id],
|
|
771
|
-
['pcbSmtpadId', element?.pcb_smtpad_id],
|
|
772
|
-
['pcbViaId', element?.pcb_via_id],
|
|
773
|
-
['pcbPlatedHoleId', element?.pcb_plated_hole_id],
|
|
774
|
-
['pcbHoleId', element?.pcb_hole_id],
|
|
775
|
-
['schematicComponentId', element?.schematic_component_id],
|
|
776
|
-
['schematicSymbolId', element?.schematic_symbol_id],
|
|
777
|
-
['schematicPortId', element?.schematic_port_id]
|
|
778
|
-
]
|
|
779
|
-
.map(([key, value]) => [key, String(value || '').trim()])
|
|
780
|
-
.filter(([_key, value]) => value)
|
|
781
|
-
)
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
/**
|
|
785
|
-
* Resolves a broad diagnostic category.
|
|
786
|
-
* @param {string} type Diagnostic type or code.
|
|
787
|
-
* @returns {string}
|
|
788
|
-
*/
|
|
789
|
-
static #diagnosticCategory(type) {
|
|
790
|
-
const text = String(type || '').toLowerCase()
|
|
791
|
-
if (text.includes('clearance')) return 'clearance'
|
|
792
|
-
if (text.includes('autorouting') || text.includes('trace_error')) {
|
|
793
|
-
return 'routing'
|
|
794
|
-
}
|
|
795
|
-
if (text.includes('placement') || text.includes('outside_board')) {
|
|
796
|
-
return 'placement'
|
|
797
|
-
}
|
|
798
|
-
if (
|
|
799
|
-
text.includes('trace_missing') ||
|
|
800
|
-
text.includes('not_connected') ||
|
|
801
|
-
text.includes('missing_trace') ||
|
|
802
|
-
text.includes('pin_missing_trace') ||
|
|
803
|
-
text.includes('pin_must_be_connected')
|
|
804
|
-
) {
|
|
805
|
-
return 'connectivity'
|
|
806
|
-
}
|
|
807
|
-
if (text.includes('layout')) return 'layout'
|
|
808
|
-
if (text.includes('simulation')) return 'simulation'
|
|
809
|
-
if (text.includes('footprint')) return 'footprint'
|
|
810
|
-
if (
|
|
811
|
-
text.includes('pin_defined') ||
|
|
812
|
-
text.includes('pins_underspecified') ||
|
|
813
|
-
text.includes('ground_pin') ||
|
|
814
|
-
text.includes('power_pin')
|
|
815
|
-
) {
|
|
816
|
-
return 'pin-definition'
|
|
817
|
-
}
|
|
818
|
-
if (
|
|
819
|
-
text.includes('manufacturer_part') ||
|
|
820
|
-
text.includes('missing_property') ||
|
|
821
|
-
text.includes('property_ignored')
|
|
822
|
-
) {
|
|
823
|
-
return 'metadata'
|
|
824
|
-
}
|
|
825
|
-
if (text.includes('manual_edit_conflict')) return 'edit-conflict'
|
|
826
|
-
if (text.includes('property') || text.includes('misconfigured')) {
|
|
827
|
-
return 'configuration'
|
|
828
|
-
}
|
|
829
|
-
return 'general'
|
|
830
|
-
}
|
|
831
911
|
}
|