From 091f04e7d8eb50805c3277e5c232be1adbe5c9ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20S=C3=B6derstr=C3=B6m?= Date: Wed, 9 Feb 2022 19:02:37 +0100 Subject: [PATCH] feat: add wasm setup --- .gitignore | 2 + Cargo.toml | 21 +++++ build-wasm.sh | 4 + src/lib.rs | 8 ++ src/tool.rs | 216 +++++++++++++++++++++++++++++++++++++++++++++++ src/types.rs | 226 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/utils.rs | 27 ++++++ 7 files changed, 504 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 build-wasm.sh create mode 100644 src/lib.rs create mode 100644 src/tool.rs create mode 100644 src/types.rs create mode 100644 src/utils.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..96ef6c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5de2f1e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "cnccoder" +authors = ["Fredrik Söderström "] +description = "A library for generating gcode operations targeted on cnc machines and simulatable with camotics" +license = "GPLv2" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wasm-bindgen = "0.2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +anyhow = "1" +lazy_static = "1" + +[profile.release] +opt-level = 's' +lto = true \ No newline at end of file diff --git a/build-wasm.sh b/build-wasm.sh new file mode 100644 index 0000000..57ec842 --- /dev/null +++ b/build-wasm.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +wasm-pack build --release --target web +wasm-opt -Os -o pkg/cnccoder_bg.wasm pkg/cnccoder_bg.wasm diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..1de42fd --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,8 @@ +use wasm_bindgen::prelude::*; + +/* +#[wasm_bindgen] +pub fn add(a: f64, b: f64) -> f64 { + a + b +} +*/ \ No newline at end of file diff --git a/src/tool.rs b/src/tool.rs new file mode 100644 index 0000000..a42f8bd --- /dev/null +++ b/src/tool.rs @@ -0,0 +1,216 @@ +use std::hash::{Hash, Hasher}; + +use serde::{Deserialize, Serialize}; + +use crate::types::Units; +use crate::utils::round_precision; + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, Copy)] +#[serde(tag = "shape", rename_all = "lowercase")] +pub enum Tool { + Cylindrical(Cylindrical), + Ballnose(Ballnose), + Conical(Conical), +} + +impl Tool { + pub fn fake() -> Self { + Self::Cylindrical(Cylindrical { + diameter: -1.0, + length: -1.0, + ..Default::default() + }) + } + + pub fn to_string_description(&self) -> String { + match self { + Self::Cylindrical(tool) => format!( + "Cylindrical: diameter = {}{}, length = {}{}", + round_precision(tool.diameter), + tool.units.to_string_base_unit(), + round_precision(tool.length), + tool.units.to_string_base_unit() + ), + Self::Ballnose(tool) => format!( + "Ballnose: diameter = {}{}, length = {}{}", + round_precision(tool.diameter), + tool.units.to_string_base_unit(), + round_precision(tool.length), + tool.units.to_string_base_unit() + ), + Self::Conical(tool) => format!( + "Conical: angle = {}°, diameter = {}{}, length = {}{}", + round_precision(tool.angle), + round_precision(tool.diameter), + tool.units.to_string_base_unit(), + round_precision(tool.length), + tool.units.to_string_base_unit() + ), + } + } + + pub fn radius(&self) -> f64 { + match self { + Self::Cylindrical(tool) => tool.radius(), + Self::Ballnose(tool) => tool.radius(), + Self::Conical(tool) => tool.radius(), + } + } + + pub fn spindle_rpm(&self) -> f64 { + match self { + Self::Cylindrical(tool) => tool.spindle_rpm, + Self::Ballnose(tool) => tool.spindle_rpm, + Self::Conical(tool) => tool.spindle_rpm, + } + } +} + +impl Default for Tool { + fn default() -> Self { + Self::Cylindrical(Cylindrical::default()) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] +pub struct Cylindrical { + pub units: Units, + pub length: f64, + pub diameter: f64, + pub spindle_rpm: f64, +} + +impl Cylindrical { + pub fn radius(&self) -> f64 { + self.diameter / 2.0 + } +} + +impl Default for Cylindrical { + fn default() -> Self { + Self { + units: Units::default(), + length: 10.0, + diameter: 4.0, + spindle_rpm: 5000.0, + } + } +} + +impl PartialEq for Cylindrical { + fn eq(&self, other: &Cylindrical) -> bool { + self.units == other.units && + self.length == other.length && + self.diameter == other.diameter && + self.spindle_rpm == other.spindle_rpm + } +} + +impl Eq for Cylindrical {} + +impl Hash for Cylindrical { + fn hash(&self, state: &mut H) { + self.units.hash(state); + self.length.to_bits().hash(state); + self.diameter.to_bits().hash(state); + self.spindle_rpm.to_bits().hash(state); + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] +pub struct Ballnose { + pub units: Units, + pub length: f64, + pub diameter: f64, + pub spindle_rpm: f64 +} + +impl Ballnose { + pub fn radius(&self) -> f64 { + self.diameter / 2.0 + } +} + +impl Default for Ballnose { + fn default() -> Self { + Self { + units: Units::default(), + length: 10.0, + diameter: 4.0, + spindle_rpm: 5000.0, + } + } +} + +impl PartialEq for Ballnose { + fn eq(&self, other: &Ballnose) -> bool { + self.units == other.units && + self.length == other.length && + self.diameter == other.diameter && + self.spindle_rpm == other.spindle_rpm + } +} + +impl Eq for Ballnose {} + +impl Hash for Ballnose { + fn hash(&self, state: &mut H) { + self.units.hash(state); + self.length.to_bits().hash(state); + self.diameter.to_bits().hash(state); + self.spindle_rpm.to_bits().hash(state); + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, Copy)] +pub struct Conical { + pub units: Units, + pub angle: f64, + pub length: f64, + pub diameter: f64, + pub spindle_rpm: f64, +} + +impl Conical { + pub fn new(units: Units, angle: f64, diameter: f64) -> Self { + Self { + units, + angle, + length: (diameter / 2.0) / (angle / 2.0).to_radians().tan(), + diameter, + spindle_rpm: 5000.0, + } + } + + pub fn radius(&self) -> f64 { + self.diameter / 2.0 + } +} + +impl Default for Conical { + fn default() -> Self { + Self::new(Units::default(), 90.0, 4.0) + } +} + +impl PartialEq for Conical { + fn eq(&self, other: &Conical) -> bool { + self.units == other.units && + self.angle == other.angle && + self.length == other.length && + self.diameter == other.diameter && + self.spindle_rpm == other.spindle_rpm + } +} + +impl Eq for Conical {} + +impl Hash for Conical { + fn hash(&self, state: &mut H) { + self.units.hash(state); + self.angle.to_bits().hash(state); + self.length.to_bits().hash(state); + self.diameter.to_bits().hash(state); + self.spindle_rpm.to_bits().hash(state); + } +} diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..49ae482 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,226 @@ +use std::{ + f64::{self, consts::PI}, + fmt, +}; + +use serde::{Deserialize, Deserializer, Serialize, Serializer}; + +use crate::utils::round_precision; + +// Used to deserialize a struct as a tuple. +macro_rules! as_serde_tuple { + ($(#[$smeta:meta])* + $svis:vis struct $sname:ident { + $($fvis:vis $fname:ident : $ftype:ty,)* + }) => { + $(#[$smeta])* + $svis struct $sname { + $($fvis $fname : $ftype,)* + } + + impl<'de> Deserialize<'de> for $sname { + fn deserialize(deserializer: D) -> Result + where D: Deserializer<'de> + { + #[derive(Deserialize, Serialize)] + pub struct Array($(pub $ftype,)*); + + Deserialize::deserialize(deserializer) + .map(|Array($($fname,)*)| Self { $($fname: $fname,)* }) + } + } + + impl Serialize for $sname { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + #[derive(Deserialize, Serialize)] + pub struct Array($(pub $ftype,)*); + + (Array($(self.$fname.clone(),)*)).serialize(serializer) + } + } + } +} + +as_serde_tuple! { + #[derive(Default, Debug, PartialEq, Clone, Copy)] + pub struct Vector2 { + pub x: f64, + pub y: f64, + } +} + +impl Vector2 { + pub fn new(x: f64, y: f64) -> Self { + Self { x, y } + } + + pub fn distance_to(&self, to: Self) -> f64 { + ((self.x - to.x) * (self.x - to.x) + (self.y - to.y) * (self.y - to.y)).sqrt() + } + + /// Computes the angle in radians with respect to the positive x-axis + pub fn angle(&self) -> f64 { + (-self.x).atan2(-self.y) + PI + } + + /// Computes the angle in degrees with respect to the positive x-axis + pub fn angle_degrees(&self) -> f64 { + self.angle().to_degrees() + } + + pub fn add_x(&self, value: f64) -> Self { + let mut vector = *self; + vector.x += value; + vector + } + + pub fn add_y(&self, value: f64) -> Self { + let mut vector = *self; + vector.y += value; + vector + } +} + +impl fmt::Display for Vector2 { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!( + formatter, + "{{x: {}, y: {}}}", + round_precision(self.x), + round_precision(self.y) + ) + } +} + +as_serde_tuple! { + #[derive(Default, Debug, PartialEq, Clone, Copy)] + pub struct Vector3 { + pub x: f64, + pub y: f64, + pub z: f64, + } +} + +impl Vector3 { + pub fn new(x: f64, y: f64, z: f64) -> Self { + Self { x, y, z } + } + + pub fn min() -> Self { + Self { + x: f64::MIN, + y: f64::MIN, + z: f64::MIN, + } + } + + pub fn max() -> Self { + Self { + x: f64::MAX, + y: f64::MAX, + z: f64::MAX, + } + } + + pub fn xy(&self) -> Vector2 { + Vector2::new(self.x, self.y) + } + + pub fn add_x(&self, value: f64) -> Self { + let mut vector = *self; + vector.x += value; + vector + } + + pub fn add_y(&self, value: f64) -> Self { + let mut vector = *self; + vector.y += value; + vector + } + + pub fn add_z(&self, value: f64) -> Self { + let mut vector = *self; + vector.z += value; + vector + } +} + +impl fmt::Display for Vector3 { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!( + formatter, + "{{x: {}, y: {}, z: {}}}", + round_precision(self.x), + round_precision(self.y), + round_precision(self.z) + ) + } +} + +#[derive(Serialize, Deserialize, Default, Debug, Copy, Clone)] +pub struct Bounds { + pub min: Vector3, + pub max: Vector3, +} + +impl Bounds { + pub fn max() -> Self { + Self { + min: Vector3::max(), + max: Vector3::min(), + } + } + + pub fn new(x: f64, y: f64, z: f64) -> Self { + Self { + min: Vector3::new(0.0, 0.0, 0.0), + max: Vector3::new(x, y, z), + } + } + + pub fn size(&self) -> Vector3 { + Vector3::new( + self.max.x - self.min.x, + self.max.y - self.min.y, + self.max.z - self.min.z, + ) + } +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, Copy)] +#[serde(rename_all = "lowercase")] +pub enum Units { + Metric, + Imperial, +} + +impl Units { + pub fn to_string_base_unit(&self) -> String { + match self { + Self::Metric => "mm".to_string(), + Self::Imperial => "inch".to_string(), + } + } +} + +impl Default for Units { + fn default() -> Self { + Units::Metric + } +} + +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)] +#[serde(rename_all = "lowercase")] +pub enum Direction { + Clockwise, + Counterclockwise, +} + +impl Default for Direction { + fn default() -> Self { + Direction::Clockwise + } +} diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..5cfc334 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,27 @@ +pub fn scale(x: f64, in_min: f64, in_max: f64, out_min: f64, out_max: f64) -> f64 { + (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min +} + +pub fn round_precision(value: f64) -> f64 { + ((value * 10000.0) as f64).round() / 10000.0 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_scale() { + let start = scale(0.0, 0.0, 1.0, 4.0, 8.0); + assert!((start - 4.0).abs() < 0.1); + + let middle = scale(0.5, 0.0, 1.0, 4.0, 8.0); + assert!((middle - 6.0).abs() < 0.1); + + let end = scale(1.0, 0.0, 1.0, 4.0, 8.0); + assert!((end - 8.0).abs() < 0.1); + + let inverted = scale(0.25, 0.0, 1.0, 8.0, 4.0); + assert!((inverted - 7.0).abs() < 0.1); + } +}