diff --git a/topola-egui/src/display.rs b/topola-egui/src/display.rs index d30fd61..97097f3 100644 --- a/topola-egui/src/display.rs +++ b/topola-egui/src/display.rs @@ -53,7 +53,7 @@ impl Display { joint, workspace.appearance_panel.layer_color( ctx, - board.layer_name(joint.spec.layer), + board.layer_desc(joint.spec.layer), board.pin_selection_contains_joint(&workspace.selection.pins, joint_id), ), ); @@ -68,7 +68,7 @@ impl Display { segment, workspace.appearance_panel.layer_color( ctx, - board.layer_name(segment.layer), + board.layer_desc(segment.layer), board.pin_selection_contains_segment(&workspace.selection.pins, segment_id), ), ); @@ -85,7 +85,7 @@ impl Display { polygon, workspace.appearance_panel.layer_color( ctx, - board.layer_name(polygon.layer), + board.layer_desc(polygon.layer), board.pin_selection_contains_polygon(&workspace.selection.pins, polygon_id), ), ); diff --git a/topola-egui/src/layers_panel.rs b/topola-egui/src/layers_panel.rs index 88ac48d..865bea6 100644 --- a/topola-egui/src/layers_panel.rs +++ b/topola-egui/src/layers_panel.rs @@ -6,7 +6,7 @@ use std::collections::BTreeMap; use egui::{Context, Grid, ScrollArea, SidePanel, widget_text::WidgetText}; use serde::{Deserialize, Serialize}; -use topola::{Board, LayerId}; +use topola::{Board, LayerDesc, LayerId, LayerTier, LayerType}; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Colors { @@ -16,13 +16,13 @@ pub struct Colors { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ColorLayers { default: LayerColors, - colors: BTreeMap, + colors: BTreeMap, } impl ColorLayers { - pub fn colors(&self, layer_name: Option<&str>) -> &LayerColors { - layer_name - .map(|layername| self.colors.get(layername).unwrap_or(&self.default)) + pub fn colors(&self, layer_desc: Option<&LayerDesc>) -> &LayerColors { + layer_desc + .map(|layer_desc| self.colors.get(layer_desc).unwrap_or(&self.default)) .unwrap_or(&self.default) } } @@ -49,56 +49,84 @@ pub struct LayersPanel { impl LayersPanel { pub fn new(board: &Board) -> Self { + let mut dark_layer_colors = BTreeMap::new(); + let mut light_layer_colors = BTreeMap::new(); + + for layer_index in 0..*board.layout().layer_count() { + let layer = LayerId::new(layer_index); + let Some(layer_desc) = board.layer_desc(layer) else { + continue; + }; + + let color = match layer_desc.typ { + LayerType::Copper => match layer_desc.tier { + LayerTier::Top => Some(( + LayerColors { + normal: egui::Color32::from_rgb(255, 52, 52), + highlighted: egui::Color32::from_rgb(255, 100, 100), + }, + LayerColors { + normal: egui::Color32::from_rgb(255, 27, 27), + highlighted: egui::Color32::from_rgb(255, 52, 52), + }, + )), + LayerTier::Bottom => Some(( + LayerColors { + normal: egui::Color32::from_rgb(52, 52, 255), + highlighted: egui::Color32::from_rgb(100, 100, 255), + }, + LayerColors { + normal: egui::Color32::from_rgb(27, 27, 255), + highlighted: egui::Color32::from_rgb(52, 52, 255), + }, + )), + LayerTier::Inner => (layer_desc.index % 2 == 0) + .then_some(( + LayerColors { + normal: egui::Color32::from_rgb(127, 200, 127), + highlighted: egui::Color32::from_rgb(213, 236, 213), + }, + LayerColors { + normal: egui::Color32::from_rgb(76, 169, 76), + highlighted: egui::Color32::from_rgb(127, 200, 127), + }, + )) + .or(Some(( + LayerColors { + normal: egui::Color32::from_rgb(206, 125, 44), + highlighted: egui::Color32::from_rgb(232, 195, 158), + }, + LayerColors { + normal: egui::Color32::from_rgb(183, 80, 12), + highlighted: egui::Color32::from_rgb(206, 125, 44), + }, + ))), + }, + LayerType::Outline => Some(( + LayerColors { + normal: egui::Color32::from_rgb(255, 255, 255), + highlighted: egui::Color32::from_rgb(255, 255, 255), + }, + LayerColors { + normal: egui::Color32::from_rgb(255, 255, 255), + highlighted: egui::Color32::from_rgb(255, 255, 255), + }, + )), + }; + + if let Some((dark_color, light_color)) = color { + dark_layer_colors.insert(layer_desc.clone(), dark_color); + light_layer_colors.insert(layer_desc.clone(), light_color); + } + } + let dark_colors = Colors { layers: ColorLayers { default: LayerColors { normal: egui::Color32::from_rgb(255, 255, 255), highlighted: egui::Color32::from_rgb(255, 255, 255), }, - colors: BTreeMap::from([ - ( - "F.Cu".to_string(), - LayerColors { - normal: egui::Color32::from_rgb(255, 52, 52), - highlighted: egui::Color32::from_rgb(255, 100, 100), - }, - ), - ( - "1".to_string(), - LayerColors { - normal: egui::Color32::from_rgb(255, 52, 52), - highlighted: egui::Color32::from_rgb(255, 100, 100), - }, - ), - ( - "B.Cu".to_string(), - LayerColors { - normal: egui::Color32::from_rgb(52, 52, 255), - highlighted: egui::Color32::from_rgb(100, 100, 255), - }, - ), - ( - "2".to_string(), - LayerColors { - normal: egui::Color32::from_rgb(52, 52, 255), - highlighted: egui::Color32::from_rgb(100, 100, 255), - }, - ), - ( - "In1.Cu".to_string(), - LayerColors { - normal: egui::Color32::from_rgb(127, 200, 127), - highlighted: egui::Color32::from_rgb(213, 236, 213), - }, - ), - ( - "In2.Cu".to_string(), - LayerColors { - normal: egui::Color32::from_rgb(206, 125, 44), - highlighted: egui::Color32::from_rgb(232, 195, 158), - }, - ), - ]), + colors: dark_layer_colors, }, }; let light_colors = Colors { @@ -107,50 +135,7 @@ impl LayersPanel { normal: egui::Color32::from_rgb(0, 0, 0), highlighted: egui::Color32::from_rgb(0, 0, 0), }, - colors: BTreeMap::from([ - ( - "F.Cu".to_string(), - LayerColors { - normal: egui::Color32::from_rgb(255, 27, 27), - highlighted: egui::Color32::from_rgb(255, 52, 52), - }, - ), - ( - "1".to_string(), - LayerColors { - normal: egui::Color32::from_rgb(255, 27, 27), - highlighted: egui::Color32::from_rgb(255, 52, 52), - }, - ), - ( - "B.Cu".to_string(), - LayerColors { - normal: egui::Color32::from_rgb(27, 27, 255), - highlighted: egui::Color32::from_rgb(52, 52, 255), - }, - ), - ( - "2".to_string(), - LayerColors { - normal: egui::Color32::from_rgb(27, 27, 255), - highlighted: egui::Color32::from_rgb(52, 52, 255), - }, - ), - ( - "In1.Cu".to_string(), - LayerColors { - normal: egui::Color32::from_rgb(76, 169, 76), - highlighted: egui::Color32::from_rgb(127, 200, 127), - }, - ), - ( - "In2.Cu".to_string(), - LayerColors { - normal: egui::Color32::from_rgb(183, 80, 12), - highlighted: egui::Color32::from_rgb(206, 125, 44), - }, - ), - ]), + colors: light_layer_colors, }, }; @@ -188,7 +173,7 @@ impl LayersPanel { 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(|| { + ui.label(layer_name.unwrap_or_else(|| { format!("{} - Unnamed layer", layer.index()) })); ui.end_row(); @@ -206,20 +191,20 @@ impl LayersPanel { } } - pub fn layer_colors(&self, ctx: &Context, layer_name: Option<&str>) -> &LayerColors { - self.colors(ctx).layers.colors(layer_name) + pub fn layer_colors(&self, ctx: &Context, layer_desc: Option<&LayerDesc>) -> &LayerColors { + self.colors(ctx).layers.colors(layer_desc) } pub fn layer_color( &self, ctx: &Context, - layer_name: Option<&str>, + layer_desc: Option<&LayerDesc>, highlight: bool, ) -> egui::Color32 { if highlight { - self.colors(ctx).layers.colors(layer_name).highlighted + self.colors(ctx).layers.colors(layer_desc).highlighted } else { - self.colors(ctx).layers.colors(layer_name).normal + self.colors(ctx).layers.colors(layer_desc).normal } } } diff --git a/topola/src/board/layer.rs b/topola/src/board/layer.rs new file mode 100644 index 0000000..1ad5e6e --- /dev/null +++ b/topola/src/board/layer.rs @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: 2026 Topola contributors +// +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use core::fmt::Display; +use derive_more::{Constructor, From}; +use serde::{Deserialize, Serialize}; +use std::fmt::Formatter; + +#[derive( + Clone, + Constructor, + Copy, + Debug, + Default, + Deserialize, + Eq, + From, + Ord, + PartialEq, + PartialOrd, + Serialize, +)] +pub struct LayerGroupId(usize); + +impl LayerGroupId { + #[inline] + pub fn index(self) -> usize { + self.0 + } +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] +pub enum LayerType { + Copper, + Outline, +} + +#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] +pub enum LayerTier { + Top, + Inner, + Bottom, +} + +#[derive(Clone, Constructor, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] +pub struct LayerDesc { + pub typ: LayerType, + pub tier: LayerTier, + pub index: usize, +} + +impl Display for LayerDesc { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self.typ { + LayerType::Copper => match self.tier { + LayerTier::Top => write!(f, "F.Cu"), + LayerTier::Bottom => write!(f, "B.Cu"), + LayerTier::Inner => write!(f, "In{}.Cu", self.index.saturating_sub(1)), + }, + LayerType::Outline => match self.tier { + LayerTier::Top => write!(f, "outlines.top"), + LayerTier::Bottom => write!(f, "outlines.bottom"), + LayerTier::Inner => write!(f, "outlines.{}", self.index), + }, + } + } +} diff --git a/topola/src/board/mod.rs b/topola/src/board/mod.rs index e3d3243..9254a85 100644 --- a/topola/src/board/mod.rs +++ b/topola/src/board/mod.rs @@ -2,15 +2,16 @@ // // SPDX-License-Identifier: MIT OR Apache-2.0 +mod layer; mod resolve; mod select; pub mod selections; mod transforms; +pub use crate::board::layer::{LayerDesc, LayerGroupId, LayerTier, LayerType}; + use bidimap::BiBTreeMap; use derive_getters::Getters; -use derive_more::{Constructor, From}; -use serde::{Deserialize, Serialize}; use undoredo::{Delta, Recorder}; use crate::{ @@ -25,29 +26,6 @@ use crate::{ math::Vector2, }; -#[derive( - Clone, - Constructor, - Copy, - Debug, - Default, - Deserialize, - Eq, - From, - Ord, - PartialEq, - PartialOrd, - Serialize, -)] -pub struct LayerGroupId(usize); - -impl LayerGroupId { - #[inline] - pub fn index(self) -> usize { - self.0 - } -} - #[derive(Clone, Debug, Getters, Delta)] pub struct Board { layout: Layout, @@ -58,7 +36,7 @@ pub struct Board { #[getter(skip)] pin_names: Recorder>, #[getter(skip)] - layer_names: Recorder>, + layer_descs: Recorder>, #[getter(skip)] net_names: Recorder>, } @@ -69,7 +47,7 @@ impl Board { layout: Layout::new(boundary.into_iter().map(Into::into).collect(), layer_count), component_names: Recorder::new(BiBTreeMap::new()), pin_names: Recorder::new(BiBTreeMap::new()), - layer_names: Recorder::new(BiBTreeMap::new()), + layer_descs: Recorder::new(BiBTreeMap::new()), net_names: Recorder::new(BiBTreeMap::new()), } }*/ @@ -77,7 +55,7 @@ impl Board { pub fn with_names( boundary: Vec>, layer_groups: Vec, - layer_names: BiBTreeMap, + layer_descs: BiBTreeMap, net_names: BiBTreeMap, ) -> Self { Self { @@ -88,7 +66,7 @@ impl Board { layer_groups: Recorder::new(layer_groups), component_names: Recorder::new(BiBTreeMap::new()), pin_names: Recorder::new(BiBTreeMap::new()), - layer_names: Recorder::new(layer_names), + layer_descs: Recorder::new(layer_descs), net_names: Recorder::new(net_names), } } @@ -162,12 +140,23 @@ impl Board { self.pin_names.as_ref().get_by_right(pin_name).copied() } - pub fn layer_name(&self, layer: LayerId) -> Option<&str> { - self.layer_names.get_by_left(&layer).map(String::as_str) + pub fn layer_name(&self, layer: LayerId) -> Option { + self.layer_descs + .get_by_left(&layer) + .map(ToString::to_string) + } + + pub fn layer_desc(&self, layer: LayerId) -> Option<&LayerDesc> { + self.layer_descs.get_by_left(&layer) } pub fn layer_id(&self, layer_name: &str) -> Option { - self.layer_names.as_ref().get_by_right(layer_name).copied() + self.layer_descs + .as_ref() + .iter() + .find_map(|(layer_id, layer_desc)| { + (layer_desc.to_string() == layer_name).then_some(*layer_id) + }) } pub fn layer_group(&self, layer: LayerId) -> LayerGroupId { diff --git a/topola/src/board/select.rs b/topola/src/board/select.rs index a1a34ae..a2b3e51 100644 --- a/topola/src/board/select.rs +++ b/topola/src/board/select.rs @@ -205,7 +205,7 @@ impl Board { Some(PinSelector { pin: self.pin_name(joint.spec.pin?)?.to_string(), - layer: self.layer_name(joint.spec.layer)?.to_string(), + layer: self.layer_name(joint.spec.layer)?, }) } @@ -214,7 +214,7 @@ impl Board { Some(PinSelector { pin: self.pin_name(segment.spec.pin?)?.to_string(), - layer: self.layer_name(segment.layer)?.to_string(), + layer: self.layer_name(segment.layer)?, }) } @@ -225,7 +225,7 @@ impl Board { Some(PinSelector { pin: self.pin_name(polygon.pin?)?.to_string(), - layer: self.layer_name(polygon.layer)?.to_string(), + layer: self.layer_name(polygon.layer)?, }) } } diff --git a/topola/src/lib.rs b/topola/src/lib.rs index 7066266..fa6185b 100644 --- a/topola/src/lib.rs +++ b/topola/src/lib.rs @@ -15,6 +15,9 @@ mod specctra; pub use crate::autorouter::Autorouter; pub use crate::board::Board; +pub use crate::board::LayerDesc; +pub use crate::board::LayerTier; +pub use crate::board::LayerType; pub use crate::board::selections; pub use crate::layout::LayerId; pub use crate::layout::Layout; diff --git a/topola/src/specctra.rs b/topola/src/specctra.rs index e0a2720..7a4140e 100644 --- a/topola/src/specctra.rs +++ b/topola/src/specctra.rs @@ -11,7 +11,7 @@ use specctra::{ }; use crate::{ - board::{Board, LayerGroupId}, + board::{Board, LayerDesc, LayerGroupId, LayerTier, LayerType}, layout::LayerId, layout::compounds::{ComponentId, NetId, PinId}, math::Vector2, @@ -21,16 +21,45 @@ use crate::{ impl Board { pub fn from_specctra(dsn: DsnFile) -> Self { let coordinate_scale = Self::coordinate_scale(&dsn); + let top_outline_layer_id = LayerId::new(0); let pcb_layer_offset = 1; let bottom_outline_layer_id = LayerId::new(dsn.pcb.structure.layers.len() + pcb_layer_offset); - let mut layer_names = + + let mut layer_descs = BiBTreeMap::from_iter(dsn.pcb.structure.layers.iter().enumerate().map( - |(index, layer)| (LayerId::new(index + pcb_layer_offset), layer.name.clone()), + |(index, _layer)| { + let tier = if index == 0 { + LayerTier::Top + } else if index + 1 == dsn.pcb.structure.layers.len() { + LayerTier::Bottom + } else { + LayerTier::Inner + }; + ( + LayerId::new(index + pcb_layer_offset), + LayerDesc::new(LayerType::Copper, tier, index + pcb_layer_offset), + ) + }, )); - layer_names.insert(top_outline_layer_id, "outlines.top".to_string()); - layer_names.insert(bottom_outline_layer_id, "outlines.bottom".to_string()); + + layer_descs.insert( + top_outline_layer_id, + LayerDesc::new( + LayerType::Outline, + LayerTier::Top, + top_outline_layer_id.index(), + ), + ); + layer_descs.insert( + bottom_outline_layer_id, + LayerDesc::new( + LayerType::Outline, + LayerTier::Bottom, + bottom_outline_layer_id.index(), + ), + ); // assign IDs to all nets named in pcb.network let net_names = { @@ -75,7 +104,7 @@ impl Board { }) .collect(), layer_groups, - layer_names, + layer_descs, net_names, ); let outline_net = board.net_id("outlines").unwrap(); @@ -302,7 +331,7 @@ impl Board { } for wire in dsn.pcb.wiring.wires.iter() { - let layer = board.layer_id(&wire.path.layer).unwrap(); + let layer = Self::layer(&board, &dsn.pcb.structure.layers, &wire.path.layer, true); let net = board.net_id(&wire.net).unwrap(); Self::place_path( @@ -463,9 +492,11 @@ impl Board { }); } - fn layer(board: &Board, layers: &[Layer], name: &str, front: bool) -> LayerId { + fn layer(_board: &Board, layers: &[Layer], name: &str, front: bool) -> LayerId { let pcb_layer_offset = 1; - let image_layer = board.layer_id(name).unwrap(); + let image_layer = LayerId::new( + layers.iter().position(|layer| layer.name == name).unwrap() + pcb_layer_offset, + ); let image_layer_index = image_layer.index() - pcb_layer_offset; if front {