50 {.name =
"project_list_recent",
52 "List recent project directories from editor.cfg (absolute paths), with on-disk "
53 "existence and inspect_project compatibility status.",
60 return {.text =
"[]", .is_error =
false};
66 std::string json =
"[";
67 for(
size_t i = 0; i <
items.size(); ++i)
75 const auto& path =
items[i];
76 const bool exists = fs::exists(path, ec);
77 const auto report = pm.inspect_project(path);
79 R
"({{"index":{},"path":{},"exists":{},"compat":{},"guid":{},"engine_version_opened":{}}})",
82 exists ? "true" :
"false",
88 return {.text = json, .is_error =
false};
90 .mutates_scene =
false});
93 {.name =
"project_open",
95 "Open a project by absolute path, recent:true (most recent), or recent_index. "
96 "Bypasses UI confirmations. If open_project would show the create-scene modal "
97 "(no last/startup scene), completes it with preset (default medium). "
98 "May take a while while assets are watched.",
100 R
"json({"type":"object","properties":{"path":{"type":"string","description":"Absolute project directory, or 'recent'"},"recent":{"type":"boolean"},"recent_index":{"type":"integer","minimum":0},"preset":{"type":"string","enum":["low","medium","high","showcase"],"description":"Used only when a new scene modal would appear; default medium"},"force":{"type":"boolean","description":"Discard unsaved scene changes before open; default true"}}})json",
106 std::string path_arg;
110 int64_t recent_index = -1;
111 if(args[
"recent_index"].get(recent_index))
115 std::string preset_str =
"medium";
120 auto result = mcp.invoke_on_main(
126 return {.text =
error, .is_error =
true};
130 return {.text =
"project_manager unavailable", .is_error =
true};
136 return {.text =
"Invalid preset (use low|medium|high|showcase)", .is_error =
true};
142 if(em.has_unsaved_changes() && !
force)
144 return {.text =
"Unsaved scene changes; pass force:true to discard", .is_error =
true};
148 em.clear_unsaved_changes();
153 fs::path project_path;
154 const auto& recent_projects = pm.get_editor_settings().projects.recent_projects;
155 if(recent || path_arg ==
"recent")
157 if(recent_projects.empty())
159 return {.text =
"No recent projects", .is_error =
true};
161 project_path = recent_projects.front();
163 else if(recent_index >= 0)
165 if(
static_cast<size_t>(recent_index) >= recent_projects.size())
167 return {.text =
"recent_index out of range", .is_error =
true};
169 project_path = recent_projects[
static_cast<size_t>(recent_index)];
171 else if(!path_arg.empty())
173 project_path = fs::path(path_arg);
177 return {.text =
"Provide path, recent:true, or recent_index", .is_error =
true};
181 if(!fs::exists(project_path, ec))
183 return {.text =
"Project directory does not exist: " + project_path.generic_string(),
187 const auto report = pm.inspect_project(project_path);
188 if(!pm.open_project(ctx, project_path))
190 return {.text =
"Failed to open project: " + project_path.generic_string(), .is_error =
true};
193 bool created_from_preset =
false;
196 created_from_preset =
true;
199 return {.text = fmt::format(
200 R
"({{"ok":true,"project":{},"compat":{},"created_scene_from_preset":{},"preset":{}}})",
201 project_info_json(pm),
203 created_from_preset ? "true" :
"false",
207 std::chrono::milliseconds(120000));
211 return {.text =
"Timed out opening project on main thread", .is_error =
true};
215 .mutates_scene =
true,
216 .requires_main_thread =
false});
219 {.name =
"project_close",
221 "Close the current project (no ImGui save prompt). Pass force:true (default) to "
222 "discard unsaved scene changes.",
224 R
"({"type":"object","properties":{"force":{"type":"boolean","default":true}}})",
231 return {.text =
error, .is_error =
true};
235 return {.text =
"project_manager unavailable", .is_error =
true};
239 if(!pm.has_open_project())
241 return {.text = R
"({"ok":true,"was_open":false})", .is_error = false};
248 if(em.has_unsaved_changes() && !
force)
250 return {.text =
"Unsaved scene changes; pass force:true to discard", .is_error =
true};
254 em.clear_unsaved_changes();
258 pm.close_project(ctx);
259 return {.text = R
"({"ok":true,"was_open":true})", .is_error = false};
261 .mutates_scene =
true});