True grid snapping for nodes: This snaps nodes to the actual background grid instead...
authorLukas Toenne <lukas.toenne@googlemail.com>
Wed, 6 Nov 2013 17:46:32 +0000 (17:46 +0000)
committerLukas Toenne <lukas.toenne@googlemail.com>
Wed, 6 Nov 2013 17:46:32 +0000 (17:46 +0000)
confusion, grid snap is now the default as it seems to be the most wanted and easy to use mode.

Absolute grid snapping happens in a somewhat generic function 'applyGridAbsolute', which could also be used for objects and other transforms later on. It is conceptually similar to the 'project' snapping
option, in that it calculates a delta vector for each element on top of the overall transform, which places each node on the grid.

Node transform now uses the top-left node corner for TransformData->loc. The transform center is still the average of node centers, so that scaling and rotation works nicely.

snapGrid*** functions have been renamed to snapGridIncrement*** to distinguish better between incremental and absolute grid snapping.

12 files changed:
release/scripts/startup/bl_ui/space_node.py
source/blender/blenkernel/intern/scene.c
source/blender/blenloader/intern/readfile.c
source/blender/editors/include/ED_node.h
source/blender/editors/space_node/node_draw.c
source/blender/editors/transform/transform.c
source/blender/editors/transform/transform.h
source/blender/editors/transform/transform_constraints.c
source/blender/editors/transform/transform_conversions.c
source/blender/editors/transform/transform_snap.c
source/blender/makesdna/DNA_scene_types.h
source/blender/makesrna/intern/rna_scene.c

index 1721eacd8ac2a7fdf9529f1916dc1d60d1b75182..b9aa3feb60767609ed46a66bbc410a33339a806d 100644 (file)
@@ -105,7 +105,7 @@ class NODE_HT_header(Header):
         row = layout.row(align=True)
         row.prop(toolsettings, "use_snap", text="")
         row.prop(toolsettings, "snap_node_element", text="", icon_only=True)
-        if toolsettings.snap_node_element != 'INCREMENT':
+        if toolsettings.snap_node_element != 'GRID':
             row.prop(toolsettings, "snap_target", text="")
 
         row = layout.row(align=True)
index 9356536557a47cbeb782939ec3cf85519a86430f..05e1ec82392a8704c439743bf4aa93f66ad11669 100644 (file)
@@ -504,6 +504,8 @@ Scene *BKE_scene_add(Main *bmain, const char *name)
        sce->toolsettings->normalsize = 0.1;
        sce->toolsettings->autokey_mode = U.autokey_mode;
 
+       sce->toolsettings->snap_node_mode = SCE_SNAP_MODE_GRID;
+
        sce->toolsettings->skgen_resolution = 100;
        sce->toolsettings->skgen_threshold_internal     = 0.01f;
        sce->toolsettings->skgen_threshold_external     = 0.01f;
index aaf646b70bd8f2da8024f2b83ac27c1fb4d1a7ad..48c61c7db6b2d8c06fa597f18553cbde302db71b 100644 (file)
@@ -9818,6 +9818,12 @@ static void do_versions(FileData *fd, Library *lib, Main *main)
                                scene->gm.matmode = GAME_MAT_MULTITEX;
                        }
                }
+
+               /* 'Increment' mode disabled for nodes, use true grid snapping instead */
+               for (scene = main->scene.first; scene; scene = scene->id.next) {
+                       if (scene->toolsettings->snap_node_mode == SCE_SNAP_MODE_INCREMENT)
+                               scene->toolsettings->snap_node_mode = SCE_SNAP_MODE_GRID;
+               }
        }
 
        /* WATCH IT!!!: pointers from libdata have not been converted yet here! */
index 2f16d84aed017bcde8f1920609591841ff1cd22f..de83df9cc05017cc168f8e290d06d65f1c6fab11 100644 (file)
@@ -54,6 +54,8 @@ typedef enum {
        NODE_RIGHT  = 8
 } NodeBorder;
 
+#define NODE_GRID_STEPS     5
+
 /* space_node.c */
 int ED_node_tree_path_length(struct SpaceNode *snode);
 void ED_node_tree_path_get(struct SpaceNode *snode, char *value);
@@ -81,6 +83,7 @@ void ED_node_tree_update(const struct bContext *C);
 void ED_node_tag_update_id(struct ID *id);
 void ED_node_tag_update_nodetree(struct Main *bmain, struct bNodeTree *ntree);
 void ED_node_sort(struct bNodeTree *ntree);
