Duet3D Logo Duet3D
    • Tags
    • Documentation
    • Order
    • Register
    • Login

    Handy way to browse GCode docco directly from the gcode

    Scheduled Pinned Locked Moved
    General Discussion
    3
    9
    810
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • theKMundefined
      theKM
      last edited by theKM

      edit: added cross-link gcode references in the docco itself.
      edit 2: gave the pasted gcode half the screen and re-packed/stripped rest of page style so it can fit and work better.

      _
      Browsing other people's gcode is super handy, particularly for custom setups and going your own way. However, it's a genuine hassle to both read someone else's GCode while trying to bounce around the gcode dictionary docco (The docco site its fine, but there's a lot of info, so it's naturally just a hassle to navigate around it). So I wrote me a "greasemonkey" script for the browser, for the docco site.

      • open the gcode dictionary site: https://duet3d.dozuki.com/Wiki/Gcode
      • show the javascript console ("web developer tools" > "console")
        "ctrl + shift + J" in chrome, "ctrl + shift + i" in FireFox.
      • copy and paste in the following code and hit return
        (someone else can feel free to code review to ensure there's no evil)...
      // ==UserScript==
      // @name     GCode Wiki script
      // @version  1
      // @include     https://duet3d.dozuki.com/Wiki/Gcode*
      // ==/UserScript==
      
      /** for processing injected menu clicks, gets changed
       * to proper implementation when needed
       */
      let processMegaPage = () => {};
      let crossLinkDoc = () => {};
      let setupGcodeBrowser = () => {};
      
      let codeMap = {};
      let crossLinked = false;
      
      let runnerCount = 2;
      
      // for identifying gcode references
      let codeRx = /[GM]+[0-9]+(.[0-9])?/g;
      
      /** Helper function with finding things, so I don't need a framework
       */
      let docE = (f, inp) => {
          if (Array.isArray(inp)) {
              return (inp.map(i => docE(f, i))).flat();
          }
          let seek = document[f](inp);
          if (!seek || seek instanceof HTMLElement) return seek;
          let tmp = [];
          for (let t of seek) tmp.push(t);
          return tmp;
      };
      
      let content = document.getElementById('content');
      let isInContent = function (e) {
          if (!e) return false;
          if (!e.parentElement || e.parentElement === document.body) return false;
          if (e.parentElement === content) return true;
          return isInContent(e.parentElement);
      }
      
      /** Handler to take input and process links in gcode browser
       */
      let ref = null;
      let paster = () => {
          if (ref) clearTimeout(ref);
          ref = setTimeout(() => {
              let linkStyle = 'all:unset;cursor:pointer;text-decoration:underline;color:blue;';
              let v = docE('getElementById', 'gcode-paste-input').value;
              v = v.replace(codeRx, (s) => {
                  if (codeMap[s]) return '<a href="#' + codeMap[s] + '" style="' + linkStyle + '">' + s + '</a>';
                  return s;
              });
      
              docE('getElementById', 'gcode-paste').innerHTML = '<pre style="text-align:left">' + v + '</pre>';
          }, 500);
      };
      
      
      // check to see if this page is the one we want, otherwise do nothing
      let loc = null;
      let pos = null;
      let hash = null;
      let qstring = null;
      
      const evalLocation = () => {
          loc = document.location + '';
          pos = loc.indexOf('#');
          hash = null;
          if (pos > -1) {
              hash = loc.substring(pos);
              loc = loc.substring(0, pos);
          }
      
          qstring = '';
          pos = loc.indexOf('?');
          if (pos > -1) {
              qstring = loc.substring(pos + 1);
              loc = loc.substring(0, pos);
          }
      };
      
      const moveToHash = () => {
          if (hash) {
              document.location = hash;
          }
      };
      
      // current location details...
      evalLocation();
      
      if (loc.endsWith('/Wiki/Gcode')) {
          // current page is the gcode wiki...
      
          // find the menu
          let topTitle = docE('getElementsByClassName', 'toc-title')[0];
      
          // add the link to run the mega-page import...
          let squirt = document.createElement('div');
          squirt.innerHTML = '<a id="create-mega-page-link" href="javascript:;">Create Mega-Page</a>';
      
          topTitle.parentElement.insertBefore(squirt, topTitle.nextSibling);
      
          setTimeout(() => {
              // take a beat, wire the event clicker (greasemonkey complication)...
              let tmp = null;
              (tmp = document.getElementById('create-mega-page-link')) ? tmp.addEventListener('click', () => processMegaPage()) : null;
          }, 50);
      
          // function handler for the click...
          processMegaPage = () => {
              squirt.innerHTML = '';
      
              // find all the specific links
              let glinks = docE('getElementsByTagName', 'a').filter(a => {
                  let t = a.innerText;
                  let h = '' + a.href;
                  h = h.substring(h.indexOf('/', 10));
                  return (t.match(codeRx) && !h.startsWith('/Wiki/Gcode') && (h.startsWith('/Wiki/M') || h.startsWith('/Wiki/G')));
              });
      
              // a function that represents the work for a page download
              let processor = a => {
      
                  return new Promise((resolve, reject) => {
      
                      // download...
                      fetch(a.href).then(async response => {
      
                          // the document dext...
                          let t = await response.text();
      
                          // pull out the good stuff...
                          let from = t.indexOf('<div id="Wiki_Details">');
                          let to = t.lastIndexOf('<div class="clearer">', t.lastIndexOf('<div itemprop="author"'));
                          t = t.substring(from, to);
      
                          // dont need this link in there now...
                          t = t.replace('<p>Back to the <a href="/Wiki/Gcode">Gcode Dictionary</a></p>', '');
      
                          // make a div for it
                          const d = document.createElement('div');
                          d.innerHTML = t;
      
                          // replace the link tag's parent <p>
                          let p = a.parentElement;
                          p.parentNode.replaceChild(d, p);
      
                          // end of task
                          resolve();
      
                      }).catch(reject);
                  });
              };
      
              let count = 0;
      
              // a function that represents a processing thread so we can start N of them
              let runner = async () => {
                  while (glinks.length > 0) {
                      count++;
                      if (count % 25 === 0) console.log(count);
                      let x = glinks.shift();
                      try {
                          await processor(x);
                      } catch (e) {
                          console.log(e);
                      }
                  }
              };
      
              // start the runners...
              (async () => {
                  let time = new Date().getTime();
      
                  console.log('links to process: ' + glinks.length);
      
                  let runners = [];
                  for (let i = 0; i < runnerCount; i++) runners.push(runner());
      
                  await Promise.all(runners);
      
                  console.log('DONE!!! (' + ((new Date().getTime() - time) / 1000) + ' seconds)');
      
                  moveToHash();
      
                  squirt.innerHTML = '<a id="cross-linker-link" href="javascript:;">Cross-link gcodes</a><br>'
                      + '<a id="setup-gcode-browser-link" href="javascript:;">Setup GCode Browser</a><br><br>';
      
                  setTimeout(() => {
                      // take a beat, wire the event clicker (greasemonkey complication)...
                      let tmp = null;
                      (tmp = document.getElementById('cross-linker-link')) ? tmp.addEventListener('click', () => crossLinkDoc()) : null;
                      (tmp = document.getElementById('setup-gcode-browser-link')) ? tmp.addEventListener('click', () => setupGcodeBrowser()) : null;
                  }, 50);
              })();
      
              crossLinkDoc = () => {
                  if (crossLinked) return;
                  crossLinked = true;
      
                  let link = document.getElementById('cross-linker-link');
                  link.parentElement.removeChild(link);
      
                  /** Parse the references out of the document
                   */
                  docE('getElementsByClassName', 'header').forEach(sect => {
                      let s = sect.id + '';
                      s.replace(codeRx, sx => {
                          sx = sx.replace('_', '.');
                          if (!codeMap[sx]) {
                              codeMap[sx] = s;
                          }
                      });
                  });
      
                  /** Cross-link gcode references through the document
                   */
                  docE('getElementsByTagName', ['p', 'li', 'pre', 'strong']).forEach(tag => {
                      if (!tag) return;
                      if (!isInContent(tag)) return;
                      let linkStyle = 'all:unset;cursor:pointer;text-decoration:underline;color:blue;';
                      let h = ('' + tag.innerHTML).trim();
                    	if (h.startsWith('<div/')) return;
                      tag.innerHTML = h.replace(/(?<!_)([GM]+[0-9]+(.[0-9])?)/g, (s) => {
                          if (codeMap[s]) return '<a href="#' + codeMap[s] + '" style="' + linkStyle + '" class="crosslink">' + s + '</a>';
                          return s;
                      });
                  });
      
                  evalLocation();
                  moveToHash();
              };
      
              setupGcodeBrowser = () => {
                  crossLinkDoc();
      
                  if (squirt) {
                      squirt.parentElement.removeChild(squirt);
                      squirt = null;
                  }
      
                  /** Strip the main wrapper element of styling
                   */
                  let wrap = docE('getElementById', 'page');
                  wrap.className = '';
                  wrap.id = 'gnavPage';
      
                  /** Remove styling of sidebar nested element
                   */
                  docE('getElementById', 'sidebar-wiki-toc').className = '';
                  docE('getElementById', 'sidebar-wiki-toc').id = 'gnavSidebar';
      
                  /** Strip the sidebar
                   */
                  let sb = null;
                  sb = docE('getElementById', 'page-sidebar');
                  sb.innerHTML = '';
                  sb.id = 'gnavSidebar';
      
                  /** Strip the main area
                   */
                  let main = docE('getElementById', 'main');
                  main.id = 'gnavMain';
                  docE('getElementsByClassName', 'articleContainer')[0].className = '';
      
                  /** Clear our other elements not needed
                   */
                  let mb = docE('getElementById', 'mainBody');
                  for (let kid of mb.children) {
                      if (kid.id !== 'contentFloat') mb.removeChild(kid);
                  }
      
                  let bg = docE('getElementById', 'background');
                  for (let kid of bg.children) {
                      if (kid.id === 'gnavPage') break;
                      else bg.removeChild(kid);
                  }
      
                  for (let kid of document.body.children) {
                      if (kid.id !== 'background') document.body.removeChild(kid);
                  }
      
                  docE('getElementById', 'content').id = 'offContent';
      
                  /** Apply new styles to the wrapper, sidebar and main areas
                   */
                  wrap.setAttribute('style', 'display:flex;gap:1em;');
                  sb.setAttribute('style', 'flex: 1 1 45%; height: 100vh;');
                  main.setAttribute('style', 'flex: 1 1 50%; height: 100vh');
      
                  /** Input field to paste the gcode
                   */
                  sb.innerHTML = `
      <div style="width: 45vw; height: 100vh; position: fixed;overflow: scroll;">
          <textarea id="gcode-paste-input"></textArea>
        <hr />
        <pre id="gcode-paste" style="text-align: left"><center>( paste gcode above )</center></pre>
      </div>`;
      
                  setTimeout(() => {
                      // take a beat, wire the event clicker (greasemonkey complication)...
                      let tmp = null;
                      (tmp = document.getElementById('gcode-paste-input')) ? tmp.addEventListener('keyup', () => paster()) : null;
                  }, 50);
              };
          };
      }
      
      // auto-trigger...
      //if (qstring === 'inflateContent') {
          processMegaPage();
      //}
      

      ...the script will hide the current menu, strip the page down so it's two side-by-side panels, and provide a place to paste gcode. Paste in any chunck of Gcode (like entire contents of 'config.g' ) and click "Run". It will then put the Gcode in place of the menu with all the codes changed to links in the document.

      Much easier to browse what's happening without losing concentration with bouncing around.

      I would love for the docco site to pilfer this idea.
      (I haven't put that much effort into styling or whatever, I just wanted it to work, so the styling is "rough").

      _
      CROSS LINKING: The docco frequently references other codes in the document, but it's a hassle to browse to them and back to where you were. This cross linker runs through the doc to find the gcode references, adds the links... so now you can click on the link, see what it was referencing and click back in the browser as the browser remembers scroll positions when clicking.

      I can understand why maintainers haven't put the links in, as it would be a hassle to look after the links with all the edits, if any link changes, blah blah. This script just looks after that as a post-processor style :)!

      Screenshot for what it's worth...

      67183642-03cd-47de-b49a-8dbc03f3a64a-image.png

      droftartsundefined 1 Reply Last reply Reply Quote 5
      • droftartsundefined
        droftarts administrators @theKM
        last edited by

        @thekm Nice hack! Works well. I'll ask the powers-that-be if it's possible to incorporate something like this into a page. Maybe a separate page that shows the gcode dictionary in a window.

        Future development: highlight unrecognised gcodes?

        Ian

        Bed-slinger - Mini5+ WiFi/1LC | RRP Fisher v1 - D2 WiFi | Polargraph - D2 WiFi | TronXY X5S - 6HC/Roto | CNC router - 6HC | Tractus3D T1250 - D2 Eth

        theKMundefined 1 Reply Last reply Reply Quote 1
        • theKMundefined
          theKM @droftarts
          last edited by

          @droftarts groovy! There's a lot of different ways it could work in a forever-home, but mostly I'd look to just make sure it wasn't adding a maintenance overhead. A separate page could rip the wiki content with basic http call, but it would need to be under the same domain or the wiki server would need to allow the cross-origin call.

          For sure it could show what wasn't found in the dictionary. It could also properly parse the gcode and confirm options, but getting too fancy would make a maintenance hassle 🙂

          theKMundefined 1 Reply Last reply Reply Quote 0
          • theKMundefined
            theKM @theKM
            last edited by

            @thekm ...edited, now re-packs the screen more aggressively to improve the browsing experience.

            1 Reply Last reply Reply Quote 0
            • theKMundefined
              theKM
              last edited by

              What's helped me the most, I think, is that it lowers the bar to confirm an assumption; as you're reading and the docco or gcode references something, I assumed I knew what a code was doing because it's too much of a hassle to go there and confirm the assumption and get back to where I was... now it's just a click, confirm the assumption, then hit back in the browser to be back where I was.

              droftartsundefined 1 Reply Last reply Reply Quote 0
              • droftartsundefined
                droftarts administrators @theKM
                last edited by

                @thekm We've had to make a change to the Gcode dictionary page; see https://forum.duet3d.com/topic/25156/gcode-documentation-change
                I'm not sure if this makes it easier or more difficult to do this now?!

                Ian

                Bed-slinger - Mini5+ WiFi/1LC | RRP Fisher v1 - D2 WiFi | Polargraph - D2 WiFi | TronXY X5S - 6HC/Roto | CNC router - 6HC | Tractus3D T1250 - D2 Eth

                theKMundefined 1 Reply Last reply Reply Quote 0
                • theKMundefined
                  theKM @droftarts
                  last edited by

                  @droftarts , it'd take a tweak or three, but there's always a way to hack around things... I think it's a good step forward either way, nice! 🙂

                  1 Reply Last reply Reply Quote 0
                  • theKMundefined
                    theKM
                    last edited by

                    update: I've since updated the script to have it wrangle all the newly managed pages into the single gcode page that my original script relied on. The script above was updated just so people don't try something that wont work, but I did start another thread on it (mods can maybe close this thread? not sure how things are done around here for old content 🙂 ).

                    https://forum.duet3d.com/topic/25459/mega-gcode-docco-page-gcode-file-navigator

                    T3P3Tonyundefined 1 Reply Last reply Reply Quote 0
                    • T3P3Tonyundefined
                      T3P3Tony administrators @theKM
                      last edited by

                      @thekm locked on your request.

                      www.duet3d.com

                      1 Reply Last reply Reply Quote 0
                      • First post
                        Last post
                      Unless otherwise noted, all forum content is licensed under CC-BY-SA