⚠ Diseñado para pantallas de escritorio

Esta herramienta fue creada para Pantallas PC (1920×1080 y superiores) con teclado y ratón. ¿Probablemente ¿No funciona bien en pantalla móvil?.

Consejo: marca cncfoam.com y ábrelo en una computadora portátil.

Corte por hilo caliente de espuma CAM que funciona en tu navegador

Diseña o importa, previsualiza en 3D y envía directamente el G-code a tu ESP32/FluidNC o GRBL máquina por USB o Wi-Fi 2–5 ejes. Sin instalación, sin registro, sin límites de exportación.

Construido por Pete Scheepens¿El creador detrás de FoamCube — máquinas enviadas a makers en todo el mundo desde 2015? ¿Por qué cncfoam?

Importar SVG, DXF o G-code archivo (o modelo 3D STL/OBJ), configure el tamaño de su máquina, bloque de espuma, velocidad de avance y temperatura del alambre, y la herramienta simula el corte y calcula el tiempo de corte y una vista previa 3D sólida antes de derretir cualquier espuma. Corta superficies regladas: extrusiones rectas (2 ejes), cónicas y retorcidas Aletas y perfiles alares vía un morfismo de dos perfiles (4 ejes X/Y + U/V), formas alabeadas, conductos, columnas helicoidales y arquitectura de superficies regladas, además de piezas con rotación indexada en equipos de 3/5 ejes.

Cortes reales, no renders ¿Corte de espuma en una máquina real?

Una serie de piezas de espuma EPS blanca cortadas con alambre caliente en un FoamCube — una barandilla de dos columnas, un remate, un friso de clave griega, engranajes y cilindros — sobre un banco de trabajo
Un lote de piezas arquitectónicas de espuma, cortadas con alambre caliente
Varios perfiles de espiral idénticos cortados rectos a través de un paquete completo de tablero de aislamiento de poliestireno expandido (EPS), con un flor de espuma cortada al lado.
¿Perfiles idénticos cortados a través de un paquete completo de tablero de EPS?

CNCFOAM.COM

Biblioteca de formas Wiki
XYUVA ▾
ESCALA
%
%
%
%
ROTAR
°
DESPLAZAMIENTO DESDE 0,0
mm
mm
mm
mm
Soltar .gcode / .nc / .txt / .cnc / .tap / .ngc / .svg
Cortador de alambre caliente
Alambre (rápido/apagado)
Ruta de corte
Origen de línea (X)0 Y0)

Estado

4-ejes
0

Girar pieza

Eje X Eje Y Eje Z
Importar STL/OBJ (+ Cargar piezas) para reorientarlo.

Configuración de corte

Bloque de material

¿Desde la línea 0. Almacenado en este dispositivo?
G-CÓDIGO ⋮⋮ XYUVA ▾ Copiar ×
Plano X/Y ⋮⋮ planobloque ×
rueda / +− zoom · arrastrar para desplazar · 0 reiniciar
U/V · plano ⋮⋮ planobloque ×
rueda / +− zoom · arrastrar para desplazar · 0 reiniciar

Controles

Órbitaarrastrar / flechas
Panshift-arrastrar / arrastrar-derecho
Zoomrueda / + −
Reproducir / Pausarespacio
RebobinarR
Restablecer vistaInicio / F
Configuración arrastrar ⋮⋮
Configuración de la máquina

Tipo de máquina

2-axis espejos U/V a X/Y automáticamente. La morfología de dos partes está deshabilitada. 3/5-ejes añaden un eje de rotación para cortes multi-lado indexados.

Eje de rotación (A)

Vertical utiliza Offset X + Z. Horizontal utiliza Offset Y + Z. Posicionamiento libre compatible: configure los offsets a su gusto.
Nota: G92 solo declara la posición actual como cero — NO mueve físicamente la máquina. Activar esto omite esa línea por completo; útil si tu flujo de trabajo del controlador ya establece su propio cero de trabajo (ej. vía $H o un botón personalizado).

Tamaño del cortador (envolvente de la máquina)

Orientación del eje

¿X invertido por defecto para que la línea 0 quede al frente?
Bloque de material en el panel izquierdo.
Controlador / formato G-code
y tu controlador y cncfoam.com siempre realizan movimiento idéntico ¿Este ajuste solo cambia qué eje? letras ¿se escribe en el G-code exportado/transmitido? ¿Las insignias siempre reflejan tu elección?
Foam-native (X Y U V A) — LinuxCNC, Mach3/4 y la mayoría de controladores dedicados para espuma. GRBL / FluidNC (X Y Z B A) — ESP32 + FluidNC, grbl-Mega-5X y placas similares que solo soportan X Y Z A B C, por lo que la torre U/V se renombra a Z/B. Personalizado X/Y/Z/U/V/A
Las formas en la biblioteca siempre se almacenan canónicamente (X Y U V A) y se vuelven a etiquetar a tu formato al descargarlas, por lo que la misma forma sirve para cada controlador. Nombres de ejes y su controlador
Marcador de origen (X=0, Y=0)
El marcador visible 0,0 en la vista 3D — un pequeño punto + línea punteada fina que muestra dónde está el cero de la máquina. La posición está en coordenadas de máquina; el color y el diámetro son solo visuales y nunca afectan al corte.
Interfaz de herramienta

Visible

Apariencia

Transmisión por USB

Línea 0 / 0 · inactivo

Wi-Fi · Máquina FluidNC

Proyectos

Material

GEN's Generadores de formas

Generar espuma Panel de ala de reactor RC / EDF de NACA 4 dígitos perfiles aerodinámicos — o elige un perfil nombrado (MH45, Clark Y, …) para alas voladoras y planeadores. Los 4 dígitos NACA son máximos ángulo de comba %, combadura posición ¿(×10%)? grosor % — e.g. 2412 = 2% camber at 40% chord, 12% thick; 0009 = symmetric, 9% thick. Set a smaller punta de cuerda ¿Para el ahusamiento, una punta de perfil aerodinámico diferente para mezclar, además? barrido y lavado¿Cortar como morfología de 4 cables desde la raíz hasta la punta?

Inicio rápido: 20 presets predefinidos

Ala recta: establecer cuerda en punta = cuerda en raíz, barrido 0 Lavado ¿Gira la punta hacia abajo para suaves pérdidas (pivota sobre el ¼-cuerdaAletas cortadas como una panel — cortar un par simétrico Voltee el G-code o el foam para el segundo lado. ¿Paths de la torre son automáticos?proyectado ¿Para la posición de tu bloque? Aletas y NACA explicado · Túnel Spar

 

Compartir tu creación

Publicar en biblioteca

Necesitas una cuenta gratuita para publicar en la biblioteca pública de Shape. Las nuevas formas son revisadas antes de estar disponibles.

¿Aún no hay biblioteca privada? Todo lo que publiques será público.

corte de espuma CNC multieje simplificado y de uso gratuito )
⚠ Prueba beta — por favor ayúdame | Gracias PIBOT ¿Para tu donación?
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 = '¿Sin nombre?<>No output provided for translation.<':'<','>No translation needed.¿Edad?'; 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.';