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_depth = 11.0; 81 int_payload_height = 50.8; /* space between the top and the floor */ 82 int_connector_height = 13.5; /* vertical offset of bottom/floor of payload area */ 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 bottom = 1; /* thickness of floor of payload area */ 91 92 /* How much extra depth the back provides for mating with the front. */ 93 94 groove_width_extra = 1.0; 95 front_back_overlap = 1.0; 96 groove_width_overlap = front_back_overlap + groove_width_extra; 97 98 /* Division of pieces into front and back, defining the extents. */ 99 100 int_front_depth = 4.5; 101 front_depth = int_front_depth + front; 102 int_back_depth = int_depth - int_front_depth + front_back_overlap; 103 back_depth = int_back_depth + back; 104 105 /* Cartridge dimensions. */ 106 107 width = int_width + side + side; 108 height = top + int_payload_height + bottom + int_connector_height; 109 depth = int_depth + front + back; 110 int_payload_upper_extent = height / 2 - top; 111 112 /* Cartridge surfaces. */ 113 114 front_side = side; 115 front_left = front_side; 116 front_right = front_side; 117 back_side = side; 118 back_left = back_side; 119 back_right = back_side; 120 121 /* Label details. */ 122 123 front_label_width = 83.0; 124 front_label_height = 46.0; 125 front_label_depth = 1.0; 126 front_label_offset_from_bottom = 19.5; 127 128 top_label_width = front_label_width; 129 top_label_height = 11.5; /* the height as seen from above */ 130 top_label_depth = front_label_depth; 131 top_label_offset_from_front = 2.5; 132 133 /* 134 The groove around the sides and top. This is extended to allow the 135 pieces to fit together, and this extension is generated regardless of 136 whether the visible groove is enabled or not. 137 */ 138 139 groove_width_exposed = GROOVE ? 1.5 : 0; 140 groove_width = groove_width_exposed + groove_width_overlap; 141 groove_depth = 1.0; /* how deep the groove goes into each side */ 142 143 /* Additional cutting to mate the back and front. */ 144 145 top_groove_width = groove_width_overlap; 146 top_groove_depth = 2.0; 147 148 /* 149 Space for the inner edge of the back inside the front. 150 Offsets are measured from the outside surfaces. 151 */ 152 153 inner_top_front_cutout_width = int_width; 154 inner_top_front_cutout_depth = top_groove_width; 155 inner_top_front_cutout_height = top - top_groove_depth; 156 157 inner_side_front_cutout_height = height - top_groove_depth; 158 inner_side_front_cutout_width = front_side - groove_depth; 159 inner_side_front_cutout_depth = groove_width_overlap; 160 161 /* 162 The back cavity is the indented part at the bottom of the back of the 163 cartridge. It appears to be mechanically superfluous, just making the 164 shape of the cartridge look a bit fancier. 165 */ 166 167 back_cavity_width = 68.0; 168 back_cavity_inner_width = 65.0; 169 back_cavity_offset_from_inner_left = 9.0; 170 back_cavity_inner_offset_from_inner_left = 10.5; 171 back_cavity_height = 13.5; 172 back_cavity_inner_height = 12.0; 173 back_cavity_depth = 1.5; 174 175 /* 176 The effect of the cavity on the inside of the case. The most important 177 measurement is the maximum offset since it defines the width of the 178 internal space that should accommodate the plastic guides of the Plus 1 179 socket. 180 */ 181 182 inner_back_slope_depth = 2.5; 183 inner_back_slope_width = inner_back_slope_depth; 184 inner_back_slope_max_offset = 10.5; 185 inner_back_slope_min_offset = inner_back_slope_max_offset - inner_back_slope_width; 186 187 /* The tapering off of the inner back edge. */ 188 189 inner_back_edge_width = 69.0; 190 inner_back_edge_height = 3.0; 191 inner_back_edge_depth = 1.5; 192 193 /* The tapering off of the inner front edge. */ 194 195 inner_front_edge_width = width - front_side * 2; 196 inner_front_edge_height = 3.0; 197 inner_front_edge_depth = 1.5; 198 199 /* 200 The cutouts in the floor of the front of the cartridge that produce a 201 kind of tab that guides the edge connector into place in the back cutout. 202 */ 203 204 edge_connector_cutout_front_offset = 1.0; 205 edge_connector_cutout_front_depth = front_back_overlap; 206 edge_connector_cutout_front_width = 15.0; 207 208 /* 209 The cutout in the floor of the back of the cartridge that accommodates 210 the edge connector. 211 */ 212 213 edge_connector_cutout_back_depth = 3.0; 214 edge_connector_cutout_back_width = 57.5; 215 216 /* 217 Edge connectors are themselves 0.05" or approximately 1.27mm in 218 thickness according to the Acorn Electron Cartridge Interface Specification 219 (Acorn Support Application Group Note 014). 220 */ 221 222 /* Extra internal features. Depths include front and back surfaces. */ 223 224 pcb_back_support_width = 1.2; 225 pcb_back_support_depth = back_depth - 226 edge_connector_cutout_back_depth; 227 pcb_back_support_height = height - int_connector_height - top - bottom; 228 229 pcb_front_support_width = 1.2; 230 pcb_front_support_depth = front_depth; 231 pcb_front_support_height = pcb_back_support_height; 232 233 /* 234 Features measured from the Stardot Dual ROM Adaptor cartridge board 235 dimensions diagram. Offsets are from inside the bottom "floor". 236 */ 237 238 pcb_back_support_bump_width = pcb_front_support_width; 239 pcb_back_support_bump_depth = 1.5; 240 pcb_back_support_left_bump_height = 13.2; 241 pcb_back_support_right_bump_height = 10.7; 242 pcb_back_support_left_bump_offset_from_bottom = 15.1; 243 pcb_back_support_right_bump_offset_from_bottom = 17.6; 244 pcb_back_support_top_bump_height = 3.8; 245 246 /* Move the PCB support towards the centre. */ 247 pcb_support_margin = 1.75; 248 pcb_support_offset_from_centre = edge_connector_cutout_back_width / 2 249 - pcb_support_margin; 250 251 pcb_lug_depth = pcb_back_support_depth + 252 pcb_back_support_bump_depth; 253 pcb_lug_inner_radius = 1.0; 254 pcb_lug_outer_radius = 5.5 / 2; 255 pcb_lug_offset_from_bottom = 14.35; 256 pcb_lug_offset_from_outside = 7.55; 257 258 pcb_lug_cross_width = 6.7; 259 pcb_lug_cross_depth = pcb_back_support_depth; 260 pcb_lug_cross_height = 1.4; 261 262 pcb_front_lug_depth = pcb_back_support_bump_depth + 263 pcb_front_support_depth - front; 264 pcb_front_lug_inner_radius = pcb_lug_outer_radius; 265 pcb_front_lug_outer_radius = pow( 266 pow(pcb_lug_cross_width / 2, 2) + 267 pow(pcb_lug_cross_height / 2, 2), 268 0.5); 269 270 /* Repeated constructs. */ 271 272 module pcb_support(xdir, bump_height, bump_offset) { 273 274 /* 275 Translate the support in the stated direction. 276 Since the support is already justified in the direction, the translation 277 moves the inner edge of the support to alignment with the end of the 278 edge connector cutout and then back by the PCB support margin. 279 */ 280 281 translate([xdir * 282 pcb_support_offset_from_centre, 283 edge_connector_cutout_back_depth, 284 -height / 2 + bottom + int_connector_height]) 285 justify(pcb_back_support_width, 286 pcb_back_support_depth, 287 pcb_back_support_height, 288 xdir, 1, 1) 289 union() { 290 291 /* Underlying support strut. */ 292 293 cube([pcb_back_support_width, 294 pcb_back_support_depth, 295 pcb_back_support_height], center = true); 296 297 /* Middle bump. */ 298 299 cube_at(pcb_back_support_bump_width, 300 pcb_back_support_bump_depth, 301 bump_height, 302 0, -1, 1, 303 0, 304 -pcb_back_support_depth / 2, 305 -pcb_back_support_height / 2 + bump_offset); 306 307 /* Top bump. */ 308 309 cube_at(pcb_back_support_bump_width, 310 pcb_back_support_bump_depth, 311 pcb_back_support_top_bump_height, 312 0, -1, 1, 313 0, 314 -pcb_back_support_depth / 2, 315 pcb_back_support_height / 2 - 316 pcb_back_support_top_bump_height); 317 } 318 } 319 320 module pcb_lug(xdir) { 321 translate([xdir * 322 (width/2 - pcb_lug_offset_from_outside), 323 back_depth, 324 -height / 2 + bottom + int_connector_height + 325 pcb_lug_offset_from_bottom 326 ]) 327 rotate([90, 0, 0]) 328 difference() { 329 union() { 330 cylinder(h=pcb_lug_depth, r=pcb_lug_outer_radius); 331 cube_at(pcb_lug_cross_width, 332 pcb_lug_cross_height, pcb_lug_cross_depth, 333 0, 0, 1, 334 0, 0, 0); 335 cube_at(pcb_lug_cross_height, 336 pcb_lug_cross_width, pcb_lug_cross_depth, 337 0, 0, 1, 338 0, 0, 0); 339 } 340 cylinder(h=pcb_lug_depth, r=pcb_lug_inner_radius); 341 } 342 } 343 344 module pcb_front_lug(xdir) { 345 translate([xdir * 346 (width/2 - pcb_lug_offset_from_outside), 347 -int_front_depth + pcb_front_lug_depth, 348 -height / 2 + bottom + int_connector_height + 349 pcb_lug_offset_from_bottom 350 ]) 351 rotate([90, 0, 0]) 352 difference() { 353 cylinder(h=pcb_front_lug_depth, 354 r=pcb_front_lug_outer_radius); 355 cylinder(h=pcb_front_lug_depth, 356 r=pcb_front_lug_inner_radius); 357 } 358 } 359 360 /* The actual shapes. */ 361 362 translate([-width * 0.6, 0, 0]) 363 difference() { 364 365 /* The cartridge surfaces. */ 366 367 union() { 368 369 /* Front portion. */ 370 371 translate([0, -front_depth + front / 2, 0]) 372 cube([width, front, height], center = true); 373 translate([-width / 2 + front_left / 2, -front_depth / 2, 0]) 374 cube([front_left, front_depth, height], center = true); 375 translate([width / 2 - front_right / 2, -front_depth / 2, 0]) 376 cube([front_right, front_depth, height], center = true); 377 translate([0, -front_depth / 2, height / 2 - top / 2]) 378 cube([width, front_depth, top], center = true); 379 difference() { 380 381 /* Floor of cartridge. */ 382 383 cube_at(width, front_depth, bottom, 384 0, -1, 1, 385 0, 0, -height / 2 + int_connector_height); 386 387 /* Left cutout. */ 388 389 cube_at(edge_connector_cutout_front_width, 390 edge_connector_cutout_front_depth, 391 bottom, 392 1, -1, 1, 393 -width / 2 + edge_connector_cutout_front_offset, 394 0, 395 -height / 2 + int_connector_height); 396 397 /* Right cutout. */ 398 399 cube_at(edge_connector_cutout_front_width, 400 edge_connector_cutout_front_depth, 401 bottom, 402 -1, -1, 1, 403 width / 2 - edge_connector_cutout_front_offset, 404 0, 405 -height / 2 + int_connector_height); 406 } 407 408 /* PCB supports. */ 409 410 cube_at(pcb_front_support_width, 411 pcb_front_support_depth, 412 pcb_front_support_height, 413 1, -1, 1, 414 -edge_connector_cutout_back_width / 2 + 415 pcb_support_margin, 416 0, 417 -height / 2 + bottom + int_connector_height); 418 419 cube_at(pcb_front_support_width, 420 pcb_front_support_depth, 421 pcb_front_support_height, 422 -1, -1, 1, 423 edge_connector_cutout_back_width / 2 - 424 pcb_support_margin, 425 0, 426 -height / 2 + bottom + int_connector_height); 427 428 /* Circular "lugs" to hold PCBs in place. */ 429 430 pcb_front_lug(-1); 431 pcb_front_lug(1); 432 } 433 434 /* Label insets. */ 435 436 union() { 437 438 /* Front label. */ 439 440 if (FRONT_LABEL_INSET) 441 translate([-front_label_width / 2, -front_depth, 442 front_label_offset_from_bottom - height / 2]) 443 cube([front_label_width, front_label_depth, 444 front_label_height]); 445 446 /* Top label. See also the back piece. */ 447 448 if (TOP_LABEL_INSET) 449 translate([-top_label_width / 2, 450 -front_depth + top_label_offset_from_front, 451 height / 2 - top_label_depth]) 452 cube([top_label_width, top_label_height, 453 top_label_depth]); 454 } 455 456 /* Inner front edge cavity. */ 457 458 translate([inner_front_edge_width / 2, -int_front_depth, -height / 2]) 459 rotate([0, -90, 0]) 460 linear_extrude(height = inner_front_edge_width) 461 polygon([ 462 [-extra, -inner_front_edge_depth], 463 [0, -inner_front_edge_depth], 464 [inner_front_edge_height, 0], 465 [-extra, 0], 466 ]); 467 468 /* Inner top cutout for the top and sides of the back portion. */ 469 470 cube_at(inner_top_front_cutout_width, 471 inner_top_front_cutout_depth, 472 inner_top_front_cutout_height, 473 0, -1, 1, 474 0, 0, int_payload_upper_extent); 475 476 cube_at(inner_side_front_cutout_width, 477 inner_side_front_cutout_depth, 478 inner_side_front_cutout_height, 479 1, -1, 0, 480 int_width / 2, 0, -inner_top_front_cutout_height); 481 482 cube_at(inner_side_front_cutout_width, 483 inner_side_front_cutout_depth, 484 inner_side_front_cutout_height, 485 -1, -1, 0, 486 -int_width / 2, 0, -inner_top_front_cutout_height); 487 488 /* Fillets to round off the edges. */ 489 490 union() { 491 492 /* Top left and right rounding. */ 493 494 translate([-width / 2 + ro, -front_depth / 2, height / 2 - ro]) 495 rotate([0, 0, 180]) 496 rotate([90, 0, 0]) 497 fillet(rr, front_depth); 498 translate([width / 2 - ro, -front_depth / 2, height / 2 - ro]) 499 rotate([90, 0, 0]) 500 fillet(rr, front_depth); 501 502 /* Top front rounding. */ 503 504 translate([0, -front_depth + ro, height / 2 - ro]) 505 rotate([0, 0, 180]) 506 rotate([0, -90, 0]) 507 fillet(rr, width); 508 509 /* Edge rounding. */ 510 511 translate([-width / 2 + ro, -front_depth + ro, 0]) 512 rotate([0, 0, 180]) 513 fillet(rr, height); 514 translate([width / 2 - ro, -front_depth + ro, 0]) 515 rotate([0, 0, 270]) 516 fillet(rr, height); 517 } 518 } 519 520 /* 521 Place the back piece next to the front, with the internals facing out 522 the same way. 523 */ 524 525 translate([width * 0.6, 0, 0]) 526 rotate([0, 0, 180]) 527 difference() { 528 529 /* The cartridge surfaces. */ 530 531 union() { 532 533 /* Back portion. */ 534 535 cube_at(width, back, height, 536 0, 1, 0, 537 0, int_back_depth, 0); 538 cube_at(back_left, back_depth + groove_width_extra, height, 539 -1, 1, 0, 540 -int_width / 2, -groove_width_extra, 0); 541 cube_at(back_right, back_depth + groove_width_extra, height, 542 1, 1, 0, 543 int_width / 2, -groove_width_extra, 0); 544 cube_at(width, back_depth + groove_width_extra, top, 545 0, 1, 1, 546 0, -groove_width_extra, int_payload_upper_extent); 547 548 difference() { 549 550 /* Floor of cartridge. */ 551 552 cube_at(width, back_depth, bottom, 553 0, 1, 1, 554 0, 0, -height / 2 + int_connector_height); 555 556 /* Edge connector cutout. */ 557 558 cube_at(edge_connector_cutout_back_width, 559 edge_connector_cutout_back_depth, 560 bottom, 561 0, 1, 1, 562 0, 0, -height / 2 + int_connector_height); 563 } 564 565 /* PCB supports. */ 566 567 pcb_support(-1, pcb_back_support_left_bump_height, 568 pcb_back_support_left_bump_offset_from_bottom); 569 pcb_support(1, pcb_back_support_right_bump_height, 570 pcb_back_support_right_bump_offset_from_bottom); 571 572 /* Circular "lugs" to hold PCBs in place. */ 573 574 pcb_lug(-1); 575 pcb_lug(1); 576 } 577 578 /* Label insets. */ 579 580 union() { 581 582 /* Top label. See also the front piece. */ 583 584 if (TOP_LABEL_INSET) 585 translate([-top_label_width / 2, 586 -front_depth + top_label_offset_from_front, 587 height / 2 - top_label_depth]) 588 cube([top_label_width, top_label_height, 589 top_label_depth]); 590 } 591 592 /* Top and side grooves, positioned in the back portion. */ 593 594 union() { 595 596 /* Left groove. */ 597 598 cube_at(groove_depth, groove_width, height, 599 1, 1, 0, 600 -width / 2, -groove_width_extra, 0); 601 602 /* Right groove. */ 603 604 cube_at(groove_depth, groove_width, height, 605 -1, 1, 0, 606 width / 2, -groove_width_extra, 0); 607 608 /* Top grooves. */ 609 610 cube_at(width, groove_width, groove_depth, 611 0, 1, -1, 612 0, -groove_width_extra, height / 2); 613 614 cube_at(width, top_groove_width, top_groove_depth, 615 0, 1, -1, 616 0, -groove_width_extra, height / 2); 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();