Curve handle type - script, addon request

Hello. Recently I have a lot of work with “Bezier Curves” directly in blender. I really need an operator, who turns bezier curve handle type to “Aligned” but both handles at once, the same as in the “Automatic” command but without changing the length of the handles.
Thank You very much for any help.

Do you have any python experience? If you were wanting to start to learn a little Blender Python, this is a perfect task for “my first addon” or “my first operator.” Not many people have time to just write something out of the blue, but many have time in small increments to help others along the way.

Also, some screenshots for clarity may help specify exactly what you want. Wouldnt it be a shame if someone spent time writing something only to find out it wasnt the project goal?

Post some pics, open some python templates in the text editor and jump on the wiki.blender.org and search for curves. Post any progress you make and I will gladly help :slight_smile: ill post a few code snippets tonight and check back tomorrow

And for the record, are you talking about curve objects or curve handles in the F-Curve window?

The hardest part about tackling your first python operator is just finding out what darn data you need access to. How are those control points and handles organized? What if there are multiple squiggles in the same object?

So…I go to the regular wiki and checkout the curves part
http://www.blender.org/manual/modeling/curves/introduction.html#bezier-curves

Ok, seems like there are “Segments,” “Control Points” and “Handles”

Then I go to the API reference at wiki.blender.org, and search curve, segment, control point, handle which give some confusing results (any search starting out like this will). I get those different search results in different tabs and start looking for things that make sense or seem right. Often, nothing will jump out, so I click around.

In general, you are looking for this

bpy.Types.___SOMETING__ 

These are python objects (not Blender objects). and reading what is in the Type is like, reading the blueprint or an encylcopedia about your type of object. It tells you how its made, where it came from, what kinds of things belong to it and what it can do.

Alternatively, you can look for:

bpy.ops._Category_.do_something_that_I_want()

This would me more trying to simulate operations that a user does. But since you have described a problem where your regular operators are not performing as desired, we will probably need to dive in to the data (eg, the python Object) and modify it ourselves and make our own operator.

Here bpy.Types.Curve:
http://www.blender.org/api/blender_python_api_2_73a_release/bpy.types.Curve.html?highlight=curve

well what the heck is that? the answer is hiding right at the top of the page, but to me it’s really not obvious. I don’t know if it’s the font or what, but my eyes used to skip right over the main description.

“Curve datablock storing curves, splines and NURBS”

Ok, now let’s see what kinds of things belong to a bpy.Types.Curve. If you scroll down that page, every bold item is “something” that all Curves will have or something that any Curve can do to itself or others. Attributes and methods.

anything that ends with parentheses eg… do_something() is a method
anything that doesn’t end in parentheses is a “something”…which will be some other Type of object. Luckily, there are links for all these things for what Type they are. So you can read the blueprint on them too.

Now, Craig’s question is why we aren’t going any further until we get some more details. Here I was thinking we were talking about curve objects in the 3d view…who even knew there were curves in the F-Curve window. Whatever that is says the modeler to the animator.

I know the basics, I know what is the operator, know how to prepare own pie menu or panel, but it’s all based on templates.
In writing from scratch, I have no experience.

It is about curve objects.

I attach a blender file “Curve”, in the present situation, which I wrote about.
Step by step I imagine something like this:

start script

  1. pick and store length of the handles at the selected points of the curve
  2. perform “Automatic” function
  3. perform “Aligned” function
  4. set previously stored length at the appropriate handles
    end script

Attachments

Curve.blend (1.04 MB)


Ok, perfect. Never write from scratch because you would be reinventing the wheel.
Start with the simple operator template. Delete evrything from the “main()” and then paste your 1,2,3,4 from above with “#” to comment, and then we will tackle each item.

-Patrick


import bpy


def main(context):
    #Find the Blender object of interest 
    crv_obj = context.object
  
    #Get access to the curve object data
    crv_data = crv_obj.data


    #active spline
    spline = crv_data.splines.active


    #Find the active bezier control point bcp
    selected_points = [bcp for bcp in spline.bezier_points if bcp.select_control_point]
    
    for bez_point in selected_points:
        #Find the active control point location
        b_loc = bez_point.co
        
        #Find the left handle location
        l_loc = bez_point.handle_left
        
        #Find the right handle location
        r_loc = bez_point.handle_right
        
        #Claculate length handle right
        l_len = (l_loc - b_loc).length
        
        #Calculate length handle left
        r_len = (r_loc - b_loc).length
        
        #Set automatic
        bez_point.handle_right_type = 'AUTO'
        bez_point.handle_left_type = 'AUTO'
        
        #Now set handle type to aligned
        bez_point.handle_right_type = 'ALIGNED'
        bez_point.handle_left_type = 'ALIGNED'
        
        #Reset left handle to correct length
        l_loc_new = bez_point.handle_left
        l_vec = l_loc_new - b_loc
        bez_point.handle_left = b_loc + l_len * l_vec.normalized()
        
        #Reset right handle to correct length
        r_loc_new = bez_point.handle_right
        r_vec = r_loc_new - b_loc
        bez_point.handle_right = b_loc + r_len * r_vec.normalized()
        


    
