Smooth rotation in python

Hi. I posted this in the Game Engine side, but it might be more suited here, since I will eventually want the ease of simply changing numbers in a python code to result in different rotation values.

I’m modeling a robot arm with 6 joints, each of with allows free movement along one axis. I’m using the bge to control it, but my lack of experience with Python is making things a bit challenging. What I would like is if I could input a degree angle into the “Rotation_Amount” section and have the model rotate there by clicking the space bar once. This will only be for one of the joints, and then I’ll adjust accordingly for the other joints.

I’ve made two different blend files to try and get this, but both have slight issues. I’ve used 100 degrees as my test angle. Help would be greatly appreciated.

import bge
import time
from math import pi

def main():
    
    Rotation_Amount = 100
    
    cont = bge.logic.getCurrentController()
    owner = cont.owner
    keyboard = bge.logic.keyboard
    spaceKey = bge.logic.KX_INPUT_JUST_ACTIVATED== keyboard.events[bge.events.SPACEKEY]
    
    unit = pi/180

    if spaceKey and Rotation_Amount > 0:
        for n in range(0,Rotation_Amount,1):
            owner.applyRotation((0,0,unit), True)

    if spaceKey and Rotation_Amount < 0:
        for n in range(0,0-Rotation_Amount,1):
            owner.applyRotation((0,0,-unit), True)

            
main()  

This file rotates the model to its desired location, but it just sort of “teleports” there. I want to visually see the model rotate itself over to that spot. I would imagine there’s a way to finagle with this code to get it to do that, but I haven’t found a way yet. I’m thinking that the “teleporting” effect is actually just the speed of the computer; i.e. it rotates there, but extremely fast. I tried slowing it down using a time.sleep function, but it didn’t work. Is there any way to get this to work?

Then, here’s the other code.

import bge
from math import pi

def rotate():
    cont = bge.logic.getCurrentController()
    owner = cont.owner
    keyboard = bge.logic.keyboard
    spaceKey = bge.logic.KX_INPUT_ACTIVE == keyboard.events[bge.events.SPACEKEY]
    
    Rotation_Amount = 100    
    curRot = owner.localOrientation.to_euler()[2]
    unit = pi/180
  
    Rotation_Radians = Rotation_Amount*unit
    
    if spaceKey and curRot < Rotation_Radians:
        owner.applyRotation((0,0,unit), True)
        
    if spaceKey and curRot > Rotation_Radians:
        owner.applyRotation((0,0,-unit), True)
   
rotate()

This code shows the visuals of the model actually rotating to its destination, but the problem here is that I have to hold the spacebar to get it there; I just want to be able to press it once and have the model rotate on its own. This one also has the problem of “jittering” once it gets to its destination because of holding the spacebar down, which obviously wouldn’t be a problem if it were just pressed once.

Any help on the matter would be great. Thanks!

Hi, put in a property for spacekey pressed…or a global


pressed = owner["pressed"]
if not pressed and spacekey:
    owner["pressed"] = True

if pressed: 
    if  rot < max:
        owner.....
    else:
       owner... 
       owner[" pressed"] = False


there is a radians method in the math lib too.


from math import radians

angle = radians(100)

Thanks for the advice.

Hmmm, I get the error “KeyError: ‘value = gameOb[key]: KX_GameObject, key “pressed” does not exist’” when inserting the first line of that code into mine, though.

Add a Boolean game property to the object named “pressed”

Ah, that makes things work. That’s exactly what I was looking for. Thanks.

I still have a few questions / some things are still not going exactly right.

  1. It’s still doing that “jittering” effect once it reaches its destination. Is there a way to stop this? It does not look right at all, and I’d like to see it gone.

  2. The owner.localOrientation.to_euler() function is still a little confusing to me. I know it tracks the object’s x, y, and z coordinates, but because of this, I’m not able to rotate over 180 degrees; it just spins infinitely. Is there a way to be able to rotate over 180 degrees while using this function, or is there a method to rotate without using that function? My first code in my first post doesn’t use it, and it’s able to rotate over 180 degrees, but that’s the one that just “teleports” there. I tried to implement it with the “pressed” property, but it didn’t work out for me. Is there a way to use that?

