Add Chromium-only Blender WebEngine parity work

This commit is contained in:
mes123456
2026-08-12 04:47:48 -04:00
commit 9fd26010f6
18225 changed files with 11622124 additions and 0 deletions

View File

@@ -0,0 +1,142 @@
# Web-only Blender data engine target. This is intentionally separate from the
# desktop creator/window-manager entry point and only exposes the C ABI.
set(SRC
web_engine_blend_reader.cpp
web_engine_depsgraph.cpp
web_engine_main_state.cpp
web_engine_api.cpp
web_engine_decimate.cpp
web_engine_headless_fallback.cpp
web_gpu_fallback.cpp
web_rna_fallback.cpp
)
set(INC
PUBLIC .
${CMAKE_SOURCE_DIR}/source/blender/blenkernel
${CMAKE_SOURCE_DIR}/source/blender/blenfont
${CMAKE_SOURCE_DIR}/source/blender/animrig
${CMAKE_SOURCE_DIR}/source/blender/blenloader
${CMAKE_SOURCE_DIR}/source/blender/depsgraph
${CMAKE_SOURCE_DIR}/source/blender/gpu
${CMAKE_SOURCE_DIR}/source/blender/imbuf
${CMAKE_SOURCE_DIR}/source/blender/makesrna
${CMAKE_SOURCE_DIR}/source/blender/makesrna/intern
${CMAKE_SOURCE_DIR}/source/blender/nodes
${CMAKE_SOURCE_DIR}/source/blender/sequencer
${CMAKE_SOURCE_DIR}/source/blender/render
${CMAKE_SOURCE_DIR}/source/blender/windowmanager
${CMAKE_SOURCE_DIR}/source/blender/editors/include
${CMAKE_SOURCE_DIR}/source/blender/editors/asset
${CMAKE_SOURCE_DIR}/source/blender/functions
${CMAKE_SOURCE_DIR}/source/blender/makesdna/intern
${CMAKE_SOURCE_DIR}/intern/clog
${CMAKE_SOURCE_DIR}/extern/json/include
)
set(LIB
PRIVATE bf::blenlib
PRIVATE bf::dna
PRIVATE bf::blenloader_core
PRIVATE bf::blenloader
PRIVATE bf::depsgraph
PRIVATE bf::bmesh
PRIVATE bf::intern::guardedalloc
PRIVATE bf::dependencies::zstd
)
blender_add_lib(blender_web_engine_core "${SRC}" "${INC}" "" "${LIB}")
add_library(bf::web_engine ALIAS blender_web_engine_core)
option(WEB_ENGINE_ENABLE_UNSAFE_DEPSGRAPH
"Enable verbose native Depsgraph diagnostics" OFF)
mark_as_advanced(WEB_ENGINE_ENABLE_UNSAFE_DEPSGRAPH)
if(WEB_ENGINE_ENABLE_UNSAFE_DEPSGRAPH)
target_compile_definitions(blender_web_engine_core PRIVATE WEB_ENGINE_ENABLE_UNSAFE_DEPSGRAPH)
endif()
add_executable(web_engine
"${CMAKE_SOURCE_DIR}/../web/engine/web_engine_smoke.cpp"
)
target_include_directories(web_engine PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
# Link archive files instead of CMake targets here. Blender's static targets
# intentionally expose their implementation dependencies to the desktop
# executable; following those edges would pull the entire editor and image
# stack into this data-only WebAssembly module.
set(WEB_ENGINE_ARCHIVE_TARGETS
blender_web_engine_core
bf_bmesh
bf_blenkernel
bf_blenloader_core
bf_blenloader
bf_depsgraph
bf_rna
bf_blentranslation
bf_animrig
bf_asset_system
bf_imbuf
bf_imbuf_movie
bf_functions
bf_geometry
bf_nodes
bf_modifiers
bf_blenfont
bf_editor_datafiles
bf_imbuf_opencolorio
bf_imbuf_openexr
bf_simulation
bf_ikplugin
bf_intern_iksolver
bf_intern_eigen
bf_intern_memutil
bf_intern_ghost
extern_xxhash
extern_wcwidth
bf_intern_libmv
bf_intern_opensubdiv
bf_blenlib
bf_dna
bf_intern_clog
bf_intern_guardedalloc
extern_rangetree
zlibstatic
libzstd_static
osd_static_cpu
)
set(WEB_ENGINE_ARCHIVES "")
foreach(archive_target IN LISTS WEB_ENGINE_ARCHIVE_TARGETS)
list(APPEND WEB_ENGINE_ARCHIVES "$<TARGET_FILE:${archive_target}>")
endforeach()
if(WITH_GMP)
list(APPEND WEB_ENGINE_ARCHIVES ${GMP_LIBRARIES})
endif()
add_dependencies(web_engine ${WEB_ENGINE_ARCHIVE_TARGETS})
# The libraries contain a few intentional cycles. Repeating the small archive
# group is equivalent to Blender's LINK_INTERFACE_MULTIPLICITY policy without
# restoring the desktop-only dependency graph.
target_link_libraries(web_engine PRIVATE
${WEB_ENGINE_ARCHIVES}
${WEB_ENGINE_ARCHIVES}
${WEB_ENGINE_ARCHIVES}
)
target_link_options(web_engine PRIVATE
"-Wl,--error-limit=0"
"-sUSE_FREETYPE=1"
"-sENVIRONMENT=web,node,worker"
"-sALLOW_MEMORY_GROWTH=1"
"-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_RUNTIME_METHODS=['ccall','cwrap','UTF8ToString']"
)
if(WEB_ENGINE_ENABLE_UNSAFE_DEPSGRAPH)
target_link_options(web_engine PRIVATE
"-sASSERTIONS=2"
"-sSTACK_OVERFLOW_CHECK=2"
"--profiling-funcs"
)
endif()
set_target_properties(web_engine PROPERTIES OUTPUT_NAME "web_engine")

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,50 @@
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct WebEngineMemoryStats {
uint32_t live_handles;
uint32_t allocated_bytes;
} WebEngineMemoryStats;
typedef enum WebEngineResultCode {
WEB_ENGINE_OK = 0,
WEB_ENGINE_INVALID_HANDLE = -1,
WEB_ENGINE_INVALID_ARGUMENT = -2,
WEB_ENGINE_NOT_IMPLEMENTED = -3,
WEB_ENGINE_BLEND_READ_FAILED = -4,
WEB_ENGINE_BLEND_WRITE_FAILED = -5,
WEB_ENGINE_HISTORY_EMPTY = -6,
} WebEngineResultCode;
int web_engine_create(void);
void web_engine_destroy(int handle);
WebEngineMemoryStats web_engine_get_memory_stats(void);
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_undo(int handle);
int web_engine_redo(int handle);
int web_engine_get_scene_snapshot(int handle, const uint8_t **data, uint32_t *length);
int web_engine_get_scene_metadata(int handle, const uint8_t **data, uint32_t *length);
int web_engine_get_scene_geometry(int handle, const uint8_t **data, uint32_t *length);
int web_engine_get_scene_delta(int handle, const uint8_t **data, uint32_t *length);
int web_engine_get_packed_asset(int handle,
const uint8_t *asset_id,
uint32_t asset_id_length,
const uint8_t **data,
uint32_t *length);
int web_engine_evaluate_depsgraph(int handle, const uint8_t **data, uint32_t *length);
int web_engine_save_blend(int handle, const uint8_t **data, uint32_t *length);
void web_engine_free_buffer(const uint8_t *data);
int web_engine_last_error_code(void);
const char *web_engine_last_error_message(void);
#ifdef __cplusplus
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,18 @@
#pragma once
#include <cstdint>
#include <string>
#include <unordered_map>
#include <vector>
struct WebBlendReadResult {
bool ok = false;
std::string error;
std::string scene_ir;
std::unordered_map<std::string, std::vector<uint8_t>> packed_assets;
uint32_t source_bytes = 0;
};
WebBlendReadResult web_engine_read_blend_scene_ir(const uint8_t *data,
uint32_t length,
uint64_t revision);

View File

@@ -0,0 +1,338 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "web_engine_decimate.h"
#include <algorithm>
#include <array>
#include <cmath>
#include <cstring>
#include <unordered_map>
#include <vector>
#include "BLI_mempool.h"
#include "MEM_guardedalloc.h"
#include "bmesh.hh"
#include "tools/bmesh_decimate.hh"
#ifdef __EMSCRIPTEN__
# include <emscripten/emscripten.h>
#else
# define EMSCRIPTEN_KEEPALIVE
#endif
namespace {
using blender::BMesh;
using blender::BMFace;
using blender::BMLoop;
using blender::BMVert;
struct OutputMesh {
std::vector<float> positions;
std::vector<uint32_t> indices;
std::vector<float> uvs;
std::vector<float> colors;
std::vector<uint32_t> material_indices;
uint32_t face_count = 0;
};
blender::BMO_Delimit delimit_flags(const uint32_t value)
{
blender::BMO_Delimit flags = blender::BMO_Delimit(0);
if (value & (1u << 0)) flags |= blender::BMO_DELIM_NORMAL;
if (value & (1u << 1)) flags |= blender::BMO_DELIM_MATERIAL;
if (value & (1u << 2)) flags |= blender::BMO_DELIM_SEAM;
if (value & (1u << 3)) flags |= blender::BMO_DELIM_SHARP;
if (value & (1u << 4)) flags |= blender::BMO_DELIM_UV;
return flags;
}
int build_and_decimate(const float *positions,
const uint32_t vertex_count,
const uint32_t *indices,
const uint32_t triangle_count,
const uint32_t mode,
const float ratio,
const uint32_t iterations,
const float angle_limit,
const uint32_t delimit,
const uint32_t use_dissolve_boundaries,
const uint32_t triangulate,
const int32_t symmetry_axis,
const float symmetry_epsilon,
const float *vertex_weights,
const float vertex_group_factor,
const uint32_t vertex_group_invert,
const float *input_uvs,
const float *input_colors,
const uint32_t *input_material_indices,
OutputMesh &output)
{
if (positions == nullptr || indices == nullptr || vertex_count == 0 || triangle_count == 0) {
return WEB_ENGINE_DECIMATE_INVALID_ARGUMENT;
}
if (mode > WEB_ENGINE_DECIMATE_DISSOLVE_PLANAR) return WEB_ENGINE_DECIMATE_NOT_IMPLEMENTED;
if (mode == WEB_ENGINE_DECIMATE_COLLAPSE && (ratio <= 0.0f || ratio > 1.0f)) {
return WEB_ENGINE_DECIMATE_INVALID_ARGUMENT;
}
if (mode == WEB_ENGINE_DECIMATE_UNSUBDIV && iterations == 0) {
return WEB_ENGINE_DECIMATE_INVALID_ARGUMENT;
}
if (mode == WEB_ENGINE_DECIMATE_DISSOLVE_PLANAR && (angle_limit < 0.0f || angle_limit > 3.14159265358979323846f)) {
return WEB_ENGINE_DECIMATE_INVALID_ARGUMENT;
}
if (symmetry_axis < -1 || symmetry_axis > 2 || symmetry_epsilon <= 0.0f || vertex_group_factor < 0.0f ||
vertex_group_factor > 1.0f) {
return WEB_ENGINE_DECIMATE_INVALID_ARGUMENT;
}
blender::BMeshCreateParams create_params{};
BMesh *bm = blender::BM_mesh_create(&blender::bm_mesh_allocsize_default, &create_params);
if (bm == nullptr) return WEB_ENGINE_DECIMATE_FAILED;
/* UV/color transfer is performed by the command layer. Adding transient
* loop CustomData to a hand-built BMesh is not safe for all Web allocator
* layouts; Blender's native topology result remains authoritative. */
(void)input_uvs;
(void)input_colors;
std::vector<BMVert *> input_verts(vertex_count, nullptr);
for (uint32_t index = 0; index < vertex_count; index++) {
input_verts[index] = blender::BM_vert_create(bm, positions + index * 3, nullptr, blender::BM_CREATE_NOP);
if (input_verts[index] == nullptr) {
blender::BM_mesh_free(bm);
return WEB_ENGINE_DECIMATE_FAILED;
}
}
for (uint32_t triangle = 0; triangle < triangle_count; triangle++) {
const uint32_t a = indices[triangle * 3];
const uint32_t b = indices[triangle * 3 + 1];
const uint32_t c = indices[triangle * 3 + 2];
if (a >= vertex_count || b >= vertex_count || c >= vertex_count || a == b || b == c || a == c) {
blender::BM_mesh_free(bm);
return WEB_ENGINE_DECIMATE_INVALID_ARGUMENT;
}
BMVert *face_verts[3] = {input_verts[a], input_verts[b], input_verts[c]};
BMFace *face = blender::BM_face_create_verts(
bm, face_verts, 3, nullptr, blender::BM_CREATE_NO_DOUBLE, true);
if (face == nullptr) {
blender::BM_mesh_free(bm);
return WEB_ENGINE_DECIMATE_FAILED;
}
if (input_material_indices != nullptr) {
face->mat_nr = static_cast<short>(std::min(input_material_indices[triangle], uint32_t(32767)));
}
}
std::vector<float> weights;
if (vertex_weights != nullptr) {
weights.assign(vertex_weights, vertex_weights + vertex_count);
if (vertex_group_invert != 0) {
for (float &weight : weights) weight = 1.0f - weight;
}
}
try {
switch (mode) {
case WEB_ENGINE_DECIMATE_COLLAPSE: {
/* A hand-built BMesh can be safely collapsed one native step at a
* time. Blender's desktop conversion keeps a longer-lived heap, but
* repeatedly mutating this small Web mesh in one call can leave heap
* nodes pointing at removed triangles. Each step gets a fresh native
* heap and recalculates the Blender quadric choice from the current
* mesh state without exposing that lifetime issue to WASM. */
const int target_faces = static_cast<int>(bm->totface * ratio);
std::unordered_map<BMVert *, float> weight_by_vertex;
if (!weights.empty()) {
blender::BM_mesh_elem_index_ensure(bm, blender::BM_VERT);
blender::BMIter weight_iter;
BMVert *weight_vertex;
int weight_index = 0;
BM_ITER_MESH_INDEX (weight_vertex, &weight_iter, bm, blender::BM_VERTS_OF_MESH, weight_index) {
weight_by_vertex[weight_vertex] = weights[weight_index];
}
}
uint32_t step_count = 0;
while (bm->totface > target_faces && step_count++ < triangle_count * 4 + 16) {
const int faces_before = bm->totface;
blender::BM_mesh_elem_index_ensure(bm, blender::BM_VERT | blender::BM_EDGE);
std::vector<float> step_weights;
if (!weights.empty()) {
step_weights.assign(static_cast<size_t>(bm->totvert), 0.0f);
blender::BMIter weight_iter;
BMVert *weight_vertex;
int weight_index = 0;
BM_ITER_MESH_INDEX (weight_vertex,
&weight_iter,
bm,
blender::BM_VERTS_OF_MESH,
weight_index) {
const auto found = weight_by_vertex.find(weight_vertex);
if (found != weight_by_vertex.end()) step_weights[weight_index] = found->second;
}
}
const float step_factor = static_cast<float>(faces_before - 1) /
static_cast<float>(std::max(faces_before, 1));
blender::BM_mesh_decimate_collapse(bm,
step_factor,
step_weights.empty() ? nullptr : step_weights.data(),
vertex_group_factor,
triangulate != 0,
symmetry_axis,
symmetry_epsilon);
if (bm->totface >= faces_before) break;
if (!step_weights.empty()) {
weight_by_vertex.clear();
blender::BM_mesh_elem_index_ensure(bm, blender::BM_VERT);
blender::BMIter weight_iter;
BMVert *weight_vertex;
int weight_index = 0;
BM_ITER_MESH_INDEX (weight_vertex,
&weight_iter,
bm,
blender::BM_VERTS_OF_MESH,
weight_index) {
weight_by_vertex[weight_vertex] = step_weights[weight_index];
}
}
}
break;
}
case WEB_ENGINE_DECIMATE_UNSUBDIV:
blender::BM_mesh_decimate_unsubdivide(bm, static_cast<int>(iterations));
break;
case WEB_ENGINE_DECIMATE_DISSOLVE_PLANAR:
blender::BM_mesh_decimate_dissolve(bm,
angle_limit,
use_dissolve_boundaries != 0,
delimit_flags(delimit));
break;
}
}
catch (...) {
blender::BM_mesh_free(bm);
return WEB_ENGINE_DECIMATE_FAILED;
}
blender::BM_mesh_elem_index_ensure(bm, blender::BM_VERT | blender::BM_FACE);
output.positions.reserve(static_cast<size_t>(bm->totvert) * 3);
blender::BMIter iter;
BMVert *vertex;
int vertex_index = 0;
BM_ITER_MESH_INDEX (vertex, &iter, bm, blender::BM_VERTS_OF_MESH, vertex_index) {
blender::BM_elem_index_set(vertex, vertex_index);
output.positions.insert(output.positions.end(), vertex->co, vertex->co + 3);
}
BMFace *face;
BM_ITER_MESH (face, &iter, bm, blender::BM_FACES_OF_MESH) {
BMLoop *first = face->l_first;
if (first == nullptr || first->next == first || first->next->next == first) continue;
BMLoop *second = first->next;
BMLoop *third = second->next;
while (third != first) {
const uint32_t a = static_cast<uint32_t>(blender::BM_elem_index_get(first->v));
const uint32_t b = static_cast<uint32_t>(blender::BM_elem_index_get(second->v));
const uint32_t c = static_cast<uint32_t>(blender::BM_elem_index_get(third->v));
if (a != b && b != c && a != c) {
output.indices.insert(output.indices.end(), {a, b, c});
if (input_material_indices != nullptr) {
output.material_indices.push_back(static_cast<uint32_t>(std::max(face->mat_nr, short(0))));
}
}
second = third;
third = third->next;
}
output.face_count++;
}
blender::BM_mesh_free(bm);
return WEB_ENGINE_DECIMATE_OK;
}
} // namespace
extern "C" EMSCRIPTEN_KEEPALIVE int web_engine_decimate_apply(const float *positions,
const uint32_t vertex_count,
const uint32_t *indices,
const uint32_t triangle_count,
const uint32_t mode,
const float ratio,
const uint32_t iterations,
const float angle_limit,
const uint32_t delimit,
const uint32_t use_dissolve_boundaries,
const uint32_t triangulate,
const int32_t symmetry_axis,
const float symmetry_epsilon,
const float *vertex_weights,
const float vertex_group_factor,
const uint32_t vertex_group_invert,
const float *uvs,
const float *colors,
const uint32_t *material_indices,
float *positions_out,
const uint32_t positions_capacity,
uint32_t *positions_count,
uint32_t *indices_out,
const uint32_t indices_capacity,
uint32_t *indices_count,
float *uvs_out,
const uint32_t uvs_capacity,
uint32_t *uvs_count,
float *colors_out,
const uint32_t colors_capacity,
uint32_t *colors_count,
uint32_t *material_indices_out,
const uint32_t material_indices_capacity,
uint32_t *material_indices_count,
uint32_t *face_count)
{
if (positions_count == nullptr || indices_count == nullptr || uvs_count == nullptr || colors_count == nullptr ||
material_indices_count == nullptr || face_count == nullptr) {
return WEB_ENGINE_DECIMATE_INVALID_ARGUMENT;
}
OutputMesh output;
const int result = build_and_decimate(positions,
vertex_count,
indices,
triangle_count,
mode,
ratio,
iterations,
angle_limit,
delimit,
use_dissolve_boundaries,
triangulate,
symmetry_axis,
symmetry_epsilon,
vertex_weights,
vertex_group_factor,
vertex_group_invert,
uvs,
colors,
material_indices,
output);
if (result != WEB_ENGINE_DECIMATE_OK) return result;
*positions_count = static_cast<uint32_t>(output.positions.size());
*indices_count = static_cast<uint32_t>(output.indices.size());
*uvs_count = static_cast<uint32_t>(output.uvs.size());
*colors_count = static_cast<uint32_t>(output.colors.size());
*material_indices_count = static_cast<uint32_t>(output.material_indices.size());
*face_count = output.face_count;
if (positions_capacity < output.positions.size() || indices_capacity < output.indices.size() ||
uvs_capacity < output.uvs.size() || colors_capacity < output.colors.size() ||
material_indices_capacity < output.material_indices.size() ||
(output.positions.size() > 0 && positions_out == nullptr) ||
(output.indices.size() > 0 && indices_out == nullptr) ||
(output.uvs.size() > 0 && uvs_out == nullptr) || (output.colors.size() > 0 && colors_out == nullptr) ||
(output.material_indices.size() > 0 && material_indices_out == nullptr)) {
return WEB_ENGINE_DECIMATE_BUFFER_TOO_SMALL;
}
if (!output.positions.empty()) std::memcpy(positions_out, output.positions.data(), output.positions.size() * sizeof(float));
if (!output.indices.empty()) std::memcpy(indices_out, output.indices.data(), output.indices.size() * sizeof(uint32_t));
if (!output.uvs.empty()) std::memcpy(uvs_out, output.uvs.data(), output.uvs.size() * sizeof(float));
if (!output.colors.empty()) std::memcpy(colors_out, output.colors.data(), output.colors.size() * sizeof(float));
if (!output.material_indices.empty()) {
std::memcpy(material_indices_out,
output.material_indices.data(),
output.material_indices.size() * sizeof(uint32_t));
}
return WEB_ENGINE_DECIMATE_OK;
}

View File

@@ -0,0 +1,61 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#pragma once
#include <cstdint>
enum WebEngineDecimateMode : uint32_t {
WEB_ENGINE_DECIMATE_COLLAPSE = 0,
WEB_ENGINE_DECIMATE_UNSUBDIV = 1,
WEB_ENGINE_DECIMATE_DISSOLVE_PLANAR = 2,
};
enum WebEngineDecimateResultCode : int {
WEB_ENGINE_DECIMATE_OK = 0,
WEB_ENGINE_DECIMATE_INVALID_ARGUMENT = -2,
WEB_ENGINE_DECIMATE_NOT_IMPLEMENTED = -3,
WEB_ENGINE_DECIMATE_FAILED = -4,
WEB_ENGINE_DECIMATE_BUFFER_TOO_SMALL = -6,
};
/*
* Run Blender's BMesh decimate implementation on a triangle input. The first
* call may pass null output buffers and zero capacities to query the required
* float/index counts. A second call with those capacities copies the evaluated
* topology while leaving the input arrays untouched.
*/
extern "C" int web_engine_decimate_apply(const float *positions,
uint32_t vertex_count,
const uint32_t *indices,
uint32_t triangle_count,
uint32_t mode,
float ratio,
uint32_t iterations,
float angle_limit,
uint32_t delimit,
uint32_t use_dissolve_boundaries,
uint32_t triangulate,
int32_t symmetry_axis,
float symmetry_epsilon,
const float *vertex_weights,
float vertex_group_factor,
uint32_t vertex_group_invert,
const float *uvs,
const float *colors,
const uint32_t *material_indices,
float *positions_out,
uint32_t positions_capacity,
uint32_t *positions_count,
uint32_t *indices_out,
uint32_t indices_capacity,
uint32_t *indices_count,
float *uvs_out,
uint32_t uvs_capacity,
uint32_t *uvs_count,
float *colors_out,
uint32_t colors_capacity,
uint32_t *colors_count,
uint32_t *material_indices_out,
uint32_t material_indices_capacity,
uint32_t *material_indices_count,
uint32_t *face_count);

View File

@@ -0,0 +1,750 @@
#include "web_engine_depsgraph.h"
#include "web_engine_blend_reader.h"
#include <cstdint>
#include <limits>
#include <mutex>
#include <optional>
#include <unordered_set>
#ifdef WEB_ENGINE_ENABLE_UNSAFE_DEPSGRAPH
# include <cstdio>
# define WEB_DEPSGRAPH_TRACE(stage) \
do { \
std::fprintf(stderr, "web-depsgraph-stage=%s\n", stage); \
std::fflush(stderr); \
} while (false)
#else
# define WEB_DEPSGRAPH_TRACE(stage) ((void)0)
#endif
#include "json.hpp"
#include "BKE_appdir.hh"
#include "BKE_attribute.hh"
#include "BKE_blender.hh"
#include "BKE_callbacks.hh"
#include "BKE_cpp_types.hh"
#include "BKE_global.hh"
#include "BKE_idtype.hh"
#include "BKE_layer.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_material.hh"
#include "BKE_mesh.h"
#include "BKE_mesh_wrapper.hh"
#include "BKE_modifier.hh"
#include "BKE_node.hh"
#include "BKE_object.hh"
#include "BKE_scene.hh"
#include "BKE_vfont.hh"
#include "BLI_listbase.h"
#include "BLI_task.h"
#include "BLI_threads.h"
#include "BLO_readfile.hh"
#include "BLF_api.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
#include "DEG_depsgraph_query.hh"
#include "ED_datafiles.h"
#include "DNA_genfile.h"
#include "DNA_action_types.h"
#include "DNA_constraint_types.h"
#include "DNA_curve_types.h"
#include "DNA_mesh_types.h"
#include "DNA_modifier_types.h"
#include "DNA_object_types.h"
#include "DNA_scene_types.h"
#include "DNA_vfont_types.h"
#include "DNA_world_types.h"
#include "FN_init.hh"
#include "RNA_define.hh"
#include "CLG_log.h"
namespace blender {
void WEB_headless_node_system_init();
void web_rna_init();
}
namespace {
using json = nlohmann::json;
using namespace blender;
std::once_flag blender_runtime_once;
void initialize_blender_runtime()
{
std::call_once(blender_runtime_once, []() {
CLG_init();
BLI_threadapi_init();
DNA_sdna_current_init();
BKE_blender_globals_init();
BKE_cpp_types_init();
web_rna_init();
fn::multi_function::register_common_functions();
BKE_idtype_init();
BKE_appdir_init();
BKE_modifier_init();
BKE_callback_global_init();
BLI_task_scheduler_init();
BLF_init();
BKE_vfont_builtin_register(datatoc_bfont_pfb, datatoc_bfont_pfb_size);
WEB_headless_node_system_init();
DEG_register_node_types();
G.background = true;
G.factory_startup = true;
});
}
#ifdef WEB_ENGINE_ENABLE_UNSAFE_DEPSGRAPH
void trace_loaded_scene(Main *main, Scene *scene)
{
std::fprintf(stderr,
"web-depsgraph-data scene=%p master=%p nodetree=%p world=%p collections=%p\n",
static_cast<void *>(scene),
scene ? static_cast<void *>(scene->master_collection) : nullptr,
scene ? static_cast<void *>(scene->nodetree) : nullptr,
scene && scene->world ? static_cast<void *>(scene->world->nodetree) : nullptr,
main ? main->collections.first : nullptr);
if (main == nullptr) return;
int index = 0;
for (Collection &collection : main->collections) {
if (index++ >= 16) break;
std::fprintf(stderr,
"web-depsgraph-collection index=%d id=%p name=%c%c%c%c first=%p last=%p\n",
index - 1,
static_cast<void *>(&collection),
collection.id.name[0],
collection.id.name[1],
collection.id.name[2],
collection.id.name[3],
collection.gobject.first,
collection.children.first);
}
}
#endif
class GlobalMainScope {
public:
explicit GlobalMainScope(Main *loaded_main) : loaded_main_(loaded_main)
{
if (loaded_main_ != nullptr && !loaded_main_->is_global_main) {
previous_main_ = BKE_blender_globals_main_swap(loaded_main_);
}
}
~GlobalMainScope()
{
if (previous_main_ != nullptr) {
Main *restored_loaded_main = BKE_blender_globals_main_swap(previous_main_);
BLI_assert(restored_loaded_main == loaded_main_);
(void)restored_loaded_main;
}
}
GlobalMainScope(const GlobalMainScope &) = delete;
GlobalMainScope &operator=(const GlobalMainScope &) = delete;
private:
Main *loaded_main_ = nullptr;
Main *previous_main_ = nullptr;
};
std::string id_name(const ID &id)
{
return id.name[0] != '\0' && id.name[1] != '\0' ? std::string(id.name + 2) : std::string();
}
std::string scene_ir_id(const char *prefix, const ID &id)
{
const std::string name = id_name(id);
return std::string(prefix) + ":" + (name.empty() ? "unnamed" : name);
}
std::string object_type_name(const Object &object)
{
switch (object.type) {
case OB_MESH:
return "MESH";
case OB_CURVES_LEGACY:
return "CURVE";
case OB_SURF:
return "SURFACE";
case OB_FONT:
return "FONT";
case OB_MBALL:
return "METABALL";
case OB_LATTICE:
return "LATTICE";
case OB_ARMATURE:
return "ARMATURE";
case OB_GREASE_PENCIL:
return "GREASE_PENCIL";
case OB_CURVES:
return "CURVES";
case OB_POINTCLOUD:
return "POINT_CLOUD";
case OB_VOLUME:
return "VOLUME";
case OB_EMPTY:
return "EMPTY";
default:
return "OTHER";
}
}
std::optional<std::string> preflight_unsafe_resource(const uint8_t *data, const uint32_t length)
{
/* Geometry node trees are safe to load with undefined node typeinfo. The
* bounded Web modifier validates its supported graph before executing it. */
(void)data;
(void)length;
return std::nullopt;
}
struct ModifierTargetObjects {
const Object *primary = nullptr;
const Object *secondary = nullptr;
bool required = false;
};
ModifierTargetObjects modifier_target_objects(const ModifierData &modifier)
{
switch (modifier.type) {
case eModifierType_Array: {
const auto &array = reinterpret_cast<const ArrayModifierData &>(modifier);
return {array.offset_ob, array.curve_ob, false};
}
case eModifierType_Mirror:
return {reinterpret_cast<const MirrorModifierData &>(modifier).mirror_ob, nullptr, false};
case eModifierType_Boolean:
return {reinterpret_cast<const BooleanModifierData &>(modifier).object, nullptr, false};
case eModifierType_Lattice:
return {reinterpret_cast<const LatticeModifierData &>(modifier).object, nullptr, true};
case eModifierType_Curve:
return {reinterpret_cast<const CurveModifierData &>(modifier).object, nullptr, true};
case eModifierType_Armature:
return {reinterpret_cast<const ArmatureModifierData &>(modifier).object, nullptr, true};
case eModifierType_Hook:
return {reinterpret_cast<const HookModifierData &>(modifier).object, nullptr, true};
case eModifierType_MeshDeform:
return {reinterpret_cast<const MeshDeformModifierData &>(modifier).object, nullptr, true};
case eModifierType_Shrinkwrap: {
const auto &shrinkwrap = reinterpret_cast<const ShrinkwrapModifierData &>(modifier);
return {shrinkwrap.target, shrinkwrap.auxTarget, true};
}
case eModifierType_SurfaceDeform:
return {reinterpret_cast<const SurfaceDeformModifierData &>(modifier).target, nullptr, true};
case eModifierType_Screw:
return {reinterpret_cast<const ScrewModifierData &>(modifier).ob_axis, nullptr, false};
case eModifierType_Displace:
return {reinterpret_cast<const DisplaceModifierData &>(modifier).map_object, nullptr, false};
default:
return {};
}
}
bool object_dependency_reaches(const Object *object,
const Object *needle,
std::unordered_set<const Object *> &visited)
{
if (object == nullptr) return false;
if (object == needle) return true;
if (!visited.insert(object).second) return false;
for (const ModifierData *modifier = static_cast<const ModifierData *>(object->modifiers.first);
modifier != nullptr;
modifier = modifier->next)
{
if ((modifier->mode & eModifierMode_Realtime) == 0) continue;
const ModifierTargetObjects targets = modifier_target_objects(*modifier);
if (object_dependency_reaches(targets.primary, needle, visited) ||
object_dependency_reaches(targets.secondary, needle, visited))
{
return true;
}
}
return false;
}
json evaluated_modifier_report(const Object &object, const Object *object_eval)
{
json modifiers = json::array();
size_t index = 0;
std::string previous_uuid;
for (const ModifierData *modifier = static_cast<const ModifierData *>(object.modifiers.first);
modifier != nullptr;
modifier = modifier->next, index++) {
const ModifierTypeInfo *info = BKE_modifier_get_info(modifier->type);
const bool supported_runtime = info != nullptr &&
(modifier->type == eModifierType_None ||
info->type != ModifierTypeType::None);
const int uid = modifier->persistent_uid;
const std::string uuid = "modifier:" + scene_ir_id("object", object.id) + ":" +
(uid > 0 ? std::to_string(uid) : std::to_string(index));
const bool viewport_enabled = (modifier->mode & eModifierMode_Realtime) != 0;
const ModifierTargetObjects targets = modifier_target_objects(*modifier);
json target_object_ids = json::array();
auto append_target = [&target_object_ids](const Object *target) {
if (target != nullptr) target_object_ids.push_back(scene_ir_id("object", target->id));
};
if (supported_runtime) {
append_target(targets.primary);
append_target(targets.secondary);
}
json entry = {{"uuid", uuid},
{"index", index},
{"persistentUid", uid},
{"typeCode", int(modifier->type)},
{"type", supported_runtime ? std::string(info->idname) :
"UNSUPPORTED_" + std::to_string(int(modifier->type))},
{"name", std::string(modifier->name)},
{"showViewport", viewport_enabled},
{"showRender", (modifier->mode & eModifierMode_Render) != 0},
{"showEditmode", (modifier->mode & eModifierMode_Editmode) != 0},
{"showOnCage", (modifier->mode & eModifierMode_OnCage) != 0},
{"status", viewport_enabled ? "EVALUATED" : "DISABLED"}};
if (!target_object_ids.empty()) entry["targetObjectIds"] = target_object_ids;
if (!viewport_enabled) {
entry["reason"] = "viewport-disabled";
}
if (viewport_enabled && !supported_runtime) {
entry["status"] = "BLOCKED";
entry["errorCode"] = "UNSUPPORTED_MODIFIER_TYPE";
entry["error"] = "Blender modifier type is not registered in the WebEngine runtime";
entry["suggestion"] = "Disable, apply, or convert this modifier before Web evaluation";
}
if (viewport_enabled && supported_runtime && targets.required && target_object_ids.empty()) {
entry["status"] = "BLOCKED";
entry["errorCode"] = "MODIFIER_TARGET_MISSING";
entry["error"] = "Modifier requires a target object";
entry["suggestion"] = "Assign a valid target object or disable this modifier";
}
if (object_eval != nullptr) {
for (const ModifierData *evaluated = static_cast<const ModifierData *>(object_eval->modifiers.first);
evaluated != nullptr;
evaluated = evaluated->next) {
if (uid > 0 && evaluated->persistent_uid == uid) {
if (viewport_enabled && evaluated->error != nullptr && evaluated->error[0] != '\0') {
const std::string error(evaluated->error);
entry["status"] = "BLOCKED";
if (error.starts_with("WEB_GEOMETRY_NODES_SIMULATION_UNAVAILABLE:")) {
entry["errorCode"] = "GEOMETRY_NODES_SIMULATION_UNAVAILABLE";
entry["suggestion"] =
"Bake a deterministic cache in desktop Blender or disable the simulation zone";
}
else if (error.starts_with("WEB_DISPLACE_CONFIGURATION_UNAVAILABLE:")) {
entry["errorCode"] = "MODIFIER_CONFIGURATION_UNSUPPORTED";
entry["suggestion"] =
"Use constant local-space displacement or disable/apply this modifier in desktop Blender";
}
else {
entry["errorCode"] = "BLENDER_MODIFIER_ERROR";
entry["suggestion"] = "Fix the Blender modifier settings or disable the modifier";
}
entry["error"] = error;
}
break;
}
}
}
if (viewport_enabled && supported_runtime && !target_object_ids.empty()) {
std::unordered_set<const Object *> visited;
if (object_dependency_reaches(targets.primary, &object, visited) ||
object_dependency_reaches(targets.secondary, &object, visited))
{
entry["status"] = "BLOCKED";
entry["errorCode"] = "MODIFIER_DEPENDENCY_CYCLE";
entry["error"] = "Modifier target dependency reaches the owning object";
entry["suggestion"] = "Break the target-object cycle or disable one modifier in the cycle";
}
}
json dependencies = json::array();
if (!previous_uuid.empty()) dependencies.push_back(previous_uuid);
for (const json &target : target_object_ids) dependencies.push_back(target);
if (!dependencies.empty()) entry["dependsOn"] = std::move(dependencies);
modifiers.push_back(std::move(entry));
previous_uuid = uuid;
}
return modifiers;
}
json evaluated_mesh_report(const Depsgraph *graph, Object *object)
{
Object *object_eval = DEG_get_evaluated(graph, object);
if (object_eval == nullptr) return json();
Mesh *mesh = BKE_object_get_evaluated_mesh(object_eval);
if (mesh == nullptr) return json();
BKE_mesh_wrapper_ensure_mdata(mesh);
const Span<float3> positions = mesh->vert_positions();
const Span<int> corner_verts = mesh->corner_verts();
const Span<int3> corner_tris = mesh->corner_tris();
json report = {{"objectId", scene_ir_id("object", object->id)},
{"meshId", scene_ir_id("mesh", mesh->id)},
{"sourceMeshId", object->data != nullptr ?
scene_ir_id("mesh", *static_cast<ID *>(object->data)) :
std::string()},
{"vertexCount", positions.size()},
{"triangleCount", corner_tris.size()},
{"modifierCount", BLI_listbase_count(&object->modifiers)},
{"modifiers", evaluated_modifier_report(*object, object_eval)},
{"worldMatrix", json::array()},
{"positions", json::array()},
{"indices", json::array()}};
for (int row = 0; row < 4; row++) {
for (int column = 0; column < 4; column++) {
/* The Web scene IR uses the same column-major flattening as Blender's Python API and glTF. */
report["worldMatrix"].push_back(object_eval->object_to_world()[column][row]);
}
}
for (const float3 &position : positions) {
report["positions"].push_back(position.x);
report["positions"].push_back(position.y);
report["positions"].push_back(position.z);
}
for (const int3 &triangle : corner_tris) {
for (int index = 0; index < 3; index++) {
report["indices"].push_back(corner_verts[triangle[index]]);
}
}
return report;
}
std::string non_mesh_data_id(const Object &object)
{
if (object.data == nullptr) return {};
const ID &id = *static_cast<const ID *>(object.data);
switch (object.type) {
case OB_CURVES_LEGACY:
case OB_SURF:
case OB_FONT:
return scene_ir_id("curve", id);
case OB_MBALL:
return scene_ir_id("metaball", id);
case OB_CURVES:
return scene_ir_id("curves", id);
case OB_POINTCLOUD:
return scene_ir_id("pointcloud", id);
case OB_VOLUME:
return scene_ir_id("volume", id);
default:
return {};
}
}
void ensure_builtin_font_links(Main *main)
{
if (main == nullptr) return;
VFont *builtin = nullptr;
for (Object &object : main->objects) {
if (object.type != OB_FONT || object.data == nullptr) continue;
Curve &curve = *reinterpret_cast<Curve *>(object.data);
if (curve.vfont == nullptr) {
if (builtin == nullptr) {
builtin = BKE_vfont_builtin_ensure();
}
curve.vfont = builtin;
builtin->id.us++;
}
}
}
json evaluated_non_mesh_report(Depsgraph *graph, Object *object)
{
Object *object_eval = DEG_get_evaluated(graph, object);
if (object_eval == nullptr) return json();
Mesh *mesh = BKE_object_get_evaluated_mesh(object_eval);
bool owns_mesh = false;
if (mesh == nullptr) {
mesh = BKE_mesh_new_from_object(graph, object_eval, false, false, true);
owns_mesh = mesh != nullptr;
}
if (mesh == nullptr) return json();
BKE_mesh_wrapper_ensure_mdata(mesh);
const Span<float3> positions = mesh->vert_positions();
const Span<float3> normals = mesh->vert_normals();
const Span<int2> edges = mesh->edges();
const Span<int> corner_verts = mesh->corner_verts();
const Span<int3> corner_tris = mesh->corner_tris();
const Span<int> triangle_faces = mesh->corner_tri_faces();
json report = {{"objectId", scene_ir_id("object", object->id)},
{"sourceDataId", non_mesh_data_id(*object)},
{"sourceType", object_type_name(*object)},
{"meshId", "evaluated:" + scene_ir_id("object", object->id)},
{"vertexCount", positions.size()},
{"edgeCount", edges.size()},
{"triangleCount", corner_tris.size()},
{"status", "EVALUATED"},
{"worldMatrix", json::array()},
{"positions", json::array()},
{"normals", json::array()},
{"edgeVertexIndices", json::array()},
{"indices", json::array()},
{"uvs", json::array()},
{"triangleMaterialIndices", json::array()},
{"sourceElementIndices", json::array()},
{"materialSlotIds", json::array()},
{"sourceMappingStatus", corner_tris.is_empty() && !edges.is_empty() ?
"EVALUATED_EDGE" :
"EVALUATED_FACE"}};
for (int row = 0; row < 4; row++) {
for (int column = 0; column < 4; column++) {
report["worldMatrix"].push_back(object_eval->object_to_world()[column][row]);
}
}
if (positions.size() > 1000000 || edges.size() > 2000000 ||
corner_tris.size() > 2000000)
{
report["status"] = "BLOCKED";
report["errorCode"] = "NON_MESH_DATA_BUDGET_EXCEEDED";
for (const char *field : {"positions", "normals", "edgeVertexIndices", "indices", "uvs", "triangleMaterialIndices", "sourceElementIndices"}) {
report.erase(field);
}
if (owns_mesh) BKE_id_free(nullptr, mesh);
return report;
}
for (const float3 &position : positions) {
report["positions"].push_back(position.x);
report["positions"].push_back(position.y);
report["positions"].push_back(position.z);
}
for (const float3 &normal : normals) {
report["normals"].push_back(normal.x);
report["normals"].push_back(normal.y);
report["normals"].push_back(normal.z);
}
for (const int2 &edge : edges) {
report["edgeVertexIndices"].push_back(edge[0]);
report["edgeVertexIndices"].push_back(edge[1]);
}
const bke::AttributeAccessor attributes = mesh->attributes();
const bke::GAttributeReader material_reader = attributes.lookup(
"material_index", bke::AttrDomain::Face, bke::AttrType::Int32);
const VArray<int> material_indices = material_reader ?
material_reader.varray.typed<int>() :
VArray<int>();
const StringRef active_uv = mesh->active_uv_map_name();
const bke::AttributeReader<float2> uv_reader = active_uv.is_empty() ?
bke::AttributeReader<float2>() :
attributes.lookup<float2>(active_uv,
bke::AttrDomain::Corner);
for (size_t triangle_index = 0; triangle_index < corner_tris.size(); triangle_index++) {
const int3 &triangle = corner_tris[triangle_index];
for (int index = 0; index < 3; index++) {
report["indices"].push_back(corner_verts[triangle[index]]);
if (uv_reader) {
const float2 uv = uv_reader.varray[triangle[index]];
report["uvs"].push_back(uv.x);
report["uvs"].push_back(uv.y);
}
}
const int face_index = triangle_index < triangle_faces.size() ? triangle_faces[triangle_index] :
int(triangle_index);
report["sourceElementIndices"].push_back(face_index);
report["triangleMaterialIndices"].push_back(
!material_indices.is_empty() && face_index >= 0 && face_index < material_indices.size() ?
std::max(0, material_indices[face_index]) :
0);
}
for (short slot = 1; slot <= BKE_object_material_count_eval(object_eval); slot++) {
if (Material *material = BKE_object_material_get(object_eval, slot)) {
report["materialSlotIds"].push_back(scene_ir_id("material", material->id));
}
else {
report["materialSlotIds"].push_back("");
}
}
if (owns_mesh) BKE_id_free(nullptr, mesh);
return report;
}
json evaluated_armature_report(const Depsgraph *graph, Object *object)
{
Object *object_eval = DEG_get_evaluated(graph, object);
if (object_eval == nullptr || object_eval->pose == nullptr || object_eval->data == nullptr) {
return json();
}
const ID &armature_id = *static_cast<const ID *>(object->data);
json bones = json::array();
for (const bPoseChannel *channel = static_cast<const bPoseChannel *>(object_eval->pose->chanbase.first);
channel != nullptr;
channel = channel->next)
{
const std::string bone_id = "bone:" + scene_ir_id("armature", armature_id) + ":" +
std::string(channel->name);
json pose_matrix = json::array();
for (int row = 0; row < 4; row++) {
for (int column = 0; column < 4; column++) pose_matrix.push_back(channel->pose_mat[column][row]);
}
json constraints = json::array();
for (const bConstraint *constraint = static_cast<const bConstraint *>(channel->constraints.first);
constraint != nullptr;
constraint = constraint->next)
{
json entry = {{"name", std::string(constraint->name)},
{"typeCode", int(constraint->type)},
{"influence", constraint->enforce},
{"flag", int(constraint->flag)}};
const Object *target = constraint->space_object;
if (constraint->type == CONSTRAINT_TYPE_ROTLIKE && constraint->data != nullptr) {
target = reinterpret_cast<const bRotateLikeConstraint *>(constraint->data)->tar;
}
if (constraint->type == CONSTRAINT_TYPE_KINEMATIC && constraint->data != nullptr) {
const bKinematicConstraint *kinematic = reinterpret_cast<const bKinematicConstraint *>(
constraint->data);
target = kinematic->tar;
if (kinematic->poletar != nullptr) {
entry["poleTargetObjectId"] = scene_ir_id("object", kinematic->poletar->id);
}
}
if (target != nullptr) entry["targetObjectId"] = scene_ir_id("object", target->id);
constraints.push_back(std::move(entry));
}
bones.push_back({{"id", bone_id}, {"name", std::string(channel->name)}, {"poseMatrix", pose_matrix}, {"constraints", constraints}});
}
return json{{"id", scene_ir_id("armature", armature_id)},
{"objectId", scene_ir_id("object", object->id)},
{"bones", std::move(bones)}};
}
} // namespace
void web_engine_blender_runtime_initialize()
{
initialize_blender_runtime();
}
WebDepsgraphEvaluationResult web_engine_evaluate_blender_depsgraph(const uint8_t *data,
const uint32_t length,
const int requested_frame)
{
WebDepsgraphEvaluationResult result;
if (data == nullptr || length == 0 || length > uint32_t(std::numeric_limits<int>::max())) {
result.error = "Depsgraph requires a non-empty addressable blend buffer";
return result;
}
if (const std::optional<std::string> blocked = preflight_unsafe_resource(data, length)) {
result.error = *blocked;
return result;
}
WEB_DEPSGRAPH_TRACE("runtime-init-begin");
initialize_blender_runtime();
WEB_DEPSGRAPH_TRACE("runtime-init-end");
BlendFileData *blend = BLO_read_from_memory(data, int(length), BLO_READ_SKIP_USERDEF, nullptr);
WEB_DEPSGRAPH_TRACE("blend-read-end");
if (blend == nullptr || blend->main == nullptr) {
result.error = "Blender full loader rejected the blend buffer";
if (blend != nullptr) BLO_blendfiledata_free(blend);
return result;
}
Scene *scene = blend->curscene != nullptr ? blend->curscene :
static_cast<Scene *>(blend->main->scenes.first);
ViewLayer *view_layer = blend->cur_view_layer != nullptr ? blend->cur_view_layer :
(scene != nullptr ? static_cast<ViewLayer *>(scene->view_layers.first) : nullptr);
if (scene == nullptr || view_layer == nullptr) {
result.error = "Blender full loader found no scene/view layer";
BLO_blendfiledata_free(blend);
return result;
}
BKE_scene_frame_set(scene,
float(std::clamp(requested_frame, scene->r.sfra, scene->r.efra)));
for (ViewLayer &candidate : scene->view_layers) {
BKE_view_layer_synced_ensure(*blend->main, scene, &candidate);
}
WEB_DEPSGRAPH_TRACE("view-layer-sync-end");
#ifdef WEB_ENGINE_ENABLE_UNSAFE_DEPSGRAPH
trace_loaded_scene(blend->main, scene);
#endif
{
GlobalMainScope global_main_scope(blend->main);
ensure_builtin_font_links(blend->main);
WEB_DEPSGRAPH_TRACE("global-main-swap-end");
Depsgraph *graph = DEG_graph_new(blend->main, scene, view_layer, DAG_EVAL_VIEWPORT);
WEB_DEPSGRAPH_TRACE("graph-new-end");
if (graph == nullptr) {
result.error = "Blender Depsgraph allocation failed";
}
else {
DEG_graph_build_from_view_layer(graph);
WEB_DEPSGRAPH_TRACE("graph-build-end");
BKE_scene_graph_update_for_newframe(graph);
WEB_DEPSGRAPH_TRACE("frame-change-end");
for (Object *object = static_cast<Object *>(blend->main->objects.first); object != nullptr;
object = static_cast<Object *>(object->id.next)) {
if (Object *object_eval = DEG_get_evaluated(graph, object)) {
BKE_object_where_is_calc_time(
graph, DEG_get_evaluated_scene(graph), object_eval, DEG_get_ctime(graph));
}
}
/* The reduced RNA registry cannot always expose transform dependencies to the graph
* builder. Re-run the time update after the explicit transform pass so constraints consume
* animated target matrices in the same frame. */
BKE_scene_graph_update_for_newframe(graph);
for (Object *object = static_cast<Object *>(blend->main->objects.first); object != nullptr;
object = static_cast<Object *>(object->id.next)) {
if (Object *object_eval = DEG_get_evaluated(graph, object)) {
BKE_object_where_is_calc_time(
graph, DEG_get_evaluated_scene(graph), object_eval, DEG_get_ctime(graph));
}
}
size_t object_count = 0;
size_t mesh_object_count = 0;
json evaluated_meshes = json::array();
json evaluated_non_mesh_geometries = json::array();
json evaluated_objects = json::array();
json evaluated_armatures = json::array();
for (Object *object = static_cast<Object *>(blend->main->objects.first); object != nullptr;
object = static_cast<Object *>(object->id.next)) {
object_count++;
Object *object_eval = DEG_get_evaluated(graph, object);
evaluated_objects.push_back({{"objectId", scene_ir_id("object", object->id)},
{"type", object_type_name(*object)},
{"modifierCount", BLI_listbase_count(&object->modifiers)},
{"modifiers", evaluated_modifier_report(*object, object_eval)}});
if (object->type == OB_MESH) {
mesh_object_count++;
const json mesh = evaluated_mesh_report(graph, object);
if (!mesh.is_null()) evaluated_meshes.push_back(mesh);
}
else if (object->type == OB_ARMATURE) {
const json armature = evaluated_armature_report(graph, object);
if (!armature.is_null()) evaluated_armatures.push_back(armature);
}
else if (object->type == OB_CURVES_LEGACY || object->type == OB_SURF ||
object->type == OB_FONT || object->type == OB_MBALL)
{
const json geometry = evaluated_non_mesh_report(graph, object);
if (!geometry.is_null()) evaluated_non_mesh_geometries.push_back(geometry);
}
}
result.report = json{{"engine", "BlenderDepsgraph"},
{"status", "EVALUATED"},
{"scene", scene->id.name + 2},
{"viewLayer", view_layer->name},
{"frame", scene->r.cfra},
{"objectCount", object_count},
{"meshObjectCount", mesh_object_count},
{"objects", evaluated_objects},
{"meshes", evaluated_meshes},
{"nonMeshGeometries", evaluated_non_mesh_geometries},
{"armatures", evaluated_armatures}}
.dump();
result.ok = true;
DEG_graph_free(graph);
WEB_DEPSGRAPH_TRACE("graph-free-end");
}
}
WEB_DEPSGRAPH_TRACE("global-main-restore-end");
BLO_blendfiledata_free(blend);
WEB_DEPSGRAPH_TRACE("blend-free-end");
return result;
}

View File

@@ -0,0 +1,16 @@
#pragma once
#include <cstdint>
#include <string>
struct WebDepsgraphEvaluationResult {
bool ok = false;
std::string error;
std::string report;
};
void web_engine_blender_runtime_initialize();
WebDepsgraphEvaluationResult web_engine_evaluate_blender_depsgraph(const uint8_t *data,
uint32_t length,
int frame);

View File

@@ -0,0 +1,414 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Headless data-engine boundary.
*
* Blender's datablock and dependency-graph code intentionally keeps a few
* references to desktop systems. The browser build renders with Three.js and
* therefore has no GPU context, render engine, window manager, sequencer
* playback thread, or geometry-node editor. These narrowly-scoped fallbacks
* make those boundaries explicit; they never fabricate evaluated geometry.
*/
#include <memory>
#include <optional>
#include "BKE_image.hh"
#include "BKE_node.hh"
#include "DNA_node_types.h"
#include "DNA_scene_types.h"
#include "DNA_sequence_types.h"
#include "ED_asset_shelf.hh"
#include "ED_viewer_path.hh"
#include "GPU_context.hh"
#include "GPU_material.hh"
#include "GPU_state.hh"
#include "GPU_texture.hh"
#include "MEM_guardedalloc.h"
#include "NOD_compositor_nodes_srna.hh"
#include "NOD_geometry_nodes_gizmos.hh"
#include "NOD_geometry_nodes_lazy_function.hh"
#include "NOD_geometry_nodes_srna.hh"
#include "NOD_shader.h"
#include "NOD_sync_sockets.hh"
#include "NOD_texture.h"
#include "RE_pipeline.h"
#include "RE_texture.h"
#include "rna_internal.hh"
#include "SEQ_relations.hh"
#include "SEQ_modifier.hh"
#include "SEQ_sequencer.hh"
#include "SEQ_iterator.hh"
#include "WM_api.hh"
extern "C" unsigned long build_commit_timestamp = 0;
extern "C" char build_hash[16] = "unknown";
namespace blender {
bke::bNodeTreeType *ntreeType_Geometry;
static void web_geometry_node_tree_update(bNodeTree *ntree)
{
bke::node_tree_set_output(*ntree);
}
static bool web_geometry_node_tree_validate_link(const eNodeSocketDatatype type_a,
const eNodeSocketDatatype type_b)
{
return type_a == type_b;
}
static void register_web_geometry_node_types()
{
bke::bNodeTreeType *tree_type = ntreeType_Geometry = MEM_new<bke::bNodeTreeType>(__func__);
tree_type->type = NTREE_GEOMETRY;
tree_type->idname = "GeometryNodeTree"_ustr;
tree_type->group_idname = "GeometryNodeGroup"_ustr;
tree_type->update = web_geometry_node_tree_update;
tree_type->valid_socket_type = [](bke::bNodeTreeType *, bke::bNodeSocketType *) { return true; };
tree_type->validate_link = web_geometry_node_tree_validate_link;
bke::node_tree_type_add(*tree_type);
}
void WEB_headless_node_system_init()
{
static bool initialized = false;
if (initialized) {
return;
}
initialized = true;
bke::NodeTreeTypeUndefined.type = NTREE_UNDEFINED;
bke::NodeTreeTypeUndefined.idname = "NodeTreeUndefined"_ustr;
bke::NodeTypeUndefined.idname = "NodeUndefined"_ustr;
bke::NodeSocketTypeUndefined.idname = "NodeSocketUndefined"_ustr;
static bke::bNodeTreeType compositor_type;
static bke::bNodeTreeType texture_type;
compositor_type.type = NTREE_COMPOSIT;
compositor_type.idname = "CompositorNodeTree"_ustr;
texture_type.type = NTREE_TEXTURE;
texture_type.idname = "TextureNodeTree"_ustr;
bke::node_tree_type_add(compositor_type);
bke::node_tree_type_add(texture_type);
static bke::bNodeTreeType shader_type;
shader_type.type = NTREE_SHADER;
shader_type.idname = "ShaderNodeTree"_ustr;
bke::node_tree_type_add(shader_type);
ntreeType_Shader = &shader_type;
register_web_geometry_node_types();
}
bool rna_property_override_apply_default(Main * /*bmain*/,
RNAPropertyOverrideApplyContext & /*context*/)
{
return false;
}
/* ---------------------------- GPU / Render ---------------------------- */
GPUContext *GPU_context_active_get()
{
return nullptr;
}
void GPU_memory_barrier(const GPUBarrier /*barrier*/) {}
void *GPU_texture_read(gpu::Texture * /*texture*/, eGPUDataFormat /*data_format*/, int /*mip_level*/)
{
return nullptr;
}
void GPU_texture_read(gpu::Texture * /*texture*/,
eGPUDataFormat /*data_format*/,
int /*mip_level*/,
void * /*dst*/)
{
}
namespace gpu {
void GPU_activate_secondary_context(const GPUSecondaryContextData & /*data*/) {}
void GPU_deactivate_secondary_context(const GPUSecondaryContextData & /*data*/) {}
} // namespace gpu
void GPU_material_free(ListBaseT<LinkData> * /*gpumaterial*/) {}
void BKE_image_free_gputextures(Image * /*image*/) {}
Render *RE_GetSceneRender(const Scene * /*scene*/)
{
return nullptr;
}
void RE_FreeRenderResult(RenderResult * /*result*/) {}
void RE_AcquireResultImage(Render * /*render*/, RenderResult * /*result*/, int /*view_id*/) {}
void RE_ReleaseResultImage(Render * /*render*/) {}
bool RE_RenderResult_is_stereo(const RenderResult * /*result*/)
{
return false;
}
bool RE_HasCombinedLayer(const RenderResult * /*result*/)
{
return false;
}
RenderResult *RE_MultilayerConvert(ExrHandle * /*exr_handle*/,
const char * /*colorspace*/,
bool /*predivide*/,
int /*width*/,
int /*height*/)
{
return nullptr;
}
RenderEngineType *RE_engines_find(const char * /*idname*/)
{
return nullptr;
}
bool RE_texture_evaluate(const MTex * /*mtex*/,
const float /*vec*/[3],
int /*thread*/,
ImagePool * /*pool*/,
bool /*skip_load_image*/,
bool /*texnode_preview*/,
float *r_intensity,
float r_rgba[4])
{
if (r_intensity) {
*r_intensity = 0.0f;
}
if (r_rgba) {
r_rgba[0] = r_rgba[1] = r_rgba[2] = 0.0f;
r_rgba[3] = 1.0f;
}
return false;
}
float texture_value_blend(float /*tex*/, float out, float /*fact*/, float /*facg*/, int /*blendtype*/)
{
return out;
}
int multitex_ext(Tex * /*tex*/,
const float /*texvec*/[3],
TexResult *texres,
short /*thread*/,
ImagePool * /*pool*/,
bool /*scene_color_manage*/,
bool /*skip_load_image*/)
{
if (texres) {
texres->tin = 0.0f;
texres->trgba[0] = texres->trgba[1] = texres->trgba[2] = 0.0f;
texres->trgba[3] = 1.0f;
texres->talpha = 0;
}
return 0;
}
int multitex_ext_safe(Tex *tex,
const float texvec[3],
TexResult *texres,
ImagePool *pool,
bool scene_color_manage,
bool skip_load_image)
{
return multitex_ext(tex, texvec, texres, 0, pool, scene_color_manage, skip_load_image);
}
/* --------------------------- Sequencer data --------------------------- */
Editing::Editing() = default;
Editing::~Editing() = default;
bool StripModifierData::is_type_sound() const
{
return false;
}
namespace seq {
void compositor_nodes_update_interface(Scene & /*sequencer_scene*/,
SequencerCompositorModifierData & /*cmd*/)
{
}
SequencerToolSettings *tool_settings_init()
{
return MEM_new<SequencerToolSettings>(__func__);
}
SequencerToolSettings *tool_settings_copy(SequencerToolSettings *settings)
{
if (!settings) {
return nullptr;
}
return MEM_dupalloc(settings);
}
void tool_settings_free(SequencerToolSettings *settings)
{
MEM_delete(settings);
}
Editing *editing_get(const Scene *scene)
{
return scene ? scene->ed : nullptr;
}
void editing_free(Scene *scene, bool /*do_id_user*/)
{
if (scene && scene->ed) {
MEM_delete(scene->ed);
scene->ed = nullptr;
}
}
bool is_valid_strip_channel(const Strip *strip)
{
return strip == nullptr || strip->channel >= 1;
}
void seqbase_duplicate_recursive(Main * /*bmain*/,
const Scene * /*scene_src*/,
Scene * /*scene_dst*/,
ListBaseT<Strip> * /*seqbase_dst*/,
const ListBaseT<Strip> * /*seqbase_src*/,
StripDuplicate /*dupe_flag*/,
int /*copy_flag*/)
{
}
void blend_write(BlendWriter * /*writer*/, ListBaseT<Strip> * /*seqbase*/) {}
void blend_read(BlendDataReader * /*reader*/, ListBaseT<Strip> * /*seqbase*/) {}
void eval_strips(Depsgraph * /*depsgraph*/, Scene * /*scene*/, ListBaseT<Strip> * /*seqbase*/) {}
void foreach_strip(ListBaseT<Strip> *seqbase, ForEachFunc callback, void *user_data)
{
if (!seqbase || !callback) {
return;
}
for (Strip *strip = static_cast<Strip *>(seqbase->first); strip;) {
Strip *next = strip->next;
if (!callback(strip, user_data)) {
break;
}
strip = next;
}
}
void foreach_strip(ListBaseT<Strip> *seqbase, FunctionRef<bool(Strip *)> callback)
{
if (!seqbase) {
return;
}
for (Strip *strip = static_cast<Strip *>(seqbase->first); strip;) {
Strip *next = strip->next;
if (!callback(strip)) {
break;
}
strip = next;
}
}
VectorSet<Strip *> query_all_strips_recursive(const ListBaseT<Strip> *seqbase)
{
VectorSet<Strip *> result;
if (!seqbase) {
return result;
}
for (Strip *strip = static_cast<Strip *>(seqbase->first); strip; strip = strip->next) {
result.add(strip);
result.add_multiple(query_all_strips_recursive(&strip->seqbase));
}
return result;
}
void relations_check_uids_unique_and_report(const Scene * /*scene*/) {}
} // namespace seq
/* ----------------------- Nodes / editor boundaries ------------------- */
namespace nodes {
const std::shared_ptr<const GeometryNodesLazyFunctionGraphInfo> &
ensure_geometry_nodes_lazy_function_graph(const bNodeTree & /*tree*/)
{
static const std::shared_ptr<const GeometryNodesLazyFunctionGraphInfo> empty;
return empty;
}
std::shared_ptr<GeneratedTreeSrnaData> create_geometry_nodes_rna_for_modifier(
const bNodeTree & /*tree*/)
{
return {};
}
std::shared_ptr<GeneratedTreeSrnaData> create_compositor_nodes_rna_for_strip_modifier(
const bNodeTree & /*tree*/)
{
return {};
}
void node_can_sync_cache_clear(Main & /*bmain*/) {}
namespace gizmos {
bool update_tree_gizmo_propagation(bNodeTree & /*tree*/)
{
return false;
}
bool is_builtin_gizmo_node(const bNode & /*node*/)
{
return false;
}
} // namespace gizmos
} // namespace nodes
namespace bke {
bNode *node_get_active_paint_canvas(bNodeTree & /*tree*/)
{
return nullptr;
}
} // namespace bke
void ntreeTexCheckCyclics(bNodeTree * /*tree*/) {}
void ntreeShaderEndExecTree(bNodeTreeExec * /*exec*/) {}
void ntreeTexEndExecTree(bNodeTreeExec * /*exec*/) {}
bke::bNodeTreeType *ntreeType_Shader = nullptr;
void MOD_nodes_update_interface(Object * /*object*/, NodesModifierData * /*modifier*/) {}
} // namespace blender
namespace blender::ed::asset::shelf {
void region_blend_read_data(BlendDataReader * /*reader*/, ARegion * /*region*/) {}
} // namespace blender::ed::asset::shelf
namespace blender::ed::viewer_path {
std::optional<ViewerPathForGeometryNodesViewer> parse_geometry_nodes_viewer(
const ViewerPath & /*viewer_path*/)
{
return std::nullopt;
}
const ComputeContext *compute_context_for_viewer_path_elem(
const ViewerPathElem & /*elem*/,
bke::ComputeContextCache & /*cache*/,
const ComputeContext * /*parent_context*/)
{
return nullptr;
}
} // namespace blender::ed::viewer_path
namespace blender {
void WM_main_add_notifier(unsigned int /*type*/, void * /*reference*/) {}
} // namespace blender

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,402 @@
#pragma once
#include <cstdint>
#include <array>
#include <string>
#include <vector>
struct WebBlendMainState;
struct WebMetaballElementEdit {
int type = 0;
std::array<float, 3> position = {0.0f, 0.0f, 0.0f};
float radius = 1.0f;
std::array<float, 3> scale = {1.0f, 1.0f, 1.0f};
};
struct WebCurveTopologyEdit {
std::vector<int> spline_types;
std::vector<bool> cyclic_u;
std::vector<bool> cyclic_v;
std::vector<int> handle_types;
std::vector<float> handle_points;
};
struct WebCurveSplineEdit {
int type = 0;
int point_count = 0;
int order_u = 0;
bool cyclic_u = false;
};
struct WebSurfaceSplineEdit {
int u = 0;
int v = 0;
int order_u = 0;
int order_v = 0;
bool cyclic_u = false;
bool cyclic_v = false;
};
struct WebFontPropertiesEdit {
std::string alignment;
std::string align_y;
float extrude = -1.0f;
float bevel_depth = -1.0f;
int bevel_resolution = -1;
float offset = -1.0f;
float spacing = -1.0f;
float line_distance = -1.0f;
float word_space = -1.0f;
float shear = -11.0f;
float font_size = -1.0f;
float offset_x = -100001.0f;
float offset_y = -100001.0f;
};
struct WebFontCharacterEdit {
float kern = 0.0f;
int material_index = 0;
int style_flags = 0;
};
struct WebFontTextBoxEdit {
float x = 0.0f;
float y = 0.0f;
float width = 0.0f;
float height = 0.0f;
};
struct WebGreasePencilPointEdit {
std::array<float, 3> position = {0.0f, 0.0f, 0.0f};
float radius = 0.01f;
float opacity = 1.0f;
std::array<float, 4> vertex_color = {0.0f, 0.0f, 0.0f, 0.0f};
};
struct WebGreasePencilStrokeEdit {
bool cyclic = false;
int material_index = 0;
std::vector<WebGreasePencilPointEdit> points;
};
void web_engine_blender_runtime_initialize();
WebBlendMainState *web_engine_blend_main_open(const uint8_t *data,
uint32_t length,
std::string &error);
void web_engine_blend_main_free(WebBlendMainState *state);
bool web_engine_blend_main_set_frame(WebBlendMainState *state, int frame, std::string &error);
bool web_engine_blend_main_set_visibility(WebBlendMainState *state,
const char *object_id,
bool visible,
std::string &error);
bool web_engine_blend_main_set_modifier_enabled(WebBlendMainState *state,
const char *mesh_id,
const char *modifier_uuid,
bool enabled,
std::string &error);
bool web_engine_blend_main_set_modifier_visibility(WebBlendMainState *state,
const char *mesh_id,
const char *modifier_uuid,
bool show_viewport,
bool show_render,
bool show_edit_mode,
bool show_on_cage,
std::string &error);
bool web_engine_blend_main_replace_mesh(WebBlendMainState *state,
const char *mesh_id,
const std::vector<float> &positions,
const std::vector<uint32_t> &indices,
const std::vector<float> &uvs,
const std::vector<float> &colors,
const std::vector<uint32_t> &material_indices,
std::string &error);
bool web_engine_blend_main_create_primitive(WebBlendMainState *state,
const char *primitive,
const char *name,
const std::vector<float> &location,
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,
bool linked,
std::string &new_object_id,
std::string &error);
bool web_engine_blend_main_delete_object(WebBlendMainState *state,
const char *object_id,
std::string &error);
bool web_engine_blend_main_set_transform(WebBlendMainState *state,
const char *object_id,
const std::vector<float> &translation,
const std::vector<float> &rotation_euler,
const std::vector<float> &scale,
std::string &error);
bool web_engine_blend_main_translate_vertices(WebBlendMainState *state,
const char *mesh_id,
const std::vector<uint32_t> &vertex_indices,
const std::vector<float> &offset,
std::string &error);
bool web_engine_blend_main_set_sculpt_attributes(WebBlendMainState *state,
const char *mesh_id,
const std::vector<float> &mask,
const std::vector<uint32_t> &face_sets,
std::string &error);
bool web_engine_blend_main_sculpt_stroke(WebBlendMainState *state,
const char *stroke_json,
std::string &error);
bool web_engine_blend_main_mesh_edit(WebBlendMainState *state,
const char *mesh_id,
const char *operation,
const char *selection_mode,
const std::vector<uint32_t> &element_indices,
const std::vector<float> &offset,
float amount,
int segments,
std::string &error);
bool web_engine_blend_main_create_uv_map(WebBlendMainState *state,
const char *mesh_id,
const char *name,
std::string &error);
bool web_engine_blend_main_set_active_uv_map(WebBlendMainState *state,
const char *mesh_id,
const char *name,
std::string &error);
bool web_engine_blend_main_unwrap_uv(WebBlendMainState *state,
const char *mesh_id,
const std::vector<uint32_t> &face_indices,
const char *method,
std::string &error);
bool web_engine_blend_main_add_material_slot(WebBlendMainState *state,
const char *object_id,
const char *name,
std::string &material_id,
std::string &error);
bool web_engine_blend_main_remove_material_slot(WebBlendMainState *state,
const char *object_id,
int slot_index,
std::string &error);
bool web_engine_blend_main_assign_material_faces(WebBlendMainState *state,
const char *mesh_id,
const std::vector<uint32_t> &face_indices,
int slot_index,
std::string &error);
bool web_engine_blend_main_set_material_principled(WebBlendMainState *state,
const char *material_id,
const std::vector<float> &base_color,
float roughness,
float metallic,
const std::vector<float> &emission_color,
float alpha,
float ior,
float specular_ior_level,
float transmission_weight,
float coat_weight,
float coat_roughness,
float emission_strength,
std::string &error);
bool web_engine_blend_main_import_image(WebBlendMainState *state,
const char *name,
const char *mime_type,
int width,
int height,
const char *base64,
std::string &image_id,
std::string &error);
bool web_engine_blend_main_set_material_image(WebBlendMainState *state,
const char *material_id,
const char *image_id,
const char *usage,
const char *uv_map,
std::string &error);
bool web_engine_blend_main_set_shader_graph(WebBlendMainState *state,
const char *material_id,
const char *graph_json,
std::string &error);
bool web_engine_blend_main_insert_keyframe(WebBlendMainState *state,
const char *object_id,
int frame,
const char *property,
const char *interpolation,
std::string &error);
bool web_engine_blend_main_delete_keyframe(WebBlendMainState *state,
const char *object_id,
int frame,
const char *property,
std::string &error);
bool web_engine_blend_main_set_fcurve_interpolation(WebBlendMainState *state,
const char *animation_id,
const char *path,
const char *interpolation,
std::string &error);
bool web_engine_blend_main_set_active_action(WebBlendMainState *state,
const char *object_id,
const char *action_id,
std::string &error);
bool web_engine_blend_main_set_nla_stack(WebBlendMainState *state,
const char *object_id,
const char *tracks_json,
std::string &error);
bool web_engine_blend_main_set_constraint(WebBlendMainState *state,
const char *object_id,
const char *constraint_name,
bool enabled,
float influence,
std::string &error);
bool web_engine_blend_main_set_parent(WebBlendMainState *state,
const char *object_id,
const char *parent_id,
bool keep_transform,
std::string &error);
bool web_engine_blend_main_create_collection(WebBlendMainState *state,
const char *name,
const char *parent_collection_id,
std::string &collection_id,
std::string &error);
bool web_engine_blend_main_move_to_collection(WebBlendMainState *state,
const char *object_id,
const char *collection_id,
std::string &error);
bool web_engine_blend_main_rename_id(WebBlendMainState *state,
const char *id,
const char *name,
std::string &renamed_id,
std::string &error);
bool web_engine_blend_main_join_objects(WebBlendMainState *state,
const char *active_object_id,
const std::vector<std::string> &object_ids,
std::string &error);
bool web_engine_blend_main_separate_faces(WebBlendMainState *state,
const char *object_id,
const std::vector<uint32_t> &face_indices,
const char *name,
std::string &new_object_id,
std::string &error);
bool web_engine_blend_main_apply_transform(WebBlendMainState *state,
const char *object_id,
std::string &error);
bool web_engine_blend_main_set_origin(WebBlendMainState *state,
const char *object_id,
const char *mode,
std::string &error);
bool web_engine_blend_main_create_curve(WebBlendMainState *state,
const char *curve_type,
const char *name,
const std::vector<float> &control_points,
bool cyclic,
int resolution,
std::string &object_id,
std::string &data_id,
std::string &error);
bool web_engine_blend_main_delete_non_mesh_data(WebBlendMainState *state,
const char *data_id,
std::string &error);
bool web_engine_blend_main_set_curve_control_points(WebBlendMainState *state,
const char *data_id,
const std::vector<float> &control_points,
const std::vector<uint32_t> &spline_offsets,
int resolution,
std::string &error);
bool web_engine_blend_main_set_curve_handle(WebBlendMainState *state,
const char *data_id,
uint32_t point_index,
bool right_handle,
const std::vector<float> &position,
std::string &error);
bool web_engine_blend_main_set_curve_topology(WebBlendMainState *state,
const char *data_id,
const WebCurveTopologyEdit &edit,
std::string &error);
bool web_engine_blend_main_set_curve_splines(WebBlendMainState *state,
const char *data_id,
const std::vector<WebCurveSplineEdit> &splines,
const std::vector<float> &control_points,
const std::vector<float> &point_weights,
const std::vector<int> &handle_types,
const std::vector<float> &handle_points,
std::string &error);
bool web_engine_blend_main_set_surface_topology(WebBlendMainState *state,
const char *data_id,
const std::vector<WebSurfaceSplineEdit> &splines,
const std::vector<float> &control_points,
const std::vector<float> &point_weights,
std::string &error);
bool web_engine_blend_main_set_font_body(WebBlendMainState *state,
const char *data_id,
const char *body,
std::string &error);
bool web_engine_blend_main_set_font_properties(WebBlendMainState *state,
const char *data_id,
const WebFontPropertiesEdit &properties,
std::string &error);
bool web_engine_blend_main_set_font_advanced(WebBlendMainState *state,
const char *data_id,
const std::vector<WebFontCharacterEdit> &characters,
const std::vector<WebFontTextBoxEdit> &text_boxes,
int active_text_box,
std::string &error);
bool web_engine_blend_main_set_font_links(WebBlendMainState *state,
const char *data_id,
const std::array<std::string, 4> &font_ids,
std::string &error);
bool web_engine_blend_main_set_metaball_elements(WebBlendMainState *state,
const char *data_id,
const std::vector<WebMetaballElementEdit> &elements,
std::string &error);
bool web_engine_blend_main_grease_pencils_json(WebBlendMainState *state,
std::string &grease_pencils_json,
std::string &error);
bool web_engine_blend_main_create_grease_pencil_layer(WebBlendMainState *state,
const char *data_id,
const char *name,
std::string &error);
bool web_engine_blend_main_remove_grease_pencil_layer(WebBlendMainState *state,
const char *data_id,
const char *layer_id,
std::string &error);
bool web_engine_blend_main_move_grease_pencil_layer(WebBlendMainState *state,
const char *data_id,
const char *layer_id,
const char *direction,
std::string &error);
bool web_engine_blend_main_insert_grease_pencil_frame(WebBlendMainState *state,
const char *data_id,
const char *layer_id,
int frame,
int duration,
std::string &error);
bool web_engine_blend_main_remove_grease_pencil_frame(WebBlendMainState *state,
const char *data_id,
const char *layer_id,
int frame,
std::string &error);
bool web_engine_blend_main_set_grease_pencil_strokes(WebBlendMainState *state,
const char *data_id,
const char *layer_id,
int frame,
const std::vector<WebGreasePencilStrokeEdit> &strokes,
std::string &error);
bool web_engine_blend_main_set_vertex_colors(WebBlendMainState *state,
const char *mesh_id,
const char *attribute_name,
const char *domain,
const std::vector<uint32_t> &indices,
const std::vector<float> &colors,
std::string &error);
bool web_engine_blend_main_set_vertex_weights(WebBlendMainState *state,
const char *object_id,
const char *vertex_group,
const std::vector<uint32_t> &indices,
const std::vector<float> &values,
bool normalize,
bool mirror,
std::string &error);
bool web_engine_blend_main_set_render_properties(WebBlendMainState *state,
const char *target_id,
const char *properties_json,
std::string &error);
bool web_engine_blend_main_write(WebBlendMainState *state,
std::vector<uint8_t> &bytes,
std::string &error);

View File

@@ -0,0 +1,44 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/**
* Web data-engine GPU boundary.
*
* Blender's datablock code keeps a few GPU cleanup and material inspection
* calls even when the browser build delegates rendering to Three.js. No GPU
* objects are created by this target, so these functions deliberately provide
* the lifetime boundary without pulling a desktop backend into the module.
*/
#include "DNA_listBase.h"
#include "GPU_batch.hh"
#include "GPU_capabilities.hh"
#include "GPU_context.hh"
#include "GPU_material.hh"
#include "GPU_texture.hh"
#include "GPU_vertex_buffer.hh"
namespace blender {
GPUBackendType GPU_backend_get_type()
{
return GPU_BACKEND_NONE;
}
int GPU_max_compute_shader_storage_blocks()
{
return 0;
}
void GPU_batch_discard(gpu::Batch * /*batch*/) {}
void GPU_vertbuf_discard(gpu::VertBuf * /*verts*/) {}
void GPU_texture_free(gpu::Texture * /*texture*/) {}
ListBaseT<GPUMaterialTexture> GPU_material_textures(GPUMaterial * /*material*/)
{
return {};
}
} // namespace blender

View File

@@ -0,0 +1,507 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "RNA_types.hh"
#include <limits>
#include "DNA_ID.h"
#include "DNA_action_types.h"
#include "DNA_object_types.h"
#include "BLI_listbase.h"
#include "BLI_listbase_iterator.hh"
#include "RNA_access.hh"
#include "rna_internal.hh"
namespace blender {
StructRNA *RNA_Struct = nullptr;
StructRNA *RNA_Property = nullptr;
extern StructRNA *RNA_ID;
extern StructRNA *RNA_Action;
extern StructRNA *RNA_Armature;
extern StructRNA *RNA_Constraint;
extern StructRNA *RNA_NlaTrack;
extern StructRNA *RNA_CameraBackgroundImage;
extern StructRNA *RNA_Object;
extern StructRNA *RNA_PoseBone;
extern StructRNA *RNA_Scene;
static StructRNA web_rna_struct;
static StructRNA web_rna_property;
static StructRNA web_rna_id;
static StructRNA web_rna_action;
static StructRNA web_rna_armature;
static StructRNA web_rna_constraint;
static StructRNA web_rna_object;
static StructRNA web_rna_pose_bone;
static StructRNA web_rna_scene;
static FloatPropertyRNA web_object_location;
static FloatPropertyRNA web_object_rotation_euler;
static FloatPropertyRNA web_object_scale;
static FloatPropertyRNA web_pose_rotation_euler;
static BoolPropertyRNA web_object_hide_viewport;
static BoolPropertyRNA web_object_hide_render;
static float *web_object_float_vector(PointerRNA *ptr, const FloatPropertyRNA *prop)
{
Object *object = static_cast<Object *>(ptr->data);
if (prop == &web_object_location) {
return object->loc;
}
if (prop == &web_object_rotation_euler) {
return object->rot;
}
return object->scale;
}
static void web_object_location_get(PointerRNA *ptr, float values[3])
{
memcpy(values, web_object_float_vector(ptr, &web_object_location), sizeof(float) * 3);
}
static void web_object_location_set(PointerRNA *ptr, const float values[3])
{
memcpy(web_object_float_vector(ptr, &web_object_location), values, sizeof(float) * 3);
}
static void web_object_rotation_get(PointerRNA *ptr, float values[3])
{
memcpy(values, web_object_float_vector(ptr, &web_object_rotation_euler), sizeof(float) * 3);
}
static void web_object_rotation_set(PointerRNA *ptr, const float values[3])
{
memcpy(web_object_float_vector(ptr, &web_object_rotation_euler), values, sizeof(float) * 3);
}
static void web_object_scale_get(PointerRNA *ptr, float values[3])
{
memcpy(values, web_object_float_vector(ptr, &web_object_scale), sizeof(float) * 3);
}
static void web_object_scale_set(PointerRNA *ptr, const float values[3])
{
memcpy(web_object_float_vector(ptr, &web_object_scale), values, sizeof(float) * 3);
}
static void web_pose_rotation_get(PointerRNA *ptr, float values[3])
{
memcpy(values, static_cast<bPoseChannel *>(ptr->data)->eul, sizeof(float) * 3);
}
static void web_pose_rotation_set(PointerRNA *ptr, const float values[3])
{
memcpy(static_cast<bPoseChannel *>(ptr->data)->eul, values, sizeof(float) * 3);
}
static bool web_object_hide_viewport_get(PointerRNA *ptr)
{
return (static_cast<Object *>(ptr->data)->visibility_flag & OB_HIDE_VIEWPORT) != 0;
}
static void web_object_hide_viewport_set(PointerRNA *ptr, bool value)
{
Object *object = static_cast<Object *>(ptr->data);
if (value) {
object->visibility_flag |= OB_HIDE_VIEWPORT;
}
else {
object->visibility_flag &= ~OB_HIDE_VIEWPORT;
}
}
static bool web_object_hide_render_get(PointerRNA *ptr)
{
return (static_cast<Object *>(ptr->data)->visibility_flag & OB_HIDE_RENDER) != 0;
}
static void web_object_hide_render_set(PointerRNA *ptr, bool value)
{
Object *object = static_cast<Object *>(ptr->data);
if (value) {
object->visibility_flag |= OB_HIDE_RENDER;
}
else {
object->visibility_flag &= ~OB_HIDE_RENDER;
}
}
static void web_init_float_property(FloatPropertyRNA &prop,
const char *identifier,
PropertySubType subtype,
PropFloatArrayGetFunc getarray,
PropFloatArraySetFunc setarray)
{
prop = {};
prop.magic = RNA_MAGIC;
prop.identifier = identifier;
prop.flag = PROP_ANIMATABLE | PROP_EDITABLE;
prop.type = PROP_FLOAT;
prop.subtype = subtype;
prop.softmin = std::numeric_limits<float>::lowest();
prop.softmax = std::numeric_limits<float>::max();
prop.hardmin = std::numeric_limits<float>::lowest();
prop.hardmax = std::numeric_limits<float>::max();
prop.step = 0.1f;
prop.precision = 3;
prop.arraydimension = 1;
prop.arraylength[0] = 3;
prop.totarraylength = 3;
prop.getarray = getarray;
prop.setarray = setarray;
}
static void web_init_bool_property(BoolPropertyRNA &prop,
const char *identifier,
PropBooleanGetFunc get,
PropBooleanSetFunc set)
{
prop = {};
prop.magic = RNA_MAGIC;
prop.identifier = identifier;
prop.flag = PROP_ANIMATABLE | PROP_EDITABLE;
prop.type = PROP_BOOLEAN;
prop.get = get;
prop.set = set;
}
static void web_append_property(StructRNA &type, PropertyRNA &prop)
{
BLI_addtail(&type.cont.properties, &prop);
}
void web_rna_init()
{
static bool initialized = false;
if (initialized) {
return;
}
initialized = true;
web_rna_struct = {};
web_rna_struct.identifier = "Struct";
web_rna_property = {};
web_rna_property.identifier = "Property";
web_rna_id = {};
web_rna_id.identifier = "ID";
web_rna_id.flag = STRUCT_ID;
web_rna_action = {};
web_rna_action.identifier = "Action";
web_rna_action.flag = STRUCT_ID;
web_rna_armature = {};
web_rna_armature.identifier = "Armature";
web_rna_armature.flag = STRUCT_ID;
web_rna_constraint = {};
web_rna_constraint.identifier = "Constraint";
web_rna_object = {};
web_rna_object.identifier = "Object";
web_rna_object.flag = STRUCT_ID;
web_rna_object.base = &web_rna_id;
web_rna_pose_bone = {};
web_rna_pose_bone.identifier = "PoseBone";
web_rna_scene = {};
web_rna_scene.identifier = "Scene";
web_rna_scene.flag = STRUCT_ID;
web_rna_scene.base = &web_rna_id;
web_init_float_property(
web_object_location, "location", PROP_TRANSLATION, web_object_location_get, web_object_location_set);
web_init_float_property(web_object_rotation_euler,
"rotation_euler",
PROP_EULER,
web_object_rotation_get,
web_object_rotation_set);
web_init_float_property(web_object_scale, "scale", PROP_XYZ, web_object_scale_get, web_object_scale_set);
web_init_float_property(
web_pose_rotation_euler, "rotation_euler", PROP_EULER, web_pose_rotation_get, web_pose_rotation_set);
web_init_bool_property(
web_object_hide_viewport, "hide_viewport", web_object_hide_viewport_get, web_object_hide_viewport_set);
web_init_bool_property(
web_object_hide_render, "hide_render", web_object_hide_render_get, web_object_hide_render_set);
web_append_property(web_rna_object, web_object_location);
web_append_property(web_rna_object, web_object_rotation_euler);
web_append_property(web_rna_object, web_object_scale);
web_append_property(web_rna_object, web_object_hide_viewport);
web_append_property(web_rna_object, web_object_hide_render);
web_append_property(web_rna_pose_bone, web_pose_rotation_euler);
RNA_Struct = &web_rna_struct;
RNA_Property = &web_rna_property;
RNA_ID = &web_rna_id;
RNA_Action = &web_rna_action;
RNA_Armature = &web_rna_armature;
RNA_Constraint = &web_rna_constraint;
RNA_Object = &web_rna_object;
RNA_PoseBone = &web_rna_pose_bone;
RNA_Scene = &web_rna_scene;
}
StructRNA *web_rna_id_type(const short idcode)
{
switch (ID_Type(idcode)) {
case ID_OB:
return &web_rna_object;
case ID_AR:
return &web_rna_armature;
case ID_SCE:
return &web_rna_scene;
case ID_AC:
return &web_rna_action;
default:
return &web_rna_id;
}
}
bool web_rna_path_resolve_property(const PointerRNA *ptr,
const char *path,
PointerRNA *r_ptr,
PropertyRNA **r_prop)
{
if (!ptr || !ptr->data || !path || !r_ptr || !r_prop) {
return false;
}
if (STREQ(path, "location")) {
*r_ptr = *ptr;
*r_prop = &web_object_location;
return true;
}
if (STREQ(path, "rotation_euler")) {
*r_ptr = *ptr;
*r_prop = (ptr->type == &web_rna_pose_bone) ? static_cast<PropertyRNA *>(&web_pose_rotation_euler) : static_cast<PropertyRNA *>(&web_object_rotation_euler);
return true;
}
if (STREQ(path, "scale")) {
*r_ptr = *ptr;
*r_prop = &web_object_scale;
return true;
}
constexpr char prefix[] = "pose.bones[\"";
if (ptr->type != &web_rna_object || !STRPREFIX(path, prefix)) {
return false;
}
const char *name_begin = path + sizeof(prefix) - 1;
const char *name_end = strstr(name_begin, "\"]");
if (!name_end || !STREQ(name_end + 2, ".rotation_euler")) {
return false;
}
const Object *object = static_cast<const Object *>(ptr->data);
if (!object->pose) {
return false;
}
for (bPoseChannel &pchan : object->pose->chanbase) {
if (size_t(name_end - name_begin) == strlen(pchan.name) &&
strncmp(name_begin, pchan.name, size_t(name_end - name_begin)) == 0)
{
*r_ptr = {ptr->owner_id, &web_rna_pose_bone, &pchan};
*r_prop = &web_pose_rotation_euler;
return true;
}
}
return false;
}
/* The Web build intentionally omits rna_rna.cc because its global RNA
* registry pulls editor-only schemas into the data-only engine. Generated
* RNA structs still point at these builtin callbacks, so keep the small
* property iterator implementation here. */
static bool web_rna_property_builtin(CollectionPropertyIterator * /*iter*/, void *data)
{
const PropertyRNA *prop = static_cast<const PropertyRNA *>(data);
return (prop->flag_internal & PROP_INTERN_BUILTIN) != 0;
}
static void web_rna_inheritance_next_level_restart(CollectionPropertyIterator *iter,
PointerRNA *ptr,
IteratorSkipFunc skip)
{
while (!iter->valid && iter->level > 0) {
StructRNA *srna = static_cast<StructRNA *>(iter->parent.data);
iter->level--;
for (int i = iter->level; i > 0; i--) {
srna = srna->base;
}
rna_iterator_listbase_end(iter);
rna_iterator_listbase_begin(iter, ptr, &srna->cont.properties, skip);
}
}
static void web_rna_struct_properties_begin(CollectionPropertyIterator *iter, PointerRNA *ptr)
{
StructRNA *srna = static_cast<StructRNA *>(ptr->data);
while (srna->base) {
iter->level++;
srna = srna->base;
}
rna_iterator_listbase_begin(
iter, ptr, &srna->cont.properties, web_rna_property_builtin);
web_rna_inheritance_next_level_restart(iter, ptr, web_rna_property_builtin);
}
static void web_rna_struct_properties_next(CollectionPropertyIterator *iter)
{
rna_iterator_listbase_next(iter);
web_rna_inheritance_next_level_restart(iter, &iter->parent, web_rna_property_builtin);
}
static PointerRNA web_rna_struct_properties_get(CollectionPropertyIterator *iter)
{
return RNA_pointer_create_discrete(nullptr, RNA_Property, rna_iterator_listbase_get(iter));
}
void rna_builtin_properties_begin(CollectionPropertyIterator *iter, PointerRNA *ptr)
{
PointerRNA newptr;
newptr.type = RNA_Struct;
newptr.data = ptr->type;
newptr.owner_id = (ptr->type->flag & STRUCT_ID) ? static_cast<ID *>(ptr->data) : nullptr;
iter->parent = newptr;
iter->builtin_parent = *ptr;
web_rna_struct_properties_begin(iter, &newptr);
}
void rna_builtin_properties_next(CollectionPropertyIterator *iter)
{
web_rna_struct_properties_next(iter);
}
PointerRNA rna_builtin_properties_get(CollectionPropertyIterator *iter)
{
return web_rna_struct_properties_get(iter);
}
bool rna_builtin_properties_lookup_string(PointerRNA *ptr, const char *key, PointerRNA *r_ptr)
{
for (StructRNA *srna = ptr->type; srna != nullptr; srna = srna->base) {
for (PropertyRNA *prop = static_cast<PropertyRNA *>(srna->cont.properties.first); prop;
prop = prop->next)
{
if (!(prop->flag_internal & PROP_INTERN_BUILTIN) && STREQ(prop->identifier, key)) {
*r_ptr = {nullptr, RNA_Property, prop};
return true;
}
}
}
*r_ptr = {};
return false;
}
PointerRNA rna_builtin_type_get(PointerRNA *ptr)
{
return RNA_pointer_create_discrete(nullptr, RNA_Struct, ptr->type);
}
/* Versioning code refers to editor-only enum tables. Their values are not
* consulted by the model evaluator; retain a valid terminated table so old
* files can be loaded without linking the UI RNA schema. */
#define WEB_RNA_EMPTY_ENUM(name) \
extern const EnumPropertyItem name[] = {{0, nullptr, 0, nullptr, nullptr}}
WEB_RNA_EMPTY_ENUM(rna_enum_asset_access_items);
WEB_RNA_EMPTY_ENUM(rna_enum_boidrule_type_items);
WEB_RNA_EMPTY_ENUM(rna_enum_color_space_convert_default_items);
WEB_RNA_EMPTY_ENUM(rna_enum_dummy_DEFAULT_items);
WEB_RNA_EMPTY_ENUM(rna_enum_dummy_NULL_items);
WEB_RNA_EMPTY_ENUM(rna_enum_fileselect_params_sort_items);
WEB_RNA_EMPTY_ENUM(rna_enum_region_panel_category_items);
WEB_RNA_EMPTY_ENUM(rna_enum_region_type_items);
WEB_RNA_EMPTY_ENUM(rna_enum_space_action_mode_items);
WEB_RNA_EMPTY_ENUM(rna_enum_space_file_browse_mode_items);
WEB_RNA_EMPTY_ENUM(rna_enum_space_graph_mode_items);
WEB_RNA_EMPTY_ENUM(rna_enum_space_image_mode_all_items);
WEB_RNA_EMPTY_ENUM(rna_enum_space_image_mode_items);
WEB_RNA_EMPTY_ENUM(rna_enum_space_sequencer_view_type_items);
WEB_RNA_EMPTY_ENUM(rna_enum_space_type_items);
WEB_RNA_EMPTY_ENUM(rna_enum_strip_modifier_type_items);
WEB_RNA_EMPTY_ENUM(rna_enum_strip_sound_modifier_type_items);
WEB_RNA_EMPTY_ENUM(rna_enum_strip_video_modifier_type_items);
WEB_RNA_EMPTY_ENUM(rna_enum_property_subtype_items);
#undef WEB_RNA_EMPTY_ENUM
/* These type handles belong to excluded UI/media RNA definitions. A null
* handle is intentional: the Web evaluator rejects those data domains before
* asking RNA to resolve properties on them. */
#define WEB_RNA_NULL_TYPE(name) StructRNA *name = nullptr
WEB_RNA_NULL_TYPE(RNA_Annotation);
WEB_RNA_NULL_TYPE(RNA_AnnotationLayer);
WEB_RNA_NULL_TYPE(RNA_Area);
WEB_RNA_NULL_TYPE(RNA_Brush);
WEB_RNA_NULL_TYPE(RNA_CacheFile);
WEB_RNA_NULL_TYPE(RNA_Camera);
WEB_RNA_NULL_TYPE(RNA_Curves);
WEB_RNA_NULL_TYPE(RNA_FreestyleLineStyle);
WEB_RNA_NULL_TYPE(RNA_Light);
WEB_RNA_NULL_TYPE(RNA_LightProbe);
WEB_RNA_NULL_TYPE(RNA_Mask);
WEB_RNA_NULL_TYPE(RNA_MetaBall);
WEB_RNA_NULL_TYPE(RNA_Modifier);
WEB_RNA_NULL_TYPE(RNA_MovieClip);
WEB_RNA_NULL_TYPE(RNA_MovieTrackingStabilization);
WEB_RNA_NULL_TYPE(RNA_MovieTrackingTrack);
WEB_RNA_NULL_TYPE(RNA_NlaStrip);
WEB_RNA_NULL_TYPE(RNA_NlaTrack);
WEB_RNA_NULL_TYPE(RNA_CameraBackgroundImage);
WEB_RNA_NULL_TYPE(RNA_Node);
WEB_RNA_NULL_TYPE(RNA_NodeSocket);
WEB_RNA_NULL_TYPE(RNA_NodeSocketStandard);
WEB_RNA_NULL_TYPE(RNA_NodeTree);
WEB_RNA_NULL_TYPE(RNA_NodesModifier);
WEB_RNA_NULL_TYPE(RNA_PaintCurve);
WEB_RNA_NULL_TYPE(RNA_Palette);
WEB_RNA_NULL_TYPE(RNA_ParticleSettings);
WEB_RNA_NULL_TYPE(RNA_PointCloud);
WEB_RNA_NULL_TYPE(RNA_PrimitiveString);
WEB_RNA_NULL_TYPE(RNA_Region);
WEB_RNA_NULL_TYPE(RNA_CurveModifier);
WEB_RNA_NULL_TYPE(RNA_ArmatureModifier);
WEB_RNA_NULL_TYPE(RNA_ArrayModifier);
WEB_RNA_NULL_TYPE(RNA_BevelModifier);
WEB_RNA_NULL_TYPE(RNA_BooleanModifier);
WEB_RNA_NULL_TYPE(RNA_BuildModifier);
WEB_RNA_NULL_TYPE(RNA_DecimateModifier);
WEB_RNA_NULL_TYPE(RNA_LatticeModifier);
WEB_RNA_NULL_TYPE(RNA_MirrorModifier);
WEB_RNA_NULL_TYPE(RNA_HookModifier);
WEB_RNA_NULL_TYPE(RNA_MeshDeformModifier);
WEB_RNA_NULL_TYPE(RNA_ShrinkwrapModifier);
WEB_RNA_NULL_TYPE(RNA_SimpleDeformModifier);
WEB_RNA_NULL_TYPE(RNA_SolidifyModifier);
WEB_RNA_NULL_TYPE(RNA_SubsurfModifier);
WEB_RNA_NULL_TYPE(RNA_SurfaceDeformModifier);
WEB_RNA_NULL_TYPE(RNA_TriangulateModifier);
WEB_RNA_NULL_TYPE(RNA_WaveModifier);
WEB_RNA_NULL_TYPE(RNA_WeldModifier);
WEB_RNA_NULL_TYPE(RNA_Screen);
WEB_RNA_NULL_TYPE(RNA_ShaderFx);
WEB_RNA_NULL_TYPE(RNA_ShaderNode);
WEB_RNA_NULL_TYPE(RNA_Sound);
WEB_RNA_NULL_TYPE(RNA_Speaker);
WEB_RNA_NULL_TYPE(RNA_Strip);
WEB_RNA_NULL_TYPE(RNA_SequencerCompositorModifierProperties);
WEB_RNA_NULL_TYPE(RNA_Text);
WEB_RNA_NULL_TYPE(RNA_Texture);
WEB_RNA_NULL_TYPE(RNA_VectorFont);
WEB_RNA_NULL_TYPE(RNA_Volume);
WEB_RNA_NULL_TYPE(RNA_WindowManager);
WEB_RNA_NULL_TYPE(RNA_WorkSpace);
#undef WEB_RNA_NULL_TYPE
void rna_property_override_diff_default(Main * /*bmain*/,
RNAPropertyOverrideDiffContext & /*rnadiff_ctx*/)
{
}
} // namespace blender