class SimpleOperator(bpy.types.Operator):
    """Tooltip"""
    bl_idname = "object.simple_operator"
    bl_label = "Simple Object Operator"


    @classmethod
    def poll(cls, context):
        #lets update the poll more.  What conditions do we want?
        return context.active_object is not None


    def execute(self, context):
        main(context)
        return {'FINISHED'}


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


def unregister():
    bpy.utils.unregister_class(SimpleOperator)


if __name__ == "__main__":
    register()

Okay, thanks so far. I’ll try to deal with it.

How did it go. Post what you have and we will go from there.

I decided that I will start to learn the basics first. At the moment code looks very primitive.

import bpy

obj = bpy.context.object
curve = obj.data
spline = curve.splines.active 

# Test operations

spline.bezier_points[0].handle_left_type = 'ALIGNED'

Now I am looking for the way how to get to the active point of spline, because now the operations take place only on the first one, index [0].
At the same time I am also looking for a parameter which allows to store the scale of left and right handle but most likely it is not as simple as I think :slight_smile:

http://www.blender.org/api/blender_python_api_2_73a_release/bpy.types.BezierSplinePoint.html?highlight=handles

Good work. the selection status is a little tricky to find. Not quite as easy as the curve.splines.active.
I’m not sure if there is an “active point” so you will have to filter what is selected.
3 options, depending on how strict you want to be about “selection.”


selected_points = [bcp for bcp in spline.bezier_points if bcp.select_control_point]
selected_points = [bcp for bcp in spline.bezier_points if bcp.select_control_point or bcp.select_handle_left or bcp.select_handle_right]
selected_points = [bcp for bcp in spline.bezier_points if bcp.select_control_point and bcp.select_left_handle and bcp.select_right_handle]


active_point = selected_points[0]

I can’t guess any better way to do this, if I was a math wizard I would start writing math code but the most painless way is to go as Blender would. I have copied these steps from the information window.


bpy.ops.curve.spline_type_set(type='BEZIER')
bpy.ops.curve.handle_type_set(type='AUTOMATIC')
bpy.ops.curve.handle_type_set(type='FREE_ALIGN')

What is the meaning in all these?


# Provided that your curve is POLY in the first place.
# bpy.ops.curve.spline_type_set(type='POLY')

# In order the curve to change, at least one point must be activated.
bpy.ops.curve.spline_type_set(type='BEZIER')

# Unfortunately now the handles match the poly style (this is the problem you have).

# Thankfully they can get automatically recalculated, make sure that all of the points you want to change should be selected.
bpy.ops.curve.handle_type_set(type='AUTOMATIC')

# But since the automatic handles are not exactly what you want. You can change them again to free alignment.
bpy.ops.curve.handle_type_set(type='FREE_ALIGN')

If this script is close to what you want we might proceed to pack it in an operator.

bpy.ops is certainly one way to do it, but my understanding is that the operators were giving slightly unsatisfactory results for OP. We can wrap this up tonight with both the ops and the Blender data methods and compare. I was hoping to see a little more progress before finishing.

I updated post #7 above with completed code. Please test and mark thread as solved if it behaves as expected

Yes, the script works exactly as I wanted, patmo141 I am very grateful for your help and patience.
I must necessarily learn the basics of Python…

You are very welcome! If you have questions about why something is doing what it is up there, there are lots of good leaning opportunities in this script and I would be happy to go line by line in this thread. I’ll leave it to you to ask questions and post, but I’ll check back periodically.

Best,
Patrick

You wrote commentary over every line of code that explains a lot… but as I said before I do not know the basics well, for example how to interpret this part of the code:

@classmethod
    def poll(cls, context):
        #lets update the poll more.  What conditions do we want?
        return context.active_object is not None

What exactly does that mean?

Line by Line

@classmethod

this is a decorator. i actually don’t fully understand what its for. But all blender Operators have the same structure. They have a poll method, and that poll method is always preceded by the @classmethod decorator. Need to do some googling myself there.

def poll(cls, context)

the “cls” in this, is the method looking back at the class as an argument. In the poll method, I’m not sure why it’s there.
the “context” is the blender context. This one is more logical to me because the what the poll() method does.

poll() determines if it is appropriate at this time, place and circumstance, to use the operator. We do this all the time in real life. Before I swing a hammer, I make sure that 1. I’m outside, 2. There is a nail 3. there is some wood. Otherwise, my hammer may cause an error

return context.object is not None

This one is a little tricky.

  1. poll() methods of Blender operators must return True or False. Any other method could potentially return anything it wanted but we need a True or False to spit out of poll()

  2. “is not None” is a comparison. Same family of expression as “A == B” of “A > B.” Comparisons always return True or False. More on the particular comparison “is not None” here

We could less compactly write


@classmethod
def poll(cls, context):
    condition0 = context.object is not None
    return condition0

  1. Simply having an active object as our only criteria to run the operator is not very stringent, and we may want to do some more strict polling make sure our users don’t get into trouble. For example…is a Cube or Mesh object going to have splines or bezier points? I don’t think so, so what happens when we ask for the following if we don’t have a curve object?
context.object.data.splines.active

ERROR.

Brainstorm on what criteria you want for your operator poll, and I’ll help you put them in.

best,
Patrick