├── doc ├── knots.jpg ├── piece-alphabet.jpg └── tile-alphabet.jpg ├── LICENSE ├── bracelet.scad ├── pendant.scad ├── knot-tile-alphabet.scad ├── README.md ├── knot-piece-alphabet.scad └── celtic-knots.scad /doc/knots.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beanz/celtic-knot-scad/HEAD/doc/knots.jpg -------------------------------------------------------------------------------- /doc/piece-alphabet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beanz/celtic-knot-scad/HEAD/doc/piece-alphabet.jpg -------------------------------------------------------------------------------- /doc/tile-alphabet.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/beanz/celtic-knot-scad/HEAD/doc/tile-alphabet.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This work is licensed under a Creative Commons Attribution-ShareAlike 2 | 4.0 International License. 3 | -------------------------------------------------------------------------------- /bracelet.scad: -------------------------------------------------------------------------------- 1 | /* 2 | * openscad celtic knot library 3 | * Copyright 2014 Mark Hindess 4 | * 5 | * This work is licensed under a Creative Commons 6 | * Attribution-ShareAlike 4.0 International License. 7 | * 8 | * Sample of a simple bracelet 9 | */ 10 | 11 | use 12 | 13 | // radius=30 for a child 14 | radius=30; 15 | segments = 10; 16 | inset = 0.2; 17 | 18 | 19 | angle = 360/segments; 20 | w = radius*sin(angle/2); 21 | l = radius*cos(angle/2); 22 | tile_width = w*2/6; 23 | height = tile_width*3.5; 24 | gap = tile_width*.1; 25 | 26 | cylinder(r = radius, h = height, center = true, $fn = segments); 27 | 28 | for (i = [ 0 : segments-1 ]) { 29 | difference() { 30 | rotate([-90, 0, i*angle]) translate([0, 0, l]) 31 | fine_knot([ "-rqrq--r", 32 | "wtdsYewt", 33 | "fa__fafa" ], 34 | tile_width = tile_width, gap = gap, tile_height = inset); 35 | rotate([0, 180, (+0.5+i)*angle]) translate([0,0,-radius]) cube(radius*2); 36 | rotate([0, 0, (-0.5+i)*angle]) translate([0,0,-radius]) cube(radius*2); 37 | } 38 | } 39 | 40 | echo("length = ", l*segments); 41 | echo("height = ", height); 42 | echo("gap = ", gap); 43 | -------------------------------------------------------------------------------- /pendant.scad: -------------------------------------------------------------------------------- 1 | /* 2 | * openscad celtic knot library 3 | * Copyright 2014 Mark Hindess 4 | * 5 | * This work is licensed under a Creative Commons 6 | * Attribution-ShareAlike 4.0 International License. 7 | * 8 | * Sample of a simple pendant 9 | */ 10 | 11 | use 12 | 13 | tile_width = 5; 14 | tile_height = 2.5; 15 | pointy = true; 16 | 17 | // define the shape of the knot 18 | k = [ (pointy ? " <~> " : " ,~. "), 19 | " !&; ", 20 | (pointy ? "<~w&e~>" : ",~w&e~."), 21 | "|(&&&):", 22 | (pointy ? "[-a&s-]" : "{-a&s-}"), 23 | " !&; ", 24 | " !&; ", // or try " |X: " 25 | " !&; ", 26 | (pointy ? " [-] " : " {-} ")]; 27 | 28 | // half height with no gap so it isn't in pieces 29 | knot(k, tile_width = tile_width, tile_height = tile_height/2, gap = 0); 30 | 31 | // full height with a gap 32 | knot(k, tile_width = tile_width, tile_height = tile_height, gap = 1); 33 | 34 | // loop for threading 35 | translate([0, tile_width*8, 0]) 36 | difference() { 37 | cylinder(r = tile_width, h = tile_height/2); 38 | cylinder(r = tile_width/2, h = tile_height*3, center = true); 39 | } -------------------------------------------------------------------------------- /knot-tile-alphabet.scad: -------------------------------------------------------------------------------- 1 | /* 2 | * openscad celtic knot library 3 | * Copyright 2014 Mark Hindess 4 | * 5 | * This work is licensed under a Creative Commons 6 | * Attribution-ShareAlike 4.0 International License. 7 | * 8 | * Sample of the "alphabet" for knot tiles 9 | * 10 | * 11 | * AtYF 12 | * EGhW 13 | * [sd] 14 | * 15 | */ 16 | 17 | use 18 | use 19 | 20 | sw = 5; 21 | 22 | translate([-sw*8, +sw*8, 0]) { 23 | block_group("Corners", ",.{}", "<>[]"); 24 | } 25 | 26 | translate([+sw*3, +sw*8, 0]) { 27 | block_group("Curves", "qwas", "QWAS"); 28 | } 29 | 30 | translate([-sw*8, 0, 0]) { 31 | block_group("Cut Curves", "erdf", "ERDF"); 32 | } 33 | 34 | translate([+sw*3, 0, 0]) { 35 | block_group("Cut Slopes", "tygh", "TYGH"); 36 | } 37 | 38 | translate([0, -sw*8, 0]) { 39 | translate([0, +sw*3.8, 0]) write("Edges", center = true); 40 | char_block("-_|!"); 41 | } 42 | 43 | module block_group(title, a, b) { 44 | translate([+sw*2.5, +sw*3.8, 0]) write(title, center = true); 45 | char_block(a); 46 | translate([+sw*5, 0, 0]) char_block(b); 47 | } 48 | 49 | module char_block(c) { 50 | translate([-sw, +sw*1.5, 0]) { 51 | sample_tile(c[0]); 52 | translate([0, sw*1.3, 0]) write(c[0], center = true); 53 | } 54 | translate([+sw, +sw*1.5, 0]) { 55 | sample_tile(c[1]); 56 | translate([0, sw*1.3, 0]) write(c[1], center = true); 57 | } 58 | translate([-sw, -sw*1.5, 0]) { 59 | write(c[2], center = true); 60 | translate([0, sw*1.3, 0]) sample_tile(c[2]); 61 | } 62 | translate([+sw, -sw*1.5, 0]) { 63 | write(c[3], center = true); 64 | translate([0, sw*1.3, 0]) sample_tile(c[3]); 65 | } 66 | 67 | } 68 | 69 | module sample_tile(c) { 70 | knot_tile(c, tile_width = sw); 71 | color("lightgrey") knot_tile_boundary(sw); 72 | } 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Celtic Knot Library for Openscad 2 | ================================ 3 | 4 | API is still in development and is likely to change 5 | --------------------------------------------------- 6 | 7 | Feedback very welcome 8 | --------------------- 9 | 10 | My son wanted a celtic knot bracelet. This library was used to create 11 | my first attempt. The knots look like: 12 | 13 | ![example knots](https://github.com/beanz/celtic-knot-scad/raw/master/doc/knots.jpg "Example Knots") 14 | 15 | The library provides two interfaces. The first API uses large knot 16 | parts (called pieces in the code) and looks like: 17 | 18 | knot([ "<~>", 19 | "!&;", 20 | "[-]" ], tile_width = 5); 21 | 22 | where the first parameter is an kind of ASCII art for the desired 23 | pattern and ```tile_width``` is the knot tile size. The "alphabet" for 24 | pieces is generated by ```knot-piece-alphabet.scad``` and looks like: 25 | 26 | ![Knot Piece Alphabet](https://github.com/beanz/celtic-knot-scad/raw/master/doc/piece-alphabet.jpg "Knot Piece Alphabet") 27 | 28 | Each pieces is a 2x2 square of smaller knot parts (called tiles in the 29 | code). However, edge and corner pieces have some empty tiles. 30 | 31 | The second API uses knot tiles and looks like: 32 | 33 | fine_knot([ "", 34 | "AtYF", 35 | "EGhW", 36 | "[sd]" ], tile_width = 5); 37 | 38 | where the first parameter is an ASCII art for the desired pattern and 39 | ```tile_width``` is the knot tile size. 40 | 41 | The "alphabet" for tiles is generated by ```knot-tile-alphabet.scad``` and 42 | looks like: 43 | 44 | ![Knot Tile Alphabet](https://github.com/beanz/celtic-knot-scad/raw/master/doc/tile-alphabet.jpg "Knot Tile Alphabet") 45 | 46 | 47 | License 48 | ------- 49 | 50 | This work is licensed under a Creative Commons Attribution-ShareAlike 51 | 4.0 International License. 52 | -------------------------------------------------------------------------------- /knot-piece-alphabet.scad: -------------------------------------------------------------------------------- 1 | /* 2 | * openscad celtic knot library 3 | * Copyright 2014 Mark Hindess 4 | * 5 | * This work is licensed under a Creative Commons 6 | * Attribution-ShareAlike 4.0 International License. 7 | * 8 | * Sample of the "alphabet" for knot pieces (two by two tiles) 9 | * 10 | * <~> 11 | * !&; 12 | * [-] 13 | * 14 | */ 15 | 16 | use 17 | use 18 | 19 | sw = 5; 20 | sh = 2.5; 21 | 22 | translate([-sw*9, +sw*13, 0]) char_block("<>[]"); 23 | translate([-sw*3, +sw*13, 0]) char_block(",.{}"); 24 | translate([+sw*3, +sw*13, 0]) char_block("~=-_"); 25 | translate([+sw*9, +sw*13, 0]) char_block("!;|:"); 26 | 27 | translate([-sw*9, +sw*5, 0]) char_block("nu()"); 28 | translate([-sw*3, +sw*5, 0]) char_block("XxO#"); 29 | translate([+sw*3, +sw*5, 0]) char_block("qpdb"); 30 | translate([+sw*9, +sw*5, 0]) char_block("QPDB"); 31 | 32 | translate([-sw*9, -sw*3, 0]) char_block("weas"); 33 | translate([-sw*3, -sw*3, 0]) char_block("ikjl"); 34 | translate([+sw*3, -sw*3, 0]) char_block("IKJL"); 35 | translate([+sw*9, -sw*3, 0]) char_block("&&&&"); 36 | 37 | translate([-sw*3, -sw*11, 0]) char_block("tgfh"); 38 | translate([+sw*3, -sw*11, 0]) char_block("TGFH"); 39 | 40 | module char_block(c) { 41 | translate([-sw*1.2, +sw*2, 0]) sample_piece(c[0]); 42 | translate([+sw*1.2, +sw*2, 0]) sample_piece(c[1]); 43 | translate([-sw*1.2, -sw*2, 0]) sample_piece_alt(c[2]); 44 | translate([+sw*1.2, -sw*2, 0]) sample_piece_alt(c[3]); 45 | } 46 | 47 | module sample_piece(c) { 48 | write(c, center = true); 49 | translate([-sw, -sw*.5, 0]) { 50 | knot_piece(c, tile_width = sw); 51 | color("lightgrey") knot_piece_boundary(sw); 52 | } 53 | } 54 | 55 | module sample_piece_alt(c) { 56 | translate([0, -sw*1.5, 0]) write(c, center = true); 57 | translate([-sw, sw, 0]) { 58 | knot_piece(c, tile_width = sw); 59 | color("lightgrey") knot_piece_boundary(sw); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /celtic-knots.scad: -------------------------------------------------------------------------------- 1 | /* 2 | * openscad celtic knot library 3 | * Copyright 2014 Mark Hindess 4 | * 5 | * This work is licensed under a Creative Commons 6 | * Attribution-ShareAlike 4.0 International License. 7 | * 8 | * Based loosely on http://nrich.maths.org/5365 9 | */ 10 | 11 | knot_test = 1; 12 | sp = 22.5; 13 | if (knot_test == 1) { 14 | 15 | translate([ -sp*2, +sp*3, 0]) knot([ "<=>", "|Q;", "[-]" ]); 16 | translate([ -sp*1, +sp*3, 0]) knot([ "<=>", "|q;", "[-]" ]); 17 | translate([ +sp*0, +sp*3, 0]) knot([ "<=>", "!n;", "[-]" ]); 18 | translate([ +sp*1, +sp*3, 0]) knot([ "<=>", "!p:", "[-]" ]); 19 | translate([ +sp*2, +sp*3, 0]) knot([ "<=>", "!P:", "[-]" ]); 20 | 21 | translate([ -sp*2, +sp*2, 0]) knot([ "<=>", "|f;", "[_]" ]); 22 | translate([ -sp*1, +sp*2, 0]) knot([ "<=>", "|t:", "[-]" ]); 23 | translate([ +sp*0, +sp*2, 0]) knot([ ",~.", "!&;", "{-}" ]); 24 | translate([ +sp*1, +sp*2, 0]) knot([ "<~>", "|g:", "[_]" ]); 25 | translate([ +sp*2, +sp*2, 0]) knot([ "<=>", "!h:", "[_]" ]); 26 | 27 | translate([ -sp*2, +sp*1, 0]) knot([ "<=>", "|j;", "[_]" ]); 28 | translate([ -sp*1, +sp*1, 0]) knot([ "<=>", "|i:", "[-]" ]); 29 | translate([ +sp*0, +sp*1, 0]) knot([ ",~.", "!&;", "{-}" ]); 30 | translate([ +sp*1, +sp*1, 0]) knot([ "<~>", "|k:", "[_]" ]); 31 | translate([ +sp*2, +sp*1, 0]) knot([ "<=>", "!l:", "[_]" ]); 32 | 33 | translate([ -sp*2, +sp*0, 0]) knot([ "<~>", "|(;", "[-]" ]); 34 | translate([ -sp*1, +sp*0, 0]) knot([ "<=>", "|O:", "[_]" ]); 35 | translate([ +sp*0, +sp*0, 0]) knot([ "<~>", "!&;", "[-]" ]); 36 | translate([ +sp*1, +sp*0, 0]) knot([ ",=.", "|#:", "{_}" ]); 37 | translate([ +sp*2, +sp*0, 0]) knot([ "<~>", "!):", "[-]" ]); 38 | 39 | translate([ -sp*2, -sp*1, 0]) knot([ "<=>", "|J;", "[_]" ]); 40 | translate([ -sp*1, -sp*1, 0]) knot([ "<=>", "|I:", "[-]" ]); 41 | translate([ +sp*0, -sp*1, 0]) knot([ ",~.", "|X:", "{-}" ]); 42 | translate([ +sp*1, -sp*1, 0]) knot([ "<~>", "|K:", "[_]" ]); 43 | translate([ +sp*2, -sp*1, 0]) knot([ "<=>", "!L:", "[_]" ]); 44 | 45 | translate([ -sp*2, -sp*2, 0]) knot([ "<=>", "|F;", "[_]" ]); 46 | translate([ -sp*1, -sp*2, 0]) knot([ "<=>", "|T:", "[-]" ]); 47 | translate([ +sp*0, -sp*2, 0]) knot([ ",=.", "!x;", "{_}" ]); 48 | translate([ +sp*1, -sp*2, 0]) knot([ "<~>", "|G:", "[_]" ]); 49 | translate([ +sp*2, -sp*2, 0]) knot([ "<=>", "!H:", "[_]" ]); 50 | 51 | translate([ -sp*2, -sp*3, 0]) knot([ "<~>", "|D;", "[_]" ]); 52 | translate([ -sp*1, -sp*3, 0]) knot([ "<~>", "|d;", "[_]" ]); 53 | translate([ +sp*0, -sp*3, 0]) knot([ "<~>", "!u;", "[_]" ]); 54 | translate([ +sp*1, -sp*3, 0]) knot([ "<~>", "!b:", "[_]" ]); 55 | translate([ +sp*2, -sp*3, 0]) knot([ "<~>", "!B:", "[_]" ]); 56 | 57 | } else if (knot_test == 2) { 58 | 59 | knot([ "<~=~>", 60 | "!&n&;", 61 | "!&u&;", 62 | "[-_-]" ]); 63 | 64 | } else if (knot_test == 3) { 65 | 66 | knot([ " <~> ", 67 | "<~w&e~>", 68 | "[-a&s-]", 69 | " [-] " ]); 70 | 71 | } else if (knot_test == 4) { 72 | 73 | knot([ " <~> ", 74 | "", 75 | "!)+(;", 76 | "[ans]", 77 | " [-] " ], tile_height = 5, tile_width = 8); 78 | 79 | } else if (knot_test == 5) { 80 | fine_knot([ ",rqrqrq>", 81 | "AtYtYtR!", 82 | "EGhGhGS!", 83 | "AtYtYtYF", 84 | "EGhGhGhW", 85 | "AtYtYtYF", 86 | "EafGhGhW", 87 | "[__sdsd}" ]); 88 | 89 | translate([0, 5*-8.2, 0] ) 90 | knot([ ",~~~>", 91 | "!&&):", 92 | "!&&&;", 93 | "!u&&;", 94 | "[_--}" ]); 95 | 96 | } else if (knot_test == 6) { 97 | 98 | difference() { 99 | cube([5*8, 5*4.3, 2], center = true); 100 | translate([0,0,.5]) 101 | knot([ "~~~=", 102 | "&u&n", 103 | "-_--" ], tile_width = 5); 104 | } 105 | 106 | } else if (knot_test == 7) { 107 | 108 | knot([ "~~~", 109 | "&u&", 110 | "-_-" ], tile_width = 5, gap = 1.1); 111 | 112 | } else if (knot_test == 8) { 113 | 114 | fine_knot([ "rqrq--", 115 | "tdsYew", 116 | "a__faf" ], tile_width = 5, tile_height = 0.2, gap = 1.1); 117 | 118 | } 119 | 120 | module knot(knot, tile_width = 5, tile_height, ribbon_width, gap) { 121 | rth = tile_height != undef ? tile_height : tile_width/2; 122 | rrw = ribbon_width != undef ? ribbon_width : tile_width/sqrt(2); 123 | rgap = gap != undef ? gap : tile_width/(6*sqrt(2)); 124 | 125 | x = len(knot[0]); 126 | y = len(knot); 127 | for (i=[0:x-1]) { 128 | for (j=[0:y-1]) { 129 | translate([(i-x/2)*tile_width*2, (y/2-j)*tile_width*2, 0]) 130 | knot_piece(knot[j][i], tile_width, rth, rrw, rgap); 131 | } 132 | } 133 | } 134 | 135 | module fine_knot(knot, tile_width = 5, tile_height, ribbon_width, gap) { 136 | rth = tile_height != undef ? tile_height : tile_width/2; 137 | rrw = ribbon_width != undef ? ribbon_width : tile_width/sqrt(2); 138 | rgap = gap != undef ? gap : tile_width/(6*sqrt(2)); 139 | 140 | x = len(knot[0]); 141 | y = len(knot); 142 | for (i=[0:x-1]) { 143 | for (j=[0:y-1]) { 144 | translate([(i-(x-1)/2)*tile_width, ((y-1)/2-j)*tile_width, 0]) 145 | knot_tile(knot[j][i], tile_width, rth, rrw, rgap); 146 | } 147 | } 148 | } 149 | 150 | module knot_piece(c, tile_width = 5, tile_height, ribbon_width, gap) { 151 | rth = tile_height != undef ? tile_height : tile_width/2; 152 | rrw = ribbon_width != undef ? ribbon_width : tile_width/sqrt(2); 153 | rgap = gap != undef ? gap : tile_width/(6*sqrt(2)); 154 | 155 | if (c == " ") { 156 | } else if (c == "<") { // top square corners 157 | make_knot_piece(" <", tile_width, rth, rrw, rgap); 158 | } else if (c == ">") { 159 | make_knot_piece(" > ", tile_width, rth, rrw, rgap); 160 | } else if (c == ",") { // top rounded corners 161 | make_knot_piece(" ,", tile_width, rth, rrw, rgap); 162 | } else if (c == ".") { 163 | make_knot_piece(" . ", tile_width, rth, rrw, rgap); 164 | 165 | } else if (c == "~") { // normal top piece 166 | make_knot_piece(" rq", tile_width, rth, rrw, rgap); 167 | } else if (c == "=") { // straight top piece 168 | make_knot_piece(" --", tile_width, rth, rrw, rgap); 169 | 170 | } else if (c == "!") { // normal left piece 171 | make_knot_piece(" A E", tile_width, rth, rrw, rgap); 172 | } else if (c == "|") { // straight left piece 173 | make_knot_piece(" | |", tile_width, rth, rrw, rgap); 174 | 175 | } else if (c == "&") { // normal internal piece 176 | make_knot_piece("tYGh", tile_width, rth, rrw, rgap); 177 | 178 | } else if (c == ";") { // normal right piece 179 | make_knot_piece("F W ", tile_width, rth, rrw, rgap); 180 | } else if (c == ":") { // straight right piece 181 | make_knot_piece("! ! ", tile_width, rth, rrw, rgap); 182 | 183 | } else if (c == "-") { // normal bottom piece 184 | make_knot_piece("sd ", tile_width, rth, rrw, rgap); 185 | } else if (c == "_") { // straight bottom piece 186 | make_knot_piece("__ ", tile_width, rth, rrw, rgap); 187 | 188 | } else if (c == "[") { // bottom square corners 189 | make_knot_piece(" [ ", tile_width, rth, rrw, rgap); 190 | } else if (c == "]") { 191 | make_knot_piece("] ", tile_width, rth, rrw, rgap); 192 | 193 | } else if (c == "{") { // bottom rounded corners 194 | make_knot_piece(" { ", tile_width, rth, rrw, rgap); 195 | } else if (c == "}") { 196 | make_knot_piece("} ", tile_width, rth, rrw, rgap); 197 | 198 | } else if (c == "n") { // top rounded internal piece 199 | make_knot_piece("ewGh", tile_width, rth, rrw, rgap); 200 | } else if (c == "u") { // bottom rounded internal piece 201 | make_knot_piece("tYaf", tile_width, rth, rrw, rgap); 202 | } else if (c == "(") { // left rounded internal piece 203 | make_knot_piece("QYDh", tile_width, rth, rrw, rgap); 204 | } else if (c == ")") { // right rounded internal piece 205 | make_knot_piece("tRGS", tile_width, rth, rrw, rgap); 206 | 207 | } else if (c == "X") { // crossing internal pieces 208 | make_knot_piece("QRDS", tile_width, rth, rrw, rgap); 209 | } else if (c == "x") { 210 | make_knot_piece("ewaf", tile_width, rth, rrw, rgap); 211 | 212 | } else if (c == "O") { // round internal loop piece 213 | make_knot_piece(",.{}", tile_width, rth, rrw, rgap); 214 | } else if (c == "#") { // square internal loop piece 215 | make_knot_piece("<>[]", tile_width, rth, rrw, rgap); 216 | 217 | 218 | } else if (c == "Q") { // two crossing internal pieces 219 | // (square if upper case) 220 | make_knot_piece("GS", tile_width, rth, rrw, rgap); 227 | } else if (c == "D") { 228 | make_knot_piece("QY[f", tile_width, rth, rrw, rgap); 229 | } else if (c == "d") { 230 | make_knot_piece("QY{f", tile_width, rth, rrw, rgap); 231 | } else if (c == "B") { 232 | make_knot_piece("tRa]", tile_width, rth, rrw, rgap); 233 | } else if (c == "b") { 234 | make_knot_piece("tRa}", tile_width, rth, rrw, rgap); 235 | 236 | 237 | } else if (c == "i") { // loops open at one end 238 | // (square if upper case) 239 | make_knot_piece(",.DS", tile_width, rth, rrw, rgap); 240 | } else if (c == "I") { 241 | make_knot_piece("<>DS", tile_width, rth, rrw, rgap); 242 | } else if (c == "j") { 243 | make_knot_piece(",w{f", tile_width, rth, rrw, rgap); 244 | } else if (c == "J") { 245 | make_knot_piece("a]", tile_width, rth, rrw, rgap); 254 | 255 | 256 | } else if (c == "t") { // loops open at one end 257 | // (mix of round and square) 258 | make_knot_piece(",>DS", tile_width, rth, rrw, rgap); 259 | } else if (c == "T") { 260 | make_knot_piece("<.DS", tile_width, rth, rrw, rgap); 261 | } else if (c == "f") { 262 | make_knot_piece(",w[f", tile_width, rth, rrw, rgap); 263 | } else if (c == "F") { 264 | make_knot_piece("a}", tile_width, rth, rrw, rgap); 273 | 274 | } else if (c == "w") { // concave corners 275 | make_knot_piece(" Arh", tile_width, rth, rrw, rgap); 276 | } else if (c == "a") { 277 | make_knot_piece("sY E", tile_width, rth, rrw, rgap); 278 | } else if (c == "e") { 279 | make_knot_piece("F Gq", tile_width, rth, rrw, rgap); 280 | } else if (c == "s") { 281 | make_knot_piece("tdW ", tile_width, rth, rrw, rgap); 282 | 283 | } else { 284 | translate([tile_width,-tile_width,0]) 285 | invalid_tile(tile_width, rth, rrw, rgap); 286 | } 287 | } 288 | 289 | module knot_tile(c, tile_width = 5, tile_height, ribbon_width, gap) { 290 | rth = tile_height != undef ? tile_height : tile_width/2; 291 | rrw = ribbon_width != undef ? ribbon_width : tile_width/sqrt(2); 292 | rgap = gap != undef ? gap : tile_width/(6*sqrt(2)); 293 | 294 | if (c == " ") { // Blank 295 | 296 | } else if (c == ",") { // Corners 297 | rotate([0,0,90]) round_corner(tile_width, rth, rrw, rgap); 298 | } else if (c == "<") { 299 | rotate([0,0,90]) square_corner(tile_width, rth, rrw, rgap); 300 | } else if (c == ".") { 301 | round_corner(tile_width, rth, rrw, rgap); 302 | } else if (c == ">") { 303 | square_corner(tile_width, rth, rrw, rgap); 304 | } else if (c == "{") { 305 | rotate([0,0,180]) round_corner(tile_width, rth, rrw, rgap); 306 | } else if (c == "[") { 307 | rotate([0,0,180]) square_corner(tile_width, rth, rrw, rgap); 308 | } else if (c == "}") { 309 | rotate([0,0,-90]) round_corner(tile_width, rth, rrw, rgap); 310 | } else if (c == "]") { 311 | rotate([0,0,-90]) square_corner(tile_width, rth, rrw, rgap); 312 | 313 | } else if (c == "q") { // Curves 314 | curve(tile_width, rth, rrw, rgap); 315 | } else if (c == "Q") { 316 | scale([1,-1,1]) rotate([0,0,90]) curve(tile_width, rth, rrw, rgap); 317 | } else if (c == "w") { 318 | scale([-1,1,1]) curve(tile_width, rth, rrw, rgap); 319 | } else if (c == "W") { 320 | rotate([0,0,-90]) curve(tile_width, rth, rrw, rgap); 321 | } else if (c == "s") { 322 | rotate([0,0,180]) curve(tile_width, rth, rrw, rgap); 323 | } else if (c == "S") { 324 | scale([-1,1,1]) rotate([0,0,90]) curve(tile_width, rth, rrw, rgap); 325 | } else if (c == "a") { 326 | scale([1,-1,1]) curve(tile_width, rth, rrw, rgap); 327 | } else if (c == "A") { 328 | rotate([0,0,90]) curve(tile_width, rth, rrw, rgap); 329 | 330 | } else if (c == "e") { // Cross Curves 331 | cross_curve(tile_width, rth, rrw, rgap); 332 | } else if (c == "E") { 333 | scale([1,-1,1]) rotate([0,0,90]) cross_curve(tile_width, rth, rrw, rgap); 334 | } else if (c == "r") { 335 | scale([-1,1,1]) cross_curve(tile_width, rth, rrw, rgap); 336 | } else if (c == "R") { 337 | rotate([0,0,-90]) cross_curve(tile_width, rth, rrw, rgap); 338 | } else if (c == "f") { 339 | rotate([0,0,180]) cross_curve(tile_width, rth, rrw, rgap); 340 | } else if (c == "F") { 341 | scale([-1,1,1]) rotate([0,0,90]) cross_curve(tile_width, rth, rrw, rgap); 342 | } else if (c == "d") { 343 | scale([1,-1,1]) cross_curve(tile_width, rth, rrw, rgap); 344 | } else if (c == "D") { 345 | rotate([0,0,90]) cross_curve(tile_width, rth, rrw, rgap); 346 | 347 | } else if (c == "t") { // Cross 348 | cross(tile_width, rth, rrw, rgap); 349 | } else if (c == "T") { 350 | scale([1,-1,1]) rotate([0,0,90]) cross(tile_width, rth, rrw, rgap); 351 | } else if (c == "y") { 352 | scale([-1,1,1]) cross(tile_width, rth, rrw, rgap); 353 | } else if (c == "Y") { 354 | rotate([0,0,-90]) cross(tile_width, rth, rrw, rgap); 355 | } else if (c == "h") { 356 | rotate([0,0,180]) cross(tile_width, rth, rrw, rgap); 357 | } else if (c == "H") { 358 | scale([-1,1,1]) rotate([0,0,90]) cross(tile_width, rth, rrw, rgap); 359 | } else if (c == "g") { 360 | scale([1,-1,1]) cross(tile_width, rth, rrw, rgap); 361 | } else if (c == "G") { 362 | rotate([0,0,90]) cross(tile_width, rth, rrw, rgap); 363 | 364 | } else if (c == "-") { // Straight edge 365 | rotate([0,0,90]) straight(tile_width, rth, rrw, rgap); 366 | } else if (c == "_") { 367 | rotate([0,0,-90]) straight(tile_width, rth, rrw, rgap); 368 | } else if (c == "|") { 369 | rotate([0,0,180]) straight(tile_width, rth, rrw, rgap); 370 | } else if (c == "!") { 371 | straight(tile_width, rth, rrw, rgap); 372 | } else { 373 | 374 | invalid_tile(tile_width, rth, rrw, rgap); 375 | } 376 | } 377 | 378 | module knot_piece_boundary(tile_width = 5, tile_height) { 379 | // helper for alphabet only 380 | for (p = [ [tile_width*0.5, -tile_width*0.5, 0], 381 | [tile_width*1.5, -tile_width*0.5, 0], 382 | [tile_width*0.5, -tile_width*1.5, 0], 383 | [tile_width*1.5, -tile_width*1.5, 0] ]) { 384 | translate(p) knot_tile_boundary(tile_width, tile_height); 385 | } 386 | } 387 | 388 | module make_knot_piece(p, tile_width, tile_height, ribbon_width, gap) { 389 | rth = tile_height != undef ? tile_height : tile_width/2; 390 | rrw = ribbon_width != undef ? ribbon_width : tile_width/sqrt(2); 391 | rgap = gap != undef ? gap : tile_width/(6*sqrt(2)); 392 | 393 | top_left = [tile_width*0.5, -tile_width*0.5, 0]; 394 | top_right = [tile_width*1.5, -tile_width*0.5, 0]; 395 | bottom_left = [tile_width*0.5, -tile_width*1.5, 0]; 396 | bottom_right = [tile_width*1.5, -tile_width*1.5, 0]; 397 | 398 | translate(top_left) knot_tile(p[0], tile_width, rth, rrw, rgap); 399 | translate(top_right) knot_tile(p[1], tile_width, rth, rrw, rgap); 400 | translate(bottom_left) knot_tile(p[2], tile_width, rth, rrw, rgap); 401 | translate(bottom_right) knot_tile(p[3], tile_width, rth, rrw, rgap); 402 | } 403 | 404 | module knot_tile_boundary(tile_width, tile_height) { 405 | translate([0, 0, 1.001*tile_height/2]) 406 | cube([tile_width*1.001, tile_width*1.001, tile_height*1.001], 407 | center = true); 408 | } 409 | 410 | module cross(tile_width, tile_height, ribbon_width, gap) { 411 | gap2=gap*sqrt(2); 412 | 413 | intersection() { 414 | knot_tile_boundary(tile_width, tile_height); 415 | difference() { 416 | union() { 417 | rotate([0,0,-45]) 418 | cube([ribbon_width, tile_width*2, tile_height*2], center = true); 419 | translate([-tile_width/2, -tile_width/2, tile_height]) rotate([0,0,45]) 420 | cube([ribbon_width, ribbon_width, tile_height], center = true); 421 | } 422 | translate([-tile_width/4+gap2/4, -tile_width/4+gap2/4, 0]) 423 | rotate([0,0,-45]) 424 | cube([tile_width*2, gap, tile_height*4], center = true); 425 | } 426 | } 427 | } 428 | 429 | module curve(tile_width = 5, tile_height, ribbon_width, gap) { 430 | 431 | cra=66; 432 | cr=.5*tile_width/cos(cra); 433 | 434 | intersection() { 435 | knot_tile_boundary(tile_width, tile_height); 436 | union() { 437 | intersection() { 438 | translate([0,-tile_width/2*cos(cra),0]) 439 | rotate([0,0,cra]) translate([cr*1.5,0,0]) cube(cr*3, center = true); 440 | translate([ tile_width/2, 441 | (tile_width-ribbon_width)/2-(cr-ribbon_width/2), 0]) 442 | rotate_extrude() { 443 | translate([cr-ribbon_width/2, 0, 0]) 444 | square([ribbon_width, tile_height*2], center = true); 445 | } 446 | } 447 | difference() { 448 | rotate([0,0,-45]) 449 | cube([ribbon_width,tile_width*2, tile_height*2], center = true); 450 | translate([0,-tile_width/2*cos(cra),0]) 451 | rotate([0,0,cra]) translate([cr*1.5,0,0]) cube(cr*3, center = true); 452 | } 453 | } 454 | } 455 | } 456 | 457 | module cross_curve(tile_width = 5, tile_height, ribbon_width, gap) { 458 | gap2=gap*sqrt(2); 459 | 460 | intersection() { 461 | knot_tile_boundary(tile_width, tile_height); 462 | difference() { 463 | curve(tile_width, tile_height, ribbon_width); 464 | translate([-tile_width/4+gap2/4, -tile_width/4+gap2/4, 0]) 465 | rotate([0,0,-45]) 466 | cube([tile_width*2, gap, tile_height*3], center = true); 467 | } 468 | } 469 | } 470 | 471 | module round_corner(tile_width = 5, tile_height, ribbon_width, gap) { 472 | intersection() { 473 | knot_tile_boundary(tile_width, tile_height); 474 | translate([-tile_width/2, -tile_width/2, 0]) rotate_extrude() 475 | translate([tile_width-ribbon_width/2, 0, 0]) 476 | square([ribbon_width, tile_height*2], center = true); 477 | } 478 | } 479 | 480 | module square_corner(tile_width = 5, tile_height, ribbon_width, gap) { 481 | intersection() { 482 | knot_tile_boundary(tile_width, tile_height); 483 | union() { 484 | translate([(tile_width-ribbon_width)/2, 0, 0]) 485 | cube([ribbon_width,tile_width*2, tile_height*2], center = true); 486 | translate([0, (tile_width-ribbon_width)/2, 0]) 487 | cube([tile_width*2,ribbon_width, tile_height*2], center = true); 488 | } 489 | } 490 | } 491 | 492 | module straight(tile_width = 5, tile_height, ribbon_width, gap) { 493 | intersection() { 494 | knot_tile_boundary(tile_width, tile_height); 495 | translate([(tile_width-ribbon_width)/2, 0, 0]) 496 | cube([ribbon_width,tile_width*2, tile_height*2], center = true); 497 | } 498 | } 499 | 500 | module invalid_tile(tile_width = 5, tile_height, ribbon_width) { 501 | #cube([tile_width, tile_width, tile_height], center = true); 502 | } 503 | --------------------------------------------------------------------------------