Compare commits

...

3 commits

16 changed files with 179 additions and 114 deletions

53
README.md Normal file
View file

@ -0,0 +1,53 @@
# Lock Puzzle
A 3D-printed puzzle box written in OpenSCAD.
![The puzzle box in its closed state](images/lockpuzzle_rendered_locked.png)
![The puzzle box in its opened state](images/lockpuzzle_rendered_unlocked.png)
## 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:
![Rendering of the main case of the lock](images/lockpuzzle_assembly_01.png)
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:
![The first ring placed on the case](images/lockpuzzle_assembly_02.png)
3. Add the first dial case as shown here.
![The first dial placed on the case](images/lockpuzzle_assembly_03.png)
4. Repeat steps 2 and 3 until you reach the last dial.
![The remaining rings and dials placed on the case](images/lockpuzzle_assembly_04.png)
5. Add the final ring to the case as shown here. Note the absence of the clicker.
![The final ring placed on the case](images/lockpuzzle_assembly_05.png)
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.
![All dials lined up with the slot in the case](images/lockpuzzle_assembly_06.png)
7. Finally, insert the plug all the way, then turn the dials to lock the puzzle box:
![The plug inserted into the case, closing the puzzle box](images/lockpuzzle_assembly_07.png)

View file

@ -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;

View file

@ -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);

Binary file not shown.

After

(image error) Size: 34 KiB

Binary file not shown.

After

(image error) Size: 43 KiB

Binary file not shown.

After

(image error) Size: 36 KiB

Binary file not shown.

After

(image error) Size: 64 KiB

Binary file not shown.

After

(image error) Size: 64 KiB

Binary file not shown.

After

(image error) Size: 39 KiB

Binary file not shown.

After

(image error) Size: 45 KiB

Binary file not shown.

After

(image error) Size: 61 KiB

Binary file not shown.

After

(image error) Size: 60 KiB

View file

@ -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]) {

View file

@ -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);
}
}

View file

@ -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
View 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);