├── LICENSE.md
├── README.md
├── documentation
└── 3D Model.skp
├── examples
├── accept-all-keys.html
├── acoustics.html
├── bumpkey.html
├── construction.html
├── ic-best.html
├── ic-pinning.html
├── ic.html
├── impression.html
├── interactive-buildup.html
├── interactive.html
├── schlage-mastered.html
├── schlage-nonmastered.html
├── svg.min.js
└── svg.min.js.map
└── lockview.js
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright 2020 Bill Graydon.
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8 |
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Lockview
2 | A small JS library to generate parametric SVG images of locks and keys, animate them and integrate onto web pages. Released as a supporting tool for my DEF CON 28 talk on key space hacking: https://defcon.org/html/defcon-safemode/dc-safemode-speakers.html#Graydon.
3 |
4 | # Beta
5 | The version currently released is very rough around the edges. I'm primarily using it to aid in my DEF CON presentation at the moment. Polishes to come after DEF CON.
6 |
7 | # Examples
8 | The majority of people will probably just want to play with the examples rather than use this as a library.
9 |
10 | Example source code is in the /examples folder. Live examples are online:
11 |
12 | ### Standard non-mastered system:
13 |
14 | https://ggrsecurity.com/personal/~bgraydon/lockview/examples/schlage-nonmastered.html
15 |
16 | ### Mastered system:
17 |
18 | https://ggrsecurity.com/personal/~bgraydon/lockview/examples/schlage-mastered.html
19 |
20 | ### Interchangeable Core:
21 |
22 | https://ggrsecurity.com/personal/~bgraydon/lockview/examples/ic.html
23 |
24 | ### Construction Keyed Lock:
25 |
26 | https://ggrsecurity.com/personal/~bgraydon/lockview/examples/construction.html
27 |
28 | ### Bump Key (note - inertial physics not simulated):
29 |
30 | https://ggrsecurity.com/personal/~bgraydon/lockview/examples/bumpkey.html
31 |
32 | ### Lock Impressioning Minigame:
33 |
34 | https://ggrsecurity.com/personal/~bgraydon/lockview/examples/impression.html
35 |
36 | # Using the Library
37 | Lockview is a javascript library that allows interactive images of locks and keys to be incorporated into any web page. It requires SVG.js: https://svgjs.com/docs/3.0/
38 |
39 | All relevant functions and objects are under the "lockview" object.
40 |
41 | To add a lock to a webpage, create a container div or other HTML element with some id:
42 | `
`
43 |
44 | And _after the page has loaded_ call the `lockview.addLock` function:
45 |
46 | ```
47 | lockview.addLock(
48 | 'lock0', // HTML Container element ID
49 | lockview.schlageLockspec, // Information about the lock dimensions
50 | lockview.defaultViewOpts, // What controls should be visible, styling, etc
51 | [2,4,5,3,1], // Key Code
52 | [[2],[4],[5],[3],[1]], // Shear lines
53 | "KEY" // Text stamped on the bow of the key
54 | );
55 | ```
56 |
57 | The return value of `lockview.addLock` is a JS Object with fields and functions that can be used to manipulate the lock / key after its creation.
58 |
59 | Much more complete documentation is to come.
60 |
--------------------------------------------------------------------------------
/documentation/3D Model.skp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bgraydon/lockview/c0c12e54a9c698457339649550cf8b7accb503a1/documentation/3D Model.skp
--------------------------------------------------------------------------------
/examples/accept-all-keys.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/examples/acoustics.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
225 |
248 |
249 |
250 | Generate New Random Lock
251 |
252 | Ridge and pin spacing:
253 |
254 | Click timeline (length of timeline is the time it takes to insert the key):
255 |
256 |
257 |
258 |
259 |
--------------------------------------------------------------------------------
/examples/bumpkey.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/construction.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/examples/ic-best.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/examples/ic-pinning.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
30 |
31 |
32 |
33 |
34 | BEST A2
35 | BEST A4
36 | Sargent 6300
37 | Medeco
38 | Corbin Russwin Z
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/examples/ic.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/examples/impression.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
31 |
32 |
33 | Generate New Lock to Impression
34 |
35 |
36 |
--------------------------------------------------------------------------------
/examples/interactive-buildup.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/examples/interactive.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/examples/schlage-mastered.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/examples/schlage-nonmastered.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/examples/svg.min.js:
--------------------------------------------------------------------------------
1 | /*! @svgdotjs/svg.js v3.0.16 MIT*/;
2 | var SVG=function(){"use strict";var t="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function e(t,e){return t(e={exports:{}},e.exports),e.exports}var n,r,i,o=function(t){return t&&t.Math==Math&&t},y=o("object"==typeof globalThis&&globalThis)||o("object"==typeof window&&window)||o("object"==typeof self&&self)||o("object"==typeof t&&t)||Function("return this")(),m=function(t){try{return!!t()}catch(t){return!0}},f=!m(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a}),s={}.propertyIsEnumerable,u=Object.getOwnPropertyDescriptor,v={f:u&&!s.call({1:2},1)?function(t){var e=u(this,t);return!!e&&e.enumerable}:s},b=function(t,e){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:e}},a={}.toString,h=function(t){return a.call(t).slice(8,-1)},l="".split,_=m(function(){return!Object("z").propertyIsEnumerable(0)})?function(t){return"String"==h(t)?l.call(t,""):Object(t)}:Object,c=function(t){if(null==t)throw TypeError("Can't call method on "+t);return t},d=function(t){return _(c(t))},g=function(t){return"object"==typeof t?null!==t:"function"==typeof t},p=function(t,e){if(!g(t))return t;var n,r;if(e&&"function"==typeof(n=t.toString)&&!g(r=n.call(t)))return r;if("function"==typeof(n=t.valueOf)&&!g(r=n.call(t)))return r;if(!e&&"function"==typeof(n=t.toString)&&!g(r=n.call(t)))return r;throw TypeError("Can't convert object to primitive value")},w={}.hasOwnProperty,x=function(t,e){return w.call(t,e)},k=y.document,O=g(k)&&g(k.createElement),S=function(t){return O?k.createElement(t):{}},j=!f&&!m(function(){return 7!=Object.defineProperty(S("div"),"a",{get:function(){return 7}}).a}),M=Object.getOwnPropertyDescriptor,E={f:f?M:function(t,e){if(t=d(t),e=p(e,!0),j)try{return M(t,e)}catch(t){}if(x(t,e))return b(!v.f.call(t,e),t[e])}},T=function(t){if(!g(t))throw TypeError(String(t)+" is not an object");return t},C=Object.defineProperty,P={f:f?C:function(t,e,n){if(T(t),e=p(e,!0),T(n),j)try{return C(t,e,n)}catch(t){}if("get"in n||"set"in n)throw TypeError("Accessors not supported");return"value"in n&&(t[e]=n.value),t}},I=f?function(t,e,n){return P.f(t,e,b(1,n))}:function(t,e,n){return t[e]=n,t},N=function(e,n){try{I(y,e,n)}catch(t){y[e]=n}return n},D="__core-js_shared__",R=y[D]||N(D,{}),L=e(function(t){(t.exports=function(t,e){return R[t]||(R[t]=void 0!==e?e:{})})("versions",[]).push({version:"3.3.6",mode:"global",copyright:"© 2019 Denis Pushkarev (zloirock.ru)"})}),F=L("native-function-to-string",Function.toString),z=y.WeakMap,q="function"==typeof z&&/native code/.test(F.call(z)),Y=0,X=Math.random(),V=function(t){return"Symbol("+String(void 0===t?"":t)+")_"+(++Y+X).toString(36)},H=L("keys"),B=function(t){return H[t]||(H[t]=V(t))},U={},$=y.WeakMap;if(q){var Q=new $,W=Q.get,J=Q.has,Z=Q.set;n=function(t,e){return Z.call(Q,t,e),e},r=function(t){return W.call(Q,t)||{}},i=function(t){return J.call(Q,t)}}else{var K=B("state");U[K]=!0,n=function(t,e){return I(t,K,e),e},r=function(t){return x(t,K)?t[K]:{}},i=function(t){return x(t,K)}}var tt={set:n,get:r,has:i,enforce:function(t){return i(t)?r(t):n(t,{})},getterFor:function(n){return function(t){var e;if(!g(t)||(e=r(t)).type!==n)throw TypeError("Incompatible receiver, "+n+" required");return e}}},et=e(function(t){var e=tt.get,u=tt.enforce,a=String(F).split("toString");L("inspectSource",function(t){return F.call(t)}),(t.exports=function(t,e,n,r){var i=!!r&&!!r.unsafe,o=!!r&&!!r.enumerable,s=!!r&&!!r.noTargetGet;"function"==typeof n&&("string"!=typeof e||x(n,"name")||I(n,"name",e),u(n).source=a.join("string"==typeof e?e:"")),t!==y?(i?!s&&t[e]&&(o=!0):delete t[e],o?t[e]=n:I(t,e,n)):o?t[e]=n:N(e,n)})(Function.prototype,"toString",function(){return"function"==typeof this&&e(this).source||F.call(this)})}),nt=y,rt=function(t){return"function"==typeof t?t:void 0},it=function(t,e){return arguments.length<2?rt(nt[t])||rt(y[t]):nt[t]&&nt[t][e]||y[t]&&y[t][e]},ot=Math.ceil,st=Math.floor,ut=function(t){return isNaN(t=+t)?0:(0i;)x(r,n=e[i++])&&(~pt(o,n)||o.push(n));return o},mt=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"],gt=mt.concat("length","prototype"),wt={f:Object.getOwnPropertyNames||function(t){return yt(t,gt)}},bt={f:Object.getOwnPropertySymbols},_t=it("Reflect","ownKeys")||function(t){var e=wt.f(T(t)),n=bt.f;return n?e.concat(n(t)):e},xt=function(t,e){for(var n=_t(e),r=P.f,i=E.f,o=0;odocument.F=Object"+r+">"),t.close(),me=t.F;n--;)delete me[pe][mt[n]];return me()},ge=Object.create||function(t,e){var n;return null!==t?(ye[pe]=T(t),n=new ye,ye[pe]=null,n[de]=t):n=me(),void 0===e?n:fe(n,e)};U[de]=!0;var we={f:Gt},be=P.f,_e=function(t){var Symbol=nt.Symbol||(nt.Symbol={});x(Symbol,t)||be(Symbol,t,{value:we.f(t)})},xe=P.f,ke=Gt("toStringTag"),Oe=function(t,e,n){t&&!x(t=n?t:t.prototype,ke)&&xe(t,ke,{configurable:!0,value:e})},Se=function(t){if("function"!=typeof t)throw TypeError(String(t)+" is not a function");return t},Ae=function(r,i,t){if(Se(r),void 0===i)return r;switch(t){case 0:return function(){return r.call(i)};case 1:return function(t){return r.call(i,t)};case 2:return function(t,e){return r.call(i,t,e)};case 3:return function(t,e,n){return r.call(i,t,e,n)}}return function(){return r.apply(i,arguments)}},je=Gt("species"),Me=function(t,e){var n;return Ft(t)&&("function"!=typeof(n=t.constructor)||n!==Array&&!Ft(n.prototype)?g(n)&&null===(n=n[je])&&(n=void 0):n=void 0),new(void 0===n?Array:n)(0===e?0:e)},Ee=[].push,Te=function(v){var d=1==v,p=2==v,y=3==v,m=4==v,g=6==v,w=5==v||g;return function(t,e,n,r){for(var i,o,s=It(t),u=_(s),a=Ae(e,n,3),h=ht(u.length),l=0,c=r||Me,f=d?c(t,h):p?c(t,0):void 0;li;)r.push(arguments[i++]);if(n=e=r[1],(g(e)||void 0!==t)&&!nn(t))return Ft(e)||(e=function(t,e){if("function"==typeof n&&(e=n.call(this,t,e)),!nn(e))return e}),r[1]=e,Xe.apply(Ye,r)}}),qe[De][Re]||I(qe[De],Re,qe[De].valueOf),Oe(qe,Ne),U[Ie]=!0;var ln=P.f,cn=y.Symbol;if(f&&"function"==typeof cn&&(!("description"in cn.prototype)||void 0!==cn().description)){var fn={},vn=function(){var t=arguments.length<1||void 0===arguments[0]?void 0:String(arguments[0]),e=this instanceof vn?new cn(t):void 0===t?cn():cn(t);return""===t&&(fn[e]=!0),e};xt(vn,cn);var dn=vn.prototype=cn.prototype;dn.constructor=vn;var pn=dn.toString,yn="Symbol(test)"==String(cn("test")),mn=/^Symbol\((.*)\)[^)]+$/;ln(dn,"description",{configurable:!0,get:function(){var t=g(this)?this.valueOf():this,e=pn.call(t);if(x(fn,t))return"";var n=yn?e.slice(7,-1):e.replace(mn,"$1");return""===n?void 0:n}}),Ct({global:!0,forced:!0},{Symbol:vn})}_e("iterator");var gn=Gt("unscopables"),wn=Array.prototype;null==wn[gn]&&I(wn,gn,ge(null));var bn,_n,xn,kn=function(t){wn[gn][t]=!0},On={},Sn=!m(function(){function t(){}return t.prototype.constructor=null,Object.getPrototypeOf(new t)!==t.prototype}),An=B("IE_PROTO"),jn=Object.prototype,Mn=Sn?Object.getPrototypeOf:function(t){return t=It(t),x(t,An)?t[An]:"function"==typeof t.constructor&&t instanceof t.constructor?t.constructor.prototype:t instanceof Object?jn:null},En=Gt("iterator"),Tn=!1;[].keys&&("next"in(xn=[].keys())?(_n=Mn(Mn(xn)))!==Object.prototype&&(bn=_n):Tn=!0),null==bn&&(bn={}),x(bn,En)||I(bn,En,function(){return this});var Cn={IteratorPrototype:bn,BUGGY_SAFARI_ITERATORS:Tn},Pn=Cn.IteratorPrototype,In=function(){return this},Nn=Object.setPrototypeOf||("__proto__"in{}?function(){var n,r=!1,t={};try{(n=Object.getOwnPropertyDescriptor(Object.prototype,"__proto__").set).call(t,[]),r=t instanceof Array}catch(t){}return function(t,e){return T(t),function(t){if(!g(t)&&null!==t)throw TypeError("Can't set "+String(t)+" as a prototype")}(e),r?n.call(t,e):t.__proto__=e,t}}():void 0),Dn=Cn.IteratorPrototype,Rn=Cn.BUGGY_SAFARI_ITERATORS,Ln=Gt("iterator"),Fn="values",zn="entries",qn=function(){return this},Yn=function(t,e,n,r,i,o,s){var u,a,h;a=r,h=e+" Iterator",(u=n).prototype=ge(Pn,{next:b(1,a)}),Oe(u,h,!1),On[h]=In;var l,c,f,v=function(t){if(t===i&&g)return g;if(!Rn&&t in y)return y[t];switch(t){case"keys":case Fn:case zn:return function(){return new n(this,t)}}return function(){return new n(this)}},d=e+" Iterator",p=!1,y=t.prototype,m=y[Ln]||y["@@iterator"]||i&&y[i],g=!Rn&&m||v(i),w="Array"==e&&y.entries||m;if(w&&(l=Mn(w.call(new t)),Dn!==Object.prototype&&l.next&&(Mn(l)!==Dn&&(Nn?Nn(l,Dn):"function"!=typeof l[Ln]&&I(l,Ln,qn)),Oe(l,d,!0))),i==Fn&&m&&m.name!==Fn&&(p=!0,g=function(){return m.call(this)}),y[Ln]!==g&&I(y,Ln,g),On[e]=g,i)if(c={values:v(Fn),keys:o?g:v("keys"),entries:v(zn)},s)for(f in c)!Rn&&!p&&f in y||et(y,f,c[f]);else Ct({target:e,proto:!0,forced:Rn||p},c);return c},Xn="Array Iterator",Gn=tt.set,Vn=tt.getterFor(Xn),Hn=Yn(Array,"Array",function(t,e){Gn(this,{type:Xn,target:d(t),index:0,kind:e})},function(){var t=Vn(this),e=t.target,n=t.kind,r=t.index++;return!e||r>=e.length?{value:t.target=void 0,done:!0}:"keys"==n?{value:r,done:!1}:"values"==n?{value:e[r],done:!1}:{value:[r,e[r]],done:!1}},"values");On.Arguments=On.Array,kn("keys"),kn("values"),kn("entries");var Bn=Gt("toStringTag"),Un="Arguments"==h(function(){return arguments}()),$n=function(t){var e,n,r;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(n=function(t,e){try{return t[e]}catch(t){}}(e=Object(t),Bn))?n:Un?h(e):"Object"==(r=h(e))&&"function"==typeof e.callee?"Arguments":r},Qn={};Qn[Gt("toStringTag")]="z";var Wn="[object z]"!==String(Qn)?function(){return"[object "+$n(this)+"]"}:Qn.toString,Jn=Object.prototype;Wn!==Jn.toString&&et(Jn,"toString",Wn,{unsafe:!0});var Zn=!m(function(){return Object.isExtensible(Object.preventExtensions({}))}),Kn=e(function(t){var e=P.f,n=V("meta"),r=0,i=Object.isExtensible||function(){return!0},o=function(t){e(t,n,{value:{objectID:"O"+ ++r,weakData:{}}})},s=t.exports={REQUIRED:!1,fastKey:function(t,e){if(!g(t))return"symbol"==typeof t?t:("string"==typeof t?"S":"P")+t;if(!x(t,n)){if(!i(t))return"F";if(!e)return"E";o(t)}return t[n].objectID},getWeakData:function(t,e){if(!x(t,n)){if(!i(t))return!0;if(!e)return!1;o(t)}return t[n].weakData},onFreeze:function(t){return Zn&&s.REQUIRED&&i(t)&&!x(t,n)&&o(t),t}};U[n]=!0}),tr=Gt("iterator"),er=Array.prototype,nr=Gt("iterator"),rr=function(e,t,n,r){try{return r?t(T(n)[0],n[1]):t(n)}catch(t){var i=e.return;throw void 0!==i&&T(i.call(e)),t}},ir=e(function(t){var d=function(t,e){this.stopped=t,this.result=e};(t.exports=function(t,e,n,r,i){var o,s,u,a,h,l,c,f,v=Ae(e,n,r?2:1);if(i)o=t;else{if("function"!=typeof(s=function(t){if(null!=t)return t[nr]||t["@@iterator"]||On[$n(t)]}(t)))throw TypeError("Target is not iterable");if(void 0!==(f=s)&&(On.Array===f||er[tr]===f)){for(u=0,a=ht(t.length);u=n.length?{value:void 0,done:!0}:(t=wr(n,r),e.index+=t.length,{value:t,done:!1})});var kr={CSSRuleList:0,CSSStyleDeclaration:0,CSSValueList:0,ClientRectList:0,DOMRectList:0,DOMStringList:0,DOMTokenList:1,DataTransferItemList:0,FileList:0,HTMLAllCollection:0,HTMLCollection:0,HTMLFormElement:0,HTMLSelectElement:0,MediaList:0,MimeTypeArray:0,NamedNodeMap:0,NodeList:1,PaintRequestList:0,Plugin:0,PluginArray:0,SVGLengthList:0,SVGNumberList:0,SVGPathSegList:0,SVGPointList:0,SVGStringList:0,SVGTransformList:0,SourceBufferList:0,StyleSheetList:0,TextTrackCueList:0,TextTrackList:0,TouchList:0},Or=Gt("iterator"),Sr=Gt("toStringTag"),Ar=Hn.values;for(var jr in kr){var Mr=y[jr],Er=Mr&&Mr.prototype;if(Er){if(Er[Or]!==Ar)try{I(Er,Or,Ar)}catch(t){Er[Or]=Ar}if(Er[Sr]||I(Er,Sr,jr),kr[jr])for(var Tr in Hn)if(Er[Tr]!==Hn[Tr])try{I(Er,Tr,Hn[Tr])}catch(t){Er[Tr]=Hn[Tr]}}}function Cr(t){return function(t){if(Array.isArray(t)){for(var e=0,n=new Array(t.length);e")}),Zr=!m(function(){var t=/(?:)/,e=t.exec;t.exec=function(){return e.apply(this,arguments)};var n="ab".split(t);return 2!==n.length||"a"!==n[0]||"b"!==n[1]}),Kr=function(n,t,e,r){var i=Gt(n),o=!m(function(){var t={};return t[i]=function(){return 7},7!=""[n](t)}),s=o&&!m(function(){var t=!1,e=/a/;return"split"===n&&((e={constructor:{}}).constructor[Wr]=function(){return e},e.flags="",e[i]=/./[i]),e.exec=function(){return t=!0,null},e[i](""),!t});if(!o||!s||"replace"===n&&!Jr||"split"===n&&!Zr){var u=/./[i],a=e(i,""[n],function(t,e,n,r,i){return e.exec===Br?o&&!i?{done:!0,value:u.call(e,n,r)}:{done:!0,value:t.call(n,e,r)}:{done:!1}}),h=a[0],l=a[1];et(String.prototype,n,h),et(RegExp.prototype,i,2==t?function(t,e){return l.call(t,this,e)}:function(t){return l.call(t,this)}),r&&I(RegExp.prototype[i],"sham",!0)}},ti=gr.charAt,ei=function(t,e,n){return e+(n?ti(t,e).length:1)},ni=function(t,e){var n=t.exec;if("function"==typeof n){var r=n.call(t,e);if("object"!=typeof r)throw TypeError("RegExp exec method returned something other than an Object or null");return r}if("RegExp"!==h(t))throw TypeError("RegExp#exec called on incompatible receiver");return Br.call(t,e)},ri=Math.max,ii=Math.min,oi=Math.floor,si=/\$([$&'`]|\d\d?|<[^>]*>)/g,ui=/\$([$&'`]|\d\d?)/g;Kr("replace",2,function(i,_,x){return[function(t,e){var n=c(this),r=null==t?void 0:t[i];return void 0!==r?r.call(t,n,e):_.call(String(n),t,e)},function(t,e){var n=x(_,t,this,e);if(n.done)return n.value;var r=T(t),i=String(this),o="function"==typeof e;o||(e=String(e));var s=r.global;if(s){var u=r.unicode;r.lastIndex=0}for(var a=[];;){var h=ni(r,i);if(null===h)break;if(a.push(h),!s)break;""===String(h[0])&&(r.lastIndex=ei(i,ht(r.lastIndex),u))}for(var l,c="",f=0,v=0;v>>0;if(0===r)return[];if(void 0===t)return[n];if(!$r(t))return g.call(n,t,r);for(var i,o,s,u=[],a=(t.ignoreCase?"i":"")+(t.multiline?"m":"")+(t.unicode?"u":"")+(t.sticky?"y":""),h=0,l=new RegExp(t.source,a+"g");(i=Br.call(l,n))&&!(h<(o=l.lastIndex)&&(u.push(n.slice(h,i.index)),1=r));)l.lastIndex===i.index&&l.lastIndex++;return h===n.length?!s&&l.test("")||u.push(""):u.push(n.slice(h)),u.length>r?u.slice(0,r):u}:"0".split(void 0,0).length?function(t,e){return void 0===t&&0===e?[]:g.call(this,t,e)}:g,[function(t,e){var n=c(this),r=null==t?void 0:t[i];return void 0!==r?r.call(t,n,e):b.call(String(n),t,e)},function(t,e){var n=w(b,t,this,e,b!==g);if(n.done)return n.value;var r,i,o,s=T(t),u=String(this),a=(r=RegExp,void 0===(o=T(s).constructor)||null==(i=T(o)[eo])?r:Se(i)),h=s.unicode,l=(s.ignoreCase?"i":"")+(s.multiline?"m":"")+(s.unicode?"u":"")+(oo?"y":"g"),c=new a(oo?s:"^(?:"+s.source+")",l),f=void 0===e?io:e>>>0;if(0===f)return[];if(0===u.length)return null===ni(c,u)?[u]:[];for(var v=0,d=0,A=[];d>>0||(Ko.test(n)?16:10))}:Zo;Ct({global:!0,forced:parseInt!=ts},{parseInt:ts});var es="toString",ns=RegExp.prototype,rs=ns[es],is=m(function(){return"/a/b"!=rs.call({source:"a",flags:"b"})}),os=rs.name!=es;function ss(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){if(Symbol.iterator in Object(t)||"[object Arguments]"===Object.prototype.toString.call(t)){var n=[],r=!0,i=!1,o=void 0;try{for(var s,u=t[Symbol.iterator]();!(r=(s=u.next()).done)&&(n.push(s.value),!e||n.length!==e);r=!0);}catch(t){i=!0,o=t}finally{try{r||null==u.return||u.return()}finally{if(i)throw o}}return n}}(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}()}function us(t,e){for(var n=0;nv?Math.pow(l,3):(l-f)/7.787),e=1*(Math.pow(h,3)>v?Math.pow(h,3):(h-f)/7.787),n=1.08883*(Math.pow(c,3)>v?Math.pow(c,3):(c-f)/7.787)}var d=3.2406*t+-1.5372*e+-.4986*n,p=-.9689*t+1.8758*e+.0415*n,y=.0557*t+-.204*e+1.057*n,m=Math.pow,g=.0031308;return new T(255*(gPs;Ps++)x(Ss,Es=Cs[Ps])&&!x(Ts,Es)&&xs(Ts,Es,_s(Ss,Es));(Ts.prototype=As).constructor=Ts,et(y,Os,Ts)}var Is=di.trim,Ns=y.parseFloat,Ds=1/Ns(hi+"-0")!=-1/0?function(t){var e=Is(String(t)),n=Ns(e);return 0===n&&"-"==e.charAt(0)?-0:n}:Ns;Ct({global:!0,forced:parseFloat!=Ds},{parseFloat:Ds});var Rs=function(){function t(){Ti(this,t),this.init.apply(this,arguments)}return as(t,[{key:"init",value:function(t,e){var n=0,r=0,i=Array.isArray(t)?{x:t[0],y:t[1]}:"object"===ce(t)?{x:t.x,y:t.y}:{x:t,y:e};return this.x=null==i.x?n:i.x,this.y=null==i.y?r:i.y,this}},{key:"clone",value:function(){return new t(this)}},{key:"transform",value:function(t){return this.clone().transformO(t)}},{key:"transformO",value:function(t){Fs.isMatrixLike(t)||(t=new Fs(t));var e=this.x,n=this.y;return this.x=t.a*e+t.c*n+t.e,this.y=t.b*e+t.d*n+t.f,this}},{key:"toArray",value:function(){return[this.x,this.y]}}]),t}();function Ls(t,e,n){return Math.abs(e-t)<(n||1e-6)}var Fs=function(){function h(){Ti(this,h),this.init.apply(this,arguments)}return as(h,[{key:"init",value:function(t){var e=h.fromArray([1,0,0,1,0,0]);return t=t instanceof Element?t.matrixify():"string"==typeof t?h.fromArray(t.split(go).map(parseFloat)):Array.isArray(t)?h.fromArray(t):"object"===ce(t)&&h.isMatrixLike(t)?t:"object"===ce(t)?(new h).transform(t):6===arguments.length?h.fromArray([].slice.call(arguments)):e,this.a=null!=t.a?t.a:e.a,this.b=null!=t.b?t.b:e.b,this.c=null!=t.c?t.c:e.c,this.d=null!=t.d?t.d:e.d,this.e=null!=t.e?t.e:e.e,this.f=null!=t.f?t.f:e.f,this}},{key:"clone",value:function(){return new h(this)}},{key:"transform",value:function(t){if(h.isMatrixLike(t))return new h(t).multiplyO(this);var e=h.formatTransforms(t),n=new Rs(e.ox,e.oy).transform(this),r=n.x,i=n.y,o=(new h).translateO(e.rx,e.ry).lmultiplyO(this).translateO(-r,-i).scaleO(e.scaleX,e.scaleY).skewO(e.skewX,e.skewY).shearO(e.shear).rotateO(e.theta).translateO(r,i);if(isFinite(e.px)||isFinite(e.py)){var s=new Rs(r,i).transform(o),u=e.px?e.px-s.x:0,a=e.py?e.py-s.y:0;o.translateO(u,a)}return o.translateO(e.tx,e.ty),o}},{key:"compose",value:function(t){t.origin&&(t.originX=t.origin[0],t.originY=t.origin[1]);var e=t.originX||0,n=t.originY||0,r=t.scaleX||1,i=t.scaleY||1,o=t.shear||0,s=t.rotate||0,u=t.translateX||0,a=t.translateY||0;return(new h).translateO(-e,-n).scaleO(r,i).shearO(o).rotateO(s).translateO(u,a).lmultiplyO(this).translateO(e,n)}},{key:"decompose",value:function(){var t=0",delay:0},Ws={"fill-opacity":1,"stroke-opacity":1,"stroke-width":0,"stroke-linejoin":"miter","stroke-linecap":"butt",fill:"#000000",stroke:"#000000",opacity:1,x:0,y:0,cx:0,cy:0,width:0,height:0,r:0,rx:0,ry:0,offset:0,"stop-opacity":1,"stop-color":"#000000","text-anchor":"start"},Js={__proto__:null,noop:$s,timeline:Qs,attrs:Ws},Zs=Gs("SVGArray",Array,function(t){this.init(t)});Vi(Zs,{init:function(t){return"number"==typeof t||(this.length=0,this.push.apply(this,Cr(this.parse(t)))),this},toArray:function(){return Array.prototype.concat.apply([],this)},toString:function(){return this.join(" ")},valueOf:function(){var t=[];return t.push.apply(t,Cr(this)),t},parse:function(){var t=0n.x&&e>n.y&&tu;)void 0!==(n=i(r,e=o[u++]))&&zt(s,e,n);return s}}),Nr("Element",{untransform:function(){return this.attr("transform",null)},matrixify:function(){return(this.attr("transform")||"").split(lo).slice(0,-1).map(function(t){var e=t.trim().split("(");return[e[0],e[1].split(go).map(function(t){return parseFloat(t)})]}).reverse().reduce(function(t,e){return"matrix"===e[0]?t.lmultiply(Fs.fromArray(e[1])):t[e[0]].apply(t,e[1])},new Fs)},toParent:function(t){if(this===t)return this;var e=this.screenCTM(),n=t.screenCTM().inverse();return this.addTo(t).untransform().transform(n.multiply(e)),this},toRoot:function(){return this.toParent(this.root())},transform:function(t,e){if(null==t||"string"==typeof t){var n=new Fs(this).decompose();return null==t?n:n[t]}Fs.isMatrixLike(t)||(t=function(e){for(var t=1;t":function(t){return-Math.cos(t*Math.PI)/2+.5},">":function(t){return Math.sin(t*Math.PI/2)},"<":function(t){return 1-Math.cos(t*Math.PI/2)},bezier:function(e,n,r,i){return function(t){return t<0?0=e.time?e.run():Ku.timeouts.push(e),e!==n););for(var r=null,i=Ku.frames.last();r!==i&&(r=Ku.frames.shift());)r.run(t);for(var o=null;o=Ku.immediates.shift();)o();Ku.nextDraw=Ku.timeouts.first()||Ku.frames.first()?Ei.window.requestAnimationFrame(Ku._draw):null}},ta=function(t){var e=t.start,n=t.runner.duration();return{start:e,duration:n,end:e+n,runner:t.runner}},ea=function(){var t=Ei.window;return(t.performance||t.Date).now()},na=function(t){function n(){var t,e=0=r;this._lastTime=this._time,i&&this.fire("start",this);var s=this._isDeclarative;if(this.done=!s&&!o&&this._time>=r,this._reseted=!1,n||s){this._initialise(n),this.transforms=new Fs;var u=this._run(s?t:e);this.fire("step",this)}return this.done=this.done||u&&s,o&&this.fire("finished",this),this}},{key:"reset",value:function(){return this._reseted||(this.time(0),this._reseted=!0),this}},{key:"finish",value:function(){return this.step(1/0)}},{key:"reverse",value:function(t){return this._reverse=null==t?!this._reverse:t,this}},{key:"ease",value:function(t){return this._stepper=new Cu(t),this}},{key:"active",value:function(t){return null==t?this.enabled:(this.enabled=t,this)}},{key:"_rememberMorpher",value:function(t,e){if(this._history[t]={morpher:e,caller:this._queue[this._queue.length-1]},this._isDeclarative){var n=this.timeline();n&&n.play()}}},{key:"_tryRetarget",value:function(t,e,n){if(this._history[t]){if(!this._history[t].caller.initialised){var r=this._queue.indexOf(this._history[t].caller);return this._queue.splice(r,1),!1}this._history[t].caller.retarget?this._history[t].caller.retarget(e,n):this._history[t].morpher.to(e),this._history[t].caller.finished=!1;var i=this.timeline();return i&&i.play(),!0}return!1}},{key:"_initialise",value:function(t){if(t||this._isDeclarative)for(var e=0,n=this._queue.length;ev+200),
122 | pinStackHeight: 500,
123 | keywayHeight: 318,
124 | keywayLength: 1350,
125 | totalLockHeight: 1100,
126 | totalLockLength: 1500,
127 | constantDriverHeight: false,
128 | cutterWheel: {
129 | foreAngle: 45,
130 | aftAngle: 45,
131 | foreLen: 20,
132 | aftLen: 20,
133 | },
134 | icCollar: {
135 | present: false,
136 | pins: [0,1,2,3,4,5,6], // Zero-indexed!
137 | depth: 10,
138 | amend: function(params){return lockview.shallowcopy_change_vals(this,params);}
139 | },
140 | });
141 |
142 | lockview.bestA4Lockspec = lockview.bestA2Lockspec.amend({
143 | numDepths: 10,
144 | depths: [318,297,276,255,234,213,192,171,150,129,108,87],
145 | pinStackHeight: 500,
146 | constantDriverHeight: false,
147 | icCollar: {
148 | present: false,
149 | pins: [0,1,2,3,4,5,6], // Zero-indexed!
150 | depth: 6,
151 | amend: function(params){return lockview.shallowcopy_change_vals(this,params);}
152 | },
153 | });
154 |
155 | lockview.style = {
156 | keyPin: {fill: "#DDDD00", stroke: 'black'},
157 | driverPin: {fill: "#CCCCCC", stroke: 'black'},
158 | masterWafer: {fill: "#FF4444", stroke: 'black'},
159 | unknownPins: {fill: "#FFFFFF", stroke: 'black', 'stroke-width': 3, 'stroke-dasharray': 4},
160 | plug: {fill: "#555555", stroke: 'black'},
161 | housing: {fill: "#888888", stroke: 'black'},
162 | icCollar: {fill: "#8888FF", stroke: 'black'}
163 | }
164 |
165 | lockview.defaultViewOpts = {
166 | style: lockview.style,
167 | keyRemovable: true,
168 | width: 750,
169 | cutaway: true,
170 | unknownPinstackBinds: false,
171 | controls: {
172 | moveKey: true,
173 | tryTurn: true,
174 | impression: true,
175 | setCode: true,
176 | setShearLines: true,
177 | toggleCutaway: true,
178 | amend: function(params){return lockview.shallowcopy_change_vals(this,params);}
179 | },
180 | amend: function(params){return lockview.shallowcopy_change_vals(this,params);}
181 | }
182 |
183 | lockview.lockSpecFromNumPins = function(numPins, numDepths, offset) {
184 | offset = offset || 0;
185 | return lockview.schlageLockspec.amend({
186 | numPins: numPins,
187 | numDepths: numDepths,
188 | depthIndexOffset: offset,
189 | spaces: [...Array(numPins).keys()].map(x=>231+156*x),
190 | depths: Array(offset).fill(0).concat([...Array(numDepths).keys()].map(x=>335-x*((numDepths<4?84:135)/(numDepths-1)))),
191 | keywayLength: 156*numPins+280,
192 | totalLockLength: 156*numPins+520
193 | });
194 | }
195 |
196 | lockview.exampleShearLines = [
197 | [2],
198 | [2,4],
199 | [1,3,5],
200 | [3,9],
201 | [1,9]
202 | ];
203 | lockview.exampleKeyCode = [2,4,5,3,1];
204 |
205 | lockview.upperPin = function(svg, lockspec, pinIndex, height, length, styleAttrs) {
206 | if(length<=0) console.log("WARNING: non positive upper pin length: "+length);
207 | var pin = svg.group();
208 | pin.rect(lockspec.pinDia, length)
209 | .translate(lockspec.spaces[pinIndex] - lockspec.pinDia/2, -height-length)
210 | .attr(styleAttrs);
211 | return pin;
212 | }
213 | lockview.keyPin = function(svg, lockspec, pinIndex, height, length, styleAttrs) {
214 | if(length<=0) console.log("WARNING: non positive key pin length: "+length);
215 | pinEdgeAngleRadians = lockspec.pinEdgeAngle / 180 * Math.PI;
216 | edgeSlope = Math.tan(pinEdgeAngleRadians);
217 | halfChordLen = Math.sin(pinEdgeAngleRadians) * lockspec.pinTipRadius;
218 | chordOffset = lockspec.pinTipRadius * (1 - Math.cos(pinEdgeAngleRadians));
219 | var pin = svg.group();
220 | pin.path(
221 | "M 0 "+length // top left corner
222 | +" V "+(lockspec.pinDia/2*edgeSlope-chordOffset) // bottom of left straight section
223 | +" L "+(lockspec.pinDia/2-halfChordLen)+" "+(chordOffset) // tip left
224 | +" a "+lockspec.pinTipRadius+" "+lockspec.pinTipRadius+" 0 0 1"+(2*halfChordLen)+" 0" // tip right
225 | +" L "+(lockspec.pinDia)+" "+(lockspec.pinDia/2*edgeSlope-chordOffset) // bottom of right straight section
226 | +" V "+length // top right corner
227 | +" Z")
228 | .fill("#FFFF00")
229 | .attr(styleAttrs)
230 | .rotate(180)
231 | .translate(lockspec.spaces[pinIndex] - lockspec.pinDia/2, -height-length);
232 | return pin;
233 | }
234 | lockview.pinSpring = function(svg, lockspec, pinIndex, length, styleAttrs) {
235 | styleAttrs = styleAttrs || {fill: "none", stroke: 'black', 'stroke-width': 0.1};
236 | spring = svg.group();
237 | spring.path(lockspec.springSVG)
238 | .attr(styleAttrs)
239 | .rotate(90, 0, 0);
240 | spring.translate(lockspec.spaces[pinIndex], -lockspec.plugDia - lockspec.housingPinStackLength)
241 | .scale(lockspec.pinDia*0.152, length*0.0687, 0, 0);
242 | return spring;
243 | }
244 |
245 | lockview.getPinStackHeight = function(lockspec, pinStackShears) {
246 | if(lockspec.constantDriverHeight)
247 | return lockspec.plugDia - lockspec.depths[pinStackShears.slice(-1)] + lockspec.driverHeight;
248 | else
249 | return lockspec.pinStackHeight;
250 | }
251 |
252 | lockview.pinsFromShears = function(svg, lockspec, knownShears, tipHeights) {
253 | tipHeights = tipHeights || Array(lockspec.numPins).fill(lockspec.pinRestHeight);
254 | var pinStacks = [];
255 | for(var i in knownShears) {
256 | var pinStack = [];
257 | sortedShears = knownShears[i].sort((a,b)=>a-b);
258 | if(sortedShears.length > 0 && !isNaN(sortedShears[0])) {
259 | pinStack.push(lockview.keyPin(svg, lockspec, i, tipHeights[i], lockspec.plugDia - lockspec.depths[sortedShears[0]], lockview.style.keyPin));
260 | for(j in sortedShears) {
261 | if(j == 0) continue;
262 | pinStack.push(
263 | lockview.upperPin(svg, lockspec, i,
264 | tipHeights[i] + lockspec.plugDia - lockspec.depths[sortedShears[j-1]],
265 | lockspec.depths[sortedShears[j-1]] - lockspec.depths[sortedShears[j]],
266 | lockview.style.masterWafer
267 | )
268 | );
269 | }
270 | pinStack.push(
271 | lockview.upperPin(svg, lockspec, i,
272 | tipHeights[i] + lockspec.plugDia - lockspec.depths[sortedShears[sortedShears.length-1]],
273 | lockspec.constantDriverHeight
274 | ? lockspec.driverHeight
275 | : lockspec.pinStackHeight - lockspec.plugDia + lockspec.depths[sortedShears[sortedShears.length-1]]
276 | ,
277 | lockview.style.driverPin
278 | )
279 | );
280 | pinStack.push(lockview.pinSpring(svg, lockspec, i, lockspec.plugDia + lockspec.housingPinStackLength - lockview.getPinStackHeight(lockspec, sortedShears) - tipHeights[i]));
281 | } else {
282 | pinStack.push(lockview.keyPin(svg, lockspec, i, -lockspec.pinStackHeight, lockspec.pinStackHeight, lockview.style.unknownPins));
283 | //pinStack.push(svg.rect().attr({visibility: "hidden"}));
284 | pinStack.push(svg.rect().attr({visibility: "hidden"}));
285 | /*pinStack.push(svg
286 | .text("?")
287 | .translate(lockspec.spaces[i] - lockspec.pinDia/2, lockspec.plugDia)
288 | );*/
289 | pinStack.push(lockview.pinSpring(svg, lockspec, i, lockspec.plugDia + lockspec.housingPinStackLength));
290 | }
291 | pinStacks.push(pinStack);
292 | }
293 | return pinStacks;
294 | }
295 | lockview.lockPlug = function(svg, lockspec) {
296 | plug = svg.group();
297 | plug.rect(
298 | lockspec.spaces[0] - lockspec.pinDia/2 - lockspec.pinSideSlop,
299 | lockspec.plugDia-lockspec.keywayHeight
300 | ).translate(0, 0)
301 | .attr(lockview.style.plug);
302 | var i;
303 | for(i=1;ia+
329 | " H "+(v+lockspec.pinDia/2+lockspec.pinSideSlop)
330 | +" v -"+lockspec.housingPinStackLength
331 | +" h -"+(lockspec.pinDia+2*lockspec.pinSideSlop)
332 | +" v "+lockspec.housingPinStackLength
333 | ,"")
334 | +" H 0"
335 | +" V -"+(lockspec.totalLockHeight-lockspec.housingBelowKeywayThickness)
336 | +" H "+lockspec.totalLockLength
337 | +" V "+lockspec.housingBelowKeywayThickness
338 | +" H 0"
339 | +" Z"
340 | ).attr(lockview.style.housing);
341 | }
342 | lockview.ICCollar = function(svg, lockspec, styleAttrs) {
343 | if(!lockspec.icCollar.present) return;
344 | if(lockspec.icCollar.depth<=0) return;
345 | if(lockspec.icCollar.pins.length<=0) return;
346 |
347 |
348 | icStart = Math.min(...lockspec.icCollar.pins);
349 | icEnd = Math.max(...lockspec.icCollar.pins);
350 | spacesAugmented = [2*lockspec.spaces[0]-lockspec.spaces[1]].concat(lockspec.spaces.slice(0,lockspec.numPins)).concat([2*lockspec.spaces[lockspec.numPins-1]-lockspec.spaces[lockspec.numPins-2]]);
351 | collarThickness = lockspec.icCollar.depth*(lockspec.depths[1]-lockspec.depths[2]);
352 | collar = svg.group();
353 |
354 | collar.rect( // Top of collar, start
355 | (spacesAugmented[icStart+1]-spacesAugmented[icStart]-lockspec.pinDia)/2-lockspec.pinSideSlop,
356 | collarThickness
357 | ).translate(
358 | lockspec.spaces[icStart] - (spacesAugmented[icStart+1]-spacesAugmented[icStart])/2,
359 | -lockspec.plugDia - collarThickness - lockspec.pinSideSlop
360 | ).attr(styleAttrs);
361 | for(var i=icStart;i lockspec.housingBelowKeywayThickness ? lockspec.housingBelowKeywayThickness / 2 : collarThickness
380 | ).translate(
381 | lockspec.spaces[icStart]-(spacesAugmented[icStart+1]-spacesAugmented[icStart])/2,
382 | lockspec.pinSideSlop
383 | ).attr(styleAttrs);
384 |
385 |
386 | /*var inGroup = false;
387 | var lastGroupStart = -1;
388 | for(var i=0; i[lockspec.spaces[i],lockspec.depths[v]])
414 | .concat([[lockspec.keyBlankLength + lockspec.cutterWheel.foreLen, lockspec.keyTipIntercept]]);
415 |
416 | var prevProtrusion = 1;
417 | for(var i in cutCoords) {
418 | var protrusion = (cutCoords[i][1] > lockspec.keywayHeight) ? -1 : 1;
419 | var leadingMForPin = leadingM*prevProtrusion;
420 | var laggingMForPin = laggingM*protrusion;
421 | if(i == cutCoords.length - 1) {
422 | laggingM = laggingMForPin = Math.tan(lockspec.keyTipAngle / 180 * Math.PI);
423 | }
424 |
425 | prevPoint = points[points.length - 1];
426 | point0 = ([cutCoords[i][0] - lockspec.cutterWheel.foreLen, lockspec.keywayHeight - cutCoords[i][1]]);
427 | point1 = ([cutCoords[i][0] + lockspec.cutterWheel.aftLen, lockspec.keywayHeight - cutCoords[i][1]]);
428 | intersectionX = (prevPoint[1] - point0[1] - leadingM*prevPoint[0] + laggingM*point0[0]) / (laggingM - leadingM);
429 | if(intersectionX >= prevPoint[0] && intersectionX <= point0[0]) {
430 | intersectionY = leadingMForPin*(intersectionX - prevPoint[0]) + prevPoint[1];
431 | if(intersectionY < 0) {
432 | if(prevPoint[0] !== 0)
433 | points.push([-prevPoint[1]/leadingMForPin + prevPoint[0], 0]);
434 | points.push([-point0[1]/laggingMForPin + point0[0], 0]);
435 | } else {
436 | points.push([intersectionX, intersectionY]);
437 | }
438 | points.push(point0);
439 | } else if(intersectionX <= point0[0]) {
440 | prevPoint[0] = (prevPoint[1] - point0[1])/laggingMForPin + point0[0];
441 | points.push(point0);
442 | } else {
443 | points.push([(point0[1] - prevPoint[1])/leadingMForPin + prevPoint[0], point0[1]]);
444 | }
445 | points.push(point1);
446 | prevProtrusion = protrusion;
447 | }
448 | points.pop();
449 | return points;
450 | }
451 | lockview.pinHeightAtPosition = function(lockspec, code, position) {
452 | profile = lockview.keyProfile(lockspec, code);
453 | return lockspec.keywayHeight - profile
454 | .slice(0, profile.length-1) // remove last element to prep for pairing
455 | .sort((a,b)=>a[0]>b[0])
456 | .map((v,i)=>[profile[i],profile[i+1]]) // get pairs of coords (line segments)
457 | .filter(v=> v[0][0]-lockspec.pinTipRadius < position && v[1][0]+lockspec.pinTipRadius > position) // only look at nearby line segments
458 | .map(function(points){
459 | slope = (points[0][1] - points[1][1]) / (points[0][0] - points[1][0]);
460 | angle = Math.atan2((points[0][1] - points[1][1]), (points[0][0] - points[1][0]));
461 | if(points[1][1] == points[1][1]
462 | && points[0][0]<=position+Math.sin(angle)*lockspec.pinTipRadius
463 | && points[1][0]>=position+Math.sin(angle)*lockspec.pinTipRadius) {
464 | return slope*(position - points[0][0]) + points[0][1] + lockspec.pinTipRadius + 1/Math.cos(angle)*lockspec.pinTipRadius;
465 | //return 100;
466 | } else if(points[0][0]<=position && points[0][0]>position+Math.sin(angle)*lockspec.pinTipRadius) {
467 | return points[0][1] - Math.sqrt(lockspec.pinTipRadius**2 - (points[0][0] - position)**2) + lockspec.pinTipRadius;
468 | } else if(points[1][0]>=position && points[1][0]Math.min(v,a), lockspec.keywayHeight - lockspec.pinRestHeight);
474 | }
475 | lockview.drawKey = function(svg, lockspec, code, text) {
476 | if(!lockspec.keyBlankLength) lockspec.keyBlankLength = lockspec.keywayLength;
477 | key = svg.group();
478 | key.path(
479 | lockview.keyProfile(lockspec, code).map(
480 | (v,i)=>
481 | " "+(i?"L":"M")+" "+v[0]+" "+v[1]
482 | )
483 | .join("")
484 | .concat(" V "+(lockspec.keywayHeight - lockspec.keyTipClipIntercept))
485 | .concat(" L "+(lockspec.keyBlankLength - lockspec.keyTipClipIntercept * Math.tan(lockspec.keyTipClipAngle / 180 * Math.PI))+" "+lockspec.keywayHeight)
486 | .concat(" H 0")
487 | .concat(lockspec.keyShoulderHeadSVG)
488 | )
489 | .stroke("black")
490 | .fill("yellow")
491 | .attr({"fill-rule":"nonzero"});
492 |
493 | text = text || "";
494 | key.text(text)
495 | .font({ fill: '#000000', family: 'sans-serif', size: '140pt', anchor: 'middle'})
496 | .rotate(-90)
497 | .translate(-350, -30)
498 |
499 | key.translate(0, -lockspec.keywayHeight);
500 | //key.polyline([...Array(lockspec.keyBlankLength+100).keys()].map(v=>v+","+(2*335-lockview.pinHeightAtPosition(lockspec, code,v))).join(" ")).fill('none').stroke({width: 2, color:'green'}).translate(0,-lockspec.keywayHeight);
501 | return key;
502 | }
503 |
504 | lockview.raisePins = function(drawingInfo, heights) {
505 | //lockspec.pinStackHeight
506 | for(var i in drawingInfo.svgElements.pins) {
507 | var spring = drawingInfo.svgElements.pins[i][drawingInfo.svgElements.pins[i].length-1];
508 | var driver = drawingInfo.svgElements.pins[i][drawingInfo.svgElements.pins[i].length-2];
509 |
510 | pinStackHeight = lockview.getPinStackHeight(drawingInfo.lockspec, drawingInfo.lockShears[i]);
511 |
512 | heightDiff = heights[i] + driver.y() + pinStackHeight;
513 | var pins = drawingInfo.svgElements.pins[i].slice(0, drawingInfo.svgElements.pins[i].length - 2);
514 | for(j in pins) {
515 | pins[j].translate(0, -heightDiff);
516 | }
517 | if(heights[i]<500) { // Why does this work???
518 | spring.scale(1, (drawingInfo.lockspec.plugDia + drawingInfo.lockspec.housingPinStackLength - pinStackHeight - heights[i]) / (drawingInfo.lockspec.plugDia + drawingInfo.lockspec.housingPinStackLength + driver.y()), 0, 0);
519 | }
520 | driver.y(-heights[i] - pinStackHeight);
521 | }
522 | }
523 | lockview.raisePinsByKey = function(drawingInfo, keyPos) {
524 | lockview.raisePins(drawingInfo, drawingInfo.lockspec.spaces.slice(0, drawingInfo.lockspec.numPins).map(v=>lockview.pinHeightAtPosition(drawingInfo.lockspec, drawingInfo.keyCode, v+keyPos)));
525 | }
526 | lockview.keyIn = function(drawingInfo) {
527 | slider = document.getElementById(drawingInfo.containerId+"_slider");
528 | drawingInfo.activeIntervals.forEach(v=>clearInterval(v));
529 | drawingInfo.activeIntervals = [];
530 | drawingInfo.activeIntervals.push(intervalKeyIn = setInterval(() => {
531 | slider.value=parseInt(slider.value)+10;
532 | slider.dispatchEvent(new Event("input"));
533 | if(slider.value >= parseInt(slider.max)) {
534 | clearInterval(intervalKeyIn);
535 | if(drawingInfo.activeIntervals.includes(intervalKeyIn)) drawingInfo.activeIntervals.splice(drawingInfo.activeIntervals.indexOf(intervalKeyIn), 1);
536 | }
537 | }, 10));
538 | }
539 | lockview.keyOut = function(drawingInfo) {
540 | slider = document.getElementById(drawingInfo.containerId+"_slider");
541 | drawingInfo.activeIntervals.forEach(v=>clearInterval(v));
542 | drawingInfo.activeIntervals = [];
543 | drawingInfo.activeIntervals.push(intervalKeyOut = setInterval(() => {
544 | slider.value=parseInt(slider.value)-10;
545 | slider.dispatchEvent(new Event("input"));
546 | if(slider.value <= 0) {
547 | clearInterval(intervalKeyOut);
548 | if(drawingInfo.activeIntervals.includes(intervalKeyOut)) drawingInfo.activeIntervals.splice(drawingInfo.activeIntervals.indexOf(intervalKeyOut), 1);
549 | }
550 | }, 10));
551 | }
552 | lockview.redrawKey = function(newCode, newStamp) {
553 | keyX = this.svgElements.key.x();
554 | this.svgElements.key.remove();
555 | this.svgElements.key = lockview.drawKey(this.svgElements.scaled, this.lockspec, newCode, newStamp);
556 | this.svgElements.key.x(keyX);
557 | this.keyCode = newCode;
558 | this.keyStamp = newStamp;
559 | this.moveKey(this.keyPosition);
560 | this.svgElements.side.front();
561 | if(this.viewopts.controls.setCode) {
562 | [...Array(this.lockspec.numCuts ? this.lockspec.numCuts : this.lockspec.numPins).keys()].forEach(j=>document.getElementById(this.containerId+"_cut_"+j).value = newCode[j]);
563 | }
564 | this.events.keyCodeChanged(newCode);
565 | }
566 | lockview.redrawPins = function(newShears) {
567 | if(newShears)
568 | this.lockShears = newShears;
569 | this.svgElements.pins.forEach(stack=>stack.forEach(pin=>pin.remove()));
570 | this.svgElements.pins = lockview.pinsFromShears(this.svgElements.scaled, this.lockspec, this.lockShears, Array(this.lockspec.numPins).fill(this.lockspec.pinRestHeight));
571 | this.moveKey(this.keyPosition);
572 | this.events.lockShearsChanged(newShears);
573 | }
574 | lockview.calcBinding = function(code, shears, lockspec, unknownPinstackBinds) {
575 | cutCode = code.slice(0, lockspec.numCuts ? lockspec.numCuts : Infinity)
576 | if(shears.length > cutCode.length) return [...Array(shears.length - cutCode.length).keys()].map(x=>x+cutCode.length);
577 | return [...shears.keys()].filter(i=>!shears[i].includes(code[i]) && ((shears[i].length > 0 && !isNaN(shears[i][0])) || unknownPinstackBinds));
578 | }
579 | lockview.calcICBinding = function(code, shears, collarPins, collarDepth) {
580 | return [...Array(Math.min(shears.length, code.length)).keys()].filter(i=>collarPins.includes(i) && !shears[i].includes(code[i]+collarDepth) && ((shears[i].length > 0 && !isNaN(shears[i][0])) || unknownPinstackBinds));
581 | }
582 | lockview.tryTurn = function(drawingInfo) {
583 | drawingInfo = drawingInfo || this;
584 | binding = lockview.calcBinding(drawingInfo.keyCode, drawingInfo.lockShears, drawingInfo.lockspec, drawingInfo.viewopts.unknownPinstackBinds);
585 | drawingInfo.clearShearMarks();
586 | if(Math.abs(drawingInfo.svgElements.key.x() + drawingInfo.svgElements.key.width() - drawingInfo.lockspec.keyBlankLength) > 5) {
587 | document.getElementById(drawingInfo.containerId+"_txtTryTurn").innerHTML = "Key not inserted.";
588 | return [];
589 | }
590 | textStatus = "Key does not turn.";
591 | for(var i in binding) {
592 | drawingInfo.bindingSVGLines.push(drawingInfo.svgElements.scaled
593 | .line(0, 0, drawingInfo.lockspec.pinDia, 0)
594 | .translate(drawingInfo.lockspec.spaces[binding[i]] - drawingInfo.lockspec.pinDia/2, -drawingInfo.lockspec.plugDia)
595 | .stroke({color: "#FFAA00", width: 30, linecap:'round'})
596 | .attr({'stroke-opacity':"75%"}));
597 | }
598 | if(binding.length == 0) {
599 | drawingInfo.bindingSVGLines.push(drawingInfo.svgElements.scaled
600 | .line(0, 0, drawingInfo.lockspec.spaces[drawingInfo.lockspec.numCuts-1] - drawingInfo.lockspec.spaces[0] + drawingInfo.lockspec.pinDia, 0)
601 | .translate(drawingInfo.lockspec.spaces[0] - drawingInfo.lockspec.pinDia/2, -drawingInfo.lockspec.plugDia)
602 | .stroke({color: "#88FF00", width: 30, linecap:'round'})
603 | .attr({'stroke-opacity':"70%"}));
604 | textStatus = "Lock opens."
605 | }
606 | if(drawingInfo.lockspec.icCollar.present) {
607 | icbinding = lockview.calcICBinding(drawingInfo.keyCode, drawingInfo.lockShears, drawingInfo.lockspec.icCollar.pins, drawingInfo.lockspec.icCollar.depth);
608 | collarThickness = drawingInfo.lockspec.icCollar.depth*(drawingInfo.lockspec.depths[1]-drawingInfo.lockspec.depths[2]);
609 | for(var i in icbinding) {
610 | drawingInfo.bindingSVGLines.push(drawingInfo.svgElements.scaled
611 | .line(0, 0, drawingInfo.lockspec.pinDia, 0)
612 | .translate(drawingInfo.lockspec.spaces[icbinding[i]] - drawingInfo.lockspec.pinDia/2, -drawingInfo.lockspec.plugDia - collarThickness)
613 | .stroke({color: "#FFAA00", width: 30, linecap:'round'})
614 | .attr({'stroke-opacity':"75%"}));
615 | }
616 | if(icbinding.length == 0 && binding.filter(v=>!drawingInfo.lockspec.icCollar.pins.includes(v)).length == 0) {
617 | drawingInfo.bindingSVGLines.push(drawingInfo.svgElements.scaled
618 | .line(0, 0, drawingInfo.lockspec.spaces[Math.max(...drawingInfo.lockspec.icCollar.pins)] - drawingInfo.lockspec.spaces[Math.min(...drawingInfo.lockspec.icCollar.pins)] + drawingInfo.lockspec.pinDia, 0)
619 | .translate(drawingInfo.lockspec.spaces[Math.min(...drawingInfo.lockspec.icCollar.pins)] - drawingInfo.lockspec.pinDia/2, -drawingInfo.lockspec.plugDia - collarThickness)
620 | .stroke({color: "#88FF00", width: 30, linecap:'round'})
621 | .attr({'stroke-opacity':"70%"}));
622 | textStatus = (binding.length == 0) ? "IC Collar spins freely" : "IC Core Released";
623 | }
624 | }
625 | drawingInfo.events.tryTurn(textStatus);
626 | document.getElementById(drawingInfo.containerId+"_txtTryTurn").innerHTML = textStatus;
627 | if(!drawingInfo.viewopts.cutaway) {
628 | drawingInfo.clearShearMarks();
629 | }
630 | return drawingInfo.bindingSVGLines;
631 | }
632 | lockview.impression = function(drawingInfo) {
633 | drawingInfo = drawingInfo || this;
634 | lockview.tryTurn(drawingInfo);
635 | if(Math.abs(drawingInfo.svgElements.key.x() + drawingInfo.svgElements.key.width() - drawingInfo.lockspec.keyBlankLength) > 5) {
636 | return;
637 | }
638 |
639 | binding = lockview.calcBinding(drawingInfo.keyCode, drawingInfo.lockShears, drawingInfo.lockspec, drawingInfo.viewopts.unknownPinstackBinds);
640 | if(binding.length == 0) return;
641 | bindingPin = binding[0];
642 | drawingInfo.events.impression([bindingPin]);
643 | return drawingInfo.svgElements.key
644 | .line(0, 0, drawingInfo.lockspec.pinTipRadius*0.7, 0)
645 | .translate(
646 | drawingInfo.lockspec.spaces[bindingPin] - drawingInfo.lockspec.pinTipRadius*0.35,
647 | drawingInfo.lockspec.keywayHeight - drawingInfo.lockspec.depths[drawingInfo.keyCode[bindingPin]] + 6
648 | )
649 | .stroke({color: "#77EE00", width: 12, linecap:'round'});
650 | }
651 | lockview.codeFromString = function(str) {
652 | candidate=str.split(",");
653 | if(candidate.length<2) {
654 | candidate=str.split(" ");
655 | if(candidate.length<2) {
656 | candidate=str.split("");
657 | }
658 | }
659 | return candidate.map(function(x) {return parseInt(x)-1;});
660 | }
661 | lockview.clearMarks = function(drawingInfo) {
662 | drawingInfo.clearShearMarks();
663 | }
664 |
665 | lockview.addLock = function(containerId, lockspec, viewopts, keyCode, lockShears, keyStamp) {
666 | if(!lockspec.numCuts) lockspec.numCuts = lockspec.numPins;
667 | if(keyCode.length !== (lockspec.numCuts ? lockspec.numCuts : lockspec.numPins)) console.log("WARNING: key code length does not match number of pins.");
668 | if(lockShears.length !== lockspec.numPins) console.log("WARNING: lock shears array length does not match number of pins.");
669 | if(!document.getElementById(containerId)) console.log("WARNING: ID '"+containerId+"' not found in the document.");
670 | document.getElementById(containerId).innerHTML =
671 | ((viewopts.keyBank && viewopts.keyBank.length) ? (
672 | "
"
673 | ) : "")
674 | +((viewopts.controls.setCode) ? (
675 | ""
676 | +"Key Code: "+(keyCode.slice(0, lockspec.numCuts).map((v,i)=>"Cut "+(i+1)+": ").join(""))+"
"
677 | ) : "")
678 | +((viewopts.controls.setShearLines) ? (
679 | ""
680 | +"Shear Lines: "+(lockShears.map((v,i)=>"Pin "+(i+1)+": ").join(""))+"
"
681 | ) : "")
682 | +((viewopts.controls.setCode || viewopts.controls.setShearLines) ? (" ") : "")
683 | +((viewopts.controls.moveKey && viewopts.keyRemovable) ? (
684 | "Key in "
685 | +"Key out | "
686 | ) : "")
687 | +((viewopts.controls.tryTurn) ? ("Try Turning Key ") : "")
688 | +((viewopts.controls.impression) ? ("Impression ") : "")
689 | +((viewopts.controls.tryTurn || viewopts.controls.impression) ? (" ") : "")
690 | +((viewopts.controls.toggleCutaway) ? (" | Cutaway Lock ") : "")
691 | +((viewopts.controls.moveKey && viewopts.keyRemovable) ? (" ") : "")
692 | ;
693 | var draw = SVG().addTo('#'+containerId).size(viewopts.width, viewopts.width*0.55);
694 | var scaled = draw.group();
695 |
696 | var drawingInfo = {
697 | containerId: containerId,
698 | lockspec: lockspec,
699 | viewopts: viewopts,
700 | keyCode: keyCode,
701 | lockShears: lockShears,
702 | keyStamp: keyStamp,
703 | svgElements: {
704 | top: draw,
705 | scaled: scaled
706 | },
707 | redrawKey: lockview.redrawKey,
708 | redrawPins: lockview.redrawPins,
709 | bindingSVGLines: [],
710 | tryTurn: lockview.tryTurn,
711 | impression: lockview.impression,
712 | clearShearMarks: function(){this.bindingSVGLines.map(v=>v.remove()); this.bindingSVGLines=[];},
713 | keyPosition: 0,
714 | events: {
715 | keyMoved: function(position) {},
716 | keyCodeChanged: function(newCode) {},
717 | lockShearsChanged: function(newShears) {},
718 | keyFromBankSelected: function(bankItem) {},
719 | tryTurn: function(result) {},
720 | impression: function(markPositions) {},
721 | cutawayChanged: function(isCutaway) {}
722 | },
723 | moveKey: function(position) {
724 | this.keyPosition = position;
725 | lockview.raisePinsByKey(drawingInfo, position);
726 | drawingInfo.svgElements.key.x(lockspec.keyBlankLength - position - drawingInfo.svgElements.key.width());
727 | drawingInfo.clearShearMarks();
728 | this.events.keyMoved(position);
729 | },
730 | activeIntervals: []
731 | };
732 | document.getElementById(containerId).dataset.drawingInfo = drawingInfo;
733 |
734 | lockview.lockPlug(scaled, lockspec);
735 | lockview.lockHousing(scaled, lockspec);
736 | lockview.ICCollar(scaled, lockspec, viewopts.style.icCollar);
737 | drawingInfo.svgElements.pins = lockview.pinsFromShears(scaled, lockspec, lockShears, Array(lockspec.numPins).fill(lockspec.pinRestHeight));
738 | drawingInfo.svgElements.key = lockview.drawKey(scaled, lockspec, keyCode, keyStamp);
739 | drawingInfo.svgElements.side = scaled
740 | .rect(lockspec.totalLockLength, lockspec.totalLockHeight)
741 | .translate(0, lockspec.housingBelowKeywayThickness - lockspec.totalLockHeight)
742 | .attr(lockview.shallowcopy_change_vals(viewopts.style.housing, {visibility: viewopts.cutaway ? "hidden" : "visible"}));
743 |
744 | if(viewopts.keyRemovable) {
745 | drawingInfo.scale = viewopts.width / (drawingInfo.svgElements.key.width() + lockspec.totalLockLength);
746 | scaled.scale(drawingInfo.scale, 0, 0).translate(drawingInfo.svgElements.key.width() * drawingInfo.scale, lockspec.totalLockHeight * drawingInfo.scale);
747 | draw.size(viewopts.width, Math.max(lockspec.totalLockHeight + lockspec.housingBelowKeywayThickness, lockspec.keywayHeight - drawingInfo.svgElements.key.y()+drawingInfo.svgElements.key.height()) * drawingInfo.scale);
748 | } else {
749 | drawingInfo.scale = viewopts.width / (drawingInfo.svgElements.key.width() - lockspec.keywayLength + lockspec.totalLockLength);
750 | scaled.scale(drawingInfo.scale, 0, 0).translate((drawingInfo.svgElements.key.width() - lockspec.keywayLength) * drawingInfo.scale, lockspec.totalLockHeight * drawingInfo.scale);
751 | draw.size(viewopts.width, Math.max(lockspec.totalLockHeight + lockspec.housingBelowKeywayThickness, lockspec.keywayHeight - drawingInfo.svgElements.key.y()+drawingInfo.svgElements.key.height()) * drawingInfo.scale);
752 | }
753 |
754 | if(viewopts.controls.tryTurn) document.getElementById(containerId+"_btnTryTurn").addEventListener("click", function() {drawingInfo.tryTurn()});
755 | if(viewopts.controls.impression) document.getElementById(containerId+"_btnImpression").addEventListener("click", function() {drawingInfo.impression()});
756 | if(viewopts.controls.toggleCutaway) document.getElementById(containerId+"_chkCutaway").addEventListener("change", function() {
757 | drawingInfo.svgElements.side.attr({visibility: this.checked ? "hidden" : "visible"});
758 | drawingInfo.viewopts.cutaway = this.checked;
759 | drawingInfo.events.cutawayChanged(this.checked);
760 | });
761 |
762 | for(var i=0; iparseInt(document.getElementById(containerId+"_cut_"+i).value-lockspec.depthIndexOffset)), drawingInfo.keyStamp)
766 | });
767 | if(viewopts.controls.setShearLines && idocument.getElementById(containerId+"_shear_"+i).value.split(",").map(v=>parseInt(v))))
770 | });
771 | }
772 | if(viewopts.controls.moveKey && viewopts.keyRemovable) {
773 | document.getElementById(containerId+"_btnKeyOut").addEventListener("click", function() {lockview.keyOut(drawingInfo)});
774 | document.getElementById(containerId+"_btnKeyIn").addEventListener("click", function() {lockview.keyIn(drawingInfo)});
775 | document.getElementById(containerId+"_slider").max = lockspec.keyBlankLength;
776 | document.getElementById(containerId+"_slider").addEventListener("input", function() {drawingInfo.moveKey(lockspec.keyBlankLength - this.value)});
777 | document.getElementById(containerId+"_slider").value=0;
778 | document.getElementById(containerId+"_slider").dispatchEvent(new Event("input"));
779 | } else {
780 | drawingInfo.moveKey(0);
781 | }
782 |
783 | if(viewopts.keyBank && viewopts.keyBank.length) {
784 | var kbDiv = document.getElementById(containerId+"_keyBank");
785 | for(var i in viewopts.keyBank) {
786 | kbDiv.innerHTML += "
"+viewopts.keyBank[i].name+" ";
787 | }
788 | for(var i in viewopts.keyBank) {
789 | viewopts.keyBank[i].SVGNode = SVG().addTo('#'+containerId+"_keyBank_SVGContainer_"+i).size(100, 60);
790 | viewopts.keyBank[i].SVGGroup = viewopts.keyBank[i].SVGNode.group()
791 | viewopts.keyBank[i].SVGKey = lockview.drawKey(viewopts.keyBank[i].SVGGroup, lockspec, viewopts.keyBank[i].code, viewopts.keyBank[i].stamp)
792 | viewopts.keyBank[i].SVGKey
793 | .translate(
794 | viewopts.keyBank[i].SVGGroup.width() - lockspec.keyBlankLength,
795 | viewopts.keyBank[i].SVGGroup.height() + viewopts.keyBank[i].SVGKey.y() + 100
796 | )
797 | viewopts.keyBank[i].SVGGroup.scale(100/viewopts.keyBank[i].SVGGroup.width(), 0, 0);
798 | document.getElementById(containerId+"_keyBank_"+i).dataset.bankIndex = i;
799 | document.getElementById(containerId+"_keyBank_"+i).dataset.stamp = viewopts.keyBank[i].stamp;
800 | document.getElementById(containerId+"_keyBank_"+i).dataset.code = JSON.stringify(viewopts.keyBank[i].code);
801 | if(viewopts.keyBank[i].amendLockspec) document.getElementById(containerId+"_keyBank_"+i).dataset.amendLockspec = JSON.stringify(viewopts.keyBank[i].amendLockspec);
802 | document.getElementById(containerId+"_keyBank_"+i).addEventListener("click", function() {
803 | var code = JSON.parse(this.dataset.code);
804 | if(this.dataset.amendLockspec) drawingInfo.lockspec = drawingInfo.lockspec.amend(JSON.parse(this.dataset.amendLockspec));
805 | drawingInfo.redrawKey(code, this.dataset.stamp);
806 | drawingInfo.events.keyFromBankSelected(drawingInfo.viewopts.keyBank[this.dataset.bankIndex]);
807 | });
808 | }
809 | }
810 |
811 | return drawingInfo;
812 | }
--------------------------------------------------------------------------------