Skip to main content

altium_format/records/sch/
implementation.rs

1//! Implementation-related schematic records.
2//!
3//! These records handle component model implementations (footprints, simulation models, etc).
4
5use crate::error::Result;
6use crate::traits::{FromParams, ToParams};
7use crate::types::{CoordRect, ParameterCollection, UnknownFields};
8use altium_format_derive::AltiumRecord;
9
10use super::{SchPrimitive, SchPrimitiveBase};
11
12/// SchImplementationList (Record 44) - Container for implementation records.
13///
14/// This is essentially a container/parent record for SchImplementation children.
15#[derive(Debug, Clone, Default, AltiumRecord)]
16#[altium(record_id = 44, format = "params")]
17pub struct SchImplementationList {
18    /// Base primitive fields.
19    #[altium(flatten)]
20    pub base: SchPrimitiveBase,
21
22    /// Unknown parameters (preserved for non-destructive editing).
23    #[altium(unknown)]
24    pub unknown_params: UnknownFields,
25}
26
27impl SchPrimitive for SchImplementationList {
28    const RECORD_ID: i32 = 44;
29
30    fn record_type_name(&self) -> &'static str {
31        "ImplementationList"
32    }
33
34    fn import_from_params(params: &ParameterCollection) -> Result<Self> {
35        Self::from_params(params)
36    }
37
38    fn export_to_params(&self) -> ParameterCollection {
39        self.to_params()
40    }
41
42    fn owner_index(&self) -> i32 {
43        self.base.owner_index
44    }
45
46    fn calculate_bounds(&self) -> CoordRect {
47        CoordRect::default()
48    }
49}
50
51/// Known parameter keys for SchImplementation (for unknown field filtering).
52const IMPLEMENTATION_KNOWN_KEYS: &[&str] = &[
53    "RECORD",
54    "OWNERINDEX",
55    "ISNOTACCESIBLE",
56    "OWNERPARTID",
57    "OWNERPARTDISPLAYMODE",
58    "GRAPHICALLYLOCKED",
59    "DESCRIPTION",
60    "MODELNAME",
61    "MODELTYPE",
62    "DATAFILECOUNT",
63    "ISCURRENT",
64];
65
66/// SchImplementation (Record 45) - Component model implementation.
67///
68/// Represents a model attached to a component (footprint, simulation model, etc).
69#[derive(Debug, Clone, Default)]
70pub struct SchImplementation {
71    /// Base primitive fields.
72    pub base: SchPrimitiveBase,
73    /// Description of the implementation.
74    pub description: String,
75    /// Model name (e.g., footprint name).
76    pub model_name: String,
77    /// Model type (e.g., "PCBLIB", "SIM", "SI", "PCB3DLib").
78    pub model_type: String,
79    /// Data file references.
80    pub data_files: Vec<String>,
81    /// Whether this is the current implementation.
82    pub is_current: bool,
83    /// Unknown parameters (preserved for non-destructive editing).
84    pub unknown_params: UnknownFields,
85}
86
87impl SchPrimitive for SchImplementation {
88    const RECORD_ID: i32 = 45;
89
90    fn record_type_name(&self) -> &'static str {
91        "Implementation"
92    }
93
94    fn get_property(&self, name: &str) -> Option<String> {
95        match name {
96            "MODELNAME" => Some(self.model_name.clone()),
97            "MODELTYPE" => Some(self.model_type.clone()),
98            _ => None,
99        }
100    }
101
102    fn import_from_params(params: &ParameterCollection) -> Result<Self> {
103        let base = SchPrimitiveBase::import_from_params(params);
104
105        let data_file_count = params
106            .get("DATAFILECOUNT")
107            .map(|v| v.as_int_or(0))
108            .unwrap_or(0);
109
110        let mut data_files = Vec::new();
111        for i in 1..=data_file_count {
112            if let Some(file) = params.get(&format!("MODELDATAFILEKIND{}", i)) {
113                data_files.push(file.as_str().to_string());
114            }
115        }
116
117        // Build prefixes for unknown field filtering
118        let indexed_prefixes: Vec<String> = (1..=data_file_count)
119            .map(|i| format!("MODELDATAFILEKIND{}", i))
120            .collect();
121        let prefix_refs: Vec<&str> = indexed_prefixes.iter().map(|s| s.as_str()).collect();
122
123        // Combine known keys with indexed prefixes
124        let mut all_known: Vec<&str> = IMPLEMENTATION_KNOWN_KEYS.to_vec();
125        all_known.extend(prefix_refs.iter());
126
127        Ok(SchImplementation {
128            base,
129            description: params
130                .get("DESCRIPTION")
131                .map(|v| v.as_str().to_string())
132                .unwrap_or_default(),
133            model_name: params
134                .get("MODELNAME")
135                .map(|v| v.as_str().to_string())
136                .unwrap_or_default(),
137            model_type: params
138                .get("MODELTYPE")
139                .map(|v| v.as_str().to_string())
140                .unwrap_or_default(),
141            data_files,
142            is_current: params
143                .get("ISCURRENT")
144                .map(|v| v.as_bool_or(false))
145                .unwrap_or(false),
146            unknown_params: UnknownFields::from_remaining_params(params, &all_known),
147        })
148    }
149
150    fn export_to_params(&self) -> ParameterCollection {
151        let mut params = ParameterCollection::new();
152        params.add_int("RECORD", Self::RECORD_ID);
153        self.base.export_to_params(&mut params);
154        params.add("DESCRIPTION", &self.description);
155        params.add("MODELNAME", &self.model_name);
156        params.add("MODELTYPE", &self.model_type);
157        params.add_int("DATAFILECOUNT", self.data_files.len() as i32);
158        for (i, file) in self.data_files.iter().enumerate() {
159            params.add(&format!("MODELDATAFILEKIND{}", i + 1), file);
160        }
161        params.add_bool("ISCURRENT", self.is_current);
162
163        // Merge unknown parameters back
164        self.unknown_params.merge_into_params(&mut params);
165
166        params
167    }
168
169    fn owner_index(&self) -> i32 {
170        self.base.owner_index
171    }
172
173    fn calculate_bounds(&self) -> CoordRect {
174        CoordRect::default()
175    }
176}
177
178/// SchMapDefinerList (Record 46) - Container for pin map definitions.
179#[derive(Debug, Clone, Default, AltiumRecord)]
180#[altium(record_id = 46, format = "params")]
181pub struct SchMapDefinerList {
182    /// Base primitive fields.
183    #[altium(flatten)]
184    pub base: SchPrimitiveBase,
185
186    /// Unknown parameters (preserved for non-destructive editing).
187    #[altium(unknown)]
188    pub unknown_params: UnknownFields,
189}
190
191impl SchPrimitive for SchMapDefinerList {
192    const RECORD_ID: i32 = 46;
193
194    fn record_type_name(&self) -> &'static str {
195        "MapDefinerList"
196    }
197
198    fn import_from_params(params: &ParameterCollection) -> Result<Self> {
199        Self::from_params(params)
200    }
201
202    fn export_to_params(&self) -> ParameterCollection {
203        self.to_params()
204    }
205
206    fn owner_index(&self) -> i32 {
207        self.base.owner_index
208    }
209
210    fn calculate_bounds(&self) -> CoordRect {
211        CoordRect::default()
212    }
213}
214
215/// Known parameter keys for SchMapDefiner (for unknown field filtering).
216const MAP_DEFINER_KNOWN_KEYS: &[&str] = &[
217    "RECORD",
218    "OWNERINDEX",
219    "ISNOTACCESIBLE",
220    "OWNERPARTID",
221    "OWNERPARTDISPLAYMODE",
222    "GRAPHICALLYLOCKED",
223    "DESINTF",
224    "DESIMPCOUNT",
225    "ISTRIVIAL",
226];
227
228/// SchMapDefiner (Record 47) - Pin map definition.
229///
230/// Maps schematic pin designators to implementation (footprint) pin designators.
231#[derive(Debug, Clone, Default)]
232pub struct SchMapDefiner {
233    /// Base primitive fields.
234    pub base: SchPrimitiveBase,
235    /// Interface (schematic) designator.
236    pub designator_interface: String,
237    /// Implementation (footprint) designators.
238    pub designator_implementation: Vec<String>,
239    /// Whether this is a trivial (identity) mapping.
240    pub is_trivial: bool,
241    /// Unknown parameters (preserved for non-destructive editing).
242    pub unknown_params: UnknownFields,
243}
244
245impl SchPrimitive for SchMapDefiner {
246    const RECORD_ID: i32 = 47;
247
248    fn record_type_name(&self) -> &'static str {
249        "MapDefiner"
250    }
251
252    fn import_from_params(params: &ParameterCollection) -> Result<Self> {
253        let base = SchPrimitiveBase::import_from_params(params);
254
255        let impl_count = params
256            .get("DESIMPCOUNT")
257            .map(|v| v.as_int_or(0))
258            .unwrap_or(0);
259
260        let mut implementations = Vec::new();
261        for i in 0..impl_count {
262            if let Some(des) = params.get(&format!("DESIMP{}", i)) {
263                implementations.push(des.as_str().to_string());
264            }
265        }
266
267        // Build prefixes for unknown field filtering (0-indexed)
268        let indexed_prefixes: Vec<String> =
269            (0..impl_count).map(|i| format!("DESIMP{}", i)).collect();
270        let prefix_refs: Vec<&str> = indexed_prefixes.iter().map(|s| s.as_str()).collect();
271
272        // Combine known keys with indexed prefixes
273        let mut all_known: Vec<&str> = MAP_DEFINER_KNOWN_KEYS.to_vec();
274        all_known.extend(prefix_refs.iter());
275
276        Ok(SchMapDefiner {
277            base,
278            designator_interface: params
279                .get("DESINTF")
280                .map(|v| v.as_str().to_string())
281                .unwrap_or_default(),
282            designator_implementation: implementations,
283            is_trivial: params
284                .get("ISTRIVIAL")
285                .map(|v| v.as_bool_or(false))
286                .unwrap_or(false),
287            unknown_params: UnknownFields::from_remaining_params(params, &all_known),
288        })
289    }
290
291    fn export_to_params(&self) -> ParameterCollection {
292        let mut params = ParameterCollection::new();
293        params.add_int("RECORD", Self::RECORD_ID);
294        self.base.export_to_params(&mut params);
295        params.add("DESINTF", &self.designator_interface);
296        params.add_int("DESIMPCOUNT", self.designator_implementation.len() as i32);
297        for (i, des) in self.designator_implementation.iter().enumerate() {
298            params.add(&format!("DESIMP{}", i), des);
299        }
300        params.add_bool("ISTRIVIAL", self.is_trivial);
301
302        // Merge unknown parameters back
303        self.unknown_params.merge_into_params(&mut params);
304
305        params
306    }
307
308    fn owner_index(&self) -> i32 {
309        self.base.owner_index
310    }
311
312    fn calculate_bounds(&self) -> CoordRect {
313        CoordRect::default()
314    }
315}
316
317/// SchImplementationParameters (Record 48) - Additional implementation parameters.
318#[derive(Debug, Clone, Default, AltiumRecord)]
319#[altium(record_id = 48, format = "params")]
320pub struct SchImplementationParameters {
321    /// Base primitive fields.
322    #[altium(flatten)]
323    pub base: SchPrimitiveBase,
324
325    /// Unknown parameters (preserved for non-destructive editing).
326    #[altium(unknown)]
327    pub unknown_params: UnknownFields,
328}
329
330impl SchPrimitive for SchImplementationParameters {
331    const RECORD_ID: i32 = 48;
332
333    fn record_type_name(&self) -> &'static str {
334        "ImplementationParameters"
335    }
336
337    fn import_from_params(params: &ParameterCollection) -> Result<Self> {
338        Self::from_params(params)
339    }
340
341    fn export_to_params(&self) -> ParameterCollection {
342        self.to_params()
343    }
344
345    fn owner_index(&self) -> i32 {
346        self.base.owner_index
347    }
348
349    fn calculate_bounds(&self) -> CoordRect {
350        CoordRect::default()
351    }
352}