/******************************************************************** * Description: rs274ngc_pre.cc * * Derived from a work by Thomas Kramer * * Author: * License: GPL Version 2 * System: Linux * * Copyright (c) 2004 All rights reserved. * * Last change: ********************************************************************/ /* rs274ngc.cc This rs274ngc.cc file contains the source code for (1) the kernel of several rs274ngc interpreters and (2) two of the four sets of interface functions declared in canon.hh: 1. interface functions to call to tell the interpreter what to do. These all return a status value. 2. interface functions to call to get information from the interpreter. Kernel functions call each other. A few kernel functions are called by interface functions. Interface function names all begin with "Interp::". Error handling is by returning a status value of either a non-error code (INTERP_OK, INTERP_EXIT, etc.) or some specific error code from each function where there is a possibility of error. If an error occurs, processing is always stopped, and control is passed back up through the function call hierarchy to an interface function; the error code is also passed back up. The stack of functions called is also recorded. The external program calling an interface function may then handle the error further as it sees fit. Since returned values are usually used as just described to handle the possibility of errors, an alternative method of passing calculated values is required. In general, if function A needs a value for variable V calculated by function B, this is handled by passing a pointer to V from A to B, and B calculates and sets V. There are a lot of functions named read_XXXX. All such functions read characters from a string using a counter. They all reset the counter to point at the character in the string following the last one used by the function. The counter is passed around from function to function by using pointers to it. The first character read by each of these functions is expected to be a member of some set of characters (often a specific character), and each function checks the first character. This version of the interpreter not saving input lines. A list of all lines will be needed in future versions to implement loops, and probably for other purposes. This version does not use any additional memory as it runs. No memory is allocated by the source code. This version does not suppress superfluous commands, such as a command to start the spindle when the spindle is already turning, or a command to turn on flood coolant, when flood coolant is already on. When the interpreter is being used for direct control of the machining center, suppressing superfluous commands might confuse the user and could be dangerous, but when it is used to translate from one file to another, suppression can produce more concise output. Future versions might include an option for suppressing superfluous commands. ****************************************************************************/ #define BOOST_PYTHON_MAX_ARITY 4 #include "python_plugin.hh" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rtapi.h" #include "inifile.hh" // INIFILE #include "rs274ngc.hh" #include "rs274ngc_return.hh" #include "interp_internal.hh" // interpreter private definitions #include "interp_queue.hh" #include "rs274ngc_interp.hh" #include #include "units.h" #include #include using namespace interp_param_global; namespace bp = boost::python; extern char * _rs274ngc_errors[]; const char *Interp::interp_status(int status) { static char statustext[50]; static const char *msgs[] = { "INTERP_OK", "INTERP_EXIT", "INTERP_EXECUTE_FINISH", "INTERP_ENDFILE", "INTERP_FILE_NOT_OPEN", "INTERP_ERROR" }; snprintf(statustext, sizeof(statustext), "%s%s%d", ((status >= INTERP_OK) && (status <= INTERP_ERROR)) ? msgs[status] : "unknown interpreter error", (status > INTERP_MIN_ERROR) ? " - error: " : " - ", status); return statustext; } extern struct _inittab builtin_modules[]; int trace; static char savedError[LINELEN+1]; Interp::Interp() : log_file(stderr), _setup{} { _setup.init_once = 1; init_named_parameters(); // need this before Python init. if (!PythonPlugin::instantiate(builtin_modules)) { // factory Error("Interp ctor: can\'t instantiate Python plugin"); return; } // KLUDGE just to get unit tests to stop complaining about python modules we won't use anyway #ifndef UNIT_TEST try { // this import will register the C++->Python converter for Interp bp::object interp_module = bp::import("interpreter"); // use a boost::cref to avoid per-call instantiation of the // Interp Python wrapper (used for the 'self' parameter in handlers) // since interp.init() may be called repeatedly this would create a new // wrapper instance on every init(), abandoning the old one and all user attributes // tacked onto it, so make sure this is done exactly once _setup.pythis = new bp::object(boost::cref(*this)); // alias to 'interpreter.this' for the sake of ';py, .... ' comments // besides 'this', eventually use proper instance names to handle // several instances bp::scope(interp_module).attr("this") = *_setup.pythis; // make "this" visible without importing interpreter explicitly bp::object retval; python_plugin->run_string("from interpreter import this", retval, false); } catch (const bp::error_already_set&) { std::string exception_msg; if (PyErr_Occurred()) { exception_msg = handle_pyerror(); } else exception_msg = "unknown exception"; bp::handle_exception(); PyErr_Clear(); Error("PYTHON: exception during 'this' export:\n%s\n",exception_msg.c_str()); } #endif } InterpBase *makeInterp() { return new Interp; } Interp::~Interp() { if(log_file) { if(log_file != stderr) fclose(log_file); log_file = nullptr; } } void Interp::doLog(unsigned int flags, const char *file, int line, const char *fmt, ...) { struct timeval tv; struct tm *tm; va_list ap; if (flags & LOG_TIME) { gettimeofday(&tv, NULL); tm = localtime(&tv.tv_sec); fprintf(log_file, "%04d%02d%02d-%02d:%02d:%02d.%03ld ", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, tv.tv_usec/1000); } if (flags & LOG_PID) { fprintf(log_file, "%4d ",getpid()); } if (flags & LOG_FILENAME) { fprintf(log_file, "%s:%d: ",file,line); } va_start(ap, fmt); vfprintf(log_file, fmt, ap); fflush(log_file); va_end(ap); } /****************************************************************************/ /* The functions in this section of this file are functions for external programs to call to tell the rs274ngc interpreter what to do. They are declared in rs274ngc.hh. */ /***********************************************************************/ /*! Interp::close Returned Value: int (INTERP_OK) Side Effects: The NC-code file is closed if open. The _setup world model is reset. Called By: external programs */ int Interp::close() { logOword("Interp::close()"); // be "lazy" only if we're not aborting a call in progress // in which case we need to reset() the call stack // this does not reset the filename properly if(_setup.use_lazy_close) // && (_setup.call_level == 0)) { _setup.lazy_closing = 1; return INTERP_OK; } if (_setup.file_pointer != NULL) { fclose(_setup.file_pointer); _setup.file_pointer = NULL; _setup.percent_flag = false; } reset(); return INTERP_OK; } /***********************************************************************/ /*! Interp::execute Returned Value: int) If execute_block returns INTERP_EXIT, this returns that. If execute_block returns INTERP_EXECUTE_FINISH, this returns that. If execute_block returns an error code, this returns that code. Otherwise, this returns INTERP_OK. Side Effects: Calls to canonical machining commands are made. The interpreter variables are changed. At the end of the program, the file is closed. If using a file, the active G-codes and M codes are updated. Called By: external programs This executes a previously parsed block. */ int Interp::_execute(const char *command) { int status; int n; int MDImode = 0; block_pointer eblock = &EXECUTING_BLOCK(_setup); extern const char *call_statenames[]; extern const char *call_typenames[]; extern const char *o_ops[]; if (NULL != command) { MDImode = 1; status = read(command); if (status != INTERP_OK) { // if (status > INTERP_MIN_ERROR) // _setup.remap_level = 0; return status; } } logDebug("execute:%s %s='%s' mdi_int=%d o_type=%s o_name=%s cl=%d rl=%d type=%s state=%s", MDImode ? "MDI" : "auto", command ? "command" : "line", command ? command : _setup.linetext, _setup.mdi_interrupt, o_ops[eblock->o_type], eblock->o_name, _setup.call_level,_setup.remap_level, eblock->call_type < 0 ? "*unset*" : call_typenames[eblock->call_type], call_statenames[_setup.call_state]); // process control functions -- will skip if skipping if ((eblock->o_name != 0) || _setup.mdi_interrupt) { status = convert_control_functions(eblock, &_setup); CHP(status); // relinquish control if INTERP_EXECUTE_FINISH, INTERP_ERROR etc // let MDI code call subroutines. // !!!KL not clear what happens if last execution failed while in // !!!KL a subroutine // NOTE: the last executed file will still be open, because "close" // is really a lazy close. // we had an INTERP_OK, so no need to set up another call to finish after sync() if (_setup.mdi_interrupt) { _setup.mdi_interrupt = false; MDImode = 1; } logDebug("!!!KL Open file is:%s:", _setup.filename); logDebug("MDImode = %d", MDImode); while(MDImode && _setup.call_level) // we are still in a subroutine { status = read(0); // reads from current file and calls parse if (status > INTERP_MIN_ERROR) CHP(status); status = execute(); // special handling for mdi errors if (status != INTERP_OK) { if (status == INTERP_EXECUTE_FINISH) { _setup.mdi_interrupt = true; } CHP(status); } } _setup.mdi_interrupt = false; if (MDImode) { FINISH(); _setup.offset_map.clear(); } return INTERP_OK; } // skip if skipping if(_setup.skipping_o) { logDebug("skipping to: %s", _setup.skipping_o); return INTERP_OK; } for (n = 0; n < _setup.parameter_occurrence; n++) { // copy parameter settings from parameter buffer into parameter table _setup.parameters[_setup.parameter_numbers[n]] = _setup.parameter_values[n]; } // logDebug("_setup.named_parameter_occurrence = %d", // _setup.named_parameter_occurrence); for (n = 0; n < _setup.named_parameter_occurrence; n++) { // copy parameter settings from parameter buffer into parameter table logDebug("storing param:|%s|", _setup.named_parameters[n]); CHP(store_named_param(&_setup, _setup.named_parameters[n], _setup.named_parameter_values[n])); } _setup.named_parameter_occurrence = 0; if (_setup.line_length != 0) { /* line not blank */ // at this point we have a parsed block // if items are to be remapped the flow is as follows: // // 1. push this block onto the remap stack because this might take several // interp invcocations to finish, while other blocks will be parsed and // executed by the oword subs. The top-of-stack block is the 'current // remapped block' or CONTROLLING_BLOCK. // // 2. execute the remap stack top level block, ticking off all items which are done. // // 3. when a remap operation is encountered, this will result in a call like so: // 'ocall'. // // this replacement call is parsed with read() into _setup.blocks[0] by the // corresponding routine (see e.g. handling of T in interp_execute.cc) // through calling into convert_remapped_code() // // 4. The oword call code might execute an optional prologue handler which is called // when the subroutine environment is set up (parameters set, execution of // body to begin). This is the way to set local named parameters e.g. for canned cycles. // // 5. The oword endsub/return code might call an epilogue handler // which finishes any work at the Python level on endsub/return, and thereafter // calls back into remap_finished(). // // 6. The execution stops after parsing, and returns with an indication of the // execution phase. We use negative values of enum steps to distinguish them // from normal INTERP_* type codes which are all >= 0. // // 7. In MDI mode, we have to kick execution by replicating code from above // to get the osub call going. // // 8. In Auto mode, we do an initial execute(0) to get things going, thereafer // task will do it for us. // // 9. When a replacement sub finishes, remap_finished() continues execution of // the current remapped block until done. // if (eblock->remappings.size() > 0) { std::set::iterator it; int next_remap = *eblock->remappings.begin(); logRemap("found remap %d in '%s', level=%d filename=%s line=%d", next_remap,_setup.blocktext,_setup.call_level,_setup.filename,_setup.sequence_number); CHP(enter_remap()); block_pointer cblock = &CONTROLLING_BLOCK(_setup); cblock->phase = next_remap; // execute up to the first remap including read() of its handler // this also sets cblock->executing_remap status = execute_block(cblock, &_setup); #if 0 // this is too naive a test and needs improving (aka: not segfault). // It needs to kick in only for new codes, not remapped ones, for which // recursion just means 'use builtin semantics' // add some kind of 'is_remapped_builtin()' macro or test method // detect a remapping recursion. // since each remapped item pushes a new block onto the remap stack, we walk // the remap stack searching for an identical remap below the TOS for (int i = _setup.remap_level - 1; i > 0; i--) { if (_setup.blocks[i].executing_remap == cblock->executing_remap) { ERS("recursive remapping for %s detected", cblock->executing_remap->name); } } #endif // All items up to the first remap item have been executed. // The remap item procedure call has been parsed into _setup.blocks[0], // the EXECUTING_BLOCK. // after parsing a handler, execute_block() either fails to toplevel or // returns the negative value of phase (to distinguish them from INTERP_* codes which are all >= 0) if (status < 0) { // the remap phase indicator was returned. // sanity: if (cblock->remappings.find(- status) == cblock->remappings.end()) { ERS("BUG: execute_block: got %d - not in remappings() !! (next_remap=%d)",- status,next_remap); } logRemap("initial phase %d",-status); if (MDImode) { // need to trigger execution of parsed _setup.block1 here // replicate MDI oword execution code here if ((eblock->o_name != 0) || (_setup.mdi_interrupt)) { status = convert_control_functions(eblock, &_setup); CHP(status); if (_setup.mdi_interrupt) { _setup.mdi_interrupt = false; MDImode = 1; } status = INTERP_OK; while(MDImode && _setup.call_level) { // we are still in a subroutine CHP(read(0)); // reads from current file and calls parse status = execute(); // special handling for mdi errors if (status == INTERP_EXECUTE_FINISH) _setup.mdi_interrupt = true; CHP(status); } _setup.mdi_interrupt = false; // at this point the MDI execution of a remapped block is complete. logRemap("MDI remap execution complete status=%s\n",interp_status(status)); write_g_codes(eblock, &_setup); write_m_codes(eblock, &_setup); write_settings(&_setup); return INTERP_OK; } } else { // this should get the osub going status = execute(0); CHP(status); // when this is done, blocks[0] will be executed as per standard case // on endsub/return and g_codes/m_codes/settings recorded there. } if ((status != INTERP_OK) && (status != INTERP_EXECUTE_FINISH) && (status != INTERP_EXIT)) ERP(status); } else { CHP(status); } } else { // standard case: unremapped block execution status = execute_block(eblock, &_setup); write_g_codes(eblock, &_setup); write_m_codes(eblock, &_setup); write_settings(&_setup); if ((status == INTERP_EXIT) && (_setup.remap_level > 0) && (_setup.call_level > 0)) { // an M2 was encountered while executing a handler. logRemap("standard case status=%s remap_level=%d call_level=%d blocktext='%s' MDImode=%d", interp_status(status),_setup.remap_level,_setup.call_level, _setup.blocktext,MDImode); logRemap("_setup.filename = %s, fn[0]=%s, fn[1]=%s", _setup.filename, _setup.sub_context[0].filename, _setup.sub_context[1].filename); } } if ((status != INTERP_OK) && (status != INTERP_EXECUTE_FINISH) && (status != INTERP_EXIT)) ERP(status); } else /* blank line is OK */ status = INTERP_OK; return status; } int Interp::execute(const char *command) { int status; if ((status = _execute(command)) > INTERP_MIN_ERROR) { unwind_call(status, __FILE__,__LINE__,__FUNCTION__); } return status; } int Interp::execute() { return Interp::execute(0); } int Interp::execute(const char *command, int line_number) { int status; if(command && line_number) _setup.sequence_number = line_number; status = Interp::execute(command); if (status > INTERP_MIN_ERROR) { unwind_call(status, __FILE__,__LINE__,__FUNCTION__); logDebug("<-- execute(): error returned, clearing remap and call stack"); } if ((_setup.call_level == 0) && (status == INTERP_EXECUTE_FINISH) && (_setup.mdi_interrupt)) { logDebug(" execute() clearing mdi_interrupt"); _setup.mdi_interrupt = false; // seems to work ok! FIXME mah is this needed? } return status; } // when a remapping sub finishes, the oword return/endsub handling code // calls back in here to continue block execution int Interp::remap_finished(int phase) { int next_remap,status; block_pointer cblock = &CONTROLLING_BLOCK(_setup); logRemap("remap_finished phase=%d remap_level=%d call_level=%d filename=%s", phase, _setup.remap_level,_setup.call_level,_setup.filename); // the controlling block had a remapped item, which just finished and the // oword return/endsub code called in here. if (phase < 0) { // paranoia. if (cblock->remappings.find(-phase) == cblock->remappings.end()) { ERS("remap_finished: got %d - not in cblock.remappings!",phase); } // done with this phase. cblock->remappings.erase(-phase); // check the controlling block for the next remapped item std::set::iterator it = cblock->remappings.begin(); if (it != cblock->remappings.end()) { next_remap = *it; cblock->phase = next_remap; logRemap("starting phase %d (remap_level=%d call_level=%d)",next_remap,_setup.remap_level,_setup.call_level); // this will execute up to the next remap, and return // after parsing the handler with read() // so blocks[0] is armed (meaning: a osub call is parsed, but not executed yet) status = execute_block(cblock, &_setup); logRemap("phase %d started, execute_block() returns %d", next_remap, status); if (status < 0) { // a remap was parsed, get the block going return execute(0); } else return status; } else { if (cblock->remappings.size()) { ERS("BUG - remappings not empty"); } // execution of controlling block finished executing a remap, and it contains no more // remapped items. Execute any leftover items. logRemap("no more remaps in controlling_block found (remap_level=%d call_level=%d), remappings size=%zd, dropping", _setup.remap_level,_setup.call_level,cblock->remappings.size()); status = execute_block(cblock, &_setup); if ((status < 0) || (status > INTERP_MIN_ERROR)) { // status < 0 is a bug; might happen if find_remappings() failed to indicate the next remap logRemap("executing block leftover items: %s status=%s remap_level=%d call_level=%d (failing)", status < 0 ? "BUG":"ERROR", interp_status(status),_setup.remap_level,_setup.call_level); if (status < 0) ERS("BUG - check find_remappings()!! status=%d nesting=%d",status,_setup.remap_level); } else { // we're done with this remapped block. // execute_block may return INTERP_EXECUTE_FINISH if a probe, input or toolchange // command was executed. // not sure what INTERP_ENDFILE & INTERP_EXIT really mean here. // if ((status == INTERP_OK) || (status == INTERP_ENDFILE) || (status == INTERP_EXIT) || (status == INTERP_EXECUTE_FINISH)) { // leftover items finished. Drop a remapping level. if (status != INTERP_EXIT) // Don't drop remap lev. at prog exit CHP(leave_remap()); logRemap("executing block leftover items complete, status=%s remap_level=%d call_level=%d tc=%d probe=%d input=%d mdi_interrupt=%d line=%d backtoline=%d", interp_status(status),_setup.remap_level,_setup.call_level,_setup.toolchange_flag, _setup.probe_flag,_setup.input_flag,_setup.mdi_interrupt,_setup.sequence_number, cblock->line_number); } } return status; } else { // "should not happen" ERS("BUG: remap_finished(): phase=%d nesting=%d", phase, _setup.remap_level); } return INTERP_OK; } // examine a block for an active items which are remapped // insert all remapped item phases into block.remapping set // return number of remaps found int Interp::find_remappings(block_pointer block, setup_pointer settings) { if (block->f_flag && remapping("F")) { if (remap_in_progress("F")) CONTROLLING_BLOCK(*settings).builtin_used = true; else block->remappings.insert(STEP_SET_FEED_RATE); } if (block->s_flag && remapping("S")) { if (remap_in_progress("S")) CONTROLLING_BLOCK(*settings).builtin_used = true; else block->remappings.insert(STEP_SET_SPINDLE_SPEED); } if (block->t_flag && remapping("T")) { if (remap_in_progress("T")) CONTROLLING_BLOCK(*settings).builtin_used = true; else block->remappings.insert(STEP_PREPARE); } // User defined M-Codes in group 5 if (IS_USER_MCODE(block,settings,5) && !(((block->m_modes[5] == 62) && remap_in_progress("M62")) || ((block->m_modes[5] == 63) && remap_in_progress("M63")) || ((block->m_modes[5] == 64) && remap_in_progress("M64")) || ((block->m_modes[5] == 65) && remap_in_progress("M65")) || ((block->m_modes[5] == 66) && remap_in_progress("M66")) || ((block->m_modes[5] == 67) && remap_in_progress("M67")) || ((block->m_modes[5] == 68) && remap_in_progress("M68")))) // non-recursive behavior block->remappings.insert(STEP_M_5); // User defined M-Codes in group 6 (including M6, M61) // call the remap procedure if it the code in that group is remapped unless: // it's an M6 or M61 and a remap is in progress // (recursion case) if (IS_USER_MCODE(block,settings,6) && !(((block->m_modes[6] == 6) && remap_in_progress("M6")) || ((block->m_modes[6] == 61) && remap_in_progress("M61")))) { block->remappings.insert(STEP_M_6); // then call the remap procedure } // else we get the builtin behaviour // User defined M-Codes in group 7 if (IS_USER_MCODE(block,settings,7)) block->remappings.insert(STEP_M_7); // User defined M-Codes in group 8 if (IS_USER_MCODE(block,settings,8)) block->remappings.insert(STEP_M_8); // User defined M-Codes in group 9 if (IS_USER_MCODE(block,settings,9)) block->remappings.insert(STEP_M_9); // User defined M-Codes in group 10 if (IS_USER_MCODE(block,settings,10)) block->remappings.insert(STEP_M_10); // User-defined motion codes (G0 to G3, G33, G73, G76, G80 to G89) // as modified (possibly) by G53. int mode = block->g_modes[GM_MOTION]; if ((mode != -1) && IS_USER_GCODE(mode)) block->remappings.insert(STEP_MOTION); // this makes it possible to call remapped codes like cycles: // G84.2 x1 y2 // x3 // will execute 'G84.2 x1 y2', then 'G84.2 x3 y2' // provided the remap function explicitly sets motion_mode like so: // def g842(self,**words): // .... // self.motion_mode = 842 // return INTERP_OK mode = block->motion_to_be; if ((mode != -1) && IS_USER_GCODE(mode)) { block->remappings.insert(STEP_MOTION); } // User defined M-Codes in group 4 (stopping) if (IS_USER_MCODE(block,settings,4)) { if (remap_in_progress("M0") || remap_in_progress("M1") || remap_in_progress("M60")) { // detect recursion case // these require real work. // remap_in_progress("M2") || // remap_in_progress("M60") CONTROLLING_BLOCK(*settings).builtin_used = true; } else { block->remappings.insert(STEP_MGROUP4); } } return block->remappings.size(); } /***********************************************************************/ /*! Interp::exit Returned Value: int (INTERP_OK) Side Effects: See below Called By: external programs The system parameters are saved to a file and some parts of the world model are reset. If GET_EXTERNAL_PARAMETER_FILE_NAME provides a non-empty file name, that name is used for the file that is written. Otherwise, the default parameter file name is used. */ int Interp::exit() { char file_name[LINELEN]; GET_EXTERNAL_PARAMETER_FILE_NAME(file_name, (LINELEN - 1)); save_parameters(((file_name[0] == 0) ? RS274NGC_PARAMETER_FILE_NAME_DEFAULT : file_name), _setup.parameters); reset(); // interpreter shutdown Python hook if (python_plugin->is_callable(NULL, DELETE_FUNC)) { bp::object retval, tupleargs, kwargs; bp::list plist; plist.append(*_setup.pythis); // self tupleargs = bp::tuple(plist); kwargs = bp::dict(); python_plugin->call(NULL, DELETE_FUNC, tupleargs, kwargs, retval); if (python_plugin->plugin_status() == PLUGIN_EXCEPTION) { ERM("pycall(%s):\n%s", INIT_FUNC, python_plugin->last_exception().c_str()); // this likely wont make it to the UI's any more so bark on stderr fprintf(stderr, "%s\n",savedError); } } return INTERP_OK; } void Interp::set_loglevel(int level) { _setup.loggingLevel = level; } /***********************************************************************/ /*! rs274_ngc_init Returned Value: int If any of the following errors occur, this returns the error code shown. Otherwise, this returns INTERP_OK. 1. Interp::restore_parameters returns an error code. Side Effects: Many values in the _setup structure are reset. A USE_LENGTH_UNITS canonical command call is made. A SET_FEED_REFERENCE canonical command call is made. A SET_ORIGIN_OFFSETS canonical command call is made. An INIT_CANON call is made. Called By: external programs Currently we are running only in CANON_XYZ feed_reference mode. There is no command regarding feed_reference in the rs274 language (we should try to get one added). The initialization routine, therefore, always calls SET_FEED_REFERENCE(CANON_XYZ). */ int Interp::init() { int k; // starting index in parameters of origin offsets char filename[LINELEN]; double *pars; // short name for _setup.parameters char *iniFileName; IniFile::ErrorCode r; INIT_CANON(); iniFileName = getenv("INI_FILE_NAME"); // the default log file _setup.loggingLevel = 0; _setup.tool_change_at_g30 = 0; _setup.tool_change_quill_up = 0; _setup.tool_change_with_spindle_on = 0; _setup.a_axis_wrapped = 0; _setup.b_axis_wrapped = 0; _setup.c_axis_wrapped = 0; _setup.random_toolchanger = 0; _setup.a_indexer_jnum = -1; // -1 means not used _setup.b_indexer_jnum = -1; // -1 means not used _setup.c_indexer_jnum = -1; // -1 means not used _setup.return_value = 0; _setup.value_returned = 0; _setup.remap_level = 0; // remapped blocks stack index _setup.call_state = CS_NORMAL; _setup.num_spindles = 1; // default arc radius tolerances // we'll try to override these from the INI file below _setup.center_arc_radius_tolerance_inch = CENTER_ARC_RADIUS_TOLERANCE_INCH; _setup.center_arc_radius_tolerance_mm = CENTER_ARC_RADIUS_TOLERANCE_MM; if(iniFileName != NULL) { IniFile inifile; if (inifile.Open(iniFileName) == false) { fprintf(stderr,"Unable to open inifile:%s:\n", iniFileName); } else { bool opt; const char *inistring; inifile.Find(&_setup.tool_change_at_g30, "TOOL_CHANGE_AT_G30", "EMCIO"); inifile.Find(&_setup.tool_change_quill_up, "TOOL_CHANGE_QUILL_UP", "EMCIO"); inifile.Find(&_setup.tool_change_with_spindle_on, "TOOL_CHANGE_WITH_SPINDLE_ON", "EMCIO"); inifile.Find(&_setup.a_axis_wrapped, "WRAPPED_ROTARY", "AXIS_A"); inifile.Find(&_setup.b_axis_wrapped, "WRAPPED_ROTARY", "AXIS_B"); inifile.Find(&_setup.c_axis_wrapped, "WRAPPED_ROTARY", "AXIS_C"); inifile.Find(&_setup.random_toolchanger, "RANDOM_TOOLCHANGER", "EMCIO"); inifile.Find(&_setup.num_spindles, "SPINDLES", "TRAJ"); // First the features that default to ON opt = true; inifile.Find(&opt, "INI_VARS", "RS274NGC"); if (opt) _setup.feature_set |= FEATURE_INI_VARS; opt = true; inifile.Find(&opt, "HAL_PIN_VARS", "RS274NGC"); if (opt) _setup.feature_set |= FEATURE_HAL_PIN_VARS; // Now those that (currently) default to off opt = false; inifile.Find(&opt, "RETAIN_G43", "RS274NGC"); if (opt) _setup.feature_set |= FEATURE_RETAIN_G43; opt = false; inifile.Find(&opt, "OWORD_NARGS", "RS274NGC"); if (opt) _setup.feature_set |= FEATURE_OWORD_N_ARGS; opt = false; inifile.Find(&opt, "NO_DOWNCASE_OWORD", "RS274NGC"); if (opt) _setup.feature_set |= FEATURE_NO_DOWNCASE_OWORD; opt = false; inifile.Find(&opt, "OWORD_WARNONLY", "RS274NGC"); if (opt) _setup.feature_set |= FEATURE_OWORD_WARNONLY; if (NULL != (inistring =inifile.Find("LOCKING_INDEXER_JOINT", "AXIS_A"))) { _setup.a_indexer_jnum = atol(inistring); } if (NULL != (inistring =inifile.Find("LOCKING_INDEXER_JOINT", "AXIS_B"))) { _setup.b_indexer_jnum = atol(inistring); } if (NULL != (inistring =inifile.Find("LOCKING_INDEXER_JOINT", "AXIS_C"))) { _setup.c_indexer_jnum = atol(inistring); } inifile.Find(&_setup.orient_offset, "ORIENT_OFFSET", "RS274NGC"); inifile.Find(&_setup.debugmask, "DEBUG", "EMC"); _setup.debugmask |= EMC_DEBUG_UNCONDITIONAL; if(NULL != (inistring = inifile.Find("LOG_LEVEL", "RS274NGC"))) { _setup.loggingLevel = atol(inistring); } // default the log_file to stderr. if(NULL != (inistring = inifile.Find("LOG_FILE", "RS274NGC"))) { if ((log_file = fopen(inistring, "a")) == NULL) { log_file = stderr; logDebug( "(%d): Unable to open log file:%s, using stderr", getpid(), inistring); } } else { log_file = stderr; } _setup.use_lazy_close = 1; _setup.wizard_root[0] = 0; if(NULL != (inistring = inifile.Find("WIZARD_ROOT", "WIZARD"))) { logDebug("[WIZARD]WIZARD_ROOT:%s", inistring); if (realpath(inistring, _setup.wizard_root) == NULL) { //realpath didn't find the file logDebug("realpath failed to find wizard_root:%s:", inistring); } } logDebug("_setup.wizard_root:%s:", _setup.wizard_root); _setup.program_prefix[0] = 0; if(NULL != (inistring = inifile.Find("PROGRAM_PREFIX", "DISPLAY"))) { // found it char expandinistring[LINELEN]; if (inifile.TildeExpansion(inistring,expandinistring,sizeof(expandinistring))) { logDebug("TildeExpansion failed for: %s",inistring); } if (realpath(expandinistring, _setup.program_prefix) == NULL){ //realpath didn't find the file logDebug("realpath failed to find program_prefix:%s:", inistring); } logDebug("program prefix:%s: prefix:%s:", inistring, _setup.program_prefix); } else { logDebug("PROGRAM_PREFIX not found"); } logDebug("_setup.program_prefix:%s:", _setup.program_prefix); if(NULL != (inistring = inifile.Find("SUBROUTINE_PATH", "RS274NGC"))) { // found it int dct; char* nextdir; char tmpdirs[PATH_MAX+1]; for (dct=0; dct < MAX_SUB_DIRS; dct++) { _setup.subroutines[dct] = NULL; } rtapi_strxcpy(tmpdirs,inistring); nextdir = strtok(tmpdirs,":"); // first token dct = 0; while (1) { char tmp_path[PATH_MAX]; char expandnextdir[LINELEN]; if (inifile.TildeExpansion(nextdir,expandnextdir,sizeof(expandnextdir))) { logDebug("TildeExpansion failed for: %s",nextdir); } if (realpath(expandnextdir, tmp_path) == NULL){ //realpath didn't find the directory logDebug("realpath failed to find subroutines[%d]:%s:",dct,nextdir); _setup.subroutines[dct] = NULL; } else { _setup.subroutines[dct] = strstore(tmp_path); logDebug("program prefix[%d]:%s",dct,_setup.subroutines[dct]); dct++; } if (dct >= MAX_SUB_DIRS) { logDebug("too many entries in SUBROUTINE_PATH, max=%d", MAX_SUB_DIRS); break; } nextdir = strtok(NULL,":"); if (nextdir == NULL) break; // no more tokens } } else { logDebug("SUBROUTINE_PATH not found"); } // subroutine to execute on aborts - for instance to retract // toolchange HAL pins if (NULL != (inistring = inifile.Find("ON_ABORT_COMMAND", "RS274NGC"))) { _setup.on_abort_command = strstore(inistring); logDebug("_setup.on_abort_command=%s", _setup.on_abort_command); } else { _setup.on_abort_command = NULL; } // initialize the Python plugin singleton if (NULL != (inistring = inifile.Find("TOPLEVEL", "PYTHON"))) { int status = python_plugin->configure(iniFileName,"PYTHON"); if (status != PLUGIN_OK) { Error("Python plugin configure() failed, status = %d", status); } } int n = 1; int lineno = -1; _setup.g_remapped.clear(); _setup.m_remapped.clear(); _setup.remaps.clear(); while (NULL != (inistring = inifile.Find("REMAP", "RS274NGC", n, &lineno))) { CHP(parse_remap( inistring, lineno)); n++; } // if exist and within bounds, apply INI file arc tolerances // limiting figures are defined in interp_internal.hh r = inifile.Find( &_setup.center_arc_radius_tolerance_inch, MIN_CENTER_ARC_RADIUS_TOLERANCE_INCH, CENTER_ARC_RADIUS_TOLERANCE_INCH, "CENTER_ARC_RADIUS_TOLERANCE_INCH", "RS274NGC" ); if ((r != IniFile::ERR_NONE) && (r != IniFile::ERR_TAG_NOT_FOUND)) { Error("invalid [RS274NGC]CENTER_ARC_RADIUS_TOLERANCE_INCH in INI file\n"); } r = inifile.Find( &_setup.center_arc_radius_tolerance_mm, MIN_CENTER_ARC_RADIUS_TOLERANCE_MM, CENTER_ARC_RADIUS_TOLERANCE_MM, "CENTER_ARC_RADIUS_TOLERANCE_MM", "RS274NGC" ); if ((r != IniFile::ERR_NONE) && (r != IniFile::ERR_TAG_NOT_FOUND)) { Error("invalid [RS274NGC]CENTER_ARC_RADIUS_TOLERANCE_MM in INI file\n"); } // INI file g52/g92 offset persistence default setting inifile.Find(&_setup.disable_g92_persistence, "DISABLE_G92_PERSISTENCE", "RS274NGC"); // INI file m98/m99 subprogram default setting inifile.Find(&_setup.disable_fanuc_style_sub, "DISABLE_FANUC_STYLE_SUB", "RS274NGC"); logDebug("init: DISABLE_FANUC_STYLE_SUB = %d", _setup.disable_fanuc_style_sub); // close it inifile.Close(); } } _setup.length_units = GET_EXTERNAL_LENGTH_UNIT_TYPE(); USE_LENGTH_UNITS(_setup.length_units); GET_EXTERNAL_PARAMETER_FILE_NAME(filename, LINELEN); if (filename[0] == 0) rtapi_strxcpy(filename, RS274NGC_PARAMETER_FILE_NAME_DEFAULT); CHP(restore_parameters(filename)); pars = _setup.parameters; _setup.origin_index = (int) (pars[5220] + 0.0001); if(_setup.origin_index < 1 || _setup.origin_index > 9) { _setup.origin_index = 1; pars[5220] = 1.0; } k = (5200 + (_setup.origin_index * 20)); _setup.origin_offset_x = USER_TO_PROGRAM_LEN(pars[k + 1]); _setup.origin_offset_y = USER_TO_PROGRAM_LEN(pars[k + 2]); _setup.origin_offset_z = USER_TO_PROGRAM_LEN(pars[k + 3]); _setup.AA_origin_offset = USER_TO_PROGRAM_ANG(pars[k + 4]); _setup.BB_origin_offset = USER_TO_PROGRAM_ANG(pars[k + 5]); _setup.CC_origin_offset = USER_TO_PROGRAM_ANG(pars[k + 6]); _setup.u_origin_offset = USER_TO_PROGRAM_LEN(pars[k + 7]); _setup.v_origin_offset = USER_TO_PROGRAM_LEN(pars[k + 8]); _setup.w_origin_offset = USER_TO_PROGRAM_LEN(pars[k + 9]); SET_G5X_OFFSET(_setup.origin_index, _setup.origin_offset_x , _setup.origin_offset_y , _setup.origin_offset_z , _setup.AA_origin_offset, _setup.BB_origin_offset, _setup.CC_origin_offset, _setup.u_origin_offset , _setup.v_origin_offset , _setup.w_origin_offset); // Restore G92 offset if DISABLE_G92_PERSISTENCE not set in INI file. // This can't be done with the static _required_parameters[], where // the .vars file contents would reflect that setting, so instead // edit the restored parameters here. if (_setup.disable_g92_persistence) // Persistence disabled: clear g92 parameters for (k = 5210; k < 5220; k++) pars[k] = 0; if (pars[5210]) { _setup.axis_offset_x = USER_TO_PROGRAM_LEN(pars[5211]); _setup.axis_offset_y = USER_TO_PROGRAM_LEN(pars[5212]); _setup.axis_offset_z = USER_TO_PROGRAM_LEN(pars[5213]); _setup.AA_axis_offset = USER_TO_PROGRAM_ANG(pars[5214]); _setup.BB_axis_offset = USER_TO_PROGRAM_ANG(pars[5215]); _setup.CC_axis_offset = USER_TO_PROGRAM_ANG(pars[5216]); _setup.u_axis_offset = USER_TO_PROGRAM_LEN(pars[5217]); _setup.v_axis_offset = USER_TO_PROGRAM_LEN(pars[5218]); _setup.w_axis_offset = USER_TO_PROGRAM_LEN(pars[5219]); } else { _setup.axis_offset_x = 0.0; _setup.axis_offset_y = 0.0; _setup.axis_offset_z = 0.0; _setup.AA_axis_offset = 0.0; _setup.BB_axis_offset = 0.0; _setup.CC_axis_offset = 0.0; _setup.u_axis_offset = 0.0; _setup.v_axis_offset = 0.0; _setup.w_axis_offset = 0.0; } SET_G92_OFFSET(_setup.axis_offset_x , _setup.axis_offset_y , _setup.axis_offset_z , _setup.AA_axis_offset, _setup.BB_axis_offset, _setup.CC_axis_offset, _setup.u_axis_offset , _setup.v_axis_offset , _setup.w_axis_offset); _setup.rotation_xy = pars[k+10]; SET_XY_ROTATION(pars[k+10]); SET_FEED_REFERENCE(CANON_XYZ); //_setup.active_g_codes initialized below //_setup.active_m_codes initialized below //_setup.active_settings initialized below //_setup.block1 does not need initialization _setup.blocktext[0] = 0; //_setup.current_slot set in Interp::synch //_setup.current_x set in Interp::synch //_setup.current_y set in Interp::synch //_setup.current_z set in Interp::synch _setup.cutter_comp_side = false; _setup.arc_not_allowed = false; _setup.cycle_il_flag = false; _setup.distance_mode = MODE_ABSOLUTE; _setup.ijk_distance_mode = MODE_INCREMENTAL; // backwards compatibility _setup.feed_mode = UNITS_PER_MINUTE; //_setup.feed_override set in Interp::synch //_setup.feed_rate set in Interp::synch _setup.filename[0] = 0; _setup.file_pointer = NULL; //_setup.flood set in Interp::synch // _setup.tool_offset_index = 1; // unused - removed, mah //_setup.length_units set in Interp::synch _setup.line_length = 0; _setup.linetext[0] = 0; //_setup.mist set in Interp::synch _setup.motion_mode = G_80; //_setup.origin_index set above //_setup.parameters set above //_setup.parameter_occurrence does not need initialization //_setup.parameter_numbers does not need initialization //_setup.parameter_values does not need initialization //_setup.percent_flag does not need initialization //_setup.plane set in Interp::synch _setup.probe_flag = false; _setup.toolchange_flag = false; _setup.input_flag = false; _setup.input_index = -1; _setup.input_digital = false; _setup.program_x = 0.; /* for cutter comp */ _setup.program_y = 0.; /* for cutter comp */ _setup.program_z = 0.; /* for cutter comp */ _setup.cutter_comp_firstmove = true; //_setup.retract_mode does not need initialization //_setup.selected_tool_slot set in Interp::synch _setup.sequence_number = 0; /*DOES THIS NEED TO BE AT TOP? */ //_setup.speed set in Interp::synch _setup.speed_feed_mode = CANON_INDEPENDENT; // setup.spindle_mode set in interp_synch; //_setup.speed_override set in Interp::synch //_setup.spindle_turning set in Interp::synch //_setup.stack does not need initialization //_setup.stack_index does not need initialization ZERO_EMC_POSE(_setup.tool_offset); //_setup.tool_max set in Interp::synch //_setup.tool_table set in Interp::synch //_setup.traverse_rate set in Interp::synch //_setup.adaptive_feed set in Interp::synch //_setup.feed_hold set in Interp::synch // initialization stuff for subroutines and control structures _setup.call_level = 0; _setup.defining_sub = 0; _setup.skipping_o = 0; _setup.offset_map.clear(); _setup.lathe_diameter_mode = false; _setup.parameters[5599] = 1.0; // enable (DEBUG, ) output memcpy(_readers, default_readers, sizeof(default_readers)); long axis_mask = GET_EXTERNAL_AXIS_MASK(); if(!(axis_mask & AXIS_MASK_X)) _readers[(int)'x'] = 0; if(!(axis_mask & AXIS_MASK_Y)) _readers[(int)'y'] = 0; if(!(axis_mask & AXIS_MASK_Z)) _readers[(int)'z'] = 0; if(!(axis_mask & AXIS_MASK_A)) _readers[(int)'a'] = 0; if(!(axis_mask & AXIS_MASK_B)) _readers[(int)'b'] = 0; if(!(axis_mask & AXIS_MASK_C)) _readers[(int)'c'] = 0; if(!(axis_mask & AXIS_MASK_U)) _readers[(int)'u'] = 0; if(!(axis_mask & AXIS_MASK_V)) _readers[(int)'v'] = 0; if(!(axis_mask & AXIS_MASK_W)) _readers[(int)'w'] = 0; synch(); //synch first, then update the interface write_g_codes((block_pointer) NULL, &_setup); write_m_codes((block_pointer) NULL, &_setup); write_settings(&_setup); init_tool_parameters(); // Synch rest of settings to external world // call __init__(self) once in toplevel module if defined // once fully set up and sync()ed if ((iniFileName != NULL) && _setup.init_once && PYUSABLE ) { // initialize any python global predefined named parameters // walk the namedparams module for callables and add their names as predefs try { bp::object npmod = python_plugin->main_namespace[NAMEDPARAMS_MODULE]; bp::dict predef_dict = bp::extract(npmod.attr("__dict__")); bp::list keys = (bp::list) predef_dict.keys(); for (int i = 0; i < bp::len(keys); i++) { std::string key = bp::extract(keys[i]); bp::object value = predef_dict[key]; if (PyCallable_Check(value.ptr())) { CHP(init_python_predef_parameter(key.c_str())); } } } catch (const bp::error_already_set&) { std::string exception_msg; bool unexpected = false; // KeyError is ok - this means the namedparams module doesn't exist if (!PyErr_ExceptionMatches(PyExc_KeyError)) { // something else, strange exception_msg = handle_pyerror(); unexpected = true; } bp::handle_exception(); PyErr_Clear(); CHKS(unexpected, "exception adding Python predefined named parameter: %s", exception_msg.c_str()); } if (python_plugin->is_callable(NULL, INIT_FUNC)) { bp::object retval, tupleargs, kwargs; bp::list plist; plist.append(*_setup.pythis); // self tupleargs = bp::tuple(plist); kwargs = bp::dict(); python_plugin->call(NULL, INIT_FUNC, tupleargs, kwargs, retval); CHKS(python_plugin->plugin_status() == PLUGIN_EXCEPTION, "pycall(%s):\n%s", INIT_FUNC, python_plugin->last_exception().c_str()); } } _setup.init_once = 0; return INTERP_OK; } void Interp::set_loop_on_main_m99(bool state) { // Enable/disable M99 main program endless looping _setup.loop_on_main_m99 = state; } /***********************************************************************/ /*! Interp::load_tool_table Returned Value: int If any of the following errors occur, this returns the error code shown. Otherwise, this returns INTERP_OK. 1. _setup.tool_max is larger than CANON_TOOL_MAX: NCE_TOOL_MAX_TOO_LARGE Side Effects: _setup.tool_table[] is modified. Called By: Interp::synch external programs This function calls the canonical interface function GET_EXTERNAL_TOOL_TABLE to load the whole tool table into the _setup. The CANON_TOOL_MAX is an upper limit for this software. The _setup.tool_max is intended to be set for a particular machine. */ int Interp::load_tool_table() { int n; for (n = 0; n < CANON_POCKETS_MAX; n++) { _setup.tool_table[n] = GET_EXTERNAL_TOOL_TABLE(n); } set_tool_parameters(); return INTERP_OK; } /***********************************************************************/ /*! Interp::open Returned Value: int If any of the following errors occur, this returns the error code shown. Otherwise it returns INTERP_OK. 1. A file is already open: NCE_A_FILE_IS_ALREADY_OPEN 2. The name of the file is too long: NCE_FILE_NAME_TOO_LONG 3. The file cannot be opened: NCE_UNABLE_TO_OPEN_FILE Side Effects: See below Called By: external programs The file is opened for reading and _setup.file_pointer is set. The file name is copied into _setup.filename. The _setup.sequence_number, is set to zero. Interp::reset() is called, changing several more _setup attributes. The manual [NCMS, page 3] discusses the use of the "%" character at the beginning of a "tape". It is not clear whether it is intended that every NC-code file should begin with that character. In the following, "uses percents" means the first non-blank line of the file must consist of nothing but the percent sign, with optional leading and trailing white space, and there must be a second line of the same sort later on in the file. If a file uses percents, execution stops at the second percent line. Any lines after the second percent line are ignored. In this interpreter (recalling that M2 and M30 always ends execution): 1. If execution of a file is ended by M2 or M30 (not necessarily on the last line of the file), then it is optional that the file uses percents. 2. If execution of a file is not ended by M2 or M30, then it is required that the file uses percents. If the file being opened uses percents, this function turns on the _setup.percent flag, reads any initial blank lines, and reads the first line with the "%". If not, after reading enough to determine that, this function puts the file pointer back at the beginning of the file. */ int Interp::open(const char *filename) //!< string: the name of the input NC-program file { char *line; int index; int length; logOword("open()"); if(_setup.use_lazy_close && _setup.lazy_closing) { _setup.use_lazy_close = 0; // so that close will work close(); _setup.use_lazy_close = 1; _setup.lazy_closing = 0; } CHKS((_setup.file_pointer != NULL), NCE_A_FILE_IS_ALREADY_OPEN); CHKS((strlen(filename) > (LINELEN - 1)), NCE_FILE_NAME_TOO_LONG); _setup.file_pointer = fopen(filename, "r"); CHKS((_setup.file_pointer == NULL), NCE_UNABLE_TO_OPEN_FILE, filename); line = _setup.linetext; for (index = -1; index == -1;) { /* skip blank lines */ CHKS((fgets(line, LINELEN, _setup.file_pointer) == NULL), NCE_FILE_ENDED_WITH_NO_PERCENT_SIGN); length = strlen(line); if (length == (LINELEN - 1)) { // line is too long. need to finish reading the line to recover for (; fgetc(_setup.file_pointer) != '\n' && !feof(_setup.file_pointer);); ERS(NCE_COMMAND_TOO_LONG); } for (index = (length - 1); // index set on last char (index >= 0) && (isspace(line[index])); index--); } if (line[index] == '%') { for (index--; (index >= 0) && (isspace(line[index])); index--); if (index == -1) { _setup.percent_flag = true; _setup.sequence_number = 1; // We have already read the first line // and we are not going back to it. } else { fseek(_setup.file_pointer, 0, SEEK_SET); _setup.percent_flag = false; _setup.sequence_number = 0; // Going back to line 0 } } else { fseek(_setup.file_pointer, 0, SEEK_SET); _setup.percent_flag = false; _setup.sequence_number = 0; // Going back to line 0 } rtapi_strxcpy(_setup.filename, filename); reset(); return INTERP_OK; } int Interp::read_inputs(setup_pointer settings) { // logDebug("read_inputs probe=%d input=%d toolchange=%d", // settings->probe_flag, settings->toolchange_flag, settings->input_flag); if (settings->probe_flag) { CHKS((GET_EXTERNAL_QUEUE_EMPTY() == 0), NCE_QUEUE_IS_NOT_EMPTY_AFTER_PROBING); set_probe_data(&_setup); settings->probe_flag = false; } if (settings->toolchange_flag) { CHKS((GET_EXTERNAL_QUEUE_EMPTY() == 0), _("Queue is not empty after tool change")); refresh_actual_position(&_setup); load_tool_table(); settings->toolchange_flag = false; } // always track toolchanger-fault and toolchanger-reason codes settings->parameters[5600] = GET_EXTERNAL_TC_FAULT(); settings->parameters[5601] = GET_EXTERNAL_TC_REASON(); if (settings->input_flag) { CHKS((GET_EXTERNAL_QUEUE_EMPTY() == 0), NCE_QUEUE_IS_NOT_EMPTY_AFTER_INPUT); if (settings->input_digital) { // we are checking for a digital input settings->parameters[5399] = GET_EXTERNAL_DIGITAL_INPUT(settings->input_index, (settings->parameters[5399] != 0.0)); } else { // checking for analog input settings->parameters[5399] = GET_EXTERNAL_ANALOG_INPUT(settings->input_index, settings->parameters[5399]); } settings->input_flag = false; } return INTERP_OK; } /***********************************************************************/ /*! Interp::read Returned Value: int If any of the following errors occur, this returns the error code shown. Otherwise, this returns: a. INTERP_ENDFILE if the only non-white character on the line is %, b. INTERP_EXECUTE_FINISH if the first character of the close_and_downcased line is a slash, and c. INTERP_OK otherwise. 1. The command and_setup.file_pointer are both NULL: INTERP_FILE_NOT_OPEN 2. The probe_flag is true but the HME command queue is not empty: NCE_QUEUE_IS_NOT_EMPTY_AFTER_PROBING 3. If read_text (which gets a line of NC code from file) or parse_line (which parses the line) returns an error code, this returns that code. Side Effects: _setup.sequence_number is incremented. The executing block is filled with data. Called By: external programs This reads a line of NC-code from the command string or, (if the command string is NULL) from the currently open file. The _setup.line_length will be set by read_text. This will be zero if the line is blank or consists of nothing but a slash. If the length is not zero, this parses the line into the _setup.block1. */ int Interp::_read(const char *command) //!< may be NULL or a string to read { static char name[] = "Interp::read"; int read_status; // this input reading code is in the wrong place. It should be executed // in sync(), not here. This would make correct parameter values available // without doing a read() (e.g. from Python). // Unfortunately synch() isn't called in preview (gcodemodule) #if 0 if (_setup.probe_flag) { CHKS((GET_EXTERNAL_QUEUE_EMPTY() == 0), NCE_QUEUE_IS_NOT_EMPTY_AFTER_PROBING); set_probe_data(&_setup); _setup.probe_flag = false; } if (_setup.toolchange_flag) { CHKS((GET_EXTERNAL_QUEUE_EMPTY() == 0), _("Queue is not empty after tool change")); refresh_actual_position(&_setup); load_tool_table(); _setup.toolchange_flag = false; } // always track toolchanger-fault and toolchanger-reason codes _setup.parameters[5600] = GET_EXTERNAL_TC_FAULT(); _setup.parameters[5601] = GET_EXTERNAL_TC_REASON(); if (_setup.input_flag) { CHKS((GET_EXTERNAL_QUEUE_EMPTY() == 0), NCE_QUEUE_IS_NOT_EMPTY_AFTER_INPUT); if (_setup.input_digital) { // we are checking for a digital input _setup.parameters[5399] = GET_EXTERNAL_DIGITAL_INPUT(_setup.input_index, (_setup.parameters[5399] != 0.0)); } else { // checking for analog input _setup.parameters[5399] = GET_EXTERNAL_ANALOG_INPUT(_setup.input_index, _setup.parameters[5399]); } _setup.input_flag = false; } #endif // Support for restartable Python handlers during Auto mode // Conceptually a O_call or O_endsub/O_return block might need to be executed several // times in a row until it finally returns INTERP_OK, the reason being that all Python // procedures might 'yield INTERP_EXECUTE_FINISH' or execute a queue buster an arbitrary number // of times. So they need to be called again post-sync and post-read-input possibly several times. // // the task readahead logic assumes a block execution may result in a single INTERP_EXECUTE_FINISH // and readahead is started thereafter immediately. Modifying the readahead logic would be a massive // change. Therefore we use the trick to suppress reading the next block as required, which means // we will get several calls to execute() in a row which are used to finish the handlers. This is // needed for remapped codes which might involve up to three Python handlers, and Python oword subs. // Note this is not an issue for NGC oword procedures. The call/return logic will set _setup.call_state to // CS_REEXEC_PROLOG, CS_REEXEC_PYBODY, CS_REEXEC_EPILOG or CS_REEXEC_PYOSUB before returning, which // also indicates the point which handler needs to be restarted // // We use the following conditions to 'skip reading the next block and stay on the same block' // until done as follows: // // 1. block.o_type = O_call and // block.call_type in {CT_PYTHON_OWORD_SUB, CT_REMAP} and // _setup.call_state > CS_NORMAL // // 2. block.o_type in {O_endsub, O_return} and // block.call_type in {CT_PYTHON_OWORD_SUB, CT_REMAP} and // _setup.call_state > CS_NORMAL // // handlers eventually return INTERP_OK, which sets _setup.call_state to CS_NORMAL. Then // normal readahead continues. // A call frame is tagged with the eblock->call_type since this potentially needs to persist across // several blocks. Inside the execute_call()/execute_return() logic we use the frame call type // to decide what to do. // The handler reexec code will call read_inputs() just before continuation. block_pointer eblock = &EXECUTING_BLOCK(_setup); if ((_setup.call_state > CS_NORMAL) && (eblock->call_type != CT_NGC_OWORD_SUB) && (eblock->call_type != CT_NGC_M98_SUB) && (eblock->call_type != CT_NONE) && ((eblock->o_type == O_call) || (eblock->o_type == M_98) || (eblock->o_type == O_return) || (eblock->o_type == O_endsub) || (eblock->o_type == M_99))) { logDebug("read(): skipping read"); _setup.line_length = 0; _setup.linetext[0] = 0; return INTERP_OK; } _setup.call_state = CS_NORMAL; CHP(read_inputs(&_setup)); CHKN(((command == NULL) && (_setup.file_pointer == NULL)), INTERP_FILE_NOT_OPEN); _setup.parameters[5420] = _setup.current_x; _setup.parameters[5421] = _setup.current_y; _setup.parameters[5422] = _setup.current_z; _setup.parameters[5423] = _setup.AA_current; _setup.parameters[5424] = _setup.BB_current; _setup.parameters[5425] = _setup.CC_current; _setup.parameters[5426] = _setup.u_current; _setup.parameters[5427] = _setup.v_current; _setup.parameters[5428] = _setup.w_current; if(_setup.file_pointer) { EXECUTING_BLOCK(_setup).offset = ftell(_setup.file_pointer); } read_status = read_text(command, _setup.file_pointer, _setup.linetext, _setup.blocktext, &_setup.line_length); if (read_status == INTERP_ERROR && _setup.skipping_to_sub) { _setup.skipping_to_sub = NULL; } if(command)logDebug("%s:[cmd]:|%s|", name, command); else logDebug("%s:|%s|", name, _setup.linetext); if ((read_status == INTERP_EXECUTE_FINISH) || (read_status == INTERP_OK)) { if (_setup.line_length != 0) { CHP(parse_line(_setup.blocktext, &(EXECUTING_BLOCK(_setup)), &_setup)); } else // Blank line (zero length) { /* RUM - this case reached when the block delete '/' character is used, or READ_FULL_COMMENT is false and a comment is the only content of a line. If a block o-type is in effect, block->o_number needs to be incremented to allow o-extensions to work. Note that the the block is 'refreshed' by init_block(), not created new, so this is a legal operation on block1. */ // mah: FIXME test this - no idea what this is about; o_number is history if (EXECUTING_BLOCK(_setup).o_type != O_none) { // Clear o_type, this isn't line isn't a command... EXECUTING_BLOCK(_setup).o_type = 0; } } } else if (read_status == INTERP_ENDFILE) { // If skipping but not defining the main program, we hit EOF // before finding the sub we're looking for; error out CHKS((_setup.skipping_o != NULL), "Failed to find sub 'O%s' before EOF", _setup.skipping_o); } else ERP(read_status); return read_status; } int Interp::read(const char *command) { int status; if ((status = _read(command)) > INTERP_MIN_ERROR) { unwind_call(status, __FILE__,__LINE__,__FUNCTION__); } return status; } // Reset interpreter state and terminate a call in progress by // falling back to toplevel in a controlled way. Idempotent. int Interp::unwind_call(int status, const char *file, int line, const char *function) { logDebug("unwind_call: call_level=%d status=%s from %s %s:%d", _setup.call_level, interp_status(status), function, file, line); for(; _setup.call_level > 0; _setup.call_level--) { int i; context * sub = _setup.sub_context + _setup.call_level - 1; free_named_parameters(&_setup.sub_context[_setup.call_level]); if(sub->subName) { logDebug("unwind_call leaving sub '%s'", sub->subName); sub->subName = 0; } if (sub->call_type != CT_NGC_M98_SUB) // M98: pass #1..#30 from parent for(i=0; isaved_params[i]; } // When called from Interp::close via Interp::reset, this one is NULL if (!_setup.file_pointer) continue; // some frames may not have a filename and hence a position to seek to // on return, like Python handlers // needed to make sure this works in rs274 -n 0 (continue on error) mode if (sub->filename && sub->filename[0]) { if(0 != strcmp(_setup.filename, sub->filename)) { fclose(_setup.file_pointer); _setup.file_pointer = fopen(sub->filename, "r"); logDebug("unwind_call: reopening '%s' at %ld", sub->filename, sub->position); rtapi_strxcpy(_setup.filename, sub->filename); } fseek(_setup.file_pointer, sub->position, SEEK_SET); } _setup.sequence_number = sub->sequence_number; logDebug("unwind_call: setting sequence number=%d from frame %d", _setup.sequence_number,_setup.call_level); } // call_level == 0 here. if(_setup.sub_name) { logDebug("unwind_call: exiting current sub '%s'\n", _setup.sub_name); _setup.sub_name = 0; } _setup.remap_level = 0; // reset remapping stack _setup.defining_sub = 0; _setup.skipping_o = 0; _setup.skipping_to_sub = 0; _setup.offset_map.clear(); _setup.mdi_interrupt = false; qc_reset(); return INTERP_OK; } int Interp::read() { return read(0); } /***********************************************************************/ /*! Interp::reset Returned Value: int (INTERP_OK) Side Effects: See below Called By: external programs Interp::close Interp::exit Interp::open This function resets the parts of the _setup model having to do with reading and interpreting one line. It does not affect the parts of the model dealing with a file being open; Interp::open and Interp::close do that. There is a hierarchy of resetting the interpreter. Each of the following calls does everything the ones above it do. Interp::reset() Interp::close() Interp::init() In addition, Interp::synch and Interp::restore_parameters (both of which are called by Interp::init) change the model. */ int Interp::reset() { _setup.call_state = CS_NORMAL; //!!!KL According to the comment, //!!!KL this should not be here because this is for //!!!KL more than one line. //!!!KL But the comment seems wrong -- it is only called at open, close, //!!!KL init times which should affect the more global structure. //!!!KL (also called by external -- but probably OK) // // initialization stuff for subroutines and control structures _setup.linetext[0] = 0; _setup.blocktext[0] = 0; _setup.line_length = 0; // drop any queued points in canon ON_RESET(); unwind_call(INTERP_OK, __FILE__,__LINE__,__FUNCTION__); return INTERP_OK; } /***********************************************************************/ /*! Interp::restore_parameters Returned Value: If any of the following errors occur, this returns the error code shown. Otherwise it returns INTERP_OK. 1. The parameter file cannot be opened for reading: NCE_UNABLE_TO_OPEN_FILE 2. A parameter index is out of range: NCE_PARAMETER_NUMBER_OUT_OF_RANGE 3. The parameter file is not in increasing order: NCE_PARAMETER_FILE_OUT_OF_ORDER Side Effects: See below Called By: external programs Interp::init This function restores the parameters from a file, modifying the parameters array. Usually parameters is _setup.parameters. The file contains lines of the form: e.g. 5161 10.456 The variable numbers must be in increasing order, and certain parameters must be included, as given in the _required_parameters array. These are the axis offsets, the origin index (5220), and nine sets of origin offsets. Any parameter not given a value in the file has its value set to zero. */ int Interp::restore_parameters(const char *filename) //!< name of parameter file to read { FORCE_LC_NUMERIC_C; FILE *infile; char line[256]; int variable; double value; int required; // number of next required parameter int index; // index into _required_parameters double *pars; // short name for _setup.parameters int k; // it's OK if the parameter file doesn't exist yet // it'll be created in due course with some default values if(access(filename, F_OK) == -1) return INTERP_OK; // open original for reading infile = fopen(filename, "r"); CHKS((infile == NULL), _("Unable to open parameter file: '%s'"), filename); pars = _setup.parameters; k = 0; index = 0; required = _required_parameters[index++]; while (feof(infile) == 0) { if (fgets(line, 256, infile) == NULL) { break; } // try for a variable-value match in the file if (sscanf(line, "%d %lf", &variable, &value) == 2) { CHKS(((variable <= 0) || (variable >= RS274NGC_MAX_PARAMETERS)), NCE_PARAMETER_NUMBER_OUT_OF_RANGE); for (; k < RS274NGC_MAX_PARAMETERS; k++) { if (k > variable) { fclose(infile); ERS(NCE_PARAMETER_FILE_OUT_OF_ORDER); } else if (k == variable) { pars[k] = value; if (k == required) required = _required_parameters[index++]; k++; break; } else // if (k < variable) { if (k == required) required = _required_parameters[index++]; pars[k] = 0; } } } } fclose(infile); for (; k < RS274NGC_MAX_PARAMETERS; k++) { pars[k] = 0; } return INTERP_OK; } /***********************************************************************/ /*! Interp::save_parameters Returned Value: If any of the following errors occur, this returns the error code shown. Otherwise it returns INTERP_OK. 1. The existing file cannot be renamed: NCE_CANNOT_CREATE_BACKUP_FILE 2. The renamed file cannot be opened to read: NCE_CANNOT_OPEN_BACKUP_FILE 3. The new file cannot be opened to write: NCE_CANNOT_OPEN_VARIABLE_FILE 4. A parameter index is out of range: NCE_PARAMETER_NUMBER_OUT_OF_RANGE 5. The renamed file is out of order: NCE_PARAMETER_FILE_OUT_OF_ORDER Side Effects: See below Called By: external programs Interp::exit A file containing variable-value assignments is updated. The old version of the file is saved under a different name. For each variable-value pair in the old file, a line is written in the new file giving the current value of the variable. File lines have the form: e.g. 5161 10.456 If a required parameter is missing from the input file, this does not complain, but does write it in the output file. */ int Interp::save_parameters(const char *filename, //!< name of file to write const double parameters[]) //!< parameters to save { FORCE_LC_NUMERIC_C; FILE *infile; FILE *outfile; char line[PATH_MAX]; int variable; double value; int required; // number of next required parameter int index; // index into _required_parameters int k; std::string tempfile = std::string(filename) + ".new"; outfile = fopen(tempfile.c_str(), "w"); CHKS((outfile == NULL), NCE_CANNOT_OPEN_VARIABLE_FILE); infile = fopen(filename, "r"); if(!infile) infile = fopen("/dev/null", "r"); k = 0; index = 0; required = _required_parameters[index++]; while (feof(infile) == 0) { if (fgets(line, sizeof(line), infile) == NULL) { break; } // try for a variable-value match if (sscanf(line, "%d %lf", &variable, &value) == 2) { CHKS(((variable <= 0) || (variable >= RS274NGC_MAX_PARAMETERS)), NCE_PARAMETER_NUMBER_OUT_OF_RANGE); for (; k < RS274NGC_MAX_PARAMETERS; k++) { if (k > variable) { fclose(infile); fclose(outfile); ERS(NCE_PARAMETER_FILE_OUT_OF_ORDER); } else if (k == variable) { snprintf(line, sizeof(line), "%d\t%f\n", k, parameters[k]); fputs(line, outfile); if (k == required) required = _required_parameters[index++]; k++; break; } else if (k == required) // know (k < variable) { snprintf(line, sizeof(line), "%d\t%f\n", k, parameters[k]); fputs(line, outfile); required = _required_parameters[index++]; } } } } fclose(infile); for (; k < RS274NGC_MAX_PARAMETERS; k++) { if (k == required) { snprintf(line, sizeof(line), "%d\t%f\n", k, parameters[k]); fputs(line, outfile); required = _required_parameters[index++]; } } fflush(outfile); fdatasync(fileno(outfile)); fclose(outfile); std::string bakfile = std::string(filename) + RS274NGC_PARAMETER_FILE_BACKUP_SUFFIX; unlink(bakfile.c_str()); if(link(filename, bakfile.c_str()) < 0) perror("link (updating variable file)"); if(rename(tempfile.c_str(), filename) < 0) perror("rename (updating variable file)"); return INTERP_OK; } /***********************************************************************/ /*! Interp::synch Returned Value: int (INTERP_OK) Side Effects: sets the value of many attribute of _setup by calling various GET_EXTERNAL_xxx functions. Called By: Interp::init external programs This function gets the _setup world model in synch with the rest of the controller. */ int Interp::synch() { char file_name[LINELEN]; _setup.current_x = GET_EXTERNAL_POSITION_X(); _setup.current_y = GET_EXTERNAL_POSITION_Y(); _setup.current_z = GET_EXTERNAL_POSITION_Z(); _setup.control_mode = GET_EXTERNAL_MOTION_CONTROL_MODE(); _setup.tolerance = GET_EXTERNAL_MOTION_CONTROL_TOLERANCE(); _setup.naivecam_tolerance = GET_EXTERNAL_MOTION_CONTROL_NAIVECAM_TOLERANCE(); _setup.AA_current = GET_EXTERNAL_POSITION_A(); _setup.BB_current = GET_EXTERNAL_POSITION_B(); _setup.CC_current = GET_EXTERNAL_POSITION_C(); _setup.u_current = GET_EXTERNAL_POSITION_U(); _setup.v_current = GET_EXTERNAL_POSITION_V(); _setup.w_current = GET_EXTERNAL_POSITION_W(); _setup.control_mode = GET_EXTERNAL_MOTION_CONTROL_MODE(); /* misnomer: _setup.current_pocket,selected_pocket ** These variables are actually indexes to sequential tool ** data structs (not real pockets). ** Future renaming will affect current usage in python remaps. */ _setup.current_pocket = GET_EXTERNAL_TOOL_SLOT(); _setup.selected_pocket = GET_EXTERNAL_SELECTED_TOOL_SLOT(); _setup.feed_rate = GET_EXTERNAL_FEED_RATE(); _setup.flood = GET_EXTERNAL_FLOOD(); _setup.length_units = GET_EXTERNAL_LENGTH_UNIT_TYPE(); _setup.mist = GET_EXTERNAL_MIST(); _setup.plane = GET_EXTERNAL_PLANE(); _setup.traverse_rate = GET_EXTERNAL_TRAVERSE_RATE(); _setup.feed_override = GET_EXTERNAL_FEED_OVERRIDE_ENABLE(); _setup.adaptive_feed = GET_EXTERNAL_ADAPTIVE_FEED_ENABLE(); _setup.feed_hold = GET_EXTERNAL_FEED_HOLD_ENABLE(); for (int s = 0; s < EMCMOT_MAX_SPINDLES; s++){ _setup.speed[s] = GET_EXTERNAL_SPEED(s); _setup.spindle_turning[s] = GET_EXTERNAL_SPINDLE(s); _setup.speed_override[s] = GET_EXTERNAL_SPINDLE_OVERRIDE_ENABLE(s); _setup.spindle_mode[s] = CONSTANT_RPM; } GET_EXTERNAL_PARAMETER_FILE_NAME(file_name, (LINELEN - 1)); save_parameters(((file_name[0] == 0) ? RS274NGC_PARAMETER_FILE_NAME_DEFAULT : file_name), _setup.parameters); load_tool_table(); /* must set _setup.tool_max first */ // read_inputs(&_setup); // input/probe/toolchange write_settings(&_setup); return INTERP_OK; } /***********************************************************************/ /***********************************************************************/ /* The functions in this section are to extract information from the interpreter. */ /***********************************************************************/ /*! Interp::active_g_codes Returned Value: none Side Effects: copies active G-codes into the codes array Called By: external programs See documentation of write_g_codes. */ void Interp::active_g_codes(int *codes) //!< array of codes to copy into { int n; for (n = 0; n < ACTIVE_G_CODES; n++) { codes[n] = _setup.active_g_codes[n]; } } /***********************************************************************/ /*! Interp::active_m_codes Returned Value: none Side Effects: copies active M codes into the codes array Called By: external programs See documentation of write_m_codes. */ void Interp::active_m_codes(int *codes) //!< array of codes to copy into { int n; for (n = 0; n < ACTIVE_M_CODES; n++) { codes[n] = _setup.active_m_codes[n]; } } /***********************************************************************/ /*! Interp::active_settings Returned Value: none Side Effects: copies active F, S settings into array Called By: external programs See documentation of write_settings. */ void Interp::active_settings(double *settings) //!< array of settings to copy into { int n; for (n = 0; n < ACTIVE_SETTINGS; n++) { settings[n] = _setup.active_settings[n]; } } //TODO rename to read_state_tag? /** * Unpack state information from a motion line tag into * TASK_STAT-style arrays of G-/M-Codes. * * This method allows us to keep the existing infrastructure for g * code status / state storage intact. */ int Interp::active_modes(int *g_codes, int *m_codes, double *settings, StateTag const &tag) { int i; // Pre-checks on fields if (!tag.is_valid()) { return INTERP_ERROR; } // Extract as-is field values directly into appropriate array // position g_codes[0] = tag.fields[GM_FIELD_LINE_NUMBER]; g_codes[1] = tag.fields[GM_FIELD_MOTION_MODE]; g_codes[2] = tag.fields[GM_FIELD_G_MODE_0]; g_codes[3] = tag.fields[GM_FIELD_PLANE]; g_codes[4] = tag.fields[GM_FIELD_CUTTER_COMP]; // Unpack flags into appropriate G-code equivalents g_codes[5] = tag.flags[GM_FLAG_UNITS] ? G_20 : G_21; g_codes[6] = tag.flags[GM_FLAG_DISTANCE_MODE] ? G_90 : G_91; g_codes[7] = tag.flags[GM_FLAG_FEED_INVERSE_TIME] ? G_93 : tag.flags[GM_FLAG_FEED_UPM] ? G_94 : G_95; g_codes[8] = tag.fields[GM_FIELD_ORIGIN]; g_codes[9] = tag.flags[GM_FLAG_TOOL_OFFSETS_ON] ? G_43 : G_49; g_codes[10] = tag.flags[GM_FLAG_RETRACT_OLDZ] ? G_98 : G_99; g_codes[11] = tag.flags[GM_FLAG_BLEND] ? G_64 : tag.flags[GM_FLAG_EXACT_STOP] ? G_61_1 : G_61; // Empty status code, leave as default g_codes[12] = -1; g_codes[13] = tag.flags[GM_FLAG_CSS_MODE] ? G_97 : G_96; g_codes[14] = tag.flags[GM_FLAG_IJK_ABS] ? G_90_1 : G_91_1; g_codes[15] = tag.flags[GM_FLAG_DIAMETER_MODE] ? G_7 : G_8; g_codes[16] = tag.flags[GM_FLAG_G92_IS_APPLIED] ? G_92_3: G_92_2; //TODO remove redundant line number? m_codes[0] = tag.fields[GM_FIELD_LINE_NUMBER]; m_codes[1] = tag.fields[GM_FIELD_M_MODES_4]; m_codes[2] = !tag.flags[GM_FLAG_SPINDLE_ON] ? 5 : tag.flags[GM_FLAG_SPINDLE_CW] ? 3 : 4; m_codes[3] = tag.fields[GM_FIELD_TOOLCHANGE]; m_codes[4] = tag.flags[GM_FLAG_MIST] ? 7 : tag.flags[GM_FLAG_FLOOD] ? -1 : 9; m_codes[5] = tag.flags[GM_FLAG_FLOOD] ? 8 : -1; // Copied from write_m_codes if (tag.flags[GM_FLAG_FEED_OVERRIDE]) { if (tag.flags[GM_FLAG_SPEED_OVERRIDE]) m_codes[6] = 48; else m_codes[6] = 50; } else if (tag.flags[GM_FLAG_SPEED_OVERRIDE]) { m_codes[6] = 51; } else m_codes[6] = 49; m_codes[7] = /* 7 overrides */ tag.flags[GM_FLAG_ADAPTIVE_FEED] ? 52 : -1; m_codes[8] = /* 8 overrides */ tag.flags[GM_FLAG_FEED_HOLD] ? 53 : -1; // Copy float-type state for (i=0; i -1) && (stack_index < STACK_LEN)) { the_name = _setup.stack[stack_index]; for (n = 0; n < (max_size - 1); n++) { if (the_name[n] != 0) function_name[n] = the_name[n]; else break; } function_name[n] = 0; } else function_name[0] = 0; return function_name; } /***********************************************************************/ /* Interp::ini_load() Returned Value: INTERP_OK, RS274NGC_ERROR Side Effects: An INI file containing values for global variables is used to update the globals Called By: emctask before calling Interp::init() The file looks like this: [RS274NGC] VARIABLE_FILE = rs274ngc.var */ int Interp::ini_load(const char *filename) { IniFile inifile; const char *inistring; // open it if (inifile.Open(filename) == false) { logDebug("Unable to open inifile:%s:", filename); return -1; } logDebug("Opened inifile:%s:", filename); char parameter_file_name[LINELEN]={}; if (NULL != (inistring = inifile.Find("PARAMETER_FILE", "RS274NGC"))) { strncpy(parameter_file_name, inistring, LINELEN); if (parameter_file_name[LINELEN-1] != '\0') { logDebug("%s:[RS274NGC]PARAMETER_FILE is too long (max len %d)", filename, LINELEN-1); } else { logDebug("found PARAMETER_FILE:%s:", parameter_file_name); } } else { // not found, leave RS274NGC_PARAMETER_FILE alone logDebug("did not find PARAMETER_FILE"); } SET_PARAMETER_FILE_NAME(parameter_file_name); // close it inifile.Close(); CHKS((strlen(parameter_file_name) == 0), _("Parameter file name is missing")); return 0; } int Interp::init_tool_parameters() { if (_setup.random_toolchanger) { // random_toolchanger: tool at startup expected _setup.parameters[5400] = _setup.tool_table[0].toolno; _setup.parameters[5401] = _setup.tool_table[0].offset.tran.x; _setup.parameters[5402] = _setup.tool_table[0].offset.tran.y; _setup.parameters[5403] = _setup.tool_table[0].offset.tran.z; _setup.parameters[5404] = _setup.tool_table[0].offset.a; _setup.parameters[5405] = _setup.tool_table[0].offset.b; _setup.parameters[5406] = _setup.tool_table[0].offset.c; _setup.parameters[5407] = _setup.tool_table[0].offset.u; _setup.parameters[5408] = _setup.tool_table[0].offset.v; _setup.parameters[5409] = _setup.tool_table[0].offset.w; _setup.parameters[5410] = _setup.tool_table[0].diameter; _setup.parameters[5411] = _setup.tool_table[0].frontangle; _setup.parameters[5412] = _setup.tool_table[0].backangle; _setup.parameters[5413] = _setup.tool_table[0].orientation; } else { // non random_toolchanger: no tool at startup, one-time init if (_setup.tool_table[0].toolno == -1) { default_tool_parameters(); } } return 0; } int Interp::default_tool_parameters() { _setup.parameters[5400] = 0; // toolno _setup.parameters[5401] = 0; // x offset _setup.parameters[5402] = 0; // y offset RESERVED _setup.parameters[5403] = 0; // z offset _setup.parameters[5404] = 0; // a offset RESERVED _setup.parameters[5405] = 0; // b offset RESERVED _setup.parameters[5406] = 0; // c offset RESERVED _setup.parameters[5407] = 0; // u offset RESERVED _setup.parameters[5408] = 0; // v offset RESERVED _setup.parameters[5409] = 0; // w offset RESERVED _setup.parameters[5410] = 0; // diameter _setup.parameters[5411] = 0; // frontangle _setup.parameters[5412] = 0; // backangle _setup.parameters[5413] = 0; // orientation return 0; } int Interp::set_tool_parameters() { // invoke to set tool parameters for current tool (pocket==0) // when a tool is absent, set default (zero offset) tool parameters if ((! _setup.random_toolchanger) && (_setup.tool_table[0].toolno <= 0)) { default_tool_parameters(); return 0; } _setup.parameters[5400] = _setup.tool_table[0].toolno; _setup.parameters[5401] = _setup.tool_table[0].offset.tran.x; _setup.parameters[5402] = _setup.tool_table[0].offset.tran.y; _setup.parameters[5403] = _setup.tool_table[0].offset.tran.z; _setup.parameters[5404] = _setup.tool_table[0].offset.a; _setup.parameters[5405] = _setup.tool_table[0].offset.b; _setup.parameters[5406] = _setup.tool_table[0].offset.c; _setup.parameters[5407] = _setup.tool_table[0].offset.u; _setup.parameters[5408] = _setup.tool_table[0].offset.v; _setup.parameters[5409] = _setup.tool_table[0].offset.w; _setup.parameters[5410] = _setup.tool_table[0].diameter; _setup.parameters[5411] = _setup.tool_table[0].frontangle; _setup.parameters[5412] = _setup.tool_table[0].backangle; _setup.parameters[5413] = _setup.tool_table[0].orientation; return 0; } int Interp::enter_remap(void) { _setup.remap_level++; if (_setup.remap_level == MAX_NESTED_REMAPS) { _setup.remap_level = 0; ERS("maximum nesting of remapped blocks exceeded"); } // push onto block stack CONTROLLING_BLOCK(_setup) = EXECUTING_BLOCK(_setup); CONTROLLING_BLOCK(_setup).breadcrumbs = 0; // clear trail // set later but tested for in remap_in_progress() CONTROLLING_BLOCK(_setup).executing_remap = NULL; // remember the line where remap was discovered logRemap("enter_remap into %d - saved_line_number=%d", _setup.remap_level, _setup.sequence_number); CONTROLLING_BLOCK(_setup).saved_line_number = _setup.sequence_number; _setup.sequence_number = 0; return INTERP_OK; } int Interp::leave_remap(void) { // restore the line number where remap was found logRemap("leave_remap from %d propagate saved_line_number=%d", _setup.remap_level, CONTROLLING_BLOCK(_setup).saved_line_number); _setup.sequence_number = CONTROLLING_BLOCK(_setup).saved_line_number; _setup.blocks[_setup.remap_level].executing_remap = NULL; _setup.remap_level--; // drop one nesting level if (_setup.remap_level < 0) { ERS("BUG: remap_level < 0 : %d",_setup.remap_level); } return INTERP_OK; } int Interp::on_abort(int reason, const char *message) { logDebug("on_abort reason=%d message='%s'", reason, message); reset(); _setup.mdi_interrupt = false; // clear in case set by an interrupted remapped procedure // if set, may cause a "Queue is not empty after tool change" error _setup.toolchange_flag = false; _setup.probe_flag = false; _setup.input_flag = false; if (_setup.on_abort_command == NULL) { return -1; } char cmd[LINELEN]; snprintf(cmd,sizeof(cmd), "%s [%d]",_setup.on_abort_command, reason); int status = execute(cmd); ERP(status); return status; } // spun out from interp_o_word so we can use it to test ngc file accessibility during // config file parsing (REMAP... ngc=) // Will expand ~ to user's home path // searches this sequence until it finds a match: // 1) checks if path is already the full path // 2) tries adding the INI defined program prefix to path // 3) tries adding the INI defined subroutine prefix to path // 4) tries adding the INI defined whizard prefix to path FILE *Interp::find_ngc_file(setup_pointer settings,const char *basename, char *foundhere ) { FILE *newFP = NULL; char tmpFileName[PATH_MAX+1]; char newFileName[PATH_MAX+1]; char foundPlace[PATH_MAX+1]; int dct; wordexp_t exp_result; // #1 check if this is the full path already // expand user path wordexp(basename, &exp_result, 0); // add .ngc to expanded path snprintf(tmpFileName, sizeof(tmpFileName), "%s.ngc", exp_result.we_wordv[0]); // copy to newFileName - in case this is the one... size_t chk = snprintf(newFileName, sizeof(newFileName), "%s", tmpFileName); // found a file we can open? if (chk < sizeof(newFileName)){ newFP = fopen(newFileName, "r"); } // #2 then look in the program_prefix place if (!newFP) { // expand '~' into user path wordexp(settings->program_prefix, &exp_result, 0); chk = snprintf(newFileName, sizeof(newFileName), "%s/%s", exp_result.we_wordv[0], tmpFileName); // found a file we can open? if (chk < sizeof(newFileName)){ newFP = fopen(newFileName, "r"); } } // #3 then look in the list of subroutines prefixes if (!newFP) { for (dct = 0; dct < MAX_SUB_DIRS; dct++) { if (!settings->subroutines[dct]) continue; // expand '~' into user path wordexp(settings->subroutines[dct], &exp_result, 0); chk = snprintf(newFileName, sizeof(newFileName), "%s/%s", exp_result.we_wordv[0], tmpFileName); // found a file we can open? if (chk < sizeof(newFileName)){ newFP = fopen(newFileName, "r"); if (newFP) { // logOword("fopen: |%s|", newFileName); break; // use first occurrence in dir search } } } } // #4 if still not found, search the wizard tree // Wiz directory already expands the '~' to user path if (!newFP) { int ret; // walks the directory hierarchy ? ret = findFile(settings->wizard_root, tmpFileName, foundPlace); if (INTERP_OK == ret) { // create the long name chk = snprintf(newFileName, sizeof(newFileName), "%s/%s", foundPlace, tmpFileName); // found a file we can open? if (chk < sizeof(newFileName)){ newFP = fopen(newFileName, "r"); } } } // pass what we found if (foundhere && (newFP != NULL)) strcpy(foundhere, newFileName); // Not sure this is needed but the internet told me wordfree(&exp_result); return newFP; } const char *strstore(const char *s) { static std::unordered_set stringtable; using namespace std; if (s == nullptr) { throw invalid_argument("strstore(): NULL argument"); } auto pair = stringtable.insert(s); return pair.first->c_str(); } context_struct::context_struct() : position(0), sequence_number(0), filename(""), subName(""), m98_loop_counter(-1), context_status(0), call_type(0) { memset(saved_params, 0, sizeof(saved_params)); memset(saved_g_codes, 0, sizeof(saved_g_codes)); memset(saved_m_codes, 0, sizeof(saved_m_codes)); memset(saved_settings, 0, sizeof(saved_settings)); } void context_struct::clear() { new (this) context_struct(); }