feat(board/mod): Make it possible to iterate over nodes under pin name

This commit is contained in:
Mikolaj Wielgus 2025-09-19 13:05:31 +02:00
parent d708c7177a
commit da37fbe2ec
7 changed files with 144 additions and 33 deletions

View File

@ -35,6 +35,8 @@ allowed_scopes = [
"autorouter/ratline", "autorouter/ratline",
"autorouter/remove_bands", "autorouter/remove_bands",
"autorouter/selection", "autorouter/selection",
"board/mod",
"board/edit",
"drawing/band", "drawing/band",
"drawing/bend", "drawing/bend",
"drawing/cane", "drawing/cane",

100
src/bimapset.rs Normal file
View File

@ -0,0 +1,100 @@
// SPDX-FileCopyrightText: 2025 Topola contributors
//
// SPDX-License-Identifier: MIT
use std::{
borrow::Borrow,
collections::{BTreeMap, BTreeSet},
};
/// A bidirectional map between keys and sets of values.
///
/// - Each key can have multiple associated values (`BTreeSet<V>`).
/// - Each value maps to exactly one key (i.e., it's unique across keys).
#[derive(Debug)]
pub struct BiBTreeMapSet<K, V> {
key_to_values: BTreeMap<K, BTreeSet<V>>, // Forward mapping: key -> set of values.
value_to_key: BTreeMap<V, K>, // Reverse mapping: value -> key.
}
impl<K: Eq + Ord + Clone, V: Eq + Ord + Clone> BiBTreeMapSet<K, V> {
/// Creates a new, empty `BiBTreeMapSet`.
pub fn new() -> Self {
Self {
key_to_values: BTreeMap::new(),
value_to_key: BTreeMap::new(),
}
}
/// Inserts a (key, value) pair.
///
/// If the value was previously associated with a different key,
/// it is removed from the old key and reassigned to the new one.
pub fn insert(&mut self, key: K, value: V) {
// Insert the new value-to-key mapping, capturing the old key if it existed.
if let Some(old_key) = self.value_to_key.insert(value.clone(), key.clone()) {
// Remove value from the old key's set of values.
if let Some(set) = self.key_to_values.get_mut(&old_key) {
set.remove(&value);
// Clean up the old key if its set becomes empty.
if set.is_empty() {
self.key_to_values.remove(&old_key);
}
}
}
// Insert value into the new key's set of values.
self.key_to_values
.entry(key)
.or_insert_with(BTreeSet::new)
.insert(value);
}
/// Gets the set of values associated with the given key.
pub fn get_values<Q: ?Sized + Ord>(&self, key: &Q) -> Option<&BTreeSet<V>>
where
K: Borrow<Q>,
{
self.key_to_values.get(key)
}
/// Gets the key associated with the given value.
pub fn get_key(&self, value: &V) -> Option<&K> {
self.value_to_key.get(value)
}
/// Removes a key from the forward map and all its associated values from
/// the reverse map.
pub fn remove_by_key<Q: ?Sized + Ord>(&mut self, key: &Q) -> Option<BTreeSet<V>>
where
K: Borrow<Q>,
{
if let Some(values) = self.key_to_values.remove(key) {
// Remove each value from the reverse map.
for v in &values {
self.value_to_key.remove(v);
}
Some(values)
} else {
None
}
}
/// Removes a value from the reverse map and the key from the forward map if
/// it no longer has any values.
pub fn remove_by_value(&mut self, value: &V) -> Option<K> {
if let Some(k) = self.value_to_key.remove(value) {
// Remove the value from the key's value set.
if let Some(set) = self.key_to_values.get_mut(&k) {
set.remove(value);
// Remove the key if it no longer has any values.
if set.is_empty() {
self.key_to_values.remove(&k);
}
}
Some(k)
} else {
None
}
}
}

View File

@ -13,9 +13,9 @@ pub use specctra_core::mesadata::AccessMesadata;
use bimap::BiBTreeMap; use bimap::BiBTreeMap;
use derive_getters::Getters; use derive_getters::Getters;
use geo::Point; use geo::Point;
use std::collections::BTreeMap;
use crate::{ use crate::{
bimapset::BiBTreeMapSet,
drawing::{ drawing::{
band::BandUid, band::BandUid,
dot::{FixedDotIndex, FixedDotWeight}, dot::{FixedDotIndex, FixedDotWeight},
@ -82,7 +82,7 @@ pub struct Board<M> {
bands_by_id: BiBTreeMap<EtchedPath, BandUid>, bands_by_id: BiBTreeMap<EtchedPath, BandUid>,
// TODO: Simplify access logic to these members so that `#[getter(skip)]`s can be removed. // TODO: Simplify access logic to these members so that `#[getter(skip)]`s can be removed.
#[getter(skip)] #[getter(skip)]
node_to_pinname: BTreeMap<NodeIndex, String>, pinname_nodes: BiBTreeMapSet<String, NodeIndex>,
#[getter(skip)] #[getter(skip)]
band_bandname: BiBTreeMap<BandUid, BandName>, band_bandname: BiBTreeMap<BandUid, BandName>,
} }
@ -93,7 +93,7 @@ impl<M> Board<M> {
Self { Self {
layout, layout,
bands_by_id: BiBTreeMap::new(), bands_by_id: BiBTreeMap::new(),
node_to_pinname: BTreeMap::new(), pinname_nodes: BiBTreeMapSet::new(),
band_bandname: BiBTreeMap::new(), band_bandname: BiBTreeMap::new(),
} }
} }
@ -119,8 +119,8 @@ impl<M: AccessMesadata> Board<M> {
.add_fixed_dot_infringably(&mut recorder.layout_edit, weight); .add_fixed_dot_infringably(&mut recorder.layout_edit, weight);
if let Some(pin) = maybe_pin { if let Some(pin) = maybe_pin {
self.node_to_pinname self.pinname_nodes
.insert(GenericNode::Primitive(dot.into()), pin); .insert(pin, GenericNode::Primitive(dot.into()));
} }
dot dot
@ -142,8 +142,8 @@ impl<M: AccessMesadata> Board<M> {
.add_fixed_seg_infringably(&mut recorder.layout_edit, from, to, weight); .add_fixed_seg_infringably(&mut recorder.layout_edit, from, to, weight);
if let Some(pin) = maybe_pin { if let Some(pin) = maybe_pin {
self.node_to_pinname self.pinname_nodes
.insert(GenericNode::Primitive(seg.into()), pin); .insert(pin, GenericNode::Primitive(seg.into()));
} }
seg seg
@ -166,34 +166,43 @@ impl<M: AccessMesadata> Board<M> {
if let Some(pin) = maybe_pin { if let Some(pin) = maybe_pin {
for i in nodes { for i in nodes {
self.node_to_pinname self.pinname_nodes
.insert(GenericNode::Primitive(*i), pin.clone()); .insert(pin.clone(), GenericNode::Primitive(*i));
} }
self.node_to_pinname self.pinname_nodes
.insert(GenericNode::Primitive(apex.into()), pin.clone()); .insert(pin.clone(), GenericNode::Primitive(apex.into()));
self.node_to_pinname self.pinname_nodes
.insert(GenericNode::Compound(poly.into()), pin); .insert(pin, GenericNode::Compound(poly.into()));
} }
poly poly
} }
/// Returns the pin name associated with a given node. /// Returns an iterator over the set of all nodes associated with a given
pub fn node_pinname(&self, node: &NodeIndex) -> Option<&String> { /// pin name.
self.node_to_pinname.get(node) pub fn pinname_nodes(&self, pinname: &str) -> impl Iterator<Item = NodeIndex> + '_ {
self.pinname_nodes
.get_values(pinname)
.into_iter()
.flat_map(|set| set.iter().map(|node| *node))
} }
/// Returns the apex belonging to a given pin, if any /// Returns the pin name associated with a given node.
/// pub fn node_pinname(&self, node: &NodeIndex) -> Option<&String> {
/// Warning: this is very slow. self.pinname_nodes.get_key(node)
}
/// Returns the apex belonging to a given pin, if there is any.
pub fn pin_apex(&self, pin: &str, layer: usize) -> Option<(FixedDotIndex, Point)> { pub fn pin_apex(&self, pin: &str, layer: usize) -> Option<(FixedDotIndex, Point)> {
self.node_to_pinname self.pinname_nodes
.iter() .get_values(pin)
.filter(|(_, node_pin)| *node_pin == pin) .map(|node| {
// this should only ever return one result node.iter()
.find_map(|(node, _)| self.layout().apex_of_compoundless_node(*node, layer)) .find_map(|node| self.layout().apex_of_compoundless_node(*node, layer))
})
.flatten()
} }
/// Returns the band name associated with a given band. /// Returns the band name associated with a given band.

View File

@ -34,10 +34,10 @@ use crate::{
primitive::PrimitiveShape, primitive::PrimitiveShape,
recording_with_rtree::RecordingGeometryWithRtree, recording_with_rtree::RecordingGeometryWithRtree,
with_rtree::BboxedIndex, with_rtree::BboxedIndex,
AccessBendWeight, AccessDotWeight, AccessSegWeight, GenericNode, Geometry, GeometryLabel, AccessBendWeight, AccessDotWeight, AccessSegWeight, GenericNode, Geometry, GetLayer,
GetLayer, GetOffset, GetSetPos, GetWidth, GetOffset, GetSetPos, GetWidth,
}, },
graph::{GenericIndex, GetIndex, MakeRef}, graph::{GenericIndex, MakeRef},
math::{NoBitangents, RotationSense}, math::{NoBitangents, RotationSense},
}; };

