Skip to main content

altium_format/records/pcb/
primitive.rs

1//! Base primitive types and enums for PCB records.
2
3use bitflags::bitflags;
4use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
5use std::io::{Read, Write};
6
7use crate::error::Result;
8use crate::traits::{FromBinary, ToBinary};
9use crate::types::{Coord, CoordPoint, CoordRect, Layer};
10use altium_format_derive::AltiumRecord;
11
12/// PCB primitive object IDs.
13///
14/// Based on DXP API TObjectId enumeration.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
16#[repr(u8)]
17pub enum PcbObjectId {
18    #[default]
19    None = 0,
20    /// Arc primitive.
21    Arc = 1,
22    /// Pad primitive.
23    Pad = 2,
24    /// Via primitive.
25    Via = 3,
26    /// Track primitive.
27    Track = 4,
28    /// Text primitive.
29    Text = 5,
30    /// Fill (solid rectangle) primitive.
31    Fill = 6,
32    /// Ratsnest connection.
33    Connection = 7,
34    /// Net definition.
35    Net = 8,
36    /// Component (footprint instance).
37    Component = 9,
38    /// Polygon pour.
39    Polygon = 10,
40    /// Region (copper/keepout area).
41    Region = 11,
42    /// Component 3D body.
43    ComponentBody = 12,
44    /// Dimension annotation.
45    Dimension = 13,
46    /// Coordinate annotation.
47    Coordinate = 14,
48    /// Net/component class.
49    Class = 15,
50    /// Design rule.
51    Rule = 16,
52    /// From-To definition.
53    FromTo = 17,
54    /// Differential pair definition.
55    DifferentialPair = 18,
56    /// DRC violation marker.
57    Violation = 19,
58    /// Embedded document.
59    Embedded = 20,
60    /// Embedded board (panel).
61    EmbeddedBoard = 21,
62    // 22-23 are internal (Trace, SpareVia)
63    /// Board definition.
64    Board = 24,
65    /// Board outline.
66    BoardOutline = 25,
67}
68
69impl PcbObjectId {
70    /// Create from a byte value.
71    pub fn from_byte(value: u8) -> Self {
72        match value {
73            0 => PcbObjectId::None,
74            1 => PcbObjectId::Arc,
75            2 => PcbObjectId::Pad,
76            3 => PcbObjectId::Via,
77            4 => PcbObjectId::Track,
78            5 => PcbObjectId::Text,
79            6 => PcbObjectId::Fill,
80            7 => PcbObjectId::Connection,
81            8 => PcbObjectId::Net,
82            9 => PcbObjectId::Component,
83            10 => PcbObjectId::Polygon,
84            11 => PcbObjectId::Region,
85            12 => PcbObjectId::ComponentBody,
86            13 => PcbObjectId::Dimension,
87            14 => PcbObjectId::Coordinate,
88            15 => PcbObjectId::Class,
89            16 => PcbObjectId::Rule,
90            17 => PcbObjectId::FromTo,
91            18 => PcbObjectId::DifferentialPair,
92            19 => PcbObjectId::Violation,
93            20 => PcbObjectId::Embedded,
94            21 => PcbObjectId::EmbeddedBoard,
95            24 => PcbObjectId::Board,
96            25 => PcbObjectId::BoardOutline,
97            _ => PcbObjectId::None,
98        }
99    }
100
101    /// Convert to byte value.
102    pub const fn to_byte(self) -> u8 {
103        self as u8
104    }
105
106    /// Get the name of this object type.
107    pub const fn name(self) -> &'static str {
108        match self {
109            PcbObjectId::None => "None",
110            PcbObjectId::Arc => "Arc",
111            PcbObjectId::Pad => "Pad",
112            PcbObjectId::Via => "Via",
113            PcbObjectId::Track => "Track",
114            PcbObjectId::Text => "Text",
115            PcbObjectId::Fill => "Fill",
116            PcbObjectId::Connection => "Connection",
117            PcbObjectId::Net => "Net",
118            PcbObjectId::Component => "Component",
119            PcbObjectId::Polygon => "Polygon",
120            PcbObjectId::Region => "Region",
121            PcbObjectId::ComponentBody => "ComponentBody",
122            PcbObjectId::Dimension => "Dimension",
123            PcbObjectId::Coordinate => "Coordinate",
124            PcbObjectId::Class => "Class",
125            PcbObjectId::Rule => "Rule",
126            PcbObjectId::FromTo => "FromTo",
127            PcbObjectId::DifferentialPair => "DifferentialPair",
128            PcbObjectId::Violation => "Violation",
129            PcbObjectId::Embedded => "Embedded",
130            PcbObjectId::EmbeddedBoard => "EmbeddedBoard",
131            PcbObjectId::Board => "Board",
132            PcbObjectId::BoardOutline => "BoardOutline",
133        }
134    }
135}
136
137bitflags! {
138    /// PCB primitive flags.
139    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
140    pub struct PcbFlags: u16 {
141        const UNKNOWN2 = 2;
142        const UNLOCKED = 4;
143        const UNKNOWN8 = 8;
144        const UNKNOWN16 = 16;
145        const TENTING_TOP = 32;
146        const TENTING_BOTTOM = 64;
147        const FABRICATION_TOP = 128;
148        const FABRICATION_BOTTOM = 256;
149        const KEEPOUT = 512;
150    }
151}
152
153impl Default for PcbFlags {
154    fn default() -> Self {
155        PcbFlags::UNLOCKED | PcbFlags::UNKNOWN8
156    }
157}
158
159/// Pad stack mode for pads and vias.
160#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
161#[repr(u8)]
162pub enum PcbStackMode {
163    #[default]
164    Simple = 0,
165    TopMiddleBottom = 1,
166    FullStack = 2,
167}
168
169impl PcbStackMode {
170    pub fn from_byte(value: u8) -> Self {
171        match value {
172            0 => PcbStackMode::Simple,
173            1 => PcbStackMode::TopMiddleBottom,
174            2 => PcbStackMode::FullStack,
175            _ => PcbStackMode::Simple,
176        }
177    }
178
179    pub const fn to_byte(self) -> u8 {
180        self as u8
181    }
182}
183
184/// Pad shapes.
185///
186/// Based on DXP API TShape enumeration.
187#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
188#[repr(u8)]
189pub enum PcbPadShape {
190    /// No shape defined.
191    NoShape = 0,
192    /// Round/circular pad.
193    #[default]
194    Round = 1,
195    /// Rectangular pad.
196    Rectangular = 2,
197    /// Octagonal pad.
198    Octagonal = 3,
199    /// Circle shape (alternate to Round).
200    Circle = 4,
201    /// Arc-shaped pad.
202    Arc = 5,
203    /// Terminator-shaped pad.
204    Terminator = 6,
205    /// Round rectangle variant.
206    RoundRect = 7,
207    /// Rotated rectangle.
208    RotatedRect = 8,
209    /// Rounded rectangular pad.
210    RoundedRectangle = 9,
211}
212
213impl PcbPadShape {
214    pub fn from_byte(value: u8) -> Self {
215        match value {
216            0 => PcbPadShape::NoShape,
217            1 => PcbPadShape::Round,
218            2 => PcbPadShape::Rectangular,
219            3 => PcbPadShape::Octagonal,
220            4 => PcbPadShape::Circle,
221            5 => PcbPadShape::Arc,
222            6 => PcbPadShape::Terminator,
223            7 => PcbPadShape::RoundRect,
224            8 => PcbPadShape::RotatedRect,
225            9 => PcbPadShape::RoundedRectangle,
226            _ => PcbPadShape::Round,
227        }
228    }
229
230    pub const fn to_byte(self) -> u8 {
231        self as u8
232    }
233
234    /// Get the name of this shape.
235    pub const fn name(self) -> &'static str {
236        match self {
237            PcbPadShape::NoShape => "NoShape",
238            PcbPadShape::Round => "Round",
239            PcbPadShape::Rectangular => "Rectangular",
240            PcbPadShape::Octagonal => "Octagonal",
241            PcbPadShape::Circle => "Circle",
242            PcbPadShape::Arc => "Arc",
243            PcbPadShape::Terminator => "Terminator",
244            PcbPadShape::RoundRect => "RoundRect",
245            PcbPadShape::RotatedRect => "RotatedRect",
246            PcbPadShape::RoundedRectangle => "RoundedRectangle",
247        }
248    }
249}
250
251/// Pad hole shapes.
252#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
253#[repr(u8)]
254pub enum PcbPadHoleShape {
255    #[default]
256    Round = 0,
257    Square = 1,
258    Slot = 2,
259}
260
261impl PcbPadHoleShape {
262    pub fn from_byte(value: u8) -> Self {
263        match value {
264            0 => PcbPadHoleShape::Round,
265            1 => PcbPadHoleShape::Square,
266            2 => PcbPadHoleShape::Slot,
267            _ => PcbPadHoleShape::Round,
268        }
269    }
270
271    pub const fn to_byte(self) -> u8 {
272        self as u8
273    }
274}
275
276/// Text kinds.
277#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
278#[repr(u8)]
279pub enum PcbTextKind {
280    #[default]
281    Stroke = 0,
282    TrueType = 1,
283    BarCode = 2,
284}
285
286impl PcbTextKind {
287    pub fn from_byte(value: u8) -> Self {
288        match value {
289            0 => PcbTextKind::Stroke,
290            1 => PcbTextKind::TrueType,
291            2 => PcbTextKind::BarCode,
292            _ => PcbTextKind::Stroke,
293        }
294    }
295
296    pub const fn to_byte(self) -> u8 {
297        self as u8
298    }
299}
300
301/// Stroke font types.
302#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
303#[repr(i16)]
304pub enum PcbTextStrokeFont {
305    #[default]
306    Default = 0,
307    SansSerif = 1,
308    Serif = 3,
309}
310
311impl PcbTextStrokeFont {
312    pub fn from_i16(value: i16) -> Self {
313        match value {
314            0 => PcbTextStrokeFont::Default,
315            1 => PcbTextStrokeFont::SansSerif,
316            3 => PcbTextStrokeFont::Serif,
317            _ => PcbTextStrokeFont::Default,
318        }
319    }
320
321    pub const fn to_i16(self) -> i16 {
322        self as i16
323    }
324}
325
326/// Text justification.
327#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
328#[repr(u8)]
329pub enum PcbTextJustification {
330    BottomRight = 1,
331    MiddleRight = 2,
332    TopRight = 3,
333    BottomCenter = 4,
334    #[default]
335    MiddleCenter = 5,
336    TopCenter = 6,
337    BottomLeft = 7,
338    MiddleLeft = 8,
339    TopLeft = 9,
340}
341
342impl PcbTextJustification {
343    pub fn from_byte(value: u8) -> Self {
344        match value {
345            1 => PcbTextJustification::BottomRight,
346            2 => PcbTextJustification::MiddleRight,
347            3 => PcbTextJustification::TopRight,
348            4 => PcbTextJustification::BottomCenter,
349            5 => PcbTextJustification::MiddleCenter,
350            6 => PcbTextJustification::TopCenter,
351            7 => PcbTextJustification::BottomLeft,
352            8 => PcbTextJustification::MiddleLeft,
353            9 => PcbTextJustification::TopLeft,
354            _ => PcbTextJustification::MiddleCenter,
355        }
356    }
357
358    pub const fn to_byte(self) -> u8 {
359        self as u8
360    }
361}
362
363/// Common fields for all PCB primitives.
364#[derive(Debug, Clone, Default)]
365pub struct PcbPrimitiveCommon {
366    /// PCB layer.
367    pub layer: Layer,
368    /// Flags.
369    pub flags: PcbFlags,
370    /// Unique ID (from UniqueIdPrimitiveInformation).
371    pub unique_id: Option<String>,
372}
373
374impl PcbPrimitiveCommon {
375    /// Read common primitive fields from binary stream.
376    pub fn read_from<R: Read>(reader: &mut R) -> Result<Self> {
377        let layer = Layer(reader.read_u8()?);
378        let flags = PcbFlags::from_bits_truncate(reader.read_u16::<LittleEndian>()?);
379
380        // Read and assert 10 0xFF bytes
381        let mut ff_bytes = [0u8; 10];
382        reader.read_exact(&mut ff_bytes)?;
383        // Note: In production, we might want to warn if these aren't all 0xFF
384
385        Ok(PcbPrimitiveCommon {
386            layer,
387            flags,
388            unique_id: None,
389        })
390    }
391
392    /// Check if the primitive is locked.
393    pub fn is_locked(&self) -> bool {
394        !self.flags.contains(PcbFlags::UNLOCKED)
395    }
396
397    /// Check if top tenting is enabled.
398    pub fn is_tenting_top(&self) -> bool {
399        self.flags.contains(PcbFlags::TENTING_TOP)
400    }
401
402    /// Check if bottom tenting is enabled.
403    pub fn is_tenting_bottom(&self) -> bool {
404        self.flags.contains(PcbFlags::TENTING_BOTTOM)
405    }
406
407    /// Check if this is a keepout.
408    pub fn is_keepout(&self) -> bool {
409        self.flags.contains(PcbFlags::KEEPOUT)
410    }
411}
412
413impl FromBinary for PcbPrimitiveCommon {
414    fn read_from<R: Read>(reader: &mut R) -> Result<Self> {
415        PcbPrimitiveCommon::read_from(reader)
416    }
417}
418
419impl ToBinary for PcbPrimitiveCommon {
420    fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
421        writer.write_u8(self.layer.to_byte())?;
422        writer.write_u16::<LittleEndian>(self.flags.bits())?;
423        writer.write_all(&[0xFFu8; 10])?;
424        Ok(())
425    }
426
427    fn binary_size(&self) -> usize {
428        13
429    }
430}
431
432impl FromBinary for PcbFlags {
433    fn read_from<R: Read>(reader: &mut R) -> Result<Self> {
434        Ok(PcbFlags::from_bits_truncate(
435            reader.read_u16::<LittleEndian>()?,
436        ))
437    }
438}
439
440impl ToBinary for PcbFlags {
441    fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
442        writer.write_u16::<LittleEndian>(self.bits())?;
443        Ok(())
444    }
445
446    fn binary_size(&self) -> usize {
447        2
448    }
449}
450
451impl FromBinary for PcbStackMode {
452    fn read_from<R: Read>(reader: &mut R) -> Result<Self> {
453        Ok(PcbStackMode::from_byte(reader.read_u8()?))
454    }
455}
456
457impl ToBinary for PcbStackMode {
458    fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
459        writer.write_u8(self.to_byte())?;
460        Ok(())
461    }
462
463    fn binary_size(&self) -> usize {
464        1
465    }
466}
467
468impl FromBinary for PcbPadShape {
469    fn read_from<R: Read>(reader: &mut R) -> Result<Self> {
470        Ok(PcbPadShape::from_byte(reader.read_u8()?))
471    }
472}
473
474impl ToBinary for PcbPadShape {
475    fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
476        writer.write_u8(self.to_byte())?;
477        Ok(())
478    }
479
480    fn binary_size(&self) -> usize {
481        1
482    }
483}
484
485impl FromBinary for PcbPadHoleShape {
486    fn read_from<R: Read>(reader: &mut R) -> Result<Self> {
487        Ok(PcbPadHoleShape::from_byte(reader.read_u8()?))
488    }
489}
490
491impl ToBinary for PcbPadHoleShape {
492    fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
493        writer.write_u8(self.to_byte())?;
494        Ok(())
495    }
496
497    fn binary_size(&self) -> usize {
498        1
499    }
500}
501
502impl FromBinary for PcbTextKind {
503    fn read_from<R: Read>(reader: &mut R) -> Result<Self> {
504        Ok(PcbTextKind::from_byte(reader.read_u8()?))
505    }
506}
507
508impl ToBinary for PcbTextKind {
509    fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
510        writer.write_u8(self.to_byte())?;
511        Ok(())
512    }
513
514    fn binary_size(&self) -> usize {
515        1
516    }
517}
518
519impl FromBinary for PcbTextStrokeFont {
520    fn read_from<R: Read>(reader: &mut R) -> Result<Self> {
521        Ok(PcbTextStrokeFont::from_i16(
522            reader.read_i16::<LittleEndian>()?,
523        ))
524    }
525}
526
527impl ToBinary for PcbTextStrokeFont {
528    fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
529        writer.write_i16::<LittleEndian>(self.to_i16())?;
530        Ok(())
531    }
532
533    fn binary_size(&self) -> usize {
534        2
535    }
536}
537
538impl FromBinary for PcbTextJustification {
539    fn read_from<R: Read>(reader: &mut R) -> Result<Self> {
540        Ok(PcbTextJustification::from_byte(reader.read_u8()?))
541    }
542}
543
544impl ToBinary for PcbTextJustification {
545    fn write_to<W: Write>(&self, writer: &mut W) -> Result<()> {
546        writer.write_u8(self.to_byte())?;
547        Ok(())
548    }
549
550    fn binary_size(&self) -> usize {
551        1
552    }
553}
554
555// ============================================================================
556// Additional enums from DXP API
557// ============================================================================
558
559/// Routing corner style.
560///
561/// Based on DXP API TCornerStyle enumeration.
562#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
563#[repr(u8)]
564pub enum CornerStyle {
565    /// 90 degree corners.
566    #[default]
567    Deg90 = 0,
568    /// 45 degree corners.
569    Deg45 = 1,
570    /// Rounded corners (arc).
571    Round = 2,
572}
573
574impl CornerStyle {
575    pub fn from_byte(value: u8) -> Self {
576        match value {
577            0 => CornerStyle::Deg90,
578            1 => CornerStyle::Deg45,
579            2 => CornerStyle::Round,
580            _ => CornerStyle::Deg90,
581        }
582    }
583
584    pub const fn to_byte(self) -> u8 {
585        self as u8
586    }
587
588    pub const fn name(self) -> &'static str {
589        match self {
590            CornerStyle::Deg90 => "90",
591            CornerStyle::Deg45 => "45",
592            CornerStyle::Round => "Round",
593        }
594    }
595}
596
597/// Plane connection style for pads/vias.
598///
599/// Based on DXP API TPlaneConnectionStyle enumeration.
600#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
601#[repr(u8)]
602pub enum PlaneConnectionStyle {
603    /// No connection to plane.
604    NoConnect = 0,
605    /// Relief (thermal) connection.
606    #[default]
607    ReliefConnect = 1,
608    /// Direct connection (no thermal relief).
609    DirectConnect = 2,
610}
611
612impl PlaneConnectionStyle {
613    pub fn from_byte(value: u8) -> Self {
614        match value {
615            0 => PlaneConnectionStyle::NoConnect,
616            1 => PlaneConnectionStyle::ReliefConnect,
617            2 => PlaneConnectionStyle::DirectConnect,
618            _ => PlaneConnectionStyle::ReliefConnect,
619        }
620    }
621
622    pub const fn to_byte(self) -> u8 {
623        self as u8
624    }
625
626    pub const fn name(self) -> &'static str {
627        match self {
628            PlaneConnectionStyle::NoConnect => "NoConnect",
629            PlaneConnectionStyle::ReliefConnect => "Relief",
630            PlaneConnectionStyle::DirectConnect => "Direct",
631        }
632    }
633}
634
635/// Net routing topology.
636///
637/// Based on DXP API TNetTopology enumeration.
638#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
639#[repr(u8)]
640pub enum NetTopology {
641    /// Shortest path routing.
642    #[default]
643    Shortest = 0,
644    /// Horizontal routing preference.
645    Horizontal = 1,
646    /// Vertical routing preference.
647    Vertical = 2,
648    /// Simple daisy chain (sequential).
649    DaisyChainSimple = 3,
650    /// Mid-driven daisy chain.
651    DaisyChainMidDriven = 4,
652    /// Balanced daisy chain.
653    DaisyChainBalanced = 5,
654    /// Starburst (all from center).
655    Starburst = 6,
656}
657
658impl NetTopology {
659    pub fn from_byte(value: u8) -> Self {
660        match value {
661            0 => NetTopology::Shortest,
662            1 => NetTopology::Horizontal,
663            2 => NetTopology::Vertical,
664            3 => NetTopology::DaisyChainSimple,
665            4 => NetTopology::DaisyChainMidDriven,
666            5 => NetTopology::DaisyChainBalanced,
667            6 => NetTopology::Starburst,
668            _ => NetTopology::Shortest,
669        }
670    }
671
672    pub const fn to_byte(self) -> u8 {
673        self as u8
674    }
675
676    pub const fn name(self) -> &'static str {
677        match self {
678            NetTopology::Shortest => "Shortest",
679            NetTopology::Horizontal => "Horizontal",
680            NetTopology::Vertical => "Vertical",
681            NetTopology::DaisyChainSimple => "DaisyChain",
682            NetTopology::DaisyChainMidDriven => "DaisyChainMidDriven",
683            NetTopology::DaisyChainBalanced => "DaisyChainBalanced",
684            NetTopology::Starburst => "Starburst",
685        }
686    }
687}
688
689/// Dimension object kind.
690///
691/// Based on DXP API TDimensionKind enumeration.
692#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
693#[repr(u8)]
694pub enum DimensionKind {
695    /// No dimension.
696    #[default]
697    None = 0,
698    /// Linear dimension.
699    Linear = 1,
700    /// Angular dimension.
701    Angular = 2,
702    /// Radial dimension.
703    Radial = 3,
704    /// Leader (callout).
705    Leader = 4,
706    /// Datum dimension.
707    Datum = 5,
708    /// Baseline dimension.
709    Baseline = 6,
710    /// Center dimension.
711    Center = 7,
712    /// Original dimension.
713    Original = 8,
714    /// Linear diameter dimension.
715    LinearDiameter = 9,
716    /// Radial diameter dimension.
717    RadialDiameter = 10,
718}
719
720impl DimensionKind {
721    pub fn from_byte(value: u8) -> Self {
722        match value {
723            0 => DimensionKind::None,
724            1 => DimensionKind::Linear,
725            2 => DimensionKind::Angular,
726            3 => DimensionKind::Radial,
727            4 => DimensionKind::Leader,
728            5 => DimensionKind::Datum,
729            6 => DimensionKind::Baseline,
730            7 => DimensionKind::Center,
731            8 => DimensionKind::Original,
732            9 => DimensionKind::LinearDiameter,
733            10 => DimensionKind::RadialDiameter,
734            _ => DimensionKind::None,
735        }
736    }
737
738    pub const fn to_byte(self) -> u8 {
739        self as u8
740    }
741
742    pub const fn name(self) -> &'static str {
743        match self {
744            DimensionKind::None => "None",
745            DimensionKind::Linear => "Linear",
746            DimensionKind::Angular => "Angular",
747            DimensionKind::Radial => "Radial",
748            DimensionKind::Leader => "Leader",
749            DimensionKind::Datum => "Datum",
750            DimensionKind::Baseline => "Baseline",
751            DimensionKind::Center => "Center",
752            DimensionKind::Original => "Original",
753            DimensionKind::LinearDiameter => "LinearDiameter",
754            DimensionKind::RadialDiameter => "RadialDiameter",
755        }
756    }
757}
758
759/// Polygon region kind.
760///
761/// Based on DXP API TPolyRegionKind enumeration.
762#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
763#[repr(u8)]
764pub enum PolyRegionKind {
765    /// Copper region.
766    #[default]
767    Copper = 0,
768    /// Cutout region (removes copper).
769    Cutout = 1,
770    /// Named region (for design rules).
771    NamedRegion = 2,
772}
773
774impl PolyRegionKind {
775    pub fn from_byte(value: u8) -> Self {
776        match value {
777            0 => PolyRegionKind::Copper,
778            1 => PolyRegionKind::Cutout,
779            2 => PolyRegionKind::NamedRegion,
780            _ => PolyRegionKind::Copper,
781        }
782    }
783
784    pub const fn to_byte(self) -> u8 {
785        self as u8
786    }
787
788    pub const fn name(self) -> &'static str {
789        match self {
790            PolyRegionKind::Copper => "Copper",
791            PolyRegionKind::Cutout => "Cutout",
792            PolyRegionKind::NamedRegion => "NamedRegion",
793        }
794    }
795}
796
797/// Unit system type.
798///
799/// Based on DXP API TUnit enumeration.
800/// Note: This has Metric=0, Imperial=1 (DXP convention).
801/// For display units in board settings, see `DisplayUnit` in board.rs.
802#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
803#[repr(u8)]
804pub enum UnitSystem {
805    /// Metric units (mm).
806    #[default]
807    Metric = 0,
808    /// Imperial units (mil/inch).
809    Imperial = 1,
810}
811
812impl UnitSystem {
813    pub fn from_byte(value: u8) -> Self {
814        match value {
815            0 => UnitSystem::Metric,
816            1 => UnitSystem::Imperial,
817            _ => UnitSystem::Metric,
818        }
819    }
820
821    pub const fn to_byte(self) -> u8 {
822        self as u8
823    }
824
825    pub const fn name(self) -> &'static str {
826        match self {
827            UnitSystem::Metric => "Metric",
828            UnitSystem::Imperial => "Imperial",
829        }
830    }
831}
832
833/// Text auto-position mode.
834///
835/// Based on DXP API TTextAutoposition enumeration.
836#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
837#[repr(u8)]
838pub enum TextAutoposition {
839    /// Manual positioning.
840    #[default]
841    Manual = 0,
842    /// Top-left of component.
843    TopLeft = 1,
844    /// Center-left of component.
845    CenterLeft = 2,
846    /// Bottom-left of component.
847    BottomLeft = 3,
848    /// Top-center of component.
849    TopCenter = 4,
850    /// Center of component.
851    CenterCenter = 5,
852    /// Bottom-center of component.
853    BottomCenter = 6,
854    /// Top-right of component.
855    TopRight = 7,
856    /// Center-right of component.
857    CenterRight = 8,
858    /// Bottom-right of component.
859    BottomRight = 9,
860}
861
862impl TextAutoposition {
863    pub fn from_byte(value: u8) -> Self {
864        match value {
865            0 => TextAutoposition::Manual,
866            1 => TextAutoposition::TopLeft,
867            2 => TextAutoposition::CenterLeft,
868            3 => TextAutoposition::BottomLeft,
869            4 => TextAutoposition::TopCenter,
870            5 => TextAutoposition::CenterCenter,
871            6 => TextAutoposition::BottomCenter,
872            7 => TextAutoposition::TopRight,
873            8 => TextAutoposition::CenterRight,
874            9 => TextAutoposition::BottomRight,
875            _ => TextAutoposition::Manual,
876        }
877    }
878
879    pub const fn to_byte(self) -> u8 {
880        self as u8
881    }
882
883    pub const fn name(self) -> &'static str {
884        match self {
885            TextAutoposition::Manual => "Manual",
886            TextAutoposition::TopLeft => "TopLeft",
887            TextAutoposition::CenterLeft => "CenterLeft",
888            TextAutoposition::BottomLeft => "BottomLeft",
889            TextAutoposition::TopCenter => "TopCenter",
890            TextAutoposition::CenterCenter => "Center",
891            TextAutoposition::BottomCenter => "BottomCenter",
892            TextAutoposition::TopRight => "TopRight",
893            TextAutoposition::CenterRight => "CenterRight",
894            TextAutoposition::BottomRight => "BottomRight",
895        }
896    }
897}
898
899/// Component style/package type.
900///
901/// Based on DXP API TComponentStyle enumeration.
902#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
903#[repr(u8)]
904pub enum ComponentStyle {
905    /// Unknown package style.
906    #[default]
907    Unknown = 0,
908    /// Small discrete component.
909    Small = 1,
910    /// Small SMT component.
911    SmallSMT = 2,
912    /// Edge connector.
913    Edge = 3,
914    /// Dual in-line package.
915    DIP = 4,
916    /// Single in-line package.
917    SIP = 5,
918    /// SM single in-line package.
919    SMSIP = 6,
920    /// SM dual in-line package.
921    SMDIP = 7,
922    /// Leadless chip carrier.
923    LCC = 8,
924    /// Ball grid array.
925    BGA = 9,
926    /// Pin grid array.
927    PGA = 10,
928}
929
930impl ComponentStyle {
931    pub fn from_byte(value: u8) -> Self {
932        match value {
933            0 => ComponentStyle::Unknown,
934            1 => ComponentStyle::Small,
935            2 => ComponentStyle::SmallSMT,
936            3 => ComponentStyle::Edge,
937            4 => ComponentStyle::DIP,
938            5 => ComponentStyle::SIP,
939            6 => ComponentStyle::SMSIP,
940            7 => ComponentStyle::SMDIP,
941            8 => ComponentStyle::LCC,
942            9 => ComponentStyle::BGA,
943            10 => ComponentStyle::PGA,
944            _ => ComponentStyle::Unknown,
945        }
946    }
947
948    pub const fn to_byte(self) -> u8 {
949        self as u8
950    }
951
952    pub const fn name(self) -> &'static str {
953        match self {
954            ComponentStyle::Unknown => "Unknown",
955            ComponentStyle::Small => "Small",
956            ComponentStyle::SmallSMT => "SmallSMT",
957            ComponentStyle::Edge => "Edge",
958            ComponentStyle::DIP => "DIP",
959            ComponentStyle::SIP => "SIP",
960            ComponentStyle::SMSIP => "SMSIP",
961            ComponentStyle::SMDIP => "SMDIP",
962            ComponentStyle::LCC => "LCC",
963            ComponentStyle::BGA => "BGA",
964            ComponentStyle::PGA => "PGA",
965        }
966    }
967}
968
969/// Dielectric material type in layer stack.
970///
971/// Based on DXP API TDielectricType enumeration.
972#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
973#[repr(u8)]
974pub enum DielectricType {
975    /// No dielectric.
976    #[default]
977    None = 0,
978    /// Core material.
979    Core = 1,
980    /// PrePreg material.
981    PrePreg = 2,
982    /// Surface material.
983    SurfaceMaterial = 3,
984}
985
986impl DielectricType {
987    pub fn from_byte(value: u8) -> Self {
988        match value {
989            0 => DielectricType::None,
990            1 => DielectricType::Core,
991            2 => DielectricType::PrePreg,
992            3 => DielectricType::SurfaceMaterial,
993            _ => DielectricType::None,
994        }
995    }
996
997    pub const fn to_byte(self) -> u8 {
998        self as u8
999    }
1000
1001    pub const fn name(self) -> &'static str {
1002        match self {
1003            DielectricType::None => "None",
1004            DielectricType::Core => "Core",
1005            DielectricType::PrePreg => "PrePreg",
1006            DielectricType::SurfaceMaterial => "SurfaceMaterial",
1007        }
1008    }
1009}
1010
1011/// Extended drill type.
1012///
1013/// Based on DXP API TExtendedDrillType enumeration.
1014#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1015#[repr(u8)]
1016pub enum ExtendedDrillType {
1017    /// Standard drilled hole.
1018    #[default]
1019    Drilled = 0,
1020    /// Punched hole.
1021    Punched = 1,
1022    /// Laser drilled hole.
1023    LaserDrilled = 2,
1024    /// Plasma drilled hole.
1025    PlasmaDrilled = 3,
1026}
1027
1028impl ExtendedDrillType {
1029    pub fn from_byte(value: u8) -> Self {
1030        match value {
1031            0 => ExtendedDrillType::Drilled,
1032            1 => ExtendedDrillType::Punched,
1033            2 => ExtendedDrillType::LaserDrilled,
1034            3 => ExtendedDrillType::PlasmaDrilled,
1035            _ => ExtendedDrillType::Drilled,
1036        }
1037    }
1038
1039    pub const fn to_byte(self) -> u8 {
1040        self as u8
1041    }
1042
1043    pub const fn name(self) -> &'static str {
1044        match self {
1045            ExtendedDrillType::Drilled => "Drilled",
1046            ExtendedDrillType::Punched => "Punched",
1047            ExtendedDrillType::LaserDrilled => "LaserDrilled",
1048            ExtendedDrillType::PlasmaDrilled => "PlasmaDrilled",
1049        }
1050    }
1051}
1052
1053/// Board side (top or bottom).
1054///
1055/// Based on DXP API TBoardSide enumeration.
1056#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1057#[repr(u8)]
1058pub enum BoardSide {
1059    /// Top side of board.
1060    #[default]
1061    Top = 0,
1062    /// Bottom side of board.
1063    Bottom = 1,
1064}
1065
1066impl BoardSide {
1067    pub fn from_byte(value: u8) -> Self {
1068        match value {
1069            0 => BoardSide::Top,
1070            1 => BoardSide::Bottom,
1071            _ => BoardSide::Top,
1072        }
1073    }
1074
1075    pub const fn to_byte(self) -> u8 {
1076        self as u8
1077    }
1078
1079    pub const fn name(self) -> &'static str {
1080        match self {
1081            BoardSide::Top => "Top",
1082            BoardSide::Bottom => "Bottom",
1083        }
1084    }
1085}
1086
1087// ============================================================================
1088// End of DXP API enums
1089// ============================================================================
1090
1091/// Base fields for rectangular primitives (Fill, Text).
1092#[derive(Debug, Clone, Default, AltiumRecord)]
1093#[altium(format = "binary")]
1094pub struct PcbRectangularBase {
1095    /// Common fields.
1096    #[altium(flatten)]
1097    pub common: PcbPrimitiveCommon,
1098    /// First corner.
1099    #[altium(coord_point)]
1100    pub corner1: CoordPoint,
1101    /// Second corner.
1102    #[altium(coord_point)]
1103    pub corner2: CoordPoint,
1104    /// Rotation angle in degrees.
1105    pub rotation: f64,
1106}
1107
1108impl PcbRectangularBase {
1109    /// Width of the rectangle.
1110    pub fn width(&self) -> Coord {
1111        Coord::from_raw(self.corner2.x.to_raw() - self.corner1.x.to_raw())
1112    }
1113
1114    /// Height of the rectangle.
1115    pub fn height(&self) -> Coord {
1116        Coord::from_raw(self.corner2.y.to_raw() - self.corner1.y.to_raw())
1117    }
1118
1119    /// Calculate bounds (ignoring rotation for now).
1120    pub fn calculate_bounds(&self) -> CoordRect {
1121        CoordRect::from_corners(self.corner1, self.corner2)
1122    }
1123}
1124
1125/// Dispatch enum containing all PCB record types.
1126///
1127/// Large variants (Pad, ComponentBody) are boxed to reduce enum size on the stack.
1128#[derive(Debug, Clone)]
1129pub enum PcbRecord {
1130    Arc(super::PcbArc),
1131    Pad(Box<super::PcbPad>),
1132    Via(super::PcbVia),
1133    Track(super::PcbTrack),
1134    Text(super::PcbText),
1135    Fill(super::PcbFill),
1136    Region(super::PcbRegion),
1137    ComponentBody(Box<super::PcbComponentBody>),
1138    Polygon(super::PcbPolygon),
1139    /// Unknown record type.
1140    Unknown {
1141        object_id: PcbObjectId,
1142        raw_data: Vec<u8>,
1143    },
1144}
1145
1146impl PcbRecord {
1147    /// Get the object ID of this record.
1148    pub fn object_id(&self) -> PcbObjectId {
1149        match self {
1150            PcbRecord::Arc(_) => PcbObjectId::Arc,
1151            PcbRecord::Pad(_) => PcbObjectId::Pad,
1152            PcbRecord::Via(_) => PcbObjectId::Via,
1153            PcbRecord::Track(_) => PcbObjectId::Track,
1154            PcbRecord::Text(_) => PcbObjectId::Text,
1155            PcbRecord::Fill(_) => PcbObjectId::Fill,
1156            PcbRecord::Region(_) => PcbObjectId::Region,
1157            PcbRecord::ComponentBody(_) => PcbObjectId::ComponentBody,
1158            PcbRecord::Polygon(_) => PcbObjectId::Polygon,
1159            PcbRecord::Unknown { object_id, .. } => *object_id,
1160        }
1161    }
1162
1163    /// Get the layer of this record.
1164    pub fn layer(&self) -> Layer {
1165        match self {
1166            PcbRecord::Arc(r) => r.common.layer,
1167            PcbRecord::Pad(r) => r.common.layer,
1168            PcbRecord::Via(r) => r.common.layer,
1169            PcbRecord::Track(r) => r.common.layer,
1170            PcbRecord::Text(r) => r.base.common.layer,
1171            PcbRecord::Fill(r) => r.base.common.layer,
1172            PcbRecord::Region(r) => r.common.layer,
1173            PcbRecord::ComponentBody(r) => r.common.layer,
1174            PcbRecord::Polygon(r) => r.layer,
1175            PcbRecord::Unknown { .. } => Layer::default(),
1176        }
1177    }
1178}