3
$\begingroup$

I'm trying to write a script to create some objects along and around a curve object, set up for a generative modeling thing, to make randomized "tentacles" out of paths.

Anyways! I'm approaching this by creating an empty at the root of any selected curve, putting a "follow path" constraint on the empty, targeting the curve, animating the path, and then creating objects relative to the empty's location and orientation on different frames, staggering some objects like that.

What I've got so far looks like this:

for curve in operable:
    print("Doing curve")
    rootLoc = curve.data.splines[0].points[0].co
    emptyLoc = (curve.matrix_world * rootLoc)
    bpy.ops.object.empty_add(type='ARROWS')
    bpy.context.object.name = "empty_" + curve.name
    bpy.ops.object.constraint_add(type='FOLLOW_PATH')
    bpy.context.object.constraints['Follow Path'].target = curve
    old_area = bpy.context.area.type
    bpy.context.area.type = 'PROPERTIES'
    bpy.context.space_data.context = 'CONSTRAINT'
    bpy.ops.constraint.followpath_path_animate(constraint='Follow Path', length=frame_steps)
    bpy.context.object.constraints['Follow Path'].use_curve_follow = True
    bpy.context.area.type = old_area
    bpy.data.objects['empty_' + curve.name].location = emptyLoc.xyz

However, in the "followpath_path_animate" line, I get an error from that function's "poll()" saying only that "the context is incorrect".

For one, if someone could tell me what context I need to be in and how to get there, that would be great.

It would also be great if someone could explain to me how in the world I'm supposed to figure out what context to be in to run these functions! You'd think this would be at least vaguely documented...

I found the code related to this function in the source code, found that this is the poll() function used:

static int edit_constraint_poll_generic(bContext *C, StructRNA *rna_type)
{
    PointerRNA ptr = CTX_data_pointer_get_type(C, "constraint", rna_type);
    Object *ob = (ptr.id.data) ? ptr.id.data : ED_object_active_context(C);

    if (!ptr.data) {
        CTX_wm_operator_poll_msg_set(C, "Context missing 'constraint'");
        return 0;
    }

    if (!ob) {
        CTX_wm_operator_poll_msg_set(C, "Context missing active object");
        return 0;
    }

    if (ob->id.lib || (ptr.id.data && ((ID *)ptr.id.data)->lib)) {
        CTX_wm_operator_poll_msg_set(C, "Cannot edit library data");
        return 0;
    }

    return 1;
}

This is pretty cryptic to me, seems to check whether the active context is of type "constraint", but I thought I'd done that. Not sure how to resolve that.

$\endgroup$

2 Answers 2

4
$\begingroup$

With the source snippet you posted these two lines are the key -

PointerRNA ptr = CTX_data_pointer_get_type(C, "constraint", rna_type);
Object *ob = (ptr.id.data) ? ptr.id.data : ED_object_active_context(C);

The first line gets the memory location of the constraint data, the second gets the location of the active object. If either of these are not available (like no active object) they will be set to zero, in which case the error will be given.

The best way to get around context errors is to not rely on them, instead of using operators you can create items directly.

# create the empty
newempty = bpy.data.objects.new('empty_'+curve.name, None)
newempty.empty_draw_type = 'ARROWS'
# link it to the current scene
bpy.context.scene.objects.link(newempty)
# add a constraint to it
emptyconstraint = newempty.constraints.new('FOLLOW_PATH')
emptyconstraint.target = curve

In the above example there are no operators used so you won't get context errors, it uses solid links to the items created to easily alter any part of them. It also doesn't alter the active object, if you want the new object to be selected you need to do that as well.

$\endgroup$
5
$\begingroup$

I encountered a similar error recently, and thought of adding another solution in addition to the one from @sambler , for the sake of posterity.

The line of code that caused the exception was:

bpy.ops.constraint.followpath_path_animate(constraint='Follow Path')

and, the exception was as follows:

Traceback (most recent call last):
  File "/Users/xxxxxxx/Workspace/blender/moveAlongPath.py", line 30, in <module>
    bpy.ops.constraint.followpath_path_animate(constraint='Follow Path')
  File "/Applications/Blender/blender.app/Contents/MacOS/../Resources/2.76/scripts/modules/bpy/ops.py", line 189, in __call__
    ret = op_call(self.idname_py(), None, kw)
RuntimeError: Operator bpy.ops.constraint.followpath_path_animate.poll() Context missing 'constraint'

Digging into the source code of ops.py that's mentioned in the exception, I ended up at the documentation for bpy.ops and noticed the section on "Overriding Context". The exception's indication of Context missing 'constraint' seemed to ring a bell, and I changed the above line of code to the following:

override={'constraint':mycube.constraints["Follow Path"]}
bpy.ops.constraint.followpath_path_animate(override,constraint='Follow Path')

and, voila! it worked. So, it seemed that somehow the data on the constraint was missing in the context, and injecting the constraint information using the override helped the engine find the constraint where it expected.

Full working code:

import bpy
import math

"""
blender -b template.blend -P moveAlongPath.py
"""

scene = bpy.data.scenes["Scene"]
mycube = bpy.data.objects["Cube"]

scene.frame_start = 1
scene.frame_end = 100

# Add a nurbs curve
bpy.ops.object.select_all(action = "DESELECT") # Deselect all objects
bpy.ops.curve.primitive_nurbs_circle_add() # Adds a Nurbs Circle and sets it to selected object
bpy.ops.transform.resize(value=(3,3,3)) # Increase the size of the circle
path = bpy.context.selected_objects[0] # Get the currently selected object and get a handle

# Select the cube, and set it to the active object
mycube.select = True
bpy.context.scene.objects.active = mycube

# Add the follow path constraint to the cube, and set target as the Nurbs Circle we just added
mycube.constraints.new('FOLLOW_PATH')
mycube.constraints["Follow Path"].target = path
mycube.constraints["Follow Path"].forward_axis = 'FORWARD_X'
mycube.constraints["Follow Path"].use_curve_follow = True
override={'constraint':mycube.constraints["Follow Path"]}
bpy.ops.constraint.followpath_path_animate(override,constraint='Follow Path')

# Render the animation
scene.render.filepath = "render/anim"
scene.render.image_settings.file_format = "AVI_JPEG"
bpy.ops.wm.save_as_mainfile(filepath="moveAlongPath.blend")
bpy.ops.render.render(animation=True)
$\endgroup$
1
  • $\begingroup$ wonderful, thx for figuring this out! $\endgroup$ Aug 16, 2020 at 9:36

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .