Advance WebGPU volume and bounded workflows

This commit is contained in:
mes123456
2026-08-14 18:08:29 -04:00
parent 3da1dfc804
commit 68d50f810f
119 changed files with 9028 additions and 430 deletions

View File

@@ -1543,7 +1543,7 @@ EMSCRIPTEN_KEEPALIVE int web_engine_apply_command(const int handle,
type == "moveObjectToCollection" || type == "renameId" || type == "joinObjects" ||
type == "separateMeshFaces" || type == "applyObjectTransform" || type == "setObjectOrigin" ||
type == "setCurveControlPoints" || type == "setCurveHandle" || type == "setCurveTopology" || type == "setCurveSplines" || type == "setSurfaceTopology" || type == "setFontBody" ||
type == "setFontProperties" || type == "setFontAdvanced" || type == "setFontLinks" || type == "deleteNonMeshData" ||
type == "setFontProperties" || type == "setFontAdvanced" || type == "setFontLinks" || type == "setVolumeProperties" || type == "deleteNonMeshData" ||
type == "setMetaballElements" || type == "createGreasePencilLayer" ||
type == "removeGreasePencilLayer" || type == "moveGreasePencilLayer" ||
type == "insertGreasePencilFrame" || type == "removeGreasePencilFrame" ||
@@ -1934,6 +1934,20 @@ EMSCRIPTEN_KEEPALIVE int web_engine_apply_command(const int handle,
applied = main_error.empty() && web_engine_blend_main_set_font_links(
engine->authoritative_main, command.value("dataId", "").c_str(), font_ids, main_error);
}
else if (type == "setVolumeProperties") {
WebVolumePropertiesEdit properties;
properties.source_path = command.value("sourcePath", "");
properties.display_density = command.value("displayDensity", 1.0f);
properties.interpolation = command.value("interpolation", "LINEAR");
properties.step_size = command.value("stepSize", 0.0f);
properties.velocity_grid = command.value("velocityGrid", "");
properties.velocity_scale = command.value("velocityScale", 1.0f);
applied = web_engine_blend_main_set_volume_properties(
engine->authoritative_main,
command.value("dataId", "").c_str(),
properties,
main_error);
}
else if (type == "setMetaballElements") {
std::vector<WebMetaballElementEdit> elements;
const json source_elements = command.value("elements", json::array());

View File

@@ -735,6 +735,28 @@ std::vector<float> node_socket_default_values(const ParsedBlend &blend, const El
return read_float_array(*blend.sdna, *default_value, "value", 4);
}
std::optional<ElementRef> node_socket_by_identifier(const ParsedBlend &blend,
const ElementRef &node,
const std::string &identifier)
{
for (const ElementRef &socket : linked_list_elements(blend, node, "inputs")) {
std::string socket_identifier = read_string(*blend.sdna, socket, "identifier");
if (socket_identifier.empty()) socket_identifier = read_string(*blend.sdna, socket, "name");
if (socket_identifier == identifier) return socket;
}
return std::nullopt;
}
std::optional<ElementRef> node_socket_default_element(const ParsedBlend &blend,
const ElementRef &node,
const std::string &identifier)
{
const std::optional<ElementRef> socket = node_socket_by_identifier(blend, node, identifier);
if (!socket) return std::nullopt;
const std::optional<uint64_t> pointer = read_pointer(*blend.sdna, *socket, "default_value");
return pointer && *pointer != 0 ? element_for_pointer(blend, *pointer) : std::nullopt;
}
std::optional<ElementRef> raw_array_element(const ParsedBlend &blend,
uint64_t pointer,
const std::string &type_name,
@@ -813,6 +835,36 @@ std::optional<json> compositor_graph_from_scene(const ParsedBlend &blend,
node_ir["properties"] = {{"color", {values[0], values[1], values[2], values[3]}}};
}
}
else if (blender_type == "CompositorNodeExposure") {
const std::optional<ElementRef> exposure = node_socket_default_element(
blend, node, "Exposure");
const std::optional<float> value = exposure ? read_float(*blend.sdna, *exposure, "value") :
std::nullopt;
if (value && std::isfinite(*value) && *value >= -20.0f && *value <= 20.0f) {
node_ir["type"] = "EXPOSURE";
node_ir.erase("blenderType");
node_ir["properties"] = {{"exposure", *value}};
}
}
else if (blender_type == "CompositorNodeInvert") {
const std::optional<ElementRef> factor = node_socket_default_element(blend, node, "Fac");
const std::optional<ElementRef> invert_color = node_socket_default_element(
blend, node, "Invert Color");
const std::optional<ElementRef> invert_alpha = node_socket_default_element(
blend, node, "Invert Alpha");
const std::optional<float> factor_value = factor ? read_float(*blend.sdna, *factor, "value") :
std::nullopt;
const std::optional<int64_t> color_value = invert_color ?
read_integer(*blend.sdna, *invert_color, "value") : std::nullopt;
const std::optional<int64_t> alpha_value = invert_alpha ?
read_integer(*blend.sdna, *invert_alpha, "value") : std::nullopt;
if (factor_value && std::abs(*factor_value - 1.0f) <= 1e-6f && color_value == 1 &&
alpha_value == 0)
{
node_ir["type"] = "INVERT";
node_ir.erase("blenderType");
}
}
nodes.push_back(std::move(node_ir));
}
if (output_node_id.empty()) output_node_id = fallback_output_node_id;
@@ -835,6 +887,18 @@ std::optional<json> compositor_graph_from_scene(const ParsedBlend &blend,
std::string to_socket_name = read_string(*blend.sdna, *to_socket, "identifier");
if (from_socket_name.empty()) from_socket_name = read_string(*blend.sdna, *from_socket, "name");
if (to_socket_name.empty()) to_socket_name = read_string(*blend.sdna, *to_socket, "name");
const std::optional<ElementRef> to_node = element_for_pointer(blend, *to_node_pointer);
const std::string to_blender_type = to_node ? read_string(*blend.sdna, *to_node, "idname") :
std::string();
if (to_blender_type == "CompositorNodeInvert" && to_socket_name == "Color") {
to_socket_name = "Image";
}
else if ((to_blender_type == "NodeGroupOutput" ||
to_blender_type == "CompositorNodeComposite") &&
read_string(*blend.sdna, *to_socket, "name") == "Image")
{
to_socket_name = "Image";
}
if (from_socket_name.empty() || to_socket_name.empty() || from_socket_name.size() > 256 ||
to_socket_name.size() > 256)
{
@@ -2414,6 +2478,22 @@ json non_mesh_data_from_record(const ParsedBlend &blend,
else if (record.type_name == "Volume") {
const std::string path = read_string(*blend.sdna, element, "filepath");
data["resourceKind"] = "OPENVDB";
json properties = {
{"displayDensity", 1.0f},
{"interpolation", "LINEAR"},
{"stepSize", 0.0f},
{"velocityGrid", read_string(*blend.sdna, element, "velocity_grid")},
{"velocityScale", read_float(*blend.sdna, element, "velocity_scale").value_or(1.0f)},
};
if (const std::optional<ElementRef> display = embedded_element(*blend.sdna, element, "display")) {
properties["displayDensity"] = read_float(*blend.sdna, *display, "density").value_or(1.0f);
const int64_t interpolation = read_integer(*blend.sdna, *display, "interpolation_method").value_or(0);
properties["interpolation"] = interpolation == 2 ? "NEAREST" : "LINEAR";
}
if (const std::optional<ElementRef> render = embedded_element(*blend.sdna, element, "render")) {
properties["stepSize"] = read_float(*blend.sdna, *render, "step_size").value_or(0.0f);
}
data["volumeProperties"] = std::move(properties);
const std::optional<uint64_t> packed_file = read_pointer(*blend.sdna, element, "packedfile");
const std::vector<uint8_t> packed_bytes = packed_file ?
packed_file_bytes(blend, *packed_file) :
@@ -3079,7 +3159,9 @@ json scene_ir_from_blend(const ParsedBlend &blend,
mesh_bind_matrices[data_id->second] = matrix;
json groups = json::array();
int index = 0;
for (const ElementRef &group : linked_list_elements(blend, object, "defbase")) {
std::vector<ElementRef> source_groups = linked_list_elements(blend, *data, "vertex_group_names");
if (source_groups.empty()) source_groups = linked_list_elements(blend, object, "defbase");
for (const ElementRef &group : source_groups) {
groups.push_back({{"name", read_string(*blend.sdna, group, "name")}, {"index", index++}});
}
if (!groups.empty()) vertex_groups_by_mesh_id[data_id->second] = std::move(groups);

View File

@@ -37,6 +37,7 @@
#include "BKE_object.hh"
#include "BKE_object_deform.h"
#include "BKE_scene.hh"
#include "BKE_volume.hh"
#include "BKE_fcurve.hh"
#include "BKE_image.hh"
@@ -69,6 +70,7 @@
#include "DNA_world_types.h"
#include "DNA_userdef_enums.h"
#include "DNA_vfont_types.h"
#include "DNA_volume_types.h"
#include "BLI_listbase_iterator.hh"
#include "BLI_listbase.h"
@@ -187,6 +189,18 @@ MetaBall *find_metaball(Main *main, const char *data_id)
return nullptr;
}
Volume *find_volume(Main *main, const char *data_id)
{
if (main == nullptr || data_id == nullptr || strncmp(data_id, "volume:", 7) != 0) {
return nullptr;
}
const std::string name(data_id + 7);
for (Volume &volume : main->volumes) {
if (id_name(volume.id) == name) return &volume;
}
return nullptr;
}
Light *find_light(Main *main, const char *data_id)
{
if (main == nullptr || data_id == nullptr || strncmp(data_id, "light:", 6) != 0) return nullptr;
@@ -3471,6 +3485,54 @@ bool web_engine_blend_main_set_font_links(WebBlendMainState *state,
return true;
}
bool web_engine_blend_main_set_volume_properties(WebBlendMainState *state,
const char *data_id,
const WebVolumePropertiesEdit &properties,
std::string &error)
{
Volume *volume = find_volume(state != nullptr ? state->main : nullptr, data_id);
if (volume == nullptr ||
!ensure_single_user_data(state != nullptr ? state->main : nullptr, &volume->id, error))
{
if (error.empty()) error = "NON_MESH_DATA_UNSUPPORTED: Volume data-block was not found";
return false;
}
const std::string &path = properties.source_path;
const bool project_relative = path.size() >= 7 && path.rfind("//", 0) == 0 &&
path.compare(path.size() - 4, 4, ".vdb") == 0 &&
path.find('\\') == std::string::npos &&
path.find("/../") == std::string::npos &&
path.compare(path.size() - 3, 3, "/..") != 0 &&
path.size() < sizeof(volume->filepath);
if (!project_relative || !std::isfinite(properties.display_density) ||
properties.display_density < 0.0f || properties.display_density > 1000000.0f ||
(properties.interpolation != "NEAREST" && properties.interpolation != "LINEAR") ||
!std::isfinite(properties.step_size) || properties.step_size < 0.0f ||
properties.step_size > 1000000.0f || properties.velocity_grid.size() >= sizeof(volume->velocity_grid) ||
!std::isfinite(properties.velocity_scale) || properties.velocity_scale < -1000000.0f ||
properties.velocity_scale > 1000000.0f)
{
error = "NON_MESH_PROPERTY_INVALID: Volume source and properties are outside the bounded project-relative range";
return false;
}
BKE_volume_unload(volume);
BLI_strncpy(volume->filepath, path.c_str(), sizeof(volume->filepath));
volume->display.density = properties.display_density;
volume->display.interpolation_method = properties.interpolation == "NEAREST" ?
VOLUME_DISPLAY_INTERP_CLOSEST :
VOLUME_DISPLAY_INTERP_LINEAR;
volume->render.step_size = properties.step_size;
BLI_strncpy(volume->velocity_grid,
properties.velocity_grid.c_str(),
sizeof(volume->velocity_grid));
volume->velocity_scale = properties.velocity_scale;
volume->id.recalc |= ID_RECALC_GEOMETRY;
for (Object &object : state->main->objects) {
if (object.data == &volume->id) object.id.recalc |= ID_RECALC_GEOMETRY;
}
return true;
}
bool web_engine_blend_main_set_metaball_elements(WebBlendMainState *state,
const char *data_id,
const std::vector<WebMetaballElementEdit> &elements,

View File

@@ -67,6 +67,15 @@ struct WebFontTextBoxEdit {
float height = 0.0f;
};
struct WebVolumePropertiesEdit {
std::string source_path;
float display_density = 1.0f;
std::string interpolation;
float step_size = 0.0f;
std::string velocity_grid;
float velocity_scale = 1.0f;
};
struct WebGreasePencilPointEdit {
std::array<float, 3> position = {0.0f, 0.0f, 0.0f};
float radius = 0.01f;
@@ -341,6 +350,10 @@ 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_volume_properties(WebBlendMainState *state,
const char *data_id,
const WebVolumePropertiesEdit &properties,
std::string &error);
bool web_engine_blend_main_set_metaball_elements(WebBlendMainState *state,
const char *data_id,
const std::vector<WebMetaballElementEdit> &elements,