feat(web): add scene view layer authoring
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-08-29 13:50:23 +08:00
parent 27b1f77ffb
commit b8695839ac
47 changed files with 1959 additions and 40 deletions

View File

@@ -1592,7 +1592,7 @@ EMSCRIPTEN_KEEPALIVE int web_engine_apply_command(const int handle,
type == "insertGreasePencilFrame" || type == "removeGreasePencilFrame" ||
type == "setGreasePencilStrokes" || 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 == "setWorldProperties" || type == "setSceneProperties" || type == "setSceneRenderSettings" || type == "setSceneAudioSettings" || type == "setSceneUnitSettings" || type == "setSceneTimelineSettings" || type == "setScenePipelineSettings" || type == "setSceneBindings" || type == "setSceneViewLayerSettings" ||
type == "setSculptMeshAttributes" ||
type == "sculptStroke") {
if (!engine->authoritative_commands || engine->authoritative_main == nullptr) {
@@ -2196,7 +2196,7 @@ 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")
else if (type == "setCameraProperties" || type == "setLightProperties" || type == "setWorldProperties" || type == "setSceneProperties" || type == "setSceneRenderSettings" || type == "setSceneAudioSettings" || type == "setSceneUnitSettings" || type == "setSceneTimelineSettings" || type == "setScenePipelineSettings" || type == "setSceneBindings" || type == "setSceneViewLayerSettings")
{
const json properties = command.value("properties", json::object());
if (type == "setSceneProperties") {
@@ -2234,6 +2234,11 @@ EMSCRIPTEN_KEEPALIVE int web_engine_apply_command(const int handle,
engine->authoritative_main, command.value("dataId", "").c_str(),
properties.dump().c_str(), main_error);
}
else if (type == "setSceneViewLayerSettings") {
applied = web_engine_blend_main_set_scene_view_layer_settings(
engine->authoritative_main, command.value("dataId", "").c_str(),
command.value("layerName", "").c_str(), properties.dump().c_str(), main_error);
}
else {
applied = web_engine_blend_main_set_render_properties(
engine->authoritative_main, command.value("dataId", "").c_str(),

View File

@@ -5294,6 +5294,15 @@ json scene_ir_from_blend(const ParsedBlend &blend,
{"timeUnit", read_integer(*blend.sdna, *units, "time_unit").value_or(0)},
{"temperatureUnit", read_integer(*blend.sdna, *units, "temperature_unit").value_or(0)}};
}
json view_layers = json::array();
for (const ElementRef &view_layer : linked_list_elements(blend, element, "view_layers")) {
const int64_t flags = read_integer(*blend.sdna, view_layer, "flag").value_or(0);
view_layers.push_back({{"name", read_string(*blend.sdna, view_layer, "name")},
{"useRender", (flags & VIEW_LAYER_RENDER) != 0},
{"useFreestyle", (flags & VIEW_LAYER_FREESTYLE) != 0},
{"samples", read_integer(*blend.sdna, view_layer, "samples").value_or(0)}});
}
if (!view_layers.empty()) scene["viewLayers"] = std::move(view_layers);
if (const std::optional<ElementRef> view = embedded_element(*blend.sdna, element, "view_settings")) {
json color_management = {{"viewTransform", read_string(*blend.sdna, *view, "view_transform")},
{"look", read_string(*blend.sdna, *view, "look")},

View File

@@ -5898,7 +5898,7 @@ bool web_engine_blend_main_set_scene_audio_settings(WebBlendMainState *state,
}
scene->audio.distance_model = int(model);
}
const auto read_flag = [&](const char *key, short flag) {
const auto read_flag = [&](const char *key, eAudio_Flag flag) {
if (!properties.contains(key)) return true;
if (!properties[key].is_boolean()) {
error = std::string("RENDER_PROPERTY_INVALID: ") + key + " must be boolean";
@@ -6176,6 +6176,72 @@ bool web_engine_blend_main_set_scene_bindings(WebBlendMainState *state,
return true;
}
bool web_engine_blend_main_set_scene_view_layer_settings(WebBlendMainState *state,
const char *target_id,
const char *layer_name,
const char *properties_json,
std::string &error)
{
if (state == nullptr || state->main == nullptr || target_id == nullptr || layer_name == nullptr || properties_json == nullptr) {
error = "RENDER_PROPERTY_INVALID: scene view layer target is missing";
return false;
}
Scene *scene = find_scene(state->main, target_id);
if (scene == nullptr || scene->id.lib != nullptr || layer_name[0] == '\0') {
error = "LINKED_DATA_MUTATION_BLOCKED: Scene view layer is not editable";
return false;
}
ViewLayer *layer = nullptr;
for (ViewLayer &candidate : scene->view_layers) {
if (strncmp(candidate.name, layer_name, sizeof(candidate.name)) == 0) {
layer = &candidate;
break;
}
}
if (layer == nullptr) {
error = "RENDER_PROPERTY_INVALID: view layer does not exist in Scene";
return false;
}
const json properties = json::parse(properties_json, nullptr, false);
if (!properties.is_object() || properties.empty()) {
error = "RENDER_PROPERTY_INVALID: view layer settings must be a non-empty object";
return false;
}
const std::unordered_set<std::string> allowed = {"useRender", "useFreestyle", "samples"};
for (const auto &[key, value] : properties.items()) {
(void)value;
if (!allowed.contains(key)) {
error = "RENDER_PROPERTY_INVALID: unsupported view layer property " + key;
return false;
}
}
const auto read_flag = [&](const char *key, eViewLayer_Flag flag) {
if (!properties.contains(key)) return true;
if (!properties[key].is_boolean()) {
error = std::string("RENDER_PROPERTY_INVALID: ") + key + " must be boolean";
return false;
}
if (properties[key].get<bool>()) layer->flag |= flag;
else layer->flag &= ~flag;
return true;
};
if (!read_flag("useRender", VIEW_LAYER_RENDER) || !read_flag("useFreestyle", VIEW_LAYER_FREESTYLE)) return false;
if (properties.contains("samples")) {
if (!properties["samples"].is_number_integer()) {
error = "RENDER_PROPERTY_INVALID: samples must be an integer";
return false;
}
const int64_t samples = properties["samples"].get<int64_t>();
if (samples < 0 || samples > 1000000) {
error = "RENDER_PROPERTY_INVALID: samples is outside the bounded range";
return false;
}
layer->samples = int(samples);
}
scene->id.recalc |= ID_RECALC_PARAMETERS;
return true;
}
bool web_engine_blend_main_write(WebBlendMainState *state,
std::vector<uint8_t> &bytes,
std::string &error)

View File

@@ -544,6 +544,11 @@ bool web_engine_blend_main_set_scene_bindings(WebBlendMainState *state,
const char *target_id,
const char *properties_json,
std::string &error);
bool web_engine_blend_main_set_scene_view_layer_settings(WebBlendMainState *state,
const char *target_id,
const char *layer_name,
const char *properties_json,
std::string &error);
bool web_engine_blend_main_write(WebBlendMainState *state,
std::vector<uint8_t> &bytes,
std::string &error);