Feat: Phase 5.4 — Table entity render (grid wireframe + cell text)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
28f441b3f2
commit
313e7226d2
3 changed files with 184 additions and 0 deletions
|
|
@ -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;
|
||||
|
|
|
|||
174
src/entities/table.rs
Normal file
174
src/entities/table.rs
Normal file
|
|
@ -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<TruckEntity> {
|
||||
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<f32> = {
|
||||
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<f32> = {
|
||||
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<GripDef> {
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
Loading…
Reference in a new issue