forked from 1b-agent/opencadstudio-upstream
Merge pull request #848 from ramox81/codex/complete-solid-workflow
This commit is contained in:
commit
a59537c337
12 files changed
+399
-202
No files matched your search
@@ -1704,12 +1704,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
|
||||
@@ -1727,8 +1740,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;
|
||||
}
|
||||
|
||||
@@ -1260,7 +1260,7 @@ pub fn tessellate(
|
||||
| EntityType::PolyfaceMesh(_)
|
||||
| EntityType::PolygonMesh(_)
|
||||
| EntityType::Mesh(_)
|
||||
);
|
||||
) || matches!(entity, EntityType::Solid(solid) if solid.thickness.abs() > 1.0e-10);
|
||||
// Thickness walls ride on the wire that carries their edges, not
|
||||
// on a wire of their own: they are pick geometry for that entity,
|
||||
// and `fill_tris` below deliberately splits off into a fill-only
|
||||
|
||||
+3
-3
@@ -2222,8 +2222,8 @@ impl Scene {
|
||||
}
|
||||
|
||||
/// Build a solid-fill HatchModel for a DXF Solid entity.
|
||||
/// Conventional DXF SOLID corners use Z-order; legacy entities may already
|
||||
/// be in perimeter order. Use the same non-crossing resolver as wire fill.
|
||||
/// SOLID corners use Z-order. Preserve it in the projected hatch as well so
|
||||
/// intentionally crossing geometry is not silently rewritten.
|
||||
pub(super) fn solid_hatch_model(solid: &DxfSolid, color: [f32; 4]) -> HatchModel {
|
||||
// Keep the corners in f64 until the AABB centre is known, then store
|
||||
// each as a small f32 offset from it — same precision-preserving anchor
|
||||
@@ -2231,7 +2231,7 @@ impl Scene {
|
||||
// to f32 costs ~0.06 units of resolution at UTM magnitudes (~1e6), so
|
||||
// the quad snapped to a grid and the fill drifted off its outline.
|
||||
let wcs = crate::entities::solid::wcs_corners(solid);
|
||||
let order = crate::entities::solid::perimeter_indices(&wcs);
|
||||
let order = [0, 1, 3, 2];
|
||||
let corners: [[f64; 2]; 4] = order.map(|index| [wcs[index][0], wcs[index][1]]);
|
||||
let mut min = [f64::INFINITY; 2];
|
||||
let mut max = [f64::NEG_INFINITY; 2];
|
||||
|
||||
@@ -22,6 +22,48 @@ use crate::scene::model::wire_model::WireModel;
|
||||
use iced::wgpu;
|
||||
use iced::wgpu::util::DeviceExt;
|
||||
|
||||
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.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)]
|
||||
@@ -74,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
|
||||
@@ -149,6 +191,7 @@ impl Face3DGpu {
|
||||
all_wires: &[WireModel],
|
||||
keep_3d_mesh_fills: bool,
|
||||
show_2d_solid_fills: bool,
|
||||
view_dir: glam::Vec3,
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
) -> Self {
|
||||
let depth_of =
|
||||
@@ -200,6 +243,9 @@ impl Face3DGpu {
|
||||
if !show_2d_solid_fills && wire.fill_is_2d_solid {
|
||||
continue;
|
||||
}
|
||||
if !planar_solid_faces_view(wire, view_dir) {
|
||||
continue;
|
||||
}
|
||||
// A real 3-D surface fill (PolyfaceMesh / PolygonMesh) carries a
|
||||
// double-single low residual paired with `fill_tris` — it lives at
|
||||
// true world coordinates and must keep its real depth. 2-D fills
|
||||
@@ -259,6 +305,7 @@ impl Face3DGpu {
|
||||
all_wires,
|
||||
keep_3d_mesh_fills,
|
||||
show_2d_solid_fills,
|
||||
view_dir,
|
||||
depth_map,
|
||||
);
|
||||
Self {
|
||||
@@ -275,6 +322,7 @@ fn upload_block_chunks(
|
||||
wires: &[WireModel],
|
||||
keep_3d_mesh_fills: bool,
|
||||
show_2d_solid_fills: bool,
|
||||
view_dir: glam::Vec3,
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
) -> (Vec<BlockFace3DChunk>, Vec<BlockFace3DChunk>) {
|
||||
let mut slots = rustc_hash::FxHashMap::default();
|
||||
@@ -285,6 +333,7 @@ fn upload_block_chunks(
|
||||
};
|
||||
if wire.fill_tris.is_empty()
|
||||
|| (!show_2d_solid_fills && wire.fill_is_2d_solid)
|
||||
|| !planar_solid_faces_view(wire, view_dir)
|
||||
|| (wire.fill_is_3d && !keep_3d_mesh_fills)
|
||||
{
|
||||
continue;
|
||||
|
||||
@@ -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)` 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),
|
||||
/// 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),
|
||||
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,
|
||||
@@ -3023,6 +3019,7 @@ impl Pipeline {
|
||||
all_wires: &[WireModel],
|
||||
wireframe_only: bool,
|
||||
show_2d_solid_fills: bool,
|
||||
view_dir: glam::Vec3,
|
||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||
) {
|
||||
let perf_started = crate::perf::enabled().then(iced::time::Instant::now);
|
||||
@@ -3056,6 +3053,7 @@ impl Pipeline {
|
||||
all_wires,
|
||||
keep_3d_mesh_fills,
|
||||
show_2d_solid_fills,
|
||||
view_dir,
|
||||
depth_map,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
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;
|
||||
@@ -484,6 +484,12 @@ impl shader::Primitive for Primitive {
|
||||
// the view toggle so 2D fills stay on even when the user picks
|
||||
// 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 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
|
||||
@@ -574,14 +580,16 @@ impl shader::Primitive for Primitive {
|
||||
&& !face_pass_unchanged);
|
||||
if face3d_changed
|
||||
|| face3d_fill_active != inner.cached_face3d_key.1
|
||||
|| vp.show_2d_solid_fills != inner.cached_face3d_key.2
|
||||
|| solid_fill_active != inner.cached_face3d_key.2
|
||||
|| solid_visibility_key != inner.cached_face3d_key.3
|
||||
{
|
||||
inner.upload_face3d(
|
||||
device,
|
||||
&vp.face3d_wires[..],
|
||||
&vp_wires[..],
|
||||
!face3d_fill_active,
|
||||
vp.show_2d_solid_fills,
|
||||
solid_fill_active,
|
||||
vp.view_dir,
|
||||
&draw_depths,
|
||||
);
|
||||
inner.cached_face3d_source = Some(Arc::clone(&vp.face3d_wires));
|
||||
@@ -590,7 +598,8 @@ impl shader::Primitive for Primitive {
|
||||
inner.cached_face3d_key = (
|
||||
vp.wire_content_id,
|
||||
face3d_fill_active,
|
||||
vp.show_2d_solid_fills,
|
||||
solid_fill_active,
|
||||
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).
|
||||
|
||||
Reference in new issue
Block a user