Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic automated ATC implementations (Draft/WIP) #1297

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
167 changes: 167 additions & 0 deletions FluidNC/src/ToolChangers/atc_basic.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Copyright (c) 2024 - Bart Dring
// Copyright (c) 2024 - Nicolas Lang
// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file.

#include "atc_basic.h"
#include "../Machine/MachineConfig.h"
#include <cstdio>
#include <iostream>

namespace ATCs {
void Basic_ATC::init() {
log_info("ATC:" << name());
}

void Basic_ATC::probe_notification() {}

bool Basic_ATC::tool_change(uint8_t new_tool, bool pre_select, bool set_tool) {
std::string message = "ATC:tool_change: from " + std::to_string(_prev_tool) + " to " + std::to_string(new_tool);
log_info(message);

protocol_buffer_synchronize(); // wait for all motion to complete
_macro.erase(); // clear previous gcode

if (pre_select) { // not implemented
log_debug("ATC: Preselect");
return true;
}

// save current location, so we can return after the tool change.
_macro.addf("#<start_x >= #<_x>");
_macro.addf("#<start_y >= #<_y>");
_macro.addf("#<start_z >= #<_z>");

//save machine states
Macro set_state;
Macro restore_state;
set_state.addf("M9"); // Disable coolant
if (gc_state.modal.coolant.Mist) {
restore_state.addf("M7");
}
if (gc_state.modal.coolant.Flood) {
restore_state.addf("M8");
}
set_state.addf("G21"); // become Metric
if (gc_state.modal.units == Units::Inches) {
restore_state.addf("G20");
}
set_state.addf("G90"); // become absolute
if (gc_state.modal.distance != Distance::Absolute) {
restore_state.addf("G91");
}
set_state.addf("M5"); // turn off the spindle
if (gc_state.modal.spindle != SpindleState::Disable) {
restore_state.addf("M3");
}

_macro.addf(set_state._gcode.c_str());

// set_tool is used to update the current tool and reset the TLO to 0
// if we dont have_tool_setter_offset then we also do an initial probe
if (set_tool) {
log_debug("ATC: Set Tool");
_prev_tool = new_tool;
_macro.addf("G4P0 0.0");
if (!_have_tool_setter_offset) {
get_ets_offset();
}
_macro.addf(restore_state._gcode.c_str());
move_to_start_position();
_macro.run(nullptr);
return true;
}

try {
_macro.addf(_atc_activate_macro._gcode.c_str());
_macro.addf(set_state._gcode.c_str());

if (_prev_tool > 0) {
log_debug("ATC: return tool");
move_to_safe_z();
set_tool_position(_prev_tool);
_macro.addf(_toolreturn_macro._gcode.c_str()); // use macro with G91 movements or the _tc_tool_* variables to to return tool, operating the ATC using M62 & M63
_macro.addf(set_state._gcode.c_str()); // ensure the previous user macro didn't change modes
}

if (new_tool > 0) {
log_debug("ATC: pickup tool");
//if this is the 1st pickup ever, we also probe the tool_setter_offset
move_to_safe_z();
set_tool_position(new_tool);
_macro.addf(_toolpickup_macro._gcode.c_str()); // use macro with G91 movements or the _tc_tool_* variables to to pickup tool, operating the ATC using M62 & M63
_macro.addf(set_state._gcode.c_str()); // ensure the previous user macro didn't change modes
if (!_have_tool_setter_offset) {
get_ets_offset();
_macro.addf("#<_my_tlo_z >=0.0"); //first tool is reference with 0 offset
} else {
ets_probe(); // probe the new tool
// TLO is simply the difference between the tool1 probe and the new tool probe.
_macro.addf("#<_my_tlo_z >=[#5063 - #<_ets_tool1_z>]");
}
_macro.addf("(print,ATC: New TLO #<_my_tlo_z>)");
_macro.addf("G43.1Z#<_my_tlo_z>");
}

move_to_start_position();

_macro.addf(_atc_deactivate_macro._gcode.c_str());
_macro.addf(set_state._gcode.c_str());

_macro.addf(restore_state._gcode.c_str());
_macro.run(nullptr);

_prev_tool = new_tool;

return true;
} catch (...) { log_info("Exception caught"); }

return false;
}

void Basic_ATC::move_to_start_position(){
// return to location before the tool change
move_to_safe_z();
_macro.addf("G0 X#<start_x>Y#<start_y>");
_macro.addf("G0 Z#<start_z>");
}

void Basic_ATC::set_tool_position(uint8_t tool_index) {
tool_index -= 1;
_macro.addf("#<_tc_tool_x >=%0.3f", _tool_mpos[tool_index][X_AXIS]);
_macro.addf("#<_tc_tool_y >=%0.3f", _tool_mpos[tool_index][Y_AXIS]);
_macro.addf("#<_tc_tool_z >=%0.3f", _tool_mpos[tool_index][Z_AXIS]);
}

void Basic_ATC::move_to_safe_z() {
_macro.addf("G53 G0 Z%0.3f", _safe_z);
}

void Basic_ATC::move_over_toolsetter() {
move_to_safe_z();
_macro.addf("G53 G0 X%0.3fY%0.3f", _ets_mpos[0], _ets_mpos[1]);
}

void Basic_ATC::get_ets_offset() {
ets_probe();
_macro.addf("#<_ets_tool1_z>=[#5063]"); // save the value of the tool1 ETS Z
_have_tool_setter_offset = true;
}

void Basic_ATC::ets_probe() {
move_to_safe_z();
move_over_toolsetter();
_macro.addf("G53 G0 Z #</ atc_basic / ets_rapid_z_mpos_mm>"); // rapid down

// do a fast probe if there is a seek that is faster than feed
if (_probe_seek_rate > _probe_feed_rate) {
_macro.addf("G53 G38.2 Z%0.3f F%0.3f", _ets_mpos[2], _probe_seek_rate);
_macro.addf("G0 Z[#<_z> + 5]"); // retract before next probe
}
// do the feed rate probe
_macro.addf("G53 G38.2 Z%0.3f F%0.3f", _ets_mpos[2], _probe_feed_rate);
}

namespace {
ATCFactory::InstanceBuilder<Basic_ATC> registration("atc_basic");
}
}
83 changes: 83 additions & 0 deletions FluidNC/src/ToolChangers/atc_basic.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (c) 2024 - Bart Dring
// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file.

#pragma once

#include "src/Config.h"

#include "src/Configuration/Configurable.h"

#include "src/Channel.h"
#include "src/Module.h"
#include "atc.h"
#include "../Machine/Macros.h"

namespace ATCs {
const int TOOL_COUNT = 8;

class Basic_ATC : public ATC {
public:
Basic_ATC(const char* name) : ATC(name) {}

Basic_ATC(const Basic_ATC&) = delete;
Basic_ATC(Basic_ATC&&) = delete;
Basic_ATC& operator=(const Basic_ATC&) = delete;
Basic_ATC& operator=(Basic_ATC&&) = delete;

virtual ~Basic_ATC() = default;

private:
// config items
float _safe_z = 50.0;
float _probe_seek_rate = 200.0;
float _probe_feed_rate = 80.0;
std::vector<float> _ets_mpos = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
float _ets_rapid_z_mpos = 0;
std::vector<float> _tool_mpos[TOOL_COUNT];

uint8_t _prev_tool = 0; // TODO This could be a NV setting
bool _have_tool_setter_offset = false;
float _tool_setter_offset = 0.0; // have we established an offset.
float _tool_setter_position[MAX_N_AXIS];

void move_to_safe_z();
void move_over_toolsetter();
void ets_probe();
void get_ets_offset();
void set_tool_position(uint8_t tool_index);
void move_to_start_position();

Macro _macro;
Macro _toolreturn_macro;
Macro _toolpickup_macro;
Macro _atc_activate_macro;
Macro _atc_deactivate_macro;

public:
void init() override;
void probe_notification() override;
bool tool_change(uint8_t value, bool pre_select, bool set_tool) override;

void validate() override {}

void group(Configuration::HandlerBase& handler) override {
handler.item("safe_z_mpos_mm", _safe_z, -100000, 100000);
handler.item("probe_seek_rate_mm_per_min", _probe_seek_rate, 1, 10000);
handler.item("probe_feed_rate_mm_per_min", _probe_feed_rate, 1, 10000);
handler.item("ets_mpos_mm", _ets_mpos);
handler.item("ets_rapid_z_mpos_mm", _ets_rapid_z_mpos);
handler.item("toolreturn_macro", _toolreturn_macro);
handler.item("toolpickup_macro", _toolpickup_macro);
handler.item("atc_activate_macro", _atc_activate_macro);
handler.item("atc_deactivate_macro", _atc_deactivate_macro);
handler.item("tool1_mpos_mm", _tool_mpos[0]);
handler.item("tool2_mpos_mm", _tool_mpos[1]);
handler.item("tool3_mpos_mm", _tool_mpos[2]);
handler.item("tool4_mpos_mm", _tool_mpos[3]);
handler.item("tool5_mpos_mm", _tool_mpos[4]);
handler.item("tool6_mpos_mm", _tool_mpos[5]);
handler.item("tool7_mpos_mm", _tool_mpos[6]);
handler.item("tool8_mpos_mm", _tool_mpos[7]);
}
};
}
64 changes: 64 additions & 0 deletions FluidNC/src/ToolChangers/atc_basic.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
### Disclaimer: Use at Your Own Risk

