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