How to add a translation to a xformable?

The physicsutils has a functionset_or_add_translate_op, but this does not seem to “add”, it rather replaces any existing translation operation. Likewise, trying to directly add a translation operation AddTranslateOp().Set(Gf.Vec3f(1, 1, 1)) after an existing one, errors with a c++

2021-11-24 00:02:55  [Error] [omni.usd.python] ErrorException: 
	Error in 'pxrInternal_v0_20__pxrReserved__::UsdGeomXformable::AddXformOp' at line 189 in file E:\w\ca6c508eae419cf8\USD\pxr\usd\usdGeom\xformable.cpp : 'The xformOp 'xformOp:translate' already exists in xformOpOrder [[xformOp:translate]].'

So, how can I add multiple transformations?

Hi,
Yes, the utility adds the translate op to the given xformable. I am not sure what you want to achieve exactly. If you need multiple transformations that are time dependent you can do something like this:

        translateOp = cubeGeom.AddTranslateOp() 
        translateOp.Set(position)
        translateOp.Set(time=0, value=position)
        translateOp.Set(time=50, value=positionEnd)
        translateOp.Set(time=100, value=position)

Regards,
Ales

Hi Ales, thanks for the response.

I am trying to chain transformations on the same timestep. I.E. I have some algorithm that determines a value for a translation and/or rotation on a body, defined in different functions/sections of the code. For simplicity sake lets say it is like (pseudocode):

def randomize(bodies):
   foreach body in bodies:
        translateOp = body.AddTranslateOp() 
        translateOp.Set(rand_pos,rand_pos,rand_pos)

def movebytype(bodies):
   foreach body in bodies:
       if type(body) is cube:
           translateOp.Set(5,0,0)
       else: 
           translateOp.Set(0,5,0)

randomize(bodies)
movebytype(bodies)

I think there are two issues at play here.

(1) from what I understand and how it behaves; the name and description of set_or_add_translate_op is a little confusing. It does not add a translation. It sets the only translation. It is not adding translations, but rather if there is no translation method, it will add a translation operation, and if there is an existing one, it will replace that one. Assuming this is the correct understanding, it should be called set_or_replace_translate_op.

(2) The simulation architecture is a little unclear, since omniverse seems a bit novel in its integration with so many applications that all handle it differently. For example, having some extended explanation like unity Unity - Manual: Order of execution for event functions would be helpful (although not sure where it would be best. The two places I look would be Physics Core — Omniverse Create documentation and Architecture — kit-sdk 103.1 documentation) .
So in the unity approach, one would access the position of the object and add to its x,y,z components. That new value is stored in the object but not really represented in the simulation until the next update. In Bullet(pybullet), there is a separation of the bodies transform from the physics step, so you can update positions of objects without running a simulation step, but also conduct intersection tests and the like.

In the case of Maya (as I recall), the API basically lets you query the xform as a homogenous matrix, then multiply your own transformation and set it as the new frame. This is instantly stored (which from OV docs sound like its the USDcache), so in another function that current/latest position can be queried and modified again.

For (1) the helper function tries to add the translateOp attribute to the USD prim, so it does really adds something, if it succeed it would set the position. But here you should use USD directly and just remember the translateOp to reuse it later.

For (2) we did not settled yet on the update loop, this is something that is still in a process of definition. I could try to add some diagram that might show the loop with respect to physics.
As for the objects position update. Physics does after the simulation is done write the updated positions back to USD. In your python code you should be able to query the new position using the translateOp.Get(). If you set new position translateOp.Set(), physics will receive USD notification and will update the rigid bodies positions in physics.

Hi @AlesBorovicka

Sorry if I am totally misunderstanding something here, I appreciate your patience in explanation.

So I am trying to add a translation operation to a body, after I have added one.

As you can see in the image… I set_or_add_translate_op, translating it up on the z axis 1000 units. Since there is no translation operation in existence, it sets the operation. The next line (in the image) calls the same function, which based on the description, should add a translation operation, shifting it on x-axis 500 units. However, it does not add it, it replaces the old one, and instead of the box position being at (500,0,1000), its just at (500,0,0).

Same thing happens with explicit Set

        translateOp = self.skin_mesh.AddTranslateOp() 
        translateOp.Set(Gf.Vec3f(0.0, 0.0, 1000.0))
        translateOp.Set(Gf.Vec3f(500.0, 0.0, 0.0))  # this just overrides previous

So how can I ‘reuse’ the translateOp attribute to add transformations, instead of replacing them?


For what its worth, having direct control over the simulation steps is extremely valuable and important. My aim here is to move my labs research onto the omniverse platform. Unity (even with its ml-agents) has always been difficult to do ML/RL work with (so we usually use Bullet). i.e. please go with a bullet/mujoco type control that lets explicit stepping over the unity approach to the simulation/render loop.

