aboutsummaryrefslogtreecommitdiff
path: root/pride-braid-triple-stand.scad
diff options
context:
space:
mode:
authorMelody Horn <melody@boringcactus.com>2026-07-03 14:21:38 -0600
committerMelody Horn <melody@boringcactus.com>2026-07-03 14:24:44 -0600
commit5f9b5a67b73336e9810a3fc87c03dd2b4b43bed0 (patch)
treec62940d855bb9b1a9eaba85001c25d9a7aaa28bd /pride-braid-triple-stand.scad
downloadpride-braid-5f9b5a67b73336e9810a3fc87c03dd2b4b43bed0.tar.gz
pride-braid-5f9b5a67b73336e9810a3fc87c03dd2b4b43bed0.zip
initial commitHEADcanon
Diffstat (limited to 'pride-braid-triple-stand.scad')
-rw-r--r--pride-braid-triple-stand.scad231
1 files changed, 231 insertions, 0 deletions
diff --git a/pride-braid-triple-stand.scad b/pride-braid-triple-stand.scad
new file mode 100644
index 0000000..a3d603a
--- /dev/null
+++ b/pride-braid-triple-stand.scad
@@ -0,0 +1,231 @@
+$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();