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