openscad Picatinny forward grip

Sep 8, 2026 5:11pm · 20 views
openscad1c3d
Code 7,608 characters
// 4-Piece Collet-Lock Foregrip: Left Half, Right Half, Separate Recoil Bar, & Handle
$fn = 60;

/* [Display Mode] */
mode = 2; // 0 = Assembled, 1 = Exploded View, 2 = Print Bed Layout (All 4 Parts Flat)

/* [Mount Dimensions] */
mount_len     = 42;
mount_width   = 34;
mount_height  = 16;

/* [Separate Recoil Key Specs] */
bar_width   = 20.0; // Spans across rail channel into both halves
bar_thick   = 5.0;  // Fits MIL-STD-1913 slot (5.23mm nominal)
bar_height  = 3.2;  // Slot depth engagement
bar_tol     = 0.20; // Clearance for sliding fit

/* [Thread & Conical Wedge Specs] */
thread_dia       = 24.0; 
thread_pitch     = 4.0;  
thread_len       = 18.0; 
shroud_height    = 8.0;  
shroud_outer_dia = 34.0; 
thread_steps     = 24;   

wedge_dia_bottom = 27.5; 
wedge_dia_top    = 32.5; 

/* [Fit Calibration] */
tol         = 0.20; 
split_gap   = 1.0; 

/* [Grip Dimensions] */
grip_h      = 80;
grip_dia    = 32;
flute_count = 16;

// -------------------------------------------------------------
// MIL-STD-1913 Picatinny Negative Channel
// -------------------------------------------------------------
module picatinny_channel(len) {
    w_crown = 20.6 / 2;
    w_neck  = 15.0 / 2;
    h_crown = 2.4;
    h_bevel = 3.0;

    rotate([90, 0, 0])
    linear_extrude(height = len, center = true)
    polygon(points = [
        [-w_neck, 2.0],
        [ w_neck, 2.0],
        [ w_neck, -0.2],
        [ w_crown, -0.2 - h_bevel],
        [ w_crown, -0.2 - h_bevel - h_crown],
        [-w_crown, -0.2 - h_bevel - h_crown],
        [-w_crown, -0.2 - h_bevel],
        [-w_neck, -0.2]
    ]);
}

// -------------------------------------------------------------
// Helical Thread Tool
// -------------------------------------------------------------
module helical_thread(dia, pitch, len, internal=false) {
    total_deg = (len / pitch) * 360;
    deg_step  = 360 / thread_steps;
    dz_step   = pitch / thread_steps;
    r_core    = dia / 2 + (internal ? tol : 0);
    h_ridge   = 1.15;

    for (a = [0 : deg_step : total_deg - deg_step]) {
        z1 = (a / 360) * pitch;
        z2 = z1 + dz_step;
        hull() {
            rotate([0, 0, a])
                translate([r_core, 0, z1])
                rotate([0, 90, 0])
                cylinder(r1 = h_ridge, r2 = 0.2, h = 1.0, $fn = 4);

            rotate([0, 0, a + deg_step])
                translate([r_core, 0, z2])
                rotate([0, 90, 0])
                cylinder(r1 = h_ridge, r2 = 0.2, h = 1.0, $fn = 4);
        }
    }
}

// -------------------------------------------------------------
// Part 1 & 2: Symmetrical Mount Core Block
// -------------------------------------------------------------
module base_collet_solid() {
    difference() {
        union() {
            // Main block
            translate([0, 0, -mount_height / 2])
                cube([mount_width, mount_len, mount_height], center = true);

            // Conical clamping wedge
            translate([0, 0, -mount_height - shroud_height])
                cylinder(d1 = wedge_dia_bottom, d2 = wedge_dia_top, h = shroud_height);

            // Male thread stem
            translate([0, 0, -mount_height - shroud_height - thread_len])
                cylinder(d = thread_dia - 2.2, h = thread_len);

            // Helical threads
            translate([0, 0, -mount_height - shroud_height - thread_len])
                helical_thread(dia = thread_dia - 2.2, pitch = thread_pitch, len = thread_len, internal = false);
        }

        // Picatinny rail channel
        picatinny_channel(mount_len + 4);

        // Precision mortise pocket for the separate recoil bar
        translate([0, 0, -3.2])
            cube([bar_width + (bar_tol * 2), bar_thick + (bar_tol * 2), bar_height + (bar_tol * 2)], center = true);
    }
}