Essentially, I just want to be able to rotate > 180 degrees.

  1. Since I want to be able to easily change the rotation amounts easily, I have all 6 python codes that each joint uses open. Is there a way to put all of these python codes in to one code that still runs effectively? That would mean that I could simply receive a txt file with new rotation amounts, copy it into Blender, and run it right away.

I’ve got my blend file to show what I have so far.

Attachments

rotating arm.blend (885 KB)

Hi again.

Probably more a beginner in the game engine than you. It’s always been a goal of mine to get into it, so I’m happy to help and learn something new along the way.

On 1 & 2. Use the print method extensively when testing, to see what’s happening, especially with rotations. 181 or -179 degrees will put your object in the same place, but currot < 180 is true for only one. If you put in some print statements you’ll see what’s happening.


import bge
from math import radians

def rotate_kuka_3():
    cont = bge.logic.getCurrentController()
    owner = cont.owner
    keyboard = bge.logic.keyboard
    spaceKey = bge.logic.KX_INPUT_ACTIVE == keyboard.events[bge.events.SPACEKEY]
      
    Rotation_Amount = 280
    # Rotation for kuka_3, in degrees. Positive is counter-clockwise.
    
    curRot = owner.localOrientation.to_euler().z
    unit = radians(1)    
    Rotation_Radians = radians(Rotation_Amount)

    pressed = owner["pressed"]
    if not pressed and spaceKey:
        print("PRESSED")
        owner["pressed"] = True
    print(owner.name, spaceKey, owner["pressed"], curRot, Rotation_Radians)
    if owner["pressed"]: 
        if curRot &lt; 0:
            curRot += radians(360)
                    
        if  curRot  &lt; Rotation_Radians:
            owner.applyRotation((0,0,unit), True)
            
        else:
            #owner.applyRotation((0,0,-unit), True)
            owner["pressed"] = False
rotate_kuka_3()

Also owner[“pressed”] never seems to be false.
Took me a while to see this, but there is an unneeded space owner[" pressed"] = False… should be owner[“pressed”] = False… typo error from me in post 3.
Wonder why this didn’t throw a bug… must be able to create properties on the fly like custom properties on bpy ID objects.

You’ll see that CurRot flips from pi to -pi or 180 to -180, or vice versa hence your infinite loop.
Using angle = atan(tan(x)) will map your angles from -180 to 180 but is an “expensive” way to do it
I used angle < 0 angle = 360 + angle in example above… the next thing is what to do when angle > 360.

for part 3 Have a look at using the module script execution method and putting the rotation_amount in as a property too. Then you wont need a script for each part. Or consider using an armature instead.

Ah, thanks a whole bunch, man. That’s awesome stuff. I’m looking at putting the additional game properties to put it all in one script. Your help was greatly appreciated!

I’ll see what I can do now, but the first hurdle’s crossed. :wink:

Ok, so my original blend’s been updated with the newer code. I also added another if statement to get everything to work for an input of a negative rotation amount. That animation’s looking all nice and pretty, but it has the 6 script files.

So, now I’m working on the “stick everything into one large script” file, and I’ve hit a bit of a snag. I’m assuming that I have to name the “pressed” property with a different name for each joint so I can clarify which joint to rotate which way in the bottom part of the code. However, the code just doesn’t work when I define more than one “pressed” property. No freeze, no error, it just does nothing. I’m just trying right now with two of the joints to get started. If one is removed/commented, the other works fine, but they do not work together. Any suggestions?

Attachments

rotation2.blend (807 KB)

Ok…

Just read a couple of Monsters Guides, and used your file to play around with.
Heres what I’ve come up with. I adjusted the script to use modules.
I used the rot property to increment / decrement until zero. This let me rotate any amount forwards or backwards.

joint.py


import bge
from math import radians
from mathutils import Vector

