Browse Source

initial commit

Thomas Buck 1 year ago
commit
f0365249f9
3 changed files with 924 additions and 0 deletions
  1. 134
    0
      hardware/external/cherry_mx.scad
  2. 431
    0
      hardware/external/threads.scad
  3. 359
    0
      hardware/trackball.scad

+ 134
- 0
hardware/external/cherry_mx.scad View File

@@ -0,0 +1,134 @@
1
+$fn=60;
2
+/**
3
+a cherry mx switch.
4
+
5
+most of the measurements done with a caliper. some taken from
6
+http://geekhack.org/index.php?topic=47744.0
7
+
8
+This is just to illustrate and to drop in a a gross reference. It is mostly artistic apart from the steam and mounting plate dimensions
9
+*/
10
+
11
+// slightly adapted from https://www.thingiverse.com/thing:421524
12
+
13
+module mx_switch(t = 0) {
14
+
15
+translate([0, 0, 9.25])
16
+//translate([0,0,-3])
17
+{
18
+
19
+	//1. steam
20
+	color("#503000")
21
+    translate([0, 0, -3.9 * t]) {
22
+		//1.1. l-r tab is 1.35mm
23
+		translate([0,0,-3.62/2])
24
+			cube([1.35,4.5,3.62], center=true);
25
+		//1.2. f-b tab is 1.15mm. it has a smal notch that i will ignore.
26
+		translate([0,0,-3.62/2])
27
+			cube([4.5,1.15,3.62], center=true);
28
+		//1.3. base. it has a chamfered top that i will ignore.
29
+		translate([0,0,-5.62])
30
+			cube([7.2,5.56,4], center=true);
31
+	}
32
+	// 2. top
33
+	color("grey"){
34
+		difference(){
35
+			// make a trapezoid with the general shape (volume?) of the top
36
+			hull(){
37
+				translate([0,0,-4]) //distance from top of switch... some i measured 3.9 others 4.2... so leaving at 4
38
+					cube([9.87,10.62,0.1], center=true);
39
+				translate([0,0,-4 -5.2]) // bottom has a measured 5.3... so move 5.2 and use the 0.1 bellow
40
+					cube([14.58,14.58,0.1], center=true);
41
+			}
42
+            
43
+			translate([0, 0, -5.8])
44
+            cube([7.2,5.56,4], center=true);
45
+            
46
+
47
+		// and subtract:
48
+		// the front led. NOTE: totally off... measured by eye. just for astetics
49
+		// adding just so there is a visual cue of the direction
50
+			translate([0,-4.7,-6])
51
+				cylinder(r=3/2, h=6, center=true);
52
+			translate([0,-5.5,-6])
53
+				cube([8,4,5], center=true);
54
+		// the four corners
55
+			// TODO waste of time? this is all for looks, you shouldn't invade any of that space anyway...
56
+		}
57
+	}
58
+
59
+	// 3. bottom
60
+	color("green")
61
+		// 3.1 main body volume
62
+		hull(){
63
+				translate([0,0,-4 -5.3]) //steam + top
64
+					cube([13.98,13.98,0.1], center=true);
65
+				translate([0,0,-4 -5.3 -2.2]) //steam + top + straigth part
66
+					cube([13.98,13.98,0.1], center=true);
67
+				translate([0,0,-4 -5.3 -5.5]) //steam + top + bottom (measured 5.5)
68
+					cube([12.74,13.6,0.1], center=true);
69
+		}
70
+		// 3.2 tabs
71
+		// note: only measured the lenght, if they are slightly off-center, this will be all wrong :)
72
+		color("black")
73
+		difference(){
74
+			translate([0,0,-4 -5.3 -0.82/2]) //steam + top
75
+				cube([15.64,15.64,0.82], center=true);
76
+			translate([0,0,-4 -5.3 -0.82/2  ]) // front-back cut
77
+				cube([5.64,20,0.82 +2], center=true);
78
+			translate([0,0,-4 -5.3 -0.82/2  ]) //side cut
79
+				cube([20,11.64,0.82 +2], center=true);
80
+		}
81
+
82
+		// 3.3 tab (plate snap on). to use this mechanically, you have to take into account the bending (as it will move the bottom part slightly up...) just for gross reference here for now
83
+		color("white"){
84
+			// 3.3.1 top
85
+			translate([0,0,-4 -5.3 -0.82/2  ]) // front-back cut
86
+				cube([1.82,16.33,0.82], center=true);
87
+			// 3.3.2 bottom
88
+			difference(){
89
+				hull(){
90
+					translate([0,0,-4 -5.3 -0.82/2 -1.76  ]) // front-back cut
91
+						cube([3.65,14,0.1], center=true);
92
+					translate([0,0,-4 -5.3 -0.82/2 -2.2  ]) // front-back cut
93
+						cube([3.65,14.74,0.1], center=true);
94
+					translate([0,0,-4 -5.3 -0.82/2 -2.89  ]) // front-back cut
95
+						cube([3.65,14,0.1], center=true);
96
+				}
97
+				translate([0,0,-4 -5.3 -0.82/2 -1.76   ]) // front-back cut
98
+					cube([2.2,20,4], center=true);
99
+
100
+			}
101
+		}
102
+
103
+		// 4. bottom guides
104
+		// again, i'm assuming everything is centered...
105
+		color("darkGreen"){
106
+			// 4.1 cylinder
107
+			translate([0,0,-4 -5.3 -5.5 -2/2]) //steam + top + bottom (measured 5.5)
108
+				cylinder(r=3.85/2, h=2, center=true);
109
+			translate([0,0,-4 -5.3 -5.5 -2 -1/2]) //steam + top + bottom (measured 5.5)
110
+				cylinder(r2=3.85/2, r1=2.8/2, h=1, center=true);
111
+			// 4.2 PCB pins
112
+			translate([4.95,0,-4 -5.3 -5.5 -2/2]) //steam + top + bottom (measured 5.5)
113
+				cylinder(r=1.6/2, h=2, center=true);
114
+			translate([4.95,0,-4 -5.3 -5.5 -2 -1/2]) //steam + top + bottom (measured 5.5)
115
+				cylinder(r2=1.6/2, r1=1/2, h=1, center=true);
116
+			translate([-4.95,0,-4 -5.3 -5.5 -2/2]) //steam + top + bottom (measured 5.5)
117
+				cylinder(r=1.6/2, h=2, center=true);
118
+			translate([-4.95,0,-4 -5.3 -5.5 -2 -1/2]) //steam + top + bottom (measured 5.5)
119
+				cylinder(r2=1.6/2, r1=1/2, h=1, center=true);
120
+		}
121
+
122
+		// 5. pins
123
+		color("orange"){
124
+			translate([-3.77,2.7,-4 -5.3 -5.5 -3.1/2]) //steam + top + bottom (measured 5.5)
125
+				cube([.86, 0.2,3.1], center=true);
126
+			translate([2.7,5.2,-4 -5.3 -5.5 -3.1/2]) //steam + top + bottom (measured 5.5)
127
+				cube([.86, 0.2,3.1], center=true);
128
+		}
129
+
130
+}
131
+
132
+}
133
+
134
+mx_switch();

