Encapsulate layer indices in new `LayerId` type

This commit is contained in:
Mikolaj Wielgus 2026-05-23 20:28:57 +02:00
parent 1b9673e3e7
commit a947a72a02
16 changed files with 209 additions and 83 deletions

View File

@ -3,6 +3,7 @@
// SPDX-License-Identifier: MIT OR Apache-2.0
use crate::{viewport::Viewport, workspace::Workspace};
use topola::LayerId;
use topola::primitives::{Joint, Polygon, Segment};
pub struct Display {}
@ -38,8 +39,8 @@ impl Display {
let layout = board.layout();
// Start from the bottom layer so that top layers are drawn on top.
for layer in (0..*layout.layer_count()).rev() {
if !workspace.appearance_panel.visible[layer] {
for layer in (0..*layout.layer_count()).rev().map(LayerId::new) {
if !workspace.appearance_panel.visible[layer.index()] {
continue;
}
@ -167,8 +168,8 @@ impl Display {
let board = workspace.autorouter.router().navmesher_board().board();
let layout = board.layout();
for layer in (0..*layout.layer_count()).rev() {
if !workspace.appearance_panel.visible[layer] {
for layer in (0..*layout.layer_count()).rev().map(LayerId::new) {
if !workspace.appearance_panel.visible[layer.index()] {
continue;
}
@ -231,22 +232,23 @@ impl Display {
viewport: &Viewport,
workspace: &Workspace,
) {
for layer in 0..*workspace
for layer in (0..*workspace
.autorouter
.router()
.navmesher_board()
.board()
.layout()
.layer_count()
.layer_count())
.map(LayerId::new)
{
if workspace.appearance_panel.visible[layer] {
if workspace.appearance_panel.visible[layer.index()] {
for navmesh in workspace
.autorouter
.router()
.navmesher_board()
.navmesher()
.layer_navmeshers()[layer]
.navmeshes()
.layer_navmeshers()[layer.index()]
.navmeshes()
{
for edge_geom in navmesh
.triangulation()
@ -305,8 +307,8 @@ impl Display {
let layers = *ratline.endpoint_layers();
let endpoints = *ratline.endpoints();
if !workspace.appearance_panel.visible[layers[0]]
|| !workspace.appearance_panel.visible[layers[1]]
if !workspace.appearance_panel.visible[layers[0].index()]
|| !workspace.appearance_panel.visible[layers[1].index()]
{
continue;
}

View File

@ -6,7 +6,7 @@ use std::collections::BTreeMap;
use egui::{Context, Grid, ScrollArea, SidePanel, widget_text::WidgetText};
use serde::{Deserialize, Serialize};
use topola::Board;
use topola::{Board, LayerId};
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Colors {
@ -42,7 +42,7 @@ pub struct LayersPanel {
light_colors: Colors,
#[serde(skip)]
pub active: usize,
pub active: LayerId,
#[serde(skip)]
pub visible: Box<[bool]>,
}
@ -161,7 +161,7 @@ impl LayersPanel {
Self {
dark_colors,
light_colors,
active: 0,
active: LayerId::new(0),
visible,
}
}
@ -181,17 +181,16 @@ impl LayersPanel {
.num_columns(3)
.start_row(start_row)
.show(ui, |ui| {
for layer in row_range {
let visible = &mut self.visible[layer];
for layer_index in row_range {
let layer = LayerId::new(layer_index);
let visible = &mut self.visible[layer.index()];
let layer_name = board.layer_name(layer);
ui.radio_value(&mut self.active, layer, WidgetText::default());
ui.checkbox(visible, WidgetText::default());
ui.label(
layer_name
.map(|i| i.to_string())
.unwrap_or_else(|| format!("{} - Unnamed layer", layer)),
);
ui.label(layer_name.map(|i| i.to_string()).unwrap_or_else(|| {
format!("{} - Unnamed layer", layer.index())
}));
ui.end_row();
}
})

View File

@ -13,6 +13,7 @@ use undoredo::{Delta, Recorder};
use crate::{
compounds::{ComponentId, NetId, PinId},
layout::LayerId,
layout::{
Layout, LayoutHalfDelta,
primitives::{
@ -31,7 +32,7 @@ pub struct Board {
#[getter(skip)]
pin_names: Recorder<BiBTreeMap<PinId, String>>,
#[getter(skip)]
layer_names: Recorder<BiBTreeMap<usize, String>>,
layer_names: Recorder<BiBTreeMap<LayerId, String>>,
#[getter(skip)]
net_names: Recorder<BiBTreeMap<NetId, String>>,
}
@ -50,7 +51,7 @@ impl Board {
pub fn with_names(
boundary: Vec<Vector2<i64>>,
layer_count: usize,
layer_names: BiBTreeMap<usize, String>,
layer_names: BiBTreeMap<LayerId, String>,
net_names: BiBTreeMap<NetId, String>,
) -> Self {
Self {
@ -131,11 +132,11 @@ impl Board {
self.pin_names.as_ref().get_by_right(pin_name).copied()
}
pub fn layer_name(&self, layer: usize) -> Option<&str> {
pub fn layer_name(&self, layer: LayerId) -> Option<&str> {
self.layer_names.get_by_left(&layer).map(String::as_str)
}
pub fn layer_id(&self, layer_name: &str) -> Option<usize> {
pub fn layer_id(&self, layer_name: &str) -> Option<LayerId> {
self.layer_names.as_ref().get_by_right(layer_name).copied()
}

View File

@ -7,6 +7,7 @@ use crate::{
Board,
selections::{ComponentSelection, ComponentSelector, PinSelection, PinSelector},
},
layout::LayerId,
math::Vector2,
primitives::{JointId, PolygonId, SegmentId},
};
@ -109,7 +110,7 @@ impl Board {
pub fn point_component_selector(
&self,
layer: usize,
layer: LayerId,
point: Vector2<i64>,
) -> Option<ComponentSelector> {
if let Some(joint_id) = self.layout.locate_joints_at_point(layer, point).next() {
@ -181,7 +182,7 @@ impl Board {
selection.0.contains(&selector)
}
pub fn point_pin_selector(&self, layer: usize, point: Vector2<i64>) -> Option<PinSelector> {
pub fn point_pin_selector(&self, layer: LayerId, point: Vector2<i64>) -> Option<PinSelector> {
if let Some(joint_id) = self.layout.locate_joints_at_point(layer, point).next() {
return self.joint_pin_selector(joint_id);
}

View File

@ -8,7 +8,18 @@ use serde::{Deserialize, Serialize};
use crate::primitives::{JointId, PolygonId, SegmentId, ViaId};
#[derive(
Clone, Constructor, Copy, Debug, Deserialize, Eq, From, Ord, PartialEq, PartialOrd, Serialize,
Clone,
Constructor,
Copy,
Debug,
Default,
Deserialize,
Eq,
From,
Ord,
PartialEq,
PartialOrd,
Serialize,
)]
pub struct ComponentId(usize);

View File

@ -6,7 +6,18 @@ use derive_more::{Constructor, From};
use serde::{Deserialize, Serialize};
#[derive(
Clone, Constructor, Copy, Debug, Deserialize, Eq, From, Ord, PartialEq, PartialOrd, Serialize,
Clone,
Constructor,
Copy,
Debug,
Default,
Deserialize,
Eq,
From,
Ord,
PartialEq,
PartialOrd,
Serialize,
)]
pub struct NetId(usize);

View File

@ -8,7 +8,18 @@ use serde::{Deserialize, Serialize};
use crate::primitives::{JointId, PolygonId, SegmentId, ViaId};
#[derive(
Clone, Constructor, Copy, Debug, Deserialize, Eq, From, Ord, PartialEq, PartialOrd, Serialize,
Clone,
Constructor,
Copy,
Debug,
Default,
Deserialize,
Eq,
From,
Ord,
PartialEq,
PartialOrd,
Serialize,
)]
pub struct PinId(usize);

View File

@ -6,10 +6,12 @@ pub mod primitives;
mod transforms;
use derive_getters::Getters;
use derive_more::{Constructor, From};
use rstar::{
AABB, RTree,
primitives::{GeomWithData, Rectangle},
};
use serde::{Deserialize, Serialize};
use stable_vec::StableVec;
use undoredo::aliases::RTreeHalfDelta;
use undoredo::{Delta, Recorder};
@ -23,6 +25,29 @@ use crate::{
math::Vector2,
};
#[derive(
Clone,
Constructor,
Copy,
Debug,
Default,
Deserialize,
Eq,
From,
Ord,
PartialEq,
PartialOrd,
Serialize,
)]
pub struct LayerId(usize);
impl LayerId {
#[inline]
pub fn index(self) -> usize {
self.0
}
}
#[derive(Clone, Debug, Delta, Getters)]
pub struct Layout {
#[undoredo(skip)]
@ -346,24 +371,24 @@ impl Layout {
pub fn locate_joints_at_point(
&self,
layer: usize,
layer: LayerId,
point: Vector2<i64>,
) -> impl Iterator<Item = JointId> {
self.joints_rtree
.as_ref()
.locate_all_at_point(&[point.x, point.y, layer as i64])
.locate_all_at_point(&[point.x, point.y, layer.index() as i64])
.map(|geom_with_data| geom_with_data.data)
.filter(move |&joint_id| self.joints[joint_id.index()].contains_point(point))
}
pub fn locate_segments_at_point(
&self,
layer: usize,
layer: LayerId,
point: Vector2<i64>,
) -> impl Iterator<Item = SegmentId> {
self.segments_rtree
.as_ref()
.locate_all_at_point(&[point.x, point.y, layer as i64])
.locate_all_at_point(&[point.x, point.y, layer.index() as i64])
.map(|geom_with_data| geom_with_data.data)
.filter(move |&segment_id| self.segment(segment_id).contains_point(point))
}
@ -372,17 +397,17 @@ impl Layout {
pub fn locate_polygons_at_point(
&self,
layer: usize,
layer: LayerId,
point: Vector2<i64>,
) -> impl Iterator<Item = PolygonId> {
self.polygons_rtree
.as_ref()
.locate_all_at_point(&[point.x, point.y, layer as i64])
.locate_all_at_point(&[point.x, point.y, layer.index() as i64])
.map(|geom_with_data| geom_with_data.data)
.filter(move |&polygon_id| self.polygons[polygon_id.index()].contains_point(point))
}
pub fn layer_joints(&self, layer: usize) -> impl Iterator<Item = JointId> + '_ {
pub fn layer_joints(&self, layer: LayerId) -> impl Iterator<Item = JointId> + '_ {
let envelope = Self::whole_layer_aabb(layer);
self.joints_rtree
.as_ref()
@ -391,7 +416,7 @@ impl Layout {
.filter(move |&id| self.joint(id).spec.layer == layer)
}
pub fn layer_segments(&self, layer: usize) -> impl Iterator<Item = SegmentId> + '_ {
pub fn layer_segments(&self, layer: LayerId) -> impl Iterator<Item = SegmentId> + '_ {
let envelope = Self::whole_layer_aabb(layer);
self.segments_rtree
.as_ref()
@ -400,7 +425,7 @@ impl Layout {
.filter(move |&id| self.segment(id).layer == layer)
}
pub fn layer_polygons(&self, layer: usize) -> impl Iterator<Item = PolygonId> + '_ {
pub fn layer_polygons(&self, layer: LayerId) -> impl Iterator<Item = PolygonId> + '_ {
let envelope = Self::whole_layer_aabb(layer);
self.polygons_rtree
.as_ref()
@ -409,10 +434,10 @@ impl Layout {
.filter(move |&id| self.polygon(id).layer == layer)
}
fn whole_layer_aabb(layer: usize) -> AABB<[i64; 3]> {
fn whole_layer_aabb(layer: LayerId) -> AABB<[i64; 3]> {
AABB::from_corners(
[i64::MIN, i64::MIN, layer as i64],
[i64::MAX, i64::MAX, layer as i64],
[i64::MIN, i64::MIN, layer.index() as i64],
[i64::MAX, i64::MAX, layer.index() as i64],
)
}

View File

@ -7,12 +7,24 @@ use rstar::{AABB, primitives::Rectangle};
use serde::{Deserialize, Serialize};
use crate::compounds::{ComponentId, NetId, PinId};
use crate::layout::LayerId;
use crate::math::Vector2;
use super::{SegmentId, ViaId};
#[derive(
Clone, Constructor, Copy, Debug, Deserialize, Eq, From, Ord, PartialEq, PartialOrd, Serialize,
Clone,
Constructor,
Copy,
Debug,
Default,
Deserialize,
Eq,
From,
Ord,
PartialEq,
PartialOrd,
Serialize,
)]
pub struct JointId(usize);
@ -27,7 +39,7 @@ impl JointId {
#[derive(Clone, Copy, Debug)]
pub struct JointSpec {
pub position: Vector2<i64>,
pub layer: usize,
pub layer: LayerId,
pub radius: u64,
pub net: NetId,
pub component: Option<ComponentId>,
@ -51,12 +63,12 @@ impl Joint {
[
self.spec.position.x - self.spec.radius as i64,
self.spec.position.y - self.spec.radius as i64,
self.spec.layer as i64,
self.spec.layer.index() as i64,
],
[
self.spec.position.x + self.spec.radius as i64,
self.spec.position.y + self.spec.radius as i64,
self.spec.layer as i64,
self.spec.layer.index() as i64,
],
))
}

View File

@ -7,10 +7,22 @@ use rstar::{AABB, Envelope, primitives::Rectangle};
use serde::{Deserialize, Serialize};
use crate::compounds::{ComponentId, NetId, PinId};
use crate::layout::LayerId;
use crate::math::Vector2;
#[derive(
Clone, Constructor, Copy, Debug, Deserialize, Eq, From, Ord, PartialEq, PartialOrd, Serialize,
Clone,
Constructor,
Copy,
Debug,
Default,
Deserialize,
Eq,
From,
Ord,
PartialEq,
PartialOrd,
Serialize,
)]
pub struct PolygonId(usize);
@ -25,7 +37,7 @@ impl PolygonId {
#[derive(Clone, Debug)]
pub struct Polygon {
pub vertices: Vec<Vector2<i64>>,
pub layer: usize,
pub layer: LayerId,
pub net: NetId,
pub component: Option<ComponentId>,
pub pin: Option<PinId>,
@ -33,14 +45,16 @@ pub struct Polygon {
impl Polygon {
pub fn bbox(&self) -> Rectangle<[i64; 3]> {
Rectangle::from_aabb(
self.vertices
.clone()
.into_iter()
.fold(AABB::new_empty(), |aabb, vertex| {
aabb.merged(&AABB::from_point([vertex.x, vertex.y, self.layer as i64]))
}),
)
Rectangle::from_aabb(self.vertices.clone().into_iter().fold(
AABB::new_empty(),
|aabb, vertex| {
aabb.merged(&AABB::from_point([
vertex.x,
vertex.y,
self.layer.index() as i64,
]))
},
))
}
pub fn center(&self) -> Vector2<i64> {

View File

@ -7,12 +7,24 @@ use rstar::primitives::Rectangle;
use serde::{Deserialize, Serialize};
use crate::compounds::{ComponentId, NetId, PinId};
use crate::layout::LayerId;
use crate::math::Vector2;
use super::JointId;
#[derive(
Clone, Constructor, Copy, Debug, Deserialize, Eq, From, Ord, PartialEq, PartialOrd, Serialize,
Clone,
Constructor,
Copy,
Debug,
Default,
Deserialize,
Eq,
From,
Ord,
PartialEq,
PartialOrd,
Serialize,
)]
pub struct SegmentId(usize);
@ -36,7 +48,7 @@ pub struct SegmentSpec {
pub struct Segment {
pub spec: SegmentSpec,
pub endpoints: [Vector2<i64>; 2],
pub layer: usize,
pub layer: LayerId,
pub net: NetId,
}
@ -70,7 +82,7 @@ impl Segment {
pub fn bbox(&self) -> Rectangle<[i64; 3]> {
let endpoints = self.endpoints;
let layer = self.layer as i64;
let layer = self.layer.index() as i64;
let half_width = self.spec.half_width as i64;
let min_x = std::cmp::min(endpoints[0].x, endpoints[1].x) - half_width;

View File

@ -7,12 +7,24 @@ use rstar::{AABB, primitives::Rectangle};
use serde::{Deserialize, Serialize};
use crate::compounds::{ComponentId, NetId, PinId};
use crate::layout::LayerId;
use crate::math::Vector2;
use super::JointId;
#[derive(
Clone, Constructor, Copy, Debug, Deserialize, Eq, From, Ord, PartialEq, PartialOrd, Serialize,
Clone,
Constructor,
Copy,
Debug,
Default,
Deserialize,
Eq,
From,
Ord,
PartialEq,
PartialOrd,
Serialize,
)]
pub struct ViaId(usize);
@ -36,8 +48,8 @@ pub struct ViaSpec {
pub struct Via {
pub spec: ViaSpec,
pub position: Vector2<i64>,
pub min_layer: usize,
pub max_layer: usize,
pub min_layer: LayerId,
pub max_layer: LayerId,
pub net: NetId,
}
@ -49,12 +61,12 @@ impl Via {
[
self.position.x - radius,
self.position.y - radius,
self.min_layer as i64,
self.min_layer.index() as i64,
],
[
self.position.x + radius,
self.position.y + radius,
self.max_layer as i64,
self.max_layer.index() as i64,
],
))
}

View File

@ -18,6 +18,7 @@ pub use crate::autorouter::Autorouter;
pub use crate::board::Board;
pub use crate::board::selections;
pub use crate::compounds::{Pin, PinId};
pub use crate::layout::LayerId;
pub use crate::layout::Layout;
pub use crate::layout::primitives;
pub use crate::math::Vector2;

View File

@ -11,22 +11,34 @@ use undoredo::Recorder;
use crate::{
Board,
layout::LayerId,
math::Vector2,
primitives::{Joint, JointId, JointSpec, Polygon, PolygonId, Segment, SegmentId},
};
#[derive(
Clone, Constructor, Copy, Debug, Deserialize, Eq, From, Ord, PartialEq, PartialOrd, Serialize,
Clone,
Constructor,
Copy,
Debug,
Default,
Deserialize,
Eq,
From,
Ord,
PartialEq,
PartialOrd,
Serialize,
)]
pub struct MultiObstacleId {
layer: usize,
layer: LayerId,
index: usize,
}
impl MultiObstacleId {
/// Layer of the obstacle.
#[inline]
pub fn layer(self) -> usize {
pub fn layer(self) -> LayerId {
self.layer
}
@ -38,17 +50,17 @@ impl MultiObstacleId {
}
#[derive(
Clone, Constructor, Debug, Deserialize, Eq, From, Ord, PartialEq, PartialOrd, Serialize,
Clone, Constructor, Debug, Default, Deserialize, Eq, From, Ord, PartialEq, PartialOrd, Serialize,
)]
pub struct MultiVertexId {
layer: usize,
layer: LayerId,
indices: Vec<VertexId>,
}
impl MultiVertexId {
/// Layer of the obstacle.
#[inline]
pub fn layer(self) -> usize {
pub fn layer(self) -> LayerId {
self.layer
}
}
@ -156,12 +168,12 @@ impl Navmesher {
pub fn insert_multiobstacle(
&mut self,
layer: usize,
layer: LayerId,
multiobstacle: impl IntoIterator<Item = Vector2<i64>>,
) -> MultiObstacleId {
MultiObstacleId::new(
layer,
self.layer_navmeshers[layer].insert_multiobstacle(multiobstacle),
self.layer_navmeshers[layer.index()].insert_multiobstacle(multiobstacle),
)
}
@ -172,7 +184,7 @@ impl Navmesher {
) -> MultiVertexId {
MultiVertexId {
layer: multiobstacle_id.layer,
indices: self.layer_navmeshers[multiobstacle_id.layer]
indices: self.layer_navmeshers[multiobstacle_id.layer.index()]
.insert_free_multivertex_in_multiobstacle(multiobstacle_id.index, position),
}
}

View File

@ -11,6 +11,7 @@ use spade::{DelaunayTriangulation, HasPosition, Triangulation, handles::FixedVer
use crate::{
Board,
compounds::NetId,
layout::LayerId,
math::Vector2,
primitives::{JointId, PolygonId, PrimitiveId, SegmentId},
};
@ -18,12 +19,12 @@ use crate::{
#[derive(Clone, Copy, Debug, Deserialize, Eq, Getters, Ord, PartialEq, PartialOrd, Serialize)]
pub struct Ratline {
endpoint_primitive_ids: [PrimitiveId; 2],
endpoint_layers: [usize; 2],
endpoint_layers: [LayerId; 2],
endpoints: [Vector2<i64>; 2],
}
struct DelaunayVertex {
pub layer: usize,
pub layer: LayerId,
pub center: Vector2<i64>,
pub position: spade::Point2<f64>,
pub primitive_id: PrimitiveId,
@ -46,7 +47,7 @@ impl Ratsnest {
pub fn new(board: &Board) -> Self {
let mut ratlines = Vec::new();
let mut triangulations: BTreeMap<(NetId, usize), DelaunayTriangulation<DelaunayVertex>> =
let mut triangulations: BTreeMap<(NetId, LayerId), DelaunayTriangulation<DelaunayVertex>> =
BTreeMap::new();
for (i, joint) in board.layout().joints().container().iter() {

View File

@ -13,6 +13,7 @@ use specctra::{
use crate::{
board::Board,
compounds::{ComponentId, NetId, PinId},
layout::LayerId,
math::Vector2,
primitives::{JointSpec, Polygon, Segment, SegmentSpec},
};
@ -25,7 +26,7 @@ impl Board {
.layers
.iter()
.enumerate()
.map(|(index, layer)| (index, layer.name.clone())),
.map(|(index, layer)| (LayerId::new(index), layer.name.clone())),
);
// assign IDs to all nets named in pcb.network
@ -284,7 +285,7 @@ impl Board {
place: PointWithRotation,
pin_pos: PointWithRotation,
radius: u64,
layer: usize,
layer: LayerId,
net: NetId,
component: Option<ComponentId>,
pin: Option<PinId>,
@ -308,7 +309,7 @@ impl Board {
y1: f64,
x2: f64,
y2: f64,
layer: usize,
layer: LayerId,
net: NetId,
component: Option<ComponentId>,
pin: Option<PinId>,
@ -334,7 +335,7 @@ impl Board {
pin_pos: PointWithRotation,
coords: &[Point],
width: f64,
layer: usize,
layer: LayerId,
net: NetId,
component: Option<ComponentId>,
pin: Option<PinId>,
@ -392,7 +393,7 @@ impl Board {
pin_pos: PointWithRotation,
coords: &[Point],
_width: f64,
layer: usize,
layer: LayerId,
net: NetId,
component: Option<ComponentId>,
pin: Option<PinId>,
@ -411,13 +412,13 @@ impl Board {
});
}
fn layer(board: &Board, layers: &[Layer], name: &str, front: bool) -> usize {
fn layer(board: &Board, layers: &[Layer], name: &str, front: bool) -> LayerId {
let image_layer = board.layer_id(name).unwrap();
if front {
image_layer
} else {
layers.len() - image_layer - 1
LayerId::new(layers.len() - image_layer.index() - 1)
}
}