diff options
Diffstat (limited to 'source/blender/editors/space_node')
-rw-r--r-- | source/blender/editors/space_node/node_draw.cc | 54 | ||||
-rw-r--r-- | source/blender/editors/space_node/node_edit.cc | 44 | ||||
-rw-r--r-- | source/blender/editors/space_node/node_intern.hh | 1 | ||||
-rw-r--r-- | source/blender/editors/space_node/node_ops.cc | 2 | ||||
-rw-r--r-- | source/blender/editors/space_node/node_relationships.cc | 237 | ||||
-rw-r--r-- | source/blender/editors/space_node/node_select.cc | 20 | ||||
-rw-r--r-- | source/blender/editors/space_node/space_node.cc | 3 |
7 files changed, 240 insertions, 121 deletions
diff --git a/source/blender/editors/space_node/node_draw.cc b/source/blender/editors/space_node/node_draw.cc index 8f72d292740..29dad103dc3 100644 --- a/source/blender/editors/space_node/node_draw.cc +++ b/source/blender/editors/space_node/node_draw.cc @@ -60,6 +60,7 @@ #include "ED_node.h" #include "ED_screen.h" #include "ED_space_api.h" +#include "ED_viewer_path.hh" #include "UI_interface.hh" #include "UI_resources.h" @@ -95,6 +96,14 @@ extern void ui_draw_dropshadow( */ struct TreeDrawContext { /** + * Whether a viewer node is active in geometry nodes can not be determined by a flag on the node + * alone. That's because if the node group with the viewer is used multiple times, it's only + * active in one of these cases. + * The active node is cached here to avoid doing the more expensive check for every viewer node + * in the tree. + */ + const bNode *active_geometry_nodes_viewer = nullptr; + /** * Geometry nodes logs various data during execution. The logged data that corresponds to the * currently drawn node tree can be retrieved from the log below. */ @@ -639,15 +648,19 @@ static void node_update_hidden(bNode &node, uiBlock &block) node.totr.ymax); } -static int node_get_colorid(const bNode &node) +static int node_get_colorid(TreeDrawContext &tree_draw_ctx, const bNode &node) { const int nclass = (node.typeinfo->ui_class == nullptr) ? node.typeinfo->nclass : node.typeinfo->ui_class(&node); switch (nclass) { case NODE_CLASS_INPUT: return TH_NODE_INPUT; - case NODE_CLASS_OUTPUT: + case NODE_CLASS_OUTPUT: { + if (node.type == GEO_NODE_VIEWER) { + return &node == tree_draw_ctx.active_geometry_nodes_viewer ? TH_NODE_OUTPUT : TH_NODE; + } return (node.flag & NODE_DO_OUTPUT) ? TH_NODE_OUTPUT : TH_NODE; + } case NODE_CLASS_CONVERTER: return TH_NODE_CONVERTER; case NODE_CLASS_OP_COLOR: @@ -2055,7 +2068,7 @@ static void node_draw_basis(const bContext &C, const rctf &rct = node.totr; float color[4]; - int color_id = node_get_colorid(node); + int color_id = node_get_colorid(tree_draw_ctx, node); GPU_line_width(1.0f); @@ -2153,6 +2166,29 @@ static void node_draw_basis(const bContext &C, ""); UI_block_emboss_set(&block, UI_EMBOSS); } + if (node.type == GEO_NODE_VIEWER) { + const bool is_active = &node == tree_draw_ctx.active_geometry_nodes_viewer; + iconofs -= iconbutw; + UI_block_emboss_set(&block, UI_EMBOSS_NONE); + uiBut *but = uiDefIconBut(&block, + UI_BTYPE_BUT, + 0, + is_active ? ICON_HIDE_OFF : ICON_HIDE_ON, + iconofs, + rct.ymax - NODE_DY, + iconbutw, + UI_UNIT_Y, + nullptr, + 0, + 0, + 0, + 0, + ""); + /* Selection implicitly activates the node. */ + const char *operator_idname = is_active ? "NODE_OT_deactivate_viewer" : "NODE_OT_select"; + UI_but_func_set(but, node_toggle_button_cb, &node, (void *)operator_idname); + UI_block_emboss_set(&block, UI_EMBOSS); + } node_add_error_message_button(tree_draw_ctx, node, block, rct, iconofs); @@ -2341,7 +2377,7 @@ static void node_draw_hidden(const bContext &C, float scale; UI_view2d_scale_get(&v2d, &scale, nullptr); - const int color_id = node_get_colorid(node); + const int color_id = node_get_colorid(tree_draw_ctx, node); node_draw_extra_info_panel(tree_draw_ctx, snode, node, block); @@ -2698,7 +2734,8 @@ static void node_update_nodetree(const bContext &C, } } -static void frame_node_draw_label(const bNodeTree &ntree, +static void frame_node_draw_label(TreeDrawContext &tree_draw_ctx, + const bNodeTree &ntree, const bNode &node, const SpaceNode &snode) { @@ -2717,7 +2754,7 @@ static void frame_node_draw_label(const bNodeTree &ntree, BLF_size(fontid, MIN2(24.0f, font_size) * U.dpi_fac); /* title color */ - int color_id = node_get_colorid(node); + int color_id = node_get_colorid(tree_draw_ctx, node); uchar color[3]; UI_GetThemeColorBlendShade3ubv(TH_TEXT, color_id, 0.4f, 10, color); BLF_color3ubv(fontid, color); @@ -2831,7 +2868,7 @@ static void frame_node_draw(const bContext &C, } /* label and text */ - frame_node_draw_label(ntree, node, snode); + frame_node_draw_label(tree_draw_ctx, ntree, node, snode); node_draw_extra_info_panel(tree_draw_ctx, snode, node, block); @@ -3036,6 +3073,9 @@ static void draw_nodetree(const bContext &C, tree_draw_ctx.geo_tree_log->ensure_node_warnings(); tree_draw_ctx.geo_tree_log->ensure_node_run_time(); } + WorkSpace *workspace = CTX_wm_workspace(&C); + tree_draw_ctx.active_geometry_nodes_viewer = viewer_path::find_geometry_nodes_viewer( + workspace->viewer_path, *snode); } node_update_nodetree(C, tree_draw_ctx, ntree, nodes, blocks); diff --git a/source/blender/editors/space_node/node_edit.cc b/source/blender/editors/space_node/node_edit.cc index e446d5421e7..8135369f271 100644 --- a/source/blender/editors/space_node/node_edit.cc +++ b/source/blender/editors/space_node/node_edit.cc @@ -43,7 +43,7 @@ #include "ED_render.h" #include "ED_screen.h" #include "ED_select_utils.h" -#include "ED_spreadsheet.h" +#include "ED_viewer_path.hh" #include "RNA_access.h" #include "RNA_define.h" @@ -821,8 +821,8 @@ void ED_node_set_active( } } node->flag |= NODE_DO_OUTPUT; - ED_spreadsheet_context_paths_set_geometry_node(bmain, snode, node); } + blender::ed::viewer_path::activate_geometry_node(*bmain, *snode, *node); } } } @@ -1700,6 +1700,46 @@ void NODE_OT_preview_toggle(wmOperatorType *ot) ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; } +static int node_deactivate_viewer_exec(bContext *C, wmOperator *UNUSED(op)) +{ + SpaceNode &snode = *CTX_wm_space_node(C); + WorkSpace &workspace = *CTX_wm_workspace(C); + + bNode *active_viewer = viewer_path::find_geometry_nodes_viewer(workspace.viewer_path, snode); + + LISTBASE_FOREACH (bNode *, node, &snode.edittree->nodes) { + if (node->type != GEO_NODE_VIEWER) { + continue; + } + if (!(node->flag & SELECT)) { + continue; + } + if (node == active_viewer) { + node->flag &= ~NODE_DO_OUTPUT; + BKE_ntree_update_tag_node_property(snode.edittree, node); + } + } + + ED_node_tree_propagate_change(C, CTX_data_main(C), snode.edittree); + + return OPERATOR_FINISHED; +} + +void NODE_OT_deactivate_viewer(wmOperatorType *ot) +{ + /* identifiers */ + ot->name = "Deactivate Viewer Node"; + ot->description = "Deactivate selected viewer node in geometry nodes"; + ot->idname = __func__; + + /* callbacks */ + ot->exec = node_deactivate_viewer_exec; + ot->poll = ED_operator_node_active; + + /* flags */ + ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO; +} + static int node_options_toggle_exec(bContext *C, wmOperator *UNUSED(op)) { SpaceNode *snode = CTX_wm_space_node(C); diff --git a/source/blender/editors/space_node/node_intern.hh b/source/blender/editors/space_node/node_intern.hh index 70ac0e48d01..50c03489027 100644 --- a/source/blender/editors/space_node/node_intern.hh +++ b/source/blender/editors/space_node/node_intern.hh @@ -322,6 +322,7 @@ void NODE_OT_hide_socket_toggle(wmOperatorType *ot); void NODE_OT_preview_toggle(wmOperatorType *ot); void NODE_OT_options_toggle(wmOperatorType *ot); void NODE_OT_node_copy_color(wmOperatorType *ot); +void NODE_OT_deactivate_viewer(wmOperatorType *ot); void NODE_OT_read_viewlayers(wmOperatorType *ot); void NODE_OT_render_changed(wmOperatorType *ot); diff --git a/source/blender/editors/space_node/node_ops.cc b/source/blender/editors/space_node/node_ops.cc index f02c019359d..6c52dae5b86 100644 --- a/source/blender/editors/space_node/node_ops.cc +++ b/source/blender/editors/space_node/node_ops.cc @@ -44,6 +44,7 @@ void node_operatortypes() WM_operatortype_append(NODE_OT_options_toggle); WM_operatortype_append(NODE_OT_hide_socket_toggle); WM_operatortype_append(NODE_OT_node_copy_color); + WM_operatortype_append(NODE_OT_deactivate_viewer); WM_operatortype_append(NODE_OT_duplicate); WM_operatortype_append(NODE_OT_delete); @@ -135,6 +136,7 @@ void ED_operatormacros_node() mot = WM_operatortype_macro_define(ot, "NODE_OT_select"); RNA_boolean_set(mot->ptr, "extend", false); RNA_boolean_set(mot->ptr, "socket_select", true); + RNA_boolean_set(mot->ptr, "clear_viewer", true); WM_operatortype_macro_define(ot, "NODE_OT_link_viewer"); ot = WM_operatortype_append_macro("NODE_OT_translate_attach", diff --git a/source/blender/editors/space_node/node_relationships.cc b/source/blender/editors/space_node/node_relationships.cc index f597bf20b55..84cef798907 100644 --- a/source/blender/editors/space_node/node_relationships.cc +++ b/source/blender/editors/space_node/node_relationships.cc @@ -27,8 +27,8 @@ #include "ED_render.h" #include "ED_screen.h" #include "ED_space_api.h" -#include "ED_spreadsheet.h" #include "ED_util.h" +#include "ED_viewer_path.hh" #include "RNA_access.h" #include "RNA_define.h" @@ -495,17 +495,6 @@ static bool is_viewer_node(const bNode &node) return ELEM(node.type, CMP_NODE_VIEWER, CMP_NODE_SPLITVIEWER, GEO_NODE_VIEWER); } -static Vector<const bNode *> find_viewer_nodes(const bNodeTree &tree) -{ - Vector<const bNode *> viewer_nodes; - for (const bNode *node : tree.all_nodes()) { - if (is_viewer_node(*node)) { - viewer_nodes.append(node); - } - } - return viewer_nodes; -} - static bool is_viewer_socket_in_viewer(const bNodeSocket &socket) { const bNode &node = socket.owner_node(); @@ -516,18 +505,10 @@ static bool is_viewer_socket_in_viewer(const bNodeSocket &socket) return socket.index() == 0; } -static bool is_linked_to_viewer(const bNodeSocket &socket, const bNode &viewer_node) +static bool is_viewer_socket(const bNodeSocket &socket) { - for (const bNodeSocket *target_socket : socket.directly_linked_sockets()) { - if (&target_socket->owner_node() != &viewer_node) { - continue; - } - if (!target_socket->is_available()) { - continue; - } - if (is_viewer_socket_in_viewer(*target_socket)) { - return true; - } + if (is_viewer_node(socket.owner_node())) { + return is_viewer_socket_in_viewer(socket); } return false; } @@ -549,137 +530,165 @@ static void remove_links_to_unavailable_viewer_sockets(bNodeTree &btree, bNode & } } -static const bNode *get_existing_viewer(const bNodeTree &tree) +static bNodeSocket *determine_socket_to_view(bNode &node_to_view) { - Vector<const bNode *> viewer_nodes = find_viewer_nodes(tree); - - /* Check if there is already an active viewer node that should be used. */ - for (const bNode *viewer_node : viewer_nodes) { - if (viewer_node->flag & NODE_DO_OUTPUT) { - return viewer_node; + int last_linked_socket_index = -1; + for (bNodeSocket *socket : node_to_view.output_sockets()) { + if (!socket_can_be_viewed(*socket)) { + continue; } - } - - /* If no active but non-active viewers exist, make one active. */ - if (!viewer_nodes.is_empty()) { - const_cast<bNode *>(viewer_nodes[0])->flag |= NODE_DO_OUTPUT; - return viewer_nodes[0]; - } - return nullptr; -} - -static const bNodeSocket *find_output_socket_to_be_viewed(const bNode *active_viewer_node, - const bNode &node_to_view) -{ - /* Check if any of the output sockets is selected, which is the case when the user just clicked - * on the socket. */ - for (const bNodeSocket *output_socket : node_to_view.output_sockets()) { - if (output_socket->flag & SELECT) { - return output_socket; + for (bNodeLink *link : socket->directly_linked_links()) { + bNodeSocket &target_socket = *link->tosock; + bNode &target_node = *link->tonode; + if (is_viewer_socket(target_socket)) { + if (link->is_muted() || !(target_node.flag & NODE_DO_OUTPUT)) { + /* This socket is linked to a deactivated viewer, the viewer should be activated. */ + return socket; + } + last_linked_socket_index = socket->index(); + } } } - const bNodeSocket *last_socket_linked_to_viewer = nullptr; - if (active_viewer_node != nullptr) { - for (const bNodeSocket *output_socket : node_to_view.output_sockets()) { - if (!socket_can_be_viewed(*output_socket)) { - continue; - } - if (is_linked_to_viewer(*output_socket, *active_viewer_node)) { - last_socket_linked_to_viewer = output_socket; + if (last_linked_socket_index == -1) { + /* Returnt he first socket that can be viewed. */ + for (bNodeSocket *socket : node_to_view.output_sockets()) { + if (socket_can_be_viewed(*socket)) { + return socket; } } + return nullptr; } - if (last_socket_linked_to_viewer == nullptr) { - /* If no output is connected to a viewer, use the first output that can be viewed. */ - for (const bNodeSocket *output_socket : node_to_view.output_sockets()) { - if (socket_can_be_viewed(*output_socket)) { - return output_socket; - } + + /* Pick the next socket to be linked to the viewer. */ + const int tot_outputs = node_to_view.output_sockets().size(); + for (const int offset : IndexRange(1, tot_outputs)) { + const int index = (last_linked_socket_index + offset) % tot_outputs; + bNodeSocket &output_socket = node_to_view.output_socket(index); + if (!socket_can_be_viewed(output_socket)) { + continue; } - } - else { - /* Pick the next socket to be linked to the viewer. */ - const int tot_outputs = node_to_view.output_sockets().size(); - for (const int offset : IndexRange(1, tot_outputs - 1)) { - const int index = (last_socket_linked_to_viewer->index() + offset) % tot_outputs; - const bNodeSocket &output_socket = node_to_view.output_socket(index); - if (!socket_can_be_viewed(output_socket)) { + bool is_currently_viewed = false; + for (const bNodeLink *link : output_socket.directly_linked_links()) { + bNodeSocket &target_socket = *link->tosock; + bNode &target_node = *link->tonode; + if (!is_viewer_socket(target_socket)) { continue; } - if (is_linked_to_viewer(output_socket, *active_viewer_node)) { + if (link->is_muted()) { continue; } - return &output_socket; + if (!(target_node.flag & NODE_DO_OUTPUT)) { + continue; + } + is_currently_viewed = true; + break; } + if (is_currently_viewed) { + continue; + } + return &output_socket; } return nullptr; } -static int link_socket_to_viewer(const bContext &C, - bNode *viewer_bnode, - bNode &bnode_to_view, - bNodeSocket &bsocket_to_view) +static void finalize_viewer_link(const bContext &C, + SpaceNode &snode, + bNode &viewer_node, + bNodeLink &viewer_link) { - SpaceNode &snode = *CTX_wm_space_node(&C); - bNodeTree &btree = *snode.edittree; + Main *bmain = CTX_data_main(&C); + remove_links_to_unavailable_viewer_sockets(*snode.edittree, viewer_node); + viewer_link.flag &= ~NODE_LINK_MUTED; + viewer_node.flag &= ~NODE_MUTED; + viewer_node.flag |= NODE_DO_OUTPUT; + if (snode.edittree->type == NTREE_GEOMETRY) { + viewer_path::activate_geometry_node(*bmain, snode, viewer_node); + } + ED_node_tree_propagate_change(&C, bmain, snode.edittree); +} + +static int view_socket(const bContext &C, + SpaceNode &snode, + bNodeTree &btree, + bNode &bnode_to_view, + bNodeSocket &bsocket_to_view) +{ + bNode *viewer_node = nullptr; + /* Try to find a viewer that is already active. */ + LISTBASE_FOREACH (bNode *, node, &btree.nodes) { + if (is_viewer_node(*node)) { + if (node->flag & NODE_DO_OUTPUT) { + viewer_node = node; + break; + } + } + } - if (viewer_bnode == nullptr) { - /* Create a new viewer node if none exists. */ + /* Try to reactivate existing viewer connection. */ + for (bNodeLink *link : bsocket_to_view.directly_linked_links()) { + bNodeSocket &target_socket = *link->tosock; + bNode &target_node = *link->tonode; + if (is_viewer_socket(target_socket) && ELEM(viewer_node, nullptr, &target_node)) { + finalize_viewer_link(C, snode, target_node, *link); + return OPERATOR_FINISHED; + } + } + + if (viewer_node == nullptr) { + LISTBASE_FOREACH (bNode *, node, &btree.nodes) { + if (is_viewer_node(*node)) { + viewer_node = node; + break; + } + } + } + if (viewer_node == nullptr) { const int viewer_type = get_default_viewer_type(&C); const float2 location{bsocket_to_view.locx / UI_DPI_FAC + 100, bsocket_to_view.locy / UI_DPI_FAC}; - viewer_bnode = add_static_node(C, viewer_type, location); - if (viewer_bnode == nullptr) { - return OPERATOR_CANCELLED; - } + viewer_node = add_static_node(C, viewer_type, location); } - bNodeSocket *viewer_bsocket = node_link_viewer_get_socket(btree, *viewer_bnode, bsocket_to_view); + bNodeSocket *viewer_bsocket = node_link_viewer_get_socket(btree, *viewer_node, bsocket_to_view); if (viewer_bsocket == nullptr) { return OPERATOR_CANCELLED; } - - bNodeLink *link_to_change = nullptr; - LISTBASE_FOREACH (bNodeLink *, link, &btree.links) { + bNodeLink *viewer_link = nullptr; + LISTBASE_FOREACH_MUTABLE (bNodeLink *, link, &btree.links) { if (link->tosock == viewer_bsocket) { - link_to_change = link; + viewer_link = link; break; } } - - if (link_to_change == nullptr) { - nodeAddLink(&btree, &bnode_to_view, &bsocket_to_view, viewer_bnode, viewer_bsocket); + if (viewer_link == nullptr) { + viewer_link = nodeAddLink( + &btree, &bnode_to_view, &bsocket_to_view, viewer_node, viewer_bsocket); } else { - link_to_change->fromnode = &bnode_to_view; - link_to_change->fromsock = &bsocket_to_view; + viewer_link->fromnode = &bnode_to_view; + viewer_link->fromsock = &bsocket_to_view; BKE_ntree_update_tag_link_changed(&btree); } - - remove_links_to_unavailable_viewer_sockets(btree, *viewer_bnode); - - if (btree.type == NTREE_GEOMETRY) { - ED_spreadsheet_context_paths_set_geometry_node(CTX_data_main(&C), &snode, viewer_bnode); - } - - ED_node_tree_propagate_change(&C, CTX_data_main(&C), &btree); - return OPERATOR_FINISHED; + finalize_viewer_link(C, snode, *viewer_node, *viewer_link); + return OPERATOR_CANCELLED; } -static int node_link_viewer(const bContext &C, bNode &bnode_to_view) +static int node_link_viewer(const bContext &C, bNode &bnode_to_view, bNodeSocket *bsocket_to_view) { SpaceNode &snode = *CTX_wm_space_node(&C); bNodeTree *btree = snode.edittree; btree->ensure_topology_cache(); - bNode *active_viewer_bnode = const_cast<bNode *>(get_existing_viewer(*btree)); - bNodeSocket *bsocket_to_view = const_cast<bNodeSocket *>( - find_output_socket_to_be_viewed(active_viewer_bnode, bnode_to_view)); if (bsocket_to_view == nullptr) { - return OPERATOR_FINISHED; + bsocket_to_view = determine_socket_to_view(bnode_to_view); } - return link_socket_to_viewer(C, active_viewer_bnode, bnode_to_view, *bsocket_to_view); + + if (bsocket_to_view == nullptr) { + return OPERATOR_CANCELLED; + } + + return view_socket(C, snode, *btree, bnode_to_view, *bsocket_to_view); } /** \} */ @@ -701,7 +710,15 @@ static int node_active_link_viewer_exec(bContext *C, wmOperator *UNUSED(op)) ED_preview_kill_jobs(CTX_wm_manager(C), CTX_data_main(C)); - if (viewer_linking::node_link_viewer(*C, *node) == OPERATOR_CANCELLED) { + bNodeSocket *socket_to_view = nullptr; + LISTBASE_FOREACH (bNodeSocket *, socket, &node->outputs) { + if (socket->flag & SELECT) { + socket_to_view = socket; + break; + } + } + + if (viewer_linking::node_link_viewer(*C, *node, socket_to_view) == OPERATOR_CANCELLED) { return OPERATOR_CANCELLED; } diff --git a/source/blender/editors/space_node/node_select.cc b/source/blender/editors/space_node/node_select.cc index 1b47316ebd0..c11ae323115 100644 --- a/source/blender/editors/space_node/node_select.cc +++ b/source/blender/editors/space_node/node_select.cc @@ -23,13 +23,14 @@ #include "BKE_main.h" #include "BKE_node.h" #include "BKE_node_runtime.hh" +#include "BKE_node_tree_update.h" #include "BKE_workspace.h" #include "ED_node.h" /* own include */ #include "ED_screen.h" #include "ED_select_utils.h" -#include "ED_spreadsheet.h" #include "ED_view3d.h" +#include "ED_viewer_path.hh" #include "RNA_access.h" #include "RNA_define.h" @@ -644,6 +645,15 @@ static bool node_mouse_select(bContext *C, } } + if (RNA_boolean_get(op->ptr, "clear_viewer")) { + if (node == nullptr) { + /* Disable existing active viewer. */ + WorkSpace *workspace = CTX_wm_workspace(C); + BKE_viewer_path_clear(&workspace->viewer_path); + WM_event_add_notifier(C, NC_VIEWER_PATH, nullptr); + } + } + if (!(changed || found)) { return false; } @@ -655,7 +665,7 @@ static bool node_mouse_select(bContext *C, 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); + viewer_path::activate_geometry_node(bmain, snode, *node); } ED_node_set_active_viewer_key(&snode); node_sort(*snode.edittree); @@ -731,6 +741,12 @@ void NODE_OT_select(wmOperatorType *ot) RNA_def_property_flag(prop, PROP_HIDDEN); RNA_def_boolean(ot->srna, "socket_select", false, "Socket Select", ""); + + RNA_def_boolean(ot->srna, + "clear_viewer", + false, + "Clear Viewer", + "Deactivate geometry nodes viewer when clicking in empty space"); } /** \} */ diff --git a/source/blender/editors/space_node/space_node.cc b/source/blender/editors/space_node/space_node.cc index ea551b7488a..3e5cbf88e15 100644 --- a/source/blender/editors/space_node/space_node.cc +++ b/source/blender/editors/space_node/space_node.cc @@ -823,6 +823,9 @@ static void node_region_listener(const wmRegionListenerParams *params) ED_region_tag_redraw(region); } break; + case NC_VIEWER_PATH: + ED_region_tag_redraw(region); + break; } } |