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