diff --git a/Cargo.lock b/Cargo.lock index 22309fc..29fe133 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -820,6 +820,46 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "clap" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.60", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + [[package]] name = "clipboard-win" version = "5.3.1" @@ -1133,10 +1173,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de78e66ac9061e030587b2a2e75cc88f22304913c907b11307bca737141230cb" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro-error", ] +[[package]] +name = "ecad-cli" +version = "0.1.0" +dependencies = [ + "altium", + "clap", + "serde_json", +] + [[package]] name = "ecadg" version = "0.1.0" @@ -1818,6 +1867,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -1929,6 +1984,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + [[package]] name = "jni" version = "0.21.1" @@ -2908,6 +2969,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + [[package]] name = "same-file" version = "1.0.6" @@ -2961,6 +3028,17 @@ dependencies = [ "syn 2.0.60", ] +[[package]] +name = "serde_json" +version = "1.0.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_repr" version = "0.1.19" @@ -3092,6 +3170,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "svg" version = "0.17.0" @@ -3373,6 +3457,7 @@ dependencies = [ "atomic", "getrandom", "rand", + "serde", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 72d0df8..2e6f84a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ "altium", "altium-macros", + "ecad-cli", "ecadg", # "drawsvg", ] diff --git a/altium/Cargo.toml b/altium/Cargo.toml index 0625580..6e5e00b 100644 --- a/altium/Cargo.toml +++ b/altium/Cargo.toml @@ -22,11 +22,11 @@ num_enum = "0.7.2" quick-xml = "0.31.0" regex = "1.10.4" rust-ini = "0.21.0" -serde = "1.0.200" +serde = { version = "1.0.200", features = ["derive"] } serde-xml-rs = "0.6.0" svg = "0.17.0" uom = "0.36.0" -uuid = { version = "1.8.0", features = ["v1", "v4", "fast-rng"]} +uuid = { version = "1.8.0", features = ["v1", "v4", "fast-rng", "serde"]} xml-rs = "0.8.20" [dev-dependencies] diff --git a/altium/src/common.rs b/altium/src/common.rs index 6fa9a64..ab9ba6a 100644 --- a/altium/src/common.rs +++ b/altium/src/common.rs @@ -1,6 +1,7 @@ use std::{fmt, str}; use num_traits::CheckedMul; +use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::error::{AddContext, ErrorKind, Result, TruncBuf}; @@ -11,7 +12,7 @@ const SEP: u8 = b'|'; const KV_SEP: u8 = b'='; /// Common coordinate type with x and y positions in nnaometers. -#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct Location { // These are nonpublic because we might want to combine `Location` and `LocationFract` pub(crate) x: i32, @@ -19,7 +20,7 @@ pub struct Location { } /// Location with fraction -#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct LocationFract { pub x: i32, pub x_fract: i32, @@ -60,6 +61,17 @@ impl Location { } } +impl LocationFract { + /// Sometimes we don't want to deal with fractions + #[inline] + pub fn as_location(self) -> Location { + Location { + x: self.x, + y: self.y, + } + } +} + impl From<(i32, i32)> for Location { fn from(value: (i32, i32)) -> Self { Self { @@ -69,7 +81,7 @@ impl From<(i32, i32)> for Location { } } -#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] pub enum Visibility { Hidden, #[default] @@ -86,9 +98,11 @@ impl FromUtf8<'_> for Visibility { /// /// Every entity in Altium has a unique ID including files, library items, and records. // TODO: figure out what file types use this exact format -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq, Serialize, Deserialize)] +#[serde(untagged)] pub enum UniqueId { /// Altium's old style string unique ID + #[serde(with = "unique_id_serde")] Simple([u8; 8]), /// UUID style, used by some newer files Uuid(Uuid), @@ -139,6 +153,23 @@ impl FromUtf8<'_> for UniqueId { } } +mod unique_id_serde { + use serde::{de::Error, Deserialize, Deserializer, Serializer}; + + #[allow(clippy::trivially_copy_pass_by_ref)] + pub fn serialize(val: &[u8; 8], serializer: S) -> Result { + serializer.serialize_str(std::str::from_utf8(val).unwrap()) + } + + pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<[u8; 8], D::Error> { + // let s = ::deserialize(deserializer); + let s: &str = Deserialize::deserialize(deserializer)?; + s.as_bytes() + .try_into() + .map_err(|_| D::Error::custom(format!("expected a 8-byte unique ID; got '{s}'"))) + } +} + /// Altium uses the format `Key1=Val1|Key2=Val2...`, this handles that pub fn split_altium_map(buf: &[u8]) -> impl Iterator { buf.split(|b| *b == SEP).filter(|x| !x.is_empty()).map(|x| { @@ -169,7 +200,7 @@ pub fn str_from_utf8(buf: &[u8]) -> Result<&str, ErrorKind> { } /// RGB color -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub struct Rgb { pub r: u8, pub g: u8, @@ -230,7 +261,7 @@ impl FromUtf8<'_> for Rgb { } /// Rotation when only 4 values are allowed -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum Rotation90 { #[default] R0 = 0, @@ -256,7 +287,7 @@ impl FromUtf8<'_> for Rotation90 { } } -#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] pub enum ReadOnlyState { #[default] ReadWrite, diff --git a/altium/src/font.rs b/altium/src/font.rs index 5e56aa1..9c40c50 100644 --- a/altium/src/font.rs +++ b/altium/src/font.rs @@ -2,6 +2,8 @@ use std::ops::{Deref, DerefMut}; +use serde::{Deserialize, Serialize}; + lazy_static::lazy_static! { pub(crate) static ref DEFAULT_FONT: Font = Font { name: "Calibri".into(), @@ -10,7 +12,7 @@ lazy_static::lazy_static! { } /// A font that is stored in a library -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct Font { pub(crate) name: Box, pub(crate) size: u16, @@ -41,7 +43,7 @@ impl Default for &Font { // // Or `Arc>>>`. Yucky, but editable (edit the // font if you're the only user duplicate it if you're not) -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct FontCollection(Vec); impl FontCollection { diff --git a/altium/src/sch/params.rs b/altium/src/sch/params.rs index bbdf670..827d889 100644 --- a/altium/src/sch/params.rs +++ b/altium/src/sch/params.rs @@ -1,5 +1,7 @@ //! Parameter types stored in a schematic +use serde::{Deserialize, Serialize}; + use crate::{ common::{PosHoriz, PosVert}, parse::{FromUtf8, ParseUtf8}, @@ -87,7 +89,7 @@ impl FromUtf8<'_> for SheetStyle { } /// Allowed text alignments in a schematic -#[derive(Clone, Copy, Debug, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)] pub enum Justification { #[default] BottomLeft = 0, diff --git a/altium/src/sch/pin.rs b/altium/src/sch/pin.rs index d68509b..4e2ad0e 100644 --- a/altium/src/sch/pin.rs +++ b/altium/src/sch/pin.rs @@ -5,6 +5,7 @@ use std::str::{self, Utf8Error}; use altium_macros::FromRecord; use log::warn; +use serde::{Deserialize, Serialize}; use super::SchRecord; use crate::common::{mils_to_nm, Location, Rotation90, Visibility}; @@ -18,7 +19,7 @@ use crate::{ErrorKind, Result, UniqueId}; /// Altium stores pins as binary in the schematic libraries but text in the /// schematic documents, so we need to parse both. #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 2, record_variant = Pin)] pub struct SchPin { pub(super) formal_type: u8, @@ -77,7 +78,7 @@ impl SchPin { let (name, rest) = sized_buf_to_utf8(rest, "name")?; let (designator, rest) = sized_buf_to_utf8(rest, "designator")?; - if !matches!(rest, [_, 0x03, b'|', b'&', b'|']) { + if !matches!(rest, [_, 0x03, b'|', b'&', b'|'] | [0x0, 0x0]) { warn!("unexpected rest: {rest:02x?}"); } @@ -175,7 +176,7 @@ fn get_rotation_and_hiding(val: u8) -> (Rotation90, Visibility, Visibility) { } #[repr(u8)] -#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum ElectricalType { #[default] Input = 0, diff --git a/altium/src/sch/record.rs b/altium/src/sch/record.rs index ae8ab87..5965b04 100644 --- a/altium/src/sch/record.rs +++ b/altium/src/sch/record.rs @@ -58,6 +58,7 @@ use std::str; use altium_macros::FromRecord; pub use draw::SchDrawCtx; pub(super) use parse::parse_all_records; +use serde::{Deserialize, Serialize}; use super::params::Justification; use super::pin::SchPin; @@ -77,7 +78,7 @@ use crate::{ /// representation whereas others are just metadata. Each variant represents a record /// type. #[non_exhaustive] -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum SchRecord { Undefined, MetaData(Box), @@ -118,6 +119,50 @@ pub enum SchRecord { ImplementationChild2(ImplementationChild2), } +impl SchRecord { + pub fn name(&self) -> &'static str { + match self { + Self::Undefined => "Undefined", + Self::MetaData(_) => "MetaData", + Self::Pin(_) => "Pin", + Self::IeeeSymbol(_) => "IeeeSymbol", + Self::Label(_) => "Label", + Self::Bezier(_) => "Bezier", + Self::PolyLine(_) => "PolyLine", + Self::Polygon(_) => "Polygon", + Self::Ellipse(_) => "Ellipse", + Self::Piechart(_) => "Piechart", + Self::RectangleRounded(_) => "RectangleRounded", + Self::ElipticalArc(_) => "ElipticalArc", + Self::Arc(_) => "Arc", + Self::Line(_) => "Line", + Self::Rectangle(_) => "Rectangle", + Self::SheetSymbol(_) => "SheetSymbol", + Self::SheetEntry(_) => "SheetEntry", + Self::PowerPort(_) => "PowerPort", + Self::Port(_) => "Port", + Self::NoErc(_) => "NoErc", + Self::NetLabel(_) => "NetLabel", + Self::Bus(_) => "Bus", + Self::Wire(_) => "Wire", + Self::TextFrame(_) => "TextFrame", + Self::Junction(_) => "Junction", + Self::Image(_) => "Image", + Self::Sheet(_) => "Sheet", + Self::SheetName(_) => "SheetName", + Self::FileName(_) => "FileName", + Self::Designator(_) => "Designator", + Self::BusEntry(_) => "BusEntry", + Self::Template(_) => "Template", + Self::Parameter(_) => "Parameter", + Self::ImplementationList(_) => "ImplementationList", + Self::Implementation(_) => "Implementation", + Self::ImplementationChild1(_) => "ImplementationChild1", + Self::ImplementationChild2(_) => "ImplementationChild2", + } + } +} + /// Try all known record types (excludes binary pins) pub fn parse_any_record(buf: &[u8]) -> Result { let buf = buf.strip_prefix(b"|RECORD=").unwrap_or_else(|| { @@ -178,7 +223,7 @@ pub fn parse_any_record(buf: &[u8]) -> Result { /// Component metadata (AKA "Component") #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 1, use_box = true)] pub struct MetaData { all_pin_count: u32, @@ -209,7 +254,7 @@ pub struct MetaData { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 3)] pub struct IeeeSymbol { is_not_accessible: bool, @@ -219,7 +264,7 @@ pub struct IeeeSymbol { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 4)] pub struct Label { pub color: Rgb, @@ -227,7 +272,8 @@ pub struct Label { index_in_sheet: i16, is_not_accessible: bool, is_mirrored: bool, - pub location: Location, + pub location: LocationFract, + orientation: i32, owner_index: u8, owner_part_id: i8, text: Box, @@ -236,7 +282,7 @@ pub struct Label { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 5)] pub struct Bezier { color: Rgb, @@ -253,7 +299,7 @@ pub struct Bezier { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 6)] pub struct PolyLine { owner_index: u8, @@ -264,12 +310,12 @@ pub struct PolyLine { line_width: u32, pub color: Rgb, #[from_record(array = true, count = b"LocationCount", map = (X -> x, Y -> y))] - pub locations: Vec, + pub locations: Vec, pub unique_id: UniqueId, } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 7)] pub struct Polygon { pub area_color: Rgb, @@ -288,7 +334,7 @@ pub struct Polygon { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 8)] pub struct Ellipse { pub area_color: Rgb, @@ -308,7 +354,7 @@ pub struct Ellipse { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 9)] pub struct Piechart { owner_index: u8, @@ -316,7 +362,7 @@ pub struct Piechart { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 10)] pub struct RectangleRounded { pub area_color: Rgb, @@ -338,7 +384,7 @@ pub struct RectangleRounded { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 11)] pub struct ElipticalArc { owner_index: u8, @@ -359,14 +405,14 @@ pub struct ElipticalArc { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 12)] pub struct Arc { owner_index: u8, owner_part_id: i8, is_not_accessible: bool, index_in_sheet: i16, - pub location: Location, + pub location: LocationFract, radius: i8, radius_frac: i32, secondary_radius: i8, @@ -380,7 +426,7 @@ pub struct Arc { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 13)] pub struct Line { pub color: Rgb, @@ -401,20 +447,20 @@ pub struct Line { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 14)] pub struct Rectangle { pub area_color: Rgb, pub color: Rgb, /// Top right corner - pub corner: Location, + pub corner: LocationFract, index_in_sheet: i16, is_not_accessible: bool, pub is_solid: bool, #[from_record(convert = mils_to_nm)] line_width: u32, /// Bottom left corner - pub location: Location, + pub location: LocationFract, owner_index: u8, owner_part_id: i8, owner_part_display_mode: i8, @@ -423,7 +469,7 @@ pub struct Rectangle { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 15)] pub struct SheetSymbol { owner_index: u8, @@ -446,7 +492,7 @@ pub struct SheetSymbol { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 16)] pub struct SheetEntry { owner_index: u8, @@ -467,7 +513,7 @@ pub struct SheetEntry { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 17)] pub struct PowerPort { owner_index: u8, @@ -485,7 +531,7 @@ pub struct PowerPort { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 18)] pub struct Port { alignment: u16, @@ -507,7 +553,7 @@ pub struct Port { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 22)] pub struct NoErc { owner_index: u8, @@ -523,7 +569,7 @@ pub struct NoErc { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 25)] pub struct NetLabel { owner_index: u8, @@ -537,7 +583,7 @@ pub struct NetLabel { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 26)] pub struct Bus { owner_index: u8, @@ -552,7 +598,7 @@ pub struct Bus { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 27)] pub struct Wire { owner_index: u8, @@ -567,7 +613,7 @@ pub struct Wire { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 28)] pub struct TextFrame { location: LocationFract, @@ -585,7 +631,7 @@ pub struct TextFrame { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 29)] pub struct Junction { owner_index: u8, @@ -593,7 +639,7 @@ pub struct Junction { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 30)] pub struct Image { owner_index: u8, @@ -611,7 +657,7 @@ pub struct Image { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 31)] pub struct Sheet { owner_index: u8, @@ -645,7 +691,7 @@ pub struct Sheet { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 32)] pub struct SheetName { owner_index: u8, @@ -659,7 +705,7 @@ pub struct SheetName { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 33)] pub struct FileName { owner_index: u8, @@ -673,7 +719,7 @@ pub struct FileName { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 34)] pub struct Designator { owner_index: u8, @@ -690,7 +736,7 @@ pub struct Designator { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 37)] pub struct BusEntry { owner_index: u8, @@ -698,7 +744,7 @@ pub struct BusEntry { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 39)] pub struct Template { owner_index: u8, @@ -708,7 +754,7 @@ pub struct Template { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 41)] pub struct Parameter { owner_index: u8, @@ -725,7 +771,7 @@ pub struct Parameter { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 44)] pub struct ImplementationList { owner_index: u8, @@ -734,7 +780,7 @@ pub struct ImplementationList { /// Things like models, including footprints #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 45)] pub struct Implementation { owner_index: u8, @@ -753,7 +799,7 @@ pub struct Implementation { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 46)] pub struct ImplementationChild1 { owner_index: u8, @@ -761,7 +807,7 @@ pub struct ImplementationChild1 { } #[non_exhaustive] -#[derive(Clone, Debug, Default, PartialEq, FromRecord)] +#[derive(Clone, Debug, Default, PartialEq, FromRecord, Serialize, Deserialize)] #[from_record(id = 48)] pub struct ImplementationChild2 { owner_index: u8, diff --git a/altium/src/sch/record/draw.rs b/altium/src/sch/record/draw.rs index d39c7f3..33104b2 100644 --- a/altium/src/sch/record/draw.rs +++ b/altium/src/sch/record/draw.rs @@ -186,8 +186,8 @@ impl Draw for record::PolyLine { let &[a, b] = window else { unreachable!() }; canvas.draw_line(DrawLine { - start: a, - end: b, + start: a.as_location(), + end: b.as_location(), color: self.color, width: self.line_width * 4, ..Default::default() diff --git a/ecad-cli/Cargo.toml b/ecad-cli/Cargo.toml new file mode 100644 index 0000000..7e3cf28 --- /dev/null +++ b/ecad-cli/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "ecad-cli" +version = "0.1.0" +edition = "2021" + +[dependencies] +clap = { version = "4.5.4", features = ["derive"] } +altium = { path = "../altium", version = "0.2.1" } +serde_json = "1.0.116" diff --git a/ecad-cli/src/cli.rs b/ecad-cli/src/cli.rs new file mode 100644 index 0000000..6758c5d --- /dev/null +++ b/ecad-cli/src/cli.rs @@ -0,0 +1,41 @@ +use std::path::PathBuf; + +#[derive(clap::Parser, Debug)] +#[command(version, about, long_about = None)] +pub struct Args { + #[command(subcommand)] + pub sub: Subcommand, +} + +#[derive(Debug, clap::Subcommand)] +pub enum Subcommand { + #[command(subcommand, alias = "sl")] + Schlib(CmdSchlib), + #[command(subcommand, alias = "pl")] + Pcblib(CmdPcblib), +} + +#[derive(Debug, clap::Subcommand)] +pub enum CmdSchlib { + List(LibListArgs), +} + +#[derive(Debug, clap::Subcommand)] +pub enum CmdPcblib { + List(LibListArgs), +} + +#[derive(Debug, clap::Args)] +pub struct LibListArgs { + /// Name of the file to open + pub fname: PathBuf, + /// Single item + #[arg(short, long)] + pub item: Vec, + /// List records for the items + #[arg(short, long, default_value_t = false)] + pub records: bool, + + #[arg(short, long, value_delimiter = ',')] + pub filter: Vec, +} diff --git a/ecad-cli/src/main.rs b/ecad-cli/src/main.rs new file mode 100644 index 0000000..8c21645 --- /dev/null +++ b/ecad-cli/src/main.rs @@ -0,0 +1,73 @@ +use altium::{sch::Component, SchLib}; +use clap::Parser; +use cli::CmdSchlib; +use serde_json::{json, Value}; + +use crate::cli::Subcommand; + +mod cli; + +fn main() { + let args = cli::Args::parse(); + + match args.sub { + Subcommand::Schlib(schlib_cmd) => handle_schlib_cmd(schlib_cmd), + Subcommand::Pcblib(_pcblib_cmd) => {} + }; +} + +fn handle_schlib_cmd(cmd: CmdSchlib) { + match cmd { + CmdSchlib::List(args) => { + let lib = SchLib::open(&args.fname).unwrap(); + let cfg = PrintCfg { + include_records: args.records, + filter: &args.filter, + }; + + if args.item.is_empty() { + lib.components() + .for_each(|comp| print_component(&comp, &cfg)); + } else { + args.item + .iter() + .for_each(|name| print_component(&lib.get_component(name).unwrap(), &cfg)) + } + // for comp in lib.components() + // { + // dbg!(comp); + // } + } + } +} + +struct PrintCfg<'a> { + include_records: bool, + filter: &'a [String], +} + +/// Just use json to print the thing, it's easier +fn print_component(comp: &Component, cfg: &PrintCfg) { + let val = if cfg.include_records { + let records: Vec<_> = if cfg.filter.is_empty() { + comp.records().collect() + } else { + comp.records() + .filter(|rec| cfg.filter.iter().any(|filt| filt == rec.name())) + .collect() + }; + + // Print full records + json! {{ + comp.name(): { + "records": records + } + }} + } else { + // Print only the name + Value::String(comp.name().into()) + }; + + let s = serde_json::to_string_pretty(&val).unwrap(); + println!("{s}"); +} diff --git a/ecadg/src/lib.rs b/ecadg/src/lib.rs index deb28b7..693a287 100644 --- a/ecadg/src/lib.rs +++ b/ecadg/src/lib.rs @@ -5,6 +5,23 @@ #![allow(clippy::too_many_lines)] #![allow(clippy::cast_precision_loss)] +#[allow(unused_macros)] +macro_rules! do_once { + ($($tt:tt)*) => {{ + static DONE: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); + if !DONE.swap(true, std::sync::atomic::Ordering::Relaxed) { + $($tt)* + } + }} +} + +#[allow(unused_macros)] +macro_rules! println_once { + ($($tt:tt)*) => { + do_once!(println!($($tt:tt)*)) + } +} + mod app; mod backend; mod draw;