Interactive Snapping 3d cursor placement

When placing the 3d cursor in the scene, I want to be able to snap it to vertices, grid points, edges or faces etc, .

Currently to place the cursor you have to click randomly in the 3d viewport, OR first create an object at the location you want and use Shift + s to snap the cursor to selected etc. This method works quite alright for some cases, but it is backwards for me.

I know I can use, bpy.context.scene.cursor_location = (x, y, z).
The only problem with this is that I have to know the coordinates before hand, which undermines the purpose of the cursor for me, in such use cases as I will show below.

I searched and found bpy.ops.view3d.cursor3d() this is the command to interactively set the cursor location in the 3d viewport.

Is it possible (please show me how) to tweak that function in python to enable me to interactively snap to grids and vertices, (maybe faces and edges too) while I hover my mouse around the viewport, rather than clicking randomly in the 3d viewport, or having to use one of the shift + s modes of cursor snapping.

Blender 2.8’s new “set cursor button” seems to be leaning in that direction, but without the snapping.

In the long run I am trying to make an add-on to enable me interactively add objects(for a start) e. g

(below an example of how I think it might work, please pardon any syntax errors in my code sample below)


def place_cube():
    """ set 3d cursor location interactively and add a cube at cursor location """
    # here comes the interactive cursor placement  by snapping
    # ( for now it is not interactive, I need help here) 
    bpy.ops.view3d.cursor3d() 

    # standard add mesh cube function
    bpy.ops.mesh.primitive_cube_add(radius=1)

So in the example above, I have effectively killed two birds with one stone, saving my self the initial (or subsequent, as the case my be) step of “set cursor to center, or selection etc” before the “add cube” command.

I really hope I have made my explanation clear enough. Thanks

P. S I use blender for Architectural work, at times , adding and moving objects tends to be done in precise ways that would benefit from this kind of interactive cursor placement mode.

To set the cursor:

bpy.ops.view3d.cursor3d('INVOKE_DEFAULT')

To transform the cursor and have all the snapping benefits as regular transform:

bpy.ops.transform.translate('INVOKE_DEFAULT', cursor_transform=True)

Both require to be executed from viewport.

Not sure if you’re aware, the 3d cursor also exists as an active tool in 2.8 and supports all the snapping features. What it doesn’t support, however, is being a one-shot hotkey which lets you quickly go back to whichever tool you were using previously. Executing the lines above in succession would allow that, if they were wrapped into an operator.

Thanks for your reply. This is just what I was looking for. About the 3d cursor in blender 2.8, I have not spent any reasonable amount of time on blender 2.8, will definitely check it out.

I should probably have added that the operator keyword argument: cursor_transform is something they added in 2.8 and isn’t available in 2.79.

If you’re still bound by 2.79 for some reason, you could take a look at the enhanced 3d cursor addon which comes by default (but not activated) in blender 2.79. It essentially re-creates the snapping framework for the 3d cursor and comes with some decent visual aids. A very elaborate addon.

I never knew about the enhanced 3d cursor add-on, I just tried it, it’s really handy, I really like the 3d cursor bookmarks feature, thanks.

1 Like

I got blender 2.8 beta on my office computer, and I have seen the cursor transform feature. (I use 2.79 on my home computer which does not have an opengl 3.3 compatible graphics card).

Anyway, now on 2.8, I am experimenting with the possibility of combining the cursor transform operation with the primitive_cube_add() operation.

Please see my attempt below.

The issue I am having is that once I invoke my “place cube” operator", The cube is added before I enter into the cursor transform mode. Whereas, what I would like to achieve is to transform the cursor to a desired location and then place the cube at that location.


bl_info = {
    "name": "Place cube",
    "category": "Object",
    "author": "Jaocek",
    "location": " View3d > Tools > place cube "  
}

import bpy


class place_cube(bpy.types.Operator):
    """ interactively set 3d_cursor location and add a cube at that location"""
    bl_idname = "object.place_cube"
    bl_label = "place cube"
    bl_options = {'REGISTER', 'UNDO'}
    
    def execute(self, context):
        bpy.ops.transform.translate('INVOKE_DEFAULT', cursor_transform=True) # enter cursor transform mode
        
        # how can I get the cursor transform to finish executing before the primitive_cube_add on the next line
        
        bpy.ops.mesh.primitive_cube_add(size=2) # add cube 
        
        
        return {'FINISHED'}
    
def register():
    bpy.utils.register_class(place_cube)
        
def unregister():
    bpy.utils.unregister_class(place_cube)
        
if __name__ == "__main__":
    register()

Thanks for your time.

PS.
I have very little experience with blender add-on development and programming in general.

For a strictly sequential execution, the operators need to be wrapped in a bpy.types.Macro class, which is a special macro container for executing modal operators after one another.

The class itself is empty (see below) with only the bl_ meta data filled out. Then, as you register it like a normal operator class, you also define its sub-operators in the sequence they are to be executed.

Try this:

import bpy
from bpy.types import Macro

class OBJECT_OT_place_cube(Macro):
    """ interactively set 3d_cursor location and add a cube at that location"""
    bl_idname = "object.place_cube"
    bl_label = "place cube"
    bl_options = {'REGISTER', 'UNDO'}
    

def register():
    bpy.utils.register_class(OBJECT_OT_place_cube)

    cls = OBJECT_OT_place_cube
    op = cls.define("VIEW3D_OT_cursor3d")

    op = cls.define("TRANSFORM_OT_translate")
    op.properties.cursor_transform = True
    
    op = cls.define("MESH_OT_primitive_cube_add")
    op.properties.size = 2
        
def unregister():
    bpy.utils.unregister_class(OBJECT_OT_place_cube)
        
if __name__ == "__main__":
    register()

I followed your suggestion, and Its now working. Thank you.

Glad it worked!