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