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

commit9666b0d252a19408ae49a920e6ae5901cca310d7
parent24c930ea1d
authorLucas Galante <[email protected]>
date2026-02-08 20:35
hc: repackage the package

 python3.11libs/hc/__init__.py      |  43 ++++++
 python3.11libs/hc/bindings.py      | 308 +++++++++++++++++++++++++++++++++++++
 python3.11libs/hc/cam.py           | 268 ++++++++++++++++++++++++++++++++
 python3.11libs/hc/defaultcam.py    |  24 +++
 python3.11libs/hc/geo.py           |  18 +++
 python3.11libs/hc/guides.py        | 245 +++++++++++++++++++++++++++++
 python3.11libs/hc/layouts.py       |  88 +++++++++++
 python3.11libs/hc/listener.py      | 100 ++++++++++++
 python3.11libs/hc/menuutils.py     |  28 ++++
 python3.11libs/hc/networkeditor.py | 143 +++++++++++++++++
 python3.11libs/hc/node.py          |  97 ++++++++++++
 python3.11libs/hc/pane.py          | 127 +++++++++++++++
 python3.11libs/hc/panel.py         | 226 +++++++++++++++++++++++++++
 python3.11libs/hc/pathtab.py       |  26 ++++
 python3.11libs/hc/prefs.py         |   3 +
 python3.11libs/hc/radial.py        | 186 ++++++++++++++++++++++
 python3.11libs/hc/sceneviewer.py   | 275 +++++++++++++++++++++++++++++++++
 python3.11libs/hc/session.py       | 295 +++++++++++++++++++++++++++++++++++
 python3.11libs/hc/status.py        |  69 +++++++++
 python3.11libs/hc/tab.py           |  73 +++++++++
 python3.11libs/hc/viewport.py      |  15 ++
 python3.11libs/hc/visualizers.py   | 133 ++++++++++++++++
 python3.11libs/hc/widgets.py       |  19 +++
 23 files changed, 2809 insertions(+)

diff --git a/python3.11libs/hc/__init__.py b/python3.11libs/hc/__init__.py
new file mode 100755
index 0000000..509cd63
--- /dev/null
+++ b/python3.11libs/hc/__init__.py
@@ -0,0 +1,43 @@
+from .bindings      import Bindings
+from .cam           import Cam
+from .defaultcam    import DefaultCam
+from .geo           import Geo
+from .guides        import Guides
+from .networkeditor import NetworkEditor
+from .node          import Node
+from .panel         import Panel
+from .pane          import Pane
+from .pathtab       import PathTab
+from .prefs         import Prefs
+from .radial        import Radial
+from .sceneviewer   import SceneViewer
+from .session       import Session
+from .status        import Status
+from .tab           import Tab
+from .viewport      import Viewport
+from .visualizers   import Visualizers
+from .widgets       import Widgets
+
+__version__ = "1.0.0"
+
+__all__ = [
+    "Bindings",
+    "Cam",
+    "DefaultCam",
+    "Geo",
+    "Guides",
+    "NetworkEditor",
+    "Node",
+    "Panel",
+    "Pane",
+    "PathTab",
+    "Prefs",
+    "Radial",
+    "Session",
+    "SceneViewer",
+    "Status",
+    "Tab",
+    "Viewport",
+    "Visualizers",
+    "Widgets"
+]
diff --git a/python3.11libs/hc/bindings.py b/python3.11libs/hc/bindings.py
new file mode 100644
index 0000000..03c5512
--- /dev/null
+++ b/python3.11libs/hc/bindings.py
@@ -0,0 +1,308 @@
+import hou, platform
+
+# KEY NAMES
+# uparrow, downarrow, leftarrow, rightarrow
+# alt, cmd, shift, ctrl, tab, del, backspace, enter, esc
+# f1...f12
+# pageup, pagedown, end, home, insert
+# /, \\
+# pad0...pad9
+# padstar, padslash
+# pause
+# space
+
+class Bindings:
+    def __init__(self):
+        return
+
+    def load(self):
+        self.clearAssignments()
+        self.addAssignments()
+
+    def clearAssignments(self):
+        assignments = (
+            #
+            # deskmgr
+            #
+            'deskmgr.new',
+            'deskmgr.save',
+            #
+            # h
+            #
+            'h.add_key',
+            'h.aliases',
+            'h.audio_panel',
+            'h.auto_key',
+            'h.cache_manager',
+            'h.channeleditor',
+            'h.clear_cop_caches',
+            'h.comp_project_manager',
+            'h.context_help',
+            'h.desktop_mgr',
+            'h.find',
+            'h.global_animation_options',
+            'h.mat_palette',
+            'h.merge',
+            'h.new',
+            'h.next_key',
+            'h.open',
+            'h.perf_monitor',
+            'h.python_shell',
+            'h.textport',
+            #
+            # h.pane
+            #
+            'h.pane.clone',
+            'h.pane.closetab',
+            'h.pane.copytab',
+            'h.pane.edit_bookmark',
+            'h.pane.editpathastext',
+            'h.pane.link_minus',
+            'h.pane.link_plus',
+            'h.pane.nexttab',
+            'h.pane.prevtab',
+            'h.pane.to_cop_view',
+            'h.pane.to_chanedit',
+            'h.pane.to_chop_view',
+            'h.pane.to_geosheet',
+            'h.pane.to_ipr_viewer',
+            'h.pane.to_mat_palette',
+            'h.pane.to_net',
+            'h.pane.to_old_net',
+            'h.pane.to_parm',
+            'h.pane.to_scene_view',
+            'h.pane.to_textport',
+            'h.pane.to_treeview',
+            #
+            # h.pane.bundle
+            #
+            'h.pane.bundle.addbundle',
+            'h.pane.bundle.duplicate',
+            #
+            # h.pane.chedit
+            #
+            'h.pane.chedit.bsave',
+            'h.pane.chedit.load',
+            'h.pane.chedit.save',
+            'h.pane.chedit.scroll_to_current_frame',
+            'h.pane.chedit.show_groups',
+            'h.pane.chedit.show_layers',
+            'h.pane.chedit.show_parmbox',
+            #
+            # h.pane.chlist
+            #
+            'h.pane.chlist.parmbox.show_filter',
+            #
+            # h.pane.group
+            #
+            'h.pane.group.addgroup',
+            'h.pane.group.equals',
+            'h.pane.group.minus',
+            'h.pane.group.selectall',
+            'h.pane.group.selectnone',
+            'h.pane.group.togpin',
+            'h.pane.group.togpinall',
+            #
+            # h.pane.gview
+            #
+            'h.pane.gview.increase_subd',
+            'h.pane.gview.decrease_subd',
+            'h.pane.gview.delete',
+            'h.pane.gview.dockhudhandles',
+            'h.pane.gview.edit_snap_options',
+            'h.pane.gview.operator_menu_branch',
+            'h.pane.gview.state.view.home_selection',
+            'h.pane.gview.repeat_current',
+            'h.pane.gview.repeat_current_branch',
+            'h.pane.gview.restore_prevcam',
+            'h.pane.gview.selectstylebox',
+            'h.pane.gview.selectstylebrush',
+            'h.pane.gview.selectstylelaser',
+            'h.pane.gview.selectstylelasso',
+            #
+            # h.pane.gview.handle
+            #
+            'h.pane.gview.handle.remove_keyframe',
+            'h.pane.gview.handle.set_keyframe',
+            #
+            # h.pane.gview.handle.agentcollisionlayer
+            #
+            'h.pane.gview.handle.agentcollisionlayer.boxhandle',
+            'h.pane.gview.handle.agentcollisionlayer.orientationhandle',
+            #
+            # h.pane.gview.handle.agentconfigurejoints
+            #
+            'h.pane.gview.handle.agentconfigurejoints.conehandle',
+            'h.pane.gview.handle.agentconfigurejoints.childrotationhandle',
+            #
+            # h.pane.gview.state
+            #
+            'h.pane.gview.state.volatile_chmodify',
+            #
+            # h.pane.wsheet
+            #
+            'h.pane.wsheet.change_wire_style',
+            'h.pane.wsheet.add_image',
+            'h.pane.wsheet.add_postit',
+            'h.pane.wsheet.addfileop',
+            'h.pane.wsheet.allowdroponwire',
+            'h.pane.wsheet.batch_rename',
+            'h.pane.wsheet.bypass_mode',
+            'h.pane.wsheet.color_palette',
+            'h.pane.wsheet.doautomovenodes',
+            'h.pane.wsheet.dosnapping',
+            'h.pane.wsheet.expose',
+            'h.pane.wsheet.flag1_mode',
+            'h.pane.wsheet.flag2_mode',
+            'h.pane.wsheet.flag3_mode',
+            'h.pane.wsheet.flag4_mode',
+            'h.pane.wsheet.home',
+            'h.pane.wsheet.home_selected',
+            'h.pane.wsheet.up',
+            'h.pane.wsheet.down',
+            'h.pane.wsheet.left',
+            'h.pane.wsheet.right',
+            'h.pane.wsheet.edit_images',
+            'h.pane.wsheet.layout',
+            'h.pane.wsheet.layout_all',
+            'h.pane.wsheet.layout_mode',
+            'h.pane.wsheet.maximize',
+            'h.pane.wsheet.maximize_adjust',
+            'h.pane.wsheet.minimize',
+            'h.pane.wsheet.minimize_adjust',
+            'h.pane.wsheet.mode_switch',
+            'h.pane.wsheet.node_quick_nav',
+            'h.pane.wsheet.open_display_options',
+            'h.pane.wsheet.open_groups',
+            'h.pane.wsheet.open_treeview',
+            'h.pane.wsheet.opmenu.flag_debug',
+            'h.pane.wsheet.rename_node',
+            'h.pane.wsheet.scope_chans',
+            'h.pane.wsheet.select',
+            'h.pane.wsheet.select_mode',
+            'h.pane.wsheet.snap_netbox_bounds',
+            'h.pane.wsheet.stitch_mode',
+            'h.pane.wsheet.zoom_in',
+            'h.pane.wsheet.zoom_out',
+            #
+            # h.playbar
+            #
+            'h.playbar.next_bookmark',
+            'h.playbar.prev_bookmark',
+            'h.playbar.show_range',
+            #
+            # inputfield
+            #
+            'inputfield.context_help',
+            'inputfield.editor',
+        )
+
+        for assignment in assignments:
+            context = assignment.rpartition('.')[0]
+            symbol = assignment
+            hou.hotkeys.clearAssignments(context, symbol)
+
+
+    def addAssignments(self):
+        assignments = (
+            #
+            # h
+            #
+            ('h.hc_panel', 'alt+shift+d'),
+            ('h.stow_shelf', 'ctrl+]'),
+            #
+            # h.pane
+            #
+            ('h.pane.shelf',   'ctrl+['),
+            ('h.pane.nexttab', 'ctrl+tab'),
+            ('h.pane.prevtab', 'ctrl+shift+tab'),
+            #
+            # h.pane.gview
+            #
+            ('h.pane.gview.refplane',                  'shift+g'),
+            ('h.pane.gview.state.view.home_selection', 'shift+s'),
+            #
+            # h.pane.parms
+            #
+            ('h.pane.parms.edit_expression', 'alt+e'),
+            #
+            # h.pane.editparms
+            #
+            ('h.pane.editparms.selectall', 'alt+h'),
+            #
+            # h.pane.wsheet
+            #
+            ('h.pane.wsheet.bypass_mode',       'q'),
+            # ('h.pane.wsheet.desel_noncurrent',  'ctrl+d'),
+            ('h.pane.wsheet.doautomovenodes',   'ctrl+pad9'),
+            ('h.pane.wsheet.down',              'shift+j'),
+            ('h.pane.wsheet.drop_on_wire_mode', 'pad8'),
+            ('h.pane.wsheet.flag2_mode',        'w'),
+            ('h.pane.wsheet.flag3_mode',        'e'),
+            ('h.pane.wsheet.home_selected',     'shift+s'),
+            ('h.pane.wsheet.jump_mark_1',       'pad1'),
+            ('h.pane.wsheet.jump_mark_2',       'pad2'),
+            ('h.pane.wsheet.jump_mark_3',       'pad3'),
+            ('h.pane.wsheet.jump_mark_4',       'pad4'),
+            ('h.pane.wsheet.jump_mark_5',       'pad5'),
+            ('h.pane.wsheet.left',              'shift+h'),
+            ('h.pane.wsheet.node_quick_nav',    'ctrl+f'),
+            ('h.pane.wsheet.right',             'shift+l'),
+            ('h.pane.wsheet.rename_node',       'ctrl+r'),
+            ('h.pane.wsheet.set_mark_1',        'ctrl+pad1'),
+            ('h.pane.wsheet.set_mark_2',        'ctrl+pad2'),
+            ('h.pane.wsheet.set_mark_3',        'ctrl+pad3'),
+            ('h.pane.wsheet.set_mark_4',        'ctrl+pad4'),
+            ('h.pane.wsheet.set_mark_5',        'ctrl+pad5'),
+            ('h.pane.wsheet.select',            'ctrl+a'),
+            ('h.pane.wsheet.up',                'shift+k'),
+            #
+            # h.pane.textport
+            #
+            ('h.pane.textport.up',             'ctrl+p'),
+            ('h.pane.textport.down',           'ctrl+n'),
+            ('h.pane.textport.next',           'ctrl+f'),
+            ('h.pane.textport.prev',           'ctrl+b'),
+            ('h.pane.textport.top',            'ctrl+a'),
+            ('h.pane.textport.bottom',         'ctrl+e'),
+            ('h.pane.textport.nextword',       'alt+f'),
+            ('h.pane.textport.pageup',         'alt+v'),
+            ('h.pane.textport.pagedown',       'ctrl+v'),
+            ('h.pane.textport.firstline',      'ctrl+shift+,'),
+            ('h.pane.textport.lastline',       'ctrl+shift+.'),
+            ('h.pane.textport.jump_to_line',   'alt+g'),
+            ('h.pane.textport.selectup',       'ctrl+shift+p'),
+            ('h.pane.textport.selectdown',     'ctrl+shift+n'),
+            ('h.pane.textport.selectnext',     'ctrl+shift+f'),
+            ('h.pane.textport.selectprev',     'ctrl+shift+b'),
+            ('h.pane.textport.selectbottom',   'ctrl+shift+e'),
+            ('h.pane.textport.selecttop',      'ctrl+shift+a'),
+            ('h.pane.textport.selectnextword', 'alt+shift+f'),
+            ('h.pane.textport.toggle_comment', 'ctrl+/'),
+            #
+            # inputfield
+            #
+            ('inputfield.up',        'ctrl+p'),
+            ('inputfield.down',      'ctrl+n'),
+            ('inputfield.next',      'ctrl+f'),
+            ('inputfield.prev',      'ctrl+b'),
+            ('inputfield.next_word', 'alt+f'),
+            ('inputfield.prev_word', 'alt+b'),
+            ('inputfield.end',       'ctrl+e'),
+            ('inputfield.home',      'ctrl+a'),
+        )
+
+        for assignment in assignments:
+            context = assignment[0].rpartition('.')[0]
+            symbol = assignment[0]
+            key = assignment[1]
+            hou.hotkeys.addAssignment(context, symbol, key)
+
+
+    def bindCommands(self):
+
+        list = ()
+        for command in list:
+            context = binding[0]
+            command = binding[1]
+            hou.hotkeys.addCommandBinding(context, command)
diff --git a/python3.11libs/hc/cam.py b/python3.11libs/hc/cam.py
new file mode 100644
index 0000000..aa0ea99
--- /dev/null
+++ b/python3.11libs/hc/cam.py
@@ -0,0 +1,268 @@
+import hou
+from .geo import Geo
+from .sceneviewer import SceneViewer
+
+"""
+Accepts 1 camera node object and 1 scene viewer object as arguments.
+"""
+
+
+class Cam:
+    def __init__(self, node, hou_scene_viewer):
+        self.node             = node
+        self.hou_scene_viewer = hou_scene_viewer
+        self.scene_viewer     = SceneViewer(hou_scene_viewer)
+
+        self.node      = node
+        self._t            = hou.Vector3(0, 0, 0)
+        self._r            = hou.Vector3(0, 0, 0)
+        self._p            = hou.Vector3(0, 0, 0)
+        self._ow           = 10
+        self._projection   = 'perspective'
+        self._resx         = 1000
+        self._resy         = 1000
+        self._aspect_ratio = 1
+        self.local_x       = hou.Vector3(1, 0, 0)
+        self.local_y       = hou.Vector3(0, 1, 0)
+        self.local_z       = hou.Vector3(0, 0, 1)
+        self.global_x      = hou.Vector3(1, 0, 0)
+        self.global_y      = hou.Vector3(0, 1, 0)
+        self.global_z      = hou.Vector3(0, 0, 1)
+        # self.zoom          = 10
+        self.delta_t       = 1
+        self.delta_r       = 15
+        self.delta_zoom    = 1
+        self.target        = None
+
+        self.fitAspectRatio()
+        self.lock()
+        self.reset()
+
+    """ Movement """
+
+    def center(self):
+        centroid = self.scene_viewer.geo().centroid()
+        self.t = hou.Vector3(centroid)
+        self.p = hou.Vector3(centroid)
+
+    def frame(self):
+        centroid = self.geo().centroid()
+        self.t = hou.Vector3(centroid)
+        self.p = hou.Vector3(centroid)
+        # self.ow = 10
+        self.setZoom(10)
+
+    def home(self):
+        centroid = self.geo().centroid()
+        self.t = centroid
+        self.p = centroid
+        # self.ow = 10
+        # self.setZoom(6)
+
+    # def movePivot(self):
+        # If origin
+        # if self.target == 0:
+        #     self.t = [0, 0, self.zoom]
+        #     self.r = [45, 45, 0]
+        #     self.p = [0, 0, self.zoom * -1]
+        #     self.ow = 10
+
+    def rotateUp(self):
+        delta = hou.Vector3(self.delta_r, 0, 0)
+        self.r += delta
+        m = hou.hmath.buildRotateAboutAxis(self.local_x, self.delta_r)
+        self.t -= self.p
+        self.t *= m
+        self.t += self.p
+        self.local_x *= m
+        self.local_y *= m
+        self.local_z *= m
+
+    def rotateDown(self):
+        delta = hou.Vector3(-self.delta_r, 0, 0)
+        self.r += delta
+        m = hou.hmath.buildRotateAboutAxis(self.local_x, -self.delta_r)
+        self.t -= self.p
+        self.t *= m
+        self.t += self.p
+        self.local_x *= m
+        self.local_y *= m
+        self.local_z *= m
+
+    def rotateLeft(self):
+        delta = hou.Vector3(0, -self.delta_r, 0)
+        self.r += delta
+        m = hou.hmath.buildRotateAboutAxis(self.global_y, -self.delta_r)
+        self.t -= self.p
+        self.t *= m
+        self.t += self.p
+        self.local_x *= m
+        self.local_y *= m
+        self.local_z *= m
+
+    def rotateRight(self):
+        delta = hou.Vector3(0, self.delta_r, 0)
+        self.r += delta
+        m = hou.hmath.buildRotateAboutAxis(self.global_y, self.delta_r)
+        self.t -= self.p
+        self.t *= m
+        self.t += self.p
+        self.local_x *= m
+        self.local_y *= m
+        self.local_z *= m
+
+    def setZoom(self, amt):
+        move = self.local_z * amt
+        self.t += move
+
+    def translateUp(self):
+        move = self.local_y * self.delta_t
+        self.t += move
+        self.p += move
+
+    def translateDown(self):
+        move = self.local_y * self.delta_t * -1
+        self.t += move
+        self.p += move
+
+    def translateLeft(self):
+        move = self.local_x * self.delta_t * -1
+        self.t += move
+        self.p += move
+
+    def translateRight(self):
+        move = self.local_x * self.delta_t
+        self.t += move
+        self.p += move
+
+    def zoom(self, dir):
+        dir_map = {'out': 1, 'in': -1}
+        move = self.local_z * self.delta_zoom * dir_map[dir]
+        self.t += move
+
+    def zoomOrtho(self, dir):
+        dir_map = {'out': 1, 'in': -1}
+        self.ow += self.delta_zoom * dir_map[dir]
+
+
+    """ Props """
+
+    @property
+    def t(self):
+        return self._t
+    @t.setter
+    def t(self, val):
+        self._t = val
+        self.node.parmTuple('t').set(val)
+
+    @property
+    def r(self):
+        return self._r
+    @r.setter
+    def r(self, val):
+        self._r = val
+        self.node.parmTuple('r').set(val)
+
+    @property
+    def p(self):
+        return self._p
+    @p.setter
+    def p(self, val):
+        self._p = val
+
+    @property
+    def ow(self):
+        return self._ow
+    @ow.setter
+    def ow(self, val):
+        self._ow = val
+        self.node.parm('orthowidth').set(val)
+
+    @property
+    def projection(self):
+        return self._projection
+    @projection.setter
+    def projection(self, val):
+        self._projection = val
+        self.node.parm('projection').set(val)
+
+    @property
+    def resx(self):
+        return self._resx
+    @resx.setter
+    def resx(self, val):
+        self._resx = val
+        self.node.parm('resx').set(val)
+
+    @property
+    def resy(self):
+        return self._resy
+    @resy.setter
+    def resy(self, val):
+        self._resy = val
+        self.node.parm('resy').set(val)
+
+    @property
+    def aspect_ratio(self):
+        return self._aspect_ratio
+    @aspect_ratio.setter
+    def aspect_ratio(self, val):
+        self._aspect_ratio = val
+        self.node.parm('aspect').set(val)
+
+    """ Util """
+
+    def fitAspectRatio(self):
+        self.resx = 1000
+        self.resy = 1000
+        ratio = self.viewport().size()[2] / self.viewport().size()[3]
+        self.aspect_ratio = ratio
+
+    def geo(self):
+        return self.scene_viewer.geo()
+
+    def lock(self):
+        viewport = self.viewport()
+        viewport.setCamera(self.node)
+        viewport.lockCameraToView(1)
+
+    def reset(self):
+        self.t          = hou.Vector3(0, 0, 2)
+        self.r          = hou.Vector3(0, 0, 0)
+        self.p          = hou.Vector3(0, 0, 0)
+        # self.zoom       = 10
+        self.ow         = 10
+        self.delta_t    = 1
+        self.delta_r    = 15
+        self.delta_zoom = 1
+        self.local_x    = hou.Vector3(1, 0, 0)
+        self.local_y    = hou.Vector3(0, 1, 0)
+        self.local_z    = hou.Vector3(0, 0, 1)
+        self.global_x   = hou.Vector3(1, 0, 0)
+        self.global_y   = hou.Vector3(0, 1, 0)
+        self.global_z   = hou.Vector3(0, 0, 1)
+
+    def setView(self):
+        view_map = {
+            'top':    hou.Vector3(270, 0, 0),
+            'bottom': hou.Vector3(90, 0, 0),
+            'front':  hou.Vector3(0, 180, 0),
+            'back':   hou.Vector3(0, 0, 0),
+            'right':  hou.Vector3(0, 90, 0),
+            'left':   hou.Vector3(0, 270, 0)
+        }
+        self.r = view_map[self.view]
+
+    def toggleProjection(self):
+        projection_map = {
+            'perspective': 'ortho',
+            'ortho':       'perspective'
+        }
+        self.projection = projection_map[self.projection]
+
+    def unlock(self):
+        self.viewport().lockCameraToView(0)
+
+    def viewport(self):
+        return self.scene_viewer.allViewports()[3]
+
diff --git a/python3.11libs/hc/defaultcam.py b/python3.11libs/hc/defaultcam.py
new file mode 100644
index 0000000..a5e7074
--- /dev/null
+++ b/python3.11libs/hc/defaultcam.py
@@ -0,0 +1,24 @@
+import hou
+
+class DefaultCam:
+    def __init__(self, hou_defaultcam):
+        self.hou_defaultcam = hou_defaultcam
+
+    def frame(self):
+        for viewport in self.viewports():
+            hou_defaultcam = viewport.camera()
+            # Is cam default or node.
+            if not hou_defaultcam:
+                viewport.frameAll()
+
+    def nextView(self):
+        return
+
+    def rotate(self, key):
+        return
+
+    def translate(self, key):
+        return
+
+    def zoom(self):
+        return
diff --git a/python3.11libs/hc/geo.py b/python3.11libs/hc/geo.py
new file mode 100644
index 0000000..695f61d
--- /dev/null
+++ b/python3.11libs/hc/geo.py
@@ -0,0 +1,18 @@
+import hou
+
+class Geo:
+    def __init__(self, hou_geo):
+        self.hou_geo = hou_geo
+
+    def bbox(self):
+        return self.hou_geo.boundingBox()
+
+    def centroid(self):
+        geo_out = hou.Geometry()
+        extract_centroid = hou.sopNodeTypeCategory().nodeVerb("extractcentroid")
+        extract_centroid.setParms(
+            {'partitiontype': 2}
+        )
+        extract_centroid.execute(geo_out, [self.hou_geo])
+        pt = geo_out.point(0)
+        return pt.position()
diff --git a/python3.11libs/hc/guides.py b/python3.11libs/hc/guides.py
new file mode 100644
index 0000000..b266650
--- /dev/null
+++ b/python3.11libs/hc/guides.py
@@ -0,0 +1,245 @@
+import hou
+
+class Guides:
+    def __init__(self, state):
+        self.state            = state
+        self.cam_node         = state.cam_node
+        self.cam              = state.cam
+        self.hou_scene_viewer = state.hou_scene_viewer
+        self.scene_viewer     = state.scene_viewer
+
+        self.options = {
+            'axis_size': 1,
+            'tie_axis_to_radius': 0
+        }
+
+        self.states = {
+            'cam_axis':   0,
+            'pivot_axis': 1,
+            'bbox':       0,
+            'perim':      0,
+            'pivot_2d':   0,
+            'pivot_3d':   0,
+            'ray':        0
+        }
+
+        self.cam_axis = hou.GeometryDrawable(
+            scene_viewer=self.hou_scene_viewer,
+            geo_type=hou.drawableGeometryType.Line,
+            name='cam_axis'
+        )
+
+        self.pivot_axis = hou.GeometryDrawable(
+            scene_viewer=self.hou_scene_viewer,
+            geo_type=hou.drawableGeometryType.Line,
+            name='pivot_axis',
+            params={
+                'color1': hou.Vector4((1, 1, 1, 0.5))
+            }
+        )
+
+        self.bbox = hou.GeometryDrawable(
+            scene_viewer=self.hou_scene_viewer,
+            geo_type=hou.drawableGeometryType.Line,
+            name='bbox',
+            params={
+                'color1': hou.Vector4((1, 1, 1, 0.3)),
+                'fade_factor': 0.0
+            }
+        )
+
+        self.perim = hou.GeometryDrawable(
+            scene_viewer=self.hou_scene_viewer,
+            geo_type=hou.drawableGeometryType.Line,
+            name='perim'
+        )
+
+        self.pivot_2d = hou.GeometryDrawable(
+            scene_viewer=self.hou_scene_viewer,
+            geo_type=hou.drawableGeometryType.Line,
+            name='pivot_2d'
+        )
+
+        self.pivot_3d = hou.GeometryDrawable(
+            scene_viewer=self.hou_scene_viewer,
+            geo_type=hou.drawableGeometryType.Face,
+            name='pivot_3d',
+            params={
+                'color1': hou.Vector4(0.2, 0.8, 0.2, 0.6),
+                'fade_factor': 0.2
+            }
+        )
+
+        self.ray = hou.GeometryDrawable(
+            scene_viewer=self.hou_scene_viewer,
+            geo_type=hou.drawableGeometryType.Line,
+            name='ray',
+            params={
+                'color1': hou.Vector4((1, 0.8, 1, 0.5))
+            }
+        )
+
+    def draw(self, kwargs):
+        func_map = {
+            'bbox':       self.bbox.draw,
+            'cam_axis':   self.cam_axis.draw,
+            'pivot_axis': self.pivot_axis.draw,
+            'perim':      self.perim.draw,
+            'pivot_2d':   self.pivot_2d.draw,
+            'pivot_3d':   self.pivot_3d.draw,
+            'ray':        self.ray.draw
+        }
+        for name, value in self.states.items():
+            if value:
+                func_map[name](kwargs['draw_handle'], {})
+
+    def update(self):
+        func_map = {
+            'bbox':       self.makeBbox,
+            'cam_axis':   self.makeCamAxis,
+            'pivot_axis': self.makePivotAxis,
+            'perim':      self.makePerim,
+            'pivot_2d':   self.makePivot2d,
+            'pivot_3d':   self.makePivot3d,
+            'ray':        self.makeRay
+        }
+        for name, value in self.states.items():
+            if value:
+                func_map[name]()
+        # self.cam_axis.show(self.states['cam_axis'])
+        self.pivot_axis.show(self.states['pivot_axis'])
+        self.bbox.show(self.states['bbox'])
+        # self.cam.show(self.states['cam'])
+        # self.perim.show(self.states['perim'])
+        # self.pivot_2d.show(self.states['pivot_2d'])
+        self.pivot_3d.show(self.states['pivot_3d'])
+        # self.ray.show(self.states['ray'])
+
+
+    """ Construction """
+
+    def makeCamAxis(self):
+        axes = (
+            self.cam.local_x,
+            self.cam.local_y,
+            self.cam.local_z
+        )
+        geo = hou.Geometry()
+        for i in range(3):
+            P0 = self.cam.t + axes[i]
+            P1 = self.cam.t + axes[i] * -1
+            pts = geo.createPoints((P0, P1))
+            poly = geo.createPolygon(is_closed=False)
+            poly.addVertex(pts[0])
+            poly.addVertex(pts[1])
+        self.cam_axis.setGeometry(geo)
+
+    def makePivotAxis(self):
+        print('test')
+        axes = (
+            self.cam.local_x,
+            self.cam.local_y,
+            self.cam.local_z
+            # hou.Vector3(1, 0, 0),
+            # hou.Vector3(0, 1, 0),
+            # hou.Vector3(0, 0, 1)
+        )
+        colors = (
+            [1.0, 0.7, 0.7],
+            [0.7, 1.0, 0.7],
+            [0.7, 0.7, 1.0]
+        )
+        geo = hou.Geometry()
+        geo.addAttrib(hou.attribType.Point, 'Cd', (0.1, 0.1, 0.1))
+        for i in range(3):
+            P0 = self.cam.p + axes[i]
+            P1 = self.cam.p + axes[i] * -1
+            pts = geo.createPoints((P0, P1))
+            pts[0].setAttribValue('Cd', colors[i])
+            pts[1].setAttribValue('Cd', colors[i])
+            poly = geo.createPolygon(is_closed=False)
+            poly.addVertex(pts[0])
+            poly.addVertex(pts[1])
+        self.pivot_axis.setGeometry(geo)
+        self.pivot_axis.setParams(
+            {'fade_factor': 0.0}
+        )
+
+    def makeBbox(self):
+        guide_geo = hou.Geometry()
+        target_geo = self.scene_viewer.geo()
+        bbox = target_geo.bbox()
+        box = hou.sopNodeTypeCategory().nodeVerb('box')
+        box.setParms(
+            {
+                'size': bbox.sizevec(),
+                't':    bbox.center()
+            }
+        )
+        box.execute(guide_geo, [])
+        self.bbox.setGeometry(guide_geo)
+
+    def makePerim(self):
+        guide_geo = hou.Geometry()
+        circle = hou.sopNodeTypeCategory().nodeVerb('circle')
+        circle.setParms(
+            {
+                'divs': 128,
+                'type': 1,
+                't':    self.cam.p,
+                'scale': self.cam.p.distanceTo(self.cam.t),
+                'orient': 2
+            }
+        )
+        circle.execute(guide_geo, [])
+        self.perim.setParams(
+            {
+                'color1': hou.Vector4(1.0, 1.0, 1.0, 0.25),
+                'fade_factor': 1.0
+            }
+        )
+        self.perim.setGeometry(guide_geo)
+
+    def makePivot2d(self):
+        guide_geo = hou.Geometry()
+        circle = hou.sopNodeTypeCategory().nodeVerb('circle')
+        circle.setParms(
+            {
+                'type': 1,
+                'r':    self.cam.r,
+                't':    self.cam.p,
+                'scale': self.cam.ow * 0.0075
+            }
+        )
+        circle.execute(guide_geo, [])
+        self.pivot_2d.setParams(
+            {
+                'color1': hou.Vector4(0.0, 0.0, 1, 1),
+                'fade_factor': 1.0
+            }
+        )
+        self.pivot_2d.setGeometry(guide_geo)
+
+    def makePivot3d(self):
+        guide_geo = hou.Geometry()
+        sphere = hou.sopNodeTypeCategory().nodeVerb('sphere')
+        scale = self.cam.t.distanceTo(self.cam.p) * 0.01
+        sphere.setParms(
+            {
+                'freq':  7,
+                'scale': scale,
+                'type':  1,
+                't':     self.cam.p
+            }
+        )
+        sphere.execute(guide_geo, [])
+        self.pivot_3d.setGeometry(guide_geo)
+
+    def makeRay(self):
+        guide_geo = hou.Geometry()
+        guide_geo.addAttrib(hou.attribType.Point, 'Cd', (1, 0, 0))
+        pts = guide_geo.createPoints((self.cam.p, self.cam.t))
+        poly = guide_geo.createPolygon()
+        poly.addVertex(pts[0])
+        poly.addVertex(pts[1])
+        self.ray.setGeometry(guide_geo)
diff --git a/python3.11libs/hc/layouts.py b/python3.11libs/hc/layouts.py
new file mode 100644
index 0000000..d5edef8
--- /dev/null
+++ b/python3.11libs/hc/layouts.py
@@ -0,0 +1,88 @@
+from .session import Session
+
+def setLayoutQuad(self):
+    session = Session()
+    session.clearLayout()
+    session.allTabs()[0].setType("PythonShell")
+    session.allPanes()[0].splitHorizontal()
+    session.allPanes()[0].splitVertical()
+    session.allPanes()[1].splitVertical()
+
+def setLayoutRamp(self):
+    session = Session()
+    session.removeEventLoopCallbacks()
+    session.clearLayout()
+    session.allPanes()[0].splitVertical()
+    session.allTabs()[1].setType("Parm")
+    session.allPanes()[1].setSplitRatio(0.3)
+    session.allPanes()[1].createTab()
+
+def setLayoutTriH(self):
+    session = Session()
+    session.removeEventLoopCallbacks()
+    session.clearLayout()
+    # Make panes
+    session.allTabs()[0].setType("PythonShell")
+    session.allPanes()[0].splitHorizontal()
+    session.allPanes()[1].splitHorizontal()
+    # Make tabs
+    session.allPanes()[1].createTab("PythonShell")
+    session.allPanes()[1].allTabs()[0].setIsCurrentTab()
+    # Set types
+    session.allPanes()[0].allTabs()[0].setType("SceneViewer")
+    session.allPanes()[1].allTabs()[0].setType("Parm")
+    session.allPanes()[1].allTabs()[1].setType("DetailsView")
+    session.allPanes()[2].allTabs()[0].setType("NetworkEditor")
+    # Ratios
+    session.allPanes()[0].setSplitFraction(0.5)
+    hou.session.last_pane = self.pane()
+    hou.ui.addEventLoopCallback(triHCallback)
+
+def triHCallback(session):
+    panes = session.panes()
+    pane = session.pane()
+    if str(pane) != str(hou.session.lastPane):
+        hou.session.last_pane = pane
+        if str(pane) == str(panes[1]):
+            pane.setSplitFraction(0.6)
+        elif str(pane) == str(panes[2]):
+            pane.setSplitFraction(0.3)
+    return True
+
+def setLayoutTriV(session):
+    # Remove callbacks
+    session.removeEventLoopCallbacks()
+    # Reset layout
+    session.clearLayout()
+    # Make panes
+    session.allPanes()[0].allTabs()[0].setType("PythonShell")
+    session.allPanes()[0].splitHorizontal()
+    session.allPanes()[1].splitVertical()
+    # Make pane tabs
+    session.allPanes()[1].createTab("PythonShell")
+    session.allPanes()[1].allTabs()[0].setIsCurrentTab()
+    # Set types
+    session.allPanes()[0].allTabs()[0].setType("SceneViewer")
+    session.allPanes()[1].allTabs()[0].setType("Parm")
+    session.allPanes()[1].allTabs()[1].setType("DetailsView")
+    session.allPanes()[2].allTabs()[0].setType("NetworkEditor")
+    # Set ratios
+    session.allPanes()[0].setSplitFraction(0.66)
+    # Ok
+    hou.session.last_pane = self.pane()
+    hou.ui.addEventLoopCallback(self.triVCallback)
+
+def triVCallback(session):
+    panes = session.allPanes()
+    pane = session.currentPane()
+    if str(pane) != str(hou.session.last_pane):
+        hou.session.last_pane = pane
+        if str(pane) == str(panes[0]):
+            pane.setSplitFraction(0.7)
+        elif str(pane) == str(panes[1]):
+            panes[0].setSplitFraction(0.4)
+            pane.setSplitFraction(0.33)
+        elif str(pane) == str(panes[2]):
+            panes[0].setSplitFraction(0.4)
+            pane.setSplitFraction(0.66)
+    return True
diff --git a/python3.11libs/hc/listener.py b/python3.11libs/hc/listener.py
new file mode 100644
index 0000000..c8d4c5a
--- /dev/null
+++ b/python3.11libs/hc/listener.py
@@ -0,0 +1,100 @@
+import hou
+from .session import Session
+from .pane import Pane
+from .tab import Tab
+
+
+class Listener():
+    def __init__(self):
+        self.report_tab = 1
+        self.tab = None
+        self.pane = None
+        self.session = None
+        self.pane = None
+        self.tab = None
+        self.sceneviewer = None
+        self.networkeditor = None
+        self.project_path = hou.hipFile.path()
+        self.network_path = None
+        self.tab_type = None
+        self.update_objects()
+        return
+
+    def start(self):
+        hou.ui.addEventLoopCallback(self.listener)
+
+    def stop(self):
+        hou.ui.removeEventLoopCallback(self.listener)
+
+    def update_objects(self):
+        self.tab = hou.ui.paneTabUnderCursor()
+        self.pane = hou.ui.paneUnderCursor()
+
+        self.session = Session()
+        self.pane = Pane(hou.ui.paneUnderCursor())
+        self.tab = Tab(self.tab)
+
+        if self.tab != None:
+
+            if self.tab.hasNetworkControls():
+                self.network_path = self.tab.path()
+            self.tab_type = self.tab.type()
+
+            if self.tab.type() == "SceneViewer:"
+                self.sceneviewer = SceneViewer(self.tab)
+            else:
+                self.sceneviewer = None
+
+            if self.tab.type() == "NetworkEditor":
+                self.networkeditor = NetworkEditor(self.tab)
+            else:
+                self.networkeditor = None
+
+    def listener(self):
+        tab = hou.ui.paneTabUnderCursor()
+        if tab == None:
+            hou.session.tab = None
+        elif tab != hou.session.tab:
+            print(tab)
+            self.update_objects()
+
+    def lists(self):
+        # Arrays for navigating pane tabs
+        self.tab_types = (
+            hou.paneTabType.ApexEditor,
+            hou.paneTabType.CompositorViewer,
+            hou.paneTabType.DetailsView,
+            hou.paneTabType.NetworkEditor,
+            hou.paneTabType.Parm,
+            hou.paneTabType.PythonPanel,
+            hou.paneTabType.PythonShell,
+            hou.paneTabType.SceneViewer,
+            hou.paneTabType.Textport
+        )
+        self.tab_type_names = (
+            "ApexEditor",
+            "CompositorViewer",
+            "DetailsView",
+            "NetworkEditor",
+            "Parm",
+            "PythonPanel",
+            "PythonShell",
+            "SceneViewer",
+            "Textport"
+        )
+        self.tab_names = [tab.name() for tab in hou.session.session.tabs()]
+
+        # Populate pane tab labels array
+        self.tab_labels = []
+        for tab in self.session.tabs():
+            index = self.tab_types.index(tab.type())
+            label = self.tab_type_names[index]
+            self.tab_labels.append(label)
+
+    def networkPath(self):
+        return str(hou.session.tab.pwd())
+
+    def projectPath(self):
+        return hou.hipFile.name()
+        # ct = self.project_path.count("/")
+        # self.project_path = self.project_path.split("/", ct - 2)[-1]
diff --git a/python3.11libs/hc/menuutils.py b/python3.11libs/hc/menuutils.py
new file mode 100644
index 0000000..a8f7701
--- /dev/null
+++ b/python3.11libs/hc/menuutils.py
@@ -0,0 +1,28 @@
+import hou
+
+
+def vector3Test(kwargs):
+    parms = kwargs['parms']
+    print(parms[0])
+    if len(parms) == 3 \
+    and parms[0].parmTemplate().type() == hou.parmTemplateType.Float \
+    and parms[1].parmTemplate().type() == hou.parmTemplateType.Float \
+    and parms[2].parmTemplate().type() == hou.parmTemplateType.Float:
+        return True
+    else:
+        return False
+
+def setVector3(kwargs, x, y, z):
+    parms = kwargs['parms']
+    name0 = parms[0].name()
+    name1 = parms[1].name()
+    name2 = parms[2].name()
+    node = parms[0].node()
+    node.setParmExpressions({name0: x, name1: y, name2: z})
+
+def vector3XMaster(kwargs):
+    name0 = parms[0].name()
+    name1 = parms[1].name()
+    name2 = parms[2].name()
+    node = parms[0].node()
+    node.setParmExpressions({name1: 'ch("'+name0+'")', name2: 'ch("'+name0+'")'})
diff --git a/python3.11libs/hc/networkeditor.py b/python3.11libs/hc/networkeditor.py
new file mode 100644
index 0000000..daf5c36
--- /dev/null
+++ b/python3.11libs/hc/networkeditor.py
@@ -0,0 +1,143 @@
+import hou, math, types
+from .pathtab import PathTab
+from .prefs import Prefs
+
+
+class NetworkEditor(PathTab):
+    def __init__(self, hou_tab):
+        self.hou_tab = hou_tab
+        self.delta_t = 2
+
+    """ Move network objects """
+
+    def arrangeNodes(self):
+        return
+
+    def snapToGrid(self, node):
+        P = node.position()
+        P[0] = round(P[0] - 0.5) + 0.5
+        P[1] = round(P[1] - 0.85) + 0.85
+        node.setPosition(P)
+
+    def translateNodes(self, direction):
+        for node in self.pwd().selectedChildren():
+            self.snapToGrid(node)
+            P = node.position()
+            # do the move
+            if direction == 'up':
+                P[1] += 1
+            elif direction == 'down':
+                P[1] -= 1
+            elif direction == 'left':
+                P[0] -= 1
+            elif direction == 'right':
+                P[0] += 1
+            node.setPosition(P)
+
+    """ Viewport """
+
+    def bounds(self):
+        return self.hou_tab.visibleBounds()
+
+    def cursorPosition(self):
+        return self.hou_tab.cursorPosition()
+
+    def frameAll(self):
+        self.hou_tab.requestZoomReset()
+
+    def screenSize(self):
+        return self.hou_tab.screenBounds().size()
+
+    def size(self):
+        return self.bounds().size()
+
+    def setBounds(self, bounds):
+        self.hou_tab.setVisibleBounds(bounds)
+
+    def translateView(self, direction):
+        xform_map = {
+            'up':    hou.Vector2(0, self.delta_t * self.zoomLevel()),
+            'down':  hou.Vector2(0, self.delta_t * self.zoomLevel() * -1),
+            'left':  hou.Vector2(self.delta_t * self.zoomLevel() * -1, 0),
+            'right': hou.Vector2(self.delta_t * self.zoomLevel(), 0)
+        }
+        bounds = self.bounds()
+        bounds.translate(xform_map[direction])
+        self.setBounds(bounds)
+
+    def zoom(self, direction):
+        scalemap = {
+            'in':  (0.75, 0.75),
+            'out': (1.25, 1.25)
+        }
+        bounds = self.bounds()
+        bounds.scale(scalemap[direction])
+        self.setBounds(bounds)
+
+    def zoomLevel(self):
+        zoomlevel = self.size()[0] / self.size()[0]
+        return zoomlevel
+
+    """ Interface states & utils """
+
+    def isMenuOpen(self):
+        value = self.hou_tab.getPref('showmenu')
+        return int(value)
+
+    def setMenuOpen(self, value):
+        self.hou_tab.setPref('showmenu', str(value))
+
+    def showPathMessage(self):
+        self.hou_tab.flashMessage(image=None, message=self.path(), duration=1)
+
+    # def showRadialMenu(self):
+        # from .radialutils import editorRadialMain
+        # menu = editorRadialMain()
+        # self.hou_tab.displayRadialMenu("hc_editor_radial_menu")
+
+    def toggleDimUnusedNodes(self):
+        map = {
+            '0': '1',
+            '1': '0'
+        }
+        mode = self.hou_tab.getPref('dimunusednodes')
+        self.hou_tab.setPref('dimunusednodes', map[mode])
+
+    def toggleGridMode(self):
+        map = {
+            '0': '1',
+            '1': '2',
+            '2': '0'
+        }
+        mode = self.hou_tab.getPref('gridmode')
+        self.hou_tab.setPref('gridmode', map[mode])
+
+    def toggleMenu(self):
+        map = {
+            '0': '1',
+            '1': '0'
+        }
+        mode = self.hou_tab.getPref('showmenu')
+        self.hou_tab.setPref('showmenu', map[mode])
+
+    """ Utility """
+
+    def type(self):
+        return "NetworkEditor"
+
+    def renameNode(self):
+        node = self.currentNode()
+        name = hou.ui.readInput("Rename_node", buttons=("Yes", "No"))
+        if name[0] == 0:
+            node.setName(name[1])
+
+    def setNodeColors(self):
+        color = hou.ui.selectColor(hou.Color(Prefs().node_color))
+        nodes = self.pwd().selectedChildren()
+        for node in nodes:
+            node.setColor(color)
+
+    def setNodeShapes(self):
+        nodes = self.pwd().children()
+        for node in nodes:
+            node.setUserData("nodeshape", "rect")
diff --git a/python3.11libs/hc/node.py b/python3.11libs/hc/node.py
new file mode 100644
index 0000000..b09bc28
--- /dev/null
+++ b/python3.11libs/hc/node.py
@@ -0,0 +1,97 @@
+from .geo import Geo
+import hou
+
+class Node:
+    def __init__(self, hou_node):
+        self.hou_node = hou_node
+
+
+    """ Selection """
+
+    def currentNode(self):
+        return self.__class__(self.hou_node.currentNode())
+
+    def deselectAll(self):
+        for child in self.selectedChildren():
+            child.setSelected(False)
+
+    def selectAllChildren(self):
+        for child in self.children():
+            child.setSelected(True)
+
+    def selectedChildren(self):
+        hou_nodes = self.hou_node.selectedChildren()
+        return [self.__class__(hou_node) for hou_node in hou_nodes]
+
+    def setSelected(self, state):
+        self.hou_node.setSelected(state)
+
+
+    """ Visibility """
+
+    def displayNode(self):
+        display_node = self.hou_node.displayNode()
+        if display_node:
+            return self.__class__(display_node)
+        else:
+            return None
+
+    def visibleNodes(self):
+        stack = self.children()
+        visible_nodes = []
+
+        while stack:
+            node = stack.pop()
+            if node.hou_node.isSubNetwork():
+                child_cat = node.childCat()
+                if child_cat == 'Object' and node.hou_node.isDisplayFlagSet():
+                    stack.extend(node.children())
+                elif child_cat == 'Sop' and node.hou_node.isDisplayFlagSet():
+                    display_node = node.displayNode()
+                    if display_node:
+                        visible_nodes.append(display_node)
+
+        return visible_nodes
+
+
+    """ Geometry """
+
+    def geometry(self):
+        return self.hou_node.geometry()
+
+    def hou_geo(self):
+        geo = hou.Geometry()
+        for node in self.visibleNodes():
+            geo.merge(node.geometry())
+        return geo
+
+
+    """ Children """
+
+    def childCat(self):
+        """ Possibilities: Object, Sop, Lop """
+        return self.hou_node.childTypeCategory().name()
+
+    def children(self):
+        hou_nodes = self.hou_node.children()
+        return [self.__class__(hou_node) for hou_node in hou_nodes]
+
+    def netPos(self):
+        """ net editor xy position """
+        return self.hou_node.position()
+
+
+    """ Network """
+
+    def position(self):
+        return self.hou_node.position()
+
+    def setColor(self, color):
+        self.hou_node.setColor(color)
+
+    def setPosition(self, pos):
+        self.hou_node.setPosition(pos)
+
+    def setUserData(self, a, b):
+        self.hou_node.setUserData(a, b)
+
diff --git a/python3.11libs/hc/pane.py b/python3.11libs/hc/pane.py
new file mode 100644
index 0000000..f92166f
--- /dev/null
+++ b/python3.11libs/hc/pane.py
@@ -0,0 +1,127 @@
+import hou
+from .tab           import Tab
+from .pathtab       import PathTab
+from .sceneviewer   import SceneViewer
+from .networkeditor import NetworkEditor
+
+
+class Pane:
+    def __init__(self, hou_pane):
+        self.hou_pane = hou_pane
+
+
+    """ Pane """
+
+    def close(self):
+        for tab in self.allTabs():
+            tab.close()
+
+    def isMaximized(self):
+        return self.hou_pane.isMaximized()
+
+    def qtScreenGeometry(self):
+        return self.hou_pane.qtScreenGeometry()
+
+    def setIsMaximized(self, bool):
+        self.hou_pane.setIsMaximized(bool)
+
+    def toggleMaximize(self):
+        self.hou_pane.setIsMaximized(not self.isMaximized())
+
+
+    """ Split """
+
+    def contract(self):
+        fraction = round(self.splitFraction(), 3) + 0.1
+        self.setSplitFraction(fraction)
+        hou.ui.setStatusMessage("Pane fraction: " + str(fraction))
+
+    def expand(self):
+        fraction = round(self.splitFraction(), 3) - 0.1
+        self.setSplitFraction(fraction)
+        hou.ui.setStatusMessage("Pane fraction: " + str(fraction))
+
+    def isSplitMaximized(self):
+        return self.hou_pane.isSplitMaximized()
+
+    def setIsSplitMaximized(self, bool):
+        self.hou_pane.setIsSplitMaximized(bool)
+
+    def setRatioHalf(self):
+        self.setSplitFraction(0.5)
+
+    def setRatioQuarter(self):
+        self.setSplitFraction(0.25)
+
+    def setRatioThird(self):
+        self.setSplitFraction(0.333)
+
+    def setSplitFraction(self, fraction):
+        self.hou_pane.setSplitFraction(fraction)
+
+    def splitFraction(self):
+        return self.hou_pane.getSplitFraction()
+
+    def splitHorizontal(self):
+        self.hou_pane.splitHorizontally()
+
+    def splitRotate(self):
+        self.hou_pane.splitRotate()
+
+    def splitSwap(self):
+        self.hou_pane.splitSwap()
+
+    def splitVertical(self):
+        self.hou_pane.splitVertically()
+
+    def toggleSplitMaximized(self):
+        self.setIsSplitMaximized(not self.isSplitMaximized())
+
+
+    """ Interface """
+
+    def allTabs(self):
+        tabs = []
+        for hou_tab in self.hou_pane.tabs():
+            tabs.append(self.convertTab(hou_tab))
+        return tabs
+
+    def convertTab(self, hou_tab):
+        hou_type = hou_tab.type()
+        if hou_type == hou.paneTabType.SceneViewer:
+            return SceneViewer(hou_tab)
+        elif hou_type == hou.paneTabType.NetworkEditor:
+            return NetworkEditor(hou_tab)
+        elif hou_type == hou.paneTabType.DetailsView:
+            return PathTab(hou_tab)
+        elif hou_type == hou.paneTabType.Parm:
+            return PathTab(hou_tab)
+        else:
+            return Tab(hou_tab)
+
+    def currentTab(self):
+        hou_tab = self.hou_pane.currentTab()
+        return self.convertTab(hou_tab)
+
+    def isShowingTabs(self):
+        return self.hou_pane.isShowingPaneTabs()
+
+    def nextTab(self):
+        tabs = self.hou_pane.tabs()
+        tab = self.hou_pane.currentTab()
+        index = tabs.index(tab)
+        index = (index + 1) % len(tabs)
+        tabs[index].setIsCurrentTab()
+
+    def previousTab(self):
+        tabs = self.hou_pane.tabs()
+        tab = self.hou_pane.currentTab()
+        index = tabs.index(tab)
+        index = (index - 1) % len(tabs)
+        tabs[index].setIsCurrentTab()
+
+    def showTabs(self, value):
+        self.hou_pane.showPaneTabs(value)
+
+    def toggleTabs(self):
+        self.showTabs(not self.isShowingTabs())
diff --git a/python3.11libs/hc/panel.py b/python3.11libs/hc/panel.py
new file mode 100644
index 0000000..1d00fcf
--- /dev/null
+++ b/python3.11libs/hc/panel.py
@@ -0,0 +1,226 @@
+import hou, inspect
+from PySide6        import QtCore, QtGui, QtWidgets
+from PySide6.QtCore import Qt
+from fuzzyfinder    import fuzzyfinder
+from .session       import Session
+from .pane          import Pane
+from .tab           import Tab
+from importlib      import reload
+
+
+class Panel(QtWidgets.QDialog):
+    def __init__(self):
+        super(Panel, self).__init__(hou.qt.mainWindow())
+
+        # Style
+        self.resize(900, 400)
+        self.setWindowTitle("Panel")
+        self.setWindowFlags(Qt.Tool | Qt.WindowStaysOnTopHint)
+
+        # Context
+        self.session = Session()
+        self.tab = self.session.currentTab()
+        self.pane = self.tab.pane()
+        self.map = {}
+        type = self.tab.type()
+        print("\n###\nPanel Context: " + str(type) + "\n###")
+        if type == "Tab":
+            self.map = self.baseMap()
+        elif type == "PathTab":
+            self.map = self.baseMap() | self.pathTabMap()
+        elif type == "NetworkEditor":
+            self.map = self.baseMap() | self.pathTabMap() | self.networkEditorMap()
+        elif type == "SceneViewer":
+            self.map = self.baseMap() | self.pathTabMap() | self.sceneViewerMap()
+
+        # Function list
+        self.function_list = FunctionList()
+        self.populate()
+        self.function_list.setIndex(0)
+        # Signals
+        self.function_list.itemClicked.connect(self.exec)
+        self.function_list.setSelectionMode(QtWidgets.QListWidget.SingleSelection)
+
+        # Input line
+        self.input_line = QtWidgets.QLineEdit()
+        # Signals
+        self.input_line.returnPressed.connect(self.exec)
+        self.input_line.textEdited.connect(self.function_list.filter)
+        # Set focus
+
+        # Layout
+        self.layout = QtWidgets.QVBoxLayout()
+        self.layout.addWidget(self.function_list)
+        self.layout.addWidget(self.input_line)
+        self.setLayout(self.layout)
+
+        # Do this last I guess
+        self.input_line.setFocus()
+
+    def closeEvent(self, event):
+        self.setParent(None)
+
+    def keyPressEvent(self, event):
+        key = event.key()
+        mod = event.modifiers()
+
+        if key == Qt.Key_Up:
+            self.function_list.selectPrevious()
+            return
+        elif key == Qt.Key_Down:
+            self.function_list.selectNext()
+            return
+        elif key == Qt.Key_P and mod == Qt.ControlModifier:
+            self.function_list.selectPrevious()
+            return
+        elif key == Qt.Key_N and mod == Qt.ControlModifier:
+            self.function_list.selectNext()
+            return
+
+        super().keyPressEvent(event)
+
+    def exec(self):
+        items = self.function_list.items()
+        index = items.index(self.function_list.currentItem())
+        item = items[index]
+        function = self.map[item.text()]
+        function()
+
+    def populate(self):
+        items = []
+        for key in self.map:
+            items.append(key)
+        self.function_list.addItems(items)
+
+    def baseMap(self):
+        return {
+            "Close Other Tabs":          self.tab.closeOtherTabs,
+            "Close Tab":                 self.tab.close,
+            "Color Editor":              self.session.colorEditor,
+            "Contract Pane":             self.pane.contract,
+            "Expand Pane":               self.pane.expand,
+            "Floating Parameter Editor": self.session.floatingParameterEditor,
+            "Hide Shelf":                self.session.hideShelf,
+            "Maximize Pane":             self.pane.toggleMaximize,
+            "Next Tab":                  self.pane.nextTab,
+            "Open File":                 self.session.openFile,
+            "Open Preferences":          self.session.openPreferences,
+            "Previous Tab":              self.pane.previousTab,
+            "Reload Colors":             self.session.reloadColorSchemes,
+            "Reload Hotkeys":            self.session.reloadHotkeys,
+            "Reload Keycam":             self.session.reloadKeycam,
+            "Rename Tabs":               self.session.renameTabs,
+            "Save":                      self.session.save,
+            "Set Type Network Editor":   self.tab.setTypeNetworkEditor,
+            "Set Type Parameters":       self.tab.setTypeParm,
+            "Set Type Python Shell":     self.tab.setTypePythonShell,
+            "Set Type Scene Viewer":     self.tab.setTypeSceneViewer,
+            "Set Type Spreadsheet":      self.tab.setTypeDetailsView,
+            "Show Shelf":                self.session.showShelf,
+            "Split Rotate":              self.pane.splitRotate,
+            "Split Swap":                self.pane.splitSwap,
+            "Toggle all Menus":          self.session.toggleMenus,
+            "Toggle All Paths":          self.session.toggleNetworkControls,
+            "Toggle All Tabs":           self.session.toggleTabs,
+            "Toggle Autosave":           self.session.toggleAutoSave,
+            "Toggle Main Menu":          self.session.toggleMainMenu,
+            "Toggle Stowbars":           self.session.toggleStowbars,
+            "Toggle Tabs":               self.pane.toggleTabs,
+            "Toggle Update Mode":        self.session.toggleUpdateMode,
+            "Update Mode: Auto":         self.session.setUpdateModeAuto,
+            "Update Mode: Manual":       self.session.setUpdateModeManual
+        }
+
+    def sceneViewerMap(self):
+        return {
+            "Frame":                self.tab.frame,
+            "Home Viewports":       self.tab.homeAllViewports,
+            "Keycam":               self.tab.keycam,
+            "Toggle Backface":      self.tab.toggleBackface,
+            "Toggle Bars":          self.tab.toggleBars,
+            "Toggle Display Bar":   self.tab.toggleDisplayBar,
+            "Toggle Grid":          self.tab.toggleGrid,
+            "Toggle Group List":    self.tab.toggleGroupList,
+            "Toggle Light Geo":     self.tab.toggleLightGeo,
+            "Toggle Operation Bar": self.tab.toggleOperationBar,
+            "Toggle Point Markers": self.tab.togglePointMarkers,
+            "Toggle Point Normals": self.tab.togglePointNormals,
+            "Toggle Point Numbers": self.tab.togglePointNumbers,
+            "Toggle Prim Normals":  self.tab.togglePrimNormals,
+            "Toggle Prim Numbers":  self.tab.togglePrimNumbers,
+            "ToggleSelectionBar":   self.tab.toggleSelectionBar,
+            "Toggle Vectors":       self.tab.toggleVectors
+        }
+
+    def pathTabMap(self):
+        return {
+            "Toggle Path": self.tab.toggleNetworkControls,
+            "Toggle Pin":  self.tab.togglePin
+        }
+
+    def networkEditorMap(self):
+        return {
+            "Arrange Nodes":     self.tab.arrangeNodes,
+            "Deselect All":      self.tab.pwd().deselectAll,
+            "Frame All":         self.tab.frameAll,
+            "Rename Node":       self.tab.renameNode,
+            "Set Node Colors":   self.tab.setNodeColors,
+            "Set Node Shapes":   self.tab.setNodeShapes,
+            "Show Path Message": self.tab.showPathMessage,
+            "Toggle Grid Mode":  self.tab.toggleGridMode,
+            "Toggle Menu":       self.tab.toggleMenu
+            # "Show Radial Menu":  self.tab.showRadialMenu,
+        }
+
+
+class FunctionList(QtWidgets.QListWidget):
+    def currentItem(self):
+        return self.selectedItems()[0]
+
+    def filter(self, query):
+        items = self.items()
+        item_names = [item.text() for item in items]
+        matches = list(fuzzyfinder(query, item_names))
+        for item in items:
+            if item.text() in matches:
+                item.setHidden(0)
+            else:
+                item.setHidden(1)
+        self.setIndex(0)
+
+    def items(self):
+        items = []
+        for i in range(self.count()):
+            items.append(self.item(i))
+        return(items)
+
+    def selectNext(self):
+        items = self.visibleItems()
+        index = items.index(self.currentItem())
+        index = (index + 1) % len(items)
+        self.setIndex(index)
+
+    def selectPrevious(self):
+        items = self.visibleItems()
+        index = items.index(self.currentItem())
+        index = (index - 1) % len(items)
+        self.setIndex(index)
+
+    def setIndex(self, index):
+        counter = 0
+        for item in self.visibleItems():
+            if counter == index:
+                self.setCurrentItem(item)
+            counter += 1
+
+    def visibleItems(self):
+        items = []
+        for i in range(self.count()):
+            item = self.item(i)
+            if not item.isHidden():
+                items.append(item)
+        return(items)
+
+
+# class InputLine(QtWidgets.QLineEdit):
+
diff --git a/python3.11libs/hc/pathtab.py b/python3.11libs/hc/pathtab.py
new file mode 100644
index 0000000..d1ed0bb
--- /dev/null
+++ b/python3.11libs/hc/pathtab.py
@@ -0,0 +1,26 @@
+import hou
+from .geo  import Geo
+from .node import Node
+from .tab  import Tab
+
+class PathTab(Tab):
+    def __init__(self, hou_tab):
+        self.hou_tab = hou_tab
+
+    def geo(self):
+        return Geo(self.hou_geo())
+
+    def hou_geo(self):
+        visible_nodes = self.pwd().visibleNodes()
+        geo = hou.Geometry()
+
+        for node in visible_nodes:
+            geo.merge(node.geometry())
+
+        return geo
+
+    def path(self):
+        return self.pwd().path()
+
+    def pwd(self):
+        return Node(self.hou_tab.pwd())
diff --git a/python3.11libs/hc/prefs.py b/python3.11libs/hc/prefs.py
new file mode 100644
index 0000000..af32f46
--- /dev/null
+++ b/python3.11libs/hc/prefs.py
@@ -0,0 +1,3 @@
+class Prefs:
+    def __init__(self):
+        self.node_color = (0.66, 0.66, 0.66)
diff --git a/python3.11libs/hc/radial.py b/python3.11libs/hc/radial.py
new file mode 100644
index 0000000..5dcef18
--- /dev/null
+++ b/python3.11libs/hc/radial.py
@@ -0,0 +1,186 @@
+import hou
+
+#
+# PaneTab object is retrieved by calling kwargs["pane"]
+#
+
+class Radial:
+    def __init__(self):
+        return
+
+    def createItem(label="label", pos=0, submenu=False, script="script"):
+        item = hou.ui.createRadialItem(submenu=submenu)
+        item.setLabel(label)
+        item.setScript(script)
+        hou.ui.injectRadialItem(pos, item)
+
+    #
+    # Scene Viewer
+    #
+
+    def sceneViewerMain(**kwargs):
+        menu = hou.ui.createRadialMenu("hc_radial_sceneviewer_main", "HC Radial Scene Viewer Main")
+        createItem(
+            pos=0,
+            label="UI",
+            submenu=True, script="from hc import Radial; Radial.sceneViewerUI()"
+        )
+        createItem(
+            pos=7,
+            label="Operation bar",
+            script="from hc import SceneViewer; SceneViewer(kwargs['pane']).toggleOperationBar()"
+        )
+        createItem(
+            pos=6,
+            label="Display bar",
+            script="from hc import SceneViewer; SceneViewer(kwargs['pane']).toggleDisplayOptionsToolbar()"
+        )
+        createItem(
+            pos=5,
+            label="Selection bar",
+            script="from hc import SceneViewer; SceneViewer(kwargs['pane']).toggleSelectionBar()"
+        )
+        createItem(
+            pos=4,
+            label="Keycam",
+            script="from hc import SceneViewer; SceneViewer(kwargs['pane']).keycam()"
+        )
+        return menu
+
+    def sceneViewerLayout(**kwargs):
+        menu = hou.ui.createRadialMenu("hc_radial_sceneviewer_layout", "HC Radial - Scene Viewer Layout")
+        createItem(
+            pos=0,
+            label="DoubleSide",
+            script="from hc import SceneViewer; SceneViewer(kwargs['pane']).setLayout('DoubleSide')"
+        )
+        createItem(
+            pos=1,
+            label="DoubleStack",
+            script="from hc import SceneViewer; SceneViewer(kwargs['pane']).setLayout('DoubleStack')"
+        )
+        createItem(
+            pos=2,
+            label="Quad",
+            script="from hc import SceneViewer; SceneViewer(kwargs['pane']).setLayout('Quad')"
+        )
+        createItem(
+            pos=3,
+            label="QuadBottomSplit",
+            script="from hc import SceneViewer; SceneViewer(kwargs['pane']).setLayout('QuadBottomSplit')"
+        )
+        createItem(
+            pos=4,
+            label="QuadLeftSplit",
+            script="from hc import SceneViewer; SceneViewer(kwargs['pane']).setLayout('QuadLeftSplit')"
+        )
+        createItem(
+            pos=5,
+            label="Single",
+            script="from hc import SceneViewer; SceneViewer(kwargs['pane']).setLayout('Single')"
+        )
+        createItem(
+            pos=6,
+            label="TripleBottomSplit",
+            script="from hc import SceneViewer; SceneViewer(kwargs['pane']).setLayout('TripleBottomSplit')"
+        )
+        createItem(
+            pos=7,
+            label="TripleLeftSplit",
+            script="from hc import SceneViewer; SceneViewer(kwargs['pane']).setLayout('TripleLeftSplit')"
+        )
+        return menu
+
+    def sceneViewerUI(**kwargs):
+        menu = hou.ui.createRadialMenu("hc_radial_sceneviewer_ui", "HC Radial - Scene Viewer UI")
+        createItem(
+            pos=0,
+            label="Tabs",
+            script="from hc import Tab; Tab(kwargs['pane']).pane().toggleTabs()"
+        )
+        createItem(
+            pos=7,
+            label="Path",
+            script="from hc import Tab; Tab(kwargs['pane']).toggleNetworkControls()"
+        )
+        createItem(
+            pos=6,
+            label="Maximize",
+            script="from hc import Tab; Tab(kwargs['pane']).pane().toggleMaximize()"
+        )
+        createItem(
+            pos=5,
+            label="Stowbars",
+            script="from hc import Session; Session().toggleStowbars()"
+        )
+        createItem(
+            pos=4,
+            label="All tabs",
+            script="from hc import Session; Session().toggleTabs()"
+        )
+        createItem(
+            pos=3,
+            label="All paths",
+            script="from hc import Session; Session().toggleNetworkControls()"
+        )
+        createItem(
+            pos=2,
+            label="Layout",
+            submenu=True, script="from hc import radialutils; radialutils.viewerRadialLayout()"
+        )
+        return menu
+
+
+    def networkEditorMain(**kwargs):
+        menu = hou.ui.createRadialMenu("hc_radial_networkeditor", "HC Radial - Network Editor")
+        createItem(
+            pos=0,
+            submenu=True,
+            label="UI",
+            script="from hc import Radial; Radial.networkEditorUI()"
+        )
+        createItem(
+            pos=7,
+            label="Menu",
+            script="from hc import NetworkEditor; NetworkEditor(kwargs['pane']).toggleMenu()"
+        )
+        createItem(
+            pos=6,
+            label="Frame all",
+            script="from hc import NetworkEditor; NetworkEditor(kwargs['pane']).frameAll()"
+        )
+        return menu
+
+    def networkEditorUI(**kwargs):
+        menu = hou.ui.createRadialMenu("hc_radial_networkeditor", "HC Radial - Network Editor")
+        createItem(
+            pos=0,
+            label="Tabs",
+            script="from hc import Tab; Tab(kwargs['pane']).pane().toggleTabs()"
+        )
+        createItem(
+            pos=7,
+            label="Path",
+            script="from hc import Tab; Tab(kwargs['pane']).toggleNetworkControls()"
+        )
+        createItem(
+            pos=6,
+            label="All tabs",
+            script="from hc import Session; Session().toggleTabs()"
+        )
+        createItem(
+            pos=5,
+            label="All paths",
+            script="from hc import Session; Session().toggleNetworkControls()"
+        )
+        createItem(
+            pos=4,
+            label="Stowbars",
+            script="from hc import Session; Session().toggleStowbars()"
+        )
+        createItem(
+            pos=3,
+            label="Maximize",
+            script="from hc import Tab; Tab(kwargs['pane']).pane().toggleMaximize()"
+        )
+        return menu
diff --git a/python3.11libs/hc/sceneviewer.py b/python3.11libs/hc/sceneviewer.py
new file mode 100644
index 0000000..11d3292
--- /dev/null
+++ b/python3.11libs/hc/sceneviewer.py
@@ -0,0 +1,275 @@
+import hou, types
+from .pathtab import PathTab
+
+"""
+Layout/viewport IDs
+
+DoubleSide:        2 3
+
+DoubleStack:       3
+                   0
+
+Quad:              2 3
+                   1 0
+
+QuadBottomSplit:     3
+                   2 1 0
+
+QuadLeftSplit:     2
+                   1 3
+                   0
+
+TripleBottomSplit:   3
+                   1   0
+
+TripleLeftSplit:   2
+                   0 3
+                   1
+
+Single:
+setViewportLayout(layout, single=-1)
+-1: current viewport (viewportmouse is/was over)
+0: top-left quad viewport (default: Top)
+1: top-right quad viewport (default: Perspective)
+2: bottom-left quad viewport (default: Front)
+3: bottom-right quad viewport (default: Right)
+"""
+
+class SceneViewer(PathTab):
+    def __init__(self, hou_tab):
+        self.hou_tab = hou_tab
+
+
+    """ Geometry """
+
+    def displaySets(self):
+        displaySets = []
+        for viewport in self.viewports():
+            settings = viewport.settings()
+            displaySet = settings.displaySet(hou.displaySetType.DisplayModel)
+            displaySets.append(displaySet)
+        return(displaySets)
+
+    def toggleLightGeo(self):
+        self.setShowLights(not self.showLights())
+
+    def toggleBackface(self):
+        visible = 0
+        displaySets = self.displaySets()
+        for displaySet in displaySets:
+            if displaySet.isShowingPrimBackfaces():
+                visible = 1
+        for displaySet in displaySets:
+            displaySet.showPrimBackfaces(not visible)
+
+    def togglePointMarkers(self):
+        visible = 0
+        displaySets = self.displaySets()
+        for displaySet in displaySets:
+            if displaySet.isShowingPointMarkers():
+                visible = 1
+        for displaySet in displaySets:
+            displaySet.showPointMarkers(not visible)
+
+    def togglePointNormals(self):
+        visible = 0
+        displaySets = self.displaySets()
+        for displaySet in displaySets:
+            if displaySet.isShowingPointNormals():
+                visible = 1
+        for displaySet in displaySets:
+            displaySet.showPointNormals(not visible)
+
+    def togglePointNumbers(self):
+        visible = 0
+        displaySets = self.displaySets()
+        for displaySet in displaySets:
+            if displaySet.isShowingPointNumbers():
+                visible = 1
+        for displaySet in displaySets:
+            displaySet.showPointNumbers(not visible)
+
+    def togglePrimNormals(self):
+        visible = 0
+        displaySets = self.displaySets()
+        for displaySet in displaySets:
+            if displaySet.isShowingPrimNormals():
+                visible = 1
+        for displaySet in displaySets:
+            displaySet.showPrimNormals(not visible)
+
+    def togglePrimNumbers(self):
+        visible = 0
+        displaySets = self.displaySets()
+        for displaySet in displaySets:
+            if displaySet.isShowingPrimNumbers():
+                visible = 1
+        for displaySet in displaySets:
+            displaySet.showPrimNumbers(not visible)
+
+    def toggleVectors(self):
+        for viewport in self.viewports():
+            settings = viewport.settings()
+            scale = settings.vectorScale()
+            if scale == 1:
+                settings.setVectorScale(0)
+            elif scale == 0:
+                settings.setVectorScale(1)
+            else:
+                settings.setVectorScale(1)
+
+
+    """ Grids """
+
+    def referencePlane(self):
+        return self.hou_tab.referencePlane()
+
+    def toggleGrid(self):
+        refplane = self.referencePlane()
+        refplane.setIsVisible(not refplane.isVisible())
+
+
+    """ Layout """
+
+    def layout(self):
+        return self.hou_tab.viewportLayout()
+
+    def layouts(self):
+        return (
+            hou.geometryViewportLayout.DoubleSide,
+            hou.geometryViewportLayout.DoubleStack,
+            hou.geometryViewportLayout.Quad,
+            hou.geometryViewportLayout.QuadBottomSplit,
+            hou.geometryViewportLayout.QuadLeftSplit,
+            hou.geometryViewportLayout.TripleBottomSplit,
+            hou.geometryViewportLayout.TripleLeftSplit,
+            hou.geometryViewportLayout.Single,
+        )
+
+    def layoutIndices(self):
+        return (
+            (2, 3),
+            (3, 0),
+            (2, 3, 1, 0),
+            (3, 2, 1, 0),
+            (2, 1, 0, 3),
+            (3, 1, 0),
+            (2, 3, 1),
+            (3),
+        )
+
+    def nextLayout(self):
+        layouts = self.layouts()
+        layout = self.layout()
+        index = layouts.index(layout)
+        index = (index+1) % len(layouts)
+        self.setLayout(layouts[index])
+        return
+
+    def setLayout(self, layout):
+        layout_map = {
+            "DoubleSide":        hou.geometryViewportLayout.DoubleSide,
+            "DoubleStack":       hou.geometryViewportLayout.DoubleStack,
+            "Quad":              hou.geometryViewportLayout.Quad,
+            "QuadBottomSplit":   hou.geometryViewportLayout.QuadBottomSplit,
+            "QuadLeftSplit":     hou.geometryViewportLayout.QuadLeftSplit,
+            "Single":            hou.geometryViewportLayout.Single,
+            "TripleBottomSplit": hou.geometryViewportLayout.TripleBottomSplit,
+            "TripleLeftSplit":   hou.geometryViewportLayout.TripleLeftSplit
+        }
+        self.hou_tab.setViewportLayout(layout_map[layout])
+
+
+    """ UI """
+
+    def isVisibleDisplayBar(self):
+        return self.hou_tab.isShowingDisplayOptionsBar()
+
+    def isVisibleGroupList(self):
+        return self.hou_tab.isGroupListVisible()
+
+    def isVisibleOperationBar(self):
+        return self.hou_tab.isShowingOperationBar()
+
+    def isVisibleSelectionBar(self):
+        return self.hou_tab.isShowingSelectionBar()
+
+    def showDisplayBar(self, value):
+        self.hou_tab.showDisplayOptionsBar(value)
+
+    def showGroupList(self, value):
+        self.hou_tab.setGroupListVisible(value)
+
+    def showOperationBar(self, value):
+        self.hou_tab.showOperationBar(value)
+
+    def showSelectionBar(self, value):
+        self.hou_tab.showSelectionBar(value)
+
+    def toggleDisplayBar(self):
+        self.showDisplayBar(not self.isVisibleDisplayBar())
+
+    def toggleGroupList(self):
+        self.showGroupList(not self.isVisibleGroupList)
+
+    def toggleOperationBar(self):
+        self.showOperationBar(not self.isVisibleOperationBar())
+
+    def toggleSelectionBar(self):
+        self.showSelectionBar(not self.isVisibleSelectionBar())
+
+    def toggleBars(self):
+        state = self.hou_tab.isShowingOperationBar() + self.hou_tab.isShowingDisplayOptionsBar() + self.hou_tab.isShowingSelectionBar()
+        if state > 0:
+            self.showOperationBar(0)
+            self.showDisplayBar(0)
+            self.showSelectionBar(0)
+        else:
+            self.showOperationBar(1)
+            self.showDisplayBar(1)
+            self.showSelectionBar(1)
+
+
+    """ Utils """
+
+    def type(self):
+        return "SceneViewer"
+
+    def keycam(self):
+        """
+        Contexts:
+        Chop, ChopNet, Cop, Cop2, CopNet, Data, Director, Dop, Driver, Lop, Manager, Object, Shop, Sop, Top, TopNet, Vop, VopNet
+        """
+        context_map = {
+            "Object": "Entered keycam in an obj context",
+            "Sop": "Entered keycam in a sop context",
+            "Lop": "Entered keycam in a lop context",
+        }
+        context = self.pwd().childCat()
+        if context in context_map:
+            self.hou_tab.setCurrentState('keycam')
+            hou.ui.setStatusMessage(context_map[context])
+        else:
+            hou.ui.setStatusMessage("No obj, sop or lop context", hou.severityType.Error)
+
+
+    """ Viewports """
+
+    def homeAllViewports(self):
+        for viewport in self.viewports():
+            viewport.home()
+
+    def currentViewport(self):
+        return self.hou_tab.curViewport()
+
+    def allViewports(self):
+        return self.hou_tab.viewports()
+
+    def frame(self):
+        for viewport in self.allViewports():
+            cam = viewport.camera()
+            # Is camera node or default
+            if not cam:
+                viewport.frameAll()
+            else:
+                viewport.frameAll()
diff --git a/python3.11libs/hc/session.py b/python3.11libs/hc/session.py
new file mode 100644
index 0000000..a5cb232
--- /dev/null
+++ b/python3.11libs/hc/session.py
@@ -0,0 +1,295 @@
+import hou
+from .networkeditor import NetworkEditor
+from .sceneviewer import SceneViewer
+from .pane import Pane
+from .pathtab import PathTab
+from .tab import Tab
+
+
+class Session:
+    def __init__(self):
+        return
+
+
+    """ Context """
+
+    def currentNode(self):
+        return self.currentTab().currentNode()
+
+    def projectPath(self):
+        return hou.hipFile.path()
+
+
+    """ Layout """
+    """ 'allTabs' and 'currentTab' methods intentionally do not use the hou.ui.paneTabUnderCursor method """
+
+    def allNetworkEditors(self):
+        editors = []
+        for tab in self.allTabs():
+            if tab.type() == "NetworkEditor":
+                editors.append(tab)
+        return editors
+
+    def allPanes(self):
+        panes = []
+        for hou_pane in self.desktop().panes():
+            panes.append(Pane(hou_pane))
+        return panes
+
+    def allSceneViewers(self):
+        viewers = []
+        for tab in self.tabs():
+            if tab.type() == "SceneViewer":
+                viewers.append(SceneViewer(tab))
+        return viewers
+
+    def allTabs(self):
+        tabs = []
+        for pane in self.allPanes():
+            for tab in pane.allTabs():
+                tabs.append(tab)
+        return tabs
+
+    def allViewports(self):
+        viewports = []
+        viewers = self.sceneViewers
+        for viewer in viewers:
+            for viewport in viewer.viewports():
+                viewports.append(viewport)
+        return viewports
+
+    def clearLayout(self):
+        tabs = self.tabs()
+        for tab in tabs:
+            if tab != tabs[0]:
+                tab.close()
+
+    def currentPane(self):
+        return Pane(hou.ui.paneUnderCursor())
+
+    def currentTab(self):
+        return self.currentPane().currentTab()
+
+    def desktop(self):
+        return hou.ui.curDesktop()
+
+    def printLayout(self):
+        panes = hou.ui.panes()
+        root = panes[0].getSplitParent()
+        lefts = []
+        tops = []
+        for pane in self.panes():
+            geo = pane.qtScreenGeometry()
+            lefts.append(geo.left())
+            tops.append(geo.top())
+        print(lefts)
+        print(tops)
+
+    def renameTabs(self):
+        tabs = hou.ui.paneTabs()
+        i = 0
+        for tab in tabs:
+            tab.setName('placeholder' + str(i))
+            i += 1
+        i = 0
+        for tab in tabs:
+            tab.setName('panetab' + str(i))
+            i += 1
+
+    def root(self):
+        root = hou.ui.panes()[0].getSplitParent().getSplitParent()
+
+
+    """ Settings """
+
+    def autosave(self):
+        return hou.getPreference('autoSave')
+
+    def openPreferences(self):
+        hou.ui.openPreferences('ui', '')
+
+    def reloadColorSchemes(self):
+        hou.ui.reloadColorScheme()
+        hou.ui.reloadViewportColorSchemes()
+
+    def reloadStudioUtils(self):
+        hou.ui.reloadPackage(hou.homeHoudiniDirectory() + '/packages/houdini-studio-utils.json')
+
+    def reloadHotkeys(self):
+        from .bindings import Bindings
+        Bindings().load()
+
+    def reloadKeycam(self):
+        hou.ui.reloadViewerState('keycam')
+
+    def setUpdateModeAuto(self):
+        hou.setUpdateMode(hou.updateMode.AutoUpdate)
+
+    def setUpdateModeManual(self):
+        hou.setUpdateMode(hou.updateMode.Manual)
+
+    def triggerUpdate(self):
+        hou.ui.triggerUpdate()
+
+    def toggleAutoSave(self):
+        map = {'0': '1', '1': '0'}
+        hou.setPreference('autoSave', map[hou.getPreference('autoSave')])
+
+    def toggleUpdateMode(self):
+        mode_map = {
+            "updateMode.AutoUpdate": hou.updateMode.Manual,
+            "updateMode.Manual": hou.updateMode.AutoUpdate
+        }
+        hou.setUpdateMode(mode_map(str(hou.updateModeSetting)))
+
+    def updateMainMenuBar(self):
+        hou.ui.updateMainMenuBar()
+
+
+    """ Utils """
+
+    def colorEditor(self):
+        hou.ui.selectColor()
+
+    def keycam(self):
+        msgmap = {
+            "Object": "Entered keycam in an obj/object context",
+            "Sop": "Entered keycam in a sop/geometry context",
+            "Lop": "Entered keycam in a lop context"
+        }
+        viewer = self.sceneViewers()[0]
+        category = viewer.pwd().childTypeCategory().name()
+        if category in msgmap:
+            viewer.setCurrentState('keycam')
+            hou.ui.setStatusMessage(msgmap[category])
+        else:
+            hou.ui.setStatusMessage("Keycam is only available in obj, sop and lop contexts",
+                hou.severityType.Error)
+
+    def openFile(self):
+        file = hou.ui.selectFile()
+        hou.hipFile.load(file)
+
+    def removeEventLoopCallbacks(self):
+        callbacks = hou.ui.eventLoopCallbacks()
+        for callback in callbacks:
+            hou.ui.removeEventLoopCallback(callback)
+
+    # def restartHoudini(self):
+        # import os
+        # import subprocess
+        # executable = sys.argv[0]
+        # executable = os.environ.get("HFS") + "/bin/houdini"
+        # subprocess.Popen([executable])
+        # hou.exit()
+        # return
+
+    def save(self):
+        hou.hipFile.save()
+
+
+    """ Interface """
+
+    def isVisibleMainMenu(self):
+        value = hou.getPreference('showmenu.val')
+        return int(value)
+
+    def isVisibleMenus(self):
+        visible = 0
+        panes = self.allPanes()
+        tabs = self.allTabs()
+        # Main menu
+        if self.isVisibleMainMenu():
+            visible = 1
+        # Tabs
+        for tab in tabs:
+            if tab.type() == "NetworkEditor":
+                if tab.isMenuOpen():
+                    visible = 1
+                elif tab.isShowingNetworkControls():
+                    visible = 1
+            elif tab.type() == "PathTab":
+                if tab.isShowingNetworkControls():
+                    visible = 1
+            elif tab.type() == "SceneViewer":
+                if tab.isShowingNetworkControls():
+                    visible = 1
+                elif tab.isVisibleOperationBar():
+                    visible = 1
+                elif tab.isVisibleDisplayBar():
+                    visible = 1
+                elif tab.isVisibleSelectionBar():
+                    visible = 1
+        # Panes
+        for pane in panes:
+            if pane.isShowingTabs():
+                visible = 1
+        return visible
+
+    def floatingParameterEditor(self):
+        tab = self.currentTab()
+        if tab.type() == "NetworkEditor":
+            hou.ui.showFloatingParameterEditor(self.currentNode())
+        else:
+            hou.ui.setStatusMessage("Not a network editor", hou.severityType.Error)
+
+    def hideShelf(self):
+        self.desktop().shelfDock().show(0)
+
+    def showMainMenu(self, value):
+        hou.setPreference('showmenu.val', str(value))
+
+    def showShelf(self):
+        self.shelfDock().show(1)
+
+    def toggleMainMenu(self):
+        value = (self.isVisibleMainMenu()+1) % 2
+        self.showMainMenu(value)
+
+    def toggleMenus(self):
+        visible = self.isVisibleMenus()
+        panes = self.allPanes()
+        tabs = self.allTabs()
+        # Set state
+        self.showMainMenu((visible+1) % 2)
+        for tab in tabs:
+            if tab.type() == "NetworkEditor":
+                tab.showNetworkControls(not visible)
+                tab.setMenuOpen(0)
+            elif tab.type() == "PathTab":
+                tab.showNetworkControls(not visible)
+            elif tab.type() == "SceneViewer":
+                tab.showNetworkControls(not visible)
+                tab.showOperationBar(not visible)
+                tab.showDisplayBar(not visible)
+                tab.showSelectionBar(not visible)
+        for pane in panes:
+            pane.showTabs(not visible)
+        # Apply (needs to be called twice for some reason)
+        # hou.ui.setHideAllMinimizedStowbars(visible)
+        # hou.ui.setHideAllMinimizedStowbars(visible)
+
+    def toggleNetworkControls(self):
+        visible = 0
+        tabs = self.allTabs()
+        for tab in tabs:
+            if tab.isShowingNetworkControls():
+                visible = 1
+        for tab in tabs:
+            tab.showNetworkControls(not visible)
+
+    def toggleStowbars(self):
+        if hou.ui.hideAllMinimizedStowbars():
+            hou.ui.setHideAllMinimizedStowbars(False)
+        else:
+            hou.ui.setHideAllMinimizedStowbars(True)
+            hou.ui.setHideAllMinimizedStowbars(True)
+
+    def toggleTabs(self):
+        visible = 0
+        panes = self.allPanes()
+        for pane in panes:
+            if pane.isShowingTabs():
+                visible = 1
+        for pane in panes:
+            pane.showTabs(not visible)
diff --git a/python3.11libs/hc/status.py b/python3.11libs/hc/status.py
new file mode 100644
index 0000000..3f519a4
--- /dev/null
+++ b/python3.11libs/hc/status.py
@@ -0,0 +1,69 @@
+import hou
+from PySide6 import QtWidgets
+from PySide6.QtWidgets import QLabel, QVBoxLayout
+from PySide6.QtCore import Qt
+
+
+class Status(QtWidgets.QDialog):
+    def __init__(self):
+        super(Dialog, self).__init__(hou.qt.mainWindow())
+        self.resize(500, 200)
+        self.setWindowTitle("Status")
+        self.setWindowFlags(Qt.Tool | Qt.WindowStaysOnTopHint)
+
+        # Widgets
+        self.project_path_label = QLabel("Project Path")
+        self.network_path_label = QLabel("Network Path")
+        self.tab_type_label = QLabel("Tab Type")
+
+        # Layout
+        self.layout = QVBoxLayout()
+        self.layout.addWidget(self.project_path_label)
+        self.layout.addWidget(self.network_path_label)
+        self.layout.addWidget(self.tab_type_label)
+        self.layout.addStretch()
+        self.setLayout(self.layout)
+
+        self.update()
+
+    def closeEvent(self, event):
+        self.setParent(None)
+
+    def lists(self):
+        # Arrays for navigating pane tabs
+        self.tab_types = (
+            hou.paneTabType.ApexEditor,
+            hou.paneTabType.CompositorViewer,
+            hou.paneTabType.DetailsView,
+            hou.paneTabType.NetworkEditor,
+            hou.paneTabType.Parm,
+            hou.paneTabType.PythonPanel,
+            hou.paneTabType.PythonShell,
+            hou.paneTabType.SceneViewer,
+            hou.paneTabType.Textport
+        )
+        self.tab_type_names = (
+            "ApexEditor",
+            "CompositorViewer",
+            "DetailsView",
+            "NetworkEditor",
+            "Parm",
+            "PythonPanel",
+            "PythonShell",
+            "SceneViewer",
+            "Textport"
+        )
+        self.tab_names = [tab.name() for tab in self.session.allTabs()]
+
+        # Populate pane tab labels array
+        self.tab_labels = []
+        for tab in self.session.allTabs():
+            index = self.tab_types.index(tab.type())
+            label = self.tab_type_names[index]
+            self.tab_labels.append(label)
+
+    def update(self):
+        # Labels
+        self.project_path_label.setText("Project path: " + hou.session.projectPath)
+        self.network_path_label.setText("Network Path: " + hou.session.networkPath)
+        self.tab_type_label.setText("Tab type: " + str(hou.session.tabType))
diff --git a/python3.11libs/hc/tab.py b/python3.11libs/hc/tab.py
new file mode 100644
index 0000000..8bbf2c0
--- /dev/null
+++ b/python3.11libs/hc/tab.py
@@ -0,0 +1,73 @@
+import hou
+import types
+
+
+class Tab():
+    def __init__(self, hou_tab):
+        self.hou_tab = hou_tab
+
+    #
+    # Tab
+    #
+
+    def close(self):
+        self.hou_tab.close()
+
+    def closeOtherTabs(self):
+        for tab in self.pane().allTabs():
+            if tab != self.tab:
+                tab.close()
+
+    def pane(self):
+        from .pane import Pane
+        return Pane(self.hou_tab.pane())
+
+    def type(self):
+        return "Tab"
+
+    def setTypeDetailsView(self):
+        self.setType(hou.paneTabType.DetailsView)
+
+    def setTypeNetworkEditor(self):
+        self.setType(hou.paneTabType.NetworkEditor)
+
+    def setTypeParm(self):
+        self.setType(hou.paneTabType.Parm)
+
+    def setTypePythonShell(self):
+        self.setType(hou.paneTabType.PythonShell)
+
+    def setTypeSceneViewer(self):
+        self.setType(hou.paneTabType.SceneViewer)
+
+    def setType(self, type):
+        tab = self.tab.setType(type)
+        return tab
+
+    #
+    # UI
+    #
+
+    def hasNetworkControls(self):
+        return self.hou_tab.hasNetworkControls()
+
+    def isPin(self):
+        return self.hou_tab.isPin()
+    
+    def isShowingNetworkControls(self):
+        value = self.hou_tab.isShowingNetworkControls()
+        return value
+
+    def setPin(self, value):
+        self.hou_tab.setPin(value)
+    
+    def showNetworkControls(self, value):
+        self.hou_tab.showNetworkControls(value)
+    
+    def toggleNetworkControls(self):
+        if self.hasNetworkControls():
+            self.showNetworkControls(not self.isShowingNetworkControls())
+
+    def togglePin(self):
+        self.setPin(not self.isPin())
+
diff --git a/python3.11libs/hc/viewport.py b/python3.11libs/hc/viewport.py
new file mode 100644
index 0000000..69bf803
--- /dev/null
+++ b/python3.11libs/hc/viewport.py
@@ -0,0 +1,15 @@
+import hou
+
+
+class Viewport():
+    def __init__(self, hou_viewport):
+        self.hou_viewport = hou_viewport
+        return
+
+    def visualizers(self):
+        category = hou.viewportVisualizerCategory.Scene
+        vis_arr = hou.viewportVisualizers.visualizers(category)
+        return vis_arr
+
+    def setType(self, type):
+        self.hou_viewport.changeType(type)
diff --git a/python3.11libs/hc/visualizers.py b/python3.11libs/hc/visualizers.py
new file mode 100644
index 0000000..0e8c3ab
--- /dev/null
+++ b/python3.11libs/hc/visualizers.py
@@ -0,0 +1,133 @@
+import hou
+from fuzzyfinder import fuzzyfinder
+from PySide6 import QtCore
+from PySide6.QtWidgets import QListWidget, QListWidgetItem, QDialog, QLineEdit, QBoxLayout
+from PySide6.QtCore import Qt, QEvent
+from .session import Session
+
+class Visualizers(QDialog):
+    def __init__(self):
+        super(VisualizerMenu, self).__init__(hou.qt.mainWindow())
+
+        # Resources
+        self.session = Session()
+        self.viewport = self.session.viewport()
+        self.vis_arr = self.viewport.visualizers()
+
+        # Filter box
+        self.filter_box = filterBox()
+        self.filter_box.onTab.connect(self.listNext)
+        self.filter_box.returnPressed.connect(self.itemToggle)
+        self.filter_box.textEdited.connect(self.listFilter)
+
+        # List widget
+        self.list_widget = QListWidget()
+        if self.vis_arr:
+            for vis in self.vis_arr:
+                list_item = QListWidgetItem()
+                item_label = vis.label()
+                item_state = vis.isActive(self.curViewport)
+
+                list_item.setText(item_label)
+
+                if item_state:
+                    list_item.setCheckState(Qt.Checked)
+                else:
+                    list_item.setCheckState(Qt.Unchecked)
+
+                self.list_widget.addItem(list_item)
+
+        self.list_widget.itemClicked.connect(self.item_toggle)
+
+        # Layout
+        self.layout = QBoxLayout(QBoxLayout.Direction.TopToBottom)
+        self.layout.addWidget(self.filter_box)
+        self.layout.addWidget(self.list_widget)
+        self.setLayout(self.layout)
+        self.listSetIndex(0)
+
+    def closeEvent(self, event):
+        print("Closing")
+        self.setParent(None)
+
+    def itemToggle(self):
+        current_item = self.list_widget.selectedItems()[0]
+        item_name = current_tem.text()
+        items = self.listGetItems()
+        item_names = [item.text() for item in items]
+        index = item_names.index(item_name)
+
+        vis = self.vis_arr[index]
+        state = vis.isActive(self.viewport)
+
+        vis.setIsActive(not state, self.viewport)
+
+        if state:
+            current_item.setCheckState(Qt.Unchecked)
+        else:
+            current_item.setCheckState(Qt.Checked)
+
+    def listFilter(self):
+        text = self.input_box.text()
+        items = self.getItems()
+        names = [item.text() for item in items]
+        suggestions = fuzzyfinder(text, names)
+        suggestions = list(suggestions)
+        for item in items:
+            if item.text() in suggestions:
+                item.setHidden(0)
+            else:
+                item.setHidden(1)
+        self.listSetIndex(0)
+
+    def listGetItems(self):
+        item_count = self.list_widget.count()
+        items = [self.list_widget.item(i) for i in range(item_count)]
+        return(items)
+
+    def listGetVisibleItems(self):
+        item_count = self.list_widget.count()
+        items = []
+        for i in range(item_count):
+            item = self.list_widget.item(i)
+            if not item.isHidden():
+                items.append(item)
+        return(items)
+
+    def listNext(self):
+        visible_items = self.listGetVisibleItems()
+        current_item = self.list_widget.selectedItems()[0]
+        index = visible_items.index(current_item)
+        index = (index + 1) % len(visible_items)
+        self.listSetIndex(index)
+
+    def listPrev(self):
+        visible_items = self.listGetVisibleItems()
+        current_item = self.list_widget.selectedItems()[0]
+        index = visible_items.index(current_item)
+        index = (index - 1) % len(visible_items)
+        self.listSetIndex(index)
+
+    def listSetIndex(self, index):
+        visible_items = self.listGetVisibleItems()
+        counter = 0
+        for visible_item in visible_items:
+            if counter == index:
+                self.list_widget.setItemSelected(visible_item, 1)
+                return
+            counter += 1
+
+    class filterBox(QLineEdit):
+        def __init__(self):
+            # Key handler
+            self.onTab = QtCore.Signal()
+
+        def event(self, event):
+            if event.type() == QEvent.Type.KeyPress:
+                key = event.key()
+                if key == Qt.Key_Tab:
+                    self.onTab.emit()
+                    return True
+            return QLineEdit.event(self, event)
+
+
diff --git a/python3.11libs/hc/widgets.py b/python3.11libs/hc/widgets.py
new file mode 100644
index 0000000..c599795
--- /dev/null
+++ b/python3.11libs/hc/widgets.py
@@ -0,0 +1,19 @@
+from importlib import reload
+from PySide6 import QtWidgets
+from PySide6.QtCore import Qt
+
+class Widgets:
+
+    class Button(QtWidgets.QPushButton):
+        def __init__(self, text, parent=None):
+            super().__init__(text, parent)
+            self.setStyleSheet("text-align: left; padding: 0 0 0 0")
+            self.setFlat(1)
+            self.setFixedSize(120, 16)
+
+
+    class Menu(QtWidgets.QPushButton):
+        def __init__(self, label, parent=None):
+            super().__init__(label, parent)
+            self.setStyleSheet("text-align: left; padding: 0 0 0 0")
+            self.setFlat(1)