diff --git a/Cargo.toml b/Cargo.toml index 21e0c13..3c6f05b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,10 @@ lazy_static = "1" embed-doc-image = "0.1" glam = { version = "0.24", optional = true } nalgebra = { version = "0.32", optional = true } +time = { version = "0.3.36", features = ["local-offset", "parsing"] } +moby-name-gen = "0.1.0" +hostname = "0.4.0" +username = "0.2.0" [profile.release] opt-level = 's' diff --git a/examples/planing.rs b/examples/planing.rs index b3c565c..e432fe7 100644 --- a/examples/planing.rs +++ b/examples/planing.rs @@ -6,6 +6,7 @@ fn main() -> Result<()> { // Create a program with metric measurements where the tool can travel freely at 10 mm // height, and move to 50 mm height for manual tool change. let mut program = Program::new(Units::Metric, 10.0, 50.0); + program.set_name("planing"); // Create a cylindrical tool let tool = Tool::cylindrical( @@ -35,7 +36,7 @@ fn main() -> Result<()> { // Write the G-code (for CNC) `planing.gcode` and Camotics project file // `planing.camotics` (for simulation) to disk using a resolution value // of 0.5 for the Camotics simulation. - write_project("planing", &program, 0.5)?; + write_project(&program, 0.5)?; Ok(()) } diff --git a/src/filesystem.rs b/src/filesystem.rs index 1868a56..d3a1781 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -19,6 +19,7 @@ use crate::{camotics::*, program::*}; /// 10.0, /// 50.0, /// ); +/// program.set_name("planing"); /// /// let tool = Tool::cylindrical( /// Units::Metric, @@ -38,12 +39,13 @@ use crate::{camotics::*, program::*}; /// 1.0, /// )); /// -/// write_project("planing", &program, 0.5)?; +/// write_project(&program, 0.5)?; /// /// Ok(()) /// } /// ``` -pub fn write_project(name: &str, program: &Program, camotics_resolution: f64) -> Result<()> { +pub fn write_project(program: &Program, camotics_resolution: f64) -> Result<()> { + let name = program.name(); let camotics = Camotics::from_program(name, program, camotics_resolution); let gcode = program.to_gcode()?; @@ -72,6 +74,7 @@ mod tests { #[test] fn test_camotics_from_program() -> Result<()> { let mut program = Program::new(Units::Metric, 10.0, 50.0); + program.set_name("test-temp"); let tool = Tool::cylindrical( Units::Metric, @@ -105,7 +108,7 @@ mod tests { 1.0, )); - write_project("test-temp", &program, 0.5)?; + write_project(&program, 0.5)?; let camotics: Value = serde_json::from_str(&read_to_string("test-temp.camotics")?)?; remove_file("test-temp.camotics")?; diff --git a/src/program.rs b/src/program.rs index cfa616e..1739425 100644 --- a/src/program.rs +++ b/src/program.rs @@ -51,6 +51,7 @@ use std::collections::HashMap; use std::rc::Rc; use anyhow::{anyhow, Result}; +use time::OffsetDateTime; use crate::cuts::*; use crate::instructions::*; @@ -328,12 +329,72 @@ impl<'a> Context<'a> { } } +#[derive(Debug, Clone)] +struct ProgramMeta { + name: String, + description: Vec, + created_on: OffsetDateTime, + created_by: String, + generator: String, +} + +impl ProgramMeta { + fn to_instructions(&self) -> Vec { + let mut instructions = vec![]; + + instructions.push(Instruction::Comment(Comment { + text: format!("Name: {}", self.name), + })); + + instructions.push(Instruction::Comment(Comment { + text: format!("Created on: {}", self.created_on), + })); + + instructions.push(Instruction::Comment(Comment { + text: format!("Created by: {}", self.created_by), + })); + + instructions.push(Instruction::Comment(Comment { + text: format!("Generator: {}", self.generator), + })); + + for description in &self.description { + instructions.push(Instruction::Comment(Comment { + text: format!("Description: {}", description), + })); + } + + instructions + } +} + +impl Default for ProgramMeta { + fn default() -> Self { + let username = username::get_user_name().unwrap_or("unknown".into()); + let hostname = hostname::get() + .unwrap_or("unknown".into()) + .to_string_lossy() + .to_string(); + + let args: Vec = std::env::args().collect(); + + Self { + name: moby_name_gen::random_name(), + description: Vec::new(), + created_on: OffsetDateTime::now_local().unwrap_or(OffsetDateTime::now_utc()), + created_by: format!("{username}@{hostname}").to_string(), + generator: args.join(" "), + } + } +} + /// A program that stores information about all structs and tools used in a project. Several programs can /// also be merged into a single one. #[derive(Debug, Clone)] pub struct Program { z_safe: f64, z_tool_change: f64, + meta: ProgramMeta, units: Units, contexts: Rc>>, tool_ordering: Rc>, @@ -346,6 +407,7 @@ impl Program { Self { z_safe, z_tool_change, + meta: ProgramMeta::default(), units, contexts: Rc::new(RefCell::new(HashMap::new())), tool_ordering: Rc::new(RefCell::new(ToolOrdering::default())), @@ -358,12 +420,35 @@ impl Program { Self { z_safe: program.z_safe, z_tool_change: program.z_tool_change, + meta: ProgramMeta::default(), units: program.units, contexts: Rc::new(RefCell::new(HashMap::new())), tool_ordering: Rc::new(RefCell::new(ToolOrdering::default())), } } + /// Set the name of the program + pub fn set_name(&mut self, name: &str) { + self.meta.name = name.into(); + } + + /// Get the name of the program + #[must_use] + pub fn name(&self) -> &str { + self.meta.name.as_str() + } + + /// Add to program description + pub fn add_description(&mut self, description: &str) { + self.meta.description.push(description.into()); + } + + /// Get program description + #[must_use] + pub fn description(&self) -> &[String] { + &self.meta.description + } + /// Returns the z safe value set for this context. /// /// The value indicates the z height where the machine tool can safely travel @@ -568,8 +653,48 @@ impl Program { pub fn to_instructions(&self) -> Result> { let contexts = self.contexts.borrow(); let tools = self.tools(); + let z_safe = self.z_safe(); + let z_tool_change = self.z_tool_change(); + let bounds = self.bounds(); + let size = bounds.size(); + let units = self.units; + + if z_tool_change < z_safe { + return Err(anyhow!( + "z_tool_change {} {} must be larger than or equal to the z_safe value of {} {}", + z_tool_change, + units, + z_safe, + units + )); + } + + if z_safe < bounds.max.z { + return Err(anyhow!( + "z_safe {} {} must be larger than or equal to the workpiece max z value of {} {}", + z_safe, + units, + bounds.max.z, + units + )); + } - let mut raw_instructions = vec![Instruction::G17(G17 {})]; + let mut raw_instructions = self.meta.to_instructions(); + + raw_instructions.push(Instruction::Comment(Comment { + text: format!( + "Workarea: size_x = {} {units}, size_y = {} {units}, size_z = {} {units}, min_x = {} {units}, min_y = {} {units}, max_z = {} {units}, z_safe = {z_safe} {units}, z_tool_change = {z_tool_change} {units}", + size.x, + size.y, + size.z, + bounds.min.x, + bounds.min.y, + bounds.max.z + ) + })); + + raw_instructions.push(Instruction::Empty(Empty {})); + raw_instructions.push(Instruction::G17(G17 {})); for tool in tools { if let Some(context) = contexts.get(&tool) { @@ -659,6 +784,7 @@ impl Default for Program { Self { z_safe: 50.0, z_tool_change: 100.0, + meta: ProgramMeta::default(), units: Units::default(), contexts: Rc::new(RefCell::new(HashMap::new())), tool_ordering: Rc::new(RefCell::new(ToolOrdering::default())),