mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 02:25:55 +00:00
Repair Part: the repair runs once, as the feature evaluates
The command worked the repair out itself for its reply and the feature worked it out again when it evaluated. The evaluation now keeps the kernel's report on the feature's result (as an import keeps its import report, cached with it), and the command's reply is built from that: one repair per call, and a part with nothing to repair fails the evaluation, which leaves the design unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+13
-1
@@ -734,6 +734,7 @@ struct CachedEval {
|
||||
error: Option<String>,
|
||||
warning: Option<String>,
|
||||
import_report: Option<Arc<kernel::ImportReport>>,
|
||||
repair: Option<Arc<kernel::RepairReport>>,
|
||||
ms: f64,
|
||||
state: Arc<ModelState>,
|
||||
/// What the state costs, worked out once when it was stored (see
|
||||
@@ -943,6 +944,9 @@ pub struct FeatureResult {
|
||||
/// (`warning` is the first of them as the kernel words them).
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub import_report: Option<Arc<kernel::ImportReport>>,
|
||||
/// What a Repair Part feature changed, face by face (the command's reply says it).
|
||||
#[serde(skip)]
|
||||
pub repair: Option<Arc<kernel::RepairReport>>,
|
||||
pub ms: f64,
|
||||
pub skipped: bool,
|
||||
/// Not evaluated on the document's current inputs: a feature at or after a sketch shown live
|
||||
@@ -1874,6 +1878,7 @@ impl Model {
|
||||
error: None,
|
||||
warning: None,
|
||||
import_report: None,
|
||||
repair: None,
|
||||
ms: 0.0,
|
||||
skipped: true,
|
||||
stale: false,
|
||||
@@ -1910,6 +1915,7 @@ impl Model {
|
||||
error: c.error,
|
||||
warning: c.warning,
|
||||
import_report: c.import_report,
|
||||
repair: c.repair,
|
||||
ms: c.ms,
|
||||
skipped: false,
|
||||
stale: false,
|
||||
@@ -1944,6 +1950,7 @@ impl Model {
|
||||
}
|
||||
let mut warning = None;
|
||||
let mut import_report = None;
|
||||
let mut repair = None;
|
||||
// Face references by name: move each face point onto its named face (on the body the
|
||||
// feature names, when it names one and the face is there).
|
||||
let renamed;
|
||||
@@ -1967,7 +1974,8 @@ impl Model {
|
||||
let r = match frame_error.or(bad_param) {
|
||||
Some(e) => Err(kernel::KernelError::Failed(e)),
|
||||
None => kernel::guard(&f.name, || {
|
||||
eval_feature(doc, &vals, f, &mut next, &mut warning, &mut import_report).map_err(|e| kernel::KernelError::Failed(e.to_string()))
|
||||
eval_feature(doc, &vals, f, &mut next, &mut warning, &mut import_report, &mut repair)
|
||||
.map_err(|e| kernel::KernelError::Failed(e.to_string()))
|
||||
}),
|
||||
};
|
||||
if r.is_ok() {
|
||||
@@ -2023,6 +2031,7 @@ impl Model {
|
||||
error: error.clone(),
|
||||
warning: warning.clone(),
|
||||
import_report: import_report.clone(),
|
||||
repair: repair.clone(),
|
||||
ms,
|
||||
bytes: state.added_bytes(&before),
|
||||
state: state.clone(),
|
||||
@@ -2041,6 +2050,7 @@ impl Model {
|
||||
error,
|
||||
warning,
|
||||
import_report,
|
||||
repair,
|
||||
ms,
|
||||
skipped: false,
|
||||
stale: false,
|
||||
@@ -6237,6 +6247,7 @@ fn eval_feature(
|
||||
st: &mut ModelState,
|
||||
warning: &mut Option<String>,
|
||||
import_report: &mut Option<Arc<kernel::ImportReport>>,
|
||||
repair: &mut Option<Arc<kernel::RepairReport>>,
|
||||
) -> Result<()> {
|
||||
// Its tools meet bodies in other placements where those are shown (#3383).
|
||||
let _frames = ToolFrames::install(doc, f, st);
|
||||
@@ -7040,6 +7051,7 @@ fn eval_feature(
|
||||
}
|
||||
let target = st.bodies.get_mut(index).ok_or_else(|| DocError::Unknown(format!("body `{body}`")))?;
|
||||
*target = result;
|
||||
*repair = Some(Arc::new(report));
|
||||
Ok(())
|
||||
}
|
||||
FeatureKind::MeshImport { meshes, .. } => {
|
||||
|
||||
@@ -596,6 +596,7 @@ impl Model {
|
||||
.get("import_report")
|
||||
.map(|v| serde_json::from_value::<kernel::ImportReport>(v.clone()).map(Arc::new).map_err(|_| bad("saved model: import report")))
|
||||
.transpose()?,
|
||||
repair: None,
|
||||
ms,
|
||||
skipped: r.get("skipped").and_then(Json::as_bool).ok_or_else(|| bad("saved model: skipped"))?,
|
||||
stale: false,
|
||||
@@ -634,6 +635,7 @@ impl Model {
|
||||
error: None,
|
||||
warning: r.warning.clone(),
|
||||
import_report: r.import_report.clone(),
|
||||
repair: r.repair.clone(),
|
||||
ms: r.ms,
|
||||
bytes: r.state.retained_bytes(),
|
||||
state: r.state.clone(),
|
||||
|
||||
@@ -815,12 +815,12 @@ pub(crate) fn counted(f: &solidcraft_doc::Feature, i: usize) -> bool {
|
||||
f.body_names.get(i).is_none_or(|n| n.trim().is_empty()) && (f.shell_piece_order || i < f.body_names.len() || !first)
|
||||
}
|
||||
|
||||
/// Repair Part (#8371): the part's repair is worked out first (refused with why when nothing
|
||||
/// applies), then made a feature, which works it out again as it evaluates; the reply says what
|
||||
/// changed, face by face.
|
||||
/// Repair Part (#8371): a feature, which repairs the part as it evaluates (a part with nothing
|
||||
/// to repair fails it, with why, and the command changes nothing); the reply is what that
|
||||
/// evaluation changed, face by face.
|
||||
fn repair_body(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
use solidcraft_doc::expr::Kind;
|
||||
use solidcraft_kernel::{Continuity, RepairOptions, SurfaceKind};
|
||||
use solidcraft_kernel::{Continuity, SurfaceKind};
|
||||
let cmd = "body.repair";
|
||||
let name = str_(p, "body").filter(|b| !b.trim().is_empty()).ok_or_else(|| bad(cmd, "`body` must name the part to repair"))?.to_string();
|
||||
let state = s.model.state();
|
||||
@@ -830,11 +830,17 @@ fn repair_body(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
let gap = expr(p, "gap").unwrap_or_else(|| "0.2 mm".into());
|
||||
check_expr(s, &gap, Kind::Length, cmd, "gap")?;
|
||||
let gap_mm = s.doc.eval(&gap, Kind::Length).map_err(|e| bad(cmd, format!("gap: {e}")))?;
|
||||
let patch_holes = bool_(p, "patch_holes").unwrap_or(false);
|
||||
let (repaired, report) =
|
||||
solidcraft_kernel::repair(&source.body, &RepairOptions { gap: gap_mm, patch_holes }).map_err(|e| bad(cmd, e.to_string()))?;
|
||||
let volume = if report.closed { solidcraft_kernel::measure(&repaired).ok().map(|m| m.volume) } else { None };
|
||||
let v = add_feature(s, p, FeatureKind::Repair { body: name.clone().into(), gap, patch_holes })?;
|
||||
let field = |k: &str| v.get(k).cloned().unwrap_or(Value::Null);
|
||||
let feature = field("feature").as_u64().unwrap_or(0);
|
||||
let report = s
|
||||
.model
|
||||
.result(feature)
|
||||
.and_then(|r| r.repair.clone())
|
||||
.ok_or_else(|| crate::EngineError::Other(format!("{cmd}: the feature kept no report of the repair")))?;
|
||||
let repaired = s.model.state().body(&name).map(|b| b.body.clone());
|
||||
let volume = repaired.filter(|_| report.closed).and_then(|b| solidcraft_kernel::measure(&b).ok()).map(|m| m.volume);
|
||||
let surface = |k: SurfaceKind| format!("{k:?}").to_lowercase();
|
||||
let face = |face: usize, k: SurfaceKind, why: &str| RepairFace { face, surface: surface(k), why: why.to_string() };
|
||||
let done = RepairDone {
|
||||
@@ -862,13 +868,10 @@ fn repair_body(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
modelling: report.modelling,
|
||||
volume,
|
||||
};
|
||||
let kind = FeatureKind::Repair { body: name.clone().into(), gap, patch_holes };
|
||||
let v = add_feature(s, p, kind)?;
|
||||
let field = |k: &str| v.get(k).cloned().unwrap_or(Value::Null);
|
||||
let list =
|
||||
|k: &str| v.get(k).and_then(Value::as_array).map(|a| a.iter().filter_map(Value::as_str).map(str::to_string).collect()).unwrap_or_default();
|
||||
let reply = RepairResult {
|
||||
feature: field("feature").as_u64().unwrap_or(0),
|
||||
feature,
|
||||
name: field("name").as_str().unwrap_or_default().to_string(),
|
||||
bodies: list("bodies"),
|
||||
created_bodies: list("created_bodies"),
|
||||
|
||||
Reference in New Issue
Block a user