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