Advance Blender 5.2 web parity through M12-03D
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(¶meters,
|
||||
state->main,
|
||||
BLO_LIBLINK_APPEND_RECURSIVE,
|
||||
0,
|
||||
state->scene,
|
||||
state->view_layer,
|
||||
nullptr);
|
||||
BlendfileLinkAppendContext *context = BKE_blendfile_link_append_context_new(¶meters);
|
||||
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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user