@lee7670 ...or just do the math of steps per unit measure to how many units it needs to move?... if you're at 100 steps per centimeter, the move there is obviously four centimeters?
Posts made by theKM
-
RE: Manually provide the number of steps to be executed by a drive?
-
RE: Handy way to browse GCode docco directly from the gcode
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
-
RE: Mega GCode docco page + gcode file navigator
updated the script for greasemonkey/tampermonkey... apparently they sandbox a few things so link handling attachments had to be more complicated than a usual first-class-citizen script. The *monkey plugins seem to work pretty nice now, certainly a more handy way to run it than to paste it in all the time
-
RE: Gcode documentation change
@zapta said in Gcode documentation change:
Can we have the official documentation page running this script automatically when opened? Loading time was very fast.
I updated and tested the script in greasemonkey (firefox) / tampermonkey (chrome) plugins... so if you want it to work automagically, that's one way to do it without the wiki admins applying the script.
With the plugin, this link will automatically make the large page without needing to click:
https://duet3d.dozuki.com/Wiki/Gcode?inflateContent
And if the 'inflateContent' link is too much of a hassle, you can always have it build the page by changing the last part in the script from ...
// auto-trigger... if (qstring === 'inflateContent') { processMegaPage(); }
...to...
processMegaPage();
-
RE: Gcode documentation change
@zapta said in Gcode documentation change:
If the target section link is represented in the URL, the loader could potentially parse it after loading the parts and jump there, or something like that, but it's also above my pay grade.
See, then you went and made me feel bad
...script above is updated to reevaluate the hash link to re-bump into the correct location after the content injection.
It is also looking for "inflate content" in the URL query string, if it's there it will automatically inflate the content without needing to click.
-
RE: Gcode documentation change
@zapta said in Gcode documentation change:
Works for me! (Using chrome/mac so had to open the console in a different way).
woot
Can we have the official documentation page running this script automatically when opened? Loading time was very fast.
way above my pay grade to include it, but it is written in a way that has it do nothing if the user's not on the right page.
also, is it possible to have links to this overall page? E.g. a link for G1 that will take you to the G1 in that page?
The page is inflated after loading, the browser doesn't know how to cope with sub-linking into a page that's altered after the main document as loaded, so some things are simply off the table.
That said, try the script I put here... https://forum.duet3d.com/topic/25459/mega-gcode-docco-page-gcode-file-navigator
...specifically the "cross linking" mentioned, and see if that's what you're after.
-
RE: Gcode documentation change
For what it's worth...
A script that puts a link at the top of the left hand menu, that if clicked, will re-assemble the single mega-page from all the separate pages.
It is written in a way that if included as a script tag in the wiki's root html template, it will only wake up on the correct page, and simply add a link to the menu. User can click it to assemble the mega page, or ignore it all together to leave it as-is. I'd recommend putting it in a file somewhere and referring to it so the browser can cache it so there's no download impact each page load.
For anyone that wants to run it themselves, just go to the gcode page, open JS console (
ctrl + shift + J
for chrome,ctrl + shift + i
for firefox ) , paste it in, press enter...// ==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; // 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... let runnerCount = 1; (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(); }
-
RE: Repository for CNC Config's and Macro's
@o_lampe ...I originally replied that the script no longer works, but then I re-wrote it to include a content downloader that re-creates the previous structure of the single mega-page before setting up the gcode browser.
The updated script and details are above if you want to give it a go (I edited the post to reflect the new changes).
-
Mega GCode docco page + gcode file navigator
For fans of the old mega-gcode-page...
I was starting to miss the mega page, but understand why they had to do it.
So I messed with my greaseMonkey script. Below is a script that re-creates the original mega-page by plucking content from the little gcode pages and squirting them into the main page. With three processing "threads" it rips the individual pages down and assembles the mega page in around 10-12 seconds on my connection (six threads is amusing, but it trips the server's rate limit, because it starts to look like a DDOS attack, so, 3 is ideal, IMO ).
To run it, browse to the gcode page ( https://duet3d.dozuki.com/Wiki/Gcode ) open JS console (
ctrl + shift + J
in chrome,ctrl + shift + i
in firefox), copy and paste this in... hit enter to run it.When it runs, it should put a "Create Mega-Page" link at the top of the left hand menu... click that...
You should see the scroll bar on the right go tiny as it downloads and injects the content from all the separate gcode pages into this page. What results is a page exactly like what used to be there, menu on the left still works, you can `ctrl+F' to find content, etc.
_
Script to copy and paste:// ==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; // 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... let runnerCount = 1; (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(); }
_
CROSS LINKING
After all the gcode content is downloaded and set up on the page, there will be two links at the top of the menu... click on "Cross-link gcodes", and the script will then link all the GCode mentions on the page with other parts of the document, so you can bounce around from wherever you see a gcode mentioned._
GCODE BROWSERClick on "Setup Gcode Browser"... it will cross-link the gcodes as mentioned, and the page gets wrangled to set up the left hand size as a gcode panel with the documentation on the right.
Paste the contents of a gcode file (like
config.g
) into the text area, and it will update to show the gcode, with all the codes linked to the documentation on the right.This makes it handy to browse what's happening in a gcode file, the browser know where you're at (for example, rather than assume you know what a code is doing, click on it, confirm in the docco that it does what you assume, then just click back in the browser to be right back where you're at).
This has greatly helped my understanding of what's going on in these setup files so I can set up my CNC machine.
_
GreasemonkeyIf you like this script, and wish it was just part of the site, then you can use a plugin called GreaseMonkey in FireFox (chrome has one called TamperMonkey that runs the same thing)... which just tells the browser to run scripts against sites you configure. The above script takes greasemonkey into account and should work as is.
The script is also looking for "inflateContent" in the URL, so if you install it in greaseMoneky, and then just browse to
https://duet3d.dozuki.com/Wiki/Gcode?inflateContent
, the page will automatically inflate to the mega-page without needing to click anything.
_...if the wiki maintainers wish, this script could be included in the top html document. It only works when it sees that it's on the correct page, and then injects the link. So the site stays as-is, until the user clicks the link to create the voltron mega-page.
I'm still fiddling with it to do all the cross-linking that my other script did previously, but thought people might like this to recreate what was, while still allowing wiki maintainers to do the separate page thing.
-
RE: Configurable Main Menu ( /w source code )
@t3p3tony , no worries! Not even a problem. I used to be a maintainer over on Apache/Jakarta stuff, totally get it.
My other branch has the menu generating whole pages/routes to make control panels, and that's much less likely to be merged
Forks are part of the coolness of open source. The Ooznest CNC version is what convinced me to take the leap on something not a printer, and now I don't think I'd like it any other way... and now it's part of the core, sweetness.
-
RE: Configurable Main Menu ( /w source code )
If anyone wants to try it out, I put the DWC zip files build here...
https://github.com/abates-dentsu/DuetWebControl/tree/az_generatedUI_build/dist-packaged
...note that this is based on the latest beta version. I'd only recommend people try it if they've already successfully moved up to the latest core beta of DWC.
-
RE: Configurable Main Menu ( /w source code )
Thinking that people might like the current groups, just want the system to keep managing the default groups the way it always has... so this would be a way to set a menu to keep the default groups, but just put a plugin on top of the whole stack so it's more handy
{ "mainMenu": [ "/BtnCmd", "default-control-group", "default-job-group", "default-files-group", "default-plugins-group", "default-settings-group" ] }
...that way after system updates, anyone who's configured a menu this way doesn't have to guess about what the new menu might contain.
Have pushed this to the PR mentioned earlier.
For what it's worth (not much ).
-
RE: CNC with Duet3
In general, the accuracy of limit/homing switches is only a factor if there's something else on the machine that needs absolute registration to those switches... for example, fixtures, which are things bolted to the machine so you can put stock up against them (or clamping fixtures that hold the material themselves), assume their position is correct, and you can start the job.
Most CNC'ers place the material somewhere on the bed, secure it, and then using things like touch-probes to locate the piece and set work zero, and go from there. Working in this fashion isn't effected by the accuracy of the limit/homing switches.
-
RE: Configurable Main Menu ( /w source code )
Wasn't totally happy with where some of the logic was sitting, so moved it own to its own vuex store file so it was less intrusive on the original
routes/index.js
andstore/settings.js
files... this did also mean that it got its own menu config file indwc-mainMenu.json
file, which is optional (just keeps using the internal default if not found). Changed pushed, fwiw. -
Configurable Main Menu ( /w source code )
Note edit: If anyone wants to try it out, I put the DWC zip files build here...
https://github.com/abates-dentsu/DuetWebControl/tree/az_generatedUI_build/dist-packaged
...this is based on the latest beta version. I'd only recommend people try it if they've already successfully moved up to the latest core beta of DWC.--
Other thread was going on about configurability, I latched onto the thought that given there's some handy plugins that can do what we want in the main panel, that the main menu is the part that would be nice to be able to configure in the settings json.
So for whatever it's worth, I wrote a thing, and have a PR for anyone running their own builds and wants to play with it:
https://github.com/Duet3D/DuetWebControl/pull/388It's the main menu, so it can't be plugin'ified.
I tried to make sure it didn't get in the way of the DWC maintainers, while still getting the flexibility to people desiring to wrangle with the menu.
Maintainers can muse over it, people who have a build setup can merge and play with it in their own branch, whatever anyone wants. There's dev info in the PR, but I need to put in some more user docco somewhere.
.
In short, in
dwc-settings.json
add amainMenuConfig: []
array/list config in themain
block, which represents the list of things in the main menu, including groups of items or buttons or whatever. The internal/system page components now have keys that you can use to define where they go. For the stock menu, the configuration would look like..."mainMenuConfig": [ { "name": "Control", "icon": "mdi-tune", "caption": "menu.control.caption", "pages": [ "control-dashboard", "control-console" ] }, { "name": "Job", "icon": "mdi-printer", "caption": "menu.job.caption", "pages": [ "job-status", "job-webcam" ] }, { "name": "Files", "icon": "mdi-sd", "caption": "menu.files.caption", "pages": [ "files-jobs", "files-macros", "files-filaments", "files-system" ] }, { "name": "Plugins", "icon": "mdi-puzzle", "caption": "menu.plugins.caption", "pages": [] }, { "name": "Settings", "icon": "mdi-wrench", "caption": "menu.settings.caption", "pages": [ "settings-general", "settings-machine" ] } ]
...this sets up the groups to work as they did, allowing plugins to register themselves as they always have.
But you can see how the string key placeholders for the stock components are working, and how the main components will set themselves into the menu. Here's an example of flattening out the menu with some select components to have just the list of items without the groups.
"mainMenuConfig": [ "control-dashboard", "control-console", "/MoveItMoveIt", "job-status", "files-jobs", "files-macros", "files-system", "settings-general", "settings-machine" ]
...the "/MoveItMoveIt" in the list is my own plugin's path, showing how to add a plugin to the list that you may have installed. So you can set what you want, in the order you want, etc etc.
Maybe I wanted to rename the dashboard item, you could define all the things about it including the icon....
"mainMenuConfig": [ { "caption": "Extra Dashboard!", "icon": "mdi-wrench", "path": "/", "button": true }, "control-console", "/MoveItMoveIt", "job-status", "files-jobs", "files-macros", "files-system", "settings-general", "settings-machine" ]
...how about a submenu that actually works like a super handy macro panel (buttons can run gcode as well as route to pages)...
"mainMenuConfig": [ { "caption": "Extra Dashboard!", "icon": "mdi-wrench", "path": "/", "button": true }, "control-console", "/MoveItMoveIt", "job-status", "files-jobs", "files-macros", "files-system", { "icon": "mdi-wrench", "caption": "Super Handy Macros", "pages": [ { "caption": "Home IT!!", "icon": "mdi-cog", "gcode": "G28", "button": true }, { "caption": "Home Sweet Home", "icon": "mdi-wrench", "path": "G28", "button": true } ] }, "settings-general", "settings-machine" ]
.
Anyways, have a play with it if ya have the urge. I did accidentally fork with the account I use for work and couldn't be bothered moving it, so please no naughty review comment language.
-
RE: DWC Custom Configuration
Messing with menu config after work...
- config comes from and stored in 'dwc-settings.json' along with everything else
- default works as-is, nobody will know anything is different until they tweak the config of it themselves
- complete control over the menu; change the order of it, flatten it (remove the groupings), add buttons that run gcode including adding them to submenus, create submenus, change the icons and colours (yup, can have a menu of macros that run directly from the menu, because why not!? )
- internally the system pages are tweaked how they register in to the menu, but no-change for existing plugins to be able to plonk them somewhere specific by configuration.
Good fun.
-
Filament sensor as a physical pause button?
In the 3d printer, filament sensor will pause the printer... but the sensor has to be associated with an extruder... so I'm curious if this can be used without using an extruder motor?? ...I reckon that a physical pause/resume button would be handy
I did some searching, haven't found anything, might mess with it on the machine this weekend.