diff --git a/cli/src/main.rs b/cli/src/main.rs index 972c9d2..36b93c5 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] = Some(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..635ebd8 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: [Option; 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: zero_origin() , } } } @@ -177,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(); @@ -219,6 +226,7 @@ pub fn svg_to_turtle( origin_transform, selector_filter, coordinate_system, + starting_point, ); let turtle = &mut conversion_visitor.terrarium.turtle; for stroke in strokes { @@ -244,6 +252,7 @@ fn svg_to_optimized_strokes( origin_transform: Transform2D, selector_filter: Option, coordinate_system: CoordinateSystem, + starting_point: [Option; 2] ) -> Vec { let mut collect_visitor = ConversionVisitor { terrarium: Terrarium::new(StrokeCollectingTurtle::default()), @@ -260,7 +269,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..6a1d857 100644 --- a/star/src/turtle/elements/tsp.rs +++ b/star/src/turtle/elements/tsp.rs @@ -27,21 +27,22 @@ fn dist(a: Point, b: Point) -> f64 { /// /// /// -pub fn minimize_travel_time(strokes: Vec) -> Vec { +pub fn minimize_travel_time(strokes: Vec,starting_point: [Option; 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].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) } /// 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,13 +133,13 @@ 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); - let mut current_sum = best_sum; + let mut current_sum ; const ITERATIONS: usize = 20000; let mut rng = rand::rng(); @@ -282,8 +283,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 +323,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..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 { @@ -56,6 +57,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()?, @@ -117,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), + ], } } }