fix(solid): use kernel geometry

Derive extrusion, bounds, edges, and fill from cadkernel while preserving precise view filtering and stable GPU uploads.
This commit is contained in:
Hakan Seven committed 2026-08-21 19:47:03 +03:00
1 parent 17dc0aff39
commit ee89458f8a
5 files changed
+225 -104

No files matched your search

+20 -7
View File
@@ -1657,12 +1657,25 @@ pub(crate) fn set_wire_aabb(w: &mut WireModel, entity_box: [f32; 4]) {
w.aabb = out;
}
fn entity_bounds(e: &acadrust::EntityType) -> ([f64; 3], [f64; 3]) {
if let acadrust::EntityType::Solid(solid) = e {
if let Some(bounds) = crate::entities::solid::wcs_bounds(solid) {
return (bounds.min, bounds.max);
}
}
let bounds = e.as_entity().bounding_box();
(
[bounds.min.x, bounds.min.y, bounds.min.z],
[bounds.max.x, bounds.max.y, bounds.max.z],
)
}
pub(crate) fn entity_aabb(e: &acadrust::EntityType) -> [f32; 4] {
let bbox = e.as_entity().bounding_box();
let min_x = (bbox.min.x) as f32;
let min_y = (bbox.min.y) as f32;
let max_x = (bbox.max.x) as f32;
let max_y = (bbox.max.y) as f32;
let (min, max) = entity_bounds(e);
let min_x = min[0] as f32;
let min_y = min[1] as f32;
let max_x = max[0] as f32;
let max_y = max[1] as f32;
// The all-zero box is bounding_box()'s Default — returned by entities with
// no usable box (unimplemented) — so treat it as UNBOUNDED (never
// pre-rejected). A genuinely zero-size box *away* from the origin (e.g. a
@@ -1680,8 +1693,8 @@ pub(crate) fn entity_aabb(e: &acadrust::EntityType) -> [f32; 4] {
/// indexing uses this so changing `world_offset` doesn't invalidate
/// the index.
pub(crate) fn entity_world_aabb_f64(e: &acadrust::EntityType) -> Option<[f64; 4]> {
let bbox = e.as_entity().bounding_box();
let (xmin, ymin, xmax, ymax) = (bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y);
let (min, max) = entity_bounds(e);
let (xmin, ymin, xmax, ymax) = (min[0], min[1], max[0], max[1]);
if xmin == xmax && ymin == ymax {
return None;
}
+36 -8
View File
@@ -22,20 +22,48 @@ use crate::scene::model::wire_model::WireModel;
use iced::wgpu;
use iced::wgpu::util::DeviceExt;
fn planar_solid_faces_view(wire: &WireModel, view_dir: glam::Vec3) -> bool {
pub(crate) fn planar_solid_faces_view(wire: &WireModel, view_dir: glam::Vec3) -> bool {
if !wire.fill_is_2d_solid || wire.fill_is_3d {
return true;
}
let view = view_dir.normalize_or_zero();
wire.fill_tris.chunks_exact(3).any(|triangle| {
let first = glam::Vec3::from_array(triangle[0]);
let second = glam::Vec3::from_array(triangle[1]);
let third = glam::Vec3::from_array(triangle[2]);
let view = view_dir.as_dvec3().normalize_or_zero();
let point = |index: usize| {
let high = wire.fill_tris[index];
let low = wire
.fill_tris_low
.get(index)
.copied()
.unwrap_or([0.0; 3]);
glam::DVec3::new(
high[0] as f64 + low[0] as f64,
high[1] as f64 + low[1] as f64,
high[2] as f64 + low[2] as f64,
)
};
(0..wire.fill_tris.len() / 3).any(|triangle| {
let first = point(triangle * 3);
let second = point(triangle * 3 + 1);
let third = point(triangle * 3 + 2);
let normal = (second - first).cross(third - first).normalize_or_zero();
normal.length_squared() > 0.0 && normal.dot(view).abs() >= 1.0 - 1.0e-5
})
}
pub(crate) fn planar_solid_visibility_key(
wires: &[WireModel],
view_dir: glam::Vec3,
) -> u64 {
let mut key = 0xcbf29ce484222325_u64;
for wire in wires
.iter()
.filter(|wire| wire.fill_is_2d_solid && !wire.fill_is_3d)
{
key ^= planar_solid_faces_view(wire, view_dir) as u64;
key = key.wrapping_mul(0x100000001b3);
}
key
}
// ── Vertex layout ──────────────────────────────────────────────────────────
#[repr(C)]
@@ -88,8 +116,8 @@ impl Face3DVertex {
// ── GPU handle ─────────────────────────────────────────────────────────────
/// One vertex buffer + draw count. Fill data is split into as many chunks
/// as the device's `max_buffer_size` requires: a mesh-heavy DWG (e.g. a
/// Navisworks import) can hold tens of millions of fill triangles, and at
/// as the device's `max_buffer_size` requires: a mesh-heavy drawing can hold
/// tens of millions of fill triangles, and at
/// 44 B/vertex a single batched buffer blows past the default 256 MB limit
/// once enough layers are thawed — wgpu then raises an uncaptured
/// validation error and aborts the process (#358). Same scheme as the
+3 -7
View File
@@ -312,12 +312,8 @@ pub struct Pipeline {
/// a pick bumps only `selection_generation`, refreshing the overlay without
/// touching the main wire buffers.
pub cached_selection: (u64, u64),
/// `(wire_content_id, face3d_fill_active, show_2d_solid_fills, view_dir)` the Face3D
/// edge/fill buffers were uploaded for. A stable content id avoids retaining
/// the resident wire Arc:
/// that Arc must stay uniquely owned by Scene so a small edit can splice it
/// in place instead of rebuilding the whole drawing.
pub cached_face3d_key: (u64, bool, bool, [u32; 3]),
/// Face upload key; keeps the resident wire Arc uniquely owned by Scene.
pub cached_face3d_key: (u64, bool, bool, u64),
/// Handle → indices into the resident wire set, built once per wire upload
/// (when `cached_wire_id` changes). Lets the selection/hover xray overlay
/// gather just the highlighted entity's wires (`O(highlighted)`) instead of
@@ -2323,7 +2319,7 @@ impl Pipeline {
cached_epoch: (u64::MAX, u64::MAX, u64::MAX),
cached_wire_id: u64::MAX,
cached_selection: (u64::MAX, u64::MAX),
cached_face3d_key: (u64::MAX, false, false, [u32::MAX; 3]),
cached_face3d_key: (u64::MAX, false, false, u64::MAX),
wire_handle_index: std::sync::Arc::new(rustc_hash::FxHashMap::default()),
render_sig: u64::MAX,
skip_geometry: false,
+8 -8
View File
@@ -378,7 +378,7 @@ impl shader::Primitive for Primitive {
inner.cached_wire_id = u64::MAX;
inner.cached_selection = (u64::MAX, u64::MAX);
inner.cached_mesh_content_id = u64::MAX;
inner.cached_face3d_key = (u64::MAX, false, false, [u32::MAX; 3]);
inner.cached_face3d_key = (u64::MAX, false, false, u64::MAX);
inner.cached_hatch_source = None;
inner.cached_preview_hatch_source = None;
inner.cached_wipeout_source = None;
@@ -485,11 +485,11 @@ impl shader::Primitive for Primitive {
// the Wireframe overlay style.
let face3d_fill_active = fill_mode && !vp.view_wireframe;
let solid_fill_active = fill_mode && vp.show_2d_solid_fills;
let view_dir_key = [
vp.view_dir.x.to_bits(),
vp.view_dir.y.to_bits(),
vp.view_dir.z.to_bits(),
];
let solid_visibility_key =
crate::scene::pipeline::face3d_gpu::planar_solid_visibility_key(
&vp_wires,
vp.view_dir,
);
let fill_changed = inner.cached_fill_mode != fill_mode;
let hatch_changed = inner
.cached_hatch_source
@@ -581,7 +581,7 @@ impl shader::Primitive for Primitive {
if face3d_changed
|| face3d_fill_active != inner.cached_face3d_key.1
|| solid_fill_active != inner.cached_face3d_key.2
|| view_dir_key != inner.cached_face3d_key.3
|| solid_visibility_key != inner.cached_face3d_key.3
{
inner.upload_face3d(
device,
@@ -599,7 +599,7 @@ impl shader::Primitive for Primitive {
vp.wire_content_id,
face3d_fill_active,
solid_fill_active,
view_dir_key,
solid_visibility_key,
);
// Wire buffers are world-space, so a camera move alone doesn't
// change them — only the view_proj uniform (uploaded every frame).