session_rust/
polyline.rs

1use crate::{Color, Plane, Point, Tolerance, Vector, Xform};
2use serde::{Deserialize, Serialize};
3use std::fmt;
4use std::ops::{Add, AddAssign, Sub, SubAssign};
5use uuid::Uuid;
6
7/// A polyline defined by a collection of points with an associated plane.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9#[serde(tag = "type", rename = "Polyline")]
10pub struct Polyline {
11    pub guid: String,
12    pub name: String,
13    pub points: Vec<Point>,
14    pub plane: Plane,
15    pub width: f32,
16    pub linecolor: Color,
17    #[serde(default = "Xform::identity")]
18    pub xform: Xform,
19}
20
21impl Default for Polyline {
22    fn default() -> Self {
23        Self {
24            guid: Uuid::new_v4().to_string(),
25            name: "my_polyline".to_string(),
26            points: Vec::new(),
27            plane: Plane::default(),
28            width: 1.0,
29            linecolor: Color::white(),
30            xform: Xform::identity(),
31        }
32    }
33}
34
35impl Polyline {
36    /// Creates a new `Polyline` with default guid and name.
37    ///
38    /// # Arguments
39    ///
40    /// * `points` - The collection of points.
41    pub fn new(points: Vec<Point>) -> Self {
42        // Delegate plane computation to Plane::from_points
43        let plane = if points.len() >= 3 {
44            Plane::from_points(points.clone())
45        } else {
46            Plane::default()
47        };
48
49        Self {
50            guid: Uuid::new_v4().to_string(),
51            name: "my_polyline".to_string(),
52            points,
53            plane,
54            width: 1.0,
55            linecolor: Color::white(),
56            xform: Xform::identity(),
57        }
58    }
59
60    /// Returns the number of points in the polyline.
61    pub fn len(&self) -> usize {
62        self.points.len()
63    }
64
65    /// Returns true if the polyline has no points.
66    pub fn is_empty(&self) -> bool {
67        self.points.is_empty()
68    }
69
70    /// Returns the number of segments in the polyline.
71    /// A polyline with n points has n-1 segments.
72    pub fn segment_count(&self) -> usize {
73        if self.points.len() > 1 {
74            self.points.len() - 1
75        } else {
76            0
77        }
78    }
79
80    /// Calculates the total length of the polyline.
81    pub fn length(&self) -> f32 {
82        let mut total_length = 0.0;
83        for i in 0..self.segment_count() {
84            let mut segment_vector = self.points[i + 1].clone() - self.points[i].clone();
85            total_length += segment_vector.magnitude();
86        }
87        total_length
88    }
89
90    /// Returns a reference to the point at the given index.
91    pub fn get_point(&self, index: usize) -> Option<&Point> {
92        self.points.get(index)
93    }
94
95    /// Returns a mutable reference to the point at the given index.
96    pub fn get_point_mut(&mut self, index: usize) -> Option<&mut Point> {
97        self.points.get_mut(index)
98    }
99
100    /// Adds a point to the end of the polyline.
101    pub fn add_point(&mut self, point: Point) {
102        self.points.push(point);
103        // Recompute plane if we have at least 3 points
104        if self.points.len() == 3 {
105            self.plane = Plane::from_points(self.points.clone());
106        }
107    }
108
109    /// Inserts a point at the specified index.
110    pub fn insert_point(&mut self, index: usize, point: Point) {
111        self.points.insert(index, point);
112        // Recompute plane if we have at least 3 points
113        if self.points.len() == 3 {
114            self.plane = Plane::from_points(self.points.clone());
115        }
116    }
117
118    /// Removes and returns the point at the specified index.
119    pub fn remove_point(&mut self, index: usize) -> Option<Point> {
120        if index < self.points.len() {
121            let point = self.points.remove(index);
122            // Recompute plane if we still have at least 3 points
123            if self.points.len() == 3 {
124                self.plane = Plane::from_points(self.points.clone());
125            }
126            Some(point)
127        } else {
128            None
129        }
130    }
131
132    /// Reverses the order of points in the polyline.
133    pub fn reverse(&mut self) {
134        self.points.reverse();
135        self.plane.reverse();
136    }
137
138    /// Returns a new polyline with reversed point order.
139    pub fn reversed(&self) -> Self {
140        let mut reversed = self.clone();
141        reversed.reverse();
142        reversed
143    }
144
145    /// Serializes the Polyline to a JSON string.
146    pub fn jsondump(&self) -> Result<String, Box<dyn std::error::Error>> {
147        let mut buf = Vec::new();
148        let formatter = serde_json::ser::PrettyFormatter::with_indent(b"    ");
149        let mut ser = serde_json::Serializer::with_formatter(&mut buf, formatter);
150        self.serialize(&mut ser)?;
151        Ok(String::from_utf8(buf)?)
152    }
153
154    /// Deserializes a Polyline from a JSON string.
155    pub fn jsonload(json_data: &str) -> Result<Self, Box<dyn std::error::Error>> {
156        Ok(serde_json::from_str(json_data)?)
157    }
158
159    /// Serializes the Polyline to a JSON file.
160    pub fn to_json(&self, filepath: &str) -> Result<(), Box<dyn std::error::Error>> {
161        let json = self.jsondump()?;
162        std::fs::write(filepath, json)?;
163        Ok(())
164    }
165
166    /// Deserializes a Polyline from a JSON file.
167    pub fn from_json(filepath: &str) -> Result<Self, Box<dyn std::error::Error>> {
168        let json = std::fs::read_to_string(filepath)?;
169        Self::jsonload(&json)
170    }
171
172    ///////////////////////////////////////////////////////////////////////////////////////////
173    // Geometric Utilities
174    ///////////////////////////////////////////////////////////////////////////////////////////
175
176    /// Shift polyline points by specified number of positions
177    pub fn shift(&mut self, times: i32) {
178        if self.points.is_empty() {
179            return;
180        }
181        let len = self.points.len();
182        let shift_amount = ((times % len as i32) + len as i32) % len as i32;
183        self.points.rotate_left(shift_amount as usize);
184    }
185
186    /// Calculate squared length of polyline (faster, no sqrt)
187    pub fn length_squared(&self) -> f64 {
188        let mut length = 0.0f64;
189        for i in 0..self.segment_count() {
190            let segment = self.points[i + 1].clone() - self.points[i].clone();
191            length += segment.length_squared() as f64;
192        }
193        length
194    }
195
196    /// Get point at parameter t along a line segment (t=0 is start, t=1 is end)
197    pub fn point_at_parameter(start: &Point, end: &Point, t: f64) -> Point {
198        let s = 1.0 - t;
199        let t_f32 = t as f32;
200        let s_f32 = s as f32;
201        Point::new(
202            if start.x() == end.x() {
203                start.x()
204            } else {
205                s_f32 * start.x() + t_f32 * end.x()
206            },
207            if start.y() == end.y() {
208                start.y()
209            } else {
210                s_f32 * start.y() + t_f32 * end.y()
211            },
212            if start.z() == end.z() {
213                start.z()
214            } else {
215                s_f32 * start.z() + t_f32 * end.z()
216            },
217        )
218    }
219
220    /// Find closest point on line segment to given point, returns parameter t
221    pub fn closest_point_to_line(point: &Point, line_start: &Point, line_end: &Point) -> f64 {
222        let d = line_end.clone() - line_start.clone();
223        let dod = d.length_squared();
224
225        if dod > 0.0 {
226            let t = if (point.clone() - line_start.clone()).length_squared()
227                <= (point.clone() - line_end.clone()).length_squared()
228            {
229                (point.clone() - line_start.clone()).dot(&d) / dod
230            } else {
231                1.0 + (point.clone() - line_end.clone()).dot(&d) / dod
232            };
233            t as f64
234        } else {
235            0.0
236        }
237    }
238
239    /// Check if two line segments overlap and return the overlapping segment
240    pub fn line_line_overlap(
241        line0_start: &Point,
242        line0_end: &Point,
243        line1_start: &Point,
244        line1_end: &Point,
245    ) -> Option<(Point, Point)> {
246        let mut t = [0.0, 1.0, 0.0, 0.0];
247        t[2] = Self::closest_point_to_line(line1_start, line0_start, line0_end);
248        t[3] = Self::closest_point_to_line(line1_end, line0_start, line0_end);
249
250        let do_overlap = !((t[2] < 0.0 && t[3] < 0.0) || (t[2] > 1.0 && t[3] > 1.0));
251        t.sort_by(|a, b| a.partial_cmp(b).unwrap());
252
253        let overlap_valid = (t[2] - t[1]).abs() > Tolerance::ZERO_TOLERANCE;
254
255        if do_overlap && overlap_valid {
256            Some((
257                Self::point_at_parameter(line0_start, line0_end, t[1]),
258                Self::point_at_parameter(line0_start, line0_end, t[2]),
259            ))
260        } else {
261            None
262        }
263    }
264
265    /// Calculate average of two line segments
266    pub fn line_line_average(
267        line0_start: &Point,
268        line0_end: &Point,
269        line1_start: &Point,
270        line1_end: &Point,
271    ) -> (Point, Point) {
272        let output_start = Point::new(
273            (line0_start.x() + line1_start.x()) * 0.5,
274            (line0_start.y() + line1_start.y()) * 0.5,
275            (line0_start.z() + line1_start.z()) * 0.5,
276        );
277        let output_end = Point::new(
278            (line0_end.x() + line1_end.x()) * 0.5,
279            (line0_end.y() + line1_end.y()) * 0.5,
280            (line0_end.z() + line1_end.z()) * 0.5,
281        );
282        (output_start, output_end)
283    }
284
285    /// Calculate overlap average of two line segments
286    pub fn line_line_overlap_average(
287        line0_start: &Point,
288        line0_end: &Point,
289        line1_start: &Point,
290        line1_end: &Point,
291    ) -> (Point, Point) {
292        let line_a = Self::line_line_overlap(line0_start, line0_end, line1_start, line1_end);
293        let line_b = Self::line_line_overlap(line1_start, line1_end, line0_start, line0_end);
294
295        if let (Some((line_a_start, line_a_end)), Some((line_b_start, line_b_end))) =
296            (line_a, line_b)
297        {
298            let mid_line0_start = Point::new(
299                (line_a_start.x() + line_b_start.x()) * 0.5,
300                (line_a_start.y() + line_b_start.y()) * 0.5,
301                (line_a_start.z() + line_b_start.z()) * 0.5,
302            );
303            let mid_line0_end = Point::new(
304                (line_a_end.x() + line_b_end.x()) * 0.5,
305                (line_a_end.y() + line_b_end.y()) * 0.5,
306                (line_a_end.z() + line_b_end.z()) * 0.5,
307            );
308            let mid_line1_start = Point::new(
309                (line_a_start.x() + line_b_end.x()) * 0.5,
310                (line_a_start.y() + line_b_end.y()) * 0.5,
311                (line_a_start.z() + line_b_end.z()) * 0.5,
312            );
313            let mid_line1_end = Point::new(
314                (line_a_end.x() + line_b_start.x()) * 0.5,
315                (line_a_end.y() + line_b_start.y()) * 0.5,
316                (line_a_end.z() + line_b_start.z()) * 0.5,
317            );
318
319            let mid0_vec = mid_line0_end.clone() - mid_line0_start.clone();
320            let mid1_vec = mid_line1_end.clone() - mid_line1_start.clone();
321
322            if mid0_vec.length_squared() > mid1_vec.length_squared() {
323                (mid_line0_start, mid_line0_end)
324            } else {
325                (mid_line1_start, mid_line1_end)
326            }
327        } else {
328            Self::line_line_average(line0_start, line0_end, line1_start, line1_end)
329        }
330    }
331
332    /// Create line from projected points onto a base line
333    pub fn line_from_projected_points(
334        line_start: &Point,
335        line_end: &Point,
336        points: &[Point],
337    ) -> Option<(Point, Point)> {
338        if points.is_empty() {
339            return None;
340        }
341
342        let mut t_values: Vec<f64> = points
343            .iter()
344            .map(|p| Self::closest_point_to_line(p, line_start, line_end))
345            .collect();
346
347        t_values.sort_by(|a, b| a.partial_cmp(b).unwrap());
348
349        let output_start = Self::point_at_parameter(line_start, line_end, t_values[0]);
350        let output_end =
351            Self::point_at_parameter(line_start, line_end, t_values[t_values.len() - 1]);
352
353        if (t_values[0] - t_values[t_values.len() - 1]).abs() > Tolerance::ZERO_TOLERANCE {
354            Some((output_start, output_end))
355        } else {
356            None
357        }
358    }
359
360    /// Find closest distance and point from a point to this polyline
361    pub fn closest_distance_and_point(&self, point: &Point) -> (f64, usize, Point) {
362        let mut edge_id = 0;
363        let mut closest_distance = f64::MAX;
364        let mut best_t = 0.0;
365
366        for i in 0..self.segment_count() {
367            let t = Self::closest_point_to_line(point, &self.points[i], &self.points[i + 1]);
368            let point_on_segment =
369                Self::point_at_parameter(&self.points[i], &self.points[i + 1], t);
370            let distance = point.distance(&point_on_segment) as f64;
371
372            if distance < closest_distance {
373                closest_distance = distance;
374                edge_id = i;
375                best_t = t;
376            }
377
378            if closest_distance < Tolerance::ZERO_TOLERANCE {
379                break;
380            }
381        }
382
383        let closest_point =
384            Self::point_at_parameter(&self.points[edge_id], &self.points[edge_id + 1], best_t);
385        (closest_distance, edge_id, closest_point)
386    }
387
388    /// Check if polyline is closed (first and last points are the same)
389    pub fn is_closed(&self) -> bool {
390        if self.points.len() < 2 {
391            return false;
392        }
393        self.points
394            .first()
395            .unwrap()
396            .distance(self.points.last().unwrap())
397            < Tolerance::ZERO_TOLERANCE as f32
398    }
399
400    /// Calculate center point of polyline
401    pub fn center(&self) -> Point {
402        if self.points.is_empty() {
403            return Point::new(0.0, 0.0, 0.0);
404        }
405
406        let n = if self.is_closed() && self.points.len() > 1 {
407            self.points.len() - 1
408        } else {
409            self.points.len()
410        };
411
412        let mut sum_x = 0.0;
413        let mut sum_y = 0.0;
414        let mut sum_z = 0.0;
415
416        for i in 0..n {
417            sum_x += self.points[i].x();
418            sum_y += self.points[i].y();
419            sum_z += self.points[i].z();
420        }
421
422        Point::new(sum_x / n as f32, sum_y / n as f32, sum_z / n as f32)
423    }
424
425    /// Calculate center as vector
426    pub fn center_vec(&self) -> Vector {
427        let center = self.center();
428        Vector::new(center.x(), center.y(), center.z())
429    }
430
431    /// Get average plane from polyline points
432    pub fn get_average_plane(&self) -> (Point, Vector, Vector, Vector) {
433        let origin = self.center();
434
435        let x_axis = if self.points.len() >= 2 {
436            let mut x = self.points[1].clone() - self.points[0].clone();
437            x.normalize_self();
438            x
439        } else {
440            Vector::new(1.0, 0.0, 0.0)
441        };
442
443        let z_axis = self.average_normal();
444        let mut y_axis = z_axis.cross(&x_axis);
445        y_axis.normalize_self();
446
447        (origin, x_axis, y_axis, z_axis)
448    }
449
450    /// Get fast plane calculation from polyline
451    pub fn get_fast_plane(&self) -> (Point, Plane) {
452        let origin = if !self.points.is_empty() {
453            self.points[0].clone()
454        } else {
455            Point::new(0.0, 0.0, 0.0)
456        };
457
458        let average_normal = self.average_normal();
459        let plane = Plane::from_point_normal(origin.clone(), average_normal);
460        (origin, plane)
461    }
462
463    /// Calculate middle line between two line segments
464    pub fn get_middle_line(
465        line0_start: &Point,
466        line0_end: &Point,
467        line1_start: &Point,
468        line1_end: &Point,
469    ) -> (Point, Point) {
470        let p0 = Point::new(
471            (line0_start.x() + line1_start.x()) * 0.5,
472            (line0_start.y() + line1_start.y()) * 0.5,
473            (line0_start.z() + line1_start.z()) * 0.5,
474        );
475        let p1 = Point::new(
476            (line0_end.x() + line1_end.x()) * 0.5,
477            (line0_end.y() + line1_end.y()) * 0.5,
478            (line0_end.z() + line1_end.z()) * 0.5,
479        );
480        (p0, p1)
481    }
482
483    /// Extend line segment by specified distances at both ends
484    pub fn extend_line(
485        line_start: &mut Point,
486        line_end: &mut Point,
487        distance0: f64,
488        distance1: f64,
489    ) {
490        let mut v = line_end.clone() - line_start.clone();
491        v.normalize_self();
492
493        *line_start = line_start.clone() - (v.clone() * distance0 as f32);
494        *line_end = line_end.clone() + (v * distance1 as f32);
495    }
496
497    /// Scale line segment inward by specified distance
498    pub fn scale_line(line_start: &mut Point, line_end: &mut Point, distance: f64) {
499        let v = line_end.clone() - line_start.clone();
500        *line_start = line_start.clone() + (v.clone() * distance as f32);
501        *line_end = line_end.clone() - (v * distance as f32);
502    }
503
504    /// Extend polyline segment
505    pub fn extend_segment(
506        &mut self,
507        segment_id: usize,
508        dist0: f64,
509        dist1: f64,
510        proportion0: f64,
511        proportion1: f64,
512    ) {
513        if segment_id >= self.segment_count() {
514            return;
515        }
516
517        let mut p0 = self.points[segment_id].clone();
518        let mut p1 = self.points[segment_id + 1].clone();
519        let v = p1.clone() - p0.clone();
520
521        if proportion0 != 0.0 || proportion1 != 0.0 {
522            p0 -= v.clone() * proportion0 as f32;
523            p1 += v * proportion1 as f32;
524        } else {
525            let v_norm = v.normalize();
526            p0 -= v_norm.clone() * dist0 as f32;
527            p1 += v_norm * dist1 as f32;
528        }
529
530        self.points[segment_id] = p0;
531        self.points[segment_id + 1] = p1;
532
533        if self.is_closed() {
534            let len = self.points.len();
535            if segment_id == 0 {
536                let first = self.points[0].clone();
537                self.points[len - 1] = first;
538            } else if segment_id + 1 == len - 1 {
539                let last = self.points[len - 1].clone();
540                self.points[0] = last;
541            }
542        }
543    }
544
545    /// Extend segment equally on both ends (static utility)
546    pub fn extend_segment_equally_static(
547        segment_start: &mut Point,
548        segment_end: &mut Point,
549        dist: f64,
550        proportion: f64,
551    ) {
552        if dist == 0.0 && proportion == 0.0 {
553            return;
554        }
555
556        let v = segment_end.clone() - segment_start.clone();
557
558        if proportion != 0.0 {
559            *segment_start = segment_start.clone() - (v.clone() * proportion as f32);
560            *segment_end = segment_end.clone() + (v * proportion as f32);
561        } else {
562            let mut v_norm = v;
563            v_norm.normalize_self();
564            *segment_start = segment_start.clone() - (v_norm.clone() * dist as f32);
565            *segment_end = segment_end.clone() + (v_norm * dist as f32);
566        }
567    }
568
569    /// Extend polyline segment equally
570    pub fn extend_segment_equally(&mut self, segment_id: usize, dist: f64, proportion: f64) {
571        if segment_id >= self.segment_count() {
572            return;
573        }
574
575        // Extract points to avoid borrowing issues
576        let mut start = self.points[segment_id].clone();
577        let mut end = self.points[segment_id + 1].clone();
578        Self::extend_segment_equally_static(&mut start, &mut end, dist, proportion);
579        self.points[segment_id] = start;
580        self.points[segment_id + 1] = end;
581
582        if self.points.len() > 2 && self.is_closed() {
583            let len = self.points.len();
584            if segment_id == 0 {
585                self.points[len - 1] = self.points[0].clone();
586            } else if segment_id + 1 == len - 1 {
587                self.points[0] = self.points[len - 1].clone();
588            }
589        }
590    }
591
592    /// Move polyline by direction vector
593    pub fn move_by(&mut self, direction: &Vector) {
594        for point in &mut self.points {
595            *point += direction.clone();
596        }
597    }
598
599    /// Check if polyline is clockwise oriented
600    pub fn is_clockwise(&self, _plane: &Plane) -> bool {
601        if self.points.len() < 3 {
602            return false;
603        }
604
605        let mut sum = 0.0;
606        let n = if self.is_closed() {
607            self.points.len() - 1
608        } else {
609            self.points.len()
610        };
611
612        for i in 0..n {
613            let current = &self.points[i];
614            let next = &self.points[(i + 1) % n];
615            sum += (next.x() - current.x()) * (next.y() + current.y());
616        }
617
618        sum > 0.0
619    }
620
621    /// Flip polyline direction (reverse point order)
622    pub fn flip(&mut self) {
623        self.points.reverse();
624    }
625
626    /// Get convex/concave corners of polyline
627    pub fn get_convex_corners(&self) -> Vec<bool> {
628        if self.points.len() < 3 {
629            return Vec::new();
630        }
631
632        let closed = self.is_closed();
633        let normal = self.average_normal();
634        let n = if closed {
635            self.points.len() - 1
636        } else {
637            self.points.len()
638        };
639        let mut convex_corners = Vec::with_capacity(n);
640
641        for current in 0..n {
642            let prev = if current == 0 { n - 1 } else { current - 1 };
643            let next = if current == n - 1 { 0 } else { current + 1 };
644
645            let mut dir0 = self.points[current].clone() - self.points[prev].clone();
646            dir0.normalize_self();
647
648            let mut dir1 = self.points[next].clone() - self.points[current].clone();
649            dir1.normalize_self();
650
651            let mut cross = dir0.cross(&dir1);
652            cross.normalize_self();
653
654            let dot = cross.dot(&normal);
655            let is_convex = dot >= 0.0;
656            convex_corners.push(is_convex);
657        }
658
659        convex_corners
660    }
661
662    /// Interpolate between two polylines
663    pub fn tween_two_polylines(
664        polyline0: &Polyline,
665        polyline1: &Polyline,
666        weight: f64,
667    ) -> Polyline {
668        if polyline0.points.len() != polyline1.points.len() {
669            return polyline0.clone();
670        }
671
672        let mut result = Polyline::default();
673        result.points.reserve(polyline0.points.len());
674
675        for i in 0..polyline0.points.len() {
676            let diff = polyline1.points[i].clone() - polyline0.points[i].clone();
677            let interpolated = polyline0.points[i].clone() + (diff * weight as f32);
678            result.points.push(interpolated);
679        }
680
681        result
682    }
683
684    /// Calculate average normal from polyline points
685    fn average_normal(&self) -> Vector {
686        let len = self.points.len();
687        if len < 3 {
688            return Vector::new(0.0, 0.0, 1.0);
689        }
690
691        let closed = self.is_closed();
692        let n = if closed && len > 1 { len - 1 } else { len };
693
694        let mut average_normal = Vector::new(0.0, 0.0, 0.0);
695
696        for i in 0..n {
697            let prev = if i == 0 { n - 1 } else { i - 1 };
698            let next = (i + 1) % n;
699
700            let v1 = self.points[prev].clone() - self.points[i].clone();
701            let v2 = self.points[i].clone() - self.points[next].clone();
702            let cross = v1.cross(&v2);
703            average_normal += &cross;
704        }
705
706        average_normal.normalize_self();
707        average_normal
708    }
709}
710
711impl AddAssign<&Vector> for Polyline {
712    /// Translates all points in the polyline by a vector.
713    ///
714    /// # Arguments
715    ///
716    /// * `other` - The translation vector.
717    fn add_assign(&mut self, other: &Vector) {
718        for p in &mut self.points {
719            *p += other.clone();
720        }
721        // Update plane origin
722        self.plane = Plane::new(
723            self.plane.origin() + other.clone(),
724            self.plane.x_axis(),
725            self.plane.y_axis(),
726        );
727    }
728}
729
730impl Add<&Vector> for Polyline {
731    type Output = Polyline;
732
733    /// Translates the polyline by a vector and returns a new polyline.
734    ///
735    /// # Arguments
736    ///
737    /// * `other` - The translation vector.
738    fn add(self, other: &Vector) -> Polyline {
739        let mut result = self.clone();
740        result += other;
741        result
742    }
743}
744
745impl SubAssign<&Vector> for Polyline {
746    /// Translates all points in the polyline by the negative of a vector.
747    ///
748    /// # Arguments
749    ///
750    /// * `other` - The vector to subtract.
751    fn sub_assign(&mut self, other: &Vector) {
752        for p in &mut self.points {
753            *p -= other.clone();
754        }
755        // Update plane origin
756        self.plane = Plane::new(
757            self.plane.origin() - other.clone(),
758            self.plane.x_axis(),
759            self.plane.y_axis(),
760        );
761    }
762}
763
764impl Sub<&Vector> for Polyline {
765    type Output = Polyline;
766
767    /// Translates the polyline by the negative of a vector and returns a new polyline.
768    ///
769    /// # Arguments
770    ///
771    /// * `other` - The vector to subtract.
772    fn sub(self, other: &Vector) -> Polyline {
773        let mut result = self.clone();
774        result -= other;
775        result
776    }
777}
778
779impl fmt::Display for Polyline {
780    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
781        write!(
782            f,
783            "Polyline(guid={}, name={}, points={})",
784            self.guid,
785            self.name,
786            self.points.len()
787        )
788    }
789}
790
791#[cfg(test)]
792#[path = "polyline_test.rs"]
793mod polyline_test;