⚠ Ontworpen voor desktopschermen

Deze tool is gebouwd voor PC-schermen (1920×1080 en hoger) met een toetsenbord en muis. Het zal waarschijnlijk werkt niet prettig op een mobiel scherm?.

Tip: bladwijzer cncfoam.com en open deze op een laptop.

Gratis hot-wire foam CAM die in je browser draait

Ontwerp of importeer, bekijk in 3D en stuur direct G-code naar je ESP32/FluidNC of GRBL machine via USB of Wi-Fi. 2–5 assen. Geen installatie, geen aanmelding, geen exportlimieten.

Gemaakt door Pete Scheepensde maker achter FoamCube — machines verzonden naar makers wereldwijd sinds 2015 Waarom cncfoam?

Importeer SVG, DXF of G-code bestand (of een STL/OBJ 3D-model), stel je machinegrootte, schuimblok, voedsnelheid en draadtemperatuur in, en de tool simuleert de snede en berekent de snijtijd en een solide 3D-voorbeeld voordat je ook maar één stuk schuim smelt. Het snijdt regeloppervlakken: rechte extrusies (2-assig), taps toelopend en gedraaid vleugels en profielen via een twee-profiel morph (4-assig X/Y + U/V), geloftte vormen, kanalen, spiraalvormige kolommen en oppervlakken met geregelde lijnen, plus onderdelen met geïndexeerde rotatie op 3/5-assige opstellingen.

Echte snedes, geen renders — schuim snijden op een echte machine:

Een batch witte EPS-schuim onderdelen, heetdraadgesneden op een FoamCube — een twee-koloms balustrade, een bekroning, een Grieks sleutelpatroon, tandwielen en cilinders — op een werkbank
Een batch schuimarchitectuurdelen, hete-draad gesneden
Meerdere identieke scroll-profielen snijden rechtstreeks door een volledige stapel EPS-polystyreen isolatieplaten, met ernaast een uitgesneden schuimbloem.
Identieke profielen snijden door een hele stapel EPS-platen

CNCFOAM.COM

🗂 Vormbibliotheek Wiki
XYUVA ▾
Schaal
%
%
%
%
Draaien
°
UITLIJNING VAN 0,0
mm
mm
mm
mm
Drop .gcode / .nc / .txt / .cnc / .tap / .ngc / .svg
Heet draad
Draad (snel/uit)
Snijpad
Oorspronglijn (X)0 Y0)

Status

4-assig
0

Draai onderdeel

X-as Y-as Z-as
Import een STL/OBJ (+ Laad onderdelen) om deze te heroriënteren.

Snij-instellingen

Blok materiaal

Van de 0-lijn. Opgeslagen op dit apparaat.
G-CODE ⋮⋮ XYUVA ▾ Kopiëren ×
X/Y · vlak ⋮⋮ vlakblok ×
wiel / +− zoom · sleep om te verschuiven · 0 reset
U/V · vlak ⋮⋮ vlakblok ×
wiel / +− zoom · sleep om te verschuiven · 0 reset

Besturing

Baangsleep / pijlen
Pannenshift-drag / rechtermuisknop-drag
Zoomwiel / + −
Afspelen / Pauzerenruimte
TerugspoelenR
Reset viewStartpagina / F
Instellingen sleep ⋮⋮
Machine-instellingen

Machine type

2-assige spiegels U/V naar X/Y automatisch. Tweedelige morph is uitgeschakeld. 3/5-assig voegen een rotatie-as toe voor geïndexeerde meerzijdige bewerkingen.

Rotatieas (A)

Vertical gebruikt Offset X + Z. Horizontaal gebruikt Offset Y + Z. Vrije positionering ondersteund — stel offsets naar wens in.
Opmerking: G92 declareert alleen de huidige positie als nul — het verplaatst de machine NIET fysiek. Dit vakje aanvinken slaat die regel over; handig als je controllerworkflow al zijn eigen werk-nul instelt (bijv. via $H of een aangepaste knop).

Snijmachineafmeting (machineomhulling)

Asoriëntatie

X omgekeerd standaard, zodat de 0-lijn vooraan zit.
Materialblok bevindt zich in het linkerzijpaneel.
Controller / G-code-indeling
en je cncfoam.com altijd uitvoeren? identieke beweging — deze instelling wijzigt alleen welke as letters wordt geschreven in de geëxporteerde / gestreamde G-code. Het live G-code venster en de badges weerspiegelen altijd je keuze.
Foam-native (X Y U V A) LinuxCNC, Mach3/4 en de meeste gespecialiseerde schuimcontrollers. GRBL / FluidNC (X Y Z B A) — ESP32 + FluidNC, grbl-Mega-5X en soortgelijke borden die alleen X Y Z A B C ondersteunen, dus de U/V-toren wordt hernoemd naar Z/B. Aangepast X-as, Y-as, Z-as, U-as, V-as, A-as
Vormen in de bibliotheek worden altijd canoniek opgeslagen (X Y U V A) en hernoemd naar jouw formaat bij download — dus dezelfde vorm dient elke controller. As X/Y/Z/U/V/A
Oorsprongmarker (X=0, Y=0)
De zichtbare 0,0-marker in de 3D-weergave — een klein stipje + dunne stippellijn die aangeeft waar machine-nul is. De positie is in machinecoördinaten; kleur en diameter zijn alleen visueel en beïnvloeden de snede nooit.
Toolinterface

Zichtbaar

Uiterlijk

Streamen via USB

Lijn 0 / 0 · inactief

Wi-Fi · FluidNC-machine

Projecten

Materiaal

GEN's vormgeneratoren

Genereer schuim RC / EDF vleugelpaneel van NACA 4-cijferig aerofoils — of kies een benoemd profiel (MH45, Clark Y, …) voor vleugelvliegtuigen en zweefvliegtuigen. De 4 NACA-cijfers zijn max camber %, camber positie en ×10% dikte % — e.g. 2412 = 2% camber at 40% chord, 12% thick; 0009 = symmetric, 9% thick. Set a smaller tip chord voor tapsheid, een andere tip-aërofoil om te mengen, plus sweep en uitspoelenSnijd als een 4-draads morf van wortel naar top.

Snelle start: 20 kant-en-klare presets

Rechte vleugel: stel tip koorde = wortel koorde, sweep 0 Uitspoelen draait de tip omlaag voor zachte stalls (pivot rond de ¼-koordeVleugels snijden als één paneel — snijd een gespiegelde paar Draai de G-code of het schuim voor de tweede zijde.geprojecteerd voor je blokpositie Vleugels & NACA uitgelegd · Spar-tunnel

 

Delen met je creatie

📤 Publiceren naar bibliotheek

Je hebt een gratis account nodig om naar de openbare Shape-bibliotheek te publiceren. Nieuwe vormen worden beoordeeld voordat ze live gaan.

Er is nog geen privé-bibliotheek — alles wat je publiceert is openbaar.

multi-as CNC-foam snijden eenvoudig gemaakt en gratis te gebruiken )
⚠ Bèta-test — help me alstublieft | Bedankt PIBOT voor je donatie
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 = 'Bestanden<>Selecteer<':'<','>Er is een fout opgetredenleeftijd'; 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.';