diff --git a/CHANGELOG.md b/CHANGELOG.md index e137bc6bd..5458e6267 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ and this project adheres to Semantic Versioning (https://semver.org/spec/v2.0.0. ## [Unreleased] +### Changed + +- KiCad symbol is now the source of truth for component metadata (footprint, datasheet, part); generated `.zen` files are minimal wrappers. +- `Component()` inherits `skip_bom` from the KiCad symbol `in_bom` flag (inverted) when not explicitly set. + ### Added - Warn when module `io()`s are declared but never connected to any realized ports. diff --git a/crates/pcb-component-gen/src/lib.rs b/crates/pcb-component-gen/src/lib.rs index a77198518..70fc4a700 100644 --- a/crates/pcb-component-gen/src/lib.rs +++ b/crates/pcb-component-gen/src/lib.rs @@ -93,49 +93,28 @@ pub fn sanitize_pin_name(name: &str) -> String { } pub struct GenerateComponentZenArgs<'a> { - pub mpn: &'a str, pub component_name: &'a str, pub symbol: &'a Symbol, pub symbol_filename: &'a str, - pub footprint_filename: Option<&'a str>, - pub datasheet_filename: Option<&'a str>, - pub manufacturer: Option<&'a str>, pub generated_by: &'a str, pub include_skip_bom: bool, pub include_skip_pos: bool, pub skip_bom_default: bool, pub skip_pos_default: bool, - pub pin_defs: Option<&'a BTreeMap>, } pub fn generate_component_zen(args: GenerateComponentZenArgs<'_>) -> Result { let component_name = sanitize_mpn_for_path(args.component_name); + // Group pins by sanitized name; duplicate signal names (e.g. multiple "NC" pads) + // naturally merge into one io() declaration. let mut pin_groups: BTreeMap> = BTreeMap::new(); - let mut pin_defs_vec: Option> = None; - - if let Some(pin_defs) = args.pin_defs { - pin_defs_vec = Some( - pin_defs - .iter() - .map(|(name, pad)| serde_json::json!({"name": name, "pad": pad})) - .collect(), - ); - - for signal_name in pin_defs.keys() { - pin_groups - .entry(sanitize_pin_name(signal_name)) - .or_default() - .insert(signal_name.to_string()); - } - } else { - for pin in &args.symbol.pins { - let signal_name = pin.signal_name().to_string(); - pin_groups - .entry(sanitize_pin_name(&signal_name)) - .or_default() - .insert(signal_name); - } + for pin in &args.symbol.pins { + let signal_name = pin.signal_name().to_string(); + pin_groups + .entry(sanitize_pin_name(&signal_name)) + .or_default() + .insert(signal_name); } let pin_groups_vec: Vec<_> = pin_groups @@ -162,14 +141,9 @@ pub fn generate_component_zen(args: GenerateComponentZenArgs<'_>) -> Result = BTreeMap::new(); - pin_defs.insert("NC_6".to_string(), "6".to_string()); - pin_defs.insert("NC_7".to_string(), "7".to_string()); - let zen = generate_component_zen(GenerateComponentZenArgs { - mpn: "MPN1", component_name: "MPN1", symbol: &symbol, symbol_filename: "MPN1.kicad_sym", - footprint_filename: Some("FP.kicad_mod"), - datasheet_filename: None, - manufacturer: None, generated_by: "pcb import", include_skip_bom: false, include_skip_pos: false, skip_bom_default: false, skip_pos_default: false, - pin_defs: Some(&pin_defs), }) .unwrap(); - assert!(zen.contains("pin_defs = {")); - assert!(zen.contains("\"NC_6\": \"6\"")); - assert!(zen.contains("\"NC_7\": \"7\"")); - assert!(zen.contains("\"NC_6\": Pins.NC_6")); - assert!(zen.contains("\"NC_7\": Pins.NC_7")); + // Single io() for the shared signal name + assert!(zen.contains("NC = io(\"NC\", Net)")); + assert!(zen.contains("\"NC\": Pins.NC")); + // No pin_defs needed + assert!(!zen.contains("pin_defs")); } } diff --git a/crates/pcb-component-gen/templates/component.zen.jinja b/crates/pcb-component-gen/templates/component.zen.jinja index ae46f188c..54105b359 100644 --- a/crates/pcb-component-gen/templates/component.zen.jinja +++ b/crates/pcb-component-gen/templates/component.zen.jinja @@ -4,15 +4,12 @@ Auto-generated using `{{ generated_by }}`. """ -mpn = config("mpn", str, optional=True, default="{{ mpn }}") -manufacturer = config("manufacturer", str, optional=True{% if manufacturer %}, default="{{ manufacturer }}"{% endif %}) -alternatives = config("alternatives", list, default=[]) -{%- if include_skip_bom %} -skip_bom = config("skip_bom", bool, default={{ "True" if skip_bom_default else "False" }}) -{%- endif %} -{%- if include_skip_pos %} -skip_pos = config("skip_pos", bool, default={{ "True" if skip_pos_default else "False" }}) -{%- endif %} +{% if include_skip_bom %} +skip_bom = config("skip_bom", bool, default = {{ "True" if skip_bom_default else "False" }}) +{% endif %} +{% if include_skip_pos %} +skip_pos = config("skip_pos", bool, default = {{ "True" if skip_pos_default else "False" }}) +{% endif %} Pins = struct( {%- for pin in pin_groups %} @@ -22,32 +19,16 @@ Pins = struct( Component( name = "{{ component_name }}", - mpn = mpn, - manufacturer = manufacturer, {%- if include_skip_bom %} skip_bom = skip_bom, {%- endif %} {%- if include_skip_pos %} skip_pos = skip_pos, -{%- endif %} -{%- if datasheet_file %} - datasheet = File("{{ datasheet_file }}"), -{%- endif %} -{%- if pin_defs %} - pin_defs = { -{%- for pin in pin_defs %} - "{{ pin.name }}": "{{ pin.pad }}", -{%- endfor %} - }, {%- endif %} symbol = Symbol(library = "{{ sym_path }}"), - footprint = File("{{ footprint_path }}"), pins = { {%- for pin in pin_mappings %} "{{ pin.original_name }}": Pins.{{ pin.sanitized_name }}{{ "," if not loop.last }} {%- endfor %} }, - properties = { - "alternatives": alternatives, - }, ) diff --git a/crates/pcb-diode-api/src/component.rs b/crates/pcb-diode-api/src/component.rs index b88b3195a..45c71ea73 100644 --- a/crates/pcb-diode-api/src/component.rs +++ b/crates/pcb-diode-api/src/component.rs @@ -4,6 +4,7 @@ use clap::Args; use colored::Colorize; use indicatif::ProgressBar; use inquire::{Select, Text}; +use pcb_eda::kicad::metadata::SymbolMetadata; use pcb_sexpr::formatter::{FormatMode, format_tree, prettify}; use pcb_sexpr::kicad::symbol::{ find_symbol_index, kicad_symbol_lib_items_mut, rewrite_symbol_properties, symbol_names, @@ -879,10 +880,11 @@ pub fn add_component_to_workspace( ); spinner.finish_and_clear(); - let include_datasheet_in_zen = matches!( + let datasheet_ref = matches!( datasheet_outcome, DatasheetProcessingOutcome::FallbackScanned - ); + ) + .then(|| format!("{}.pdf", &sanitized_mpn)); match datasheet_outcome { DatasheetProcessingOutcome::SymbolResolved => { eprintln!("{} Resolved datasheet from symbol", "✓".green()); @@ -909,7 +911,7 @@ pub fn add_component_to_workspace( &component_dir, &identity.part_number, identity.manufacturer.as_deref(), - include_datasheet_in_zen, + datasheet_ref.as_deref(), )?; if !zen_file.exists() { @@ -949,13 +951,12 @@ fn finalize_component( component_dir: &Path, mpn: &str, manufacturer: Option<&str>, - include_datasheet_in_zen: bool, + datasheet_ref: Option<&str>, ) -> Result<()> { let sanitized_mpn = pcb_component_gen::sanitize_mpn_for_path(mpn); let symbol_path = component_dir.join(format!("{}.kicad_sym", &sanitized_mpn)); let footprint_path = component_dir.join(format!("{}.kicad_mod", &sanitized_mpn)); let step_path = component_dir.join(format!("{}.step", &sanitized_mpn)); - let datasheet_path = component_dir.join(format!("{}.pdf", &sanitized_mpn)); if footprint_path.exists() { if step_path.exists() { @@ -975,6 +976,7 @@ fn finalize_component( &symbol_source, &symbol_path, footprint_stem_if_exists(&footprint_path)?.as_deref(), + datasheet_ref, mpn, manufacturer, )?; @@ -986,18 +988,9 @@ fn finalize_component( let symbol = only_symbol_in_library(&symbol_lib, &symbol_path)?; let content = generate_zen_file( - mpn, &sanitized_mpn, symbol, &format!("{}.kicad_sym", &sanitized_mpn), - footprint_path - .exists() - .then(|| format!("{}.kicad_mod", &sanitized_mpn)) - .as_deref(), - (include_datasheet_in_zen && datasheet_path.exists()) - .then(|| format!("{}.pdf", &sanitized_mpn)) - .as_deref(), - manufacturer, )?; let zen_file = component_dir.join(format!("{}.zen", &sanitized_mpn)); @@ -1035,6 +1028,7 @@ fn rewrite_symbol_component_metadata_text( source: &str, symbol_path: &Path, footprint_ref: Option<&str>, + datasheet_ref: Option<&str>, mpn: &str, manufacturer: Option<&str>, ) -> Result { @@ -1044,10 +1038,14 @@ fn rewrite_symbol_component_metadata_text( })?; let symbol_items = only_symbol_in_library_mut(root, symbol_path)?; - let mut next_properties = symbol_properties(symbol_items); + let current_metadata = SymbolMetadata::from_property_iter(symbol_properties(symbol_items)); + let mut next_properties = current_metadata.to_properties_map(); if let Some(footprint_ref) = footprint_ref { next_properties.insert("Footprint".to_string(), footprint_ref.to_string()); } + if let Some(datasheet_ref) = datasheet_ref { + next_properties.insert("Datasheet".to_string(), datasheet_ref.to_string()); + } next_properties.insert("Manufacturer_Part_Number".to_string(), mpn.to_string()); if let Some(manufacturer) = manufacturer.filter(|m| !m.trim().is_empty()) { next_properties.insert("Manufacturer_Name".to_string(), manufacturer.to_string()); @@ -1144,28 +1142,19 @@ fn write_component_files(component_file: &Path, component_dir: &Path, content: & } fn generate_zen_file( - mpn: &str, component_name: &str, symbol: &pcb_eda::Symbol, symbol_filename: &str, - footprint_filename: Option<&str>, - datasheet_filename: Option<&str>, - manufacturer: Option<&str>, ) -> Result { pcb_component_gen::generate_component_zen(pcb_component_gen::GenerateComponentZenArgs { - mpn, component_name, symbol, symbol_filename, - footprint_filename, - datasheet_filename, - manufacturer, generated_by: "pcb search", include_skip_bom: false, include_skip_pos: false, skip_bom_default: false, skip_pos_default: false, - pin_defs: None, }) } @@ -1512,7 +1501,13 @@ fn execute_from_dir(dir: &Path, workspace_root: &Path) -> Result<()> { // Finalize: embed STEP, generate .zen file println!("{} Generating .zen file...", "→".blue().bold()); - finalize_component(&component_dir, &mpn, manufacturer.as_deref(), true)?; + let datasheet_ref = has_datasheet.then(|| format!("{}.pdf", &sanitized_mpn)); + finalize_component( + &component_dir, + &mpn, + manufacturer.as_deref(), + datasheet_ref.as_deref(), + )?; // Show result let display_path = zen_file.strip_prefix(workspace_root).unwrap_or(&zen_file); @@ -2467,16 +2462,7 @@ mod tests { ..Default::default() }; - let zen_content = generate_zen_file( - "TEST-MPN", - "TestComponent", - &symbol, - "symbol.kicad_sym", - Some("footprint.kicad_mod"), - None, - Some("TestMfr"), - ) - .unwrap(); + let zen_content = generate_zen_file("TestComponent", &symbol, "symbol.kicad_sym").unwrap(); // The pins dict should NOT have duplicate "GND" keys // Count how many times "GND" appears as a dict key @@ -2559,12 +2545,14 @@ mod tests { symbol, Path::new("TEST.kicad_sym"), Some("NewFootprint"), + Some("NEW-MPN.pdf"), "NEW-MPN", Some("NewMfr"), ) .unwrap(); assert!(updated.contains("(property \"Footprint\" \"NewFootprint\"")); assert!(!updated.contains("OldLib:OldFootprint")); + assert!(updated.contains("(property \"Datasheet\" \"NEW-MPN.pdf\"")); assert!(updated.contains("(property \"Manufacturer_Part_Number\" \"NEW-MPN\"")); assert!(updated.contains("(property \"Manufacturer_Name\" \"NewMfr\"")); } @@ -2581,7 +2569,7 @@ mod tests { nonce )); std::fs::create_dir_all(&dir).unwrap(); - let err = finalize_component(&dir, "MISSING", None, false).unwrap_err(); + let err = finalize_component(&dir, "MISSING", None, None).unwrap_err(); let _ = std::fs::remove_dir_all(&dir); assert!(err.to_string().contains("Expected symbol file not found")); } diff --git a/crates/pcb-diode-api/src/datasheet.rs b/crates/pcb-diode-api/src/datasheet.rs index a1a16af7c..9127d25d4 100644 --- a/crates/pcb-diode-api/src/datasheet.rs +++ b/crates/pcb-diode-api/src/datasheet.rs @@ -99,9 +99,17 @@ pub fn resolve_datasheet( execute_resolve_execution(&client, auth_token, execution, None) } ResolveDatasheetInput::KicadSymPath { path, symbol_name } => { - let url = extract_datasheet_url_from_kicad_sym(path, symbol_name.as_deref())?; - let canonical_url = canonicalize_url(&url)?; - resolve_source_url_datasheet(&client, auth_token, canonical_url) + let reference = + extract_datasheet_reference_from_kicad_sym(path, symbol_name.as_deref())?; + if is_http_datasheet_url(&reference) { + let canonical_url = canonicalize_url(&reference)?; + resolve_source_url_datasheet(&client, auth_token, canonical_url) + } else { + let pdf_path = resolve_local_datasheet_path_from_kicad_sym(path, &reference)?; + validate_local_pdf(&pdf_path)?; + let execution = ResolveExecution::from_pdf_path(pdf_path, None)?; + execute_resolve_execution(&client, auth_token, execution, None) + } } } } @@ -316,7 +324,10 @@ fn reset_materialized_cache(materialized_dir: &Path, images_dir: &Path, complete } } -fn extract_datasheet_url_from_kicad_sym(path: &Path, symbol_name: Option<&str>) -> Result { +fn extract_datasheet_reference_from_kicad_sym( + path: &Path, + symbol_name: Option<&str>, +) -> Result { let symbol_lib = pcb_eda::SymbolLibrary::from_file(path) .with_context(|| format!("Failed to parse KiCad symbol file {}", path.display()))?; @@ -324,10 +335,22 @@ fn extract_datasheet_url_from_kicad_sym(path: &Path, symbol_name: Option<&str>) symbol .datasheet .as_deref() - .map(str::trim) - .filter(|v| is_usable_datasheet_value(v)) + .and_then(pcb_eda::usable_kicad_field_value) .map(ToOwned::to_owned) - .ok_or_else(|| anyhow::anyhow!("No valid Datasheet URL found in {}", path.display())) + .ok_or_else(|| anyhow::anyhow!("No valid Datasheet found in {}", path.display())) +} + +fn resolve_local_datasheet_path_from_kicad_sym( + path: &Path, + datasheet_ref: &str, +) -> Result { + let datasheet_path = Path::new(datasheet_ref); + if datasheet_path.is_absolute() { + return Ok(datasheet_path.to_path_buf()); + } + + let symbol_dir = path.parent().unwrap_or_else(|| Path::new("")); + Ok(symbol_dir.join(datasheet_path)) } fn select_symbol_from_library<'a>( @@ -365,10 +388,7 @@ fn select_symbol_from_library<'a>( Ok(&symbols[0]) } -fn is_usable_datasheet_value(value: &str) -> bool { - if value.is_empty() || value == "~" { - return false; - } +fn is_http_datasheet_url(value: &str) -> bool { value.starts_with("http://") || value.starts_with("https://") } @@ -727,7 +747,7 @@ mod tests { } #[test] - fn test_extract_datasheet_url_from_symbols_uses_first_valid_value() { + fn test_extract_datasheet_reference_from_symbols_uses_first_valid_value() { let source = r#"(kicad_symbol_lib (version 20211014) (generator kicad_symbol_editor) @@ -760,6 +780,21 @@ mod tests { ); } + #[test] + fn test_resolve_local_datasheet_path_from_kicad_sym_uses_symbol_directory() { + let root = std::env::temp_dir().join(format!("datasheet-local-ref-{}", Uuid::new_v4())); + let symbol_dir = root.join("parts"); + fs::create_dir_all(&symbol_dir).unwrap(); + let symbol_path = symbol_dir.join("Part.kicad_sym"); + fs::write(&symbol_path, "(kicad_symbol_lib)").unwrap(); + + let resolved = + resolve_local_datasheet_path_from_kicad_sym(&symbol_path, "docs/Part.pdf").unwrap(); + assert_eq!(resolved, symbol_dir.join("docs/Part.pdf")); + + fs::remove_dir_all(root).unwrap(); + } + #[test] fn test_parse_request_rejects_symbol_name_without_kicad_sym_path() { let args = serde_json::json!({ diff --git a/crates/pcb-eda/src/kicad/symbol.rs b/crates/pcb-eda/src/kicad/symbol.rs index b7d2120bf..1ae5aa061 100644 --- a/crates/pcb-eda/src/kicad/symbol.rs +++ b/crates/pcb-eda/src/kicad/symbol.rs @@ -1,5 +1,5 @@ use crate::kicad::metadata::SymbolMetadata; -use crate::{Part, Pin, PinAt, Symbol}; +use crate::{Part, Pin, PinAt, Symbol, is_placeholder_kicad_pin_name}; use anyhow::Result; use pcb_sexpr::{Sexpr, SexprKind, parse}; use serde::Serialize; @@ -167,6 +167,7 @@ pub(super) fn parse_symbol(symbol_data: &[Sexpr]) -> Result { let mut symbol = KicadSymbol { name, raw_sexp: Some(Sexpr::list(symbol_data.to_vec())), + in_bom: true, // KiCad default; overridden by explicit (in_bom no) ..Default::default() }; let mut nested_pin_groups: BTreeMap> = BTreeMap::new(); @@ -231,7 +232,7 @@ struct NestedStylePins { } fn is_named_pin(pin: &KicadPin) -> bool { - !pin.name.is_empty() && pin.name != "~" + !is_placeholder_kicad_pin_name(&pin.name) } // Parse pins from a nested symbol section. diff --git a/crates/pcb-eda/src/lib.rs b/crates/pcb-eda/src/lib.rs index 8c5d99810..fb025160c 100644 --- a/crates/pcb-eda/src/lib.rs +++ b/crates/pcb-eda/src/lib.rs @@ -62,13 +62,23 @@ fn is_false(v: &bool) -> bool { !*v } +/// KiCad uses `~` as a placeholder for an absent text value in several contexts. +pub fn usable_kicad_field_value(value: &str) -> Option<&str> { + let trimmed = value.trim(); + (!trimmed.is_empty() && trimmed != "~").then_some(trimmed) +} + +pub fn is_placeholder_kicad_pin_name(name: &str) -> bool { + usable_kicad_field_value(name).is_none() +} + impl Pin { /// KiCad uses `~` as a placeholder pin name for "unnamed" pins. /// /// When consuming KiCad symbols, treat unnamed pins as being identified by their number so /// connectivity mappings stay stable and match Zener's Symbol signal naming semantics. pub fn signal_name(&self) -> &str { - if self.name == "~" || self.name.is_empty() { + if is_placeholder_kicad_pin_name(&self.name) { &self.number } else { &self.name @@ -152,3 +162,34 @@ impl SymbolLibrary { self.symbols.first() } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn usable_kicad_field_value_filters_placeholders() { + assert_eq!(usable_kicad_field_value("value"), Some("value")); + assert_eq!(usable_kicad_field_value(" value "), Some("value")); + assert_eq!(usable_kicad_field_value(""), None); + assert_eq!(usable_kicad_field_value(" "), None); + assert_eq!(usable_kicad_field_value("~"), None); + } + + #[test] + fn signal_name_falls_back_for_placeholder_pin_names() { + let pin = Pin { + name: "~".to_string(), + number: "42".to_string(), + ..Default::default() + }; + assert_eq!(pin.signal_name(), "42"); + + let named_pin = Pin { + name: "VCC".to_string(), + number: "1".to_string(), + ..Default::default() + }; + assert_eq!(named_pin.signal_name(), "VCC"); + } +} diff --git a/crates/pcb-zen-core/src/lang/component.rs b/crates/pcb-zen-core/src/lang/component.rs index 95d236c2f..854149e9e 100644 --- a/crates/pcb-zen-core/src/lang/component.rs +++ b/crates/pcb-zen-core/src/lang/component.rs @@ -43,8 +43,6 @@ pub enum ComponentError { NameNotString, #[error("`footprint` must be a string")] FootprintNotString, - #[error("could not determine parent directory of current file")] - ParentDirectoryNotFound, #[error("`pins` must be a dict mapping pin names to Net")] PinsNotDict, #[error("`prefix` must be a string")] @@ -359,7 +357,8 @@ fn resolve_component_sourcing<'v>( symbol .properties() .get("Manufacturer_Part_Number") - .map(|s| s.to_owned()) + .and_then(|s| pcb_eda::usable_kicad_field_value(s)) + .map(ToOwned::to_owned) }); let manufacturer = explicit_manufacturer .or_else(|| property_string(properties_map, "manufacturer")) @@ -368,7 +367,8 @@ fn resolve_component_sourcing<'v>( symbol .properties() .get("Manufacturer_Name") - .map(|s| s.to_owned()) + .and_then(|s| pcb_eda::usable_kicad_field_value(s)) + .map(ToOwned::to_owned) }); // Manifest parts fallback: if no MPN from explicit/properties/symbol, use manifest primary @@ -388,6 +388,61 @@ fn resolve_component_sourcing<'v>( (part, vec![]) } +fn resolve_symbol_datasheet( + final_symbol: &SymbolValue, + eval_ctx: &crate::EvalContext, +) -> starlark::Result> { + let Some(datasheet_prop) = final_symbol + .properties() + .get("Datasheet") + .and_then(|value| pcb_eda::usable_kicad_field_value(value)) + else { + return Ok(None); + }; + + if datasheet_prop.starts_with("http://") + || datasheet_prop.starts_with("https://") + || datasheet_prop.starts_with(pcb_sch::PACKAGE_URI_PREFIX) + { + return Ok(Some(datasheet_prop.to_owned())); + } + + let symbol_source_uri = final_symbol.source_uri().ok_or_else(|| { + starlark::Error::new_other(anyhow!( + "`symbol` datasheet path can only be inferred when the symbol is loaded from a file" + )) + })?; + let symbol_source = eval_ctx + .resolution() + .resolve_package_uri(symbol_source_uri) + .map_err(|e| { + starlark::Error::new_other(anyhow!( + "Failed to resolve symbol library '{}': {}", + symbol_source_uri, + e + )) + })?; + + let resolved = eval_ctx + .get_config() + .resolve_path(datasheet_prop, &symbol_source) + .map_err(|e| { + starlark::Error::new_other(anyhow!( + "Failed to resolve symbol datasheet path '{}' relative to '{}': {}", + datasheet_prop, + symbol_source.display(), + e + )) + })?; + + Ok(Some( + eval_ctx + .resolution() + .format_package_uri(&resolved) + .unwrap_or_else(|| resolved.to_string_lossy().into_owned()), + )) +} + fn manifest_part_matches_symbol(part: &ManifestPart, symbol: &SymbolValue) -> bool { part.symbol_name .as_deref() @@ -504,11 +559,7 @@ fn infer_local_footprint_from_symbol_property( return Ok(None); }; - let symbol_dir = symbol_source.parent().ok_or_else(|| { - starlark::Error::new_other(anyhow!( - "Could not infer footprint: symbol source path has no parent directory" - )) - })?; + let symbol_dir = symbol_source.parent().unwrap_or_else(|| Path::new("")); let candidate = symbol_dir.join(format!("{stem}.kicad_mod")); if !eval_ctx.file_provider().exists(&candidate) { @@ -605,12 +656,12 @@ fn resolve_component_footprint( let footprint_prop = final_symbol .properties() .get("Footprint") + .and_then(|value| pcb_eda::usable_kicad_field_value(value)) .ok_or_else(|| { starlark::Error::new_other(anyhow!( "`footprint` is required unless symbol property `Footprint` can be inferred" )) - })? - .as_str(); + })?; if let Some(inferred) = infer_local_footprint_from_symbol_property(&symbol_source, footprint_prop, eval_ctx)? @@ -1303,6 +1354,7 @@ where source_uri: symbol_value.source_uri.clone(), raw_sexp: symbol_value.raw_sexp.clone(), properties: symbol_value.properties.clone(), + in_bom: symbol_value.in_bom, } } else { // symbol is not a Symbol type, just use pin_defs @@ -1312,6 +1364,7 @@ where source_uri: None, raw_sexp: None, properties: SmallMap::new(), + in_bom: true, } } } else { @@ -1322,6 +1375,7 @@ where source_uri: None, raw_sexp: None, properties: SmallMap::new(), + in_bom: true, } } } else if let Some(symbol) = &symbol_val { @@ -1448,20 +1502,22 @@ where // If datasheet is not explicitly provided, try to get it from properties, then symbol properties // Skip empty strings and "~" (KiCad's placeholder for no datasheet) - prefer None over empty - let final_datasheet = datasheet_val - .and_then(|v| v.unpack_str().map(|s| s.to_owned())) + let explicit_datasheet = datasheet_val + .and_then(|v| v.unpack_str()) + .and_then(pcb_eda::usable_kicad_field_value) + .map(ToOwned::to_owned) .or_else(|| { properties_map .get("datasheet") - .and_then(|v| v.unpack_str().map(|s| s.to_owned())) - }) - .or_else(|| { - final_symbol - .properties() - .get("Datasheet") - .filter(|s| !s.is_empty() && s.as_str() != "~") - .map(|s| s.to_owned()) + .and_then(|v| v.unpack_str()) + .and_then(pcb_eda::usable_kicad_field_value) + .map(ToOwned::to_owned) }); + let final_datasheet = if let Some(datasheet) = explicit_datasheet { + Some(normalize_path_to_package_uri(&datasheet, Some(ctx))) + } else { + resolve_symbol_datasheet(&final_symbol, ctx)? + }; // If description is not explicitly provided, try to get it from properties, then symbol properties // Skip empty strings - prefer None over empty @@ -1506,12 +1562,13 @@ where ) }; - // Consolidate skip_bom: check kwarg, then legacy properties + // Consolidate skip_bom: check kwarg, then legacy properties, then symbol in_bom (inverted) let final_skip_bom = consolidate_bool_property( skip_bom_val, &properties_map, &["Exclude_from_bom", "exclude_from_bom"], - ); + ) + .unwrap_or(!final_symbol.in_bom); // Consolidate skip_pos: check kwarg, then legacy properties let final_skip_pos = consolidate_bool_property( @@ -1555,7 +1612,7 @@ where data: RefCell::new(ComponentData { part: final_part, dnp: final_dnp.unwrap_or(false), - skip_bom: final_skip_bom.unwrap_or(false), + skip_bom: final_skip_bom, skip_pos: final_skip_pos.unwrap_or(false), properties: properties_map, }), @@ -1626,6 +1683,7 @@ mod tests { source_uri: None, raw_sexp: None, properties, + in_bom: true, } } diff --git a/crates/pcb-zen-core/src/lang/symbol.rs b/crates/pcb-zen-core/src/lang/symbol.rs index d09be07b5..ec0390d85 100644 --- a/crates/pcb-zen-core/src/lang/symbol.rs +++ b/crates/pcb-zen-core/src/lang/symbol.rs @@ -55,6 +55,7 @@ pub struct SymbolValue { pub source_uri: Option, // Stable package URI for the symbol library when available pub raw_sexp: Option, // Raw s-expression of the symbol (if loaded from file, otherwise None) pub properties: SmallMap, // Properties from the symbol definition + pub in_bom: bool, // KiCad in_bom flag (inverse of skip_bom) } impl std::fmt::Debug for SymbolValue { @@ -226,6 +227,7 @@ impl<'v> SymbolValue { source_uri: None, raw_sexp: None, properties: SmallMap::new(), + in_bom: true, }) } // Case 2: Load from library @@ -327,6 +329,7 @@ impl<'v> SymbolValue { source_uri: Some(source_uri), raw_sexp: sexpr, properties, + in_bom: symbol.in_bom, }) } else { Err(starlark::Error::new_other(anyhow!( diff --git a/crates/pcb-zen/tests/spice_model.rs b/crates/pcb-zen/tests/spice_model.rs index 7781263fe..986fabc2d 100644 --- a/crates/pcb-zen/tests/spice_model.rs +++ b/crates/pcb-zen/tests/spice_model.rs @@ -68,7 +68,6 @@ R1 p n {RVAL} env.add_file( "myresistor.zen", r#" -load("@stdlib/generics/SolderJumper.zen", "pin_defs") load("@stdlib/config.zen", "config_properties") load("@stdlib/units.zen", "Resistance", "Voltage") load("@stdlib/utils.zen", "format_value") @@ -113,13 +112,9 @@ Component( spice_model = SpiceModel('./r.lib', 'my_resistor', nets=[P1, P2], args={"RVAL": str(value.value)}), - pin_defs = { - "P1": "1", - "P2": "2", - }, pins = { - "P1": P1, - "P2": P2, + "1": P1, + "2": P2, }, properties = properties, ) @@ -166,7 +161,6 @@ R1 p n {RVAL} env.add_file( "myresistor.zen", r#" -load("@stdlib/generics/SolderJumper.zen", "pin_defs") load("@stdlib/config.zen", "config_properties") load("@stdlib/units.zen", "Resistance", "Voltage") load("@stdlib/utils.zen", "format_value") @@ -195,13 +189,9 @@ Component( spice_model = SpiceModel('./r.lib', 'my_resistor', nets=[P1, P2], args={"RVAL": str(value.value)}), - pin_defs = { - "P1": "1", - "P2": "2", - }, pins = { - "P1": P1, - "P2": P2, + "1": P1, + "2": P2, }, properties = properties, ) @@ -247,7 +237,6 @@ R1 p n {RVAL} env.add_file( "myresistor.zen", r#" -load("@stdlib/generics/SolderJumper.zen", "pin_defs") load("@stdlib/config.zen", "config_properties") load("@stdlib/units.zen", "Resistance", "Voltage") load("@stdlib/utils.zen", "format_value") @@ -276,13 +265,9 @@ Component( spice_model = SpiceModel('./r.lib', 'my_resistor', nets=[P1, P2], args={"RVAL": str(value.value)}), - pin_defs = { - "P1": "1", - "P2": "2", - }, pins = { - "P1": P1, - "P2": P2, + "1": P1, + "2": P2, }, properties = properties, ) diff --git a/crates/pcb/src/import/generate.rs b/crates/pcb/src/import/generate.rs index 1d1e6753c..ec602bf30 100644 --- a/crates/pcb/src/import/generate.rs +++ b/crates/pcb/src/import/generate.rs @@ -12,6 +12,10 @@ use log::debug; use pcb_component_gen as component_gen; use pcb_sexpr::Sexpr; use pcb_sexpr::find_child_list; +use pcb_sexpr::formatter::{FormatMode, format_tree}; +use pcb_sexpr::kicad::symbol::{ + kicad_symbol_lib_items_mut, rewrite_symbol_properties, symbol_names, symbol_properties, +}; use pcb_sexpr::{PatchSet, Span, board as sexpr_board}; use pcb_zen_core::lang::stackup as zen_stackup; use std::collections::{BTreeMap, BTreeSet}; @@ -1636,17 +1640,27 @@ fn generate_imported_components( // Render all artifacts first; only touch the filesystem if we can produce a complete // component package. - let symbol = + let mut symbol = render_component_symbol(&part_dir.component_dir, component, schematic_lib_symbols) .with_context(|| format!("Failed to render symbol for {}", out_dir.display()))?; let footprint = render_component_footprint(component) .with_context(|| format!("Failed to render footprint for {}", out_dir.display()))?; + + // Patch the symbol's Footprint property to the local footprint stem so + // that `Component()` can infer it during build. + let fp_stem = footprint + .filename + .strip_suffix(".kicad_mod") + .unwrap_or(&footprint.filename); + symbol.library_text = patch_symbol_footprint_property(&symbol.library_text, fp_stem) + .with_context(|| { + format!("Failed to patch symbol Footprint for {}", out_dir.display()) + })?; + let zen = render_component_zen( &part_dir.component_dir, - component, &symbol.symbol, &symbol.filename, - Some(&footprint.filename), flags, ) .with_context(|| format!("Failed to render .zen for {}", out_dir.display()))?; @@ -1976,6 +1990,22 @@ fn render_component_symbol( }) } +fn patch_symbol_footprint_property(library_text: &str, footprint_stem: &str) -> Result { + let mut parsed = pcb_sexpr::parse(library_text).map_err(|e| anyhow::anyhow!(e))?; + let root = kicad_symbol_lib_items_mut(&mut parsed).context("Not a KiCad symbol library")?; + let names = symbol_names(root); + anyhow::ensure!(!names.is_empty(), "Symbol library contains no symbols"); + let idx = + pcb_sexpr::kicad::symbol::find_symbol_index(root, &names[0]).context("Symbol not found")?; + let symbol_items = root[idx] + .as_list_mut() + .context("Invalid symbol structure")?; + let mut props = symbol_properties(symbol_items); + props.insert("Footprint".to_string(), footprint_stem.to_string()); + rewrite_symbol_properties(symbol_items, &props); + Ok(format_tree(&parsed, FormatMode::Normal)) +} + #[derive(Debug, Clone)] struct RenderedComponentFootprint { filename: String, @@ -2014,65 +2044,6 @@ fn render_component_footprint( Ok(RenderedComponentFootprint { filename, mod_text }) } -fn build_pin_defs_for_symbol(symbol: &pcb_eda::Symbol) -> Option> { - // If a symbol contains duplicate pin signal names, our generated Zener module interface - // must disambiguate them; otherwise a single IO would span multiple pin numbers and - // net assignment would become ambiguous (KiCad connectivity keys by pin number). - - let mut base_by_pad: BTreeMap = BTreeMap::new(); - for pin in &symbol.pins { - let pad = KiCadPinNumber::from(pin.number.clone()); - let base = component_gen::sanitize_pin_name(pin.signal_name()); - - base_by_pad - .entry(pad) - .and_modify(|cur| { - if base < *cur { - *cur = base.clone(); - } - }) - .or_insert(base); - } - - let mut counts: BTreeMap = BTreeMap::new(); - for base in base_by_pad.values() { - *counts.entry(base.clone()).or_insert(0) += 1; - } - - if counts.values().all(|&n| n <= 1) { - return None; - } - - let mut used: BTreeSet = BTreeSet::new(); - let mut out: BTreeMap = BTreeMap::new(); - - for (pad, base) in base_by_pad { - let mut name = if counts.get(&base).copied().unwrap_or(1) <= 1 { - base - } else { - format!("{base}_{}", pad.as_str()) - }; - - // Defensive: avoid collisions from pathological pin names or stacked pads. - if !used.insert(name.clone()) { - let base_name = name.clone(); - let mut i = 2; - loop { - let candidate = format!("{base_name}_{i}"); - if used.insert(candidate.clone()) { - name = candidate; - break; - } - i += 1; - } - } - - out.insert(name, pad.as_str().to_string()); - } - - Some(out) -} - #[derive(Debug, Clone)] struct RenderedComponentZen { filename: String, @@ -2082,56 +2053,27 @@ struct RenderedComponentZen { fn render_component_zen( component_name: &str, - component: &ImportComponentData, symbol: &pcb_eda::Symbol, symbol_filename: &str, - footprint_filename: Option<&str>, flags: ImportPartFlags, ) -> Result { - let pin_defs = build_pin_defs_for_symbol(symbol); - let mut io_pins: BTreeMap> = BTreeMap::new(); - if let Some(pin_defs) = &pin_defs { - for (signal_name, pad) in pin_defs { - let pin_number = KiCadPinNumber::from(pad.clone()); - let io_name = component_gen::sanitize_pin_name(signal_name); - io_pins.entry(io_name).or_default().insert(pin_number); - } - } else { - for pin in &symbol.pins { - let pin_number = KiCadPinNumber::from(pin.number.clone()); - let io_name = component_gen::sanitize_pin_name(pin.signal_name()); - io_pins.entry(io_name).or_default().insert(pin_number); - } + for pin in &symbol.pins { + let pin_number = KiCadPinNumber::from(pin.number.clone()); + let io_name = component_gen::sanitize_pin_name(pin.signal_name()); + io_pins.entry(io_name).or_default().insert(pin_number); } - let props = component.best_properties(); - let mpn = props - .and_then(|p| { - find_property_ci(p, &["mpn"]) - .or_else(|| find_property_ci(p, &["manufacturer_part_number"])) - .or_else(|| find_property_ci(p, &["manufacturer part number"])) - }) - .map(|s| s.to_string()) - .unwrap_or_else(|| component_name.to_string()); - let manufacturer = props - .and_then(|p| find_property_ci(p, &["manufacturer", "mfr", "mfg"])) - .map(|s| s.to_string()); let zen_content = component_gen::generate_component_zen(component_gen::GenerateComponentZenArgs { - mpn: &mpn, component_name, symbol, symbol_filename, - footprint_filename, - datasheet_filename: None, - manufacturer: manufacturer.as_deref(), generated_by: "pcb import", include_skip_bom: flags.any_skip_bom, include_skip_pos: flags.any_skip_pos, skip_bom_default: flags.all_skip_bom, skip_pos_default: flags.all_skip_pos, - pin_defs: pin_defs.as_ref(), }) .context("Failed to generate component .zen")?; diff --git a/crates/pcb/tests/part.rs b/crates/pcb/tests/part.rs index 7987d8692..a7534e7e7 100644 --- a/crates/pcb/tests/part.rs +++ b/crates/pcb/tests/part.rs @@ -387,3 +387,157 @@ parts = [ serde_json::json!(["Preferred"]) ); } + +#[test] +fn component_inherits_local_symbol_datasheet() { + let output = Sandbox::new() + .write( + "components/TestPart/Part.kicad_sym", + r#"(kicad_symbol_lib + (version 20241209) + (symbol "TestPart" + (property "Reference" "U" (at 0 0 0) (effects (font (size 1.27 1.27)))) + (property "Value" "TestPart" (at 0 -2.54 0) (effects (font (size 1.27 1.27)))) + (property "Footprint" "Part" (at 0 0 0) (effects (font (size 1.27 1.27)) hide)) + (property "Datasheet" "docs/Part.pdf" (at 0 0 0) (effects (font (size 1.27 1.27)) hide)) + (symbol "TestPart_0_1" + (pin input line (at -5.08 0 0) (length 2.54) (name "P1" (effects (font (size 1.27 1.27)))) (number "1" (effects (font (size 1.27 1.27))))) + (pin input line (at 5.08 0 180) (length 2.54) (name "P2" (effects (font (size 1.27 1.27)))) (number "2" (effects (font (size 1.27 1.27))))) + ) + ) +)"#, + ) + .write( + "components/TestPart/Part.kicad_mod", + r#"(footprint "Part" + (layer "F.Cu") + (pad "1" smd rect (at -1 0) (size 1 1) (layers "F.Cu")) + (pad "2" smd rect (at 1 0) (size 1 1) (layers "F.Cu")) +)"#, + ) + .write("components/TestPart/docs/Part.pdf", "%PDF-1.4\n%") + .write( + "components/TestPart/Part.zen", + r#" +P1 = io("P1", Net) +P2 = io("P2", Net) + +Component( + name = "U", + symbol = Symbol(library = "Part.kicad_sym"), + pins = {"P1": P1, "P2": P2}, +) +"#, + ) + .write( + "board.zen", + r#" +Part = Module("components/TestPart/Part.zen") + +Part(name = "U1", P1 = Net("A"), P2 = Net("B")) +"#, + ) + .snapshot_run("pcb", ["build", "board.zen", "--netlist"]); + + let netlist = parse_netlist_json(&output); + let attrs = component_attrs(&netlist); + assert_eq!( + attrs["datasheet"]["String"].as_str(), + Some("package://workspace/components/TestPart/docs/Part.pdf") + ); +} + +#[test] +fn component_inherits_skip_bom_from_symbol_in_bom() { + let sym_not_in_bom = r#"(kicad_symbol_lib + (version 20241209) + (symbol "TestPart" (in_bom no) (on_board yes) + (property "Reference" "U" (at 0 0 0) (effects (font (size 1.27 1.27)))) + (property "Value" "TestPart" (at 0 -2.54 0) (effects (font (size 1.27 1.27)))) + (property "Footprint" "TestPart" (at 0 0 0) (effects (font (size 1.27 1.27)) hide)) + (symbol "TestPart_0_1" + (pin input line (at -5.08 0 0) (length 2.54) (name "P1" (effects (font (size 1.27 1.27)))) (number "1" (effects (font (size 1.27 1.27))))) + (pin input line (at 5.08 0 180) (length 2.54) (name "P2" (effects (font (size 1.27 1.27)))) (number "2" (effects (font (size 1.27 1.27))))) + ) + ) +)"#; + + // Symbol has in_bom=no → component should inherit skip_bom=true + let output = Sandbox::new() + .write("components/TestPart/TestPart.kicad_sym", sym_not_in_bom) + .write("components/TestPart/TestPart.kicad_mod", TEST_KICAD_MOD) + .write( + "components/TestPart/TestPart.zen", + r#" +P1 = io("P1", Net) +P2 = io("P2", Net) + +Component( + name = "U", + symbol = Symbol(library = "TestPart.kicad_sym"), + pins = {"P1": P1, "P2": P2}, +) +"#, + ) + .write( + "board.zen", + r#" +# ```pcb +# [workspace] +# pcb-version = "0.3" +# ``` + +TestPart = Module("components/TestPart/TestPart.zen") + +TestPart(name = "U1", P1 = Net("A"), P2 = Net("B")) +"#, + ) + .snapshot_run("pcb", ["build", "board.zen", "--netlist"]); + + let netlist = parse_netlist_json(&output); + let attrs = component_attrs(&netlist); + assert_eq!( + attrs["skip_bom"]["Boolean"].as_bool(), + Some(true), + "symbol with in_bom=no should set skip_bom=true" + ); + + // Symbol has in_bom=yes → component should have skip_bom=false (default) + let output2 = Sandbox::new() + .write("components/TestPart/TestPart.kicad_sym", MINIMAL_KICAD_SYM) + .write("components/TestPart/TestPart.kicad_mod", TEST_KICAD_MOD) + .write( + "components/TestPart/TestPart.zen", + r#" +P1 = io("P1", Net) +P2 = io("P2", Net) + +Component( + name = "U", + symbol = Symbol(library = "TestPart.kicad_sym"), + pins = {"P1": P1, "P2": P2}, +) +"#, + ) + .write( + "board.zen", + r#" +# ```pcb +# [workspace] +# pcb-version = "0.3" +# ``` + +TestPart = Module("components/TestPart/TestPart.zen") + +TestPart(name = "U1", P1 = Net("A"), P2 = Net("B")) +"#, + ) + .snapshot_run("pcb", ["build", "board.zen", "--netlist"]); + + let netlist2 = parse_netlist_json(&output2); + let attrs2 = component_attrs(&netlist2); + assert!( + attrs2.get("skip_bom").is_none() || attrs2["skip_bom"]["Boolean"].as_bool() == Some(false), + "symbol with in_bom=yes should not set skip_bom" + ); +} diff --git a/crates/pcb/tests/snapshots/release__publish_full.snap b/crates/pcb/tests/snapshots/release__publish_full.snap index 98d43fa06..381d7bd07 100644 --- a/crates/pcb/tests/snapshots/release__publish_full.snap +++ b/crates/pcb/tests/snapshots/release__publish_full.snap @@ -117,7 +117,7 @@ Designator,Val,Package,Mid X,Mid Y,Rotation,Layer "user": "" } } -=== netlist.json <73038 bytes, sha256: 9e3dc0b> +=== netlist.json <73006 bytes, sha256: 0f3d886> === src/boards/TestBoard.zen load("@stdlib/interfaces.zen", "Gpio") diff --git a/crates/pcb/tests/snapshots/release__publish_source_only.snap b/crates/pcb/tests/snapshots/release__publish_source_only.snap index 42dd8ce59..ebb6a6a21 100644 --- a/crates/pcb/tests/snapshots/release__publish_source_only.snap +++ b/crates/pcb/tests/snapshots/release__publish_source_only.snap @@ -43,7 +43,7 @@ expression: sb.snapshot_dir(&staging_dir) "user": "" } } -=== netlist.json <73038 bytes, sha256: 9e3dc0b> +=== netlist.json <73006 bytes, sha256: 0f3d886> === src/boards/TestBoard.zen load("@stdlib/interfaces.zen", "Gpio") diff --git a/crates/pcb/tests/snapshots/release__publish_with_description.snap b/crates/pcb/tests/snapshots/release__publish_with_description.snap index 66b2f78ea..3b61149c5 100644 --- a/crates/pcb/tests/snapshots/release__publish_with_description.snap +++ b/crates/pcb/tests/snapshots/release__publish_with_description.snap @@ -44,7 +44,7 @@ expression: sb.snapshot_dir(&staging_dir) "user": "" } } -=== netlist.json <73038 bytes, sha256: 095564f> +=== netlist.json <73006 bytes, sha256: 42cb844> === src/boards/DescBoard.zen load("@stdlib/interfaces.zen", "Gpio") diff --git a/crates/pcb/tests/snapshots/release__publish_with_version.snap b/crates/pcb/tests/snapshots/release__publish_with_version.snap index 8fb9e1924..b718ea401 100644 --- a/crates/pcb/tests/snapshots/release__publish_with_version.snap +++ b/crates/pcb/tests/snapshots/release__publish_with_version.snap @@ -43,7 +43,7 @@ expression: sb.snapshot_dir(staging_dir) "user": "" } } -=== netlist.json <72299 bytes, sha256: 215b338> +=== netlist.json <72267 bytes, sha256: 243c2ef> === src/boards/TB0001.zen load("@stdlib/interfaces.zen", "Gpio") diff --git a/docs/pages/spec.mdx b/docs/pages/spec.mdx index e787ee6c2..8ca2ae075 100644 --- a/docs/pages/spec.mdx +++ b/docs/pages/spec.mdx @@ -253,13 +253,12 @@ Component( | `prefix` | no | Reference designator prefix (default: `"U"`) | | `manufacturer` | no | Manufacturer name (legacy — prefer `part`) | | `mpn` | no | Manufacturer part number (legacy — prefer `part`) | -| `footprint` | no | PCB footprint path (usually omit — inferred from symbol) | +| `footprint` | no | PCB footprint path (default: inferred from symbol `Footprint` property) | | `type` | no | Component type string | | `properties` | no | Additional properties dict | | `dnp` | no | Do Not Populate flag | -| `skip_bom` | no | Exclude from BOM | - -When `footprint` is omitted, it is inferred from the symbol's metadata. When `part` is provided, its `mpn` and `manufacturer` override any scalar `mpn`/`manufacturer` parameters or symbol metadata. If no `part` is set and no explicit/scalar `mpn` is set, `Component()` can inherit a default part from the package manifest's `parts` entries matching the symbol path. `manufacturer` without `mpn` is treated as incomplete legacy scalar sourcing and does not prevent manifest fallback; prefer `part = Part(...)` as the simplest and most robust way to specify sourcing. +| `skip_bom` | no | Exclude from BOM (default: inverse of symbol `in_bom` flag) | +| `datasheet` | no | Datasheet path (default: symbol `Datasheet` property; local paths resolved relative to the `.kicad_sym` file) | ### Part diff --git a/stdlib/generics/Capacitor.zen b/stdlib/generics/Capacitor.zen index 37dfe12fd..d48520225 100644 --- a/stdlib/generics/Capacitor.zen +++ b/stdlib/generics/Capacitor.zen @@ -130,13 +130,9 @@ Component( footprint=File(_footprint(mount, package)), properties=properties, type="capacitor", - pin_defs={ - "P1": "1", - "P2": "2", - }, pins={ - "P1": P1, - "P2": P2, + "1": P1, + "2": P2, }, spice_model=SpiceModel( "spice/Capacitor.lib", diff --git a/stdlib/generics/FerriteBead.zen b/stdlib/generics/FerriteBead.zen index 65299afed..eab537493 100644 --- a/stdlib/generics/FerriteBead.zen +++ b/stdlib/generics/FerriteBead.zen @@ -106,13 +106,9 @@ Component( symbol=Symbol(**_symbol(package)), footprint=File(_footprint(package)), prefix="FB", - pin_defs={ - "P1": "1", - "P2": "2", - }, pins={ - "P1": P1, - "P2": P2, + "1": P1, + "2": P2, }, spice_model=SpiceModel( "spice/FerriteBead.lib", diff --git a/stdlib/generics/Resistor.zen b/stdlib/generics/Resistor.zen index d5cc27116..f217fce21 100644 --- a/stdlib/generics/Resistor.zen +++ b/stdlib/generics/Resistor.zen @@ -111,13 +111,9 @@ Component( symbol=Symbol(**_symbol(mount, package, use_us_symbol)), footprint=File(_footprint(mount, package)), prefix="R", - pin_defs={ - "P1": "1", - "P2": "2", - }, pins={ - "P1": P1, - "P2": P2, + "1": P1, + "2": P2, }, spice_model=SpiceModel( "spice/Resistor.lib", diff --git a/stdlib/generics/SolderJumper.zen b/stdlib/generics/SolderJumper.zen index 65caa074e..0f3c01aae 100644 --- a/stdlib/generics/SolderJumper.zen +++ b/stdlib/generics/SolderJumper.zen @@ -85,20 +85,16 @@ def _symbol(pin_count): def _pins_and_connections(pin_count): - # pin_defs: mapping of pin name to pin number as string - # pins: mapping of pin name to net (if connected) - pin_defs = {} pins = {} nets = P for i in range(pin_count): pin_name = str(i + 1) - pin_defs[pin_name] = str(i + 1) net = nets[i] if net != None: pins[pin_name] = net - return pins, pin_defs + return pins -pins, pin_defs = _pins_and_connections(pin_count) +pins = _pins_and_connections(pin_count) Component( name="SJ", diff --git a/stdlib/generics/Thermistor.zen b/stdlib/generics/Thermistor.zen index 6950c9aaa..0f39c630e 100644 --- a/stdlib/generics/Thermistor.zen +++ b/stdlib/generics/Thermistor.zen @@ -1,5 +1,5 @@ load("../interfaces.zen", "Net") -load("./SolderJumper.zen", "pin_defs") + load("../units.zen", "Capacitance", "Inductance", "Resistance", "Voltage") load("../utils.zen", "format_value") @@ -95,13 +95,9 @@ Component( symbol=Symbol(**_symbol(mount, package, temperature_coefficient, use_us_symbol)), footprint=File(_footprint(mount, package)), prefix="TH", - pin_defs={ - "P1": "1", - "P2": "2", - }, pins={ - "P1": P1, - "P2": P2, + "1": P1, + "2": P2, }, spice_model=SpiceModel( "spice/Thermistor.lib",