/ unit_tests / interp / test_interp_basics.cc
test_interp_basics.cc
  1  #include "catch.hpp"
  2  
  3  #include <interp_testing_util.hh> // For core interp stuff and extra REQUIRE macros/ setup
  4  #include <rs274ngc_interp.hh>
  5  #include <interp_inspection.hh>
  6  #include <interp_return.hh>
  7  #include <saicanon.hh>
  8  #include <interp_parameter_def.hh>
  9  using namespace interp_param_global;
 10  
 11  TEST_CASE("Interp Basics")
 12  {
 13    DECL_INIT_TEST_INTERP();
 14    SECTION("Interp Init")
 15    {
 16      REQUIRE(currentX(settings) == 0.0);
 17      REQUIRE(currentY(settings) == 0.0);
 18      REQUIRE(currentZ(settings) == 0.0);
 19    }
 20  
 21    SECTION("Change Units")
 22    {
 23      REQUIRE_INTERP_OK(test_interp.convert_length_units(G_21, settings));
 24      REQUIRE(settings->length_units == CANON_UNITS_MM);
 25      // Magically jump 1 inch
 26      currentX(settings) = 25.4;
 27      REQUIRE_INTERP_OK(test_interp.convert_length_units(G_20, settings));
 28      REQUIRE(settings->length_units == CANON_UNITS_INCHES);
 29      CHECK_FUZZ(currentX(settings), 1.0);
 30      REQUIRE_INTERP_OK(test_interp.convert_length_units(G_21, settings));
 31    }
 32  
 33    SECTION("G52 without rotation")
 34    {
 35      // Assume all offsets and such start at zero
 36      REQUIRE(settings->length_units == CANON_UNITS_MM);
 37      REQUIRE(settings->parameters[G92_X] == 0.0);
 38      REQUIRE(settings->parameters[G92_Y] == 0.0);
 39      REQUIRE_INTERP_OK(test_interp.execute("G52 X25.4 Y0"));
 40      CHECK_FUZZ(settings->parameters[G92_X], 1.0);
 41      CHECK_FUZZ(settings->parameters[G92_Y], 0.0);
 42      REQUIRE_INTERP_OK(test_interp.execute("G52 Y25.4"));
 43      CHECK_FUZZ(settings->parameters[G92_X], 1.0);
 44      CHECK_FUZZ(settings->parameters[G92_Y], 1.0);
 45    }
 46  
 47    SECTION("G92 X and G5x Rotation")
 48    {
 49      // Assume all offsets and such start at zero
 50      REQUIRE_INTERP_OK(test_interp.execute("G20"));
 51      REQUIRE(settings->length_units == CANON_UNITS_INCHES);
 52      REQUIRE_INTERP_OK(test_interp.execute("G92.1"));
 53      REQUIRE(settings->parameters[G92_X] == 0.0);
 54      REQUIRE_INTERP_OK(test_interp.execute("G52 X1 Y0"));
 55      REQUIRE_INTERP_OK(test_interp.execute("G10 L2 P0 X1 Y0 Z0 R0"));
 56      CHECK_FUZZ(currentX(settings), -2.0);
 57      // FIXME this is the wrong behavior but what the interpreter currently expects
 58      REQUIRE_INTERP_OK(test_interp.execute("G10 L2 P0 X1 Y0 Z0 R90"));
 59      CHECK_FUZZ(currentX(settings), -0.0);
 60      CHECK_FUZZ(currentY(settings), 2.0);
 61    }
 62  
 63    SECTION("G92 off-axis behavior with rotation")
 64    {
 65      // Assume all offsets and such start at zero
 66      REQUIRE_INTERP_OK(test_interp.execute("G20"));
 67      REQUIRE(settings->length_units == CANON_UNITS_INCHES);
 68      REQUIRE_INTERP_OK(test_interp.execute("G92.1"));
 69      REQUIRE_INTERP_OK(test_interp.execute("G52 X1 Y0"));
 70      REQUIRE_INTERP_OK(test_interp.execute("G10 L2 P0 X1 Y2 Z3 A4 B5 C6 R90"));
 71      // FIXME this is the wrong behavior but what the interpreter currently expects
 72      CHECK_FUZZ(currentX(settings), -2.0);
 73      CHECK_FUZZ(currentY(settings), 2.0);
 74      // Should not be affected by rotation
 75      CHECK_FUZZ(currentZ(settings), -3.0);
 76      CHECK_FUZZ(currentA(settings), -4.0);
 77      CHECK_FUZZ(currentB(settings), -5.0);
 78      CHECK_FUZZ(currentC(settings), -6.0);
 79    }
 80  
 81    SECTION("G55 without rotation")
 82    {
 83      REQUIRE_INTERP_OK(test_interp.execute("G20"));
 84      CHECK_FUZZ(currentX(settings), 0.0);
 85      CHECK_FUZZ(currentY(settings), 0.0);
 86      CHECK_FUZZ(currentZ(settings), 0.0);
 87  
 88      currentX(settings) = 1.0;
 89      currentY(settings) = 1.0;
 90      currentZ(settings) = 1.0;
 91      // KLUDGE hack in parameters directly to avoid depending on other functions
 92      test_interp._setup.parameters[G55_X] = 2;
 93      test_interp._setup.parameters[G55_Y] = 3;
 94      test_interp._setup.parameters[G55_Z] = 1;
 95      REQUIRE_INTERP_OK(test_interp.convert_coordinate_system(G_55, settings));
 96      CHECK_FUZZ(currentX(settings), -1.0);
 97      CHECK_FUZZ(currentY(settings), -2.0);
 98      CHECK_FUZZ(currentZ(settings), 0.0);
 99    }
100  
101    SECTION("G55 with rotation")
102    {
103      REQUIRE_INTERP_OK(test_interp.execute("G20"));
104      currentX(settings) = 0.0;
105      // KLUDGE hack in parameters directly to avoid depending on other functions
106      test_interp._setup.parameters[G55_X] = 2;
107      test_interp._setup.parameters[G55_Y] = 3;
108      test_interp._setup.parameters[G55_Z] = 1;
109      test_interp._setup.parameters[G55_R] = 90;
110      REQUIRE_INTERP_OK(test_interp.convert_coordinate_system(G_55, settings));
111      CHECK_FUZZ(currentX(settings), -3.0);
112      CHECK_FUZZ(currentY(settings), 2.0);
113      CHECK_FUZZ(currentZ(settings), -1.0);
114    }
115  }
116  
117  TEST_CASE("G10 init")
118  {
119    DECL_INIT_TEST_INTERP();
120  
121    const char *test_setup[] = {
122      "g20",
123      "g10 l2 p1 x0 y0 z0",
124      "g54",
125      "g10 l1 p1 x0 y0 z0",
126      "t1 m6 g43",
127    };
128    execute_lines(test_interp, test_setup);
129    CHECK_FUZZ(settings->parameters[TOOL_OFFSET_X], 0.0);
130    CHECK_FUZZ(settings->parameters[TOOL_OFFSET_Y], 0.0);
131    CHECK_FUZZ(settings->parameters[TOOL_OFFSET_Z], 0.0);
132  
133    SECTION("G10 L1 direct offsets")
134    {
135      // 2-pass loop ensures that tool offsets persist regardless of G43 / G49 state
136      for (int k = 0; k < 2; ++k) {
137        REQUIRE_INTERP_OK(test_interp.execute("g10 l1 p1 x0 y0 z0 "));
138        REQUIRE_INTERP_OK(test_interp.execute("g10 l1 p1 x1"));
139        CHECK_FUZZ(settings->parameters[TOOL_OFFSET_X], 1.0);
140        CHECK_FUZZ(settings->parameters[TOOL_OFFSET_Y], 0.0);
141        CHECK_FUZZ(settings->parameters[TOOL_OFFSET_Z], 0.0);
142  
143        REQUIRE_INTERP_OK(test_interp.execute("g10 l1 p1 y2"));
144        CHECK_FUZZ(settings->parameters[TOOL_OFFSET_X], 1.0);
145        CHECK_FUZZ(settings->parameters[TOOL_OFFSET_Y], 2.0);
146        CHECK_FUZZ(settings->parameters[TOOL_OFFSET_Z], 0.0);
147  
148        REQUIRE_INTERP_OK(test_interp.execute("g10 l1 p1 z3"));
149        CHECK_FUZZ(settings->parameters[TOOL_OFFSET_X], 1.0);
150        CHECK_FUZZ(settings->parameters[TOOL_OFFSET_Y], 2.0);
151        CHECK_FUZZ(settings->parameters[TOOL_OFFSET_Z], 3.0);
152  
153        REQUIRE_INTERP_OK(test_interp.execute("g49"));
154      }
155    }
156  }
157  
158  TEST_CASE("G10 L10 ")
159  {
160    DECL_INIT_TEST_INTERP();
161  
162    SECTION("tool offsets relative to position no rotation")
163    {
164    const char *test_setup[] = {
165        "g20",
166        "g10 l2 p1 x0 y0 z0",
167        "g54",
168        "g10 l1 p1 x0 y0 z0",
169        "t1 m6 g43",
170    };
171    REQUIRE_INTERP_OK(execute_lines(test_interp, test_setup));
172  
173    REQUIRE_INTERP_OK(test_interp.execute("g0 x.1 y.2 z.3"));
174    REQUIRE_INTERP_OK(test_interp.execute("g10 l10 p1 x1"));
175    CHECK_FUZZ(settings->parameters[TOOL_OFFSET_X], -0.9);
176    CHECK_FUZZ(settings->parameters[TOOL_OFFSET_Y], 0.0);
177    CHECK_FUZZ(settings->parameters[TOOL_OFFSET_Z], 0.0);
178    REQUIRE_INTERP_OK(test_interp.execute("g10 l10 p1 y2"));
179    CHECK_FUZZ(settings->parameters[TOOL_OFFSET_X], -0.9);
180    CHECK_FUZZ(settings->parameters[TOOL_OFFSET_Y], -1.8);
181    CHECK_FUZZ(settings->parameters[TOOL_OFFSET_Z], 0.0);
182  
183    REQUIRE_INTERP_OK(test_interp.execute("g10 l10 p1 z3"));
184    CHECK_FUZZ(settings->parameters[TOOL_OFFSET_X], -0.9);
185    CHECK_FUZZ(settings->parameters[TOOL_OFFSET_Y], -1.8);
186    CHECK_FUZZ(settings->parameters[TOOL_OFFSET_Z], -2.7);
187  }
188  
189  SECTION("G10 L10 tool offsets relative to position + 45 deg rotation")
190  {
191  
192    const char *test_setup[] = {
193        "g20",
194        "g10 l2 p1 x0 y0 z0 r45",
195        "g54",
196        "g10 l1 p1 x0 y0 z0",
197        "t1 m6 g43",
198    };
199    REQUIRE(execute_lines(test_interp, test_setup));
200  
201    REQUIRE_INTERP_OK(test_interp.execute("g0 x0 y0 z0"));
202    REQUIRE_INTERP_OK(test_interp.execute("g10 l10 p1 x1"));
203    REQUIRE_INTERP_OK(test_interp.execute("g43"));
204    CHECK_FUZZ(settings->parameters[TOOL_OFFSET_X], -sqrt(2.0)/2.0);
205    CHECK_FUZZ(settings->parameters[TOOL_OFFSET_Y], -sqrt(2.0)/2.0);
206    CHECK_FUZZ(settings->parameters[TOOL_OFFSET_Z], 0.0);
207    CHECK_FUZZ(currentX(settings), 1.0);
208    CHECK_FUZZ(currentY(settings), 0.0);
209  
210    REQUIRE_INTERP_OK(test_interp.execute("g10 l10 p1 y1"));
211    REQUIRE_INTERP_OK(test_interp.execute("g43"));
212    CHECK_FUZZ(settings->parameters[TOOL_OFFSET_X], 0.0);
213    CHECK_FUZZ(settings->parameters[TOOL_OFFSET_Y], -sqrt(2));
214    CHECK_FUZZ(settings->parameters[TOOL_OFFSET_Z], 0.0);
215    CHECK_FUZZ(currentX(settings), 1.0);
216    CHECK_FUZZ(currentY(settings), 1.0);
217    REQUIRE_INTERP_OK(test_interp.execute("g49"));
218    CHECK_FUZZ(settings->parameters[TOOL_OFFSET_X], 0.0);
219    CHECK_FUZZ(settings->parameters[TOOL_OFFSET_Y], -sqrt(2));
220    CHECK_FUZZ(settings->parameters[TOOL_OFFSET_Z], 0.0);
221    CHECK_FUZZ(currentX(settings), 0.0);
222    CHECK_FUZZ(currentY(settings), 0.0);
223  }
224  }
225  
226  SCENARIO("Call G10 with G92 active (based on runtest g10-with-g92)")
227  {
228    GIVEN("No G92 offsets applied")
229    {
230      DECL_INIT_TEST_INTERP();
231      REQUIRE_INTERP_OK(test_interp.execute("g20"));
232      REQUIRE(settings->length_units == CANON_UNITS_INCHES);
233      WHEN("No offsets are applied") {
234        THEN("Both current position and current offset should be zero") {
235          CHECK_FUZZ(currentX(settings), 0.0);
236          CHECK_FUZZ(currentY(settings), 0.0);
237          CHECK_FUZZ(currentZ(settings), 0.0);
238          CHECK_FUZZ(currentWorkOffsetX(settings), 0);
239          CHECK_FUZZ(currentWorkOffsetY(settings), 0);
240          CHECK_FUZZ(currentWorkOffsetZ(settings), 0);
241          CHECK_FUZZ(currentWorkOffsetA(settings), 0);
242          CHECK_FUZZ(currentWorkOffsetB(settings), 0);
243          CHECK_FUZZ(currentWorkOffsetC(settings), 0);
244        }
245      }
246      WHEN("Work offsets are specified via G10 L2 for the active coordinate system")
247      {
248        REQUIRE_INTERP_OK(test_interp.execute("g10 l2 p0 x7 y8 z9 a10 b11 c12"));
249        THEN ("New offset values are stored and immediately applied")
250        {
251          CHECK_FUZZ(settings->origin_index, 1); // Confirm it's G54
252          CHECK_FUZZ(settings->parameters[G54_X], 7);
253          CHECK_FUZZ(settings->parameters[G54_Y], 8);
254          CHECK_FUZZ(settings->parameters[G54_Z], 9);
255          CHECK_FUZZ(settings->parameters[G54_A], 10);
256          CHECK_FUZZ(settings->parameters[G54_B], 11);
257          CHECK_FUZZ(settings->parameters[G54_C], 12);
258          CHECK_FUZZ(currentWorkOffsetX(settings), 7);
259          CHECK_FUZZ(currentWorkOffsetY(settings), 8);
260          CHECK_FUZZ(currentWorkOffsetZ(settings), 9);
261          CHECK_FUZZ(currentWorkOffsetA(settings), 10);
262          CHECK_FUZZ(currentWorkOffsetB(settings), 11);
263          CHECK_FUZZ(currentWorkOffsetC(settings), 12);
264  
265          CHECK_FUZZ(currentX(settings), -7);
266          CHECK_FUZZ(currentY(settings), -8);
267          CHECK_FUZZ(currentZ(settings), -9);
268          CHECK_FUZZ(currentA(settings), -10);
269          CHECK_FUZZ(currentB(settings), -11);
270          CHECK_FUZZ(currentC(settings), -12);
271        }
272      }
273      WHEN("Work offsets are specified via G10 L2 for inactive coordinate systems")
274      {
275        REQUIRE_INTERP_OK(test_interp.execute("G54"));
276        REQUIRE_INTERP_OK(test_interp.execute("g10 l2 p2 x7 y8 z9 a10 b11 c12"));
277        CHECK_FUZZ(settings->origin_index, 1); // Confirm it's G54
278        THEN("Work offset parameters are updated but the current offset is not (nor is the current position)") {
279          CHECK_FUZZ(settings->parameters[G55_X], 7);
280          CHECK_FUZZ(settings->parameters[G55_Y], 8);
281          CHECK_FUZZ(settings->parameters[G55_Z], 9);
282          CHECK_FUZZ(settings->parameters[G55_A], 10);
283          CHECK_FUZZ(settings->parameters[G55_B], 11);
284          CHECK_FUZZ(settings->parameters[G55_C], 12);
285  
286          CHECK_FUZZ(currentWorkOffsetX(settings), 0);
287          CHECK_FUZZ(currentWorkOffsetY(settings), 0);
288          CHECK_FUZZ(currentWorkOffsetZ(settings), 0);
289          CHECK_FUZZ(currentWorkOffsetA(settings), 0);
290          CHECK_FUZZ(currentWorkOffsetB(settings), 0);
291          CHECK_FUZZ(currentWorkOffsetC(settings), 0);
292  
293          CHECK_FUZZ(currentX(settings), 0);
294          CHECK_FUZZ(currentY(settings), 0);
295          CHECK_FUZZ(currentZ(settings), 0);
296          CHECK_FUZZ(currentA(settings), 0);
297          CHECK_FUZZ(currentB(settings), 0);
298          CHECK_FUZZ(currentC(settings), 0);
299        }
300      }
301      WHEN("Work offsets are specified via G10 L2 for the active coordinate system")
302      {
303        REQUIRE_INTERP_OK(test_interp.execute("g10 l2 p0 x7 y8 z9 a10 b11 c12"));
304        THEN ("New offset values are stored and immediately applied")
305        {
306          CHECK_FUZZ(settings->origin_index, 1); // Confirm it's G54
307          CHECK_FUZZ(settings->parameters[G54_X], 7);
308          CHECK_FUZZ(settings->parameters[G54_Y], 8);
309          CHECK_FUZZ(settings->parameters[G54_Z], 9);
310          CHECK_FUZZ(settings->parameters[G54_A], 10);
311          CHECK_FUZZ(settings->parameters[G54_B], 11);
312          CHECK_FUZZ(settings->parameters[G54_C], 12);
313  
314          CHECK_FUZZ(currentWorkOffsetX(settings), 7);
315          CHECK_FUZZ(currentWorkOffsetY(settings), 8);
316          CHECK_FUZZ(currentWorkOffsetZ(settings), 9);
317          CHECK_FUZZ(currentWorkOffsetA(settings), 10);
318          CHECK_FUZZ(currentWorkOffsetB(settings), 11);
319          CHECK_FUZZ(currentWorkOffsetC(settings), 12);
320  
321          CHECK_FUZZ(currentX(settings), -7);
322          CHECK_FUZZ(currentY(settings), -8);
323          CHECK_FUZZ(currentZ(settings), -9);
324          CHECK_FUZZ(currentA(settings), -10);
325          CHECK_FUZZ(currentB(settings), -11);
326          CHECK_FUZZ(currentC(settings), -12);
327        }
328      }
329    }
330    GIVEN("G92 offsets active")
331    {
332      DECL_INIT_TEST_INTERP();
333      REQUIRE_INTERP_OK(test_interp.execute("g20"));
334      REQUIRE(settings->length_units == CANON_UNITS_INCHES);
335      REQUIRE_INTERP_OK(test_interp.execute("g92 x3 y4 z5"));
336      constexpr double axis_offset_x = -3;
337      constexpr double axis_offset_y = -4;
338  
339      WHEN("No work offsets are applied") {
340        THEN("Both current position and current offset should only be due to G92") {
341          CHECK_FUZZ(currentX(settings), -axis_offset_x);
342          CHECK_FUZZ(currentY(settings), -axis_offset_y);
343          CHECK_FUZZ(currentZ(settings), 5);
344  
345          CHECK_FUZZ(currentWorkOffsetX(settings), 0);
346          CHECK_FUZZ(currentWorkOffsetY(settings), 0);
347          CHECK_FUZZ(currentWorkOffsetZ(settings), 0);
348          CHECK_FUZZ(currentWorkOffsetA(settings), 0);
349          CHECK_FUZZ(currentWorkOffsetB(settings), 0);
350          CHECK_FUZZ(currentWorkOffsetC(settings), 0);
351        }
352      }
353      WHEN("Work offsets are specified via G10 L2 for the active coordinate system with rotation")
354      {
355        constexpr double work_offset_x = 7;
356        constexpr double work_offset_y = 8;
357  
358        REQUIRE_INTERP_OK(test_interp.execute("g10 l2 p0 x7 y8 z9 a10 b11 c12 R45"));
359        THEN ("Current position will be rotated in X/Y")
360        {
361          CHECK_FUZZ(settings->origin_index, 1); // Confirm it's G54
362          CHECK_FUZZ(settings->parameters[G54_X], work_offset_x);
363          CHECK_FUZZ(settings->parameters[G54_Y], work_offset_y);
364          CHECK_FUZZ(settings->parameters[G54_Z], 9);
365          CHECK_FUZZ(settings->parameters[G54_A], 10);
366          CHECK_FUZZ(settings->parameters[G54_B], 11);
367          CHECK_FUZZ(settings->parameters[G54_C], 12);
368  
369          CHECK_FUZZ(currentWorkOffsetX(settings), work_offset_x);
370          CHECK_FUZZ(currentWorkOffsetY(settings), work_offset_y);
371          CHECK_FUZZ(currentWorkOffsetZ(settings), 9);
372          CHECK_FUZZ(currentWorkOffsetA(settings), 10);
373          CHECK_FUZZ(currentWorkOffsetB(settings), 11);
374          CHECK_FUZZ(currentWorkOffsetC(settings), 12);
375  
376          constexpr double xy_rotation = M_PI / 4;
377          // FIXME this math is wrong but what the current interpreter expects
378          const double total_offset_x = axis_offset_x + work_offset_x;
379          const double total_offset_y = axis_offset_y + work_offset_y;
380          const double expect_x = (cos(-xy_rotation) * -total_offset_x + -sin(-xy_rotation) * -total_offset_y);
381          const double expect_y = (sin(-xy_rotation) * -total_offset_x + cos(-xy_rotation) * -total_offset_y);
382          CHECK_FUZZ(currentX(settings), expect_x);
383          CHECK_FUZZ(currentY(settings), expect_y);
384          CHECK_FUZZ(currentZ(settings), -4);
385          CHECK_FUZZ(currentA(settings), -10);
386          CHECK_FUZZ(currentB(settings), -11);
387          CHECK_FUZZ(currentC(settings), -12);
388        }
389      }
390    }
391  }
392  
393  SCENARIO("Save / restore of G92 parameters")
394  {
395    GIVEN("Active G92 Offsets")
396    {
397      DECL_INIT_TEST_INTERP();
398      REQUIRE_INTERP_OK(test_interp.execute("g20"));
399      REQUIRE(settings->length_units == CANON_UNITS_INCHES);
400      REQUIRE_INTERP_OK(test_interp.execute("g92 x3 y4 z5"));
401      constexpr double axis_offset_x = -3;
402      constexpr double axis_offset_y = -4;
403      constexpr double axis_offset_z = -5;
404      CHECK_FUZZ(settings->parameters[G92_X], axis_offset_x);
405      CHECK_FUZZ(settings->parameters[G92_Y], axis_offset_y);
406      CHECK_FUZZ(settings->parameters[G92_Z], axis_offset_z);
407  
408      WHEN("Offsets are disabled with G92.2") {
409        REQUIRE_INTERP_OK(test_interp.execute("g92.2"));
410        THEN("Current position changes but internal offset parameters are unaffected") {
411          CHECK_FUZZ(currentX(settings), 0);
412          CHECK_FUZZ(currentY(settings), 0);
413          CHECK_FUZZ(currentZ(settings), 0);
414  
415          CHECK_FUZZ(settings->parameters[G92_X], axis_offset_x);
416          CHECK_FUZZ(settings->parameters[G92_Y], axis_offset_y);
417          CHECK_FUZZ(settings->parameters[G92_Z], axis_offset_z);
418  
419          WHEN("Offsets are re-enabled with G92.3") {
420            REQUIRE_INTERP_OK(test_interp.execute("g92.3"));
421            THEN("Original position and offsets are restored") {
422              CHECK_FUZZ(currentX(settings), -axis_offset_x);
423              CHECK_FUZZ(currentY(settings), -axis_offset_y);
424              CHECK_FUZZ(currentZ(settings), -axis_offset_z);
425  
426              CHECK_FUZZ(settings->parameters[G92_X], axis_offset_x);
427              CHECK_FUZZ(settings->parameters[G92_Y], axis_offset_y);
428              CHECK_FUZZ(settings->parameters[G92_Z], axis_offset_z);
429            }
430          }
431        }
432      }
433    }
434  }
435  
436  
437  SCENARIO("Convert G20 / G21 ")
438  {
439    GIVEN("Starting in G20")
440    {
441      DECL_INIT_TEST_INTERP();
442      REQUIRE_INTERP_OK(test_interp.execute("g20"));
443      REQUIRE_INTERP_OK(test_interp.execute("g0 x1 y2 z3 a4 b5 c6"));
444  
445      CHECK_FUZZ(currentX(settings), 1);
446      CHECK_FUZZ(currentY(settings), 2);
447      CHECK_FUZZ(currentZ(settings), 3);
448      CHECK_FUZZ(currentA(settings), 4);
449      CHECK_FUZZ(currentB(settings), 5);
450      CHECK_FUZZ(currentC(settings), 6);
451      REQUIRE(settings->length_units == CANON_UNITS_INCHES);
452      WHEN("Convert current position to mm") {
453        REQUIRE_INTERP_OK(test_interp.execute("g21"));
454        REQUIRE(settings->length_units == CANON_UNITS_MM);
455        THEN("urrent position should be in new program units") {
456          CHECK_FUZZ(currentX(settings), 1 * 25.4);
457          CHECK_FUZZ(currentY(settings), 2 * 25.4);
458          CHECK_FUZZ(currentZ(settings), 3 * 25.4);
459          CHECK_FUZZ(currentA(settings), 4);
460          CHECK_FUZZ(currentB(settings), 5);
461          CHECK_FUZZ(currentC(settings), 6);
462        }
463        WHEN("Convert to back to inches") {
464          REQUIRE_INTERP_OK(test_interp.execute("g20"));
465          REQUIRE(settings->length_units == CANON_UNITS_INCHES);
466          THEN("current position returns to inch value") {
467            CHECK_FUZZ(currentX(settings), 1);
468            CHECK_FUZZ(currentY(settings), 2);
469            CHECK_FUZZ(currentZ(settings), 3);
470            CHECK_FUZZ(currentA(settings), 4);
471            CHECK_FUZZ(currentB(settings), 5);
472            CHECK_FUZZ(currentC(settings), 6);
473          }
474        }
475      }
476  
477      WHEN("Switch to G21") {
478        REQUIRE_INTERP_OK(test_interp.execute("g10 L2 P1 X1 Y2 Z3 A4 B5 C6"));
479        REQUIRE_INTERP_OK(test_interp.execute("g54"));
480        REQUIRE_INTERP_OK(test_interp.execute("g0 x0.5 y0.5"));
481        REQUIRE_INTERP_OK(test_interp.execute("g21"));
482        REQUIRE(settings->length_units == CANON_UNITS_MM);
483        THEN("Current work offset in mm") {
484          CHECK_FUZZ(currentWorkOffsetX(settings), 1 * 25.4);
485          CHECK_FUZZ(currentWorkOffsetY(settings), 2 * 25.4);
486          CHECK_FUZZ(currentWorkOffsetZ(settings), 3 * 25.4);
487          CHECK_FUZZ(currentWorkOffsetA(settings), 4);
488          CHECK_FUZZ(currentWorkOffsetB(settings), 5);
489          CHECK_FUZZ(currentWorkOffsetC(settings), 6);
490        }
491        WHEN("Switch to back to G20") {
492          REQUIRE_INTERP_OK(test_interp.execute("g20"));
493          REQUIRE(settings->length_units == CANON_UNITS_INCHES);
494          THEN("current work offset returns to inches") {
495            CHECK_FUZZ(currentWorkOffsetX(settings), 1);
496            CHECK_FUZZ(currentWorkOffsetY(settings), 2);
497            CHECK_FUZZ(currentWorkOffsetZ(settings), 3);
498            CHECK_FUZZ(currentWorkOffsetA(settings), 4);
499            CHECK_FUZZ(currentWorkOffsetB(settings), 5);
500            CHECK_FUZZ(currentWorkOffsetC(settings), 6);
501          }
502        }
503      }
504    }
505  
506    GIVEN("Starting in with an axis offset")
507    {
508      DECL_INIT_TEST_INTERP();
509      REQUIRE_INTERP_OK(test_interp.execute("g20"));
510      REQUIRE_INTERP_OK(test_interp.execute("g92 x1 y2 z3 a4 b5 c6"));
511  
512      THEN("Initial axis offsets should be in inches") {
513        CHECK_FUZZ(currentAxisOffsetX(settings), -1);
514        CHECK_FUZZ(currentAxisOffsetY(settings), -2);
515        CHECK_FUZZ(currentAxisOffsetZ(settings), -3);
516        CHECK_FUZZ(currentAxisOffsetA(settings), -4);
517        CHECK_FUZZ(currentAxisOffsetB(settings), -5);
518        CHECK_FUZZ(currentAxisOffsetC(settings), -6);
519      }
520      WHEN("Switch to G21") {
521        REQUIRE_INTERP_OK(test_interp.execute("g21"));
522        REQUIRE(settings->length_units == CANON_UNITS_MM);
523        THEN("Axis offsets in mm") {
524          CHECK_FUZZ(currentAxisOffsetX(settings), -1 * 25.4);
525          CHECK_FUZZ(currentAxisOffsetY(settings), -2 * 25.4);
526          CHECK_FUZZ(currentAxisOffsetZ(settings), -3 * 25.4);
527          CHECK_FUZZ(currentAxisOffsetA(settings), -4);
528          CHECK_FUZZ(currentAxisOffsetB(settings), -5);
529          CHECK_FUZZ(currentAxisOffsetC(settings), -6);
530        }
531        WHEN("Switch to back to G20") {
532          REQUIRE_INTERP_OK(test_interp.execute("g20"));
533          REQUIRE(settings->length_units == CANON_UNITS_INCHES);
534          THEN("Axis offsets in inches again") {
535            CHECK_FUZZ(currentAxisOffsetX(settings), -1);
536            CHECK_FUZZ(currentAxisOffsetY(settings), -2);
537            CHECK_FUZZ(currentAxisOffsetZ(settings), -3);
538            CHECK_FUZZ(currentAxisOffsetA(settings), -4);
539            CHECK_FUZZ(currentAxisOffsetB(settings), -5);
540            CHECK_FUZZ(currentAxisOffsetC(settings), -6);
541          }
542        }
543      }
544    }
545  }
546  
547  SCENARIO("Applying a work offset while active")
548  {
549    GIVEN("A 45 deg rotated coordinate system with no offsets")
550    {
551      DECL_INIT_TEST_INTERP();
552      REQUIRE_INTERP_OK(test_interp.execute("g20"));
553      REQUIRE(settings->length_units == CANON_UNITS_INCHES);
554      REQUIRE_INTERP_OK(test_interp.execute("g54"));
555      REQUIRE_INTERP_OK(test_interp.execute("g10 l2 p1 x0 y0 z0 r45"));
556      REQUIRE_INTERP_OK(test_interp.execute("g0 g53 x0 y0 z0"));
557      REQUIRE(currentX(settings) == 0.0);
558      REQUIRE(currentY(settings) == 0.0);
559      REQUIRE(currentZ(settings) == 0.0);
560  
561      WHEN("Request L20 for an X offset") {
562        REQUIRE_INTERP_OK(test_interp.execute("g10 l20 p1 x-1"));
563        THEN("Actual work offsets are rotated") {
564            CHECK_FUZZ(currentWorkOffsetX(settings), sqrt(2)/2);
565            CHECK_FUZZ(currentWorkOffsetY(settings), sqrt(2)/2);
566            CHECK_FUZZ(currentWorkOffsetZ(settings), 0);
567        }
568  
569        WHEN("Move to X0 and request a Y offset") {
570          REQUIRE_INTERP_OK(test_interp.execute("g0 x0"));
571          REQUIRE_INTERP_OK(test_interp.execute("g10 l20 p1 y-1"));
572          THEN("Actual work offsets are rotated") {
573            CHECK_FUZZ(currentWorkOffsetX(settings), 0.0);
574            CHECK_FUZZ(currentWorkOffsetY(settings), sqrt(2));
575            CHECK_FUZZ(currentWorkOffsetZ(settings), 0);
576          }
577        }
578      }
579    }
580  }
581