+ 431
- 0
hardware/external/threads.scad View File

@@ -0,0 +1,431 @@
1
+/*
2
+ * ISO-standard metric threads, following this specification:
3
+ *          http://en.wikipedia.org/wiki/ISO_metric_screw_thread
4
+ *
5
+ * Copyright 2022 Dan Kirshner - dan_kirshner@yahoo.com
6
+ * This program is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * See <http://www.gnu.org/licenses/>.
17
+ *
18
+ * Version 2.7.  2022-02-27  Increase minimum thread segments.
19
+ * Version 2.6.  2021-05-16  Contributed patches for leadin (thanks,
20
+                             jeffery.spirko@tamucc.edu) and aligning thread
21
+                             "facets" (triangulation) with base cylinder
22
+                             (thanks, rambetter@protonmail.com).
23
+ * Version 2.5.  2020-04-11  Leadin option works for internal threads.
24
+ * Version 2.4.  2019-07-14  Add test option - do not render threads.
25
+ * Version 2.3.  2017-08-31  Default for leadin: 0 (best for internal threads).
26
+ * Version 2.2.  2017-01-01  Correction for angle; leadfac option.  (Thanks to
27
+ *                           Andrew Allen <a2intl@gmail.com>.)
28
+ * Version 2.1.  2016-12-04  Chamfer bottom end (low-z); leadin option.
29
+ * Version 2.0.  2016-11-05  Backwards compatibility (earlier OpenSCAD) fixes.
30
+ * Version 1.9.  2016-07-03  Option: tapered.
31
+ * Version 1.8.  2016-01-08  Option: (non-standard) angle.
32
+ * Version 1.7.  2015-11-28  Larger x-increment - for small-diameters.
33
+ * Version 1.6.  2015-09-01  Options: square threads, rectangular threads.
34
+ * Version 1.5.  2015-06-12  Options: thread_size, groove.
35
+ * Version 1.4.  2014-10-17  Use "faces" instead of "triangles" for polyhedron
36
+ * Version 1.3.  2013-12-01  Correct loop over turns -- don't have early cut-off
37
+ * Version 1.2.  2012-09-09  Use discrete polyhedra rather than linear_extrude ()
38
+ * Version 1.1.  2012-09-07  Corrected to right-hand threads!
39
+ */
40
+
41
+// Examples.
42
+//
43
+// Standard M8 x 1.
44
+// metric_thread (diameter=8, pitch=1, length=4);
45
+
46
+// Square thread.
47
+// metric_thread (diameter=8, pitch=1, length=4, square=true);
48
+
49
+// Non-standard: long pitch, same thread size.
50
+//metric_thread (diameter=8, pitch=4, length=4, thread_size=1, groove=true);
51
+
52
+// Non-standard: 20 mm diameter, long pitch, square "trough" width 3 mm,
53
+// depth 1 mm.
54
+//metric_thread (diameter=20, pitch=8, length=16, square=true, thread_size=6,
55
+//               groove=true, rectangle=0.333);
56
+
57
+// English: 1/4 x 20.
58
+//english_thread (diameter=1/4, threads_per_inch=20, length=1);
59
+
60
+// Tapered.  Example -- pipe size 3/4" -- per:
61
+// http://www.engineeringtoolbox.com/npt-national-pipe-taper-threads-d_750.html
62
+// english_thread (diameter=1.05, threads_per_inch=14, length=3/4, taper=1/16);
63
+
64
+// Thread for mounting on Rohloff hub.
65
+//difference () {
66
+//   cylinder (r=20, h=10, $fn=100);
67
+//
68
+//   metric_thread (diameter=34, pitch=1, length=10, internal=true, n_starts=6);
69
+//}
70
+
71
+
72
+// ----------------------------------------------------------------------------
73
+function segments (diameter) = min (150, max (ceil (diameter*6), 25));
74
+
75
+
76
+// ----------------------------------------------------------------------------
77
+// diameter -    outside diameter of threads in mm. Default: 8.
78
+// pitch    -    thread axial "travel" per turn in mm.  Default: 1.
79
+// length   -    overall axial length of thread in mm.  Default: 1.
80
+// internal -    true = clearances for internal thread (e.g., a nut).
81
+//               false = clearances for external thread (e.g., a bolt).
82
+//               (Internal threads should be "cut out" from a solid using
83
+//               difference ()).  Default: false.
84
+// n_starts -    Number of thread starts (e.g., DNA, a "double helix," has
85
+//               n_starts=2).  See wikipedia Screw_thread.  Default: 1.
86
+// thread_size - (non-standard) axial width of a single thread "V" - independent
87
+//               of pitch.  Default: same as pitch.
88
+// groove      - (non-standard) true = subtract inverted "V" from cylinder
89
+//                (rather thanadd protruding "V" to cylinder).  Default: false.
90
+// square      - true = square threads (per
91
+//               https://en.wikipedia.org/wiki/Square_thread_form).  Default:
92
+//               false.
93
+// rectangle   - (non-standard) "Rectangular" thread - ratio depth/(axial) width
94
+//               Default: 0 (standard "v" thread).
95
+// angle       - (non-standard) angle (deg) of thread side from perpendicular to
96
+//               axis (default = standard = 30 degrees).
97
+// taper       - diameter change per length (National Pipe Thread/ANSI B1.20.1
98
+//               is 1" diameter per 16" length). Taper decreases from 'diameter'
99
+//               as z increases.  Default: 0 (no taper).
100
+// leadin      - 0 (default): no chamfer; 1: chamfer (45 degree) at max-z end;
101
+//               2: chamfer at both ends, 3: chamfer at z=0 end.
102
+// leadfac     - scale of leadin chamfer length (default: 1.0 = 1/2 thread).
103
+// test        - true = do not render threads (just draw "blank" cylinder).
104
+//               Default: false (draw threads).
105
+module metric_thread (diameter=8, pitch=1, length=1, internal=false, n_starts=1,
106
+                      thread_size=-1, groove=false, square=false, rectangle=0,
107
+                      angle=30, taper=0, leadin=0, leadfac=1.0, test=false)
108
+{
109
+   // thread_size: size of thread "V" different than travel per turn (pitch).
110
+   // Default: same as pitch.
111
+   local_thread_size = thread_size == -1 ? pitch : thread_size;
112
+   local_rectangle = rectangle ? rectangle : 1;
113
+
114
+   n_segments = segments (diameter);
115
+   h = (test && ! internal) ? 0 : (square || rectangle) ? local_thread_size*local_rectangle/2 : local_thread_size / (2 * tan(angle));
116
+
117
+   h_fac1 = (square || rectangle) ? 0.90 : 0.625;
118
+
119
+   // External thread includes additional relief.
120
+   h_fac2 = (square || rectangle) ? 0.95 : 5.3/8;
121
+
122
+   tapered_diameter = diameter - length*taper;
123
+
124
+   difference () {
125
+      union () {
126
+         if (! groove) {
127
+            if (! test) {
128
+               metric_thread_turns (diameter, pitch, length, internal, n_starts,
129
+                                    local_thread_size, groove, square, rectangle, angle,
130
+                                    taper);
131
+            }
132
+         }
133
+
134
+         difference () {
135
+
136
+            // Solid center, including Dmin truncation.
137
+            if (groove) {
138
+               cylinder (r1=diameter/2, r2=tapered_diameter/2,
139
+                         h=length, $fn=n_segments);
140
+            } else if (internal) {
141
+               cylinder (r1=diameter/2 - h*h_fac1, r2=tapered_diameter/2 - h*h_fac1,
142
+                         h=length, $fn=n_segments);
143
+            } else {
144
+
145
+               // External thread.
146
+               cylinder (r1=diameter/2 - h*h_fac2, r2=tapered_diameter/2 - h*h_fac2,
147
+                         h=length, $fn=n_segments);
148
+            }
149
+
150
+            if (groove) {
151
+               if (! test) {
152
+                  metric_thread_turns (diameter, pitch, length, internal, n_starts,
153
+                                       local_thread_size, groove, square, rectangle,
154
+                                       angle, taper);
155
+               }
156
+            }
157
+         }
158
+
159
+         // Internal thread lead-in: take away from external solid.
160
+         if (internal) {
161
+
162
+            // "Negative chamfer" z=0 end if leadin is 2 or 3.
163
+            if (leadin == 2 || leadin == 3) {
164
+
165
+               // Fixes by jeffery.spirko@tamucc.edu.
166
+               cylinder (r1=diameter/2 - h + h*h_fac1*leadfac,
167
+                         r2=diameter/2 - h,
168
+                         h=h*h_fac1*leadfac, $fn=n_segments);
169
+               /*
170
+               cylinder (r1=diameter/2,
171
+                         r2=diameter/2 - h*h_fac1*leadfac,
172
+                         h=h*h_fac1*leadfac, $fn=n_segments);
173
+               */
174
+            }
175
+
176
+            // "Negative chamfer" z-max end if leadin is 1 or 2.
177
+            if (leadin == 1 || leadin == 2) {
178
+               translate ([0, 0, length + 0.05 - h*h_fac1*leadfac]) {
179
+
180
+                  cylinder (r1=tapered_diameter/2 - h,
181
+                            h=h*h_fac1*leadfac,
182
+                            r2=tapered_diameter/2 - h + h*h_fac1*leadfac,
183
+                            $fn=n_segments);
184
+                  /*
185
+                  cylinder (r1=tapered_diameter/2 - h*h_fac1*leadfac,
186
+                            h=h*h_fac1*leadfac,
187
+                            r2=tapered_diameter/2,
188
+                            $fn=n_segments);
189
+                  */
190
+               }
191
+            }
192
+         }
193
+      }
194
+
195
+      if (! internal) {
196
+
197
+         // Chamfer z=0 end if leadin is 2 or 3.
198
+         if (leadin == 2 || leadin == 3) {
199
+            difference () {
200
+               cylinder (r=diameter/2 + 1, h=h*h_fac1*leadfac, $fn=n_segments);
201
+
202
+               cylinder (r2=diameter/2, r1=diameter/2 - h*h_fac1*leadfac, h=h*h_fac1*leadfac,
203
+                         $fn=n_segments);
204
+            }
205
+         }
206
+
207
+         // Chamfer z-max end if leadin is 1 or 2.
208
+         if (leadin == 1 || leadin == 2) {
209
+            translate ([0, 0, length + 0.05 - h*h_fac1*leadfac]) {
210
+               difference () {
211
+                  cylinder (r=diameter/2 + 1, h=h*h_fac1*leadfac, $fn=n_segments);
212
+
213
+                  cylinder (r1=tapered_diameter/2, r2=tapered_diameter/2 - h*h_fac1*leadfac, h=h*h_fac1*leadfac,
214
+                            $fn=n_segments);
215
+               }
216
+            }
217
+         }
218
+      }
219
+   }
220
+}
221
+
222
+
223
+// ----------------------------------------------------------------------------
224
+// Input units in inches.
225
+// Note: units of measure in drawing are mm!
226
+module english_thread (diameter=0.25, threads_per_inch=20, length=1,
227
+                      internal=false, n_starts=1, thread_size=-1, groove=false,
228
+                      square=false, rectangle=0, angle=30, taper=0, leadin=0,
229
+                      leadfac=1.0, test=false)
230
+{
231
+   // Convert to mm.
232
+   mm_diameter = diameter*25.4;
233
+   mm_pitch = (1.0/threads_per_inch)*25.4;
234
+   mm_length = length*25.4;
235
+
236
+   echo (str ("mm_diameter: ", mm_diameter));
237
+   echo (str ("mm_pitch: ", mm_pitch));
238
+   echo (str ("mm_length: ", mm_length));
239
+   metric_thread (mm_diameter, mm_pitch, mm_length, internal, n_starts,
240
+                  thread_size, groove, square, rectangle, angle, taper, leadin,
241
+                  leadfac, test);
242
+}
243
+
244
+// ----------------------------------------------------------------------------
245
+module metric_thread_turns (diameter, pitch, length, internal, n_starts,
246
+                            thread_size, groove, square, rectangle, angle,
247
+                            taper)
248
+{
249
+   // Number of turns needed.
250
+   n_turns = floor (length/pitch);
251
+
252
+   intersection () {
253
+
254
+      // Start one below z = 0.  Gives an extra turn at each end.
255
+      for (i=[-1*n_starts : n_turns+1]) {
256
+         translate ([0, 0, i*pitch]) {
257
+            metric_thread_turn (diameter, pitch, internal, n_starts,
258
+                                thread_size, groove, square, rectangle, angle,
259
+                                taper, i*pitch);
260
+         }
261
+      }
262
+
263
+      // Cut to length.
264
+      translate ([0, 0, length/2]) {
265
+         cube ([diameter*3, diameter*3, length], center=true);
266
+      }
267
+   }
268
+}
269
+
270
+
271
+// ----------------------------------------------------------------------------
272
+module metric_thread_turn (diameter, pitch, internal, n_starts, thread_size,
273
+                           groove, square, rectangle, angle, taper, z)
274
+{
275
+   n_segments = segments (diameter);
276
+   fraction_circle = 1.0/n_segments;
277
+   for (i=[0 : n_segments-1]) {
278
+
279
+      // Keep polyhedron "facets" aligned -- circumferentially -- with base
280
+      // cylinder facets.  (Patch contributed by rambetter@protonmail.com.)
281
+      rotate ([0, 0, (i + 0.5)*360*fraction_circle + 90]) {
282
+         translate ([0, 0, i*n_starts*pitch*fraction_circle]) {
283
+            //current_diameter = diameter - taper*(z + i*n_starts*pitch*fraction_circle);
284
+            thread_polyhedron ((diameter - taper*(z + i*n_starts*pitch*fraction_circle))/2,
285
+                               pitch, internal, n_starts, thread_size, groove,
286
+                               square, rectangle, angle);
287
+         }
288
+      }
289
+   }
290
+}
291
+
292
+
293
+// ----------------------------------------------------------------------------
294
+module thread_polyhedron (radius, pitch, internal, n_starts, thread_size,
295
+                          groove, square, rectangle, angle)
296
+{
297
+   n_segments = segments (radius*2);
298
+   fraction_circle = 1.0/n_segments;
299
+
300
+   local_rectangle = rectangle ? rectangle : 1;
301
+
302
+   h = (square || rectangle) ? thread_size*local_rectangle/2 : thread_size / (2 * tan(angle));
303
+   outer_r = radius + (internal ? h/20 : 0); // Adds internal relief.
304
+   //echo (str ("outer_r: ", outer_r));
305
+
306
+   // A little extra on square thread -- make sure overlaps cylinder.
307
+   h_fac1 = (square || rectangle) ? 1.1 : 0.875;
308
+   inner_r = radius - h*h_fac1; // Does NOT do Dmin_truncation - do later with
309
+                                // cylinder.
310
+
311
+   translate_y = groove ? outer_r + inner_r : 0;
312
+   reflect_x   = groove ? 1 : 0;
313
+
314
+   // Make these just slightly bigger (keep in proportion) so polyhedra will
315
+   // overlap.
316
+   x_incr_outer = (! groove ? outer_r : inner_r) * fraction_circle * 2 * PI * 1.02;
317
+   x_incr_inner = (! groove ? inner_r : outer_r) * fraction_circle * 2 * PI * 1.02;
318
+   z_incr = n_starts * pitch * fraction_circle * 1.005;
319
+
320
+   /*
321
+    (angles x0 and x3 inner are actually 60 deg)
322
+
323
+                          /\  (x2_inner, z2_inner) [2]
324
+                         /  \
325
+   (x3_inner, z3_inner) /    \
326
+                  [3]   \     \
327
+                        |\     \ (x2_outer, z2_outer) [6]
328
+                        | \    /
329
+                        |  \  /|
330
+             z          |[7]\/ / (x1_outer, z1_outer) [5]
331
+             |          |   | /
332
+             |   x      |   |/
333
+             |  /       |   / (x0_outer, z0_outer) [4]
334
+             | /        |  /     (behind: (x1_inner, z1_inner) [1]
335
+             |/         | /
336
+    y________|          |/
337
+   (r)                  / (x0_inner, z0_inner) [0]
338
+
339
+   */
340
+
341
+   x1_outer = outer_r * fraction_circle * 2 * PI;
342
+
343
+   z0_outer = (outer_r - inner_r) * tan(angle);
344
+   //echo (str ("z0_outer: ", z0_outer));
345
+
346
+   //polygon ([[inner_r, 0], [outer_r, z0_outer],
347
+   //        [outer_r, 0.5*pitch], [inner_r, 0.5*pitch]]);
348
+   z1_outer = z0_outer + z_incr;
349
+
350
+   // Give internal square threads some clearance in the z direction, too.
351
+   bottom = internal ? 0.235 : 0.25;
352
+   top    = internal ? 0.765 : 0.75;
353
+
354
+   translate ([0, translate_y, 0]) {
355
+      mirror ([reflect_x, 0, 0]) {
356
+
357
+         if (square || rectangle) {
358
+
359
+            // Rule for face ordering: look at polyhedron from outside: points must
360
+            // be in clockwise order.
361
+            polyhedron (
362
+               points = [
363
+                         [-x_incr_inner/2, -inner_r, bottom*thread_size],         // [0]
364
+                         [x_incr_inner/2, -inner_r, bottom*thread_size + z_incr], // [1]
365
+                         [x_incr_inner/2, -inner_r, top*thread_size + z_incr],    // [2]
366
+                         [-x_incr_inner/2, -inner_r, top*thread_size],            // [3]
367
+
368
+                         [-x_incr_outer/2, -outer_r, bottom*thread_size],         // [4]
369
+                         [x_incr_outer/2, -outer_r, bottom*thread_size + z_incr], // [5]
370
+                         [x_incr_outer/2, -outer_r, top*thread_size + z_incr],    // [6]
371
+                         [-x_incr_outer/2, -outer_r, top*thread_size]             // [7]
372
+                        ],
373
+
374
+               faces = [
375
+                         [0, 3, 7, 4],  // This-side trapezoid
376
+
377
+                         [1, 5, 6, 2],  // Back-side trapezoid
378
+
379
+                         [0, 1, 2, 3],  // Inner rectangle
380
+
381
+                         [4, 7, 6, 5],  // Outer rectangle
382
+
383
+                         // These are not planar, so do with separate triangles.
384
+                         [7, 2, 6],     // Upper rectangle, bottom
385
+                         [7, 3, 2],     // Upper rectangle, top
386
+
387
+                         [0, 5, 1],     // Lower rectangle, bottom
388
+                         [0, 4, 5]      // Lower rectangle, top
389
+                        ]
390
+            );
391
+         } else {
392
+
393
+            // Rule for face ordering: look at polyhedron from outside: points must
394
+            // be in clockwise order.
395
+            polyhedron (
396
+               points = [
397
+                         [-x_incr_inner/2, -inner_r, 0],                        // [0]
398
+                         [x_incr_inner/2, -inner_r, z_incr],                    // [1]
399
+                         [x_incr_inner/2, -inner_r, thread_size + z_incr],      // [2]
400
+                         [-x_incr_inner/2, -inner_r, thread_size],              // [3]
401
+
402
+                         [-x_incr_outer/2, -outer_r, z0_outer],                 // [4]
403
+                         [x_incr_outer/2, -outer_r, z0_outer + z_incr],         // [5]
404
+                         [x_incr_outer/2, -outer_r, thread_size - z0_outer + z_incr], // [6]
405
+                         [-x_incr_outer/2, -outer_r, thread_size - z0_outer]    // [7]
406
+                        ],
407
+
408
+               faces = [
409
+                         [0, 3, 7, 4],  // This-side trapezoid
410
+
411
+                         [1, 5, 6, 2],  // Back-side trapezoid
412
+
413
+                         [0, 1, 2, 3],  // Inner rectangle
414
+
415
+                         [4, 7, 6, 5],  // Outer rectangle
416
+
417
+                         // These are not planar, so do with separate triangles.
418
+                         [7, 2, 6],     // Upper rectangle, bottom
419
+                         [7, 3, 2],     // Upper rectangle, top
420
+
421
+                         [0, 5, 1],     // Lower rectangle, bottom
422
+                         [0, 4, 5]      // Lower rectangle, top
423
+                        ]
424
+            );
425
+         }
426
+      }
427
+   }
428
+}
429
+
430
+
431
+

