Files
OneCAD-Draft/src/modules/annotate/table_cmd.rs
T
Hakan SevenandClaude Opus 4.8 5bd683cf4e feat(mline): GPU-dash multiline elements with shared A-type alignment
MLINE dashed elements were fully CPU-expanded (apply_along) — one wire per
dash — to keep the parallel lines in phase. Move pure-dash elements onto the
GPU dash shader and end-align every element to the multiline's centre-line so
they share one interior phase.

- wire_distances now accumulates arc-length from the double-single (high+low)
  points, so the dash phase stays precise at UTM coordinates. f32-high-only
  deltas quantised ~0.1 there and drifted the parallel lines apart — the
  original reason MLINE dashes were CPU-side.
- New WireModel.dash_align_end carries a shared begin/end dash length. The
  shader uses it as align_end for every parallel element (the interior phase
  depends on align_end, not the wire's own length) while align_total stays the
  element's own length, so each still ends on a dash at its own endpoint.
- MLINE tessellation derives that length once from the centre-line, routes
  pure dash/space/dot elements to the GPU pattern and text-bearing elements to
  apply_along.
- apply_along gained an "A"-type mode (shared reference length): a solid
  begin/end dash and a phased interior, so its dashes — and embedded text —
  line up with the GPU-dashed sibling elements instead of tiling from start.

Pure-dash MLINEs now cost one WireModel + pattern per element instead of N
segments; single (non-MLINE) entities keep the from-start tiling unchanged.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-17 02:59:11 +03:00

181 lines
5.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// TABLE command — create an empty table entity.
//
// Workflow:
// 1. Text: Enter number of columns (default 3)
// 2. Text: Enter number of rows (default 4, includes header row)
// 3. Point: Click insertion point
//
// Creates a Table entity with uniform row height (0.5) and column width (2.0).
use acadrust::entities::TableBuilder;
use acadrust::types::Vector3;
use acadrust::EntityType;
use glam::DVec3;
use crate::command::{CadCommand, CmdResult};
use crate::modules::{IconKind, ModuleEvent, ToolDef};
use crate::scene::model::wire_model::WireModel;
pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../assets/icons/table.svg"));
pub fn tool() -> ToolDef {
ToolDef {
id: "TABLE",
label: "Table",
icon: ICON,
event: ModuleEvent::Command("TABLE".to_string()),
}
}
const DEFAULT_COLS: usize = 3;
const DEFAULT_ROWS: usize = 4;
const COL_WIDTH: f64 = 2.0;
const ROW_HEIGHT: f64 = 0.5;
enum Step {
Columns,
Rows { cols: usize },
Insertion { cols: usize, rows: usize },
}
pub struct TableCommand {
step: Step,
}
impl TableCommand {
pub fn new() -> Self {
Self {
step: Step::Columns,
}
}
}
impl CadCommand for TableCommand {
fn name(&self) -> &'static str {
"TABLE"
}
fn prompt(&self) -> String {
match &self.step {
Step::Columns => format!("TABLE Enter number of columns [{DEFAULT_COLS}]:"),
Step::Rows { cols } => format!(
"TABLE Enter number of rows (incl. header) [{DEFAULT_ROWS}] ({cols} cols):"
),
Step::Insertion { cols, rows } => {
format!("TABLE Specify insertion point [{cols}×{rows}]:")
}
}
}
fn wants_text_input(&self) -> bool {
!matches!(self.step, Step::Insertion { .. })
}
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
let t = text.trim();
match &self.step {
Step::Columns => {
let cols = if t.is_empty() {
DEFAULT_COLS
} else {
t.parse::<usize>().ok().filter(|&n| n >= 1)?
};
self.step = Step::Rows { cols };
Some(CmdResult::NeedPoint)
}
Step::Rows { cols } => {
let cols = *cols;
let rows = if t.is_empty() {
DEFAULT_ROWS
} else {
t.parse::<usize>().ok().filter(|&n| n >= 1)?
};
self.step = Step::Insertion { cols, rows };
Some(CmdResult::NeedPoint)
}
Step::Insertion { .. } => None,
}
}
fn on_enter(&mut self) -> CmdResult {
match &self.step {
Step::Columns => {
// Accept default.
self.step = Step::Rows { cols: DEFAULT_COLS };
CmdResult::NeedPoint
}
Step::Rows { cols } => {
let cols = *cols;
self.step = Step::Insertion {
cols,
rows: DEFAULT_ROWS,
};
CmdResult::NeedPoint
}
Step::Insertion { .. } => CmdResult::Cancel,
}
}
fn on_point(&mut self, pt: DVec3) -> CmdResult {
if let Step::Insertion { cols, rows } = self.step {
let ins = Vector3::new(pt.x, pt.y, pt.z);
let table = TableBuilder::new(rows, cols)
.at(ins)
.row_height(ROW_HEIGHT)
.column_width(COL_WIDTH)
.build();
CmdResult::CommitAndExit(EntityType::Table(table))
} else {
CmdResult::NeedPoint
}
}
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> { let pt = pt.as_vec3();
if let Step::Insertion { cols, rows } = self.step {
// Preview: outline of the table bounding box.
let w = (cols as f32) * COL_WIDTH as f32;
let h = (rows as f32) * ROW_HEIGHT as f32;
let x = pt.x;
let y = pt.y;
let z = pt.z;
Some(WireModel {
dash_from_start: false,
dash_align_end: None,
text_verts: Vec::new(),
name: "table_preview".into(),
points: vec![
[x, y, z],
[x + w, y, z],
[x + w, y, z],
[x + w, y, z - h],
[x + w, y, z - h],
[x, y, z - h],
[x, y, z - h],
[x, y, z],
],
points_low: Vec::new(),
color: WireModel::CYAN,
selected: false,
pattern_length: 0.0,
pattern: [0.0; 8],
line_weight_px: 1.0,
snap_pts: vec![],
tangent_geoms: vec![],
aci: 0,
key_vertices: vec![],
aabb: WireModel::UNBOUNDED_AABB,
plinegen: true,
vp_scissor: None,
fill_tris: vec![],
fill_tris_low: Vec::new(),
})
} else {
None
}
}
}
// ── Autocomplete registry ─────────────────────────────────
inventory::submit!(crate::command::CommandRegistration { names: &["TABLE"] }); // TableCommand