feat(web): complete portable Blender feature gaps
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled

This commit is contained in:
mes123456
2026-09-06 19:33:50 +08:00
parent ed347098b4
commit 66d3f4e0f4
48 changed files with 58048 additions and 61 deletions

View File

@@ -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");

View File

@@ -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,