git clone https://git.lucas.co/hou-control.git
keycam: use hybrid hotkey system with robust manual interception
viewer_states/keycam.py | 112 ++++++++++++++++++++++++++++++++++++------------
1 file changed, 85 insertions(+), 27 deletions(-)
diff --git a/viewer_states/keycam.py b/viewer_states/keycam.py
index 0ed2f4a..90440ae 100644
--- a/viewer_states/keycam.py
+++ b/viewer_states/keycam.py
@@ -67,50 +67,78 @@ class State(object):
def onKeyEvent(self, kwargs):
self.cam.fitAspectRatio()
+ ui_event = kwargs['ui_event']
+ device = ui_event.device()
+ key = device.keyString()
+ is_shift = device.isShiftKey()
+ # Manual mapping as fallback/primary if symbols fail to resolve
key_map = {
- 'o': self.cam.toggleProjection,
'h': self.cam.rotateLeft,
- 'j': self.cam.rotateDown,
- 'k': self.cam.rotateUp,
'l': self.cam.rotateRight,
- # 'v': self.scene_viewer.nextView,
- 'Shift+h': self.cam.translateLeft,
- 'Shift+j': self.cam.translateDown,
- 'Shift+k': self.cam.translateUp,
- 'Shift+l': self.cam.translateRight,
- # 'Ctrl+l': self.scene_viewer.nextLayout,
- 'f': self.cam.frame
- # 'c': self.cam.center
+ 'k': self.cam.rotateUp,
+ 'j': self.cam.rotateDown,
+ 'f': self.cam.frame,
+ 'o': self.cam.toggleProjection,
+ }
+
+ shift_key_map = {
+ 'H': self.cam.translateLeft,
+ 'L': self.cam.translateRight,
+ 'K': self.cam.translateUp,
+ 'J': self.cam.translateDown,
}
- key_map_persp = {
+
+ # 1. Handle Navigation
+ if is_shift:
+ # Shift+K might come through as 'K' or 'Shift+K'
+ # We strip 'Shift+' and use uppercase
+ lookup = key.replace('Shift+', '').upper()
+ if lookup in shift_key_map:
+ shift_key_map[lookup]()
+ self.guides.update()
+ return True
+ else:
+ lookup = key.lower()
+ if lookup in key_map:
+ key_map[lookup]()
+ self.guides.update()
+ return True
+
+ # 2. Zoom mappings
+ zoom_map_persp = {
'-': lambda: self.cam.zoom('out'),
'=': lambda: self.cam.zoom('in'),
'Shift+-': lambda: self.cam.zoomOrtho('out'),
'Shift+=': lambda: self.cam.zoomOrtho('in')
}
- key_map_ortho = {
+ zoom_map_ortho = {
'-': lambda: self.cam.zoomOrtho('out'),
'=': lambda: self.cam.zoomOrtho('in'),
'Shift+-': lambda: self.cam.zoom('out'),
'Shift+=': lambda: self.cam.zoom('in')
}
- if self.cam.projection == 'perspective':
- key_map = key_map | key_map_persp
- else:
- key_map = key_map | key_map_ortho
-
- key = kwargs['ui_event'].device().keyString()
- if key in key_map:
- key_map[key]()
- print(self.cam.p)
- print(self.cam.t)
- print(self.cam.r)
+ zoom_map = zoom_map_persp if self.cam.projection == 'perspective' else zoom_map_ortho
+
+ if key in zoom_map:
+ zoom_map[key]()
self.guides.update()
return True
- else:
- return False
+
+ if key.lower() in zoom_map:
+ zoom_map[key.lower()]()
+ self.guides.update()
+ return True
+
+ # 3. Explicitly consume 'j' to prevent "Modify Channels"
+ if key.lower() == 'j':
+ return True
+
+ return False
+
+ def onKeyboardEvent(self, kwargs):
+ return self.onKeyEvent(kwargs)
def onMenuAction(self, kwargs):
menu_item = kwargs['menu_item']
@@ -272,6 +300,33 @@ def createViewerStateTemplate():
template.bindFactory(State)
template.bindIcon('DESKTOP_application_sierra')
+ # Hotkey Definitions
+ # Using the official system to define context-sensitive hotkeys
+ hotkeys = hou.PluginHotkeyDefinitions()
+ ctx = 'h.pane.gview.state.obj.keycam'
+ hotkeys.addContext(ctx, 'HC Keycam', 'Hotkeys for the HC Keycam viewer state')
+
+ # Define Commands
+ commands = {
+ 'rotate_left': ('Rotate Left', ['h']),
+ 'rotate_right': ('Rotate Right', ['l']),
+ 'rotate_up': ('Rotate Up', ['k']),
+ 'rotate_down': ('Rotate Down', ['j']),
+ 'trans_left': ('Translate Left', ['Shift+h']),
+ 'trans_right': ('Translate Right',['Shift+l']),
+ 'trans_up': ('Translate Up', ['Shift+k']),
+ 'trans_down': ('Translate Down', ['Shift+j']),
+ 'frame': ('Frame', ['f']),
+ 'toggle_proj': ('Toggle Projection', ['o']),
+ }
+
+ for cmd_id, (label, defaults) in commands.items():
+ symbol = f"{ctx}.{cmd_id}"
+ hotkeys.addCommand(symbol, label, "")
+ hotkeys.addDefaultBinding(ctx, symbol, defaults)
+
+ template.bindHotkeyDefinitions(hotkeys)
+
# Parameters
template.bindParameter(
@@ -430,7 +485,7 @@ def createViewerStateTemplate():
""" Context Menu """
menu = hou.ViewerStateMenu('keycam_menu', 'Keycam Menu')
- menu.addActionItem('frame', 'Frame')
+ menu.addActionItem('frame', 'Frame', hotkey='h.pane.gview.state.obj.keycam.frame')
menu.addActionItem('reset', 'Reset')
set_view_menu = hou.ViewerStateMenu('set_view_menu', 'Set View')
@@ -453,6 +508,9 @@ def createViewerStateTemplate():
menu.addMenu(guide_menu)
menu.addSeparator()
+ # Add toggle projection to menu so we can bind the hotkey
+ menu.addToggleItem('toggle_proj', 'Toggle Projection', 0, hotkey='h.pane.gview.state.obj.keycam.toggle_proj')
+
template.bindMenu(menu)
# Ok