Geometry Nodes: new Viewer node

This adds a viewer node similar to the one in the compositor.
The icon in the headers of nodes is removed because it served
the same purpose and is not necessary anymore.

Node outputs can be connected to the active viewer using
ctrl+shift+LMB, just like in the compositor. Right now this collides
with the shortcut used in the node wrangler addon, which will
be changed separately.

As of now, the viewed geometry is only visible in the spreadsheet.
Viewport visualization will be added separately.

There are a couple of benefits of using a viewer node compared
to the old approach with the icon in the node header:
* Better support for nodes that have more than one geometry output.
* It's more consistent with the compositor.
* If attributes become decoupled from geometry in the future,
  the viewer can have a separate input for the attribute to visualize.
* The viewer node could potentially have visualization settings.
* Allows to keep "visualization points" around by having multiple
  viewer nodes.
* Less visual clutter in node headers.

Differential Revision: https://developer.blender.org/D11470
This commit is contained in:
Jacques Lucke 2021-07-05 10:46:00 +02:00
parent 04313f1bb5
commit 9009ac2c3d
Notes: blender-bot 2023-02-14 06:00:46 +01:00
Referenced by issue #90786, Geometry nodes: editor do not refresh immediately if GN modifier clicked out of icon.
Referenced by issue #89829, Geometry Nodes: Crash when selecting attribute name
Referenced by issue #88229, Viewer Node for geometry nodes
31 changed files with 484 additions and 208 deletions

View File