joints = {}
joints["kuka_1"] = {"axis" : 2,  "rot" : 90}
joints["kuka_2"] = {"axis" : 1, "rot" : 30}
joints["kuka_3"] = {"axis" : 2,  "rot" : 720}
joints["kuka_4"] = {"axis" : 1, "rot" : 57}
joints["kuka_5"] = {"axis" : 2,  "rot" :-60}
joints["kuka_6"] = {"axis" : 2, "rot" : 299}

def set_rotate_state(cont):
    print("SET ROTATE STATE")
    
    scene = bge.logic.getCurrentScene()
    joint_objects = [ob for ob in scene.objects if ob.name in joints]
    for joint in joint_objects:
        #joint["pressed"] = not bool(joint.get("pressed", False))
        joint["pressed"] = True
        print(joint.name, joint["pressed"])
        joint_dic = joints[joint.name]
        for (key, value) in joint_dic.items():
            joint[key] = value
    

def rotate_joint(cont):
    #cont = bge.logic.getCurrentController()
    owner = cont.owner
    
    rot_v = Vector([0,0,0])
      
    Rotation_Amount = owner.get("rot",0)
    if abs(Rotation_Amount) &lt; 1:
        #print("NO ROT")
        owner["pressed"] = False
        return
    axis = owner["axis"]
    direction = int(Rotation_Amount / abs(Rotation_Amount))
    curRot = owner.localOrientation.to_euler()[axis]
    unit = direction * radians(1)
    
    if curRot &lt; 0:
        curRot += radians(360)  # mapping from -180,180 to 0, 360        
    
    Rotation_Radians = radians(Rotation_Amount)

    pressed = owner.get("pressed", False)
    
    if pressed:
        print(owner.name, "PRESSED", Rotation_Amount) 
        
                    
        if  owner.get("rot", 0) != 0:
            rot_v[axis] = unit
            owner["rot"] -= direction
            owner.applyRotation(rot_v, True)
            
        else:
            #owner.applyRotation((0,0,-unit), True)
            owner["pressed"] = False
            
            

And a sample file.

PS replace the script in file with above… missing the abs() in rotationAmount test, also note “pressed” prop isn’t really needed.

Attachments

arm.blend (798 KB)

Alright, that code is a little confusing for me to grasp exactly what it does. The “set_rotate_state” part is fine (except I’m not really sure what joint_dic does), but I’m not really understanding how “rotate_joint” works. Also, the rotation isn’t precise; it overshoots the final value by a bit. You can see this is you put in 360 for all of the numbers. This worked fine on mine, and I’m just wondering how your “rotate_joint” section works, since that’s where I would imagine the error is occurring.

Great stuff, though.

Thanks for all your help.

I have another question, and I’ll just use this thread so not to clutter the forum. How would I implement another rotation for each joint to occur directly as the first set of rotations ends? I’ve set something up, but instead of doing one after another, it just runs both at the same time (ex. I want to rotate the first joint 360 degrees, then -60 degrees. It just rotates it the sum of the two: 300 degrees). How is this done?

Hi again.

Helping in this post has really whet my appetite for the game engine. Cheers for that.

If you are using the code in post #11 you could change


joints = {}
joints["kuka_1"] = {"axis":2,  "rot":360, "v":1}
joints["kuka_2"] = {"axis":1, "rot":360, "v":1}

to


joints = {}
joints["kuka_1"] = {"axis":2,  "rots":[360, -50, 400, 19], "v":1}
joints["kuka_2"] = {"axis":1, "rots":[360], "v":1}
etc ..

You now have a list of rotations instead of a single rotation. If we make these FILO lists you could set the rot property from the last member of the list, then pop it off. When the rot is zero check the rots list has length, if so go again until it’s empty. Saving them to another list would give you the ability to undo or reverse too.

You will need to update the code to reflect this change.

for set_rotate_state:



for (key, value) in joint_dic.items():
    if key == "rots":
        if len(rots) &gt; 0: # list has items.
            joint["rot"] = value[-1]  # make the last item the rot
            value.pop() # pop it off the list
        else:
            joint["rot"] = 0
    else:
        joint[key] = value

In rotate_joint



if x == 0: 
    owner["pressed"] = False 
    set_rotate_state(cont)  
    return 
       

