diff --git a/Cargo.toml b/Cargo.toml index cb08873..a116027 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,11 +3,13 @@ members = [".", "crates/*"] resolver = "2" [workspace.dependencies] +bimap = "0.6.3" derive-getters = "0.5" petgraph = { git = "https://codeberg.org/topola/petgraph.git" } rstar = "0.12" serde_json = "1.0" spade = "2.12" +thiserror = "2.0" [workspace.dependencies.geo] version = "0.29" @@ -29,7 +31,7 @@ default = ["disable_contracts"] disable_contracts = ["contracts-try/disable_contracts"] [dependencies] -bimap = "0.6.3" +bimap.workspace = true contracts-try = "0.7" derive-getters.workspace = true enum_dispatch = "0.3" @@ -38,11 +40,8 @@ petgraph.workspace = true rstar.workspace = true serde.workspace = true spade.workspace = true -thiserror = "2.0" -utf8-chars = "3.0" - -[dependencies.specctra_derive] -path = "crates/specctra_derive" +specctra-core.path = "crates/specctra-core" +thiserror.workspace = true [dev-dependencies] serde_json.workspace = true diff --git a/crates/specctra-core/Cargo.toml b/crates/specctra-core/Cargo.toml new file mode 100644 index 0000000..161f05e --- /dev/null +++ b/crates/specctra-core/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "specctra-core" +version = "0.1.0" +edition = "2021" + +[dependencies] +bimap.workspace = true +geo.workspace = true +serde.workspace = true +specctra_derive.path = "../specctra_derive" +thiserror.workspace = true +utf8-chars = "3.0" diff --git a/src/specctra/common.rs b/crates/specctra-core/src/common.rs similarity index 97% rename from src/specctra/common.rs rename to crates/specctra-core/src/common.rs index 1e9a3bc..96c070e 100644 --- a/src/specctra/common.rs +++ b/crates/specctra-core/src/common.rs @@ -1,4 +1,4 @@ -use super::read::ParseError; +use crate::error::ParseError; pub enum ListToken { Start { name: String }, diff --git a/crates/specctra-core/src/error.rs b/crates/specctra-core/src/error.rs new file mode 100644 index 0000000..849f0da --- /dev/null +++ b/crates/specctra-core/src/error.rs @@ -0,0 +1,31 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ParseError { + #[error("unexpected end of file")] + Eof, + #[error(transparent)] + Io(#[from] std::io::Error), + #[error("expected {0}")] + Expected(&'static str), + #[error("expected ({0}")] + ExpectedStartOfList(&'static str), + #[error("found a space inside a quoted string, but file didn't declare this possibility")] + UnexpectedSpaceInQuotedStr, +} + +impl ParseError { + pub fn add_context(self, context: (usize, usize)) -> ParseErrorContext { + ParseErrorContext { + error: self, + context, + } + } +} + +#[derive(Error, Debug)] +#[error("line {}, column {}: {error}", .context.0, .context.1)] +pub struct ParseErrorContext { + pub error: ParseError, + pub context: (usize, usize), +} diff --git a/crates/specctra-core/src/lib.rs b/crates/specctra-core/src/lib.rs new file mode 100644 index 0000000..383ef7d --- /dev/null +++ b/crates/specctra-core/src/lib.rs @@ -0,0 +1,12 @@ +//! Module containing the informations about handling the Specctra +//! based file format, and parsing it into Topola's objects + +mod common; +pub use common::*; +pub mod error; +pub mod math; +pub mod mesadata; +pub mod read; +pub mod rules; +pub mod structure; +pub mod write; diff --git a/crates/specctra-core/src/math.rs b/crates/specctra-core/src/math.rs new file mode 100644 index 0000000..e098d2b --- /dev/null +++ b/crates/specctra-core/src/math.rs @@ -0,0 +1,44 @@ +use core::ops::Sub; +use geo::geometry::Point; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +pub struct Circle { + pub pos: Point, + pub r: f64, +} + +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +pub struct PointWithRotation { + pub pos: Point, + pub rot: f64, +} + +impl Sub for Circle { + type Output = Self; + + fn sub(self, other: Self) -> Self { + Self { + pos: self.pos - other.pos, + r: self.r, + } + } +} + +impl Default for PointWithRotation { + fn default() -> Self { + Self { + pos: (0.0, 0.0).into(), + rot: 0.0, + } + } +} + +impl PointWithRotation { + pub fn from_xy(x: f64, y: f64) -> Self { + Self { + pos: (x, y).into(), + rot: 0.0, + } + } +} diff --git a/src/specctra/mesadata.rs b/crates/specctra-core/src/mesadata.rs similarity index 88% rename from src/specctra/mesadata.rs rename to crates/specctra-core/src/mesadata.rs index 153d9be..2af29f4 100644 --- a/src/specctra/mesadata.rs +++ b/crates/specctra-core/src/mesadata.rs @@ -1,16 +1,38 @@ //! Module for handling Specctra's mesadata - design rules, as well as layers //! or net properties +use bimap::BiHashMap; use std::collections::HashMap; -use bimap::BiHashMap; - use crate::{ - board::mesadata::AccessMesadata, - drawing::rules::{AccessRules, Conditions}, - specctra::structure::Pcb, + rules::{AccessRules, Conditions}, + structure::Pcb, }; +/// Trait for managing the Specctra's mesadata +/// +/// This trait implements generic function for accessing or modifying different +/// compounds of board parts like nets or layers +pub trait AccessMesadata: AccessRules { + /// Renames a layer based on its index. + fn bename_layer(&mut self, layer: usize, layername: String); + + /// Retrieves the name of a layer by its index. + fn layer_layername(&self, layer: usize) -> Option<&str>; + + /// Retrieves the index of a layer by its name. + fn layername_layer(&self, layername: &str) -> Option; + + /// Renames a net based on its index. + fn bename_net(&mut self, net: usize, netname: String); + + /// Retrieves the name of a net by its index. + fn net_netname(&self, net: usize) -> Option<&str>; + + /// Retrieves the index of a net by its name. + fn netname_net(&self, netname: &str) -> Option; +} + #[derive(Debug)] /// [`SpecctraRule`] represents the basic routing constraints used by an auto-router, such as /// the Topola auto-router, in a PCB design process. This struct defines two key design diff --git a/src/specctra/read.rs b/crates/specctra-core/src/read.rs similarity index 93% rename from src/specctra/read.rs rename to crates/specctra-core/src/read.rs index 28c2456..4bd6cfa 100644 --- a/src/specctra/read.rs +++ b/crates/specctra-core/src/read.rs @@ -1,38 +1,8 @@ use super::common::ListToken; +use super::error::{ParseError, ParseErrorContext}; use super::structure::Parser; -use thiserror::Error; use utf8_chars::BufReadCharsExt; -#[derive(Error, Debug)] -pub enum ParseError { - #[error("unexpected end of file")] - Eof, - #[error(transparent)] - Io(#[from] std::io::Error), - #[error("expected {0}")] - Expected(&'static str), - #[error("expected ({0}")] - ExpectedStartOfList(&'static str), - #[error("found a space inside a quoted string, but file didn't declare this possibility")] - UnexpectedSpaceInQuotedStr, -} - -impl ParseError { - pub fn add_context(self, context: (usize, usize)) -> ParseErrorContext { - ParseErrorContext { - error: self, - context, - } - } -} - -#[derive(Error, Debug)] -#[error("line {}, column {}: {error}", .context.0, .context.1)] -pub struct ParseErrorContext { - error: ParseError, - context: (usize, usize), -} - pub struct InputToken { pub token: ListToken, pub context: (usize, usize), diff --git a/src/drawing/rules.rs b/crates/specctra-core/src/rules.rs similarity index 80% rename from src/drawing/rules.rs rename to crates/specctra-core/src/rules.rs index 0a617bb..2fba102 100644 --- a/src/drawing/rules.rs +++ b/crates/specctra-core/src/rules.rs @@ -1,8 +1,3 @@ -use enum_dispatch::enum_dispatch; - -use crate::drawing::primitive::Primitive; - -#[enum_dispatch] pub trait GetConditions { fn conditions(&self) -> Conditions; } diff --git a/src/specctra/structure.rs b/crates/specctra-core/src/structure.rs similarity index 98% rename from src/specctra/structure.rs rename to crates/specctra-core/src/structure.rs index 40cce8c..b8e4951 100644 --- a/src/specctra/structure.rs +++ b/crates/specctra-core/src/structure.rs @@ -1,9 +1,9 @@ -use super::common::ListToken; -use super::read::ReadDsn; -use super::read::{ListTokenizer, ParseError, ParseErrorContext}; +use super::read::{ListTokenizer, ReadDsn}; use super::write::ListWriter; use super::write::WriteSes; +use crate::error::{ParseError, ParseErrorContext}; use crate::math::PointWithRotation; +use crate::ListToken; use specctra_derive::ReadDsn; use specctra_derive::WriteSes; diff --git a/src/specctra/write.rs b/crates/specctra-core/src/write.rs similarity index 100% rename from src/specctra/write.rs rename to crates/specctra-core/src/write.rs diff --git a/crates/topola-egui/src/app.rs b/crates/topola-egui/src/app.rs index bfea6b6..dfe0dfc 100644 --- a/crates/topola-egui/src/app.rs +++ b/crates/topola-egui/src/app.rs @@ -10,7 +10,7 @@ use unic_langid::{langid, LanguageIdentifier}; use topola::{ interactor::activity::InteractiveInput, - specctra::design::{LoadingError as SpecctraLoadingError, SpecctraDesign}, + specctra::{design::SpecctraDesign, ParseErrorContext as SpecctraLoadingError}, }; use crate::{ @@ -92,27 +92,29 @@ impl App { .push_error("tr-module-specctra-dsn-file-loader", err); } }, - Err(SpecctraLoadingError::Parse(err)) => { - self.error_dialog.push_error( - "tr-module-specctra-dsn-file-loader", - format!( - "{}; {}", - self.translator - .text("tr-error-failed-to-parse-as-specctra-dsn"), - err - ), - ); - } - Err(SpecctraLoadingError::Io(err)) => { - self.error_dialog.push_error( - "tr-module-specctra-dsn-file-loader", - format!( - "{}; {}", - self.translator.text("tr-error-unable-to-read-file"), - err - ), - ); - } + Err(err) => match &err.error { + topola::specctra::ParseError::Io(err) => { + self.error_dialog.push_error( + "tr-module-specctra-dsn-file-loader", + format!( + "{}; {}", + self.translator.text("tr-error-unable-to-read-file"), + err + ), + ); + } + _ => { + self.error_dialog.push_error( + "tr-module-specctra-dsn-file-loader", + format!( + "{}; {}", + self.translator + .text("tr-error-failed-to-parse-as-specctra-dsn"), + err + ), + ); + } + }, } } diff --git a/crates/topola-egui/src/menu_bar.rs b/crates/topola-egui/src/menu_bar.rs index a766546..9d8e343 100644 --- a/crates/topola-egui/src/menu_bar.rs +++ b/crates/topola-egui/src/menu_bar.rs @@ -6,7 +6,7 @@ use topola::{ }, interactor::activity::{ActivityContext, ActivityStepperWithStatus, InteractiveInput}, router::RouterOptions, - specctra::design::{LoadingError as SpecctraLoadingError, SpecctraDesign}, + specctra::{design::SpecctraDesign, ParseError, ParseErrorContext as SpecctraLoadingError}, stepper::Abort, }; @@ -199,7 +199,7 @@ impl MenuBar { if let Some(file_handle) = task.await { let data = handle_file(&file_handle) .await - .map_err(Into::into) + .map_err(|e| ParseError::from(e).add_context((0, 0))) .and_then(SpecctraDesign::load); content_sender.send(data); ctx.request_repaint(); diff --git a/src/board/board.rs b/src/board/board.rs deleted file mode 100644 index cabb736..0000000 --- a/src/board/board.rs +++ /dev/null @@ -1,275 +0,0 @@ -use std::{cmp::Ordering, collections::HashMap}; - -use bimap::BiHashMap; -use derive_getters::Getters; -use serde::{Deserialize, Serialize}; - -use crate::{ - board::mesadata::AccessMesadata, - drawing::{ - band::BandUid, - bend::{BendIndex, BendWeight}, - dot::{DotIndex, DotWeight, FixedDotIndex, FixedDotWeight}, - graph::{GetLayer, GetMaybeNet, PrimitiveIndex, PrimitiveWeight}, - seg::{FixedSegIndex, FixedSegWeight, SegIndex, SegWeight}, - }, - geometry::{edit::ApplyGeometryEdit, shape::AccessShape, GenericNode}, - graph::GenericIndex, - layout::{ - poly::{GetMaybeApex, MakePolyShape, PolyWeight}, - CompoundWeight, Layout, LayoutEdit, NodeIndex, - }, - math::Circle, -}; - -/// Represents a band between two pins. -#[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] -pub struct BandName(String, String); - -impl BandName { - /// Creates a new [`BandName`] and manages their order. - /// - /// This function ensures that the two pin names are sorted in lexicographical order, so that the smaller name always comes first. - pub fn new(pinname1: String, pinname2: String) -> Self { - if pinname1.cmp(&pinname2) == Ordering::Greater { - BandName(pinname2, pinname1) - } else { - BandName(pinname1, pinname2) - } - } -} - -/// Represents a board layout and its associated metadata. -/// -/// The struct manages the relationships between board's layout, -/// and its compounds, as well as provides methods to manipulate them. -#[derive(Debug, Getters)] -pub struct Board { - layout: Layout, - // TODO: Simplify access logic to these members so that `#[getter(skip)]`s can be removed. - #[getter(skip)] - node_to_pinname: HashMap, - #[getter(skip)] - band_bandname: BiHashMap, -} - -impl Board { - /// Initializes the board with given [`Layout`] - pub fn new(layout: Layout) -> Self { - Self { - layout, - node_to_pinname: HashMap::new(), - band_bandname: BiHashMap::new(), - } - } - - /// Adds a new fixed dot with an optional pin name. - /// - /// Inserts the dot into the layout and, if a pin name is provided, maps it to the created dot's node. - pub fn add_fixed_dot_infringably( - &mut self, - recorder: &mut LayoutEdit, - weight: FixedDotWeight, - maybe_pin: Option, - ) -> FixedDotIndex { - let dot = self.layout.add_fixed_dot_infringably(recorder, weight); - - if let Some(ref pin) = maybe_pin { - self.node_to_pinname - .insert(GenericNode::Primitive(dot.into()), pin.clone()); - } - - dot - } - - /// Adds a fixed segment between two dots with an optional pin name. - /// - /// Adds the segment to the layout and maps the pin name to the created segment if provided. - pub fn add_poly_fixed_dot_infringably( - &mut self, - recorder: &mut LayoutEdit, - weight: FixedDotWeight, - poly: GenericIndex, - ) -> FixedDotIndex { - let dot = self - .layout - .add_poly_fixed_dot_infringably(recorder, weight, poly); - - if let Some(pin) = self.node_pinname(&GenericNode::Compound(poly.into())) { - self.node_to_pinname - .insert(GenericNode::Primitive(dot.into()), pin.to_string()); - } - - dot - } - - /// Adds a fixed segment associated with a polygon in the layout. - /// - /// Adds the segment to the layout and updates the internal mapping if necessary. - pub fn add_fixed_seg_infringably( - &mut self, - recorder: &mut LayoutEdit, - from: FixedDotIndex, - to: FixedDotIndex, - weight: FixedSegWeight, - maybe_pin: Option, - ) -> FixedSegIndex { - let seg = self - .layout - .add_fixed_seg_infringably(recorder, from, to, weight); - - if let Some(pin) = maybe_pin { - self.node_to_pinname - .insert(GenericNode::Primitive(seg.into()), pin.to_string()); - } - - seg - } - - /// Adds a fixed segment associated with a polygon in the layout. - /// - /// Adds the segment to the layout and updates the internal mapping if necessary. - pub fn add_poly_fixed_seg_infringably( - &mut self, - recorder: &mut LayoutEdit, - from: FixedDotIndex, - to: FixedDotIndex, - weight: FixedSegWeight, - poly: GenericIndex, - ) -> FixedSegIndex { - let seg = self - .layout - .add_poly_fixed_seg_infringably(recorder, from, to, weight, poly); - - if let Some(pin) = self.node_pinname(&GenericNode::Compound(poly.into())) { - self.node_to_pinname - .insert(GenericNode::Primitive(seg.into()), pin.to_string()); - } - - seg - } - - /// Adds a new polygon to the layout with an optional pin name. - /// - /// Inserts the polygon into the layout and, if a pin name is provided, maps it to the created polygon's node. - pub fn add_poly( - &mut self, - recorder: &mut LayoutEdit, - weight: PolyWeight, - maybe_pin: Option, - ) -> GenericIndex { - let poly = self.layout.add_poly(recorder, weight); - - if let Some(pin) = maybe_pin { - self.node_to_pinname - .insert(GenericNode::Compound(poly.into()), pin.to_string()); - } - - poly - } - - /// Retrieves or creates the apex (center point) of a polygon in the layout. - /// - /// If the polygon already has an apex, returns it. Otherwise, creates and returns a new fixed dot as the apex. - pub fn poly_apex( - &mut self, - recorder: &mut LayoutEdit, - poly: GenericIndex, - ) -> FixedDotIndex { - if let Some(apex) = self.layout.poly(poly).maybe_apex() { - apex - } else { - self.add_poly_fixed_dot_infringably( - recorder, - FixedDotWeight { - circle: Circle { - pos: self.layout.poly(poly).shape().center(), - r: 100.0, - }, - layer: self.layout.poly(poly).layer(), - maybe_net: self.layout.poly(poly).maybe_net(), - }, - poly, - ) - } - } - - /// Returns the pin name associated with a given node. - pub fn node_pinname(&self, node: &NodeIndex) -> Option<&String> { - self.node_to_pinname.get(node) - } - - /// Returns the band name associated with a given band. - pub fn band_bandname(&self, band: &BandUid) -> Option<&BandName> { - self.band_bandname.get_by_left(band) - } - - /// Returns the unique id associated with a given band name. - pub fn bandname_band(&self, bandname: &BandName) -> Option<&BandUid> { - self.band_bandname.get_by_right(bandname) - } - - /// Creates band between the two nodes - pub fn try_set_band_between_nodes( - &mut self, - source: FixedDotIndex, - target: FixedDotIndex, - band: BandUid, - ) { - let source_pinname = self - .node_pinname(&GenericNode::Primitive(source.into())) - .unwrap() - .to_string(); - let target_pinname = self - .node_pinname(&GenericNode::Primitive(target.into())) - .unwrap() - .to_string(); - self.band_bandname - .insert(band, BandName::new(source_pinname, target_pinname)); - } - - /// Finds a band between two pin names. - pub fn band_between_pins(&self, pinname1: &str, pinname2: &str) -> Option { - if let Some(band) = self - .band_bandname - .get_by_right(&BandName::new(pinname1.to_string(), pinname2.to_string())) - { - Some(*band) - } else if let Some(band) = self - .band_bandname - .get_by_right(&BandName::new(pinname2.to_string(), pinname1.to_string())) - { - Some(*band) - } else { - None - } - } - - /// Returns the mesadata associated with the layout's drawing rules. - pub fn mesadata(&self) -> &M { - self.layout.drawing().rules() - } - - /// Returns a mutable reference to the layout, allowing modifications. - pub fn layout_mut(&mut self) -> &mut Layout { - &mut self.layout - } -} - -impl - ApplyGeometryEdit< - PrimitiveWeight, - DotWeight, - SegWeight, - BendWeight, - CompoundWeight, - PrimitiveIndex, - DotIndex, - SegIndex, - BendIndex, - > for Board -{ - fn apply(&mut self, edit: LayoutEdit) { - self.layout.apply(edit); - } -} diff --git a/src/board/mesadata.rs b/src/board/mesadata.rs deleted file mode 100644 index e368c92..0000000 --- a/src/board/mesadata.rs +++ /dev/null @@ -1,26 +0,0 @@ -//! Module implementing the logic behind board metadata -use crate::drawing::rules::AccessRules; - -/// Trait for managing the Specctra's mesadata -/// -/// This trait implements generic function for accessing or modifying different -/// compounds of board parts like nets or layers -pub trait AccessMesadata: AccessRules { - /// Renames a layer based on its index. - fn bename_layer(&mut self, layer: usize, layername: String); - - /// Retrieves the name of a layer by its index. - fn layer_layername(&self, layer: usize) -> Option<&str>; - - /// Retrieves the index of a layer by its name. - fn layername_layer(&self, layername: &str) -> Option; - - /// Renames a net based on its index. - fn bename_net(&mut self, net: usize, netname: String); - - /// Retrieves the name of a net by its index. - fn net_netname(&self, net: usize) -> Option<&str>; - - /// Retrieves the index of a net by its name. - fn netname_net(&self, netname: &str) -> Option; -} diff --git a/src/board/mod.rs b/src/board/mod.rs index 2ced2e4..567163a 100644 --- a/src/board/mod.rs +++ b/src/board/mod.rs @@ -2,7 +2,282 @@ //! between nodes, pins, and bands, as well as handle metadata and geometric data //! for layout construction. -mod board; -pub mod mesadata; +pub mod mesadata { + pub use specctra_core::mesadata::AccessMesadata; +} -pub use board::*; +use std::{cmp::Ordering, collections::HashMap}; + +use bimap::BiHashMap; +use derive_getters::Getters; +use serde::{Deserialize, Serialize}; + +use crate::{ + board::mesadata::AccessMesadata, + drawing::{ + band::BandUid, + bend::{BendIndex, BendWeight}, + dot::{DotIndex, DotWeight, FixedDotIndex, FixedDotWeight}, + graph::{GetLayer, GetMaybeNet, PrimitiveIndex, PrimitiveWeight}, + seg::{FixedSegIndex, FixedSegWeight, SegIndex, SegWeight}, + }, + geometry::{edit::ApplyGeometryEdit, shape::AccessShape, GenericNode}, + graph::GenericIndex, + layout::{ + poly::{GetMaybeApex, MakePolyShape, PolyWeight}, + CompoundWeight, Layout, LayoutEdit, NodeIndex, + }, + math::Circle, +}; + +/// Represents a band between two pins. +#[derive(Debug, Hash, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BandName(String, String); + +impl BandName { + /// Creates a new [`BandName`] and manages their order. + /// + /// This function ensures that the two pin names are sorted in lexicographical order, so that the smaller name always comes first. + pub fn new(pinname1: String, pinname2: String) -> Self { + if pinname1.cmp(&pinname2) == Ordering::Greater { + BandName(pinname2, pinname1) + } else { + BandName(pinname1, pinname2) + } + } +} + +/// Represents a board layout and its associated metadata. +/// +/// The struct manages the relationships between board's layout, +/// and its compounds, as well as provides methods to manipulate them. +#[derive(Debug, Getters)] +pub struct Board { + layout: Layout, + // TODO: Simplify access logic to these members so that `#[getter(skip)]`s can be removed. + #[getter(skip)] + node_to_pinname: HashMap, + #[getter(skip)] + band_bandname: BiHashMap, +} + +impl Board { + /// Initializes the board with given [`Layout`] + pub fn new(layout: Layout) -> Self { + Self { + layout, + node_to_pinname: HashMap::new(), + band_bandname: BiHashMap::new(), + } + } + + /// Adds a new fixed dot with an optional pin name. + /// + /// Inserts the dot into the layout and, if a pin name is provided, maps it to the created dot's node. + pub fn add_fixed_dot_infringably( + &mut self, + recorder: &mut LayoutEdit, + weight: FixedDotWeight, + maybe_pin: Option, + ) -> FixedDotIndex { + let dot = self.layout.add_fixed_dot_infringably(recorder, weight); + + if let Some(ref pin) = maybe_pin { + self.node_to_pinname + .insert(GenericNode::Primitive(dot.into()), pin.clone()); + } + + dot + } + + /// Adds a fixed segment between two dots with an optional pin name. + /// + /// Adds the segment to the layout and maps the pin name to the created segment if provided. + pub fn add_poly_fixed_dot_infringably( + &mut self, + recorder: &mut LayoutEdit, + weight: FixedDotWeight, + poly: GenericIndex, + ) -> FixedDotIndex { + let dot = self + .layout + .add_poly_fixed_dot_infringably(recorder, weight, poly); + + if let Some(pin) = self.node_pinname(&GenericNode::Compound(poly.into())) { + self.node_to_pinname + .insert(GenericNode::Primitive(dot.into()), pin.to_string()); + } + + dot + } + + /// Adds a fixed segment associated with a polygon in the layout. + /// + /// Adds the segment to the layout and updates the internal mapping if necessary. + pub fn add_fixed_seg_infringably( + &mut self, + recorder: &mut LayoutEdit, + from: FixedDotIndex, + to: FixedDotIndex, + weight: FixedSegWeight, + maybe_pin: Option, + ) -> FixedSegIndex { + let seg = self + .layout + .add_fixed_seg_infringably(recorder, from, to, weight); + + if let Some(pin) = maybe_pin { + self.node_to_pinname + .insert(GenericNode::Primitive(seg.into()), pin.to_string()); + } + + seg + } + + /// Adds a fixed segment associated with a polygon in the layout. + /// + /// Adds the segment to the layout and updates the internal mapping if necessary. + pub fn add_poly_fixed_seg_infringably( + &mut self, + recorder: &mut LayoutEdit, + from: FixedDotIndex, + to: FixedDotIndex, + weight: FixedSegWeight, + poly: GenericIndex, + ) -> FixedSegIndex { + let seg = self + .layout + .add_poly_fixed_seg_infringably(recorder, from, to, weight, poly); + + if let Some(pin) = self.node_pinname(&GenericNode::Compound(poly.into())) { + self.node_to_pinname + .insert(GenericNode::Primitive(seg.into()), pin.to_string()); + } + + seg + } + + /// Adds a new polygon to the layout with an optional pin name. + /// + /// Inserts the polygon into the layout and, if a pin name is provided, maps it to the created polygon's node. + pub fn add_poly( + &mut self, + recorder: &mut LayoutEdit, + weight: PolyWeight, + maybe_pin: Option, + ) -> GenericIndex { + let poly = self.layout.add_poly(recorder, weight); + + if let Some(pin) = maybe_pin { + self.node_to_pinname + .insert(GenericNode::Compound(poly.into()), pin.to_string()); + } + + poly + } + + /// Retrieves or creates the apex (center point) of a polygon in the layout. + /// + /// If the polygon already has an apex, returns it. Otherwise, creates and returns a new fixed dot as the apex. + pub fn poly_apex( + &mut self, + recorder: &mut LayoutEdit, + poly: GenericIndex, + ) -> FixedDotIndex { + if let Some(apex) = self.layout.poly(poly).maybe_apex() { + apex + } else { + self.add_poly_fixed_dot_infringably( + recorder, + FixedDotWeight { + circle: Circle { + pos: self.layout.poly(poly).shape().center(), + r: 100.0, + }, + layer: self.layout.poly(poly).layer(), + maybe_net: self.layout.poly(poly).maybe_net(), + }, + poly, + ) + } + } + + /// Returns the pin name associated with a given node. + pub fn node_pinname(&self, node: &NodeIndex) -> Option<&String> { + self.node_to_pinname.get(node) + } + + /// Returns the band name associated with a given band. + pub fn band_bandname(&self, band: &BandUid) -> Option<&BandName> { + self.band_bandname.get_by_left(band) + } + + /// Returns the unique id associated with a given band name. + pub fn bandname_band(&self, bandname: &BandName) -> Option<&BandUid> { + self.band_bandname.get_by_right(bandname) + } + + /// Creates band between the two nodes + pub fn try_set_band_between_nodes( + &mut self, + source: FixedDotIndex, + target: FixedDotIndex, + band: BandUid, + ) { + let source_pinname = self + .node_pinname(&GenericNode::Primitive(source.into())) + .unwrap() + .to_string(); + let target_pinname = self + .node_pinname(&GenericNode::Primitive(target.into())) + .unwrap() + .to_string(); + self.band_bandname + .insert(band, BandName::new(source_pinname, target_pinname)); + } + + /// Finds a band between two pin names. + pub fn band_between_pins(&self, pinname1: &str, pinname2: &str) -> Option { + if let Some(band) = self + .band_bandname + .get_by_right(&BandName::new(pinname1.to_string(), pinname2.to_string())) + { + Some(*band) + } else if let Some(band) = self + .band_bandname + .get_by_right(&BandName::new(pinname2.to_string(), pinname1.to_string())) + { + Some(*band) + } else { + None + } + } + + /// Returns the mesadata associated with the layout's drawing rules. + pub fn mesadata(&self) -> &M { + self.layout.drawing().rules() + } + + /// Returns a mutable reference to the layout, allowing modifications. + pub fn layout_mut(&mut self) -> &mut Layout { + &mut self.layout + } +} + +impl + ApplyGeometryEdit< + PrimitiveWeight, + DotWeight, + SegWeight, + BendWeight, + CompoundWeight, + PrimitiveIndex, + DotIndex, + SegIndex, + BendIndex, + > for Board +{ + fn apply(&mut self, edit: LayoutEdit) { + self.layout.apply(edit); + } +} diff --git a/src/drawing/mod.rs b/src/drawing/mod.rs index 585da9b..bef5cd8 100644 --- a/src/drawing/mod.rs +++ b/src/drawing/mod.rs @@ -11,7 +11,7 @@ pub mod guide; pub mod head; pub mod loose; pub mod primitive; -pub mod rules; +pub use specctra_core::rules; pub mod seg; pub use drawing::*; diff --git a/src/drawing/primitive.rs b/src/drawing/primitive.rs index eda9c19..22d3185 100644 --- a/src/drawing/primitive.rs +++ b/src/drawing/primitive.rs @@ -6,7 +6,6 @@ use crate::{ bend::{BendIndex, FixedBendWeight, LooseBendIndex, LooseBendWeight}, dot::{DotIndex, DotWeight, FixedDotIndex, FixedDotWeight, LooseDotIndex, LooseDotWeight}, graph::{GetLayer, GetMaybeNet, PrimitiveIndex, PrimitiveWeight, Retag}, - rules::{AccessRules, Conditions, GetConditions}, seg::{FixedSegWeight, LoneLooseSegWeight, SegIndex, SeqLooseSegIndex, SeqLooseSegWeight}, Drawing, }, @@ -14,6 +13,8 @@ use crate::{ graph::{GenericIndex, GetPetgraphIndex}, }; +use specctra_core::rules::{AccessRules, Conditions, GetConditions}; + #[enum_dispatch] pub trait GetDrawing<'a, R: AccessRules> { fn drawing(&self) -> &Drawing; @@ -154,8 +155,7 @@ macro_rules! impl_loose_primitive { GetWidth, GetDrawing, MakePrimitiveShape, - GetLimbs, - GetConditions + GetLimbs )] pub enum Primitive<'a, CW: Copy, R: AccessRules> { FixedDot(FixedDot<'a, CW, R>), @@ -167,6 +167,20 @@ pub enum Primitive<'a, CW: Copy, R: AccessRules> { LooseBend(LooseBend<'a, CW, R>), } +impl<'a, CW: Copy, R: AccessRules> specctra_core::rules::GetConditions for Primitive<'a, CW, R> { + fn conditions(&self) -> specctra_core::rules::Conditions { + match self { + Self::FixedDot(x) => x.conditions(), + Self::LooseDot(x) => x.conditions(), + Self::FixedSeg(x) => x.conditions(), + Self::LoneLooseSeg(x) => x.conditions(), + Self::SeqLooseSeg(x) => x.conditions(), + Self::FixedBend(x) => x.conditions(), + Self::LooseBend(x) => x.conditions(), + } + } +} + #[derive(Debug)] pub struct GenericPrimitive<'a, W, CW: Copy, R: AccessRules> { pub index: GenericIndex, diff --git a/src/math.rs b/src/math.rs index b712350..c1c1d4b 100644 --- a/src/math.rs +++ b/src/math.rs @@ -1,6 +1,4 @@ use geo::{geometry::Point, point, EuclideanDistance, Line}; -use serde::{Deserialize, Serialize}; -use std::ops::Sub; use thiserror::Error; #[derive(Error, Debug, Clone, Copy, PartialEq)] @@ -14,46 +12,7 @@ pub struct CanonicalLine { pub c: f64, } -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] -pub struct Circle { - pub pos: Point, - pub r: f64, -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub struct PointWithRotation { - pub pos: Point, - pub rot: f64, -} - -impl Sub for Circle { - type Output = Self; - - fn sub(self, other: Self) -> Self { - Self { - pos: self.pos - other.pos, - r: self.r, - } - } -} - -impl Default for PointWithRotation { - fn default() -> Self { - Self { - pos: (0.0, 0.0).into(), - rot: 0.0, - } - } -} - -impl PointWithRotation { - pub fn from_xy(x: f64, y: f64) -> Self { - Self { - pos: (x, y).into(), - rot: 0.0, - } - } -} +pub use specctra_core::math::{Circle, PointWithRotation}; fn _tangent(center: Point, r1: f64, r2: f64) -> Result { let epsilon = 1e-9; diff --git a/src/specctra/design.rs b/src/specctra/design.rs index 417eece..c41b8dc 100644 --- a/src/specctra/design.rs +++ b/src/specctra/design.rs @@ -1,10 +1,9 @@ //! Module for managing the various Specctra PCB design, including loading the //! Design DSN file, creating the [`Board`] object from the file, as well as //! exporting the session file -use std::collections::HashMap; use geo::{point, Point, Rotate}; -use thiserror::Error; +use std::collections::HashMap; use crate::{ board::{mesadata::AccessMesadata, Board}, @@ -20,25 +19,13 @@ use crate::{ math::{Circle, PointWithRotation}, specctra::{ mesadata::SpecctraMesadata, - read::{self, ListTokenizer}, + read::ListTokenizer, structure::{self, DsnFile, Layer, Pcb, Shape}, write::ListWriter, }, }; -pub use read::ParseErrorContext; - -/// Errors raised by [`SpecctraDesign::load`] -#[derive(Error, Debug)] -pub enum LoadingError { - /// I/O file reading error from [`std::io::Error`] - #[error(transparent)] - Io(#[from] std::io::Error), - /// File parsing errors containing information about unexpected end of file, - /// or any other parsing issues with provided DSN file - #[error(transparent)] - Parse(#[from] read::ParseErrorContext), -} +pub use specctra_core::error::ParseErrorContext; /// This struct is responsible for managing the various Specctra components of a PCB design, /// including parsing the DSN file, handling the resolution, unit of measurement, @@ -55,7 +42,7 @@ impl SpecctraDesign { /// This function reads the Specctra Design data from an input stream. /// Later the data is parsed and loaded into a [`SpecctraDesign`] structure, /// allowing further operations such as rule validation, routing, or netlist management. - pub fn load(reader: impl std::io::BufRead) -> Result { + pub fn load(reader: impl std::io::BufRead) -> Result { let mut list_reader = ListTokenizer::new(reader); let dsn = list_reader.read_value::()?; diff --git a/src/specctra/mod.rs b/src/specctra/mod.rs index f8e7b7f..d16ed70 100644 --- a/src/specctra/mod.rs +++ b/src/specctra/mod.rs @@ -3,9 +3,8 @@ #![forbid(unused_must_use)] #![forbid(clippy::panic_in_result_fn, clippy::unwrap_in_result)] -mod common; +pub use specctra_core::error::{ParseError, ParseErrorContext}; +pub use specctra_core::mesadata; +use specctra_core::*; + pub mod design; -pub mod mesadata; -mod read; -mod structure; -mod write;