🖥 Beste op desktop

De simulator heeft een toetsenbord, een muis en een breed scherm nodig. Hij is niet bedoeld voor telefoons.

Kun je nog steeds de vormbibliotheek doorbladeren, het verhaal lezen of de hardware-opbouw bekijken?

🗂 Vormbibliotheek Over & verhaal Hardware Q & A
Tip: bladwijzer cncfoam.com en open deze op een laptop.

CNCFOAM.COM

🗂 Vormbibliotheek Wiki
Inloggen Aanmelden
Schaal
%
%
%
%
Draaien
°
UITLIJNING VAN 0,0
mm
mm
mm
mm
Drop .gcode / .nc / .txt / .cnc / .tap / .ngc / .svg
👥 · vandaag
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 ⋮⋮ Kopiëren ×
X/Y · vlak ⋮⋮ vlakblok ×
wiel / +− om in te zoomen
U/V · vlak ⋮⋮ vlakblok ×
wiel / +− om in te zoomen

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.

Oorsprongmarker (X=0, Y=0)

Toolinterface

Zichtbaar

Uiterlijk

Streamen via USB

Lijn 0 / 0 · inactief

Wi-Fi · FluidNC-machine

Projecten

Materiaal

GEN's vormgeneratoren

Genereer schuim RC-vleugelpaneel van NACA 4-cijferig aerofoils. De 4 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.

Rechte vleugel: stel tip koorde = wortel koorde, sweep 0 Uitspoelen draait de tip een paar graden naar beneden voor zachte overtrekken Vleugels & NACA uitgelegd

 

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
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.';