Merge pull request #848 from ramox81/codex/complete-solid-workflow
This commit is contained in:
commit
a59537c337
12 changed files with 398 additions and 201 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
|
@ -72,7 +72,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "acadrust"
|
name = "acadrust"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
source = "git+https://github.com/HakanSeven12/cadcodec.git?rev=0908da7#0908da7b6e4f702a6c78359a57f53e2b79cf39eb"
|
source = "git+https://github.com/HakanSeven12/cadcodec.git?rev=8a92803#8a9280381d75a52ac5a35663449bdd44827e1463"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.12",
|
"ahash 0.8.12",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|
@ -878,7 +878,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cadkernel"
|
name = "cadkernel"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=ebeb2ec#ebeb2ecc2d17d92c588b0fa8bbe3864b156bc71f"
|
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=b1901dd#b1901dde3351fc4bbcdffbdcf7328a5dec4d28d3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"acadrust",
|
"acadrust",
|
||||||
"cavalier_contours",
|
"cavalier_contours",
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ glam = { version = "0.33", features = ["bytemuck"] }
|
||||||
rfd = "0.17"
|
rfd = "0.17"
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "0908da7", features = ["serde"] }
|
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "8a92803", features = ["serde"] }
|
||||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "ebeb2ec", features = ["acis", "offset"] }
|
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "b1901dd", features = ["acis", "offset"] }
|
||||||
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
|
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
|
||||||
flate2 = "1"
|
flate2 = "1"
|
||||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
|
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ serde = { version = "1", features = ["derive"] }
|
||||||
# Pulled in only by the `host` feature, which adds the `acadrust`-typed
|
# Pulled in only by the `host` feature, which adds the `acadrust`-typed
|
||||||
# `HostApi` runtime surface. The default crate stays dependency-free so engine
|
# `HostApi` runtime surface. The default crate stays dependency-free so engine
|
||||||
# crates and external tooling can depend on the manifest/ribbon contract cheaply.
|
# crates and external tooling can depend on the manifest/ribbon contract cheaply.
|
||||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "0908da7", optional = true, features = ["serde"] }
|
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "8a92803", optional = true, features = ["serde"] }
|
||||||
|
|
||||||
# Runtime IPC and serialization (host feature only).
|
# Runtime IPC and serialization (host feature only).
|
||||||
interprocess = { version = "2", optional = true }
|
interprocess = { version = "2", optional = true }
|
||||||
|
|
@ -37,7 +37,7 @@ serde_json = "1"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
cargo-lock = "11"
|
cargo-lock = "11"
|
||||||
# acadrust is scanned at build time to generate the embedded type registry.
|
# acadrust is scanned at build time to generate the embedded type registry.
|
||||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "0908da7", features = ["serde"] }
|
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "8a92803", features = ["serde"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ publish = false
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "0908da7", features = ["serde"] }
|
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "8a92803", features = ["serde"] }
|
||||||
bincode = "1.3"
|
bincode = "1.3"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
console_error_panic_hook = "0.1"
|
console_error_panic_hook = "0.1"
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,35 @@
|
||||||
// SOLID entity — 2D filled quadrilateral (or triangle when p3 == p4).
|
// SOLID geometry comes from the kernel; entity coordinates stay in WCS here.
|
||||||
//
|
|
||||||
// Wireframe: the 4 perimeter edges as RenderObject::Lines.
|
|
||||||
// Filled: two triangles on `fill_tris`, preserving the entity's full WCS
|
|
||||||
// plane both at top level and through block expansion. The scene
|
|
||||||
// keeps a separate 2-D HatchModel only for plot projection; screen
|
|
||||||
// rendering filters that flattened copy out.
|
|
||||||
// Grips: 4 corner grip points.
|
|
||||||
|
|
||||||
use acadrust::entities::Solid;
|
use acadrust::entities::Solid;
|
||||||
use crate::t;
|
use crate::t;
|
||||||
|
|
||||||
use crate::command::EntityTransform;
|
use crate::command::EntityTransform;
|
||||||
use crate::entities::common::{edit_prop as edit, ro_prop as ro, square_grip};
|
use crate::entities::common::{edit_prop as edit, square_grip};
|
||||||
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, RenderConvertible};
|
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, RenderConvertible};
|
||||||
use crate::scene::convert::acad_to_render::{RenderEntity, RenderObject};
|
use crate::scene::convert::acad_to_render::{RenderEntity, RenderObject};
|
||||||
use crate::scene::model::object::{GripApply, GripDef, PropSection};
|
use crate::scene::model::object::{GripApply, GripDef, PropSection};
|
||||||
use crate::scene::model::wire_model::SnapHint;
|
use crate::scene::model::wire_model::SnapHint;
|
||||||
|
|
||||||
fn dvec3(v: &acadrust::types::Vector3) -> glam::DVec3 {
|
type Edge = ([f64; 3], [f64; 3]);
|
||||||
glam::DVec3::new(v.x, v.y, v.z)
|
|
||||||
|
struct SolidGeometry {
|
||||||
|
edges: Vec<Edge>,
|
||||||
|
fill_tris: Vec<[f64; 3]>,
|
||||||
|
extruded: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normal_tuple(solid: &Solid) -> (f64, f64, f64) {
|
||||||
|
let normal = glam::DVec3::new(solid.normal.x, solid.normal.y, solid.normal.z)
|
||||||
|
.normalize_or(glam::DVec3::Z);
|
||||||
|
(normal.x, normal.y, normal.z)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dvec3(v: [f64; 3]) -> glam::DVec3 {
|
||||||
|
glam::DVec3::from_array(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn wcs_corners(solid: &Solid) -> [[f64; 3]; 4] {
|
pub(crate) fn wcs_corners(solid: &Solid) -> [[f64; 3]; 4] {
|
||||||
let n = (solid.normal.x, solid.normal.y, solid.normal.z);
|
let n = normal_tuple(solid);
|
||||||
let w = |v: &acadrust::types::Vector3| {
|
let w = |v: &acadrust::types::Vector3| {
|
||||||
let (x, y, z) = crate::scene::view::transform::ocs_point_to_wcs((v.x, v.y, v.z), n);
|
let (x, y, z) = crate::scene::view::transform::ocs_point_to_wcs((v.x, v.y, v.z), n);
|
||||||
[x, y, z]
|
[x, y, z]
|
||||||
|
|
@ -35,140 +42,244 @@ pub(crate) fn wcs_corners(solid: &Solid) -> [[f64; 3]; 4] {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a non-self-intersecting perimeter for either conventional DXF SOLID
|
fn set_wcs_corner(solid: &mut Solid, index: usize, point: glam::DVec3) {
|
||||||
/// Z-order or older/perimeter-ordered data. Prefer DXF order when both are
|
let n = normal_tuple(solid);
|
||||||
/// valid, but recover legacy solids whose 1-2-4-3 walk forms a bow-tie.
|
let (x, y, z) = crate::scene::view::transform::wcs_point_to_ocs(
|
||||||
pub(crate) fn perimeter_indices(corners: &[[f64; 3]; 4]) -> [usize; 4] {
|
(point.x, point.y, point.z),
|
||||||
let p = |index: usize| glam::DVec3::from_array(corners[index]);
|
n,
|
||||||
let edge = p(1) - p(0);
|
);
|
||||||
let mut normal = edge.cross(p(2) - p(0));
|
let corner = match index {
|
||||||
if normal.length_squared() < 1.0e-20 {
|
0 => &mut solid.first_corner,
|
||||||
normal = edge.cross(p(3) - p(0));
|
1 => &mut solid.second_corner,
|
||||||
}
|
2 => &mut solid.third_corner,
|
||||||
if normal.length_squared() < 1.0e-20 {
|
3 => &mut solid.fourth_corner,
|
||||||
return [0, 1, 3, 2];
|
_ => return,
|
||||||
}
|
|
||||||
let orient = |a: usize, b: usize, c: usize| {
|
|
||||||
(p(b) - p(a)).cross(p(c) - p(a)).dot(normal)
|
|
||||||
};
|
};
|
||||||
let segments_cross = |a: usize, b: usize, c: usize, d: usize| {
|
corner.x = x;
|
||||||
let ab_c = orient(a, b, c);
|
corner.y = y;
|
||||||
let ab_d = orient(a, b, d);
|
corner.z = z;
|
||||||
let cd_a = orient(c, d, a);
|
}
|
||||||
let cd_b = orient(c, d, b);
|
|
||||||
((ab_c > 0.0 && ab_d < 0.0) || (ab_c < 0.0 && ab_d > 0.0))
|
fn push_edge(lines: &mut Vec<[f64; 3]>, start: [f64; 3], end: [f64; 3]) {
|
||||||
&& ((cd_a > 0.0 && cd_b < 0.0) || (cd_a < 0.0 && cd_b > 0.0))
|
lines.extend([start, end, [f64::NAN; 3]]);
|
||||||
};
|
}
|
||||||
let order_crosses = |order: [usize; 4]| {
|
|
||||||
segments_cross(order[0], order[1], order[2], order[3])
|
fn boundary(solid: &Solid) -> Vec<[f64; 3]> {
|
||||||
|| segments_cross(order[1], order[2], order[3], order[0])
|
let corners = wcs_corners(solid);
|
||||||
};
|
if solid.is_triangle() {
|
||||||
let dxf = [0, 1, 3, 2];
|
vec![corners[0], corners[1], corners[2]]
|
||||||
let perimeter = [0, 1, 2, 3];
|
|
||||||
if order_crosses(dxf) && !order_crosses(perimeter) {
|
|
||||||
perimeter
|
|
||||||
} else {
|
} else {
|
||||||
dxf
|
vec![corners[0], corners[1], corners[3], corners[2]]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn boundary_edges(points: &[[f64; 3]]) -> Vec<Edge> {
|
||||||
|
(0..points.len())
|
||||||
|
.map(|index| (points[index], points[(index + 1) % points.len()]))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn kernel_tolerance(points: &[[f64; 3]]) -> f64 {
|
||||||
|
cadkernel::space::coplanarity_tolerance(points).max(1.0e-12)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extrusion_body(
|
||||||
|
solid: &Solid,
|
||||||
|
base: &[[f64; 3]],
|
||||||
|
tolerance: f64,
|
||||||
|
profile_valid: bool,
|
||||||
|
) -> Option<cadkernel::brep::Body> {
|
||||||
|
if solid.thickness.abs() <= 1.0e-10 || !profile_valid {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let normal = glam::DVec3::new(solid.normal.x, solid.normal.y, solid.normal.z)
|
||||||
|
.normalize_or(glam::DVec3::Z);
|
||||||
|
let origin = glam::DVec3::from_array(base[0]);
|
||||||
|
let axis = base
|
||||||
|
.iter()
|
||||||
|
.skip(1)
|
||||||
|
.map(|point| glam::DVec3::from_array(*point) - origin)
|
||||||
|
.find(|axis| axis.length() > tolerance)?;
|
||||||
|
let plane = cadkernel::space::Plane::orthonormal(
|
||||||
|
base[0],
|
||||||
|
axis.to_array(),
|
||||||
|
normal.to_array(),
|
||||||
|
)?;
|
||||||
|
if base
|
||||||
|
.iter()
|
||||||
|
.any(|point| !plane.contains(*point, tolerance))
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let projected = base
|
||||||
|
.iter()
|
||||||
|
.map(|point| plane.project(*point))
|
||||||
|
.collect::<Option<Vec<_>>>()?;
|
||||||
|
let profile = (0..projected.len())
|
||||||
|
.map(|index| {
|
||||||
|
cadkernel::geom2d::Curve::Line(cadkernel::geom2d::Line {
|
||||||
|
start: projected[index],
|
||||||
|
end: projected[(index + 1) % projected.len()],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
cadkernel::brep::extrude(
|
||||||
|
plane,
|
||||||
|
&profile,
|
||||||
|
(normal * solid.thickness).to_array(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn solid_geometry(solid: &Solid) -> Option<SolidGeometry> {
|
||||||
|
let base = boundary(solid);
|
||||||
|
let tolerance = kernel_tolerance(&base);
|
||||||
|
let fill_tris = cadkernel::space::polygon::triangulate(
|
||||||
|
&base,
|
||||||
|
cadkernel::geom2d::Tolerance::new(tolerance),
|
||||||
|
);
|
||||||
|
if solid.thickness.abs() <= 1.0e-10 {
|
||||||
|
return Some(SolidGeometry {
|
||||||
|
edges: boundary_edges(&base),
|
||||||
|
fill_tris,
|
||||||
|
extruded: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let Some(body) = extrusion_body(solid, &base, tolerance, !fill_tris.is_empty()) else {
|
||||||
|
return Some(SolidGeometry {
|
||||||
|
edges: boundary_edges(&base),
|
||||||
|
fill_tris: Vec::new(),
|
||||||
|
extruded: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
let mesh = cadkernel::brep::mesh::tessellate(
|
||||||
|
&body,
|
||||||
|
cadkernel::brep::mesh::TessellationTolerance::new(
|
||||||
|
cadkernel::tessellation::DEFAULT_ANGLE,
|
||||||
|
tolerance,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if !mesh.missing_faces.is_empty() || mesh.mesh.is_empty() {
|
||||||
|
return Some(SolidGeometry {
|
||||||
|
edges: boundary_edges(&base),
|
||||||
|
fill_tris: Vec::new(),
|
||||||
|
extruded: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let edges = mesh
|
||||||
|
.edges
|
||||||
|
.iter()
|
||||||
|
.flat_map(|edge| edge.positions.windows(2).map(|pair| (pair[0], pair[1])))
|
||||||
|
.collect();
|
||||||
|
let mut fill_tris = Vec::with_capacity(mesh.mesh.triangles.len() * 3);
|
||||||
|
for triangle in mesh.mesh.triangles {
|
||||||
|
fill_tris.extend(triangle.map(|index| mesh.mesh.positions[index]));
|
||||||
|
}
|
||||||
|
Some(SolidGeometry {
|
||||||
|
edges,
|
||||||
|
fill_tris,
|
||||||
|
extruded: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn wcs_bounds(solid: &Solid) -> Option<cadkernel::brep::Aabb> {
|
||||||
|
let base = boundary(solid);
|
||||||
|
let tolerance = kernel_tolerance(&base);
|
||||||
|
let profile_valid = !cadkernel::space::polygon::triangulate(
|
||||||
|
&base,
|
||||||
|
cadkernel::geom2d::Tolerance::new(tolerance),
|
||||||
|
)
|
||||||
|
.is_empty();
|
||||||
|
extrusion_body(solid, &base, tolerance, profile_valid)
|
||||||
|
.and_then(|body| cadkernel::brep::body_bounds(&body))
|
||||||
|
.or_else(|| cadkernel::brep::Aabb::around(base))
|
||||||
|
}
|
||||||
|
|
||||||
impl RenderConvertible for Solid {
|
impl RenderConvertible for Solid {
|
||||||
fn to_render(&self, _document: &acadrust::CadDocument) -> Option<RenderEntity> {
|
fn to_render(&self, _document: &acadrust::CadDocument) -> Option<RenderEntity> {
|
||||||
// SOLID corners are OCS. Map them to WCS, then resolve either the DXF
|
let geometry = solid_geometry(self)?;
|
||||||
// Z-order or legacy perimeter order before building edges and fill.
|
let mut lines = Vec::new();
|
||||||
let corners = wcs_corners(self);
|
for &(start, end) in &geometry.edges {
|
||||||
let order = perimeter_indices(&corners);
|
push_edge(&mut lines, start, end);
|
||||||
let [p0, p1, p2, p3] = order.map(|index| corners[index]);
|
}
|
||||||
let pts = vec![
|
let mut nodes = Vec::new();
|
||||||
p0,
|
for &(start, end) in &geometry.edges {
|
||||||
p1,
|
for point in [start, end] {
|
||||||
[f64::NAN; 3],
|
if !nodes.contains(&point) {
|
||||||
p1,
|
nodes.push(point);
|
||||||
p2,
|
}
|
||||||
[f64::NAN; 3],
|
}
|
||||||
p2,
|
}
|
||||||
p3,
|
let mut snap = Vec::with_capacity(nodes.len() * 2 + geometry.edges.len());
|
||||||
[f64::NAN; 3],
|
for point in nodes {
|
||||||
p3,
|
snap.push((dvec3(point), SnapHint::Node));
|
||||||
p0,
|
snap.push((dvec3(point), SnapHint::Endpoint));
|
||||||
];
|
}
|
||||||
|
snap.extend(geometry.edges.iter().map(|(start, end)| {
|
||||||
let dvp = |p: [f64; 3]| glam::DVec3::from_array(p);
|
((dvec3(*start) + dvec3(*end)) * 0.5, SnapHint::Midpoint)
|
||||||
let snap = corners
|
}));
|
||||||
.iter()
|
let pick_tris = geometry
|
||||||
.copied()
|
.extruded
|
||||||
.map(|point| (dvp(point), SnapHint::Node))
|
.then(|| geometry.fill_tris.clone())
|
||||||
.collect();
|
.unwrap_or_default();
|
||||||
|
|
||||||
// Fill the resolved perimeter as two triangles. For a triangle the last
|
|
||||||
// two points coincide and the second triangle degenerates harmlessly.
|
|
||||||
let fill_tris = vec![p0, p1, p2, p0, p2, p3];
|
|
||||||
|
|
||||||
Some(RenderEntity {
|
Some(RenderEntity {
|
||||||
pick_tris: Vec::new(),
|
pick_tris,
|
||||||
object: RenderObject::Lines(pts),
|
object: RenderObject::Lines(lines),
|
||||||
snap_pts: snap,
|
snap_pts: snap,
|
||||||
tangent_geoms: vec![],
|
tangent_geoms: vec![],
|
||||||
key_vertices: corners.to_vec(),
|
key_vertices: Vec::new(),
|
||||||
fill_tris,
|
fill_tris: geometry.fill_tris,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Grippable for Solid {
|
impl Grippable for Solid {
|
||||||
fn grips(&self) -> Vec<GripDef> {
|
fn grips(&self) -> Vec<GripDef> {
|
||||||
|
let corners = wcs_corners(self);
|
||||||
vec![
|
vec![
|
||||||
square_grip(0, dvec3(&self.first_corner)),
|
square_grip(0, dvec3(corners[0])),
|
||||||
square_grip(1, dvec3(&self.second_corner)),
|
square_grip(1, dvec3(corners[1])),
|
||||||
square_grip(2, dvec3(&self.third_corner)),
|
square_grip(2, dvec3(corners[2])),
|
||||||
square_grip(3, dvec3(&self.fourth_corner)),
|
square_grip(3, dvec3(corners[3])),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
||||||
let corner = match grip_id {
|
let Some(current) = wcs_corners(self).get(grip_id).copied() else {
|
||||||
0 => &mut self.first_corner,
|
return;
|
||||||
1 => &mut self.second_corner,
|
|
||||||
2 => &mut self.third_corner,
|
|
||||||
3 => &mut self.fourth_corner,
|
|
||||||
_ => return,
|
|
||||||
};
|
};
|
||||||
match apply {
|
let point = match apply {
|
||||||
GripApply::Translate(d) => {
|
GripApply::Translate(delta) => dvec3(current) + delta,
|
||||||
corner.x += d.x as f64;
|
GripApply::Absolute(point) => point,
|
||||||
corner.y += d.y as f64;
|
};
|
||||||
corner.z += d.z as f64;
|
set_wcs_corner(self, grip_id, point);
|
||||||
}
|
|
||||||
GripApply::Absolute(p) => {
|
|
||||||
corner.x = p.x as f64;
|
|
||||||
corner.y = p.y as f64;
|
|
||||||
corner.z = p.z as f64;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PropertyEditable for Solid {
|
impl PropertyEditable for Solid {
|
||||||
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
|
fn geometry_properties(&self, _text_style_names: &[String]) -> Vec<PropSection> {
|
||||||
// Elevation is the OCS Z shared by the planar corners (no dedicated
|
let corners = wcs_corners(self);
|
||||||
// field on the entity); reported from the first corner's Z.
|
|
||||||
let elevation = self.first_corner.z;
|
let elevation = self.first_corner.z;
|
||||||
vec![PropSection {
|
vec![PropSection {
|
||||||
title: t!("Geometry").into_owned(),
|
title: t!("Geometry").into_owned(),
|
||||||
props: vec![
|
props: vec![
|
||||||
edit(t!("Point 1 X").as_ref(), "sl_p1x", self.first_corner.x),
|
edit(t!("Point 1 X").as_ref(), "sl_p1x", corners[0][0]),
|
||||||
edit(t!("Point 1 Y").as_ref(), "sl_p1y", self.first_corner.y),
|
edit(t!("Point 1 Y").as_ref(), "sl_p1y", corners[0][1]),
|
||||||
edit(t!("Point 1 Z").as_ref(), "sl_p1z", self.first_corner.z),
|
edit(t!("Point 1 Z").as_ref(), "sl_p1z", corners[0][2]),
|
||||||
edit(t!("Point 2 X").as_ref(), "sl_p2x", self.second_corner.x),
|
edit(t!("Point 2 X").as_ref(), "sl_p2x", corners[1][0]),
|
||||||
edit(t!("Point 2 Y").as_ref(), "sl_p2y", self.second_corner.y),
|
edit(t!("Point 2 Y").as_ref(), "sl_p2y", corners[1][1]),
|
||||||
edit(t!("Point 2 Z").as_ref(), "sl_p2z", self.second_corner.z),
|
edit(t!("Point 2 Z").as_ref(), "sl_p2z", corners[1][2]),
|
||||||
edit(t!("Point 3 X").as_ref(), "sl_p3x", self.third_corner.x),
|
edit(t!("Point 3 X").as_ref(), "sl_p3x", corners[2][0]),
|
||||||
edit(t!("Point 3 Y").as_ref(), "sl_p3y", self.third_corner.y),
|
edit(t!("Point 3 Y").as_ref(), "sl_p3y", corners[2][1]),
|
||||||
edit(t!("Point 3 Z").as_ref(), "sl_p3z", self.third_corner.z),
|
edit(t!("Point 3 Z").as_ref(), "sl_p3z", corners[2][2]),
|
||||||
edit(t!("Point 4 X").as_ref(), "sl_p4x", self.fourth_corner.x),
|
edit(t!("Point 4 X").as_ref(), "sl_p4x", corners[3][0]),
|
||||||
edit(t!("Point 4 Y").as_ref(), "sl_p4y", self.fourth_corner.y),
|
edit(t!("Point 4 Y").as_ref(), "sl_p4y", corners[3][1]),
|
||||||
edit(t!("Point 4 Z").as_ref(), "sl_p4z", self.fourth_corner.z),
|
edit(t!("Point 4 Z").as_ref(), "sl_p4z", corners[3][2]),
|
||||||
ro(t!("Elevation").as_ref(), "sl_elev", format!("{:.4}", elevation)),
|
edit(t!("Elevation").as_ref(), "sl_elev", elevation),
|
||||||
|
edit(t!("Thickness").as_ref(), "sl_thickness", self.thickness),
|
||||||
|
edit(t!("Normal X").as_ref(), "sl_normal_x", self.normal.x),
|
||||||
|
edit(t!("Normal Y").as_ref(), "sl_normal_y", self.normal.y),
|
||||||
|
edit(t!("Normal Z").as_ref(), "sl_normal_z", self.normal.z),
|
||||||
],
|
],
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
@ -177,19 +288,56 @@ impl PropertyEditable for Solid {
|
||||||
let Ok(v) = value.trim().parse::<f64>() else {
|
let Ok(v) = value.trim().parse::<f64>() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
let point_field = match field {
|
||||||
|
"sl_p1x" => Some((0, 0)),
|
||||||
|
"sl_p1y" => Some((0, 1)),
|
||||||
|
"sl_p1z" => Some((0, 2)),
|
||||||
|
"sl_p2x" => Some((1, 0)),
|
||||||
|
"sl_p2y" => Some((1, 1)),
|
||||||
|
"sl_p2z" => Some((1, 2)),
|
||||||
|
"sl_p3x" => Some((2, 0)),
|
||||||
|
"sl_p3y" => Some((2, 1)),
|
||||||
|
"sl_p3z" => Some((2, 2)),
|
||||||
|
"sl_p4x" => Some((3, 0)),
|
||||||
|
"sl_p4y" => Some((3, 1)),
|
||||||
|
"sl_p4z" => Some((3, 2)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some((point_index, component)) = point_field {
|
||||||
|
let mut point = dvec3(wcs_corners(self)[point_index]);
|
||||||
|
point[component] = v;
|
||||||
|
set_wcs_corner(self, point_index, point);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
match field {
|
match field {
|
||||||
"sl_p1x" => self.first_corner.x = v,
|
"sl_elev" => {
|
||||||
"sl_p1y" => self.first_corner.y = v,
|
let delta = v - self.first_corner.z;
|
||||||
"sl_p1z" => self.first_corner.z = v,
|
self.first_corner.z += delta;
|
||||||
"sl_p2x" => self.second_corner.x = v,
|
self.second_corner.z += delta;
|
||||||
"sl_p2y" => self.second_corner.y = v,
|
self.third_corner.z += delta;
|
||||||
"sl_p2z" => self.second_corner.z = v,
|
self.fourth_corner.z += delta;
|
||||||
"sl_p3x" => self.third_corner.x = v,
|
}
|
||||||
"sl_p3y" => self.third_corner.y = v,
|
"sl_thickness" => self.thickness = v,
|
||||||
"sl_p3z" => self.third_corner.z = v,
|
"sl_normal_x" | "sl_normal_y" | "sl_normal_z" => {
|
||||||
"sl_p4x" => self.fourth_corner.x = v,
|
let world = wcs_corners(self);
|
||||||
"sl_p4y" => self.fourth_corner.y = v,
|
let mut normal = glam::DVec3::new(self.normal.x, self.normal.y, self.normal.z);
|
||||||
"sl_p4z" => self.fourth_corner.z = v,
|
match field {
|
||||||
|
"sl_normal_x" => normal.x = v,
|
||||||
|
"sl_normal_y" => normal.y = v,
|
||||||
|
"sl_normal_z" => normal.z = v,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
if normal.length_squared() > 1.0e-20 {
|
||||||
|
normal = normal.normalize();
|
||||||
|
self.normal.x = normal.x;
|
||||||
|
self.normal.y = normal.y;
|
||||||
|
self.normal.z = normal.z;
|
||||||
|
for (index, point) in world.into_iter().enumerate() {
|
||||||
|
set_wcs_corner(self, index, dvec3(point));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
// 2D solid tool — interactive command.
|
// 2D solid tool — interactive command.
|
||||||
//
|
//
|
||||||
// Command: SOLID (reachable as SO / SOLID2D — the bare SOLID verb currently
|
// Command: SOLID (reachable as SO / SOLID2D) — pick three or four corner points
|
||||||
// toggles the shaded display) — pick three or four corner points and commit a
|
// and commit a filled triangle or quadrilateral. Four-point input is preserved
|
||||||
// filled triangle or quadrilateral. Four points may be picked either around
|
// in the entity's documented Z order; after a quadrilateral, its opposite edge
|
||||||
// the perimeter (the natural order shown by the rubber band) or in the
|
// starts the next connected shape. Enter after the third point commits a
|
||||||
// traditional Z pattern. The command normalizes both forms to the DXF corner
|
// triangle.
|
||||||
// order before committing. Enter after the third point commits a triangle.
|
|
||||||
|
|
||||||
use acadrust::entities::Solid;
|
use acadrust::entities::Solid;
|
||||||
use acadrust::types::Vector3;
|
use acadrust::types::Vector3;
|
||||||
|
|
@ -49,42 +48,12 @@ impl Solid2dCommand {
|
||||||
Vector3::new(p.x, p.y, p.z)
|
Vector3::new(p.x, p.y, p.z)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// True when walking the four points in pick order produces a bow-tie.
|
|
||||||
/// SOLID2D currently commits into the default world-XY OCS, so use that
|
|
||||||
/// same plane when deciding whether the last two points need swapping.
|
|
||||||
fn pick_order_crosses(points: &[DVec3]) -> bool {
|
|
||||||
if points.len() != 4 {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let orient = |a: DVec3, b: DVec3, c: DVec3| {
|
|
||||||
(b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)
|
|
||||||
};
|
|
||||||
let crosses = |a: DVec3, b: DVec3, c: DVec3, d: DVec3| {
|
|
||||||
let ab_c = orient(a, b, c);
|
|
||||||
let ab_d = orient(a, b, d);
|
|
||||||
let cd_a = orient(c, d, a);
|
|
||||||
let cd_b = orient(c, d, b);
|
|
||||||
((ab_c > 0.0 && ab_d < 0.0) || (ab_c < 0.0 && ab_d > 0.0))
|
|
||||||
&& ((cd_a > 0.0 && cd_b < 0.0) || (cd_a < 0.0 && cd_b > 0.0))
|
|
||||||
};
|
|
||||||
crosses(points[0], points[1], points[2], points[3])
|
|
||||||
|| crosses(points[1], points[2], points[3], points[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert either perimeter picks (1-2-3-4) or classic SOLID Z picks
|
|
||||||
/// (1-2-4-3 around the perimeter) into the entity's DXF Z order.
|
|
||||||
fn solid_from_four_points(points: &[DVec3]) -> Solid {
|
fn solid_from_four_points(points: &[DVec3]) -> Solid {
|
||||||
let perimeter_order = !Self::pick_order_crosses(points);
|
|
||||||
let (third, fourth) = if perimeter_order {
|
|
||||||
(points[3], points[2])
|
|
||||||
} else {
|
|
||||||
(points[2], points[3])
|
|
||||||
};
|
|
||||||
Solid::new(
|
Solid::new(
|
||||||
Self::v3(points[0]),
|
Self::v3(points[0]),
|
||||||
Self::v3(points[1]),
|
Self::v3(points[1]),
|
||||||
Self::v3(third),
|
Self::v3(points[2]),
|
||||||
Self::v3(fourth),
|
Self::v3(points[3]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -126,7 +95,10 @@ impl CadCommand for Solid2dCommand {
|
||||||
.map(|point| self.plane.to_local(*point))
|
.map(|point| self.plane.to_local(*point))
|
||||||
.collect();
|
.collect();
|
||||||
let solid = Self::solid_from_four_points(&local);
|
let solid = Self::solid_from_four_points(&local);
|
||||||
CmdResult::CommitAndExit(self.plane.place_entity(EntityType::Solid(solid)))
|
let next_edge = [self.points[2], self.points[3]];
|
||||||
|
self.points.clear();
|
||||||
|
self.points.extend(next_edge);
|
||||||
|
CmdResult::CommitEntity(self.plane.place_entity(EntityType::Solid(solid)))
|
||||||
} else {
|
} else {
|
||||||
CmdResult::NeedPoint
|
CmdResult::NeedPoint
|
||||||
}
|
}
|
||||||
|
|
@ -154,14 +126,22 @@ impl CadCommand for Solid2dCommand {
|
||||||
if self.points.is_empty() {
|
if self.points.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
// Outline the corners picked so far plus the cursor, closed back to the
|
// Outline the exact Z-ordered boundary that would be committed. Do not
|
||||||
// first point, as a rubber-band hint.
|
// reorder a crossing shape: point order is intentional geometry.
|
||||||
let mut preview = self.points.clone();
|
let mut preview = self.points.clone();
|
||||||
preview.push(pt);
|
preview.push(pt);
|
||||||
if preview.len() == 4 && Self::pick_order_crosses(&preview) {
|
let display_order: Vec<usize> = if preview.len() == 4 {
|
||||||
preview.swap(2, 3);
|
vec![0, 1, 3, 2]
|
||||||
}
|
} else {
|
||||||
let mut pts: Vec<[f64; 3]> = preview.iter().map(|p| [p.x, p.y, p.z]).collect();
|
(0..preview.len()).collect()
|
||||||
|
};
|
||||||
|
let mut pts: Vec<[f64; 3]> = display_order
|
||||||
|
.iter()
|
||||||
|
.map(|index| {
|
||||||
|
let point = preview[*index];
|
||||||
|
[point.x, point.y, point.z]
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
pts.push([preview[0].x, preview[0].y, preview[0].z]);
|
pts.push([preview[0].x, preview[0].y, preview[0].z]);
|
||||||
Some(WireModel::solid_f64(
|
Some(WireModel::solid_f64(
|
||||||
"rubber_band".to_string(),
|
"rubber_band".to_string(),
|
||||||
|
|
|
||||||
|
|
@ -1704,12 +1704,25 @@ pub(crate) fn set_wire_aabb(w: &mut WireModel, entity_box: [f32; 4]) {
|
||||||
w.aabb = out;
|
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] {
|
pub(crate) fn entity_aabb(e: &acadrust::EntityType) -> [f32; 4] {
|
||||||
let bbox = e.as_entity().bounding_box();
|
let (min, max) = entity_bounds(e);
|
||||||
let min_x = (bbox.min.x) as f32;
|
let min_x = min[0] as f32;
|
||||||
let min_y = (bbox.min.y) as f32;
|
let min_y = min[1] as f32;
|
||||||
let max_x = (bbox.max.x) as f32;
|
let max_x = max[0] as f32;
|
||||||
let max_y = (bbox.max.y) as f32;
|
let max_y = max[1] as f32;
|
||||||
// The all-zero box is bounding_box()'s Default — returned by entities with
|
// The all-zero box is bounding_box()'s Default — returned by entities with
|
||||||
// no usable box (unimplemented) — so treat it as UNBOUNDED (never
|
// no usable box (unimplemented) — so treat it as UNBOUNDED (never
|
||||||
// pre-rejected). A genuinely zero-size box *away* from the origin (e.g. a
|
// 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
|
/// indexing uses this so changing `world_offset` doesn't invalidate
|
||||||
/// the index.
|
/// the index.
|
||||||
pub(crate) fn entity_world_aabb_f64(e: &acadrust::EntityType) -> Option<[f64; 4]> {
|
pub(crate) fn entity_world_aabb_f64(e: &acadrust::EntityType) -> Option<[f64; 4]> {
|
||||||
let bbox = e.as_entity().bounding_box();
|
let (min, max) = entity_bounds(e);
|
||||||
let (xmin, ymin, xmax, ymax) = (bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y);
|
let (xmin, ymin, xmax, ymax) = (min[0], min[1], max[0], max[1]);
|
||||||
if xmin == xmax && ymin == ymax {
|
if xmin == xmax && ymin == ymax {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1260,7 +1260,7 @@ pub fn tessellate(
|
||||||
| EntityType::PolyfaceMesh(_)
|
| EntityType::PolyfaceMesh(_)
|
||||||
| EntityType::PolygonMesh(_)
|
| EntityType::PolygonMesh(_)
|
||||||
| EntityType::Mesh(_)
|
| 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
|
// Thickness walls ride on the wire that carries their edges, not
|
||||||
// on a wire of their own: they are pick geometry for that entity,
|
// on a wire of their own: they are pick geometry for that entity,
|
||||||
// and `fill_tris` below deliberately splits off into a fill-only
|
// and `fill_tris` below deliberately splits off into a fill-only
|
||||||
|
|
|
||||||
|
|
@ -2222,8 +2222,8 @@ impl Scene {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Build a solid-fill HatchModel for a DXF Solid entity.
|
/// Build a solid-fill HatchModel for a DXF Solid entity.
|
||||||
/// Conventional DXF SOLID corners use Z-order; legacy entities may already
|
/// SOLID corners use Z-order. Preserve it in the projected hatch as well so
|
||||||
/// be in perimeter order. Use the same non-crossing resolver as wire fill.
|
/// intentionally crossing geometry is not silently rewritten.
|
||||||
pub(super) fn solid_hatch_model(solid: &DxfSolid, color: [f32; 4]) -> HatchModel {
|
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
|
// 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
|
// 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
|
// 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.
|
// the quad snapped to a grid and the fill drifted off its outline.
|
||||||
let wcs = crate::entities::solid::wcs_corners(solid);
|
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 corners: [[f64; 2]; 4] = order.map(|index| [wcs[index][0], wcs[index][1]]);
|
||||||
let mut min = [f64::INFINITY; 2];
|
let mut min = [f64::INFINITY; 2];
|
||||||
let mut max = [f64::NEG_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;
|
||||||
use iced::wgpu::util::DeviceExt;
|
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 ──────────────────────────────────────────────────────────
|
// ── Vertex layout ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
|
|
@ -74,8 +116,8 @@ impl Face3DVertex {
|
||||||
// ── GPU handle ─────────────────────────────────────────────────────────────
|
// ── GPU handle ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
/// One vertex buffer + draw count. Fill data is split into as many chunks
|
/// 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
|
/// as the device's `max_buffer_size` requires: a mesh-heavy drawing can hold
|
||||||
/// Navisworks import) can hold tens of millions of fill triangles, and at
|
/// tens of millions of fill triangles, and at
|
||||||
/// 44 B/vertex a single batched buffer blows past the default 256 MB limit
|
/// 44 B/vertex a single batched buffer blows past the default 256 MB limit
|
||||||
/// once enough layers are thawed — wgpu then raises an uncaptured
|
/// once enough layers are thawed — wgpu then raises an uncaptured
|
||||||
/// validation error and aborts the process (#358). Same scheme as the
|
/// validation error and aborts the process (#358). Same scheme as the
|
||||||
|
|
@ -149,6 +191,7 @@ impl Face3DGpu {
|
||||||
all_wires: &[WireModel],
|
all_wires: &[WireModel],
|
||||||
keep_3d_mesh_fills: bool,
|
keep_3d_mesh_fills: bool,
|
||||||
show_2d_solid_fills: bool,
|
show_2d_solid_fills: bool,
|
||||||
|
view_dir: glam::Vec3,
|
||||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let depth_of =
|
let depth_of =
|
||||||
|
|
@ -200,6 +243,9 @@ impl Face3DGpu {
|
||||||
if !show_2d_solid_fills && wire.fill_is_2d_solid {
|
if !show_2d_solid_fills && wire.fill_is_2d_solid {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if !planar_solid_faces_view(wire, view_dir) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// A real 3-D surface fill (PolyfaceMesh / PolygonMesh) carries a
|
// A real 3-D surface fill (PolyfaceMesh / PolygonMesh) carries a
|
||||||
// double-single low residual paired with `fill_tris` — it lives at
|
// double-single low residual paired with `fill_tris` — it lives at
|
||||||
// true world coordinates and must keep its real depth. 2-D fills
|
// true world coordinates and must keep its real depth. 2-D fills
|
||||||
|
|
@ -259,6 +305,7 @@ impl Face3DGpu {
|
||||||
all_wires,
|
all_wires,
|
||||||
keep_3d_mesh_fills,
|
keep_3d_mesh_fills,
|
||||||
show_2d_solid_fills,
|
show_2d_solid_fills,
|
||||||
|
view_dir,
|
||||||
depth_map,
|
depth_map,
|
||||||
);
|
);
|
||||||
Self {
|
Self {
|
||||||
|
|
@ -275,6 +322,7 @@ fn upload_block_chunks(
|
||||||
wires: &[WireModel],
|
wires: &[WireModel],
|
||||||
keep_3d_mesh_fills: bool,
|
keep_3d_mesh_fills: bool,
|
||||||
show_2d_solid_fills: bool,
|
show_2d_solid_fills: bool,
|
||||||
|
view_dir: glam::Vec3,
|
||||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||||
) -> (Vec<BlockFace3DChunk>, Vec<BlockFace3DChunk>) {
|
) -> (Vec<BlockFace3DChunk>, Vec<BlockFace3DChunk>) {
|
||||||
let mut slots = rustc_hash::FxHashMap::default();
|
let mut slots = rustc_hash::FxHashMap::default();
|
||||||
|
|
@ -285,6 +333,7 @@ fn upload_block_chunks(
|
||||||
};
|
};
|
||||||
if wire.fill_tris.is_empty()
|
if wire.fill_tris.is_empty()
|
||||||
|| (!show_2d_solid_fills && wire.fill_is_2d_solid)
|
|| (!show_2d_solid_fills && wire.fill_is_2d_solid)
|
||||||
|
|| !planar_solid_faces_view(wire, view_dir)
|
||||||
|| (wire.fill_is_3d && !keep_3d_mesh_fills)
|
|| (wire.fill_is_3d && !keep_3d_mesh_fills)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -312,12 +312,8 @@ pub struct Pipeline {
|
||||||
/// a pick bumps only `selection_generation`, refreshing the overlay without
|
/// a pick bumps only `selection_generation`, refreshing the overlay without
|
||||||
/// touching the main wire buffers.
|
/// touching the main wire buffers.
|
||||||
pub cached_selection: (u64, u64),
|
pub cached_selection: (u64, u64),
|
||||||
/// `(wire_content_id, face3d_fill_active, show_2d_solid_fills)` the Face3D
|
/// Face upload key; keeps the resident wire Arc uniquely owned by Scene.
|
||||||
/// edge/fill buffers were uploaded for. A stable content id avoids retaining
|
pub cached_face3d_key: (u64, bool, bool, u64),
|
||||||
/// 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),
|
|
||||||
/// Handle → indices into the resident wire set, built once per wire upload
|
/// Handle → indices into the resident wire set, built once per wire upload
|
||||||
/// (when `cached_wire_id` changes). Lets the selection/hover xray overlay
|
/// (when `cached_wire_id` changes). Lets the selection/hover xray overlay
|
||||||
/// gather just the highlighted entity's wires (`O(highlighted)`) instead of
|
/// 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_epoch: (u64::MAX, u64::MAX, u64::MAX),
|
||||||
cached_wire_id: u64::MAX,
|
cached_wire_id: u64::MAX,
|
||||||
cached_selection: (u64::MAX, 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()),
|
wire_handle_index: std::sync::Arc::new(rustc_hash::FxHashMap::default()),
|
||||||
render_sig: u64::MAX,
|
render_sig: u64::MAX,
|
||||||
skip_geometry: false,
|
skip_geometry: false,
|
||||||
|
|
@ -3023,6 +3019,7 @@ impl Pipeline {
|
||||||
all_wires: &[WireModel],
|
all_wires: &[WireModel],
|
||||||
wireframe_only: bool,
|
wireframe_only: bool,
|
||||||
show_2d_solid_fills: bool,
|
show_2d_solid_fills: bool,
|
||||||
|
view_dir: glam::Vec3,
|
||||||
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
depth_map: &rustc_hash::FxHashMap<u64, [f32; 2]>,
|
||||||
) {
|
) {
|
||||||
let perf_started = crate::perf::enabled().then(iced::time::Instant::now);
|
let perf_started = crate::perf::enabled().then(iced::time::Instant::now);
|
||||||
|
|
@ -3056,6 +3053,7 @@ impl Pipeline {
|
||||||
all_wires,
|
all_wires,
|
||||||
keep_3d_mesh_fills,
|
keep_3d_mesh_fills,
|
||||||
show_2d_solid_fills,
|
show_2d_solid_fills,
|
||||||
|
view_dir,
|
||||||
depth_map,
|
depth_map,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -378,7 +378,7 @@ impl shader::Primitive for Primitive {
|
||||||
inner.cached_wire_id = u64::MAX;
|
inner.cached_wire_id = u64::MAX;
|
||||||
inner.cached_selection = (u64::MAX, u64::MAX);
|
inner.cached_selection = (u64::MAX, u64::MAX);
|
||||||
inner.cached_mesh_content_id = 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_hatch_source = None;
|
||||||
inner.cached_preview_hatch_source = None;
|
inner.cached_preview_hatch_source = None;
|
||||||
inner.cached_wipeout_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 view toggle so 2D fills stay on even when the user picks
|
||||||
// the Wireframe overlay style.
|
// the Wireframe overlay style.
|
||||||
let face3d_fill_active = fill_mode && !vp.view_wireframe;
|
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 fill_changed = inner.cached_fill_mode != fill_mode;
|
||||||
let hatch_changed = inner
|
let hatch_changed = inner
|
||||||
.cached_hatch_source
|
.cached_hatch_source
|
||||||
|
|
@ -574,14 +580,16 @@ impl shader::Primitive for Primitive {
|
||||||
&& !face_pass_unchanged);
|
&& !face_pass_unchanged);
|
||||||
if face3d_changed
|
if face3d_changed
|
||||||
|| face3d_fill_active != inner.cached_face3d_key.1
|
|| 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(
|
inner.upload_face3d(
|
||||||
device,
|
device,
|
||||||
&vp.face3d_wires[..],
|
&vp.face3d_wires[..],
|
||||||
&vp_wires[..],
|
&vp_wires[..],
|
||||||
!face3d_fill_active,
|
!face3d_fill_active,
|
||||||
vp.show_2d_solid_fills,
|
solid_fill_active,
|
||||||
|
vp.view_dir,
|
||||||
&draw_depths,
|
&draw_depths,
|
||||||
);
|
);
|
||||||
inner.cached_face3d_source = Some(Arc::clone(&vp.face3d_wires));
|
inner.cached_face3d_source = Some(Arc::clone(&vp.face3d_wires));
|
||||||
|
|
@ -590,7 +598,8 @@ impl shader::Primitive for Primitive {
|
||||||
inner.cached_face3d_key = (
|
inner.cached_face3d_key = (
|
||||||
vp.wire_content_id,
|
vp.wire_content_id,
|
||||||
face3d_fill_active,
|
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
|
// Wire buffers are world-space, so a camera move alone doesn't
|
||||||
// change them — only the view_proj uniform (uploaded every frame).
|
// change them — only the view_proj uniform (uploaded every frame).
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue