6
$\begingroup$

I have coded an Operator that tries to change the view to Top using bpy.ops.view3d.viewnumpad, and then the invoke knife_project() operator to select an area as projected by a Curve onto a mesh.

The knife_project() succeeds, and the view is indeed set to 'TOP' when my operator finishes, however, the projection vector used by the knife_project() operator is not respecting the bpy.ops.view3d.viewnumpad('TOP') call prior to the knife_project() call.

I can tell that whatever view happened to be showing at the time I invoked my Operator was used for the knife_project. I was hoping to be able to set the projection vector of the knife_project within my operator so it doesn't need to rely on interactive user input.

Is there a way to ensure that the view matrix used by knife_project() takes into account the bpy.ops.view3d command?

def execute(self, context): 
    bpy.ops.view3d.viewnumpad(type = 'TOP') 
    setupTestareaScene() # load scene, start in 'OBJECT' mode 
    # Operator selection 
    # bpy.ops.object.select_all(action='DESELECT') 
    bpy.data.objects[meshObj].select = True 
    bpy.data.objects[curveObj].select = True 
    bpy.context.scene.objects.active = bpy.data.objects[meshObj] 
    # Select target area with projectorCurve 
    bpy.ops.object.mode_set(mode='EDIT') 
    bpy.ops.mesh.knife_project()

Thanks, Jon

$\endgroup$
2
  • $\begingroup$ bpy.ops.view3d.viewnumpad(type = 'TOP')setupTestareaScene() # start in 'OBJECT' mode $\endgroup$ – Jonathan Schmid Dec 20 '15 at 0:20
  • $\begingroup$ def execute(self, context): bpy.ops.view3d.viewnumpad(type = 'TOP') setupTestareaScene() # load scene, start in 'OBJECT' mode # Operator selection # bpy.ops.object.select_all(action='DESELECT') bpy.data.objects[meshObj].select = True bpy.data.objects[curveObj].select = True bpy.context.scene.objects.active = bpy.data.objects[meshObj] # Select target area with projectorCurve bpy.ops.object.mode_set(mode='EDIT') bpy.ops.mesh.knife_project() $\endgroup$ – Jonathan Schmid Dec 20 '15 at 0:26
3
$\begingroup$

To get this to work you need to do a few things:

  1. To override the context of the knife_project operator (which is necessary for running it outside the 3D view UI).
  2. To force the view to move to TOP without using the viewnumpad operator, which doesn't come into effect until your script is finished, even if you try to force redraw the scene.
  3. So to do this, you can rotate the view directly using the 3D view's region object (this is how you set the projection vector).
  4. Then you need to force redraw the scene in a way that's kind of risky and considered bad practice, but I know of no other way to do this (bpy.ops.wm.redraw_timer). If this is not used, the view angle isn't updated and the knife projects from the current camera view. EDIT: Well there is one slightly cumbersome way come to think of it, you can wrap this whole thing with a Modal Operator. Here's a nice example of this.

Here's how the code looks like (This code works only after you already have both objects selected, and the target object in edit mode):

import bpy
from mathutils import Euler

def view3d_find( return_area = False ):
    # returns first 3d view, normally we get from context
    for area in bpy.context.window.screen.areas:
        if area.type == 'VIEW_3D':
            v3d = area.spaces[0]
            rv3d = v3d.region_3d
            for region in area.regions:
                if region.type == 'WINDOW':
                    if return_area: return region, rv3d, v3d, area
                    return region, rv3d, v3d
    return None, None

region, rv3d, v3d, area = view3d_find(True)

# Define context override dictionary for overriding the knife_project operator's context
override = {
    'scene'            : bpy.context.scene,
    'region'           : region,
    'area'             : area,
    'space'            : v3d,
    'active_object'    : bpy.context.object,
    'window'           : bpy.context.window,
    'screen'           : bpy.context.screen,
    'selected_objects' : bpy.context.selected_objects,
    'edit_object'      : bpy.context.object
}

# Set view to TOP by directly rotating the 3D view region's view_rotation
rv3d.view_rotation = Euler( (0,0,0) ).to_quaternion()

# Force redraw the scene - this is considered unsavory but is necessary here
bpy.ops.wm.redraw_timer(type='DRAW_WIN_SWAP', iterations=1)

bpy.ops.mesh.knife_project(override)

DEMO

$\endgroup$
2
  • $\begingroup$ Excellent, thanks for the clear example. If the timer-redraw is not considered good practice, what are some of the issues it might cause? I'm guessing its best to have only one python thread issuing Operators to Blender. Thanks much, Jon. $\endgroup$ – Jonathan Schmid Dec 20 '15 at 16:28
  • $\begingroup$ Actually you can use a Modal Operator to avoid this as well. It's more work to write one but will avoid having to use the redraw timer. More about issues with using the redraw timer here: blender.org/api/blender_python_api_2_63_14/info_gotcha.html $\endgroup$ – TLousky Dec 20 '15 at 16:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.