The configuration and source code provided are made available "as is" without any guarantees or warranties, express or implied. By using this configuration and source code, you acknowledge that you do so at your own risk. The creator is not responsible for any damages, malfunctions, or issues that may arise from their use. Please review and modify the code according to your own requirements and ensure proper testing and safety measures are in place.

# Example cfg

```yaml
atc_basic:
safe_z_mpos_mm: -1.000
probe_seek_rate_mm_per_min: 400.000
probe_feed_rate_mm_per_min: 100.000
ets_mpos_mm: -72.500 -7.500 -30.000
ets_rapid_z_mpos_mm: -10.000
atc_activate_macro: $LocalFS/Run=/ATC/atc_activate_macro.g
atc_deactivate_macro: $LocalFS/Run=/ATC/atc_deactivate_macro.g
toolpickup_macro: $LocalFS/Run=/ATC/toolpickup_macro.g
toolreturn_macro: $LocalFS/Run=/ATC/toolreturn_macro.g
tool8_mpos_mm: -844.000 -2.000 -76.000
tool7_mpos_mm: -844.000 -2.000 -76.000
tool6_mpos_mm: -784.000 -2.000 -76.000
tool5_mpos_mm: -724.000 -2.000 -76.000
tool4_mpos_mm: -664.000 -2.000 -76.000
tool3_mpos_mm: -604.000 -2.000 -76.000
tool2_mpos_mm: -544.000 -2.000 -76.000
tool1_mpos_mm: -484.000 -2.000 -76.000
```

the following macros need to be saved to `$LocalFS/ATC/`
```gcode
(/ATC/atc_activate_macro.g)
G53 G0 Z-1
G53 G0 X-694 Y-150
```

```gcode
(/ATC/atc_deactivate_macro.g)
```

```gcode
(/ATC/toolpickup_macro.g)
G91
G53 G0 X#<_tc_tool_x> Y#<_tc_tool_y>
M62 P6
G4 P0 1.0
G53 G0 Z#<_tc_tool_z>
M63 P6
G4 P0 1.0
G1 Y-15.0 F500
G0 Y-20.0
G53 G0 Z-1.0
```

```gcode
(/ATC/toolreturn_macro.g)
G91
G53 G0 X#<_tc_tool_x> Y[#<_tc_tool_y> -35.0]
G53 G0 Z#<_tc_tool_z>
G0 Y+20.0
G1 Y+15.0 F500
M62 P6
G4 P0 1.0
G53 G0 Z-1.0
M63 P6
```
Loading