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 = int_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; 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, int_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 -pcb_support_offset_from_centre, 429 0, 430 -height / 2 + bottom + int_connector_height); 431 432 cube_at(pcb_front_support_width, 433 pcb_front_support_depth, 434 pcb_front_support_height, 435 1, -1, 1, 436 pcb_support_offset_from_centre, 437 0, 438 -height / 2 + bottom + int_connector_height); 439 440 /* Circular "lugs" to hold PCBs in place. */ 441 442 pcb_front_lug(-1); 443 pcb_front_lug(1); 444 } 445 446 /* Label insets. */ 447 448 union() { 449 450 /* Front label. */ 451 452 if (FRONT_LABEL_INSET) 453 translate([-front_label_width / 2, -front_depth, 454 front_label_offset_from_bottom - height / 2]) 455 cube([front_label_width, front_label_depth, 456 front_label_height]); 457 458 /* Top label. See also the back piece. */ 459 460 if (TOP_LABEL_INSET) 461 translate([-top_label_width / 2, 462 -front_depth + top_label_offset_from_front, 463 height / 2 - top_label_depth]) 464 cube([top_label_width, top_label_height, 465 top_label_depth]); 466 } 467 468 /* Inner front edge cavity. */ 469 470 translate([inner_front_edge_width / 2, -int_front_depth, -height / 2]) 471 rotate([0, -90, 0]) 472 linear_extrude(height = inner_front_edge_width) 473 polygon([ 474 [-extra, -inner_front_edge_depth], 475 [0, -inner_front_edge_depth], 476 [inner_front_edge_height, 0], 477 [-extra, 0], 478 ]); 479 480 /* Inner top cutout for the top and sides of the back portion. */ 481 482 cube_at(inner_top_front_cutout_width, 483 inner_top_front_cutout_depth, 484 inner_top_front_cutout_height, 485 0, -1, 1, 486 0, 0, int_payload_upper_extent); 487 488 cube_at(inner_side_front_cutout_width, 489 inner_side_front_cutout_depth, 490 inner_side_front_cutout_height, 491 1, -1, 0, 492 int_width / 2, 0, -inner_top_front_cutout_height); 493 494 cube_at(inner_side_front_cutout_width, 495 inner_side_front_cutout_depth, 496 inner_side_front_cutout_height, 497 -1, -1, 0, 498 -int_width / 2, 0, -inner_top_front_cutout_height); 499 500 /* Fillets to round off the edges. */ 501 502 union() { 503 504 /* Top left and right rounding. */ 505 506 translate([-width / 2 + ro, -front_depth / 2, height / 2 - ro]) 507 rotate([0, 0, 180]) 508 rotate([90, 0, 0]) 509 fillet(rr, front_depth); 510 translate([width / 2 - ro, -front_depth / 2, height / 2 - ro]) 511 rotate([90, 0, 0]) 512 fillet(rr, front_depth); 513 514 /* Top front rounding. */ 515 516 translate([0, -front_depth + ro, height / 2 - ro]) 517 rotate([0, 0, 180]) 518 rotate([0, -90, 0]) 519 fillet(rr, width); 520 521 /* Edge rounding. */ 522 523 translate([-width / 2 + ro, -front_depth + ro, 0]) 524 rotate([0, 0, 180]) 525 fillet(rr, height); 526 translate([width / 2 - ro, -front_depth + ro, 0]) 527 rotate([0, 0, 270]) 528 fillet(rr, height); 529 } 530 } 531 532 /* 533 Place the back piece next to the front, with the internals facing out 534 the same way, if APART is defined. 535 */ 536 537 back_displacement = APART ? width * 0.6 : 0; 538 back_rotation = APART ? 180 : 0; 539 540 if (BACK) 541 translate([back_displacement, 0, 0]) 542 rotate([0, 0, back_rotation]) 543 difference() { 544 545 /* The cartridge surfaces. */ 546 547 union() { 548 549 /* Back portion. */ 550 551 if (BACK_SURFACE) { 552 cube_at(width, back, height, 553 0, 1, 0, 554 0, int_back_depth, 0); 555 cube_at(back_left, back_depth + groove_width_extra, height, 556 -1, 1, 0, 557 -int_width / 2, -groove_width_extra, 0); 558 cube_at(back_right, back_depth + groove_width_extra, height, 559 1, 1, 0, 560 int_width / 2, -groove_width_extra, 0); 561 cube_at(width, back_depth + groove_width_extra, top, 562 0, 1, 1, 563 0, -groove_width_extra, int_payload_upper_extent); 564 } 565 566 difference() { 567 568 /* Floor of cartridge. */ 569 570 cube_at(width, int_back_depth, bottom, 571 0, 1, 1, 572 0, 0, -height / 2 + int_connector_height); 573 574 /* Edge connector cutout. */ 575 576 cube_at(edge_connector_cutout_back_width, 577 edge_connector_cutout_back_depth, 578 bottom, 579 0, 1, 1, 580 0, 0, -height / 2 + int_connector_height); 581 } 582 583 /* PCB supports. */ 584 585 pcb_support(-1, pcb_back_support_left_bump_height, 586 pcb_back_support_left_bump_offset_from_bottom); 587 pcb_support(1, pcb_back_support_right_bump_height, 588 pcb_back_support_right_bump_offset_from_bottom); 589 590 /* Circular "lugs" to hold PCBs in place. */ 591 592 pcb_lug(-1); 593 pcb_lug(1); 594 } 595 596 /* Label insets. */ 597 598 union() { 599 600 /* Top label. See also the front piece. */ 601 602 if (TOP_LABEL_INSET) 603 translate([-top_label_width / 2, 604 -front_depth + top_label_offset_from_front, 605 height / 2 - top_label_depth]) 606 cube([top_label_width, top_label_height, 607 top_label_depth]); 608 } 609 610 /* Top and side grooves, positioned in the back portion. */ 611 612 union() { 613 614 /* Left groove. */ 615 616 cube_at(groove_depth, groove_width, height, 617 1, 1, 0, 618 -width / 2, -groove_width_extra, 0); 619 620 /* Right groove. */ 621 622 cube_at(groove_depth, groove_width, height, 623 -1, 1, 0, 624 width / 2, -groove_width_extra, 0); 625 626 /* Top grooves. */ 627 628 cube_at(width, groove_width, groove_depth, 629 0, 1, -1, 630 0, -groove_width_extra, height / 2); 631 632 cube_at(width, top_groove_width, top_groove_depth, 633 0, 1, -1, 634 0, -groove_width_extra, height / 2); 635 } 636 637 /* Back cavity. */ 638 639 if (BACK_CAVITY) 640 intersection() { 641 642 /* From the bottom upwards. */ 643 644 translate([0, back_depth, -height / 2]) 645 linear_extrude(height = back_cavity_height) 646 translate([-int_width / 2, 0, 0]) 647 polygon([ 648 [back_cavity_offset_from_inner_left, 0], 649 [back_cavity_inner_offset_from_inner_left, 650 -back_cavity_depth], 651 [back_cavity_inner_offset_from_inner_left + 652 back_cavity_inner_width, 653 -back_cavity_depth], 654 [back_cavity_offset_from_inner_left + 655 back_cavity_width, 0] 656 ]); 657 658 /* From left to right. */ 659 660 translate([back_cavity_width / 2, back_depth, -height / 2]) 661 rotate([0, -90, 0]) 662 linear_extrude(height = back_cavity_width) 663 polygon([ 664 [-extra, -back_cavity_depth], 665 [back_cavity_inner_height, 666 -back_cavity_depth], 667 [back_cavity_height, 0], 668 [-extra, 0] 669 ]); 670 } 671 672 /* Inner back cavities. */ 673 674 translate([0, int_back_depth, -height / 2]) 675 linear_extrude(height = int_connector_height) 676 translate([-int_width / 2, 0, 0]) 677 polygon([ 678 [0, 0], 679 [inner_back_slope_max_offset, 0], 680 [inner_back_slope_min_offset, 681 inner_back_slope_depth], 682 [0, inner_back_slope_depth] 683 ]); 684 685 translate([0, int_back_depth, -height / 2]) 686 linear_extrude(height = int_connector_height) 687 translate([int_width / 2, 0, 0]) 688 polygon([ 689 [0, 0], 690 [-inner_back_slope_max_offset, 0], 691 [-inner_back_slope_min_offset, 692 inner_back_slope_depth], 693 [0, inner_back_slope_depth] 694 ]); 695 696 /* Inner back edge cavity. */ 697 698 translate([inner_back_edge_width / 2, 699 int_back_depth + inner_back_edge_depth, -height / 2]) 700 rotate([0, -90, 0]) 701 linear_extrude(height = inner_back_edge_width) 702 polygon([ 703 [-extra, -inner_back_edge_depth], 704 [inner_back_edge_height, -inner_back_edge_depth], 705 [0, 0], 706 [-extra, 0] 707 ]); 708 709 /* Fillets to round off the edges. */ 710 711 union() { 712 713 /* Top left and right rounding. */ 714 715 translate([-width / 2 + ro, back_depth / 2, height / 2 - ro]) 716 rotate([0, 0, 180]) 717 rotate([90, 0, 0]) 718 fillet(rr, back_depth); 719 translate([width / 2 - ro, back_depth / 2, height / 2 - ro]) 720 rotate([90, 0, 0]) 721 fillet(rr, back_depth); 722 723 /* Top back rounding. */ 724 725 translate([0, back_depth - ro, height / 2 - ro]) 726 rotate([0, -90, 0]) 727 fillet(rr, width); 728 729 /* Edge rounding. */ 730 731 translate([width / 2 - ro, back_depth - ro, 0]) 732 fillet(rr, height); 733 translate([-width / 2 + ro, back_depth - ro, 0]) 734 rotate([0, 0, 90]) 735 fillet(rr, height); 736 } 737 } 738 } 739 740 cartridge();