Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,7 @@
"misc_controls_trackball",
"misc_controls_transform",
"misc_exporter_draco",
"misc_exporter_gcode",
"misc_exporter_gltf",
"misc_exporter_obj",
"misc_exporter_ply",
Expand Down
298 changes: 298 additions & 0 deletions examples/misc_exporter_gcode.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - exporter - GCode</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
</head>
<body>
<div id="info">
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - exporter - <a href="https://github.com/jgphilpott/polyslice" target="_blank" rel="noopener">GCode</a>
<br>Slice 3D models to G-code for 3D printing
<br><a href="https://www.youtube.com/watch?v=V2h3SiafXRc" target="_blank" rel="noopener">Watch the Demo Video</a>
</div>

<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>

<script type="module">

import * as THREE from 'three';
import Polyslice from 'https://unpkg.com/@jgphilpott/polyslice@25.12.8/dist/index.browser.esm.js';

import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

// Make THREE available globally for Polyslice
window.THREE = THREE;

let camera, scene, renderer, mesh;

const params = {
addCube: addCube,
addCylinder: addCylinder,
addCone: addCone,
addSphere: addSphere,
addTorus: addTorus,
exportToGCode: exportToGCode,
geometryName: 'cube',
printer: 'Ender3',
filament: 'GenericPLA',
layerHeight: 0.2,
infillDensity: 20,
infillPattern: 'grid'
};

// Dropdown options (extend as supported by Polyslice profiles)
const PRINTER_OPTIONS = [ 'Ender3', 'UltimakerS5', 'PrusaI3MK3S', 'AnycubicI3Mega', 'BambuLabP1P' ];
const FILAMENT_OPTIONS = [ 'GenericPLA', 'GenericPETG', 'GenericABS' ];

init();

function init() {

renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
document.body.appendChild( renderer.domElement );

camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 );
// Use Z-up coordinate system
THREE.Object3D.DEFAULT_UP.set( 0, 0, 1 );
camera.up.set( 0, 0, 1 );
camera.position.set( 42, 42, 42 );

scene = new THREE.Scene();
scene.background = new THREE.Color( 0xa0a0a0 );

const ambientLight = new THREE.AmbientLight( 0xffffff, 0.5 );
scene.add( ambientLight );

const directionalLight = new THREE.DirectionalLight( 0xffffff, 2.5 );
directionalLight.position.set( 0, 200, 100 );
scene.add( directionalLight );

// Add ground plane for reference (XY plane when Z is up)
const gridHelper = new THREE.GridHelper( 220, 10 );
gridHelper.rotation.x = - Math.PI / 2; // rotate from XZ to XY
scene.add( gridHelper );

const gui = new GUI();

let h = gui.addFolder( 'Printer & Filament' );
h.add( params, 'printer', PRINTER_OPTIONS ).name( 'Printer' );
h.add( params, 'filament', FILAMENT_OPTIONS ).name( 'Filament' );

h = gui.addFolder( 'Slicer Settings' );
h.add( params, 'layerHeight', 0.1, 0.4, 0.05 ).name( 'Layer Height (mm)' );
h.add( params, 'infillDensity', 0, 100, 5 ).name( 'Infill Density (%)' );
h.add( params, 'infillPattern', [ 'grid', 'triangles', 'hexagons' ] ).name( 'Infill Pattern' );

h = gui.addFolder( 'Geometry Selection' );
h.add( params, 'addCube' ).name( 'Cube' );
h.add( params, 'addCylinder' ).name( 'Cylinder' );
h.add( params, 'addCone' ).name( 'Cone' );
h.add( params, 'addSphere' ).name( 'Sphere' );
h.add( params, 'addTorus' ).name( 'Torus' );

h = gui.addFolder( 'Export' );
h.add( params, 'exportToGCode' ).name( 'Export G-code' );

gui.open();

addCube();

window.addEventListener( 'resize', onWindowResize );

const controls = new OrbitControls( camera, renderer.domElement );
controls.target.set( 0, 0, 0 );
controls.update();

}

