Promote specyfic channel to animation.

   227   3   1
User Avatar
Member
2 posts
Joined: April 2021
Offline
Hi folks!

Recently, I've been learning KineFX and APEX by following this tutorial:

Rig & Animate a Stylized Crab [www.sidefx.com]

I'm wondering how to expose only specific animation channels on a control, similar to the Lock & Hide workflow in Maya or Softimage.

For example, let's say I have a control that should only rotate around the X axis. As a rigger, I'd like to expose only Rotate X to the animator, while Rotate Y/Z and all Translate/Scale channels remain hidden or unavailable.

Ideally, when the animator selects this control, they would see only the intended animatable channels in the Animation Editor.

I can see that APEX allows us to Promote T, R, and S, but is there a way to go more granular and promote only a specific component, such as RX, rather than the entire Rotate vector?

What is the recommended workflow for this in Houdini 22?

Thanks!
User Avatar
Member
454 posts
Joined: Feb. 2016
Offline
This can be controlled by modifying channel limits with Configure APEX Controls.
Here is a screenshot where I limit the rotation to only revolve around RY and translation on TX.
Edited by AslakKS - Aug. 28, 2026 12:48:49

Attachments:
apex_config_controls.png (1.7 MB)

User Avatar
Member
2 posts
Joined: April 2021
Offline
Thank you for your answer. Yes, I'm already aware of the limits, but unfortunately they don't solve what I'm trying to achieve.

Even when the limits are set, selecting the controller still shows all of its channels in the Animation Editor.

What I'm looking for is a way to expose only the channels that are actually intended for animation. In other DCCs, such as Maya or Softimage, this can be done by locking and hiding unnecessary channels.

This keeps the Animation Editor clean, so the animator sees only the channels that are relevant for a particular control.

For example, if a controller should only rotate around the X axis, I'd like the animator to see only Rotate X, rather than X, Y, and Z with Y and Z simply limited or locked.
User Avatar
Member
454 posts
Joined: Feb. 2016
Offline
Sorry, I completely jumped over the part where you mentioned the animation editor.
This does sound like a very good RFE to send to support, I would also love to see this easily done without having to dive deep into apex.

I did manage to create a monkey patch that does this with shelf tool that you click when an apex animate state is active.
Slop code, but I tested that it works 🫣
import stateutils; import types

viewer = stateutils.findSceneViewer()
if viewer is None:
  hou.ui.displayMessage("No scene viewer found")
else:
  args = {}
  viewer.runStateCommand("getState", args)
  state = args.get("state")
  if state is None or not hasattr(state, "getScopedChannels"):
    viewer.setPromptMessage("Not in APEX Animate state")
  elif hasattr(state, "_apex_non_locked_scoped_original"):
    state.getScopedChannels = state._apex_non_locked_scoped_original
    del state._apex_non_locked_scoped_original
    viewer.setPromptMessage("Scope non-locked channels: disabled")
  else:
    original_scoped_channels = state.getScopedChannels

    def locked_axes(control_path, transform):
      try:
        control = state.control_manager.controls[control_path]
        minimum = (
          getattr(control, f"{transform}_min_lock")
          if getattr(control, f"{transform}_use_min_lock")
          else getattr(control, f"{transform}_min")
          if getattr(control, f"{transform}_use_min") else None
        )
        maximum = (
          getattr(control, f"{transform}_max_lock")
          if getattr(control, f"{transform}_use_max_lock")
          else getattr(control, f"{transform}_max")
          if getattr(control, f"{transform}_use_max") else None
        )
        if minimum is None or maximum is None:
          return set()
        return {
          axis for axis in "xyz"
          if abs(getattr(minimum, axis)() - getattr(maximum, axis)()) < 1e-6
        }
      except (AttributeError, KeyError, TypeError):
        return set()

    def filter_channels(scoped_channels):
      return {
        rig_path: [
          channel for channel in channels
          if not (
            (control_name := channel.rpartition("_")[0])
            and len(component := channel.rpartition("_")[2]) == 2
            and component[0] in "rts"
            and component[1] in locked_axes(f"{rig_path}/{control_name}", component[0])
          )
        ]
        for rig_path, channels in scoped_channels.items()
      }

    def get_scoped_channels(_state):
      return filter_channels(original_scoped_channels())

    state._apex_non_locked_scoped_original = original_scoped_channels
    state.getScopedChannels = types.MethodType(get_scoped_channels, state)
    viewer.setPromptMessage("Scope non-locked channels: enabled")
edit: made the code more precise by filtering the scoped channels
Edited by AslakKS - Aug. 29, 2026 06:45:12

Attachments:
apex_channel_only_free.mp4 (612.4 KB)

  • Quick Links