4 Commits 5a27b88ba3 ... 217bac7f20

Author SHA1 Message Date
  Kevin Lee 217bac7f20 More work around skew/rotation 3 months ago
  Kevin Lee 1c886bb325 Checkpoint 3 months ago
  Kevin Lee 98b2670b1b Refactoring 3 months ago
  Kevin Lee a4ac621288 Comments 3 months ago

BIN
samples/CircleRotate135.mlp


BIN
samples/CircleRotate270.mlp


BIN
samples/Circle2.mlp


BIN
samples/CircleScaleX.mlp


BIN
samples/Circle5.mlp


BIN
samples/CircleScaleY.mlp


BIN
samples/CircleSkewRightRotate45.mlp


BIN
samples/Circle4.mlp


BIN
samples/CircleSkewTopLeft.mlp


samples/Ellipse3.mlp → samples/EllipseAngle.mlp


BIN
samples/EllipseClosed.mlp


samples/Ellipse.mlp → samples/EllipseOpen.mlp


samples/EllipseRotated2.mlp → samples/EllipseRotated30.mlp


samples/EllipseRotated.mlp → samples/EllipseRotated45.mlp


samples/EllipseSkewed4.mlp → samples/EllipseSkewBottomRight.mlp


samples/EllipseSkewed3.mlp → samples/EllipseSkewLeftDown.mlp


samples/EllipseSkewed.mlp → samples/EllipseSkewRightDown.mlp


samples/EllipseSkewed2.mlp → samples/EllipseSkewTopLeft.mlp


samples/Rectangle3.mlp → samples/RectangleDrawn.mlp


BIN
samples/RectangleDrawnRotate140.mlp


BIN
samples/RectangleDrawnRotate20.mlp


BIN
samples/Circle3.mlp


BIN
samples/Rectangle2.mlp


BIN
samples/RectangleDrawnScaleY.mlp


BIN
samples/RectangleDrawnSkewRightUp.mlp


BIN
samples/RectangleDrawnSkewTopRight.mlp


BIN
samples/Rectangle4.mlp


BIN
samples/RectangleSkewTopRight.mlp


BIN
samples/RectangleSkewTopRightTranslated.mlp


+ 374 - 374
src/config/object.rs

