Browse Source

generate incs for 3d visualizers

Thomas Buck 9 months ago
parent
commit
f2b1925ecb
7 changed files with 220 additions and 160 deletions
  1. 5
    2
      .github/workflows/docs.yml
  2. 1
    1
      .github/workflows/kicad.yml
  3. 26
    0
      .github/workflows/scad.yml
  4. 46
    0
      docs/generate_docs.sh
  5. 31
    28
      docs/src/3dprint.md
  6. 110
    109
      docs/src/js/3d.js
  7. 1
    20
      docs/src/pcb1_pcb.md

+ 5
- 2
.github/workflows/docs.yml View File

@@ -52,9 +52,9 @@ jobs:
52 52
 
53 53
       - name: Install dependencies
54 54
         run: |
55
-          sudo add-apt-repository --yes ppa:kicad/kicad-7.0-releases
55
+          sudo add-apt-repository --yes ppa:kicad/kicad-8.0-releases
56 56
           sudo apt update
57
-          sudo apt install -y --install-recommends kicad pipx libfuse2 libegl1 poppler-utils
57
+          sudo apt install -y --install-recommends kicad pipx libfuse2 libegl1 poppler-utils openscad zip
58 58
 
59 59
       - name: Install latest mdbook
60 60
         run: |
@@ -67,6 +67,9 @@ jobs:
67 67
       - name: Generate Plots
68 68
         run: pcb/generate_plot.sh
69 69
 
70
+      - name: Render STLs
71
+        run: 3dprint/generate_stls.sh
72
+
70 73
       - name: Build Book
71 74
         run: docs/generate_docs.sh build
72 75
 

+ 1
- 1
.github/workflows/kicad.yml View File

@@ -47,7 +47,7 @@ jobs:
47 47
 
48 48
       - name: Install dependencies
49 49
         run: |
50
-          sudo add-apt-repository --yes ppa:kicad/kicad-7.0-releases
50
+          sudo add-apt-repository --yes ppa:kicad/kicad-8.0-releases
51 51
           sudo apt update
52 52
           sudo apt install -y --install-recommends kicad
53 53
           sudo apt-get install -y zip

+ 26
- 0
.github/workflows/scad.yml View File

@@ -1,3 +1,29 @@
1
+# SPDX-FileCopyrightText: 2024 Thomas Buck <thomas@xythobuz.de>
2
+# SPDX-License-Identifier: CERN-OHL-S-2.0+
3
+#
4
+#  ------------------------------------------------------------------------------
5
+# | Copyright (c) 2024 Thomas Buck <thomas@xythobuz.de>                          |
6
+# |                                                                              |
7
+# | This source describes Open Hardware and is licensed under the CERN-OHL-S v2  |
8
+# | or any later version.                                                        |
9
+# |                                                                              |
10
+# | You may redistribute and modify this source and make products using it under |
11
+# | the terms of the CERN-OHL-S v2 (https://ohwr.org/cern_ohl_s_v2.txt)          |
12
+# | or any later version.                                                        |
13
+# |                                                                              |
14
+# | This source is distributed WITHOUT ANY EXPRESS OR IMPLIED WARRANTY,          |
15
+# | INCLUDING OF MERCHANTABILITY, SATISFACTORY QUALITY AND FITNESS FOR A         |
16
+# | PARTICULAR PURPOSE. Please see the CERN-OHL-S v2 (or any later version)      |
17
+# | for applicable conditions.                                                   |
18
+# |                                                                              |
19
+# | Source location: https://git.xythobuz.de/thomas/drumkit                      |
20
+# |                                                                              |
21
+# | As per CERN-OHL-S v2 section 4, should You produce hardware based on this    |
22
+# | source, You must where practicable maintain the Source Location visible      |
23
+# | on the external case of the Gizmo or other products you make using this      |
24
+# | source.                                                                      |
25
+#  ------------------------------------------------------------------------------
26
+
1 27
 name: STLs
2 28
 
3 29
 # build for each push and pull request

+ 46
- 0
docs/generate_docs.sh View File

@@ -43,6 +43,8 @@ rm -rf src/stl
43 43
 cp -r ../3dprint/stl src
44 44
 #echo
45 45
 
46
+INSTL=`ls ../3dprint/stl/*.stl`
47
+
46 48
 for IN in $INSCH
47 49
 do
48 50
     o="src/inc_$IN.md"
@@ -73,6 +75,50 @@ do
73 75
     echo
74 76
 done
75 77
 
