use cfb::CompoundFile;
use std::fs::File;
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use std::path::Path;
use crate::dump::{DumpTree, TreeBuilder};
use crate::error::{AltiumError, Result};
use crate::io::reader::{read_block, read_parameters_block};
use crate::io::writer::write_parameters_block;
use crate::records::pcb::{
PcbAdvancedPlacerOptions, PcbArc, PcbClass, PcbDrcOptions, PcbFill, PcbObjectId,
PcbPinSwapOptions, PcbPolygon, PcbRecord, PcbRegion, PcbRule, PcbText, PcbTrack, PcbVia,
};
use crate::traits::FromBinary;
use crate::types::ParameterCollection;
#[derive(Debug, Default)]
pub struct PcbDoc {
pub board_params: ParameterCollection,
pub components: Vec<PcbDocComponent>,
pub primitives: Vec<PcbRecord>,
pub nets: Vec<String>,
pub rules: Vec<PcbRule>,
pub classes: Vec<PcbClass>,
pub placer_options: Option<PcbAdvancedPlacerOptions>,
pub drc_options: Option<PcbDrcOptions>,
pub pin_swap_options: Option<PcbPinSwapOptions>,
}
#[derive(Debug, Default)]
pub struct PcbDocComponent {
pub designator: String,
pub pattern: String,
pub comment: String,
pub params: ParameterCollection,
pub primitives: Vec<PcbRecord>,
}
impl PcbDoc {
pub fn open<R: Read + Seek>(reader: R) -> Result<Self> {
let mut pcbdoc = PcbDoc::default();
let mut cf = CompoundFile::open(reader).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
e.to_string(),
))
})?;
pcbdoc.read_board(&mut cf)?;
pcbdoc.read_components(&mut cf)?;
pcbdoc.read_primitives(&mut cf)?;
pcbdoc.read_nets(&mut cf)?;
pcbdoc.read_rules(&mut cf)?;
pcbdoc.read_classes(&mut cf)?;
pcbdoc.read_options(&mut cf)?;
Ok(pcbdoc)
}
pub fn open_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let file = File::open(path)?;
Self::open(file)
}
fn read_board<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
let data_path = "/Board6/Data";
if cf.entry(data_path).is_err() {
return Ok(());
}
let mut stream = cf.open_stream(data_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
let mut data = Vec::new();
stream.read_to_end(&mut data)?;
if data.is_empty() {
return Ok(());
}
let mut cursor = Cursor::new(&data);
self.board_params = read_parameters_block(&mut cursor)?;
Ok(())
}
fn read_components<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
let data_path = "/Components6/Data";
if cf.entry(data_path).is_err() {
return Ok(());
}
let mut stream = cf.open_stream(data_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
let mut data = Vec::new();
stream.read_to_end(&mut data)?;
if data.is_empty() {
return Ok(());
}
let mut cursor = Cursor::new(&data);
while (cursor.position() as usize) < data.len() {
match self.read_component_record(&mut cursor) {
Ok(comp) => self.components.push(comp),
Err(_) => break,
}
}
Ok(())
}
fn read_component_record<R: Read>(&self, reader: &mut R) -> Result<PcbDocComponent> {
let params = read_parameters_block(reader)?;
Ok(PcbDocComponent {
designator: params
.get("SOURCEDESIGNATOR")
.or_else(|| params.get("DESIGNATOR"))
.map(|v| v.as_str().to_string())
.unwrap_or_default(),
pattern: params
.get("PATTERN")
.map(|v| v.as_str().to_string())
.unwrap_or_default(),
comment: params
.get("COMMENT")
.map(|v| v.as_str().to_string())
.unwrap_or_default(),
params,
primitives: Vec::new(),
})
}
fn read_primitives<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
use byteorder::ReadBytesExt;
self.read_primitive_storage(cf, "/Tracks6/Data", |cursor, _| {
let record_id = cursor.read_u8()?;
if record_id != PcbObjectId::Track.to_byte() {
return Err(AltiumError::InvalidRecord(format!(
"Expected Track record ID (4), got {}",
record_id
)));
}
let block = read_block(cursor)?;
let mut block_cursor = Cursor::new(&block);
<PcbTrack as FromBinary>::read_from(&mut block_cursor).map(PcbRecord::Track)
})?;
self.read_primitive_storage(cf, "/Arcs6/Data", |cursor, _| {
let record_id = cursor.read_u8()?;
if record_id != PcbObjectId::Arc.to_byte() {
return Err(AltiumError::InvalidRecord(format!(
"Expected Arc record ID (1), got {}",
record_id
)));
}
let block = read_block(cursor)?;
let mut block_cursor = Cursor::new(&block);
<PcbArc as FromBinary>::read_from(&mut block_cursor).map(PcbRecord::Arc)
})?;
self.read_primitive_storage(cf, "/Vias6/Data", |cursor, _| {
let record_id = cursor.read_u8()?;
if record_id != PcbObjectId::Via.to_byte() {
return Err(AltiumError::InvalidRecord(format!(
"Expected Via record ID (3), got {}",
record_id
)));
}
let block = read_block(cursor)?;
let mut block_cursor = Cursor::new(&block);
<PcbVia as FromBinary>::read_from(&mut block_cursor).map(PcbRecord::Via)
})?;
self.read_primitive_storage(cf, "/Fills6/Data", |cursor, _| {
let record_id = cursor.read_u8()?;
if record_id != PcbObjectId::Fill.to_byte() {
return Err(AltiumError::InvalidRecord(format!(
"Expected Fill record ID (6), got {}",
record_id
)));
}
let block = read_block(cursor)?;
let mut block_cursor = Cursor::new(&block);
<PcbFill as FromBinary>::read_from(&mut block_cursor).map(PcbRecord::Fill)
})?;
self.read_primitive_storage(cf, "/Regions6/Data", |cursor, _| {
let record_id = cursor.read_u8()?;
if record_id != PcbObjectId::Region.to_byte() {
return Err(AltiumError::InvalidRecord(format!(
"Expected Region record ID (11), got {}",
record_id
)));
}
let block = read_block(cursor)?;
let mut block_cursor = Cursor::new(&block);
<PcbRegion as FromBinary>::read_from(&mut block_cursor).map(PcbRecord::Region)
})?;
self.read_primitive_storage(cf, "/Polygons6/Data", |cursor, _| {
let record_id = cursor.read_u8()?;
if record_id != PcbObjectId::Polygon.to_byte() {
return Err(AltiumError::InvalidRecord(format!(
"Expected Polygon record ID (10), got {}",
record_id
)));
}
let params = read_parameters_block(cursor)?;
Ok(PcbRecord::Polygon(PcbPolygon::from_params(¶ms)))
})?;
self.read_primitive_storage(cf, "/Texts6/Data", |cursor, _| {
let record_id = cursor.read_u8()?;
if record_id != PcbObjectId::Text.to_byte() {
return Err(AltiumError::InvalidRecord(format!(
"Expected Text record ID (5), got {}",
record_id
)));
}
let block = read_block(cursor)?;
let mut block_cursor = Cursor::new(&block);
<PcbText as FromBinary>::read_from(&mut block_cursor).map(PcbRecord::Text)
})?;
Ok(())
}
fn read_primitive_storage<R, F>(
&mut self,
cf: &mut CompoundFile<R>,
path: &str,
reader_fn: F,
) -> Result<()>
where
R: Read + Seek,
F: Fn(&mut Cursor<&Vec<u8>>, usize) -> Result<PcbRecord>,
{
if cf.entry(path).is_err() {
return Ok(());
}
let mut stream = cf.open_stream(path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
let mut data = Vec::new();
stream.read_to_end(&mut data)?;
if data.is_empty() {
return Ok(());
}
let mut cursor = Cursor::new(&data);
let mut index = 0;
while (cursor.position() as usize) < data.len() {
match reader_fn(&mut cursor, index) {
Ok(record) => self.primitives.push(record),
Err(_) => break,
}
index += 1;
}
Ok(())
}
fn read_nets<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
let data_path = "/Nets6/Data";
if cf.entry(data_path).is_err() {
return Ok(());
}
let mut stream = cf.open_stream(data_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
let mut data = Vec::new();
stream.read_to_end(&mut data)?;
if data.is_empty() {
return Ok(());
}
let mut cursor = Cursor::new(&data);
while (cursor.position() as usize) < data.len() {
match read_parameters_block(&mut cursor) {
Ok(params) => {
if let Some(name) = params.get("NAME") {
self.nets.push(name.as_str().to_string());
}
}
Err(_) => break,
}
}
Ok(())
}
fn read_rules<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
let data_path = "/Rules6/Data";
if cf.entry(data_path).is_err() {
return Ok(());
}
let mut stream = cf.open_stream(data_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
let mut data = Vec::new();
stream.read_to_end(&mut data)?;
if data.is_empty() {
return Ok(());
}
let mut cursor = Cursor::new(&data);
while (cursor.position() as usize) < data.len() {
match PcbRule::read_from(&mut cursor) {
Ok(rule) => self.rules.push(rule),
Err(_) => break,
}
}
Ok(())
}
fn read_classes<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
let data_path = "/Classes6/Data";
if cf.entry(data_path).is_err() {
return Ok(());
}
let mut stream = cf.open_stream(data_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
let mut data = Vec::new();
stream.read_to_end(&mut data)?;
if data.is_empty() {
return Ok(());
}
let mut cursor = Cursor::new(&data);
while (cursor.position() as usize) < data.len() {
match read_parameters_block(&mut cursor) {
Ok(params) => {
let class = PcbClass::from_params(¶ms);
self.classes.push(class);
}
Err(_) => break,
}
}
Ok(())
}
fn read_options<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
if let Ok(params) = Self::read_options_stream(cf, "/Advanced Placer Options6/Data") {
self.placer_options = Some(PcbAdvancedPlacerOptions::from_params(¶ms));
}
if let Ok(params) = Self::read_options_stream(cf, "/Design Rule Checker Options6/Data") {
self.drc_options = Some(PcbDrcOptions::from_params(¶ms));
}
if let Ok(params) = Self::read_options_stream(cf, "/Pin Swap Options6/Data") {
self.pin_swap_options = Some(PcbPinSwapOptions::from_params(¶ms));
}
Ok(())
}
fn read_options_stream<R: Read + Seek>(
cf: &mut CompoundFile<R>,
path: &str,
) -> Result<ParameterCollection> {
if cf.entry(path).is_err() {
return Err(AltiumError::Parse(format!("Stream not found: {}", path)));
}
let mut stream = cf.open_stream(path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
let mut data = Vec::new();
stream.read_to_end(&mut data)?;
if data.is_empty() {
return Err(AltiumError::Parse("Empty stream".to_string()));
}
let mut cursor = Cursor::new(&data);
read_parameters_block(&mut cursor)
}
pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(path.as_ref())?;
let mut cf = CompoundFile::open(file).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
e.to_string(),
))
})?;
self.write_rules(&mut cf)?;
cf.flush()
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
Ok(())
}
fn write_rules<R: Read + Write + Seek>(&self, cf: &mut CompoundFile<R>) -> Result<()> {
let data_path = "/Rules6/Data";
let mut buffer = Vec::new();
for rule in &self.rules {
rule.write_to(&mut buffer)?;
}
if cf.entry(data_path).is_err() {
return Err(AltiumError::Parse(
"Rules6/Data stream not found".to_string(),
));
}
let mut stream = cf.open_stream(data_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
stream.seek(SeekFrom::Start(0))?;
stream.write_all(&buffer)?;
let new_len = buffer.len() as u64;
stream
.set_len(new_len)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
Ok(())
}
pub fn save_board_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(path.as_ref())?;
let mut cf = CompoundFile::open(file).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
e.to_string(),
))
})?;
self.write_board(&mut cf)?;
cf.flush()
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
Ok(())
}
fn write_board<R: Read + Write + Seek>(&self, cf: &mut CompoundFile<R>) -> Result<()> {
use crate::io::writer::write_parameters_block;
let data_path = "/Board6/Data";
if cf.entry(data_path).is_err() {
return Err(AltiumError::Parse(
"Board6/Data stream not found".to_string(),
));
}
let mut buffer = Vec::new();
write_parameters_block(&mut buffer, &self.board_params)?;
let mut stream = cf.open_stream(data_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
stream.seek(SeekFrom::Start(0))?;
stream.write_all(&buffer)?;
let new_len = buffer.len() as u64;
stream
.set_len(new_len)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
Ok(())
}
pub fn save_regions_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
use crate::io::writer::write_block;
use crate::traits::ToBinary;
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(path.as_ref())?;
let mut cf = CompoundFile::open(file).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
e.to_string(),
))
})?;
let data_path = "/Regions6/Data";
if cf.entry(data_path).is_err() {
return Err(AltiumError::Parse(
"Regions6/Data stream not found".to_string(),
));
}
let mut buffer = Vec::new();
for prim in &self.primitives {
if let PcbRecord::Region(r) = prim {
let mut region_data = Vec::new();
r.write_to(&mut region_data)?;
write_block(&mut buffer, ®ion_data, 0)?;
}
}
let mut stream = cf.open_stream(data_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
stream.seek(SeekFrom::Start(0))?;
stream.write_all(&buffer)?;
let new_len = buffer.len() as u64;
stream
.set_len(new_len)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
cf.flush()
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
Ok(())
}
pub fn save_polygons_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(path.as_ref())?;
let mut cf = CompoundFile::open(file).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
e.to_string(),
))
})?;
let data_path = "/Polygons6/Data";
if cf.entry(data_path).is_err() {
return Err(AltiumError::Parse(
"Polygons6/Data stream not found".to_string(),
));
}
let mut buffer = Vec::new();
for prim in &self.primitives {
if let PcbRecord::Polygon(p) = prim {
let params = p.to_params();
write_parameters_block(&mut buffer, ¶ms)?;
}
}
let mut stream = cf.open_stream(data_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
stream.seek(SeekFrom::Start(0))?;
stream.write_all(&buffer)?;
let new_len = buffer.len() as u64;
stream
.set_len(new_len)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
cf.flush()
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
Ok(())
}
pub fn component_count(&self) -> usize {
self.components.len()
}
pub fn primitive_count(&self) -> usize {
self.primitives.len()
}
pub fn net_count(&self) -> usize {
self.nets.len()
}
pub fn rule_count(&self) -> usize {
self.rules.len()
}
pub fn iter_rules(&self) -> impl Iterator<Item = &PcbRule> {
self.rules.iter()
}
pub fn iter_rules_mut(&mut self) -> impl Iterator<Item = &mut PcbRule> {
self.rules.iter_mut()
}
pub fn add_rule(&mut self, rule: PcbRule) {
self.rules.push(rule);
}
pub fn find_rule(&self, name: &str) -> Option<&PcbRule> {
self.rules.iter().find(|r| r.name == name)
}
pub fn find_rule_mut(&mut self, name: &str) -> Option<&mut PcbRule> {
self.rules.iter_mut().find(|r| r.name == name)
}
pub fn iter_components(&self) -> impl Iterator<Item = &PcbDocComponent> {
self.components.iter()
}
pub fn iter_primitives(&self) -> impl Iterator<Item = &PcbRecord> {
self.primitives.iter()
}
pub fn track_count(&self) -> usize {
self.primitives
.iter()
.filter(|p| matches!(p, PcbRecord::Track(_)))
.count()
}
pub fn via_count(&self) -> usize {
self.primitives
.iter()
.filter(|p| matches!(p, PcbRecord::Via(_)))
.count()
}
pub fn pad_count(&self) -> usize {
self.components
.iter()
.flat_map(|c| &c.primitives)
.filter(|p| matches!(p, PcbRecord::Pad(_)))
.count()
}
pub fn find_component(&self, designator: &str) -> Option<&PcbDocComponent> {
self.components
.iter()
.find(|c| c.designator.eq_ignore_ascii_case(designator))
}
pub fn find_component_mut(&mut self, designator: &str) -> Option<&mut PcbDocComponent> {
self.components
.iter_mut()
.find(|c| c.designator.eq_ignore_ascii_case(designator))
}
pub fn iter_components_mut(&mut self) -> impl Iterator<Item = &mut PcbDocComponent> {
self.components.iter_mut()
}
fn write_components<R: Read + Write + Seek>(&self, cf: &mut CompoundFile<R>) -> Result<()> {
use crate::io::writer::write_parameters_block;
let data_path = "/Components6/Data";
if cf.entry(data_path).is_err() {
return Err(AltiumError::Parse(
"Components6/Data stream not found".to_string(),
));
}
let mut buffer = Vec::new();
for component in &self.components {
write_parameters_block(&mut buffer, &component.params)?;
}
let mut stream = cf.open_stream(data_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
stream.seek(SeekFrom::Start(0))?;
stream.write_all(&buffer)?;
let new_len = buffer.len() as u64;
stream
.set_len(new_len)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
Ok(())
}
pub fn save_with_components<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(path.as_ref())?;
let mut cf = CompoundFile::open(file).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
e.to_string(),
))
})?;
self.write_rules(&mut cf)?;
self.write_components(&mut cf)?;
cf.flush()
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
Ok(())
}
pub fn save_all_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let file = std::fs::OpenOptions::new()
.read(true)
.write(true)
.open(path.as_ref())?;
let mut cf = CompoundFile::open(file).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
e.to_string(),
))
})?;
self.write_tracks(&mut cf)?;
self.write_vias(&mut cf)?;
self.write_arcs(&mut cf)?;
self.write_fills(&mut cf)?;
self.write_regions_internal(&mut cf)?;
self.write_polygons_internal(&mut cf)?;
self.write_pads(&mut cf)?;
self.write_texts(&mut cf)?;
self.write_rules(&mut cf)?;
self.write_components(&mut cf)?;
cf.flush()
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
Ok(())
}
fn write_tracks<R: Read + Write + Seek>(&self, cf: &mut CompoundFile<R>) -> Result<()> {
use crate::io::writer::write_block;
use crate::traits::ToBinary;
use byteorder::WriteBytesExt;
let data_path = "/Tracks6/Data";
if cf.entry(data_path).is_err() {
return Ok(()); }
let mut buffer = Vec::new();
for prim in &self.primitives {
if let PcbRecord::Track(track) = prim {
buffer.write_u8(PcbObjectId::Track.to_byte())?;
let mut track_data = Vec::new();
track.write_to(&mut track_data)?;
write_block(&mut buffer, &track_data, 0)?;
}
}
let mut stream = cf.open_stream(data_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
stream.seek(SeekFrom::Start(0))?;
stream.write_all(&buffer)?;
stream
.set_len(buffer.len() as u64)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
Ok(())
}
fn write_vias<R: Read + Write + Seek>(&self, cf: &mut CompoundFile<R>) -> Result<()> {
use crate::io::writer::write_block;
use crate::traits::ToBinary;
use byteorder::WriteBytesExt;
let data_path = "/Vias6/Data";
if cf.entry(data_path).is_err() {
return Ok(());
}
let mut buffer = Vec::new();
for prim in &self.primitives {
if let PcbRecord::Via(via) = prim {
buffer.write_u8(PcbObjectId::Via.to_byte())?;
let mut via_data = Vec::new();
via.write_to(&mut via_data)?;
write_block(&mut buffer, &via_data, 0)?;
}
}
let mut stream = cf.open_stream(data_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
stream.seek(SeekFrom::Start(0))?;
stream.write_all(&buffer)?;
stream
.set_len(buffer.len() as u64)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
Ok(())
}
fn write_arcs<R: Read + Write + Seek>(&self, cf: &mut CompoundFile<R>) -> Result<()> {
use crate::io::writer::write_block;
use crate::traits::ToBinary;
use byteorder::WriteBytesExt;
let data_path = "/Arcs6/Data";
if cf.entry(data_path).is_err() {
return Ok(());
}
let mut buffer = Vec::new();
for prim in &self.primitives {
if let PcbRecord::Arc(arc) = prim {
buffer.write_u8(PcbObjectId::Arc.to_byte())?;
let mut arc_data = Vec::new();
arc.write_to(&mut arc_data)?;
write_block(&mut buffer, &arc_data, 0)?;
}
}
let mut stream = cf.open_stream(data_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
stream.seek(SeekFrom::Start(0))?;
stream.write_all(&buffer)?;
stream
.set_len(buffer.len() as u64)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
Ok(())
}
fn write_fills<R: Read + Write + Seek>(&self, cf: &mut CompoundFile<R>) -> Result<()> {
use crate::io::writer::write_block;
use crate::traits::ToBinary;
use byteorder::WriteBytesExt;
let data_path = "/Fills6/Data";
if cf.entry(data_path).is_err() {
return Ok(());
}
let mut buffer = Vec::new();
for prim in &self.primitives {
if let PcbRecord::Fill(fill) = prim {
buffer.write_u8(PcbObjectId::Fill.to_byte())?;
let mut fill_data = Vec::new();
fill.write_to(&mut fill_data)?;
write_block(&mut buffer, &fill_data, 0)?;
}
}
let mut stream = cf.open_stream(data_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
stream.seek(SeekFrom::Start(0))?;
stream.write_all(&buffer)?;
stream
.set_len(buffer.len() as u64)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
Ok(())
}
fn write_pads<R: Read + Write + Seek>(&self, cf: &mut CompoundFile<R>) -> Result<()> {
use crate::io::writer::write_block;
use crate::traits::ToBinary;
use byteorder::WriteBytesExt;
let data_path = "/Pads6/Data";
if cf.entry(data_path).is_err() {
return Ok(());
}
let mut buffer = Vec::new();
for prim in &self.primitives {
if let PcbRecord::Pad(pad) = prim {
buffer.write_u8(PcbObjectId::Pad.to_byte())?;
let mut pad_data = Vec::new();
pad.write_to(&mut pad_data)?;
write_block(&mut buffer, &pad_data, 0)?;
}
}
let mut stream = cf.open_stream(data_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
stream.seek(SeekFrom::Start(0))?;
stream.write_all(&buffer)?;
stream
.set_len(buffer.len() as u64)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
Ok(())
}
fn write_texts<R: Read + Write + Seek>(&self, cf: &mut CompoundFile<R>) -> Result<()> {
use crate::io::writer::write_block;
use crate::traits::ToBinary;
use byteorder::WriteBytesExt;
let data_path = "/Texts6/Data";
if cf.entry(data_path).is_err() {
return Ok(());
}
let mut buffer = Vec::new();
for prim in &self.primitives {
if let PcbRecord::Text(text) = prim {
buffer.write_u8(PcbObjectId::Text.to_byte())?;
let mut text_data = Vec::new();
text.write_to(&mut text_data)?;
write_block(&mut buffer, &text_data, 0)?;
}
}
let mut stream = cf.open_stream(data_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
stream.seek(SeekFrom::Start(0))?;
stream.write_all(&buffer)?;
stream
.set_len(buffer.len() as u64)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
Ok(())
}
fn write_regions_internal<R: Read + Write + Seek>(
&self,
cf: &mut CompoundFile<R>,
) -> Result<()> {
use crate::io::writer::write_block;
use crate::traits::ToBinary;
use byteorder::WriteBytesExt;
let data_path = "/Regions6/Data";
if cf.entry(data_path).is_err() {
return Ok(());
}
let mut buffer = Vec::new();
for prim in &self.primitives {
if let PcbRecord::Region(region) = prim {
buffer.write_u8(PcbObjectId::Region.to_byte())?;
let mut region_data = Vec::new();
region.write_to(&mut region_data)?;
write_block(&mut buffer, ®ion_data, 0)?;
}
}
let mut stream = cf.open_stream(data_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
stream.seek(SeekFrom::Start(0))?;
stream.write_all(&buffer)?;
stream
.set_len(buffer.len() as u64)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
Ok(())
}
fn write_polygons_internal<R: Read + Write + Seek>(
&self,
cf: &mut CompoundFile<R>,
) -> Result<()> {
let data_path = "/Polygons6/Data";
if cf.entry(data_path).is_err() {
return Ok(());
}
let mut buffer = Vec::new();
for prim in &self.primitives {
if let PcbRecord::Polygon(polygon) = prim {
let params = polygon.to_params();
write_parameters_block(&mut buffer, ¶ms)?;
}
}
let mut stream = cf.open_stream(data_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
stream.seek(SeekFrom::Start(0))?;
stream.write_all(&buffer)?;
stream
.set_len(buffer.len() as u64)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
Ok(())
}
pub fn arc_count(&self) -> usize {
self.primitives
.iter()
.filter(|p| matches!(p, PcbRecord::Arc(_)))
.count()
}
pub fn fill_count(&self) -> usize {
self.primitives
.iter()
.filter(|p| matches!(p, PcbRecord::Fill(_)))
.count()
}
pub fn region_count(&self) -> usize {
self.primitives
.iter()
.filter(|p| matches!(p, PcbRecord::Region(_)))
.count()
}
pub fn polygon_count(&self) -> usize {
self.primitives
.iter()
.filter(|p| matches!(p, PcbRecord::Polygon(_)))
.count()
}
pub fn text_count(&self) -> usize {
self.primitives
.iter()
.filter(|p| matches!(p, PcbRecord::Text(_)))
.count()
}
pub fn add_track(&mut self, track: PcbTrack) {
self.primitives.push(PcbRecord::Track(track));
}
pub fn add_via(&mut self, via: PcbVia) {
self.primitives.push(PcbRecord::Via(via));
}
pub fn add_arc(&mut self, arc: PcbArc) {
self.primitives.push(PcbRecord::Arc(arc));
}
pub fn add_fill(&mut self, fill: PcbFill) {
self.primitives.push(PcbRecord::Fill(fill));
}
pub fn add_region(&mut self, region: PcbRegion) {
self.primitives.push(PcbRecord::Region(region));
}
pub fn add_polygon(&mut self, polygon: PcbPolygon) {
self.primitives.push(PcbRecord::Polygon(polygon));
}
pub fn remove_primitive(&mut self, index: usize) -> Option<PcbRecord> {
if index < self.primitives.len() {
Some(self.primitives.remove(index))
} else {
None
}
}
pub fn get_primitive(&self, index: usize) -> Option<&PcbRecord> {
self.primitives.get(index)
}
pub fn get_primitive_mut(&mut self, index: usize) -> Option<&mut PcbRecord> {
self.primitives.get_mut(index)
}
pub fn iter_tracks(&self) -> impl Iterator<Item = &PcbTrack> {
self.primitives.iter().filter_map(|p| {
if let PcbRecord::Track(t) = p {
Some(t)
} else {
None
}
})
}
pub fn iter_vias(&self) -> impl Iterator<Item = &PcbVia> {
self.primitives.iter().filter_map(|p| {
if let PcbRecord::Via(v) = p {
Some(v)
} else {
None
}
})
}
pub fn iter_arcs(&self) -> impl Iterator<Item = &PcbArc> {
self.primitives.iter().filter_map(|p| {
if let PcbRecord::Arc(a) = p {
Some(a)
} else {
None
}
})
}
pub fn iter_fills(&self) -> impl Iterator<Item = &PcbFill> {
self.primitives.iter().filter_map(|p| {
if let PcbRecord::Fill(f) = p {
Some(f)
} else {
None
}
})
}
pub fn iter_regions(&self) -> impl Iterator<Item = &PcbRegion> {
self.primitives.iter().filter_map(|p| {
if let PcbRecord::Region(r) = p {
Some(r)
} else {
None
}
})
}
pub fn iter_polygons(&self) -> impl Iterator<Item = &PcbPolygon> {
self.primitives.iter().filter_map(|p| {
if let PcbRecord::Polygon(pol) = p {
Some(pol)
} else {
None
}
})
}
pub fn iter_texts(&self) -> impl Iterator<Item = &PcbText> {
self.primitives.iter().filter_map(|p| {
if let PcbRecord::Text(t) = p {
Some(t)
} else {
None
}
})
}
pub fn add_text(&mut self, text: PcbText) {
self.primitives.push(PcbRecord::Text(text));
}
}
impl PcbDocComponent {
pub fn x(&self) -> Option<crate::types::Coord> {
self.params
.get("X")
.map(|v| v.as_coord_or(crate::types::Coord::ZERO))
}
pub fn y(&self) -> Option<crate::types::Coord> {
self.params
.get("Y")
.map(|v| v.as_coord_or(crate::types::Coord::ZERO))
}
pub fn rotation(&self) -> f64 {
self.params
.get("ROTATION")
.and_then(|v| v.as_str().trim().parse::<f64>().ok())
.unwrap_or(0.0)
}
pub fn layer(&self) -> crate::types::Layer {
self.params
.get("LAYER")
.and_then(|v| {
let layer_str = v.as_str();
crate::types::Layer::from_name(layer_str).or_else(|| {
match layer_str.to_uppercase().as_str() {
"TOP" => Some(crate::types::Layer::TOP_LAYER),
"BOTTOM" => Some(crate::types::Layer::BOTTOM_LAYER),
"TOPOVERLAY" | "TOP_OVERLAY" => Some(crate::types::Layer::TOP_OVERLAY),
"BOTTOMOVERLAY" | "BOTTOM_OVERLAY" => {
Some(crate::types::Layer::BOTTOM_OVERLAY)
}
_ => None,
}
})
})
.unwrap_or(crate::types::Layer::TOP_LAYER)
}
pub fn set_x(&mut self, x: crate::types::Coord) {
self.params.add_coord("X", x);
}
pub fn set_y(&mut self, y: crate::types::Coord) {
self.params.add_coord("Y", y);
}
pub fn set_position(&mut self, x: crate::types::Coord, y: crate::types::Coord) {
self.set_x(x);
self.set_y(y);
}
pub fn set_rotation(&mut self, rotation: f64) {
self.params.add("ROTATION", &format!("{:.14E}", rotation));
}
pub fn set_layer(&mut self, layer: crate::types::Layer) {
self.params.add("LAYER", layer.name());
}
}
impl DumpTree for PcbDoc {
fn dump(&self, tree: &mut TreeBuilder) {
tree.root(&format!(
"PcbDoc ({} components, {} primitives, {} rules)",
self.components.len(),
self.primitives.len(),
self.rules.len()
));
tree.push(
!self.components.is_empty() || !self.primitives.is_empty() || !self.rules.is_empty(),
);
tree.add_leaf(
"Summary",
&[
("components", format!("{}", self.components.len())),
("tracks", format!("{}", self.track_count())),
("vias", format!("{}", self.via_count())),
("nets", format!("{}", self.nets.len())),
("rules", format!("{}", self.rules.len())),
("primitives", format!("{}", self.primitives.len())),
],
);
tree.pop();
if !self.components.is_empty() {
tree.push(!self.primitives.is_empty());
tree.begin_node(&format!("Components ({})", self.components.len()));
for (i, comp) in self.components.iter().enumerate() {
tree.push(i < self.components.len() - 1);
comp.dump(tree);
tree.pop();
}
tree.pop();
}
if !self.nets.is_empty() {
tree.push(false);
tree.add_leaf(
&format!("Nets ({})", self.nets.len()),
&[(
"first_few",
self.nets
.iter()
.take(5)
.cloned()
.collect::<Vec<_>>()
.join(", "),
)],
);
tree.pop();
}
}
}
impl DumpTree for PcbDocComponent {
fn dump(&self, tree: &mut TreeBuilder) {
let mut props = vec![("designator", self.designator.clone())];
if !self.pattern.is_empty() {
props.push(("pattern", self.pattern.clone()));
}
if !self.comment.is_empty() {
props.push(("comment", self.comment.clone()));
}
tree.add_leaf_with_params("Component", &props, Some(&self.params));
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_read_classes_and_options() {
let data = std::fs::read("data/PCB1.PcbDoc").expect("Failed to read file");
let pcbdoc = PcbDoc::open(Cursor::new(&data)).expect("Failed to parse PcbDoc");
assert!(!pcbdoc.classes.is_empty(), "Should have parsed classes");
println!("Classes: {}", pcbdoc.classes.len());
for class in &pcbdoc.classes {
println!(" - {} ({:?})", class.name, class.kind);
}
assert!(
pcbdoc.placer_options.is_some(),
"Should have placer options"
);
let opts = pcbdoc.placer_options.as_ref().unwrap();
assert!(opts.use_rotation);
assert!(pcbdoc.drc_options.is_some(), "Should have DRC options");
assert!(
pcbdoc.pin_swap_options.is_some(),
"Should have pin swap options"
);
assert!(!pcbdoc.rules.is_empty(), "Should have rules");
println!("Rules: {}", pcbdoc.rules.len());
}
}