{"id":25081,"date":"2026-11-10T02:42:59","date_gmt":"2026-11-10T02:42:59","guid":{"rendered":"https:\/\/www.mechstream.com\/?p=25081"},"modified":"2026-11-10T03:08:35","modified_gmt":"2026-11-10T03:08:35","slug":"sheet-metal-nesting-efficiency-calculator","status":"publish","type":"post","link":"https:\/\/www.mechstream.com\/ar\/sheet-metal-nesting-efficiency-calculator\/","title":{"rendered":"Sheet Metal Nesting Efficiency Calculator"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">The Sheet Metal Nesting Efficiency Calculator estimates how many parts can be arranged on a sheet and calculates material utilization, sheet requirements, and potential scrap. Enter sheet and part dimensions, quantity, spacing, and edge margins to compare basic rectangular layouts, including rotated parts. Use this calculator to plan sheet metal cutting jobs and make more informed material usage estimates.<\/p>\n\n\n\n<!-- Sheet Metal Nesting Calculator: paste this whole block into a WordPress \"Custom HTML\" block -->\n<snc-nesting-calculator style=\"display:block;width:100%;\"><\/snc-nesting-calculator>\n<template id=\"snc-nesting-calculator-tpl\"><style>\n  :host {\n    --bg: #f3f5f8;\n    --surface: #ffffff;\n    --surface-2: #f7f9fc;\n    --ink: #1b1f23;\n    --ink-2: #4a5259;\n    --line: #d6dce5;\n    --line-strong: #98a2b3;\n    --accent: #2563eb;\n    --accent-ink: #ffffff;\n    --accent-soft: #e7effd;\n    --error: #b42318;\n    --error-soft: #fdecea;\n    --part: #2563eb;\n    --part-fill: rgba(37, 99, 235, 0.16);\n    --sheet: #c8d0dc;\n    --usable: #eef2f8;\n    --focus: #f0a500;\n    --radius: 10px;\n    --mono: ui-monospace, \"SFMono-Regular\", Menlo, Consolas, \"Liberation Mono\", monospace;\n    --sans: system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif;\n  }\n  :host { all: initial; display: block; width: 100%; color: var(--ink); font: 16px\/1.5 var(--sans); text-align: left; -webkit-text-size-adjust: 100%; }\n  *, *::before, *::after { box-sizing: border-box; }\n  [hidden] { display: none !important; }\n  .wrap { all: initial; display: block; container-type: inline-size; container-name: snc; background: var(--bg); border-radius: 12px; padding: 24px 16px; color: var(--ink); font: 16px\/1.5 var(--sans); }\n  .layout { display: grid; grid-template-columns: minmax(0, 1fr); gap: 20px; max-width: 760px; margin: 0 auto; }\n  .card {\n    background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);\n    padding: 20px;\n  }\n  fieldset { border: 0; padding: 0; margin: 0 0 22px; min-width: 0; }\n  legend {\n    font: 600 12px\/1 var(--mono); letter-spacing: .1em; text-transform: uppercase; color: var(--ink-2);\n    padding: 0 0 12px; width: 100%; border-bottom: 1px solid var(--line); margin-bottom: 14px;\n  }\n  .unit-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 16px; }\n  .unit-row label { font-weight: 600; }\n  .grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px 14px; }\n  @container snc (max-width: 520px) { .grid { grid-template-columns: minmax(0, 1fr); } }\n  .field label { display: block; font-weight: 600; font-size: 14.88px; margin-bottom: 6px; }\n  .control {\n    display: flex; align-items: stretch; border: 1px solid var(--line-strong); border-radius: 8px;\n    background: var(--surface); overflow: hidden;\n  }\n  .control:focus-within { outline: 3px solid var(--focus); outline-offset: 1px; }\n  .control input {\n    flex: 1; min-width: 0; border: 0; background: transparent; color: var(--ink);\n    font: 500 16px\/1.2 var(--mono); padding: 10px 12px;\n  }\n  .control input:focus { outline: none; }\n  .control select {\n    border: 0; border-left: 1px solid var(--line); background: var(--surface-2); color: var(--ink);\n    font: 600 14.4px\/1 var(--mono); padding: 0 10px; cursor: pointer;\n  }\n  .control select:focus { outline: none; }\n  .suffix {\n    display: flex; align-items: center; padding: 0 12px; background: var(--surface-2);\n    border-left: 1px solid var(--line); font: 600 14.4px\/1 var(--mono); color: var(--ink-2);\n  }\n  .field.invalid .control { border-color: var(--error); box-shadow: 0 0 0 1px var(--error); }\n  .hint { margin: 6px 0 0; font-size: 13.12px; color: var(--ink-2); }\n  .err-msg { margin: 6px 0 0; font-size: 13.6px; color: var(--error); font-weight: 600; }\n  .err-msg:empty { display: none; }\n  .check {\n    display: flex; gap: 10px; align-items: center; margin-top: 16px; font-weight: 600; cursor: pointer;\n    padding: 10px 12px; border: 1px solid var(--line); border-radius: 8px; background: var(--surface-2);\n  }\n  .check input { width: 18px; height: 18px; accent-color: var(--accent); margin: 0; }\n  .check:focus-within { outline: 3px solid var(--focus); outline-offset: 1px; }\n  .actions { display: flex; justify-content: center; }\n  button {\n    font: 600 16px\/1 var(--sans); border-radius: 8px; padding: 13px 18px; cursor: pointer;\n    border: 1px solid transparent;\n  }\n  button:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }\n  .btn-primary { background: #2563eb; color: #ffffff; min-width: 180px; padding: 13px 36px; }\n  .btn-primary:hover { background: #1d4ed8; }\n  .error-summary {\n    border: 1px solid var(--error); background: var(--error-soft); color: var(--ink);\n    border-radius: 8px; padding: 12px 14px; margin-bottom: 18px;\n  }\n  .error-summary h2 { font-size: 16px; margin: 0 0 6px; color: var(--error); }\n  .error-summary ul { margin: 0; padding-left: 20px; }\n  .error-summary a { color: var(--error); font-weight: 600; }\n  \/* Results *\/\n  .results h2 { font-size: 17.6px; margin: 0 0 14px; }\n  .placeholder { color: var(--ink-2); margin: 0; padding: 18px; border: 1px dashed var(--line-strong); border-radius: 8px; text-align: center; }\n  .stale-note {\n    font-size: 14.08px; background: var(--accent-soft); border-radius: 8px; padding: 8px 12px; margin: 0 0 12px;\n  }\n  .is-stale .metrics, .is-stale .viz, .is-stale .details { opacity: .45; }\n  .metrics { list-style: none; margin: 0 0 16px; padding: 0; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }\n  .metric { background: var(--surface-2); border: 1px solid var(--line); border-radius: 8px; padding: 12px 14px; min-width: 0; }\n  .metric.hero { grid-column: 1 \/ -1; background: var(--accent-soft); border-color: transparent; }\n  .metric .k { display: block; font-size: 12.8px; color: var(--ink-2); font-weight: 600; }\n  .metric .v { display: block; font: 700 21.6px\/1.25 var(--mono); margin-top: 4px; overflow-wrap: anywhere; }\n  .metric.hero .v { font-size: 32px; color: var(--accent); }\n  .metric .u { font-size: 12.8px; font-weight: 600; color: var(--ink-2); margin-left: 4px; }\n  .bar { height: 8px; background: var(--line); border-radius: 99px; overflow: hidden; margin-top: 10px; }\n  .bar > span { display: block; height: 100%; background: var(--accent); border-radius: 99px; }\n  .viz { margin: 0 0 14px; }\n  .viz figcaption { font-size: 13.12px; color: var(--ink-2); margin-top: 6px; }\n  .viz svg { width: 100%; height: auto; display: block; max-height: 360px; }\n  .legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 12.8px; color: var(--ink-2); margin-top: 6px; }\n  .legend i { display: inline-block; width: 12px; height: 12px; border-radius: 2px; vertical-align: -2px; margin-right: 5px; border: 1px solid var(--line-strong); }\n  table.details { width: 100%; border-collapse: collapse; font-size: 14.4px; }\n  table.details th, table.details td { padding: 7px 0; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; }\n  table.details td { text-align: right; font-family: var(--mono); }\n  table.details th { font-weight: 500; color: var(--ink-2); padding-right: 10px; width: 45%; }\n  .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }\n  @media (prefers-reduced-motion: no-preference) { .bar > span { transition: width .4s ease; } }\n<\/style>\n<div class=\"wrap\">\n  <div class=\"layout\">\n    <section class=\"card\" aria-labelledby=\"inputs-title\">\n      <h2 id=\"inputs-title\" class=\"sr-only\">Calculator inputs<\/h2>\n      <div id=\"error-summary\" class=\"error-summary\" role=\"alert\" tabindex=\"-1\" hidden>\n        <h2>Please fix the following:<\/h2>\n        <ul id=\"error-list\"><\/ul>\n      <\/div>\n      <form id=\"calc\" novalidate>\n        <fieldset>\n          <legend>Sheet settings<\/legend>\n          <div class=\"grid\">\n            <div class=\"field\" id=\"f-sheet-width\">\n              <label for=\"sheet-width\">Sheet width<\/label>\n              <div class=\"control\">\n                <input id=\"sheet-width\" type=\"number\" min=\"0\" step=\"any\" inputmode=\"decimal\" required aria-describedby=\"sheet-width-err\">\n                <select id=\"sheet-width-unit\" aria-label=\"Sheet width unit\">\n                  <option value=\"mm\">mm<\/option>\n                  <option value=\"cm\">cm<\/option>\n                  <option value=\"in\">in<\/option>\n                <\/select>\n              <\/div>\n              <p class=\"err-msg\" id=\"sheet-width-err\"><\/p>\n            <\/div>\n            <div class=\"field\" id=\"f-sheet-length\">\n              <label for=\"sheet-length\">Sheet length<\/label>\n              <div class=\"control\">\n                <input id=\"sheet-length\" type=\"number\" min=\"0\" step=\"any\" inputmode=\"decimal\" required aria-describedby=\"sheet-length-err\">\n                <select id=\"sheet-length-unit\" aria-label=\"Sheet length unit\">\n                  <option value=\"mm\">mm<\/option>\n                  <option value=\"cm\">cm<\/option>\n                  <option value=\"in\">in<\/option>\n                <\/select>\n              <\/div>\n              <p class=\"err-msg\" id=\"sheet-length-err\"><\/p>\n            <\/div>\n            <div class=\"field\" id=\"f-edge-margin\" style=\"grid-column: 1 \/ -1;\">\n              <label for=\"edge-margin\">Edge margin on each side<\/label>\n              <div class=\"control\">\n                <input id=\"edge-margin\" type=\"number\" min=\"0\" step=\"any\" inputmode=\"decimal\" value=\"0\" aria-describedby=\"edge-margin-hint edge-margin-err\">\n                <span class=\"suffix\" data-unit>mm<\/span>\n              <\/div>\n              <p class=\"hint\" id=\"edge-margin-hint\">Uses the same unit as the sheet dimensions.<\/p>\n              <p class=\"err-msg\" id=\"edge-margin-err\"><\/p>\n            <\/div>\n          <\/div>\n        <\/fieldset>\n        <fieldset>\n          <legend>Part settings<\/legend>\n          <div class=\"grid\">\n            <div class=\"field\" id=\"f-part-width\">\n              <label for=\"part-width\">Part width<\/label>\n              <div class=\"control\">\n                <input id=\"part-width\" type=\"number\" min=\"0\" step=\"any\" inputmode=\"decimal\" required aria-describedby=\"part-width-err\">\n                <span class=\"suffix\" data-unit>mm<\/span>\n              <\/div>\n              <p class=\"err-msg\" id=\"part-width-err\"><\/p>\n            <\/div>\n            <div class=\"field\" id=\"f-part-length\">\n              <label for=\"part-length\">Part length<\/label>\n              <div class=\"control\">\n                <input id=\"part-length\" type=\"number\" min=\"0\" step=\"any\" inputmode=\"decimal\" required aria-describedby=\"part-length-err\">\n                <span class=\"suffix\" data-unit>mm<\/span>\n              <\/div>\n              <p class=\"err-msg\" id=\"part-length-err\"><\/p>\n            <\/div>\n            <div class=\"field\" id=\"f-part-quantity\">\n              <label for=\"part-quantity\">Required quantity<\/label>\n              <div class=\"control\">\n                <input id=\"part-quantity\" type=\"number\" min=\"1\" step=\"1\" inputmode=\"numeric\" value=\"1\" required aria-describedby=\"part-quantity-err\">\n                <span class=\"suffix\">pcs<\/span>\n              <\/div>\n              <p class=\"err-msg\" id=\"part-quantity-err\"><\/p>\n            <\/div>\n            <div class=\"field\" id=\"f-part-gap\">\n              <label for=\"part-gap\">Gap between parts<\/label>\n              <div class=\"control\">\n                <input id=\"part-gap\" type=\"number\" min=\"0\" step=\"any\" inputmode=\"decimal\" value=\"0\" aria-describedby=\"part-gap-err\">\n                <span class=\"suffix\" data-unit>mm<\/span>\n              <\/div>\n              <p class=\"err-msg\" id=\"part-gap-err\"><\/p>\n            <\/div>\n          <\/div>\n          <label class=\"check\" for=\"allow-rotation\">\n            <input id=\"allow-rotation\" type=\"checkbox\" checked>\n            Allow parts to rotate by 90\u00b0\n          <\/label>\n          <p class=\"hint\">All dimensions, the margin and the gap use the sheet unit selected above, so units always stay consistent.<\/p>\n        <\/fieldset>\n        <div class=\"actions\">\n          <button type=\"submit\" class=\"btn-primary\">Calculate<\/button>\n        <\/div>\n      <\/form>\n    <\/section>\n    <section class=\"card results\" aria-labelledby=\"results-title\" hidden>\n      <h2 id=\"results-title\">Results<\/h2>\n      <div id=\"results\" aria-live=\"polite\" role=\"status\">\n        <p class=\"placeholder\">Enter your sheet and part sizes, then press <strong>Calculate<\/strong>.<\/p>\n      <\/div>\n    <\/section>\n  <\/div>\n<\/div><\/template>\n<script>\n(function () {\n  \"use strict\";\n  var tpl = document.getElementById(\"snc-nesting-calculator-tpl\");\n  if (!tpl || !(\"attachShadow\" in Element.prototype)) return;\n  var hosts = document.querySelectorAll(\"snc-nesting-calculator\");\n  for (var i = 0; i < hosts.length; i++) { if (!hosts[i].shadowRoot) init(hosts[i]); }\n  function init(host) {\n  var root = host.attachShadow({ mode: \"open\" });\n  root.appendChild(tpl.content.cloneNode(true));\n  var form = root.getElementById(\"calc\");\n  var resultsEl = root.getElementById(\"results\");\n  var resultsCard = resultsEl.parentElement;\n  var summary = root.getElementById(\"error-summary\");\n  var summaryList = root.getElementById(\"error-list\");\n  var unitW = root.getElementById(\"sheet-width-unit\");\n  var unitL = root.getElementById(\"sheet-length-unit\");\n  var hasResult = false;\n  var PLACEHOLDER = resultsEl.innerHTML;\n  \/* ---------- Units: keep both selects in sync so every value shares one unit ---------- *\/\n  function currentUnit() { return unitW.value; }\n  function syncUnits(source) {\n    var u = source.value;\n    unitW.value = u; unitL.value = u;\n    root.querySelectorAll(\"[data-unit]\").forEach(function (el) { el.textContent = u; });\n  }\n  unitW.addEventListener(\"change\", function () { syncUnits(unitW); markStale(); });\n  unitL.addEventListener(\"change\", function () { syncUnits(unitL); markStale(); });\n  \/* ---------- Formatting ---------- *\/\n  var nf2 = new Intl.NumberFormat(\"en-US\", { minimumFractionDigits: 2, maximumFractionDigits: 2 });\n  var nf0 = new Intl.NumberFormat(\"en-US\", { maximumFractionDigits: 0 });\n  function f2(n) { return nf2.format(Math.round((n + Number.EPSILON) * 100) \/ 100); }\n  function fi(n) { return nf0.format(n); }\n  function esc(s) { return String(s).replace(\/[&#038;<>\"]\/g, function (c) { return { \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\" }[c]; }); }\n  \/* ---------- Validation helpers ---------- *\/\n  var FIELDS = {\n    \"sheet-width\": \"Sheet width\",\n    \"sheet-length\": \"Sheet length\",\n    \"edge-margin\": \"Edge margin\",\n    \"part-width\": \"Part width\",\n    \"part-length\": \"Part length\",\n    \"part-quantity\": \"Required quantity\",\n    \"part-gap\": \"Gap between parts\"\n  };\n  function clearErrors() {\n    Object.keys(FIELDS).forEach(function (id) {\n      var input = root.getElementById(id);\n      input.removeAttribute(\"aria-invalid\");\n      root.getElementById(\"f-\" + id).classList.remove(\"invalid\");\n      root.getElementById(id + \"-err\").textContent = \"\";\n    });\n    summary.hidden = true;\n    summaryList.innerHTML = \"\";\n  }\n  function readNumber(id) {\n    var raw = root.getElementById(id).value.trim();\n    if (raw === \"\") return { blank: true, value: NaN };\n    var v = Number(raw);\n    return { blank: false, value: v };\n  }\n  function showErrors(errors) {\n    \/\/ errors: [{id|null, msg}]\n    summaryList.innerHTML = \"\";\n    errors.forEach(function (e) {\n      var li = document.createElement(\"li\");\n      if (e.id) {\n        var input = root.getElementById(e.id);\n        input.setAttribute(\"aria-invalid\", \"true\");\n        root.getElementById(\"f-\" + e.id).classList.add(\"invalid\");\n        var slot = root.getElementById(e.id + \"-err\");\n        if (!slot.textContent) slot.textContent = e.msg;\n        var a = document.createElement(\"a\");\n        a.href = \"#\" + e.id;\n        a.textContent = e.msg;\n        a.addEventListener(\"click\", function (ev) { ev.preventDefault(); input.focus(); });\n        li.appendChild(a);\n      } else {\n        li.textContent = e.msg;\n      }\n      summaryList.appendChild(li);\n    });\n    summary.hidden = false;\n    summary.focus();\n  }\n  \/* ---------- Core calculation ---------- *\/\n  function fit(usableW, usableL, pw, pl, gap) {\n    var perRow = Math.floor((usableW + gap) \/ (pw + gap) + 1e-9);\n    var rows = Math.floor((usableL + gap) \/ (pl + gap) + 1e-9);\n    perRow = Math.max(0, perRow); rows = Math.max(0, rows);\n    return { perRow: perRow, rows: rows, count: perRow * rows, pw: pw, pl: pl };\n  }\n  function calculate() {\n    clearErrors();\n    var errors = [];\n    var vals = {};\n    \/\/ Required positive values\n    [\"sheet-width\", \"sheet-length\", \"part-width\", \"part-length\"].forEach(function (id) {\n      var r = readNumber(id);\n      if (r.blank) errors.push({ id: id, msg: FIELDS[id] + \" is required.\" });\n      else if (!isFinite(r.value)) errors.push({ id: id, msg: FIELDS[id] + \" must be a number.\" });\n      else if (r.value <= 0) errors.push({ id: id, msg: FIELDS[id] + \" must be greater than zero.\" });\n      vals[id] = r.value;\n    });\n    \/\/ Quantity: positive whole number\n    (function () {\n      var id = \"part-quantity\", r = readNumber(id);\n      if (r.blank) errors.push({ id: id, msg: \"Required quantity is required.\" });\n      else if (!isFinite(r.value) || r.value <= 0) errors.push({ id: id, msg: \"Required quantity must be greater than zero.\" });\n      else if (!Number.isInteger(r.value)) errors.push({ id: id, msg: \"Required quantity must be a whole number.\" });\n      else if (r.value > 1e9) errors.push({ id: id, msg: \"Required quantity is too large.\" });\n      vals[id] = r.value;\n    })();\n    \/\/ Margin and gap: zero allowed, negative or blank not\n    [\"edge-margin\", \"part-gap\"].forEach(function (id) {\n      var r = readNumber(id);\n      if (r.blank) errors.push({ id: id, msg: FIELDS[id] + \" is required (enter 0 for none).\" });\n      else if (!isFinite(r.value)) errors.push({ id: id, msg: FIELDS[id] + \" must be a number.\" });\n      else if (r.value < 0) errors.push({ id: id, msg: FIELDS[id] + \" cannot be negative.\" });\n      vals[id] = r.value;\n    });\n    if (unitW.value !== unitL.value) {\n      errors.push({ id: null, msg: \"Sheet width and length must use the same unit.\" });\n    }\n    if (errors.length) { showErrors(errors); renderEmpty(); return; }\n    var unit = currentUnit();\n    var sw = vals[\"sheet-width\"], sl = vals[\"sheet-length\"];\n    var m = vals[\"edge-margin\"], gap = vals[\"part-gap\"];\n    var pw = vals[\"part-width\"], pl = vals[\"part-length\"], qty = vals[\"part-quantity\"];\n    var rotate = root.getElementById(\"allow-rotation\").checked;\n    var uw = sw - 2 * m, ul = sl - 2 * m;\n    if (uw <= 0 || ul <= 0) {\n      showErrors([{ id: \"edge-margin\", msg: \"Edge margin is too large: it leaves no usable sheet area (usable size \" + f2(Math.max(uw, 0)) + \" \u00d7 \" + f2(Math.max(ul, 0)) + \" \" + unit + \").\" }]);\n      renderEmpty(); return;\n    }\n    var orig = fit(uw, ul, pw, pl, gap);\n    var rot = rotate ? fit(uw, ul, pl, pw, gap) : null;\n    var useRot = !!(rot &#038;&#038; rot.count > orig.count);\n    var best = useRot ? rot : orig;\n    if (best.count === 0) {\n      var msg = \"The part (\" + f2(pw) + \" \u00d7 \" + f2(pl) + \" \" + unit + \") does not fit on the usable sheet area (\" +\n        f2(uw) + \" \u00d7 \" + f2(ul) + \" \" + unit + \")\" + (rotate ? \" in either orientation.\" : \". Try allowing rotation.\");\n      showErrors([{ id: \"part-width\", msg: msg }]);\n      root.getElementById(\"f-part-length\").classList.add(\"invalid\");\n      root.getElementById(\"part-length\").setAttribute(\"aria-invalid\", \"true\");\n      renderEmpty(); return;\n    }\n    var pps = best.count;                       \/\/ > 0, so no division by zero\n    var sheets = Math.ceil(qty \/ pps);\n    var sheetArea = sw * sl;\n    var totalSheetArea = sheetArea * sheets;\n    var partArea = pw * pl;\n    var totalPartArea = partArea * qty;\n    var util = totalSheetArea > 0 ? (totalPartArea \/ totalSheetArea) * 100 : 0;\n    var scrap = totalSheetArea - totalPartArea;\n    var lastSheet = qty - (sheets - 1) * pps;\n    var fullSheetUtil = (pps * partArea \/ sheetArea) * 100;\n    renderResults({\n      unit: unit, sw: sw, sl: sl, m: m, gap: gap, uw: uw, ul: ul, qty: qty,\n      best: best, orig: orig, rot: rot, useRot: useRot, rotate: rotate,\n      pps: pps, sheets: sheets, util: util, scrap: scrap,\n      totalSheetArea: totalSheetArea, totalPartArea: totalPartArea,\n      lastSheet: lastSheet, fullSheetUtil: fullSheetUtil\n    });\n  }\n  \/* ---------- Rendering ---------- *\/\n  function renderEmpty() {\n    hasResult = false;\n    resultsCard.hidden = true;\n    resultsCard.classList.remove(\"is-stale\");\n    resultsEl.innerHTML = '<p class=\"placeholder\">No results \u2014 correct the highlighted inputs and calculate again.<\/p>';\n  }\n  function sheetSvg(r) {\n    var VB = 600;\n    var scale = VB \/ Math.max(r.sw, r.sl);\n    var W = r.sw * scale, H = r.sl * scale;\n    var p = r.best, g = r.gap * scale, mm = r.m * scale;\n    var pw = p.pw * scale, pl = p.pl * scale;\n    var parts = \"\";\n    var MAX_DRAW = 2500;\n    if (p.count <= MAX_DRAW) {\n      for (var row = 0; row < p.rows; row++) {\n        for (var col = 0; col < p.perRow; col++) {\n          var x = mm + col * (pw + g), y = mm + row * (pl + g);\n          parts += '<rect x=\"' + x.toFixed(2) + '\" y=\"' + y.toFixed(2) + '\" width=\"' + pw.toFixed(2) + '\" height=\"' + pl.toFixed(2) + '\" \/>';\n        }\n      }\n    } else {\n      var bw = p.perRow * pw + (p.perRow - 1) * g, bh = p.rows * pl + (p.rows - 1) * g;\n      parts += '<rect x=\"' + mm + '\" y=\"' + mm + '\" width=\"' + bw.toFixed(2) + '\" height=\"' + bh.toFixed(2) + '\" \/>';\n    }\n    var sw = Math.max(0.5, Math.min(1.5, Math.min(pw, pl) \/ 12));\n    return '<svg viewBox=\"-4 -4 ' + (W + 8).toFixed(2) + ' ' + (H + 8).toFixed(2) + '\" role=\"img\" aria-label=\"Layout of one full sheet: ' +\n      p.perRow + ' parts per row by ' + p.rows + ' rows.\">' +\n      '<rect x=\"0\" y=\"0\" width=\"' + W.toFixed(2) + '\" height=\"' + H.toFixed(2) + '\" fill=\"var(--sheet)\" rx=\"2\" \/>' +\n      '<rect x=\"' + mm.toFixed(2) + '\" y=\"' + mm.toFixed(2) + '\" width=\"' + (r.uw * scale).toFixed(2) + '\" height=\"' + (r.ul * scale).toFixed(2) + '\" fill=\"var(--usable)\" \/>' +\n      '<g fill=\"var(--part-fill)\" stroke=\"var(--part)\" stroke-width=\"' + sw.toFixed(2) + '\">' + parts + '<\/g><\/svg>';\n  }\n  function renderResults(r) {\n    hasResult = true;\n    resultsCard.classList.remove(\"is-stale\");\n    var u = r.unit, a = u + \"\u00b2\";\n    var orientLabel = r.useRot ? \"Rotated 90\u00b0\" : \"Original\";\n    var orientNote = !r.rotate ? \"Rotation not allowed\" :\n      (r.rot.count === r.orig.count ? \"Both orientations fit \" + fi(r.orig.count) + \"; original kept\" :\n        \"Original fits \" + fi(r.orig.count) + \", rotated fits \" + fi(r.rot.count));\n    var utilClamped = Math.max(0, Math.min(100, r.util));\n    var html =\n      '<ul class=\"metrics\">' +\n        '<li class=\"metric hero\"><span class=\"k\">Estimated material utilization<\/span>' +\n          '<span class=\"v\">' + f2(r.util) + '<span class=\"u\">%<\/span><\/span>' +\n          '<div class=\"bar\" aria-hidden=\"true\"><span style=\"width:' + utilClamped.toFixed(2) + '%\"><\/span><\/div><\/li>' +\n        '<li class=\"metric\"><span class=\"k\">Estimated parts per sheet<\/span><span class=\"v\">' + fi(r.pps) + '<\/span><\/li>' +\n        '<li class=\"metric\"><span class=\"k\">Estimated sheets required<\/span><span class=\"v\">' + fi(r.sheets) + '<\/span><\/li>' +\n        '<li class=\"metric\"><span class=\"k\">Estimated scrap area<\/span><span class=\"v\">' + f2(r.scrap) + '<span class=\"u\">' + esc(a) + '<\/span><\/span><\/li>' +\n        '<li class=\"metric\"><span class=\"k\">Selected orientation<\/span><span class=\"v\">' + orientLabel + '<\/span><\/li>' +\n      '<\/ul>' +\n      '<figure class=\"viz\">' + sheetSvg(r) +\n        '<figcaption>One full sheet: ' + fi(r.best.perRow) + ' per row \u00d7 ' + fi(r.best.rows) + ' rows, part placed as ' +\n          f2(r.best.pw) + ' \u00d7 ' + f2(r.best.pl) + ' ' + esc(u) + '.' +\n          (r.best.count > 2500 ? ' (Too many parts to draw individually; nested block shown.)' : '') + '<\/figcaption>' +\n        '<div class=\"legend\" aria-hidden=\"true\"><span><i style=\"background:var(--sheet)\"><\/i>Edge margin<\/span>' +\n          '<span><i style=\"background:var(--usable)\"><\/i>Usable area<\/span>' +\n          '<span><i style=\"background:var(--part-fill);border-color:var(--part)\"><\/i>Parts<\/span><\/div>' +\n      '<\/figure>' +\n      '<table class=\"details\"><caption class=\"sr-only\">Calculation details<\/caption><tbody>' +\n        row(\"Usable sheet area\", f2(r.uw) + \" \u00d7 \" + f2(r.ul) + \" \" + u) +\n        row(\"Layout\", fi(r.best.perRow) + \" per row \u00d7 \" + fi(r.best.rows) + \" rows\") +\n        row(\"Orientation check\", orientNote) +\n        row(\"Parts on last sheet\", fi(r.lastSheet) + \" of \" + fi(r.pps)) +\n        row(\"Full-sheet utilization\", f2(r.fullSheetUtil) + \"%\") +\n        row(\"Total sheet area\", f2(r.totalSheetArea) + \" \" + a) +\n        row(\"Total part area\", f2(r.totalPartArea) + \" \" + a) +\n      '<\/tbody><\/table>';\n    resultsEl.innerHTML = html;\n    var wasHidden = resultsCard.hidden;\n    resultsCard.hidden = false;\n    var rect = resultsCard.getBoundingClientRect();\n    if (wasHidden || rect.top > window.innerHeight || rect.bottom < 0) {\n      var reduce = window.matchMedia &#038;&#038; window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n      resultsCard.scrollIntoView({ behavior: reduce ? \"auto\" : \"smooth\", block: \"start\" });\n    }\n    function row(k, v) { return '<tr><th scope=\"row\">' + esc(k) + '<\/th><td>' + esc(v) + '<\/td><\/tr>'; }\n  }\n  \/* ---------- Stale results: never leave misleading numbers on screen ---------- *\/\n  function markStale() {\n    if (!hasResult || resultsCard.classList.contains(\"is-stale\")) return;\n    resultsCard.classList.add(\"is-stale\");\n    var note = document.createElement(\"p\");\n    note.className = \"stale-note\";\n    note.textContent = \"Inputs have changed. Press Calculate to update these results.\";\n    resultsEl.insertBefore(note, resultsEl.firstChild);\n  }\n  form.addEventListener(\"input\", function (e) {\n    if (e.target && e.target.id) {\n      var fieldWrap = root.getElementById(\"f-\" + e.target.id);\n      if (fieldWrap && fieldWrap.classList.contains(\"invalid\")) {\n        fieldWrap.classList.remove(\"invalid\");\n        e.target.removeAttribute(\"aria-invalid\");\n        root.getElementById(e.target.id + \"-err\").textContent = \"\";\n      }\n    }\n    markStale();\n  });\n  form.addEventListener(\"change\", function (e) { if (e.target.id === \"allow-rotation\") markStale(); });\n  form.addEventListener(\"submit\", function (e) { e.preventDefault(); calculate(); });\n  syncUnits(unitW);\n  }\n})();\n<\/script>\n\n\n\n<div id=\"snc-nest-calc\" class=\"snc-root\">\n<style>\n#snc-nest-calc{--snc-surface:#ffffff;--snc-surface-2:#f8f9fb;--snc-text:#1b1f24;--snc-muted:#59616c;--snc-border:#d4d8de;--snc-accent:#1f5fae;--snc-accent-hover:#174b8c;--snc-accent-soft:#e6eef8;--snc-error:#b42318;--snc-error-soft:#fdecea;--snc-sheet:#e9ecf0;--snc-part:#7aa5d8;--snc-part-stroke:#1f5fae;--snc-focus:0 0 0 3px rgba(31,95,174,.35);\n  display:block;max-width:760px;margin:0 auto;padding:0;color:var(--snc-text);font-family:inherit;font-size:16px;line-height:1.55;text-align:left;}\n#snc-nest-calc,#snc-nest-calc *,#snc-nest-calc *::before,#snc-nest-calc *::after{box-sizing:border-box;}\n#snc-nest-calc .snc-layout{display:grid;grid-template-columns:minmax(0,1fr);gap:20px;margin:0;padding:0;}\n#snc-nest-calc .snc-card{background:var(--snc-surface);border:1px solid var(--snc-border);border-radius:10px;padding:20px;margin:0;box-shadow:none;}\n#snc-nest-calc .snc-card[hidden]{display:none;}\n#snc-nest-calc .snc-h2{font-family:inherit;font-size:1.2rem;font-weight:700;line-height:1.3;margin:0 0 12px;padding:0;color:var(--snc-text);text-transform:none;letter-spacing:normal;border:0;background:transparent;}\n#snc-nest-calc .snc-fieldset{border:0;padding:0;margin:0 0 20px;min-width:0;background:none;}\n#snc-nest-calc .snc-legend{font-weight:650;font-size:1.02rem;padding:0;margin:0 0 12px;color:var(--snc-text);border:0;width:auto;float:none;text-transform:none;background:transparent;}\n#snc-nest-calc .snc-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;}\n@media (max-width:480px){#snc-nest-calc .snc-grid{grid-template-columns:minmax(0,1fr);}}\n#snc-nest-calc .snc-field{display:flex;flex-direction:column;gap:4px;min-width:0;margin:0;}\n#snc-nest-calc .snc-full{grid-column:1\/-1;}\n#snc-nest-calc .snc-label{display:block;font-weight:550;font-size:.93rem;margin:0;padding:0;color:var(--snc-text);text-transform:none;letter-spacing:normal;background:transparent;border:0;}\n#snc-nest-calc .snc-wrap{display:flex;align-items:stretch;min-width:0;margin:0;}\n#snc-nest-calc .snc-input,#snc-nest-calc .snc-select{font-family:inherit;font-size:16px;line-height:1.4;color:var(--snc-text);background:var(--snc-surface-2);border:1px solid var(--snc-border);border-radius:8px 0 0 8px;padding:9px 11px;margin:0;min-height:44px;height:auto;width:100%;min-width:0;max-width:none;box-shadow:none;outline:none;appearance:auto;-webkit-appearance:auto;}\n#snc-nest-calc .snc-select{width:auto;flex:0 0 auto;border-left:0;border-radius:0 8px 8px 0;padding-right:8px;cursor:pointer;}\n#snc-nest-calc .snc-suffix{display:inline-flex;align-items:center;justify-content:center;flex:0 0 auto;padding:0 12px;min-width:52px;margin:0;background:var(--snc-accent-soft);color:var(--snc-muted);border:1px solid var(--snc-border);border-left:0;border-radius:0 8px 8px 0;font-size:.92rem;}\n#snc-nest-calc .snc-input:focus,#snc-nest-calc .snc-select:focus{outline:none;box-shadow:var(--snc-focus);border-color:var(--snc-accent);}\n#snc-nest-calc .snc-input[aria-invalid=\"true\"]{border-color:var(--snc-error);background:var(--snc-error-soft);}\n#snc-nest-calc .snc-hint{display:block;color:var(--snc-muted);font-size:.85rem;margin:0;}\n#snc-nest-calc .snc-err{display:block;color:var(--snc-error);font-size:.85rem;font-weight:550;margin:0;}\n#snc-nest-calc .snc-err:empty{display:none;}\n#snc-nest-calc .snc-check{display:flex;align-items:center;gap:10px;font-weight:550;cursor:pointer;min-height:44px;margin:0;color:var(--snc-text);}\n#snc-nest-calc .snc-checkbox{width:20px;height:20px;margin:0;padding:0;accent-color:var(--snc-accent);appearance:auto;-webkit-appearance:auto;position:static;opacity:1;cursor:pointer;}\n#snc-nest-calc .snc-checkbox:focus-visible{outline:none;box-shadow:var(--snc-focus);}\n#snc-nest-calc .snc-note{margin:0 0 14px;padding:0;background:transparent;}\n#snc-nest-calc .snc-actions{display:flex;justify-content:center;margin:0;}\n#snc-nest-calc .snc-btn{font-family:inherit;font-size:16px;font-weight:600;line-height:1.3;min-height:44px;min-width:180px;width:auto;padding:10px 32px;margin:0;border-radius:8px;cursor:pointer;border:1px solid var(--snc-accent);background:var(--snc-accent);color:#fff;text-transform:none;letter-spacing:normal;box-shadow:none;text-shadow:none;}\n#snc-nest-calc .snc-btn:hover{background:var(--snc-accent-hover);border-color:var(--snc-accent-hover);color:#fff;}\n#snc-nest-calc .snc-btn:focus-visible{outline:none;box-shadow:var(--snc-focus);}\n#snc-nest-calc .snc-error-box{background:var(--snc-error-soft);color:var(--snc-error);border:1px solid var(--snc-error);border-radius:8px;padding:12px 14px;margin:0;}\n#snc-nest-calc .snc-error-box ul{margin:6px 0 0;padding:0 0 0 20px;list-style:disc;}\n#snc-nest-calc .snc-error-box li{margin:0;padding:0;}\n#snc-nest-calc .snc-kpis{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;}\n#snc-nest-calc .snc-kpis li{background:var(--snc-surface-2);border:1px solid var(--snc-border);border-radius:8px;padding:12px 14px;margin:0;min-width:0;list-style:none;}\n#snc-nest-calc .snc-kpis li::before{content:none;}\n#snc-nest-calc .snc-kpis li.snc-wide{grid-column:1\/-1;}\n#snc-nest-calc .snc-kpi-label{display:block;color:var(--snc-muted);font-size:.84rem;}\n#snc-nest-calc .snc-kpi-value{display:block;font-size:1.35rem;font-weight:700;font-variant-numeric:tabular-nums;overflow-wrap:anywhere;color:var(--snc-text);}\n#snc-nest-calc .snc-kpi-value small{font-size:.8rem;font-weight:500;color:var(--snc-muted);}\n#snc-nest-calc .snc-nowrap{white-space:nowrap;}\n#snc-nest-calc .snc-bar{height:8px;background:var(--snc-sheet);border-radius:99px;overflow:hidden;margin-top:8px;}\n#snc-nest-calc .snc-bar span{display:block;height:100%;background:var(--snc-accent);width:0;}\n#snc-nest-calc .snc-details{margin:16px 0 0;padding:0;display:grid;grid-template-columns:auto auto;gap:6px 14px;font-size:.92rem;}\n#snc-nest-calc .snc-details dt{color:var(--snc-muted);margin:0;padding:0;font-weight:400;}\n#snc-nest-calc .snc-details dd{margin:0;padding:0;text-align:right;font-variant-numeric:tabular-nums;overflow-wrap:anywhere;color:var(--snc-text);}\n@media (max-width:520px){#snc-nest-calc .snc-kpis{grid-template-columns:minmax(0,1fr);}#snc-nest-calc .snc-details{grid-template-columns:minmax(0,1fr);}#snc-nest-calc .snc-details dd{text-align:left;margin-bottom:6px;}}\n#snc-nest-calc .snc-preview{margin:18px 0 0;padding:0;}\n#snc-nest-calc .snc-preview figcaption{color:var(--snc-muted);font-size:.85rem;margin:0 0 6px;padding:0;text-align:left;font-style:normal;}\n#snc-nest-calc .snc-preview svg{width:100%;height:auto;max-width:100%;max-height:360px;display:block;background:var(--snc-surface-2);border:1px solid var(--snc-border);border-radius:8px;margin:0;}\n#snc-nest-calc .snc-legend-row{display:flex;gap:14px;flex-wrap:wrap;font-size:.82rem;color:var(--snc-muted);margin-top:6px;}\n#snc-nest-calc .snc-sw{display:inline-block;width:12px;height:12px;border-radius:2px;vertical-align:-1px;margin-right:5px;border:1px solid var(--snc-border);}\n<\/style>\n<div class=\"snc-layout\">\n<form id=\"snc-form\" class=\"snc-card\" novalidate aria-describedby=\"snc-form-note\">\n<fieldset class=\"snc-fieldset\">\n<legend class=\"snc-legend\">Sheet Settings<\/legend>\n<div class=\"snc-grid\">\n<div class=\"snc-field\">\n<label class=\"snc-label\" for=\"snc-sheet-width\">Sheet width<\/label>\n<div class=\"snc-wrap\"><input class=\"snc-input\" id=\"snc-sheet-width\" type=\"number\" min=\"0\" step=\"any\" inputmode=\"decimal\" required aria-describedby=\"snc-sheet-width-err\"><select class=\"snc-select\" id=\"snc-sheet-width-unit\" aria-label=\"Sheet width unit\"><option value=\"mm\">mm<\/option><option value=\"cm\">cm<\/option><option value=\"in\">in<\/option><\/select><\/div>\n<span class=\"snc-err\" id=\"snc-sheet-width-err\"><\/span>\n<\/div>\n<div class=\"snc-field\">\n<label class=\"snc-label\" for=\"snc-sheet-length\">Sheet length<\/label>\n<div class=\"snc-wrap\"><input class=\"snc-input\" id=\"snc-sheet-length\" type=\"number\" min=\"0\" step=\"any\" inputmode=\"decimal\" required aria-describedby=\"snc-sheet-length-err\"><select class=\"snc-select\" id=\"snc-sheet-length-unit\" aria-label=\"Sheet length unit\"><option value=\"mm\">mm<\/option><option value=\"cm\">cm<\/option><option value=\"in\">in<\/option><\/select><\/div>\n<span class=\"snc-err\" id=\"snc-sheet-length-err\"><\/span>\n<\/div>\n<div class=\"snc-field snc-full\">\n<label class=\"snc-label\" for=\"snc-edge-margin\">Edge margin on each side<\/label>\n<div class=\"snc-wrap\"><input class=\"snc-input\" id=\"snc-edge-margin\" type=\"number\" min=\"0\" step=\"any\" inputmode=\"decimal\" value=\"0\" aria-describedby=\"snc-edge-margin-hint snc-edge-margin-err\"><span class=\"snc-suffix\" data-snc-unit aria-hidden=\"true\">mm<\/span><\/div>\n<span class=\"snc-hint\" id=\"snc-edge-margin-hint\">Use the same unit as the sheet dimensions.<\/span>\n<span class=\"snc-err\" id=\"snc-edge-margin-err\"><\/span>\n<\/div>\n<\/div>\n<\/fieldset>\n<fieldset class=\"snc-fieldset\">\n<legend class=\"snc-legend\">Part Settings<\/legend>\n<div class=\"snc-grid\">\n<div class=\"snc-field\">\n<label class=\"snc-label\" for=\"snc-part-width\">Part width<\/label>\n<div class=\"snc-wrap\"><input class=\"snc-input\" id=\"snc-part-width\" type=\"number\" min=\"0\" step=\"any\" inputmode=\"decimal\" required aria-describedby=\"snc-part-width-err\"><span class=\"snc-suffix\" data-snc-unit aria-hidden=\"true\">mm<\/span><\/div>\n<span class=\"snc-err\" id=\"snc-part-width-err\"><\/span>\n<\/div>\n<div class=\"snc-field\">\n<label class=\"snc-label\" for=\"snc-part-length\">Part length<\/label>\n<div class=\"snc-wrap\"><input class=\"snc-input\" id=\"snc-part-length\" type=\"number\" min=\"0\" step=\"any\" inputmode=\"decimal\" required aria-describedby=\"snc-part-length-err\"><span class=\"snc-suffix\" data-snc-unit aria-hidden=\"true\">mm<\/span><\/div>\n<span class=\"snc-err\" id=\"snc-part-length-err\"><\/span>\n<\/div>\n<div class=\"snc-field\">\n<label class=\"snc-label\" for=\"snc-part-quantity\">Required quantity<\/label>\n<div class=\"snc-wrap\"><input class=\"snc-input\" id=\"snc-part-quantity\" type=\"number\" min=\"1\" step=\"1\" inputmode=\"numeric\" value=\"1\" required aria-describedby=\"snc-part-quantity-err\"><span class=\"snc-suffix\" aria-hidden=\"true\">pcs<\/span><\/div>\n<span class=\"snc-err\" id=\"snc-part-quantity-err\"><\/span>\n<\/div>\n<div class=\"snc-field\">\n<label class=\"snc-label\" for=\"snc-part-gap\">Gap between parts<\/label>\n<div class=\"snc-wrap\"><input class=\"snc-input\" id=\"snc-part-gap\" type=\"number\" min=\"0\" step=\"any\" inputmode=\"decimal\" value=\"0\" aria-describedby=\"snc-part-gap-err\"><span class=\"snc-suffix\" data-snc-unit aria-hidden=\"true\">mm<\/span><\/div>\n<span class=\"snc-err\" id=\"snc-part-gap-err\"><\/span>\n<\/div>\n<div class=\"snc-field snc-full\">\n<label class=\"snc-check\" for=\"snc-allow-rotation\"><input class=\"snc-checkbox\" id=\"snc-allow-rotation\" type=\"checkbox\" checked> Allow parts to rotate by 90\u00b0<\/label>\n<\/div>\n<\/div>\n<\/fieldset>\n<p class=\"snc-hint snc-note\" id=\"snc-form-note\">All dimensions, margins and gaps use the sheet unit shown. Changing either sheet unit updates the other.<\/p>\n<div class=\"snc-actions\"><button type=\"submit\" class=\"snc-btn\">Calculate<\/button><\/div>\n<\/form>\n<section class=\"snc-card\" id=\"snc-results-card\" aria-labelledby=\"snc-results-heading\" hidden>\n<h2 class=\"snc-h2\" id=\"snc-results-heading\">Results<\/h2>\n<div id=\"snc-results\" aria-live=\"polite\" role=\"status\"><\/div>\n<\/section>\n<\/div>\n<script>\n(function () {\n  'use strict';\n  var root = document.getElementById('snc-nest-calc');\n  if (!root || root.getAttribute('data-snc-ready')) return;\n  root.setAttribute('data-snc-ready', '1');\n  function $(id) { return root.querySelector('#' + id); }\n  var form = $('snc-form');\n  var results = $('snc-results');\n  var resultsCard = $('snc-results-card');\n  var unitW = $('snc-sheet-width-unit');\n  var unitL = $('snc-sheet-length-unit');\n  var rotation = $('snc-allow-rotation');\n  var EPS = 1e-9;\n  var MAX_DRAWN = 3000;\n  var fields = {\n    sheetW: { el: $('snc-sheet-width'), name: 'Sheet width', positive: true },\n    sheetL: { el: $('snc-sheet-length'), name: 'Sheet length', positive: true },\n    margin: { el: $('snc-edge-margin'), name: 'Edge margin', positive: false },\n    partW:  { el: $('snc-part-width'), name: 'Part width', positive: true },\n    partL:  { el: $('snc-part-length'), name: 'Part length', positive: true },\n    qty:    { el: $('snc-part-quantity'), name: 'Required quantity', positive: true, integer: true },\n    gap:    { el: $('snc-part-gap'), name: 'Gap between parts', positive: false }\n  };\n  var fieldKeys = Object.keys(fields);\n  function unit() { return unitW.value; }\n  function syncUnitLabels() {\n    var u = unit();\n    Array.prototype.forEach.call(root.querySelectorAll('[data-snc-unit]'), function (s) { s.textContent = u; });\n  }\n  function fmt2(n) { return n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); }\n  function fmtInt(n) { return n.toLocaleString('en-US', { maximumFractionDigits: 0 }); }\n  function esc(s) {\n    return String(s).replace(\/[&<>\"]\/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;' }[c]; });\n  }\n  function setFieldError(f, msg) {\n    var errEl = $(f.el.id + '-err');\n    if (errEl) errEl.textContent = msg || '';\n    if (msg) f.el.setAttribute('aria-invalid', 'true'); else f.el.removeAttribute('aria-invalid');\n  }\n  function clearErrors() { fieldKeys.forEach(function (k) { setFieldError(fields[k], ''); }); }\n  function clearResults() { results.innerHTML = ''; resultsCard.hidden = true; }\n  function readField(f) {\n    var raw = f.el.value.trim();\n    if (raw === '' || (f.el.validity && f.el.validity.badInput)) return { error: f.name + ' is required.' };\n    var v = Number(raw);\n    if (!isFinite(v)) return { error: f.name + ' must be a valid number.' };\n    if (f.positive && v <= 0) return { error: f.name + ' must be greater than zero.' };\n    if (!f.positive &#038;&#038; v < 0) return { error: f.name + ' cannot be negative.' };\n    if (f.integer &#038;&#038; Math.floor(v) !== v) return { error: f.name + ' must be a whole number.' };\n    return { value: v };\n  }\n  function fit(usableW, usableL, pw, pl, gap) {\n    var perRow = Math.max(0, Math.floor((usableW + gap) \/ (pw + gap) + EPS));\n    var rows = Math.max(0, Math.floor((usableL + gap) \/ (pl + gap) + EPS));\n    return { perRow: perRow, rows: rows, total: perRow * rows, pw: pw, pl: pl };\n  }\n  function showErrors(list, focusEl) {\n    results.innerHTML = '<div class=\"snc-error-box\"><strong>Please fix the following before calculating:<\/strong><ul>' +\n      list.map(function (m) { return '<li>' + esc(m) + '<\/li>'; }).join('') + '<\/ul><\/div>';\n    if (focusEl) focusEl.focus();\n  }\n  function buildPreview(d) {\n    var sw = d.sheetW, sl = d.sheetL, m = d.margin, g = d.gap, L = d.layout;\n    var n = 0, rects = '', r, c;\n    for (r = 0; r < L.rows &#038;&#038; n < MAX_DRAWN; r++) {\n      for (c = 0; c < L.perRow &#038;&#038; n < MAX_DRAWN; c++) {\n        rects += '<rect x=\"' + (m + c * (L.pw + g)) + '\" y=\"' + (m + r * (L.pl + g)) + '\" width=\"' + L.pw + '\" height=\"' + L.pl +\n          '\" fill=\"var(--snc-part)\" stroke=\"var(--snc-part-stroke)\" vector-effect=\"non-scaling-stroke\" stroke-width=\"1\"\/>';\n        n++;\n      }\n    }\n    var truncated = L.total > MAX_DRAWN;\n    var label = 'Layout preview of one full sheet: ' + fmtInt(L.perRow) + ' parts per row by ' + fmtInt(L.rows) + ' rows, ' + d.orientation + ' orientation.';\n    return '<figure class=\"snc-preview\">' +\n      '<figcaption>One full sheet, ' + esc(d.orientation) + ' orientation (width runs left to right)' +\n      (truncated ? ' \u2014 first ' + fmtInt(MAX_DRAWN) + ' parts drawn' : '') + '<\/figcaption>' +\n      '<svg viewBox=\"0 0 ' + sw + ' ' + sl + '\" preserveAspectRatio=\"xMidYMid meet\" role=\"img\" aria-label=\"' + esc(label) + '\">' +\n      '<rect x=\"0\" y=\"0\" width=\"' + sw + '\" height=\"' + sl + '\" fill=\"var(--snc-sheet)\" stroke=\"var(--snc-muted)\" vector-effect=\"non-scaling-stroke\" stroke-width=\"1.5\"\/>' +\n      (m > 0 ? '<rect x=\"' + m + '\" y=\"' + m + '\" width=\"' + (sw - 2 * m) + '\" height=\"' + (sl - 2 * m) + '\" fill=\"none\" stroke=\"var(--snc-muted)\" stroke-dasharray=\"4 3\" vector-effect=\"non-scaling-stroke\" stroke-width=\"1\"\/>' : '') +\n      rects + '<\/svg>' +\n      '<div class=\"snc-legend-row\" aria-hidden=\"true\"><span><i class=\"snc-sw\" style=\"background:var(--snc-part)\"><\/i>Part<\/span>' +\n      '<span><i class=\"snc-sw\" style=\"background:var(--snc-sheet)\"><\/i>Scrap \/ margin<\/span>' +\n      (m > 0 ? '<span>Dashed line: usable area<\/span>' : '') + '<\/div><\/figure>';\n  }\n  unitW.addEventListener('change', function () { unitL.value = unitW.value; syncUnitLabels(); clearResults(); });\n  unitL.addEventListener('change', function () { unitW.value = unitL.value; syncUnitLabels(); clearResults(); });\n  form.addEventListener('submit', function (e) {\n    e.preventDefault();\n    clearErrors();\n    resultsCard.hidden = false;\n    var errors = [], firstBad = null, vals = {};\n    fieldKeys.forEach(function (k) {\n      var f = fields[k], r = readField(f);\n      if (r.error) { setFieldError(f, r.error); errors.push(r.error); if (!firstBad) firstBad = f.el; }\n      else vals[k] = r.value;\n    });\n    if (unitW.value !== unitL.value) {\n      errors.push('Sheet width and sheet length must use the same unit.');\n      if (!firstBad) firstBad = unitL;\n    }\n    if (errors.length) { showErrors(errors, firstBad); return; }\n    var u = unit();\n    var usableW = vals.sheetW - 2 * vals.margin;\n    var usableL = vals.sheetL - 2 * vals.margin;\n    if (usableW <= 0 || usableL <= 0) {\n      setFieldError(fields.margin, 'Margin is too large for this sheet.');\n      showErrors(['The edge margin (' + fmt2(vals.margin) + ' ' + u + ' on each side) leaves no usable sheet area. Reduce the margin or use a larger sheet.'], fields.margin.el);\n      return;\n    }\n    var original = fit(usableW, usableL, vals.partW, vals.partL, vals.gap);\n    var rotated = rotation.checked ? fit(usableW, usableL, vals.partL, vals.partW, vals.gap) : null;\n    var layout = original, orientation = 'original';\n    if (rotated &#038;&#038; rotated.total > original.total) { layout = rotated; orientation = 'rotated'; }\n    if (layout.total <= 0) {\n      setFieldError(fields.partW, 'Part does not fit on the usable sheet.');\n      setFieldError(fields.partL, 'Part does not fit on the usable sheet.');\n      showErrors(['A ' + fmt2(vals.partW) + ' \u00d7 ' + fmt2(vals.partL) + ' ' + u +\n        ' part does not fit on the usable sheet area of ' + fmt2(usableW) + ' \u00d7 ' + fmt2(usableL) + ' ' + u +\n        (rotated ? ' in either orientation.' : '. Try allowing 90\u00b0 rotation, reducing the edge margin, or using a larger sheet.')], fields.partW.el);\n      return;\n    }\n    var perSheet = layout.total;\n    var sheets = Math.ceil(vals.qty \/ perSheet);\n    var sheetArea = vals.sheetW * vals.sheetL;\n    var totalSheetArea = sheetArea * sheets;\n    var partArea = vals.partW * vals.partL;\n    var totalPartArea = partArea * vals.qty;\n    var utilization = totalSheetArea > 0 ? (totalPartArea \/ totalSheetArea) * 100 : 0;\n    var scrap = totalSheetArea - totalPartArea;\n    var lastSheet = vals.qty - (sheets - 1) * perSheet;\n    var spare = sheets * perSheet - vals.qty;\n    var fullSheetUtil = (partArea * perSheet \/ sheetArea) * 100;\n    var compare = rotated\n      ? 'Original: ' + fmtInt(original.total) + ' \/ sheet \u00b7 Rotated: ' + fmtInt(rotated.total) + ' \/ sheet'\n      : 'Rotation disabled';\n    results.innerHTML =\n      '<ul class=\"snc-kpis\">' +\n        '<li><span class=\"snc-kpi-label\">Estimated parts per sheet<\/span><span class=\"snc-kpi-value\">' + fmtInt(perSheet) + '<\/span><\/li>' +\n        '<li><span class=\"snc-kpi-label\">Estimated sheets required<\/span><span class=\"snc-kpi-value\">' + fmtInt(sheets) + '<\/span><\/li>' +\n        '<li class=\"snc-wide\"><span class=\"snc-kpi-label\">Estimated material utilization<\/span><span class=\"snc-kpi-value\">' + fmt2(utilization) + '%<\/span>' +\n          '<div class=\"snc-bar\" aria-hidden=\"true\"><span style=\"width:' + Math.min(100, utilization).toFixed(2) + '%\"><\/span><\/div><\/li>' +\n        '<li><span class=\"snc-kpi-label\">Estimated scrap area<\/span><span class=\"snc-kpi-value\"><span class=\"snc-nowrap\">' + fmt2(scrap) + '<\/span> <small>' + u + '\u00b2<\/small><\/span><\/li>' +\n        '<li><span class=\"snc-kpi-label\">Selected orientation<\/span><span class=\"snc-kpi-value\">' + (orientation === 'rotated' ? 'Rotated 90\u00b0' : 'Original') + '<\/span><\/li>' +\n      '<\/ul>' +\n      '<dl class=\"snc-details\">' +\n        '<dt>Layout<\/dt><dd>' + fmtInt(layout.perRow) + ' per row \u00d7 ' + fmtInt(layout.rows) + (layout.rows === 1 ? ' row' : ' rows') + '<\/dd>' +\n        '<dt>Part as placed (W \u00d7 L)<\/dt><dd>' + fmt2(layout.pw) + ' \u00d7 ' + fmt2(layout.pl) + ' ' + u + '<\/dd>' +\n        '<dt>Orientation comparison<\/dt><dd>' + compare + '<\/dd>' +\n        '<dt>Usable area per sheet<\/dt><dd>' + fmt2(usableW) + ' \u00d7 ' + fmt2(usableL) + ' ' + u + '<\/dd>' +\n        '<dt>Total sheet area<\/dt><dd>' + fmt2(totalSheetArea) + ' ' + u + '\u00b2<\/dd>' +\n        '<dt>Total part area<\/dt><dd>' + fmt2(totalPartArea) + ' ' + u + '\u00b2<\/dd>' +\n        '<dt>Utilization of a full sheet<\/dt><dd>' + fmt2(fullSheetUtil) + '%<\/dd>' +\n        '<dt>Parts on last sheet<\/dt><dd>' + fmtInt(lastSheet) + ' of ' + fmtInt(perSheet) + '<\/dd>' +\n        '<dt>Spare capacity<\/dt><dd>' + fmtInt(spare) + ' part' + (spare === 1 ? '' : 's') + '<\/dd>' +\n      '<\/dl>' +\n      buildPreview({ sheetW: vals.sheetW, sheetL: vals.sheetL, margin: vals.margin, gap: vals.gap, layout: layout, orientation: orientation });\n  });\n  form.addEventListener('input', function (e) {\n    var t = e.target;\n    for (var i = 0; i < fieldKeys.length; i++) {\n      if (fields[fieldKeys[i]].el === t) { setFieldError(fields[fieldKeys[i]], ''); break; }\n    }\n    if (!resultsCard.hidden &#038;&#038; results.querySelector('.snc-kpis')) clearResults();\n  });\n  rotation.addEventListener('change', function () {\n    if (!resultsCard.hidden &#038;&#038; results.querySelector('.snc-kpis')) clearResults();\n  });\n  syncUnitLabels();\n})();\n<\/script>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>The Sheet Metal Nesting Efficiency Calculator estimates how many parts can be arranged on a sheet and calculates material utilization, sheet requirements, and potential scrap&#8230;.<\/p>","protected":false},"author":2,"featured_media":25082,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"pmpro_default_level":"","_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_hide_featured_image":false,"_jetpack_featured_image_duplicate_dismissed":0,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_post_was_ever_published":false},"categories":[8808],"tags":[8845,8846,8847],"class_list":["post-25081","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-manufacturing-and-processes","tag-material-utilization","tag-sheet-metal-cutting","tag-sheet-metal-nesting","pmpro-has-access"],"acf":[],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"https:\/\/www.mechstream.com\/wp-content\/uploads\/2026\/11\/Sheet-Metal-Nesting-Efficiency-Calculator.png","_links":{"self":[{"href":"https:\/\/www.mechstream.com\/ar\/wp-json\/wp\/v2\/posts\/25081","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.mechstream.com\/ar\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.mechstream.com\/ar\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.mechstream.com\/ar\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.mechstream.com\/ar\/wp-json\/wp\/v2\/comments?post=25081"}],"version-history":[{"count":3,"href":"https:\/\/www.mechstream.com\/ar\/wp-json\/wp\/v2\/posts\/25081\/revisions"}],"predecessor-version":[{"id":25091,"href":"https:\/\/www.mechstream.com\/ar\/wp-json\/wp\/v2\/posts\/25081\/revisions\/25091"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.mechstream.com\/ar\/wp-json\/wp\/v2\/media\/25082"}],"wp:attachment":[{"href":"https:\/\/www.mechstream.com\/ar\/wp-json\/wp\/v2\/media?parent=25081"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.mechstream.com\/ar\/wp-json\/wp\/v2\/categories?post=25081"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.mechstream.com\/ar\/wp-json\/wp\/v2\/tags?post=25081"}],"curies":[{"name":"\u0648\u0648\u0631\u062f\u0628\u0631\u064a\u0633","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}