├── img ├── favicon.ico ├── led-icons │ ├── 1license.txt │ ├── connect.png │ ├── disconnect.png │ └── page_white_text.png └── bootstrap │ ├── glyphicons-halflings.png │ └── glyphicons-halflings-white.png ├── js ├── sorttable.js ├── gcodeviewer │ ├── ui.js │ ├── gcode-parser.js │ ├── Three.OrbitControls.js │ ├── gcode-model.js │ └── Three.TrackballControls.js ├── thingiview │ ├── plane.js │ ├── binaryReader.js │ ├── thingiloader.js │ └── thingiview.js ├── jquery.mousewheel.js ├── bootbox.js ├── modernizr.js ├── bootstrap.js └── sugar.js ├── testfiles └── demo_file.stl ├── .gitignore ├── makedist.sh ├── package.json ├── css ├── style.css ├── layout-default-latest.css └── bootstrap │ └── bootstrap-responsive.css ├── README.md ├── index.html └── main.js /img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garyhodgson/3D-Printroom/HEAD/img/favicon.ico -------------------------------------------------------------------------------- /js/sorttable.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garyhodgson/3D-Printroom/HEAD/js/sorttable.js -------------------------------------------------------------------------------- /testfiles/demo_file.stl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garyhodgson/3D-Printroom/HEAD/testfiles/demo_file.stl -------------------------------------------------------------------------------- /img/led-icons/1license.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garyhodgson/3D-Printroom/HEAD/img/led-icons/1license.txt -------------------------------------------------------------------------------- /img/led-icons/connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garyhodgson/3D-Printroom/HEAD/img/led-icons/connect.png -------------------------------------------------------------------------------- /img/led-icons/disconnect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garyhodgson/3D-Printroom/HEAD/img/led-icons/disconnect.png -------------------------------------------------------------------------------- /img/led-icons/page_white_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garyhodgson/3D-Printroom/HEAD/img/led-icons/page_white_text.png -------------------------------------------------------------------------------- /img/bootstrap/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garyhodgson/3D-Printroom/HEAD/img/bootstrap/glyphicons-halflings.png -------------------------------------------------------------------------------- /img/bootstrap/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/garyhodgson/3D-Printroom/HEAD/img/bootstrap/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules/nstore 3 | node_modules/jade 4 | node_modules/underscore 5 | node_modules/humanize 6 | node_modules/moment 7 | node_modules/.bin 8 | -------------------------------------------------------------------------------- /makedist.sh: -------------------------------------------------------------------------------- 1 | # Packages the app into a windows binary. 2 | 3 | if [ ! -d "dist" ]; then 4 | echo "This script expects a dist directory which contains the node-webkit files for packaging to exist." 5 | echo "See also: https://github.com/rogerwang/node-webkit/wiki/How-to-package-and-distribute-your-apps" 6 | echo "" 7 | echo "Aborting." 8 | exit -1 9 | fi 10 | 11 | zip -r dist/3D-printroom.nw index.html js css img node_modules package.json README.md testfiles main.js 12 | cat dist/nw.exe dist/3D-printroom.nw > dist/3D-Printroom.exe 13 | chmod +x dist/3D-Printroom.exe 14 | cd dist 15 | zip -r 3D-Printroom-distribution.zip 3D-Printroom.exe *.dll nw.pak 16 | cd .. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "3d-Printroom", 3 | "description": "3D printer file explorer.", 4 | "version": "0.1", 5 | "maintainers":[{ 6 | "name": "Gary Hodgson", 7 | "email": "contact@garyhodgson.com", 8 | "web": "http://garyhodgson.com" 9 | }], 10 | "licenses": [{ 11 | "type": "GPLv3", 12 | "url": "http://www.example.com/licenses/gpl.html" 13 | }], 14 | "repositories": [{ 15 | "type": "git", 16 | "url": "https://github.com/garyhodgson/3D-Printroom" 17 | }], 18 | "main": "index.html", 19 | "window": { 20 | "toolbar": false, 21 | "width": 800, 22 | "height": 600 23 | }, 24 | "dependencies": { 25 | "filestat.js": "*", 26 | "humanize": "0.0.x", 27 | "jade": "0.28.x", 28 | "moment": "1.7.x", 29 | "nstore": "0.5.x", 30 | "sourcefolder_view.js": "*", 31 | "targetfolder_view.js": "*", 32 | "underscore": "1.4.x" 33 | } 34 | } -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-user-select: none; 3 | } 4 | 5 | html, body { 6 | background: #fff; 7 | width: 100%; 8 | height: 100%; 9 | padding: 0; 10 | margin: 0; 11 | overflow: auto; /* when page gets too small */ 12 | } 13 | 14 | 15 | #container { 16 | height: 100%; 17 | width: 100%; 18 | } 19 | 20 | #sourceviewer { 21 | width: 100%; 22 | height: 100%; 23 | min-height: 300px; 24 | } 25 | 26 | #gcodeviewer { 27 | width: 100%; 28 | height: 100%; 29 | background-color: #000000; 30 | } 31 | 32 | #sourcedircontainer { 33 | width: 100%; 34 | height: 100%; 35 | } 36 | 37 | .over { 38 | border: 2px dashed #000; 39 | } 40 | 41 | 42 | 43 | .filepath { 44 | cursor: default; 45 | } 46 | 47 | .filepath.focus { 48 | background-color: #A7CADB; 49 | color: white; 50 | } 51 | 52 | .filepath .filename { 53 | width: 80px; 54 | word-wrap: break-word; 55 | } 56 | 57 | table.sortable thead { 58 | cursor: default; 59 | } 60 | 61 | .ui-layout-resizer-west { border-width: 0 1px; } 62 | .ui-layout-toggler-west { border-width: 0; } 63 | .ui-layout-toggler-west div { 64 | width: 8px; 65 | height: 35px; /* 3x 35 = 105 total height */ 66 | } 67 | 68 | .ui-layout-toggler-west .btnTogglerRight { background: #999; } 69 | .ui-layout-toggler-west .btnTogglerCenter { background: #000; } 70 | .ui-layout-toggler-west .btnTogglerLeft { background: #999; } 71 | -------------------------------------------------------------------------------- /js/gcodeviewer/ui.js: -------------------------------------------------------------------------------- 1 | unction error(msg) { 2 | alert('Error: ' + msg); 3 | } 4 | 5 | function loadFile(path, callback /* function(contents) */) { 6 | $.get(path, null, callback, 'text').error(function() { error("loading file") }); 7 | } 8 | 9 | var scene = null; 10 | var object = new THREE.Object3D(); 11 | 12 | function openGCodeFromPath(path) { 13 | if (object) { 14 | scene.remove(object); 15 | } 16 | loadFile(path, function(gcode) { 17 | object = createObjectFromGCode(gcode); 18 | scene.add(object); 19 | }); 20 | } 21 | 22 | function openGCodeFromText(gcode) { 23 | if (object) { 24 | scene.remove(object); 25 | } 26 | object = createObjectFromGCode(gcode); 27 | scene.add(object); 28 | } 29 | 30 | 31 | $(function() { 32 | 33 | // Drop files from desktop onto main page to import them. 34 | $('#gcodeviewer').on('dragover', function(event) { 35 | event.stopPropagation(); 36 | event.preventDefault(); 37 | event.originalEvent.dataTransfer.dropEffect = 'copy' 38 | }).on('drop', function(event) { 39 | event.stopPropagation(); 40 | event.preventDefault(); 41 | var files = event.originalEvent.dataTransfer.files; 42 | if (files.length > 0) { 43 | var reader = new FileReader(); 44 | reader.onload = function() { 45 | openGCodeFromText(reader.result); 46 | }; 47 | reader.readAsText(files[0]); 48 | } 49 | }); 50 | 51 | scene = createScene($('#gcodeviewer')); 52 | 53 | scene.add(object); 54 | 55 | }); 56 | -------------------------------------------------------------------------------- /js/thingiview/plane.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mr.doob / http://mrdoob.com/ 3 | * based on http://papervision3d.googlecode.com/svn/trunk/as3/trunk/src/org/papervision3d/objects/primitives/Plane.as 4 | */ 5 | 6 | var Plane = function ( width, height, segments_width, segments_height ) { 7 | 8 | THREE_32.Geometry.call( this ); 9 | 10 | var ix, iy, 11 | width_half = width / 2, 12 | height_half = height / 2, 13 | gridX = segments_width || 1, 14 | gridY = segments_height || 1, 15 | gridX1 = gridX + 1, 16 | gridY1 = gridY + 1, 17 | segment_width = width / gridX, 18 | segment_height = height / gridY; 19 | 20 | 21 | for( iy = 0; iy < gridY1; iy++ ) { 22 | 23 | for( ix = 0; ix < gridX1; ix++ ) { 24 | 25 | var x = ix * segment_width - width_half; 26 | var y = iy * segment_height - height_half; 27 | 28 | this.vertices.push( new THREE_32.Vertex( new THREE_32.Vector3( x, - y, 0 ) ) ); 29 | 30 | } 31 | 32 | } 33 | 34 | for( iy = 0; iy < gridY; iy++ ) { 35 | 36 | for( ix = 0; ix < gridX; ix++ ) { 37 | 38 | var a = ix + gridX1 * iy; 39 | var b = ix + gridX1 * ( iy + 1 ); 40 | var c = ( ix + 1 ) + gridX1 * ( iy + 1 ); 41 | var d = ( ix + 1 ) + gridX1 * iy; 42 | 43 | this.faces.push( new THREE_32.Face4( a, b, c, d ) ); 44 | this.uvs.push( [ 45 | new THREE_32.UV( ix / gridX, iy / gridY ), 46 | new THREE_32.UV( ix / gridX, ( iy + 1 ) / gridY ), 47 | new THREE_32.UV( ( ix + 1 ) / gridX, ( iy + 1 ) / gridY ), 48 | new THREE_32.UV( ( ix + 1 ) / gridX, iy / gridY ) 49 | ] ); 50 | 51 | } 52 | 53 | } 54 | 55 | this.computeCentroids(); 56 | this.computeFaceNormals(); 57 | this.sortFacesByMaterial(); 58 | 59 | }; 60 | 61 | Plane.prototype = new THREE_32.Geometry(); 62 | Plane.prototype.constructor = Plane; 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 3D Printroom 2 | 3 | **Warning: Work in Progress!** 4 | 5 | ## Synopsis 6 | A [node-webkit](https://github.com/rogerwang/node-webkit) application that acts as a simple 3D printer file explorer. 7 | 8 | ## Grand Plan 9 | 10 | Using file explorers to manage 3D printer files is a bit clunky. There are no previews and the metadata held in most gcode files is not visible. This leads to long filenames consisting of some key attributes, e.g. ```directdrive_bowden_holder.PLA.slow_PLA_prusa.0.24.gcode```. Once a folder contains more than a few files it becomes tricky to manage. 11 | 12 | This app is a little side project which attempts to address this problem, inspired by Adobe Lightroom (which does an admirable job of organising photos). Rather than attempt a modification of Windows Explorer to handle 3D printer files I decided to make something more portable. I chose node-webkit so I could develop with web tech and still use node.js in order to access local filesystems. 13 | 14 | ## Screen shot 15 | 16 | ![screenshot](https://dl.dropbox.com/u/22464622/3D-Printroom/screenshot.png) 17 | 18 | ## Features 19 | 20 | * STL preview 21 | * Gcode preview 22 | * Gcode attribute display (Slic3r only) 23 | 24 | ## Future Features 25 | 26 | * Ad-hoc collections 27 | * Remember & browse folders 28 | * Folder sync 29 | * Additional metadata, e.g. ratings, comments 30 | * Send to slicer 31 | * Send to printer host 32 | 33 | 34 | ## Running 35 | At this stage the best bet is to follow the hacking instructions below and run the latest version using node-webkit directly. If you want to simply play with a version there is a Windows binary here: [3D-Printroom-distribution.zip](https://dl.dropbox.com/u/22464622/3D-Printroom/3D-Printroom-distribution.zip). (Linux and Mac will have to sadly follow the hacking instructions below.) 36 | 37 | * Download and extract zip file. 38 | * Double click 3D-Printroom.exe. 39 | * An example folder is already included. 40 | * Drag and Drop a folder containing your STL and Gcode files to the top left window. 41 | 42 | ## Hacking 43 | * Clone the project. 44 | * Install node-webkit. 45 | * Install the node dependencies. 46 | * humanize 47 | * jade 48 | * moment 49 | * nstore 50 | * underscore 51 | * Run with ```nw.exe --disable-application-cache 3D-Printroom``` (where '3D-Printroom' is the project folder) 52 | 53 | ## Credits 54 | * [Thingiview.js](https://github.com/tbuser/thingiview.js) 55 | * [Gcode-Viewer](https://github.com/joewalnes/gcode-viewer) 56 | * [Node-webkit](https://github.com/rogerwang/node-webkit) 57 | 58 | ## License 59 | [GPLv3](http://www.example.com/licenses/gpl.html) -------------------------------------------------------------------------------- /js/jquery.mousewheel.js: -------------------------------------------------------------------------------- 1 | /*! Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net) 2 | * Licensed under the MIT License (LICENSE.txt). 3 | * 4 | * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers. 5 | * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix. 6 | * Thanks to: Seamus Leahy for adding deltaX and deltaY 7 | * 8 | * Version: 3.0.6 9 | * 10 | * Requires: 1.2.2+ 11 | */ 12 | 13 | (function($) { 14 | 15 | var types = ['DOMMouseScroll', 'mousewheel']; 16 | 17 | if ($.event.fixHooks) { 18 | for ( var i=types.length; i; ) { 19 | $.event.fixHooks[ types[--i] ] = $.event.mouseHooks; 20 | } 21 | } 22 | 23 | $.event.special.mousewheel = { 24 | setup: function() { 25 | if ( this.addEventListener ) { 26 | for ( var i=types.length; i; ) { 27 | this.addEventListener( types[--i], handler, false ); 28 | } 29 | } else { 30 | this.onmousewheel = handler; 31 | } 32 | }, 33 | 34 | teardown: function() { 35 | if ( this.removeEventListener ) { 36 | for ( var i=types.length; i; ) { 37 | this.removeEventListener( types[--i], handler, false ); 38 | } 39 | } else { 40 | this.onmousewheel = null; 41 | } 42 | } 43 | }; 44 | 45 | $.fn.extend({ 46 | mousewheel: function(fn) { 47 | return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel"); 48 | }, 49 | 50 | unmousewheel: function(fn) { 51 | return this.unbind("mousewheel", fn); 52 | } 53 | }); 54 | 55 | 56 | function handler(event) { 57 | var orgEvent = event || window.event, args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true, deltaX = 0, deltaY = 0; 58 | event = $.event.fix(orgEvent); 59 | event.type = "mousewheel"; 60 | 61 | // Old school scrollwheel delta 62 | if ( orgEvent.wheelDelta ) { delta = orgEvent.wheelDelta/120; } 63 | if ( orgEvent.detail ) { delta = -orgEvent.detail/3; } 64 | 65 | // New school multidimensional scroll (touchpads) deltas 66 | deltaY = delta; 67 | 68 | // Gecko 69 | if ( orgEvent.axis !== undefined && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) { 70 | deltaY = 0; 71 | deltaX = -1*delta; 72 | } 73 | 74 | // Webkit 75 | if ( orgEvent.wheelDeltaY !== undefined ) { deltaY = orgEvent.wheelDeltaY/120; } 76 | if ( orgEvent.wheelDeltaX !== undefined ) { deltaX = -1*orgEvent.wheelDeltaX/120; } 77 | 78 | // Add event and delta to the front of the arguments 79 | args.unshift(event, delta, deltaX, deltaY); 80 | 81 | return ($.event.dispatch || $.event.handle).apply(this, args); 82 | } 83 | 84 | })(jQuery); -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 3D Printroom 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 |
20 | 21 |
22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
 NameSize
