doc: a hole in sheet metal is a cut-out or a bore by its drill's direction

sheet_hole_cut chose by failure: a hole's drills were tried all at once and, if that was
refused, one by one; each drill was tried as a flat cut-out and, if that was refused (or
machined nothing), as a bore. Now the drill's geometry decides, one path each:

- SheetBody::drilled_panels: the panels a straight drill meets and how square it is to each
  (|panel normal · axis|); a drill into a bend is refused, as before. add_bore uses it.
- sheet_drill: square across every panel it meets is Drill::Across (sheet_primitive_cut, a
  cut-out or pocket of the flat pattern), along every one into a rim is Drill::Along
  (sheet_bore_cut, a bore), meeting no sheet is None, and anything else (square to one panel
  and along another, or at an angle) is refused with the angle message.
- The all-at-once pass (sheet_drill_cuts) passes its errors on; its None (the drills not all
  square along one axis) still puts the model back and drills them one by one.
- sheet_bore_cut passes add_bore's refusal on instead of answering None.

(not built: base in progress)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017itGh5fpMEousnZ91cYcVt
This commit is contained in:
jyviko
2026-10-22 07:38:28 -04:00
parent 431c3f6d0c
commit 4ca7793a3c
3 changed files with 65 additions and 32 deletions
+9 -16
View File
@@ -3991,15 +3991,15 @@ fn sheet_hole_cut(
// Many plain drills along one axis (a hole on each of a pattern's points): all in one pass.
let prisms: Option<Vec<more::PrimitivePrism>> = tools.iter().map(|(_, prism)| *prism).collect();
if let Some(prisms) = prisms {
match more::sheet_drill_cuts(f, st, &prisms, &mut parted) {
Ok(Some(done)) => {
match more::sheet_drill_cuts(f, st, &prisms, &mut parted)? {
Some(done) => {
if let Some(note) = parted {
append_warning(warning, note);
}
return Ok(done);
}
// Not all of them that way, or refused at once: one by one, from the model as it was.
Ok(None) | Err(_) => {
// Not all of them that way: one by one, from the model as it was.
None => {
(st.sheets, st.bodies) = saved.clone();
parted = None;
}
@@ -4007,22 +4007,15 @@ fn sheet_hole_cut(
}
for (tool, prism) in tools {
let tool = &tool.body;
// Square across the panels it meets, a flat cut-out; along them into a rim, a bore.
let done = match prism {
Some(prism) => match more::sheet_primitive_cut(f, st, tool, *prism, Operation::Cut, true, &mut parted) {
Ok(done) => done,
// Drilled along a panel (into a rim) the hole is no flat cut-out: a bore of
// that panel, when it stays inside it.
Err(e) => match more::sheet_bore_cut(f, st, *prism)? {
Some(done) => done,
None => return Err(e),
},
Some(prism) => match more::sheet_drill(f, st, *prism)? {
Some(more::Drill::Across) => more::sheet_primitive_cut(f, st, tool, *prism, Operation::Cut, true, &mut parted)?,
Some(more::Drill::Along) => more::sheet_bore_cut(f, st, *prism)?.unwrap_or_default(),
None => Vec::new(),
},
None => Vec::new(),
};
let done = match (done.is_empty(), prism) {
(true, Some(prism)) => more::sheet_bore_cut(f, st, *prism)?.unwrap_or_default(),
_ => done,
};
if done.is_empty() {
let mut meets = false;
for sheet in &st.sheets {
+33 -7
View File
@@ -1065,11 +1065,39 @@ pub(super) fn sheet_primitive_cut(
sheet_prism_cut(f, st, &plane, std::slice::from_ref(&region), operation, &tools, &[], None, false, &[], warning)
}
/// How a hole's straight drill machines the sheets it meets, from the panels it passes through.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum Drill {
/// Square across every panel it meets: a cut-out or pocket of the flat pattern
/// ([`sheet_primitive_cut`]).
Across,
/// Along every panel it meets, into a rim: a bore of each ([`sheet_bore_cut`]).
Along,
}
/// Which [`Drill`] a hole's straight drill is for the sheets `f` machines; None when it meets
/// none of them. A box is a prism across its sheet. A drill square to some panels it meets and
/// along others, or at an angle to one, has no exact place in the flat pattern and is refused.
pub(super) fn sheet_drill(f: &Feature, st: &ModelState, prism: PrimitivePrism) -> Result<Option<Drill>> {
let PrimitivePrism::Cylinder { base, axis, radius, height } = prism else { return Ok(Some(Drill::Across)) };
let mut squares = Vec::new();
for sh in st.sheets.iter().filter(|sh| sheet_eligible(f, st, &[], sh)) {
squares.extend(sh.drilled_panels(base, axis, radius, height)?.into_iter().map(|(_, square)| square));
}
if squares.is_empty() {
Ok(None)
} else if squares.iter().all(|s| *s >= 1.0 - 1e-6) {
Ok(Some(Drill::Across))
} else if squares.iter().all(|s| *s <= 1e-6) {
Ok(Some(Drill::Along))
} else {
Err(DocError::Invalid("this hole crosses a sheet panel at an angle; drill it square to the panel, or along it into a rim".into()))
}
}
/// A hole's straight drill along a panel of a sheet (into its rim), kept as a bore of each
/// panel it meets (see [`crate::sheet::SheetBore`]), so the sheet still unfolds. Returns the
/// sheet bodies it machined; None, with the model as it was, when the drill is not a plain
/// cylinder or a sheet it meets can't keep it that way (it runs into a bend, or crosses a
/// panel at an angle).
/// sheet bodies it machined; None when the drill is not a plain cylinder or meets no sheet.
pub(super) fn sheet_bore_cut(f: &Feature, st: &mut ModelState, prism: PrimitivePrism) -> Result<Option<Vec<String>>> {
let PrimitivePrism::Cylinder { base, axis, radius, height } = prism else { return Ok(None) };
let mut plans = Vec::new();
@@ -1081,8 +1109,6 @@ pub(super) fn sheet_bore_cut(f: &Feature, st: &mut ModelState, prism: PrimitiveP
match sh.add_bore(base, axis, radius, height) {
Ok(true) => {}
Ok(false) => continue,
// Refused here, the hole keeps the message of the machining it was first tried as.
Err(DocError::Invalid(_)) => return Ok(None),
Err(e) => return Err(e),
}
let body = sh.solid()?;
@@ -1112,8 +1138,8 @@ const MAX_DRILLS_AT_ONCE: usize = 1000;
/// of them, rather than one per drill, each on a sheet machined by the ones before.
/// The drills are the cut-outs and pockets [`sheet_primitive_cut`] makes of them one at a time.
/// None when they can't all be taken this way (they are not all cylinders along one axis that
/// every sheet they may machine takes square, or a sheet keeps one some other way): the caller
/// then puts the model back and drills them one by one.
/// every sheet they may machine takes square, or a depth of them meets no sheet): the caller then
/// puts the model back and drills them one by one.
pub(super) fn sheet_drill_cuts(
f: &Feature,
st: &mut ModelState,
+23 -9
View File
@@ -1691,18 +1691,14 @@ impl SheetBody {
Ok(body)
}
/// Record a round bore drilled along the sheet (a hole into a rim): the world cylinder from
/// `base` along `axis`, one record per panel whose material it meets. Ok(false) when it
/// meets no material; an error when it meets a bend, or crosses a panel other than along
/// its plane (that machining has no exact place in the flat pattern).
pub(crate) fn add_bore(&mut self, base: Vec3, axis: Vec3, radius: f64, height: f64) -> Result<bool> {
/// The panels a straight drill (the world cylinder from `base` along `axis`) meets, each with
/// how square the drill is to it: |panel normal · axis|, 1 across the panel and 0 along it.
/// A drill that meets a bend is refused (a bend's material has no flat place for a hole).
pub(crate) fn drilled_panels(&self, base: Vec3, axis: Vec3, radius: f64, height: f64) -> Result<Vec<(Option<usize>, f64)>> {
let axis = axis.normalized().ok_or_else(|| DocError::Invalid("bore axis".into()))?;
if ![base.x, base.y, base.z, radius, height].iter().all(|v| v.is_finite()) || !(radius > 0.0) || height == 0.0 {
return Err(DocError::Invalid("invalid sheet bore".into()));
}
if self.bores.len() >= MAX_BORES {
return Err(flat_budget_error());
}
let mut work = FlatWork::new();
let drill = kernel::cylinder(base, axis, radius, height)?;
let mut found = Vec::new();
@@ -1726,11 +1722,29 @@ impl SheetBody {
));
};
let w = self.world(if self.flat { None } else { pi });
if apply_vector(&w, Vec3::Z).dot(axis).abs() > 1e-6 {
found.push((pi, apply_vector(&w, Vec3::Z).dot(axis).abs()));
}
Ok(found)
}
/// Record a round bore drilled along the sheet (a hole into a rim): the world cylinder from
/// `base` along `axis`, one record per panel whose material it meets. Ok(false) when it
/// meets no material; an error when it meets a bend, or crosses a panel other than along
/// its plane (that machining has no exact place in the flat pattern).
pub(crate) fn add_bore(&mut self, base: Vec3, axis: Vec3, radius: f64, height: f64) -> Result<bool> {
if self.bores.len() >= MAX_BORES {
return Err(flat_budget_error());
}
let panels = self.drilled_panels(base, axis, radius, height)?;
let axis = axis.normalized().ok_or_else(|| DocError::Invalid("bore axis".into()))?;
let mut found = Vec::new();
for (pi, square) in panels {
if square > 1e-6 {
return Err(DocError::Invalid(
"this hole crosses a sheet panel at an angle; drill it square to the panel, or along it into a rim".into(),
));
}
let w = self.world(if self.flat { None } else { pi });
let inv = mat_inverse(&w).ok_or_else(|| DocError::Invalid("invalid sheet panel frame".into()))?;
let bore = SheetBore { panel: pi, base: apply_point(&inv, base), axis: apply_vector(&inv, axis), radius, height };
if !found.contains(&bore) {