forked from 1b-agent/opencadstudio-upstream
fix(annotate): complete table and data workflows
This commit is contained in:
1 parent
6505b27818
commit
a35c996f9f
17 files changed
+2481
-145
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -2474,6 +2474,7 @@ impl OpenCADStudio {
|
||||
CmdResult::CommitManyAndEditText {
|
||||
entities,
|
||||
edit_index,
|
||||
open_editor,
|
||||
} => {
|
||||
let label = self.history_label_from_active_cmd(i, "ENTITY");
|
||||
let delta_safe = entities
|
||||
@@ -2527,8 +2528,10 @@ impl OpenCADStudio {
|
||||
if let Some(pd) = pending {
|
||||
self.commit_undo_delta(i, pd);
|
||||
}
|
||||
if let Some(h) = edit_handle {
|
||||
return self.begin_text_edit(h);
|
||||
if open_editor {
|
||||
if let Some(h) = edit_handle {
|
||||
return self.begin_text_edit(h);
|
||||
}
|
||||
}
|
||||
}
|
||||
CmdResult::CreateBlock {
|
||||
|
||||
+1
-26
@@ -668,32 +668,7 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
"TABLE" => {
|
||||
use crate::modules::annotate::table_cmd::TableCommand;
|
||||
let name = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.header
|
||||
.current_table_style_name
|
||||
.clone();
|
||||
let style = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.objects
|
||||
.iter()
|
||||
.find_map(|(handle, object)| match object {
|
||||
acadrust::objects::ObjectType::TableStyle(style)
|
||||
if style.name.eq_ignore_ascii_case(&name) =>
|
||||
{
|
||||
Some((*handle, style.clone()))
|
||||
}
|
||||
_ => None,
|
||||
});
|
||||
let multiplier = self.tabs[i].scene.creation_annotation_multiplier();
|
||||
let cmd = style.as_ref().map_or_else(TableCommand::new, |(handle, style)| {
|
||||
TableCommand::with_style(*handle, style, multiplier)
|
||||
});
|
||||
self.command_line.push_info(&cmd.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||
self.open_table_insert();
|
||||
}
|
||||
|
||||
"TABLEDIT" => {
|
||||
|
||||
+29
-30
@@ -1585,10 +1585,7 @@ impl OpenCADStudio {
|
||||
|
||||
// DATALINK <path.csv> — create a persistent linked table.
|
||||
"DATALINK" => {
|
||||
use crate::command::ValuePromptCommand;
|
||||
let c = ValuePromptCommand::new("DATALINK", "DATALINK path to the .csv file:");
|
||||
self.command_line.push_info(&c.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
||||
self.open_data_link_manager(false);
|
||||
}
|
||||
cmd if cmd.starts_with("DATALINK ") => {
|
||||
let path = cmd.trim_start_matches("DATALINK").trim();
|
||||
@@ -1690,20 +1687,7 @@ impl OpenCADStudio {
|
||||
let Some(link_handle) = link_handle else {
|
||||
continue;
|
||||
};
|
||||
let path = match self.tabs[i].scene.document.objects.get(&link_handle) {
|
||||
Some(acadrust::objects::ObjectType::ClassObject(object)) => {
|
||||
match &object.data {
|
||||
acadrust::objects::ClassObjectData::DataLink(link) => {
|
||||
Some(link.connection_string.clone())
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if let Some(path) = path {
|
||||
jobs.push((*handle, link_handle, path));
|
||||
}
|
||||
jobs.push((*handle, link_handle));
|
||||
}
|
||||
if jobs.is_empty() {
|
||||
self.command_line
|
||||
@@ -1712,28 +1696,43 @@ impl OpenCADStudio {
|
||||
}
|
||||
if write_back {
|
||||
let mut written = 0usize;
|
||||
for (table_handle, _, path) in &jobs {
|
||||
let mut errors = Vec::new();
|
||||
for (table_handle, link_handle) in &jobs {
|
||||
let Some(acadrust::EntityType::Table(table)) =
|
||||
self.tabs[i].scene.document.get_entity(*table_handle)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
let csv = table_to_csv(table);
|
||||
if std::fs::write(path, csv).is_ok() {
|
||||
written += 1;
|
||||
match crate::app::annotation_data::data_link_write_path(
|
||||
&self.tabs[i].scene.document,
|
||||
*link_handle,
|
||||
)
|
||||
.and_then(|path| std::fs::write(path, csv).map_err(|error| error.to_string()))
|
||||
{
|
||||
Ok(()) => written += 1,
|
||||
Err(error) => errors.push(error),
|
||||
}
|
||||
}
|
||||
self.command_line.push_output(
|
||||
crate::tf!("DATALINKUPDATE: wrote {} linked source(s).", written).as_ref(),
|
||||
);
|
||||
if written > 0 {
|
||||
self.command_line.push_output(
|
||||
crate::tf!("DATALINKUPDATE: wrote {} linked source(s).", written).as_ref(),
|
||||
);
|
||||
}
|
||||
for error in errors {
|
||||
self.command_line.push_error(&error);
|
||||
}
|
||||
return Some(Task::none());
|
||||
}
|
||||
let updates: Vec<_> = jobs
|
||||
.into_iter()
|
||||
.filter_map(|(table_handle, link_handle, path)| {
|
||||
std::fs::read_to_string(&path)
|
||||
.ok()
|
||||
.map(|text| (table_handle, link_handle, parse_csv_table(&text)))
|
||||
.filter_map(|(table_handle, link_handle)| {
|
||||
crate::app::annotation_data::read_data_link(
|
||||
&self.tabs[i].scene.document,
|
||||
link_handle,
|
||||
)
|
||||
.ok()
|
||||
.map(|rows| (table_handle, link_handle, rows))
|
||||
})
|
||||
.filter(|(_, _, rows)| !rows.is_empty())
|
||||
.collect();
|
||||
@@ -1888,7 +1887,7 @@ fn parse_landxml_cgpoints(xml: &str) -> Vec<[f64; 3]> {
|
||||
out
|
||||
}
|
||||
|
||||
fn parse_csv_table(text: &str) -> Vec<Vec<String>> {
|
||||
pub(crate) fn parse_csv_table(text: &str) -> Vec<Vec<String>> {
|
||||
let mut rows = Vec::new();
|
||||
let mut row = Vec::new();
|
||||
let mut field = String::new();
|
||||
@@ -1928,7 +1927,7 @@ fn parse_csv_table(text: &str) -> Vec<Vec<String>> {
|
||||
rows
|
||||
}
|
||||
|
||||
fn table_to_csv(table: &acadrust::entities::Table) -> String {
|
||||
pub(crate) fn table_to_csv(table: &acadrust::entities::Table) -> String {
|
||||
fn escape(value: &str) -> String {
|
||||
if value.contains([',', '"', '\r', '\n']) {
|
||||
format!("\"{}\"", value.replace('"', "\"\""))
|
||||
|
||||
@@ -1198,8 +1198,7 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
"DATAEXTRACTION" | "EATTEXT" | "ATTEXT" => {
|
||||
let csv = build_data_extraction_csv(&self.tabs[i].scene.document);
|
||||
return Some(Task::done(Message::DataExtractionSave(csv)));
|
||||
self.open_data_extraction();
|
||||
}
|
||||
|
||||
// ── Find / Replace ────────────────────────────────────────────────
|
||||
@@ -1935,82 +1934,6 @@ fn z_axis_normal(normal: acadrust::types::Vector3) -> bool {
|
||||
normal.x == 0.0 && normal.y == 0.0 && normal.z != 0.0
|
||||
}
|
||||
|
||||
// ── DATAEXTRACTION ─────────────────────────────────────────────────────────
|
||||
|
||||
/// Build a CSV string with one row per entity in model space.
|
||||
/// Columns: Type, Handle, Layer, Color, Linetype, ExtraInfo
|
||||
fn build_data_extraction_csv(doc: &acadrust::CadDocument) -> String {
|
||||
use acadrust::EntityType;
|
||||
|
||||
let mut out = String::from("Type,Handle,Layer,Color,Linetype,ExtraInfo\n");
|
||||
|
||||
let ms_handle = doc.header.model_space_block_handle;
|
||||
for e in doc.entities() {
|
||||
// Skip Block/EndBlock sentinels and paper-space entities.
|
||||
if matches!(e, EntityType::Block(_) | EntityType::BlockEnd(_)) {
|
||||
continue;
|
||||
}
|
||||
if !ms_handle.is_null() && e.common().owner_handle != ms_handle {
|
||||
continue;
|
||||
}
|
||||
let type_name = crate::entities::names::dxf_name(e);
|
||||
let handle = format!("{:X}", e.common().handle.value());
|
||||
let layer = csv_escape(&e.common().layer);
|
||||
let color = format!("{}", e.common().color);
|
||||
let lt = csv_escape(&e.common().linetype);
|
||||
let extra = csv_escape(&entity_extra_info(e));
|
||||
out.push_str(&format!(
|
||||
"{type_name},{handle},{layer},{color},{lt},{extra}\n"
|
||||
));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Return a short geometry summary for CSV ExtraInfo column.
|
||||
fn entity_extra_info(entity: &acadrust::EntityType) -> String {
|
||||
use acadrust::EntityType;
|
||||
match entity {
|
||||
EntityType::Line(e) => format!(
|
||||
"({:.3},{:.3})-({:.3},{:.3})",
|
||||
e.start.x, e.start.y, e.end.x, e.end.y
|
||||
),
|
||||
EntityType::Circle(e) => {
|
||||
format!("C({:.3},{:.3}) R={:.3}", e.center.x, e.center.y, e.radius)
|
||||
}
|
||||
EntityType::Arc(e) => format!(
|
||||
"C({:.3},{:.3}) R={:.3} {:.1}°-{:.1}°",
|
||||
e.center.x,
|
||||
e.center.y,
|
||||
e.radius,
|
||||
e.start_angle.to_degrees(),
|
||||
e.end_angle.to_degrees()
|
||||
),
|
||||
EntityType::Text(e) => e.value.clone(),
|
||||
EntityType::MText(e) => e.value.chars().take(60).collect(),
|
||||
EntityType::Insert(e) => format!(
|
||||
"BLK={} @({:.3},{:.3})",
|
||||
e.block_name, e.insert_point.x, e.insert_point.y
|
||||
),
|
||||
EntityType::LwPolyline(e) => crate::tf!("{} vertices", e.vertices.len()).into_owned(),
|
||||
EntityType::Polyline(e) => crate::tf!("{} vertices", e.vertices.len()).into_owned(),
|
||||
EntityType::Polyline2D(e) => crate::tf!("{} vertices", e.vertices.len()).into_owned(),
|
||||
EntityType::Polyline3D(e) => crate::tf!("{} vertices", e.vertices.len()).into_owned(),
|
||||
EntityType::Hatch(e) => format!("PAT={}", e.pattern.name),
|
||||
EntityType::Dimension(e) => format!("{:.3}", e.base().actual_measurement),
|
||||
EntityType::Spline(e) => crate::tf!("{} ctrl pts", e.control_points.len()).into_owned(),
|
||||
_ => String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Escape a string for a CSV field (wrap in quotes if it contains comma/quote/newline).
|
||||
fn csv_escape(s: &str) -> String {
|
||||
if s.contains(',') || s.contains('"') || s.contains('\n') {
|
||||
format!("\"{}\"", s.replace('"', "\"\""))
|
||||
} else {
|
||||
s.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
// ── CAL — arithmetic expression evaluator ──────────────────────────────────
|
||||
// A small recursive-descent parser for `+ - * /`, parentheses, unary signs and
|
||||
// decimal numbers. Self-contained (no external dependency).
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::path::PathBuf;
|
||||
|
||||
mod blocks;
|
||||
mod dim;
|
||||
mod display;
|
||||
pub(crate) mod display;
|
||||
mod draw;
|
||||
mod fileops;
|
||||
mod inquiry;
|
||||
|
||||
@@ -29,6 +29,7 @@ mod startup;
|
||||
mod style_ops;
|
||||
mod text_inline;
|
||||
mod tolerance_dialog;
|
||||
mod annotation_data;
|
||||
mod update;
|
||||
mod view;
|
||||
mod visibility;
|
||||
@@ -1092,6 +1093,12 @@ pub(super) struct OpenCADStudio {
|
||||
/// without Apply, mirroring the style managers' staging.
|
||||
scale_stage: Option<crate::app::style_ops::ScaleStage>,
|
||||
|
||||
// ── Annotation table/data dialogs ────────────────────────────────────
|
||||
table_insert: crate::ui::window::annotation_data::TableInsertState,
|
||||
data_link_manager: crate::ui::window::annotation_data::DataLinkManagerState,
|
||||
data_link_parent_table: bool,
|
||||
data_extraction: crate::ui::window::annotation_data::DataExtractionState,
|
||||
|
||||
// ── Plot Style Panel ──────────────────────────────────────────────────
|
||||
/// Selected ACI index in the panel (1-255).
|
||||
plotstyle_panel_aci: u8,
|
||||
@@ -1747,6 +1754,9 @@ pub enum ModalKind {
|
||||
/// Add / remove the annotation scales a single selected object has a
|
||||
/// per-object representation for.
|
||||
AnnoObjectScale,
|
||||
InsertTable,
|
||||
DataLinkManager,
|
||||
DataExtraction,
|
||||
/// The scene is drawn by a software rasterizer, or not at all: what that
|
||||
/// means and what usually fixes it. Queued once per verdict; the status
|
||||
/// bar's ⚠ pill reopens it.
|
||||
@@ -3412,6 +3422,35 @@ pub enum Message {
|
||||
/// Background extraction/write completion.
|
||||
WblockWriteFinished(String, std::path::PathBuf, Result<(), String>),
|
||||
// ── DATAEXTRACTION ────────────────────────────────────────────────────
|
||||
TableInsertStyle(String),
|
||||
TableInsertField(crate::ui::window::annotation_data::TableInsertField),
|
||||
TableInsertApply,
|
||||
DataLinkManagerOpen,
|
||||
DataLinkNew,
|
||||
DataLinkSelect(acadrust::types::Handle),
|
||||
DataLinkEdit,
|
||||
DataLinkEditCancel,
|
||||
DataLinkField(crate::ui::window::annotation_data::DataLinkField),
|
||||
DataLinkBrowse,
|
||||
DataLinkBrowseResult(Option<std::path::PathBuf>),
|
||||
DataLinkSave,
|
||||
DataLinkDelete,
|
||||
DataLinkInsert,
|
||||
DataLinkClose,
|
||||
DataExtractionOpen,
|
||||
DataExtractionField(crate::ui::window::annotation_data::DataExtractionField),
|
||||
DataExtractionBack,
|
||||
DataExtractionNext,
|
||||
DataExtractionBrowseSettings,
|
||||
DataExtractionBrowseSettingsResult(Option<std::path::PathBuf>),
|
||||
DataExtractionAddDrawings,
|
||||
DataExtractionAddDrawingsResult(Vec<std::path::PathBuf>),
|
||||
DataExtractionAddFolder,
|
||||
DataExtractionAddFolderResult(Option<std::path::PathBuf>),
|
||||
DataExtractionClearSources,
|
||||
DataExtractionBrowseOutput,
|
||||
DataExtractionBrowseOutputResult(Option<std::path::PathBuf>),
|
||||
DataExtractionFinish,
|
||||
/// Save the pre-built CSV string to a file chosen by the user.
|
||||
DataExtractionSave(String),
|
||||
/// Path chosen (or None = cancelled).
|
||||
@@ -3780,6 +3819,10 @@ impl OpenCADStudio {
|
||||
anno_object_scale_target: None,
|
||||
scale_rename_buf: String::new(),
|
||||
scale_stage: None,
|
||||
table_insert: Default::default(),
|
||||
data_link_manager: Default::default(),
|
||||
data_link_parent_table: false,
|
||||
data_extraction: Default::default(),
|
||||
layout_manager_rename_buf: String::new(),
|
||||
plotstyle_panel_aci: 1,
|
||||
ps_color_buf: String::new(),
|
||||
|
||||
@@ -1070,6 +1070,59 @@ impl OpenCADStudio {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::TableInsertStyle(value) => self.on_table_insert_style(value),
|
||||
Message::TableInsertField(field) => self.on_table_insert_field(field),
|
||||
Message::TableInsertApply => self.on_table_insert_apply(),
|
||||
Message::DataLinkManagerOpen => {
|
||||
let from_table = self.active_modal == Some(super::ModalKind::InsertTable);
|
||||
self.open_data_link_manager(from_table);
|
||||
Task::none()
|
||||
}
|
||||
Message::DataLinkNew => self.on_data_link_new(),
|
||||
Message::DataLinkSelect(handle) => self.on_data_link_select(handle),
|
||||
Message::DataLinkEdit => self.on_data_link_edit(),
|
||||
Message::DataLinkEditCancel => {
|
||||
self.data_link_manager.editing = false;
|
||||
self.data_link_manager.editing_handle = None;
|
||||
self.data_link_manager.status.clear();
|
||||
Task::none()
|
||||
}
|
||||
Message::DataLinkField(field) => self.on_data_link_field(field),
|
||||
Message::DataLinkBrowse => self.on_data_link_browse(),
|
||||
Message::DataLinkBrowseResult(path) => self.on_data_link_browse_result(path),
|
||||
Message::DataLinkSave => self.on_data_link_save(),
|
||||
Message::DataLinkDelete => self.on_data_link_delete(),
|
||||
Message::DataLinkInsert => self.on_data_link_insert(),
|
||||
Message::DataLinkClose => self.on_data_link_close(),
|
||||
Message::DataExtractionOpen => {
|
||||
self.open_data_extraction();
|
||||
Task::none()
|
||||
}
|
||||
Message::DataExtractionField(field) => self.on_data_extraction_field(field),
|
||||
Message::DataExtractionBack => self.on_data_extraction_back(),
|
||||
Message::DataExtractionNext => self.on_data_extraction_next(),
|
||||
Message::DataExtractionBrowseSettings => self.on_data_extraction_browse_settings(),
|
||||
Message::DataExtractionBrowseSettingsResult(path) => {
|
||||
self.on_data_extraction_browse_settings_result(path)
|
||||
}
|
||||
Message::DataExtractionAddDrawings => self.on_data_extraction_add_drawings(),
|
||||
Message::DataExtractionAddDrawingsResult(paths) => {
|
||||
self.on_data_extraction_add_drawings_result(paths)
|
||||
}
|
||||
Message::DataExtractionAddFolder => self.on_data_extraction_add_folder(),
|
||||
Message::DataExtractionAddFolderResult(path) => {
|
||||
self.on_data_extraction_add_folder_result(path)
|
||||
}
|
||||
Message::DataExtractionClearSources => {
|
||||
self.data_extraction.source_files.clear();
|
||||
Task::none()
|
||||
}
|
||||
Message::DataExtractionBrowseOutput => self.on_data_extraction_browse_output(),
|
||||
Message::DataExtractionBrowseOutputResult(path) => {
|
||||
self.on_data_extraction_browse_output_result(path)
|
||||
}
|
||||
Message::DataExtractionFinish => self.on_data_extraction_finish(),
|
||||
|
||||
Message::DataExtractionSave(csv) => {
|
||||
let csv_clone = csv.clone();
|
||||
Task::perform(
|
||||
|
||||
@@ -31,6 +31,9 @@ impl OpenCADStudio {
|
||||
Some(K::LayoutManager) => crate::tr!("modal", "layout-manager"),
|
||||
Some(K::ScaleManager) => crate::tr!("modal", "scale-manager"),
|
||||
Some(K::AnnoObjectScale) => crate::tr!("modal", "annotation-object-scale"),
|
||||
Some(K::InsertTable) => crate::t!("Insert Table").into_owned(),
|
||||
Some(K::DataLinkManager) => crate::t!("Data Link Manager").into_owned(),
|
||||
Some(K::DataExtraction) => crate::t!("Data Extraction Wizard").into_owned(),
|
||||
Some(K::Plotstyle) => crate::tr!("modal", "plot-style-editor"),
|
||||
Some(K::TextStyle) => crate::tr!("modal", "text-style-manager"),
|
||||
Some(K::MlStyle) => crate::tr!("modal", "multiline-style-manager"),
|
||||
@@ -581,6 +584,24 @@ impl OpenCADStudio {
|
||||
},
|
||||
)
|
||||
}
|
||||
super::super::ModalKind::InsertTable => sized_flow(
|
||||
ex,
|
||||
620,
|
||||
650,
|
||||
|flow| crate::ui::window::annotation_data::table_insert_view(&self.table_insert, flow),
|
||||
),
|
||||
super::super::ModalKind::DataLinkManager => sized_flow(
|
||||
ex,
|
||||
760,
|
||||
560,
|
||||
|flow| crate::ui::window::annotation_data::data_link_view(&self.data_link_manager, flow),
|
||||
),
|
||||
super::super::ModalKind::DataExtraction => sized_flow(
|
||||
ex,
|
||||
780,
|
||||
570,
|
||||
|flow| crate::ui::window::annotation_data::data_extraction_view(&self.data_extraction, flow),
|
||||
),
|
||||
super::super::ModalKind::Plotstyle => sized_flow(
|
||||
ex,
|
||||
780,
|
||||
|
||||
Reference in new issue
Block a user