$fa = $preview ? 10 : 10; // 1; $fs = $preview ? 0.2 : 0.2; // 0.01; colors = [ "#df0000", "#fb8721", "#f1e100", "#158f25", "#2c3ee5", "#7706af", "#333333", "#88502b", "#5dccfb", "#f0a67b", "#ffffff" ]; wideR = 65; braidScale = 5; braidR = 12; strands = len(colors); strandGap = 3; wideRes = $preview ? 90 : 360; strandRes = $preview ? 20 : 180; extraHeight=50; braidCircumference = 2 * PI * braidR; strandR = (braidCircumference / strands - strandGap) / 2; outermostCircumference = 2 * PI * (wideR + braidR); strandH = outermostCircumference * wideRes / 360 + 0.1; function rodriguesRotation(vector, axis, angle) = vector * cos(angle) + cross(axis, vector) * sin(angle) + axis * (axis * vector) * (1 - cos(angle)); /* these cross sections were always parallel to the Z axis, but that’s not really correct actual d/dwideT of center of braid is (-wideR sin wideT, wideR cos wideT, 0) magnitude of that is wideR absolute d/dwideT of center of strand is ( -wideR sin wideT - braidR (braidScale cos wideT sin braidT + sin wideT cos braidT), wideR cos wideT + braidR (-braidScale sin wideT sin braidT + cos wideT cos braidT), braidR braidScale cos braidT ) the Maxima CAS says that the magnitude of that is sqrt(wideR² + 2wideRbraidR cos braidT + braidR²cos²braidT + braidR²braidScale²) but actually OpenSCAD has a norm() function to get magnitudes anyway oops strand plane basis is strand plane normal x braid center normal, since we know those two vectors are never parallel with braidScale ≠ 0 */ function point(wideT, strandI, strandT) = let ( braidO = [wideR * cos(wideT), wideR * sin(wideT), 0], braidPlaneNormal = [-wideR * sin(wideT), wideR * cos(wideT), 0], braidPlaneNormalUnit = braidPlaneNormal / wideR, braidT = wideT * braidScale - strandI * 360 / strands, braidPlaneRel = [braidR * cos(braidT), braidR * sin(braidT)], strandO = [braidO.x + braidPlaneRel.x * cos(wideT), braidO.y + braidPlaneRel.x * sin(wideT), braidPlaneRel.y], strandPlaneNormal = [braidPlaneNormal.x - braidR * (braidScale * cos(wideT) * sin(braidT) + sin(wideT) * cos(braidT)), braidPlaneNormal.y + braidR * (-braidScale * sin(wideT) * sin(braidT) + cos(wideT) * cos(braidT)), braidR * braidScale * cos(braidT)], strandPlaneNormalUnit = strandPlaneNormal / norm(strandPlaneNormal), strandPlaneBasis = cross(strandPlaneNormalUnit, braidPlaneNormalUnit), strandRelDir = rodriguesRotation(strandPlaneBasis, strandPlaneNormalUnit, strandT), strandRel = strandRelDir * strandR / norm(strandRelDir) ) strandO + strandRel; pointCount = (wideRes + 1) * (strandRes + 1); module theStrand(strandI) { color(colors[strandI]) polyhedron( [ for (wideT = [0 : 360/wideRes : 360.01]) each [ for (strandT = [0 : 360/strandRes : 360.01]) point(wideT, strandI, strandT) ] ], [ for (wideTI = [0 : (strandRes + 1) : pointCount - 1]) each [ for (strandTI = [wideTI : wideTI + strandRes + 1]) each [ [(strandTI + 1) % pointCount, strandTI % pointCount, (strandTI + strandRes + 1) % pointCount], [(strandTI + strandRes + 1) % pointCount, strandTI % pointCount, (strandTI + strandRes) % pointCount], ] ] ] ); } module prideBraid() { for (strandI = [0 : strands - 1]) { theStrand(strandI); } } function pointForSlot(wideT, strandI, strandT) = let ( braidO = [wideR * cos(wideT), wideR * sin(wideT), 0], braidPlaneNormal = [-wideR * sin(wideT), wideR * cos(wideT), 0], braidPlaneNormalUnit = braidPlaneNormal / wideR, braidT = wideT * braidScale - strandI * 360 / strands, braidPlaneRel = [braidR * cos(braidT), braidR * sin(braidT)], strandO = [braidO.x + braidPlaneRel.x * cos(wideT), braidO.y + braidPlaneRel.x * sin(wideT), braidPlaneRel.y], strandPlaneNormal = [braidPlaneNormal.x - braidR * (braidScale * cos(wideT) * sin(braidT) + sin(wideT) * cos(braidT)), braidPlaneNormal.y + braidR * (-braidScale * sin(wideT) * sin(braidT) + cos(wideT) * cos(braidT)), braidR * braidScale * cos(braidT)], strandPlaneNormalUnit = strandPlaneNormal / norm(strandPlaneNormal), strandPlaneBasis = cross(strandPlaneNormalUnit, braidPlaneNormalUnit), strandRelDir = rodriguesRotation(strandPlaneBasis, strandPlaneNormalUnit, strandT), strandRel = strandRelDir * (strandR + 0.75) / norm(strandRelDir) ) strandO + strandRel; module theStrandForSlot(strandI) { polyhedron( [ for (wideT = [0 : 360/wideRes : 360.01]) each [ for (strandT = [0 : 360/strandRes : 360.01]) pointForSlot(wideT, strandI, strandT) ] ], [ for (wideTI = [0 : (strandRes + 1) : pointCount - 1]) each [ for (strandTI = [wideTI : wideTI + strandRes + 1]) each [ [(strandTI + 1) % pointCount, strandTI % pointCount, (strandTI + strandRes + 1) % pointCount], [(strandTI + strandRes + 1) % pointCount, strandTI % pointCount, (strandTI + strandRes) % pointCount], ] ] ] ); } module prideBraidForSlot() { for (strandI = [0 : strands - 1]) { theStrandForSlot(strandI); } } module prideBraidTorus() { rotate_extrude() translate([wideR, 0, 0]) circle(r=braidR+0.25); } module slotForDifference() { maxR=wideR+braidR+strandR; union() { translate([0, 0, (braidR+strandR+2.5)/2]) prideBraidTorus(); translate([0, 0, (braidR+strandR+2.5)/2]) prideBraidForSlot(); translate([0, 0, braidR+strandR/2]) rotate_extrude() translate([wideR, 0, 0]) circle(r=braidR+strandR+3); } } module peg() { rotate([0, 0, 45]) cube([2, 2, 10], center=true); } sliceX = 1; sliceY = 1; slices = 3; //color("#000000") intersection() { r = (wideR + braidR + strandR + 3) * 2; difference() { slotCenterR=r*0.75; translate([0, 0, -10-extraHeight*0.5]) cylinder(h=(braidR+strandR+3)*2+extraHeight, r=r, center=true); translate([slotCenterR,0,0]) slotForDifference(); translate([slotCenterR*cos(120),slotCenterR*sin(120),0]) slotForDifference(); translate([slotCenterR*cos(-120),slotCenterR*sin(-120),0]) slotForDifference(); } difference() { translate([-r+2*r*sliceX/slices, -r+2*r*sliceY/slices, -500000]) cube([2*r/slices, 2*r/slices, 1000000]); // pegs parallel to the Y axis for (pegX = [0 : slices - 1]) { for (pegY = [1 : slices - 1]) { for (pegZ = [0 : 90 : 360]) { translate([-r+2*r*(pegX+0.5)/slices, -r+2*r*pegY/slices, -10-extraHeight*0.5]) translate([0.75 * r/slices * cos(pegZ+45), 0, 0.75 * (braidR+strandR+3+extraHeight/2) * sin(pegZ+45) * 0.8 - 6]) rotate([90, 0, 0]) peg(); }}} // pegs parallel to the X axis for (pegX = [1 : slices - 1]) { for (pegY = [0 : slices - 1]) { for (pegZ = [0 : 90 : 360]) { translate([-r+2*r*pegX/slices, -r+2*r*(pegY+0.5)/slices, -10-extraHeight*0.5]) translate([0, 0.75 * r/slices * cos(pegZ+45), 0.75 * (braidR+strandR+3+extraHeight/2) * sin(pegZ+45) * 0.8 - 6]) rotate([0, 90, 0]) peg(); }}} } } rotate([0, 0, 30]) translate([0, 7, 6.9]) { color("#057748") translate([0, 4, -0.01]) scale(34) union() { translate([wideR*0.4/34, 0, 0]) import("my-hand.stl"); translate([-wideR*0.4/34, 0, 0]) scale([-1, 1, 1]) import("my-hand.stl"); } color("#057748") translate([0, -40, 3.4/2-0.01]) rotate([180, 0, -45]) scale([25, 25, 1]) linear_extrude(height = 3.4, center = true) { polygon([ [0, 0.5], [0.5, 0.5], [0.5, -0.5], [0.4, -0.5], [0.4, 0.4], [0, 0.4] ]); } } // !peg();