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

View File

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

View File

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

View File

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

View File

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