Skip to main content

altium_format/records/sch/
primitive.rs

1//! Base primitive trait and SchRecord dispatch enum.
2
3use crate::error::Result;
4use crate::traits::{FromParams, ToParams};
5use crate::tree::TreeRecord;
6use crate::types::{
7    CoordPoint, CoordRect, ParameterCollection, coord_to_dxp_frac, dxp_frac_to_coord,
8};
9
10/// Base trait for all schematic primitives.
11pub trait SchPrimitive: Sized {
12    /// Record type ID for this primitive.
13    const RECORD_ID: i32;
14
15    /// Import primitive data from parameters.
16    fn import_from_params(params: &ParameterCollection) -> Result<Self>;
17
18    /// Export primitive data to parameters.
19    fn export_to_params(&self) -> ParameterCollection;
20
21    /// Get the owner index (index of parent primitive in the list).
22    fn owner_index(&self) -> i32;
23
24    /// Get the location (if applicable).
25    fn location(&self) -> Option<CoordPoint> {
26        None
27    }
28
29    /// Get the record type name for diagnostics.
30    fn record_type_name(&self) -> &'static str;
31
32    /// Get a property value by name (for generic queries).
33    fn get_property(&self, _name: &str) -> Option<String> {
34        None
35    }
36
37    /// Calculate the bounding rectangle.
38    fn calculate_bounds(&self) -> CoordRect;
39}
40
41/// Common fields shared by all schematic primitives.
42#[derive(Debug, Clone)]
43pub struct SchPrimitiveBase {
44    /// Index of owner primitive in the component's primitive list.
45    pub owner_index: i32,
46    /// Whether the primitive is not accessible.
47    pub is_not_accessible: bool,
48    /// Owner part ID (for multi-part symbols).
49    pub owner_part_id: Option<i32>,
50    /// Owner display mode (for symbols with multiple display modes).
51    pub owner_part_display_mode: Option<i32>,
52    /// Whether the primitive is graphically locked.
53    pub graphically_locked: bool,
54}
55
56impl Default for SchPrimitiveBase {
57    fn default() -> Self {
58        Self {
59            owner_index: -1, // -1 indicates no owner
60            is_not_accessible: false,
61            owner_part_id: None, // None serializes as 1 for pins; overridden to Some(-1) for components
62            owner_part_display_mode: None,
63            graphically_locked: false,
64        }
65    }
66}
67
68impl SchPrimitiveBase {
69    /// Import base fields from parameters.
70    pub fn import_from_params(params: &ParameterCollection) -> Self {
71        SchPrimitiveBase {
72            owner_index: params
73                .get("OWNERINDEX")
74                .map(|v| v.as_int_or(0))
75                .unwrap_or(0),
76            is_not_accessible: params
77                .get("ISNOTACCESIBLE")
78                .map(|v| v.as_bool_or(false))
79                .unwrap_or(false),
80            owner_part_id: params.get("OWNERPARTID").map(|v| v.as_int_or(0)),
81            owner_part_display_mode: params.get("OWNERPARTDISPLAYMODE").map(|v| v.as_int_or(0)),
82            graphically_locked: params
83                .get("GRAPHICALLYLOCKED")
84                .map(|v| v.as_bool_or(false))
85                .unwrap_or(false),
86        }
87    }
88
89    /// Export base fields to parameters.
90    pub fn export_to_params(&self, params: &mut ParameterCollection) {
91        params.add_int("OWNERINDEX", self.owner_index);
92        params.add_bool("ISNOTACCESIBLE", self.is_not_accessible);
93        if let Some(part_id) = self.owner_part_id {
94            params.add_int("OWNERPARTID", part_id);
95        }
96        if let Some(display_mode) = self.owner_part_display_mode {
97            params.add_int("OWNERPARTDISPLAYMODE", display_mode);
98        }
99        params.add_bool("GRAPHICALLYLOCKED", self.graphically_locked);
100    }
101
102    /// Get the owner index.
103    pub fn owner_index(&self) -> i32 {
104        self.owner_index
105    }
106
107    /// Set the owner index.
108    pub fn set_owner_index(&mut self, index: i32) {
109        self.owner_index = index;
110    }
111}
112
113// Trait implementations for derive macro support
114impl FromParams for SchPrimitiveBase {
115    fn from_params(params: &ParameterCollection) -> Result<Self> {
116        Ok(Self::import_from_params(params))
117    }
118}
119
120impl ToParams for SchPrimitiveBase {
121    fn append_to_params(&self, params: &mut ParameterCollection) {
122        self.export_to_params(params);
123    }
124}
125
126/// Common fields for graphical schematic objects (extends SchPrimitiveBase).
127#[derive(Debug, Clone, Default)]
128pub struct SchGraphicalBase {
129    /// Base primitive fields.
130    pub base: SchPrimitiveBase,
131    /// Location of the object.
132    pub location_x: i32,
133    pub location_y: i32,
134    /// Color (Win32 COLORREF).
135    pub color: i32,
136    /// Area/fill color (Win32 COLORREF).
137    pub area_color: i32,
138}
139
140impl SchGraphicalBase {
141    /// Standard colors from Altium schematics (Win32 COLORREF format: 0xBBGGRR).
142    pub const COLOR_BLUE: i32 = 128; // 0x000080 - Dark blue for components, junctions, ports
143    pub const COLOR_RED: i32 = 8388608; // 0x800000 - Dark red for wires, text, labels
144    pub const COLOR_LIGHT_CYAN: i32 = 11599871; // 0xB0FFFF - Light cyan for component fill
145
146    /// Create a new SchGraphicalBase with default colors for graphical objects (blue).
147    ///
148    /// Use this for components, junctions, ports, and other graphical primitives.
149    pub fn new_graphical() -> Self {
150        Self {
151            base: SchPrimitiveBase {
152                owner_index: -1,
153                is_not_accessible: false,
154                owner_part_id: Some(-1),
155                owner_part_display_mode: None,
156                graphically_locked: false,
157            },
158            location_x: 0,
159            location_y: 0,
160            color: Self::COLOR_BLUE,
161            area_color: Self::COLOR_LIGHT_CYAN,
162        }
163    }
164
165    /// Create a new SchGraphicalBase with default colors for wires and text (red).
166    ///
167    /// Use this for wires, labels, and text objects.
168    pub fn new_wire_or_text() -> Self {
169        Self {
170            base: SchPrimitiveBase {
171                owner_index: -1,
172                is_not_accessible: false,
173                owner_part_id: Some(-1),
174                owner_part_display_mode: None,
175                graphically_locked: false,
176            },
177            location_x: 0,
178            location_y: 0,
179            color: Self::COLOR_RED,
180            area_color: 0,
181        }
182    }
183
184    /// Import graphical fields from parameters.
185    pub fn import_from_params(params: &ParameterCollection) -> Self {
186        let base = SchPrimitiveBase::import_from_params(params);
187
188        let loc_x = params
189            .get("LOCATION.X")
190            .map(|v| v.as_int_or(0))
191            .unwrap_or(0);
192        let loc_x_frac = params
193            .get("LOCATION.X_FRAC")
194            .map(|v| v.as_int_or(0))
195            .unwrap_or(0);
196        let loc_y = params
197            .get("LOCATION.Y")
198            .map(|v| v.as_int_or(0))
199            .unwrap_or(0);
200        let loc_y_frac = params
201            .get("LOCATION.Y_FRAC")
202            .map(|v| v.as_int_or(0))
203            .unwrap_or(0);
204
205        SchGraphicalBase {
206            base,
207            location_x: dxp_frac_to_coord(loc_x, loc_x_frac),
208            location_y: dxp_frac_to_coord(loc_y, loc_y_frac),
209            color: params.get("COLOR").map(|v| v.as_int_or(0)).unwrap_or(0),
210            area_color: params.get("AREACOLOR").map(|v| v.as_int_or(0)).unwrap_or(0),
211        }
212    }
213
214    /// Export graphical fields to parameters.
215    pub fn export_to_params(&self, params: &mut ParameterCollection) {
216        self.base.export_to_params(params);
217
218        let (x, x_frac) = coord_to_dxp_frac(self.location_x);
219        let (y, y_frac) = coord_to_dxp_frac(self.location_y);
220        params.add_int("LOCATION.X", x);
221        params.add_int("LOCATION.X_FRAC", x_frac);
222        params.add_int("LOCATION.Y", y);
223        params.add_int("LOCATION.Y_FRAC", y_frac);
224        params.add_int("COLOR", self.color);
225        params.add_int("AREACOLOR", self.area_color);
226    }
227
228    /// Get the owner index (delegates to base).
229    pub fn owner_index(&self) -> i32 {
230        self.base.owner_index
231    }
232
233    /// Set the owner index (delegates to base).
234    pub fn set_owner_index(&mut self, index: i32) {
235        self.base.owner_index = index;
236    }
237}
238
239// Trait implementations for derive macro support
240impl FromParams for SchGraphicalBase {
241    fn from_params(params: &ParameterCollection) -> Result<Self> {
242        Ok(Self::import_from_params(params))
243    }
244}
245
246impl ToParams for SchGraphicalBase {
247    fn append_to_params(&self, params: &mut ParameterCollection) {
248        self.export_to_params(params);
249    }
250}
251
252/// Dispatch enum containing all schematic record types.
253#[derive(Debug, Clone)]
254pub enum SchRecord {
255    Component(super::SchComponent),
256    Pin(super::SchPin),
257    Symbol(super::SchSymbol),
258    Label(super::SchLabel),
259    Bezier(super::SchBezier),
260    Polyline(super::SchPolyline),
261    Polygon(super::SchPolygon),
262    Ellipse(super::SchEllipse),
263    Pie(super::SchPie),
264    EllipticalArc(super::SchEllipticalArc),
265    Arc(super::SchArc),
266    Line(super::SchLine),
267    Rectangle(super::SchRectangle),
268    PowerObject(super::SchPowerObject),
269    Port(super::SchPort),
270    NoErc(super::SchNoErc),
271    NetLabel(super::SchNetLabel),
272    Bus(super::SchBus),
273    Wire(super::SchWire),
274    TextFrame(super::SchTextFrame),
275    TextFrameVariant(super::SchTextFrameVariant),
276    Junction(super::SchJunction),
277    Image(super::SchImage),
278    SheetHeader(super::SchSheetHeader),
279    Designator(super::SchDesignator),
280    BusEntry(super::SchBusEntry),
281    Parameter(super::SchParameter),
282    WarningSign(super::SchWarningSign),
283    ImplementationList(super::SchImplementationList),
284    Implementation(super::SchImplementation),
285    MapDefinerList(super::SchMapDefinerList),
286    MapDefiner(super::SchMapDefiner),
287    ImplementationParameters(super::SchImplementationParameters),
288    /// Unknown record type - stores raw parameters.
289    Unknown {
290        record_id: i32,
291        params: ParameterCollection,
292    },
293}
294
295impl SchRecord {
296    /// Create a record from parameters by dispatching on RECORD type.
297    pub fn from_params(params: &ParameterCollection) -> Result<Self> {
298        let record_id = params.get("RECORD").map(|v| v.as_int_or(-1)).unwrap_or(-1);
299
300        let record = match record_id {
301            1 => SchRecord::Component(super::SchComponent::import_from_params(params)?),
302            2 => SchRecord::Pin(super::SchPin::import_from_params(params)?),
303            3 => SchRecord::Symbol(super::SchSymbol::import_from_params(params)?),
304            4 => SchRecord::Label(super::SchLabel::import_from_params(params)?),
305            5 => SchRecord::Bezier(super::SchBezier::import_from_params(params)?),
306            6 => SchRecord::Polyline(super::SchPolyline::import_from_params(params)?),
307            7 => SchRecord::Polygon(super::SchPolygon::import_from_params(params)?),
308            8 => SchRecord::Ellipse(super::SchEllipse::import_from_params(params)?),
309            9 => SchRecord::Pie(super::SchPie::import_from_params(params)?),
310            11 => SchRecord::EllipticalArc(super::SchEllipticalArc::import_from_params(params)?),
311            12 => SchRecord::Arc(super::SchArc::import_from_params(params)?),
312            13 => SchRecord::Line(super::SchLine::import_from_params(params)?),
313            14 => SchRecord::Rectangle(super::SchRectangle::import_from_params(params)?),
314            17 => SchRecord::PowerObject(super::SchPowerObject::import_from_params(params)?),
315            18 => SchRecord::Port(super::SchPort::import_from_params(params)?),
316            22 => SchRecord::NoErc(super::SchNoErc::import_from_params(params)?),
317            25 => SchRecord::NetLabel(super::SchNetLabel::import_from_params(params)?),
318            26 => SchRecord::Bus(super::SchBus::import_from_params(params)?),
319            27 => SchRecord::Wire(super::SchWire::import_from_params(params)?),
320            28 => SchRecord::TextFrame(super::SchTextFrame::import_from_params(params)?),
321            29 => SchRecord::Junction(super::SchJunction::import_from_params(params)?),
322            30 => SchRecord::Image(super::SchImage::import_from_params(params)?),
323            31 => SchRecord::SheetHeader(super::SchSheetHeader::import_from_params(params)?),
324            34 => SchRecord::Designator(super::SchDesignator::import_from_params(params)?),
325            37 => SchRecord::BusEntry(super::SchBusEntry::import_from_params(params)?),
326            41 => SchRecord::Parameter(super::SchParameter::import_from_params(params)?),
327            43 => SchRecord::WarningSign(super::SchWarningSign::import_from_params(params)?),
328            44 => SchRecord::ImplementationList(super::SchImplementationList::import_from_params(
329                params,
330            )?),
331            45 => SchRecord::Implementation(super::SchImplementation::import_from_params(params)?),
332            46 => SchRecord::MapDefinerList(super::SchMapDefinerList::import_from_params(params)?),
333            47 => SchRecord::MapDefiner(super::SchMapDefiner::import_from_params(params)?),
334            48 => SchRecord::ImplementationParameters(
335                super::SchImplementationParameters::import_from_params(params)?,
336            ),
337            209 => {
338                SchRecord::TextFrameVariant(super::SchTextFrameVariant::import_from_params(params)?)
339            }
340            _ => SchRecord::Unknown {
341                record_id,
342                params: params.clone(),
343            },
344        };
345
346        Ok(record)
347    }
348
349    /// Get the record type ID.
350    pub fn record_id(&self) -> i32 {
351        match self {
352            SchRecord::Component(_) => 1,
353            SchRecord::Pin(_) => 2,
354            SchRecord::Symbol(_) => 3,
355            SchRecord::Label(_) => 4,
356            SchRecord::Bezier(_) => 5,
357            SchRecord::Polyline(_) => 6,
358            SchRecord::Polygon(_) => 7,
359            SchRecord::Ellipse(_) => 8,
360            SchRecord::Pie(_) => 9,
361            SchRecord::EllipticalArc(_) => 11,
362            SchRecord::Arc(_) => 12,
363            SchRecord::Line(_) => 13,
364            SchRecord::Rectangle(_) => 14,
365            SchRecord::PowerObject(_) => 17,
366            SchRecord::Port(_) => 18,
367            SchRecord::NoErc(_) => 22,
368            SchRecord::NetLabel(_) => 25,
369            SchRecord::Bus(_) => 26,
370            SchRecord::Wire(_) => 27,
371            SchRecord::TextFrame(_) => 28,
372            SchRecord::Junction(_) => 29,
373            SchRecord::Image(_) => 30,
374            SchRecord::SheetHeader(_) => 31,
375            SchRecord::Designator(_) => 34,
376            SchRecord::BusEntry(_) => 37,
377            SchRecord::Parameter(_) => 41,
378            SchRecord::WarningSign(_) => 43,
379            SchRecord::ImplementationList(_) => 44,
380            SchRecord::Implementation(_) => 45,
381            SchRecord::MapDefinerList(_) => 46,
382            SchRecord::MapDefiner(_) => 47,
383            SchRecord::ImplementationParameters(_) => 48,
384            SchRecord::TextFrameVariant(_) => 209,
385            SchRecord::Unknown { record_id, .. } => *record_id,
386        }
387    }
388
389    /// Get the owner index of this record.
390    pub fn owner_index(&self) -> i32 {
391        match self {
392            SchRecord::Component(r) => r.owner_index(),
393            SchRecord::Pin(r) => r.owner_index(),
394            SchRecord::Symbol(r) => r.owner_index(),
395            SchRecord::Label(r) => r.owner_index(),
396            SchRecord::Bezier(r) => r.owner_index(),
397            SchRecord::Polyline(r) => r.owner_index(),
398            SchRecord::Polygon(r) => r.owner_index(),
399            SchRecord::Ellipse(r) => r.owner_index(),
400            SchRecord::Pie(r) => r.owner_index(),
401            SchRecord::EllipticalArc(r) => r.owner_index(),
402            SchRecord::Arc(r) => r.owner_index(),
403            SchRecord::Line(r) => r.owner_index(),
404            SchRecord::Rectangle(r) => r.owner_index(),
405            SchRecord::PowerObject(r) => r.owner_index(),
406            SchRecord::Port(r) => r.owner_index(),
407            SchRecord::NoErc(r) => r.owner_index(),
408            SchRecord::NetLabel(r) => r.owner_index(),
409            SchRecord::Bus(r) => r.owner_index(),
410            SchRecord::Wire(r) => r.owner_index(),
411            SchRecord::TextFrame(r) => r.owner_index(),
412            SchRecord::TextFrameVariant(r) => r.owner_index(),
413            SchRecord::Junction(r) => r.owner_index(),
414            SchRecord::Image(r) => r.owner_index(),
415            SchRecord::SheetHeader(r) => r.owner_index(),
416            SchRecord::Designator(r) => r.owner_index(),
417            SchRecord::BusEntry(r) => r.owner_index(),
418            SchRecord::Parameter(r) => r.owner_index(),
419            SchRecord::WarningSign(r) => r.owner_index(),
420            SchRecord::ImplementationList(r) => r.owner_index(),
421            SchRecord::Implementation(r) => r.owner_index(),
422            SchRecord::MapDefinerList(r) => r.owner_index(),
423            SchRecord::MapDefiner(r) => r.owner_index(),
424            SchRecord::ImplementationParameters(r) => r.owner_index(),
425            SchRecord::Unknown { params, .. } => params
426                .get("OWNERINDEX")
427                .map(|v| v.as_int_or(0))
428                .unwrap_or(0),
429        }
430    }
431
432    /// Set the owner index for this record.
433    pub fn set_owner_index(&mut self, index: i32) {
434        match self {
435            SchRecord::Component(r) => r.graphical.base.set_owner_index(index),
436            SchRecord::Pin(r) => r.graphical.set_owner_index(index),
437            SchRecord::Symbol(r) => r.graphical.set_owner_index(index),
438            SchRecord::Label(r) => r.graphical.set_owner_index(index),
439            SchRecord::Bezier(r) => r.graphical.set_owner_index(index),
440            SchRecord::Polyline(r) => r.graphical.set_owner_index(index),
441            SchRecord::Polygon(r) => r.graphical.set_owner_index(index),
442            SchRecord::Ellipse(r) => r.graphical.set_owner_index(index),
443            SchRecord::Pie(r) => r.graphical.set_owner_index(index),
444            SchRecord::EllipticalArc(r) => r.graphical.set_owner_index(index),
445            SchRecord::Arc(r) => r.graphical.set_owner_index(index),
446            SchRecord::Line(r) => r.graphical.set_owner_index(index),
447            SchRecord::Rectangle(r) => r.graphical.set_owner_index(index),
448            SchRecord::PowerObject(r) => r.graphical.set_owner_index(index),
449            SchRecord::Port(r) => r.graphical.set_owner_index(index),
450            SchRecord::NoErc(r) => r.graphical.set_owner_index(index),
451            SchRecord::NetLabel(r) => r.label.graphical.set_owner_index(index),
452            SchRecord::Bus(r) => r.graphical.set_owner_index(index),
453            SchRecord::Wire(r) => r.graphical.set_owner_index(index),
454            SchRecord::TextFrame(r) => r.graphical.set_owner_index(index),
455            SchRecord::TextFrameVariant(r) => r.graphical.set_owner_index(index),
456            SchRecord::Junction(r) => r.graphical.set_owner_index(index),
457            SchRecord::Image(r) => r.graphical.set_owner_index(index),
458            SchRecord::SheetHeader(r) => r.base.set_owner_index(index),
459            SchRecord::Designator(r) => r.param.label.graphical.set_owner_index(index),
460            SchRecord::BusEntry(r) => r.graphical.set_owner_index(index),
461            SchRecord::Parameter(r) => r.label.graphical.set_owner_index(index),
462            SchRecord::WarningSign(r) => r.graphical.set_owner_index(index),
463            SchRecord::ImplementationList(r) => r.base.set_owner_index(index),
464            SchRecord::Implementation(r) => r.base.set_owner_index(index),
465            SchRecord::MapDefinerList(r) => r.base.set_owner_index(index),
466            SchRecord::MapDefiner(r) => r.base.set_owner_index(index),
467            SchRecord::ImplementationParameters(r) => r.base.set_owner_index(index),
468            SchRecord::Unknown { params, .. } => {
469                params.add_int("OWNERINDEX", index);
470            }
471        }
472    }
473
474    /// Get location for all record types.
475    pub fn location(&self) -> Option<CoordPoint> {
476        match self {
477            SchRecord::Component(r) => Some(CoordPoint::from_raw(
478                r.graphical.location_x,
479                r.graphical.location_y,
480            )),
481            SchRecord::Pin(r) => Some(CoordPoint::from_raw(
482                r.graphical.location_x,
483                r.graphical.location_y,
484            )),
485            SchRecord::Symbol(r) => Some(CoordPoint::from_raw(
486                r.graphical.location_x,
487                r.graphical.location_y,
488            )),
489            SchRecord::Label(r) => Some(CoordPoint::from_raw(
490                r.graphical.location_x,
491                r.graphical.location_y,
492            )),
493            SchRecord::Bezier(r) => Some(CoordPoint::from_raw(
494                r.graphical.location_x,
495                r.graphical.location_y,
496            )),
497            SchRecord::Polyline(r) => Some(CoordPoint::from_raw(
498                r.graphical.location_x,
499                r.graphical.location_y,
500            )),
501            SchRecord::Polygon(r) => Some(CoordPoint::from_raw(
502                r.graphical.location_x,
503                r.graphical.location_y,
504            )),
505            SchRecord::Ellipse(r) => Some(CoordPoint::from_raw(
506                r.graphical.location_x,
507                r.graphical.location_y,
508            )),
509            SchRecord::Pie(r) => Some(CoordPoint::from_raw(
510                r.graphical.location_x,
511                r.graphical.location_y,
512            )),
513            SchRecord::EllipticalArc(r) => Some(CoordPoint::from_raw(
514                r.graphical.location_x,
515                r.graphical.location_y,
516            )),
517            SchRecord::Arc(r) => Some(CoordPoint::from_raw(
518                r.graphical.location_x,
519                r.graphical.location_y,
520            )),
521            SchRecord::Line(r) => Some(CoordPoint::from_raw(
522                r.graphical.location_x,
523                r.graphical.location_y,
524            )),
525            SchRecord::Rectangle(r) => Some(CoordPoint::from_raw(
526                r.graphical.location_x,
527                r.graphical.location_y,
528            )),
529            SchRecord::PowerObject(r) => Some(CoordPoint::from_raw(
530                r.graphical.location_x,
531                r.graphical.location_y,
532            )),
533            SchRecord::Port(r) => Some(CoordPoint::from_raw(
534                r.graphical.location_x,
535                r.graphical.location_y,
536            )),
537            SchRecord::NoErc(r) => Some(CoordPoint::from_raw(
538                r.graphical.location_x,
539                r.graphical.location_y,
540            )),
541            SchRecord::NetLabel(r) => Some(CoordPoint::from_raw(
542                r.label.graphical.location_x,
543                r.label.graphical.location_y,
544            )),
545            SchRecord::Bus(r) => Some(CoordPoint::from_raw(
546                r.graphical.location_x,
547                r.graphical.location_y,
548            )),
549            SchRecord::Wire(r) => Some(CoordPoint::from_raw(
550                r.graphical.location_x,
551                r.graphical.location_y,
552            )),
553            SchRecord::TextFrame(r) => Some(CoordPoint::from_raw(
554                r.graphical.location_x,
555                r.graphical.location_y,
556            )),
557            SchRecord::TextFrameVariant(r) => Some(CoordPoint::from_raw(
558                r.graphical.location_x,
559                r.graphical.location_y,
560            )),
561            SchRecord::Junction(r) => Some(CoordPoint::from_raw(
562                r.graphical.location_x,
563                r.graphical.location_y,
564            )),
565            SchRecord::Image(r) => Some(CoordPoint::from_raw(
566                r.graphical.location_x,
567                r.graphical.location_y,
568            )),
569            SchRecord::SheetHeader(_) => None,
570            SchRecord::Designator(r) => Some(CoordPoint::from_raw(
571                r.param.label.graphical.location_x,
572                r.param.label.graphical.location_y,
573            )),
574            SchRecord::BusEntry(r) => Some(CoordPoint::from_raw(
575                r.graphical.location_x,
576                r.graphical.location_y,
577            )),
578            SchRecord::Parameter(r) => Some(CoordPoint::from_raw(
579                r.label.graphical.location_x,
580                r.label.graphical.location_y,
581            )),
582            SchRecord::WarningSign(r) => Some(CoordPoint::from_raw(
583                r.graphical.location_x,
584                r.graphical.location_y,
585            )),
586            SchRecord::ImplementationList(_) => None,
587            SchRecord::Implementation(_) => None,
588            SchRecord::MapDefinerList(_) => None,
589            SchRecord::MapDefiner(_) => None,
590            SchRecord::ImplementationParameters(_) => None,
591            SchRecord::Unknown { .. } => None,
592        }
593    }
594
595    /// Get record type name for diagnostics.
596    pub fn record_type_name(&self) -> &'static str {
597        match self {
598            SchRecord::Component(_) => "Component",
599            SchRecord::Pin(_) => "Pin",
600            SchRecord::Symbol(_) => "Symbol",
601            SchRecord::Label(_) => "Label",
602            SchRecord::Bezier(_) => "Bezier",
603            SchRecord::Polyline(_) => "Polyline",
604            SchRecord::Polygon(_) => "Polygon",
605            SchRecord::Ellipse(_) => "Ellipse",
606            SchRecord::Pie(_) => "Pie",
607            SchRecord::EllipticalArc(_) => "EllipticalArc",
608            SchRecord::Arc(_) => "Arc",
609            SchRecord::Line(_) => "Line",
610            SchRecord::Rectangle(_) => "Rectangle",
611            SchRecord::PowerObject(_) => "PowerObject",
612            SchRecord::Port(_) => "Port",
613            SchRecord::NoErc(_) => "NoErc",
614            SchRecord::NetLabel(_) => "NetLabel",
615            SchRecord::Bus(_) => "Bus",
616            SchRecord::Wire(_) => "Wire",
617            SchRecord::TextFrame(_) => "TextFrame",
618            SchRecord::TextFrameVariant(_) => "TextFrameVariant",
619            SchRecord::Junction(_) => "Junction",
620            SchRecord::Image(_) => "Image",
621            SchRecord::SheetHeader(_) => "SheetHeader",
622            SchRecord::Designator(_) => "Designator",
623            SchRecord::BusEntry(_) => "BusEntry",
624            SchRecord::Parameter(_) => "Parameter",
625            SchRecord::WarningSign(_) => "WarningSign",
626            SchRecord::ImplementationList(_) => "ImplementationList",
627            SchRecord::Implementation(_) => "Implementation",
628            SchRecord::MapDefinerList(_) => "MapDefinerList",
629            SchRecord::MapDefiner(_) => "MapDefiner",
630            SchRecord::ImplementationParameters(_) => "ImplementationParameters",
631            SchRecord::Unknown { .. } => "Unknown",
632        }
633    }
634
635    /// Get property value by name for generic queries.
636    pub fn get_property(&self, name: &str) -> Option<String> {
637        match self {
638            SchRecord::Component(r) => match name {
639                "LIBREFERENCE" => Some(r.lib_reference.clone()),
640                "COMPONENTDESCRIPTION" => Some(r.component_description.clone()),
641                "UNIQUEID" => Some(r.unique_id.clone()),
642                _ => None,
643            },
644            SchRecord::Pin(r) => match name {
645                "NAME" => Some(r.name.clone()),
646                "DESIGNATOR" => Some(r.designator.clone()),
647                "DESCRIPTION" => Some(r.description.clone()),
648                _ => None,
649            },
650            SchRecord::Label(r) => match name {
651                "TEXT" => Some(r.text.clone()),
652                _ => None,
653            },
654            SchRecord::PowerObject(r) => match name {
655                "TEXT" => Some(r.text.clone()),
656                _ => None,
657            },
658            SchRecord::Port(r) => match name {
659                "NAME" => Some(r.name.clone()),
660                _ => None,
661            },
662            SchRecord::NetLabel(r) => match name {
663                "TEXT" => Some(r.label.text.clone()),
664                _ => None,
665            },
666            SchRecord::TextFrame(r) => match name {
667                "TEXT" => Some(r.text.clone()),
668                _ => None,
669            },
670            SchRecord::TextFrameVariant(r) => match name {
671                "TEXT" => Some(r.text.clone()),
672                _ => None,
673            },
674            SchRecord::Parameter(r) => match name {
675                "NAME" => Some(r.name.clone()),
676                "TEXT" => Some(r.label.text.clone()),
677                _ => None,
678            },
679            SchRecord::Designator(r) => match name {
680                "NAME" => Some(r.param.name.clone()),
681                "TEXT" => Some(r.param.label.text.clone()),
682                _ => None,
683            },
684            SchRecord::WarningSign(r) => match name {
685                "NAME" => Some(r.name.clone()),
686                _ => None,
687            },
688            SchRecord::Implementation(r) => match name {
689                "MODELNAME" => Some(r.model_name.clone()),
690                "MODELTYPE" => Some(r.model_type.clone()),
691                _ => None,
692            },
693            _ => None,
694        }
695    }
696}
697
698/// TreeRecord implementation for SchRecord enables tree structure support.
699impl TreeRecord for SchRecord {
700    fn owner_index(&self) -> i32 {
701        self.owner_index()
702    }
703
704    fn set_owner_index(&mut self, index: i32) {
705        self.set_owner_index(index)
706    }
707}