Browse Source

Object scalers

Kevin Lee 4 months ago
parent
commit
68dcc94161

BIN
samples/HatchRectangleSmall.mlp


BIN
samples/HatchRectangleSmall2.mlp


+ 53 - 28
samples/config.yml

@@ -1,29 +1,54 @@
 Ops: 
-  - !ClonePen
-    From: 0
-    To: 255
-    Inclusive: True
-
-  - !PatchPen
-    Pen: 0
-    Power: 10.0
-
-  - !PatternPen
-    From: 0
-    To: 40
-    Field: !Power 2
-
-  - !DeleteObjects
-    Layer: 0
-
-  - !HatchArray
-    Layer: 0
-    Width: 3.0
-    Height: 2.0
-    Columns: 8
-    Rows: 5
-    Spacing: 0.5
-    Z: 0.0
-    StartingPen: 0
-    Hatch:
-      LineSpacing: 0.1
+  # - !ClonePen
+  #   From: 0
+  #   To: 255
+  #   Inclusive: True
+
+  # - !PatchPen
+  #   Pen: 0
+  #   Power: 10.0
+
+  # - !PatternPen
+  #   From: 0
+  #   To: 40
+  #   Field: !Power 2
+
+  # - !DeleteObjects
+  #   Layer: 0
+
+  # - !HatchArray
+  #   Layer: 0
+  #   Width: 3.0
+  #   Height: 2.0
+  #   Columns: 8
+  #   Rows: 5
+  #   Spacing: 0.5
+  #   Z: 0.0
+  #   StartingPen: 0
+  #   Hatch:
+  #     LineSpacing: 0.1
+
+  # - !Object
+  #   Input: !Existing { Layer: 0, Object: 0 }
+  #   Export: 'export.bin'
+
+  - !ObjectOperation
+    Input: !Existing { Layer: 0, Object: 0 }
+    Z: 1.0
+    Origin: { X: 10.0, Y: 10.0 }
+    Pen: 1
+    Array:
+      Columns: 3
+      Rows: 2
+      Spacing: 6.0
+      RandomizeOrder: False
+      StartingPen: 0
+    # Hatch:
+    #   LineSpacing: 0.01
+    ReplaceObject: 0
+
+  # - !Object
+  #   Input: !Circle { Radius: 3.0 }
+
+  # - !Object
+  #   Input: !Import { Path: 'export.bin' }

+ 8 - 11
src/config/mod.rs

@@ -1,7 +1,10 @@
 use ezcad::{array_of::ArrayOf, layer::Layer, pen::Pen};
 use serde::{Deserialize, Serialize};
 
-use self::pen::{ClonePen, ImportExportPen, PatchPen, PatternPen};
+use self::{
+    object::{DeleteObjects, ObjectOperation},
+    pen::{ClonePen, ImportExportPen, PatchPen, PatternPen},
+};
 
 pub mod object;
 pub mod pen;
@@ -13,11 +16,8 @@ pub enum Operation {
     PatternPen(PatternPen),
     ExportPen(ImportExportPen),
     ImportPen(ImportExportPen),
-    // ExportObject(ExportObject),
-    // ImportObject(ImportObject),
-    // DeleteObjects(DeleteObjects),
-    // MoveObject(MoveObject),
-    // Array(Array),
+    DeleteObjects(DeleteObjects),
+    ObjectOperation(ObjectOperation),
 }
 
 pub trait Operations {
@@ -33,11 +33,8 @@ impl Operations for Vec<Operation> {
                 Operation::PatternPen(x) => x.pattern(pens),
                 Operation::ImportPen(x) => x.import(pens),
                 Operation::ExportPen(x) => x.export(pens),
-                // Operation::ExportObject(x) => x.export(layers),
-                // Operation::ImportObject(x) => x.import(layers),
-                // Operation::DeleteObjects(x) => x.delete(layers),
-                // Operation::MoveObject(x) => x.r#move(layers),
-                // Operation::Array(x) => x.generate(pens, layers),
+                Operation::DeleteObjects(x) => x.delete(layers),
+                Operation::ObjectOperation(x) => x.process(pens, layers),
             }
         }
     }

+ 110 - 146
src/config/object.rs

@@ -1,10 +1,5 @@
-use std::{
-    fs::File,
-    io::{Cursor, Read, Write},
-    path::PathBuf,
-};
+use std::path::PathBuf;
 
-use binrw::{BinRead, BinWriterExt};
 use ezcad::{
     array_of::ArrayOf,
     layer::Layer,
@@ -15,11 +10,11 @@ use ezcad::{
         Object, ObjectCore, Translate,
     },
     pen::Pen,
-    types::{ObjectType, Point},
+    types::{ObjectType, Coordinate},
 };
