diff --git a/Cargo.lock b/Cargo.lock index 7da03b4..22309fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -140,6 +140,7 @@ dependencies = [ "image 0.25.1", "lazy_static", "log", + "num-traits", "num_enum", "quick-xml", "regex", @@ -147,6 +148,7 @@ dependencies = [ "serde", "serde-xml-rs", "svg", + "uom", "uuid", "xml-rs", ] @@ -3328,6 +3330,16 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "uom" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffd36e5350a65d112584053ee91843955826bf9e56ec0d1351214e01f6d7cd9c" +dependencies = [ + "num-traits", + "typenum", +] + [[package]] name = "url" version = "2.5.0" diff --git a/altium-macros/src/lib.rs b/altium-macros/src/lib.rs index ca2528f..a270e57 100644 --- a/altium-macros/src/lib.rs +++ b/altium-macros/src/lib.rs @@ -6,7 +6,7 @@ use convert_case::{Case, Casing}; use proc_macro::TokenStream; use proc_macro2::{Delimiter, Ident, Literal, Span, TokenStream as TokenStream2, TokenTree}; use quote::{quote, ToTokens}; -use syn::{parse2, Attribute, Data, DeriveInput, Meta, Type}; +use syn::{parse2, Attribute, Data, DeriveInput, Field, Meta, Type}; /// Derive `FromRecord` for a type. See that trait for better information. /// @@ -69,100 +69,7 @@ fn inner(tokens: TokenStream2) -> syn::Result { // Loop through each field in the struct for field in data.fields { - let Type::Path(path) = field.ty else { - panic!("invalid type") - }; - - let field_ident = field.ident.unwrap(); - - // Parse attributes that exist on the field - let mut field_attr_map = parse_attrs(field.attrs).unwrap_or_default(); - - // Check if we need to parse an array - if let Some(arr_val) = field_attr_map.remove("array") { - let arr_val_str = arr_val.to_string(); - if arr_val_str == "true" { - let count_ident = field_attr_map - .remove("count") - .expect("missing 'count' attribute"); - - let arr_map = field_attr_map - .remove("map") - .expect("missing 'map' attribute"); - - process_array( - &struct_ident, - &field_ident, - count_ident, - arr_map, - &mut match_arms, - ); - error_if_map_not_empty(&field_attr_map); - continue; - } else if arr_val_str != "false" { - panic!("array must be `true` or `false` but got {arr_val_str}"); - } - } - - // We match a single literal, like `OwnerPartId` - // Perform renaming if attribute requests it - let match_pat = match field_attr_map.remove("rename") { - Some(TokenTree::Literal(v)) => v, - Some(v) => panic!("expected literal, got {v:?}"), - None => create_key_name(&field_ident), - }; - - // If we haven't consumed all attributes, yell - error_if_map_not_empty(&field_attr_map); - - let update_stmt = if path.path.segments.first().unwrap().ident == "Option" { - // Wrap our field is an `Option` - quote! { ret.#field_ident = Some(parsed); } - } else { - quote! { ret.#field_ident = parsed; } - }; - - let path_str = path.to_token_stream().to_string(); - - // Types `Location` and `LocationFract` are special cases - let is_location_fract = path_str.contains("LocationFract"); - if is_location_fract || path_str.contains("Location") { - process_location( - &struct_ident, - &field_ident, - is_location_fract, - &mut match_arms, - ); - continue; - } - - let Utf8Handler { - arm: utf8_arm, - define_flag: utf8_def_flag, - check_flag: utf8_check_flag, - } = if path_str.contains("String") || path_str.contains("str") { - // Altium does this weird thing where it will create a normal key and a key - // with `%UTF8%` if a value is utf8. We need to discard those redundant values - make_utf8_handler(&match_pat, &field_ident, &struct_ident, &update_stmt) - } else { - Utf8Handler::default() - }; - - let ctx_msg = make_ctx_message(&match_pat, &field_ident, &struct_ident); - - let quoted = quote! { - #utf8_arm - - #match_pat => { - #utf8_check_flag - - let parsed = val.parse_as_utf8().context(#ctx_msg)?; - #update_stmt - }, - }; - - outer_flags.push(utf8_def_flag); - match_arms.push(quoted); + handle_field(field, &struct_ident, &mut match_arms, &mut outer_flags); } let ret_val = if use_box { @@ -198,6 +105,110 @@ fn inner(tokens: TokenStream2) -> syn::Result { Ok(ret) } +fn handle_field( + field: Field, + struct_ident: &Ident, + match_arms: &mut Vec, + outer_flags: &mut Vec, +) { + let Type::Path(path) = field.ty else { + panic!("invalid type") + }; + + let field_ident = field.ident.unwrap(); + + // Parse attributes that exist on the field + let mut field_attr_map = parse_attrs(field.attrs).unwrap_or_default(); + + // Check if we need to parse an array + if let Some(arr_val) = field_attr_map.remove("array") { + let arr_val_str = arr_val.to_string(); + if arr_val_str == "true" { + let count_ident = field_attr_map + .remove("count") + .expect("missing 'count' attribute"); + + let arr_map = field_attr_map + .remove("map") + .expect("missing 'map' attribute"); + + process_array( + &struct_ident, + &field_ident, + count_ident, + arr_map, + match_arms, + ); + error_if_map_not_empty(&field_attr_map); + return; + } else if arr_val_str != "false" { + panic!("array must be `true` or `false` but got {arr_val_str}"); + } + } + + // We match a single literal, like `OwnerPartId` + // Perform renaming if attribute requests it + let match_pat = match field_attr_map.remove("rename") { + Some(TokenTree::Literal(v)) => v, + Some(v) => panic!("expected literal, got {v:?}"), + None => create_key_name(&field_ident), + }; + + let convert = match field_attr_map.remove("convert") { + Some(conv_fn) => quote! { .map_err(Into::into).and_then(#conv_fn) }, + None => TokenStream2::new(), + }; + + // If we haven't consumed all attributes, yell + error_if_map_not_empty(&field_attr_map); + + let update_stmt = if path.path.segments.first().unwrap().ident == "Option" { + // Wrap our field is an `Option` + quote! { ret.#field_ident = Some(parsed); } + } else { + quote! { ret.#field_ident = parsed; } + }; + + let path_str = path.to_token_stream().to_string(); + + // Types `Location` and `LocationFract` are special cases + let is_location_fract = path_str.contains("LocationFract"); + if is_location_fract || path_str.contains("Location") { + process_location(&struct_ident, &field_ident, is_location_fract, match_arms); + return; + } + + let Utf8Handler { + arm: utf8_arm, + define_flag: utf8_def_flag, + check_flag: utf8_check_flag, + } = if path_str.contains("String") || path_str.contains("str") { + // Altium does this weird thing where it will create a normal key and a key + // with `%UTF8%` if a value is utf8. We need to discard those redundant values + make_utf8_handler(&match_pat, &field_ident, &struct_ident, &update_stmt) + } else { + Utf8Handler::default() + }; + + let ctx_msg = make_ctx_message(&match_pat, &field_ident, &struct_ident); + + let quoted = quote! { + #utf8_arm + + #match_pat => { + #utf8_check_flag + + let parsed = val.parse_as_utf8() + #convert + .context(#ctx_msg)?; + #update_stmt + }, + }; + + outer_flags.push(utf8_def_flag); + match_arms.push(quoted); +} + /// Next type of token we are expecting #[derive(Clone, Debug, PartialEq)] enum AttrParseState { @@ -364,7 +375,7 @@ fn process_location( #match_pat => #assign_field = val.parse_as_utf8() .map_err(Into::into) - .and_then(crate::common::i32_mils_to_nm) + .and_then(crate::common::mils_to_nm) .context(#ctx_msg)?, } } else { diff --git a/altium/Cargo.toml b/altium/Cargo.toml index bfc83f9..0625580 100644 --- a/altium/Cargo.toml +++ b/altium/Cargo.toml @@ -17,6 +17,7 @@ flate2 = "1.0.30" image = { version = "0.25.1", default-features = false, features = ["png", "bmp", "jpeg"] } lazy_static = "1.4.0" log = "0.4.21" +num-traits = "0.2.18" num_enum = "0.7.2" quick-xml = "0.31.0" regex = "1.10.4" @@ -24,6 +25,7 @@ rust-ini = "0.21.0" serde = "1.0.200" serde-xml-rs = "0.6.0" svg = "0.17.0" +uom = "0.36.0" uuid = { version = "1.8.0", features = ["v1", "v4", "fast-rng"]} xml-rs = "0.8.20" diff --git a/altium/src/common.rs b/altium/src/common.rs index 790a0a3..dbd03fa 100644 --- a/altium/src/common.rs +++ b/altium/src/common.rs @@ -1,5 +1,6 @@ use std::{fmt, str}; +use num_traits::CheckedMul; use uuid::Uuid; use crate::error::{AddContext, ErrorKind, Result, TruncBuf}; @@ -316,16 +317,14 @@ pub fn is_number_pattern(s: &[u8], prefix: &[u8]) -> bool { } /// Infallible conversion -pub fn i32_mils_to_nm(mils: i32) -> Result { - const FACTOR: i32 = 25400; - mils.checked_mul(FACTOR).ok_or_else(|| { - ErrorKind::Overflow(mils.into(), FACTOR.into(), '*').context("converting units") - }) -} - -pub fn u32_mils_to_nm(mils: u32) -> Result { - const FACTOR: u32 = 25400; - mils.checked_mul(FACTOR).ok_or_else(|| { +pub fn mils_to_nm(mils: T) -> Result +where + T: CheckedMul, + T: From, + i64: From, +{ + const FACTOR: u16 = 25400; + mils.checked_mul(&FACTOR.into()).ok_or_else(|| { ErrorKind::Overflow(mils.into(), FACTOR.into(), '*').context("converting units") }) } diff --git a/altium/src/draw/canvas.rs b/altium/src/draw/canvas.rs index f16de2f..aed2ab1 100644 --- a/altium/src/draw/canvas.rs +++ b/altium/src/draw/canvas.rs @@ -31,7 +31,7 @@ pub struct DrawLine { pub start: Location, pub end: Location, pub color: Rgb, - pub width: u16, + pub width: u32, // pub width: Option<&'a str>, } @@ -43,7 +43,7 @@ pub struct DrawRectangle { pub height: i32, pub fill_color: Rgb, pub stroke_color: Rgb, - pub stroke_width: u16, + pub stroke_width: u32, } #[derive(Clone, Debug, Default)] @@ -51,7 +51,7 @@ pub struct DrawPolygon<'a> { pub locations: &'a [Location], pub fill_color: Rgb, pub stroke_color: Rgb, - pub stroke_width: u16, + pub stroke_width: u32, } pub struct DrawImage {} diff --git a/altium/src/sch/pin.rs b/altium/src/sch/pin.rs index cb4f9e3..46dbbe5 100644 --- a/altium/src/sch/pin.rs +++ b/altium/src/sch/pin.rs @@ -7,7 +7,7 @@ use altium_macros::FromRecord; use log::warn; use super::SchRecord; -use crate::common::{i32_mils_to_nm, u32_mils_to_nm, Location, Rotation90, Visibility}; +use crate::common::{mils_to_nm, Location, Rotation90, Visibility}; use crate::error::AddContext; use crate::parse::ParseUtf8; use crate::parse::{FromRecord, FromUtf8}; @@ -82,8 +82,8 @@ impl SchPin { } let location = Location { - x: i32_mils_to_nm(i32::from(location_x))?, - y: i32_mils_to_nm(i32::from(location_y))?, + x: mils_to_nm(i32::from(location_x))?, + y: mils_to_nm(i32::from(location_y))?, }; let retval = Self { formal_type: *formal_type, @@ -93,7 +93,7 @@ impl SchPin { designator: designator.into(), name: name.into(), location, - length: u32_mils_to_nm(u32::from(length))?, + length: mils_to_nm(u32::from(length))?, // location_x: i32::from(location_x) * 10, // location_y: i32::from(location_y) * 10, // length: u32::from(length) * 10, diff --git a/altium/src/sch/record.rs b/altium/src/sch/record.rs index e18c5fa..ae8ab87 100644 --- a/altium/src/sch/record.rs +++ b/altium/src/sch/record.rs @@ -61,7 +61,7 @@ pub(super) use parse::parse_all_records; use super::params::Justification; use super::pin::SchPin; -use crate::common::{Location, LocationFract, ReadOnlyState, UniqueId}; +use crate::common::{mils_to_nm, Location, LocationFract, ReadOnlyState, UniqueId}; use crate::error::{AddContext, TruncBuf}; use crate::font::FontCollection; use crate::Error; @@ -242,7 +242,8 @@ pub struct Bezier { color: Rgb, index_in_sheet: i16, is_not_accessible: bool, - line_width: u16, + #[from_record(convert = mils_to_nm)] + line_width: u32, #[from_record(array = true, count = b"LocationCount", map = (X -> x, Y -> y))] pub locations: Vec, owner_index: u8, @@ -259,7 +260,8 @@ pub struct PolyLine { owner_part_id: i8, is_not_accessible: bool, index_in_sheet: i16, - line_width: u16, + #[from_record(convert = mils_to_nm)] + line_width: u32, pub color: Rgb, #[from_record(array = true, count = b"LocationCount", map = (X -> x, Y -> y))] pub locations: Vec, @@ -275,7 +277,8 @@ pub struct Polygon { index_in_sheet: i16, is_not_accessible: bool, is_solid: bool, - line_width: u16, + #[from_record(convert = mils_to_nm)] + line_width: u32, #[from_record(array = true, count = b"LocationCount", map = (X -> x, Y -> y))] pub locations: Vec, owner_index: u8, @@ -293,7 +296,8 @@ pub struct Ellipse { index_in_sheet: i16, is_not_accessible: bool, is_solid: bool, - line_width: u16, + #[from_record(convert = mils_to_nm)] + line_width: u32, pub location: Location, owner_index: u8, owner_part_id: i8, @@ -323,7 +327,8 @@ pub struct RectangleRounded { index_in_sheet: i16, is_not_accessible: bool, is_solid: bool, - line_width: u16, + #[from_record(convert = mils_to_nm)] + line_width: u32, location: Location, owner_index: u8, owner_part_id: i8, @@ -345,7 +350,8 @@ pub struct ElipticalArc { radius_frac: i32, secondary_radius: i8, secondary_radius_frac: i32, - line_width: i8, + #[from_record(convert = mils_to_nm)] + line_width: i32, start_angle: f32, end_angle: f32, pub color: Rgb, @@ -365,7 +371,8 @@ pub struct Arc { radius_frac: i32, secondary_radius: i8, secondary_radius_frac: i32, - line_width: i8, + #[from_record(convert = mils_to_nm)] + line_width: i32, start_angle: f32, end_angle: f32, pub color: Rgb, @@ -382,7 +389,8 @@ pub struct Line { index_in_sheet: i16, is_not_accessible: bool, is_solid: bool, - line_width: u16, + #[from_record(convert = mils_to_nm)] + line_width: u32, location_count: u16, location_x: i32, location_y: i32, @@ -403,7 +411,8 @@ pub struct Rectangle { index_in_sheet: i16, is_not_accessible: bool, pub is_solid: bool, - line_width: u16, + #[from_record(convert = mils_to_nm)] + line_width: u32, /// Bottom left corner pub location: Location, owner_index: u8, @@ -420,7 +429,8 @@ pub struct SheetSymbol { owner_index: u8, owner_part_id: i8, index_in_sheet: i16, - line_width: u16, + #[from_record(convert = mils_to_nm)] + line_width: u32, pub color: Rgb, pub area_color: Rgb, is_solid: bool, @@ -533,7 +543,8 @@ pub struct Bus { owner_index: u8, owner_part_id: i8, index_in_sheet: i16, - line_width: u16, + #[from_record(convert = mils_to_nm)] + line_width: u32, pub color: Rgb, #[from_record(array = true, count = b"LocationCount", map = (X -> x, Y -> y))] pub locations: Vec, @@ -546,7 +557,8 @@ pub struct Bus { pub struct Wire { owner_index: u8, owner_part_id: i8, - line_width: u16, + #[from_record(convert = mils_to_nm)] + line_width: u32, pub color: Rgb, #[from_record(array = true, count = b"LocationCount", map = (X -> x, Y -> y))] pub locations: Vec, diff --git a/altium/src/sch/record/draw.rs b/altium/src/sch/record/draw.rs index ce91227..0d54d4d 100644 --- a/altium/src/sch/record/draw.rs +++ b/altium/src/sch/record/draw.rs @@ -86,24 +86,24 @@ impl Draw for SchPin { start, end, color: Rgb::black(), - width: 4, + width: 30000, // ..Default::default() }); // Altium draws a small white plus at the pin's connect position, so we // do too canvas.draw_line(DrawLine { - start: end.add_x(1), - end: end.add_x(-1), + start: end.add_x(10000), + end: end.add_x(-10000), color: Rgb::white(), - width: 1, + width: 5000, }); canvas.draw_line(DrawLine { - start: end.add_y(1), - end: end.add_y(-1), + start: end.add_y(10000), + end: end.add_y(-10000), color: Rgb::white(), - width: 1, + width: 5000, }); // FIXME: use actual spacing & fonts from pin spec diff --git a/ecadg/src/draw.rs b/ecadg/src/draw.rs index b36a22b..fae3d2f 100644 --- a/ecadg/src/draw.rs +++ b/ecadg/src/draw.rs @@ -51,7 +51,7 @@ impl Canvas for PlotUiWrapper<'_> { [f64::from(item.end.x()), f64::from(item.end.y())], ]) .color(to_c32(item.color)) - .width(item.width), + .width(item.width as f32), ); } @@ -86,7 +86,7 @@ impl Canvas for PlotUiWrapper<'_> { .collect::(), ) .stroke(Stroke { - width: f32::from(item.stroke_width) * 20.0, + width: item.stroke_width as f32 * 20.0, color: to_c32(item.stroke_color), }) .fill_color(to_c32(item.fill_color).gamma_multiply(1.0)); diff --git a/ecadg/src/gfx/tessellated.rs b/ecadg/src/gfx/tessellated.rs index d882289..647e16e 100644 --- a/ecadg/src/gfx/tessellated.rs +++ b/ecadg/src/gfx/tessellated.rs @@ -435,7 +435,7 @@ impl Canvas for TessCtx { self.stroke_tess .tessellate_path( &path, - &STROKE_OPTIONS.with_line_width(dbg!(item.width as f32)), + &STROKE_OPTIONS.with_line_width(item.width as f32), &mut BuffersBuilder::new( &mut self.stroke_geometry, WithColor(item.color.as_float_rgba()), @@ -469,7 +469,7 @@ impl Canvas for TessCtx { self.stroke_tess .tessellate_rectangle( &rect, - &STROKE_OPTIONS, + &STROKE_OPTIONS.with_line_width(item.stroke_width as f32), &mut BuffersBuilder::new( &mut self.stroke_geometry, WithColor(item.stroke_color.as_float_rgba()),