function exportToGCode() {

try {

// Check if Polyslice is loaded
if ( typeof Polyslice === 'undefined' ) {

alert( 'Polyslice library failed to load from CDN.\n\nPossible solutions:\n1. Disable ad blockers or browser extensions\n2. Check your network connection\n3. Ensure unpkg.com is not blocked by your firewall' );
return;

}

// Create printer and filament configurations using Polyslice
const printer = new Polyslice.Printer( params.printer || 'Ender3' );
const filament = new Polyslice.Filament( params.filament || 'GenericPLA' );

// Create the slicer instance with user-defined settings
const slicer = new Polyslice.Polyslice( {
printer: printer,
filament: filament,
layerHeight: params.layerHeight,
infillPattern: params.infillPattern,
infillDensity: params.infillDensity,
verbose: true
} );

// Slice the current mesh directly
const gcode = slicer.slice( mesh );

// Download the G-code file (include geometry name)
const name = params.geometryName || 'model';
saveString( gcode, `${ name }-geometry.gcode` );

} catch ( error ) {

console.error( 'Error exporting to G-code:', error );
alert( 'Error exporting to G-code. Check console for details.' );
alert( 'Note: Polyslice is an external library. Please ensure it is loaded correctly.' );

}

}

function clearScene() {

if ( mesh ) {

mesh.geometry.dispose();
mesh.material.dispose();
scene.remove( mesh );

}

}

// Ensure the mesh sits on the XY plane with min Z = 0
function placeOnXYPlane( object ) {

// Update world matrix so bounding box reflects transforms
object.updateMatrixWorld( true );

const box = new THREE.Box3().setFromObject( object );
const minZ = box.min.z;

if ( isFinite( minZ ) ) {

// Shift object upward by -minZ so it rests on z=0
object.position.z -= minZ;
object.updateMatrixWorld( true );

}

}

function addCube() {

clearScene();

const material = new THREE.MeshLambertMaterial( { color: 0x00cc00 } );
const geometry = new THREE.BoxGeometry( 10, 10, 10 );
mesh = new THREE.Mesh( geometry, material );
params.geometryName = 'cube';
placeOnXYPlane( mesh );
scene.add( mesh );

}

function addCylinder() {

clearScene();

const material = new THREE.MeshLambertMaterial( { color: 0x00cc00 } );
const geometry = new THREE.CylinderGeometry( 5, 5, 10, 42 );
mesh = new THREE.Mesh( geometry, material );
mesh.rotation.x = Math.PI / 2;
params.geometryName = 'cylinder';
placeOnXYPlane( mesh );
scene.add( mesh );

}

function addCone() {

clearScene();

const material = new THREE.MeshLambertMaterial( { color: 0x00cc00 } );
const geometry = new THREE.ConeGeometry( 5, 10, 42 );
mesh = new THREE.Mesh( geometry, material );
mesh.rotation.x = Math.PI / 2;
params.geometryName = 'cone';
placeOnXYPlane( mesh );
scene.add( mesh );

}

function addSphere() {

clearScene();

const material = new THREE.MeshLambertMaterial( { color: 0x00cc00 } );
const geometry = new THREE.SphereGeometry( 5, 42, 42 );
mesh = new THREE.Mesh( geometry, material );
params.geometryName = 'sphere';
placeOnXYPlane( mesh );
scene.add( mesh );

}

function addTorus() {

clearScene();

const material = new THREE.MeshLambertMaterial( { color: 0x00cc00 } );
const geometry = new THREE.TorusGeometry( 5, 2, 24, 100 );
mesh = new THREE.Mesh( geometry, material );
params.geometryName = 'torus';
placeOnXYPlane( mesh );
scene.add( mesh );

}

const link = document.createElement( 'a' );
link.style.display = 'none';
document.body.appendChild( link );

function save( blob, filename ) {

link.href = URL.createObjectURL( blob );
link.download = filename;
link.click();

}

function saveString( text, filename ) {

save( new Blob( [ text ], { type: 'text/plain' } ), filename );

}

function onWindowResize() {

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

renderer.setSize( window.innerWidth, window.innerHeight );

}

function animate() {

renderer.render( scene, camera );

}

</script>

</body>
</html>
Binary file added examples/screenshots/misc_exporter_gcode.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions examples/tags.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"misc_controls_orbit": [ "rotation" ],
"misc_controls_trackball": [ "rotation" ],
"misc_controls_transform": [ "scale", "rotate", "translate" ],
"misc_exporter_gcode": [ "community" ],
"misc_raycaster_helper": [ "community" ],
"physics_ammo_break": [ "community" ],
"physics_ammo_cloth": [ "integration", "community" ],
Expand Down