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"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
|
bimap = "0.6.3"
|
||||||
derive-getters = "0.5"
|
derive-getters = "0.5"
|
||||||
petgraph = { git = "https://codeberg.org/topola/petgraph.git" }
|
petgraph = { git = "https://codeberg.org/topola/petgraph.git" }
|
||||||
rstar = "0.12"
|
rstar = "0.12"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
spade = "2.12"
|
spade = "2.12"
|
||||||
|
thiserror = "2.0"
|
||||||
|
|
||||||
[workspace.dependencies.geo]
|
[workspace.dependencies.geo]
|
||||||
version = "0.29"
|
version = "0.29"
|
||||||
|
|
@ -29,7 +31,7 @@ default = ["disable_contracts"]
|
||||||
disable_contracts = ["contracts-try/disable_contracts"]
|
disable_contracts = ["contracts-try/disable_contracts"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bimap = "0.6.3"
|
bimap.workspace = true
|
||||||
contracts-try = "0.7"
|
contracts-try = "0.7"
|
||||||
derive-getters.workspace = true
|
derive-getters.workspace = true
|
||||||
enum_dispatch = "0.3"
|
enum_dispatch = "0.3"
|
||||||
|
|
@ -38,11 +40,8 @@ petgraph.workspace = true
|
||||||
rstar.workspace = true
|
rstar.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
spade.workspace = true
|
spade.workspace = true
|
||||||
thiserror = "2.0"
|
specctra-core.path = "crates/specctra-core"
|
||||||
utf8-chars = "3.0"
|
thiserror.workspace = true
|
||||||
|
|
||||||
[dependencies.specctra_derive]
|
|
||||||
path = "crates/specctra_derive"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde_json.workspace = true
|
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 {
|
pub enum ListToken {
|
||||||
Start { name: String },
|
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
|
//! Module for handling Specctra's mesadata - design rules, as well as layers
|
||||||
//! or net properties
|
//! or net properties
|
||||||
|
|
||||||
|
use bimap::BiHashMap;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use bimap::BiHashMap;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
board::mesadata::AccessMesadata,
|
rules::{AccessRules, Conditions},
|
||||||
drawing::rules::{AccessRules, Conditions},
|
structure::Pcb,
|
||||||
specctra::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)]
|
#[derive(Debug)]
|
||||||
/// [`SpecctraRule`] represents the basic routing constraints used by an auto-router, such as
|
/// [`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
|
/// 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::common::ListToken;
|
||||||
|
use super::error::{ParseError, ParseErrorContext};
|
||||||
use super::structure::Parser;
|
use super::structure::Parser;
|
||||||
use thiserror::Error;
|
|
||||||
use utf8_chars::BufReadCharsExt;
|
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 struct InputToken {
|
||||||
pub token: ListToken,
|
pub token: ListToken,
|
||||||
pub context: (usize, usize),
|
pub context: (usize, usize),
|
||||||
|
|
@ -1,8 +1,3 @@
|
||||||
use enum_dispatch::enum_dispatch;
|
|
||||||
|
|
||||||
use crate::drawing::primitive::Primitive;
|
|
||||||
|
|
||||||
#[enum_dispatch]
|
|
||||||
pub trait GetConditions {
|
pub trait GetConditions {
|
||||||
fn conditions(&self) -> Conditions;
|
fn conditions(&self) -> Conditions;
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
use super::common::ListToken;
|
use super::read::{ListTokenizer, ReadDsn};
|
||||||
use super::read::ReadDsn;
|
|
||||||
use super::read::{ListTokenizer, ParseError, ParseErrorContext};
|
|
||||||
use super::write::ListWriter;
|
use super::write::ListWriter;
|
||||||
use super::write::WriteSes;
|
use super::write::WriteSes;
|
||||||
|
use crate::error::{ParseError, ParseErrorContext};
|
||||||
use crate::math::PointWithRotation;
|
use crate::math::PointWithRotation;
|
||||||
|
use crate::ListToken;
|
||||||
use specctra_derive::ReadDsn;
|
use specctra_derive::ReadDsn;
|
||||||
use specctra_derive::WriteSes;
|
use specctra_derive::WriteSes;
|
||||||
|
|
||||||
|
|
@ -10,7 +10,7 @@ use unic_langid::{langid, LanguageIdentifier};
|
||||||
|
|
||||||
use topola::{
|
use topola::{
|
||||||
interactor::activity::InteractiveInput,
|
interactor::activity::InteractiveInput,
|
||||||
specctra::design::{LoadingError as SpecctraLoadingError, SpecctraDesign},
|
specctra::{design::SpecctraDesign, ParseErrorContext as SpecctraLoadingError},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
@ -92,27 +92,29 @@ impl App {
|
||||||
.push_error("tr-module-specctra-dsn-file-loader", err);
|
.push_error("tr-module-specctra-dsn-file-loader", err);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(SpecctraLoadingError::Parse(err)) => {
|
Err(err) => match &err.error {
|
||||||
self.error_dialog.push_error(
|
topola::specctra::ParseError::Io(err) => {
|
||||||
"tr-module-specctra-dsn-file-loader",
|
self.error_dialog.push_error(
|
||||||
format!(
|
"tr-module-specctra-dsn-file-loader",
|
||||||
"{}; {}",
|
format!(
|
||||||
self.translator
|
"{}; {}",
|
||||||
.text("tr-error-failed-to-parse-as-specctra-dsn"),
|
self.translator.text("tr-error-unable-to-read-file"),
|
||||||
err
|
err
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Err(SpecctraLoadingError::Io(err)) => {
|
_ => {
|
||||||
self.error_dialog.push_error(
|
self.error_dialog.push_error(
|
||||||
"tr-module-specctra-dsn-file-loader",
|
"tr-module-specctra-dsn-file-loader",
|
||||||
format!(
|
format!(
|
||||||
"{}; {}",
|
"{}; {}",
|
||||||
self.translator.text("tr-error-unable-to-read-file"),
|
self.translator
|
||||||
err
|
.text("tr-error-failed-to-parse-as-specctra-dsn"),
|
||||||
),
|
err
|
||||||
);
|
),
|
||||||
}
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ use topola::{
|
||||||
},
|
},
|
||||||
interactor::activity::{ActivityContext, ActivityStepperWithStatus, InteractiveInput},
|
interactor::activity::{ActivityContext, ActivityStepperWithStatus, InteractiveInput},
|
||||||
router::RouterOptions,
|
router::RouterOptions,
|
||||||
specctra::design::{LoadingError as SpecctraLoadingError, SpecctraDesign},
|
specctra::{design::SpecctraDesign, ParseError, ParseErrorContext as SpecctraLoadingError},
|
||||||
stepper::Abort,
|
stepper::Abort,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -199,7 +199,7 @@ impl MenuBar {
|
||||||
if let Some(file_handle) = task.await {
|
if let Some(file_handle) = task.await {
|
||||||
let data = handle_file(&file_handle)
|
let data = handle_file(&file_handle)
|
||||||
.await
|
.await
|
||||||
.map_err(Into::into)
|
.map_err(|e| ParseError::from(e).add_context((0, 0)))
|
||||||
.and_then(SpecctraDesign::load);
|
.and_then(SpecctraDesign::load);
|
||||||
content_sender.send(data);
|
content_sender.send(data);
|
||||||
ctx.request_repaint();
|
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
|
//! between nodes, pins, and bands, as well as handle metadata and geometric data
|
||||||
//! for layout construction.
|
//! 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 head;
|
||||||
pub mod loose;
|
pub mod loose;
|
||||||
pub mod primitive;
|
pub mod primitive;
|
||||||
pub mod rules;
|
pub use specctra_core::rules;
|
||||||
pub mod seg;
|
pub mod seg;
|
||||||
|
|
||||||
pub use drawing::*;
|
pub use drawing::*;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ use crate::{
|
||||||
bend::{BendIndex, FixedBendWeight, LooseBendIndex, LooseBendWeight},
|
bend::{BendIndex, FixedBendWeight, LooseBendIndex, LooseBendWeight},
|
||||||
dot::{DotIndex, DotWeight, FixedDotIndex, FixedDotWeight, LooseDotIndex, LooseDotWeight},
|
dot::{DotIndex, DotWeight, FixedDotIndex, FixedDotWeight, LooseDotIndex, LooseDotWeight},
|
||||||
graph::{GetLayer, GetMaybeNet, PrimitiveIndex, PrimitiveWeight, Retag},
|
graph::{GetLayer, GetMaybeNet, PrimitiveIndex, PrimitiveWeight, Retag},
|
||||||
rules::{AccessRules, Conditions, GetConditions},
|
|
||||||
seg::{FixedSegWeight, LoneLooseSegWeight, SegIndex, SeqLooseSegIndex, SeqLooseSegWeight},
|
seg::{FixedSegWeight, LoneLooseSegWeight, SegIndex, SeqLooseSegIndex, SeqLooseSegWeight},
|
||||||
Drawing,
|
Drawing,
|
||||||
},
|
},
|
||||||
|
|
@ -14,6 +13,8 @@ use crate::{
|
||||||
graph::{GenericIndex, GetPetgraphIndex},
|
graph::{GenericIndex, GetPetgraphIndex},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use specctra_core::rules::{AccessRules, Conditions, GetConditions};
|
||||||
|
|
||||||
#[enum_dispatch]
|
#[enum_dispatch]
|
||||||
pub trait GetDrawing<'a, R: AccessRules> {
|
pub trait GetDrawing<'a, R: AccessRules> {
|
||||||
fn drawing(&self) -> &Drawing<impl Copy, R>;
|
fn drawing(&self) -> &Drawing<impl Copy, R>;
|
||||||
|
|
@ -154,8 +155,7 @@ macro_rules! impl_loose_primitive {
|
||||||
GetWidth,
|
GetWidth,
|
||||||
GetDrawing,
|
GetDrawing,
|
||||||
MakePrimitiveShape,
|
MakePrimitiveShape,
|
||||||
GetLimbs,
|
GetLimbs
|
||||||
GetConditions
|
|
||||||
)]
|
)]
|
||||||
pub enum Primitive<'a, CW: Copy, R: AccessRules> {
|
pub enum Primitive<'a, CW: Copy, R: AccessRules> {
|
||||||
FixedDot(FixedDot<'a, CW, R>),
|
FixedDot(FixedDot<'a, CW, R>),
|
||||||
|
|
@ -167,6 +167,20 @@ pub enum Primitive<'a, CW: Copy, R: AccessRules> {
|
||||||
LooseBend(LooseBend<'a, CW, R>),
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct GenericPrimitive<'a, W, CW: Copy, R: AccessRules> {
|
pub struct GenericPrimitive<'a, W, CW: Copy, R: AccessRules> {
|
||||||
pub index: GenericIndex<W>,
|
pub index: GenericIndex<W>,
|
||||||
|
|
|
||||||
43
src/math.rs
43
src/math.rs
|
|
@ -1,6 +1,4 @@
|
||||||
use geo::{geometry::Point, point, EuclideanDistance, Line};
|
use geo::{geometry::Point, point, EuclideanDistance, Line};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::ops::Sub;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug, Clone, Copy, PartialEq)]
|
#[derive(Error, Debug, Clone, Copy, PartialEq)]
|
||||||
|
|
@ -14,46 +12,7 @@ pub struct CanonicalLine {
|
||||||
pub c: f64,
|
pub c: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
pub use specctra_core::math::{Circle, PointWithRotation};
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn _tangent(center: Point, r1: f64, r2: f64) -> Result<CanonicalLine, ()> {
|
fn _tangent(center: Point, r1: f64, r2: f64) -> Result<CanonicalLine, ()> {
|
||||||
let epsilon = 1e-9;
|
let epsilon = 1e-9;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
//! Module for managing the various Specctra PCB design, including loading the
|
//! Module for managing the various Specctra PCB design, including loading the
|
||||||
//! Design DSN file, creating the [`Board`] object from the file, as well as
|
//! Design DSN file, creating the [`Board`] object from the file, as well as
|
||||||
//! exporting the session file
|
//! exporting the session file
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use geo::{point, Point, Rotate};
|
use geo::{point, Point, Rotate};
|
||||||
use thiserror::Error;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
board::{mesadata::AccessMesadata, Board},
|
board::{mesadata::AccessMesadata, Board},
|
||||||
|
|
@ -20,25 +19,13 @@ use crate::{
|
||||||
math::{Circle, PointWithRotation},
|
math::{Circle, PointWithRotation},
|
||||||
specctra::{
|
specctra::{
|
||||||
mesadata::SpecctraMesadata,
|
mesadata::SpecctraMesadata,
|
||||||
read::{self, ListTokenizer},
|
read::ListTokenizer,
|
||||||
structure::{self, DsnFile, Layer, Pcb, Shape},
|
structure::{self, DsnFile, Layer, Pcb, Shape},
|
||||||
write::ListWriter,
|
write::ListWriter,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use read::ParseErrorContext;
|
pub use specctra_core::error::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),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This struct is responsible for managing the various Specctra components of a PCB design,
|
/// 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,
|
/// 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.
|
/// This function reads the Specctra Design data from an input stream.
|
||||||
/// Later the data is parsed and loaded into a [`SpecctraDesign`] structure,
|
/// Later the data is parsed and loaded into a [`SpecctraDesign`] structure,
|
||||||
/// allowing further operations such as rule validation, routing, or netlist management.
|
/// 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 mut list_reader = ListTokenizer::new(reader);
|
||||||
let dsn = list_reader.read_value::<DsnFile>()?;
|
let dsn = list_reader.read_value::<DsnFile>()?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,8 @@
|
||||||
#![forbid(unused_must_use)]
|
#![forbid(unused_must_use)]
|
||||||
#![forbid(clippy::panic_in_result_fn, clippy::unwrap_in_result)]
|
#![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 design;
|
||||||
pub mod mesadata;
|
|
||||||
mod read;
|
|
||||||
mod structure;
|
|
||||||
mod write;
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue