forked from 1b-agent/opencadstudio-upstream
feat(scene): integrate standard DWG data
This commit is contained in:
1 parent
731df4a725
commit
eb23715605
38 files changed
+8376
-618
No files matched your search
@@ -1224,6 +1224,102 @@ use crate::scene::convert::tessellate::{
|
||||
};
|
||||
use crate::scene::model::wire_model::{SnapHint, WireModel};
|
||||
|
||||
fn apply_dimension_breaks(
|
||||
document: &CadDocument,
|
||||
dimension: Handle,
|
||||
lines: &mut Vec<[f32; 3]>,
|
||||
) {
|
||||
let references: Vec<_> = document
|
||||
.objects
|
||||
.values()
|
||||
.filter_map(|object| {
|
||||
let acadrust::objects::ObjectType::DataObject(object) = object else {
|
||||
return None;
|
||||
};
|
||||
let acadrust::objects::DataObjectData::BreakData(data) = &object.data
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
(data.dimension_reference == dimension).then_some(&data.point_references)
|
||||
})
|
||||
.flatten()
|
||||
.filter(|reference| {
|
||||
let points = [reference.first_point, reference.second_point];
|
||||
points.iter().all(|point| {
|
||||
point.x.is_finite() && point.y.is_finite() && point.z.is_finite()
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
if references.is_empty() || lines.len() < 2 {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut output = Vec::with_capacity(lines.len() + references.len() * 3);
|
||||
for run in lines.split(|point| point[0].is_nan()) {
|
||||
for segment in run.windows(2) {
|
||||
let a = Vec3::from_array(segment[0]);
|
||||
let b = Vec3::from_array(segment[1]);
|
||||
let direction = b - a;
|
||||
let length_squared = direction.length_squared();
|
||||
if length_squared <= 1e-12 {
|
||||
continue;
|
||||
}
|
||||
let mut intervals = vec![(0.0f32, 1.0f32)];
|
||||
for reference in &references {
|
||||
let first = vec3_local(reference.first_point);
|
||||
let second = vec3_local(reference.second_point);
|
||||
let t1 = (first - a).dot(direction) / length_squared;
|
||||
let t2 = (second - a).dot(direction) / length_squared;
|
||||
let closest1 = a + direction * t1.clamp(0.0, 1.0);
|
||||
let closest2 = a + direction * t2.clamp(0.0, 1.0);
|
||||
let requested_gap = (second - first).length();
|
||||
let tolerance = (requested_gap * 0.25)
|
||||
.max(direction.length() * 1e-5)
|
||||
.max(1e-4);
|
||||
if (first - closest1).length() > tolerance
|
||||
|| (second - closest2).length() > tolerance
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let half_point_gap = if requested_gap <= 1e-6 {
|
||||
(tolerance / direction.length()).min(0.1)
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let cut_start = (t1.min(t2) - half_point_gap).clamp(0.0, 1.0);
|
||||
let cut_end = (t1.max(t2) + half_point_gap).clamp(0.0, 1.0);
|
||||
if cut_end <= cut_start {
|
||||
continue;
|
||||
}
|
||||
let mut remaining = Vec::new();
|
||||
for (start, end) in intervals {
|
||||
if cut_end <= start || cut_start >= end {
|
||||
remaining.push((start, end));
|
||||
continue;
|
||||
}
|
||||
if cut_start > start {
|
||||
remaining.push((start, cut_start));
|
||||
}
|
||||
if cut_end < end {
|
||||
remaining.push((cut_end, end));
|
||||
}
|
||||
}
|
||||
intervals = remaining;
|
||||
}
|
||||
for (start, end) in intervals {
|
||||
if end - start > 1e-6 {
|
||||
add_segment(
|
||||
&mut output,
|
||||
a + direction * start,
|
||||
a + direction * end,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
*lines = output;
|
||||
}
|
||||
|
||||
pub trait DimensionTess {
|
||||
fn tessellate(
|
||||
&self,
|
||||
@@ -1464,6 +1560,7 @@ fn tessellate_dimension_inner(
|
||||
// supersedes it. Read but not honoured.
|
||||
let _ = s.dimunit;
|
||||
}
|
||||
apply_dimension_breaks(document, handle, &mut geom.dim_lines);
|
||||
// Dimension entity fields that the render path doesn't yet use but are
|
||||
// preserved on save:
|
||||
// - base.insertion_point: legacy anchor reference; render uses
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -53,6 +53,14 @@ pub fn resolve(document: &CadDocument, host: Handle) -> Option<String> {
|
||||
acadrust::fields::resolve(document, host, &OcsFieldContext)
|
||||
}
|
||||
|
||||
pub fn resolve_handle(
|
||||
document: &CadDocument,
|
||||
field: Handle,
|
||||
host: Handle,
|
||||
) -> Option<String> {
|
||||
acadrust::fields::resolve_handle(document, field, host, &OcsFieldContext)
|
||||
}
|
||||
|
||||
/// Seconds since the Unix epoch. Native uses the system clock; wasm uses the JS
|
||||
/// `Date` clock. This platform-specific read stays in the app, not the library.
|
||||
fn epoch_secs() -> i64 {
|
||||
|
||||
+570
-176
@@ -2,10 +2,10 @@ use acadrust::entities::{mesh::Mesh, polygon_mesh::PolygonMesh, Face3D, Polyface
|
||||
use glam::Vec3;
|
||||
|
||||
use crate::command::EntityTransform;
|
||||
use crate::entities::common::{ro_prop as ro, square_grip};
|
||||
use crate::entities::common::{parse_f64, 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::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
||||
use crate::scene::model::wire_model::SnapHint;
|
||||
|
||||
/// Triangulate a planar (possibly concave) polygon into a flat triangle-soup
|
||||
@@ -319,63 +319,13 @@ impl TruckConvertible for PolygonMesh {
|
||||
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 {
|
||||
pick_tris: Vec::new(),
|
||||
object: TruckObject::Lines(pts),
|
||||
object: TruckObject::Lines(Vec::new()),
|
||||
snap_pts: vec![],
|
||||
tangent_geoms: vec![],
|
||||
key_vertices: vec![],
|
||||
fill_tris,
|
||||
fill_tris: vec![],
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -491,47 +441,13 @@ impl TruckConvertible for PolyfaceMesh {
|
||||
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]);
|
||||
|
||||
// Ear-clip the face for solid fill (handles concave faces).
|
||||
if verts.len() >= 3 {
|
||||
fill_tris.extend(triangulate_planar(&verts));
|
||||
}
|
||||
}
|
||||
|
||||
Some(TruckEntity {
|
||||
pick_tris: Vec::new(),
|
||||
object: TruckObject::Lines(pts),
|
||||
object: TruckObject::Lines(Vec::new()),
|
||||
snap_pts: vec![],
|
||||
tangent_geoms: vec![],
|
||||
key_vertices: vec![],
|
||||
fill_tris,
|
||||
fill_tris: vec![],
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -637,53 +553,527 @@ impl Transformable for PolyfaceMesh {
|
||||
// ── 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.
|
||||
// the refined per-edge wireframe and triangulates each face into fill_tris so
|
||||
// solid views draw the same Catmull-Clark surface described by the DWG.
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RefinedMesh {
|
||||
vertices: Vec<[f64; 3]>,
|
||||
faces: Vec<Vec<usize>>,
|
||||
creases: std::collections::HashMap<(usize, usize), f64>,
|
||||
}
|
||||
|
||||
fn edge_key(a: usize, b: usize) -> (usize, usize) {
|
||||
if a < b { (a, b) } else { (b, a) }
|
||||
}
|
||||
|
||||
fn add3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
|
||||
[a[0] + b[0], a[1] + b[1], a[2] + b[2]]
|
||||
}
|
||||
|
||||
fn mul3(a: [f64; 3], scale: f64) -> [f64; 3] {
|
||||
[a[0] * scale, a[1] * scale, a[2] * scale]
|
||||
}
|
||||
|
||||
fn mix3(a: [f64; 3], b: [f64; 3], amount: f64) -> [f64; 3] {
|
||||
add3(mul3(a, 1.0 - amount), mul3(b, amount))
|
||||
}
|
||||
|
||||
fn mean_points<'a>(points: impl Iterator<Item = &'a [f64; 3]>) -> [f64; 3] {
|
||||
let mut sum = [0.0; 3];
|
||||
let mut count = 0usize;
|
||||
for point in points {
|
||||
sum = add3(sum, *point);
|
||||
count += 1;
|
||||
}
|
||||
if count == 0 {
|
||||
sum
|
||||
} else {
|
||||
mul3(sum, 1.0 / count as f64)
|
||||
}
|
||||
}
|
||||
|
||||
fn base_refined_mesh(mesh: &Mesh) -> RefinedMesh {
|
||||
let vertices: Vec<[f64; 3]> = mesh.vertices.iter().map(|v| [v.x, v.y, v.z]).collect();
|
||||
let faces = mesh
|
||||
.faces
|
||||
.iter()
|
||||
.filter_map(|face| {
|
||||
let valid: Vec<usize> = face
|
||||
.vertices
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|&index| index < vertices.len())
|
||||
.collect();
|
||||
(valid.len() >= 3).then_some(valid)
|
||||
})
|
||||
.collect();
|
||||
let creases = mesh
|
||||
.edges
|
||||
.iter()
|
||||
.filter_map(|edge| {
|
||||
let sharpness = edge.crease?;
|
||||
(edge.start < vertices.len() && edge.end < vertices.len() && sharpness > 0.0)
|
||||
.then_some((edge_key(edge.start, edge.end), sharpness))
|
||||
})
|
||||
.collect();
|
||||
RefinedMesh {
|
||||
vertices,
|
||||
faces,
|
||||
creases,
|
||||
}
|
||||
}
|
||||
|
||||
fn subdivide_catmull_clark(mesh: &RefinedMesh, blend_crease: bool) -> RefinedMesh {
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
if mesh.faces.is_empty() {
|
||||
return mesh.clone();
|
||||
}
|
||||
|
||||
let face_points: Vec<[f64; 3]> = mesh
|
||||
.faces
|
||||
.iter()
|
||||
.map(|face| mean_points(face.iter().filter_map(|&index| mesh.vertices.get(index))))
|
||||
.collect();
|
||||
|
||||
let mut edge_faces: HashMap<(usize, usize), Vec<usize>> = HashMap::new();
|
||||
let mut vertex_faces: Vec<Vec<usize>> = vec![Vec::new(); mesh.vertices.len()];
|
||||
let mut vertex_edges: Vec<HashSet<(usize, usize)>> = vec![HashSet::new(); mesh.vertices.len()];
|
||||
for (face_index, face) in mesh.faces.iter().enumerate() {
|
||||
for &vertex in face {
|
||||
if let Some(faces) = vertex_faces.get_mut(vertex) {
|
||||
faces.push(face_index);
|
||||
}
|
||||
}
|
||||
for corner in 0..face.len() {
|
||||
let a = face[corner];
|
||||
let b = face[(corner + 1) % face.len()];
|
||||
if a == b {
|
||||
continue;
|
||||
}
|
||||
let key = edge_key(a, b);
|
||||
edge_faces.entry(key).or_default().push(face_index);
|
||||
if let Some(edges) = vertex_edges.get_mut(a) {
|
||||
edges.insert(key);
|
||||
}
|
||||
if let Some(edges) = vertex_edges.get_mut(b) {
|
||||
edges.insert(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut vertex_points = Vec::with_capacity(mesh.vertices.len());
|
||||
for (vertex_index, &point) in mesh.vertices.iter().enumerate() {
|
||||
let incident_edges = &vertex_edges[vertex_index];
|
||||
let incident_faces = &vertex_faces[vertex_index];
|
||||
if incident_edges.is_empty() || incident_faces.is_empty() {
|
||||
vertex_points.push(point);
|
||||
continue;
|
||||
}
|
||||
|
||||
let face_average =
|
||||
mean_points(incident_faces.iter().filter_map(|&index| face_points.get(index)));
|
||||
let edge_midpoints: Vec<[f64; 3]> = incident_edges
|
||||
.iter()
|
||||
.filter_map(|&(a, b)| {
|
||||
let other = if a == vertex_index { b } else { a };
|
||||
mesh.vertices
|
||||
.get(other)
|
||||
.map(|&p| mul3(add3(point, p), 0.5))
|
||||
})
|
||||
.collect();
|
||||
let edge_average = mean_points(edge_midpoints.iter());
|
||||
let n = incident_faces.len() as f64;
|
||||
let smooth = mul3(
|
||||
add3(add3(face_average, mul3(edge_average, 2.0)), mul3(point, n - 3.0)),
|
||||
1.0 / n,
|
||||
);
|
||||
|
||||
let mut sharp_neighbours: Vec<(f64, [f64; 3])> = incident_edges
|
||||
.iter()
|
||||
.filter_map(|&key| {
|
||||
let boundary = edge_faces.get(&key).is_some_and(|faces| faces.len() == 1);
|
||||
let sharpness = if boundary {
|
||||
f64::INFINITY
|
||||
} else {
|
||||
mesh.creases.get(&key).copied().unwrap_or(0.0)
|
||||
};
|
||||
if sharpness <= 0.0 {
|
||||
return None;
|
||||
}
|
||||
let other = if key.0 == vertex_index { key.1 } else { key.0 };
|
||||
mesh.vertices.get(other).copied().map(|p| (sharpness, p))
|
||||
})
|
||||
.collect();
|
||||
sharp_neighbours.sort_by(|a, b| b.0.total_cmp(&a.0));
|
||||
|
||||
let crease_point = match sharp_neighbours.as_slice() {
|
||||
[(_, first), (_, second), ..] => mul3(
|
||||
add3(add3(mul3(point, 6.0), *first), *second),
|
||||
1.0 / 8.0,
|
||||
),
|
||||
_ => smooth,
|
||||
};
|
||||
let corner_point = if sharp_neighbours.len() >= 3 {
|
||||
point
|
||||
} else {
|
||||
crease_point
|
||||
};
|
||||
let sharpness = sharp_neighbours
|
||||
.get(1)
|
||||
.map(|item| item.0)
|
||||
.unwrap_or(0.0);
|
||||
let amount = if blend_crease {
|
||||
sharpness.clamp(0.0, 1.0)
|
||||
} else if sharpness > 0.0 {
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
vertex_points.push(mix3(smooth, corner_point, amount));
|
||||
}
|
||||
|
||||
let mut edge_point_indices = HashMap::with_capacity(edge_faces.len());
|
||||
let mut vertices = vertex_points;
|
||||
for (&key, adjacent_faces) in &edge_faces {
|
||||
let Some((&a, &b)) = mesh.vertices.get(key.0).zip(mesh.vertices.get(key.1)) else {
|
||||
continue;
|
||||
};
|
||||
let midpoint = mul3(add3(a, b), 0.5);
|
||||
let smooth = if adjacent_faces.len() == 2 {
|
||||
let f0 = face_points[adjacent_faces[0]];
|
||||
let f1 = face_points[adjacent_faces[1]];
|
||||
mul3(add3(add3(a, b), add3(f0, f1)), 0.25)
|
||||
} else {
|
||||
midpoint
|
||||
};
|
||||
let boundary = adjacent_faces.len() != 2;
|
||||
let sharpness = mesh.creases.get(&key).copied().unwrap_or(0.0);
|
||||
let amount = if boundary {
|
||||
1.0
|
||||
} else if blend_crease {
|
||||
sharpness.clamp(0.0, 1.0)
|
||||
} else if sharpness > 0.0 {
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
edge_point_indices.insert(key, vertices.len());
|
||||
vertices.push(mix3(smooth, midpoint, amount));
|
||||
}
|
||||
|
||||
let face_point_start = vertices.len();
|
||||
vertices.extend(face_points.iter().copied());
|
||||
let mut faces = Vec::new();
|
||||
let mut creases = HashMap::new();
|
||||
for (face_index, face) in mesh.faces.iter().enumerate() {
|
||||
for corner in 0..face.len() {
|
||||
let vertex = face[corner];
|
||||
let next_key = edge_key(vertex, face[(corner + 1) % face.len()]);
|
||||
let prev_key = edge_key(face[(corner + face.len() - 1) % face.len()], vertex);
|
||||
let (Some(&next_edge), Some(&prev_edge)) = (
|
||||
edge_point_indices.get(&next_key),
|
||||
edge_point_indices.get(&prev_key),
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
faces.push(vec![
|
||||
vertex,
|
||||
next_edge,
|
||||
face_point_start + face_index,
|
||||
prev_edge,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
for (&key, &sharpness) in &mesh.creases {
|
||||
let Some(&edge_point) = edge_point_indices.get(&key) else {
|
||||
continue;
|
||||
};
|
||||
let child_sharpness = (sharpness - 1.0).max(0.0);
|
||||
if child_sharpness > 0.0 {
|
||||
creases.insert(edge_key(key.0, edge_point), child_sharpness);
|
||||
creases.insert(edge_key(edge_point, key.1), child_sharpness);
|
||||
}
|
||||
}
|
||||
|
||||
RefinedMesh {
|
||||
vertices,
|
||||
faces,
|
||||
creases,
|
||||
}
|
||||
}
|
||||
|
||||
fn display_mesh(mesh: &Mesh) -> RefinedMesh {
|
||||
let mut refined = base_refined_mesh(mesh);
|
||||
for _ in 0..mesh.subdivision_level.clamp(0, 4) {
|
||||
refined = subdivide_catmull_clark(&refined, mesh.blend_crease);
|
||||
}
|
||||
refined
|
||||
}
|
||||
|
||||
fn face_triangle_indices(
|
||||
vertices: &[[f64; 3]],
|
||||
faces: &[Vec<usize>],
|
||||
) -> (Vec<u32>, Vec<usize>) {
|
||||
let mut indices = Vec::new();
|
||||
let mut triangle_faces = Vec::new();
|
||||
for (face_index, face) in faces.iter().enumerate() {
|
||||
let polygon: Vec<[f64; 3]> = face
|
||||
.iter()
|
||||
.filter_map(|&index| vertices.get(index).copied())
|
||||
.collect();
|
||||
if polygon.len() < 3 {
|
||||
continue;
|
||||
}
|
||||
for triangle in triangulate_planar(&polygon).chunks_exact(3) {
|
||||
let mut mapped = [0u32; 3];
|
||||
let mut valid = true;
|
||||
for corner in 0..3 {
|
||||
let Some(local) = polygon.iter().position(|point| *point == triangle[corner]) else {
|
||||
valid = false;
|
||||
break;
|
||||
};
|
||||
mapped[corner] = face[local] as u32;
|
||||
}
|
||||
if valid && mapped[0] != mapped[1] && mapped[1] != mapped[2] && mapped[2] != mapped[0] {
|
||||
indices.extend(mapped);
|
||||
triangle_faces.push(face_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
(indices, triangle_faces)
|
||||
}
|
||||
|
||||
fn vertex_normals(vertices: &[[f64; 3]], indices: &[u32]) -> Vec<[f32; 3]> {
|
||||
let mut normals = vec![[0.0f64; 3]; vertices.len()];
|
||||
for triangle in indices.chunks_exact(3) {
|
||||
let (Some(&a), Some(&b), Some(&c)) = (
|
||||
vertices.get(triangle[0] as usize),
|
||||
vertices.get(triangle[1] as usize),
|
||||
vertices.get(triangle[2] as usize),
|
||||
) else {
|
||||
continue;
|
||||
};
|
||||
let ab = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
|
||||
let ac = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
|
||||
let normal = [
|
||||
ab[1] * ac[2] - ab[2] * ac[1],
|
||||
ab[2] * ac[0] - ab[0] * ac[2],
|
||||
ab[0] * ac[1] - ab[1] * ac[0],
|
||||
];
|
||||
for &index in triangle {
|
||||
if let Some(sum) = normals.get_mut(index as usize) {
|
||||
*sum = add3(*sum, normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
normals
|
||||
.into_iter()
|
||||
.map(|normal| {
|
||||
let length =
|
||||
(normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]).sqrt();
|
||||
if length > 1e-15 {
|
||||
[
|
||||
(normal[0] / length) as f32,
|
||||
(normal[1] / length) as f32,
|
||||
(normal[2] / length) as f32,
|
||||
]
|
||||
} else {
|
||||
[0.0, 0.0, 1.0]
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn append_feature_edge(
|
||||
edge_verts: &mut Vec<[f32; 3]>,
|
||||
edge_verts_low: &mut Vec<[f32; 3]>,
|
||||
vertices: &[[f64; 3]],
|
||||
a: usize,
|
||||
b: usize,
|
||||
) {
|
||||
for index in [a, b] {
|
||||
let Some(&[x, y, z]) = vertices.get(index) else {
|
||||
continue;
|
||||
};
|
||||
let high = [x as f32, y as f32, z as f32];
|
||||
edge_verts.push(high);
|
||||
edge_verts_low.push([
|
||||
(x - high[0] as f64) as f32,
|
||||
(y - high[1] as f64) as f32,
|
||||
(z - high[2] as f64) as f32,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
fn make_mesh_lod_set(
|
||||
name: String,
|
||||
color: [f32; 4],
|
||||
vertices: Vec<[f64; 3]>,
|
||||
faces: Vec<Vec<usize>>,
|
||||
visible_edges: std::collections::HashSet<(usize, usize)>,
|
||||
face_colors: &[Option<[f32; 4]>],
|
||||
) -> Option<crate::scene::model::mesh_model::MeshLodSet> {
|
||||
let (indices, triangle_faces) = face_triangle_indices(&vertices, &faces);
|
||||
if indices.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let normals = vertex_normals(&vertices, &indices);
|
||||
let triangle_colors = triangle_faces
|
||||
.iter()
|
||||
.map(|&face| face_colors.get(face).copied().flatten())
|
||||
.collect();
|
||||
let model = crate::scene::convert::solid3d_tess::finalize_mesh(
|
||||
name,
|
||||
vertices.clone(),
|
||||
normals,
|
||||
indices,
|
||||
Vec::new(),
|
||||
triangle_colors,
|
||||
color,
|
||||
None,
|
||||
);
|
||||
let mut set = crate::scene::model::mesh_model::MeshLodSet::from_lods(vec![model]);
|
||||
for (a, b) in visible_edges {
|
||||
append_feature_edge(
|
||||
&mut set.edge_verts,
|
||||
&mut set.edge_verts_low,
|
||||
&vertices,
|
||||
a,
|
||||
b,
|
||||
);
|
||||
}
|
||||
Some(set)
|
||||
}
|
||||
|
||||
/// Convert every standard DWG mesh family to the material-aware shaded mesh
|
||||
/// pipeline. The retained wire entity carries snaps/grips only; fill, normals,
|
||||
/// depth, materials and face colours live in `MeshLodSet`.
|
||||
pub(crate) fn tessellate_shaded_mesh(
|
||||
entity: &acadrust::EntityType,
|
||||
color: [f32; 4],
|
||||
) -> Option<crate::scene::model::mesh_model::MeshLodSet> {
|
||||
use std::collections::HashSet;
|
||||
|
||||
match entity {
|
||||
acadrust::EntityType::Mesh(mesh) => {
|
||||
let display = display_mesh(mesh);
|
||||
let mut edges = HashSet::new();
|
||||
for face in &display.faces {
|
||||
for corner in 0..face.len() {
|
||||
edges.insert(edge_key(face[corner], face[(corner + 1) % face.len()]));
|
||||
}
|
||||
}
|
||||
make_mesh_lod_set(
|
||||
mesh.common.handle.value().to_string(),
|
||||
color,
|
||||
display.vertices,
|
||||
display.faces,
|
||||
edges,
|
||||
&[],
|
||||
)
|
||||
}
|
||||
acadrust::EntityType::PolygonMesh(mesh) => {
|
||||
let m = mesh.m_vertex_count.max(0) as usize;
|
||||
let n = mesh.n_vertex_count.max(0) as usize;
|
||||
if m == 0 || n == 0 || mesh.vertices.len() < m.saturating_mul(n) {
|
||||
return None;
|
||||
}
|
||||
let vertices: Vec<[f64; 3]> = mesh
|
||||
.vertices
|
||||
.iter()
|
||||
.take(m * n)
|
||||
.map(|vertex| {
|
||||
[
|
||||
vertex.location.x,
|
||||
vertex.location.y,
|
||||
vertex.location.z,
|
||||
]
|
||||
})
|
||||
.collect();
|
||||
let closed_m = mesh.is_closed_m();
|
||||
let closed_n = mesh.is_closed_n();
|
||||
let m_cells = if closed_m { m } else { m.saturating_sub(1) };
|
||||
let n_cells = if closed_n { n } else { n.saturating_sub(1) };
|
||||
let mut faces = Vec::with_capacity(m_cells.saturating_mul(n_cells));
|
||||
let mut edges = HashSet::new();
|
||||
for i in 0..m_cells {
|
||||
for j in 0..n_cells {
|
||||
let face = vec![
|
||||
i * n + j,
|
||||
((i + 1) % m) * n + j,
|
||||
((i + 1) % m) * n + (j + 1) % n,
|
||||
i * n + (j + 1) % n,
|
||||
];
|
||||
for corner in 0..4 {
|
||||
edges.insert(edge_key(face[corner], face[(corner + 1) % 4]));
|
||||
}
|
||||
faces.push(face);
|
||||
}
|
||||
}
|
||||
make_mesh_lod_set(
|
||||
mesh.common.handle.value().to_string(),
|
||||
color,
|
||||
vertices,
|
||||
faces,
|
||||
edges,
|
||||
&[],
|
||||
)
|
||||
}
|
||||
acadrust::EntityType::PolyfaceMesh(mesh) => {
|
||||
let vertices: Vec<[f64; 3]> = mesh
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|vertex| {
|
||||
[
|
||||
vertex.location.x,
|
||||
vertex.location.y,
|
||||
vertex.location.z,
|
||||
]
|
||||
})
|
||||
.collect();
|
||||
let mut faces = Vec::new();
|
||||
let mut face_colors = Vec::new();
|
||||
let mut edges = HashSet::new();
|
||||
for face in &mesh.faces {
|
||||
let raw = [face.index1, face.index2, face.index3, face.index4];
|
||||
let indices: Vec<usize> = raw
|
||||
.iter()
|
||||
.filter(|&&index| index != 0)
|
||||
.filter_map(|&index| (index.unsigned_abs() as usize).checked_sub(1))
|
||||
.filter(|&index| index < vertices.len())
|
||||
.collect();
|
||||
if indices.len() < 3 {
|
||||
continue;
|
||||
}
|
||||
for corner in 0..indices.len() {
|
||||
if raw[corner] > 0 {
|
||||
edges.insert(edge_key(indices[corner], indices[(corner + 1) % indices.len()]));
|
||||
}
|
||||
}
|
||||
faces.push(indices);
|
||||
face_colors.push(face.color.as_ref().map(crate::scene::convert::tess_util::aci_to_rgba));
|
||||
}
|
||||
make_mesh_lod_set(
|
||||
mesh.common.handle.value().to_string(),
|
||||
color,
|
||||
vertices,
|
||||
faces,
|
||||
edges,
|
||||
&face_colors,
|
||||
)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ear-clip each face into fill_tris so shaded views render the mesh as
|
||||
// a solid surface (fan triangulation spills outside concave faces).
|
||||
let mut fill_tris: Vec<[f64; 3]> = Vec::new();
|
||||
for face in &self.faces {
|
||||
let verts: Vec<[f64; 3]> = face.vertices.iter().filter_map(|&vi| get(vi)).collect();
|
||||
if verts.len() >= 3 {
|
||||
fill_tris.extend(triangulate_planar(&verts));
|
||||
}
|
||||
}
|
||||
|
||||
// Per-vertex snap tables cost ~56 B/vertex and are retained in every
|
||||
// copy of the wire set. On NWD-scale imports (#358: tens of millions
|
||||
@@ -705,11 +1095,11 @@ impl TruckConvertible for Mesh {
|
||||
|
||||
Some(TruckEntity {
|
||||
pick_tris: Vec::new(),
|
||||
object: TruckObject::Lines(pts),
|
||||
object: TruckObject::Lines(Vec::new()),
|
||||
snap_pts,
|
||||
tangent_geoms: vec![],
|
||||
key_vertices,
|
||||
fill_tris,
|
||||
fill_tris: vec![],
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -761,12 +1151,36 @@ impl PropertyEditable for Mesh {
|
||||
vec![PropSection {
|
||||
title: "Geometry".into(),
|
||||
props: vec![
|
||||
ro(
|
||||
"Level of Smoothness",
|
||||
"msh_subdiv",
|
||||
self.subdivision_level.to_string(),
|
||||
),
|
||||
Property {
|
||||
label: "Level of Smoothness".into(),
|
||||
field: "msh_subdiv_edit",
|
||||
value: PropValue::EditText(self.subdivision_level.to_string()),
|
||||
},
|
||||
Property {
|
||||
label: "Blend Creases".into(),
|
||||
field: "msh_blend_crease",
|
||||
value: PropValue::BoolToggle {
|
||||
field: "msh_blend_crease",
|
||||
value: self.blend_crease,
|
||||
},
|
||||
},
|
||||
ro("Number of Faces", "msh_f", self.faces.len().to_string()),
|
||||
ro("Number of Vertices", "msh_v", self.vertices.len().to_string()),
|
||||
ro("Number of Edges", "msh_e", self.edges.len().to_string()),
|
||||
ro(
|
||||
"Creased Edges",
|
||||
"msh_creased",
|
||||
self.edges
|
||||
.iter()
|
||||
.filter(|edge| edge.crease.is_some_and(|value| value > 0.0))
|
||||
.count()
|
||||
.to_string(),
|
||||
),
|
||||
ro(
|
||||
"Override Option",
|
||||
"msh_override",
|
||||
self.override_option.to_string(),
|
||||
),
|
||||
ro("Number of Grips", "msh_grips", self.vertices.len().to_string()),
|
||||
ro(
|
||||
"Watertight",
|
||||
@@ -777,7 +1191,24 @@ impl PropertyEditable for Mesh {
|
||||
}]
|
||||
}
|
||||
|
||||
fn apply_geom_prop(&mut self, _field: &str, _value: &str) {}
|
||||
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
||||
match field {
|
||||
"msh_subdiv_edit" => {
|
||||
if let Some(value) = parse_f64(value) {
|
||||
self.subdivision_level = (value.round() as i32).clamp(0, 4);
|
||||
}
|
||||
}
|
||||
"msh_blend_crease" => {
|
||||
self.blend_crease = match value {
|
||||
"toggle" => !self.blend_crease,
|
||||
"On" | "Yes" | "true" | "1" => true,
|
||||
"Off" | "No" | "false" | "0" => false,
|
||||
_ => self.blend_crease,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Transformable for Mesh {
|
||||
@@ -789,40 +1220,3 @@ impl Transformable for Mesh {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod triangulate_tests {
|
||||
use super::triangulate_planar;
|
||||
|
||||
fn poly_area2d(p: &[[f64; 2]]) -> f64 {
|
||||
let n = p.len();
|
||||
let mut a = 0.0;
|
||||
for i in 0..n {
|
||||
let b = p[(i + 1) % n];
|
||||
a += p[i][0] * b[1] - b[0] * p[i][1];
|
||||
}
|
||||
(a * 0.5).abs()
|
||||
}
|
||||
fn tri_area(a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> f64 {
|
||||
// area in XY plane (test polygons are in Z=0)
|
||||
(((b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0])) * 0.5).abs()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn concave_l_shape_no_spillover() {
|
||||
// L-shaped hexagon (concave at the inner corner).
|
||||
let poly: Vec<[f64; 3]> = vec![
|
||||
[0.0, 0.0, 0.0], [4.0, 0.0, 0.0], [4.0, 2.0, 0.0],
|
||||
[2.0, 2.0, 0.0], [2.0, 4.0, 0.0], [0.0, 4.0, 0.0],
|
||||
];
|
||||
let p2: Vec<[f64; 2]> = poly.iter().map(|p| [p[0], p[1]]).collect();
|
||||
let want = poly_area2d(&p2); // = 12.0
|
||||
let tris = triangulate_planar(&poly);
|
||||
assert_eq!(tris.len() % 3, 0);
|
||||
assert_eq!(tris.len() / 3, poly.len() - 2, "expected n-2 triangles");
|
||||
let got: f64 = tris.chunks(3).map(|t| tri_area(t[0], t[1], t[2])).sum();
|
||||
eprintln!("concave L: want_area={want} got_area={got} tris={}", tris.len() / 3);
|
||||
// Fan would overshoot (triangles outside the L). Ear clip = exact.
|
||||
assert!((got - want).abs() < 1e-6, "triangle area {got} != polygon area {want} (spillover)");
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ pub mod common;
|
||||
pub mod dim_override;
|
||||
pub mod dimension;
|
||||
pub mod ellipse;
|
||||
pub mod extended;
|
||||
pub mod field;
|
||||
pub mod hatch;
|
||||
pub mod helix;
|
||||
@@ -34,3 +35,4 @@ pub mod tolerance;
|
||||
pub mod traits;
|
||||
pub mod underlay;
|
||||
pub mod viewport;
|
||||
pub mod view_symbol;
|
||||
+683
-81
@@ -7,8 +7,6 @@
|
||||
// the MeshModel vertices to match.
|
||||
|
||||
use acadrust::entities::{Body, Region, Solid3D, Surface};
|
||||
use glam::Vec3;
|
||||
|
||||
use crate::command::EntityTransform;
|
||||
use crate::entities::common::{center_grip, edit_prop as edit, parse_f64, ro_prop as ro};
|
||||
use crate::entities::traits::{Grippable, PropertyEditable, Transformable};
|
||||
@@ -42,13 +40,166 @@ fn dvec3(v: &acadrust::types::Vector3) -> glam::DVec3 {
|
||||
glam::DVec3::new(v.x, v.y, v.z)
|
||||
}
|
||||
|
||||
fn translate_wires(wires: &mut Vec<acadrust::entities::Wire>, d: Vec3) {
|
||||
for wire in wires.iter_mut() {
|
||||
for pt in wire.points.iter_mut() {
|
||||
pt.x += d.x as f64;
|
||||
pt.y += d.y as f64;
|
||||
pt.z += d.z as f64;
|
||||
}
|
||||
fn translate_acis_entity<T: acadrust::Entity>(entity: &mut T, d: glam::DVec3) {
|
||||
acadrust::Entity::translate(
|
||||
entity,
|
||||
acadrust::types::Vector3::new(d.x, d.y, d.z),
|
||||
);
|
||||
}
|
||||
|
||||
fn yes_no(value: bool) -> &'static str {
|
||||
if value { "Yes" } else { "No" }
|
||||
}
|
||||
|
||||
fn handle_text(handle: Option<acadrust::Handle>) -> String {
|
||||
handle
|
||||
.filter(|handle| handle.is_valid())
|
||||
.map(|handle| format!("{:X}", handle.value()))
|
||||
.unwrap_or_else(|| "None".to_string())
|
||||
}
|
||||
|
||||
fn acis_sections(
|
||||
acis: &acadrust::entities::AcisData,
|
||||
wires: &[acadrust::entities::Wire],
|
||||
silhouettes: &[acadrust::entities::Silhouette],
|
||||
history: Option<acadrust::Handle>,
|
||||
) -> Vec<PropSection> {
|
||||
let mut wire_types = [0usize; 5];
|
||||
let mut transformed = 0usize;
|
||||
let mut points = 0usize;
|
||||
for wire in wires {
|
||||
let index = match wire.wire_type {
|
||||
acadrust::entities::WireType::Silhouette => 1,
|
||||
acadrust::entities::WireType::VisibleEdge => 2,
|
||||
acadrust::entities::WireType::HiddenEdge => 3,
|
||||
acadrust::entities::WireType::Isoline => 4,
|
||||
_ => 0,
|
||||
};
|
||||
wire_types[index] += 1;
|
||||
transformed += wire.has_transform as usize;
|
||||
points += wire.points.len();
|
||||
}
|
||||
let revision = if acis.revision.has_guid {
|
||||
format!(
|
||||
"{}-{}-{}-{:02X?}; end {}",
|
||||
acis.revision.major,
|
||||
acis.revision.minor1,
|
||||
acis.revision.minor2,
|
||||
acis.revision.bytes,
|
||||
acis.revision.end_marker
|
||||
)
|
||||
} else {
|
||||
"None".to_string()
|
||||
};
|
||||
let bindings = if acis.materials.is_empty() {
|
||||
"None".to_string()
|
||||
} else {
|
||||
acis.materials
|
||||
.iter()
|
||||
.map(|binding| {
|
||||
format!(
|
||||
"{}:{}→{}",
|
||||
binding.array_index,
|
||||
binding.absolute_reference,
|
||||
handle_text(binding.material_handle)
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
};
|
||||
let extra = acis.extra_acis_data.as_ref().map_or_else(
|
||||
|| "None".to_string(),
|
||||
|data| {
|
||||
format!(
|
||||
"{}; {} bytes",
|
||||
if data.is_binary { "SAB" } else { "SAT" },
|
||||
data.size()
|
||||
)
|
||||
},
|
||||
);
|
||||
vec![
|
||||
PropSection {
|
||||
title: "Modeler Geometry".into(),
|
||||
props: vec![
|
||||
ro(
|
||||
"Format",
|
||||
"acis_format",
|
||||
if acis.is_binary { "SAB" } else { "SAT" },
|
||||
),
|
||||
ro("Version", "acis_version", format!("{:?}", acis.version)),
|
||||
ro("Data Size", "acis_size", format!("{} bytes", acis.size())),
|
||||
ro("Revision", "acis_revision", revision),
|
||||
ro("Material Bindings", "acis_materials", bindings),
|
||||
ro("Extra Modeler Data", "acis_extra", extra),
|
||||
ro("History", "acis_history", handle_text(history)),
|
||||
],
|
||||
},
|
||||
PropSection {
|
||||
title: "Modeler Display Data".into(),
|
||||
props: vec![
|
||||
ro(
|
||||
"Wireframe Cache",
|
||||
"acis_wireframe",
|
||||
yes_no(acis.wireframe_data_present),
|
||||
),
|
||||
ro(
|
||||
"Reference Point",
|
||||
"acis_wireframe_point",
|
||||
yes_no(acis.wireframe_point_present),
|
||||
),
|
||||
ro(
|
||||
"Isoline List",
|
||||
"acis_wireframe_isolines",
|
||||
yes_no(acis.wireframe_isoline_present),
|
||||
),
|
||||
ro(
|
||||
"Isolines",
|
||||
"acis_isolines",
|
||||
acis.wireframe_isolines.to_string(),
|
||||
),
|
||||
ro(
|
||||
"Wires",
|
||||
"acis_wires",
|
||||
format!(
|
||||
"{} (unknown {}, silhouette {}, visible {}, hidden {}, isoline {}; {} points; {} transformed)",
|
||||
wires.len(),
|
||||
wire_types[0],
|
||||
wire_types[1],
|
||||
wire_types[2],
|
||||
wire_types[3],
|
||||
wire_types[4],
|
||||
points,
|
||||
transformed
|
||||
),
|
||||
),
|
||||
ro(
|
||||
"View Silhouettes",
|
||||
"acis_silhouettes",
|
||||
format!(
|
||||
"{} views; {} wires",
|
||||
silhouettes.len(),
|
||||
silhouettes.iter().map(|silhouette| silhouette.wires.len()).sum::<usize>()
|
||||
),
|
||||
),
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn position_section(prefix: &str, p: &acadrust::types::Vector3) -> PropSection {
|
||||
let fields = match prefix {
|
||||
"rgn" => ["rgn_px", "rgn_py", "rgn_pz"],
|
||||
"bdy" => ["bdy_px", "bdy_py", "bdy_pz"],
|
||||
"srf" => ["srf_px", "srf_py", "srf_pz"],
|
||||
_ => ["s3d_px", "s3d_py", "s3d_pz"],
|
||||
};
|
||||
PropSection {
|
||||
title: "Geometry".into(),
|
||||
props: vec![
|
||||
edit("Position X", fields[0], p.x),
|
||||
edit("Position Y", fields[1], p.y),
|
||||
edit("Position Z", fields[2], p.z),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,54 +248,34 @@ impl Grippable for Solid3D {
|
||||
return;
|
||||
}
|
||||
if let GripApply::Translate(d) = apply {
|
||||
self.point_of_reference.x += d.x as f64;
|
||||
self.point_of_reference.y += d.y as f64;
|
||||
self.point_of_reference.z += d.z as f64;
|
||||
translate_wires(&mut self.wires, d.as_vec3());
|
||||
translate_acis_entity(self, d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PropertyEditable for Solid3D {
|
||||
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
|
||||
let has_history = matches!(self.history_handle, Some(h) if !h.is_null());
|
||||
let history = if has_history {
|
||||
format!("{:X}", self.history_handle.unwrap().value())
|
||||
} else {
|
||||
"None".to_string()
|
||||
};
|
||||
vec![
|
||||
PropSection {
|
||||
title: "Solid History".into(),
|
||||
props: vec![
|
||||
ro("History", "s3d_history", history),
|
||||
ro(
|
||||
"Show History",
|
||||
"s3d_show_history",
|
||||
if has_history { "Yes" } else { "No" },
|
||||
),
|
||||
],
|
||||
},
|
||||
PropSection {
|
||||
title: "Geometry".into(),
|
||||
props: vec![
|
||||
edit("Position X", "s3d_px", self.point_of_reference.x),
|
||||
edit("Position Y", "s3d_py", self.point_of_reference.y),
|
||||
edit("Position Z", "s3d_pz", self.point_of_reference.z),
|
||||
],
|
||||
},
|
||||
]
|
||||
let mut sections =
|
||||
acis_sections(&self.acis_data, &self.wires, &self.silhouettes, self.history_handle);
|
||||
sections[0]
|
||||
.props
|
||||
.insert(0, ro("UID", "s3d_uid", self.uid.clone()));
|
||||
sections.push(position_section("s3d", &self.point_of_reference));
|
||||
sections
|
||||
}
|
||||
|
||||
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
||||
let Some(v) = parse_f64(value) else {
|
||||
return;
|
||||
};
|
||||
match field {
|
||||
"s3d_px" => self.point_of_reference.x = v,
|
||||
"s3d_py" => self.point_of_reference.y = v,
|
||||
"s3d_pz" => self.point_of_reference.z = v,
|
||||
_ => {}
|
||||
let delta = match field {
|
||||
"s3d_px" => acadrust::types::Vector3::new(v - self.point_of_reference.x, 0.0, 0.0),
|
||||
"s3d_py" => acadrust::types::Vector3::new(0.0, v - self.point_of_reference.y, 0.0),
|
||||
"s3d_pz" => acadrust::types::Vector3::new(0.0, 0.0, v - self.point_of_reference.z),
|
||||
_ => return,
|
||||
};
|
||||
if delta != acadrust::types::Vector3::ZERO {
|
||||
acadrust::Entity::translate(self, delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -161,10 +292,7 @@ impl Grippable for Region {
|
||||
return;
|
||||
}
|
||||
if let GripApply::Translate(d) = apply {
|
||||
self.point_of_reference.x += d.x as f64;
|
||||
self.point_of_reference.y += d.y as f64;
|
||||
self.point_of_reference.z += d.z as f64;
|
||||
translate_wires(&mut self.wires, d.as_vec3());
|
||||
translate_acis_entity(self, d);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -172,16 +300,36 @@ impl Grippable for Region {
|
||||
impl PropertyEditable for Region {
|
||||
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
|
||||
let (area, perimeter) = region_area_perimeter(&self.wires);
|
||||
vec![PropSection {
|
||||
title: "Geometry".into(),
|
||||
props: vec![
|
||||
ro("Area", "rgn_area", format!("{area:.4}")),
|
||||
ro("Perimeter", "rgn_perimeter", format!("{perimeter:.4}")),
|
||||
],
|
||||
}]
|
||||
let mut sections =
|
||||
acis_sections(&self.acis_data, &self.wires, &self.silhouettes, self.history_handle);
|
||||
sections[0]
|
||||
.props
|
||||
.insert(0, ro("UID", "rgn_uid", self.uid.clone()));
|
||||
let mut geometry = position_section("rgn", &self.point_of_reference);
|
||||
geometry
|
||||
.props
|
||||
.push(ro("Area", "rgn_area", format!("{area:.4}")));
|
||||
geometry
|
||||
.props
|
||||
.push(ro("Perimeter", "rgn_perimeter", format!("{perimeter:.4}")));
|
||||
sections.push(geometry);
|
||||
sections
|
||||
}
|
||||
|
||||
fn apply_geom_prop(&mut self, _field: &str, _value: &str) {}
|
||||
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
||||
let Some(v) = parse_f64(value) else {
|
||||
return;
|
||||
};
|
||||
let delta = match field {
|
||||
"rgn_px" => acadrust::types::Vector3::new(v - self.point_of_reference.x, 0.0, 0.0),
|
||||
"rgn_py" => acadrust::types::Vector3::new(0.0, v - self.point_of_reference.y, 0.0),
|
||||
"rgn_pz" => acadrust::types::Vector3::new(0.0, 0.0, v - self.point_of_reference.z),
|
||||
_ => return,
|
||||
};
|
||||
if delta != acadrust::types::Vector3::ZERO {
|
||||
acadrust::Entity::translate(self, delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Body ──────────────────────────────────────────────────────────────────────
|
||||
@@ -196,36 +344,458 @@ impl Grippable for Body {
|
||||
return;
|
||||
}
|
||||
if let GripApply::Translate(d) = apply {
|
||||
self.point_of_reference.x += d.x as f64;
|
||||
self.point_of_reference.y += d.y as f64;
|
||||
self.point_of_reference.z += d.z as f64;
|
||||
translate_wires(&mut self.wires, d.as_vec3());
|
||||
translate_acis_entity(self, d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PropertyEditable for Body {
|
||||
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
|
||||
let has_history = matches!(self.history_handle, Some(h) if !h.is_null());
|
||||
let history = if has_history {
|
||||
format!("{:X}", self.history_handle.unwrap().value())
|
||||
} else {
|
||||
"None".to_string()
|
||||
};
|
||||
vec![PropSection {
|
||||
title: "Solid History".into(),
|
||||
props: vec![
|
||||
ro("History", "bdy_history", history),
|
||||
ro(
|
||||
"Show History",
|
||||
"bdy_show_history",
|
||||
if has_history { "Yes" } else { "No" },
|
||||
),
|
||||
],
|
||||
}]
|
||||
let mut sections =
|
||||
acis_sections(&self.acis_data, &self.wires, &self.silhouettes, self.history_handle);
|
||||
sections[0]
|
||||
.props
|
||||
.insert(0, ro("UID", "bdy_uid", self.uid.clone()));
|
||||
sections.push(position_section("bdy", &self.point_of_reference));
|
||||
sections
|
||||
}
|
||||
|
||||
fn apply_geom_prop(&mut self, _field: &str, _value: &str) {}
|
||||
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
||||
let Some(v) = parse_f64(value) else {
|
||||
return;
|
||||
};
|
||||
let delta = match field {
|
||||
"bdy_px" => acadrust::types::Vector3::new(v - self.point_of_reference.x, 0.0, 0.0),
|
||||
"bdy_py" => acadrust::types::Vector3::new(0.0, v - self.point_of_reference.y, 0.0),
|
||||
"bdy_pz" => acadrust::types::Vector3::new(0.0, 0.0, v - self.point_of_reference.z),
|
||||
_ => return,
|
||||
};
|
||||
if delta != acadrust::types::Vector3::ZERO {
|
||||
acadrust::Entity::translate(self, delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn embedded_name(entity: Option<&acadrust::entities::EmbeddedEntity>) -> &'static str {
|
||||
match entity {
|
||||
Some(acadrust::entities::EmbeddedEntity::Point(_)) => "Point",
|
||||
Some(acadrust::entities::EmbeddedEntity::Line(_)) => "Line",
|
||||
Some(acadrust::entities::EmbeddedEntity::Arc(_)) => "Arc",
|
||||
Some(acadrust::entities::EmbeddedEntity::Circle(_)) => "Circle",
|
||||
Some(acadrust::entities::EmbeddedEntity::Ellipse(_)) => "Ellipse",
|
||||
Some(acadrust::entities::EmbeddedEntity::Spline(_)) => "Spline",
|
||||
Some(acadrust::entities::EmbeddedEntity::LwPolyline(_)) => "Polyline",
|
||||
Some(acadrust::entities::EmbeddedEntity::Ray(_)) => "Ray",
|
||||
Some(acadrust::entities::EmbeddedEntity::XLine(_)) => "XLine",
|
||||
Some(acadrust::entities::EmbeddedEntity::Unknown { .. }) => "Unknown",
|
||||
None => "None",
|
||||
}
|
||||
}
|
||||
|
||||
fn vector_text(vector: &acadrust::types::Vector3) -> String {
|
||||
format!("{:.6}, {:.6}, {:.6}", vector.x, vector.y, vector.z)
|
||||
}
|
||||
|
||||
fn matrix_text(matrix: &[f64; 16]) -> String {
|
||||
matrix
|
||||
.chunks_exact(4)
|
||||
.map(|row| {
|
||||
format!(
|
||||
"[{:.4}, {:.4}, {:.4}, {:.4}]",
|
||||
row[0], row[1], row[2], row[3]
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
}
|
||||
|
||||
fn sweep_options_text(options: &acadrust::entities::SurfaceSweepOptions) -> String {
|
||||
format!(
|
||||
"draft {:.6} ({:.6}→{:.6}); twist {:.6}; scale {:.6}; align {:.6}; solid {}; flags {}/{}; align-start {}; bank {}; base {}; sweep-xform {}; path-xform {}; ref {}",
|
||||
options.draft_angle,
|
||||
options.draft_start_distance,
|
||||
options.draft_end_distance,
|
||||
options.twist_angle,
|
||||
options.scale_factor,
|
||||
options.align_angle,
|
||||
yes_no(options.is_solid),
|
||||
options.sweep_alignment_flags,
|
||||
options.path_flags,
|
||||
yes_no(options.align_start),
|
||||
yes_no(options.bank),
|
||||
yes_no(options.base_point_set),
|
||||
yes_no(options.sweep_entity_transform_computed),
|
||||
yes_no(options.path_entity_transform_computed),
|
||||
vector_text(&options.reference_vector)
|
||||
)
|
||||
}
|
||||
|
||||
fn surface_construction_section(surface: &Surface) -> PropSection {
|
||||
use acadrust::entities::SurfaceData;
|
||||
let mut props = vec![
|
||||
ro("Kind", "srf_kind", format!("{:?}", surface.kind)),
|
||||
ro(
|
||||
"Modeler Format",
|
||||
"srf_modeler_version",
|
||||
surface.modeler_format_version.to_string(),
|
||||
),
|
||||
edit(
|
||||
"U Isolines",
|
||||
"srf_u_isolines",
|
||||
surface.u_isolines as f64,
|
||||
),
|
||||
edit(
|
||||
"V Isolines",
|
||||
"srf_v_isolines",
|
||||
surface.v_isolines as f64,
|
||||
),
|
||||
];
|
||||
match &surface.surface_data {
|
||||
SurfaceData::Generic => {}
|
||||
SurfaceData::Plane { class_version } => {
|
||||
props.push(ro(
|
||||
"Class Version",
|
||||
"srf_class_version",
|
||||
class_version.to_string(),
|
||||
));
|
||||
}
|
||||
SurfaceData::Extruded {
|
||||
sweep_entity,
|
||||
options,
|
||||
sweep_vector,
|
||||
sweep_transform,
|
||||
} => {
|
||||
props.extend([
|
||||
ro(
|
||||
"Sweep Entity",
|
||||
"srf_sweep_entity",
|
||||
embedded_name(sweep_entity.as_ref()),
|
||||
),
|
||||
ro("Sweep Vector", "srf_sweep_vector", vector_text(sweep_vector)),
|
||||
ro("Sweep Options", "srf_sweep_options", sweep_options_text(options)),
|
||||
ro(
|
||||
"Sweep Transform",
|
||||
"srf_sweep_transform",
|
||||
matrix_text(sweep_transform),
|
||||
),
|
||||
ro(
|
||||
"Sweep Entity Transform",
|
||||
"srf_sweep_entity_transform",
|
||||
matrix_text(&options.sweep_entity_transform),
|
||||
),
|
||||
ro(
|
||||
"Path Entity Transform",
|
||||
"srf_path_entity_transform",
|
||||
matrix_text(&options.path_entity_transform),
|
||||
),
|
||||
]);
|
||||
}
|
||||
SurfaceData::Lofted {
|
||||
loft_transform,
|
||||
cross_section_entities,
|
||||
guide_entities,
|
||||
path_entity,
|
||||
plane_normal_lofting_type,
|
||||
start_draft_angle,
|
||||
end_draft_angle,
|
||||
start_draft_magnitude,
|
||||
end_draft_magnitude,
|
||||
arc_length_parameterization,
|
||||
no_twist,
|
||||
align_direction,
|
||||
simple_surfaces,
|
||||
closed_surfaces,
|
||||
solid,
|
||||
ruled_surface,
|
||||
virtual_guide,
|
||||
cross_sections,
|
||||
guide_curves,
|
||||
path_curve,
|
||||
} => {
|
||||
props.extend([
|
||||
ro(
|
||||
"Embedded Curves",
|
||||
"srf_loft_embedded",
|
||||
format!(
|
||||
"{} cross sections; {} guides; path {}",
|
||||
cross_section_entities.len(),
|
||||
guide_entities.len(),
|
||||
embedded_name(path_entity.as_ref())
|
||||
),
|
||||
),
|
||||
ro(
|
||||
"Database Curves",
|
||||
"srf_loft_handles",
|
||||
format!(
|
||||
"{} cross sections; {} guides; path {}",
|
||||
cross_sections.len(),
|
||||
guide_curves.len(),
|
||||
handle_text(*path_curve)
|
||||
),
|
||||
),
|
||||
ro(
|
||||
"Draft",
|
||||
"srf_loft_draft",
|
||||
format!(
|
||||
"type {}; angle {:.6}→{:.6}; magnitude {:.6}→{:.6}",
|
||||
plane_normal_lofting_type,
|
||||
start_draft_angle,
|
||||
end_draft_angle,
|
||||
start_draft_magnitude,
|
||||
end_draft_magnitude
|
||||
),
|
||||
),
|
||||
ro(
|
||||
"Loft Flags",
|
||||
"srf_loft_flags",
|
||||
format!(
|
||||
"arc-length {}; no-twist {}; align {}; simple {}; closed {}; solid {}; ruled {}; virtual-guide {}",
|
||||
yes_no(*arc_length_parameterization),
|
||||
yes_no(*no_twist),
|
||||
yes_no(*align_direction),
|
||||
yes_no(*simple_surfaces),
|
||||
yes_no(*closed_surfaces),
|
||||
yes_no(*solid),
|
||||
yes_no(*ruled_surface),
|
||||
yes_no(*virtual_guide)
|
||||
),
|
||||
),
|
||||
ro(
|
||||
"Loft Transform",
|
||||
"srf_loft_transform",
|
||||
matrix_text(loft_transform),
|
||||
),
|
||||
]);
|
||||
}
|
||||
SurfaceData::Revolved {
|
||||
revolve_entity,
|
||||
class_version,
|
||||
entity_id,
|
||||
axis_point,
|
||||
axis_vector,
|
||||
revolve_angle,
|
||||
start_angle,
|
||||
entity_transform,
|
||||
draft_angle,
|
||||
draft_start_distance,
|
||||
draft_end_distance,
|
||||
twist_angle,
|
||||
solid,
|
||||
close_to_axis,
|
||||
} => {
|
||||
props.extend([
|
||||
ro(
|
||||
"Revolve Entity",
|
||||
"srf_revolve_entity",
|
||||
embedded_name(revolve_entity.as_ref()),
|
||||
),
|
||||
ro(
|
||||
"Class / Entity",
|
||||
"srf_revolve_ids",
|
||||
format!("{class_version} / {entity_id}"),
|
||||
),
|
||||
ro("Axis Point", "srf_axis_point", vector_text(axis_point)),
|
||||
ro("Axis Vector", "srf_axis_vector", vector_text(axis_vector)),
|
||||
ro(
|
||||
"Angles",
|
||||
"srf_revolve_angles",
|
||||
format!(
|
||||
"start {:.6}; revolve {:.6}; draft {:.6}; twist {:.6}",
|
||||
start_angle, revolve_angle, draft_angle, twist_angle
|
||||
),
|
||||
),
|
||||
ro(
|
||||
"Draft Distances",
|
||||
"srf_revolve_draft_distances",
|
||||
format!("{draft_start_distance:.6}→{draft_end_distance:.6}"),
|
||||
),
|
||||
ro(
|
||||
"Revolve Flags",
|
||||
"srf_revolve_flags",
|
||||
format!(
|
||||
"solid {}; close-to-axis {}",
|
||||
yes_no(*solid),
|
||||
yes_no(*close_to_axis)
|
||||
),
|
||||
),
|
||||
ro(
|
||||
"Entity Transform",
|
||||
"srf_revolve_transform",
|
||||
matrix_text(entity_transform),
|
||||
),
|
||||
]);
|
||||
}
|
||||
SurfaceData::Swept {
|
||||
class_version,
|
||||
sweep_entity,
|
||||
path_entity,
|
||||
sweep_transform,
|
||||
path_transform,
|
||||
options,
|
||||
} => {
|
||||
props.extend([
|
||||
ro(
|
||||
"Class Version",
|
||||
"srf_class_version",
|
||||
class_version.to_string(),
|
||||
),
|
||||
ro(
|
||||
"Sweep / Path",
|
||||
"srf_swept_entities",
|
||||
format!(
|
||||
"{} / {}",
|
||||
embedded_name(sweep_entity.as_ref()),
|
||||
embedded_name(path_entity.as_ref())
|
||||
),
|
||||
),
|
||||
ro("Sweep Options", "srf_sweep_options", sweep_options_text(options)),
|
||||
ro(
|
||||
"Sweep Transform",
|
||||
"srf_sweep_transform",
|
||||
matrix_text(sweep_transform),
|
||||
),
|
||||
ro(
|
||||
"Path Transform",
|
||||
"srf_path_transform",
|
||||
matrix_text(path_transform),
|
||||
),
|
||||
ro(
|
||||
"Sweep Entity Transform",
|
||||
"srf_sweep_entity_transform",
|
||||
matrix_text(&options.sweep_entity_transform),
|
||||
),
|
||||
ro(
|
||||
"Path Entity Transform",
|
||||
"srf_path_entity_transform",
|
||||
matrix_text(&options.path_entity_transform),
|
||||
),
|
||||
]);
|
||||
}
|
||||
SurfaceData::Nurb {
|
||||
short_170,
|
||||
cv_hull_display,
|
||||
u_vector1,
|
||||
v_vector1,
|
||||
u_vector2,
|
||||
v_vector2,
|
||||
} => {
|
||||
props.extend([
|
||||
ro(
|
||||
"NURB Version",
|
||||
"srf_nurb_version",
|
||||
short_170.to_string(),
|
||||
),
|
||||
ro(
|
||||
"CV Hull",
|
||||
"srf_nurb_cv_hull",
|
||||
yes_no(*cv_hull_display),
|
||||
),
|
||||
ro("U Vector 1", "srf_nurb_u1", vector_text(u_vector1)),
|
||||
ro("V Vector 1", "srf_nurb_v1", vector_text(v_vector1)),
|
||||
ro("U Vector 2", "srf_nurb_u2", vector_text(u_vector2)),
|
||||
ro("V Vector 2", "srf_nurb_v2", vector_text(v_vector2)),
|
||||
]);
|
||||
}
|
||||
}
|
||||
props.extend([
|
||||
ro(
|
||||
"Raw DWG Body",
|
||||
"srf_raw_dwg",
|
||||
surface.raw_dwg_data.as_ref().map_or_else(
|
||||
|| "None".to_string(),
|
||||
|bytes| format!("{} bytes; {} handle bits", bytes.len(), surface.dwg_handle_bits),
|
||||
),
|
||||
),
|
||||
ro(
|
||||
"Raw Source Version",
|
||||
"srf_raw_version",
|
||||
surface
|
||||
.dwg_source_version
|
||||
.map(|version| format!("{version:?}"))
|
||||
.unwrap_or_else(|| "None".to_string()),
|
||||
),
|
||||
]);
|
||||
PropSection {
|
||||
title: "Surface Construction".into(),
|
||||
props,
|
||||
}
|
||||
}
|
||||
|
||||
fn translate_surface(surface: &mut Surface, delta: acadrust::types::Vector3) {
|
||||
let before = surface.point_of_reference;
|
||||
acadrust::Entity::translate(surface, delta);
|
||||
if surface.point_of_reference == before {
|
||||
surface.point_of_reference = before + delta;
|
||||
}
|
||||
}
|
||||
|
||||
impl Grippable for Surface {
|
||||
fn grips(&self) -> Vec<GripDef> {
|
||||
vec![center_grip(0, dvec3(&self.point_of_reference))]
|
||||
}
|
||||
|
||||
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
||||
if grip_id != 0 {
|
||||
return;
|
||||
}
|
||||
if let GripApply::Translate(delta) = apply {
|
||||
translate_surface(
|
||||
self,
|
||||
acadrust::types::Vector3::new(delta.x, delta.y, delta.z),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PropertyEditable for Surface {
|
||||
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
|
||||
let mut sections =
|
||||
acis_sections(&self.acis_data, &self.wires, &self.silhouettes, self.history_handle);
|
||||
sections.push(surface_construction_section(self));
|
||||
sections.push(position_section("srf", &self.point_of_reference));
|
||||
sections
|
||||
}
|
||||
|
||||
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
||||
match field {
|
||||
"srf_u_isolines" => {
|
||||
if let Some(value) = parse_f64(value) {
|
||||
self.u_isolines = (value.round() as i16).max(0);
|
||||
}
|
||||
}
|
||||
"srf_v_isolines" => {
|
||||
if let Some(value) = parse_f64(value) {
|
||||
self.v_isolines = (value.round() as i16).max(0);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let Some(value) = parse_f64(value) else {
|
||||
return;
|
||||
};
|
||||
let delta = match field {
|
||||
"srf_px" => acadrust::types::Vector3::new(
|
||||
value - self.point_of_reference.x,
|
||||
0.0,
|
||||
0.0,
|
||||
),
|
||||
"srf_py" => acadrust::types::Vector3::new(
|
||||
0.0,
|
||||
value - self.point_of_reference.y,
|
||||
0.0,
|
||||
),
|
||||
"srf_pz" => acadrust::types::Vector3::new(
|
||||
0.0,
|
||||
0.0,
|
||||
value - self.point_of_reference.z,
|
||||
),
|
||||
_ => return,
|
||||
};
|
||||
if delta != acadrust::types::Vector3::ZERO {
|
||||
translate_surface(self, delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Accessors for the Solid3D / Region / Body trio ─────────────────────────
|
||||
@@ -246,6 +816,7 @@ pub fn point_of_reference(e: &EntityType) -> Option<&Vector3> {
|
||||
EntityType::Solid3D(s) => Some(&s.point_of_reference),
|
||||
EntityType::Region(r) => Some(&r.point_of_reference),
|
||||
EntityType::Body(b) => Some(&b.point_of_reference),
|
||||
EntityType::Surface(s) => Some(&s.point_of_reference),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -263,6 +834,32 @@ pub fn fallback_wires(e: &EntityType) -> Option<&[acadrust::entities::Wire]> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wire_point(
|
||||
wire: &acadrust::entities::Wire,
|
||||
point: &acadrust::types::Vector3,
|
||||
) -> acadrust::types::Vector3 {
|
||||
if !wire.has_transform {
|
||||
return *point;
|
||||
}
|
||||
let x = point.x * wire.scale.x;
|
||||
let y = point.y * wire.scale.y;
|
||||
let z = point.z * wire.scale.z;
|
||||
acadrust::types::Vector3::new(
|
||||
wire.translation.x
|
||||
+ wire.x_axis.x * x
|
||||
+ wire.y_axis.x * y
|
||||
+ wire.z_axis.x * z,
|
||||
wire.translation.y
|
||||
+ wire.x_axis.y * x
|
||||
+ wire.y_axis.y * y
|
||||
+ wire.z_axis.y * z,
|
||||
wire.translation.z
|
||||
+ wire.x_axis.z * x
|
||||
+ wire.y_axis.z * y
|
||||
+ wire.z_axis.z * z,
|
||||
)
|
||||
}
|
||||
|
||||
/// Whether every ACIS face uses a surface family the mesh pipeline can decode.
|
||||
/// Unsupported or unresolved faces must keep their display-cache wires visible;
|
||||
/// otherwise a parseable but incomplete shell looks like a valid solid.
|
||||
@@ -288,13 +885,15 @@ pub fn acis_has_complete_surface_support(e: &EntityType) -> bool {
|
||||
| "sphere-surface"
|
||||
| "torus-surface"
|
||||
| "spline-surface"
|
||||
| "meshsurf-surface"
|
||||
| "bs3-surface"
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/// Run the appropriate `solid3d_tess::tessellate_*` for the entity,
|
||||
/// returning `None` for non-volume entities or when the kernel fails.
|
||||
/// Build material-aware shaded geometry for every standard 3-D solid/surface
|
||||
/// and mesh family, returning `None` when decoded geometry is unusable.
|
||||
pub fn tessellate_volume(
|
||||
e: &EntityType,
|
||||
color: [f32; 4],
|
||||
@@ -306,6 +905,9 @@ pub fn tessellate_volume(
|
||||
EntityType::Region(r) => solid3d_tess::tessellate_region(r, color, facet_res, isolines),
|
||||
EntityType::Body(b) => solid3d_tess::tessellate_body(b, color, facet_res, isolines),
|
||||
EntityType::Surface(s) => solid3d_tess::tessellate_surface(s, color, facet_res, isolines),
|
||||
EntityType::Mesh(_) | EntityType::PolygonMesh(_) | EntityType::PolyfaceMesh(_) => {
|
||||
crate::entities::mesh::tessellate_shaded_mesh(e, color)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
+1112
-153
File diff suppressed because it is too large.
Load diff
+26
-18
@@ -276,7 +276,7 @@ impl EntityTypeOps for EntityType {
|
||||
Wipeout, AttributeDefinition, AttributeEntity, MLine,
|
||||
Tolerance, Solid, Face3D, PolygonMesh, PolyfaceMesh, Mesh,
|
||||
Table, Text, MText, Leader, MultiLeader, Underlay, Shape,
|
||||
Ole2Frame, Helix, Light,
|
||||
Ole2Frame, Helix, Light, Extended,
|
||||
],
|
||||
_ => None,
|
||||
)
|
||||
@@ -289,10 +289,11 @@ impl EntityTypeOps for EntityType {
|
||||
Line, Circle, Arc, Ellipse, LwPolyline, Polyline, Polyline2D,
|
||||
Polyline3D, Ray, XLine, RasterImage, Wipeout,
|
||||
AttributeDefinition, AttributeEntity, MLine, Tolerance,
|
||||
Solid, Solid3D, Region, Body, Face3D, PolygonMesh,
|
||||
Solid, Solid3D, Region, Body, Surface, Face3D, PolygonMesh,
|
||||
PolyfaceMesh, Mesh, Table, Point, Spline, Text, MText,
|
||||
Viewport, Insert, Leader, MultiLeader, Dimension, Hatch,
|
||||
Underlay, Shape, Ole2Frame, Helix, Light,
|
||||
Underlay, Shape, Ole2Frame, Helix, Light, SectionSymbol,
|
||||
ViewBorder, Extended,
|
||||
],
|
||||
_ => vec![],
|
||||
)
|
||||
@@ -305,10 +306,11 @@ impl EntityTypeOps for EntityType {
|
||||
Line, Circle, Arc, Ellipse, LwPolyline, Polyline, Polyline2D,
|
||||
Polyline3D, Ray, XLine, RasterImage, Wipeout,
|
||||
AttributeDefinition, AttributeEntity, MLine, Tolerance,
|
||||
Solid, Solid3D, Region, Body, Face3D, PolygonMesh,
|
||||
Solid, Solid3D, Region, Body, Surface, Face3D, PolygonMesh,
|
||||
PolyfaceMesh, Mesh, Table, Hatch, Point, Spline, Text, MText,
|
||||
Viewport, Insert, Dimension, Leader, MultiLeader, Underlay,
|
||||
Shape, Ole2Frame, Helix, Light,
|
||||
Shape, Ole2Frame, Helix, Light, SectionSymbol, ViewBorder,
|
||||
Extended,
|
||||
],
|
||||
_ => vec![],
|
||||
)
|
||||
@@ -321,10 +323,11 @@ impl EntityTypeOps for EntityType {
|
||||
Line, Circle, Arc, Ellipse, LwPolyline, Polyline, Polyline2D,
|
||||
Polyline3D, Ray, XLine, RasterImage, Wipeout,
|
||||
AttributeDefinition, AttributeEntity, MLine, Tolerance,
|
||||
Solid, Solid3D, Region, Body, Face3D, PolygonMesh,
|
||||
Solid, Solid3D, Region, Body, Surface, Face3D, PolygonMesh,
|
||||
PolyfaceMesh, Mesh, Table, Hatch, Point, Spline, Text, MText,
|
||||
Viewport, Insert, Dimension, Leader, MultiLeader, Underlay,
|
||||
Shape, Ole2Frame, Helix, Light,
|
||||
Shape, Ole2Frame, Helix, Light, SectionSymbol, ViewBorder,
|
||||
Extended,
|
||||
],
|
||||
_ => {},
|
||||
)
|
||||
@@ -337,10 +340,11 @@ impl EntityTypeOps for EntityType {
|
||||
Line, Circle, Arc, Ellipse, LwPolyline, Polyline, Polyline2D,
|
||||
Polyline3D, Ray, XLine, RasterImage, Wipeout,
|
||||
AttributeDefinition, AttributeEntity, MLine, Tolerance,
|
||||
Solid, Solid3D, Region, Body, Face3D, PolygonMesh,
|
||||
Solid, Solid3D, Region, Body, Surface, Face3D, PolygonMesh,
|
||||
PolyfaceMesh, Mesh, Table, Point, Spline, Text, MText,
|
||||
Viewport, Insert, Leader, MultiLeader, Dimension, Hatch,
|
||||
Underlay, Shape, Ole2Frame, Helix, Light,
|
||||
Underlay, Shape, Ole2Frame, Helix, Light, SectionSymbol,
|
||||
ViewBorder, Extended,
|
||||
],
|
||||
_ => {},
|
||||
)
|
||||
@@ -353,10 +357,11 @@ impl EntityTypeOps for EntityType {
|
||||
Line, Circle, Arc, Ellipse, LwPolyline, Polyline, Polyline2D,
|
||||
Polyline3D, Ray, XLine, RasterImage, Wipeout,
|
||||
AttributeDefinition, AttributeEntity, MLine, Tolerance,
|
||||
Solid, Solid3D, Region, Body, Face3D, PolygonMesh,
|
||||
Solid, Solid3D, Region, Body, Surface, Face3D, PolygonMesh,
|
||||
PolyfaceMesh, Mesh, Table, Point, Spline, Text, MText,
|
||||
Viewport, Insert, Leader, MultiLeader, Dimension, Hatch,
|
||||
Underlay, Shape, Ole2Frame,
|
||||
Underlay, Shape, Ole2Frame, SectionSymbol, ViewBorder,
|
||||
Extended,
|
||||
],
|
||||
_ => vec![],
|
||||
)
|
||||
@@ -369,10 +374,11 @@ impl EntityTypeOps for EntityType {
|
||||
Line, Circle, Arc, Ellipse, LwPolyline, Polyline, Polyline2D,
|
||||
Polyline3D, Ray, XLine, RasterImage, Wipeout,
|
||||
AttributeDefinition, AttributeEntity, MLine, Tolerance,
|
||||
Solid, Solid3D, Region, Body, Face3D, PolygonMesh,
|
||||
Solid, Solid3D, Region, Body, Surface, Face3D, PolygonMesh,
|
||||
PolyfaceMesh, Mesh, Table, Point, Spline, Text, MText,
|
||||
Viewport, Insert, Leader, MultiLeader, Dimension, Hatch,
|
||||
Underlay, Shape, Ole2Frame,
|
||||
Underlay, Shape, Ole2Frame, SectionSymbol, ViewBorder,
|
||||
Extended,
|
||||
],
|
||||
_ => {},
|
||||
)
|
||||
@@ -389,10 +395,11 @@ impl EntityTypeOps for EntityType {
|
||||
Line, Circle, Arc, Ellipse, LwPolyline, Polyline, Polyline2D,
|
||||
Polyline3D, Ray, XLine, RasterImage, Wipeout,
|
||||
AttributeDefinition, AttributeEntity, MLine, Tolerance,
|
||||
Solid, Solid3D, Region, Body, Face3D, PolygonMesh,
|
||||
Solid, Solid3D, Region, Body, Surface, Face3D, PolygonMesh,
|
||||
PolyfaceMesh, Mesh, Table, Point, Spline, Text, MText,
|
||||
Viewport, Insert, Leader, MultiLeader, Dimension, Hatch,
|
||||
Underlay, Shape, Ole2Frame,
|
||||
Underlay, Shape, Ole2Frame, SectionSymbol, ViewBorder,
|
||||
Extended,
|
||||
],
|
||||
_ => None,
|
||||
)
|
||||
@@ -410,10 +417,10 @@ impl EntityTypeOps for EntityType {
|
||||
Line, Circle, Arc, Ellipse, LwPolyline, Polyline, Polyline2D,
|
||||
Polyline3D, Ray, XLine, RasterImage, Wipeout,
|
||||
AttributeDefinition, AttributeEntity, MLine, Tolerance,
|
||||
Solid, Solid3D, Region, Body, Face3D, PolygonMesh,
|
||||
Solid, Solid3D, Region, Body, Surface, Face3D, PolygonMesh,
|
||||
PolyfaceMesh, Mesh, Table, Point, Spline, Text, MText,
|
||||
Viewport, Insert, Leader, MultiLeader, Dimension, Hatch,
|
||||
Underlay, Shape, Ole2Frame,
|
||||
Underlay, Shape, Ole2Frame, SectionSymbol, ViewBorder,
|
||||
],
|
||||
_ => {},
|
||||
)
|
||||
@@ -429,7 +436,8 @@ impl EntityTypeOps for EntityType {
|
||||
Tolerance, Solid, Face3D, PolygonMesh, PolyfaceMesh, Mesh,
|
||||
Table, MText, Point, Spline, Text, Viewport, Dimension,
|
||||
Leader, MultiLeader, Underlay, Shape, Ole2Frame,
|
||||
Solid3D, Region, Body, Surface, Helix, Light,
|
||||
Solid3D, Region, Body, Surface, Helix, Light, SectionSymbol,
|
||||
ViewBorder, Extended,
|
||||
],
|
||||
_ => {},
|
||||
)
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
use acadrust::entities::{SectionSymbol, ViewBorder};
|
||||
|
||||
use crate::command::EntityTransform;
|
||||
use crate::entities::common::{edit_angle_prop, edit_prop, parse_f64, ro_prop, square_grip};
|
||||
use crate::entities::traits::{Grippable, PropertyEditable, Transformable};
|
||||
use crate::scene::model::object::{GripApply, GripDef, PropSection};
|
||||
|
||||
fn handle_text(handle: acadrust::Handle) -> String {
|
||||
if handle.is_null() {
|
||||
"None".to_string()
|
||||
} else {
|
||||
format!("{:X}", handle.value())
|
||||
}
|
||||
}
|
||||
|
||||
fn vector_text(vector: &acadrust::types::Vector3) -> String {
|
||||
format!("{:.6}, {:.6}, {:.6}", vector.x, vector.y, vector.z)
|
||||
}
|
||||
|
||||
fn apply_entity_transform<T: acadrust::Entity>(entity: &mut T, transform: &EntityTransform) {
|
||||
crate::scene::view::transform::apply_standard_entity_transform(
|
||||
entity,
|
||||
transform,
|
||||
|entity, first, second| {
|
||||
entity.apply_transform(
|
||||
&crate::scene::view::transform::reflection_about_xy_line(first, second),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
impl Grippable for SectionSymbol {
|
||||
fn grips(&self) -> Vec<GripDef> {
|
||||
self.points
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, point)| {
|
||||
square_grip(
|
||||
index,
|
||||
glam::DVec3::new(point.point.x, point.point.y, point.point.z),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
||||
let Some(point) = self.points.get_mut(grip_id) else {
|
||||
return;
|
||||
};
|
||||
match apply {
|
||||
GripApply::Translate(delta) => {
|
||||
point.point.x += delta.x;
|
||||
point.point.y += delta.y;
|
||||
point.point.z += delta.z;
|
||||
}
|
||||
GripApply::Absolute(position) => {
|
||||
point.point =
|
||||
acadrust::types::Vector3::new(position.x, position.y, position.z);
|
||||
}
|
||||
}
|
||||
self.sync_display_fields();
|
||||
}
|
||||
}
|
||||
|
||||
impl PropertyEditable for SectionSymbol {
|
||||
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
|
||||
let point_data = self
|
||||
.points
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, point)| {
|
||||
format!(
|
||||
"{}: point [{}]; bulge {:.6}; label {:?}; offset [{}]; flag {}",
|
||||
index + 1,
|
||||
vector_text(&point.point),
|
||||
point.bulge,
|
||||
point.label,
|
||||
vector_text(&point.label_offset),
|
||||
point.raw_flag_280
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
vec![
|
||||
PropSection {
|
||||
title: "Section Symbol".into(),
|
||||
props: vec![
|
||||
edit_prop("Symbol Scale", "section_scale", self.symbol_scale),
|
||||
ro_prop(
|
||||
"View Symbol Version",
|
||||
"section_view_version",
|
||||
self.view_symbol_version.to_string(),
|
||||
),
|
||||
ro_prop("Version", "section_version", self.version.to_string()),
|
||||
ro_prop(
|
||||
"Style",
|
||||
"section_style",
|
||||
handle_text(self.style_handle),
|
||||
),
|
||||
ro_prop(
|
||||
"View Representation",
|
||||
"section_view_rep",
|
||||
handle_text(self.view_rep_handle),
|
||||
),
|
||||
ro_prop(
|
||||
"View Symbol Flags",
|
||||
"section_view_flags",
|
||||
self.raw_view_symbol_70.to_string(),
|
||||
),
|
||||
ro_prop(
|
||||
"Point Count",
|
||||
"section_point_count",
|
||||
self.points.len().to_string(),
|
||||
),
|
||||
ro_prop(
|
||||
"Stored Point Counts",
|
||||
"section_raw_counts",
|
||||
format!(
|
||||
"{}, {}, flags {}",
|
||||
self.raw_point_count_90,
|
||||
self.raw_point_record_count,
|
||||
self.raw_flags_90
|
||||
),
|
||||
),
|
||||
ro_prop("Identifier", "section_label", self.label.clone()),
|
||||
],
|
||||
},
|
||||
PropSection {
|
||||
title: "Section Points".into(),
|
||||
props: vec![ro_prop("Records", "section_points", point_data)],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
||||
if field == "section_scale" {
|
||||
if let Some(value) = parse_f64(value) {
|
||||
self.symbol_scale = value.max(0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Transformable for SectionSymbol {
|
||||
fn apply_transform(&mut self, transform: &EntityTransform) {
|
||||
apply_entity_transform(self, transform);
|
||||
self.sync_display_fields();
|
||||
}
|
||||
}
|
||||
|
||||
impl Grippable for ViewBorder {
|
||||
fn grips(&self) -> Vec<GripDef> {
|
||||
vec![
|
||||
square_grip(0, glam::DVec3::new(self.min[0], self.min[1], 0.0)),
|
||||
square_grip(1, glam::DVec3::new(self.max[0], self.max[1], 0.0)),
|
||||
square_grip(2, glam::DVec3::new(self.center[0], self.center[1], 0.0)),
|
||||
]
|
||||
}
|
||||
|
||||
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
||||
let delta = match apply {
|
||||
GripApply::Translate(delta) => delta,
|
||||
GripApply::Absolute(position) => {
|
||||
let current = match grip_id {
|
||||
0 => glam::DVec3::new(self.min[0], self.min[1], 0.0),
|
||||
1 => glam::DVec3::new(self.max[0], self.max[1], 0.0),
|
||||
2 => glam::DVec3::new(self.center[0], self.center[1], 0.0),
|
||||
_ => return,
|
||||
};
|
||||
position - current
|
||||
}
|
||||
};
|
||||
match grip_id {
|
||||
0 => {
|
||||
self.min[0] += delta.x;
|
||||
self.min[1] += delta.y;
|
||||
}
|
||||
1 => {
|
||||
self.max[0] += delta.x;
|
||||
self.max[1] += delta.y;
|
||||
}
|
||||
2 => {
|
||||
self.min[0] += delta.x;
|
||||
self.min[1] += delta.y;
|
||||
self.max[0] += delta.x;
|
||||
self.max[1] += delta.y;
|
||||
}
|
||||
_ => return,
|
||||
}
|
||||
self.center = [
|
||||
(self.min[0] + self.max[0]) * 0.5,
|
||||
(self.min[1] + self.max[1]) * 0.5,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
impl PropertyEditable for ViewBorder {
|
||||
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
|
||||
vec![PropSection {
|
||||
title: "Drawing View".into(),
|
||||
props: vec![
|
||||
edit_prop("Center X", "view_border_x", self.center[0]),
|
||||
edit_prop("Center Y", "view_border_y", self.center[1]),
|
||||
ro_prop(
|
||||
"Width",
|
||||
"view_border_width",
|
||||
format!("{:.6}", self.max[0] - self.min[0]),
|
||||
),
|
||||
ro_prop(
|
||||
"Height",
|
||||
"view_border_height",
|
||||
format!("{:.6}", self.max[1] - self.min[1]),
|
||||
),
|
||||
edit_prop("Scale", "view_border_scale", self.scale),
|
||||
edit_angle_prop(
|
||||
"Rotation",
|
||||
"view_border_rotation",
|
||||
self.rotation_angle.to_degrees(),
|
||||
),
|
||||
ro_prop("Version", "view_border_version", self.version.to_string()),
|
||||
ro_prop(
|
||||
"Active Viewport",
|
||||
"view_border_viewport",
|
||||
handle_text(self.active_viewport),
|
||||
),
|
||||
ro_prop(
|
||||
"Scale Object",
|
||||
"view_border_scale_handle",
|
||||
handle_text(self.scale_handle),
|
||||
),
|
||||
],
|
||||
}]
|
||||
}
|
||||
|
||||
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
||||
let Some(value) = parse_f64(value) else {
|
||||
return;
|
||||
};
|
||||
match field {
|
||||
"view_border_x" => {
|
||||
let delta = value - self.center[0];
|
||||
self.min[0] += delta;
|
||||
self.max[0] += delta;
|
||||
self.center[0] = value;
|
||||
}
|
||||
"view_border_y" => {
|
||||
let delta = value - self.center[1];
|
||||
self.min[1] += delta;
|
||||
self.max[1] += delta;
|
||||
self.center[1] = value;
|
||||
}
|
||||
"view_border_scale" => self.scale = value.max(0.0),
|
||||
"view_border_rotation" => self.rotation_angle = value.to_radians(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Transformable for ViewBorder {
|
||||
fn apply_transform(&mut self, transform: &EntityTransform) {
|
||||
apply_entity_transform(self, transform);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user