View File

@ -5,7 +5,6 @@
use std::collections::btree_map::Entry as BTreeMapEntry; use std::collections::btree_map::Entry as BTreeMapEntry;
use geo::Point; use geo::Point;
use petgraph::stable_graph::StableDiGraph;
use rstar::RTree; use rstar::RTree;
use crate::graph::{GenericIndex, GetIndex}; use crate::graph::{GenericIndex, GetIndex};
@ -14,8 +13,8 @@ use super::{
compound::ManageCompounds, compound::ManageCompounds,
edit::{ApplyGeometryEdit, GeometryEdit}, edit::{ApplyGeometryEdit, GeometryEdit},
with_rtree::{BboxedIndex, GeometryWithRtree}, with_rtree::{BboxedIndex, GeometryWithRtree},
AccessBendWeight, AccessDotWeight, AccessSegWeight, GenericNode, Geometry, GeometryLabel, AccessBendWeight, AccessDotWeight, AccessSegWeight, GenericNode, Geometry, GetLayer, GetWidth,
GetLayer, GetWidth, Retag, Retag,
}; };
#[derive(Debug)] #[derive(Debug)]

View File

@ -5,15 +5,15 @@
use contracts_try::debug_invariant; use contracts_try::debug_invariant;
use derive_getters::Getters; use derive_getters::Getters;
use geo::Point; use geo::Point;
use petgraph::{stable_graph::StableDiGraph, visit::Walker}; use petgraph::visit::Walker;
use rstar::{primitives::GeomWithData, Envelope, RTree, RTreeObject, AABB}; use rstar::{primitives::GeomWithData, Envelope, RTree, RTreeObject, AABB};
use crate::{ use crate::{
geometry::{ geometry::{
compound::ManageCompounds, compound::ManageCompounds,
primitive::{AccessPrimitiveShape, PrimitiveShape}, primitive::{AccessPrimitiveShape, PrimitiveShape},
AccessBendWeight, AccessDotWeight, AccessSegWeight, GenericNode, Geometry, GeometryLabel, AccessBendWeight, AccessDotWeight, AccessSegWeight, GenericNode, Geometry, GetLayer,
GetLayer, GetWidth, Retag, GetWidth, Retag,
}, },
graph::{GenericIndex, GetIndex}, graph::{GenericIndex, GetIndex},
}; };

View File

@ -22,6 +22,7 @@ pub mod graph;
#[macro_use] #[macro_use]
pub mod drawing; pub mod drawing;
pub mod autorouter; pub mod autorouter;
pub mod bimapset;
pub mod board; pub mod board;
pub mod geometry; pub mod geometry;
pub mod interactor; pub mod interactor;