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