///|
pub(all) enum GCodeErrorCode {
EmptyLine
InvalidWord
InvalidNumber
UnknownCommand
InvalidArc
} derive(Eq, Debug)
///|
pub struct SourceLocation {
line_value : Int
column_value : Int
} derive(Eq, Debug)
///|
pub fn SourceLocation::new(line : Int, column : Int) -> SourceLocation {
{ line_value: line, column_value: column }
}
///|
pub fn SourceLocation::line(self : SourceLocation) -> Int {
self.line_value
}
///|
pub fn SourceLocation::column(self : SourceLocation) -> Int {
self.column_value
}
///|
pub struct GCodeError {
code_value : GCodeErrorCode
message_value : String
location_value : SourceLocation
} derive(Eq, Debug)
///|
pub fn GCodeError::code(self : GCodeError) -> GCodeErrorCode {
self.code_value
}
///|
pub fn GCodeError::message(self : GCodeError) -> String {
self.message_value
}
///|
pub fn GCodeError::location(self : GCodeError) -> SourceLocation {
self.location_value
}
///|
pub fn GCodeError::format(self : GCodeError) -> String {
"line " +
self.location().line().to_string() +
":" +
self.location().column().to_string() +
": " +
self.message()
}
///|
pub(all) enum MotionKind {
Rapid
Linear
ClockwiseArc
CounterclockwiseArc
} derive(Eq, Debug)
///|
pub struct Position {
x_value : Double
y_value : Double
z_value : Double
e_value : Double
} derive(Eq, Debug)
///|
pub fn Position::new(
x : Double,
y : Double,
z : Double,
e : Double,
) -> Position {
{ x_value: x, y_value: y, z_value: z, e_value: e }
}
///|
pub fn Position::x(self : Position) -> Double {
self.x_value
}
///|
pub fn Position::y(self : Position) -> Double {
self.y_value
}
///|
pub fn Position::z(self : Position) -> Double {
self.z_value
}
///|
pub fn Position::e(self : Position) -> Double {
self.e_value
}
///|
pub struct Motion {
kind_value : MotionKind
start_value : Position
end_value : Position
i_value : Double
j_value : Double
feed_value : Double?
source_line_value : Int
} derive(Eq, Debug)
///|
pub fn Motion::kind(self : Motion) -> MotionKind {
self.kind_value
}
///|
pub fn Motion::start(self : Motion) -> Position {
self.start_value
}
///|
pub fn Motion::end(self : Motion) -> Position {
self.end_value
}
///|
pub fn Motion::feed(self : Motion) -> Double? {
self.feed_value
}
///|
pub fn Motion::i(self : Motion) -> Double {
self.i_value
}
///|
pub fn Motion::j(self : Motion) -> Double {
self.j_value
}
///|
pub fn Motion::source_line(self : Motion) -> Int {
self.source_line_value
}
///|
pub struct GCodeProgram {
motions_value : Array[Motion]
ignored_lines_value : Int
} derive(Eq, Debug)
///|
pub fn GCodeProgram::motions(self : GCodeProgram) -> Array[Motion] {
self.motions_value.copy()
}
///|
pub fn GCodeProgram::motion_count(self : GCodeProgram) -> Int {
self.motions_value.length()
}
///|
pub fn GCodeProgram::ignored_lines(self : GCodeProgram) -> Int {
self.ignored_lines_value
}
///|
fn make_error(
code : GCodeErrorCode,
message : String,
line : Int,
) -> GCodeError {
{
code_value: code,
message_value: message,
location_value: SourceLocation::new(line, 1),
}
}
///|
fn parse_number(text : String) -> Double? {
if text == "" {
return None
}
let mut sign = 1.0
let mut value = 0.0
let mut fraction = 0.0
let mut divisor = 1.0
let mut decimal = false
let mut digits = 0
for index, character in text {
let code = character.to_int()
if index == 0 && code == 45 {
sign = -1.0
} else if index == 0 && code == 43 {
()
} else if code == 46 && !decimal {
decimal = true
} else if code >= 48 && code <= 57 {
let digit = (code - 48).to_double()
digits += 1
if decimal {
divisor *= 10.0
fraction += digit / divisor
} else {
value = value * 10.0 + digit
}
} else {
return None
}
}
if digits == 0 {
None
} else {
Some(sign * (value + fraction))
}
}
///|
fn word_number(word : String) -> Double? {
parse_number(word[1:].to_owned())
}
///|
fn coordinate(current : Double, value : Double?) -> Double {
match value {
Some(v) => v
None => current
}
}
///|
fn modal_coordinate(
current : Double,
value : Double,
absolute : Bool,
unit_scale : Double,
) -> Double {
if absolute {
value * unit_scale
} else {
current + value * unit_scale
}
}
///|
pub fn parse_gcode(
text : String,
max_lines? : Int = 100000,
) -> Result[GCodeProgram, GCodeError] {
let motions : Array[Motion] = []
let mut position = Position::new(0.0, 0.0, 0.0, 0.0)
let mut feed : Double? = None
let mut modal_motion : MotionKind? = None
let mut absolute = true
let mut unit_scale = 1.0
let mut ignored = 0
let mut line_number = 0
for raw in text.split("\n") {
line_number += 1
if line_number > max_lines {
return Err(make_error(InvalidWord, "line limit exceeded", line_number))
}
let line = clean_gcode_line(raw.to_owned())
if line == "" {
ignored += 1
continue
}
if line[0].to_int() == 59 {
ignored += 1
continue
}
let words = tokenize_gcode_line(line)
if words.length() > 256 {
return Err(make_error(InvalidWord, "word limit exceeded", line_number))
}
let mut kind : MotionKind? = None
let mut x : Double? = None
let mut y : Double? = None
let mut z : Double? = None
let mut e : Double? = None
let mut i = 0.0
let mut j = 0.0
let mut has_axis = false
let mut has_arc_offset = false
for word in words {
if word.length() < 2 {
return Err(make_error(InvalidWord, "word is too short", line_number))
}
let number = match word_number(word) {
Some(v) => v
None =>
return Err(
make_error(InvalidNumber, "invalid numeric word", line_number),
)
}
let raw_letter = word[0].to_int()
let letter = if raw_letter >= 97 && raw_letter <= 122 {
raw_letter - 32
} else {
raw_letter
}
if letter == 71 {
if number == 90.0 {
absolute = true
} else if number == 91.0 {
absolute = false
} else if number == 20.0 {
unit_scale = 25.4
} else if number == 21.0 {
unit_scale = 1.0
} else if number == 0.0 {
kind = Some(Rapid)
modal_motion = kind
} else if number == 1.0 {
kind = Some(Linear)
modal_motion = kind
} else if number == 2.0 {
kind = Some(ClockwiseArc)
modal_motion = kind
} else if number == 3.0 {
kind = Some(CounterclockwiseArc)
modal_motion = kind
} else {
return Err(
make_error(UnknownCommand, "unsupported G motion", line_number),
)
}
} else if letter == 88 {
x = Some(modal_coordinate(position.x(), number, absolute, unit_scale))
has_axis = true
} else if letter == 89 {
y = Some(modal_coordinate(position.y(), number, absolute, unit_scale))
has_axis = true
} else if letter == 90 {
z = Some(modal_coordinate(position.z(), number, absolute, unit_scale))
has_axis = true
} else if letter == 69 {
e = Some(modal_coordinate(position.e(), number, absolute, unit_scale))
has_axis = true
} else if letter == 70 {
feed = Some(number * unit_scale)
} else if letter == 73 {
i = number * unit_scale
has_arc_offset = true
} else if letter == 74 {
j = number * unit_scale
has_arc_offset = true
} else if letter == 77 || letter == 78 {
ignored += 1
} else {
return Err(
make_error(InvalidWord, "unsupported word letter", line_number),
)
}
}
let effective_kind = match kind {
Some(value) => Some(value)
None => modal_motion
}
match effective_kind {
None => ignored += 1
Some(motion_kind) => {
if !has_axis && !has_arc_offset {
ignored += 1
continue
}
if motions.length() >= 1000000 {
return Err(
make_error(InvalidWord, "motion limit exceeded", line_number),
)
}
let end = Position::new(
coordinate(position.x(), x),
coordinate(position.y(), y),
coordinate(position.z(), z),
coordinate(position.e(), e),
)
if (motion_kind == ClockwiseArc || motion_kind == CounterclockwiseArc) &&
i == 0.0 &&
j == 0.0 {
return Err(make_error(InvalidArc, "arc requires I or J", line_number))
}
motions.push({
kind_value: motion_kind,
start_value: position,
end_value: end,
i_value: i,
j_value: j,
feed_value: feed,
source_line_value: line_number,
})
position = end
}
}
}
Ok({ motions_value: motions, ignored_lines_value: ignored })
}
///|
pub fn total_distance(program : GCodeProgram) -> Double {
let mut total = 0.0
for motion in program.motions() {
total += motion_path_length(motion)
}
total
}
///|
pub struct Bounds {
min_value : Position
max_value : Position
} derive(Eq, Debug)
///|
pub fn Bounds::min(self : Bounds) -> Position {
self.min_value
}
///|
pub fn Bounds::max(self : Bounds) -> Position {
self.max_value
}
///|
pub fn bounds(program : GCodeProgram) -> Bounds {
let mut min = Position::new(0.0, 0.0, 0.0, 0.0)
let mut max = min
for motion in program.motions() {
for point in [motion.start(), motion.end()] {
min = Position::new(
if point.x() < min.x() {
point.x()
} else {
min.x()
},
if point.y() < min.y() {
point.y()
} else {
min.y()
},
if point.z() < min.z() {
point.z()
} else {
min.z()
},
0.0,
)
max = Position::new(
if point.x() > max.x() {
point.x()
} else {
max.x()
},
if point.y() > max.y() {
point.y()
} else {
max.y()
},
if point.z() > max.z() {
point.z()
} else {
max.z()
},
0.0,
)
}
}
{ min_value: min, max_value: max }
}
///|
pub fn render_summary(program : GCodeProgram) -> String {
let box = bounds(program)
"motions=" +
program.motion_count().to_string() +
", distance=" +
total_distance(program).to_string() +
", bounds=[" +
box.min().x().to_string() +
"," +
box.min().y().to_string() +
"]..[" +
box.max().x().to_string() +
"," +
box.max().y().to_string() +
"]"
}