backup commit

master
skybldev 9 months ago
parent 99e8abef6a
commit d19c63e531

129
Cargo.lock generated

@ -101,25 +101,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e"
[[package]]
name = "codespan"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3362992a0d9f1dd7c3d0e89e0ab2bb540b7a95fea8cd798090e758fda2899b5e"
dependencies = [
"codespan-reporting",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "diff"
version = "0.1.13"
@ -144,19 +125,6 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "g-code"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6261d157241cc6ec7e015a19f2b8ea37e2018a32980a60d65bda6c7b336349fc"
dependencies = [
"codespan",
"codespan-reporting",
"paste",
"peg",
"rust_decimal",
]
[[package]]
name = "getrandom"
version = "0.2.12"
@ -205,6 +173,16 @@ version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "kurbo"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1618d4ebd923e97d67e7cd363d80aef35fe961005cbbbb3d2dad8bdd1bc63440"
dependencies = [
"arrayvec",
"smallvec",
]
[[package]]
name = "libc"
version = "0.2.152"
@ -268,44 +246,11 @@ dependencies = [
name = "osubot"
version = "0.1.0"
dependencies = [
"g-code",
"kurbo",
"osu-file-parser",
"rust_decimal",
]
[[package]]
name = "paste"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "peg"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "400bcab7d219c38abf8bd7cc2054eb9bbbd4312d66f6a5557d572a203f646f61"
dependencies = [
"peg-macros",
"peg-runtime",
]
[[package]]
name = "peg-macros"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46e61cce859b76d19090f62da50a9fe92bab7c2a5f09e183763559a2ac392c90"
dependencies = [
"peg-runtime",
"proc-macro2",
"quote",
]
[[package]]
name = "peg-runtime"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36bae92c60fa2398ce4678b98b2c4b5a7c61099961ca1fa305aec04a9ad28922"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@ -547,6 +492,12 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a"
[[package]]
name = "smallvec"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
[[package]]
name = "strum"
version = "0.24.1"
@ -606,15 +557,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "termcolor"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
dependencies = [
"winapi-util",
]
[[package]]
name = "thiserror"
version = "1.0.56"
@ -673,12 +615,6 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-width"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
[[package]]
name = "uuid"
version = "1.7.0"
@ -697,37 +633,6 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "winnow"
version = "0.5.34"

@ -7,6 +7,6 @@ edition = "2021"
[dependencies]
osu-file-parser = "1.1.0"
g-code = "0.3.6"
#matrix = "0.22.0"
rust_decimal = { version = "1.33.1", features = ["maths"] }
kurbo = "0.10.4"

@ -1,76 +1,18 @@
use osu_file_parser::{
osu_file::{
hitobjects::{
CurveType::*,
HitObjectParams::*
}
},
OsuFile
hitobjects::{HitObject, HitObjectParams::*, SlideParams, CurveType},
osu_file::hitobjects,
Decimal as OsuDecimal,
OsuFile,
Position
};
use rust_decimal::{Decimal, prelude::ToPrimitive};
use kurbo::{BezPath, PathEl, Point, QuadSpline};
mod types;
use types::*;
/*
The input file.
*/
const OSU: &str = include_str!("../osu/test2/test2.osu");
/*
Machine's maximum acceleration in mm/s².
*/
const MAX_ACCEL: f64 = 110_000.0;
/*
The default acceleration to use for max_v calculation.
*/
const DEFAULT_ACCEL: f64 = MAX_ACCEL / 2.0;
/*
The acceleration to use for groups of hitobjects. This should be higher as
groups of hitobjects usually have to be traveled with "linear" velocity
across all the hitobjects.
*/
const GROUP_ACCEL: f64 = MAX_ACCEL;
/*
How much to increase the acceleration if the current acceleration doesn't
produce a result.
*/
const ACCEL_RETRY_STEP: f64 = 5_000.0;
/*
Since hitobjects' times are rounded to the nearest millisecond, linear
velocity calculations aren't very precise, leading to hitobjects with linear
velocities perceived to be identical not counted for grouping. To compensate
for this, a threshold is set, and any difference between linear velocities
under this threshold are counted.
*/
const GROUP_VEL_LENIENCY: f64 = 0.2;
/*
Size of the bounding box for the beatmap in storage. Do not change.
*/
const BOUNDING_BOX_SIZE: (f64, f64) = (512.0, 384.0); // lazer default ig
/*
Physical limits of the machine
*/
const PHYSICAL_MAX: (f64, f64) = (105.0, 110.0);
/*
Active area
*/
const AREA_ORIGIN: (f64, f64) = (10.0, 10.0);
const AREA_MAX: (f64, f64) = (95.0, 95.0);
/*
Rotation in degrees anteclockwise
*/
const ROTATION: f64 = 90.0;
const AREA_SIZE: (f64, f64) = (
AREA_MAX.0 - AREA_ORIGIN.0,
AREA_MAX.1 - AREA_ORIGIN.1
);
const BOX_TO_AREA_OFFSET: (f64, f64) = (
AREA_SIZE.0 / 2.0 - BOUNDING_BOX_SIZE.0 / 2.0,
AREA_SIZE.1 / 2.0 - BOUNDING_BOX_SIZE.1 / 2.0
);
mod config;
use config as cfg;
/*
Calculates the maximum velocity (mm/ms) given the the start and end
@ -142,8 +84,8 @@ fn solve_feedrate_and_accel(
loop {
max_v = max_velocity(p1, p2, accel, ms);
if !max_v.is_nan() { break }
accel += ACCEL_RETRY_STEP;
if accel >= MAX_ACCEL {
accel += cfg::ACCEL_RETRY_STEP;
if accel >= cfg::MAX_ACCEL {
panic!("couldn't calculate max_v for dist {} within {}ms", p1 >> p2, ms);
}
}
@ -163,141 +105,366 @@ fn solve_feedrate_and_accel(
(feedrate, accel)
}
fn generate_gcode_move(
destination: PositionF64,
accel: f64,
last_accel: f64,
feedrate: f64
) -> String {
let mut output = String::new();
if accel != last_accel {
output = format!("M204 S{}\n", accel);
}
output = format!(
"{}G0 F{:.4}\nG0 X{} Y{}",
output,
feedrate,
destination.x, destination.y
);
println!("{}", output);
output
}
fn osu_position_to_kurbo_point(pos: &Position) -> Point {
Point {
x: pos.x.get().clone().expect_left("why").to_f64().expect("why"),
y: pos.y.get().clone().expect_left("why").to_f64().expect("why")
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
/* Steps
1. Read beatmap and hitcircles
a. convert curves to lines
b. apply scaling for every point
c. apply rotation for every point
2. Convert to gcode paths
a. add preliminary gcode
b. calculate F for a given movement
c. write movements to file
*/
let src = OSU.parse::<OsuFile>()?;
/*
# Calculating moves and optimizing
The way this system works is by iterating through the hitobjects and
"accumulating" state, then creating *moves* from said state. On each
iteration, we don't "move to the next point", instead, we decide whether
or not to generate gcode to move to the last point. This also means that
every move starts at a point in the past. Here is a pseudocode
representation:
start of the move = the last bounding box corner
last point = the last bounding box corner
if it's the first point:
tell the machine to pause
otherwise:
if this point is in the same location as the last point:
enable stack mode
otherwise:
if stack mode is on:
generate gcode to move to the first point in the stack
generate gcode to dwell for the duration of the stack
disable stack mode
otherwise, if the hitobject qualifies for grouping:
increment the group size
otherwise:
generate gcode to move to the last point
store the last point as the start of the move
store the current point as the last point
increment total time
Keep in mind that a line of gcode only tells the pen to move to another
position, and the starting location is based on its current position,
which was the destination of the last move.
*/
let src = cfg::OSU.parse::<OsuFile>()?;
let binding = src.hitobjects.expect("Beatmap has no hit objects!");
let mut hitobjects = binding.0.iter();
let scale_fac = find_bbox_scale_factor(
BOUNDING_BOX_SIZE,
AREA_SIZE,
ROTATION
cfg::BOUNDING_BOX_SIZE,
cfg::AREA_SIZE,
cfg::ROTATION
);
println!("G90\nG0 Z0");
let corners = [
AREA_ORIGIN,
(AREA_ORIGIN.0, AREA_MAX.1),
(AREA_MAX.0, AREA_MAX.1),
(AREA_MAX.0, AREA_ORIGIN.1)
cfg::AREA_ORIGIN,
(cfg::AREA_ORIGIN.0, cfg::AREA_MAX.1),
(cfg::AREA_MAX.0, cfg::AREA_MAX.1),
(cfg::AREA_MAX.0, cfg::AREA_ORIGIN.1)
];
for corner in corners {
println!("G0 X{} Y{} F7500", corner.0, corner.1);
}
let mut total_time: f64 = 0.0;
let final_corner = PositionF64::from_tuple(corners[corners.len() - 1]);
// An extra element is needed at the end of the hitobjects vec
let temp_binding = binding.0.clone();
let mut last_hitobject = temp_binding.last().unwrap().clone();
last_hitobject.position = Position {
x: OsuDecimal::new(Decimal::from_f64_retain(final_corner.x).unwrap()),
y: OsuDecimal::new(Decimal::from_f64_retain(final_corner.y).unwrap()),
};
let mut hitobjects = binding.0;
hitobjects.push(last_hitobject.clone());
let hitobjects = hitobjects.iter();
let mut s = IteratorState {
total_time: 0,
last_point: final_corner,
last_point_ms: 0,
last_accel: 0.0,
move_start: final_corner,
move_start_ms: 0,
group_linear_vel: 0.0,
group_slope: 0.0,
group_size: 0,
stack: false,
stack_start_ms: 0,
osu_file: src
};
for (idx, obj) in hitobjects.enumerate() {
iteration(idx, obj, scale_fac, &mut s);
}
eprintln!("total time should be {}ms", s.total_time);
Ok(())
}
fn iteration(
idx: usize,
obj: &HitObject,
scale_fac: f64,
s: &mut IteratorState
) {
let current_ms = obj.time.get().clone().unwrap_left().to_u32().unwrap();
let ms_since_last_point = current_ms - s.last_point_ms;
let current_point = PositionF64
::from_position(obj.position.clone())
.unwrap()
.convert_to_machine_space(
scale_fac,
cfg::BOUNDING_BOX_SIZE,
cfg::AREA_ORIGIN,
cfg::AREA_SIZE,
cfg::ROTATION
);
eprintln!("point {}", idx);
if idx == 0 {
// Pause right before the first move
println!("PAUSE");
} else if let HitCircle = obj.obj_params {
handle_hitcircle(current_ms, ms_since_last_point, current_point, s);
} else {
if s.stack {
end_stack(s);
} else {
end_move(s);
}
match &obj.obj_params {
Slider(params) => {
handle_slider(obj.position.clone(), &params, s);
},
_ => {}
}
}
s.total_time += ms_since_last_point;
s.last_point = current_point;
s.last_point_ms = current_ms;
}
fn handle_hitcircle(
current_ms: u32,
ms_since_last_point: u32,
current_point: PositionF64,
s: &mut IteratorState
) {
if s.last_point == current_point && !s.stack {
// Begin stacking if the last point is the same as the current one
s.stack = true;
s.stack_start_ms = s.last_point_ms;
} else if s.last_point != current_point {
let linear_vel = (s.last_point >> current_point) / ms_since_last_point as f64;
let slope = s.last_point / current_point;
let can_group = s.group_linear_vel < cfg::GROUP_VEL_LENIENCY
&& slope == s.group_slope;
if s.stack {
end_stack(s);
} else if can_group {
println!("; !! adding point to group");
s.group_size += 1;
} else {
end_move(s);
s.group_linear_vel = linear_vel;
s.group_slope = slope;
}
}
}
let mut p1 = PositionF64::new(0.0, 0.0);
let mut p1_ms: Decimal = 0.into();
let mut last_accel: f64 = 0.0;
/*
This essentially works the same way as the base iteration. It creates
subpaths out of a set of control points by "accumulating" previous points
and deciding on whether or not to create a new subpath based on the next
point.
Sliders are saved as different types if they have multiple types of curves.
Essentially, a path becomes a bézier if it isn't completely linear. Perfect
circle subpaths get approximated as béziers, too.
*/
fn handle_slider(
pos: Position,
params: &SlideParams,
s: &mut IteratorState
) {
if params.curve_points.len() == 0 as usize { return; }
let points = &params.curve_points
.iter()
.map(|p| osu_position_to_kurbo_point(&p.0));
let ticks = match params.curve_type {
CurveType::Linear => { },
CurveType::PerfectCircle => { },
_ => { }
}
}
fn calculate_linear_ticks(
pos: Position,
points: Vec<Point>,
/*
These are used for merging notes that share the same angle and linear
velocity into one move. Linear velocity is plainly (dist / ms_delta).
) -> Vec<PositionF64> {
if points.len() == 0 { panic!("wtf") }
move_p1 is the starting point of a g-code move.
*/
let mut move_p1: PositionF64 = p1;
let mut move_p1_ms: Decimal = 0.into();
let mut group_linear_vel = 0.0;
let mut group_slope = 0.0;
let mut group_size = 0;
let mut dwell_state = false;
for (count, obj) in hitobjects.enumerate() {
let p2_ms = obj.time.get().clone().unwrap_left();
let p1_to_p2_ms = p2_ms
.checked_sub(p1_ms)
.unwrap()
.to_f64()
.unwrap();
let mut dist = 0.0;
let dist_between_ticks =
let p2 = PositionF64
::from_position(obj.position.clone())
.unwrap()
.convert_to_machine_space(
scale_fac,
BOUNDING_BOX_SIZE,
AREA_ORIGIN,
AREA_SIZE,
ROTATION
);
eprintln!("point {}", count);
if count == 0 {
println!("PAUSE");
} else if p1 == p2 && !dwell_state {
dwell_state = true;
} else if p1 != p2 {
let move_p1_to_p1_ms = p1_ms
.checked_sub(move_p1_ms)
.unwrap()
.to_f64()
.unwrap();
let linear_vel = (p1 >> p2) / p1_to_p2_ms;
let slope = p1 / p2;
println!("; linear_vel {:.4}, slope {:.4}", linear_vel, slope);
if
linear_vel - group_linear_vel < GROUP_VEL_LENIENCY
&& slope == group_slope
{
println!("; !! merging point");
group_size += 1;
} else {
let (feedrate, accel) = solve_feedrate_and_accel(
move_p1,
p1,
if group_size > 0 {
GROUP_ACCEL
} else {
DEFAULT_ACCEL
},
move_p1_to_p1_ms
);
let mut ticks = Vec::<PositionF64>::new();
if accel != last_accel {
println!("M204 S{}", accel);
}
let mut dist_remainder = dist;
let mut point_iter = points.iter();
let mut p1 = PositionF64::from_point(*point_iter.next().unwrap());
println!("G0 F{:.4}\nG0 X{} Y{}", feedrate, p1.x, p1.y);
'each_tick: while {
last_accel = accel;
group_linear_vel = linear_vel;
group_slope = slope;
group_size = 0;
}
}
loop {
let next_point = point_iter.next();
let p2 = PositionF64::from_point(*point_iter.next().unwrap());
let dist = p1 >> p2;
if dist_remainder < dist {
let fac = dist_remainder / dist;
ticks.push(PositionF64 {
x: (p2.x - p1.x) * fac,
y: (p2.y - p1.y) * fac
});
break;
} else if dist_remainder == dist {
ticks.push(p2);
break;
} else {
dist_remainder -= dist;
p1 = p2;
}
}
if dwell_state {
println!("G4 P{}", move_p1_to_p1_ms);
dwell_state = false;
ticks
}
fn calculate_bezier_ticks(
pos: Position,
points: Vec<Point>,
slider_mul: f64,
slider_ticks: u8
) -> Vec<PositionF64> {
let last_point: Point;
let mut segment_vertices = Vec::<Point>::new();
segment_vertices.push(last_point);
let mut split_path = BezPath::from_vec(vec![PathEl::MoveTo(last_point)]);
for point in points.iter() {
// Previous path ended, new subpath begins
if *point == last_point {
match segment_vertices.len() {
0 => { panic!("bruhhhhhh {:?}", pos) },
1 => { split_path.line_to(last_point) },
_ => {
QuadSpline
::new(segment_vertices)
.to_quads()
.for_each(|quad| split_path.quad_to(quad.p1, quad.p2));
}
}
move_p1_ms = p1_ms;
move_p1 = p1;
segment_vertices = Vec::<Point>::new();
segment_vertices.push(last_point);
segment_vertices.push(*point);
} else {
segment_vertices.push(*point);
}
p1 = p2;
p1_ms = p2_ms;
total_time += p1_to_p2_ms;
}
vec![PositionF64::new(10.0, 10.0)]
}
fn end_stack(s: &mut IteratorState) {
// Generates gcode to move to the stack and wait there for the
// totkal duration of its hitobjects, and resets the stack state.
let (feedrate, accel) = solve_feedrate_and_accel(
s.move_start,
s.last_point,
cfg::DEFAULT_ACCEL,
(s.stack_start_ms - s.move_start_ms) as f64
);
eprintln!("total time should be {}ms", total_time);
generate_gcode_move(s.last_point, accel, s.last_accel, feedrate);
println!("G4 P{}", s.last_point_ms - s.stack_start_ms);
Ok(())
s.last_accel = accel;
s.move_start_ms = s.last_point_ms;
s.move_start = s.last_point;
s.stack = false;
}
fn end_move(s: &mut IteratorState) {
// Generates gcode for a move and starts a new one.
// Use higher acceleration when moving across a group
let accel = if s.group_size > 0 {
cfg::GROUP_ACCEL
} else {
cfg::DEFAULT_ACCEL
};
let (feedrate, accel) = solve_feedrate_and_accel(
s.move_start,
s.last_point,
accel,
(s.last_point_ms - s.move_start_ms) as f64
);
generate_gcode_move(s.last_point, accel, s.last_accel, feedrate);
s.last_accel = accel;
s.move_start_ms = s.last_point_ms;
s.move_start = s.last_point;
s.group_size = 0;
}
/*

@ -1,5 +1,6 @@
use osu_file_parser::Position;
use osu_file_parser::{osu_file, timingpoints::TimingPoint, Position, Decimal as RetardedDecimal};
use rust_decimal::{Decimal, prelude::ToPrimitive};
use kurbo::{Point};
pub type DecimalTuple = (Decimal, Decimal);
@ -28,6 +29,13 @@ impl PositionF64 {
})
}
pub fn from_point(a: Point) -> Self {
Self {
x: a.x,
y: a.y
}
}
pub fn scale(mut self, fac: f64) -> Self {
self.x *= fac;
self.y *= fac;
@ -127,3 +135,34 @@ impl std::ops::Div for PositionF64 {
diff.y / diff.x
}
}
pub struct IteratorState {
pub total_time: u32,
pub last_point: PositionF64,
pub last_point_ms: u32,
pub last_accel: f64,
pub move_start: PositionF64,
pub move_start_ms: u32,
pub group_linear_vel: f64,
pub group_slope: f64,
pub group_size: u32,
pub stack: bool,
pub stack_start_ms: u32,
pub osu_file: osu_file::OsuFile
}
impl IteratorState {
fn get_timing_point_at_ms(&self, ms: u32) -> &TimingPoint {
for t_point in self.osu_file.timing_points.unwrap().0.iter() {
if ms >= t_point.time().try_into().expect() {
return t_point;
}
}
&self.osu_file.timing_points.unwrap().0[0]
}
}

@ -5,74 +5,53 @@ G0 X10 Y95 F7500
G0 X95 Y95 F7500
G0 X95 Y10 F7500
PAUSE
; linear_vel 0.1064, slope 5.3750
M204 S55000
G0 F14734.6525
G0 F11960.7600
G0 X51.171875 Y35.8984375
; linear_vel 0.0818, slope -2.3846
G0 F6429.3272
G0 X56.484375 Y64.453125
; linear_vel 0.1392, slope 2.5238
G0 F982.1545
G0 F4933.1169
G0 X65.1171875 Y43.8671875
G4 P1364
; linear_vel 0.0584, slope -inf
G4 P1091
G0 F8430.0676
G0 X79.0625 Y79.0625
; linear_vel 0.0590, slope -inf
; !! merging point
; linear_vel 0.0584, slope -inf
; !! merging point
; linear_vel 0.0334, slope 0.3735
; !! adding point to group
; !! adding point to group
M204 S110000
G0 F3509.5833
G0 F3517.1981
G0 X79.0625 Y31.083984375
; linear_vel 0.5068, slope 13.0312
M204 S55000
G0 F2005.8322
G0 X36.396484375 Y15.146484375000004
; linear_vel 0.4223, slope -2.4884
G0 F32785.7872
G0 X41.70898437500001 Y84.375
; linear_vel 0.2518, slope -1.2481
G0 F26957.4090
G0 X63.125 Y31.083984375000004
; linear_vel 0.2373, slope -0.1710
G0 F15656.4508
G0 X41.708984375 Y57.8125
; linear_vel 0.1172, slope -0.0000
G0 F14715.1658
G0 X73.75 Y52.333984375
; linear_vel 0.1614, slope 3.8788
G0 F7145.0001
G0 X57.8125 Y52.333984375
; linear_vel 0.4009, slope 1.7950
G0 F9899.9328
G0 X52.333984375 Y31.083984375000004
; linear_vel 0.4309, slope -inf
G0 F25490.3308
G0 X79.0625 Y79.0625
; linear_vel 0.1953, slope -1.3333
G0 F27545.0689
G0 X79.0625 Y20.458984375
; linear_vel 0.2371, slope -6.0313
G0 F12041.8473
G0 X63.125 Y41.708984375
; linear_vel 0.1758, slope -2.0156
G0 F14702.2521
G0 X57.81250000000001 Y73.75
; linear_vel 0.2491, slope 1.2403
G0 F10807.3775
G0 X68.4375 Y52.333984375
; linear_vel 0.3923, slope 0.7510
G0 F15472.8573
G0 X47.021484375 Y25.771484375000004
; linear_vel 0.2509, slope 0.8063
G0 F24924.2159
G0 X89.6875 Y57.8125
; linear_vel 0.2838, slope -3.4615
G0 F15595.0898
G0 X63.125 Y36.396484375
; linear_vel 0.1622, slope 4.0312
G0 F17722.9324
G0 X52.33398437500001 Y73.75
G0 F9955.4352
G0 X47.021484375 Y52.333984375

Loading…
Cancel
Save