+float ED_node_grid_size(void);
 
 /* node_relationships.c */
 void ED_node_link_intersect_test(struct ScrArea *sa, int test);
index 65eb75f85231ca2a9103398682a59d58810a480a..e031e056cd397447f5c2d470cfd971a7d108c785 100644 (file)
 /* XXX interface.h */
 extern void ui_dropshadow(const rctf *rct, float radius, float aspect, float alpha, int select);
 
+float ED_node_grid_size(void)
+{
+       return U.widget_unit;
+}
+
 void ED_node_tree_update(const bContext *C)
 {
        SpaceNode *snode = CTX_wm_space_node(C);
@@ -1325,7 +1330,7 @@ void drawnodespace(const bContext *C, ARegion *ar)
                        snode_setup_v2d(snode, ar, center);
                        
                        /* grid, uses theme color based on node path depth */
-                       UI_view2d_multi_grid_draw(v2d, (depth > 0 ? TH_NODE_GROUP : TH_BACK), U.widget_unit, 5, 2);
+                       UI_view2d_multi_grid_draw(v2d, (depth > 0 ? TH_NODE_GROUP : TH_BACK), ED_node_grid_size(), NODE_GRID_STEPS, 2);
                        
                        /* backdrop */
                        draw_nodespace_back_pix(C, ar, snode, path->parent_key);
@@ -1350,7 +1355,7 @@ void drawnodespace(const bContext *C, ARegion *ar)
        }
        else {
                /* default grid */
-               UI_view2d_multi_grid_draw(v2d, TH_BACK, U.widget_unit, 5, 2);
+               UI_view2d_multi_grid_draw(v2d, TH_BACK, ED_node_grid_size(), NODE_GRID_STEPS, 2);
                
                /* backdrop */
                draw_nodespace_back_pix(C, ar, snode, NODE_INSTANCE_KEY_NONE);
index ab2732d4e8c7ce9726c8d5fe741e10e0cb058a5c..2caf04e3143f41c6422a6bc822a5a4e3e1c5ef6c 100644 (file)
@@ -82,6 +82,7 @@
 #include "ED_mesh.h"
 #include "ED_clip.h"
 #include "ED_mask.h"
+#include "ED_node.h"
 
 #include "WM_types.h"
 #include "WM_api.h"
@@ -2778,7 +2779,7 @@ static void Warp(TransInfo *t, const int UNUSED(mval[2]))
                const float radius_snap = 0.1f;
                const float snap_hack = (t->snap[1] * data->warp_init_dist) / radius_snap;
                values.scale *= snap_hack;
-               snapGrid(t, values.vector);
+               snapGridIncrement(t, values.vector);
                values.scale /= snap_hack;
        }
 #endif
@@ -2950,7 +2951,7 @@ static void applyShear(TransInfo *t, const int UNUSED(mval[2]))
        
        value = t->values[0];
        
-       snapGrid(t, &value);
+       snapGridIncrement(t, &value);
        
        applyNumInput(&t->num, &value);
        
@@ -3238,7 +3239,7 @@ static void applyResize(TransInfo *t, const int mval[2])
        
        size[0] = size[1] = size[2] = ratio;
        
