1 /* 2 Copyright (C) 2014 Paul Boddie 3 4 This program is free software; you can redistribute it and/or modify it under 5 the terms of the GNU General Public License as published by the Free Software 6 Foundation; either version 3 of the License, or (at your option) any later 7 version. 8 9 This program is distributed in the hope that it will be useful, but WITHOUT 10 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 12 more details. 13 14 You should have received a copy of the GNU General Public License along with 15 this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 module cartridge() 19 { 20 $fn = 50; 21 22 /* 23 Rounding/fillet radius and additional margin of subtracted 24 material. The additional margin helps avoid geometry problems. 25 */ 26 27 rr = 2; 28 ro = rr; 29 extra = 0.1; 30 31 module fillet(r, h) { 32 translate([0, 0, -h/2]) 33 difference() { 34 cube([r + extra, r + extra, h + extra]); 35 cylinder(r = r, h = h); 36 } 37 } 38 39 /* 40 Justify an object of the given dimensions, according to the given 41 factors (where 1 indicates moving the object to the positive side of an 42 axis, and -1 indicates moving it to the negative side of an axis). 43 44 NOTE: child should eventually be replaced by children. 45 */ 46 module justify(width, depth, height, wdir, ddir, hdir) { 47 translate([ 48 wdir * width / 2, 49 ddir * depth / 2, 50 hdir * height / 2]) 51 child(); 52 } 53 54 /* 55 Make a cuboid of the given dimensions, justifying it according to the given 56 factors, and moving it to the specified coordinates. 57 58 NOTE: Usage of justify within this module will not work due to recursion 59 NOTE: limitations in openscad, potentially removed in more recent 60 NOTE: releases. Thus, the justify transform is merged in here. 61 */ 62 module cube_at(width, depth, height, wdir, ddir, hdir, x, y, z) { 63 translate([ 64 x + wdir * width / 2, 65 y + ddir * depth / 2, 66 z + hdir * height / 2]) 67 cube([width, depth, height], center = true); 68 } 69 70 /* Cartridge dimensions. */ 71 72 width = 90.0; 73 height = 68.3; 74 depth = 16.5; 75 front_depth = 6.5; 76 77 /* How much extra depth the back provides for mating with the front. */ 78 groove_width_extra = 2.0; 79 back_depth = depth - front_depth + groove_width_extra; 80 81 /* Side thicknesses. */ 82 83 front = 2; 84 back = 3.5; 85 top = 3; 86 side = 2; /* increased from 1.5 for 3D printing reliability */ 87 88 front_side = side; 89 front_left = front_side; 90 front_right = front_side; 91 back_side = side; 92 back_left = back_side; 93 back_right = back_side; 94 bottom = 1; 95 96 /* Label details. */ 97 98 front_label_width = 83.0; 99 front_label_height = 46.0; 100 front_label_depth = 1.0; 101 front_label_offset_from_bottom = 19.5; 102 103 top_label_width = front_label_width; 104 top_label_height = 11.5; /* the height as seen from above */ 105 top_label_depth = front_label_depth; 106 top_label_offset_from_front = 2.5; 107 108 /* The groove around the sides and top. */ 109 110 groove_width_exposed = 1.5; 111 groove_width = groove_width_exposed + groove_width_extra; 112 groove_depth = 1.0; /* how deep the groove goes into each side */ 113 114 /* Additional cutting to mate the back and front. */ 115 116 top_groove_width = groove_width_extra; 117 top_groove_depth = 2.0; 118 119 /* 120 Space for the inner edge of the back inside the front. 121 Offsets are measured from the outside surfaces. 122 */ 123 124 inner_top_front_cutout_width = width - back_left - back_right; 125 inner_top_front_cutout_depth = top_groove_width; 126 inner_top_front_cutout_height = top - top_groove_depth; 127 inner_top_front_cutout_offset = top_groove_depth; 128 129 inner_side_front_cutout_height = height - inner_top_front_cutout_offset; 130 inner_side_front_cutout_width = front_side - groove_depth; 131 inner_side_front_cutout_depth = groove_width_extra; 132 inner_side_front_cutout_offset = groove_depth; 133 134 /* 135 The back cavity is the indented part at the bottom of the back of the 136 cartridge. 137 */ 138 139 back_cavity_width = 68.0; 140 back_cavity_inner_width = 65.0; 141 back_cavity_offset_from_left = 10.5; 142 back_cavity_inner_offset_from_left = 12.0; 143 back_cavity_height = 13.5; 144 back_cavity_inner_height = 12.0; 145 back_cavity_depth = 1.5; 146 147 /* The effect of the cavity on the inside of the case. */ 148 149 inner_back_cavity_offset = 1.0; 150 inner_back_cavity_offset_from_left = back_left; 151 inner_back_slope_offset_from_left = 10.0; 152 inner_back_slope_width = 2.5; 153 inner_back_slope_depth = 2.5; 154 155 inner_back_edge_offset = 2.0; 156 inner_back_edge_width = 69.0; 157 inner_back_edge_height = 3.0; 158 inner_back_edge_depth = 1.5; 159 160 inner_front_edge_offset = 0.5; 161 inner_front_edge_width = width - front_side * 2; 162 inner_front_edge_height = 3.0; 163 inner_front_edge_depth = 1.5; 164 165 /* The height of the floor of the compartment above the very bottom. */ 166 167 bottom_from_base = 13.5; 168 169 edge_connector_cutout_front_offset = 1.0; 170 edge_connector_cutout_front_depth = 1.0; 171 edge_connector_cutout_front_width = 15.0; 172 edge_connector_cutout_back_depth = 3.0; 173 edge_connector_cutout_back_width = 57.5; 174 175 /* 176 Edge connectors are themselves 0.05" or approximately 1.27mm in 177 thickness according to the Acorn Electron Cartridge Interface Specification 178 (Acorn Support Application Group Note 014). 179 */ 180 181 /* Extra internal features. Depths include front and back surfaces. */ 182 183 pcb_back_support_width = 1.2; 184 pcb_back_support_depth = back_depth - 185 edge_connector_cutout_back_depth; 186 pcb_back_support_height = height - bottom_from_base - top - bottom; 187 188 pcb_front_support_width = 1.2; 189 pcb_front_support_depth = front_depth; 190 pcb_front_support_height = pcb_back_support_height; 191 192 /* 193 Features measured from the Stardot Dual ROM Adaptor cartridge board 194 dimensions diagram. Offsets are from inside the bottom "floor". 195 */ 196 197 pcb_back_support_bump_width = pcb_front_support_width; 198 pcb_back_support_bump_depth = 1.5; 199 pcb_back_support_left_bump_height = 13.2; 200 pcb_back_support_right_bump_height = 10.7; 201 pcb_back_support_left_bump_offset_from_bottom = 15.1; 202 pcb_back_support_right_bump_offset_from_bottom = 17.6; 203 pcb_back_support_top_bump_height = 3.8; 204 205 /* Move the PCB support towards the centre. */ 206 pcb_support_margin = 1.75; 207 pcb_support_offset_from_centre = edge_connector_cutout_back_width / 2 208 - pcb_support_margin; 209 210 pcb_lug_depth = pcb_back_support_depth + 211 pcb_back_support_bump_depth; 212 pcb_lug_inner_radius = 1.0; 213 pcb_lug_outer_radius = 5.5 / 2; 214 pcb_lug_offset_from_bottom = 14.35; 215 pcb_lug_offset_from_outside = 7.55; 216 217 pcb_lug_cross_width = 6.7; 218 pcb_lug_cross_depth = pcb_back_support_depth; 219 pcb_lug_cross_height = 1.4; 220 221 pcb_front_lug_depth = pcb_back_support_bump_depth + 222 pcb_front_support_depth - front; 223 pcb_front_lug_inner_radius = pcb_lug_outer_radius; 224 pcb_front_lug_outer_radius = pow( 225 pow(pcb_lug_cross_width / 2, 2) + 226 pow(pcb_lug_cross_height / 2, 2), 227 0.5); 228 229 /* Repeated constructs. */ 230 231 module pcb_support(xdir, bump_height, bump_offset) { 232 233 /* 234 Translate the support in the stated direction. 235 Since the support is already justified in the direction, the translation 236 moves the inner edge of the support to alignment with the end of the 237 edge connector cutout and then back by the PCB support margin. 238 */ 239 240 translate([xdir * 241 pcb_support_offset_from_centre, 242 edge_connector_cutout_back_depth, 243 -height / 2 + bottom + bottom_from_base]) 244 justify(pcb_back_support_width, 245 pcb_back_support_depth, 246 pcb_back_support_height, 247 xdir, 1, 1) 248 union() { 249 250 /* Underlying support strut. */ 251 252 cube([pcb_back_support_width, 253 pcb_back_support_depth, 254 pcb_back_support_height], center = true); 255 256 /* Middle bump. */ 257 258 cube_at(pcb_back_support_bump_width, 259 pcb_back_support_bump_depth, 260 bump_height, 261 0, -1, 1, 262 0, 263 -pcb_back_support_depth / 2, 264 -pcb_back_support_height / 2 + bump_offset); 265 266 /* Top bump. */ 267 268 cube_at(pcb_back_support_bump_width, 269 pcb_back_support_bump_depth, 270 pcb_back_support_top_bump_height, 271 0, -1, 1, 272 0, 273 -pcb_back_support_depth / 2, 274 pcb_back_support_height / 2 - 275 pcb_back_support_top_bump_height); 276 } 277 } 278 279 module pcb_lug(xdir) { 280 translate([xdir * 281 (width/2 - pcb_lug_offset_from_outside), 282 back_depth, 283 -height / 2 + bottom + bottom_from_base + 284 pcb_lug_offset_from_bottom 285 ]) 286 rotate([90, 0, 0]) 287 difference() { 288 union() { 289 cylinder(h=pcb_lug_depth, r=pcb_lug_outer_radius); 290 cube_at(pcb_lug_cross_width, 291 pcb_lug_cross_height, pcb_lug_cross_depth, 292 0, 0, 1, 293 0, 0, 0); 294 cube_at(pcb_lug_cross_height, 295 pcb_lug_cross_width, pcb_lug_cross_depth, 296 0, 0, 1, 297 0, 0, 0); 298 } 299 cylinder(h=pcb_lug_depth, r=pcb_lug_inner_radius); 300 } 301 } 302 303 module pcb_front_lug(xdir) { 304 translate([xdir * 305 (width/2 - pcb_lug_offset_from_outside), 306 -front_depth + front + pcb_front_lug_depth, 307 -height / 2 + bottom + bottom_from_base + 308 pcb_lug_offset_from_bottom 309 ]) 310 rotate([90, 0, 0]) 311 difference() { 312 cylinder(h=pcb_front_lug_depth, 313 r=pcb_front_lug_outer_radius); 314 cylinder(h=pcb_front_lug_depth, 315 r=pcb_front_lug_inner_radius); 316 } 317 } 318 319 /* The actual shapes. */ 320 321 translate([-width * 0.6, 0, 0]) 322 difference() { 323 324 /* The cartridge surfaces. */ 325 326 union() { 327 328 /* Front portion. */ 329 330 translate([0, -front_depth + front / 2, 0]) 331 cube([width, front, height], center = true); 332 translate([-width / 2 + front_left / 2, -front_depth / 2, 0]) 333 cube([front_left, front_depth, height], center = true); 334 translate([width / 2 - front_right / 2, -front_depth / 2, 0]) 335 cube([front_right, front_depth, height], center = true); 336 translate([0, -front_depth / 2, height / 2 - top / 2]) 337 cube([width, front_depth, top], center = true); 338 difference() { 339 340 /* Floor of cartridge. */ 341 342 cube_at(width, front_depth, bottom, 343 0, -1, 1, 344 0, 0, -height / 2 + bottom_from_base); 345 346 /* Left cutout. */ 347 348 cube_at(edge_connector_cutout_front_width, 349 edge_connector_cutout_front_depth, 350 bottom, 351 1, -1, 1, 352 -width / 2 + edge_connector_cutout_front_offset, 353 0, 354 -height / 2 + bottom_from_base); 355 356 /* Right cutout. */ 357 358 cube_at(edge_connector_cutout_front_width, 359 edge_connector_cutout_front_depth, 360 bottom, 361 -1, -1, 1, 362 width / 2 - edge_connector_cutout_front_offset, 363 0, 364 -height / 2 + bottom_from_base); 365 } 366 367 /* PCB supports. */ 368 369 cube_at(pcb_front_support_width, 370 pcb_front_support_depth, 371 pcb_front_support_height, 372 1, -1, 1, 373 -edge_connector_cutout_back_width / 2 + 374 pcb_support_margin, 375 0, 376 -height / 2 + bottom + bottom_from_base); 377 378 cube_at(pcb_front_support_width, 379 pcb_front_support_depth, 380 pcb_front_support_height, 381 -1, -1, 1, 382 edge_connector_cutout_back_width / 2 - 383 pcb_support_margin, 384 0, 385 -height / 2 + bottom + bottom_from_base); 386 387 /* Circular "lugs" to hold PCBs in place. */ 388 389 pcb_front_lug(-1); 390 pcb_front_lug(1); 391 } 392 393 /* Label insets. */ 394 395 union() { 396 397 /* Front label. */ 398 399 translate([-front_label_width / 2, -front_depth, 400 front_label_offset_from_bottom - height / 2]) 401 cube([front_label_width, front_label_depth, 402 front_label_height]); 403 404 /* Top label. */ 405 406 translate([-top_label_width / 2, 407 -front_depth + top_label_offset_from_front, 408 height / 2 - top_label_depth]) 409 cube([top_label_width, top_label_height, 410 top_label_depth]); 411 } 412 413 /* Inner front edge cavity. */ 414 415 translate([inner_front_edge_width / 2, 416 -front_depth + inner_front_edge_offset, -height / 2]) 417 rotate([0, -90, 0]) 418 linear_extrude(height = inner_front_edge_width) 419 polygon([ 420 [-extra, 0], 421 [0, 0], 422 [inner_front_edge_height, inner_front_edge_depth], 423 [-extra, inner_front_edge_depth], 424 ]); 425 426 /* Inner top cutout for the top and sides of the back portion. */ 427 428 translate([0, -inner_top_front_cutout_depth / 2, height / 2 - 429 inner_top_front_cutout_offset - 430 inner_top_front_cutout_height / 2]) 431 cube([inner_top_front_cutout_width, 432 inner_top_front_cutout_depth, 433 inner_top_front_cutout_height], center = true); 434 435 translate([width / 2 - inner_side_front_cutout_offset - 436 inner_side_front_cutout_width / 2, 437 -inner_side_front_cutout_depth / 2, 438 -inner_top_front_cutout_offset / 2]) 439 cube([inner_side_front_cutout_width, 440 inner_side_front_cutout_depth, 441 inner_side_front_cutout_height], center = true); 442 443 translate([-width / 2 + inner_side_front_cutout_offset + 444 inner_side_front_cutout_width / 2, 445 -inner_side_front_cutout_depth / 2, 446 -inner_top_front_cutout_offset / 2]) 447 cube([inner_side_front_cutout_width, 448 inner_side_front_cutout_depth, 449 inner_side_front_cutout_height], center = true); 450 451 /* Fillets to round off the edges. */ 452 453 union() { 454 455 /* Top left and right rounding. */ 456 457 translate([-width / 2 + ro, -front_depth / 2, height / 2 - ro]) 458 rotate([0, 0, 180]) 459 rotate([90, 0, 0]) 460 fillet(rr, front_depth); 461 translate([width / 2 - ro, -front_depth / 2, height / 2 - ro]) 462 rotate([90, 0, 0]) 463 fillet(rr, front_depth); 464 465 /* Top front rounding. */ 466 467 translate([0, -front_depth + ro, height / 2 - ro]) 468 rotate([0, 0, 180]) 469 rotate([0, -90, 0]) 470 fillet(rr, width); 471 472 /* Edge rounding. */ 473 474 translate([-width / 2 + ro, -front_depth + ro, 0]) 475 rotate([0, 0, 180]) 476 fillet(rr, height); 477 translate([width / 2 - ro, -front_depth + ro, 0]) 478 rotate([0, 0, 270]) 479 fillet(rr, height); 480 } 481 } 482 483 translate([width * 0.6, 0, 0]) 484 rotate([0, 0, 180]) 485 difference() { 486 487 /* The cartridge surfaces. */ 488 489 union() { 490 491 /* Back portion. */ 492 493 translate([0, back_depth - back / 2, 0]) 494 cube([width, back, height], center = true); 495 translate([-width / 2 + back_left / 2, back_depth / 2, 0]) 496 cube([back_left, back_depth, height], center = true); 497 translate([width / 2 - back_right / 2, back_depth / 2, 0]) 498 cube([back_right, back_depth, height], center = true); 499 translate([0, back_depth / 2, height / 2 - top / 2]) 500 cube([width, back_depth, top], center = true); 501 difference() { 502 503 /* Floor of cartridge. */ 504 505 cube_at(width, back_depth, bottom, 506 0, 1, 1, 507 0, 0, -height / 2 + bottom_from_base); 508 509 /* Edge connector cutout. */ 510 511 cube_at(edge_connector_cutout_back_width, 512 edge_connector_cutout_back_depth, 513 bottom, 514 0, 1, 1, 515 0, 0, -height / 2 + bottom_from_base); 516 } 517 518 /* PCB supports. */ 519 520 pcb_support(-1, pcb_back_support_left_bump_height, 521 pcb_back_support_left_bump_offset_from_bottom); 522 pcb_support(1, pcb_back_support_right_bump_height, 523 pcb_back_support_right_bump_offset_from_bottom); 524 525 /* Circular "lugs" to hold PCBs in place. */ 526 527 pcb_lug(-1); 528 pcb_lug(1); 529 } 530 531 /* Label insets. */ 532 533 union() { 534 535 /* Top label. */ 536 537 translate([-top_label_width / 2, 538 -front_depth + top_label_offset_from_front, 539 height / 2 - top_label_depth]) 540 cube([top_label_width, top_label_height, 541 top_label_depth]); 542 } 543 544 /* Top and side grooves, positioned in the back portion. */ 545 546 union() { 547 548 /* Left groove. */ 549 550 translate([-width / 2 + groove_depth / 2, groove_width / 2, 0]) 551 cube([groove_depth, groove_width, height], 552 center = true); 553 554 /* Right groove. */ 555 556 translate([width / 2 - groove_depth / 2, groove_width / 2, 0]) 557 cube([groove_depth, groove_width, height], 558 center = true); 559 560 /* Top grooves. */ 561 562 translate([0, groove_width / 2, height / 2 - groove_depth / 2]) 563 cube([width, groove_width, groove_depth], 564 center = true); 565 566 translate([0, top_groove_width / 2, 567 height / 2 - top_groove_depth / 2]) 568 cube([width, top_groove_width, top_groove_depth], 569 center = true); 570 } 571 572 /* Back cavity. */ 573 574 intersection() { 575 576 /* From the bottom upwards. */ 577 578 translate([0, back_depth, -height / 2]) 579 linear_extrude(height = back_cavity_height) 580 translate([-width / 2, 0, 0]) 581 polygon([ 582 [back_cavity_offset_from_left, 0], 583 [back_cavity_inner_offset_from_left, 584 -back_cavity_depth], 585 [back_cavity_inner_offset_from_left + 586 back_cavity_inner_width, 587 -back_cavity_depth], 588 [back_cavity_offset_from_left + 589 back_cavity_width, 0] 590 ]); 591 592 /* From left to right. */ 593 594 translate([back_cavity_width / 2, back_depth, -height / 2]) 595 rotate([0, -90, 0]) 596 linear_extrude(height = back_cavity_width) 597 polygon([ 598 [-extra, -back_cavity_depth], 599 [back_cavity_inner_height, 600 -back_cavity_depth], 601 [back_cavity_height, 0], 602 [-extra, 0] 603 ]); 604 } 605 606 /* Inner back cavities. */ 607 608 translate([0, back_depth - inner_back_cavity_offset, -height / 2]) 609 linear_extrude(height = bottom_from_base) 610 translate([-width / 2, 0, 0]) 611 polygon([ 612 [inner_back_cavity_offset_from_left, 0], 613 [inner_back_slope_offset_from_left, 0], 614 [inner_back_slope_offset_from_left + 615 inner_back_slope_width, 616 -inner_back_slope_depth], 617 [inner_back_cavity_offset_from_left, 618 -inner_back_slope_depth] 619 ]); 620 621 translate([0, back_depth - inner_back_cavity_offset, -height / 2]) 622 linear_extrude(height = bottom_from_base) 623 translate([-width / 2, 0, 0]) 624 polygon([ 625 [width - inner_back_slope_offset_from_left, 0], 626 [width - inner_back_cavity_offset_from_left, 0], 627 [width - inner_back_cavity_offset_from_left, 628 -inner_back_slope_depth], 629 [width - inner_back_slope_offset_from_left - 630 inner_back_slope_width, 631 -inner_back_slope_depth] 632 ]); 633 634 /* Inner back edge cavity. */ 635 636 translate([inner_back_edge_width / 2, 637 back_depth - inner_back_edge_offset, -height / 2]) 638 rotate([0, -90, 0]) 639 linear_extrude(height = inner_back_edge_width) 640 polygon([ 641 [-extra, -inner_back_edge_depth], 642 [inner_back_edge_height, -inner_back_edge_depth], 643 [0, 0], 644 [-extra, 0] 645 ]); 646 647 /* Fillets to round off the edges. */ 648 649 union() { 650 651 /* Top left and right rounding. */ 652 653 translate([-width / 2 + ro, back_depth / 2, height / 2 - ro]) 654 rotate([0, 0, 180]) 655 rotate([90, 0, 0]) 656 fillet(rr, back_depth); 657 translate([width / 2 - ro, back_depth / 2, height / 2 - ro]) 658 rotate([90, 0, 0]) 659 fillet(rr, back_depth); 660 661 /* Top back rounding. */ 662 663 translate([0, back_depth - ro, height / 2 - ro]) 664 rotate([0, -90, 0]) 665 fillet(rr, width); 666 667 /* Edge rounding. */ 668 669 translate([width / 2 - ro, back_depth - ro, 0]) 670 fillet(rr, height); 671 translate([-width / 2 + ro, back_depth - ro, 0]) 672 rotate([0, 0, 90]) 673 fillet(rr, height); 674 } 675 } 676 } 677 678 cartridge();