fix(annotate): complete table and data workflows

This commit is contained in:
ramox81 committed 2026-09-14 14:54:55 +03:00
1 parent 6505b27818
commit a35c996f9f
17 files changed
+2481 -145

No files matched your search

+44 -2
View File
@@ -22,6 +22,7 @@ pub fn tool() -> ToolDef {
pub struct DataLinkPlaceCommand {
table: Table,
plane: WorkingPlane,
command_name: &'static str,
}
impl DataLinkPlaceCommand {
@@ -30,6 +31,43 @@ impl DataLinkPlaceCommand {
Self {
table,
plane: WorkingPlane::default(),
command_name: "DATALINK",
}
}
/// Place a table that references a data-link object already stored in the
/// drawing. Every populated cell is locked and points at the same link so
/// update and save/reopen keep one stable relationship.
pub fn existing(mut table: Table, link: acadrust::Handle) -> Self {
use acadrust::entities::table::CellStateFlags;
let rows = table.row_count() as i32;
let columns = table.column_count() as i32;
for row in &mut table.rows {
for cell in &mut row.cells {
cell.has_linked_data = true;
cell.data_link_handle = Some(link);
cell.data_link_rows = rows;
cell.data_link_columns = columns;
cell.state.insert(
CellStateFlags::LINKED
| CellStateFlags::CONTENT_LOCKED
| CellStateFlags::FORMAT_LOCKED,
);
}
}
Self {
table,
plane: WorkingPlane::default(),
command_name: "DATALINK",
}
}
/// Place a prepared, unlinked table (used by the extraction wizard).
pub fn unlinked(table: Table) -> Self {
Self {
table,
plane: WorkingPlane::default(),
command_name: "DATAEXTRACTION",
}
}
@@ -105,11 +143,15 @@ impl CadCommand for DataLinkPlaceCommand {
}
fn name(&self) -> &'static str {
"DATALINK"
self.command_name
}
fn prompt(&self) -> String {
t!("DATALINK Specify insertion point:").into_owned()
if self.command_name == "DATALINK" {
t!("DATALINK Specify insertion point:").into_owned()
} else {
t!("DATAEXTRACTION Specify insertion point:").into_owned()
}
}
fn on_point(&mut self, point: DVec3) -> CmdResult {
+58 -5
View File
@@ -35,6 +35,8 @@ pub fn tool() -> ToolDef {
enum Step {
Points,
Format,
Annotation,
AnnotationOptions,
}
pub struct LeaderCommand {
@@ -50,6 +52,7 @@ pub struct LeaderCommand {
gap: f64,
arrow_size: f64,
annotative: bool,
annotation_lines: Vec<String>,
}
impl LeaderCommand {
@@ -76,10 +79,11 @@ impl LeaderCommand {
gap: defaults.gap,
arrow_size: defaults.arrow_size,
annotative: defaults.annotative,
annotation_lines: Vec::new(),
}
}
fn finish(&self) -> CmdResult {
fn finish(&self, annotation: Option<&str>, open_editor: bool) -> CmdResult {
if self.verts.len() < 2 {
return CmdResult::Cancel;
}
@@ -134,8 +138,13 @@ impl LeaderCommand {
// when another representation becomes active.
let mtext_height = displayed_height;
if annotation.is_none() {
leader.creation_type = LeaderCreationType::NoAnnotation;
return CmdResult::CommitAndExit(self.plane.place_entity(EntityType::Leader(leader)));
}
let mtext = build_mtext(
"",
annotation.unwrap_or_default(),
anchor,
mtext_height,
attach,
@@ -150,6 +159,7 @@ impl LeaderCommand {
self.plane.place_entity(EntityType::MText(mtext)),
],
edit_index: 1,
open_editor,
}
}
}
@@ -168,6 +178,16 @@ impl CadCommand for LeaderCommand {
return t!("LEADER Enter leader formatting option [Spline/Straight/Arrow/None] <exit>:")
.into_owned();
}
if self.step == Step::AnnotationOptions {
return t!("LEADER Enter an annotation option [None/Mtext]:").into_owned();
}
if self.step == Step::Annotation {
return if self.annotation_lines.is_empty() {
t!("LEADER Enter first line of annotation text or <options>:").into_owned()
} else {
t!("LEADER Enter next line of annotation text:").into_owned()
};
}
match self.verts.len() {
0 => t!("LEADER Specify leader start point:").into_owned(),
1 => t!("LEADER Specify next point:").into_owned(),
@@ -185,6 +205,12 @@ impl CadCommand for LeaderCommand {
CmdOption::new("None", "NONE"),
];
}
if self.step == Step::AnnotationOptions {
return vec![
CmdOption::new("None", "NONE"),
CmdOption::new("Mtext", "MTEXT"),
];
}
if self.verts.len() >= 2 {
vec![
CmdOption::new("Annotation", "ANNOTATION"),
@@ -197,8 +223,10 @@ impl CadCommand for LeaderCommand {
}
fn input_kind(&self) -> InputKind {
if self.step == Step::Format {
if matches!(self.step, Step::Format | Step::AnnotationOptions) {
InputKind::SingleToken
} else if self.step == Step::Annotation {
InputKind::FreeText
} else {
InputKind::Point
}
@@ -210,6 +238,17 @@ impl CadCommand for LeaderCommand {
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
let keyword = text.trim().to_ascii_uppercase();
if self.step == Step::Annotation {
self.annotation_lines.push(text.to_string());
return Some(CmdResult::NeedPoint);
}
if self.step == Step::AnnotationOptions {
return match keyword.as_str() {
"N" | "NONE" => Some(self.finish(None, false)),
"M" | "MTEXT" => Some(self.finish(Some(""), true)),
_ => None,
};
}
if self.step == Step::Format {
match keyword.as_str() {
"S" | "SPLINE" => self.path_type = LeaderPathType::Spline,
@@ -225,7 +264,10 @@ impl CadCommand for LeaderCommand {
return None;
}
match keyword.as_str() {
"A" | "ANNOTATION" => Some(self.finish()),
"A" | "ANNOTATION" => {
self.step = Step::Annotation;
Some(CmdResult::NeedPoint)
}
"F" | "FORMAT" => {
self.step = Step::Format;
Some(CmdResult::NeedPoint)
@@ -250,8 +292,19 @@ impl CadCommand for LeaderCommand {
if self.step == Step::Format {
self.step = Step::Points;
CmdResult::NeedPoint
} else if self.step == Step::Annotation {
if self.annotation_lines.is_empty() {
self.step = Step::AnnotationOptions;
CmdResult::NeedPoint
} else {
let text = self.annotation_lines.join("\n");
self.finish(Some(&text), false)
}
} else if self.step == Step::AnnotationOptions {
CmdResult::NeedPoint
} else {
self.finish()
self.step = Step::Annotation;
CmdResult::NeedPoint
}
}
+99
View File
@@ -4,6 +4,7 @@
// data rows. The command remembers its previous settings and supports either a
// fixed insertion point or a two-corner sizing window.
use acadrust::entities::table::{CellStyle, CellStylePropertyFlags};
use acadrust::entities::TableBuilder;
use acadrust::types::Vector3;
use acadrust::EntityType;
@@ -101,9 +102,47 @@ pub struct TableCommand {
preview_scale: f64,
title_row: bool,
header_row: bool,
row_style_overrides: Option<[CellStyle; 3]>,
preview_enabled: bool,
plane: WorkingPlane,
}
fn cell_style_from_row(style: &acadrust::objects::RowCellStyle) -> CellStyle {
let mut result = CellStyle::new();
result.property_flags = CellStylePropertyFlags::DATA_TYPE
| CellStylePropertyFlags::DATA_FORMAT
| CellStylePropertyFlags::ALIGNMENT
| CellStylePropertyFlags::CONTENT_COLOR
| CellStylePropertyFlags::TEXT_STYLE
| CellStylePropertyFlags::TEXT_HEIGHT
| CellStylePropertyFlags::BACKGROUND_COLOR;
result.content_color = style.text_color;
result.text_style_handle = style.text_style_handle;
result.text_style_name = style.text_style_name.clone();
result.text_height = style.text_height;
result.alignment = style.alignment as i32;
result.background_color = style.fill_color;
result.fill_enabled = style.fill_enabled;
result.value_data_type = style.data_type;
result.value_unit_type = style.unit_type;
result.value_format = style.format_string.clone();
result
}
fn selected_row_style(
table_style: &acadrust::objects::TableStyle,
name: &str,
) -> CellStyle {
let style = if name.eq_ignore_ascii_case("Title") {
&table_style.title_row_style
} else if name.eq_ignore_ascii_case("Header") {
&table_style.header_row_style
} else {
&table_style.data_row_style
};
cell_style_from_row(style)
}
impl TableCommand {
pub fn new() -> Self {
Self {
@@ -114,6 +153,8 @@ impl TableCommand {
preview_scale: 1.0,
title_row: true,
header_row: true,
row_style_overrides: None,
preview_enabled: true,
plane: WorkingPlane::default(),
}
}
@@ -141,10 +182,60 @@ impl TableCommand {
},
title_row: !style.title_suppressed,
header_row: !style.header_suppressed,
row_style_overrides: None,
preview_enabled: true,
plane: WorkingPlane::default(),
}
}
/// Build the insertion command from the values accepted by the Insert
/// Table dialog. The dialog owns validation, so the command starts at the
/// point/window placement step instead of asking for the same values again.
pub fn configured(
style: Option<(acadrust::Handle, &acadrust::objects::TableStyle)>,
annotation_multiplier: f64,
columns: usize,
data_rows: usize,
column_width: f64,
row_height: f64,
use_window: bool,
preview_enabled: bool,
row_style_names: [&str; 3],
) -> Self {
let mut command = style.map_or_else(Self::new, |(handle, style)| {
Self::with_style(handle, style, annotation_multiplier)
});
let insertion_mode = if use_window {
InsertionMode::Window
} else {
InsertionMode::Point
};
if let Ok(mut saved) = saved_defaults().lock() {
*saved = TableDefaults {
columns,
data_rows,
column_width,
row_height,
insertion_mode,
};
}
command.step = if use_window {
Step::WindowFirst { columns, data_rows }
} else {
Step::Insertion {
columns,
data_rows,
column_width,
row_height,
}
};
command.preview_enabled = preview_enabled;
command.row_style_overrides = style.map(|(_, table_style)| {
row_style_names.map(|name| selected_row_style(table_style, name))
});
command
}
fn defaults(&self) -> TableDefaults {
let mut defaults = saved_defaults().lock().map(|value| *value).unwrap_or_default();
if (defaults.column_width - DEFAULT_COL_WIDTH).abs() < 1.0e-9 {
@@ -175,6 +266,11 @@ impl TableCommand {
.column_width(column_width)
.build();
table.table_style_handle = self.style_handle;
if let Some(styles) = &self.row_style_overrides {
for (row_index, row) in table.rows.iter_mut().enumerate() {
row.style = Some(styles[row_index.min(2)].clone());
}
}
self.plane.place_entity(EntityType::Table(table))
}
@@ -447,6 +543,9 @@ impl CadCommand for TableCommand {
}
fn on_mouse_move(&mut self, point: DVec3) -> Option<WireModel> {
if !self.preview_enabled {
return None;
}
match self.step {
Step::Insertion {
columns,