瀏覽代碼

more work on 3d viewer for docs

Thomas Buck 7 月之前
父節點
當前提交
9754853493
共有 7 個檔案被更改,包括 248 行新增183 行删除
  1. 1
    0
      .gitignore
  2. 6
    0
      docs/generate_docs.sh
  3. 35
    0
      docs/src/3dprint.md
  4. 174
    0
      docs/src/js/3d.js
  5. 0
    183
      docs/src/pcb1.md
  6. 23
    0
      docs/src/pcb1_pcb.md
  7. 9
    0
      pcb/generate_plot.sh

+ 1
- 0
.gitignore 查看文件

@@ -10,6 +10,7 @@ pcb/fab.zip
10 10
 
11 11
 docs/book
12 12
 docs/src/plot
13
+docs/src/stl
13 14
 docs/src/inc_*.md
14 15
 
15 16
 3dprint/stl

+ 6
- 0
docs/generate_docs.sh 查看文件

@@ -37,6 +37,12 @@ rm -rf src/plot
37 37
 cp -r ../pcb/plot src
38 38
 #echo
39 39
 
40
+#echo "Generating stls"
41
+rm -rf src/stl
42
+#../3dprint/generate_stls.sh
43
+cp -r ../3dprint/stl src
44
+#echo
45
+
40 46
 for IN in $INSCH
41 47
 do
42 48
     o="src/inc_$IN.md"

+ 35
- 0
docs/src/3dprint.md 查看文件

@@ -2,4 +2,39 @@
2 2
 
3 3
 **TODO** work in progress
4 4
 
5
+<script type="importmap">
6
+    {
7
+        "imports": {
8
+            "three": "https://cdn.jsdelivr.net/npm/three@0.163.0/build/three.module.js",
9
+            "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.163.0/examples/jsm/"
10
+        }
11
+    }
12
+</script>
5 13
 
14
+## Actuator All
15
+
16
+<p>Status: "<span id="3d_info">Preparing 3D model...</span>"</p>
17
+<div id="3d_viewer" style="width: 100%; height: 100%; background-color: white; border: 1px solid black;"></div>
18
+
19
+<script type="module">
20
+    var info = document.getElementById('3d_info');
21
+    var view = document.getElementById('3d_viewer');
22
+    view.style.height = Math.floor(view.clientWidth * 0.707) + "px";
23
+
24
+    import { init_3d } from "./js/3d.js";
25
+    init_3d('stl/actuator_all.stl', view, info, view.clientWidth, view.clientHeight);
26
+</script>
27
+
28
+## Actuator Cap
29
+
30
+<p>Status: "<span id="3d_info2">Preparing 3D model...</span>"</p>
31
+<div id="3d_viewer2" style="width: 100%; height: 100%; background-color: white; border: 1px solid black;"></div>
32
+
33
+<script type="module">
34
+    var info = document.getElementById('3d_info2');
35
+    var view = document.getElementById('3d_viewer2');
36
+    view.style.height = Math.floor(view.clientWidth * 0.707) + "px";
37
+
38
+    import { init_3d } from "./js/3d.js";
39
+    init_3d('stl/actuator_cap.stl', view, info, view.clientWidth, view.clientHeight);
40
+</script>

+ 174
- 0
docs/src/js/3d.js 查看文件

