1 /* 2 Copyright (C) 2014, 2015 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 /* Configure the generated shapes. */ 23 24 BACK_CAVITY = 1; 25 FRONT_LABEL_INSET = 1; 26 TOP_LABEL_INSET = 1; 27 GROOVE = 1; 28 SHORT_PCB = 0; 29 30 /* Model configurations. */ 31 32 ROM_CARTRIDGE = 0; 33 WIDE_CARTRIDGE = 1; 34 35 /* Set this to the desired model type. */ 36 37 MODEL = ROM_CARTRIDGE; 38 39 /* 40 Options for checking. Some useful combinations... 41 42 Check feature alignment: APART = 0; BACK_SURFACE = 0; FRONT_SURFACE = 0; 43 Check PCB alignment: PCB = 1; BACK_SURFACE = 0; 44 */ 45 46 BACK = 1; BACK_SURFACE = 1; 47 FRONT = 1; FRONT_SURFACE = 1; 48 TOP_SURFACE = 1; 49 50 /* For printing: APART = 1; PCB = 0; */ 51 52 APART = 1; 53 PCB = 0; 54 55 /* To save time (before printing): FILLET = 0; */ 56 57 FILLET = 1; 58 59 /* 60 Rounding/fillet radius and additional margin of subtracted 61 material. The additional margin helps avoid geometry problems. 62 */ 63 64 rr = 2; 65 ro = rr; 66 extra = 0.1; 67 68 groove_rr = 0.2; 69 groove_ro = groove_rr; 70 71 pcb_lug_rr = 0.5; 72 pcb_lug_ro = pcb_lug_rr; 73 74 /* A fillet performs rounding using a quarter segment of a cylinder. */ 75 76 module fillet(r, h) { 77 if (FILLET) 78 translate([0, 0, -h/2]) 79 difference() { 80 cube([r + extra, r + extra, h + extra]); 81 cylinder(r = r, h = h); 82 } 83 } 84 85 /* A fillet justified using the axes. */ 86 87 module fillet_justified(r, h) { 88 if (FILLET) 89 difference() { 90 cube([r + extra, r + extra, h + extra]); 91 cylinder(r = r, h = h); 92 } 93 } 94 95 /* A justified fillet with the extra material below the y-axis. */ 96 97 module fillet_partitioned(r, h) { 98 if (FILLET) 99 difference() { 100 translate([0, 0, -extra]) 101 cube([r + extra, r + extra, h + extra]); 102 cylinder(r = r, h = h); 103 } 104 } 105 106 module fillet_torus(radius, rounding) { 107 if (FILLET) 108 difference() { 109 cube_at((radius + extra) * 2, (radius + extra) * 2, rounding + extra, 110 0, 0, 1, 111 0, 0, 0); 112 union() { 113 rotate_extrude(convexity = 10) 114 translate([radius - rounding, 0, 0]) 115 circle(r = rounding); 116 cylinder(r = radius - rounding, h = rounding); 117 } 118 } 119 } 120 121 /* 122 Justify an object of the given dimensions, according to the given 123 factors (where 1 indicates moving the object to the positive side of an 124 axis, and -1 indicates moving it to the negative side of an axis). 125 126 NOTE: child should eventually be replaced by children. 127 */ 128 module justify(width, depth, height, wdir, ddir, hdir) { 129 translate([ 130 wdir * width / 2, 131 ddir * depth / 2, 132 hdir * height / 2]) 133 child(); 134 } 135 136 /* 137 Make a cuboid of the given dimensions, justifying it according to the given 138 factors, and moving it to the specified coordinates. 139 140 NOTE: Usage of justify within this module will not work due to recursion 141 NOTE: limitations in openscad, potentially removed in more recent 142 NOTE: releases. Thus, the justify transform is merged in here. 143 */ 144 module cube_at(width, depth, height, wdir, ddir, hdir, x, y, z) { 145 translate([ 146 x + wdir * width / 2, 147 y + ddir * depth / 2, 148 z + hdir * height / 2]) 149 cube([width, depth, height], center = true); 150 } 151 152 /* Model widths. */ 153 154 ROM_CARTRIDGE_int_payload_width = 86.0; 155 WIDE_CARTRIDGE_int_payload_width = 140.0; 156 157 /* Internal dimensions. */ 158 159 /* internal width in the payload section */ 160 int_payload_width = 161 MODEL == ROM_CARTRIDGE ? ROM_CARTRIDGE_int_payload_width : 162 MODEL == WIDE_CARTRIDGE ? WIDE_CARTRIDGE_int_payload_width : 163 ROM_CARTRIDGE_int_payload_width; 164 165 int_connector_width = 86.0; /* limited to the Plus 1 socket dimensions */ 166 int_payload_depth = 14.0; /* maximum depth in the payload section */ 167 int_connector_depth = 11.5; /* maximum depth in the connector section */ 168 int_payload_height = 50.8; /* space between the top and the floor */ 169 int_connector_height = 13.5; /* vertical offset of bottom/floor of payload area */ 170 171 /* Side thicknesses. */ 172 173 front = 2; 174 payload_back = 1; /* in the payload area the thickness is reduced */ 175 connector_back = 3.5; /* the back cavity requires a thicker back wall */ 176 top = 3; 177 side = 2; /* increased from 1.5 for 3D printing reliability */ 178 bottom = 1; /* thickness of floor of payload area */ 179 180 /* How much extra depth the back provides for mating with the front. */ 181 182 groove_width_extra = 2.0; /* the depth of the groove accommodating the front rim */ 183 front_back_overlap = 1.0; 184 groove_width_overlap = front_back_overlap + groove_width_extra; 185 186 /* Division of pieces into front and back, defining the extents. */ 187 188 int_front_depth = 4.5; 189 front_depth = int_front_depth + front; 190 int_payload_back_depth = int_payload_depth - int_front_depth + front_back_overlap; 191 int_connector_back_depth = int_connector_depth - int_front_depth + front_back_overlap; 192 back_depth = int_payload_back_depth + payload_back; 193 194 /* Cartridge dimensions. */ 195 196 payload_width = int_payload_width + side + side; 197 connector_width = int_connector_width + side + side; 198 payload_height = top + int_payload_height; 199 connector_height = bottom + int_connector_height; 200 height = payload_height + bottom + int_connector_height; 201 depth = int_payload_depth + front + payload_back; 202 upper_extent = height / 2; 203 lower_extent = -upper_extent; 204 int_payload_upper_extent = upper_extent - top; 205 int_payload_lower_extent = lower_extent + int_connector_height + bottom; 206 207 /* Where the payload is wider than the connector, the payload expands to the left. */ 208 209 int_payload_right_extent = int_connector_width / 2; 210 int_payload_left_extent = int_payload_right_extent - int_payload_width; 211 payload_left_extent = int_payload_left_extent - side; 212 payload_right_extent = int_payload_right_extent + side; 213 payload_centre = (payload_left_extent + payload_right_extent) / 2; 214 215 /* Cartridge surfaces. */ 216 217 front_side = side; 218 front_left = front_side; 219 front_right = front_side; 220 back_side = side; 221 back_left = back_side; 222 back_right = back_side; 223 224 /* Label details. */ 225 226 front_label_width = payload_width - side - side - 3.0; 227 front_label_height = 46.0; 228 front_label_depth = 1.0; 229 front_label_offset_from_bottom = 19.5; 230 front_label_centre = payload_centre; 231 front_label_left_extent = front_label_centre - front_label_width / 2; 232 233 top_label_width = front_label_width; 234 top_label_height = 11.5; /* the height as seen from above */ 235 top_label_depth = front_label_depth; 236 top_label_offset_from_front = 2.5; 237 top_label_centre = payload_centre; 238 top_label_left_extent = top_label_centre - front_label_width / 2; 239 240 /* 241 The groove around the sides and top. This is extended to allow the 242 pieces to fit together, and this extension is generated regardless of 243 whether the visible groove is enabled or not. 244 */ 245 246 groove_width_exposed = GROOVE ? 1.5 : 0; 247 groove_width_normal = groove_width_exposed + front_back_overlap; 248 groove_depth = 1.0; /* how deep the groove goes into each side */ 249 250 /* Additional cutting to mate the back and front. */ 251 252 top_groove_width = groove_width_overlap; 253 top_groove_depth = 2.0; 254 255 /* 256 Space for the inner edge of the back inside the front. 257 Offsets are measured from the outside surfaces. 258 */ 259 260 inner_top_front_cutout_width = int_payload_width; 261 inner_top_front_cutout_depth = top_groove_width; 262 inner_top_front_cutout_height = top - top_groove_depth; 263 264 inner_payload_front_cutout_height = payload_height - top_groove_depth; 265 inner_payload_front_cutout_width = front_side - groove_depth; 266 inner_payload_front_cutout_depth = groove_width_overlap; 267 268 inner_connector_front_cutout_height = connector_height; 269 inner_connector_front_cutout_width = front_side - groove_depth; 270 inner_connector_front_cutout_depth = groove_width_overlap; 271 272 /* 273 The back cavity is the indented part at the bottom of the back of the 274 cartridge. It appears to be mechanically necessary, making sure that 275 cartridges cannot be plugged in the wrong way round. 276 */ 277 278 back_cavity_width = 68.0; 279 back_cavity_inner_width = 65.0; 280 back_cavity_offset_from_inner_left = 9.0; 281 back_cavity_inner_offset_from_inner_left = 10.5; 282 back_cavity_height = 13.5; 283 back_cavity_inner_height = 12.0; 284 back_cavity_depth = 1.5; 285 286 /* 287 The effect of the cavity on the inside of the case. In principle, the 288 interior of the case could be straight since the plastic guides of the 289 Plus 1 socket are outside the case. 290 */ 291 292 inner_back_slope_depth = 2.5; 293 inner_back_slope_width = inner_back_slope_depth; 294 inner_back_slope_max_offset = 10.5; 295 inner_back_slope_min_offset = inner_back_slope_max_offset - inner_back_slope_width; 296 297 /* The tapering off of the inner back edge. */ 298 299 inner_back_edge_width = 69.0; 300 inner_back_edge_height = 3.0; 301 inner_back_edge_depth = 1.5; 302 303 /* The tapering off of the inner front edge. */ 304 305 inner_front_edge_width = connector_width - front_side * 2; 306 inner_front_edge_height = 3.0; 307 inner_front_edge_depth = 1.5; 308 309 /* 310 The cutout in the floor of the back of the cartridge that accommodates 311 the edge connector. 312 */ 313 314 edge_connector_cutout_back_depth = 3.0; 315 edge_connector_cutout_back_width = 57.5; 316 317 /* 318 The cutouts in the floor of the front of the cartridge that produce a 319 kind of tab that guides the edge connector into place in the back cutout. 320 */ 321 322 edge_connector_cutout_front_depth = front_back_overlap; 323 edge_connector_cutout_front_width = (int_connector_width - edge_connector_cutout_back_width) / 2; 324 325 /* 326 Edge connectors are themselves 0.05" or approximately 1.27mm in 327 thickness according to the Acorn Electron Cartridge Interface Specification 328 (Acorn Support Application Group Note 014). 329 */ 330 331 /* Extra internal features. Depths include front and back surfaces. */ 332 333 pcb_back_support_width = 1.2; 334 pcb_back_support_depth = back_depth - 335 edge_connector_cutout_back_depth; 336 pcb_back_support_height = height - int_connector_height - top - bottom; 337 338 pcb_front_support_width = 1.2; 339 pcb_front_support_depth = int_front_depth; 340 pcb_front_support_height = pcb_back_support_height; 341 342 /* 343 Features measured from the Stardot Dual ROM Adaptor cartridge board 344 dimensions diagram. Offsets are from inside the bottom "floor". 345 */ 346 347 pcb_back_support_bump_width = pcb_front_support_width; 348 pcb_back_support_bump_depth = 1.5; 349 pcb_back_support_left_bump_height = 13.2; 350 pcb_back_support_right_bump_height = 10.7; 351 pcb_back_support_left_bump_offset_from_bottom = 15.1; 352 pcb_back_support_right_bump_offset_from_bottom = 17.6; 353 354 /* Configured by SHORT_PCB. */ 355 pcb_back_support_top_bump_height = 3.8; 356 357 /* Move the PCB support towards the centre. */ 358 pcb_support_margin = 1.75; 359 pcb_support_offset_from_centre = edge_connector_cutout_back_width / 2 360 - pcb_support_margin; 361 362 /* 363 The PCB lugs protrude through the holes in the PCB. By extending both 364 the front and back lugs by the depth of the back support bump, they 365 overlap by that amount. The "mating" depth is defined as that amount 366 plus an additional amount for adhesion. 367 */ 368 369 pcb_lug_mating_depth = pcb_back_support_bump_depth + 1.0; 370 371 pcb_lug_depth = pcb_back_support_depth + 372 pcb_lug_mating_depth; 373 pcb_lug_inner_radius = 1.0; 374 pcb_lug_outer_radius = 5.5 / 2; 375 pcb_lug_offset_from_bottom = 14.35; 376 pcb_lug_offset_from_inside = 5.55; 377 378 /* 379 The cross detail on the back lug is intended to resist the front lug, 380 and thus starts at the point that the front lug ends. 381 */ 382 383 pcb_lug_cross_width = 6.7; 384 pcb_lug_cross_depth = pcb_back_support_depth + pcb_back_support_bump_depth - 385 pcb_lug_mating_depth; 386 pcb_lug_cross_height = 1.4; 387 388 pcb_front_lug_depth = pcb_front_support_depth + 389 pcb_lug_mating_depth; 390 pcb_front_lug_inner_radius = pcb_lug_outer_radius; 391 pcb_front_lug_outer_radius = pow( 392 pow(pcb_lug_cross_width / 2, 2) + 393 pow(pcb_lug_cross_height / 2, 2), 394 0.5); 395 396 wide_pcb_lug_offset_from_inside = 6.0; 397 wide_pcb_lug_offset_from_bottom = 6.0; 398 399 /* PCBs for checking. */ 400 401 pcb_width = 84.85; pcb_height = 62.5; pcb_depth = 1; 402 edge_connector_width = 56.5; edge_connector_height = 11.85; 403 404 /* 405 The rectangular part of the narrow left and right holes is smaller 406 than the "bump" in the case, but the circular parts make the overall 407 hole larger than the "bump". An extra depth is used for holes to avoid 408 surface definition problems. 409 */ 410 411 pcb_hole_margin = 0.55; 412 pcb_hole_width = 2.2; 413 pcb_hole_extra_depth = 0.1; 414 pcb_lug_hole_radius = 3.75; 415 416 pcb_left_hole_offset = pcb_back_support_left_bump_offset_from_bottom + pcb_hole_margin; 417 pcb_left_hole_height = pcb_back_support_left_bump_height - pcb_hole_margin * 2; 418 pcb_left_hole_offset_from_centre = -(pcb_support_offset_from_centre + 419 pcb_back_support_width / 2 - pcb_hole_width / 2); 420 421 pcb_right_hole_offset = pcb_back_support_right_bump_offset_from_bottom + pcb_hole_margin; 422 pcb_right_hole_height = pcb_back_support_right_bump_height - pcb_hole_margin * 2; 423 pcb_right_hole_offset_from_centre = -pcb_left_hole_offset_from_centre; 424 425 pcb_hole_depth = pcb_depth + 2 * pcb_hole_extra_depth; 426 pcb_hole_start_depth = edge_connector_cutout_back_depth + pcb_hole_extra_depth; 427 428 wide_pcb_width = 138.0; wide_pcb_height = pcb_height; wide_pcb_depth = pcb_depth; 429 wide_pcb_hole_depth = pcb_hole_depth; 430 wide_pcb_hole_start_depth = pcb_hole_start_depth; 431 432 wide_pcb_lug_hole_radius = 4.0; 433 434 /* An example feature for use with PCB testing. */ 435 436 feature_width = 15.0; feature_depth = 7.0; feature_height = 40.0; 437 feature_height_offset = 10.0; 438 439 /* Repeated constructs. */ 440 441 module pcb_support(xdir, bump_height, bump_offset) { 442 443 /* 444 Translate the support in the stated direction. 445 Since the support is already justified in the direction, the translation 446 moves the inner edge of the support to alignment with the end of the 447 edge connector cutout and then back by the PCB support margin. 448 */ 449 450 translate([xdir * 451 pcb_support_offset_from_centre, 452 edge_connector_cutout_back_depth, 453 int_payload_lower_extent]) 454 justify(pcb_back_support_width, 455 pcb_back_support_depth, 456 pcb_back_support_height, 457 xdir, 1, 1) 458 union() { 459 460 /* Underlying support strut. */ 461 462 cube([pcb_back_support_width, 463 pcb_back_support_depth, 464 pcb_back_support_height], center = true); 465 466 /* Middle bump. */ 467 468 cube_at(pcb_back_support_bump_width, 469 pcb_back_support_bump_depth, 470 bump_height, 471 0, -1, 1, 472 0, 473 -pcb_back_support_depth / 2, 474 -pcb_back_support_height / 2 + bump_offset); 475 476 /* Top bump. */ 477 478 if (SHORT_PCB) 479 cube_at(pcb_back_support_bump_width, 480 pcb_back_support_bump_depth, 481 pcb_back_support_top_bump_height, 482 0, -1, 1, 483 0, 484 -pcb_back_support_depth / 2, 485 pcb_back_support_height / 2 - 486 pcb_back_support_top_bump_height); 487 } 488 } 489 490 module pcb_lug(xdir) { 491 pcb_lug_explicit(xdir, int_connector_width / 2 - pcb_lug_offset_from_inside, 492 pcb_lug_offset_from_bottom); 493 } 494 495 module pcb_lug_wide(xdir) { 496 pcb_lug_explicit(xdir, int_payload_width / 2 - wide_pcb_lug_offset_from_inside, 497 wide_pcb_lug_offset_from_bottom); 498 pcb_lug_explicit(xdir, int_payload_width / 2 - wide_pcb_lug_offset_from_inside, 499 wide_pcb_height - edge_connector_height - wide_pcb_lug_offset_from_bottom); 500 } 501 502 module pcb_lug_explicit(xdir, pcb_lug_offset_from_centre, pcb_lug_offset_from_bottom) { 503 translate([payload_centre + xdir * pcb_lug_offset_from_centre, 504 back_depth, 505 int_payload_lower_extent + pcb_lug_offset_from_bottom 506 ]) 507 rotate([90, 0, 0]) 508 difference() { 509 510 /* Cylinder with cross. */ 511 512 union() { 513 cylinder(h=pcb_lug_depth, r=pcb_lug_outer_radius); 514 cube_at(pcb_lug_cross_width, 515 pcb_lug_cross_height, pcb_lug_cross_depth, 516 0, 0, 1, 517 0, 0, 0); 518 cube_at(pcb_lug_cross_height, 519 pcb_lug_cross_width, pcb_lug_cross_depth, 520 0, 0, 1, 521 0, 0, 0); 522 } 523 524 /* Hollowed out by a cylinder. */ 525 526 cylinder(h=pcb_lug_depth, r=pcb_lug_inner_radius); 527 528 /* Tapered off for easier connection. */ 529 530 translate([0, 0, pcb_lug_depth - pcb_lug_ro]) 531 fillet_torus(pcb_lug_outer_radius, pcb_lug_rr); 532 } 533 } 534 535 module pcb_front_lug(xdir) { 536 pcb_front_lug_explicit(xdir, int_connector_width / 2 - pcb_lug_offset_from_inside, 537 pcb_lug_offset_from_bottom); 538 } 539 540 module pcb_front_lug_wide(xdir) { 541 pcb_front_lug_explicit(xdir, int_payload_width / 2 - wide_pcb_lug_offset_from_inside, 542 wide_pcb_lug_offset_from_bottom); 543 pcb_front_lug_explicit(xdir, int_payload_width / 2 - wide_pcb_lug_offset_from_inside, 544 wide_pcb_height - edge_connector_height - wide_pcb_lug_offset_from_bottom); 545 } 546 547 module pcb_front_lug_explicit(xdir, pcb_lug_offset_from_centre, pcb_lug_offset_from_bottom) { 548 translate([payload_centre + xdir * pcb_lug_offset_from_centre, 549 -int_front_depth + pcb_front_lug_depth, 550 int_payload_lower_extent + pcb_lug_offset_from_bottom 551 ]) 552 rotate([90, 0, 0]) 553 difference() { 554 cylinder(h=pcb_front_lug_depth, 555 r=pcb_front_lug_outer_radius); 556 cylinder(h=pcb_front_lug_depth, 557 r=pcb_front_lug_inner_radius); 558 } 559 } 560 561 /* The actual shapes. */ 562 563 front_displacement = APART ? -connector_width * 0.6 : 0; 564 565 if (FRONT) 566 translate([front_displacement, 0, 0]) 567 difference() { 568 569 /* The cartridge surfaces. */ 570 571 union() { 572 573 /* Front portion. */ 574 575 if (FRONT_SURFACE) { 576 577 /* Surfaces surrounding the payload. */ 578 579 cube_at(payload_width, front, payload_height, 580 0, -1, 1, 581 payload_centre, -int_front_depth, int_payload_lower_extent); 582 583 cube_at(front_left, front_depth, payload_height, 584 -1, -1, 1, 585 int_payload_left_extent, 0, int_payload_lower_extent); 586 587 cube_at(front_right, front_depth, payload_height, 588 1, -1, 1, 589 int_payload_right_extent, 0, int_payload_lower_extent); 590 591 /* Surfaces surrounding the connector. */ 592 593 cube_at(connector_width, front, connector_height, 594 0, -1, -1, 595 0, -int_front_depth, int_payload_lower_extent); 596 597 cube_at(front_left, front_depth, connector_height, 598 -1, -1, -1, 599 -int_connector_width / 2, 0, int_payload_lower_extent); 600 601 cube_at(front_right, front_depth, connector_height, 602 1, -1, -1, 603 int_connector_width / 2, 0, int_payload_lower_extent); 604 605 /* Top surface for the front piece. */ 606 607 if (TOP_SURFACE) 608 cube_at(payload_width, front_depth, top, 609 0, -1, 1, 610 payload_centre, 0, int_payload_upper_extent); 611 } 612 613 difference() { 614 615 /* Floor of cartridge. */ 616 617 cube_at(int_connector_width, int_front_depth, bottom, 618 0, -1, 1, 619 0, 0, lower_extent + int_connector_height); 620 621 /* Left cutout. */ 622 623 cube_at(edge_connector_cutout_front_width, 624 edge_connector_cutout_front_depth, 625 bottom, 626 1, -1, 1, 627 -int_connector_width / 2, 628 0, 629 lower_extent + int_connector_height); 630 631 /* Right cutout. */ 632 633 cube_at(edge_connector_cutout_front_width, 634 edge_connector_cutout_front_depth, 635 bottom, 636 -1, -1, 1, 637 int_connector_width / 2, 638 0, 639 lower_extent + int_connector_height); 640 } 641 642 /* Extended floor. */ 643 644 if (payload_width > connector_width) { 645 646 cube_at(payload_width - connector_width, front_depth, bottom, 647 1, -1, 1, 648 payload_left_extent, 0, lower_extent + int_connector_height); 649 } 650 651 /* PCB supports. */ 652 653 if (MODEL == ROM_CARTRIDGE) { 654 cube_at(pcb_front_support_width, 655 pcb_front_support_depth, 656 pcb_front_support_height, 657 -1, -1, 1, 658 -pcb_support_offset_from_centre, 659 0, 660 int_payload_lower_extent); 661 662 cube_at(pcb_front_support_width, 663 pcb_front_support_depth, 664 pcb_front_support_height, 665 1, -1, 1, 666 pcb_support_offset_from_centre, 667 0, 668 int_payload_lower_extent); 669 670 /* Circular "lugs" to hold PCBs in place. */ 671 672 pcb_front_lug(-1); 673 pcb_front_lug(1); 674 } 675 676 if (MODEL == WIDE_CARTRIDGE) { 677 678 pcb_front_lug_wide(-1); 679 pcb_front_lug_wide(1); 680 } 681 } 682 683 /* Label insets. */ 684 685 union() { 686 687 /* Front label. */ 688 689 if (FRONT_LABEL_INSET) 690 translate([front_label_left_extent, -front_depth, 691 lower_extent + front_label_offset_from_bottom]) 692 cube([front_label_width, front_label_depth, 693 front_label_height]); 694 695 /* Top label. See also the back piece. */ 696 697 if (TOP_LABEL_INSET) 698 translate([top_label_left_extent, 699 -front_depth + top_label_offset_from_front, 700 upper_extent - top_label_depth]) 701 cube([top_label_width, top_label_height, 702 top_label_depth]); 703 } 704 705 /* Inner front edge cavity. */ 706 707 translate([inner_front_edge_width / 2, -int_front_depth, lower_extent]) 708 rotate([0, -90, 0]) 709 linear_extrude(height = inner_front_edge_width) 710 polygon([ 711 [-extra, -inner_front_edge_depth], 712 [0, -inner_front_edge_depth], 713 [inner_front_edge_height, 0], 714 [-extra, 0], 715 ]); 716 717 /* Inner top cutout for the top and sides of the back portion. */ 718 719 cube_at(inner_top_front_cutout_width, 720 inner_top_front_cutout_depth, 721 inner_top_front_cutout_height, 722 1, -1, 1, 723 int_payload_left_extent, 0, int_payload_upper_extent); 724 725 cube_at(inner_payload_front_cutout_width, 726 inner_payload_front_cutout_depth, 727 inner_payload_front_cutout_height, 728 1, -1, 1, 729 int_payload_right_extent, 0, int_payload_lower_extent); 730 731 cube_at(inner_payload_front_cutout_width, 732 inner_payload_front_cutout_depth, 733 inner_payload_front_cutout_height, 734 -1, -1, 1, 735 int_payload_left_extent, 0, int_payload_lower_extent); 736 737 cube_at(inner_connector_front_cutout_width, 738 inner_connector_front_cutout_depth, 739 inner_connector_front_cutout_height, 740 1, -1, -1, 741 int_connector_width / 2, 0, int_payload_lower_extent); 742 743 cube_at(inner_connector_front_cutout_width, 744 inner_connector_front_cutout_depth, 745 inner_connector_front_cutout_height, 746 -1, -1, -1, 747 -int_connector_width / 2, 0, int_payload_lower_extent); 748 749 /* Fillets to round off the edges. */ 750 751 union() { 752 753 /* Top left and right rounding. */ 754 755 translate([payload_left_extent + ro, -front_depth / 2, upper_extent - ro]) 756 rotate([0, 0, 180]) 757 rotate([90, 0, 0]) 758 fillet(rr, front_depth); 759 translate([payload_right_extent - ro, -front_depth / 2, upper_extent - ro]) 760 rotate([90, 0, 0]) 761 fillet(rr, front_depth); 762 763 /* Top front rounding. */ 764 765 translate([payload_centre, -front_depth + ro, upper_extent - ro]) 766 rotate([0, 0, 180]) 767 rotate([0, -90, 0]) 768 fillet(rr, payload_width); 769 770 /* Edge rounding. */ 771 772 translate([payload_right_extent - ro, -front_depth + ro, int_payload_lower_extent - bottom - extra]) 773 rotate([0, 0, 270]) 774 fillet_justified(rr, payload_height + bottom + extra); 775 776 translate([payload_left_extent + ro, -front_depth + ro, int_payload_lower_extent - bottom - extra]) 777 rotate([0, 0, 180]) 778 fillet_justified(rr, payload_height + bottom + extra); 779 780 translate([connector_width / 2 - ro, -front_depth + ro, lower_extent]) 781 rotate([0, 0, 270]) 782 fillet_partitioned(rr, connector_height - bottom); 783 784 translate([-connector_width / 2 + ro, -front_depth + ro, lower_extent]) 785 rotate([0, 0, 180]) 786 fillet_partitioned(rr, connector_height - bottom); 787 } 788 } 789 790 /* 791 Place the back piece next to the front, with the internals facing out 792 the same way, if APART is defined. 793 */ 794 795 back_displacement = APART ? connector_width * 0.6 : 0; 796 back_rotation = APART ? 180 : 0; 797 798 if (BACK) 799 translate([back_displacement, 0, 0]) 800 rotate([0, 0, back_rotation]) { 801 difference() { 802 803 /* The cartridge surfaces. */ 804 805 union() { 806 807 /* Back portion. */ 808 809 if (BACK_SURFACE) { 810 811 /* Surfaces surrounding the payload. */ 812 813 /* Payload section of back surface. */ 814 815 cube_at(payload_width, payload_back, payload_height, 816 0, 1, 1, 817 payload_centre, int_payload_back_depth, int_payload_lower_extent); 818 819 cube_at(back_left, back_depth, payload_height, 820 -1, 1, 1, 821 int_payload_left_extent, 0, int_payload_lower_extent); 822 823 cube_at(back_right, back_depth, payload_height, 824 1, 1, 1, 825 int_payload_right_extent, 0, int_payload_lower_extent); 826 827 /* Surfaces surrounding the connector. */ 828 829 /* Connector section of back surface overlapping the floor. */ 830 831 cube_at(connector_width, connector_back, connector_height, 832 0, 1, -1, 833 0, int_connector_back_depth, int_payload_lower_extent); 834 835 cube_at(back_left, back_depth, connector_height, 836 -1, 1, -1, 837 -int_connector_width / 2, 0, int_payload_lower_extent); 838 839 cube_at(back_right, back_depth, connector_height, 840 1, 1, -1, 841 int_connector_width / 2, 0, int_payload_lower_extent); 842 843 /* Top of back piece. */ 844 845 if (TOP_SURFACE) 846 cube_at(payload_width, back_depth, top, 847 0, 1, 1, 848 payload_centre, 0, int_payload_upper_extent); 849 850 /* 851 The extension of the back to connect with the front. Here, the inside 852 edges are rounded. The outside edges are rounded later. 853 854 An extra overlapping measure is employed so that the filleting is 855 continuous between the payload and connector portions. On the inside 856 edges, the connector filleting is extended upwards. 857 */ 858 859 /* Left side of payload. */ 860 861 difference() { 862 cube_at(back_left - groove_depth, groove_width_extra, inner_payload_front_cutout_height, 863 -1, 1, 1, 864 int_payload_left_extent, -groove_width_extra, int_payload_lower_extent); 865 translate([int_payload_left_extent - groove_ro, -groove_width_extra + groove_ro, 866 int_payload_lower_extent]) 867 rotate([0, 0, -90]) 868 fillet_justified(groove_rr, inner_payload_front_cutout_height); 869 } 870 871 /* Right side of payload. */ 872 873 difference() { 874 cube_at(back_right - groove_depth, groove_width_extra, inner_payload_front_cutout_height, 875 1, 1, 1, 876 int_payload_right_extent, -groove_width_extra, int_payload_lower_extent); 877 translate([int_payload_right_extent + groove_ro, -groove_width_extra + groove_ro, 878 int_payload_lower_extent]) 879 rotate([0, 0, 180]) 880 fillet_justified(groove_rr, inner_payload_front_cutout_height); 881 } 882 883 /* Left side of connector. */ 884 885 difference() { 886 cube_at(back_left - groove_depth, groove_width_extra, inner_connector_front_cutout_height, 887 -1, 1, -1, 888 -int_connector_width / 2, -groove_width_extra, int_payload_lower_extent); 889 translate([-int_connector_width / 2 - groove_ro, -groove_width_extra + groove_ro, 890 lower_extent]) 891 rotate([0, 0, -90]) 892 fillet_partitioned(groove_rr, inner_connector_front_cutout_height + extra); 893 } 894 895 /* Right side of connector. */ 896 897 difference() { 898 cube_at(back_right - groove_depth, groove_width_extra, inner_connector_front_cutout_height, 899 1, 1, -1, 900 int_connector_width / 2, -groove_width_extra, int_payload_lower_extent); 901 translate([int_connector_width / 2 + groove_ro, -groove_width_extra + groove_ro, 902 lower_extent]) 903 rotate([0, 0, 180]) 904 fillet_partitioned(groove_rr, inner_connector_front_cutout_height + extra); 905 } 906 907 /* Top side. */ 908 909 difference() { 910 cube_at(payload_width - groove_depth * 2, groove_width_extra, top - top_groove_depth, 911 0, 1, 1, 912 payload_centre, -groove_width_extra, int_payload_upper_extent); 913 translate([payload_centre, -groove_width_extra + groove_ro, 914 int_payload_upper_extent + groove_ro]) 915 rotate([0, 0, 180]) 916 rotate([0, 90, 0]) 917 fillet(groove_rr, payload_width - groove_depth * 2); 918 } 919 } 920 921 difference() { 922 923 /* Floor of cartridge. */ 924 925 cube_at(int_connector_width, int_connector_back_depth, bottom, 926 0, 1, 1, 927 0, 0, lower_extent + int_connector_height); 928 929 /* Edge connector cutout. */ 930 931 cube_at(edge_connector_cutout_back_width, 932 edge_connector_cutout_back_depth, 933 bottom, 934 0, 1, 1, 935 0, 0, lower_extent + int_connector_height); 936 } 937 938 /* Extended floor. */ 939 940 if (payload_width > connector_width) { 941 942 difference() { 943 944 cube_at(payload_width - connector_width, back_depth, bottom, 945 1, 1, 1, 946 payload_left_extent, 0, lower_extent + int_connector_height); 947 948 cube_at(payload_width - connector_width, edge_connector_cutout_front_depth, bottom, 949 1, 1, 1, 950 payload_left_extent, 0, lower_extent + int_connector_height); 951 } 952 } 953 954 /* PCB supports. */ 955 956 if (MODEL == ROM_CARTRIDGE) { 957 pcb_support(-1, pcb_back_support_left_bump_height, 958 pcb_back_support_left_bump_offset_from_bottom); 959 pcb_support(1, pcb_back_support_right_bump_height, 960 pcb_back_support_right_bump_offset_from_bottom); 961 962 /* Circular "lugs" to hold PCBs in place. */ 963 964 pcb_lug(-1); 965 pcb_lug(1); 966 } 967 968 if (MODEL == WIDE_CARTRIDGE) { 969 970 pcb_lug_wide(1); 971 pcb_lug_wide(-1); 972 } 973 } 974 975 /* Label insets. */ 976 977 union() { 978 979 /* Top label. See also the front piece. */ 980 981 if (TOP_LABEL_INSET) 982 translate([top_label_left_extent, 983 -front_depth + top_label_offset_from_front, 984 upper_extent - top_label_depth]) 985 cube([top_label_width, top_label_height, 986 top_label_depth]); 987 } 988 989 /* Top and side grooves, positioned in the back portion. */ 990 991 union() { 992 993 /* Left groove. */ 994 995 cube_at(groove_depth, groove_width_normal, height, 996 1, 1, 0, 997 payload_left_extent, 0, 0); 998 999 /* Right groove. */ 1000 1001 cube_at(groove_depth, groove_width_normal, height, 1002 -1, 1, 0, 1003 payload_right_extent, 0, 0); 1004 1005 /* Top grooves. */ 1006 1007 cube_at(payload_width, groove_width_normal, groove_depth, 1008 0, 1, -1, 1009 payload_centre, 0, upper_extent); 1010 1011 cube_at(payload_width, top_groove_width, top_groove_depth, 1012 0, 1, -1, 1013 payload_centre, -groove_width_extra, upper_extent); 1014 } 1015 1016 /* Back cavity. */ 1017 1018 if (BACK_CAVITY) 1019 intersection() { 1020 1021 /* From the bottom upwards. */ 1022 1023 translate([0, back_depth, lower_extent]) 1024 linear_extrude(height = back_cavity_height) 1025 translate([-int_connector_width / 2, 0, 0]) 1026 polygon([ 1027 [back_cavity_offset_from_inner_left, 0], 1028 [back_cavity_inner_offset_from_inner_left, 1029 -back_cavity_depth], 1030 [back_cavity_inner_offset_from_inner_left + 1031 back_cavity_inner_width, 1032 -back_cavity_depth], 1033 [back_cavity_offset_from_inner_left + 1034 back_cavity_width, 0] 1035 ]); 1036 1037 /* From left to right. */ 1038 1039 translate([back_cavity_width / 2, back_depth, lower_extent]) 1040 rotate([0, -90, 0]) 1041 linear_extrude(height = back_cavity_width) 1042 polygon([ 1043 [-extra, -back_cavity_depth], 1044 [back_cavity_inner_height, 1045 -back_cavity_depth], 1046 [back_cavity_height, 0], 1047 [-extra, 0] 1048 ]); 1049 } 1050 1051 /* Inner back cavities. */ 1052 1053 translate([0, int_connector_back_depth, lower_extent]) 1054 linear_extrude(height = int_connector_height) 1055 translate([-int_connector_width / 2, 0, 0]) 1056 polygon([ 1057 [0, 0], 1058 [inner_back_slope_max_offset, 0], 1059 [inner_back_slope_min_offset, 1060 inner_back_slope_depth], 1061 [0, inner_back_slope_depth] 1062 ]); 1063 1064 translate([0, int_connector_back_depth, lower_extent]) 1065 linear_extrude(height = int_connector_height) 1066 translate([int_connector_width / 2, 0, 0]) 1067 polygon([ 1068 [0, 0], 1069 [-inner_back_slope_max_offset, 0], 1070 [-inner_back_slope_min_offset, 1071 inner_back_slope_depth], 1072 [0, inner_back_slope_depth] 1073 ]); 1074 1075 /* Inner back edge cavity. */ 1076 1077 translate([inner_back_edge_width / 2, 1078 int_connector_back_depth + inner_back_edge_depth, lower_extent]) 1079 rotate([0, -90, 0]) 1080 linear_extrude(height = inner_back_edge_width) 1081 polygon([ 1082 [-extra, -inner_back_edge_depth], 1083 [inner_back_edge_height, -inner_back_edge_depth], 1084 [0, 0], 1085 [-extra, 0] 1086 ]); 1087 1088 /* Fillets to round off the edges. */ 1089 1090 union() { 1091 1092 /* Top left and right rounding. */ 1093 1094 translate([payload_left_extent + ro, back_depth / 2, upper_extent - ro]) 1095 rotate([0, 0, 180]) 1096 rotate([90, 0, 0]) 1097 fillet(rr, back_depth); 1098 translate([payload_right_extent - ro, back_depth / 2, upper_extent - ro]) 1099 rotate([90, 0, 0]) 1100 fillet(rr, back_depth); 1101 1102 /* Top back rounding. */ 1103 1104 translate([payload_centre, back_depth - ro, upper_extent - ro]) 1105 rotate([0, -90, 0]) 1106 fillet(rr, payload_width); 1107 1108 /* Outer edge rounding. */ 1109 1110 translate([payload_right_extent - ro, back_depth - ro, int_payload_lower_extent - bottom - extra]) 1111 fillet_justified(rr, payload_height + bottom + extra); 1112 1113 translate([payload_left_extent + ro, back_depth - ro, int_payload_lower_extent - bottom - extra]) 1114 rotate([0, 0, 90]) 1115 fillet_justified(rr, payload_height + bottom + extra); 1116 1117 translate([connector_width / 2 - ro, back_depth - ro, lower_extent]) 1118 fillet_partitioned(rr, connector_height - bottom); 1119 1120 translate([-connector_width / 2 + ro, back_depth - ro, lower_extent]) 1121 rotate([0, 0, 90]) 1122 fillet_partitioned(rr, connector_height - bottom); 1123 1124 /* 1125 Outer edge rounding of the back extension. This is done as a 1126 separate removal operation rather than occurring when creating the 1127 extension in order to ensure that the edges are actually rounded. 1128 1129 An extra overlapping measure is employed so that the filleting is 1130 continuous between the payload and connector portions. On the outside 1131 edges, the payload filleting is extended downwards. 1132 */ 1133 1134 translate([payload_left_extent + groove_depth + groove_ro, -groove_width_extra + groove_ro, 1135 int_payload_lower_extent - extra]) 1136 rotate([0, 0, 180]) 1137 fillet_justified(groove_rr, inner_payload_front_cutout_height + extra); 1138 1139 translate([payload_right_extent - groove_depth - groove_ro, -groove_width_extra + groove_ro, 1140 int_payload_lower_extent - extra]) 1141 rotate([0, 0, -90]) 1142 fillet_justified(groove_rr, inner_payload_front_cutout_height + extra); 1143 1144 /* Sides of connector. */ 1145 1146 translate([-connector_width / 2 + groove_depth + groove_ro, -groove_width_extra + groove_ro, 1147 lower_extent]) 1148 rotate([0, 0, 180]) 1149 fillet_partitioned(groove_rr, inner_connector_front_cutout_height); 1150 1151 translate([connector_width / 2 - groove_depth - groove_ro, -groove_width_extra + groove_ro, 1152 lower_extent]) 1153 rotate([0, 0, -90]) 1154 fillet_partitioned(groove_rr, inner_connector_front_cutout_height); 1155 1156 /* Top of payload. */ 1157 1158 translate([payload_centre, -groove_width_extra + groove_ro, 1159 int_payload_upper_extent + inner_top_front_cutout_height - groove_ro]) 1160 rotate([0, 0, 180]) 1161 rotate([0, -90, 0]) 1162 fillet(groove_rr, payload_width - groove_depth * 2); 1163 } 1164 } 1165 1166 /* PCBs for checking. */ 1167 1168 if (PCB) if (MODEL == ROM_CARTRIDGE) { 1169 translate([0, 0, int_payload_lower_extent]) 1170 difference() { 1171 1172 /* Mock PCB. */ 1173 1174 union() { 1175 cube_at(pcb_width, pcb_depth, 1176 pcb_height - edge_connector_height, 1177 0, -1, 1, 1178 0, edge_connector_cutout_back_depth, 0); 1179 cube_at(edge_connector_width, pcb_depth, 1180 edge_connector_height, 1181 0, -1, -1, 1182 0, edge_connector_cutout_back_depth, 0); 1183 1184 /* Feature. */ 1185 1186 cube_at(feature_width, feature_depth, feature_height, 1187 0, 1, 1, 1188 0, edge_connector_cutout_back_depth, feature_height_offset); 1189 } 1190 1191 /* Holes for mounting. */ 1192 1193 union() { 1194 cube_at(pcb_hole_width, pcb_hole_depth, pcb_left_hole_height, 1195 -1, -1, 1, 1196 pcb_left_hole_offset_from_centre, pcb_hole_start_depth, 1197 pcb_left_hole_offset); 1198 translate([pcb_left_hole_offset_from_centre - 1199 pcb_hole_width / 2, pcb_hole_start_depth, pcb_left_hole_offset]) 1200 rotate([90, 0, 0]) 1201 cylinder(h=pcb_hole_depth, r=pcb_hole_width / 2); 1202 translate([pcb_left_hole_offset_from_centre - 1203 pcb_hole_width / 2, pcb_hole_start_depth, 1204 pcb_left_hole_offset + pcb_left_hole_height]) 1205 rotate([90, 0, 0]) 1206 cylinder(h=pcb_hole_depth, r=pcb_hole_width / 2); 1207 } 1208 1209 union() { 1210 cube_at(pcb_hole_width, pcb_hole_depth, pcb_right_hole_height, 1211 1, -1, 1, 1212 pcb_right_hole_offset_from_centre, pcb_hole_start_depth, 1213 pcb_right_hole_offset); 1214 translate([pcb_right_hole_offset_from_centre + 1215 pcb_hole_width / 2, pcb_hole_start_depth, pcb_right_hole_offset]) 1216 rotate([90, 0, 0]) 1217 cylinder(h=pcb_hole_depth, r=pcb_hole_width / 2); 1218 translate([pcb_right_hole_offset_from_centre + 1219 pcb_hole_width / 2, pcb_hole_start_depth, 1220 pcb_right_hole_offset + pcb_right_hole_height]) 1221 rotate([90, 0, 0]) 1222 cylinder(h=pcb_hole_depth, r=pcb_hole_width / 2); 1223 } 1224 1225 /* Holes for lugs. */ 1226 1227 translate([ 1228 -int_connector_width / 2 + pcb_lug_offset_from_inside, 1229 pcb_hole_start_depth, pcb_lug_offset_from_bottom]) 1230 rotate([90, 0, 0]) 1231 cylinder(h=pcb_hole_depth, r=pcb_lug_hole_radius); 1232 1233 translate([ 1234 int_connector_width / 2 - pcb_lug_offset_from_inside, 1235 pcb_hole_start_depth, pcb_lug_offset_from_bottom]) 1236 rotate([90, 0, 0]) 1237 cylinder(h=pcb_hole_depth, r=pcb_lug_hole_radius); 1238 } 1239 } 1240 1241 if (PCB) if (MODEL == WIDE_CARTRIDGE) { 1242 translate([0, 0, int_payload_lower_extent]) 1243 difference() { 1244 1245 /* Mock PCB. */ 1246 1247 union() { 1248 cube_at(wide_pcb_width, wide_pcb_depth, 1249 wide_pcb_height - edge_connector_height, 1250 0, -1, 1, 1251 payload_centre, edge_connector_cutout_back_depth, 0); 1252 cube_at(edge_connector_width, wide_pcb_depth, 1253 edge_connector_height, 1254 0, -1, -1, 1255 0, edge_connector_cutout_back_depth, 0); 1256 1257 /* Feature. */ 1258 1259 cube_at(feature_width, feature_depth, feature_height, 1260 0, 1, 1, 1261 0, edge_connector_cutout_back_depth, feature_height_offset); 1262 } 1263 1264 /* Holes for lugs. */ 1265 1266 translate([ 1267 payload_centre + int_payload_width / 2 - wide_pcb_lug_offset_from_inside , 1268 wide_pcb_hole_start_depth, 1269 wide_pcb_height - edge_connector_height - wide_pcb_lug_offset_from_bottom]) 1270 rotate([90, 0, 0]) 1271 cylinder(h=wide_pcb_hole_depth, r=wide_pcb_lug_hole_radius); 1272 1273 translate([ 1274 payload_centre - int_payload_width / 2 + wide_pcb_lug_offset_from_inside , 1275 wide_pcb_hole_start_depth, 1276 wide_pcb_height - edge_connector_height - wide_pcb_lug_offset_from_bottom]) 1277 rotate([90, 0, 0]) 1278 cylinder(h=wide_pcb_hole_depth, r=wide_pcb_lug_hole_radius); 1279 1280 translate([ 1281 payload_centre + int_payload_width / 2 - wide_pcb_lug_offset_from_inside , 1282 wide_pcb_hole_start_depth, 1283 wide_pcb_lug_offset_from_bottom]) 1284 rotate([90, 0, 0]) 1285 cylinder(h=wide_pcb_hole_depth, r=wide_pcb_lug_hole_radius); 1286 1287 translate([ 1288 payload_centre - int_payload_width / 2 + wide_pcb_lug_offset_from_inside , 1289 wide_pcb_hole_start_depth, 1290 wide_pcb_lug_offset_from_bottom]) 1291 rotate([90, 0, 0]) 1292 cylinder(h=wide_pcb_hole_depth, r=wide_pcb_lug_hole_radius); 1293 } 1294 } 1295 } 1296 } 1297 1298 cartridge();