build: put `specctra` module into separate crate

This commit is contained in:
Alain Emilia Anna Zscheile 2024-11-28 19:27:52 +01:00
parent e77a034634
commit 0fe23c9c71
21 changed files with 467 additions and 447 deletions

View File

@ -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

View File

@ -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"

View File

@ -1,4 +1,4 @@
use super::read::ParseError;
use crate::error::ParseError;
pub enum ListToken {
Start { name: String },

View File

@ -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),
}

View File

@ -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;

View File

@ -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,
}
}
}

View File

@ -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

View File

@ -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),

View File

@ -1,8 +1,3 @@
use enum_dispatch::enum_dispatch;
use crate::drawing::primitive::Primitive;
#[enum_dispatch]
pub trait GetConditions {
fn conditions(&self) -> Conditions;
}

View File

@ -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;

View File

@ -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
),
);
}
},
}
}

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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>;
}

View File

@ -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);
}
}

View File

@ -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::*;

View File

@ -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>,

View File

@ -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;

View File

@ -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>()?;

View File

@ -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;