pcb-scene3d-viewer 1.0.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.
Files changed (65) hide show
  1. package/AGENTS.md +67 -0
  2. package/COMMERCIAL-LICENSE.md +15 -0
  3. package/CONTRIBUTING.md +14 -0
  4. package/LICENSE +19 -0
  5. package/LICENSES/AGPL-3.0-or-later.txt +235 -0
  6. package/LICENSES/CC-BY-SA-4.0.txt +170 -0
  7. package/LICENSES/LGPL-2.1-or-later.txt +176 -0
  8. package/LICENSES/LicenseRef-PolyForm-Noncommercial-1.0.0.txt +131 -0
  9. package/NOTICE.md +36 -0
  10. package/README.md +128 -0
  11. package/REUSE.toml +16 -0
  12. package/docs/api.md +148 -0
  13. package/docs/circuitjson.md +190 -0
  14. package/docs/model-format.md +117 -0
  15. package/docs/testing.md +23 -0
  16. package/package.json +65 -0
  17. package/spec/library-scope.md +36 -0
  18. package/src/PcbModelArchiveExporter.mjs +320 -0
  19. package/src/PcbScene3dArcUtils.mjs +27 -0
  20. package/src/PcbScene3dBoardAssemblyPlacement.mjs +36 -0
  21. package/src/PcbScene3dBoardAssemblyPresentation.mjs +859 -0
  22. package/src/PcbScene3dBoardEdgeCutoutBuilder.mjs +537 -0
  23. package/src/PcbScene3dBoardMaterialPalette.mjs +40 -0
  24. package/src/PcbScene3dBoardShapeFactory.mjs +895 -0
  25. package/src/PcbScene3dBoardSolderMaskFactory.mjs +613 -0
  26. package/src/PcbScene3dCameraRig.mjs +168 -0
  27. package/src/PcbScene3dCircuitJsonAdapter.mjs +545 -0
  28. package/src/PcbScene3dController.mjs +956 -0
  29. package/src/PcbScene3dCopperDetailFilter.mjs +490 -0
  30. package/src/PcbScene3dCopperFactory.mjs +559 -0
  31. package/src/PcbScene3dCopperTextFactory.mjs +534 -0
  32. package/src/PcbScene3dCutoutGeometryFilter.mjs +873 -0
  33. package/src/PcbScene3dDetailCoordinateNormalizer.mjs +65 -0
  34. package/src/PcbScene3dDrillCutoutFilter.mjs +224 -0
  35. package/src/PcbScene3dDrillPathFactory.mjs +362 -0
  36. package/src/PcbScene3dDrillVoidFactory.mjs +268 -0
  37. package/src/PcbScene3dExternalModelLoadOrder.mjs +54 -0
  38. package/src/PcbScene3dExternalModels.mjs +968 -0
  39. package/src/PcbScene3dFallbackVisibility.mjs +82 -0
  40. package/src/PcbScene3dInteractionHints.mjs +56 -0
  41. package/src/PcbScene3dMountRig.mjs +53 -0
  42. package/src/PcbScene3dOutlineBuilder.mjs +210 -0
  43. package/src/PcbScene3dPadFactory.mjs +553 -0
  44. package/src/PcbScene3dPresetState.mjs +48 -0
  45. package/src/PcbScene3dRenderGroupVisibility.mjs +134 -0
  46. package/src/PcbScene3dRuntime.mjs +996 -0
  47. package/src/PcbScene3dRuntimeBoardMeshes.mjs +99 -0
  48. package/src/PcbScene3dSelectionStyler.mjs +252 -0
  49. package/src/PcbScene3dShapePathFactory.mjs +220 -0
  50. package/src/PcbScene3dShellRenderer.mjs +131 -0
  51. package/src/PcbScene3dSilkscreenFactory.mjs +854 -0
  52. package/src/PcbScene3dSilkscreenStrokeWidthResolver.mjs +81 -0
  53. package/src/PcbScene3dStepLoader.mjs +611 -0
  54. package/src/PcbScene3dStrokeFont.mjs +671 -0
  55. package/src/PcbScene3dStrokeGeometryBuilder.mjs +322 -0
  56. package/src/PcbScene3dText.mjs +99 -0
  57. package/src/PcbScene3dTrueTypeTextFactory.mjs +885 -0
  58. package/src/PcbScene3dViaFactory.mjs +176 -0
  59. package/src/PcbScene3dViewCompensation.mjs +109 -0
  60. package/src/PcbScene3dViewScale.mjs +24 -0
  61. package/src/PcbScene3dViewportResize.mjs +35 -0
  62. package/src/PcbScene3dWorkerClient.mjs +123 -0
  63. package/src/index.mjs +1 -0
  64. package/src/scene3d.mjs +44 -0
  65. package/src/styles/scene3d.css +295 -0
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Resolves camera presets and basic rig state for the PCB 3D scene.
3
+ */
4
+ export class PcbScene3dCameraRig {
5
+ /**
6
+ * Resolves the initial camera radius from the board size.
7
+ * @param {{ board?: { widthMil?: number, heightMil?: number }, sourceFormat?: string }} sceneDescription
8
+ * @returns {number}
9
+ */
10
+ static resolveInitialRadius(sceneDescription) {
11
+ const width = Number(sceneDescription?.board?.widthMil || 0)
12
+ const height = Number(sceneDescription?.board?.heightMil || 0)
13
+ return Math.max(width, height, 800) * 1.9
14
+ }
15
+
16
+ /**
17
+ * Resolves one named camera preset into a z-up camera pose.
18
+ * @param {string} preset
19
+ * @param {{ board?: { widthMil?: number, heightMil?: number }, sourceFormat?: string }} sceneDescription
20
+ * @param {{ radius?: number, target?: { x?: number, y?: number, z?: number } }} [options]
21
+ * @returns {{ radius: number, target: { x: number, y: number, z: number }, up: { x: number, y: number, z: number }, position: { x: number, y: number, z: number } }}
22
+ */
23
+ static resolvePreset(preset, sceneDescription, options = {}) {
24
+ const normalizedPreset = String(preset || 'isometric').toLowerCase()
25
+ const radius =
26
+ Number(options.radius) ||
27
+ PcbScene3dCameraRig.resolveInitialRadius(sceneDescription)
28
+ const target = {
29
+ x: Number(options.target?.x || 0),
30
+ y: Number(options.target?.y || 0),
31
+ z: Number(options.target?.z || 0)
32
+ }
33
+
34
+ if (normalizedPreset === 'top') {
35
+ return {
36
+ radius,
37
+ target,
38
+ up: { x: 0, y: 1, z: 0 },
39
+ position: {
40
+ x: target.x,
41
+ y: target.y,
42
+ z: target.z + radius
43
+ }
44
+ }
45
+ }
46
+
47
+ if (normalizedPreset === 'bottom') {
48
+ return {
49
+ radius,
50
+ target,
51
+ up: { x: 0, y: -1, z: 0 },
52
+ position: {
53
+ x: target.x,
54
+ y: target.y,
55
+ z: target.z - radius
56
+ }
57
+ }
58
+ }
59
+
60
+ const theta = -Math.PI / 4
61
+ const phi = Math.PI / 3.3
62
+
63
+ return {
64
+ radius,
65
+ target,
66
+ up: { x: 0, y: 0, z: 1 },
67
+ position: PcbScene3dCameraRig.#resolvePosition(
68
+ theta,
69
+ phi,
70
+ radius,
71
+ target
72
+ )
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Applies one preset to a Three camera and optional OrbitControls instance.
78
+ * @param {{ position?: { set?: (x: number, y: number, z: number) => void }, up?: { set?: (x: number, y: number, z: number) => void }, lookAt?: (x: number, y: number, z: number) => void, updateProjectionMatrix?: () => void }} camera
79
+ * @param {{ target?: { x?: number, y?: number, z?: number, set?: (x: number, y: number, z: number) => void }, update?: () => void }} [controls]
80
+ * @param {string} preset
81
+ * @param {{ board?: { widthMil?: number, heightMil?: number }, sourceFormat?: string }} sceneDescription
82
+ * @returns {{ radius: number, target: { x: number, y: number, z: number }, up: { x: number, y: number, z: number }, position: { x: number, y: number, z: number } }}
83
+ */
84
+ static applyPreset(camera, controls, preset, sceneDescription) {
85
+ const normalizedPreset = String(preset || 'isometric').toLowerCase()
86
+ const resetsView =
87
+ normalizedPreset === 'isometric' || normalizedPreset === 'reset'
88
+ const pose = PcbScene3dCameraRig.resolvePreset(
89
+ normalizedPreset,
90
+ sceneDescription,
91
+ {
92
+ radius: resetsView
93
+ ? PcbScene3dCameraRig.resolveInitialRadius(sceneDescription)
94
+ : PcbScene3dCameraRig.#resolveCurrentRadius(
95
+ camera,
96
+ controls
97
+ ),
98
+ target: resetsView
99
+ ? { x: 0, y: 0, z: 0 }
100
+ : PcbScene3dCameraRig.#resolveCurrentTarget(controls)
101
+ }
102
+ )
103
+
104
+ camera?.up?.set?.(pose.up.x, pose.up.y, pose.up.z)
105
+ controls?.target?.set?.(pose.target.x, pose.target.y, pose.target.z)
106
+ camera?.position?.set?.(
107
+ pose.position.x,
108
+ pose.position.y,
109
+ pose.position.z
110
+ )
111
+ camera?.lookAt?.(pose.target.x, pose.target.y, pose.target.z)
112
+ controls?.update?.()
113
+ camera?.updateProjectionMatrix?.()
114
+
115
+ return pose
116
+ }
117
+
118
+ /**
119
+ * Resolves one camera position from spherical coordinates in a z-up world.
120
+ * @param {number} theta
121
+ * @param {number} phi
122
+ * @param {number} radius
123
+ * @param {{ x: number, y: number, z: number }} target
124
+ * @returns {{ x: number, y: number, z: number }}
125
+ */
126
+ static #resolvePosition(theta, phi, radius, target) {
127
+ const sinPhi = Math.sin(phi)
128
+
129
+ return {
130
+ x: target.x + radius * sinPhi * Math.cos(theta),
131
+ y: target.y + radius * sinPhi * Math.sin(theta),
132
+ z: target.z + radius * Math.cos(phi)
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Resolves the current camera radius from the camera-target distance.
138
+ * @param {{ position?: { x?: number, y?: number, z?: number } }} [camera]
139
+ * @param {{ target?: { x?: number, y?: number, z?: number } }} [controls]
140
+ * @returns {number}
141
+ */
142
+ static #resolveCurrentRadius(camera, controls) {
143
+ const position = camera?.position || {}
144
+ const target = controls?.target || {}
145
+
146
+ return Math.max(
147
+ Math.hypot(
148
+ Number(position.x || 0) - Number(target.x || 0),
149
+ Number(position.y || 0) - Number(target.y || 0),
150
+ Number(position.z || 0) - Number(target.z || 0)
151
+ ),
152
+ 1
153
+ )
154
+ }
155
+
156
+ /**
157
+ * Resolves the current controls target into a plain object.
158
+ * @param {{ target?: { x?: number, y?: number, z?: number } }} [controls]
159
+ * @returns {{ x: number, y: number, z: number }}
160
+ */
161
+ static #resolveCurrentTarget(controls) {
162
+ return {
163
+ x: Number(controls?.target?.x || 0),
164
+ y: Number(controls?.target?.y || 0),
165
+ z: Number(controls?.target?.z || 0)
166
+ }
167
+ }
168
+ }