mirror of https://codeberg.org/topola/topola.git
build: put `specctra` module into separate crate
This commit is contained in:
parent
e77a034634
commit
0fe23c9c71
11
Cargo.toml
11
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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use super::read::ParseError;
|
||||
use crate::error::ParseError;
|
||||
|
||||
pub enum ListToken {
|
||||
Start { name: String },
|
||||
|
|
@ -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),
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<usize>;
|
||||
|
||||
/// 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<usize>;
|
||||
}
|
||||
|
||||
#[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
|
||||
|
|
@ -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),
|
||||
|
|
@ -1,8 +1,3 @@
|
|||
use enum_dispatch::enum_dispatch;
|
||||
|
||||
use crate::drawing::primitive::Primitive;
|
||||
|
||||
#[enum_dispatch]
|
||||
pub trait GetConditions {
|
||||
fn conditions(&self) -> Conditions;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
@ -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,7 +92,18 @@ impl App {
|
|||
.push_error("tr-module-specctra-dsn-file-loader", err);
|
||||
}
|
||||
},
|
||||
Err(SpecctraLoadingError::Parse(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!(
|
||||
|
|
@ -103,16 +114,7 @@ impl App {
|
|||
),
|
||||
);
|
||||
}
|
||||
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
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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<M: AccessMesadata> {
|
||||
layout: Layout<M>,
|
||||
// TODO: Simplify access logic to these members so that `#[getter(skip)]`s can be removed.
|
||||
#[getter(skip)]
|
||||
node_to_pinname: HashMap<NodeIndex, String>,
|
||||
#[getter(skip)]
|
||||
band_bandname: BiHashMap<BandUid, BandName>,
|
||||
}
|
||||
|
||||
impl<M: AccessMesadata> Board<M> {
|
||||
/// Initializes the board with given [`Layout`]
|
||||
pub fn new(layout: Layout<M>) -> 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<String>,
|
||||
) -> 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<PolyWeight>,
|
||||
) -> 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<String>,
|
||||
) -> 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<PolyWeight>,
|
||||
) -> 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<String>,
|
||||
) -> GenericIndex<PolyWeight> {
|
||||
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<PolyWeight>,
|
||||
) -> 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<BandUid> {
|
||||
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<M> {
|
||||
&mut self.layout
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: AccessMesadata>
|
||||
ApplyGeometryEdit<
|
||||
PrimitiveWeight,
|
||||
DotWeight,
|
||||
SegWeight,
|
||||
BendWeight,
|
||||
CompoundWeight,
|
||||
PrimitiveIndex,
|
||||
DotIndex,
|
||||
SegIndex,
|
||||
BendIndex,
|
||||
> for Board<M>
|
||||
{
|
||||
fn apply(&mut self, edit: LayoutEdit) {
|
||||
self.layout.apply(edit);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<usize>;
|
||||
|
||||
/// 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<usize>;
|
||||
}
|
||||
281
src/board/mod.rs
281
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<M: AccessMesadata> {
|
||||
layout: Layout<M>,
|
||||
// TODO: Simplify access logic to these members so that `#[getter(skip)]`s can be removed.
|
||||
#[getter(skip)]
|
||||
node_to_pinname: HashMap<NodeIndex, String>,
|
||||
#[getter(skip)]
|
||||
band_bandname: BiHashMap<BandUid, BandName>,
|
||||
}
|
||||
|
||||
impl<M: AccessMesadata> Board<M> {
|
||||
/// Initializes the board with given [`Layout`]
|
||||
pub fn new(layout: Layout<M>) -> 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<String>,
|
||||
) -> 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<PolyWeight>,
|
||||
) -> 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<String>,
|
||||
) -> 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<PolyWeight>,
|
||||
) -> 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<String>,
|
||||
) -> GenericIndex<PolyWeight> {
|
||||
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<PolyWeight>,
|
||||
) -> 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<BandUid> {
|
||||
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<M> {
|
||||
&mut self.layout
|
||||
}
|
||||
}
|
||||
|
||||
impl<M: AccessMesadata>
|
||||
ApplyGeometryEdit<
|
||||
PrimitiveWeight,
|
||||
DotWeight,
|
||||
SegWeight,
|
||||
BendWeight,
|
||||
CompoundWeight,
|
||||
PrimitiveIndex,
|
||||
DotIndex,
|
||||
SegIndex,
|
||||
BendIndex,
|
||||
> for Board<M>
|
||||
{
|
||||
fn apply(&mut self, edit: LayoutEdit) {
|
||||
self.layout.apply(edit);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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::*;
|
||||
|
|
|
|||
|
|
@ -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<impl Copy, R>;
|
||||
|
|
@ -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<W>,
|
||||
|
|
|
|||
43
src/math.rs
43
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<CanonicalLine, ()> {
|
||||
let epsilon = 1e-9;
|
||||
|
|
|
|||
|
|
@ -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<SpecctraDesign, LoadingError> {
|
||||
pub fn load(reader: impl std::io::BufRead) -> Result<SpecctraDesign, ParseErrorContext> {
|
||||
let mut list_reader = ListTokenizer::new(reader);
|
||||
let dsn = list_reader.read_value::<DsnFile>()?;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue