Celownik v2

This commit is contained in:
sasza 2025-06-02 12:26:09 +02:00
parent ec4c7e4d91
commit ecb5fd9f72
3 changed files with 666 additions and 0 deletions

176
Sasza/celownik/Chamfer.scad Normal file
View file

@ -0,0 +1,176 @@
/**
* This code is published under a
* Creative Commons Attribution-NonCommercial-ShareAlike 3.0
* licence, please respect it.
*
* Chamfered primitives for OpenSCAD v1.2 - By TimeWaster
*/
/**
* chamferCube returns an cube with 45° chamfers on the edges of the
* cube. The chamfers are diectly printable on Fused deposition
* modelling (FDM) printers without support structures.
*
* @param size The size of the cube along the [x, y, z] axis,
* example: [1, 2, 3]
* @param chamfers Which chamfers to render along the [x, y, z] axis,
* example: [[0, 0, 0, 0], [1, 1, 1, 1], [0, 0, 0, 0]]
* X axis: 4 values in clockwise order starting from
* the zero point, as seen from "Left view" (Ctrl + 6)
* Y axis: 4 values in clockwise order starting from
* the zero point, as seen from "Front view" (Ctrl + 8)
* Z axis: 4 values in clockwise order starting from
* the zero point, as seen from "Bottom view" (Ctrl + 5)
* @param ch The "height" of the chamfers as seen from
* one of the dimensional planes (The real
* length is side c in a right angled triangle)
*/
module chamferCube(size, chamfers = [undef, undef, undef], ch = 1, ph1 = 1, ph2 = undef, ph3 = undef, ph4 = undef, sizeX = undef, sizeY = undef, sizeZ = undef, chamferHeight = undef, chamferX = undef, chamferY = undef, chamferZ = undef) {
if(size[0]) {
chamferCubeImpl(size[0], size[1], size[2], ch, chamfers[0], chamfers[1], chamfers[2]);
} else {
// keep backwards compatibility
size = (sizeX == undef) ? size : sizeX;
chamfers = (sizeY == undef) ? chamfers : sizeY;
ch = (sizeZ == undef) ? ch : sizeZ;
ph1 = (chamferHeight == undef) ? ph1 : chamferHeight;
ph2 = (chamferX == undef) ? ph2 : chamferX;
ph3 = (chamferY == undef) ? ph3 : chamferY;
ph4 = (chamferZ == undef) ? ph4 : chamferZ;
chamferCubeImpl(size, chamfers, ch, ph1, ph2, ph3, ph4);
}
}
module chamferCubeImpl(sizeX, sizeY, sizeZ, chamferHeight, chamferX, chamferY, chamferZ) {
chamferX = (chamferX == undef) ? [1, 1, 1, 1] : chamferX;
chamferY = (chamferY == undef) ? [1, 1, 1, 1] : chamferY;
chamferZ = (chamferZ == undef) ? [1, 1, 1, 1] : chamferZ;
chamferCLength = sqrt(chamferHeight * chamferHeight * 2);
difference() {
cube([sizeX, sizeY, sizeZ]);
for(x = [0 : 3]) {
chamferSide1 = min(x, 1) - floor(x / 3); // 0 1 1 0
chamferSide2 = floor(x / 2); // 0 0 1 1
if(chamferX[x]) {
translate([-0.1, chamferSide1 * sizeY, -chamferHeight + chamferSide2 * sizeZ])
rotate([45, 0, 0])
cube([sizeX + 0.2, chamferCLength, chamferCLength]);
}
if(chamferY[x]) {
translate([-chamferHeight + chamferSide2 * sizeX, -0.1, chamferSide1 * sizeZ])
rotate([0, 45, 0])
cube([chamferCLength, sizeY + 0.2, chamferCLength]);
}
if(chamferZ[x]) {
translate([chamferSide1 * sizeX, -chamferHeight + chamferSide2 * sizeY, -0.1])
rotate([0, 0, 45])
cube([chamferCLength, chamferCLength, sizeZ + 0.2]);
}
}
}
}
/**
* chamferCylinder returns an cylinder or cone with 45° chamfers on
* the edges of the cylinder. The chamfers are diectly printable on
* Fused deposition modelling (FDM) printers without support structures.
*
* @param h Height of the cylinder
* @param r Radius of the cylinder (At the bottom)
* @param r2 Radius of the cylinder (At the top)
* @param ch The "height" of the chamfer at radius 1 as
* seen from one of the dimensional planes (The
* real length is side c in a right angled triangle)
* @param ch2 The "height" of the chamfer at radius 2 as
* seen from one of the dimensional planes (The
* real length is side c in a right angled triangle)
* @param a The angle of the visible part of a wedge
* starting from the x axis counter-clockwise
* @param q A circle quality factor where 1.0 is a fairly
* good quality, range from 0.0 to 2.0
*/
module chamferCylinder(h, r, r2 = undef, ch = 1, ch2 = undef, a = 0, q = -1.0, height = undef, radius = undef, radius2 = undef, chamferHeight = undef, chamferHeight2 = undef, angle = undef, quality = undef) {
// keep backwards compatibility
h = (height == undef) ? h : height;
r = (radius == undef) ? r : radius;
r2 = (radius2 == undef) ? r2 : radius2;
ch = (chamferHeight == undef) ? ch : chamferHeight;
ch2 = (chamferHeight2 == undef) ? ch2 : chamferHeight2;
a = (angle == undef) ? a : angle;
q = (quality == undef) ? q : quality;
height = h;
radius = r;
radius2 = (r2 == undef) ? r : r2;
chamferHeight = ch;
chamferHeight2 = (ch2 == undef) ? ch : ch2;
angle = a;
quality = q;
module cc() {
upperOverLength = (chamferHeight2 >= 0) ? 0 : 0.01;
lowerOverLength = (chamferHeight >= 0) ? 0 : 0.01;
cSegs = circleSegments(max(radius, radius2), quality);
if(chamferHeight >= 0 || chamferHeight2 >= 0) {
hull() {
if(chamferHeight2 > 0) {
translate([0, 0, height - abs(chamferHeight2)]) cylinder(abs(chamferHeight2), r1 = radius2, r2 = radius2 - chamferHeight2, $fn = cSegs);
}
translate([0, 0, abs(chamferHeight)]) cylinder(height - abs(chamferHeight2) - abs(chamferHeight), r1 = radius, r2 = radius2, $fn = cSegs);
if(chamferHeight > 0) {
cylinder(abs(chamferHeight), r1 = radius - chamferHeight, r2 = radius, $fn = cSegs);
}
}
}
if(chamferHeight < 0 || chamferHeight2 < 0) {
if(chamferHeight2 < 0) {
translate([0, 0, height - abs(chamferHeight2)]) cylinder(abs(chamferHeight2), r1 = radius2, r2 = radius2 - chamferHeight2, $fn = cSegs);
}
translate([0, 0, abs(chamferHeight) - lowerOverLength]) cylinder(height - abs(chamferHeight2) - abs(chamferHeight) + lowerOverLength + upperOverLength, r1 = radius, r2 = radius2, $fn = cSegs);
if(chamferHeight < 0) {
cylinder(abs(chamferHeight), r1 = radius - chamferHeight, r2 = radius, $fn = cSegs);
}
}
}
module box(brim = abs(min(chamferHeight2, 0)) + 1) {
translate([-radius - brim, 0, -brim]) cube([radius * 2 + brim * 2, radius + brim, height + brim * 2]);
}
module hcc() {
intersection() {
cc();
box();
}
}
if(angle <= 0 || angle >= 360) cc();
else {
if(angle > 180) hcc();
difference() {
if(angle <= 180) hcc();
else rotate([0, 0, 180]) hcc();
rotate([0, 0, angle]) box(abs(min(chamferHeight2, 0)) + radius);
}
}
}
/**
* circleSegments calculates the number of segments needed to maintain
* a constant circle quality.
* If a globalSegementsQuality variable exist it will overwrite the
* standard quality setting (1.0). Order of usage is:
* Standard (1.0) <- globalCircleQuality <- Quality parameter
*
* @param r Radius of the circle
* @param q A quality factor, where 1.0 is a fairly good
* quality, range from 0.0 to 2.0
*
* @return The number of segments for the circle
*/
function circleSegments(r, q = -1.0) = (q >= 3 ? q : ((r * PI * 4 + 40) * ((q >= 0.0) ? q : globalCircleQuality)));
// set global quality to 1.0, can be overridden by user
globalCircleQuality = 1.0;

