So currently what works for me is setting up project based on packages.
In theory I think it's how it's supposed to work and $JOB workflow should be deprecated in favor of packages, but it needs to be supported somehow by houdini itself.
The whole idea is based on relatively small script in
ready.pyimport pathlib
import hdefereval
import hou
current_hproject: pathlib.Path | None = None
project_was_loaded: bool = False
def find_hpackage(path: pathlib.Path) -> pathlib.Path | None:
path = path.resolve()
for current in [path] + list(path.parents):
hpackage = current / "hpackage.json"
if hpackage.is_file():
return hpackage
return None
def unload_hproject() -> None:
global current_hproject
if current_hproject:
hou.logging.log(hou.logging.LogEntry(f"Unload package: {current_hproject} for project"))
hou.ui.unloadPackage(current_hproject.as_posix())
current_hproject = None
hou.unsetenv("HPROJECT_ROOT")
def try_load_hproject(hip_path: pathlib.Path) -> None:
global current_hproject
global project_was_loaded
hpackage = find_hpackage(hip_path)
if hpackage:
project_was_loaded = True
hpackage_file = hpackage.resolve()
current_hproject = hpackage_file
hou.ui.loadPackage(hpackage_file.as_posix())
hou.logging.log(hou.logging.LogEntry(f"Load package: {current_hproject} for project"))
else:
unload_hproject()
def hipfile_callback(event: hou.hipFileEventType):
global current_hproject
global project_was_loaded
match event:
case hou.hipFileEventType.BeforeLoad | hou.hipFileEventType.BeforeSave:
hip_path = pathlib.Path(hou.hipFile.path())
try_load_hproject(hip_path)
case hou.hipFileEventType.AfterLoad | hou.hipFileEventType.AfterSave:
project_was_loaded = False
hip_path = pathlib.Path(hou.hipFile.path())
if current_hproject:
hdefereval.executeDeferred(
lambda: hou.qt.mainWindow().setWindowTitle(
f"[PROJECT: {current_hproject}]: {hip_path}"
)
)
case hou.hipFileEventType.BeforeClear:
if not project_was_loaded:
unload_hproject()
case _:
return
if hou.isUIAvailable():
hou.hipFile.addEventCallback(hipfile_callback)
How it works:
There is a file
hpackage.json (not to be conflicted with nodejs, unity or whatever) that should be placed in project root directory.
The package actually could be identical for any project
{
"hpath": "$HOUDINI_PACKAGE_PATH",
"env": [
{
"HPROJECT_ROOT": "$HOUDINI_PACKAGE_PATH"
}
]
}
When I load a hip file I search for
hpackage.json in any parent directory and if I find one I load the package dynamically with
hou.ui.loadPackage. I'm not resolving if there are more than one hpackage, currently I just work with the simplest way possible without trying to implement anything like workspaces or monorepo.
I also load package on saving and unload it on new file or I couldn't find one in parent directories. Additionally I customize window title so I can see if I'm working "inside" project.
This is working great for me so far and even though it's mostly for UI session it's really easy to load the same "project" in hython because I rely on packages API.
However it's still clunky. Dynamic loading and especially unloading packages is not working as expected. For example once environment variables are set they are set for the whole session. Unloading packages are not unsetting variables, so I just came up with HPROJECT_ROOT that I unset manually.
I think ideally something like this should be provided and supported by houdini. I'd argue even something like "Project Explorer", a pane with current project files, essentially just a file explorer inside project root, could be really useful. Houdini
is already like an IDE, it just lacks good ux with grouping resources as a project on disk.