From 37278f585248303a99746f2faf828b4fd7020aa5 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Thu, 8 Jan 2026 14:35:20 +0100 Subject: [PATCH] GCodeLoader: Add support for M82/M83 commands. (#32687) --- examples/jsm/loaders/GCodeLoader.js | 42 ++++++++++++---- examples/models/gcode/test_m82.gcode | 39 +++++++++++++++ examples/models/gcode/test_m83.gcode | 39 +++++++++++++++ examples/webgl_loader_gcode.html | 72 +++++++++++++++++++++++----- 4 files changed, 173 insertions(+), 19 deletions(-) create mode 100644 examples/models/gcode/test_m82.gcode create mode 100644 examples/models/gcode/test_m83.gcode diff --git a/examples/jsm/loaders/GCodeLoader.js b/examples/jsm/loaders/GCodeLoader.js index 7cb83916cc5f6c..43c50810e3947c 100644 --- a/examples/jsm/loaders/GCodeLoader.js +++ b/examples/jsm/loaders/GCodeLoader.js @@ -94,7 +94,7 @@ class GCodeLoader extends Loader { */ parse( data ) { - let state = { x: 0, y: 0, z: 0, e: 0, f: 0, extruding: false, relative: false }; + let state = { x: 0, y: 0, z: 0, e: 0, f: 0, extruding: false, relative: false, extrusionOverride: false, extrusionRelative: false }; const layers = []; let currentLayer = undefined; @@ -147,6 +147,14 @@ class GCodeLoader extends Loader { } + function absoluteExtrusion( v1, v2 ) { + + const relative = state.extrusionOverride ? state.extrusionRelative : state.relative; + + return relative ? v1 + v2 : v2; + + } + const lines = data.replace( /;.+/g, '' ).split( '\n' ); for ( let i = 0; i < lines.length; i ++ ) { @@ -172,13 +180,13 @@ class GCodeLoader extends Loader { //G0/G1 – Linear Movement if ( cmd === 'G0' || cmd === 'G1' ) { - const line = { - x: args.x !== undefined ? absolute( state.x, args.x ) : state.x, - y: args.y !== undefined ? absolute( state.y, args.y ) : state.y, - z: args.z !== undefined ? absolute( state.z, args.z ) : state.z, - e: args.e !== undefined ? absolute( state.e, args.e ) : state.e, - f: args.f !== undefined ? absolute( state.f, args.f ) : state.f, - }; + const line = Object.assign( {}, state ); // clone state + + if ( args.x !== undefined ) line.x = absolute( state.x, args.x ); + if ( args.y !== undefined ) line.y = absolute( state.y, args.y ); + if ( args.z !== undefined ) line.z = absolute( state.z, args.z ); + if ( args.e !== undefined ) line.e = absoluteExtrusion( state.e, args.e ); + if ( args.f !== undefined ) line.f = absolute( state.f, args.f ); //Layer change detection is or made by watching Z, it's made by watching when we extrude at a new Z position if ( delta( state.e, line.e ) > 0 ) { @@ -206,11 +214,29 @@ class GCodeLoader extends Loader { //G90: Set to Absolute Positioning state.relative = false; + // reset M82/M83 extrusion override + state.extrusionOverride = false; + } else if ( cmd === 'G91' ) { //G91: Set to state.relative Positioning state.relative = true; + // reset M82/M83 extrusion override + state.extrusionOverride = false; + + } else if ( cmd === 'M82' ) { + + //M82: Override G91 and put the E axis into absolute mode independent of the other axes + state.extrusionOverride = true; + state.extrusionRelative = false; + + } else if ( cmd === 'M83' ) { + + //M83: Overrides G90 and put the E axis into relative mode independent of the other axes + state.extrusionOverride = true; + state.extrusionRelative = true; + } else if ( cmd === 'G92' ) { //G92: Set Position diff --git a/examples/models/gcode/test_m82.gcode b/examples/models/gcode/test_m82.gcode new file mode 100644 index 00000000000000..db920529f4e7ee --- /dev/null +++ b/examples/models/gcode/test_m82.gcode @@ -0,0 +1,39 @@ +M82 +G92 +G0 Z1 +G1 X10 Y0 E0 +G1 X9.95004165278026 Y0.998334166468282 E0.1 +G1 X9.80066577841242 Y1.98669330795061 E0.2 +G1 X9.55336489125606 Y2.9552020666134 E0.3 +G1 X9.21060994002885 Y3.89418342308651 E0.4 +G1 X8.77582561890373 Y4.79425538604203 E0.5 +G1 X8.25335614909678 Y5.64642473395035 E0.6 +G1 X7.64842187284489 Y6.44217687237691 E0.7 +G1 X6.96706709347165 Y7.17356090899523 E0.8 +G1 X6.21609968270664 Y7.83326909627483 E0.9 +G1 X5.4030230586814 Y8.41470984807897 E1 +G1 X4.53596121425577 Y8.91207360061435 E1.1 +G1 X3.62357754476674 Y9.32039085967226 E1.2 +G1 X2.67498828624587 Y9.63558185417193 E1.3 +G1 X1.69967142900241 Y9.8544972998846 E1.4 +G1 X0.707372016677029 Y9.97494986604054 E1.5 +G1 X-0.291995223012888 Y9.99573603041505 E1.6 +G1 X-1.28844494295525 Y9.91664810452469 E1.7 +G1 X-2.27202094693087 Y9.73847630878195 E1.8 +G1 X-3.23289566863503 Y9.46300087687414 E1.9 +G1 X-4.16146836547142 Y9.09297426825682 E2 +G1 X-5.04846104599858 Y8.63209366648874 E2.1 +G1 X-5.88501117255346 Y8.0849640381959 E2.2 +G1 X-6.66276021279824 Y7.4570521217672 E2.3 +G1 X-7.37393715541245 Y6.75463180551151 E2.4 +G1 X-8.01143615546934 Y5.98472144103957 E2.5 +G1 X-8.56888753368947 Y5.15501371821464 E2.6 +G1 X-9.04072142017061 Y4.2737988023383 E2.7 +G1 X-9.42222340668658 Y3.34988150155905 E2.8 +G1 X-9.70958165149591 Y2.39249329213982 E2.9 +G1 X-9.89992496600445 Y1.41120008059867 E3 +G1 X-9.99135150273279 Y0.415806624332905 E3.1 +G1 X-9.98294775794753 Y-0.583741434275801 E3.2 +G1 X-9.87479769908865 Y-1.57745694143248 E3.3 +G1 X-9.66798192579461 Y-2.55541102026831 E3.4 +G1 X-9.36456687290796 Y-3.5078322768962 E3.5 \ No newline at end of file diff --git a/examples/models/gcode/test_m83.gcode b/examples/models/gcode/test_m83.gcode new file mode 100644 index 00000000000000..4a45f0f25bbacb --- /dev/null +++ b/examples/models/gcode/test_m83.gcode @@ -0,0 +1,39 @@ +M83 +G92 +G0 Z1 +G1 X10 Y0 E0 +G1 X9.95004165278026 Y0.998334166468282 E0.1 +G1 X9.80066577841242 Y1.98669330795061 E0.1 +G1 X9.55336489125606 Y2.9552020666134 E0.1 +G1 X9.21060994002885 Y3.89418342308651 E0.1 +G1 X8.77582561890373 Y4.79425538604203 E0.1 +G1 X8.25335614909678 Y5.64642473395035 E0.1 +G1 X7.64842187284489 Y6.44217687237691 E0.1 +G1 X6.96706709347165 Y7.17356090899523 E0.1 +G1 X6.21609968270664 Y7.83326909627483 E0.1 +G1 X5.4030230586814 Y8.41470984807897 E0.1 +G1 X4.53596121425577 Y8.91207360061435 E0.1 +G1 X3.62357754476674 Y9.32039085967226 E0.1 +G1 X2.67498828624587 Y9.63558185417193 E0.1 +G1 X1.69967142900241 Y9.8544972998846 E0.1 +G1 X0.707372016677029 Y9.97494986604054 E0.1 +G1 X-0.291995223012888 Y9.99573603041505 E0.1 +G1 X-1.28844494295525 Y9.91664810452469 E0.1 +G1 X-2.27202094693087 Y9.73847630878195 E0.1 +G1 X-3.23289566863503 Y9.46300087687414 E0.1 +G1 X-4.16146836547142 Y9.09297426825682 E0.1 +G1 X-5.04846104599858 Y8.63209366648874 E0.1 +G1 X-5.88501117255346 Y8.0849640381959 E0.1 +G1 X-6.66276021279824 Y7.4570521217672 E0.1 +G1 X-7.37393715541245 Y6.75463180551151 E0.1 +G1 X-8.01143615546934 Y5.98472144103957 E0.1 +G1 X-8.56888753368947 Y5.15501371821464 E0.1 +G1 X-9.04072142017061 Y4.2737988023383 E0.1 +G1 X-9.42222340668658 Y3.34988150155905 E0.1 +G1 X-9.70958165149591 Y2.39249329213982 E0.1 +G1 X-9.89992496600445 Y1.41120008059867 E0.1 +G1 X-9.99135150273279 Y0.415806624332905 E0.1 +G1 X-9.98294775794753 Y-0.583741434275801 E0.1 +G1 X-9.87479769908865 Y-1.57745694143248 E0.1 +G1 X-9.66798192579461 Y-2.55541102026831 E0.1 +G1 X-9.36456687290796 Y-3.5078322768962 E0.1 \ No newline at end of file diff --git a/examples/webgl_loader_gcode.html b/examples/webgl_loader_gcode.html index 2423b062d48fed..3db259a7758225 100644 --- a/examples/webgl_loader_gcode.html +++ b/examples/webgl_loader_gcode.html @@ -27,8 +27,27 @@ import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { GCodeLoader } from 'three/addons/loaders/GCodeLoader.js'; + import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; - let camera, scene, renderer; + let camera, scene, renderer, controls, loader; + + let model; + + const params = { + asset: 'benchy' + }; + + const assets = [ + 'benchy', + 'test_m82', + 'test_m83', + ]; + + const positions = [ + new THREE.Vector3( - 100, - 20, 100 ), + new THREE.Vector3( 0, 0, 0 ), + new THREE.Vector3( 0, 0, 0 ), + ]; init(); render(); @@ -40,28 +59,59 @@ scene = new THREE.Scene(); - const loader = new GCodeLoader(); - loader.load( 'models/gcode/benchy.gcode', function ( object ) { - - object.position.set( - 100, - 20, 100 ); - scene.add( object ); - - render(); - - } ); + loader = new GCodeLoader(); + loadAsset( params.asset ); renderer = new THREE.WebGLRenderer( { antialias: true } ); renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); document.body.appendChild( renderer.domElement ); - const controls = new OrbitControls( camera, renderer.domElement ); + controls = new OrbitControls( camera, renderer.domElement ); controls.addEventListener( 'change', render ); // use if there is no animation loop controls.minDistance = 10; controls.maxDistance = 100; window.addEventListener( 'resize', resize ); + const gui = new GUI(); + + + gui.add( params, 'asset', assets ).onChange( function ( value ) { + + if ( model ) { + + model.traverse( function ( object ) { + + if ( object.material ) object.material.dispose(); + if ( object.geometry ) object.geometry.dispose(); + + } ); + + scene.remove( model ); + + } + + loadAsset( value ); + + } ); + + gui.open(); + + + } + + function loadAsset( asset ) { + + loader.load( 'models/gcode/' + asset + '.gcode', function ( object ) { + + model = object; + model.position.copy( positions[ assets.indexOf( asset ) ] ); + scene.add( model ); + controls.reset(); + + } ); + } function resize() {