altium-toolkit 1.0.10 → 1.1.1
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 +4 -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 +132 -1
- 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/normalized_model_a1.schema.json +692 -2
- 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/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_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/package.json +1 -1
- package/src/core/altium/AltiumParser.mjs +7 -2
- package/src/core/altium/CiArtifactBundleBuilder.mjs +16 -5
- package/src/core/altium/ContractGateReportBuilder.mjs +351 -0
- package/src/core/altium/DraftsmanBoardViewMetadataBuilder.mjs +653 -0
- package/src/core/altium/DraftsmanDigestParser.mjs +246 -7
- 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/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 +182 -18
- package/src/core/altium/PcbPickPlacePositionResolver.mjs +3 -0
- package/src/core/altium/PcbPlacedFootprintManifestBuilder.mjs +338 -0
- package/src/core/altium/PcbPolygonRecordParser.mjs +120 -0
- 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/PrintableTextDecoder.mjs +70 -6
- package/src/core/altium/PrjPcbModelParser.mjs +45 -0
- package/src/core/altium/PrjScrModelParser.mjs +386 -0
- package/src/core/altium/ProjectBomPnpReconciliationBuilder.mjs +237 -0
- package/src/core/altium/ProjectDesignBundleBuilder.mjs +61 -2
- package/src/core/altium/ProjectOutJobDigestBuilder.mjs +424 -13
- package/src/core/altium/SvgModelCrossLinkValidator.mjs +35 -2
- package/src/core/circuit-json/CircuitJsonModelAdapter.mjs +164 -0
- package/src/parser.mjs +15 -0
- package/src/ui/PcbEdgeFacingGlyphNormalizer.mjs +155 -5
- package/src/ui/PcbFootprintPrimitiveSelector.mjs +13 -1
- package/src/ui/PcbScene3dBuilder.mjs +26 -4
- package/src/ui/SchematicRenderOpsSidecarBuilder.mjs +554 -0
- package/src/ui/SchematicSvgRenderer.mjs +48 -2
|
@@ -8,6 +8,68 @@ import { CircuitJsonModelAdapterPcbElements } from './CircuitJsonModelAdapterPcb
|
|
|
8
8
|
|
|
9
9
|
const Primitives = CircuitJsonModelAdapterPrimitives
|
|
10
10
|
const PcbElements = CircuitJsonModelAdapterPcbElements
|
|
11
|
+
const ALTIUM_TOOLKIT_SIDECARS = [
|
|
12
|
+
{
|
|
13
|
+
type: 'altium_toolkit_pcb_layer_stack',
|
|
14
|
+
paths: [['pcb', 'layerStackReadModel']],
|
|
15
|
+
schema: 'altium-toolkit.pcb.layer-stack.a1'
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
type: 'altium_toolkit_pcb_rigid_flex_topology',
|
|
19
|
+
paths: [['pcb', 'rigidFlexTopology']],
|
|
20
|
+
schema: 'altium-toolkit.pcb.rigid-flex-topology.a1'
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
type: 'altium_toolkit_pcb_review_metadata',
|
|
24
|
+
paths: [['pcb', 'reviewMetadata']],
|
|
25
|
+
schema: 'altium-toolkit.pcb.review-metadata.a1'
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
type: 'altium_toolkit_pcb_placed_footprint_extraction',
|
|
29
|
+
paths: [['pcb', 'footprintExtractionManifest']],
|
|
30
|
+
schema: 'altium-toolkit.pcb.placed-footprint-extraction.a1'
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
type: 'altium_toolkit_pcblib_parity',
|
|
34
|
+
paths: [['pcbLibrary', 'parityReport']],
|
|
35
|
+
schema: 'altium-toolkit.pcblib.parity.a1'
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
type: 'altium_toolkit_project_outjob_digest',
|
|
39
|
+
paths: [['project', 'outJobDigest']],
|
|
40
|
+
schema: 'altium-toolkit.project.outjob-digest.a1'
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: 'altium_toolkit_project_document_graph',
|
|
44
|
+
paths: [['project', 'documentGraph'], ['documentGraph']],
|
|
45
|
+
schema: 'altium-toolkit.project.document-graph.a1'
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
type: 'altium_toolkit_project_bom_pnp_reconciliation',
|
|
49
|
+
paths: [['reconciliation']],
|
|
50
|
+
schema: 'altium-toolkit.project.bom-pnp-reconciliation.a1'
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
type: 'altium_toolkit_draftsman_image_payloads',
|
|
54
|
+
paths: [['draftsman', 'imagePayloads']],
|
|
55
|
+
schema: 'altium-toolkit.draftsman.image-payloads.a1'
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
type: 'altium_toolkit_draftsman_board_view_metadata',
|
|
59
|
+
paths: [['draftsman', 'boardViewMetadata']],
|
|
60
|
+
schema: 'altium-toolkit.draftsman.board-view-cache.a1'
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
type: 'altium_toolkit_contract_gate',
|
|
64
|
+
paths: [['contractGate']],
|
|
65
|
+
schema: 'altium-toolkit.contract-gate.a1'
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
type: 'altium_toolkit_host_capabilities',
|
|
69
|
+
paths: [['hostCapabilities']],
|
|
70
|
+
schema: 'altium-toolkit.host-capabilities.a1'
|
|
71
|
+
}
|
|
72
|
+
]
|
|
11
73
|
|
|
12
74
|
/**
|
|
13
75
|
* Converts between legacy renderer models and Circuit JSON element arrays.
|
|
@@ -55,6 +117,7 @@ export class CircuitJsonModelAdapter {
|
|
|
55
117
|
}
|
|
56
118
|
|
|
57
119
|
CircuitJsonModelAdapter.#appendBom(circuitJson, model, idScope)
|
|
120
|
+
CircuitJsonModelAdapter.#appendSidecars(circuitJson, model, idScope)
|
|
58
121
|
CircuitJsonModelAdapter.#attachCompatibility(circuitJson, model)
|
|
59
122
|
|
|
60
123
|
return CircuitJsonModelSchema.attach(circuitJson)
|
|
@@ -651,6 +714,107 @@ export class CircuitJsonModelAdapter {
|
|
|
651
714
|
}
|
|
652
715
|
}
|
|
653
716
|
|
|
717
|
+
/**
|
|
718
|
+
* Appends serialized Altium Toolkit sidecar elements.
|
|
719
|
+
* @param {object[]} circuitJson
|
|
720
|
+
* @param {Record<string, unknown>} model
|
|
721
|
+
* @param {string} idScope
|
|
722
|
+
* @returns {void}
|
|
723
|
+
*/
|
|
724
|
+
static #appendSidecars(circuitJson, model, idScope) {
|
|
725
|
+
for (const descriptor of ALTIUM_TOOLKIT_SIDECARS) {
|
|
726
|
+
const payload = CircuitJsonModelAdapter.#sidecarPayload(
|
|
727
|
+
model,
|
|
728
|
+
descriptor.paths
|
|
729
|
+
)
|
|
730
|
+
if (!payload) continue
|
|
731
|
+
|
|
732
|
+
circuitJson.push(
|
|
733
|
+
CircuitJsonModelAdapter.#sidecarElement(
|
|
734
|
+
descriptor,
|
|
735
|
+
payload,
|
|
736
|
+
model,
|
|
737
|
+
idScope
|
|
738
|
+
)
|
|
739
|
+
)
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Builds one custom Altium Toolkit Circuit JSON sidecar element.
|
|
745
|
+
* @param {{ type: string, schema: string }} descriptor
|
|
746
|
+
* @param {Record<string, unknown>} payload
|
|
747
|
+
* @param {Record<string, unknown>} model
|
|
748
|
+
* @param {string} idScope
|
|
749
|
+
* @returns {object}
|
|
750
|
+
*/
|
|
751
|
+
static #sidecarElement(descriptor, payload, model, idScope) {
|
|
752
|
+
return {
|
|
753
|
+
type: descriptor.type,
|
|
754
|
+
altium_toolkit_sidecar_id: Primitives.id(idScope, [
|
|
755
|
+
'altium_toolkit_sidecar',
|
|
756
|
+
descriptor.type
|
|
757
|
+
]),
|
|
758
|
+
source_document: CircuitJsonModelAdapter.#sourceDocument(model),
|
|
759
|
+
schema: Primitives.string(payload.schema, descriptor.schema),
|
|
760
|
+
payload
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* Returns the first available sidecar payload from candidate model paths.
|
|
766
|
+
* @param {Record<string, unknown>} model
|
|
767
|
+
* @param {string[][]} paths
|
|
768
|
+
* @returns {Record<string, unknown> | null}
|
|
769
|
+
*/
|
|
770
|
+
static #sidecarPayload(model, paths) {
|
|
771
|
+
for (const path of paths) {
|
|
772
|
+
const payload = CircuitJsonModelAdapter.#valueAtPath(model, path)
|
|
773
|
+
if (CircuitJsonModelAdapter.#isSidecarPayload(payload)) {
|
|
774
|
+
return payload
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
return null
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Returns a nested object value.
|
|
782
|
+
* @param {Record<string, unknown>} value
|
|
783
|
+
* @param {string[]} path
|
|
784
|
+
* @returns {unknown}
|
|
785
|
+
*/
|
|
786
|
+
static #valueAtPath(value, path) {
|
|
787
|
+
return path.reduce(
|
|
788
|
+
(current, key) =>
|
|
789
|
+
current && typeof current === 'object'
|
|
790
|
+
? current[key]
|
|
791
|
+
: undefined,
|
|
792
|
+
value
|
|
793
|
+
)
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
/**
|
|
797
|
+
* Returns true when a value can be serialized as a sidecar payload.
|
|
798
|
+
* @param {unknown} value
|
|
799
|
+
* @returns {value is Record<string, unknown>}
|
|
800
|
+
*/
|
|
801
|
+
static #isSidecarPayload(value) {
|
|
802
|
+
return !!value && typeof value === 'object' && !Array.isArray(value)
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Builds the source document identity for sidecar elements.
|
|
807
|
+
* @param {Record<string, unknown>} model
|
|
808
|
+
* @returns {{ kind: string, file_type: string, file_name: string }}
|
|
809
|
+
*/
|
|
810
|
+
static #sourceDocument(model) {
|
|
811
|
+
return {
|
|
812
|
+
kind: Primitives.string(model.kind, 'document'),
|
|
813
|
+
file_type: Primitives.string(model.fileType, ''),
|
|
814
|
+
file_name: Primitives.string(model.fileName, '')
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
|
|
654
818
|
/**
|
|
655
819
|
* Appends one schematic line or trace.
|
|
656
820
|
* @param {object[]} circuitJson
|
package/src/parser.mjs
CHANGED
|
@@ -9,12 +9,16 @@ export { OleDirectoryEntry } from './core/ole/OleDirectoryEntry.mjs'
|
|
|
9
9
|
export { AltiumParser } from './core/altium/AltiumParser.mjs'
|
|
10
10
|
export { AltiumLayoutParser } from './core/altium/AltiumLayoutParser.mjs'
|
|
11
11
|
export { AsciiRecordParser } from './core/altium/AsciiRecordParser.mjs'
|
|
12
|
+
export { HostCapabilityDiagnosticsBuilder } from './core/altium/HostCapabilityDiagnosticsBuilder.mjs'
|
|
12
13
|
export { IntLibModelParser } from './core/altium/IntLibModelParser.mjs'
|
|
13
14
|
export { IntLibStreamExtractor } from './core/altium/IntLibStreamExtractor.mjs'
|
|
14
15
|
export { EmbeddedFileInventoryBuilder } from './core/altium/EmbeddedFileInventoryBuilder.mjs'
|
|
15
16
|
export { CiArtifactBundleBuilder } from './core/altium/CiArtifactBundleBuilder.mjs'
|
|
17
|
+
export { ContractGateReportBuilder } from './core/altium/ContractGateReportBuilder.mjs'
|
|
18
|
+
export { DraftsmanBoardViewMetadataBuilder } from './core/altium/DraftsmanBoardViewMetadataBuilder.mjs'
|
|
16
19
|
export { DraftsmanDigestParser } from './core/altium/DraftsmanDigestParser.mjs'
|
|
17
20
|
export { LibraryRenderManifestBuilder } from './core/altium/LibraryRenderManifestBuilder.mjs'
|
|
21
|
+
export { LibraryQaReportBuilder } from './core/altium/LibraryQaReportBuilder.mjs'
|
|
18
22
|
export { LibrarySearchIndex } from './core/altium/LibrarySearchIndex.mjs'
|
|
19
23
|
export { ParserUtils } from './core/altium/ParserUtils.mjs'
|
|
20
24
|
export { ParserCompatibilityFuzzer } from './core/altium/ParserCompatibilityFuzzer.mjs'
|
|
@@ -27,7 +31,9 @@ export { ProjectVariantViewBuilder } from './core/altium/ProjectVariantViewBuild
|
|
|
27
31
|
export { CircuitJsonModelSchema } from './core/circuit-json/CircuitJsonModelSchema.mjs'
|
|
28
32
|
export { CircuitJsonModelAdapter } from './core/circuit-json/CircuitJsonModelAdapter.mjs'
|
|
29
33
|
export { PcbBinaryPrimitiveParser } from './core/altium/PcbBinaryPrimitiveParser.mjs'
|
|
34
|
+
export { PcbBomProfileBuilder } from './core/altium/PcbBomProfileBuilder.mjs'
|
|
30
35
|
export { PcbBoardRegionSemanticsParser } from './core/altium/PcbBoardRegionSemanticsParser.mjs'
|
|
36
|
+
export { PcbComponentKindPolicy } from './core/altium/PcbComponentKindPolicy.mjs'
|
|
31
37
|
export { PcbComponentPrimitiveIndexer } from './core/altium/PcbComponentPrimitiveIndexer.mjs'
|
|
32
38
|
export { PcbCustomPadShapeParser } from './core/altium/PcbCustomPadShapeParser.mjs'
|
|
33
39
|
export { PcbDimensionParser } from './core/altium/PcbDimensionParser.mjs'
|
|
@@ -36,7 +42,11 @@ export { PcbEmbeddedModelExtractor } from './core/altium/PcbEmbeddedModelExtract
|
|
|
36
42
|
export { PcbExtendedPrimitiveInformationParser } from './core/altium/PcbExtendedPrimitiveInformationParser.mjs'
|
|
37
43
|
export { PcbFontMetricsParser } from './core/altium/PcbFontMetricsParser.mjs'
|
|
38
44
|
export { PcbLayerIdCodec } from './core/altium/PcbLayerIdCodec.mjs'
|
|
45
|
+
export { PcbLayerStackFidelityReportBuilder } from './core/altium/PcbLayerStackFidelityReportBuilder.mjs'
|
|
46
|
+
export { PcbLayerStackInterchangeParser } from './core/altium/PcbLayerStackInterchangeParser.mjs'
|
|
47
|
+
export { PcbLayerStackQueryHelper } from './core/altium/PcbLayerStackQueryHelper.mjs'
|
|
39
48
|
export { PcbLibModelParser } from './core/altium/PcbLibModelParser.mjs'
|
|
49
|
+
export { PcbLibParityReportBuilder } from './core/altium/PcbLibParityReportBuilder.mjs'
|
|
40
50
|
export { PcbLibStreamExtractor } from './core/altium/PcbLibStreamExtractor.mjs'
|
|
41
51
|
export { PcbMechanicalLayerPairParser } from './core/altium/PcbMechanicalLayerPairParser.mjs'
|
|
42
52
|
export { PcbModelParser } from './core/altium/PcbModelParser.mjs'
|
|
@@ -44,8 +54,11 @@ export { PcbOutlineRasterizer } from './core/altium/PcbOutlineRasterizer.mjs'
|
|
|
44
54
|
export { PcbOutlineRecovery } from './core/altium/PcbOutlineRecovery.mjs'
|
|
45
55
|
export { PcbOwnershipGraphBuilder } from './core/altium/PcbOwnershipGraphBuilder.mjs'
|
|
46
56
|
export { PcbPadStackParser } from './core/altium/PcbPadStackParser.mjs'
|
|
57
|
+
export { PcbPlacedFootprintManifestBuilder } from './core/altium/PcbPlacedFootprintManifestBuilder.mjs'
|
|
47
58
|
export { PcbPickPlacePositionResolver } from './core/altium/PcbPickPlacePositionResolver.mjs'
|
|
59
|
+
export { PcbPolygonRecordParser } from './core/altium/PcbPolygonRecordParser.mjs'
|
|
48
60
|
export { PcbRawRecordRegistry } from './core/altium/PcbRawRecordRegistry.mjs'
|
|
61
|
+
export { PcbReviewMetadataBuilder } from './core/altium/PcbReviewMetadataBuilder.mjs'
|
|
49
62
|
export { PcbRouteAnalysisBuilder } from './core/altium/PcbRouteAnalysisBuilder.mjs'
|
|
50
63
|
export { PcbRuleParser } from './core/altium/PcbRuleParser.mjs'
|
|
51
64
|
export { PcbSidecarRecordParser } from './core/altium/PcbSidecarRecordParser.mjs'
|
|
@@ -57,6 +70,8 @@ export { PcbViaStackParser } from './core/altium/PcbViaStackParser.mjs'
|
|
|
57
70
|
export { PcbViaStructureParser } from './core/altium/PcbViaStructureParser.mjs'
|
|
58
71
|
export { PrintableTextDecoder } from './core/altium/PrintableTextDecoder.mjs'
|
|
59
72
|
export { PrjPcbModelParser } from './core/altium/PrjPcbModelParser.mjs'
|
|
73
|
+
export { PrjScrModelParser } from './core/altium/PrjScrModelParser.mjs'
|
|
74
|
+
export { ProjectBomPnpReconciliationBuilder } from './core/altium/ProjectBomPnpReconciliationBuilder.mjs'
|
|
60
75
|
export { SchematicMultipartOwnerMatcher } from './core/altium/SchematicMultipartOwnerMatcher.mjs'
|
|
61
76
|
export { SchematicCrossSheetConnectorParser } from './core/altium/SchematicCrossSheetConnectorParser.mjs'
|
|
62
77
|
export { SchematicHarnessParser } from './core/altium/SchematicHarnessParser.mjs'
|
|
@@ -14,6 +14,8 @@ export class PcbEdgeFacingGlyphNormalizer {
|
|
|
14
14
|
static #EDGE_GLYPH_CENTER_TOLERANCE = 1.5
|
|
15
15
|
static #EDGE_GLYPH_PROXIMITY_RATIO = 0.2
|
|
16
16
|
static #MARKER_PROXIMITY_MULTIPLIER = 3
|
|
17
|
+
static #SPATIAL_INDEX_MIN_CELL_SIZE = 32
|
|
18
|
+
static #SPATIAL_INDEX_MAX_CELLS_PER_ITEM = 256
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
21
|
* Normalizes repeated edge-facing documentation glyphs so their opening
|
|
@@ -107,6 +109,8 @@ export class PcbEdgeFacingGlyphNormalizer {
|
|
|
107
109
|
)
|
|
108
110
|
]
|
|
109
111
|
const visited = new Array(items.length).fill(false)
|
|
112
|
+
const spatialIndex =
|
|
113
|
+
PcbEdgeFacingGlyphNormalizer.#buildSpatialIndex(items)
|
|
110
114
|
const groups = []
|
|
111
115
|
|
|
112
116
|
for (let index = 0; index < items.length; index += 1) {
|
|
@@ -137,11 +141,10 @@ export class PcbEdgeFacingGlyphNormalizer {
|
|
|
137
141
|
arcIndexes.push(currentItem.arcIndex)
|
|
138
142
|
}
|
|
139
143
|
|
|
140
|
-
for (
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
) {
|
|
144
|
+
for (const nextIndex of PcbEdgeFacingGlyphNormalizer.#collectCandidateIndexes(
|
|
145
|
+
currentItem.bounds,
|
|
146
|
+
spatialIndex
|
|
147
|
+
)) {
|
|
145
148
|
if (visited[nextIndex]) {
|
|
146
149
|
continue
|
|
147
150
|
}
|
|
@@ -171,6 +174,153 @@ export class PcbEdgeFacingGlyphNormalizer {
|
|
|
171
174
|
return groups
|
|
172
175
|
}
|
|
173
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Builds a spatial lookup for candidate bounds intersections.
|
|
179
|
+
* @param {{ bounds: { minX: number, maxX: number, minY: number, maxY: number } }[]} items
|
|
180
|
+
* @returns {{ cellSize: number, cells: Map<string, number[]>, overflowIndexes: number[], marks: Uint32Array, mark: number }}
|
|
181
|
+
*/
|
|
182
|
+
static #buildSpatialIndex(items) {
|
|
183
|
+
const cellSize =
|
|
184
|
+
PcbEdgeFacingGlyphNormalizer.#resolveSpatialCellSize(items)
|
|
185
|
+
const cells = new Map()
|
|
186
|
+
const overflowIndexes = []
|
|
187
|
+
|
|
188
|
+
items.forEach((item, index) => {
|
|
189
|
+
const range = PcbEdgeFacingGlyphNormalizer.#resolveCellRange(
|
|
190
|
+
item.bounds,
|
|
191
|
+
cellSize
|
|
192
|
+
)
|
|
193
|
+
const cellCount =
|
|
194
|
+
(range.maxX - range.minX + 1) * (range.maxY - range.minY + 1)
|
|
195
|
+
|
|
196
|
+
if (
|
|
197
|
+
cellCount >
|
|
198
|
+
PcbEdgeFacingGlyphNormalizer.#SPATIAL_INDEX_MAX_CELLS_PER_ITEM
|
|
199
|
+
) {
|
|
200
|
+
overflowIndexes.push(index)
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
for (let cellX = range.minX; cellX <= range.maxX; cellX += 1) {
|
|
205
|
+
for (let cellY = range.minY; cellY <= range.maxY; cellY += 1) {
|
|
206
|
+
const key = PcbEdgeFacingGlyphNormalizer.#cellKey(
|
|
207
|
+
cellX,
|
|
208
|
+
cellY
|
|
209
|
+
)
|
|
210
|
+
const bucket = cells.get(key)
|
|
211
|
+
|
|
212
|
+
if (bucket) {
|
|
213
|
+
bucket.push(index)
|
|
214
|
+
} else {
|
|
215
|
+
cells.set(key, [index])
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
cellSize,
|
|
223
|
+
cells,
|
|
224
|
+
overflowIndexes,
|
|
225
|
+
marks: new Uint32Array(items.length),
|
|
226
|
+
mark: 0
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Collects unique candidate item indexes that could intersect one bounds box.
|
|
232
|
+
* @param {{ minX: number, maxX: number, minY: number, maxY: number }} bounds
|
|
233
|
+
* @param {{ cellSize: number, cells: Map<string, number[]>, overflowIndexes: number[], marks: Uint32Array, mark: number }} spatialIndex
|
|
234
|
+
* @returns {number[]}
|
|
235
|
+
*/
|
|
236
|
+
static #collectCandidateIndexes(bounds, spatialIndex) {
|
|
237
|
+
const candidates = []
|
|
238
|
+
const range = PcbEdgeFacingGlyphNormalizer.#resolveCellRange(
|
|
239
|
+
bounds,
|
|
240
|
+
spatialIndex.cellSize
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
spatialIndex.mark += 1
|
|
244
|
+
if (spatialIndex.mark >= 0xffffffff) {
|
|
245
|
+
spatialIndex.marks.fill(0)
|
|
246
|
+
spatialIndex.mark = 1
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const appendCandidate = (index) => {
|
|
250
|
+
if (spatialIndex.marks[index] === spatialIndex.mark) {
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
spatialIndex.marks[index] = spatialIndex.mark
|
|
255
|
+
candidates.push(index)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
for (let cellX = range.minX; cellX <= range.maxX; cellX += 1) {
|
|
259
|
+
for (let cellY = range.minY; cellY <= range.maxY; cellY += 1) {
|
|
260
|
+
const bucket = spatialIndex.cells.get(
|
|
261
|
+
PcbEdgeFacingGlyphNormalizer.#cellKey(cellX, cellY)
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
if (bucket) {
|
|
265
|
+
bucket.forEach(appendCandidate)
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
spatialIndex.overflowIndexes.forEach(appendCandidate)
|
|
271
|
+
return candidates
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Resolves a spatial cell size from typical primitive bounds spans.
|
|
276
|
+
* @param {{ bounds: { minX: number, maxX: number, minY: number, maxY: number } }[]} items
|
|
277
|
+
* @returns {number}
|
|
278
|
+
*/
|
|
279
|
+
static #resolveSpatialCellSize(items) {
|
|
280
|
+
const spans = items
|
|
281
|
+
.map((item) =>
|
|
282
|
+
Math.max(
|
|
283
|
+
Number(item.bounds.maxX) - Number(item.bounds.minX),
|
|
284
|
+
Number(item.bounds.maxY) - Number(item.bounds.minY),
|
|
285
|
+
0
|
|
286
|
+
)
|
|
287
|
+
)
|
|
288
|
+
.filter((span) => Number.isFinite(span))
|
|
289
|
+
.sort((left, right) => left - right)
|
|
290
|
+
const medianSpan = spans[Math.floor(spans.length / 2)] || 0
|
|
291
|
+
|
|
292
|
+
return Math.max(
|
|
293
|
+
medianSpan * 4,
|
|
294
|
+
PcbEdgeFacingGlyphNormalizer.#EDGE_GLYPH_CONNECTION_TOLERANCE * 8,
|
|
295
|
+
PcbEdgeFacingGlyphNormalizer.#SPATIAL_INDEX_MIN_CELL_SIZE
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Resolves the inclusive grid-cell range for one bounds box.
|
|
301
|
+
* @param {{ minX: number, maxX: number, minY: number, maxY: number }} bounds
|
|
302
|
+
* @param {number} cellSize
|
|
303
|
+
* @returns {{ minX: number, maxX: number, minY: number, maxY: number }}
|
|
304
|
+
*/
|
|
305
|
+
static #resolveCellRange(bounds, cellSize) {
|
|
306
|
+
return {
|
|
307
|
+
minX: Math.floor(Number(bounds.minX) / cellSize),
|
|
308
|
+
maxX: Math.floor(Number(bounds.maxX) / cellSize),
|
|
309
|
+
minY: Math.floor(Number(bounds.minY) / cellSize),
|
|
310
|
+
maxY: Math.floor(Number(bounds.maxY) / cellSize)
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Builds one deterministic spatial index key.
|
|
316
|
+
* @param {number} cellX
|
|
317
|
+
* @param {number} cellY
|
|
318
|
+
* @returns {string}
|
|
319
|
+
*/
|
|
320
|
+
static #cellKey(cellX, cellY) {
|
|
321
|
+
return `${cellX}:${cellY}`
|
|
322
|
+
}
|
|
323
|
+
|
|
174
324
|
/**
|
|
175
325
|
* Resolves whether one connected screw glyph needs its semicircular head
|
|
176
326
|
* moved onto the same side as the screw tip while keeping the authored
|
|
@@ -141,9 +141,21 @@ export class PcbFootprintPrimitiveSelector {
|
|
|
141
141
|
* @returns {boolean}
|
|
142
142
|
*/
|
|
143
143
|
static #includesLayerName(layerName, needle) {
|
|
144
|
+
return PcbFootprintPrimitiveSelector.#compactLayerName(
|
|
145
|
+
layerName
|
|
146
|
+
).includes(PcbFootprintPrimitiveSelector.#compactLayerName(needle))
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Normalizes layer labels so compact names such as TopOverlay match
|
|
151
|
+
* spaced Altium labels such as Top Overlay.
|
|
152
|
+
* @param {string} layerName
|
|
153
|
+
* @returns {string}
|
|
154
|
+
*/
|
|
155
|
+
static #compactLayerName(layerName) {
|
|
144
156
|
return String(layerName || '')
|
|
145
157
|
.trim()
|
|
146
158
|
.toUpperCase()
|
|
147
|
-
.
|
|
159
|
+
.replace(/[^A-Z0-9]/g, '')
|
|
148
160
|
}
|
|
149
161
|
}
|
|
@@ -986,16 +986,38 @@ export class PcbScene3dBuilder {
|
|
|
986
986
|
return new Set(
|
|
987
987
|
(Array.isArray(primitiveLayers) ? primitiveLayers : [])
|
|
988
988
|
.filter((layer) =>
|
|
989
|
-
|
|
990
|
-
.trim()
|
|
991
|
-
.toUpperCase()
|
|
992
|
-
.includes(needle)
|
|
989
|
+
PcbScene3dBuilder.#includesLayerName(layer?.name, needle)
|
|
993
990
|
)
|
|
994
991
|
.map((layer) => Number(layer.layerId))
|
|
995
992
|
.filter((layerId) => Number.isInteger(layerId))
|
|
996
993
|
)
|
|
997
994
|
}
|
|
998
995
|
|
|
996
|
+
/**
|
|
997
|
+
* Returns true when one layer name matches a spaced or compact target.
|
|
998
|
+
* @param {string} layerName
|
|
999
|
+
* @param {string} needle
|
|
1000
|
+
* @returns {boolean}
|
|
1001
|
+
*/
|
|
1002
|
+
static #includesLayerName(layerName, needle) {
|
|
1003
|
+
return PcbScene3dBuilder.#compactLayerName(layerName).includes(
|
|
1004
|
+
PcbScene3dBuilder.#compactLayerName(needle)
|
|
1005
|
+
)
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* Normalizes layer labels so compact names such as TopOverlay match
|
|
1010
|
+
* spaced Altium labels such as Top Overlay.
|
|
1011
|
+
* @param {string} layerName
|
|
1012
|
+
* @returns {string}
|
|
1013
|
+
*/
|
|
1014
|
+
static #compactLayerName(layerName) {
|
|
1015
|
+
return String(layerName || '')
|
|
1016
|
+
.trim()
|
|
1017
|
+
.toUpperCase()
|
|
1018
|
+
.replace(/[^A-Z0-9]/g, '')
|
|
1019
|
+
}
|
|
1020
|
+
|
|
999
1021
|
/**
|
|
1000
1022
|
* Normalizes one Altium overlay text into the runtime stroke-text shape.
|
|
1001
1023
|
* @param {{ text?: string, value?: string, x?: number, y?: number, height?: number, strokeWidth?: number, rotation?: number, mirrored?: boolean | number | string, isMirrored?: boolean | number | string, mirrorFlag?: boolean | number | string, Mirrored?: boolean | number | string, IsMirrored?: boolean | number | string, MirrorFlag?: boolean | number | string, layerId?: number }} text
|