@@ -0,0 +1,174 @@
1
+import * as THREE from 'three';
2
+import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
3
+import { STLLoader } from 'three/addons/loaders/STLLoader.js'
4
+import { VRMLLoader } from 'three/addons/loaders/VRMLLoader.js';
5
+
6
+export function init_3d(path, container, status, div_width, div_height) {
7
+    const width = div_width;
8
+    const height = div_height;
9
+
10
+    const scene = new THREE.Scene();
11
+    scene.add(new THREE.AxesHelper(1));
12
+
13
+    const camera = new THREE.PerspectiveCamera( 75, width / height, 0.1, 1000 );
14
+
15
+    const renderer = new THREE.WebGLRenderer();
16
+    renderer.setSize( width, height );
17
+
18
+    container.appendChild( renderer.domElement );
19
+
20
+    const light = new THREE.DirectionalLight( 0xffffff, 0.5 );
21
+    light.position.set(0, 1, 0);
22
+    scene.add(light);
23
+
24
+    const light42 = new THREE.DirectionalLight( 0xffffff, 0.5 );
25
+    light42.position.set(1, 0, 0);
26
+    scene.add(light42);
27
+
28
+    const light23 = new THREE.DirectionalLight( 0xffffff, 0.5 );
29
+    light23.position.set(0, 0, 1);
30
+    scene.add(light23);
31
+
32
+    const lightb = new THREE.DirectionalLight( 0xffffff, 0.5 );
33
+    lightb.position.set(0, -1, 0);
34
+    scene.add(lightb);
35
+
36
+    const light42b = new THREE.DirectionalLight( 0xffffff, 0.5 );
37
+    light42b.position.set(-1, 0, 0);
38
+    scene.add(light42b);
39
+
40
+    const light23b = new THREE.DirectionalLight( 0xffffff, 0.5 );
41
+    light23b.position.set(0, 0, -1);
42
+    scene.add(light23b);
43
+
44
+    const light2 = new THREE.AmbientLight(0x101010);
45
+    light2.position.set(100, 100, 100);
46
+    scene.add(light2);
47
+
48
+    const material = new THREE.MeshStandardMaterial();
49
+    //material.roughness = 0.42;
50
+
51
+    if (path.endsWith(".stl")) {
52
+        const loader = new STLLoader();
53
+        loader.load(
54
+            //'plot/actuator_all.stl',
55
+            path,
56
+            function (geometry) {
57
+                const mesh = new THREE.Mesh(geometry, material);
58
+                scene.add(mesh);
59
+            },
60
+            (xhr) => {
61
+                const s = (xhr.loaded / xhr.total) * 100 + '% loaded';
62
+                console.log(s);
63
+                status.textContent = s;
64
+            },
65
+            (error) => {
66
+                console.log(error);
67
+                status.textContent = error;
68
+            }
69
+        );
70
+    } else if (path.endsWith(".wrl")) {
71
+        const loader = new VRMLLoader();
72
+        loader.load(
73
+            //'plot/drumkit.kicad_pcb.wrl',
74
+            //'plot/dispensy.wrl',
75
+            path,
76
+            function (object) {
77
+                scene.add(object);
78
+        });
79
+    } else {
80
+        const s = "error: unknown filetype for " + path;
81
+        console.log(s);
82
+        status.textContent = s;
83
+    }
84
+
85
+    const controls = new OrbitControls( camera, renderer.domElement );
86
+    controls.enableDamping = true;
87
+
88
+    // https://wejn.org/2020/12/cracking-the-threejs-object-fitting-nut/
89
+    const fitCameraToCenteredObject = function (camera, object, offset, orbitControls ) {
90
+        const boundingBox = new THREE.Box3();
91
+        boundingBox.setFromObject( object );
92
+
93
+        var middle = new THREE.Vector3();
94
+        var size = new THREE.Vector3();
95
+        boundingBox.getSize(size);
96
+
97
+        // figure out how to fit the box in the view:
98
+        // 1. figure out horizontal FOV (on non-1.0 aspects)
99
+        // 2. figure out distance from the object in X and Y planes
100
+        // 3. select the max distance (to fit both sides in)
101
+        //
102
+        // The reason is as follows:
103
+        //
104
+        // Imagine a bounding box (BB) is centered at (0,0,0).
105
+        // Camera has vertical FOV (camera.fov) and horizontal FOV
106
+        // (camera.fov scaled by aspect, see fovh below)
107
+        //
108
+        // Therefore if you want to put the entire object into the field of view,
109
+        // you have to compute the distance as: z/2 (half of Z size of the BB
110
+        // protruding towards us) plus for both X and Y size of BB you have to
111
+        // figure out the distance created by the appropriate FOV.
112
+        //
113
+        // The FOV is always a triangle:
114
+        //
115
+        //  (size/2)
116
+        // +--------+
117
+        // |       /
118
+        // |      /
119
+        // |     /
120
+        // | F° /
121
+        // |   /
122
+        // |  /
123
+        // | /
124
+        // |/
125
+        //
126
+        // F° is half of respective FOV, so to compute the distance (the length
127
+        // of the straight line) one has to: `size/2 / Math.tan(F)`.
128
+        //
129
+        // FTR, from https://threejs.org/docs/#api/en/cameras/PerspectiveCamera
130
+        // the camera.fov is the vertical FOV.
131
+
132
+        const fov = camera.fov * ( Math.PI / 180 );
133
+        const fovh = 2*Math.atan(Math.tan(fov/2) * camera.aspect);
134
+        let dx = size.z / 2 + Math.abs( size.x / 2 / Math.tan( fovh / 2 ) );
135
+        let dy = size.z / 2 + Math.abs( size.y / 2 / Math.tan( fov / 2 ) );
136
+        let cameraZ = Math.max(dx, dy);
137
+
138
+        // offset the camera, if desired (to avoid filling the whole canvas)
139
+        if( offset !== undefined && offset !== 0 ) cameraZ *= offset;
140
+
141
+        camera.position.set( 0, 0, cameraZ );
142
+
143
+        // set the far plane of the camera so that it easily encompasses the whole object
144
+        const minZ = boundingBox.min.z;
145
+        const cameraToFarEdge = ( minZ < 0 ) ? -minZ + cameraZ : cameraZ - minZ;
146
+
147
+        camera.far = cameraToFarEdge * 3;
148
+        camera.updateProjectionMatrix();
149
+
150
+        if ( orbitControls !== undefined ) {
151
+            // set camera to rotate around the center
152
+            orbitControls.target = new THREE.Vector3(0, 0, 0);
153
+
154
+            // prevent camera from zooming out far enough to create far plane cutoff
155
+            orbitControls.maxDistance = cameraToFarEdge * 2;
156
+        }
157
+    };
158
+
159
+    //camera.position.z = 50;
160
+    fitCameraToCenteredObject(camera, scene, 0, controls)
161
+
162
+    function render() {
163
+        renderer.render(scene, camera);
164
+    }
165
+
166
+    function animate() {
167
+        requestAnimationFrame(animate);
168
+        controls.update();
169
+        render();
170
+    }
171
+
172
+    animate();
173
+    status.textContent = "3D model ready!";
174
+}

