parent
08762f432c
commit
7c6c790378
@ -1,17 +1,22 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "svg2gcode"
|
name = "svg2gcode"
|
||||||
version = "0.0.1"
|
version = "0.0.2"
|
||||||
authors = ["Sameer Puri <purisame@spuri.io>"]
|
authors = ["Sameer Puri <purisame@spuri.io>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "Convert paths in SVG files to GCode for a pen plotter or laser engraver"
|
description = "Convert paths in SVG files to GCode for a pen plotter, laser engraver, or other machine."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
g-code = "0.0.1"
|
||||||
lyon_geom = "0"
|
lyon_geom = "0"
|
||||||
clap = "2"
|
euclid = "0.22"
|
||||||
|
structopt = "0.3"
|
||||||
log = "0"
|
log = "0"
|
||||||
env_logger = "0"
|
env_logger = "0"
|
||||||
uom = "0.31.0"
|
uom = "0.31.0"
|
||||||
paste = "0"
|
|
||||||
roxmltree = "0"
|
roxmltree = "0"
|
||||||
svgtypes = "0"
|
svgtypes = "0"
|
||||||
petgraph = "0"
|
codespan-reporting = "0.11"
|
||||||
|
paste = "1"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
pretty_assertions = "0.6"
|
||||||
|
@ -1,107 +0,0 @@
|
|||||||
use core::convert::TryFrom;
|
|
||||||
use std::io::{self, Write};
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
mod spec;
|
|
||||||
pub use spec::*;
|
|
||||||
|
|
||||||
/// Collapses GCode words into higher-level commands
|
|
||||||
pub struct CommandVecIntoIterator {
|
|
||||||
vec: Vec<Word>,
|
|
||||||
index: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for CommandVecIntoIterator {
|
|
||||||
type Item = Command;
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
if self.vec.len() <= self.index {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let mut i = self.index + 1;
|
|
||||||
while i < self.vec.len() {
|
|
||||||
if CommandWord::is_command(&self.vec[i]) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
let command = Command::try_from(&self.vec[self.index..i]).ok();
|
|
||||||
self.index = i;
|
|
||||||
command
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Vec<Word>> for CommandVecIntoIterator {
|
|
||||||
fn from(vec: Vec<Word>) -> Self {
|
|
||||||
Self { vec, index: 0 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_gcode(gcode: &str) -> Vec<Word> {
|
|
||||||
let mut vec = vec![];
|
|
||||||
let mut in_string = false;
|
|
||||||
let mut letter: Option<char> = None;
|
|
||||||
let mut value_range = 0..0;
|
|
||||||
gcode.char_indices().for_each(|(i, c)| {
|
|
||||||
if (c.is_alphabetic() || c.is_ascii_whitespace()) && !in_string {
|
|
||||||
if let Some(l) = letter {
|
|
||||||
vec.push(Word {
|
|
||||||
letter: l,
|
|
||||||
value: parse_value(&gcode[value_range.clone()]),
|
|
||||||
});
|
|
||||||
letter = None;
|
|
||||||
}
|
|
||||||
if c.is_alphabetic() {
|
|
||||||
letter = Some(c);
|
|
||||||
}
|
|
||||||
value_range = (i + 1)..(i + 1);
|
|
||||||
} else if in_string {
|
|
||||||
value_range = value_range.start..(i + 1);
|
|
||||||
} else {
|
|
||||||
if c == '"' {
|
|
||||||
in_string = !in_string;
|
|
||||||
}
|
|
||||||
value_range = value_range.start..(i + 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if let Some(l) = letter {
|
|
||||||
vec.push(Word {
|
|
||||||
letter: l,
|
|
||||||
value: parse_value(&gcode[value_range]),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
vec
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_value(word: &str) -> Value {
|
|
||||||
if word.starts_with('"') && word.ends_with('"') {
|
|
||||||
Value::String(Box::new(word.to_string()))
|
|
||||||
} else {
|
|
||||||
let index_of_dot = word.find('.');
|
|
||||||
Value::Fractional(
|
|
||||||
word[..index_of_dot.unwrap_or_else(|| word.len())]
|
|
||||||
.parse::<u32>()
|
|
||||||
.unwrap(),
|
|
||||||
index_of_dot.map(|j| word[j + 1..].parse::<u32>().unwrap()),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes a GCode program or sequence to a Writer
|
|
||||||
/// Each command is placed on a separate line
|
|
||||||
pub fn program2gcode<W: Write>(program: Vec<Command>, mut w: W) -> io::Result<()> {
|
|
||||||
for command in program.into_iter() {
|
|
||||||
let words: Vec<Word> = command.into();
|
|
||||||
let mut it = words.iter();
|
|
||||||
if let Some(command_word) = it.next() {
|
|
||||||
write!(w, "{}{}", command_word.letter, command_word.value)?;
|
|
||||||
for (i, word) in it.enumerate() {
|
|
||||||
write!(w, " {}{}", word.letter, word.value)?;
|
|
||||||
if i != words.len() - 2 {
|
|
||||||
write!(w, " ")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeln!(w, "")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@ -1,300 +0,0 @@
|
|||||||
use std::convert::TryFrom;
|
|
||||||
|
|
||||||
/// Fundamental unit of GCode: a value preceded by a descriptive letter.
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
|
||||||
pub struct Word {
|
|
||||||
pub letter: char,
|
|
||||||
pub value: Value,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// All the possible variations of a word's value.
|
|
||||||
/// Fractional is needed to support commands like G91.1 which would be changed by float arithmetic.
|
|
||||||
/// Some flavors of GCode also allow for strings.
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
|
||||||
pub enum Value {
|
|
||||||
Fractional(u32, Option<u32>),
|
|
||||||
Float(f64),
|
|
||||||
String(Box<String>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<f64> for &Value {
|
|
||||||
fn into(self) -> f64 {
|
|
||||||
match self {
|
|
||||||
Value::Float(f) => *f,
|
|
||||||
_ => panic!("Unwrapping a non-float"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::fmt::Display for Value {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Fractional(number, Some(fraction)) => write!(f, "{}.{}", number, fraction),
|
|
||||||
Self::Fractional(number, None) => write!(f, "{}", number),
|
|
||||||
Self::Float(float) => write!(f, "{}", float),
|
|
||||||
Self::String(string) => write!(f, "{}", string),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A macro for quickly instantiating a float-valued command
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! command {
|
|
||||||
($commandWord: expr, {$($argument: ident : $value: expr,)*}) => {
|
|
||||||
paste::expr! (Command::new($commandWord, vec![$(Word {
|
|
||||||
letter: stringify!([<$argument:upper>]).chars().next().unwrap(),
|
|
||||||
value: Value::Float($value),
|
|
||||||
},)*]))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! commands {
|
|
||||||
($($(#[$outer:meta])* $commandName: ident {$letter: expr, $number: expr, $fraction: path, {$($(#[$inner:meta])* $argument: ident), *} },)*) => {
|
|
||||||
|
|
||||||
/// Commands are the operational unit of GCode
|
|
||||||
/// They consist of an identifying word followed by arguments
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
|
||||||
pub struct Command {
|
|
||||||
command_word: CommandWord,
|
|
||||||
arguments: Vec<Word>
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Command {
|
|
||||||
pub fn new(command_word: CommandWord, mut arguments: Vec<Word>) -> Self {
|
|
||||||
Self {
|
|
||||||
command_word: command_word.clone(),
|
|
||||||
arguments: arguments.drain(..).filter(|w| {
|
|
||||||
match command_word {
|
|
||||||
$(CommandWord::$commandName => match w.letter.to_lowercase() {
|
|
||||||
$($argument => true,)*
|
|
||||||
_ => false
|
|
||||||
},)*
|
|
||||||
_ => false
|
|
||||||
}
|
|
||||||
}).collect()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push(&mut self, argument: Word) {
|
|
||||||
match self.command_word {
|
|
||||||
$(CommandWord::$commandName => match argument.letter.to_lowercase() {
|
|
||||||
$($argument => {
|
|
||||||
self.arguments.push(argument);
|
|
||||||
})*
|
|
||||||
_ => {}
|
|
||||||
},)*
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn word(&'_ self) -> &'_ CommandWord {
|
|
||||||
&self.command_word
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&'_ self, letter: char) -> Option<&'_ Word> {
|
|
||||||
let letter = letter.to_ascii_uppercase();
|
|
||||||
self.arguments.iter().find(|arg| arg.letter == letter)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set(&mut self, letter: char, value: Value) {
|
|
||||||
let letter = letter.to_ascii_uppercase();
|
|
||||||
for i in 0..self.arguments.len() {
|
|
||||||
if self.arguments[i].letter == letter {
|
|
||||||
self.arguments[i].value = value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<Vec<Word>> for Command {
|
|
||||||
fn into(self) -> Vec<Word> {
|
|
||||||
let mut args = self.arguments;
|
|
||||||
args.insert(0, self.command_word.into());
|
|
||||||
args
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&[Word]> for Command {
|
|
||||||
type Error = ();
|
|
||||||
fn try_from(words: &[Word]) -> Result<Self, ()> {
|
|
||||||
if words.len() == 0 {
|
|
||||||
return Err(());
|
|
||||||
}
|
|
||||||
let command_word = CommandWord::try_from(&words[0])?;
|
|
||||||
let mut arguments = Vec::with_capacity(words.len() - 1);
|
|
||||||
for i in 1..words.len() {
|
|
||||||
match command_word {
|
|
||||||
$(CommandWord::$commandName => match words[i].letter.to_lowercase() {
|
|
||||||
$($argument => {
|
|
||||||
arguments.push(words[i].clone());
|
|
||||||
})*
|
|
||||||
_ => {}
|
|
||||||
},)*
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Self {
|
|
||||||
command_word,
|
|
||||||
arguments
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
|
||||||
pub enum CommandWord {
|
|
||||||
$(
|
|
||||||
$(#[$outer])*
|
|
||||||
$commandName,
|
|
||||||
)*
|
|
||||||
/// A comment is a special command: it is a semicolon followed by text until the end of the line
|
|
||||||
Comment(Box<String>),
|
|
||||||
/// Letter N followed by an integer (with no sign) between 0 and 99999 written with no more than five digits
|
|
||||||
LineNumber(u16),
|
|
||||||
/// Byte-sized checksums are used by some GCode generators at the end of each line
|
|
||||||
Checksum(u8),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CommandWord {
|
|
||||||
pub fn is_command(word: &Word) -> bool {
|
|
||||||
let (number, fraction) = match &word.value {
|
|
||||||
Value::Fractional(number, fraction) => (number, fraction),
|
|
||||||
_other => return false
|
|
||||||
};
|
|
||||||
match (word.letter, number, fraction) {
|
|
||||||
$(($letter, $number, $fraction) => true,)*
|
|
||||||
('*', _checksum, None) => true,
|
|
||||||
('N', _line_number, None) => true,
|
|
||||||
(_, _, _) => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<&Word> for CommandWord {
|
|
||||||
type Error = ();
|
|
||||||
fn try_from(word: &Word) -> Result<Self, ()> {
|
|
||||||
let (number, fraction) = match &word.value {
|
|
||||||
Value::Fractional(number, fraction) => (number, fraction),
|
|
||||||
_other => return Err(())
|
|
||||||
};
|
|
||||||
match (word.letter, number, fraction) {
|
|
||||||
$(($letter, $number, $fraction) => Ok(Self::$commandName),)*
|
|
||||||
('*', checksum, None) => Ok(Self::Checksum(*checksum as u8)),
|
|
||||||
('N', line_number, None) => Ok(Self::LineNumber(*line_number as u16)),
|
|
||||||
(_, _, _) => Err(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<Word> for CommandWord {
|
|
||||||
fn into(self) -> Word {
|
|
||||||
match self {
|
|
||||||
$(
|
|
||||||
Self::$commandName {} => Word {
|
|
||||||
letter: $letter,
|
|
||||||
// TODO: fix fraction
|
|
||||||
value: Value::Fractional($number, $fraction)
|
|
||||||
},
|
|
||||||
)*
|
|
||||||
Self::Checksum(value) => Word {
|
|
||||||
letter: '*',
|
|
||||||
value: Value::Fractional(value as u32, None)
|
|
||||||
},
|
|
||||||
Self::LineNumber(value) => Word {
|
|
||||||
letter: 'N',
|
|
||||||
value: Value::Fractional(value as u32, None)
|
|
||||||
},
|
|
||||||
Self::Comment(string) => Word {
|
|
||||||
letter: ';',
|
|
||||||
value: Value::String(string)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
commands!(
|
|
||||||
/// Moves the head at the fastest possible speed to the desired speed
|
|
||||||
/// Never enter a cut with rapid positioning
|
|
||||||
/// Some older machines may "dog leg" rapid positioning, moving one axis at a time
|
|
||||||
RapidPositioning {
|
|
||||||
'G', 0, None, {
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
z,
|
|
||||||
e,
|
|
||||||
f,
|
|
||||||
h,
|
|
||||||
r,
|
|
||||||
s,
|
|
||||||
a,
|
|
||||||
b,
|
|
||||||
c
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/// Typically used for "cutting" motion
|
|
||||||
LinearInterpolation {
|
|
||||||
'G', 1, None, {
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
z,
|
|
||||||
e,
|
|
||||||
f,
|
|
||||||
h,
|
|
||||||
r,
|
|
||||||
s,
|
|
||||||
a,
|
|
||||||
b,
|
|
||||||
c
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/// This will keep the axes unmoving for the period of time in seconds specified by the P number
|
|
||||||
Dwell {
|
|
||||||
'G', 4, None, {
|
|
||||||
/// Time in seconds
|
|
||||||
p
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/// Use inches for length units
|
|
||||||
UnitsInches {
|
|
||||||
'G', 20, None, {}
|
|
||||||
},
|
|
||||||
/// Use millimeters for length units
|
|
||||||
UnitsMillimeters {
|
|
||||||
'G', 21, None, {}
|
|
||||||
},
|
|
||||||
/// In absolute distance mode, axis numbers usually represent positions in terms of the currently active coordinate system.
|
|
||||||
AbsoluteDistanceMode {
|
|
||||||
'G', 90, None, {}
|
|
||||||
},
|
|
||||||
/// In relative distance mode, axis numbers usually represent increments from the current values of the numbers
|
|
||||||
RelativeDistanceMode {
|
|
||||||
'G', 91, None, {}
|
|
||||||
},
|
|
||||||
FeedRateUnitsPerMinute {
|
|
||||||
'G', 94, None, {}
|
|
||||||
},
|
|
||||||
/// Start spinning the spindle clockwise with speed `p`
|
|
||||||
StartSpindleClockwise {
|
|
||||||
'M', 3, None, {
|
|
||||||
/// Speed
|
|
||||||
p
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/// Start spinning the spindle counterclockwise with speed `p`
|
|
||||||
StartSpindleCounterclockwise {
|
|
||||||
'M', 4, None, {
|
|
||||||
/// Speed
|
|
||||||
p
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/// Stop spinning the spindle
|
|
||||||
StopSpindle {
|
|
||||||
'M', 5, None, {}
|
|
||||||
},
|
|
||||||
/// Signals the end of a program
|
|
||||||
ProgramEnd {
|
|
||||||
'M', 20, None, {}
|
|
||||||
},
|
|
||||||
);
|
|
@ -1,65 +1,86 @@
|
|||||||
use crate::gcode::CommandWord::*;
|
use euclid::default::Box2D;
|
||||||
use crate::gcode::*;
|
use g_code::emit::{
|
||||||
|
Field, Token, Value, ABSOLUTE_DISTANCE_MODE_FIELD, RELATIVE_DISTANCE_MODE_FIELD,
|
||||||
|
};
|
||||||
use lyon_geom::{point, vector, Point};
|
use lyon_geom::{point, vector, Point};
|
||||||
|
|
||||||
type F64Point = Point<f64>;
|
type F64Point = Point<f64>;
|
||||||
|
|
||||||
/// Moves all the commands so that they are beyond a specified position
|
/// Moves all the commands so that they are beyond a specified position
|
||||||
pub fn set_origin(commands: &mut [Command], origin: F64Point) {
|
pub fn set_origin(tokens: &mut [Token], origin: F64Point) {
|
||||||
let offset = -get_bounding_box(commands).0.to_vector() + origin.to_vector();
|
let offset = -get_bounding_box(tokens.iter()).min.to_vector() + origin.to_vector();
|
||||||
|
|
||||||
let mut is_relative = false;
|
let mut is_relative = false;
|
||||||
let mut current_position = point(0f64, 0f64);
|
let mut current_position = point(0f64, 0f64);
|
||||||
|
let x = "X".to_string();
|
||||||
for command in commands {
|
let y = "Y".to_string();
|
||||||
match command.word() {
|
let abs_tok = Token::Field(ABSOLUTE_DISTANCE_MODE_FIELD.clone());
|
||||||
RapidPositioning | LinearInterpolation => {
|
let rel_tok = Token::Field(RELATIVE_DISTANCE_MODE_FIELD.clone());
|
||||||
let x: f64 = (&command.get('X').unwrap().value).into();
|
for token in tokens {
|
||||||
let y: f64 = (&command.get('Y').unwrap().value).into();
|
match token {
|
||||||
|
abs if *abs == abs_tok => is_relative = false,
|
||||||
|
rel if *rel == rel_tok => is_relative = true,
|
||||||
|
Token::Field(Field { letters, value }) if *letters == x => {
|
||||||
|
if let Some(float) = value.as_f64() {
|
||||||
if is_relative {
|
if is_relative {
|
||||||
current_position += vector(x, y);
|
current_position += vector(float, 0.)
|
||||||
} else {
|
} else {
|
||||||
current_position = point(x, y);
|
current_position = point(float, 0.);
|
||||||
command.set('X', Value::Float((current_position + offset).x));
|
}
|
||||||
command.set('Y', Value::Float((current_position + offset).y));
|
*value = Value::Float(current_position.x + offset.x)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Token::Field(Field { letters, value }) if *letters == y => {
|
||||||
|
if let Some(float) = value.as_f64() {
|
||||||
|
if is_relative {
|
||||||
|
current_position += vector(0., float)
|
||||||
|
} else {
|
||||||
|
current_position = point(0., float);
|
||||||
}
|
}
|
||||||
AbsoluteDistanceMode => {
|
*value = Value::Float(current_position.y + offset.y)
|
||||||
is_relative = false;
|
|
||||||
}
|
}
|
||||||
RelativeDistanceMode => {
|
|
||||||
is_relative = true;
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_bounding_box(commands: &[Command]) -> (F64Point, F64Point) {
|
fn get_bounding_box<'a, I: Iterator<Item = &'a Token>>(tokens: I) -> Box2D<f64> {
|
||||||
let (mut minimum, mut maximum) = (point(0f64, 0f64), point(0f64, 0f64));
|
let (mut minimum, mut maximum) = (point(0f64, 0f64), point(0f64, 0f64));
|
||||||
let mut is_relative = false;
|
let mut is_relative = false;
|
||||||
let mut current_position = point(0f64, 0f64);
|
let mut current_position = point(0f64, 0f64);
|
||||||
for command in commands {
|
let x = "X".to_string();
|
||||||
match command.word() {
|
let y = "Y".to_string();
|
||||||
AbsoluteDistanceMode => {
|
let abs_tok = Token::Field(ABSOLUTE_DISTANCE_MODE_FIELD.clone());
|
||||||
is_relative = false;
|
let rel_tok = Token::Field(RELATIVE_DISTANCE_MODE_FIELD.clone());
|
||||||
|
for token in tokens {
|
||||||
|
match token {
|
||||||
|
abs if *abs == abs_tok => is_relative = false,
|
||||||
|
rel if *rel == rel_tok => is_relative = true,
|
||||||
|
Token::Field(Field { letters, value }) if *letters == x => {
|
||||||
|
if let Some(value) = value.as_f64() {
|
||||||
|
if is_relative {
|
||||||
|
current_position += vector(value, 0.)
|
||||||
|
} else {
|
||||||
|
current_position = point(value, 0.);
|
||||||
|
}
|
||||||
|
minimum = minimum.min(current_position);
|
||||||
|
maximum = maximum.max(current_position);
|
||||||
}
|
}
|
||||||
RelativeDistanceMode => {
|
|
||||||
is_relative = true;
|
|
||||||
}
|
}
|
||||||
LinearInterpolation | RapidPositioning => {
|
Token::Field(Field { letters, value }) if *letters == y => {
|
||||||
let x: f64 = (&command.get('X').unwrap().value).into();
|
if let Some(value) = value.as_f64() {
|
||||||
let y: f64 = (&command.get('Y').unwrap().value).into();
|
|
||||||
if is_relative {
|
if is_relative {
|
||||||
current_position += vector(x, y)
|
current_position += vector(0., value)
|
||||||
} else {
|
} else {
|
||||||
current_position = point(x, y);
|
current_position = point(0., value);
|
||||||
}
|
}
|
||||||
minimum = minimum.min(current_position);
|
minimum = minimum.min(current_position);
|
||||||
maximum = maximum.max(current_position);
|
maximum = maximum.max(current_position);
|
||||||
}
|
}
|
||||||
_ => (),
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(minimum, maximum)
|
Box2D::new(minimum, maximum)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in new issue