Initial wasm simulator checkpoint

This commit is contained in:
CNC Local
2026-05-22 04:43:21 +08:00
commit 55d9fff9f0
54 changed files with 5523 additions and 0 deletions

174
core/CMakeLists.txt Normal file
View File

@@ -0,0 +1,174 @@
cmake_minimum_required(VERSION 3.20)
project(cnc_sim_wasm_core LANGUAGES CXX)
option(CNC_SIM_ENABLE_LINUXCNC_BRIDGE "Build the experimental LinuxCNC canon bridge" OFF)
option(CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND "Build the native LinuxCNC librs274 backend" OFF)
set(CNC_SIM_LINUXCNC_ROOT "" CACHE PATH "LinuxCNC source root for the experimental bridge")
set(cnc_sim_core_sources
src/canon_event_sink.cpp
src/cnc_sim_api.cpp
src/gcode_backend.cpp
src/rtcp_kinematics.cpp
src/simulator_gcode_controls.cpp
src/smoke_gcode_parser.cpp
)
if(CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND)
if(EMSCRIPTEN)
message(FATAL_ERROR "CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND is native-only; it links LinuxCNC librs274")
endif()
if(NOT CNC_SIM_LINUXCNC_ROOT)
message(FATAL_ERROR "Set CNC_SIM_LINUXCNC_ROOT to the LinuxCNC source root")
endif()
list(APPEND cnc_sim_core_sources
src/linuxcnc_canon_bridge.cpp
src/linuxcnc_rs274_backend.cpp
)
endif()
add_library(cnc_sim_objects OBJECT
${cnc_sim_core_sources}
)
target_include_directories(cnc_sim_objects
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)
target_compile_features(cnc_sim_objects PUBLIC cxx_std_17)
if(CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND)
find_package(Python3 REQUIRED COMPONENTS Development)
target_compile_definitions(cnc_sim_objects
PRIVATE
CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND
)
target_include_directories(cnc_sim_objects
PRIVATE
${Python3_INCLUDE_DIRS}
${CNC_SIM_LINUXCNC_ROOT}/src
${CNC_SIM_LINUXCNC_ROOT}/src/emc
${CNC_SIM_LINUXCNC_ROOT}/src/emc/nml_intf
${CNC_SIM_LINUXCNC_ROOT}/src/emc/rs274ngc
${CNC_SIM_LINUXCNC_ROOT}/src/emc/motion
${CNC_SIM_LINUXCNC_ROOT}/include
)
endif()
add_library(cnc_sim_core STATIC
$<TARGET_OBJECTS:cnc_sim_objects>
)
target_include_directories(cnc_sim_core
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)
if(CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND)
target_link_directories(cnc_sim_core
PUBLIC
${CNC_SIM_LINUXCNC_ROOT}/lib
)
target_link_libraries(cnc_sim_core
PUBLIC
rs274
tooldata
Python3::Python
)
target_link_options(cnc_sim_core
PUBLIC
"-Wl,-rpath,${CNC_SIM_LINUXCNC_ROOT}/lib"
)
endif()
if(NOT EMSCRIPTEN)
add_executable(cnc_sim_api_smoke
tests/cnc_sim_api_smoke.cpp
)
target_link_libraries(cnc_sim_api_smoke PRIVATE cnc_sim_core)
target_include_directories(cnc_sim_api_smoke PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
add_executable(canon_event_sink_smoke
tests/canon_event_sink_smoke.cpp
)
target_link_libraries(canon_event_sink_smoke PRIVATE cnc_sim_core)
target_include_directories(canon_event_sink_smoke PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
add_executable(rtcp_kinematics_smoke
tests/rtcp_kinematics_smoke.cpp
)
target_link_libraries(rtcp_kinematics_smoke PRIVATE cnc_sim_core)
target_include_directories(rtcp_kinematics_smoke PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
add_executable(simulator_gcode_controls_smoke
tests/simulator_gcode_controls_smoke.cpp
)
target_link_libraries(simulator_gcode_controls_smoke PRIVATE cnc_sim_core)
target_include_directories(simulator_gcode_controls_smoke PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
add_executable(cnc_sim_dump
tools/cnc_sim_dump.cpp
)
target_link_libraries(cnc_sim_dump PRIVATE cnc_sim_core)
target_include_directories(cnc_sim_dump PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
if(CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND)
add_executable(cnc_sim_api_linuxcnc_rs274_smoke
tests/cnc_sim_api_linuxcnc_rs274_smoke.cpp
)
target_link_libraries(cnc_sim_api_linuxcnc_rs274_smoke PRIVATE cnc_sim_core)
target_include_directories(cnc_sim_api_linuxcnc_rs274_smoke PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
endif()
endif()
if(CNC_SIM_ENABLE_LINUXCNC_BRIDGE)
if(NOT CNC_SIM_LINUXCNC_ROOT)
message(FATAL_ERROR "Set CNC_SIM_LINUXCNC_ROOT to the LinuxCNC source root")
endif()
add_library(cnc_sim_linuxcnc_canon_bridge STATIC
src/linuxcnc_canon_bridge.cpp
)
target_include_directories(cnc_sim_linuxcnc_canon_bridge
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/src
${CNC_SIM_LINUXCNC_ROOT}/src/emc
${CNC_SIM_LINUXCNC_ROOT}/src/emc/nml_intf
${CNC_SIM_LINUXCNC_ROOT}/src/emc/rs274ngc
${CNC_SIM_LINUXCNC_ROOT}/src
${CNC_SIM_LINUXCNC_ROOT}/include
)
target_link_libraries(cnc_sim_linuxcnc_canon_bridge PRIVATE cnc_sim_core)
endif()
if(EMSCRIPTEN)
add_executable(cnc_sim_wasm
$<TARGET_OBJECTS:cnc_sim_objects>
)
target_include_directories(cnc_sim_wasm
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/src
)
set_target_properties(cnc_sim_wasm PROPERTIES
OUTPUT_NAME "cnc_sim"
SUFFIX ".js"
)
target_link_options(cnc_sim_wasm PRIVATE
"--no-entry"
"-sMODULARIZE=1"
"-sEXPORT_NAME=createCncSimModule"
"-sALLOW_MEMORY_GROWTH=1"
"-sALLOW_TABLE_GROWTH=1"
"-sEXPORTED_FUNCTIONS=['_malloc','_free','_cnc_sim_create','_cnc_sim_destroy','_cnc_sim_reset','_cnc_sim_set_dialect','_cnc_sim_set_event_callback','_cnc_sim_load_config_json','_cnc_sim_parse_program','_cnc_sim_last_error']"
"-sEXPORTED_RUNTIME_METHODS=['ccall','cwrap','UTF8ToString','stringToUTF8','lengthBytesUTF8','addFunction','removeFunction']"
)
endif()

View File

@@ -0,0 +1,88 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#if defined(_WIN32)
#define CNC_SIM_EXPORT __declspec(dllexport)
#else
#define CNC_SIM_EXPORT __attribute__((visibility("default")))
#endif
typedef struct CncSimHandle CncSimHandle;
typedef enum CncSimDialect {
CNC_SIM_DIALECT_LINUXCNC = 0,
CNC_SIM_DIALECT_FANUC = 1,
CNC_SIM_DIALECT_SIEMENS = 2
} CncSimDialect;
typedef enum CncSimEventType {
CNC_SIM_EVENT_NONE = 0,
CNC_SIM_EVENT_ERROR = 1,
CNC_SIM_EVENT_COMMENT = 2,
CNC_SIM_EVENT_SET_UNITS = 3,
CNC_SIM_EVENT_SET_PLANE = 4,
CNC_SIM_EVENT_SET_FEED = 5,
CNC_SIM_EVENT_SET_SPINDLE = 6,
CNC_SIM_EVENT_TOOL_CHANGE = 7,
CNC_SIM_EVENT_DWELL = 8,
CNC_SIM_EVENT_RAPID = 9,
CNC_SIM_EVENT_LINEAR_FEED = 10,
CNC_SIM_EVENT_ARC_FEED = 11,
CNC_SIM_EVENT_PROBE = 12,
CNC_SIM_EVENT_PROGRAM_END = 13,
CNC_SIM_EVENT_RTCP_PIVOT = 14,
CNC_SIM_EVENT_KINEMATICS_SWITCH = 15,
CNC_SIM_EVENT_RTCP_STATE = 16,
CNC_SIM_EVENT_SET_G5X_OFFSET = 17,
CNC_SIM_EVENT_SET_G92_OFFSET = 18,
CNC_SIM_EVENT_SET_XY_ROTATION = 19
} CncSimEventType;
typedef struct CncSimPose {
double x;
double y;
double z;
double a;
double b;
double c;
double u;
double v;
double w;
} CncSimPose;
typedef struct CncSimEvent {
uint32_t version;
CncSimEventType type;
int32_t line;
int32_t plane;
int32_t tool;
double feed;
double spindle;
double dwell_seconds;
CncSimPose start;
CncSimPose end;
CncSimPose center;
int32_t arc_turns;
int32_t reserved;
} CncSimEvent;
typedef int (*CncSimEventCallback)(const CncSimEvent *event, void *user_data);
CNC_SIM_EXPORT CncSimHandle *cnc_sim_create(void);
CNC_SIM_EXPORT void cnc_sim_destroy(CncSimHandle *handle);
CNC_SIM_EXPORT void cnc_sim_reset(CncSimHandle *handle);
CNC_SIM_EXPORT int cnc_sim_set_dialect(CncSimHandle *handle, CncSimDialect dialect);
CNC_SIM_EXPORT int cnc_sim_set_event_callback(CncSimHandle *handle, CncSimEventCallback callback, void *user_data);
CNC_SIM_EXPORT int cnc_sim_load_config_json(CncSimHandle *handle, const char *json, size_t json_len);
CNC_SIM_EXPORT int cnc_sim_parse_program(CncSimHandle *handle, const char *program, size_t program_len);
CNC_SIM_EXPORT const char *cnc_sim_last_error(CncSimHandle *handle);
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,242 @@
#include "canon_event_sink.h"
#include "rtcp_kinematics.h"
void CanonEventSink::reset() {
position_ = {};
plane_ = 17;
unit_scale_ = 1.0;
feed_ = 0.0;
spindle_ = 0.0;
selected_tool_ = 0;
callback_status_ = 0;
kinematics_type_ = 1;
}
void CanonEventSink::set_callback(CncSimEventCallback callback, void *user_data) {
callback_ = callback;
user_data_ = user_data;
callback_status_ = 0;
}
void CanonEventSink::clear_callback_status() {
callback_status_ = 0;
}
bool CanonEventSink::callback_aborted() const {
return callback_status_ != 0;
}
void CanonEventSink::configure_rtcp(bool enabled, double tool_length) {
rtcp_enabled_ = enabled;
default_rtcp_tool_length_ = tool_length;
rtcp_tool_length_ = tool_length;
}
void CanonEventSink::set_tool_length(int h_code, double tool_length) {
if (h_code <= 0) {
default_rtcp_tool_length_ = tool_length;
if (rtcp_h_code_ == 0) {
rtcp_tool_length_ = tool_length;
}
return;
}
tool_lengths_[h_code] = tool_length;
if (rtcp_h_code_ == h_code) {
rtcp_tool_length_ = tool_length;
}
}
void CanonEventSink::set_rtcp_state(bool enabled, int h_code, int line) {
rtcp_enabled_ = enabled;
rtcp_h_code_ = enabled ? h_code : 0;
if (enabled) {
const auto tool = tool_lengths_.find(h_code);
rtcp_tool_length_ = tool != tool_lengths_.end() ? tool->second : default_rtcp_tool_length_;
}
CncSimEvent event = base_event(CNC_SIM_EVENT_RTCP_STATE, line);
event.feed = rtcp_enabled_ ? 1.0 : 0.0;
event.tool = rtcp_h_code_;
event.dwell_seconds = rtcp_tool_length_;
emit(event);
}
void CanonEventSink::switch_kinematics(int kinematics_type, bool rtcp_enabled, int line) {
kinematics_type_ = kinematics_type;
rtcp_enabled_ = rtcp_enabled;
CncSimEvent event = base_event(CNC_SIM_EVENT_KINEMATICS_SWITCH, line);
event.reserved = kinematics_type_;
event.feed = rtcp_enabled_ ? 1.0 : 0.0;
emit(event);
}
void CanonEventSink::set_g5x_offset(int index, const CncSimPose &offset, int line) {
CncSimEvent event = base_event(CNC_SIM_EVENT_SET_G5X_OFFSET, line);
event.tool = index;
event.start = offset;
emit(event);
}
void CanonEventSink::set_g92_offset(const CncSimPose &offset, int line) {
CncSimEvent event = base_event(CNC_SIM_EVENT_SET_G92_OFFSET, line);
event.start = offset;
emit(event);
}
void CanonEventSink::set_xy_rotation(double angle_degrees, int line) {
CncSimEvent event = base_event(CNC_SIM_EVENT_SET_XY_ROTATION, line);
event.feed = angle_degrees;
emit(event);
}
void CanonEventSink::use_length_units(double scale, int line) {
unit_scale_ = scale;
CncSimEvent event = base_event(CNC_SIM_EVENT_SET_UNITS, line);
event.feed = scale;
emit(event);
}
void CanonEventSink::select_plane(int plane, int line) {
plane_ = plane;
CncSimEvent event = base_event(CNC_SIM_EVENT_SET_PLANE, line);
event.plane = plane_;
emit(event);
}
void CanonEventSink::set_feed_rate(double feed, int line) {
feed_ = feed;
CncSimEvent event = base_event(CNC_SIM_EVENT_SET_FEED, line);
event.feed = feed_;
emit(event);
}
void CanonEventSink::set_spindle_speed(double spindle, int line) {
spindle_ = spindle;
CncSimEvent event = base_event(CNC_SIM_EVENT_SET_SPINDLE, line);
event.spindle = spindle_;
emit(event);
}
void CanonEventSink::select_tool(int tool) {
selected_tool_ = tool;
}
void CanonEventSink::change_tool(int line) {
CncSimEvent event = base_event(CNC_SIM_EVENT_TOOL_CHANGE, line);
event.tool = selected_tool_;
emit(event);
}
void CanonEventSink::straight_traverse(int line, const CncSimPose &end) {
CncSimEvent event = base_event(CNC_SIM_EVENT_RAPID, line);
event.start = position_;
event.end = end;
emit(event);
emit_rtcp_pivot(line, position_, end);
position_ = end;
}
void CanonEventSink::straight_feed(int line, const CncSimPose &end) {
CncSimEvent event = base_event(CNC_SIM_EVENT_LINEAR_FEED, line);
event.start = position_;
event.end = end;
emit(event);
emit_rtcp_pivot(line, position_, end);
position_ = end;
}
void CanonEventSink::arc_feed(int line, const CncSimPose &end, const CncSimPose &center, int turns) {
CncSimEvent event = base_event(CNC_SIM_EVENT_ARC_FEED, line);
event.start = position_;
event.end = end;
event.center = center;
event.arc_turns = turns;
emit(event);
emit_rtcp_pivot(line, position_, end);
position_ = end;
}
void CanonEventSink::dwell(double seconds, int line) {
CncSimEvent event = base_event(CNC_SIM_EVENT_DWELL, line);
event.dwell_seconds = seconds;
emit(event);
}
void CanonEventSink::program_end(int line) {
emit(base_event(CNC_SIM_EVENT_PROGRAM_END, line));
}
void CanonEventSink::emit_raw(CncSimEvent event) {
if (event.version == 0) {
event.version = 1;
}
emit(event);
}
const CncSimPose &CanonEventSink::position() const {
return position_;
}
int CanonEventSink::plane() const {
return plane_;
}
double CanonEventSink::unit_scale() const {
return unit_scale_;
}
double CanonEventSink::feed_rate() const {
return feed_;
}
double CanonEventSink::spindle_speed() const {
return spindle_;
}
int CanonEventSink::selected_tool() const {
return selected_tool_;
}
int CanonEventSink::kinematics_type() const {
return kinematics_type_;
}
bool CanonEventSink::rtcp_enabled() const {
return rtcp_enabled_;
}
double CanonEventSink::rtcp_tool_length() const {
return rtcp_tool_length_;
}
void CanonEventSink::emit(CncSimEvent event) {
if (callback_ && callback_status_ == 0) {
callback_status_ = callback_(&event, user_data_);
}
}
void CanonEventSink::emit_rtcp_pivot(int line, const CncSimPose &start, const CncSimPose &end) {
if (!rtcp_enabled_ || rtcp_tool_length_ == 0.0 || callback_status_ != 0) {
return;
}
CncSimEvent event = base_event(CNC_SIM_EVENT_RTCP_PIVOT, line);
event.start = rtcp_pivot_from_tool_tip(start, rtcp_tool_length_);
event.end = rtcp_pivot_from_tool_tip(end, rtcp_tool_length_);
const RtcpVector tool = rtcp_tool_vector_from_pose(end, rtcp_tool_length_);
event.center.x = tool.x;
event.center.y = tool.y;
event.center.z = tool.z;
emit(event);
}
CncSimEvent CanonEventSink::base_event(CncSimEventType type, int line) const {
CncSimEvent event{};
event.version = 1;
event.type = type;
event.line = line;
event.plane = plane_;
event.feed = feed_;
event.spindle = spindle_;
event.tool = selected_tool_;
return event;
}

View File

@@ -0,0 +1,64 @@
#pragma once
#include "cnc_sim_api.h"
#include <unordered_map>
class CanonEventSink {
public:
void reset();
void set_callback(CncSimEventCallback callback, void *user_data);
void clear_callback_status();
bool callback_aborted() const;
void configure_rtcp(bool enabled, double tool_length);
void set_tool_length(int h_code, double tool_length);
void set_rtcp_state(bool enabled, int h_code, int line);
void switch_kinematics(int kinematics_type, bool rtcp_enabled, int line);
void set_g5x_offset(int index, const CncSimPose &offset, int line);
void set_g92_offset(const CncSimPose &offset, int line);
void set_xy_rotation(double angle_degrees, int line);
void use_length_units(double scale, int line);
void select_plane(int plane, int line);
void set_feed_rate(double feed, int line);
void set_spindle_speed(double spindle, int line);
void select_tool(int tool);
void change_tool(int line);
void straight_traverse(int line, const CncSimPose &end);
void straight_feed(int line, const CncSimPose &end);
void arc_feed(int line, const CncSimPose &end, const CncSimPose &center, int turns);
void dwell(double seconds, int line);
void program_end(int line);
void emit_raw(CncSimEvent event);
const CncSimPose &position() const;
int plane() const;
double unit_scale() const;
double feed_rate() const;
double spindle_speed() const;
int selected_tool() const;
int kinematics_type() const;
bool rtcp_enabled() const;
double rtcp_tool_length() const;
private:
void emit(CncSimEvent event);
void emit_rtcp_pivot(int line, const CncSimPose &start, const CncSimPose &end);
CncSimEvent base_event(CncSimEventType type, int line) const;
CncSimEventCallback callback_ = nullptr;
void *user_data_ = nullptr;
int callback_status_ = 0;
CncSimPose position_{};
int plane_ = 17;
double unit_scale_ = 1.0;
double feed_ = 0.0;
double spindle_ = 0.0;
int selected_tool_ = 0;
bool rtcp_enabled_ = false;
double default_rtcp_tool_length_ = 0.0;
double rtcp_tool_length_ = 0.0;
int rtcp_h_code_ = 0;
int kinematics_type_ = 1;
std::unordered_map<int, double> tool_lengths_;
};

235
core/src/cnc_sim_api.cpp Normal file
View File

@@ -0,0 +1,235 @@
#include "cnc_sim_api.h"
#include "canon_event_sink.h"
#include "gcode_backend.h"
#include <algorithm>
#include <cctype>
#include <cstdlib>
#include <string>
struct CncSimHandle {
CncSimDialect dialect = CNC_SIM_DIALECT_LINUXCNC;
GcodeBackendKind backend = GcodeBackendKind::Smoke;
std::string last_error;
CanonEventSink sink;
};
namespace {
void set_error(CncSimHandle *handle, const std::string &message) {
if (handle) {
handle->last_error = message;
}
}
std::string config_text(const char *json, size_t json_len) {
if (!json || json_len == 0) {
return {};
}
return std::string(json, json + json_len);
}
std::string lower_ascii(std::string value) {
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char ch) {
return static_cast<char>(std::tolower(ch));
});
return value;
}
bool contains_backend_value(const std::string &json, const char *value) {
std::string compact;
compact.reserve(json.size());
for (char ch : json) {
if (!std::isspace(static_cast<unsigned char>(ch))) {
compact.push_back(static_cast<char>(std::tolower(static_cast<unsigned char>(ch))));
}
}
const std::string needle = std::string("\"backend\":\"") + value + "\"";
return compact.find(needle) != std::string::npos;
}
std::string compact_lower_json(const std::string &json) {
std::string compact;
compact.reserve(json.size());
for (char ch : json) {
if (!std::isspace(static_cast<unsigned char>(ch))) {
compact.push_back(static_cast<char>(std::tolower(static_cast<unsigned char>(ch))));
}
}
return compact;
}
bool contains_bool_value(const std::string &compact, const char *key, bool *value) {
const std::string true_needle = std::string("\"") + key + "\":true";
if (compact.find(true_needle) != std::string::npos) {
*value = true;
return true;
}
const std::string false_needle = std::string("\"") + key + "\":false";
if (compact.find(false_needle) != std::string::npos) {
*value = false;
return true;
}
return false;
}
bool find_number_value(const std::string &compact, const char *key, double *value) {
const std::string needle = std::string("\"") + key + "\":";
const size_t pos = compact.find(needle);
if (pos == std::string::npos) {
return false;
}
const char *start = compact.c_str() + pos + needle.size();
char *end = nullptr;
const double parsed = std::strtod(start, &end);
if (end == start) {
return false;
}
*value = parsed;
return true;
}
void apply_tool_length_table(CanonEventSink &sink, const std::string &compact) {
const std::string needle = "\"toollengths\":{";
size_t pos = compact.find(needle);
if (pos == std::string::npos) {
return;
}
pos += needle.size();
while (pos < compact.size() && compact[pos] != '}') {
if (compact[pos] == ',') {
++pos;
continue;
}
if (compact[pos] != '"') {
break;
}
++pos;
char *h_end = nullptr;
const long h_code = std::strtol(compact.c_str() + pos, &h_end, 10);
if (h_end == compact.c_str() + pos || *h_end != '"') {
break;
}
pos = static_cast<size_t>(h_end - compact.c_str()) + 1;
if (pos >= compact.size() || compact[pos] != ':') {
break;
}
++pos;
char *length_end = nullptr;
const double tool_length = std::strtod(compact.c_str() + pos, &length_end);
if (length_end == compact.c_str() + pos) {
break;
}
if (h_code > 0) {
sink.set_tool_length(static_cast<int>(h_code), tool_length);
}
pos = static_cast<size_t>(length_end - compact.c_str());
}
}
int apply_config(CncSimHandle *handle, const std::string &json) {
if (json.empty()) {
return 0;
}
const std::string compact = compact_lower_json(json);
if (contains_backend_value(json, "smoke")) {
handle->backend = GcodeBackendKind::Smoke;
} else if (contains_backend_value(json, "linuxcnc-rs274")) {
handle->backend = GcodeBackendKind::LinuxCncRs274;
} else if (compact.find("\"backend\"") != std::string::npos) {
set_error(handle, "unsupported backend in config");
return -1;
}
bool rtcp_enabled = false;
double tool_length = 0.0;
const bool has_rtcp_enabled = contains_bool_value(compact, "rtcp", &rtcp_enabled) ||
contains_bool_value(compact, "enabled", &rtcp_enabled);
const bool has_tool_length = find_number_value(compact, "toollength", &tool_length) ||
find_number_value(compact, "tool_length", &tool_length);
if (has_rtcp_enabled || has_tool_length) {
handle->sink.configure_rtcp(rtcp_enabled, tool_length);
}
apply_tool_length_table(handle->sink, compact);
return 0;
}
} // namespace
extern "C" {
CncSimHandle *cnc_sim_create(void) {
return new CncSimHandle();
}
void cnc_sim_destroy(CncSimHandle *handle) {
delete handle;
}
void cnc_sim_reset(CncSimHandle *handle) {
if (!handle) {
return;
}
handle->last_error.clear();
handle->sink.reset();
}
int cnc_sim_set_dialect(CncSimHandle *handle, CncSimDialect dialect) {
if (!handle) {
return -1;
}
switch (dialect) {
case CNC_SIM_DIALECT_LINUXCNC:
case CNC_SIM_DIALECT_FANUC:
case CNC_SIM_DIALECT_SIEMENS:
handle->dialect = dialect;
return 0;
default:
set_error(handle, "unsupported dialect");
return -1;
}
}
int cnc_sim_set_event_callback(CncSimHandle *handle, CncSimEventCallback callback, void *user_data) {
if (!handle) {
return -1;
}
handle->sink.set_callback(callback, user_data);
return 0;
}
int cnc_sim_load_config_json(CncSimHandle *handle, const char *json, size_t json_len) {
if (!handle) {
return -1;
}
if (!json && json_len != 0) {
set_error(handle, "null config buffer");
return -1;
}
handle->last_error.clear();
return apply_config(handle, config_text(json, json_len));
}
int cnc_sim_parse_program(CncSimHandle *handle, const char *program, size_t program_len) {
if (!handle) {
return -1;
}
handle->last_error.clear();
const int rc = parse_gcode_with_backend(handle->backend, handle->sink, program, program_len, &handle->last_error);
if (rc != 0 && handle->last_error.empty()) {
handle->last_error = "program parse failed";
}
return rc;
}
const char *cnc_sim_last_error(CncSimHandle *handle) {
if (!handle) {
return "null simulator handle";
}
return handle->last_error.c_str();
}
} // extern "C"

View File

@@ -0,0 +1,44 @@
#include "gcode_backend.h"
#ifdef CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND
#include "linuxcnc_rs274_backend.h"
#endif
#include "smoke_gcode_parser.h"
const char *gcode_backend_name(GcodeBackendKind backend) {
switch (backend) {
case GcodeBackendKind::Smoke:
return "smoke";
case GcodeBackendKind::LinuxCncRs274:
return "linuxcnc-rs274";
default:
return "unknown";
}
}
int parse_gcode_with_backend(GcodeBackendKind backend,
CanonEventSink &sink,
const char *program,
size_t program_len,
std::string *error) {
switch (backend) {
case GcodeBackendKind::Smoke: {
SmokeGcodeParser parser(sink);
return parser.parse(program, program_len, error);
}
case GcodeBackendKind::LinuxCncRs274:
#ifdef CNC_SIM_ENABLE_LINUXCNC_RS274_BACKEND
return parse_linuxcnc_rs274_backend(sink, program, program_len, error);
#else
if (error) {
*error = "linuxcnc-rs274 backend is not compiled into this build";
}
return -1;
#endif
default:
if (error) {
*error = "unknown G-code backend";
}
return -1;
}
}

20
core/src/gcode_backend.h Normal file
View File

@@ -0,0 +1,20 @@
#pragma once
#include "canon_event_sink.h"
#include <cstddef>
#include <string>
enum class GcodeBackendKind {
Smoke,
LinuxCncRs274,
};
const char *gcode_backend_name(GcodeBackendKind backend);
int parse_gcode_with_backend(GcodeBackendKind backend,
CanonEventSink &sink,
const char *program,
size_t program_len,
std::string *error);

View File

@@ -0,0 +1,703 @@
#include "linuxcnc_canon_bridge.h"
#include "canon_event_sink.h"
#include "canon.hh"
#include <cstdarg>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <string>
#include <vector>
namespace {
CanonEventSink *active_sink = nullptr;
std::string parameter_file_name = "rs274ngc.var";
CncSimPose make_pose(double x, double y, double z,
double a, double b, double c,
double u, double v, double w) {
CncSimPose pose{};
pose.x = x;
pose.y = y;
pose.z = z;
pose.a = a;
pose.b = b;
pose.c = c;
pose.u = u;
pose.v = v;
pose.w = w;
return pose;
}
int plane_to_g_code(CANON_PLANE plane) {
switch (plane) {
case CANON_PLANE::XY:
return 17;
case CANON_PLANE::XZ:
return 18;
case CANON_PLANE::YZ:
return 19;
default:
return 17;
}
}
double units_to_scale(CANON_UNITS units) {
switch (units) {
case CANON_UNITS_INCHES:
return 25.4;
case CANON_UNITS_CM:
return 10.0;
case CANON_UNITS_MM:
default:
return 1.0;
}
}
CANON_PLANE g_code_to_plane(int plane) {
switch (plane) {
case 18:
return CANON_PLANE::XZ;
case 19:
return CANON_PLANE::YZ;
case 17:
default:
return CANON_PLANE::XY;
}
}
const CncSimPose &current_position() {
static CncSimPose zero{};
return active_sink ? active_sink->position() : zero;
}
void trace_call(const char *name) {
if (std::getenv("CNC_SIM_TRACE_CANON")) {
std::fprintf(stderr, "canon:%s\n", name);
std::fflush(stderr);
}
}
} // namespace
void cnc_sim_linuxcnc_set_canon_sink(CanonEventSink *sink) {
trace_call("cnc_sim_linuxcnc_set_canon_sink");
active_sink = sink;
}
CanonEventSink *cnc_sim_linuxcnc_get_canon_sink() {
return active_sink;
}
void INIT_CANON() {
trace_call("INIT_CANON");
if (active_sink) {
active_sink->reset();
}
}
void USE_LENGTH_UNITS(CANON_UNITS units) {
trace_call("USE_LENGTH_UNITS");
if (active_sink) {
active_sink->use_length_units(units_to_scale(units), 0);
}
}
void SELECT_PLANE(CANON_PLANE plane) {
trace_call("SELECT_PLANE");
if (active_sink) {
active_sink->select_plane(plane_to_g_code(plane), 0);
}
}
void SET_FEED_RATE(double rate) {
trace_call("SET_FEED_RATE");
if (active_sink) {
active_sink->set_feed_rate(rate, 0);
}
}
void SET_SPINDLE_SPEED(int, double speed) {
trace_call("SET_SPINDLE_SPEED");
if (active_sink) {
active_sink->set_spindle_speed(speed, 0);
}
}
void SELECT_TOOL(int tool) {
trace_call("SELECT_TOOL");
if (active_sink) {
active_sink->select_tool(tool);
}
}
void CHANGE_TOOL() {
trace_call("CHANGE_TOOL");
if (active_sink) {
active_sink->change_tool(0);
}
}
void STRAIGHT_TRAVERSE(int lineno,
double x, double y, double z,
double a, double b, double c,
double u, double v, double w) {
trace_call("STRAIGHT_TRAVERSE");
if (active_sink) {
active_sink->straight_traverse(lineno, make_pose(x, y, z, a, b, c, u, v, w));
}
}
void STRAIGHT_FEED(int lineno,
double x, double y, double z,
double a, double b, double c,
double u, double v, double w) {
trace_call("STRAIGHT_FEED");
if (active_sink) {
active_sink->straight_feed(lineno, make_pose(x, y, z, a, b, c, u, v, w));
}
}
void ARC_FEED(int lineno,
double first_end, double second_end,
double first_axis, double second_axis, int rotation,
double axis_end_point,
double a, double b, double c,
double u, double v, double w) {
trace_call("ARC_FEED");
if (!active_sink) {
return;
}
CncSimPose end = active_sink->position();
CncSimPose center = active_sink->position();
switch (active_sink->plane()) {
case 17:
end.x = first_end;
end.y = second_end;
end.z = axis_end_point;
center.x = first_axis;
center.y = second_axis;
break;
case 18:
end.z = first_end;
end.x = second_end;
end.y = axis_end_point;
center.z = first_axis;
center.x = second_axis;
break;
case 19:
end.y = first_end;
end.z = second_end;
end.x = axis_end_point;
center.y = first_axis;
center.z = second_axis;
break;
default:
break;
}
end.a = a;
end.b = b;
end.c = c;
end.u = u;
end.v = v;
end.w = w;
active_sink->arc_feed(lineno, end, center, rotation);
}
void DWELL(double seconds) {
trace_call("DWELL");
if (active_sink) {
active_sink->dwell(seconds, 0);
}
}
void PROGRAM_END() {
trace_call("PROGRAM_END");
if (active_sink) {
active_sink->program_end(0);
}
}
void FINISH(void) {
trace_call("FINISH");
}
void SET_G5X_OFFSET(int index,
double x, double y, double z,
double a, double b, double c,
double u, double v, double w) {
trace_call("SET_G5X_OFFSET");
if (active_sink) {
active_sink->set_g5x_offset(index, make_pose(x, y, z, a, b, c, u, v, w), 0);
}
}
void SET_G92_OFFSET(double x, double y, double z,
double a, double b, double c,
double u, double v, double w) {
trace_call("SET_G92_OFFSET");
if (active_sink) {
active_sink->set_g92_offset(make_pose(x, y, z, a, b, c, u, v, w), 0);
}
}
void SET_XY_ROTATION(double angle) {
trace_call("SET_XY_ROTATION");
if (active_sink) {
active_sink->set_xy_rotation(angle, 0);
}
}
void CANON_UPDATE_END_POINT(double x, double y, double z,
double a, double b, double c,
double u, double v, double w) {
trace_call("CANON_UPDATE_END_POINT");
if (active_sink) {
active_sink->straight_traverse(0, make_pose(x, y, z, a, b, c, u, v, w));
}
}
void SET_TRAVERSE_RATE(double) {
}
void SET_FEED_REFERENCE(CANON_FEED_REFERENCE) {
}
void SET_FEED_MODE(int, int) {
}
void SET_MOTION_CONTROL_MODE(CANON_MOTION_MODE, double) {
}
void SET_NAIVECAM_TOLERANCE(double) {
}
void SET_CUTTER_RADIUS_COMPENSATION(double) {
}
void START_CUTTER_RADIUS_COMPENSATION(int) {
}
void STOP_CUTTER_RADIUS_COMPENSATION() {
}
void START_SPEED_FEED_SYNCH(int, double, bool) {
}
void STOP_SPEED_FEED_SYNCH() {
}
void NURBS_G5_FEED(int lineno, const std::vector<NURBS_CONTROL_POINT> &points, unsigned int, CANON_PLANE) {
if (!active_sink) {
return;
}
for (const auto &point : points) {
CncSimPose end = active_sink->position();
end.x = point.NURBS_X;
end.y = point.NURBS_Y;
active_sink->straight_feed(lineno, end);
}
}
void NURBS_G6_FEED(int lineno, const std::vector<NURBS_G6_CONTROL_POINT> &points, unsigned int, double, int, CANON_PLANE) {
if (!active_sink) {
return;
}
for (const auto &point : points) {
CncSimPose end = active_sink->position();
end.x = point.NURBS_X;
end.y = point.NURBS_Y;
active_sink->straight_feed(lineno, end);
}
}
void RIGID_TAP(int lineno, double x, double y, double z, double) {
if (active_sink) {
CncSimPose end = active_sink->position();
end.x = x;
end.y = y;
end.z = z;
active_sink->straight_feed(lineno, end);
}
}
void STRAIGHT_PROBE(int lineno,
double x, double y, double z,
double a, double b, double c,
double u, double v, double w,
unsigned char) {
if (active_sink) {
active_sink->straight_feed(lineno, make_pose(x, y, z, a, b, c, u, v, w));
}
}
void STOP() {
}
void SET_SPINDLE_MODE(int, double) {
}
void SPINDLE_RETRACT_TRAVERSE() {
}
void START_SPINDLE_CLOCKWISE(int, int) {
}
void START_SPINDLE_COUNTERCLOCKWISE(int, int) {
}
void STOP_SPINDLE_TURNING(int) {
if (active_sink) {
active_sink->set_spindle_speed(0.0, 0);
}
}
void SPINDLE_RETRACT() {
}
void ORIENT_SPINDLE(int, double, int) {
}
void WAIT_SPINDLE_ORIENT_COMPLETE(int, double) {
}
void LOCK_SPINDLE_Z() {
}
void USE_SPINDLE_FORCE() {
}
void USE_NO_SPINDLE_FORCE() {
}
void SET_TOOL_TABLE_ENTRY(int, int, const EmcPose &, double, double, double, int) {
}
void USE_TOOL_LENGTH_OFFSET(const EmcPose &) {
}
void CHANGE_TOOL_NUMBER(int number) {
if (active_sink) {
active_sink->select_tool(number);
active_sink->change_tool(0);
}
}
void RELOAD_TOOLDATA(void) {
}
void CLAMP_AXIS(CANON_AXIS) {
}
void COMMENT(const char *) {
if (active_sink) {
CncSimEvent event{};
event.version = 1;
event.type = CNC_SIM_EVENT_COMMENT;
active_sink->emit_raw(event);
}
}
void DISABLE_ADAPTIVE_FEED() {
}
void ENABLE_ADAPTIVE_FEED() {
}
void DISABLE_FEED_OVERRIDE() {
}
void ENABLE_FEED_OVERRIDE() {
}
void DISABLE_SPEED_OVERRIDE(int) {
}
void ENABLE_SPEED_OVERRIDE(int) {
}
void DISABLE_FEED_HOLD() {
}
void ENABLE_FEED_HOLD() {
}
void FLOOD_OFF() {
}
void FLOOD_ON() {
}
void MESSAGE(char *) {
}
void LOG(char *) {
}
void LOGOPEN(char *) {
}
void LOGAPPEND(char *) {
}
void LOGCLOSE() {
}
void MIST_OFF() {
}
void MIST_ON() {
}
void PALLET_SHUTTLE() {
}
void TURN_PROBE_OFF() {
}
void TURN_PROBE_ON() {
}
void UNCLAMP_AXIS(CANON_AXIS) {
}
void NURB_KNOT_VECTOR() {
}
void NURB_CONTROL_POINT(int, double, double, double, double) {
}
void NURB_FEED(double, double) {
}
void SET_BLOCK_DELETE(bool) {
}
bool GET_BLOCK_DELETE(void) {
return false;
}
void OPTIONAL_PROGRAM_STOP() {
}
void SET_OPTIONAL_PROGRAM_STOP(bool) {
}
bool GET_OPTIONAL_PROGRAM_STOP() {
return false;
}
void PROGRAM_STOP() {
}
void SET_MOTION_OUTPUT_BIT(int) {
}
void CLEAR_MOTION_OUTPUT_BIT(int) {
}
void SET_AUX_OUTPUT_BIT(int) {
}
void CLEAR_AUX_OUTPUT_BIT(int) {
}
void SET_MOTION_OUTPUT_VALUE(int, double) {
}
void SET_AUX_OUTPUT_VALUE(int, double) {
}
int WAIT(int, int, int wait_type, double) {
return wait_type;
}
int UNLOCK_ROTARY(int, int) {
return 0;
}
int LOCK_ROTARY(int, int) {
return 0;
}
double GET_EXTERNAL_FEED_RATE() {
return active_sink ? active_sink->feed_rate() : 0.0;
}
int GET_EXTERNAL_FLOOD() {
return 0;
}
CANON_UNITS GET_EXTERNAL_LENGTH_UNIT_TYPE() {
return active_sink && active_sink->unit_scale() == 25.4 ? CANON_UNITS_INCHES : CANON_UNITS_MM;
}
double GET_EXTERNAL_LENGTH_UNITS() {
return active_sink ? active_sink->unit_scale() : 1.0;
}
double GET_EXTERNAL_ANGLE_UNITS() {
return 1.0;
}
int GET_EXTERNAL_MIST() {
return 0;
}
CANON_MOTION_MODE GET_EXTERNAL_MOTION_CONTROL_MODE() {
return CANON_EXACT_STOP;
}
double GET_EXTERNAL_MOTION_CONTROL_TOLERANCE() {
return 0.0;
}
double GET_EXTERNAL_MOTION_CONTROL_NAIVECAM_TOLERANCE() {
return 0.0;
}
void GET_EXTERNAL_PARAMETER_FILE_NAME(char *filename, int max_size) {
trace_call("GET_EXTERNAL_PARAMETER_FILE_NAME");
std::snprintf(filename, max_size, "%s", parameter_file_name.c_str());
}
void SET_PARAMETER_FILE_NAME(const char *filename) {
trace_call("SET_PARAMETER_FILE_NAME");
parameter_file_name = filename ? filename : "rs274ngc.var";
}
CANON_PLANE GET_EXTERNAL_PLANE() {
return active_sink ? g_code_to_plane(active_sink->plane()) : CANON_PLANE::XY;
}
double GET_EXTERNAL_POSITION_A() { return current_position().a; }
double GET_EXTERNAL_POSITION_B() { return current_position().b; }
double GET_EXTERNAL_POSITION_C() { return current_position().c; }
double GET_EXTERNAL_POSITION_X() { return current_position().x; }
double GET_EXTERNAL_POSITION_Y() { return current_position().y; }
double GET_EXTERNAL_POSITION_Z() { return current_position().z; }
double GET_EXTERNAL_POSITION_U() { return current_position().u; }
double GET_EXTERNAL_POSITION_V() { return current_position().v; }
double GET_EXTERNAL_POSITION_W() { return current_position().w; }
double GET_EXTERNAL_PROBE_POSITION_A() { return current_position().a; }
double GET_EXTERNAL_PROBE_POSITION_B() { return current_position().b; }
double GET_EXTERNAL_PROBE_POSITION_C() { return current_position().c; }
double GET_EXTERNAL_PROBE_POSITION_X() { return current_position().x; }
double GET_EXTERNAL_PROBE_POSITION_Y() { return current_position().y; }
double GET_EXTERNAL_PROBE_POSITION_Z() { return current_position().z; }
double GET_EXTERNAL_PROBE_POSITION_U() { return current_position().u; }
double GET_EXTERNAL_PROBE_POSITION_V() { return current_position().v; }
double GET_EXTERNAL_PROBE_POSITION_W() { return current_position().w; }
double GET_EXTERNAL_PROBE_VALUE() {
return 0.0;
}
int GET_EXTERNAL_PROBE_TRIPPED_VALUE() {
return 0;
}
int GET_EXTERNAL_QUEUE_EMPTY() {
return 1;
}
double GET_EXTERNAL_SPEED(int) {
return active_sink ? active_sink->spindle_speed() : 0.0;
}
CANON_DIRECTION GET_EXTERNAL_SPINDLE(int) {
return active_sink && active_sink->spindle_speed() != 0.0 ? CANON_CLOCKWISE : CANON_STOPPED;
}
double GET_EXTERNAL_TOOL_LENGTH_XOFFSET() { return 0.0; }
double GET_EXTERNAL_TOOL_LENGTH_YOFFSET() { return 0.0; }
double GET_EXTERNAL_TOOL_LENGTH_ZOFFSET() { return 0.0; }
double GET_EXTERNAL_TOOL_LENGTH_AOFFSET() { return 0.0; }
double GET_EXTERNAL_TOOL_LENGTH_BOFFSET() { return 0.0; }
double GET_EXTERNAL_TOOL_LENGTH_COFFSET() { return 0.0; }
double GET_EXTERNAL_TOOL_LENGTH_UOFFSET() { return 0.0; }
double GET_EXTERNAL_TOOL_LENGTH_VOFFSET() { return 0.0; }
double GET_EXTERNAL_TOOL_LENGTH_WOFFSET() { return 0.0; }
int GET_EXTERNAL_TOOL_SLOT() {
return active_sink ? active_sink->selected_tool() : 0;
}
int GET_EXTERNAL_SELECTED_TOOL_SLOT() {
return active_sink ? active_sink->selected_tool() : -1;
}
CANON_TOOL_TABLE GET_EXTERNAL_TOOL_TABLE(int) {
CANON_TOOL_TABLE tool{};
std::memset(&tool, 0, sizeof(tool));
return tool;
}
int GET_EXTERNAL_TC_FAULT() {
return 0;
}
int GET_EXTERNAL_TC_REASON() {
return 0;
}
double GET_EXTERNAL_TRAVERSE_RATE() {
return 0.0;
}
int GET_EXTERNAL_FEED_OVERRIDE_ENABLE() {
return 1;
}
int GET_EXTERNAL_SPINDLE_OVERRIDE_ENABLE(int) {
return 1;
}
int GET_EXTERNAL_ADAPTIVE_FEED_ENABLE() {
return 1;
}
int GET_EXTERNAL_FEED_HOLD_ENABLE() {
return 1;
}
int GET_EXTERNAL_DIGITAL_INPUT(int, int def) {
return def;
}
double GET_EXTERNAL_ANALOG_INPUT(int, double def) {
return def;
}
int GET_EXTERNAL_AXIS_MASK() {
return 0x1ff;
}
void ON_RESET(void) {
if (active_sink) {
active_sink->reset();
}
}
void CANON_ERROR(const char *, ...) {
}
void UPDATE_TAG(const StateTag &) {
}
USER_DEFINED_FUNCTION_TYPE USER_DEFINED_FUNCTION[USER_DEFINED_FUNCTION_NUM];
int GET_EXTERNAL_OFFSET_APPLIED() {
return 0;
}
EmcPose GET_EXTERNAL_OFFSETS() {
EmcPose pose;
ZERO_EMC_POSE(pose);
return pose;
}

View File

@@ -0,0 +1,7 @@
#pragma once
class CanonEventSink;
void cnc_sim_linuxcnc_set_canon_sink(CanonEventSink *sink);
CanonEventSink *cnc_sim_linuxcnc_get_canon_sink();

View File

@@ -0,0 +1,179 @@
#include "linuxcnc_rs274_backend.h"
#include <Python.h>
#include "linuxcnc_canon_bridge.h"
#include "simulator_gcode_controls.h"
#include "linuxcnc.h"
#include "nml_intf/canon.hh"
#include "nml_intf/interp_return.hh"
#include "rs274ngc/interp_base.hh"
#include "emc/tooldata/tooldata.hh"
#include <sstream>
#include <string>
#include <vector>
#include <cstdlib>
int _task = 0;
char _parameter_file_name[LINELEN];
extern "C" PyObject *PyInit_interpreter(void);
extern "C" PyObject *PyInit_emccanon(void);
extern "C" struct _inittab builtin_modules[];
struct _inittab builtin_modules[] = {
{"interpreter", PyInit_interpreter},
{"emccanon", PyInit_emccanon},
{nullptr, nullptr},
};
namespace {
void init_minimal_tooldata_once() {
static bool created = false;
if (!created) {
tool_mmap_creator(nullptr, 0);
created = true;
}
tooldata_reset();
CANON_TOOL_TABLE spindle = tooldata_entry_init();
spindle.toolno = 0;
spindle.pocketno = 0;
tooldata_put(spindle, 0);
CANON_TOOL_TABLE tool = tooldata_entry_init();
tool.toolno = 1;
tool.pocketno = 1;
tool.diameter = 6.0;
tooldata_put(tool, 1);
}
bool normal_read_status(int status) {
return status == INTERP_OK ||
status == INTERP_EXECUTE_FINISH ||
status == INTERP_ENDFILE ||
status == INTERP_EXIT;
}
bool normal_execute_status(int status, bool *program_done) {
if (status == INTERP_EXIT || status == INTERP_ENDFILE) {
*program_done = true;
return true;
}
return status == INTERP_OK || status == INTERP_EXECUTE_FINISH;
}
bool stop_if_callback_aborted(CanonEventSink &sink, std::string *error) {
if (!sink.callback_aborted()) {
return false;
}
if (error) {
*error = "event callback aborted parsing";
}
return true;
}
std::string interp_error(InterpBase *interp, int status, const char *stage) {
char message[1024]{};
interp->error_text(status, message, sizeof(message));
std::string result = stage;
result += " failed";
if (message[0]) {
result += ": ";
result += message;
}
return result;
}
} // namespace
int parse_linuxcnc_rs274_backend(CanonEventSink &sink,
const char *program,
size_t program_len,
std::string *error) {
if (!program && program_len != 0) {
if (error) {
*error = "null program buffer";
}
return -1;
}
if (const char *parameter_file = std::getenv("CNC_SIM_RS274_VAR")) {
SET_PARAMETER_FILE_NAME(parameter_file);
}
init_minimal_tooldata_once();
sink.clear_callback_status();
cnc_sim_linuxcnc_set_canon_sink(&sink);
InterpBase *interp = makeInterp();
int status = interp->init();
if (status != INTERP_OK) {
if (error) {
*error = interp_error(interp, status, "init");
}
delete interp;
cnc_sim_linuxcnc_set_canon_sink(nullptr);
return -1;
}
std::string source(program, program + program_len);
std::istringstream input(source);
std::string line;
bool program_done = false;
int line_number = 0;
while (!program_done && std::getline(input, line)) {
++line_number;
std::vector<SimulatorGcodeControlAction> control_actions;
if (parse_simulator_gcode_control_line(line, &control_actions)) {
for (const auto &action : control_actions) {
emit_simulator_gcode_control_action(sink, action, line_number);
if (stop_if_callback_aborted(sink, error)) {
interp->exit();
delete interp;
cnc_sim_linuxcnc_set_canon_sink(nullptr);
return -1;
}
}
continue;
}
status = interp->read(line.c_str());
if (!normal_read_status(status)) {
if (error) {
*error = interp_error(interp, status, "read");
}
interp->exit();
delete interp;
cnc_sim_linuxcnc_set_canon_sink(nullptr);
return -1;
}
if (status == INTERP_EXIT || status == INTERP_ENDFILE) {
break;
}
status = interp->execute();
if (!normal_execute_status(status, &program_done)) {
if (error) {
*error = interp_error(interp, status, "execute");
}
interp->exit();
delete interp;
cnc_sim_linuxcnc_set_canon_sink(nullptr);
return -1;
}
if (stop_if_callback_aborted(sink, error)) {
interp->exit();
delete interp;
cnc_sim_linuxcnc_set_canon_sink(nullptr);
return -1;
}
}
interp->exit();
delete interp;
cnc_sim_linuxcnc_set_canon_sink(nullptr);
return 0;
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include "canon_event_sink.h"
#include <cstddef>
#include <string>
int parse_linuxcnc_rs274_backend(CanonEventSink &sink,
const char *program,
size_t program_len,
std::string *error);

View File

@@ -0,0 +1,72 @@
#include "rtcp_kinematics.h"
#include <cmath>
namespace {
constexpr double kPi = 3.141592653589793238462643383279502884;
double radians(double degrees) {
return degrees * kPi / 180.0;
}
RtcpVector rotate_x(RtcpVector vector, double angle) {
const double c = std::cos(angle);
const double s = std::sin(angle);
return {
vector.x,
vector.y * c - vector.z * s,
vector.y * s + vector.z * c,
};
}
RtcpVector rotate_y(RtcpVector vector, double angle) {
const double c = std::cos(angle);
const double s = std::sin(angle);
return {
vector.x * c + vector.z * s,
vector.y,
-vector.x * s + vector.z * c,
};
}
RtcpVector rotate_z(RtcpVector vector, double angle) {
const double c = std::cos(angle);
const double s = std::sin(angle);
return {
vector.x * c - vector.y * s,
vector.x * s + vector.y * c,
vector.z,
};
}
} // namespace
RtcpVector rtcp_rotate_abc_degrees(RtcpVector vector, double a_deg, double b_deg, double c_deg) {
vector = rotate_x(vector, radians(a_deg));
vector = rotate_y(vector, radians(b_deg));
vector = rotate_z(vector, radians(c_deg));
return vector;
}
RtcpVector rtcp_tool_vector_from_pose(const CncSimPose &pose, double tool_length) {
return rtcp_rotate_abc_degrees({0.0, 0.0, -tool_length}, pose.a, pose.b, pose.c);
}
CncSimPose rtcp_pivot_from_tool_tip(const CncSimPose &tool_tip, double tool_length) {
const RtcpVector tool = rtcp_tool_vector_from_pose(tool_tip, tool_length);
CncSimPose pivot = tool_tip;
pivot.x = tool_tip.x - tool.x;
pivot.y = tool_tip.y - tool.y;
pivot.z = tool_tip.z - tool.z;
return pivot;
}
CncSimPose rtcp_tool_tip_from_pivot(const CncSimPose &pivot, double tool_length) {
const RtcpVector tool = rtcp_tool_vector_from_pose(pivot, tool_length);
CncSimPose tool_tip = pivot;
tool_tip.x = pivot.x + tool.x;
tool_tip.y = pivot.y + tool.y;
tool_tip.z = pivot.z + tool.z;
return tool_tip;
}

View File

@@ -0,0 +1,17 @@
#pragma once
#include "cnc_sim_api.h"
struct RtcpVector {
double x;
double y;
double z;
};
// Rotation order is intrinsic tool orientation A then B then C, represented as
// Rz(C) * Ry(B) * Rx(A) applied to a local tool vector.
RtcpVector rtcp_rotate_abc_degrees(RtcpVector vector, double a_deg, double b_deg, double c_deg);
RtcpVector rtcp_tool_vector_from_pose(const CncSimPose &pose, double tool_length);
CncSimPose rtcp_pivot_from_tool_tip(const CncSimPose &tool_tip, double tool_length);
CncSimPose rtcp_tool_tip_from_pivot(const CncSimPose &pivot, double tool_length);

View File

@@ -0,0 +1,145 @@
#include "simulator_gcode_controls.h"
#include <cctype>
#include <cmath>
#include <cstdlib>
namespace {
std::string strip_line_comment(const std::string &line) {
std::string out;
bool in_paren = false;
for (char raw : line) {
const char ch = static_cast<char>(std::toupper(static_cast<unsigned char>(raw)));
if (in_paren) {
if (ch == ')') {
in_paren = false;
}
continue;
}
if (ch == '(') {
in_paren = true;
continue;
}
if (ch == ';') {
break;
}
out.push_back(ch);
}
return out;
}
bool g_code_is(double actual, double expected) {
return std::fabs(actual - expected) < 0.0001;
}
bool parse_m_control_line(const std::string &stripped,
std::vector<SimulatorGcodeControlAction> *actions) {
const char *text = stripped.c_str();
bool saw_code = false;
for (size_t i = 0; text[i] != '\0';) {
if (std::isspace(static_cast<unsigned char>(text[i]))) {
++i;
continue;
}
if (text[i] != 'M') {
return false;
}
++i;
char *end = nullptr;
const long value = std::strtol(text + i, &end, 10);
if (end == text + i) {
return false;
}
SimulatorGcodeControlAction action{};
action.kind = SimulatorGcodeControlKind::KinematicsSwitch;
if (value == 428) {
action.value = 1;
action.rtcp_enabled = true;
} else if (value == 429) {
action.value = 0;
action.rtcp_enabled = false;
} else if (value == 430) {
action.value = 2;
action.rtcp_enabled = true;
} else {
return false;
}
actions->push_back(action);
saw_code = true;
i = static_cast<size_t>(end - text);
}
return saw_code;
}
bool parse_rtcp_control_line(const std::string &stripped,
std::vector<SimulatorGcodeControlAction> *actions) {
const char *text = stripped.c_str();
std::vector<SimulatorGcodeControlAction> parsed;
int h_code = 0;
bool saw_rtcp_code = false;
for (size_t i = 0; text[i] != '\0';) {
if (std::isspace(static_cast<unsigned char>(text[i]))) {
++i;
continue;
}
const char letter = text[i];
if (letter != 'G' && letter != 'H') {
return false;
}
++i;
char *end = nullptr;
const double value = std::strtod(text + i, &end);
if (end == text + i) {
return false;
}
if (letter == 'H') {
h_code = static_cast<int>(std::lround(value));
} else if (g_code_is(value, 43.4) || g_code_is(value, 43.5) || g_code_is(value, 49.0)) {
SimulatorGcodeControlAction action{};
action.kind = SimulatorGcodeControlKind::RtcpState;
action.value = static_cast<int>(std::lround(value * 10.0));
action.rtcp_enabled = g_code_is(value, 43.4) || g_code_is(value, 43.5);
parsed.push_back(action);
saw_rtcp_code = true;
} else {
return false;
}
i = static_cast<size_t>(end - text);
}
for (auto &action : parsed) {
action.h_code = action.rtcp_enabled ? h_code : 0;
actions->push_back(action);
}
return saw_rtcp_code;
}
} // namespace
bool parse_simulator_gcode_control_line(const std::string &line,
std::vector<SimulatorGcodeControlAction> *actions) {
actions->clear();
const std::string stripped = strip_line_comment(line);
if (parse_m_control_line(stripped, actions)) {
return true;
}
actions->clear();
if (parse_rtcp_control_line(stripped, actions)) {
return true;
}
actions->clear();
return false;
}
void emit_simulator_gcode_control_action(CanonEventSink &sink,
const SimulatorGcodeControlAction &action,
int line) {
if (action.kind == SimulatorGcodeControlKind::KinematicsSwitch) {
sink.switch_kinematics(action.value, action.rtcp_enabled, line);
} else if (action.kind == SimulatorGcodeControlKind::RtcpState) {
sink.set_rtcp_state(action.rtcp_enabled, action.h_code, line);
}
}

View File

@@ -0,0 +1,24 @@
#pragma once
#include "canon_event_sink.h"
#include <string>
#include <vector>
enum class SimulatorGcodeControlKind {
KinematicsSwitch,
RtcpState,
};
struct SimulatorGcodeControlAction {
SimulatorGcodeControlKind kind;
int value;
bool rtcp_enabled;
int h_code;
};
bool parse_simulator_gcode_control_line(const std::string &line,
std::vector<SimulatorGcodeControlAction> *actions);
void emit_simulator_gcode_control_action(CanonEventSink &sink,
const SimulatorGcodeControlAction &action,
int line);

View File

@@ -0,0 +1,399 @@
#include "smoke_gcode_parser.h"
#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdlib>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
namespace {
struct Word {
char letter;
double value;
};
std::string trim(const std::string &value) {
auto first = std::find_if_not(value.begin(), value.end(), [](unsigned char ch) { return std::isspace(ch); });
auto last = std::find_if_not(value.rbegin(), value.rend(), [](unsigned char ch) { return std::isspace(ch); }).base();
if (first >= last) {
return {};
}
return std::string(first, last);
}
std::string strip_comments(const std::string &line, std::string *comment) {
std::string out;
bool in_paren = false;
std::string paren;
for (char raw : line) {
char ch = static_cast<char>(std::toupper(static_cast<unsigned char>(raw)));
if (in_paren) {
if (ch == ')') {
in_paren = false;
if (comment && !paren.empty()) {
if (!comment->empty()) {
*comment += " ";
}
*comment += trim(paren);
}
paren.clear();
} else {
paren.push_back(raw);
}
continue;
}
if (ch == '(') {
in_paren = true;
continue;
}
if (ch == ';') {
break;
}
out.push_back(ch);
}
return out;
}
std::vector<Word> parse_word_list(const std::string &line) {
std::vector<Word> words;
const char *text = line.c_str();
char *end = nullptr;
for (size_t i = 0; text[i] != '\0';) {
if (std::isspace(static_cast<unsigned char>(text[i]))) {
++i;
continue;
}
char letter = text[i];
if (!std::isalpha(static_cast<unsigned char>(letter))) {
++i;
continue;
}
++i;
double value = std::strtod(text + i, &end);
if (end == text + i) {
continue;
}
words.push_back({letter, value});
i = static_cast<size_t>(end - text);
}
return words;
}
std::unordered_map<char, double> last_words_by_letter(const std::vector<Word> &word_list) {
std::unordered_map<char, double> words;
for (const Word &word : word_list) {
words[word.letter] = word.value;
}
return words;
}
bool has_axis_word(const std::unordered_map<char, double> &words) {
static constexpr char axes[] = {'X', 'Y', 'Z', 'A', 'B', 'C', 'U', 'V', 'W'};
for (char axis : axes) {
if (words.find(axis) != words.end()) {
return true;
}
}
return false;
}
void apply_axis(CncSimPose *pose, const std::unordered_map<char, double> &words, char axis, double CncSimPose::*member, bool absolute, double scale) {
auto it = words.find(axis);
if (it == words.end()) {
return;
}
const double value = it->second * scale;
if (absolute) {
pose->*member = value;
} else {
pose->*member += value;
}
}
void apply_axes(CncSimPose *pose, const std::unordered_map<char, double> &words, bool absolute, double scale) {
apply_axis(pose, words, 'X', &CncSimPose::x, absolute, scale);
apply_axis(pose, words, 'Y', &CncSimPose::y, absolute, scale);
apply_axis(pose, words, 'Z', &CncSimPose::z, absolute, scale);
apply_axis(pose, words, 'A', &CncSimPose::a, absolute, 1.0);
apply_axis(pose, words, 'B', &CncSimPose::b, absolute, 1.0);
apply_axis(pose, words, 'C', &CncSimPose::c, absolute, 1.0);
apply_axis(pose, words, 'U', &CncSimPose::u, absolute, scale);
apply_axis(pose, words, 'V', &CncSimPose::v, absolute, scale);
apply_axis(pose, words, 'W', &CncSimPose::w, absolute, scale);
}
void set_arc_center(CncSimEvent *event, const std::unordered_map<char, double> &words, int plane, double scale) {
event->center = event->start;
if (words.count('R')) {
const double radius = words.at('R') * scale;
double start_first = 0.0;
double start_second = 0.0;
double end_first = 0.0;
double end_second = 0.0;
if (plane == 17) {
start_first = event->start.x;
start_second = event->start.y;
end_first = event->end.x;
end_second = event->end.y;
} else if (plane == 18) {
start_first = event->start.x;
start_second = event->start.z;
end_first = event->end.x;
end_second = event->end.z;
} else if (plane == 19) {
start_first = event->start.y;
start_second = event->start.z;
end_first = event->end.y;
end_second = event->end.z;
}
const double dx = end_first - start_first;
const double dy = end_second - start_second;
const double chord = std::hypot(dx, dy);
if (chord > 0.0 && std::fabs(radius) >= chord * 0.5) {
const double mid_first = (start_first + end_first) * 0.5;
const double mid_second = (start_second + end_second) * 0.5;
double h = std::sqrt(std::max(0.0, radius * radius - chord * chord * 0.25));
if (radius < 0.0) {
h = -h;
}
const double direction = event->arc_turns >= 0 ? 1.0 : -1.0;
const double center_first = mid_first + (-dy / chord) * h * direction;
const double center_second = mid_second + (dx / chord) * h * direction;
if (plane == 17) {
event->center.x = center_first;
event->center.y = center_second;
} else if (plane == 18) {
event->center.x = center_first;
event->center.z = center_second;
} else if (plane == 19) {
event->center.y = center_first;
event->center.z = center_second;
}
return;
}
}
const double i = words.count('I') ? words.at('I') * scale : 0.0;
const double j = words.count('J') ? words.at('J') * scale : 0.0;
const double k = words.count('K') ? words.at('K') * scale : 0.0;
if (plane == 17) {
event->center.x = event->start.x + i;
event->center.y = event->start.y + j;
} else if (plane == 18) {
event->center.x = event->start.x + i;
event->center.z = event->start.z + k;
} else if (plane == 19) {
event->center.y = event->start.y + j;
event->center.z = event->start.z + k;
}
}
int rounded_word(const std::unordered_map<char, double> &words, char letter, int fallback) {
auto it = words.find(letter);
if (it == words.end()) {
return fallback;
}
return static_cast<int>(std::lround(it->second));
}
int rounded_value(double value) {
return static_cast<int>(std::lround(value));
}
bool g_code_is(double actual, double expected) {
return std::fabs(actual - expected) < 0.0001;
}
} // namespace
SmokeGcodeParser::SmokeGcodeParser(CanonEventSink &sink)
: sink_(sink) {
}
namespace {
bool stop_if_callback_aborted(CanonEventSink &sink, std::string *error) {
if (!sink.callback_aborted()) {
return false;
}
if (error) {
*error = "event callback aborted parsing";
}
return true;
}
} // namespace
int SmokeGcodeParser::parse(const char *program, size_t program_len, std::string *error) {
if (!program && program_len != 0) {
if (error) {
*error = "null program buffer";
}
return -1;
}
sink_.reset();
sink_.clear_callback_status();
absolute_ = true;
std::string source(program, program + program_len);
std::istringstream input(source);
std::string raw_line;
int line_number = 0;
int modal_motion = -1;
while (std::getline(input, raw_line)) {
++line_number;
std::string comment;
std::string line = strip_comments(raw_line, &comment);
if (!comment.empty()) {
CncSimEvent event{};
event.version = 1;
event.type = CNC_SIM_EVENT_COMMENT;
event.line = line_number;
sink_.emit_raw(event);
if (stop_if_callback_aborted(sink_, error)) {
return -1;
}
}
auto word_list = parse_word_list(line);
if (word_list.empty()) {
continue;
}
auto words = last_words_by_letter(word_list);
std::vector<int> m_codes;
for (const Word &word : word_list) {
if (word.letter == 'G') {
const int g = rounded_value(word.value);
if (g_code_is(word.value, 43.4) || g_code_is(word.value, 43.5)) {
sink_.set_rtcp_state(true, rounded_word(words, 'H', 0), line_number);
if (stop_if_callback_aborted(sink_, error)) {
return -1;
}
} else if (g == 49) {
sink_.set_rtcp_state(false, 0, line_number);
if (stop_if_callback_aborted(sink_, error)) {
return -1;
}
} else if (g == 17 || g == 18 || g == 19) {
sink_.select_plane(g, line_number);
if (stop_if_callback_aborted(sink_, error)) {
return -1;
}
} else if (g == 20 || g == 70) {
sink_.use_length_units(25.4, line_number);
if (stop_if_callback_aborted(sink_, error)) {
return -1;
}
} else if (g == 21 || g == 71) {
sink_.use_length_units(1.0, line_number);
if (stop_if_callback_aborted(sink_, error)) {
return -1;
}
} else if (g == 90) {
absolute_ = true;
} else if (g == 91) {
absolute_ = false;
} else if (g == 0 || g == 1 || g == 2 || g == 3) {
modal_motion = g;
} else if (g == 4) {
sink_.dwell(words.count('P') ? words['P'] : 0.0, line_number);
if (stop_if_callback_aborted(sink_, error)) {
return -1;
}
}
} else if (word.letter == 'M') {
m_codes.push_back(rounded_value(word.value));
}
}
if (words.count('F')) {
sink_.set_feed_rate(words['F'] * sink_.unit_scale(), line_number);
if (stop_if_callback_aborted(sink_, error)) {
return -1;
}
}
if (words.count('S')) {
sink_.set_spindle_speed(words['S'], line_number);
if (stop_if_callback_aborted(sink_, error)) {
return -1;
}
}
if (words.count('T')) {
sink_.select_tool(rounded_word(words, 'T', sink_.selected_tool()));
}
bool program_end = false;
for (int m : m_codes) {
if (m == 3 || m == 4 || m == 5) {
sink_.set_spindle_speed(m == 5 ? 0.0 : sink_.spindle_speed(), line_number);
if (stop_if_callback_aborted(sink_, error)) {
return -1;
}
} else if (m == 6) {
sink_.change_tool(line_number);
if (stop_if_callback_aborted(sink_, error)) {
return -1;
}
} else if (m == 428) {
sink_.switch_kinematics(1, true, line_number);
if (stop_if_callback_aborted(sink_, error)) {
return -1;
}
} else if (m == 429) {
sink_.switch_kinematics(0, false, line_number);
if (stop_if_callback_aborted(sink_, error)) {
return -1;
}
} else if (m == 430) {
sink_.switch_kinematics(2, true, line_number);
if (stop_if_callback_aborted(sink_, error)) {
return -1;
}
} else if (m == 2 || m == 30) {
sink_.program_end(line_number);
if (stop_if_callback_aborted(sink_, error)) {
return -1;
}
program_end = true;
}
}
if (program_end) {
break;
}
if (has_axis_word(words) && (modal_motion == 0 || modal_motion == 1 || modal_motion == 2 || modal_motion == 3)) {
CncSimEvent event{};
event.start = sink_.position();
event.end = sink_.position();
apply_axes(&event.end, words, absolute_, sink_.unit_scale());
if (modal_motion == 2 || modal_motion == 3) {
event.arc_turns = modal_motion == 2 ? -1 : 1;
set_arc_center(&event, words, sink_.plane(), sink_.unit_scale());
}
if (modal_motion == 0) {
sink_.straight_traverse(line_number, event.end);
} else if (modal_motion == 1) {
sink_.straight_feed(line_number, event.end);
} else {
sink_.arc_feed(line_number, event.end, event.center, event.arc_turns);
}
if (stop_if_callback_aborted(sink_, error)) {
return -1;
}
}
}
return 0;
}

View File

@@ -0,0 +1,18 @@
#pragma once
#include "canon_event_sink.h"
#include <cstddef>
#include <string>
class SmokeGcodeParser {
public:
explicit SmokeGcodeParser(CanonEventSink &sink);
int parse(const char *program, size_t program_len, std::string *error);
private:
CanonEventSink &sink_;
bool absolute_ = true;
};

View File

@@ -0,0 +1,61 @@
#include "canon_event_sink.h"
#include <iostream>
#include <vector>
namespace {
int collect_event(const CncSimEvent *event, void *user_data) {
auto *events = static_cast<std::vector<CncSimEvent> *>(user_data);
events->push_back(*event);
return 0;
}
int abort_on_second_event(const CncSimEvent *, void *user_data) {
auto *count = static_cast<int *>(user_data);
++(*count);
return *count >= 2 ? 1 : 0;
}
bool expect(bool value, const char *message) {
if (!value) {
std::cerr << "FAIL: " << message << '\n';
return false;
}
return true;
}
} // namespace
int main() {
bool ok = true;
std::vector<CncSimEvent> events;
CanonEventSink sink;
sink.set_callback(collect_event, &events);
CncSimPose end{};
end.x = 10.0;
end.y = 5.0;
sink.use_length_units(1.0, 1);
sink.select_plane(17, 1);
sink.set_feed_rate(500.0, 2);
sink.straight_feed(3, end);
sink.program_end(4);
ok &= expect(events.size() == 5, "expected five emitted events");
ok &= expect(events[3].type == CNC_SIM_EVENT_LINEAR_FEED, "expected linear event");
ok &= expect(events[3].end.x == 10.0 && events[3].end.y == 5.0, "expected updated end pose");
ok &= expect(sink.position().x == 10.0 && sink.position().y == 5.0, "expected sink position update");
int abort_count = 0;
sink.reset();
sink.set_callback(abort_on_second_event, &abort_count);
sink.select_plane(17, 1);
sink.set_feed_rate(100.0, 2);
sink.straight_feed(3, end);
ok &= expect(sink.callback_aborted(), "expected callback abort state");
ok &= expect(abort_count == 2, "expected no further callbacks after abort");
return ok ? 0 : 1;
}

View File

@@ -0,0 +1,162 @@
#include "cnc_sim_api.h"
#include <iostream>
#include <vector>
namespace {
int collect_event(const CncSimEvent *event, void *user_data) {
auto *events = static_cast<std::vector<CncSimEvent> *>(user_data);
events->push_back(*event);
return 0;
}
int abort_on_second_event(const CncSimEvent *, void *user_data) {
auto *count = static_cast<int *>(user_data);
++(*count);
return *count >= 2 ? 1 : 0;
}
bool expect(bool value, const char *message) {
if (!value) {
std::cerr << "FAIL: " << message << '\n';
return false;
}
return true;
}
} // namespace
int main() {
const char config[] =
"{\"backend\":\"linuxcnc-rs274\",\"rtcp\":{\"enabled\":true,\"toolLength\":100,\"toolLengths\":{\"7\":125}}}";
const char program[] =
"G21 G90 G17\n"
"T1 M6\n"
"M428 M429 M430\n"
"G49\n"
"G43.4 H7\n"
"S8000 M3\n"
"G0 X0 Y0 Z5\n"
"F600\n"
"G1 Z-1\n"
"G1 X40 Y0\n"
"G3 X40 Y40 I0 J20\n"
"G1 X0 Y40\n"
"G1 X0 Y0\n"
"M30\n";
std::vector<CncSimEvent> events;
CncSimHandle *sim = cnc_sim_create();
cnc_sim_set_event_callback(sim, collect_event, &events);
bool ok = true;
ok &= expect(cnc_sim_load_config_json(sim, config, sizeof(config) - 1) == 0, cnc_sim_last_error(sim));
ok &= expect(cnc_sim_parse_program(sim, program, sizeof(program) - 1) == 0, cnc_sim_last_error(sim));
bool saw_tool = false;
bool saw_rapid = false;
bool saw_feed = false;
bool saw_arc = false;
bool saw_end = false;
bool saw_rtcp = false;
bool saw_m428 = false;
bool saw_m429 = false;
bool saw_m430 = false;
bool saw_g49 = false;
bool saw_g434 = false;
bool saw_g5x_offset = false;
bool saw_xy_rotation = false;
bool saw_g92_clear = false;
for (const auto &event : events) {
saw_tool = saw_tool || event.type == CNC_SIM_EVENT_TOOL_CHANGE;
saw_rapid = saw_rapid || event.type == CNC_SIM_EVENT_RAPID;
saw_feed = saw_feed || event.type == CNC_SIM_EVENT_LINEAR_FEED;
saw_arc = saw_arc || event.type == CNC_SIM_EVENT_ARC_FEED;
saw_end = saw_end || event.type == CNC_SIM_EVENT_PROGRAM_END;
saw_rtcp = saw_rtcp ||
(event.type == CNC_SIM_EVENT_RTCP_PIVOT &&
event.end.z == 130.0);
saw_m428 = saw_m428 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 3 &&
event.reserved == 1 &&
event.feed == 1.0);
saw_m429 = saw_m429 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 3 &&
event.reserved == 0 &&
event.feed == 0.0);
saw_m430 = saw_m430 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 3 &&
event.reserved == 2 &&
event.feed == 1.0);
saw_g49 = saw_g49 ||
(event.type == CNC_SIM_EVENT_RTCP_STATE &&
event.line == 4 &&
event.feed == 0.0);
saw_g434 = saw_g434 ||
(event.type == CNC_SIM_EVENT_RTCP_STATE &&
event.line == 5 &&
event.feed == 1.0 &&
event.tool == 7 &&
event.dwell_seconds == 125.0);
}
ok &= expect(saw_tool, "expected LinuxCNC tool-change event");
ok &= expect(saw_rapid, "expected LinuxCNC rapid event");
ok &= expect(saw_feed, "expected LinuxCNC linear-feed event");
ok &= expect(saw_arc, "expected LinuxCNC arc-feed event");
ok &= expect(saw_end, "expected LinuxCNC program-end event");
ok &= expect(saw_rtcp, "expected LinuxCNC RTCP pivot event");
ok &= expect(saw_m428, "expected LinuxCNC M428 kinematics switch");
ok &= expect(saw_m429, "expected LinuxCNC M429 kinematics switch");
ok &= expect(saw_m430, "expected LinuxCNC M430 kinematics switch");
ok &= expect(saw_g49, "expected LinuxCNC G49 RTCP off state");
ok &= expect(saw_g434, "expected LinuxCNC G43.4 RTCP on state with H code");
const char coordinate_program[] =
"G21 G90 G17\n"
"G10 L2 P1 X12.5 Y-3 Z4 R30\n"
"G92 X1 Y2 Z3\n"
"G92.1\n"
"M30\n";
events.clear();
ok &= expect(cnc_sim_parse_program(sim, coordinate_program, sizeof(coordinate_program) - 1) == 0,
cnc_sim_last_error(sim));
for (const auto &event : events) {
saw_g5x_offset = saw_g5x_offset ||
(event.type == CNC_SIM_EVENT_SET_G5X_OFFSET &&
event.tool == 1 &&
event.start.x == 12.5 &&
event.start.y == -3.0 &&
event.start.z == 4.0);
saw_xy_rotation = saw_xy_rotation ||
(event.type == CNC_SIM_EVENT_SET_XY_ROTATION &&
event.feed == 30.0);
saw_g92_clear = saw_g92_clear ||
(event.type == CNC_SIM_EVENT_SET_G92_OFFSET &&
event.start.x == 0.0 &&
event.start.y == 0.0 &&
event.start.z == 0.0);
}
ok &= expect(saw_g5x_offset, "expected LinuxCNC G10 L2 G5X offset event");
ok &= expect(saw_xy_rotation, "expected LinuxCNC G10 L2 XY rotation event");
ok &= expect(saw_g92_clear, "expected LinuxCNC G92.1 clear offset event");
cnc_sim_destroy(sim);
int abort_count = 0;
sim = cnc_sim_create();
cnc_sim_set_event_callback(sim, abort_on_second_event, &abort_count);
ok &= expect(cnc_sim_load_config_json(sim, config, sizeof(config) - 1) == 0, cnc_sim_last_error(sim));
ok &= expect(cnc_sim_parse_program(sim, program, sizeof(program) - 1) != 0,
"expected callback abort to stop LinuxCNC parsing");
ok &= expect(abort_count == 2, "expected LinuxCNC parser to stop callbacks after abort");
ok &= expect(std::string(cnc_sim_last_error(sim)) == "event callback aborted parsing",
"expected callback abort error");
cnc_sim_destroy(sim);
return ok ? 0 : 1;
}

View File

@@ -0,0 +1,141 @@
#include "cnc_sim_api.h"
#include <iostream>
#include <vector>
namespace {
int collect_event(const CncSimEvent *event, void *user_data) {
auto *events = static_cast<std::vector<CncSimEvent> *>(user_data);
events->push_back(*event);
return 0;
}
bool expect(bool value, const char *message) {
if (!value) {
std::cerr << "FAIL: " << message << '\n';
return false;
}
return true;
}
} // namespace
int main() {
const char program[] =
"G21 G90 G17\n"
"T1 M6\n"
"M428 M429 M430\n"
"G49\n"
"G43.4 H7\n"
"S8000 M3\n"
"G0 X0 Y0 Z5\n"
"F600\n"
"G1 Z-1\n"
"G1 X20 Y0\n"
"G3 X20 Y20 I0 J10\n"
"G91 G1 X5\n"
"G90 G4 P0.25\n"
"G2 X25 Y25 R5\n"
"M30\n";
std::vector<CncSimEvent> events;
CncSimHandle *sim = cnc_sim_create();
cnc_sim_set_event_callback(sim, collect_event, &events);
const char smoke_config[] =
"{\"backend\":\"smoke\",\"rtcp\":{\"enabled\":true,\"toolLength\":100,\"toolLengths\":{\"7\":125}}}";
int config_rc = cnc_sim_load_config_json(sim, smoke_config, sizeof(smoke_config) - 1);
int rc = cnc_sim_parse_program(sim, program, sizeof(program) - 1);
bool ok = true;
ok &= expect(config_rc == 0, cnc_sim_last_error(sim));
ok &= expect(rc == 0, cnc_sim_last_error(sim));
ok &= expect(events.size() >= 8, "expected at least 8 events");
ok &= expect(events[0].type == CNC_SIM_EVENT_SET_PLANE || events[0].type == CNC_SIM_EVENT_SET_UNITS,
"expected modal setup event first");
bool saw_rapid = false;
bool saw_feed = false;
bool saw_arc = false;
bool saw_dwell = false;
bool saw_end = false;
bool saw_units = false;
bool saw_plane = false;
bool saw_rtcp = false;
bool saw_m428 = false;
bool saw_m429 = false;
bool saw_m430 = false;
bool saw_g49 = false;
bool saw_g434 = false;
bool saw_incremental_move = false;
bool saw_r_arc_center = false;
for (const auto &event : events) {
saw_rapid = saw_rapid || event.type == CNC_SIM_EVENT_RAPID;
saw_feed = saw_feed || event.type == CNC_SIM_EVENT_LINEAR_FEED;
saw_arc = saw_arc || event.type == CNC_SIM_EVENT_ARC_FEED;
saw_dwell = saw_dwell || event.type == CNC_SIM_EVENT_DWELL;
saw_end = saw_end || event.type == CNC_SIM_EVENT_PROGRAM_END;
saw_units = saw_units || event.type == CNC_SIM_EVENT_SET_UNITS;
saw_plane = saw_plane || event.type == CNC_SIM_EVENT_SET_PLANE;
saw_rtcp = saw_rtcp ||
(event.type == CNC_SIM_EVENT_RTCP_PIVOT &&
event.line == 7 &&
event.end.z == 130.0);
saw_m428 = saw_m428 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 3 &&
event.reserved == 1 &&
event.feed == 1.0);
saw_m429 = saw_m429 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 3 &&
event.reserved == 0 &&
event.feed == 0.0);
saw_m430 = saw_m430 ||
(event.type == CNC_SIM_EVENT_KINEMATICS_SWITCH &&
event.line == 3 &&
event.reserved == 2 &&
event.feed == 1.0);
saw_g49 = saw_g49 ||
(event.type == CNC_SIM_EVENT_RTCP_STATE &&
event.line == 4 &&
event.feed == 0.0);
saw_g434 = saw_g434 ||
(event.type == CNC_SIM_EVENT_RTCP_STATE &&
event.line == 5 &&
event.feed == 1.0 &&
event.tool == 7 &&
event.dwell_seconds == 125.0);
saw_incremental_move = saw_incremental_move ||
(event.type == CNC_SIM_EVENT_LINEAR_FEED &&
event.start.x == 20.0 && event.end.x == 25.0);
saw_r_arc_center = saw_r_arc_center ||
(event.type == CNC_SIM_EVENT_ARC_FEED &&
event.line == 14 &&
(event.center.x != event.start.x || event.center.y != event.start.y));
}
ok &= expect(saw_rapid, "expected rapid event");
ok &= expect(saw_feed, "expected linear feed event");
ok &= expect(saw_arc, "expected arc feed event");
ok &= expect(saw_dwell, "expected dwell event");
ok &= expect(saw_end, "expected program end event");
ok &= expect(saw_units, "expected units event from multi-G line");
ok &= expect(saw_plane, "expected plane event from multi-G line");
ok &= expect(saw_rtcp, "expected RTCP pivot event from config");
ok &= expect(saw_m428, "expected M428 original kinematics switch");
ok &= expect(saw_m429, "expected M429 identity kinematics switch");
ok &= expect(saw_m430, "expected M430 five-axis BC kinematics switch");
ok &= expect(saw_g49, "expected G49 RTCP off state");
ok &= expect(saw_g434, "expected G43.4 RTCP on state with H code");
ok &= expect(saw_incremental_move, "expected G91 incremental move");
ok &= expect(saw_r_arc_center, "expected R arc center calculation");
const char linuxcnc_config[] = "{\"backend\":\"linuxcnc-rs274\"}";
config_rc = cnc_sim_load_config_json(sim, linuxcnc_config, sizeof(linuxcnc_config) - 1);
rc = cnc_sim_parse_program(sim, program, sizeof(program) - 1);
ok &= expect(config_rc == 0, cnc_sim_last_error(sim));
ok &= expect(rc != 0, "expected uncompiled linuxcnc-rs274 backend to fail clearly");
cnc_sim_destroy(sim);
return ok ? 0 : 1;
}

