git.lucas.co / hou-control
git clone https://git.lucas.co/hou-control.git

commitac2ad039ed2a27d12147fb6a2d8e18fc8ef939af
parentc8d3221697
authorLucas Galante <lsgalante12@gmail.com>
date2026-04-16 22:32
keycam: fully implement PluginHotkey system with menu-linked symbols

 viewer_states/keycam.py | 149 ++++++++++++++++++++----------------------------
 1 file changed, 61 insertions(+), 88 deletions(-)

diff --git a/viewer_states/keycam.py b/viewer_states/keycam.py
index a8d4c53..4be4c41 100644
--- a/viewer_states/keycam.py
+++ b/viewer_states/keycam.py
@@ -66,32 +66,16 @@ class State(object):
             viewport.lockCameraToView(False)
 
     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 = {
-            'h': self.cam.rotateLeft,
-            'l': self.cam.rotateRight,
-            '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,
-        }
+        """Keys are now handled via PluginHotkeyDefinitions and onMenuAction."""
+        return False
 
-        ctx = 'h.pane.gview.state.obj.keycam'
+    def onKeyboardEvent(self, kwargs):
+        return self.onKeyEvent(kwargs)
 
-        # 1. Define zoom behavior based on projection
+    def onMenuAction(self, kwargs):
+        menu_item = kwargs['menu_item']
+        
+        # Define zoom behavior based on projection
         def zoom_in():
             self.cam.zoom('in') if self.cam.projection == 'perspective' else self.cam.zoomOrtho('in')
         def zoom_out():
@@ -101,67 +85,36 @@ class State(object):
         def zoom_shift_out():
             self.cam.zoomOrtho('out') if self.cam.projection == 'perspective' else self.cam.zoom('out')
 
-        # 2. Check defined plugin hotkeys
-        hc_symbol_map = {
-            f'{ctx}.rotate_left':   self.cam.rotateLeft,
-            f'{ctx}.rotate_right':  self.cam.rotateRight,
-            f'{ctx}.rotate_up':     self.cam.rotateUp,
-            f'{ctx}.rotate_down':   self.cam.rotateDown,
-            f'{ctx}.trans_left':    self.cam.translateLeft,
-            f'{ctx}.trans_right':   self.cam.translateRight,
-            f'{ctx}.trans_up':      self.cam.translateUp,
-            f'{ctx}.trans_down':    self.cam.translateDown,
-            f'{ctx}.frame':         self.cam.frame,
-            f'{ctx}.toggle_proj':   self.cam.toggleProjection,
-            f'{ctx}.zoom_in':       zoom_in,
-            f'{ctx}.zoom_out':      zoom_out,
-            f'{ctx}.zoom_ortho_in': zoom_shift_in,
-            f'{ctx}.zoom_ortho_out':zoom_shift_out,
-        }
-
-        for symbol, func in hc_symbol_map.items():
-            if hou.hotkeys.isKeyMatch(ui_event, symbol):
-                func()
-                self.guides.update()
-                return True
-
-        # 3. Handle Navigation Fallback
-        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
-
-        # 4. 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']
-        guides = self.guides
         action_map = {
-            'frame': self.cam.frame,
-            'reset': self.cam.reset
+            'frame':         self.cam.frame,
+            'reset':         self.cam.reset,
+            'rotate_left':   self.cam.rotateLeft,
+            'rotate_right':  self.cam.rotateRight,
+            'rotate_up':     self.cam.rotateUp,
+            'rotate_down':   self.cam.rotateDown,
+            'trans_left':    self.cam.translateLeft,
+            'trans_right':   self.cam.translateRight,
+            'trans_up':      self.cam.translateUp,
+            'trans_down':    self.cam.translateDown,
+            'toggle_proj':   self.cam.toggleProjection,
+            'zoom_in':       zoom_in,
+            'zoom_out':      zoom_out,
+            'zoom_ortho_in': zoom_shift_in,
+            'zoom_ortho_out':zoom_shift_out,
         }
+
         if menu_item in action_map:
