Skip to main content

altium_format/ops/
util.rs

1// SPDX-License-Identifier: GPL-3.0-only
2// SPDX-FileCopyrightText: 2026 Alexander Kiselev <alex@akiselev.com>
3//
4//! Shared utilities for operations modules.
5
6use crate::io::SchDoc;
7use crate::records::sch::SchRecord;
8use crate::tree::{RecordId, RecordTree};
9use std::collections::HashMap;
10
11/// Sort strings alphanumerically (e.g., "R1", "R2", "R10" instead of "R1", "R10", "R2").
12///
13/// This function extracts numeric suffixes from strings and sorts them numerically
14/// while keeping the prefix in alphabetical order.
15pub fn alphanumeric_sort(a: &str, b: &str) -> std::cmp::Ordering {
16    let extract_num = |s: &str| -> (String, i32) {
17        let mut prefix = String::new();
18        let mut num_str = String::new();
19
20        for c in s.chars() {
21            if c.is_ascii_digit() {
22                num_str.push(c);
23            } else if num_str.is_empty() {
24                prefix.push(c);
25            }
26        }
27
28        let num = num_str.parse().unwrap_or(0);
29        (prefix, num)
30    };
31
32    let (prefix_a, num_a) = extract_num(a);
33    let (prefix_b, num_b) = extract_num(b);
34
35    match prefix_a.cmp(&prefix_b) {
36        std::cmp::Ordering::Equal => num_a.cmp(&num_b),
37        other => other,
38    }
39}
40
41/// Get the name of a record type.
42pub fn record_type_name(record: &SchRecord) -> &'static str {
43    match record {
44        SchRecord::Component(_) => "Component",
45        SchRecord::Pin(_) => "Pin",
46        SchRecord::Symbol(_) => "Symbol",
47        SchRecord::Label(_) => "Label",
48        SchRecord::Bezier(_) => "Bezier",
49        SchRecord::Polyline(_) => "Polyline",
50        SchRecord::Polygon(_) => "Polygon",
51        SchRecord::Ellipse(_) => "Ellipse",
52        SchRecord::Pie(_) => "Pie",
53        SchRecord::EllipticalArc(_) => "EllipticalArc",
54        SchRecord::Arc(_) => "Arc",
55        SchRecord::Line(_) => "Line",
56        SchRecord::Rectangle(_) => "Rectangle",
57        SchRecord::PowerObject(_) => "PowerObject",
58        SchRecord::Port(_) => "Port",
59        SchRecord::NoErc(_) => "NoERC",
60        SchRecord::NetLabel(_) => "NetLabel",
61        SchRecord::Bus(_) => "Bus",
62        SchRecord::Wire(_) => "Wire",
63        SchRecord::TextFrame(_) => "TextFrame",
64        SchRecord::TextFrameVariant(_) => "TextFrameVariant",
65        SchRecord::Junction(_) => "Junction",
66        SchRecord::Image(_) => "Image",
67        SchRecord::SheetHeader(_) => "SheetHeader",
68        SchRecord::Designator(_) => "Designator",
69        SchRecord::BusEntry(_) => "BusEntry",
70        SchRecord::Parameter(_) => "Parameter",
71        SchRecord::WarningSign(_) => "WarningSign",
72        SchRecord::ImplementationList(_) => "ImplementationList",
73        SchRecord::Implementation(_) => "Implementation",
74        SchRecord::MapDefinerList(_) => "MapDefinerList",
75        SchRecord::MapDefiner(_) => "MapDefiner",
76        SchRecord::ImplementationParameters(_) => "ImplementationParameters",
77        SchRecord::Unknown { .. } => "Unknown",
78    }
79}
80
81/// Count record types in a document.
82pub fn count_record_types(doc: &SchDoc) -> HashMap<&'static str, usize> {
83    let mut counts: HashMap<&'static str, usize> = HashMap::new();
84    for record in &doc.primitives {
85        let name = record_type_name(record);
86        *counts.entry(name).or_insert(0) += 1;
87    }
88    counts
89}
90
91/// Get sheet size name from style number.
92pub fn sheet_size_name(style: i32) -> &'static str {
93    match style {
94        0 => "A4",
95        1 => "A3",
96        2 => "A2",
97        3 => "A1",
98        4 => "A0",
99        5 => "A",
100        6 => "B",
101        7 => "C",
102        8 => "D",
103        9 => "E",
104        10 => "Letter",
105        11 => "Legal",
106        12 => "Tabloid",
107        13 => "OrCAD A",
108        14 => "OrCAD B",
109        15 => "OrCAD C",
110        16 => "OrCAD D",
111        17 => "OrCAD E",
112        _ => "Custom",
113    }
114}
115
116/// Get the designator for a component by finding its Designator child.
117pub fn get_component_designator(
118    tree: &RecordTree<SchRecord>,
119    component_id: RecordId,
120) -> Option<String> {
121    for (_child_id, child) in tree.children(component_id) {
122        if let SchRecord::Designator(d) = child {
123            return Some(d.param.label.text.clone());
124        }
125    }
126    None
127}
128
129/// Get component pins for netlist building.
130pub fn get_component_pins(
131    tree: &RecordTree<SchRecord>,
132    comp_id: RecordId,
133) -> Vec<(String, String, i32, i32)> {
134    let mut pins = Vec::new();
135    for (_, child) in tree.children(comp_id) {
136        if let SchRecord::Pin(p) = child {
137            let (corner_x, corner_y) = p.get_corner();
138            pins.push((p.designator.clone(), p.name.clone(), corner_x, corner_y));
139        }
140    }
141    pins
142}