Instead of mapping colors to strings, map them to desc structs

This commit is contained in:
Mikolaj Wielgus 2026-05-25 00:46:55 +02:00
parent 342081bbe5
commit dd2ce0ce3b
7 changed files with 222 additions and 146 deletions

View File

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

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, 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<String, LayerColors>,
colors: BTreeMap<LayerDesc, LayerColors>,
}
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
}
}
}

68
topola/src/board/layer.rs Normal file
View File

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

View File

@ -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<BiBTreeMap<PinId, String>>,
#[getter(skip)]
layer_names: Recorder<BiBTreeMap<LayerId, String>>,
layer_descs: Recorder<BiBTreeMap<LayerId, LayerDesc>>,
#[getter(skip)]
net_names: Recorder<BiBTreeMap<NetId, String>>,
}
@ -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<Vector2<i64>>,
layer_groups: Vec<LayerGroupId>,
layer_names: BiBTreeMap<LayerId, String>,
layer_descs: BiBTreeMap<LayerId, LayerDesc>,
net_names: BiBTreeMap<NetId, String>,
) -> 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<String> {
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<LayerId> {
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 {

View File

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

View File

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

View File

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