View file

@ -0,0 +1,79 @@
use <picatinny-rail.scad>
use <Chamfer.scad>
// To jest model celownika do karabinu CZ Varmint
// Koncepcyjnie składa się z 3 części - na górze jest szyna
// Picattiny (rail), na środku jest obejma lufy (clamp), i na dole
// wystaje pałąk do przykręcenia (post)
// parameters
length = 35; // ustaw poniżej 5 do testów wymiarów
c_s = length>5 ? 1 : 0.4; // chamfer_size używane tylko do testów
rail_height = 15;
clamp_inner = 22;
clamp_inner_acurracy = 50;
clamp_thickness = 4;
post_thickness=15;
post_length=20;
post_slit=2;
m3 = [6,3,5.7]; // M3
m2 = [4.3,2,3.3]; // M2
m2_5 = [5.5, 2.5, 4.68]; // M2.5
screw = m2;
hex_size=screw[0]; // rozmiar nakrętki od rogu do rogu
screw_thread=screw[1]; // rozmiar gwintu
screw_head=screw[2]; // rozmiar główki śruby
sink_depth = 3; // jak głęboko w pałąk powinny się wtopić główka oraz nakrętka
difference() {
// celownik
union() {
// obejma wokół lufy
cyl_size = clamp_inner/2 + clamp_thickness;
chamferCylinder(h=length, r=cyl_size, ch=1*c_s, q=1);
// na górze picatinny
if (length > 5) {
rotate([90,0,-90]) translate([0,0,rail_height])
picatinny_rail(l=length, d=rail_height, c=1.5, n=1, a=10);
}
// na dole wystający trzpień do przykręcenia
translate([clamp_inner/2, post_thickness*-0.5, 0])
chamferCube([post_length, post_thickness, length], ch=c_s);
}
// lufa, śruby oraz przecięcie
union() {
// lufa karabinu
translate([0,0,-0.1])
chamferCylinder(h=length+0.2, r=clamp_inner/2, $fn=clamp_inner_acurracy, ch=-c_s);
// przecięcie
translate([clamp_inner/2-0.1,(post_slit * -0.5),-0.1])
cube([(post_length + 0.2),post_slit,length+0.2]);
// miejsce na śrubę
if (length > 5){
for (i = [length*1/4, length*3/4]) {
translate([(clamp_inner + post_length)/2,0,i])
rotate([90,90,0])
union() {
// Miejsce na nakrętkę
translate([0,0,post_thickness/2-sink_depth])
cylinder(r=hex_size, h=sink_depth+0.1, $fn=6);
// dziura na wylot na gwint
translate([0,0,(-post_thickness-0.5)/2])
cylinder(r=screw_thread, h=post_thickness+0.5, $fn=20);
// miejsce na główkę śruby
translate([0,0,(-post_thickness/2)-0.1])
chamferCylinder(r=screw_head, h=sink_depth+0.1, ch=-0.5, ch2=0);
}
}
}
}
}

