├── 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 | 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 | 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 | 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"),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 | "" 685 | +" | " 686 | ) : "") 687 | +((viewopts.controls.tryTurn) ? (" ") : "") 688 | +((viewopts.controls.impression) ? (" ") : "") 689 | +((viewopts.controls.tryTurn || viewopts.controls.impression) ? ("") : "") 690 | +((viewopts.controls.toggleCutaway) ? (" |
") : "") 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 += ""; 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 | } --------------------------------------------------------------------------------