viewer.js 55 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728
  1. // viewer.js
  2. // was openjscad.js, originally written by Joost Nieuwenhuijse (MIT License)
  3. // few adjustments by Rene K. Mueller <spiritdude@gmail.com> for OpenJSCAD.org
  4. // more adjustments by J. Yoder for BlocksCAD
  5. //
  6. var Blockscad = Blockscad || {};
  7. var CSG = CSG || {};
  8. var CAG = CAG || {};
  9. // A viewer is a WebGL canvas that lets the user view a mesh. The user can
  10. // tumble it around by dragging the mouse.
  11. Blockscad.Viewer = function(containerelement, width, height, initialdepth) {
  12. var gl = GL.create();
  13. this.gl = gl;
  14. this.angleX = -60;
  15. this.angleY = 0;
  16. this.angleZ = -45;
  17. this.viewpointX = 0;
  18. this.viewpointY = -5;
  19. this.viewpointZ = initialdepth;
  20. this.defaultColor = [1,0.5,1,1];
  21. // Blockscad.defaultColor = this.defaultColor.toString();
  22. this.touch = {
  23. lastX: 0,
  24. lastY: 0,
  25. scale: 0,
  26. ctrl: 0,
  27. shiftTimer: null,
  28. shiftControl: null,
  29. cur: null //current state
  30. };
  31. // Draw triangle lines:
  32. this.drawLines = false;
  33. // Set to true so lines don't use the depth buffer
  34. this.lineOverlay = false;
  35. // Set up the viewport
  36. gl.canvas.width = width;
  37. gl.canvas.height = height;
  38. gl.viewport(0, 0, width, height);
  39. gl.matrixMode(gl.PROJECTION);
  40. gl.loadIdentity();
  41. // console.log("am I getting this new code?");
  42. gl.perspective(45, width / height, 1, 3000);
  43. gl.matrixMode(gl.MODELVIEW);
  44. // Set up WebGL state
  45. gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
  46. gl.clearColor(1,1,1, 1);
  47. gl.enable(gl.DEPTH_TEST);
  48. gl.enable(gl.CULL_FACE);
  49. gl.polygonOffset(1, 1);
  50. // Black shader for wireframe
  51. this.blackShader = new GL.Shader('' +
  52. 'void main() {' +
  53. 'gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;' +
  54. '}', '' +
  55. 'void main() {' +
  56. 'gl_FragColor = vec4(0.0, 0.0, 0.0, 0.1);' +
  57. '}');
  58. // Shader with diffuse and specular lighting
  59. this.lightingShader = new GL.Shader('' +
  60. 'varying vec3 color;' +
  61. 'varying float alpha;' +
  62. 'varying vec3 normal;' +
  63. 'varying vec3 light;' +
  64. 'void main() {' +
  65. 'const vec3 lightDir = vec3(1.0, 2.0, 3.0) / 3.741657386773941;' +
  66. 'light = lightDir;' +
  67. 'color = gl_Color.rgb;' +
  68. 'alpha = gl_Color.a;' +
  69. 'normal = gl_NormalMatrix * gl_Normal;' +
  70. 'gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;' +
  71. '}',
  72. 'varying vec3 color;' +
  73. 'varying float alpha;' +
  74. 'varying vec3 normal;' +
  75. 'varying vec3 light;' +
  76. 'void main() {' +
  77. 'vec3 n = normalize(normal);' +
  78. 'float diffuse = max(0.0, dot(light, n));' +
  79. 'float specular = pow(max(0.0, -reflect(light, n).z), 18.0) * sqrt(diffuse);' +
  80. 'gl_FragColor = vec4(mix(color * (0.3 + 0.7 * diffuse), vec3(1.0), specular), alpha);' +
  81. '}');
  82. var _this=this;
  83. var shiftControl = $('<div class="shift-scene"><div class="arrow arrow-left" />' +
  84. '<div class="arrow arrow-right" />' +
  85. '<div class="arrow arrow-top" />' +
  86. '<div class="arrow arrow-bottom" /></div>' );
  87. this.touch.shiftControl = shiftControl;
  88. $(containerelement).append(gl.canvas)
  89. .append(shiftControl)
  90. .hammer({//touch screen control
  91. drag_lock_to_axis: true
  92. }).on("transform", function(e){
  93. if (e.gesture.touches.length >= 2) {
  94. _this.clearShift();
  95. _this.onTransform(e);
  96. e.preventDefault();
  97. }
  98. }).on("touch", function(e) {
  99. if (e.gesture.pointerType != 'touch'){
  100. e.preventDefault();
  101. return;
  102. }
  103. if (e.gesture.touches.length == 1) {
  104. var point = e.gesture.center;
  105. _this.touch.shiftTimer = setTimeout(function(){
  106. shiftControl.addClass('active').css({
  107. left: point.pageX + 'px',
  108. top: point.pageY + 'px'
  109. });
  110. _this.touch.shiftTimer = null;
  111. _this.touch.cur = 'shifting';
  112. }, 500);
  113. } else {
  114. _this.clearShift();
  115. }
  116. }).on("drag", function(e) {
  117. if (e.gesture.pointerType != 'touch') {
  118. e.preventDefault();
  119. return;
  120. }
  121. if (!_this.touch.cur || _this.touch.cur == 'dragging') {
  122. _this.clearShift();
  123. _this.onPanTilt(e);
  124. } else if (_this.touch.cur == 'shifting') {
  125. _this.onShift(e);
  126. }
  127. }).on("touchend", function(e) {
  128. _this.clearShift();
  129. if (_this.touch.cur) {
  130. shiftControl.removeClass('active shift-horizontal shift-vertical');
  131. }
  132. }).on("transformend dragstart dragend", function(e) {
  133. if ((e.type == 'transformend' && _this.touch.cur == 'transforming') ||
  134. (e.type == 'dragend' && _this.touch.cur == 'shifting') ||
  135. (e.type == 'dragend' && _this.touch.cur == 'dragging'))
  136. _this.touch.cur = null;
  137. _this.touch.lastX = 0;
  138. _this.touch.lastY = 0;
  139. _this.touch.scale = 0;
  140. });
  141. gl.onmousemove = function(e) {
  142. _this.onMouseMove(e);
  143. };
  144. gl.ondraw = function() {
  145. _this.onDraw();
  146. };
  147. gl.onmousewheel = function(e) {
  148. var wheelDelta = 0;
  149. if (e.wheelDelta) {
  150. wheelDelta = e.wheelDelta;
  151. } else if (e.detail) {
  152. // for firefox, see http://stackoverflow.com/questions/8886281/event-wheeldelta-returns-undefined
  153. wheelDelta = e.detail * -40;
  154. }
  155. if(wheelDelta) {
  156. wheelDelta /= 2;
  157. var factor = Math.pow(1.003, -wheelDelta);
  158. var coeff = _this.getZoom();
  159. coeff *= factor;
  160. _this.setZoom(coeff);
  161. }
  162. };
  163. this.clear();
  164. };
  165. Blockscad.Viewer.prototype = {
  166. setCsg: function(csg) {
  167. // if(0&&csg.length) { // preparing multiple CSG's (not union-ed), not yet working
  168. // for(var i=0; i<csg.length; i++)
  169. // this.meshes.concat(OpenJsCad.Viewer.csgToMeshes(csg[i]));
  170. // } else {
  171. // this.meshes = OpenJsCad.Viewer.csgToMeshes(csg);
  172. // }
  173. this.gl.makeCurrent();
  174. this.meshes = Blockscad.Viewer.csgToMeshes(csg,this.defaultColor);
  175. this.onDraw();
  176. },
  177. rendered_resize: function(width, height) {
  178. this.gl.canvas.width = width;
  179. this.gl.canvas.height = height;
  180. this.gl.viewport(0, 0, width, height);
  181. this.gl.matrixMode(this.gl.PROJECTION);
  182. this.gl.loadIdentity();
  183. this.gl.perspective(45, width / height, 1, 3000);
  184. this.gl.matrixMode(this.gl.MODELVIEW);
  185. this.onDraw();
  186. // console.log("end of rendered_resize");
  187. },
  188. // str is an optional string input that can be used to override the viewMenu position.
  189. viewReset: function(str) {
  190. if (str) {
  191. var whichView = str;
  192. }
  193. else
  194. var whichView = document.getElementById("viewMenu").value;
  195. if (whichView == "diagonal") {
  196. // diagonal
  197. this.angleX = -60;
  198. this.angleY = 0;
  199. this.angleZ = -45;
  200. this.viewpointX = 0;
  201. this.viewpointY = -5;
  202. this.viewpointZ = 100;
  203. }
  204. else if (whichView == "top") {
  205. // top
  206. this.angleX = 0;
  207. this.angleY = 0;
  208. this.angleZ = 0;
  209. this.viewpointX = 0;
  210. this.viewpointY = 0;
  211. this.viewpointZ = 100;
  212. }
  213. else if (whichView == "bottom") {
  214. // bottom
  215. this.angleX = 180;
  216. this.angleY = 0;
  217. this.angleZ = 0;
  218. this.viewpointX = 0;
  219. this.viewpointY = 0;
  220. this.viewpointZ = 100;
  221. }
  222. else if (whichView == "right") {
  223. // front
  224. this.angleX = -90;
  225. this.angleY = 0;
  226. this.angleZ = 0;
  227. this.viewpointX = 0;
  228. this.viewpointY = 0;
  229. this.viewpointZ = 100;
  230. }
  231. else if (whichView == "front") {
  232. // right??
  233. this.angleX = -90;
  234. this.angleY = 0;
  235. this.angleZ = -90;
  236. this.viewpointX = 0;
  237. this.viewpointY = 0;
  238. this.viewpointZ = 100;
  239. }
  240. else if (whichView == "left") {
  241. // back
  242. this.angleX = -90;
  243. this.angleY = 0;
  244. this.angleZ = 180;
  245. this.viewpointX = 0;
  246. this.viewpointY = 0;
  247. this.viewpointZ = 100;
  248. }
  249. else if (whichView == "back") {
  250. // left??
  251. this.angleX = -90;
  252. this.angleY = 0;
  253. this.angleZ = 90;
  254. this.viewpointX = 0;
  255. this.viewpointY = 0;
  256. this.viewpointZ = 100;
  257. }
  258. this.onDraw();
  259. },
  260. clear: function() {
  261. // empty mesh list:
  262. this.meshes = [];
  263. this.onDraw();
  264. },
  265. supported: function() {
  266. return !!this.gl;
  267. },
  268. ZOOM_MAX: 1500,
  269. ZOOM_MIN: 5,
  270. onZoomChanged: null,
  271. plate: true, // render plate
  272. setZoom: function(coeff) { //0...1
  273. coeff=Math.max(coeff, 0);
  274. coeff=Math.min(coeff, 1);
  275. this.viewpointZ = this.ZOOM_MIN + coeff * (this.ZOOM_MAX - this.ZOOM_MIN);
  276. if(this.onZoomChanged) {
  277. this.onZoomChanged();
  278. }
  279. this.onDraw();
  280. },
  281. getZoom: function() {
  282. var coeff = (this.viewpointZ-this.ZOOM_MIN) / (this.ZOOM_MAX - this.ZOOM_MIN);
  283. //console.log("zoom is: ",this.viewpointZ);
  284. return coeff;
  285. },
  286. zoomOut: function() {
  287. var coeff = this.getZoom();
  288. coeff *= 1.15;
  289. this.setZoom(coeff);
  290. },
  291. zoomIn: function() {
  292. var coeff = this.getZoom();
  293. coeff *= 0.85;
  294. this.setZoom(coeff);
  295. },
  296. onMouseMove: function(e) {
  297. if (e.dragging) {
  298. //console.log(e.which,e.button);
  299. var b = e.button;
  300. if(e.which) { // RANT: not even the mouse buttons are coherent among the brand (chrome,firefox,etc)
  301. b = e.which;
  302. }
  303. e.preventDefault();
  304. if(e.altKey||b==3) { // ROTATE X,Y (ALT or right mouse button)
  305. this.angleY += e.deltaX;
  306. this.angleX += e.deltaY;
  307. //this.angleX = Math.max(-180, Math.min(180, this.angleX));
  308. } else if(e.shiftKey||b==2) { // PAN (SHIFT or middle mouse button)
  309. var factor = 5e-3;
  310. this.viewpointX += factor * e.deltaX * this.viewpointZ;
  311. this.viewpointY -= factor * e.deltaY * this.viewpointZ;
  312. } else if(e.ctrlKey) { // ZOOM IN/OU
  313. var factor = Math.pow(1.006, e.deltaX+e.deltaY);
  314. var coeff = this.getZoom();
  315. coeff *= factor;
  316. this.setZoom(coeff);
  317. } else { // ROTATE X,Z left mouse button
  318. this.angleZ += e.deltaX;
  319. this.angleX += e.deltaY;
  320. }
  321. this.onDraw();
  322. }
  323. },
  324. clearShift: function() {
  325. if(this.touch.shiftTimer) {
  326. clearTimeout(this.touch.shiftTimer);
  327. this.touch.shiftTimer = null;
  328. }
  329. return this;
  330. },
  331. //pan & tilt with one finger
  332. onPanTilt: function(e) {
  333. this.touch.cur = 'dragging';
  334. var delta = 0;
  335. if (this.touch.lastY && (e.gesture.direction == 'up' || e.gesture.direction == 'down')) {
  336. //tilt
  337. delta = e.gesture.deltaY - this.touch.lastY;
  338. this.angleX += delta;
  339. } else if (this.touch.lastX && (e.gesture.direction == 'left' || e.gesture.direction == 'right')) {
  340. //pan
  341. delta = e.gesture.deltaX - this.touch.lastX;
  342. this.angleZ += delta;
  343. }
  344. if (delta)
  345. this.onDraw();
  346. this.touch.lastX = e.gesture.deltaX;
  347. this.touch.lastY = e.gesture.deltaY;
  348. },
  349. //shift after 0.5s touch&hold
  350. onShift: function(e) {
  351. this.touch.cur = 'shifting';
  352. var factor = 5e-3;
  353. var delta = 0;
  354. if (this.touch.lastY && (e.gesture.direction == 'up' || e.gesture.direction == 'down')) {
  355. this.touch.shiftControl
  356. .removeClass('shift-horizontal')
  357. .addClass('shift-vertical')
  358. .css('top', e.gesture.center.pageY + 'px');
  359. delta = e.gesture.deltaY - this.touch.lastY;
  360. this.viewpointY -= factor * delta * this.viewpointZ;
  361. this.angleX += delta;
  362. }
  363. if (this.touch.lastX && (e.gesture.direction == 'left' || e.gesture.direction == 'right')) {
  364. this.touch.shiftControl
  365. .removeClass('shift-vertical')
  366. .addClass('shift-horizontal')
  367. .css('left', e.gesture.center.pageX + 'px');
  368. delta = e.gesture.deltaX - this.touch.lastX;
  369. this.viewpointX += factor * delta * this.viewpointZ;
  370. this.angleZ += delta;
  371. }
  372. if (delta)
  373. this.onDraw();
  374. this.touch.lastX = e.gesture.deltaX;
  375. this.touch.lastY = e.gesture.deltaY;
  376. },
  377. //zooming
  378. onTransform: function(e) {
  379. this.touch.cur = 'transforming';
  380. if (this.touch.scale) {
  381. var factor = 1 / (1 + e.gesture.scale - this.touch.scale);
  382. var coeff = this.getZoom();
  383. coeff *= factor;
  384. this.setZoom( coeff);
  385. }
  386. this.touch.scale = e.gesture.scale;
  387. return this;
  388. },
  389. onDraw: function(takePic,angle,camera) {
  390. var gl = this.gl;
  391. gl.makeCurrent();
  392. if (takePic)
  393. gl.clearColor(1,1,1, 1);
  394. if (takePic && this.meshes[0]) {
  395. // I need to find out size and position of the object and position the camera accordingly.
  396. // angle (in radians) of how far I've rotated around my object.
  397. // console.log("this",this);
  398. angle += 3*Math.PI/4;
  399. var bsph = this.bsph;
  400. var bbox = this.bbox; // use bbox to see if this is a flat project or a tall one.
  401. // console.log(bbox);
  402. // var tallness = 1.2 ;
  403. var tallness = 1.3 - (bbox[1].z - bbox[0].z)/(2*bsph.radius);
  404. var r = 2.6 * bsph.radius;
  405. gl.matrixMode(gl.PROJECTION);
  406. gl.loadIdentity();
  407. gl.perspective(45,gl.canvas.width / gl.canvas.height,1,3000);
  408. gl.matrixMode(gl.MODELVIEW);
  409. gl.loadIdentity();
  410. gl.lookAt(bsph.center.x + r * Math.sin(angle), bsph.center.y + r * Math.cos(angle), bsph.center.z + tallness*r/2,
  411. bsph.center.x, bsph.center.y, bsph.center.z, 0,0,1);
  412. }
  413. else {
  414. // console.log("setting normal camera position and angle");
  415. // these values were getting corrupted in the picDiv. Don't know why.
  416. gl.matrixMode(gl.PROJECTION);
  417. gl.loadIdentity();
  418. gl.perspective(45, gl.canvas.width / gl.canvas.height, 1, 3000);
  419. gl.matrixMode(gl.MODELVIEW);
  420. gl.loadIdentity();
  421. gl.translate(this.viewpointX, this.viewpointY, -this.viewpointZ);
  422. gl.rotate(this.angleX, 1, 0, 0);
  423. gl.rotate(this.angleY, 0, 1, 0);
  424. gl.rotate(this.angleZ, 0, 0, 1);
  425. }
  426. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  427. gl.enable(gl.BLEND);
  428. //gl.disable(gl.DEPTH_TEST);
  429. if (!this.lineOverlay) gl.enable(gl.POLYGON_OFFSET_FILL);
  430. // don't send empty meshes to WebGL - check for length of vertices array - JY
  431. for (var i = 0; i < this.meshes.length; i++) {
  432. var mesh = this.meshes[i];
  433. if (mesh.vertices.length > 0) {
  434. this.lightingShader.draw(mesh, gl.TRIANGLES);
  435. }
  436. }
  437. if (!this.lineOverlay) gl.disable(gl.POLYGON_OFFSET_FILL);
  438. gl.disable(gl.BLEND);
  439. //gl.enable(gl.DEPTH_TEST);
  440. if(this.drawLines) {
  441. if (this.lineOverlay) gl.disable(gl.DEPTH_TEST);
  442. gl.enable(gl.BLEND);
  443. for (var i = 0; i < this.meshes.length; i++) {
  444. var mesh = this.meshes[i];
  445. if (mesh.vertices.length > 0) {
  446. this.blackShader.draw(mesh, gl.LINES);
  447. }
  448. }
  449. gl.disable(gl.BLEND);
  450. if (this.lineOverlay) gl.enable(gl.DEPTH_TEST);
  451. }
  452. //EDW: axes
  453. // Jennie - I don't draw major or minor gridlines on X or Y axis, because they
  454. // cover up the colored axis lines drawn later. That's the x!=0 part.
  455. // if (Blockscad.drawAxes) {
  456. if (Blockscad.drawAxes && !takePic) {
  457. gl.enable(gl.BLEND);
  458. gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
  459. gl.begin(gl.LINES);
  460. // can I have the plate change size based on your zoom level?
  461. // var plate = Math.ceil(this.viewpointZ * 1.1);
  462. // if (plate%2) plate++;
  463. //console.log("plate is:",plate);
  464. var plate = 200;
  465. if(this.plate) {
  466. gl.color(0.8,0.8,0.8,0.5); // -- minor grid
  467. for(var x=-plate/2; x<=plate/2; x++) {
  468. if(x%10 && x!==0) {
  469. gl.vertex(-plate/2, x, 0);
  470. gl.vertex(plate/2, x, 0);
  471. gl.vertex(x, -plate/2, 0);
  472. gl.vertex(x, plate/2, 0);
  473. // hashmarks on z-axis
  474. if (x < plate/2.8 && x > -plate/2.8) {
  475. gl.vertex(-0.5, 0, x);
  476. gl.vertex(0.5, 0, x);
  477. gl.vertex(0, -0.50, x);
  478. gl.vertex(0, 0.50, x);
  479. }
  480. }
  481. }
  482. gl.color(0.5,0.5,0.5,0.5); // -- major grid
  483. for(var x=10; x<=plate/2; x+=10) {
  484. if(x!==0) {
  485. gl.vertex(-plate/2, x, 0);
  486. gl.vertex(plate/2, x, 0);
  487. gl.vertex(x, -plate/2, 0);
  488. gl.vertex(x, plate/2, 0);
  489. gl.vertex(-plate/2, -x, 0);
  490. gl.vertex(plate/2, -x, 0);
  491. gl.vertex(-x, -plate/2, 0);
  492. gl.vertex(-x, plate/2, 0);
  493. // hashmarks on z-axis
  494. if (x < plate/2.8 ) {
  495. gl.vertex(-1, 0, x);
  496. gl.vertex(1, 0, x);
  497. gl.vertex(0, -1, x);
  498. gl.vertex(0, 1, x);
  499. gl.vertex(-1, 0, -x);
  500. gl.vertex(1, 0, -x);
  501. gl.vertex(0, -1, -x);
  502. gl.vertex(0, 1, -x);
  503. }
  504. }
  505. }
  506. }
  507. // JY - set alpha for axes color to 1 to make the colors more obvious, and changed colors.
  508. if(1) {
  509. //X - red
  510. gl.color(1, 0, 0, 1); //positive direction
  511. gl.vertex(0, 0, 0);
  512. gl.vertex(plate/2, 0, 0);
  513. // I'd like to try making the negative direction dashed
  514. for (var i=-plate/2; i < 0; i++) {
  515. if (i%2) {
  516. gl.vertex(i,0,0);
  517. gl.vertex(i+1,0,0);
  518. }
  519. }
  520. //Y - green
  521. gl.color(0, 0.7, 0, 1); //positive direction
  522. gl.vertex(0, 0, 0);
  523. gl.vertex(0, plate/2, 0);
  524. for (var i=-plate/2; i < 0; i++) { // negative direction is dashed
  525. if (i%2) {
  526. gl.vertex(0,i,0);
  527. gl.vertex(0,i+1,0);
  528. }
  529. }
  530. gl.color(0.1, 0.1, 0.4, 1); //positive direction
  531. gl.vertex(0, 0, 0);
  532. gl.vertex(0, 0, plate/2.8);
  533. for (var i=Math.floor(-plate/2.8); i < 0; i++) { // negative direction is dashed
  534. if (i%2) {
  535. gl.vertex(0,0,i);
  536. gl.vertex(0,0,i+1);
  537. }
  538. }
  539. }
  540. if(1) {
  541. //can I draw in an x just by drawing lines? Text is hard. - JY
  542. // sf is a size factor (inverse). The smaller this number, the larger
  543. // the x,y,z labels will be.
  544. var sf = 80;
  545. gl.color(0,0,0,1); // black?
  546. gl.vertex(plate/2 + 1*plate/sf,-1*plate/sf,0);
  547. gl.vertex(plate/2 + 3*plate/sf,1*plate/sf,0);
  548. gl.vertex(plate/2+1*plate/sf,1*plate/sf,0);
  549. gl.vertex(plate/2+3*plate/sf,-1*plate/sf,0);
  550. // drawing in a "y" - JY
  551. gl.vertex(-1*plate/sf,plate/2 + 4*plate/sf,0);
  552. gl.vertex(0,plate/2+3*plate/sf,0);
  553. gl.vertex(0,plate/2+3*plate/sf,0);
  554. gl.vertex(1*plate/sf,plate/2+4*plate/sf,0);
  555. gl.vertex(0,plate/2+1*plate/sf,0);
  556. gl.vertex(0,plate/2+3*plate/sf,0);
  557. // why not a "z" - JY
  558. gl.vertex(-1*plate/sf,0,plate/2.8+3*plate/sf);
  559. gl.vertex(1*plate/sf,0,plate/2.8+3*plate/sf);
  560. gl.vertex(-1*plate/sf,0,plate/2.8+1*plate/sf);
  561. gl.vertex(1*plate/sf,0,plate/2.8+1*plate/sf);
  562. gl.vertex(-1*plate/sf,0,plate/2.8+1*plate/sf);
  563. gl.vertex(1*plate/sf,0,plate/2.8+3*plate/sf);
  564. }
  565. gl.end();
  566. gl.disable(gl.BLEND);
  567. // GL.Mesh.plane({ detailX: 20, detailY: 40 });
  568. }
  569. // take a thumbnail and large pic if needed
  570. if (takePic) {
  571. var images = [];
  572. images[0] = this.gl.canvas.toDataURL('image/jpeg', takePic);
  573. var canv1 = document.createElement("canvas");
  574. var ctx1 = canv1.getContext("2d");
  575. canv1.height = this.gl.canvas.height * 0.5;
  576. canv1.width = this.gl.canvas.height * 0.5;
  577. ctx1.drawImage(this.gl.canvas, 0, 0, canv1.width, canv1.height);
  578. var canv2 = document.createElement("canvas");
  579. var ctx2 = canv2.getContext("2d");
  580. canv2.height = canv1.height * 0.5;
  581. canv2.width = canv1.width * 0.5;
  582. ctx2.drawImage(canv1, 0, 0, canv1.width * 0.5, canv1.height * 0.5);
  583. images[1] = canv2.toDataURL('image/jpeg', takePic);
  584. return images;
  585. }
  586. // take a screenshot pic if needed
  587. if (camera) {
  588. var image = this.gl.canvas.toDataURL('image/jpeg', camera);
  589. return image;
  590. }
  591. },
  592. // quality is the jpeg quality level (between 0 and 1). Note that a value of 0
  593. // won't take a pic at all, because it is used as a true/false to take the pic.
  594. takePic: function(quality, angle) {
  595. return this.onDraw(quality, angle);
  596. },
  597. // new function for taking a screen shot
  598. takeCameraPic: function(quality) {
  599. return this.onDraw(0,0,quality);
  600. }
  601. };
  602. // Convert from CSG solid to an array of GL.Mesh objects
  603. // limiting the number of vertices per mesh to less than 2^16
  604. Blockscad.Viewer.csgToMeshes = function(initial_csg, defaultColor) {
  605. var csg = initial_csg.canonicalized();
  606. var mesh = new GL.Mesh({ normals: true, colors: true });
  607. var meshes = [ mesh ];
  608. var vertexTag2Index = {};
  609. var vertices = [];
  610. var colors = [];
  611. var triangles = [];
  612. // set to true if we want to use interpolated vertex normals
  613. // this creates nice round spheres but does not represent the shape of
  614. // the actual model
  615. var smoothlighting = false;
  616. var polygons = csg.toPolygons();
  617. var numpolygons = polygons.length;
  618. for(var j = 0; j < numpolygons; j++) {
  619. var polygon = polygons[j];
  620. var color = defaultColor; // -- default color
  621. if(polygon.shared && polygon.shared.color) {
  622. color = polygon.shared.color;
  623. }
  624. if(polygon.color) {
  625. color = polygon.color;
  626. }
  627. if (color.length < 4)
  628. color.push(1.0); //opaque
  629. var indices = polygon.vertices.map(function(vertex) {
  630. var vertextag = vertex.getTag();
  631. var vertexindex;
  632. if(smoothlighting && (vertextag in vertexTag2Index)) {
  633. vertexindex = vertexTag2Index[vertextag];
  634. } else {
  635. vertexindex = vertices.length;
  636. vertexTag2Index[vertextag] = vertexindex;
  637. vertices.push([vertex.pos.x, vertex.pos.y, vertex.pos.z]);
  638. colors.push(color);
  639. }
  640. return vertexindex;
  641. });
  642. for (var i = 2; i < indices.length; i++) {
  643. triangles.push([indices[0], indices[i - 1], indices[i]]);
  644. }
  645. // if too many vertices, start a new mesh;
  646. if (vertices.length > 65000) {
  647. // finalize the old mesh
  648. mesh.triangles = triangles;
  649. mesh.vertices = vertices;
  650. mesh.colors = colors;
  651. mesh.computeWireframe();
  652. mesh.computeNormals();
  653. // start a new mesh
  654. mesh = new GL.Mesh({ normals: true, colors: true });
  655. triangles = [];
  656. colors = [];
  657. vertices = [];
  658. meshes.push(mesh);
  659. }
  660. }
  661. // finalize last mesh
  662. mesh.triangles = triangles;
  663. mesh.vertices = vertices;
  664. mesh.colors = colors;
  665. mesh.computeWireframe();
  666. mesh.computeNormals();
  667. return meshes;
  668. };
  669. // this is a bit of a hack; doesn't properly supports urls that start with '/'
  670. // but does handle relative urls containing ../
  671. Blockscad.makeAbsoluteUrl = function(url, baseurl) {
  672. // console.log("in makeAbsoluteUrl: ",url + " " + baseurl);
  673. if(!url.match(/^[a-z]+\:/i)) {
  674. var basecomps = baseurl.split("/");
  675. if(basecomps.length > 0) {
  676. basecomps.splice(basecomps.length - 1, 1);
  677. }
  678. var urlcomps = url.split("/");
  679. var comps = basecomps.concat(urlcomps);
  680. var comps2 = [];
  681. comps.map(function(c) {
  682. if(c == "..") {
  683. if(comps2.length > 0) {
  684. comps2.splice(comps2.length - 1, 1);
  685. }
  686. } else {
  687. comps2.push(c);
  688. }
  689. });
  690. url = "";
  691. for(var i = 0; i < comps2.length; i++) {
  692. if(i > 0) url += "/";
  693. url += comps2[i];
  694. }
  695. }
  696. return url;
  697. };
  698. Blockscad.isChrome = function() {
  699. return (navigator.userAgent.search("Chrome") >= 0);
  700. };
  701. // this is called from within the web worker. Run the parser, create the main() function, execute the main() function.
  702. Blockscad.parseCodeInWorker = function(code, fontkeys, fontdata) {
  703. // this needs to call the parser, get the main string back. Will it be a function? I think it will be a string. We'll see.
  704. // self.postMessage({cmd: 'log', txt: "code in paresCodeInWorker: " + code});
  705. try {
  706. // I want to convert a font buffer to an actual font file using opentype.
  707. var csgcode = openscadOpenJscadParser.parse(code);
  708. // self.postMessage({cmd: 'log', txt: "csgcode in paresCodeInWorker: " + csgcode});
  709. // is the code any good? it should start with function main()
  710. if (!csgcode || !csgcode.length) {
  711. // I don't expect this to actually happen. I think the catch method would get it.
  712. // self.postMessage({cmd: 'errorParse', err: "parser failed during execution"});
  713. throw new Error('parser produced no output at all');
  714. }
  715. // we think we have good code. Try to run it in the worker now.
  716. self.postMessage({cmd: 'parsed', err: "haha"});
  717. Blockscad.runMainInWorker(csgcode);
  718. }
  719. catch(e) {
  720. // self.postMessage({cmd: 'log', txt: "in catch of parseCodeInWorker: "});
  721. var errtxt = e.toString();
  722. self.postMessage({cmd: 'errorParse', err: errtxt});
  723. }
  724. }
  725. // This is called from within the web worker. Execute the main() function of the supplied script
  726. // and post a message to the calling thread when finished
  727. Blockscad.runMainInWorker = function(csgcode) {
  728. // var code = csgcode.substring(csgcode.indexOf("{")+1, csgcode.lastIndexOf("}"));
  729. var main = new Function(csgcode);
  730. try {
  731. if(typeof(main) != 'function') throw new Error('Your code has an error somewhere. Parsing your blocks failed.');
  732. var result = main();
  733. // self.postMessage({cmd: 'log', txt: result});
  734. // result will always return an array of objects. check the first object to make sure it is good. empty stuff gets "undefined".
  735. if( (typeof(result[0]) != "object") || ((!(result[0] instanceof CSG)) && (!(result[0] instanceof CAG)))) {
  736. throw new Error("Nothing to Render!");
  737. }
  738. else {
  739. // just for fun, let's send a message with how many objects there were.
  740. // self.postMessage({cmd: 'log', txt: "number of objects: " + result.length});
  741. var numPolys = 0;
  742. // I have something to render. If it was a single object, extrude and return it.
  743. var o = result[0];
  744. if(o instanceof CAG) {
  745. o = o.extrude({offset: [0,0,0.1]});
  746. }
  747. if (result.length == 1) {
  748. // if (o.polygons) self.postMessage({cmd: 'log', txt: "number of polys final: " + o.polygons.length});
  749. var cp = o.toCompactBinary();
  750. self.postMessage({cmd: 'finalMesh', result: cp});
  751. }
  752. else {
  753. // // I know I have more than one object. First make a fake union for display only.
  754. // for(var i=1; i<result.length; i++) {
  755. // var c = result[i];
  756. // if(c instanceof CAG) {
  757. // c = c.extrude({offset: [0,0,0.1]});
  758. // }
  759. // o = o.unionForNonIntersecting(c);
  760. // numPolys += c.polygons.length;
  761. // }
  762. // var result_compact = o.toCompactBinary();
  763. // self.postMessage({cmd: 'log', txt: "number of polys: " + numPolys});
  764. // self.postMessage({cmd: 'rendered', result: result_compact});
  765. // now that I've returned the display obj, do a real union and return it.
  766. var a = result[0];
  767. var rest_of_objs = result.slice(1);
  768. a = a.union(rest_of_objs);
  769. if(a instanceof CAG) {
  770. a = a.extrude({offset: [0,0,0.1]});
  771. }
  772. self.postMessage({cmd: 'log', txt: "number of polys final: " + a.polygons.length});
  773. var result_compact = a.toCompactBinary();
  774. self.postMessage({cmd: 'finalMesh', result: result_compact});
  775. result = null; // not needed anymore
  776. } // end else (result.length > 1)
  777. } // end else (have something to render)
  778. }
  779. catch(e) {
  780. var errtxt = e.toString();
  781. self.postMessage({cmd: 'error', err: errtxt});
  782. }
  783. };
  784. Blockscad.parseBlockscadScriptSync = function(script, debugging) {
  785. var workerscript = "//SYNC\n";
  786. workerscript += "_includePath = "+JSON.stringify(_includePath)+";\n";
  787. workerscript += script;
  788. // workerscript += "var me = " + JSON.stringify(me) + ";\n";
  789. workerscript += "return main();";
  790. // trying to get include() somewhere:
  791. // 1) XHR works for SYNC <---
  792. // 2) importScripts() does not work in SYNC
  793. // 3) _csg_libraries.push(fn) provides only 1 level include()
  794. workerscript += "function include(fn) {" +
  795. "if(0) {" +
  796. "_csg_libraries.push(fn);" +
  797. "} else if(0) {" +
  798. "var url = _includePath!=='undefined'?_includePath:'./';" +
  799. "var index = url.indexOf('index.html');" +
  800. "if(index!=-1) {" +
  801. "url = url.substring(0,index);" +
  802. "}" +
  803. "importScripts(url+fn);" +
  804. "} else {" +
  805. // "console.log('SYNC checking gMemFs for '+fn);" +
  806. // "if(gMemFs[fn]) {" +
  807. // "console.log('found locally & eval:',gMemFs[fn].name);" +
  808. // "eval(gMemFs[fn].source); return;" +
  809. // "}" +
  810. "var xhr = new XMLHttpRequest();" +
  811. "xhr.open('GET',_includePath+fn,false);" +
  812. "console.log('include:'+_includePath+fn);" +
  813. "xhr.onload = function() {" +
  814. "var src = this.responseText;" +
  815. "eval(src);" +
  816. "};" +
  817. "xhr.onerror = function() {" +
  818. "};" +
  819. "xhr.send();" +
  820. "}" +
  821. "}";
  822. var f = new Function(workerscript);
  823. return f(); // execute the actual code
  824. };
  825. // callback: should be function(error, csg)
  826. Blockscad.parseBlockscadScriptASync = function(script, callback) {
  827. var baselibraries = [
  828. // "opentype/dist/opentype.min.js",
  829. // "blockscad/viewer_compressed.js",
  830. // "blockscad/underscore.js",
  831. // "blockscad/openscad-openjscad-translator.js"
  832. "blockscad/csg.js",
  833. "blockscad/formats.js",
  834. "opentype/dist/opentype.min.js",
  835. "blockscad/viewer.js",
  836. "blockscad/underscore.js",
  837. "blockscad/openscad-openjscad-translator.js"
  838. ];
  839. // console.log("in parseBlockscadScriptASync");
  840. var baseurl = document.location.href.replace(/\?.*$/, '');
  841. baseurl = baseurl.replace(/#.*$/,''); // remove remote URL
  842. var blockscadurl = baseurl;
  843. var libraries = [];
  844. var workerscript = "//ASYNC\n";
  845. workerscript += "var _csg_baseurl=" + JSON.stringify(baseurl)+";\n"; // -- we need it early for include()
  846. var ignoreInclude = false;
  847. var mainFile;
  848. // workerscript += script;
  849. // workerscript += "var code = `" + script + '`;';
  850. workerscript += "\n\n\n\n//// The following code was added by BlocksCAD:\n";
  851. workerscript += "var _csg_baselibraries=" + JSON.stringify(baselibraries)+";\n";
  852. workerscript += "var _csg_libraries=" + JSON.stringify(libraries)+";\n";
  853. workerscript += "var _csg_blockscadurl=" + JSON.stringify(blockscadurl)+";\n";
  854. workerscript += "var _csg_makeAbsoluteURL=" + Blockscad.makeAbsoluteUrl.toString()+";\n";
  855. // workerscript += "if(typeof(libs) == 'function') _csg_libraries = _csg_libraries.concat(libs());\n";
  856. workerscript += "_csg_baselibraries = _csg_baselibraries.map(function(l){return _csg_makeAbsoluteURL(l,_csg_blockscadurl);});\n";
  857. workerscript += "_csg_libraries = _csg_libraries.map(function(l){return _csg_makeAbsoluteURL(l,_csg_baseurl);});\n";
  858. workerscript += "_csg_baselibraries.map(function(l){importScripts(l)});\n";
  859. workerscript += "_csg_libraries.map(function(l){importScripts(l)});\n";
  860. workerscript += "self.addEventListener('message', function(e) {if(e.data && e.data.cmd == 'render'){";
  861. // workerscript += " Blockscad.runMainInWorker();";
  862. workerscript += "Blockscad.resolution = " + Blockscad.resolution + ';';
  863. workerscript += "Blockscad.csg_filename = e.data.csg_filename;";
  864. workerscript += "Blockscad.csg_commands = e.data.csg_commands;";
  865. workerscript += "Blockscad.fonts = {};";
  866. workerscript += "var fontkeys = e.data.fontkeys;";
  867. workerscript += "var fontdata = e.data.fontdata;";
  868. // can I actually parse the font buffers here?
  869. workerscript += "for (var i = 0; i < fontkeys.length; i++) {";
  870. workerscript += " Blockscad.fonts[fontkeys[i]] = opentype.parse(fontdata[i]); }";
  871. workerscript += " Blockscad.parseCodeInWorker(e.data.data);";
  872. workerscript += "}},false);\n";
  873. var blobURL = Blockscad.textToBlobUrl(workerscript);
  874. if(!window.Worker) throw new Error("Your browser doesn't support Web Workers. Please try the Chrome or Firefox browser instead.");
  875. var worker = new Worker(blobURL);
  876. worker.onmessage = function(e) {
  877. if(e.data)
  878. {
  879. if (e.data.cmd == 'rendered' || e.data.cmd == 'finalMesh') {
  880. // console.log("got the final, unioned mesh:", e.data.result);
  881. var resulttype = e.data.result.class;
  882. var result;
  883. if (resulttype == "CSG") {
  884. result = CSG.fromCompactBinary(e.data.result);
  885. }
  886. else if (resulttype == "CAG") {
  887. result = CAG.fromCompactBinary(e.data.result);
  888. }
  889. else {
  890. throw new Error("cannot parse final result");
  891. }
  892. callback(e.data.cmd, result);
  893. }
  894. else if(e.data.cmd == "error")
  895. {
  896. callback(e.data.err, null);
  897. }
  898. else if (e.data.cmd == "errorParse") {
  899. console.log("caught parsing error:", e.data.err);
  900. $( '#error-message' ).html(e.data.err);
  901. $( '#error-message' ).addClass("has-error");
  902. callback(e.data.err, null);
  903. }
  904. else if (e.data.cmd == "parsed") {
  905. $( '#render-ongoing').html(Blockscad.Msg.RENDER_IN_PROGRESS + '<img id=busy src="imgs/busy2.gif">');
  906. }
  907. else if(e.data.cmd == "log")
  908. {
  909. console.log(e.data.txt);
  910. }
  911. }
  912. };
  913. worker.onerror = function(e) {
  914. var errtxt = "Error in line "+e.lineno+": "+e.message;
  915. callback(errtxt, null);
  916. };
  917. var fontKeys = [];
  918. var fontData = [];
  919. for (var buf in Blockscad.fonts) {
  920. fontKeys.push(buf);
  921. fontData.push(Blockscad.fonts[buf]);
  922. }
  923. worker.postMessage({
  924. cmd: "render",
  925. data: script,
  926. fontkeys: fontKeys,
  927. fontdata: fontData,
  928. csg_filename: Blockscad.csg_filename,
  929. csg_commands: Blockscad.csg_commands
  930. }); // Start the worker.
  931. return worker;
  932. };
  933. Blockscad.getWindowURL = function() {
  934. if(window.URL) return window.URL;
  935. else if(window.webkitURL) return window.webkitURL;
  936. else throw new Error("Your browser doesn't support window.URL");
  937. };
  938. Blockscad.textToBlobUrl = function(txt) {
  939. var windowURL=Blockscad.getWindowURL();
  940. var blob = new Blob([txt]);
  941. var blobURL = windowURL.createObjectURL(blob);
  942. if(!blobURL) throw new Error("createObjectURL() failed");
  943. return blobURL;
  944. };
  945. Blockscad.revokeBlobUrl = function(url) {
  946. if(window.URL) window.URL.revokeObjectURL(url);
  947. else if(window.webkitURL) window.webkitURL.revokeObjectURL(url);
  948. else throw new Error("Your browser doesn't support window.URL");
  949. };
  950. Blockscad.AlertUserOfUncaughtExceptions = function() {
  951. window.onerror = function(message, url, line) {
  952. message = message.replace(/^Uncaught /i, "");
  953. alert(message+"\n\n("+url+" line "+line+")");
  954. };
  955. };
  956. Blockscad.Processor = function(containerdiv, onchange) {
  957. this.containerdiv = containerdiv;
  958. this.onchange = onchange;
  959. this.viewerdiv = null;
  960. this.picdiv = null;
  961. this.viewer = null;
  962. this.picviewer = null;
  963. this.rpicviewer = null;
  964. this.initialViewerDistance = 100;
  965. this.processing = false;
  966. this.currentObject = null;
  967. this.hasValidCurrentObject = false;
  968. this.hasOutputFile = false;
  969. this.worker = null;
  970. this.paramDefinitions = [];
  971. this.paramControls = [];
  972. this.script = null;
  973. this.hasError = false;
  974. this.debugging = false;
  975. this.thumbnail = "none";
  976. this.imgStrip = "none";
  977. this.img = "none";
  978. this.createElements();
  979. };
  980. Blockscad.Processor.convertToSolid = function(obj) {
  981. // obj is an array with one object in it. It has already been extruded and unioned.
  982. // this function really has nothing to do.
  983. if (typeof(obj) != "object" || !( ((obj) instanceof CAG) || ((obj) instanceof CSG) ) )
  984. throw new Error("Cannot convert to solid");
  985. return obj;
  986. };
  987. Blockscad.Processor.prototype = {
  988. createElements: function() {
  989. var that = this; // for event handlers
  990. // JY - I added a "reset view" button.
  991. // now, the container (content-render) HAS A CHILD FROM THE REST OF THE HTML (my reset view button)
  992. // this code throws an error if I try to throw that child away. SO, I always leave the first
  993. // child.
  994. // JY - now there are lots of children - these are the resizable div's handles and such. Argh! Leave 5.
  995. while(this.containerdiv.children.length > 7)
  996. {
  997. this.containerdiv.removeChild(7);
  998. }
  999. var viewerdiv = document.createElement("div");
  1000. viewerdiv.style.width = '100%';
  1001. viewerdiv.style.height = '100%';
  1002. viewerdiv.style.top = '0px';
  1003. viewerdiv.style.position = 'absolute';
  1004. viewerdiv.style.zIndex = '9';
  1005. this.containerdiv.appendChild(viewerdiv);
  1006. this.viewerdiv = viewerdiv;
  1007. var picdiv = document.createElement("div");
  1008. picdiv.setAttribute('id','picdiv');
  1009. picdiv.style.width = Blockscad.picSize[0] + 'px';
  1010. picdiv.style.height = Blockscad.picSize[1] + 'px';
  1011. picdiv.style.top = '0px';
  1012. picdiv.style.right = '10px';
  1013. picdiv.style.position = 'absolute';
  1014. picdiv.style.zIndex = '-1';
  1015. document.getElementById("blocklyDiv").appendChild(picdiv);
  1016. this.picdiv = picdiv;
  1017. var rpicdiv = document.createElement("div");
  1018. rpicdiv.setAttribute('id','picdiv');
  1019. rpicdiv.style.width = Blockscad.rpicSize[0] + 'px';
  1020. rpicdiv.style.height = Blockscad.rpicSize[1] + 'px';
  1021. rpicdiv.style.top = '0px';
  1022. rpicdiv.style.right = '10px';
  1023. rpicdiv.style.position = 'absolute';
  1024. rpicdiv.style.zIndex = '-1';
  1025. document.getElementById("blocklyDiv").appendChild(rpicdiv);
  1026. this.rpicdiv = rpicdiv;
  1027. try {
  1028. this.picviewer = new Blockscad.Viewer(this.picdiv, picdiv.offsetWidth, picdiv.offsetHeight, this.initialViewerDistance);
  1029. } catch(e) {
  1030. this.picdiv.innerHTML = "<b><br><br>Error: " + e.toString() + "</b><br><br>BlocksCAD currently requires Google Chrome or Firefox with WebGL enabled";
  1031. }
  1032. $("#picdiv").addClass('hidden');
  1033. try {
  1034. this.rpicviewer = new Blockscad.Viewer(this.rpicdiv, rpicdiv.offsetWidth, rpicdiv.offsetHeight, this.initialViewerDistance);
  1035. } catch(e) {
  1036. this.rpicdiv.innerHTML = "<b><br><br>Error: " + e.toString() + "</b><br><br>BlocksCAD currently requires Google Chrome or Firefox with WebGL enabled";
  1037. }
  1038. $("#rpicdiv").addClass('hidden');
  1039. try {
  1040. this.viewer = new Blockscad.Viewer(this.viewerdiv, viewerdiv.offsetWidth, viewerdiv.offsetHeight, this.initialViewerDistance);
  1041. } catch(e) {
  1042. this.viewerdiv.innerHTML = "<b><br><br>Error: " + e.toString() + "</b><br><br>BlocksCAD currently requires Google Chrome or Firefox with WebGL enabled";
  1043. }
  1044. this.abortbutton = document.getElementById("abortButton");
  1045. this.renderbutton = document.getElementById("renderButton");
  1046. this.ongoingrender = document.getElementById("render-ongoing");
  1047. this.abortbutton.onclick = function(e) {
  1048. that.abort();
  1049. };
  1050. this.formatDropdown = document.getElementById("render-type");
  1051. this.formatDropdown.onchange = function(e) {
  1052. that.currentFormat = that.formatDropdown.options[that.formatDropdown.selectedIndex].value;
  1053. that.updateDownloadLink();
  1054. };
  1055. this.generateOutputFileButton = document.getElementById("stlButton");
  1056. this.generateOutputFileButton.onclick = function(e) {
  1057. that.generateOutputFile();
  1058. };
  1059. this.enableItems();
  1060. // this.clearViewer();
  1061. },
  1062. // // getSphere takes the axis-aligned bounding box of a csg object and returns a bounding sphere.
  1063. // // this isn't the minimal bounding sphere, but it is a good approximation.
  1064. // getBoundingSphere: function(aabb) {
  1065. // // console.log(aabb);
  1066. // // the sphere center is halfway between the min and max points for each coordinate
  1067. // // to get the radius, go through all vertices (yuck) and test their distance from the center
  1068. // // pick the biggest, and that's the radius.
  1069. // // I use lengthSquared for per-vertex calcualations to avoid doing a square root on each vertex.
  1070. // var sphere = {center: aabb[0].plus(aabb[1]).dividedBy(2), radius: 0 };
  1071. // for (var i = 0; i < this.currentObject.polygons.length; i++) {
  1072. // for (var j = 0; j < this.currentObject.polygons[i].vertices.length; j++) {
  1073. // var v = this.currentObject.polygons[i].vertices[j];
  1074. // sphere.radius = Math.max(sphere.radius,
  1075. // new CSG.Vector3D(v.pos.x, v.pos.y, v.pos.z).minus(sphere.center).lengthSquared());
  1076. // }
  1077. // }
  1078. // sphere.radius = Math.sqrt(sphere.radius);
  1079. // return sphere;
  1080. // },
  1081. setCurrentObject: function(obj, forDownload) {
  1082. var csg = Blockscad.Processor.convertToSolid(obj); // enfore CSG to display
  1083. this.currentObject = csg;
  1084. this.hasValidCurrentObject = true;
  1085. if (this.viewer) this.viewer.setCsg(csg);
  1086. // for taking pics I also need the bounds and bounding sphere
  1087. if (this.picviewer) {
  1088. this.picviewer.bbox = csg.getBounds();
  1089. this.picviewer.bsph = csg.getBoundingSphere();
  1090. this.picviewer.setCsg(csg);
  1091. }
  1092. if (this.rpicviewer) {
  1093. this.rpicviewer.bbox = csg.getBounds();
  1094. this.rpicviewer.bsph = csg.getBoundingSphere();
  1095. this.rpicviewer.setCsg(csg);
  1096. }
  1097. if (forDownload) {
  1098. // console.log("trying to turn on stl_buttons");
  1099. $('#stl_buttons').removeClass('hidden');
  1100. while(this.formatDropdown.options.length > 0)
  1101. this.formatDropdown.options.remove(0);
  1102. var that = this;
  1103. this.supportedFormatsForCurrentObject().forEach(function(format) {
  1104. var option = document.createElement("option");
  1105. option.setAttribute("value", format);
  1106. option.appendChild(document.createTextNode(that.formatInfo(format).displayName));
  1107. that.formatDropdown.options.add(option);
  1108. });
  1109. this.updateDownloadLink();
  1110. }
  1111. },
  1112. selectedFormat: function() {
  1113. return this.formatDropdown.options[this.formatDropdown.selectedIndex].value;
  1114. },
  1115. selectedFormatInfo: function() {
  1116. return this.formatInfo(this.selectedFormat());
  1117. },
  1118. updateDownloadLink: function() {
  1119. var ext = this.selectedFormatInfo().extension;
  1120. this.generateOutputFileButton.innerHTML = '<i class="material-icons">file_download</i>';
  1121. // this.generateOutputFileButton.innerHTML = Blockscad.Msg.GENERATE_STL + " "+ext.toUpperCase();
  1122. },
  1123. clearViewer: function() {
  1124. this.clearOutputFile();
  1125. this.setCurrentObject(new CSG());
  1126. this.hasValidCurrentObject = false;
  1127. this.thumbnail = "none";
  1128. this.imgStrip = "none";
  1129. this.img = "none";
  1130. // console.log('trying to hid stl_buttons');
  1131. $('#stl_buttons').addClass('hidden');
  1132. this.enableItems();
  1133. },
  1134. abort: function() {
  1135. if(this.processing)
  1136. {
  1137. //todo: abort
  1138. // I want to turn the render button back on!
  1139. $('#renderButton').prop('disabled', false);
  1140. // I might need to change the "in progress" message too.
  1141. $( '#render-ongoing').html(Blockscad.Msg.PARSE_IN_PROGRESS + '<img id=busy src="imgs/busy2.gif">');
  1142. this.processing=false;
  1143. //this.statusspan.innerHTML = "Aborted.";
  1144. this.worker.terminate();
  1145. this.enableItems();
  1146. if(this.onchange) this.onchange();
  1147. }
  1148. },
  1149. enableItems: function() {
  1150. this.abortbutton.style.display = this.processing? "inline-block":"none";
  1151. this.renderbutton.style.display = this.processing? "none":"inline-block";
  1152. this.ongoingrender.style.display = this.processing? "inline-block":"none";
  1153. },
  1154. setError: function(txt) {
  1155. this.hasError = (txt != "");
  1156. $( "#error-message" ).text(txt);
  1157. // console.log("in setError with text", txt, "this.hasError is", this.hasError);
  1158. this.enableItems();
  1159. },
  1160. setDebugging: function(debugging) {
  1161. this.debugging = debugging;
  1162. },
  1163. // build/display a mesh
  1164. setBlockscad: function(script) {
  1165. // this.abort();
  1166. // this.clearViewer();
  1167. this.script = script;
  1168. // console.log("script for worker is:", this.script);
  1169. this.rebuildSolid();
  1170. },
  1171. rebuildSolid: function()
  1172. {
  1173. this.abort();
  1174. this.setError("");
  1175. this.clearViewer();
  1176. this.processing = true;
  1177. // $( '#renderButton' ).html(Blockscad.Msg.RENDER_BUTTON);
  1178. $('#renderButton').html('<i class="material-icons mdl-color-text--white" style="font-size: 30px">play_arrow</i></button>');
  1179. $( '#renderButton' ).prop('disabled', false);
  1180. this.enableItems();
  1181. var that = this;
  1182. var useSync = this.debugging;
  1183. //useSync = true;
  1184. if(!useSync)
  1185. {
  1186. try
  1187. {
  1188. // console.log("trying async compute");
  1189. this.worker = Blockscad.parseBlockscadScriptASync(this.script, function(err, obj) {
  1190. if (err && err == "finalMesh") {
  1191. // console.log("got back final mesh that can be downloaded");
  1192. $( '#render-ongoing').html(Blockscad.Msg.PARSE_IN_PROGRESS + '<img id=busy src="imgs/busy2.gif">');
  1193. // I got back the final mesh here. get the "ready for download" stuff ready.
  1194. that.processing = false;
  1195. that.setCurrentObject(obj, true);
  1196. // console.log(that);
  1197. var images = that.picviewer.takePic(Blockscad.picQuality,0,1);
  1198. that.img = images[0];
  1199. that.thumbnail = images[1];
  1200. that.imgStrip = that.takeRotatingPic(1,Blockscad.numRotPics);
  1201. that.processing = false;
  1202. that.worker = null;
  1203. }
  1204. else if (err && err == "rendered")
  1205. {
  1206. // console.log("got back rendered shapes for display");
  1207. // change message to say "prep for final union"
  1208. $( '#render-ongoing').html("Prepare for Download..." + '<img id=busy src="imgs/busy2.gif">');
  1209. that.setCurrentObject(obj, false);
  1210. // console.log(that);
  1211. var images = that.picviewer.takePic(Blockscad.picQuality,0,1);
  1212. that.img = images[0];
  1213. that.thumbnail = images[1];
  1214. that.imgStrip = that.takeRotatingPic(1,Blockscad.numRotPics);
  1215. }
  1216. else
  1217. {
  1218. console.log("what is going on?");
  1219. console.log("error in proc" + err);
  1220. that.processing = false;
  1221. that.worker = null;
  1222. // alert(err);
  1223. // console.log("script was:",this.script;
  1224. that.setError(err);
  1225. }
  1226. if(that.onchange) that.onchange();
  1227. that.enableItems();
  1228. });
  1229. }
  1230. catch(e)
  1231. {
  1232. console.log("async failed, try sync compute, error: "+e.message);
  1233. // console.log("script was:",this.script);
  1234. useSync = true;
  1235. }
  1236. }
  1237. if(useSync)
  1238. {
  1239. try
  1240. {
  1241. var obj = Blockscad.parseBlockscadScriptSync(this.script, this.debugging);
  1242. that.setCurrentObject(obj);
  1243. that.processing = false;
  1244. }
  1245. catch(e)
  1246. {
  1247. that.processing = false;
  1248. var errtxt = e.stack;
  1249. if(!errtxt)
  1250. {
  1251. errtxt = e.toString();
  1252. }
  1253. that.setError(errtxt);
  1254. }
  1255. that.enableItems();
  1256. if(that.onchange) that.onchange();
  1257. }
  1258. },
  1259. hasSolid: function() {
  1260. return this.hasValidCurrentObject;
  1261. },
  1262. isProcessing: function() {
  1263. return this.processing;
  1264. },
  1265. clearOutputFile: function() {
  1266. if(this.hasOutputFile)
  1267. {
  1268. if(this.outputFileBlobUrl)
  1269. {
  1270. Blockscad.revokeBlobUrl(this.outputFileBlobUrl);
  1271. this.outputFileBlobUrl = null;
  1272. }
  1273. this.enableItems();
  1274. if(this.onchange) this.onchange();
  1275. }
  1276. },
  1277. generateOutputFile: function() {
  1278. this.clearOutputFile();
  1279. if(this.hasValidCurrentObject)
  1280. {
  1281. this.generateAndSaveRenderedFile();
  1282. }
  1283. },
  1284. currentObjectToBlob: function() {
  1285. var format = this.selectedFormat();
  1286. var blob;
  1287. if(format == "stla") {
  1288. blob = this.currentObject.toStlString();
  1289. // console.log("this format mimetype is:", this.formatInfo(format).mimetype);
  1290. blob = new Blob([blob],{ type: "text/plain; charset=utf-8"});
  1291. }
  1292. else if(format == "stlb") {
  1293. blob = this.currentObject.toStlBinary({webBlob: true});
  1294. // -- binary string -> blob gives bad data, so we request cgs.js already blobbing the binary
  1295. //blob = new Blob([blob],{ type: this.formatInfo(format).mimetype+"/charset=UTF-8" });
  1296. }
  1297. else if(format == "amf") {
  1298. blob = this.currentObject.toAMFString({
  1299. producer: "BlocksCAD "+Blockscad.version,
  1300. date: new Date()
  1301. });
  1302. blob = new Blob([blob],{ type: "text/plain; charset=utf-8"});
  1303. }
  1304. else if(format == "x3d") {
  1305. blob = this.currentObject.toX3D();
  1306. blob = new Blob([blob],{ type: "text/plain; charset=utf-8"});
  1307. }
  1308. else if(format == "dxf") {
  1309. blob = this.currentObject.toDxf();
  1310. blob = new Blob([blob],{ type: "text/plain; charset=utf-8"});
  1311. }
  1312. else if (format == "obj") {
  1313. blob = this.currentObject.toObj();
  1314. blob = new Blob([blob],{type: "text/plain; charset=utf-8"});
  1315. }
  1316. else {
  1317. throw new Error("Not supported");
  1318. }
  1319. return blob;
  1320. },
  1321. supportedFormatsForCurrentObject: function() {
  1322. if (this.currentObject instanceof CSG) {
  1323. // if safari, don't let them save stlb
  1324. if (navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1)
  1325. return ["stla", "amf", "x3d"];
  1326. return ["stlb", "stla", "x3d", "obj", "amf"];
  1327. } else if (this.currentObject instanceof CAG) {
  1328. return ["dxf"];
  1329. } else {
  1330. throw new Error("Not supported");
  1331. }
  1332. },
  1333. formatInfo: function(format) {
  1334. return {
  1335. stla: {
  1336. displayName: "STL (ASCII)",
  1337. extension: "stl",
  1338. mimetype: "application/sla",
  1339. },
  1340. obj: {
  1341. displayName: "OBJ (ASCII)",
  1342. extension: "obj",
  1343. mimetype: "application/sla",
  1344. },
  1345. stlb: {
  1346. displayName: "STL (Binary)",
  1347. extension: "stl",
  1348. mimetype: "application/sla",
  1349. },
  1350. amf: {
  1351. displayName: "AMF",
  1352. extension: "amf",
  1353. mimetype: "application/amf+xml",
  1354. },
  1355. x3d: {
  1356. displayName: "X3D",
  1357. extension: "x3d",
  1358. mimetype: "model/x3d+xml",
  1359. },
  1360. dxf: {
  1361. displayName: "DXF",
  1362. extension: "dxf",
  1363. mimetype: "application/dxf",
  1364. }
  1365. }[format];
  1366. },
  1367. generateAndSaveRenderedFile: function() {
  1368. var blob = this.currentObjectToBlob();
  1369. var ext = this.selectedFormatInfo().extension;
  1370. // I want the user to be able to enter a filename for the stl download - JY
  1371. // pull a filename entered by the user
  1372. var filename = $('#project-name').val();
  1373. // don't save without a filename. Name isn't checked for quality.
  1374. if (filename) {
  1375. saveAs(blob, filename + "." + ext);
  1376. }
  1377. else {
  1378. $('#message-text').html("<h4>" + Blockscad.Msg.SAVE_FAILED + ' ' + Blockscad.Msg.SAVE_FAILED_PROJECT_NAME + ".</h4>");
  1379. $('#message-modal').modal();
  1380. }
  1381. },
  1382. takeRotatingPic: function(quality, numframes) {
  1383. var frames = [];
  1384. var images = [];
  1385. var c = document.createElement('canvas');
  1386. c.width = Blockscad.rpicSize[0] * numframes;
  1387. c.height = Blockscad.rpicSize[0];
  1388. var ctx = c.getContext("2d");
  1389. for (var i = 0; i < numframes; i += 1) {
  1390. var angle = -i * (2*Math.PI / numframes);
  1391. frames[i] = this.rpicviewer.takePic(quality,angle)[0];
  1392. images[i] = new Image();
  1393. images[i].src = frames[i];
  1394. ctx.drawImage(images[i],i * Blockscad.rpicSize[0],0);
  1395. // change angle?
  1396. }
  1397. var strip = c.toDataURL("image/jpeg");
  1398. // console.log("have a strip - returning it");
  1399. return strip;
  1400. }
  1401. };
  1402. // pathToPoints() takes a Path object created by opentype.js
  1403. // resolution is a number used for the number of points used
  1404. // to approximate a curve in the font path
  1405. // returns an array of points and an array of paths
  1406. // NOTE: web svg coordinates have flipped Y coordinates
  1407. // (increasing positive as you move down)
  1408. // so all Y coordinates are multiplied by -1
  1409. Blockscad.pathToPoints = function(path,resolution) {
  1410. var points = [];
  1411. var paths = [];
  1412. var new_path = [];
  1413. var fn = 2; // default resolution in case resolution is not >= 2
  1414. var to, c1,c2,nx,ny,a; //for curve approximation
  1415. if (resolution > 2) fn = resolution;
  1416. if (fn > 10) fn = 10; // cap the resolution for performance
  1417. // console.log(path.commands);
  1418. if (path && path.commands && path.commands.length > 0) {
  1419. // hopefully got a legal path
  1420. var point_index = 0;
  1421. var prev = []; // save the previous point for curves
  1422. for (var i = 0; i < path.commands.length; i++) {
  1423. switch(path.commands[i].type) {
  1424. case 'M':
  1425. // save, then clear, the last path
  1426. if (new_path.length>2) {
  1427. paths.push(new_path);
  1428. }
  1429. new_path = [];
  1430. // load up the new point
  1431. points.push([path.commands[i].x, -1 * path.commands[i].y]);
  1432. new_path.push(point_index++);
  1433. prev = [path.commands[i].x, -1 * path.commands[i].y];
  1434. break;
  1435. case 'L':
  1436. // load up the new point
  1437. points.push([path.commands[i].x, -1 * path.commands[i].y]);
  1438. new_path.push(point_index++);
  1439. prev = [path.commands[i].x, -1 * path.commands[i].y];
  1440. break;
  1441. case 'C':
  1442. // Cubic Bezier curve
  1443. // uses two control points c1(x1,y1) and c2(x2,y2)
  1444. // the previous point prev[x,y], and current point to[x,y]
  1445. to = [path.commands[i].x, -1 * path.commands[i].y];
  1446. c1 = [path.commands[i].x1, -1 * path.commands[i].y1];
  1447. c2 = [path.commands[i].x2, -1 * path.commands[i].y2];
  1448. // approximate the curve with fn points
  1449. for (var k=1;k<=fn;k++) {
  1450. a = k / fn;
  1451. nx = prev[0] * Math.pow(1-a,3) +
  1452. c1[0] * 3 * Math.pow(1-a,2) * a +
  1453. c2[0] * 3 * Math.pow(1-a,1) * a * a +
  1454. to[0] * Math.pow(a,3);
  1455. nx = prev[1] * Math.pow(1-a,3) +
  1456. c1[1] * 3 * Math.pow(1-a,2) * a +
  1457. c2[1] * 3 * Math.pow(1-a,1) * a * a +
  1458. to[1] * Math.pow(a,3);
  1459. // load up this new point
  1460. points.push([nx,ny]);
  1461. new_path.push(point_index++);
  1462. }
  1463. prev = to;
  1464. break;
  1465. case 'Q':
  1466. // Quadratic Bezier curve
  1467. // uses one control point c1[x1,y1]
  1468. // the previous point prev[x,y], and current point to[x,y]
  1469. to = [path.commands[i].x, -1 * path.commands[i].y];
  1470. c1 = [path.commands[i].x1, -1 * path.commands[i].y1];
  1471. for (var k=1;k<=fn;k++) {
  1472. a = k / fn;
  1473. nx = prev[0] * Math.pow(1-a,2) +
  1474. c1[0] * 2 * Math.pow(1-a,1) * a +
  1475. to[0] * Math.pow(a,2);
  1476. ny = prev[1] * Math.pow(1-a,2) +
  1477. c1[1] * 2 * Math.pow(1-a,1) * a +
  1478. to[1] * Math.pow(a,2);
  1479. // load up this new point
  1480. points.push([nx,ny]);
  1481. new_path.push(point_index++);
  1482. }
  1483. prev = to;
  1484. break;
  1485. case 'Z':
  1486. // log the old path
  1487. if (new_path.length>2) {
  1488. paths.push(new_path);
  1489. new_path = [];
  1490. }
  1491. break;
  1492. } // end switch commands
  1493. }
  1494. }
  1495. // else console.log("no path found");
  1496. // fix case with a Path with just an MZ
  1497. if (points.length < 3) points = [];
  1498. return [points,paths];
  1499. };