mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-08 04:52:10 +00:00
Merge pull request #2355 from Karl-VonRichter/blend-corner-chamfers
feat: chamfers meeting at a vertex close exactly by their planes, the blend corner's chamfer event, opt-in (#2345)
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
//! | [`mixed`] | the Mixed corner (`ClassifyMixed`, `MakeMixedTopology`, `CalcMixedVertGeom`, `CalcMixedEdgeGeom`, `CreateMixedPatches`; 6fd0411, 6e79de5, e1a2153) |
|
||||
//! | [`setback`] | the Setback (`ClassifySetBack`, `MakeSetBackTopology`, `BuildSetBackFill`, `CreateSetBackPatches`; 61547af, 1506e2f, f1d81f4) |
|
||||
//! | [`swept`] | the Swept turn (`Classify`'s Swept part, `BuildSweptSurface`, `CreateSweptPatch`) |
|
||||
//! | [`BlendChamferCorner`], [`chamfer`] | not the fork's: chamfers meeting at a vertex, closed by their planes (WR-716, #2345) |
|
||||
//!
|
||||
//! Deviations: the blend corner is not its own corner kind here. The fork's `SmBlendCorner`
|
||||
//! builds the Join, the corners of four edges and the Mitre turn with the two-blend
|
||||
@@ -64,6 +65,7 @@ use crate::topology::face::p2 as face2;
|
||||
use crate::topology::{edge, Brep, EdgeId, EdgeuseId, VertexId};
|
||||
|
||||
pub mod ball;
|
||||
pub mod chamfer;
|
||||
pub mod end;
|
||||
pub mod mixed;
|
||||
pub mod setback;
|
||||
@@ -94,6 +96,9 @@ pub enum BlendCornerEvent {
|
||||
/// `SM_BCE_MIXED` (the fork's 6fd0411, smlib#82): a convex and a concave blend
|
||||
/// continuing each other G1 across a tangent seam ([`mixed`]).
|
||||
Mixed,
|
||||
/// Not the fork's (WR-716, #2345): chamfers meeting at a vertex where every edge is
|
||||
/// chamfered, closed by their planes ([`chamfer`]).
|
||||
Chamfer,
|
||||
}
|
||||
|
||||
/// `SmBlendEnd` (the fork's 53c47b8): how an End ends, chosen when the corner is made.
|
||||
@@ -124,6 +129,18 @@ pub enum BlendTurn {
|
||||
Swept,
|
||||
}
|
||||
|
||||
/// The two ways the blend corner closes chamfers meeting at a vertex
|
||||
/// ([`chamfer`], `Kernel::fixes.fillet_chamfer_corner`), as [`BlendTurn`] is a Turn's.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
pub enum BlendChamferCorner {
|
||||
/// Each chamfer plane runs on until it meets the others: three meet at one point,
|
||||
/// pairwise along lines to it, and there is no corner face (the default).
|
||||
#[default]
|
||||
Mitre,
|
||||
/// One plane through the rails' crossings cuts the corner off: one new planar face.
|
||||
Facet,
|
||||
}
|
||||
|
||||
/// The blend corner's own members (`m_eEvent`, `m_eTurn`, `m_eEnd`) on a corner built as
|
||||
/// one.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||
@@ -182,6 +199,12 @@ impl BlendCorner {
|
||||
self.event == BlendCornerEvent::Mixed
|
||||
}
|
||||
|
||||
/// The chamfer event ([`chamfer`]; WR-716).
|
||||
#[must_use]
|
||||
pub fn is_chamfer(self) -> bool {
|
||||
self.event == BlendCornerEvent::Chamfer
|
||||
}
|
||||
|
||||
/// `IsSwept`: a Turn, Swept.
|
||||
#[must_use]
|
||||
pub fn is_swept(self) -> bool {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -54,6 +54,9 @@ pub enum CornerKind {
|
||||
Swept,
|
||||
/// A Mixed corner's faces (`SM_BCE_MIXED`).
|
||||
Mixed,
|
||||
/// The chamfer event's faces (not the fork's; WR-716, #2345): a Facet's plane, or a
|
||||
/// corner patch's bilinear quadrilaterals round the crossings' centroid.
|
||||
Chamfer,
|
||||
/// A corner the blend corner does not build: the pinned corner hierarchy
|
||||
/// (`SmFilletCorner` and its subclasses, #2400), or a blend corner of no event.
|
||||
Pinned,
|
||||
@@ -77,6 +80,7 @@ impl CornerKind {
|
||||
BlendTurn::Swept => Self::Swept,
|
||||
},
|
||||
BlendCornerEvent::Mixed => Self::Mixed,
|
||||
BlendCornerEvent::Chamfer => Self::Chamfer,
|
||||
BlendCornerEvent::None => Self::Pinned,
|
||||
}
|
||||
}
|
||||
@@ -92,6 +96,7 @@ impl CornerKind {
|
||||
Self::Mitre => "mitre",
|
||||
Self::Swept => "swept",
|
||||
Self::Mixed => "mixed",
|
||||
Self::Chamfer => "chamfer",
|
||||
Self::Pinned => "pinned",
|
||||
}
|
||||
}
|
||||
@@ -142,6 +147,7 @@ impl BlendFaceKind {
|
||||
CornerKind::Swept => 15,
|
||||
CornerKind::Mixed => 16,
|
||||
CornerKind::Pinned => 17,
|
||||
CornerKind::Chamfer => 18,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -161,6 +167,7 @@ impl BlendFaceKind {
|
||||
15 => Self::Corner(CornerKind::Swept),
|
||||
16 => Self::Corner(CornerKind::Mixed),
|
||||
17 => Self::Corner(CornerKind::Pinned),
|
||||
18 => Self::Corner(CornerKind::Chamfer),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
@@ -349,6 +356,7 @@ mod tests {
|
||||
CornerKind::Mitre,
|
||||
CornerKind::Swept,
|
||||
CornerKind::Mixed,
|
||||
CornerKind::Chamfer,
|
||||
CornerKind::Pinned,
|
||||
];
|
||||
let mut kinds = vec![
|
||||
|
||||
@@ -714,6 +714,10 @@ pub struct FilletCorner {
|
||||
/// The fork's Ball's own members (`m_pBallSurface` and its loop; smlib#79 step 4c,
|
||||
/// WR-697): set by its topology on a blend corner that is a Ball.
|
||||
pub ball: Option<crate::blend::blend_corner::ball::BallCorner>,
|
||||
/// The blend corner's chamfer event (WR-716, #2345, `fixes.fillet_chamfer_corner`): its
|
||||
/// corner type, exact points and, until the patch takes it, the facet's face; set
|
||||
/// with [`Self::blend`] on a 3x3 chamfer corner that is the event.
|
||||
pub chamfer_corner: Option<crate::blend::blend_corner::chamfer::ChamferCorner>,
|
||||
/// The fork's Setback's own members (`m_vSetBacks`, `m_lFillOrder`, `m_bConvexSetBack`,
|
||||
/// `m_bSetBackOverflows`, `m_pFill`; smlib#104, WR-697): set at its creation on a blend
|
||||
/// corner that is a Setback.
|
||||
@@ -1057,6 +1061,7 @@ impl FilletCorner {
|
||||
blend: None,
|
||||
swept: None,
|
||||
ball: None,
|
||||
chamfer_corner: None,
|
||||
setback: None,
|
||||
mixed: None,
|
||||
end_cap: None,
|
||||
@@ -2360,7 +2365,8 @@ impl FilletCorner {
|
||||
nxn: impl FnOnce(&mut Self, &mut C) -> Result<()>,
|
||||
) -> Result<()> {
|
||||
// the fork's SmBlendCorner::AdjustTopology: a Ball, a Setback, a Mixed corner, a Join,
|
||||
// an End or a Swept turn adjusts nothing (WR-697; 543d25a, 53c47b8)
|
||||
// an End or a Swept turn adjusts nothing (WR-697; 543d25a, 53c47b8); nor does the
|
||||
// chamfer event (WR-716: no patch to stretch)
|
||||
if self.blend.is_some_and(|blend| {
|
||||
blend.is_ball()
|
||||
|| blend.is_setback()
|
||||
@@ -2368,6 +2374,7 @@ impl FilletCorner {
|
||||
|| blend.is_join()
|
||||
|| blend.is_end()
|
||||
|| blend.is_swept()
|
||||
|| blend.is_chamfer()
|
||||
}) {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -2725,7 +2732,7 @@ impl FilletCorner {
|
||||
) -> Result<()> {
|
||||
// the fork's SmBlendCorner::AdjustTangentCorner: nothing at a Ball, a Setback, a Mixed
|
||||
// corner, a Join, an End or a Swept turn (WR-697; 543d25a, 53c47b8; the Mitre turn's is
|
||||
// the Nx2 port's)
|
||||
// the Nx2 port's), nor at the chamfer event (WR-716)
|
||||
if self.blend.is_some_and(|blend| {
|
||||
blend.is_ball()
|
||||
|| blend.is_setback()
|
||||
@@ -2733,6 +2740,7 @@ impl FilletCorner {
|
||||
|| blend.is_join()
|
||||
|| blend.is_end()
|
||||
|| blend.is_swept()
|
||||
|| blend.is_chamfer()
|
||||
}) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -1109,6 +1109,9 @@ pub enum CornerCreation<O> {
|
||||
/// The blended edge no size builds (the fork's `NoteFitRadius(0.0, edge)`), noted
|
||||
/// before the refusal.
|
||||
fit_edge: Option<O>,
|
||||
/// The size of `fit_edge`'s blend that builds and what it sizes, where one does
|
||||
/// (the chamfer event's, WR-716); `None` notes 0, no size.
|
||||
fit: Option<(f64, crate::blend::fillet_executive::FitKind)>,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1198,12 +1201,15 @@ impl<O: ClassObject + SolutionObject + Ord> FilletExecutive<O> {
|
||||
error,
|
||||
comment,
|
||||
fit_edge,
|
||||
fit,
|
||||
} => {
|
||||
// NoteFitRadius(0.0, edge)
|
||||
// NoteFitRadius(0.0, edge), or the size that builds where one does
|
||||
if let Some(edge) = fit_edge {
|
||||
self.fit_radius = 0.0;
|
||||
let (size, kind) =
|
||||
fit.unwrap_or((0.0, crate::blend::fillet_executive::FitKind::Radius));
|
||||
self.fit_radius = size;
|
||||
self.fit_edge = Some(edge);
|
||||
self.fit_kind = crate::blend::fillet_executive::FitKind::Radius;
|
||||
self.fit_kind = kind;
|
||||
}
|
||||
self.error.note_vertex_error(
|
||||
vertex,
|
||||
|
||||
@@ -325,6 +325,9 @@ struct SessionObjects<'target> {
|
||||
/// (not upstream, #2489) The marks of the faces the session made, while
|
||||
/// `Kernel::blend_face_log` is on (`blend_face_log`).
|
||||
blend_face_marks: Vec<AttributeHandle>,
|
||||
/// The corner type of chamfers meeting at a vertex (`set_chamfer_corner`; WR-716).
|
||||
/// Read only with `Kernel::fixes.fillet_chamfer_corner`.
|
||||
chamfer_corner: crate::blend::blend_corner::BlendChamferCorner,
|
||||
}
|
||||
|
||||
impl SessionObjects<'_> {
|
||||
@@ -637,6 +640,51 @@ impl crate::blend::blend_corner::setback::SetbackSolvers for SetbackSessionSolve
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::blend::blend_corner::chamfer::ChamferSolvers for SweptSessionSolvers<'_> {
|
||||
fn chamfer_solver(
|
||||
&self,
|
||||
b: &Brep,
|
||||
eu: EdgeuseId,
|
||||
) -> Result<
|
||||
Option<(
|
||||
&FilletSolver,
|
||||
Option<crate::blend::blend_corner::chamfer::ChamferSizeKind>,
|
||||
)>,
|
||||
> {
|
||||
use crate::blend::blend_corner::chamfer::ChamferSizeKind;
|
||||
let executive = self
|
||||
.executive
|
||||
.ok_or_else(|| invalid("chamfer solver lookup without the executive"))?;
|
||||
let of = |eu: EdgeuseId| {
|
||||
let object = FilletObject::Topology(FilletBody::Target, EntityRef::Edgeuse(eu));
|
||||
match executive.fillet_solver_of_edgeuse(object) {
|
||||
Some(FilletObject::Solver(id)) => Some(id),
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
let id = match of(eu) {
|
||||
Some(id) => Some(id),
|
||||
None => crate::topology::edgeuse::p1::mate(b, eu)?.and_then(of),
|
||||
};
|
||||
let Some(id) = id else {
|
||||
return Ok(None);
|
||||
};
|
||||
let slot = self
|
||||
.solvers
|
||||
.get(id.0 as usize)
|
||||
.and_then(Option::as_ref)
|
||||
.ok_or_else(|| invalid("chamfer solver is not live"))?;
|
||||
let size = match slot {
|
||||
SolverObject::ConstantRadius(s) if !FilletSolverOps::offset_radii_can_change(&**s)? => {
|
||||
Some(ChamferSizeKind::Offsets)
|
||||
}
|
||||
SolverObject::ConstantDistance(s) => Some(ChamferSizeKind::Chord(s.distance())),
|
||||
_ => None,
|
||||
};
|
||||
Ok(Some((slot.base(), size)))
|
||||
}
|
||||
}
|
||||
|
||||
/// The fork's Setback noting why it refused at its vertex (`SetBackNotBuilt`, its
|
||||
/// `NoteError(m_cpVertex, SM_FIL_FAILURE, ..)`), and the setback that builds
|
||||
/// (`NoteFitSetBack`) where it names one.
|
||||
@@ -891,6 +939,13 @@ fn live_corner_vertices_geom(
|
||||
};
|
||||
crate::blend::blend_corner::ball::place_ball_contacts(k, target, pseudo, corner, &lookup)?;
|
||||
}
|
||||
// WR-716: the chamfer event's vertices at their exact points
|
||||
if corner
|
||||
.blend
|
||||
.is_some_and(crate::blend::blend_corner::BlendCorner::is_chamfer)
|
||||
{
|
||||
crate::blend::blend_corner::chamfer::place_chamfer_vertices(pseudo, corner)?;
|
||||
}
|
||||
// The fork's (WR-697, 6fd0411): a Mixed corner places its vertices A, P, Q and B once
|
||||
// both blends are traced (CalcMixedVertGeom), after the base's vertex geometry
|
||||
if corner
|
||||
@@ -1584,6 +1639,7 @@ impl<'target> FilletSession<'target> {
|
||||
set_back_vertices: Vec::new(),
|
||||
set_back_fills: Vec::new(),
|
||||
blend_face_marks: Vec::new(),
|
||||
chamfer_corner: crate::blend::blend_corner::BlendChamferCorner::Mitre,
|
||||
},
|
||||
_gn_scope: gn_scope,
|
||||
})
|
||||
@@ -2093,10 +2149,12 @@ impl<'target> FilletSession<'target> {
|
||||
corners,
|
||||
swept_turns,
|
||||
set_back_vertices,
|
||||
chamfer_corner,
|
||||
set_back_fills,
|
||||
..
|
||||
} = &mut self.objects;
|
||||
let swept_turns = *swept_turns;
|
||||
let chamfer_corner = *chamfer_corner;
|
||||
let set_back_vertices = &*set_back_vertices;
|
||||
let set_back_fills = &*set_back_fills;
|
||||
self.executive
|
||||
@@ -2157,6 +2215,52 @@ impl<'target> FilletSession<'target> {
|
||||
let location = crate::topology::vertex::point(target, vertex).ok();
|
||||
return Ok(CornerCreation::Unsupported { location });
|
||||
};
|
||||
// Not the fork's (WR-716, #2345): chamfers meeting at a vertex where every
|
||||
// edge is chamfered are the blend corner's chamfer event, closed by their
|
||||
// planes, of the session's corner type; refused where that type does not
|
||||
// exist there; a corner the event declines stays the pinned 3x3 one
|
||||
if k.fixes.fillet_chamfer_corner
|
||||
&& corner.chamfer
|
||||
&& corner.kind == FilletCornerKind::ConvexNByN
|
||||
{
|
||||
use crate::blend::blend_corner::chamfer::{classify_chamfer, ChamferClass};
|
||||
let lookup = SweptSessionSolvers {
|
||||
solvers: solvers.as_slice(),
|
||||
executive: Some(executive),
|
||||
};
|
||||
let all_eus = corner.all_eus.clone().unwrap_or_default();
|
||||
match classify_chamfer(
|
||||
k,
|
||||
target,
|
||||
vertex,
|
||||
&all_eus,
|
||||
chamfer_corner,
|
||||
corner.approx_tol_3d,
|
||||
&lookup,
|
||||
)? {
|
||||
ChamferClass::Event(event) => {
|
||||
corner.blend = Some(crate::blend::blend_corner::BlendCorner {
|
||||
event: crate::blend::blend_corner::BlendCornerEvent::Chamfer,
|
||||
turn: crate::blend::blend_corner::BlendTurn::Mitre,
|
||||
end: crate::blend::blend_corner::BlendEnd::OnEndFace,
|
||||
});
|
||||
corner.chamfer_corner = Some(*event);
|
||||
}
|
||||
ChamferClass::Refused(refusal) => {
|
||||
let location = crate::topology::vertex::point(target, vertex).ok();
|
||||
return Ok(CornerCreation::Refused {
|
||||
location,
|
||||
error: refusal.error,
|
||||
comment: refusal.comment,
|
||||
fit_edge: refusal.fit_edge.map(|e| {
|
||||
FilletObject::Topology(FilletBody::Target, EntityRef::Edge(e))
|
||||
}),
|
||||
fit: refusal.fit,
|
||||
});
|
||||
}
|
||||
ChamferClass::Declined(_) => {}
|
||||
}
|
||||
}
|
||||
// The fork's (WR-697, 79d4763): three equal rounds of one convexity, every
|
||||
// edge there blended, are the blend corner's Ball
|
||||
// (SmBlendCorner::ClassifyBall, in SmFilletCorner::Create's three-solver
|
||||
@@ -2296,6 +2400,7 @@ impl<'target> FilletSession<'target> {
|
||||
fit_edge: refusal.fit_edge.map(|e| {
|
||||
FilletObject::Topology(FilletBody::Target, EntityRef::Edge(e))
|
||||
}),
|
||||
fit: None,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2623,6 +2728,16 @@ impl<'target> FilletSession<'target> {
|
||||
self.objects.swept_turns = swept;
|
||||
}
|
||||
|
||||
/// How chamfers meeting at a vertex where every edge is chamfered close the corner,
|
||||
/// with `Kernel::fixes.fillet_chamfer_corner` (WR-716): the Mitre (the default; the
|
||||
/// chamfer planes run on to meet each other) or the Facet (one plane through the
|
||||
/// rails' crossings). It overrides the corner's bevel flag
|
||||
/// ([`Self::set_corner_bevel`]) at such a corner. Set it before [`Self::do_fillet`]
|
||||
/// (the corners are made there). Without the flag it changes nothing.
|
||||
pub fn set_chamfer_corner(&mut self, corner: crate::blend::blend_corner::BlendChamferCorner) {
|
||||
self.objects.chamfer_corner = corner;
|
||||
}
|
||||
|
||||
/// The fork's `AddSetBackVertex` (smlib#79 step 4c; WR-697): the caller sets
|
||||
/// `vertex` back (its corner's setback distance, set after the corners are made), so
|
||||
/// with `Kernel::fixes.fillet_blend_corner` no Ball is built there
|
||||
@@ -2924,6 +3039,14 @@ impl CornerEdgeAccess<FilletObject> for SessionObjects<'_> {
|
||||
k, target, pseudo, corner, &lookup,
|
||||
)?;
|
||||
}
|
||||
// WR-716: the chamfer event's edges as exact lines and its facet's plane, before
|
||||
// the edges' own kinds would march them
|
||||
if corner
|
||||
.blend
|
||||
.is_some_and(crate::blend::blend_corner::BlendCorner::is_chamfer)
|
||||
{
|
||||
crate::blend::blend_corner::chamfer::chamfer_corner_edge_geom(k, pseudo, corner)?;
|
||||
}
|
||||
// The fork's (61547af, smlib#104): a Setback's fill, its base points and its spring
|
||||
// curves (SmBlendCorner::BuildSetBackFill, CalcCornerEdgeGeom), before the edges' own
|
||||
// kinds compute their curves
|
||||
@@ -4380,6 +4503,24 @@ impl FaceConstructionAccess<FilletObject> for SessionObjects<'_> {
|
||||
};
|
||||
return crate::blend::blend_corner::swept::create_swept_patch(corner, &mut env);
|
||||
}
|
||||
// WR-716: the chamfer event's facet is its plane
|
||||
if corner
|
||||
.blend
|
||||
.is_some_and(crate::blend::blend_corner::BlendCorner::is_chamfer)
|
||||
{
|
||||
let mut no_note = |_: &FilletCorner| {};
|
||||
let mut env = CornerPatchEnv {
|
||||
k,
|
||||
target,
|
||||
pseudo,
|
||||
solvers: &store,
|
||||
fillet: Some(&mut fillet),
|
||||
note_face_error: &mut no_note,
|
||||
};
|
||||
return crate::blend::blend_corner::chamfer::create_chamfer_patch(
|
||||
corner, &mut env,
|
||||
);
|
||||
}
|
||||
// The fork's (WR-697, 543d25a): a Join has no patch of its own
|
||||
// (SmBlendCorner::CreateCornerPatch)
|
||||
if corner
|
||||
@@ -6241,6 +6382,14 @@ impl SessionObjects<'_> {
|
||||
executive: &mut FilletExecutive<FilletObject>,
|
||||
) -> Result<()> {
|
||||
const NATIVE: &str = "SmFilletConvexNxNCorner::MakeCornerTopology";
|
||||
// WR-716: the chamfer event's Mitre is the bevel's topology (the crossings, the
|
||||
// centre M, the edges to it), its Facet and its corner patch the ring's (the
|
||||
// crossings and the sides between them), whatever the caller set
|
||||
if let Some(corner) = self.corners.get_mut(id.0 as usize) {
|
||||
if let Some(event) = corner.chamfer_corner.as_ref() {
|
||||
corner.bevel = event.definition.is_mitre();
|
||||
}
|
||||
}
|
||||
let edgeuses = self
|
||||
.corners
|
||||
.get(id.0 as usize)
|
||||
|
||||
@@ -1556,6 +1556,34 @@ pub struct UpstreamFixes {
|
||||
pub fillet_round_ends_on_outline: bool,
|
||||
/// With `fillet_blend_corner`, a vertex of three constant circular rounds of one convexity where a face is a natural quadric (a cylinder, cone or sphere; the others planes) is the blend corner's Setback too ([`crate::blend::blend_corner::setback`]; the fork's 37c9e1d, smlib#85 part C, built in solidcore). Each round is set back from the vertex, at least to where the rails cross on the faces as they are. The hole is filled by a G1 fill with spring curves exact on the faces, each patch G1 to its face by construction and certified no more concave than what it is G1 to (`II >= k G`), its control net never turning back (`check_brep`'s test #5) and contained; the fill's programme also minimises the net's own thin-plate energy. A fill not certified grows its setbacks to the least scale on the grid of thousandths that is certified by more than its own rounding bound, and an asked setback that certifies only grown is refused naming the setback that builds. With `fillet_round_as_definition` the fill's patches are its definition, the curved faces kept as their quadrics. Off: such a corner is the corner `fillet_blend_corner` builds without it (the N-by-N convex corner), unchanged (WR-697 addendum 2026-11-03, #2343; solidcore-native, no fork commit).
|
||||
pub fillet_setback_beside_curved_faces: bool,
|
||||
/// Chamfers meeting at a vertex where every edge is chamfered, each chamfer a plane
|
||||
/// between two planes, are closed by the chamfer planes themselves, exactly: the blend
|
||||
/// corner's chamfer event ([`crate::blend::blend_corner::chamfer`]), in place of the
|
||||
/// pinned 3x3 corner's chamfer branch (`SmFilletConvexNxNCorner` with `SetChamfer`,
|
||||
/// convex and concave alike). The corner type is the session's
|
||||
/// (`FilletSession::set_chamfer_corner`): the Mitre (the default), where each chamfer
|
||||
/// runs on to the others and three of them meet at the point M of their 3x3 solve,
|
||||
/// pairwise along the exact lines from the rails' crossings to M, with no corner face;
|
||||
/// or the Facet, one plane through the rails' crossings whose edges are the chamfers'
|
||||
/// end sections, exact lines in their planes. A mitre point outside the corner (a
|
||||
/// chamfer much wider than the others) is refused with `FilletErrorType::NoRoom`,
|
||||
/// naming the largest size of that chamfer that keeps it inside, less the fit margin,
|
||||
/// as the refusal's fit; the facet is refused there too, its plane leaving the
|
||||
/// chamfers. Four or more chamfers whose crossings lie in one plane build the facet
|
||||
/// (their mitre is refused, naming why); where their crossings are not in one plane the
|
||||
/// corner is a patch, whichever type was asked: the chamfers stop at their crossings and
|
||||
/// n bilinear quadrilaterals round the crossings' centroid close the skew polygon of
|
||||
/// their end sections, G0 to each other and to each chamfer (at four, the one bilinear
|
||||
/// patch of the crossings), within the crossings' convex hull and so in the corner.
|
||||
/// Chamfer planes so nearly coplanar that the points where they
|
||||
/// meet are not determined to the part's rounding are refused with
|
||||
/// `FilletErrorType::CornerUnsupported`, naming that. The pinned corner marches the
|
||||
/// mitre's edges as surface intersections, refuses a mitre of four or more chamfers with
|
||||
/// no reason, and builds its corner face as a Coons patch where the chamfers are unequal
|
||||
/// (WR-716, #2345; own design, smlib#89's brief, the owner's answers on #2345). For the
|
||||
/// port in progress only: at parity it becomes the default and the pinned corner's
|
||||
/// chamfer branch is deleted (WR-716's retirement plan).
|
||||
pub fillet_chamfer_corner: bool,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -270,6 +270,7 @@ never returns "no kind": a configuration the table does not name is a Setback.
|
||||
| **Join** | two blends meeting G1 and continuing each other | one shared section |
|
||||
| **Turn** | two blends sharing a face, turning at a sharp unrounded side edge (a boss base, a pocket's floor or rim) | **Mitre** (default) or **Swept** (option) |
|
||||
| **Ball** | three equal rounds of one convexity at a vertex of three faces | the corner ball's sphere |
|
||||
| **Chamfer** | every edge at the vertex chamfered, each chamfer a plane between two planes | **Mitre** (default): the chamfer planes run on to their common point; or **Facet** (option): one plane through the rails' crossings |
|
||||
| **Setback** | everything else: unequal radii, four or more blends, mixed convexity, and any corner where G2 is selected | the setback hole and its certified convex fill |
|
||||
|
||||

|
||||
@@ -291,6 +292,20 @@ ball's sphere. Setback: the rounds stop short and a 2n-sided hole is filled.*
|
||||
- **Ball.** Three equal rounds at three faces end on the corner ball's sphere [37, 6]. The corner
|
||||
face is always one rational patch exactly on the sphere, of degree (4, 2) by stereographic
|
||||
construction [8], with no analytic sphere special case: one code path.
|
||||
- **Chamfer.** Where every edge at the vertex is chamfered and every chamfer is a plane, the
|
||||
chamfer planes close the corner exactly. The Mitre runs each plane on until it meets the others:
|
||||
three meet at one point M (a 3x3 solve), pairwise along the exact lines from the rails' crossings
|
||||
X_i to M, with no corner face. The Facet cuts the corner off with the plane through the X_i; its
|
||||
edges are the chamfers' end sections. Validity is sign tests on linear forms at the part-scale
|
||||
rounding gate. A mitre point outside the corner is refused, naming the largest size of the
|
||||
too-wide chamfer that keeps it inside. Four or more chamfers build the facet where their
|
||||
crossings are coplanar (their mitre is refused, naming why); where they are not, the corner is a
|
||||
patch whichever type was asked: the chamfers stop at their crossings and n bilinear
|
||||
quadrilaterals round the crossings' centroid close the skew polygon of their end sections, G0
|
||||
to each other and to each chamfer (at four sides, the one bilinear patch of the crossings).
|
||||
Chamfer planes so nearly coplanar
|
||||
that the points where they meet are not determined to the part's rounding are refused, naming
|
||||
that. The corner keeps its definition: its chamfers and the points they meet in (opt-in, WR-716).
|
||||
- **Setback** [44, 43]. Each blend stops at a setback distance from the vertex. The hole has 2n
|
||||
sides: n profile curves (the blends' end sections) alternating with n spring curves on the faces.
|
||||
Plain trimlines that simply meet give poor shapes for unequal radii, edges far from 90° and
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
#### WR-716. Chamfers meeting at a vertex: the mitre's edges are marched, the facet is a Coons patch, and four chamfers are refused with no reason
|
||||
- **Severity:** Wrong result
|
||||
- **Area:** Blend (fillet corners)
|
||||
- **Location:** `source/SMLib/src/SmFilletCorner.cpp:1269` and `:1445`, `SmFilletCorner::Create` (def. `:1129`): a corner where every blended edge use has a linear section is a chamfer corner, skips the Ball, the Setback and the concavity test, and is always `SmFilletConvexNxNCorner` with `SetChamfer(TRUE)`, convex and concave alike. `SmFilletConvexNxNCorner::MakeCornerTopology` (def. `:5642`) in bevel mode refuses any count but three (`:5724`, `SER(SM_ERR)`, "Will not handle other cases for now"). `SmFilletCorner::MakeAnalyticCornerSurface` (def. `:3379`) builds the non-bevel corner face through `sm_CreatePlanarSurface` (`:833`, called at `:3508`). `source/SM_API/src/SmApiFillets.cpp:95` sets every corner of `SmApiChamferFillet` to bevel.
|
||||
- **What happens:** where every edge at a vertex is chamfered and every chamfer is a plane between two planes, the corner is closed exactly by the chamfer planes, but the pinned corner does not build it that way.
|
||||
- **Bevel (the mitre):** the three edges from the rails' crossings X_i to the chamfers' common point M are general surface/surface intersections of the chamfer surfaces (`CalcFilletIntFillet`, `sm_SrfSrfIntersection`), not the planes' exact lines. Four or more chamfers are refused with `SM_ERR` and no reason or size.
|
||||
- **Not bevel (the facet):** the corner face goes through the corner's vertices: an `SmPlane` for three, a bilinear B-spline for four (a plane only if the four are coplanar), refused for more. Where the chamfers are unequal, the corner face can have four sides and is then not a plane. For a 40 x 30 x 20 box with the edges at its (40, 30, 20) corner chamfered 2, 3 and 4 through `fillet_edges_per_edge`, the result has 10 faces and volume 23640.833333. The closed forms are 23645.25 for the mitre (9 faces) and 23644.0 for the facet (10 faces). The corner face's vertices (4, 2, 0), (3, 4, 0), (0, 4, 3), (4, 0, 2), in the corner frame (u, v, w inward), are not coplanar (measured in tangency-labs/solidcore#2345).
|
||||
- **A mitre point outside the corner** (one chamfer much wider than the others) is not detected. Neither branch names a size that builds.
|
||||
- **Trigger:** tangency-labs/smlib#89's brief and its inventory (65 chamfer corners in the fork's suite, all N = 3 convex mitres), ported as tangency-labs/solidcore#2345.
|
||||
- **How found:** porting the generic blend's corner events (`docs/generic-blend.md` §3.2) from the fork's brief.
|
||||
- **Confirmed:** in solidcore, `crates/solidcore/tests/wr697_ball_corner.rs` (`without_the_flag_the_chamfer_corner_is_the_pinned_patch` pins today's corner with the flag off; `per_edge_linear_chamfers_at_a_corner_are_the_mitre` runs `fillet_edges_per_edge` with linear sections on the 2/3/4 corner: 10 faces, 23640.833333, a four-sided B-spline corner face whose fourth corner is off the plane of the other three, without the flag; the mitre's 9 faces and 23645.25 with it).
|
||||
- **Rust port:** `Kernel::fixes.fillet_chamfer_corner` (off by default): the blend corner's chamfer event, `crate::blend::blend_corner::chamfer`, an own design from the brief with no fork code. The corner type is the session's (`FilletSession::set_chamfer_corner`, `BlendChamferCorner`).
|
||||
- **Mitre** (the default): M from the 3x3 solve. The edges X_i-M are exact lines with exact line pcurves on both chamfers, and there is no corner face.
|
||||
- **Facet:** the plane through the X_i, oriented out of the material. Its edges are the chamfers' end sections, exact lines on both faces.
|
||||
- **Corner patch** (the owner's answer 3 on #2345): where four or more chamfers' crossings are not in one plane, the corner is a patch, whichever type was asked. The chamfers stop at their crossings and n bilinear quadrilaterals round the crossings' centroid C close the skew polygon of their end sections: quadrilateral m between C, the middle of the side before X_m, X_m and the middle of the side after it, G0 to each other along the straight spokes from C and to each chamfer along its end section (a chamfer corner is G0, as its chamfers are). At four sides they are the quarters of the crossings' own bilinear patch (the hyperbolic paraboloid through them) and are that one face; past four, no one smooth patch can carry the straight end sections (one polynomial over a polygon has one value where two sides' lines meet, and past four sides non-adjacent sides' lines meet), so they are n faces.
|
||||
- **Definition:** the corner keeps what defines it as plain data (`ChamferCornerDefinition`): the ring's chamfers (edge, width, plane), the X_i, M at a mitre, the facet's normal at a facet, and the patch (the crossings' centroid and their order round it) at a corner patch. It is what the corner-definition type of tangency-labs/solidcore#2488 is to take.
|
||||
- **Classification and validity** use sign tests on linear forms at the part-scale rounding gate. The event declines (and the pinned corner builds) where an edge is not straight, a face not a plane, a chamfer not of constant width, or the chamfers of mixed convexity.
|
||||
- **Refusals:**
|
||||
- A mitre point outside the corner is refused with `FilletErrorType::NoRoom`, naming the largest size of the too-wide chamfer that keeps M inside, less the fit margin, as the refusal's fit. The facet is refused there too.
|
||||
- Four or more chamfers whose crossings lie in one plane build the facet; their mitre is refused, naming why.
|
||||
- A corner patch is refused where a crossing is not on the kept side of a chamfer not through it. It never stands past a face or into a chamfer: every corner of every quadrilateral lies in the crossings' convex hull, which lies in the corner.
|
||||
- Chamfer planes so nearly coplanar (a very flat corner) that the points where they meet are not determined to the part's rounding are refused with `FilletErrorType::CornerUnsupported`, naming that: the 3x3 solves' first-order rounding bound, `u (s + |y|) sum_k |c_k| / |D|`, is above the gate.
|
||||
|
||||
Tests: `crates/solidcore/tests/wr697_ball_corner.rs`, closed forms only: the 20 cube's corners as mitres and facets; `unequal_chamfers_at_a_corner` (2/3/4) as the mitre (M = (2.5, 1.5, 0.5), 24000 - 354.75) and the facet (10u + 9v + 4w = 48, 1.25 more removed); a concave pocket-floor corner both ways; a mitre point outside the corner, refused with its fit, which then builds; an N = 4 pyramid apex; the 2/3/4 corner's definition; a low three-face apex, refused at slope 1/100 and an exact mitre at 5/12.
|
||||
|
||||
- **Retirement (the flag is for the port in progress only):**
|
||||
- At parity the flag becomes the default and is removed, and the pinned 3x3 corner's chamfer branch is deleted with it: `SetChamfer`'s route to `SmFilletConvexNxNCorner` for chamfer corners, its bevel topology and marched mitre edges, and `MakeAnalyticCornerSurface`'s chamfer corner faces (the plane through three vertices and the bilinear patch through four).
|
||||
- Parity here means every chamfer corner that branch builds today is either built by the event or refused, naming why. The event still declines where a chamfer edge is not straight, a face is not a plane, a chamfer is not of constant width, or the chamfers are of mixed convexity; those cases keep the pinned branch until they are covered.
|
||||
- WR-254 (`analytic_corner_failure_is_false`) overlaps: its `SER` / `NER` exits in `MakeAnalyticCornerSurface` include the chamfer faces'. Those exits retire with the chamfer branch. The flag stays for the corners that are not chamfers (the sphere, the surface of revolution, the variable-radius corner).
|
||||
Reference in New Issue
Block a user