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