78
+plot_3d() {
79
+    echo '<script type="importmap">' >> $1
80
+    echo '    {' >> $1
81
+    echo '        "imports": {' >> $1
82
+    echo '            "three": "https://cdn.jsdelivr.net/npm/three@0.163.0/build/three.module.js",' >> $1
83
+    echo '            "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.163.0/examples/jsm/"' >> $1
84
+    echo '        }' >> $1
85
+    echo '    }' >> $1
86
+    echo '</script>' >> $1
87
+    echo "<p>Status: \"<span id=\"3d_info_$2\">Preparing 3D model...</span>\"</p>" >> $1
88
+    echo "<div id=\"3d_viewer_$2\" style=\"width: 100%; height: 100%; background-color: white; border: 1px solid black;\"></div>" >> $1
89
+    echo '<script type="module">' >> $1
90
+    echo "    var info = document.getElementById(\"3d_info_$2\");" >> $1
91
+    echo "    var view = document.getElementById(\"3d_viewer_$2\");" >> $1
92
+    echo '    view.style.height = Math.floor(view.clientWidth * 0.707) + "px";' >> $1
93
+    echo '    import { init_3d } from "./js/3d.js";' >> $1
94
+    echo "    init_3d(\"$3\", view, info, view.clientWidth, view.clientHeight);" >> $1
95
+    echo '</script>' >> $1
96
+}
97
+
98
+for IN in $INPCB
99
+do
100
+    o="src/inc_$IN.md"
101
+    file="plot/$IN.wrl"
102
+    name=`echo $file | sed "s:plot/::g" | sed 's:.wrl::g'`
103
+    echo "Include for $IN at $o, $file, $name"
104
+    rm -rf $o
105
+
106
+    plot_3d $o $name $file
107
+    echo
108
+done
109
+
110
+for IN in $INSTL
111
+do
112
+    o=`echo $IN | sed "s:../3dprint/stl/:src/inc_:g" | sed "s:.stl:.stl.md:g"`
113
+    file=`echo $IN | sed "s:../3dprint/::g"`
114
+    name=`echo $file | sed "s:stl/::g" | sed 's:.stl::g'`
115
+    echo "Include for $IN at $o, $file, $name"
116
+    rm -rf $o
117
+
118
+    plot_3d $o $name $file
119
+    echo
120
+done
121
+
76 122
 echo "Generating docs"
77 123
 if [ "$1" = "serve" ] ; then
78 124
     mdbook serve --open

+ 31
- 28
docs/src/3dprint.md View File

@@ -2,39 +2,42 @@
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>
13
-
14 5
 ## Actuator All
15 6
 
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>
7
+{{#include inc_actuator_all.stl.md}}
8
+
9
+## Actuator Cap
18 10
 
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";
11
+{{#include inc_actuator_cap.stl.md}}
23 12
 
24
-    import { init_3d } from "./js/3d.js";
25
-    init_3d('stl/actuator_all.stl', view, info, view.clientWidth, view.clientHeight);
26
-</script>
13
+## Actuator Hammer
27 14
 
28
-## Actuator Cap
15
+{{#include inc_actuator_hammer.stl.md}}
16
+
17
+## Actuator Spool
18
+
19
+{{#include inc_actuator_spool.stl.md}}
20
+
21
+## Tamb Mount All Visualize
22
+
23
+{{#include inc_tamb_mount_all_visualize.stl.md}}
24
+
25
+## Tamb Mount Inner
26
+
27
+{{#include inc_tamb_mount_inner.stl.md}}
28
+
29
+## Tamb Mount Outer
30
+
31
+{{#include inc_tamb_mount_outer.stl.md}}
32
+
33
+## Enclosure Bottom
34
+
35
+{{#include inc_enclosure_bottom.stl.md}}
36
+
37
+## Enclosure Top
29 38
 
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>
39
+{{#include inc_enclosure_top.stl.md}}
32 40
 
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";
41
+## Enclosure Faceplate
37 42
 
38
-    import { init_3d } from "./js/3d.js";
39
-    init_3d('stl/actuator_cap.stl', view, info, view.clientWidth, view.clientHeight);
40
-</script>
43
+{{#include inc_enclosure_faceplate.stl.md}}

+ 110
- 109
docs/src/js/3d.js View File

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

+ 1
- 20
docs/src/pcb1_pcb.md View File

@@ -36,23 +36,4 @@ You can also view the [DIY layout as PDF](./plot/drumkit.kicad_pcb_diy.pdf).
36 36
 
37 37
 ## 3D PCB Layout
38 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>
39
+{{#include inc_drumkit.kicad_pcb.md}}

Loading…
Cancel
Save