// Fractal tree: a module that calls itself. Each branch splits in two, // a little shorter and thinner, until the twigs end in leaves. The seed // bends every branch differently, so no two trees are the same. /* [Tree] */ // Diameter of the ring size = 120; // [80:5:240] // How many times the branches split depth = 6; // [4:1:8] // Angle between two branches, in degrees spread = 56; // [20:1:80] // Length of a branch compared with the one before ratio = 0.76; // [0.6:0.01:0.82] // How much the branches wander wildness = 14; // [0:1:30] // Different seeds grow different trees seed = 12; // [1:1:999] // Round leaves at the ends of the twigs leaves = true; /* [Text] */ // Engraved on the ground, leave empty for none label = "2026"; // 16 /* [Material] */ // Sheet thickness $thickness = 3; // [2:0.5:6] // Beam width $kerf = 0.15; // [0:0.01:0.3] /* [Hidden] */ $material = "plywood"; $fn = 20; r = size / 2; rim = max(3, size * 0.03); ground = -r * 0.62; leaf = size / 120; thinnest = 1.6; rnd = rands(-1, 1, pow(2, depth + 1), seed); base = [0, ground + r * 0.05]; // The trunk length that would make the tree about as tall as the ring tall = r * 1.45 * (1 - ratio) / (1 - pow(ratio, depth + 1)); // Where every branch ends, from a trunk of this length standing at [0, 0]: // the same steps as branch() below, as numbers instead of shapes function tips(length, level, k, pos = [0, 0], a = 0) = let ( a2 = a + rnd[k] * wildness, end = pos + length * [-sin(a2), cos(a2)] ) level == 0 ? [end] : concat([end], [for (side = [0, 1]) each tips( length * ratio * (1 + rnd[(2 * k + 1 + side) % len(rnd)] * 0.12), level - 1, 2 * k + 1 + side, end, a2 + (side == 0 ? 1 : -1) * spread / 2)]); // How far a tip may go: inside the ring, leaving room for its leaf inner = r - rim - 3.6 * leaf - 1.5; // How much of direction d fits inside the ring from the base function fit(d) = let (bd = base * d, dd = d * d, c = base * base - inner * inner) dd == 0 ? 1 : (-bd + sqrt(bd * bd - dd * c)) / dd; // Shorten the tree just enough to keep every leaf inside the ring trunk = tall * min(1, min([for (d = tips(tall, depth, 0)) fit(d)])); // One branch of this length and width w, then two smaller ones on top. // k numbers the branches so each one gets its own random bend. module branch(length, w, level, k) { bend = rnd[k] * wildness; rotate(bend) { hull() { circle(d = w); translate([0, length]) circle(d = max(thinnest, w * 0.7)); } if (level > 0) translate([0, length]) for (side = [0, 1]) rotate((side == 0 ? 1 : -1) * spread / 2) branch(length * ratio * (1 + rnd[(2 * k + 1 + side) % len(rnd)] * 0.12), max(thinnest, w * 0.7), level - 1, 2 * k + 1 + side); else if (leaves) translate([0, length + leaf]) ellipse([1.5 * leaf, 2.6 * leaf]); } } module ground_shape() intersection() { circle(r = r - rim / 2, $fn = 180); translate([0, ground - r * 0.9]) ellipse([r * 1.3, r], $fn = 180); } difference() { union() { difference() { circle(r = r, $fn = 180); circle(r = r - rim, $fn = 180); } ground_shape(); // Closing: grow then shrink, so gaps too small to cut are filled in offset(r = -0.3) offset(r = 0.3) translate(base) branch(trunk, max(5, size * 0.055), depth, 0); // Hanging loop translate([0, r]) circle(d = 12); } translate([0, r + 1.5]) circle(d = 5); } if (label != "") engrave() translate([0, ground - r * 0.2]) text(label, size = r * 0.1, halign = "center", valign = "center");