1use std::collections::HashMap;
4use std::path::Path;
5
6use crate::error::{AltiumError, Result};
7use crate::io::schlib::SchLibComponent;
8use crate::io::{SchDoc, SchLib};
9use crate::records::sch::{SchDesignator, SchParameter, SchRecord, TextOrientations};
10use crate::types::{Coord, CoordPoint, UnknownFields};
11
12use super::types::Orientation;
13
14pub struct LibraryManager {
16 libraries: HashMap<String, SchLib>,
18 designator_counters: HashMap<String, u32>,
20}
21
22impl Default for LibraryManager {
23 fn default() -> Self {
24 Self::new()
25 }
26}
27
28impl LibraryManager {
29 pub fn new() -> Self {
31 Self {
32 libraries: HashMap::new(),
33 designator_counters: HashMap::new(),
34 }
35 }
36
37 pub fn load_library<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
39 let path_str = path.as_ref().to_string_lossy().to_string();
40 if self.libraries.contains_key(&path_str) {
41 return Ok(()); }
43
44 let lib = SchLib::open_file(&path)?;
45 self.libraries.insert(path_str, lib);
46 Ok(())
47 }
48
49 pub fn get_library(&self, path: &str) -> Option<&SchLib> {
51 self.libraries.get(path)
52 }
53
54 pub fn list_libraries(&self) -> Vec<&str> {
56 self.libraries.keys().map(|s| s.as_str()).collect()
57 }
58
59 pub fn find_component(&self, lib_reference: &str) -> Option<(&str, &SchLibComponent)> {
61 for (path, lib) in &self.libraries {
62 for component in lib.iter() {
63 if component.name() == lib_reference {
64 return Some((path.as_str(), component));
65 }
66 }
67 }
68 None
69 }
70
71 pub fn list_all_components(&self) -> Vec<(&str, &str, &str)> {
73 let mut components = Vec::new();
74 for (path, lib) in &self.libraries {
75 for component in lib.iter() {
76 components.push((path.as_str(), component.name(), component.description()));
77 }
78 }
79 components
80 }
81
82 pub fn search_components(&self, pattern: &str) -> Vec<(&str, &str, &str)> {
84 let pattern_lower = pattern.to_lowercase();
85 self.list_all_components()
86 .into_iter()
87 .filter(|(_, name, desc)| {
88 name.to_lowercase().contains(&pattern_lower)
89 || desc.to_lowercase().contains(&pattern_lower)
90 })
91 .collect()
92 }
93
94 pub fn init_designators_from(&mut self, doc: &SchDoc) {
96 self.designator_counters.clear();
97
98 for record in &doc.primitives {
99 if let SchRecord::Designator(d) = record {
100 let text = d.text();
101 if let Some((prefix, num)) = Self::parse_designator(text) {
102 let entry = self.designator_counters.entry(prefix).or_insert(0);
103 *entry = (*entry).max(num);
104 }
105 }
106 }
107 }
108
109 fn parse_designator(designator: &str) -> Option<(String, u32)> {
111 let mut prefix = String::new();
112 let mut num_str = String::new();
113
114 for c in designator.chars() {
115 if c.is_ascii_digit() {
116 num_str.push(c);
117 } else if num_str.is_empty() {
118 prefix.push(c);
119 } else {
120 return None;
122 }
123 }
124
125 if prefix.is_empty() || num_str.is_empty() {
126 return None;
127 }
128
129 num_str.parse().ok().map(|n| (prefix, n))
130 }
131
132 pub fn next_designator(&mut self, prefix: &str) -> String {
134 let counter = self
135 .designator_counters
136 .entry(prefix.to_string())
137 .or_insert(0);
138 *counter += 1;
139 format!("{}{}", prefix, counter)
140 }
141
142 pub fn suggest_designator_prefix(&self, lib_reference: &str) -> &str {
144 let name_upper = lib_reference.to_uppercase();
145
146 if name_upper.contains("RESISTOR") || name_upper.starts_with("R") && name_upper.len() <= 6 {
148 return "R";
149 }
150 if name_upper.contains("CAPACITOR") || name_upper.starts_with("C") && name_upper.len() <= 6
151 {
152 return "C";
153 }
154 if name_upper.contains("INDUCTOR") || name_upper.starts_with("L") && name_upper.len() <= 6 {
155 return "L";
156 }
157 if name_upper.contains("DIODE") || name_upper.starts_with("D") && name_upper.len() <= 6 {
158 return "D";
159 }
160 if name_upper.contains("TRANSISTOR")
161 || name_upper.contains("MOSFET")
162 || name_upper.contains("BJT")
163 {
164 return "Q";
165 }
166 if name_upper.contains("LED") {
167 return "D";
168 }
169 if name_upper.contains("CRYSTAL") || name_upper.contains("OSCILLATOR") {
170 return "Y";
171 }
172 if name_upper.contains("CONNECTOR") || name_upper.starts_with("J") {
173 return "J";
174 }
175 if name_upper.contains("HEADER") {
176 return "J";
177 }
178 if name_upper.contains("SWITCH") {
179 return "SW";
180 }
181 if name_upper.contains("FUSE") {
182 return "F";
183 }
184 if name_upper.contains("TRANSFORMER") {
185 return "T";
186 }
187 if name_upper.contains("RELAY") {
188 return "K";
189 }
190 if name_upper.contains("OPAMP") || name_upper.contains("OP-AMP") {
191 return "U";
192 }
193
194 "U"
196 }
197
198 pub fn instantiate_component(
200 &mut self,
201 lib_reference: &str,
202 location: CoordPoint,
203 orientation: Orientation,
204 designator: Option<&str>,
205 doc: &mut SchDoc,
206 ) -> Result<usize> {
207 let (lib_path, lib_component_clone, lib_primitives) = {
209 let (path, lib_component) = self.find_component(lib_reference).ok_or_else(|| {
210 AltiumError::Parse(format!("Component not found: {}", lib_reference))
211 })?;
212 (
213 path.to_string(),
214 lib_component.component.clone(),
215 lib_component.primitives.clone(),
216 )
217 };
218
219 let designator = match designator {
221 Some(d) => {
222 if let Some((prefix, num)) = Self::parse_designator(d) {
224 let entry = self.designator_counters.entry(prefix).or_insert(0);
225 *entry = (*entry).max(num);
226 }
227 d.to_string()
228 }
229 None => {
230 let prefix = self.suggest_designator_prefix(lib_reference).to_string();
231 self.next_designator(&prefix)
232 }
233 };
234
235 let component_index = doc.primitives.len();
237
238 let mut component = lib_component_clone;
240 component.graphical.location_x = location.x.to_raw();
241 component.graphical.location_y = location.y.to_raw();
242 component.graphical.base.owner_index = -1; component.orientation = TextOrientations::from_int(orientation.to_altium());
244 component.library_path = lib_path.clone();
245 component.source_library_name = lib_path;
246
247 let mut det = ();
249 component.unique_id = format!("{}_{}", designator, uuid_simple_deterministic(&mut det));
250
251 doc.primitives.push(SchRecord::Component(component));
252
253 for record in lib_primitives.iter().skip(1) {
255 let new_record = self.transform_primitive(
257 record,
258 location,
259 orientation,
260 component_index as i32,
261 &designator,
262 );
263
264 doc.primitives.push(new_record);
265 }
266
267 let has_designator = lib_primitives
269 .iter()
270 .any(|r| matches!(r, SchRecord::Designator(_)));
271 if !has_designator {
272 let designator_record =
273 self.create_designator(&designator, location, component_index as i32);
274 doc.primitives.push(designator_record);
275 }
276
277 Ok(component_index)
278 }
279
280 fn transform_primitive(
282 &self,
283 record: &SchRecord,
284 location: CoordPoint,
285 orientation: Orientation,
286 owner_index: i32,
287 designator: &str,
288 ) -> SchRecord {
289 match record {
290 SchRecord::Pin(pin) => {
291 let mut new_pin = pin.clone();
292 new_pin.graphical.base.owner_index = owner_index;
293
294 let local =
296 CoordPoint::from_raw(pin.graphical.location_x, pin.graphical.location_y);
297 let absolute = self.transform_point(local, location, orientation);
298 new_pin.graphical.location_x = absolute.x.to_raw();
299 new_pin.graphical.location_y = absolute.y.to_raw();
300
301 if orientation.rotation_degrees() != 0.0 {
303 use crate::records::sch::PinConglomerateFlags;
304
305 let rotated = pin.pin_conglomerate.contains(PinConglomerateFlags::ROTATED);
307 let flipped = pin.pin_conglomerate.contains(PinConglomerateFlags::FLIPPED);
308
309 let new_rotated = match orientation {
311 Orientation::Rotated90 | Orientation::Rotated270 => !rotated,
312 _ => rotated,
313 };
314
315 if new_rotated {
317 new_pin
318 .pin_conglomerate
319 .insert(PinConglomerateFlags::ROTATED);
320 } else {
321 new_pin
322 .pin_conglomerate
323 .remove(PinConglomerateFlags::ROTATED);
324 }
325 if flipped {
326 new_pin
327 .pin_conglomerate
328 .insert(PinConglomerateFlags::FLIPPED);
329 }
330 }
331
332 SchRecord::Pin(new_pin)
333 }
334 SchRecord::Line(line) => {
335 let mut new_line = line.clone();
336 new_line.graphical.base.owner_index = owner_index;
337
338 let start =
339 CoordPoint::from_raw(line.graphical.location_x, line.graphical.location_y);
340 let end = CoordPoint::from_raw(line.corner_x, line.corner_y);
341
342 let new_start = self.transform_point(start, location, orientation);
343 let new_end = self.transform_point(end, location, orientation);
344
345 new_line.graphical.location_x = new_start.x.to_raw();
346 new_line.graphical.location_y = new_start.y.to_raw();
347 new_line.corner_x = new_end.x.to_raw();
348 new_line.corner_y = new_end.y.to_raw();
349
350 SchRecord::Line(new_line)
351 }
352 SchRecord::Rectangle(rect) => {
353 let mut new_rect = rect.clone();
354 new_rect.graphical.base.owner_index = owner_index;
355
356 let loc =
357 CoordPoint::from_raw(rect.graphical.location_x, rect.graphical.location_y);
358 let corner = CoordPoint::from_raw(rect.corner_x, rect.corner_y);
359
360 let new_loc = self.transform_point(loc, location, orientation);
361 let new_corner = self.transform_point(corner, location, orientation);
362
363 new_rect.graphical.location_x = new_loc.x.to_raw();
364 new_rect.graphical.location_y = new_loc.y.to_raw();
365 new_rect.corner_x = new_corner.x.to_raw();
366 new_rect.corner_y = new_corner.y.to_raw();
367
368 SchRecord::Rectangle(new_rect)
369 }
370 SchRecord::Polygon(poly) => {
371 let mut new_poly = poly.clone();
372 new_poly.graphical.base.owner_index = owner_index;
373
374 let loc =
375 CoordPoint::from_raw(poly.graphical.location_x, poly.graphical.location_y);
376 let new_loc = self.transform_point(loc, location, orientation);
377 new_poly.graphical.location_x = new_loc.x.to_raw();
378 new_poly.graphical.location_y = new_loc.y.to_raw();
379
380 new_poly.vertices = poly
382 .vertices
383 .iter()
384 .map(|(x, y)| {
385 let pt = CoordPoint::from_raw(*x, *y);
386 let new_pt = self.transform_point(pt, location, orientation);
387 (new_pt.x.to_raw(), new_pt.y.to_raw())
388 })
389 .collect();
390
391 SchRecord::Polygon(new_poly)
392 }
393 SchRecord::Polyline(polyline) => {
394 let mut new_polyline = polyline.clone();
395 new_polyline.graphical.base.owner_index = owner_index;
396
397 let loc = CoordPoint::from_raw(
398 polyline.graphical.location_x,
399 polyline.graphical.location_y,
400 );
401 let new_loc = self.transform_point(loc, location, orientation);
402 new_polyline.graphical.location_x = new_loc.x.to_raw();
403 new_polyline.graphical.location_y = new_loc.y.to_raw();
404
405 new_polyline.vertices = polyline
406 .vertices
407 .iter()
408 .map(|(x, y)| {
409 let pt = CoordPoint::from_raw(*x, *y);
410 let new_pt = self.transform_point(pt, location, orientation);
411 (new_pt.x.to_raw(), new_pt.y.to_raw())
412 })
413 .collect();
414
415 SchRecord::Polyline(new_polyline)
416 }
417 SchRecord::Arc(arc) => {
418 let mut new_arc = arc.clone();
419 new_arc.graphical.base.owner_index = owner_index;
420
421 let loc = CoordPoint::from_raw(arc.graphical.location_x, arc.graphical.location_y);
422 let new_loc = self.transform_point(loc, location, orientation);
423 new_arc.graphical.location_x = new_loc.x.to_raw();
424 new_arc.graphical.location_y = new_loc.y.to_raw();
425
426 let rotation = orientation.rotation_degrees();
428 if rotation != 0.0 {
429 new_arc.start_angle = (arc.start_angle + rotation) % 360.0;
430 new_arc.end_angle = (arc.end_angle + rotation) % 360.0;
431 }
432
433 SchRecord::Arc(new_arc)
434 }
435 SchRecord::Ellipse(ellipse) => {
436 let mut new_ellipse = ellipse.clone();
437 new_ellipse.graphical.base.owner_index = owner_index;
438
439 let loc = CoordPoint::from_raw(
440 ellipse.graphical.location_x,
441 ellipse.graphical.location_y,
442 );
443 let new_loc = self.transform_point(loc, location, orientation);
444 new_ellipse.graphical.location_x = new_loc.x.to_raw();
445 new_ellipse.graphical.location_y = new_loc.y.to_raw();
446
447 SchRecord::Ellipse(new_ellipse)
448 }
449 SchRecord::Label(label) => {
450 let mut new_label = label.clone();
451 new_label.graphical.base.owner_index = owner_index;
452
453 let loc =
454 CoordPoint::from_raw(label.graphical.location_x, label.graphical.location_y);
455 let new_loc = self.transform_point(loc, location, orientation);
456 new_label.graphical.location_x = new_loc.x.to_raw();
457 new_label.graphical.location_y = new_loc.y.to_raw();
458
459 SchRecord::Label(new_label)
460 }
461 SchRecord::Designator(d) => {
462 let mut new_d = d.clone();
463 new_d.param.label.graphical.base.owner_index = owner_index;
464
465 new_d.param.label.text = designator.to_string();
467
468 let loc = CoordPoint::from_raw(
469 d.param.label.graphical.location_x,
470 d.param.label.graphical.location_y,
471 );
472 let new_loc = self.transform_point(loc, location, orientation);
473 new_d.param.label.graphical.location_x = new_loc.x.to_raw();
474 new_d.param.label.graphical.location_y = new_loc.y.to_raw();
475
476 SchRecord::Designator(new_d)
477 }
478 SchRecord::Parameter(p) => {
479 let mut new_p = p.clone();
480 new_p.label.graphical.base.owner_index = owner_index;
481
482 let loc = CoordPoint::from_raw(
483 p.label.graphical.location_x,
484 p.label.graphical.location_y,
485 );
486 let new_loc = self.transform_point(loc, location, orientation);
487 new_p.label.graphical.location_x = new_loc.x.to_raw();
488 new_p.label.graphical.location_y = new_loc.y.to_raw();
489
490 SchRecord::Parameter(new_p)
491 }
492 _ => {
493 record.clone()
495 }
496 }
497 }
498
499 fn transform_point(
501 &self,
502 local: CoordPoint,
503 component_location: CoordPoint,
504 orientation: Orientation,
505 ) -> CoordPoint {
506 let rotated = local.rotate(CoordPoint::ZERO, orientation.rotation_degrees());
507
508 let mirrored = if orientation.is_mirrored() {
509 CoordPoint::new(-rotated.x, rotated.y)
510 } else {
511 rotated
512 };
513
514 mirrored.translate(component_location.x, component_location.y)
515 }
516
517 fn create_designator(&self, text: &str, location: CoordPoint, owner_index: i32) -> SchRecord {
519 use crate::records::sch::{
520 SchGraphicalBase, SchLabel, SchPrimitiveBase, TextJustification,
521 };
522
523 let label = SchLabel {
524 graphical: SchGraphicalBase {
525 base: SchPrimitiveBase {
526 owner_index,
527 ..Default::default()
528 },
529 location_x: location.x.to_raw() + Coord::from_mils(10.0).to_raw(),
530 location_y: location.y.to_raw() + Coord::from_mils(30.0).to_raw(),
531 ..Default::default()
532 },
533 text: text.to_string(),
534 justification: TextJustification::BOTTOM_LEFT,
535 font_id: 1,
536 ..Default::default()
537 };
538
539 let param = SchParameter {
540 label,
541 name: "Designator".to_string(),
542 read_only_state: 1,
543 ..Default::default()
544 };
545
546 SchRecord::Designator(SchDesignator {
547 param,
548 unknown_params: UnknownFields::default(),
549 })
550 }
551}
552
553#[deprecated(
557 since = "0.1.0",
558 note = "Use uuid_simple_deterministic() with a DeterminismContext for reproducible execution"
559)]
560#[allow(dead_code)]
561fn uuid_simple() -> String {
562 use std::time::{SystemTime, UNIX_EPOCH};
563
564 let timestamp = SystemTime::now()
565 .duration_since(UNIX_EPOCH)
566 .unwrap_or_default()
567 .as_nanos();
568
569 format!("{:016X}", timestamp & 0xFFFFFFFFFFFFFFFF)
570}
571
572fn uuid_simple_deterministic(_det: &mut ()) -> String {
574 uuid::Uuid::new_v4().simple().to_string()
575}