From e145a137f0b49d71fbb7d388ac9a5b7d4fcd99d8 Mon Sep 17 00:00:00 2001 From: Justin Lin Date: Wed, 18 Sep 2019 14:38:10 +0800 Subject: [PATCH] add qr_coder --- examples/qr_coder.scad | 381 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 381 insertions(+) create mode 100644 examples/qr_coder.scad diff --git a/examples/qr_coder.scad b/examples/qr_coder.scad new file mode 100644 index 00000000..de71dd0b --- /dev/null +++ b/examples/qr_coder.scad @@ -0,0 +1,381 @@ +t = "1000 followers"; +head_size = 30; +qr_thickness = 1.5; + +shaft_r = 2.5; +lip_r = 3; +pin_height = 6; +spacing = 0.5; + +min_error_correction_level = "medium"; // [low, medium, quartile, high] +mask = 3; // [0:7] +encoding = "byte"; // [num, alphanum, byte] + +qr_coder(); + +module qr_coder() { + color("black") + rotate([0, 180, 0]) + translate([0, 0, -qr_thickness - head_size]) + linear_extrude(qr_thickness) + qrcode(t, head_size * 0.9, encoding, min_error_correction_level, mask, center = true); + + cube_character(head_size); +} + +module cube_character(head_size) { + $fn = 48; + + module pin(type, shaft_r = 2.5, lip_r = 3, height = 6, spacing = 0.5, lip_fn = 4) { + r_diff = lip_r - shaft_r; + + module pin_base(shaft_r, lip_r, height) { + linear_extrude(height) + circle(shaft_r); + + translate([0, 0, height - r_diff]) + rotate_extrude() + translate([lip_r - r_diff, 0, 0]) + circle(r_diff, $fn = lip_fn); + } + + module pinpeg() { + difference() { + pin_base(shaft_r, lip_r, height); + + translate([0, 0, r_diff * 2]) + linear_extrude(height - r_diff * 2) + square([r_diff * 2, lip_r * 2], center = true); + + } + } + + module pinheightole() { + pin_base(shaft_r + spacing, lip_r + spacing, height); + translate([0, 0, height]) + linear_extrude(spacing) + circle(lip_r); + + } + + if(type == "peg") { + pinpeg(); + } else if(type == "hole") { + pinheightole(); + } + } + + module head() { + half_head_size = head_size / 2; + difference() { + linear_extrude(head_size) + square(head_size, center = true); + + translate([0, -half_head_size, half_head_size]) + rotate([-90, 0, 0]) + pin("hole", shaft_r, lip_r, pin_height, spacing); + } + } + + module body() { + body_size = head_size * 0.75; + half_body_size = body_size / 2; + + module pin_hole_df() { + translate([half_body_size + 1, head_size * 0.2, half_body_size]) + rotate([0, -90, 0]) + pin("hole", shaft_r, lip_r, pin_height, spacing); + } + + difference() { + linear_extrude(body_size) union() { + square(body_size, center = true); + // feet + translate([0, -head_size * 0.4, 0]) + difference() { + square(head_size * 0.65, center = true); + square([head_size * 0.05, head_size * 0.65], center = true); + } + } + + // pin holes + translate([0, half_body_size + 1, half_body_size]) + rotate([90, 0, 0]) + pin("hole", shaft_r, lip_r, pin_height, spacing); + + pin_hole_df(); + mirror([1, 0, 0]) pin_hole_df(); + } + + // hands + hand_size = [head_size * 0.275, head_size * 0.625]; + hand_offsetx = body_size + 2; + + module hand() { + translate([half_body_size / 2, 0, 0]) + rotate([0, -90, 0]) + translate([hand_size[0] / 2, 0, 0]) { + linear_extrude(half_body_size) + square(hand_size, center = true); + + translate([head_size * 0.1375, head_size * 0.1375, half_body_size / 2]) + rotate([0, 90, 0]) + pin("peg", shaft_r, lip_r, pin_height, spacing); + } + } + + translate([-body_size, 0]) hand(); + translate([body_size, 0]) hand(); + } + + head(); + + translate([0, -head_size, 0]) + body(); + + translate([0, head_size * 0.75, pin_height]) { + pin("peg", shaft_r, lip_r, pin_height, spacing); + mirror([0, 0, 1]) + pin("peg", shaft_r, lip_r, pin_height, spacing); + } + +} + +// refactored from https://www.thingiverse.com/thing:258542 +module qrcode(t, size, encoding = "byte", min_ec = "medium", mask = 3, center = false) { + module px(x, y) { + translate([x,y]) square([1.01, 1.01]); + } + + module draw_encoded_string(encoded_string, version, mask) { + for(bit=[0:len(encoded_string)-1]) { + draw(encoded_string, version, bit, position(bit,version), mask); + } + } + + module draw(t, version, bit, xy, mask) { + _draw(t, version, bit, floor(xy/1000), xy%1000, mask); + } + + module _draw(t, version, bit, x, y, mask) { + if(xor(t[bit]=="1",mask(x,y,mask))) { + px(x, y); + } + } + + module draw_alignment_patterns(version) { + if(version > 1) { + if(version < 7) { + offset = 8+4*version; + alignment_pattern(offset, offset); + } else if(version < 14) { + for(i=[0:2]) { + for(j=[0:2]) { + if((i!=0 && j!=0) || i==1 || j==1) { + alignment_pattern(2*i*(version+1)+4, 2*j*(version+1)+4); + } + } + } + } + } + } + + module position_pattern() { + difference() { + square([7.01,7.01]); + translate(v=[1,1]) + square([5.01,5.01]); + } + translate(v=[2,2]) + square([3.01,3.01]); + } + + module alignment_pattern(x, y) { + translate(v=[x, y]) { + difference() { + square([5.01,5.01]); + translate(v=[1,1]) + square([3.01,3.01]); + } + px(2, 2); + } + } + + module format_pattern(sequence, version) { + for(i=[0:5]) { + if(sequence[14-i]=="1") { + px(i, 8); + px(8, 16+4*version-i); + } + } + for(i=[6:7]) { + if(sequence[14-i]=="1") { + px(i+1, 8); + px(8, 16+4*version-i); + } + } + for(i=[8]) { + if(sequence[14-i]=="1") { + px(8, 15-i); + px(2+4*version+i, 8); + } + } + for(i=[9:14]) { + if(sequence[14-i]=="1") { + px(8, 14-i); + px(2+4*version+i, 8); + } + } + } + + module timing_pattern(version) { + for(i=[0:2*version]) { + px(2*i+8, 6); + px(6, 2*i+8); + } + } + + function substring(t,start,end,output="") = start==end ? output : str(t[start],substring(t,start+1,end,output)); + + function encode(t, ec, version) = str(encode_data(pad_data(t, ec, version), ec, version), encode_error(pad_data(t, ec, version), ec, version), padding_bits(ec, version)); + + function encode_error(s, ec, v) = encode_error_data(error_data(s, ec, v), ec, v); + + version_table = [ + [[17,[[19,7]],1],[14,[[16,10]],1],[11,[[13,13]],1],[7,[[9,17]],1],0], + [[32,[[34,10]],1],[26,[[28,16]],1],[20,[[22,22]],1],[14,[[16,28]],1],7], + [[53,[[55,15]],1],[42,[[44,26]],1],[32,[[17,18],[17,18]],1],[24,[[13,22],[13,22]],2],7], + [[78,[[80,20]],1],[62,[[32,18],[32,18]],2],[46,[[24,26],[24,26]],2],[34,[[9,16],[9,16],[9,16],[9,16]],4],7], + [[106,[[108,26]],1],[84,[[43,24],[43,24]],2],[60,[[15,18],[15,18],[16,18],[16,18]],4],[44,[[11,22],[11,22],[12,22],[12,22]],4],7], + [[134,[[68,18],[68,18]],2],[106,[[27,16],[27,16],[27,16],[27,16]],4],[74,[[19,24],[19,24],[19,24],[19,24]],4],[58,[[15,28],[15,28],[15,28],[15,28]],4],7] + ]; + function encode_error_data(t, ec, version, pos=0, byte=0, block=0) = + version_table[version-1][ec][1][block][1]>byte ? str(bits(hex_to_int(t[pos])*16+hex_to_int(t[pos+1])), encode_error_data(t, ec, version, _next_epos(version, ec, pos, byte, block), _next_ebyte(version, ec, pos, byte, block), _next_eblock(version, ec, pos, byte, block))) : ""; + function _next_epos(v,ec,p,y,b) = bbyte ? str(bits(hex_to_int(t[pos])*16+hex_to_int(t[pos+1])), encode_data(t, ec, version, _next_dpos(version, ec, pos, byte, block), _next_dbyte(version, ec, pos, byte, block), _next_dblock(version, ec, pos, byte, block))) : ""; + function _next_dpos(v,ec,p,y,b) = b2*version_table[v-1][ec][0]+2+floor((v+20)/30) ? str(t,pad_string) : + len(pad_string)%4 == 0 ? pad_data(t, ec, v, str(pad_string,"EC")) : + pad_data(t, ec, v, str(pad_string,"11")); + + function bits(char, bit=0) = bit<7 ? str(_bit(char,bit),bits(char - (_bit(char,bit) * pow(2,7-bit)),bit+1)) : _bit(char,bit); + function _bit(char, bit) = char>=pow(2,7-bit) ? 1 : 0; + + function hex_bits(chars) = bits(hex_to_int(chars[0])*16+hex_to_int(chars[1])); + function hex_to_int(char) = search(char,"0123456789ABCDEF")[0]; + + function int(char) = (search(char," !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ \t\n \r")[0]+32) % 256; + function ec_to_int(ec) = ec=="low" ? 0 : + ec=="medium" ? 1 : + ec=="quartile" ? 2 : 3; + + function format_sequence(ec,mask) = ["101010000010010","101000100100101","101111001111100","101101101001011","100010111111001","100000011001110","100111110010111","100101010100000","111011111000100","111001011110011","111110110101010","111100010011101","110011000101111","110001100011000","110110001000001","110100101110110","001011010001001","001001110111110","001110011100111","001100111010000","000011101100010","000001001010101","000110100001100","000100000111011","011010101011111","011000001101000","011111100110001","011101000000110","010010010110100","010000110000011","010111011011010","010101111101101"][bitwise_xor(ec,1)*8+mask]; + + function version(t, encoding, ec, v=1) = len(t)<=version_table[v-1][ec][0] ? v : version(t, encoding, ec, v+1); + + function ec(s,ec,v) = ec<4 && len(s)<=version_table[v-1][ec][0] ? ec(s,ec+1,v) : ec-1; + + function xor(a, b) = (a||b)&& !(a&&b); + + function bitwise_xor(a, b) = (a==0 && b==0) ? 0 : 2*bitwise_xor(floor(a/2), floor(b/2))+(xor(a%2==1,b%2==1)?1:0); + + function mask(x, y, m) = m==0 ? mask0(x,y) : + m==1 ? mask1(x,y) : + m==2 ? mask2(x,y) : + m==3 ? mask3(x,y) : + m==4 ? mask4(x,y) : + m==5 ? mask5(x,y) : + m==6 ? mask6(x,y) : + mask7(x,y); + function mask0(x,y) = (x+y)%2 == 0; + function mask1(x,y) = x%2 == 0; + function mask2(x,y) = y%3 == 0; + function mask3(x,y) = (x+y)%3 == 0; + function mask4(x,y) = (floor(x/2)+floor(y/3))%2 == 0; + function mask5(x,y) = (x*y)%2 + (x*y)%3 == 0; + function mask6(x,y) = ((x*y)%2 + (x*y)%3)%2 == 0; + function mask7(x,y) = ((x+y)%2 + (x*y)%3)%2 == 0; + + function position(bit, version) = _position(bit,4*version+16,4*version+16,version,-1); + function reserved(x,y,v) = x==6 || y==6 || + (x<9 && y<9) || (x<9 && y>4*v+8) || (x>4*v+8 && y<9) || + (v>6 && x>4*v+5 && y<6) || (v>6 && y>4*v+5 && x<6) || + (v>1 && x>=4*v+8 && x<=4*v+12 && y>=4*v+8 && y<=4*v+12) || + (v>=7 && v<=13 && ((x>=2*v+6 && x<=2*v+10 && y%(2*v+2)>=4 && y%(2*v+2)<=8) || (y>=2*v+6 && y<=2*v+10 && x%(2*v+2)>=4 && x%(2*v+2)<=8))); + function _position(b,x,y,v,d) = b==0 && !reserved(x,y,v) ? 1000*x+y : + reserved(x,y,v) ? _next_pos(b,x,y,v,d) : _next_pos(b-1,x,y,v,d); + function _next_pos(b,x,y,v,d) = (y == 6) || ((y>6 && y%2 == 0) || (y<6 && y%2 == 1)) ? _position(b,x,y-1,v,d) : + (d<0 && x == 0) || (d>0 && x == 4*v+16) ? _position(b,x,y-1,v,-d) : + _position(b,x+d,y+1,v,d); + + function str_to_hex(t,char=0) = char==len(t) ? "" : str("0123456789ABCDEF"[floor(int(t[char])/16)],"0123456789ABCDEF"[int(t[char])%16],str_to_hex(t,char+1)); + + function int_to_hex(i) = str("0123456789ABCDEF"[floor(i/16)],"0123456789ABCDEF"[i%16]); + + version = version(t, encoding, ec_to_int(min_ec)); + dist_between_position_pattern = 10 + 4 * version; + + ec=ec(t,ec_to_int(min_ec),version); + + encoded_string = version<10 ? encode(str("4",int_to_hex(len(t)),str_to_hex(t),"0"), ec, version) : encode(str("4",int_to_hex(floor(len(t)/256)),int_to_hex(len(t)%256),str_to_hex(t),"0"), ec, version); + + qr_size = dist_between_position_pattern + 7; + half_qr_size = qr_size / 2; + + s_factor = is_undef(size) ? 1 : size / qr_size; + scaled_size = s_factor * qr_size; + half_scaled_size = scaled_size / 2; + + // QR Code + translate(center ? [-half_scaled_size, -half_scaled_size] : [0, 0]) scale(s_factor) { + position_pattern(); + + translate(v = [dist_between_position_pattern, 0]) + position_pattern(); + + translate(v=[0, dist_between_position_pattern]) + position_pattern(); + + px(9+4*version, 8); + + format_pattern(format_sequence(ec,mask), version); + timing_pattern(version); + draw_alignment_patterns(version); + draw_encoded_string(encoded_string, version, mask); + } +} \ No newline at end of file