1use cfb::CompoundFile;
7use std::fs::File;
8use std::io::{Cursor, Read, Seek, SeekFrom, Write};
9use std::path::Path;
10
11use crate::dump::{DumpTree, TreeBuilder};
12use crate::error::{AltiumError, Result};
13use crate::io::reader::{read_block, read_parameters_block};
14use crate::io::writer::write_parameters_block;
15use crate::records::pcb::{
16 PcbAdvancedPlacerOptions, PcbArc, PcbClass, PcbDrcOptions, PcbFill, PcbObjectId,
17 PcbPinSwapOptions, PcbPolygon, PcbRecord, PcbRegion, PcbRule, PcbText, PcbTrack, PcbVia,
18};
19use crate::traits::FromBinary;
20use crate::types::ParameterCollection;
21
22#[derive(Debug, Default)]
24pub struct PcbDoc {
25 pub board_params: ParameterCollection,
27 pub components: Vec<PcbDocComponent>,
29 pub primitives: Vec<PcbRecord>,
31 pub nets: Vec<String>,
33 pub rules: Vec<PcbRule>,
35 pub classes: Vec<PcbClass>,
37 pub placer_options: Option<PcbAdvancedPlacerOptions>,
39 pub drc_options: Option<PcbDrcOptions>,
41 pub pin_swap_options: Option<PcbPinSwapOptions>,
43}
44
45#[derive(Debug, Default)]
47pub struct PcbDocComponent {
48 pub designator: String,
50 pub pattern: String,
52 pub comment: String,
54 pub params: ParameterCollection,
56 pub primitives: Vec<PcbRecord>,
58}
59
60impl PcbDoc {
61 pub fn open<R: Read + Seek>(reader: R) -> Result<Self> {
63 let mut pcbdoc = PcbDoc::default();
64 let mut cf = CompoundFile::open(reader).map_err(|e| {
65 AltiumError::Io(std::io::Error::new(
66 std::io::ErrorKind::InvalidData,
67 e.to_string(),
68 ))
69 })?;
70
71 pcbdoc.read_board(&mut cf)?;
73
74 pcbdoc.read_components(&mut cf)?;
76
77 pcbdoc.read_primitives(&mut cf)?;
79
80 pcbdoc.read_nets(&mut cf)?;
82
83 pcbdoc.read_rules(&mut cf)?;
85
86 pcbdoc.read_classes(&mut cf)?;
88
89 pcbdoc.read_options(&mut cf)?;
91
92 Ok(pcbdoc)
93 }
94
95 pub fn open_file<P: AsRef<Path>>(path: P) -> Result<Self> {
97 let file = File::open(path)?;
98 Self::open(file)
99 }
100
101 fn read_board<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
103 let data_path = "/Board6/Data";
104
105 if cf.entry(data_path).is_err() {
106 return Ok(());
108 }
109
110 let mut stream = cf.open_stream(data_path).map_err(|e| {
111 AltiumError::Io(std::io::Error::new(
112 std::io::ErrorKind::NotFound,
113 e.to_string(),
114 ))
115 })?;
116
117 let mut data = Vec::new();
118 stream.read_to_end(&mut data)?;
119
120 if data.is_empty() {
121 return Ok(());
122 }
123
124 let mut cursor = Cursor::new(&data);
125
126 self.board_params = read_parameters_block(&mut cursor)?;
128
129 Ok(())
130 }
131
132 fn read_components<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
134 let data_path = "/Components6/Data";
135
136 if cf.entry(data_path).is_err() {
137 return Ok(());
138 }
139
140 let mut stream = cf.open_stream(data_path).map_err(|e| {
141 AltiumError::Io(std::io::Error::new(
142 std::io::ErrorKind::NotFound,
143 e.to_string(),
144 ))
145 })?;
146
147 let mut data = Vec::new();
148 stream.read_to_end(&mut data)?;
149
150 if data.is_empty() {
151 return Ok(());
152 }
153
154 let mut cursor = Cursor::new(&data);
155
156 while (cursor.position() as usize) < data.len() {
158 match self.read_component_record(&mut cursor) {
159 Ok(comp) => self.components.push(comp),
160 Err(_) => break,
161 }
162 }
163
164 Ok(())
165 }
166
167 fn read_component_record<R: Read>(&self, reader: &mut R) -> Result<PcbDocComponent> {
169 let params = read_parameters_block(reader)?;
170
171 Ok(PcbDocComponent {
172 designator: params
174 .get("SOURCEDESIGNATOR")
175 .or_else(|| params.get("DESIGNATOR"))
176 .map(|v| v.as_str().to_string())
177 .unwrap_or_default(),
178 pattern: params
179 .get("PATTERN")
180 .map(|v| v.as_str().to_string())
181 .unwrap_or_default(),
182 comment: params
183 .get("COMMENT")
184 .map(|v| v.as_str().to_string())
185 .unwrap_or_default(),
186 params,
187 primitives: Vec::new(),
188 })
189 }
190
191 fn read_primitives<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
193 use byteorder::ReadBytesExt;
194
195 self.read_primitive_storage(cf, "/Tracks6/Data", |cursor, _| {
197 let record_id = cursor.read_u8()?;
198 if record_id != PcbObjectId::Track.to_byte() {
199 return Err(AltiumError::InvalidRecord(format!(
200 "Expected Track record ID (4), got {}",
201 record_id
202 )));
203 }
204 let block = read_block(cursor)?;
205 let mut block_cursor = Cursor::new(&block);
206 <PcbTrack as FromBinary>::read_from(&mut block_cursor).map(PcbRecord::Track)
207 })?;
208
209 self.read_primitive_storage(cf, "/Arcs6/Data", |cursor, _| {
210 let record_id = cursor.read_u8()?;
211 if record_id != PcbObjectId::Arc.to_byte() {
212 return Err(AltiumError::InvalidRecord(format!(
213 "Expected Arc record ID (1), got {}",
214 record_id
215 )));
216 }
217 let block = read_block(cursor)?;
218 let mut block_cursor = Cursor::new(&block);
219 <PcbArc as FromBinary>::read_from(&mut block_cursor).map(PcbRecord::Arc)
220 })?;
221
222 self.read_primitive_storage(cf, "/Vias6/Data", |cursor, _| {
223 let record_id = cursor.read_u8()?;
224 if record_id != PcbObjectId::Via.to_byte() {
225 return Err(AltiumError::InvalidRecord(format!(
226 "Expected Via record ID (3), got {}",
227 record_id
228 )));
229 }
230 let block = read_block(cursor)?;
231 let mut block_cursor = Cursor::new(&block);
232 <PcbVia as FromBinary>::read_from(&mut block_cursor).map(PcbRecord::Via)
233 })?;
234
235 self.read_primitive_storage(cf, "/Fills6/Data", |cursor, _| {
236 let record_id = cursor.read_u8()?;
237 if record_id != PcbObjectId::Fill.to_byte() {
238 return Err(AltiumError::InvalidRecord(format!(
239 "Expected Fill record ID (6), got {}",
240 record_id
241 )));
242 }
243 let block = read_block(cursor)?;
244 let mut block_cursor = Cursor::new(&block);
245 <PcbFill as FromBinary>::read_from(&mut block_cursor).map(PcbRecord::Fill)
246 })?;
247
248 self.read_primitive_storage(cf, "/Regions6/Data", |cursor, _| {
249 let record_id = cursor.read_u8()?;
250 if record_id != PcbObjectId::Region.to_byte() {
251 return Err(AltiumError::InvalidRecord(format!(
252 "Expected Region record ID (11), got {}",
253 record_id
254 )));
255 }
256 let block = read_block(cursor)?;
257 let mut block_cursor = Cursor::new(&block);
258 <PcbRegion as FromBinary>::read_from(&mut block_cursor).map(PcbRecord::Region)
259 })?;
260
261 self.read_primitive_storage(cf, "/Polygons6/Data", |cursor, _| {
263 let record_id = cursor.read_u8()?;
264 if record_id != PcbObjectId::Polygon.to_byte() {
265 return Err(AltiumError::InvalidRecord(format!(
266 "Expected Polygon record ID (10), got {}",
267 record_id
268 )));
269 }
270 let params = read_parameters_block(cursor)?;
271 Ok(PcbRecord::Polygon(PcbPolygon::from_params(¶ms)))
272 })?;
273
274 self.read_primitive_storage(cf, "/Texts6/Data", |cursor, _| {
276 let record_id = cursor.read_u8()?;
277 if record_id != PcbObjectId::Text.to_byte() {
278 return Err(AltiumError::InvalidRecord(format!(
279 "Expected Text record ID (5), got {}",
280 record_id
281 )));
282 }
283 let block = read_block(cursor)?;
284 let mut block_cursor = Cursor::new(&block);
285 <PcbText as FromBinary>::read_from(&mut block_cursor).map(PcbRecord::Text)
286 })?;
287
288 Ok(())
289 }
290
291 fn read_primitive_storage<R, F>(
293 &mut self,
294 cf: &mut CompoundFile<R>,
295 path: &str,
296 reader_fn: F,
297 ) -> Result<()>
298 where
299 R: Read + Seek,
300 F: Fn(&mut Cursor<&Vec<u8>>, usize) -> Result<PcbRecord>,
301 {
302 if cf.entry(path).is_err() {
303 return Ok(());
304 }
305
306 let mut stream = cf.open_stream(path).map_err(|e| {
307 AltiumError::Io(std::io::Error::new(
308 std::io::ErrorKind::NotFound,
309 e.to_string(),
310 ))
311 })?;
312
313 let mut data = Vec::new();
314 stream.read_to_end(&mut data)?;
315
316 if data.is_empty() {
317 return Ok(());
318 }
319
320 let mut cursor = Cursor::new(&data);
321 let mut index = 0;
322
323 while (cursor.position() as usize) < data.len() {
324 match reader_fn(&mut cursor, index) {
325 Ok(record) => self.primitives.push(record),
326 Err(_) => break,
327 }
328 index += 1;
329 }
330
331 Ok(())
332 }
333
334 fn read_nets<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
336 let data_path = "/Nets6/Data";
337
338 if cf.entry(data_path).is_err() {
339 return Ok(());
340 }
341
342 let mut stream = cf.open_stream(data_path).map_err(|e| {
343 AltiumError::Io(std::io::Error::new(
344 std::io::ErrorKind::NotFound,
345 e.to_string(),
346 ))
347 })?;
348
349 let mut data = Vec::new();
350 stream.read_to_end(&mut data)?;
351
352 if data.is_empty() {
353 return Ok(());
354 }
355
356 let mut cursor = Cursor::new(&data);
357
358 while (cursor.position() as usize) < data.len() {
359 match read_parameters_block(&mut cursor) {
360 Ok(params) => {
361 if let Some(name) = params.get("NAME") {
362 self.nets.push(name.as_str().to_string());
363 }
364 }
365 Err(_) => break,
366 }
367 }
368
369 Ok(())
370 }
371
372 fn read_rules<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
374 let data_path = "/Rules6/Data";
375
376 if cf.entry(data_path).is_err() {
377 return Ok(());
378 }
379
380 let mut stream = cf.open_stream(data_path).map_err(|e| {
381 AltiumError::Io(std::io::Error::new(
382 std::io::ErrorKind::NotFound,
383 e.to_string(),
384 ))
385 })?;
386
387 let mut data = Vec::new();
388 stream.read_to_end(&mut data)?;
389
390 if data.is_empty() {
391 return Ok(());
392 }
393
394 let mut cursor = Cursor::new(&data);
395
396 while (cursor.position() as usize) < data.len() {
397 match PcbRule::read_from(&mut cursor) {
398 Ok(rule) => self.rules.push(rule),
399 Err(_) => break,
400 }
401 }
402
403 Ok(())
404 }
405
406 fn read_classes<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
408 let data_path = "/Classes6/Data";
409
410 if cf.entry(data_path).is_err() {
411 return Ok(());
412 }
413
414 let mut stream = cf.open_stream(data_path).map_err(|e| {
415 AltiumError::Io(std::io::Error::new(
416 std::io::ErrorKind::NotFound,
417 e.to_string(),
418 ))
419 })?;
420
421 let mut data = Vec::new();
422 stream.read_to_end(&mut data)?;
423
424 if data.is_empty() {
425 return Ok(());
426 }
427
428 let mut cursor = Cursor::new(&data);
429
430 while (cursor.position() as usize) < data.len() {
431 match read_parameters_block(&mut cursor) {
432 Ok(params) => {
433 let class = PcbClass::from_params(¶ms);
434 self.classes.push(class);
435 }
436 Err(_) => break,
437 }
438 }
439
440 Ok(())
441 }
442
443 fn read_options<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
445 if let Ok(params) = Self::read_options_stream(cf, "/Advanced Placer Options6/Data") {
447 self.placer_options = Some(PcbAdvancedPlacerOptions::from_params(¶ms));
448 }
449
450 if let Ok(params) = Self::read_options_stream(cf, "/Design Rule Checker Options6/Data") {
452 self.drc_options = Some(PcbDrcOptions::from_params(¶ms));
453 }
454
455 if let Ok(params) = Self::read_options_stream(cf, "/Pin Swap Options6/Data") {
457 self.pin_swap_options = Some(PcbPinSwapOptions::from_params(¶ms));
458 }
459
460 Ok(())
461 }
462
463 fn read_options_stream<R: Read + Seek>(
465 cf: &mut CompoundFile<R>,
466 path: &str,
467 ) -> Result<ParameterCollection> {
468 if cf.entry(path).is_err() {
469 return Err(AltiumError::Parse(format!("Stream not found: {}", path)));
470 }
471
472 let mut stream = cf.open_stream(path).map_err(|e| {
473 AltiumError::Io(std::io::Error::new(
474 std::io::ErrorKind::NotFound,
475 e.to_string(),
476 ))
477 })?;
478
479 let mut data = Vec::new();
480 stream.read_to_end(&mut data)?;
481
482 if data.is_empty() {
483 return Err(AltiumError::Parse("Empty stream".to_string()));
484 }
485
486 let mut cursor = Cursor::new(&data);
487 read_parameters_block(&mut cursor)
488 }
489
490 pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
495 let file = std::fs::OpenOptions::new()
497 .read(true)
498 .write(true)
499 .open(path.as_ref())?;
500
501 let mut cf = CompoundFile::open(file).map_err(|e| {
502 AltiumError::Io(std::io::Error::new(
503 std::io::ErrorKind::InvalidData,
504 e.to_string(),
505 ))
506 })?;
507
508 self.write_rules(&mut cf)?;
510
511 cf.flush()
512 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
513
514 Ok(())
515 }
516
517 fn write_rules<R: Read + Write + Seek>(&self, cf: &mut CompoundFile<R>) -> Result<()> {
519 let data_path = "/Rules6/Data";
520
521 let mut buffer = Vec::new();
523 for rule in &self.rules {
524 rule.write_to(&mut buffer)?;
525 }
526
527 if cf.entry(data_path).is_err() {
529 return Err(AltiumError::Parse(
532 "Rules6/Data stream not found".to_string(),
533 ));
534 }
535
536 let mut stream = cf.open_stream(data_path).map_err(|e| {
538 AltiumError::Io(std::io::Error::new(
539 std::io::ErrorKind::NotFound,
540 e.to_string(),
541 ))
542 })?;
543
544 stream.seek(SeekFrom::Start(0))?;
546 stream.write_all(&buffer)?;
547
548 let new_len = buffer.len() as u64;
551 stream
552 .set_len(new_len)
553 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
554
555 Ok(())
556 }
557
558 pub fn save_board_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
563 let file = std::fs::OpenOptions::new()
564 .read(true)
565 .write(true)
566 .open(path.as_ref())?;
567
568 let mut cf = CompoundFile::open(file).map_err(|e| {
569 AltiumError::Io(std::io::Error::new(
570 std::io::ErrorKind::InvalidData,
571 e.to_string(),
572 ))
573 })?;
574
575 self.write_board(&mut cf)?;
576
577 cf.flush()
578 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
579
580 Ok(())
581 }
582
583 fn write_board<R: Read + Write + Seek>(&self, cf: &mut CompoundFile<R>) -> Result<()> {
585 use crate::io::writer::write_parameters_block;
586
587 let data_path = "/Board6/Data";
588
589 if cf.entry(data_path).is_err() {
591 return Err(AltiumError::Parse(
592 "Board6/Data stream not found".to_string(),
593 ));
594 }
595
596 let mut buffer = Vec::new();
598 write_parameters_block(&mut buffer, &self.board_params)?;
599
600 let mut stream = cf.open_stream(data_path).map_err(|e| {
602 AltiumError::Io(std::io::Error::new(
603 std::io::ErrorKind::NotFound,
604 e.to_string(),
605 ))
606 })?;
607
608 stream.seek(SeekFrom::Start(0))?;
609 stream.write_all(&buffer)?;
610
611 let new_len = buffer.len() as u64;
612 stream
613 .set_len(new_len)
614 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
615
616 Ok(())
617 }
618
619 pub fn save_regions_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
623 use crate::io::writer::write_block;
624 use crate::traits::ToBinary;
625
626 let file = std::fs::OpenOptions::new()
627 .read(true)
628 .write(true)
629 .open(path.as_ref())?;
630
631 let mut cf = CompoundFile::open(file).map_err(|e| {
632 AltiumError::Io(std::io::Error::new(
633 std::io::ErrorKind::InvalidData,
634 e.to_string(),
635 ))
636 })?;
637
638 let data_path = "/Regions6/Data";
639
640 if cf.entry(data_path).is_err() {
642 return Err(AltiumError::Parse(
643 "Regions6/Data stream not found".to_string(),
644 ));
645 }
646
647 let mut buffer = Vec::new();
649 for prim in &self.primitives {
650 if let PcbRecord::Region(r) = prim {
651 let mut region_data = Vec::new();
652 r.write_to(&mut region_data)?;
653 write_block(&mut buffer, ®ion_data, 0)?;
654 }
655 }
656
657 let mut stream = cf.open_stream(data_path).map_err(|e| {
659 AltiumError::Io(std::io::Error::new(
660 std::io::ErrorKind::NotFound,
661 e.to_string(),
662 ))
663 })?;
664
665 stream.seek(SeekFrom::Start(0))?;
666 stream.write_all(&buffer)?;
667
668 let new_len = buffer.len() as u64;
669 stream
670 .set_len(new_len)
671 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
672
673 cf.flush()
674 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
675
676 Ok(())
677 }
678
679 pub fn save_polygons_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
683 let file = std::fs::OpenOptions::new()
684 .read(true)
685 .write(true)
686 .open(path.as_ref())?;
687
688 let mut cf = CompoundFile::open(file).map_err(|e| {
689 AltiumError::Io(std::io::Error::new(
690 std::io::ErrorKind::InvalidData,
691 e.to_string(),
692 ))
693 })?;
694
695 let data_path = "/Polygons6/Data";
696
697 if cf.entry(data_path).is_err() {
699 return Err(AltiumError::Parse(
700 "Polygons6/Data stream not found".to_string(),
701 ));
702 }
703
704 let mut buffer = Vec::new();
706 for prim in &self.primitives {
707 if let PcbRecord::Polygon(p) = prim {
708 let params = p.to_params();
709 write_parameters_block(&mut buffer, ¶ms)?;
710 }
711 }
712
713 let mut stream = cf.open_stream(data_path).map_err(|e| {
715 AltiumError::Io(std::io::Error::new(
716 std::io::ErrorKind::NotFound,
717 e.to_string(),
718 ))
719 })?;
720
721 stream.seek(SeekFrom::Start(0))?;
722 stream.write_all(&buffer)?;
723
724 let new_len = buffer.len() as u64;
725 stream
726 .set_len(new_len)
727 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
728
729 cf.flush()
730 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
731
732 Ok(())
733 }
734
735 pub fn component_count(&self) -> usize {
737 self.components.len()
738 }
739
740 pub fn primitive_count(&self) -> usize {
742 self.primitives.len()
743 }
744
745 pub fn net_count(&self) -> usize {
747 self.nets.len()
748 }
749
750 pub fn rule_count(&self) -> usize {
752 self.rules.len()
753 }
754
755 pub fn iter_rules(&self) -> impl Iterator<Item = &PcbRule> {
757 self.rules.iter()
758 }
759
760 pub fn iter_rules_mut(&mut self) -> impl Iterator<Item = &mut PcbRule> {
762 self.rules.iter_mut()
763 }
764
765 pub fn add_rule(&mut self, rule: PcbRule) {
767 self.rules.push(rule);
768 }
769
770 pub fn find_rule(&self, name: &str) -> Option<&PcbRule> {
772 self.rules.iter().find(|r| r.name == name)
773 }
774
775 pub fn find_rule_mut(&mut self, name: &str) -> Option<&mut PcbRule> {
777 self.rules.iter_mut().find(|r| r.name == name)
778 }
779
780 pub fn iter_components(&self) -> impl Iterator<Item = &PcbDocComponent> {
782 self.components.iter()
783 }
784
785 pub fn iter_primitives(&self) -> impl Iterator<Item = &PcbRecord> {
787 self.primitives.iter()
788 }
789
790 pub fn track_count(&self) -> usize {
792 self.primitives
793 .iter()
794 .filter(|p| matches!(p, PcbRecord::Track(_)))
795 .count()
796 }
797
798 pub fn via_count(&self) -> usize {
800 self.primitives
801 .iter()
802 .filter(|p| matches!(p, PcbRecord::Via(_)))
803 .count()
804 }
805
806 pub fn pad_count(&self) -> usize {
808 self.components
809 .iter()
810 .flat_map(|c| &c.primitives)
811 .filter(|p| matches!(p, PcbRecord::Pad(_)))
812 .count()
813 }
814
815 pub fn find_component(&self, designator: &str) -> Option<&PcbDocComponent> {
817 self.components
818 .iter()
819 .find(|c| c.designator.eq_ignore_ascii_case(designator))
820 }
821
822 pub fn find_component_mut(&mut self, designator: &str) -> Option<&mut PcbDocComponent> {
824 self.components
825 .iter_mut()
826 .find(|c| c.designator.eq_ignore_ascii_case(designator))
827 }
828
829 pub fn iter_components_mut(&mut self) -> impl Iterator<Item = &mut PcbDocComponent> {
831 self.components.iter_mut()
832 }
833
834 fn write_components<R: Read + Write + Seek>(&self, cf: &mut CompoundFile<R>) -> Result<()> {
836 use crate::io::writer::write_parameters_block;
837
838 let data_path = "/Components6/Data";
839
840 if cf.entry(data_path).is_err() {
842 return Err(AltiumError::Parse(
843 "Components6/Data stream not found".to_string(),
844 ));
845 }
846
847 let mut buffer = Vec::new();
849 for component in &self.components {
850 write_parameters_block(&mut buffer, &component.params)?;
851 }
852
853 let mut stream = cf.open_stream(data_path).map_err(|e| {
855 AltiumError::Io(std::io::Error::new(
856 std::io::ErrorKind::NotFound,
857 e.to_string(),
858 ))
859 })?;
860
861 stream.seek(SeekFrom::Start(0))?;
863 stream.write_all(&buffer)?;
864
865 let new_len = buffer.len() as u64;
867 stream
868 .set_len(new_len)
869 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
870
871 Ok(())
872 }
873
874 pub fn save_with_components<P: AsRef<Path>>(&self, path: P) -> Result<()> {
876 let file = std::fs::OpenOptions::new()
878 .read(true)
879 .write(true)
880 .open(path.as_ref())?;
881
882 let mut cf = CompoundFile::open(file).map_err(|e| {
883 AltiumError::Io(std::io::Error::new(
884 std::io::ErrorKind::InvalidData,
885 e.to_string(),
886 ))
887 })?;
888
889 self.write_rules(&mut cf)?;
891
892 self.write_components(&mut cf)?;
894
895 cf.flush()
896 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
897
898 Ok(())
899 }
900
901 pub fn save_all_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
913 let file = std::fs::OpenOptions::new()
914 .read(true)
915 .write(true)
916 .open(path.as_ref())?;
917
918 let mut cf = CompoundFile::open(file).map_err(|e| {
919 AltiumError::Io(std::io::Error::new(
920 std::io::ErrorKind::InvalidData,
921 e.to_string(),
922 ))
923 })?;
924
925 self.write_tracks(&mut cf)?;
927
928 self.write_vias(&mut cf)?;
930
931 self.write_arcs(&mut cf)?;
933
934 self.write_fills(&mut cf)?;
936
937 self.write_regions_internal(&mut cf)?;
939
940 self.write_polygons_internal(&mut cf)?;
942
943 self.write_pads(&mut cf)?;
945
946 self.write_texts(&mut cf)?;
948
949 self.write_rules(&mut cf)?;
951
952 self.write_components(&mut cf)?;
954
955 cf.flush()
956 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
957
958 Ok(())
959 }
960
961 fn write_tracks<R: Read + Write + Seek>(&self, cf: &mut CompoundFile<R>) -> Result<()> {
963 use crate::io::writer::write_block;
964 use crate::traits::ToBinary;
965 use byteorder::WriteBytesExt;
966
967 let data_path = "/Tracks6/Data";
968
969 if cf.entry(data_path).is_err() {
970 return Ok(()); }
972
973 let mut buffer = Vec::new();
974 for prim in &self.primitives {
975 if let PcbRecord::Track(track) = prim {
976 buffer.write_u8(PcbObjectId::Track.to_byte())?;
978 let mut track_data = Vec::new();
980 track.write_to(&mut track_data)?;
981 write_block(&mut buffer, &track_data, 0)?;
982 }
983 }
984
985 let mut stream = cf.open_stream(data_path).map_err(|e| {
986 AltiumError::Io(std::io::Error::new(
987 std::io::ErrorKind::NotFound,
988 e.to_string(),
989 ))
990 })?;
991
992 stream.seek(SeekFrom::Start(0))?;
993 stream.write_all(&buffer)?;
994 stream
995 .set_len(buffer.len() as u64)
996 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
997
998 Ok(())
999 }
1000
1001 fn write_vias<R: Read + Write + Seek>(&self, cf: &mut CompoundFile<R>) -> Result<()> {
1003 use crate::io::writer::write_block;
1004 use crate::traits::ToBinary;
1005 use byteorder::WriteBytesExt;
1006
1007 let data_path = "/Vias6/Data";
1008
1009 if cf.entry(data_path).is_err() {
1010 return Ok(());
1011 }
1012
1013 let mut buffer = Vec::new();
1014 for prim in &self.primitives {
1015 if let PcbRecord::Via(via) = prim {
1016 buffer.write_u8(PcbObjectId::Via.to_byte())?;
1018 let mut via_data = Vec::new();
1020 via.write_to(&mut via_data)?;
1021 write_block(&mut buffer, &via_data, 0)?;
1022 }
1023 }
1024
1025 let mut stream = cf.open_stream(data_path).map_err(|e| {
1026 AltiumError::Io(std::io::Error::new(
1027 std::io::ErrorKind::NotFound,
1028 e.to_string(),
1029 ))
1030 })?;
1031
1032 stream.seek(SeekFrom::Start(0))?;
1033 stream.write_all(&buffer)?;
1034 stream
1035 .set_len(buffer.len() as u64)
1036 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
1037
1038 Ok(())
1039 }
1040
1041 fn write_arcs<R: Read + Write + Seek>(&self, cf: &mut CompoundFile<R>) -> Result<()> {
1043 use crate::io::writer::write_block;
1044 use crate::traits::ToBinary;
1045 use byteorder::WriteBytesExt;
1046
1047 let data_path = "/Arcs6/Data";
1048
1049 if cf.entry(data_path).is_err() {
1050 return Ok(());
1051 }
1052
1053 let mut buffer = Vec::new();
1054 for prim in &self.primitives {
1055 if let PcbRecord::Arc(arc) = prim {
1056 buffer.write_u8(PcbObjectId::Arc.to_byte())?;
1058 let mut arc_data = Vec::new();
1060 arc.write_to(&mut arc_data)?;
1061 write_block(&mut buffer, &arc_data, 0)?;
1062 }
1063 }
1064
1065 let mut stream = cf.open_stream(data_path).map_err(|e| {
1066 AltiumError::Io(std::io::Error::new(
1067 std::io::ErrorKind::NotFound,
1068 e.to_string(),
1069 ))
1070 })?;
1071
1072 stream.seek(SeekFrom::Start(0))?;
1073 stream.write_all(&buffer)?;
1074 stream
1075 .set_len(buffer.len() as u64)
1076 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
1077
1078 Ok(())
1079 }
1080
1081 fn write_fills<R: Read + Write + Seek>(&self, cf: &mut CompoundFile<R>) -> Result<()> {
1083 use crate::io::writer::write_block;
1084 use crate::traits::ToBinary;
1085 use byteorder::WriteBytesExt;
1086
1087 let data_path = "/Fills6/Data";
1088
1089 if cf.entry(data_path).is_err() {
1090 return Ok(());
1091 }
1092
1093 let mut buffer = Vec::new();
1094 for prim in &self.primitives {
1095 if let PcbRecord::Fill(fill) = prim {
1096 buffer.write_u8(PcbObjectId::Fill.to_byte())?;
1098 let mut fill_data = Vec::new();
1100 fill.write_to(&mut fill_data)?;
1101 write_block(&mut buffer, &fill_data, 0)?;
1102 }
1103 }
1104
1105 let mut stream = cf.open_stream(data_path).map_err(|e| {
1106 AltiumError::Io(std::io::Error::new(
1107 std::io::ErrorKind::NotFound,
1108 e.to_string(),
1109 ))
1110 })?;
1111
1112 stream.seek(SeekFrom::Start(0))?;
1113 stream.write_all(&buffer)?;
1114 stream
1115 .set_len(buffer.len() as u64)
1116 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
1117
1118 Ok(())
1119 }
1120
1121 fn write_pads<R: Read + Write + Seek>(&self, cf: &mut CompoundFile<R>) -> Result<()> {
1123 use crate::io::writer::write_block;
1124 use crate::traits::ToBinary;
1125 use byteorder::WriteBytesExt;
1126
1127 let data_path = "/Pads6/Data";
1128
1129 if cf.entry(data_path).is_err() {
1130 return Ok(());
1131 }
1132
1133 let mut buffer = Vec::new();
1134 for prim in &self.primitives {
1135 if let PcbRecord::Pad(pad) = prim {
1136 buffer.write_u8(PcbObjectId::Pad.to_byte())?;
1138 let mut pad_data = Vec::new();
1140 pad.write_to(&mut pad_data)?;
1141 write_block(&mut buffer, &pad_data, 0)?;
1142 }
1143 }
1144
1145 let mut stream = cf.open_stream(data_path).map_err(|e| {
1146 AltiumError::Io(std::io::Error::new(
1147 std::io::ErrorKind::NotFound,
1148 e.to_string(),
1149 ))
1150 })?;
1151
1152 stream.seek(SeekFrom::Start(0))?;
1153 stream.write_all(&buffer)?;
1154 stream
1155 .set_len(buffer.len() as u64)
1156 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
1157
1158 Ok(())
1159 }
1160
1161 fn write_texts<R: Read + Write + Seek>(&self, cf: &mut CompoundFile<R>) -> Result<()> {
1163 use crate::io::writer::write_block;
1164 use crate::traits::ToBinary;
1165 use byteorder::WriteBytesExt;
1166
1167 let data_path = "/Texts6/Data";
1168
1169 if cf.entry(data_path).is_err() {
1170 return Ok(());
1171 }
1172
1173 let mut buffer = Vec::new();
1174 for prim in &self.primitives {
1175 if let PcbRecord::Text(text) = prim {
1176 buffer.write_u8(PcbObjectId::Text.to_byte())?;
1178 let mut text_data = Vec::new();
1180 text.write_to(&mut text_data)?;
1181 write_block(&mut buffer, &text_data, 0)?;
1182 }
1183 }
1184
1185 let mut stream = cf.open_stream(data_path).map_err(|e| {
1186 AltiumError::Io(std::io::Error::new(
1187 std::io::ErrorKind::NotFound,
1188 e.to_string(),
1189 ))
1190 })?;
1191
1192 stream.seek(SeekFrom::Start(0))?;
1193 stream.write_all(&buffer)?;
1194 stream
1195 .set_len(buffer.len() as u64)
1196 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
1197
1198 Ok(())
1199 }
1200
1201 fn write_regions_internal<R: Read + Write + Seek>(
1203 &self,
1204 cf: &mut CompoundFile<R>,
1205 ) -> Result<()> {
1206 use crate::io::writer::write_block;
1207 use crate::traits::ToBinary;
1208 use byteorder::WriteBytesExt;
1209
1210 let data_path = "/Regions6/Data";
1211
1212 if cf.entry(data_path).is_err() {
1213 return Ok(());
1214 }
1215
1216 let mut buffer = Vec::new();
1217 for prim in &self.primitives {
1218 if let PcbRecord::Region(region) = prim {
1219 buffer.write_u8(PcbObjectId::Region.to_byte())?;
1221 let mut region_data = Vec::new();
1223 region.write_to(&mut region_data)?;
1224 write_block(&mut buffer, ®ion_data, 0)?;
1225 }
1226 }
1227
1228 let mut stream = cf.open_stream(data_path).map_err(|e| {
1229 AltiumError::Io(std::io::Error::new(
1230 std::io::ErrorKind::NotFound,
1231 e.to_string(),
1232 ))
1233 })?;
1234
1235 stream.seek(SeekFrom::Start(0))?;
1236 stream.write_all(&buffer)?;
1237 stream
1238 .set_len(buffer.len() as u64)
1239 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
1240
1241 Ok(())
1242 }
1243
1244 fn write_polygons_internal<R: Read + Write + Seek>(
1246 &self,
1247 cf: &mut CompoundFile<R>,
1248 ) -> Result<()> {
1249 let data_path = "/Polygons6/Data";
1250
1251 if cf.entry(data_path).is_err() {
1252 return Ok(());
1253 }
1254
1255 let mut buffer = Vec::new();
1256 for prim in &self.primitives {
1257 if let PcbRecord::Polygon(polygon) = prim {
1258 let params = polygon.to_params();
1259 write_parameters_block(&mut buffer, ¶ms)?;
1260 }
1261 }
1262
1263 let mut stream = cf.open_stream(data_path).map_err(|e| {
1264 AltiumError::Io(std::io::Error::new(
1265 std::io::ErrorKind::NotFound,
1266 e.to_string(),
1267 ))
1268 })?;
1269
1270 stream.seek(SeekFrom::Start(0))?;
1271 stream.write_all(&buffer)?;
1272 stream
1273 .set_len(buffer.len() as u64)
1274 .map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
1275
1276 Ok(())
1277 }
1278
1279 pub fn arc_count(&self) -> usize {
1281 self.primitives
1282 .iter()
1283 .filter(|p| matches!(p, PcbRecord::Arc(_)))
1284 .count()
1285 }
1286
1287 pub fn fill_count(&self) -> usize {
1289 self.primitives
1290 .iter()
1291 .filter(|p| matches!(p, PcbRecord::Fill(_)))
1292 .count()
1293 }
1294
1295 pub fn region_count(&self) -> usize {
1297 self.primitives
1298 .iter()
1299 .filter(|p| matches!(p, PcbRecord::Region(_)))
1300 .count()
1301 }
1302
1303 pub fn polygon_count(&self) -> usize {
1305 self.primitives
1306 .iter()
1307 .filter(|p| matches!(p, PcbRecord::Polygon(_)))
1308 .count()
1309 }
1310
1311 pub fn text_count(&self) -> usize {
1313 self.primitives
1314 .iter()
1315 .filter(|p| matches!(p, PcbRecord::Text(_)))
1316 .count()
1317 }
1318
1319 pub fn add_track(&mut self, track: PcbTrack) {
1321 self.primitives.push(PcbRecord::Track(track));
1322 }
1323
1324 pub fn add_via(&mut self, via: PcbVia) {
1326 self.primitives.push(PcbRecord::Via(via));
1327 }
1328
1329 pub fn add_arc(&mut self, arc: PcbArc) {
1331 self.primitives.push(PcbRecord::Arc(arc));
1332 }
1333
1334 pub fn add_fill(&mut self, fill: PcbFill) {
1336 self.primitives.push(PcbRecord::Fill(fill));
1337 }
1338
1339 pub fn add_region(&mut self, region: PcbRegion) {
1341 self.primitives.push(PcbRecord::Region(region));
1342 }
1343
1344 pub fn add_polygon(&mut self, polygon: PcbPolygon) {
1346 self.primitives.push(PcbRecord::Polygon(polygon));
1347 }
1348
1349 pub fn remove_primitive(&mut self, index: usize) -> Option<PcbRecord> {
1351 if index < self.primitives.len() {
1352 Some(self.primitives.remove(index))
1353 } else {
1354 None
1355 }
1356 }
1357
1358 pub fn get_primitive(&self, index: usize) -> Option<&PcbRecord> {
1360 self.primitives.get(index)
1361 }
1362
1363 pub fn get_primitive_mut(&mut self, index: usize) -> Option<&mut PcbRecord> {
1365 self.primitives.get_mut(index)
1366 }
1367
1368 pub fn iter_tracks(&self) -> impl Iterator<Item = &PcbTrack> {
1370 self.primitives.iter().filter_map(|p| {
1371 if let PcbRecord::Track(t) = p {
1372 Some(t)
1373 } else {
1374 None
1375 }
1376 })
1377 }
1378
1379 pub fn iter_vias(&self) -> impl Iterator<Item = &PcbVia> {
1381 self.primitives.iter().filter_map(|p| {
1382 if let PcbRecord::Via(v) = p {
1383 Some(v)
1384 } else {
1385 None
1386 }
1387 })
1388 }
1389
1390 pub fn iter_arcs(&self) -> impl Iterator<Item = &PcbArc> {
1392 self.primitives.iter().filter_map(|p| {
1393 if let PcbRecord::Arc(a) = p {
1394 Some(a)
1395 } else {
1396 None
1397 }
1398 })
1399 }
1400
1401 pub fn iter_fills(&self) -> impl Iterator<Item = &PcbFill> {
1403 self.primitives.iter().filter_map(|p| {
1404 if let PcbRecord::Fill(f) = p {
1405 Some(f)
1406 } else {
1407 None
1408 }
1409 })
1410 }
1411
1412 pub fn iter_regions(&self) -> impl Iterator<Item = &PcbRegion> {
1414 self.primitives.iter().filter_map(|p| {
1415 if let PcbRecord::Region(r) = p {
1416 Some(r)
1417 } else {
1418 None
1419 }
1420 })
1421 }
1422
1423 pub fn iter_polygons(&self) -> impl Iterator<Item = &PcbPolygon> {
1425 self.primitives.iter().filter_map(|p| {
1426 if let PcbRecord::Polygon(pol) = p {
1427 Some(pol)
1428 } else {
1429 None
1430 }
1431 })
1432 }
1433
1434 pub fn iter_texts(&self) -> impl Iterator<Item = &PcbText> {
1436 self.primitives.iter().filter_map(|p| {
1437 if let PcbRecord::Text(t) = p {
1438 Some(t)
1439 } else {
1440 None
1441 }
1442 })
1443 }
1444
1445 pub fn add_text(&mut self, text: PcbText) {
1447 self.primitives.push(PcbRecord::Text(text));
1448 }
1449}
1450
1451impl PcbDocComponent {
1452 pub fn x(&self) -> Option<crate::types::Coord> {
1454 self.params
1455 .get("X")
1456 .map(|v| v.as_coord_or(crate::types::Coord::ZERO))
1457 }
1458
1459 pub fn y(&self) -> Option<crate::types::Coord> {
1461 self.params
1462 .get("Y")
1463 .map(|v| v.as_coord_or(crate::types::Coord::ZERO))
1464 }
1465
1466 pub fn rotation(&self) -> f64 {
1468 self.params
1469 .get("ROTATION")
1470 .and_then(|v| v.as_str().trim().parse::<f64>().ok())
1471 .unwrap_or(0.0)
1472 }
1473
1474 pub fn layer(&self) -> crate::types::Layer {
1476 self.params
1477 .get("LAYER")
1478 .and_then(|v| {
1479 let layer_str = v.as_str();
1480 crate::types::Layer::from_name(layer_str).or_else(|| {
1482 match layer_str.to_uppercase().as_str() {
1484 "TOP" => Some(crate::types::Layer::TOP_LAYER),
1485 "BOTTOM" => Some(crate::types::Layer::BOTTOM_LAYER),
1486 "TOPOVERLAY" | "TOP_OVERLAY" => Some(crate::types::Layer::TOP_OVERLAY),
1487 "BOTTOMOVERLAY" | "BOTTOM_OVERLAY" => {
1488 Some(crate::types::Layer::BOTTOM_OVERLAY)
1489 }
1490 _ => None,
1491 }
1492 })
1493 })
1494 .unwrap_or(crate::types::Layer::TOP_LAYER)
1495 }
1496
1497 pub fn set_x(&mut self, x: crate::types::Coord) {
1499 self.params.add_coord("X", x);
1500 }
1501
1502 pub fn set_y(&mut self, y: crate::types::Coord) {
1504 self.params.add_coord("Y", y);
1505 }
1506
1507 pub fn set_position(&mut self, x: crate::types::Coord, y: crate::types::Coord) {
1509 self.set_x(x);
1510 self.set_y(y);
1511 }
1512
1513 pub fn set_rotation(&mut self, rotation: f64) {
1515 self.params.add("ROTATION", &format!("{:.14E}", rotation));
1517 }
1518
1519 pub fn set_layer(&mut self, layer: crate::types::Layer) {
1521 self.params.add("LAYER", layer.name());
1522 }
1523}
1524
1525impl DumpTree for PcbDoc {
1526 fn dump(&self, tree: &mut TreeBuilder) {
1527 tree.root(&format!(
1528 "PcbDoc ({} components, {} primitives, {} rules)",
1529 self.components.len(),
1530 self.primitives.len(),
1531 self.rules.len()
1532 ));
1533
1534 tree.push(
1536 !self.components.is_empty() || !self.primitives.is_empty() || !self.rules.is_empty(),
1537 );
1538 tree.add_leaf(
1539 "Summary",
1540 &[
1541 ("components", format!("{}", self.components.len())),
1542 ("tracks", format!("{}", self.track_count())),
1543 ("vias", format!("{}", self.via_count())),
1544 ("nets", format!("{}", self.nets.len())),
1545 ("rules", format!("{}", self.rules.len())),
1546 ("primitives", format!("{}", self.primitives.len())),
1547 ],
1548 );
1549 tree.pop();
1550
1551 if !self.components.is_empty() {
1553 tree.push(!self.primitives.is_empty());
1554 tree.begin_node(&format!("Components ({})", self.components.len()));
1555 for (i, comp) in self.components.iter().enumerate() {
1556 tree.push(i < self.components.len() - 1);
1557 comp.dump(tree);
1558 tree.pop();
1559 }
1560 tree.pop();
1561 }
1562
1563 if !self.nets.is_empty() {
1565 tree.push(false);
1566 tree.add_leaf(
1567 &format!("Nets ({})", self.nets.len()),
1568 &[(
1569 "first_few",
1570 self.nets
1571 .iter()
1572 .take(5)
1573 .cloned()
1574 .collect::<Vec<_>>()
1575 .join(", "),
1576 )],
1577 );
1578 tree.pop();
1579 }
1580 }
1581}
1582
1583impl DumpTree for PcbDocComponent {
1584 fn dump(&self, tree: &mut TreeBuilder) {
1585 let mut props = vec![("designator", self.designator.clone())];
1586 if !self.pattern.is_empty() {
1587 props.push(("pattern", self.pattern.clone()));
1588 }
1589 if !self.comment.is_empty() {
1590 props.push(("comment", self.comment.clone()));
1591 }
1592 tree.add_leaf_with_params("Component", &props, Some(&self.params));
1593 }
1594}
1595
1596#[cfg(test)]
1597mod tests {
1598 use super::*;
1599 use std::io::Cursor;
1600
1601 #[test]
1602 fn test_read_classes_and_options() {
1603 let data = std::fs::read("data/PCB1.PcbDoc").expect("Failed to read file");
1604 let pcbdoc = PcbDoc::open(Cursor::new(&data)).expect("Failed to parse PcbDoc");
1605
1606 assert!(!pcbdoc.classes.is_empty(), "Should have parsed classes");
1608 println!("Classes: {}", pcbdoc.classes.len());
1609 for class in &pcbdoc.classes {
1610 println!(" - {} ({:?})", class.name, class.kind);
1611 }
1612
1613 assert!(
1615 pcbdoc.placer_options.is_some(),
1616 "Should have placer options"
1617 );
1618 let opts = pcbdoc.placer_options.as_ref().unwrap();
1619 assert!(opts.use_rotation); assert!(pcbdoc.drc_options.is_some(), "Should have DRC options");
1623
1624 assert!(
1626 pcbdoc.pin_swap_options.is_some(),
1627 "Should have pin swap options"
1628 );
1629
1630 assert!(!pcbdoc.rules.is_empty(), "Should have rules");
1632 println!("Rules: {}", pcbdoc.rules.len());
1633 }
1634}