-use log::{debug, warn};
+use log::{debug, error, warn};
 use rand::{seq::SliceRandom, thread_rng};
-use serde::{de, Deserialize, Serialize};
+use serde::{Deserialize, Serialize};
 
 #[derive(Debug, Serialize, Deserialize)]
 #[serde(rename_all = "PascalCase")]
@@ -56,6 +51,7 @@ impl DeleteObjects {
 pub struct HatchConfig {
     line_spacing: f64,
 
+    pen: Option<u32>,
     count: Option<u32>,
     edge_offset: Option<f64>,
     start_offset: Option<f64>,
@@ -90,6 +86,7 @@ impl From<HatchConfig> for HatchSetting {
         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());
@@ -120,21 +117,18 @@ pub struct ArrayConfig {
 #[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>,
     },
-    Circle {
-        radius: f64,
-    },
-    Import {
-        path: PathBuf,
-    },
-    Existing {
-        layer: usize,
-        object: usize,
-    },
+    #[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 {
@@ -145,8 +139,8 @@ impl InputObject {
                 height,
                 round_corner,
             } => Object::Rectangle(Rectangle {
-                corner_a: Point::from((-width / 2.0, -height / 2.0)).into(),
-                corner_b: Point::from((width / 2.0, height / 2.0)).into(),
+                corner_a: Coordinate::from((-width / 2.0, -height / 2.0)).into(),
+                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(),
@@ -175,7 +169,7 @@ impl InputObject {
 pub struct ObjectOperation {
     input: InputObject,
     z: Option<f64>,
-    origin: Option<Point>,
+    origin: Option<Coordinate>,
     pen: Option<u32>,
     layer: Option<usize>,
     array: Option<ArrayConfig>,
@@ -186,19 +180,81 @@ pub struct ObjectOperation {
 
 impl ObjectOperation {
     pub fn process(&self, pens: &Vec<Pen>, layers: &mut ArrayOf<Layer>) {
-        debug!("Processing object {}", self.input);
+        debug!("Begin processing of object {:?}", self.input);
 
         let mut object: Object = self.input.new(layers);
 
         // Process basic transformation
-        object.translate(self.origin, self.z);
-        self.pen.map(|pen| object.set_pen(pen));
+        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 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().origin,
+                            z: object.core().z,
+                            ..ObjectCore::default(ObjectType::Hatch)
+                        },
+                        outline: vec![object].into(),
+                        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(),
+                        }),
+                    })
+                }
+                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(vec![object.clone()], |array| {
-            let bottom_left: Point = Point {
+            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,
             };
@@ -209,7 +265,7 @@ impl ObjectOperation {
                 let x_pos: f64 = (index % array.columns) as f64;
                 let y_pos: f64 = (index / array.columns) as f64;
 
-                Point {
+                Coordinate {
                     x: bottom_left.x + array.spacing * x_pos,
                     y: bottom_left.y + array.spacing * y_pos,
                 }
@@ -225,142 +281,50 @@ impl ObjectOperation {
             let mut new_obj: Vec<Object> = vec![];
 
             for obj_idx in seq.into_iter() {
+                let delta: Coordinate = calc_pt(obj_idx);
+                let pen: u32 = array.starting_pen + u32::try_from(obj_idx).unwrap();
+
                 let mut object: Object = object.clone();
-                object.translate(Some(calc_pt(obj_idx)), None);
-                object.set_pen(array.starting_pen + u32::try_from(obj_idx).unwrap());
+                object.move_relative(Some(delta), None);
+                object.set_pen(pen);
 
+                debug!(
+                    "Adding new array instance at {} with pen #{}",
+                    object.core().origin,
+                    pen
+                );
                 new_obj.push(object);
             }
 
             new_obj
         });
 
-        // Process export of object
-        self.export.as_ref().map(|path| {
+        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 the first object in list of objects");
             } else {
                 debug!(
-                    "Exporting object {} to '{}'",
+                    "Exporting object {} in layer #{} to '{}'",
                     new_objects[0],
+                    layer_id,
                     path.to_string_lossy()
                 );
             }
             new_objects[0].write_to_file(path);
-        });
-
-        // Append or replace object in layer
-        let layer: &mut Layer = layers
-            .get_mut(self.layer.unwrap_or(0))
-            .expect("Invalid layer index");
-        match self.replace_object {
-            Some(object) => {
-                // debug!("Replacing object #{} with {}", object, new_objects);
-                layer.objects.splice(object..=object, new_objects);
-            }
-            None => {
-                layer.objects.extend(new_objects);
-            }
+        } 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);
         }
     }
 }
-
-// #[derive(Debug, Serialize, Deserialize)]
-// #[serde(rename_all = "PascalCase")]
-// pub struct Array {
-//     layer: usize,
-//     z: f64,
-//     columns: usize,
-//     rows: usize,
-//     spacing: f64,
-//     starting_pen: usize,
-//     object: InputObject,
-//     hatch: Option<HatchConfig>,
-// }
-
-// impl Array {
-//     pub fn generate(&self, pens: &Vec<Pen>, layers: &mut ArrayOf<Layer>) {
-//         debug!(
-//             "Generating {} x {} array of {:?} with spacing of {} starting from pen #{} on layer #{}",
-//             self.columns, self.rows, self.object, self.spacing, self.starting_pen, self.layer
-//         );
-//         assert!(
-//             self.rows >= 1 && self.columns >= 1,
-//             "Invalid row/column value"
-//         );
-//         assert!(
-//             self.starting_pen + (self.rows * self.columns) < pens.len(),
-//             "Invalid starting pen"
-//         );
-
-//         let layer: &mut Layer = layers.get_mut(self.layer).expect("Invalid layer index");
-
-//         let bottom_left: Point = Point {
-//             x: (self.columns - 1) as f64 * self.spacing / -2.0,
-//             y: (self.rows - 1) as f64 * self.spacing / -2.0,
-//         };
-
-//         // Closure that returns origin point of given index in array, where index starts
-//         // from bottom left and increments to the right and wraps around to the row above
-//         let calc_pt = |index: usize| {
-//             let x_pos: f64 = (index % self.columns) as f64;
-//             let y_pos: f64 = (index / self.columns) as f64;
-
-//             Point {
-//                 x: bottom_left.x + self.spacing * x_pos,
-//                 y: bottom_left.y + self.spacing * y_pos,
-//             }
-//         };
-
-//         // Randomize draw order
-//         let mut seq: Vec<usize> = (0..(self.rows * self.columns)).collect();
-//         seq.shuffle(&mut thread_rng());
-
-//         // Generate objects and append to layer
-//         for obj_idx in seq.into_iter() {
-//             let pen: u32 = (self.starting_pen + obj_idx).try_into().unwrap();
-
-//             //     // Build outline object
-//             //     let mut rect: Rectangle = Rectangle::default();
-//             //     let origin: Point = calc_pt(obj_idx);
-
-//             //     *rect.corner_a = Point {
-//             //         x: origin.x - self.width / 2.0,
-//             //         y: origin.y - self.height / 2.0,
-//             //     };
-//             //     *rect.corner_b = Point {
-//             //         x: origin.x + self.width / 2.0,
-//             //         y: origin.y + self.height / 2.0,
-//             //     };
-//             //     *rect.core.origin = origin;
-//             //     *rect.core.pen = pen;
-//             //     *rect.core.z = self.z;
-
-//             //     debug!(
-//             //         "Adding hatched rectangle with pen #{} at {} (from {} to {})",
-//             //         *rect.core.pen, *rect.core.origin, *rect.corner_a, *rect.corner_b
-//             //     );
-
-//             //     let mut hatch_setting: HatchSetting = self.hatch.into();
-//             //     *hatch_setting.pen = pen;
-
-//             //     // Build hatch object
-//             //     let hatch: Hatch = Hatch {
-//             //         core: ObjectCore {
-//             //             origin: origin.into(),
-//             //             z: self.z.into(),
-//             //             ..ObjectCore::default(ObjectType::Hatch)
-//             //         },
-//             //         outline: vec![Object::Rectangle(rect)].into(),
-//             //         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(),
-//             //         }),
-//             //     };
-
-//             //     layer.objects.push(Object::Hatch(hatch));
-//         }
-//     }
-// }

+ 14 - 17
src/ezcad/objects/circle.rs

@@ -5,10 +5,10 @@ use diff::Diff;
 
 use crate::{
     field_of::FieldOf,
-    types::{Field, ObjectType, Point, F64, U32},
+    types::{ObjectType, Coordinate, F64, U32},
 };
 
-use super::{ObjectCore, Translate};
+use super::{ObjectCore, Translate, ObjectModified};
 
 #[cfg_attr(feature = "default-debug", derive(Debug))]
 #[derive(BinRead, BinWrite, Clone, Diff, PartialEq)]
@@ -18,11 +18,11 @@ use super::{ObjectCore, Translate};
 pub struct Circle {
     pub core: ObjectCore,
     #[brw(magic(6u32))] // Number of following fields in struct
-    pub origin: FieldOf<Point>,
+    pub origin: FieldOf<Coordinate>,
     pub radius: F64,
     pub start_angle: F64, // Radians
     pub clockwise: U32,
-    pub _unknown_1: [Field; 2],
+    pub modified: ObjectModified,
 }
 
 // Custom Debug implementation to only print known fields
@@ -43,26 +43,23 @@ impl Default for Circle {
     fn default() -> Self {
         Self {
             core: ObjectCore::default(ObjectType::Circle),
-            origin: Point { x: 0.0, y: 0.0 }.into(),
+            origin: Coordinate { x: 0.0, y: 0.0 }.into(),
             radius: 0.0.into(),
             start_angle: 0.0.into(),
             clockwise: 0.into(),
-            _unknown_1: [
-                vec![0, 0, 0, 0].into(), // 0_u32
-                vec![
-                    0, 0, 0, 0, 0, 0, 240, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 63,
-                ] // ???
-                .into(),
-            ],
+            modified: ObjectModified::default(),
         }
     }
 }
 
 impl Translate for Circle {
-    fn translate(&mut self, origin: Option<Point>, z: Option<f64>) {
-        self.core.translate(origin, z);
-        origin.map(|origin| self.origin = origin.into());
+    fn move_absolute(&mut self, origin: Option<Coordinate>, z: Option<f64>) {
+        origin.map(|origin| *self.origin = origin);
+        self.core.move_absolute(origin, z);
+    }
+
+    fn move_relative(&mut self, delta: Option<Coordinate>, z: Option<f64>) {
+        delta.map(|delta| *self.origin += delta);
+        self.core.move_relative(delta, z);
     }
 }

+ 21 - 20
src/ezcad/objects/ellipse.rs

@@ -5,10 +5,10 @@ use diff::Diff;
 
 use crate::{
     field_of::FieldOf,
-    types::{Field, ObjectType, Point, F64, U32},
+    types::{ObjectType, Coordinate, F64, U32},
 };
 
-use super::{ObjectCore, Translate};
+use super::{ObjectCore, Translate, ObjectModified};
 
 #[cfg_attr(feature = "default-debug", derive(Debug))]
 #[derive(BinRead, BinWrite, Clone, Diff, PartialEq)]
@@ -19,11 +19,11 @@ pub struct Ellipse {
     pub core: ObjectCore,
     #[brw(magic(8u32))] // Number of following fields in struct
     pub clockwise: U32,
-    pub corner_a: FieldOf<Point>,
-    pub corner_b: FieldOf<Point>,
+    pub corner_a: FieldOf<Coordinate>,
+    pub corner_b: FieldOf<Coordinate>,
     pub start_angle: F64, // Radians
     pub end_angle: F64,   // Radians
-    pub _unknown_1: [Field; 2],
+    pub modified: ObjectModified,
     pub open_curve: U32,
 }
 
@@ -48,30 +48,31 @@ impl Default for Ellipse {
         Self {
             core: ObjectCore::default(ObjectType::Ellipse),
             clockwise: 0.into(),
-            corner_a: Point { x: 0.0, y: 0.0 }.into(),
-            corner_b: Point { x: 0.0, y: 0.0 }.into(),
+            corner_a: Coordinate { x: 0.0, y: 0.0 }.into(),
+            corner_b: Coordinate { x: 0.0, y: 0.0 }.into(),
             start_angle: 0.0.into(),
             end_angle: 0.0.into(),
-            _unknown_1: [
-                vec![0, 0, 0, 0].into(), // 0_u32
-                vec![
-                    0, 0, 0, 0, 0, 0, 240, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 63,
-                ] // ???
-                .into(),
-            ],
+            modified: ObjectModified::default(),
             open_curve: 0.into(),
         }
     }
 }
 
 impl Translate for Ellipse {
-    fn translate(&mut self, origin: Option<Point>, z: Option<f64>) {
-        self.core.translate(origin, z);
+    fn move_absolute(&mut self, origin: Option<Coordinate>, z: Option<f64>) {
         origin.map(|origin| {
-            *self.corner_a += origin;
-            *self.corner_b += origin;
+            let delta: Coordinate = *self.core.origin - origin;
+            *self.corner_a += delta;
+            *self.corner_b += delta;
         });
+        self.core.move_absolute(origin, z);
+    }
+
+    fn move_relative(&mut self, delta: Option<Coordinate>, z: Option<f64>) {
+        delta.map(|delta| {
+            *self.corner_a += delta;
+            *self.corner_b += delta;
+        });
+        self.core.move_relative(delta, z);
     }
 }

+ 9 - 5
src/ezcad/objects/hatch.rs

@@ -3,7 +3,7 @@ use std::fmt::Debug;
 use crate::{
     array_of::ArrayOf,
     field_of::FieldOf,
-    types::{Field, Point, F64, U32},
+    types::{Field, Coordinate, F64, U32},
 };
 use binrw::{binrw, BinRead, BinWrite};
 use diff::Diff;
@@ -354,9 +354,13 @@ pub struct Hatch {
 }
 
 impl Translate for Hatch {
-    fn translate(&mut self, origin: Option<Point>, z: Option<f64>) {
-        self.core.translate(origin, z);
-        self.outline.iter_mut().for_each(|x| x.translate(origin, z));
-        // Origin of individual hatch lines is always (0, 0)
+    fn move_absolute(&mut self, origin: Option<Coordinate>, z: Option<f64>) {
+        self.outline.iter_mut().for_each(|x| x.move_absolute(origin, z));
+        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));
+        self.core.move_relative(delta, z);
     }
 }

+ 28 - 16
src/ezcad/objects/line.rs

@@ -3,7 +3,7 @@ use std::fmt::Debug;
 use binrw::{binrw, BinRead, BinWrite};
 use diff::Diff;
 
-use crate::{array_of::ArrayOf, types::Point};
+use crate::{array_of::ArrayOf, types::Coordinate};
 
 use super::{ObjectCore, Translate};
 
@@ -14,11 +14,11 @@ use super::{ObjectCore, Translate};
 ))]
 pub enum LineType {
     #[brw(magic = 0x0001u16)]
-    Point { _zero: u32, point: Point },
+    Point { _zero: u32, point: Coordinate },
     #[brw(magic = 0x0100u16)]
-    Line { _zero: u32, points: ArrayOf<Point> },
+    Line { _zero: u32, points: ArrayOf<Coordinate> },
     #[brw(magic = 0x0300u16)]
-    Bezier { _zero: u32, points: ArrayOf<Point> },
+    Bezier { _zero: u32, points: ArrayOf<Coordinate> },
 }
 
 // Custom Debug implementation to only print known fields
