feat(frame): complete frame controls

Preserve frame geometry for selection while separating viewport and plot visibility across supported reference entities.

Refs #642
This commit is contained in:
Hakan Seven committed 2026-08-20 23:37:02 +03:00
commit 44efb8fd53
88 files changed
+1538 -702

No files matched your search

+125 -57
View File
@@ -1393,6 +1393,9 @@ impl Scene {
wipeout.v_vector =
context.transform.apply_rotation(source.v_vector);
}
let Some(mut fill_plane) = Self::wipeout_fill_plane(&wipeout) else {
return;
};
let (world_origin, mut boundary) =
Self::wipeout_boundary_2d(&wipeout);
for clip in &context.clips {
@@ -1409,6 +1412,16 @@ impl Scene {
if boundary.len() < 3 {
return;
}
if !context.clips.is_empty() {
let Some(local) = Self::wipeout_boundary_at_xy(
fill_plane.0,
world_origin,
&boundary,
) else {
return;
};
fill_plane.1 = local;
}
let selection_handle =
if tint_insert_selection && context.is_instanced() {
context.root_handle
@@ -1446,10 +1459,14 @@ impl Scene {
} else {
None
};
let fill_plane_boundary = Some(Arc::new(fill_plane.1));
let fill_plane = Some(fill_plane.0);
models.push(HatchModel {
render_instance,
boundary: Arc::new(boundary),
boundary_wcs: None,
fill_plane,
fill_plane_boundary,
boundary_exterior: None,
boundary_sources: None,
pattern: model::hatch_model::HatchPattern::Solid,
@@ -1474,66 +1491,113 @@ impl Scene {
pub(super) fn wipeout_boundary_2d(
wo: &acadrust::entities::Wipeout,
) -> ([f64; 2], Vec<[f32; 2]>) {
use acadrust::entities::WipeoutClipType;
let origin = [wo.insertion_point.x, wo.insertion_point.y];
let is_polygon = wo.clipping_enabled
&& wo.clip_boundary_vertices.len() >= 3
&& matches!(wo.clip_type, WipeoutClipType::Polygonal);
if is_polygon {
// DXF clip vertices live in image-pixel space, centred on the
// image (range −size/2 … +size/2). Image-bottom-left → insertion,
// image-y-axis points DOWN (per the DXF "v_vector points down the
// image" convention), so map:
// x_off = (clip.x + size.x/2) × u_vec
// y_off = (size.y/2 − clip.y) × v_vec ← y flipped
// Offsets are relative to `origin` (the insertion point).
let cx_of = |v: &acadrust::types::Vector2| v.x + wo.size.x * 0.5;
let cy_of = |v: &acadrust::types::Vector2| wo.size.y * 0.5 - v.y;
let mut poly: Vec<[f32; 2]> = wo
.clip_boundary_vertices
.iter()
.map(|v| {
let cx = cx_of(v);
let cy = cy_of(v);
let wx = (wo.u_vector.x * cx + wo.v_vector.x * cy) as f32;
let wy = (wo.u_vector.y * cx + wo.v_vector.y * cy) as f32;
[wx, wy]
})
.collect();
// Close the loop: the GPU `in_polygon` ray-cast walks
// sequential pairs and doesn't wrap, so without an explicit
// closing vertex the last edge (vN-1 → v0) is never tested and
// the fill bleeds far past the boundary.
if let Some(&first) = poly.first() {
if poly.last() != Some(&first) {
poly.push(first);
let plane = cadkernel::space::Plane::from_axes(
[wo.insertion_point.x, wo.insertion_point.y, wo.insertion_point.z],
[wo.u_vector.x, wo.u_vector.y, wo.u_vector.z],
[wo.v_vector.x, wo.v_vector.y, wo.v_vector.z],
);
let boundary = Self::wipeout_local_boundary(wo)
.into_iter()
.map(|point| {
if point[0].is_finite() && point[1].is_finite() {
let offset = plane.vector_at(point);
[offset[0] as f32, offset[1] as f32]
} else {
[f32::NAN; 2]
}
}
(origin, poly)
} else {
// Rectangular boundary from 4 corners, as offsets from `origin`.
let ux = (wo.u_vector.x * wo.size.x) as f32;
let uy = (wo.u_vector.y * wo.size.x) as f32;
let vx = (wo.v_vector.x * wo.size.y) as f32;
let vy = (wo.v_vector.y * wo.size.y) as f32;
// Close the loop (repeat corner 0): the GPU `in_polygon` ray-cast
// walks sequential vertex pairs and never wraps last→first, so an
// unclosed quad leaves the v3→v0 edge untested and the solid mask
// bleeds past the boundary — same reason the polygon branch closes.
(
origin,
vec![
[0.0, 0.0],
[ux, uy],
[ux + vx, uy + vy],
[vx, vy],
[0.0, 0.0],
],
)
})
.collect();
(origin, boundary)
}
fn wipeout_local_boundary(wo: &acadrust::entities::Wipeout) -> Vec<[f64; 2]> {
use acadrust::entities::{WipeoutClipMode, WipeoutClipType};
let mut outer = vec![
[0.0, 0.0],
[wo.size.x, 0.0],
[wo.size.x, wo.size.y],
[0.0, wo.size.y],
[0.0, 0.0],
];
if !wo.clipping_enabled
|| wo.clip_boundary_vertices.len() < 3
|| !matches!(wo.clip_type, WipeoutClipType::Polygonal)
{
return outer;
}
let mut clip: Vec<[f64; 2]> = wo
.clip_boundary_vertices
.iter()
.map(|point| {
[
point.x + wo.size.x * 0.5,
wo.size.y * 0.5 - point.y,
]
})
.collect();
if clip.last() != clip.first() {
clip.push(clip[0]);
}
if matches!(wo.clip_mode, WipeoutClipMode::Inside) {
outer.push([f64::NAN; 2]);
outer.extend(clip);
outer
} else {
clip
}
}
fn wipeout_fill_plane(
wipeout: &acadrust::entities::Wipeout,
) -> Option<(model::hatch_model::FillPlane, Vec<[f32; 2]>)> {
let origin = [
wipeout.insertion_point.x,
wipeout.insertion_point.y,
wipeout.insertion_point.z,
];
let x_axis = [wipeout.u_vector.x, wipeout.u_vector.y, wipeout.u_vector.z];
let y_axis = [wipeout.v_vector.x, wipeout.v_vector.y, wipeout.v_vector.z];
cadkernel::space::Plane::from_axes(origin, x_axis, y_axis).normal()?;
let boundary = Self::wipeout_local_boundary(wipeout)
.into_iter()
.map(|point| [point[0] as f32, point[1] as f32])
.collect();
Some((
model::hatch_model::FillPlane {
origin,
x_axis,
y_axis,
},
boundary,
))
}
fn wipeout_boundary_at_xy(
plane: model::hatch_model::FillPlane,
world_origin: [f64; 2],
boundary: &[[f32; 2]],
) -> Option<Vec<[f32; 2]>> {
let plane = cadkernel::space::Plane::from_axes(
plane.origin,
plane.x_axis,
plane.y_axis,
);
boundary
.iter()
.map(|point| {
if point[0].is_finite() && point[1].is_finite() {
plane
.coordinates_at_xy([
world_origin[0] + point[0] as f64,
world_origin[1] + point[1] as f64,
])
.map(|point| [point[0] as f32, point[1] as f32])
} else {
Some([f32::NAN; 2])
}
})
.collect()
}
pub(crate) fn hatch_model_from_dxf(
@@ -1817,6 +1881,8 @@ impl Scene {
render_instance: None,
boundary: std::sync::Arc::new(boundary_f32),
boundary_wcs: None,
fill_plane: None,
fill_plane_boundary: None,
boundary_exterior: Some(std::sync::Arc::new(boundary_exterior)),
boundary_sources: Some(std::sync::Arc::new(boundary_sources)),
pattern,
@@ -2133,6 +2199,8 @@ impl Scene {
render_instance: None,
boundary: std::sync::Arc::new(boundary),
boundary_wcs: None,
fill_plane: None,
fill_plane_boundary: None,
boundary_exterior: None,
boundary_sources: None,
pattern: model::hatch_model::HatchPattern::Solid,