Show a bounded native-cache preview before authoritative reconstruction (#463)
Refs #303. file.save / file.save_as can store a warm native geometry cache (cache: true, off by default) keyed to the document hash, app version and adapter revision. Opening shows a bounded preview from it until the rebuilt session replaces it. Any cache failure counts as a miss.
This commit is contained in:
1 parent
1abd989a67
commit
913583bfee
20 files changed
+943
-84
No files matched your search
Vendored
+6
@@ -46,6 +46,12 @@ Changes:
|
||||
## truck-meshalgo 0.4.0 (Apache-2.0, © RICOS Co. Ltd., https://github.com/ricosjp/truck)
|
||||
|
||||
Changes:
|
||||
- `tessellation/mod.rs`, `triangulation.rs`: a separate provisional `preview_face`
|
||||
path accepts at most 4,096 already sampled boundary points and a fixed grid of
|
||||
at most 16 subdivisions per direction, including periodic seam connections.
|
||||
It does not run adaptive subdivision or grid balancing. The kernel caller bounds
|
||||
projection complexity and total allocation before using it; ordinary modelling
|
||||
tessellation keeps its existing accuracy policy.
|
||||
- `tessellation/triangulation.rs`: the inside test of a face's boundary skips loops whose
|
||||
parameter box is clear of the point (it tested every segment of every loop, so a face with
|
||||
hundreds of holes meshed in quadratic time).
|
||||
|
||||
@@ -18,6 +18,13 @@ mod parallelizable {
|
||||
|
||||
pub use parallelizable::*;
|
||||
|
||||
/// SolidCraft: a disposable preview of a face from already sampled boundary loops.
|
||||
/// Caps input points and uses a fixed parameter grid, including periodic seams.
|
||||
/// This is approximate display, never an accuracy-controlled modelling mesh.
|
||||
pub fn preview_face<S: RobustMeshableSurface>(surface: &S, loops: &[Vec<Point3>], steps: usize) -> Option<PolygonMesh> {
|
||||
triangulation::preview_face(surface, loops, steps)
|
||||
}
|
||||
|
||||
/// Gathered the traits used in tessellation.
|
||||
pub trait PolylineableCurve:
|
||||
ParametricCurve3D + BoundedCurve + ParameterDivision1D<Point = Point3> + Parallelizable {
|
||||
|
||||
+55
-17
@@ -483,11 +483,18 @@ fn loop_orientation(curve: &[Point2]) -> bool {
|
||||
}
|
||||
|
||||
impl PolyBoundary {
|
||||
fn new(
|
||||
pieces: Vec<PolyBoundaryPiece>,
|
||||
surface: &impl PreMeshableSurface,
|
||||
tol: Deflection,
|
||||
) -> Self {
|
||||
fn new(pieces: Vec<PolyBoundaryPiece>, surface: &impl PreMeshableSurface, tol: Deflection) -> Self {
|
||||
Self::with_steps(pieces, surface, tol, None)
|
||||
}
|
||||
// SolidCraft: only the disposable preview supplies fixed seam subdivisions.
|
||||
fn with_steps<S: PreMeshableSurface>(pieces: Vec<PolyBoundaryPiece>, surface: &S, tol: Deflection, steps: Option<usize>) -> Self {
|
||||
let polyline_on_surface = |surface: &S, p: Point2, q: Point2, tol| {
|
||||
if let Some(n) = steps {
|
||||
(0..=n).map(|i| p + (q - p) * (i as f64 / n as f64)).collect()
|
||||
} else {
|
||||
polyline_on_surface(surface, p, q, tol)
|
||||
}
|
||||
};
|
||||
let (mut closed, mut open) = (Vec::new(), Vec::new());
|
||||
pieces.into_iter().for_each(|PolyBoundaryPiece(mut vec)| {
|
||||
match vec[0].distance(vec[vec.len() - 1]) < 1.0e-3 {
|
||||
@@ -682,18 +689,42 @@ fn spade_round(x: f64) -> f64 {
|
||||
}
|
||||
|
||||
/// Tessellates one surface trimmed by polyline.
|
||||
fn trimming_tessellation<S>(
|
||||
surface: &S,
|
||||
polyboundary: &PolyBoundary,
|
||||
tol: Deflection,
|
||||
cap: usize,
|
||||
) -> PolygonMesh
|
||||
where
|
||||
S: PreMeshableSurface,
|
||||
{
|
||||
fn trimming_tessellation<S>(surface: &S, polyboundary: &PolyBoundary, tol: Deflection, cap: usize) -> PolygonMesh
|
||||
where S: PreMeshableSurface {
|
||||
trimming_with_steps(surface, polyboundary, tol, cap, None)
|
||||
}
|
||||
|
||||
/// Fixed-size SolidCraft preview path; regular tessellation retains its adaptive policy.
|
||||
pub(super) fn preview_face<S: RobustMeshableSurface>(surface: &S, loops: &[Vec<Point3>], steps: usize) -> Option<PolygonMesh> {
|
||||
if !(1..=16).contains(&steps) || loops.is_empty()
|
||||
|| loops.iter().any(|w| w.len() < 3)
|
||||
|| loops.iter().map(Vec::len).sum::<usize>() > 4096 {
|
||||
return None;
|
||||
}
|
||||
let search = |surface: &S, point, hint| {
|
||||
surface.search_parameter(point, hint, 16)
|
||||
.or_else(|| surface.search_parameter(point, None, 16))
|
||||
.or_else(|| surface.search_nearest_parameter(point, hint, 16).map(|uv| sane(surface, point, uv)))
|
||||
.or_else(|| surface.search_nearest_parameter(point, None, 16).map(|uv| sane(surface, point, uv)))
|
||||
};
|
||||
let pieces = loops.iter().map(|w| {
|
||||
PolyBoundaryPiece::try_new(surface, std::iter::once(PolylineCurve(w.clone())), search)
|
||||
}).collect::<Option<Vec<_>>>()?;
|
||||
let boundary = PolyBoundary::with_steps(pieces, surface, Deflection::chord(1e-3), Some(steps));
|
||||
// Projection can duplicate points at singularities; periodic seams add at most
|
||||
// eight fixed polylines. Reject non-finite parameters before spade sees them.
|
||||
if boundary.0.iter().map(Vec::len).sum::<usize>() > 8192 + 8 * (steps + 1)
|
||||
|| boundary.0.iter().flatten().any(|p| !p.x.is_finite() || !p.y.is_finite()) {
|
||||
return None;
|
||||
}
|
||||
Some(trimming_with_steps(surface, &boundary, Deflection::chord(1e-3), (steps + 1).pow(2), Some(steps)))
|
||||
}
|
||||
|
||||
// SolidCraft: the normal entry point supplies None, preserving adaptive meshing.
|
||||
fn trimming_with_steps<S: PreMeshableSurface>(surface: &S, polyboundary: &PolyBoundary, tol: Deflection, cap: usize, steps: Option<usize>) -> PolygonMesh {
|
||||
let mut triangulation = Cdt::new();
|
||||
polyboundary.insert_to(&mut triangulation);
|
||||
insert_surface(&mut triangulation, surface, polyboundary, tol, cap);
|
||||
insert_surface(&mut triangulation, surface, polyboundary, tol, cap, steps);
|
||||
let mut mesh = triangulation_into_polymesh(
|
||||
triangulation.vertices(),
|
||||
triangulation.inner_faces(),
|
||||
@@ -711,14 +742,21 @@ fn insert_surface(
|
||||
polyline: &PolyBoundary,
|
||||
tol: Deflection,
|
||||
cap: usize,
|
||||
steps: Option<usize>,
|
||||
) {
|
||||
let bdb: BoundingBox<Point2> = polyline.0.iter().flatten().collect();
|
||||
let range = ((bdb.min()[0], bdb.max()[0]), (bdb.min()[1], bdb.max()[1]));
|
||||
if !(range.0 .0.is_finite() && range.0 .1.is_finite() && range.1 .0.is_finite() && range.1 .1.is_finite()) {
|
||||
return;
|
||||
}
|
||||
let (udiv, vdiv) = bounded_surface_division(&surface, range, tol, cap);
|
||||
let (udiv, vdiv) = balanced_grid(&surface, udiv, vdiv, cap);
|
||||
// SolidCraft: fixed preview grids skip both adaptive division and balancing.
|
||||
let (udiv, vdiv) = if let Some(n) = steps {
|
||||
let division = |(a, b): (f64, f64)| (0..=n).map(|i| a * (1. - i as f64 / n as f64) + b * (i as f64 / n as f64)).collect();
|
||||
(division(range.0), division(range.1))
|
||||
} else {
|
||||
let (u, v) = bounded_surface_division(&surface, range, tol, cap);
|
||||
balanced_grid(&surface, u, v, cap)
|
||||
};
|
||||
let insert_res: Vec<Vec<Option<_>>> = udiv
|
||||
.into_iter()
|
||||
.map(|u| {
|
||||
|
||||
Reference in new issue
Block a user