Advance Blender 5.2 web parity through M12-03D
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-17 17:30:27 -04:00
parent 0fe8d2bb56
commit 5a11045ca5
148 changed files with 11683 additions and 66 deletions

View File

@@ -157,7 +157,7 @@ if(WITH_WEB)
# runtime only needs model/animation RNA needed by depsgraph evaluation.
# Trim after generation so the generator itself remains source-compatible.
list(FILTER GENSRC INCLUDE REGEX
"rna_(ID|action|animation|armature|collection|constraint|curve|depsgraph|fcurve|grease_pencil|image|key|lattice|layer|main|material|mesh|object|packedfile|pose|scene|world)_gen\\.cc$")
"rna_(ID|action|animation|armature|blendfile_import|collection|constraint|curve|depsgraph|fcurve|grease_pencil|image|key|lattice|layer|main|material|mesh|object|packedfile|pose|scene|world)_gen\\.cc$")
list(APPEND GENSRC
"${CMAKE_CURRENT_BINARY_DIR}/rna_prototypes_gen.hh"
"${CMAKE_CURRENT_BINARY_DIR}/../RNA_prototypes.hh"

View File

@@ -129,7 +129,7 @@ target_link_options(web_engine PRIVATE
"-sINITIAL_MEMORY=67108864"
"-sMAXIMUM_MEMORY=2147483648"
"-sSTACK_SIZE=8388608"
"-sEXPORTED_FUNCTIONS=['_malloc','_free','_web_engine_create','_web_engine_destroy','_web_engine_get_memory_stats','_web_engine_get_live_handles','_web_engine_get_allocated_bytes','_web_engine_open_blend','_web_engine_apply_command','_web_engine_undo','_web_engine_redo','_web_engine_get_scene_snapshot','_web_engine_get_scene_metadata','_web_engine_get_scene_geometry','_web_engine_get_scene_delta','_web_engine_get_packed_asset','_web_engine_evaluate_depsgraph','_web_engine_save_blend','_web_engine_free_buffer','_web_engine_last_error_code','_web_engine_last_error_message','_web_engine_decimate_apply']"
"-sEXPORTED_FUNCTIONS=['_malloc','_free','_web_engine_create','_web_engine_destroy','_web_engine_get_memory_stats','_web_engine_get_live_handles','_web_engine_get_allocated_bytes','_web_engine_open_blend','_web_engine_apply_command','_web_engine_append_library_object','_web_engine_undo','_web_engine_redo','_web_engine_get_scene_snapshot','_web_engine_get_scene_metadata','_web_engine_get_scene_geometry','_web_engine_get_scene_delta','_web_engine_get_packed_asset','_web_engine_evaluate_depsgraph','_web_engine_save_blend','_web_engine_free_buffer','_web_engine_last_error_code','_web_engine_last_error_message','_web_engine_decimate_apply']"
"-sEXPORTED_RUNTIME_METHODS=['ccall','cwrap','UTF8ToString']"
)
if(WEB_ENGINE_ENABLE_UNSAFE_DEPSGRAPH)

View File

@@ -2145,6 +2145,139 @@ EMSCRIPTEN_KEEPALIVE int web_engine_apply_command(const int handle,
}
}
EMSCRIPTEN_KEEPALIVE int web_engine_append_library_object(const int handle,
const uint8_t *source_data,
const uint32_t source_length,
const uint8_t *metadata,
const uint32_t metadata_length)
{
EngineState *engine = find_engine(handle);
if (engine == nullptr) return last_error_code;
if (source_data == nullptr || source_length == 0 ||
source_length > 64u * 1024u * 1024u || metadata == nullptr || metadata_length == 0)
{
set_error(WEB_ENGINE_INVALID_ARGUMENT, "ASSET_BUDGET_EXCEEDED: library append input is empty or exceeds 64 MiB");
return last_error_code;
}
if (!engine->authoritative_commands || engine->authoritative_main == nullptr) {
set_error(WEB_ENGINE_NOT_IMPLEMENTED,
"COMMAND_REQUIRES_MAIN_AUTHORITY: library append requires an authoritative Main");
return last_error_code;
}
try {
const json request = json::parse(reinterpret_cast<const char *>(metadata),
reinterpret_cast<const char *>(metadata) + metadata_length);
if (!request.is_object() || request.size() != 5 || request.value("schemaVersion", 0) != 1 ||
!request.contains("baseRevision") || !request["baseRevision"].is_number_unsigned() ||
!request.contains("sourceLocator") || !request["sourceLocator"].is_string() ||
!request.contains("sourceDataBlockId") || !request["sourceDataBlockId"].is_string() ||
!request.contains("expectedClosure") || !request["expectedClosure"].is_object())
{
set_error(WEB_ENGINE_INVALID_ARGUMENT,
"ASSET_MANIFEST_INVALID: native library append metadata is malformed");
return last_error_code;
}
for (const char *key : {"schemaVersion", "baseRevision", "sourceLocator", "sourceDataBlockId", "expectedClosure"}) {
if (!request.contains(key)) {
set_error(WEB_ENGINE_INVALID_ARGUMENT,
"ASSET_MANIFEST_INVALID: native library append metadata fields are not exact");
return last_error_code;
}
}
const uint64_t base_revision = request["baseRevision"].get<uint64_t>();
if (base_revision != engine->revision) {
set_error(WEB_ENGINE_INVALID_ARGUMENT,
"REVISION_CONFLICT: library append base revision does not match the current SceneIR");
return last_error_code;
}
const json &closure = request["expectedClosure"];
if (closure.size() != 4) {
set_error(WEB_ENGINE_INVALID_ARGUMENT, "ASSET_MANIFEST_INVALID: append closure fields are not exact");
return last_error_code;
}
auto closure_name = [&](const char *field, const char *prefix) -> std::string {
if (!closure.contains(field) || !closure[field].is_string()) return {};
const std::string value = closure[field].get<std::string>();
const size_t prefix_length = strlen(prefix);
if (value.size() <= prefix_length || value.compare(0, prefix_length, prefix) != 0) return {};
return value.substr(prefix_length);
};
const std::string object_name = closure_name("object", "Object/");
const std::string mesh_name = closure_name("mesh", "Mesh/");
const std::string material_name = closure_name("material", "Material/");
const std::string image_name = closure_name("image", "Image/");
if (object_name.empty() || mesh_name.empty() || material_name.empty() || image_name.empty() ||
request["sourceDataBlockId"].get<std::string>() != "Object/" + object_name)
{
set_error(WEB_ENGINE_INVALID_ARGUMENT,
"ASSET_SOURCE_HASH_MISMATCH: native append root does not match the expected closure");
return last_error_code;
}
HistoryEntry previous_state;
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 last_error_code;
}
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;
auto rollback = [&]() {
std::string rollback_error;
if (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;
engine->binary_revision = std::numeric_limits<uint64_t>::max();
}
};
std::string object_id;
std::string append_error;
if (!web_engine_blend_main_append_object(engine->authoritative_main,
source_data,
source_length,
request["sourceLocator"].get_ref<const std::string &>().c_str(),
object_name.c_str(),
mesh_name.c_str(),
material_name.c_str(),
image_name.c_str(),
object_id,
append_error))
{
rollback();
set_error(append_error.rfind("BLEND_READ_FAILED:", 0) == 0 ?
WEB_ENGINE_BLEND_READ_FAILED : WEB_ENGINE_INVALID_ARGUMENT,
append_error.c_str());
return last_error_code;
}
json snapshot;
if (!refresh_scene_from_main(*engine, snapshot, object_id, append_error)) {
rollback();
set_error(WEB_ENGINE_BLEND_WRITE_FAILED, append_error.c_str());
return last_error_code;
}
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);
set_error(WEB_ENGINE_OK, "ok");
return WEB_ENGINE_OK;
}
catch (const std::exception &exception) {
set_error(WEB_ENGINE_INVALID_ARGUMENT, exception.what());
return last_error_code;
}
}
EMSCRIPTEN_KEEPALIVE int web_engine_undo(const int handle)
{
EngineState *engine = find_engine(handle);

View File

@@ -28,6 +28,11 @@ uint32_t web_engine_get_live_handles(void);
uint32_t web_engine_get_allocated_bytes(void);
int web_engine_open_blend(int handle, const uint8_t *data, uint32_t length);
int web_engine_apply_command(int handle, const uint8_t *data, uint32_t length);
int web_engine_append_library_object(int handle,
const uint8_t *source_data,
uint32_t source_length,
const uint8_t *metadata,
uint32_t metadata_length);
int web_engine_undo(int handle);
int web_engine_redo(int handle);
int web_engine_get_scene_snapshot(int handle, const uint8_t **data, uint32_t *length);

View File

@@ -72,6 +72,13 @@ static void register_web_geometry_node_types()
}
namespace seq {
/* The browser engine does not load desktop sequencer playback state. */
void doversion_250_sound_proxy_update(Main * /*bmain*/, Editing * /*editing*/) {}
} // namespace seq
void WEB_headless_node_system_init()
{
static bool initialized = false;
@@ -411,4 +418,5 @@ const ComputeContext *compute_context_for_viewer_path_elem(
namespace blender {
void WM_main_add_notifier(unsigned int /*type*/, void * /*reference*/) {}
void WM_msg_publish_rna(wmMsgBus * /*mbus*/, PointerRNA * /*ptr*/, PropertyRNA * /*prop*/) {}
} // namespace blender

View File

@@ -20,6 +20,7 @@
#include "BKE_anim_data.hh"
#include "BKE_attribute.hh"
#include "BKE_attribute.h"
#include "BKE_blendfile_link_append.hh"
#include "BKE_layer.hh"
#include "BKE_collection.hh"
#include "BKE_curve.hh"
@@ -1120,6 +1121,117 @@ bool web_engine_blend_main_create_primitive(WebBlendMainState *state,
return true;
}
bool web_engine_blend_main_append_object(WebBlendMainState *state,
const uint8_t *source_data,
const uint32_t source_length,
const char *source_locator,
const char *object_name,
const char *mesh_name,
const char *material_name,
const char *image_name,
std::string &object_id,
std::string &error)
{
if (state == nullptr || state->main == nullptr || state->scene == nullptr ||
state->view_layer == nullptr || source_data == nullptr || source_length == 0 ||
source_length > 64u * 1024u * 1024u || source_locator == nullptr ||
source_locator[0] == '\0' || object_name == nullptr || mesh_name == nullptr ||
material_name == nullptr || image_name == nullptr || object_name[0] == '\0' ||
mesh_name[0] == '\0' || material_name[0] == '\0' || image_name[0] == '\0')
{
error = "ASSET_MANIFEST_INVALID: append requires a bounded source and complete Object closure";
return false;
}
if (find_object(state->main, ("object:" + std::string(object_name)).c_str()) != nullptr ||
find_material(state->main, ("material:" + std::string(material_name)).c_str()) != nullptr ||
find_image(state->main, ("image:" + std::string(image_name)).c_str()) != nullptr)
{
error = "ASSET_MANIFEST_INVALID: append closure collides with an existing local ID";
return false;
}
for (const Mesh &mesh : state->main->meshes) {
if (id_name(mesh.id) == mesh_name) {
error = "ASSET_MANIFEST_INVALID: append closure collides with an existing local Mesh";
return false;
}
}
BlendHandle *blend_handle = BLO_blendhandle_from_memory(
source_data, int(source_length), nullptr);
if (blend_handle == nullptr) {
error = "BLEND_READ_FAILED: Blender rejected the append source buffer";
return false;
}
GlobalMainScope global_main_scope(state->main);
BKE_main_id_tag_all(state->main, ID_TAG_PRE_EXISTING, true);
LibraryLink_Params parameters{};
BLO_library_link_params_init_with_context(&parameters,
state->main,
BLO_LIBLINK_APPEND_RECURSIVE,
0,
state->scene,
state->view_layer,
nullptr);
BlendfileLinkAppendContext *context = BKE_blendfile_link_append_context_new(&parameters);
if (context == nullptr) {
BLO_blendhandle_close(blend_handle);
BKE_main_id_tag_all(state->main, ID_TAG_PRE_EXISTING, false);
error = "BLEND_READ_FAILED: Blender could not create the append context";
return false;
}
BKE_blendfile_link_append_context_library_add(context, source_locator, blend_handle);
BlendfileLinkAppendContextItem *item = BKE_blendfile_link_append_context_item_add(
context, object_name, ID_OB, nullptr);
BKE_blendfile_link_append_context_item_library_index_enable(context, item, 0);
BKE_blendfile_link_append_context_init_done(context);
BKE_blendfile_link(context, nullptr);
BKE_blendfile_append(context, nullptr);
BKE_blendfile_link_append_instantiate_loose(context, nullptr);
BKE_blendfile_link_append_context_finalize(context);
ID *new_id = BKE_blendfile_link_append_context_item_newid_get(context, item);
BKE_blendfile_link_append_context_free(context);
BKE_main_id_tag_all(state->main, ID_TAG_PRE_EXISTING, false);
Object *object = new_id != nullptr && GS(new_id->name) == ID_OB ?
reinterpret_cast<Object *>(new_id) :
nullptr;
Mesh *mesh = object != nullptr && object->type == OB_MESH && object->data != nullptr ?
reinterpret_cast<Mesh *>(object->data) :
nullptr;
Material *material = mesh != nullptr && mesh->totcol == 1 && mesh->mat != nullptr ?
mesh->mat[0] :
nullptr;
Image *image = find_image(state->main, ("image:" + std::string(image_name)).c_str());
bool material_uses_image = false;
if (material != nullptr && material->nodetree != nullptr && image != nullptr) {
for (const bNode &node : material->nodetree->nodes) {
if (node.id == &image->id) {
material_uses_image = true;
break;
}
}
}
if (object == nullptr || mesh == nullptr || material == nullptr || image == nullptr ||
id_name(object->id) != object_name || id_name(mesh->id) != mesh_name ||
id_name(material->id) != material_name || id_name(image->id) != image_name ||
object->id.lib != nullptr || mesh->id.lib != nullptr || material->id.lib != nullptr ||
image->id.lib != nullptr || object->id.override_library != nullptr ||
mesh->id.override_library != nullptr || material->id.override_library != nullptr ||
image->id.override_library != nullptr || !material_uses_image)
{
error = "ASSET_SOURCE_HASH_MISMATCH: appended Object closure is incomplete or not fully local";
return false;
}
BKE_view_layer_synced_ensure(*state->main, state->scene, state->view_layer);
object->id.recalc |= ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY;
mesh->id.recalc |= ID_RECALC_GEOMETRY;
material->id.recalc |= ID_RECALC_SYNC_TO_EVAL;
image->id.recalc |= ID_RECALC_SOURCE;
object_id = "object:" + id_name(object->id);
return true;
}
bool web_engine_blend_main_create_curve(WebBlendMainState *state,
const char *curve_type,
const char *name,

View File

@@ -128,6 +128,16 @@ bool web_engine_blend_main_create_primitive(WebBlendMainState *state,
const std::vector<float> &location,
std::string &object_id,
std::string &error);
bool web_engine_blend_main_append_object(WebBlendMainState *state,
const uint8_t *source_data,
uint32_t source_length,
const char *source_locator,
const char *object_name,
const char *mesh_name,
const char *material_name,
const char *image_name,
std::string &object_id,
std::string &error);
bool web_engine_blend_main_duplicate_object(WebBlendMainState *state,
const char *object_id,
const std::vector<float> &offset,