+ 0
- 183
docs/src/pcb1.md 查看文件

@@ -5,186 +5,3 @@ It's a single sided PCB layout suitable for hand etching at home.
5 5
 
6 6
 [![hand etching prototype pcb](https://www.xythobuz.de/img/lars_13_small.jpg)](https://www.xythobuz.de/img/lars_13.jpg)
7 7
 [![assembled prototype](https://www.xythobuz.de/img/lars_10_small.jpg)](https://www.xythobuz.de/img/lars_10.jpg)
8
-
9
-
10
-
11
-
12
-<div id="threejstarget" style="width: 600px; height: 400px; background-color: white; border: 1px solid black;"></div>
13
-
14
-<script type="importmap">
15
-  {
16
-    "imports": {
17
-      "three": "https://cdn.jsdelivr.net/npm/three@0.163.0/build/three.module.js",
18
-      "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.163.0/examples/jsm/"
19
-    }
20
-  }
21
-</script>
22
-
23
-<script type="module">
24
-
25
-
26
-import * as THREE from 'three';
27
-import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
28
-import { STLLoader } from 'three/addons/loaders/STLLoader.js'
29
-import { VRMLLoader } from 'three/addons/loaders/VRMLLoader.js';
30
-
31
-const container = document.getElementById('threejstarget');
32
-
33
-const width = 600;
34
-const height = 400;
35
-
36
-const scene = new THREE.Scene();
37
-scene.add(new THREE.AxesHelper(3));
38
-
39
-const camera = new THREE.PerspectiveCamera( 75, width / height, 0.1, 1000 );
40
-
41
-const renderer = new THREE.WebGLRenderer();
42
-renderer.setSize( width, height );
43
-
44
-container.appendChild( renderer.domElement );
45
-
46
-const light = new THREE.DirectionalLight( 0xffffff, 0.5 );
47
-light.position.set(0, 1, 0);
48
-scene.add(light);
49
-
50
-const light42 = new THREE.DirectionalLight( 0xffffff, 0.5 );
51
-light42.position.set(1, 0, 0);
52
-scene.add(light42);
53
-
54
-const light23 = new THREE.DirectionalLight( 0xffffff, 0.5 );
55
-light23.position.set(0, 0, 1);
56
-scene.add(light23);
57
-
58
-const lightb = new THREE.DirectionalLight( 0xffffff, 0.5 );
59
-lightb.position.set(0, -1, 0);
60
-scene.add(lightb);
61
-
62
-const light42b = new THREE.DirectionalLight( 0xffffff, 0.5 );
63
-light42b.position.set(-1, 0, 0);
64
-scene.add(light42b);
65
-
66
-const light23b = new THREE.DirectionalLight( 0xffffff, 0.5 );
67
-light23b.position.set(0, 0, -1);
68
-scene.add(light23b);
69
-
70
-const light2 = new THREE.AmbientLight(0x101010);
71
-light2.position.set(100, 100, 100);
72
-scene.add(light2);
73
-
74
-const material = new THREE.MeshStandardMaterial();
75
-//material.roughness = 0.42;
76
-
77
-const loader = new STLLoader();
78
-loader.load(
79
-    'actuator_all.stl',
80
-    function (geometry) {
81
-        const mesh = new THREE.Mesh(geometry, material)
82
-        scene.add(mesh)
83
-    },
84
-    (xhr) => {
85
-        console.log((xhr.loaded / xhr.total) * 100 + '% loaded')
86
-    },
87
-    (error) => {
88
-        console.log(error)
89
-    }
90
-);
91
-
92
-/*
93
-const loader = new VRMLLoader();
94
-loader.load(
95
-    //'drumkit.wrl',
96
-    'dispensy.wrl',
97
-    function (object) {
98
-        scene.add(object);
99
-});
100
-*/
101
-
102
-const controls = new OrbitControls( camera, renderer.domElement );
103
-controls.enableDamping = true;
104
-
105
-// https://wejn.org/2020/12/cracking-the-threejs-object-fitting-nut/
106
-const fitCameraToCenteredObject = function (camera, object, offset, orbitControls ) {
107
-    const boundingBox = new THREE.Box3();
108
-    boundingBox.setFromObject( object );
109
-
110
-    var middle = new THREE.Vector3();
111
-    var size = new THREE.Vector3();
112
-    boundingBox.getSize(size);
113
-
114
-    // figure out how to fit the box in the view:
115
-    // 1. figure out horizontal FOV (on non-1.0 aspects)
116
-    // 2. figure out distance from the object in X and Y planes
117
-    // 3. select the max distance (to fit both sides in)
118
-    //
119
-    // The reason is as follows:
120
-    //
121
-    // Imagine a bounding box (BB) is centered at (0,0,0).
122
-    // Camera has vertical FOV (camera.fov) and horizontal FOV
123
-    // (camera.fov scaled by aspect, see fovh below)
124
-    //
125
-    // Therefore if you want to put the entire object into the field of view,
126
-    // you have to compute the distance as: z/2 (half of Z size of the BB
127
-    // protruding towards us) plus for both X and Y size of BB you have to
128
-    // figure out the distance created by the appropriate FOV.
129
-    //
130
-    // The FOV is always a triangle:
131
-    //
132
-    //  (size/2)
133
-    // +--------+
134
-    // |       /
135
-    // |      /
136
-    // |     /
137
-    // | F° /
138
-    // |   /
139
-    // |  /
140
-    // | /
141
-    // |/
142
-    //
143
-    // F° is half of respective FOV, so to compute the distance (the length
144
-    // of the straight line) one has to: `size/2 / Math.tan(F)`.
145
-    //
146
-    // FTR, from https://threejs.org/docs/#api/en/cameras/PerspectiveCamera
147
-    // the camera.fov is the vertical FOV.
148
-
149
-    const fov = camera.fov * ( Math.PI / 180 );
150
-    const fovh = 2*Math.atan(Math.tan(fov/2) * camera.aspect);
151
-    let dx = size.z / 2 + Math.abs( size.x / 2 / Math.tan( fovh / 2 ) );
152
-    let dy = size.z / 2 + Math.abs( size.y / 2 / Math.tan( fov / 2 ) );
153
-    let cameraZ = Math.max(dx, dy);
154
-
155
-    // offset the camera, if desired (to avoid filling the whole canvas)
156
-    if( offset !== undefined && offset !== 0 ) cameraZ *= offset;
157
-
158
-    camera.position.set( 0, 0, cameraZ );
159
-
160
-    // set the far plane of the camera so that it easily encompasses the whole object
161
-    const minZ = boundingBox.min.z;
162
-    const cameraToFarEdge = ( minZ < 0 ) ? -minZ + cameraZ : cameraZ - minZ;
163
-
164
-    camera.far = cameraToFarEdge * 3;
165
-    camera.updateProjectionMatrix();
166
-
167
-    if ( orbitControls !== undefined ) {
168
-        // set camera to rotate around the center
169
-        orbitControls.target = new THREE.Vector3(0, 0, 0);
170
-
171
-        // prevent camera from zooming out far enough to create far plane cutoff
172
-        orbitControls.maxDistance = cameraToFarEdge * 2;
173
-    }
174
-};
175
-
176
-//camera.position.z = 50;
177
-fitCameraToCenteredObject(camera, scene, 0, controls)
178
-
179
-function render() {
180
-    renderer.render(scene, camera);
181
-}
182
-
183
-function animate() {
184
-    requestAnimationFrame(animate);
185
-    controls.update();
186
-    render();
187
-}
188
-
189
-animate();
190
-</script>

+ 23
- 0
docs/src/pcb1_pcb.md 查看文件

@@ -33,3 +33,26 @@ You can also view the [DIY layout as PDF](./plot/drumkit.kicad_pcb_diy.pdf).
33 33
 </div>
34 34
 
35 35
 [Direct link to this file](./plot/drumkit.kicad_pcb_diy.svg).
36
+
37
+## 3D PCB Layout
38
+
39
+<p>Status: "<span id="3d_info">Preparing 3D model...</span>"</p>
40
+<div id="3d_viewer" style="width: 100%; height: 100%; background-color: white; border: 1px solid black;"></div>
41
+
42
+<script type="importmap">
43
+    {
44
+        "imports": {
45
+            "three": "https://cdn.jsdelivr.net/npm/three@0.163.0/build/three.module.js",
46
+            "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.163.0/examples/jsm/"
47
+        }
48
+    }
49
+</script>
50
+
51
+<script type="module">
52
+    var info = document.getElementById('3d_info');
53
+    var view = document.getElementById('3d_viewer');
54
+    view.style.height = Math.floor(view.clientWidth * 0.707) + "px";
55
+
56
+    import { init_3d } from "./js/3d.js";
57
+    init_3d('plot/drumkit.kicad_pcb.wrl', view, info, view.clientWidth, view.clientHeight);
58
+</script>

+ 9
- 0
pcb/generate_plot.sh 查看文件

@@ -88,4 +88,13 @@ do
88 88
             $IN
89 89
         echo
90 90
     done
91
+
92
+    #  -----------
93
+    # | 3D Layout |
94
+    #  -----------
95
+    echo "Exporting board 3D"
96
+    kicad-cli pcb export vrml \
97
+        -o $OUTDIR/$IN.wrl \
98
+        $IN
99
+    echo
91 100
 done

Loading…
取消
儲存