// SPDX-FileCopyrightText: 2024 Topola contributors // // SPDX-License-Identifier: MIT use super::read::{ListTokenizer, ReadDsn}; use super::write::{ListWriter, WriteSes}; use crate::error::{ParseError, ParseErrorContext}; use crate::math::PointWithRotation; use crate::ListToken; use core::fmt; use geo_types::{LineString, Polygon as GeoPolygon}; use specctra_derive::{ReadDsn, WriteSes}; use std::{borrow::Cow, io}; #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Dummy {} #[derive(ReadDsn, WriteSes, Debug)] pub struct SesFile { pub session: Session, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Session { #[anon] pub id: String, pub routes: Routes, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Routes { pub resolution: Resolution, pub library_out: Library, pub network_out: NetworkOut, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct NetworkOut { #[vec("net")] pub net: Vec, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct NetOut { #[anon] pub name: String, #[vec("wire")] pub wire: Vec, #[vec("via")] pub via: Vec, } #[derive(ReadDsn, WriteSes, Debug)] pub struct DsnFile { pub pcb: Pcb, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Pcb { #[anon] pub name: String, pub parser: Option, pub resolution: Resolution, pub unit: Option, pub structure: Structure, pub placement: Placement, pub library: Library, pub network: Network, pub wiring: Wiring, } #[derive(WriteSes, Debug, Clone, PartialEq)] pub struct Parser { pub string_quote: Option, pub space_in_quoted_tokens: Option, pub host_cad: Option, pub host_version: Option, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Resolution { #[anon] pub unit: String, #[anon] pub value: f32, } #[derive(WriteSes, Debug, Clone, PartialEq)] pub struct Structure { #[vec("layer")] pub layers: Vec, pub boundary: Boundary, pub place_boundary: Option, #[vec("plane")] pub planes: Vec, #[anon] pub keepouts: Keepouts, pub via: ViaNames, #[vec("grid")] pub grids: Vec, // this is a vec of special structs because EasyEDA uses different syntax // it outputs a sequence of rules containing a clearance each // (in class rules it outputs a single rule with all clearances like KiCad) #[vec("rule")] pub rules: Vec, } // custom impl to handle layers appearing late impl ReadDsn for Structure { fn read_dsn(tokenizer: &mut ListTokenizer) -> Result { let mut value = Self { layers: tokenizer.read_named_array(&["layer"])?, boundary: tokenizer.read_named(&["boundary"])?, place_boundary: tokenizer.read_optional(&["place_boundary"])?, planes: tokenizer.read_named_array(&["plane"])?, keepouts: Keepouts::read_dsn(tokenizer)?, via: tokenizer.read_named(&["via"])?, grids: tokenizer.read_named_array(&["grid"])?, rules: tokenizer.read_named_array(&["rule"])?, }; value .layers .append(&mut tokenizer.read_named_array(&["layer"])?); Ok(value) } } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Layer { #[anon] pub name: String, pub r#type: String, pub property: Option, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Property { pub index: usize, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub enum Boundary { Path(Path), Rect(Rect), } impl Boundary { /// * For the `.structure.boundary`, it is expected that `.layer() == "pcb"`; /// * Another special value which could be encountered is `"signal"`, for the boundary of the area available for routing. pub fn layer(&self) -> &str { match self { Self::Path(x) => &x.layer, Self::Rect(x) => &x.layer, } } pub fn coords(&self) -> Cow<'_, [Point]> { match self { Self::Path(x) => Cow::Borrowed(&x.coords[..]), Self::Rect(x) => Cow::Owned(x.coords()), } } pub fn to_polygon(&self) -> GeoPolygon { GeoPolygon::new( LineString( self.coords() .iter() .map(|i| geo_types::coord! { x: i.x, y: i.y }) .collect(), ), Vec::new(), ) } } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Plane { #[anon] pub net: String, pub polygon: Polygon, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct ViaNames { #[anon_vec] pub names: Vec, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Grid { #[anon] pub kind: String, #[anon] pub value: f64, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct StructureRule { pub width: Option, #[vec("clearance", "clear")] pub clearances: Vec, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Placement { #[vec("component")] pub components: Vec, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Component { #[anon] pub name: String, #[vec("place")] pub places: Vec, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] #[allow(non_snake_case)] pub struct Place { #[anon] pub name: String, #[anon] pub x: f64, #[anon] pub y: f64, #[anon] pub side: String, #[anon] pub rotation: f64, pub PN: Option, } impl Place { pub fn point_with_rotation(&self) -> PointWithRotation { PointWithRotation { pos: (self.x, self.y).into(), rot: self.rotation, } } } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Library { #[vec("image")] pub images: Vec, #[vec("padstack")] pub padstacks: Vec, } impl Library { pub fn find_padstack_by_name(&self, name: &str) -> Option<&Padstack> { self.padstacks.iter().find(|padstack| padstack.name == name) } } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Image { #[anon] pub name: String, #[vec("outline")] pub outlines: Vec, #[vec("pin")] pub pins: Vec, #[anon] pub keepouts: Keepouts, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Outline { pub path: Path, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Pin { #[anon] pub name: String, pub rotate: Option, #[anon] pub id: String, #[anon] pub x: f64, #[anon] pub y: f64, } impl Pin { pub fn point_with_rotation(&self) -> PointWithRotation { PointWithRotation { pos: (self.x, self.y).into(), rot: self.rotate.unwrap_or(0.0), } } } #[derive(Clone, Debug, PartialEq)] pub struct Keepouts(pub Vec); impl ReadDsn for Keepouts { fn read_dsn(tokenizer: &mut ListTokenizer) -> Result { let mut ret = Vec::new(); while let Ok(input) = tokenizer.consume_token() { let is_keepout = input.token.is_start_of(&[ "keepout", "place_keepout", "via_keepout", "wire_keepout", "bend_keepout", "elongate_keepout", ]); tokenizer.return_token(input); if !is_keepout { break; } ret.push(Keepout::read_dsn(tokenizer)?); } Ok(Self(ret)) } } impl WriteSes for Keepouts { fn write_dsn(&self, writer: &mut ListWriter) -> Result<(), io::Error> { for i in &self.0[..] { i.write_dsn(writer)?; } Ok(()) } } #[derive(Clone, Debug, PartialEq)] pub enum KeepoutKind { /// `keepout` Normal, /// `place_keepout` Place, /// `via_keepout` Via, /// `wire_keepout` Wire, /// `bend_keepout` Bend, /// `elongate_keepout` Elongate, } impl core::str::FromStr for KeepoutKind { type Err = (); fn from_str(s: &str) -> Result { Ok(match s { "keepout" => Self::Normal, "place_keepout" => Self::Place, "via_keepout" => Self::Via, "wire_keepout" => Self::Wire, "bend_keepout" => Self::Bend, "elongate_keepout" => Self::Elongate, _ => return Err(()), }) } } impl AsRef for KeepoutKind { fn as_ref(&self) -> &str { match self { Self::Normal => "keepout", Self::Place => "place_keepout", Self::Via => "via_keepout", Self::Wire => "wire_keepout", Self::Bend => "bend_keepout", Self::Elongate => "elongate_keepout", } } } impl fmt::Display for KeepoutKind { #[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.as_ref()) } } #[derive(Clone, Debug, PartialEq)] pub struct Keepout { pub kind: KeepoutKind, pub id_: String, pub sequence_number: Option, pub shape: Shape, pub rule: Option, // TODO: `place_rule`: `(place_rule (spacing ....))` } impl ReadDsn for Keepout { fn read_dsn(tokenizer: &mut ListTokenizer) -> Result { let input = tokenizer.consume_token()?; let err = ParseError::ExpectedStartOfListOneOf(&[ "keepout", "place_keepout", "via_keepout", "wire_keepout", "bend_keepout", "elongate_keepout", ]); let kind = if let ListToken::Start { name: ref kind } = input.token { kind.parse::().map_err(|()| { tokenizer.return_token(input); tokenizer.add_context(err) })? } else { tokenizer.return_token(input); return Err(tokenizer.add_context(err)); }; let id_ = String::read_dsn(tokenizer)?; let sequence_number = tokenizer.read_optional(&["sequence_number"])?; let shape = Shape::read_dsn(tokenizer)?; let rule = tokenizer.read_optional(&["rule"])?; // TODO: handle `place_rule` tokenizer.consume_token()?.expect_end()?; Ok(Self { kind, id_, sequence_number, shape, rule, }) } } impl WriteSes for Keepout { fn write_dsn(&self, writer: &mut ListWriter) -> Result<(), io::Error> { writer.write_token(ListToken::Start { name: self.kind.as_ref().to_string(), })?; self.id_.write_dsn(writer)?; writer.write_optional("sequence_number", &self.sequence_number)?; self.shape.write_dsn(writer)?; writer.write_optional("rule", &self.rule)?; writer.write_token(ListToken::End) } } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Padstack { #[anon] pub name: String, #[vec("shape")] pub shapes: Vec, pub attach: Option, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub enum Shape { Circle(Circle), Rect(Rect), Path(Path), Polygon(Polygon), } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Circle { #[anon] pub layer: String, #[anon] pub diameter: f64, #[anon] pub offset: Option, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Network { #[vec("net")] pub nets: Vec, #[vec("class")] pub classes: Vec, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] // dsn names this "net", but it's a structure unrelated to "net" in wiring or elsewhere pub struct NetPinAssignments { #[anon] pub name: String, pub pins: Option, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Pins { #[anon_vec] pub names: Vec, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Class { #[anon] pub name: String, #[anon_vec] pub nets: Vec, pub circuit: Circuit, pub rule: Rule, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Circuit { pub use_via: String, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Wiring { #[vec("wire")] pub wires: Vec, #[vec("via")] pub vias: Vec, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Wire { pub path: Path, pub net: String, pub r#type: String, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct WireOut { pub path: Path, } //////////////////////////////////////////// // structs that appear in multiple places // //////////////////////////////////////////// // This type isn't meant to be deserialized as is (single points are // more conveniently represented as fields on the enclosing struct) // It exists to give a way to read arrays of coordinates // (and enforce that such an array actually contains a whole number of points) #[derive(Debug, Clone, PartialEq)] pub struct Point { pub x: f64, pub y: f64, } impl From for Point { #[inline(always)] fn from(z: geo_types::Point) -> Self { Point { x: z.0.x, y: z.0.y } } } impl From for geo_types::Point { #[inline(always)] fn from(z: Point) -> Self { geo_types::point! { x: z.x, y: z.y } } } // Custom impl for the case described above impl ReadDsn for Vec { fn read_dsn(tokenizer: &mut ListTokenizer) -> Result { let mut array = Vec::new(); while let Some(x) = tokenizer.read_value::>()? { array.push(x); } Ok(array) } } impl ReadDsn for Option { fn read_dsn(tokenizer: &mut ListTokenizer) -> Result { let input = tokenizer.consume_token()?; Ok(if let ListToken::Leaf { value: ref x } = input.token { let x = x .parse::() .map_err(|_| ParseError::Expected("f64").add_context(input.context))?; let y = tokenizer.read_value::()?; Some(Point { x, y }) } else { tokenizer.return_token(input); None }) } } impl WriteSes for Vec { fn write_dsn(&self, writer: &mut ListWriter) -> Result<(), io::Error> { for elem in self { writer.write_value(&elem.x)?; writer.write_value(&elem.y)?; } Ok(()) } } impl WriteSes for Option { fn write_dsn(&self, writer: &mut ListWriter) -> Result<(), io::Error> { if let Some(value) = self { writer.write_value(&value.x)?; writer.write_value(&value.y)?; } Ok(()) } } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Polygon { #[anon] pub layer: String, #[anon] pub width: f64, #[anon] pub coords: Vec, } impl Polygon { pub fn to_polygon(&self) -> GeoPolygon { GeoPolygon::new( LineString( self.coords .iter() .map(|i| geo_types::coord! { x: i.x, y: i.y }) .collect(), ), Vec::new(), ) } } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Path { #[anon] pub layer: String, #[anon] pub width: f64, #[anon] pub coords: Vec, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Rect { #[anon] pub layer: String, #[anon] pub x1: f64, #[anon] pub y1: f64, #[anon] pub x2: f64, #[anon] pub y2: f64, } impl Rect { fn coords(&self) -> Vec { vec![ Point { x: self.x1, y: self.y1, }, Point { x: self.x2, y: self.y1, }, Point { x: self.x2, y: self.y2, }, Point { x: self.x1, y: self.y2, }, ] } } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Via { #[anon] pub name: String, #[anon] pub x: f64, #[anon] pub y: f64, pub net: String, pub r#type: String, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Rule { pub width: f32, #[vec("clearance")] pub clearances: Vec, } #[derive(ReadDsn, WriteSes, Debug, Clone, PartialEq)] pub struct Clearance { #[anon] pub value: f32, pub r#type: Option, }