Skip to content

Commit 2c7b578

Browse files
committed
Support gcode_arcs [experimental]
1 parent dddd5b6 commit 2c7b578

File tree

5 files changed

+286
-19
lines changed

5 files changed

+286
-19
lines changed

lib/src/arcs.rs

+220
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
use glam::{DVec3 as Vec3, Vec4Swizzles};
2+
3+
use crate::gcode::GCodeTraditionalParams;
4+
use crate::kind_tracker::Kind;
5+
use crate::planner::{OperationSequence, PositionMode, ToolheadState};
6+
7+
#[derive(Debug, Default)]
8+
pub struct ArcState {
9+
plane: Plane,
10+
}
11+
12+
impl ArcState {
13+
pub fn set_plane(&mut self, plane: Plane) {
14+
self.plane = plane;
15+
}
16+
17+
pub fn generate_arc(
18+
&self,
19+
toolhead_state: &mut ToolheadState,
20+
op_sequence: &mut OperationSequence,
21+
move_kind: Option<Kind>,
22+
params: &GCodeTraditionalParams,
23+
direction: ArcDirection,
24+
) -> usize {
25+
let args = match self.get_args(toolhead_state, params) {
26+
None => return 0,
27+
Some(args) => args,
28+
};
29+
30+
let (segments, arc) = args.plan_arc(
31+
toolhead_state.position.xyz(),
32+
direction,
33+
args.mm_per_arc_segment,
34+
);
35+
let e_base = toolhead_state.position.w;
36+
let e_per_move = args.e.map(|e| (e - e_base) / (segments as f64));
37+
38+
toolhead_state.set_speed(args.velocity);
39+
40+
let old_pos_mode = toolhead_state.position_modes;
41+
toolhead_state.position_modes = [PositionMode::Absolute; 4];
42+
for (i, segment) in arc.enumerate() {
43+
let coord = [
44+
Some(segment.x),
45+
Some(segment.y),
46+
Some(segment.z),
47+
e_per_move.map(|e| e_base + (i as f64) * e),
48+
];
49+
let mut pm = toolhead_state.perform_move(coord);
50+
pm.kind = move_kind;
51+
op_sequence.add_move(pm, toolhead_state);
52+
}
53+
toolhead_state.position_modes = old_pos_mode;
54+
55+
segments
56+
}
57+
58+
fn get_args(
59+
&self,
60+
toolhead_state: &mut ToolheadState,
61+
params: &GCodeTraditionalParams,
62+
) -> Option<ArcArgs> {
63+
let mm_per_arc_segment = toolhead_state.limits.mm_per_arc_segment?;
64+
65+
let map_coord = |c: f64, axis: usize| {
66+
ToolheadState::new_element(
67+
c,
68+
toolhead_state.position.as_ref()[axis],
69+
toolhead_state.position_modes[axis],
70+
)
71+
};
72+
73+
let (axes, offset) = match self.plane {
74+
Plane::XY => (
75+
(0, 1, 2),
76+
(
77+
params.get_number::<f64>('I').unwrap_or(0.0),
78+
params.get_number::<f64>('J').unwrap_or(0.0),
79+
),
80+
),
81+
Plane::XZ => (
82+
(0, 2, 1),
83+
(
84+
params.get_number::<f64>('I').unwrap_or(0.0),
85+
params.get_number::<f64>('K').unwrap_or(0.0),
86+
),
87+
),
88+
Plane::YZ => (
89+
(1, 2, 0),
90+
(
91+
params.get_number::<f64>('J').unwrap_or(0.0),
92+
params.get_number::<f64>('K').unwrap_or(0.0),
93+
),
94+
),
95+
};
96+
97+
if offset.0 == 0.0 && offset.1 == 0.0 {
98+
return None; // We need at least one coordinate to work with
99+
}
100+
101+
Some(ArcArgs {
102+
target: Vec3::new(
103+
params
104+
.get_number::<f64>('X')
105+
.map_or(toolhead_state.position.x, |c| map_coord(c, 0)),
106+
params
107+
.get_number::<f64>('Y')
108+
.map_or(toolhead_state.position.y, |c| map_coord(c, 1)),
109+
params
110+
.get_number::<f64>('Z')
111+
.map_or(toolhead_state.position.z, |c| map_coord(c, 2)),
112+
),
113+
e: params.get_number::<f64>('E').map(|c| map_coord(c, 3)),
114+
velocity: params
115+
.get_number::<f64>('F')
116+
.map_or(toolhead_state.velocity, |v| v / 60.0),
117+
axes,
118+
offset,
119+
mm_per_arc_segment,
120+
})
121+
}
122+
}
123+
124+
#[derive(Debug, Copy, Clone, PartialEq)]
125+
struct ArcArgs {
126+
target: Vec3,
127+
e: Option<f64>,
128+
velocity: f64,
129+
axes: (usize, usize, usize),
130+
offset: (f64, f64),
131+
mm_per_arc_segment: f64,
132+
}
133+
134+
impl ArcArgs {
135+
// Ported from klipper, originally from Marlins plan-arc() function.
136+
fn plan_arc(
137+
&self,
138+
start_position: Vec3,
139+
direction: ArcDirection,
140+
mm_per_arc_segment: f64,
141+
) -> (usize, impl Iterator<Item = Vec3> + '_) {
142+
let current_position = start_position.as_ref();
143+
let target_position = self.target.as_ref();
144+
let (alpha_axis, beta_axis, helical_axis) = self.axes;
145+
146+
let r_p = -self.offset.0;
147+
let r_q = -self.offset.1;
148+
149+
let center_p = current_position[alpha_axis] - r_p;
150+
let center_q = current_position[beta_axis] - r_q;
151+
let rt_alpha = target_position[alpha_axis] - center_p;
152+
let rt_beta = target_position[beta_axis] - center_q;
153+
let mut angular_travel =
154+
(r_p * rt_beta - r_q * rt_alpha).atan2(r_p * rt_alpha + r_q * rt_beta);
155+
if angular_travel < 0.0 {
156+
angular_travel += 2.0 * std::f64::consts::PI;
157+
}
158+
if direction == ArcDirection::Clockwise {
159+
angular_travel -= 2.0 * std::f64::consts::PI;
160+
}
161+
162+
if angular_travel == 0.0
163+
&& current_position[alpha_axis] == target_position[alpha_axis]
164+
&& current_position[beta_axis] == target_position[beta_axis]
165+
{
166+
angular_travel = 2.0 * std::f64::consts::PI
167+
}
168+
169+
let linear_travel = target_position[helical_axis] - current_position[helical_axis];
170+
let radius = r_p.hypot(r_q);
171+
let flat_mm = radius * angular_travel;
172+
let mm_of_travel = if linear_travel != 0.0 {
173+
flat_mm.hypot(linear_travel)
174+
} else {
175+
flat_mm.abs()
176+
};
177+
178+
let segments = ((mm_of_travel / mm_per_arc_segment).floor() as usize).max(1);
179+
180+
let theta_per_segment = angular_travel / (segments as f64);
181+
let linear_per_segment = linear_travel / (segments as f64);
182+
(
183+
segments,
184+
(1..segments)
185+
.map(move |i| {
186+
let i = i as f64;
187+
let dist_helical = i * linear_per_segment;
188+
let cos_ti = (i * theta_per_segment).cos();
189+
let sin_ti = (i * theta_per_segment).sin();
190+
let r_p = -self.offset.0 * cos_ti + self.offset.1 * sin_ti;
191+
let r_q = -self.offset.0 * sin_ti - self.offset.1 * cos_ti;
192+
let mut coord = [0.0f64; 3];
193+
coord[alpha_axis] = center_p + r_p;
194+
coord[beta_axis] = center_q + r_q;
195+
coord[helical_axis] = start_position.as_ref()[helical_axis] + dist_helical;
196+
coord.into()
197+
})
198+
.chain(std::iter::once(self.target)),
199+
)
200+
}
201+
}
202+
203+
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
204+
pub enum ArcDirection {
205+
Clockwise,
206+
CounterClockwise,
207+
}
208+
209+
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
210+
pub enum Plane {
211+
XY,
212+
XZ,
213+
YZ,
214+
}
215+
216+
impl Default for Plane {
217+
fn default() -> Self {
218+
Self::XY
219+
}
220+
}

