{"id":3283,"date":"2026-08-21T09:05:33","date_gmt":"2026-08-21T09:05:33","guid":{"rendered":"https:\/\/nedprojects.com\/?page_id=3283"},"modified":"2026-08-21T14:23:27","modified_gmt":"2026-08-21T14:23:27","slug":"3dcalc","status":"publish","type":"page","link":"https:\/\/nedprojects.com\/en\/3dcalc\/","title":{"rendered":"3D print calculator"},"content":{"rendered":"<div data-elementor-type=\"wp-page\" data-elementor-id=\"3283\" class=\"elementor elementor-3283\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-9a92ea6 e-flex e-con-boxed e-con e-parent\" data-id=\"9a92ea6\" data-element_type=\"container\" data-settings=\"{&quot;_ob_use_container_extras&quot;:&quot;no&quot;,&quot;_ob_column_hoveranimator&quot;:&quot;no&quot;,&quot;_ob_glider_is_slider&quot;:&quot;no&quot;,&quot;_ob_column_has_pseudo&quot;:&quot;no&quot;}\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-ec70f5b ob-has-background-overlay elementor-widget elementor-widget-shortcode\" data-id=\"ec70f5b\" data-element_type=\"widget\" data-settings=\"{&quot;_ob_widget_stalker_use&quot;:&quot;no&quot;,&quot;_ob_poopart_use&quot;:&quot;yes&quot;,&quot;_ob_shadough_use&quot;:&quot;no&quot;,&quot;_ob_allow_hoveranimator&quot;:&quot;no&quot;}\" data-widget_type=\"shortcode.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-shortcode\"><script>window.P3DC_CFG = {\"root\":\"https:\\\/\\\/nedprojects.com\\\/en\\\/wp-json\\\/p3dc\\\/v1\\\/\",\"canSync\":false};<\/script><style>\n.p3dc{--bg:#0f1115;--panel:#171a21;--panel-2:#1e222b;--line:#2a2f3a;--txt:#e7eaf0;--muted:#9aa3b2;--accent:#4f8cff;--accent-2:#22c58b;--danger:#ff5c5c;--radius:14px;--field:#12151c;\n display:block;color:var(--txt);font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif;background:var(--bg);padding:22px;border-radius:var(--radius);line-height:1.45;max-width:1080px;margin:0 auto;}\n.p3dc *{box-sizing:border-box;font-family:inherit}\n.p3dc h1{font-size:20px;font-weight:700;margin:0 0 2px}\n.p3dc .sub{color:var(--muted);font-size:13px;margin:0 0 14px}\n.p3dc .grid{display:grid;grid-template-columns:1.35fr 1fr;gap:18px}\n@media(max-width:820px){.p3dc .grid{grid-template-columns:1fr}}\n.p3dc .card{background:var(--panel);border:1px solid var(--line);border-radius:var(--radius);padding:18px;margin-bottom:16px}\n.p3dc .card h2{font-size:13px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);margin:0 0 14px;font-weight:600}\n.p3dc .row{display:grid;grid-template-columns:1fr 1fr;gap:12px}\n.p3dc .row-3{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px}\n@media(max-width:520px){.p3dc .row,.p3dc .row-3{grid-template-columns:1fr}}\n.p3dc .field{margin-bottom:12px}\n.p3dc label{display:block;font-size:12px;color:var(--muted);margin-bottom:5px}\n.p3dc input,.p3dc select{width:100%;background:var(--field);border:1px solid var(--line);color:var(--txt);padding:9px 10px;border-radius:9px;font-size:14px;outline:none}\n.p3dc input:focus,.p3dc select:focus{border-color:var(--accent)}\n.p3dc input[type=range]{padding:0;height:6px;-webkit-appearance:none;appearance:none;background:var(--line);border-radius:6px;cursor:pointer}\n.p3dc input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:20px;height:20px;border-radius:50%;background:var(--accent);border:3px solid #fff;cursor:pointer}\n.p3dc input[type=range]::-moz-range-thumb{width:18px;height:18px;border-radius:50%;background:var(--accent);border:3px solid #fff;cursor:pointer}\n.p3dc .btn{display:inline-flex;align-items:center;gap:6px;background:var(--accent);color:#fff;border:none;padding:9px 14px;border-radius:9px;font-size:13px;font-weight:600;cursor:pointer}\n.p3dc .btn:hover{filter:brightness(1.08)}\n.p3dc .btn.ghost{background:transparent;border:1px solid var(--line);color:var(--txt)}\n.p3dc .btn.sm{padding:6px 10px;font-size:12px}\n.p3dc .btn.danger{background:transparent;border:1px solid var(--danger);color:var(--danger)}\n.p3dc .hint{font-size:11px;color:var(--muted);margin-top:4px}\n.p3dc .result{position:sticky;top:14px}\n.p3dc .price-cap{color:var(--muted);font-size:12px;text-transform:uppercase;letter-spacing:.06em;margin-bottom:6px}\n.p3dc .price-edit{display:flex;align-items:center;gap:6px;background:var(--field);border:1px solid var(--line);border-radius:11px;padding:8px 12px}\n.p3dc .price-edit:focus-within{border-color:var(--accent)}\n.p3dc .price-edit .cur{font-size:28px;font-weight:800;color:var(--txt);line-height:1}\n.p3dc .price-edit input{all:unset;width:100%;font-size:32px;font-weight:800;letter-spacing:-.02em;color:var(--txt);line-height:1;font-variant-numeric:tabular-nums}\n.p3dc .price-edit input::-webkit-outer-spin-button,.p3dc .price-edit input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}\n.p3dc .price-hint{font-size:11px;color:var(--muted);margin-top:5px}\n.p3dc .profit-box{display:flex;justify-content:space-between;align-items:center;margin-top:16px;background:rgba(34,197,139,.08);border:1px solid rgba(34,197,139,.28);border-radius:11px;padding:11px 14px}\n.p3dc .profit-box .lbl{font-size:13px;color:var(--txt)}\n.p3dc .profit-box .profit-val{color:var(--accent-2);font-weight:800;font-size:22px;line-height:1.1;font-variant-numeric:tabular-nums;text-align:right;display:block}\n.p3dc .profit-box .mgn{font-size:11px;color:var(--muted)}\n\/* cost breakdown \u2014 hard reset so the theme's table CSS cannot leak in *\/\n.p3dc .bd{width:100%;border-collapse:collapse;font-size:13px;margin-top:16px;background:transparent}\n.p3dc .bd tr,.p3dc .bd td{background:transparent!important;border:0;box-shadow:none}\n.p3dc .bd td{padding:8px 2px;border-bottom:1px solid rgba(255,255,255,.07);color:var(--muted);font-weight:400}\n.p3dc .bd td:last-child{text-align:right;color:var(--txt);font-weight:600;font-variant-numeric:tabular-nums}\n.p3dc .bd tr:last-child td{border-bottom:0}\n.p3dc .bd tr.total td{border-top:1px solid var(--line);border-bottom:0;padding-top:12px;color:var(--txt);font-weight:800;font-size:15px}\n.p3dc .slider-val{float:right;color:var(--accent);font-weight:700}\n.p3dc .chip{display:inline-block;font-size:11px;background:var(--panel-2);border:1px solid var(--line);border-radius:999px;padding:2px 9px;color:var(--muted);margin-left:6px}\n.p3dc .chip.ok{color:var(--accent-2);border-color:rgba(34,197,139,.4)}\n.p3dc .chip.local{color:#f0b429;border-color:rgba(240,180,41,.4)}\n.p3dc .saved-item{display:flex;justify-content:space-between;align-items:center;gap:8px;background:var(--panel-2);border:1px solid var(--line);border-radius:9px;padding:8px 10px;margin-bottom:7px;font-size:13px}\n.p3dc .saved-item .meta{color:var(--muted);font-size:11px}\n.p3dc .saved-actions{display:flex;gap:6px;flex-shrink:0}\n.p3dc .empty{color:var(--muted);font-size:12px;font-style:italic}\n.p3dc .fillib{display:flex;gap:6px;flex-wrap:wrap;margin-top:8px}\n.p3dc .fil-tag{display:inline-flex;align-items:center;gap:6px;background:var(--panel-2);border:1px solid var(--line);border-radius:999px;padding:3px 6px 3px 10px;font-size:12px}\n.p3dc .fil-tag .x{cursor:pointer;color:var(--muted);font-weight:700;padding:0 3px}\n.p3dc .fil-tag .x:hover{color:var(--danger)}\n.p3dc .two-btn{display:flex;gap:8px;margin-top:6px;flex-wrap:wrap}\n<\/style>\n\n<div class=\"p3dc\" id=\"p3dc-app\">\n  <h1>Nedprojects 3D Print Cost Calculator <span class=\"chip\">EUR<\/span> <span class=\"chip\" id=\"syncChip\">\u2026<\/span><\/h1>\n  <p class=\"sub\">Internal pricing tool. <span id=\"importWrap\" style=\"display:none\"><a href=\"#\" id=\"importBtn\" style=\"color:var(--accent)\">Import saved data from this browser \u2192<\/a><\/span><\/p>\n\n  <div class=\"grid\">\n    <div>\n      <div class=\"card\">\n        <h2>Print job<\/h2>\n        <div class=\"row\">\n          <div class=\"field\"><label>Printer<\/label><select id=\"machine\"><\/select><\/div>\n          <div class=\"field\"><label>Power draw (W)<\/label><input id=\"watts\" type=\"number\" min=\"0\" step=\"1\" value=\"100\"><div class=\"hint\">Auto-filled by preset; override for any machine.<\/div><\/div>\n        <\/div>\n        <div class=\"row-3\">\n          <div class=\"field\"><label>Print hours<\/label><input id=\"printH\" type=\"number\" min=\"0\" step=\"1\" value=\"0\"><\/div>\n          <div class=\"field\"><label>Print minutes<\/label><input id=\"printM\" type=\"number\" min=\"0\" max=\"59\" step=\"1\" value=\"0\"><\/div>\n          <div class=\"field\"><label>Failure \/ waste buffer (%)<\/label><input id=\"buffer\" type=\"number\" min=\"0\" step=\"1\" value=\"5\"><div class=\"hint\">Applied to print costs only.<\/div><\/div>\n        <\/div>\n      <\/div>\n\n      <div class=\"card\">\n        <h2>Filament<\/h2>\n        <div class=\"field\"><label>Saved filament<\/label><select id=\"filSelect\"><option value=\"\">\u2014 choose \u2014<\/option><\/select><\/div>\n        <div class=\"row-3\">\n          <div class=\"field\"><label>Name<\/label><input id=\"filName\" type=\"text\" placeholder=\"e.g. Bambu PLA Matte Black\"><\/div>\n          <div class=\"field\"><label>Price \/ kg (\u20ac)<\/label><input id=\"filPrice\" type=\"number\" min=\"0\" step=\"0.01\" value=\"20\"><\/div>\n          <div class=\"field\"><label>Grams used<\/label><input id=\"grams\" type=\"number\" min=\"0\" step=\"1\" value=\"0\"><\/div>\n        <\/div>\n        <div class=\"two-btn\">\n          <button class=\"btn sm\" id=\"filSave\">Save \/ update filament<\/button>\n          <button class=\"btn sm ghost\" id=\"filClear\">Clear fields<\/button>\n        <\/div>\n        <div class=\"fillib\" id=\"filLib\"><\/div>\n      <\/div>\n\n      <div class=\"card\">\n        <h2>Labor<\/h2>\n        <div class=\"row\">\n          <div class=\"field\"><label>Modeling \/ file prep \u2014 hours<\/label><input id=\"modelH\" type=\"number\" min=\"0\" step=\"0.25\" value=\"0\"><\/div>\n          <div class=\"field\"><label>Modeling rate (\u20ac\/hr)<\/label><input id=\"modelRate\" type=\"number\" min=\"0\" step=\"1\" value=\"30\"><\/div>\n        <\/div>\n        <div class=\"row\">\n          <div class=\"field\"><label>Post-processing \u2014 hours<\/label><input id=\"postH\" type=\"number\" min=\"0\" step=\"0.25\" value=\"0\"><div class=\"hint\">Support removal, sanding, assembly.<\/div><\/div>\n          <div class=\"field\"><label>Post-processing rate (\u20ac\/hr)<\/label><input id=\"postRate\" type=\"number\" min=\"0\" step=\"1\" value=\"20\"><\/div>\n        <\/div>\n      <\/div>\n\n      <div class=\"card\">\n        <h2>Overheads <span class=\"chip\">defaults saved<\/span><\/h2>\n        <div class=\"row-3\">\n          <div class=\"field\"><label>Electricity (\u20ac\/kWh)<\/label><input id=\"elec\" type=\"number\" min=\"0\" step=\"0.01\" value=\"0.25\"><\/div>\n          <div class=\"field\"><label>Maintenance (\u20ac\/print-hr)<\/label><input id=\"maint\" type=\"number\" min=\"0\" step=\"0.01\" value=\"0.75\"><div class=\"hint\">Nozzle, belts, plate, PTFE.<\/div><\/div>\n          <div class=\"field\"><label>Depreciation (\u20ac\/print-hr)<\/label><input id=\"dep\" type=\"number\" min=\"0\" step=\"0.01\" value=\"0.10\"><div class=\"hint\">Printer price \u00f7 lifetime hrs.<\/div><\/div>\n        <\/div>\n      <\/div>\n    <\/div>\n\n    <div>\n      <div class=\"card result\">\n        <h2>Estimate<\/h2>\n        <div class=\"price-cap\">Selling price \u2014 editable<\/div>\n        <div class=\"price-edit\"><span class=\"cur\">\u20ac<\/span><input id=\"finalPrice\" type=\"number\" min=\"0\" step=\"0.5\" value=\"0.00\"><\/div>\n        <div class=\"price-hint\">Type a price to set it directly \u2014 profit and markup back-solve from cost.<\/div>\n        <div class=\"field\" style=\"margin-top:16px\">\n          <label>Profit markup <span class=\"slider-val\" id=\"markupVal\">50%<\/span><\/label>\n          <input id=\"markup\" type=\"range\" min=\"0\" max=\"300\" step=\"5\" value=\"50\">\n        <\/div>\n        <div class=\"field\">\n          <label>Or set exact profit (\u20ac)<\/label>\n          <input id=\"profitInput\" type=\"number\" min=\"0\" step=\"0.5\" value=\"0\">\n          <div class=\"hint\">Type a target profit \u2014 the markup back-solves from total cost.<\/div>\n        <\/div>\n        <div class=\"profit-box\">\n          <span class=\"lbl\">Your profit<\/span>\n          <span><span class=\"profit-val\" id=\"profitVal\">\u20ac0.00<\/span><span class=\"mgn\" id=\"marginNote\">= 0.0% margin<\/span><\/span>\n        <\/div>\n        <table class=\"bd\" id=\"breakdown\"><\/table>\n        <div class=\"two-btn\" style=\"margin-top:14px\">\n          <input id=\"estName\" type=\"text\" placeholder=\"Estimate label (e.g. Client X \u2014 figurine)\" style=\"flex:1;background:var(--field);border:1px solid var(--line);color:var(--txt);padding:8px 10px;border-radius:9px;font-size:13px\">\n        <\/div>\n        <div class=\"two-btn\" style=\"margin-top:8px\"><button class=\"btn sm\" id=\"saveEst\">Save estimate<\/button><\/div>\n      <\/div>\n      <div class=\"card\">\n        <h2>Saved estimates<\/h2>\n        <div class=\"two-btn\" style=\"margin:0 0 10px\"><button class=\"btn sm ghost\" id=\"exportCsv\">Export CSV<\/button><\/div>\n        <div id=\"savedList\"><p class=\"empty\">Loading\u2026<\/p><\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/div>\n\n<script>\n(function(){\n  \"use strict\";\n  var $=function(id){return document.getElementById(id);};\n  var CFG=window.P3DC_CFG||null;\n  var useServer=!!(CFG&&CFG.root&&CFG.nonce&&CFG.canSync);\n  var LS={fil:\"p3dc_filaments_v1\",est:\"p3dc_estimates_v1\",set:\"p3dc_settings_v1\"};\n\n  function lsLoad(k,d){try{var v=localStorage.getItem(k);return v?JSON.parse(v):d;}catch(e){return d;}}\n  function lsSave(k,v){try{localStorage.setItem(k,JSON.stringify(v));}catch(e){}}\n\n  function api(path,opts){\n    return fetch(CFG.root+path,Object.assign({\n      credentials:'same-origin',\n      cache:'no-store',\n      headers:{'Content-Type':'application\/json','X-WP-Nonce':CFG.nonce,'Cache-Control':'no-cache'}\n    },opts||{})).then(function(r){\n      if(!r.ok) throw new Error('HTTP '+r.status);\n      return r.status===204?null:r.json();\n    });\n  }\n\n  var PRESETS=[{name:\"Bambu Lab A1\",watts:100},{name:\"Bambu Lab X2D\",watts:250},{name:\"Custom \/ other\",watts:150}];\n  var filaments=[],estimates=[],settings={};\n  var markupPct=50; \/\/ canonical profit markup; slider and exact-profit field both write here\n\n  \/\/ machine dropdown\n  (function(){\n    var sel=$(\"machine\");\n    PRESETS.forEach(function(p,i){var o=document.createElement(\"option\");o.value=i;o.textContent=p.name+\" (\"+p.watts+\"W)\";sel.appendChild(o);});\n    sel.addEventListener(\"change\",function(){var p=PRESETS[+sel.value];if(p){$(\"watts\").value=p.watts;recalc();}});\n  })();\n\n  function applySettings(){[\"elec\",\"maint\",\"dep\",\"modelRate\",\"postRate\",\"buffer\"].forEach(function(id){if(settings[id]!=null&&$(id))$(id).value=settings[id];});}\n  var setTimer=null;\n  function persistSettings(){\n    var s={};[\"elec\",\"maint\",\"dep\",\"modelRate\",\"postRate\",\"buffer\"].forEach(function(id){s[id]=$(id).value;});\n    settings=s;\n    if(useServer){clearTimeout(setTimer);setTimer=setTimeout(function(){api('settings',{method:'POST',body:JSON.stringify({data:s})}).catch(function(){});},600);}\n    else{lsSave(LS.set,s);}\n  }\n\n  function setSyncChip(){\n    var c=$(\"syncChip\");\n    if(useServer){c.textContent=\"Synced to site\";c.className=\"chip ok\";$(\"importWrap\").style.display=\"inline\";}\n    else{c.textContent=\"This browser only\";c.className=\"chip local\";}\n  }\n\n  \/\/ ---------- load ----------\n  function loadAll(){\n    if(!useServer){filaments=lsLoad(LS.fil,[]);estimates=lsLoad(LS.est,[]);settings=lsLoad(LS.set,{});return Promise.resolve();}\n    return Promise.all([\n      api('items\/filament').then(function(rows){filaments=(rows||[]).map(function(x){return {id:x.id,name:x.label,price:parseFloat((x.data&&x.data.price)||0)};});}),\n      api('items\/estimate').then(function(rows){estimates=(rows||[]).map(function(x){var d=x.data||{};return {id:x.id,label:x.label,price:parseFloat(d.price||0),date:d.date||\"\",vals:d.vals||{}};});}),\n      api('settings').then(function(d){settings=(d&&typeof d===\"object\")?d:{};}).catch(function(){settings={};})\n    ]).catch(function(){useServer=false;filaments=lsLoad(LS.fil,[]);estimates=lsLoad(LS.est,[]);settings=lsLoad(LS.set,{});});\n  }\n\n  \/\/ ---------- filaments ----------\n  function renderFilaments(){\n    var sel=$(\"filSelect\");sel.innerHTML='<option value=\"\">\u2014 choose \u2014<\/option>';\n    filaments.forEach(function(f,i){var o=document.createElement(\"option\");o.value=i;o.textContent=f.name+\" (\u20ac\"+f.price.toFixed(2)+\"\/kg)\";sel.appendChild(o);});\n    var lib=$(\"filLib\");lib.innerHTML=\"\";\n    if(!filaments.length){lib.innerHTML='<span class=\"empty\">No saved filaments yet \u2014 add one above.<\/span>';return;}\n    filaments.forEach(function(f,i){var t=document.createElement(\"span\");t.className=\"fil-tag\";t.innerHTML='<span>'+esc(f.name)+' \u00b7 \u20ac'+f.price.toFixed(2)+'<\/span><span class=\"x\" data-i=\"'+i+'\">\u2715<\/span>';lib.appendChild(t);});\n    lib.querySelectorAll(\".x\").forEach(function(x){x.addEventListener(\"click\",function(){delFilament(+x.getAttribute(\"data-i\"));});});\n  }\n  $(\"filSelect\").addEventListener(\"change\",function(){var f=filaments[+this.value];if(f){$(\"filName\").value=f.name;$(\"filPrice\").value=f.price;recalc();}});\n  $(\"filSave\").addEventListener(\"click\",function(){\n    var name=$(\"filName\").value.trim();var price=parseFloat($(\"filPrice\").value);\n    if(!name){$(\"filName\").focus();return;}\n    if(isNaN(price)||price<0){$(\"filPrice\").focus();return;}\n    var existing=null;for(var i=0;i<filaments.length;i++){if(filaments[i].name.toLowerCase()===name.toLowerCase()){existing=filaments[i];break;}}\n    if(useServer){\n      var body={kind:'filament',label:name,data:{price:price}};if(existing&&existing.id)body.id=existing.id;\n      api('items',{method:'POST',body:JSON.stringify(body)}).then(function(row){\n        if(existing){existing.price=price;existing.id=row.id;}else{filaments.unshift({id:row.id,name:name,price:price});}\n        renderFilaments();\n      }).catch(apiErr);\n    }else{\n      if(existing)existing.price=price;else filaments.unshift({name:name,price:price});\n      lsSave(LS.fil,filaments);renderFilaments();\n    }\n  });\n  $(\"filClear\").addEventListener(\"click\",function(){$(\"filName\").value=\"\";$(\"filPrice\").value=\"20\";$(\"filSelect\").value=\"\";});\n  function delFilament(i){\n    var f=filaments[i];if(!f)return;\n    if(useServer&&f.id){api('items\/'+f.id,{method:'DELETE'}).then(function(){filaments.splice(i,1);renderFilaments();}).catch(apiErr);}\n    else{filaments.splice(i,1);lsSave(LS.fil,filaments);renderFilaments();}\n  }\n\n  \/\/ ---------- calculation ----------\n  function num(id){var v=parseFloat($(id).value);return isNaN(v)?0:v;}\n  function eur(n){return \"\u20ac\"+n.toFixed(2);}\n  function compute(){\n    var printHrs=num(\"printH\")+num(\"printM\")\/60;\n    var cElec=(num(\"watts\")\/1000)*printHrs*num(\"elec\");\n    var cFil=(num(\"grams\")\/1000)*num(\"filPrice\");\n    var cMaint=printHrs*num(\"maint\");\n    var cDep=printHrs*num(\"dep\");\n    var printCost=cElec+cFil+cMaint+cDep;\n    var cBuffer=printCost*(num(\"buffer\")\/100);\n    var cModel=num(\"modelH\")*num(\"modelRate\");\n    var cPost=num(\"postH\")*num(\"postRate\");\n    var totalCost=printCost+cBuffer+cModel+cPost;\n    var profit=totalCost*(markupPct\/100);\n    var price=totalCost+profit;\n    var margin=price>0?profit\/price*100:0;\n    return {cElec:cElec,cFil:cFil,cMaint:cMaint,cDep:cDep,cBuffer:cBuffer,cModel:cModel,cPost:cPost,totalCost:totalCost,profit:profit,price:price,margin:margin,markupPct:markupPct};\n  }\n  function fmtPct(p){return (Math.abs(p%1)<0.05?Math.round(p):p.toFixed(1))+\"%\";}\n  function recalc(){\n    var r=compute();\n    $(\"profitVal\").textContent=eur(r.profit);\n    $(\"markupVal\").textContent=fmtPct(r.markupPct);\n    $(\"marginNote\").textContent=\"= \"+r.margin.toFixed(1)+\"% margin\";\n    \/\/ mirror the three linked fields (skip whichever one is being typed in)\n    var sl=$(\"markup\");sl.value=Math.min(+sl.max,Math.max(+sl.min,markupPct));\n    if(document.activeElement!==$(\"profitInput\")) $(\"profitInput\").value=r.profit.toFixed(2);\n    if(document.activeElement!==$(\"finalPrice\")) $(\"finalPrice\").value=r.price.toFixed(2);\n    var rows=[[\"Filament\",r.cFil],[\"Electricity\",r.cElec],[\"Maintenance\",r.cMaint],[\"Depreciation\",r.cDep],[\"Failure \/ waste buffer\",r.cBuffer],[\"Modeling \/ file prep\",r.cModel],[\"Post-processing\",r.cPost]];\n    var html=\"\";rows.forEach(function(row){html+='<tr><td>'+row[0]+'<\/td><td>'+eur(row[1])+'<\/td><\/tr>';});\n    html+='<tr class=\"total\"><td>Total cost<\/td><td>'+eur(r.totalCost)+'<\/td><\/tr>';\n    $(\"breakdown\").innerHTML=html;\n    persistSettings();\n  }\n\n  \/\/ ---------- estimates ----------\n  function snapshot(){var ids=[\"machine\",\"watts\",\"printH\",\"printM\",\"buffer\",\"filName\",\"filPrice\",\"grams\",\"modelH\",\"modelRate\",\"postH\",\"postRate\",\"elec\",\"maint\",\"dep\"];var v={};ids.forEach(function(id){v[id]=$(id).value;});v.markup=markupPct;return v;}\n  function restore(vals){Object.keys(vals).forEach(function(id){if($(id)&&id!==\"markup\")$(id).value=vals[id];});if(vals.markup!=null){markupPct=parseFloat(vals.markup)||0;}recalc();}\n  function renderEstimates(){\n    var box=$(\"savedList\");\n    if(!estimates.length){box.innerHTML='<p class=\"empty\">No saved estimates yet.<\/p>';return;}\n    box.innerHTML=\"\";\n    estimates.forEach(function(e,i){\n      var d=document.createElement(\"div\");d.className=\"saved-item\";\n      d.innerHTML='<div><div>'+esc(e.label)+'<\/div><div class=\"meta\">'+eur(e.price)+' \u00b7 '+esc(e.date)+'<\/div><\/div><div class=\"saved-actions\"><button class=\"btn sm ghost\" data-load=\"'+i+'\">Load<\/button><button class=\"btn sm danger\" data-del=\"'+i+'\">Delete<\/button><\/div>';\n      box.appendChild(d);\n    });\n    box.querySelectorAll(\"[data-load]\").forEach(function(b){b.addEventListener(\"click\",function(){restore(estimates[+b.getAttribute(\"data-load\")].vals);});});\n    box.querySelectorAll(\"[data-del]\").forEach(function(b){b.addEventListener(\"click\",function(){delEstimate(+b.getAttribute(\"data-del\"));});});\n  }\n  $(\"saveEst\").addEventListener(\"click\",function(){\n    var r=compute();var label=$(\"estName\").value.trim()||(\"Estimate \"+(estimates.length+1));\n    var d=new Date();var date=d.getFullYear()+\"-\"+String(d.getMonth()+1).padStart(2,\"0\")+\"-\"+String(d.getDate()).padStart(2,\"0\");\n    var rec={label:label,price:r.price,date:date,vals:snapshot()};\n    if(useServer){\n      api('items',{method:'POST',body:JSON.stringify({kind:'estimate',label:label,data:{price:r.price,date:date,vals:rec.vals}})}).then(function(row){rec.id=row.id;estimates.unshift(rec);renderEstimates();$(\"estName\").value=\"\";}).catch(apiErr);\n    }else{estimates.unshift(rec);lsSave(LS.est,estimates);renderEstimates();$(\"estName\").value=\"\";}\n  });\n  function delEstimate(i){\n    var e=estimates[i];if(!e)return;\n    if(useServer&&e.id){api('items\/'+e.id,{method:'DELETE'}).then(function(){estimates.splice(i,1);renderEstimates();}).catch(apiErr);}\n    else{estimates.splice(i,1);lsSave(LS.est,estimates);renderEstimates();}\n  }\n\n  \/\/ ---------- import from browser ----------\n  $(\"importBtn\").addEventListener(\"click\",function(ev){\n    ev.preventDefault();\n    var lf=lsLoad(LS.fil,[]),le=lsLoad(LS.est,[]);\n    if(!lf.length&&!le.length){alert(\"Nothing saved in this browser to import.\");return;}\n    if(!confirm(\"Import \"+lf.length+\" filament(s) and \"+le.length+\" estimate(s) from this browser into the site database?\"))return;\n    var chain=Promise.resolve();\n    lf.forEach(function(f){chain=chain.then(function(){return api('items',{method:'POST',body:JSON.stringify({kind:'filament',label:f.name,data:{price:f.price}})});});});\n    le.forEach(function(e){chain=chain.then(function(){return api('items',{method:'POST',body:JSON.stringify({kind:'estimate',label:e.label,data:{price:e.price,date:e.date,vals:e.vals}})});});});\n    chain.then(function(){return loadAll();}).then(function(){renderFilaments();renderEstimates();alert(\"Import complete.\");}).catch(apiErr);\n  });\n\n  \/\/ ---------- CSV export ----------\n  function pn(v){var n=parseFloat(v);return isNaN(n)?0:n;}\n  function computeVals(v){\n    v=v||{};\n    var printHrs=pn(v.printH)+pn(v.printM)\/60;\n    var cElec=(pn(v.watts)\/1000)*printHrs*pn(v.elec);\n    var cFil=(pn(v.grams)\/1000)*pn(v.filPrice);\n    var printCost=cElec+cFil+printHrs*pn(v.maint)+printHrs*pn(v.dep);\n    var cBuffer=printCost*(pn(v.buffer)\/100);\n    var totalCost=printCost+cBuffer+pn(v.modelH)*pn(v.modelRate)+pn(v.postH)*pn(v.postRate);\n    var profit=totalCost*(pn(v.markup)\/100);\n    return {totalCost:totalCost,profit:profit,price:totalCost+profit};\n  }\n  function csvCell(x){x=String(x==null?\"\":x);if(\/[\",\\n\\r]\/.test(x))x='\"'+x.replace(\/\"\/g,'\"\"')+'\"';return x;}\n  function exportCSV(){\n    if(!estimates.length){alert(\"No estimates to export.\");return;}\n    var headers=[\"Label\",\"Date\",\"Printer W\",\"Print h\",\"Print m\",\"Grams\",\"Filament EUR\/kg\",\"Markup %\",\"Total cost EUR\",\"Profit EUR\",\"Selling price EUR\"];\n    var lines=[headers];\n    estimates.forEach(function(e){var v=e.vals||{};var c=computeVals(v);\n      lines.push([e.label,e.date,v.watts||\"\",v.printH||\"\",v.printM||\"\",v.grams||\"\",v.filPrice||\"\",v.markup||\"\",c.totalCost.toFixed(2),c.profit.toFixed(2),c.price.toFixed(2)]);\n    });\n    var csv=lines.map(function(r){return r.map(csvCell).join(\",\");}).join(\"\\r\\n\");\n    var blob=new Blob([\"\ufeff\"+csv],{type:\"text\/csv;charset=utf-8;\"});\n    var a=document.createElement(\"a\");a.href=URL.createObjectURL(blob);a.download=\"3d-print-estimates.csv\";\n    document.body.appendChild(a);a.click();document.body.removeChild(a);URL.revokeObjectURL(a.href);\n  }\n  $(\"exportCsv\").addEventListener(\"click\",exportCSV);\n\n  function apiErr(e){console.error(\"[P3DC]\",e);alert(\"Could not reach the site database. Make sure you're logged in as an admin. Changes were not saved.\");}\n  function esc(s){return String(s).replace(\/[&<>\"']\/g,function(c){return {\"&\":\"&amp;\",\"<\":\"&lt;\",\">\":\"&gt;\",'\"':\"&quot;\",\"'\":\"&#39;\"}[c];});}\n\n  [\"watts\",\"printH\",\"printM\",\"buffer\",\"filPrice\",\"grams\",\"modelH\",\"modelRate\",\"postH\",\"postRate\",\"elec\",\"maint\",\"dep\"].forEach(function(id){$(id).addEventListener(\"input\",recalc);});\n  \/\/ profit controls (two-way): slider sets markup %, exact-profit field back-solves markup from cost\n  function computeCost(){var r=compute();return r.totalCost;}\n  $(\"markup\").addEventListener(\"input\",function(){markupPct=+$(\"markup\").value;recalc();});\n  $(\"profitInput\").addEventListener(\"input\",function(){\n    var tc=computeCost();var pv=parseFloat($(\"profitInput\").value);if(isNaN(pv))pv=0;\n    markupPct=tc>0?(pv\/tc*100):0;recalc();\n  });\n  $(\"finalPrice\").addEventListener(\"input\",function(){\n    var tc=computeCost();var pv=parseFloat($(\"finalPrice\").value);if(isNaN(pv))pv=0;\n    markupPct=tc>0?((pv-tc)\/tc*100):0;recalc();\n  });\n\n  setSyncChip();\n  loadAll().then(function(){setSyncChip();applySettings();renderFilaments();renderEstimates();recalc();});\n})();\n<\/script><\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_header_footer","meta":{"footnotes":""},"class_list":["post-3283","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/nedprojects.com\/en\/wp-json\/wp\/v2\/pages\/3283","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nedprojects.com\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/nedprojects.com\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/nedprojects.com\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/nedprojects.com\/en\/wp-json\/wp\/v2\/comments?post=3283"}],"version-history":[{"count":10,"href":"https:\/\/nedprojects.com\/en\/wp-json\/wp\/v2\/pages\/3283\/revisions"}],"predecessor-version":[{"id":3295,"href":"https:\/\/nedprojects.com\/en\/wp-json\/wp\/v2\/pages\/3283\/revisions\/3295"}],"wp:attachment":[{"href":"https:\/\/nedprojects.com\/en\/wp-json\/wp\/v2\/media?parent=3283"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}