@@ -35,17 +35,19 @@ impl Debug for LineType {
     }
 }
 
-impl Translate for LineType {
-    fn translate(&mut self, origin: Option<Point>, _z: Option<f64>) {
-        origin.map(|origin| match self {
-            LineType::Point { _zero, point } => *point = origin,
+impl LineType {
+    fn move_relative(&mut self, delta: Coordinate) {
+        match self {
+            LineType::Point { _zero, point } => {
+                *point += delta;
+            },
             LineType::Line { _zero, points } => {
-                points.iter_mut().for_each(|pt| *pt += origin);
-            }
+                points.iter_mut().for_each(|pt| *pt += delta);
+            },
             LineType::Bezier { _zero, points } => {
-                points.iter_mut().for_each(|pt| *pt += origin);
-            }
-        });
+                points.iter_mut().for_each(|pt| *pt += delta);
+            },
+        }
     }
 }
 
@@ -68,8 +70,18 @@ pub struct Lines {
 }
 
 impl Translate for Lines {
-    fn translate(&mut self, origin: Option<Point>, z: Option<f64>) {
-        self.core.translate(origin, z);
-        self.lines.iter_mut().for_each(|x| x.translate(origin, z));
+    fn move_absolute(&mut self, origin: Option<Coordinate>, z: Option<f64>) {
+        origin.map(|origin| {
+            let delta: Coordinate = *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>) {
+        delta.map(|delta| {
+            self.lines.iter_mut().for_each(|x| x.move_relative(delta));
+        });
+        self.core.move_relative(delta, z);
     }
 }

+ 118 - 16
src/ezcad/objects/mod.rs

@@ -7,7 +7,6 @@ use std::{
 
 use binrw::{BinRead, BinWrite, BinWriterExt};
 use diff::Diff;
-use log::error;
 use modular_bitfield::{
     bitfield,
     specifiers::{B1, B14},
@@ -15,7 +14,7 @@ use modular_bitfield::{
 
 use crate::{
     field_of::FieldOf,
-    types::{Field, ObjectType, Point, WString, F64, U16, U32},
+    types::{Coordinate, Field, ObjectType, WString, F64, U16, U32},
 };
 
 use self::{
@@ -32,7 +31,8 @@ pub mod rectangle;
 
 pub trait Translate {
     /// Move origin of object to point
-    fn translate(&mut self, origin: Option<Point>, z: Option<f64>);
+    fn move_absolute(&mut self, origin: Option<Coordinate>, z: Option<f64>);
+    fn move_relative(&mut self, delta: Option<Coordinate>, z: Option<f64>);
 }
 
 #[bitfield(bits = 16)]
@@ -66,7 +66,7 @@ pub struct ObjectCore {
     pub io_control_enable_mask: U16,
     pub io_control_disable_mask: U16,
     pub _unknown_2: [Field; 5],
-    pub origin: FieldOf<Point>,
+    pub origin: FieldOf<Coordinate>,
     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)
@@ -93,10 +93,15 @@ impl Debug for ObjectCore {
 }
 
 impl Translate for ObjectCore {
-    fn translate(&mut self, origin: Option<Point>, z: Option<f64>) {
-        origin.map(|origin| *self.origin += origin);
+    fn move_absolute(&mut self, origin: Option<Coordinate>, 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>) {
+        delta.map(|delta| *self.origin += delta);
+        z.map(|z| *self.z += z);
+    }
 }
 
 impl ObjectCore {
@@ -121,7 +126,7 @@ impl ObjectCore {
                 vec![0, 0, 0, 0, 0, 0, 36, 64].into(), // 10.0_f64
             ]
             .into(),
-            origin: Point { x: 0.0, y: 0.0 }.into(),
+            origin: Coordinate { x: 0.0, y: 0.0 }.into(),
             z: 0.0.into(),
             a: 0.0.into(),
             index: 0.into(),
@@ -151,15 +156,27 @@ pub enum Object {
 }
 
 impl Translate for Object {
-    fn translate(&mut self, origin: Option<Point>, z: Option<f64>) {
+    fn move_absolute(&mut self, origin: Option<Coordinate>, z: Option<f64>) {
+        match self {
+            Object::Curve(x) => x.move_absolute(origin, z),
+            Object::Point(x) => x.move_absolute(origin, z),
+            Object::Rectangle(x) => x.move_absolute(origin, z),
+            Object::Circle(x) => x.move_absolute(origin, z),
+            Object::Ellipse(x) => x.move_absolute(origin, z),
+            Object::Polygon(x) => x.move_absolute(origin, z),
+            Object::Hatch(x) => x.move_absolute(origin, z),
+        }
+    }
+
+    fn move_relative(&mut self, delta: Option<Coordinate>, z: Option<f64>) {
         match self {
-            Object::Curve(x) => x.translate(origin, z),
-            Object::Point(x) => x.translate(origin, z),
-            Object::Rectangle(x) => x.translate(origin, z),
-            Object::Circle(x) => x.translate(origin, z),
-            Object::Ellipse(x) => x.translate(origin, z),
-            Object::Polygon(x) => x.translate(origin, z),
-            Object::Hatch(x) => x.translate(origin, z),
+            Object::Curve(x) => x.move_relative(delta, z),
+            Object::Point(x) => x.move_relative(delta, z),
+            Object::Rectangle(x) => x.move_relative(delta, z),
+            Object::Circle(x) => x.move_relative(delta, z),
+            Object::Ellipse(x) => x.move_relative(delta, z),
+            Object::Polygon(x) => x.move_relative(delta, z),
+            Object::Hatch(x) => x.move_relative(delta, z),
         }
     }
 }
@@ -194,7 +211,92 @@ impl Object {
             Object::Circle(x) => x.core.pen = pen.into(),
             Object::Ellipse(x) => x.core.pen = pen.into(),
             Object::Polygon(x) => x.core.pen = pen.into(),
-            Object::Hatch(x) => error!("Cannot set universal pen for hatch"),
+            Object::Hatch(x) => {
+                x.core.pen = pen.into();
+                x.hatch_settings.iter_mut().for_each(|y| y.pen = pen.into());
+                x.legacy_setting.hatch_0_pen = pen.into();
+                x.legacy_setting.hatch_1_pen = pen.into();
+                x.legacy_setting.hatch_2_pen = pen.into();
+            }
+        }
+    }
+
+    pub fn core(&mut self) -> &mut ObjectCore {
+        match self {
+            Object::Curve(x) => &mut x.core,
+            Object::Point(x) => &mut x.core,
+            Object::Rectangle(x) => &mut x.core,
+            Object::Circle(x) => &mut x.core,
+            Object::Ellipse(x) => &mut x.core,
+            Object::Polygon(x) => &mut x.core,
+            Object::Hatch(x) => &mut x.core,
         }
     }
 }
+
+#[derive(BinRead, BinWrite, Clone, Debug, Diff, PartialEq)]
+#[diff(attr(
+    #[derive(Debug, PartialEq)]
+))]
+pub struct ObjectModified {
+    pub default: U32, // 0 if values are default, 255 otherwise
+    pub changes: FieldOf<ScaleValues>,
+}
+
+impl Default for ObjectModified {
+    fn default() -> Self {
+        Self {
+            default: 0.into(),
+            changes: ScaleValues::default().into(),
+        }
+    }
+}
+
+#[derive(BinRead, BinWrite, Clone, Diff, PartialEq)]
+#[diff(attr(
+    #[derive(Debug, PartialEq)]
+))]
+pub struct ScaleValues {
+    pub width_scale: f64,
+    #[br(assert(_unknown_1 == 0.0))]
+    _unknown_1: f64,
+    #[br(assert(_unknown_2 == 0.0))]
+    _unknown_2: f64,
+    #[br(assert(_unknown_3 == 0.0))]
+    _unknown_3: f64,
+    pub height_scale: f64,
+    #[br(assert(_unknown_4 == 0.0))]
+    _unknown_4: f64,
+    pub origin_x_delta: f64,
+    pub origin_y_delta: f64,
+    #[br(assert(_unknown_5 == 1.0))]
+    _unknown_5: f64,
+}
+
+impl Default for ScaleValues {
+    fn default() -> Self {
+        Self {
+            width_scale: 1.0.into(),
+            _unknown_1: 0.0.into(),
+            _unknown_2: 0.0.into(),
+            _unknown_3: 0.0.into(),
+            height_scale: 1.0.into(),
+            _unknown_4: 0.0.into(),
+            origin_x_delta: 0.0.into(),
+            origin_y_delta: 0.0.into(),
+            _unknown_5: 1.0.into(),
+        }
+    }
+}
+
+// Custom Debug implementation to only print known fields
+impl Debug for ScaleValues {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        f.debug_struct("ScaleValues")
+            .field("width_scale", &self.width_scale)
+            .field("height_scale", &self.height_scale)
+            .field("origin_x_delta", &self.origin_x_delta)
+            .field("origin_y_delta", &self.origin_y_delta)
+            .finish()
+    }
+}

+ 21 - 20
src/ezcad/objects/polygon.rs

@@ -5,10 +5,10 @@ use diff::Diff;
 
 use crate::{
     field_of::FieldOf,
-    types::{Field, ObjectType, Point, F64, U32},
+    types::{ObjectType, Coordinate, F64, U32},
 };
 
-use super::{ObjectCore, Translate};
+use super::{ObjectCore, Translate, ObjectModified};
 
 #[cfg_attr(feature = "default-debug", derive(Debug))]
 #[derive(BinRead, BinWrite, Clone, Diff, PartialEq)]
@@ -19,14 +19,14 @@ pub struct Polygon {
     pub core: ObjectCore,
     #[brw(magic(10u32))] // Number of following fields in struct
     pub invert_shape: U32,
-    pub corner_a: FieldOf<Point>,
-    pub corner_b: FieldOf<Point>,
+    pub corner_a: FieldOf<Coordinate>,
+    pub corner_b: FieldOf<Coordinate>,
     pub offset_cx: F64,
     pub offset_cy: F64,
     pub offset_dx: F64,
     pub offset_dy: F64,
     pub edges: U32,
-    pub _unknown_1: [Field; 2],
+    pub modified: ObjectModified,
 }
 
 // Custom Debug implementation to only print known fields
@@ -52,32 +52,33 @@ impl Default for Polygon {
         Self {
             core: ObjectCore::default(ObjectType::Polygon),
             invert_shape: 1.into(),
-            corner_a: Point { x: 0.0, y: 0.0 }.into(),
-            corner_b: Point { x: 0.0, y: 0.0 }.into(),
+            corner_a: Coordinate { x: 0.0, y: 0.0 }.into(),
+            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(),
-            _unknown_1: [
-                vec![0, 0, 0, 0].into(), // 0_u32
-                vec![
-                    0, 0, 0, 0, 0, 0, 240, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 63,
-                ] // ???
-                .into(),
-            ],
+            modified: ObjectModified::default(),
         }
     }
 }
 
 impl Translate for Polygon {
-    fn translate(&mut self, origin: Option<Point>, z: Option<f64>) {
-        self.core.translate(origin, z);
+    fn move_absolute(&mut self, origin: Option<Coordinate>, z: Option<f64>) {
         origin.map(|origin| {
-            *self.corner_a += origin;
-            *self.corner_b += origin;
+            let delta: Coordinate = *self.core.origin - origin;
+            *self.corner_a += delta;
+            *self.corner_b += delta;
         });
+        self.core.move_absolute(origin, z);
+    }
+
+    fn move_relative(&mut self, delta: Option<Coordinate>, z: Option<f64>) {
+        delta.map(|delta| {
+            *self.corner_a += delta;
+            *self.corner_b += delta;
+        });
+        self.core.move_relative(delta, z);
     }
 }

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

@@ -5,10 +5,10 @@ use diff::Diff;
 
 use crate::{
     field_of::FieldOf,
-    types::{Field, ObjectType, Point, F64},
+    types::{Coordinate, ObjectType, F64},
 };
 
-use super::{ObjectCore, Translate};
+use super::{ObjectCore, Translate, ObjectModified};
 
 #[cfg_attr(feature = "default-debug", derive(Debug))]
 #[derive(BinRead, BinWrite, Clone, Diff, PartialEq)]
@@ -18,13 +18,13 @@ use super::{ObjectCore, Translate};
 pub struct Rectangle {
     pub core: ObjectCore,
     #[brw(magic(8u32))] // Number of following fields in struct
-    pub corner_a: FieldOf<Point>,
-    pub corner_b: FieldOf<Point>,
+    pub corner_a: FieldOf<Coordinate>,
+    pub corner_b: FieldOf<Coordinate>,
     pub round_bottom_left: F64,
     pub round_bottom_right: F64,
     pub round_top_right: F64,
     pub round_top_left: F64,
-    pub _unknown_1: [Field; 2],
+    pub modified: ObjectModified,
 }
 
 // Custom Debug implementation to only print known fields
@@ -47,32 +47,32 @@ impl Default for Rectangle {
     fn default() -> Self {
         Self {
             core: ObjectCore::default(ObjectType::Rectangle),
-            corner_a: Point { x: 0.0, y: 0.0 }.into(),
-            corner_b: Point { x: 0.0, y: 0.0 }.into(),
+            corner_a: Coordinate { x: 0.0, y: 0.0 }.into(),
+            corner_b: Coordinate { 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(),
             round_top_left: 0.0.into(),
-            _unknown_1: [
-                vec![0, 0, 0, 0].into(), // 0_u32
-                vec![
-                    0, 0, 0, 0, 0, 0, 240, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 63, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-                    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 240, 63,
-                ] // ???
-                .into(),
-            ]
-            .into(),
+            modified: ObjectModified::default(),
         }
     }
 }
 
 impl Translate for Rectangle {
-    fn translate(&mut self, origin: Option<Point>, z: Option<f64>) {
-        self.core.translate(origin, z);
+    fn move_absolute(&mut self, origin: Option<Coordinate>, z: Option<f64>) {
         origin.map(|origin| {
-            *self.corner_a += origin;
-            *self.corner_b += origin;
+            let delta: Coordinate = *self.core.origin - origin;
+            *self.corner_a += delta;
+            *self.corner_b += delta;
         });
+        self.core.move_absolute(origin, z);
+    }
+
+    fn move_relative(&mut self, delta: Option<Coordinate>, z: Option<f64>) {
+        delta.map(|delta| {
+            *self.corner_a += delta;
+            *self.corner_b += delta;
+        });
+        self.core.move_relative(delta, z);
     }
 }

+ 11 - 10
src/ezcad/types.rs

@@ -130,47 +130,48 @@ impl Rgba {
 #[diff(attr(
     #[derive(Debug, PartialEq)]
 ))]
-pub struct Point {
+#[serde(rename_all = "PascalCase")]
+pub struct Coordinate {
     pub x: f64,
     pub y: f64,
 }
 
-impl Display for Point {
+impl Display for Coordinate {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         write!(f, "({}, {})", self.x, self.y)
     }
 }
 
-impl Sub for Point {
-    type Output = Point;
+impl Sub for Coordinate {
+    type Output = Coordinate;
 
     fn sub(self, rhs: Self) -> Self::Output {
-        Point {
+        Coordinate {
             x: self.x - rhs.x,
             y: self.y - rhs.y,
         }
     }
 }
 
-impl Add for Point {
-    type Output = Point;
+impl Add for Coordinate {
+    type Output = Coordinate;
 
     fn add(self, rhs: Self) -> Self::Output {
-        Point {
+        Coordinate {
             x: self.x + rhs.x,
             y: self.y + rhs.y,
         }
     }
 }
 
-impl AddAssign for Point {
+impl AddAssign for Coordinate {
     fn add_assign(&mut self, rhs: Self) {
         self.x += rhs.x;
         self.y += rhs.y;
     }
 }
 
-impl From<(f64, f64)> for Point {
+impl From<(f64, f64)> for Coordinate {
     fn from(value: (f64, f64)) -> Self {
         Self {
             x: value.0,