+ 359
- 0
hardware/trackball.scad View File

@@ -0,0 +1,359 @@
1
+/*
2
+ * Trackball
3
+ * Copyright 2022 Thomas Buck - thomas@xythobuz.de
4
+ *
5
+ * Required parts:
6
+ *  - 1x Raspberry Pi Pico
7
+ *  - 5x Cherry MX compatible switches and keycaps
8
+ *  - 1x Billard ball, diameter 38mm 
9
+ *  - 3x Si3N4 static bearing balls, diameter 3mm
10
+ *  - 3x spring, diameter 2mm, length 10mm
11
+ *  - 1x PMW3360 sensor with breakout board
12
+ *  - 8x M2 screw, length 5mm
13
+ *  - 8x M2 heat melt insert, length 4mm
14
+ *
15
+ * For the PMW3360 breakout board get this:
16
+ * https://github.com/jfedor2/pmw3360-breakout
17
+ *
18
+ * The "Threads" library used by this project is:
19
+ * Copyright 2022 Dan Kirshner - dan_kirshner@yahoo.com
20
+ *
21
+ * This program is free software: you can redistribute it and/or modify
22
+ * it under the terms of the GNU General Public License as published by
23
+ * the Free Software Foundation, either version 3 of the License, or
24
+ * (at your option) any later version.
25
+ *
26
+ * This program is distributed in the hope that it will be useful,
27
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
28
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
29
+ * GNU General Public License for more details.
30
+ *
31
+ * See <http://www.gnu.org/licenses/>.
32
+ */
33
+
34
+// https://www.thingiverse.com/thing:421524
35
+use <external/cherry_mx.scad>
36
+
37
+// https://dkprojects.net/openscad-threads/
38
+use <external/threads.scad>
39
+
40
+// #######################
41
+// #### Configuration ####
42
+// #######################
43
+
44
+ball_dia = 38.0;
45
+roller_dia = 3.0;
46
+
47
+roller_ball_h = 8;
48
+roller_count = 3;
49
+
50
+wall = 3.0;
51
+
52
+cut_roller_holder = false;
53
+draw_threads = false;
54
+
55
+// #######################
56
+// ## Raspberry Pi Pico ##
57
+// #######################
58
+
59
+pico_w = 21;
60
+pico_l = 51;
61
+pico_d = 1.6; // todo
62
+pico_hole_d = 2.1;
63
+pico_hole_x = 4.8;
64
+pico_hole_y = 2.0;
65
+pico_hole_d_x = 11.4;
66
+pico_hole_d_y = pico_l - 2 * pico_hole_y;
67
+pico_usb_w = 8.0;
68
+pico_usb_h = 3.0; // todo
69
+pico_usb_d = 10.0; // todo
70
+pico_usb_off = 1.3;
71
+
72
+pico_h = pico_d + 1; // todo
73
+
74
+// ######################
75
+// ### PMW3360 Sensor ###
76
+// ######################
77
+
78
+// https://github.com/jfedor2/pmw3360-breakout
79
+sensor_w = 22;
80
+sensor_l = 34;
81
+sensor_pcb_h = 1.6;
82
+sensor_hole_dia = 2.2;
83
+sensor_hole_off_x = 3.0;
84
+sensor_hole_off_y = 3.0;
85
+sensor_hole_dist_x = 16.0;
86
+sensor_hole_dist_y = 24.5;
87
+sensor_cut_w = 8.0 + 0.5;
88
+sensor_cut_h = 17.26;
89
+sensor_cut_off_x = 7.0 - 0.25;
90
+sensor_cut_off_y = 5.27;
91
+sensor_cut_edge_to_pin1 = 2.75;
92
+sensor_edge_to_pin1 = 1.52;
93
+
94
+sensor_ball_to_lens_top = 2.4;
95
+sensor_ball_to_chip_bottom = 9.81;
96
+
97
+sensor_chip_w = 9.1;
98
+sensor_chip_l = 16.2;
99
+sensor_chip_h = 2.21;
100
+
101
+sensor_pin_w = 0.5;
102
+sensor_pin_h = 4.51;
103
+sensor_pin_d = 0.2;
104
+sensor_pin_dist = 10.7;
105
+sensor_pin_off_top = 0.5;
106
+sensor_pin_pitch = 0.89;
107
+
108
+sensor_pin1_to_optical_center = 5.66;
109
+
110
+// ######################
111
+// ## MX Switch Cutout ##
112
+// ######################
113
+
114
+// https://geekhack.org/index.php?topic=70654.0
115
+mx_co_w = 14.0;
116
+mx_co_w_add = 0.8;
117
+mx_co_h = 14.0;
118
+mx_co_h_off_1 = 1.0;
119
+mx_co_h_off_2 = 3.5;
120
+mx_co_h_off_3 = mx_co_h - 2 * (mx_co_h_off_1 + mx_co_h_off_2);
121
+mx_co_r = 0.4;
122
+
123
+// https://geekhack.org/index.php?topic=71550.0
124
+mx_co_th = 1.5 - 0.1;
125
+mx_co_b_add = 1.0;
126
+mx_co_b_w = mx_co_w + mx_co_b_add;
127
+mx_co_b_h = mx_co_h + mx_co_b_add;
128
+
129
+mx_travel = 3.9;
130
+
131
+// ######################
132
+// ### Implementation ###
133
+// ######################
134
+
135
+roller_thread_dia = roller_dia + 5.0;
136
+roller_thread_pitch = 2.0;
137
+roller_h = roller_dia + 7.0;
138
+roller_ball_h_off = 0.6;
139
+roller_ball_hold_off = 0.5;
140
+roller_thread_hole = roller_dia + 0.5;
141
+roller_small_hole = sphere_r_at_h(roller_ball_hold_off, roller_dia / 2) * 2;
142
+
143
+roller_ridge_h = 1.5;
144
+roller_mount_angle_off = 90;
145
+roller_mount_dia = roller_thread_dia + 2.0;
146
+
147
+ball_h = 15; // todo
148
+
149
+switch_test_w = 25;
150
+
151
+$fn = 42;
152
+
153
+function sphere_r_at_h(h, r) = r * sin(acos(h / r));
154
+function sphere_angle_at_rh(h, r) = acos(h / r);
155
+
156
+module mx_switch_cutout(h) {
157
+    translate([-mx_co_w / 2 - mx_co_w_add, -mx_co_h / 2, 0]) {
158
+        linear_extrude(h + 1) {
159
+            translate([mx_co_w_add, 0]) {
160
+                square([mx_co_w, mx_co_h]);
161
+                
162
+                for (x = [mx_co_r / 2, mx_co_w - mx_co_r / 2])
163
+                for (y = [mx_co_r / 2, mx_co_h - mx_co_r / 2])
164
+                translate([x, y])
165
+                circle(r = mx_co_r);
166
+            }
167
+            
168
+            for (x = [0, mx_co_w + mx_co_w_add])
169
+            for (y = [0, mx_co_h_off_2 + mx_co_h_off_3])
170
+            translate([x, mx_co_h_off_1 + y, 0])
171
+            square([mx_co_w_add, mx_co_h_off_2]);
172
+        }
173
+        
174
+        translate([mx_co_w_add - mx_co_b_add / 2, -mx_co_b_add / 2, -1])
175
+        cube([mx_co_b_w, mx_co_b_h, h - mx_co_th + 1]);
176
+    }
177
+}
178
+
179
+module mx_switch_test() {
180
+    difference() {
181
+        translate([-switch_test_w / 2, -switch_test_w / 2, 0])
182
+        cube([switch_test_w, switch_test_w, wall]);
183
+        
184
+        mx_switch_cutout(wall);
185
+        
186
+        translate([0, -switch_test_w / 2 + 1, wall - 1.0])
187
+        linear_extrude(1.1)
188
+        text("switch test", size = 3, halign = "center");
189
+    }
190
+    
191
+    %translate([0, 0, wall])
192
+    rotate([0, 0, 180])
193
+    mx_switch($t);
194
+}
195
+
196
+module pico() {
197
+    translate([-pico_w / 2, -pico_l / 2, 0])
198
+    difference() {
199
+        union() {
200
+            color("green")
201
+            cube([pico_w, pico_l, pico_d]);
202
+            
203
+            translate([(pico_w - pico_usb_w) / 2, pico_l - pico_usb_d + pico_usb_off, pico_d])
204
+            cube([pico_usb_w, pico_usb_d, pico_usb_h]);
205
+        }
206
+        
207
+        for (x = [0, pico_hole_d_x])
208
+        for (y = [0, pico_hole_d_y])
209
+        translate([pico_hole_x + x, pico_hole_y + y, -1])
210
+        cylinder(d = pico_hole_d, h = pico_d + 2);
211
+    }
212
+}
213
+
214
+module sensor() {
215
+    translate([-sensor_w / 2, -sensor_l / 2, 0])
216
+    difference() {
217
+        color("green")
218
+        cube([sensor_w, sensor_l, sensor_pcb_h]);
219
+        
220
+        translate([sensor_cut_off_x, sensor_cut_off_y, -1])
221
+        cube([sensor_cut_w, sensor_cut_h, sensor_pcb_h + 2]);
222
+        
223
+        for (x = [0, sensor_hole_dist_x])
224
+        for (y = [0, sensor_hole_dist_y])
225
+        translate([sensor_hole_off_x + x, sensor_hole_off_y + y, -1])
226
+        cylinder(d = sensor_hole_dia, h = sensor_pcb_h + 2);
227
+    }
228
+    
229
+    color("#303030")
230
+    translate([-sensor_chip_w / 2, -sensor_l / 2 - sensor_chip_l + sensor_edge_to_pin1 + sensor_cut_off_y + sensor_cut_h - sensor_cut_edge_to_pin1, -sensor_chip_h])
231
+    cube([sensor_chip_w, sensor_chip_l, sensor_chip_h]);
232
+    
233
+    translate([0, -sensor_l / 2 - 15 * sensor_pin_pitch + sensor_cut_off_y + sensor_cut_h - sensor_cut_edge_to_pin1, 0])
234
+    for (p = [0 : 15])
235
+    translate([0, p * sensor_pin_pitch, 0])
236
+    for (x = [-sensor_pin_dist / 2, sensor_pin_dist / 2])
237
+    if (((p % 2 == 0) && (x < 0))
238
+            || ((p % 2 == 1) && (x > 0)))
239
+    translate([-sensor_pin_d / 2 + x, -sensor_pin_w / 2, -sensor_chip_h + sensor_pin_off_top])
240
+    cube([sensor_pin_d, sensor_pin_w, sensor_pin_h]);
241
+    
242
+    color("cyan")
243
+    translate([0, -sensor_l / 2 + sensor_cut_off_y + sensor_cut_h - sensor_cut_edge_to_pin1 - sensor_pin1_to_optical_center, -sensor_chip_h + 1])
244
+    cylinder(d = 0.2, h = sensor_ball_to_chip_bottom - 1);
245
+}
246
+
247
+module ball_and_roller() {
248
+    color("red")
249
+    sphere(d = ball_dia, $fn = 200);
250
+    
251
+    for (r = [0 : roller_count - 1])
252
+    rotate([0, 0, roller_mount_angle_off + 360 / roller_count * r])
253
+    translate([sphere_r_at_h(roller_ball_h - ball_dia / 2, ball_dia / 2), 0, -ball_dia / 2 + roller_ball_h])
254
+    rotate([0, 180 + sphere_angle_at_rh(roller_ball_h - ball_dia / 2, ball_dia / 2), 0])
255
+    translate([0, 0, -roller_dia / 2])
256
+    roller_holder();
257
+}
258
+
259
+module roller_holder() {
260
+    translate([0, 0, -roller_h + roller_dia / 2 - roller_ball_h_off])
261
+    difference() {
262
+        color("magenta")
263
+        union() {
264
+            translate([0, 0, roller_h - roller_ridge_h])
265
+            cylinder(d = roller_mount_dia, h = roller_ridge_h, $fn = 6);
266
+            
267
+            translate([0, 0, roller_h - roller_ridge_h])
268
+            scale([1, 1, -1])
269
+            metric_thread(diameter = roller_thread_dia,
270
+                    pitch = roller_thread_pitch,
271
+                    length = roller_h - roller_ridge_h,
272
+                    internal = false, n_starts = 1,
273
+                    taper = 0.1, leadin = 2,
274
+                    test = !draw_threads);
275
+        }
276
+        
277
+        translate([0, 0, -1]) {
278
+            cylinder(d = roller_thread_hole, h = roller_h + 1 - roller_dia / 2 + roller_ball_h_off + roller_ball_hold_off);
279
+            
280
+            cylinder(d = roller_small_hole, h = roller_h + 2);
281
+        }
282
+        
283
+        if (cut_roller_holder) {
284
+            translate([-roller_thread_dia / 2 - 1, -roller_thread_dia, -1])
285
+            cube([roller_thread_dia + 2, roller_thread_dia, roller_h + 2]);
286
+        }
287
+    }
288
+    
289
+    %color("blue")
290
+    sphere(d = roller_dia);
291
+}
292
+
293
+module roller_mount() {
294
+    difference() {
295
+        color("cyan")
296
+        cylinder(d = roller_mount_dia, h = roller_h - roller_ridge_h);
297
+        
298
+        translate([0, 0, roller_h - roller_ridge_h])
299
+        scale([1, 1, -1])
300
+        metric_thread(diameter = roller_thread_dia,
301
+                pitch = roller_thread_pitch,
302
+                length = roller_h - roller_ridge_h,
303
+                internal = true, n_starts = 1,
304
+                taper = 0, leadin = 2,
305
+                test = !draw_threads);
306
+        
307
+        if (cut_roller_holder) {
308
+            translate([-roller_thread_dia / 2 - 2, -roller_thread_dia, -1])
309
+            cube([roller_thread_dia + 4, roller_thread_dia, roller_h + 2]);
310
+        }
311
+    }
312
+}
313
+
314
+module roller_mount_test() {
315
+    roller_mount();
316
+    
317
+    translate([0, 0, roller_h - roller_dia / 2 + roller_ball_h_off])
318
+    roller_holder();
319
+}
320
+
321
+base_dia = pico_l + 9;
322
+
323
+module trackball() {
324
+    %translate([0, 0, ball_dia / 2 + ball_h])
325
+    ball_and_roller();
326
+    
327
+    %rotate([0, 180, 0])
328
+    pico();
329
+    
330
+    %translate([0, sensor_l / 2 - sensor_cut_off_y - sensor_cut_h + sensor_cut_edge_to_pin1 + sensor_pin1_to_optical_center, ball_h + sensor_chip_h - sensor_ball_to_chip_bottom])
331
+    sensor();
332
+    
333
+    translate([0, 0, ball_dia / 2 + ball_h])
334
+    for (r = [0 : roller_count - 1])
335
+    rotate([0, 0, roller_mount_angle_off + 360 / roller_count * r])
336
+    translate([sphere_r_at_h(roller_ball_h - ball_dia / 2, ball_dia / 2), 0, -ball_dia / 2 + roller_ball_h])
337
+    rotate([0, 180 + sphere_angle_at_rh(roller_ball_h - ball_dia / 2, ball_dia / 2), 0])
338
+    translate([0, 0, -roller_dia / 2])
339
+    translate([0, 0, -roller_h + roller_dia / 2 - roller_ball_h_off])
340
+    roller_mount();
341
+    
342
+    color("grey")
343
+    translate([0, 0, -8])
344
+    cylinder(d = base_dia, h = wall);
345
+}
346
+
347
+// ######################
348
+// ## Rendering Select ##
349
+// ######################
350
+
351
+//ball_and_roller();
352
+//pico();
353
+//sensor();
354
+//mx_switch_cutout(wall);
355
+//mx_switch_test();
356
+//roller_mount_test();
357
+
358
+//roller_holder();
359
+trackball();

Loading…
Cancel
Save