← Back to Models
Neat clamshell box. Print-in-place, customizable

Neat clamshell box. Print-in-place, customizable

by Mach3 ·

A neat little clamshell-style box with a hinge and a satisfying snap-fit closure that clicks into place with a gentle finger press. Built in OpenSCAD, it is fully customizable right in your browser - just click Customize and change height and width to your liking!

Interactive Preview & Customizer

3D preview with browser-based OpenSCAD rendering on parameter changes.

Loading previewLoading preview...
step 0.5
step 0.5
step 0.1
step 0.1
step 0.1
step 0.1
step 0.1
step 0.1
step 0.01
step 0.01
step 0.1
step 0.1
step 0.1
step 0.1
step 0.1
step 0.01
step 0.1
step 0.1
step 0.01
step 0.1
step 1
WASM compiler loads on first parameter change.
Download

Screenshots

Model screenshot

Prompt

design a neat clamshell box

OpenSCAD Code

/*
 * Clamshell Case with Snap-together Cone Hinge and Two-Dot Snap Clasp
 * Soap-box style rounded case with 45-degree chamfered bottom edges for support-free printing.
 * Designed to be printed flat on the print bed.
 */

/* [Box Dimensions] */
width = 38;           // Closed width (X direction)
length = 38;          // Closed length (Y direction)
base_height = 15;     // Height of the bottom part
lid_height = 6;       // Height of the top part
wall_thickness = 3.0; // Wall thickness of the shell

/* [Hinge Parameters] */
cone_base_radius = 2.5;     // Base radius of the hinge cone
cone_tip_radius = 1.3;     // Tip radius of the hinge cone
cone_height = 2.3;        // Height of the cone protrusion
cone_clearance = 0.26;       // Fit clearance for the socket (increased to prevent fusing)
hinge_axial_clearance = 0.34;// Axial clearance between knuckles (increased to prevent fusing)
hinge_sleeve_radius = 3.6;  // Outer radius of the hinge sleeves
body_clearance = 1.0;        // Clearance between base and lid bodies (increased to prevent rubbing)

/* [Rounding, Chamfer & Fit] */
corner_radius = 6;        // Outer corner rounding radius (XY plane)
chamfer_z = 2.2;          // Bottom edge 45-degree chamfer height (replaces Z rounding for support-free printing)
lip_height = 2.4;         // Height of the alignment collar
collar_clearance = 0.25;  // Clearance for friction fit collar (increased from 0.15)

/* [Clasp Parameters] */
clasp_radius = 0.9;      // Radius of the snap dots (increased by ~30% from 0.8)
clasp_protrusion = 0.5;  // Protrusion height of the dots (increased by ~30% from 0.4)
clasp_clearance = 0.12;   // Fit clearance for the recesses (increased from 0.1)

/* [Debug / Verification] */
render_slice = false;
slice_y = 0;              // Y position for 2D slice analysis
view_mode = "assembly";   // [assembly:Full Assembly, base_only:Base Only, lid_only:Lid Only]
lid_rotation_angle = 0;   // [0:180] - Angle of rotation (0 = open/print position, 180 = closed)

/* [Hidden] */
$fn = 64;
hinge_offset = hinge_sleeve_radius + body_clearance; // 4.6
hinge_axis_z = (base_height + lid_height) / 2; // 10.5mm

// --- Geometry Helper Modules ---

// Base module for the chamfered solid half-box
module chamfered_half_box(w, l, h, r_xy, r_z, is_lid=false) {
    x_min = is_lid ? hinge_offset : -w - hinge_offset;
    x_max = is_lid ? w + hinge_offset : -hinge_offset;
    
    c_x_min = x_min + r_xy;
    c_x_max = x_max - r_xy;
    c_y_min = -l/2 + r_xy;
    c_y_max = l/2 - r_xy;
    
    hull() {
        // Bottom footprint at Z = 0 (shrunk by chamfer_z to form 45-degree chamfer)
        r_bot = r_xy - r_z;
        translate([c_x_min, c_y_min, 0]) cylinder(r=r_bot, h=0.1);
        translate([c_x_max, c_y_min, 0]) cylinder(r=r_bot, h=0.1);
        translate([c_x_max, c_y_max, 0]) cylinder(r=r_bot, h=0.1);
        translate([c_x_min, c_y_max, 0]) cylinder(r=r_bot, h=0.1);
        
        // Full footprint at Z = r_z and above
        translate([c_x_min, c_y_min, r_z]) cylinder(r=r_xy, h=h-r_z);
        translate([c_x_max, c_y_min, r_z]) cylinder(r=r_xy, h=h-r_z);
        translate([c_x_max, c_y_max, r_z]) cylinder(r=r_xy, h=h-r_z);
        translate([c_x_min, c_y_max, r_z]) cylinder(r=r_xy, h=h-r_z);
    }
}

