prettygcode.js 86 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932
  1. $(function () {
  2. console.log("Create PrettyGCode View Model");
  3. function PrettyGCodeViewModel(parameters) {
  4. var self = this;
  5. self.printerProfiles = parameters[2];
  6. self.controlViewModel = parameters[3];
  7. //Parse terminal data for file and pos updates.
  8. var curJobName = "";
  9. var durJobDate = 0;//use date of file to check for update.
  10. function updateJob(job) {
  11. if (durJobDate != job.file.date) {
  12. curJobName = job.file.path;
  13. durJobDate = job.file.date;
  14. if (viewInitialized && gcodeProxy) {
  15. gcodeProxy.loadGcode('downloads/files/local/' + curJobName);
  16. printHeadSim = new PrintHeadSimulator();
  17. //terminalGcodeProxy = new GCodeParser();
  18. //terminalGcodeProxy;//used to display gcode actualy sent to printer.
  19. }
  20. }
  21. }
  22. self.fromHistoryData = function (data) {
  23. if (!viewInitialized)
  24. return;
  25. updateJob(data.job);
  26. };
  27. /* Arc Interpolation Parameters */
  28. self.mm_per_arc_segment = 1.0; // The absolute longest length of an interpolated segment
  29. self.min_arc_segments = 20; // The minimum number of interpolated segments in a full circle, 0 to disable
  30. // The absolute minimum length of an interpolated segment.
  31. // Limited by mm_per_arc_segment as a max and min_arc_segments as a minimum, 0 to disable
  32. self.min_mm_per_arc_segment = 0.1;
  33. // This controls how many arcs will be drawn before the exact position of the
  34. // next segment is recalculated. Reduces the number of sin/cos calls.
  35. // 0 to disable
  36. self.n_arc_correction = 24;
  37. // A function to interpolate arcs into straight segments. Returns an array of positions
  38. self.interpolateArc = function (state, arc) {
  39. // This is adapted from the Marlin arc interpolation routine found at
  40. // https://github.com/MarlinFirmware/Marlin/
  41. // The license can be found here: https://github.com/MarlinFirmware/Marlin/blob/2.0.x/LICENSE
  42. // This allows the rendered arcs to be VERY close to what would be printed,
  43. // depending on the firmware settings.
  44. // Create vars to hold the initial and current position so we don't affect the state
  45. var initial_position = {}, current_position = {};
  46. Object.assign(initial_position, state)
  47. Object.assign(current_position, state)
  48. // Create the results which contain the copied initial position
  49. var interpolated_segments = [initial_position];
  50. // note that arc.is_clockwise determines if this is a G2, else it is a G3
  51. // I'm going to also extract all the necessary variables up front to make this easier
  52. // to convert from the source c++ arc interpolation code
  53. // Convert r format to i j format if necessary
  54. // I have no code like this to test, so I am not 100% sure this will work as expected
  55. // commenting out for now
  56. /*
  57. if (arc.r)
  58. {
  59. if (arc.x != current_position.x || arc.y != current_position.y) {
  60. var vector = {x: (arc.x - current_position.x)/2.0, y: (arc.y - current_position.y)/2.0};
  61. var e = arc.is_clockwise ^ (arc.r < 0) ? -1 : 1;
  62. var len = Math.sqrt(Math.pow(vector.x,2) + Math.pow(vector.y,2));
  63. var h2 = (arc.r - len) * (arc.r + len);
  64. var h = (h2 >= 0) ? Math.sqrt(h2) : 0.0;
  65. var bisector = {x: -1.0*vector.y, y: vector.x };
  66. arc.i = (vector.x + bisector.x) / len * e * h;
  67. arc.j = (vector.y + bisector.y) / len * e * h;
  68. }
  69. }*/
  70. // Calculate the radius, we will be using it a lot.
  71. var radius = Math.hypot(arc.i, arc.j);
  72. // Radius Vector
  73. var v_radius = { x: -1.0 * arc.i, y: -1.0 * arc.j };
  74. // Center of arc
  75. var center = { x: current_position.x - v_radius.x, y: current_position.y - v_radius.y };
  76. // Z Travel Total
  77. var travel_z = arc.z - current_position.z;
  78. // Extruder Travel
  79. var travel_e = arc.e - current_position.e;
  80. // Radius Target Vector
  81. var v_radius_target = { x: arc.x - center.x, y: arc.y - center.y };
  82. var angular_travel_total = Math.atan2(
  83. v_radius.x * v_radius_target.y - v_radius.y * v_radius_target.x,
  84. v_radius.x * v_radius_target.x + v_radius.y * v_radius_target.y
  85. );
  86. // Having a positive angle is convenient here. We will make it negative later
  87. // if we need to.
  88. if (angular_travel_total < 0) { angular_travel_total += 2.0 * Math.PI }
  89. // Copy our mm_per_arc_segments var because we may be modifying it for this arc
  90. var mm_per_arc_segment = self.mm_per_arc_segment;
  91. // Enforce min_arc_segments if it is greater than 0
  92. if (self.min_arc_segments > 0) {
  93. mm_per_arc_segment = (radius * ((2.0 * Math.PI) / self.min_arc_segments));
  94. // We will need to enforce our max segment length later, flag this
  95. }
  96. // Enforce the minimum segment length if it is set
  97. if (self.min_mm_per_arc_segment > 0) {
  98. if (mm_per_arc_segment < self.min_mm_per_arc_segment) {
  99. mm_per_arc_segment = self.min_mm_per_arc_segment;
  100. }
  101. }
  102. // Enforce the maximum segment length
  103. if (mm_per_arc_segment > self.mm_per_arc_segment) {
  104. mm_per_arc_segment = self.mm_per_arc_segment;
  105. }
  106. // Adjust the angular travel if the direction is clockwise
  107. if (arc.is_clockwise) { angular_travel_total -= (2.0 * Math.PI); }
  108. // Compensate for a full circle, which would give us an angle of 0 here
  109. // We want that to be 2Pi. Note, full circles are bad in 3d printing, but they
  110. // should still render correctly
  111. if (current_position.x == arc.x && current_position.y == arc.y && angular_travel_total == 0) {
  112. angular_travel_total += 2.0 * Math.PI;
  113. }
  114. // Now it's time to calculate the mm of total travel along the arc, making sure we take Z into account
  115. var mm_of_travel_arc = Math.hypot(angular_travel_total * radius, Math.abs(travel_z));
  116. // Get the number of segments total we will be generating
  117. var num_segments = Math.ceil(mm_of_travel_arc / mm_per_arc_segment);
  118. // Calculate xy_segment_theta, z_segment_theta, and e_segment_theta
  119. // This is the distance we will be moving for each interpolated segment
  120. var xy_segment_theta = angular_travel_total / num_segments;
  121. var z_segment_theta = travel_z / num_segments;
  122. var e_segment_theta = travel_e / num_segments;
  123. // Time to interpolate!
  124. if (num_segments > 1) {
  125. // it's possible for num_segments to be zero. If that's true, we just need to draw a line
  126. // from the start to the end coordinates, and this isn't needed.
  127. // I am NOT going to use the small angel approximation for sin and cos here, but it
  128. // could be easily added if performance is a problem. Here is code for this if it becomes
  129. // necessary:
  130. //var sq_theta_per_segment = theta_per_segment * theta_per_segment;
  131. //var sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6;
  132. //var cos_T = 1 - 0.5f * sq_theta_per_segment; // Small angle approximation
  133. var cos_t = Math.cos(xy_segment_theta);
  134. var sin_t = Math.sin(xy_segment_theta);
  135. var r_axisi;
  136. // We are going to correct sin and cos only occasionally to reduce cpu usage
  137. var count = 0;
  138. // Loop through each interpolated segment, minus the endpoint which will be handled separately
  139. for (var i = 1; i < num_segments; i++) {
  140. if (count < self.n_arc_correction) {
  141. // not time to recalculate X and Y.
  142. // Apply the rotational vector
  143. r_axisi = v_radius.x * sin_t + v_radius.y * cos_t;
  144. v_radius.x = v_radius.x * cos_t - v_radius.y * sin_t;
  145. v_radius.y = r_axisi;
  146. count++;
  147. }
  148. else {
  149. // Arc correction to radius vector. Computed only every N_ARC_CORRECTION increments.
  150. // Compute exact location by applying transformation matrix from initial radius vector(=-offset).
  151. var sin_ti = Math.sin(i * xy_segment_theta);
  152. var cos_ti = Math.cos(i * xy_segment_theta);
  153. v_radius.x = (-1.0 * arc.i) * cos_ti + arc.j * sin_ti;
  154. v_radius.y = (-1.0 * arc.i) * sin_ti - arc.j * cos_ti;
  155. count = 0;
  156. }
  157. // Draw the segment
  158. var line = {
  159. x: center.x + v_radius.x,
  160. y: center.y + v_radius.y,
  161. z: current_position.z + z_segment_theta,
  162. e: current_position.e + e_segment_theta,
  163. f: arc.f
  164. };
  165. /*console.debug(
  166. "Arc Segment " + i.toString() + ":" +
  167. " X" + line.x.toString() +
  168. " Y" + line.y.toString() +
  169. " Z" + line.z.toString() +
  170. " E" + line.e.toString() +
  171. " F" + line.f.toString()
  172. );*/
  173. interpolated_segments.push(line);
  174. // Update the current state
  175. current_position.x = line.x;
  176. current_position.y = line.y;
  177. current_position.z = line.z;
  178. current_position.e = line.e;
  179. }
  180. }
  181. // Move to the target position
  182. var line = {
  183. x: arc.x,
  184. y: arc.y,
  185. z: arc.z,
  186. e: arc.e,
  187. f: arc.f
  188. };
  189. interpolated_segments.push(line);
  190. //Done!!!
  191. return interpolated_segments;
  192. };
  193. //used to animate the nozzle position in response to terminal messages
  194. function PrintHeadSimulator() {
  195. var buffer = [];
  196. var HeadState = function () {
  197. this.position = new THREE.Vector3(0, 0, 0);
  198. this.rate = 5.0 * 60;
  199. this.extrude = false;
  200. this.relative = false;
  201. //this.lastExtrudedZ=0;//used to better calc layer number
  202. this.layerLineNumber = 0;
  203. this.clone = function () {
  204. var newState = new HeadState();
  205. newState.position.copy(this.position);
  206. newState.rate = this.rate;
  207. newState.extrude = this.extrude;
  208. newState.relative = this.relative;
  209. //newState.lastExtrudedZ=this.lastExtrudedZ;
  210. newState.layerLineNumber = this.layerLineNumber;
  211. return (newState);
  212. }
  213. };
  214. var curState = new HeadState();
  215. var curEnd = new HeadState();
  216. var parserCurState = new HeadState();
  217. var observedLayerCount = 0;
  218. var parserLayerLineNumber = 0;
  219. var parserLastExtrudedZ = 0;
  220. var curLastExtrudedZ = 0;
  221. parserCurState.extrude = true;
  222. this.getCurPosition = function () {
  223. return ({ position: curState.position, layerZ: curLastExtrudedZ, lineNumber: curState.layerLineNumber });
  224. }
  225. this.getBufferStats = function () {
  226. return (buffer.length);
  227. }
  228. //
  229. //var currentFileOffset=0;
  230. //add gcode command to the buffer
  231. this.addCommand = function (cmd) {
  232. //currentFileOffset+=cmd.length;
  233. if (buffer.length > 1000) {
  234. console.log("PrintHeadSimulator buffer overflow")
  235. return;
  236. }
  237. var is_g0_g1 = cmd.indexOf(" G0") > -1 || cmd.indexOf(" G1") > -1;
  238. var is_g2_g3 = !is_g0_g1 && cmd.indexOf(" G2") > -1 || cmd.indexOf(" G3") > -1;
  239. if (is_g0_g1 || is_g2_g3) {
  240. var parserPreviousState = {};
  241. // If this is a g2/g3, we need to know the previous state to interpolate the arcs
  242. if (is_g2_g3) { parserPreviousState = Object.assign(parserPreviousState, parserCurState); }
  243. // Extract x, y, z, f and e
  244. var x = parseFloat(cmd.split("X")[1])
  245. if (!Number.isNaN(x)) {
  246. if (parserCurState.relative)
  247. parserCurState.position.x += x;
  248. else
  249. parserCurState.position.x = x;
  250. }
  251. var y = parseFloat(cmd.split("Y")[1])
  252. if (!Number.isNaN(y)) {
  253. if (parserCurState.relative)
  254. parserCurState.position.y += y;
  255. else
  256. parserCurState.position.y = y;
  257. }
  258. var z = parseFloat(cmd.split("Z")[1])
  259. if (!Number.isNaN(z)) {
  260. if (parserCurState.relative)
  261. parserCurState.position.z += z;
  262. else
  263. parserCurState.position.z = z;
  264. }
  265. var f = parseFloat(cmd.split("F")[1])
  266. if (!Number.isNaN(f)) {
  267. parserCurState.rate = f;
  268. }
  269. var e = parseFloat(cmd.split("E")[1])
  270. if (!Number.isNaN(e)) {
  271. parserCurState.extrude = true;
  272. if (parserLastExtrudedZ != parserCurState.position.z) {
  273. //new layer (probably)
  274. //observedLayerCount++
  275. //console.log("New layer Z."+parserCurState.position.z+" File offset:"+currentFileOffset)
  276. parserLayerLineNumber = 0;
  277. parserLastExtrudedZ = parserCurState.position.z;
  278. }
  279. else
  280. parserLayerLineNumber++;
  281. } else {
  282. parserCurState.extrude = false;
  283. }
  284. parserCurState.layerLineNumber = parserLayerLineNumber;
  285. // if this is a g0/g1, push the state to the buffer
  286. if (is_g0_g1) { buffer.push(parserCurState.clone()); }
  287. else {
  288. // This is a g2/g3, so we need to do things a bit differently.
  289. // Extract I and J, R, and is_clockwise
  290. var is_clockwise = cmd.indexOf(" G2") > -1;
  291. var i = parseFloat(cmd.split("I")[1]);
  292. var j = parseFloat(cmd.split("J")[1]);
  293. var r = parseFloat(cmd.split("R")[1]);
  294. var arc = {
  295. // Get X Y and Z from the previous state if it is not
  296. // provided
  297. x: this.getCurrentCoordinate(x, parserPreviousState.position.x),
  298. y: this.getCurrentCoordinate(y, parserPreviousState.position.y),
  299. z: this.getCurrentCoordinate(z, parserPreviousState.position.z),
  300. // Set I and J and R to 0 if they are not provided.
  301. i: this.getCurrentCoordinate(i, 0),
  302. j: this.getCurrentCoordinate(j, 0),
  303. r: this.getCurrentCoordinate(r, 0),
  304. // K omitted, not sure what that's supposed to do
  305. //k: k !== undefined ? k : 0,
  306. // Since the amount extruded doesn't really matter, set it to 1 if we are extruding,
  307. // We don't want undefined values going into the arc interpolation routine
  308. e: this.getCurrentCoordinate(e, parserPreviousState.extrude ? 1 : 0),
  309. f: this.getCurrentCoordinate(r, parserPreviousState.rate),
  310. is_clockwise: is_clockwise
  311. };
  312. // Need to handle R maybe
  313. var segments = self.interpolateArc(parserPreviousState, arc);
  314. for (var index = 1; index < segments.length; index++) {
  315. var cur_segment = segments[index];
  316. var cur_state = parserCurState.clone();
  317. cur_state.position = new THREE.Vector3(cur_segment.x, cur_segment.y, cur_segment.z);
  318. buffer.push(cur_state);
  319. }
  320. }
  321. } else if (cmd.indexOf(" G90") > -1) {
  322. //G90: Set to Absolute Positioning
  323. parserCurState.relative = false;
  324. } else if (cmd.indexOf(" G91") > -1) {
  325. //G91: Set to state.relative Positioning
  326. parserCurState.relative = true;
  327. }
  328. }
  329. //window.myMaxRate=120.0;
  330. //window.fudge=7;
  331. // Handle undefined and NaN for current coordinates.
  332. this.getCurrentCoordinate = function (cmdCoord, prevCoord) {
  333. if (cmdCoord === undefined || isNaN(cmdCoord)) { cmdCoord = prevCoord; }
  334. return cmdCoord;
  335. }
  336. //Update the printhead position based on time elapsed.
  337. this.updatePosition = function (timeStep) {
  338. //Convert the gcode feed rate (in MM/per min?) to rate per second.
  339. var rate = curState.rate / 60.0;
  340. //rate=rate/2;//todo. why still too fast?
  341. //adapt rate to keep up with buffer.
  342. //todo. Make dist based rather than just buffer size.
  343. if (buffer.length > 10) {
  344. rate = rate * (buffer.length / 5.0);
  345. //console.log(["Too Slow ",rate,buffer.length])
  346. }
  347. if (buffer.length < 5) {
  348. rate = rate * (1.0 / (buffer.length * 5.0));
  349. //console.log(["Too fast ",rate,buffer.length])
  350. }
  351. //rate=Math.min(rate,window.myMaxRate);
  352. //dist head needs to travel this frame
  353. var dist = rate * timeStep
  354. while (buffer.length > 0 && dist > 0)//while some place to go and some dist left.
  355. {
  356. //direction
  357. var vectToCurEnd = curEnd.position.clone().sub(curState.position);
  358. var distToEnd = vectToCurEnd.length();
  359. if (dist < distToEnd)//Inside current line?
  360. {
  361. //move pos the distance along line
  362. vectToCurEnd.setLength(dist);
  363. curState.position.add(vectToCurEnd);
  364. dist = 0;//all done
  365. } else {
  366. //move pos to end point.
  367. curState.position.copy(curEnd.position);
  368. curState.rate = curEnd.rate;
  369. //subract dist for next loop.
  370. dist = dist - distToEnd;
  371. //draw segment
  372. //todo.
  373. //update lastZ for display of layers.
  374. if (curEnd.extrude && curEnd.position.z != curLastExtrudedZ) {
  375. curLastExtrudedZ = curEnd.position.z;
  376. }
  377. //console.log([curState.position.z,curState.layerLineNumber])
  378. //start on next buffer command
  379. buffer.shift();
  380. if (buffer.length > 0) {
  381. curEnd = buffer[0];
  382. curState.layerLineNumber = curEnd.layerLineNumber;
  383. }
  384. }
  385. }
  386. }
  387. }
  388. var printHeadSim = new PrintHeadSimulator();
  389. var curPrinterState = null;
  390. var curPrintFilePos = 0;
  391. self.fromCurrentData = function (data) {
  392. //Dont do anything if view not initalized
  393. if (!viewInitialized)
  394. return;
  395. //update current loaded model.
  396. updateJob(data.job);
  397. if (curPrinterState && curPrinterState.text != data.state.text) {
  398. //console.log(["Printer state changed: ",curPrinterState.text," -> ",data.state.text])
  399. if (data.state.text.startsWith("Operational")) {
  400. //console.log("Resetting print simulation");
  401. printHeadSim = new PrintHeadSimulator();
  402. }
  403. }
  404. curPrinterState = data.state;
  405. curPrintFilePos = data.progress.filepos;
  406. //parse logs position data for simulator
  407. if (data.logs.length) {
  408. data.logs.forEach(function (e, i) {
  409. if (e.startsWith("Send:")) {
  410. //console.log(["GCmd:",e]);
  411. if (printHeadSim)
  412. printHeadSim.addCommand(e);
  413. //Strip out the extra stuff in the terminal line.
  414. //match second space to * character. I hate regexp.
  415. if (terminalGcodeProxy) {
  416. var reg = new RegExp('(?<=\\s\\S*\\s).[^*]*', 'g');
  417. var matches = e.match(reg);
  418. if (matches && matches.length > 0)
  419. terminalGcodeProxy.parse(matches[0] + '\n');
  420. }
  421. }
  422. else if (e.startsWith("Recv: T:")) {
  423. //console.log(["GCmd:",e]);
  424. let parts = e.substr(6).split("@");//remove Recv: and checksum.
  425. let temps = parts[0];
  426. let statusStr = temps;//+" Buffer:"+printHeadSim.getBufferStats()
  427. $(".pgstatus").text(statusStr);
  428. }
  429. })
  430. }
  431. };
  432. self.updateCss = function (newCss) {
  433. //alert(this)
  434. var newCss = $("#pg_add_css").val();
  435. console.log(["Update css:", newCss]);
  436. localStorage.setItem('pg_add_css_val', newCss)
  437. $("#pgcss").html(newCss);
  438. }
  439. self.onAfterBinding = function () {
  440. console.log("onAfterBinding")
  441. //var addCss=$("#add_css").val();
  442. $("<style id='pgcss'>")
  443. .prop("type", "text/css")
  444. .html("")
  445. .appendTo("head");
  446. var css = localStorage.getItem('pg_add_css_val')
  447. if (css) {
  448. $("#pgcss").html(css);
  449. $("#pg_add_css").val(css);
  450. }
  451. };
  452. self.onEventFileSelected = function (payload) {
  453. //console.log(["onEventFileSelected ",payload])
  454. }
  455. //Scene globals
  456. var camera, cameraControls, cameraLight;
  457. var scene, renderer;
  458. var lightBackground, darkBackground;
  459. var gcodeProxy;//used to display loaded gcode.
  460. var terminalGcodeProxy;//todo remove(prob not used anymore). used to display gcode actualy sent to printer.
  461. var cubeCamera;//todo make reflections optional.
  462. var nozzleModel;
  463. var clock;
  464. var dimensionsGroup;
  465. var sceneBounds = new THREE.Box3();
  466. //todo. Are these needed?
  467. var gcodeWid = 580;
  468. var gcodeHei = 580;
  469. var gui;
  470. var forceNoSync = false;//used to override sync when user drags slider. Todo. Better way to handle this?
  471. var currentLayerNumber = 0;
  472. //settings that are saved between sessions
  473. var PGSettings = function () {
  474. this.showMirror = false;//default changed
  475. this.fatLines = true;//default changed
  476. //this.reflections=false;//remove this
  477. this.darkMode = false;
  478. this.syncToProgress = true;
  479. this.orbitWhenIdle = false;
  480. this.reloadGcode = function () {
  481. if (gcodeProxy && curJobName != "")
  482. gcodeProxy.loadGcode('downloads/files/local/' + curJobName);
  483. };
  484. this.showState = true;
  485. this.showWebcam = false;
  486. this.showFiles = false;
  487. this.showDash = false;
  488. this.antialias = true;
  489. this.showNozzle = true;
  490. this.highlightCurrentLayer = true;
  491. };
  492. var pgSettings = new PGSettings();
  493. function updateWindowStates() {
  494. if (pgSettings.showState) {
  495. $("#state_wrapper").removeClass("pghidden");
  496. }
  497. else {
  498. $("#state_wrapper").addClass("pghidden");
  499. }
  500. if (pgSettings.showFiles) {
  501. $("#files_wrapper").removeClass("pghidden");
  502. }
  503. else {
  504. $("#files_wrapper").addClass("pghidden");
  505. }
  506. if (pgSettings.showWebcam) {
  507. $(".gwin #webcam_rotator").removeClass("pghidden");
  508. }
  509. else {
  510. $(".gwin #webcam_rotator").addClass("pghidden");
  511. }
  512. if (pgSettings.showDash) {
  513. $("#tab_plugin_dashboard").removeClass("pghidden");
  514. }
  515. else {
  516. $("#tab_plugin_dashboard").addClass("pghidden");
  517. }
  518. }
  519. var bedVolume = {
  520. depth: 0,
  521. formFactor: "",
  522. height: 0,
  523. origin: "",
  524. width: 0,
  525. };
  526. var viewInitialized = false;
  527. self.onTabChange = function (current, previous) {
  528. if (current == "#tab_plugin_prettygcode") {
  529. if (!viewInitialized) {
  530. viewInitialized = true;
  531. //Watch for bed volume changes
  532. self.printerProfiles.currentProfileData.subscribe(
  533. function () {
  534. //get new build volume.
  535. updateBedVolume();
  536. //update scene if any
  537. updateGridMesh();
  538. //Needed in case center has changed.
  539. resetCamera();
  540. });
  541. //get current (possibly default) printer build volume.
  542. updateBedVolume();
  543. //console.log(["bedVolume",bedVolume]);
  544. if (true) {
  545. //simple gui
  546. dat.GUI.TEXT_OPEN = "View Options"
  547. dat.GUI.TEXT_CLOSED = "View Options"
  548. gui = new dat.GUI({ autoPlace: false, name: "View Options", closed: false, closeOnTop: true, useLocalStorage: true });
  549. //Override default storage location to fix bug with tabs.
  550. //Not working
  551. //gui.setLocalStorageHash("PrettyGCodeSettings");
  552. gui.useLocalStorage = true;
  553. // var guielem = $("<div id='mygui' style='position:absolute;right:95px;top:20px;opacity:0.8;z-index:5;'></div>");
  554. // $('.gwin').prepend(guielem)
  555. $('#mygui').append(gui.domElement);
  556. gui.remember(pgSettings);
  557. gui.add(pgSettings, 'syncToProgress').onFinishChange(function () {
  558. if (pgSettings.syncToProgress) {
  559. // syncLayerToZ();
  560. }
  561. });
  562. gui.add(pgSettings, 'darkMode').onFinishChange(function (checked) {
  563. var color = checked ? darkBackground : lightBackground;
  564. scene.background = new THREE.Color(color);
  565. // Apply dark mode CSS class
  566. if (checked) {
  567. $(".page-container").addClass("pgdarkmode");
  568. } else {
  569. $(".page-container").removeClass("pgdarkmode");
  570. }
  571. renderer.render(scene, camera);
  572. });
  573. gui.add(pgSettings, 'showMirror').onFinishChange(pgSettings.reloadGcode);
  574. gui.add(pgSettings, 'orbitWhenIdle');
  575. gui.add(pgSettings, 'fatLines').onFinishChange(pgSettings.reloadGcode);
  576. //gui.add(pgSettings, 'reflections');
  577. gui.add(pgSettings, 'antialias').onFinishChange(function () {
  578. new PNotify({
  579. title: "Reload page required",
  580. text: "Antialias chenges won't take effect until you refresh the page",
  581. type: "info"
  582. });
  583. //alert("Antialias chenges won't take effect until you refresh the page");
  584. });
  585. gui.add(pgSettings, 'showNozzle');
  586. //gui.add(pgSettings, 'reloadGcode');
  587. var folder = gui.addFolder('Windows');//hidden.
  588. folder.add(pgSettings, 'showState').onFinishChange(updateWindowStates).listen();
  589. folder.add(pgSettings, 'showWebcam').onFinishChange(updateWindowStates).listen();
  590. folder.add(pgSettings, 'showFiles').onFinishChange(updateWindowStates).listen();
  591. folder.add(pgSettings, 'showDash').onFinishChange(updateWindowStates).listen();
  592. //dont show Windows. Automatically handled by toggle buttons
  593. $(folder.domElement).attr("hidden", true);
  594. // Apply dark mode on initial load if enabled
  595. if (pgSettings.darkMode) {
  596. $(".page-container").addClass("pgdarkmode");
  597. }
  598. }
  599. initThree();
  600. //load Nozzle model.
  601. var objloader = new THREE.OBJLoader();
  602. objloader.load('plugin/prettygcode/static/js/models/ExtruderNozzle.obj', function (obj) {
  603. obj.quaternion.setFromEuler(new THREE.Euler(Math.PI / 2, 0, 0));
  604. obj.scale.setScalar(0.1)
  605. obj.position.set(0, 0, 10);
  606. obj.name = "nozzle";
  607. var nozzleMaterial = new THREE.MeshStandardMaterial({
  608. metalness: 1, // between 0 and 1
  609. roughness: 0.5, // between 0 and 1
  610. envMap: cubeCamera.renderTarget.texture,
  611. color: new THREE.Color(0xba971b),
  612. //flatShading:false,
  613. });
  614. obj.children.forEach(function (e, i) {
  615. if (e instanceof THREE.Mesh) {
  616. e.material = nozzleMaterial;
  617. //e.geometry.computeVertexNormals();
  618. }
  619. })
  620. nozzleModel = obj;
  621. scene.add(obj);
  622. });
  623. //GCode loader.
  624. gcodeProxy = new GCodeParser();
  625. var gcodeObject = gcodeProxy.getObject();
  626. gcodeObject.position.set(-0, -0, 0);
  627. scene.add(gcodeObject);
  628. if (curJobName != "")
  629. gcodeProxy.loadGcode('downloads/files/local/' + curJobName);
  630. if (false) {
  631. //terminal parser
  632. terminalGcodeProxy = new GCodeParser();
  633. terminalGcodeProxy.addSegment = function (p1, p2) {
  634. //console.log(["addSegment",p1,p2])
  635. if (currentLayer === undefined) {
  636. newLayer(p1);
  637. }
  638. }
  639. var terminalGcodeObject = terminalGcodeProxy.getObject();
  640. terminalGcodeObject.position.set(100, -0, 0);
  641. scene.add(terminalGcodeObject);
  642. }
  643. //note this is an octoprint version of a bootstrap slider. not a jquery ui slider.
  644. $('.gwin').append($('<div id="myslider-vertical" style=""></div>'));
  645. $("#myslider-vertical").slider({
  646. id: "myslider",
  647. orientation: "vertical",
  648. reversed: true,
  649. range: "min",
  650. min: 0,
  651. max: 100,
  652. value: 100,
  653. }).on("slide", function (event, ui) {
  654. currentLayerNumber = event.value;
  655. $("#myslider .slider-handle").text(currentLayerNumber);
  656. }).on("slideStart", function (event, ui) {
  657. //console.log("slideStart");
  658. forceNoSync = true;
  659. }).on("slideStop", function (event, ui) {
  660. //console.log("slideStop");
  661. forceNoSync = false;
  662. });
  663. $("#myslider").attr("style", "height:90%;position:absolute;top:5%;right:20px")
  664. //Create a web camera inset for the view.
  665. var camView = $("#webcam_rotator").clone();
  666. let img = camView.find("#webcam_image")
  667. img.attr("id", "pg_webcam_image")
  668. $(".gwin").append(camView)
  669. //check url for fullscreen mode
  670. if (urlParam("fullscreen"))
  671. $(".page-container").addClass("pgfullscreen");
  672. //setup window toggle buttons
  673. $(".fstoggle").on("click", function () {
  674. $(".page-container").toggleClass("pgfullscreen");
  675. });
  676. $(".pgsettingstoggle").on("click", function () {
  677. $("#mygui").toggleClass("pghidden");
  678. });
  679. $(".pgstatetoggle").on("click", function () {
  680. pgSettings.showState = !pgSettings.showState;
  681. updateWindowStates();
  682. });
  683. $(".pgfilestoggle").on("click", function () {
  684. pgSettings.showFiles = !pgSettings.showFiles;
  685. updateWindowStates();
  686. });
  687. $(".pgcameratoggle").on("click", function () {
  688. pgSettings.showWebcam = !pgSettings.showWebcam;
  689. updateWindowStates();
  690. });
  691. $(".pgdashtoggle").on("click", function () {
  692. pgSettings.showDash = !pgSettings.showDash;;
  693. updateWindowStates();
  694. });
  695. updateWindowStates();
  696. }
  697. //Activate webcam view in window.
  698. // Use modern OctoPrint webcam API if available (OctoPrint 1.9+)
  699. var webcamUrl = null;
  700. if (self.settings && self.settings.webcam && self.settings.webcam.streamUrl) {
  701. webcamUrl = self.settings.webcam.streamUrl();
  702. }
  703. // Fallback to legacy API if modern API not available
  704. if (!webcamUrl) {
  705. webcamUrl = "/webcam/?action=stream";
  706. }
  707. $(".gwin #pg_webcam_image").attr("src", webcamUrl + "&" + Math.random());
  708. // Ensure webcam is enabled even if controlViewModel isn't ready yet
  709. if (self.controlViewModel && typeof self.controlViewModel._enableWebcam === 'function') {
  710. self.controlViewModel._enableWebcam();
  711. }
  712. } else if (previous == "#tab_plugin_prettygcode") {
  713. //todo. disable animation
  714. //Disable camera when tab isnt visible.
  715. $(".gwin #pg_webcam_image").attr("src", "")
  716. if (self.controlViewModel && typeof self.controlViewModel._disableWebcam === 'function') {
  717. self.controlViewModel._disableWebcam();
  718. }
  719. }
  720. // Re-enable webcam for control tab if it exists
  721. if (self.controlViewModel && typeof self.controlViewModel._enableWebcam === 'function') {
  722. self.controlViewModel._enableWebcam();
  723. }
  724. };
  725. //util function
  726. String.prototype.hashCode = function () {
  727. var hash = 0, i, chr;
  728. if (this.length === 0) return hash;
  729. for (i = 0; i < this.length; i++) {
  730. chr = this.charCodeAt(i);
  731. hash = ((hash << 5) - hash) + chr;
  732. hash |= 0; // Convert to 32bit integer
  733. }
  734. return hash;
  735. };
  736. //util function
  737. urlParam = function (name) {
  738. var results = new RegExp('[?&]' + name + '=([^&#]*)').exec(window.location.href);
  739. if (results == null) {
  740. return null;
  741. }
  742. return decodeURI(results[1]) || 0;
  743. }
  744. //Handle "focus" url param. Not used anymore.
  745. var focus = urlParam("focus");
  746. if (focus != null) {
  747. console.log("Focusing on:" + focus);
  748. $("body").children().hide();
  749. $("#webcam_container").hide();
  750. if (!focus.startsWith("."))
  751. focus = "#" + focus;
  752. var el = $(focus)[0];
  753. $("body").prepend(el);
  754. }
  755. function updateBedVolume() {
  756. var currentProfileData = self.printerProfiles.currentProfileData();
  757. if (!currentProfileData || !currentProfileData.volume) {
  758. return;
  759. }
  760. var volume = currentProfileData.volume;
  761. //console.log([arguments.callee.name,volume]);
  762. if (typeof volume.custom_box === "function") //check for custom bounds.
  763. {
  764. bedVolume = {
  765. width: volume.width(),
  766. height: volume.height(),
  767. depth: volume.depth(),
  768. origin: volume.origin(),
  769. formFactor: volume.formFactor(),
  770. };
  771. }
  772. else {
  773. //console.log(["volume.custom_box",volume.custom_box]);
  774. bedVolume = {
  775. width: volume.custom_box.x_max() - volume.custom_box.x_min(),
  776. height: volume.custom_box.z_max() - volume.custom_box.z_min(),
  777. depth: volume.custom_box.y_max() - volume.custom_box.y_min(),
  778. origin: volume.origin(),
  779. formFactor: volume.formFactor(),
  780. };
  781. }
  782. }
  783. function GCodeParser(data) {
  784. var state = { x: 0, y: 0, z: 0, e: 0, f: 0, extruding: false, relative: false };
  785. var layers = [];
  786. var currentLayer = undefined;
  787. var defaultColor = new THREE.Color('white');
  788. var curColor = defaultColor;
  789. var filePos = 0;//used for syncing when printing.
  790. var previousPiece = "";//used for parsing gcode in chunks.
  791. //material for fatlines
  792. var curMaterial = new THREE.LineMaterial({
  793. linewidth: 3, // in pixels
  794. //transparent: true,
  795. //opacity: 0.5,
  796. //color: new THREE.Color(curColorHex),// rainbow.getColor(layers.length % 64).getHex()
  797. vertexColors: THREE.VertexColors,
  798. });
  799. //todo. handle window resize
  800. // curMaterial.resolution.set(gcodeWid, gcodeHei);
  801. curMaterial.resolution.set(500, 500);
  802. //for plain lines
  803. var curLineBasicMaterial = new THREE.LineBasicMaterial({
  804. color: 0xffffff,
  805. vertexColors: THREE.VertexColors
  806. });
  807. var gcodeGroup = new THREE.Group();
  808. gcodeGroup.name = 'gcode';
  809. //reset parser for another object.
  810. this.reset = function () {
  811. this.clearObject();
  812. state = { x: 0, y: 0, z: 0, e: 0, f: 0, extruding: false, relative: false };
  813. layers = [];
  814. currentLayer = undefined;
  815. curColor = defaultColor;
  816. filePos = 0;
  817. previousPiece = "";
  818. }
  819. this.getObject = function () {
  820. return gcodeGroup;
  821. }
  822. this.clearObject = function () {
  823. if (gcodeGroup) {
  824. for (var i = gcodeGroup.children.length - 1; i >= 0; i--) {
  825. gcodeGroup.remove(gcodeGroup.children[i]);
  826. }
  827. }
  828. }
  829. easeOutBounce = function (t, b, c, d) {
  830. if ((t /= d) < (1 / 2.75)) {
  831. return c * (7.5625 * t * t) + b;
  832. } else if (t < (2 / 2.75)) {
  833. return c * (7.5625 * (t -= (1.5 / 2.75)) * t + .75) + b;
  834. } else if (t < (2.5 / 2.75)) {
  835. return c * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375) + b;
  836. } else {
  837. return c * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375) + b;
  838. }
  839. }
  840. easeInBounce = function (t, b, c, d) {
  841. return c - easeOutBounce(d - t, 0, c, d) + b;
  842. };
  843. easeOutExpo = function (t, b, c, d) {
  844. return (t == d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
  845. }
  846. easeOutFall = function (t, b, c, d) {
  847. var dist = 0.5 * 9.8 * (t * t)
  848. var per = (dist + b)
  849. return dist;
  850. }
  851. this.animateLayers = function (curTime, deltaTime) {
  852. if (true) {
  853. var startZ = 100;
  854. gcodeGroup.traverse(function (child) {
  855. if (child.name.startsWith("layer#")) {
  856. var udata = child.userData;
  857. var dist = (2.0 - (curTime / 2)) * 100.0;
  858. var newZ = Math.max(0, udata.layerNumber * dist);
  859. child.position.set(0, 0, newZ);
  860. // var startTime = udata.layerNumber/8.0;
  861. // if(curTime>=startTime)
  862. // {
  863. // var myTime = curTime-startTime;
  864. // var dist = 9.8*(myTime*myTime)
  865. // var newZ = Math.max(0,startZ-dist);
  866. // child.position.set(0,0,newZ);
  867. // }
  868. // else{
  869. // child.position.set(0,0,startZ);
  870. // }
  871. }
  872. });
  873. } else {
  874. gcodeGroup.traverse(function (child) {
  875. if (child.name.startsWith("layer#")) {
  876. var udata = child.userData;
  877. var endTime = udata.layerNumber / 4.0;
  878. if (curTime < endTime) {
  879. var z = easeOutExpo(curTime, 0, 100, endTime);
  880. //Math.sin(curTime+(udata.layerNumber*0.1))
  881. child.position.set(0, 0, 100 - z);
  882. }
  883. else
  884. child.position.set(0, 0, 0);
  885. }
  886. });
  887. }
  888. }
  889. this.highlightLayer = function (layerNumber, highlightMaterial) {
  890. var needUpdate = false;//only need update if visiblity changes
  891. var defaultMat = curLineBasicMaterial;
  892. if (pgSettings.fatLines) {
  893. defaultMat = curMaterial;
  894. }
  895. gcodeGroup.traverse(function (child) {
  896. if (child.name.startsWith("layer#")) {
  897. if (child.userData.layerNumber < layerNumber) {
  898. if (child.material.uuid != defaultMat.uuid) {
  899. child.material = defaultMat;
  900. needUpdate = true;
  901. }
  902. } else if (child.userData.layerNumber == layerNumber) {
  903. if (child.material.uuid != highlightMaterial.uuid) {
  904. child.material = highlightMaterial;
  905. needUpdate = true;
  906. }
  907. }
  908. else {
  909. if (child.material.uuid != defaultMat.uuid) {
  910. child.material = defaultMat;
  911. needUpdate = true;
  912. }
  913. }
  914. }
  915. });
  916. return (needUpdate);
  917. }
  918. this.syncGcodeObjToLayer = function (layerNumber, lineNumber = Infinity) {
  919. var needUpdate = false;//only need update if visiblity changes
  920. //hack comp for mirror.
  921. //todo. better handle of mirror object so this isnt needed.
  922. if (pgSettings.showMirror && lineNumber != Infinity)
  923. lineNumber = lineNumber * 2;
  924. gcodeGroup.traverse(function (child) {
  925. if (child.name.startsWith("layer#")) {
  926. if (child.userData.layerNumber < layerNumber) {
  927. if (!child.visible || child.geometry.maxInstancedCount != child.userData.numLines)
  928. needUpdate = true;
  929. child.visible = true;
  930. child.geometry.maxInstancedCount = child.userData.numLines;
  931. } else if (child.userData.layerNumber == layerNumber) {
  932. if (!child.visible || child.geometry.maxInstancedCount != Math.min(lineNumber, child.userData.numLines))
  933. needUpdate = true;
  934. child.visible = true;
  935. child.geometry.maxInstancedCount = Math.min(lineNumber, child.userData.numLines);
  936. }
  937. else {
  938. if (child.visible)
  939. needUpdate = true;
  940. child.visible = false;
  941. }
  942. }
  943. });
  944. return (needUpdate);
  945. }
  946. this.syncGcodeObjTo = function (layerZ, lineNumber = Infinity) {
  947. //hack comp for mirror.
  948. //todo. better handle of mirror object so this isnt needed.
  949. if (pgSettings.showMirror && lineNumber != Infinity)
  950. lineNumber = lineNumber * 2;
  951. gcodeGroup.traverse(function (child) {
  952. if (child.name.startsWith("layer#")) {
  953. if (child.userData.layerZ < layerZ) {
  954. child.visible = true;
  955. child.geometry.maxInstancedCount = child.userData.numLines;
  956. } else if (child.userData.layerZ == layerZ) {
  957. child.visible = true;
  958. child.geometry.maxInstancedCount = Math.min(lineNumber, child.userData.numLines);
  959. }
  960. else {
  961. child.visible = false;
  962. }
  963. }
  964. });
  965. }
  966. this.syncGcodeObjToFilePos = function (filePosition) {
  967. let syncLayerNumber = 0;//derived layer number based on pos and user data.
  968. gcodeGroup.traverse(function (child) {
  969. if (child.name.startsWith("layer#")) {
  970. var filePositions = child.userData.filePositions;
  971. var fpMin = filePositions[0];
  972. var fpMax = filePositions[filePositions.length];
  973. if (fpMax < filePosition) { //way before.
  974. child.visible = true;
  975. child.geometry.maxInstancedCount = child.userData.numLines;
  976. } else if (fpMin > filePosition) { //way after
  977. child.visible = false;
  978. } else //must be during. right?
  979. {
  980. child.visible = true;
  981. //count number of lines before filePos
  982. var count = 0;
  983. while (count < filePositions.length && filePositions[count] < filePosition)
  984. count++;
  985. //hack comp for mirror.
  986. //todo. better handle of mirror object so this isnt needed.
  987. if (pgSettings.showMirror)
  988. count = count * 2;
  989. child.geometry.maxInstancedCount = Math.min(count, child.userData.numLines);
  990. syncLayerNumber = child.userData.layerNumber
  991. }
  992. }
  993. });
  994. return syncLayerNumber;//used to sync other elements.
  995. }
  996. this.currentUrl = "";
  997. this.loadGcode = function (url) {
  998. this.reset();
  999. currentUrl = url;
  1000. var parserObject = this;
  1001. var file_url = url;//'downloads/files/local/xxx.gcode';
  1002. var myRequest = new Request(file_url);
  1003. fetch(myRequest)
  1004. .then(function (response) {
  1005. var contentLength = response.headers.get('Content-Length');
  1006. if (!response.body || !window['TextDecoder']) {
  1007. response.text().then(function (text) {
  1008. parserObject.parse(text);
  1009. parserObject.finishLoading();
  1010. });
  1011. } else {
  1012. var myReader = response.body.getReader();
  1013. var decoder = new TextDecoder();
  1014. var buffer = '';
  1015. var received = 0;
  1016. myReader.read().then(function processResult(result) {
  1017. if (result.done) {
  1018. parserObject.finishLoading();
  1019. return;
  1020. }
  1021. received += result.value.length;
  1022. // buffer += decoder.decode(result.value, {stream: true});
  1023. /* process the buffer string */
  1024. parserObject.parse(decoder.decode(result.value, { stream: true }));
  1025. // read the next piece of the stream and process the result
  1026. return myReader.read().then(processResult);
  1027. })
  1028. }
  1029. })
  1030. }
  1031. this.finishLoading = function () {
  1032. if (currentLayer !== undefined) {
  1033. addObject(currentLayer, true);
  1034. }
  1035. //update scene bounds.
  1036. var bsize = new THREE.Vector3();
  1037. sceneBounds.getSize(bsize);
  1038. //update ui slider
  1039. if ($("#myslider-vertical").length) {
  1040. $("#myslider-vertical").slider("setMax", layers.length)
  1041. $("#myslider-vertical").slider("setValue", layers.length, false, true)
  1042. $("#myslider .slider-handle").text(layers.length);
  1043. currentLayerNumber = layers.length;
  1044. }
  1045. console.log("Finished loading GCode object.")
  1046. console.log(["layers:", layers.length, "size:", filePos])
  1047. let totalLines = 0;
  1048. for (let layer of layers) {
  1049. totalLines += layer.vertex.length / 6;
  1050. }
  1051. console.log(["lines:", totalLines])
  1052. //console.log([sceneBounds,layers])
  1053. //gcodeProxy.syncGcodeObjTo(Infinity);
  1054. //updateDimensions(bsize);
  1055. //Move zoom camera to new bounds.
  1056. var dist = Math.max(Math.abs(bsize.x), Math.abs(bsize.y)) / 2;
  1057. dist = Math.max(20, dist);//min distance to model.
  1058. //console.log(dist)
  1059. cameraControls.dollyTo(dist * 2.0, true);
  1060. }
  1061. function addObject(layer, extruding) {
  1062. if (layer.vertex.length > 2) { //Something to draw?
  1063. if (pgSettings.fatLines) {//fancy lines
  1064. var geo = new THREE.LineGeometry();
  1065. geo.setPositions(layer.vertex);
  1066. geo.setColors(layer.colors)
  1067. var line = new THREE.Line2(geo, curMaterial);
  1068. line.name = 'layer#' + layers.length;
  1069. line.userData = { layerZ: layer.z, layerNumber: layers.length, numLines: layer.vertex.length / 6, filePositions: layer.filePositions };// 6 because 2 x triplets
  1070. gcodeGroup.add(line);
  1071. //line.renderOrder = 2;
  1072. } else {//plain lines
  1073. var geo = new THREE.BufferGeometry();
  1074. geo.addAttribute('position', new THREE.BufferAttribute(new Float32Array(layer.vertex), 3));
  1075. geo.addAttribute('color', new THREE.BufferAttribute(new Float32Array(layer.colors), 3));
  1076. var line = new THREE.LineSegments(geo, curLineBasicMaterial);
  1077. line.name = 'layer#' + layers.length;
  1078. line.userData = { layerZ: layer.z, layerNumber: layers.length, numLines: layer.vertex.length / 6, filePositions: layer.filePositions };
  1079. gcodeGroup.add(line);
  1080. }
  1081. }
  1082. }
  1083. function newLayer(line) {
  1084. if (currentLayer !== undefined) {
  1085. addObject(currentLayer, true);
  1086. }
  1087. currentLayer = { vertex: [], pathVertex: [], z: line.z, colors: [], filePositions: [] };
  1088. layers.push(currentLayer);
  1089. //console.log("layer #" + layers.length + " z:" + line.z);
  1090. }
  1091. /*this.addArc= function (arc, material ) {
  1092. // let geometry = new THREE.Geometry();
  1093. // let start = new THREE.Vector3(arc.x1, arc.y1, arc.z1);
  1094. // let center = new THREE.Vector3(arc.i, arc.j, arc.k);
  1095. // let end = new THREE.Vector3(arc.x2, arc.y2, arc.z2);
  1096. let radius = Math.sqrt(
  1097. Math.pow((arc.x1 - arc.i), 2) + Math.pow((arc.y1 - arc.j), 2)
  1098. );
  1099. let arcCurve = new THREE.ArcCurve(
  1100. arc.i, // aX
  1101. arc.j, // aY
  1102. radius, // aRadius
  1103. Math.atan2(arc.y1 - arc.j, arc.x1 - arc.i), // aStartAngle
  1104. Math.atan2(arc.y2 - arc.j, arc.x2 - arc.i), // aEndAngle
  1105. !!arc.isClockwise // isClockwise
  1106. );
  1107. let divisions = 10;
  1108. let vertices = arcCurve.getPoints(divisions);
  1109. let vectorthrees = [];
  1110. for (var i = 0; i < vertices.length; i++) {
  1111. vectorthrees.push(new THREE.Vector3(vertices[i].x, vertices[i].y, arc.z1));
  1112. }
  1113. if (vectorthrees.length) {
  1114. let geometry = new THREE.Geometry();
  1115. geometry.vertices = vectorthrees;
  1116. object.add(new THREE.Line(geometry, material));
  1117. }
  1118. }*/
  1119. this.addSegment = function (p1, p2) {
  1120. if (currentLayer === undefined) {
  1121. newLayer(p1);
  1122. }
  1123. if (Number.isNaN(p1.x) || Number.isNaN(p1.y) || Number.isNaN(p1.z) || Number.isNaN(p2.x) || Number.isNaN(p2.y) || Number.isNaN(p2.z)) {
  1124. console.log(["Bad line segment", p1, p2]);
  1125. return;
  1126. }
  1127. currentLayer.vertex.push(p1.x, p1.y, p1.z);
  1128. currentLayer.vertex.push(p2.x, p2.y, p2.z);
  1129. currentLayer.filePositions.push(filePos);//save for syncing.
  1130. if (curColor != defaultColor) {
  1131. sceneBounds.expandByPoint(p1);
  1132. sceneBounds.expandByPoint(p2);
  1133. }
  1134. if (pgSettings.showMirror) {
  1135. //add mirror version
  1136. currentLayer.vertex.push(p1.x, p1.y, -p1.z);
  1137. currentLayer.vertex.push(p2.x, p2.y, -p2.z);
  1138. }
  1139. if (true)//faux shading. Darken line color based on angle
  1140. {
  1141. //var p1=new THREE.Vector3(10,10,0);
  1142. //var p2=new THREE.Vector3(15,15,0);
  1143. var per = 1.0;//bright
  1144. if (true) {
  1145. //var np2=new THREE.Vector3(p2.x,p2.y,p2.z);
  1146. var vec = new THREE.Vector3(p2.x - p1.x, p2.y - p1.y, p2.z - p1.z);
  1147. vec.normalize();
  1148. // per= Math.max(vec.dot(new THREE.Vector3(1,0,0)),0.0)
  1149. // per= Math.abs(vec.dot(new THREE.Vector3(1,0,0)),0.0)
  1150. per = (vec.dot(new THREE.Vector3(1, 0, 0)) / 2) + 0.5;
  1151. per = (per / 5.0);
  1152. } else {
  1153. var deltaX = p2.x - p1.x;
  1154. var deltaY = p2.y - p1.y;
  1155. var rad = Math.atan2(deltaY, deltaX);
  1156. rad = Math.abs(rad)
  1157. per = (rad) / (2.0 * 3.1415);
  1158. //console.log(rad + " " + per);
  1159. }
  1160. var drawColor = new THREE.Color(curColor)
  1161. var hsl = {}
  1162. drawColor.getHSL(hsl);
  1163. //darken every other line to make the layers easier to see.
  1164. if ((layers.length % 2) == 0)
  1165. hsl.l = per + 0.25;
  1166. else
  1167. hsl.l = per + 0.30;
  1168. drawColor.setHSL(hsl.h, hsl.s, hsl.l);
  1169. //console.log(drawColor.r + " " + drawColor.g + " " + drawColor.b )
  1170. currentLayer.colors.push(drawColor.r, drawColor.g, drawColor.b);
  1171. currentLayer.colors.push(drawColor.r, drawColor.g, drawColor.b);
  1172. if (pgSettings.showMirror) {
  1173. //add mirror version
  1174. drawColor.setHSL(hsl.h, hsl.s, hsl.l / 2);
  1175. currentLayer.colors.push(drawColor.r, drawColor.g, drawColor.b);
  1176. currentLayer.colors.push(drawColor.r, drawColor.g, drawColor.b);
  1177. }
  1178. }
  1179. else {
  1180. currentLayer.colors.push(curColor.r, curColor.g, curColor.b);
  1181. currentLayer.colors.push(curColor.r, curColor.g, curColor.b);
  1182. }
  1183. }
  1184. function delta(v1, v2) {
  1185. return state.relative ? v2 : v2 - v1;
  1186. }
  1187. function absolute(v1, v2) {
  1188. return state.relative ? v1 + v2 : v2;
  1189. }
  1190. this.parse = function (chunk) {
  1191. //remove comments from chunk.
  1192. //var lines = chunk.replace(/;.+/g, '').split('\n');
  1193. //or not
  1194. var lines = chunk.split('\n');
  1195. //handle partial lines from previous chunk.
  1196. lines[0] = previousPiece + lines[0];
  1197. previousPiece = lines[lines.length - 1];
  1198. //note -1 so we dont process last line in case it is a partial.
  1199. //Todo process the last line. Probably not needed since last line is usually gcode cleanup and not extruded lines.
  1200. for (var i = 0; i < lines.length - 1; i++) {
  1201. filePos += lines[i].length + 1;//+1 because of split \n.
  1202. //Process comments
  1203. //figure out line color from comments.
  1204. if (lines[i].indexOf(";") > -1) {
  1205. var cmdLower = lines[i].toLowerCase();
  1206. if (cmdLower.indexOf("inner") > -1) {
  1207. curColor = new THREE.Color(0x00ff00);//green
  1208. }
  1209. else if (cmdLower.indexOf("outer") > -1) {
  1210. curColor = new THREE.Color('red');
  1211. }
  1212. else if (cmdLower.indexOf("perimeter") > -1) {
  1213. curColor = new THREE.Color('red');
  1214. }
  1215. else if (cmdLower.indexOf("fill") > -1) {
  1216. curColor = new THREE.Color('orange');
  1217. }
  1218. else if (cmdLower.indexOf("skin") > -1) {
  1219. curColor = new THREE.Color('yellow');
  1220. }
  1221. else if (cmdLower.indexOf("support") > -1) {
  1222. curColor = new THREE.Color('skyblue');
  1223. }
  1224. else if (cmdLower.indexOf("skirt") > -1) {
  1225. curColor = new THREE.Color('skyblue');
  1226. }
  1227. else {
  1228. //var curColorHex = (Math.abs(cmd.hashCode()) & 0xffffff);
  1229. //curColor = new THREE.Color(curColorHex);
  1230. //console.log(cmd + ' ' + curColorHex.toString(16))
  1231. }
  1232. //console.log(lines[i])
  1233. }
  1234. //remove comments and process command part of line.
  1235. var tokens = lines[i].replace(/;.+/g, '').split(' ');
  1236. if (tokens.length < 1)
  1237. continue; //nothing left to process.
  1238. var cmd = tokens[0].toUpperCase();
  1239. //Arguments
  1240. var args = {};
  1241. tokens.splice(1).forEach(function (token) {
  1242. if (token[0] !== undefined) {
  1243. var key = token[0].toLowerCase();
  1244. var value = parseFloat(token.substring(1));
  1245. args[key] = value;
  1246. }
  1247. });
  1248. //G0/G1 - Linear Movement
  1249. if (cmd === 'G0' || cmd === 'G1') {
  1250. var line = {
  1251. x: args.x !== undefined ? absolute(state.x, args.x) : state.x,
  1252. y: args.y !== undefined ? absolute(state.y, args.y) : state.y,
  1253. z: args.z !== undefined ? absolute(state.z, args.z) : state.z,
  1254. e: args.e !== undefined ? absolute(state.e, args.e) : state.e,
  1255. f: args.f !== undefined ? absolute(state.f, args.f) : state.f,
  1256. };
  1257. //Layer change detection is or made by watching Z, it's made by watching when we extrude at a new Z position
  1258. if (delta(state.e, line.e) > 0) {
  1259. var diff = delta(state.e, line.e);
  1260. line.extruding = delta(state.e, line.e) > 0;
  1261. if (currentLayer == undefined || line.z != currentLayer.z) {
  1262. newLayer(line);
  1263. }
  1264. }
  1265. //make sure extruding is updated. might not be needed.
  1266. //line.extruding = delta(state.e, line.e) > 0;
  1267. //if (line.extruding)
  1268. // addSegment(state, line);//only if extruding right now.
  1269. //If E is defined in the args then extruding. Todo. is this right?
  1270. if (args.e !== undefined)
  1271. this.addSegment(state, line);//only if extruding right now.
  1272. state = line;
  1273. } else if (cmd === 'G2' || cmd === 'G3') {
  1274. //G2/G3 - Arc Movement ( G2 clock wise and G3 counter clock wise )
  1275. // Not supporting K ATM
  1276. if (args.k !== undefined) {
  1277. // I have no idea what K is for...
  1278. console.warn('THREE.GCodeLoader: Arcs with K parameter not currently supported');
  1279. }
  1280. else if (args.r !== undefined) {
  1281. console.warn('THREE.GCodeLoader: Arc in R form are not currently supported.');
  1282. }
  1283. else {
  1284. var arc = {
  1285. x: args.x !== undefined ? absolute(state.x, args.x) : state.x,
  1286. y: args.y !== undefined ? absolute(state.y, args.y) : state.y,
  1287. z: args.z !== undefined ? absolute(state.z, args.z) : state.z,
  1288. i: args.i !== undefined ? args.i : 0,
  1289. j: args.j !== undefined ? args.j : 0,
  1290. r: args.r !== undefined ? args.r : null,
  1291. // What is this K I'm seeing here, lol
  1292. //k: args.k !== undefined ? absolute( state.k, args.k ) : state.k,
  1293. e: args.e !== undefined ? absolute(state.e, args.e) : state.e,
  1294. f: args.f !== undefined ? absolute(state.f, args.f) : state.f,
  1295. is_clockwise: cmd === 'G2'
  1296. };
  1297. /* If R format is working, this could be used. I have no test code so I can't verify
  1298. if ((arc.i || arc.j) && arc.r)
  1299. {
  1300. console.warn('THREE.GCodeLoader: Arc contains I/J and R, which is not allowed. Removing R');
  1301. arc.r = null;
  1302. }
  1303. else
  1304. {
  1305. var segments = self.interpolateArc(state, arc);
  1306. for(var index = 1; index < segments.length; index++)
  1307. {
  1308. this.addSegment(segments[index-1], segments[index]);
  1309. }
  1310. }*/
  1311. var segments = self.interpolateArc(state, arc);
  1312. for (var index = 1; index < segments.length; index++) {
  1313. this.addSegment(segments[index - 1], segments[index]);
  1314. }
  1315. // Set the state to the last segment
  1316. state = segments[segments.length - 1];
  1317. }
  1318. } else if (cmd === 'G90') {
  1319. //G90: Set to Absolute Positioning
  1320. state.relative = false;
  1321. } else if (cmd === 'G91') {
  1322. //G91: Set to state.relative Positioning
  1323. state.relative = true;
  1324. } else if (cmd === 'G92') {
  1325. //G92: Set Position
  1326. var line = state;
  1327. line.x = args.x !== undefined ? args.x : line.x;
  1328. line.y = args.y !== undefined ? args.y : line.y;
  1329. line.z = args.z !== undefined ? args.z : line.z;
  1330. line.e = args.e !== undefined ? args.e : line.e;
  1331. state = line;
  1332. } else {
  1333. //console.warn( 'THREE.GCodeLoader: Command not supported:' + cmd );
  1334. }
  1335. }
  1336. }
  1337. };
  1338. //todo move to new file or remove.
  1339. function updateDimensions(bsize) {
  1340. if (dimensionsGroup === undefined) {
  1341. dimensionsGroup = new THREE.Group();
  1342. dimensionsGroup.name = 'dimensions';
  1343. scene.add(dimensionsGroup);
  1344. }
  1345. var fontLoader = new THREE.FontLoader();
  1346. fontLoader.load('plugin/prettygcode/static/js/helvetiker_bold.typeface.json', function (font) {
  1347. var xMid, text;
  1348. var color = 0x006699;
  1349. var matDark = new THREE.LineBasicMaterial({
  1350. color: color,
  1351. side: THREE.DoubleSide
  1352. });
  1353. var matLite = new THREE.MeshBasicMaterial({
  1354. color: color,
  1355. transparent: true,
  1356. opacity: 0.8,
  1357. side: THREE.DoubleSide
  1358. });
  1359. var center = new THREE.Vector3(0, 0, 0);
  1360. sceneBounds.getCenter(center);
  1361. //console.log(["center",center]);
  1362. //clear out any old lines
  1363. for (var i = dimensionsGroup.children.length - 1; i >= 0; i--) {
  1364. dimensionsGroup.remove(dimensionsGroup.children[i]);
  1365. }
  1366. var textHeight = 3;
  1367. var textZ = 0.2;
  1368. var message = bsize.x.toFixed(2) + " MM";
  1369. var shapes = font.generateShapes(message, textHeight);
  1370. var geometry = new THREE.ShapeBufferGeometry(shapes);
  1371. geometry.computeBoundingBox();
  1372. xMid = -0.5 * (geometry.boundingBox.max.x - geometry.boundingBox.min.x);
  1373. geometry.translate(xMid, 0, 0);
  1374. // make shape ( N.B. edge view not visible )
  1375. text = new THREE.Mesh(geometry, matLite);
  1376. text.position.set(center.x, sceneBounds.min.y - (textHeight * 2), textZ);
  1377. dimensionsGroup.add(text);
  1378. var lineMat = new THREE.LineMaterial({
  1379. linewidth: 6,
  1380. color: color
  1381. });
  1382. lineMat.resolution.set(gcodeWid, gcodeHei);
  1383. var lineGeo = new THREE.LineGeometry();
  1384. var lineVerts = [
  1385. sceneBounds.min.x, sceneBounds.min.y - (textHeight * 0.8), textZ,
  1386. sceneBounds.max.x, sceneBounds.min.y - (textHeight * 0.8), textZ,
  1387. sceneBounds.min.x, sceneBounds.min.y - 1, textZ,
  1388. sceneBounds.min.x, sceneBounds.min.y - (textHeight * 1.2), textZ,
  1389. sceneBounds.max.x, sceneBounds.min.y - 1, textZ,
  1390. sceneBounds.max.x, sceneBounds.min.y - (textHeight * 1.2), textZ,
  1391. ];
  1392. lineGeo.setPositions(lineVerts);
  1393. var line = new THREE.Line2(lineGeo, lineMat);
  1394. dimensionsGroup.add(line);
  1395. var textHeight = 3;
  1396. var message = bsize.y.toFixed(2) + " MM";
  1397. var shapes = font.generateShapes(message, textHeight);
  1398. var geometry = new THREE.ShapeBufferGeometry(shapes);
  1399. geometry.computeBoundingBox();
  1400. xMid = -0.5 * (geometry.boundingBox.max.x - geometry.boundingBox.min.x);
  1401. geometry.translate(xMid, 0, 0);
  1402. geometry.rotateZ(Math.PI / 2);
  1403. // make shape ( N.B. edge view not visible )
  1404. text = new THREE.Mesh(geometry, matLite);
  1405. text.position.set(sceneBounds.max.x + (textHeight * 2), center.y, textZ);
  1406. dimensionsGroup.add(text);
  1407. var lineGeo = new THREE.LineGeometry();
  1408. var lineVerts = [
  1409. sceneBounds.max.x + (textHeight * 0.8), sceneBounds.min.y, textZ,
  1410. sceneBounds.max.x + (textHeight * 0.8), sceneBounds.max.y, textZ,
  1411. sceneBounds.max.x + 1, sceneBounds.min.y, textZ,
  1412. sceneBounds.max.x + (textHeight * 1.2), sceneBounds.min.y, textZ,
  1413. sceneBounds.max.x + 1, sceneBounds.max.y, textZ,
  1414. sceneBounds.max.x + (textHeight * 1.2), sceneBounds.max.y, textZ,
  1415. ];
  1416. lineGeo.setPositions(lineVerts);
  1417. var line = new THREE.Line2(lineGeo, lineMat);
  1418. dimensionsGroup.add(line);
  1419. var textHeight = 3;
  1420. var message = bsize.z.toFixed(2) + " MM";
  1421. var shapes = font.generateShapes(message, textHeight);
  1422. var geometry = new THREE.ShapeBufferGeometry(shapes);
  1423. geometry.computeBoundingBox();
  1424. xMid = 0; // - 0.5 * ( geometry.boundingBox.max.x - geometry.boundingBox.min.x );
  1425. geometry.translate(xMid, 0, 0);
  1426. geometry.rotateX(Math.PI / 2);
  1427. // make shape ( N.B. edge view not visible )
  1428. text = new THREE.Mesh(geometry, matLite);
  1429. text.position.set(sceneBounds.max.x + (textHeight * 1), sceneBounds.max.y, center.z);
  1430. dimensionsGroup.add(text);
  1431. var lineGeo = new THREE.LineGeometry();
  1432. var lineVerts = [
  1433. sceneBounds.max.x + (textHeight * 0.8), sceneBounds.max.y + (textHeight * 0.8), 0,
  1434. sceneBounds.max.x + (textHeight * 0.8), sceneBounds.max.y + (textHeight * 0.8), bsize.z,
  1435. ];
  1436. lineGeo.setPositions(lineVerts);
  1437. var line = new THREE.Line2(lineGeo, lineMat);
  1438. dimensionsGroup.add(line);
  1439. });
  1440. }
  1441. function resizeCanvasToDisplaySize() {
  1442. const canvas = renderer.domElement;
  1443. // look up the size the canvas is being displayed
  1444. const width = canvas.clientWidth;
  1445. const height = canvas.clientHeight;
  1446. // adjust displayBuffer size to match
  1447. if (canvas.width !== width || canvas.height !== height) {
  1448. // you must pass false here or three.js sadly fights the browser
  1449. renderer.setSize(width, height, false);
  1450. camera.aspect = width / height;
  1451. camera.updateProjectionMatrix();
  1452. gcodeWid = width;
  1453. gcodeHei = height;
  1454. cameraControls.setViewport(0, 0, width, height);
  1455. return true;//update needed.
  1456. }
  1457. return false;//no update needed
  1458. }
  1459. function initThree() {
  1460. renderer = new THREE.WebGLRenderer({ canvas: document.getElementById("mycanvas"), antialias: pgSettings.antialias });
  1461. //todo. is this right?
  1462. renderer.setPixelRatio(window.devicePixelRatio);
  1463. //renderer2 = new THREE.WebGLRenderer({ canvas: document.getElementById("pipcanvas") });
  1464. //todo. is this right?
  1465. //renderer2.setPixelRatio(window.devicePixelRatio*3.0);
  1466. //todo allow save/pos camera at start.
  1467. camera = new THREE.PerspectiveCamera(70, 2, 0.1, 10000);
  1468. camera.up.set(0, 0, 1);
  1469. camera.position.set(bedVolume.width, 0, 50);
  1470. CameraControls.install({ THREE: THREE });
  1471. clock = new THREE.Clock();
  1472. var canvas = $("#mycanvas");
  1473. cameraControls = new CameraControls(camera, canvas[0]);
  1474. //todo handle other than lowerleft
  1475. resetCamera();
  1476. lightBackground = 0xd0d0d0;
  1477. darkBackground = 0x000000;
  1478. //for debugging
  1479. window.myCameraControls = cameraControls;
  1480. //scene
  1481. scene = new THREE.Scene();
  1482. if (pgSettings.darkMode) {
  1483. scene.background = new THREE.Color(darkBackground);
  1484. } else {
  1485. scene.background = new THREE.Color(lightBackground);
  1486. }
  1487. //for debugging
  1488. window.myScene = scene;
  1489. //add a light. might not be needed.
  1490. var light = new THREE.PointLight(0xffffff);
  1491. light.position.set(0, 0, -bedVolume.height);
  1492. scene.add(light);
  1493. // light = new THREE.PointLight(0xffffff);
  1494. // light.position.set(bedVolume.width/2, bedVolume.depth/2,bedVolume.height);
  1495. // scene.add(light);
  1496. cameraLight = new THREE.PointLight(0xffffff);
  1497. cameraLight.position.copy(camera.position);
  1498. scene.add(cameraLight);
  1499. // light = new THREE.AmbientLight( 0xffffff ); // soft white light
  1500. // scene.add( light );
  1501. // light = new THREE.PointLight(0xffffff);
  1502. // light.position.copy(camera.position);
  1503. // scene.add(light);
  1504. //Semi-transparent plane to represent the bed.
  1505. updateGridMesh();
  1506. cubeCamera = new THREE.CubeCamera(1, 100000, 128);
  1507. cubeCamera.position.set(bedVolume.width / 2, bedVolume.depth / 2, 10);
  1508. scene.add(cubeCamera);
  1509. cubeCamera.update(renderer, scene);
  1510. var syncSavedZ = 0;
  1511. var cameraIdleTime = 0;
  1512. var firstFrame = true; /*possible bug fix. this might not be needed.*/
  1513. //material for fatline highlighter
  1514. var highlightMaterial = undefined;
  1515. if (pgSettings.fatLines) {
  1516. highlightMaterial = new THREE.LineMaterial({
  1517. linewidth: 4, // in pixels
  1518. //transparent: true,
  1519. //opacity: 0.5,
  1520. //color: new THREE.Color(curColorHex),// rainbow.getColor(layers.length % 64).getHex()
  1521. vertexColors: THREE.VertexColors,
  1522. });
  1523. highlightMaterial.resolution.set(500, 500);
  1524. } else {
  1525. //highlightMaterial=
  1526. }
  1527. function animate() {
  1528. const delta = clock.getDelta();
  1529. const elapsed = clock.getElapsedTime();
  1530. var needRender = false;
  1531. /*possible bug fix. this might not be needed.*/
  1532. if (firstFrame) {
  1533. needRender = true;
  1534. firstFrame = false;
  1535. }
  1536. if (printHeadSim) {
  1537. printHeadSim.updatePosition(delta);
  1538. }
  1539. if (curPrinterState &&
  1540. (curPrinterState.flags.printing || curPrinterState.flags.paused) &&
  1541. pgSettings.syncToProgress && (!forceNoSync)) {
  1542. if (nozzleModel && printHeadSim) {
  1543. var curState = printHeadSim.getCurPosition();
  1544. nozzleModel.position.copy(curState.position);
  1545. needRender = true;
  1546. }
  1547. if (gcodeProxy) {
  1548. var calculatedLayer = gcodeProxy.syncGcodeObjToFilePos(curPrintFilePos);
  1549. if (highlightMaterial !== undefined) {
  1550. gcodeProxy.highlightLayer(calculatedLayer, highlightMaterial);
  1551. }
  1552. $("#myslider-vertical").slider('setValue', calculatedLayer, false, true);
  1553. $("#myslider .slider-handle").text(calculatedLayer);
  1554. needRender = true;
  1555. // gcodeProxy.syncGcodeObjTo(curState.layerZ,curState.lineNumber-1/*-window.fudge*/);//todo. figure out why *2 is needed.
  1556. }
  1557. } else {
  1558. if (nozzleModel && nozzleModel.position.lengthSq()) {
  1559. nozzleModel.position.set(0, 0, 0);//todo. hide instead/also?
  1560. needRender = true;
  1561. }
  1562. if (gcodeProxy) {
  1563. if (gcodeProxy.syncGcodeObjToLayer(currentLayerNumber)) {
  1564. if (highlightMaterial !== undefined) {
  1565. gcodeProxy.highlightLayer(currentLayerNumber, highlightMaterial);
  1566. }
  1567. needRender = true;
  1568. //console.log("GCode Proxy needs update");
  1569. }
  1570. }
  1571. }
  1572. //show or hide nozzle based on settings.
  1573. if (nozzleModel && nozzleModel.visible != pgSettings.showNozzle) {
  1574. nozzleModel.visible = pgSettings.showNozzle;
  1575. needRender = true;
  1576. }
  1577. if (highlightMaterial !== undefined) {
  1578. //fake a glow by ramping the diffuse color.
  1579. let nv = 0.5 + ((Math.sin(elapsed * 4) + 1) / 4.0);
  1580. //console.log(nv);
  1581. //highlightMaterial.uniforms.linewidth.value=nv*15;
  1582. nv = 0.5;
  1583. highlightMaterial.uniforms.diffuse.value.r = nv;
  1584. highlightMaterial.uniforms.diffuse.value.g = nv;
  1585. highlightMaterial.uniforms.diffuse.value.b = nv;
  1586. }
  1587. //if(gcodeProxy)
  1588. // gcodeProxy.animateLayers(elapsed)
  1589. cameraControls.dollyToCursor = true;//todo. needed every frame?
  1590. const updated = cameraControls.update(delta);//handle mouse/keyboard etc.
  1591. if (updated)//did user move the camera?
  1592. {
  1593. cameraIdleTime = 0;
  1594. needRender = true;
  1595. }
  1596. else {
  1597. cameraIdleTime += delta;
  1598. if (pgSettings.orbitWhenIdle && cameraIdleTime > 5) {
  1599. cameraControls.rotate(delta / 5.0, 0, false);//auto orbit camera a bit.
  1600. cameraControls.update(delta);//force update so it wont look like manual move next frame.
  1601. needRender = true;
  1602. }
  1603. }
  1604. if (cameraLight) {
  1605. cameraLight.position.copy(camera.position);
  1606. }
  1607. if (resizeCanvasToDisplaySize())
  1608. needRender = true;
  1609. if (needRender) {
  1610. // //do real time reflections. Probably overkill. Certianly overkill.
  1611. // if(pgSettings.reflections && cubeCamera && nozzleModel)
  1612. // {
  1613. // cubeCamera.position.copy( nozzleModel.position );
  1614. // cubeCamera.position.z=cubeCamera.position.z+10;
  1615. // nozzleModel.visible=false;
  1616. // cubeCamera.update( renderer, scene );
  1617. // nozzleModel.visible=true;
  1618. // }
  1619. renderer.render(scene, camera);
  1620. } else {
  1621. //console.log("idle");
  1622. }
  1623. //renderer2.render(scene, camera);
  1624. requestAnimationFrame(animate);
  1625. }
  1626. animate();
  1627. }
  1628. function resetCamera() {
  1629. if (!cameraControls)//Make sure controls exist.
  1630. return;
  1631. if (bedVolume.origin == "lowerleft")
  1632. cameraControls.setTarget(bedVolume.width / 2, bedVolume.depth / 2, 0, false);
  1633. else
  1634. cameraControls.setTarget(0, 0, 0, false);
  1635. }
  1636. function updateGridMesh() {
  1637. //console.log("updateGridMesh");
  1638. console.log(arguments.callee.name);
  1639. if (!scene)//scene loaded yet?
  1640. return;
  1641. var existingPlane = scene.getObjectByName("plane");
  1642. if (existingPlane)
  1643. scene.remove(existingPlane);
  1644. var existingGrid = scene.getObjectByName("grid");
  1645. if (existingGrid)
  1646. scene.remove(existingGrid);
  1647. console.log([existingPlane, existingGrid]);
  1648. var planeGeometry = new THREE.PlaneGeometry(bedVolume.width, bedVolume.depth);
  1649. var planeMaterial = new THREE.MeshBasicMaterial({
  1650. color: 0x909090,
  1651. side: THREE.DoubleSide,
  1652. transparent: true,
  1653. opacity: 0.2,
  1654. });
  1655. var plane = new THREE.Mesh(planeGeometry, planeMaterial);
  1656. plane.name = "plane";
  1657. //todo handle other than lowerleft
  1658. if (bedVolume.origin == "lowerleft")
  1659. plane.position.set(bedVolume.width / 2, bedVolume.depth / 2, -0.1);
  1660. //plane.quaternion.setFromEuler(new THREE.Euler(- Math.PI / 2, 0, 0));
  1661. scene.add(plane);
  1662. //make bed sized grid.
  1663. var grid = new THREE.GridHelper(bedVolume.width, bedVolume.width / 10, 0x000000, 0x888888);
  1664. grid.name = "grid";
  1665. //todo handle other than lowerleft
  1666. if (bedVolume.origin == "lowerleft")
  1667. grid.position.set(bedVolume.width / 2, bedVolume.depth / 2, 0);
  1668. //if (pgSettings.transparency){
  1669. grid.material.opacity = 0.6;
  1670. grid.material.transparent = true;
  1671. grid.quaternion.setFromEuler(new THREE.Euler(-Math.PI / 2, 0, 0));
  1672. scene.add(grid);
  1673. }
  1674. }
  1675. OCTOPRINT_VIEWMODELS.push({
  1676. construct: PrettyGCodeViewModel,
  1677. dependencies: ["settingsViewModel", "loginStateViewModel", "printerProfilesViewModel", "controlViewModel"],
  1678. elements: ["#tab_plugin_prettygcode"]
  1679. });
  1680. });