DEV Community

Black
Black

Posted on

Reading 3D printer G-code in the browser: slicer comment formats and a price calculator with no upload

I wanted a 3D print price calculator where you drop the sliced file and get a price, without uploading a 40 MB G-code file to anyone's server. It turned out to need about 30 lines of JavaScript. Here is how it works, and the slicer comment formats I had to handle.

Try it first if you like: 3D print price calculator (static page, nothing leaves the browser).

Only read the ends of the file

Slicers write their summary as comments, either in the header or in the footer. The middle is millions of moves you do not care about. Blob.slice() lets you read just the ends without loading the whole file into a string:

async function load(file) {
  const head = await file.slice(0, 60000).text();
  const tail = await file.slice(Math.max(0, file.size - 80000)).text();
  const [grams, secs] = read(head + '\n' + tail);
}
Enter fullscreen mode Exit fullscreen mode

That is 140 KB read no matter how big the file is, so it stays fast on a phone. file.text() would pull the whole file into one string first.

The comment formats

These are the lines I match, in order of preference:

Slicer Filament Time
PrusaSlicer, OrcaSlicer ; filament used [g] = 12.34 ; estimated printing time (normal mode) = 1h 2m 3s
Bambu Studio ; total filament weight [g] : 12.34 ; total estimated time: 1h 2m 3s
Cura ;Filament used: 4.12m (length only) ;TIME:3723 (seconds)

Multi-material files list one value per extruder, comma separated (= 5.10, 2.30), so sum them:

function read(text) {
  let g = null, secs = null, mm = null, x;
  if (x = text.match(/;\s*(?:total )?filament (?:used|weight) \[g\]\s*[=:]\s*([\d.,]+)/i))
    g = x[1].split(',').filter(Boolean).reduce((a, b) => a + +b, 0);
  if (x = text.match(/;\s*filament used \[mm\]\s*=\s*([\d.,]+)/i))
    mm = x[1].split(',').filter(Boolean).reduce((a, b) => a + +b, 0);
  if (x = text.match(/;\s*(?:total estimated time|estimated printing time[^=\n]*)\s*[=:]\s*([0-9dhms ]+)/i))
    secs = hms(x[1]);
  if (secs == null && (x = text.match(/;\s*TIME:\s*(\d+)/))) secs = +x[1];
  if (g == null && mm == null && (x = text.match(/;\s*Filament used:\s*([\d.]+)\s*m/))) mm = +x[1] * 1000;
  if (g == null && mm != null) g = Math.PI * 0.0875 ** 2 * (mm / 10) * 1.24; // 1.75 mm PLA
  return [g, secs];
}

function hms(s) {
  let t = 0;
  for (const x of s.matchAll(/(\d+)\s*([dhms])/g)) t += +x[1] * { d: 86400, h: 3600, m: 60, s: 1 }[x[2]];
  return t;
}
Enter fullscreen mode Exit fullscreen mode

Two gotchas:

  • [^=\n]* in the time pattern matters. Without the \n, PrusaSlicer's "(normal mode)" and "(silent mode)" lines can run together and the match grabs the wrong one.
  • Cura only gives length. Grams = cross-section area x length x density. For 1.75 mm filament the radius is 0.0875 cm, length in cm, PLA about 1.24 g/cm³. PETG is 1.27, ABS 1.04, so the estimate is a little high for ABS.

From grams and hours to a price

Once you have grams and hours, the cost is mostly arithmetic people skip:

filament  = grams / 1000 * spool price
power     = watts * hours / 1000 * price per kWh
wear      = printer cost / expected life hours * hours
buffer    = (filament + power + wear) * failure %
your time = hands-on minutes / 60 * hourly rate
price     = cost * (1 + markup) / (1 - selling fee %)
Enter fullscreen mode Exit fullscreen mode

The last line is the one most calculators get wrong. If Etsy takes 9.5% of the sale, adding 9.5% to your price does not cover it; you divide by 0.905 instead. And hands-on time (removing supports, packing) is usually worth more than the filament.

Same idea, other tools

I built a few more single-file, no-backend calculators the same way this week, all phone-first with the numbers saved in localStorage:

If you sell prints and want the whole job log, spool tracking and printer payback in a spreadsheet, that is the 3D Print Shop Tracker. The parser above is free to copy.

Top comments (0)