$fn=64; // --- Parameters --- dish_length = 120; dish_width = 80; dish_height = 18; wall_thickness = 2.5; base_thickness = 3; corner_radius = 12; ridge_width = 4; ridge_height = 4; ridge_spacing = 12; num_ridges = 9; hole_diameter = 5; num_holes_x = 7; num_holes_y = 3; hole_spacing_x = 15; hole_spacing_y = 20; // --- Modules --- module rounded_rect(l, w, h, r) { hull() { translate([-l/2 + r, -w/2 + r, 0]) cylinder(h=h, r=r); translate([ l/2 - r, -w/2 + r, 0]) cylinder(h=h, r=r); translate([ l/2 - r, w/2 - r, 0]) cylinder(h=h, r=r); translate([-l/2 + r, w/2 - r, 0]) cylinder(h=h, r=r); } } // --- Main Model --- difference() { union() { // Main tray body difference() { // Outer shell rounded_rect(dish_length, dish_width, dish_height, corner_radius); // Inner cavity (hollowed out) translate([0, 0, base_thickness]) rounded_rect( dish_length - 2*wall_thickness, dish_width - 2*wall_thickness, dish_height + 1, corner_radius - wall_thickness ); } // Drainage ridges intersection() { // Clip ridges to fit perfectly inside the inner cavity's rounded corners translate([0, 0, base_thickness]) rounded_rect( dish_length - 2*wall_thickness, dish_width - 2*wall_thickness, dish_height + 1, corner_radius - wall_thickness ); // Generate the ridges union() { for (i = [-(num_ridges-1)/2 : (num_ridges-1)/2]) { // Rounded top for the soap to rest on translate([i * ridge_spacing, 0, base_thickness + ridge_height - (ridge_width/2)]) rotate([90, 0, 0]) cylinder(h=dish_width, d=ridge_width, center=true); // Base of the ridge translate([i * ridge_spacing - ridge_width/2, -dish_width/2, base_thickness]) cube([ridge_width, dish_width, ridge_height - (ridge_width/2) + 0.1]); } } } } // Drainage holes through the base and ridges for (i = [-(num_holes_x-1)/2 : (num_holes_x-1)/2]) { for (j = [-(num_holes_y-1)/2 : (num_holes_y-1)/2]) { translate([i * hole_spacing_x, j * hole_spacing_y, -1]) cylinder(h=base_thickness + ridge_height + 2, d=hole_diameter); } } }