⚠️ Otimizado para telas de desktop

Esta ferramenta foi construída para Ecrãs PC (1920×1080 e superiores) com teclado e mouse. Provavelmente não funciona bem em tela móvel?.

Dica: adicione cncfoam.com aos favoritos e abra-o em um notebook.

Corte por fio quente de espuma CAM gratuito que roda no seu navegador

Projete ou importe, visualize em 3D e envie o G-code diretamente para o seu ESP32/FluidNC ou GRBL máquina via USB ou Wi-Fi 2–5 eixos Sem instalação, sem cadastro, sem limites de exportação.

Construído por Pete Scheepens, o criador por trás da FoamCube — máquinas enviadas a makers em todo o mundo desde 2015. Por que cncfoam?

Importar SVG, DXF ou G-code Arquivo (ou um modelo 3D em STL/OBJ), defina o tamanho da sua máquina, bloco de espuma, velocidade de avanço e temperatura do fio, e a ferramenta simula o corte e calcula o tempo de corte e uma pré-visualização 3D sólida antes de derreter qualquer espuma. Ele corta superfícies reguladas: extrusões retas (2-eixos), cônicas e torcidas. Asas e aerofólios via um morfismo de dois perfis (4 eixos X/Y + U/V), formas loftadas, dutos, colunas helicoidais e arquitetura de superfície regrada, além de peças com rotação indexada em equipamentos de 3/5 eixos.

Cortes reais, não renders — corte de espuma em máquina real?

Uma série de peças de espuma EPS branca cortadas com fio quente em um FoamCube — um corrimão de duas colunas, um pináculo, um ornamento grego, engrenagens e cilindros — sobre uma bancada de trabalho
Um lote de peças arquitetônicas de espuma, cortadas com fio quente
Vários perfis de scroll idênticos cortados em linha reta através de um pacote completo de placa de isolamento de poliestireno EPS, com uma flor de espuma cortada ao lado.
Cortes de perfis idênticos em toda a espessura do bloco de EPS

CNCFOAM.COM

Biblioteca de formas Wiki
XYUVA ▾
ESCALA
%
%
%
%
GIRAR
°
Deslocamento de 0,0
mm
mm
mm
mm
Solte .gcode / .nc / .txt / .cnc / .tap / .ngc / .svg
Fio quente
Fio (rápido/desligado)
Caminho de corte
Origem da linha (X)0 Y0)

Status

4-eixos
0

Gire a peça

Eixo X Eixo Y Eixo Z
Importar STL/OBJ (+ Carregar peças) para reorientar.

Configurações de corte

Bloco de material

Da linha 0. Armazenado neste dispositivo.
G-CÓDIGO ⋮⋮ XYUVA ▾ Copiar ×
X/Y · plano ⋮⋮ planobloco ×
roda / +− zoom · arraste para panorâmica · 0 redefinir
U/V · plano ⋮⋮ planobloco ×
roda / +− zoom · arraste para panorâmica · 0 redefinir

Controles

Órbitaarraste / setas
Painelshift-arrast / arrast direito
Ampliarroda / + −
Reproduzir / Pausarespaço
RebobinarR
Resetar visualizaçãoInício / F
Configurações arraste ⋮⋮
Configurações da máquina

Tipo de máquina

Espelhos de 2 eixos U/V para X/Y automaticamente. A morfologia de duas partes está desativada. 3/5 eixos adicionam um eixo de rotação para cortes multi-lados indexados.

Eixo de rotação (A)

Vertical usa Offset X + Z. Horizontal usa Offset Y + Z. Posicionamento livre suportado — defina os deslocamentos conforme sua preferência.
Nota: G92 apenas declara a posição atual como zero — NÃO move fisicamente a máquina. Marcar esta opção ignora essa linha completamente; útil se o fluxo de trabalho do seu controlador já estabelece a sua própria origem-zero (por exemplo, via $H ou um botão personalizado).

Tamanho do cortador (envelope da máquina)

Orientação do eixo

X invertido por padrão para que a linha 0 fique na frente.
Bloco de material fica no painel lateral esquerdo.
Formato do Controlador / G-code
seu controlador e cncfoam.com sempre executam movimento idêntico — esta configuração altera apenas qual eixo letras será escrito no G-code exportado / transmitido. A janela de G-code ao vivo e a selos sempre refletem sua escolha
Foam-native (X Y U V A) LinuxCNC, Mach3/4 e a maioria dos controladores dedicados para espuma. GRBL / FluidNC (X Y Z B A) — ESP32 + FluidNC, grbl-Mega-5X e placas semelhantes que só suportam X Y Z A B C, então a torre U/V é renomeada para Z/B. Personalizado X/Y/Z/U/V/A
As formas na biblioteca são sempre armazenadas canonicamente (X Y U V A) e reetiquetadas no seu formato ao fazer o download — assim, a mesma forma serve para todos os controladores. Nomes dos eixos & seu controlador
Marcador de origem (X=0, Y=0)
O marcador visível 0,0 na viewport 3D — um pequeno ponto + linha tracejada fina mostrando onde está o zero da máquina. A posição está em coordenadas da máquina; a cor e o diâmetro são apenas visuais e nunca afetam o corte.
Interface da ferramenta

Visível

Aparência

Transmissão via USB

Linha 0 / 0 · ocioso

Wi-Fi · Máquina FluidNC

Projetos

Material

GEN's Geradores de formas

Gerar espuma Painel de asa RC / EDF de NACA 4 dígitos aerofólios — ou escolha um perfil nomeado (MH45, Clark Y, …) para asas voadoras e planadores. Os 4 dígitos NACA são máximos curvatura %, cambagem posição e (×10%) espessura % — e.g. 2412 = 2% camber at 40% chord, 12% thick; 0009 = symmetric, 9% thick. Set a smaller corda da ponta para afilamento, uma ponta diferente aerofólio para misturar, mais varredura e lavagemCorte como um morf de 4 fios da raiz à ponta.

Início rápido: 20 predefinições prontas

Asa reta: definir corda da ponta = corda da raiz, varredura 0 Lavagem Gira a ponta para baixo para estolagens suaves (pivota em torno do ¼-cordaAsas cortadas como uma placa — corte um par espelhado Gire o G-code ou a espuma para o segundo lado.projetado para a posição do seu bloco Asas & NACA explicado · Túnel de suporte

 

Compartilhe sua criação

Publicar na biblioteca

Precisa de uma conta gratuita para publicar na biblioteca pública de formas do Shape. Novas formas são revisadas antes de entrarem no ar.

Ainda não há uma biblioteca privada — tudo o que você publicar será público.

Entrar Criar conta
corte de espuma CNC multi-eixos simplificado e livre para usar )
⚠ Teste Beta — por favor, ajude-me | Obrigado PIBOT para sua doação
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 = 'Código G<>Desculpe, não entendi a solicitação. Por favor, forneça o texto que deseja traduzir.<':'<','>Deslocamentoidade'; 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.';