lib/src/kind_tracker.rs

+20
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::collections::HashMap;
44
pub struct KindTracker {
55
pub i2k: HashMap<String, u16>,
66
pub k2i: HashMap<u16, String>,
7+
pub current_kind: Option<Kind>,
78
}
89

910
impl KindTracker {
@@ -26,6 +27,25 @@ impl KindTracker {
2627
pub fn resolve_kind(&self, k: Kind) -> &str {
2728
self.k2i.get(&k.0).expect("missing kind")
2829
}
30+
31+
pub fn kind_from_comment(&mut self, comment: &Option<String>) -> Option<Kind> {
32+
comment
33+
.as_ref()
34+
.map(|s| s.trim())
35+
.map(|s| {
36+
if s.starts_with("move to next layer ") {
37+
"move to next layer"
38+
} else {
39+
s
40+
}
41+
})
42+
.map(|s| self.get_kind(s))
43+
.or(self.current_kind)
44+
}
45+
46+
pub fn set_current(&mut self, kind: Option<Kind>) {
47+
self.current_kind = kind;
48+
}
2949
}
3050

3151
#[derive(Debug, Copy, Clone, Eq, PartialEq)]

lib/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#[macro_use]
22
extern crate lazy_static;
33

4+
pub mod arcs;
45
pub mod firmware_retraction;
56
pub mod gcode;
67
mod kind_tracker;

lib/src/planner.rs

+37-19
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::collections::VecDeque;
22
use std::f64::EPSILON;
33
use std::time::Duration;
44

5+
use crate::arcs::ArcState;
56
pub use crate::firmware_retraction::FirmwareRetractionOptions;
67
use crate::firmware_retraction::FirmwareRetractionState;
78
use crate::gcode::{GCodeCommand, GCodeOperation};
@@ -16,8 +17,8 @@ pub struct Planner {
1617
operations: OperationSequence,
1718
pub toolhead_state: ToolheadState,
1819
pub kind_tracker: KindTracker,
19-
pub current_kind: Option<Kind>,
2020
pub firmware_retraction: Option<FirmwareRetractionState>,
21+
pub arc_state: ArcState,
2122
}
2223

2324
impl Planner {
@@ -30,8 +31,8 @@ impl Planner {
3031
operations: OperationSequence::default(),
3132
toolhead_state: ToolheadState::from_limits(limits),
3233
kind_tracker: KindTracker::new(),
33-
current_kind: None,
3434
firmware_retraction,
35+
arc_state: ArcState::default(),
3536
}
3637
}
3738

@@ -46,19 +47,7 @@ impl Planner {
4647
self.toolhead_state.set_speed(v / 60.0);
4748
}
4849

49-
let move_kind = cmd
50-
.comment
51-
.as_ref()
52-
.map(|s| s.trim())
53-
.map(|s| {
54-
if s.starts_with("move to next layer ") {
55-
"move to next layer"
56-
} else {
57-
s
58-
}
59-
})
60-
.map(|s| self.kind_tracker.get_kind(s))
61-
.or(self.current_kind);
50+
let move_kind = self.kind_tracker.kind_from_comment(&cmd.comment);
6251

6352
if x.is_some() || y.is_some() || z.is_some() || e.is_some() {
6453
let mut m = self.toolhead_state.perform_move([*x, *y, *z, *e]);
@@ -90,6 +79,31 @@ impl Planner {
9079
return fr.unretract(kt, m, seq);
9180
}
9281
}
82+
('G', v @ 2 | v @ 3) => {
83+
let move_kind = self.kind_tracker.kind_from_comment(&cmd.comment);
84+
let m = &mut self.toolhead_state;
85+
let seq = &mut self.operations;
86+
return self.arc_state.generate_arc(
87+
m,
88+
seq,
89+
move_kind,
90+
params,
91+
match v {
92+
2 => crate::arcs::ArcDirection::Clockwise,
93+
3 => crate::arcs::ArcDirection::CounterClockwise,
94+
_ => unreachable!("v can only be 2 or 3"),
95+
},
96+
);
97+
}
98+
('G', 17) => {
99+
self.arc_state.set_plane(crate::arcs::Plane::XY);
100+
}
101+
('G', 18) => {
102+
self.arc_state.set_plane(crate::arcs::Plane::XZ);
103+
}
104+
('G', 19) => {
105+
self.arc_state.set_plane(crate::arcs::Plane::YZ);
106+
}
93107
('G', 92) => {
94108
if let Some(v) = params.get_number::<f64>('X') {
95109
self.toolhead_state.position.x = v;
@@ -151,7 +165,8 @@ impl Planner {
151165

152166
if let Some(comment) = comment.strip_prefix("TYPE:") {
153167
// IdeaMaker only gives us `TYPE:`s
154-
self.current_kind = Some(self.kind_tracker.get_kind(comment));
168+
let kind = self.kind_tracker.get_kind(comment);
169+
self.kind_tracker.set_current(Some(kind));
155170
self.operations.add_fill();
156171
} else if let Some(cmd) = comment.trim_start().strip_prefix("ESTIMATOR_ADD_TIME ") {
157172
if let Some((duration, kind)) = Self::parse_buffer_cmd(&mut self.kind_tracker, cmd)
@@ -321,7 +336,7 @@ pub struct PlanningMove {
321336
impl PlanningMove {
322337
/// Create a new `PlanningMove` that travels between the two points `start`
323338
/// and `end`.
324-
fn new(start: Vec4, end: Vec4, toolhead_state: &ToolheadState) -> PlanningMove {
339+
pub(crate) fn new(start: Vec4, end: Vec4, toolhead_state: &ToolheadState) -> PlanningMove {
325340
if start.xyz() == end.xyz() {
326341
Self::new_extrude_move(start, end, toolhead_state)
327342
} else {
@@ -724,9 +739,11 @@ pub struct PrinterLimits {
724739
#[serde(skip)]
725740
pub junction_deviation: f64,
726741
pub instant_corner_velocity: f64,
727-
pub move_checkers: Vec<MoveChecker>,
728742
#[serde(default, skip_serializing_if = "Option::is_none")]
729743
pub firmware_retraction: Option<FirmwareRetractionOptions>,
744+
#[serde(default, skip_serializing_if = "Option::is_none")]
745+
pub mm_per_arc_segment: Option<f64>,
746+
pub move_checkers: Vec<MoveChecker>,
730747
}
731748

732749
impl Default for PrinterLimits {
@@ -740,6 +757,7 @@ impl Default for PrinterLimits {
740757
instant_corner_velocity: 1.0,
741758
move_checkers: vec![],
742759
firmware_retraction: None,
760+
mm_per_arc_segment: None,
743761
}
744762
}
745763
}
@@ -847,7 +865,7 @@ impl ToolheadState {
847865
pm
848866
}
849867

850-
fn new_element(v: f64, old: f64, mode: PositionMode) -> f64 {
868+
pub(crate) fn new_element(v: f64, old: f64, mode: PositionMode) -> f64 {
851869
match mode {
852870
PositionMode::Relative => old + v,
853871
PositionMode::Absolute => v,

0 commit comments

Comments
 (0)