forked from 1b-agent/opencadstudio-upstream
TruckEntity.snap_pts is now DVec3 and every entity producer builds snap candidates straight from the acadrust f64 coordinates instead of casting to f32 first (text, mtext, arc, circle, ellipse, point, shape, solid, mline, mtext, attribute, tolerance, table, underlay, ole2frame, mesh, multileader, dimension, block-cache). offset_snap_pts stays f64 throughout. Still offset-relative (inert) — but snap precision is no longer capped at ~0.5 m by an early f32 cast, which fixes a latent UTM-scale snap error and sets up the absolute-coordinate switch. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
636 lines
22 KiB
Rust
636 lines
22 KiB
Rust
use acadrust::entities::{mesh::Mesh, polygon_mesh::PolygonMesh, Face3D, PolyfaceMesh};
|
||
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::convert::acad_to_truck::{TruckEntity, TruckObject};
|
||
use crate::scene::model::object::{GripApply, GripDef, PropSection};
|
||
use crate::scene::model::wire_model::SnapHint;
|
||
|
||
// ── Face3D ────────────────────────────────────────────────────────────────────
|
||
|
||
fn v3(v: &acadrust::types::Vector3) -> [f64; 3] {
|
||
[v.x, v.y, v.z]
|
||
}
|
||
|
||
fn dvec3(v: &acadrust::types::Vector3) -> glam::DVec3 {
|
||
glam::DVec3::new(v.x, v.y, v.z)
|
||
}
|
||
|
||
fn v3f32(v: &acadrust::types::Vector3) -> [f32; 3] {
|
||
[v.x as f32, v.y as f32, v.z as f32]
|
||
}
|
||
|
||
impl TruckConvertible for Face3D {
|
||
fn to_truck(&self, _document: &acadrust::CadDocument) -> Option<TruckEntity> {
|
||
let p0 = v3(&self.first_corner);
|
||
let p1 = v3(&self.second_corner);
|
||
let p2 = v3(&self.third_corner);
|
||
let p3 = v3(&self.fourth_corner);
|
||
let p0f = v3f32(&self.first_corner);
|
||
let p1f = v3f32(&self.second_corner);
|
||
let p2f = v3f32(&self.third_corner);
|
||
let p3f = v3f32(&self.fourth_corner);
|
||
let inv = self.invisible_edges;
|
||
|
||
// Add edge as a line segment (separated by NaN from previous edges).
|
||
let mut pts: Vec<[f64; 3]> = Vec::new();
|
||
let mut add_edge = |a: [f64; 3], b: [f64; 3]| {
|
||
if !pts.is_empty() {
|
||
pts.push([f64::NAN; 3]);
|
||
}
|
||
pts.push(a);
|
||
pts.push(b);
|
||
};
|
||
|
||
if !inv.is_first_invisible() {
|
||
add_edge(p0, p1);
|
||
}
|
||
if !inv.is_second_invisible() {
|
||
add_edge(p1, p2);
|
||
}
|
||
if !inv.is_third_invisible() {
|
||
add_edge(p2, p3);
|
||
}
|
||
if !inv.is_fourth_invisible() {
|
||
add_edge(p3, p0);
|
||
}
|
||
|
||
if pts.is_empty() {
|
||
// All edges invisible — show a tiny cross at centroid.
|
||
let cx = (p0[0] + p1[0] + p2[0] + p3[0]) / 4.0;
|
||
let cy = (p0[1] + p1[1] + p2[1] + p3[1]) / 4.0;
|
||
let cz = (p0[2] + p1[2] + p2[2] + p3[2]) / 4.0;
|
||
let s = 0.1_f64;
|
||
pts = vec![[cx - s, cy, cz], [cx + s, cy, cz]];
|
||
}
|
||
|
||
Some(TruckEntity {
|
||
object: TruckObject::Lines(pts),
|
||
snap_pts: vec![
|
||
(Vec3::from(p0f).as_dvec3(), SnapHint::Node),
|
||
(Vec3::from(p1f).as_dvec3(), SnapHint::Node),
|
||
(Vec3::from(p2f).as_dvec3(), SnapHint::Node),
|
||
(Vec3::from(p3f).as_dvec3(), SnapHint::Node),
|
||
],
|
||
tangent_geoms: vec![],
|
||
key_vertices: vec![p0, p1, p2, p3],
|
||
fill_tris: vec![],
|
||
})
|
||
}
|
||
}
|
||
|
||
impl Grippable for Face3D {
|
||
fn grips(&self) -> Vec<GripDef> {
|
||
vec![
|
||
square_grip(0, dvec3(&self.first_corner)),
|
||
square_grip(1, dvec3(&self.second_corner)),
|
||
square_grip(2, dvec3(&self.third_corner)),
|
||
square_grip(3, dvec3(&self.fourth_corner)),
|
||
]
|
||
}
|
||
|
||
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
||
let corner = match grip_id {
|
||
0 => &mut self.first_corner,
|
||
1 => &mut self.second_corner,
|
||
2 => &mut self.third_corner,
|
||
3 => &mut self.fourth_corner,
|
||
_ => return,
|
||
};
|
||
match apply {
|
||
GripApply::Translate(d) => {
|
||
corner.x += d.x as f64;
|
||
corner.y += d.y as f64;
|
||
corner.z += d.z as f64;
|
||
}
|
||
GripApply::Absolute(p) => {
|
||
corner.x = p.x as f64;
|
||
corner.y = p.y as f64;
|
||
corner.z = p.z as f64;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
impl PropertyEditable for Face3D {
|
||
fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection {
|
||
use crate::entities::common::edit_prop as edit;
|
||
PropSection {
|
||
title: "Geometry".into(),
|
||
props: vec![
|
||
edit("P1 X", "f3_p1x", self.first_corner.x),
|
||
edit("P1 Y", "f3_p1y", self.first_corner.y),
|
||
edit("P1 Z", "f3_p1z", self.first_corner.z),
|
||
edit("P2 X", "f3_p2x", self.second_corner.x),
|
||
edit("P2 Y", "f3_p2y", self.second_corner.y),
|
||
edit("P2 Z", "f3_p2z", self.second_corner.z),
|
||
edit("P3 X", "f3_p3x", self.third_corner.x),
|
||
edit("P3 Y", "f3_p3y", self.third_corner.y),
|
||
edit("P3 Z", "f3_p3z", self.third_corner.z),
|
||
edit("P4 X", "f3_p4x", self.fourth_corner.x),
|
||
edit("P4 Y", "f3_p4y", self.fourth_corner.y),
|
||
edit("P4 Z", "f3_p4z", self.fourth_corner.z),
|
||
],
|
||
}
|
||
}
|
||
|
||
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
||
let Ok(v) = value.trim().parse::<f64>() else {
|
||
return;
|
||
};
|
||
match field {
|
||
"f3_p1x" => self.first_corner.x = v,
|
||
"f3_p1y" => self.first_corner.y = v,
|
||
"f3_p1z" => self.first_corner.z = v,
|
||
"f3_p2x" => self.second_corner.x = v,
|
||
"f3_p2y" => self.second_corner.y = v,
|
||
"f3_p2z" => self.second_corner.z = v,
|
||
"f3_p3x" => self.third_corner.x = v,
|
||
"f3_p3y" => self.third_corner.y = v,
|
||
"f3_p3z" => self.third_corner.z = v,
|
||
"f3_p4x" => self.fourth_corner.x = v,
|
||
"f3_p4y" => self.fourth_corner.y = v,
|
||
"f3_p4z" => self.fourth_corner.z = v,
|
||
_ => {}
|
||
}
|
||
}
|
||
}
|
||
|
||
impl Transformable for Face3D {
|
||
fn apply_transform(&mut self, t: &EntityTransform) {
|
||
crate::scene::view::transform::apply_standard_entity_transform(self, t, |entity, p1, p2| {
|
||
for corner in [
|
||
&mut entity.first_corner,
|
||
&mut entity.second_corner,
|
||
&mut entity.third_corner,
|
||
&mut entity.fourth_corner,
|
||
] {
|
||
crate::scene::view::transform::reflect_xy_point(&mut corner.x, &mut corner.y, p1, p2);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// ── PolygonMesh (N×M grid) ────────────────────────────────────────────────────
|
||
|
||
impl TruckConvertible for PolygonMesh {
|
||
fn to_truck(&self, _document: &acadrust::CadDocument) -> Option<TruckEntity> {
|
||
let m = self.m_vertex_count as usize;
|
||
let n = self.n_vertex_count as usize;
|
||
if m == 0 || n == 0 || self.vertices.len() < m * n {
|
||
return None;
|
||
}
|
||
|
||
let closed_m = self
|
||
.flags
|
||
.contains(acadrust::entities::PolygonMeshFlags::CLOSED_M);
|
||
let closed_n = self
|
||
.flags
|
||
.contains(acadrust::entities::PolygonMeshFlags::CLOSED_N);
|
||
|
||
let pt = |i: usize, j: usize| -> [f64; 3] {
|
||
let v = &self.vertices[i * n + j];
|
||
[v.location.x, v.location.y, v.location.z]
|
||
};
|
||
|
||
let mut pts: Vec<[f64; 3]> = Vec::new();
|
||
let mut fill_tris: Vec<[f64; 3]> = Vec::new();
|
||
|
||
// Rows (M direction).
|
||
for i in 0..m {
|
||
pts.push([f64::NAN; 3]);
|
||
for j in 0..n {
|
||
pts.push(pt(i, j));
|
||
}
|
||
if closed_n {
|
||
pts.push(pt(i, 0));
|
||
}
|
||
}
|
||
|
||
// Columns (N direction).
|
||
for j in 0..n {
|
||
pts.push([f64::NAN; 3]);
|
||
for i in 0..m {
|
||
pts.push(pt(i, j));
|
||
}
|
||
if closed_m {
|
||
pts.push(pt(0, j));
|
||
}
|
||
}
|
||
|
||
// Fill: triangulate each grid quad (two triangles per cell).
|
||
let mi = if closed_m { m } else { m - 1 };
|
||
let ni = if closed_n { n } else { n - 1 };
|
||
for i in 0..mi {
|
||
for j in 0..ni {
|
||
let p00 = pt(i, j);
|
||
let p10 = pt((i + 1) % m, j);
|
||
let p01 = pt(i, (j + 1) % n);
|
||
let p11 = pt((i + 1) % m, (j + 1) % n);
|
||
fill_tris.extend_from_slice(&[p00, p10, p11, p00, p11, p01]);
|
||
}
|
||
}
|
||
|
||
Some(TruckEntity {
|
||
object: TruckObject::Lines(pts),
|
||
snap_pts: vec![],
|
||
tangent_geoms: vec![],
|
||
key_vertices: vec![],
|
||
fill_tris,
|
||
})
|
||
}
|
||
}
|
||
|
||
impl Grippable for PolygonMesh {
|
||
fn grips(&self) -> Vec<GripDef> {
|
||
self.vertices
|
||
.iter()
|
||
.enumerate()
|
||
.map(|(i, v)| {
|
||
square_grip(
|
||
i,
|
||
glam::DVec3::new(v.location.x, v.location.y, v.location.z),
|
||
)
|
||
})
|
||
.collect()
|
||
}
|
||
|
||
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
||
if let Some(v) = self.vertices.get_mut(grip_id) {
|
||
match apply {
|
||
GripApply::Translate(d) => {
|
||
v.location.x += d.x as f64;
|
||
v.location.y += d.y as f64;
|
||
v.location.z += d.z as f64;
|
||
}
|
||
GripApply::Absolute(p) => {
|
||
v.location.x = p.x as f64;
|
||
v.location.y = p.y as f64;
|
||
v.location.z = p.z as f64;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
impl PropertyEditable for PolygonMesh {
|
||
fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection {
|
||
let smooth = match self.smooth_type {
|
||
acadrust::entities::polygon_mesh::SurfaceSmoothType::NoSmooth => "None",
|
||
acadrust::entities::polygon_mesh::SurfaceSmoothType::Quadratic => "Quadratic",
|
||
acadrust::entities::polygon_mesh::SurfaceSmoothType::Cubic => "Cubic",
|
||
acadrust::entities::polygon_mesh::SurfaceSmoothType::Bezier => "Bezier",
|
||
};
|
||
PropSection {
|
||
title: "Geometry".into(),
|
||
props: vec![
|
||
ro("M count", "pm_m", self.m_vertex_count.to_string()),
|
||
ro("N count", "pm_n", self.n_vertex_count.to_string()),
|
||
ro("Vertices", "pm_v", self.vertices.len().to_string()),
|
||
ro("Smooth Type", "pm_smooth", smooth),
|
||
ro(
|
||
"Smooth M Density",
|
||
"pm_smooth_m",
|
||
self.m_smooth_density.to_string(),
|
||
),
|
||
ro(
|
||
"Smooth N Density",
|
||
"pm_smooth_n",
|
||
self.n_smooth_density.to_string(),
|
||
),
|
||
ro(
|
||
"Elevation",
|
||
"pm_elevation",
|
||
format!("{:.4}", self.elevation),
|
||
),
|
||
ro(
|
||
"Normal",
|
||
"pm_normal",
|
||
format!(
|
||
"{:.3}, {:.3}, {:.3}",
|
||
self.normal.x, self.normal.y, self.normal.z
|
||
),
|
||
),
|
||
],
|
||
}
|
||
}
|
||
|
||
fn apply_geom_prop(&mut self, _field: &str, _value: &str) {}
|
||
}
|
||
|
||
impl Transformable for PolygonMesh {
|
||
fn apply_transform(&mut self, t: &EntityTransform) {
|
||
crate::scene::view::transform::apply_standard_entity_transform(self, t, |entity, p1, p2| {
|
||
for v in &mut entity.vertices {
|
||
crate::scene::view::transform::reflect_xy_point(
|
||
&mut v.location.x,
|
||
&mut v.location.y,
|
||
p1,
|
||
p2,
|
||
);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// ── PolyfaceMesh (arbitrary faces with 1-based vertex indices) ────────────────
|
||
|
||
impl TruckConvertible for PolyfaceMesh {
|
||
fn to_truck(&self, _document: &acadrust::CadDocument) -> Option<TruckEntity> {
|
||
if self.vertices.is_empty() || self.faces.is_empty() {
|
||
return None;
|
||
}
|
||
|
||
let get_v = |idx: i16| -> Option<[f64; 3]> {
|
||
let i = (idx.abs() as usize).checked_sub(1)?;
|
||
let v = self.vertices.get(i)?;
|
||
Some([v.location.x, v.location.y, v.location.z])
|
||
};
|
||
|
||
let mut pts: Vec<[f64; 3]> = Vec::new();
|
||
let mut fill_tris: Vec<[f64; 3]> = Vec::new();
|
||
|
||
for face in &self.faces {
|
||
// Indices: 0 means unused. Negative = invisible edge (still render for wireframe).
|
||
let indices = [face.index1, face.index2, face.index3, face.index4];
|
||
let verts: Vec<[f64; 3]> = indices
|
||
.iter()
|
||
.filter(|&&i| i != 0)
|
||
.filter_map(|&i| get_v(i))
|
||
.collect();
|
||
|
||
if verts.len() < 2 {
|
||
continue;
|
||
}
|
||
pts.push([f64::NAN; 3]);
|
||
for &p in &verts {
|
||
pts.push(p);
|
||
}
|
||
// Close the face polygon.
|
||
pts.push(verts[0]);
|
||
|
||
// Fan-triangulate the face for solid fill.
|
||
if verts.len() >= 3 {
|
||
for i in 1..(verts.len() - 1) {
|
||
fill_tris.push(verts[0]);
|
||
fill_tris.push(verts[i]);
|
||
fill_tris.push(verts[i + 1]);
|
||
}
|
||
}
|
||
}
|
||
|
||
Some(TruckEntity {
|
||
object: TruckObject::Lines(pts),
|
||
snap_pts: vec![],
|
||
tangent_geoms: vec![],
|
||
key_vertices: vec![],
|
||
fill_tris,
|
||
})
|
||
}
|
||
}
|
||
|
||
impl Grippable for PolyfaceMesh {
|
||
fn grips(&self) -> Vec<GripDef> {
|
||
self.vertices
|
||
.iter()
|
||
.enumerate()
|
||
.map(|(i, v)| {
|
||
square_grip(
|
||
i,
|
||
glam::DVec3::new(v.location.x, v.location.y, v.location.z),
|
||
)
|
||
})
|
||
.collect()
|
||
}
|
||
|
||
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
||
if let Some(v) = self.vertices.get_mut(grip_id) {
|
||
match apply {
|
||
GripApply::Translate(d) => {
|
||
v.location.x += d.x as f64;
|
||
v.location.y += d.y as f64;
|
||
v.location.z += d.z as f64;
|
||
}
|
||
GripApply::Absolute(p) => {
|
||
v.location.x = p.x as f64;
|
||
v.location.y = p.y as f64;
|
||
v.location.z = p.z as f64;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
impl PropertyEditable for PolyfaceMesh {
|
||
fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection {
|
||
let smooth = match self.smooth_surface {
|
||
acadrust::entities::PolyfaceSmoothType::None => "None",
|
||
acadrust::entities::PolyfaceSmoothType::Quadratic => "Quadratic",
|
||
acadrust::entities::PolyfaceSmoothType::Cubic => "Cubic",
|
||
acadrust::entities::PolyfaceSmoothType::Bezier => "Bezier",
|
||
};
|
||
PropSection {
|
||
title: "Geometry".into(),
|
||
props: vec![
|
||
ro("Vertices", "pfm_v", self.vertices.len().to_string()),
|
||
ro("Faces", "pfm_f", self.faces.len().to_string()),
|
||
ro("Smooth Surface", "pfm_smooth", smooth),
|
||
ro(
|
||
"Seqend",
|
||
"pfm_seqend_handle",
|
||
match self.seqend_handle {
|
||
Some(h) if !h.is_null() => format!("{:X}", h.value()),
|
||
_ => "(none)".to_string(),
|
||
},
|
||
),
|
||
ro(
|
||
"Elevation",
|
||
"pfm_elevation",
|
||
format!("{:.4}", self.elevation),
|
||
),
|
||
ro(
|
||
"Normal",
|
||
"pfm_normal",
|
||
format!(
|
||
"{:.3}, {:.3}, {:.3}",
|
||
self.normal.x, self.normal.y, self.normal.z
|
||
),
|
||
),
|
||
ro(
|
||
"Thickness",
|
||
"pfm_thickness",
|
||
format!("{:.4}", self.thickness),
|
||
),
|
||
],
|
||
}
|
||
}
|
||
|
||
fn apply_geom_prop(&mut self, _field: &str, _value: &str) {}
|
||
}
|
||
|
||
impl Transformable for PolyfaceMesh {
|
||
fn apply_transform(&mut self, t: &EntityTransform) {
|
||
crate::scene::view::transform::apply_standard_entity_transform(self, t, |entity, p1, p2| {
|
||
for v in &mut entity.vertices {
|
||
crate::scene::view::transform::reflect_xy_point(
|
||
&mut v.location.x,
|
||
&mut v.location.y,
|
||
p1,
|
||
p2,
|
||
);
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
// ── Mesh (SubD mesh) ──────────────────────────────────────────────────────────
|
||
//
|
||
// Modern subdivision mesh — distinct from PolygonMesh. The render path emits
|
||
// the file's per-edge wireframe and triangulates each face into fill_tris so
|
||
// solid views still draw a shaded surface. Subdivision-level smoothing is
|
||
// honoured only as metadata; we don't run a Catmull-Clark refinement pass
|
||
// here yet.
|
||
|
||
impl TruckConvertible for Mesh {
|
||
fn to_truck(&self, _document: &acadrust::CadDocument) -> Option<TruckEntity> {
|
||
if self.vertices.is_empty() {
|
||
return None;
|
||
}
|
||
let get = |i: usize| -> Option<[f64; 3]> { self.vertices.get(i).map(|v| [v.x, v.y, v.z]) };
|
||
|
||
let mut pts: Vec<[f64; 3]> = Vec::new();
|
||
if !self.edges.is_empty() {
|
||
for edge in &self.edges {
|
||
if let (Some(a), Some(b)) = (get(edge.start), get(edge.end)) {
|
||
pts.push([f64::NAN; 3]);
|
||
pts.push(a);
|
||
pts.push(b);
|
||
}
|
||
}
|
||
} else {
|
||
for face in &self.faces {
|
||
if face.vertices.len() < 2 {
|
||
continue;
|
||
}
|
||
pts.push([f64::NAN; 3]);
|
||
for &vi in &face.vertices {
|
||
if let Some(p) = get(vi) {
|
||
pts.push(p);
|
||
}
|
||
}
|
||
if let Some(first) = face.vertices.first().and_then(|&i| get(i)) {
|
||
pts.push(first);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Fan-triangulate each face into fill_tris so shaded views render
|
||
// the mesh as a solid surface.
|
||
let mut fill_tris: Vec<[f64; 3]> = Vec::new();
|
||
for face in &self.faces {
|
||
if face.vertices.len() < 3 {
|
||
continue;
|
||
}
|
||
let v0 = match get(face.vertices[0]) {
|
||
Some(p) => p,
|
||
None => continue,
|
||
};
|
||
for tri in 1..(face.vertices.len() - 1) {
|
||
let v1 = match get(face.vertices[tri]) {
|
||
Some(p) => p,
|
||
None => continue,
|
||
};
|
||
let v2 = match get(face.vertices[tri + 1]) {
|
||
Some(p) => p,
|
||
None => continue,
|
||
};
|
||
fill_tris.push(v0);
|
||
fill_tris.push(v1);
|
||
fill_tris.push(v2);
|
||
}
|
||
}
|
||
|
||
let snap_pts: Vec<(glam::DVec3, SnapHint)> = self
|
||
.vertices
|
||
.iter()
|
||
.map(|v| (glam::DVec3::new(v.x, v.y, v.z), SnapHint::Node))
|
||
.collect();
|
||
let key_vertices: Vec<[f64; 3]> = self.vertices.iter().map(|v| [v.x, v.y, v.z]).collect();
|
||
|
||
Some(TruckEntity {
|
||
object: TruckObject::Lines(pts),
|
||
snap_pts,
|
||
tangent_geoms: vec![],
|
||
key_vertices,
|
||
fill_tris,
|
||
})
|
||
}
|
||
}
|
||
|
||
impl Grippable for Mesh {
|
||
fn grips(&self) -> Vec<GripDef> {
|
||
self.vertices
|
||
.iter()
|
||
.enumerate()
|
||
.map(|(i, v)| square_grip(i, glam::DVec3::new(v.x, v.y, v.z)))
|
||
.collect()
|
||
}
|
||
|
||
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
||
if let Some(v) = self.vertices.get_mut(grip_id) {
|
||
match apply {
|
||
GripApply::Translate(d) => {
|
||
v.x += d.x as f64;
|
||
v.y += d.y as f64;
|
||
v.z += d.z as f64;
|
||
}
|
||
GripApply::Absolute(p) => {
|
||
v.x = p.x as f64;
|
||
v.y = p.y as f64;
|
||
v.z = p.z as f64;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
impl PropertyEditable for Mesh {
|
||
fn geometry_properties(&self, _text_style_names: &[String]) -> PropSection {
|
||
let creased_edges = self.edges.iter().filter(|e| e.has_crease()).count();
|
||
PropSection {
|
||
title: "Geometry".into(),
|
||
props: vec![
|
||
ro("Vertices", "msh_v", self.vertices.len().to_string()),
|
||
ro("Faces", "msh_f", self.faces.len().to_string()),
|
||
ro("Edges", "msh_e", self.edges.len().to_string()),
|
||
ro("Creased Edges", "msh_creased", creased_edges.to_string()),
|
||
ro("Version", "msh_version", self.version.to_string()),
|
||
ro(
|
||
"Subdivision Level",
|
||
"msh_subdiv",
|
||
self.subdivision_level.to_string(),
|
||
),
|
||
ro(
|
||
"Blend Crease",
|
||
"msh_blend_crease",
|
||
if self.blend_crease { "Yes" } else { "No" },
|
||
),
|
||
],
|
||
}
|
||
}
|
||
|
||
fn apply_geom_prop(&mut self, _field: &str, _value: &str) {}
|
||
}
|
||
|
||
impl Transformable for Mesh {
|
||
fn apply_transform(&mut self, t: &EntityTransform) {
|
||
crate::scene::view::transform::apply_standard_entity_transform(self, t, |entity, p1, p2| {
|
||
for v in &mut entity.vertices {
|
||
crate::scene::view::transform::reflect_xy_point(&mut v.x, &mut v.y, p1, p2);
|
||
}
|
||
});
|
||
}
|
||
}
|