main.py (17555B)
1from types import SimpleNamespace 2from cadquery import Workplane 3from math import sin, cos, pi 4 5if "show_object" not in globals(): 6 show_object = lambda v: () 7 8nothing = 0.00001 9origin = (0, 0, 0) 10XY = Workplane("XY") 11center_xy = (True, True, False) 12m3hole = 2.8 / 2 13 14class V(SimpleNamespace): 15 def __add__(self, rhs): 16 assert(set(self.__dict__.keys()) <= set(rhs.__dict__.keys())) 17 items = [(k, v + rhs.__dict__[k]) for k,v in self.__dict__.items()] 18 return V(**dict(items)) 19 20 def __sub__(self, rhs): 21 assert(set(self.__dict__.keys()) <= set(rhs.__dict__.keys())) 22 items = [(k, v - rhs.__dict__[k]) for k,v in self.__dict__.items()] 23 return V(**dict(items)) 24 25 def __mul__(self, rhs): 26 assert(isinstance(rhs, int)) 27 items = [(k, v *rhs) for k,v in self.__dict__.items()] 28 return V(**dict(items)) 29 30 def xyz(self): 31 return (self.x, self.y, self.z) 32 33def Ext(part, bounds): 34 setattr(part, "bounds", bounds) 35 return part 36 37def V3(x, y, z): 38 return V(x=x, y=y, z=z) 39 40def V21(v): 41 return V3(2 * v, 2 * v, v) 42 43def box(*args): 44 return XY.box(*args, centered=center_xy) 45 46def cylinder(r, h): 47 return XY.circle(r).extrude(h) 48 49def build_usbc(width, height, depth, tol): 50 def shape(width, height, depth): 51 radius = height / 2 - nothing 52 offset = width / 2 - radius 53 part = box(width - radius * 2, height, depth) 54 part = part.union(cylinder(radius, depth).translate((-offset, 0, 0))) 55 part = part.union(cylinder(radius, depth).translate((offset, 0, 0))) 56 return part 57 def gen(tol): 58 dims = (width + 2 * tol, height + 2 * tol, depth + 2 * tol) 59 part = shape(*dims).translate((0, 0, -tol)) 60 if tol != 0: 61 part = part.union(shape(*dims[:2], 20).translate((0, 0, -20))) 62 return part 63 part = gen(0).translate((0, -height / 2, 0)).rotate(origin, (1, 0, 0), -90) 64 bounds = gen(tol).translate((0, -height / 2, 0)).rotate(origin, (1, 0, 0), -90) 65 return Ext(part, bounds) 66 67def build_usbc_board(board, usbc, tol, usbc_overhang, hole_radius, hole_offset, 68 bolt_radius, bolt_head_radius, bolt_depth, usbc_height): 69 def gen(tol): 70 part = box(*(board + V3(tol, tol, tol) * 2).xyz()).translate((0, 0, -tol)) 71 part_usbc = build_usbc(**usbc.__dict__) 72 if tol != 0: 73 part_usbc = part_usbc.bounds 74 part = part.union(part_usbc.rotate(origin, (0, 0, 1), 180)\ 75 .translate((0, board.y / 2 + usbc_overhang, usbc_height))) 76 for x in (-1, 1): 77 if tol != 0: 78 part = part.union(cylinder(bolt_head_radius, 10) 79 .translate((x * (board.x / 2 - hole_offset), board.y / 2 - hole_offset, board.z + tol))) 80 part = part.union(cylinder(m3hole, board.z + 2 * tol + bolt_depth) 81 .translate((x * (board.x / 2 - hole_offset), board.y / 2 - hole_offset, -tol-bolt_depth))) 82 else: 83 part = part.cut(cylinder(hole_radius, board.z) 84 .translate((x * (board.x / 2 - hole_offset), board.y / 2 - hole_offset, 0))) 85 return part.translate((0, -board.y/2, 0)) 86 return Ext(gen(0), gen(tol)) 87 88def build_bolt(bolt, shaft, tol): 89 height = shaft + bolt.head_height 90 91 def gen(tol): 92 part = cylinder(bolt.head_radius + tol, bolt.head_height + 2 * tol)\ 93 .translate((0, 0, height + 2 * tol - bolt.head_height - 2 * tol)) 94 if tol != 0: 95 part = part.union(cylinder(bolt.cut_radius + tol, height + 2 * tol)) 96 part = part.translate((0, 0, -tol)) 97 else: 98 part = part.union(cylinder(bolt.radius, height)) 99 return part 100 101 return Ext(gen(0), gen(tol)) 102 103def build_rod(rod, tol): 104 part = XY.circle(rod.outer).circle(rod.inner).extrude(rod.len) 105 106 bounds = cylinder(rod.outer + tol, rod.len + 2 * tol).translate((0, 0, -tol)) 107 108 return Ext(part, bounds) 109 110def build_oled(outer, screen, tol, hole, pins, pinlen, ribbon): 111 def gen(tol): 112 slab = outer.z - screen.z 113 lower = V3(outer.x + 2 * tol, outer.y + 2 * tol, slab + 2 * tol) 114 upper = screen + V3(tol, tol, (10 if tol != 0 else 0)) * 2 115 part2 = box(*upper.xyz()).translate((0, 0, lower.z)) 116 part = box(*lower.xyz()).union(part2).translate((0, 0, -tol)) 117 for y in range(-1, 2, 2): 118 for x in range(-1, 2, 2): 119 part = part.cut(cylinder(hole - tol, outer.z - tol) 120 .translate((x * 21 / 2, y * 22 / 2, 0))) 121 part = part.union(box(*pins.xyz()).translate((0, outer.y / 2 - pins.y / 2, slab))) 122 part = part.union(box(pins.x, pins.y, pinlen)\ 123 .translate((0, outer.y / 2 - pins.y / 2, slab - pinlen))) 124 if tol != 0: 125 part = part.union(box(*(ribbon + V21(tol)).xyz()).translate((0, -outer.y / 2 + ribbon.y / 2, slab))) 126 return part 127 return Ext(gen(0), gen(tol)) 128 129def build_antenna(radius, height, top_offset, top_space, top_radius, tol, hex_height, hex_radius): 130 def gen(tol): 131 part = cylinder(radius + tol, height + 2 * tol).translate((0, 0, -tol)) 132 if tol != 0: 133 part = part.union(cylinder(top_radius + 2 * tol, top_space + tol).translate((0, 0, top_offset - tol))) 134 part = part.union(cylinder(top_radius + tol + 4, height + tol - top_offset - top_space) \ 135 .translate((0, 0, top_offset + top_space))) 136 part = part.union(XY.polygon(6, hex_radius + tol * 2) \ 137 .extrude(10 + hex_height + tol).translate((0, 0, -10))) 138 else: 139 part = part.union(cylinder(top_radius, height - top_offset).translate((0, 0, top_offset))) 140 part = part.union(XY.polygon(6, hex_radius).extrude(hex_height)) 141 return part 142 return Ext(gen(0), gen(tol)) 143 144def build_base(base, rod, rod_shaft, usbc_board, bolt, wall, tol, lid, board_space, debug=False): 145 outer = base 146 inner = outer - V3(2 * wall.x, 2 * wall.y, wall.z) + V21(tol) 147 148 tower_radius = bolt.radius + wall.x + 1 149 tower_height = inner.z 150 151 floor = wall.z - tol 152 part = box(*base.xyz()) 153 part = part.cut(box(*inner.xyz()).translate((0, 0, floor))) 154 155 antenna_wall_radius = lid.antenna.radius + 4 156 antenna_rod = cylinder(antenna_wall_radius+wall.x, tower_height) \ 157 .cut(cylinder(antenna_wall_radius, tower_height)) 158 part = part.union(antenna_rod.translate((lid.antenna_offset + V3(0, -outer.y / 2, floor)).xyz())) 159 part = part.union(antenna_rod.translate((lid.antenna_offset + V3(0, -outer.y / 2, floor) - V3(2 * lid.antenna_offset.x, 0, 0)).xyz())) 160 part = part.union(box(abs(lid.antenna_offset.x)*2-2*nothing, (antenna_wall_radius+wall.x)*2, tower_height)\ 161 .translate((0, lid.antenna_offset.y - outer.y / 2, floor))) 162 163 board_tol = 2 * tol 164 main_wall_dist = board_space.x + 2 * board_tol + wall.x 165 part = part.union(box(wall.x, inner.y, tower_height).translate((-main_wall_dist/2, 0, floor))) 166 part = part.union(box(wall.x, inner.y, tower_height).translate((main_wall_dist/2, 0, floor))) 167 168 part = part.cut(box(main_wall_dist - wall.x, inner.y, tower_height).translate((0, 0, floor))) 169 part = part.cut(box(abs(lid.antenna_offset.x)*2, antenna_wall_radius*2, tower_height)\ 170 .translate((0, lid.antenna_offset.y - outer.y / 2, floor))) 171 172 for y in range(-1, 2, 2): 173 for x in range(-1, 2, 2): 174 pos = V3(x * lid.hole_spacing.x / 2, y * lid.hole_spacing.y / 2, floor) 175 offset_x = (inner.x - lid.hole_spacing.x) / 2 176 offset_y = (inner.y - lid.hole_spacing.y) / 2 177 part = part.union(box(offset_x, offset_y + tower_radius, tower_height) \ 178 .translate((pos + V3(offset_x*x/2, y*(offset_y-(offset_y+tower_radius)/2), 0)).xyz())) 179 part = part.union(box(offset_x + tower_radius, offset_y, tower_height) \ 180 .translate((pos + V3(x*(offset_x-(offset_x+tower_radius)/2), offset_y*y/2, 0)).xyz())) 181 part = part.union(cylinder(tower_radius, tower_height).translate(pos.xyz())) 182 183 # rod cut-out 184 rod_pos = rod_shaft.pos + V3(0, 0, floor) 185 base_rod_outer = rod.outer + 6 186 part = part.union(cylinder(base_rod_outer, tower_height).translate(rod_pos.xyz())) 187 188 fill_y = inner.y / 2 - rod_shaft.pos.y 189 part = part.union(box(main_wall_dist, 2 * base_rod_outer - 4, tower_height).translate(rod_pos.xyz()).translate((0, 1, 0))) 190 part_rod = cylinder(rod.outer + tol, tower_height).translate(rod_pos.xyz()) 191 part = part.cut(part_rod) 192 part = part.union(box(main_wall_dist, fill_y, 3).translate(rod_pos.xyz())\ 193 .translate((0, -rod_shaft.pos.y+inner.y/2-fill_y/2, 0)) \ 194 .intersect(part_rod)) 195 #part = part.cut(cylinder(rod_hole.radius, 50).rotate(origin, (1, 0, 0), 90) 196 # .translate((rod_pos + V3(0, 0, rod_hole.height)).xyz())) 197 part = part.cut(box(20, 25, 8).translate(rod_pos.xyz()).translate((0, -rod.outer - wall.x/2))) 198 part = part.cut(box(8, 40, 5).translate(rod_pos.xyz()).translate((0, 10))) 199 200 part = part.cut(build_lid(**lid.__dict__).bounds.translate((0, 0, base.z - lid.base.z))) 201 202 board_hole = V(outer = 3, inner = m3hole, height = 4) 203 print("Board Space", board_space) 204 board_space_inner = board_space - V3(2 * board_hole.outer, 2 * board_hole.outer, 0) 205 board_center = V3(0, -inner.y / 2 + board_space.y / 2 + board_tol, floor) 206 for y in (-1, 1): 207 for x in (-1, 1): 208 pos = board_center + V3(x * board_space_inner.x / 2, y * board_space_inner.y / 2, 0) 209 part = part.union(cylinder(board_hole.outer, board_hole.height).translate(pos.xyz())) 210 dist = board_hole.outer + board_tol 211 if y == 1: 212 part = part.union(box(dist, board_hole.outer * 2, board_hole.height-nothing) 213 .translate((pos.x + x * dist / 2, pos.y, pos.z))) 214 else: 215 dist2 = dist + board_hole.outer 216 part = part.union(box(dist, dist2, board_hole.height) 217 .translate((pos.x + x * dist / 2, 218 pos.y - dist + dist2 / 2, pos.z))) 219 part = part.union(box(dist2, dist, board_hole.height) 220 .translate((pos.x + x * (dist - dist2 / 2), 221 pos.y - dist / 2, pos.z))) 222 part = part.cut(cylinder(board_hole.inner, board_hole.height).translate(pos.xyz())) 223 224 part_usbc = build_usbc_board(**usbc_board.__dict__) 225 usbc_board_pos = V3(0, outer.y / 2 - usbc_board.usbc_overhang, floor + 4) 226 part = part.union(box(usbc_board.board.x, usbc_board.board.y, 4)\ 227 .translate((usbc_board_pos - V3(0, usbc_board.board.y / 2, 4)).xyz())) 228 part = part.cut(part_usbc.bounds.translate(usbc_board_pos.xyz())) 229 if debug: 230 part = part.union(part_usbc.translate(usbc_board_pos.xyz())) 231 232 return part 233 234def build_lid(base, rod, rod_shaft, wall, bolt, bolt_depth, tol, hole_spacing, 235 oled, oled_offset, antenna, antenna_offset, rod_support, debug=False): 236 outer = base 237 inner = base - V21(wall + tol * 3) 238 rod_wall = V(outer = rod.outer + tol * 2 + wall, inner = rod.outer + tol * 2, z = rod_shaft.height) 239 240 part_bolt = build_bolt(bolt, bolt_depth, tol) 241 242 def gen(tol): 243 outer_padded = outer + V3(tol, tol, tol) * 4 244 inner_padded = inner - V3(tol, tol, tol) * 4 245 part = box(*outer_padded.xyz()).translate((0, 0, -tol*2)) 246 part = part.cut(box(*inner_padded.xyz()).translate((0, 0, -tol*2))) 247 for y in range(-1, 2, 2): 248 for x in range(-1, 2, 2): 249 pos = V3(x * hole_spacing.x / 2, y * hole_spacing.y / 2, outer.z) 250 head_radius = bolt.head_radius + wall + tol * 2 251 head_height = bolt.head_height + wall + tol * 2 252 offset_x = (outer.x - 2 * wall - hole_spacing.x) / 2 + tol * 2 253 offset_y = (outer.y - 2 * wall - hole_spacing.y) / 2 + tol * 2 254 part = part.union(cylinder(head_radius, head_height) \ 255 .translate((pos + V3(0, 0, -head_height)).xyz())) 256 part = part.union(box(offset_x, offset_y + head_radius, head_height) \ 257 .translate((pos + V3(offset_x*x/2, y*(offset_y-(offset_y+head_radius)/2), -head_height)).xyz())) 258 part = part.union(box(offset_x + head_radius, offset_y, head_height) \ 259 .translate((pos + V3(x*(offset_x-(offset_x+head_radius)/2), offset_y*y/2, -head_height)).xyz())) 260 bolt_pos = (pos + V3(0, 0, -bolt_depth - bolt.head_height)).xyz() 261 if tol != 0: 262 part = part.union(part_bolt.bounds.translate(bolt_pos)) 263 else: 264 part = part.cut(part_bolt.bounds.translate(bolt_pos)) 265 part = part.cut(cylinder(bolt.radius + tol, bolt_depth + bolt.head_height).translate(bolt_pos)) 266 for r in range(0, 360, 90): 267 part = part.cut(box(outer.x, 8, 2).rotate(origin, (1, 0, 0), -45) 268 .translate((0, outer.x / 2, outer.z - 2)).rotate(origin, (0, 0, 1), r)) 269 270 oled_pos = (oled_offset + V3(0, -outer_padded.y / 2, outer_padded.z - oled.outer.z)).xyz() 271 part_oled = build_oled(**oled.__dict__) 272 part = part.union(box(*oled.outer.xyz()).translate(oled_pos)) 273 part = part.cut(part_oled.bounds.translate(oled_pos)) 274 if debug: 275 part = part.union(part_oled.translate(oled_pos)) 276 277 antenna_pos = (antenna_offset + V3(0, -outer_padded.y / 2, outer_padded.z - antenna.top_offset - antenna.top_space)).xyz() 278 part_antenna = build_antenna(**antenna.__dict__) 279 part = part.union(cylinder(antenna.top_radius + 2, antenna.height).translate(antenna_pos)) 280 part = part.cut(part_antenna.bounds.translate(antenna_pos)) 281 if debug: 282 part = part.union(part_antenna.translate(antenna_pos)) 283 284 part = part.union(cylinder(rod.outer + tol * 2 + rod_support.thickness, rod_support.height + 2 * tol) 285 .translate((rod_shaft.pos + V3(0, 0, inner.z - rod_support.height - 2 * tol)).xyz())) 286 287 return part 288 289 part = gen(0) 290 part_rod_wall = cylinder(rod_wall.outer + 5 + tol * 2, 10) 291 part = part.cut(part_rod_wall.translate((rod_shaft.pos + V3(0, 0, outer.z - 1)).xyz())) 292 part = part.cut(build_rod(rod, tol).bounds.translate((rod_shaft.pos + V3(0, 0, inner.z - rod_support.height - 2 * tol)).xyz())) 293 294 part_bounds = gen(tol) 295 296 return Ext(part, part_bounds) 297 298def build_sleeve(rod, rod_shaft, tol, wall): 299 rod_wall = V(outer = rod.outer + tol + wall, inner = rod.outer + tol, z = rod_shaft.height) 300 part = cylinder(rod_wall.outer + 5, 2) 301 part = part.union(cylinder(rod_wall.outer, rod_wall.z)) 302 part = part.cut(build_rod(rod, tol).bounds) 303 return part 304 305def main(): 306 # TEST PRINT 307 base = V3(80, 80, 15) 308 rod_shaft = V(pos = V3(0, 0, 0), height = 20) 309 310 base = V3(120, 120, 30) 311 rod_shaft = V(pos = V3(0, 23, 0), height = 35) 312 313 tol = 0.05 314 rod = V(outer = 25.2 / 2, inner = 21 / 2, len = 500) 315 thin_wall = 2.4 316 thick_wall = 4 317 bolt = V(radius = 3 / 2, head_radius = 5.4 / 2, head_height = 3, cut_radius = m3hole) 318 319 oled = V(outer = V3(25.5, 26, 4), screen = V3(24.9, 16.6, 1.6), 320 tol = tol * 2, hole = 1, pins = V3(4 * 2.54, 2.54, 1), 321 pinlen = 8, ribbon = V3(9, 5, 1.1)) 322 323 antenna = V(radius = 6.2/2, height = 13, top_offset = 7.8, 324 top_radius = 7.6/2, top_space = 2.8, tol = tol * 2, 325 hex_height = 2, hex_radius = 9) 326 327 lid = V3(base.x, base.y, 10) 328 lid_hole_spacing = V3(lid.x - 15, lid.y - 15, 0) 329 insert = V(radius=5 / 2, height=5) 330 base_wall = V3(thick_wall, thick_wall, thin_wall) 331 332 bolt_depth = base.z - bolt.head_height # let bolt go through 333 334 usbc = V(width = 9, height = 3, depth = 7.3, tol = tol * 2) 335 usbc_board = V(usbc = usbc, board = V3(22, 13, 2), usbc_overhang = 1, 336 hole_radius = 3.2/2, hole_offset = 2.8, tol = tol, 337 bolt_radius = 3/2, bolt_head_radius = 3, bolt_depth = 4, usbc_height = 1.70) 338 339 board_space = V3(36, 48, 10) 340 rod_support = V(thickness = thin_wall, height = 9) 341 #rod_hole = V(height = 12, radius = m3hole) 342 343 #show_object(build_rod(rod).translate((0, 0, 10))) 344 345 sleeve_ctx = V(rod=rod, rod_shaft=rod_shaft, tol=tol, wall=thin_wall) 346 #show_object(build_sleeve(**sheath_ctx.__dict__).translate((-100, 0, 0))) 347 348 lid_ctx = V(base=lid, rod=rod, rod_shaft=rod_shaft, 349 wall=thin_wall, bolt=bolt, bolt_depth=bolt_depth, tol=tol, 350 hole_spacing=lid_hole_spacing, oled=oled, oled_offset=V3(0, 20, 0), 351 antenna=antenna, antenna_offset=V3(-30, 20, 0), rod_support=rod_support) 352 show_object(build_lid(**lid_ctx.__dict__).translate((0, 0, 50))) 353 354 base_ctx = V(base=base, rod=rod, rod_shaft=rod_shaft, bolt=bolt, 355 wall=base_wall, tol=tol, usbc_board=usbc_board, 356 board_space=board_space) 357 #show_object(build_base(**base_ctx.__dict__, lid=lid_ctx)) 358 359 #show_object(build_oled(**oled.__dict__).bounds) 360 361 #show_object(build_antenna(**antenna.__dict__).bounds) 362 363 #show_object(build_usbc_board(**usbc_board.__dict__).bounds) 364 365main()