feat: Plot Style Table Editor GUI (PLOTSTYLEPANEL / STYLESMANAGER)
Opens a panel overlay showing all 255 ACI entries with their current overrides (color, lineweight, screening). Allows editing entries interactively, creating a new identity table when none is loaded, and saving the modified table back to a CTB/STB file. Commands: PLOTSTYLEPANEL, PLOTSTYLEEDITOR, STYLESMANAGER Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
1fceec92ea
commit
b8d15e5155
5 changed files with 373 additions and 3 deletions
|
|
@ -153,7 +153,7 @@ Underlay (PDF/DWF/DGN)
|
|||
| TEXT (DT) | ✅ |
|
||||
| MTEXT (T) | ✅ |
|
||||
| DIMSTYLE yöneticisi (DIMSTYLE/DDIM) | ✅ |
|
||||
| MLEADERSTYLE | ⬜ |
|
||||
| MLEADERSTYLE | ✅ |
|
||||
| DIMORDINATE | ✅ |
|
||||
|
||||
---
|
||||
|
|
@ -294,7 +294,7 @@ Underlay (PDF/DWF/DGN)
|
|||
| Solid3D tessellation (acadrust ACIS) | ✅ |
|
||||
| Boolean operasyonlar (UNION/SUBTRACT/INTERSECT) | ⬜ |
|
||||
| EXTRUDE / REVOLVE | ✅ |
|
||||
| SWEEP / LOFT | ⬜ |
|
||||
| SWEEP / LOFT | ✅ |
|
||||
| 3D ARRAY | ✅ |
|
||||
| STL dışa aktarma (STLOUT) | ✅ |
|
||||
| STEP dışa aktarma | ⬜ |
|
||||
|
|
|
|||
|
|
@ -3579,6 +3579,11 @@ impl H7CAD {
|
|||
return Task::done(Message::StlExport);
|
||||
}
|
||||
|
||||
// ── Plot Style Editor GUI ─────────────────────────────────────
|
||||
"PLOTSTYLEPANEL"|"PLOTSTYLEEDITOR"|"STYLESMANAGER" => {
|
||||
return Task::done(Message::PlotStylePanelOpen);
|
||||
}
|
||||
|
||||
// ── Plot / Page Setup ──────────────────────────────────────────
|
||||
"PRINT"|"PLOT"|"EXPORT" => {
|
||||
return Task::done(Message::PlotExport);
|
||||
|
|
|
|||
|
|
@ -112,6 +112,15 @@ pub(super) struct H7CAD {
|
|||
/// Edit buffer for oblique angle (degrees).
|
||||
textstyle_oblique: String,
|
||||
|
||||
// ── Plot Style Panel ──────────────────────────────────────────────────
|
||||
plotstyle_panel_open: bool,
|
||||
/// Selected ACI index in the panel (1-255).
|
||||
plotstyle_panel_aci: u8,
|
||||
/// Edit buffers for the selected entry.
|
||||
ps_color_buf: String,
|
||||
ps_lineweight_buf: String,
|
||||
ps_screening_buf: String,
|
||||
|
||||
// ── DimStyle Dialog ───────────────────────────────────────────────────
|
||||
dimstyle_open: bool,
|
||||
/// Name of the style currently shown in the dialog.
|
||||
|
|
@ -371,6 +380,21 @@ pub enum Message {
|
|||
PlotStyleLoaded(Option<crate::io::plot_style::PlotStyleTable>),
|
||||
/// Clear the active plot style table.
|
||||
PlotStyleClear,
|
||||
/// Open/close the Plot Style panel.
|
||||
PlotStylePanelOpen,
|
||||
PlotStylePanelClose,
|
||||
/// Select an ACI entry in the panel.
|
||||
PlotStylePanelSelectAci(u8),
|
||||
/// Edit buffers changed.
|
||||
PlotStylePanelColorBuf(String),
|
||||
PlotStylePanelLwBuf(String),
|
||||
PlotStylePanelScreenBuf(String),
|
||||
/// Apply current edit buffers to the selected ACI entry.
|
||||
PlotStylePanelApply,
|
||||
/// Save the modified table back to disk.
|
||||
PlotStylePanelSave,
|
||||
/// Save callback.
|
||||
PlotStylePanelSavePath(Option<std::path::PathBuf>),
|
||||
// ── TextStyle Font Browser ────────────────────────────────────────────
|
||||
TextStyleDialogOpen,
|
||||
TextStyleDialogClose,
|
||||
|
|
@ -490,6 +514,11 @@ impl H7CAD {
|
|||
page_setup_scale: "Fit".to_string(),
|
||||
// Plot style
|
||||
active_plot_style: None,
|
||||
plotstyle_panel_open: false,
|
||||
plotstyle_panel_aci: 1,
|
||||
ps_color_buf: String::new(),
|
||||
ps_lineweight_buf: "255".to_string(),
|
||||
ps_screening_buf: "100".to_string(),
|
||||
// TextStyle font browser
|
||||
textstyle_open: false,
|
||||
textstyle_selected: "Standard".to_string(),
|
||||
|
|
|
|||
|
|
@ -2603,6 +2603,112 @@ impl H7CAD {
|
|||
Task::none()
|
||||
}
|
||||
|
||||
// ── Plot Style Panel ──────────────────────────────────────────────
|
||||
Message::PlotStylePanelOpen => {
|
||||
self.plotstyle_panel_open = true;
|
||||
// Initialise edit buffers for ACI 1.
|
||||
self.plotstyle_panel_aci = 1;
|
||||
let entry = self.active_plot_style.as_ref()
|
||||
.and_then(|t| t.aci_entries.get(1));
|
||||
self.ps_color_buf = entry.and_then(|e| e.color.map(|[r,g,b]| format!("#{:02X}{:02X}{:02X}", r, g, b))).unwrap_or_default();
|
||||
self.ps_lineweight_buf = entry.map(|e| e.lineweight.to_string()).unwrap_or("255".into());
|
||||
self.ps_screening_buf = entry.map(|e| e.screening.to_string()).unwrap_or("100".into());
|
||||
Task::none()
|
||||
}
|
||||
Message::PlotStylePanelClose => {
|
||||
self.plotstyle_panel_open = false;
|
||||
Task::none()
|
||||
}
|
||||
Message::PlotStylePanelSelectAci(aci) => {
|
||||
self.plotstyle_panel_aci = aci;
|
||||
let entry = self.active_plot_style.as_ref()
|
||||
.and_then(|t| t.aci_entries.get(aci as usize));
|
||||
self.ps_color_buf = entry.and_then(|e| e.color.map(|[r,g,b]| format!("#{:02X}{:02X}{:02X}", r, g, b))).unwrap_or_default();
|
||||
self.ps_lineweight_buf = entry.map(|e| e.lineweight.to_string()).unwrap_or("255".into());
|
||||
self.ps_screening_buf = entry.map(|e| e.screening.to_string()).unwrap_or("100".into());
|
||||
Task::none()
|
||||
}
|
||||
Message::PlotStylePanelColorBuf(s) => { self.ps_color_buf = s; Task::none() }
|
||||
Message::PlotStylePanelLwBuf(s) => { self.ps_lineweight_buf = s; Task::none() }
|
||||
Message::PlotStylePanelScreenBuf(s) => { self.ps_screening_buf = s; Task::none() }
|
||||
|
||||
Message::PlotStylePanelApply => {
|
||||
let aci = self.plotstyle_panel_aci as usize;
|
||||
if let Some(table) = self.active_plot_style.as_mut() {
|
||||
if let Some(entry) = table.aci_entries.get_mut(aci) {
|
||||
// Parse color.
|
||||
let color_str = self.ps_color_buf.trim();
|
||||
if color_str.is_empty() {
|
||||
entry.color = None;
|
||||
} else if color_str.starts_with('#') && color_str.len() == 7 {
|
||||
let r = u8::from_str_radix(&color_str[1..3], 16).unwrap_or(0);
|
||||
let g = u8::from_str_radix(&color_str[3..5], 16).unwrap_or(0);
|
||||
let b = u8::from_str_radix(&color_str[5..7], 16).unwrap_or(0);
|
||||
entry.color = Some([r, g, b]);
|
||||
}
|
||||
if let Ok(lw) = self.ps_lineweight_buf.trim().parse::<u8>() {
|
||||
entry.lineweight = lw;
|
||||
}
|
||||
if let Ok(sc) = self.ps_screening_buf.trim().parse::<u8>() {
|
||||
entry.screening = sc.min(100);
|
||||
}
|
||||
self.command_line.push_output(&format!("Plot style ACI {aci} updated."));
|
||||
}
|
||||
} else {
|
||||
// No table loaded: create an identity table and apply.
|
||||
let mut table = crate::io::plot_style::PlotStyleTable::identity("Custom.ctb");
|
||||
if let Some(entry) = table.aci_entries.get_mut(aci) {
|
||||
let color_str = self.ps_color_buf.trim();
|
||||
if color_str.starts_with('#') && color_str.len() == 7 {
|
||||
let r = u8::from_str_radix(&color_str[1..3], 16).unwrap_or(0);
|
||||
let g = u8::from_str_radix(&color_str[3..5], 16).unwrap_or(0);
|
||||
let b = u8::from_str_radix(&color_str[5..7], 16).unwrap_or(0);
|
||||
entry.color = Some([r, g, b]);
|
||||
}
|
||||
if let Ok(lw) = self.ps_lineweight_buf.trim().parse::<u8>() { entry.lineweight = lw; }
|
||||
if let Ok(sc) = self.ps_screening_buf.trim().parse::<u8>() { entry.screening = sc.min(100); }
|
||||
}
|
||||
self.active_plot_style = Some(table);
|
||||
self.command_line.push_output(&format!("Created new CTB table, ACI {aci} updated."));
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::PlotStylePanelSave => {
|
||||
if self.active_plot_style.is_none() {
|
||||
self.command_line.push_error("No plot style table loaded. Load or create one first.");
|
||||
return Task::none();
|
||||
}
|
||||
let default_name = self.active_plot_style.as_ref()
|
||||
.map(|t| t.name.clone()).unwrap_or("export.ctb".into());
|
||||
Task::perform(
|
||||
async move {
|
||||
rfd::AsyncFileDialog::new()
|
||||
.set_title("Save Plot Style Table")
|
||||
.set_file_name(&default_name)
|
||||
.add_filter("Plot Style Files", &["ctb", "stb", "CTB", "STB"])
|
||||
.add_filter("All Files", &["*"])
|
||||
.save_file()
|
||||
.await
|
||||
.map(|h| h.path().to_path_buf())
|
||||
},
|
||||
Message::PlotStylePanelSavePath,
|
||||
)
|
||||
}
|
||||
|
||||
Message::PlotStylePanelSavePath(Some(path)) => {
|
||||
if let Some(table) = &self.active_plot_style {
|
||||
match table.save(&path) {
|
||||
Ok(()) => self.command_line.push_output(&format!(
|
||||
"Plot style table saved to \"{}\".", path.display()
|
||||
)),
|
||||
Err(e) => self.command_line.push_error(&format!("Save error: {e}")),
|
||||
}
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
Message::PlotStylePanelSavePath(None) => Task::none(),
|
||||
|
||||
// ── TextStyle Font Browser ────────────────────────────────────────
|
||||
Message::TextStyleDialogOpen => {
|
||||
let i = self.active_tab;
|
||||
|
|
|
|||
232
src/app/view.rs
232
src/app/view.rs
|
|
@ -311,6 +311,19 @@ impl H7CAD {
|
|||
iced::widget::Space::new().width(0).height(0).into()
|
||||
};
|
||||
|
||||
// ── Plot Style Panel ──────────────────────────────────────────────
|
||||
let plotstyle_layer: Element<'_, Message> = if self.plotstyle_panel_open {
|
||||
plotstyle_panel_overlay(
|
||||
self.active_plot_style.as_ref(),
|
||||
self.plotstyle_panel_aci,
|
||||
&self.ps_color_buf,
|
||||
&self.ps_lineweight_buf,
|
||||
&self.ps_screening_buf,
|
||||
)
|
||||
} else {
|
||||
iced::widget::Space::new().width(0).height(0).into()
|
||||
};
|
||||
|
||||
let dimstyle_layer: Element<'_, Message> = if self.dimstyle_open {
|
||||
let tab = &self.tabs[self.active_tab];
|
||||
let styles: Vec<String> = tab.scene.document.dim_styles
|
||||
|
|
@ -339,7 +352,7 @@ impl H7CAD {
|
|||
}
|
||||
};
|
||||
|
||||
stack![main_ui, self.app_menu.view(), snap_layer, dropdown_layer, layout_ctx_layer, page_setup_layer, textstyle_layer, tablestyle_layer, mlstyle_layer, dimstyle_layer, viewport_ctx_layer].into()
|
||||
stack![main_ui, self.app_menu.view(), snap_layer, dropdown_layer, layout_ctx_layer, page_setup_layer, textstyle_layer, tablestyle_layer, mlstyle_layer, plotstyle_layer, dimstyle_layer, viewport_ctx_layer].into()
|
||||
}
|
||||
|
||||
pub fn subscription(&self) -> Subscription<Message> {
|
||||
|
|
@ -1753,3 +1766,220 @@ fn mlstyle_overlay<'a>(
|
|||
|
||||
stack![catcher, positioned].into()
|
||||
}
|
||||
|
||||
// ── Plot Style Panel ───────────────────────────────────────────────────────
|
||||
|
||||
fn plotstyle_panel_overlay<'a>(
|
||||
table: Option<&'a crate::io::plot_style::PlotStyleTable>,
|
||||
selected_aci: u8,
|
||||
color_buf: &'a str,
|
||||
lw_buf: &'a str,
|
||||
screen_buf: &'a str,
|
||||
) -> Element<'a, Message> {
|
||||
use iced::Length::Fill;
|
||||
use iced::Color;
|
||||
use iced::widget::scrollable;
|
||||
|
||||
const PANEL_BG: Color = Color { r: 0.15, g: 0.15, b: 0.15, a: 1.0 };
|
||||
const BORDER: Color = Color { r: 0.35, g: 0.35, b: 0.35, a: 1.0 };
|
||||
const TEXT_COL: Color = Color { r: 0.88, g: 0.88, b: 0.88, a: 1.0 };
|
||||
const DIM_COL: Color = Color { r: 0.55, g: 0.55, b: 0.55, a: 1.0 };
|
||||
const ACCENT: Color = Color { r: 0.25, g: 0.50, b: 0.85, a: 1.0 };
|
||||
const ACTIVE_BG: Color = Color { r: 0.20, g: 0.40, b: 0.70, a: 1.0 };
|
||||
|
||||
let btn_style = |accent: bool| move |_: &Theme, status: button::Status| button::Style {
|
||||
background: Some(Background::Color(match (accent, status) {
|
||||
(true, button::Status::Hovered | button::Status::Pressed) => Color { r: 0.20, g: 0.42, b: 0.72, a: 1.0 },
|
||||
(false, button::Status::Hovered | button::Status::Pressed) => Color { r: 0.28, g: 0.28, b: 0.28, a: 1.0 },
|
||||
(true, _) => ACCENT,
|
||||
_ => Color { r: 0.22, g: 0.22, b: 0.22, a: 1.0 },
|
||||
})),
|
||||
text_color: TEXT_COL,
|
||||
border: Border { color: BORDER, width: 1.0, radius: 4.0.into() },
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let field_style = |_: &Theme, _: text_input::Status| text_input::Style {
|
||||
background: Background::Color(Color { r: 0.10, g: 0.10, b: 0.10, a: 1.0 }),
|
||||
border: Border { color: BORDER, width: 1.0, radius: 3.0.into() },
|
||||
icon: TEXT_COL,
|
||||
placeholder: DIM_COL,
|
||||
value: TEXT_COL,
|
||||
selection: ACCENT,
|
||||
};
|
||||
|
||||
// ACI color list (1..=255)
|
||||
let aci_items: Vec<Element<'_, Message>> = (1u8..=255).map(|aci| {
|
||||
let is_sel = aci == selected_aci;
|
||||
let has_override = table.and_then(|t| t.aci_entries.get(aci as usize))
|
||||
.map(|e| e.color.is_some() || e.lineweight != 255 || e.screening != 100)
|
||||
.unwrap_or(false);
|
||||
let lw_str = table.and_then(|t| t.aci_entries.get(aci as usize))
|
||||
.and_then(|e| {
|
||||
if e.lineweight != 255 {
|
||||
crate::io::plot_style::LW_TABLE.get(e.lineweight as usize)
|
||||
.map(|lw| format!("{:.2}mm", lw))
|
||||
} else { None }
|
||||
}).unwrap_or_default();
|
||||
let color_str = table.and_then(|t| t.aci_entries.get(aci as usize))
|
||||
.and_then(|e| e.color.map(|[r, g, b]| format!("#{:02X}{:02X}{:02X}", r, g, b)))
|
||||
.unwrap_or_default();
|
||||
let label = if has_override {
|
||||
format!("{aci:>3} {color_str:<9} {lw_str}")
|
||||
} else {
|
||||
format!("{aci:>3} (default)")
|
||||
};
|
||||
button(text(label).size(10).color(TEXT_COL).font(iced::Font::MONOSPACE))
|
||||
.on_press(Message::PlotStylePanelSelectAci(aci))
|
||||
.style(move |_: &Theme, st| button::Style {
|
||||
background: Some(Background::Color(match (is_sel, st) {
|
||||
(true, _) => ACTIVE_BG,
|
||||
(false, button::Status::Hovered | button::Status::Pressed) =>
|
||||
Color { r: 0.26, g: 0.26, b: 0.26, a: 1.0 },
|
||||
_ => Color::TRANSPARENT,
|
||||
})),
|
||||
text_color: TEXT_COL,
|
||||
..Default::default()
|
||||
})
|
||||
.padding([2, 8])
|
||||
.width(Fill)
|
||||
.into()
|
||||
}).collect();
|
||||
|
||||
let list_panel = container(
|
||||
scrollable(column(aci_items).spacing(1)).height(300)
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color { r: 0.12, g: 0.12, b: 0.12, a: 1.0 })),
|
||||
border: Border { color: BORDER, width: 1.0, radius: 4.0.into() },
|
||||
..Default::default()
|
||||
})
|
||||
.padding(4)
|
||||
.width(260);
|
||||
|
||||
// Right panel: edit selected entry
|
||||
let entry = table.and_then(|t| t.aci_entries.get(selected_aci as usize));
|
||||
let edit_panel: Element<'_, Message> = {
|
||||
let lbl = |s: &'static str| text(s).size(11).color(DIM_COL);
|
||||
column![
|
||||
row![
|
||||
text("ACI:").size(11).color(DIM_COL).width(90),
|
||||
text(format!("{selected_aci}")).size(11).color(TEXT_COL),
|
||||
].spacing(8).align_y(iced::Center),
|
||||
lbl("Color override (#RRGGBB):"),
|
||||
text_input("#RRGGBB or blank", color_buf)
|
||||
.on_input(Message::PlotStylePanelColorBuf)
|
||||
.style(field_style)
|
||||
.size(11)
|
||||
.padding([4, 8]),
|
||||
lbl("Lineweight index (0-24, 255=obj):"),
|
||||
text_input("255", lw_buf)
|
||||
.on_input(Message::PlotStylePanelLwBuf)
|
||||
.style(field_style)
|
||||
.size(11)
|
||||
.padding([4, 8]),
|
||||
lbl("Screening (0-100):"),
|
||||
text_input("100", screen_buf)
|
||||
.on_input(Message::PlotStylePanelScreenBuf)
|
||||
.style(field_style)
|
||||
.size(11)
|
||||
.padding([4, 8]),
|
||||
Space::new().height(8),
|
||||
{
|
||||
let cur_color = entry.and_then(|e| e.color.map(|[r,g,b]| format!("#{:02X}{:02X}{:02X}", r,g,b))).unwrap_or("(none)".into());
|
||||
let cur_lw = entry.map(|e| if e.lineweight == 255 { "object".into() } else {
|
||||
crate::io::plot_style::LW_TABLE.get(e.lineweight as usize)
|
||||
.map(|lw| format!("{:.2}mm (idx {})", lw, e.lineweight))
|
||||
.unwrap_or_else(|| format!("idx {}", e.lineweight))
|
||||
}).unwrap_or("—".into());
|
||||
let cur_scr = entry.map(|e| format!("{}%", e.screening)).unwrap_or("—".into());
|
||||
let vals: Element<'_, Message> = column![
|
||||
text("Current values:").size(10).color(DIM_COL),
|
||||
text(format!(" Color: {cur_color}")).size(10).color(TEXT_COL),
|
||||
text(format!(" Lweight: {cur_lw}")).size(10).color(TEXT_COL),
|
||||
text(format!(" Screening: {cur_scr}")).size(10).color(TEXT_COL),
|
||||
].spacing(3).into();
|
||||
vals
|
||||
},
|
||||
Space::new().height(Fill),
|
||||
button(text("Apply to ACI").size(11))
|
||||
.on_press(Message::PlotStylePanelApply)
|
||||
.style(btn_style(true))
|
||||
.padding([5, 10]),
|
||||
]
|
||||
.spacing(6)
|
||||
.width(220)
|
||||
.height(300)
|
||||
.into()
|
||||
};
|
||||
|
||||
let table_name = table.map(|t| t.name.as_str()).unwrap_or("(no table loaded)");
|
||||
|
||||
let panel = container(
|
||||
column![
|
||||
row![
|
||||
text("Plot Style Table Editor").size(13).color(TEXT_COL),
|
||||
Space::new().width(Fill),
|
||||
button(text("✕").size(12).color(DIM_COL))
|
||||
.on_press(Message::PlotStylePanelClose)
|
||||
.style(|_: &Theme, _| button::Style {
|
||||
background: Some(Background::Color(Color::TRANSPARENT)),
|
||||
text_color: DIM_COL,
|
||||
..Default::default()
|
||||
})
|
||||
.padding([2, 6]),
|
||||
].align_y(iced::Center),
|
||||
text(format!("Table: {table_name}")).size(11).color(DIM_COL),
|
||||
Space::new().height(8),
|
||||
row![
|
||||
list_panel,
|
||||
Space::new().width(12),
|
||||
edit_panel,
|
||||
].align_y(iced::alignment::Vertical::Top),
|
||||
Space::new().height(8),
|
||||
row![
|
||||
button(text("Load CTB/STB").size(11))
|
||||
.on_press(Message::PlotStyleLoad)
|
||||
.style(btn_style(false))
|
||||
.padding([5, 10]),
|
||||
button(text("Save As…").size(11))
|
||||
.on_press(Message::PlotStylePanelSave)
|
||||
.style(btn_style(false))
|
||||
.padding([5, 10]),
|
||||
button(text("Clear Table").size(11))
|
||||
.on_press(Message::PlotStyleClear)
|
||||
.style(btn_style(false))
|
||||
.padding([5, 10]),
|
||||
Space::new().width(Fill),
|
||||
button(text("Close").size(11))
|
||||
.on_press(Message::PlotStylePanelClose)
|
||||
.style(btn_style(false))
|
||||
.padding([5, 10]),
|
||||
].spacing(8),
|
||||
]
|
||||
.spacing(6)
|
||||
.padding(16)
|
||||
)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(PANEL_BG)),
|
||||
border: Border { color: BORDER, width: 1.0, radius: 6.0.into() },
|
||||
..Default::default()
|
||||
})
|
||||
.width(520)
|
||||
.height(430);
|
||||
|
||||
let catcher = mouse_area(container(Space::new().width(Fill).height(Fill))
|
||||
.width(Fill).height(Fill)
|
||||
.style(|_: &Theme| container::Style {
|
||||
background: Some(Background::Color(Color { r: 0.0, g: 0.0, b: 0.0, a: 0.45 })),
|
||||
..Default::default()
|
||||
})
|
||||
).on_press(Message::PlotStylePanelClose);
|
||||
|
||||
let positioned = container(panel)
|
||||
.width(Fill).height(Fill)
|
||||
.align_x(iced::Alignment::Center)
|
||||
.align_y(iced::Alignment::Center);
|
||||
|
||||
stack![catcher, positioned].into()
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue