Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the beginning of a very simplistic kicad board file parser #94

Merged
merged 16 commits into from
Dec 26, 2023
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Add the beginning of a very simplistic kicad board file parser
bschwind committed Dec 20, 2023
commit 894f5287936c9b223b61d72c1c9a8dd208124fc8
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
[workspace]
members = [
"examples",
"crates/kicad-parser",
"crates/opencascade",
"crates/opencascade-sys",
"crates/viewer",
"examples",
]

resolver = "2"
8 changes: 8 additions & 0 deletions crates/kicad-parser/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "kicad-parser"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1"
sexp = "1.1.4"
83 changes: 83 additions & 0 deletions crates/kicad-parser/sample-files/sample.kicad_pcb
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
(kicad_pcb
(version 20221018)
(generator pcbnew)
(general
(thickness 0.89)
)

(paper "A4")

(layers
(0 "F.Cu" signal)
(31 "B.Cu" signal)
(32 "B.Adhes" user "B.Adhesive")
(33 "F.Adhes" user "F.Adhesive")
(34 "B.Paste" user)
(35 "F.Paste" user)
(36 "B.SilkS" user "B.Silkscreen")
(37 "F.SilkS" user "F.Silkscreen")
(38 "B.Mask" user)
(39 "F.Mask" user)
(40 "Dwgs.User" user "User.Drawings")
(41 "Cmts.User" user "User.Comments")
(42 "Eco1.User" user "User.Eco1")
(43 "Eco2.User" user "User.Eco2")
(44 "Edge.Cuts" user)
(45 "Margin" user)
(46 "B.CrtYd" user "B.Courtyard")
(47 "F.CrtYd" user "F.Courtyard")
(48 "B.Fab" user)
(49 "F.Fab" user)
(50 "User.1" user)
(51 "User.2" user)
(52 "User.3" user)
(53 "User.4" user)
(54 "User.5" user)
(55 "User.6" user)
(56 "User.7" user)
(57 "User.8" user)
(58 "User.9" user)
)

(gr_line (start 174.8 55.1) (end 174.35 55.55)
(stroke (width 0.1) (type default)) (layer "B.SilkS") (tstamp 0baaefa6-77a5-4afb-bf3f-9e0db0a584be))
(gr_line (start 157 56.35) (end 157 56.1)
(stroke (width 0.1) (type default)) (layer "B.SilkS") (tstamp 209e05e1-97c1-43a4-bbae-3c88186d7e8e))
(gr_line (start 174.35 55.55) (end 174.35 55.2)
(stroke (width 0.1) (type default)) (layer "B.SilkS") (tstamp 40dcca91-2392-4dac-b749-9e6fce3ee42b))
(gr_line (start 156.55 55.9) (end 157 56.35)
(stroke (width 0.1) (type default)) (layer "B.SilkS") (tstamp e8c228f2-4fe7-4f97-a224-f4aa9deeed51))
(gr_line (start 185 71) (end 151 71)
(stroke (width 0.1) (type default)) (layer "Edge.Cuts") (tstamp 0d0b18b8-29e1-400d-9fe0-0c553320d369))
(gr_line (start 188.950546 53.08) (end 188.950546 47.524454)
(stroke (width 0.1) (type default)) (layer "Edge.Cuts") (tstamp 19f9a363-e864-4747-9205-86bc18fec056))
(gr_arc (start 188.675546 47.249454) (mid 188.87 47.33) (end 188.950546 47.524454)
(stroke (width 0.1) (type default)) (layer "Edge.Cuts") (tstamp 27a9dd0c-17b3-494e-8eb1-5591d63b4214))
(gr_arc (start 188 68) (mid 187.12132 70.12132) (end 185 71)
(stroke (width 0.1) (type default)) (layer "Edge.Cuts") (tstamp 2b4447d8-cc2c-42c4-8c3c-94373f69b331))
(gr_arc (start 148 41) (mid 148.87868 38.87868) (end 151 38)
(stroke (width 0.1) (type default)) (layer "Edge.Cuts") (tstamp 2c508737-2daa-4bed-82b0-cefc47998f5f))
(gr_line (start 188 46.973866) (end 188 41)
(stroke (width 0.1) (type default)) (layer "Edge.Cuts") (tstamp 2fc0c9d8-bd5d-4598-b05c-649a87b15bd8))
(gr_arc (start 188.950546 53.08) (mid 188.87 53.274454) (end 188.675546 53.355)
(stroke (width 0.1) (type default)) (layer "Edge.Cuts") (tstamp 32fa687c-3f1e-4171-8891-b3caae4ab217))
(gr_line (start 151 38) (end 185 38)
(stroke (width 0.1) (type default)) (layer "Edge.Cuts") (tstamp ae1d430b-8a10-4b47-a76e-f4f7ab7d79a1))
(gr_line (start 188 53.64) (end 188 68)
(stroke (width 0.1) (type default)) (layer "Edge.Cuts") (tstamp bece4ada-f6bb-41b4-abb7-f17f91e74bb7))
(gr_arc (start 151 71) (mid 148.87868 70.12132) (end 148 68)
(stroke (width 0.1) (type default)) (layer "Edge.Cuts") (tstamp c7ad8427-669a-40f7-b224-92bcde4ab5a8))
(gr_line (start 148 68) (end 148 41)
(stroke (width 0.1) (type default)) (layer "Edge.Cuts") (tstamp cc9c2e93-e0ca-429c-89b1-1b471ece0c41))
(gr_arc (start 185 38) (mid 187.12132 38.87868) (end 188 41)
(stroke (width 0.1) (type default)) (layer "Edge.Cuts") (tstamp d543461e-372c-4d57-90d6-704eeadbb4df))
(gr_line (start 188.675546 53.355588) (end 188.275 53.355588)
(stroke (width 0.1) (type default)) (layer "Edge.Cuts") (tstamp da1c4d49-6186-46e5-8107-f3f3d10f3ac9))
(gr_arc (start 188 53.630588) (mid 188.080546 53.436134) (end 188.275 53.355588)
(stroke (width 0.1) (type default)) (layer "Edge.Cuts") (tstamp e6f929d5-29b3-4daf-96c5-8cca7ca35c0d))
(gr_line (start 188.275 47.248866) (end 188.675546 47.248866)
(stroke (width 0.1) (type default)) (layer "Edge.Cuts") (tstamp eb3fa897-53c6-40af-9ce5-21313d913df4))
(gr_arc (start 188.275 47.248866) (mid 188.080546 47.16832) (end 188 46.973866)
(stroke (width 0.1) (type default)) (layer "Edge.Cuts") (tstamp f279b724-e617-43d5-8412-ff57704a025f))

)
133 changes: 133 additions & 0 deletions crates/kicad-parser/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
use anyhow::{anyhow, Context, Result};
use sexp::{Atom, Sexp};
use std::path::Path;

