Unravel Engine C++ Reference
Loading...
Searching...
No Matches
mcp_tools_common.h
Go to the documentation of this file.
1#pragma once
2
3#include "mcp_protocol.h"
4#include "mcp_tool_registry.h"
5
8#include <engine/ecs/scene.h>
9#include <engine/play_mode.h>
14#include <math/math.h>
15#include <uuid/uuid.h>
16
18
19#include <algorithm>
20#include <cctype>
21#include <chrono>
22#include <logging/logging.h>
23#include <optional>
24#include <string>
25#include <string_view>
26#include <thread>
27#include <vector>
28
29namespace unravel::mcp
30{
31
32inline auto entity_id_string(entt::handle entity) -> std::string
33{
35}
36
37inline auto find_entity(scene& scn, const std::string& id) -> entt::handle
38{
39 return find_entity_by_id(scn, id);
40}
41
42inline auto require_edit_scene(rtti::context& ctx, scene*& out_scene, std::string& error) -> bool
43{
44 auto* play = ctx.has<play_mode>() ? &ctx.get_cached<play_mode>() : nullptr;
45 if(play && play->is_active())
46 {
47 error = "Scene is in play mode; mutating tools are disabled";
48 return false;
49 }
50
51 auto& em = ctx.get_cached<editing_manager>();
52 out_scene = em.get_active_scene(ctx);
53 if(!out_scene || !out_scene->registry)
54 {
55 error = "No active scene";
56 return false;
57 }
58 return true;
59}
60
61inline auto read_string(const simdjson::dom::object& args, const char* key, std::string& out) -> bool
62{
63 std::string_view view;
64 if(args[key].get(view))
65 {
66 return false;
67 }
68 out.assign(view);
69 return true;
70}
71
72inline auto read_bool(const simdjson::dom::object& args, const char* key, bool& out) -> bool
73{
74 bool value = false;
75 if(args[key].get(value))
76 {
77 return false;
78 }
79 out = value;
80 return true;
81}
82
83inline auto read_double(const simdjson::dom::object& args, const char* key, double& out) -> bool
84{
85 double value = 0.0;
86 if(args[key].get(value))
87 {
88 return false;
89 }
90 out = value;
91 return true;
92}
93
94inline auto read_vec3(const simdjson::dom::object& args, const char* key, math::vec3& out) -> bool
95{
96 simdjson::dom::array arr;
97 if(args[key].get(arr))
98 {
99 return false;
100 }
101 std::vector<double> values;
102 for(auto el : arr)
103 {
104 double v = 0.0;
105 if(el.get(v))
106 {
107 return false;
108 }
109 values.push_back(v);
110 }
111 if(values.size() != 3)
112 {
113 return false;
114 }
115 out = math::vec3{static_cast<float>(values[0]), static_cast<float>(values[1]), static_cast<float>(values[2])};
116 return true;
117}
118
119inline auto empty_object_schema() -> std::string
120{
121 return R"({"type":"object","properties":{}})";
122}
123
124inline auto require_not_play_mode(rtti::context& ctx, std::string& error) -> bool
125{
126 auto* play = ctx.has<play_mode>() ? &ctx.get_cached<play_mode>() : nullptr;
127 if(play && play->is_active())
128 {
129 error = "Editor is in play mode; this tool is disabled";
130 return false;
131 }
132 return true;
133}
134
135inline auto require_open_project(rtti::context& ctx, std::string& error) -> bool
136{
137 if(!ctx.has<project_manager>() || !ctx.get_cached<project_manager>().has_open_project())
138 {
139 error = "No project open";
140 return false;
141 }
142 return true;
143}
144
146inline auto read_transform_space(const simdjson::dom::object& args, bool& out_is_local, std::string& error) -> bool
147{
148 out_is_local = false;
149 std::string space;
150 if(!read_string(args, "space", space) || space.empty() || space == "world" || space == "global")
151 {
152 out_is_local = false;
153 return true;
154 }
155 if(space == "local")
156 {
157 out_is_local = true;
158 return true;
159 }
160 error = "Invalid space (use \"world\" or \"local\")";
161 return false;
162}
163
165{
166 math::vec3 position{};
167 math::vec3 rotation_euler{};
168 math::vec3 scale{1.0f, 1.0f, 1.0f};
169 bool has_position{false};
170 bool has_rotation{false};
171 bool has_scale{false};
172};
173
174inline auto read_transform_snapshot(const simdjson::dom::object& obj, transform_snapshot& out) -> void
175{
176 out.has_position = read_vec3(obj, "position", out.position);
177 out.has_rotation = read_vec3(obj, "rotation_euler", out.rotation_euler);
178 out.has_scale = read_vec3(obj, "scale", out.scale);
179}
180
181inline void apply_pose_direct(entt::handle entity, const transform_snapshot& pose, bool is_local)
182{
183 if(!entity || !entity.all_of<transform_component>())
184 {
185 return;
186 }
187 auto& transform = entity.get<transform_component>();
188 if(is_local)
189 {
191 {
192 transform.set_position_local(pose.position);
193 }
195 {
196 transform.set_rotation_euler_local(pose.rotation_euler);
197 }
198 if(pose.has_scale)
199 {
200 transform.set_scale_local(pose.scale);
201 }
203 {
208 false);
209 }
210 return;
211 }
213 {
214 transform.set_position_global(pose.position);
215 }
217 {
218 transform.set_rotation_euler_global(pose.rotation_euler);
219 }
220 if(pose.has_scale)
221 {
222 transform.set_scale_global(pose.scale);
223 }
225 {
230 false);
231 }
232}
233
234inline auto read_wait_ms(const simdjson::dom::object& args, int64_t default_ms = 1000) -> std::chrono::milliseconds
235{
236 int64_t wait_ms = default_ms;
237 if(args["wait_ms"].get(wait_ms))
238 {
239 wait_ms = default_ms;
240 }
241 if(wait_ms < 0)
242 {
243 wait_ms = 0;
244 }
245 if(wait_ms > 15000)
246 {
247 wait_ms = 15000;
248 }
249 return std::chrono::milliseconds(wait_ms);
250}
251
252inline auto sleep_worker(std::chrono::milliseconds wait_ms) -> void
253{
254 if(wait_ms.count() > 0)
255 {
256 std::this_thread::sleep_for(wait_ms);
257 }
258}
259
260inline auto to_lower_ascii(std::string value) -> std::string
261{
262 std::transform(value.begin(),
263 value.end(),
264 value.begin(),
265 [](unsigned char c)
266 {
267 return static_cast<char>(std::tolower(c));
268 });
269 return value;
270}
271
272inline auto contains_ci(std::string_view haystack, std::string_view needle) -> bool
273{
274 if(needle.empty())
275 {
276 return true;
277 }
278 const auto h = to_lower_ascii(std::string(haystack));
279 const auto n = to_lower_ascii(std::string(needle));
280 return h.find(n) != std::string::npos;
281}
282
283inline auto starts_with(std::string_view value, std::string_view prefix) -> bool
284{
285 return value.size() >= prefix.size() && value.compare(0, prefix.size(), prefix) == 0;
286}
287
288inline auto bbox_to_json(const math::bbox& bounds) -> std::string
289{
290 const auto center = bounds.get_center();
291 const auto extents = bounds.get_extents();
292 return fmt::format(
293 R"({{"min":[{:.6g},{:.6g},{:.6g}],"max":[{:.6g},{:.6g},{:.6g}],"center":[{:.6g},{:.6g},{:.6g}],"extents":[{:.6g},{:.6g},{:.6g}]}})",
294 bounds.min.x,
295 bounds.min.y,
296 bounds.min.z,
297 bounds.max.x,
298 bounds.max.y,
299 bounds.max.z,
300 center.x,
301 center.y,
302 center.z,
303 extents.x,
304 extents.y,
305 extents.z);
306}
307
308inline auto normalize_asset_type_filter(std::string type) -> std::string
309{
310 type = to_lower_ascii(std::move(type));
311 while(!type.empty() && (type.front() == '*' || type.front() == '.'))
312 {
313 if(type.front() == '*')
314 {
315 type.erase(type.begin());
316 continue;
317 }
318 break;
319 }
320 if(!type.empty() && type.front() != '.')
321 {
322 type.insert(type.begin(), '.');
323 }
324 // Friendly names -> primary format from ex::get_suported_formats (first entry).
325 if(type == ".prefab")
326 {
328 }
329 if(type == ".mesh" || type == ".model")
330 {
332 }
333 if(type == ".material")
334 {
336 }
337 if(type == ".texture" || type == ".tex")
338 {
340 }
341 if(type == ".audio" || type == ".audioclip")
342 {
344 }
345 if(type == ".scene")
346 {
348 }
349 if(type == ".physics" || type == ".phxmat" || type == ".physics_material")
350 {
352 }
353 return type;
354}
355
356inline auto protocol_to_group(const std::string& protocol) -> std::string
357{
358 if(protocol == "app" || protocol == "app:/")
359 {
360 return "app:/";
361 }
362 if(protocol == "engine" || protocol == "engine:/")
363 {
364 return "engine:/";
365 }
366 if(protocol == "editor" || protocol == "editor:/")
367 {
368 return "editor:/";
369 }
370 return {};
371}
372
373} // namespace unravel::mcp
auto has_open_project() const -> bool
Component that handles transformations (position, rotation, scale, etc.) in the ACE framework.
uint16_t view
std::string error
Definition mcp_async.cpp:33
bool is_local
transform_snapshot pose
texture_job_type type
auto get_format(bool include_dot=true) -> std::string
auto require_not_play_mode(rtti::context &ctx, std::string &error) -> bool
auto read_vec3(const simdjson::dom::object &args, const char *key, math::vec3 &out) -> bool
auto sleep_worker(std::chrono::milliseconds wait_ms) -> void
auto protocol_to_group(const std::string &protocol) -> std::string
auto empty_object_schema() -> std::string
auto find_entity(scene &scn, const std::string &id) -> entt::handle
auto to_lower_ascii(std::string value) -> std::string
auto require_edit_scene(rtti::context &ctx, scene *&out_scene, std::string &error) -> bool
auto read_bool(const simdjson::dom::object &args, const char *key, bool &out) -> bool
auto read_double(const simdjson::dom::object &args, const char *key, double &out) -> bool
auto entity_id_string(entt::handle entity) -> std::string
void apply_pose_direct(entt::handle entity, const transform_snapshot &pose, bool is_local)
auto starts_with(std::string_view value, std::string_view prefix) -> bool
auto read_wait_ms(const simdjson::dom::object &args, int64_t default_ms=1000) -> std::chrono::milliseconds
auto read_transform_space(const simdjson::dom::object &args, bool &out_is_local, std::string &error) -> bool
Parse transform space: "world" (default) or "local". Returns false on invalid values.
auto read_string(const simdjson::dom::object &args, const char *key, std::string &out) -> bool
auto require_open_project(rtti::context &ctx, std::string &error) -> bool
auto contains_ci(std::string_view haystack, std::string_view needle) -> bool
auto normalize_asset_type_filter(std::string type) -> std::string
auto bbox_to_json(const math::bbox &bounds) -> std::string
auto read_transform_snapshot(const simdjson::dom::object &obj, transform_snapshot &out) -> void
auto entity_id_string(entt::handle entity) -> std::string
Stable id string for MCP/editor tooling (UUID preferred).
auto find_entity_by_id(scene &scn, const std::string &id) -> entt::handle
Resolve an entity by UUID string or integral entt id.
entt::handle entity
Storage for box vector values and wraps up common functionality.
Definition bbox.h:21
auto get_active_scene(rtti::context &ctx) -> scene *
Owns play-mode state and orchestrates the splash -> running lifecycle.
Definition play_mode.h:17
static void mark_transform_global_as_changed(entt::handle entity, bool position, bool rotation, bool scale, bool skew)
static void mark_transform_as_changed(entt::handle entity, bool position, bool rotation, bool scale, bool skew)
Marks transform properties as changed in prefab override system.
Represents a scene in the ACE framework, managing entities and their relationships.
Definition scene.h:70