/* SPDX-FileCopyrightText: 2023 Blender Foundation * * SPDX-License-Identifier: GPL-2.0-or-later */ /** \file * \ingroup edcurves */ #include "ED_curves.hh" #include "ED_object.h" #include "ED_screen.h" #include "ED_select_utils.h" #include "ED_view3d.h" #include "WM_api.h" #include "BKE_asset.h" #include "BKE_attribute_math.hh" #include "BKE_compute_contexts.hh" #include "BKE_context.h" #include "BKE_curves.hh" #include "BKE_editmesh.h" #include "BKE_geometry_set.hh" #include "BKE_layer.h" #include "BKE_lib_id.h" #include "BKE_material.h" #include "BKE_mesh.hh" #include "BKE_mesh_wrapper.h" #include "BKE_node_runtime.hh" #include "BKE_object.h" #include "BKE_pointcloud.h" #include "BKE_report.h" #include "BKE_screen.h" #include "DNA_object_types.h" #include "DNA_scene_types.h" #include "DEG_depsgraph.h" #include "DEG_depsgraph_query.h" #include "RNA_access.h" #include "RNA_enum_types.h" #include "UI_interface.h" #include "UI_resources.h" #include "ED_asset.h" #include "ED_geometry.h" #include "ED_mesh.h" #include "BLT_translation.h" #include "FN_lazy_function_execute.hh" #include "NOD_geometry_nodes_execute.hh" #include "NOD_geometry_nodes_lazy_function.hh" #include "AS_asset_catalog.hh" #include "AS_asset_catalog_path.hh" #include "AS_asset_catalog_tree.hh" #include "AS_asset_library.hh" #include "AS_asset_representation.hh" #include "geometry_intern.hh" namespace blender::ed::geometry { /* -------------------------------------------------------------------- */ /** \name Operator * \{ */ static const bNodeTree *get_node_group(const bContext &C) { const asset_system::AssetRepresentation *asset = CTX_wm_asset(&C); if (!asset) { return nullptr; } Main &bmain = *CTX_data_main(&C); bNodeTree *node_group = reinterpret_cast( asset::asset_local_id_ensure_imported(bmain, *asset)); if (!node_group) { return nullptr; } if (node_group->type != NTREE_GEOMETRY) { return nullptr; } return node_group; } class OperatorComputeContext : public ComputeContext { private: static constexpr const char *s_static_type = "OPERATOR"; std::string operator_name_; public: OperatorComputeContext(std::string operator_name) : ComputeContext(s_static_type, nullptr), operator_name_(std::move(operator_name)) { hash_.mix_in(s_static_type, strlen(s_static_type)); hash_.mix_in(operator_name_.data(), operator_name_.size()); } private: void print_current_in_line(std::ostream &stream) const override { stream << "Operator: " << operator_name_; } }; /** * Geometry nodes currently requires working on "evaluated" data-blocks (rather than "original" * data-blocks that are part of a #Main data-base). This could change in the future, but for now, * we need to create evaluated copies of geometry before passing it to geometry nodes. Implicit * sharing lets us avoid copying attribute data though. */ static bke::GeometrySet get_original_geometry_eval_copy(Object &object) { switch (object.type) { case OB_CURVES: { Curves *curves = BKE_curves_copy_for_eval(static_cast(object.data)); return bke::GeometrySet::create_with_curves(curves); } case OB_POINTCLOUD: { PointCloud *points = BKE_pointcloud_copy_for_eval( static_cast(object.data)); return bke::GeometrySet::create_with_pointcloud(points); } case OB_MESH: { const Mesh *mesh = static_cast(object.data); if (mesh->edit_mesh) { Mesh *mesh_copy = BKE_mesh_wrapper_from_editmesh(mesh->edit_mesh, nullptr, mesh); BKE_mesh_wrapper_ensure_mdata(mesh_copy); Mesh *final_copy = BKE_mesh_copy_for_eval(mesh_copy); BKE_id_free(nullptr, mesh_copy); return bke::GeometrySet::create_with_mesh(final_copy); } return bke::GeometrySet::create_with_mesh(BKE_mesh_copy_for_eval(mesh)); } default: return {}; } } static void store_result_geometry(Main &bmain, Object &object, bke::GeometrySet geometry) { switch (object.type) { case OB_CURVES: { Curves &curves = *static_cast(object.data); Curves *new_curves = geometry.get_curves_for_write(); if (!new_curves) { curves.geometry.wrap() = {}; break; } /* Anonymous attributes shouldn't be available on the applied geometry. */ new_curves->geometry.wrap().attributes_for_write().remove_anonymous(); curves.geometry.wrap() = std::move(new_curves->geometry.wrap()); BKE_object_material_from_eval_data(&bmain, &object, &new_curves->id); break; } case OB_POINTCLOUD: { PointCloud &points = *static_cast(object.data); PointCloud *new_points = geometry.get_component_for_write().release(); if (!new_points) { CustomData_free(&points.pdata, points.totpoint); points.totpoint = 0; break; } /* Anonymous attributes shouldn't be available on the applied geometry. */ new_points->attributes_for_write().remove_anonymous(); BKE_object_material_from_eval_data(&bmain, &object, &new_points->id); BKE_pointcloud_nomain_to_pointcloud(new_points, &points); break; } case OB_MESH: { Mesh &mesh = *static_cast(object.data); Mesh *new_mesh = geometry.get_component_for_write().release(); if (!new_mesh) { BKE_mesh_clear_geometry(&mesh); if (object.mode == OB_MODE_EDIT) { EDBM_mesh_make(&object, SCE_SELECT_VERTEX, true); } break; } /* Anonymous attributes shouldn't be available on the applied geometry. */ new_mesh->attributes_for_write().remove_anonymous(); BKE_object_material_from_eval_data(&bmain, &object, &new_mesh->id); BKE_mesh_nomain_to_mesh(new_mesh, &mesh, &object); if (object.mode == OB_MODE_EDIT) { EDBM_mesh_make(&object, SCE_SELECT_VERTEX, true); BKE_editmesh_looptri_and_normals_calc(mesh.edit_mesh); } break; } } } static int run_node_group_exec(bContext *C, wmOperator *op) { Main *bmain = CTX_data_main(C); Depsgraph *depsgraph = CTX_data_ensure_evaluated_depsgraph(C); Scene *scene = CTX_data_scene(C); ViewLayer *view_layer = CTX_data_view_layer(C); Object *active_object = CTX_data_active_object(C); if (!active_object) { return OPERATOR_CANCELLED; } if (active_object->mode == OB_MODE_OBJECT) { return OPERATOR_CANCELLED; } const eObjectMode mode = eObjectMode(active_object->mode); const bNodeTree *node_tree = get_node_group(*C); if (!node_tree) { return OPERATOR_CANCELLED; } const nodes::GeometryNodesLazyFunctionGraphInfo *lf_graph_info = nodes::ensure_geometry_nodes_lazy_function_graph(*node_tree); if (lf_graph_info == nullptr) { BKE_report(op->reports, RPT_ERROR, "Cannot evaluate node group"); return OPERATOR_CANCELLED; } uint objects_len = 0; Object **objects = BKE_view_layer_array_from_objects_in_mode_unique_data( scene, view_layer, CTX_wm_view3d(C), &objects_len, mode); OperatorComputeContext compute_context(op->type->idname); for (Object *object : Span(objects, objects_len)) { if (!ELEM(object->type, OB_CURVES, OB_POINTCLOUD, OB_MESH)) { continue; } nodes::GeoNodesOperatorData operator_eval_data{}; operator_eval_data.depsgraph = depsgraph; operator_eval_data.self_object = object; bke::GeometrySet geometry_orig = get_original_geometry_eval_copy(*object); bke::GeometrySet new_geometry = nodes::execute_geometry_nodes_on_geometry( *node_tree, op->properties, compute_context, std::move(geometry_orig), [&](nodes::GeoNodesLFUserData &user_data) { user_data.operator_data = &operator_eval_data; user_data.log_socket_values = false; }); store_result_geometry(*bmain, *object, std::move(new_geometry)); DEG_id_tag_update(static_cast(object->data), ID_RECALC_GEOMETRY); WM_event_add_notifier(C, NC_GEOM | ND_DATA, object->data); } MEM_SAFE_FREE(objects); return OPERATOR_FINISHED; } static int run_node_group_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/) { const bNodeTree *node_tree = get_node_group(*C); if (!node_tree) { return OPERATOR_CANCELLED; } nodes::update_input_properties_from_node_tree(*node_tree, op->properties, *op->properties); nodes::update_output_properties_from_node_tree(*node_tree, op->properties, *op->properties); return run_node_group_exec(C, op); } static char *run_node_group_get_description(bContext *C, wmOperatorType * /*ot*/, PointerRNA * /*ptr*/) { const asset_system::AssetRepresentation *asset = CTX_wm_asset(C); if (!asset) { return nullptr; } const char *description = asset->get_metadata().description; if (!description) { return nullptr; } return BLI_strdup(description); } static bool run_node_group_poll(bContext *C) { const asset_system::AssetRepresentation *asset = CTX_wm_asset(C); if (!asset) { return false; } const Object *object = CTX_data_active_object(C); if (object->type != OB_CURVES) { return false; } if (object->mode != OB_MODE_SCULPT_CURVES) { return false; } return true; } void GEOMETRY_OT_execute_node_group(wmOperatorType *ot) { ot->name = "Run Node Group"; ot->idname = __func__; ot->description = "Execute a node group on geometry"; ot->poll = run_node_group_poll; ot->invoke = run_node_group_invoke; ot->exec = run_node_group_exec; ot->get_description = run_node_group_get_description; ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } /** \} */ /* -------------------------------------------------------------------- */ /** \name Menu * \{ */ static bool asset_menu_poll(const bContext *C, MenuType * /*mt*/) { return CTX_wm_view3d(C); } static asset::AssetItemTree &get_static_item_tree() { static asset::AssetItemTree tree; return tree; } static asset::AssetItemTree build_catalog_tree(const bContext &C) { AssetFilterSettings type_filter{}; type_filter.id_types = FILTER_ID_NT; AssetTag operator_tag; STRNCPY(operator_tag.name, "Operator"); BLI_addtail(&type_filter.tags, &operator_tag); auto meta_data_filter = [&](const AssetMetaData &meta_data) { const IDProperty *tree_type = BKE_asset_metadata_idprop_find(&meta_data, "type"); if (tree_type == nullptr || IDP_Int(tree_type) != NTREE_GEOMETRY) { return false; } return true; }; const AssetLibraryReference library = asset_system::all_library_reference(); return asset::build_filtered_all_catalog_tree(library, C, type_filter, meta_data_filter); } /** * Avoid adding a separate root catalog when the assets have already been added to one of the * builtin menus. The need to define the builtin menu labels here is non-ideal. We don't have * any UI introspection that can do this though. */ static Set get_builtin_menus(const ObjectType object_type, const eObjectMode mode) { Set menus; switch (object_type) { case OB_CURVES: menus.add_new("View"); menus.add_new("Select"); menus.add_new("Curves"); break; case OB_MESH: switch (mode) { case OB_MODE_EDIT: menus.add_new("View"); menus.add_new("Select"); menus.add_new("Add"); menus.add_new("Mesh"); menus.add_new("Vertex"); menus.add_new("Edge"); menus.add_new("Face"); menus.add_new("UV"); break; case OB_MODE_SCULPT: menus.add_new("View"); menus.add_new("Sculpt"); menus.add_new("Mask"); menus.add_new("Face Sets"); break; case OB_MODE_VERTEX_PAINT: menus.add_new("View"); menus.add_new("Paint"); break; case OB_MODE_WEIGHT_PAINT: menus.add_new("View"); menus.add_new("Weights"); break; default: break; } default: break; } return menus; } static void node_add_catalog_assets_draw(const bContext *C, Menu *menu) { bScreen &screen = *CTX_wm_screen(C); asset::AssetItemTree &tree = get_static_item_tree(); const PointerRNA menu_path_ptr = CTX_data_pointer_get(C, "asset_catalog_path"); if (RNA_pointer_is_null(&menu_path_ptr)) { return; } const auto &menu_path = *static_cast(menu_path_ptr.data); const Span assets = tree.assets_per_path.lookup(menu_path); asset_system::AssetCatalogTreeItem *catalog_item = tree.catalogs.find_item(menu_path); BLI_assert(catalog_item != nullptr); if (assets.is_empty() && !catalog_item->has_children()) { return; } uiLayout *layout = menu->layout; uiItemS(layout); for (const asset_system::AssetRepresentation *asset : assets) { uiLayout *col = uiLayoutColumn(layout, false); PointerRNA asset_ptr = asset::create_asset_rna_ptr(asset); uiLayoutSetContextPointer(col, "asset", &asset_ptr); uiItemO(col, IFACE_(asset->get_name().c_str()), ICON_NONE, "GEOMETRY_OT_execute_node_group"); } asset_system::AssetLibrary *all_library = ED_assetlist_library_get_once_available( asset_system::all_library_reference()); if (!all_library) { return; } catalog_item->foreach_child([&](asset_system::AssetCatalogTreeItem &child_item) { PointerRNA path_ptr = asset::persistent_catalog_path_rna_pointer( screen, *all_library, child_item); if (path_ptr.data == nullptr) { return; } uiLayout *col = uiLayoutColumn(layout, false); uiLayoutSetContextPointer(col, "asset_catalog_path", &path_ptr); uiItemM(col, "GEO_MT_node_operator_catalog_assets", IFACE_(child_item.get_name().c_str()), ICON_NONE); }); } MenuType node_group_operator_assets_menu() { MenuType type{}; STRNCPY(type.idname, "GEO_MT_node_operator_catalog_assets"); type.poll = asset_menu_poll; type.draw = node_add_catalog_assets_draw; type.listener = asset::asset_reading_region_listen_fn; return type; } void ui_template_node_operator_asset_menu_items(uiLayout &layout, bContext &C, const StringRef catalog_path) { bScreen &screen = *CTX_wm_screen(&C); asset::AssetItemTree &tree = get_static_item_tree(); const asset_system::AssetCatalogTreeItem *item = tree.catalogs.find_root_item(catalog_path); if (!item) { return; } asset_system::AssetLibrary *all_library = ED_assetlist_library_get_once_available( asset_system::all_library_reference()); if (!all_library) { return; } PointerRNA path_ptr = asset::persistent_catalog_path_rna_pointer(screen, *all_library, *item); if (path_ptr.data == nullptr) { return; } uiItemS(&layout); uiLayout *col = uiLayoutColumn(&layout, false); uiLayoutSetContextPointer(col, "asset_catalog_path", &path_ptr); uiItemMContents(col, "GEO_MT_node_operator_catalog_assets"); } void ui_template_node_operator_asset_root_items(uiLayout &layout, bContext &C) { bScreen &screen = *CTX_wm_screen(&C); const Object *active_object = CTX_data_active_object(&C); if (!active_object) { return; } asset::AssetItemTree &tree = get_static_item_tree(); tree = build_catalog_tree(C); if (tree.catalogs.is_empty()) { return; } asset_system::AssetLibrary *all_library = ED_assetlist_library_get_once_available( asset_system::all_library_reference()); if (!all_library) { return; } const Set builtin_menus = get_builtin_menus(ObjectType(active_object->type), eObjectMode(active_object->mode)); tree.catalogs.foreach_root_item([&](asset_system::AssetCatalogTreeItem &item) { if (builtin_menus.contains(item.get_name())) { return; } PointerRNA path_ptr = asset::persistent_catalog_path_rna_pointer(screen, *all_library, item); if (path_ptr.data == nullptr) { return; } uiLayout *col = uiLayoutColumn(&layout, false); uiLayoutSetContextPointer(col, "asset_catalog_path", &path_ptr); const char *text = IFACE_(item.get_name().c_str()); uiItemM(col, "GEO_MT_node_operator_catalog_assets", text, ICON_NONE); }); } /** \} */ } // namespace blender::ed::geometry