-            return action_map[menu_item]()
-        else:
+            res = action_map[menu_item]()
+            self.guides.update()
+            return res
+        
+        # Handle Guide toggles
+        if menu_item in self.guides.states:
             self.guides.states[menu_item] = kwargs[menu_item]
-            return
+            self.guides.update()
+            return True
+            
+        return False
 
     def onMenuPreOpen(self, kwargs):
         menu_id = kwargs['menu']
@@ -310,13 +263,11 @@ def createViewerStateTemplate():
     template.bindFactory(State)
     template.bindIcon('DESKTOP_application_sierra')
 
-    # Hotkey Definitions
-    # Using the official system to define context-sensitive hotkeys
+    # Define Commands and Default Bindings
     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']),
@@ -332,6 +283,7 @@ def createViewerStateTemplate():
         'zoom_out':      ('Zoom Out',      ['-']),
         'zoom_ortho_in': ('Zoom Ortho In', ['Shift+=']),
         'zoom_ortho_out':('Zoom Ortho Out',['Shift+-']),
+        'reset':         ('Reset',         ['r']),
     }
     
     for cmd_id, (label, defaults) in commands.items():
@@ -499,8 +451,10 @@ def createViewerStateTemplate():
     """ Context Menu """
 
     menu = hou.ViewerStateMenu('keycam_menu', 'Keycam Menu')
-    menu.addActionItem('frame', 'Frame', hotkey='h.pane.gview.state.obj.keycam.frame')
-    menu.addActionItem('reset', 'Reset')
+    ctx = 'h.pane.gview.state.obj.keycam'
+    
+    menu.addActionItem('frame', 'Frame', hotkey=f'{ctx}.frame')
+    menu.addActionItem('reset', 'Reset', hotkey=f'{ctx}.reset')
 
     set_view_menu = hou.ViewerStateMenu('set_view_menu', 'Set View')
     set_view_menu.addActionItem('top',    'Top')
@@ -522,8 +476,27 @@ 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')
+    menu.addToggleItem('toggle_proj', 'Toggle Projection', 0, hotkey=f'{ctx}.toggle_proj')
+    
+    # We add the remaining hotkey actions to a submenu to keep them registered
+    # but out of the main menu if desired.
+    nav_menu = hou.ViewerStateMenu('nav_menu', 'Navigation')
+    nav_menu.addActionItem('rotate_left',   'Rotate Left',    hotkey=f'{ctx}.rotate_left')
+    nav_menu.addActionItem('rotate_right',  'Rotate Right',   hotkey=f'{ctx}.rotate_right')
+    nav_menu.addActionItem('rotate_up',     'Rotate Up',      hotkey=f'{ctx}.rotate_up')
+    nav_menu.addActionItem('rotate_down',   'Rotate Down',    hotkey=f'{ctx}.rotate_down')
+    nav_menu.addSeparator()
+    nav_menu.addActionItem('trans_left',    'Translate Left',  hotkey=f'{ctx}.trans_left')
+    nav_menu.addActionItem('trans_right',   'Translate Right', hotkey=f'{ctx}.trans_right')
+    nav_menu.addActionItem('trans_up',      'Translate Up',    hotkey=f'{ctx}.trans_up')
+    nav_menu.addActionItem('trans_down',    'Translate Down',  hotkey=f'{ctx}.trans_down')
+    nav_menu.addSeparator()
+    nav_menu.addActionItem('zoom_in',       'Zoom In',         hotkey=f'{ctx}.zoom_in')
+    nav_menu.addActionItem('zoom_out',      'Zoom Out',        hotkey=f'{ctx}.zoom_out')
+    nav_menu.addActionItem('zoom_ortho_in', 'Zoom Ortho In',   hotkey=f'{ctx}.zoom_ortho_in')
+    nav_menu.addActionItem('zoom_ortho_out','Zoom Ortho Out',  hotkey=f'{ctx}.zoom_ortho_out')
+    
+    menu.addMenu(nav_menu)
     
     template.bindMenu(menu)