From f3ace9b7870608be2e3ed5aa24932740a5c62a38 Mon Sep 17 00:00:00 2001 From: ludwig-austermann Date: Fri, 18 Jun 2021 21:46:11 +0200 Subject: [PATCH 1/2] graphical editing and mouse features and more, ready for v0.3.0 --- .gitignore | 3 +- readme.md | 12 +++ src/main.rs | 272 ++++++++++++++++++++++++++++++++++++++++++++--- src/old_parse.rs | 105 ------------------ src/parse.rs | 27 +++++ test.gcode | 21 ++++ test_added.gcode | 26 +++++ 7 files changed, 347 insertions(+), 119 deletions(-) delete mode 100644 src/old_parse.rs create mode 100644 test.gcode create mode 100644 test_added.gcode diff --git a/.gitignore b/.gitignore index 9416765..baf8884 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ /target Cargo.lock simple.gcode -test_transformed.gcode -test.gcode \ No newline at end of file +test_transformed.gcode \ No newline at end of file diff --git a/readme.md b/readme.md index 05b914c..8eaaec2 100644 --- a/readme.md +++ b/readme.md @@ -29,8 +29,20 @@ The application is programmed in rust with the [nannou library](https://nannou.c It furthermore supports a subcommand `transform`, which allows you to transform a file by translation and dilation. +## Keyboard commands and editing features + In the graphical app, a few keyboard commands are enabled. To increase a value corresponding to a key, just press key and to decrease press shift + key. For bigger steps combine these combination with a further ctrl. +Furthermore, I introduced in Version 0.3.0 the ability to extend existing g-code files. Simply press +- 1 for `G1` mode +- 2 for `G2` mode +- 3 for `G3` mode +- 0 to exit the modes + +and choose the coordinate with a left mouse click. One also can now undo and redo these added commands with Z and Y and save these changes to a new file with S. P changes the penmode and H returns to to home. + +Last but not least, right-clicking prints the mouse coordinates to console. + ## Room to improve While the app can be used for many purposes, it is in a early development phase. Tests are missing and not everything is programmed the clever way. If you want to improve it feel welcome to contribute. diff --git a/src/main.rs b/src/main.rs index 5beccec..bed7661 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,14 @@ struct AppSettings { commands: Vec<(usize, parse::CommentlessGCodeExpr)>, shift_pressed: bool, control_pressed: bool, + mouse_pos: Option, + adding_commands: Vec, + deleted_command: Option, + current_pos: Vec, + saved: bool, + current_command: u8, // only G1, G2, G3 + temp_point: Option, + pen_mode: bool, } impl AppSettings { @@ -24,11 +32,18 @@ impl AppSettings { fn load_file(&mut self) { let file = std::fs::read_to_string(&self.filename).expect(&format!("Error opening `{}`.", &self.filename)); self.commands = parse::parse_gcode_file(&file).expect("problem parsing").iter() - .filter_map(|(l, c)| if let parse::GCodeExpr::Comment(_) = c { - None - } else { - Some((*l, c.to_commentless())) + .filter_map(|(l, c)| match c { + parse::GCodeExpr::Comment(_) => None, + parse::GCodeExpr::Pen(_) => { self.pen_mode = !self.pen_mode; Some((*l, c.to_commentless())) }, + _ => Some((*l, c.to_commentless())) }).collect(); + for (_, c) in self.commands.iter().rev() { + match c { + parse::CommentlessGCodeExpr::Move { X: x, Y: y } + | parse::CommentlessGCodeExpr::Arc{ CLKW: _, X: x, Y: y, I: _, J: _ } => { self.current_pos = vec![pt2(*x, *y)]; break }, + _ => {} + } + } } } @@ -43,7 +58,7 @@ fn main() { fn start_app(app: &App) -> AppSettings { let matches = clap_app!(myapp => - (version: "0.2.1") + (version: "0.3.0") (author: "Ludwig Austermann ") (about: "Draw simple gcode.") (@arg INPUT: +required "Sets the input g-code file to use") @@ -94,6 +109,8 @@ fn start_app(app: &App) -> AppSettings { ) .key_pressed(handle_keypress) .key_released(handle_keyrelease) + .mouse_moved(handle_mouse_move) + .mouse_pressed(handle_mouse_press) .view(view) .build() .unwrap(); @@ -116,6 +133,14 @@ fn start_app(app: &App) -> AppSettings { commands: Vec::new(), shift_pressed: false, control_pressed: false, + mouse_pos: None, + adding_commands: Vec::new(), + deleted_command: None, + current_pos: vec![Vector2::zero()], + saved: true, + current_command: 0, + temp_point: None, + pen_mode: false, // at first is the pen up }; settings.load_file(); settings @@ -131,16 +156,14 @@ fn view(app: &App, settings: &AppSettings, frame: Frame) { // set background to blue draw.background().color(WHITE); - - // info about state - draw.text(&format!("scale: {:.1}, debug level: {}, grid size: {:.1}, hot reloading: {}, accuracy treshold: {}", settings.scale, settings.debug_lvl, settings.grid_size, settings.hotreloading, settings.treshold)) - .x_y(win.left() + 140.0, win.top() - 5.0).w(win.w() - 40.0).color(BLACK).right_justify(); draw_grid(&draw, &draw_area, settings.grid_size, settings.scale, 0.3, false); draw_grid(&draw, &draw_area, 5.0 * settings.grid_size, settings.scale, 1.0, true); draw_gcode(&draw, &draw_area, &settings); + draw_overlay(&draw, &win, &settings); + // put everything on the frame draw.to_frame(app, &frame).unwrap(); } @@ -162,14 +185,45 @@ fn handle_keypress(_app: &App, settings: &mut AppSettings, key: Key) { settings.debug_lvl += 1; }, Key::G => if settings.shift_pressed && settings.grid_size > step { - settings.grid_size = (100.0 * settings.grid_size.round()) / 100.0 - step; + settings.grid_size = (100.0 * (settings.grid_size.round() - step)) / 100.0; } else { - settings.grid_size = (100.0 * settings.grid_size.round()) / 100.0 + step; + settings.grid_size = (100.0 * (settings.grid_size.round() + step)) / 100.0; } Key::Equals => { settings.scale += step }, Key::Minus => { if settings.scale > step { settings.scale -= step; } }, Key::LShift | Key::RShift => { settings.shift_pressed = true }, Key::LControl | Key::RControl => { settings.control_pressed = true }, + Key::Key1 => { settings.current_command = 1 }, + Key::Key2 => { settings.current_command = 2 }, + Key::Key3 => { settings.current_command = 3 }, + Key::H => { + settings.adding_commands.push(parse::CommentlessGCodeExpr::Home); + settings.current_pos.push(Vector2::zero()); + } + Key::Key0 => { settings.current_command = 0; settings.temp_point = None }, + Key::Z => { + settings.deleted_command = settings.adding_commands.pop(); + match settings.deleted_command { + Some(parse::CommentlessGCodeExpr::Pen(_)) => { settings.pen_mode = !settings.pen_mode; }, + Some(_) => { settings.current_pos.pop(); }, + None => {}, + } + } + Key::Y => { if let Some(c) = settings.deleted_command { + settings.adding_commands.push(c); + match c { + parse::CommentlessGCodeExpr::Home => settings.current_pos.push(Vector2::zero()), + parse::CommentlessGCodeExpr::Move { X: x, Y: y } + | parse::CommentlessGCodeExpr::Arc{ CLKW: _, X: x, Y: y, I: _, J: _ } => settings.current_pos.push(pt2(x, y)), + parse::CommentlessGCodeExpr::Pen(_) => { settings.pen_mode = !settings.pen_mode; } + } + settings.deleted_command = None; + }}, + Key::S => { parse::resave(&settings.filename, &settings.adding_commands); settings.saved = true; }, + Key::P => { + settings.pen_mode = !settings.pen_mode; + settings.adding_commands.push(parse::CommentlessGCodeExpr::Pen(settings.pen_mode)); + } _ => {} } } @@ -183,6 +237,63 @@ fn handle_keyrelease(_app: &App, settings: &mut AppSettings, key: Key) { } } +/// show the coordinates under the cursor +fn handle_mouse_move(app: &App, settings: &mut AppSettings, pos: Point2) { + let draw_area = app.main_window().rect().pad(20.0).pad_left(10.0).pad_top(10.0); + if draw_area.contains(pos) { + settings.mouse_pos = Some(pos); + } else { + settings.mouse_pos = None; + } +} + +fn handle_mouse_press(app: &App, settings: &mut AppSettings, button: MouseButton) { + match button { + MouseButton::Left => if let Some(pos) = settings.mouse_pos { + let (_, p) = get_grid_node(pos, &app.main_window().rect(), settings); + match settings.current_command { + 1 => { + settings.adding_commands.push(parse::CommentlessGCodeExpr::Move { X: p.x, Y: p.y }); + settings.current_pos.push(p); + settings.saved = false; + }, + 2 => { + if let Some(pos) = settings.temp_point { + settings.adding_commands.push(parse::CommentlessGCodeExpr::Arc { + CLKW: true, X: pos.x, Y: pos.y, + I: p.x - settings.current_pos.last().unwrap().x, J: p.y - settings.current_pos.last().unwrap().y + }); + settings.current_pos.push(pos); + settings.saved = false; + settings.temp_point = None; + } else { + settings.temp_point = Some(p); + } + }, + 3 => { + if let Some(pos) = settings.temp_point { + settings.adding_commands.push(parse::CommentlessGCodeExpr::Arc { + CLKW: false, X: pos.x, Y: pos.y, + I: p.x - settings.current_pos.last().unwrap().x, J: p.y - settings.current_pos.last().unwrap().y + }); + settings.current_pos.push(pos); + settings.saved = false; + settings.temp_point = None; + } else { + settings.temp_point = Some(p); + } + }, + _ => {}, + } + }, + MouseButton::Right => if let Some(pos) = settings.mouse_pos { + let (_, p) = get_grid_node(pos, &app.main_window().rect(), settings); + println!("X{} Y{}", p.x, p.y); + } + _ => {}, + } +} + /// draw the gcode on the given window. fn draw_gcode(draw: &Draw, win: &Rect, settings: &AppSettings) { let mut current = pt2(0.0, 0.0); @@ -234,7 +345,7 @@ fn draw_gcode(draw: &Draw, win: &Rect, settings: &AppSettings) { } let a = - C; let r2 = a.magnitude2(); - let steps = ((r2.sqrt() * 3.6) as usize).min(18); + let steps = ((r2.sqrt() * 7.2) as usize).min(36); let translation = (current + C) * settings.scale + origin; let anglestep = if (B - current).magnitude2() < settings.treshold { // make circle 2.0 * PI / steps as f32 @@ -257,6 +368,85 @@ fn draw_gcode(draw: &Draw, win: &Rect, settings: &AppSettings) { } }; + let points = (0..=steps).map(|n| a.rotate(n as f32 * anglestep) * settings.scale + translation); + if is_pen_down { + draw.polyline().weight(2.0).points(points).color(BLACK); + } else if settings.debug_lvl > 0 { + draw.polyline().points(points).rgb(0.7, 0.7, 0.7); + } + current = B; + }, + } + } + for (l, cmd) in settings.adding_commands.iter().enumerate() { + use parse::CommentlessGCodeExpr::*; + match cmd { + Home => { + if is_pen_down { + draw.line().points(current * settings.scale + origin, origin).color(BLACK).weight(2.0); + } else if settings.debug_lvl > 0 { + draw.line().points(current * settings.scale + origin, origin).rgb(0.7, 0.7, 0.7); + } + current = Vector2::zero(); + }, + Move {X: x, Y: y} => { + let p = pt2(*x, *y); + if settings.debug_lvl > 2 { + if is_pen_down { + draw.arrow().points(current * settings.scale + origin, p * settings.scale + origin).color(BLACK).weight(2.0); + } else { + draw.arrow().points(current * settings.scale + origin, p * settings.scale + origin).rgb(0.7, 0.7, 0.7); + } + } else { + if is_pen_down { + draw.line().points(current * settings.scale + origin, p * settings.scale + origin).color(BLACK).weight(2.0); + } else if settings.debug_lvl > 0 { + draw.line().points(current * settings.scale + origin, p * settings.scale + origin).rgb(0.7, 0.7, 0.7); + } + } + current = p; + }, + Pen(down) => { is_pen_down = *down; }, + Arc {CLKW: clkw, X: x, Y: y, I: i, J: j} => { + #[allow(non_snake_case)] + let B = pt2(*x, *y); + #[allow(non_snake_case)] + let C = pt2(*i, *j); + if settings.debug_lvl > 1 { + let a = current * settings.scale + origin; + let b = B * settings.scale + origin; + let c = (current + C) * settings.scale + origin; + draw.ellipse().xy(b).w_h(4.0, 4.0).color(BLACK); + draw.line().points(a, c).color(RED).weight(0.3); + draw.ellipse().xy(c).w_h(5.0, 5.0).color(RED); + draw.line().points(c, b).color(RED).weight(0.3); + draw.ellipse().xy(a).w_h(4.0, 4.0).color(BLACK); + } + let a = - C; + let r2 = a.magnitude2(); + let steps = ((r2.sqrt() * 3.6) as usize).min(18); + let translation = (current + C) * settings.scale + origin; + let anglestep = if (B - current).magnitude2() < settings.treshold { // make circle + 2.0 * PI / steps as f32 + } else { + let b = a + B - current; + if (r2 - b.magnitude2()).abs() > settings.treshold { + println!("Cannot draw arc in new line {}, (I,J) is no center.", l + 1) + } + let mut anglediff = a.angle_between(b); + if *clkw { + if (a.rotate(anglediff) - b).magnitude2() < settings.treshold { // rotate `a` in G3 direction + anglediff = 2.0 * PI - anglediff; + } + -anglediff / steps as f32 + } else { + if (a.rotate(-anglediff) - b).magnitude2() < settings.treshold { // rotate `a` in G2 direction + anglediff = 2.0 * PI - anglediff; + } + anglediff / steps as f32 + } + }; + let points = (0..=steps).map(|n| a.rotate(n as f32 * anglestep) * settings.scale + translation); if is_pen_down { draw.polyline().weight(2.0).points(points).color(BLACK); @@ -298,4 +488,62 @@ fn draw_grid(draw: &Draw, win: &Rect, step: f32, scale: f32, weight: f32, make_a draw.line() .points(pt2(x_0, y_0), pt2(x_0, win.top() + 5.0)); } +} + +/// draws the bar with informations as well as the mouse + +fn draw_overlay(draw: &Draw, win: &Rect, settings: &AppSettings) { + if let Some(pos) = settings.mouse_pos { + let (pos, p) = get_grid_node(pos, win, settings); + draw.text(&format!("mouse: ({:.2}, {:.2})", p.x, p.y)) + .x_y(win.left() + 85.0, win.top() - 5.0).w(150.0).color(BLACK).left_justify(); + // draw crosshair + if settings.pen_mode { + draw.line().points(pos - pt2(3.0, 0.0), pos + pt2(3.0, 0.0)); + draw.line().points(pos - pt2(0.0, 3.0), pos + pt2(0.0, 3.0)); + } else { + draw.line().points(pos - pt2(3.0, 0.0), pos + pt2(3.0, 0.0)).rgb(0.7, 0.7, 0.7); + draw.line().points(pos - pt2(0.0, 3.0), pos + pt2(0.0, 3.0)).rgb(0.7, 0.7, 0.7); + } + match settings.current_command { + 1 => { draw.text("G1-XY").xy(pos + pt2(15.0, -5.0)).w(30.0).color(RED).left_justify(); }, + 2 => if settings.temp_point.is_none() { + draw.text("G2-XY").xy(pos + pt2(15.0, -5.0)).w(30.0).color(RED).left_justify(); + } else { + draw.text("G2-IJ").xy(pos + pt2(15.0, -5.0)).w(30.0).color(RED).left_justify(); + }, + 3 => if settings.temp_point.is_none() { + draw.text("G3-XY").xy(pos + pt2(15.0, -5.0)).w(30.0).color(RED).left_justify(); + } else { + draw.text("G3-IJ").xy(pos + pt2(15.0, -5.0)).w(30.0).color(RED).left_justify(); + }, + _ => {} + } + } + // info about state + draw.text(&format!("scale: {:.1}", settings.scale)) + .x_y(win.left() + 220.0, win.top() - 5.0).w(80.0).color(BLACK).left_justify(); + draw.text(&format!("debug level: {}", settings.debug_lvl)) + .x_y(win.left() + 300.0, win.top() - 5.0).w(100.0).color(BLACK).left_justify(); + draw.text(&format!("grid size: {:.1}", settings.grid_size)) + .x_y(win.left() + 400.0, win.top() - 5.0).w(100.0).color(BLACK).left_justify(); + draw.text(&format!("hot reloading: {}", settings.hotreloading)) + .x_y(win.left() + 520.0, win.top() - 5.0).w(150.0).color(BLACK).left_justify(); + draw.text(&format!("accuracy treshold: {}", settings.treshold)) + .x_y(win.left() + 670.0, win.top() - 5.0).w(200.0).color(BLACK).left_justify(); +} + +/// calculates the nearest corresponding point on the grid. (nannou coords, plotter cords) +fn get_grid_node(pos: Point2, win: &Rect, settings: &AppSettings) -> (Point2, Point2) { + let draw_area = win.pad(20.0).pad_left(10.0).pad_top(10.0); + let p = (pos - draw_area.corner_at_index(3).unwrap()) / settings.scale; + let p = p.map(|x| { + let n = f32::trunc( x / settings.grid_size ); + if x % settings.grid_size <= 0.5 * settings.grid_size { + n * settings.grid_size + } else { + (n + 1.0) * settings.grid_size + } + }); + let pos = p * settings.scale + draw_area.corner_at_index(3).unwrap(); + (pos, p) } \ No newline at end of file diff --git a/src/old_parse.rs b/src/old_parse.rs deleted file mode 100644 index 6bd3a13..0000000 --- a/src/old_parse.rs +++ /dev/null @@ -1,105 +0,0 @@ -let mut parts = line.split(';').next().unwrap().split(' '); // remove comments -let first = parts.next().unwrap(); // if line is empty gets first == "" -match first { - "" => {}, - "G28" => { - if is_pen_down { - draw.line().points(current * settings.scale + origin, origin).color(BLACK).weight(2.0); - } else if settings.debug_lvl > 0 { - draw.line().points(current * settings.scale + origin, origin).rgb(0.7, 0.7, 0.7); - } - current = Vector2::zero(); - }, - "G1" => { - let mut x: Option = None; - let mut y: Option = None; - for part in parts { - if part.starts_with('X') { x = part[1..].parse().ok(); } - if part.starts_with('Y') { y = part[1..].parse().ok(); } - }; - match (x, y) { - (Some(x), Some(y)) => { - let p = pt2(x, y); - if is_pen_down { - draw.line().points(current * settings.scale + origin, p * settings.scale + origin).color(BLACK).weight(2.0); - } else if settings.debug_lvl > 0 { - draw.line().points(current * settings.scale + origin, p * settings.scale + origin).rgb(0.7, 0.7, 0.7); - } - current = p; - }, - _ => { println!("move/line statement in line {} is not valid.", k); } - } - }, - "M280" => { - if let Some("P0") = parts.next() { - if let Some(p) = parts.next() { - if p.starts_with("S") { - if let Some(n) = p[1..].parse::().ok() { - is_pen_down = n >= 40; - continue - } - } - } - } - println!("setting the pen must be of form `M280 P0 Sn`, where n >= 0"); - }, - mode @ "G2" | mode @ "G3" => { - let mut x: Option = None; - let mut y: Option = None; - let mut i: Option = None; - let mut j: Option = None; - for part in parts { - if part.starts_with('X') { x = part[1..].parse().ok(); } - if part.starts_with('Y') { y = part[1..].parse().ok(); } - if part.starts_with('I') { i = part[1..].parse().ok(); } - if part.starts_with('J') { j = part[1..].parse().ok(); } - }; - match (x, y, i, j) { - (Some(x), Some(y), Some(i), Some(j)) => { - if settings.debug_lvl > 1 { - let a = current * settings.scale + origin; - let b = pt2(x, y) * settings.scale + origin; - let c = (current + pt2(i, j)) * settings.scale + origin; - draw.line().points(a, c).color(RED).weight(0.3); - draw.ellipse().xy(c).w_h(4.0, 4.0).color(RED); - draw.line().points(c, b).color(RED).weight(0.3); - draw.ellipse().xy(a).w_h(4.0, 4.0); - } - let a = - pt2(i, j); - let r2 = a.magnitude2(); - let steps = ((r2.sqrt() * 3.6) as usize).min(18); - let translation = (current + pt2(i,j)) * settings.scale + origin; - let anglestep = if (pt2(x, y) - current).magnitude2() < settings.treshold { // make circle - 2.0 * PI / steps as f32 - } else { - let b = a + pt2(x, y) - current; - if (r2 - b.magnitude2()).abs() > settings.treshold { - println!("Cannot draw arc in line {}, (I,J) is no center.", k) - } - let mut anglediff = a.angle_between(b); - if mode == "G2" { - if (a.rotate(anglediff) - b).magnitude2() < settings.treshold { // rotate `a` in G3 direction - anglediff = 2.0 * PI - anglediff; - } - -anglediff / steps as f32 - } else { - if (a.rotate(-anglediff) - b).magnitude2() < settings.treshold { // rotate `a` in G2 direction - anglediff = 2.0 * PI - anglediff; - } - anglediff / steps as f32 - } - }; - - let points = (0..=steps).map(|n| a.rotate(n as f32 * anglestep) * settings.scale + translation); - if is_pen_down { - draw.polyline().weight(2.0).points(points).color(BLACK); - } else if settings.debug_lvl > 0 { - draw.polyline().points(points).rgb(0.7, 0.7, 0.7); - } - current = pt2(x, y); - }, - _ => { println!("move/line statement in line {} is not valid.", k); } - } - } - cmd => { println!("{} in line {} not implemented.", cmd, k); } -} \ No newline at end of file diff --git a/src/parse.rs b/src/parse.rs index 17428c8..c434ea6 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -14,6 +14,7 @@ pub enum GCodeExpr<'a> { /// without Comments, for faster and more memory efficient usecases #[allow(non_snake_case)] +#[derive(Copy, Clone)] pub enum CommentlessGCodeExpr { Home, Move { X: f32, Y: f32 }, @@ -47,6 +48,21 @@ impl GCodeExpr<'_> { } } +impl CommentlessGCodeExpr { + pub fn as_str(&self) -> String { + match self { + CommentlessGCodeExpr::Home => "G28".to_string(), + CommentlessGCodeExpr::Move{X: x, Y: y} => format!("G1 X{} Y{}", x, y), + CommentlessGCodeExpr::Arc{CLKW: clkw, X: x, Y: y, I: i, J: j} => if *clkw { + format!("G2 X{} Y{} I{} J{}", x, y, i, j) + } else { + format!("G3 X{} Y{} I{} J{}", x, y, i, j) + }, + CommentlessGCodeExpr::Pen(down) => if *down { "M280 P0 S50".to_string() } else { "M280 P0 S0".to_string() }, + } + } +} + #[derive(Parser)] #[grammar = "gcode.pest"] struct GCodeParser; @@ -138,4 +154,15 @@ pub fn save(filename: &str, commands: Vec<(usize, GCodeExpr)>) { }) //collect::>().join("\n") ).expect("Unable to save the gcode."); +} + +/// saves new commands on tope +pub fn resave(filename: &str, commands: &Vec) { + let oldfile = std::fs::read_to_string(filename).expect("unable to open the file."); + std::fs::write( + format!("{}_added.gcode", filename.strip_suffix(".gcode").expect("Expected gcode file")), + format!("{}\n; added by gcodeplot\n{}", oldfile, + commands.iter().map(|cmd| cmd.as_str()).collect::>().join("\n") + ) + ).expect("Unable to save the gcode."); } \ No newline at end of file diff --git a/test.gcode b/test.gcode new file mode 100644 index 0000000..c40fd18 --- /dev/null +++ b/test.gcode @@ -0,0 +1,21 @@ +G28 +G1 X10 Y10 +G1 X20 Y28 +M280 P0 S50 +G3 X18 Y30 I-2 J0 +G1 X14 Y30 +G3 X10 Y26 I0 J-4 +G1 X10 Y21 +G3 X11 Y20 I1 J0 +G1 X15 Y20 +G3 X17 Y22 I0 J2 +G1 X17 Y18 +G3 X15 Y20 I-2 J0 +M280 P0 S0 +G1 X11 Y20 +M280 P0 S50 +G3 X10 Y19 I0 J-1 +G1 X10 Y14 +G3 X14 Y10 I4 J0 +G1 X18 Y10 +G3 X20 Y12 I0 J2 \ No newline at end of file diff --git a/test_added.gcode b/test_added.gcode new file mode 100644 index 0000000..138d401 --- /dev/null +++ b/test_added.gcode @@ -0,0 +1,26 @@ +G28 +G1 X10 Y10 +G1 X20 Y28 +M280 P0 S50 +G3 X18 Y30 I-2 J0 +G1 X14 Y30 +G3 X10 Y26 I0 J-4 +G1 X10 Y21 +G3 X11 Y20 I1 J0 +G1 X15 Y20 +G3 X17 Y22 I0 J2 +G1 X17 Y18 +G3 X15 Y20 I-2 J0 +M280 P0 S0 +G1 X11 Y20 +M280 P0 S50 +G3 X10 Y19 I0 J-1 +G1 X10 Y14 +G3 X14 Y10 I4 J0 +G1 X18 Y10 +G3 X20 Y12 I0 J2 +; added by gcodeplot +G1 X25 Y15 +G1 X30 Y10 +G2 X35 Y15 I5 J0 +G2 X30 Y20 I0 J5 \ No newline at end of file From 4fed20626043d7e378cd9026c16459ca8d27a7c4 Mon Sep 17 00:00:00 2001 From: ludwig-austermann Date: Fri, 18 Jun 2021 21:48:29 +0200 Subject: [PATCH 2/2] forgot checkmark --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 8eaaec2..c68f499 100644 --- a/readme.md +++ b/readme.md @@ -51,5 +51,5 @@ TODO: - [ ] better loop management for hot reloading - [ ] more file maniplulation features - [ ] move in grid support -- [ ] draw mode? +- [X] draw mode? - [ ] more debug options, e.g. show coordinates \ No newline at end of file