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