Compare commits
3 commits
50f1def8d5
...
279e941082
Author | SHA1 | Date | |
---|---|---|---|
279e941082 | |||
c010e3fb40 | |||
bc6c4822eb |
16 changed files with 179 additions and 114 deletions
README.mdcase.scade_ring.scad
images
lockpuzzle_assembly_01.pnglockpuzzle_assembly_02.pnglockpuzzle_assembly_03.pnglockpuzzle_assembly_04.pnglockpuzzle_assembly_05.pnglockpuzzle_assembly_06.pnglockpuzzle_assembly_07.pnglockpuzzle_rendered_locked.pnglockpuzzle_rendered_unlocked.png
key_dial.scadlockpuzzle.scadrender.shring.scad
53
README.md
Normal file
53
README.md
Normal file
|
@ -0,0 +1,53 @@
|
|||
# Lock Puzzle
|
||||
|
||||
A 3D-printed puzzle box written in OpenSCAD.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
### Rendering
|
||||
|
||||
1. Make sure you have OpenSCAD installed.
|
||||
2. In the file `render.sh`, change at least the `LENGTH` variable to the length of your chosen password.
|
||||
- You can also change other settings such as the alphabet, or the diameter of the box.
|
||||
- The default diameter (and the one the printed model has been tested with) is 50mm. The lock mechanism takes 20mm off this, so the inner diameter is 30mm.
|
||||
- The length of the box currently depends on the length of the password, and is `(LENGTH*10+15)mm`. The length of the inner space is `(LENGTH*10)mm`.
|
||||
3. Run `./render.sh`, which will create all the STL files you'll need in the `out/` directory.
|
||||
- If you are using an OpenSCAD version without support for the Manifold rendering engine, remove the `--backend=manifold` argument.
|
||||
|
||||
### 3D Printing
|
||||
|
||||
Print the following objects:
|
||||
|
||||
- 1 x `case.stl`
|
||||
- 1 x `plug.stl`
|
||||
- 1 x `key_dial_${character}.stl` for every character in your password.
|
||||
- LENGTH x `ring_click.stl`
|
||||
- 1 x `ring.stl`
|
||||
|
||||
### Assembly
|
||||
|
||||
1. Start with the printed case:
|
||||
|
||||

|
||||
2. Add the first ring to the case as shown here. Note that the outward-pointing position indicator and the sideways-pointing "clicker" must be oriented exactly as shown here:
|
||||
|
||||

|
||||
3. Add the first dial case as shown here.
|
||||
|
||||

|
||||
4. Repeat steps 2 and 3 until you reach the last dial.
|
||||
|
||||

|
||||
5. Add the final ring to the case as shown here. Note the absence of the clicker.
|
||||
|
||||

|
||||
6. Turn all dials so that the cutout lines up with the long slot in the case. Turning the dials might require quite some force at first, but after a few turns the clicker will wear down a bit, which will make the dials easier to turn.
|
||||
|
||||

|
||||
7. Finally, insert the plug all the way, then turn the dials to lock the puzzle box:
|
||||
|
||||