View File

@@ -0,0 +1,96 @@
#include "canon_event_sink.h"
#include "linuxcnc_canon_bridge.h"
#include "canon.hh"
#include <iostream>
#include <vector>
namespace {
int collect_event(const CncSimEvent *event, void *user_data) {
auto *events = static_cast<std::vector<CncSimEvent> *>(user_data);
events->push_back(*event);
return 0;
}
bool expect(bool value, const char *message) {
if (!value) {
std::cerr << "FAIL: " << message << '\n';
return false;
}
return true;
}
} // namespace
int main() {
std::vector<CncSimEvent> events;
CanonEventSink sink;
sink.set_callback(collect_event, &events);
cnc_sim_linuxcnc_set_canon_sink(&sink);
INIT_CANON();
USE_LENGTH_UNITS(CANON_UNITS_MM);
SELECT_PLANE(CANON_PLANE::XY);
SET_FEED_RATE(500.0);
SELECT_TOOL(7);
CHANGE_TOOL();
STRAIGHT_TRAVERSE(10, 0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
STRAIGHT_FEED(11, 10.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
ARC_FEED(12, 10.0, 10.0, 10.0, 5.0, 1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
SET_G5X_OFFSET(1, 10.0, 20.0, 30.0, 1.0, 2.0, 3.0, 0.0, 0.0, 0.0);
SET_G92_OFFSET(1.0, 2.0, 3.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
SET_XY_ROTATION(15.0);
PROGRAM_END();
bool ok = true;
ok &= expect(events.size() >= 8, "expected bridge events");
ok &= expect(events[0].type == CNC_SIM_EVENT_SET_UNITS, "expected units event");
ok &= expect(events[1].type == CNC_SIM_EVENT_SET_PLANE, "expected plane event");
bool saw_tool = false;
bool saw_rapid = false;
bool saw_feed = false;
bool saw_arc = false;
bool saw_g5x = false;
bool saw_g92 = false;
bool saw_rotation = false;
bool saw_end = false;
for (const auto &event : events) {
saw_tool = saw_tool || (event.type == CNC_SIM_EVENT_TOOL_CHANGE && event.tool == 7);
saw_rapid = saw_rapid || (event.type == CNC_SIM_EVENT_RAPID && event.end.z == 5.0);
saw_feed = saw_feed || (event.type == CNC_SIM_EVENT_LINEAR_FEED && event.end.x == 10.0);
saw_arc = saw_arc || (event.type == CNC_SIM_EVENT_ARC_FEED &&
event.end.y == 10.0 &&
event.center.x == 10.0 &&
event.center.y == 5.0);
saw_g5x = saw_g5x || (event.type == CNC_SIM_EVENT_SET_G5X_OFFSET &&
event.tool == 1 &&
event.start.x == 10.0 &&
event.start.y == 20.0 &&
event.start.z == 30.0 &&
event.start.a == 1.0 &&
event.start.b == 2.0 &&
event.start.c == 3.0);
saw_g92 = saw_g92 || (event.type == CNC_SIM_EVENT_SET_G92_OFFSET &&
event.start.x == 1.0 &&
event.start.y == 2.0 &&
event.start.z == 3.0);
saw_rotation = saw_rotation || (event.type == CNC_SIM_EVENT_SET_XY_ROTATION &&
event.feed == 15.0);
saw_end = saw_end || event.type == CNC_SIM_EVENT_PROGRAM_END;
}
ok &= expect(saw_tool, "expected tool change");
ok &= expect(saw_rapid, "expected rapid move");
ok &= expect(saw_feed, "expected linear feed");
ok &= expect(saw_arc, "expected arc feed");
ok &= expect(saw_g5x, "expected G5X offset event");
ok &= expect(saw_g92, "expected G92 offset event");
ok &= expect(saw_rotation, "expected XY rotation event");
ok &= expect(saw_end, "expected program end");
cnc_sim_linuxcnc_set_canon_sink(nullptr);
return ok ? 0 : 1;
}

View File

@@ -0,0 +1,67 @@
#include "rtcp_kinematics.h"
#include <cmath>
#include <iostream>
namespace {
bool near(double actual, double expected) {
return std::fabs(actual - expected) < 1e-9;
}
bool expect_near(double actual, double expected, const char *message) {
if (!near(actual, expected)) {
std::cerr << "FAIL: " << message << " actual=" << actual << " expected=" << expected << '\n';
return false;
}
return true;
}
bool expect_pose_near(const CncSimPose &actual, const CncSimPose &expected, const char *message) {
bool ok = true;
ok &= expect_near(actual.x, expected.x, message);
ok &= expect_near(actual.y, expected.y, message);
ok &= expect_near(actual.z, expected.z, message);
ok &= expect_near(actual.a, expected.a, message);
ok &= expect_near(actual.b, expected.b, message);
ok &= expect_near(actual.c, expected.c, message);
return ok;
}
} // namespace
int main() {
bool ok = true;
CncSimPose tip{};
tip.x = 10.0;
tip.y = 20.0;
tip.z = 30.0;
CncSimPose pivot = rtcp_pivot_from_tool_tip(tip, 100.0);
ok &= expect_near(pivot.x, 10.0, "zero-angle pivot x");
ok &= expect_near(pivot.y, 20.0, "zero-angle pivot y");
ok &= expect_near(pivot.z, 130.0, "zero-angle pivot z");
tip.a = 90.0;
pivot = rtcp_pivot_from_tool_tip(tip, 100.0);
ok &= expect_near(pivot.x, 10.0, "A90 pivot x");
ok &= expect_near(pivot.y, -80.0, "A90 pivot y");
ok &= expect_near(pivot.z, 30.0, "A90 pivot z");
tip.a = 0.0;
tip.b = 90.0;
pivot = rtcp_pivot_from_tool_tip(tip, 100.0);
ok &= expect_near(pivot.x, 110.0, "B90 pivot x");
ok &= expect_near(pivot.y, 20.0, "B90 pivot y");
ok &= expect_near(pivot.z, 30.0, "B90 pivot z");
tip.a = 35.0;
tip.b = -20.0;
tip.c = 15.0;
pivot = rtcp_pivot_from_tool_tip(tip, 123.4);
const CncSimPose roundtrip = rtcp_tool_tip_from_pivot(pivot, 123.4);
ok &= expect_pose_near(roundtrip, tip, "RTCP pivot/tool-tip roundtrip");
return ok ? 0 : 1;
}

View File

@@ -0,0 +1,59 @@
#include "simulator_gcode_controls.h"
#include <iostream>
#include <vector>
namespace {
bool expect(bool value, const char *message) {
if (!value) {
std::cerr << "FAIL: " << message << '\n';
return false;
}
return true;
}
} // namespace
int main() {
bool ok = true;
std::vector<SimulatorGcodeControlAction> actions;
ok &= expect(parse_simulator_gcode_control_line("M428 M429 M430 ; switch modes", &actions),
"expected M428/M429/M430 control line");
ok &= expect(actions.size() == 3, "expected three kinematics actions");
ok &= expect(actions[0].kind == SimulatorGcodeControlKind::KinematicsSwitch &&
actions[0].value == 1 &&
actions[0].rtcp_enabled,
"expected M428 original kinematics action");
ok &= expect(actions[1].kind == SimulatorGcodeControlKind::KinematicsSwitch &&
actions[1].value == 0 &&
!actions[1].rtcp_enabled,
"expected M429 identity kinematics action");
ok &= expect(actions[2].kind == SimulatorGcodeControlKind::KinematicsSwitch &&
actions[2].value == 2 &&
actions[2].rtcp_enabled,
"expected M430 five-axis BC kinematics action");
ok &= expect(parse_simulator_gcode_control_line("G49 (cancel RTCP)", &actions),
"expected G49 control line");
ok &= expect(actions.size() == 1 &&
actions[0].kind == SimulatorGcodeControlKind::RtcpState &&
!actions[0].rtcp_enabled &&
actions[0].h_code == 0,
"expected G49 RTCP off action");
ok &= expect(parse_simulator_gcode_control_line("G43.4 H7", &actions),
"expected G43.4 H7 control line");
ok &= expect(actions.size() == 1 &&
actions[0].kind == SimulatorGcodeControlKind::RtcpState &&
actions[0].rtcp_enabled &&
actions[0].h_code == 7,
"expected G43.4 RTCP on action with H7");
ok &= expect(!parse_simulator_gcode_control_line("G43.4 H7 X10", &actions),
"expected mixed RTCP/motion line to fall through to interpreter");
ok &= expect(actions.empty(), "expected no actions for mixed RTCP/motion line");
return ok ? 0 : 1;
}

120
core/tools/cnc_sim_dump.cpp Normal file
View File

@@ -0,0 +1,120 @@
#include "cnc_sim_api.h"
#include <fstream>
#include <iostream>
#include <sstream>
namespace {
const char *event_type_name(CncSimEventType type) {
switch (type) {
case CNC_SIM_EVENT_ERROR:
return "error";
case CNC_SIM_EVENT_COMMENT:
return "comment";
case CNC_SIM_EVENT_SET_UNITS:
return "set-units";
case CNC_SIM_EVENT_SET_PLANE:
return "set-plane";
case CNC_SIM_EVENT_SET_FEED:
return "set-feed";
case CNC_SIM_EVENT_SET_SPINDLE:
return "set-spindle";
case CNC_SIM_EVENT_TOOL_CHANGE:
return "tool-change";
case CNC_SIM_EVENT_DWELL:
return "dwell";
case CNC_SIM_EVENT_RAPID:
return "rapid";
case CNC_SIM_EVENT_LINEAR_FEED:
return "linear-feed";
case CNC_SIM_EVENT_ARC_FEED:
return "arc-feed";
case CNC_SIM_EVENT_PROBE:
return "probe";
case CNC_SIM_EVENT_PROGRAM_END:
return "program-end";
case CNC_SIM_EVENT_RTCP_PIVOT:
return "rtcp-pivot";
case CNC_SIM_EVENT_KINEMATICS_SWITCH:
return "kinematics-switch";
case CNC_SIM_EVENT_RTCP_STATE:
return "rtcp-state";
case CNC_SIM_EVENT_SET_G5X_OFFSET:
return "set-g5x-offset";
case CNC_SIM_EVENT_SET_G92_OFFSET:
return "set-g92-offset";
case CNC_SIM_EVENT_SET_XY_ROTATION:
return "set-xy-rotation";
default:
return "none";
}
}
void print_pose(const CncSimPose &pose) {
std::cout << "{\"x\":" << pose.x
<< ",\"y\":" << pose.y
<< ",\"z\":" << pose.z
<< ",\"a\":" << pose.a
<< ",\"b\":" << pose.b
<< ",\"c\":" << pose.c
<< ",\"u\":" << pose.u
<< ",\"v\":" << pose.v
<< ",\"w\":" << pose.w << '}';
}
int print_event(const CncSimEvent *event, void *user_data) {
auto *first = static_cast<bool *>(user_data);
if (!*first) {
std::cout << ",\n";
}
*first = false;
std::cout << " {\"type\":\"" << event_type_name(event->type)
<< "\",\"line\":" << event->line
<< ",\"plane\":" << event->plane
<< ",\"tool\":" << event->tool
<< ",\"feed\":" << event->feed
<< ",\"spindle\":" << event->spindle
<< ",\"dwellSeconds\":" << event->dwell_seconds
<< ",\"arcTurns\":" << event->arc_turns
<< ",\"reserved\":" << event->reserved
<< ",\"start\":";
print_pose(event->start);
std::cout << ",\"end\":";
print_pose(event->end);
std::cout << ",\"center\":";
print_pose(event->center);
std::cout << '}';
return 0;
}
std::string read_all(const char *path) {
if (!path || std::string(path) == "-") {
std::ostringstream out;
out << std::cin.rdbuf();
return out.str();
}
std::ifstream file(path);
std::ostringstream out;
out << file.rdbuf();
return out.str();
}
} // namespace
int main(int argc, char **argv) {
std::string program = read_all(argc > 1 ? argv[1] : "-");
CncSimHandle *sim = cnc_sim_create();
bool first = true;
cnc_sim_set_event_callback(sim, print_event, &first);
std::cout << "[\n";
const int rc = cnc_sim_parse_program(sim, program.c_str(), program.size());
std::cout << "\n]\n";
if (rc != 0) {
std::cerr << cnc_sim_last_error(sim) << '\n';
}
cnc_sim_destroy(sim);
return rc == 0 ? 0 : 1;
}

View File

@@ -0,0 +1,250 @@
#include <Python.h>
#include "canon_event_sink.h"
#include "linuxcnc_canon_bridge.h"
#include "simulator_gcode_controls.h"
#include "linuxcnc.h"
#include "nml_intf/interp_return.hh"
#include "nml_intf/canon.hh"
#include "rs274ngc/interp_base.hh"
#include "emc/tooldata/tooldata.hh"
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <cstdlib>
int _task = 0;
char _parameter_file_name[LINELEN];
extern "C" PyObject *PyInit_interpreter(void);
extern "C" PyObject *PyInit_emccanon(void);
extern "C" struct _inittab builtin_modules[];
struct _inittab builtin_modules[] = {
{"interpreter", PyInit_interpreter},
{"emccanon", PyInit_emccanon},
{nullptr, nullptr},
};
namespace {
const char *event_type_name(CncSimEventType type) {
switch (type) {
case CNC_SIM_EVENT_ERROR:
return "error";
case CNC_SIM_EVENT_COMMENT:
return "comment";
case CNC_SIM_EVENT_SET_UNITS:
return "set-units";
case CNC_SIM_EVENT_SET_PLANE:
return "set-plane";
case CNC_SIM_EVENT_SET_FEED:
return "set-feed";
case CNC_SIM_EVENT_SET_SPINDLE:
return "set-spindle";
case CNC_SIM_EVENT_TOOL_CHANGE:
return "tool-change";
case CNC_SIM_EVENT_DWELL:
return "dwell";
case CNC_SIM_EVENT_RAPID:
return "rapid";
case CNC_SIM_EVENT_LINEAR_FEED:
return "linear-feed";
case CNC_SIM_EVENT_ARC_FEED:
return "arc-feed";
case CNC_SIM_EVENT_PROBE:
return "probe";
case CNC_SIM_EVENT_PROGRAM_END:
return "program-end";
case CNC_SIM_EVENT_RTCP_PIVOT:
return "rtcp-pivot";
case CNC_SIM_EVENT_KINEMATICS_SWITCH:
return "kinematics-switch";
case CNC_SIM_EVENT_RTCP_STATE:
return "rtcp-state";
case CNC_SIM_EVENT_SET_G5X_OFFSET:
return "set-g5x-offset";
case CNC_SIM_EVENT_SET_G92_OFFSET:
return "set-g92-offset";
case CNC_SIM_EVENT_SET_XY_ROTATION:
return "set-xy-rotation";
default:
return "none";
}
}
void print_pose(const CncSimPose &pose) {
std::cout << "{\"x\":" << pose.x
<< ",\"y\":" << pose.y
<< ",\"z\":" << pose.z
<< ",\"a\":" << pose.a
<< ",\"b\":" << pose.b
<< ",\"c\":" << pose.c
<< ",\"u\":" << pose.u
<< ",\"v\":" << pose.v
<< ",\"w\":" << pose.w << '}';
}
int print_event(const CncSimEvent *event, void *user_data) {
auto *first = static_cast<bool *>(user_data);
if (!*first) {
std::cout << ",\n";
}
*first = false;
std::cout << " {\"type\":\"" << event_type_name(event->type)
<< "\",\"line\":" << event->line
<< ",\"plane\":" << event->plane
<< ",\"tool\":" << event->tool
<< ",\"feed\":" << event->feed
<< ",\"spindle\":" << event->spindle
<< ",\"dwellSeconds\":" << event->dwell_seconds
<< ",\"arcTurns\":" << event->arc_turns
<< ",\"reserved\":" << event->reserved
<< ",\"start\":";
print_pose(event->start);
std::cout << ",\"end\":";
print_pose(event->end);
std::cout << ",\"center\":";
print_pose(event->center);
std::cout << '}';
return 0;
}
std::string read_all(const char *path) {
if (!path || std::string(path) == "-") {
std::ostringstream out;
out << std::cin.rdbuf();
return out.str();
}
std::ifstream file(path);
std::ostringstream out;
out << file.rdbuf();
return out.str();
}
bool trace_enabled() {
return std::getenv("CNC_SIM_TRACE_RS274") != nullptr;
}
bool execute_line(InterpBase *interp,
CanonEventSink &sink,
const std::string &line,
int line_number,
bool *program_done) {
std::vector<SimulatorGcodeControlAction> control_actions;
if (parse_simulator_gcode_control_line(line, &control_actions)) {
for (const auto &action : control_actions) {
emit_simulator_gcode_control_action(sink, action, line_number);
if (sink.callback_aborted()) {
std::cerr << "event callback aborted parsing\n";
return false;
}
}
return true;
}
if (trace_enabled()) {
std::cerr << "rs274:read line " << line_number << ": " << line << '\n';
}
const int read_rc = interp->read(line.c_str());
if (read_rc == INTERP_ENDFILE || read_rc == INTERP_EXIT) {
*program_done = true;
return true;
}
if (read_rc != 0) {
char error[1024]{};
interp->error_text(read_rc, error, sizeof(error));
std::cerr << "read failed: " << error << '\n';
return false;
}
if (trace_enabled()) {
std::cerr << "rs274:execute line " << line_number << '\n';
}
const int exec_rc = interp->execute();
if (exec_rc == INTERP_EXIT || exec_rc == INTERP_ENDFILE) {
*program_done = true;
return true;
}
if (exec_rc == INTERP_EXECUTE_FINISH) {
return true;
}
if (exec_rc != 0) {
char error[1024]{};
interp->error_text(exec_rc, error, sizeof(error));
std::cerr << "execute failed: " << error << '\n';
return false;
}
if (trace_enabled()) {
std::cerr << "rs274:done line " << line_number << '\n';
}
return true;
}
void init_minimal_tooldata() {
tool_mmap_creator(nullptr, 0);
tooldata_reset();
CANON_TOOL_TABLE spindle = tooldata_entry_init();
spindle.toolno = 0;
spindle.pocketno = 0;
tooldata_put(spindle, 0);
CANON_TOOL_TABLE tool = tooldata_entry_init();
tool.toolno = 1;
tool.pocketno = 1;
tool.diameter = 6.0;
tooldata_put(tool, 1);
}
} // namespace
int main(int argc, char **argv) {
const std::string program = read_all(argc > 1 ? argv[1] : "-");
if (const char *parameter_file = std::getenv("CNC_SIM_RS274_VAR")) {
SET_PARAMETER_FILE_NAME(parameter_file);
}
init_minimal_tooldata();
CanonEventSink sink;
bool first = true;
sink.set_callback(print_event, &first);
cnc_sim_linuxcnc_set_canon_sink(&sink);
std::cout << "[\n";
InterpBase *interp = makeInterp();
if (trace_enabled()) {
std::cerr << "rs274:init\n";
}
const int init_rc = interp->init();
if (init_rc != 0) {
char error[1024]{};
interp->error_text(init_rc, error, sizeof(error));
std::cerr << "init failed: " << error << '\n';
std::cout << "\n]\n";
return 1;
}
if (trace_enabled()) {
std::cerr << "rs274:init done\n";
}
std::istringstream input(program);
std::string line;
bool ok = true;
bool program_done = false;
int line_number = 0;
while (!program_done && std::getline(input, line)) {
++line_number;
if (!execute_line(interp, sink, line, line_number, &program_done)) {
ok = false;
break;
}
}
std::cout << "\n]\n";
interp->exit();
delete interp;
cnc_sim_linuxcnc_set_canon_sink(nullptr);
return ok ? 0 : 1;
}