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. 152 */ 153 154 back_cavity_width = 68.0; 155 back_cavity_inner_width = 65.0; 156 back_cavity_offset_from_left = 10.5; 157 back_cavity_inner_offset_from_left = 12.0; 158 back_cavity_height = 13.5; 159 back_cavity_inner_height = 12.0; 160 back_cavity_depth = 1.5; 161 162 /* 163 The effect of the cavity on the inside of the case. 164 */ 165 166 inner_back_cavity_offset = 1.0; 167 inner_back_cavity_offset_from_left = back_left; 168 inner_back_slope_offset_from_left = 10.0; 169 inner_back_slope_width = 2.5; 170 inner_back_slope_depth = 2.5; 171 172 inner_back_edge_offset = 2.0; 173 inner_back_edge_width = 69.0; 174 inner_back_edge_height = 3.0; 175 inner_back_edge_depth = 1.5; 176 177 inner_front_edge_offset = 0.5; 178 inner_front_edge_width = width - front_side * 2; 179 inner_front_edge_height = 3.0; 180 inner_front_edge_depth = 1.5; 181 182 /* 183 The cutouts in the floor of the front of the cartridge that produce a 184 kind of tab that guides the edge connector into place in the back cutout. 185 */ 186 187 edge_connector_cutout_front_offset = 1.0; 188 edge_connector_cutout_front_depth = 1.0; 189 edge_connector_cutout_front_width = 15.0; 190 191 /* 192 The cutout in the floor of the back of the cartridge that accommodates 193 the edge connector. 194 */ 195 196 edge_connector_cutout_back_depth = 3.0; 197 edge_connector_cutout_back_width = 57.5; 198 199 /* 200 Edge connectors are themselves 0.05" or approximately 1.27mm in 201 thickness according to the Acorn Electron Cartridge Interface Specification 202 (Acorn Support Application Group Note 014). 203 */ 204 205 /* Extra internal features. Depths include front and back surfaces. */ 206 207 pcb_back_support_width = 1.2; 208 pcb_back_support_depth = back_depth - 209 edge_connector_cutout_back_depth; 210 pcb_back_support_height = height - int_connector_height - top - bottom; 211 212 pcb_front_support_width = 1.2; 213 pcb_front_support_depth = front_depth; 214 pcb_front_support_height = pcb_back_support_height; 215 216 /* 217 Features measured from the Stardot Dual ROM Adaptor cartridge board 218 dimensions diagram. Offsets are from inside the bottom "floor". 219 */ 220 221 pcb_back_support_bump_width = pcb_front_support_width; 222 pcb_back_support_bump_depth = 1.5; 223 pcb_back_support_left_bump_height = 13.2; 224 pcb_back_support_right_bump_height = 10.7; 225 pcb_back_support_left_bump_offset_from_bottom = 15.1; 226 pcb_back_support_right_bump_offset_from_bottom = 17.6; 227 pcb_back_support_top_bump_height = 3.8; 228 229 /* Move the PCB support towards the centre. */ 230 pcb_support_margin = 1.75; 231 pcb_support_offset_from_centre = edge_connector_cutout_back_width / 2 232 - pcb_support_margin; 233 234 pcb_lug_depth = pcb_back_support_depth + 235 pcb_back_support_bump_depth; 236 pcb_lug_inner_radius = 1.0; 237 pcb_lug_outer_radius = 5.5 / 2; 238 pcb_lug_offset_from_bottom = 14.35; 239 pcb_lug_offset_from_outside = 7.55; 240 241 pcb_lug_cross_width = 6.7; 242 pcb_lug_cross_depth = pcb_back_support_depth; 243 pcb_lug_cross_height = 1.4; 244 245 pcb_front_lug_depth = pcb_back_support_bump_depth + 246 pcb_front_support_depth - front; 247 pcb_front_lug_inner_radius = pcb_lug_outer_radius; 248 pcb_front_lug_outer_radius = pow( 249 pow(pcb_lug_cross_width / 2, 2) + 250 pow(pcb_lug_cross_height / 2, 2), 251 0.5); 252 253 /* Repeated constructs. */ 254 255 module pcb_support(xdir, bump_height, bump_offset) { 256 257 /* 258 Translate the support in the stated direction. 259 Since the support is already justified in the direction, the translation 260 moves the inner edge of the support to alignment with the end of the 261 edge connector cutout and then back by the PCB support margin. 262 */ 263 264 translate([xdir * 265 pcb_support_offset_from_centre, 266 edge_connector_cutout_back_depth, 267 -height / 2 + bottom + int_connector_height]) 268 justify(pcb_back_support_width, 269 pcb_back_support_depth, 270 pcb_back_support_height, 271 xdir, 1, 1) 272 union() { 273 274 /* Underlying support strut. */ 275 276 cube([pcb_back_support_width, 277 pcb_back_support_depth, 278 pcb_back_support_height], center = true); 279 280 /* Middle bump. */ 281 282 cube_at(pcb_back_support_bump_width, 283 pcb_back_support_bump_depth, 284 bump_height, 285 0, -1, 1, 286 0, 287 -pcb_back_support_depth / 2, 288 -pcb_back_support_height / 2 + bump_offset); 289 290 /* Top bump. */ 291 292 cube_at(pcb_back_support_bump_width, 293 pcb_back_support_bump_depth, 294 pcb_back_support_top_bump_height, 295 0, -1, 1, 296 0, 297 -pcb_back_support_depth / 2, 298 pcb_back_support_height / 2 - 299 pcb_back_support_top_bump_height); 300 } 301 } 302 303 module pcb_lug(xdir) { 304 translate([xdir * 305 (width/2 - pcb_lug_offset_from_outside), 306 back_depth, 307 -height / 2 + bottom + int_connector_height + 308 pcb_lug_offset_from_bottom 309 ]) 310 rotate([90, 0, 0]) 311 difference() { 312 union() { 313 cylinder(h=pcb_lug_depth, r=pcb_lug_outer_radius); 314 cube_at(pcb_lug_cross_width, 315 pcb_lug_cross_height, pcb_lug_cross_depth, 316 0, 0, 1, 317 0, 0, 0); 318 cube_at(pcb_lug_cross_height, 319 pcb_lug_cross_width, pcb_lug_cross_depth, 320 0, 0, 1, 321 0, 0, 0); 322 } 323 cylinder(h=pcb_lug_depth, r=pcb_lug_inner_radius); 324 } 325 } 326 327 module pcb_front_lug(xdir) { 328 translate([xdir * 329 (width/2 - pcb_lug_offset_from_outside), 330 -int_front_depth + pcb_front_lug_depth, 331 -height / 2 + bottom + int_connector_height + 332 pcb_lug_offset_from_bottom 333 ]) 334 rotate([90, 0, 0]) 335 difference() { 336 cylinder(h=pcb_front_lug_depth, 337 r=pcb_front_lug_outer_radius); 338 cylinder(h=pcb_front_lug_depth, 339 r=pcb_front_lug_inner_radius); 340 } 341 } 342 343 /* The actual shapes. */ 344 345 translate([-width * 0.6, 0, 0]) 346 difference() { 347 348 /* The cartridge surfaces. */ 349 350 union() { 351 352 /* Front portion. */ 353 354 translate([0, -front_depth + front / 2, 0]) 355 cube([width, front, height], center = true); 356 translate([-width / 2 + front_left / 2, -front_depth / 2, 0]) 357 cube([front_left, front_depth, height], center = true); 358 translate([width / 2 - front_right / 2, -front_depth / 2, 0]) 359 cube([front_right, front_depth, height], center = true); 360 translate([0, -front_depth / 2, height / 2 - top / 2]) 361 cube([width, front_depth, top], center = true); 362 difference() { 363 364 /* Floor of cartridge. */ 365 366 cube_at(width, front_depth, bottom, 367 0, -1, 1, 368 0, 0, -height / 2 + int_connector_height); 369 370 /* Left cutout. */ 371 372 cube_at(edge_connector_cutout_front_width, 373 edge_connector_cutout_front_depth, 374 bottom, 375 1, -1, 1, 376 -width / 2 + edge_connector_cutout_front_offset, 377 0, 378 -height / 2 + int_connector_height); 379 380 /* Right cutout. */ 381 382 cube_at(edge_connector_cutout_front_width, 383 edge_connector_cutout_front_depth, 384 bottom, 385 -1, -1, 1, 386 width / 2 - edge_connector_cutout_front_offset, 387 0, 388 -height / 2 + int_connector_height); 389 } 390 391 /* PCB supports. */ 392 393 cube_at(pcb_front_support_width, 394 pcb_front_support_depth, 395 pcb_front_support_height, 396 1, -1, 1, 397 -edge_connector_cutout_back_width / 2 + 398 pcb_support_margin, 399 0, 400 -height / 2 + bottom + int_connector_height); 401 402 cube_at(pcb_front_support_width, 403 pcb_front_support_depth, 404 pcb_front_support_height, 405 -1, -1, 1, 406 edge_connector_cutout_back_width / 2 - 407 pcb_support_margin, 408 0, 409 -height / 2 + bottom + int_connector_height); 410 411 /* Circular "lugs" to hold PCBs in place. */ 412 413 pcb_front_lug(-1); 414 pcb_front_lug(1); 415 } 416 417 /* Label insets. */ 418 419 union() { 420 421 /* Front label. */ 422 423 translate([-front_label_width / 2, -front_depth, 424 front_label_offset_from_bottom - height / 2]) 425 cube([front_label_width, front_label_depth, 426 front_label_height]); 427 428 /* Top label. */ 429 430 translate([-top_label_width / 2, 431 -front_depth + top_label_offset_from_front, 432 height / 2 - top_label_depth]) 433 cube([top_label_width, top_label_height, 434 top_label_depth]); 435 } 436 437 /* Inner front edge cavity. */ 438 439 translate([inner_front_edge_width / 2, 440 -front_depth + inner_front_edge_offset, -height / 2]) 441 rotate([0, -90, 0]) 442 linear_extrude(height = inner_front_edge_width) 443 polygon([ 444 [-extra, 0], 445 [0, 0], 446 [inner_front_edge_height, inner_front_edge_depth], 447 [-extra, inner_front_edge_depth], 448 ]); 449 450 /* Inner top cutout for the top and sides of the back portion. */ 451 452 translate([0, -inner_top_front_cutout_depth / 2, height / 2 - 453 inner_top_front_cutout_offset - 454 inner_top_front_cutout_height / 2]) 455 cube([inner_top_front_cutout_width, 456 inner_top_front_cutout_depth, 457 inner_top_front_cutout_height], center = true); 458 459 translate([width / 2 - inner_side_front_cutout_offset - 460 inner_side_front_cutout_width / 2, 461 -inner_side_front_cutout_depth / 2, 462 -inner_top_front_cutout_offset / 2]) 463 cube([inner_side_front_cutout_width, 464 inner_side_front_cutout_depth, 465 inner_side_front_cutout_height], center = true); 466 467 translate([-width / 2 + inner_side_front_cutout_offset + 468 inner_side_front_cutout_width / 2, 469 -inner_side_front_cutout_depth / 2, 470 -inner_top_front_cutout_offset / 2]) 471 cube([inner_side_front_cutout_width, 472 inner_side_front_cutout_depth, 473 inner_side_front_cutout_height], center = true); 474 475 /* Fillets to round off the edges. */ 476 477 union() { 478 479 /* Top left and right rounding. */ 480 481 translate([-width / 2 + ro, -front_depth / 2, height / 2 - ro]) 482 rotate([0, 0, 180]) 483 rotate([90, 0, 0]) 484 fillet(rr, front_depth); 485 translate([width / 2 - ro, -front_depth / 2, height / 2 - ro]) 486 rotate([90, 0, 0]) 487 fillet(rr, front_depth); 488 489 /* Top front rounding. */ 490 491 translate([0, -front_depth + ro, height / 2 - ro]) 492 rotate([0, 0, 180]) 493 rotate([0, -90, 0]) 494 fillet(rr, width); 495 496 /* Edge rounding. */ 497 498 translate([-width / 2 + ro, -front_depth + ro, 0]) 499 rotate([0, 0, 180]) 500 fillet(rr, height); 501 translate([width / 2 - ro, -front_depth + ro, 0]) 502 rotate([0, 0, 270]) 503 fillet(rr, height); 504 } 505 } 506 507 translate([width * 0.6, 0, 0]) 508 rotate([0, 0, 180]) 509 difference() { 510 511 /* The cartridge surfaces. */ 512 513 union() { 514 515 /* Back portion. */ 516 517 translate([0, back_depth - back / 2, 0]) 518 cube([width, back, height], center = true); 519 translate([-width / 2 + back_left / 2, back_depth / 2, 0]) 520 cube([back_left, back_depth, height], center = true); 521 translate([width / 2 - back_right / 2, back_depth / 2, 0]) 522 cube([back_right, back_depth, height], center = true); 523 translate([0, back_depth / 2, height / 2 - top / 2]) 524 cube([width, back_depth, top], center = true); 525 difference() { 526 527 /* Floor of cartridge. */ 528 529 cube_at(width, back_depth, bottom, 530 0, 1, 1, 531 0, 0, -height / 2 + int_connector_height); 532 533 /* Edge connector cutout. */ 534 535 cube_at(edge_connector_cutout_back_width, 536 edge_connector_cutout_back_depth, 537 bottom, 538 0, 1, 1, 539 0, 0, -height / 2 + int_connector_height); 540 } 541 542 /* PCB supports. */ 543 544 pcb_support(-1, pcb_back_support_left_bump_height, 545 pcb_back_support_left_bump_offset_from_bottom); 546 pcb_support(1, pcb_back_support_right_bump_height, 547 pcb_back_support_right_bump_offset_from_bottom); 548 549 /* Circular "lugs" to hold PCBs in place. */ 550 551 pcb_lug(-1); 552 pcb_lug(1); 553 } 554 555 /* Label insets. */ 556 557 union() { 558 559 /* Top label. */ 560 561 translate([-top_label_width / 2, 562 -front_depth + top_label_offset_from_front, 563 height / 2 - top_label_depth]) 564 cube([top_label_width, top_label_height, 565 top_label_depth]); 566 } 567 568 /* Top and side grooves, positioned in the back portion. */ 569 570 union() { 571 572 /* Left groove. */ 573 574 translate([-width / 2 + groove_depth / 2, groove_width / 2, 0]) 575 cube([groove_depth, groove_width, height], 576 center = true); 577 578 /* Right groove. */ 579 580 translate([width / 2 - groove_depth / 2, groove_width / 2, 0]) 581 cube([groove_depth, groove_width, height], 582 center = true); 583 584 /* Top grooves. */ 585 586 translate([0, groove_width / 2, height / 2 - groove_depth / 2]) 587 cube([width, groove_width, groove_depth], 588 center = true); 589 590 translate([0, top_groove_width / 2, 591 height / 2 - top_groove_depth / 2]) 592 cube([width, top_groove_width, top_groove_depth], 593 center = true); 594 } 595 596 /* Back cavity. */ 597 598 intersection() { 599 600 /* From the bottom upwards. */ 601 602 translate([0, back_depth, -height / 2]) 603 linear_extrude(height = back_cavity_height) 604 translate([-width / 2, 0, 0]) 605 polygon([ 606 [back_cavity_offset_from_left, 0], 607 [back_cavity_inner_offset_from_left, 608 -back_cavity_depth], 609 [back_cavity_inner_offset_from_left + 610 back_cavity_inner_width, 611 -back_cavity_depth], 612 [back_cavity_offset_from_left + 613 back_cavity_width, 0] 614 ]); 615 616 /* From left to right. */ 617 618 translate([back_cavity_width / 2, back_depth, -height / 2]) 619 rotate([0, -90, 0]) 620 linear_extrude(height = back_cavity_width) 621 polygon([ 622 [-extra, -back_cavity_depth], 623 [back_cavity_inner_height, 624 -back_cavity_depth], 625 [back_cavity_height, 0], 626 [-extra, 0] 627 ]); 628 } 629 630 /* Inner back cavities. */ 631 632 translate([0, back_depth - inner_back_cavity_offset, -height / 2]) 633 linear_extrude(height = int_connector_height) 634 translate([-width / 2, 0, 0]) 635 polygon([ 636 [inner_back_cavity_offset_from_left, 0], 637 [inner_back_slope_offset_from_left, 0], 638 [inner_back_slope_offset_from_left + 639 inner_back_slope_width, 640 -inner_back_slope_depth], 641 [inner_back_cavity_offset_from_left, 642 -inner_back_slope_depth] 643 ]); 644 645 translate([0, back_depth - inner_back_cavity_offset, -height / 2]) 646 linear_extrude(height = int_connector_height) 647 translate([-width / 2, 0, 0]) 648 polygon([ 649 [width - inner_back_slope_offset_from_left, 0], 650 [width - inner_back_cavity_offset_from_left, 0], 651 [width - inner_back_cavity_offset_from_left, 652 -inner_back_slope_depth], 653 [width - inner_back_slope_offset_from_left - 654 inner_back_slope_width, 655 -inner_back_slope_depth] 656 ]); 657 658 /* Inner back edge cavity. */ 659 660 translate([inner_back_edge_width / 2, 661 back_depth - inner_back_edge_offset, -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();