fn main() -> Result<()> {
let Some(input_file) = std::env::args().nth(1) else {
return Err(anyhow!("Usage: kicad-parser <input_file.kicad_pcb>"));
};

let board = KicadBoard::from_file(input_file)?;

dbg!(board);

Ok(())
}

#[derive(Debug, Clone, Default)]
pub struct KicadBoard {
graphic_lines: Vec<GraphicLine>,
}

impl KicadBoard {
pub fn from_file<P: AsRef<Path>>(file: P) -> Result<Self> {
let kicad_board_str = std::fs::read_to_string(&file)
.context(format!("Reading {:?}", file.as_ref().to_string_lossy()))?;
let sexp = sexp::parse(&kicad_board_str)?;

let Sexp::List(list) = sexp else {
return Err(anyhow!("Top level file wasn't a list"));
};

let Sexp::Atom(Atom::S(head)) = &list[0] else {
return Err(anyhow!("First element in the top level list should be a string"));
};

match head.as_str() {
"kicad_pcb" => {
let board_fields = &list[1..];
Ok(Self::handle_board_fields(board_fields)?)
},
_ => Err(anyhow!("Invalid top-level file type - expected 'kicad_pcb'")),
}
}

fn handle_board_fields(fields: &[Sexp]) -> Result<Self> {
let mut board = Self::default();

for field in fields {
let Sexp::List(list) = field else {
continue;
};

let Sexp::Atom(Atom::S(head)) = &list[0] else {
continue;
};

let rest = &list[1..];

match head.as_str() {
"version" => {},
"generator" => {},
"general" => {},
"paper" => {},
"layers" => {},
"footprint" => {},
"gr_arc" => {},
"gr_line" => {
let line = GraphicLine::from_list(rest)?;
board.graphic_lines.push(line);
},
_ => {},
}
}

Ok(board)
}
}

#[derive(Debug, Clone, Default)]
pub struct GraphicLine {
start: (f64, f64),
end: (f64, f64),
layer: String,
}

impl GraphicLine {
fn from_list(list: &[Sexp]) -> Result<Self> {
let mut line = Self::default();

for field in list {
let Sexp::List(list) = field else {
continue;
};

let Sexp::Atom(Atom::S(head)) = &list[0] else {
continue;
};

let rest = &list[1..];

match head.as_str() {
"start" => {
let coords = extract_coords(&rest[0], &rest[1])?;
line.start = coords;
},
"end" => {
let coords = extract_coords(&rest[0], &rest[1])?;
line.end = coords;
},
"layer" => {
if let Sexp::Atom(Atom::S(layer)) = &rest[0] {
line.layer = layer.to_string();
}
},
_ => {},
}
}

Ok(line)
}
}

fn extract_coords(x: &Sexp, y: &Sexp) -> Result<(f64, f64)> {
Ok((extract_number(x)?, extract_number(y)?))
}

fn extract_number(num: &Sexp) -> Result<f64> {
match num {
Sexp::Atom(Atom::F(float)) => Ok(*float),
Sexp::Atom(Atom::I(int)) => Ok(*int as f64),
_ => Err(anyhow!("Expected a number to be a float or integer")),
}
}
2 changes: 1 addition & 1 deletion crates/occt-sys/OCCT
Submodule OCCT updated 8969 files