module mount_half_left() {
    difference() {
        base_collet_solid();
        translate([50 - split_gap/2, 0, -50])
            cube([100, mount_len + 10, 150], center = true);
    }
}

module mount_half_right() {
    difference() {
        base_collet_solid();
        translate([-50 + split_gap/2, 0, -50])
            cube([100, mount_len + 10, 150], center = true);
    }
}

// -------------------------------------------------------------
// Part 3: Separate Removable Recoil Cross-Bar
// -------------------------------------------------------------
module separate_recoil_bar() {
    chamfer = 0.6;
    hull() {
        translate([0, 0, 0])
            cube([bar_width - chamfer*2, bar_thick - chamfer*2, bar_height], center = true);
        translate([0, 0, 0])
            cube([bar_width, bar_thick - chamfer*2, bar_height - chamfer*2], center = true);
    }
}

// -------------------------------------------------------------
// Part 4: Matching Wedged Handle
// -------------------------------------------------------------
module solid_reinforced_handle() {
    difference() {
        rotate_extrude($fn = 60) {
            polygon(points = [
                [0, 0],
                [grip_dia/2 + 2.5, 0],
                [grip_dia/2 + 2.5, 6],
                [grip_dia/2, 14],
                [grip_dia/2, grip_h - 20],
                [grip_dia/2 - 1.0, grip_h - 14],
                [shroud_outer_dia/2, grip_h],
                [0, grip_h]
            ]);
        }

        // Internal Conical Wedge Seat
        translate([0, 0, grip_h - shroud_height - 0.1])
            cylinder(d1 = wedge_dia_bottom + (tol * 2), d2 = wedge_dia_top + (tol * 2), h = shroud_height + 1);

        // Thread Socket
        translate([0, 0, grip_h - shroud_height - thread_len - 0.1])
            cylinder(d = thread_dia + (tol * 2), h = thread_len + 1);

        // Internal Threads
        translate([0, 0, grip_h - shroud_height - thread_len])
            helical_thread(dia = thread_dia, pitch = thread_pitch, len = thread_len - 2, internal = true);

        // Flutes
        for (i = [0 : flute_count - 1]) {
            rotate([0, 0, i * (360 / flute_count)])
                translate([grip_dia / 2, 0, (grip_h - shroud_height) / 2 - 2])
                scale([0.9, 1.3, 1])
                cylinder(d = 3.0, h = grip_h - shroud_height - 24, center = true, $fn = 16);
        }

        // Accent Ring
        translate([0, 0, 10])
            rotate_extrude()
            translate([grip_dia / 2, 0, 0])
            circle(r = 1.2, $fn = 16);
    }
}

// -------------------------------------------------------------
// Visual Output Modes
// -------------------------------------------------------------
if (mode == 0) {
    // Assembled View
    mount_half_left();
    mount_half_right();
    color([0.85, 0.2, 0.2]) translate([0, 0, -3.2]) separate_recoil_bar();
    translate([0, 0, -mount_height - grip_h]) solid_reinforced_handle();
} 
else if (mode == 1) {
    // Exploded View
    translate([-14, 0, 0]) mount_half_left();
    translate([14, 0, 0])  mount_half_right();
    color([0.85, 0.2, 0.2]) translate([0, 0, 10]) separate_recoil_bar();
    translate([0, 0, -mount_height - grip_h - 25]) solid_reinforced_handle();
} 
else if (mode == 2) {
    // All 4 Pieces Laid Completely Flat on Z = 0 for 3D Printing

    // Left Half (Flat split-face down)
    translate([-26, -20, 0])
        rotate([0, 90, 0])
        mount_half_left();

    // Right Half (Flat split-face down)
    translate([26, -20, 0])
        rotate([0, -90, 0])
        mount_half_right();

    // Recoil Bar (Flat face down)
    translate([0, -20, bar_thick / 2])
        rotate([90, 0, 0])
        separate_recoil_bar();

    // Handle (Flat bottom pommel down)
    translate([0, 25, 0])
        solid_reinforced_handle();
}