Skip to content

Commit

Permalink
gui: finish basic schematic drawing
Browse files Browse the repository at this point in the history
Includes the following changes:

- Add a simple marker for the origin
- Tessellate shapes
- Implement `Canvas` for wgpu
- Basic drawings work

Work on the grid and the scaling is still needed, but I am pretty happy
with what we currently have here.
  • Loading branch information
tgross35 committed May 2, 2024
1 parent 10a8ad9 commit db77f83
Show file tree
Hide file tree
Showing 25 changed files with 1,298 additions and 289 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ History/
# demo output
altium/*.svg
.cargo
**.xcodeproj
12 changes: 12 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

197 changes: 101 additions & 96 deletions altium-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -69,100 +69,7 @@ fn inner(tokens: TokenStream2) -> syn::Result<TokenStream2> {

// 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<T>`
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 {
Expand Down Expand Up @@ -198,6 +105,104 @@ fn inner(tokens: TokenStream2) -> syn::Result<TokenStream2> {
Ok(ret)
}

fn handle_field(
field: Field,
struct_ident: &Ident,
match_arms: &mut Vec<TokenStream2>,
outer_flags: &mut Vec<TokenStream2>,
) {
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<T>`
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 {
Expand Down Expand Up @@ -364,7 +369,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 {
Expand Down
2 changes: 2 additions & 0 deletions altium/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ 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"
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"

Expand Down
42 changes: 25 additions & 17 deletions altium/src/common.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::{fmt, str};

use num_traits::CheckedMul;
use uuid::Uuid;

use crate::error::{AddContext, ErrorKind, Result, TruncBuf};
Expand All @@ -9,7 +10,7 @@ use crate::parse::{FromUtf8, ParseUtf8};
const SEP: u8 = b'|';
const KV_SEP: u8 = b'=';

/// Common coordinate type
/// Common coordinate type with x and y positions in nnaometers.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Location {
// These are nonpublic because we might want to combine `Location` and `LocationFract`
Expand Down Expand Up @@ -180,27 +181,36 @@ impl Rgb {
format!("#{:02x}{:02x}{:02x}", self.r, self.g, self.b)
}

pub fn from_hex(r: u8, g: u8, b: u8) -> Self {
pub const fn from_hex(r: u8, g: u8, b: u8) -> Self {
Self { r, g, b }
}

pub fn black() -> Self {
pub fn as_float_rgba(self) -> [f32; 4] {
[
f32::from(self.r) / 255.0,
f32::from(self.g) / 255.0,
f32::from(self.b) / 255.0,
1.0,
]
}

pub const fn black() -> Self {
Self::from_hex(0x00, 0x00, 0x00)
}

pub fn white() -> Self {
pub const fn white() -> Self {
Self::from_hex(0xff, 0xff, 0xff)
}

pub fn red() -> Self {
pub const fn red() -> Self {
Self::from_hex(0xff, 0x00, 0x00)
}

pub fn green() -> Self {
pub const fn green() -> Self {
Self::from_hex(0x00, 0xff, 0x00)
}

pub fn blue() -> Self {
pub const fn blue() -> Self {
Self::from_hex(0x00, 0x00, 0xff)
}
}
Expand Down Expand Up @@ -307,16 +317,14 @@ pub fn is_number_pattern(s: &[u8], prefix: &[u8]) -> bool {
}

/// Infallible conversion
pub fn i32_mils_to_nm(mils: i32) -> Result<i32> {
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<u32> {
const FACTOR: u32 = 25400;
mils.checked_mul(FACTOR).ok_or_else(|| {
pub fn mils_to_nm<T>(mils: T) -> Result<T>
where
T: CheckedMul,
T: From<u16>,
i64: From<T>,
{
const FACTOR: u16 = 25400;
mils.checked_mul(&FACTOR.into()).ok_or_else(|| {
ErrorKind::Overflow(mils.into(), FACTOR.into(), '*').context("converting units")
})
}
Loading

0 comments on commit db77f83

Please sign in to comment.