From 639e5c3ea0809731636c43e7f22a82ec62911280 Mon Sep 17 00:00:00 2001 From: Erwan MAS Date: Sun, 19 Apr 2026 00:40:21 -0400 Subject: [PATCH 1/5] add a starting-point parameter change behavior when optimizing path , we take care of starting point in some optimisation operations --- cli/src/main.rs | 21 +++++++++++++++++++++ star/src/lower/mod.rs | 6 +++++- star/src/turtle/elements/tsp.rs | 28 ++++++++++++---------------- web/src/state.rs | 4 ++++ 4 files changed, 42 insertions(+), 17 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 972c9d2..0080df0 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -106,6 +106,9 @@ struct Opt { /// #[arg(long)] selector_filter: Option, + /// Starting point , usefull only if try optimize path + #[arg(long)] + starting_point: Option, } fn main() -> io::Result<()> { @@ -168,6 +171,24 @@ fn main() -> io::Result<()> { } } } + { + if let Some(starting_point) = opt.starting_point { + for (i, dimension_starting_point) in starting_point + .split(',') + .map(|point| { + if point.is_empty() { + Default::default() + } else { + point.parse::().expect("could not parse coordinate") + } + }) + .take(2) + .enumerate() + { + settings.conversion.inner.starting_point[i] =dimension_starting_point ; + } + } + } if let Some(line_numbers) = opt.line_numbers { settings.postprocess.line_numbers = line_numbers; diff --git a/star/src/lower/mod.rs b/star/src/lower/mod.rs index 78e95bf..17e3341 100644 --- a/star/src/lower/mod.rs +++ b/star/src/lower/mod.rs @@ -49,6 +49,7 @@ pub struct ConversionConfig { /// #[cfg_attr(feature = "serde", serde(default))] pub selector_filter: Option, + pub starting_point: [f64; 2], } const fn zero_origin() -> [Option; 2] { @@ -63,6 +64,7 @@ impl Default for ConversionConfig { extra_attribute_name: None, optimize_path_order: false, selector_filter: None, + starting_point: [0.0 , 0.0 ] , } } } @@ -219,6 +221,7 @@ pub fn svg_to_turtle( origin_transform, selector_filter, coordinate_system, + config.starting_point, ); let turtle = &mut conversion_visitor.terrarium.turtle; for stroke in strokes { @@ -244,6 +247,7 @@ fn svg_to_optimized_strokes( origin_transform: Transform2D, selector_filter: Option, coordinate_system: CoordinateSystem, + starting_point: [f64;2] ) -> Vec { let mut collect_visitor = ConversionVisitor { terrarium: Terrarium::new(StrokeCollectingTurtle::default()), @@ -260,7 +264,7 @@ fn svg_to_optimized_strokes( collect_visitor.end(); collect_visitor.terrarium.pop_transform(); let strokes = collect_visitor.terrarium.turtle.into_strokes(); - minimize_travel_time(strokes) + minimize_travel_time(strokes,starting_point) } fn node_name(node: &Node, attr_to_print: &Option) -> String { diff --git a/star/src/turtle/elements/tsp.rs b/star/src/turtle/elements/tsp.rs index 17d169d..76cab05 100644 --- a/star/src/turtle/elements/tsp.rs +++ b/star/src/turtle/elements/tsp.rs @@ -27,21 +27,23 @@ fn dist(a: Point, b: Point) -> f64 { /// /// /// -pub fn minimize_travel_time(strokes: Vec) -> Vec { +pub fn minimize_travel_time(strokes: Vec,starting_point: [f64; 2] ) -> Vec { if strokes.len() <= 1 { return strokes; } - let path = nearest_neighbor_greedy(strokes); - local_improvement_with_tabu_search(&path) + let the_starting_point : Point = Point::new(starting_point[0]*96.0/25.4,starting_point[1]*96.0/25.4); + + let path = nearest_neighbor_greedy(strokes,the_starting_point); + local_improvement_with_tabu_search(&path,the_starting_point) } /// Greedy nearest-neighbour ordering with flips. /// /// Repeatedly chooses the [Stroke] or [Stroke::reversed] closest to the current point until none remain. -fn nearest_neighbor_greedy(mut remaining: Vec) -> Vec { +fn nearest_neighbor_greedy(mut remaining: Vec,the_starting_point: Point ) -> Vec { let mut result = Vec::with_capacity(remaining.len()); // TODO: this assumption may be incorrect? depends on the GCode begin sequence, which this can't account for. - let mut pos = Point::zero(); + let mut pos : Point = the_starting_point ; while !remaining.is_empty() { let mut best_idx = 0; @@ -132,9 +134,9 @@ fn reverse_and_flip(strokes: &mut [Stroke]) { /// - TwoOpt and LinkSwap reversals also flip each stroke in the reversed range. /// - Relocate tries both the normal and reversed orientation of the moved stroke. /// - Distances are `f64` Euclidean rather than squared integers. -fn local_improvement_with_tabu_search(path: &[Stroke]) -> Vec { +fn local_improvement_with_tabu_search(path: &[Stroke],the_starting_point: Point ) -> Vec { let mut best = path.to_owned(); - let mut best_sum: f64 = stroke_distances(&best).iter().sum(); + let mut best_sum: f64 = stroke_distances(&best).iter().sum::() + dist(the_starting_point,best[0].start_point()) ; let mut current = best.clone(); let mut current_distances = stroke_distances(¤t); @@ -282,8 +284,8 @@ fn local_improvement_with_tabu_search(path: &[Stroke]) -> Vec { // 2 = [first_start, last_end]: both let candidates = [ (0usize, dist(from, last_end)), - (1usize, dist(first_start, to)), - (2usize, dist(first_start, last_end)), + (1usize, dist(first_start, to) +dist(the_starting_point,to)-dist(the_starting_point,first_start)), + (2usize, dist(first_start, last_end)+dist(the_starting_point,last_end)-dist(the_starting_point,first_start)), ]; let (opt, best_new_dist) = candidates .into_iter() @@ -322,14 +324,8 @@ fn local_improvement_with_tabu_search(path: &[Stroke]) -> Vec { } } - let prev_sum = current_sum; current_distances = stroke_distances(¤t); - current_sum = current_distances.iter().sum::(); - - debug_assert!( - prev_sum > current_sum - f64::EPSILON, - "operator={operator:?} prev={prev_sum} current={current_sum}" - ); + current_sum = current_distances.iter().sum::() +dist(the_starting_point,current[0].start_point()) ; if current_sum < best_sum { best = current.clone(); diff --git a/web/src/state.rs b/web/src/state.rs index 2538410..0812136 100644 --- a/web/src/state.rs +++ b/web/src/state.rs @@ -56,6 +56,10 @@ impl TryInto for &FormState { extra_attribute_name: None, optimize_path_order: self.optimize_path_order, selector_filter: None, + starting_point: [ + self.starting_point[0].clone().transpose()?, + self.starting_point[1].clone().transpose()?, + ], }, tolerance: self.tolerance.clone()?, feedrate: self.feedrate.clone()?, From 4b9e8b52ccf782c3955c07f4ec01877ac95fe70a Mon Sep 17 00:00:00 2001 From: Erwan MAS Date: Sun, 19 Apr 2026 00:51:19 -0400 Subject: [PATCH 2/5] - fix ci/cd errors --- cli/src/main.rs | 4 ++-- star/src/turtle/elements/tsp.rs | 2 +- web/src/state.rs | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 0080df0..bd8e5e5 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -106,7 +106,7 @@ struct Opt { /// #[arg(long)] selector_filter: Option, - /// Starting point , usefull only if try optimize path + /// Starting point , usefull only if try optimize path #[arg(long)] starting_point: Option, } @@ -185,7 +185,7 @@ fn main() -> io::Result<()> { .take(2) .enumerate() { - settings.conversion.inner.starting_point[i] =dimension_starting_point ; + settings.conversion.inner.starting_point[i] = dimension_starting_point; } } } diff --git a/star/src/turtle/elements/tsp.rs b/star/src/turtle/elements/tsp.rs index 76cab05..de06d56 100644 --- a/star/src/turtle/elements/tsp.rs +++ b/star/src/turtle/elements/tsp.rs @@ -140,7 +140,7 @@ fn local_improvement_with_tabu_search(path: &[Stroke],the_starting_point: Point< let mut current = best.clone(); let mut current_distances = stroke_distances(¤t); - let mut current_sum = best_sum; + let mut current_sum ; const ITERATIONS: usize = 20000; let mut rng = rand::rng(); diff --git a/web/src/state.rs b/web/src/state.rs index 0812136..df2d28a 100644 --- a/web/src/state.rs +++ b/web/src/state.rs @@ -56,9 +56,9 @@ impl TryInto for &FormState { extra_attribute_name: None, optimize_path_order: self.optimize_path_order, selector_filter: None, - starting_point: [ - self.starting_point[0].clone().transpose()?, - self.starting_point[1].clone().transpose()?, + starting_point: [ + self.starting_point[0].clone().transpose()?, + self.starting_point[1].clone().transpose()?, ], }, tolerance: self.tolerance.clone()?, From cd65f952f05e7047db871e775a06993b07b01eb7 Mon Sep 17 00:00:00 2001 From: Erwan MAS Date: Sun, 19 Apr 2026 12:38:48 -0400 Subject: [PATCH 3/5] fix ci/cd --- cli/src/main.rs | 2 +- star/src/lower/mod.rs | 13 +++++++++---- star/src/turtle/elements/tsp.rs | 6 +++--- web/src/state.rs | 5 +++++ 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index bd8e5e5..36b93c5 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -185,7 +185,7 @@ fn main() -> io::Result<()> { .take(2) .enumerate() { - settings.conversion.inner.starting_point[i] = dimension_starting_point; + settings.conversion.inner.starting_point[i] = Some(dimension_starting_point); } } } diff --git a/star/src/lower/mod.rs b/star/src/lower/mod.rs index 17e3341..635ebd8 100644 --- a/star/src/lower/mod.rs +++ b/star/src/lower/mod.rs @@ -49,7 +49,7 @@ pub struct ConversionConfig { /// #[cfg_attr(feature = "serde", serde(default))] pub selector_filter: Option, - pub starting_point: [f64; 2], + pub starting_point: [Option; 2], } const fn zero_origin() -> [Option; 2] { @@ -64,7 +64,7 @@ impl Default for ConversionConfig { extra_attribute_name: None, optimize_path_order: false, selector_filter: None, - starting_point: [0.0 , 0.0 ] , + starting_point: zero_origin() , } } } @@ -179,6 +179,11 @@ pub fn svg_to_turtle( .origin .map(|dim| dim.map(|d| UomLength::new::(d).get::() * CSS_DEFAULT_DPI)); + // Convert from millimeters to user units + let starting_point = config + .starting_point + .map(|dim| dim.map(|d| UomLength::new::(d).get::() * CSS_DEFAULT_DPI)); + let origin_transform = match origin { [None, Some(origin_y)] => { let bb = bounding_box_generator(); @@ -221,7 +226,7 @@ pub fn svg_to_turtle( origin_transform, selector_filter, coordinate_system, - config.starting_point, + starting_point, ); let turtle = &mut conversion_visitor.terrarium.turtle; for stroke in strokes { @@ -247,7 +252,7 @@ fn svg_to_optimized_strokes( origin_transform: Transform2D, selector_filter: Option, coordinate_system: CoordinateSystem, - starting_point: [f64;2] + starting_point: [Option; 2] ) -> Vec { let mut collect_visitor = ConversionVisitor { terrarium: Terrarium::new(StrokeCollectingTurtle::default()), diff --git a/star/src/turtle/elements/tsp.rs b/star/src/turtle/elements/tsp.rs index de06d56..facc2fa 100644 --- a/star/src/turtle/elements/tsp.rs +++ b/star/src/turtle/elements/tsp.rs @@ -4,7 +4,7 @@ use std::collections::VecDeque; -use log::debug; +use log::{debug,warn}; use lyon_geom::Point; use rand::{ RngExt, @@ -27,11 +27,11 @@ fn dist(a: Point, b: Point) -> f64 { /// /// /// -pub fn minimize_travel_time(strokes: Vec,starting_point: [f64; 2] ) -> Vec { +pub fn minimize_travel_time(strokes: Vec,starting_point: [Option; 2] ) -> Vec { if strokes.len() <= 1 { return strokes; } - let the_starting_point : Point = Point::new(starting_point[0]*96.0/25.4,starting_point[1]*96.0/25.4); + let the_starting_point : Point = Point::new(starting_point[0].expect("No starting point Y"),starting_point[1].expect("No starting point Y")); let path = nearest_neighbor_greedy(strokes,the_starting_point); local_improvement_with_tabu_search(&path,the_starting_point) diff --git a/web/src/state.rs b/web/src/state.rs index df2d28a..ec3cd34 100644 --- a/web/src/state.rs +++ b/web/src/state.rs @@ -24,6 +24,7 @@ pub struct FormState { pub checksums: bool, pub line_numbers: bool, pub newline_before_comment: bool, + pub starting_point: [Option>; 2], } impl Default for FormState { @@ -121,6 +122,10 @@ impl From<&Settings> for FormState { checksums: settings.postprocess.checksums, line_numbers: settings.postprocess.line_numbers, newline_before_comment: settings.postprocess.newline_before_comment, + starting_point: [ + settings.conversion.inner.starting_point[0].map(Ok), + settings.conversion.inner.starting_point[1].map(Ok), + ], } } } From bc01f6f114a4739083eb65b9b06cdc7cd04551db Mon Sep 17 00:00:00 2001 From: Erwan MAS Date: Sun, 19 Apr 2026 12:40:27 -0400 Subject: [PATCH 4/5] - remove unused module --- star/src/turtle/elements/tsp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/star/src/turtle/elements/tsp.rs b/star/src/turtle/elements/tsp.rs index facc2fa..2735bad 100644 --- a/star/src/turtle/elements/tsp.rs +++ b/star/src/turtle/elements/tsp.rs @@ -4,7 +4,7 @@ use std::collections::VecDeque; -use log::{debug,warn}; +use log::debug; use lyon_geom::Point; use rand::{ RngExt, From f512d9bc7c0bef3b9451f6155acc14384a901048 Mon Sep 17 00:00:00 2001 From: Erwan MAS Date: Thu, 23 Apr 2026 12:53:34 -0400 Subject: [PATCH 5/5] remove unuseful comment --- star/src/turtle/elements/tsp.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/star/src/turtle/elements/tsp.rs b/star/src/turtle/elements/tsp.rs index 2735bad..6a1d857 100644 --- a/star/src/turtle/elements/tsp.rs +++ b/star/src/turtle/elements/tsp.rs @@ -42,7 +42,6 @@ pub fn minimize_travel_time(strokes: Vec,starting_point: [Option; 2 /// Repeatedly chooses the [Stroke] or [Stroke::reversed] closest to the current point until none remain. fn nearest_neighbor_greedy(mut remaining: Vec,the_starting_point: Point ) -> Vec { let mut result = Vec::with_capacity(remaining.len()); - // TODO: this assumption may be incorrect? depends on the GCode begin sequence, which this can't account for. let mut pos : Point = the_starting_point ; while !remaining.is_empty() {