|
12
case.scad
12
case.scad
|
@ -15,10 +15,13 @@ module case(n=5, diameter=50, tolerance=0.2, $fn=60) {
|
|||
}
|
||||
// cutouts for e-clips
|
||||
rotate([0,0,45]) {
|
||||
for (i = [0:n]) {
|
||||
translate([0,0,10*i+3]) {
|
||||
cube([5, diameter, 3], center=true);
|
||||
cube([diameter, 5, 3], center=true);
|
||||
for (j = [0:2]) {
|
||||
rotate([0,0,120*j]) {
|
||||
for (i = [0:n]) {
|
||||
translate([0,diameter/4,10*i+3]) {
|
||||
cube([5, diameter/2, 3], center=true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +39,7 @@ module case(n=5, diameter=50, tolerance=0.2, $fn=60) {
|
|||
// DEFAULTS FOR COMMAND LINE INVOCATION
|
||||
|
||||
ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
|
||||
N = 5;
|
||||
DIAMETER = 50;
|
||||
TOLERANCE = 0.2;
|
||||
|
|
80
e_ring.scad
80
e_ring.scad
|
@ -1,80 +0,0 @@
|
|||
|
||||
|
||||
module e_ring(alphabet_length=26, dia=48, width=3, thick=2, spoke_depth=4.5, spoke_width=7, key_depth=3, key_length=4, tolerance = 0.2, indicator=false, clicker=false, $fn=60) {
|
||||
|
||||
difference() { // halfing
|
||||
union() {
|
||||
// outer ring
|
||||
difference() {
|
||||
cylinder(d=dia, h=width-4*tolerance, center=true);
|
||||
cylinder(d=dia-2*thick, h=width+1, center=true);
|
||||
}
|
||||
// key spokes
|
||||
difference() {
|
||||
union() {
|
||||
intersection() {
|
||||
cube([spoke_width, dia, width], center=true);
|
||||
cylinder(d=dia, h=width-4*tolerance, center=true);
|
||||
}
|
||||
intersection() {
|
||||
cube([dia, spoke_width, width], center=true);
|
||||
cylinder(d=dia, h=width-4*tolerance, center=true);
|
||||
}
|
||||
}
|
||||
cylinder(d=dia-2*spoke_depth+2*tolerance, h=width+1, center=true);
|
||||
}
|
||||
// keys
|
||||
difference() {
|
||||
union() {
|
||||
cube([key_length, dia-spoke_depth, width-4*tolerance], center=true);
|
||||
cube([dia-spoke_depth, key_length, width-4*tolerance], center=true);
|
||||
}
|
||||
cube([dia-spoke_depth*2-key_depth*2, dia-spoke_depth*2-key_depth*2, width+1], center=true);
|
||||
}
|
||||
// indicator
|
||||
if (indicator) {
|
||||
rotate([0,0,45]) {
|
||||
translate([-dia/2,0,0]) {
|
||||
cube([1.5, width/2, width-4*tolerance], center=true);
|
||||
}
|
||||
}
|
||||
}
|
||||
// clicker
|
||||
if (clicker) {
|
||||
rotate([0,0,45+(floor(alphabet_length/4))*360/alphabet_length]) {
|
||||
translate([-dia/2+3,0,0]) {
|
||||
cube([3, 4, width-4*tolerance], center=true);
|
||||
}
|
||||
translate([-dia/2+3.5,0,width/2-0.5]) {
|
||||
scale([0.5,1,1]) {
|
||||
rotate([0,0,45]) {
|
||||
cylinder($fn=4, h=1, d1=5.6, d2=3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
translate([-dia/2-2, -tolerance, -width]) {
|
||||
cube([dia+4, dia, 2*width]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// DEFAULTS FOR COMMAND LINE INVOCATION
|
||||
|
||||
ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
DIAMETER = 50;
|
||||
TOLERANCE = 0.2;
|
||||
FN = 60;
|
||||
INDICATOR = false;
|
||||
CLICKER = false;
|
||||
|
||||
|
||||
e_ring(
|
||||
alphabet_length=len(ALPHABET),
|
||||
dia=DIAMETER-2,
|
||||
indicator=INDICATOR,
|
||||
clicker=CLICKER,
|
||||
tolerance=TOLERANCE, $fn=FN);
|
BIN
images/lockpuzzle_assembly_01.png
Normal file
BIN
images/lockpuzzle_assembly_01.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 34 KiB |
BIN
images/lockpuzzle_assembly_02.png
Normal file
BIN
images/lockpuzzle_assembly_02.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 43 KiB |
BIN
images/lockpuzzle_assembly_03.png
Normal file
BIN
images/lockpuzzle_assembly_03.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 36 KiB |
BIN
images/lockpuzzle_assembly_04.png
Normal file
BIN
images/lockpuzzle_assembly_04.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 64 KiB |
BIN
images/lockpuzzle_assembly_05.png
Normal file
BIN
images/lockpuzzle_assembly_05.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 64 KiB |
BIN
images/lockpuzzle_assembly_06.png
Normal file
BIN
images/lockpuzzle_assembly_06.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 39 KiB |
BIN
images/lockpuzzle_assembly_07.png
Normal file
BIN
images/lockpuzzle_assembly_07.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 45 KiB |
BIN
images/lockpuzzle_rendered_locked.png
Normal file
BIN
images/lockpuzzle_rendered_locked.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 61 KiB |
BIN
images/lockpuzzle_rendered_unlocked.png
Normal file
BIN
images/lockpuzzle_rendered_unlocked.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 60 KiB |
|
@ -1,26 +1,27 @@
|
|||
|
||||
module key_dial(alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ", key="A", width=7, dia=50, thick=5.5, key_width=4, key_height=3, text_depth=1, text_width=4, antipick=1.5, tolerance = 0.2, $fn = 60) {
|
||||
module key_dial(alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ", key="C", width=7, dia=50, thick=5.5, key_width=4, key_height=3, text_depth=1, font_size=-1, antipick=1.5, tolerance = 0.2, $fn = 60) {
|
||||
key_indices = search(key, alphabet, 0);
|
||||
assert(len(key_indices[0]) == 1, "key character not unique");
|
||||
key_index = key_indices[0][0];
|
||||
_font_size = font_size > 0 ? font_size : min(dia*PI/len(alphabet)/1.6, dia/10);
|
||||
rotate([-0.5/len(alphabet)*360,0,0]) {
|
||||
difference() {
|
||||
rotate([0,90,0]) {
|
||||
cylinder($fn=len(alphabet), h=width, d=dia, center=true);
|
||||
cylinder($fn=$fn, h=width, d=dia, center=true);
|
||||
}
|
||||
// inner ring
|
||||
rotate([0,90,0]) {
|
||||
cylinder($fn=60, h=width+2, d=dia-thick*2, center=true);
|
||||
cylinder($fn=$fn, h=width+2, d=dia-thick*2, center=true);
|
||||
}
|
||||
// key
|
||||
rotate([((0.5)/len(alphabet)) * 360,0,0]) {
|
||||
rotate([45+((0.5+key_index)/len(alphabet)) * 360,0,0]) {
|
||||
translate([0,0,dia/2-thick]) {
|
||||
cube([width+2, key_width, key_height*2], center=true);
|
||||
}
|
||||
}
|
||||
// anti-picking keys
|
||||
for (i = [1:len(alphabet)-1]) {
|
||||
rotate([((i+0.5)/len(alphabet)) * 360,0,0]) {
|
||||
for (i = [1:len(alphabet)]) {
|
||||
rotate([45+((i+0.5)/len(alphabet)) * 360,0,0]) {
|
||||
translate([width/2,0,dia/2-thick]) {
|
||||
cube([2*antipick, key_width, key_height*2], center=true);
|
||||
}
|
||||
|
@ -29,9 +30,12 @@ module key_dial(alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ", key="A", width=7, dia=50,
|
|||
// text
|
||||
for (i = [0:len(alphabet)]) {
|
||||
rotate([((i+0.5)/len(alphabet)) * 360,0,0]) {
|
||||
translate([0,0,dia/2-text_depth]) {
|
||||
translate([-width/2-1,-dia/2,dia/2-0.3]) {
|
||||
cube([width+2,dia,5]);
|
||||
}
|
||||
translate([0,0,dia/2-text_depth-0.3]) {
|
||||
linear_extrude(height=text_depth+1, convexity=3) {
|
||||
text(alphabet[(i+key_index)%len(alphabet)], size=text_width, halign="center", valign="center");
|
||||
text(alphabet[i], size=_font_size, halign="center", valign="center");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,10 +48,10 @@ module key_dial(alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ", key="A", width=7, dia=50,
|
|||
// DEFAULTS FOR COMMAND LINE INVOCATION
|
||||
|
||||
ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
KEY = "A";
|
||||
KEY = "C";
|
||||
DIAMETER = 50;
|
||||
TOLERANCE = 0.2;
|
||||
FN = 60;
|
||||
FN = 120;
|
||||
INDEX = 0;
|
||||
|
||||
rotate([0,-90,0]) {
|
||||
|
|
|
@ -1,38 +1,38 @@
|
|||
|
||||
use <key_dial.scad>;
|
||||
use <e_ring.scad>;
|
||||
use <ring.scad>;
|
||||
use <case.scad>;
|
||||
use <plug.scad>;
|
||||
|
||||
|
||||
module lock(alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ", password="CHAOS", text_width=4, diameter=50, tolerance=0.2, $fn= 60) {
|
||||
module lock(alphabet="ABCDEFGHIJKLMNOPQRSTUVWXYZ", password="CHAOS", diameter=50, tolerance=0.2, $fn=60) {
|
||||
|
||||
n = len(password);
|
||||
key_indices=search(password, alphabet);
|
||||
|
||||
for (i = [0:n-1]) {
|
||||
translate([i*10,0,0]) key_dial(alphabet, key=password[i], dia=diameter, text_width=text_width);
|
||||
rotate([-key_indices[i]/len(alphabet)*360,0,0]) {
|
||||
translate([i*10,0,0]) {
|
||||
key_dial(alphabet, key=password[i], dia=diameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i = [0:n]) {
|
||||
translate([-5+10*i,0,0]) {
|
||||
rotate([0,0,180]) {
|
||||
rotate([-45,0,0]) {
|
||||
rotate([0,90,0]) {
|
||||
e_ring(dia=diameter-2, indicator=true, clicker=i>0);
|
||||
}
|
||||
}
|
||||
rotate([135,0,0]) {
|
||||
rotate([0,90,0]) {
|
||||
e_ring(dia=diameter-2);
|
||||
}
|
||||
rotate([0,90,0]) {
|
||||
ring(dia=diameter-2, clicker=i>0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case(n, diameter=diameter);
|
||||
|
||||
plug(n, diameter=diameter);
|
||||
|
||||
rotate([45,0,0]) {
|
||||
case(n, diameter=diameter);
|
||||
plug(n, diameter=diameter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
#!/bin/bash
|
||||
|
||||
ALPHABET=ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
N=5
|
||||
LENGTH=5
|
||||
DIAMETER=50
|
||||
TOLERANCE=0.2
|
||||
FN=60
|
||||
|
||||
ARGS=("-q" "-DALPHABET=\"$ALPHABET\"" "-DN=$N" "-DDIAMETER=$DIAMETER" "-DTOLERANCE=$TOLERANCE" "-DFN=$FN")
|
||||
ARGS=("--backend=manifold" "-q" "-DALPHABET=\"$ALPHABET\"" "-DN=$LENGTH" "-DDIAMETER=$DIAMETER" "-DTOLERANCE=$TOLERANCE" "-DFN=$FN")
|
||||
|
||||
mkdir -p out
|
||||
|
||||
openscad "${ARGS[@]}" -o out/plug.stl plug.scad
|
||||
openscad "${ARGS[@]}" -o out/case.stl case.scad
|
||||
openscad "${ARGS[@]}" -o out/e_ring.stl e_ring.scad
|
||||
openscad "${ARGS[@]}" -DINDICATOR=true -o out/e_ring_indicator.stl e_ring.scad
|
||||
openscad "${ARGS[@]}" -DINDICATOR=true -DCLICK=true -o out/e_ring_indicator_click.stl e_ring.scad
|
||||
openscad "${ARGS[@]}" -o out/ring.stl ring.scad
|
||||
openscad "${ARGS[@]}" -DCLICKER=true -o out/ring_click.stl ring.scad
|
||||
|
||||
while read -rn1 char; do
|
||||
openscad "${ARGS[@]}" "-DKEY=\"$char\"" -o "out/key_dial_${char}.stl" key_dial.scad
|
||||
|
|
85
ring.scad
Normal file
85
ring.scad
Normal file
|
@ -0,0 +1,85 @@
|
|||
|
||||
|
||||
module ring(alphabet_length=26, dia=48, width=3, thick=2, spoke_depth=4.5, spoke_width=7, key_depth=3, key_length=4, tolerance = 0.2, indicator=false, clicker=false, $fn=60) {
|
||||
|
||||
difference() { // halfing
|
||||
union() {
|
||||
// outer ring
|
||||
difference() {
|
||||
cylinder(d=dia, h=width-4*tolerance, center=true);
|
||||
cylinder(d=dia-2*thick, h=width+1, center=true);
|
||||
}
|
||||
// key spokes
|
||||
difference() {
|
||||
union() {
|
||||
for (i = [0:2]) {
|
||||
rotate([0,0,120*i]) {
|
||||
intersection() {
|
||||
translate([-dia/4,0,0]) {
|
||||
cube([dia/2, spoke_width, width], center=true);
|
||||
}
|
||||
cylinder(d=dia, h=width-4*tolerance, center=true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
cylinder(d=dia-2*spoke_depth+2*tolerance, h=width+1, center=true);
|
||||
}
|
||||
// keys
|
||||
for (i = [0:2]) {
|
||||
rotate([0,0,120*i]) {
|
||||
translate([-dia/2+spoke_depth+key_depth/2-1,0,0]) {
|
||||
cube([key_depth+2, key_length, width-4*tolerance], center=true);
|
||||
}
|
||||
}
|
||||
}
|
||||
// indicator
|
||||
translate([-dia/2,0,0]) {
|
||||
cube([1.5, width, width-4*tolerance], center=true);
|
||||
}
|
||||
// clicker
|
||||
if (clicker) {
|
||||
rotate([0,0,floor(alphabet_length/3)*360/alphabet_length-45]) {
|
||||
translate([-dia/2+3,0,0]) {
|
||||
cube([3, 4, width-4*tolerance], center=true);
|
||||
}
|
||||
translate([-dia/2+3.5,0,width/2-0.5]) {
|
||||
scale([0.5,1,1]) {
|
||||
rotate([0,0,45]) {
|
||||
cylinder($fn=4, h=1, d1=5.6, d2=3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ring cut
|
||||
translate([-dia/2+thick-0.5, -0.25, -width/2]) {
|
||||
cube([spoke_depth+key_depth,0.5, width]);
|
||||
}
|
||||
translate([-dia/2, width/2, -width/2]) {
|
||||
cube([thick,0.5, width]);
|
||||
}
|
||||
translate([-dia/2+thick-0.5, 0, -width/2]) {
|
||||
cube([0.5, width/2, width]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// DEFAULTS FOR COMMAND LINE INVOCATION
|
||||
|
||||
ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
DIAMETER = 50;
|
||||
TOLERANCE = 0.2;
|
||||
FN = 60;
|
||||
INDICATOR = false;
|
||||
CLICKER = false;
|
||||
|
||||
|
||||
ring(
|
||||
alphabet_length=len(ALPHABET),
|
||||
dia=DIAMETER-2,
|
||||
indicator=INDICATOR,
|
||||
clicker=CLICKER,
|
||||
tolerance=TOLERANCE, $fn=FN);
|
Loading…
Add table
Reference in a new issue