pcb-scene3d-viewer 1.1.1 → 1.1.2

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.
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Normalizes and applies live 3D component transform adjustments.
3
+ */
4
+ export class PcbScene3dComponentAdjustment {
5
+ /**
6
+ * Finds transform adjustment targets beneath one scene object.
7
+ * @param {any} rootObject Root object.
8
+ * @returns {any[]}
9
+ */
10
+ static findTargets(rootObject) {
11
+ if (!rootObject) {
12
+ return []
13
+ }
14
+
15
+ const targets = rootObject?.userData?.scene3dAdjustmentTarget
16
+ ? [rootObject]
17
+ : []
18
+ ;(rootObject?.children || []).forEach((child) => {
19
+ targets.push(...PcbScene3dComponentAdjustment.findTargets(child))
20
+ })
21
+
22
+ return targets
23
+ }
24
+
25
+ /**
26
+ * Applies one live transform to a target relative to its original baseline.
27
+ * @param {any} THREE Three.js namespace.
28
+ * @param {any} target Target object.
29
+ * @param {{ scale?: { x?: number, y?: number, z?: number }, rotationDeg?: { x?: number, y?: number, z?: number }, offsetMil?: { x?: number, y?: number, z?: number } }} adjustment Adjustment.
30
+ * @returns {void}
31
+ */
32
+ static applyToTarget(THREE, target, adjustment) {
33
+ const current = PcbScene3dComponentAdjustment.normalize(adjustment)
34
+ const baseline = PcbScene3dComponentAdjustment.normalize(
35
+ target?.userData?.scene3dAdjustmentBaseline ||
36
+ PcbScene3dComponentAdjustment.neutral()
37
+ )
38
+ const delta = {
39
+ scale: {
40
+ x: PcbScene3dComponentAdjustment.#scaleRatio(
41
+ current.scale.x,
42
+ baseline.scale.x
43
+ ),
44
+ y: PcbScene3dComponentAdjustment.#scaleRatio(
45
+ current.scale.y,
46
+ baseline.scale.y
47
+ ),
48
+ z: PcbScene3dComponentAdjustment.#scaleRatio(
49
+ current.scale.z,
50
+ baseline.scale.z
51
+ )
52
+ },
53
+ rotationDeg: {
54
+ x: current.rotationDeg.x - baseline.rotationDeg.x,
55
+ y: current.rotationDeg.y - baseline.rotationDeg.y,
56
+ z: current.rotationDeg.z - baseline.rotationDeg.z
57
+ },
58
+ offsetMil: {
59
+ x: current.offsetMil.x - baseline.offsetMil.x,
60
+ y: current.offsetMil.y - baseline.offsetMil.y,
61
+ z: current.offsetMil.z - baseline.offsetMil.z
62
+ }
63
+ }
64
+
65
+ target?.position?.set?.(
66
+ delta.offsetMil.x,
67
+ delta.offsetMil.y,
68
+ delta.offsetMil.z
69
+ )
70
+ target?.scale?.set?.(delta.scale.x, delta.scale.y, delta.scale.z)
71
+ PcbScene3dComponentAdjustment.#applyRotation(THREE, target, delta)
72
+ }
73
+
74
+ /**
75
+ * Normalizes one adjustment object.
76
+ * @param {{ scale?: { x?: number, y?: number, z?: number }, rotationDeg?: { x?: number, y?: number, z?: number }, offsetMil?: { x?: number, y?: number, z?: number }, dxMil?: number, dyMil?: number, dzMil?: number } | null | undefined} adjustment Raw adjustment.
77
+ * @returns {{ scale: { x: number, y: number, z: number }, rotationDeg: { x: number, y: number, z: number }, offsetMil: { x: number, y: number, z: number } }}
78
+ */
79
+ static normalize(adjustment) {
80
+ const scale = adjustment?.scale || {}
81
+ const rotationDeg = adjustment?.rotationDeg || {}
82
+ const offsetMil = adjustment?.offsetMil || {}
83
+
84
+ return {
85
+ scale: {
86
+ x: PcbScene3dComponentAdjustment.#numberOr(scale.x, 1),
87
+ y: PcbScene3dComponentAdjustment.#numberOr(scale.y, 1),
88
+ z: PcbScene3dComponentAdjustment.#numberOr(scale.z, 1)
89
+ },
90
+ rotationDeg: {
91
+ x: PcbScene3dComponentAdjustment.#numberOr(rotationDeg.x, 0),
92
+ y: PcbScene3dComponentAdjustment.#numberOr(rotationDeg.y, 0),
93
+ z: PcbScene3dComponentAdjustment.#numberOr(rotationDeg.z, 0)
94
+ },
95
+ offsetMil: {
96
+ x: PcbScene3dComponentAdjustment.#numberOr(
97
+ offsetMil.x ?? adjustment?.dxMil,
98
+ 0
99
+ ),
100
+ y: PcbScene3dComponentAdjustment.#numberOr(
101
+ offsetMil.y ?? adjustment?.dyMil,
102
+ 0
103
+ ),
104
+ z: PcbScene3dComponentAdjustment.#numberOr(
105
+ offsetMil.z ?? adjustment?.dzMil,
106
+ 0
107
+ )
108
+ }
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Returns a neutral transform adjustment.
114
+ * @returns {{ scale: { x: number, y: number, z: number }, rotationDeg: { x: number, y: number, z: number }, offsetMil: { x: number, y: number, z: number } }}
115
+ */
116
+ static neutral() {
117
+ return {
118
+ scale: { x: 1, y: 1, z: 1 },
119
+ rotationDeg: { x: 0, y: 0, z: 0 },
120
+ offsetMil: { x: 0, y: 0, z: 0 }
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Applies KiCad-style model-local rotation order to an adjustment target.
126
+ * @param {any} THREE Three.js namespace.
127
+ * @param {any} target Target object.
128
+ * @param {{ rotationDeg: { x: number, y: number, z: number } }} adjustment Rotation adjustment.
129
+ * @returns {void}
130
+ */
131
+ static #applyRotation(THREE, target, adjustment) {
132
+ const x = (-Number(adjustment.rotationDeg.x || 0) * Math.PI) / 180
133
+ const y = (-Number(adjustment.rotationDeg.y || 0) * Math.PI) / 180
134
+ const z = (-Number(adjustment.rotationDeg.z || 0) * Math.PI) / 180
135
+
136
+ if (THREE?.Matrix4 && target?.quaternion?.setFromRotationMatrix) {
137
+ const rotationMatrix = new THREE.Matrix4()
138
+ .makeRotationZ(z)
139
+ .multiply(new THREE.Matrix4().makeRotationY(y))
140
+ .multiply(new THREE.Matrix4().makeRotationX(x))
141
+ target.quaternion.setFromRotationMatrix(rotationMatrix)
142
+ return
143
+ }
144
+
145
+ if (!target?.rotation) {
146
+ return
147
+ }
148
+
149
+ target.rotation.x = x
150
+ target.rotation.y = y
151
+ target.rotation.z = z
152
+ }
153
+
154
+ /**
155
+ * Returns a finite number or a fallback.
156
+ * @param {unknown} value Source value.
157
+ * @param {number} fallback Fallback value.
158
+ * @returns {number}
159
+ */
160
+ static #numberOr(value, fallback) {
161
+ const numericValue = Number(value)
162
+ return Number.isFinite(numericValue) ? numericValue : fallback
163
+ }
164
+
165
+ /**
166
+ * Computes a safe scale ratio.
167
+ * @param {number} value Current value.
168
+ * @param {number} baseline Baseline value.
169
+ * @returns {number}
170
+ */
171
+ static #scaleRatio(value, baseline) {
172
+ return baseline === 0 ? value : value / baseline
173
+ }
174
+ }
@@ -0,0 +1,95 @@
1
+ import { PcbScene3dComponentAdjustment } from './PcbScene3dComponentAdjustment.mjs'
2
+
3
+ /**
4
+ * Tracks live transform adjustments and applies them to registered targets.
5
+ */
6
+ export class PcbScene3dComponentAdjustmentRegistry {
7
+ /** @type {() => any} */
8
+ #resolveThree
9
+ /** @type {Map<string, Set<any>>} */
10
+ #targets
11
+ /** @type {Map<string, { scale: { x: number, y: number, z: number }, rotationDeg: { x: number, y: number, z: number }, offsetMil: { x: number, y: number, z: number } }>} */
12
+ #adjustments
13
+
14
+ /**
15
+ * @param {() => any} resolveThree Three.js namespace resolver.
16
+ */
17
+ constructor(resolveThree) {
18
+ this.#resolveThree = resolveThree
19
+ this.#targets = new Map()
20
+ this.#adjustments = new Map()
21
+ }
22
+
23
+ /** @returns {void} */
24
+ clear() {
25
+ this.#targets.clear()
26
+ this.#adjustments.clear()
27
+ }
28
+
29
+ /**
30
+ * Applies a live model-local adjustment to one component.
31
+ * @param {string} designator Component designator.
32
+ * @param {{ scale?: { x?: number, y?: number, z?: number }, rotationDeg?: { x?: number, y?: number, z?: number }, offsetMil?: { x?: number, y?: number, z?: number } }} adjustment Transform adjustment.
33
+ * @returns {boolean}
34
+ */
35
+ set(designator, adjustment) {
36
+ const normalizedDesignator = String(designator || '').trim()
37
+ if (!normalizedDesignator) {
38
+ return false
39
+ }
40
+
41
+ const normalizedAdjustment =
42
+ PcbScene3dComponentAdjustment.normalize(adjustment)
43
+ this.#adjustments.set(normalizedDesignator, normalizedAdjustment)
44
+ this.#apply(normalizedDesignator, normalizedAdjustment)
45
+ return true
46
+ }
47
+
48
+ /**
49
+ * Registers one model-local transform adjustment target.
50
+ * @param {string | undefined} designator Component designator.
51
+ * @param {any} target Target object.
52
+ * @returns {void}
53
+ */
54
+ register(designator, target) {
55
+ const normalizedDesignator = String(designator || '').trim()
56
+ if (!normalizedDesignator || !target) {
57
+ return
58
+ }
59
+
60
+ if (!this.#targets.has(normalizedDesignator)) {
61
+ this.#targets.set(normalizedDesignator, new Set())
62
+ }
63
+
64
+ this.#targets.get(normalizedDesignator)?.add(target)
65
+ const pendingAdjustment = this.#adjustments.get(normalizedDesignator)
66
+ if (pendingAdjustment) {
67
+ PcbScene3dComponentAdjustment.applyToTarget(
68
+ this.#resolveThree(),
69
+ target,
70
+ pendingAdjustment
71
+ )
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Applies one adjustment to all known targets for a component.
77
+ * @param {string} designator Component designator.
78
+ * @param {{ scale: { x: number, y: number, z: number }, rotationDeg: { x: number, y: number, z: number }, offsetMil: { x: number, y: number, z: number } }} adjustment Adjustment.
79
+ * @returns {void}
80
+ */
81
+ #apply(designator, adjustment) {
82
+ const targets = this.#targets.get(designator)
83
+ if (!targets) {
84
+ return
85
+ }
86
+
87
+ targets.forEach((target) => {
88
+ PcbScene3dComponentAdjustment.applyToTarget(
89
+ this.#resolveThree(),
90
+ target,
91
+ adjustment
92
+ )
93
+ })
94
+ }
95
+ }