/* * Openable cablechain * Copyright (C) 2018 Alicia * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ $fn=$preview?16:64; // TODO: Clean up! // TODO: terminals /* // 3mm screws // 5.8mm distance // TODO: module cablechain_end(inner=false) difference() { cube([24.25,24,1.8], center=true); translate([(5.8+3)/2,6,0])cylinder(2, d1=3, d2=3, center=true); translate([-(5.8+3)/2,6,0])cylinder(2, d1=3, d2=3, center=true); // Soften edges a little linear_extrude(2, center=true)polygon([[12.125,9],[9,12],[-9,12],[-12.125,10],[-13,9],[-13,13],[13,13],[13,9]]); } */ module cablechain_snap_arm(thickness, width, height, tolerance=0) { translate([0,-thickness/2,0])cube([width+tolerance*2,thickness,height], center=true); if(tolerance>0) { translate([0,-thickness*1.5/2,(height-thickness-tolerance)/2])cube([width+tolerance*2,thickness*1.5,thickness+tolerance], center=true); }else{ rotate([0,-90,0])linear_extrude(width, center=true) polygon([[height/2,0],[height/2,-thickness], [height/2-thickness/2,-thickness*1.5], [height/2-thickness,-thickness],[height/2-thickness,0]]); } } module cablechain_link_side(height=13.5, pegdistance=15, pegdiameter=5.2, pegtolerance=0.25, wallwidth=2, ramp_peg=true) { rotate([90,0,0]) { difference() { cylinder(wallwidth, height/2, height/2); translate([0,height/3])cube([height,height/2,5], center=true); } difference() { // Peg cylinder(wallwidth*2, pegdiameter/2, pegdiameter/2); // Ramp the peg to make it easier to assemble if(ramp_peg) { rotate([-90,0,0])linear_extrude(height, center=true) polygon([[0,-wallwidth*2.01], [-pegdiameter/2,-wallwidth*1.2], [-pegdiameter/2,-wallwidth*2.1]]); } } // Inside offset=pegdistance+pegdiameter-height/2-0.7; toleranceA=0.4; // TODO: Find a name for these, linktolerance? toleranceB=0.5; // flattolerance? linear_extrude(wallwidth)polygon([[11.5-height/2,height/2], [11.5-height/2,height/2-1], [9.5-height/2,height/2-3], [-height/2,height/2-3], [-height/2,0], [0,0], [0,-height/2], [height/2,-height/2], [offset-toleranceA,-height/2], [offset-toleranceA,height/2-3+toleranceB], [offset+5.5,height/2-3+toleranceB], [offset+2.5,height/2]]); // Outside translate([0,0,wallwidth]) { difference() { union() { translate([pegdistance+pegdiameter,0,0])cylinder(wallwidth, height/2, height/2); linear_extrude(wallwidth)polygon([[11.5-height/2,height/2], [pegdistance+pegdiameter,height/2], [pegdistance+pegdiameter,-height/2], [0,-height/2], [11.5-height/2,0]]); } cylinder(wallwidth, height/1.8, height/1.8); // Peg hole translate([pegdistance+pegdiameter,0,0])cylinder(wallwidth, pegdiameter/2+pegtolerance, pegdiameter/2+pegtolerance); } } } } module cablechain_notches(width, topwidth, length) { rotate([0,-90,0])linear_extrude(length*0.7, center=true) polygon([[-topwidth/2,-width/2-topwidth/2], [topwidth/2,-width/2+topwidth/2], [topwidth/2,width/2-topwidth/2], [-topwidth/2,width/2+topwidth/2]]); } module cablechain_link(width=24.4, height=13.5, pegdistance=15, pegdiameter=5.2, pegtolerance=0.2, wallwidth=2, topwidth=1.3, bottomwidth=1.3, openable="hinge", ramp_peg=true, easyprint_angles=true) { difference() { // Sides union() { translate([0,-width/2+wallwidth*2,0])cablechain_link_side(height, pegdistance, pegdiameter, pegtolerance, wallwidth, ramp_peg); translate([0,width/2-wallwidth*2,0])mirror([0,1,0])cablechain_link_side(height, pegdistance, pegdiameter, pegtolerance, wallwidth, ramp_peg); } notchtolerance=0.4; // TODO: Make configurable if(openable=="snap") // Notches for the top { translate([(pegdistance+pegdiameter-height+notchtolerance)/2+6.35,0,(height-topwidth*1.5)/2]) cablechain_notches(width-wallwidth*3, topwidth, pegdistance+pegdiameter-10+notchtolerance); } else if(openable=="hinge") { translate([(pegdistance+pegdiameter-height)/2+6,-width/2+wallwidth,height/2-wallwidth]) { // Space for hinge rotate([0,90,0])difference() { cube([(wallwidth+notchtolerance)*2,(wallwidth+notchtolerance)*2,(pegdistance+pegdiameter-height)/2+notchtolerance*2], center=true); cylinder((pegdistance+pegdiameter-height)/2+notchtolerance*2, (wallwidth-notchtolerance)/2, (wallwidth-notchtolerance)/2, center=true); } // Round the rest of the wall to let the top over translate([0.5,-wallwidth,wallwidth])rotate([45,0,0])cube([pegdistance+pegdiameter-10+notchtolerance,wallwidth*2,wallwidth*0.8], center=true); translate([0.5,wallwidth,wallwidth])rotate([-45,0,0])cube([pegdistance+pegdiameter-10+notchtolerance,wallwidth*2,wallwidth*0.8], center=true); } // One notch translate([(pegdistance+pegdiameter-height+notchtolerance)/2+6.35,width/2-wallwidth*2,(height-topwidth*1.5)/2]) cablechain_notches(wallwidth, topwidth, pegdistance+pegdiameter-10+notchtolerance); } else if(openable=="snap-bottom") { // translate([(pegdistance+pegdiameter)/2.25,width/2,0])cube([pegdistance/6+notchtolerance*2,wallwidth*2,height+1], center=true); // translate([(pegdistance+pegdiameter)/2.25,width/2-wallwidth,(height-wallwidth)/2-notchtolerance])cube([pegdistance/6+notchtolerance*2,wallwidth*2,wallwidth+notchtolerance*2], center=true); translate([(pegdistance+pegdiameter)/2.25,width/2,0])cablechain_snap_arm(wallwidth, pegdistance/6, height, notchtolerance); translate([(pegdistance+pegdiameter)/2.25,-width/2,0])rotate([0,0,180])cablechain_snap_arm(wallwidth, pegdistance/6, height, notchtolerance); // Make some room for the openable bottom translate([(height+pegdistance)/2-5.25,0,(-height+bottomwidth)/2])cube([pegdistance-1.2+notchtolerance, width-wallwidth*2+notchtolerance, bottomwidth+notchtolerance], center=true); } if(easyprint_angles) // Tweak angles to reduce the need for supports { rotate([90,0,0])linear_extrude(width+1, center=true) { // Bottom curves polygon([[0,-height/2], [-height/2,0], [-height/2,-height], [pegdistance+pegdiameter+height/2,-height], [pegdistance+pegdiameter+height/2,0], [pegdistance+pegdiameter,-height/2]]); // Peg holes radius45=(pegdiameter/2+pegtolerance)*sin(45); translate([pegdistance+pegdiameter,0]) polygon([[radius45,radius45], [-radius45,radius45], [0,radius45*2]]); } } } // Bottom if(!openable || openable!="snap-bottom") { translate([(height+pegdistance)/2-5.25,0,(-height+bottomwidth)/2])cube([pegdistance-1.2, width-wallwidth*2, bottomwidth], center=true); } // Top if(!openable || openable=="snap-bottom") { translate([(pegdistance+pegdiameter-height)/2+6.5,0,(height-topwidth)/2])cube([pegdistance+pegdiameter-10, width-wallwidth*4, topwidth], center=true); } if(openable=="snap") { translate([0,width+10,-(height-topwidth)/2])cube([pegdistance+pegdiameter-10, width-wallwidth*4, topwidth], center=true); // Top notches translate([0,width+10,-(height-topwidth)/2])cablechain_notches(width-wallwidth*3, topwidth, pegdistance+pegdiameter-10); } else if(openable=="hinge") { notchtolerance=0.4; // TODO: Make configurable // Hinge translate([(pegdistance+pegdiameter-height)/2+6.5,-width+wallwidth*2-notchtolerance,(height+topwidth)/2-wallwidth*2]) cube([pegdistance+pegdiameter-10, width-wallwidth*4, topwidth], center=true); translate([(pegdistance+pegdiameter-height)/2+6,-width/2+wallwidth,height/2-wallwidth])rotate([0,90,0])difference() { union() { cylinder((pegdistance+pegdiameter-height)/2, wallwidth, wallwidth, center=true); translate([wallwidth/2,-wallwidth-notchtolerance/2,0])cube([wallwidth, wallwidth*2+notchtolerance, (pegdistance+pegdiameter-height)/2], center=true); } cylinder((pegdistance+pegdiameter-height)/2+1, wallwidth/2, wallwidth/2, center=true); } // Notch translate([(pegdistance+pegdiameter-height+notchtolerance)/2+6.35,-width*1.5+wallwidth*4-notchtolerance/2,(height+topwidth)/2-wallwidth*2]) rotate([0,180,0])cablechain_notches(wallwidth, topwidth, pegdistance+pegdiameter-10+notchtolerance); } else if(openable=="snap-bottom") { translate([(height+pegdistance)/2-5.25,width*1.5,(-height+bottomwidth)/2])cube([pegdistance-1.2, width-wallwidth*2, bottomwidth], center=true); translate([(pegdistance+pegdiameter)/2.25,width*2,0])cablechain_snap_arm(wallwidth, pegdistance/6, height); translate([(pegdistance+pegdiameter)/2.25,width,0])rotate([0,0,180])cablechain_snap_arm(wallwidth, pegdistance/6, height); } } module cablechain(links, width=24.4, height=13.5, pegdistance=15, pegdiameter=5.2, pegtolerance=0.25, wallwidth=2, topwidth=1.3, bottomwidth=1.3, openable="hinge", ramp_peg=true, easyprint_angles=true, detached=false) { for(i=[0:links-1]) { translate([(pegdistance+(detached?height:pegdiameter))*i,detached?(i*width/2):0,0]) { difference() { cablechain_link(width, height, pegdistance, pegdiameter, pegtolerance, wallwidth, topwidth, bottomwidth, openable, ramp_peg, easyprint_angles); if(!detached) // Leave some space inbetween links for print-in-place { toleranceA=0.4; // TODO: Find a name for these, and use a common variable/argument with cablechain_link()? toleranceB=0.5; offset=pegdistance+pegdiameter-height/2-0.7; rotate([90,0,0])linear_extrude(width-wallwidth*2+0.8, center=true)polygon([[offset-toleranceA,-height/2+bottomwidth], [offset-toleranceA,height/2-3+toleranceB], [offset+5.5,height/2-3+toleranceB], [offset+2.5,height/2], [offset-toleranceA,height/2], [offset,height], [offset+20,height], [offset+20,-height], [offset+3.15,-height], [offset+3.15,-height/2+bottomwidth]]); } } } } } // Samples cablechain(3); translate([0,-50,0])cablechain(3, openable=false); !cablechain(2, openable="snap-bottom"); // TODO: Move top/bottom arms to one side, rotate for strength