View file

@ -0,0 +1,411 @@
// What's this?
// This file defines a module that creates a Picatinny rail on the xy plane, running up the y axis from the origin, with the top in the z-direction. I'm using dimensions from standardization agreement 4694. This is the same as an original Picatinny rail except that the dimensions are metric (standard for 3d printing anyways) and the tolerances are different (but those depend on your printer anyways).
// Usage
// There are two intened uses for this: to put rails in your own designs, and to create standalone rails.
// If you want to incorporate rails into your designs, the easiest way to do that is to use this as a library. Place this document wherever you normally install OpenSCAD libraries on your system (or just in the same directory as your project), type "use<Picatinny rail>;" into your project, and use the picatinny_rail module where you want a rail. This will only define a picatinny_rail module and a picatinny_rail_version function - it leaves your namespace clear.
// If you want standalone rails then you'll probably want to use Picatinny_customizer.scad, which should be in the same download as this file and includes its own comments at the beginning of the file explaining how to use it.
// Parameters
// This module accepts these parameters, none of which are mandatory and all of which can be set by a short synonym:
// "length" or "l" is a number which sets the length of the rail. If this is unspecified, a length will be automatically set based on the number of slots and on alignment.
// "number" or "n" is a non-negative integer which sets the number of recoil slots. If this is unspecified, the number of slots will be automatically set based on length. (If both are unspecified, the default is a length of 75mm with 7 slots.)
// "drop" or "d" is a number which extends the base of the rail below the xy plane, which is useful for putting a rail onto something without zero-width gaps (or just making an extra-tall rail). The default is 0.
// "chamfer" or "c" is a number which creates a chamfer at the ends of the rail. The default is 0.
// "alignment" or "a" controlls the position of the recoil slots. If "auto" (or unspecified), the slots are placed automatically in a way that ignores screw ports if they are present (see below). If set to a number, the offset of the first slot is that length (measured from the start of the rail to the middle of the slot). If set to "slot" the slots will automatically align with the first screw. If set to "ridge" the ridges between the slots will automatically align with the first screw. (That's the first screw listed in the "screw_positions" vector, not necesarily the one closest to the beginning of the rail.)
// "screw_positions" or "sp" is a vector of any length which defines the position of the screw ports. Each element in the vector is the position of a screw port, measured from the start of the rail, positioned allong the centerline of the rail. The default is an empty vector, which results in no screw ports.
// "screw_type" or "st" is a number which sets the type of screw used to hold the rail down. If this is a positive number, a hole apropriate for a metric hex socket head cap screw of that size will be made. If this is negative, a metric hex socket countersunk head screw will be used instead. (If the number isn't a recognized metric screw size, this module will linearly interpolate between the closest ones.) The default is an m4 hex socket head screw.
// "screw_clearance" or "sc" is a number which sets the clearance around the screw. The default is 0.25.
// "omit_slots_near_screws" or "os" can casue slots near to screw posts to be omitted. If set to true (the default), the module chooses which slots to omit based on slot width and screw size. If set to false, no slots will be omitted. If set to a positive number, slots whose center are within that distance of the center of a screw will be omitted.
function picatinny_rail_version() = 2.1;
module picatinny_rail(
length = undef, l = undef,
number = undef, n = undef,
drop = undef, d = undef,
chamfer = undef, c = undef,
alignment = undef, a = undef,
screw_positions = undef, sp = undef,
screw_type = undef, st = undef,
screw_clearance = undef, sc = undef,
omit_slots_near_screws = undef, os = undef
){
//---------------------------------------------------------
//
// Dimensions
// from STANAG 4694
inner_box_width = 19.05;
inner_box_height = 2.74;
bounding_box_width = 21.2;
bounding_box_toprise = 4.17;
bounding_box_height = 9.4;
stem_width = 15.6;
recoil_depth = 3;
recoil_width = 5.35;
recoil_spacing = 10;
diamond_box_height = bounding_box_height - bounding_box_toprise + inner_box_height/2;
diamond_box_dimension = (inner_box_width + inner_box_height)/sqrt(2);
// Chosen
minimum_alignment = recoil_width/2 + 4;
default_alignment = 7.5;
default_number = 7;
default_screw_type = 4;
//---------------------------------------------------------
//
// Process specifications
// Collate specifications. Everything except for length, number, and alignment can be set directly from specifications.
function read(longname, shortname, default) =
longname != undef ? longname : shortname != undef ? shortname : default;
l1 = read(length, l, undef);
n1 = read(number, n, undef);
d0 = read(drop, d, 0);
c0 = read(chamfer, c, 0);
a1 = read(alignment, a, undef);
sp0 = read(screw_positions, sp, []);
st0 = read(screw_type, st, 4);
sc0 = read(screw_clearance, sc, 0.25);
os0 = read(omit_slots_near_screws, os, true);
// Sanity-check
if(is_num(n1) && is_num(l1))
if((n1 - 1) * recoil_spacing > l1){
echo("Oops! Picatinny rail was called with a specified length and number of slots, but there's no way that many slots will fit in that length.");
};
if(is_string(a1))
if(a1 != "slot" && a1 != "ridge" && a1 != "auto"){
echo("Oops! Picatinny rail was called with a specified alignment that's an unrecognized string. This may be a typo.");
};
if(is_string(a1))
if((a1 == "slot" || a1 == "ridge") && len(sp0) == 0){
echo("Oops! Picatinny rail was called with an alignment option which references screw port positions, but no screw port positions were defined.");
};
// Set alignment, based on ...
a0 =
// ... specification?
is_num(a1) ? a1 :
// ... slot alignment?
a1 == "slot" && len(sp0) > 0 ? ((sp0[0] - minimum_alignment + recoil_spacing) % recoil_spacing) + minimum_alignment :
// ... ridge alignment?
a1 == "ridge" && len(sp0) > 0 ? ((sp0[0] - minimum_alignment + 1.5 * recoil_spacing) % recoil_spacing) + minimum_alignment :
// ... length and number?
is_num(l1) && is_num(n1) ? (l1 - recoil_spacing * (n1-1))/2 :
// ... just length?
is_num(l1) ? ((l1 - 2 * minimum_alignment) % recoil_spacing)/2 + minimum_alignment :
// ... default (which is the same as what "just number" would be).
default_alignment;
// Set number, based on ...
n0 =
// ... specification?
is_num(n1) ? n1 :
// ... length (and alignment, which we just found)?
is_num(l1) ? floor((l1 - a0 - minimum_alignment)/recoil_spacing) + 1 :
// ... default.
default_number;
// Set length, based on ...
l0 =
// ... specification?
is_num(l1) ? l1 :
// ... alignment and number.
recoil_spacing * (n0 - 1) + 2 * a0;
//---------------------------------------------------------
//
// Screw plugs
// Known values
// https://boltsparts.github.io/en/parts/names/MetricHexSocketHeadCapScrew.html
// https://boltsparts.github.io/en/parts/names/MetricHexSocketCountersunkHeadScrew.html
known_sizes = [
// Key name, head dia, head depth
// Countersunk
[-24, 39, 14],
[-22, 36, 13.1],
[-20, 36, 8.5],
[-18, 33, 8],
[-16, 30, 7.5],
[-14, 27, 7],
[-12, 24, 6.5],
[-10, 20, 5.5],
[-8, 16, 4.4],
[-6,12, 3.3],
[-5, 10, 2.8],
[-4, 8, 2.3],
[-3, 6, 1.7],
[-2.5, 5, 1.5],
[-2, 4, 1.2],
// Socket
[2, 3.8, 2],
[2.5, 4.5, 2.5],
[3, 5.5, 3],
[4, 7, 4],
[5, 8.5, 5],
[6, 10, 6],
[8, 13, 8],
[10, 16, 10],
[12, 18, 12],
[14, 21, 14],
[16, 24, 16],
[18, 27, 18],
[20, 30, 20],
[22, 33, 22],
[24, 36, 24]
];
// Useful functions
function linear_interpolate(start, end, valuestart, valueend, x) =
start == end ?
valuestart :
(x - start) * (valueend-valuestart) / (end - start)
+ valuestart;
function lower_bound(m, k=len(known_sizes)-1) =
known_sizes[k][0] <= m ? k : lower_bound(m, k-1);
function upper_bound(m, k=0) =
known_sizes[k][0] >= m ? k : upper_bound(m, k+1);
function interpolating_read(key, paramiter) =
linear_interpolate(
start = known_sizes[lower_bound(key)][0],
end = known_sizes[upper_bound(key)][0],
valuestart = known_sizes[lower_bound(key)][paramiter],
valueend = known_sizes[upper_bound(key)][paramiter],
x = key
);
// Dimension-finding functions
function hole_dia() =
// Custom size?
is_list(st0) ?
st0[0] + 2 * sc0 :
// Interpolated size?
abs(is_num(st0) ? st0 : default_screw_type) + 2 * sc0;
function head_dia() =
// Custom size?
is_list(st0) ?
st0[1] + 2 * sc0 :
// Interpolated size
interpolating_read(is_num(st0) ? st0 : default_screw_type, 1) + 2 * sc0;
function head_depth() =
// Custom size?
is_list(st0) && len(st0) > 2 ?
st0[2] + sc0 :
// Interpolated size
interpolating_read(is_num(st0) ? st0 : default_screw_type, 2);
//---------------------------------------------------------
//
// Is this slot OK?
removal_threshold =
is_num(os0) ? os0 :
head_dia()/2 + recoil_width/2;
function too_close(slot, screw) =
abs(a0 + slot * recoil_spacing - sp0[screw]) < removal_threshold;
function is_OK(slot, screw = len(sp0)-1) =
os0 == false ? true :
screw < 0 ? true :
too_close(slot, screw) ? false :
is_OK(slot, screw-1);
//---------------------------------------------------------
//
// Parts
// Basic shape
module bounding_box(){
translate([-bounding_box_width/2, 0, -d0])
cube([bounding_box_width, l0, bounding_box_height + d0]);
};
module chamfer_plugs(){
translate([0, 0, bounding_box_height - c0])
rotate([-45, 0, 0])
translate([0, -bounding_box_width, 0])
cube(bounding_box_width*2, center=true);
translate([0, l0,bounding_box_height - c0])
rotate([45, 0, 0])
translate([0, bounding_box_width, 0])
cube(bounding_box_width*2, center=true);
};
module stem(){
translate([-stem_width/2, 0, -d0])
cube([stem_width, l0, diamond_box_height + d0]);
};
module diamond_box(){
translate([0,0,diamond_box_height])
rotate([0,45,0])
translate([-diamond_box_dimension/2, 0, -diamond_box_dimension/2])
cube([diamond_box_dimension, l0, diamond_box_dimension]);
};
// Recoil slots
module recoil(){
translate([-bounding_box_width, -recoil_width/2, bounding_box_height - recoil_depth])
cube([2 * bounding_box_width, recoil_width, 2 * recoil_depth]);
};
module recoils(){
if(n0==0){}
else if (n0==1){
translate([0,a0,0])
if(is_OK(0))
recoil();
}
else{
for(i = [0:n0 - 1]){
if(is_OK(i))
translate([0,a0 + i * recoil_spacing,0])
recoil();
};
};
};
// Screw ports
module screw_shaft_plug(){
cylinder(
h = 9999999,
center = true,
d = hole_dia(),
$fn = 36
);
};
module screw_socket_head_plug(){
translate([0, 0, bounding_box_height - head_depth()])
cylinder(
h = 9999999,
d = head_dia(),
$fn = 36
);
};
module screw_coundersunk_head_plug(){
translate([0,0, bounding_box_height - head_dia()/2])
cylinder(
d1 = 0,
d2 = 9999999,
h = 9999999/2,
$fn = 36
);
};
module screw_plug()
union(){
screw_shaft_plug();
if(st0 > 0){
screw_socket_head_plug();
} else {
screw_coundersunk_head_plug();
};
};
module screws_plug(){
if (len(sp0) > 0){
for(i = sp0){
translate([0,i,0])
screw_plug();
};
};
};
// Assembly
intersection(){
union(){
stem();
diamond_box();
};
difference(){
bounding_box();
union(){
recoils();
chamfer_plugs();
screws_plug();
};
};
};
};