diff --git a/assets/locales/pending/validation-once-per-feature.json b/assets/locales/pending/validation-once-per-feature.json new file mode 100644 index 0000000000..849b18ff3a --- /dev/null +++ b/assets/locales/pending/validation-once-per-feature.json @@ -0,0 +1,65 @@ +{ + "ar": { + "cmd.diagnostics_validation": "التحقق", + "prefs.validation": "التحقق من الأجسام", + "prefs.validation.each_feature": "مرة لكل ميزة", + "prefs.validation.each_step": "بعد كل خطوة", + "prefs.validation.label": "فحص SMLib الكامل", + "prefs.validation.off": "متوقف (تجريبي)", + "prefs.validation_note": "مرة لكل ميزة: تُفحص الأجسام التي تنتجها الميزة مرة واحدة قبل الاحتفاظ بها، وتفشل الميزة إذا رُفض أحدها. بعد كل خطوة: يُفحص ناتج كل عملية سحب ودمج وتقويس، كما في السابق. متوقف: لا فحص، فقد يُحتفظ بجسم معيب." + }, + "de": { + "cmd.diagnostics_validation": "Prüfung", + "prefs.validation": "Körperprüfung", + "prefs.validation.each_feature": "Einmal je Feature", + "prefs.validation.each_step": "Nach jedem Schritt", + "prefs.validation.label": "Vollständige SMLib-Prüfung", + "prefs.validation.off": "Aus (experimentell)", + "prefs.validation_note": "Einmal je Feature: Die Körper, die ein Feature erzeugt, werden einmal geprüft, bevor sie übernommen werden; wird einer abgelehnt, schlägt das Feature fehl. Nach jedem Schritt: Das Ergebnis jedes Sweeps, jeder Boolesche Operation und jeder Rundung wird geprüft, wie bisher. Aus: keine Prüfung, ein fehlerhafter Körper kann bleiben." + }, + "en": { + "cmd.diagnostics_validation": "Validation", + "prefs.validation": "Body check", + "prefs.validation.each_feature": "Once per feature", + "prefs.validation.each_step": "After every step", + "prefs.validation.label": "SMLib's full check", + "prefs.validation.off": "Off (experimental)", + "prefs.validation_note": "Once per feature: the bodies a feature makes are checked once before they are kept, and the feature fails if one is refused. After every step: the result of every sweep, boolean and blend is checked, as before. Off: no check, so a faulty body can be kept." + }, + "he": { + "cmd.diagnostics_validation": "אימות", + "prefs.validation": "בדיקת גופים", + "prefs.validation.each_feature": "פעם אחת לכל תכונה", + "prefs.validation.each_step": "אחרי כל שלב", + "prefs.validation.label": "הבדיקה המלאה של SMLib", + "prefs.validation.off": "כבוי (ניסיוני)", + "prefs.validation_note": "פעם אחת לכל תכונה: הגופים שתכונה יוצרת נבדקים פעם אחת לפני שהם נשמרים, והתכונה נכשלת אם אחד מהם נדחה. אחרי כל שלב: התוצאה של כל סחיפה, פעולה בוליאנית ועיגול נבדקת, כמו קודם. כבוי: אין בדיקה, ולכן גוף פגום עלול להישמר." + }, + "ja": { + "cmd.diagnostics_validation": "検証", + "prefs.validation": "ボディのチェック", + "prefs.validation.each_feature": "フィーチャーごとに 1 回", + "prefs.validation.each_step": "各ステップの後", + "prefs.validation.label": "SMLib の完全チェック", + "prefs.validation.off": "オフ (試験的)", + "prefs.validation_note": "フィーチャーごとに 1 回: フィーチャーが作るボディを保持する前に 1 回チェックし、拒否されたものがあればフィーチャーは失敗します。各ステップの後: これまでどおり、スイープ、ブーリアン、ブレンドのたびに結果をチェックします。オフ: チェックしないため、不正なボディが残ることがあります。" + }, + "ko": { + "cmd.diagnostics_validation": "검증", + "prefs.validation": "바디 검사", + "prefs.validation.each_feature": "피처마다 한 번", + "prefs.validation.each_step": "모든 단계 후", + "prefs.validation.label": "SMLib 전체 검사", + "prefs.validation.off": "끄기 (실험적)", + "prefs.validation_note": "피처마다 한 번: 피처가 만든 바디를 유지하기 전에 한 번 검사하며, 하나라도 거부되면 피처가 실패합니다. 모든 단계 후: 이전처럼 스윕, 불리언, 블렌드마다 결과를 검사합니다. 끄기: 검사하지 않으므로 결함 있는 바디가 남을 수 있습니다." + }, + "zh": { + "cmd.diagnostics_validation": "验证", + "prefs.validation": "实体检查", + "prefs.validation.each_feature": "每个特征一次", + "prefs.validation.each_step": "每一步之后", + "prefs.validation.label": "SMLib 完整检查", + "prefs.validation.off": "关闭(实验性)", + "prefs.validation_note": "每个特征一次:特征生成的实体在保留前检查一次,若有实体被拒绝,该特征失败。每一步之后:与以前一样,每次扫掠、布尔运算和倒圆后都检查结果。关闭:不检查,因此可能保留有缺陷的实体。" + } +} diff --git a/crates/ui-egui/src/documents.rs b/crates/ui-egui/src/documents.rs index 8e859fb55e..8b9c351b5c 100644 --- a/crates/ui-egui/src/documents.rs +++ b/crates/ui-egui/src/documents.rs @@ -160,6 +160,7 @@ fn swap_in(app: &mut SolveApp, mut slot: DocSlot) -> DocSlot { app.session.auto_project = out.session.auto_project; app.session.metric_tap_drill_major_less_pitch = out.session.metric_tap_drill_major_less_pitch; app.session.surface_extrude_symmetric_per_side = out.session.surface_extrude_symmetric_per_side; + app.session.set_validation(out.session.validation()); // So does the host's pre-command check: every tab is held to it. app.session.set_command_check(out.session.command_check()); // Views cache by revision: the incoming design must not look unchanged. diff --git a/crates/ui-egui/src/file_load.rs b/crates/ui-egui/src/file_load.rs index 8bb48e18e4..bcf5fa58f7 100644 --- a/crates/ui-egui/src/file_load.rs +++ b/crates/ui-egui/src/file_load.rs @@ -408,8 +408,10 @@ fn start(app: &mut SolveApp) { let before = if insert { crate::open_meshing::session_world(&app.session).bodies.iter().map(|b| b.name.clone()).collect() } else { Vec::new() }; let update = request.kind == Kind::UpdateLinks; let mut scratch = if insert || update || request.kind == Kind::ExportDxf { app.session.scratch() } else { Session::default() }; - // A design opened or recovered here is run through the host's pre-command check too. + // A design opened or recovered here is run through the host's pre-command check too, and + // evaluated under the user's validation mode. scratch.set_command_check(app.session.command_check()); + scratch.set_validation(app.preferences.validation); if insert { scratch.selection = app.session.selection.clone(); } @@ -537,6 +539,7 @@ fn start_staged(app: &mut SolveApp) { let before = if insert { crate::open_meshing::session_world(&app.session).bodies.iter().map(|b| b.name.clone()).collect() } else { Vec::new() }; let mut session = if insert { app.session.scratch() } else { Session::default() }; session.set_command_check(app.session.command_check()); + session.set_validation(app.preferences.validation); if insert { session.selection = app.session.selection.clone(); } diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index 8b0819186c..e467206210 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -982,6 +982,7 @@ impl SolveApp { // next frame applies them. self.session.metric_tap_drill_major_less_pitch = self.preferences.metric_tap_drill_major_less_pitch; self.session.surface_extrude_symmetric_per_side = self.preferences.surface_extrude_symmetric_per_side; + self.session.set_validation(self.preferences.validation); let t0 = now_ms(); let r = self.session.execute(id, ¶ms).map_err(|e| e.to_string()); self.big_operation |= now_ms() - t0 >= recovery_ui::BIG_OPERATION_MS; @@ -1010,6 +1011,8 @@ impl SolveApp { self.ui.visual_style = index; } } + // The command and the preference are one setting. + "diagnostics.validation" => self.preferences.validation = self.session.validation(), "view.grid" => self.ui.show_grid = v.get("on").and_then(Value::as_bool).unwrap_or(!self.ui.show_grid), "view.ground_shadow" => self.ui.ground_shadow = v.get("on").and_then(Value::as_bool).unwrap_or(!self.ui.ground_shadow), "view.navigation_bar" => { diff --git a/crates/ui-egui/src/prefs.rs b/crates/ui-egui/src/prefs.rs index 21286834f1..89fd87ba33 100644 --- a/crates/ui-egui/src/prefs.rs +++ b/crates/ui-egui/src/prefs.rs @@ -641,6 +641,10 @@ preference_settings! { /// extrudes; off by default: the distance is the total, split evenly around the start plane. /// Saved designs keep the reading they stored either way. surface_extrude_symmetric_per_side: bool = false, PAGE_DESIGN => check_id("prefs.surface_extrude_symmetric_per_side"); + /// When SMLib's full check runs while features are computed (`diagnostics.validation`): + /// `each_feature` (the default) checks the bodies each feature keeps once; `each_step`, as + /// before, checks after every sweep, boolean and blend; `off` (experimental) never does. + validation: solidcraft_engine::kernel::Validation = solidcraft_engine::kernel::Validation::EachFeature, PAGE_DESIGN; /// Reopen the saved designs that were open when the app was last closed (#5542). reopen_designs: bool = true, PAGE_GENERAL; /// Those designs' files, in tab order, as the last quit left them. @@ -1130,6 +1134,8 @@ pub fn apply(app: &mut SolveApp, ctx: &egui::Context) { // symmetric reading. app.session.metric_tap_drill_major_less_pitch = app.preferences.metric_tap_drill_major_less_pitch; app.session.surface_extrude_symmetric_per_side = app.preferences.surface_extrude_symmetric_per_side; + // The features computed from now on are checked as the preference says. + app.session.set_validation(app.preferences.validation); // The profiler records while the preference is on (switched when it changes; at start-up only // when on, so SOLIDCRAFT_PROFILE=1 is not undone by the default). let profile = app.preferences.profile_operations; @@ -2341,6 +2347,11 @@ pub fn set(app: &mut SolveApp, p: &serde_json::Value) -> Result "prefs.validation.each_step", + Validation::EachFeature => "prefs.validation.each_feature", + Validation::Off => "prefs.validation.off", + }; + let labels: Vec = Validation::ALL.iter().map(|v| app.tr(label(v)).to_string()).collect(); + let options: Vec<&str> = labels.iter().map(String::as_str).collect(); + let mut selected = Validation::ALL.iter().position(|v| *v == app.preferences.validation).unwrap_or(1); + ui_kit::row(ui, app.tr("prefs.validation.label"), |ui| { + ui_kit::segmented(ui, &mut selected, &options, ui_kit::Segments { intrinsic: true, ..Default::default() }); + }); + if let Some(v) = Validation::ALL.get(selected) { + app.preferences.validation = *v; + } + ui_kit::text(ui, app.tr("prefs.validation_note"), ui_kit::Text::Hint("validation")); + }); ui_kit::settings_section(ui, "orientation", app.tr("prefs.orientation"), |ui| { ui.horizontal_wrapped(|ui| { for (id, label, y_up) in [("z", "Z up", false), ("y", "Y up", true)] {