// Inner cavity shape with chamfered bottom matching the external chamfer to maintain constant wall thickness
// Note: h specifies the exact height of the cavity shape.
module inner_cavity(w, l, h, r_xy, t, is_lid=false) {
    x_min = is_lid ? hinge_offset + t : -w - hinge_offset + t;
    x_max = is_lid ? w + hinge_offset - t : -hinge_offset - t;
    y_min = -l/2 + t;
    y_max = l/2 - t;
    
    r_in_xy = max(0.5, r_xy - t);
    
    c_x_min = x_min + r_in_xy;
    c_x_max = x_max - r_in_xy;
    c_y_min = y_min + r_in_xy;
    c_y_max = y_max - r_in_xy;
    
    // To maintain a constant wall thickness of t perpendicular to the 45-degree outer chamfer of height chamfer_z:
    // The inner chamfer height must be max(0, chamfer_z + t * (sqrt(2) - 2)).
    inner_chamfer = max(0, chamfer_z + t * (sqrt(2) - 2));
    r_bot = r_in_xy - inner_chamfer;
    
    hull() {
        // Bottom footprint of cavity at Z = t (shrunk by inner_chamfer)
        translate([c_x_min, c_y_min, t]) cylinder(r=r_bot, h=0.1);
        translate([c_x_max, c_y_min, t]) cylinder(r=r_bot, h=0.1);
        translate([c_x_max, c_y_max, t]) cylinder(r=r_bot, h=0.1);
        translate([c_x_min, c_y_max, t]) cylinder(r=r_bot, h=0.1);
        
        // Full footprint of cavity at Z = t + inner_chamfer and above
        translate([c_x_min, c_y_min, t + inner_chamfer]) cylinder(r=r_in_xy, h=h - (t + inner_chamfer));
        translate([c_x_max, c_y_min, t + inner_chamfer]) cylinder(r=r_in_xy, h=h - (t + inner_chamfer));
        translate([c_x_max, c_y_max, t + inner_chamfer]) cylinder(r=r_in_xy, h=h - (t + inner_chamfer));
        translate([c_x_min, c_y_max, t + inner_chamfer]) cylinder(r=r_in_xy, h=h - (t + inner_chamfer));
    }
}

// --- Main Components ---

module base_part() {
    difference() {
        union() {
            // Main shelled base body (cavity extends 2mm above rim for clean cut)
            difference() {
                chamfered_half_box(width, length, base_height, corner_radius, chamfer_z, is_lid=false);
                inner_cavity(width, length, base_height + 2, corner_radius, wall_thickness, is_lid=false);
            }
            
            // Alignment collar (inner lip)
            // It is wall_thickness/2 thick, on the inner half of the wall, and height lip_height.
            // Exclude it from the back wall.
            difference() {
                // Outer part of lip
                difference() {
                    inner_cavity(width, length, base_height + lip_height, corner_radius, wall_thickness/2, is_lid=false);
                    inner_cavity(width, length, base_height + lip_height + 2, corner_radius, wall_thickness, is_lid=false);
                }
                // Cut off the back wall collar
                translate([-hinge_offset - wall_thickness - 1, -length/2 - 1, base_height - 1])
                    cube([wall_thickness + 2, length + 2, lip_height + 2]);
            }
            
            // Two clasp dots (nubs) on the front collar
            x_collar = -width - hinge_offset + wall_thickness/2;
            x_nub = x_collar - clasp_protrusion + clasp_radius;
            // Position the nub center to maintain exactly 0.4mm solid margin from the top edge of the collar
            clasp_z_offset = lip_height - 0.2 - clasp_radius; 
            z_nub = base_height + clasp_z_offset; 
            
            translate([x_nub, -length/4, z_nub]) sphere(r=clasp_radius);
            translate([x_nub,  length/4, z_nub]) sphere(r=clasp_radius);
        }
        
        // Fingernail recess (capsule-shaped pocket, width increased by 50% to 18mm)
        recess_r = 2.0;
        recess_depth = 1.2; // depth into the 3mm wall (leaves 1.8mm of wall thickness intact)
        recess_width = 18.0;
        y_limit = recess_width/2 - recess_r; // 7.0
        x_recess_center = -width - hinge_offset - (recess_r - recess_depth);
        
        hull() {
            translate([x_recess_center, -y_limit, base_height]) sphere(r=recess_r);
            translate([x_recess_center,  y_limit, base_height]) sphere(r=recess_r);
        }
    }
}


