blockscad.js 64 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874
  1. /*
  2. Copyright (C) 2014-2015 H3XL, Inc
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU General Public License as published by
  5. the Free Software Foundation, either version 3 of the License, or
  6. (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. /**
  15. * @fileoverview JavaScript for BlocksCAD.
  16. * @author jennie@einsteinsworkshop.com (jayod)
  17. */
  18. 'use strict';
  19. // create Blockscad namespace
  20. var Blockscad = Blockscad || {};
  21. Blockscad.Toolbox = Blockscad.Toolbox || {};
  22. Blockscad.Auth = Blockscad.Auth || {}; // cloud accounts plugin
  23. BlocklyStorage = BlocklyStorage || {};
  24. var Blockly = Blockly || {};
  25. var BSUtils = BSUtils || {};
  26. Blockscad.version = "1.7.3";
  27. Blockscad.releaseDate = "2017/03/28";
  28. Blockscad.offline = true; // if true, won't attempt to contact the Blockscad cloud backend.
  29. Blockscad.standalone = false; // if true, run code needed for the standalone version
  30. Blockscad.gProcessor = null; // hold the graphics processor, including the mesh generator and viewer.
  31. var _includePath = './';
  32. Blockscad.drawAxes = 1; // start with axes drawn
  33. // resolution - this value will control the default returned by $fn in the parser.
  34. // In theory I could just have the parser poll the value directly. Overwritten by the sides block.
  35. Blockscad.resolution = 1;
  36. Blockscad.showMessageModal = false;
  37. // Initialize Blockscad. Called on page load.
  38. Blockscad.init = function() {
  39. var pageData = blockscadpage.start();
  40. $('body').append(pageData);
  41. Blockscad.initLanguage();
  42. // version of input files/projects
  43. Blockscad.inputVersion = Blockscad.version;
  44. var rtl = BSUtils.isRtl();
  45. Blockscad.missingFields = []; // variable to see if any blocks are missing fields
  46. Blockscad.csg_commands = {}; // holds any converted stl file contents
  47. Blockscad.csg_filename = {}; // holds any converted stl file names
  48. Blockscad.csg_center = [0,0,0];
  49. Blockscad.renderActions = [];
  50. var container = document.getElementById('main');
  51. var onresize = function(e) {
  52. var bBox = BSUtils.getBBox_(container);
  53. var el = document.getElementById('blocklyDiv');
  54. el.style.top = bBox.y + 'px';
  55. el.style.left = bBox.x + 'px';
  56. // Height and width need to be set, read back, then set again to
  57. // compensate for scrollbars.
  58. el.style.height = bBox.height - 88 + 'px';
  59. el.style.width = bBox.width + 'px';
  60. // resize the viewer
  61. if (Blockscad.gProcessor != null && Blockscad.gProcessor.viewer) {
  62. var h = Blockscad.gProcessor.viewerdiv.offsetHeight;
  63. var w = Blockscad.gProcessor.viewerdiv.offsetWidth;
  64. Blockscad.gProcessor.viewer.rendered_resize(w,h);
  65. }
  66. /********************remove resizableDiv change***********************/
  67. // position the div using left and top (that's all I get!)
  68. // if ($( '#main' ).height() - $( '.resizableDiv' ).height() < 70)
  69. // $( '.resizableDiv' ).height($( '#main' ).height() - 70);
  70. // if ($( '#main' ).width() - $( '.resizableDiv' ).width() < 20)
  71. // $( '.resizableDiv' ).width($( '#main' ).width() - 20);
  72. // reposition the resizable div.
  73. // $(".resizableDiv").position({
  74. // of: $('#main'),
  75. // my: 'right top',
  76. // at: 'right top',
  77. // offset: '-12 -55'
  78. // });
  79. //
  80. //
  81. };
  82. window.addEventListener('resize', onresize, false);
  83. Blockscad.Toolbox.createToolbox();
  84. Blockscad.workspace = Blockly.inject(document.getElementById('blocklyDiv'),
  85. {
  86. media: 'blockly/media/',
  87. zoom:
  88. {controls: true,
  89. wheel: true,
  90. startScale: 1.0,
  91. maxScale: 3,
  92. minScale: 0.3,
  93. scaleSpeed: 1.2},
  94. trashcan: false,
  95. toolbox: Blockscad.Toolbox.adv});
  96. // Listen to events on blocksCAD workspace.
  97. Blockscad.workspace.addChangeListener(Blockscad.handleWorkspaceEvents);
  98. // set the initial color scheme
  99. Blockscad.Toolbox.setColorScheme(Blockscad.Toolbox.colorScheme['one']);
  100. // color the initial toolbox
  101. Blockscad.Toolbox.setCatColors();
  102. // hide "switch to advanced toolbox" because that's where we'll start
  103. $('#advancedToolbox').addClass('hidden');
  104. if ('BlocklyStorage' in window) {
  105. // Hook a save function onto unload.
  106. BlocklyStorage.backupOnUnload();
  107. }
  108. // how about putting in the viewer?
  109. /********************remove resizableDiv change***********************/
  110. // $(".resizableDiv").resizable({
  111. // handles: "s,w,sw",
  112. // resize: function(event, ui) {
  113. // var h = $( window ).height();
  114. // // resize the viewer
  115. // if (Blockscad.gProcessor != null) {
  116. // h = Blockscad.gProcessor.viewerdiv.offsetHeight;
  117. // var w = Blockscad.gProcessor.viewerdiv.offsetWidth;
  118. // Blockscad.gProcessor.viewer.rendered_resize(w,h);
  119. // }
  120. // // position the div using left and top (that's all I get!)
  121. // if ($( '#main' ).width() - ui.size.width < 20)
  122. // ui.size.width = $( '#main' ).width() - 20;
  123. // if ($( '#main' ).height() - ui.size.height < 70)
  124. // ui.size.height = $( '#main' ).height() - 70;
  125. // ui.position.left = $( window ).width() - (ui.size.width + 12);
  126. // ui.position.top = 55;
  127. // }
  128. // });
  129. Blockly.svgResize(Blockscad.workspace);
  130. window.dispatchEvent(new Event('resize'));
  131. if (!Blockscad.offline) {
  132. // init the user auth stuff
  133. Blockscad.Auth.init();
  134. }
  135. BSUtils.bindClick('trashButton',
  136. function() {Blockscad.discard(); });
  137. // render button should render geometry, draw axes, etc.
  138. BSUtils.bindClick('renderButton', Blockscad.doRender);
  139. // undo/redo buttons should undo/redo changes
  140. BSUtils.bindClick('undoButton',
  141. function() {
  142. Blockscad.workspace.undo(false)
  143. });
  144. BSUtils.bindClick('redoButton',
  145. function() {
  146. Blockscad.workspace.undo(true)
  147. });
  148. $( '#axesButton' ).click(function() {
  149. // toggle whether or not we draw the axes, then redraw
  150. Blockscad.drawAxes = (Blockscad.drawAxes + 1) % 2;
  151. $( '#axesButton' ).toggleClass("btn-pushed");
  152. Blockscad.gProcessor.viewer.onDraw();
  153. });
  154. $( '#zInButton' ).click(function() {
  155. Blockscad.gProcessor.viewer.zoomIn();
  156. });
  157. $( '#zOutButton' ).click(function() {
  158. Blockscad.gProcessor.viewer.zoomOut();
  159. });
  160. $( '#zResetButton' ).click(function() {
  161. Blockscad.gProcessor.viewer.viewReset();
  162. });
  163. $( '#cameraButton' ).click(function() {
  164. // toggle whether or not we draw the axes, then redraw
  165. // console.log("cameraButton clicked");
  166. Blockscad.cameraPic();
  167. });
  168. // code tab original code - uncomment this when you're done with parser testing
  169. // can I bind a click to a tab?
  170. $( '#displayCode' ).click( function() {
  171. var content = document.getElementById('openScadPre');
  172. var code = Blockly.OpenSCAD.workspaceToCode(Blockscad.workspace);
  173. // code has been run off the blocks. It hasn't been sent to the parser yet. It is openscad code.
  174. // I want to keep the currint openscad code but hack the assigns.
  175. var codeForOutput = Blockscad.processCodeForOutput(code);
  176. content.textContent = codeForOutput;
  177. Blockly.svgResize(Blockscad.workspace);
  178. });
  179. // I think the render button should start out disabled.
  180. // $('#renderButton').prop('disabled', true);
  181. // handle the project->new menu option
  182. $('#main').on('click', '.new-project', Blockscad.newProject);
  183. // handle the project->load (blocks, stl, import blocks) options
  184. $('#file-menu').on('change', '#loadLocal', function(e) { Blockscad.loadLocalBlocks(e);});
  185. $('#file-menu').on('change', '#importLocal', function(e) { readSingleFile(e, false);});
  186. $('#file-menu').on('change', '#importStl', function(e) { Blockscad.readStlFile(e);});
  187. // what size should pics be taken at?
  188. Blockscad.picSize = [450,450];
  189. Blockscad.rpicSize = [250,250];
  190. Blockscad.picQuality = 0.85; // JPEG quality level - must be between 0 and 1
  191. Blockscad.numRotPics = 13;
  192. // hook up the pic-taking button
  193. $("#picButton").click(Blockscad.takePic);
  194. $("#rPicButton").click(Blockscad.takeRPic);
  195. //Create the openjscad processing object instance
  196. Blockscad.gProcessor = new Blockscad.Processor(document.getElementById("renderDiv"));
  197. //render view reset button - JY
  198. // BSUtils.bindClick('viewReset', Blockscad.resetView);
  199. // $( '#viewMenu' ).change(function() {
  200. // Blockscad.gProcessor.viewer.viewReset();
  201. // });
  202. // do we need to prompt the user to save? to start out, no.
  203. Blockscad.needToSave = 0;
  204. // test to see if a user is logged in - use this to populate the login-area.
  205. if (!Blockscad.offline) {
  206. Blockscad.Auth.checkForUser();
  207. }
  208. // pop up about popup
  209. $('#help-menu').on('click', '#about', function() {
  210. $('#about-modal').modal('show');
  211. });
  212. // set up handler for saving blocks locally
  213. $('#file-menu').on('click', '#saveLocal', Blockscad.saveBlocksLocal);
  214. // set up handler for exporting openscad code locally
  215. $('#file-menu').on('click', '#saveOpenscad', Blockscad.saveOpenscadLocal);
  216. // toolbox toggle handlers
  217. $('#simpleToolbox').on('click', function() {
  218. // console.log("switching to simple toolbox");
  219. $('#simpleToolbox').addClass('hidden');
  220. $('#advancedToolbox').removeClass('hidden');
  221. if (Blockscad.workspace) {
  222. Blockscad.Toolbox.catIDs = [];
  223. Blockscad.workspace.updateToolbox(Blockscad.Toolbox.sim);
  224. Blockscad.Toolbox.setCatColors();
  225. }
  226. });
  227. $('#advancedToolbox').on('click', function() {
  228. // console.log("switching to advanced toolbox");
  229. $('#advancedToolbox').addClass('hidden');
  230. $('#simpleToolbox').removeClass('hidden');
  231. if (Blockscad.workspace) {
  232. Blockscad.Toolbox.catIDs = [];
  233. Blockscad.workspace.updateToolbox(Blockscad.Toolbox.adv);
  234. Blockscad.Toolbox.setCatColors();
  235. }
  236. });
  237. $('#colors_one').on('click', function() {
  238. // console.log("switching block color scheme");
  239. if (Blockscad.workspace) {
  240. Blockscad.Toolbox.setColorScheme(Blockscad.Toolbox.colorScheme['one']);
  241. Blockscad.Toolbox.setCatColors();
  242. var current_xml = Blockly.Xml.workspaceToDom(Blockscad.workspace);
  243. Blockscad.workspace.clear();
  244. Blockly.Xml.domToWorkspace(current_xml,Blockscad.workspace);
  245. }
  246. });
  247. $('#colors_two').on('click', function() {
  248. // console.log("switching block color scheme");
  249. if (Blockscad.workspace) {
  250. Blockscad.Toolbox.setColorScheme(Blockscad.Toolbox.colorScheme['two']);
  251. Blockscad.Toolbox.setCatColors();
  252. var current_xml = Blockly.Xml.workspaceToDom(Blockscad.workspace);
  253. Blockscad.workspace.clear();
  254. Blockly.Xml.domToWorkspace(current_xml,Blockscad.workspace);
  255. }
  256. });
  257. // add "default color" picker to viewer
  258. // r,g,b is expecting to get color values between 0 and 255 for r,g,b
  259. Blockscad.setColor = function(r,g,b) {
  260. // console.log("in setColor. rgb:" + r + ";" + g + ';' + b);
  261. if (Blockscad.gProcessor != null && Blockscad.gProcessor.viewer){
  262. Blockscad.gProcessor.viewer.defaultColor = [r/255,g/255,b/255,1];
  263. Blockscad.gProcessor.picviewer.defaultColor = [r/255,g/255,b/255,1];
  264. Blockscad.gProcessor.rpicviewer.defaultColor = [r/255,g/255,b/255,1];
  265. if (Blockscad.gProcessor.hasSolid()) {
  266. // I have a solid already rendered - change its color!
  267. Blockscad.gProcessor.viewer.setCsg(Blockscad.gProcessor.currentObject);
  268. Blockscad.gProcessor.picviewer.setCsg(Blockscad.gProcessor.currentObject);
  269. Blockscad.gProcessor.rpicviewer.setCsg(Blockscad.gProcessor.currentObject);
  270. // update the display image, thumbnail, and picture strip for rotating views
  271. var images = Blockscad.gProcessor.picviewer.takePic(Blockscad.picQuality,0);
  272. Blockscad.gProcessor.img = images[0];
  273. Blockscad.gProcessor.imgStrip = Blockscad.gProcessor.takeRotatingPic(0.9,Blockscad.numRotPics);
  274. Blockscad.gProcessor.thumbnail = images[1];
  275. }
  276. Blockscad.defaultColor = Math.round(r) + ',' + Math.round(g) + ',' + Math.round(b);
  277. $("#defColor").spectrum("set", 'rgb(' + Blockscad.defaultColor + ')');
  278. }
  279. }
  280. $("#defColor").spectrum({
  281. color: 'rgb(255,128,255)',
  282. showPalette: true,
  283. className: "defaultColor btn btn-default",
  284. appendTo: "#viewerButtons",
  285. hideAfterPaletteSelect:true,
  286. showPaletteOnly: true,
  287. change: function(color) {
  288. Blockscad.setColor(color._r,color._g,color._b);
  289. },
  290. palette: [
  291. ['rgb(255,128,255);', 'rgb(153,153,153);','rgb(238,60,60);', 'rgb(250,150,0);'],
  292. ['rgb(250,214,0);' , 'rgb(50,220,50);' ,'rgb(20,150,255);' , 'rgb(180,85,254);']
  293. ]
  294. });
  295. // the color picker has a downward triangle that I don't like. Remove it.
  296. $('.sp-dd').remove();
  297. // // add color picker to help menu (for use with color rgb block)
  298. // $("#colorPicker").spectrum({
  299. // color: 'rgb(255,128,255)',
  300. // showPalette: false,
  301. // className: "colSelect",
  302. // hideAfterPaletteSelect:false,
  303. // preferredFormat:'hsl',
  304. // showInput:true
  305. // });
  306. // example handlers
  307. // to add an example, add a list item in index.html, add a click handler below,
  308. // and be sure to put the name of the example file in the msg field. The xml
  309. // file should be saved in the "examples" folder.
  310. $("#examples_torus").click({msg: "torus.xml"}, Blockscad.showExample);
  311. $("#examples_box").click({msg: "box.xml"}, Blockscad.showExample);
  312. $("#examples_linear_extrude").click({msg: "linear_extrude.xml"}, Blockscad.showExample);
  313. $("#examples_rotate_extrude").click({msg: "rotate_extrude.xml"}, Blockscad.showExample);
  314. $("#examples_cube_with_cutouts").click({msg: "cube_with_cutouts.xml"}, Blockscad.showExample);
  315. $("#examples_anthias_fish").click({msg: "anthias_fish.xml"}, Blockscad.showExample);
  316. $("#examples_hulled_loop_sun").click({msg: "hulled_loop_sun.xml"}, Blockscad.showExample);
  317. $("#examples_sine_function_with_loop").click({msg: "sine_function_with_loop.xml"}, Blockscad.showExample);
  318. $("#examples_trefoil_knot_param_eq").click({msg: "trefoil_knot_param_eq.xml"}, Blockscad.showExample);
  319. // to get sub-menus to work with bootstrap 3 navbar
  320. $(function(){
  321. $(".dropdown-menu > li > a.trigger").on("click",function(e){
  322. var current=$(this).next();
  323. var grandparent=$(this).parent().parent();
  324. if($(this).hasClass('left-caret')||$(this).hasClass('right-caret'))
  325. $(this).toggleClass('right-caret left-caret');
  326. grandparent.find('.left-caret').not(this).toggleClass('right-caret left-caret');
  327. grandparent.find(".sub-menu:visible").not(current).hide();
  328. current.toggle();
  329. e.stopPropagation();
  330. });
  331. $(".dropdown-menu > li > a:not(.trigger)").on("click",function(){
  332. var root=$(this).closest('.dropdown');
  333. root.find('.left-caret').toggleClass('right-caret left-caret');
  334. root.find('.sub-menu:visible').hide();
  335. });
  336. });
  337. $('#stl_buttons').addClass('hidden');
  338. if (!Blockscad.standalone) {
  339. BSUtils.loadBlocks('');
  340. }
  341. else {
  342. // for standalone, just call restoreBlocks directly
  343. // console.log("calling standalone restore");
  344. BlocklyStorage.standaloneRestoreBlocks();
  345. }
  346. // we've just initiated BlocksCAD (page was loaded). Run block typing on all blocks.
  347. // I'll do three passes - first variable setters (does that type variable getters?)
  348. // then procedures, then the rest.
  349. // I'm running it in a timeout to make sure any events have had time to fire. What a pain.
  350. // are there any messages to show?
  351. if (Blockscad.showMessageModal)
  352. $('#outage-modal').modal('show');
  353. setTimeout(Blockscad.typeWorkspace, 10);
  354. }; // end Blockscad.init()
  355. Blockscad.typeWorkspace = function() {
  356. // I'll do three passes - first variable setters (does that type variable getters?)
  357. // then procedures, then the rest, then the whole thing.
  358. // console.log("running typeWorkspace");
  359. var blocks = Blockscad.workspace.getAllBlocks();
  360. for (var i = 0; i < blocks.length; i++) {
  361. if (blocks[i].type == 'variables_set')
  362. Blockscad.assignVarTypes(blocks[i]);
  363. }
  364. var topBlocks = Blockscad.workspace.getTopBlocks();
  365. for (var i = 0; i < topBlocks.length; i++) {
  366. if (topBlocks[i].category && topBlocks[i].category == 'PROCEDURE') {
  367. // console.log("found a procedure to type");
  368. Blockscad.assignBlockTypes([topBlocks[i]]);
  369. }
  370. }
  371. for (var k = 0; k < blocks.length; k++) {
  372. if (blocks[k].type != 'variables_set' && blocks[k].category != 'PROCEDURE') {
  373. Blockscad.assignBlockTypes(blocks[k]);
  374. }
  375. }
  376. Blockscad.assignBlockTypes(Blockscad.workspace.getTopBlocks());
  377. }
  378. // type a new block stack. block is not guaranteed to be the top block in the stack.
  379. Blockscad.typeNewStack = function(block) {
  380. // three passes - variables, procedures, then the rest.
  381. // this is called when blocks are created (think duplicated block stacks with callers in it)
  382. // console.log("in typeNewStack");
  383. var topBlock = block.getRootBlock();
  384. var blockStack = topBlock.getDescendants();
  385. for (var i = 0; blockStack && i < blockStack.length; i++) {
  386. if (blockStack[i].type == 'variables_set' || blockStack[i].type == 'variables_get')
  387. Blockscad.assignVarTypes(blockStack[i]);
  388. }
  389. for (var i = 0; blockStack && i < blockStack.length; i++) {
  390. if (blockStack[i].category == 'PROCEDURE') {
  391. Blockscad.assignBlockTypes([blockStack[i]]);
  392. }
  393. }
  394. for (var i = 0; blockStack && i < blockStack.length; i++) {
  395. if (blockStack[i].category == 'SET_OP' ||
  396. blockStack[i].category == 'TRANSFORM' ||
  397. blockStack[i].category == 'LOOP') {
  398. Blockscad.assignBlockTypes([blockStack[i]]);
  399. }
  400. }
  401. }
  402. // Blockscad.takeRPic
  403. // this takes and stores a strip of jpegs showing a rotating model
  404. // for display with a blockscad project (either on our website or on a
  405. // model page, etc.)
  406. Blockscad.takeRPic = function() {
  407. if (Blockscad.gProcessor != null) {
  408. // takeRotatingPic(quality, numFrames)
  409. // leave quality at 1!
  410. // thing holds the frames from the canvas.
  411. var strip = Blockscad.gProcessor.imgStrip;
  412. if (strip)
  413. Blockscad.savePic(strip, $('#project-name').val() + '.jpg');
  414. // var imageObj1 = new Image();
  415. // var imageObj2 = new Image();
  416. // imageObj1.src = "1.png"
  417. // imageObj1.onload = function() {
  418. // ctx.drawImage(imageObj1, 0, 0, 328, 526);
  419. // imageObj2.src = "2.png";
  420. // imageObj2.onload = function() {
  421. // ctx.drawImage(imageObj2, 15, 85, 300, 300);
  422. // var img = c.toDataURL("image/png");
  423. // document.write('<img src="' + img + '" width="328" height="526"/>');
  424. // }
  425. // };
  426. // console.log("got rotating pic");
  427. // console.log(thing);
  428. // var gif = gifshot.createGIF({
  429. // 'images': thing,
  430. // 'interval': 0.4,
  431. // 'gifWidth': Blockscad.picSize[0],
  432. // 'gifHeight': Blockscad.picSize[1],
  433. // 'sampleInterval': 1,
  434. // }, function(obj) {
  435. // if (!obj.error) {
  436. // var image = obj.image;
  437. // Blockscad.savePic(image, $('#project-name').val() + '.gif');
  438. // }
  439. // });
  440. }
  441. }
  442. Blockscad.savePic = function(image, name) {
  443. if (image) {
  444. var bytestream = atob(image.split(',')[1]);
  445. var mimestring = image.split(',')[0].split(':')[1].split(';')[0];
  446. // write the bytes of the string to an ArrayBuffer
  447. var ab = new ArrayBuffer(bytestream.length);
  448. var ia = new Uint8Array(ab);
  449. for (var i = 0; i < bytestream.length; i++) {
  450. ia[i] = bytestream.charCodeAt(i);
  451. }
  452. // console.log("jpeg size: ", bytestream.length);
  453. var blob = new Blob([ab], {type: "img/jpeg"});
  454. saveAs(blob, name);
  455. }
  456. }
  457. Blockscad.takePic = function() {
  458. if (Blockscad.gProcessor) {
  459. // console.log("image",image);
  460. if (Blockscad.gProcessor.img && Blockscad.gProcessor.img != "null")
  461. Blockscad.savePic(Blockscad.gProcessor.img, $('#project-name').val() + '.jpg');
  462. }
  463. }
  464. Blockscad.cameraPic = function() {
  465. if (Blockscad.gProcessor.viewer) {
  466. // the parameter here is the jpeg quality - between 0 and 1.
  467. //var image = Blockscad.gProcessor.viewer.takePic(.95,0);
  468. var image = Blockscad.gProcessor.viewer.takeCameraPic(.95);
  469. // Blockscad.gProcessor.img = image;
  470. // console.log("image",image);
  471. if (image)
  472. Blockscad.savePic(image, $('#project-name').val() + '.jpg');
  473. }
  474. }
  475. Blockscad.loadLocalBlocks = function(e) {
  476. var evt = e;
  477. if (evt.target.files.length) {
  478. if (Blockscad.needToSave) {
  479. promptForSave().then(function(wantToSave) {
  480. if (wantToSave=="cancel") {
  481. return;
  482. }
  483. if (wantToSave=="nosave")
  484. Blockscad.setSaveNeeded();
  485. else if (wantToSave=="save")
  486. Blockscad.saveBlocks();
  487. // console.log("time to load the local blocks!");
  488. Blockscad.createNewProject();
  489. readSingleFile(evt,true);
  490. }).catch(function(result) {
  491. console.log("caught an error in new project. result is:" + result);
  492. });
  493. }
  494. else {
  495. // console.log("no need to save old project. Just load the new blocks.");
  496. Blockscad.createNewProject();
  497. readSingleFile(evt,true);
  498. }
  499. }
  500. }
  501. //FileSaver.js stuff
  502. // Loading a blocks xml file
  503. // if replaceOld is true, any current blocks are ditched, a new project is started
  504. // and the filename loaded is used as the project filename.
  505. // if replaceOld is false, the blocks are inserted in the current project,
  506. // adding to the blocks that are there already and not changing the filename.
  507. function readSingleFile(evt, replaceOld) {
  508. //Retrieve the first (and only!) File from the FileList object
  509. var f = evt.target.files[0];
  510. //console.log("in readSingleFile. f is ", f);
  511. if (f) {
  512. var proj_name;
  513. if (replaceOld) {
  514. // use the name of the loaded file to fill the "file loading" and "project name" boxes.
  515. proj_name = f.name.substr(0,f.name.lastIndexOf('(')) || f.name;
  516. proj_name = proj_name.substr(0,f.name.lastIndexOf('.')) || proj_name;
  517. // trim any whitespace from the beginning or end of the project name
  518. proj_name = proj_name.replace(/^\s+|\s+$/g,'');
  519. }
  520. if (replaceOld) {
  521. // first, autosave anything new. Is there anything on the undo stack? If so, save the changes.
  522. // if (Blockscad.needToSave) {
  523. // // Blockscad.Auth.saveBlocksToAccount();
  524. // }
  525. // if we had a current project before, we just changed to something else!
  526. Blockscad.Auth.currentProject = '';
  527. // clear the workspace to fit the new file contents.
  528. }
  529. if (replaceOld)
  530. Blockly.getMainWorkspace().clear();
  531. var contents = {};
  532. var stuff = {};
  533. var r = new FileReader();
  534. // all the file processing has to go inside the onload function. -JY
  535. r.onload = function(e) {
  536. contents = e.target.result;
  537. var xml = Blockly.Xml.textToDom(contents);
  538. Blockly.Xml.domToWorkspace(xml, Blockscad.workspace);
  539. Blockly.svgResize(Blockscad.workspace);
  540. Blockscad.clearStlBlocks();
  541. };
  542. r.readAsText(f);
  543. // in order that we can read this filename again, I'll clear out the current filename
  544. $("#importLocal")[0].value = '';
  545. $("#loadLocal")[0].value = '';
  546. if (replaceOld)
  547. $('#project-name').val(proj_name);
  548. // I should hide the projectView
  549. // , and show the editView.
  550. $('#projectView').addClass('hidden');
  551. $('#editView').removeClass('hidden');
  552. // turn the big save button back on.
  553. if (!Blockscad.offline) $('#bigsavebutton').removeClass('hidden');
  554. // switch us back to the blocks tab in case we were on the code tabe.
  555. $('#displayBlocks').click();
  556. // trigger a resize so that I make sure the window redraws.
  557. window.dispatchEvent(new Event('resize'));
  558. // clear the render window
  559. Blockscad.gProcessor.clearViewer();
  560. } else {
  561. // alert("Failed to load file");
  562. }
  563. }
  564. Blockscad.readStlFile = function(evt) {
  565. // this can be called from an existing importSTL block. If so,
  566. // don't create a new block - instead, update the fields on the existing block.
  567. //Retrieve the first (and only!) File from the FileList object
  568. var f = evt.target.files[0];
  569. if (f) {
  570. var contents = {};
  571. var stuff = {};
  572. var r = new FileReader();
  573. // all the file processing has to go inside the onload function. -JY
  574. r.onload = function(e) {
  575. var contents = e.target.result;
  576. var result = importSTL(contents);
  577. // console.log("result is:",result);
  578. var src = result[0];
  579. var center = result[1];
  580. if (!center) center = 'blah';
  581. // console.log(center);
  582. var proj_name = f.name.substr(0,f.name.lastIndexOf('(')) || f.name;
  583. proj_name = proj_name.substr(0,f.name.lastIndexOf('.')) || proj_name;
  584. // trim any whitespace from the beginning or end of the project name
  585. proj_name = proj_name.replace(/^\s+|\s+$/g,'');
  586. var proj_name_use = proj_name;
  587. var add = 1;
  588. var found_file = 0;
  589. while (Blockscad.csg_commands[proj_name_use] && !found_file) {
  590. if (src != Blockscad.csg_commands[proj_name_use]) {
  591. proj_name_use = proj_name + '_' + add;
  592. add++;
  593. }
  594. else found_file = 1;
  595. }
  596. //console.log("stl file parsed is",src);
  597. // save these CSG commands so I never have to run this conversion again.
  598. Blockscad.csg_commands[proj_name_use] = src;
  599. if (!found_file)
  600. Blockscad.csg_filename[proj_name_use] = f.name + ':::';
  601. else Blockscad.csg_filename[proj_name_use] += f.name + ':::';
  602. Blockscad.csg_center[proj_name_use] = center;
  603. // I've got a file here. What should I do with it?
  604. var bt_input;
  605. if (Blockscad.currentInterestingBlock) {
  606. // console.log('the current block is:', Blockscad.currentInterestingBlock);
  607. var fn_input = Blockscad.currentInterestingBlock.getField('STL_FILENAME');
  608. bt_input = Blockscad.currentInterestingBlock.getField('STL_BUTTON');
  609. var ct_input = Blockscad.currentInterestingBlock.getField('STL_CONTENTS');
  610. fn_input.setText(f.name);
  611. fn_input.setVisible(true);
  612. bt_input.setVisible(false);
  613. ct_input.setText(proj_name_use);
  614. Blockscad.currentInterestingBlock.setCommentText(f.name + '\ncenter:(' + center + ')');
  615. Blockscad.currentInterestingBlock = null;
  616. }
  617. else {
  618. // lets make some xml and load a block into the workspace.
  619. // console.log("making block from xml");
  620. var xml = '<xml xmlns="http://blockscad.einsteinsworkshop.com"><block type="stl_import" id="1" x="0" y="0"><field name="STL_FILENAME">' +
  621. f.name + '</field>' + '<field name="STL_BUTTON">' + Blockscad.Msg.BROWSE + '</field>' +
  622. '<field name="STL_CONTENTS">'+ proj_name_use + '</field></block></xml>';
  623. //console.log("xml is:",xml);
  624. var stuff = Blockly.Xml.textToDom(xml);
  625. var newblock = Blockly.Xml.domToBlock(Blockscad.workspace, stuff.firstChild);
  626. newblock.moveBy(20 + Blockscad.workspace.getMetrics().viewLeft / Blockscad.workspace.scale,
  627. 20 + Blockscad.workspace.getMetrics().viewTop / Blockscad.workspace.scale);
  628. bt_input = newblock.getField('STL_BUTTON');
  629. bt_input.setVisible(false);
  630. newblock.setCommentText(f.name + '\ncenter:(' + center + ')');
  631. newblock.render();
  632. }
  633. };
  634. r.readAsBinaryString(f);
  635. // in order that we can read this filename again, I'll clear out the current filename
  636. $("#importStl")[0].value = '';
  637. // switch us back to the blocks tab in case we were on the code tab.
  638. $('#displayBlocks').click();
  639. // enable the render button.
  640. // $('#renderButton').prop('disabled', false);
  641. } else {
  642. alert("Failed to load file");
  643. }
  644. };
  645. // Load Blockly's (and Blockscad's) language strings.
  646. // console.log("trying to include message strings");
  647. // console.log("language is: ", BSUtils.LANG);
  648. document.write('<script src="blockly/msg/js/' + BSUtils.LANG + '.js"></script>\n');
  649. document.write('<script src="blockscad/msg/js/' + BSUtils.LANG + '.js"></script>\n');
  650. // on page load, call blockscad init function.
  651. window.addEventListener('load', Blockscad.init);
  652. //clear out stl blocks that have lost their original file.
  653. Blockscad.clearStlBlocks = function() {
  654. // clear out any stl blocks.
  655. var blocks = Blockscad.workspace.getAllBlocks();
  656. var num_to_load = 0;
  657. for (var i = 0; i < blocks.length; i++){
  658. if (blocks[i].type == 'stl_import') {
  659. // var csg_key = blocks[i].getField('STL_CONTENTS').getText();
  660. // var csg_filename = blocks[i].getField('STL_FILENAME').getText();
  661. var browse_button = blocks[i].getField('STL_BUTTON');
  662. blocks[i].getField('STL_CONTENTS').setText('');
  663. blocks[i].getField('STL_FILENAME').setText('');
  664. var cText = blocks[i].getCommentText();
  665. if (!cText.match(/^RELOAD/)) cText = 'RELOAD: ' + cText;
  666. blocks[i].setCommentText(cText);
  667. browse_button.setText('Reload');
  668. blocks[i].backlight();
  669. // if block is in a collapsed parent, highlight collapsed parent too
  670. var others = blocks[i].collapsedParents();
  671. if (others)
  672. for (var j=0; j < others.length; j++)
  673. others[j].backlight();
  674. // make browse button visible if not collapsed
  675. if (!blocks[i].isCollapsed()) {
  676. browse_button.setVisible(true);
  677. }
  678. // Bubble up to re-collapsed top collapsed block
  679. var parent = blocks[i];
  680. var collapsedParent = null;
  681. while (parent) {
  682. if (parent.isCollapsed()) {
  683. collapsedParent = parent;
  684. }
  685. parent = parent.getSurroundParent();
  686. }
  687. if (collapsedParent) {
  688. collapsedParent.setCollapsed(true,true);
  689. }
  690. blocks[i].render();
  691. // Add warning to render pane: Hey, you have a file import block that needs reloading!
  692. $( '#error-message' ).html(Blockscad.Msg.WARNING_RELOAD_STL);
  693. }
  694. }
  695. };
  696. // Start a new project (save old project to account if logged in, clear blocks, clear rendered view)
  697. Blockscad.newProject = function() {
  698. // if the user was on the code tab, switch them to the blocks tab.
  699. $('#displayBlocks').click();
  700. // should I prompt a save here? If I have a current project, I should just save it? Or not?
  701. // if the user is logged in, I should auto-save to the backend.
  702. // console.log("in Blockscad.newProject");
  703. // console.log("needToSave is: ", Blockscad.needToSave);
  704. if (Blockscad.needToSave) {
  705. promptForSave().then(function(wantToSave) {
  706. if (wantToSave=="cancel") {
  707. return;
  708. }
  709. if (wantToSave=="nosave")
  710. Blockscad.setSaveNeeded();
  711. else if (wantToSave=="save")
  712. Blockscad.saveBlocks();
  713. // console.log("time to get a new project!");
  714. Blockscad.createNewProject();
  715. }).catch(function(result) {
  716. console.log("caught an error in new project. result is:" + result);
  717. });
  718. }
  719. else {
  720. // console.log("no need to save old project. Just make a new one..");
  721. Blockscad.createNewProject();
  722. }
  723. };
  724. Blockscad.createNewProject = function() {
  725. Blockscad.clearProject();
  726. // Blockscad.workspaceChanged();
  727. Blockscad.workspace.clearUndo();
  728. // disable undo buttons
  729. // $('#undoButton').prop('disabled', true);
  730. // $('#redoButton').prop('disabled', true);
  731. setTimeout(Blockscad.setSaveNeeded, 300);
  732. $('#displayBlocks').click();
  733. if (!Blockscad.offline)
  734. $('#bigsavebutton').removeClass('hidden');
  735. }
  736. // first attempt to use promises for async stuff!
  737. function promptForSave() {
  738. // console.log("in promptForSave()");
  739. var message = '<h4>' + Blockscad.Msg.SAVE_PROMPT + '</h4>';
  740. return new Promise(function(resolve, reject) {
  741. bootbox.dialog({
  742. message: message,
  743. backdrop: true,
  744. size: "small",
  745. buttons: {
  746. save: {
  747. label: Blockscad.Msg.SAVE_PROMPT_YES,
  748. className: "btn-default btn-lg primary pull-right giant-yes",
  749. callback: function(result) {
  750. // console.log("save clicked. Result was: ", result);
  751. resolve("save");
  752. }
  753. },
  754. dont_save: {
  755. label: Blockscad.Msg.SAVE_PROMPT_NO,
  756. className: "btn-default btn-lg primary pull-left giant-yes",
  757. callback: function(result) {
  758. // console.log("don't save clicked. Result was: ", result);
  759. resolve("nosave");
  760. }
  761. }
  762. },
  763. onEscape: function() {
  764. // console.log("bootbox dialog escaped.");
  765. resolve("cancel");
  766. }
  767. });
  768. });
  769. }
  770. Blockscad.saveBlocks = function() {
  771. // save to cloud storage if available and logged in
  772. if (!Blockscad.offline && Blockscad.Auth.isLoggedIn) {
  773. Blockscad.Auth.saveBlocksToAccount();
  774. }
  775. else {
  776. // i'm not logged into an account. Save blocks locally.
  777. Blockscad.saveBlocksLocal();
  778. }
  779. }
  780. Blockscad.showExample = function(e) {
  781. var example = "examples/" + e.data.msg;
  782. var name = e.data.msg.split('.')[0];
  783. // console.log("in showExample");
  784. if (Blockscad.needToSave) {
  785. promptForSave().then(function(wantToSave) {
  786. if (wantToSave=="cancel") {
  787. return;
  788. }
  789. if (wantToSave=="nosave")
  790. Blockscad.setSaveNeeded();
  791. else if (wantToSave=="save")
  792. Blockscad.saveBlocks();
  793. // console.log("i would try to show the example now!");
  794. Blockscad.getExample(example, name);
  795. }).catch(function(result) {
  796. console.log("caught an error in show example 1. result is:" + result);
  797. });
  798. }
  799. else {
  800. // console.log("no need to save old project. Just go get example.");
  801. Blockscad.getExample(example, name);
  802. }
  803. }
  804. Blockscad.getExample = function(example, name) {
  805. $.get(example, function( data ) {
  806. Blockscad.clearProject();
  807. // Blockscad.workspaceChanged();
  808. // turn xml data object into a string that Blockly can use
  809. var xmlString;
  810. //IE
  811. if (window.ActiveXObject){
  812. xmlString = data.xml;
  813. }
  814. // code for Mozilla, Firefox, Opera, etc.
  815. else{
  816. xmlString = (new XMLSerializer()).serializeToString(data);
  817. }
  818. // console.log(xmlString);
  819. // load xml blocks
  820. var xml = Blockly.Xml.textToDom(xmlString);
  821. Blockly.Xml.domToWorkspace(xml, Blockscad.workspace);
  822. Blockly.svgResize(Blockscad.workspace);
  823. // update project name
  824. $('#project-name').val(name + ' example');
  825. // we just got a new project. It doesn't need saving yet.
  826. setTimeout(Blockscad.setSaveNeeded, 300);
  827. });
  828. }
  829. // used for determining when to prompt the user to save.
  830. Blockscad.setSaveNeeded = function(saveNeeded) {
  831. if (saveNeeded) {
  832. Blockscad.needToSave = 1;
  833. // console.log("setting needToSave to 1");
  834. }
  835. else {
  836. Blockscad.needToSave = 0;
  837. // console.log("setting needToSave to 0");
  838. }
  839. }
  840. Blockscad.clearProject = function() {
  841. if (!Blockscad.offline) {
  842. // now I should make the new project.
  843. Blockscad.Auth.currentProject = '';
  844. Blockscad.Auth.currentProjectKey = '';
  845. }
  846. Blockscad.workspace.clear();
  847. Blockscad.gProcessor.clearViewer();
  848. $('#project-name').val(Blockscad.Msg.PROJECT_NAME_DEFAULT);
  849. $('#projectView').addClass('hidden');
  850. $('#editView').removeClass('hidden');
  851. // turn the big save button back on.
  852. $('#bigsavebutton').removeClass('hidden');
  853. // trigger a resize so that I make sure the window redraws.
  854. window.dispatchEvent(new Event('resize'));
  855. };
  856. /**
  857. * Discard all blocks from the workspace.
  858. */
  859. Blockscad.discard = function() {
  860. var count = Blockly.mainWorkspace.getAllBlocks().length;
  861. if (count < 2) {
  862. Blockly.mainWorkspace.clear();
  863. window.location.hash = '';
  864. }
  865. else {
  866. var message = Blockscad.Msg.DISCARD_ALL.replace("%1", count);
  867. bootbox.confirm({
  868. size: "small",
  869. message: message,
  870. buttons: {
  871. confirm: {
  872. label: Blockscad.Msg.CONFIRM_DIALOG_YES,
  873. className: "btn-default confirm-yes"
  874. },
  875. cancel: {
  876. label: Blockscad.Msg.CONFIRM_DIALOG_NO,
  877. className: "btn-default confirm-yes"
  878. },
  879. },
  880. callback: function(result) {
  881. if (result) {
  882. Blockly.mainWorkspace.clear();
  883. window.location.hash = '';
  884. }
  885. }
  886. });
  887. }
  888. };
  889. /* reset the rendering view */
  890. Blockscad.resetView = function() {
  891. if (Blockscad.gProcessor != null) {
  892. if (Blockscad.gProcessor.viewer) {
  893. Blockscad.gProcessor.viewer.viewReset();
  894. }
  895. // if (Blockscad.gProcessor.picviewer) {
  896. // Blockscad.gProcessor.picviewer.viewReset();
  897. // }
  898. }
  899. };
  900. // check for if there are both 2D and 3D shapes to be rendered
  901. Blockscad.mixes2and3D = function() {
  902. var topBlocks = [];
  903. topBlocks = Blockly.mainWorkspace.getTopBlocks();
  904. var hasCSG = 0;
  905. var hasCAG = 0;
  906. var hasUnknown = 0;
  907. var hasShape = 0; // assignTypes isn't firing after page load
  908. for (var i = 0; i < topBlocks.length; i++) {
  909. if (Blockscad.stackIsShape(topBlocks[i])) {
  910. hasShape = 1;
  911. var cat = topBlocks[i].category;
  912. var mytype;
  913. if (cat == 'PRIMITIVE_CSG') hasCSG++;
  914. if (cat == 'PRIMITIVE_CAG') hasCAG++;
  915. if (cat == 'TRANSFORM' || cat == 'SET_OP') {
  916. mytype = topBlocks[i].getInput('A').connection.check_;
  917. if (mytype.length == 1 && mytype[0] == 'CSG') hasCSG++;
  918. if (mytype.length == 1 && mytype[0] == 'CAG') hasCAG++;
  919. }
  920. if (cat == 'LOOP') {
  921. mytype = topBlocks[i].getInput('DO').connection.check_;
  922. if (mytype.length == 1 && mytype[0] == 'CSG') hasCSG++;
  923. if (mytype.length == 1 && mytype[0] == 'CAG') hasCAG++;
  924. }
  925. // I don't want a procedure definition to be counted as a shape.
  926. // only the calling block is actually a shape.
  927. // if (cat == 'PROCEDURE') {
  928. // mytype = topBlocks[i].myType_;
  929. // if (mytype && mytype == 'CSG') hasCSG++;
  930. // if (mytype && mytype == 'CAG') hasCAG++;
  931. // }
  932. if (cat == 'COLOR') hasCSG++;
  933. if (cat == 'EXTRUDE') hasCSG++;
  934. if (topBlocks[i].type == 'controls_if') hasUnknown++;
  935. }
  936. }
  937. if (hasShape && !(hasCSG + hasCAG + hasUnknown)) {
  938. // assign types needs to be called here.
  939. // console.log("assignTypes needed - why?");
  940. Blockscad.assignBlockTypes(Blockly.mainWorkspace.getTopBlocks());
  941. }
  942. return [(hasCSG && hasCAG), hasShape];
  943. };
  944. Blockscad.doRender = function() {
  945. // First, lets clear any old error messages.
  946. $( '#error-message' ).html("");
  947. $( '#error-message' ).removeClass("has-error");
  948. // if there are objects to render, I'm going to want to disable the render button!
  949. $('#renderButton').prop('disabled', true);
  950. // Clear the previously rendered model
  951. Blockscad.gProcessor.clearViewer();
  952. // check to see if the code mixes 2D and 3D shapes to give a good error message
  953. var mixes = Blockscad.mixes2and3D();
  954. if (mixes[1] === 0) { // doesn't have any CSG or CAG shapes at all!
  955. $( '#error-message' ).html(Blockscad.Msg.ERROR_MESSAGE + ": " + Blockscad.Msg.RENDER_ERROR_EMPTY);
  956. $( '#error-message' ).addClass("has-error");
  957. // enable the render button.
  958. $('#renderButton').prop('disabled', false);
  959. // HACK: file load is too slow - if user tries to render during file load
  960. // they get the "no objects to render" message. Enable the render button.
  961. //$('#renderButton').prop('disabled', false);
  962. return;
  963. }
  964. if (mixes[0]) { // has both 2D and 3D shapes
  965. $( '#error-message' ).html(Blockscad.Msg.ERROR_MESSAGE + ": " + Blockscad.Msg.RENDER_ERROR_MIXED);
  966. $( '#error-message' ).addClass("has-error");
  967. // enable the render button.
  968. $('#renderButton').prop('disabled', false);
  969. return;
  970. }
  971. // check for missing fields and illegal values in blocks. Highlight them for the user
  972. // and give an error message.
  973. Blockscad.missingFields = [];
  974. Blockscad.illegalValue = [];
  975. var code = Blockly.OpenSCAD.workspaceToCode(Blockscad.workspace);
  976. var gotErr = false;
  977. var others, blk;
  978. if (Blockscad.missingFields.length > 0) {
  979. // highlight the missing blocks, set up/display the correct error message
  980. for (var i = 0; i < Blockscad.missingFields.length; i++) {
  981. blk = Blockly.mainWorkspace.getBlockById(Blockscad.missingFields[i]);
  982. blk.unselect();
  983. blk.backlight();
  984. // if block is in a collapsed parent, highlight collapsed parent too
  985. others = blk.collapsedParents();
  986. if (others)
  987. for (var j=0; j < others.length; j++) {
  988. others[j].unselect();
  989. others[j].backlight();
  990. }
  991. gotErr = true;
  992. }
  993. }
  994. if (Blockscad.illegalValue.length > 0) {
  995. // highlight the missing blocks, set up/display the correct error message
  996. for (var i = 0; i < Blockscad.illegalValue.length; i++) {
  997. blk = Blockly.mainWorkspace.getBlockById(Blockscad.illegalValue[i]);
  998. blk.unselect();
  999. blk.backlight();
  1000. // if block is in a collapsed parent, highlight collapsed parent too
  1001. others = blk.collapsedParents();
  1002. if (others)
  1003. for (var j=0; j < others.length; j++) {
  1004. others[j].unselect();
  1005. others[j].backlight();
  1006. }
  1007. }
  1008. gotErr = true;
  1009. }
  1010. if (gotErr) {
  1011. var errText = '';
  1012. var error = '';
  1013. if (Blockscad.missingFields.length) {
  1014. error = Blockscad.Msg.ERROR_MESSAGE + ": " + Blockscad.Msg.PARSING_ERROR_MISSING_FIELDS;
  1015. errText = error.replace("%1", Blockscad.missingFields.length + " ");
  1016. }
  1017. if (Blockscad.missingFields.length && Blockscad.illegalValue.length)
  1018. errText += "<br>";
  1019. if (Blockscad.illegalValue.length) {
  1020. error = Blockscad.Msg.ERROR_MESSAGE + ": " + Blockscad.Msg.PARSING_ERROR_ILLEGAL_VALUE;
  1021. errText += error.replace("%1", Blockscad.illegalValue.length + " ");
  1022. }
  1023. $( '#error-message' ).html(errText);
  1024. $( '#error-message' ).addClass("has-error");
  1025. // enable the render button.
  1026. $('#renderButton').prop('disabled', false);
  1027. return;
  1028. }
  1029. // we haven't detected an error in the code. On to rendering!
  1030. // detect default resolution
  1031. Blockscad.resolution = $('input[name="resolution"]:checked').val();
  1032. // load any needed fonts
  1033. Blockscad.loadTheseFonts = Blockscad.whichFonts(code);
  1034. // console.log(loadThese);
  1035. $('#renderButton').html('working');
  1036. if (Blockscad.loadTheseFonts.length > 0) {
  1037. // console.log("I need to load " + Blockscad.loadTheseFonts.length + " fonts.");
  1038. Blockscad.numloaded = 0;
  1039. for (var i = 0; i < Blockscad.loadTheseFonts.length; i++) {
  1040. Blockscad.loadFontThenRender(i,code);
  1041. }
  1042. }
  1043. else {
  1044. Blockscad.renderCode(code);
  1045. }
  1046. };
  1047. Blockscad.renderCode = function(code, resolution) {
  1048. // var csgcode = '';
  1049. // var code_good = true;
  1050. // try {
  1051. // console.log("code was: ",code);
  1052. // window.setTimeout(function (){ csgcode = openscadOpenJscadParser.parse(code);
  1053. // console.log("final parsed code: ",csgcode);
  1054. // }, 0);
  1055. // //code = openscadOpenJscadParser.parse(code);
  1056. // //console.log("code is now:",code);
  1057. // }
  1058. // catch(err) {
  1059. // // console.log("caught parsing error");
  1060. // $( '#error-message' ).html(err);
  1061. // $( '#error-message' ).addClass("has-error");
  1062. // code_good = false;
  1063. // }
  1064. // if (code_good) {
  1065. // // window.setTimeout(function ()
  1066. // // { Blockscad.gProcessor.setBlockscad(csgcode);
  1067. // // console.log("code is now",code);
  1068. // // }, 0);
  1069. // // unbacklight all here
  1070. // Blockscad.workspace.clearBacklight();
  1071. // }
  1072. // else {
  1073. // $('#renderButton').html(Blockscad.Msg.RENDER_BUTTON);
  1074. // }
  1075. Blockscad.gProcessor.setBlockscad(code);
  1076. };
  1077. // }; // end workspaceChanged()
  1078. Blockscad.getExtraRootBlock = function(old,current) {
  1079. //console.log("starting getExtraRootBlock");
  1080. var gotOne = 0;
  1081. var foundIt = [];
  1082. //console.log("old",old);
  1083. //console.log("current",current);
  1084. // go through the longer list, whichever it is.
  1085. // for each element of the longer list, go through
  1086. // all elements of the shorter array. At each position,
  1087. // compare block ids.
  1088. if (old.length > current.length) {
  1089. for (var i=0; i < old.length; i++) {
  1090. gotOne = 0;
  1091. for (var j = 0; j < current.length; j++) {
  1092. // compare block ids. Have we found a match for the first list?
  1093. if (old[i].getAttribute('id') == current[j].id) {
  1094. // found a match. Save the id, and break out.
  1095. gotOne = 1;
  1096. break;
  1097. }
  1098. }
  1099. if (!gotOne)
  1100. return i;
  1101. }
  1102. }
  1103. else {
  1104. for (var i=0; i < current.length; i++) {
  1105. gotOne = 0;
  1106. for (var j = 0; j < old.length; j++) {
  1107. // compare block ids. Have we found a match for the first list?
  1108. if (current[i].id == old[j].getAttribute('id')) {
  1109. // found a match. Save the id, and break out.
  1110. gotOne = 1;
  1111. break;
  1112. }
  1113. }
  1114. if (!gotOne)
  1115. return i;
  1116. }
  1117. }
  1118. // console.log("getExtraRootBlock failed!");
  1119. return 0; // this should never happen
  1120. };
  1121. // this get block from id function searches a given list of blocks,
  1122. // instead of the blocks in the main workspace. Needed for typing.
  1123. Blockscad.getBlockFromId = function(id, blocks) {
  1124. for (var i = 0, block; block = blocks[i]; i++) {
  1125. if (block.id == id) {
  1126. return block;
  1127. }
  1128. }
  1129. return null;
  1130. };
  1131. Blockscad.aCallerBlock = function(block, callers) {
  1132. for (var i = 0; i < callers.length; i++)
  1133. if (block == callers[i]) return true;
  1134. return false;
  1135. }; // end Blockscad.aCallerBlock
  1136. // have a single block, and want to find out what type it's stack makes it?
  1137. // This is for procedure caller block typing.
  1138. Blockscad.findBlockType = function(block, callers) {
  1139. var topBlock = block.getRootBlock();
  1140. var blockStack = topBlock.getDescendants();
  1141. var foundCSG = 0;
  1142. var foundCAG = 0;
  1143. // when I check the types of the surrounding blocks,
  1144. // I DON'T want to count procedure calling blocks
  1145. // that are from the same procedure definition as "block".
  1146. for (var j = 0; j < blockStack.length; j++) {
  1147. if (!Blockscad.aCallerBlock(blockStack[j],callers) && blockStack[j].category) {
  1148. var cat = blockStack[j].category;
  1149. if (cat == 'PRIMITIVE_CSG' || cat == 'EXTRUDE' || cat == 'COLOR') {
  1150. foundCSG = 1;
  1151. break;
  1152. }
  1153. if (cat == 'PRIMITIVE_CAG') foundCAG = 1;
  1154. }
  1155. }
  1156. //console.log("in findBlockAreaType with", blockStack);
  1157. if (foundCSG) {
  1158. if (Blockscad.hasExtrudeParent(block))
  1159. return 'CAG';
  1160. else return 'CSG';
  1161. }
  1162. else if (foundCAG) {
  1163. return('CAG');
  1164. }
  1165. return('EITHER');
  1166. };
  1167. // this takes a block. It checks each parent in its stack to see if it has
  1168. // a parent of the specified block_type (like variable_set)
  1169. // returns the parent block if it exists, otherwise null.
  1170. Blockscad.hasParentOfType = function(block, type) {
  1171. if (!block)
  1172. return null;
  1173. var parent = block.getParent();
  1174. while (parent) {
  1175. // console.log("found a parent of type ", type);
  1176. if (parent.type == type)
  1177. return parent;
  1178. parent = parent.getParent();
  1179. }
  1180. return null;
  1181. }
  1182. // this takes a block and walks up the parent stack.
  1183. // returns true if the first "scoping" parent is a transform or set op.
  1184. // if it hits a module or the top of the stack first, it returns false.
  1185. Blockscad.doVariableHack = function(block) {
  1186. if (!block)
  1187. return null;
  1188. var parent = block.getParent();
  1189. while (parent) {
  1190. // is the parent a transform or set op?
  1191. if (parent.category == 'LOOP' || parent.category == 'TRANSFORM' || parent.category == 'EXTRUDE'
  1192. || parent.category == 'SET_OP' || parent.category == 'COLOR' || parent.type == 'controls_if') {
  1193. return true;
  1194. }
  1195. parent = parent.getParent();
  1196. }
  1197. return false;
  1198. }
  1199. // is this block attached to an actual primitive (2D or 3D)? Needed for missing fields calc.
  1200. // if the block has a disabled parent, it won't be rendered and doesn't count.
  1201. Blockscad.stackIsShape = function(block) {
  1202. var blockStack = block.getDescendants();
  1203. for (var i = 0; i < blockStack.length; i++) {
  1204. var blk = blockStack[i];
  1205. // console.log(blk);
  1206. // console.log(blk.disabled);
  1207. if ((blk.category == 'PRIMITIVE_CSG' || blk.category == 'PRIMITIVE_CAG') && !blk.hasDisabledParent())
  1208. return true;
  1209. }
  1210. return false;
  1211. };
  1212. // Blockscad.assignVarTypes
  1213. // input: single block of type variables_set
  1214. // or a block of variables_get type whose name has just been changed
  1215. // and needs to pick up its new variable's type.
  1216. // on a refresh I will be sent an array of all blocks and need to pick
  1217. // out the variables_set blocks.
  1218. // I need to type the variable instances of the variables_set blocks.
  1219. // name_change indicates if a variable setter has had its name changed
  1220. // in that case, set the current type to a non-sensical value
  1221. // to force the variable set_type to give it a new value.
  1222. Blockscad.assignVarTypes = function(blk, name_change) {
  1223. // console.log("in assignVarTypes with ", blk.type);
  1224. // I need to go through the children of the variables_set block.
  1225. // I am only interested in children that have an output connection.
  1226. //does this block have any children? If not, change type to null.
  1227. // console.log("in assignVarTypes");
  1228. setTimeout(function() {
  1229. if (blk && blk.type == "variables_get") {
  1230. // this variables_get just had its name changed. Find out the type of this
  1231. // variable name and assign it only to this particular instance of the get.
  1232. // console.log(blk.id + " just changed name to " + blk.getFieldValue("VAR"));
  1233. var instances = Blockly.Variables.getInstances(blk.getFieldValue('VAR'), Blockscad.workspace);
  1234. var found_it = 0;
  1235. for (var i = 0; i < instances.length; i++) {
  1236. if (instances[i].type == "variables_set") {
  1237. blk.outputConnection.setCheck(instances[i].myType_);
  1238. found_it = 1;
  1239. break;
  1240. }
  1241. if (instances[i].type == "controls_for" || instances[i].type == "controls_for_chainhull") {
  1242. blk.outputConnection.setCheck(null);
  1243. found_it = 1;
  1244. break;
  1245. }
  1246. }
  1247. if (!found_it) {
  1248. // this came out of a procedure - no set_variable block to go with it.
  1249. // a procedure could have any type associated, so set type to null.
  1250. // console.log("setting a variables_get block to type null");
  1251. blk.outputConnection.setCheck(null);
  1252. }
  1253. // now, if this variables_get was inside a variables_set, that variables_set needs to be retyped.
  1254. var parent = blk.getParent();
  1255. if (parent && parent.type == "variables_set") {
  1256. Blockscad.assignVarTypes(parent);
  1257. }
  1258. }
  1259. else if (blk && blk.type == "variables_set") {
  1260. // console.log("in assignVarTypes with var_set named: ", blk.getFieldValue('VAR'));
  1261. var children = blk.getChildren();
  1262. if (children.length == 0)
  1263. blk.setType(null);
  1264. else {
  1265. var found_one = 0;
  1266. var type = null;
  1267. for (var i = 0; i < children.length; i++) {
  1268. // console.log("child " + i + " has type " + children[i].type);
  1269. if (children[i].outputConnection) {
  1270. var childType = children[i].outputConnection.check_;
  1271. // console.log("child " + i + "has an output connection of type " + childType);
  1272. // console.log(childType + " is this type an array?: " + goog.isArray(childType));
  1273. if (name_change)
  1274. blk.myType_ = "FALSE";
  1275. blk.setType(childType);
  1276. found_one = 1;
  1277. // break;
  1278. }
  1279. }
  1280. if (found_one == 0)
  1281. blk.setType(null);
  1282. }
  1283. }
  1284. },0);
  1285. }
  1286. Blockscad.handleWorkspaceEvents = function(event) {
  1287. if (event.type == Blockly.Events.UI) {
  1288. return; // Don't care about UI events
  1289. }
  1290. if (event.type == Blockly.Events.CREATE ||
  1291. event.type == Blockly.Events.DELETE) {
  1292. // this should trigger needing to save and a type change! I could duplicate a stack
  1293. // that needs typing, or delete a setter that would set its getters to null type.
  1294. // console.log("create or delete event:",event);
  1295. Blockscad.setSaveNeeded(true);
  1296. // set the type of newly created procedure call blocks.
  1297. var block = Blockscad.workspace.getBlockById(event.ids[0]);
  1298. if (block && block.workspace && !block.workspace.isFlyout) {
  1299. if (block.type == 'procedures_callnoreturn' || block.type == 'procedures_callreturn')
  1300. block.setType();
  1301. // Blockscad.typeNewStack(block);
  1302. }
  1303. }
  1304. else if (event.type == Blockly.Events.CHANGE) {
  1305. // trigger a need to save
  1306. // console.log("change event - set save needed to true");
  1307. Blockscad.setSaveNeeded(true);
  1308. // This could be variable name changes (getter or setter), which trigger typing.
  1309. // console.log(event);
  1310. if (event.element == 'field' && event.name == 'VAR') {
  1311. // a variable has changed name.
  1312. var oldName = event.oldValue;
  1313. // console.log("old variable name was:", oldName);
  1314. // var newName = event.newValue;
  1315. var block = Blockscad.workspace.getBlockById(event.blockId);
  1316. if (block && block.type == 'variables_set') {
  1317. // variables_set has changed name. Go through instances of the old name. If there is a
  1318. // variables_set, use that to type the rest. Otherwise, type the getters individually.
  1319. var instances = Blockly.Variables.getInstances(oldName,Blockscad.workspace);
  1320. // console.log("instances:", instances);
  1321. var found_it = 0;
  1322. for (var k = 0; instances && k < instances.length; k++) {
  1323. if (instances[k].type == 'variables_set')
  1324. Blockscad.assignVarTypes(instances[k],true);
  1325. found_it = 1;
  1326. }
  1327. if (!found_it) {
  1328. for (var k = 0; instances && k < instances.length; k++) {
  1329. if (instances[k].type == 'variables_get')
  1330. Blockscad.assignVarTypes(instances[k],true);
  1331. }
  1332. }
  1333. }
  1334. // also type the block associated with the new name. If this is a getter, it gets typed.
  1335. // if it is a setter, it needs to be typed and have all its new getters typed.
  1336. Blockscad.assignVarTypes(block, true);
  1337. }
  1338. if (event.element == 'field' && event.name == 'NUM') {
  1339. var block = Blockscad.workspace.getBlockById(event.blockId);
  1340. var parent = block.getParent();
  1341. if (parent && parent.type == 'cylinder')
  1342. parent.updateRadii();
  1343. }
  1344. }
  1345. else if (event.type == Blockly.Events.MOVE) {
  1346. // this holds plug/unplug events.
  1347. // plug event: has newParentID. trigger type change on new parents block stack.
  1348. // unplug event: has oldParentID. trigger type change on current block and old parent's stack.
  1349. if (event.oldParentId) {
  1350. // unplug event. call typing on old parent stack and current stack.
  1351. // console.log("unplug event");
  1352. var block = Blockscad.workspace.getBlockById(event.blockId);
  1353. var oldParent = Blockscad.workspace.getBlockById(event.oldParentId);
  1354. Blockscad.assignBlockTypes([block]);
  1355. Blockscad.assignBlockTypes([oldParent]);
  1356. if (block && block.type == 'variables_set')
  1357. Blockscad.assignVarTypes(block);
  1358. else if (oldParent && oldParent.type == 'variables_set')
  1359. Blockscad.assignVarTypes(oldParent);
  1360. }
  1361. else if (event.newParentId) {
  1362. // plug event. call typing on the stack.
  1363. // console.log("plug event");
  1364. var block = Blockscad.workspace.getBlockById(event.blockId);
  1365. var newParent = Blockscad.workspace.getBlockById(event.newParentId);
  1366. Blockscad.assignBlockTypes([block]);
  1367. if (newParent && newParent.type == 'variables_set')
  1368. Blockscad.assignVarTypes(newParent);
  1369. }
  1370. if (event.oldParentId || event.newParentId) {
  1371. // either a plug or an unplug
  1372. // console.log("plug or unplug event - set save needed to true");
  1373. Blockscad.setSaveNeeded(true);
  1374. }
  1375. }
  1376. }
  1377. // Blockscad.assignBlockTypes
  1378. // input: array of blocks whose trees need typing
  1379. // schedule typing to be done so that scheduled events have
  1380. // already been fired by the time typing is done.
  1381. Blockscad.assignBlockTypes = function(blocks) {
  1382. // console.log("in assignBlockTypes");
  1383. if (!goog.isArray(blocks))
  1384. blocks = [blocks];
  1385. setTimeout(function() {
  1386. // console.log(blocks);
  1387. for (var i=0; blocks && blocks[i] && i < blocks.length; i++) {
  1388. var topBlock = blocks[i].getRootBlock();
  1389. var blockStack = topBlock.getDescendants();
  1390. var foundCSG = 0;
  1391. var foundCAG = 0;
  1392. for (var j = 0; j < blockStack.length; j++) {
  1393. if (blockStack[j].category) {
  1394. var cat = blockStack[j].category;
  1395. if (cat == 'PRIMITIVE_CSG' || cat == 'EXTRUDE' || cat == 'COLOR') {
  1396. foundCSG = 1;
  1397. break;
  1398. }
  1399. if (cat == 'PRIMITIVE_CAG') foundCAG = 1;
  1400. }
  1401. }
  1402. // For assigning types, use the following algorithm:
  1403. // Go down the list of blocks. if foundCSG:
  1404. // if block has an EXTRUDE parent, set to CAG, otherwise CSG
  1405. // else if found CAG, set to CAG
  1406. // else set to EITHER.
  1407. for(j = 0; j < blockStack.length; j++) {
  1408. if (blockStack[j].category)
  1409. if (blockStack[j].category == 'TRANSFORM' ||
  1410. blockStack[j].category == 'SET_OP' ||
  1411. blockStack[j].category == 'PROCEDURE' ||
  1412. blockStack[j].category == 'LOOP') {
  1413. var drawMe = !blockStack[j].collapsedParents();
  1414. // var drawMe = 0;
  1415. // console.log(blockStack[j].type,"drawMe is", drawMe);
  1416. if (foundCSG) {
  1417. if (Blockscad.hasExtrudeParent(blockStack[j]))
  1418. blockStack[j].setType(['CAG'],drawMe);
  1419. else blockStack[j].setType(['CSG'],drawMe);
  1420. }
  1421. else if (foundCAG) {
  1422. blockStack[j].setType(['CAG'],drawMe);
  1423. }
  1424. else blockStack[j].setType(['CSG','CAG'],drawMe);
  1425. }
  1426. }
  1427. // console.log("in assignBlockTypes(foundCSG,foundCAG)",foundCSG,foundCAG);
  1428. //console.log("blockStack",blockStack);
  1429. }
  1430. }, 0);
  1431. };
  1432. Blockscad.hasExtrudeParent = function(block) {
  1433. do {
  1434. if (block.category == 'EXTRUDE')
  1435. return true;
  1436. block = block.parentBlock_;
  1437. } while (block);
  1438. return false;
  1439. };
  1440. /**
  1441. * Initialize the page language.
  1442. */
  1443. Blockscad.initLanguage = function() {
  1444. // Set the HTML's language and direction.
  1445. // document.dir fails in Mozilla, use document.body.parentNode.dir instead.
  1446. // https://bugzilla.mozilla.org/show_bug.cgi?id=151407
  1447. var rtl = BSUtils.isRtl();
  1448. document.head.parentElement.setAttribute('dir', rtl ? 'rtl' : 'ltr');
  1449. document.head.parentElement.setAttribute('lang', BSUtils.LANG);
  1450. // console.log("lang is:",BSUtils.LANG);
  1451. // Sort languages alphabetically.
  1452. var languages = [];
  1453. var lang;
  1454. for (lang in BSUtils.LANGUAGE_NAME) {
  1455. languages.push([BSUtils.LANGUAGE_NAME[lang], lang]);
  1456. }
  1457. var comp = function(a, b) {
  1458. // Sort based on first argument ('English', 'Русский', '简体字', etc).
  1459. if (a[0] > b[0]) return 1;
  1460. if (a[0] < b[0]) return -1;
  1461. return 0;
  1462. };
  1463. languages.sort(comp);
  1464. // Populate the language selection menu.
  1465. // languageMenu is a <ul>, populate it with <li>s
  1466. var items = [];
  1467. for (var i = 0; i < languages.length; i++) {
  1468. items.push('<li><a href="#" class="lang-option" data-lang="' + languages[i][1] + '"</a>' + languages[i][0] + '</li>');
  1469. }
  1470. $('#languageMenu').append( items.join('') );
  1471. $('.lang-option').on("click", BSUtils.changeLanguage);
  1472. };
  1473. /**
  1474. * Save the workspace to an XML file.
  1475. */
  1476. Blockscad.saveBlocksLocal = function() {
  1477. var xmlDom = Blockly.Xml.workspaceToDom(Blockly.getMainWorkspace());
  1478. var xmlText = Blockly.Xml.domToText(xmlDom);
  1479. // console.log(xmlText);
  1480. var blob = new Blob([xmlText], {type: "text/plain;charset=utf-8"});
  1481. // pull a filename entered by the user
  1482. var blocks_filename = $('#project-name').val();
  1483. // don't save without a filename. Name isn't checked for quality.
  1484. // console.log("in SaveBlocksLocal with: ", blocks_filename);
  1485. if (blocks_filename) {
  1486. saveAs(blob, blocks_filename + ".xml");
  1487. // console.log("SAVED locally: setting needToSave to 0");
  1488. Blockscad.setSaveNeeded();
  1489. }
  1490. else {
  1491. alert(Blockscad.Msg.SAVE_FAILED + '!\n' + Blockscad.Msg.SAVE_FAILED_PROJECT_NAME);
  1492. }
  1493. };
  1494. Blockscad.savePicLocal = function(pic) {
  1495. var blob = new Blob([pic], {type: "img/jpeg"});
  1496. saveAs(blob, "tryThis.jpg");
  1497. }
  1498. /**
  1499. * Save the openScad code for the current workspace to the local machine.
  1500. */
  1501. Blockscad.saveOpenscadLocal = function() {
  1502. var preCode = Blockly.OpenSCAD.workspaceToCode(Blockscad.workspace);
  1503. var code = Blockscad.processCodeForOutput(preCode);
  1504. var blob = new Blob([code], {type: "text/plain;charset=utf-8"});
  1505. // pull a filename entered by the user
  1506. var blocks_filename = $('#project-name').val();
  1507. // don't save without a filename. Name isn't checked for quality.
  1508. if (blocks_filename) {
  1509. saveAs(blob, blocks_filename + ".scad");
  1510. }
  1511. else {
  1512. alert("SAVE FAILED. Please give your project a name, then try again.");
  1513. }
  1514. };
  1515. // execute_ will delay a given action until Blockly is no longer in drag mode.
  1516. // code courtesy of miguel.ceriani@gmail.com (Miguel Ceriani)
  1517. // released under the Apache 2.0 license
  1518. Blockscad.executeAfterDrag_ = function(action, thisArg) {
  1519. Blockscad.renderActions.push( { action: action, thisArg: thisArg } );
  1520. if (Blockscad.renderActions.length === 1) {
  1521. var functId = window.setInterval(function() {
  1522. if (!Blockly.dragMode_) {
  1523. while (Blockscad.renderActions.length > 0) {
  1524. var actionItem = Blockscad.renderActions.shift();
  1525. actionItem.action.call(Blockscad.renderActions.thisArg);
  1526. }
  1527. window.clearInterval(functId);
  1528. }
  1529. }, 10);
  1530. }
  1531. };
  1532. // helper function used in typing to compare type arrays and see if they are equal.
  1533. Blockscad.arraysEqual = function(arr1, arr2) {
  1534. if (arr1 == null && arr2 == null)
  1535. return true;
  1536. if (!arr1 || !arr2)
  1537. return false;
  1538. if(arr1.length !== arr2.length)
  1539. return false;
  1540. for(var i = arr1.length; i--;) {
  1541. if(arr1[i] !== arr2[i])
  1542. return false;
  1543. }
  1544. return true;
  1545. }
  1546. // I want to enable variable declarations at the top of any scope, but I don't have the block
  1547. // set up as a transformation. So I'm hacking it with assigns(); The generators
  1548. // output assign statements, and this removes them for exporting code to OpenScad.
  1549. // this is a hack because the parse is being so intractable.
  1550. Blockscad.processCodeForOutput = function(code) {
  1551. // console.log("code before processing:", code);
  1552. var re0 = /( *)assign\((\$fn=.+)\){(.+)/g;
  1553. var output0 = code.replace(re0, "$1{\n$1 $2; $3");
  1554. var re = /( *)assign\((.+)\){/gm;
  1555. // var output = code.replace(re, "$1{\n$1$2;");
  1556. var output = output0.replace(re, "$1$2;");
  1557. // now I need to make multiple assigns on one line be separated by semicolons.
  1558. var re2 = /(\w+ = \w+),/g;
  1559. var output2 = output.replace(re2, "$1; ");
  1560. // now I need to kill the "end assign" lines so my braces match up.
  1561. var re3 = /.+end assign\n/g;
  1562. var output3 = output2.replace(re3, "");
  1563. // let's kill the duplicate newlines.
  1564. var output4 = output3.replace(/\n\s*\n\s*\n/g, '\n\n');
  1565. // I need to change all "group" commands to "unions"
  1566. var output5 = output4.replace(/group\(\)\{/g, 'union(){');
  1567. // console.log(code);
  1568. // console.log(output5);
  1569. return output5;
  1570. }
  1571. Blockly.OpenSCAD.returnIfVarCode = function(block) {
  1572. // this is an if/else block, I have to separate it into different scopes.
  1573. // if (scope 1) else if (scope 2..n) ... else (scope n+1)
  1574. // can I get a list of all the "blocks" that need code for them?
  1575. if (block.type != 'controls_if') return;
  1576. var bays = [];
  1577. var bayIndex = 0;
  1578. for (var i = 0; i < block.inputList.length; i++) {
  1579. // find the DO# inputs and the ELSE input, and get them in an array.
  1580. // each bay needs a spot (so I don't get the scopes mixed up later)
  1581. // even if there is no connection to that bay.
  1582. if (block.inputList[i].name.match(/DO./) || block.inputList[i].name == "ELSE") {
  1583. // this is a bay that needs a scope.
  1584. var b = block.getInputTargetBlock(block.inputList[i].name);
  1585. bays[bayIndex] = [];
  1586. if (b && b.type == "variables_set")
  1587. bays[bayIndex] = Blockly.OpenSCAD.getVariableCode(b);
  1588. bayIndex++;
  1589. }
  1590. }
  1591. var aC = [];
  1592. var aP = [];
  1593. for (var j = 0; j < bays.length; j++) {
  1594. var assignments = bays[j];
  1595. // console.log("bay" + j + ": " + bays[j]);
  1596. aC[j] = '';
  1597. aP[j] = '';
  1598. if (assignments.length) {
  1599. aC[j] += ' assign(';
  1600. for (var i = 0; i < assignments.length; i++) {
  1601. aC[j] += assignments[i] + ',';
  1602. }
  1603. // trim off the last comma
  1604. aC[j] = aC[j].slice(0, -1);
  1605. aC[j] += '){\n';
  1606. aP[j] = ' }//end assign\n';
  1607. }
  1608. }
  1609. // console.log(aC);
  1610. return [aC, aP];
  1611. }