Advance M8-M11 parity workflows
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled

This commit is contained in:
mes123456
2026-08-17 04:37:07 -04:00
parent 7c16b279ae
commit 0fe8d2bb56
324 changed files with 31920 additions and 863 deletions

View File

@@ -5,12 +5,17 @@
/** \file
* \ingroup modifiers
*
* Native Web evaluator for the first bounded Geometry Nodes closure. It
* accepts a single Group Input -> Transform Geometry -> Group Output graph.
* Unsupported graphs remain visible and report an explicit modifier error.
* Native Web evaluator for the bounded Geometry Nodes allowlist. Unsupported
* graphs remain visible and report an explicit modifier error.
*/
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <optional>
#include <string>
#include <variant>
#include "MEM_guardedalloc.h"
@@ -18,19 +23,32 @@
#include "BLI_math_rotation.hh"
#include "BLI_listbase.h"
#include "BLI_string.h"
#include "BLI_vector.hh"
#include "BKE_attribute.hh"
#include "BKE_geometry_set.hh"
#include "BKE_instances.hh"
#include "BKE_lib_query.hh"
#include "BKE_mesh.hh"
#include "BKE_modifier.hh"
#include "BKE_node.hh"
#include "BKE_node_legacy_types.hh"
#include "BKE_object.hh"
#include "BLO_read_write.hh"
#include "DEG_depsgraph_build.hh"
#include "DEG_depsgraph_query.hh"
#include "DNA_collection_types.h"
#include "DNA_image_types.h"
#include "DNA_modifier_types.h"
#include "DNA_node_types.h"
#include "DNA_object_types.h"
#include "GEO_join_geometries.hh"
#include "GEO_realize_instances.hh"
#include "GEO_transform.hh"
#include "RNA_prototypes.hh"
#include "UI_resources.hh"
@@ -89,8 +107,39 @@ static void foreach_ID_link(ModifierData *md, Object *object, IDWalkFunc walk, v
static void update_depsgraph(ModifierData *md, const ModifierUpdateDepsgraphContext *ctx)
{
NodesModifierData *nmd = reinterpret_cast<NodesModifierData *>(md);
if (nmd->node_group != nullptr) {
DEG_add_node_tree_output_relation(ctx->node, nmd->node_group, "Web Geometry Nodes Modifier");
if (nmd->node_group == nullptr) {
return;
}
DEG_add_node_tree_output_relation(ctx->node, nmd->node_group, "Web Geometry Nodes Modifier");
for (const bNode &node : nmd->node_group->nodes) {
for (const bNodeSocket &socket : node.inputs) {
if (socket.default_value == nullptr) {
continue;
}
if (socket.type == SOCK_OBJECT) {
Object *object = static_cast<const bNodeSocketValueObject *>(socket.default_value)->value;
if (object != nullptr && object != ctx->object) {
DEG_add_object_relation(
ctx->node, object, DEG_OB_COMP_TRANSFORM, "Web Geometry Nodes Object Info");
DEG_add_object_relation(
ctx->node, object, DEG_OB_COMP_GEOMETRY, "Web Geometry Nodes Object Info");
}
}
else if (socket.type == SOCK_COLLECTION) {
Collection *collection =
static_cast<const bNodeSocketValueCollection *>(socket.default_value)->value;
if (collection != nullptr) {
DEG_add_collection_geometry_relation(
ctx->node, collection, "Web Geometry Nodes Collection Info");
}
}
else if (socket.type == SOCK_IMAGE) {
Image *image = static_cast<const bNodeSocketValueImage *>(socket.default_value)->value;
if (image != nullptr) {
DEG_add_generic_id_relation(ctx->node, &image->id, "Web Geometry Nodes Image Info");
}
}
}
}
}
@@ -102,7 +151,7 @@ static bool is_disabled(const Scene *, ModifierData *md, bool)
static const bNodeSocket *find_input(const bNode &node, const char *identifier)
{
for (const bNodeSocket &socket : node.inputs) {
if (STREQ(socket.identifier, identifier)) {
if (STREQ(socket.identifier, identifier) || STREQ(socket.name, identifier)) {
return &socket;
}
}
@@ -119,24 +168,12 @@ static const bNodeSocket *find_output(const bNode &node, const char *identifier)
return nullptr;
}
static bool has_link(const bNodeTree &tree,
const bNode &from_node,
const char *from_socket,
const bNode &to_node,
const char *to_socket)
static bool node_is(const bNode &node, const char *idname)
{
const auto matches_socket = [](const bNodeSocket &socket, const char *name) {
return STREQ(socket.identifier, name) || STREQ(socket.name, name);
};
for (const bNodeLink &link : tree.links) {
if (link.fromnode == &from_node && link.tonode == &to_node && link.fromsock != nullptr &&
link.tosock != nullptr && matches_socket(*link.fromsock, from_socket) &&
matches_socket(*link.tosock, to_socket))
{
return true;
}
if (STREQ(node.idname, idname)) {
return true;
}
return false;
return std::string(node.idname) == "Undefined[" + std::string(idname) + "]";
}
static bool has_input_link(const bNodeTree &tree, const bNode &node, const char *socket_name)
@@ -151,6 +188,659 @@ static bool has_input_link(const bNodeTree &tree, const bNode &node, const char
return false;
}
using WebNodeValue = std::variant<std::monostate,
bke::GeometrySet,
bool,
int,
float,
float3,
std::string,
Object *,
Collection *,
Image *>;
class WebGeometryNodeEvaluator {
public:
static constexpr int max_evaluations = 4096;
static constexpr int max_point_elements = 1'000'000;
static constexpr int max_edge_elements = 2'000'000;
static constexpr int max_face_elements = 2'000'000;
static constexpr int max_corner_elements = 4'000'000;
static constexpr int max_instance_elements = 100'000;
static constexpr int64_t max_field_bytes = 64 * 1024 * 1024;
private:
const bNodeTree &tree_;
const ModifierEvalContext &ctx_;
const Mesh &input_mesh_;
std::string error_;
int evaluation_count_ = 0;
bool mesh_within_domain_budget(const Mesh &mesh)
{
if (mesh.verts_num > max_point_elements || mesh.edges_num > max_edge_elements ||
mesh.faces_num > max_face_elements || mesh.corners_num > max_corner_elements)
{
error_ = "field/domain element budget exceeded";
return false;
}
return true;
}
bool geometry_within_domain_budget(const bke::GeometrySet &geometry)
{
if (const Mesh *mesh = geometry.get_mesh()) {
if (!mesh_within_domain_budget(*mesh)) {
return false;
}
}
if (const bke::Instances *instances = geometry.get_instances()) {
if (instances->instances_num() > max_instance_elements) {
error_ = "instance domain element budget exceeded";
return false;
}
}
return true;
}
WebNodeValue bounded_geometry(bke::GeometrySet geometry)
{
if (!geometry_within_domain_budget(geometry)) {
return {};
}
return std::move(geometry);
}
const bNode *owner_of(const bNodeSocket &socket) const
{
for (const bNode &node : tree_.nodes) {
for (const bNodeSocket &candidate : node.inputs) {
if (&candidate == &socket) {
return &node;
}
}
for (const bNodeSocket &candidate : node.outputs) {
if (&candidate == &socket) {
return &node;
}
}
}
return nullptr;
}
Vector<const bNodeLink *> input_links(const bNodeSocket &socket) const
{
Vector<const bNodeLink *> links;
for (const bNodeLink &link : tree_.links) {
if (link.tosock == &socket && link.fromnode != nullptr && link.fromsock != nullptr) {
links.append(&link);
}
}
std::ranges::sort(links, [](const bNodeLink *a, const bNodeLink *b) {
return a->multi_input_sort_id > b->multi_input_sort_id;
});
return links;
}
WebNodeValue default_value(const bNodeSocket &socket)
{
if (socket.default_value == nullptr) {
return {};
}
switch (socket.type) {
case SOCK_BOOLEAN:
return static_cast<const bNodeSocketValueBoolean *>(socket.default_value)->value != 0;
case SOCK_INT:
return static_cast<const bNodeSocketValueInt *>(socket.default_value)->value;
case SOCK_FLOAT:
return static_cast<const bNodeSocketValueFloat *>(socket.default_value)->value;
case SOCK_VECTOR:
return float3(static_cast<const bNodeSocketValueVector *>(socket.default_value)->value);
case SOCK_STRING:
return std::string(
static_cast<const bNodeSocketValueString *>(socket.default_value)->value);
case SOCK_OBJECT:
return static_cast<const bNodeSocketValueObject *>(socket.default_value)->value;
case SOCK_COLLECTION:
return static_cast<const bNodeSocketValueCollection *>(socket.default_value)->value;
case SOCK_IMAGE:
return static_cast<const bNodeSocketValueImage *>(socket.default_value)->value;
default:
return {};
}
}
std::optional<float> number(const WebNodeValue &value) const
{
if (const int *integer = std::get_if<int>(&value)) {
return float(*integer);
}
if (const float *scalar = std::get_if<float>(&value)) {
return *scalar;
}
return std::nullopt;
}
bke::GeometrySet copy_geometry(const bke::GeometrySet &geometry) const
{
if (const Mesh *mesh = geometry.get_mesh()) {
return bke::GeometrySet::from_mesh(BKE_mesh_copy_for_eval(*mesh));
}
bke::GeometrySet copy = geometry;
copy.ensure_owns_all_data();
return copy;
}
WebNodeValue evaluate_input(const bNodeSocket &socket)
{
const Vector<const bNodeLink *> links = input_links(socket);
if (links.size() == 1) {
return evaluate_output(*links.first()->fromsock);
}
if (links.size() > 1) {
error_ = "multiple links on a non-multi-input socket";
return {};
}
return default_value(socket);
}
std::optional<bke::GeometrySet> geometry_input(const bNode &node, const char *name)
{
const bNodeSocket *socket = find_input(node, name);
if (socket == nullptr) {
error_ = std::string("missing geometry input: ") + name;
return std::nullopt;
}
WebNodeValue value = evaluate_input(*socket);
if (bke::GeometrySet *geometry = std::get_if<bke::GeometrySet>(&value)) {
return std::move(*geometry);
}
error_ = std::string("geometry input did not evaluate to geometry: ") + name;
return std::nullopt;
}
WebNodeValue evaluate_compare(const bNode &node)
{
const NodeFunctionCompare *storage = static_cast<const NodeFunctionCompare *>(node.storage);
const bNodeSocket *a_socket = find_input(node, "A");
const bNodeSocket *b_socket = find_input(node, "B");
if (storage == nullptr || a_socket == nullptr || b_socket == nullptr ||
!ELEM(storage->data_type, SOCK_INT, SOCK_FLOAT))
{
error_ = "Compare supports bounded Int/Float scalar inputs only";
return {};
}
const std::optional<float> a = number(evaluate_input(*a_socket));
const std::optional<float> b = number(evaluate_input(*b_socket));
if (!a || !b) {
error_ = "Compare scalar input is unavailable";
return {};
}
switch (storage->operation) {
case NODE_COMPARE_LESS_THAN:
return *a < *b;
case NODE_COMPARE_LESS_EQUAL:
return *a <= *b;
case NODE_COMPARE_GREATER_THAN:
return *a > *b;
case NODE_COMPARE_GREATER_EQUAL:
return *a >= *b;
case NODE_COMPARE_EQUAL:
return *a == *b;
case NODE_COMPARE_NOT_EQUAL:
return *a != *b;
default:
error_ = "Compare operation is outside the bounded evaluator";
return {};
}
}
WebNodeValue evaluate_math(const bNode &node)
{
const bNodeSocket *a_socket = find_input(node, "Value");
const bNodeSocket *b_socket = nullptr;
for (const bNodeSocket &socket : node.inputs) {
if (STREQ(socket.identifier, "Value_001")) {
b_socket = &socket;
}
}
if (a_socket == nullptr || b_socket == nullptr) {
error_ = "Math sockets are incomplete";
return {};
}
const std::optional<float> a = number(evaluate_input(*a_socket));
const std::optional<float> b = number(evaluate_input(*b_socket));
if (!a || !b) {
error_ = "Math scalar input is unavailable";
return {};
}
switch (node.custom1) {
case NODE_MATH_ADD:
return *a + *b;
case NODE_MATH_SUBTRACT:
return *a - *b;
case NODE_MATH_MULTIPLY:
return *a * *b;
case NODE_MATH_DIVIDE:
return *b == 0.0f ? 0.0f : *a / *b;
case NODE_MATH_MINIMUM:
return std::min(*a, *b);
case NODE_MATH_MAXIMUM:
return std::max(*a, *b);
default:
error_ = "Math operation is outside the bounded evaluator";
return {};
}
}
WebNodeValue evaluate_collection_info(const bNode &node)
{
const bNodeSocket *collection_socket = find_input(node, "Collection");
const bNodeSocket *separate_socket = find_input(node, "Separate Children");
const bNodeSocket *reset_socket = find_input(node, "Reset Children");
if (collection_socket == nullptr || separate_socket == nullptr || reset_socket == nullptr) {
error_ = "Collection Info sockets are incomplete";
return {};
}
const WebNodeValue collection_value = evaluate_input(*collection_socket);
const WebNodeValue separate_value = evaluate_input(*separate_socket);
const WebNodeValue reset_value = evaluate_input(*reset_socket);
Collection *const *collection = std::get_if<Collection *>(&collection_value);
const bool *separate_children = std::get_if<bool>(&separate_value);
const bool *reset_children = std::get_if<bool>(&reset_value);
if (collection == nullptr || *collection == nullptr || separate_children == nullptr ||
reset_children == nullptr || !*separate_children)
{
error_ = "Collection Info requires a concrete collection with Separate Children enabled";
return {};
}
Vector<Object *> objects;
for (const CollectionObject &entry : (*collection)->gobject) {
if (entry.ob != nullptr && entry.ob != ctx_.object) {
objects.append(entry.ob);
}
}
if (objects.size() > max_instance_elements) {
error_ = "Collection Info instance domain budget exceeded";
return {};
}
auto instances = std::make_unique<bke::Instances>();
instances->resize(objects.size());
MutableSpan<int> handles = instances->reference_handles_for_write();
MutableSpan<float4x4> transforms = instances->transforms_for_write();
for (const int index : objects.index_range()) {
Object *evaluated = reinterpret_cast<Object *>(
DEG_get_evaluated_id(ctx_.depsgraph, &objects[index]->id));
if (evaluated == nullptr) {
error_ = "Collection Info object is not available in the evaluated dependency graph";
return {};
}
handles[index] = instances->add_reference(*evaluated);
transforms[index] = *reset_children ? float4x4::identity() : evaluated->object_to_world();
}
instances->tag_reference_handles_changed();
return bounded_geometry(bke::GeometrySet::from_instances(std::move(instances)));
}
WebNodeValue evaluate_output(const bNodeSocket &socket)
{
if (++evaluation_count_ > max_evaluations) {
error_ = "evaluation budget exceeded";
return {};
}
const bNode *node = owner_of(socket);
if (node == nullptr) {
error_ = "output socket owner is missing";
return {};
}
if (node_is(*node, "NodeGroupInput")) {
if (socket.type != SOCK_GEOMETRY) {
error_ = "only the Geometry group input is supported";
return {};
}
if (!mesh_within_domain_budget(input_mesh_)) {
return {};
}
return bounded_geometry(
bke::GeometrySet::from_mesh(BKE_mesh_copy_for_eval(input_mesh_)));
}
if (node_is(*node, "FunctionNodeInputInt")) {
const NodeInputInt *storage = static_cast<const NodeInputInt *>(node->storage);
return storage == nullptr ? WebNodeValue{} : WebNodeValue{storage->integer};
}
if (node_is(*node, "FunctionNodeInputVector")) {
const NodeInputVector *storage = static_cast<const NodeInputVector *>(node->storage);
if (storage == nullptr || storage->dimensions != 3) {
error_ = "Vector input requires exactly three dimensions";
return {};
}
return float3(storage->vector);
}
if (node_is(*node, "ShaderNodeValue")) {
return default_value(socket);
}
if (node_is(*node, "ShaderNodeMath")) {
return evaluate_math(*node);
}
if (node_is(*node, "FunctionNodeCompare")) {
return evaluate_compare(*node);
}
if (node_is(*node, "GeometryNodeImageInfo")) {
const bNodeSocket *image_socket = find_input(*node, "Image");
if (image_socket == nullptr) {
error_ = "Image Info image input is missing";
return {};
}
const WebNodeValue image_value = evaluate_input(*image_socket);
Image *const *image = std::get_if<Image *>(&image_value);
if (image == nullptr || *image == nullptr) {
error_ = "Image Info image is unavailable";
return {};
}
if (STREQ(socket.identifier, "Width")) {
return (*image)->gen_x;
}
if (STREQ(socket.identifier, "Height")) {
return (*image)->gen_y;
}
if (STREQ(socket.identifier, "Has Alpha")) {
return true;
}
if (STREQ(socket.identifier, "Frame Count")) {
return 1;
}
if (STREQ(socket.identifier, "FPS")) {
return 0.0f;
}
error_ = "Image Info output is unsupported";
return {};
}
if (node_is(*node, "GeometryNodeObjectInfo")) {
const bNodeSocket *object_socket = find_input(*node, "Object");
if (object_socket == nullptr) {
error_ = "Object Info object input is missing";
return {};
}
const WebNodeValue object_value = evaluate_input(*object_socket);
Object *const *object = std::get_if<Object *>(&object_value);
if (object == nullptr || *object == nullptr || *object == ctx_.object) {
error_ = "Object Info target is unavailable or recursive";
return {};
}
Object *evaluated = reinterpret_cast<Object *>(
DEG_get_evaluated_id(ctx_.depsgraph, &(*object)->id));
if (evaluated == nullptr) {
error_ = "Object Info target is not evaluated";
return {};
}
if (STREQ(socket.identifier, "Location")) {
return float3(evaluated->object_to_world().location());
}
if (STREQ(socket.identifier, "Scale")) {
return float3(evaluated->scale);
}
if (STREQ(socket.identifier, "Geometry")) {
Mesh *target_mesh = BKE_object_get_evaluated_mesh(evaluated);
if (target_mesh == nullptr) {
error_ = "Object Info target has no evaluated mesh";
return {};
}
if (!mesh_within_domain_budget(*target_mesh)) {
return {};
}
return bounded_geometry(
bke::GeometrySet::from_mesh(BKE_mesh_copy_for_eval(*target_mesh)));
}
error_ = "Object Info output is outside the bounded evaluator";
return {};
}
if (node_is(*node, "GeometryNodeCollectionInfo")) {
return evaluate_collection_info(*node);
}
if (node_is(*node, "GeometryNodeJoinGeometry")) {
const bNodeSocket *input = find_input(*node, "Geometry");
if (input == nullptr) {
error_ = "Join Geometry input is missing";
return {};
}
Vector<bke::GeometrySet> geometries;
for (const bNodeLink *link : input_links(*input)) {
WebNodeValue value = evaluate_output(*link->fromsock);
bke::GeometrySet *geometry = std::get_if<bke::GeometrySet>(&value);
if (geometry == nullptr) {
error_ = "Join Geometry received a non-geometry input";
return {};
}
geometries.append(std::move(*geometry));
}
if (geometries.is_empty()) {
return bke::GeometrySet();
}
int64_t points = 0;
int64_t edges = 0;
int64_t faces = 0;
int64_t corners = 0;
int64_t instances = 0;
for (const bke::GeometrySet &geometry : geometries) {
if (const Mesh *mesh = geometry.get_mesh()) {
points += mesh->verts_num;
edges += mesh->edges_num;
faces += mesh->faces_num;
corners += mesh->corners_num;
}
if (const bke::Instances *geometry_instances = geometry.get_instances()) {
instances += geometry_instances->instances_num();
}
}
if (points > max_point_elements || edges > max_edge_elements ||
faces > max_face_elements || corners > max_corner_elements ||
instances > max_instance_elements)
{
error_ = "Join Geometry field/domain budget exceeded";
return {};
}
return bounded_geometry(geometry::join_geometries(
geometries.as_span(), bke::AttributeFilter::default_filter()));
}
if (node_is(*node, "GeometryNodeSeparateGeometry")) {
std::optional<bke::GeometrySet> geometry = geometry_input(*node, "Geometry");
const bNodeSocket *selection_socket = find_input(*node, "Selection");
if (!geometry || selection_socket == nullptr) {
return {};
}
const WebNodeValue selection_value = evaluate_input(*selection_socket);
const bool *selection = std::get_if<bool>(&selection_value);
if (selection == nullptr) {
error_ = "Separate Geometry requires a bounded constant selection";
return {};
}
const bool selected_output = STREQ(socket.identifier, "Selection");
const bool inverted_output = STREQ(socket.identifier, "Inverted");
if (!selected_output && !inverted_output) {
error_ = "Separate Geometry output is invalid";
return {};
}
return bounded_geometry((*selection == selected_output) ? std::move(*geometry) :
bke::GeometrySet());
}
if (node_is(*node, "GeometryNodeRealizeInstances")) {
std::optional<bke::GeometrySet> geometry = geometry_input(*node, "Geometry");
if (!geometry) {
return {};
}
geometry::RealizeInstancesOptions options;
options.keep_original_ids = false;
options.realize_instance_attributes = true;
options.realize_to_point_domain = true;
geometry::RealizeInstancesResult result = geometry::realize_instances(std::move(*geometry),
options);
if (!result.errors.is_empty()) {
error_ = result.errors.first();
return {};
}
return bounded_geometry(std::move(result.geometry));
}
if (node_is(*node, "GeometryNodeStoreNamedAttribute")) {
std::optional<bke::GeometrySet> geometry = geometry_input(*node, "Geometry");
const bNodeSocket *selection_socket = find_input(*node, "Selection");
const bNodeSocket *name_socket = find_input(*node, "Name");
const bNodeSocket *value_socket = find_input(*node, "Value");
const NodeGeometryStoreNamedAttribute *storage =
static_cast<const NodeGeometryStoreNamedAttribute *>(node->storage);
if (!geometry || selection_socket == nullptr || name_socket == nullptr ||
value_socket == nullptr || storage == nullptr || storage->data_type != CD_PROP_FLOAT ||
storage->domain != int8_t(bke::AttrDomain::Point))
{
error_ = "Store Named Attribute supports Float point attributes only";
return {};
}
const WebNodeValue selection_value = evaluate_input(*selection_socket);
const WebNodeValue name_value = evaluate_input(*name_socket);
const std::optional<float> attribute_value = number(evaluate_input(*value_socket));
const bool *selection = std::get_if<bool>(&selection_value);
const std::string *name = std::get_if<std::string>(&name_value);
if (selection == nullptr || name == nullptr || name->empty() || name->size() > 64 ||
!attribute_value)
{
error_ = "Store Named Attribute inputs are invalid";
return {};
}
bke::GeometrySet result = copy_geometry(*geometry);
if (*selection) {
Mesh *result_mesh = result.get_mesh_for_write();
if (result_mesh == nullptr) {
error_ = "Store Named Attribute requires mesh geometry";
return {};
}
if (!mesh_within_domain_budget(*result_mesh) ||
int64_t(result_mesh->verts_num) * int64_t(sizeof(float)) > max_field_bytes)
{
error_ = "Store Named Attribute field budget exceeded";
return {};
}
bke::SpanAttributeWriter<float> writer =
result_mesh->attributes_for_write().lookup_or_add_for_write_span<float>(
*name, bke::AttrDomain::Point);
if (!writer) {
error_ = "Store Named Attribute could not allocate the point attribute";
return {};
}
writer.span.fill(*attribute_value);
writer.finish();
}
return bounded_geometry(std::move(result));
}
if (node_is(*node, "GeometryNodeTransform")) {
std::optional<bke::GeometrySet> geometry = geometry_input(*node, "Geometry");
const bNodeSocket *translation_socket = find_input(*node, "Translation");
const bNodeSocket *rotation_socket = find_input(*node, "Rotation");
const bNodeSocket *scale_socket = find_input(*node, "Scale");
if (!geometry || translation_socket == nullptr || rotation_socket == nullptr ||
scale_socket == nullptr || translation_socket->default_value == nullptr ||
rotation_socket->default_value == nullptr || scale_socket->default_value == nullptr ||
has_input_link(tree_, *node, "Rotation") || has_input_link(tree_, *node, "Scale"))
{
error_ = "Transform Geometry inputs are outside the bounded evaluator";
return {};
}
WebNodeValue translation_value = evaluate_input(*translation_socket);
const float3 *translation = std::get_if<float3>(&translation_value);
if (translation == nullptr) {
error_ = "Transform Geometry translation is unavailable";
return {};
}
const bNodeSocketValueRotation &rotation_value =
*static_cast<const bNodeSocketValueRotation *>(rotation_socket->default_value);
const math::Quaternion rotation = math::to_quaternion(
math::EulerXYZ(float3(rotation_value.value_euler)));
const float3 scale(
static_cast<const bNodeSocketValueVector *>(scale_socket->default_value)->value);
bke::GeometrySet result = copy_geometry(*geometry);
geometry::transform_geometry(
result, math::from_loc_rot_scale<float4x4>(*translation, rotation, scale));
return bounded_geometry(std::move(result));
}
if (node_is(*node, "GeometryNodeSetPosition")) {
std::optional<bke::GeometrySet> geometry = geometry_input(*node, "Geometry");
const bNodeSocket *selection_socket = find_input(*node, "Selection");
const bNodeSocket *offset_socket = find_input(*node, "Offset");
if (!geometry || selection_socket == nullptr || offset_socket == nullptr ||
has_input_link(tree_, *node, "Position"))
{
error_ = "Set Position inputs are outside the bounded evaluator";
return {};
}
const WebNodeValue selection_value = evaluate_input(*selection_socket);
const WebNodeValue offset_value = evaluate_input(*offset_socket);
const bool *selection = std::get_if<bool>(&selection_value);
const float3 *offset = std::get_if<float3>(&offset_value);
if (selection == nullptr || offset == nullptr) {
error_ = "Set Position requires a constant selection and vector offset";
return {};
}
bke::GeometrySet result = copy_geometry(*geometry);
if (*selection) {
geometry::translate_geometry(result, *offset);
}
return bounded_geometry(std::move(result));
}
error_ = std::string("unsupported node type: ") + node->idname;
return {};
}
public:
WebGeometryNodeEvaluator(const bNodeTree &tree,
const ModifierEvalContext &ctx,
const Mesh &input_mesh)
: tree_(tree), ctx_(ctx), input_mesh_(input_mesh)
{
}
Mesh *evaluate()
{
const bNode *group_output = nullptr;
for (const bNode &node : tree_.nodes) {
if (node_is(node, "NodeGroupOutput")) {
if (group_output != nullptr) {
error_ = "multiple Group Output nodes are unsupported";
return nullptr;
}
group_output = &node;
}
}
const bNodeSocket *geometry_socket = group_output == nullptr ? nullptr :
find_input(*group_output,
"Geometry");
if (geometry_socket == nullptr) {
error_ = "Geometry Group Output is missing";
return nullptr;
}
WebNodeValue value = evaluate_input(*geometry_socket);
bke::GeometrySet *geometry = std::get_if<bke::GeometrySet>(&value);
if (geometry == nullptr || !error_.empty()) {
if (error_.empty()) {
error_ = "Geometry Group Output did not evaluate to geometry";
}
return nullptr;
}
if (!geometry_within_domain_budget(*geometry)) {
return nullptr;
}
bke::MeshComponent &mesh_component =
geometry->get_component_for_write<bke::MeshComponent>();
mesh_component.ensure_owns_direct_data();
Mesh *result = mesh_component.release();
return result == nullptr ? BKE_mesh_new_nomain(0, 0, 0, 0) : result;
}
const std::string &error() const
{
return error_;
}
};
static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh *mesh)
{
const NodesModifierData *nmd = reinterpret_cast<const NodesModifierData *>(md);
@@ -168,13 +858,8 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh
return mesh;
}
const bNode *group_input = nullptr;
const bNode *group_output = nullptr;
const bNode *transform = nullptr;
const bNode *set_position = nullptr;
bool has_simulation_node = false;
int node_count = 0;
int link_count = 0;
for (const bNode &node : tree->nodes) {
node_count++;
if (ELEM(node.type_legacy,
@@ -188,25 +873,6 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh
{
has_simulation_node = true;
}
if (find_input(node, "Translation") != nullptr && find_input(node, "Rotation") != nullptr &&
find_input(node, "Scale") != nullptr)
{
transform = &node;
}
else if (find_input(node, "Offset") != nullptr && find_input(node, "Position") != nullptr &&
find_input(node, "Selection") != nullptr)
{
set_position = &node;
}
else if (node.inputs.first == nullptr && node.outputs.first != nullptr) {
group_input = &node;
}
else if (node.inputs.first != nullptr && node.outputs.first == nullptr) {
group_output = &node;
}
}
for ([[maybe_unused]] const bNodeLink &link : tree->links) {
link_count++;
}
if (has_simulation_node) {
@@ -217,85 +883,20 @@ static Mesh *modify_mesh(ModifierData *md, const ModifierEvalContext *ctx, Mesh
return mesh;
}
const bNode *geometry_node = transform != nullptr ? transform : set_position;
const bool input_link_valid = group_input != nullptr && geometry_node != nullptr &&
has_link(*tree, *group_input, "Geometry", *geometry_node, "Geometry");
const bool output_link_valid = geometry_node != nullptr && group_output != nullptr &&
has_link(*tree, *geometry_node, "Geometry", *group_output, "Geometry");
if (node_count != 3 || link_count != 2 || group_input == nullptr ||
group_output == nullptr || geometry_node == nullptr || (transform != nullptr && set_position != nullptr) ||
!input_link_valid || !output_link_valid)
{
BKE_modifier_set_error(
ctx->object,
md,
"Web Geometry Nodes supports only a single constant Transform Geometry or Set Position node");
return mesh;
}
if (set_position != nullptr) {
const bNodeSocket *selection_socket = find_input(*set_position, "Selection");
const bNodeSocket *offset_socket = find_input(*set_position, "Offset");
if (selection_socket == nullptr || offset_socket == nullptr ||
selection_socket->default_value == nullptr || offset_socket->default_value == nullptr ||
has_input_link(*tree, *set_position, "Selection") ||
has_input_link(*tree, *set_position, "Position") ||
has_input_link(*tree, *set_position, "Offset"))
{
BKE_modifier_set_error(
ctx->object, md, "Web Geometry Nodes Set Position supports only unlinked constant inputs");
return mesh;
}
Mesh *result = BKE_mesh_copy_for_eval(*mesh);
const bool selected = static_cast<const bNodeSocketValueBoolean *>(
selection_socket->default_value)
->value;
if (selected) {
const float3 offset(
static_cast<const bNodeSocketValueVector *>(offset_socket->default_value)->value);
for (float3 &position : result->vert_positions_for_write()) {
position += offset;
}
result->tag_positions_changed();
}
return result;
}
const bNodeSocket *mode_socket = find_input(*transform, "Mode");
const int mode = mode_socket && mode_socket->default_value ?
static_cast<const bNodeSocketValueMenu *>(mode_socket->default_value)->value :
GEO_NODE_TRANSFORM_MODE_COMPONENTS;
if (mode != GEO_NODE_TRANSFORM_MODE_COMPONENTS) {
if (node_count > WebGeometryNodeEvaluator::max_evaluations) {
BKE_modifier_set_error(ctx->object,
md,
"Web Geometry Nodes Transform supports Components mode only");
"WEB_GEOMETRY_NODES_EVALUATOR_UNSUPPORTED: node budget exceeded");
return mesh;
}
const bNodeSocket *translation_socket = find_input(*transform, "Translation");
const bNodeSocket *rotation_socket = find_input(*transform, "Rotation");
const bNodeSocket *scale_socket = find_input(*transform, "Scale");
if (translation_socket == nullptr || rotation_socket == nullptr || scale_socket == nullptr ||
translation_socket->default_value == nullptr || rotation_socket->default_value == nullptr ||
scale_socket->default_value == nullptr)
{
BKE_modifier_set_error(ctx->object, md, "Web Geometry Nodes Transform sockets are incomplete");
WebGeometryNodeEvaluator evaluator(*tree, *ctx, *mesh);
Mesh *result = evaluator.evaluate();
if (result == nullptr) {
const std::string message = "WEB_GEOMETRY_NODES_EVALUATOR_UNSUPPORTED: " + evaluator.error();
BKE_modifier_set_error(ctx->object, md, "%s", message.c_str());
return mesh;
}
const float3 translation(
static_cast<const bNodeSocketValueVector *>(translation_socket->default_value)->value);
const bNodeSocketValueRotation &rotation_value =
*static_cast<const bNodeSocketValueRotation *>(rotation_socket->default_value);
const math::Quaternion rotation = math::to_quaternion(
math::EulerXYZ(float3(rotation_value.value_euler)));
const float3 scale(
static_cast<const bNodeSocketValueVector *>(scale_socket->default_value)->value);
Mesh *result = BKE_mesh_copy_for_eval(*mesh);
bke::mesh_transform(
*result, math::from_loc_rot_scale<float4x4>(translation, rotation, scale), false);
return result;
}