module lid_part() {
    difference() {
        // Main shelled lid body
        difference() {
            chamfered_half_box(width, length, lid_height, corner_radius, chamfer_z, is_lid=true);
            inner_cavity(width, length, lid_height + 2, corner_radius, wall_thickness, is_lid=true);
        }
        
        // Pocket for the base alignment collar
        // The pocket has width wall_thickness/2 + collar_clearance, depth lip_height + collar_clearance.
        // It is subtracted from the inside wall. Exclude back wall.
        difference() {
            difference() {
                inner_cavity(width, length, lid_height + 2, corner_radius, wall_thickness - (wall_thickness/2 + collar_clearance), is_lid=true);
                inner_cavity(width, length, lid_height + 3, corner_radius, wall_thickness, is_lid=true);
            }
            // Protect the back wall from having a pocket
            translate([hinge_offset - 1, -length/2 - 1, -1])
                cube([wall_thickness + 2, length + 2, lid_height + 3]);
        }
        
        // Two clasp recesses (dimples) on the front wall pocket
        x_collar = -width - hinge_offset + wall_thickness/2;
        x_nub = x_collar - clasp_protrusion + clasp_radius;
        x_recess = -x_nub; // symmetric reflection
        // Aligns with the nub center when closed (z_nub = base_height + clasp_z_offset)
        clasp_z_offset = lip_height - 0.2 - clasp_radius; 
        z_recess = lid_height - clasp_z_offset; 
        r_recess = clasp_radius + clasp_clearance;
        
        translate([x_recess, -length/4, z_recess]) sphere(r=r_recess);
        translate([x_recess,  length/4, z_recess]) sphere(r=r_recess);
    }
}

// Module for a base knuckle cylinder, support tab, and external corner chamfers
module base_knuckle(y_s, y_e, add_cone_dir=0) {
    difference() {
        union() {
            // Outer sleeve
            translate([0, y_s, hinge_axis_z])
                rotate([-90, 0, 0])
                    cylinder(r=hinge_sleeve_radius, h=y_e - y_s);
                    
            // Solid tab connecting to base body
            // Base wall is at X = -hinge_offset.
            // The tab is limited in Z range to form a 45-degree support slope (printable without support, no protrusion below chamfer)
            tab_z_min = max(0, hinge_axis_z - hinge_sleeve_radius - hinge_offset);
            hull() {
                translate([0, y_s, hinge_axis_z])
                    rotate([-90, 0, 0])
                        cylinder(r=hinge_sleeve_radius, h=y_e - y_s);
                
                translate([-hinge_offset, y_s, tab_z_min])
                    cube([0.1, y_e - y_s, base_height - tab_z_min]);
            }
        }
        
        // Chamfer cuts on the external corners of the external hinge arms
        cut_size = 3.2; // depth of cut at the sleeve end
        theta = atan(cut_size / (hinge_sleeve_radius + hinge_offset)); // angle of slice
        
        if (add_cone_dir == 1) {
            // Left knuckle: diagonal slice from [-hinge_offset, y_s] to [hinge_sleeve_radius, y_s + cut_size]
            translate([-hinge_offset, y_s, hinge_axis_z])
                rotate([0, 0, theta])
                    translate([0, -20, -20])
                        cube([25, 20, 40]);
        } else if (add_cone_dir == -1) {
            // Right knuckle: diagonal slice from [-hinge_offset, y_e] to [hinge_sleeve_radius, y_e - cut_size]
            translate([-hinge_offset, y_e, hinge_axis_z])
                rotate([0, 0, -theta])
                    translate([0, 0, -20])
                        cube([25, 20, 40]);
        }
    }
    
    // Cone protrusion (keep outside difference to ensure cone geometry is untouched)
    if (add_cone_dir == 1) {
        translate([0, y_e, hinge_axis_z])
            rotate([-90, 0, 0])
                cylinder(r1=cone_base_radius, r2=cone_tip_radius, h=cone_height);
    } else if (add_cone_dir == -1) {
        translate([0, y_s, hinge_axis_z])
            rotate([90, 0, 0])
                cylinder(r1=cone_base_radius, r2=cone_tip_radius, h=cone_height);
    }
}

