mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-11 03:01:58 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b23229fcaa |
@@ -62,7 +62,7 @@
|
||||
{"case":"adv_pp_scaled_uv","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":[0.0,0.0,0.0,0.0,0.0,0.0,0.0]}}
|
||||
{"case":"adv_pp_use_default_tol","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":44}}}
|
||||
{"case":"adv_pp_zero_tol","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":44}}}
|
||||
{"case":"adv_tc_big_tol","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":68}}}
|
||||
{"case":"adv_tc_big_tol","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":58}}}
|
||||
{"case":"adv_tc_bspline_c0_corner","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":30}}}
|
||||
{"case":"adv_tc_bspline_dim2","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":31}}}
|
||||
{"case":"adv_tc_bspline_rational_huge","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":[0.0,0.0]}}
|
||||
@@ -74,7 +74,7 @@
|
||||
{"case":"adv_tc_segment_along_edge_part","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":35}}}
|
||||
{"case":"adv_tc_segment_diag_corner_touch","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":25}}}
|
||||
{"case":"adv_tc_segment_end_on_edge","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":35}}}
|
||||
{"case":"adv_tc_segment_inside_tiny","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":35}}}
|
||||
{"case":"adv_tc_segment_inside_tiny","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":25}}}
|
||||
{"case":"adv_tc_segment_on_edge_exact","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":35}}}
|
||||
{"case":"adv_tc_zero_tol","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":35}}}
|
||||
{"case":"adv_tc_zero_tol_circle","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[14438.828427124747,14477.45584412272,64161.26955262169]],"n":75}}}
|
||||
@@ -534,7 +534,7 @@
|
||||
{"case":"gen0408","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":35}}}
|
||||
{"case":"gen0409","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":54}}}
|
||||
{"case":"gen0410","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[29175.492296197146,29186.358032300137,1065969.512160808]],"n":108}}}
|
||||
{"case":"gen0411","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":35}}}
|
||||
{"case":"gen0411","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":25}}}
|
||||
{"case":"gen0412","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":54}}}
|
||||
{"case":"gen0413","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":56}}}
|
||||
{"case":"gen0414","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":54}}}
|
||||
@@ -1074,7 +1074,7 @@
|
||||
{"case":"gen0948","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":[0.0,0.0,0.0]}}
|
||||
{"case":"gen0949","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":54}}}
|
||||
{"case":"gen0950","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":54}}}
|
||||
{"case":"gen0951","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":35}}}
|
||||
{"case":"gen0951","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":25}}}
|
||||
{"case":"gen0952","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":35}}}
|
||||
{"case":"gen0953","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":[0.0,0.0,0.0,0.0,0.0,0.0,0.0]}}
|
||||
{"case":"gen0954","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":[0.0,0.0]}}
|
||||
@@ -1831,7 +1831,7 @@
|
||||
{"case":"gen1705","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":[0.0,0.0]}}
|
||||
{"case":"gen1706","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":[0.0,0.0,0.0,0.0,0.0,0.0,0.0]}}
|
||||
{"case":"gen1707","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":[0.0,1.0,0.0,0.0,0.0,0.0,0.0]}}
|
||||
{"case":"gen1708","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":35}}}
|
||||
{"case":"gen1708","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":25}}}
|
||||
{"case":"gen1709","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":[0.0,0.0,0.0]}}
|
||||
{"case":"gen1710","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":[0.0,0.0,0.0,0.0,0.0,0.0,0.0]}}
|
||||
{"case":"gen1711","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":56}}}
|
||||
@@ -2200,7 +2200,7 @@
|
||||
{"case":"trim_circle_reversed_domain","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[14083.454157922033,14083.454157922033,57473.38889061367]],"n":45}}}
|
||||
{"case":"trim_circle_tangent_inside","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[14435.828427124747,14464.798989873227,64126.896969619665]],"n":75}}}
|
||||
{"case":"trim_circle_tangent_outside","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":[0.0,0.0]}}
|
||||
{"case":"trim_circle_tiny","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[14470.970562748482,14470.970562748485,65697.74891681032]],"n":75}}}
|
||||
{"case":"trim_circle_tiny","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[["-inf","inf","-inf"]],"n":25}}}
|
||||
{"case":"trim_circle_zero_axis","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[14459.313708498987,14478.627416997973,65102.90620433565]],"n":75}}}
|
||||
{"case":"trim_ellipse_arc","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[14450.501728902433,14459.798966462204,64410.94775035284]],"n":65}}}
|
||||
{"case":"trim_ellipse_big_tol","deferred":null,"error":null,"ints":{},"topology":null,"values":{"rec":{"blocks":[[14465.894336937534,14479.8415054063,65111.072629091555]],"n":75}}}
|
||||
|
||||
+33
-5
@@ -92,7 +92,9 @@
|
||||
//! `SmCurve::GlobalCoincidenceChecker` of the ellipse are
|
||||
//! [`curve_base::Curve::global_point_solve`] and
|
||||
//! [`curve_base::Curve::global_coincidence_checker`] on copies of the ellipses as
|
||||
//! curve kinds (taking the [`Kernel`]). The
|
||||
//! curve kinds (taking the [`Kernel`]). These query copies, including a local
|
||||
//! solve's line, preserve knots exactly: copy-constructor cleanup can collapse a
|
||||
//! nonzero parameter interval. The
|
||||
//! line's `GlobalPointSolve(SM_SO_INTERSECT)` of `IntersectWithLine` is the line's
|
||||
//! `DropPointFast`, which handles every intersection. `SmCurve::LocalCurveSolve` (unit
|
||||
//! `curve.curve_p3`) evaluates the ellipse through its B-spline base and the line
|
||||
@@ -192,7 +194,7 @@ fn ellipse_global_point_solve<O: Copy>(
|
||||
point: Point3,
|
||||
tol: f64,
|
||||
) -> Result<Vec<Solution<O>>> {
|
||||
let c = curve_base::Curve::from(curve.clone());
|
||||
let c = curve_base::Curve::from(curve.clone_exact_bounded());
|
||||
let opts = GlobalPointSolveOptions {
|
||||
handle: Some(ParamHandle(curve.base.natural_interval())),
|
||||
..GlobalPointSolveOptions::default()
|
||||
@@ -291,7 +293,7 @@ fn local_curve_solve_line<O: Copy>(
|
||||
guesses: [f64; 2],
|
||||
) -> Result<(bool, Solution<O>)> {
|
||||
// (the line evaluates through the curve dispatch, as its virtual Evaluate)
|
||||
let other = curve_base::Curve::from(other.clone());
|
||||
let other = curve_base::Curve::from(other.clone_exact_bounded());
|
||||
local_curve_solve(
|
||||
k,
|
||||
&curve.base,
|
||||
@@ -320,8 +322,8 @@ fn global_coincidence_checker<O: Copy>(
|
||||
tol: f64,
|
||||
objects: [Option<O>; 2],
|
||||
) -> Result<(bool, Vec<Solution<O>>)> {
|
||||
let a = curve_base::Curve::from(curve.clone());
|
||||
let b = curve_base::Curve::from(other.clone());
|
||||
let a = curve_base::Curve::from(curve.clone_exact_bounded());
|
||||
let b = curve_base::Curve::from(other.clone_exact_bounded());
|
||||
let handles = [
|
||||
Some(IndexHandle(0, curve.base.natural_interval())),
|
||||
Some(IndexHandle(1, other.base.natural_interval())),
|
||||
@@ -2508,6 +2510,32 @@ mod tests {
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn point_solve_keeps_a_nonzero_arc_parameter_domain() {
|
||||
let k = kernel();
|
||||
let interval = Extent1d::from_min_max(0.999_999_999_998_075_2, 1.0);
|
||||
let mut arc = circle(&k, 0.0, 90.0, 2.0, false);
|
||||
// The same quarter circle on a short parameter interval is still a
|
||||
// quarter circle. Query wrappers must not clean up its knots on copying.
|
||||
arc.base.edit_parameterization(&interval).unwrap();
|
||||
arc.set_up_polar_conversion(&k).unwrap();
|
||||
let original = arc.base.gw_nurb().unwrap().clone();
|
||||
let point = arc.base.evaluate_point(interval.mid()).unwrap();
|
||||
let solutions = ellipse_global_point_solve::<()>(&k, &arc, &interval, point, 1e-8)
|
||||
.expect("a known point on the quarter circle is found");
|
||||
assert!(!solutions.is_empty());
|
||||
assert!(solutions.iter().any(|s| {
|
||||
interval.contains_value(s.start.parameters[0], 0.0)
|
||||
&& arc
|
||||
.base
|
||||
.evaluate_point(s.start.parameters[0])
|
||||
.unwrap()
|
||||
.distance(point)
|
||||
<= 1e-8
|
||||
}));
|
||||
assert_eq!(arc.base.gw_nurb(), Some(&original));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn angle_param_conversion() {
|
||||
let t = PolarConversion::angle_param_to_nurb_param(0.0, 1.0, 0.0, 90.0, 45.0).expect("t");
|
||||
|
||||
+169
-1
@@ -20,6 +20,10 @@
|
||||
//! | [`Plane::make_new_plane_ellipse`] | `MakeNewPlaneEllipse` |
|
||||
//!
|
||||
//! Deviations:
|
||||
//! - **Analytic contacts.** A clipped line or ellipse whose whole extent is below
|
||||
//! the geometric tolerance is returned as a point curve, including a trailing
|
||||
//! interval at the source curve's end. Parameter width alone does not bound its
|
||||
//! physical size, and a contact must not become a short edge.
|
||||
//! - **Plane first with a torus (opt-in, not upstream).** With
|
||||
//! `fixes.plane_first_torus_section_exact` (WR-721, #2405) `GlobalSurfaceIntersect` takes a
|
||||
//! torus's own plane section (`Torus::intersect_with_plane_owned_into`, its orders and UV
|
||||
@@ -65,7 +69,7 @@
|
||||
//! [`SolidError::Unported`].
|
||||
|
||||
use crate::curve::curve::p8::CurveIntersectOptions;
|
||||
use crate::curve::curve::Curve;
|
||||
use crate::curve::curve::{Curve, CurveKind};
|
||||
use crate::curve::ellipse::{Ellipse, EllipseOptions};
|
||||
use crate::curve::line::{BoundedLineOptions, Line};
|
||||
use crate::error::{ignore_status, ignored, Result, SolidError, SM_ERR_WARNING};
|
||||
@@ -1080,6 +1084,34 @@ impl Plane {
|
||||
let mut new_curve = curve.copy(k)?;
|
||||
// may snap sIvl by tol to existing knots (status ignored)
|
||||
ignore_status(new_curve.trim(k, &mut trim_ivl, TrimMode::Notify))?;
|
||||
// Classify the whole analytic interval, not just its endpoints or
|
||||
// parameter width: a closed circle has coincident ends but is not a
|
||||
// point. The line length and ellipse's radius/angle bound enclose
|
||||
// every point, so this keeps the contact within the given tolerance.
|
||||
// Signed radii and line scales change orientation, not size. Use
|
||||
// their magnitudes and the caller's tolerance without a zero floor.
|
||||
let extent = match new_curve.kind() {
|
||||
CurveKind::Line(line) => Some(
|
||||
line.line_vector().length()
|
||||
* line.line_scale().abs()
|
||||
* line.step_interval().length().abs(),
|
||||
),
|
||||
CurveKind::Ellipse(ellipse) => {
|
||||
let radius = max2(ellipse.x_radius().abs(), ellipse.y_radius().abs());
|
||||
Some(min2(
|
||||
2.0 * radius,
|
||||
radius * ellipse.step_interval().length().abs() * (PI / 180.0),
|
||||
))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let point_contact = tol_3d.is_finite()
|
||||
&& extent.is_some_and(|extent| extent.is_finite() && extent < tol_3d);
|
||||
if point_contact {
|
||||
let interval = new_curve.natural_interval();
|
||||
let point = new_curve.evaluate_point(k, interval.mid())?;
|
||||
new_curve = Curve::from(BSplineCurve::create_degenerate_curve(3, point)?);
|
||||
}
|
||||
curves_3d.push(new_curve);
|
||||
}
|
||||
|
||||
@@ -1847,6 +1879,142 @@ mod tests {
|
||||
assert!(!more);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clipping_preserves_a_large_arc_on_a_short_parameter_interval() {
|
||||
let k = Kernel::default();
|
||||
let interval = Extent1d::from_min_max(0.999_999_999_998_075_2, 1.0);
|
||||
let mut arc = Ellipse::new_circle(
|
||||
&k,
|
||||
Vec3::ZERO,
|
||||
Vec3::X,
|
||||
Vec3::Y,
|
||||
Extent1d::from_min_max(0.0, 90.0),
|
||||
2.0,
|
||||
&EllipseOptions::default(),
|
||||
)
|
||||
.unwrap();
|
||||
arc.edit_parameterization(&k, &interval, true, None)
|
||||
.unwrap();
|
||||
let curve = Curve::from(arc);
|
||||
let output = rect()
|
||||
.trim_curve_to_plane_domain(&k, &dom(), &curve, 1e-6)
|
||||
.unwrap();
|
||||
assert_eq!(output.len(), 1);
|
||||
assert_eq!(output[0].natural_interval(), interval);
|
||||
for t in [interval.min(), interval.mid(), interval.max()] {
|
||||
assert_eq!(
|
||||
output[0].evaluate_point(&k, t).unwrap(),
|
||||
curve.evaluate_point(&k, t).unwrap()
|
||||
);
|
||||
}
|
||||
assert!(!output[0].is_degenerate(&k, 1e-6, None).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clipping_keeps_the_size_of_signed_analytic_geometry() {
|
||||
let k = Kernel::default();
|
||||
let plane = rect();
|
||||
for sign in [-1.0, 1.0] {
|
||||
let circle = Curve::from(
|
||||
Ellipse::new_circle(
|
||||
&k,
|
||||
Vec3::ZERO,
|
||||
Vec3::X,
|
||||
Vec3::Y,
|
||||
Extent1d::from_min_max(0.0, 360.0),
|
||||
sign * 2.0,
|
||||
&EllipseOptions::default(),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
let line = Curve::from(Line::bounded(
|
||||
Vec3::ZERO,
|
||||
Vec3::X,
|
||||
&Extent1d::from_min_max(-1.0, 1.0),
|
||||
&BoundedLineOptions {
|
||||
scale: sign * 2.0,
|
||||
..BoundedLineOptions::default()
|
||||
},
|
||||
));
|
||||
for curve in [line, circle] {
|
||||
let result = plane
|
||||
.trim_curve_to_plane_domain(&k, &dom(), &curve, 1e-6)
|
||||
.unwrap();
|
||||
assert_eq!(result.len(), 1);
|
||||
let interval = result[0].natural_interval();
|
||||
let a = result[0].evaluate_point(&k, interval.min()).unwrap();
|
||||
let b = result[0].evaluate_point(&k, interval.mid()).unwrap();
|
||||
assert!(a.distance(b) > 1.0, "the signed curve retains its extent");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clipped_sub_tolerance_tails_are_point_contacts() {
|
||||
let k = Kernel::default();
|
||||
let plane = rect();
|
||||
let tolerance = 1e-6;
|
||||
let line = Curve::from(
|
||||
Line::create_line_segment(
|
||||
3,
|
||||
Vec3::new(-10.0, 0.0, 0.0),
|
||||
Vec3::new(-5.0 + 1e-10, 0.0, 0.0),
|
||||
None,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
let arc = Curve::from(
|
||||
Ellipse::new_circle(
|
||||
&k,
|
||||
Vec3::new(-5.0, 0.0, 0.0),
|
||||
-Vec3::X,
|
||||
Vec3::Y,
|
||||
Extent1d::from_min_max(0.0, 90.0 + 1e-8),
|
||||
2.0,
|
||||
&EllipseOptions::default(),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
for curve in [line, arc] {
|
||||
let contacts = plane
|
||||
.trim_curve_to_plane_domain(&k, &dom(), &curve, tolerance)
|
||||
.unwrap();
|
||||
assert_eq!(contacts.len(), 1, "keep the boundary contact");
|
||||
let contact = &contacts[0];
|
||||
let interval = contact.natural_interval();
|
||||
let start = contact.evaluate_point(&k, interval.min()).unwrap();
|
||||
let end = contact.evaluate_point(&k, interval.max()).unwrap();
|
||||
assert_eq!(start, end, "a contact has no edge extent");
|
||||
assert!((start.x + 5.0).abs() < tolerance);
|
||||
assert_eq!(
|
||||
contact
|
||||
.evaluate(&k, interval.evaluate(0.5), 1, false, false)
|
||||
.unwrap()[1],
|
||||
Vec3::ZERO
|
||||
);
|
||||
}
|
||||
|
||||
// Endpoint coincidence alone does not make a contact: retain the full
|
||||
// circle inside the rectangle, including its nonzero tangent.
|
||||
let circle = Curve::from(
|
||||
Ellipse::new_circle(
|
||||
&k,
|
||||
Vec3::ZERO,
|
||||
Vec3::X,
|
||||
Vec3::Y,
|
||||
Extent1d::from_min_max(0.0, 360.0),
|
||||
2.0,
|
||||
&EllipseOptions::default(),
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
let curves = plane
|
||||
.trim_curve_to_plane_domain(&k, &dom(), &circle, tolerance)
|
||||
.unwrap();
|
||||
assert_eq!(curves.len(), 1);
|
||||
assert!(!curves[0].is_degenerate(&k, tolerance, None).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trims_curves_lines_and_ellipses() {
|
||||
let k = Kernel::default();
|
||||
|
||||
Vendored
BIN
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
solidcore/crates/solidcore/tests/kernel_cases/booleans/fragmented-hemisphere-cut-at-hex-corners.json
Vendored
+264
@@ -0,0 +1,264 @@
|
||||
{
|
||||
"area": "booleans",
|
||||
"case": "fragmented-hemisphere-cut-at-hex-corners",
|
||||
"sources": [
|
||||
"SolidCraft user report: resized hemisphere cut"
|
||||
],
|
||||
"what": "A circumradius-50 regular hexagonal prism (z=0..50) is cut by a lower hemisphere of radius 68 centred at z = 71. The quarter-disc profile is split at radial distances 25 and 50. The radial-50 band meets the prism only at hex corners; no small circular tail is a distinct boundary. Expected roof z(r)=71-sqrt(68^2-r^2). The volume is the exact polar integral over six hexagonal wedges: V=(R+3)A-2*pi*R^3/3+4F, A=3*sqrt(3)*s^2/2, d=sqrt(3)*s/2, a=sqrt(R^2-d^2), b=sqrt(R^2-s^2), phi=asin(s/(2a)), F=R^3*atan(R/(sqrt(3)*b))-(3*a^2*d/2+d^3)*phi-d^2*b/(2*sqrt(3)), with s = 50 and R = 68. The hemisphere axis is shifted +5e-11 mm in X to exercise a distinct clipping event at an almost coincident corner. This changes the exact volume from the centred formula by at most A*delta*(50+delta)/sqrt(R^2-(50+delta)^2)<4e-7 mm^3, using the maximum derivative of the spherical roof. This bound is far below the grading tolerance; the positive shift is part of the input, not a boolean retry.",
|
||||
"bodies": {},
|
||||
"calls": [
|
||||
{
|
||||
"inputs": [],
|
||||
"op": "extrude",
|
||||
"outputs": [
|
||||
"b1"
|
||||
],
|
||||
"params": {
|
||||
"profile": [
|
||||
{
|
||||
"line": {
|
||||
"start": [
|
||||
50.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
25.0,
|
||||
43.30127018922193,
|
||||
0.0
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"line": {
|
||||
"start": [
|
||||
25.0,
|
||||
43.30127018922193,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
-25.0,
|
||||
43.30127018922193,
|
||||
0.0
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"line": {
|
||||
"start": [
|
||||
-25.0,
|
||||
43.30127018922193,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
-50.0,
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"line": {
|
||||
"start": [
|
||||
-50.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
-25.0,
|
||||
-43.30127018922193,
|
||||
0.0
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"line": {
|
||||
"start": [
|
||||
-25.0,
|
||||
-43.30127018922193,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
25.0,
|
||||
-43.30127018922193,
|
||||
0.0
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"line": {
|
||||
"start": [
|
||||
25.0,
|
||||
-43.30127018922193,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
50.0,
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"direction": [
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"distance": 50,
|
||||
"draft": 0,
|
||||
"corner": "curvature",
|
||||
"cap": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"op": "revolve",
|
||||
"outputs": [
|
||||
"b2"
|
||||
],
|
||||
"params": {
|
||||
"profile": [
|
||||
{
|
||||
"line": {
|
||||
"start": [
|
||||
5e-11,
|
||||
0.0,
|
||||
71.0
|
||||
],
|
||||
"end": [
|
||||
34.00000000005,
|
||||
58.88972745734183,
|
||||
71.0
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"arc": {
|
||||
"center": [
|
||||
5e-11,
|
||||
0.0,
|
||||
71.0
|
||||
],
|
||||
"axis": [
|
||||
-0.8660254037844386,
|
||||
0.5,
|
||||
0.0
|
||||
],
|
||||
"reference": [
|
||||
-0.5,
|
||||
-0.8660254037844386,
|
||||
0.0
|
||||
],
|
||||
"radius": 68.0,
|
||||
"start": 3.141592653589793,
|
||||
"end": 3.886288438457613
|
||||
}
|
||||
},
|
||||
{
|
||||
"arc": {
|
||||
"center": [
|
||||
5e-11,
|
||||
0.0,
|
||||
71.0
|
||||
],
|
||||
"axis": [
|
||||
-0.8660254037844386,
|
||||
0.5,
|
||||
0.0
|
||||
],
|
||||
"reference": [
|
||||
-0.5,
|
||||
-0.8660254037844386,
|
||||
0.0
|
||||
],
|
||||
"radius": 68.0,
|
||||
"start": 3.886288438457613,
|
||||
"end": 4.33591136746237
|
||||
}
|
||||
},
|
||||
{
|
||||
"arc": {
|
||||
"center": [
|
||||
5e-11,
|
||||
0.0,
|
||||
71.0
|
||||
],
|
||||
"axis": [
|
||||
-0.8660254037844386,
|
||||
0.5,
|
||||
0.0
|
||||
],
|
||||
"reference": [
|
||||
-0.5,
|
||||
-0.8660254037844386,
|
||||
0.0
|
||||
],
|
||||
"radius": 68.0,
|
||||
"start": 4.33591136746237,
|
||||
"end": 4.71238898038469
|
||||
}
|
||||
},
|
||||
{
|
||||
"line": {
|
||||
"start": [
|
||||
5e-11,
|
||||
0.0,
|
||||
3.0
|
||||
],
|
||||
"end": [
|
||||
5e-11,
|
||||
0.0,
|
||||
71.0
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"origin": [
|
||||
5e-11,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"direction": [
|
||||
0,
|
||||
0,
|
||||
1
|
||||
],
|
||||
"angle": 6.283185307179586,
|
||||
"cap": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
"b1",
|
||||
"b2"
|
||||
],
|
||||
"op": "boolean",
|
||||
"outputs": [
|
||||
"b3"
|
||||
],
|
||||
"params": {
|
||||
"op": "subtract"
|
||||
}
|
||||
}
|
||||
],
|
||||
"result": [
|
||||
{
|
||||
"body": "Bowl",
|
||||
"made": "b3"
|
||||
}
|
||||
],
|
||||
"expected": {
|
||||
"source": "analytic",
|
||||
"method": "Closed-form polar integral of the spherical roof over the regular hexagon; derivation in what. The +5e-11 mm X shift changes the exact volume by <4e-7 mm^3 (roof derivative bound in what), below the grading tolerance.",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 73854.71109795274,
|
||||
"tolerance": {
|
||||
"rel": 1e-06
|
||||
}
|
||||
},
|
||||
"operation_expressible": true,
|
||||
"expressible": true,
|
||||
"why_not": [],
|
||||
"ungraded": []
|
||||
}
|
||||
@@ -20,7 +20,7 @@
|
||||
}
|
||||
},
|
||||
"booleans": {
|
||||
"cases": 124,
|
||||
"cases": 125,
|
||||
"chain": {
|
||||
"1": 9,
|
||||
"121": 1,
|
||||
@@ -28,7 +28,7 @@
|
||||
"16": 3,
|
||||
"193": 1,
|
||||
"28": 1,
|
||||
"3": 2,
|
||||
"3": 3,
|
||||
"31": 1,
|
||||
"4": 3,
|
||||
"5": 1,
|
||||
@@ -39,9 +39,9 @@
|
||||
"8": 6,
|
||||
"9": 2
|
||||
},
|
||||
"expressible": 109,
|
||||
"expressible": 110,
|
||||
"not_expressible": 12,
|
||||
"operation_expressible": 112,
|
||||
"operation_expressible": 113,
|
||||
"reasons": {
|
||||
"a final body built or changed by SolidCraft (its own geometry or a change to the kernel's record)": 10,
|
||||
"refused or failed in SolidCraft, not in a kernel operation": 2
|
||||
|
||||
Reference in New Issue
Block a user