Follow Target

I would like object a to orient/face to object b rotating only in z-axis. It should update every tick and object a should look at object b. The application should be embedded in a extension. Object a does some rotation, but then also translate, but it should stay at the initial position, whereas object b is able to move.

Thanks for helping!

    def on_startup(self, ext_id):
        print("[xxx.20230907] xxx 20230907 startup")

        self._count = 0

        self._window = ui.Window("Rotate Tracker", width=300, height=300)
        with self._window.frame:
            with ui.VStack():
                label = ui.Label("")


                def on_click():
                    stage = omni.usd.get_context().get_stage()
                    cube_prim = stage.GetPrimAtPath("/World/Cube")
                    #label.text = f"count: {cube_prim.orientation}"

                def on_reset():
                    self._count = 0
                    label.text = "empty"

                def onrotate():##works with defined angle
                    stage = omni.usd.get_context().get_stage()
                    AT_path = "/World/SampleScene/Geometry/Xform"  # Replace with the path to your target object
                    AS_path = "/World/SampleScene/Geometry/Xform_01"  # Replace with the path to your object to be rotated

                    # Find the target and rotate objects in the stage
                    
                    AT = stage.GetPrimAtPath(AT_path)
                    AS = stage.GetPrimAtPath(AS_path)
                    xformable = UsdGeom.Xformable(AT)
                    AT_translation = omni.usd.get_local_transform_matrix(AT)
                    AS_translation = omni.usd.get_local_transform_matrix(AS)

                    if AT is not None and AS is not None:
                        # Get the positions of the target and rotate objects in world coordinates
                        AT_translation_Vec = UsdGeom.XformCommonAPI(AT).GetXformVectors(
                            time=Usd.TimeCode.Default())[0]
                        AS_translation_Vec = UsdGeom.XformCommonAPI(AS).GetXformVectors(
                            time=Usd.TimeCode.Default())[0]
                        
                    
                    # Calculate the direction vector from the rotate_object to the target_object
                    AB = AT_translation_Vec - AS_translation_Vec

                    AB[2] = 0
                    AB = Gf.GetNormalized(AB)
                    AC = Gf.Vec3f(0.0, 1.0, 0.0)
                    dot = np.dot(AB, AC)
                    theta = math.acos(dot)
                    Degrees = math.degrees(theta)
                    # Clear transforms


                    xformable.SetXformOpOrder([])

                    #xformable.AddTranslateOp().Set(Gf.Vec3d(0, 0, 0))
                    # Rotate
                    xformable.AddRotateXYZOp().Set(Gf.Vec3d(0, 0, Degrees))
                    #Newrotation = xformable.AddRotateXYZOp().Set(Gf.Vec3d(0, 0, Degrees))
                    #xformable.AddScaleOp().Set((0.01, 0.01, 0.01))

                    label.text = f"count: {Degrees, AT_translation_Vec}"
 
                on_reset()

                with ui.HStack():
                    ui.Button("Add", clicked_fn=on_click)
                    ui.Button("Reset", clicked_fn=on_reset)
                    ui.Button("Onrotate", clicked_fn=onrotate)

    def on_shutdown(self):
        print("[xx.20230907] xxx20230907 shutdown")

Hi @raphael.zuercher - Your code seems to be on the right track. However, it seems like you’re adding a new rotation operation every time you click the “Onrotate” button. This could be the reason why your object A is translating. Instead of adding a new rotation operation every time, you should update the existing rotation operation.

Here’s a modified version of your code:

def onrotate():
    stage = omni.usd.get_context().get_stage()
    AT_path = "/World/SampleScene/Geometry/Xform"  # Replace with the path to your target object
    AS_path = "/World/SampleScene/Geometry/Xform_01"  # Replace with the path to your object to be rotated

    # Find the target and rotate objects in the stage
    AT = stage.GetPrimAtPath(AT_path)
    AS = stage.GetPrimAtPath(AS_path)
    xformable = UsdGeom.Xformable(AT)

    if AT is not None and AS is not None:
        # Get the positions of the target and rotate objects in world coordinates
        AT_translation_Vec = UsdGeom.XformCommonAPI(AT).GetXformVectors(time=Usd.TimeCode.Default())[0]
        AS_translation_Vec = UsdGeom.XformCommonAPI(AS).GetXformVectors(time=Usd.TimeCode.Default())[0]

        # Calculate the direction vector from the rotate_object to the target_object
        AB = AT_translation_Vec - AS_translation_Vec
        AB[2] = 0
        AB = Gf.GetNormalized(AB)
        AC = Gf.Vec3f(0.0, 1.0, 0.0)
        dot = np.dot(AB, AC)
        theta = math.acos(dot)
        Degrees = math.degrees(theta)

        # Clear transforms
        xformable.SetXformOpOrder([])

        # Get the existing rotation operation or create a new one if it doesn't exist
        rotation_op = xformable.GetRotateXYZOp()
        if not rotation_op:
            rotation_op = xformable.AddRotateXYZOp()

        # Update the rotation
        rotation_op.Set(Gf.Vec3d(0, 0, Degrees))

        label.text = f"count: {Degrees, AT_translation_Vec}"

Hi @rthaker,
thanks so much for your input. Regarding general follow target, is this the solution to go or is there some easier out of the box Omnigraph or function which does the trick?

Regarding the script, it works only for specific hierarchy, so if it is deeper in the hierarchy it translates as well. That is due to the local/global transformation or offset?

Hi @raphael.zuercher - Yes, in most cases building a follow target mechanism can be done more conveniently through visual scripting tools such as Omnigraph, where you can create and manipulate complex object relationships in an intuitive way.

However, depending on your specific use case or scenario, you might need to perform a more customized or unique function, and that’s when the programmatic scripting approach could be preferred.

Regarding the script, you’re correct in your assumption. If the objects are nested within a deeper hierarchy, the script might not behave as intended due to the impact of local and global transformations. Each object’s transformation is applied relative to its parent object in the hierarchy. This means any transformation applied to the parent would in turn affect the child object.

For example, in the case of a rotation operation, the position vector obtained from the GetXformVectors() function is the global position. If the parent object of your target is moved or rotated, it would cause the target object to be moved or rotated as well in world space as global space transformations influence the child objects.

So, if the hierarchy is more complex or if there are additional transformations applied in the hierarchy, you would need to account for these transformations when computing positions or rotations.

It might also be worth noting that GetXformVectors() function gives you the combined transformation (i.e., it includes the effects of rotation, scaling, and translation). If you want to specifically handle these operations separately, you might need to consider different methods like GetLocalTransformation() or GetLocalToWorldTransform().