Ah on, now I fully understand what you want to achieve I hope.
So AddTranslateOp is needed first as it adds the translate op to the xformable, this does two things:

  1. it creates an attribute that you can use to get and set the positions
  2. it setups the transformation stack that is used to determine the final pose
    Thats what confused me, this is really called add not set, because it really does add things into USD prim.

Anyway now to do what you need. So in your case, you need to first read the translate vector:
current_pos = translateOp.Get()
then you can add your delta
new_pos = current_pos + Gf.Vec3f(500,0,0)
then you set the new position
translateOp.Set(new_pos)

This is working strictly with USD, so you get current position from USD, you add your delta vector and you set the new position to USD. Physics will receive the change notification and update its rigid body.

Now to the simulation stepping question:
Right now simulation is tied to timeline control, so if you press play simulation will start and simulate, once you press stop, things get reset. This is the default behaviour.
You certainly can fully control the simulation yourself without the need to run rendering loop. The IPhysx interface that you get in python lets you do that. What you would need to do is something like this:

  1. non mandatory get_physx_interface().start_simulation() <— this will store the initial transformations, use this only if you want to reset the simulation
  2. step the simulation get_physx_interface().update_simulation(dt, currentTime) <— this will step the simulation
  3. update USD after simulation get_physx_interface().update_transformations(False, True) <---- this will write back the results to USD (the second parameter, the first one is deprecated and wont be used)
    now you can loop 2)3)
    once done you can call get_physx_interface().reset_simulation() <---- this will set the initial transformations from step 1 to USD and remove all data from PhysX

Hope it helps, but it should be totally possible to do what you need, we allow custom stepping and do that in many of our python tests actually.

Thanks @AlesBorovicka ! Yes you got it.

(just a note incase others see this, I just had to change cur_pos + Gf.Vec3d, as .Get() returns a Vec3d and there currently doesnt seem to be a type set for adding 3f and 3d).

I have been able to step the simulations in python, so that has worked well. I think the added diagram/explanation for how the physics loop and these functions all work together.

I wasn’t really aiming for all USD methods, if there was a more directly approach to apply to the objects transformation I would be happy to do that (it just seems from the docs that USD is the way?).

Regarding the physics steps, you mention physics receives the change notification from the changed USD transform, so is it correct that the state of all objects are always updated instantly (sequentially)? Referencing a pybullet situation recently, there were some discussions on people wanting to run through a bunch of kinematic solutions for a robot arm, find the possible valid states (no collisions), then set torque control to use in the next physics simulation loop. The issue here being that setting the transformations was not updating the collisions, so you couldn’t → move_body-> check for collision → step simulation. There was a PR that solved it by adding an explicit update to the collision detection that wouldn’t step the simulator.

So regarding your point 3, is it update_transformations is necessary for USD to learn about the physics simulation, but the physics always knows what is going on for USD?


I hope you can appreciate the confusion I had on the set_or_add function name and function description. Looking at the function itself, it still seems to me as set_or_replace. (comments added from me)

    if xformOp.Get() is None: # check if there is no xform
        xformOp.Set(Gf.Vec3f(translate)) # if there is no xform, SET it 
    else: # if there is already an xform
        typeName = type(xformOp.Get()) 
        xformOp.Set(typeName(translate)) # replace it, by setting a new one
    return xformOp

Without knowing all the other aspects you just explained, (and even after), it doesn’t seem to ever add. It just does one of two things; if its empty, it sets it, using a function called Set(), and if its not empty, it overrides and replaces the existing one, using the same function Set().

The first line that goes above this if statement is

xformOp = _get_or_create_xform_op(xformable, "xformOp:translate", UsdGeom.XformOp.TypeTranslate)

And this seems to be what you are saying is added? It seems like get_or_create is a clear name, its either getting it, or creating (adding) xform to the usd? Then the next step is to set or replace the translation component.

Yes, that is correct. There is no direct talk to physics, physics implementation in Omniverse is data driven through USD. You change linear velocity attribute on a prim and physics gets the change and applies it to rigid bodies.

Regarding the physics steps, you mention physics receives the change notification from the changed USD transform, so is it correct that the state of all objects are always updated instantly (sequentially)?

Yes, transformation is applied immediately after the change has been done.

So regarding your point 3, is it update_transformations is necessary for USD to learn about the physics simulation, but the physics always knows what is going on for USD?

Yes, correct. With step 2 you run the simulation step, now after the simulation is done, the new transformation/velocities are just inside physx. Then if you want to write those data back to USD you run then update_transformation, this is done in a separate step because this can be very expensive unfortunately. So sometimes you want to simulate few frames but are interested in results in USD only in some cases.
Physics knows about USD changes, since USD does have a notification system for its changes. So if any change in USD stage happens, physics will get a notification about it. It then checks if thats a change it is interested in and if yes it checks if there are some physics objects created on that prim path, if yes things get updated inside physics.

I did added a note to myself to add a diagram about the default simulation loop we have now and about the possible manual update loop. Will try to improve the doc next week, thanks a lot for the feedback.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.