feat(model): preview primitive height

This commit is contained in:
Hakan Seven committed 2026-08-11 19:39:01 +03:00
1 parent d76dcd8426
commit ed21681d48
1 file changed
+124 -21
+124 -21
View File
@@ -100,6 +100,25 @@ impl PrimitiveCommand {
.max(1.0)
}
fn cursor_height(&self, point: DVec3) -> f64 {
(self.plane.to_local(point) - self.pts[0])
.length()
.max(1e-6)
}
fn place_preview(&self, mut preview: WireModel) -> WireModel {
for point in &mut preview.points {
if !point[0].is_nan() {
*point = self
.plane
.to_world(glam::Vec3::from_array(*point).as_dvec3())
.as_vec3()
.to_array();
}
}
preview
}
/// Build both the acadrust `Solid3D` (ACIS, for persistence) and the
/// kernel `Body` (rendering + booleans) from the footprint + `height`.
fn build(&self, height: f64) -> Option<(EntityType, Body)> {
@@ -238,10 +257,7 @@ impl CadCommand for PrimitiveCommand {
fn on_point(&mut self, pt: DVec3) -> CmdResult {
if self.height_step {
// A click in the ground plane has no Z; use its distance from the
// footprint centre as the height magnitude.
let h = (self.plane.to_local(pt) - self.pts[0]).length();
return self.commit(h.max(1e-6));
return self.commit(self.cursor_height(pt));
}
self.pts.push(self.plane.to_local(pt));
if self.pts.len() < self.footprint_pts() {
@@ -281,27 +297,34 @@ impl CadCommand for PrimitiveCommand {
}
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
if self.height_step || self.pts.is_empty() {
if self.pts.is_empty() {
return None;
}
if self.height_step {
return Some(self.place_preview(height_wire(
self.shape,
&self.pts,
self.cursor_height(pt),
)));
}
let mut foot = self.pts.clone();
foot.push(self.plane.to_local(pt));
let mut preview = footprint_wire(self.shape, &foot);
preview.points = preview
.points
.iter()
.map(|point| {
if point[0].is_nan() {
*point
} else {
self.plane
.to_world(glam::Vec3::from_array(*point).as_dvec3())
.as_vec3()
.to_array()
}
})
.collect();
Some(preview)
Some(self.place_preview(footprint_wire(self.shape, &foot)))
}
fn dyn_spec(&self) -> Option<crate::command::DynSpec> {
use crate::command::{DynAnchor, DynFieldSpec, DynGuide, DynRole, DynSpec};
self.height_step.then(|| DynSpec {
anchor: DynAnchor::Point(self.plane.to_world(self.pts[0])),
fields: vec![DynFieldSpec::new(DynRole::Height)],
guide: DynGuide::None,
ref_point: None,
})
}
fn dyn_live_value(&self, cursor: DVec3) -> Option<f64> {
self.height_step.then(|| self.cursor_height(cursor))
}
}
@@ -332,6 +355,86 @@ fn footprint_wire(shape: Shape, pts: &[DVec3]) -> WireModel {
wire("primitive_preview", points)
}
fn height_wire(shape: Shape, pts: &[DVec3], height: f64) -> WireModel {
let mut points = Vec::new();
match shape {
Shape::Box => {
let (a, b) = (pts[0], pts[1]);
let base = [
DVec3::new(a.x, a.y, a.z),
DVec3::new(b.x, a.y, a.z),
DVec3::new(b.x, b.y, a.z),
DVec3::new(a.x, b.y, a.z),
];
let top = base.map(|point| point + DVec3::Z * height);
push_loop(&mut points, &base);
push_loop(&mut points, &top);
for i in 0..4 {
push_segment(&mut points, base[i], top[i]);
}
}
Shape::Wedge => {
let (a, b) = (pts[0], pts[1]);
let (x0, x1) = (a.x.min(b.x), a.x.max(b.x));
let (y0, y1) = (a.y.min(b.y), a.y.max(b.y));
let low = [
DVec3::new(x0, y0, a.z),
DVec3::new(x1, y0, a.z),
DVec3::new(x1, y0, a.z + height),
];
let high = low.map(|point| DVec3::new(point.x, y1, point.z));
push_loop(&mut points, &low);
push_loop(&mut points, &high);
for i in 0..3 {
push_segment(&mut points, low[i], high[i]);
}
}
Shape::Cylinder | Shape::Cone => {
let center = pts[0];
let radius = (pts[1] - center).length();
push_circle(&mut points, center, radius);
if shape == Shape::Cylinder {
push_circle(&mut points, center + DVec3::Z * height, radius);
for i in 0..4 {
let angle = i as f64 * std::f64::consts::FRAC_PI_2;
let base = center + DVec3::new(angle.cos() * radius, angle.sin() * radius, 0.0);
push_segment(&mut points, base, base + DVec3::Z * height);
}
} else {
let apex = center + DVec3::Z * height;
for i in 0..4 {
let angle = i as f64 * std::f64::consts::FRAC_PI_2;
let base = center + DVec3::new(angle.cos() * radius, angle.sin() * radius, 0.0);
push_segment(&mut points, base, apex);
}
}
}
Shape::Sphere | Shape::Torus => {}
}
wire("primitive_height_preview", points)
}
fn push_break(points: &mut Vec<[f32; 3]>) {
if !points.is_empty() {
points.push([f32::NAN; 3]);
}
}
fn push_loop<const N: usize>(points: &mut Vec<[f32; 3]>, path: &[DVec3; N]) {
push_break(points);
points.extend(path.iter().chain(path.first()).map(|point| point.as_vec3().to_array()));
}
fn push_segment(points: &mut Vec<[f32; 3]>, a: DVec3, b: DVec3) {
push_break(points);
points.extend([a.as_vec3().to_array(), b.as_vec3().to_array()]);
}
fn push_circle(points: &mut Vec<[f32; 3]>, center: DVec3, radius: f64) {
push_break(points);
circle_points(points, center, radius);
}
fn circle_points(out: &mut Vec<[f32; 3]>, c: DVec3, r: f64) {
const SEG: usize = 48;
for i in 0..=SEG {