From 313e7226d21b099c192bdda606ae30b1a6f4d19c Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Thu, 2 Apr 2026 23:11:09 +0300 Subject: [PATCH] =?UTF-8?q?Feat:=20Phase=205.4=20=E2=80=94=20Table=20entit?= =?UTF-8?q?y=20render=20(grid=20wireframe=20+=20cell=20text)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 4.6 --- src/entities/mod.rs | 1 + src/entities/table.rs | 174 +++++++++++++++++++++++++++++++++++++++++ src/entities/traits.rs | 9 +++ 3 files changed, 184 insertions(+) create mode 100644 src/entities/table.rs diff --git a/src/entities/mod.rs b/src/entities/mod.rs index d596fd66..86db71ea 100644 --- a/src/entities/mod.rs +++ b/src/entities/mod.rs @@ -20,6 +20,7 @@ pub mod ray; pub mod spline; pub mod text; mod text_support; +pub mod table; pub mod tolerance; pub mod traits; pub mod viewport; diff --git a/src/entities/table.rs b/src/entities/table.rs new file mode 100644 index 00000000..5e06d015 --- /dev/null +++ b/src/entities/table.rs @@ -0,0 +1,174 @@ +use acadrust::entities::Table; +use glam::Vec3; + +use crate::command::EntityTransform; +use crate::entities::common::{ro_prop as ro, square_grip}; +use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible}; +use crate::scene::acad_to_truck::{TruckEntity, TruckObject}; +use crate::scene::object::{GripApply, GripDef, PropSection}; +use crate::scene::wire_model::SnapHint; +use crate::scene::{cxf, transform}; + +fn v3(v: &acadrust::types::Vector3) -> Vec3 { + Vec3::new(v.x as f32, v.y as f32, v.z as f32) +} + +impl TruckConvertible for Table { + fn to_truck(&self, _document: &acadrust::CadDocument) -> Option { + if self.rows.is_empty() || self.columns.is_empty() { + return None; + } + + let origin = v3(&self.insertion_point); + let h_raw = v3(&self.horizontal_direction); + let h = if h_raw.length_squared() > 1e-10 { h_raw.normalize() } else { Vec3::X }; + // Perpendicular "down" direction in the drawing plane (tables grow downward) + let v_down = Vec3::new(h.y, -h.x, 0.0); + + let col_offsets: Vec = { + let mut off = 0.0f32; + let mut v = vec![0.0f32]; + for col in &self.columns { + off += col.width as f32; + v.push(off); + } + v + }; + let total_w = *col_offsets.last().unwrap_or(&0.0); + + let row_offsets: Vec = { + let mut off = 0.0f32; + let mut v = vec![0.0f32]; + for row in &self.rows { + off += row.height as f32; + v.push(off); + } + v + }; + let total_h = *row_offsets.last().unwrap_or(&0.0); + + let mut pts: Vec<[f32; 3]> = Vec::new(); + + let mut add_seg = |a: Vec3, b: Vec3| { + if !pts.is_empty() { + pts.push([f32::NAN; 3]); + } + pts.push([a.x, a.y, a.z]); + pts.push([b.x, b.y, b.z]); + }; + + for &ry in &row_offsets { + let left = origin + h * 0.0 + v_down * ry; + let right = origin + h * total_w + v_down * ry; + add_seg(left, right); + } + + for &cx in &col_offsets { + let top = origin + h * cx + v_down * 0.0; + let bottom = origin + h * cx + v_down * total_h; + add_seg(top, bottom); + } + + // Cell text — lifted into Lines points via 2D strokes + let text_height = 0.18_f32; + for (ri, row) in self.rows.iter().enumerate() { + let row_top = row_offsets[ri]; + let row_bot = row_offsets.get(ri + 1).copied().unwrap_or(row_top + row.height as f32); + let row_mid = (row_top + row_bot) * 0.5; + + for (ci, cell) in row.cells.iter().enumerate() { + let text = cell.text_value(); + if text.is_empty() { + continue; + } + let col_left = col_offsets[ci]; + let col_mid = col_left + self.columns.get(ci).map(|c| c.width as f32 * 0.5).unwrap_or(0.5); + let cell_center = origin + h * col_mid + v_down * row_mid; + + let strokes = cxf::tessellate_text_ex( + [cell_center.x, cell_center.y], + text_height, + 0.0, + 1.0, + 0.0, + "txt", + text, + ); + for stroke in strokes { + if !pts.is_empty() { + pts.push([f32::NAN; 3]); + } + for [x, y] in stroke { + pts.push([x, y, origin.z]); + } + } + } + } + + Some(TruckEntity { + object: TruckObject::Lines(pts), + snap_pts: vec![(origin, SnapHint::Insertion)], + tangent_geoms: vec![], + key_vertices: vec![], + }) + } +} + +impl Grippable for Table { + fn grips(&self) -> Vec { + vec![square_grip(0, v3(&self.insertion_point))] + } + + fn apply_grip(&mut self, grip_id: usize, apply: GripApply) { + if grip_id == 0 { + match apply { + GripApply::Translate(d) => { + self.insertion_point.x += d.x as f64; + self.insertion_point.y += d.y as f64; + self.insertion_point.z += d.z as f64; + } + GripApply::Absolute(p) => { + self.insertion_point.x = p.x as f64; + self.insertion_point.y = p.y as f64; + self.insertion_point.z = p.z as f64; + } + } + } + } +} + +impl PropertyEditable for Table { + fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection { + PropSection { + title: "Table".into(), + props: vec![ + ro("Rows", "tbl_rows", self.rows.len().to_string()), + ro("Columns", "tbl_cols", self.columns.len().to_string()), + ro("Insert X","tbl_ix", format!("{:.4}", self.insertion_point.x)), + ro("Insert Y","tbl_iy", format!("{:.4}", self.insertion_point.y)), + ro("Insert Z","tbl_iz", format!("{:.4}", self.insertion_point.z)), + ], + } + } + + fn apply_geom_prop(&mut self, _field: &str, _value: &str) {} +} + +impl Transformable for Table { + fn apply_transform(&mut self, t: &EntityTransform) { + transform::apply_standard_entity_transform(self, t, |entity, p1, p2| { + transform::reflect_xy_point( + &mut entity.insertion_point.x, + &mut entity.insertion_point.y, + p1, + p2, + ); + // Reflect the horizontal direction by reflecting a tip point + let mut tip_x = entity.insertion_point.x + entity.horizontal_direction.x; + let mut tip_y = entity.insertion_point.y + entity.horizontal_direction.y; + transform::reflect_xy_point(&mut tip_x, &mut tip_y, p1, p2); + entity.horizontal_direction.x = tip_x - entity.insertion_point.x; + entity.horizontal_direction.y = tip_y - entity.insertion_point.y; + }); + } +} diff --git a/src/entities/traits.rs b/src/entities/traits.rs index 829c1e3b..82e15ef9 100644 --- a/src/entities/traits.rs +++ b/src/entities/traits.rs @@ -55,6 +55,7 @@ impl EntityTypeOps for EntityType { EntityType::Face3D(f) => TruckConvertible::to_truck(f, document), EntityType::PolygonMesh(pm) => TruckConvertible::to_truck(pm, document), EntityType::PolyfaceMesh(pfm) => TruckConvertible::to_truck(pfm, document), + EntityType::Table(tbl) => TruckConvertible::to_truck(tbl, document), EntityType::Text(text) => TruckConvertible::to_truck(text, document), EntityType::MText(text) => TruckConvertible::to_truck(text, document), EntityType::Leader(leader) => TruckConvertible::to_truck(leader, document), @@ -84,6 +85,7 @@ impl EntityTypeOps for EntityType { EntityType::Face3D(f) => Grippable::grips(f), EntityType::PolygonMesh(pm) => Grippable::grips(pm), EntityType::PolyfaceMesh(pfm) => Grippable::grips(pfm), + EntityType::Table(tbl) => Grippable::grips(tbl), EntityType::Point(pt) => Grippable::grips(pt), EntityType::Spline(spline) => Grippable::grips(spline), EntityType::Text(text) => Grippable::grips(text), @@ -173,6 +175,10 @@ impl EntityTypeOps for EntityType { pfm, text_style_names, )), + EntityType::Table(tbl) => Some(PropertyEditable::geometry_properties( + tbl, + text_style_names, + )), EntityType::Hatch(hatch) => Some(PropertyEditable::geometry_properties( hatch, text_style_names, @@ -237,6 +243,7 @@ impl EntityTypeOps for EntityType { EntityType::Face3D(f) => PropertyEditable::apply_geom_prop(f, field, value), EntityType::PolygonMesh(pm) => PropertyEditable::apply_geom_prop(pm, field, value), EntityType::PolyfaceMesh(pfm) => PropertyEditable::apply_geom_prop(pfm, field, value), + EntityType::Table(tbl) => PropertyEditable::apply_geom_prop(tbl, field, value), EntityType::Hatch(hatch) => PropertyEditable::apply_geom_prop(hatch, field, value), EntityType::Point(pt) => PropertyEditable::apply_geom_prop(pt, field, value), EntityType::Spline(spline) => PropertyEditable::apply_geom_prop(spline, field, value), @@ -272,6 +279,7 @@ impl EntityTypeOps for EntityType { EntityType::Face3D(f) => Grippable::apply_grip(f, grip_id, apply), EntityType::PolygonMesh(pm) => Grippable::apply_grip(pm, grip_id, apply), EntityType::PolyfaceMesh(pfm) => Grippable::apply_grip(pfm, grip_id, apply), + EntityType::Table(tbl) => Grippable::apply_grip(tbl, grip_id, apply), EntityType::Point(pt) => Grippable::apply_grip(pt, grip_id, apply), EntityType::Spline(spline) => Grippable::apply_grip(spline, grip_id, apply), EntityType::Text(text) => Grippable::apply_grip(text, grip_id, apply), @@ -307,6 +315,7 @@ impl EntityTypeOps for EntityType { EntityType::Face3D(f) => Transformable::apply_transform(f, t), EntityType::PolygonMesh(pm) => Transformable::apply_transform(pm, t), EntityType::PolyfaceMesh(pfm) => Transformable::apply_transform(pfm, t), + EntityType::Table(tbl) => Transformable::apply_transform(tbl, t), EntityType::MText(text) => Transformable::apply_transform(text, t), EntityType::Point(pt) => Transformable::apply_transform(pt, t), EntityType::Spline(spline) => Transformable::apply_transform(spline, t),