Compare commits
10 Commits
72bb486238
...
a89fe6e075
| Author | SHA1 | Date | |
|---|---|---|---|
| a89fe6e075 | |||
| 1ece03e336 | |||
| 0071f32499 | |||
| f6b6d9a9f6 | |||
| 91a71c8e93 | |||
| eb501bea58 | |||
| 45c74d2377 | |||
| 619f4452d0 | |||
| 8f1f82b447 | |||
| 5c2dab4343 |
1
.gitignore
vendored
@@ -78,3 +78,4 @@ yarn-error.log*
|
||||
*.o
|
||||
*.lock
|
||||
*.dylib
|
||||
*.app
|
||||
|
||||
19
README.md
@@ -139,6 +139,7 @@ The generator automatically appends `, equirectangular 360 view` to prompts that
|
||||
- Log files from desktop generations are saved in `logs/`.
|
||||
- PNG metadata includes a `prompt` text field containing the enriched prompt.
|
||||
- Temporary generation folders are created under the project root and cleaned up by the generator.
|
||||
- In the packaged macOS app, bundled maps are seeded into `~/Library/Application Support/com.skymap.gen/output/`, and new maps/logs are written under `~/Library/Application Support/com.skymap.gen/`.
|
||||
|
||||
Generated outputs, model weights, local caches, virtual environments, and build artifacts are ignored by Git.
|
||||
|
||||
@@ -158,13 +159,13 @@ If generation is slow or memory constrained, reduce `width`, `height`, or `steps
|
||||
|
||||
```text
|
||||
.
|
||||
├── generate_equirect.py # Diffusers generation and post-processing CLI
|
||||
├── run.sh # One-command development launcher
|
||||
├── src/ # Vite/Three.js frontend
|
||||
├── src-tauri/ # Tauri Rust desktop backend
|
||||
├── configs/ # SDXL single-file loading config
|
||||
├── public/ # Static frontend assets
|
||||
├── models/ # Local model weights, ignored by Git
|
||||
├── output/ # Generated PNG maps, ignored by Git
|
||||
└── logs/ # Desktop generation logs, ignored by Git
|
||||
|-- generate_equirect.py # Diffusers generation and post-processing CLI
|
||||
|-- run.sh # One-command development launcher
|
||||
|-- src/ # Vite/Three.js frontend
|
||||
|-- src-tauri/ # Tauri Rust desktop backend
|
||||
|-- configs/ # SDXL single-file loading config
|
||||
|-- public/ # Static frontend assets
|
||||
|-- models/ # Local model weights, ignored by Git
|
||||
|-- output/ # Generated PNG maps, ignored by Git
|
||||
`-- logs/ # Desktop generation logs, ignored by Git
|
||||
```
|
||||
|
||||
BIN
src-tauri/icons/128x128.png
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
src-tauri/icons/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
src-tauri/icons/32x32.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
src-tauri/icons/Square107x107Logo.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
src-tauri/icons/Square142x142Logo.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
src-tauri/icons/Square150x150Logo.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
src-tauri/icons/Square284x284Logo.png
Normal file
|
After Width: | Height: | Size: 77 KiB |
BIN
src-tauri/icons/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
src-tauri/icons/Square310x310Logo.png
Normal file
|
After Width: | Height: | Size: 89 KiB |
BIN
src-tauri/icons/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
src-tauri/icons/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
BIN
src-tauri/icons/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
src-tauri/icons/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
src-tauri/icons/icon.icns
Normal file
BIN
src-tauri/icons/icon.ico
Normal file
|
After Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 139 KiB |
@@ -14,6 +14,7 @@ use tauri::{async_runtime, Env, State, Window};
|
||||
#[derive(Default, Clone)]
|
||||
struct Paths {
|
||||
project_root: PathBuf,
|
||||
data_root: PathBuf,
|
||||
active_generation_pid: Arc<Mutex<Option<u32>>>,
|
||||
cancel_generation_requested: Arc<AtomicBool>,
|
||||
}
|
||||
@@ -166,6 +167,35 @@ fn generation_log_path(root: &Path, prompt: &str) -> Result<PathBuf, String> {
|
||||
}
|
||||
}
|
||||
|
||||
fn seed_output_dir(resource_root: &Path, data_root: &Path) -> Result<(), String> {
|
||||
let source_dir = resource_root.join("output");
|
||||
if !source_dir.exists() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let dest_dir = output_dir(data_root)?;
|
||||
for entry in fs::read_dir(&source_dir).map_err(|e| format!("Failed to read bundled output dir: {e}"))? {
|
||||
let entry = entry.map_err(|e| format!("Failed to read bundled output entry: {e}"))?;
|
||||
let path = entry.path();
|
||||
if path
|
||||
.extension()
|
||||
.and_then(|s| s.to_str())
|
||||
.map(|s| s.eq_ignore_ascii_case("png"))
|
||||
!= Some(true)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let filename = path
|
||||
.file_name()
|
||||
.ok_or_else(|| "Bundled output entry has no filename".to_string())?;
|
||||
let dest = dest_dir.join(filename);
|
||||
if !dest.exists() {
|
||||
fs::copy(&path, &dest).map_err(|e| format!("Failed to seed bundled map: {e}"))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write_log_line(log_file: &mut File, line: impl AsRef<str>) {
|
||||
let _ = writeln!(log_file, "{}", line.as_ref());
|
||||
let _ = log_file.flush();
|
||||
@@ -256,7 +286,7 @@ fn read_png_prompt(path: &Path) -> Option<String> {
|
||||
|
||||
#[tauri::command]
|
||||
fn list_maps(state: State<Paths>) -> Result<Vec<MapInfo>, String> {
|
||||
let dir = output_dir(&state.project_root)?;
|
||||
let dir = output_dir(&state.data_root)?;
|
||||
let mut maps = Vec::new();
|
||||
if !dir.exists() {
|
||||
return Ok(maps);
|
||||
@@ -301,7 +331,7 @@ fn list_maps(state: State<Paths>) -> Result<Vec<MapInfo>, String> {
|
||||
|
||||
#[tauri::command]
|
||||
fn delete_map(path: String, state: State<Paths>) -> Result<(), String> {
|
||||
let dir = output_dir(&state.project_root)?
|
||||
let dir = output_dir(&state.data_root)?
|
||||
.canonicalize()
|
||||
.map_err(|e| format!("Failed to resolve output dir: {e}"))?;
|
||||
let candidate = PathBuf::from(path);
|
||||
@@ -376,6 +406,7 @@ async fn generate_map(
|
||||
|
||||
let prompt_clone = enrich_prompt(&user_prompt);
|
||||
let root = state.project_root.clone();
|
||||
let data_root = state.data_root.clone();
|
||||
let cfg = settings.unwrap_or_default();
|
||||
let steps = cfg.steps.unwrap_or(25);
|
||||
let width = cfg.width.unwrap_or(1536);
|
||||
@@ -416,10 +447,10 @@ async fn generate_map(
|
||||
|
||||
async_runtime::spawn_blocking(move || {
|
||||
let result = (|| {
|
||||
let out_dir = output_dir(&root)?;
|
||||
let out_dir = output_dir(&data_root)?;
|
||||
let script = script_path(&root)?;
|
||||
let python = python_binary(&root);
|
||||
let log_path = generation_log_path(&root, &prompt_clone)?;
|
||||
let log_path = generation_log_path(&data_root, &prompt_clone)?;
|
||||
let log_path_display = log_path.to_string_lossy().to_string();
|
||||
let mut log_file = File::create(&log_path)
|
||||
.map_err(|e| format!("Failed to create generation log: {e}"))?;
|
||||
@@ -431,7 +462,7 @@ async fn generate_map(
|
||||
"--output-dir".to_string(),
|
||||
out_dir.to_string_lossy().to_string(),
|
||||
"--work-dir".to_string(),
|
||||
root.to_string_lossy().to_string(),
|
||||
data_root.to_string_lossy().to_string(),
|
||||
"--upscale".to_string(),
|
||||
upscale,
|
||||
"--steps".to_string(),
|
||||
@@ -631,13 +662,28 @@ fn main() {
|
||||
// Try to locate the project root by walking up from cwd; if missing, fall back to Tauri resource dir.
|
||||
let root = discover_root()
|
||||
.or_else(|_| {
|
||||
tauri::api::path::resource_dir(context.package_info(), &env)
|
||||
.ok_or_else(|| "Could not locate project root or resource dir".to_string())
|
||||
let resource_dir = tauri::api::path::resource_dir(context.package_info(), &env)
|
||||
.ok_or_else(|| "Could not locate project root or resource dir".to_string())?;
|
||||
for candidate in [resource_dir.join("_up_"), resource_dir.clone()] {
|
||||
if candidate.join("generate_equirect.py").exists() {
|
||||
return Ok::<PathBuf, String>(candidate);
|
||||
}
|
||||
}
|
||||
Ok::<PathBuf, String>(resource_dir)
|
||||
})
|
||||
.unwrap_or_else(|_| PathBuf::from("."));
|
||||
let data_root = if root.join("src-tauri").exists() {
|
||||
root.clone()
|
||||
} else {
|
||||
tauri::api::path::app_data_dir(context.config()).unwrap_or_else(|| root.clone())
|
||||
};
|
||||
if let Err(e) = seed_output_dir(&root, &data_root) {
|
||||
eprintln!("Failed to seed bundled maps: {e}");
|
||||
}
|
||||
|
||||
let state = Paths {
|
||||
project_root: root,
|
||||
data_root,
|
||||
..Default::default()
|
||||
};
|
||||
tauri::Builder::default()
|
||||
|
||||
@@ -22,8 +22,22 @@
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"targets": "app",
|
||||
"identifier": "com.skymap.gen",
|
||||
"resources": ["../generate_equirect.py", "../default.png"]
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
"icons/icon.ico"
|
||||
],
|
||||
"resources": [
|
||||
"../generate_equirect.py",
|
||||
"../default.png",
|
||||
"../configs",
|
||||
"../output"
|
||||
]
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
|
||||