@@ -1,374 +1,374 @@
-use std::path::PathBuf;
-
-use ezcad::{
-    array_of::ArrayOf,
-    layer::Layer,
-    objects::{
-        circle::Circle,
-        hatch::{Hatch, HatchFlag, HatchPattern, HatchSetting, Hatches},
-        rectangle::Rectangle,
-        Object, ObjectCore, Translate,
-    },
-    pen::Pen,
-    types::{Coordinate, ObjectType},
-};
-use log::{debug, error, warn};
-use rand::{seq::SliceRandom, thread_rng};
-use serde::{Deserialize, Serialize};
-
-use super::pen::PatternField;
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "PascalCase")]
-pub struct DeleteObjects {
-    layer: usize,
-    object: Option<usize>,
-}
-
-impl DeleteObjects {
-    pub fn delete(&self, layers: &mut ArrayOf<Layer>) {
-        debug!(
-            "Deleting layer #{} {}",
-            match &self.object {
-                Some(object) => format!("object #{}", object),
-                None => format!("all objects"),
-            },
-            self.layer,
-        );
-
-        let layer: &mut Layer = layers.get_mut(self.layer).expect("Invalid layer index");
-
-        match self.object {
-            Some(index) => {
-                assert!(index < layer.objects.len(), "Invalid object index");
-                layer.objects.remove(index);
-            }
-            None => layer.objects.clear(),
-        }
-    }
-}
-
-#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
-#[serde(rename_all = "PascalCase")]
-pub struct HatchConfig {
-    line_spacing: f64,
-
-    pen: Option<u32>,
-    count: Option<u32>,
-    edge_offset: Option<f64>,
-    start_offset: Option<f64>,
-    end_offset: Option<f64>,
-    angle: Option<f64>,
-    rotate_angle: Option<f64>,
-    line_reduction: Option<f64>,
-    loop_distance: Option<f64>,
-    loop_count: Option<u32>,
-
-    pattern: Option<HatchPattern>,
-    follow_edge_once: Option<bool>,
-    cross_hatch: Option<bool>,
-}
-
-impl From<HatchConfig> for HatchSetting {
-    fn from(value: HatchConfig) -> Self {
-        let mut flags: HatchFlag = match value.pattern {
-            Some(pattern) => pattern.into(),
-            None => HatchPattern::Directional.into(),
-        };
-
-        value
-            .follow_edge_once
-            .map(|x| flags.set_follow_edge_once(x.into()));
-        value.cross_hatch.map(|x| flags.set_cross_hatch(x.into()));
-
-        if value.start_offset.is_none() && value.end_offset.is_none() {
-            flags.set_average_distribute_line(1);
-        }
-
-        let mut ret = Self::default();
-
-        *ret.line_spacing = value.line_spacing;
-        value.pen.map(|x| *ret.pen = x.into());
-        value.count.map(|x| *ret.count = x.into());
-        value.edge_offset.map(|x| *ret.edge_offset = x.into());
-        value.start_offset.map(|x| *ret.start_offset = x.into());
-        value.end_offset.map(|x| *ret.end_offset = x.into());
-        value.angle.map(|x| *ret.angle = x.into());
-        value.rotate_angle.map(|x| *ret.rotate_angle = x.into());
-        value.line_reduction.map(|x| *ret.line_reduction = x.into());
-        value.loop_distance.map(|x| *ret.loop_distance = x.into());
-        value.loop_count.map(|x| *ret.loop_count = x.into());
-
-        *ret.flags = flags;
-        *ret.enabled = true.into();
-
-        ret
-    }
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "PascalCase")]
-pub struct ArrayConfig {
-    columns: usize,
-    rows: usize,
-    spacing: f64,
-    randomize_order: bool,
-    starting_pen: usize,
-    pattern_x: Option<PatternField>,
-    pattern_y: Option<PatternField>,
-}
-
-#[derive(Debug, Serialize, Deserialize, strum::Display)]
-#[serde(rename_all = "PascalCase")]
-pub enum InputObject {
-    #[serde(rename_all = "PascalCase")]
-    Rectangle {
-        width: f64,
-        height: f64,
-        round_corner: Option<f64>,
-    },
-    #[serde(rename_all = "PascalCase")]
-    Circle { radius: f64 },
-    #[serde(rename_all = "PascalCase")]
-    Import { path: PathBuf },
-    #[serde(rename_all = "PascalCase")]
-    Existing { layer: usize, object: usize },
-}
-
-impl InputObject {
-    fn new(&self, layers: &ArrayOf<Layer>) -> Object {
-        match self {
-            InputObject::Rectangle {
-                width,
-                height,
-                round_corner,
-            } => Object::Rectangle(Rectangle {
-                drawn_corner_a: Coordinate::from((-width / 2.0, -height / 2.0)).into(),
-                drawn_corner_b: Coordinate::from((width / 2.0, height / 2.0)).into(),
-                round_bottom_left: round_corner.unwrap_or(0.0).into(),
-                round_bottom_right: round_corner.unwrap_or(0.0).into(),
-                round_top_left: round_corner.unwrap_or(0.0).into(),
-                round_top_right: round_corner.unwrap_or(0.0).into(),
-                ..Default::default()
-            }),
-            InputObject::Circle { radius } => Object::Circle(Circle {
-                radius: (*radius).into(),
-                ..Default::default()
-            }),
-            InputObject::Import { path } => Object::read_from_file(path),
-            InputObject::Existing { layer, object } => {
-                let layer: &Layer = layers.get(*layer).expect("Invalid layer index");
-                layer
-                    .objects
-                    .get(*object)
-                    .expect("Invalid object index")
-                    .clone()
-            }
-        }
-    }
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(rename_all = "PascalCase")]
-pub struct ObjectOperation {
-    input: InputObject,
-    z: Option<f64>,
-    origin: Option<Coordinate>,
-    pen: Option<u32>,
-    layer: Option<usize>,
-    array: Option<ArrayConfig>,
-    hatch: Option<HatchConfig>,
-    export: Option<PathBuf>,
-    replace_object: Option<usize>,
-}
-
-impl ObjectOperation {
-    pub fn process(&self, pens: &mut Vec<Pen>, layers: &mut ArrayOf<Layer>) {
-        debug!("Begin processing of object {:?}", self.input);
-
-        let mut object: Object = self.input.new(layers);
-
-        // Process basic transformation
-        if self.origin.is_some() || self.z.is_some() {
-            debug!(
-                "Translating object to origin {:?} and Z {:?}",
-                self.origin, self.z
-            );
-            object.move_absolute(self.origin, self.z);
-        }
-
-        self.pen.map(|pen| {
-            if self.array.is_some() {
-                warn!("Ignoring pen setting as values will be overridden by array setting");
-            } else {
-                assert!(pen < pens.len().try_into().unwrap(), "Invalid pen index");
-                debug!("Setting object pen to #{}", pen);
-                object.set_pen(pen);
-            }
-        });
-
-        // Process conversion to hatch object
-        let object = self.hatch.as_ref().map_or(object.clone(), |hatch| {
-            if hatch.pen.is_some() && self.array.is_some() {
-                warn!("Ignoring hatch pen setting as values will be overridden by array setting");
-            }
-            let hatch_setting: HatchSetting = (*hatch).into();
-
-            match object {
-                Object::Curve(_) | Object::Point(_) => {
-                    error!("Points, lines, and curves cannot be hatched");
-                    object
-                }
-                Object::Rectangle(_)
-                | Object::Circle(_)
-                | Object::Ellipse(_)
-                | Object::Polygon(_) => {
-                    debug!("Converting to hatch object");
-                    // Build new hatch object (no hatch lines)
-                    Object::Hatch(Hatch {
-                        core: ObjectCore {
-                            origin: object.core_mut().origin,
-                            z: object.core_mut().z,
-                            ..ObjectCore::default(ObjectType::Hatch)
-                        },
-                        outline: vec![object].into(),
-                        legacy_setting: vec![hatch_setting.clone()].into(),
-                        _unknown_1: vec![0; 960].into(),
-                        hatch_settings: vec![hatch_setting.clone()].into(),
-                        hatches: Some(Hatches {
-                            core: ObjectCore::default(ObjectType::HatchLine),
-                            hatch_lines: vec![].into(),
-                        }),
-                    })
-                }
-                Object::Hatch(hatch) => {
-                    debug!("Modifying existing hatch settings");
-                    // Modify existing hatch (delete existing hatch lines)
-                    Object::Hatch(Hatch {
-                        legacy_setting: vec![hatch_setting.clone()].into(),
-                        hatch_settings: vec![hatch_setting.clone()].into(),
-                        hatches: Some(Hatches {
-                            core: ObjectCore::default(ObjectType::HatchLine),
-                            hatch_lines: vec![].into(),
-                        }),
-                        ..hatch
-                    })
-                }
-            }
-        });
-
-        // Process array generation of object
-        let new_objects = self.array.as_ref().map_or_else(
-            || vec![object.clone()],
-            |array| {
-                let mut new_obj: Vec<Object> = vec![];
-
-                let bottom_left: Coordinate = Coordinate {
-                    x: (array.columns - 1) as f64 * array.spacing / -2.0,
-                    y: (array.rows - 1) as f64 * array.spacing / -2.0,
-                };
-
-                // Generate objects
-                for y in 0..array.rows {
-                    for x in 0..array.columns {
-                        let delta: Coordinate = bottom_left
-                            + Coordinate {
-                                x: array.spacing * x as f64,
-                                y: array.spacing * y as f64,
-                            };
-
-                        let pen: usize = array.starting_pen + y * array.columns + x;
-
-                        let mut object: Object = object.clone();
-                        object.move_relative(Some(delta), None);
-                        object.set_pen(pen.try_into().unwrap());
-
-                        debug!(
-                            "Adding new object at {} with pen #{}",
-                            object.core_mut().origin,
-                            pen
-                        );
-                        new_obj.push(object);
-                    }
-                }
-
-                // Generate pens
-                match &array.pattern_y {
-                    None => {
-                        if let Some(pattern_x) = &array.pattern_x {
-                            pattern_x.pattern(
-                                &mut pens
-                                    .iter_mut()
-                                    .enumerate()
-                                    .skip(array.starting_pen)
-                                    .take(array.columns * array.rows),
-                            );
-                        }
-                    }
-                    Some(pattern_y) => {
-                        for x in 0..array.columns {
-                            pattern_y.pattern(
-                                &mut pens
-                                    .iter_mut()
-                                    .enumerate()
-                                    .skip(x + array.starting_pen)
-                                    .step_by(array.columns)
-                                    .take(array.rows),
-                            );
-                        }
-                        if let Some(pattern_x) = &array.pattern_x {
-                            for y in 0..array.rows {
-                                pattern_x.pattern(
-                                    &mut pens
-                                        .iter_mut()
-                                        .enumerate()
-                                        .skip(array.starting_pen)
-                                        .skip(y * array.columns)
-                                        .take(array.columns),
-                                )
-                            }
-                        }
-                    }
-                }
-
-                if array.randomize_order {
-                    debug!("Randomizing draw order of array objects");
-                    new_obj.shuffle(&mut thread_rng());
-                }
-
-                new_obj
-            },
-        );
-
-        let layer_id: usize = self.layer.unwrap_or(0);
-        let layer: &mut Layer = layers.get_mut(layer_id).expect("Invalid layer index");
-
-        // Either export the object, replace an existing object, or append if neither
-        if let Some(path) = &self.export {
-            if new_objects.len() > 1 {
-                warn!(
-                    "Exporting only object #0 from layer #{} in list of objects",
-                    layer_id
-                );
-            } else {
-                debug!(
-                    "Exporting object {} in layer #{} to '{}'",
-                    new_objects[0],
-                    layer_id,
-                    path.to_string_lossy()
-                );
-            }
-            new_objects[0].write_to_file(path);
-        } else if let Some(object) = self.replace_object {
-            assert!(object < layer.objects.len(), "Invalid object index");
-            debug!(
-                "Replacing object #{} in layer #{} with new objects",
-                object, layer_id
-            );
-            layer.objects.splice(object..=object, new_objects);
-        } else {
-            debug!("Extending layer #{} with new objects", layer_id);
-            layer.objects.extend(new_objects);
-        }
-    }
-}
+use std::path::PathBuf;
+
+use ezcad::{
+    array_of::ArrayOf,
+    layer::Layer,
+    objects::{
+        circle::Circle,
+        hatch::{Hatch, HatchFlag, HatchPattern, HatchSetting, Hatches},
+        rectangle::Rectangle,
+        Object, ObjectCore, Translate,
+    },
+    pen::Pen,
+    types::{ObjectType, Point},
+};
+use log::{debug, error, warn};
+use rand::{seq::SliceRandom, thread_rng};
+use serde::{Deserialize, Serialize};
+
+use super::pen::PatternField;
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(rename_all = "PascalCase")]
+pub struct DeleteObjects {
+    layer: usize,
+    object: Option<usize>,
+}
+
+impl DeleteObjects {
+    pub fn delete(&self, layers: &mut ArrayOf<Layer>) {
+        debug!(
+            "Deleting layer #{} {}",
+            match &self.object {
+                Some(object) => format!("object #{}", object),
+                None => format!("all objects"),
+            },
+            self.layer,
+        );
+
+        let layer: &mut Layer = layers.get_mut(self.layer).expect("Invalid layer index");
+
+        match self.object {
+            Some(index) => {
+                assert!(index < layer.objects.len(), "Invalid object index");
+                layer.objects.remove(index);
+            }
+            None => layer.objects.clear(),
+        }
+    }
+}
+
+#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
+#[serde(rename_all = "PascalCase")]
+pub struct HatchConfig {
+    line_spacing: f64,
+
+    pen: Option<u32>,
+    count: Option<u32>,
+    edge_offset: Option<f64>,
+    start_offset: Option<f64>,
+    end_offset: Option<f64>,
+    angle: Option<f64>,
+    rotate_angle: Option<f64>,
+    line_reduction: Option<f64>,
+    loop_distance: Option<f64>,
+    loop_count: Option<u32>,
+
+    pattern: Option<HatchPattern>,
+    follow_edge_once: Option<bool>,
+    cross_hatch: Option<bool>,
+}
+
+impl From<HatchConfig> for HatchSetting {
+    fn from(value: HatchConfig) -> Self {
+        let mut flags: HatchFlag = match value.pattern {
+            Some(pattern) => pattern.into(),
+            None => HatchPattern::Directional.into(),
+        };
+
+        value
+            .follow_edge_once
+            .map(|x| flags.set_follow_edge_once(x.into()));
+        value.cross_hatch.map(|x| flags.set_cross_hatch(x.into()));
+
+        if value.start_offset.is_none() && value.end_offset.is_none() {
+            flags.set_average_distribute_line(1);
+        }
+
+        let mut ret = Self::default();
+
+        *ret.line_spacing = value.line_spacing;
+        value.pen.map(|x| *ret.pen = x.into());
+        value.count.map(|x| *ret.count = x.into());
+        value.edge_offset.map(|x| *ret.edge_offset = x.into());
+        value.start_offset.map(|x| *ret.start_offset = x.into());
+        value.end_offset.map(|x| *ret.end_offset = x.into());
+        value.angle.map(|x| *ret.angle = x.into());
+        value.rotate_angle.map(|x| *ret.rotate_angle = x.into());
+        value.line_reduction.map(|x| *ret.line_reduction = x.into());
+        value.loop_distance.map(|x| *ret.loop_distance = x.into());
+        value.loop_count.map(|x| *ret.loop_count = x.into());
+
+        *ret.flags = flags;
+        *ret.enabled = true.into();
+
+        ret
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(rename_all = "PascalCase")]
+pub struct ArrayConfig {
+    columns: usize,
+    rows: usize,
+    spacing: f64,
+    randomize_order: bool,
+    starting_pen: usize,
+    pattern_x: Option<PatternField>,
+    pattern_y: Option<PatternField>,
+}
+
+#[derive(Debug, Serialize, Deserialize, strum::Display)]
+#[serde(rename_all = "PascalCase")]
+pub enum InputObject {
+    #[serde(rename_all = "PascalCase")]
+    Rectangle {
+        width: f64,
+        height: f64,
+        round_corner: Option<f64>,
+    },
+    #[serde(rename_all = "PascalCase")]
+    Circle { radius: f64 },
+    #[serde(rename_all = "PascalCase")]
+    Import { path: PathBuf },
+    #[serde(rename_all = "PascalCase")]
+    Existing { layer: usize, object: usize },
+}
+
+impl InputObject {
+    fn new(&self, layers: &ArrayOf<Layer>) -> Object {
+        match self {
+            InputObject::Rectangle {
+                width,
+                height,
+                round_corner,
+            } => Object::Rectangle(Rectangle {
+                drawn_corner_a: Point::from((-width / 2.0, -height / 2.0)).into(),
+                drawn_corner_b: Point::from((width / 2.0, height / 2.0)).into(),
+                round_bottom_left: round_corner.unwrap_or(0.0).into(),
+                round_bottom_right: round_corner.unwrap_or(0.0).into(),
+                round_top_left: round_corner.unwrap_or(0.0).into(),
+                round_top_right: round_corner.unwrap_or(0.0).into(),
+                ..Default::default()
+            }),
+            InputObject::Circle { radius } => Object::Circle(Circle {
+                radius: (*radius).into(),
+                ..Default::default()
+            }),
+            InputObject::Import { path } => Object::read_from_file(path),
+            InputObject::Existing { layer, object } => {
+                let layer: &Layer = layers.get(*layer).expect("Invalid layer index");
+                layer
+                    .objects
+                    .get(*object)
+                    .expect("Invalid object index")
+                    .clone()
+            }
+        }
+    }
+}
+
+#[derive(Debug, Serialize, Deserialize)]
+#[serde(rename_all = "PascalCase")]
+pub struct ObjectOperation {
+    input: InputObject,
+    z: Option<f64>,
+    origin: Option<Point>,
+    pen: Option<u32>,
+    layer: Option<usize>,
+    array: Option<ArrayConfig>,
+    hatch: Option<HatchConfig>,
+    export: Option<PathBuf>,
+    replace_object: Option<usize>,
+}
+
+impl ObjectOperation {
+    pub fn process(&self, pens: &mut Vec<Pen>, layers: &mut ArrayOf<Layer>) {
+        debug!("Begin processing of object {:?}", self.input);
+
+        let mut object: Object = self.input.new(layers);
+
+        // Process basic transformation
+        if self.origin.is_some() || self.z.is_some() {
+            debug!(
+                "Translating object to origin {:?} and Z {:?}",
+                self.origin, self.z
+            );
+            object.move_absolute(self.origin, self.z);
+        }
+
+        self.pen.map(|pen| {
+            if self.array.is_some() {
+                warn!("Ignoring pen setting as values will be overridden by array setting");
+            } else {
+                assert!(pen < pens.len().try_into().unwrap(), "Invalid pen index");
+                debug!("Setting object pen to #{}", pen);
+                object.set_pen(pen);
+            }
+        });
+
+        // Process conversion to hatch object
+        let object = self.hatch.as_ref().map_or(object.clone(), |hatch| {
+            if hatch.pen.is_some() && self.array.is_some() {
+                warn!("Ignoring hatch pen setting as values will be overridden by array setting");
+            }
+            let hatch_setting: HatchSetting = (*hatch).into();
+
+            match object {
+                Object::Curve(_) | Object::Point(_) => {
+                    error!("Points, lines, and curves cannot be hatched");
+                    object
+                }
+                Object::Rectangle(_)
+                | Object::Circle(_)
+                | Object::Ellipse(_)
+                | Object::Polygon(_) => {
+                    debug!("Converting to hatch object");
+                    // Build new hatch object (no hatch lines)
+                    Object::Hatch(Hatch {
+                        core: ObjectCore {
+                            origin: object.core_mut().origin,
+                            z: object.core_mut().z,
+                            ..ObjectCore::default(ObjectType::Hatch)
+                        },
+                        outline: vec![object].into(),
+                        legacy_setting: vec![hatch_setting.clone()].into(),
+                        _unknown_1: vec![0; 960].into(),
+                        hatch_settings: vec![hatch_setting.clone()].into(),
+                        hatches: Some(Hatches {
+                            core: ObjectCore::default(ObjectType::HatchLine),
+                            hatch_lines: vec![].into(),
+                        }),
+                    })
+                }
+                Object::Hatch(hatch) => {
+                    debug!("Modifying existing hatch settings");
+                    // Modify existing hatch (delete existing hatch lines)
+                    Object::Hatch(Hatch {
+                        legacy_setting: vec![hatch_setting.clone()].into(),
+                        hatch_settings: vec![hatch_setting.clone()].into(),
+                        hatches: Some(Hatches {
+                            core: ObjectCore::default(ObjectType::HatchLine),
+                            hatch_lines: vec![].into(),
+                        }),
+                        ..hatch
+                    })
+                }
+            }
+        });
+
+        // Process array generation of object
+        let new_objects = self.array.as_ref().map_or_else(
+            || vec![object.clone()],
+            |array| {
+                let mut new_obj: Vec<Object> = vec![];
+
+                let bottom_left: Point = Point {
+                    x: (array.columns - 1) as f64 * array.spacing / -2.0,
+                    y: (array.rows - 1) as f64 * array.spacing / -2.0,
+                };
+
+                // Generate objects
+                for y in 0..array.rows {
+                    for x in 0..array.columns {
+                        let delta: Point = bottom_left
+                            + Point {
+                                x: array.spacing * x as f64,
+                                y: array.spacing * y as f64,
+                            };
+
+                        let pen: usize = array.starting_pen + y * array.columns + x;
+
+                        let mut object: Object = object.clone();
+                        object.move_relative(Some(delta), None);
+                        object.set_pen(pen.try_into().unwrap());
+
+                        debug!(
+                            "Adding new object at {} with pen #{}",
+                            object.core_mut().origin,
+                            pen
+                        );
+                        new_obj.push(object);
+                    }
+                }
+
+                // Generate pens
+                match &array.pattern_y {
+                    None => {
+                        if let Some(pattern_x) = &array.pattern_x {
+                            pattern_x.pattern(
+                                &mut pens
+                                    .iter_mut()
+                                    .enumerate()
+                                    .skip(array.starting_pen)
+                                    .take(array.columns * array.rows),
+                            );
+                        }
+                    }
+                    Some(pattern_y) => {
+                        for x in 0..array.columns {
+                            pattern_y.pattern(
+                                &mut pens
+                                    .iter_mut()
+                                    .enumerate()
+                                    .skip(x + array.starting_pen)
+                                    .step_by(array.columns)
+                                    .take(array.rows),
+                            );
+                        }
+                        if let Some(pattern_x) = &array.pattern_x {
+                            for y in 0..array.rows {
+                                pattern_x.pattern(
+                                    &mut pens
+                                        .iter_mut()
+                                        .enumerate()
+                                        .skip(array.starting_pen)
+                                        .skip(y * array.columns)
+                                        .take(array.columns),
+                                )
+                            }
+                        }
+                    }
+                }
+
+                if array.randomize_order {
+                    debug!("Randomizing draw order of array objects");
+                    new_obj.shuffle(&mut thread_rng());
+                }
+
+                new_obj
+            },
+        );
+
+        let layer_id: usize = self.layer.unwrap_or(0);
+        let layer: &mut Layer = layers.get_mut(layer_id).expect("Invalid layer index");
+
+        // Either export the object, replace an existing object, or append if neither
+        if let Some(path) = &self.export {
+            if new_objects.len() > 1 {
+                warn!(
+                    "Exporting only object #0 from layer #{} in list of objects",
+                    layer_id
+                );
+            } else {
+                debug!(
+                    "Exporting object {} in layer #{} to '{}'",
+                    new_objects[0],
+                    layer_id,
+                    path.to_string_lossy()
+                );
+            }
+            new_objects[0].write_to_file(path);
+        } else if let Some(object) = self.replace_object {
+            assert!(object < layer.objects.len(), "Invalid object index");
+            debug!(
+                "Replacing object #{} in layer #{} with new objects",
+                object, layer_id
+            );
+            layer.objects.splice(object..=object, new_objects);
+        } else {
+            debug!("Extending layer #{} with new objects", layer_id);
+            layer.objects.extend(new_objects);
+        }
+    }
+}

+ 22 - 9
src/ezcad/objects/circle.rs

@@ -2,10 +2,11 @@ use std::fmt::{Debug, Display};
 
 use binrw::{BinRead, BinWrite};
 use diff::Diff;
+use log::warn;
 
 use crate::{
     field_of::FieldOf,
-    types::{Coordinate, ObjectType, F64, U32},
+    types::{ObjectType, Point, F64, U32},
 };
 
 use super::{ObjectCore, ObjectModifier, Translate};
@@ -17,7 +18,7 @@ use super::{ObjectCore, ObjectModifier, Translate};
 pub struct Circle {
     pub core: ObjectCore,
     #[brw(magic(6u32))] // Number of following fields in struct
-    pub drawn_origin: FieldOf<Coordinate>,
+    pub drawn_origin: FieldOf<Point>,
     pub radius: F64,
     pub start_angle: F64, // Radians
     pub clockwise: U32,
@@ -26,13 +27,25 @@ pub struct Circle {
 
 impl Display for Circle {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let (origin, width, height) = self.modifier.corrected(
+            *self.drawn_origin - Point::from(*self.radius),
+            *self.drawn_origin + Point::from(*self.radius),
+        );
+
+        if format!("{}", origin) != format!("{}", *self.core.origin) {
+            warn!(
+                "Origin mismatch! Core: {}, Calculated: {}",
+                *self.core.origin, origin
+            );
+        }
+
         write!(
             f,
             "{}, Origin: {}, Width : {:.2}, Height: {:.2}, Start Radian: {:.2}, Clockwise: {}",
             self.core,
-            self.modifier.correction() + *self.drawn_origin * self.modifier.scale(),
-            *self.radius * self.modifier.modifiers.x_scale * 2.0,
-            *self.radius * self.modifier.modifiers.y_scale * 2.0,
+            origin,
+            width,
+            height,
             *self.start_angle,
             *self.clockwise != 0,
         )
@@ -43,7 +56,7 @@ impl Default for Circle {
     fn default() -> Self {
         Self {
             core: ObjectCore::default(ObjectType::Circle),
-            drawn_origin: Coordinate { x: 0.0, y: 0.0 }.into(),
+            drawn_origin: Point { x: 0.0, y: 0.0 }.into(),
             radius: 0.0.into(),
             start_angle: 0.0.into(),
             clockwise: 0.into(),
@@ -55,15 +68,15 @@ impl Default for Circle {
 // origin_x = x_correction + drawn_x * x_scale
 // x_correction = origin_x - drawn_x * x_scale
 impl Translate for Circle {
-    fn move_absolute(&mut self, origin: Option<Coordinate>, z: Option<f64>) {
+    fn move_absolute(&mut self, origin: Option<Point>, z: Option<f64>) {
         origin.map(|origin| {
-            let delta: Coordinate = origin - *self.core.origin;
+            let delta: Point = origin - *self.core.origin;
             self.modifier.move_relative(delta);
         });
         self.core.move_absolute(origin, z);
     }
 
-    fn move_relative(&mut self, delta: Option<Coordinate>, z: Option<f64>) {
+    fn move_relative(&mut self, delta: Option<Point>, z: Option<f64>) {
         delta.map(|delta| {
             self.modifier.move_relative(delta);
         });

+ 8 - 8
src/ezcad/objects/ellipse.rs

@@ -6,7 +6,7 @@ use log::warn;
 
 use crate::{
     field_of::FieldOf,
-    types::{Coordinate, ObjectType, F64, U32},
+    types::{ObjectType, Point, F64, U32},
 };
 
 use super::{ObjectCore, ObjectModifier, Translate};
@@ -19,8 +19,8 @@ pub struct Ellipse {
     pub core: ObjectCore,
     #[brw(magic(8u32))] // Number of following fields in struct
     pub clockwise: U32,
-    pub drawn_corner_a: FieldOf<Coordinate>,
-    pub drawn_corner_b: FieldOf<Coordinate>,
+    pub drawn_corner_a: FieldOf<Point>,
+    pub drawn_corner_b: FieldOf<Point>,
     pub start_angle: F64, // Radians
     pub end_angle: F64,   // Radians
     pub modifier: ObjectModifier,
@@ -61,8 +61,8 @@ impl Default for Ellipse {
         Self {
             core: ObjectCore::default(ObjectType::Ellipse),
             clockwise: 0.into(),
-            drawn_corner_a: Coordinate { x: 0.0, y: 0.0 }.into(),
-            drawn_corner_b: Coordinate { x: 0.0, y: 0.0 }.into(),
+            drawn_corner_a: Point { x: 0.0, y: 0.0 }.into(),
+            drawn_corner_b: Point { x: 0.0, y: 0.0 }.into(),
             start_angle: 0.0.into(),
             end_angle: 0.0.into(),
             modifier: ObjectModifier::default(),
@@ -72,15 +72,15 @@ impl Default for Ellipse {
 }
 
 impl Translate for Ellipse {
-    fn move_absolute(&mut self, origin: Option<Coordinate>, z: Option<f64>) {
+    fn move_absolute(&mut self, origin: Option<Point>, z: Option<f64>) {
         origin.map(|origin| {
-            let delta: Coordinate = origin - *self.core.origin;
+            let delta: Point = origin - *self.core.origin;
             self.modifier.move_relative(delta);
         });
         self.core.move_absolute(origin, z);
     }
 
-    fn move_relative(&mut self, delta: Option<Coordinate>, z: Option<f64>) {
+    fn move_relative(&mut self, delta: Option<Point>, z: Option<f64>) {
         delta.map(|delta| {
             self.modifier.move_relative(delta);
         });

+ 484 - 484
src/ezcad/objects/hatch.rs

@@ -1,484 +1,484 @@
-use std::fmt::{Debug, Display};
-
-use crate::{
-    array_of::ArrayOf,
-    field_of::FieldOf,
-    types::{Coordinate, Field, F64, U32},
-};
-use binrw::{binrw, BinRead, BinWrite};
-use diff::Diff;
-use modular_bitfield::{
-    bitfield,
-    specifiers::{B1, B18},
-};
-use serde::{Deserialize, Serialize};
-
-use super::{line::Lines, Object, ObjectCore, Translate};
-
-#[bitfield(bits = 32)]
-#[derive(BinRead, BinWrite, Copy, Clone, Debug, Default, Diff, PartialEq)]
-#[diff(attr(
-    #[derive(Debug, PartialEq)]
-))]
-#[br(map = Self::from_bytes)]
-#[bw(map = |&x| Self::into_bytes(x))]
-pub struct HatchFlag {
-    pub all_calc: B1,
-    pub follow_edge_once: B1,
-    pub continuous_pattern: B1, // Hatch type 3 when combined with bidirectional_pattern
-    pub bidirectional_pattern: B1, // Hatch type 1
-    pub ring_pattern: B1,       // Hatch type 2
-    #[skip]
-    __: B1,
-    pub auto_rotate_hatch_angle: B1,
-    pub average_distribute_line: B1,
-    #[skip]
-    __: B1,
-    pub gong_pattern: B1, // Hatch type 4 when combined with bidirectional_pattern
-    pub cross_hatch: B1,
-    pub background_pattern: B1, // Hatch type 5, must set all_calc as well
-    pub fill_pattern: B1, // Hatch type 6 when combined with continuous_pattern and bidirectional_pattern
-    pub zigzag_pattern: B1, // Hatch type 7
-    #[skip]
-    __: B18,
-}
-
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, strum::Display)]
-pub enum HatchPattern {
-    Directional,
-    Bidirectional,
-    Ring,
-    Continuous,
-    Gong,
-    Background,
-    Fill,
-    Zigzag,
-}
-
-impl From<HatchPattern> for HatchFlag {
-    fn from(value: HatchPattern) -> Self {
-        let mut ret = Self::new();
-        match value {
-            HatchPattern::Directional => (),
-            HatchPattern::Bidirectional => ret.set_bidirectional_pattern(1),
-            HatchPattern::Ring => ret.set_ring_pattern(1),
-            HatchPattern::Continuous => {
-                ret.set_bidirectional_pattern(1);
-                ret.set_continuous_pattern(1);
-            }
-            HatchPattern::Gong => {
-                ret.set_bidirectional_pattern(1);
-                ret.set_gong_pattern(1);
-            }
-            HatchPattern::Background => {
-                ret.set_background_pattern(1);
-                ret.set_all_calc(1);
-            }
-            HatchPattern::Fill => ret.set_fill_pattern(1),
-            HatchPattern::Zigzag => ret.set_zigzag_pattern(1),
-        }
-        ret
-    }
-}
-
-impl From<HatchFlag> for HatchPattern {
-    fn from(value: HatchFlag) -> Self {
-        if value.ring_pattern() != 0 {
-            HatchPattern::Ring
-        } else if value.background_pattern() != 0 {
-            HatchPattern::Background
-        } else if value.fill_pattern() != 0 {
-            HatchPattern::Fill
-        } else if value.zigzag_pattern() != 0 {
-            HatchPattern::Zigzag
-        } else if value.gong_pattern() != 0 {
-            HatchPattern::Gong
-        } else if value.bidirectional_pattern() != 0 {
-            if value.continuous_pattern() != 0 {
-                HatchPattern::Continuous
-            } else {
-                HatchPattern::Bidirectional
-            }
-        } else {
-            HatchPattern::Directional
-        }
-    }
-}
-
-#[cfg_attr(feature = "default-debug", derive(Debug))]
-#[derive(BinRead, BinWrite, Clone, Diff, PartialEq)]
-#[diff(attr(
-    #[derive(Debug, PartialEq)]
-))]
-// #[brw(magic(46u32))] // Number of fields in this struct
-#[brw(magic(47u32))] // Number of fields in this struct
-pub struct LegacyHatchSetting {
-    pub mark_contour: U32,
-    pub hatch_0_enable: U32,
-    pub hatch_0_pen: U32,
-    pub hatch_0_flags: FieldOf<HatchFlag>,
-    pub hatch_0_edge_offset: F64,
-    pub hatch_0_line_spacing: F64,
-    pub hatch_0_start_offset: F64,
-    pub hatch_0_end_offset: F64,
-    pub hatch_0_angle: F64,
-    pub hatch_1_enable: U32,
-    pub hatch_1_pen: U32,
-    pub hatch_1_flags: FieldOf<HatchFlag>,
-    pub hatch_1_edge_offset: F64,
-    pub hatch_1_line_spacing: F64,
-    pub hatch_1_start_offset: F64,
-    pub hatch_1_end_offset: F64,
-    pub hatch_1_angle: F64,
-    pub any_hatch_enabled: U32,
-    pub hatch_0_rotate_angle: F64,
-    pub hatch_1_rotate_angle: F64,
-    pub hatch_2_enable: U32,
-    pub hatch_2_pen: U32,
-    pub hatch_2_flags: FieldOf<HatchFlag>,
-    pub hatch_2_edge_offset: F64,
-    pub hatch_2_line_spacing: F64,
-    pub hatch_2_start_offset: F64,
-    pub hatch_2_end_offset: F64,
-    pub hatch_2_angle: F64,
-    pub hatch_2_rotate_angle: F64,
-    pub hatch_0_line_reduction: F64,
-    pub hatch_1_line_reduction: F64,
-    pub hatch_2_line_reduction: F64,
-    pub hatch_0_loop_count: U32,
-    pub hatch_1_loop_count: U32,
-    pub hatch_2_loop_count: U32,
-    pub hatch_0_loop_distance: F64,
-    pub hatch_1_loop_distance: F64,
-    pub hatch_2_loop_distance: F64,
-    pub _unknown_1: [Field; 3],
-    pub contour_priority: U32,
-    pub hatch_0_count: U32,
-    pub hatch_1_count: U32,
-    pub hatch_2_count: U32,
-}
-
-// Custom Debug implementation to only print known fields
-#[cfg(not(feature = "default-debug"))]
-impl Debug for LegacyHatchSetting {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("LegacyHatchSetting")
-            .field("mark_contour", &self.mark_contour)
-            .field("hatch_0_enable", &self.hatch_0_enable)
-            .field("hatch_0_pen", &self.hatch_0_pen)
-            .field("hatch_0_flags", &self.hatch_0_flags)
-            .field("hatch_0_edge_offset", &self.hatch_0_edge_offset)
-            .field("hatch_0_line_spacing", &self.hatch_0_line_spacing)
-            .field("hatch_0_start_offset", &self.hatch_0_start_offset)
-            .field("hatch_0_end_offset", &self.hatch_0_end_offset)
-            .field("hatch_0_angle", &self.hatch_0_angle)
-            .field("hatch_1_enable", &self.hatch_1_enable)
-            .field("hatch_1_pen", &self.hatch_1_pen)
-            .field("hatch_1_flags", &self.hatch_1_flags)
-            .field("hatch_1_edge_offset", &self.hatch_1_edge_offset)
-            .field("hatch_1_line_spacing", &self.hatch_1_line_spacing)
-            .field("hatch_1_start_offset", &self.hatch_1_start_offset)
-            .field("hatch_1_end_offset", &self.hatch_1_end_offset)
-            .field("hatch_1_angle", &self.hatch_1_angle)
-            .field("any_hatch_enabled", &self.any_hatch_enabled)
-            .field("hatch_0_auto_rotate_angle", &self.hatch_0_rotate_angle)
-            .field("hatch_1_auto_rotate_angle", &self.hatch_1_rotate_angle)
-            .field("hatch_2_enable", &self.hatch_2_enable)
-            .field("hatch_2_pen", &self.hatch_2_pen)
-            .field("hatch_2_flags", &self.hatch_2_flags)
-            .field("hatch_2_edge_offset", &self.hatch_2_edge_offset)
-            .field("hatch_2_line_spacing", &self.hatch_2_line_spacing)
-            .field("hatch_2_start_offset", &self.hatch_2_start_offset)
-            .field("hatch_2_end_offset", &self.hatch_2_end_offset)
-            .field("hatch_2_angle", &self.hatch_2_angle)
-            .field("hatch_2_auto_rotate_angle", &self.hatch_2_rotate_angle)
-            .field("hatch_0_line_reduction", &self.hatch_0_line_reduction)
-            .field("hatch_1_line_reduction", &self.hatch_1_line_reduction)
-            .field("hatch_2_line_reduction", &self.hatch_2_line_reduction)
-            .field("hatch_0_loop_count", &self.hatch_0_loop_count)
-            .field("hatch_1_loop_count", &self.hatch_1_loop_count)
-            .field("hatch_2_loop_count", &self.hatch_2_loop_count)
-            .field("hatch_0_loop_distance", &self.hatch_0_loop_distance)
-            .field("hatch_1_loop_distance", &self.hatch_1_loop_distance)
-            .field("hatch_2_loop_distance", &self.hatch_2_loop_distance)
-            .field("contour_priority", &self.contour_priority)
-            .field("hatch_0_count", &self.hatch_0_count)
-            .field("hatch_1_count", &self.hatch_1_count)
-            .field("hatch_2_count", &self.hatch_2_count)
-            .finish()
-    }
-}
-
-#[cfg_attr(feature = "default-debug", derive(Debug))]
-#[derive(BinRead, BinWrite, Clone, Diff, PartialEq)]
-#[diff(attr(
-    #[derive(Debug, PartialEq)]
-))]
-#[brw(magic(15u32))] // Number of fields in this struct
-pub struct HatchSetting {
-    pub count: U32,
-    pub enabled: U32,
-    pub pen: U32,
-    pub flags: FieldOf<HatchFlag>,
-    pub edge_offset: F64,
-    pub line_spacing: F64,
-    pub start_offset: F64,
-    pub end_offset: F64,
-    pub angle: F64,
-    pub rotate_angle: F64,
-    pub line_reduction: F64,
-    pub loop_distance: F64,
-    pub loop_count: U32,
-    pub _unknown_1: [Field; 2],
-}
-
-// Custom Debug implementation to only print known fields
-#[cfg(not(feature = "default-debug"))]
-impl Debug for HatchSetting {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("HatchSettings")
-            .field("count", &self.count)
-            .field("enabled", &self.enabled)
-            .field("pen", &self.pen)
-            .field("flags", &self.flags)
-            .field("edge_offset", &self.edge_offset)
-            .field("line_spacing", &self.line_spacing)
-            .field("start_offset", &self.start_offset)
-            .field("end_offset", &self.end_offset)
-            .field("angle", &self.angle)
-            .field("auto_rotate_angle", &self.rotate_angle)
-            .field("line_reduction", &self.line_reduction)
-            .field("loop_distance", &self.loop_distance)
-            .field("loop_count", &self.loop_count)
-            .finish()
-    }
-}
-
-impl Default for HatchSetting {
-    fn default() -> Self {
-        Self {
-            count: 1.into(),
-            enabled: false.into(),
-            pen: 0.into(),
-            flags: HatchFlag::new().into(),
-            edge_offset: 0.0.into(),
-            line_spacing: 1.0.into(),
-            start_offset: 0.0.into(),
-            end_offset: 0.0.into(),
-            angle: 0.0.into(),
-            rotate_angle: 10.0.into(),
-            line_reduction: 0.0.into(),
-            loop_distance: 0.5.into(),
-            loop_count: 0.into(),
-            _unknown_1: [vec![0, 0, 0, 0, 0, 0, 0, 0].into(), vec![0, 0, 0, 0].into()].into(),
-        }
-    }
-}
-
-impl From<Vec<HatchSetting>> for LegacyHatchSetting {
-    fn from(value: Vec<HatchSetting>) -> Self {
-        LegacyHatchSetting {
-            mark_contour: false.into(),
-            hatch_0_enable: value.get(0).map(|x| x.enabled).unwrap_or_default(),
-            hatch_0_pen: value.get(0).map(|x| x.pen).unwrap_or_default(),
-            hatch_0_flags: value.get(0).map(|x| x.flags).unwrap_or_default(),
-            hatch_0_edge_offset: value.get(0).map(|x| x.edge_offset).unwrap_or_default(),
-            hatch_0_line_spacing: value.get(0).map(|x| x.line_spacing).unwrap_or_default(),
-            hatch_0_start_offset: value.get(0).map(|x| x.start_offset).unwrap_or_default(),
-            hatch_0_end_offset: value.get(0).map(|x| x.end_offset).unwrap_or_default(),
-            hatch_0_angle: value.get(0).map(|x| x.angle).unwrap_or_default(),
-            hatch_1_enable: value.get(1).map(|x| x.enabled).unwrap_or_default(),
-            hatch_1_pen: value.get(1).map(|x| x.pen).unwrap_or_default(),
-            hatch_1_flags: value.get(1).map(|x| x.flags).unwrap_or_default(),
-            hatch_1_edge_offset: value.get(1).map(|x| x.edge_offset).unwrap_or_default(),
-            hatch_1_line_spacing: value.get(1).map(|x| x.line_spacing).unwrap_or_default(),
-            hatch_1_start_offset: value.get(1).map(|x| x.start_offset).unwrap_or_default(),
-            hatch_1_end_offset: value.get(1).map(|x| x.end_offset).unwrap_or_default(),
-            hatch_1_angle: value.get(1).map(|x| x.angle).unwrap_or_default(),
-            any_hatch_enabled: value.iter().any(|x| x.enabled.into()).into(),
-            hatch_0_rotate_angle: value.get(0).map(|x| x.rotate_angle).unwrap_or_default(),
-            hatch_1_rotate_angle: value.get(1).map(|x| x.rotate_angle).unwrap_or_default(),
-            hatch_2_enable: value.get(2).map(|x| x.enabled).unwrap_or_default(),
-            hatch_2_pen: value.get(2).map(|x| x.pen).unwrap_or_default(),
-            hatch_2_flags: value.get(2).map(|x| x.flags).unwrap_or_default(),
-            hatch_2_edge_offset: value.get(2).map(|x| x.edge_offset).unwrap_or_default(),
-            hatch_2_line_spacing: value.get(2).map(|x| x.line_spacing).unwrap_or_default(),
-            hatch_2_start_offset: value.get(2).map(|x| x.start_offset).unwrap_or_default(),
-            hatch_2_end_offset: value.get(2).map(|x| x.end_offset).unwrap_or_default(),
-            hatch_2_angle: value.get(2).map(|x| x.angle).unwrap_or_default(),
-            hatch_2_rotate_angle: value.get(2).map(|x| x.rotate_angle).unwrap_or_default(),
-            hatch_0_line_reduction: value.get(0).map(|x| x.line_reduction).unwrap_or_default(),
-            hatch_1_line_reduction: value.get(1).map(|x| x.line_reduction).unwrap_or_default(),
-            hatch_2_line_reduction: value.get(2).map(|x| x.line_reduction).unwrap_or_default(),
-            hatch_0_loop_count: value.get(0).map(|x| x.loop_count).unwrap_or_default(),
-            hatch_1_loop_count: value.get(1).map(|x| x.loop_count).unwrap_or_default(),
-            hatch_2_loop_count: value.get(2).map(|x| x.loop_count).unwrap_or_default(),
-            hatch_0_loop_distance: value.get(0).map(|x| x.loop_distance).unwrap_or_default(),
-            hatch_1_loop_distance: value.get(1).map(|x| x.loop_distance).unwrap_or_default(),
-            hatch_2_loop_distance: value.get(2).map(|x| x.loop_distance).unwrap_or_default(),
-            _unknown_1: [
-                vec![0, 0, 0, 0, 0, 0, 0, 0].into(),
-                vec![0, 0, 0, 0, 0, 0, 0, 0].into(),
-                vec![0, 0, 0, 0, 0, 0, 0, 0].into(),
-            ]
-            .into(),
-            contour_priority: false.into(),
-            hatch_0_count: value.get(0).map(|x| x.count).unwrap_or_default(),
-            hatch_1_count: value.get(1).map(|x| x.count).unwrap_or_default(),
-            hatch_2_count: value.get(2).map(|x| x.count).unwrap_or_default(),
-        }
-    }
-}
-
-#[derive(BinRead, BinWrite, Clone, Debug, Diff, PartialEq)]
-#[diff(attr(
-    #[derive(Debug, PartialEq)]
-))]
-pub struct HatchLine {
-    pub lines: ArrayOf<Lines>,
-}
-
-#[derive(BinRead, BinWrite, Clone, Debug, Diff, PartialEq)]
-#[diff(attr(
-    #[derive(Debug, PartialEq)]
-))]
-#[brw(magic(16_u32))] // ObjectType::HatchLine
-pub struct HatchLines {
-    pub core: ObjectCore, // HatchType::HatchLine
-    pub hatch_line: ArrayOf<HatchLine>,
-}
-
-#[derive(BinRead, BinWrite, Clone, Debug, Diff, PartialEq)]
-#[diff(attr(
-    #[derive(Debug, PartialEq)]
-))]
-pub struct Hatches {
-    pub core: ObjectCore, // HatchType::HatchLine
-    pub hatch_lines: ArrayOf<HatchLines>,
-}
-
-impl Hatches {
-    pub fn set_pen(&mut self, pen: u32) {
-        // Ignore self.core.pen
-        self.hatch_lines.iter_mut().for_each(|x| {
-            // Ignore HatchLines.core.pen
-            x.hatch_line.iter_mut().for_each(|x| {
-                x.lines.iter_mut().for_each(|x| {
-                    x.core.pen = pen.into();
-                })
-            })
-        });
-    }
-}
-
-#[binrw]
-#[derive(Clone, Debug, Diff, PartialEq)]
-#[diff(attr(
-    #[derive(Debug, PartialEq)]
-))]
-pub struct Hatch {
-    pub core: ObjectCore,
-    pub outline: ArrayOf<Object>,
-    pub legacy_setting: LegacyHatchSetting,
-
-    #[br(temp)]
-    #[bw(try_calc(u32::try_from(hatch_settings.len()).unwrap().try_into()))]
-    pub num_settings: U32,
-
-    pub _unknown_1: Field,
-
-    #[br(count = num_settings.value)]
-    pub hatch_settings: Vec<HatchSetting>,
-
-    #[brw(if(legacy_setting.any_hatch_enabled.value == 1))]
-    pub hatches: Option<Hatches>,
-}
-
-impl Display for Hatch {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}", self.core)?;
-        for object in self.outline.iter() {
-            write!(f, "\nOutline: {}", object)?;
-        }
-        write!(
-            f,
-            "\nMark Contour: {}, Contour Priority: {}",
-            *self.legacy_setting.mark_contour != 0,
-            *self.legacy_setting.contour_priority != 0
-        )?;
-        for (index, setting) in self.hatch_settings.iter().enumerate() {
-            if setting.enabled.into() {
-                write!(
-                    f,
-                    "\nHatch #{}: All Calc: {}, Follow Edge Once: {}, Cross Hatch: {}, Pattern: {}, Angle: {:.2}, Pen: {}, Count: {}, Line Space: {:.2}, Avg Distribte Line: {}",
-                    index,
-                    setting.flags.all_calc() != 0,
-                    setting.flags.follow_edge_once() != 0,
-                    setting.flags.cross_hatch() != 0,
-                    HatchPattern::from(*setting.flags),
-                    *setting.angle,
-                    *setting.pen,
-                    *setting.count,
-                    *setting.line_spacing,
-                    setting.flags.average_distribute_line() != 0,
-                )?;
-                write!(
-                    f,
-                    "\n          Edge Offset: {:.2}, Start Offset: {:.2}, End Offset: {:.2}, Line Reduction: {:.2}, Loop Count: {}, Loop Distance: {:.2}, Auto Rotate: {}, Auto Rotate Angle: {:.2}",
-                    *setting.edge_offset,
-                    *setting.start_offset,
-                    *setting.end_offset,
-                    *setting.line_reduction,
-                    *setting.loop_count,
-                    *setting.loop_distance,
-                    setting.flags.auto_rotate_hatch_angle() != 0,
-                    *setting.rotate_angle,
-                )?;
-            }
-        }
-        Ok(())
-    }
-}
-
-impl Translate for Hatch {
-    fn move_absolute(&mut self, origin: Option<Coordinate>, z: Option<f64>) {
-        self.outline
-            .iter_mut()
-            .for_each(|x| x.move_absolute(origin, z));
-        origin.map(|origin| {
-            let delta: Coordinate = origin - *self.core.origin;
-            self.hatches.iter_mut().for_each(|x| {
-                // Ignore Hatches.core.origin
-                x.hatch_lines.iter_mut().for_each(|x| {
-                    // Ignore HatchLines.core.origin
-                    x.hatch_line.iter_mut().for_each(|x| {
-                        x.lines
-                            .iter_mut()
-                            .for_each(|x| x.move_relative(Some(delta), None));
-                    })
-                })
-            });
-        });
-        self.core.move_absolute(origin, z);
-    }
-
-    fn move_relative(&mut self, delta: Option<Coordinate>, z: Option<f64>) {
-        self.outline
-            .iter_mut()
-            .for_each(|x| x.move_relative(delta, z));
-        delta.map(|delta| {
-            self.hatches.iter_mut().for_each(|x| {
-                // Ignore Hatches.core.origin
-                x.hatch_lines.iter_mut().for_each(|x| {
-                    // Ignore HatchLines.core.origin
-                    x.hatch_line.iter_mut().for_each(|x| {
-                        x.lines
-                            .iter_mut()
-                            .for_each(|x| x.move_relative(Some(delta), None));
-                    })
-                })
-            });
-        });
-        self.core.move_relative(delta, z);
-    }
-}
+use std::fmt::{Debug, Display};
+
+use crate::{
+    array_of::ArrayOf,
+    field_of::FieldOf,
+    types::{Field, Point, F64, U32},
+};
+use binrw::{binrw, BinRead, BinWrite};
+use diff::Diff;
+use modular_bitfield::{
+    bitfield,
+    specifiers::{B1, B18},
+};
+use serde::{Deserialize, Serialize};
+
+use super::{line::Lines, Object, ObjectCore, Translate};
+
+#[bitfield(bits = 32)]
+#[derive(BinRead, BinWrite, Copy, Clone, Debug, Default, Diff, PartialEq)]
+#[diff(attr(
+    #[derive(Debug, PartialEq)]
+))]
+#[br(map = Self::from_bytes)]
+#[bw(map = |&x| Self::into_bytes(x))]
+pub struct HatchFlag {
+    pub all_calc: B1,
+    pub follow_edge_once: B1,
+    pub continuous_pattern: B1, // Hatch type 3 when combined with bidirectional_pattern
+    pub bidirectional_pattern: B1, // Hatch type 1
+    pub ring_pattern: B1,       // Hatch type 2
+    #[skip]
+    __: B1,
+    pub auto_rotate_hatch_angle: B1,
+    pub average_distribute_line: B1,
+    #[skip]
+    __: B1,
+    pub gong_pattern: B1, // Hatch type 4 when combined with bidirectional_pattern
+    pub cross_hatch: B1,
+    pub background_pattern: B1, // Hatch type 5, must set all_calc as well
+    pub fill_pattern: B1, // Hatch type 6 when combined with continuous_pattern and bidirectional_pattern
+    pub zigzag_pattern: B1, // Hatch type 7
+    #[skip]
+    __: B18,
+}
+
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, strum::Display)]
+pub enum HatchPattern {
+    Directional,
+    Bidirectional,
+    Ring,
+    Continuous,
+    Gong,
+    Background,
+    Fill,
+    Zigzag,
+}
+
+impl From<HatchPattern> for HatchFlag {
+    fn from(value: HatchPattern) -> Self {
+        let mut ret = Self::new();
+        match value {
+            HatchPattern::Directional => (),
+            HatchPattern::Bidirectional => ret.set_bidirectional_pattern(1),
+            HatchPattern::Ring => ret.set_ring_pattern(1),
+            HatchPattern::Continuous => {
+                ret.set_bidirectional_pattern(1);
+                ret.set_continuous_pattern(1);
+            }
+            HatchPattern::Gong => {
+                ret.set_bidirectional_pattern(1);
+                ret.set_gong_pattern(1);
+            }
+            HatchPattern::Background => {
+                ret.set_background_pattern(1);
+                ret.set_all_calc(1);
+            }
+            HatchPattern::Fill => ret.set_fill_pattern(1),
+            HatchPattern::Zigzag => ret.set_zigzag_pattern(1),
+        }
+        ret
+    }
+}
+
+impl From<HatchFlag> for HatchPattern {
+    fn from(value: HatchFlag) -> Self {
+        if value.ring_pattern() != 0 {
+            HatchPattern::Ring
+        } else if value.background_pattern() != 0 {
+            HatchPattern::Background
+        } else if value.fill_pattern() != 0 {
+            HatchPattern::Fill
+        } else if value.zigzag_pattern() != 0 {
+            HatchPattern::Zigzag
+        } else if value.gong_pattern() != 0 {
+            HatchPattern::Gong
+        } else if value.bidirectional_pattern() != 0 {
+            if value.continuous_pattern() != 0 {
+                HatchPattern::Continuous
+            } else {
+                HatchPattern::Bidirectional
+            }
+        } else {
+            HatchPattern::Directional
+        }
+    }
+}
+
+#[cfg_attr(feature = "default-debug", derive(Debug))]
+#[derive(BinRead, BinWrite, Clone, Diff, PartialEq)]
+#[diff(attr(
+    #[derive(Debug, PartialEq)]
+))]
+// #[brw(magic(46u32))] // Number of fields in this struct
+#[brw(magic(47u32))] // Number of fields in this struct
+pub struct LegacyHatchSetting {
+    pub mark_contour: U32,
+    pub hatch_0_enable: U32,
+    pub hatch_0_pen: U32,
+    pub hatch_0_flags: FieldOf<HatchFlag>,
+    pub hatch_0_edge_offset: F64,
+    pub hatch_0_line_spacing: F64,
+    pub hatch_0_start_offset: F64,
+    pub hatch_0_end_offset: F64,
+    pub hatch_0_angle: F64,
+    pub hatch_1_enable: U32,
+    pub hatch_1_pen: U32,
+    pub hatch_1_flags: FieldOf<HatchFlag>,
+    pub hatch_1_edge_offset: F64,
+    pub hatch_1_line_spacing: F64,
+    pub hatch_1_start_offset: F64,
+    pub hatch_1_end_offset: F64,
+    pub hatch_1_angle: F64,
+    pub any_hatch_enabled: U32,
+    pub hatch_0_rotate_angle: F64,
+    pub hatch_1_rotate_angle: F64,
+    pub hatch_2_enable: U32,
+    pub hatch_2_pen: U32,
+    pub hatch_2_flags: FieldOf<HatchFlag>,
+    pub hatch_2_edge_offset: F64,
+    pub hatch_2_line_spacing: F64,
+    pub hatch_2_start_offset: F64,
+    pub hatch_2_end_offset: F64,
+    pub hatch_2_angle: F64,
+    pub hatch_2_rotate_angle: F64,
+    pub hatch_0_line_reduction: F64,
+    pub hatch_1_line_reduction: F64,
+    pub hatch_2_line_reduction: F64,
+    pub hatch_0_loop_count: U32,
+    pub hatch_1_loop_count: U32,
+    pub hatch_2_loop_count: U32,
+    pub hatch_0_loop_distance: F64,
+    pub hatch_1_loop_distance: F64,
+    pub hatch_2_loop_distance: F64,
+    pub _unknown_1: [Field; 3],
+    pub contour_priority: U32,
+    pub hatch_0_count: U32,
+    pub hatch_1_count: U32,
+    pub hatch_2_count: U32,
+}
+
+// Custom Debug implementation to only print known fields
+#[cfg(not(feature = "default-debug"))]
+impl Debug for LegacyHatchSetting {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("LegacyHatchSetting")
+            .field("mark_contour", &self.mark_contour)
+            .field("hatch_0_enable", &self.hatch_0_enable)
+            .field("hatch_0_pen", &self.hatch_0_pen)
+            .field("hatch_0_flags", &self.hatch_0_flags)
+            .field("hatch_0_edge_offset", &self.hatch_0_edge_offset)
+            .field("hatch_0_line_spacing", &self.hatch_0_line_spacing)
+            .field("hatch_0_start_offset", &self.hatch_0_start_offset)
+            .field("hatch_0_end_offset", &self.hatch_0_end_offset)
+            .field("hatch_0_angle", &self.hatch_0_angle)
+            .field("hatch_1_enable", &self.hatch_1_enable)
+            .field("hatch_1_pen", &self.hatch_1_pen)
+            .field("hatch_1_flags", &self.hatch_1_flags)
+            .field("hatch_1_edge_offset", &self.hatch_1_edge_offset)
+            .field("hatch_1_line_spacing", &self.hatch_1_line_spacing)
+            .field("hatch_1_start_offset", &self.hatch_1_start_offset)
+            .field("hatch_1_end_offset", &self.hatch_1_end_offset)
+            .field("hatch_1_angle", &self.hatch_1_angle)
+            .field("any_hatch_enabled", &self.any_hatch_enabled)
+            .field("hatch_0_auto_rotate_angle", &self.hatch_0_rotate_angle)
+            .field("hatch_1_auto_rotate_angle", &self.hatch_1_rotate_angle)
+            .field("hatch_2_enable", &self.hatch_2_enable)
+            .field("hatch_2_pen", &self.hatch_2_pen)
+            .field("hatch_2_flags", &self.hatch_2_flags)
+            .field("hatch_2_edge_offset", &self.hatch_2_edge_offset)
+            .field("hatch_2_line_spacing", &self.hatch_2_line_spacing)
+            .field("hatch_2_start_offset", &self.hatch_2_start_offset)
+            .field("hatch_2_end_offset", &self.hatch_2_end_offset)
+            .field("hatch_2_angle", &self.hatch_2_angle)
+            .field("hatch_2_auto_rotate_angle", &self.hatch_2_rotate_angle)
+            .field("hatch_0_line_reduction", &self.hatch_0_line_reduction)
+            .field("hatch_1_line_reduction", &self.hatch_1_line_reduction)
+            .field("hatch_2_line_reduction", &self.hatch_2_line_reduction)
+            .field("hatch_0_loop_count", &self.hatch_0_loop_count)
+            .field("hatch_1_loop_count", &self.hatch_1_loop_count)
+            .field("hatch_2_loop_count", &self.hatch_2_loop_count)
+            .field("hatch_0_loop_distance", &self.hatch_0_loop_distance)
+            .field("hatch_1_loop_distance", &self.hatch_1_loop_distance)
+            .field("hatch_2_loop_distance", &self.hatch_2_loop_distance)
+            .field("contour_priority", &self.contour_priority)
+            .field("hatch_0_count", &self.hatch_0_count)
+            .field("hatch_1_count", &self.hatch_1_count)
+            .field("hatch_2_count", &self.hatch_2_count)
+            .finish()
+    }
+}
+
+#[cfg_attr(feature = "default-debug", derive(Debug))]
+#[derive(BinRead, BinWrite, Clone, Diff, PartialEq)]
+#[diff(attr(
+    #[derive(Debug, PartialEq)]
+))]
+#[brw(magic(15u32))] // Number of fields in this struct
+pub struct HatchSetting {
+    pub count: U32,
+    pub enabled: U32,
+    pub pen: U32,
+    pub flags: FieldOf<HatchFlag>,
+    pub edge_offset: F64,
+    pub line_spacing: F64,
+    pub start_offset: F64,
+    pub end_offset: F64,
+    pub angle: F64,
+    pub rotate_angle: F64,
+    pub line_reduction: F64,
+    pub loop_distance: F64,
+    pub loop_count: U32,
+    pub _unknown_1: [Field; 2],
+}
+
+// Custom Debug implementation to only print known fields
+#[cfg(not(feature = "default-debug"))]
+impl Debug for HatchSetting {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("HatchSettings")
+            .field("count", &self.count)
+            .field("enabled", &self.enabled)
+            .field("pen", &self.pen)
+            .field("flags", &self.flags)
+            .field("edge_offset", &self.edge_offset)
+            .field("line_spacing", &self.line_spacing)
+            .field("start_offset", &self.start_offset)
+            .field("end_offset", &self.end_offset)
+            .field("angle", &self.angle)
+            .field("auto_rotate_angle", &self.rotate_angle)
+            .field("line_reduction", &self.line_reduction)
+            .field("loop_distance", &self.loop_distance)
+            .field("loop_count", &self.loop_count)
+            .finish()
+    }
+}
+
+impl Default for HatchSetting {
+    fn default() -> Self {
+        Self {
+            count: 1.into(),
+            enabled: false.into(),
+            pen: 0.into(),
+            flags: HatchFlag::new().into(),
+            edge_offset: 0.0.into(),
+            line_spacing: 1.0.into(),
+            start_offset: 0.0.into(),
+            end_offset: 0.0.into(),
+            angle: 0.0.into(),
+            rotate_angle: 10.0.into(),
+            line_reduction: 0.0.into(),
+            loop_distance: 0.5.into(),
+            loop_count: 0.into(),
+            _unknown_1: [vec![0, 0, 0, 0, 0, 0, 0, 0].into(), vec![0, 0, 0, 0].into()].into(),
+        }
+    }
+}
+
+impl From<Vec<HatchSetting>> for LegacyHatchSetting {
+    fn from(value: Vec<HatchSetting>) -> Self {
+        LegacyHatchSetting {
+            mark_contour: false.into(),
+            hatch_0_enable: value.get(0).map(|x| x.enabled).unwrap_or_default(),
+            hatch_0_pen: value.get(0).map(|x| x.pen).unwrap_or_default(),
+            hatch_0_flags: value.get(0).map(|x| x.flags).unwrap_or_default(),
+            hatch_0_edge_offset: value.get(0).map(|x| x.edge_offset).unwrap_or_default(),
+            hatch_0_line_spacing: value.get(0).map(|x| x.line_spacing).unwrap_or_default(),
+            hatch_0_start_offset: value.get(0).map(|x| x.start_offset).unwrap_or_default(),
+            hatch_0_end_offset: value.get(0).map(|x| x.end_offset).unwrap_or_default(),
+            hatch_0_angle: value.get(0).map(|x| x.angle).unwrap_or_default(),
+            hatch_1_enable: value.get(1).map(|x| x.enabled).unwrap_or_default(),
+            hatch_1_pen: value.get(1).map(|x| x.pen).unwrap_or_default(),
+            hatch_1_flags: value.get(1).map(|x| x.flags).unwrap_or_default(),
+            hatch_1_edge_offset: value.get(1).map(|x| x.edge_offset).unwrap_or_default(),
+            hatch_1_line_spacing: value.get(1).map(|x| x.line_spacing).unwrap_or_default(),
+            hatch_1_start_offset: value.get(1).map(|x| x.start_offset).unwrap_or_default(),
+            hatch_1_end_offset: value.get(1).map(|x| x.end_offset).unwrap_or_default(),
+            hatch_1_angle: value.get(1).map(|x| x.angle).unwrap_or_default(),
+            any_hatch_enabled: value.iter().any(|x| x.enabled.into()).into(),
+            hatch_0_rotate_angle: value.get(0).map(|x| x.rotate_angle).unwrap_or_default(),
+            hatch_1_rotate_angle: value.get(1).map(|x| x.rotate_angle).unwrap_or_default(),
+            hatch_2_enable: value.get(2).map(|x| x.enabled).unwrap_or_default(),
+            hatch_2_pen: value.get(2).map(|x| x.pen).unwrap_or_default(),
+            hatch_2_flags: value.get(2).map(|x| x.flags).unwrap_or_default(),
+            hatch_2_edge_offset: value.get(2).map(|x| x.edge_offset).unwrap_or_default(),
+            hatch_2_line_spacing: value.get(2).map(|x| x.line_spacing).unwrap_or_default(),
+            hatch_2_start_offset: value.get(2).map(|x| x.start_offset).unwrap_or_default(),
+            hatch_2_end_offset: value.get(2).map(|x| x.end_offset).unwrap_or_default(),
+            hatch_2_angle: value.get(2).map(|x| x.angle).unwrap_or_default(),
+            hatch_2_rotate_angle: value.get(2).map(|x| x.rotate_angle).unwrap_or_default(),
+            hatch_0_line_reduction: value.get(0).map(|x| x.line_reduction).unwrap_or_default(),
+            hatch_1_line_reduction: value.get(1).map(|x| x.line_reduction).unwrap_or_default(),
+            hatch_2_line_reduction: value.get(2).map(|x| x.line_reduction).unwrap_or_default(),
+            hatch_0_loop_count: value.get(0).map(|x| x.loop_count).unwrap_or_default(),
+            hatch_1_loop_count: value.get(1).map(|x| x.loop_count).unwrap_or_default(),
+            hatch_2_loop_count: value.get(2).map(|x| x.loop_count).unwrap_or_default(),
+            hatch_0_loop_distance: value.get(0).map(|x| x.loop_distance).unwrap_or_default(),
+            hatch_1_loop_distance: value.get(1).map(|x| x.loop_distance).unwrap_or_default(),
+            hatch_2_loop_distance: value.get(2).map(|x| x.loop_distance).unwrap_or_default(),
+            _unknown_1: [
+                vec![0, 0, 0, 0, 0, 0, 0, 0].into(),
+                vec![0, 0, 0, 0, 0, 0, 0, 0].into(),
+                vec![0, 0, 0, 0, 0, 0, 0, 0].into(),
+            ]
+            .into(),
+            contour_priority: false.into(),
+            hatch_0_count: value.get(0).map(|x| x.count).unwrap_or_default(),
+            hatch_1_count: value.get(1).map(|x| x.count).unwrap_or_default(),
+            hatch_2_count: value.get(2).map(|x| x.count).unwrap_or_default(),
+        }
+    }
+}
+
+#[derive(BinRead, BinWrite, Clone, Debug, Diff, PartialEq)]
+#[diff(attr(
+    #[derive(Debug, PartialEq)]
+))]
+pub struct HatchLine {
+    pub lines: ArrayOf<Lines>,
+}
+
+#[derive(BinRead, BinWrite, Clone, Debug, Diff, PartialEq)]
+#[diff(attr(
+    #[derive(Debug, PartialEq)]
+))]
+#[brw(magic(16_u32))] // ObjectType::HatchLine
+pub struct HatchLines {
+    pub core: ObjectCore, // HatchType::HatchLine
+    pub hatch_line: ArrayOf<HatchLine>,
+}
+
+#[derive(BinRead, BinWrite, Clone, Debug, Diff, PartialEq)]
+#[diff(attr(
+    #[derive(Debug, PartialEq)]
+))]
+pub struct Hatches {
+    pub core: ObjectCore, // HatchType::HatchLine
+    pub hatch_lines: ArrayOf<HatchLines>,
+}
+
+impl Hatches {
+    pub fn set_pen(&mut self, pen: u32) {
+        // Ignore self.core.pen
+        self.hatch_lines.iter_mut().for_each(|x| {
+            // Ignore HatchLines.core.pen
+            x.hatch_line.iter_mut().for_each(|x| {
+                x.lines.iter_mut().for_each(|x| {
+                    x.core.pen = pen.into();
+                })
+            })
+        });
+    }
+}
+
+#[binrw]
+#[derive(Clone, Debug, Diff, PartialEq)]
+#[diff(attr(
+    #[derive(Debug, PartialEq)]
+))]
+pub struct Hatch {
+    pub core: ObjectCore,
+    pub outline: ArrayOf<Object>,
+    pub legacy_setting: LegacyHatchSetting,
+
+    #[br(temp)]
+    #[bw(try_calc(u32::try_from(hatch_settings.len()).unwrap().try_into()))]
+    pub num_settings: U32,
+
+    pub _unknown_1: Field,
+
+    #[br(count = num_settings.value)]
+    pub hatch_settings: Vec<HatchSetting>,
+
+    #[brw(if(legacy_setting.any_hatch_enabled.value == 1))]
+    pub hatches: Option<Hatches>,
+}
+
+impl Display for Hatch {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.core)?;
+        for object in self.outline.iter() {
+            write!(f, "\nOutline: {}", object)?;
+        }
+        write!(
+            f,
+            "\nMark Contour: {}, Contour Priority: {}",
+            *self.legacy_setting.mark_contour != 0,
+            *self.legacy_setting.contour_priority != 0
+        )?;
+        for (index, setting) in self.hatch_settings.iter().enumerate() {
+            if setting.enabled.into() {
+                write!(
+                    f,
+                    "\nHatch #{}: All Calc: {}, Follow Edge Once: {}, Cross Hatch: {}, Pattern: {}, Angle: {:.2}, Pen: {}, Count: {}, Line Space: {:.2}, Avg Distribte Line: {}",
+                    index,
+                    setting.flags.all_calc() != 0,
+                    setting.flags.follow_edge_once() != 0,
+                    setting.flags.cross_hatch() != 0,
+                    HatchPattern::from(*setting.flags),
+                    *setting.angle,
+                    *setting.pen,
+                    *setting.count,
+                    *setting.line_spacing,
+                    setting.flags.average_distribute_line() != 0,
+                )?;
+                write!(
+                    f,
+                    "\n          Edge Offset: {:.2}, Start Offset: {:.2}, End Offset: {:.2}, Line Reduction: {:.2}, Loop Count: {}, Loop Distance: {:.2}, Auto Rotate: {}, Auto Rotate Angle: {:.2}",
+                    *setting.edge_offset,
+                    *setting.start_offset,
+                    *setting.end_offset,
+                    *setting.line_reduction,
+                    *setting.loop_count,
+                    *setting.loop_distance,
+                    setting.flags.auto_rotate_hatch_angle() != 0,
+                    *setting.rotate_angle,
+                )?;
+            }
+        }
+        Ok(())
+    }
+}
+
+impl Translate for Hatch {
+    fn move_absolute(&mut self, origin: Option<Point>, z: Option<f64>) {
+        self.outline
+            .iter_mut()
+            .for_each(|x| x.move_absolute(origin, z));
+        origin.map(|origin| {
+            let delta: Point = origin - *self.core.origin;
+            self.hatches.iter_mut().for_each(|x| {
+                // Ignore Hatches.core.origin
+                x.hatch_lines.iter_mut().for_each(|x| {
+                    // Ignore HatchLines.core.origin
+                    x.hatch_line.iter_mut().for_each(|x| {
+                        x.lines
+                            .iter_mut()
+                            .for_each(|x| x.move_relative(Some(delta), None));
+                    })
+                })
+            });
+        });
+        self.core.move_absolute(origin, z);
+    }
+
+    fn move_relative(&mut self, delta: Option<Point>, z: Option<f64>) {
+        self.outline
+            .iter_mut()
+            .for_each(|x| x.move_relative(delta, z));
+        delta.map(|delta| {
+            self.hatches.iter_mut().for_each(|x| {
+                // Ignore Hatches.core.origin
+                x.hatch_lines.iter_mut().for_each(|x| {
+                    // Ignore HatchLines.core.origin
+                    x.hatch_line.iter_mut().for_each(|x| {
+                        x.lines
+                            .iter_mut()
+                            .for_each(|x| x.move_relative(Some(delta), None));
+                    })
+                })
+            });
+        });
+        self.core.move_relative(delta, z);
+    }
+}

