mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-04 11:05:56 +00:00
solidcore: a curvature fillet is the equal-area Gⁿ section; mixed wedges refused
Blend::Curvature now builds the capi's section, as fork 6148773's capi
does: SM_FSG_BLEND_CURVE of order 2 at the equal-area thumbweight,
passed by solidcore's name GN_THUMBWEIGHT_EQUAL_AREA (solidcore#2274
moves its value from -2 to 2), with no blend-curve-at-thirds setting.
The sync-2 refusal (e9ac0e606) comes off.
One call resolves the equal-area thumbweight once, from the first
blend's wedge, and every edge takes it, in the fork as in solidcore: an
edge of another wedge then takes off the wrong area, depending on the
edges' order (17% short, 21% over;
solidcore#2250 issuecomment-6563419897). A curvature fillet over edges
whose faces meet at different angles (at each edge's midpoint, within
1e-6 rad) is refused as Error::Unported citing that comment; the call
is not split per edge.
Against closed forms on the rig, solidcore within 1e-9 of SMLib
d01d7b0: 862 7845.48670 (7845.48668), 2708 11791.330160 (11791.330159),
every-box-edge-r3 and section box-edge-r3 23922.743338, boss-root-r2
26056.152951, pipe-on-plate-r2 40302.590788 (completes, #2264),
hole-top-rim-r1 15207.56176, crease-r2 13963.15756, l-bracket 7538.62833,
peg front 9383.62724, all within 3e-9 relative. The G2 corner cases
pocket-corner and the bar corner's three edges (two variants) build and
fail continuity G1 at one seam, as on SMLib; the oblique three-edge
corner is refused for its mixed wedges. The kernel's and engine's
curvature tests now pass.
a_curvature_round_takes_its_wedges_equal_area_and_mixed_wedges_are_refused
holds each wedge's r² (cot(α/2) - (π - α)/2) to 1e-6 on a chamfered box
(135° and 90°), both orders refused, two 90° edges built together.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SdcgEu4Lr3moCCisdZShKz
This commit is contained in:
@@ -28,6 +28,7 @@ use solidcore::blend::fillet_executive::p1::{FilletLaw, FilletLawKind};
|
||||
use solidcore::blend::fillet_executive::p3::NoRoomReason;
|
||||
use solidcore::blend::fillet_executive::session::FilletSession;
|
||||
use solidcore::blend::fillet_geom::p1::{FilletCornerType, FilletErrorType};
|
||||
use solidcore::blend::fillet_section::section::GN_THUMBWEIGHT_EQUAL_AREA;
|
||||
use solidcore::blend::fillet_solver::p1::FilletSurfaceGeneratorType;
|
||||
use solidcore::geom::attribute::{Attribute, AttributeHandlers, Behavior, ids as attribute_ids};
|
||||
use solidcore::geom::vector3d::{Point3, Vec3};
|
||||
@@ -235,6 +236,10 @@ fn chamfer_radius(k: &Kernel, b: &mut Brep, e: EdgeId, distance: f64) -> solidco
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
enum Section<'a> {
|
||||
Round,
|
||||
/// The curvature-continuous section (`SM_FSG_BLEND_CURVE`): the Gⁿ generator's order 2 at the
|
||||
/// equal-area thumbweight (`SMLIB_BLEND_THUMBWEIGHT`, fork 6148773; solidcore#2261 under
|
||||
/// `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).
|
||||
@@ -340,6 +345,11 @@ fn blend_edges_stopping(
|
||||
Some(&size) => s.prepare_variable_radius_edges(k, edges, size, law, 1, 1),
|
||||
None => return Ok(Err(Err(Error::Argument))),
|
||||
},
|
||||
// The equal-area thumbweight by solidcore's name, never its value: solidcore#2274 moves
|
||||
// it from -2 to 2, after which -2 is an ordinary weight.
|
||||
(Section::Curvature, _) => {
|
||||
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),
|
||||
};
|
||||
@@ -652,13 +662,36 @@ fn untagged(mut b: Brep) -> Out<Brep> {
|
||||
Ok(b)
|
||||
}
|
||||
|
||||
/// Why [`Blend::Curvature`] is refused. The capi's curvature section is the Gⁿ generator's order 2
|
||||
/// at the equal-area thumbweight (`SMLIB_BLEND_THUMBWEIGHT`, fork 6148773), which solidcore does
|
||||
/// not have yet; the blend-curve quintic it does have takes off another area, so it is not built
|
||||
/// in its place. The refusal comes off when the solidcore pin carries #2260 and 6148773 and its
|
||||
/// `fillet_gn_section_generator` flag is on.
|
||||
pub(crate) const CURVATURE_SECTION: &str = "a curvature-continuous fillet on solidcore: the curvature section's equal-area Gⁿ \
|
||||
generator is not in solidcore yet (solidcore#2250, port in progress as #2260)";
|
||||
/// Why a curvature fillet over edges of different wedge angles is refused. The equal-area
|
||||
/// thumbweight is resolved once per call, from the first blend's wedge, and every edge takes it
|
||||
/// (in the fork as in solidcore#2261): an edge of another wedge takes off another area than its
|
||||
/// own, and which one depends on the edges' order.
|
||||
pub(crate) const CURVATURE_MIXED_WEDGES: &str = "a curvature-continuous fillet on solidcore over edges whose faces meet at different \
|
||||
angles: every edge takes the equal-area thumbweight of the first one's angle, so the others take off the wrong area \
|
||||
(https://github.com/tangency-labs/solidcore/issues/2250#issuecomment-6563419897)";
|
||||
|
||||
/// How far apart, in radians, two edges' wedge angles may be and still share one equal-area
|
||||
/// thumbweight.
|
||||
const WEDGE_TOLERANCE: f64 = 1e-6;
|
||||
|
||||
/// Whether the faces of every edge in `edges` meet at the same angle at its midpoint (the wedge the
|
||||
/// equal-area thumbweight is resolved from), within [`WEDGE_TOLERANCE`]. An edge whose angle can't
|
||||
/// be read counts as another wedge.
|
||||
fn one_wedge(k: &Kernel, b: &mut Brep, edges: &[EdgeId]) -> solidcore::Result<bool> {
|
||||
let mut first: Option<f64> = None;
|
||||
for &e in edges {
|
||||
let Some(u0) = edge::blend_edgeuse(k, b, e)? else { return Ok(false) };
|
||||
let Some(u1) = eu_p1::radial(b, u0)? else { return Ok(false) };
|
||||
let (Some((n0, _)), Some((n1, _))) = (face_frame(k, b, u0)?, face_frame(k, b, u1)?) else { return Ok(false) };
|
||||
let angle = n0.dot(n1).clamp(-1.0, 1.0).acos();
|
||||
match first {
|
||||
None => first = Some(angle),
|
||||
Some(a) if (a - angle).abs() > WEDGE_TOLERANCE => return Ok(false),
|
||||
Some(_) => {}
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// [`blend_holes`] without its holes.
|
||||
#[allow(clippy::too_many_arguments)] // the capi's blend_call
|
||||
@@ -751,7 +784,11 @@ fn blend_call(
|
||||
let solid_in = brep_p2::is_manifold_solid(&w)?;
|
||||
let section = match kind {
|
||||
Blend::Round => Section::Round,
|
||||
Blend::Curvature => return Ok(Err(Error::Unported(CURVATURE_SECTION))),
|
||||
// Every edge of one call takes the equal-area thumbweight of the first's wedge
|
||||
// (solidcore#2250's comment 6563419897): edges of other wedges would take off another
|
||||
// area than their own, so they are not built together.
|
||||
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.
|
||||
Blend::Chamfer => {
|
||||
|
||||
@@ -663,19 +663,39 @@ fn a_setback_corner_left_open_returns_its_hole() {
|
||||
assert_eq!(refused, Some(Error::CornerRefused { corner: 0 }));
|
||||
}
|
||||
|
||||
/// A curvature-continuous round is refused on solidcore, whose only curvature section is the
|
||||
/// blend-curve quintic (113/756 r² off a square corner, not the equal-area (1 - π/4) r²), until
|
||||
/// its pin has the Gⁿ section generator (solidcore#2250).
|
||||
/// A curvature-continuous round at the equal-area thumbweight takes off what the ball's circle
|
||||
/// does, r² (cot(α/2) - (π - α)/2) per unit length on a wedge α (solidcore#2261): on a 40 x 30 x 20
|
||||
/// box with its top back edge chamfered 5, r 2 along the chamfer's top edge (α 135°) takes
|
||||
/// 0.0860579227 per mm, along the bottom front edge (α 90°) 0.8584073464. Both in one call are
|
||||
/// refused: every edge takes the first one's thumbweight, so the other would take 17% too little
|
||||
/// or 21% too much (solidcore#2250's comment 6563419897), whichever order they come in.
|
||||
#[test]
|
||||
fn a_curvature_round_is_refused_until_solidcore_has_the_equal_area_section() {
|
||||
let b = BLOCK.core();
|
||||
let r = blend(&b, Blend::Curvature, &[TOP_FRONT], &[2.0], TOL, &[], &[], Corners::Filled).err();
|
||||
assert_eq!(r, Some(Error::Unported(super::blend::CURVATURE_SECTION)));
|
||||
assert!(r.is_some_and(|e| e.to_string().contains("solidcore#2250")));
|
||||
let set_back = blend(&b, Blend::Curvature, &[TOP_FRONT], &[2.0], TOL, &[[0.0, 0.0, 10.0]], &[3.0], Corners::Filled).err();
|
||||
assert_eq!(set_back, Some(Error::Unported(super::blend::CURVATURE_SECTION)));
|
||||
fn a_curvature_round_takes_its_wedges_equal_area_and_mixed_wedges_are_refused() {
|
||||
let r = 2.0;
|
||||
let cut = |alpha: f64| r * r * (1.0 / (alpha / 2.0).tan() - (PI - alpha) / 2.0);
|
||||
let chamfered =
|
||||
blend(&Shape::Box([0.0; 3], [40.0, 30.0, 20.0]).core(), Blend::Chamfer, &[[20.0, 30.0, 20.0]], &[5.0], TOL, &[], &[], Corners::Filled)
|
||||
.unwrap();
|
||||
let v0 = volume(&chamfered, 1e-8).unwrap();
|
||||
assert!(rel(v0, 23500.0) < 1e-9, "{v0}");
|
||||
let (a, b) = ([20.0, 25.0, 20.0], [20.0, 0.0, 0.0]);
|
||||
for (at, alpha) in [(a, 0.75 * PI), (b, 0.5 * PI)] {
|
||||
let round = blend(&chamfered, Blend::Curvature, &[at], &[r], TOL, &[], &[], Corners::Filled).unwrap();
|
||||
let (rec, _) = native(&to_arrays(&round).unwrap()).unwrap();
|
||||
rec.validate().unwrap();
|
||||
let removed = v0 - volume(&round, 1e-8).unwrap();
|
||||
assert!(rel(removed, 40.0 * cut(alpha)) < 1e-6, "{removed} vs {}", 40.0 * cut(alpha));
|
||||
}
|
||||
for both in [[a, b], [b, a]] {
|
||||
let refused = blend(&chamfered, Blend::Curvature, &both, &[r], TOL, &[], &[], Corners::Filled).err();
|
||||
assert_eq!(refused, Some(Error::Unported(super::blend::CURVATURE_MIXED_WEDGES)));
|
||||
assert!(refused.is_some_and(|e| e.to_string().contains("issuecomment-6563419897")));
|
||||
}
|
||||
// Edges of one wedge go together.
|
||||
let two = blend(&chamfered, Blend::Curvature, &[b, [20.0, 0.0, 20.0]], &[r], TOL, &[], &[], Corners::Filled).unwrap();
|
||||
assert!(rel(v0 - volume(&two, 1e-8).unwrap(), 80.0 * cut(0.5 * PI)) < 1e-6);
|
||||
// A pick that misses is still named first.
|
||||
assert_eq!(blend(&b, Blend::Curvature, &[[90.0; 3]], &[2.0], TOL, &[], &[], Corners::Filled).err(), Some(Error::NoMatch { point: 0 }));
|
||||
assert_eq!(blend(&chamfered, Blend::Curvature, &[[90.0; 3]], &[r], TOL, &[], &[], Corners::Filled).err(), Some(Error::NoMatch { point: 0 }));
|
||||
}
|
||||
|
||||
/// A variable round is the union of its balls (solidcore#2262, `fillet_gn_section_generator`).
|
||||
|
||||
Reference in New Issue
Block a user