pcb-scene3d-viewer 1.1.0 → 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.
- package/docs/api.md +10 -0
- package/package.json +2 -2
- package/src/PcbScene3dAdjustmentControlBinder.mjs +667 -0
- package/src/PcbScene3dBoardShapeFactory.mjs +1 -1
- package/src/PcbScene3dBoardSolderMaskFactory.mjs +1 -1
- package/src/PcbScene3dCircularCutoutOverlap.mjs +167 -0
- package/src/PcbScene3dComponentAdjustment.mjs +174 -0
- package/src/PcbScene3dComponentAdjustmentRegistry.mjs +95 -0
- package/src/PcbScene3dController.mjs +214 -197
- package/src/PcbScene3dCopperFactory.mjs +4 -1
- package/src/PcbScene3dCutoutGeometryFilter.mjs +18 -3
- package/src/PcbScene3dExternalModels.mjs +56 -71
- package/src/PcbScene3dGeometryBoundsResolver.mjs +78 -0
- package/src/PcbScene3dModelBounds.mjs +267 -0
- package/src/PcbScene3dModelSearchPlacement.mjs +40 -0
- package/src/PcbScene3dPadFactory.mjs +221 -20
- package/src/PcbScene3dRenderGroupVisibility.mjs +23 -1
- package/src/PcbScene3dRuntime.mjs +62 -51
- package/src/PcbScene3dSelectionInspectorRenderer.mjs +581 -0
- package/src/PcbScene3dSelectionResolver.mjs +42 -0
- package/src/PcbScene3dSilkscreenFactory.mjs +4 -1
- package/src/PcbScene3dText.mjs +3 -0
- package/src/styles/scene3d.css +142 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { PcbScene3dCutoutCircleDetector } from './PcbScene3dCutoutCircleDetector.mjs'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolves analytic overlap checks for sampled circular drill cutouts.
|
|
5
|
+
*/
|
|
6
|
+
export class PcbScene3dCircularCutoutOverlap {
|
|
7
|
+
/**
|
|
8
|
+
* Returns true when one triangle overlaps a circular cutout.
|
|
9
|
+
* @param {{ x: number, y: number }[]} triangle
|
|
10
|
+
* @param {{ points?: { x: number, y: number }[], centerX?: number, centerY?: number, radius?: number }} cutout
|
|
11
|
+
* @param {number} [epsilon]
|
|
12
|
+
* @returns {boolean}
|
|
13
|
+
*/
|
|
14
|
+
static overlapsTriangle(triangle, cutout, epsilon = 0.001) {
|
|
15
|
+
const radius = Number(cutout?.radius || 0) + Number(epsilon || 0)
|
|
16
|
+
if (!Number.isFinite(radius) || radius <= 0) {
|
|
17
|
+
return false
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const center = {
|
|
21
|
+
x: Number(cutout?.centerX || 0),
|
|
22
|
+
y: Number(cutout?.centerY || 0)
|
|
23
|
+
}
|
|
24
|
+
const radiusSquared = radius * radius
|
|
25
|
+
|
|
26
|
+
for (const point of triangle) {
|
|
27
|
+
if (
|
|
28
|
+
PcbScene3dCutoutCircleDetector.distanceSquared(point, cutout) <=
|
|
29
|
+
radiusSquared
|
|
30
|
+
) {
|
|
31
|
+
return true
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (
|
|
36
|
+
PcbScene3dCircularCutoutOverlap.#isPointInsideOrOnTriangle(
|
|
37
|
+
center,
|
|
38
|
+
triangle,
|
|
39
|
+
epsilon
|
|
40
|
+
)
|
|
41
|
+
) {
|
|
42
|
+
return true
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (let index = 0; index < triangle.length; index += 1) {
|
|
46
|
+
const current = triangle[index]
|
|
47
|
+
const next = triangle[(index + 1) % triangle.length]
|
|
48
|
+
|
|
49
|
+
if (
|
|
50
|
+
PcbScene3dCircularCutoutOverlap.#distanceToSegmentSquared(
|
|
51
|
+
center,
|
|
52
|
+
current,
|
|
53
|
+
next,
|
|
54
|
+
epsilon
|
|
55
|
+
) <= radiusSquared
|
|
56
|
+
) {
|
|
57
|
+
return true
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return PcbScene3dCircularCutoutOverlap.#hasSampledBoundaryOverlap(
|
|
62
|
+
triangle,
|
|
63
|
+
cutout,
|
|
64
|
+
epsilon
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Returns true when a sampled cutout boundary point lies in the triangle.
|
|
70
|
+
* @param {{ x: number, y: number }[]} triangle
|
|
71
|
+
* @param {{ points?: { x: number, y: number }[] }} cutout
|
|
72
|
+
* @param {number} epsilon
|
|
73
|
+
* @returns {boolean}
|
|
74
|
+
*/
|
|
75
|
+
static #hasSampledBoundaryOverlap(triangle, cutout, epsilon) {
|
|
76
|
+
const boundaryPoints = Array.isArray(cutout?.points)
|
|
77
|
+
? cutout.points
|
|
78
|
+
: []
|
|
79
|
+
const step = Math.max(1, Math.floor(boundaryPoints.length / 8))
|
|
80
|
+
|
|
81
|
+
for (let index = 0; index < boundaryPoints.length; index += step) {
|
|
82
|
+
if (
|
|
83
|
+
PcbScene3dCircularCutoutOverlap.#isPointInsideOrOnTriangle(
|
|
84
|
+
boundaryPoints[index],
|
|
85
|
+
triangle,
|
|
86
|
+
epsilon
|
|
87
|
+
)
|
|
88
|
+
) {
|
|
89
|
+
return true
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Returns true when a point is inside or on one triangle.
|
|
98
|
+
* @param {{ x: number, y: number }} point
|
|
99
|
+
* @param {{ x: number, y: number }[]} triangle
|
|
100
|
+
* @param {number} epsilon
|
|
101
|
+
* @returns {boolean}
|
|
102
|
+
*/
|
|
103
|
+
static #isPointInsideOrOnTriangle(point, triangle, epsilon) {
|
|
104
|
+
let hasNegative = false
|
|
105
|
+
let hasPositive = false
|
|
106
|
+
|
|
107
|
+
for (let index = 0; index < triangle.length; index += 1) {
|
|
108
|
+
const current = triangle[index]
|
|
109
|
+
const next = triangle[(index + 1) % triangle.length]
|
|
110
|
+
const sign = PcbScene3dCircularCutoutOverlap.#cross(
|
|
111
|
+
point,
|
|
112
|
+
current,
|
|
113
|
+
next
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
hasNegative = hasNegative || sign < -epsilon
|
|
117
|
+
hasPositive = hasPositive || sign > epsilon
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return !(hasNegative && hasPositive)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Resolves the squared distance from one point to a finite segment.
|
|
125
|
+
* @param {{ x: number, y: number }} point
|
|
126
|
+
* @param {{ x: number, y: number }} start
|
|
127
|
+
* @param {{ x: number, y: number }} end
|
|
128
|
+
* @param {number} epsilon
|
|
129
|
+
* @returns {number}
|
|
130
|
+
*/
|
|
131
|
+
static #distanceToSegmentSquared(point, start, end, epsilon) {
|
|
132
|
+
const dx = end.x - start.x
|
|
133
|
+
const dy = end.y - start.y
|
|
134
|
+
const lengthSquared = dx * dx + dy * dy
|
|
135
|
+
|
|
136
|
+
if (lengthSquared <= epsilon) {
|
|
137
|
+
return (point.x - start.x) ** 2 + (point.y - start.y) ** 2
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const ratio = Math.max(
|
|
141
|
+
0,
|
|
142
|
+
Math.min(
|
|
143
|
+
1,
|
|
144
|
+
((point.x - start.x) * dx + (point.y - start.y) * dy) /
|
|
145
|
+
lengthSquared
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
const projectedX = start.x + ratio * dx
|
|
149
|
+
const projectedY = start.y + ratio * dy
|
|
150
|
+
|
|
151
|
+
return (point.x - projectedX) ** 2 + (point.y - projectedY) ** 2
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Resolves the signed area for three points.
|
|
156
|
+
* @param {{ x: number, y: number }} first
|
|
157
|
+
* @param {{ x: number, y: number }} second
|
|
158
|
+
* @param {{ x: number, y: number }} third
|
|
159
|
+
* @returns {number}
|
|
160
|
+
*/
|
|
161
|
+
static #cross(first, second, third) {
|
|
162
|
+
return (
|
|
163
|
+
(second.x - first.x) * (third.y - first.y) -
|
|
164
|
+
(second.y - first.y) * (third.x - first.x)
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -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
|
+
}
|