// Module for a lid knuckle cylinder, support tab, and internal sockets 
module lid_knuckle(y_s, y_e, add_sockets=false) {
    difference() {
        union() {
            // Outer sleeve cylinder
            translate([0, y_s, hinge_axis_z])
                rotate([-90, 0, 0])
                    cylinder(r=hinge_sleeve_radius, h=y_e - y_s);
                    
            // Support tab connecting to lid body 
            // Lid wall is at X = hinge_offset.
            // The tab is limited in Z range to avoid extending below the flat portion of the wall (starts at chamfer_z).
            tab_z_min = chamfer_z;
            hull() {
                translate([0, y_s, hinge_axis_z])
                    rotate([-90, 0, 0])
                        cylinder(r=hinge_sleeve_radius, h=y_e - y_s);
                
                translate([hinge_offset , y_s, tab_z_min])
                    cube([0.1, y_e - y_s, lid_height - tab_z_min]);
            }
        }
        
        if (add_sockets) {
            // Socket at y_s (pointing +Y)
            translate([0, y_s - 0.1, hinge_axis_z])
                rotate([-90, 0, 0])
                    cylinder(r1=cone_base_radius + cone_clearance, r2=cone_tip_radius + cone_clearance, h=cone_height + cone_clearance + 0.1);
            
            // Socket at y_e (pointing -Y)
            translate([0, y_e + 0.1, hinge_axis_z])
                rotate([90, 0, 0])
                    cylinder(r1=cone_base_radius + cone_clearance, r2=cone_tip_radius + cone_clearance, h=cone_height + cone_clearance + 0.1);
        }
    }
}

module base_hinge_assembly() {
    hinge_L = length - 2 * corner_radius;
    
    // Segment 1 (Base, left) - 35% narrower (nominal width = 0.275 * hinge_L)
    y1_s = -hinge_L/2;
    y1_e = -hinge_L * 0.225;
    base_knuckle(y1_s, y1_e, add_cone_dir=1);
    
    // Segment 3 (Base, right) - 35% narrower (nominal width = 0.275 * hinge_L)
    y3_s = hinge_L * 0.225;
    y3_e = hinge_L/2;
    base_knuckle(y3_s, y3_e, add_cone_dir=-1);
}

module lid_hinge_assembly() {
    hinge_L = length - 2 * corner_radius;
    
    // Segment 2 (Lid, middle) - 35% wider (nominal width = 0.45 * hinge_L)
    y2_s = -hinge_L * 0.225 + hinge_axial_clearance;
    y2_e = hinge_L * 0.225 - hinge_axial_clearance;
    lid_knuckle(y2_s, y2_e, add_sockets=true);
}

// --- Assembly and Render Modes ---

module lid_assembly() {
    translate([0, 0, hinge_axis_z])
        rotate([0, -lid_rotation_angle, 0])
            translate([0, 0, -hinge_axis_z]) {
                union() {
                    lid_part();
                    lid_hinge_assembly();
                }
            }
}

module full_model() {
    if (view_mode == "base_only") {
        union() {
            base_part();
            base_hinge_assembly();
        }
    } else if (view_mode == "lid_only") {
        lid_assembly();
    } else {
        union() {
            base_part();
            base_hinge_assembly();
        }
        lid_assembly();
    }
}

// Render logic
if (render_slice) {
    // 2D projection slicing mode
    // To inspect the X-Z cross section at y = slice_y:
    // We translate the model along Y so that y = slice_y is at Y = 0.
    // Then we rotate 90 degrees around the X-axis so the X-Z plane aligns with the X-Y plane.
    // Then we apply projection(cut = true).
    projection(cut = true)
        rotate([90, 0, 0])
            translate([0, -slice_y, 0])
                full_model();
} else {
    // Normal 3D model
    // We instantiate the base and lid groups directly at the top level
    // to allow --enable lazy-union to export them as separate objects.
    if (view_mode == "base_only") {
        union() {
            base_part();
            base_hinge_assembly();
        }
    } else if (view_mode == "lid_only") {
        lid_assembly();
    } else {
        // Base object group
        union() {
            base_part();
            base_hinge_assembly();
        }
        // Lid object group
        lid_assembly();
    }
}

Download Model

Choose the model version and file format.

Model version