use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use cfb::CompoundFile;
use std::collections::HashMap;
use std::fs::File;
use std::io::{Cursor, Read, Seek, Write};
use std::path::Path;
use crate::error::{AltiumError, Result};
use crate::io::reader::{
read_block, read_parameters_block, read_pascal_short_string, read_pascal_string,
read_string_block,
};
use crate::io::writer::{
write_block, write_parameters, write_pascal_short_string, write_string_block,
};
use crate::records::pcb::{
PcbArc, PcbComponent, PcbComponentBody, PcbFill, PcbObjectId, PcbPad, PcbRecord, PcbRegion,
PcbText, PcbTrack, PcbVia,
};
use crate::traits::{FromBinary, ToBinary};
use crate::types::ParameterCollection;
#[derive(Debug, Default)]
pub struct PcbLib {
section_keys: HashMap<String, String>,
pub unique_id: String,
pub components: Vec<PcbComponent>,
}
impl PcbLib {
pub fn open<R: Read + Seek>(reader: R) -> Result<Self> {
let mut pcblib = PcbLib::default();
let mut cf = CompoundFile::open(reader).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::InvalidData,
e.to_string(),
))
})?;
pcblib.read_file_header(&mut cf)?;
pcblib.read_section_keys(&mut cf)?;
pcblib.read_library(&mut cf)?;
Ok(pcblib)
}
pub fn open_file<P: AsRef<Path>>(path: P) -> Result<Self> {
let file = File::open(path)?;
Self::open(file)
}
pub fn save<W: Read + Write + Seek>(&self, writer: W) -> Result<()> {
let mut cf = CompoundFile::create(writer)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
self.write_file_header(&mut cf)?;
self.write_section_keys(&mut cf)?;
self.write_library(&mut cf)?;
cf.flush()
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
Ok(())
}
pub fn save_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let file = File::create(path)?;
self.save(file)
}
fn write_file_header<F: Read + Write + Seek>(&self, cf: &mut CompoundFile<F>) -> Result<()> {
let mut data = Vec::new();
let version = "PCB 6.0 Binary Library File";
data.write_i32::<LittleEndian>(version.len() as i32)?;
write_pascal_short_string(&mut data, version)?;
data.write_f64::<LittleEndian>(5.0)?;
write_pascal_short_string(&mut data, "DVLTOKCO")?;
let stream = cf
.create_stream("/FileHeader")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
let mut stream = stream;
stream.write_all(&data)?;
Ok(())
}
fn write_section_keys<F: Read + Write + Seek>(&self, cf: &mut CompoundFile<F>) -> Result<()> {
if self.components.is_empty() {
return Ok(());
}
let mut data = Vec::new();
data.write_i32::<LittleEndian>(self.components.len() as i32)?;
for (i, comp) in self.components.iter().enumerate() {
let storage_name = format!("PCBComponent_{}", i + 1);
crate::io::writer::write_pascal_string(&mut data, &comp.pattern)?;
write_string_block(&mut data, &storage_name)?;
}
let stream = cf
.create_stream("/SectionKeys")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
let mut stream = stream;
stream.write_all(&data)?;
Ok(())
}
fn write_library<F: Read + Write + Seek>(&self, cf: &mut CompoundFile<F>) -> Result<()> {
cf.create_storage("/Library")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
self.write_library_header(cf)?;
self.write_library_data(cf)?;
self.write_library_substorages(cf)?;
self.write_file_version_info(cf)?;
for (i, comp) in self.components.iter().enumerate() {
let storage_name = format!("PCBComponent_{}", i + 1);
self.write_footprint(cf, comp, &storage_name)?;
}
Ok(())
}
fn write_library_header<F: Read + Write + Seek>(&self, cf: &mut CompoundFile<F>) -> Result<()> {
let mut header_data = Vec::new();
header_data.write_u32::<LittleEndian>(1)?;
let stream = cf
.create_stream("/Library/Header")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
let mut stream = stream;
stream.write_all(&header_data)?;
Ok(())
}
fn write_library_data<F: Read + Write + Seek>(&self, cf: &mut CompoundFile<F>) -> Result<()> {
let mut data = Vec::new();
let params = Self::build_library_parameters();
let mut params_block = Vec::new();
write_parameters(&mut params_block, ¶ms)?;
write_block(&mut data, ¶ms_block, 0)?;
data.write_u32::<LittleEndian>(self.components.len() as u32)?;
for i in 0..self.components.len() {
let storage_name = format!("PCBComponent_{}", i + 1);
write_string_block(&mut data, &storage_name)?;
}
let stream = cf
.create_stream("/Library/Data")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
let mut stream = stream;
stream.write_all(&data)?;
Ok(())
}
fn build_library_parameters() -> ParameterCollection {
let mut params = ParameterCollection::new();
params.add("KIND", "Protel_Advanced_PCB_Library");
params.add("VERSION", "1.0");
params.add("BOARDVERSION", "5.01");
params.add("VISIBLEGRIDMULTFACTOR", "1.000");
params.add("BIGVISIBLEGRIDMULTFACTOR", "5.000");
params.add("CURRENT2D3DVIEWSTATE", "2D");
params.add("CFG2D.CURRENTLAYER", "TOP");
params.add("CFG2D.SHOWPADNETS", "TRUE");
params.add("CFG2D.SHOWPADNUMBERS", "TRUE");
params.add("CFG2D.SHOWVIANETS", "TRUE");
params.add("CFG2D.SHOWORIGINMARKER", "TRUE");
params.add("CFG2D.DISPLAYSPECIALSTRINGS", "FALSE");
params.add("CFG2D.SHOWTESTPOINTS", "FALSE");
params.add("CFG2D.SHOWSTATUSINFO", "TRUE");
params.add("CFG2D.USETRANSPARENTLAYERS", "FALSE");
params.add("CFG2D.PLANEDRAWMODE", "2");
params.add("CFG2D.DISPLAYNETNAMESONTRACKS", "1");
params.add("CFG2D.SINGLELAYERMODESTATE", "3");
params.add("CFG2D.ORIGINMARKERCOLOR", "16777215");
params.add(
"CFG2D.TOGGLELAYERS",
"1111111111111111111111111111111111111111111111111111111111111111",
);
params.add("EGENABLED", "TRUE");
params.add("EGRANGE", "8mil");
params.add("OGSNAPENABLED", "TRUE");
params.add("GRIDSNAPENABLED", "TRUE");
params
}
fn write_library_substorages<F: Read + Write + Seek>(
&self,
cf: &mut CompoundFile<F>,
) -> Result<()> {
for storage in &[
"/Library/Models",
"/Library/Textures",
"/Library/ModelsNoEmbed",
] {
cf.create_storage(storage)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
let header_path = format!("{}/Header", storage);
let data_path = format!("{}/Data", storage);
let mut stream = cf
.create_stream(&header_path)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
stream.write_u32::<LittleEndian>(0)?;
let mut stream = cf
.create_stream(&data_path)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
stream.write_all(&[])?;
}
{
let mut stream = cf
.create_stream("/Library/EmbeddedFonts")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
stream.write_u32::<LittleEndian>(0)?;
}
{
cf.create_storage("/Library/PadViaLibrary")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
let mut header = cf
.create_stream("/Library/PadViaLibrary/Header")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
header.write_u32::<LittleEndian>(1)?;
let mut params = ParameterCollection::new();
params.add(
"PADVIALIBRARY.LIBRARYID",
"{00000000-0000-0000-0000-000000000000}",
);
params.add("PADVIALIBRARY.LIBRARYNAME", "<Local>");
params.add("PADVIALIBRARY.DISPLAYUNITS", "1");
let mut block = Vec::new();
write_parameters(&mut block, ¶ms)?;
let mut data_buf = Vec::new();
write_block(&mut data_buf, &block, 0)?;
let mut data = cf
.create_stream("/Library/PadViaLibrary/Data")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
data.write_all(&data_buf)?;
}
{
cf.create_storage("/Library/LayerKindMapping")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
let mut header = cf
.create_stream("/Library/LayerKindMapping/Header")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
header.write_u32::<LittleEndian>(1)?;
let mut data_buf = Vec::new();
data_buf.write_u32::<LittleEndian>(8)?;
for c in "1.0\0".encode_utf16() {
data_buf.write_u16::<LittleEndian>(c)?;
}
let mut data = cf
.create_stream("/Library/LayerKindMapping/Data")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
data.write_all(&data_buf)?;
}
{
cf.create_storage("/Library/ComponentParamsTOC")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
let mut header = cf
.create_stream("/Library/ComponentParamsTOC/Header")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
header.write_u32::<LittleEndian>(self.components.len() as u32)?;
let mut toc_data = Vec::new();
for (i, comp) in self.components.iter().enumerate() {
let storage_name = format!("PCBComponent_{}", i + 1);
let pad_count = comp
.primitives
.iter()
.filter(|p| matches!(p, PcbRecord::Pad(_)))
.count();
let mut params = ParameterCollection::new();
params.add("Name", &storage_name);
params.add("Pad Count", &pad_count.to_string());
params.add("Height", "0");
params.add("Description", &comp.description);
let mut block = Vec::new();
write_parameters(&mut block, ¶ms)?;
write_block(&mut toc_data, &block, 0)?;
}
let mut data = cf
.create_stream("/Library/ComponentParamsTOC/Data")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
data.write_all(&toc_data)?;
}
Ok(())
}
fn write_file_version_info<F: Read + Write + Seek>(
&self,
cf: &mut CompoundFile<F>,
) -> Result<()> {
cf.create_storage("/FileVersionInfo")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
let mut header = cf
.create_stream("/FileVersionInfo/Header")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
header.write_u32::<LittleEndian>(1)?;
let mut params = ParameterCollection::new();
params.add("VERSIONNUMBER", "1.0");
params.add("REVISIONDATE", "2024-01-01");
let mut block = Vec::new();
write_parameters(&mut block, ¶ms)?;
let mut data_buf = Vec::new();
write_block(&mut data_buf, &block, 0)?;
let mut data = cf
.create_stream("/FileVersionInfo/Data")
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
data.write_all(&data_buf)?;
Ok(())
}
fn write_footprint<F: Read + Write + Seek>(
&self,
cf: &mut CompoundFile<F>,
comp: &PcbComponent,
storage_name: &str,
) -> Result<()> {
let storage_path = format!("/{}", storage_name);
cf.create_storage(&storage_path)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
{
let mut header_data = Vec::new();
header_data.write_u32::<LittleEndian>(comp.primitives.len() as u32)?;
let header_path = format!("{}/Header", storage_path);
let stream = cf
.create_stream(&header_path)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
let mut stream = stream;
stream.write_all(&header_data)?;
}
{
let mut params_data = Vec::new();
let params = comp.export_to_parameters();
let mut block = Vec::new();
write_parameters(&mut block, ¶ms)?;
write_block(&mut params_data, &block, 0)?;
let params_path = format!("{}/Parameters", storage_path);
let stream = cf
.create_stream(¶ms_path)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
let mut stream = stream;
stream.write_all(¶ms_data)?;
}
{
let mut data = Vec::new();
write_string_block(&mut data, &comp.pattern)?;
for record in &comp.primitives {
self.write_primitive(&mut data, record)?;
}
let data_path = format!("{}/Data", storage_path);
let stream = cf
.create_stream(&data_path)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
let mut stream = stream;
stream.write_all(&data)?;
}
{
let mut params = ParameterCollection::new();
params.add("COUNT", "0");
let mut block = Vec::new();
write_parameters(&mut block, ¶ms)?;
let mut wide_data = Vec::new();
write_block(&mut wide_data, &block, 0)?;
let wide_path = format!("{}/WideStrings", storage_path);
let stream = cf
.create_stream(&wide_path)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
let mut stream = stream;
stream.write_all(&wide_data)?;
}
{
let guids_path = format!("{}/PrimitiveGuids", storage_path);
cf.create_storage(&guids_path)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
let header_path = format!("{}/Header", guids_path);
let mut header = cf
.create_stream(&header_path)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
header.write_u32::<LittleEndian>(comp.primitives.len() as u32)?;
let data_path = format!("{}/Data", guids_path);
let mut data = cf
.create_stream(&data_path)
.map_err(|e| AltiumError::Io(std::io::Error::other(e.to_string())))?;
for _ in 0..comp.primitives.len() {
data.write_all(&[0u8; 16])?;
}
}
Ok(())
}
fn write_primitive<W: Write>(&self, writer: &mut W, record: &PcbRecord) -> Result<()> {
let object_id = match record {
PcbRecord::Arc(_) => 1,
PcbRecord::Pad(_) => 2,
PcbRecord::Via(_) => 3,
PcbRecord::Track(_) => 4,
PcbRecord::Text(_) => 5,
PcbRecord::Fill(_) => 6,
PcbRecord::Region(_) => 11,
PcbRecord::ComponentBody(_) => 12,
PcbRecord::Polygon(_) => {
return Err(AltiumError::Parse(
"Polygons are not supported in PcbLib footprints".to_string(),
));
}
PcbRecord::Unknown { object_id, .. } => *object_id as u8,
};
writer.write_u8(object_id)?;
match record {
PcbRecord::Arc(arc) => {
let mut data = Vec::new();
arc.write_to(&mut data)?;
write_block(writer, &data, 0)?;
}
PcbRecord::Pad(pad) => {
pad.write_to(writer)?;
}
PcbRecord::Via(via) => {
let mut data = Vec::new();
via.write_to(&mut data)?;
write_block(writer, &data, 0)?;
}
PcbRecord::Track(track) => {
let mut data = Vec::new();
track.write_to(&mut data)?;
write_block(writer, &data, 0)?;
}
PcbRecord::Text(text) => {
let mut data = Vec::new();
text.write_to(&mut data)?;
write_block(writer, &data, 0)?;
write_string_block(writer, &text.text)?;
}
PcbRecord::Fill(fill) => {
let mut data = Vec::new();
fill.write_to(&mut data)?;
write_block(writer, &data, 0)?;
}
PcbRecord::Region(region) => {
let mut data = Vec::new();
region.write_to(&mut data)?;
write_block(writer, &data, 0)?;
}
PcbRecord::ComponentBody(body) => {
let mut data = Vec::new();
body.write_to(&mut data)?;
write_block(writer, &data, 0)?;
}
PcbRecord::Polygon(_) => {
unreachable!("Polygons should have errored in object_id match")
}
PcbRecord::Unknown { raw_data, .. } => {
write_block(writer, raw_data, 0)?;
}
}
Ok(())
}
fn get_section_key(&self, ref_name: &str) -> String {
self.section_keys
.get(ref_name)
.cloned()
.unwrap_or_else(|| ref_name.to_string())
}
fn read_file_header<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
let stream_path = "/FileHeader";
if cf.entry(stream_path).is_err() {
return Ok(());
}
let mut stream = cf.open_stream(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 _version_len = cursor.read_i32::<LittleEndian>()?;
let _version_text = read_pascal_short_string(&mut cursor)?;
if (cursor.position() as usize) < data.len() {
let field1 = read_pascal_short_string(&mut cursor).unwrap_or_default();
if !field1.is_empty() {
log::trace!("FileHeader optional field 1: {:?}", field1);
}
let field2 = read_pascal_short_string(&mut cursor).unwrap_or_default();
if !field2.is_empty() {
log::trace!("FileHeader optional field 2: {:?}", field2);
}
if let Ok(uid) = read_pascal_short_string(&mut cursor) {
self.unique_id = uid;
}
}
Ok(())
}
fn read_section_keys<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
let stream_path = "/SectionKeys";
if cf.entry(stream_path).is_err() {
return Ok(());
}
let mut stream = cf.open_stream(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 key_count = cursor.read_i32::<LittleEndian>()?;
for _ in 0..key_count {
let lib_ref = read_pascal_string(&mut cursor)?;
let section_key = read_string_block(&mut cursor)?;
if !lib_ref.is_empty() && !section_key.is_empty() {
self.section_keys.insert(lib_ref, section_key);
}
}
Ok(())
}
fn read_library<R: Read + Seek>(&mut self, cf: &mut CompoundFile<R>) -> Result<()> {
let storage_path = "/Library";
let header_path = format!("{}/Header", storage_path);
if cf.entry(&header_path).is_ok() {
let mut stream = cf.open_stream(&header_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
let _record_count = stream.read_u32::<LittleEndian>()?;
}
let data_path = format!("{}/Data", storage_path);
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);
let _header_params = read_parameters_block(&mut cursor)?;
let footprint_count = cursor.read_u32::<LittleEndian>()?;
let mut ref_names = Vec::with_capacity(footprint_count as usize);
for _ in 0..footprint_count {
let ref_name = read_string_block(&mut cursor)?;
ref_names.push(ref_name);
}
for ref_name in ref_names {
let section_key = self.get_section_key(&ref_name);
match self.read_footprint(cf, §ion_key) {
Ok(component) => {
self.components.push(component);
}
Err(e) => {
eprintln!("Warning: Failed to read footprint {:?}: {}", ref_name, e);
continue;
}
}
}
Ok(())
}
fn read_footprint<R: Read + Seek>(
&self,
cf: &mut CompoundFile<R>,
section_key: &str,
) -> Result<PcbComponent> {
let mut storage_path = format!("/{}", section_key);
if cf.entry(&storage_path).is_err() {
let section_key_alt = section_key.replace('/', "_");
let alt_path = format!("/{}", section_key_alt);
if cf.entry(&alt_path).is_ok() {
storage_path = alt_path;
}
}
let header_path = format!("{}/Header", storage_path);
let _record_count = if cf.entry(&header_path).is_ok() {
let mut stream = cf.open_stream(&header_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
e.to_string(),
))
})?;
stream.read_u32::<LittleEndian>()?
} else {
0
};
let mut component = PcbComponent::default();
let params_path = format!("{}/Parameters", storage_path);
if cf.entry(¶ms_path).is_ok() {
let mut stream = cf.open_stream(¶ms_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() {
let mut cursor = Cursor::new(&data);
let params = read_parameters_block(&mut cursor)?;
component.import_from_parameters(¶ms);
}
}
let wide_strings = self.read_wide_strings(cf, &storage_path)?;
let data_path = format!("{}/Data", storage_path);
let mut stream = cf.open_stream(&data_path).map_err(|e| {
AltiumError::Io(std::io::Error::new(
std::io::ErrorKind::NotFound,
format!("Footprint data not found: {} - {}", data_path, e),
))
})?;
let mut data = Vec::new();
stream.read_to_end(&mut data)?;
if data.is_empty() {
return Err(AltiumError::Parse("Empty footprint data".to_string()));
}
let mut cursor = Cursor::new(&data);
let pattern = read_string_block(&mut cursor)?;
if component.pattern.is_empty() {
component.pattern = pattern;
}
while (cursor.position() as usize) < data.len() {
match self.read_primitive(&mut cursor, &wide_strings) {
Ok(record) => component.primitives.push(record),
Err(_) => break,
}
}
Ok(component)
}
fn read_wide_strings<R: Read + Seek>(
&self,
cf: &mut CompoundFile<R>,
storage_path: &str,
) -> Result<Vec<String>> {
let wide_path = format!("{}/WideStrings", storage_path);
if cf.entry(&wide_path).is_err() {
return Ok(Vec::new());
}
let mut stream = cf.open_stream(&wide_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(Vec::new());
}
let mut cursor = Cursor::new(&data);
let params = read_parameters_block(&mut cursor)?;
let mut strings = Vec::new();
let count = params.get("COUNT").map(|v| v.as_int_or(0)).unwrap_or(0) as usize;
for i in 0..count {
let key = format!("WIDESTRING{}", i);
if let Some(val) = params.get(&key) {
strings.push(val.as_str().to_string());
} else {
strings.push(String::new());
}
}
Ok(strings)
}
fn read_primitive(
&self,
cursor: &mut Cursor<&Vec<u8>>,
wide_strings: &[String],
) -> Result<PcbRecord> {
let object_id = PcbObjectId::from_byte(cursor.read_u8()?);
match object_id {
PcbObjectId::Arc => {
let block = read_block(cursor)?;
let mut block_cursor = Cursor::new(&block);
let arc = <PcbArc as FromBinary>::read_from(&mut block_cursor)?;
Ok(PcbRecord::Arc(arc))
}
PcbObjectId::Pad => {
let pad = PcbPad::read_from(cursor)?;
Ok(PcbRecord::Pad(Box::new(pad)))
}
PcbObjectId::Via => {
let block = read_block(cursor)?;
let mut block_cursor = Cursor::new(&block);
let via = <PcbVia as FromBinary>::read_from(&mut block_cursor)?;
Ok(PcbRecord::Via(via))
}
PcbObjectId::Track => {
let block = read_block(cursor)?;
let mut block_cursor = Cursor::new(&block);
let track = <PcbTrack as FromBinary>::read_from(&mut block_cursor)?;
Ok(PcbRecord::Track(track))
}
PcbObjectId::Text => {
let block = read_block(cursor)?;
let mut block_cursor = Cursor::new(&block);
let mut text = <PcbText as FromBinary>::read_from(&mut block_cursor)?;
let ascii_text = read_string_block(cursor)?;
if text.wide_strings_index >= 0
&& (text.wide_strings_index as usize) < wide_strings.len()
{
text.text = wide_strings[text.wide_strings_index as usize].clone();
} else {
text.text = ascii_text;
}
Ok(PcbRecord::Text(text))
}
PcbObjectId::Fill => {
let block = read_block(cursor)?;
let mut block_cursor = Cursor::new(&block);
let fill = <PcbFill as FromBinary>::read_from(&mut block_cursor)?;
Ok(PcbRecord::Fill(fill))
}
PcbObjectId::Region => {
let block = read_block(cursor)?;
let mut block_cursor = Cursor::new(&block);
let region = <PcbRegion as FromBinary>::read_from(&mut block_cursor)?;
Ok(PcbRecord::Region(region))
}
PcbObjectId::ComponentBody => {
let block = read_block(cursor)?;
let mut block_cursor = Cursor::new(&block);
let body = <PcbComponentBody as FromBinary>::read_from(&mut block_cursor)?;
Ok(PcbRecord::ComponentBody(Box::new(body)))
}
_ => {
let block = read_block(cursor)?;
Ok(PcbRecord::Unknown {
object_id,
raw_data: block,
})
}
}
}
pub fn component_count(&self) -> usize {
self.components.len()
}
pub fn iter(&self) -> impl Iterator<Item = &PcbComponent> {
self.components.iter()
}
}
use crate::dump::{DumpTree, TreeBuilder};
impl DumpTree for PcbLib {
fn dump(&self, tree: &mut TreeBuilder) {
tree.root(&format!("PcbLib ({} footprints)", self.components.len()));
for (i, comp) in self.components.iter().enumerate() {
tree.push(i < self.components.len() - 1);
comp.dump(tree);
tree.pop();
}
}
}