Another way would be to set up a list of the dictionaries, then you could alter the axis, angle and speed for each one.


joints = {}
joints["kuka_1"] = [{"axis":2,  "rot":360, "v":1},
                            {"axis":2,  "rot":-60, "v":2},
                            {"axis":2,  "rot":100, "v":4}
                           ]

joints["kuka_2"] = [{"axis":1, "rot":360, "v":1}]

Hope this helps.

PS also meant to suggest changing the always sensor on the joint to a property value not equal to zero on the rot property to rotate, equals zero to choose next rotation etc.

PPS.
typed the code in without checking for errors… the cut and pasting of code from other posts is a formatting nightmare.

Check your pm’s batFINGER. I had some quick questions that I didn’t want to clutter up this thread with asking them.

Yeah, got it fixed it forgot to post.

I changed the controller from always to rot not equal to zero. Haven’t set it up on all joints.
Noticed you introduced fps into the mix, for speed.
The code I set up had been designed to work on integers. You may want to consider changing the way that you do this.

Had a look at making the rotation degrees * fps and making the speed degrees per second then the subtracted part would be speed * fps, however the unit that is added as rotation ( radians(1/fps) ) becomes so small it must get below the tolerance and then doesn’t move.

My suggestion is to fiddle around with numbers. look to making the unit tenths of degrees and then 3600 would be a full rotation.

There were some errors here.


        for (key, value) in joint_dic.items():
            if key == "rot":
                if len(value) &gt; 0:
                    print(joint.name, " rotate ",value[-1])
                    joint["rot"] = value[-1]
                    value.pop()
                else:
                    joint["rot"] = 0
            else:
                joint[key] = value

joints[“kuka_nn”][“rot”] returns a list.

When we go thru the key, value bit the keys are “rot”, “speed” etc. The value for key “rot” is the list of rotations. If the list has length, we grab the last one use it as the current rotation amount, and pop it off the list. Popping is a quick easy way to remove from lists.

Attachments

attempt.blend (801 KB)

I’ve been messing around with this, and updated the code such that the speed can be changed for each rotation set as well. One question I have is regarding the decimals, as you said. Since this version of the code only uses integers, and I’ll be wanting much more precise data, some things will need to be edited in order to get the accuracy. I’ve messed with the numbers a lot and I seem to be having problems getting things to work. I’ve tried multiplying the numbers by 1000, then rounding to the nearest integer (With my velocities, several have more than 3 decimal places), and then setting the rotation amount to account for this, but things still don’t seem to be working for me.

Can you show an example of the accuracy bit working for a sample value? It doesn’t need to be anything fancy at all, just one little sample so I can find where my mistake is.

Here’s the part of the code I have. The sample speed I want is a little over 0.9, but it won’t run at all since its below 1. That’s what I need to fix.

def rotate_joint(cont):    #cont = bge.logic.getCurrentController()
    owner = cont.owner
    
    
      
    Rotation_Amount = owner.get("rot",0)
    Rotation_Accuracy = round(Rotation_Amount*1000)
    x = abs(Rotation_Accuracy)
    #print(owner.name, x)
    if x == 0:
        print("finished one rotation", owner.name)
        owner["pressed"] = False
        set_rotate_state(cont)
        return        
    speed = owner.get("v",1)
    v = abs(speed*1000)
    if abs(Rotation_Accuracy) &lt; v:
        #print("NO ROT")
        v = abs(Rotation_Accuracy)


    rot_v = Vector([0,0,0])
    
    axis = owner["axis"]
    direction = int(Rotation_Accuracy / abs(Rotation_Accuracy))
    
    unit = radians(v * direction)
    pressed = owner.get("pressed", False)
    
    if pressed:
               
        if  owner.get("rot", 0) != 0:
            print(owner.name, owner["rot"])     
            rot_v[axis] = unit
            owner["rot"] -=  (v * direction)/1000
            owner.applyRotation(rot_v/1000, True)
            
        else:
            #owner.applyRotation((0,0,-unit), True)
            owner["pressed"] = False