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
+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;
|
||||
|
||||
Reference in new issue
Block a user