⚠ Оптимізовано для настільних екранів

Цей інструмент був створений для Екрани ПК (1920×1080 та вище) з клавіатурою та мишею. Це, ймовірно Чи зручно працювати з мобільного екрана?.

Почніть зі статті у вікі Бібліотека форм Фото галерея
Порада: додайте cncfoam.com до закладок та відкрийте його на ноутбуці.

Безкоштовний hot-wire foam CAM, який працює у вашому браузері

Проєктуйте чи імпортуйте, попередньо переглядайте в 3D та транслюйте G-код безпосередньо до вашого ESP32/FluidNC чи GRBL машина через USB чи Wi-Fi? 2–5 осей Без встановлення, без реєстрації, без обмежень на експорт.

Побудовано Піт Схіпенс, творець FoamCube — верстати, відправлені виробникам у всьому світі з 2015 року. Чому cncfoam?

Імпортувати SVG, DXF чи G-код Файл (або 3D-модель у форматі STL/OBJ), встановіть розмір вашої машини, блок пінопласту, швидкість подачі та температуру дроту, і інструмент симулює різання, обчислює час різання та надає твердий 3D-попередній перегляд ще до початку плавлення пінопласту. Він ріже лінійчаті поверхні: прямі екструзії (2-осьові), конічні та скручені крила та аеродинамічні профілі через двопрофільний морф (4-осьовий X/Y + U/V), обтічні форми, канали, гвинтові колони та архітектуру з керованими поверхнями, а також деталі з індексованим обертанням на 3/5-осьових верстатах.

Реальні різи, не рендери — різання піни на реальному верстаті?

Партія білих деталей з пінополістиролу EPS, вирізаних гарячим дротом на FoamCube — двоколонна балюстрада, фініаль, фриз у вигляді грецького ключа, шестерні та циліндри — на робочому столі
Партія архітектурних деталей з піни, вирізані гарячим дротом
Кілька однакових профілів сувою прорізають наскрізь повний пакет плити теплоізоляції з пінополістиролу EPS, поряд з вирізаним пінопластовим квіткою.
Однакові профілі вирізані через весь пакет плит EPS

CNCFOAM.COM

Бібліотека форм Вікі
XYUVA ▾
МАСШТАБ
%
%
%
%
Обертати
°
ВІДСТУП ВІД 0,0
мм
мм
мм
мм
Скинути .gcode / .nc / .txt / .cnc / .tap / .ngc / .svg
Гарячий дріт
Провід (швидко/вимкнено)
Шлях різання
Початкова лінія (X)0 Y0)

Статус

4-осьова
0

Обернути деталь

Вісь X Вісь Y Z-вісь
Імпортувати STL/OBJ (+ Завантажити деталі) для зміни орієнтації

Налаштування різання

Блок матеріалу

Від 0-лінії. Зберігається на цьому пристрої
G-КОД ⋮⋮ XYUVA ▾ Копіювати ×
X/Y · площина ⋮⋮ площинаблок ×
колесо / +− збільшення · перетягніть для панорами · 0 скинути
U/V · площина ⋮⋮ площинаблок ×
колесо / +− збільшення · перетягніть для панорами · 0 скинути

Керування

Орбітаперетягніть / стрілки
Панельзсув-перетягування / перетягування праворуч
Зумколесо / + −
Відтворити / Паузапростір
ПеремоткаR
Скинути видГоловна / F
Налаштування перетягніть ⋮⋮
Налаштування верстата

Тип верстата

2-осьові дзеркала U/V до X/Y автоматично. Двочастинний морф відключено. 3/5-осьові додають вісь обертання для індексованих багатобічних різань.

Ось обертання (A)

Вертикальний режим використовує зміщення X + Z. Горизонтальний режим використовує зміщення Y + Z. Підтримується вільне позиціонування — встановлюйте зміщення на свій смак.
Примітка: G92 лише оголошує поточну позицію нульовою — це НЕ фізично переміщує верстат. Відмітка цього пропускає цю лінію повністю; корисно, якщо ваш робочий процес контролера вже встановлює власний робочий нуль (наприклад, через $H або спеціальну кнопку).

Розмір різака (габарити верстата)

Орієнтація осей

Чи X за замовчуванням перевернуто, щоб нульова лінія була спереду?
Блок матеріалу знаходиться у лівій бічній панелі.
Контролер / Формат G-коду
Ваш контролер та cncfoam.com завжди працюють ідентичний рух Це налаштування змінює лише вісь літери записати у експортований / потоковий G-код. Вікно живого G-коду та значки завжди відображають ваш вибір.
Foam-native (X Y U V A) LinuxCNC, Mach3/4 та більшість спеціалізованих контролерів для пінопласту. GRBL / FluidNC (X Y Z B A) — ESP32 + FluidNC, grbl-Mega-5X та подібні плати, які підтримують лише X Y Z A B C, тому вежа U/V перейменовується на Z/B. Налаштування Введіть вісь X/Y/Z/U/V/A
Форми у бібліотеці завжди зберігаються канонічно (X Y U V A) і перепознаються у вашому форматі під час завантаження — тому одна й та сама форма служить для кожного контролера. Назви осей та ваш контролер
Точка початку координат (X=0, Y=0)
Видимий маркер 0,0 у 3D вікні — маленька крапка + тонка пунктирна лінія, що показує, де знаходиться машинний нуль. Положення задається у машинних координатах; колір та діаметр — лише візуальні та ніколи не впливають на різання.
Інтерфейс інструменту

Видимий

Вигляд

Трансляція через USB

Лінія 0 / 0 · бездіяльність

Wi-Fi · Машина FluidNC

Проєкти

Матеріал

ГЕН генератори форм

Згенерувати піну RC / EDF крило панелі від NACA 4-значний аеропрофілі — або оберіть іменований профіль (MH45, Clark Y, …) для літаючих крил та планерів. 4 цифри NACA вказують на максимальне вигин? %, вигин позиція ×10% товщина % — e.g. 2412 = 2% camber at 40% chord, 12% thick; 0009 = symmetric, 9% thick. Set a smaller кутова хорда для конусності, інший аеродинамічний профіль наконечника для плавного переходу розгортка та вимиванняРізати як 4-провідний морф від кореня до кінчика.

Швидкий старт: 20 готових пресетів

Пряме крило: встановіть кінцеву хорду = коренева хорда, стрілоподібність 0 Промивання чим нижче кінчик, тим м'якше зниження (обертається навколо ¼-лінія хордиКрила вирізати як одна панель — вирізати дзеркальну пару Перевернути G-код чи пінопласт для другої сторони?проекційний для позиції вашого блоку Крила та NACA пояснено · Тунель Spar

 

Поділіться своїм творінням

📤 Опублікувати до бібліотеки

Для публікації у публічній бібліотеці Shape потрібен безкоштовний обліковий запис. Нові фігури проходять перевірку перед публікацією.

Ще немає особистої бібліотеки — усе, що ви публікуєте, є публічним.

