feat(web): complete portable Blender feature gaps
This commit is contained in:
@@ -18,6 +18,11 @@
|
||||
#include "web_engine_depsgraph.h"
|
||||
#include "web_engine_main_state.h"
|
||||
|
||||
extern bool web_engine_blend_main_set_sequencer_strip_modifiers(WebBlendMainState *, const char *, const char *, const char *, std::string &);
|
||||
extern bool web_engine_blend_main_set_sequencer_transitions(WebBlendMainState *, const char *, const char *, std::string &);
|
||||
extern bool web_engine_blend_main_set_sequencer_color_balance(WebBlendMainState *, const char *, const char *, const char *, std::string &);
|
||||
extern bool web_engine_blend_main_set_compositor_node_graph(WebBlendMainState *, const char *, const char *, std::string &);
|
||||
|
||||
bool web_engine_blend_main_set_sequencer_strips(WebBlendMainState *state,
|
||||
const char *scene_id,
|
||||
const char *timeline_json,
|
||||
@@ -1304,6 +1309,11 @@ EMSCRIPTEN_KEEPALIVE int web_engine_apply_command(const int handle,
|
||||
extern bool web_engine_blend_main_create_action(WebBlendMainState *, const char *, const char *, std::string &, std::string &);
|
||||
extern bool web_engine_blend_main_remove_action(WebBlendMainState *, const char *, std::string &);
|
||||
extern bool web_engine_blend_main_set_keyframe(WebBlendMainState *, const char *, const char *, int, const char *, const char *, const char *, const char *, std::string &);
|
||||
extern bool web_engine_blend_main_set_fcurve_keyframes(WebBlendMainState *, const char *, const char *, const char *, std::string &);
|
||||
extern bool web_engine_blend_main_set_driver_expression(WebBlendMainState *, const char *, const char *, const char *, std::string &);
|
||||
extern bool web_engine_blend_main_set_action_groups(WebBlendMainState *, const char *, const char *, std::string &);
|
||||
extern bool web_engine_blend_main_set_keying_set(WebBlendMainState *, const char *, const char *, const char *, bool, std::string &);
|
||||
extern bool web_engine_blend_main_bake_animation(WebBlendMainState *, const char *, int, int, int, std::string &);
|
||||
extern bool web_engine_blend_main_set_timeline_markers(WebBlendMainState *, const char *, const char *, std::string &);
|
||||
if (type == "setFrame") {
|
||||
if (!engine->authoritative_commands || engine->authoritative_main == nullptr) {
|
||||
@@ -1717,27 +1727,36 @@ EMSCRIPTEN_KEEPALIVE int web_engine_apply_command(const int handle,
|
||||
engine->authoritative_dirty = true;
|
||||
snapshot_from_main = true;
|
||||
}
|
||||
else if (type == "meshEdit" || type == "createUVMap" || type == "setActiveUVMap" ||
|
||||
else if (type == "meshEdit" || type == "meshSubdivide" || type == "meshTriangulate" ||
|
||||
type == "meshRecalculateNormals" || type == "meshBridgeLoops" ||
|
||||
type == "meshDeleteElements" || type == "meshUVProject" ||
|
||||
type == "createUVMap" || type == "setActiveUVMap" ||
|
||||
type == "unwrapUV" || type == "addMaterialSlot" || type == "removeMaterialSlot" ||
|
||||
type == "assignMaterialFaces" || type == "setMaterialPrincipled" ||
|
||||
type == "importImage" || type == "setMaterialImageNode" || type == "setShaderGraph" ||
|
||||
type == "setGeometryNodeGraph" || type == "setPhysicsSimulation" ||
|
||||
type == "insertObjectKeyframe" || type == "deleteObjectKeyframe" ||
|
||||
type == "createAction" || type == "removeAction" || type == "setKeyframe" || type == "setTimelineMarkers" ||
|
||||
type == "setFCurveInterpolation" || type == "setActiveAction" || type == "setNLAStack" || type == "setSequencerStrips" ||
|
||||
type == "createAction" || type == "removeAction" || type == "setKeyframe" || type == "setFCurveKeyframes" ||
|
||||
type == "setDriverExpression" || type == "setActionGroups" || type == "setKeyingSet" || type == "bakeAnimation" || type == "setTimelineMarkers" ||
|
||||
type == "setFCurveInterpolation" || type == "setActiveAction" || type == "setNLAStack" || type == "setSequencerStrips" || type == "setSequencerStripModifiers" || type == "setSequencerTransitions" || type == "setSequencerColorBalance" || type == "setCompositorNodeGraph" ||
|
||||
type == "setConstraint" || type == "setParent" || type == "createCollection" ||
|
||||
type == "moveObjectToCollection" || type == "renameId" || type == "joinObjects" ||
|
||||
type == "separateMeshFaces" || type == "applyObjectTransform" || type == "setObjectOrigin" ||
|
||||
type == "setCurveControlPoints" || type == "setCurveHandle" || type == "setCurveTopology" || type == "setCurveSplines" || type == "setSurfaceTopology" || type == "setFontBody" ||
|
||||
type == "setCurveControlPoints" || type == "setCurveBevel" || type == "setCurveTaper" || type == "convertCurveType" || type == "setCurveHandle" || type == "setCurveTopology" || type == "setCurveSplines" || type == "setSurfaceTopology" || type == "setSurfaceResolution" || type == "setFontBody" ||
|
||||
type == "setFontProperties" || type == "setFontAdvanced" || type == "importVFont" || type == "setFontLinks" || type == "setTextSource" || type == "setSoundProperties" || type == "setSpeakerProperties" || type == "setParticleSettings" || type == "setTextureProperties" || type == "setVolumeProperties" || type == "deleteNonMeshData" ||
|
||||
type == "setArmatureBones" || type == "setPoseBoneTransform" ||
|
||||
type == "armatureEditOperations" || type == "setBoneRoll" ||
|
||||
type == "setBoneSymmetry" || type == "setBoneParenting" ||
|
||||
type == "setPoseConstraint" ||
|
||||
type == "setMetaballElements" || type == "createGreasePencilLayer" ||
|
||||
type == "removeGreasePencilLayer" || type == "moveGreasePencilLayer" ||
|
||||
type == "moveGreasePencilFrame" ||
|
||||
type == "insertGreasePencilFrame" || type == "removeGreasePencilFrame" ||
|
||||
type == "setGreasePencilStrokes" || type == "setVertexColors" ||
|
||||
type == "setGreasePencilStrokes" || type == "setGreasePencilDrawing" ||
|
||||
type == "setGreasePencilMaterial" || type == "setGreasePencilSelection" ||
|
||||
type == "setGreasePencilInterpolate" || type == "setVertexColors" ||
|
||||
type == "setVertexWeights" || type == "setCameraProperties" || type == "setLightProperties" ||
|
||||
type == "setWorldProperties" || type == "setSceneProperties" || type == "setSceneRenderSettings" || type == "setSceneAudioSettings" || type == "setSceneUnitSettings" || type == "setSceneTimelineSettings" || type == "setScenePipelineSettings" || type == "setSceneBindings" || type == "setSceneViewLayerSettings" || type == "setSceneCollectionSettings" || type == "setSceneBorderSettings" || type == "setSceneSimplifySettings" || type == "setSceneColorManagement" || type == "setSceneImageFormat" || type == "setSceneMotionBlur" || type == "setSceneHairSettings" || type == "setSceneCompositorSettings" || type == "setScenePerformanceSettings" || type == "setSceneSamplingSettings" || type == "setSceneMultiviewSettings" || type == "setSceneStampSettings" || type == "setSceneLineSettings" || type == "setSceneViewLayerPassSettings" || type == "setSceneViewLayerAOVs" || type == "setSceneViewLayerLightgroups" || type == "setObjectLightgroup" || type == "setWorldLightgroup" ||
|
||||
type == "setWorldProperties" || type == "renderFrame" || type == "renderAnimation" || type == "setSceneProperties" || type == "setSceneRenderSettings" || type == "setSceneAudioSettings" || type == "setSceneUnitSettings" || type == "setSceneTimelineSettings" || type == "setScenePipelineSettings" || type == "setSceneBindings" || type == "setSceneViewLayerSettings" || type == "setSceneCollectionSettings" || type == "setSceneBorderSettings" || type == "setSceneSimplifySettings" || type == "setSceneColorManagement" || type == "setSceneImageFormat" || type == "setSceneMotionBlur" || type == "setSceneHairSettings" || type == "setSceneCompositorSettings" || type == "setScenePerformanceSettings" || type == "setSceneSamplingSettings" || type == "setSceneMultiviewSettings" || type == "setSceneStampSettings" || type == "setSceneLineSettings" || type == "setSceneViewLayerPassSettings" || type == "setSceneViewLayerAOVs" || type == "setSceneViewLayerLightgroups" || type == "setTrackingMarkers" || type == "setTrackingSolve" || type == "setMaskSpline" || type == "setMaskPoint" || type == "clearObjectTransform" || type == "createObjectData" || type == "setObjectSelection" || type == "setCollectionVisibility" || type == "setObjectLightgroup" || type == "setWorldLightgroup" || type == "setLightLinking" ||
|
||||
type == "setSculptMeshAttributes" ||
|
||||
type == "sculptStroke") {
|
||||
if (!engine->authoritative_commands || engine->authoritative_main == nullptr) {
|
||||
@@ -1757,6 +1776,40 @@ EMSCRIPTEN_KEEPALIVE int web_engine_apply_command(const int handle,
|
||||
command.value("offset", std::vector<float>()), command.value("amount", 0.0f),
|
||||
command.value("segments", 1), main_error);
|
||||
}
|
||||
else if (type == "meshSubdivide") {
|
||||
applied = web_engine_blend_main_mesh_edit(
|
||||
engine->authoritative_main, command.value("meshId", "").c_str(), "SUBDIVIDE",
|
||||
command.value("selectionMode", "EDGE").c_str(),
|
||||
command.value("elementIndices", std::vector<uint32_t>()), std::vector<float>(), 0.0f,
|
||||
command.value("segments", 1), main_error);
|
||||
}
|
||||
else if (type == "meshTriangulate") {
|
||||
applied = web_engine_blend_main_mesh_edit(
|
||||
engine->authoritative_main, command.value("meshId", "").c_str(), "TRIANGULATE", "FACE",
|
||||
command.value("faceIndices", std::vector<uint32_t>()), std::vector<float>(), 0.0f, 1, main_error);
|
||||
}
|
||||
else if (type == "meshRecalculateNormals") {
|
||||
applied = web_engine_blend_main_mesh_edit(
|
||||
engine->authoritative_main, command.value("meshId", "").c_str(), "RECALCULATE_NORMALS", "FACE",
|
||||
command.value("faceIndices", std::vector<uint32_t>()), std::vector<float>(), 0.0f, 1, main_error);
|
||||
}
|
||||
else if (type == "meshBridgeLoops") {
|
||||
applied = web_engine_blend_main_mesh_edit(
|
||||
engine->authoritative_main, command.value("meshId", "").c_str(), "BRIDGE_LOOPS", "EDGE",
|
||||
command.value("edgeIndices", std::vector<uint32_t>()), std::vector<float>(), 0.0f, 1, main_error);
|
||||
}
|
||||
else if (type == "meshDeleteElements") {
|
||||
applied = web_engine_blend_main_mesh_edit(
|
||||
engine->authoritative_main, command.value("meshId", "").c_str(), "DELETE",
|
||||
command.value("selectionMode", "FACE").c_str(),
|
||||
command.value("elementIndices", std::vector<uint32_t>()), std::vector<float>(), 0.0f, 1, main_error);
|
||||
}
|
||||
else if (type == "meshUVProject") {
|
||||
applied = web_engine_blend_main_unwrap_uv(
|
||||
engine->authoritative_main, command.value("meshId", "").c_str(),
|
||||
command.value("faceIndices", std::vector<uint32_t>()),
|
||||
command.value("method", "PLANAR").c_str(), main_error);
|
||||
}
|
||||
else if (type == "createUVMap") {
|
||||
applied = web_engine_blend_main_create_uv_map(engine->authoritative_main,
|
||||
command.value("meshId", "").c_str(),
|
||||
@@ -1850,6 +1903,31 @@ EMSCRIPTEN_KEEPALIVE int web_engine_apply_command(const int handle,
|
||||
value_json.c_str(), handle_left_json.c_str(), handle_right_json.c_str(),
|
||||
command.value("interpolation", "").c_str(), main_error);
|
||||
}
|
||||
else if (type == "setFCurveKeyframes") {
|
||||
applied = web_engine_blend_main_set_fcurve_keyframes(
|
||||
engine->authoritative_main, command.value("animationId", "").c_str(),
|
||||
command.value("path", "").c_str(), command.contains("points") ? command["points"].dump().c_str() : "[]", main_error);
|
||||
}
|
||||
else if (type == "setDriverExpression") {
|
||||
applied = web_engine_blend_main_set_driver_expression(
|
||||
engine->authoritative_main, command.value("animationId", "").c_str(),
|
||||
command.value("path", "").c_str(), command.value("expression", "").c_str(), main_error);
|
||||
}
|
||||
else if (type == "setActionGroups") {
|
||||
applied = web_engine_blend_main_set_action_groups(
|
||||
engine->authoritative_main, command.value("animationId", "").c_str(),
|
||||
command.contains("groups") ? command["groups"].dump().c_str() : "[]", main_error);
|
||||
}
|
||||
else if (type == "setKeyingSet") {
|
||||
applied = web_engine_blend_main_set_keying_set(
|
||||
engine->authoritative_main, command.value("sceneId", "").c_str(), command.value("name", "").c_str(),
|
||||
command.contains("paths") ? command["paths"].dump().c_str() : "[]", command.value("active", true), main_error);
|
||||
}
|
||||
else if (type == "bakeAnimation") {
|
||||
applied = web_engine_blend_main_bake_animation(
|
||||
engine->authoritative_main, command.value("objectId", "").c_str(), command.value("frameStart", 1),
|
||||
command.value("frameEnd", 250), command.value("step", 1), main_error);
|
||||
}
|
||||
else if (type == "setTimelineMarkers") {
|
||||
applied = web_engine_blend_main_set_timeline_markers(engine->authoritative_main,
|
||||
command.value("dataId", "").c_str(),
|
||||
@@ -1946,6 +2024,49 @@ EMSCRIPTEN_KEEPALIVE int web_engine_apply_command(const int handle,
|
||||
command.value("objectId", "").c_str(),
|
||||
command.value("mode", "GEOMETRY").c_str(), main_error);
|
||||
}
|
||||
else if (type == "setCurveBevel") {
|
||||
const float bevel_depth = command.contains("bevelDepth") ? command.value("bevelDepth", -1.0f) : -1.0f;
|
||||
const int bevel_resolution = command.contains("bevelResolution") ? command.value("bevelResolution", -1) : -1;
|
||||
const float bevel_factor_start = command.contains("bevelFactorStart") ? command.value("bevelFactorStart", -1.0f) : -1.0f;
|
||||
const float bevel_factor_end = command.contains("bevelFactorEnd") ? command.value("bevelFactorEnd", -1.0f) : -1.0f;
|
||||
applied = web_engine_blend_main_set_curve_bevel(
|
||||
engine->authoritative_main,
|
||||
command.value("dataId", "").c_str(),
|
||||
bevel_depth,
|
||||
bevel_resolution,
|
||||
bevel_factor_start,
|
||||
bevel_factor_end,
|
||||
main_error);
|
||||
}
|
||||
else if (type == "setCurveTaper") {
|
||||
std::string taper_object_id;
|
||||
const char *taper_object_ptr = nullptr;
|
||||
if (command.contains("taperObjectId") && !command["taperObjectId"].is_null()) {
|
||||
if (!command["taperObjectId"].is_string()) {
|
||||
main_error = "NON_MESH_PROPERTY_INVALID: taperObjectId must be a string or null";
|
||||
}
|
||||
else {
|
||||
taper_object_id = command["taperObjectId"].get<std::string>();
|
||||
taper_object_ptr = taper_object_id.c_str();
|
||||
}
|
||||
}
|
||||
if (main_error.empty()) {
|
||||
applied = web_engine_blend_main_set_curve_taper(
|
||||
engine->authoritative_main,
|
||||
command.value("dataId", "").c_str(),
|
||||
taper_object_ptr,
|
||||
command.contains("taperRadiusMode") ? command.value("taperRadiusMode", -1) : -1,
|
||||
main_error);
|
||||
}
|
||||
}
|
||||
else if (type == "convertCurveType") {
|
||||
const std::string target_type = command.value("targetType", command.value("curveType", std::string()));
|
||||
applied = web_engine_blend_main_convert_curve_type(
|
||||
engine->authoritative_main,
|
||||
command.value("dataId", "").c_str(),
|
||||
target_type.c_str(),
|
||||
main_error);
|
||||
}
|
||||
else if (type == "setCurveControlPoints") {
|
||||
applied = web_engine_blend_main_set_curve_control_points(
|
||||
engine->authoritative_main, command.value("dataId", "").c_str(),
|
||||
@@ -2046,6 +2167,14 @@ EMSCRIPTEN_KEEPALIVE int web_engine_apply_command(const int handle,
|
||||
command.value("handleTypes", std::vector<int>()),
|
||||
command.value("handlePoints", std::vector<float>()), main_error);
|
||||
}
|
||||
else if (type == "setSurfaceResolution") {
|
||||
applied = web_engine_blend_main_set_surface_resolution(
|
||||
engine->authoritative_main,
|
||||
command.value("dataId", "").c_str(),
|
||||
command.value("resolutionU", 0),
|
||||
command.value("resolutionV", command.value("resolutionU", 0)),
|
||||
main_error);
|
||||
}
|
||||
else if (type == "setSurfaceTopology") {
|
||||
const json dimensions = command.value("splineDimensions", json::array());
|
||||
const json cyclic_u = command.value("cyclicU", json::array());
|
||||
@@ -2315,6 +2444,114 @@ EMSCRIPTEN_KEEPALIVE int web_engine_apply_command(const int handle,
|
||||
return last_error_code;
|
||||
}
|
||||
}
|
||||
else if (type == "armatureEditOperations") {
|
||||
auto read_vec3 = [](const json &value, std::array<float, 3> &target) {
|
||||
if (!value.is_array() || value.size() != 3) return false;
|
||||
for (int index = 0; index < 3; index++) {
|
||||
if (!value[index].is_number()) return false;
|
||||
target[index] = value[index].get<float>();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const json source_operations = command.value("operations", json::array());
|
||||
std::vector<WebArmatureEditOperation> operations;
|
||||
bool valid = source_operations.is_array() && !source_operations.empty() &&
|
||||
source_operations.size() <= 1024;
|
||||
for (const json &item : source_operations) {
|
||||
WebArmatureEditOperation operation{};
|
||||
operation.operation = item.value("operation", "");
|
||||
operation.bone_name = item.value("boneName", "");
|
||||
operation.name = item.value("name", "");
|
||||
operation.parent_name = item.value("parentName", "");
|
||||
operation.roll = item.value("roll", 0.0f);
|
||||
operation.connected = item.value("connected", false);
|
||||
if (item.contains("head")) valid = read_vec3(item["head"], operation.head) && valid;
|
||||
if (item.contains("tail")) valid = read_vec3(item["tail"], operation.tail) && valid;
|
||||
if (item.contains("offset")) valid = read_vec3(item["offset"], operation.offset) && valid;
|
||||
valid = valid && item.is_object();
|
||||
operations.push_back(std::move(operation));
|
||||
}
|
||||
if (!valid || !web_engine_blend_main_armature_edit_operations(
|
||||
engine->authoritative_main,
|
||||
command.value("objectId", "").c_str(),
|
||||
operations,
|
||||
main_error))
|
||||
{
|
||||
set_error(WEB_ENGINE_INVALID_ARGUMENT,
|
||||
main_error.empty() ? "ARMATURE_EDIT_INVALID: operation payload is invalid" : main_error.c_str());
|
||||
return last_error_code;
|
||||
}
|
||||
}
|
||||
else if (type == "setBoneRoll") {
|
||||
if (!command.contains("roll") || !command["roll"].is_number() ||
|
||||
!web_engine_blend_main_set_bone_roll(engine->authoritative_main,
|
||||
command.value("objectId", "").c_str(),
|
||||
command.value("boneName", "").c_str(),
|
||||
command.value("roll", 0.0f),
|
||||
main_error))
|
||||
{
|
||||
set_error(WEB_ENGINE_INVALID_ARGUMENT,
|
||||
main_error.empty() ? "ARMATURE_ROLL_INVALID: roll must be numeric" : main_error.c_str());
|
||||
return last_error_code;
|
||||
}
|
||||
}
|
||||
else if (type == "setBoneSymmetry") {
|
||||
if (!command.contains("enabled") || !command["enabled"].is_boolean() ||
|
||||
!web_engine_blend_main_set_bone_symmetry(engine->authoritative_main,
|
||||
command.value("objectId", "").c_str(),
|
||||
command.value("enabled", false),
|
||||
main_error))
|
||||
{
|
||||
set_error(WEB_ENGINE_INVALID_ARGUMENT,
|
||||
main_error.empty() ? "ARMATURE_SYMMETRY_INVALID: enabled must be boolean" : main_error.c_str());
|
||||
return last_error_code;
|
||||
}
|
||||
}
|
||||
else if (type == "setBoneParenting") {
|
||||
const bool valid_parent = command.contains("parentName") &&
|
||||
(command["parentName"].is_null() || command["parentName"].is_string());
|
||||
const std::string parent_name = valid_parent && command["parentName"].is_string() ?
|
||||
command["parentName"].get<std::string>() : "";
|
||||
if (!valid_parent || !command.contains("connected") || !command["connected"].is_boolean() ||
|
||||
!web_engine_blend_main_set_bone_parenting(engine->authoritative_main,
|
||||
command.value("objectId", "").c_str(),
|
||||
command.value("boneName", "").c_str(),
|
||||
parent_name.c_str(),
|
||||
command.value("connected", false),
|
||||
main_error))
|
||||
{
|
||||
set_error(WEB_ENGINE_INVALID_ARGUMENT,
|
||||
main_error.empty() ? "ARMATURE_PARENT_INVALID: parenting payload is invalid" : main_error.c_str());
|
||||
return last_error_code;
|
||||
}
|
||||
}
|
||||
else if (type == "setPoseConstraint") {
|
||||
WebPoseConstraintEdit edit{};
|
||||
edit.constraint_name = command.value("constraintName", "");
|
||||
edit.constraint_type = command.contains("constraintType") && command["constraintType"].is_string() ?
|
||||
command["constraintType"].get<std::string>() : "";
|
||||
edit.target_object_id = command.contains("targetObjectId") && command["targetObjectId"].is_string() ?
|
||||
command["targetObjectId"].get<std::string>() : "";
|
||||
edit.target_bone_name = command.value("targetBoneName", "");
|
||||
edit.influence = command.value("influence", 1.0f);
|
||||
edit.enabled = command.value("enabled", true);
|
||||
const bool valid = command.contains("constraintType") &&
|
||||
(command["constraintType"].is_null() || command["constraintType"].is_string()) &&
|
||||
(!command.contains("targetObjectId") || command["targetObjectId"].is_null() ||
|
||||
command["targetObjectId"].is_string()) &&
|
||||
(!command.contains("influence") || command["influence"].is_number()) &&
|
||||
(!command.contains("enabled") || command["enabled"].is_boolean());
|
||||
if (!valid || !web_engine_blend_main_set_pose_constraint(engine->authoritative_main,
|
||||
command.value("objectId", "").c_str(),
|
||||
command.value("boneName", "").c_str(),
|
||||
edit,
|
||||
main_error))
|
||||
{
|
||||
set_error(WEB_ENGINE_INVALID_ARGUMENT,
|
||||
main_error.empty() ? "POSE_CONSTRAINT_INVALID: constraint payload is invalid" : main_error.c_str());
|
||||
return last_error_code;
|
||||
}
|
||||
}
|
||||
else if (type == "setMetaballElements") {
|
||||
std::vector<WebMetaballElementEdit> elements;
|
||||
const json source_elements = command.value("elements", json::array());
|
||||
@@ -2426,6 +2663,70 @@ EMSCRIPTEN_KEEPALIVE int web_engine_apply_command(const int handle,
|
||||
engine->authoritative_main, command.value("dataId", "").c_str(),
|
||||
command.value("layerId", "").c_str(), command.value("frame", -1), strokes, main_error);
|
||||
}
|
||||
else if (type == "setGreasePencilDrawing") {
|
||||
std::vector<WebGreasePencilStrokeEdit> strokes;
|
||||
const json source_strokes = command.value("strokes", json::array());
|
||||
if (!source_strokes.is_array()) main_error = "GREASE_PENCIL_SCHEMA_INVALID: strokes must be an array";
|
||||
for (const json &source_stroke : source_strokes) {
|
||||
if (!main_error.empty()) break;
|
||||
if (!source_stroke.is_object() || !source_stroke.value("points", json()).is_array()) {
|
||||
main_error = "GREASE_PENCIL_SCHEMA_INVALID: stroke points must be an array";
|
||||
break;
|
||||
}
|
||||
WebGreasePencilStrokeEdit stroke;
|
||||
stroke.cyclic = source_stroke.value("cyclic", false);
|
||||
stroke.material_index = source_stroke.value("materialIndex", 0);
|
||||
for (const json &source_point : source_stroke["points"]) {
|
||||
if (!source_point.is_object() || !source_point.value("position", json()).is_array() || source_point["position"].size() != 3) {
|
||||
main_error = "GREASE_PENCIL_SCHEMA_INVALID: point position must contain three numbers";
|
||||
break;
|
||||
}
|
||||
WebGreasePencilPointEdit point;
|
||||
const json &position = source_point["position"];
|
||||
for (int axis = 0; axis < 3; axis++) {
|
||||
if (!json_number(position[axis])) { main_error = "GREASE_PENCIL_SCHEMA_INVALID: point position must contain finite numbers"; break; }
|
||||
point.position[axis] = position[axis].get<float>();
|
||||
}
|
||||
if (!main_error.empty()) break;
|
||||
point.radius = source_point.value("radius", 0.01f);
|
||||
point.opacity = source_point.value("opacity", 1.0f);
|
||||
const json color = source_point.value("vertexColor", json::array({0.0f, 0.0f, 0.0f, 0.0f}));
|
||||
if (!color.is_array() || color.size() != 4) { main_error = "GREASE_PENCIL_SCHEMA_INVALID: vertexColor must contain four numbers"; break; }
|
||||
for (int channel = 0; channel < 4; channel++) {
|
||||
if (!json_number(color[channel])) { main_error = "GREASE_PENCIL_SCHEMA_INVALID: vertexColor must contain finite numbers"; break; }
|
||||
point.vertex_color[channel] = color[channel].get<float>();
|
||||
}
|
||||
if (!main_error.empty()) break;
|
||||
stroke.points.push_back(point);
|
||||
}
|
||||
if (!main_error.empty()) break;
|
||||
strokes.push_back(std::move(stroke));
|
||||
}
|
||||
applied = main_error.empty() && web_engine_blend_main_set_grease_pencil_drawing(
|
||||
engine->authoritative_main, command.value("dataId", "").c_str(),
|
||||
command.value("layerId", "").c_str(), command.value("frame", -1),
|
||||
command.value("drawingId", "").c_str(), strokes, main_error);
|
||||
}
|
||||
else if (type == "setGreasePencilMaterial") {
|
||||
applied = web_engine_blend_main_set_grease_pencil_material(
|
||||
engine->authoritative_main, command.value("dataId", "").c_str(),
|
||||
command.value("layerId", "").c_str(), command.value("frame", -1),
|
||||
command.value("materialIndex", 0), command.value("strokeIndices", std::vector<uint32_t>()), main_error);
|
||||
}
|
||||
else if (type == "setGreasePencilSelection") {
|
||||
applied = web_engine_blend_main_set_grease_pencil_selection(
|
||||
engine->authoritative_main, command.value("dataId", "").c_str(),
|
||||
command.value("layerId", "").c_str(), command.value("frame", -1),
|
||||
command.value("pointIndices", std::vector<uint32_t>()),
|
||||
command.value("strokeIndices", std::vector<uint32_t>()),
|
||||
command.value("mode", "REPLACE").c_str(), main_error);
|
||||
}
|
||||
else if (type == "setGreasePencilInterpolate") {
|
||||
applied = web_engine_blend_main_set_grease_pencil_interpolate(
|
||||
engine->authoritative_main, command.value("dataId", "").c_str(),
|
||||
command.value("layerId", "").c_str(), command.value("sourceFrame", -1),
|
||||
command.value("targetFrame", -1), command.value("factor", 0.5f), main_error);
|
||||
}
|
||||
else if (type == "setVertexColors") {
|
||||
applied = web_engine_blend_main_set_vertex_colors(
|
||||
engine->authoritative_main, command.value("meshId", "").c_str(),
|
||||
@@ -2442,10 +2743,50 @@ EMSCRIPTEN_KEEPALIVE int web_engine_apply_command(const int handle,
|
||||
command.value("normalize", false), command.value("limit", 0u), command.value("mirror", false),
|
||||
command.value("mirrorAxis", 0), command.value("mirrorTolerance", 1e-4f), main_error);
|
||||
}
|
||||
else if (type == "setCameraProperties" || type == "setLightProperties" || type == "setWorldProperties" || type == "setSceneProperties" || type == "setSceneRenderSettings" || type == "setSceneAudioSettings" || type == "setSceneUnitSettings" || type == "setSceneTimelineSettings" || type == "setScenePipelineSettings" || type == "setSceneBindings" || type == "setSceneViewLayerSettings" || type == "setSceneCollectionSettings" || type == "setSceneBorderSettings" || type == "setSceneSimplifySettings" || type == "setSceneColorManagement" || type == "setSceneImageFormat" || type == "setSceneMotionBlur" || type == "setSceneHairSettings" || type == "setSceneCompositorSettings" || type == "setScenePerformanceSettings" || type == "setSceneSamplingSettings" || type == "setSceneMultiviewSettings" || type == "setSceneStampSettings" || type == "setSceneLineSettings" || type == "setSceneViewLayerPassSettings" || type == "setSceneViewLayerAOVs" || type == "setSceneViewLayerLightgroups")
|
||||
else if (type == "renderFrame" || type == "renderAnimation") {
|
||||
const std::string scene_id = command.value("dataId", command.value("sceneId", std::string()));
|
||||
if (scene_id.empty()) {
|
||||
main_error = "RENDER_PROPERTY_INVALID: render command requires a scene dataId";
|
||||
}
|
||||
else if (type == "renderFrame") {
|
||||
const json frame = command.value("frame", json(0));
|
||||
if (!frame.is_number_integer() || frame.get<int64_t>() < -1000000 || frame.get<int64_t>() > 1000000) {
|
||||
main_error = "RENDER_PROPERTY_INVALID: render frame is outside the bounded range";
|
||||
}
|
||||
else {
|
||||
applied = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
const json start = command.value("frameStart", json(1));
|
||||
const json end = command.value("frameEnd", json(250));
|
||||
const json step = command.value("step", json(1));
|
||||
if (!start.is_number_integer() || !end.is_number_integer() || !step.is_number_integer() || start.get<int64_t>() > end.get<int64_t>() || step.get<int64_t>() < 1 || step.get<int64_t>() > 1000) {
|
||||
main_error = "RENDER_PROPERTY_INVALID: render animation range is invalid";
|
||||
}
|
||||
else {
|
||||
applied = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (type == "setCameraProperties" || type == "setLightProperties" || type == "setWorldProperties" || type == "setSceneProperties" || type == "setSceneRenderSettings" || type == "setSceneAudioSettings" || type == "setSceneUnitSettings" || type == "setSceneTimelineSettings" || type == "setScenePipelineSettings" || type == "setSceneBindings" || type == "setSceneViewLayerSettings" || type == "setSceneCollectionSettings" || type == "setSceneBorderSettings" || type == "setSceneSimplifySettings" || type == "setSceneColorManagement" || type == "setSceneImageFormat" || type == "setSceneMotionBlur" || type == "setSceneHairSettings" || type == "setSceneCompositorSettings" || type == "setScenePerformanceSettings" || type == "setSceneSamplingSettings" || type == "setSceneMultiviewSettings" || type == "setSceneStampSettings" || type == "setSceneLineSettings" || type == "setSceneViewLayerPassSettings" || type == "setSceneViewLayerAOVs" || type == "setSceneViewLayerLightgroups" || type == "setRenderPasses" || type == "setCameraView" || type == "setTrackingMarkers" || type == "setTrackingSolve" || type == "setMaskSpline" || type == "setMaskPoint")
|
||||
{
|
||||
const json properties = command.value("properties", json::object());
|
||||
if (type == "setSceneProperties") {
|
||||
if (type == "setRenderPasses") {
|
||||
extern bool web_engine_blend_main_set_render_passes(WebBlendMainState *, const char *, const char *, const char *, std::string &);
|
||||
applied = web_engine_blend_main_set_render_passes(engine->authoritative_main,
|
||||
command.value("dataId", "").c_str(),
|
||||
command.value("layerName", "").c_str(),
|
||||
command.value("passes", json::object()).dump().c_str(), main_error);
|
||||
}
|
||||
else if (type == "setCameraView") {
|
||||
extern bool web_engine_blend_main_set_camera_view(WebBlendMainState *, const char *, const char *, std::string &);
|
||||
const json camera = command.value("cameraObjectId", json(nullptr));
|
||||
const std::string camera_id = camera.is_null() ? std::string() : camera.is_string() ? camera.get<std::string>() : std::string("\x01");
|
||||
applied = camera_id != "\x01" && web_engine_blend_main_set_camera_view(engine->authoritative_main,
|
||||
command.value("dataId", "").c_str(), camera_id.c_str(), main_error);
|
||||
}
|
||||
else if (type == "setSceneProperties") {
|
||||
applied = web_engine_blend_main_set_scene_properties(
|
||||
engine->authoritative_main, command.value("dataId", "").c_str(),
|
||||
properties.dump().c_str(), main_error);
|
||||
@@ -2566,12 +2907,71 @@ EMSCRIPTEN_KEEPALIVE int web_engine_apply_command(const int handle,
|
||||
engine->authoritative_main, command.value("dataId", "").c_str(),
|
||||
command.value("layerName", "").c_str(), command.value("lightgroups", json::array()).dump().c_str(), main_error);
|
||||
}
|
||||
else if (type == "setTrackingMarkers") {
|
||||
extern bool web_engine_blend_main_set_tracking_markers(WebBlendMainState *, const char *, const char *, const char *, std::string &);
|
||||
applied = web_engine_blend_main_set_tracking_markers(engine->authoritative_main,
|
||||
command.value("clipId", "").c_str(),
|
||||
command.value("trackId", "").c_str(),
|
||||
command.value("markers", json::array()).dump().c_str(), main_error);
|
||||
}
|
||||
else if (type == "setTrackingSolve") {
|
||||
extern bool web_engine_blend_main_set_tracking_solve(WebBlendMainState *, const char *, const char *, std::string &);
|
||||
applied = web_engine_blend_main_set_tracking_solve(engine->authoritative_main,
|
||||
command.value("clipId", "").c_str(),
|
||||
command.value("solve", json::object()).dump().c_str(), main_error);
|
||||
}
|
||||
else if (type == "setMaskSpline") {
|
||||
extern bool web_engine_blend_main_set_mask_spline(WebBlendMainState *, const char *, const char *, const char *, std::string &);
|
||||
applied = web_engine_blend_main_set_mask_spline(engine->authoritative_main,
|
||||
command.value("maskId", "").c_str(),
|
||||
command.value("layerId", "").c_str(),
|
||||
command.value("spline", json::object()).dump().c_str(), main_error);
|
||||
}
|
||||
else if (type == "setMaskPoint") {
|
||||
extern bool web_engine_blend_main_set_mask_point(WebBlendMainState *, const char *, const char *, const char *, const char *, std::string &);
|
||||
applied = web_engine_blend_main_set_mask_point(engine->authoritative_main,
|
||||
command.value("maskId", "").c_str(),
|
||||
command.value("layerId", "").c_str(),
|
||||
command.value("splineId", "").c_str(),
|
||||
command.value("point", json::object()).dump().c_str(), main_error);
|
||||
}
|
||||
else {
|
||||
applied = web_engine_blend_main_set_render_properties(
|
||||
engine->authoritative_main, command.value("dataId", "").c_str(),
|
||||
properties.dump().c_str(), main_error);
|
||||
}
|
||||
}
|
||||
else if (type == "clearObjectTransform") {
|
||||
applied = web_engine_blend_main_clear_object_transform(
|
||||
engine->authoritative_main, command.value("objectId", "").c_str(),
|
||||
command.value("channels", json::array()).dump().c_str(), main_error);
|
||||
}
|
||||
else if (type == "createObjectData") {
|
||||
const std::string object_type = command.value("objectType", "");
|
||||
applied = web_engine_blend_main_create_object_data(
|
||||
engine->authoritative_main, object_type.c_str(),
|
||||
command.value("name", object_type).c_str(), main_error);
|
||||
}
|
||||
else if (type == "setObjectSelection") {
|
||||
const json selected = command.value("selected", json(nullptr));
|
||||
const json active = command.value("active", json(false));
|
||||
if (!selected.is_boolean() || !active.is_boolean()) {
|
||||
main_error = "OBJECT_EDITOR_INVALID: selected and active must be booleans";
|
||||
}
|
||||
applied = selected.is_boolean() && active.is_boolean() &&
|
||||
web_engine_blend_main_set_object_selection(
|
||||
engine->authoritative_main, command.value("objectId", "").c_str(),
|
||||
selected.get<bool>(), active.get<bool>(), main_error);
|
||||
}
|
||||
else if (type == "setCollectionVisibility") {
|
||||
const json viewport = command.value("viewportVisible", json(nullptr));
|
||||
const json render = command.value("renderVisible", json(nullptr));
|
||||
const int viewport_visible = viewport.is_null() ? -1 : viewport.is_boolean() ? int(viewport.get<bool>()) : -2;
|
||||
const int render_visible = render.is_null() ? -1 : render.is_boolean() ? int(render.get<bool>()) : -2;
|
||||
applied = web_engine_blend_main_set_collection_visibility(
|
||||
engine->authoritative_main, command.value("collectionId", "").c_str(),
|
||||
viewport_visible, render_visible, main_error);
|
||||
}
|
||||
else if (type == "setObjectLightgroup") {
|
||||
const json lightgroup = command.value("lightgroup", json(nullptr));
|
||||
const std::string name = lightgroup.is_null() ? std::string() : lightgroup.is_string() ? lightgroup.get<std::string>() : std::string("\x01");
|
||||
@@ -2584,6 +2984,14 @@ EMSCRIPTEN_KEEPALIVE int web_engine_apply_command(const int handle,
|
||||
applied = name != "\x01" && web_engine_blend_main_set_world_lightgroup(
|
||||
engine->authoritative_main, command.value("dataId", "").c_str(), name.c_str(), main_error);
|
||||
}
|
||||
else if (type == "setLightLinking") {
|
||||
extern bool web_engine_blend_main_set_light_linking(WebBlendMainState *, const char *, const char *, const char *, std::string &);
|
||||
const json lightgroup = command.value("lightgroup", json(nullptr));
|
||||
const std::string name = lightgroup.is_null() ? std::string() : lightgroup.is_string() ? lightgroup.get<std::string>() : std::string("\x01");
|
||||
const std::string target = command.value("target", "");
|
||||
applied = name != "\x01" && web_engine_blend_main_set_light_linking(engine->authoritative_main,
|
||||
command.value("dataId", "").c_str(), target.c_str(), name.c_str(), main_error);
|
||||
}
|
||||
else if (type == "setSculptMeshAttributes") {
|
||||
const json attributes = command.value("attributes", json::object());
|
||||
applied = web_engine_blend_main_set_sculpt_attributes(
|
||||
@@ -2713,6 +3121,165 @@ EMSCRIPTEN_KEEPALIVE int web_engine_call_json(const int handle,
|
||||
}
|
||||
result = {{"status", "invoked"}, {"operator", params["operator"]}};
|
||||
}
|
||||
else if (cmd == "listRNADataBlocks" || cmd == "readRNAProperties" || cmd == "listRNACollections" || cmd == "setRNAProperty") {
|
||||
EngineState *engine = find_engine(handle);
|
||||
if (engine == nullptr || !engine->authoritative_commands || engine->authoritative_main == nullptr) {
|
||||
set_error(WEB_ENGINE_NOT_IMPLEMENTED, "COMMAND_REQUIRES_MAIN_AUTHORITY: RNA reflection requires an authoritative Main");
|
||||
return write_json_result({{"ok", false}, {"cmd", cmd}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
|
||||
}
|
||||
const std::string type_filter = params.value("type", params.value("dataBlockType", ""));
|
||||
const std::string name = params.value("name", params.value("dataBlockName", ""));
|
||||
const int limit = std::clamp(params.value("limit", 256), 1, 1024);
|
||||
std::string reflection_json;
|
||||
std::string reflection_error;
|
||||
if (cmd == "listRNADataBlocks") {
|
||||
if (!web_engine_blend_main_list_rna_data_blocks(engine->authoritative_main, type_filter.c_str(), params.value("editableOnly", false), limit, reflection_json, reflection_error)) {
|
||||
set_error(WEB_ENGINE_INVALID_ARGUMENT, reflection_error.c_str());
|
||||
return write_json_result({{"ok", false}, {"cmd", cmd}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
|
||||
}
|
||||
result = json::parse(reflection_json);
|
||||
}
|
||||
else if (cmd == "readRNAProperties") {
|
||||
if (name.empty() || !web_engine_blend_main_read_rna_properties(engine->authoritative_main, type_filter.c_str(), name.c_str(), limit, reflection_json, reflection_error)) {
|
||||
if (reflection_error.empty()) reflection_error = "readRNAProperties requires a data-block name";
|
||||
set_error(WEB_ENGINE_INVALID_ARGUMENT, reflection_error.c_str());
|
||||
return write_json_result({{"ok", false}, {"cmd", cmd}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
|
||||
}
|
||||
result = json::parse(reflection_json);
|
||||
}
|
||||
else if (cmd == "listRNACollections") {
|
||||
if (!web_engine_blend_main_list_rna_collections(engine->authoritative_main, type_filter.c_str(), name.c_str(), limit, reflection_json, reflection_error)) {
|
||||
set_error(WEB_ENGINE_INVALID_ARGUMENT, reflection_error.c_str());
|
||||
return write_json_result({{"ok", false}, {"cmd", cmd}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
|
||||
}
|
||||
result = json::parse(reflection_json);
|
||||
}
|
||||
else {
|
||||
const std::string property_name = params.value("property", params.value("propertyIdentifier", ""));
|
||||
if (name.empty() || property_name.empty() || !params.contains("value")) {
|
||||
set_error(WEB_ENGINE_INVALID_ARGUMENT, "setRNAProperty requires name, property and value");
|
||||
return write_json_result({{"ok", false}, {"cmd", cmd}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
|
||||
}
|
||||
HistoryEntry previous_state;
|
||||
std::string history_error;
|
||||
const uint64_t previous_revision = engine->revision;
|
||||
const std::string previous_snapshot = engine->scene_snapshot.empty() ? std::string(empty_scene_snapshot_json()) : engine->scene_snapshot;
|
||||
const std::string previous_delta = engine->scene_delta;
|
||||
if (params.contains("baseRevision")) {
|
||||
if (!params["baseRevision"].is_number_integer() && !params["baseRevision"].is_number_unsigned()) {
|
||||
set_error(WEB_ENGINE_INVALID_ARGUMENT, "REVISION_CONFLICT: baseRevision must be an integer");
|
||||
return write_json_result({{"ok", false}, {"cmd", cmd}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
|
||||
}
|
||||
if (params["baseRevision"].get<uint64_t>() != engine->revision) {
|
||||
set_error(WEB_ENGINE_INVALID_ARGUMENT, "REVISION_CONFLICT: RNA property edit is based on a stale revision");
|
||||
return write_json_result({{"ok", false}, {"cmd", cmd}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
|
||||
}
|
||||
}
|
||||
if (!capture_history_entry(*engine, previous_state, history_error)) {
|
||||
set_error(WEB_ENGINE_BLEND_WRITE_FAILED, history_error.c_str());
|
||||
return write_json_result({{"ok", false}, {"cmd", cmd}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
|
||||
}
|
||||
const std::string value_json = params["value"].dump();
|
||||
if (!web_engine_blend_main_set_rna_property(engine->authoritative_main, type_filter.c_str(), name.c_str(), property_name.c_str(), value_json.c_str(), reflection_error)) {
|
||||
std::string rollback_error;
|
||||
(void)restore_history_entry(*engine, previous_state, rollback_error);
|
||||
set_error(WEB_ENGINE_INVALID_ARGUMENT, reflection_error.c_str());
|
||||
return write_json_result({{"ok", false}, {"cmd", cmd}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
|
||||
}
|
||||
json snapshot;
|
||||
if (!refresh_scene_from_main(*engine, snapshot, "", reflection_error)) {
|
||||
std::string rollback_error;
|
||||
(void)restore_history_entry(*engine, previous_state, rollback_error);
|
||||
engine->revision = previous_revision;
|
||||
engine->scene_snapshot = previous_snapshot;
|
||||
engine->scene_delta = previous_delta;
|
||||
set_error(WEB_ENGINE_BLEND_WRITE_FAILED, reflection_error.c_str());
|
||||
return write_json_result({{"ok", false}, {"cmd", cmd}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
|
||||
}
|
||||
engine->authoritative_dirty = true;
|
||||
push_history(engine->undo_history, std::move(previous_state));
|
||||
engine->redo_history.clear();
|
||||
engine->scene_snapshot = snapshot.dump();
|
||||
engine->revision++;
|
||||
engine->scene_delta = scene_delta_json(previous_snapshot, engine->scene_snapshot);
|
||||
result = {{"status", "applied"}, {"revision", engine->revision}};
|
||||
}
|
||||
}
|
||||
else if (cmd == "assetMark" || cmd == "assetCatalog" || cmd == "libraryLink" || cmd == "libraryOverride" || cmd == "libraryReload") {
|
||||
EngineState *engine = find_engine(handle);
|
||||
if (engine == nullptr || !engine->authoritative_commands || engine->authoritative_main == nullptr) {
|
||||
set_error(WEB_ENGINE_NOT_IMPLEMENTED, "COMMAND_REQUIRES_MAIN_AUTHORITY: asset/library workflow requires an authoritative Main");
|
||||
return write_json_result({{"ok", false}, {"cmd", cmd}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
|
||||
}
|
||||
const bool mutating = !(cmd == "assetCatalog" && params.value("operation", "LIST") == "LIST");
|
||||
HistoryEntry previous_state;
|
||||
const uint64_t previous_revision = engine->revision;
|
||||
const std::string previous_snapshot = engine->scene_snapshot.empty() ? std::string(empty_scene_snapshot_json()) : engine->scene_snapshot;
|
||||
const std::string previous_delta = engine->scene_delta;
|
||||
const auto previous_packed_assets = engine->packed_assets;
|
||||
if (mutating) {
|
||||
std::string history_error;
|
||||
if (!capture_history_entry(*engine, previous_state, history_error)) {
|
||||
set_error(WEB_ENGINE_BLEND_WRITE_FAILED, history_error.c_str());
|
||||
return write_json_result({{"ok", false}, {"cmd", cmd}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
|
||||
}
|
||||
}
|
||||
std::string workflow_result;
|
||||
std::string workflow_error;
|
||||
if (!web_engine_blend_main_asset_library_command(engine->authoritative_main,
|
||||
cmd.c_str(),
|
||||
params.dump().c_str(),
|
||||
workflow_result,
|
||||
workflow_error)) {
|
||||
set_error(WEB_ENGINE_INVALID_ARGUMENT, workflow_error.c_str());
|
||||
return write_json_result({{"ok", false}, {"cmd", cmd}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
|
||||
}
|
||||
if (mutating) {
|
||||
json snapshot;
|
||||
if (!refresh_scene_from_main(*engine, snapshot, "", workflow_error)) {
|
||||
std::string rollback_error;
|
||||
(void)restore_history_entry(*engine, previous_state, rollback_error);
|
||||
engine->revision = previous_revision;
|
||||
engine->scene_snapshot = previous_snapshot;
|
||||
engine->scene_delta = previous_delta;
|
||||
engine->packed_assets = previous_packed_assets;
|
||||
set_error(WEB_ENGINE_BLEND_WRITE_FAILED, workflow_error.c_str());
|
||||
return write_json_result({{"ok", false}, {"cmd", cmd}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
|
||||
}
|
||||
engine->authoritative_dirty = true;
|
||||
push_history(engine->undo_history, std::move(previous_state));
|
||||
engine->redo_history.clear();
|
||||
engine->scene_snapshot = snapshot.dump();
|
||||
engine->revision++;
|
||||
engine->scene_delta = scene_delta_json(previous_snapshot, engine->scene_snapshot);
|
||||
}
|
||||
result = json::parse(workflow_result);
|
||||
result["revision"] = engine->revision;
|
||||
}
|
||||
else if (cmd == "renderFrame" || cmd == "renderAnimation") {
|
||||
EngineState *engine = find_engine(handle);
|
||||
if (engine == nullptr || !engine->authoritative_commands || engine->authoritative_main == nullptr) {
|
||||
set_error(WEB_ENGINE_NOT_IMPLEMENTED, "SERVER_JOB_UNAVAILABLE: render execution requires an authoritative Main and configured render job");
|
||||
return write_json_result({{"ok", false}, {"cmd", cmd}, {"code", last_error_code}, {"error", last_error_message}, {"route", "SERVER_JOB"}}, result_data, result_length);
|
||||
}
|
||||
const std::string scene_id = params.value("dataId", params.value("sceneId", std::string()));
|
||||
if (scene_id.empty()) {
|
||||
set_error(WEB_ENGINE_INVALID_ARGUMENT, "RENDER_PROPERTY_INVALID: render command requires a scene dataId");
|
||||
return write_json_result({{"ok", false}, {"cmd", cmd}, {"code", last_error_code}, {"error", last_error_message}}, result_data, result_length);
|
||||
}
|
||||
result = {{"status", "ROUTED"},
|
||||
{"route", "SERVER_JOB"},
|
||||
{"operation", cmd == "renderFrame" ? "RENDER_FRAME" : "RENDER_ANIMATION"},
|
||||
{"sceneId", scene_id}};
|
||||
if (cmd == "renderFrame") {
|
||||
result["frame"] = params.value("frame", 0);
|
||||
}
|
||||
else {
|
||||
result["frameStart"] = params.value("frameStart", 1);
|
||||
result["frameEnd"] = params.value("frameEnd", 250);
|
||||
result["step"] = params.value("step", 1);
|
||||
}
|
||||
}
|
||||
else if (cmd == "applyCommand") {
|
||||
if (!params.contains("command") || !params["command"].is_object()) {
|
||||
set_error(WEB_ENGINE_INVALID_ARGUMENT, "applyCommand params.command must be an object");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -197,6 +197,30 @@ bool web_engine_blend_main_set_modifier_parameters(WebBlendMainState *state,
|
||||
const char *modifier_uuid,
|
||||
const char *parameters_json,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_list_rna_data_blocks(WebBlendMainState *state,
|
||||
const char *type_filter,
|
||||
bool editable_only,
|
||||
int limit,
|
||||
std::string &result_json,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_read_rna_properties(WebBlendMainState *state,
|
||||
const char *type_filter,
|
||||
const char *name,
|
||||
int limit,
|
||||
std::string &result_json,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_set_rna_property(WebBlendMainState *state,
|
||||
const char *type_filter,
|
||||
const char *name,
|
||||
const char *property,
|
||||
const char *value_json,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_list_rna_collections(WebBlendMainState *state,
|
||||
const char *type_filter,
|
||||
const char *name,
|
||||
int limit,
|
||||
std::string &result_json,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_replace_mesh(WebBlendMainState *state,
|
||||
const char *mesh_id,
|
||||
const std::vector<float> &positions,
|
||||
@@ -221,6 +245,12 @@ bool web_engine_blend_main_append_object(WebBlendMainState *state,
|
||||
const char *image_name,
|
||||
std::string &object_id,
|
||||
std::string &error);
|
||||
/** Execute an asset-browser/library workflow against the authoritative Main. */
|
||||
bool web_engine_blend_main_asset_library_command(WebBlendMainState *state,
|
||||
const char *command,
|
||||
const char *params_json,
|
||||
std::string &result_json,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_duplicate_object(WebBlendMainState *state,
|
||||
const char *object_id,
|
||||
const std::vector<float> &offset,
|
||||
@@ -425,6 +455,27 @@ bool web_engine_blend_main_set_surface_topology(WebBlendMainState *state,
|
||||
const std::vector<float> &control_points,
|
||||
const std::vector<float> &point_weights,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_set_curve_bevel(WebBlendMainState *state,
|
||||
const char *data_id,
|
||||
float bevel_depth,
|
||||
int bevel_resolution,
|
||||
float bevel_factor_start,
|
||||
float bevel_factor_end,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_set_curve_taper(WebBlendMainState *state,
|
||||
const char *data_id,
|
||||
const char *taper_object_id,
|
||||
int taper_radius_mode,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_convert_curve_type(WebBlendMainState *state,
|
||||
const char *data_id,
|
||||
const char *target_type,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_set_surface_resolution(WebBlendMainState *state,
|
||||
const char *data_id,
|
||||
int resolution_u,
|
||||
int resolution_v,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_set_font_body(WebBlendMainState *state,
|
||||
const char *data_id,
|
||||
const char *body,
|
||||
@@ -482,6 +533,27 @@ struct WebArmatureBoneEdit {
|
||||
bool connected;
|
||||
};
|
||||
|
||||
struct WebArmatureEditOperation {
|
||||
std::string operation;
|
||||
std::string bone_name;
|
||||
std::string name;
|
||||
std::string parent_name;
|
||||
std::array<float, 3> head;
|
||||
std::array<float, 3> tail;
|
||||
std::array<float, 3> offset;
|
||||
float roll;
|
||||
bool connected;
|
||||
};
|
||||
|
||||
struct WebPoseConstraintEdit {
|
||||
std::string constraint_name;
|
||||
std::string constraint_type;
|
||||
std::string target_object_id;
|
||||
std::string target_bone_name;
|
||||
float influence;
|
||||
bool enabled;
|
||||
};
|
||||
|
||||
bool web_engine_blend_main_set_armature_bones(
|
||||
WebBlendMainState *state,
|
||||
const char *object_id,
|
||||
@@ -494,6 +566,31 @@ bool web_engine_blend_main_set_pose_bone_transform(WebBlendMainState *state,
|
||||
const std::array<float, 3> &rotation_euler,
|
||||
const std::array<float, 3> &scale,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_armature_edit_operations(
|
||||
WebBlendMainState *state,
|
||||
const char *object_id,
|
||||
const std::vector<WebArmatureEditOperation> &operations,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_set_bone_roll(WebBlendMainState *state,
|
||||
const char *object_id,
|
||||
const char *bone_name,
|
||||
float roll,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_set_bone_symmetry(WebBlendMainState *state,
|
||||
const char *object_id,
|
||||
bool enabled,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_set_bone_parenting(WebBlendMainState *state,
|
||||
const char *object_id,
|
||||
const char *bone_name,
|
||||
const char *parent_name,
|
||||
bool connected,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_set_pose_constraint(WebBlendMainState *state,
|
||||
const char *object_id,
|
||||
const char *bone_name,
|
||||
const WebPoseConstraintEdit &edit,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_set_metaball_elements(WebBlendMainState *state,
|
||||
const char *data_id,
|
||||
const std::vector<WebMetaballElementEdit> &elements,
|
||||
@@ -554,6 +651,35 @@ bool web_engine_blend_main_set_grease_pencil_strokes(WebBlendMainState *state,
|
||||
int frame,
|
||||
const std::vector<WebGreasePencilStrokeEdit> &strokes,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_set_grease_pencil_drawing(WebBlendMainState *state,
|
||||
const char *data_id,
|
||||
const char *layer_id,
|
||||
int frame,
|
||||
const char *drawing_id,
|
||||
const std::vector<WebGreasePencilStrokeEdit> &strokes,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_set_grease_pencil_material(WebBlendMainState *state,
|
||||
const char *data_id,
|
||||
const char *layer_id,
|
||||
int frame,
|
||||
int material_index,
|
||||
const std::vector<uint32_t> &stroke_indices,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_set_grease_pencil_selection(WebBlendMainState *state,
|
||||
const char *data_id,
|
||||
const char *layer_id,
|
||||
int frame,
|
||||
const std::vector<uint32_t> &point_indices,
|
||||
const std::vector<uint32_t> &stroke_indices,
|
||||
const char *mode,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_set_grease_pencil_interpolate(WebBlendMainState *state,
|
||||
const char *data_id,
|
||||
const char *layer_id,
|
||||
int source_frame,
|
||||
int target_frame,
|
||||
float factor,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_set_vertex_colors(WebBlendMainState *state,
|
||||
const char *mesh_id,
|
||||
const char *attribute_name,
|
||||
@@ -744,6 +870,24 @@ bool web_engine_blend_main_set_scene_view_layer_lightgroups(WebBlendMainState *s
|
||||
const char *layer_name,
|
||||
const char *lightgroups_json,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_clear_object_transform(WebBlendMainState *state,
|
||||
const char *object_id,
|
||||
const char *channels_json,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_create_object_data(WebBlendMainState *state,
|
||||
const char *object_type,
|
||||
const char *name,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_set_object_selection(WebBlendMainState *state,
|
||||
const char *object_id,
|
||||
bool selected,
|
||||
bool active,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_set_collection_visibility(WebBlendMainState *state,
|
||||
const char *collection_id,
|
||||
int viewport_visible,
|
||||
int render_visible,
|
||||
std::string &error);
|
||||
bool web_engine_blend_main_set_object_lightgroup(WebBlendMainState *state,
|
||||
const char *object_id,
|
||||
const char *lightgroup_name,
|
||||
|
||||
Reference in New Issue
Block a user