mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 10:35:58 +00:00
After the merge with main: its new code on develop's exact bounds, types and thread fields
- The sheet cut's cull (#10301) and the tapped wall's (#10227) use each body's exact box (`exact_box`), not its display mesh. - model.threads replies with develop's thread fields: form, hand (right, left or both) and starts. - surface.boundary's schema checks a network curve's continuity as the handler does, and words a list entry's error as it does. - Main's new tests take develop's Hole fields and world_state's Result. - mesh-decisions: contact_sweep's triangle check is now `depth` (main split `hits`). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ
This commit is contained in:
@@ -3058,9 +3058,9 @@ fn apply_traced_with(
|
||||
if let Some(mb) = state.bodies.get(i).filter(|b| state.sheets.iter().any(|s| s.body == b.name)) {
|
||||
let m = tool_frame(&mb.name);
|
||||
if op == Operation::Cut && m.is_none() && !mb.body.is_mesh() && !tools.iter().any(|t| t.body.is_mesh()) {
|
||||
let reach = reach.get_or_insert_with(|| tools.iter().map(|t| more::body_box(&t.body)).collect());
|
||||
let reach = reach.get_or_insert_with(|| tools.iter().map(|t| more::exact_box(&t.body)).collect());
|
||||
if !reach.is_empty()
|
||||
&& let Some(target) = more::mesh_box(mb)
|
||||
&& let Some(target) = more::exact_box(&mb.body)
|
||||
&& reach.iter().all(|tool| more::boxes_apart(&Some(target), tool))
|
||||
{
|
||||
continue;
|
||||
@@ -4348,7 +4348,7 @@ fn parallel_tapped_wall(st: &ModelState, entry: Vec3, inward: Vec3, radius: f64)
|
||||
// (and the slant its axis may have off `inward` over the body's distance), and its
|
||||
// faces lie in the body's exact box: a body whose box that line misses by more holds
|
||||
// none, and its faces are not read one by one (#10227).
|
||||
let far = more::exact_box(body).map_or(f64::INFINITY, |b| (b.min - entry).len().max((b.max - entry).len()) + b.diagonal());
|
||||
let far = more::exact_box(&body.body).map_or(f64::INFINITY, |b| (b.min - entry).len().max((b.max - entry).len()) + b.diagonal());
|
||||
if far.is_finite() && more::line_beyond_reach(body, entry, inward, radius * 1.01 + tol + 1e-3 + 2e-4 * far) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1419,7 +1419,9 @@ fn a_tapped_hole_reads_the_bodies_along_its_drill_line() {
|
||||
points: None,
|
||||
thread: Some("M6".into()),
|
||||
thread_depth: None,
|
||||
handedness: ThreadHandedness::Right,
|
||||
hand: crate::thread_forms::ThreadHand::Right,
|
||||
modeled: false,
|
||||
ease_out: None,
|
||||
to: None,
|
||||
references: Vec::new(),
|
||||
};
|
||||
|
||||
@@ -1743,17 +1743,18 @@ fn sheet_prism_cut(
|
||||
Ok(handled)
|
||||
}
|
||||
|
||||
/// The exact box of `mb`, grown by twice its tolerance. None when it can't be worked out.
|
||||
pub(super) fn exact_box(mb: &ModelBody) -> Option<Aabb3> {
|
||||
let b = mb.body.exact_bounds().ok()?;
|
||||
(!b.is_empty() && b.min.is_finite() && b.max.is_finite()).then(|| b.inflated(2.0 * mb.body.record().tolerance + 1e-6))
|
||||
/// The exact box of `body`, grown by twice its tolerance: bodies whose boxes are apart do not
|
||||
/// meet. None when it can't be worked out.
|
||||
pub(super) fn exact_box(body: &Body) -> Option<Aabb3> {
|
||||
let b = body.exact_bounds().ok()?;
|
||||
(!b.is_empty() && b.min.is_finite() && b.max.is_finite()).then(|| b.inflated(2.0 * body.record().tolerance + 1e-6))
|
||||
}
|
||||
|
||||
/// Whether the line through `at` along `dir` is known to pass farther than `reach` from the
|
||||
/// exact box of `mb` ([`exact_box`]) everywhere, and so from every point of the body. Unknown is
|
||||
/// never.
|
||||
pub(super) fn line_beyond_reach(mb: &ModelBody, at: Vec3, dir: Vec3, reach: f64) -> bool {
|
||||
let Some(b) = exact_box(mb) else { return false };
|
||||
let Some(b) = exact_box(&mb.body) else { return false };
|
||||
let b = b.inflated(reach);
|
||||
let (mut lo, mut hi) = (f64::NEG_INFINITY, f64::INFINITY);
|
||||
for (a, d, min, max) in [(at.x, dir.x, b.min.x, b.max.x), (at.y, dir.y, b.min.y, b.max.y), (at.z, dir.z, b.min.z, b.max.z)] {
|
||||
|
||||
@@ -160,7 +160,10 @@ fn curvature_comb(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if bool_(p, "clear").unwrap_or(false) {
|
||||
s.comb = None;
|
||||
s.revision += 1;
|
||||
return reply(cmd, CurvatureCombReply::Cleared(AnalysisCleared { analysis: "curvature_comb".into(), cleared: true, param_warnings: Vec::new() }));
|
||||
return reply(
|
||||
cmd,
|
||||
CurvatureCombReply::Cleared(AnalysisCleared { analysis: "curvature_comb".into(), cleared: true, param_warnings: Vec::new() }),
|
||||
);
|
||||
}
|
||||
let add = bool_(p, "add").unwrap_or(false);
|
||||
let shown = if add { s.comb.clone() } else { None };
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::{
|
||||
CrashReport, CrashReportList, CrashReportParams, CrashReportsClearParams, CrashReportsCleared, CrashReportsParams, CrashTestMode, CrashTestParams,
|
||||
ProfileParams, ValidationParams,
|
||||
CrashReport, CrashReportList, CrashReportParams, CrashReportsClearParams, CrashReportsCleared, CrashReportsParams, CrashTestMode,
|
||||
CrashTestParams, ProfileParams, ValidationParams,
|
||||
};
|
||||
use crate::Session;
|
||||
use crate::schema::tests::{assert_keys, assert_strict_keeps_todays_errors, conforms};
|
||||
|
||||
@@ -160,8 +160,12 @@ pub struct ModelThread {
|
||||
pub pitch: f64,
|
||||
/// Whether it is cut into a hole rather than on a shaft.
|
||||
pub internal: bool,
|
||||
/// Its form.
|
||||
pub form: ThreadForm,
|
||||
/// Which way it turns.
|
||||
pub handedness: ThreadHand,
|
||||
pub hand: ThreadHand,
|
||||
/// How many starts it has.
|
||||
pub starts: u32,
|
||||
/// A point on the axis, which `start` and `end` are measured from.
|
||||
pub axis_point: [f64; 3],
|
||||
/// The axis direction, a unit vector.
|
||||
@@ -174,14 +178,34 @@ pub struct ModelThread {
|
||||
|
||||
/// Which way a thread turns.
|
||||
#[derive(Clone, Copy, Debug, Serialize, JsonSchema)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ThreadHand {
|
||||
Right,
|
||||
Left,
|
||||
/// A right- and a left-handed groove crossing on one face (a reversing screw).
|
||||
Both,
|
||||
}
|
||||
|
||||
/// A thread form: ISO metric 60°, metric trapezoidal 30°, Acme 29°, metric buttress 3°/30°,
|
||||
/// buttress inch 7°/45°, square, and the 55° and 60° pipe threads.
|
||||
#[derive(Clone, Copy, Debug, Serialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum ThreadForm {
|
||||
Metric,
|
||||
Trapezoidal,
|
||||
Acme,
|
||||
Buttress,
|
||||
ButtressAsme,
|
||||
Square,
|
||||
Bspp,
|
||||
Rp,
|
||||
Bspt,
|
||||
Npt,
|
||||
}
|
||||
|
||||
impl From<&solidcraft_doc::ThreadInfo> for ModelThread {
|
||||
fn from(t: &solidcraft_doc::ThreadInfo) -> Self {
|
||||
use solidcraft_doc::thread_forms::{ThreadForm as Form, ThreadHand as Hand};
|
||||
ModelThread {
|
||||
feature: t.feature,
|
||||
body: t.body.clone(),
|
||||
@@ -190,10 +214,24 @@ impl From<&solidcraft_doc::ThreadInfo> for ModelThread {
|
||||
radius: t.radius,
|
||||
pitch: t.pitch,
|
||||
internal: t.internal,
|
||||
handedness: match t.handedness {
|
||||
solidcraft_doc::ThreadHandedness::Right => ThreadHand::Right,
|
||||
solidcraft_doc::ThreadHandedness::Left => ThreadHand::Left,
|
||||
form: match t.form {
|
||||
Form::Metric => ThreadForm::Metric,
|
||||
Form::Trapezoidal => ThreadForm::Trapezoidal,
|
||||
Form::Acme => ThreadForm::Acme,
|
||||
Form::Buttress => ThreadForm::Buttress,
|
||||
Form::ButtressAsme => ThreadForm::ButtressAsme,
|
||||
Form::Square => ThreadForm::Square,
|
||||
Form::Bspp => ThreadForm::Bspp,
|
||||
Form::Rp => ThreadForm::Rp,
|
||||
Form::Bspt => ThreadForm::Bspt,
|
||||
Form::Npt => ThreadForm::Npt,
|
||||
},
|
||||
hand: match t.hand {
|
||||
Hand::Right => ThreadHand::Right,
|
||||
Hand::Left => ThreadHand::Left,
|
||||
Hand::Both => ThreadHand::Both,
|
||||
},
|
||||
starts: t.starts,
|
||||
axis_point: t.axis_point.into(),
|
||||
axis: solidcraft_geom::Vec3::from(t.axis).into(),
|
||||
start: t.start,
|
||||
|
||||
@@ -59,7 +59,7 @@ fn model_lists_reply_with_their_schemas() {
|
||||
assert!(r["faces"].as_array().into_iter().flatten().any(|f| f["sheet_face"].as_str().is_some_and(|n| n.starts_with("sheet:"))), "{r}");
|
||||
let r = run(&mut s, "model.threads", json!({}));
|
||||
matches(&threads, &r, false);
|
||||
assert_eq!((r["threads"][0]["designation"].as_str(), r["threads"][0]["handedness"].as_str()), (Some("M8"), Some("right")), "{r}");
|
||||
assert_eq!((r["threads"][0]["designation"].as_str(), r["threads"][0]["hand"].as_str()), (Some("M8"), Some("right")), "{r}");
|
||||
|
||||
matches(&edges, &run(&mut s, "model.edges", json!({"body": "Shaft", "bdy": 1})), true);
|
||||
matches(&faces, &run(&mut s, "model.faces", json!({"body": "Shaft", "bdy": 1})), true);
|
||||
|
||||
@@ -122,7 +122,11 @@ fn flag<'de, D: Deserializer<'de>>(d: D) -> Result<Option<bool>, D::Error> {
|
||||
fn multiplier<'de, D: Deserializer<'de>>(d: D) -> Result<Option<f64>, D::Error> {
|
||||
match Value::deserialize(d)? {
|
||||
Value::Null => Ok(None),
|
||||
v => v.as_f64().filter(|x| (0.01..=100.0).contains(x)).map(Some).ok_or_else(|| D::Error::custom(whole("`length` must be a multiplier 0.01…100"))),
|
||||
v => v
|
||||
.as_f64()
|
||||
.filter(|x| (0.01..=100.0).contains(x))
|
||||
.map(Some)
|
||||
.ok_or_else(|| D::Error::custom(whole("`length` must be a multiplier 0.01…100"))),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::{CenterOfMassParams, CentersOfMass, CurvatureCombParams, CurvatureCombReply, IsocurveParams, IsocurveReply, MinimumRadii, MinimumRadiusParams};
|
||||
use super::{
|
||||
CenterOfMassParams, CentersOfMass, CurvatureCombParams, CurvatureCombReply, IsocurveParams, IsocurveReply, MinimumRadii, MinimumRadiusParams,
|
||||
};
|
||||
use crate::Session;
|
||||
use crate::schema::tests::{assert_keys, assert_strict_keeps_todays_errors, conforms};
|
||||
use crate::schema::{ParamCheck, input_schema_for, output_schema_for};
|
||||
|
||||
#[test]
|
||||
fn schemas_and_the_key_check_agree() {
|
||||
assert_keys(input_schema_for::<CurvatureCombParams>(), &["add", "clear", "count", "curves", "density", "edges", "faces", "length", "scale", "sketch"]);
|
||||
assert_keys(
|
||||
input_schema_for::<CurvatureCombParams>(),
|
||||
&["add", "clear", "count", "curves", "density", "edges", "faces", "length", "scale", "sketch"],
|
||||
);
|
||||
assert_keys(input_schema_for::<MinimumRadiusParams>(), &["curves", "edges", "faces", "sketch"]);
|
||||
assert_keys(input_schema_for::<IsocurveParams>(), &["add", "clear", "count", "faces"]);
|
||||
assert_keys(input_schema_for::<CenterOfMassParams>(), &["bodies"]);
|
||||
|
||||
@@ -131,7 +131,7 @@ impl<'de> Deserialize<'de> for BoundaryContinuity {
|
||||
.map(|x| x.as_str().and_then(Continuity::parse))
|
||||
.collect::<Option<Vec<_>>>()
|
||||
.map(BoundaryContinuity)
|
||||
.ok_or_else(|| D::Error::custom(whole("`continuity` entries must be g0, g1, g2 or g3"))),
|
||||
.ok_or_else(|| D::Error::custom(whole("a `continuity` entry must be g0, g1, g2 or g3"))),
|
||||
_ => Err(D::Error::custom(whole("`continuity` must be one word, or one per curve"))),
|
||||
}
|
||||
}
|
||||
@@ -191,10 +191,7 @@ fn network(key: &str, v: Value) -> Result<(), String> {
|
||||
curves = params::string_list(CMD, item, "curves").map_err(message)?.len();
|
||||
}
|
||||
if let Some(g) = o.get("continuity").filter(|v| !v.is_null()) {
|
||||
let g = g.as_str().and_then(Continuity::parse).ok_or_else(|| format!("{name}.continuity must be g0, g1 or g2"))?;
|
||||
if g == Continuity::G3 {
|
||||
return Err(format!("not supported yet: G3 on {name} (a curve network holds up to g2)"));
|
||||
}
|
||||
let g = params::continuity(CMD, Some(g), &format!("{name}.continuity"), solidcraft_kernel::Construction::Network).map_err(message)?;
|
||||
if g != Continuity::G0 && !on_body {
|
||||
return Err(format!("{name}: g1 and g2 apply to a curve on a body's edges (a surface's open edges)"));
|
||||
}
|
||||
|
||||
@@ -459,7 +459,7 @@ fn a_line_starts_on_a_curve_or_midpoint_and_keeps_that_snap_after_undo() {
|
||||
assert!((actual[0] - expected[0]).abs() < 1e-5 && (actual[1] - expected[1]).abs() < 1e-5, "{actual:?}, expected {expected:?}");
|
||||
}
|
||||
let sid = h.app.session.active_sketch.unwrap();
|
||||
let state = h.app.session.world_state();
|
||||
let state = h.app.session.world_state().unwrap();
|
||||
let sketch = &state.sketch(sid).unwrap().sketch;
|
||||
let guide = sketch.curve_index("guide").unwrap();
|
||||
assert!(
|
||||
@@ -494,7 +494,7 @@ fn a_line_starts_at_a_curve_crossing_and_keeps_it_after_undo() {
|
||||
let drawn = lines(&h);
|
||||
assert_eq!(drawn.len(), 3, "{:?}", h.app.status);
|
||||
assert_line(drawn[2], [-20.0, 10.0], end);
|
||||
let state = h.app.session.world_state();
|
||||
let state = h.app.session.world_state().unwrap();
|
||||
let sketch = &state.sketch(h.app.session.active_sketch.unwrap()).unwrap().sketch;
|
||||
assert_eq!(
|
||||
sketch.constraints.iter().filter(|c| matches!(c.kind, solidcraft_engine::sketch::ConstraintKind::PointOnCurve { .. })).count(),
|
||||
|
||||
@@ -107,9 +107,9 @@ crates/engine/src/cmd/compare.rs:enclosed_volume measures a B-rep body's volume
|
||||
crates/engine/src/cmd/component_clone.rs:build places pasted bodies by meshes
|
||||
crates/engine/src/cmd/component_clone.rs:mesh_distance compares pasted bodies by mesh triangles
|
||||
crates/engine/src/cmd/component_properties.rs:measure_placed measures a placed component by its display mesh
|
||||
crates/engine/src/cmd/contact_sweep.rs:ContactGeometry::depth finds bodies passing through each other by mesh triangles
|
||||
crates/engine/src/cmd/contact_sweep.rs:ContactGeometry::gap measures gaps by mesh triangles
|
||||
crates/engine/src/cmd/contact_sweep.rs:ContactGeometry::gaps measures gaps by mesh triangles
|
||||
crates/engine/src/cmd/contact_sweep.rs:ContactGeometry::hits finds bodies passing through each other by mesh triangles
|
||||
crates/engine/src/cmd/contact_sweep.rs:ContactGeometry::placed places meshes for contact checks
|
||||
crates/engine/src/cmd/contact_sweep.rs:contact_mesh tessellates bodies for contact checks
|
||||
crates/engine/src/cmd/edit.rs:selection_item keeps a selection's face or edge index through the display mesh
|
||||
|
||||
Reference in New Issue
Block a user