багатовісне CNC-різання пінопласту made simple і безкоштовно для використання )
⚠ Бета-тестування — допоможи мені | Дякую ПІВОТ для вашого пожертвування?
oves(r.moves, { lengthZ }); document.getElementById('morphStatus').textContent = `Generated ${r.moves.length} moves over ${lengthZ} mm.`; closeModal('parts'); }; // More dropdown const moreMenu = document.getElementById('moreMenu'); document.getElementById('moreBtn').onclick = e => { e.stopPropagation(); if (typeof renderRecentFiles === 'function') renderRecentFiles(); moreMenu.classList.toggle('open'); }; document.addEventListener('click', e => { if (!document.getElementById('moreWrap').contains(e.target)) moreMenu.classList.remove('open'); }); // ---- Download / stream fold-out menu (combines G-code / 3D / USB / Wi-Fi) ---- { const dlWrap = document.getElementById('dlWrap'); const dlBtn = document.getElementById('dlMenuBtn'); if (dlWrap && dlBtn){ dlBtn.onclick = e => { e.stopPropagation(); dlWrap.classList.toggle('open'); }; document.addEventListener('click', e => { if (!dlWrap.contains(e.target)) dlWrap.classList.remove('open'); }); dlWrap.querySelectorAll('#dlMenu button').forEach(b => b.addEventListener('click', () => dlWrap.classList.remove('open'))); } } // ---- Recent files (last 5 loaded shapes / demos / G-code drops) ---- // Each entry: { name, kind: 'gcode'|'svg'|'dxf'|'demo'|'library', payload, ts } // payload is the file text for local loads, or a shape-library id for // library loads. Stored in localStorage so the list survives page reloads. const RECENT_KEY = 'cncfoam-recent'; const RECENT_MAX = 5; function recentList(){ try { return JSON.parse(localStorage.getItem(RECENT_KEY) || '[]'); } catch(e){ return []; } } function recentRecord(entry){ let list = recentList().filter(e => !(e.name === entry.name && e.kind === entry.kind)); list.unshift({ ...entry, ts: Date.now() }); list = list.slice(0, RECENT_MAX); // localStorage has a ~5MB cap; drop the largest payload if the JSON grows // beyond 1.5MB (a couple of huge G-code files would otherwise fill the bucket). while (list.length){ try { const j = JSON.stringify(list); if (j.length < 1500000){ localStorage.setItem(RECENT_KEY, j); return; } } catch(e){} // Strip the payload of the oldest entry first. for (let i = list.length - 1; i >= 0; i--){ if (list[i].payload){ list[i] = { ...list[i], payload: null }; break; } if (i === 0) list.pop(); } } } function renderRecentFiles(){ const list = recentList(); const section = document.getElementById('mmRecentSection'); const container = document.getElementById('mmRecentList'); if (!section || !container) return; if (!list.length){ section.style.display = 'none'; return; } section.style.display = 'block'; container.innerHTML = ''; for (const e of list){ const row = document.createElement('div'); row.className = 'mm-recent'; const ageMs = Date.now() - (e.ts || 0); const age < 60000 ? 'just now' : ageMs < 3600000 ? Math.round(ageMs/60000) + 'm ago' : ageMs < 86400000 ? Math.round(ageMs/3600000) + 'h ago' : Math.round(ageMs/86400000) + 'd ago'; const icon = e.kind === 'library' ? '🗂' : e.kind === 'demo' ? '🎬' : e.kind === 'gcode' ? '⚙️' : e.kind === 'dxf' ? '◇' : e.kind === 'svg' ? '✎' : '·'; row.innerHTML = 'Завантажити<>Налаштування<':'<','>Продовжитивік'; row.onclick = async () => { moreMenu.classList.remove('open'); try { if (e.kind === 'library' && e.payload){ window.location.href = '/?shape=' + encodeURIComponent(e.payload); return; } if (!e.payload){ document.getElementById('status').textContent = '"' + e.name + '" is too old to reload — pick the file again.'; return; } if (e.kind === 'gcode' || e.kind === 'demo'){ loadMoves(parseGcode(e.payload)); } else { const blob = new Blob([e.payload], { type: 'text/plain' }); const file = new File([blob], e.name, { type: 'text/plain' }); await pickProfile(file, 'A'); setTimeout(() => { const b = document.getElementById('morphGo'); if (b && !b.disabled) b.click(); }, 250); } } catch(err){ document.getElementById('status').textContent = 'Recent reload failed: ' + err.message; } }; container.appendChild(row); } } // ---------- OBJECT toolbar ---------- function readScale(src){ const linkEl = document.getElementById('scaleAll'); // Direct edits to Y/U/V were being clobbered by X on every keystroke when // link-all was on. Auto-uncheck the link so manual Y/U/V edits stick. if (linkEl.checked && src && (src === 'scaleY' || src === 'scaleU' || src === 'scaleV')) { linkEl.checked = false; } const link = linkEl.checked; const rot = parseFloat(document.getElementById('rotUV').value) || 0; const mx = parseFloat(document.getElementById('movX').value) || 0; const my = parseFloat(document.getElementById('movY').value) || 0; const mu = parseFloat(document.getElementById('movU').value) || 0; const mv = parseFloat(document.getElementById('movV').value) || 0; if (link){ const v = (parseFloat(document.getElementById('scaleX').value) || 100) / 100; SCALE = { x:v, y:v, u:v, v:v, rotUV:rot, movX:mx, movY:my, movU:mu, movV:mv }; ['scaleY','scaleU','scaleV'].forEach(id => document.getElementById(id).value = Math.round(v*100)); } else { SCALE = { x: (parseFloat(document.getElementById('scaleX').value) || 100) / 100, y: (parseFloat(document.getElementById('scaleY').value) || 100) / 100, u: (parseFloat(document.getElementById('scaleU').value) || 100) / 100, v: (parseFloat(document.getElementById('scaleV').value) || 100) / 100, rotUV: rot, movX: mx, movY: my, movU: mu, movV: mv, }; } applyScaleToMoves(); } ['scaleX','scaleY','scaleU','scaleV','scaleAll','rotUV','movX','movY','movU','movV'].forEach(id => { document.getElementById(id).addEventListener('input', () => readScale(id)); document.getElementById(id).addEventListener('change', () => readScale(id)); }); document.getElementById('scaleReset').onclick = () => { ['scaleX','scaleY','scaleU','scaleV'].forEach(id => document.getElementById(id).value = 100); document.getElementById('scaleAll').checked = true; document.getElementById('rotUV').value = 0; document.getElementById('movX').value = 0; document.getElementById('movY').value = 0; document.getElementById('movU').value = 0; document.getElementById('movV').value = 0; readScale(); }; // ---------- Settings draggable + accordion ---------- (function() { const card = document.getElementById('settingsCard'); const handle = document.getElementById('settingsDrag'); let dx=0, dy=0, dragging=false; handle.addEventListener('mousedown', e => { dragging=true; const r=card.getBoundingClientRect(); dx=e.clientX-r.left; dy=e.clientY-r.top; e.preventDefault(); }); window.addEventListener('mousemove', e => { if (!dragging) return; card.style.right='auto'; card.style.left=Math.max(0,Math.min(window.innerWidth-card.offsetWidth,e.clientX-dx))+'px'; card.style.top=Math.max(0,Math.min(window.innerHeight-60,e.clientY-dy))+'px'; }); window.addEventListener('mouseup', () => dragging=false); document.querySelectorAll('#settings details.acc').forEach(d => { d.addEventListener('toggle', () => { if (d.open) document.querySelectorAll('#settings details.acc').forEach(o => { if (o!==d) o.open=false; }); }); }); })(); // ---------- UI visibility toggles ---------- function applyUiToggles(){ document.getElementById('objectBar').classList.toggle('hidden', !CFG.showObjBar); document.getElementById('controls') .classList.toggle('hidden', !CFG.showControls); // Status section now lives inside #leftPanel — toggle its visibility there. const lp = document.getElementById('leftPanel'); if (lp) lp.classList.toggle('hud-hidden', !CFG.showHud); document.getElementById('legend') .classList.toggle('hidden', !CFG.showLegend); } // Top-centre reminder when the user has disabled the auto-zero line. Dismissable // for 6 hours via the × (reappears after that, or on a fresh trigger/reload). function applyNoHomingBanner(){ const b = document.getElementById('noHomingBanner'); if (!b) return; let until = 0; try { until = parseInt(localStorage.getItem('cncfoam-nohoming-dismiss') || '0', 10) || 0; } catch(e){} b.style.display = (CFG && CFG.noHoming && Date.now() > until) ? 'block' : 'none'; } { const nx = document.getElementById('noHomingX'); if (nx) nx.onclick = () => { try { localStorage.setItem('cncfoam-nohoming-dismiss', String(Date.now() + 6 * 3600 * 1000)); } catch(e){} applyNoHomingBanner(); }; } bindCheckbox('cfgShowObjBar', 'showObjBar', applyUiToggles); bindCheckbox('cfgShowControls', 'showControls', applyUiToggles); bindCheckbox('cfgShowHud', 'showHud', applyUiToggles); bindCheckbox('cfgShowLegend', 'showLegend', applyUiToggles); // ---------- Mini preview window ---------- const previewBox = document.getElementById('preview'); const previewBody = document.getElementById('previewBody'); const previewRenderer = new THREE.WebGLRenderer({ antialias:true, alpha:true }); previewRenderer.setPixelRatio(Math.min(devicePixelRatio||1, 2)); previewRenderer.setSize(240, 220); previewRenderer.setClearColor(0x0e1116, 0); previewBody.appendChild(previewRenderer.domElement); const previewScene = new THREE.Scene(); const previewCamera = new THREE.PerspectiveCamera(35, 240/220, 0.1, 8000); previewScene.add(new THREE.AmbientLight(0xffffff, 0.55)); const pvLight = new THREE.DirectionalLight(0xffffff, 0.9); pvLight.position.set(300, 500, 400); previewScene.add(pvLight); const pvLight2 = new THREE.DirectionalLight(0xffffff, 0.25); pvLight2.position.set(-200, -100, 200); previewScene.add(pvLight2); // Share the cut geometry + material so the preview tracks the main scene live. const previewCut = new THREE.Mesh(cutGeo, cutMat); previewScene.add(previewCut); // Faint wireframe block in the preview for spatial context. const previewBlockGroup = new THREE.Group(); previewScene.add(previewBlockGroup); function rebuildPreviewBlock(){ while (previewBlockGroup.children.length){ const c=previewBlockGroup.children.pop(); if (c.geometry) c.geometry.dispose(); if (c.material) c.material.dispose(); } const g = new THREE.BoxGeometry(BLOCK.x, BLOCK.y, BLOCK.z); const edges = new THREE.LineSegments(new THREE.EdgesGeometry(g), new THREE.LineBasicMaterial({ color:0x444c56 })); edges.position.set(BLOCK.x/2, BLOCK.y/2, BLOCK.z/2); previewBlockGroup.add(edges); } rebuildPreviewBlock(); let pvYaw = 0; function renderPreview(dt){ if (!previewBox.classList.contains('show')) return; pvYaw += dt * 0.6; const dist = Math.max(BLOCK.x, BLOCK.y, BLOCK.z) * 1.5 || 1000; previewCamera.position.set( BLOCK.x/2 + dist * Math.sin(pvYaw), BLOCK.y/2 + dist * 0.35, BLOCK.z/2 + dist * Math.cos(pvYaw) ); previewCamera.lookAt(BLOCK.x/2, BLOCK.y/2, BLOCK.z/2); previewRenderer.render(previewScene, previewCamera); } // Hook into the main animation loop const _origFrame = frame; let lastPv = null; window._cncPv = () => { /* placeholder so closures see it */ }; function pvLoop(now){ requestAnimationFrame(pvLoop); if (lastPv === null){ lastPv = now; return; } const dt = Math.min(0.1, (now - lastPv) / 1000); lastPv = now; renderPreview(dt); } requestAnimationFrame(pvLoop); // Show / hide preview, drag, close const _origLoadMoves2 = loadMoves; loadMoves = function(parsed, opts){ _origLoadMoves2(parsed, opts); previewBox.classList.add('show'); rebuildPreviewBlock(); }; document.getElementById('previewClose').onclick = () => previewBox.classList.remove('show'); (function() { const handle = previewBox.querySelector('.ph'); let dx=0, dy=0, dragging=false; handle.addEventListener('mousedown', e => { if (e.target.id === 'previewClose') return; dragging=true; const r=previewBox.getBoundingClientRect(); dx=e.clientX-r.left; dy=e.clientY-r.top; e.preventDefault(); }); window.addEventListener('mousemove', e => { if (!dragging) return; previewBox.style.right='auto'; previewBox.style.left=Math.max(0,Math.min(window.innerWidth-previewBox.offsetWidth,e.clientX-dx))+'px'; previewBox.style.top=Math.max(0,Math.min(window.innerHeight-50,e.clientY-dy))+'px'; }); window.addEventListener('mouseup', () => dragging=false); })(); // ---------- Init (no auto-load demo) ---------- syncCfgInputs(); document.getElementById('cfgShowObjBar').checked = !!CFG.showObjBar; document.getElementById('cfgShowControls').checked = !!CFG.showControls; document.getElementById('cfgShowHud').checked = !!CFG.showHud; document.getElementById('cfgShowLegend').checked = !!CFG.showLegend; { const g = document.getElementById('cfgShowGcode'); if (g) g.checked = !!CFG.showGcode; } { const xy = document.getElementById('cfgShowXyView'); if (xy) xy.checked = !!CFG.showXyView; } { const uv = document.getElementById('cfgShowUvView'); if (uv) uv.checked = !!CFG.showUvView; } { const h = document.getElementById('cfgNoHoming'); if (h) h.checked = !!CFG.noHoming; } if (typeof gcViewApplyToggle === 'function') gcViewApplyToggle(); if (window.fvXY) fvXY.applyToggle(); if (window.fvUV) fvUV.applyToggle(); if (typeof applyNoHomingBanner === 'function') applyNoHomingBanner(); applyAppearance(); applyUiToggles(); buildWire(); // Hydrate BLOCK from saved CFG (cutter size persists across reloads). This // calls buildBlock() internally + re-frames the camera + fires the checkFit // warning. Direct buildBlock() here would use stale default 500×500×500. resizeBlockFromCfg(); resize(); resetView(); requestAnimationFrame(frame); updatePartsUI(); document.getElementById('status').textContent = 'No part loaded — click + Load parts to start.';