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