-       snapGrid(t, size);
+       snapGridIncrement(t, size);
        
        if (hasNumInput(&t->num)) {
                applyNumInput(&t->num, size);
@@ -3340,7 +3341,7 @@ static void applySkinResize(TransInfo *t, const int UNUSED(mval[2]))
        ratio = t->values[0];
        size[0] = size[1] = size[2] = ratio;
        
-       snapGrid(t, size);
+       snapGridIncrement(t, size);
        
        if (hasNumInput(&t->num)) {
                applyNumInput(&t->num, size);
@@ -3438,7 +3439,7 @@ static void applyToSphere(TransInfo *t, const int UNUSED(mval[2]))
        
        ratio = t->values[0];
        
-       snapGrid(t, &ratio);
+       snapGridIncrement(t, &ratio);
        
        applyNumInput(&t->num, &ratio);
        
@@ -3789,7 +3790,7 @@ static void applyRotation(TransInfo *t, const int UNUSED(mval[2]))
 
        final = t->values[0];
 
-       snapGrid(t, &final);
+       snapGridIncrement(t, &final);
 
        if ((t->con.mode & CON_APPLY) && t->con.applyRot) {
                t->con.applyRot(t, NULL, t->axis, NULL);
@@ -3902,7 +3903,7 @@ static void applyTrackball(TransInfo *t, const int UNUSED(mval[2]))
        phi[0] = t->values[0];
        phi[1] = t->values[1];
 
-       snapGrid(t, phi);
+       snapGridIncrement(t, phi);
 
        if (hasNumInput(&t->num)) {
                char c[NUM_STR_REP_LEN * 2];
@@ -3981,8 +3982,8 @@ static void initTranslation(TransInfo *t)
        }
        else if (t->spacetype == SPACE_NODE) {
                t->snap[0] = 0.0f;
-               t->snap[1] = 125.0f;
-               t->snap[2] = 25.0f;
+               t->snap[1] = ED_node_grid_size() * NODE_GRID_STEPS;
+               t->snap[2] = ED_node_grid_size();
        }
        else {
                t->snap[0] = 0.0f;
@@ -4162,7 +4163,7 @@ static void applyTranslation(TransInfo *t, const int UNUSED(mval[2]))
                headerTranslation(t, pvec, str);
        }
        else {
-               snapGrid(t, t->values);
+               snapGridIncrement(t, t->values);
                applyNumInput(&t->num, t->values);
                if (hasNumInput(&t->num)) {
                        removeAspectRatio(t, t->values);
@@ -4234,7 +4235,7 @@ static void applyShrinkFatten(TransInfo *t, const int UNUSED(mval[2]))
 
        distance = -t->values[0];
 
-       snapGrid(t, &distance);
+       snapGridIncrement(t, &distance);
 
        applyNumInput(&t->num, &distance);
 
@@ -4327,7 +4328,7 @@ static void applyTilt(TransInfo *t, const int UNUSED(mval[2]))
 
        final = t->values[0];
 
-       snapGrid(t, &final);
+       snapGridIncrement(t, &final);
 
        if (hasNumInput(&t->num)) {
                char c[NUM_STR_REP_LEN];
@@ -4402,7 +4403,7 @@ static void applyCurveShrinkFatten(TransInfo *t, const int UNUSED(mval[2]))
 
        ratio = t->values[0];
 
-       snapGrid(t, &ratio);
+       snapGridIncrement(t, &ratio);
 
        applyNumInput(&t->num, &ratio);
 
@@ -4475,7 +4476,7 @@ static void applyMaskShrinkFatten(TransInfo *t, const int UNUSED(mval[2]))
 
        ratio = t->values[0];
 
-       snapGrid(t, &ratio);
+       snapGridIncrement(t, &ratio);
 
        applyNumInput(&t->num, &ratio);
 
@@ -4566,7 +4567,7 @@ static void applyPushPull(TransInfo *t, const int UNUSED(mval[2]))
 
        distance = t->values[0];
 
-       snapGrid(t, &distance);
+       snapGridIncrement(t, &distance);
 
        applyNumInput(&t->num, &distance);
 
@@ -4658,7 +4659,7 @@ static void applyBevelWeight(TransInfo *t, const int UNUSED(mval[2]))
        weight -= 1.0f;
        if (weight > 1.0f) weight = 1.0f;
 
-       snapGrid(t, &weight);
+       snapGridIncrement(t, &weight);
 
        applyNumInput(&t->num, &weight);
 
@@ -4735,7 +4736,7 @@ static void applyCrease(TransInfo *t, const int UNUSED(mval[2]))
        crease -= 1.0f;
        if (crease > 1.0f) crease = 1.0f;
 
-       snapGrid(t, &crease);
+       snapGridIncrement(t, &crease);
 
        applyNumInput(&t->num, &crease);
 
@@ -4868,7 +4869,7 @@ static void applyBoneSize(TransInfo *t, const int mval[2])
        
        size[0] = size[1] = size[2] = ratio;
        
-       snapGrid(t, size);
+       snapGridIncrement(t, size);
        
        if (hasNumInput(&t->num)) {
                applyNumInput(&t->num, size);
@@ -4935,7 +4936,7 @@ static void applyBoneEnvelope(TransInfo *t, const int UNUSED(mval[2]))
        
        ratio = t->values[0];
        
-       snapGrid(t, &ratio);
+       snapGridIncrement(t, &ratio);
        
        applyNumInput(&t->num, &ratio);
        
@@ -6066,7 +6067,7 @@ static void applyEdgeSlide(TransInfo *t, const int UNUSED(mval[2]))
 
        final = t->values[0];
 
-       snapGrid(t, &final);
+       snapGridIncrement(t, &final);
 
        /* only do this so out of range values are not displayed */
        CLAMP(final, -1.0f, 1.0f);
@@ -6574,7 +6575,7 @@ static void applyVertSlide(TransInfo *t, const int UNUSED(mval[2]))
 
        final = t->values[0];
 
-       snapGrid(t, &final);
+       snapGridIncrement(t, &final);
 
        /* only do this so out of range values are not displayed */
        if (is_constrained) {
@@ -6643,7 +6644,7 @@ static void applyBoneRoll(TransInfo *t, const int UNUSED(mval[2]))
 
        final = t->values[0];
 
-       snapGrid(t, &final);
+       snapGridIncrement(t, &final);
 
        if (hasNumInput(&t->num)) {
                char c[NUM_STR_REP_LEN];
@@ -6716,7 +6717,7 @@ static void applyBakeTime(TransInfo *t, const int mval[2])
                time = (float)(t->center2d[0] - mval[0]) * fac;
        }
 
-       snapGrid(t, &time);
+       snapGridIncrement(t, &time);
 
        applyNumInput(&t->num, &time);
 
@@ -6983,7 +6984,7 @@ static void applySeqSlide(TransInfo *t, const int UNUSED(mval[2]))
                copy_v3_v3(t->values, tvec);
        }
        else {
-               snapGrid(t, t->values);
+               snapGridIncrement(t, t->values);
                applyNumInput(&t->num, t->values);
        }
 
index 73b8c47eb63692d15a372a6a38fc738c848d2039..ab5f034c83614b2077047fbebf14b55384a76010 100644 (file)
@@ -566,14 +566,15 @@ typedef enum {
        SMALL_GEARS     = 2
 } GearsType;
 
-void snapGrid(TransInfo *t, float *val);
-void snapGridAction(TransInfo *t, float *val, GearsType action);
+void snapGridIncrement(TransInfo *t, float *val);
+void snapGridIncrementAction(TransInfo *t, float *val, GearsType action);
 
 bool activeSnap(TransInfo *t);
 bool validSnap(TransInfo *t);
 
 void initSnapping(struct TransInfo *t, struct wmOperator *op);
 void applyProject(TransInfo *t);
+void applyGridAbsolute(TransInfo *t);
 void applySnapping(TransInfo *t, float *vec);
 void resetSnapping(TransInfo *t);
 eRedrawFlag handleSnapping(TransInfo *t, const struct wmEvent *event);
index 4497723185fe613ee9e510226485ac8f7b537152..8df289ff91714b47667af902b665f7062d5ab60f 100644 (file)
@@ -135,7 +135,7 @@ static void postConstraintChecks(TransInfo *t, float vec[3], float pvec[3])
 
        mul_m3_v3(t->con.imtx, vec);
 
-       snapGrid(t, vec);
+       snapGridIncrement(t, vec);
 
        if (t->num.flag & T_NULL_ONE) {
                if (!(t->con.mode & CON_AXIS0))
index cfc78a0e0efa8fe01d6581d48963605b2a4526f6..5332e843ea128d9a73864422bfd128a6df092f38 100644 (file)
@@ -2381,16 +2381,16 @@ void flushTransNodes(TransInfo *t)
        int a;
        TransData *td;
        TransData2D *td2d;
-
+       
+       applyGridAbsolute(t);
+       
        /* flush to 2d vector from internally used 3d vector */
        for (a = 0, td = t->data, td2d = t->data2d; a < t->total; a++, td++, td2d++) {
                bNode *node = td->extra;
-               float vec[2];
                
                /* weirdo - but the node system is a mix of free 2d elements and dpi sensitive UI */
-               add_v2_v2v2(vec, td2d->loc, td2d->ih1);
-               node->locx = vec[0] / UI_DPI_FAC;
-               node->locy = vec[1] / UI_DPI_FAC;
+               node->locx = td2d->loc[0] / UI_DPI_FAC;
+               node->locy = td2d->loc[1] / UI_DPI_FAC;
        }
        
        /* handle intersection with noodles */
@@ -5962,12 +5962,9 @@ static void createTransObject(bContext *C, TransInfo *t)
 /* transcribe given node into TransData2D for Transforming */
 static void NodeToTransData(TransData *td, TransData2D *td2d, bNode *node)
 {
-       /* hold original location */
-       float locxy[2] = {BLI_rctf_cent_x(&node->totr),
-                         BLI_rctf_cent_y(&node->totr)};
-       float nodeloc[2];
-       
-       copy_v2_v2(td2d->loc, locxy);
+       /* use top-left corner as the transform origin for nodes */
+       td2d->loc[0] = node->totr.xmin;
+       td2d->loc[1] = node->totr.ymax;
        td2d->loc[2] = 0.0f;
        td2d->loc2d = td2d->loc; /* current location */
 
@@ -5976,8 +5973,8 @@ static void NodeToTransData(TransData *td, TransData2D *td2d, bNode *node)
        td->loc = td2d->loc;
        copy_v3_v3(td->iloc, td->loc);
        /* use node center instead of origin (top-left corner) */
-       td->center[0] = locxy[0];
-       td->center[1] = locxy[1];
+       td->center[0] = BLI_rctf_cent_x(&node->totr);
+       td->center[1] = BLI_rctf_cent_y(&node->totr);
        td->center[2] = 0.0f;
 
        memset(td->axismtx, 0, sizeof(td->axismtx));
@@ -5991,11 +5988,6 @@ static void NodeToTransData(TransData *td, TransData2D *td2d, bNode *node)
        unit_m3(td->mtx);
        unit_m3(td->smtx);
 
-       /* weirdo - but the node system is a mix of free 2d elements and dpi sensitive UI */
-       nodeloc[0] = UI_DPI_FAC * node->locx;
-       nodeloc[1] = UI_DPI_FAC * node->locy;
-       sub_v2_v2v2(td2d->ih1, nodeloc, locxy);
-
        td->extra = node;
 }
 
index 354164ce36fe9eee728ecaa6156c985cc049c542..5ace7e3a73814ba9aab36f5d1fe656edc603f3e5 100644 (file)
@@ -344,6 +344,70 @@ void applyProject(TransInfo *t)
        }
 }
 
+void applyGridAbsolute(TransInfo *t)
+{
+       float grid_size = 0.0f;
+       GearsType grid_action;
+       TransData *td;
+       float imat[4][4];
+       int i;
+       
+       if (!(activeSnap(t) && (t->tsnap.mode == SCE_SNAP_MODE_GRID)))
+               return;
+       
+       grid_action = BIG_GEARS;
+       if (t->modifiers & MOD_PRECISION)
+               grid_action = SMALL_GEARS;
+       
+       switch (grid_action) {
+               case NO_GEARS: grid_size = t->snap[0]; break;
+               case BIG_GEARS: grid_size = t->snap[1]; break;
+               case SMALL_GEARS: grid_size = t->snap[2]; break;
+       }
+       /* early exit on unusable grid size */
+       if (grid_size == 0.0f)
+               return;
+       
+       if (t->flag & (T_EDIT | T_POSE)) {
+               Object *ob = t->obedit ? t->obedit : t->poseobj;
+               invert_m4_m4(imat, ob->obmat);
+       }
+       
+       for (i = 0, td = t->data; i < t->total; i++, td++) {
+               float iloc[3], loc[3], tvec[3];
+               
+               if (td->flag & TD_NOACTION)
+                       break;
+               
+               if (td->flag & TD_SKIP)
+                       continue;
+               
+               if ((t->flag & T_PROP_EDIT) && (td->factor == 0.0f))
+                       continue;
+               
+               copy_v3_v3(iloc, td->loc);
+               if (t->flag & (T_EDIT | T_POSE)) {
+                       Object *ob = t->obedit ? t->obedit : t->poseobj;
+                       mul_m4_v3(ob->obmat, iloc);
+               }
+               else if (t->flag & T_OBJECT) {
+                       td->ob->recalc |= OB_RECALC_OB | OB_RECALC_DATA | OB_RECALC_TIME;
+                       BKE_object_handle_update(t->scene, td->ob);
+                       copy_v3_v3(iloc, td->ob->obmat[3]);
+               }
+               
+               mul_v3_v3fl(loc, iloc, 1.0f/grid_size);
+               loc[0] = floorf(loc[0]);
+               loc[1] = floorf(loc[1]);
+               loc[2] = floorf(loc[2]);
+               mul_v3_fl(loc, grid_size);
+               
+               sub_v3_v3v3(tvec, loc, iloc);
+               mul_m3_v3(td->smtx, tvec);
+               add_v3_v3(td->loc, tvec);
+       }
+}
+
 void applySnapping(TransInfo *t, float *vec)
 {
        /* project is not applied this way */
@@ -818,7 +882,7 @@ static float ResizeBetween(TransInfo *t, float p1[3], float p2[3])
 
 static void UNUSED_FUNCTION(CalcSnapGrid) (TransInfo *t, float *UNUSED(vec))
 {
-       snapGridAction(t, t->tsnap.snapPoint, BIG_GEARS);
+       snapGridIncrementAction(t, t->tsnap.snapPoint, BIG_GEARS);
 }
 
 static void CalcSnapGeometry(TransInfo *t, float *UNUSED(vec))
@@ -2169,10 +2233,10 @@ bool snapNodesContext(bContext *C, const int mval[2], float *r_dist_px, float r_
 
 /*================================================================*/
 
-static void applyGrid(TransInfo *t, float *val, int max_index, float fac[3], GearsType action);
+static void applyGridIncrement(TransInfo *t, float *val, int max_index, float fac[3], GearsType action);
 
 
-void snapGridAction(TransInfo *t, float *val, GearsType action)
+void snapGridIncrementAction(TransInfo *t, float *val, GearsType action)
 {
        float fac[3];
 
@@ -2180,11 +2244,11 @@ void snapGridAction(TransInfo *t, float *val, GearsType action)
        fac[BIG_GEARS]   = t->snap[1];
        fac[SMALL_GEARS] = t->snap[2];
        
-       applyGrid(t, val, t->idx_max, fac, action);
+       applyGridIncrement(t, val, t->idx_max, fac, action);
 }
 
 
-void snapGrid(TransInfo *t, float *val)
+void snapGridIncrement(TransInfo *t, float *val)
 {
        GearsType action;
 
@@ -2198,17 +2262,17 @@ void snapGrid(TransInfo *t, float *val)
                action = SMALL_GEARS;
        }
 
-       snapGridAction(t, val, action);
+       snapGridIncrementAction(t, val, action);
 }
 
 
-static void applyGrid(TransInfo *t, float *val, int max_index, float fac[3], GearsType action)
+static void applyGridIncrement(TransInfo *t, float *val, int max_index, float fac[3], GearsType action)
 {
        int i;
        float asp[3] = {1.0f, 1.0f, 1.0f}; // TODO: Remove hard coded limit here (3)
 
        if (max_index > 2) {
-               printf("applyGrid: invalid index %d, clamping\n", max_index);
+               printf("applyGridIncrement: invalid index %d, clamping\n", max_index);
                max_index = 2;
        }
 
index 1ddc7d6ee063888428a187350caf1f32ddf42847..ca3284230728732744171c073fd57014a8022cd9 100644 (file)
@@ -1457,6 +1457,7 @@ typedef struct Scene {
 #define SCE_SNAP_MODE_NODE_X   5
 #define SCE_SNAP_MODE_NODE_Y   6
 #define SCE_SNAP_MODE_NODE_XY  7
+#define SCE_SNAP_MODE_GRID             8
 
 /* toolsettings->selectmode */
 #define SCE_SELECT_VERTEX      1 /* for mesh */
index fb0a26621f48e34e03575a68a11ea15604e3d26e..d31a29623a1e309b83f294915e39b375b440c88c 100644 (file)
@@ -145,7 +145,7 @@ EnumPropertyItem snap_element_items[] = {
 };
 
 EnumPropertyItem snap_node_element_items[] = {
-       {SCE_SNAP_MODE_INCREMENT, "INCREMENT", ICON_SNAP_INCREMENT, "Increment", "Snap to increments of grid"},
+       {SCE_SNAP_MODE_GRID, "GRID", ICON_SNAP_INCREMENT, "Grid", "Snap to grid"},
        {SCE_SNAP_MODE_NODE_X, "NODE_X", ICON_SNAP_EDGE, "Node X", "Snap to left/right node border"},
        {SCE_SNAP_MODE_NODE_Y, "NODE_Y", ICON_SNAP_EDGE, "Node Y", "Snap to top/bottom node border"},
        {SCE_SNAP_MODE_NODE_XY, "NODE_XY", ICON_SNAP_EDGE, "Node X / Y", "Snap to any node border"},