+ 8 - 14
src/ezcad/objects/line.rs

@@ -3,7 +3,7 @@ use std::fmt::{Debug, Display};
 use binrw::{binrw, BinRead, BinWrite};
 use diff::Diff;
 
-use crate::{array_of::ArrayOf, types::Coordinate};
+use crate::{array_of::ArrayOf, types::Point};
 
 use super::{ObjectCore, Translate};
 
@@ -14,17 +14,11 @@ use super::{ObjectCore, Translate};
 ))]
 pub enum LineType {
     #[brw(magic = 0x0001u16)]
-    Point { _zero: u32, point: Coordinate },
+    Point { _zero: u32, point: Point },
     #[brw(magic = 0x0100u16)]
-    Line {
-        _zero: u32,
-        points: ArrayOf<Coordinate>,
-    },
+    Line { _zero: u32, points: ArrayOf<Point> },
     #[brw(magic = 0x0300u16)]
-    Bezier {
-        _zero: u32,
-        points: ArrayOf<Coordinate>,
-    },
+    Bezier { _zero: u32, points: ArrayOf<Point> },
 }
 
 impl Display for LineType {
@@ -74,7 +68,7 @@ impl Debug for LineType {
 }
 
 impl LineType {
-    fn move_relative(&mut self, delta: Coordinate) {
+    fn move_relative(&mut self, delta: Point) {
         match self {
             LineType::Point { _zero, point } => {
                 *point += delta;
@@ -118,15 +112,15 @@ impl Display for Lines {
 }
 
 impl Translate for Lines {
-    fn move_absolute(&mut self, origin: Option<Coordinate>, z: Option<f64>) {
+    fn move_absolute(&mut self, origin: Option<Point>, z: Option<f64>) {
         origin.map(|origin| {
-            let delta: Coordinate = *self.core.origin - origin;
+            let delta: Point = *self.core.origin - origin;
             self.lines.iter_mut().for_each(|x| x.move_relative(delta));
         });
         self.core.move_absolute(origin, z);
     }
 
-    fn move_relative(&mut self, delta: Option<Coordinate>, z: Option<f64>) {
+    fn move_relative(&mut self, delta: Option<Point>, z: Option<f64>) {
         delta.map(|delta| {
             self.lines.iter_mut().for_each(|x| x.move_relative(delta));
         });

+ 62 - 69
src/ezcad/objects/mod.rs

@@ -16,7 +16,7 @@ use modular_bitfield::{
 
 use crate::{
     field_of::FieldOf,
-    types::{Coordinate, Field, ObjectType, WString, F64, U16, U32},
+    types::{Field, ObjectType, Point, WString, F64, U16, U32},
 };
 
 use self::{
@@ -33,9 +33,9 @@ pub mod rectangle;
 
 pub trait Translate {
     /// Move origin by absolute coordinates
-    fn move_absolute(&mut self, origin: Option<Coordinate>, z: Option<f64>);
+    fn move_absolute(&mut self, origin: Option<Point>, z: Option<f64>);
     /// Move origin by relative coordinates
-    fn move_relative(&mut self, delta: Option<Coordinate>, z: Option<f64>);
+    fn move_relative(&mut self, delta: Option<Point>, z: Option<f64>);
 }
 
 #[bitfield(bits = 16)]
@@ -69,7 +69,7 @@ pub struct ObjectCore {
     pub io_control_enable_mask: U16,
     pub io_control_disable_mask: U16,
     pub _unknown_2: [Field; 5],
-    pub origin: FieldOf<Coordinate>,
+    pub origin: FieldOf<Point>,
     pub z: F64,
     pub a: F64,
     pub index: U32, // Increments for each hatch fill line (types 1/2) or 0xFFFF_FFFF (types 3/4/5/7/8)
@@ -110,12 +110,12 @@ impl Debug for ObjectCore {
 }
 
 impl Translate for ObjectCore {
-    fn move_absolute(&mut self, origin: Option<Coordinate>, z: Option<f64>) {
+    fn move_absolute(&mut self, origin: Option<Point>, z: Option<f64>) {
         origin.map(|origin| *self.origin = origin);
         z.map(|z| self.z = z.into());
     }
 
-    fn move_relative(&mut self, delta: Option<Coordinate>, z: Option<f64>) {
+    fn move_relative(&mut self, delta: Option<Point>, z: Option<f64>) {
         delta.map(|delta| *self.origin += delta);
         z.map(|z| *self.z += z);
     }
@@ -143,7 +143,7 @@ impl ObjectCore {
                 vec![0, 0, 0, 0, 0, 0, 36, 64].into(), // 10.0_f64
             ]
             .into(),
-            origin: Coordinate { x: 0.0, y: 0.0 }.into(),
+            origin: Point { x: 0.0, y: 0.0 }.into(),
             z: 0.0.into(),
             a: 0.0.into(),
             index: 0.into(),
@@ -188,7 +188,7 @@ impl Display for Object {
 }
 
 impl Translate for Object {
-    fn move_absolute(&mut self, origin: Option<Coordinate>, z: Option<f64>) {
+    fn move_absolute(&mut self, origin: Option<Point>, z: Option<f64>) {
         match self {
             Object::Curve(x) => x.move_absolute(origin, z),
             Object::Point(x) => x.move_absolute(origin, z),
@@ -200,7 +200,7 @@ impl Translate for Object {
         }
     }
 
-    fn move_relative(&mut self, delta: Option<Coordinate>, z: Option<f64>) {
+    fn move_relative(&mut self, delta: Option<Point>, z: Option<f64>) {
         match self {
             Object::Curve(x) => x.move_relative(delta, z),
             Object::Point(x) => x.move_relative(delta, z),
@@ -289,55 +289,50 @@ pub struct ObjectModifier {
 }
 
 impl ObjectModifier {
-    pub fn move_relative(&mut self, delta: Coordinate) {
+    /// Apply relative move to origin correction vector
+    pub fn move_relative(&mut self, delta: Point) {
         self.default = 255.into();
-        self.modifiers.correction += delta;
+        self.modifiers.translate += delta;
     }
 
-    pub fn correction(&self) -> Coordinate {
-        self.modifiers.correction
+    /// Return correction to offset of object
+    pub fn translation(&self) -> Point {
+        self.modifiers.translate
     }
 
-    pub fn scale(&self) -> Coordinate {
-        Coordinate {
+    /// Returns scaling vector of object
+    pub fn scale(&self) -> Point {
+        Point {
             x: self.modifiers.x_scale,
             y: self.modifiers.y_scale,
         }
     }
 
-    pub fn skew(&self) -> Coordinate {
-        Coordinate {
+    /// Returns skewing vector of object
+    pub fn skew(&self) -> Point {
+        Point {
             x: self.modifiers.y_skew.asin(), // Radians from X axis
             y: self.modifiers.x_skew.asin(), // Radians from Y axis
         }
     }
 
-    /// Returns origin, width, and height of bounding box given two rectangular corners
-    pub fn corrected(&self, pt1: Coordinate, pt2: Coordinate) -> (Coordinate, f64, f64) {
-        debug!(
-            "Skew degree from X axis: {:.2}",
-            self.modifiers.x_skew.asin().to_degrees()
-        );
-        debug!(
-            "Skew degree from Y axis: {:.2}",
-            self.modifiers.y_skew.asin().to_degrees()
-        );
-
+    /// Returns origin, width, and height of bounding box after applying scale, skew, and  offset corrections
+    pub fn corrected(&self, pt1: Point, pt2: Point) -> (Point, f64, f64) {
         // Calculate 4 points of drawn rectangle
-        let drawn_pts: Vec<Coordinate> = vec![
-            Coordinate {
+        let corners: Vec<Point> = vec![
+            Point {
                 x: pt1.min(pt2).x,
                 y: pt1.min(pt2).y,
             },
-            Coordinate {
+            Point {
                 x: pt1.min(pt2).x,
                 y: pt1.max(pt2).y,
             },
-            Coordinate {
+            Point {
                 x: pt1.max(pt2).x,
                 y: pt1.max(pt2).y,
             },
-            Coordinate {
+            Point {
                 x: pt1.max(pt2).x,
                 y: pt1.min(pt2).y,
             },
@@ -345,63 +340,61 @@ impl ObjectModifier {
 
         debug!(
             "Drawn points: {}",
-            drawn_pts
+            corners
                 .iter()
                 .map(|x| format!("{x}"))
                 .collect_vec()
                 .join(", ")
         );
 
-        // Apply skew to 4 points of drawn rectangle
-        let skewed_pts: Vec<Coordinate> = drawn_pts
+        // Apply skew, then scale and translation
+        let corners: Vec<Point> = corners
             .iter()
-            .map(|pt| Coordinate {
-                x: pt.x + pt.y * self.modifiers.x_skew.asin().tan(),
-                y: pt.y + pt.x * self.modifiers.y_skew.asin().tan(),
+            .map(|pt| {
+                // TODO: WTF is this and why is it necessary?
+                // Seems to be correct as far as calculation of correct origin..
+                if self.modifiers.x_skew == 0.0 || self.modifiers.y_skew == 0.0 {
+                    Point {
+                        x: pt.x + pt.y * self.modifiers.x_skew,
+                        y: pt.y + pt.x * self.modifiers.y_skew,
+                    }
+                } else {
+                    Point {
+                        x: pt.x + pt.y * self.modifiers.x_skew.asin().tan(),
+                        y: pt.y + pt.x * self.modifiers.y_skew.asin().tan(),
+                    }
+                }
             })
+            .inspect(|pt| debug!("pt: {pt}"))
+            .map(|pt| pt * self.scale() + self.translation())
             .collect_vec();
 
         debug!(
-            "Skewed points: {}",
-            skewed_pts
-                .iter()
-                .map(|x| format!("{x}"))
-                .collect_vec()
-                .join(", ")
-        );
-
-        // Apply correction
-        let corrected_pts: Vec<Coordinate> = skewed_pts
-            .iter()
-            .map(|pt| *pt * self.scale() + self.correction())
-            .collect_vec();
-
-        debug!(
-            "Skewed points (corrected): {}",
-            corrected_pts
+            "Corrected points: {}",
+            corners
                 .iter()
                 .map(|x| format!("{x}"))
                 .collect_vec()
                 .join(", ")
         );
 
-        // Calculate bounds
-        let min_x: f64 = corrected_pts.iter().fold(f64::MAX, |acc, pt| acc.min(pt.x));
-        let max_x: f64 = corrected_pts.iter().fold(f64::MIN, |acc, pt| acc.max(pt.x));
-        let min_y: f64 = corrected_pts.iter().fold(f64::MAX, |acc, pt| acc.min(pt.y));
-        let max_y: f64 = corrected_pts.iter().fold(f64::MIN, |acc, pt| acc.max(pt.y));
+        // Calculate resulting bounds
+        let min_x: f64 = corners.iter().fold(f64::MAX, |acc, pt| acc.min(pt.x));
+        let max_x: f64 = corners.iter().fold(f64::MIN, |acc, pt| acc.max(pt.x));
+        let min_y: f64 = corners.iter().fold(f64::MAX, |acc, pt| acc.min(pt.y));
+        let max_y: f64 = corners.iter().fold(f64::MIN, |acc, pt| acc.max(pt.y));
 
-        // Compute corrected min/max bounding box
-        let bound_a: Coordinate = Coordinate { x: min_x, y: min_y };
-        let bound_b: Coordinate = Coordinate { x: max_x, y: max_y };
+        // Compute min/max bounding box
+        let bound_a: Point = Point { x: min_x, y: min_y };
+        let bound_b: Point = Point { x: max_x, y: max_y };
 
         debug!("Skewed bounding box (corrected): {}, {}", bound_a, bound_b);
 
         // Calculate origin of bounding box
-        let origin: Coordinate = (bound_a + bound_b) / Coordinate::from((2.0, 2.0));
+        let origin: Point = (bound_a + bound_b) / Point::from(2.0);
 
         // Calculate width and height of bounding box
-        let Coordinate {
+        let Point {
             x: width,
             y: height,
         } = bound_b - bound_a;
@@ -432,7 +425,7 @@ pub struct ScaleValues {
     pub y_scale: f64, // Y scaled ratio on drawn object
     #[br(assert(_unknown_4 == 0.0))]
     _unknown_4: f64,
-    correction: Coordinate, // Correction factor due to scaling
+    translate: Point, // Correction factor due to scaling
     #[br(assert(_unknown_5 == 1.0))]
     _unknown_5: f64,
 }
@@ -446,7 +439,7 @@ impl Default for ScaleValues {
             x_skew: 0.0.into(),
             y_scale: 1.0.into(),
             _unknown_4: 0.0.into(),
-            correction: (0.0, 0.0).into(),
+            translate: (0.0, 0.0).into(),
             _unknown_5: 1.0.into(),
         }
     }
@@ -460,7 +453,7 @@ impl Debug for ScaleValues {
             .field("y_scale", &self.y_scale)
             .field("x_skew", &self.x_skew)
             .field("y_skew", &self.y_skew)
-            .field("correction", &self.correction)
+            .field("translate", &self.translate)
             .finish()
     }
 }

+ 94 - 92
src/ezcad/objects/polygon.rs

@@ -1,92 +1,94 @@
-use std::fmt::{Debug, Display};
-
-use binrw::{BinRead, BinWrite};
-use diff::Diff;
-
-use crate::{
-    field_of::FieldOf,
-    types::{Coordinate, ObjectType, F64, U32},
-};
-
-use super::{ObjectCore, ObjectModifier, Translate};
-
-#[derive(BinRead, BinWrite, Clone, Debug, Diff, PartialEq)]
-#[diff(attr(
-    #[derive(Debug, PartialEq)]
-))]
-pub struct Polygon {
-    pub core: ObjectCore,
-    #[brw(magic(10u32))] // Number of following fields in struct
-    pub invert_shape: U32,
-    pub drawn_corner_a: FieldOf<Coordinate>,
-    pub drawn_corner_b: FieldOf<Coordinate>,
-    pub offset_cx: F64,
-    pub offset_cy: F64,
-    pub offset_dx: F64,
-    pub offset_dy: F64,
-    pub edges: U32,
-    pub modifier: ObjectModifier,
-}
-
-impl Display for Polygon {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        let width: f64 = (self.drawn_corner_a.x.max(self.drawn_corner_b.x)
-            - self.drawn_corner_a.x.min(self.drawn_corner_b.x))
-            * self.modifier.modifiers.x_scale;
-        let height: f64 = (self.drawn_corner_a.y.max(self.drawn_corner_b.y)
-            - self.drawn_corner_a.y.min(self.drawn_corner_b.y))
-            * self.modifier.modifiers.y_scale;
-
-        let drawn_origin: Coordinate =
-            (*self.drawn_corner_a + *self.drawn_corner_b) / Coordinate { x: 2.0, y: 2.0 };
-
-        write!(
-            f,
-            "{}, Origin: {}, Width: {:.2}, Height: {:.2}, Inverted: {}, Offset CX: {:.2}, Offset CY: {:.2}, Offset DX: {:.2}, Offset: DY: {:.2}, Edges: {}",
-            self.core,
-            self.modifier.correction() + drawn_origin * self.modifier.scale(),
-            width,
-            height,
-            *self.invert_shape != 0,
-            *self.offset_cx,
-            *self.offset_cy,
-            *self.offset_dx,
-            *self.offset_dy,
-            *self.edges,
-        )
-    }
-}
-
-impl Default for Polygon {
-    fn default() -> Self {
-        Self {
-            core: ObjectCore::default(ObjectType::Polygon),
-            invert_shape: 1.into(),
-            drawn_corner_a: Coordinate { x: 0.0, y: 0.0 }.into(),
-            drawn_corner_b: Coordinate { x: 0.0, y: 0.0 }.into(),
-            offset_cx: 0.0.into(),
-            offset_cy: 0.0.into(),
-            offset_dx: 0.0.into(),
-            offset_dy: 0.0.into(),
-            edges: 6.into(),
-            modifier: ObjectModifier::default(),
-        }
-    }
-}
-
-impl Translate for Polygon {
-    fn move_absolute(&mut self, origin: Option<Coordinate>, z: Option<f64>) {
-        origin.map(|origin| {
-            let delta: Coordinate = origin - *self.core.origin;
-            self.modifier.move_relative(delta);
-        });
-        self.core.move_absolute(origin, z);
-    }
-
-    fn move_relative(&mut self, delta: Option<Coordinate>, z: Option<f64>) {
-        delta.map(|delta| {
-            self.modifier.move_relative(delta);
-        });
-        self.core.move_relative(delta, z);
-    }
-}
+use std::fmt::{Debug, Display};
+
+use binrw::{BinRead, BinWrite};
+use diff::Diff;
+use log::warn;
+
+use crate::{
+    field_of::FieldOf,
+    types::{ObjectType, Point, F64, U32},
+};
+
+use super::{ObjectCore, ObjectModifier, Translate};
+
+#[derive(BinRead, BinWrite, Clone, Debug, Diff, PartialEq)]
+#[diff(attr(
+    #[derive(Debug, PartialEq)]
+))]
+pub struct Polygon {
+    pub core: ObjectCore,
+    #[brw(magic(10u32))] // Number of following fields in struct
+    pub invert_shape: U32,
+    pub drawn_corner_a: FieldOf<Point>,
+    pub drawn_corner_b: FieldOf<Point>,
+    pub offset_cx: F64,
+    pub offset_cy: F64,
+    pub offset_dx: F64,
+    pub offset_dy: F64,
+    pub edges: U32,
+    pub modifier: ObjectModifier,
+}
+
+impl Display for Polygon {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let (origin, width, height) = self
+            .modifier
+            .corrected(*self.drawn_corner_a, *self.drawn_corner_b);
+
+        if format!("{}", origin) != format!("{}", *self.core.origin) {
+            warn!(
+                "Origin mismatch! Core: {}, Calculated: {}",
+                *self.core.origin, origin
+            );
+        }
+
+        write!(
+            f,
+            "{}, Origin: {}, Width: {:.2}, Height: {:.2}, Inverted: {}, Offset CX: {:.2}, Offset CY: {:.2}, Offset DX: {:.2}, Offset: DY: {:.2}, Edges: {}",
+            self.core,
+            origin,
+            width,
+            height,
+            *self.invert_shape != 0,
+            *self.offset_cx,
+            *self.offset_cy,
+            *self.offset_dx,
+            *self.offset_dy,
+            *self.edges,
+        )
+    }
+}
+
+impl Default for Polygon {
+    fn default() -> Self {
+        Self {
+            core: ObjectCore::default(ObjectType::Polygon),
+            invert_shape: 1.into(),
+            drawn_corner_a: Point { x: 0.0, y: 0.0 }.into(),
+            drawn_corner_b: Point { x: 0.0, y: 0.0 }.into(),
+            offset_cx: 0.0.into(),
+            offset_cy: 0.0.into(),
+            offset_dx: 0.0.into(),
+            offset_dy: 0.0.into(),
+            edges: 6.into(),
+            modifier: ObjectModifier::default(),
+        }
+    }
+}
+
+impl Translate for Polygon {
+    fn move_absolute(&mut self, origin: Option<Point>, z: Option<f64>) {
+        origin.map(|origin| {
+            let delta: Point = origin - *self.core.origin;
+            self.modifier.move_relative(delta);
+        });
+        self.core.move_absolute(origin, z);
+    }
+
+    fn move_relative(&mut self, delta: Option<Point>, z: Option<f64>) {
+        delta.map(|delta| {
+            self.modifier.move_relative(delta);
+        });
+        self.core.move_relative(delta, z);
+    }
+}

+ 8 - 8
src/ezcad/objects/rectangle.rs

@@ -6,7 +6,7 @@ use log::warn;
 
 use crate::{
     field_of::FieldOf,
-    types::{Coordinate, ObjectType, F64},
+    types::{ObjectType, Point, F64},
 };
 
 use super::{ObjectCore, ObjectModifier, Translate};
@@ -18,8 +18,8 @@ use super::{ObjectCore, ObjectModifier, Translate};
 pub struct Rectangle {
     pub core: ObjectCore,
     #[brw(magic(8u32))] // Number of following fields in struct
-    pub drawn_corner_a: FieldOf<Coordinate>,
-    pub drawn_corner_b: FieldOf<Coordinate>,
+    pub drawn_corner_a: FieldOf<Point>,
+    pub drawn_corner_b: FieldOf<Point>,
     pub round_bottom_left: F64,
     pub round_bottom_right: F64,
     pub round_top_right: F64,
@@ -52,8 +52,8 @@ impl Default for Rectangle {
     fn default() -> Self {
         Self {
             core: ObjectCore::default(ObjectType::Rectangle),
-            drawn_corner_a: Coordinate { x: 0.0, y: 0.0 }.into(),
-            drawn_corner_b: Coordinate { x: 0.0, y: 0.0 }.into(),
+            drawn_corner_a: Point { x: 0.0, y: 0.0 }.into(),
+            drawn_corner_b: Point { x: 0.0, y: 0.0 }.into(),
             round_bottom_left: 0.0.into(),
             round_bottom_right: 0.0.into(),
             round_top_right: 0.0.into(),
@@ -66,15 +66,15 @@ impl Default for Rectangle {
 // origin_x = x_corretion + (drawn_a.x + drawn_b.x) / 2 * x_scale
 // x_correction = origin_x - (drawn_a.x + drawn_b.x) / 2 * x_scale
 impl Translate for Rectangle {
-    fn move_absolute(&mut self, origin: Option<Coordinate>, z: Option<f64>) {
+    fn move_absolute(&mut self, origin: Option<Point>, z: Option<f64>) {
         origin.map(|origin| {
-            let delta: Coordinate = origin - *self.core.origin;
+            let delta: Point = origin - *self.core.origin;
             self.modifier.move_relative(delta);
         });
         self.core.move_absolute(origin, z);
     }
 
-    fn move_relative(&mut self, delta: Option<Coordinate>, z: Option<f64>) {
+    fn move_relative(&mut self, delta: Option<Point>, z: Option<f64>) {
         delta.map(|delta| {
             self.modifier.move_relative(delta);
         });

+ 28 - 22
src/ezcad/types.rs

@@ -134,76 +134,76 @@ impl Rgba {
     #[derive(Debug, PartialEq)]
 ))]
 #[serde(rename_all = "PascalCase")]
-pub struct Coordinate {
+pub struct Point {
     pub x: f64,
     pub y: f64,
 }
 
-impl Display for Coordinate {
+impl Display for Point {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         write!(f, "({:.2}, {:.2})", self.x, self.y)
     }
 }
 
-impl Sub for Coordinate {
-    type Output = Coordinate;
+impl Sub for Point {
+    type Output = Point;
 
     fn sub(self, rhs: Self) -> Self::Output {
-        Coordinate {
+        Point {
             x: self.x - rhs.x,
             y: self.y - rhs.y,
         }
     }
 }
 
-impl SubAssign for Coordinate {
+impl SubAssign for Point {
     fn sub_assign(&mut self, rhs: Self) {
         self.x -= rhs.x;
         self.y -= rhs.y;
     }
 }
 
-impl Add for Coordinate {
-    type Output = Coordinate;
+impl Add for Point {
+    type Output = Point;
 
     fn add(self, rhs: Self) -> Self::Output {
-        Coordinate {
+        Point {
             x: self.x + rhs.x,
             y: self.y + rhs.y,
         }
     }
 }
 
-impl AddAssign for Coordinate {
+impl AddAssign for Point {
     fn add_assign(&mut self, rhs: Self) {
         self.x += rhs.x;
         self.y += rhs.y;
     }
 }
 
-impl Mul for Coordinate {
-    type Output = Coordinate;
+impl Mul for Point {
+    type Output = Point;
 
     fn mul(self, rhs: Self) -> Self::Output {
-        Coordinate {
+        Point {
             x: self.x * rhs.x,
             y: self.y * rhs.y,
         }
     }
 }
 
-impl Div for Coordinate {
-    type Output = Coordinate;
+impl Div for Point {
+    type Output = Point;
 
     fn div(self, rhs: Self) -> Self::Output {
-        Coordinate {
+        Point {
             x: self.x / rhs.x,
             y: self.y / rhs.y,
         }
     }
 }
 
-impl From<(f64, f64)> for Coordinate {
+impl From<(f64, f64)> for Point {
     fn from(value: (f64, f64)) -> Self {
         Self {
             x: value.0,
@@ -212,16 +212,22 @@ impl From<(f64, f64)> for Coordinate {
     }
 }
 
-impl Coordinate {
-    pub fn min(&self, rhs: Coordinate) -> Coordinate {
-        Coordinate {
+impl From<f64> for Point {
+    fn from(value: f64) -> Self {
+        Self { x: value, y: value }
+    }
+}
+
+impl Point {
+    pub fn min(&self, rhs: Point) -> Point {
+        Point {
             x: self.x.min(rhs.x),
             y: self.y.min(rhs.y),
         }
     }
 
-    pub fn max(&self, rhs: Coordinate) -> Coordinate {
-        Coordinate {
+    pub fn max(&self, rhs: Point) -> Point {
+        Point {
             x: self.x.max(rhs.x),
             y: self.y.max(rhs.y),
         }