altium-toolkit 1.4.15 → 1.4.16
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/README.md
CHANGED
|
@@ -57,6 +57,10 @@ extension graphs retain their proven ownership instead of repeating defensive
|
|
|
57
57
|
binary classification. See the
|
|
58
58
|
[1.4.2 release notes](docs/release-notes-v1.4.2.md).
|
|
59
59
|
|
|
60
|
+
Version 1.4.16 preserves validated authored STEP anchors during late geometric
|
|
61
|
+
owner recovery and keeps board-space component yaw unchanged. See the
|
|
62
|
+
[1.4.16 release notes](docs/release-notes-v1.4.16.md).
|
|
63
|
+
|
|
60
64
|
Default `extensions: 'canonical'` keeps compact Altium summary metadata.
|
|
61
65
|
Request the complete native read model with `extensions: 'full'`,
|
|
62
66
|
`preserveRaw: true`, or `extensions: ['altium.native-model']`. Project batches
|
|
@@ -224,6 +228,7 @@ const legacyCircuitJson = AltiumParser.parseArrayBuffer(file.name, arrayBuffer)
|
|
|
224
228
|
- [API](docs/api.md)
|
|
225
229
|
- [Capabilities](docs/capabilities.md)
|
|
226
230
|
- [Migration from 1.1.41](docs/migration.md)
|
|
231
|
+
- [1.4.16 release notes](docs/release-notes-v1.4.16.md)
|
|
227
232
|
- [1.4.2 release notes](docs/release-notes-v1.4.2.md)
|
|
228
233
|
- [1.4.1 release notes](docs/release-notes-v1.4.1.md)
|
|
229
234
|
- [1.4.0 release notes](docs/release-notes-v1.4.0.md)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# altium-toolkit 1.4.16
|
|
2
|
+
|
|
3
|
+
Version 1.4.16 preserves authored STEP anchors during late geometric owner
|
|
4
|
+
recovery and leaves model-local orientation normalization to the 3D runtime.
|
|
5
|
+
|
|
6
|
+
## 3D placement fidelity
|
|
7
|
+
|
|
8
|
+
- Near-centroid surface-mount bodies retain their authored positions when the
|
|
9
|
+
recovered owner is geometrically unambiguous.
|
|
10
|
+
- Height-backed owner recovery marks valid source anchors explicitly instead
|
|
11
|
+
of converting them into component-center offsets.
|
|
12
|
+
- Geometric owner recovery preserves the component's authored board-space yaw;
|
|
13
|
+
it no longer infers package-specific half-turns from labels or pad topology.
|
|
14
|
+
|
|
15
|
+
## Compatibility
|
|
16
|
+
|
|
17
|
+
- Placement recovery remains based on generic body, pad, and footprint
|
|
18
|
+
geometry without project names, filenames, designators, or library strings.
|
|
19
|
+
- Existing schematic colors, canvas borders, and public renderer signatures
|
|
20
|
+
remain unchanged.
|
|
21
|
+
|
|
22
|
+
## Verification
|
|
23
|
+
|
|
24
|
+
- Repository-owned synthetic tests cover near-centroid connectors,
|
|
25
|
+
height-backed authored anchors, and preserved switch yaw.
|
|
26
|
+
- The complete package suite, formatting check, and npm package dry run are
|
|
27
|
+
required for release.
|
package/package.json
CHANGED
|
@@ -18,23 +18,23 @@ vendor names, or library identifiers.
|
|
|
18
18
|
span.
|
|
19
19
|
- Use component height agreement only as supporting evidence for nearby
|
|
20
20
|
package ownership; height alone is insufficient.
|
|
21
|
-
- Preserve
|
|
22
|
-
|
|
21
|
+
- Preserve pad-centroid and nearby height-backed body positions because they
|
|
22
|
+
are valid authored source anchors, and mark height-backed anchors so the
|
|
23
|
+
runtime does not apply a second source-origin adjustment.
|
|
23
24
|
- Center proven corner-origin and row-origin bodies on the component owner and
|
|
24
25
|
retain the source offset as transform diagnostics.
|
|
25
26
|
- Recompute component side and authored vertical standoff after late owner
|
|
26
27
|
recovery.
|
|
27
|
-
-
|
|
28
|
-
|
|
29
|
-
class.
|
|
28
|
+
- Preserve authored component yaw; model-local axis normalization belongs to
|
|
29
|
+
the runtime that has access to the imported mesh envelope.
|
|
30
30
|
- Decline ambiguous matches.
|
|
31
31
|
|
|
32
32
|
## Verification
|
|
33
33
|
|
|
34
|
-
- Focused synthetic tests use obfuscated package identities and cover
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
- Focused synthetic tests use obfuscated package identities and cover exact and
|
|
35
|
+
near pad centroids, corner origin, row origin, authored source anchors, late
|
|
36
|
+
standoff, ambiguity rejection, and preserved component yaw.
|
|
37
37
|
- The full toolkit test suite and formatting check pass.
|
|
38
38
|
- A read-only probe of the supplied PCB must show real component designators
|
|
39
|
-
for the affected placements, owner-centered corner/row models,
|
|
40
|
-
top-side standoff, and
|
|
39
|
+
for the affected placements, owner-centered corner/row models, preserved
|
|
40
|
+
authored anchors, recovered top-side standoff, and source-authored yaw.
|
|
@@ -13,6 +13,7 @@ export class AltiumScene3dGeometricOwnerRecovery {
|
|
|
13
13
|
static #COMPONENT_INDEX_CELL_MIL = 500
|
|
14
14
|
static #MINIMUM_SCORE = 12
|
|
15
15
|
static #MINIMUM_SCORE_MARGIN = 4
|
|
16
|
+
static #PAD_CENTROID_TOLERANCE_MIL = 12
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* Applies geometry-backed owner recovery to final external placements.
|
|
@@ -108,14 +109,7 @@ export class AltiumScene3dGeometricOwnerRecovery {
|
|
|
108
109
|
const currentOwner = context.componentsByDesignator.get(
|
|
109
110
|
String(placement?.designator || '')
|
|
110
111
|
)
|
|
111
|
-
if (currentOwner)
|
|
112
|
-
return AltiumScene3dGeometricOwnerRecovery.#correctTactileYaw(
|
|
113
|
-
placement,
|
|
114
|
-
currentOwner,
|
|
115
|
-
body,
|
|
116
|
-
context.geometryByComponent.get(currentOwner)
|
|
117
|
-
)
|
|
118
|
-
}
|
|
112
|
+
if (currentOwner) return placement
|
|
119
113
|
|
|
120
114
|
const match =
|
|
121
115
|
AltiumScene3dGeometricOwnerRecovery.#resolveGeometricOwner(
|
|
@@ -125,18 +119,12 @@ export class AltiumScene3dGeometricOwnerRecovery {
|
|
|
125
119
|
)
|
|
126
120
|
if (!match) return placement
|
|
127
121
|
|
|
128
|
-
|
|
122
|
+
return AltiumScene3dGeometricOwnerRecovery.#withOwner(
|
|
129
123
|
placement,
|
|
130
124
|
body,
|
|
131
125
|
match,
|
|
132
126
|
context.board
|
|
133
127
|
)
|
|
134
|
-
return AltiumScene3dGeometricOwnerRecovery.#correctTactileYaw(
|
|
135
|
-
recovered,
|
|
136
|
-
match.component,
|
|
137
|
-
body,
|
|
138
|
-
context.geometryByComponent.get(match.component)
|
|
139
|
-
)
|
|
140
128
|
}
|
|
141
129
|
|
|
142
130
|
/**
|
|
@@ -329,7 +317,7 @@ export class AltiumScene3dGeometricOwnerRecovery {
|
|
|
329
317
|
|
|
330
318
|
if (
|
|
331
319
|
centroidDistance <=
|
|
332
|
-
AltiumScene3dGeometricOwnerRecovery.#
|
|
320
|
+
AltiumScene3dGeometricOwnerRecovery.#PAD_CENTROID_TOLERANCE_MIL
|
|
333
321
|
) {
|
|
334
322
|
score = 24
|
|
335
323
|
mode = 'pad-centroid'
|
|
@@ -570,7 +558,9 @@ export class AltiumScene3dGeometricOwnerRecovery {
|
|
|
570
558
|
const component = match.component
|
|
571
559
|
const mountSide =
|
|
572
560
|
AltiumScene3dGeometricOwnerRecovery.#componentSide(component)
|
|
573
|
-
const preserveAnchor =
|
|
561
|
+
const preserveAnchor =
|
|
562
|
+
match.mode === 'pad-centroid' ||
|
|
563
|
+
match.mode === 'height-backed-origin'
|
|
574
564
|
const offset = {
|
|
575
565
|
x:
|
|
576
566
|
Number(placement?.bodyPositionMil?.x || 0) -
|
|
@@ -590,6 +580,10 @@ export class AltiumScene3dGeometricOwnerRecovery {
|
|
|
590
580
|
dzMil: verticalOffset
|
|
591
581
|
}
|
|
592
582
|
|
|
583
|
+
if (match.mode === 'height-backed-origin') {
|
|
584
|
+
modelTransform.preserveSourceAnchor = true
|
|
585
|
+
}
|
|
586
|
+
|
|
593
587
|
if (!preserveAnchor) {
|
|
594
588
|
modelTransform.ownerAnchorOffsetMil = offset
|
|
595
589
|
modelTransform.offsetMil = { x: 0, y: 0, z: verticalOffset }
|
|
@@ -622,107 +616,6 @@ export class AltiumScene3dGeometricOwnerRecovery {
|
|
|
622
616
|
}
|
|
623
617
|
}
|
|
624
618
|
|
|
625
|
-
/**
|
|
626
|
-
* Corrects the source-frame half-turn for a four-pad tactile switch.
|
|
627
|
-
* @param {object} placement External placement.
|
|
628
|
-
* @param {object} component Resolved owner.
|
|
629
|
-
* @param {object} body Source body.
|
|
630
|
-
* @param {object | null} geometry Precomputed owner pad geometry.
|
|
631
|
-
* @returns {object}
|
|
632
|
-
*/
|
|
633
|
-
static #correctTactileYaw(placement, component, body, geometry) {
|
|
634
|
-
const identity = [
|
|
635
|
-
component?.designator,
|
|
636
|
-
component?.description,
|
|
637
|
-
component?.provenance?.footprintDescription
|
|
638
|
-
]
|
|
639
|
-
.map((value) => String(value || ''))
|
|
640
|
-
.join(' ')
|
|
641
|
-
const sourceTilt = AltiumScene3dGeometricOwnerRecovery.#normalizeAngle(
|
|
642
|
-
body?.modelRotationDeg?.x
|
|
643
|
-
)
|
|
644
|
-
const currentYaw = AltiumScene3dGeometricOwnerRecovery.#normalizeAngle(
|
|
645
|
-
placement?.rotationDeg
|
|
646
|
-
)
|
|
647
|
-
const componentYaw =
|
|
648
|
-
AltiumScene3dGeometricOwnerRecovery.#normalizeAngle(
|
|
649
|
-
component?.rotation
|
|
650
|
-
)
|
|
651
|
-
const isTactileSwitch =
|
|
652
|
-
/(?:^|[^a-z0-9])(?:tact|tactile|pushbutton)(?:$|[^a-z0-9])/i.test(
|
|
653
|
-
identity
|
|
654
|
-
) &&
|
|
655
|
-
AltiumScene3dGeometricOwnerRecovery.#hasTactileContactTopology(
|
|
656
|
-
geometry
|
|
657
|
-
)
|
|
658
|
-
|
|
659
|
-
if (
|
|
660
|
-
!isTactileSwitch ||
|
|
661
|
-
(sourceTilt !== 90 && sourceTilt !== 270) ||
|
|
662
|
-
currentYaw !== componentYaw
|
|
663
|
-
) {
|
|
664
|
-
return placement
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
return {
|
|
668
|
-
...placement,
|
|
669
|
-
rotationDeg: AltiumScene3dGeometricOwnerRecovery.#normalizeAngle(
|
|
670
|
-
currentYaw + 180
|
|
671
|
-
)
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
/**
|
|
676
|
-
* Detects a two-by-two tactile contact layout with two duplicated routed
|
|
677
|
-
* contact pairs aligned along one footprint axis.
|
|
678
|
-
* @param {object | null} geometry Precomputed owner pad geometry.
|
|
679
|
-
* @returns {boolean}
|
|
680
|
-
*/
|
|
681
|
-
static #hasTactileContactTopology(geometry) {
|
|
682
|
-
if (
|
|
683
|
-
geometry?.pads?.length !== 4 ||
|
|
684
|
-
geometry.xCount !== 2 ||
|
|
685
|
-
geometry.yCount !== 2
|
|
686
|
-
) {
|
|
687
|
-
return false
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
const groups = new Map()
|
|
691
|
-
for (const pad of geometry.localPads) {
|
|
692
|
-
const sourcePad = pad.source
|
|
693
|
-
const netName = String(sourcePad?.netName || '').trim()
|
|
694
|
-
const netIndex = sourcePad?.netIndex
|
|
695
|
-
const contactKey = netName
|
|
696
|
-
? `name:${netName}`
|
|
697
|
-
: netIndex !== null &&
|
|
698
|
-
netIndex !== undefined &&
|
|
699
|
-
netIndex !== '' &&
|
|
700
|
-
Number.isFinite(Number(netIndex))
|
|
701
|
-
? `index:${Number(netIndex)}`
|
|
702
|
-
: ''
|
|
703
|
-
if (!contactKey) return false
|
|
704
|
-
groups.set(contactKey, [...(groups.get(contactKey) || []), pad])
|
|
705
|
-
}
|
|
706
|
-
if (
|
|
707
|
-
groups.size !== 2 ||
|
|
708
|
-
[...groups.values()].some((group) => group.length !== 2)
|
|
709
|
-
) {
|
|
710
|
-
return false
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
return [...groups.values()].every((group) => {
|
|
714
|
-
const sameX =
|
|
715
|
-
AltiumScene3dGeometricOwnerRecovery.#distinctCoordinateCount(
|
|
716
|
-
group.map((pad) => Number(pad?.x || 0))
|
|
717
|
-
) === 1
|
|
718
|
-
const sameY =
|
|
719
|
-
AltiumScene3dGeometricOwnerRecovery.#distinctCoordinateCount(
|
|
720
|
-
group.map((pad) => Number(pad?.y || 0))
|
|
721
|
-
) === 1
|
|
722
|
-
return sameX || sameY
|
|
723
|
-
})
|
|
724
|
-
}
|
|
725
|
-
|
|
726
619
|
/**
|
|
727
620
|
* Resolves a body vertical offset after a late owner recovery.
|
|
728
621
|
* @param {object} body Source body.
|