mirror of
https://github.com/storytold/lightcraft.git
synced 2026-10-14 19:27:01 -04:00
Merge PR #666
# Conflicts: # apps/lightcraft/src/main.rs # crates/engine/src/config.rs
This commit is contained in:
3 files changed
+56
-9
No files matched your search
@@ -827,11 +827,8 @@ fn main() -> eframe::Result {
|
||||
let (mut session, problem) = open_session(in_memory, library_dir, seed_demo && files.is_empty());
|
||||
// AI masks: the SAM 3 checkpoint (facebook/sam3) in <config>/models/sam3, or LIGHTCRAFT_SAM3_DIR
|
||||
// (never required: without it, AI masks offer to download it; see docs/ai-masks.md)
|
||||
session.segmenter.dir = std::env::var_os("LIGHTCRAFT_SAM3_DIR")
|
||||
.map(std::path::PathBuf::from)
|
||||
.or_else(|| config_dir().map(|d| d.join("models").join("sam3")));
|
||||
// the user's own download locations, one base URL per line (LIGHTCRAFT_SAM3_MIRRORS too)
|
||||
session.segmenter.mirrors_file = config_dir().map(|d| d.join("models").join("sam3-mirrors.txt"));
|
||||
// and the user's own download locations, one base URL per line (LIGHTCRAFT_SAM3_MIRRORS too)
|
||||
session = session.with_default_sam3_model();
|
||||
let mut app = LightcraftApp::new(session, services(cc, app_log_file.as_deref()));
|
||||
if let Some(ui) = prefs {
|
||||
app.ui = ui;
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
//!
|
||||
//! Hosts that have a file system (the desktop app, the CLI, the MCP server) share these defaults so a model
|
||||
//! installed from one is there for the others. Nothing here is applied automatically: a [`Session`] has no
|
||||
//! face- or denoise-models folder until a host asks for one ([`Session::with_default_face_models`],
|
||||
//! [`Session::with_default_denoise_models`]), so tests stay hermetic.
|
||||
//! face-, denoise- or SAM 3 model folder until a host asks for one ([`Session::with_default_face_models`],
|
||||
//! [`Session::with_default_denoise_models`], [`Session::with_default_sam3_model`]), so tests stay hermetic.
|
||||
|
||||
use std::ffi::OsString;
|
||||
use std::path::PathBuf;
|
||||
@@ -58,7 +58,25 @@ pub fn default_denoise_models_dir() -> Option<PathBuf> {
|
||||
.or_else(|| config_dir().map(|d| d.join("denoise-models")))
|
||||
}
|
||||
|
||||
/// Where the SAM 3 model is kept: `$LIGHTCRAFT_SAM3_DIR` if set, else `<config>/models/sam3`.
|
||||
pub fn default_sam3_dir() -> Option<PathBuf> {
|
||||
sam3_dir(std::env::var_os("LIGHTCRAFT_SAM3_DIR"), config_dir())
|
||||
}
|
||||
|
||||
/// [`default_sam3_dir`] for a given override and config folder: a non-empty override wins.
|
||||
fn sam3_dir(var: Option<std::ffi::OsString>, config: Option<PathBuf>) -> Option<PathBuf> {
|
||||
var.filter(|v| !v.is_empty()).map(PathBuf::from).or_else(|| config.map(|d| d.join("models").join("sam3")))
|
||||
}
|
||||
|
||||
impl Session {
|
||||
/// Look for the SAM 3 model in the shared default folder ([`default_sam3_dir`]), and for the user's
|
||||
/// download mirrors in `<config>/models/sam3-mirrors.txt`.
|
||||
pub fn with_default_sam3_model(mut self) -> Self {
|
||||
self.segmenter.dir = default_sam3_dir();
|
||||
self.segmenter.mirrors_file = config_dir().map(|d| d.join("models").join("sam3-mirrors.txt"));
|
||||
self
|
||||
}
|
||||
|
||||
/// Keep face models in the shared default folder ([`default_face_models_dir`]).
|
||||
pub fn with_default_face_models(mut self) -> Self {
|
||||
self.face_models_dir = default_face_models_dir();
|
||||
@@ -130,3 +148,19 @@ mod config_tests {
|
||||
assert_eq!(config_dir_with(Platform::Mac, |_| Some(value.clone())), Some(PathBuf::from(value)));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// A non-empty `LIGHTCRAFT_SAM3_DIR` wins; unset or empty falls back to `<config>/models/sam3` (issue #619).
|
||||
#[test]
|
||||
fn sam3_dir_prefers_the_override() {
|
||||
let config = PathBuf::from("cfg");
|
||||
let fallback = Some(config.join("models").join("sam3"));
|
||||
assert_eq!(sam3_dir(Some("elsewhere".into()), Some(config.clone())), Some(PathBuf::from("elsewhere")));
|
||||
assert_eq!(sam3_dir(None, Some(config.clone())), fallback);
|
||||
assert_eq!(sam3_dir(Some(std::ffi::OsString::new()), Some(config)), fallback);
|
||||
assert_eq!(sam3_dir(None, None), None);
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,8 @@ impl Drop for Headless {
|
||||
|
||||
impl Default for Headless {
|
||||
fn default() -> Self {
|
||||
Self::new(Session::new().with_fs().with_default_denoise_models().with_system_clock().with_default_face_models())
|
||||
let session = Session::new().with_fs().with_default_denoise_models().with_system_clock().with_default_face_models();
|
||||
Self::new(session.with_default_sam3_model())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +44,8 @@ impl Headless {
|
||||
|
||||
/// A headless session with the procedurally generated demo library.
|
||||
pub fn demo() -> Self {
|
||||
Self::new(Session::with_demo().with_fs().with_default_denoise_models().with_system_clock().with_default_face_models())
|
||||
let session = Session::with_demo().with_fs().with_default_denoise_models().with_system_clock().with_default_face_models();
|
||||
Self::new(session.with_default_sam3_model())
|
||||
}
|
||||
|
||||
fn photo_or_active(&self, p: &Value) -> Result<PhotoId, String> {
|
||||
@@ -201,6 +203,20 @@ pub fn write_image(path: &Path, img: &Rgba8, quality: u8) -> Result<(), String>
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Headless sessions look for the SAM 3 model where the desktop app does, and `segment.model.status` reports it (issue #619).
|
||||
#[test]
|
||||
fn headless_sessions_get_the_sam3_model_folder() {
|
||||
let dir = lightcraft_engine::config::default_sam3_dir();
|
||||
assert_eq!(Headless::default().session.segmenter.dir, dir);
|
||||
assert_eq!(Headless::demo().session.segmenter.dir, dir);
|
||||
let dir = std::env::temp_dir().join("lc-mcp-sam3-dir");
|
||||
let mut session = Session::new();
|
||||
session.segmenter.dir = Some(dir.clone());
|
||||
let mut h = Headless::new(session);
|
||||
let r = h.call("engine.execute", json!({"command": "segment.model.status", "params": {}})).unwrap();
|
||||
assert_eq!(r["dir"], json!(dir.display().to_string()), "{r}");
|
||||
}
|
||||
|
||||
/// A headless session stamps imports and edits with the system clock, not the engine's fixed test clock.
|
||||
#[test]
|
||||
fn headless_uses_the_system_clock() {
|
||||
|
||||
Reference in new issue
Block a user