Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b4489f52b |
No files matched your search
Generated
+3
-2
@@ -4051,15 +4051,16 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "solidcore"
|
||||
version = "1.0.1"
|
||||
source = "git+https://github.com/tangency-labs/solidcore?rev=8ddce901671f1cebd9214a31d21895d725e886c8#8ddce901671f1cebd9214a31d21895d725e886c8"
|
||||
source = "git+https://github.com/tangency-labs/solidcore?rev=99ed57c4d4f4cd77d942ac6cd2e4d09f612acb20#99ed57c4d4f4cd77d942ac6cd2e4d09f612acb20"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcore-record"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/tangency-labs/solidcore?rev=f5e8e9a29e0c9b7cf6c4040a5bc8878382b3c62f#f5e8e9a29e0c9b7cf6c4040a5bc8878382b3c62f"
|
||||
source = "git+https://github.com/tangency-labs/solidcore?rev=99ed57c4d4f4cd77d942ac6cd2e4d09f612acb20#99ed57c4d4f4cd77d942ac6cd2e4d09f612acb20"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
+2
-2
@@ -36,10 +36,10 @@ solidcraft-3dx-client = { path = "crates/threedx-client" }
|
||||
# move the rev on its own commit. For a
|
||||
# local checkout, add `[patch."https://github.com/tangency-labs/solidcore"] solidcore = { path =
|
||||
# "../solidcore/crates/solidcore" }` to a `.cargo/config.toml` outside the repository.
|
||||
solidcore = { git = "https://github.com/tangency-labs/solidcore", rev = "8ddce901671f1cebd9214a31d21895d725e886c8" }
|
||||
solidcore = { git = "https://github.com/tangency-labs/solidcore", rev = "99ed57c4d4f4cd77d942ac6cd2e4d09f612acb20" }
|
||||
# Geometry as data (solidcore's layers §1, "Record"): the B-rep record, its structural validation
|
||||
# and the native body file. Pinned like solidcore; for a local checkout, patch it as above.
|
||||
solidcore-record = { git = "https://github.com/tangency-labs/solidcore", rev = "f5e8e9a29e0c9b7cf6c4040a5bc8878382b3c62f" }
|
||||
solidcore-record = { git = "https://github.com/tangency-labs/solidcore", rev = "99ed57c4d4f4cd77d942ac6cd2e4d09f612acb20" }
|
||||
# test tooling: a dev-dependency only (xtask/src/layers.rs)
|
||||
solidcraft-testkit = { path = "crates/testkit" }
|
||||
|
||||
|
||||
@@ -318,7 +318,7 @@ fn a_model_saved_under_another_check_mode_is_evaluated_again() {
|
||||
/// opened from its model or evaluated anew. Two edges of a 20 mm box rounded r2 (a mitre at the
|
||||
/// corner (20, 0, 20)), saved; opened, the upright edge there rounded r3: the corner all three
|
||||
/// rounded at once make (SMLib's setback corner, kernel `three_rounds_at_a_corner`), 15 faces and
|
||||
/// 7928.142563938 mm³, the same body as the design evaluated anew.
|
||||
/// 7927.72428251348 mm³, the same body as the design evaluated anew.
|
||||
#[test]
|
||||
fn a_later_round_after_opening_blends_the_saved_rounds_again() {
|
||||
let mut built = Session::default();
|
||||
@@ -336,7 +336,7 @@ fn a_later_round_after_opening_blends_the_saved_rounds_again() {
|
||||
let b = &st.body("Box").unwrap().body;
|
||||
let v = solidcraft_engine::kernel::measure(b).unwrap().volume;
|
||||
assert_eq!(b.face_count(), 15);
|
||||
assert!((v - 7928.142563938).abs() < 1e-6, "{v}");
|
||||
assert!((v - 7927.72428251348).abs() < 1e-6, "{v}"); // solidcore#2382's setback corner
|
||||
}
|
||||
assert_eq!(model("later round", &cached), model("later round", &rebuilt));
|
||||
let _ = std::fs::remove_file(&path);
|
||||
|
||||
@@ -1627,7 +1627,57 @@ pub(crate) fn section(plane: &Plane, m: &Mesh, face: Option<u32>) -> Vec<LinkGeo
|
||||
let tight = size * 1e-4;
|
||||
let checkable = exact.len().saturating_mul(close.len()) <= 4_000_000;
|
||||
let strays = |v: &[LinkGeom]| exact.iter().any(|p| !v.iter().any(|g| plain_distance(g, *p).is_none_or(|d| d <= tight)));
|
||||
if plain(&close) && (close.len() <= lossy.len() || (checkable && strays(&lossy) && !strays(&close))) { close } else { lossy }
|
||||
let fitted = if plain(&close) && (close.len() <= lossy.len() || (checkable && strays(&lossy) && !strays(&close))) { close } else { lossy };
|
||||
join_rounds(fitted, size * 1e-5)
|
||||
}
|
||||
|
||||
/// Where the fit left a line and an arc ends apart by at most `tol`, both end at one point. A
|
||||
/// line can swallow an arc's first chord (a mesh crossing just past the tangent line), and
|
||||
/// [`refine_round`] moves an arc's ends but not the lines': the outline would not close.
|
||||
fn join_rounds(mut fitted: Vec<LinkGeom>, tol: f64) -> Vec<LinkGeom> {
|
||||
for i in 0..fitted.len() {
|
||||
let Some(&LinkGeom::Arc { c, mut a, mut b }) = fitted.get(i) else { continue };
|
||||
let r = c.dist(a);
|
||||
for end in [&mut a, &mut b] {
|
||||
for curve in &mut fitted {
|
||||
let LinkGeom::Line(p, q) = curve else { continue };
|
||||
let (near, far) = if p.dist(*end) <= q.dist(*end) { (p, q) } else { (q, p) };
|
||||
if near.dist(*end) <= tol {
|
||||
let joint = line_meets_round(*far, *near, c, r, tol);
|
||||
*near = joint;
|
||||
*end = joint;
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(arc) = fitted.get_mut(i) {
|
||||
*arc = LinkGeom::Arc { c, a, b };
|
||||
}
|
||||
}
|
||||
fitted
|
||||
}
|
||||
|
||||
/// Where a line from `far` ending near `near` meets the circle (`c`, `r`): the tangent point when
|
||||
/// the line runs into the circle there, within `tol` of it over the arc's first chord, else its
|
||||
/// crossing nearest `near`, else the circle's point nearest `near`.
|
||||
fn line_meets_round(far: Vec2, near: Vec2, c: Vec2, r: f64, tol: f64) -> Vec2 {
|
||||
let nearest = |[x, y]: [Vec2; 2]| if x.dist(near) <= y.dist(near) { x } else { y };
|
||||
let to_centre = c - far;
|
||||
let span = to_centre.len();
|
||||
if let Some(unit) = to_centre.normalized().filter(|_| span > r) {
|
||||
let reach = (span * span - r * r).sqrt();
|
||||
let (along, side) = (unit * (reach * reach / span), unit.perp() * (reach * r / span));
|
||||
let tangent = nearest([far + along + side, far + along - side]);
|
||||
if tangent.dist(near) <= tol + (2.0 * r * tol).sqrt() {
|
||||
return tangent;
|
||||
}
|
||||
}
|
||||
let Some(dir) = (near - far).normalized() else { return near };
|
||||
let foot = far + dir * (c - far).dot(dir);
|
||||
let half = r * r - (foot - c).len2();
|
||||
if half >= 0.0 {
|
||||
return nearest([foot + dir * half.sqrt(), foot - dir * half.sqrt()]);
|
||||
}
|
||||
(near - c).normalized().map_or(near, |unit| c + unit * r)
|
||||
}
|
||||
|
||||
/// How far `p` lies from a line, arc or circle; None for any other curve.
|
||||
|
||||
@@ -109,11 +109,10 @@ fn fillet_as_wide_as_a_face_rounds_it_off() {
|
||||
|
||||
/// The owner's ruling of 2026-10-30: two edges of a 20 mm box rounded r2 in one fillet (they
|
||||
/// meet in a mitre at the corner (20, 0, 20), the upright edge there sharp), then the upright edge
|
||||
/// r3 in a second fillet, whose tangent chain takes the mitre's seam with it. The corner is the
|
||||
/// one solidcore 8ddce9016 builds for three rounds of different radii there: one patch, 10
|
||||
/// faces, 7928.743041456134 mm³ (the fork's setback corner, 15 faces and 7928.142563938, waits
|
||||
/// for solidcore#2310). The r2 rounds keep their names, and the r3 round and the corner's patch
|
||||
/// are the second fillet's.
|
||||
/// r3 in a second fillet, whose tangent chain takes the mitre's seam with it. The corner is
|
||||
/// solidcore's setback corner for three rounds of different radii there (solidcore#2382): 15
|
||||
/// faces, 7927.724282512234 mm³. The r2 rounds keep their names, and the r3 round and the
|
||||
/// corner's six patches are the second fillet's.
|
||||
#[test]
|
||||
fn a_later_round_at_a_mitred_corner_is_blended_with_the_rounds_there() {
|
||||
let mut s = Session::default();
|
||||
@@ -135,17 +134,17 @@ fn a_later_round_at_a_mitred_corner_is_blended_with_the_rounds_there() {
|
||||
let second = run(&mut s, "solid.fillet", json!({"edges": [[20, 0, 10]], "radius": 3}))["feature"].as_u64().unwrap();
|
||||
assert!(s.model.results.iter().all(|r| r.error.is_none()), "{:?}", s.model.results);
|
||||
let v = volume(&mut s);
|
||||
assert!((v - 7928.743041456134).abs() < 1e-6, "{v}");
|
||||
assert!((v - 7927.724282512234).abs() < 1e-6, "{v}");
|
||||
let faces = run(&mut s, "model.faces", json!({"body": "Box"}))["faces"].as_array().cloned().unwrap_or_default();
|
||||
assert_eq!(faces.len(), 10);
|
||||
assert_eq!(faces.len(), 15);
|
||||
let after: Vec<String> = rounds.iter().map(|p| named(&mut s, *p)).collect();
|
||||
assert_eq!(after, before);
|
||||
// The r3 round (halfway up, at 45°) and the corner's patches: the faces the second fillet made.
|
||||
let k3 = 3.0 - 4.5_f64.sqrt();
|
||||
assert!(named(&mut s, solidcore_record::Vec3::new(20.0 - k3, k3, 10.0)).starts_with(&format!("F{second}:blend:")));
|
||||
let made = faces.iter().filter(|f| f["name"].as_str().is_some_and(|n| n.starts_with(&format!("F{second}:")))).count();
|
||||
// the r3 round and the corner's patch
|
||||
assert_eq!(made, 2, "{faces:?}");
|
||||
// the r3 round and the corner's six patches
|
||||
assert_eq!(made, 7, "{faces:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
+31
-34
@@ -6,7 +6,6 @@
|
||||
//! fork fixed in its own sources; each one turned on here says which fork commit it matches.
|
||||
|
||||
use std::panic::{AssertUnwindSafe, catch_unwind};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::{Mutex, PoisonError};
|
||||
|
||||
use solidcore::geom::attribute::{AttributeStore, ObjectKey};
|
||||
@@ -108,7 +107,6 @@ pub(crate) fn fixes() -> UpstreamFixes {
|
||||
f.offset_resolve_crossing_loops = true;
|
||||
f.offset_walls_touch_refused = true;
|
||||
f.offset_analytic_mate_edges = true;
|
||||
f.precise_properties_split_at_surface_knots = true;
|
||||
f.nurb_cylinder_inside_out_frame = true;
|
||||
f.shell_inner_shell_of_statuses = true;
|
||||
f.local_operation_offset_status = true;
|
||||
@@ -147,13 +145,10 @@ pub(crate) fn fixes() -> UpstreamFixes {
|
||||
f.torus_signed_gen_curve_interval = true;
|
||||
f.sheet_sweep_joins_profile_wires = true;
|
||||
f.point_solve_keeps_triangle_guess = true;
|
||||
f.fillet_setback_from_start_vertex = true;
|
||||
f.imprint_trims_edge_to_proto_vertex = true;
|
||||
f.imprint_extends_edge_to_proto_vertex = true;
|
||||
f.fillet_point_step_per_point = true;
|
||||
f.analytic_copy_keeps_parameterization = true;
|
||||
f.fillet_point_bigger_step_retry = true;
|
||||
f.fillet_through_extended_rail_end = true;
|
||||
f.precise_properties_body_length_scale = true;
|
||||
f.offset_extension_from_base = true;
|
||||
f.corner_blend_stays_inside_neighbours = true;
|
||||
@@ -174,11 +169,9 @@ pub(crate) fn fixes() -> UpstreamFixes {
|
||||
f.boolean_skips_faces_apart = true;
|
||||
f.uv_trim_checks_on_surface = true;
|
||||
f.loop_orientation_floor_from_loop_box = true;
|
||||
f.offset_extends_cones_as_themselves = true;
|
||||
f.local_operation_box_holds_new_surfaces = true;
|
||||
f.uv_trim_joins_drop_pieces = true;
|
||||
f.shell_edges_on_analytics_exact = true;
|
||||
f.ssi_no_reverse_of_empty_trace = true;
|
||||
f.ssi_pole_point_keeps_its_tangent = true;
|
||||
f.iso_curve_sliver_domain_is_degenerate = true;
|
||||
f.corner_blend_coons_patch_tangent_to_sides = true;
|
||||
@@ -230,7 +223,6 @@ pub(crate) fn fixes() -> UpstreamFixes {
|
||||
f.fillet_cleanup_removes_input_slits = true;
|
||||
f.fillet_rail_end_on_continued_surface = true;
|
||||
f.glue_vertices_joins_every_region = true;
|
||||
f.fillet_nx1_tangent_side_edge_same_convexity = true;
|
||||
f.fillet_removal_takes_ring_fillets = true;
|
||||
// The fork's commits that reached `SMLIB_REV` at eaa998a (tangency-labs/smlib#68, #69, #70).
|
||||
f.fillet_trim_inside_by_best_dot = true;
|
||||
@@ -274,9 +266,7 @@ pub(crate) fn fixes() -> UpstreamFixes {
|
||||
f.offset_keep_cavity_regions = true;
|
||||
f.offset_four_face_vertex_split = true;
|
||||
f.gap_samples_non_bspline_v_stride = true;
|
||||
f.gap_array_append_updates_longest = true;
|
||||
f.imprint_skips_collapsed_definition = true;
|
||||
f.precise_properties_trim_base_v = true;
|
||||
f.fillet_blend_curve_revolved_on_arc = true;
|
||||
f.temporary_radius_setup_errors = true;
|
||||
f.ellipse_step_interval_checks = true;
|
||||
@@ -319,7 +309,6 @@ pub(crate) fn fixes() -> UpstreamFixes {
|
||||
f.circle_silhouette_perspective_center = true;
|
||||
f.ellipse_convert_to_2d_uses_both_axes = true;
|
||||
f.circle_offset_antiparallel_normal = true;
|
||||
f.masked_array_uses_bit_shift = true;
|
||||
f.global_solver_identical_prefers_larger_when_maximizing = true;
|
||||
f.surface_continuity_checks_last_v_knot = true;
|
||||
f.surface_point_solver_propagates_bbox_failure = true;
|
||||
@@ -331,7 +320,6 @@ pub(crate) fn fixes() -> UpstreamFixes {
|
||||
f.crv_in_volume_mults_by_break_index = true;
|
||||
f.curve_bounded_surface_read_uv_type_and_implicit_outer = true;
|
||||
f.polar_box_region_to_arc_transposes_domain = true;
|
||||
f.point_set_centroid_all_blocks = true;
|
||||
f.local_solve1d_unbounded_newton_step = true;
|
||||
f.composite_curve_bbox_segment_intervals = true;
|
||||
f.knot_vector_valid_requires_span = true;
|
||||
@@ -339,14 +327,12 @@ pub(crate) fn fixes() -> UpstreamFixes {
|
||||
f.curve_property_gs_branch_bounds = true;
|
||||
f.volume_representational_end_continuities = true;
|
||||
f.integrator_propagates_failure = true;
|
||||
f.precise_properties_span_end_from_below = true;
|
||||
f.offset_open_edge_second_trim_point = true;
|
||||
f.close_loop_relates_only_coincident_edges = true;
|
||||
f.ssi_split_curves_at_singular_points = true;
|
||||
f.surface_parallel_to_vector_checks = true;
|
||||
f.fillet_round_ends_at_tangent_continuation = true;
|
||||
f.geometric_g2_continuity = true;
|
||||
f.hash_table_grow_preserves_removed_records = true;
|
||||
f.bend_volume_bbox_contains_wide_angle_and_inf_u = true;
|
||||
f.bend_volume_trim_param_box_across_period = true;
|
||||
f.point_set_tight_pbox_projects_points = true;
|
||||
@@ -377,7 +363,6 @@ pub(crate) fn fixes() -> UpstreamFixes {
|
||||
f.fillet_section_end_trim_at_creases = true;
|
||||
f.fillet_refuse_mixed_corner_crossing_rails = true;
|
||||
f.fillet_keep_features = true;
|
||||
f.fillet_blend_corner = true;
|
||||
f.fillet_exact_arc_sections = true;
|
||||
f.fillet_exact_arc_spans = true;
|
||||
// Held back: see `HELD_BACK`.
|
||||
@@ -385,11 +370,21 @@ pub(crate) fn fixes() -> UpstreamFixes {
|
||||
f.cone_unbounded_parameterized_as_nurb = true;
|
||||
f.ellipse_open_arc_keeps_end_angle = true;
|
||||
f.fillet_round_as_definition = true;
|
||||
f.fillet_round_on_round_as_definition = true;
|
||||
f.fillet_overflow_crease_along_edge = true;
|
||||
f.plane_first_torus_section_exact = true;
|
||||
f.keep_exact_pcurves = true;
|
||||
f.shell_one_construction = true;
|
||||
f.approximate_surface_checks_every_row = true;
|
||||
f.topological_vertex_has_only_edge_uses = true;
|
||||
f.planar_section_face_tolerance = true;
|
||||
f.face_swap_uv_transposes_trims = true;
|
||||
f.closed_smooth_pole_tangent_limit = true;
|
||||
// Held back: see `HELD_BACK`.
|
||||
f.fillet_round_ends_on_outline = false;
|
||||
f.fillet_setback_beside_curved_faces = true;
|
||||
f.fillet_chamfer_corner = true;
|
||||
f.brep_ray_classifies_current_trims = true;
|
||||
f.revolution_frame_from_profile_origin = true;
|
||||
f
|
||||
}
|
||||
|
||||
@@ -399,29 +394,31 @@ pub(crate) fn fixes() -> UpstreamFixes {
|
||||
/// the fork doesn't have stays on, and where solidcore builds what SMLib refuses, a result right
|
||||
/// against its closed form is a win.
|
||||
///
|
||||
/// `fillet_refuse_rounds_breaking_through` (WR-707, solidcore#2311): sound geometry refused. With it
|
||||
/// on, 11 rig cases that pass with it off, each graded against its closed form or invariants, are
|
||||
/// refused as breaking through the part (190-fillet-disc-rim-past-its-2mm-wall fails status 1001
|
||||
/// instead): every-hole-top-rim-r12-past-plate-bottom, 1080-chamfer-runs-into-openings-cut-by-them
|
||||
/// (3350, its closed form), 1080-fillet-notched-block-top-outline-r8,
|
||||
/// 324-fillet-over-hole-cut-by-overlong-cylinder, 324-fillet-over-hole-overlap,
|
||||
/// 5158-fillet-adjacent-plate-edges-as-thick-as-plate and the sloped and equal-slope basin rims of
|
||||
/// #1080 and #5835. A fix under the same flag is in progress on solidcore.
|
||||
/// `fillet_refuse_rounds_breaking_through` (WR-707, solidcore#2311): sound geometry refused. At
|
||||
/// solidcore 9856ffee5, with it on, the fillet rig's 1668-fillet-both-sides-of-thin-wall-r7 is
|
||||
/// refused as breaking through the wall into the hollow behind it ("the largest radius that fits is
|
||||
/// 6.828 mm"), where with it off it builds the closed form, 39509.938 (at 8ddce9016 it refused 11
|
||||
/// rig cases that pass with it off; at this pin the other 270 fillet rig cases come out as with it
|
||||
/// off).
|
||||
///
|
||||
/// `fillet_round_ends_on_outline` (WR-730, solidcore#2541): volumes wrong against their closed
|
||||
/// forms, and rounds too big for their faces built instead of refused. With it on, each of these
|
||||
/// passes with it off: `fillet_over_a_cliff` measures 721.832 against the closed form's 693.205;
|
||||
/// `a_round_wider_than_its_faces_overflows_onto_its_tangent_sides` 6125.719 against 6107.529;
|
||||
/// `taper_follows_the_room_along_the_edge` 4210.926 against 4195.579; the round larger than the
|
||||
/// cube in `hostile_inputs_are_errors` builds (4773.168 against the 1094.853 its refusal leaves),
|
||||
/// as do the fillets the CLI's `run_strict` and `json_out` tests need to fail; and
|
||||
/// `a_corner_round_wider_than_its_faces_overflows_and_takes_the_corners_other_edges`,
|
||||
/// `overflow_goes_first`, `corners_refuse_what_they_cannot_build` and
|
||||
/// `fillet_edge_removed_by_a_larger_overflowing_round_is_noted` are refused (too large, no room,
|
||||
/// status 1001), where with it off they build. Measured again at solidcore 9856ffee5: the same.
|
||||
#[cfg(test)]
|
||||
const HELD_BACK: &[&str] = &["fillet_refuse_rounds_breaking_through"];
|
||||
const HELD_BACK: &[&str] = &["fillet_refuse_rounds_breaking_through", "fillet_round_ends_on_outline"];
|
||||
|
||||
/// How far apart two kernels' procedural blend serials start. A body keeps the serials of the
|
||||
/// kernel that made it and may be used on any other, whose memo of solved stations is keyed by
|
||||
/// serial alone: the ranges keep one definition's stations from answering for another's. A serial
|
||||
/// only names a definition, so which range one comes from changes no answer.
|
||||
pub(crate) const BLEND_SERIALS_PER_KERNEL: u64 = 1 << 40;
|
||||
|
||||
/// A new kernel. Each takes the next range of blend serials, the first starting at 0.
|
||||
/// A new kernel, with this backend's fixes.
|
||||
fn fresh() -> Kernel {
|
||||
static MADE: AtomicU64 = AtomicU64::new(0);
|
||||
let mut k = Kernel::new(&KernelOptions::default());
|
||||
k.fixes = fixes();
|
||||
k.blend_last_serial.set(MADE.fetch_add(1, Ordering::Relaxed).wrapping_mul(BLEND_SERIALS_PER_KERNEL));
|
||||
k
|
||||
}
|
||||
|
||||
|
||||
+53
-102
@@ -1,7 +1,7 @@
|
||||
//! Fillets and chamfers on solidcore: the C API's composition round SMLib's fillet executive,
|
||||
//! ported from the fork's `capi/src/smlib_ops.cpp` (`blend_call`, `round_edges`,
|
||||
//! `chamfer_offsets`, `chamfer_edges`, `chamfer_radius`, `outer_faceuse`, `face_frame`,
|
||||
//! `sides_offsets`, `blend_failure`, `setbacks_fit`, `overflow_first`, `Picked`,
|
||||
//! `chamfer_edges`, `outer_faceuse`, `face_frame`,
|
||||
//! `blend_failure`, `setbacks_fit`, `overflow_first`, `Picked`,
|
||||
//! `size_ok`, `points_ok`, `tolerance_ok`, `law_ok`, `law_pinches`, `law_along`, `finish`,
|
||||
//! `smlib_fillet`, `smlib_chamfer`, `smlib_blend`, `smlib_blend_dropping`, `smlib_blend_setback`,
|
||||
//! `smlib_blend_setback_open`, `smlib_fillet_variable`, `smlib_chamfer_sides`) and
|
||||
@@ -31,7 +31,9 @@ use solidcore::blend::fillet_executive::p3::{ErrorInfo, FitKind, NoRoomReason};
|
||||
use solidcore::blend::fillet_executive::session::{FilletObject, FilletSession};
|
||||
use solidcore::blend::fillet_geom::p1::{FilletCornerType, FilletErrorType};
|
||||
use solidcore::blend::fillet_section::section::GN_THUMBWEIGHT_EQUAL_AREA;
|
||||
use solidcore::blend::chamfer_side::faces_along;
|
||||
use solidcore::blend::fillet_solver::p1::FilletSurfaceGeneratorType;
|
||||
use solidcore::surface::procedural_blend::chamfer::ChamferSize;
|
||||
use solidcore::geom::attribute::{Attribute, AttributeHandlers, Behavior, ids as attribute_ids};
|
||||
use solidcore::geom::vector3d::{Point3, Vec3};
|
||||
use solidcore::kernel::Kernel;
|
||||
@@ -222,30 +224,7 @@ fn mid(b: &Brep, e: EdgeId) -> solidcore::Result<f64> {
|
||||
Ok(b.edge(e)?.interval.evaluate(0.5))
|
||||
}
|
||||
|
||||
/// `chamfer_radius`: the radius of the ball whose tangent points on the two faces of `e` lie
|
||||
/// `distance` back from the edge at its midpoint, `distance * tan(a / 2)` with `a` the angle
|
||||
/// between the directions into the faces; at a cusp (a knife edge) `distance` itself, the ball
|
||||
/// whose contacts the chamfer joins. `None` where the capi's returns false.
|
||||
fn chamfer_radius(k: &Kernel, b: &mut Brep, e: EdgeId, distance: f64) -> solidcore::Result<Option<f64>> {
|
||||
let Some(u0) = edge::blend_edgeuse(k, b, e)? else { return Ok(None) };
|
||||
let Some(u1) = eu_p1::radial(b, u0)? else { return Ok(None) };
|
||||
let t = mid(b, e)?;
|
||||
let options = BinormalOptions::default();
|
||||
let Some(e0) = answer(evaluate_binormal(k, b, u0, t, false, &options))? else { return Ok(None) };
|
||||
let Some(e1) = answer(evaluate_binormal(k, b, u1, t, false, &options))? else { return Ok(None) };
|
||||
// Unitize's failure (a zero vector) is the capi's `return false`.
|
||||
let (Ok(n0), Ok(b0), Ok(b1)) = (e0.face_normal.unitize(), e0.binormal.unitize(), e1.binormal.unitize()) else { return Ok(None) };
|
||||
let c = b0.dot(b1).clamp(-1.0, 1.0);
|
||||
// At a cusp (a knife edge: the faces tangent within the half degree `GetBlendEdgeuse` calls
|
||||
// tangent, folded back on each other, the binormals the same way) a is 0 and no ball lies
|
||||
// `distance` back along both: the chamfer is the chord between the contacts of the ball of
|
||||
// radius `distance` (fork e96898d).
|
||||
let cusp = c > 0.0 && b1.dot(n0).abs() < (0.5 * std::f64::consts::PI / 180.0).sin();
|
||||
let radius = if cusp { distance } else { distance * ((1.0 - c) / (1.0 + c)).sqrt() };
|
||||
Ok((radius.is_finite() && radius > 0.0).then_some(radius))
|
||||
}
|
||||
|
||||
/// What `round_edges` makes: the capi's kinds less the chamfer, which `chamfer_offsets` makes.
|
||||
/// What `round_edges` makes.
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum Section<'a> {
|
||||
/// A circular section of constant radius (or of a variable radius's law), the turns of two
|
||||
@@ -256,9 +235,10 @@ enum Section<'a> {
|
||||
/// `fillet_gn_section_generator`), which takes off what the ball's circle does.
|
||||
Curvature,
|
||||
Chord,
|
||||
/// A chamfer, with each edge's offset of its second surface (`chamfer_offsets`' `second`;
|
||||
/// the sizes are the first's).
|
||||
Chamfer(&'a [f64]),
|
||||
/// A chamfer of the sizes asked: one for all the edges or one per edge, and for each edge
|
||||
/// the face its first size is along (empty: the blend edge use's). The kernel makes their
|
||||
/// offsets, one pair for a tangent chain of one size.
|
||||
Chamfer { sizes: &'a [ChamferSize], along: &'a [FaceId] },
|
||||
}
|
||||
|
||||
/// The faces of `b`, for `made_faces`: a face whose id is new after the operation was made by
|
||||
@@ -373,13 +353,16 @@ fn blend_failure_unsized(info: &ErrorInfo<FilletObject>, e: SolidError, corner:
|
||||
}
|
||||
|
||||
/// `e` ([`Error::TooLarge`]) with the point at the middle of `edge` of `b`, the edge its size was
|
||||
/// found for; an equal chamfer's size (its ball's radius, [`chamfer_radius`]) as its distance.
|
||||
/// Where the edge or its point can't be read the size stays as it is, with no point (a chamfer's
|
||||
/// size then is not a distance, so it is left a plain refusal).
|
||||
fn sized_on(k: &Kernel, b: &mut Brep, edge: Option<EdgeId>, section: Section<'_>, e: Error) -> solidcore::Result<Error> {
|
||||
/// found for; an equal chamfer's size (an offset, [`Section::Chamfer`]) as its distance, by the
|
||||
/// edge's own ratio of the distance asked to the offset the kernel made of it (`chamfered`: per
|
||||
/// edge of an equal chamfer, the two). Where the edge or its point can't be read the size stays
|
||||
/// as it is, with no point (a chamfer's size then is not a distance, so it is left a plain
|
||||
/// refusal).
|
||||
fn sized_on(k: &Kernel, b: &mut Brep, edge: Option<EdgeId>, section: Section<'_>, chamfered: &[(EdgeId, f64, f64)], e: Error) -> solidcore::Result<Error> {
|
||||
let Error::TooLarge { size, kind, why, .. } = e else { return Ok(e) };
|
||||
let chamfer = matches!(section, Section::Chamfer { .. }) && kind == crate::FitKind::Radius;
|
||||
let Some(edge) = edge else {
|
||||
return Ok(if matches!(section, Section::Chamfer(_)) && kind == crate::FitKind::Radius { Error::NoRoom } else { e });
|
||||
return Ok(if chamfer { Error::NoRoom } else { e });
|
||||
};
|
||||
let at = match b.edge(edge) {
|
||||
Ok(rec) => match rec.curve.as_ref() {
|
||||
@@ -388,11 +371,13 @@ fn sized_on(k: &Kernel, b: &mut Brep, edge: Option<EdgeId>, section: Section<'_>
|
||||
},
|
||||
Err(_) => None,
|
||||
};
|
||||
if matches!(section, Section::Chamfer(_)) && kind == crate::FitKind::Radius {
|
||||
// The ball's radius is linear in the chamfer's distance along an edge.
|
||||
return Ok(match chamfer_radius(k, b, edge, 1.0)? {
|
||||
Some(per_mm) => Error::TooLarge { size: size / per_mm, kind: crate::FitKind::Distance, at, why },
|
||||
None => Error::NoRoom,
|
||||
if chamfer {
|
||||
// The offset is linear in the distance along an edge.
|
||||
return Ok(match chamfered.iter().find(|(e, ..)| *e == edge) {
|
||||
Some(&(_, distance, offset)) if offset > 0.0 => {
|
||||
Error::TooLarge { size: size / offset * distance, kind: crate::FitKind::Distance, at, why }
|
||||
}
|
||||
_ => Error::NoRoom,
|
||||
});
|
||||
}
|
||||
Ok(Error::TooLarge { size, kind, at, why })
|
||||
@@ -450,6 +435,8 @@ fn blend_edges_stopping(
|
||||
let before = face_set(b)?;
|
||||
// The edge a sized refusal names ([`blend_failure`]), read after the session lets `b` go.
|
||||
let mut fit_edge = None;
|
||||
// An equal chamfer's edges, each with its distance and the offset the kernel made of it.
|
||||
let mut chamfered: Vec<(EdgeId, f64, f64)> = Vec::new();
|
||||
let done = {
|
||||
let mut s = FilletSession::new(k, b)?;
|
||||
// `SetSweptTurns`: read as the corners are made (`fixes.fillet_blend_corner`).
|
||||
@@ -471,12 +458,21 @@ fn blend_edges_stopping(
|
||||
s.prepare_per_edge_constant_radius(k, edges, sizes, &[FilletSurfaceGeneratorType::BlendCurve], 2, GN_THUMBWEIGHT_EQUAL_AREA)
|
||||
}
|
||||
(Section::Chord, _) => s.prepare_per_edge_constant_distance(k, edges, sizes, &[FilletSurfaceGeneratorType::Circular], 1, 1.0),
|
||||
(Section::Chamfer(second), _) => chamfer_parameters(k, &mut s, edges, sizes, second),
|
||||
(Section::Chamfer { sizes, along }, _) => s.prepare_chamfers_along(k, edges, sizes, along).map(|offsets| {
|
||||
for (i, (&e, [r, _])) in edges.iter().zip(offsets).enumerate() {
|
||||
let size = sizes.get(if sizes.len() == 1 { 0 } else { i });
|
||||
if let Some(&ChamferSize::Distances([d, d2])) = size
|
||||
&& d == d2
|
||||
{
|
||||
chamfered.push((e, d, r));
|
||||
}
|
||||
}
|
||||
}),
|
||||
};
|
||||
// `AddSetBackVertex` for each set-back vertex, before the corners are made: a set-back
|
||||
// vertex is no Ball (`IsSetBackVertex`, read by `ClassifyBall`), so its corner is the one
|
||||
// the setback is set on.
|
||||
if !matches!(section, Section::Chamfer(_)) {
|
||||
if !matches!(section, Section::Chamfer { .. }) {
|
||||
for sb in setbacks {
|
||||
s.add_set_back_vertex(sb.vertex);
|
||||
}
|
||||
@@ -529,7 +525,7 @@ fn blend_edges_stopping(
|
||||
Err(refusal) => {
|
||||
b.refresh_cached_topology();
|
||||
return Ok(Err(match refusal {
|
||||
Err(e @ Error::TooLarge { .. }) => Err(sized_on(k, b, fit_edge, section, e)?),
|
||||
Err(e @ Error::TooLarge { .. }) => Err(sized_on(k, b, fit_edge, section, &chamfered, e)?),
|
||||
r => r,
|
||||
}));
|
||||
}
|
||||
@@ -571,16 +567,6 @@ fn blend_edges_stopping(
|
||||
Ok(Ok(()))
|
||||
}
|
||||
|
||||
/// `chamfer_offsets`' `SetFilletParameters(b, es, kinds, solvers, first, NULL, -1, -1, -1, -1,
|
||||
/// 1, 1.0, &second)` with every kind `SM_FSG_LINEAR` and every solver `SM_FS_CONST_RADIUS`, as the
|
||||
/// fork's `SetFilletParameters` runs it (its `pOptSecondRadii`, fork f653ac9).
|
||||
fn chamfer_parameters(k: &mut Kernel, s: &mut FilletSession<'_>, edges: &[EdgeId], first: &[f64], second: &[f64]) -> solidcore::Result<()> {
|
||||
if edges.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
s.prepare_per_edge_constant_radius_with_second(k, edges, first, second, &[FilletSurfaceGeneratorType::Linear], 1, 1.0)
|
||||
}
|
||||
|
||||
/// The corners' part of `round_edges` / `chamfer_offsets`: a chamfer's corners bevelled; each
|
||||
/// setback set on its vertex's corner (`GetFilletCornerOfVertex`), which must be an
|
||||
/// `SM_FCR_N_x_N_CONVEX` corner or a blend corner whose event is the setback (`SM_FCR_BLEND` with
|
||||
@@ -592,7 +578,7 @@ fn corners(
|
||||
setbacks: &[SetBack],
|
||||
) -> solidcore::Result<std::result::Result<Vec<usize>, std::result::Result<usize, Error>>> {
|
||||
let ids = s.fillet_corners()?;
|
||||
if matches!(section, Section::Chamfer(_)) {
|
||||
if matches!(section, Section::Chamfer { .. }) {
|
||||
for id in ids {
|
||||
s.set_corner_bevel(id, true)?;
|
||||
}
|
||||
@@ -936,16 +922,11 @@ fn blend_call(
|
||||
Blend::Curvature if !one_wedge(k, &mut w, &picked)? => return Ok(Err(Error::Unported(CURVATURE_MIXED_WEDGES))),
|
||||
Blend::Curvature => Section::Curvature,
|
||||
Blend::Chord => Section::Chord,
|
||||
// `chamfer_edges`: each edge's offsets from its setback at its midpoint.
|
||||
// `chamfer_edges`: the sizes asked; the kernel makes each edge's offsets.
|
||||
Blend::Chamfer => {
|
||||
let mut radii = Vec::with_capacity(picked.len());
|
||||
for (&e, &d) in picked.iter().zip(&each) {
|
||||
match chamfer_radius(k, &mut w, e, d)? {
|
||||
Some(r) => radii.push(r),
|
||||
None => return Ok(Err(Error::Status(INVALID_INPUT))),
|
||||
}
|
||||
}
|
||||
return Ok(match blend_edges(k, &mut w, &picked, &radii, Section::Chamfer(&radii), None, &[], None)? {
|
||||
let chamfers: Vec<ChamferSize> = each.iter().map(|&d| ChamferSize::Distances([d, d])).collect();
|
||||
let section = Section::Chamfer { sizes: &chamfers, along: &[] };
|
||||
return Ok(match blend_edges(k, &mut w, &picked, &[], section, None, &[], None)? {
|
||||
Ok(()) => finish(w, solid_in)?,
|
||||
Err(Ok(_)) => Err(Error::Unsupported),
|
||||
Err(Err(e)) => Err(e),
|
||||
@@ -1165,39 +1146,6 @@ fn face_frame(k: &Kernel, b: &mut Brep, u: EdgeuseId) -> solidcore::Result<Optio
|
||||
Ok(Some((normal * (sign / len), at.binormal)))
|
||||
}
|
||||
|
||||
/// `sides_offsets`: the surface offsets of `e` (its blend edge use's surface first) at which
|
||||
/// `chamfer_offsets` cuts a chamfer `d1` back along the face whose outward normal at the edge's
|
||||
/// midpoint leans most along `up`, and `second` back along the other (or at the angle `second`
|
||||
/// to the first face). With b0, b1 the directions into the faces and θ the angle between them,
|
||||
/// setbacks s0, s1 lie at offsets r0 = (s1 - s0 cos θ) / sin θ and r1 = (s0 - s1 cos θ) / sin θ;
|
||||
/// an angle φ to the first face gives s1 = s0 sin φ / sin(θ + φ).
|
||||
fn sides_offsets(k: &Kernel, b: &mut Brep, e: EdgeId, d1: f64, second: ChamferSecond, up: Vec3) -> solidcore::Result<Option<(f64, f64)>> {
|
||||
let Some(u0) = edge::blend_edgeuse(k, b, e)? else { return Ok(None) };
|
||||
let Some(u1) = eu_p1::radial(b, u0)? else { return Ok(None) };
|
||||
let (Some((n0, b0)), Some((n1, b1))) = (face_frame(k, b, u0)?, face_frame(k, b, u1)?) else { return Ok(None) };
|
||||
let c = b0.dot(b1).clamp(-1.0, 1.0);
|
||||
let sn = (1.0 - c * c).max(0.0).sqrt();
|
||||
if !(sn > 1e-6) {
|
||||
return Ok(None);
|
||||
}
|
||||
let mut s0 = d1;
|
||||
let mut s1 = match second {
|
||||
ChamferSecond::Distance(d) => d,
|
||||
ChamferSecond::Angle(phi) => {
|
||||
let d = (c.acos() + phi).sin();
|
||||
if !(d > 1e-9) {
|
||||
return Ok(None);
|
||||
}
|
||||
d1 * phi.sin() / d
|
||||
}
|
||||
};
|
||||
if n1.dot(up) > n0.dot(up) {
|
||||
std::mem::swap(&mut s0, &mut s1);
|
||||
}
|
||||
let (r0, r1) = ((s1 - s0 * c) / sn, (s0 - s1 * c) / sn);
|
||||
Ok((r0.is_finite() && r1.is_finite() && r0 > 0.0 && r1 > 0.0).then_some((r0, r1)))
|
||||
}
|
||||
|
||||
/// `smlib_chamfer_sides`: the edges nearest `points` chamfered `distance` back along the face
|
||||
/// whose outward normal leans most along `first`, and by `second` on the other, with bevelled
|
||||
/// corners.
|
||||
@@ -1219,17 +1167,20 @@ pub(crate) fn chamfer_sides(body: &Body, points: &[[f64; 3]], tolerance: f64, di
|
||||
Err(m) => return Ok(Err(edge_miss(m))),
|
||||
};
|
||||
let solid_in = brep_p2::is_manifold_solid(&w)?;
|
||||
let (mut r0, mut r1) = (Vec::with_capacity(picked.len()), Vec::with_capacity(picked.len()));
|
||||
// The first size is along the face leaning most along `up`.
|
||||
let mut along = Vec::with_capacity(picked.len());
|
||||
for &e in &picked {
|
||||
match sides_offsets(k, &mut w, e, distance, second, up)? {
|
||||
Some((a, b)) => {
|
||||
r0.push(a);
|
||||
r1.push(b);
|
||||
}
|
||||
match answer(faces_along(k, &mut w, e, up))? {
|
||||
Some([(f0, l0), (f1, l1)]) => along.push(if l1 > l0 { f1 } else { f0 }),
|
||||
None => return Ok(Err(Error::Status(INVALID_INPUT))),
|
||||
}
|
||||
}
|
||||
Ok(match blend_edges(k, &mut w, &picked, &r0, Section::Chamfer(&r1), None, &[], None)? {
|
||||
let size = match second {
|
||||
ChamferSecond::Distance(d) => ChamferSize::Distances([distance, d]),
|
||||
ChamferSecond::Angle(angle) => ChamferSize::DistanceAngle { distance, angle },
|
||||
};
|
||||
let section = Section::Chamfer { sizes: std::slice::from_ref(&size), along: &along };
|
||||
Ok(match blend_edges(k, &mut w, &picked, &[], section, None, &[], None)? {
|
||||
Ok(()) => finish(w, solid_in)?,
|
||||
Err(Ok(_)) => Err(Error::Unsupported),
|
||||
Err(Err(e)) => Err(e),
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
|
||||
use solidcore::EntityRef;
|
||||
use solidcore::SolidError;
|
||||
use solidcore::error::offset::OffsetFailure;
|
||||
use solidcore::error::{ErrorKind, Reason};
|
||||
use solidcore::geom::attribute::{Attribute, Behavior, ids};
|
||||
use solidcore::geom::axis2_placement::Axis2Placement;
|
||||
use solidcore::geom::object::Shape;
|
||||
@@ -29,7 +31,6 @@ use solidcore::nurbs::b_spline_curve::ControlPointForm;
|
||||
use solidcore::nurbs::b_spline_surface::p1::SurfParamType;
|
||||
use solidcore::nurbs::b_spline_surface_n_lib::p2::OrientType;
|
||||
use solidcore::nurbs::knots::Side;
|
||||
use solidcore::offset::construction::ShellRefusal;
|
||||
use solidcore::offset::offset_executive::p2::{OffsetOptions, ShellOptions, local_operation_simple, offset_brep_simple, shell_brep_simple};
|
||||
use solidcore::offset::offset_faces::{OffsetFacesExecutive, OffsetFacesRefusal};
|
||||
use solidcore::query::api_queries::{face_bounding_box, face_classify_uv, face_closest_point_in_shape, face_internal_point};
|
||||
@@ -50,21 +51,23 @@ type Out<T> = std::result::Result<T, Error>;
|
||||
/// `SM_ERR_INVALID_INPUT`.
|
||||
const INVALID_INPUT: i32 = 1007;
|
||||
|
||||
/// `SM_ERR_OFFSET_WALLS_TOUCH` (fork a759622), which `status` (smlib_shim.h) reports as
|
||||
/// `SMLIB_ERR_WALLS_TOUCH`.
|
||||
const OFFSET_WALLS_TOUCH: i32 = 1042;
|
||||
|
||||
/// `status` (smlib_shim.h): an offset whose walls touch is [`Error::WallsTouch`]; a vertex moved
|
||||
/// past an edge's other end (`SM_ERR_OFFSET_EDGE_VANISHES`, fork 34318f4) is
|
||||
/// `SMLIB_ERR_UNSUPPORTED`; a wall whose face's offset folds, which solidcore's one construction
|
||||
/// refuses (`fixes.shell_one_construction`, solidcore#2367), is [`Error::WallFolds`] with the
|
||||
/// largest wall it names; any other status as the blends pass it on.
|
||||
/// largest wall it names; any other status as the blends pass it on. solidcore names an offset's
|
||||
/// refusals by their kind and reason (its errors in its own terms, solidcore#2654), whichever
|
||||
/// status the pinned C++ gives them.
|
||||
fn status(e: SolidError) -> Error {
|
||||
match e {
|
||||
SolidError::Upstream { code: OFFSET_WALLS_TOUCH, .. } => Error::WallsTouch,
|
||||
SolidError::ShellRefused { refusal: ShellRefusal::Fold, size, .. } => Error::WallFolds { largest: size },
|
||||
e if e.is_offset_edge_vanishes() => Error::Unsupported,
|
||||
e => blend_status(e),
|
||||
if let SolidError::Failed(f) = &e
|
||||
&& let Reason::Offset(OffsetFailure::Folds { largest, .. }) = f.reason
|
||||
{
|
||||
return Error::WallFolds { largest: Some(largest) };
|
||||
}
|
||||
match e.kind() {
|
||||
ErrorKind::OffsetWallsTouch => Error::WallsTouch,
|
||||
ErrorKind::OffsetEdgeVanishes => Error::Unsupported,
|
||||
_ => blend_status(e),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -546,9 +546,8 @@ fn offset_faces_close_a_step_wall() {
|
||||
/// Rig case shell/4492's rounded box: four mitred rounds, r 3, come off a 40 x 30 x 10 box's top
|
||||
/// together and follow it. From height 0 the outline is the 34 x 24 top moved out by the round's
|
||||
/// w(z) = sqrt(9 - (z - axis)^2), or 3 below the axis. With the fork's values at +19, and bodies
|
||||
/// whose rounds go all the way down from -7 and are cut short from -7.5 (solidcore#2027). At -9
|
||||
/// solidcore refuses to put the rounds back (solidcore#2020, open): SolidCraft says it does not
|
||||
/// make that move.
|
||||
/// whose rounds go all the way down from -7 and are cut short from -7.5 (solidcore#2027). At -9,
|
||||
/// which solidcore refused before 9856ffee5 (solidcore#2020), it builds the closed form too.
|
||||
#[test]
|
||||
fn mitred_rounds_follow_a_moved_top() {
|
||||
use super::blend::{Corners, blend};
|
||||
@@ -574,6 +573,7 @@ fn mitred_rounds_follow_a_moved_top() {
|
||||
(-7.0, [6, 12, 8]),
|
||||
(-7.5, [6, 12, 8]),
|
||||
(-8.0, [6, 12, 8]),
|
||||
(-9.0, [6, 12, 8]),
|
||||
(-9.5, [6, 12, 8]),
|
||||
];
|
||||
for (d, n) in moves {
|
||||
@@ -582,7 +582,6 @@ fn mitred_rounds_follow_a_moved_top() {
|
||||
assert_eq!(counts(&moved).unwrap(), n, "d {d}");
|
||||
assert!(rel(v, analytic(d)) < 1e-6, "d {d}: {v} vs {}", analytic(d));
|
||||
}
|
||||
assert!(matches!(offset_faces(&rounded, &[top], 0.1, -9.0), Err(Error::Unsupported)));
|
||||
}
|
||||
|
||||
/// `moved_faces`: drafts so small they leave a plane or cylinder where it was still moved a face
|
||||
|
||||
+23
-140
@@ -24,21 +24,20 @@
|
||||
|
||||
use std::sync::PoisonError;
|
||||
|
||||
use solidcore::EntityRef;
|
||||
use solidcore::curve::curve::Curve;
|
||||
use solidcore::geom::extent2d::Extent2d;
|
||||
use solidcore::geom::geom_utility::p2::ContinuityType;
|
||||
use solidcore::geom::object::Shape;
|
||||
use solidcore::geom::tol::USE_DEFAULT;
|
||||
use solidcore::geom::vector2d::Point2;
|
||||
use solidcore::geom::vector3d::{Point3, Vec3};
|
||||
use solidcore::kernel::Kernel;
|
||||
use solidcore::nurbs::b_spline_surface::p1::SurfParamType;
|
||||
use solidcore::nurbs::b_spline_surface_n_lib::p2::EvaluateFlags;
|
||||
use solidcore::nurbs::b_spline_surface_n_lib::p2::OrientType;
|
||||
use solidcore::query::api_queries::{edge_closest_point, face_closest_point_in_shape, face_internal_point};
|
||||
use solidcore::query::brep::{BrepQuery, LiveBrepObjects};
|
||||
use solidcore::surface::cone::p1::Cone;
|
||||
use solidcore::surface::extend::Continuity;
|
||||
use solidcore::surface::side::BoxSide;
|
||||
use solidcore::surface::surface::Surface;
|
||||
use solidcore::surface::surface::SurfaceKind;
|
||||
use solidcore::topology::brep::p1 as brep_p1;
|
||||
@@ -54,6 +53,7 @@ use solidcore::topology::face::{p1 as face_p1, p2 as face_p2, p4 as face_p4};
|
||||
use solidcore::topology::loopuse::{loop_edges, loop_loopuses, notify_ref};
|
||||
use solidcore::topology::topology_traverser::Marks;
|
||||
use solidcore::topology::{Brep, BrepId, EdgeId, FaceId, LoopId, edge};
|
||||
use solidcore::{EntityRef, SolidError};
|
||||
|
||||
use super::blend::{p3, status};
|
||||
use super::sweep::build_faces;
|
||||
@@ -454,17 +454,17 @@ pub(crate) fn untrim_face(body: &Body, point: [f64; 3], tol: f64, holes: Option<
|
||||
/// grows in u), and whether it is at the box's max.
|
||||
#[derive(Clone, Copy)]
|
||||
struct Side {
|
||||
which: SurfParamType,
|
||||
which: BoxSide,
|
||||
u: bool,
|
||||
high: bool,
|
||||
}
|
||||
|
||||
/// `SIDES`.
|
||||
const SIDES: [Side; 4] = [
|
||||
Side { which: SurfParamType::UMin, u: true, high: false },
|
||||
Side { which: SurfParamType::UMax, u: true, high: true },
|
||||
Side { which: SurfParamType::VMin, u: false, high: false },
|
||||
Side { which: SurfParamType::VMax, u: false, high: true },
|
||||
Side { which: BoxSide::UMin, u: true, high: false },
|
||||
Side { which: BoxSide::UMax, u: true, high: true },
|
||||
Side { which: BoxSide::VMin, u: false, high: false },
|
||||
Side { which: BoxSide::VMax, u: false, high: true },
|
||||
];
|
||||
|
||||
/// `SAMPLES`.
|
||||
@@ -546,55 +546,6 @@ fn side_edge(k: &Kernel, b: &Brep, surf: &Surface, dom: &Extent2d, s: Side, edge
|
||||
e.filter(|_| d <= on)
|
||||
}
|
||||
|
||||
/// `across`: how far `ext` (the face's surface extended past side `s` of its box `dom`, on the
|
||||
/// same parameters) runs past that side, measured across it through the side's middle, by
|
||||
/// 16-point Gauss-Legendre quadrature on 64 pieces. Negative when it can't be evaluated.
|
||||
fn across(k: &Kernel, ext: &Surface, dom: &Extent2d, s: Side) -> f64 {
|
||||
const X: [f64; 8] = [
|
||||
0.0950125098376374,
|
||||
0.2816035507792589,
|
||||
0.4580167776572274,
|
||||
0.6178762444026438,
|
||||
0.755404408355003,
|
||||
0.8656312023878318,
|
||||
0.9445750230732326,
|
||||
0.9894009349916499,
|
||||
];
|
||||
const W: [f64; 8] = [
|
||||
0.1894506104550685,
|
||||
0.1826034150449236,
|
||||
0.1691565193950025,
|
||||
0.1495959888165767,
|
||||
0.1246289712555339,
|
||||
0.0951585116824928,
|
||||
0.0622535239386479,
|
||||
0.0271524594117541,
|
||||
];
|
||||
const PIECES: usize = 64;
|
||||
let Ok(bx) = ext.natural_uv_domain_with_nurb(k) else { return -1.0 };
|
||||
let mid = on_side(dom, s, 0.5);
|
||||
let from = if s.u { mid.x } else { mid.y };
|
||||
let to = match (s.u, s.high) {
|
||||
(true, true) => bx.max().x,
|
||||
(true, false) => bx.min().x,
|
||||
(false, true) => bx.max().y,
|
||||
(false, false) => bx.min().y,
|
||||
};
|
||||
let h = (to - from) / PIECES as f64;
|
||||
let mut length = 0.0;
|
||||
for piece in 0..PIECES {
|
||||
let c = from + (piece as f64 + 0.5) * h;
|
||||
for i in 0..16 {
|
||||
let (x, w) = if i < 8 { (-X[i], W[i]) } else { (X[i - 8], W[i - 8]) };
|
||||
let t = c + 0.5 * h * x;
|
||||
let uv = if s.u { Point2::new(t, mid.y) } else { Point2::new(mid.x, t) };
|
||||
let Some((_, du, dv)) = eval(k, ext, uv) else { return -1.0 };
|
||||
length += 0.5 * h.abs() * w * if s.u { du } else { dv }.length();
|
||||
}
|
||||
}
|
||||
length
|
||||
}
|
||||
|
||||
/// `cone_continued`: a cone's or cylinder's face continued naturally `distance` past side `s` of
|
||||
/// its box `dom`, as the analytic itself over a wider STEP domain (degrees round the axis,
|
||||
/// height along it). `None` where the cone can't be widened so (more than a turn, or past the
|
||||
@@ -656,82 +607,16 @@ fn same_patch(k: &Kernel, p: &Surface, q: &Surface, tol: f64) -> bool {
|
||||
}
|
||||
|
||||
/// `extended_by`: the surface `surf` (whose face is its whole box `dom`) extended past side `s` as
|
||||
/// `ct` says, so that it runs `distance` past that side measured across it ([`across`]), within
|
||||
/// 1e-9 of it. `CreateExtendedSurface` takes its distance as an estimate, and how far it then runs
|
||||
/// is increasing in what it is asked: a bracketed solve on that (each ask scaled by the distance
|
||||
/// over what it gave until two asks fall either side, then Illinois false position). An extension
|
||||
/// that moves the face's side off its parameters, or a target not met, is [`Error::NotExact`],
|
||||
/// never the surface as it came out.
|
||||
fn extended_by(k: &Kernel, surf: &Surface, dom: &Extent2d, s: Side, ct: ContinuityType, distance: f64, model_tol: f64) -> Result<Surface> {
|
||||
const BRACKET_TRIES: usize = 40;
|
||||
const SOLVE_TRIES: usize = 100;
|
||||
let tol = 1e-9 * distance;
|
||||
// How far asking `ask` runs, and the surface it makes.
|
||||
let try_ask = |ask: f64| -> Result<(Surface, f64)> {
|
||||
let tried = surf.create_extended_surface_dir(k, s.which, ask, ct).map_err(status)?;
|
||||
// The face's side stays where it was on the extended surface's parameters.
|
||||
let mid = on_side(dom, s, 0.5);
|
||||
let stays = matches!((point_at(k, surf, mid), point_at(k, &tried, mid)), (Some(was), Some(now)) if (was - now).length() <= model_tol);
|
||||
if !stays {
|
||||
return Err(Error::NotExact);
|
||||
}
|
||||
let got = across(k, &tried, dom, s);
|
||||
if got.is_finite() && got > 0.0 { Ok((tried, got)) } else { Err(Error::NotExact) }
|
||||
};
|
||||
// Asks that run short of and past `distance`, by f = got - distance.
|
||||
let (mut short, mut past) = (None, None);
|
||||
let mut ask = distance;
|
||||
for _ in 0..BRACKET_TRIES {
|
||||
let (made, got) = try_ask(ask)?;
|
||||
let f = got - distance;
|
||||
if f.abs() <= tol {
|
||||
return Ok(made);
|
||||
}
|
||||
if f < 0.0 {
|
||||
short = Some((ask, f));
|
||||
} else {
|
||||
past = Some((ask, f));
|
||||
}
|
||||
if short.is_some() && past.is_some() {
|
||||
break;
|
||||
}
|
||||
ask *= distance / got;
|
||||
/// `how` says, so that it runs `distance` past that side measured across it, exactly: solidcore's
|
||||
/// one extension ([`Surface::extended_by_distance`]), which keeps the source's own part bit for bit
|
||||
/// and meets the distance within 1e-9 of it. A distance it cannot meet exactly is
|
||||
/// [`Error::NotExact`], never a surface that falls short or runs past.
|
||||
fn extended_by(k: &Kernel, surf: &Surface, dom: &Extent2d, s: Side, how: Continuity, distance: f64) -> Result<Surface> {
|
||||
match surf.extended_by_distance(k, dom, s.which, how, distance) {
|
||||
Ok(e) => Ok(e.surface),
|
||||
Err(SolidError::ExtendRefused { .. } | SolidError::NotConverged { .. }) => Err(Error::NotExact),
|
||||
Err(e) => Err(status(e)),
|
||||
}
|
||||
let (Some((mut lo, mut flo)), Some((mut hi, mut fhi))) = (short, past) else { return Err(Error::NotExact) };
|
||||
// Illinois: false position, halving the value at the end that stays when the same end moves
|
||||
// twice running.
|
||||
#[derive(PartialEq)]
|
||||
enum Moved {
|
||||
Neither,
|
||||
Lo,
|
||||
Hi,
|
||||
}
|
||||
let mut moved = Moved::Neither;
|
||||
for _ in 0..SOLVE_TRIES {
|
||||
let x = hi - fhi * (hi - lo) / (fhi - flo);
|
||||
if !(x > lo.min(hi) && x < lo.max(hi)) {
|
||||
break;
|
||||
}
|
||||
let (made, got) = try_ask(x)?;
|
||||
let f = got - distance;
|
||||
if f.abs() <= tol {
|
||||
return Ok(made);
|
||||
}
|
||||
if f < 0.0 {
|
||||
(lo, flo) = (x, f);
|
||||
if moved == Moved::Lo {
|
||||
fhi *= 0.5;
|
||||
}
|
||||
moved = Moved::Lo;
|
||||
} else {
|
||||
(hi, fhi) = (x, f);
|
||||
if moved == Moved::Hi {
|
||||
flo *= 0.5;
|
||||
}
|
||||
moved = Moved::Hi;
|
||||
}
|
||||
}
|
||||
Err(Error::NotExact)
|
||||
}
|
||||
|
||||
/// `smlib_extend_sheet`: the one-face sheet `sheet`, whose face is its surface's whole parameter
|
||||
@@ -787,15 +672,13 @@ pub(crate) fn extend_sheet(sheet: &Body, point: [f64; 3], tol: f64, distance: f6
|
||||
if (nl.x - dl.x).abs() > ptol || (nl.y - dl.y).abs() > ptol || (nh.x - dh.x).abs() > ptol || (nh.y - dh.y).abs() > ptol {
|
||||
return Ok(Err(Error::Unsupported));
|
||||
}
|
||||
let ct = match how {
|
||||
Continuation::Tangent => ContinuityType::G1,
|
||||
Continuation::Curvature => ContinuityType::G1G2,
|
||||
Continuation::Natural => ContinuityType::CInfinity,
|
||||
let continuity = match how {
|
||||
Continuation::Tangent => Continuity::Tangent,
|
||||
Continuation::Curvature => Continuity::Curvature,
|
||||
Continuation::Natural => Continuity::Natural,
|
||||
};
|
||||
// CreateExtendedSurface takes its distance as an estimate (a natural parabola falls 2.5%
|
||||
// short, a G2 run-off of a circle runs more than twice as far): the distance asked of it
|
||||
// is solved for until the extension measures `distance` across the side's middle.
|
||||
let extended = match extended_by(k, &surf, &dom, grow, ct, distance, model_tol) {
|
||||
// The extension runs `distance` across the side, exactly (solidcore's one extension).
|
||||
let extended = match extended_by(k, &surf, &dom, grow, continuity, distance) {
|
||||
Ok(e) => e,
|
||||
Err(e) => return Ok(Err(e)),
|
||||
};
|
||||
|
||||
@@ -55,7 +55,7 @@ pub enum Error {
|
||||
/// middle of the edge it was found for, and why there is no room, when the refusal names them.
|
||||
TooLarge { size: f64, kind: FitKind, at: Option<[f64; 3]>, why: Option<RoomReason> },
|
||||
/// A shell, offset or thicken whose wall is thicker than a face curves: the face's offset
|
||||
/// folds (solidcore's `ShellRefused`, `ShellRefusal::Fold`). The largest wall that works,
|
||||
/// folds (solidcore's `OffsetFailure::Folds`). The largest wall that works,
|
||||
/// when solidcore names one.
|
||||
WallFolds { largest: Option<f64> },
|
||||
/// SMLib cannot build this exactly yet (a variable radius whose law has a corner).
|
||||
|
||||
@@ -486,19 +486,6 @@ fn bodies_move_between_kernels_and_calls_run_side_by_side() {
|
||||
}
|
||||
}
|
||||
|
||||
/// Each kernel has its own range of blend serials: a body keeps the serials of the kernel that
|
||||
/// made it, and another kernel's memo of solved stations must not take them for its own.
|
||||
#[test]
|
||||
fn kernels_never_issue_the_same_blend_serial() {
|
||||
let range = core::BLEND_SERIALS_PER_KERNEL as usize;
|
||||
let ranges = core::run("outer", |outer| {
|
||||
let inner = core::run("inner", |inner| Ok(inner.blend_last_serial.get() as usize)).unwrap_or_default();
|
||||
Ok([outer.blend_last_serial.get() as usize / range, inner / range])
|
||||
});
|
||||
let [outer, inner] = ranges.unwrap();
|
||||
assert_ne!(outer, inner);
|
||||
}
|
||||
|
||||
/// A panic in a call running beside another costs neither: the panicking call's kernel is thrown
|
||||
/// away and the other call and later ones go on.
|
||||
#[test]
|
||||
|
||||
@@ -7,9 +7,8 @@
|
||||
# (`gh run download <run> -R tangency-labs/solidcraft -n <artifact> -D <dir>/<artifact>` for junit
|
||||
# and junit-gpu-1..4), run `cargo xtask known-failing --junit <dir>`, then drop each FIXED line and
|
||||
# add each REGRESSION line with its cause, from the test's failure message.
|
||||
solidcraft-cli::action_matrix::action_matrix_sample 3 baseline actions fail, the same 3 as on C++: 2 shells (Bad Loop Orient on issue-616-lid-ribs; status 1001 on filleted-shelled-box), 1 fillet (SmSurfOfRevolution Test #7 on oblique_cut_of_cylinder)
|
||||
solidcraft-cli::model_cache::a_later_round_after_opening_blends_the_saved_rounds_again the later r3 round at the r2 mitre builds solidcore's 10-face corner (7928.743041), the test wants SMLib f916a8d's setback corner (smlib#104), which solidcore ports in solidcore#2310: 15 faces, 7928.142563938
|
||||
solidcraft-cli::naming_corpus::corpus_designs_name_alike_every_build_and_fail_where_a_reference_is_gone oracle/83-boundary-loop-thicken does not load: step 8, thicken status 1001, as on C++
|
||||
solidcraft-cli::action_matrix::action_matrix_sample 2 baseline actions fail: 2 shells (Bad Loop Orient on issue-616-lid-ribs; status 1001 on filleted-shelled-box)
|
||||
solidcraft-cli::naming_corpus::corpus_designs_name_alike_every_build_and_fail_where_a_reference_is_gone oracle/83-boundary-loop-thicken does not load: step 8, the offset of face 0 by -1 is not proved regular, so thicken is refused with no size named
|
||||
solidcraft-doc::eval::cost_tests::a_tap_marking_searches_only_the_bodies_near_it main's mesh-based version (#10303) not taken; the exact replacement is to come
|
||||
solidcraft-doc::eval::cost_tests::a_tap_marking_still_searches_the_bodies_within_its_reach main's mesh-based version (#10303) not taken; the exact replacement is to come
|
||||
solidcraft-engine::cmd::edit::tests::inserting_variable_fillet_control_preserves_parameters_at_existing_positions solidcore refuses a variable fillet whose law has a corner (solidcore#430)
|
||||
@@ -21,19 +20,12 @@ solidcraft-engine::measure_centre_tests::a_fillet_round_a_boss_reports_its_radiu
|
||||
solidcraft-engine::tests::chord_and_variable_fillets its 2 → 4 → 2 waist: solidcore refuses a variable fillet whose law has a corner (solidcore#430)
|
||||
solidcraft-engine::tests::fillet_fit_taper_closes_to_a_point fillet status 1001 on the taper fit (radius 16 closing to 0), as on C++
|
||||
solidcraft-engine::tests::mirror_placed::dragging_a_mirrored_part_meshes_its_copies_once a copy of a copy drawn from its first copy's triangles (#10101) is triangulated differently from a fresh mesh of it, where truck's meshes are the same under a rigid move, as on SMLib
|
||||
solidcraft-engine::tests::shell_tangent_chain_that_opens_every_face_explains_how_to_keep_walls the 2 mm shell of the box with every edge rounded r 1.5, its front face open, fails SM_ERR_INVALID_INPUT at solidcore 8ddce9016: the wall is thicker than the rounds and their Ball corners (BallPatch, fillet_blend_corner), whose inward offsets collapse
|
||||
solidcraft-kernel::blend::corner_tests::a_round_ending_in_an_earlier_round_matches_both_together fillet status 1001 on the round that ends in the earlier one, as on C++
|
||||
solidcraft-kernel::blend::corner_tests::corner_blends_hold_at_any_scale at 0.01x the arc case's rounds remove 72.783 (scaled back) against 72.713 at 1x, as on C++
|
||||
solidcraft-kernel::blend_corner::tests::issue2422_vertical_after_separate_bottom_rounds the vertical edge after the separate bottom rounds is refused as smooth ("its faces meet tangentially, so there is nothing to round"): the rounds leave it tangent at solidcore 8ddce9016
|
||||
solidcraft-kernel::blend_corner::tests::three_rounds_at_a_corner the corner's variable-radius transition between rounds of different radii meets its neighbours at 0.144 degrees once its sections are the balls' touch circles (fillet_gn_section_generator, solidcore#2262; 0 with the great circles), as on SMLib d01d7b0; the corner step replaces that transition
|
||||
solidcraft-kernel::blend_variable::tests::variable_radius_on_a_box_edge the variable round r 2 to 5 along a box edge fails status 1001 at solidcore 8ddce9016
|
||||
solidcraft-kernel::blend_variable::tests::variable_radius_with_a_corner_through_a_middle_point solidcore refuses a variable fillet whose law has a corner (solidcore#430)
|
||||
solidcraft-kernel::blend_wrong_way_tests::a_chamfer_that_would_grow_the_part_on_a_convex_edge_is_refused at solidcore 8ddce9016 the 0.5 chamfer of the ball cut's rim is no longer refused as too large: the session builds no face (made no change), its tangent chain running into an unblended edge
|
||||
solidcraft-kernel::corner_g2::tests::a_long_setback_keeps_the_g2_patch_inside_the_box at set backs 11 to 15 the G2 corner patch bulges past the box, as on C++
|
||||
solidcraft-kernel::corner_g2::tests::curvature_rounds_meet_at_a_g2_corner the corner between curvature rounds jumps 2.07 in curvature over 538 stations on solidcore; the test wants it curvature continuous, as SMLib f916a8d's setback corner (smlib#104), which solidcore ports in solidcore#2310 builds it
|
||||
solidcraft-kernel::corner_g2::tests::the_box_corner_g2_patch_is_a_closed_solid_curvature_continuous_to_its_neighbours the fitted G2 corner patch bulges 1.7e-5 past the box, as on C++
|
||||
solidcraft-kernel::fillet_setback_tests::a_setback_not_past_the_radius_is_refused_naming_the_least solidcore builds setback 5.5 (15 faces) where SMLib f916a8d's setback corner (smlib#104), which solidcore ports in solidcore#2310 refuses it naming the least that builds; nothing names the least setback on solidcore until then
|
||||
solidcraft-kernel::modify_history_tests::fillet_variable_and_chord the variable round r 2 to 5 along the cube's edge fails status 1001 at solidcore 8ddce9016, as variable_radius_on_a_box_edge
|
||||
solidcraft-kernel::native::tests::box_and_fillet_have_stable_wire_records the box-edge round's wire record differs from the fixture at solidcore 8ddce9016 (a value 3 is 5): the fixture is re-blessed (NATIVE_BREP_BLESS=1) once the change is shown right
|
||||
solidcraft-smlib::ops::tests::a_variable_round_with_a_corner_in_its_law_is_its_balls_union solidcore refuses a variable fillet whose law has a corner (solidcore#430)
|
||||
solidcraft-ui-egui::preselect_tests::variable_fillet_round_trip the dialog's variable round fails status 1001 at solidcore 8ddce9016, as variable_radius_on_a_box_edge
|
||||
@@ -4,8 +4,6 @@
|
||||
1080-fillet-equal-slope-basin-1-mm-rim-loop-4 [fail] continuity_order: got {'body': 'Slab', 'edges': 'blend', 'order': 'g0', want >= g1 at every blend seam
|
||||
1080-fillet-sloped-basin-1-mm-rim-r4-2-edges [fail] continuity_order: got {'body': 'Slab', 'edges': 'blend', 'order': 'g0', want >= g1 at every blend seam
|
||||
1080-fillet-sloped-basin-1-mm-rim-r4-4-edges [fail] continuity_order: got {'body': 'Slab', 'edges': 'blend', 'order': 'g0', want >= g1 at every blend seam
|
||||
2893-chamfer-distance-and-angle-past-the-wall-refused [fail] refused, but not because "angle": step 2 (solid.chamfer): SMLib chamfer: operation: SMLib rejected the input (SM_ERR_INVALID_INPUT)
|
||||
2893-chamfer-two-distances-elliptical-rim [fail] refused, but not because "not supported yet": step 3 (solid.chamfer): SMLib chamfer: operation: SMLib failed (status 1001) (tangent propagation blended the picked edges' tangent chain with them; with it off, tangent_chain:
|
||||
4782-fillet-bar-edge-wider-than-both-faces [fail] continuity_order: got Box: no null seams want >= g1 at every blend seam
|
||||
9-fillet-corner-curvature-with-reduce-fit [unsupported] solid.fillet builds `corner: g2` with `fit: exact` only
|
||||
9-fillet-corner-curvature-with-taper-fit [unsupported] solid.fillet builds `corner: g2` with `fit: exact` only
|
||||
@@ -16,9 +14,8 @@ cov-fillet-g2-mixed-corner [unsupported] refused as on SMLib d01d7b0: a G2 corne
|
||||
cov-fillet-g2-oblique-three-edge-corner [unsupported] refused on solidcore: its three edges meet at different wedge angles, and one curvature fillet takes the first edge's equal-area thumbweight for every edge (solidcore#2250, issuecomment-6563419897); SMLib d01d7b0 fails it too
|
||||
cov-fillet-g2-pocket-corner [fail] continuity_order: G1 at one seam where the corner patch meets a contact face (g2 wanted), as on SMLib d01d7b0
|
||||
cov-junction-boss-tilt-20-root-round [fail] fillet status 1001 on a cylinder tilted 20 degrees into the plate, as on SMLib
|
||||
cov-junction-boss-tilt-40-root-round [fail] fillet status 1001 on a cylinder tilted 40 degrees into the plate, as on SMLib
|
||||
cov-junction-tee-equal-round [fail] the result is not a closed solid, as on SMLib: equal pipes' intersection is two plane ellipses crossing, a corner the old corner code cannot join (the corner step)
|
||||
cov-junction-y-junction-120-round [unsupported] refused as on SMLib: the faces between the three intersection curves narrow to nothing at the two triple points, so the room check trips (the corner step)
|
||||
report-drafted-peg-front-and-concave-rounds-together-r2 [unsupported] step 13 (solid.fillet): invalid input: not supported yet: the fillet meets a corner of this kind, which SMLib's blends do not handle yet (where its ed
|
||||
report-fillet-curvature-three-edges-at-bar-corner [fail] continuity_order: G1 at one seam where the corner patch meets a contact face (g2 wanted), as on SMLib d01d7b0 (see KNOWN_FAILING)
|
||||
report-fillet-curvature-three-edges-at-bar-corner-solver-residues [fail] continuity_order: G1 at one seam where the corner patch meets a contact face (g2 wanted), as on SMLib d01d7b0 (see KNOWN_FAILING)
|
||||
report-fillet-curvature-three-edges-at-bar-corner-solver-residues-outside-setback [unsupported] solidcore#2697: solidcore's own filled corner builds this to its closed form (7e-11); SolidCraft's least-squares fit misses curvature by 4.02e-3 against 2.5e-3 at this pin
|
||||
@@ -2,9 +2,9 @@
|
||||
"sources": [],
|
||||
"what": "the running part with every tier: back wall, boss, slot, spline window, tapped holes, rounds, chamfers, shell, vents, and a modelled M16 thread on the boss",
|
||||
"source": "analytic",
|
||||
"method": "10/008 less the M16 groove on the boss: its cylinder runs from the r1 root round (z 21, a shoulder: the groove stops w = 0.932735 clear of it) to the 1 mm top chamfer (z 29, run out through it, a free end); the groove's root r 6.917 stays outside the boss's 1.5 wall's inside (r 6.5), so it cuts the wall only. Bbox: the part's",
|
||||
"method": "10/008 less the M16 groove on the boss: its cylinder runs down to the r1 root round's shoulder (End::Shoulder), where it is trimmed at z 21, up to the 1 mm top chamfer (z 29, run out through it, a free end); the groove's root r 6.917 stays outside the boss's 1.5 wall's inside (r 6.5), so it cuts the wall only. Bbox: the part's",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 15138.532607554,
|
||||
"volume_mm3": 15111.5575,
|
||||
"bbox": {
|
||||
"min": [
|
||||
0,
|
||||
|
||||
@@ -1822,12 +1822,12 @@
|
||||
},
|
||||
"ladder/11-cumulative-parts/004-housing": {
|
||||
"measured": {
|
||||
"area_mm2": 21105.287553425696,
|
||||
"area_mm2": 21156.778657103445,
|
||||
"body_count": 1,
|
||||
"edges": 210,
|
||||
"faces": 78,
|
||||
"vertices": 139,
|
||||
"volume_mm3": 15138.519861452509
|
||||
"edges": 218,
|
||||
"faces": 81,
|
||||
"vertices": 144,
|
||||
"volume_mm3": 15111.545095719644
|
||||
},
|
||||
"seconds": 4.128625132,
|
||||
"status": "pass"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"known_failure": "the variable round r 2 to 5 along a box edge fails status 1001 at solidcore 8ddce9016 (as blend_variable::tests::variable_radius_on_a_box_edge in tests/KNOWN_FAILING_TESTS)",
|
||||
"method": "box 40x30x20 with its top front edge (y=0, z=20) rounded at a radius running straight from 2 (x=0) to 5 (x=40). The round is the union of the balls rolled along the edge, ball s touching both faces at r(s), the law continued past the edge's ends along its end slopes: each section loses the corner points no ball reaches, up to rho(x, t), the nearest entry into a ball along the ray from the edge at the angle t. V, moments and areas integrated by Gauss-Legendre panels in x and t (tools/oracle-analytic/cases_fillets.py)",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 23888.613525715446,
|
||||
|
||||
Reference in new issue
Block a user