$fn = 64; // --- Parameters --- // Diameter of the paint tubes (31mm fits standard 60ml acrylic/oil tubes) tube_d = 31; // Wall thickness of the holders wall = 2.4; // Grid columns cols = 5; // Grid rows rows = 4; // Angle of tubes for visibility and access (degrees) angle = 45; // Depth of the tube holder cups depth = 40; // --- Derived Dimensions --- outer_d = tube_d + wall * 2; spacing_x = outer_d + 4; spacing_y = 45; bp_width = (cols - 1) * spacing_x + outer_d + 8; bp_height = (rows - 1) * spacing_y + outer_d + depth * sin(angle) + 10; bp_thick = 4; // --- Main Assembly --- // Designed to print flat on its back. // The cups will print without supports. // The mounting cleats on the back will require minor supports. union() { // Main body (Backplate + Cups) difference() { union() { // Backplate cube([bp_width, bp_height, bp_thick]); // Array of angled cups for (c = [0 : cols - 1]) { for (r = [0 : rows - 1]) { x = c * spacing_x + outer_d / 2 + 4; y = r * spacing_y + outer_d / 2 + 10; translate([x, y, bp_thick]) rotate([-angle, 0, 0]) cup(); } } } // Trim anything that protrudes behind the backplate (Z < 0) translate([-10, -10, -50]) cube([bp_width + 20, bp_height + 20, 50]); } // Mounting Cleats (ArtArsenal / French Cleat style wall hooks) translate([0, bp_height - 10, 0]) rail_cleat(bp_width); translate([0, 40, 0]) rail_cleat(bp_width); } // --- Modules --- module cup() { difference() { // Outer shell cylinder(d=outer_d, h=depth); // Inner hole for the paint tube translate([0, 0, wall]) cylinder(d=tube_d, h=depth + 1); // Front visibility and access slot // Placed on the +Y side, which rotates to become the top face translate([0, outer_d / 2, depth / 2 + 8]) cube([tube_d * 0.6, outer_d, depth], center=true); } } module rail_cleat(w) { // Extension from backplate (overlaps by 1mm into backplate for a manifold union) translate([0, -6, -8]) cube([w, 6, 9]); // Downward pointing hook body hull() { translate([0, -6, -8]) cube([w, 6, 4]); translate([0, -14, -8]) cube([w, 0.1, 4]); } }