37 |
38 |
39 |
40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 |
 NameSizeCreatedModifiedBottom Solid LayersExtrusion MultiplierFilament DiameterFill DensityFirst Layer Extrusion WidthInfill Extrusion WidthInfill SpeedLayer HeightNozzle DiameterPerimeter SpeedPerimetersPerimeters Extrusion WidthScaleTop Solid LayersTravel Speed
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /js/gcodeviewer/gcode-parser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Parses a string of gcode instructions, and invokes handlers for 3 | * each type of command. 4 | * 5 | * Special handler: 6 | * 'default': Called if no other handler matches. 7 | */ 8 | function GCodeParser(handlers) { 9 | this.handlers = handlers || {}; 10 | } 11 | 12 | GCodeParser.prototype.parseLine = function(text, info) { 13 | text = text.replace(/;.*$/, '').trim(); // Remove ; style comments 14 | if (text) { 15 | var tokens = text.split(' '); 16 | if (tokens) { 17 | var cmd = tokens[0]; 18 | var args = { 19 | 'cmd': cmd 20 | }; 21 | tokens.splice(1).forEach(function(token) { 22 | var key = token[0].toLowerCase(); 23 | var value = parseFloat(token.substring(1)); 24 | args[key] = value; 25 | }); 26 | var handler = this.handlers[tokens[0]] || this.handlers['default']; 27 | if (handler) { 28 | return handler(args, info); 29 | } 30 | } 31 | } 32 | }; 33 | 34 | GCodeParser.prototype.parse = function(gcode) { 35 | var lines = gcode.split('\n'); 36 | for (var i = 0; i < lines.length; i++) { 37 | if (this.parseLine(lines[i], i) === false) { 38 | break; 39 | } 40 | } 41 | }; 42 | 43 | GCodeParser.prototype.parseLineBjd = function(gcode, info) { 44 | var params = "G|M|E|F|H|I|J|K|P|R|S|T|X|Y|Z"; // supported command parameters 45 | var i = 0, index = 0; 46 | 47 | 48 | while (i < gcode.length) { 49 | if (gcode.substring(i, i+1).search(params) == 0) { 50 | index = gcode.substring(i+1).search(params); 51 | if (index != -1) { 52 | gcodeParam = gcode.substring(i, i+index+1); 53 | i++; 54 | } 55 | else { 56 | gcodeParam = gcode.substring(i); 57 | i = gcode.length; //this will end the while loop 58 | } 59 | 60 | if (! args) { // must be the first parameter 61 | var args = { 62 | 'cmdType': gcodeParam[0], // typically G or M 63 | 'cmdNumber': parseFloat(gcodeParam.substring(1)) // this make G1 and G01 equivelent 64 | }; 65 | } 66 | else { 67 | var key = gcodeParam[0].toLowerCase(); 68 | var value = parseFloat(gcodeParam.substring(1)); 69 | args[key] = value; 70 | } 71 | } 72 | i++; 73 | } 74 | //console.log(args); 75 | var handler = this.handlers['parsLine']; 76 | if (handler) { 77 | return handler(args, info); 78 | } 79 | }; 80 | 81 | GCodeParser.prototype.parseBjd = function(gcode) { 82 | 83 | //Fugly but valid g-code N110G17M03(start motor)G0X0Y0Z0 84 | 85 | //var gcode = testForm.output.value //"; comment\nN100G1X1Y1Z1\nN200g2X46 (Hmmm)\n;comm 2\nm03G12G1x5y5z5"; 86 | var commands = "G|M|S"; 87 | var index; 88 | var gcodeLine = ""; 89 | 90 | 91 | 92 | // clean up gcode 93 | gcode = gcode.toUpperCase(); 94 | gcode = gcode.replace(/;.*\n/g,""); //remove semicolon style comments 95 | gcode = gcode.replace(/\(.*\)/g,""); //remove parenthesis style comments 96 | gcode = gcode.replace(/%/g,""); //remove percent signs ..used as end of file?? 97 | gcode = gcode.replace(/N[0-9]*/g,""); // remove line numbers 98 | gcode = gcode.replace(/\s+/g,""); // remove white space 99 | 100 | var i = 0; 101 | 102 | while (i < gcode.length -1) { 103 | if (gcode.substring(i, i+1).search(commands) == 0) 104 | { 105 | index = gcode.substring(i+1).search(commands); 106 | if (index != -1) 107 | { 108 | gcodeLine = gcode.substring(i, i+index+1); 109 | i++; 110 | } 111 | else 112 | { 113 | gcodeLine = gcode.substring(i); 114 | i = gcode.length; 115 | } 116 | this.parseLineBjd(gcodeLine,0); 117 | } 118 | else 119 | i++; 120 | } 121 | 122 | }; 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /js/thingiview/binaryReader.js: -------------------------------------------------------------------------------- 1 | // BinaryReader 2 | // Refactored by Vjeux 3 | // http://blog.vjeux.com/2010/javascript/javascript-binary-reader.html 4 | 5 | // Original 6 | //+ Jonas Raoni Soares Silva 7 | //@ http://jsfromhell.com/classes/binary-parser [rev. #1] 8 | 9 | BinaryReader = function (data) { 10 | this._buffer = data; 11 | this._pos = 0; 12 | }; 13 | 14 | BinaryReader.prototype = { 15 | 16 | /* Public */ 17 | 18 | readInt8: function (){ return this._decodeInt(8, true); }, 19 | readUInt8: function (){ return this._decodeInt(8, false); }, 20 | readInt16: function (){ return this._decodeInt(16, true); }, 21 | readUInt16: function (){ return this._decodeInt(16, false); }, 22 | readInt32: function (){ return this._decodeInt(32, true); }, 23 | readUInt32: function (){ return this._decodeInt(32, false); }, 24 | 25 | readFloat: function (){ return this._decodeFloat(23, 8); }, 26 | readDouble: function (){ return this._decodeFloat(52, 11); }, 27 | 28 | readChar: function () { return this.readString(1); }, 29 | readString: function (length) { 30 | this._checkSize(length * 8); 31 | var result = this._buffer.substr(this._pos, length); 32 | this._pos += length; 33 | return result; 34 | }, 35 | 36 | seek: function (pos) { 37 | this._pos = pos; 38 | this._checkSize(0); 39 | }, 40 | 41 | getPosition: function () { 42 | return this._pos; 43 | }, 44 | 45 | getSize: function () { 46 | return this._buffer.length; 47 | }, 48 | 49 | 50 | /* Private */ 51 | 52 | _decodeFloat: function(precisionBits, exponentBits){ 53 | var length = precisionBits + exponentBits + 1; 54 | var size = length >> 3; 55 | this._checkSize(length); 56 | 57 | var bias = Math.pow(2, exponentBits - 1) - 1; 58 | var signal = this._readBits(precisionBits + exponentBits, 1, size); 59 | var exponent = this._readBits(precisionBits, exponentBits, size); 60 | var significand = 0; 61 | var divisor = 2; 62 | // var curByte = length + (-precisionBits >> 3) - 1; 63 | var curByte = 0; 64 | do { 65 | var byteValue = this._readByte(++curByte, size); 66 | var startBit = precisionBits % 8 || 8; 67 | var mask = 1 << startBit; 68 | while (mask >>= 1) { 69 | if (byteValue & mask) { 70 | significand += 1 / divisor; 71 | } 72 | divisor *= 2; 73 | } 74 | } while (precisionBits -= startBit); 75 | 76 | this._pos += size; 77 | 78 | return exponent == (bias << 1) + 1 ? significand ? NaN : signal ? -Infinity : +Infinity 79 | : (1 + signal * -2) * (exponent || significand ? !exponent ? Math.pow(2, -bias + 1) * significand 80 | : Math.pow(2, exponent - bias) * (1 + significand) : 0); 81 | }, 82 | 83 | _decodeInt: function(bits, signed){ 84 | var x = this._readBits(0, bits, bits / 8), max = Math.pow(2, bits); 85 | var result = signed && x >= max / 2 ? x - max : x; 86 | 87 | this._pos += bits / 8; 88 | return result; 89 | }, 90 | 91 | //shl fix: Henri Torgemane ~1996 (compressed by Jonas Raoni) 92 | _shl: function (a, b){ 93 | for (++b; --b; a = ((a %= 0x7fffffff + 1) & 0x40000000) == 0x40000000 ? a * 2 : (a - 0x40000000) * 2 + 0x7fffffff + 1); 94 | return a; 95 | }, 96 | 97 | _readByte: function (i, size) { 98 | return this._buffer.charCodeAt(this._pos + size - i - 1) & 0xff; 99 | }, 100 | 101 | _readBits: function (start, length, size) { 102 | var offsetLeft = (start + length) % 8; 103 | var offsetRight = start % 8; 104 | var curByte = size - (start >> 3) - 1; 105 | var lastByte = size + (-(start + length) >> 3); 106 | var diff = curByte - lastByte; 107 | 108 | var sum = (this._readByte(curByte, size) >> offsetRight) & ((1 << (diff ? 8 - offsetRight : length)) - 1); 109 | 110 | if (diff && offsetLeft) { 111 | sum += (this._readByte(lastByte++, size) & ((1 << offsetLeft) - 1)) << (diff-- << 3) - offsetRight; 112 | } 113 | 114 | while (diff) { 115 | sum += this._shl(this._readByte(lastByte++, size), (diff-- << 3) - offsetRight); 116 | } 117 | 118 | return sum; 119 | }, 120 | 121 | _checkSize: function (neededBits) { 122 | if (!(this._pos + Math.ceil(neededBits / 8) < this._buffer.length)) { 123 | throw new Error("Index out of bound"); 124 | } 125 | } 126 | }; -------------------------------------------------------------------------------- /js/bootbox.js: -------------------------------------------------------------------------------- 1 | /** 2 | * bootbox.js v2.5.0 3 | * 4 | * http://bootboxjs.com/license.txt 5 | */ 6 | var bootbox=window.bootbox||function(k){function h(b,a){null==a&&(a=m);return"string"==typeof i[a][b]?i[a][b]:a!=n?h(b,n):b}var m="en",n="en",s=!0,r="static",t="",j={},e={},i={en:{OK:"OK",CANCEL:"Cancel",CONFIRM:"OK"},fr:{OK:"OK",CANCEL:"Annuler",CONFIRM:"D'accord"},de:{OK:"OK",CANCEL:"Abbrechen",CONFIRM:"Akzeptieren"},es:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Aceptar"},br:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Sim"},nl:{OK:"OK",CANCEL:"Annuleren",CONFIRM:"Accepteren"},ru:{OK:"OK",CANCEL:"\u041e\u0442\u043c\u0435\u043d\u0430", 7 | CONFIRM:"\u041f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c"},it:{OK:"OK",CANCEL:"Annulla",CONFIRM:"Conferma"}};e.setLocale=function(b){for(var a in i)if(a==b){m=b;return}throw Error("Invalid locale: "+b);};e.addLocale=function(b,a){"undefined"==typeof i[b]&&(i[b]={});for(var c in a)i[b][c]=a[c]};e.setIcons=function(b){j=b;if("object"!==typeof j||null==j)j={}};e.alert=function(){var b="",a=h("OK"),c=null;switch(arguments.length){case 1:b=arguments[0];break;case 2:b=arguments[0];"function"==typeof arguments[1]? 8 | c=arguments[1]:a=arguments[1];break;case 3:b=arguments[0];a=arguments[1];c=arguments[2];break;default:throw Error("Incorrect number of arguments: expected 1-3");}return e.dialog(b,{label:a,icon:j.OK,callback:c},{onEscape:c})};e.confirm=function(){var b="",a=h("CANCEL"),c=h("CONFIRM"),f=null;switch(arguments.length){case 1:b=arguments[0];break;case 2:b=arguments[0];"function"==typeof arguments[1]?f=arguments[1]:a=arguments[1];break;case 3:b=arguments[0];a=arguments[1];"function"==typeof arguments[2]? 9 | f=arguments[2]:c=arguments[2];break;case 4:b=arguments[0];a=arguments[1];c=arguments[2];f=arguments[3];break;default:throw Error("Incorrect number of arguments: expected 1-4");}return e.dialog(b,[{label:a,icon:j.CANCEL,callback:function(){"function"==typeof f&&f(!1)}},{label:c,icon:j.CONFIRM,callback:function(){"function"==typeof f&&f(!0)}}])};e.prompt=function(){var b="",a=h("CANCEL"),c=h("CONFIRM"),f=null,u="";switch(arguments.length){case 1:b=arguments[0];break;case 2:b=arguments[0];"function"== 10 | typeof arguments[1]?f=arguments[1]:a=arguments[1];break;case 3:b=arguments[0];a=arguments[1];"function"==typeof arguments[2]?f=arguments[2]:c=arguments[2];break;case 4:b=arguments[0];a=arguments[1];c=arguments[2];f=arguments[3];break;case 5:b=arguments[0];a=arguments[1];c=arguments[2];f=arguments[3];u=arguments[4];break;default:throw Error("Incorrect number of arguments: expected 1-5");}var p=k("
");p.append("");var d=e.dialog(p,[{label:a, 11 | icon:j.CANCEL,callback:function(){"function"==typeof f&&f(null)}},{label:c,icon:j.CONFIRM,callback:function(){"function"==typeof f&&f(p.find("input[type=text]").val())}}],{header:b});d.on("shown",function(){p.find("input[type=text]").focus();p.on("submit",function(a){a.preventDefault();d.find(".btn-primary").click()})});return d};e.modal=function(){var b,a,c,f={onEscape:null,keyboard:!0,backdrop:r};switch(arguments.length){case 1:b=arguments[0];break;case 2:b=arguments[0];"object"==typeof arguments[1]? 12 | c=arguments[1]:a=arguments[1];break;case 3:b=arguments[0];a=arguments[1];c=arguments[2];break;default:throw Error("Incorrect number of arguments: expected 1-3");}f.header=a;c="object"==typeof c?k.extend(f,c):f;return e.dialog(b,[],c)};e.dialog=function(b,a,c){var f=null,e="",j=[],c=c||{};null==a?a=[]:"undefined"==typeof a.length&&(a=[a]);for(var d=a.length;d--;){var h=null,i=null,l=null,m="",n=null;if("undefined"==typeof a[d].label&&"undefined"==typeof a[d]["class"]&&"undefined"==typeof a[d].callback){var h= 13 | 0,i=null,q;for(q in a[d])if(i=q,1<++h)break;1==h&&"function"==typeof a[d][q]&&(a[d].label=i,a[d].callback=a[d][q])}"function"==typeof a[d].callback&&(n=a[d].callback);a[d]["class"]?l=a[d]["class"]:d==a.length-1&&2>=a.length&&(l="btn-primary");h=a[d].label?a[d].label:"Option "+(d+1);a[d].icon&&(m=" ");i=a[d].href?a[d].href:"javascript:;";e+=""+m+""+h+"";j[d]=n}d=["");var g=k(d.join("\n"));("undefined"===typeof c.animate?s:c.animate)&&g.addClass("fade");(e="undefined"===typeof c.classes?t:c.classes)&&g.addClass(e);k(".modal-body",g).html(b);g.bind("hidden", 15 | function(){g.remove()});g.bind("hide",function(){if("escape"==f&&"function"==typeof c.onEscape)c.onEscape()});k(document).bind("keyup.modal",function(a){27==a.which&&(f="escape")});g.bind("shown",function(){k("a.btn-primary:last",g).focus()});g.on("click",".modal-footer a, a.close",function(b){var c=k(this).data("handler"),d=j[c],e=null;"undefined"!==typeof c&&"undefined"!==typeof a[c].href||(b.preventDefault(),"function"==typeof d&&(e=d()),!1!==e&&(f="button",g.modal("hide")))});null==c.keyboard&& 16 | (c.keyboard="function"==typeof c.onEscape);k("body").append(g);g.modal({backdrop:"undefined"===typeof c.backdrop?r:c.backdrop,keyboard:c.keyboard});return g};e.hideAll=function(){k(".bootbox").modal("hide")};e.animate=function(b){s=b};e.backdrop=function(b){r=b};e.classes=function(b){t=b};return e}(window.jQuery); 17 | -------------------------------------------------------------------------------- /css/layout-default-latest.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Default Layout Theme 3 | * 4 | * Created for jquery.layout 5 | * 6 | * Copyright (c) 2010 7 | * Fabrizio Balliano (http://www.fabrizioballiano.net) 8 | * Kevin Dalman (http://allpro.net) 9 | * 10 | * Dual licensed under the GPL (http://www.gnu.org/licenses/gpl.html) 11 | * and MIT (http://www.opensource.org/licenses/mit-license.php) licenses. 12 | * 13 | * Last Updated: 2010-02-10 14 | * NOTE: For best code readability, view this with a fixed-space font and tabs equal to 4-chars 15 | */ 16 | 17 | /* 18 | * PANES & CONTENT-DIVs 19 | */ 20 | .ui-layout-pane { /* all 'panes' */ 21 | background: #FFF; 22 | border: 1px solid #BBB; 23 | padding: 10px; 24 | overflow: auto; 25 | /* DO NOT add scrolling (or padding) to 'panes' that have a content-div, 26 | otherwise you may get double-scrollbars - on the pane AND on the content-div 27 | - use ui-layout-wrapper class if pane has a content-div 28 | - use ui-layout-container if pane has an inner-layout 29 | */ 30 | } 31 | /* (scrolling) content-div inside pane allows for fixed header(s) and/or footer(s) */ 32 | .ui-layout-content { 33 | padding: 10px; 34 | position: relative; /* contain floated or positioned elements */ 35 | overflow: auto; /* add scrolling to content-div */ 36 | } 37 | 38 | /* 39 | * UTILITY CLASSES 40 | * Must come AFTER pane-class above so will override 41 | * These classes are NOT auto-generated and are NOT used by Layout 42 | */ 43 | .layout-child-container, 44 | .layout-content-container { 45 | padding: 0; 46 | overflow: hidden; 47 | } 48 | .layout-child-container { 49 | border: 0; /* remove border because inner-layout-panes probably have borders */ 50 | } 51 | .layout-scroll { 52 | overflow: auto; 53 | } 54 | .layout-hide { 55 | display: none; 56 | } 57 | 58 | /* 59 | * RESIZER-BARS 60 | */ 61 | .ui-layout-resizer { /* all 'resizer-bars' */ 62 | background: #DDD; 63 | border: 1px solid #BBB; 64 | border-width: 0; 65 | } 66 | .ui-layout-resizer-drag { /* REAL resizer while resize in progress */ 67 | } 68 | .ui-layout-resizer-hover { /* affects both open and closed states */ 69 | } 70 | /* NOTE: It looks best when 'hover' and 'dragging' are set to the same color, 71 | otherwise color shifts while dragging when bar can't keep up with mouse */ 72 | .ui-layout-resizer-open-hover , /* hover-color to 'resize' */ 73 | .ui-layout-resizer-dragging { /* resizer beging 'dragging' */ 74 | background: #C4E1A4; 75 | } 76 | .ui-layout-resizer-dragging { /* CLONED resizer being dragged */ 77 | border: 1px solid #BBB; 78 | } 79 | .ui-layout-resizer-north-dragging, 80 | .ui-layout-resizer-south-dragging { 81 | border-width: 1px 0; 82 | } 83 | .ui-layout-resizer-west-dragging, 84 | .ui-layout-resizer-east-dragging { 85 | border-width: 0 1px; 86 | } 87 | /* NOTE: Add a 'dragging-limit' color to provide visual feedback when resizer hits min/max size limits */ 88 | .ui-layout-resizer-dragging-limit { /* CLONED resizer at min or max size-limit */ 89 | background: #E1A4A4; /* red */ 90 | } 91 | 92 | .ui-layout-resizer-closed-hover { /* hover-color to 'slide open' */ 93 | background: #EBD5AA; 94 | } 95 | .ui-layout-resizer-sliding { /* resizer when pane is 'slid open' */ 96 | opacity: .10; /* show only a slight shadow */ 97 | filter: alpha(opacity=10); 98 | } 99 | .ui-layout-resizer-sliding-hover { /* sliding resizer - hover */ 100 | opacity: 1.00; /* on-hover, show the resizer-bar normally */ 101 | filter: alpha(opacity=100); 102 | } 103 | /* sliding resizer - add 'outside-border' to resizer on-hover 104 | * this sample illustrates how to target specific panes and states */ 105 | .ui-layout-resizer-north-sliding-hover { border-bottom-width: 1px; } 106 | .ui-layout-resizer-south-sliding-hover { border-top-width: 1px; } 107 | .ui-layout-resizer-west-sliding-hover { border-right-width: 1px; } 108 | .ui-layout-resizer-east-sliding-hover { border-left-width: 1px; } 109 | 110 | /* 111 | * TOGGLER-BUTTONS 112 | */ 113 | .ui-layout-toggler { 114 | border: 1px solid #BBB; /* match pane-border */ 115 | background-color: #BBB; 116 | } 117 | .ui-layout-resizer-hover .ui-layout-toggler { 118 | opacity: .60; 119 | filter: alpha(opacity=60); 120 | } 121 | .ui-layout-toggler-hover , /* need when NOT resizable */ 122 | .ui-layout-resizer-hover .ui-layout-toggler-hover { /* need specificity when IS resizable */ 123 | background-color: #FC6; 124 | opacity: 1.00; 125 | filter: alpha(opacity=100); 126 | } 127 | .ui-layout-toggler-north , 128 | .ui-layout-toggler-south { 129 | border-width: 0 1px; /* left/right borders */ 130 | } 131 | .ui-layout-toggler-west , 132 | .ui-layout-toggler-east { 133 | border-width: 1px 0; /* top/bottom borders */ 134 | } 135 | /* hide the toggler-button when the pane is 'slid open' */ 136 | .ui-layout-resizer-sliding .ui-layout-toggler { 137 | display: none; 138 | } 139 | /* 140 | * style the text we put INSIDE the togglers 141 | */ 142 | .ui-layout-toggler .content { 143 | color: #666; 144 | font-size: 12px; 145 | font-weight: bold; 146 | width: 100%; 147 | padding-bottom: 0.35ex; /* to 'vertically center' text inside text-span */ 148 | } 149 | 150 | /* 151 | * PANE-MASKS 152 | * these styles are hard-coded on mask elems, but are also 153 | * included here as !important to ensure will overrides any generic styles 154 | */ 155 | .ui-layout-mask { 156 | border: none !important; 157 | padding: 0 !important; 158 | margin: 0 !important; 159 | overflow: hidden !important; 160 | position: absolute !important; 161 | opacity: 0 !important; 162 | filter: Alpha(Opacity="0") !important; 163 | } 164 | .ui-layout-mask-inside-pane { /* masks always inside pane EXCEPT when pane is an iframe */ 165 | top: 0 !important; 166 | left: 0 !important; 167 | width: 100% !important; 168 | height: 100% !important; 169 | } 170 | div.ui-layout-mask {} /* standard mask for iframes */ 171 | iframe.ui-layout-mask {} /* extra mask for objects/applets */ 172 | 173 | -------------------------------------------------------------------------------- /js/modernizr.js: -------------------------------------------------------------------------------- 1 | /* Modernizr 2.5.3 (Custom Build) | MIT & BSD 2 | * Build: http://modernizr.com/download/#-draganddrop-shiv-cssclasses-hasevent-load 3 | */ 4 | ;window.Modernizr=function(a,b,c){function v(a){j.cssText=a}function w(a,b){return v(prefixes.join(a+";")+(b||""))}function x(a,b){return typeof a===b}function y(a,b){return!!~(""+a).indexOf(b)}function z(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:x(f,"function")?f.bind(d||b):f}return!1}var d="2.5.3",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m={},n={},o={},p=[],q=p.slice,r,s=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=x(e[d],"function"),x(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),t={}.hasOwnProperty,u;!x(t,"undefined")&&!x(t.call,"undefined")?u=function(a,b){return t.call(a,b)}:u=function(a,b){return b in a&&x(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=q.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(q.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(q.call(arguments)))};return e}),m.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a};for(var A in m)u(m,A)&&(r=A.toLowerCase(),e[r]=m[A](),p.push((e[r]?"":"no-")+r));return v(""),i=k=null,function(a,b){function g(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function h(){var a=k.elements;return typeof a=="string"?a.split(" "):a}function i(a){var b={},c=a.createElement,e=a.createDocumentFragment,f=e();a.createElement=function(a){var e=(b[a]||(b[a]=c(a))).cloneNode();return k.shivMethods&&e.canHaveChildren&&!d.test(a)?f.appendChild(e):e},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+h().join().replace(/\w+/g,function(a){return b[a]=c(a),f.createElement(a),'c("'+a+'")'})+");return n}")(k,f)}function j(a){var b;return a.documentShived?a:(k.shivCSS&&!e&&(b=!!g(a,"article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio{display:none}canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden]{display:none}audio[controls]{display:inline-block;*display:inline;*zoom:1}mark{background:#FF0;color:#000}")),f||(b=!i(a)),b&&(a.documentShived=b),a)}var c=a.html5||{},d=/^<|^(?:button|form|map|select|textarea)$/i,e,f;(function(){var a=b.createElement("a");a.innerHTML="",e="hidden"in a,f=a.childNodes.length==1||function(){try{b.createElement("a")}catch(a){return!0}var c=b.createDocumentFragment();return typeof c.cloneNode=="undefined"||typeof c.createDocumentFragment=="undefined"||typeof c.createElement=="undefined"}()})();var k={elements:c.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video",shivCSS:c.shivCSS!==!1,shivMethods:c.shivMethods!==!1,type:"default",shivDocument:j};a.html5=k,j(b)}(this,b),e._version=d,e.hasEvent=s,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+p.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return o.call(a)=="[object Function]"}function e(a){return typeof a=="string"}function f(){}function g(a){return!a||a=="loaded"||a=="complete"||a=="uninitialized"}function h(){var a=p.shift();q=1,a?a.t?m(function(){(a.t=="c"?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){a!="img"&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l={},o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};y[c]===1&&(r=1,y[c]=[],l=b.createElement(a)),a=="object"?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),a!="img"&&(r||y[c]===2?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i(b=="c"?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),p.length==1&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&o.call(a.opera)=="[object Opera]",l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return o.call(a)=="[object Array]"},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f 0 ) { 181 | 182 | this.dispatchEvent( changeEvent ); 183 | 184 | lastPosition.copy( this.object.position ); 185 | 186 | } 187 | 188 | }; 189 | 190 | 191 | function getAutoRotationAngle() { 192 | 193 | return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed; 194 | 195 | } 196 | 197 | function getZoomScale() { 198 | 199 | return Math.pow( 0.95, scope.userZoomSpeed ); 200 | 201 | } 202 | 203 | function onMouseDown( event ) { 204 | 205 | if ( !scope.userRotate ) return; 206 | 207 | event.preventDefault(); 208 | 209 | if ( event.button === 0 || event.button === 2 ) { 210 | 211 | state = STATE.ROTATE; 212 | 213 | rotateStart.set( event.clientX, event.clientY ); 214 | 215 | } else if ( event.button === 1 ) { 216 | 217 | state = STATE.ZOOM; 218 | 219 | zoomStart.set( event.clientX, event.clientY ); 220 | 221 | } 222 | 223 | document.addEventListener( 'mousemove', onMouseMove, false ); 224 | document.addEventListener( 'mouseup', onMouseUp, false ); 225 | 226 | } 227 | 228 | function onMouseMove( event ) { 229 | 230 | event.preventDefault(); 231 | 232 | if ( state === STATE.ROTATE ) { 233 | 234 | rotateEnd.set( event.clientX, event.clientY ); 235 | rotateDelta.subVectors( rotateEnd, rotateStart ); 236 | 237 | scope.rotateLeft( 2 * Math.PI * rotateDelta.x / PIXELS_PER_ROUND * scope.userRotateSpeed ); 238 | scope.rotateUp( 2 * Math.PI * rotateDelta.y / PIXELS_PER_ROUND * scope.userRotateSpeed ); 239 | 240 | rotateStart.copy( rotateEnd ); 241 | 242 | } else if ( state === STATE.ZOOM ) { 243 | 244 | zoomEnd.set( event.clientX, event.clientY ); 245 | zoomDelta.subVectors( zoomEnd, zoomStart ); 246 | 247 | if ( zoomDelta.y > 0 ) { 248 | 249 | scope.zoomIn(); 250 | 251 | } else { 252 | 253 | scope.zoomOut(); 254 | 255 | } 256 | 257 | zoomStart.copy( zoomEnd ); 258 | 259 | } 260 | 261 | } 262 | 263 | function onMouseUp( event ) { 264 | 265 | if ( ! scope.userRotate ) return; 266 | 267 | document.removeEventListener( 'mousemove', onMouseMove, false ); 268 | document.removeEventListener( 'mouseup', onMouseUp, false ); 269 | 270 | state = STATE.NONE; 271 | 272 | } 273 | 274 | function onMouseWheel( event ) { 275 | 276 | if ( ! scope.userZoom ) return; 277 | 278 | var delta = 0; 279 | 280 | if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9 281 | 282 | delta = event.wheelDelta; 283 | 284 | } else if ( event.detail ) { // Firefox 285 | 286 | delta = - event.detail; 287 | 288 | } 289 | 290 | if ( delta > 0 ) { 291 | 292 | scope.zoomOut(); 293 | 294 | } else { 295 | 296 | scope.zoomIn(); 297 | 298 | } 299 | 300 | } 301 | 302 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); 303 | this.domElement.addEventListener( 'mousedown', onMouseDown, false ); 304 | this.domElement.addEventListener( 'mousewheel', onMouseWheel, false ); 305 | this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox 306 | 307 | }; -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | global.$ = $; 2 | 3 | //TODO Globals OUT!! 4 | thingiurlbase = "js/thingiview"; 5 | 6 | var sourcefolder_view = require('sourcefolder_view'); 7 | var targetfolder_view = require('targetfolder_view'); 8 | var path = require('path'); 9 | var fs = require('fs'); 10 | var _ = require('underscore'); 11 | var events = require('events'); 12 | var util = require('util'); 13 | 14 | global.fileCache = global.fileCache || []; 15 | 16 | $(document).ready(function() { 17 | 18 | if (!Modernizr.draganddrop) { 19 | $('#message').html("Your browser cannot handle drag and drop."); 20 | } 21 | 22 | var ui = new UI(); 23 | 24 | 25 | var toggleButtons = '
' 26 | + '
' 27 | + '
'; 28 | 29 | 30 | var uiLayout = $('#container').layout({ 31 | minSize: 100, 32 | //stateManagement__enabled: true, 33 | west__size: .5, 34 | west__childOptions: { 35 | minSize: 50, 36 | south__size: .5 37 | }, 38 | spacing_open: 0, 39 | spacing_closed: 0, 40 | west__spacing_closed: 8, 41 | west__spacing_open: 8, 42 | west__togglerLength_closed: 105, 43 | west__togglerLength_open: 105, 44 | west__togglerContent_closed: toggleButtons, 45 | west__togglerContent_open: toggleButtons, 46 | 47 | 48 | center__childOptions: { 49 | center__onresize_end: $.proxy(ui.resizeViewers, ui), 50 | minSize: 50, 51 | south__size: .5 52 | } 53 | }); 54 | 55 | uiLayout.togglers.west 56 | // UN-BIND DEFAULT TOGGLER FUNCTIONALITY 57 | .unbind("click") 58 | // BIND CUSTOM WEST METHODS 59 | .find(".btnTogglerLeft") .click( maximizeCenter ).attr("title", "Maximize Left").end() 60 | .find(".btnTogglerCenter") .click( maximizeBoth ).attr("title", "Maximize Center").end() 61 | .find(".btnTogglerRight").click( maximizeWest ).attr("title", "Maximize Right").end() 62 | ; 63 | 64 | function maximizeCenter (evt) { uiLayout.close("west"); evt.stopPropagation(); }; 65 | function maximizeBoth (evt) { uiLayout.sizePane("west", "50%"); uiLayout.open("west"); evt.stopPropagation(); }; 66 | function maximizeWest (evt) { uiLayout.sizePane("west", "100%"); uiLayout.open("west"); evt.stopPropagation(); }; 67 | 68 | viewsLayout = $("#views-container").layout({ 69 | center__paneSelector: ".ui-layout-center" 70 | }); 71 | 72 | foldersLayout = $("#folders-container").layout({ 73 | center__paneSelector: ".ui-layout-center" 74 | }); 75 | 76 | ui.setUiLayout(uiLayout); 77 | ui.setViewsLayout(viewsLayout); 78 | ui.setFoldersLayout(foldersLayout); 79 | 80 | var sourceFolder = new sourcefolder_view.SourceFolder($('#sourcefilelist')); 81 | var targetFolder = new targetfolder_view.TargetFolder($('#derivativefilelist')); 82 | 83 | var folderDropZone = document.getElementById("sourcedircontainer"); 84 | 85 | sourceFolder.on('clickFile', _.bind(targetFolder.showDerivatives, targetFolder) ); 86 | 87 | sourceFolder.on('dblclickFile', function(filePath){ 88 | fs.readFile(filePath, function(err, fileContents) { 89 | if (err) throw err; 90 | 91 | ui.viewSource(filePath, fileContents); 92 | 93 | }); 94 | }); 95 | 96 | targetFolder.on('dblclickFile', function(filename,filePath){ 97 | fs.readFile(filePath, function(err, fileContents) { 98 | if (err) throw err; 99 | 100 | ui.viewGcode(fileContents.toString()); 101 | }); 102 | }); 103 | 104 | 105 | function handleDragEnter(e) { 106 | folderDropZone.classList.add('over'); 107 | } 108 | 109 | function handleDragLeave(e) { 110 | folderDropZone.classList.remove('over'); 111 | } 112 | 113 | function handleFolderDrop(e) { 114 | if (e.stopPropagation) { 115 | e.stopPropagation(); 116 | } 117 | e.preventDefault(); 118 | folderDropZone.classList.remove('over'); 119 | 120 | var path = e.dataTransfer.files[0].path; 121 | 122 | sourceFolder.open(path); 123 | 124 | return false; 125 | }; 126 | 127 | 128 | folderDropZone.addEventListener('drop', handleFolderDrop, false); 129 | folderDropZone.addEventListener('dragenter', handleDragEnter, false) 130 | folderDropZone.addEventListener('dragleave', handleDragLeave, false); 131 | 132 | folderDropZone.ondragover = function(e) { 133 | e.preventDefault(); 134 | }; 135 | 136 | folderDropZone.ondragend = function(e) { 137 | folderDropZone.classList.remove('over'); 138 | }; 139 | 140 | //testing 141 | sourceFolder.open("testfiles"); 142 | ui.resizeViewers(); 143 | 144 | }); 145 | 146 | function UI(){ 147 | events.EventEmitter.call(this); 148 | this.gcodeView = new GcodeView($('#gcodeviewer')); 149 | this.sourceView = new SourceView($('#sourceviewer')); 150 | var self = this; 151 | 152 | this.sourceView.on('toggleSourceView', function(e){ 153 | self.uiLayout.toggle("west"); 154 | self.viewsLayout.toggle("south"); 155 | }); 156 | 157 | this.gcodeView.on('toggleGcodeView', function(e){ 158 | asd = self.viewsLayout 159 | self.uiLayout.toggle("west"); 160 | self.viewsLayout.toggle("south"); 161 | }); 162 | } 163 | 164 | util.inherits(UI, events.EventEmitter); 165 | 166 | 167 | UI.prototype.setUiLayout = function(layout){ 168 | this.uiLayout = layout; 169 | } 170 | UI.prototype.setViewsLayout = function(layout){ 171 | this.viewsLayout = layout; 172 | } 173 | UI.prototype.setFoldersLayout = function(layout){ 174 | this.foldersLayout = layout; 175 | } 176 | 177 | UI.prototype.viewGcode = function(text){ 178 | $.proxy(this.gcodeView.display, this.gcodeView, text)(); 179 | } 180 | 181 | UI.prototype.viewSource = function(filename, text){ 182 | $.proxy(this.sourceView.display, this.sourceView, filename, text)(); 183 | } 184 | 185 | 186 | UI.prototype.resizeViewers = function(){ 187 | this.gcodeView.resize(); 188 | this.sourceView.resize(); 189 | } 190 | 191 | function GcodeView(jquery_element){ 192 | events.EventEmitter.call(this); 193 | this.element = jquery_element; 194 | this.scene = this.createGcodeScene(this.element); 195 | this.gcodeObject = new THREE.Object3D(); 196 | 197 | var self = this; 198 | 199 | this.element.dblclick(function(e){ 200 | self.emit('toggleGcodeView'); 201 | }) 202 | } 203 | 204 | util.inherits(GcodeView, events.EventEmitter); 205 | 206 | GcodeView.prototype.display = function(gcodeText){ 207 | if (this.gcodeObject) { 208 | this.scene.remove(this.gcodeObject); 209 | } 210 | this.gcodeObject = createObjectFromGCode(gcodeText); 211 | this.scene.add(this.gcodeObject); 212 | }; 213 | 214 | GcodeView.prototype.resize = function(gcodeText){ 215 | var w = this.element.width(); 216 | var h = this.element.height(); 217 | this.gcodeRenderer.setSize(w, h); 218 | this.camera.aspect = w / h; 219 | this.camera.updateProjectionMatrix(); 220 | this.controls.handleResize(); 221 | }; 222 | 223 | GcodeView.prototype.createGcodeScene = function(element) { 224 | 225 | var _gcodeView = this; 226 | 227 | // Renderer 228 | this.gcodeRenderer = new THREE.WebGLRenderer({clearColor:0x000000, clearAlpha: 1}); 229 | 230 | this.gcodeRenderer.setSize(element.width(), element.height()); 231 | element.append(this.gcodeRenderer.domElement); 232 | this.gcodeRenderer.clear(); 233 | 234 | // Scene 235 | this.scene = new THREE.Scene(); 236 | 237 | // Lights... 238 | [[0,0,1, 0xFFFFCC], 239 | [0,1,0, 0xFFCCFF], 240 | [1,0,0, 0xCCFFFF], 241 | [0,0,-1, 0xCCCCFF], 242 | [0,-1,0, 0xCCFFCC], 243 | [-1,0,0, 0xFFCCCC]].forEach(function(position) { 244 | var light = new THREE.DirectionalLight(position[3]); 245 | light.position.set(position[0], position[1], position[2]).normalize(); 246 | _gcodeView.scene.add(light); 247 | }); 248 | 249 | // Camera... 250 | var fov = 45, 251 | aspect = element.width() / element.height(), 252 | near = 1, 253 | far = 10000; 254 | this.camera = new THREE.PerspectiveCamera(fov, aspect, near, far); 255 | 256 | this.camera.position.z = 300; 257 | this.scene.add(this.camera); 258 | this.controls = new THREE.TrackballControls(this.camera, element.get(0)); 259 | 260 | // Action! 261 | function render() { 262 | _gcodeView.controls.update(); 263 | _gcodeView.gcodeRenderer.render(_gcodeView.scene, _gcodeView.camera); 264 | 265 | requestAnimationFrame(render); // And repeat... 266 | } 267 | render(); 268 | return this.scene; 269 | } 270 | 271 | 272 | 273 | function SourceView(container){ 274 | events.EventEmitter.call(this); 275 | 276 | this.thingiview = new Thingiview(container.get(0).id); 277 | this.thingiview.setObjectColor('#C0D8F0'); 278 | this.thingiview.setBackgroundColor('#000'); 279 | this.thingiview.initScene(); 280 | this.thingiview.setCameraView('iso'); 281 | 282 | var self = this; 283 | 284 | container.dblclick(function(e){ 285 | self.emit('toggleSourceView'); 286 | }) 287 | } 288 | 289 | util.inherits(SourceView, events.EventEmitter); 290 | 291 | SourceView.prototype.display = function(filename, fileContents){ 292 | 293 | if (fileContents.slice(0,5).toString().match(/^solid/)) { 294 | this.thingiview.loadSTLString(fileContents.toString()); 295 | } else { 296 | this.thingiview.loadSTLBinary(fileContents.toString('binary')); 297 | } 298 | }; 299 | 300 | SourceView.prototype.resize = function(gcodeText){ 301 | this.thingiview.onContainerResize(); 302 | }; -------------------------------------------------------------------------------- /js/thingiview/thingiloader.js: -------------------------------------------------------------------------------- 1 | Thingiloader = function(event) { 2 | // Code from https://developer.mozilla.org/En/Using_XMLHttpRequest#Receiving_binary_data 3 | this.load_binary_resource = function(url) { 4 | var req = new XMLHttpRequest(); 5 | req.open('GET', url, false); 6 | // The following line says we want to receive data as Binary and not as Unicode 7 | req.overrideMimeType('text/plain; charset=x-user-defined'); 8 | req.send(null); 9 | if (req.status != 200) return ''; 10 | 11 | return req.responseText; 12 | }; 13 | 14 | this.loadSTL = function(url) { 15 | var looksLikeBinary = function(reader) { 16 | // STL files don't specify a way to distinguish ASCII from binary. 17 | // The usual way is checking for "solid" at the start of the file -- 18 | // but Thingiverse has seen at least one binary STL file in the wild 19 | // that breaks this. 20 | 21 | // The approach here is different: binary STL files contain a triangle 22 | // count early in the file. If this correctly predicts the file's length, 23 | // it is most probably a binary STL file. 24 | 25 | reader.seek(80); // skip the header 26 | var count = reader.readUInt32(); 27 | 28 | var predictedSize = 80 /* header */ + 4 /* count */ + 50 * count; 29 | return reader.getSize() == predictedSize; 30 | }; 31 | 32 | workerFacadeMessage({'status':'message', 'content':'Downloading ' + url}); 33 | var file = this.load_binary_resource(url); 34 | var reader = new BinaryReader(file); 35 | 36 | if (looksLikeBinary(reader)) { 37 | this.loadSTLBinary(reader); 38 | } else { 39 | this.loadSTLString(file); 40 | } 41 | }; 42 | 43 | this.loadOBJ = function(url) { 44 | workerFacadeMessage({'status':'message', 'content':'Downloading ' + url}); 45 | var file = this.load_binary_resource(url); 46 | this.loadOBJString(file); 47 | }; 48 | 49 | this.loadJSON = function(url) { 50 | workerFacadeMessage({'status':'message', 'content':'Downloading ' + url}); 51 | var file = this.load_binary_resource(url); 52 | this.loadJSONString(file); 53 | }; 54 | 55 | this.loadPLY = function(url) { 56 | workerFacadeMessage({'status':'message', 'content':'Downloading ' + url}); 57 | 58 | var file = this.load_binary_resource(url); 59 | 60 | if (file.match(/format ascii/i)) { 61 | this.loadPLYString(file); 62 | } else { 63 | this.loadPLYBinary(file); 64 | } 65 | }; 66 | 67 | this.loadSTLString = function(STLString) { 68 | workerFacadeMessage({'status':'message', 'content':'Parsing STL String...'}); 69 | workerFacadeMessage({'status':'complete', 'content':this.ParseSTLString(STLString)}); 70 | }; 71 | 72 | this.loadSTLBinary = function(STLBinary) { 73 | workerFacadeMessage({'status':'message', 'content':'Parsing STL Binary...'}); 74 | 75 | if (STLBinary instanceof BinaryReader){ 76 | workerFacadeMessage({'status':'complete', 'content':this.ParseSTLBinary(STLBinary)}); 77 | } else { 78 | workerFacadeMessage({'status':'complete', 'content':this.ParseSTLBinary(new BinaryReader(STLBinary))}); 79 | } 80 | }; 81 | 82 | this.loadOBJString = function(OBJString) { 83 | workerFacadeMessage({'status':'message', 'content':'Parsing OBJ String...'}); 84 | workerFacadeMessage({'status':'complete', 'content':this.ParseOBJString(OBJString)}); 85 | }; 86 | 87 | this.loadJSONString = function(JSONString) { 88 | workerFacadeMessage({'status':'message', 'content':'Parsing JSON String...'}); 89 | workerFacadeMessage({'status':'complete', 'content':eval(JSONString)}); 90 | }; 91 | 92 | this.loadPLYString = function(PLYString) { 93 | workerFacadeMessage({'status':'message', 'content':'Parsing PLY String...'}); 94 | workerFacadeMessage({'status':'complete_points', 'content':this.ParsePLYString(PLYString)}); 95 | }; 96 | 97 | this.loadPLYBinary = function(PLYBinary) { 98 | workerFacadeMessage({'status':'message', 'content':'Parsing PLY Binary...'}); 99 | workerFacadeMessage({'status':'complete_points', 'content':this.ParsePLYBinary(PLYBinary)}); 100 | }; 101 | 102 | this.ParsePLYString = function(input) { 103 | var properties = []; 104 | var vertices = []; 105 | var colors = []; 106 | 107 | var vertex_count = 0; 108 | 109 | var header = /ply\n([\s\S]+)\nend_header/ig.exec(input)[1]; 110 | var data = /end_header\n([\s\S]+)$/ig.exec(input)[1]; 111 | 112 | // workerFacadeMessage({'status':'message', 'content':'header:\n' + header}); 113 | // workerFacadeMessage({'status':'message', 'content':'data:\n' + data}); 114 | 115 | header_parts = header.split("\n"); 116 | 117 | for (i in header_parts) { 118 | if (/element vertex/i.test(header_parts[i])) { 119 | vertex_count = /element vertex (\d+)/i.exec(header_parts[i])[1]; 120 | } else if (/property/i.test(header_parts[i])) { 121 | properties.push(/property (.*) (.*)/i.exec(header_parts[i])[2]); 122 | } 123 | } 124 | 125 | // workerFacadeMessage({'status':'message', 'content':'properties: ' + properties}); 126 | 127 | data_parts = data.split("\n"); 128 | 129 | for (i in data_parts) { 130 | data_line = data_parts[i]; 131 | data_line_parts = data_line.split(" "); 132 | 133 | vertices.push([ 134 | parseFloat(data_line_parts[properties.indexOf("x")]), 135 | parseFloat(data_line_parts[properties.indexOf("y")]), 136 | parseFloat(data_line_parts[properties.indexOf("z")]) 137 | ]); 138 | 139 | colors.push([ 140 | parseInt(data_line_parts[properties.indexOf("red")]), 141 | parseInt(data_line_parts[properties.indexOf("green")]), 142 | parseInt(data_line_parts[properties.indexOf("blue")]) 143 | ]); 144 | } 145 | 146 | // workerFacadeMessage({'status':'message', 'content':'vertices: ' + vertices}); 147 | 148 | return [vertices, colors]; 149 | }; 150 | 151 | this.ParsePLYBinary = function(input) { 152 | return false; 153 | }; 154 | 155 | this.ParseSTLBinary = function(input) { 156 | // Skip the header. 157 | input.seek(80); 158 | 159 | // Load the number of vertices. 160 | var count = input.readUInt32(); 161 | 162 | // During the parse loop we maintain the following data structures: 163 | var vertices = []; // Append-only list of all unique vertices. 164 | var vert_hash = {}; // Mapping from vertex to index in 'vertices', above. 165 | var faces = []; // List of triangle descriptions, each a three-element 166 | // list of indices in 'vertices', above. 167 | 168 | for (var i = 0; i < count; i++) { 169 | if (i % 100 == 0) { 170 | workerFacadeMessage({ 171 | 'status':'message', 172 | 'content':'Parsing ' + (i+1) + ' of ' + count + ' polygons...' 173 | }); 174 | workerFacadeMessage({ 175 | 'status':'progress', 176 | 'content':parseInt(i / count * 100) + '%' 177 | }); 178 | } 179 | 180 | // Skip the normal (3 single-precision floats) 181 | input.seek(input.getPosition() + 12); 182 | 183 | var face_indices = []; 184 | for (var x = 0; x < 3; x++) { 185 | var vertex = [input.readFloat(), input.readFloat(), input.readFloat()]; 186 | 187 | var vertexIndex = vert_hash[vertex]; 188 | if (vertexIndex == null) { 189 | vertexIndex = vertices.length; 190 | vertices.push(vertex); 191 | vert_hash[vertex] = vertexIndex; 192 | } 193 | 194 | face_indices.push(vertexIndex); 195 | } 196 | faces.push(face_indices); 197 | 198 | // Skip the "attribute" field (unused in common models) 199 | input.readUInt16(); 200 | } 201 | 202 | return [vertices, faces]; 203 | }; 204 | 205 | // build stl's vertex and face arrays 206 | this.ParseSTLString = function(STLString) { 207 | var vertexes = []; 208 | var faces = []; 209 | 210 | var face_vertexes = []; 211 | var vert_hash = {} 212 | 213 | // console.log(STLString); 214 | 215 | // strip out extraneous stuff 216 | STLString = STLString.replace(/\r/, "\n"); 217 | STLString = STLString.replace(/^solid[^\n]*/, ""); 218 | STLString = STLString.replace(/\n/g, " "); 219 | STLString = STLString.replace(/facet normal /g,""); 220 | STLString = STLString.replace(/outer loop/g,""); 221 | STLString = STLString.replace(/vertex /g,""); 222 | STLString = STLString.replace(/endloop/g,""); 223 | STLString = STLString.replace(/endfacet/g,""); 224 | STLString = STLString.replace(/endsolid[^\n]*/, ""); 225 | STLString = STLString.replace(/\s+/g, " "); 226 | STLString = STLString.replace(/^\s+/, ""); 227 | 228 | // console.log(STLString); 229 | 230 | var facet_count = 0; 231 | var block_start = 0; 232 | 233 | var points = STLString.split(" "); 234 | 235 | workerFacadeMessage({'status':'message', 'content':'Parsing vertices...'}); 236 | for (var i=0; i 0 && deltaY >= 0) // 0 - 90 25 | return Math.atan(deltaY/deltaX); 26 | else if (deltaX < 0 && deltaY >= 0) // 90 to 180 27 | return Math.PI - Math.abs(Math.atan(deltaY/deltaX)); 28 | else if (deltaX < 0 && deltaY < 0) // 180 - 270 29 | return Math.PI + Math.abs(Math.atan(deltaY/deltaX)); 30 | else if (deltaX > 0 && deltaY < 0) // 270 - 360 31 | return Math.PI * 2 - Math.abs(Math.atan(deltaY/deltaX)); 32 | } 33 | else { 34 | // 35 | if (deltaY > 0) { // 90 deg 36 | return Math.PI / 2.0; 37 | } 38 | else { // 270 deg 39 | return Math.PI * 3.0 / 2.0; 40 | } 41 | } 42 | 43 | } 44 | 45 | var lastLine = {x:0, y:0, z:0, e:0, f:0, extruding:false}; 46 | 47 | 48 | var bbbox = { min: { x:100000,y:100000,z:100000 }, max: { x:-100000,y:-100000,z:-100000 } }; 49 | var cutMaterial = new THREE.LineBasicMaterial({ 50 | opacity: 0.8, 51 | transparent: true, 52 | linewidth: 10, 53 | vertexColors: THREE.FaceColors }); 54 | var rapidMaterial = new THREE.LineBasicMaterial({ 55 | opacity: 0.8, 56 | transparent: true, 57 | linewidth: 10, 58 | vertexColors: THREE.FaceColors }); 59 | var cutColor = new THREE.Color(0xffffff ); 60 | var rapidColor = new THREE.Color(0x0000ff ); 61 | var arcColor = new THREE.Color(0xff0000 ); 62 | 63 | 64 | function addSegment(p1, p2) { 65 | 66 | geometry.vertices.push(new THREE.Vector3(p1.x, p1.y, p1.z)); 67 | geometry.vertices.push(new THREE.Vector3(p2.x, p2.y, p2.z)); 68 | geometry.colors.push(p2.extruding ? cutColor : rapidColor); 69 | geometry.colors.push(p2.extruding ? cutColor : rapidColor); 70 | 71 | if (p2.extruding) { 72 | bbbox.min.x = Math.min(bbbox.min.x, p2.x); 73 | bbbox.min.y = Math.min(bbbox.min.y, p2.y); 74 | bbbox.min.z = Math.min(bbbox.min.z, p2.z); 75 | bbbox.max.x = Math.max(bbbox.max.x, p2.x); 76 | bbbox.max.y = Math.max(bbbox.max.y, p2.y); 77 | bbbox.max.z = Math.max(bbbox.max.z, p2.z); 78 | } 79 | 80 | } 81 | 82 | function addArc(p1, p2, center, isCw) { 83 | 84 | var numPoints = 24; // TO DO....could this be dynamic or user selectable? 85 | var radius; 86 | var lineStart = p1; 87 | var lineEnd = p2; 88 | var sweep; 89 | 90 | //use pythag theorum...to get the radius 91 | radius = Math.pow(Math.pow(p1.x - center.x, 2.0) + Math.pow(p1.y - center.y, 2.0), 0.5); 92 | 93 | var startAngle = getAngle(center, p1); 94 | //console.log("strt", startAngle); 95 | var endAngle = getAngle(center, p2); 96 | //console.log("end", endAngle); 97 | 98 | // if it ends at 0 it really should end at 360 99 | if (endAngle == 0) 100 | endAngle = Math.PI * 2; 101 | 102 | if (!isCw && endAngle < startAngle) 103 | sweep = ((Math.PI * 2 - startAngle) + endAngle); 104 | else if (isCw && endAngle > startAngle) 105 | sweep = ((Math.PI * 2 - endAngle) + startAngle); 106 | else 107 | sweep = Math.abs(endAngle - startAngle); 108 | 109 | //console.log("sweep",sweep); 110 | //console.log("end ang",endAngle); 111 | 112 | for(i=0; i<=numPoints; i++) 113 | { 114 | 115 | if (isCw) 116 | var angle = (startAngle - i * sweep/numPoints); 117 | else 118 | var angle = (startAngle + i * sweep/numPoints); 119 | 120 | //console.log("ang",angle); 121 | 122 | if (angle >= Math.PI * 2) 123 | angle = angle - Math.PI * 2; 124 | 125 | var x = Math.cos(angle) * radius; 126 | var y = Math.sin(angle) * radius; 127 | 128 | lineEnd.x = center.x + x; 129 | lineEnd.y = center.y + y; 130 | 131 | geometry.vertices.push(new THREE.Vector3(lineStart.x, lineStart.y, lineStart.z)); 132 | 133 | if (i == numPoints) { // last point goes to the end 134 | geometry.vertices.push(new THREE.Vector3(p2.x, p2.y, p2.z)); 135 | 136 | } 137 | else { 138 | geometry.vertices.push(new THREE.Vector3(lineEnd.x, lineEnd.y, lineEnd.z)); 139 | 140 | } 141 | 142 | geometry.colors.push(arcColor); 143 | geometry.colors.push(arcColor); 144 | 145 | lineStart.x = lineEnd.x; 146 | lineStart.y = lineEnd.y; 147 | 148 | } 149 | 150 | 151 | //TO DO: deal with bounding box 152 | 153 | } 154 | 155 | // add counter clockwise arc 156 | function addCcwArc(p1, p2, center) { 157 | 158 | var numPoints = 24; // TO DO....could this be dynamic or user selectable? 159 | var radius; 160 | var lineStart = p1; 161 | var lineEnd = p2; 162 | 163 | //use pythag theorum...to get the radius 164 | radius = Math.pow(Math.pow(p1.x - center.x, 2.0) + Math.pow(p1.y - center.y, 2.0), 0.5); 165 | 166 | var startAngle = getAngle(center, p1); 167 | var endAngle = getAngle(center, p2); 168 | 169 | // if it ends at 0 it really should end at 360 170 | if (endAngle == 0) 171 | endAngle = Math.PI * 2; 172 | 173 | var sweep = endAngle - startAngle; 174 | 175 | for(i=0; i<=numPoints; i++) 176 | { 177 | var angle = (startAngle + i * sweep/numPoints); 178 | 179 | if (angle >= Math.PI *2) 180 | angle = angle - Math.PI * 2; 181 | else if (angle < 0) 182 | angle = angle + Math.PI * 2; // angle is less than 0 so it subtracts 183 | 184 | 185 | var x = Math.cos(angle) * radius; 186 | var y = Math.sin(angle) * radius; 187 | 188 | lineEnd.x = center.x + x; 189 | lineEnd.y = center.y + y; 190 | 191 | geometry.vertices.push(new THREE.Vector3(lineStart.x, lineStart.y, lineStart.z)); 192 | 193 | if (i == numPoints) { // last point goes to the end 194 | geometry.vertices.push(new THREE.Vector3(p2.x, p2.y, p2.z)); 195 | 196 | } 197 | else { 198 | geometry.vertices.push(new THREE.Vector3(lineEnd.x, lineEnd.y, lineEnd.z)); 199 | 200 | } 201 | 202 | geometry.colors.push(arcColor); 203 | geometry.colors.push(arcColor); 204 | 205 | lineStart.x = lineEnd.x; 206 | lineStart.y = lineEnd.y; 207 | 208 | } 209 | 210 | 211 | //TO DO: deal with bounding box 212 | 213 | } 214 | 215 | var relative = false; 216 | function delta(v1, v2) { 217 | return relative ? v2 : v2 - v1; 218 | } 219 | function absolute (v1, v2) { 220 | return relative ? v1 + v2 : v2; 221 | } 222 | 223 | 224 | 225 | var parser = new GCodeParser({ 226 | 227 | 'default': function(args, info) { 228 | console.error('Unknown command:', args.cmd, args, info); 229 | }, 230 | 231 | 'parsLine': function (args) { 232 | if (args.cmdType == "G") { 233 | switch (args.cmdNumber) { 234 | case 0: // G0 rapid linear move 235 | case 1: // G1 cut move 236 | var newLine = { 237 | x: args.x !== undefined ? absolute(lastLine.x, args.x) : lastLine.x, 238 | y: args.y !== undefined ? absolute(lastLine.y, args.y) : lastLine.y, 239 | z: args.z !== undefined ? absolute(lastLine.z, args.z) : lastLine.z, 240 | e: args.e !== undefined ? args.e : 0, 241 | f: args.f !== undefined ? absolute(lastLine.f, args.f) : lastLine.f, 242 | }; 243 | 244 | if (args.cmdNumber == 0) 245 | newLine.extruding = false; 246 | else 247 | if (isPrinterGcode) 248 | { 249 | newLine.extruding = (args.e > 0); 250 | } 251 | else 252 | { 253 | newLine.extruding = true; 254 | } 255 | 256 | 257 | 258 | addSegment(lastLine, newLine); 259 | lastLine = newLine; 260 | 261 | break; 262 | case 2: // CW Arc 263 | case 3: // CCW Arc 264 | //console.log("Do G2"); 265 | var endPoint = { 266 | x: args.x !== undefined ? args.x : lastLine.x, 267 | y: args.y !== undefined ? args.y : lastLine.y, 268 | z: args.z !== undefined ? args.z : lastLine.z, 269 | }; 270 | 271 | // the center point is offset from the start point by args I and J 272 | var center = { 273 | x: lastLine.x + args.i, 274 | y: lastLine.y + args.j, 275 | z: lastLine.z, 276 | }; 277 | 278 | //addArc(lastLine, endPoint, center, true); 279 | addArc(lastLine, endPoint, center, (args.cmdNumber == "2") ? true : false); 280 | lastLine = endPoint; 281 | break; 282 | case 20: // units inch 283 | units = "inch" 284 | break; 285 | case 21: // units mm 286 | units = "mm" 287 | break; 288 | case 90: 289 | relative = false; 290 | break; 291 | case 91: 292 | relative = true; 293 | break; 294 | case 92: // zero machine coordinates 295 | 296 | if (args.e !== undefined && args.e == 0) { 297 | isPrinterGcode = true; 298 | } 299 | break; 300 | default: 301 | //console.log("do unknown command G" + args.cmdNumber); 302 | break; 303 | 304 | } 305 | } 306 | else if (args.cmdType == "M") { 307 | } 308 | else if (args.cmdType == "S") { 309 | } 310 | else { 311 | console.log("Unknown cmd type: " + args.cmdType); 312 | } 313 | 314 | 315 | }, 316 | 317 | }); 318 | 319 | parser.parseBjd(gcode); // in gcode-parser.js 320 | 321 | //console.log("Layer Count", layers.count()); 322 | 323 | var object = new THREE.Object3D(); 324 | 325 | object.add(new THREE.Line(geometry, cutMaterial, THREE.LinePieces)); 326 | 327 | 328 | var radius = 1, 329 | segments = 16, 330 | rings = 16; 331 | 332 | 333 | if (units == "mm") 334 | radius = 1; 335 | else 336 | radius = .039; 337 | //radius = (units == "mm") ? 1 : 2; 338 | 339 | var sphereMaterial = 340 | new THREE.MeshLambertMaterial( 341 | { 342 | color: 0x00CC00 343 | }); 344 | 345 | // put a sphere at 0,0,0 346 | var sphere = new THREE.Mesh( 347 | 348 | new THREE.SphereGeometry( 349 | radius, 350 | segments, 351 | rings), 352 | 353 | sphereMaterial); 354 | 355 | sphere.position.x = 0; 356 | sphere.position.y = 0; 357 | sphere.position.z = 0; 358 | 359 | // add the sphere to the scene 360 | object.add(sphere); 361 | 362 | //console.log("bbox ", bbbox); 363 | 364 | // Center 365 | var scale = 3; // TODO: Auto size 366 | 367 | var center = new THREE.Vector3( 368 | bbbox.min.x + ((bbbox.max.x - bbbox.min.x) / 2), 369 | bbbox.min.y + ((bbbox.max.y - bbbox.min.y) / 2), 370 | bbbox.min.z + ((bbbox.max.z - bbbox.min.z) / 2)); 371 | //console.log("center ", center); 372 | 373 | object.position = center.multiplyScalar(-scale); 374 | 375 | object.scale.multiplyScalar(scale); 376 | 377 | return object; 378 | } -------------------------------------------------------------------------------- /js/gcodeviewer/Three.TrackballControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Eberhard Graether / http://egraether.com/ 3 | */ 4 | 5 | THREE.TrackballControls = function ( object, domElement ) { 6 | 7 | THREE.EventDispatcher.call( this ); 8 | 9 | var _this = this; 10 | var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM: 4, TOUCH_PAN: 5 }; 11 | 12 | this.object = object; 13 | this.domElement = ( domElement !== undefined ) ? domElement : document; 14 | 15 | // API 16 | 17 | this.enabled = true; 18 | 19 | this.screen = { width: 0, height: 0, offsetLeft: 0, offsetTop: 0 }; 20 | this.radius = ( this.screen.width + this.screen.height ) / 4; 21 | 22 | this.rotateSpeed = 1.0; 23 | this.zoomSpeed = 1.2; 24 | this.panSpeed = 0.3; 25 | 26 | this.noRotate = false; 27 | this.noZoom = false; 28 | this.noPan = false; 29 | 30 | this.staticMoving = false; 31 | this.dynamicDampingFactor = 0.2; 32 | 33 | this.minDistance = 0; 34 | this.maxDistance = Infinity; 35 | 36 | this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ]; 37 | 38 | // internals 39 | 40 | this.target = new THREE.Vector3(); 41 | 42 | var lastPosition = new THREE.Vector3(); 43 | 44 | var _state = STATE.NONE, 45 | _prevState = STATE.NONE, 46 | 47 | _eye = new THREE.Vector3(), 48 | 49 | _rotateStart = new THREE.Vector3(), 50 | _rotateEnd = new THREE.Vector3(), 51 | 52 | _zoomStart = new THREE.Vector2(), 53 | _zoomEnd = new THREE.Vector2(), 54 | 55 | _touchZoomDistanceStart = 0, 56 | _touchZoomDistanceEnd = 0, 57 | 58 | _panStart = new THREE.Vector2(), 59 | _panEnd = new THREE.Vector2(); 60 | 61 | // events 62 | 63 | var changeEvent = { type: 'change' }; 64 | 65 | 66 | // methods 67 | 68 | this.handleResize = function () { 69 | 70 | this.screen.width = window.innerWidth; 71 | this.screen.height = window.innerHeight; 72 | 73 | this.screen.offsetLeft = 0; 74 | this.screen.offsetTop = 0; 75 | 76 | this.radius = ( this.screen.width + this.screen.height ) / 4; 77 | 78 | }; 79 | 80 | this.handleEvent = function ( event ) { 81 | 82 | if ( typeof this[ event.type ] == 'function' ) { 83 | 84 | this[ event.type ]( event ); 85 | 86 | } 87 | 88 | }; 89 | 90 | this.getMouseOnScreen = function ( clientX, clientY ) { 91 | 92 | return new THREE.Vector2( 93 | ( clientX - _this.screen.offsetLeft ) / _this.radius * 0.5, 94 | ( clientY - _this.screen.offsetTop ) / _this.radius * 0.5 95 | ); 96 | 97 | }; 98 | 99 | this.getMouseProjectionOnBall = function ( clientX, clientY ) { 100 | 101 | var mouseOnBall = new THREE.Vector3( 102 | ( clientX - _this.screen.width * 0.5 - _this.screen.offsetLeft ) / _this.radius, 103 | ( _this.screen.height * 0.5 + _this.screen.offsetTop - clientY ) / _this.radius, 104 | 0.0 105 | ); 106 | 107 | var length = mouseOnBall.length(); 108 | 109 | if ( length > 1.0 ) { 110 | 111 | mouseOnBall.normalize(); 112 | 113 | } else { 114 | 115 | mouseOnBall.z = Math.sqrt( 1.0 - length * length ); 116 | 117 | } 118 | 119 | _eye.copy( _this.object.position ).sub( _this.target ); 120 | 121 | var projection = _this.object.up.clone().setLength( mouseOnBall.y ); 122 | projection.add( _this.object.up.clone().cross( _eye ).setLength( mouseOnBall.x ) ); 123 | projection.add( _eye.setLength( mouseOnBall.z ) ); 124 | 125 | return projection; 126 | 127 | }; 128 | 129 | this.rotateCamera = function () { 130 | 131 | var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() ); 132 | 133 | if ( angle ) { 134 | 135 | var axis = ( new THREE.Vector3() ).crossVectors( _rotateStart, _rotateEnd ).normalize(), 136 | quaternion = new THREE.Quaternion(); 137 | 138 | angle *= _this.rotateSpeed; 139 | 140 | quaternion.setFromAxisAngle( axis, -angle ); 141 | 142 | _eye.applyQuaternion( quaternion ); 143 | _this.object.up.applyQuaternion( quaternion ); 144 | 145 | _rotateEnd.applyQuaternion( quaternion ); 146 | 147 | if ( _this.staticMoving ) { 148 | 149 | _rotateStart.copy( _rotateEnd ); 150 | 151 | } else { 152 | 153 | quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) ); 154 | _rotateStart.applyQuaternion( quaternion ); 155 | 156 | } 157 | 158 | } 159 | 160 | }; 161 | 162 | this.zoomCamera = function () { 163 | 164 | if ( _state === STATE.TOUCH_ZOOM ) { 165 | 166 | var factor = _touchZoomDistanceStart / _touchZoomDistanceEnd; 167 | _touchZoomDistanceStart = _touchZoomDistanceEnd; 168 | _eye.multiplyScalar( factor ); 169 | 170 | } else { 171 | 172 | var factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed; 173 | 174 | if ( factor !== 1.0 && factor > 0.0 ) { 175 | 176 | _eye.multiplyScalar( factor ); 177 | 178 | if ( _this.staticMoving ) { 179 | 180 | _zoomStart.copy( _zoomEnd ); 181 | 182 | } else { 183 | 184 | _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor; 185 | 186 | } 187 | 188 | } 189 | 190 | } 191 | 192 | }; 193 | 194 | this.panCamera = function () { 195 | 196 | var mouseChange = _panEnd.clone().sub( _panStart ); 197 | 198 | if ( mouseChange.lengthSq() ) { 199 | 200 | mouseChange.multiplyScalar( _eye.length() * _this.panSpeed ); 201 | 202 | var pan = _eye.clone().cross( _this.object.up ).setLength( mouseChange.x ); 203 | pan.add( _this.object.up.clone().setLength( mouseChange.y ) ); 204 | 205 | _this.object.position.add( pan ); 206 | _this.target.add( pan ); 207 | 208 | if ( _this.staticMoving ) { 209 | 210 | _panStart = _panEnd; 211 | 212 | } else { 213 | 214 | _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) ); 215 | 216 | } 217 | 218 | } 219 | 220 | }; 221 | 222 | this.checkDistances = function () { 223 | 224 | if ( !_this.noZoom || !_this.noPan ) { 225 | 226 | if ( _this.object.position.lengthSq() > _this.maxDistance * _this.maxDistance ) { 227 | 228 | _this.object.position.setLength( _this.maxDistance ); 229 | 230 | } 231 | 232 | if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) { 233 | 234 | _this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) ); 235 | 236 | } 237 | 238 | } 239 | 240 | }; 241 | 242 | this.update = function () { 243 | 244 | _eye.subVectors( _this.object.position, _this.target ); 245 | 246 | if ( !_this.noRotate ) { 247 | 248 | _this.rotateCamera(); 249 | 250 | } 251 | 252 | if ( !_this.noZoom ) { 253 | 254 | _this.zoomCamera(); 255 | 256 | } 257 | 258 | if ( !_this.noPan ) { 259 | 260 | _this.panCamera(); 261 | 262 | } 263 | 264 | _this.object.position.addVectors( _this.target, _eye ); 265 | 266 | _this.checkDistances(); 267 | 268 | _this.object.lookAt( _this.target ); 269 | 270 | if ( lastPosition.distanceToSquared( _this.object.position ) > 0 ) { 271 | 272 | _this.dispatchEvent( changeEvent ); 273 | 274 | lastPosition.copy( _this.object.position ); 275 | 276 | } 277 | 278 | }; 279 | 280 | // listeners 281 | 282 | function keydown( event ) { 283 | 284 | if ( _this.enabled === false ) return; 285 | 286 | window.removeEventListener( 'keydown', keydown ); 287 | 288 | _prevState = _state; 289 | 290 | if ( _state !== STATE.NONE ) { 291 | 292 | return; 293 | 294 | } else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && !_this.noRotate ) { 295 | 296 | _state = STATE.ROTATE; 297 | 298 | } else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && !_this.noZoom ) { 299 | 300 | _state = STATE.ZOOM; 301 | 302 | } else if ( event.keyCode === _this.keys[ STATE.PAN ] && !_this.noPan ) { 303 | 304 | _state = STATE.PAN; 305 | 306 | } 307 | 308 | } 309 | 310 | function keyup( event ) { 311 | 312 | if ( _this.enabled === false ) return; 313 | 314 | _state = _prevState; 315 | 316 | window.addEventListener( 'keydown', keydown, false ); 317 | 318 | } 319 | 320 | function mousedown( event ) { 321 | 322 | if ( _this.enabled === false ) return; 323 | 324 | event.preventDefault(); 325 | event.stopPropagation(); 326 | 327 | if ( _state === STATE.NONE ) { 328 | 329 | _state = event.button; 330 | 331 | } 332 | 333 | if ( _state === STATE.ROTATE && !_this.noRotate ) { 334 | 335 | _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.clientX, event.clientY ); 336 | 337 | } else if ( _state === STATE.ZOOM && !_this.noZoom ) { 338 | 339 | _zoomStart = _zoomEnd = _this.getMouseOnScreen( event.clientX, event.clientY ); 340 | 341 | } else if ( _state === STATE.PAN && !_this.noPan ) { 342 | 343 | _panStart = _panEnd = _this.getMouseOnScreen( event.clientX, event.clientY ); 344 | 345 | } 346 | 347 | document.addEventListener( 'mousemove', mousemove, false ); 348 | document.addEventListener( 'mouseup', mouseup, false ); 349 | 350 | } 351 | 352 | function mousemove( event ) { 353 | 354 | if ( _this.enabled === false ) return; 355 | 356 | event.preventDefault(); 357 | event.stopPropagation(); 358 | 359 | if ( _state === STATE.ROTATE && !_this.noRotate ) { 360 | 361 | _rotateEnd = _this.getMouseProjectionOnBall( event.clientX, event.clientY ); 362 | 363 | } else if ( _state === STATE.ZOOM && !_this.noZoom ) { 364 | 365 | _zoomEnd = _this.getMouseOnScreen( event.clientX, event.clientY ); 366 | 367 | } else if ( _state === STATE.PAN && !_this.noPan ) { 368 | 369 | _panEnd = _this.getMouseOnScreen( event.clientX, event.clientY ); 370 | 371 | } 372 | 373 | } 374 | 375 | function mouseup( event ) { 376 | 377 | if ( _this.enabled === false ) return; 378 | 379 | event.preventDefault(); 380 | event.stopPropagation(); 381 | 382 | _state = STATE.NONE; 383 | 384 | document.removeEventListener( 'mousemove', mousemove ); 385 | document.removeEventListener( 'mouseup', mouseup ); 386 | 387 | } 388 | 389 | function mousewheel( event ) { 390 | 391 | if ( _this.enabled === false ) return; 392 | 393 | event.preventDefault(); 394 | event.stopPropagation(); 395 | 396 | var delta = 0; 397 | 398 | if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9 399 | 400 | delta = event.wheelDelta / 40; 401 | 402 | } else if ( event.detail ) { // Firefox 403 | 404 | delta = - event.detail / 3; 405 | 406 | } 407 | 408 | _zoomStart.y += ( 1 / delta ) * 0.05; 409 | 410 | } 411 | 412 | function touchstart( event ) { 413 | 414 | if ( _this.enabled === false ) return; 415 | 416 | switch ( event.touches.length ) { 417 | 418 | case 1: 419 | _state = STATE.TOUCH_ROTATE; 420 | _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 421 | break; 422 | 423 | case 2: 424 | _state = STATE.TOUCH_ZOOM; 425 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 426 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 427 | _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy ); 428 | break; 429 | 430 | case 3: 431 | _state = STATE.TOUCH_PAN; 432 | _panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 433 | break; 434 | 435 | default: 436 | _state = STATE.NONE; 437 | 438 | } 439 | 440 | } 441 | 442 | function touchmove( event ) { 443 | 444 | if ( _this.enabled === false ) return; 445 | 446 | event.preventDefault(); 447 | event.stopPropagation(); 448 | 449 | switch ( event.touches.length ) { 450 | 451 | case 1: 452 | _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 453 | break; 454 | 455 | case 2: 456 | var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX; 457 | var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY; 458 | _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy ) 459 | break; 460 | 461 | case 3: 462 | _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 463 | break; 464 | 465 | default: 466 | _state = STATE.NONE; 467 | 468 | } 469 | 470 | } 471 | 472 | function touchend( event ) { 473 | 474 | if ( _this.enabled === false ) return; 475 | 476 | switch ( event.touches.length ) { 477 | 478 | case 1: 479 | _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 480 | break; 481 | 482 | case 2: 483 | _touchZoomDistanceStart = _touchZoomDistanceEnd = 0; 484 | break; 485 | 486 | case 3: 487 | _panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ); 488 | break; 489 | 490 | } 491 | 492 | _state = STATE.NONE; 493 | 494 | } 495 | 496 | this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false ); 497 | 498 | this.domElement.addEventListener( 'mousedown', mousedown, false ); 499 | 500 | this.domElement.addEventListener( 'mousewheel', mousewheel, false ); 501 | this.domElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox 502 | 503 | this.domElement.addEventListener( 'touchstart', touchstart, false ); 504 | this.domElement.addEventListener( 'touchend', touchend, false ); 505 | this.domElement.addEventListener( 'touchmove', touchmove, false ); 506 | 507 | window.addEventListener( 'keydown', keydown, false ); 508 | window.addEventListener( 'keyup', keyup, false ); 509 | 510 | this.handleResize(); 511 | 512 | }; 513 | -------------------------------------------------------------------------------- /js/thingiview/thingiview.js: -------------------------------------------------------------------------------- 1 | Thingiview = function(containerId) { 2 | scope = this; 3 | 4 | this.containerId = containerId; 5 | var container = document.getElementById(containerId); 6 | 7 | var camera = null; 8 | var scene = null; 9 | var renderer = null; 10 | var object = null; 11 | var plane = null; 12 | 13 | var ambientLight = null; 14 | var directionalLight = null; 15 | var pointLight = null; 16 | 17 | var targetXRotation = 0; 18 | var targetXRotationOnMouseDown = 0; 19 | var mouseX = 0; 20 | var mouseXOnMouseDown = 0; 21 | 22 | var targetYRotation = 0; 23 | var targetYRotationOnMouseDown = 0; 24 | var mouseY = 0; 25 | var mouseYOnMouseDown = 0; 26 | 27 | var mouseDown = false; 28 | var mouseOver = false; 29 | 30 | var windowHalfX = window.innerWidth / 2; 31 | var windowHalfY = window.innerHeight / 2 32 | 33 | var view = null; 34 | var infoMessage = null; 35 | var progressBar = null; 36 | var alertBox = null; 37 | 38 | var timer = null; 39 | 40 | var rotateTimer = null; 41 | var rotateListener = null; 42 | var wasRotating = null; 43 | 44 | var cameraView = 'iso'; 45 | var cameraZoom = 0; 46 | var rotate = false; 47 | var backgroundColor = '#606060'; 48 | var objectMaterial = 'solid'; 49 | var objectColor = 0xffffff; 50 | var showPlane = true; 51 | var isWebGl = false; 52 | 53 | if (document.defaultView && document.defaultView.getComputedStyle) { 54 | var width = parseFloat(document.defaultView.getComputedStyle(container,null).getPropertyValue('width')); 55 | var height = parseFloat(document.defaultView.getComputedStyle(container,null).getPropertyValue('height')); 56 | } else { 57 | var width = parseFloat(container.currentStyle.width); 58 | var height = parseFloat(container.currentStyle.height); 59 | } 60 | 61 | var geometry; 62 | 63 | this.initScene = function() { 64 | container.style.position = 'relative'; 65 | container.innerHTML = ''; 66 | 67 | camera = new THREE_32.Camera(45, width/ height, 1, 100000); 68 | camera.updateMatrix(); 69 | 70 | scene = new THREE_32.Scene(); 71 | 72 | ambientLight = new THREE_32.AmbientLight(0x202020); 73 | scene.addLight(ambientLight); 74 | 75 | directionalLight = new THREE_32.DirectionalLight(0xffffff, 0.75); 76 | directionalLight.position.x = 1; 77 | directionalLight.position.y = 1; 78 | directionalLight.position.z = 2; 79 | directionalLight.position.normalize(); 80 | scene.addLight(directionalLight); 81 | 82 | pointLight = new THREE_32.PointLight(0xffffff, 0.3); 83 | pointLight.position.x = 0; 84 | pointLight.position.y = -25; 85 | pointLight.position.z = 10; 86 | scene.addLight(pointLight); 87 | 88 | progressBar = document.createElement('div'); 89 | progressBar.style.position = 'absolute'; 90 | progressBar.style.top = '0px'; 91 | progressBar.style.left = '0px'; 92 | progressBar.style.backgroundColor = 'red'; 93 | progressBar.style.padding = '5px'; 94 | progressBar.style.display = 'none'; 95 | progressBar.style.overflow = 'visible'; 96 | progressBar.style.whiteSpace = 'nowrap'; 97 | progressBar.style.zIndex = 100; 98 | container.appendChild(progressBar); 99 | 100 | alertBox = document.createElement('div'); 101 | alertBox.id = 'alertBox'; 102 | alertBox.style.position = 'absolute'; 103 | alertBox.style.top = '25%'; 104 | alertBox.style.left = '25%'; 105 | alertBox.style.width = '50%'; 106 | alertBox.style.height = '50%'; 107 | alertBox.style.backgroundColor = '#dddddd'; 108 | alertBox.style.padding = '10px'; 109 | alertBox.style.display = 'none'; 110 | alertBox.style.zIndex = 100; 111 | container.appendChild(alertBox); 112 | 113 | if (showPlane) { 114 | loadPlaneGeometry(); 115 | } 116 | 117 | this.setCameraView(cameraView); 118 | this.setObjectMaterial(objectMaterial); 119 | 120 | testCanvas = document.createElement('canvas'); 121 | try { 122 | if (testCanvas.getContext('experimental-webgl')) { 123 | isWebGl = true; 124 | renderer = new THREE_32.WebGLRenderer(); 125 | } else { 126 | renderer = new THREE_32.CanvasRenderer(); 127 | } 128 | } catch(e) { 129 | renderer = new THREE_32.CanvasRenderer(); 130 | } 131 | 132 | renderer.setSize(width, height); 133 | renderer.domElement.style.backgroundColor = backgroundColor; 134 | container.appendChild(renderer.domElement); 135 | 136 | window.addEventListener('mousemove', onRendererMouseMove, false); 137 | renderer.domElement.addEventListener('mouseover', onRendererMouseOver, false); 138 | renderer.domElement.addEventListener('mouseout', onRendererMouseOut, false); 139 | renderer.domElement.addEventListener('mousedown', onRendererMouseDown, false); 140 | window.addEventListener('mouseup', onRendererMouseUp, false); 141 | 142 | renderer.domElement.addEventListener('touchstart', onRendererTouchStart, false); 143 | renderer.domElement.addEventListener('touchend', onRendererTouchEnd, false); 144 | renderer.domElement.addEventListener('touchmove', onRendererTouchMove, false); 145 | 146 | renderer.domElement.addEventListener('DOMMouseScroll', onRendererScroll, false); 147 | renderer.domElement.addEventListener('mousewheel', onRendererScroll, false); 148 | renderer.domElement.addEventListener('gesturechange', onRendererGestureChange, false); 149 | } 150 | 151 | this.onContainerResize = function(event) { 152 | width = parseFloat(document.defaultView.getComputedStyle(container,null).getPropertyValue('width')); 153 | height = parseFloat(document.defaultView.getComputedStyle(container,null).getPropertyValue('height')); 154 | 155 | if (renderer) { 156 | renderer.setSize(width, height); 157 | camera.projectionMatrix = THREE_32.Matrix4.makePerspective(70, width / height, 1, 10000); 158 | sceneLoop(); 159 | } 160 | }; 161 | 162 | onRendererScroll = function(event) { 163 | event.preventDefault(); 164 | 165 | var rolled = 0; 166 | 167 | if (event.wheelDelta === undefined) { 168 | // Firefox 169 | // The measurement units of the detail and wheelDelta properties are different. 170 | rolled = -40 * event.detail; 171 | } else { 172 | rolled = event.wheelDelta; 173 | } 174 | 175 | if (rolled > 0) { 176 | // up 177 | scope.setCameraZoom(+10); 178 | } else { 179 | // down 180 | scope.setCameraZoom(-10); 181 | } 182 | } 183 | 184 | onRendererGestureChange = function(event) { 185 | event.preventDefault(); 186 | 187 | if (event.scale > 1) { 188 | scope.setCameraZoom(+5); 189 | } else { 190 | scope.setCameraZoom(-5); 191 | } 192 | } 193 | 194 | onRendererMouseOver = function(event) { 195 | mouseOver = true; 196 | if (timer == null) { 197 | timer = setInterval(sceneLoop, 1000/60); 198 | } 199 | } 200 | 201 | onRendererMouseDown = function(event) { 202 | event.preventDefault(); 203 | mouseDown = true; 204 | 205 | if(scope.getRotation()){ 206 | wasRotating = true; 207 | scope.setRotation(false); 208 | } else { 209 | wasRotating = false; 210 | } 211 | 212 | mouseXOnMouseDown = event.clientX - windowHalfX; 213 | mouseYOnMouseDown = event.clientY - windowHalfY; 214 | 215 | targetXRotationOnMouseDown = targetXRotation; 216 | targetYRotationOnMouseDown = targetYRotation; 217 | } 218 | 219 | onRendererMouseMove = function(event) { 220 | if (mouseDown) { 221 | mouseX = event.clientX - windowHalfX; 222 | xrot = targetXRotationOnMouseDown + (mouseX - mouseXOnMouseDown) * 0.02; 223 | 224 | mouseY = event.clientY - windowHalfY; 225 | yrot = targetYRotationOnMouseDown + (mouseY - mouseYOnMouseDown) * 0.02; 226 | 227 | targetXRotation = xrot; 228 | targetYRotation = yrot; 229 | } 230 | } 231 | 232 | onRendererMouseUp = function(event) { 233 | if (mouseDown) { 234 | mouseDown = false; 235 | if (!mouseOver) { 236 | clearInterval(timer); 237 | timer = null; 238 | } 239 | if (wasRotating) { 240 | scope.setRotation(true); 241 | } 242 | } 243 | } 244 | 245 | onRendererMouseOut = function(event) { 246 | if (!mouseDown) { 247 | clearInterval(timer); 248 | timer = null; 249 | } 250 | mouseOver = false; 251 | } 252 | 253 | onRendererTouchStart = function(event) { 254 | targetXRotation = object.rotation.z; 255 | targetYRotation = object.rotation.x; 256 | 257 | timer = setInterval(sceneLoop, 1000/60); 258 | 259 | if (event.touches.length == 1) { 260 | event.preventDefault(); 261 | 262 | mouseXOnMouseDown = event.touches[0].pageX - windowHalfX; 263 | targetXRotationOnMouseDown = targetXRotation; 264 | 265 | mouseYOnMouseDown = event.touches[0].pageY - windowHalfY; 266 | targetYRotationOnMouseDown = targetYRotation; 267 | } 268 | } 269 | 270 | onRendererTouchEnd = function(event) { 271 | clearInterval(timer); 272 | timer = null; 273 | } 274 | 275 | onRendererTouchMove = function(event) { 276 | if (event.touches.length == 1) { 277 | event.preventDefault(); 278 | 279 | mouseX = event.touches[0].pageX - windowHalfX; 280 | targetXRotation = targetXRotationOnMouseDown + (mouseX - mouseXOnMouseDown) * 0.05; 281 | 282 | mouseY = event.touches[0].pageY - windowHalfY; 283 | targetYRotation = targetYRotationOnMouseDown + (mouseY - mouseYOnMouseDown) * 0.05; 284 | } 285 | } 286 | 287 | sceneLoop = function() { 288 | if (object) { 289 | if (showPlane) { 290 | plane.rotation.z = object.rotation.z = (targetXRotation - object.rotation.z) * 0.2; 291 | plane.rotation.x = object.rotation.x = (targetYRotation - object.rotation.x) * 0.2; 292 | } else { 293 | object.rotation.z = (targetXRotation - object.rotation.z) * 0.2; 294 | object.rotation.x = (targetYRotation - object.rotation.x) * 0.2; 295 | } 296 | 297 | camera.updateMatrix(); 298 | object.updateMatrix(); 299 | 300 | if (showPlane) { 301 | plane.updateMatrix(); 302 | } 303 | 304 | renderer.render(scene, camera); 305 | } 306 | } 307 | 308 | rotateLoop = function() { 309 | targetXRotation += 0.05; 310 | sceneLoop(); 311 | } 312 | 313 | this.getShowPlane = function(){ 314 | return showPlane; 315 | } 316 | 317 | this.setShowPlane = function(show) { 318 | showPlane = show; 319 | 320 | if (show) { 321 | if (scene && !plane) { 322 | loadPlaneGeometry(); 323 | } 324 | plane.material[0].opacity = 1; 325 | // plane.updateMatrix(); 326 | } else { 327 | if (scene && plane) { 328 | // alert(plane.material[0].opacity); 329 | plane.material[0].opacity = 0; 330 | // plane.updateMatrix(); 331 | } 332 | } 333 | 334 | sceneLoop(); 335 | } 336 | 337 | this.getRotation = function() { 338 | return rotateTimer !== null; 339 | } 340 | 341 | this.setRotation = function(rotate) { 342 | rotation = rotate; 343 | 344 | if (rotate) { 345 | rotateTimer = setInterval(rotateLoop, 1000/60); 346 | } else { 347 | clearInterval(rotateTimer); 348 | rotateTimer = null; 349 | } 350 | 351 | scope.onSetRotation(); 352 | } 353 | 354 | this.onSetRotation = function(callback) { 355 | if(callback === undefined){ 356 | if(rotateListener !== null){ 357 | try{ 358 | rotateListener(scope.getRotation()); 359 | } catch(ignored) {} 360 | } 361 | } else { 362 | rotateListener = callback; 363 | } 364 | } 365 | 366 | this.setCameraView = function(dir) { 367 | cameraView = dir; 368 | 369 | targetXRotation = 0; 370 | targetYRotation = 0; 371 | 372 | if (object) { 373 | object.rotation.x = 0; 374 | object.rotation.y = 0; 375 | object.rotation.z = 0; 376 | } 377 | 378 | if (showPlane && object) { 379 | plane.rotation.x = object.rotation.x; 380 | plane.rotation.y = object.rotation.y; 381 | plane.rotation.z = object.rotation.z; 382 | } 383 | 384 | if (dir == 'top') { 385 | // camera.position.y = 0; 386 | // camera.position.z = 100; 387 | // camera.target.position.z = 0; 388 | if (showPlane) { 389 | plane.flipSided = false; 390 | } 391 | } else if (dir == 'side') { 392 | // camera.position.y = -70; 393 | // camera.position.z = 70; 394 | // camera.target.position.z = 0; 395 | targetYRotation = -4.5; 396 | if (showPlane) { 397 | plane.flipSided = false; 398 | } 399 | } else if (dir == 'bottom') { 400 | // camera.position.y = 0; 401 | // camera.position.z = -100; 402 | // camera.target.position.z = 0; 403 | if (showPlane) { 404 | plane.flipSided = true; 405 | } 406 | } else if (dir == 'iso') { 407 | targetXRotation = 45; 408 | targetYRotation = 0; 409 | if (showPlane) { 410 | plane.flipSided = true; 411 | } 412 | } else { 413 | // camera.position.y = -70; 414 | // camera.position.z = 70; 415 | // camera.target.position.z = 0; 416 | if (showPlane) { 417 | plane.flipSided = false; 418 | } 419 | } 420 | 421 | mouseX = targetXRotation; 422 | mouseXOnMouseDown = targetXRotation; 423 | 424 | mouseY = targetYRotation; 425 | mouseYOnMouseDown = targetYRotation; 426 | 427 | scope.centerCamera(); 428 | 429 | sceneLoop(); 430 | } 431 | 432 | this.setCameraZoom = function(factor) { 433 | cameraZoom = factor; 434 | 435 | if (cameraView == 'bottom') { 436 | if (camera.position.z + factor > 0) { 437 | factor = 0; 438 | } 439 | } else { 440 | if (camera.position.z - factor < 0) { 441 | factor = 0; 442 | } 443 | } 444 | 445 | if (cameraView == 'top') { 446 | camera.position.z -= factor; 447 | } else if (cameraView == 'bottom') { 448 | camera.position.z += factor; 449 | } else if (cameraView == 'side') { 450 | camera.position.y += factor; 451 | camera.position.z -= factor; 452 | } else { 453 | camera.position.y += factor; 454 | camera.position.z -= factor; 455 | } 456 | 457 | sceneLoop(); 458 | } 459 | 460 | this.getObjectMaterial = function() { 461 | return objectMaterial; 462 | } 463 | 464 | this.setObjectMaterial = function(type) { 465 | objectMaterial = type; 466 | 467 | loadObjectGeometry(); 468 | } 469 | 470 | this.setBackgroundColor = function(color) { 471 | backgroundColor = color 472 | 473 | if (renderer) { 474 | renderer.domElement.style.backgroundColor = color; 475 | } 476 | } 477 | 478 | this.setObjectColor = function(color) { 479 | objectColor = parseInt(color.replace(/\#/g, ''), 16); 480 | 481 | loadObjectGeometry(); 482 | } 483 | 484 | this.loadSTL = function(url) { 485 | scope.newWorker('loadSTL', url); 486 | } 487 | 488 | this.loadOBJ = function(url) { 489 | scope.newWorker('loadOBJ', url); 490 | } 491 | 492 | this.loadSTLString = function(STLString) { 493 | scope.newWorker('loadSTLString', STLString); 494 | } 495 | 496 | this.loadSTLBinary = function(STLBinary) { 497 | scope.newWorker('loadSTLBinary', STLBinary); 498 | } 499 | 500 | this.loadOBJString = function(OBJString) { 501 | scope.newWorker('loadOBJString', OBJString); 502 | } 503 | 504 | this.loadJSON = function(url) { 505 | scope.newWorker('loadJSON', url); 506 | } 507 | 508 | this.loadPLY = function(url) { 509 | scope.newWorker('loadPLY', url); 510 | } 511 | 512 | this.loadPLYString = function(PLYString) { 513 | scope.newWorker('loadPLYString', PLYString); 514 | } 515 | 516 | this.loadPLYBinary = function(PLYBinary) { 517 | scope.newWorker('loadPLYBinary', PLYBinary); 518 | } 519 | 520 | this.centerCamera = function() { 521 | if (geometry) { 522 | // Using method from http://msdn.microsoft.com/en-us/library/bb197900(v=xnagamestudio.10).aspx 523 | // log("bounding sphere radius = " + geometry.boundingSphere.radius); 524 | 525 | // look at the center of the object 526 | camera.target.position.x = geometry.center_x; 527 | camera.target.position.y = geometry.center_y; 528 | camera.target.position.z = geometry.center_z; 529 | 530 | // set camera position to center of sphere 531 | camera.position.x = geometry.center_x; 532 | camera.position.y = geometry.center_y; 533 | camera.position.z = geometry.center_z; 534 | 535 | // find distance to center 536 | distance = geometry.boundingSphere.radius / Math.sin((camera.fov/2) * (Math.PI / 180)); 537 | 538 | // zoom backwards about half that distance, I don't think I'm doing the math or backwards vector calculation correctly? 539 | // scope.setCameraZoom(-distance/1.8); 540 | // scope.setCameraZoom(-distance/1.5); 541 | scope.setCameraZoom(-distance/0.9); 542 | 543 | directionalLight.position.x = geometry.min_y * 2; 544 | directionalLight.position.y = geometry.min_y * 2; 545 | directionalLight.position.z = geometry.max_z * 2; 546 | 547 | pointLight.position.x = geometry.center_y; 548 | pointLight.position.y = geometry.center_y; 549 | pointLight.position.z = geometry.max_z * 2; 550 | } else { 551 | // set to any valid position so it doesn't fail before geometry is available 552 | camera.position.y = -70; 553 | camera.position.z = 70; 554 | camera.target.position.z = 0; 555 | } 556 | } 557 | 558 | this.loadArray = function(array) { 559 | log("loading array..."); 560 | geometry = new STLGeometry(array); 561 | loadObjectGeometry(); 562 | scope.setRotation(false); 563 | scope.setRotation(true); 564 | scope.centerCamera(); 565 | log("finished loading " + geometry.faces.length + " faces."); 566 | } 567 | 568 | this.progressBarMessage = function(msg){ 569 | progressBar.style.display = 'block'; 570 | progressBar.innerHTML = msg; 571 | } 572 | 573 | this.newWorker = function(cmd, param) { 574 | scope.setRotation(false); 575 | 576 | var worker = new WorkerFacade(thingiurlbase + '/thingiloader.js'); 577 | 578 | worker.onmessage = function(event) { 579 | if (event.data.status == "complete") { 580 | progressBar.innerHTML = 'Initializing geometry...'; 581 | // scene.removeObject(object); 582 | geometry = new STLGeometry(event.data.content); 583 | loadObjectGeometry(); 584 | progressBar.innerHTML = ''; 585 | progressBar.style.display = 'none'; 586 | 587 | scope.setRotation(false); 588 | //scope.setRotation(true); 589 | log("finished loading " + geometry.faces.length + " faces."); 590 | //thingiview.setCameraView(cameraView); 591 | scope.setCameraView(cameraView); 592 | scope.centerCamera(); 593 | } else if (event.data.status == "complete_points") { 594 | progressBar.innerHTML = 'Initializing points...'; 595 | 596 | geometry = new THREE_32.Geometry(); 597 | 598 | var material = new THREE_32.ParticleBasicMaterial( { color: 0xff0000, opacity: 1 } ); 599 | 600 | // material = new THREE_32.ParticleBasicMaterial( { size: 35, sizeAttenuation: false} ); 601 | // material.color.setHSV( 1.0, 0.2, 0.8 ); 602 | 603 | for (i in event.data.content[0]) { 604 | // for (var i=0; i<10; i++) { 605 | vector = new THREE_32.Vector3( event.data.content[0][i][0], event.data.content[0][i][1], event.data.content[0][i][2] ); 606 | geometry.vertices.push( new THREE_32.Vertex( vector ) ); 607 | } 608 | 609 | particles = new THREE_32.ParticleSystem( geometry, material ); 610 | particles.sortParticles = true; 611 | particles.updateMatrix(); 612 | scene.addObject( particles ); 613 | 614 | camera.updateMatrix(); 615 | renderer.render(scene, camera); 616 | 617 | progressBar.innerHTML = ''; 618 | progressBar.style.display = 'none'; 619 | 620 | scope.setRotation(false); 621 | scope.setRotation(true); 622 | log("finished loading " + event.data.content[0].length + " points."); 623 | // scope.centerCamera(); 624 | } else if (event.data.status == "progress") { 625 | progressBar.style.display = 'block'; 626 | progressBar.style.width = event.data.content; 627 | // log(event.data.content); 628 | } else if (event.data.status == "message") { 629 | progressBar.style.display = 'block'; 630 | progressBar.innerHTML = event.data.content; 631 | log(event.data.content); 632 | } else if (event.data.status == "alert") { 633 | scope.displayAlert(event.data.content); 634 | } else { 635 | alert('Error: ' + event.data); 636 | log('Unknown Worker Message: ' + event.data); 637 | } 638 | } 639 | 640 | worker.onerror = function(error) { 641 | log(error); 642 | error.preventDefault(); 643 | } 644 | 645 | worker.postMessage({'cmd':cmd, 'param':param}); 646 | } 647 | 648 | this.displayAlert = function(msg) { 649 | msg = msg + "

" 650 | 651 | alertBox.innerHTML = msg; 652 | alertBox.style.display = 'block'; 653 | 654 | // log(msg); 655 | } 656 | 657 | function loadPlaneGeometry() { 658 | // TODO: switch to lines instead of the Plane object so we can get rid of the horizontal lines in canvas renderer... 659 | plane = new THREE_32.Mesh(new Plane(100, 100, 10, 10), new THREE_32.MeshBasicMaterial({color:0xafafaf,wireframe:true})); 660 | scene.addObject(plane); 661 | } 662 | 663 | function loadObjectGeometry() { 664 | if (scene && geometry) { 665 | if (objectMaterial == 'wireframe') { 666 | material = new THREE_32.MeshBasicMaterial({color:objectColor,wireframe:true}); 667 | } else { 668 | if (isWebGl) { 669 | material = new THREE_32.MeshLambertMaterial({color:objectColor, shading: THREE_32.FlatShading}); 670 | } else { 671 | material = new THREE_32.MeshLambertMaterial({color:objectColor, shading: THREE_32.FlatShading}); 672 | } 673 | } 674 | 675 | if (object) { 676 | // shouldn't be needed, but this fixes a bug with webgl not removing previous object when loading a new one dynamically 677 | object.materials = [new THREE_32.MeshBasicMaterial({color:0xffffff, opacity:0})]; 678 | scene.removeObject(object); 679 | } 680 | 681 | object = new THREE_32.Mesh(geometry, material); 682 | scene.addObject(object); 683 | 684 | if (objectMaterial != 'wireframe') { 685 | object.overdraw = true; 686 | object.doubleSided = true; 687 | } 688 | 689 | object.updateMatrix(); 690 | 691 | targetXRotation = 0; 692 | targetYRotation = 0; 693 | 694 | sceneLoop(); 695 | } 696 | } 697 | 698 | }; 699 | 700 | var STLGeometry = function(stlArray) { 701 | // log("building geometry..."); 702 | THREE_32.Geometry.call(this); 703 | 704 | var scope = this; 705 | 706 | for (var i=0; i 0){ 798 | theworker.postToWorkerFunction(callings[0]); 799 | callings.shift(); 800 | } 801 | }; 802 | document.body.appendChild(scr); 803 | 804 | var binaryscr = document.createElement("SCRIPT"); 805 | binaryscr.src = thingiurlbase + '/binaryReader.js'; 806 | binaryscr.type = "text/javascript"; 807 | document.body.appendChild(binaryscr); 808 | 809 | return theworker; 810 | }; 811 | that.fake = true; 812 | that.add = function(pth, worker){ 813 | workers[pth] = worker; 814 | return function(param){ 815 | masters[pth].onmessage({"data": param}); 816 | }; 817 | }; 818 | that.toString = function(){ 819 | return "FakeWorker('"+path+"')"; 820 | }; 821 | return that; 822 | }()); 823 | } 824 | -------------------------------------------------------------------------------- /css/bootstrap/bootstrap-responsive.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap Responsive v2.2.2 3 | * 4 | * Copyright 2012 Twitter, Inc 5 | * Licensed under the Apache License v2.0 6 | * http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Designed and built with all the love in the world @twitter by @mdo and @fat. 9 | */ 10 | 11 | @-ms-viewport { 12 | width: device-width; 13 | } 14 | 15 | .clearfix { 16 | *zoom: 1; 17 | } 18 | 19 | .clearfix:before, 20 | .clearfix:after { 21 | display: table; 22 | line-height: 0; 23 | content: ""; 24 | } 25 | 26 | .clearfix:after { 27 | clear: both; 28 | } 29 | 30 | .hide-text { 31 | font: 0/0 a; 32 | color: transparent; 33 | text-shadow: none; 34 | background-color: transparent; 35 | border: 0; 36 | } 37 | 38 | .input-block-level { 39 | display: block; 40 | width: 100%; 41 | min-height: 30px; 42 | -webkit-box-sizing: border-box; 43 | -moz-box-sizing: border-box; 44 | box-sizing: border-box; 45 | } 46 | 47 | .hidden { 48 | display: none; 49 | visibility: hidden; 50 | } 51 | 52 | .visible-phone { 53 | display: none !important; 54 | } 55 | 56 | .visible-tablet { 57 | display: none !important; 58 | } 59 | 60 | .hidden-desktop { 61 | display: none !important; 62 | } 63 | 64 | .visible-desktop { 65 | display: inherit !important; 66 | } 67 | 68 | @media (min-width: 768px) and (max-width: 979px) { 69 | .hidden-desktop { 70 | display: inherit !important; 71 | } 72 | .visible-desktop { 73 | display: none !important ; 74 | } 75 | .visible-tablet { 76 | display: inherit !important; 77 | } 78 | .hidden-tablet { 79 | display: none !important; 80 | } 81 | } 82 | 83 | @media (max-width: 767px) { 84 | .hidden-desktop { 85 | display: inherit !important; 86 | } 87 | .visible-desktop { 88 | display: none !important; 89 | } 90 | .visible-phone { 91 | display: inherit !important; 92 | } 93 | .hidden-phone { 94 | display: none !important; 95 | } 96 | } 97 | 98 | @media (min-width: 1200px) { 99 | .row { 100 | margin-left: -30px; 101 | *zoom: 1; 102 | } 103 | .row:before, 104 | .row:after { 105 | display: table; 106 | line-height: 0; 107 | content: ""; 108 | } 109 | .row:after { 110 | clear: both; 111 | } 112 | [class*="span"] { 113 | float: left; 114 | min-height: 1px; 115 | margin-left: 30px; 116 | } 117 | .container, 118 | .navbar-static-top .container, 119 | .navbar-fixed-top .container, 120 | .navbar-fixed-bottom .container { 121 | width: 1170px; 122 | } 123 | .span12 { 124 | width: 1170px; 125 | } 126 | .span11 { 127 | width: 1070px; 128 | } 129 | .span10 { 130 | width: 970px; 131 | } 132 | .span9 { 133 | width: 870px; 134 | } 135 | .span8 { 136 | width: 770px; 137 | } 138 | .span7 { 139 | width: 670px; 140 | } 141 | .span6 { 142 | width: 570px; 143 | } 144 | .span5 { 145 | width: 470px; 146 | } 147 | .span4 { 148 | width: 370px; 149 | } 150 | .span3 { 151 | width: 270px; 152 | } 153 | .span2 { 154 | width: 170px; 155 | } 156 | .span1 { 157 | width: 70px; 158 | } 159 | .offset12 { 160 | margin-left: 1230px; 161 | } 162 | .offset11 { 163 | margin-left: 1130px; 164 | } 165 | .offset10 { 166 | margin-left: 1030px; 167 | } 168 | .offset9 { 169 | margin-left: 930px; 170 | } 171 | .offset8 { 172 | margin-left: 830px; 173 | } 174 | .offset7 { 175 | margin-left: 730px; 176 | } 177 | .offset6 { 178 | margin-left: 630px; 179 | } 180 | .offset5 { 181 | margin-left: 530px; 182 | } 183 | .offset4 { 184 | margin-left: 430px; 185 | } 186 | .offset3 { 187 | margin-left: 330px; 188 | } 189 | .offset2 { 190 | margin-left: 230px; 191 | } 192 | .offset1 { 193 | margin-left: 130px; 194 | } 195 | .row-fluid { 196 | width: 100%; 197 | *zoom: 1; 198 | } 199 | .row-fluid:before, 200 | .row-fluid:after { 201 | display: table; 202 | line-height: 0; 203 | content: ""; 204 | } 205 | .row-fluid:after { 206 | clear: both; 207 | } 208 | .row-fluid [class*="span"] { 209 | display: block; 210 | float: left; 211 | width: 100%; 212 | min-height: 30px; 213 | margin-left: 2.564102564102564%; 214 | *margin-left: 2.5109110747408616%; 215 | -webkit-box-sizing: border-box; 216 | -moz-box-sizing: border-box; 217 | box-sizing: border-box; 218 | } 219 | .row-fluid [class*="span"]:first-child { 220 | margin-left: 0; 221 | } 222 | .row-fluid .controls-row [class*="span"] + [class*="span"] { 223 | margin-left: 2.564102564102564%; 224 | } 225 | .row-fluid .span12 { 226 | width: 100%; 227 | *width: 99.94680851063829%; 228 | } 229 | .row-fluid .span11 { 230 | width: 91.45299145299145%; 231 | *width: 91.39979996362975%; 232 | } 233 | .row-fluid .span10 { 234 | width: 82.90598290598291%; 235 | *width: 82.8527914166212%; 236 | } 237 | .row-fluid .span9 { 238 | width: 74.35897435897436%; 239 | *width: 74.30578286961266%; 240 | } 241 | .row-fluid .span8 { 242 | width: 65.81196581196582%; 243 | *width: 65.75877432260411%; 244 | } 245 | .row-fluid .span7 { 246 | width: 57.26495726495726%; 247 | *width: 57.21176577559556%; 248 | } 249 | .row-fluid .span6 { 250 | width: 48.717948717948715%; 251 | *width: 48.664757228587014%; 252 | } 253 | .row-fluid .span5 { 254 | width: 40.17094017094017%; 255 | *width: 40.11774868157847%; 256 | } 257 | .row-fluid .span4 { 258 | width: 31.623931623931625%; 259 | *width: 31.570740134569924%; 260 | } 261 | .row-fluid .span3 { 262 | width: 23.076923076923077%; 263 | *width: 23.023731587561375%; 264 | } 265 | .row-fluid .span2 { 266 | width: 14.52991452991453%; 267 | *width: 14.476723040552828%; 268 | } 269 | .row-fluid .span1 { 270 | width: 5.982905982905983%; 271 | *width: 5.929714493544281%; 272 | } 273 | .row-fluid .offset12 { 274 | margin-left: 105.12820512820512%; 275 | *margin-left: 105.02182214948171%; 276 | } 277 | .row-fluid .offset12:first-child { 278 | margin-left: 102.56410256410257%; 279 | *margin-left: 102.45771958537915%; 280 | } 281 | .row-fluid .offset11 { 282 | margin-left: 96.58119658119658%; 283 | *margin-left: 96.47481360247316%; 284 | } 285 | .row-fluid .offset11:first-child { 286 | margin-left: 94.01709401709402%; 287 | *margin-left: 93.91071103837061%; 288 | } 289 | .row-fluid .offset10 { 290 | margin-left: 88.03418803418803%; 291 | *margin-left: 87.92780505546462%; 292 | } 293 | .row-fluid .offset10:first-child { 294 | margin-left: 85.47008547008548%; 295 | *margin-left: 85.36370249136206%; 296 | } 297 | .row-fluid .offset9 { 298 | margin-left: 79.48717948717949%; 299 | *margin-left: 79.38079650845607%; 300 | } 301 | .row-fluid .offset9:first-child { 302 | margin-left: 76.92307692307693%; 303 | *margin-left: 76.81669394435352%; 304 | } 305 | .row-fluid .offset8 { 306 | margin-left: 70.94017094017094%; 307 | *margin-left: 70.83378796144753%; 308 | } 309 | .row-fluid .offset8:first-child { 310 | margin-left: 68.37606837606839%; 311 | *margin-left: 68.26968539734497%; 312 | } 313 | .row-fluid .offset7 { 314 | margin-left: 62.393162393162385%; 315 | *margin-left: 62.28677941443899%; 316 | } 317 | .row-fluid .offset7:first-child { 318 | margin-left: 59.82905982905982%; 319 | *margin-left: 59.72267685033642%; 320 | } 321 | .row-fluid .offset6 { 322 | margin-left: 53.84615384615384%; 323 | *margin-left: 53.739770867430444%; 324 | } 325 | .row-fluid .offset6:first-child { 326 | margin-left: 51.28205128205128%; 327 | *margin-left: 51.175668303327875%; 328 | } 329 | .row-fluid .offset5 { 330 | margin-left: 45.299145299145295%; 331 | *margin-left: 45.1927623204219%; 332 | } 333 | .row-fluid .offset5:first-child { 334 | margin-left: 42.73504273504273%; 335 | *margin-left: 42.62865975631933%; 336 | } 337 | .row-fluid .offset4 { 338 | margin-left: 36.75213675213675%; 339 | *margin-left: 36.645753773413354%; 340 | } 341 | .row-fluid .offset4:first-child { 342 | margin-left: 34.18803418803419%; 343 | *margin-left: 34.081651209310785%; 344 | } 345 | .row-fluid .offset3 { 346 | margin-left: 28.205128205128204%; 347 | *margin-left: 28.0987452264048%; 348 | } 349 | .row-fluid .offset3:first-child { 350 | margin-left: 25.641025641025642%; 351 | *margin-left: 25.53464266230224%; 352 | } 353 | .row-fluid .offset2 { 354 | margin-left: 19.65811965811966%; 355 | *margin-left: 19.551736679396257%; 356 | } 357 | .row-fluid .offset2:first-child { 358 | margin-left: 17.094017094017094%; 359 | *margin-left: 16.98763411529369%; 360 | } 361 | .row-fluid .offset1 { 362 | margin-left: 11.11111111111111%; 363 | *margin-left: 11.004728132387708%; 364 | } 365 | .row-fluid .offset1:first-child { 366 | margin-left: 8.547008547008547%; 367 | *margin-left: 8.440625568285142%; 368 | } 369 | input, 370 | textarea, 371 | .uneditable-input { 372 | margin-left: 0; 373 | } 374 | .controls-row [class*="span"] + [class*="span"] { 375 | margin-left: 30px; 376 | } 377 | input.span12, 378 | textarea.span12, 379 | .uneditable-input.span12 { 380 | width: 1156px; 381 | } 382 | input.span11, 383 | textarea.span11, 384 | .uneditable-input.span11 { 385 | width: 1056px; 386 | } 387 | input.span10, 388 | textarea.span10, 389 | .uneditable-input.span10 { 390 | width: 956px; 391 | } 392 | input.span9, 393 | textarea.span9, 394 | .uneditable-input.span9 { 395 | width: 856px; 396 | } 397 | input.span8, 398 | textarea.span8, 399 | .uneditable-input.span8 { 400 | width: 756px; 401 | } 402 | input.span7, 403 | textarea.span7, 404 | .uneditable-input.span7 { 405 | width: 656px; 406 | } 407 | input.span6, 408 | textarea.span6, 409 | .uneditable-input.span6 { 410 | width: 556px; 411 | } 412 | input.span5, 413 | textarea.span5, 414 | .uneditable-input.span5 { 415 | width: 456px; 416 | } 417 | input.span4, 418 | textarea.span4, 419 | .uneditable-input.span4 { 420 | width: 356px; 421 | } 422 | input.span3, 423 | textarea.span3, 424 | .uneditable-input.span3 { 425 | width: 256px; 426 | } 427 | input.span2, 428 | textarea.span2, 429 | .uneditable-input.span2 { 430 | width: 156px; 431 | } 432 | input.span1, 433 | textarea.span1, 434 | .uneditable-input.span1 { 435 | width: 56px; 436 | } 437 | .thumbnails { 438 | margin-left: -30px; 439 | } 440 | .thumbnails > li { 441 | margin-left: 30px; 442 | } 443 | .row-fluid .thumbnails { 444 | margin-left: 0; 445 | } 446 | } 447 | 448 | @media (min-width: 768px) and (max-width: 979px) { 449 | .row { 450 | margin-left: -20px; 451 | *zoom: 1; 452 | } 453 | .row:before, 454 | .row:after { 455 | display: table; 456 | line-height: 0; 457 | content: ""; 458 | } 459 | .row:after { 460 | clear: both; 461 | } 462 | [class*="span"] { 463 | float: left; 464 | min-height: 1px; 465 | margin-left: 20px; 466 | } 467 | .container, 468 | .navbar-static-top .container, 469 | .navbar-fixed-top .container, 470 | .navbar-fixed-bottom .container { 471 | width: 724px; 472 | } 473 | .span12 { 474 | width: 724px; 475 | } 476 | .span11 { 477 | width: 662px; 478 | } 479 | .span10 { 480 | width: 600px; 481 | } 482 | .span9 { 483 | width: 538px; 484 | } 485 | .span8 { 486 | width: 476px; 487 | } 488 | .span7 { 489 | width: 414px; 490 | } 491 | .span6 { 492 | width: 352px; 493 | } 494 | .span5 { 495 | width: 290px; 496 | } 497 | .span4 { 498 | width: 228px; 499 | } 500 | .span3 { 501 | width: 166px; 502 | } 503 | .span2 { 504 | width: 104px; 505 | } 506 | .span1 { 507 | width: 42px; 508 | } 509 | .offset12 { 510 | margin-left: 764px; 511 | } 512 | .offset11 { 513 | margin-left: 702px; 514 | } 515 | .offset10 { 516 | margin-left: 640px; 517 | } 518 | .offset9 { 519 | margin-left: 578px; 520 | } 521 | .offset8 { 522 | margin-left: 516px; 523 | } 524 | .offset7 { 525 | margin-left: 454px; 526 | } 527 | .offset6 { 528 | margin-left: 392px; 529 | } 530 | .offset5 { 531 | margin-left: 330px; 532 | } 533 | .offset4 { 534 | margin-left: 268px; 535 | } 536 | .offset3 { 537 | margin-left: 206px; 538 | } 539 | .offset2 { 540 | margin-left: 144px; 541 | } 542 | .offset1 { 543 | margin-left: 82px; 544 | } 545 | .row-fluid { 546 | width: 100%; 547 | *zoom: 1; 548 | } 549 | .row-fluid:before, 550 | .row-fluid:after { 551 | display: table; 552 | line-height: 0; 553 | content: ""; 554 | } 555 | .row-fluid:after { 556 | clear: both; 557 | } 558 | .row-fluid [class*="span"] { 559 | display: block; 560 | float: left; 561 | width: 100%; 562 | min-height: 30px; 563 | margin-left: 2.7624309392265194%; 564 | *margin-left: 2.709239449864817%; 565 | -webkit-box-sizing: border-box; 566 | -moz-box-sizing: border-box; 567 | box-sizing: border-box; 568 | } 569 | .row-fluid [class*="span"]:first-child { 570 | margin-left: 0; 571 | } 572 | .row-fluid .controls-row [class*="span"] + [class*="span"] { 573 | margin-left: 2.7624309392265194%; 574 | } 575 | .row-fluid .span12 { 576 | width: 100%; 577 | *width: 99.94680851063829%; 578 | } 579 | .row-fluid .span11 { 580 | width: 91.43646408839778%; 581 | *width: 91.38327259903608%; 582 | } 583 | .row-fluid .span10 { 584 | width: 82.87292817679558%; 585 | *width: 82.81973668743387%; 586 | } 587 | .row-fluid .span9 { 588 | width: 74.30939226519337%; 589 | *width: 74.25620077583166%; 590 | } 591 | .row-fluid .span8 { 592 | width: 65.74585635359117%; 593 | *width: 65.69266486422946%; 594 | } 595 | .row-fluid .span7 { 596 | width: 57.18232044198895%; 597 | *width: 57.12912895262725%; 598 | } 599 | .row-fluid .span6 { 600 | width: 48.61878453038674%; 601 | *width: 48.56559304102504%; 602 | } 603 | .row-fluid .span5 { 604 | width: 40.05524861878453%; 605 | *width: 40.00205712942283%; 606 | } 607 | .row-fluid .span4 { 608 | width: 31.491712707182323%; 609 | *width: 31.43852121782062%; 610 | } 611 | .row-fluid .span3 { 612 | width: 22.92817679558011%; 613 | *width: 22.87498530621841%; 614 | } 615 | .row-fluid .span2 { 616 | width: 14.3646408839779%; 617 | *width: 14.311449394616199%; 618 | } 619 | .row-fluid .span1 { 620 | width: 5.801104972375691%; 621 | *width: 5.747913483013988%; 622 | } 623 | .row-fluid .offset12 { 624 | margin-left: 105.52486187845304%; 625 | *margin-left: 105.41847889972962%; 626 | } 627 | .row-fluid .offset12:first-child { 628 | margin-left: 102.76243093922652%; 629 | *margin-left: 102.6560479605031%; 630 | } 631 | .row-fluid .offset11 { 632 | margin-left: 96.96132596685082%; 633 | *margin-left: 96.8549429881274%; 634 | } 635 | .row-fluid .offset11:first-child { 636 | margin-left: 94.1988950276243%; 637 | *margin-left: 94.09251204890089%; 638 | } 639 | .row-fluid .offset10 { 640 | margin-left: 88.39779005524862%; 641 | *margin-left: 88.2914070765252%; 642 | } 643 | .row-fluid .offset10:first-child { 644 | margin-left: 85.6353591160221%; 645 | *margin-left: 85.52897613729868%; 646 | } 647 | .row-fluid .offset9 { 648 | margin-left: 79.8342541436464%; 649 | *margin-left: 79.72787116492299%; 650 | } 651 | .row-fluid .offset9:first-child { 652 | margin-left: 77.07182320441989%; 653 | *margin-left: 76.96544022569647%; 654 | } 655 | .row-fluid .offset8 { 656 | margin-left: 71.2707182320442%; 657 | *margin-left: 71.16433525332079%; 658 | } 659 | .row-fluid .offset8:first-child { 660 | margin-left: 68.50828729281768%; 661 | *margin-left: 68.40190431409427%; 662 | } 663 | .row-fluid .offset7 { 664 | margin-left: 62.70718232044199%; 665 | *margin-left: 62.600799341718584%; 666 | } 667 | .row-fluid .offset7:first-child { 668 | margin-left: 59.94475138121547%; 669 | *margin-left: 59.838368402492065%; 670 | } 671 | .row-fluid .offset6 { 672 | margin-left: 54.14364640883978%; 673 | *margin-left: 54.037263430116376%; 674 | } 675 | .row-fluid .offset6:first-child { 676 | margin-left: 51.38121546961326%; 677 | *margin-left: 51.27483249088986%; 678 | } 679 | .row-fluid .offset5 { 680 | margin-left: 45.58011049723757%; 681 | *margin-left: 45.47372751851417%; 682 | } 683 | .row-fluid .offset5:first-child { 684 | margin-left: 42.81767955801105%; 685 | *margin-left: 42.71129657928765%; 686 | } 687 | .row-fluid .offset4 { 688 | margin-left: 37.01657458563536%; 689 | *margin-left: 36.91019160691196%; 690 | } 691 | .row-fluid .offset4:first-child { 692 | margin-left: 34.25414364640884%; 693 | *margin-left: 34.14776066768544%; 694 | } 695 | .row-fluid .offset3 { 696 | margin-left: 28.45303867403315%; 697 | *margin-left: 28.346655695309746%; 698 | } 699 | .row-fluid .offset3:first-child { 700 | margin-left: 25.69060773480663%; 701 | *margin-left: 25.584224756083227%; 702 | } 703 | .row-fluid .offset2 { 704 | margin-left: 19.88950276243094%; 705 | *margin-left: 19.783119783707537%; 706 | } 707 | .row-fluid .offset2:first-child { 708 | margin-left: 17.12707182320442%; 709 | *margin-left: 17.02068884448102%; 710 | } 711 | .row-fluid .offset1 { 712 | margin-left: 11.32596685082873%; 713 | *margin-left: 11.219583872105325%; 714 | } 715 | .row-fluid .offset1:first-child { 716 | margin-left: 8.56353591160221%; 717 | *margin-left: 8.457152932878806%; 718 | } 719 | input, 720 | textarea, 721 | .uneditable-input { 722 | margin-left: 0; 723 | } 724 | .controls-row [class*="span"] + [class*="span"] { 725 | margin-left: 20px; 726 | } 727 | input.span12, 728 | textarea.span12, 729 | .uneditable-input.span12 { 730 | width: 710px; 731 | } 732 | input.span11, 733 | textarea.span11, 734 | .uneditable-input.span11 { 735 | width: 648px; 736 | } 737 | input.span10, 738 | textarea.span10, 739 | .uneditable-input.span10 { 740 | width: 586px; 741 | } 742 | input.span9, 743 | textarea.span9, 744 | .uneditable-input.span9 { 745 | width: 524px; 746 | } 747 | input.span8, 748 | textarea.span8, 749 | .uneditable-input.span8 { 750 | width: 462px; 751 | } 752 | input.span7, 753 | textarea.span7, 754 | .uneditable-input.span7 { 755 | width: 400px; 756 | } 757 | input.span6, 758 | textarea.span6, 759 | .uneditable-input.span6 { 760 | width: 338px; 761 | } 762 | input.span5, 763 | textarea.span5, 764 | .uneditable-input.span5 { 765 | width: 276px; 766 | } 767 | input.span4, 768 | textarea.span4, 769 | .uneditable-input.span4 { 770 | width: 214px; 771 | } 772 | input.span3, 773 | textarea.span3, 774 | .uneditable-input.span3 { 775 | width: 152px; 776 | } 777 | input.span2, 778 | textarea.span2, 779 | .uneditable-input.span2 { 780 | width: 90px; 781 | } 782 | input.span1, 783 | textarea.span1, 784 | .uneditable-input.span1 { 785 | width: 28px; 786 | } 787 | } 788 | 789 | @media (max-width: 767px) { 790 | body { 791 | padding-right: 20px; 792 | padding-left: 20px; 793 | } 794 | .navbar-fixed-top, 795 | .navbar-fixed-bottom, 796 | .navbar-static-top { 797 | margin-right: -20px; 798 | margin-left: -20px; 799 | } 800 | .container-fluid { 801 | padding: 0; 802 | } 803 | .dl-horizontal dt { 804 | float: none; 805 | width: auto; 806 | clear: none; 807 | text-align: left; 808 | } 809 | .dl-horizontal dd { 810 | margin-left: 0; 811 | } 812 | .container { 813 | width: auto; 814 | } 815 | .row-fluid { 816 | width: 100%; 817 | } 818 | .row, 819 | .thumbnails { 820 | margin-left: 0; 821 | } 822 | .thumbnails > li { 823 | float: none; 824 | margin-left: 0; 825 | } 826 | [class*="span"], 827 | .uneditable-input[class*="span"], 828 | .row-fluid [class*="span"] { 829 | display: block; 830 | float: none; 831 | width: 100%; 832 | margin-left: 0; 833 | -webkit-box-sizing: border-box; 834 | -moz-box-sizing: border-box; 835 | box-sizing: border-box; 836 | } 837 | .span12, 838 | .row-fluid .span12 { 839 | width: 100%; 840 | -webkit-box-sizing: border-box; 841 | -moz-box-sizing: border-box; 842 | box-sizing: border-box; 843 | } 844 | .row-fluid [class*="offset"]:first-child { 845 | margin-left: 0; 846 | } 847 | .input-large, 848 | .input-xlarge, 849 | .input-xxlarge, 850 | input[class*="span"], 851 | select[class*="span"], 852 | textarea[class*="span"], 853 | .uneditable-input { 854 | display: block; 855 | width: 100%; 856 | min-height: 30px; 857 | -webkit-box-sizing: border-box; 858 | -moz-box-sizing: border-box; 859 | box-sizing: border-box; 860 | } 861 | .input-prepend input, 862 | .input-append input, 863 | .input-prepend input[class*="span"], 864 | .input-append input[class*="span"] { 865 | display: inline-block; 866 | width: auto; 867 | } 868 | .controls-row [class*="span"] + [class*="span"] { 869 | margin-left: 0; 870 | } 871 | .modal { 872 | position: fixed; 873 | top: 20px; 874 | right: 20px; 875 | left: 20px; 876 | width: auto; 877 | margin: 0; 878 | } 879 | .modal.fade { 880 | top: -100px; 881 | } 882 | .modal.fade.in { 883 | top: 20px; 884 | } 885 | } 886 | 887 | @media (max-width: 480px) { 888 | .nav-collapse { 889 | -webkit-transform: translate3d(0, 0, 0); 890 | } 891 | .page-header h1 small { 892 | display: block; 893 | line-height: 20px; 894 | } 895 | input[type="checkbox"], 896 | input[type="radio"] { 897 | border: 1px solid #ccc; 898 | } 899 | .form-horizontal .control-label { 900 | float: none; 901 | width: auto; 902 | padding-top: 0; 903 | text-align: left; 904 | } 905 | .form-horizontal .controls { 906 | margin-left: 0; 907 | } 908 | .form-horizontal .control-list { 909 | padding-top: 0; 910 | } 911 | .form-horizontal .form-actions { 912 | padding-right: 10px; 913 | padding-left: 10px; 914 | } 915 | .media .pull-left, 916 | .media .pull-right { 917 | display: block; 918 | float: none; 919 | margin-bottom: 10px; 920 | } 921 | .media-object { 922 | margin-right: 0; 923 | margin-left: 0; 924 | } 925 | .modal { 926 | top: 10px; 927 | right: 10px; 928 | left: 10px; 929 | } 930 | .modal-header .close { 931 | padding: 10px; 932 | margin: -10px; 933 | } 934 | .carousel-caption { 935 | position: static; 936 | } 937 | } 938 | 939 | @media (max-width: 979px) { 940 | body { 941 | padding-top: 0; 942 | } 943 | .navbar-fixed-top, 944 | .navbar-fixed-bottom { 945 | position: static; 946 | } 947 | .navbar-fixed-top { 948 | margin-bottom: 20px; 949 | } 950 | .navbar-fixed-bottom { 951 | margin-top: 20px; 952 | } 953 | .navbar-fixed-top .navbar-inner, 954 | .navbar-fixed-bottom .navbar-inner { 955 | padding: 5px; 956 | } 957 | .navbar .container { 958 | width: auto; 959 | padding: 0; 960 | } 961 | .navbar .brand { 962 | padding-right: 10px; 963 | padding-left: 10px; 964 | margin: 0 0 0 -5px; 965 | } 966 | .nav-collapse { 967 | clear: both; 968 | } 969 | .nav-collapse .nav { 970 | float: none; 971 | margin: 0 0 10px; 972 | } 973 | .nav-collapse .nav > li { 974 | float: none; 975 | } 976 | .nav-collapse .nav > li > a { 977 | margin-bottom: 2px; 978 | } 979 | .nav-collapse .nav > .divider-vertical { 980 | display: none; 981 | } 982 | .nav-collapse .nav .nav-header { 983 | color: #777777; 984 | text-shadow: none; 985 | } 986 | .nav-collapse .nav > li > a, 987 | .nav-collapse .dropdown-menu a { 988 | padding: 9px 15px; 989 | font-weight: bold; 990 | color: #777777; 991 | -webkit-border-radius: 3px; 992 | -moz-border-radius: 3px; 993 | border-radius: 3px; 994 | } 995 | .nav-collapse .btn { 996 | padding: 4px 10px 4px; 997 | font-weight: normal; 998 | -webkit-border-radius: 4px; 999 | -moz-border-radius: 4px; 1000 | border-radius: 4px; 1001 | } 1002 | .nav-collapse .dropdown-menu li + li a { 1003 | margin-bottom: 2px; 1004 | } 1005 | .nav-collapse .nav > li > a:hover, 1006 | .nav-collapse .dropdown-menu a:hover { 1007 | background-color: #f2f2f2; 1008 | } 1009 | .navbar-inverse .nav-collapse .nav > li > a, 1010 | .navbar-inverse .nav-collapse .dropdown-menu a { 1011 | color: #999999; 1012 | } 1013 | .navbar-inverse .nav-collapse .nav > li > a:hover, 1014 | .navbar-inverse .nav-collapse .dropdown-menu a:hover { 1015 | background-color: #111111; 1016 | } 1017 | .nav-collapse.in .btn-group { 1018 | padding: 0; 1019 | margin-top: 5px; 1020 | } 1021 | .nav-collapse .dropdown-menu { 1022 | position: static; 1023 | top: auto; 1024 | left: auto; 1025 | display: none; 1026 | float: none; 1027 | max-width: none; 1028 | padding: 0; 1029 | margin: 0 15px; 1030 | background-color: transparent; 1031 | border: none; 1032 | -webkit-border-radius: 0; 1033 | -moz-border-radius: 0; 1034 | border-radius: 0; 1035 | -webkit-box-shadow: none; 1036 | -moz-box-shadow: none; 1037 | box-shadow: none; 1038 | } 1039 | .nav-collapse .open > .dropdown-menu { 1040 | display: block; 1041 | } 1042 | .nav-collapse .dropdown-menu:before, 1043 | .nav-collapse .dropdown-menu:after { 1044 | display: none; 1045 | } 1046 | .nav-collapse .dropdown-menu .divider { 1047 | display: none; 1048 | } 1049 | .nav-collapse .nav > li > .dropdown-menu:before, 1050 | .nav-collapse .nav > li > .dropdown-menu:after { 1051 | display: none; 1052 | } 1053 | .nav-collapse .navbar-form, 1054 | .nav-collapse .navbar-search { 1055 | float: none; 1056 | padding: 10px 15px; 1057 | margin: 10px 0; 1058 | border-top: 1px solid #f2f2f2; 1059 | border-bottom: 1px solid #f2f2f2; 1060 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 1061 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 1062 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); 1063 | } 1064 | .navbar-inverse .nav-collapse .navbar-form, 1065 | .navbar-inverse .nav-collapse .navbar-search { 1066 | border-top-color: #111111; 1067 | border-bottom-color: #111111; 1068 | } 1069 | .navbar .nav-collapse .nav.pull-right { 1070 | float: none; 1071 | margin-left: 0; 1072 | } 1073 | .nav-collapse, 1074 | .nav-collapse.collapse { 1075 | height: 0; 1076 | overflow: hidden; 1077 | } 1078 | .navbar .btn-navbar { 1079 | display: block; 1080 | } 1081 | .navbar-static .navbar-inner { 1082 | padding-right: 10px; 1083 | padding-left: 10px; 1084 | } 1085 | } 1086 | 1087 | @media (min-width: 980px) { 1088 | .nav-collapse.collapse { 1089 | height: auto !important; 1090 | overflow: visible !important; 1091 | } 1092 | } 1093 | -------------------------------------------------------------------------------- /js/bootstrap.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap.js by @fat & @mdo 3 | * Copyright 2012 Twitter, Inc. 4 | * http://www.apache.org/licenses/LICENSE-2.0.txt 5 | */ 6 | !function($){"use strict";$(function(){$.support.transition=function(){var transitionEnd=function(){var name,el=document.createElement("bootstrap"),transEndEventNames={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(name in transEndEventNames)if(void 0!==el.style[name])return transEndEventNames[name]}();return transitionEnd&&{end:transitionEnd}}()})}(window.jQuery),!function($){"use strict";var dismiss='[data-dismiss="alert"]',Alert=function(el){$(el).on("click",dismiss,this.close)};Alert.prototype.close=function(e){function removeElement(){$parent.trigger("closed").remove()}var $parent,$this=$(this),selector=$this.attr("data-target");selector||(selector=$this.attr("href"),selector=selector&&selector.replace(/.*(?=#[^\s]*$)/,"")),$parent=$(selector),e&&e.preventDefault(),$parent.length||($parent=$this.hasClass("alert")?$this:$this.parent()),$parent.trigger(e=$.Event("close")),e.isDefaultPrevented()||($parent.removeClass("in"),$.support.transition&&$parent.hasClass("fade")?$parent.on($.support.transition.end,removeElement):removeElement())};var old=$.fn.alert;$.fn.alert=function(option){return this.each(function(){var $this=$(this),data=$this.data("alert");data||$this.data("alert",data=new Alert(this)),"string"==typeof option&&data[option].call($this)})},$.fn.alert.Constructor=Alert,$.fn.alert.noConflict=function(){return $.fn.alert=old,this},$(document).on("click.alert.data-api",dismiss,Alert.prototype.close)}(window.jQuery),!function($){"use strict";var Button=function(element,options){this.$element=$(element),this.options=$.extend({},$.fn.button.defaults,options)};Button.prototype.setState=function(state){var d="disabled",$el=this.$element,data=$el.data(),val=$el.is("input")?"val":"html";state+="Text",data.resetText||$el.data("resetText",$el[val]()),$el[val](data[state]||this.options[state]),setTimeout(function(){"loadingText"==state?$el.addClass(d).attr(d,d):$el.removeClass(d).removeAttr(d)},0)},Button.prototype.toggle=function(){var $parent=this.$element.closest('[data-toggle="buttons-radio"]');$parent&&$parent.find(".active").removeClass("active"),this.$element.toggleClass("active")};var old=$.fn.button;$.fn.button=function(option){return this.each(function(){var $this=$(this),data=$this.data("button"),options="object"==typeof option&&option;data||$this.data("button",data=new Button(this,options)),"toggle"==option?data.toggle():option&&data.setState(option)})},$.fn.button.defaults={loadingText:"loading..."},$.fn.button.Constructor=Button,$.fn.button.noConflict=function(){return $.fn.button=old,this},$(document).on("click.button.data-api","[data-toggle^=button]",function(e){var $btn=$(e.target);$btn.hasClass("btn")||($btn=$btn.closest(".btn")),$btn.button("toggle")})}(window.jQuery),!function($){"use strict";var Carousel=function(element,options){this.$element=$(element),this.options=options,"hover"==this.options.pause&&this.$element.on("mouseenter",$.proxy(this.pause,this)).on("mouseleave",$.proxy(this.cycle,this))};Carousel.prototype={cycle:function(e){return e||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval($.proxy(this.next,this),this.options.interval)),this},to:function(pos){var $active=this.$element.find(".item.active"),children=$active.parent().children(),activePos=children.index($active),that=this;if(!(pos>children.length-1||0>pos))return this.sliding?this.$element.one("slid",function(){that.to(pos)}):activePos==pos?this.pause().cycle():this.slide(pos>activePos?"next":"prev",$(children[pos]))},pause:function(e){return e||(this.paused=!0),this.$element.find(".next, .prev").length&&$.support.transition.end&&(this.$element.trigger($.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){return this.sliding?void 0:this.slide("next")},prev:function(){return this.sliding?void 0:this.slide("prev")},slide:function(type,next){var e,$active=this.$element.find(".item.active"),$next=next||$active[type](),isCycling=this.interval,direction="next"==type?"left":"right",fallback="next"==type?"first":"last",that=this;if(this.sliding=!0,isCycling&&this.pause(),$next=$next.length?$next:this.$element.find(".item")[fallback](),e=$.Event("slide",{relatedTarget:$next[0]}),!$next.hasClass("active")){if($.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(e),e.isDefaultPrevented())return;$next.addClass(type),$next[0].offsetWidth,$active.addClass(direction),$next.addClass(direction),this.$element.one($.support.transition.end,function(){$next.removeClass([type,direction].join(" ")).addClass("active"),$active.removeClass(["active",direction].join(" ")),that.sliding=!1,setTimeout(function(){that.$element.trigger("slid")},0)})}else{if(this.$element.trigger(e),e.isDefaultPrevented())return;$active.removeClass("active"),$next.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return isCycling&&this.cycle(),this}}};var old=$.fn.carousel;$.fn.carousel=function(option){return this.each(function(){var $this=$(this),data=$this.data("carousel"),options=$.extend({},$.fn.carousel.defaults,"object"==typeof option&&option),action="string"==typeof option?option:options.slide;data||$this.data("carousel",data=new Carousel(this,options)),"number"==typeof option?data.to(option):action?data[action]():options.interval&&data.cycle()})},$.fn.carousel.defaults={interval:5e3,pause:"hover"},$.fn.carousel.Constructor=Carousel,$.fn.carousel.noConflict=function(){return $.fn.carousel=old,this},$(document).on("click.carousel.data-api","[data-slide]",function(e){var href,$this=$(this),$target=$($this.attr("data-target")||(href=$this.attr("href"))&&href.replace(/.*(?=#[^\s]+$)/,"")),options=$.extend({},$target.data(),$this.data());$target.carousel(options),e.preventDefault()})}(window.jQuery),!function($){"use strict";var Collapse=function(element,options){this.$element=$(element),this.options=$.extend({},$.fn.collapse.defaults,options),this.options.parent&&(this.$parent=$(this.options.parent)),this.options.toggle&&this.toggle()};Collapse.prototype={constructor:Collapse,dimension:function(){var hasWidth=this.$element.hasClass("width");return hasWidth?"width":"height"},show:function(){var dimension,scroll,actives,hasData;if(!this.transitioning){if(dimension=this.dimension(),scroll=$.camelCase(["scroll",dimension].join("-")),actives=this.$parent&&this.$parent.find("> .accordion-group > .in"),actives&&actives.length){if(hasData=actives.data("collapse"),hasData&&hasData.transitioning)return;actives.collapse("hide"),hasData||actives.data("collapse",null)}this.$element[dimension](0),this.transition("addClass",$.Event("show"),"shown"),$.support.transition&&this.$element[dimension](this.$element[0][scroll])}},hide:function(){var dimension;this.transitioning||(dimension=this.dimension(),this.reset(this.$element[dimension]()),this.transition("removeClass",$.Event("hide"),"hidden"),this.$element[dimension](0))},reset:function(size){var dimension=this.dimension();return this.$element.removeClass("collapse")[dimension](size||"auto")[0].offsetWidth,this.$element[null!==size?"addClass":"removeClass"]("collapse"),this},transition:function(method,startEvent,completeEvent){var that=this,complete=function(){"show"==startEvent.type&&that.reset(),that.transitioning=0,that.$element.trigger(completeEvent)};this.$element.trigger(startEvent),startEvent.isDefaultPrevented()||(this.transitioning=1,this.$element[method]("in"),$.support.transition&&this.$element.hasClass("collapse")?this.$element.one($.support.transition.end,complete):complete())},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var old=$.fn.collapse;$.fn.collapse=function(option){return this.each(function(){var $this=$(this),data=$this.data("collapse"),options="object"==typeof option&&option;data||$this.data("collapse",data=new Collapse(this,options)),"string"==typeof option&&data[option]()})},$.fn.collapse.defaults={toggle:!0},$.fn.collapse.Constructor=Collapse,$.fn.collapse.noConflict=function(){return $.fn.collapse=old,this},$(document).on("click.collapse.data-api","[data-toggle=collapse]",function(e){var href,$this=$(this),target=$this.attr("data-target")||e.preventDefault()||(href=$this.attr("href"))&&href.replace(/.*(?=#[^\s]+$)/,""),option=$(target).data("collapse")?"toggle":$this.data();$this[$(target).hasClass("in")?"addClass":"removeClass"]("collapsed"),$(target).collapse(option)})}(window.jQuery),!function($){"use strict";function clearMenus(){$(toggle).each(function(){getParent($(this)).removeClass("open")})}function getParent($this){var $parent,selector=$this.attr("data-target");return selector||(selector=$this.attr("href"),selector=selector&&/#/.test(selector)&&selector.replace(/.*(?=#[^\s]*$)/,"")),$parent=$(selector),$parent.length||($parent=$this.parent()),$parent}var toggle="[data-toggle=dropdown]",Dropdown=function(element){var $el=$(element).on("click.dropdown.data-api",this.toggle);$("html").on("click.dropdown.data-api",function(){$el.parent().removeClass("open")})};Dropdown.prototype={constructor:Dropdown,toggle:function(){var $parent,isActive,$this=$(this);if(!$this.is(".disabled, :disabled"))return $parent=getParent($this),isActive=$parent.hasClass("open"),clearMenus(),isActive||$parent.toggleClass("open"),$this.focus(),!1},keydown:function(e){var $this,$items,$parent,isActive,index;if(/(38|40|27)/.test(e.keyCode)&&($this=$(this),e.preventDefault(),e.stopPropagation(),!$this.is(".disabled, :disabled"))){if($parent=getParent($this),isActive=$parent.hasClass("open"),!isActive||isActive&&27==e.keyCode)return $this.click();$items=$("[role=menu] li:not(.divider):visible a",$parent),$items.length&&(index=$items.index($items.filter(":focus")),38==e.keyCode&&index>0&&index--,40==e.keyCode&&$items.length-1>index&&index++,~index||(index=0),$items.eq(index).focus())}}};var old=$.fn.dropdown;$.fn.dropdown=function(option){return this.each(function(){var $this=$(this),data=$this.data("dropdown");data||$this.data("dropdown",data=new Dropdown(this)),"string"==typeof option&&data[option].call($this)})},$.fn.dropdown.Constructor=Dropdown,$.fn.dropdown.noConflict=function(){return $.fn.dropdown=old,this},$(document).on("click.dropdown.data-api touchstart.dropdown.data-api",clearMenus).on("click.dropdown touchstart.dropdown.data-api",".dropdown form",function(e){e.stopPropagation()}).on("touchstart.dropdown.data-api",".dropdown-menu",function(e){e.stopPropagation()}).on("click.dropdown.data-api touchstart.dropdown.data-api",toggle,Dropdown.prototype.toggle).on("keydown.dropdown.data-api touchstart.dropdown.data-api",toggle+", [role=menu]",Dropdown.prototype.keydown)}(window.jQuery),!function($){"use strict";var Modal=function(element,options){this.options=options,this.$element=$(element).delegate('[data-dismiss="modal"]',"click.dismiss.modal",$.proxy(this.hide,this)),this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};Modal.prototype={constructor:Modal,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var that=this,e=$.Event("show");this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.escape(),this.backdrop(function(){var transition=$.support.transition&&that.$element.hasClass("fade");that.$element.parent().length||that.$element.appendTo(document.body),that.$element.show(),transition&&that.$element[0].offsetWidth,that.$element.addClass("in").attr("aria-hidden",!1),that.enforceFocus(),transition?that.$element.one($.support.transition.end,function(){that.$element.focus().trigger("shown")}):that.$element.focus().trigger("shown")}))},hide:function(e){e&&e.preventDefault(),e=$.Event("hide"),this.$element.trigger(e),this.isShown&&!e.isDefaultPrevented()&&(this.isShown=!1,this.escape(),$(document).off("focusin.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),$.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal())},enforceFocus:function(){var that=this;$(document).on("focusin.modal",function(e){that.$element[0]===e.target||that.$element.has(e.target).length||that.$element.focus()})},escape:function(){var that=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.modal",function(e){27==e.which&&that.hide()}):this.isShown||this.$element.off("keyup.dismiss.modal")},hideWithTransition:function(){var that=this,timeout=setTimeout(function(){that.$element.off($.support.transition.end),that.hideModal()},500);this.$element.one($.support.transition.end,function(){clearTimeout(timeout),that.hideModal()})},hideModal:function(){this.$element.hide().trigger("hidden"),this.backdrop()},removeBackdrop:function(){this.$backdrop.remove(),this.$backdrop=null},backdrop:function(callback){var animate=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var doAnimate=$.support.transition&&animate;this.$backdrop=$('