载入中...
搜索中...
未找到
AvatarTarget.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <set>
6#include <sstream>
7#include <utility>
8
9namespace eve::avatar_editing {
10namespace {
11const EditorValue* field(const EditorValue& v, const char* k) {
12 const auto* o = v.getIf<EditorValue::Object>();
13 if (!o) return nullptr;
14 const auto i = o->find(k);
15 return i == o->end() ? nullptr : &i->second;
16}
17EditorValue floats(const float* v, int n) {
19 for (int i = 0; i < n; ++i) a.push_back(v[i]);
20 return a;
21}
22bool readFloats(const EditorValue* v, float* out, int n) {
23 const auto* a = v ? v->getIf<EditorValue::Array>() : nullptr;
24 if (!a || static_cast<int>(a->size()) != n) return false;
25 for (int i = 0; i < n; ++i) {
26 const auto* x = (*a)[i].getIf<double>();
27 if (!x || !std::isfinite(*x)) return false;
28 out[i] = static_cast<float>(*x);
29 }
30 return true;
31}
32EditorValue layerValue(const AvatarLayerValue& v) {
33 return EditorValue::Object{{"id", v.id.value()},
34 {"name", v.name},
35 {"texture", v.textureAsset},
36 {"z", int64_t{v.zIndex}},
37 {"visible", v.visible},
38 {"offset", floats(v.offset.data(), 2)},
39 {"size", floats(v.size.data(), 2)},
40 {"color", floats(v.color.data(), 4)}};
41}
42EditorValue parameterValue(const AvatarParameterValue& v) {
43 return EditorValue::Object{{"id", v.id.value()}, {"name", v.name}, {"default", v.defaultValue},
44 {"minimum", v.minimum}, {"maximum", v.maximum}, {"value", v.value}};
45}
46EditorValue expressionValue(const AvatarExpressionValue& v) {
48 for (const auto& [name, value] : v.channels) channels[name] = value;
49 return EditorValue::Object{{"id", v.id.value()}, {"name", v.name}, {"channels", std::move(channels)}};
50}
51Result<AvatarLayerValue> parseLayer(const EditorValue& v) {
52 AvatarLayerValue out;
53 const auto * id = field(v, "id"), *name = field(v, "name"), *texture = field(v, "texture"), *z = field(v, "z"),
54 *visible = field(v, "visible");
55 const auto* ids = id ? id->getIf<std::string>() : nullptr;
56 const auto* names = name ? name->getIf<std::string>() : nullptr;
57 const auto* textures = texture ? texture->getIf<std::string>() : nullptr;
58 const auto* zs = z ? z->getIf<int64_t>() : nullptr;
59 const auto* vis = visible ? visible->getIf<bool>() : nullptr;
60 if (!ids || ids->empty() || !names || !textures || !zs || !vis ||
61 !readFloats(field(v, "offset"), out.offset.data(), 2) || !readFloats(field(v, "size"), out.size.data(), 2) ||
62 !readFloats(field(v, "color"), out.color.data(), 4))
63 return eve::editing::failed<AvatarLayerValue>(EditorStatus::Rejected, RuleId("editor.avatar.layer"),
64 "Avatar layer is invalid");
65 out.id = ObjectId(*ids);
66 out.name = *names;
67 out.textureAsset = *textures;
68 out.zIndex = static_cast<int>(*zs);
69 out.visible = *vis;
70 return eve::editing::applied<AvatarLayerValue>(std::move(out));
71}
72Result<AvatarParameterValue> parseParameter(const EditorValue& v) {
73 AvatarParameterValue out;
74 const auto * id = field(v, "id"), *name = field(v, "name");
75 const auto* ids = id ? id->getIf<std::string>() : nullptr;
76 const auto* names = name ? name->getIf<std::string>() : nullptr;
77 if (!ids || ids->empty() || !names)
78 return eve::editing::failed<AvatarParameterValue>(EditorStatus::Rejected, RuleId("editor.avatar.parameter"),
79 "Avatar parameter identity is invalid");
80 out.id = ObjectId(*ids);
81 out.name = *names;
82 float* values[] = {&out.defaultValue, &out.minimum, &out.maximum, &out.value};
83 const char* fields[] = {"default", "minimum", "maximum", "value"};
84 for (int i = 0; i < 4; ++i) {
85 const auto* x = field(v, fields[i]);
86 const auto* n = x ? x->getIf<double>() : nullptr;
87 if (!n || !std::isfinite(*n))
88 return eve::editing::failed<AvatarParameterValue>(
89 EditorStatus::Rejected, RuleId("editor.avatar.parameter-number"), "Avatar parameter number is invalid");
90 *values[i] = static_cast<float>(*n);
91 }
92 return eve::editing::applied<AvatarParameterValue>(std::move(out));
93}
94Result<AvatarExpressionValue> parseExpression(const EditorValue& v) {
95 AvatarExpressionValue out;
96 const auto * id = field(v, "id"), *name = field(v, "name"), *channels = field(v, "channels");
97 const auto* ids = id ? id->getIf<std::string>() : nullptr;
98 const auto* names = name ? name->getIf<std::string>() : nullptr;
99 const auto* object = channels ? channels->getIf<EditorValue::Object>() : nullptr;
100 if (!ids || ids->empty() || !names || !object)
101 return eve::editing::failed<AvatarExpressionValue>(EditorStatus::Rejected, RuleId("editor.avatar.expression"),
102 "Avatar expression is invalid");
103 out.id = ObjectId(*ids);
104 out.name = *names;
105 for (const auto& [key, value] : *object) {
106 const auto* n = value.getIf<double>();
107 if (!n || !std::isfinite(*n))
108 return eve::editing::failed<AvatarExpressionValue>(EditorStatus::Rejected,
109 RuleId("editor.avatar.expression-channel"),
110 "Avatar expression channel is invalid");
111 out.channels[key] = static_cast<float>(*n);
112 }
113 return eve::editing::applied<AvatarExpressionValue>(std::move(out));
114}
115PropertyDescriptor prop(std::string path, PropertyType type, EditorValue value, const char* category, double min = -1e9,
116 double max = 1e9) {
119 d.displayNameKey = "editor.avatar." + path;
120 d.category = category;
121 d.type = type;
122 d.defaultValue = std::move(value);
123 d.flags = PropertyFlag::Runtime;
124 d.numeric.minimum = min;
125 d.numeric.maximum = max;
126 return d;
127}
128bool errors(const std::vector<EditorDiagnostic>& d) {
129 return std::any_of(d.begin(), d.end(), [](const auto& v) { return v.severity() == DiagnosticSeverity::Error; });
130}
131EditorDiagnostic diagnostic(const char* rule, std::string message) {
133 DiagnosticSeverity::Error, std::move(message));
134}
135} // namespace
136AvatarDocumentTarget::AvatarDocumentTarget(std::string id) : id_(std::move(id)) {}
138 return {TargetId(id_),
139 "avatar-asset",
141 false,
142 {CapabilityId("eve.editor.target.avatar-properties"), IPropertyProvider::editingCapabilityId()}};
143}
145 if (c == CapabilityId("eve.editor.target.avatar-properties") || c == IPropertyProvider::editingCapabilityId())
146 return static_cast<IPropertyProvider*>(this);
147 return nullptr;
148}
149bool AvatarDocumentTarget::matches(const SelectionSnapshot& s) const {
150 if (s.items.empty()) return false;
151 const std::string type = s.items.front().type;
152 for (const auto& i : s.items) {
153 if (i.target != TargetId(id_) || i.type != type) return false;
154 if (type == "avatar.layer" &&
155 std::none_of(layers_.begin(), layers_.end(), [&](const auto& v) { return v.id.value() == i.item.value(); }))
156 return false;
157 if (type == "avatar.parameter" && std::none_of(parameters_.begin(), parameters_.end(),
158 [&](const auto& v) { return v.id.value() == i.item.value(); }))
159 return false;
160 }
161 return type == "avatar.layer" || type == "avatar.parameter";
162}
171 if (!s.items.empty() && s.items.front().type == "avatar.parameter") {
172 schema.typeId = "avatar.parameter";
173 schema.properties = {prop("parameter.default", PropertyType::Float, 0.0, "parameter"),
174 prop("parameter.minimum", PropertyType::Float, 0.0, "parameter"),
175 prop("parameter.maximum", PropertyType::Float, 1.0, "parameter"),
176 prop("parameter.value", PropertyType::Float, 0.0, "parameter")};
177 } else {
178 schema.typeId = "avatar.layer";
179 schema.properties = {
180 prop("layer.texture", PropertyType::AssetRef, "", "layer"),
181 prop("layer.z", PropertyType::Int, int64_t{0}, "layer", -100000, 100000),
182 prop("layer.visible", PropertyType::Bool, true, "layer"),
183 prop("layer.offset", PropertyType::Vec2, EditorValue::Array{0.0, 0.0}, "layer"),
184 prop("layer.size", PropertyType::Vec2, EditorValue::Array{64.0, 64.0}, "layer", .01, 100000),
185 prop("layer.color", PropertyType::Color, EditorValue::Array{1.0, 1.0, 1.0, 1.0}, "layer", 0, 1)};
186 schema.properties[0].assetTypeFilters = {"image", "texture"};
187 }
188 return schema;
189}
191 if (!matches(s) || !schema(s).find(p)) return {};
192 std::optional<EditorValue> common;
193 for (const auto& i : s.items) {
195 if (i.type == "avatar.layer") {
196 const auto& v = *std::find_if(layers_.begin(), layers_.end(),
197 [&](const auto& x) { return x.id.value() == i.item.value(); });
198 if (p == PropertyPath("layer.texture"))
199 value = v.textureAsset;
200 else if (p == PropertyPath("layer.z"))
201 value = int64_t{v.zIndex};
202 else if (p == PropertyPath("layer.visible"))
203 value = v.visible;
204 else if (p == PropertyPath("layer.offset"))
205 value = floats(v.offset.data(), 2);
206 else if (p == PropertyPath("layer.size"))
207 value = floats(v.size.data(), 2);
208 else
209 value = floats(v.color.data(), 4);
210 } else {
211 const auto& v = *std::find_if(parameters_.begin(), parameters_.end(),
212 [&](const auto& x) { return x.id.value() == i.item.value(); });
213 if (p == PropertyPath("parameter.default"))
214 value = v.defaultValue;
215 else if (p == PropertyPath("parameter.minimum"))
216 value = v.minimum;
217 else if (p == PropertyPath("parameter.maximum"))
218 value = v.maximum;
219 else
220 value = v.value;
221 }
222 if (!common)
223 common = value;
224 else if (*common != value)
225 return {PropertyReadState::Mixed, {}, {}};
226 }
227 return {PropertyReadState::Value, *common, {}};
228}
229EditorValue AvatarDocumentTarget::contentValue() const {
231 for (const auto& v : layers_) layers.push_back(layerValue(v));
232 for (const auto& v : parameters_) parameters.push_back(parameterValue(v));
233 for (const auto& v : expressions_) expressions.push_back(expressionValue(v));
234 return EditorValue::Object{{"kind", kind_},
235 {"source", sourceAsset_},
236 {"layers", std::move(layers)},
237 {"parameters", std::move(parameters)},
238 {"expressions", std::move(expressions)}};
239}
240Result<DomainOperation> AvatarDocumentTarget::replacement(EditorValue content, std::string property) const {
242 op.type = "avatar.document.replace.v1";
243 op.inverseType = op.type;
244 op.target = TargetId(id_);
245 op.payload = std::move(content);
246 op.inverse = contentValue();
247 op.hasInverse = true;
248 if (!property.empty()) op.affectedProperties.push_back(property);
249 op.mergeKey = "avatar:" + id_ + ":" + (property.empty() ? "structure" : property);
250 return eve::editing::applied<DomainOperation>(std::move(op));
251}
253 const EditorValue& value, PropertySetMode mode) const {
254 if (mode == PropertySetMode::Reset) return makeReset(s, p);
255 auto d = schema(s).find(p);
256 if (!matches(s) || !d || mode != PropertySetMode::Absolute || !validatePropertyValue(*d, value).ok())
257 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.avatar.set"),
258 "Avatar property edit is invalid");
259 auto candidate = *this;
260 for (const auto& i : s.items)
261 if (i.type == "avatar.layer") {
262 auto& v = *std::find_if(candidate.layers_.begin(), candidate.layers_.end(),
263 [&](const auto& x) { return x.id.value() == i.item.value(); });
264 if (p == PropertyPath("layer.texture"))
265 v.textureAsset = *value.getIf<std::string>();
266 else if (p == PropertyPath("layer.z"))
267 v.zIndex = static_cast<int>(*value.getIf<int64_t>());
268 else if (p == PropertyPath("layer.visible"))
269 v.visible = *value.getIf<bool>();
270 else if (p == PropertyPath("layer.offset"))
271 readFloats(&value, v.offset.data(), 2);
272 else if (p == PropertyPath("layer.size"))
273 readFloats(&value, v.size.data(), 2);
274 else
275 readFloats(&value, v.color.data(), 4);
276 } else {
277 auto& v = *std::find_if(candidate.parameters_.begin(), candidate.parameters_.end(),
278 [&](const auto& x) { return x.id.value() == i.item.value(); });
279 const float n = static_cast<float>(*value.getIf<double>());
280 if (p == PropertyPath("parameter.default"))
281 v.defaultValue = n;
282 else if (p == PropertyPath("parameter.minimum"))
283 v.minimum = n;
284 else if (p == PropertyPath("parameter.maximum"))
285 v.maximum = n;
286 else
287 v.value = n;
288 }
289 if (errors(candidate.validate()))
290 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.avatar.invalid"),
291 "Avatar edit produces an invalid asset");
292 return replacement(candidate.contentValue(), p.value());
293}
295 auto d = schema(s).find(p);
296 if (!d)
297 return eve::editing::failed<DomainOperation>(EditorStatus::Unsupported, RuleId("editor.avatar.property"),
298 "Unknown Avatar property");
299 return makeSet(s, p, d->defaultValue, PropertySetMode::Absolute);
300}
302 auto candidate = *this;
303 candidate.kind_ = std::move(kind);
304 candidate.sourceAsset_ = std::move(asset);
305 if (errors(candidate.validate()))
306 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.avatar.source"),
307 "Avatar source is invalid");
308 return replacement(candidate.contentValue(), "source");
309}
311 if (v.id.empty() ||
312 std::any_of(layers_.begin(), layers_.end(), [&](const auto& x) { return x.id == v.id || x.name == v.name; }))
313 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.avatar.layer-id"),
314 "Avatar layer ID and name must be unique");
315 auto c = *this;
316 c.layers_.push_back(v);
317 if (errors(c.validate()))
318 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.avatar.layer"),
319 "Avatar layer is invalid");
320 return replacement(c.contentValue());
321}
323 if (std::none_of(layers_.begin(), layers_.end(), [&](const auto& v) { return v.id == id; }))
324 return eve::editing::failed<DomainOperation>(EditorStatus::NotFound, RuleId("editor.avatar.layer"),
325 "Avatar layer does not exist");
326 const auto& name = std::find_if(layers_.begin(), layers_.end(), [&](const auto& v) { return v.id == id; })->name;
327 for (const auto& e : expressions_)
328 if (e.channels.contains(name))
329 return eve::editing::failed<DomainOperation>(EditorStatus::Conflict,
330 RuleId("editor.avatar.layer-reference"),
331 "Avatar expressions still reference this layer");
332 auto c = *this;
333 std::erase_if(c.layers_, [&](const auto& v) { return v.id == id; });
334 return replacement(c.contentValue());
335}
337 if (v.id.empty() || std::any_of(parameters_.begin(), parameters_.end(),
338 [&](const auto& x) { return x.id == v.id || x.name == v.name; }))
339 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.avatar.parameter-id"),
340 "Avatar parameter ID and name must be unique");
341 auto c = *this;
342 c.parameters_.push_back(v);
343 if (errors(c.validate()))
344 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.avatar.parameter"),
345 "Avatar parameter is invalid");
346 return replacement(c.contentValue());
347}
349 if (std::none_of(parameters_.begin(), parameters_.end(), [&](const auto& v) { return v.id == id; }))
350 return eve::editing::failed<DomainOperation>(EditorStatus::NotFound, RuleId("editor.avatar.parameter"),
351 "Avatar parameter does not exist");
352 const auto& name =
353 std::find_if(parameters_.begin(), parameters_.end(), [&](const auto& v) { return v.id == id; })->name;
354 for (const auto& e : expressions_)
355 if (e.channels.contains(name))
356 return eve::editing::failed<DomainOperation>(EditorStatus::Conflict,
357 RuleId("editor.avatar.parameter-reference"),
358 "Avatar expressions still reference this parameter");
359 auto c = *this;
360 std::erase_if(c.parameters_, [&](const auto& v) { return v.id == id; });
361 return replacement(c.contentValue());
362}
364 if (v.id.empty() || std::any_of(expressions_.begin(), expressions_.end(),
365 [&](const auto& x) { return x.id == v.id || x.name == v.name; }))
366 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.avatar.expression-id"),
367 "Avatar expression ID and name must be unique");
368 auto c = *this;
369 c.expressions_.push_back(v);
370 if (errors(c.validate()))
371 return eve::editing::failed<DomainOperation>(EditorStatus::Rejected, RuleId("editor.avatar.expression"),
372 "Avatar expression is invalid");
373 return replacement(c.contentValue());
374}
376 if (std::none_of(expressions_.begin(), expressions_.end(), [&](const auto& v) { return v.id == id; }))
377 return eve::editing::failed<DomainOperation>(EditorStatus::NotFound, RuleId("editor.avatar.expression"),
378 "Avatar expression does not exist");
379 auto c = *this;
380 std::erase_if(c.expressions_, [&](const auto& v) { return v.id == id; });
381 return replacement(c.contentValue());
382}
383std::vector<EditorDiagnostic> AvatarDocumentTarget::validate() const {
384 std::vector<EditorDiagnostic> d;
385 if (kind_ != "image" && kind_ != "live2d" && kind_ != "vroid")
386 d.push_back(diagnostic("editor.avatar.kind", "Avatar kind must be image, live2d or vroid"));
387 if (kind_ != "image" && sourceAsset_.empty())
388 d.push_back(diagnostic("editor.avatar.source", "Live2D/VRoid Avatar requires a source asset"));
389 if (kind_ != "image" && !layers_.empty())
390 d.push_back(diagnostic("editor.avatar.backend-layers",
391 "Image layers are supported only by image Avatar assets"));
392 if (layers_.size() > 512 || parameters_.size() > 2048 || expressions_.size() > 1024)
393 d.push_back(diagnostic("editor.avatar.budget", "Avatar asset exceeds layer, parameter or expression budgets"));
394 std::set<std::string> ids, names, channels;
395 for (const auto& v : layers_) {
396 if (v.id.empty() || v.name.empty() || !ids.insert(v.id.value()).second || !names.insert(v.name).second)
397 d.push_back(diagnostic("editor.avatar.layer-identity", "Avatar layer IDs and names must be unique"));
398 channels.insert(v.name);
399 const bool vectorsFinite =
400 std::all_of(v.offset.begin(), v.offset.end(), [](float n) { return std::isfinite(n); }) &&
401 std::all_of(v.size.begin(), v.size.end(), [](float n) { return std::isfinite(n); });
402 if (v.textureAsset.empty() || v.zIndex < -100000 || v.zIndex > 100000 || !vectorsFinite || v.size[0] <= 0 ||
403 v.size[1] <= 0 ||
404 !std::all_of(v.color.begin(), v.color.end(), [](float n) { return std::isfinite(n) && n >= 0 && n <= 1; }))
405 d.push_back(diagnostic("editor.avatar.layer-range", "Avatar layer values are invalid"));
406 }
407 for (const auto& v : parameters_) {
408 if (v.id.empty() || v.name.empty() || !ids.insert(v.id.value()).second || !names.insert(v.name).second)
409 d.push_back(diagnostic("editor.avatar.parameter-identity",
410 "Avatar parameter IDs and names must be unique"));
411 channels.insert(v.name);
412 if (!std::isfinite(v.minimum) || !std::isfinite(v.maximum) || !std::isfinite(v.defaultValue) ||
413 !std::isfinite(v.value) || v.minimum > v.maximum || v.defaultValue < v.minimum ||
414 v.defaultValue > v.maximum || v.value < v.minimum || v.value > v.maximum)
415 d.push_back(diagnostic("editor.avatar.parameter-range", "Avatar parameter range/default/value is invalid"));
416 }
417 for (const auto& e : expressions_) {
418 if (e.id.empty() || e.name.empty() || !ids.insert(e.id.value()).second || !names.insert(e.name).second)
419 d.push_back(diagnostic("editor.avatar.expression-identity",
420 "Avatar expression IDs and names must be unique"));
421 if (e.channels.size() > 2048)
422 d.push_back(diagnostic("editor.avatar.expression-budget", "Avatar expression exceeds 2048 channels"));
423 for (const auto& [name, value] : e.channels)
424 if (!channels.contains(name) || !std::isfinite(value))
425 d.push_back(diagnostic("editor.avatar.expression-channel",
426 "Avatar expression references an unknown or invalid channel"));
427 }
428 return d;
429}
431 if (op.target != TargetId(id_) || op.type != "avatar.document.replace.v1" ||
432 !op.payload.isWithinLimits(8, 100000, 8 * 1024 * 1024))
433 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.avatar.operation"),
434 "Avatar operation is invalid");
435 const auto *kind = field(op.payload, "kind"), *source = field(op.payload, "source"),
436 *layers = field(op.payload, "layers"), *parameters = field(op.payload, "parameters"),
437 *expressions = field(op.payload, "expressions");
438 const auto* k = kind ? kind->getIf<std::string>() : nullptr;
439 const auto* s = source ? source->getIf<std::string>() : nullptr;
440 const auto* la = layers ? layers->getIf<EditorValue::Array>() : nullptr;
441 const auto* pa = parameters ? parameters->getIf<EditorValue::Array>() : nullptr;
442 const auto* ea = expressions ? expressions->getIf<EditorValue::Array>() : nullptr;
443 if (!k || !s || !la || !pa || !ea || la->size() > 512 || pa->size() > 2048 || ea->size() > 1024)
444 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.avatar.payload"),
445 "Avatar payload is invalid or exceeds budgets");
447 c.kind_ = *k;
448 c.sourceAsset_ = *s;
449 for (const auto& v : *la) {
450 auto parsed = parseLayer(v);
451 if (!parsed.ok())
452 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.avatar.layer"),
453 "Avatar layer cannot be parsed");
454 c.layers_.push_back(std::move(parsed.value()));
455 }
456 for (const auto& v : *pa) {
457 auto parsed = parseParameter(v);
458 if (!parsed.ok())
459 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.avatar.parameter"),
460 "Avatar parameter cannot be parsed");
461 c.parameters_.push_back(std::move(parsed.value()));
462 }
463 for (const auto& v : *ea) {
464 auto parsed = parseExpression(v);
465 if (!parsed.ok())
466 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.avatar.expression"),
467 "Avatar expression cannot be parsed");
468 c.expressions_.push_back(std::move(parsed.value()));
469 }
470 if (errors(c.validate()))
471 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.avatar.invalid"),
472 "Avatar document validation failed");
473 kind_ = std::move(c.kind_);
474 sourceAsset_ = std::move(c.sourceAsset_);
475 layers_ = std::move(c.layers_);
476 parameters_ = std::move(c.parameters_);
477 expressions_ = std::move(c.expressions_);
478 bumpRevision();
479 widenDirty(0, 0);
480 return eve::editing::applied<void>();
481}
482std::unique_ptr<IDomainOperationTarget> AvatarDocumentTarget::cloneDomainState() const {
483 return std::make_unique<AvatarDocumentTarget>(*this);
484}
485Result<void> AvatarDocumentTarget::commitDomainState(std::unique_ptr<IDomainOperationTarget> c) {
486 auto* t = dynamic_cast<AvatarDocumentTarget*>(c.get());
487 if (!t || t->id_ != id_)
488 return eve::editing::failed<void>(EditorStatus::Conflict, RuleId("editor.avatar.candidate"),
489 "Avatar candidate mismatch");
490 *this = *t;
491 return eve::editing::applied<void>();
492}
494 return EditorValue::Object{{"schemaVersion", int64_t{1}}, {"content", contentValue()}};
495}
497 const auto *v = field(s, "schemaVersion"), *content = field(s, "content");
498 const auto* version = v ? v->getIf<int64_t>() : nullptr;
499 if (!version || *version != 1 || !content)
500 return eve::editing::failed<void>(EditorStatus::Unsupported, RuleId("editor.avatar.snapshot"),
501 "Unsupported Avatar snapshot");
503 op.target = TargetId(id_);
504 op.type = "avatar.document.replace.v1";
505 op.payload = *content;
506 auto result = applyDomainOperation(op);
507 if (result.ok()) clearDirtyRegion();
508 return result;
509}
510} // namespace eve::avatar_editing
double value
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::map< std::string, std::vector< Key >, std::less<> > channels
const std::string & s
glm::vec4 p[6]
std::map< std::string, Var > values
std::string message
std::uint32_t key
glm::uvec4 ids
glm::vec3 n
Definition Grass.cpp:63
float v
std::int32_t c
TokenKind kind
std::string name
MeleePoint3 a
Definition MeleeHit.cpp:40
std::string path
Definition PlayHost.cpp:110
std::vector< std::string > fields
Definition PlayHost.cpp:111
std::string id
Definition PlayHost.cpp:108
float d
float t
bool visible
Json object
const char * category
const UnitySourceAsset & source
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
UUID-backed identifier adapter for legacy textual boundaries.
Definition Identity.h:314
Revisioned image/Live2D/VRoid Avatar authoring asset.
Result< DomainOperation > makeSet(const SelectionSnapshot &, const PropertyPath &, const EditorValue &, PropertySetMode) const override
Make set.
EditorValue snapshotValue() const
Snapshot value.
Result< void > commitDomainState(std::unique_ptr< IDomainOperationTarget >) override
Commits domain state.
Result< void > applyDomainOperation(const DomainOperation &) override
Applies domain operation.
std::unique_ptr< IDomainOperationTarget > cloneDomainState() const override
Clones domain state.
std::vector< EditorDiagnostic > validate() const
Validate.
eve::Result< eve::Revision > currentRevision(const SelectionSnapshot &) const override
Current revision.
Result< DomainOperation > makeReset(const SelectionSnapshot &, const PropertyPath &) const override
Make reset.
const std::vector< AvatarExpressionValue > & expressions() const
Expressions.
Result< void > loadSnapshot(const EditorValue &)
Loads snapshot.
Result< DomainOperation > makeDeleteParameter(const ObjectId &) const
Make delete parameter.
AvatarDocumentTarget(std::string id)
Avatar document target.
PropertySchema schema(const SelectionSnapshot &) const override
Schema.
Result< DomainOperation > makeSetSource(std::string kind, std::string asset) const
Change backend kind and model source.
Result< DomainOperation > makeCreateExpression(const AvatarExpressionValue &) const
Make create expression.
Result< DomainOperation > makeCreateLayer(const AvatarLayerValue &) const
Make create layer.
const std::string & kind() const
Kind.
PropertyReadResult read(const SelectionSnapshot &, const PropertyPath &) const override
Reads .
Result< DomainOperation > makeCreateParameter(const AvatarParameterValue &) const
Make create parameter.
void * queryCapability(const CapabilityId &) override
Query an optional stable editing capability.
Result< DomainOperation > makeDeleteExpression(const ObjectId &) const
Make delete expression.
Result< DomainOperation > makeDeleteLayer(const ObjectId &) const
Make delete layer.
const std::vector< AvatarLayerValue > & layers() const
Layers.
const std::vector< AvatarParameterValue > & parameters() const
Parameters.
TargetDescriptor describe() const override
Describe.
void bumpRevision()
Advance the revision without touching the dirty region.
Revision revisionValue() const noexcept
Return the revision as the shared protocol revision type.
void widenDirty()
Mark an unknown area dirty without advancing the revision.
virtual void clearDirtyRegion()=0
Clears dirty region.
UI-independent property access for an immutable selection snapshot.
static CapabilityId editingCapabilityId()
Stable capability identity for generic property editing.
Dotted property path (layout.size) compared and persisted as text.
Definition EditingIds.h:78
Deterministic owning value tree shared by authoring hosts.
std::vector< Value > Array
bool isWithinLimits(size_t maxDepth, size_t maxElements, size_t maxStringBytes) const
Validate nesting, element count and approximate string byte limits.
std::map< std::string, Value > Object
const T * getIf() const
Return an immediately borrowed typed value.
editing::CapabilityId CapabilityId
editing::TargetId TargetId
editing::PropertyDescriptor PropertyDescriptor
editing::ObjectId ObjectId
editing::DomainOperation DomainOperation
editing::PropertyPath PropertyPath
editing::PropertyType PropertyType
editing::Diagnostic EditorDiagnostic
editing::Value EditorValue
editing::RuleId RuleId
PropertySetMode
PropertySetMode public API.
Diagnostic ruleDiagnostic(eve::DiagnosticCode code, RuleId rule, DiagnosticSeverity severity, std::string message)
Build a common diagnostic carrying an open editing rule identity.
const EditorValue * field(const EditorValue &value, const char *name)
std::vector< float > floats(const RuntimeTensor &v)
Floats.
Named expression containing layer/parameter channel targets.
Stable image layer in an Avatar asset.
Reflected Avatar parameter and its initial value.
DomainOperation public API.
PropertyReadResult public API.
EVENGINE_API_PLATFORM public API.
std::vector< PropertyDescriptor > properties
PropertyDescriptorLookup find(const PropertyPath &path) const
Finds find.
SelectionSnapshot public API.
Stable discovery metadata exposed by every editable target.