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