@ -306,67 +306,6 @@ class NODE_OT_tree_path_parent(Operator):
return {'FINISHED'}
class NODE_OT_active_preview_toggle(Operator):
'''Toggle active preview state of node'''
bl_idname = "node.active_preview_toggle"
bl_label = "Toggle Active Preview"
bl_options = {'REGISTER', 'UNDO'}
@classmethod
def poll(cls, context):
space = context.space_data
if space is None:
return False
if space.type != 'NODE_EDITOR':
return False
if space.edit_tree is None:
return False
if space.edit_tree.nodes.active is None:
return False
return True
def execute(self, context):
node_editor = context.space_data
ntree = node_editor.edit_tree
active_node = ntree.nodes.active
if active_node.active_preview:
self._disable_preview(context, active_node)
else:
self._enable_preview(context, node_editor, ntree, active_node)
return {'FINISHED'}
@classmethod
def _enable_preview(cls, context, node_editor, ntree, active_node):
spreadsheets = cls._find_unpinned_spreadsheets(context)
for spreadsheet in spreadsheets:
spreadsheet.set_geometry_node_context(node_editor, active_node)
for node in ntree.nodes:
node.active_preview = False
active_node.active_preview = True
@classmethod
def _disable_preview(cls, context, active_node):
spreadsheets = cls._find_unpinned_spreadsheets(context)
for spreadsheet in spreadsheets:
spreadsheet.context_path.clear()
active_node.active_preview = False
@staticmethod
def _find_unpinned_spreadsheets(context):
spreadsheets = []
for window in context.window_manager.windows:
for area in window.screen.areas:
space = area.spaces.active
if space.type == 'SPREADSHEET' and not space.is_pinned:
spreadsheets.append(space)
return spreadsheets
classes = (
NodeSetting,
@ -375,5 +314,4 @@ classes = (
NODE_OT_add_search,
NODE_OT_collapse_hide_unused_toggle,
NODE_OT_tree_path_parent,
NODE_OT_active_preview_toggle,
)

View File

@ -47,18 +47,7 @@ class SPREADSHEET_OT_toggle_pin(Operator):
def unpin(self, context):
space = context.space_data
space.is_pinned = False
space.context_path.clear()
# Try to find a node with an active preview in any open editor.
if space.object_eval_state == 'EVALUATED':
node_editors = self.find_geometry_node_editors(context)
for node_editor in node_editors:
ntree = node_editor.edit_tree
for node in ntree.nodes:
if node.active_preview:
space.set_geometry_node_context(node_editor, node)
return
space.context_path.guess()
def find_geometry_node_editors(self, context):
editors = []

View File

@ -54,6 +54,9 @@ class SPREADSHEET_HT_header(bpy.types.Header):
pin_icon = 'PINNED' if space.is_pinned else 'UNPINNED'
layout.operator("spreadsheet.toggle_pin", text="", icon=pin_icon, emboss=False)
if space.object_eval_state == 'VIEWER_NODE' and len(context_path) < 3:
layout.label(text="No active viewer node.", icon='INFO')
layout.separator_spacer()
row = layout.row(align=True)

View File

@ -581,6 +581,9 @@ geometry_node_categories = [
NodeItem("ShaderNodeVectorMath"),
NodeItem("ShaderNodeVectorRotate"),
]),
GeometryNodeCategory("GEO_OUTPUT", "Output", items=[
NodeItem("GeometryNodeViewer"),
]),
GeometryNodeCategory("GEO_VOLUME", "Volume", items=[
NodeItem("GeometryNodePointsToVolume"),
NodeItem("GeometryNodeVolumeToMesh"),

View File

@ -1444,6 +1444,7 @@ int ntreeTexExecTree(struct bNodeTree *ntree,
#define GEO_NODE_CURVE_PRIMITIVE_QUADRATIC_BEZIER 1064
#define GEO_NODE_CURVE_PRIMITIVE_BEZIER_SEGMENT 1065
#define GEO_NODE_CURVE_PRIMITIVE_CIRCLE 1066
#define GEO_NODE_VIEWER 1067
/** \} */

View File

@ -3151,8 +3151,8 @@ void ntreeSetOutput(bNodeTree *ntree)
if (ntree->type == NTREE_COMPOSIT) {
/* same type, exception for viewer */
if (tnode->type == node->type ||
(ELEM(tnode->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER) &&
ELEM(node->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER))) {
(ELEM(tnode->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER, GEO_NODE_VIEWER) &&
ELEM(node->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER, GEO_NODE_VIEWER))) {
if (tnode->flag & NODE_DO_OUTPUT) {
output++;
if (output > 1) {
@ -5097,6 +5097,7 @@ static void registerGeometryNodes()
register_node_type_geo_switch();
register_node_type_geo_transform();
register_node_type_geo_triangulate();
register_node_type_geo_viewer();
register_node_type_geo_volume_to_mesh();
}

View File

@ -40,6 +40,7 @@ struct bNodeSocketType;
struct bNodeTree;
struct bNodeTreeType;
struct bNodeType;
struct SpaceNode;
typedef enum {
NODE_TOP = 1,
@ -114,6 +115,7 @@ bool ED_node_select_check(const ListBase *lb);
void ED_node_select_all(ListBase *lb, int action);
void ED_node_post_apply_transform(struct bContext *C, struct bNodeTree *ntree);
void ED_node_set_active(struct Main *bmain,
struct SpaceNode *snode,
struct bNodeTree *ntree,
struct bNode *node,
bool *r_active_texture_changed);

View File

@ -21,6 +21,9 @@ struct SpaceSpreadsheet;
struct SpaceNode;
struct ID;
struct bNode;
struct Main;
struct bContext;
struct Object;
#ifdef __cplusplus
extern "C" {
@ -29,14 +32,25 @@ extern "C" {
struct SpreadsheetContext *ED_spreadsheet_context_new(int type);
void ED_spreadsheet_context_free(struct SpreadsheetContext *context);
void ED_spreadsheet_context_path_clear(struct SpaceSpreadsheet *sspreadsheet);
void ED_spreadsheet_context_path_update_tag(struct SpaceSpreadsheet *sspreadsheet);
bool ED_spreadsheet_context_path_update_tag(struct SpaceSpreadsheet *sspreadsheet);
uint64_t ED_spreadsheet_context_path_hash(const struct SpaceSpreadsheet *sspreadsheet);
struct ID *ED_spreadsheet_get_current_id(const struct SpaceSpreadsheet *sspreadsheet);
void ED_spreadsheet_set_geometry_node_context(struct SpaceSpreadsheet *sspreadsheet,
struct SpaceNode *snode,
struct bNode *node);
void ED_spreadsheet_context_path_set_geometry_node(struct SpaceSpreadsheet *sspreadsheet,
struct SpaceNode *snode,
struct bNode *node);
void ED_spreadsheet_context_paths_set_geometry_node(struct Main *bmain,
struct SpaceNode *snode,
struct bNode *node);
void ED_spreadsheet_context_path_set_evaluated_object(struct SpaceSpreadsheet *sspreadsheet,
struct Object *object);
void ED_spreadsheet_context_path_guess(const struct bContext *C,
struct SpaceSpreadsheet *sspreadsheet);
bool ED_spreadsheet_context_path_is_active(const struct bContext *C,
struct SpaceSpreadsheet *sspreadsheet);
bool ED_spreadsheet_context_path_exists(struct Main *bmain, struct SpaceSpreadsheet *sspreadsheet);
#ifdef __cplusplus
}

View File

@ -451,7 +451,7 @@ static void template_texture_select(bContext *C, void *user_p, void *UNUSED(arg)
/* set user as active */
if (user->node) {
ED_node_set_active(CTX_data_main(C), user->ntree, user->node, NULL);
ED_node_set_active(CTX_data_main(C), NULL, user->ntree, user->node, NULL);
ct->texture = NULL;
/* Not totally sure if we should also change selection? */

View File

@ -90,7 +90,7 @@ bNode *node_add_node(const bContext *C, const char *idname, int type, float locx
nodeSetSelected(node, true);
ntreeUpdateTree(bmain, snode->edittree);
ED_node_set_active(bmain, snode->edittree, node, nullptr);
ED_node_set_active(bmain, snode, snode->edittree, node, nullptr);
snode_update(snode, node);

View File

@ -1405,28 +1405,6 @@ static void node_draw_basis(const bContext *C,
"");
UI_block_emboss_set(node->block, UI_EMBOSS);
}
if (ntree->type == NTREE_GEOMETRY) {
/* Active preview toggle. */
iconofs -= iconbutw;
UI_block_emboss_set(node->block, UI_EMBOSS_NONE);
int icon = (node->flag & NODE_ACTIVE_PREVIEW) ? ICON_RESTRICT_VIEW_OFF : ICON_RESTRICT_VIEW_ON;
uiBut *but = uiDefIconBut(node->block,
UI_BTYPE_BUT_TOGGLE,
0,
icon,
iconofs,
rct->ymax - NODE_DY,
iconbutw,
UI_UNIT_Y,
nullptr,
0,
0,
0,
0,
"Show this node's geometry output in the spreadsheet");
UI_but_func_set(but, node_toggle_button_cb, node, (void *)"NODE_OT_active_preview_toggle");
UI_block_emboss_set(node->block, UI_EMBOSS);
}
node_add_error_message_button(C, *ntree, *node, *rct, iconofs);

View File

@ -54,6 +54,7 @@
#include "ED_render.h"
#include "ED_screen.h"
#include "ED_select_utils.h"
#include "ED_spreadsheet.h"
#include "RNA_access.h"
#include "RNA_define.h"
@ -662,7 +663,8 @@ void snode_update(SpaceNode *snode, bNode *node)
}
}
void ED_node_set_active(Main *bmain, bNodeTree *ntree, bNode *node, bool *r_active_texture_changed)
void ED_node_set_active(
Main *bmain, SpaceNode *snode, bNodeTree *ntree, bNode *node, bool *r_active_texture_changed)
{
const bool was_active_texture = (node->flag & NODE_ACTIVE_TEXTURE) != 0;
if (r_active_texture_changed) {
@ -782,6 +784,19 @@ void ED_node_set_active(Main *bmain, bNodeTree *ntree, bNode *node, bool *r_acti
}
#endif
}
else if (ntree->type == NTREE_GEOMETRY) {
if (node->type == GEO_NODE_VIEWER) {
if ((node->flag & NODE_DO_OUTPUT) == 0) {
LISTBASE_FOREACH (bNode *, node_iter, &ntree->nodes) {
if (node_iter->type == GEO_NODE_VIEWER) {
node_iter->flag &= ~NODE_DO_OUTPUT;
}
}
node->flag |= NODE_DO_OUTPUT;
ED_spreadsheet_context_paths_set_geometry_node(bmain, snode, node);
}
}
}
}
}
@ -1318,7 +1333,6 @@ static int node_duplicate_exec(bContext *C, wmOperator *op)
nodeSetSelected(node, false);
node->flag &= ~(NODE_ACTIVE | NODE_ACTIVE_TEXTURE);
nodeSetSelected(newnode, true);
newnode->flag &= ~NODE_ACTIVE_PREVIEW;
do_tag_update |= (do_tag_update || node_connected_to_output(bmain, ntree, newnode));
}

View File

@ -279,7 +279,6 @@ void NODE_OT_hide_toggle(struct wmOperatorType *ot);
void NODE_OT_hide_socket_toggle(struct wmOperatorType *ot);
void NODE_OT_preview_toggle(struct wmOperatorType *ot);
void NODE_OT_options_toggle(struct wmOperatorType *ot);
void NODE_OT_active_preview_toggle(struct wmOperatorType *ot);
void NODE_OT_node_copy_color(struct wmOperatorType *ot);
void NODE_OT_read_viewlayers(struct wmOperatorType *ot);

View File

@ -40,11 +40,14 @@
#include "ED_node.h" /* own include */
#include "ED_render.h"
#include "ED_screen.h"
#include "ED_spreadsheet.h"
#include "ED_util.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "DEG_depsgraph.h"
#include "WM_api.h"
#include "WM_types.h"
@ -160,6 +163,11 @@ bool node_connected_to_output(Main *bmain, bNodeTree *ntree, bNode *node)
return true;
}
}
if (current_node->type == GEO_NODE_VIEWER) {
if (ntree_check_nodes_connected(ntree, node, current_node)) {
return true;
}
}
}
return false;
}
@ -610,14 +618,14 @@ static int node_link_viewer(const bContext *C, bNode *tonode)
if (tonode == nullptr || BLI_listbase_is_empty(&tonode->outputs)) {
return OPERATOR_CANCELLED;
}
if (ELEM(tonode->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER)) {
if (ELEM(tonode->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER, GEO_NODE_VIEWER)) {
return OPERATOR_CANCELLED;
}
/* get viewer */
bNode *viewer_node = nullptr;
LISTBASE_FOREACH (bNode *, node, &snode->edittree->nodes) {
if (ELEM(node->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER)) {
if (ELEM(node->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER, GEO_NODE_VIEWER)) {
if (node->flag & NODE_DO_OUTPUT) {
viewer_node = node;
break;
@ -627,7 +635,7 @@ static int node_link_viewer(const bContext *C, bNode *tonode)
/* no viewer, we make one active */
if (viewer_node == nullptr) {
LISTBASE_FOREACH (bNode *, node, &snode->edittree->nodes) {
if (ELEM(node->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER)) {
if (ELEM(node->type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER, GEO_NODE_VIEWER)) {
node->flag |= NODE_DO_OUTPUT;
viewer_node = node;
break;
@ -686,7 +694,8 @@ static int node_link_viewer(const bContext *C, bNode *tonode)
/* add a new viewer if none exists yet */
if (!viewer_node) {
/* XXX location is a quick hack, just place it next to the linked socket */
viewer_node = node_add_node(C, nullptr, CMP_NODE_VIEWER, sock->locx + 100, sock->locy);
const int viewer_type = ED_node_is_compositor(snode) ? CMP_NODE_VIEWER : GEO_NODE_VIEWER;
viewer_node = node_add_node(C, nullptr, viewer_type, sock->locx + 100, sock->locy);
if (!viewer_node) {
return OPERATOR_CANCELLED;
}
@ -712,8 +721,13 @@ static int node_link_viewer(const bContext *C, bNode *tonode)
/* make sure the dependency sorting is updated */
snode->edittree->update |= NTREE_UPDATE_LINKS;
}
if (ED_node_is_geometry(snode)) {
ED_spreadsheet_context_paths_set_geometry_node(CTX_data_main(C), snode, viewer_node);
}
ntreeUpdateTree(CTX_data_main(C), snode->edittree);
snode_update(snode, viewer_node);
DEG_id_tag_update(&snode->edittree->id, 0);
}
return OPERATOR_FINISHED;
@ -739,6 +753,15 @@ static int node_active_link_viewer_exec(bContext *C, wmOperator *UNUSED(op))
return OPERATOR_FINISHED;
}
static bool node_active_link_viewer_poll(bContext *C)
{
if (!ED_operator_node_editable(C)) {
return false;
}
SpaceNode *snode = CTX_wm_space_node(C);
return ED_node_is_compositor(snode) || ED_node_is_geometry(snode);
}
void NODE_OT_link_viewer(wmOperatorType *ot)
{
/* identifiers */
@ -748,7 +771,7 @@ void NODE_OT_link_viewer(wmOperatorType *ot)
/* api callbacks */
ot->exec = node_active_link_viewer_exec;
ot->poll = composite_node_editable;
ot->poll = node_active_link_viewer_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;

View File

@ -44,6 +44,7 @@
#include "ED_node.h" /* own include */
#include "ED_screen.h"
#include "ED_select_utils.h"
#include "ED_spreadsheet.h"
#include "ED_view3d.h"
#include "RNA_access.h"
@ -469,7 +470,7 @@ void node_select_single(bContext *C, bNode *node)
}
nodeSetSelected(node, true);
ED_node_set_active(bmain, snode->edittree, node, &active_texture_changed);
ED_node_set_active(bmain, snode, snode->edittree, node, &active_texture_changed);
ED_node_set_active_viewer_key(snode);
ED_node_sort(snode->edittree);
@ -606,12 +607,18 @@ static int node_mouse_select(bContext *C,
/* update node order */
if (ret_value != OPERATOR_CANCELLED) {
bool active_texture_changed = false;
bool viewer_node_changed = false;
if (node != nullptr && ret_value != OPERATOR_RUNNING_MODAL) {
ED_node_set_active(bmain, snode->edittree, node, &active_texture_changed);
viewer_node_changed = (node->flag & NODE_DO_OUTPUT) == 0 && node->type == GEO_NODE_VIEWER;
ED_node_set_active(bmain, snode, snode->edittree, node, &active_texture_changed);
}
else if (node != nullptr && node->type == GEO_NODE_VIEWER) {
ED_spreadsheet_context_paths_set_geometry_node(bmain, snode, node);
}
ED_node_set_active_viewer_key(snode);
ED_node_sort(snode->edittree);
if (active_texture_changed && has_workbench_in_texture_color(wm, scene, ob)) {
if ((active_texture_changed && has_workbench_in_texture_color(wm, scene, ob)) ||
viewer_node_changed) {
DEG_id_tag_update(&snode->edittree->id, ID_RECALC_COPY_ON_WRITE);
}

View File

@ -222,24 +222,11 @@ ID *ED_spreadsheet_get_current_id(const struct SpaceSpreadsheet *sspreadsheet)
static void update_pinned_context_path_if_outdated(const bContext *C)
{
SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C);
/* Currently, this only checks if the object has been deleted. In the future we can have a more
* sophisticated check for the entire context (including modifier and nodes). */
LISTBASE_FOREACH (SpreadsheetContext *, context, &sspreadsheet->context_path) {
if (context->type == SPREADSHEET_CONTEXT_OBJECT) {
SpreadsheetContextObject *object_context = (SpreadsheetContextObject *)context;
if (object_context->object == nullptr) {
ED_spreadsheet_context_path_clear(sspreadsheet);
break;
}
}
}
if (BLI_listbase_is_empty(&sspreadsheet->context_path)) {
Object *active_object = CTX_data_active_object(C);
if (active_object != nullptr) {
SpreadsheetContext *new_context = spreadsheet_context_new(SPREADSHEET_CONTEXT_OBJECT);
((SpreadsheetContextObject *)new_context)->object = active_object;
BLI_addtail(&sspreadsheet->context_path, new_context);
Main *bmain = CTX_data_main(C);
if (!ED_spreadsheet_context_path_exists(bmain, sspreadsheet)) {
ED_spreadsheet_context_path_guess(C, sspreadsheet);
if (ED_spreadsheet_context_path_update_tag(sspreadsheet)) {
ED_area_tag_redraw(CTX_wm_area(C));
}
}
@ -252,25 +239,12 @@ static void update_pinned_context_path_if_outdated(const bContext *C)
static void update_context_path_from_context(const bContext *C)
{
SpaceSpreadsheet *sspreadsheet = CTX_wm_space_spreadsheet(C);
Object *active_object = CTX_data_active_object(C);
if (active_object == nullptr) {
ED_spreadsheet_context_path_clear(sspreadsheet);
return;
}
if (!BLI_listbase_is_empty(&sspreadsheet->context_path)) {
SpreadsheetContext *root_context = (SpreadsheetContext *)sspreadsheet->context_path.first;
if (root_context->type == SPREADSHEET_CONTEXT_OBJECT) {
SpreadsheetContextObject *object_context = (SpreadsheetContextObject *)root_context;
if (object_context->object != active_object) {
ED_spreadsheet_context_path_clear(sspreadsheet);
}
if (!ED_spreadsheet_context_path_is_active(C, sspreadsheet)) {
ED_spreadsheet_context_path_guess(C, sspreadsheet);
if (ED_spreadsheet_context_path_update_tag(sspreadsheet)) {
ED_area_tag_redraw(CTX_wm_area(C));
}
}
if (BLI_listbase_is_empty(&sspreadsheet->context_path)) {
SpreadsheetContext *new_context = spreadsheet_context_new(SPREADSHEET_CONTEXT_OBJECT);
((SpreadsheetContextObject *)new_context)->object = active_object;
BLI_addtail(&sspreadsheet->context_path, new_context);
}
}
void spreadsheet_update_context_path(const bContext *C)

View File

@ -24,16 +24,28 @@
#include "BLI_utildefines.h"
#include "BLI_vector.hh"
#include "ED_screen.h"
#include "ED_spreadsheet.h"
#include "DEG_depsgraph.h"
#include "BKE_context.h"
#include "BKE_main.h"
#include "BKE_modifier.h"
#include "BKE_node.h"
#include "BKE_object.h"
#include "BKE_workspace.h"
#include "DNA_modifier_types.h"
#include "DNA_windowmanager_types.h"
#include "spreadsheet_context.hh"
using blender::IndexRange;
using blender::Span;
using blender::StringRef;
using blender::Vector;
namespace blender::ed::spreadsheet {
static SpreadsheetContextObject *spreadsheet_context_object_new()
@ -206,28 +218,30 @@ void spreadsheet_context_free(SpreadsheetContext *context)
/**
* Tag any data relevant to the spreadsheet's context for recalculation in order to collect
* information to display in the editor, which may be cached during evaluation.
* \return True when any data has been tagged for update.
*/
static void spreadsheet_context_update_tag(SpaceSpreadsheet *sspreadsheet)
static bool spreadsheet_context_update_tag(SpaceSpreadsheet *sspreadsheet)
{
using namespace blender;
Vector<const SpreadsheetContext *> context_path = sspreadsheet->context_path;
if (context_path.is_empty()) {
return;
return false;
}
if (context_path[0]->type != SPREADSHEET_CONTEXT_OBJECT) {
return;
return false;
}
SpreadsheetContextObject *object_context = (SpreadsheetContextObject *)context_path[0];
Object *object = object_context->object;
if (object == nullptr) {
return;
return false;
}
if (context_path.size() == 1) {
/* No need to reevaluate, when the final or original object is viewed. */
return;
return false;
}
DEG_id_tag_update(&object->id, ID_RECALC_GEOMETRY);
return true;
}
} // namespace blender::ed::spreadsheet
@ -250,9 +264,9 @@ void ED_spreadsheet_context_path_clear(struct SpaceSpreadsheet *sspreadsheet)
BLI_listbase_clear(&sspreadsheet->context_path);
}
void ED_spreadsheet_context_path_update_tag(SpaceSpreadsheet *sspreadsheet)
bool ED_spreadsheet_context_path_update_tag(SpaceSpreadsheet *sspreadsheet)
{
blender::ed::spreadsheet::spreadsheet_context_update_tag(sspreadsheet);
return blender::ed::spreadsheet::spreadsheet_context_update_tag(sspreadsheet);
}
uint64_t ED_spreadsheet_context_path_hash(const SpaceSpreadsheet *sspreadsheet)
@ -265,15 +279,32 @@ uint64_t ED_spreadsheet_context_path_hash(const SpaceSpreadsheet *sspreadsheet)
return BLI_hash_mm2a_end(&mm2);
}
void ED_spreadsheet_set_geometry_node_context(struct SpaceSpreadsheet *sspreadsheet,
struct SpaceNode *snode,
struct bNode *node)
void ED_spreadsheet_context_path_set_geometry_node(struct SpaceSpreadsheet *sspreadsheet,
struct SpaceNode *snode,
struct bNode *node)
{
using namespace blender::ed::spreadsheet;
ED_spreadsheet_context_path_clear(sspreadsheet);
Object *object = (Object *)snode->id;
/* Try to find the modifier the node tree belongs to. */
ModifierData *modifier = BKE_object_active_modifier(object);
if (modifier && modifier->type != eModifierType_Nodes) {
modifier = nullptr;
LISTBASE_FOREACH (ModifierData *, md, &object->modifiers) {
if (md->type == eModifierType_Nodes) {
NodesModifierData *nmd = (NodesModifierData *)md;
if (nmd->node_group == snode->nodetree) {
modifier = md;
break;
}
}
}
}
if (modifier == nullptr) {
return;
}
ED_spreadsheet_context_path_clear(sspreadsheet);
{
SpreadsheetContextObject *context = spreadsheet_context_object_new();
@ -302,5 +333,251 @@ void ED_spreadsheet_set_geometry_node_context(struct SpaceSpreadsheet *sspreadsh
BLI_addtail(&sspreadsheet->context_path, context);
}
sspreadsheet->object_eval_state = SPREADSHEET_OBJECT_EVAL_STATE_EVALUATED;
sspreadsheet->object_eval_state = SPREADSHEET_OBJECT_EVAL_STATE_VIEWER_NODE;
}
void ED_spreadsheet_context_paths_set_geometry_node(Main *bmain, SpaceNode *snode, bNode *node)
{
wmWindowManager *wm = (wmWindowManager *)bmain->wm.first;
if (wm == nullptr) {
return;
}
LISTBASE_FOREACH (wmWindow *, window, &wm->windows) {
bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook);
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
SpaceLink *sl = (SpaceLink *)area->spacedata.first;
if (sl->spacetype == SPACE_SPREADSHEET) {
SpaceSpreadsheet *sspreadsheet = (SpaceSpreadsheet *)sl;
if ((sspreadsheet->flag & SPREADSHEET_FLAG_PINNED) == 0) {
const uint64_t context_hash_before = ED_spreadsheet_context_path_hash(sspreadsheet);
ED_spreadsheet_context_path_set_geometry_node(sspreadsheet, snode, node);
const uint64_t context_hash_after = ED_spreadsheet_context_path_hash(sspreadsheet);
if (context_hash_before != context_hash_after) {
ED_spreadsheet_context_path_update_tag(sspreadsheet);
}
ED_area_tag_redraw(area);
}
}
}
}
}
void ED_spreadsheet_context_path_set_evaluated_object(SpaceSpreadsheet *sspreadsheet,
Object *object)
{
using namespace blender::ed::spreadsheet;
ED_spreadsheet_context_path_clear(sspreadsheet);
SpreadsheetContextObject *context = spreadsheet_context_object_new();
context->object = object;
BLI_addtail(&sspreadsheet->context_path, context);
}
void ED_spreadsheet_context_path_guess(const bContext *C, SpaceSpreadsheet *sspreadsheet)
{
ED_spreadsheet_context_path_clear(sspreadsheet);
Main *bmain = CTX_data_main(C);
wmWindowManager *wm = (wmWindowManager *)bmain->wm.first;
if (wm == nullptr) {
return;
}
if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_VIEWER_NODE) {
LISTBASE_FOREACH (wmWindow *, window, &wm->windows) {
bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook);
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
SpaceLink *sl = (SpaceLink *)area->spacedata.first;
if (sl->spacetype == SPACE_NODE) {
SpaceNode *snode = (SpaceNode *)sl;
if (snode->edittree != nullptr) {
if (snode->edittree->type == NTREE_GEOMETRY) {
LISTBASE_FOREACH (bNode *, node, &snode->edittree->nodes) {
if (node->type == GEO_NODE_VIEWER) {
if (node->flag & NODE_DO_OUTPUT) {
ED_spreadsheet_context_path_set_geometry_node(sspreadsheet, snode, node);
return;
}
}
}
}
}
}
}
}
}
Object *active_object = CTX_data_active_object(C);
if (active_object != nullptr) {
ED_spreadsheet_context_path_set_evaluated_object(sspreadsheet, active_object);
return;
}
}
bool ED_spreadsheet_context_path_is_active(const bContext *C, SpaceSpreadsheet *sspreadsheet)
{
Main *bmain = CTX_data_main(C);
wmWindowManager *wm = (wmWindowManager *)bmain->wm.first;
if (wm == nullptr) {
return false;
}
Vector<SpreadsheetContext *> context_path = sspreadsheet->context_path;
if (context_path.is_empty()) {
return false;
}
if (context_path[0]->type != SPREADSHEET_CONTEXT_OBJECT) {
return false;
}
Object *object = ((SpreadsheetContextObject *)context_path[0])->object;
if (object == nullptr) {
return false;
}
if (context_path.size() == 1) {
if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_VIEWER_NODE) {
return false;
}
Object *active_object = CTX_data_active_object(C);
return object == active_object;
}
if (sspreadsheet->object_eval_state != SPREADSHEET_OBJECT_EVAL_STATE_VIEWER_NODE) {
return false;
}
if (context_path[1]->type != SPREADSHEET_CONTEXT_MODIFIER) {
return false;
}
const char *modifier_name = ((SpreadsheetContextModifier *)context_path[1])->modifier_name;
const ModifierData *modifier = BKE_modifiers_findby_name(object, modifier_name);
if (modifier == nullptr) {
return false;
}
if (!(modifier->flag & eModifierFlag_Active)) {
return false;
}
if (modifier->type != eModifierType_Nodes) {
return false;
}
bNodeTree *root_node_tree = ((NodesModifierData *)modifier)->node_group;
if (root_node_tree == nullptr) {
return false;
}
const Span<SpreadsheetContext *> node_context_path = context_path.as_span().drop_front(2);
if (node_context_path.is_empty()) {
return false;
}
LISTBASE_FOREACH (wmWindow *, window, &wm->windows) {
bScreen *screen = BKE_workspace_active_screen_get(window->workspace_hook);
LISTBASE_FOREACH (ScrArea *, area, &screen->areabase) {
SpaceLink *sl = (SpaceLink *)area->spacedata.first;
if (sl->spacetype != SPACE_NODE) {
continue;
}
SpaceNode *snode = (SpaceNode *)sl;
if (snode->nodetree != root_node_tree) {
continue;
}
if (snode->id != &object->id) {
continue;
}
Vector<bNodeTreePath *> tree_path = snode->treepath;
if (node_context_path.size() != tree_path.size()) {
continue;
}
int valid_count = 0;
for (const int i : IndexRange(tree_path.size() - 1)) {
if (node_context_path[i]->type != SPREADSHEET_CONTEXT_NODE) {
break;
}
SpreadsheetContextNode *node_context = (SpreadsheetContextNode *)node_context_path[i];
if (!STREQ(node_context->node_name, tree_path[i]->node_name)) {
break;
}
valid_count++;
}
if (valid_count != tree_path.size() - 1) {
continue;
}
SpreadsheetContext *last_context = node_context_path.last();
if (last_context->type != SPREADSHEET_CONTEXT_NODE) {
return false;
}
const char *node_name = ((SpreadsheetContextNode *)last_context)->node_name;
bNode *node = nodeFindNodebyName(snode->edittree, node_name);
if (node == nullptr) {
return false;
}
if (node->type != GEO_NODE_VIEWER) {
return false;
}
if (!(node->flag & NODE_DO_OUTPUT)) {
return false;
}
return true;
}
}
return false;
}
bool ED_spreadsheet_context_path_exists(Main *UNUSED(bmain), SpaceSpreadsheet *sspreadsheet)
{
Vector<SpreadsheetContext *> context_path = sspreadsheet->context_path;
if (context_path.is_empty()) {
return false;
}
if (context_path[0]->type != SPREADSHEET_CONTEXT_OBJECT) {
return false;
}
Object *object = ((SpreadsheetContextObject *)context_path[0])->object;
if (object == nullptr) {
return false;
}
if (context_path.size() == 1) {
return true;
}
if (context_path[1]->type != SPREADSHEET_CONTEXT_MODIFIER) {
return false;
}
const char *modifier_name = ((SpreadsheetContextModifier *)context_path[1])->modifier_name;
const ModifierData *modifier = BKE_modifiers_findby_name(object, modifier_name);
if (modifier == nullptr) {
return false;
}
if (modifier->type != eModifierType_Nodes) {
return false;
}
bNodeTree *root_node_tree = ((NodesModifierData *)modifier)->node_group;
if (root_node_tree == nullptr) {
return false;
}
const Span<SpreadsheetContext *> node_context_path = context_path.as_span().drop_front(2);
if (node_context_path.is_empty()) {
return false;
}
bNodeTree *node_tree = root_node_tree;
for (const int i : node_context_path.index_range()) {
if (node_context_path[i]->type != SPREADSHEET_CONTEXT_NODE) {
return false;
}
const char *node_name = ((SpreadsheetContextNode *)node_context_path[i])->node_name;
bNode *node = nodeFindNodebyName(node_tree, node_name);
if (node == nullptr) {
return false;
}
if (node->type == GEO_NODE_VIEWER) {
if (i == node_context_path.index_range().last()) {
return true;
}
return false;
}
else if (node->id != nullptr) {
if (GS(node->id->name) != ID_NT) {
return false;
}
node_tree = (bNodeTree *)node->id;
}
else {
return false;
}
}
return false;
}

View File

@ -419,7 +419,7 @@ GeometrySet spreadsheet_get_display_geometry_set(const SpaceSpreadsheet *sspread
pointcloud_component.replace(pointcloud, GeometryOwnershipType::ReadOnly);
}
}
else if (sspreadsheet->object_eval_state == SPREADSHEET_OBJECT_EVAL_STATE_EVALUATED) {
else {
if (used_component_type == GEO_COMPONENT_TYPE_MESH && object_eval->mode == OB_MODE_EDIT) {
Mesh *mesh = BKE_modifier_get_evaluated_mesh_from_evaluated_object(object_eval, false);
if (mesh == nullptr) {

View File

@ -325,6 +325,7 @@ typedef struct bNode {
#define NODE_HIDDEN 8
#define NODE_ACTIVE 16
#define NODE_ACTIVE_ID 32
/* Used to indicate which group output node is used and which viewer node is active. */
#define NODE_DO_OUTPUT 64
#define __NODE_GROUP_EDIT 128 /* DEPRECATED */
/* free test flag, undefined */
@ -359,7 +360,7 @@ typedef struct bNode {
*/
#define NODE_DO_OUTPUT_RECALC (1 << 17)
/* A preview for the data in this node can be displayed in the spreadsheet editor. */
#define NODE_ACTIVE_PREVIEW (1 << 18)
#define __NODE_ACTIVE_PREVIEW (1 << 18) /* deprecated */
/* node->update */
/* XXX NODE_UPDATE is a generic update flag. More fine-grained updates

View File

@ -1922,6 +1922,7 @@ typedef enum eSpreadsheetFilterOperation {
typedef enum eSpaceSpreadsheet_ObjectEvalState {
SPREADSHEET_OBJECT_EVAL_STATE_EVALUATED = 0,
SPREADSHEET_OBJECT_EVAL_STATE_ORIGINAL = 1,
SPREADSHEET_OBJECT_EVAL_STATE_VIEWER_NODE = 2,
} eSpaceSpreadsheet_Context;
typedef enum eSpaceSpreadsheet_ContextType {

View File

@ -690,6 +690,7 @@ static void rna_Modifier_is_active_set(PointerRNA *ptr, bool value)
}
md->flag |= eModifierFlag_Active;
WM_main_add_notifier(NC_OBJECT | ND_MODIFIER, ptr->owner_id);
}
}

View File

@ -11320,12 +11320,6 @@ static void rna_def_node(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Show Texture", "Display node in viewport textured shading mode");
RNA_def_property_update(prop, 0, "rna_Node_update");
prop = RNA_def_property(srna, "active_preview", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", NODE_ACTIVE_PREVIEW);
RNA_def_property_ui_text(prop, "Active Preview", "Node is previewed in other editor");
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
RNA_def_property_update(prop, NC_NODE, NULL);
/* generic property update function */
func = RNA_def_function(srna, "socket_value_update", "rna_Node_socket_value_update");
RNA_def_function_ui_description(func, "Update after property changes");

View File

@ -1706,6 +1706,8 @@ static void rna_Object_active_modifier_set(PointerRNA *ptr, PointerRNA value, Re
Object *ob = (Object *)ptr->owner_id;
ModifierData *md = value.data;
WM_main_add_notifier(NC_OBJECT | ND_MODIFIER, ob);
if (RNA_pointer_is_null(&value)) {
BKE_object_modifier_set_active(ob, NULL);
return;

View File

@ -3177,11 +3177,9 @@ static void rna_spreadsheet_context_update(Main *UNUSED(bmain),
}
}
static void rna_spreadsheet_set_geometry_node_context(SpaceSpreadsheet *sspreadsheet,
SpaceNode *snode,
bNode *node)
static void rna_SpaceSpreadsheet_context_path_guess(SpaceSpreadsheet *sspreadsheet, bContext *C)
{
ED_spreadsheet_set_geometry_node_context(sspreadsheet, snode, node);
ED_spreadsheet_context_path_guess(C, sspreadsheet);
ED_spreadsheet_context_path_update_tag(sspreadsheet);
WM_main_add_notifier(NC_SPACE | ND_SPACE_SPREADSHEET, NULL);
}
@ -7646,13 +7644,16 @@ static void rna_def_space_spreadsheet_context_path(BlenderRNA *brna, PropertyRNA
func = RNA_def_function(srna, "clear", "rna_SpaceSpreadsheet_context_path_clear");
RNA_def_function_ui_description(func, "Clear entire context path");
func = RNA_def_function(srna, "guess", "rna_SpaceSpreadsheet_context_path_guess");
RNA_def_function_ui_description(func, "Guess the context path from the current context");
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
}
static void rna_def_space_spreadsheet(BlenderRNA *brna)
{
PropertyRNA *prop, *parm;
PropertyRNA *prop;
StructRNA *srna;
FunctionRNA *func;
static const EnumPropertyItem geometry_component_type_items[] = {
{GEO_COMPONENT_TYPE_MESH,
@ -7689,6 +7690,11 @@ static void rna_def_space_spreadsheet(BlenderRNA *brna)
ICON_NONE,
"Original",
"Use data from original object without any modifiers applied"},
{SPREADSHEET_OBJECT_EVAL_STATE_VIEWER_NODE,
"VIEWER_NODE",
ICON_NONE,
"Viewer Node",
"Use intermediate data from viewer node"},
{0, NULL, 0, NULL, NULL},
};
@ -7764,16 +7770,6 @@ static void rna_def_space_spreadsheet(BlenderRNA *brna)
RNA_def_property_struct_type(prop, "SpreadsheetRowFilter");
RNA_def_property_ui_text(prop, "Row Filters", "Filters to remove rows from the displayed data");
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_SPREADSHEET, NULL);
func = RNA_def_function(
srna, "set_geometry_node_context", "rna_spreadsheet_set_geometry_node_context");
RNA_def_function_ui_description(
func, "Update context_path to point to a specific node in a node editor");
parm = RNA_def_pointer(
func, "node_editor", "SpaceNodeEditor", "", "Editor to take the context from");
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
parm = RNA_def_pointer(func, "node", "Node", "", "");
RNA_def_parameter_flags(parm, 0, PARM_REQUIRED);
}
void RNA_def_space(BlenderRNA *brna)

View File

@ -768,22 +768,6 @@ static Vector<SpaceSpreadsheet *> find_spreadsheet_editors(Main *bmain)
using PreviewSocketMap = blender::MultiValueMap<DSocket, uint64_t>;
static DSocket try_find_preview_socket_in_node(const DNode node)
{
for (const SocketRef *socket : node->outputs()) {
if (socket->bsocket()->type == SOCK_GEOMETRY) {
return {node.context(), socket};
}
}
for (const SocketRef *socket : node->inputs()) {
if (socket->bsocket()->type == SOCK_GEOMETRY &&
(socket->bsocket()->flag & SOCK_MULTI_INPUT) == 0) {
return {node.context(), socket};
}
}
return {};
}
static DSocket try_get_socket_to_preview_for_spreadsheet(SpaceSpreadsheet *sspreadsheet,
NodesModifierData *nmd,
const ModifierEvalContext *ctx,
@ -839,7 +823,17 @@ static DSocket try_get_socket_to_preview_for_spreadsheet(SpaceSpreadsheet *sspre
const NodeTreeRef &tree_ref = context->tree();
for (const NodeRef *node_ref : tree_ref.nodes()) {
if (node_ref->name() == last_context->node_name) {
return try_find_preview_socket_in_node({context, node_ref});
const DNode viewer_node{context, node_ref};
DSocket socket_to_view;
viewer_node.input(0).foreach_origin_socket(
[&](const DSocket socket) { socket_to_view = socket; });
if (!socket_to_view) {
return {};
}
bNodeSocket *bsocket = socket_to_view->bsocket();
if (bsocket->type == SOCK_GEOMETRY && bsocket->flag != SOCK_MULTI_INPUT) {
return socket_to_view;
}
}
}
return {};
@ -975,6 +969,8 @@ static GeometrySet compute_geometry(const DerivedNodeTree &tree,
blender::modifiers::geometry_nodes::GeometryNodesEvaluationParams eval_params;
eval_params.input_values = group_inputs;
eval_params.output_sockets = group_outputs;
eval_params.force_compute_sockets.extend(preview_sockets.keys().begin(),
preview_sockets.keys().end());
eval_params.mf_by_node = &mf_by_node;
eval_params.modifier_ = nmd;
eval_params.depsgraph = ctx->depsgraph;

View File

@ -404,6 +404,9 @@ class GeometryNodesEvaluator {
for (const DInputSocket &socket : params_.output_sockets) {
nodes_to_check.push(socket.node());
}
for (const DSocket &socket : params_.force_compute_sockets) {
nodes_to_check.push(socket.node());
}
/* Use the local allocator because the states do not need to outlive the evaluator. */
LinearAllocator<> &allocator = local_allocators_.local();
while (!nodes_to_check.is_empty()) {
@ -501,7 +504,8 @@ class GeometryNodesEvaluator {
},
{});
if (output_state.potential_users == 0) {
/* If it does not have any potential users, it is unused. */
/* If it does not have any potential users, it is unused. It might become required again in
* `schedule_initial_nodes`. */
output_state.output_usage = ValueUsage::Unused;
}
}
@ -576,6 +580,20 @@ class GeometryNodesEvaluator {
this->set_input_required(locked_node, socket);
});
}
for (const DSocket socket : params_.force_compute_sockets) {
const DNode node = socket.node();
NodeState &node_state = this->get_node_state(node);
this->with_locked_node(node, node_state, [&](LockedNode &locked_node) {
if (socket->is_input()) {
this->set_input_required(locked_node, DInputSocket(socket));
}
else {
OutputState &output_state = node_state.outputs[socket->index()];
output_state.output_usage = ValueUsage::Required;
this->schedule_node(locked_node);
}
});
}
}
void schedule_node(LockedNode &locked_node)
@ -1124,10 +1142,14 @@ class GeometryNodesEvaluator {
this->with_locked_node(node, node_state, [&](LockedNode &locked_node) {
output_state.potential_users -= 1;
if (output_state.potential_users == 0) {
/* The output socket has no users anymore. */
output_state.output_usage = ValueUsage::Unused;
/* Schedule the origin node in case it wants to set its inputs as unused as well. */
this->schedule_node(locked_node);
/* The socket might be required even though the output is not used by other sockets. That
* can happen when the socket is forced to be computed. */
if (output_state.output_usage != ValueUsage::Required) {
/* The output socket has no users anymore. */
output_state.output_usage = ValueUsage::Unused;
/* Schedule the origin node in case it wants to set its inputs as unused as well. */
this->schedule_node(locked_node);
}
}
});
}

View File

@ -38,6 +38,12 @@ struct GeometryNodesEvaluationParams {
Map<DOutputSocket, GMutablePointer> input_values;
Vector<DInputSocket> output_sockets;
/* These sockets will be computed but are not part of the output. Their value can be retrieved in
* `log_socket_value_fn`. These sockets are not part of `output_sockets` because then the
* evaluator would have to keep the socket values in memory until the end, which might not be
* necessary in all cases. Sometimes `log_socket_value_fn` might just want to look at the value
* and then it can be freed. */
Vector<DSocket> force_compute_sockets;
nodes::MultiFunctionByNode *mf_by_node;
const NodesModifierData *modifier_;
Depsgraph *depsgraph;

View File

@ -206,6 +206,7 @@ set(SRC
geometry/nodes/node_geo_switch.cc
geometry/nodes/node_geo_transform.cc
geometry/nodes/node_geo_triangulate.cc
geometry/nodes/node_geo_viewer.cc
geometry/nodes/node_geo_volume_to_mesh.cc
geometry/node_geometry_exec.cc
geometry/node_geometry_tree.cc

View File

@ -95,6 +95,7 @@ void register_node_type_geo_subdivision_surface(void);
void register_node_type_geo_switch(void);
void register_node_type_geo_transform(void);
void register_node_type_geo_triangulate(void);
void register_node_type_geo_viewer(void);
void register_node_type_geo_volume_to_mesh(void);
#ifdef __cplusplus

View File

@ -333,6 +333,7 @@ DefNode(GeometryNode, GEO_NODE_SUBDIVISION_SURFACE, 0, "SUBDIVISION_SURFACE", Su
DefNode(GeometryNode, GEO_NODE_SWITCH, def_geo_switch, "SWITCH", Switch, "Switch", "")
DefNode(GeometryNode, GEO_NODE_TRANSFORM, 0, "TRANSFORM", Transform, "Transform", "")
DefNode(GeometryNode, GEO_NODE_TRIANGULATE, def_geo_triangulate, "TRIANGULATE", Triangulate, "Triangulate", "")
DefNode(GeometryNode, GEO_NODE_VIEWER, 0, "VIEWER", Viewer, "Viewer", "")
DefNode(GeometryNode, GEO_NODE_VOLUME_TO_MESH, def_geo_volume_to_mesh, "VOLUME_TO_MESH", VolumeToMesh, "Volume to Mesh", "")
/* undefine macros */

View File

@ -0,0 +1,31 @@
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "node_geometry_util.hh"
static bNodeSocketTemplate geo_node_viewer_in[] = {
{SOCK_GEOMETRY, N_("Geometry")},
{-1, ""},
};
void register_node_type_geo_viewer()
{
static bNodeType ntype;
geo_node_type_base(&ntype, GEO_NODE_VIEWER, "Viewer", NODE_CLASS_OUTPUT, 0);
node_type_socket_templates(&ntype, geo_node_viewer_in, nullptr);
nodeRegisterType(&ntype);
}