Kernel choice removed: solidcore is the only kernel

The C++ SMLib kernel goes (owner, 2026-11-02), so nothing chooses a kernel
any more:
- diagnostics.kernel and its schema types (KernelParams, KernelReply,
  KernelChoice) are gone, with their tests;
- the Geometry kernel preference and its section of the Design page are
  gone, with assets/locales/pending/geometry-kernel.json. A saved
  preference file that still names a kernel loads without error and the
  key is ignored, like any removed setting
  (saved_preferences_with_a_removed_setting_still_load);
- the model cache records "solidcore" as the kernel that computed it, so a
  cache an earlier build wrote on the C++ kernel is still a miss;
- the CLI test of a cache saved on one kernel and opened on the other is
  gone (the io test covers the miss).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ
This commit is contained in:
jyviko
2026-11-02 17:56:23 -05:00
parent 4757402675
commit fa1b4a9b9d
9 changed files with 19 additions and 410 deletions
-41
View File
@@ -386,44 +386,3 @@ fn a_design_saved_before_names_were_pinned_opens_pinned_and_unchanged() {
}
let _ = std::fs::remove_file(&path);
}
/// A saved model is its kernel's: the two differ in tolerances and in what they refuse, so a design
/// opened on the other kernel is evaluated on it, in both directions; on the kernel that saved it the
/// model is taken as it is. A process each, since the kernel is chosen for the process.
/// It reaches the C++ kernel on purpose: SOLIDCRAFT_KERNEL=smlib is the option under test.
#[cfg(feature = "smlib")]
#[test]
fn a_model_saved_on_one_kernel_is_evaluated_again_on_the_other() {
let cli = || {
let mut c = std::process::Command::new(env!("CARGO_BIN_EXE_solidcraft-cli"));
c.env_remove("SOLIDCRAFT_NO_MODEL_CACHE");
c
};
// Under the build's own temporary folder: the files are the command line's, not this process's.
let temp = |name: &str| Path::new(env!("CARGO_TARGET_TMPDIR")).join(format!("model-cache-{}-{name}.solidcraft", std::process::id()));
let script = temp("kernels-script").with_extension("json");
let recipe = json!({"commands": [
{"command": "solid.box", "params": {"length": 20, "width": 20, "height": 10, "body_name": "Plate"}},
{"command": "solid.cylinder", "params": {"diameter": 8, "height": 10, "base": [10, 10, 10], "operation": "join", "targets": ["Plate"]}},
]});
std::fs::write(&script, recipe.to_string()).unwrap();
// Whether opening found a model in the file it may take, whether it took it, and how many
// features it evaluated.
let open = |design: &Path, kernel: &str| {
let out = cli().env("SOLIDCRAFT_KERNEL", kernel).arg("bench-open").arg(design).output().unwrap();
assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr));
let lines: Vec<Value> = String::from_utf8_lossy(&out.stdout).lines().filter_map(|l| serde_json::from_str(l).ok()).collect();
let step = |name: &str| lines.iter().find(|l| l["step"] == name).cloned().unwrap();
let (parse, evaluated) = (step("parse"), step("evaluate"));
(parse["saved_model"].as_bool().unwrap(), evaluated["adopted"].as_bool().unwrap(), evaluated["recomputed"].as_u64().unwrap())
};
for (saved, other) in [("solidcore", "smlib"), ("smlib", "solidcore")] {
let design = temp(&format!("kernels-{saved}"));
let out = cli().env("SOLIDCRAFT_KERNEL", saved).arg("run").arg(&script).arg("--save").arg(&design).output().unwrap();
assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr));
assert_eq!(open(&design, saved), (true, true, 0), "saved on {saved}, opened on it");
assert_eq!(open(&design, other), (false, false, 2), "saved on {saved}, opened on {other}");
std::fs::remove_file(&design).unwrap();
}
std::fs::remove_file(&script).unwrap();
}
@@ -1,58 +0,0 @@
{
"en": {
"cmd.diagnostics_kernel": "Geometry Kernel",
"prefs.geometry_kernel": "Geometry kernel",
"prefs.geometry_kernel_environment": "The SOLIDCRAFT_KERNEL environment variable is set; it wins over this setting.",
"prefs.geometry_kernel_note": "solidcore, the default, answers every operation. Choose SMLib for the C++ kernel. A solidcore failure is reported, never retried on SMLib.",
"prefs.geometry_kernel_unavailable": "SMLib is not available in this build. Operations run on solidcore."
},
"ar": {
"cmd.diagnostics_kernel": "نواة الهندسة",
"prefs.geometry_kernel": "نواة الهندسة",
"prefs.geometry_kernel_environment": "متغير البيئة SOLIDCRAFT_KERNEL مضبوط، وله الأولوية على هذا الإعداد.",
"prefs.geometry_kernel_note": "يجيب solidcore، وهو الافتراضي، عن كل العمليات. اختر SMLib لاستخدام نواة C++‎. ويُبلَّغ عن إخفاق solidcore ولا تُعاد المحاولة أبدًا على SMLib.",
"prefs.geometry_kernel_unavailable": "SMLib غير متاح في هذا الإصدار. تعمل العمليات على solidcore."
},
"de": {
"cmd.diagnostics_kernel": "Geometriekernel",
"prefs.geometry_kernel": "Geometriekernel",
"prefs.geometry_kernel_environment": "Die Umgebungsvariable SOLIDCRAFT_KERNEL ist gesetzt; sie hat Vorrang vor dieser Einstellung.",
"prefs.geometry_kernel_note": "solidcore, die Standardeinstellung, führt alle Vorgänge aus. Wählen Sie SMLib für den C++-Kernel. Ein Fehler in solidcore wird gemeldet, nie mit SMLib wiederholt.",
"prefs.geometry_kernel_unavailable": "SMLib ist in diesem Build nicht verfügbar. Vorgänge laufen auf solidcore."
},
"fr": {
"cmd.diagnostics_kernel": "Noyau géométrique",
"prefs.geometry_kernel": "Noyau géométrique",
"prefs.geometry_kernel_environment": "La variable d’environnement SOLIDCRAFT_KERNEL est définie ; elle l’emporte sur ce réglage.",
"prefs.geometry_kernel_note": "solidcore, le réglage par défaut, traite toutes les opérations. Choisissez SMLib pour le noyau C++. Un échec de solidcore est signalé, jamais réessayé avec SMLib.",
"prefs.geometry_kernel_unavailable": "SMLib n’est pas disponible dans cette version. Les opérations s’exécutent sur solidcore."
},
"he": {
"cmd.diagnostics_kernel": "ליבת גאומטריה",
"prefs.geometry_kernel": "ליבת גאומטריה",
"prefs.geometry_kernel_environment": "משתנה הסביבה SOLIDCRAFT_KERNEL מוגדר, והוא גובר על הגדרה זו.",
"prefs.geometry_kernel_note": "solidcore, ברירת המחדל, מבצע את כל הפעולות. בחרו ב-SMLib עבור ליבת ה-C++‎. כשל של solidcore מדווח, ולעולם אינו מנוסה שוב ב-SMLib.",
"prefs.geometry_kernel_unavailable": "SMLib אינו זמין בגרסה זו. הפעולות מתבצעות ב-solidcore."
},
"ja": {
"cmd.diagnostics_kernel": "ジオメトリ カーネル",
"prefs.geometry_kernel": "ジオメトリ カーネル",
"prefs.geometry_kernel_environment": "環境変数 SOLIDCRAFT_KERNEL が設定されているため、この設定より優先されます。",
"prefs.geometry_kernel_note": "既定の solidcore がすべての処理を行います。C++ カーネルを使うには SMLib を選択してください。solidcore の失敗は報告され、SMLib で再試行されることはありません。",
"prefs.geometry_kernel_unavailable": "このビルドでは SMLib を利用できません。処理は solidcore で実行されます。"
},
"vi": {
"cmd.diagnostics_kernel": "Nhân hình học",
"prefs.geometry_kernel": "Nhân hình học",
"prefs.geometry_kernel_environment": "Biến môi trường SOLIDCRAFT_KERNEL đã được đặt; biến này được ưu tiên hơn thiết lập này.",
"prefs.geometry_kernel_note": "solidcore, mặc định, xử lý mọi thao tác. Chọn SMLib để dùng nhân C++. Lỗi của solidcore được báo cáo, không bao giờ thử lại trên SMLib.",
"prefs.geometry_kernel_unavailable": "Bản dựng này không có SMLib. Các thao tác chạy trên solidcore."
},
"zh": {
"cmd.diagnostics_kernel": "几何内核",
"prefs.geometry_kernel": "几何内核",
"prefs.geometry_kernel_environment": "已设置环境变量 SOLIDCRAFT_KERNEL,它优先于此设置。",
"prefs.geometry_kernel_note": "默认的 solidcore 处理所有操作。如需使用 C++ 内核,请选择 SMLib。solidcore 失败时会报告,绝不会改用 SMLib 重试。",
"prefs.geometry_kernel_unavailable": "此版本不提供 SMLib。操作在 solidcore 上运行。"
}
}
-40
View File
@@ -60,17 +60,6 @@ pub static COMMANDS: &[CommandSpec] = &[
inside it; total counts its outermost run only. Recording also starts with SOLIDCRAFT_PROFILE=1, the CLI's \
--profile and the app's preference; off, it costs nothing measurable. Read-only",
),
CommandSpec::new("diagnostics.kernel", "Geometry Kernel", crate::checked!("diagnostics.kernel", kernel))
.icon("bug_report")
.noundo()
.contract(&KERNEL_CONTRACT)
.output(output_schema_for::<crate::schema::diagnostics::KernelReply>)
.params(
"choice?: solidcore | smlib (sets which kernel answers every operation: solidcore, the default = the Rust port; \
smlib = the C++ SMLib kernel, refused as not available on a build without it, such as the web app; bodies already \
computed keep their results). SOLIDCRAFT_KERNEL, when set, wins over it. Returns {choice, environment}: choice is \
the kernel in force, environment whether SOLIDCRAFT_KERNEL chose it",
),
CommandSpec::new("diagnostics.validation", "Validation", crate::checked!("diagnostics.validation", validation))
.icon("bug_report")
.noundo()
@@ -96,34 +85,6 @@ pub static COMMANDS: &[CommandSpec] = &[
),
];
fn kernel(_: &mut Session, p: &Value) -> Result<Value> {
use solidcraft_kernel::kernel_route as route;
if let Some(v) = p.get("choice").filter(|v| !v.is_null()) {
route::set_choice(kernel_asked(v, solidcraft_kernel::smlib_built())?);
}
use crate::schema::diagnostics::{KernelChoice, KernelReply};
let choice = match route::choice() {
route::Backend::Solidcore => KernelChoice::Solidcore,
route::Backend::Smlib => KernelChoice::Smlib,
};
serde_json::to_value(KernelReply { choice, environment: route::overridden() }).map_err(|e| bad("diagnostics.kernel", e.to_string()))
}
/// The kernel `choice` asks for, on a build that has (`built`) the C++ kernel or doesn't. A choice of
/// the wrong type is refused like an unknown one, not read as no choice; `smlib` on a build without
/// it is refused too, never answered by the other kernel.
pub(crate) fn kernel_asked(choice: &Value, built: bool) -> Result<solidcraft_kernel::kernel_route::Backend> {
use solidcraft_kernel::kernel_route::Backend;
let asked = choice.as_str().and_then(Backend::from_id).ok_or_else(|| bad("diagnostics.kernel", "`choice` is solidcore or smlib"))?;
if asked == Backend::Smlib && !built {
return Err(EngineError::Disabled(
"diagnostics.kernel".into(),
"this build doesn't include the C++ SMLib kernel; build with --features smlib".into(),
));
}
Ok(asked)
}
fn dir(cmd: &str) -> Result<std::path::PathBuf> {
crash::default_dir().ok_or_else(|| bad(cmd, "no crash folder on this system"))
}
@@ -182,7 +143,6 @@ fn crash_test(_: &mut Session, p: &Value) -> Result<Value> {
const PROFILE_CONTRACT: crate::schema::TypedContract<crate::schema::diagnostics::ProfileParams> = crate::schema::TypedContract::new();
const KERNEL_CONTRACT: crate::schema::TypedContract<crate::schema::diagnostics::KernelParams> = crate::schema::TypedContract::new();
const VALIDATION_CONTRACT: crate::schema::TypedContract<crate::schema::diagnostics::ValidationParams> = crate::schema::TypedContract::new();
fn validation(s: &mut Session, p: &Value) -> Result<Value> {
-38
View File
@@ -330,44 +330,6 @@ impl From<solidcraft_kernel::Validation> for ValidationMode {
}
}
/// Read, or set, which kernel answers every operation.
#[derive(Debug, Default, Deserialize, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct KernelParams {
/// The kernel to use from now on. Default: leave it as it is. `smlib` is refused on a build
/// without the C++ kernel; `SOLIDCRAFT_KERNEL`, when set, wins over it.
#[serde(default, deserialize_with = "kernel_choice")]
pub choice: Option<KernelChoice>,
}
/// A geometry kernel.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum KernelChoice {
/// solidcore, the Rust port of SMLib (the default).
Solidcore,
/// The C++ SMLib kernel.
Smlib,
}
fn kernel_choice<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Option<KernelChoice>, D::Error> {
match Value::deserialize(d)? {
Value::Null => Ok(None),
Value::String(s) if s == "solidcore" => Ok(Some(KernelChoice::Solidcore)),
Value::String(s) if s == "smlib" => Ok(Some(KernelChoice::Smlib)),
_ => Err(serde::de::Error::custom(super::check::whole("`choice` is solidcore or smlib"))),
}
}
/// The reply of `diagnostics.kernel`.
#[derive(Debug, Serialize, JsonSchema)]
pub struct KernelReply {
/// The kernel in force now.
pub choice: KernelChoice,
/// Whether `SOLIDCRAFT_KERNEL` chose it.
pub environment: bool,
}
/// Read, or set, when SMLib's full check runs on the features computed from now on.
#[derive(Debug, Default, Deserialize, JsonSchema)]
#[schemars(deny_unknown_fields)]
+1 -92
View File
@@ -2,7 +2,7 @@ use serde_json::{Value, json};
use super::{
CrashReport, CrashReportList, CrashReportParams, CrashReportsClearParams, CrashReportsCleared, CrashReportsParams, CrashTestMode,
CrashTestParams, KernelParams, ProfileParams, ValidationParams,
CrashTestParams, ProfileParams, ValidationParams,
};
use crate::Session;
use crate::schema::tests::{assert_keys, assert_strict_keeps_todays_errors, conforms};
@@ -16,7 +16,6 @@ fn schemas_and_the_key_check_agree() {
assert_keys(input_schema_for::<CrashTestParams>(), &["mode"]);
assert_keys(input_schema_for::<ProfileParams>(), &["enabled", "reset", "top", "trace"]);
assert_keys(input_schema_for::<ValidationParams>(), &["mode"]);
assert_keys(input_schema_for::<KernelParams>(), &["choice"]);
}
#[test]
@@ -30,8 +29,6 @@ fn strict_keeps_todays_errors() {
("diagnostics.profile", json!({"trace": 5})),
("diagnostics.validation", json!({"mode": "each step"})),
("diagnostics.validation", json!({"mode": true})),
("diagnostics.kernel", json!({"choice": "cpp"})),
("diagnostics.kernel", json!({"choice": 1})),
]);
}
@@ -154,94 +151,6 @@ fn diagnostics_profile_reports_commands_features_and_the_kernel() {
let _ = std::fs::remove_dir_all(&dir);
}
/// Tests that set the process-wide kernel choice run one at a time, and put `solidcore` back
/// however they end (a failed assertion included).
struct KernelChoice(#[allow(dead_code)] std::sync::MutexGuard<'static, ()>);
static KERNEL_TESTS: std::sync::Mutex<()> = std::sync::Mutex::new(());
impl KernelChoice {
fn take() -> Self {
KernelChoice(KERNEL_TESTS.lock().unwrap_or_else(std::sync::PoisonError::into_inner))
}
}
impl Drop for KernelChoice {
fn drop(&mut self) {
solidcraft_kernel::kernel_route::set_choice(solidcraft_kernel::kernel_route::Backend::Solidcore);
}
}
/// diagnostics.kernel says the kernel in force and whether the environment chose it, and its
/// `choice` sets it (unless SOLIDCRAFT_KERNEL overrides it).
///
/// The choice is process-wide and the other tests in this binary build bodies meanwhile, so only
/// `solidcore` is set through the command; what `smlib` asks for is read from `kernel_asked`.
#[test]
fn diagnostics_kernel_reports_and_sets_the_choice() {
use solidcraft_kernel::kernel_route as route;
let _choice = KernelChoice::take();
let mut s = Session::default();
let r = s.execute("diagnostics.kernel", &json!({})).unwrap();
let environment = route::overridden();
assert_eq!(r, json!({ "choice": route::choice().id(), "environment": environment }));
let r = s.execute("diagnostics.kernel", &json!({ "choice": "solidcore" })).unwrap();
assert!(environment || r["choice"] == json!("solidcore"), "{r}");
assert_eq!(crate::cmd::diagnostics::kernel_asked(&json!("solidcore"), false).unwrap(), route::Backend::Solidcore);
assert_eq!(crate::cmd::diagnostics::kernel_asked(&json!("solidcore"), true).unwrap(), route::Backend::Solidcore);
assert_eq!(crate::cmd::diagnostics::kernel_asked(&json!("smlib"), true).unwrap(), route::Backend::Smlib);
}
/// `smlib` on a build without the C++ kernel is refused as not available, whichever kernel is in
/// force, and leaves the choice alone: never an answer from the other kernel. Built with it, the
/// command would move the other tests' bodies, so `kernel_asked` alone is asked.
#[test]
fn diagnostics_kernel_refuses_smlib_on_a_build_without_it() {
let _choice = KernelChoice::take();
let refused = crate::cmd::diagnostics::kernel_asked(&json!("smlib"), false).unwrap_err();
assert!(matches!(&refused, crate::EngineError::Disabled(cmd, _) if cmd == "diagnostics.kernel"), "{refused}");
if !solidcraft_kernel::smlib_built() {
let mut s = Session::default();
let before = s.execute("diagnostics.kernel", &json!({})).unwrap();
assert!(matches!(s.execute("diagnostics.kernel", &json!({"choice": "smlib"})), Err(crate::EngineError::Disabled(..))));
assert_eq!(s.execute("diagnostics.kernel", &json!({})).unwrap(), before);
}
}
/// An unknown or mistyped choice is an error that changes nothing; no hostile value panics.
#[test]
fn diagnostics_kernel_refuses_unknown_choices() {
let _choice = KernelChoice::take();
let mut s = Session::default();
let before = s.execute("diagnostics.kernel", &json!({})).unwrap()["choice"].clone();
let huge = "solidcore".repeat(100_000);
for bad in [
json!("truck"),
json!("SMLIB"),
json!(""),
json!(huge),
json!("routed"),
json!("routed\0"),
json!(5),
json!(true),
json!(["smlib"]),
json!({"choice": "smlib"}),
] {
match s.execute("diagnostics.kernel", &json!({ "choice": bad })) {
Err(crate::EngineError::BadParams { .. }) => {}
other => panic!("`{}` was not refused: {:?}", bad.as_str().map_or(0, str::len), other.map_err(|e| e.to_string())),
}
}
for v in crate::tests::hostile_values() {
for p in [json!({ "choice": v.clone() }), v] {
if let Err(crate::EngineError::Internal(_, msg)) = s.execute("diagnostics.kernel", &p) {
panic!("diagnostics.kernel {p}: {msg}");
}
}
}
assert_eq!(s.execute("diagnostics.kernel", &json!({})).unwrap()["choice"], before);
}
/// Features are checked once each by default; the command reads and sets the mode, and the mode
/// is the user's: it stays when another design is started and when everything is computed again.
#[test]
+5 -7
View File
@@ -1548,13 +1548,11 @@ mod tests {
manifest.as_object_mut().unwrap().remove("evaluation");
entries.insert("cache/model.json".into(), serde_json::to_vec(&manifest).unwrap());
assert!(read_design_geometry(&package(&entries)).is_none());
// The kernel that computed it is recorded, and another kernel's model is evaluated again:
// the same source gives different bodies on the two (a cache without one is a miss too).
use solidcraft_kernel::kernel_route::{Backend, choice};
assert_eq!(written(&original)["kernel"], json!(choice().id()));
let other = if choice() == Backend::Solidcore { Backend::Smlib } else { Backend::Solidcore };
let bytes = with(&[("kernel", other.id())]);
assert!(read_design_geometry(&bytes).is_none(), "{}'s cache", other.id());
// The kernel that computed it is recorded, and a model the C++ kernel computed (an earlier
// build's) is evaluated again (a cache without one is a miss too).
assert_eq!(written(&original)["kernel"], json!(native_cache::KERNEL));
let bytes = with(&[("kernel", "smlib")]);
assert!(read_design_geometry(&bytes).is_none(), "the C++ kernel's cache");
assert_eq!(crate::read_design(&bytes).unwrap().to_json(), doc.to_json());
let mut entries = original.clone();
let mut manifest = written(&entries);
+6 -7
View File
@@ -29,11 +29,10 @@ fn evaluation() -> String {
format!("{}/{}-{}", env!("SOLIDCRAFT_EVALUATION_SOURCE"), std::env::consts::ARCH, std::env::consts::OS)
}
/// The geometry kernel that answers in this process: another kernel's cache is not its result (the
/// two differ in tolerances and in what they refuse), so a design opened under it evaluates again.
fn kernel() -> &'static str {
solidcraft_kernel::kernel_route::choice().id()
}
/// The geometry kernel that computed a cache. A cache another kernel wrote (the C++ SMLib, which
/// earlier builds could run on) is not this build's result: the kernels differ in tolerances and in
/// what they refuse, so a design opened from it evaluates again.
pub(super) const KERNEL: &str = "solidcore";
/// A 4 x 4 matrix as its 16 numbers, in storage order.
fn matrix(t: &[[f64; 4]; 4]) -> Vec<f64> {
@@ -302,7 +301,7 @@ impl Encoded {
document: digest(document),
evaluation: evaluation(),
format: FORMAT,
kernel: kernel(),
kernel: KERNEL,
model: records,
payload: digest(&data),
producer: PRODUCER,
@@ -352,7 +351,7 @@ fn current(manifest: &Value, document: &[u8]) -> bool {
is("format", FORMAT)
&& is("producer", PRODUCER)
&& is("evaluation", &evaluation())
&& is("kernel", kernel())
&& is("kernel", KERNEL)
&& is("document", &digest(document))
&& source_trusted(env!("SOLIDCRAFT_EVALUATION_SOURCE"), cfg!(any(test, feature = "trust-unknown-source")))
}
+7 -103
View File
@@ -37,8 +37,6 @@ use crate::ui_kit;
use crate::viewport::NavMode;
pub const UNITS: &[&str] = &["mm", "cm", "m", "in", "ft"];
/// The Geometry kernel preference's choices, as `diagnostics.kernel` spells them.
pub const KERNELS: [&str; 2] = ["solidcore", "smlib"];
pub const OPENSCAD_NAV: &str = "openscad";
pub const TOUCHPAD_NAV: &str = "touchpad";
/// A mouse button that can drag the camera.
@@ -1161,11 +1159,6 @@ preference_settings! {
/// Up/Down and the wheel over a focused value field step its plain number (`value_step`,
/// #2320). Off by default: the keys move the text cursor and the wheel scrolls, as before.
step_values: bool = false, PAGE_DESIGN => check_id("ui.step_values");
/// Which geometry kernel answers every operation (one of [`KERNELS`]): `solidcore`, the
/// default, or `smlib`, the C++ kernel. A saved `routed`, the old default, reads as
/// `solidcore` ([`migrate`]). Applied through
/// `diagnostics.kernel`; `SOLIDCRAFT_KERNEL`, when set, wins.
geometry_kernel: String = "solidcore".into(), PAGE_DESIGN;
/// 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.
@@ -1633,14 +1626,6 @@ pub struct PrefsWindow {
/// The Profile operations preference the profiler last followed: applied when it changes,
/// so `diagnostics.profile` (an agent) and SOLIDCRAFT_PROFILE can switch it in between.
applied_profile: Option<bool>,
/// The Geometry kernel preference `diagnostics.kernel` last applied: applied when it changes
/// (and at startup), so an agent's `diagnostics.kernel` in between stands.
applied_kernel: Option<String>,
/// The choice `SOLIDCRAFT_KERNEL` forces when it overrides the preference (the command's
/// `environment` and `choice`).
pub(crate) kernel_environment: Option<String>,
/// The saved kernel is one this build doesn't have (`smlib` without the C++ kernel): the page says so.
pub(crate) kernel_unavailable: bool,
/// The appearance last asked of the native window ([`sync_window_theme`]).
window_theme: Option<egui::SystemTheme>,
/// The perspective angle the view was last drawn with ([`follow_view_fov`]); 0 before the first.
@@ -2313,26 +2298,6 @@ pub fn apply(app: &mut SolveApp, ctx: &egui::Context) {
}
app.prefs_window.applied_profile = Some(profile);
}
// The geometry kernel, through its command (an unknown saved value is the default). `smlib` on
// a build without it is refused: the preference page and the status line say so.
let kernel = KERNELS.iter().copied().find(|k| *k == app.preferences.geometry_kernel).unwrap_or(KERNELS[0]);
if app.prefs_window.applied_kernel.as_deref() != Some(kernel) {
match app.session.execute("diagnostics.kernel", &json!({ "choice": kernel })) {
Ok(v) => {
let environment = v.get("environment").and_then(serde_json::Value::as_bool).unwrap_or(false);
app.prefs_window.kernel_environment =
environment.then(|| v.get("choice").and_then(serde_json::Value::as_str).unwrap_or_default().to_string());
app.prefs_window.kernel_unavailable = false;
}
Err(e) => {
log::warn!("geometry kernel preference `{kernel}`: {e}");
app.prefs_window.kernel_unavailable = true;
let message = app.tr("prefs.geometry_kernel_unavailable").to_string();
app.set_status(message, true);
}
}
app.prefs_window.applied_kernel = Some(kernel.into());
}
apply_file_limits(&app.preferences);
// The viewport's display quality (an unknown saved value draws at Normal).
solidcraft_engine::doc::display_quality::set_current(
@@ -4057,10 +4022,6 @@ pub fn migrate(mut p: Prefs) -> Prefs {
p.orbit_around = OrbitAround::Cursor;
p.orbit_cursor = false;
}
// `routed`, the old default, ran every operation on solidcore.
if p.geometry_kernel == "routed" {
p.geometry_kernel = KERNELS[0].into();
}
if !crate::space_mouse::drives_each_once(p.space_mouse_axis_source) {
log::warn!("preferences: the 3D mouse axis assignment {:?} doesn't drive each axis once; using the default", p.space_mouse_axis_source);
p.space_mouse_axis_source = crate::space_mouse::SAME_AXES;
@@ -4201,10 +4162,6 @@ fn prepare_preferences(
{
return Err("`validation` must be each_step, each_feature or off".into());
}
// Before `migrate`, which reads the old saved `routed` as the default: asked for now, it is refused.
if p.get("geometry_kernel").is_some_and(|v| !v.as_str().is_some_and(|k| KERNELS.contains(&k))) {
return Err(format!("`geometry_kernel` must be one of {}", KERNELS.join(", ")));
}
if let Some(value) = p.get("navigation_bar") {
crate::navigation_bar::validate(value)?;
}
@@ -4228,9 +4185,6 @@ fn prepare_preferences(
if !UNITS.contains(&new.default_units.as_str()) {
return Err(format!("units must be one of {}", UNITS.join(", ")));
}
if !KERNELS.contains(&new.geometry_kernel.as_str()) {
return Err(format!("`geometry_kernel` must be one of {}", KERNELS.join(", ")));
}
if !WINDOW_SYSTEMS.contains(&new.window_system.as_str()) {
return Err(format!("`window_system` must be one of {}", WINDOW_SYSTEMS.join(", ")));
}
@@ -4408,68 +4362,18 @@ mod tests {
assert!(!solidcraft_engine::profile::enabled());
}
/// The Geometry kernel preference reaches the kernel through diagnostics.kernel, at startup
/// and when it changes, and only a choice the command knows is accepted. `smlib` keeps the C++
/// kernel reachable, the behaviour from before solidcore was the default. (The kernel choice is
/// process-wide and other tests start harnesses meanwhile, so `smlib` is only set on a build
/// without it, where it is refused and nothing moves.)
#[test]
fn the_geometry_kernel_preference_applies_through_its_command() {
use serde_json::json;
let mut h = crate::scenario::Harness::new();
h.frames(1);
assert_eq!(h.app.prefs_window.applied_kernel.as_deref(), Some("solidcore"), "applied at startup");
let r = h.app.session.execute("diagnostics.kernel", &json!({})).unwrap();
let environment = (r["environment"] == json!(true)).then(|| r["choice"].as_str().unwrap_or_default().to_string());
assert_eq!(h.app.prefs_window.kernel_environment, environment);
assert!(h.call("ui.prefs", json!({"geometry_kernel": "truck"}))["error"].as_str().is_some_and(|e| e.contains("geometry_kernel")));
// `routed` is gone: a saved one reads as solidcore (below), asked for now it is refused.
assert!(h.call("ui.prefs", json!({"geometry_kernel": "routed"}))["error"].as_str().is_some_and(|e| e.contains("geometry_kernel")));
assert_eq!(h.app.preferences.geometry_kernel, "solidcore");
assert_eq!(super::KERNELS, ["solidcore", "smlib"]);
h.call("ui.prefs", json!({"geometry_kernel": "solidcore"}));
h.frames(2);
assert_eq!(h.app.prefs_window.applied_kernel.as_deref(), Some("solidcore"));
assert!(!h.app.prefs_window.kernel_unavailable);
// Saved with the other preferences.
let saved = serde_json::to_value(&h.app.preferences).unwrap();
assert_eq!(saved["geometry_kernel"], json!("solidcore"));
// On a build without the C++ kernel, asking for it says so: not a switch to the other one.
if !solidcraft_engine::kernel::smlib_built() {
h.call("ui.prefs", json!({"geometry_kernel": "smlib"}));
h.frames(2);
assert!(h.app.prefs_window.kernel_unavailable);
assert_eq!(h.app.preferences.geometry_kernel, "smlib");
assert_ne!(h.app.session.execute("diagnostics.kernel", &json!({})).unwrap()["choice"], json!("smlib"));
h.call("ui.prefs", json!({"geometry_kernel": "solidcore"}));
h.frames(2);
assert!(!h.app.prefs_window.kernel_unavailable);
}
}
/// A saved `routed`, the old default, loads as `solidcore`, without an error; any other value
/// this version doesn't know stays as it was saved and runs on the default, and nothing panics.
#[test]
fn a_saved_routed_kernel_loads_as_solidcore() {
use serde_json::json;
let read = |saved: serde_json::Value| super::migrate(serde_json::from_value::<super::Prefs>(saved).unwrap()).geometry_kernel;
assert_eq!(read(json!({"geometry_kernel": "routed"})), "solidcore");
assert_eq!(read(json!({})), "solidcore");
assert_eq!(read(json!({"geometry_kernel": "smlib"})), "smlib");
assert_eq!(read(json!({"geometry_kernel": "truck"})), "truck");
let mut h = crate::scenario::Harness::new();
h.app.preferences.geometry_kernel = "truck".into();
h.frames(2);
assert_eq!(h.app.prefs_window.applied_kernel.as_deref(), Some("solidcore"));
}
/// Preferences saved with a setting this version no longer has still load, every other
/// setting kept: the removed key is ignored like any unknown one.
#[test]
fn saved_preferences_with_a_removed_setting_still_load() {
use serde_json::json;
for removed in ["metric_tap_drill_major_less_pitch", "surface_extrude_symmetric_per_side"] {
let saved = json!({"length_decimals": 4, removed: true});
for (removed, value) in [
("geometry_kernel", json!("smlib")),
("geometry_kernel", json!("solidcore")),
("metric_tap_drill_major_less_pitch", json!(true)),
("surface_extrude_symmetric_per_side", json!(true)),
] {
let saved = json!({"length_decimals": 4, removed: value});
let p = serde_json::from_value::<super::Prefs>(saved.clone()).unwrap();
assert_eq!(p.length_decimals, 4, "{removed}");
assert_eq!(super::lenient(&saved).length_decimals, 4, "{removed}");
-24
View File
@@ -136,28 +136,4 @@ fn origin_plane_names(app: &mut SolveApp, ui: &mut egui::Ui) {
setting::hide_origin(app, ui);
ui_kit::text(ui, app.tr("ui.hide_origin_note"), ui_kit::Text::Hint("hide_origin"));
});
// Which geometry kernel answers; `prefs::apply` applies it through diagnostics.kernel.
ui_kit::settings_section(ui, "geometry_kernel", app.tr("prefs.geometry_kernel"), |ui| {
// Under SOLIDCRAFT_KERNEL the control shows the environment's choice, disabled.
let environment = app.prefs_window.kernel_environment.clone();
ui.add_enabled_ui(environment.is_none(), |ui| {
let options = ["solidcore", "SMLib"];
let shown = environment.as_deref().unwrap_or(&app.preferences.geometry_kernel);
let mut selected = KERNELS.iter().position(|k| *k == shown).unwrap_or(0);
ui_kit::segmented(ui, &mut selected, &options, ui_kit::Segments { intrinsic: true, ..Default::default() });
if let Some(kernel) = KERNELS.get(selected)
&& environment.is_none()
&& app.preferences.geometry_kernel != *kernel
{
app.preferences.geometry_kernel = (*kernel).into();
}
});
ui_kit::text(ui, app.tr("prefs.geometry_kernel_note"), ui_kit::Text::Hint("geometry_kernel"));
if environment.is_some() {
ui_kit::text(ui, app.tr("prefs.geometry_kernel_environment"), ui_kit::Text::Hint("geometry_kernel_environment"));
}
if app.prefs_window.kernel_unavailable {
ui_kit::text(ui, app.tr("prefs.geometry_kernel_unavailable"), ui_kit::Text::Hint("geometry_kernel_unavailable"));
}
});
}