@@ -20,3 +20,98 @@ test('G21 sets the units to millimeters', () => {
2020
2121 expect ( job . state . units ) . toEqual ( 'mm' ) ;
2222} ) ;
23+
24+ import { Parser } from '../../../parser/gcode-parser' ;
25+ import { Interpreter } from '../../../interpreter' ;
26+
27+ function run ( gcode : string ) {
28+ return new Interpreter ( ) . execute ( new Parser ( ) . parseGCode ( gcode ) . commands ) ;
29+ }
30+
31+ test . each ( [ 'M82' , 'M83' ] ) ( 'inch and mm straight moves have identical geometry and filament length in %s' , ( mode ) => {
32+ const inch = run ( `G20\n${ mode } \nG28\nG0 X1 Y1 Z1\nG1 X2 E1\nG1 Y2 Z2 E${ mode === 'M82' ? 2 : 1 } ` ) ;
33+ const mm = run (
34+ `G21\n${ mode } \nG28\nG0 X25.4 Y25.4 Z25.4\nG1 X50.8 E25.4\nG1 Y50.8 Z50.8 E${ mode === 'M82' ? 50.8 : 25.4 } `
35+ ) ;
36+
37+ expect ( inch . paths . map ( ( path ) => ( { type : path . type , vertices : path . vertices } ) ) ) . toEqual (
38+ mm . paths . map ( ( path ) => ( { type : path . type , vertices : path . vertices } ) )
39+ ) ;
40+ expect ( inch . boundingBox ) . toEqual ( mm . boundingBox ) ;
41+ expect ( inch . stats . extrusionDistance ) . toBeCloseTo ( mm . stats . extrusionDistance ) ;
42+ expect ( inch . stats . extrusionDistance ) . toBeCloseTo ( 50.8 ) ;
43+ } ) ;
44+
45+ test . each ( [ 'M82' , 'M83' ] ) ( 'inch E-only retract and prime preserve geometry and subsequent extrusion in %s' , ( mode ) => {
46+ const start = `G20\n${ mode } \nG28\nG1 X1 Y1 Z1 E2` ;
47+ const baseline = run ( start ) ;
48+ const job = run ( start ) ;
49+ const execute = ( gcode : string ) => new Interpreter ( ) . execute ( new Parser ( ) . parseGCode ( gcode ) . commands , job ) ;
50+
51+ for ( const [ command , expectedE ] of [
52+ [ mode === 'M82' ? 'G1 E1' : 'G1 E-1' , 25.4 ] ,
53+ [ mode === 'M82' ? 'G1 E2' : 'G1 E1' , 50.8 ]
54+ ] as const ) {
55+ execute ( command ) ;
56+ expect ( job . state . e ) . toBeCloseTo ( expectedE ) ;
57+ expect ( [ job . state . x , job . state . y , job . state . z ] ) . toEqual ( [ 25.4 , 25.4 , 25.4 ] ) ;
58+ expect ( job . paths . map ( ( path ) => path . vertices ) ) . toEqual ( baseline . paths . map ( ( path ) => path . vertices ) ) ;
59+ expect ( job . boundingBox ) . toEqual ( baseline . boundingBox ) ;
60+ expect ( job . stats . extrusionDistance ) . toBeCloseTo ( 50.8 ) ;
61+ }
62+
63+ const nextMove = `G1 X2 E${ mode === 'M82' ? 3 : 1 } ` ;
64+ execute ( nextMove ) ;
65+ const uninterrupted = run ( `${ start } \n${ nextMove } ` ) ;
66+ expect ( job . paths . map ( ( path ) => ( { type : path . type , vertices : path . vertices } ) ) ) . toEqual (
67+ uninterrupted . paths . map ( ( path ) => ( { type : path . type , vertices : path . vertices } ) )
68+ ) ;
69+ expect ( job . boundingBox ) . toEqual ( uninterrupted . boundingBox ) ;
70+ expect ( job . state . e ) . toBeCloseTo ( 76.2 ) ;
71+ expect ( job . stats . extrusionDistance ) . toBeCloseTo ( 76.2 ) ;
72+ } ) ;
73+
74+ test . each ( [ 'M82' , 'M83' ] ) ( 'normalizes linear moves, E-only moves and G92 in %s' , ( mode ) => {
75+ const job = run ( `G20\n${ mode } \nG28\nG1 X1 Y2 Z3 E1\nG1 E-1\nG92 X2 Y3 Z4 E2\nG1 X3 Y4 Z5 E3` ) ;
76+ expect ( job . state . x ) . toBeCloseTo ( 50.8 ) ;
77+ expect ( job . state . y ) . toBeCloseTo ( 76.2 ) ;
78+ expect ( job . state . z ) . toBeCloseTo ( 101.6 ) ;
79+ expect ( job . state . e ) . toBeCloseTo ( mode === 'M82' ? 76.2 : 127 ) ;
80+ } ) ;
81+
82+ test ( 'unit switches preserve stored coordinates and omitted axes' , ( ) => {
83+ const job = run ( 'G20\nG28\nG1 X1 Y2 Z3 E1\nG21\nG1 X50.8 E50.8\nG92 X0 E0\nG20\nG1 X1 E1' ) ;
84+ expect ( job . state . x ) . toBeCloseTo ( 76.2 ) ;
85+ expect ( job . state . y ) . toBeCloseTo ( 50.8 ) ;
86+ expect ( job . state . z ) . toBeCloseTo ( 76.2 ) ;
87+ expect ( job . stats . extrusionDistance ) . toBeCloseTo ( 76.2 ) ;
88+ expect ( job . state . e ) . toBeCloseTo ( 25.4 ) ;
89+ } ) ;
90+
91+ test . each ( [ 'G2 X2 Y0 Z1 I1 J0 E2' , 'G3 X2 Y0 Z1 R1 E2' ] ) ( 'inch and mm arcs have identical geometry: %s' , ( arc ) => {
92+ const inch = run ( `G20\nG28\n${ arc } ` ) ;
93+ const mmArc = arc . replace ( / ( [ X Y Z I J R E ] ) ( - ? \d + ) / g, ( _ , word , value ) => `${ word } ${ Number ( value ) * 25.4 } ` ) ;
94+ const mm = run ( `G21\nG28\n${ mmArc } ` ) ;
95+ expect ( inch . paths . map ( ( path ) => path . vertices ) ) . toEqual ( mm . paths . map ( ( path ) => path . vertices ) ) ;
96+ expect ( inch . boundingBox ) . toEqual ( mm . boundingBox ) ;
97+ expect ( inch . stats . extrusionDistance ) . toBeCloseTo ( 50.8 ) ;
98+ } ) ;
99+
100+ test . each ( [ 'G31' , 'G38.2' , 'G38.3' , 'G38.4' , 'G38.5' ] ) ( 'normalizes %s targets and preserves Z0 contact' , ( probe ) => {
101+ const job = run ( `G20\nG28\nG1 Z1\n${ probe } X2 Y3 Z-1` ) ;
102+ expect ( job . state . x ) . toBeCloseTo ( 50.8 ) ;
103+ expect ( job . state . y ) . toBeCloseTo ( 76.2 ) ;
104+ expect ( job . state . z ) . toBe ( 0 ) ;
105+ const unknown = run ( `G20\n${ probe } Z-1` ) ;
106+ expect ( unknown . state . z ) . toBeCloseTo ( - 25.4 ) ;
107+ } ) ;
108+
109+ test ( 'slicer dimensions remain millimeters in inch mode' , ( ) => {
110+ const parser = new Parser ( ) ;
111+ const parsed = parser . parseGCode ( '; generated by PrusaSlicer\nG20\n;WIDTH:0.45\n;HEIGHT:0.2\nG28\nG1 X1 E1' ) ;
112+ const job = new Job ( ) ;
113+ job . metadata = parser . metadata ;
114+ new Interpreter ( ) . execute ( parsed . commands , job ) ;
115+ expect ( job . state . extrusionWidth ) . toBe ( 0.45 ) ;
116+ expect ( job . state . lineHeight ) . toBe ( 0.2 ) ;
117+ } ) ;
0 commit comments