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