alc.path¶
The addressing system. See PathObject — Addressing for the concepts.
alc.path.path_object ¶
CoordinateError ¶
Bases: ValueError
A tool URL could not be encoded or decoded.
The message always names the token that was missing or malformed. This is deliberately loud: a link that cannot be resolved is a broken link, and quietly opening a default page instead is how you end up reviewing (or screenshotting) something other than what you were sent.
UnknownTokenError ¶
Bases: ValueError
A PathObject was built with a token the model does not carry.
Pydantic's extra="ignore" drops unknown keys without a word, which is the
right policy for the token dicts the route engine hands the validator (a
route's vocabulary is deliberately wider than the model's — category,
asset, video are all route-side names for tokens the model stores as
parent/child). It is the wrong policy for what a caller typed:
PathObject(video="theaxe") silently produced parent="" and an object
that addressed the wrong thing — the coordinate was accepted and the identity
was gone. A coordinate that names a token nobody has is not a coordinate.
PathObject ¶
Bases: BaseModel
Unified entry point. We wrap strings/paths into a dict key so the 'before' validator can find them reliably.
Source code in alc\path\path_object.py
version_thumbnail
property
¶
Version thumbnail under the resolution-rooted .thumb convention
({res}/.thumb/{subpath}/{base}.png). Mirrors
:meth:get_thumb_path (thumb_type="version").
web_prefix
property
¶
/c/{company}/project/{project} — the grounded URL prefix every
tool route already mounts under (AlcContextMiddleware binds the alc
request context from exactly these two tokens).
web_coordinate
property
¶
The at/{entity_type}/{parent}/{child}/{task}?… tail on its own.
Raises :class:CoordinateError naming every address token that is
missing — an unaddressable coordinate is a loud failure, never a quiet
fall back to a partial address that would open the wrong thing.
source_object
property
¶
returns the "source" version of the current PathObject, this is useful when working with source files. Returns:
render_object
property
¶
Returns the "render" version of the current pathObject, this is useful when working with "rendered" or exported files.
Returns:
render_files
property
¶
Returns the files in the current fully resolved "render" directory, "resolution" as the last token
Returns:
source_files
property
¶
Returns the files in the current fully resolved "source" directory, "resolution" as the last token Returns:
versions
property
¶
lists the versions on disk for the current pathObject context
Only NNN.NNN directories count (see :data:VERSION_DIR_RE). Anything
else sitting in the user folder is somebody's stray directory, not a rung
on the rail, and listing it poisons every reader downstream —
latest_version_object would hand it to copy(version=...) and
raise a pydantic ValidationError for the whole task.
Returns:
latest_version_object
property
¶
Return the latest pathObject for the current context. Returns:
preview_path
property
¶
Get the preview path for the current context (the .preview
convention). Back-compat shim — prefer :meth:get_preview_path.
task_msd_root
property
¶
Return the root directory where all task specific models files are located Returns:
event_msd_root
property
¶
Return the root directory where all event specific models files are located Returns:
bug_msd_root
property
¶
Return the root directory where all bug specific models files are located Returns:
review_msd_root
property
¶
Return the root directory where all review specific models files are located Returns:
playlist_msd_root
property
¶
Root dir for playlist manifests — the project's ordered review lists (one project-level playlists.json, since a playlist crosses tasks by definition).
todo_msd_root
property
¶
Root dir for todo manifests — the Art-phase work lists (one JSON per task, todos tagged by working version).
dirname
property
¶
Returns the directory portion of the path, excluding the filename and extension. Returns:
msd_path
property
¶
Returns the path for the models file for the current context. Returns:
pub_msd_path
property
¶
Returns the publish models path for the current context. Returns:
tracker
property
¶
The active production tracker (ShotGrid / Ayon / ...) for this context.
Convenience: po.tracker.get_shot(po) / po.tracker.publish(po).
version_msd
property
¶
Returns the actual version manifest object for the current context. Returns:
task_msd
property
¶
Returns the actual task manifest object for the current context. Returns:
asset_details
property
¶
This coordinate's registry entry, looked up in ITS OWN project.
Resolved from self.company / self.project — never from the ambient
"current" project. A path IS a statement of which project it belongs to, so
asking a global which project we are in, while holding a coordinate that
already says so, can only ever be right by luck or by someone having
remembered to bind a context first. It was wrong on the farm for exactly that
reason: an loj model build read jlt's registries, found no asset, and
died on a height that was set the whole time.
Returns None when this coordinate is not an asset (no matching parent), or
when the asset genuinely is not in that project's registry — this is a
LOOKUP, and "is it registered?" is a fair question to ask it. Callers that
cannot proceed without one should say so themselves; :attr:height does.
Raises ValueError via :func:store_for if the coordinate carries no project,
because there is then nothing to look it up in and guessing is the whole bug.
Warns when the project it resolves against was INFERRED rather than stated
(see :attr:project_inferred). Resolving from the coordinate is only as
trustworthy as the coordinate; measurement says every asset path in the repo
and the cookbook names its own project, so this should stay silent — and if
it ever speaks, it is naming the exact silent cross-project read that took a
farm outage to notice.
height
property
¶
This asset's real-world height, in centimetres.
Raises rather than letting a missing registry entry surface as
AttributeError: 'NoneType' object has no attribute 'height' several
frames away — that traceback said nothing about WHICH asset, or which
project it had been looked for in, which is why a farm job reading the wrong
project's registries took so long to read as a routing problem at all.
version_basename
property
¶
The ONE canonical name shared by a version and every file derived from it.
Resolved from the pathmap (filenames config) via the route engine — the
single source of truth — so it honors per-task token rules: default_tokens
(assets/shots → parent_child_task, e.g. 010_SEQ_svz, chr_jack_mdl) and
by_task custom tokens (e.g. a tex channel → chr_jack_tex_metal_rough).
Every media sibling is this basename plus a kind suffix in a known dot-folder
(.preview/{base}.mp4, .thumb/{base}.png, .proxy/{base}.####.png), so
the derived paths agree no matter how the po was built (working file, bare
directory, set_filename path). This is the rule humans and machines reach for; the
working file (get_filename) and set_filename resolve from the same config.
unreal_asset_name
property
¶
Content-browser asset name following the naming convention
(parent_child_task, e.g. chr_jack_mdl).
hydrate_context
classmethod
¶
The 'Brain' - Normalizes dicts, strings, and Paths into the final attribute dictionary.
Source code in alc\path\path_object.py
cookbook_relative_path ¶
This path expressed in the cookbook files addressing form.
Cookbook-authored files are a first-class path category — they live under
the cookbook root (CFG.cookbook_dir), routinely a different drive than
the repo. This returns the path RELATIVE to that root
(blender/build/mdl/Build.py) when it sits inside the cookbook tree, so
it can be run back through classify_path(repo_label="cookbook") or
matched against a cookbook area's software-first path_prefixes.
When the path is OUTSIDE the cookbook tree, there is no meaningful
cookbook-relative form, so it returns the absolute posix path (the honest
addressable form) — or "" when absolute_fallback is False and the
caller wants "cookbook-relative or nothing". Prefers the object's own
cookbook_root token when set, else grounds on CFG.cookbook_dir.
Source code in alc\path\path_object.py
path_with_ext ¶
gets the absolute path of the current PathObject with ext - uses "set_filename" by default. Returns:
Source code in alc\path\path_object.py
tool_url ¶
The shareable URL for tool opened on THIS coordinate.
tool is the tool's own path under the project prefix — transcript,
media, or a nested one like cookbook/task. This is what tools
push with hx-push-url as the user navigates, and what a headless
re-visit (screengrab, request pill, tutorial) can replay to land on the
same state in a fresh session.
Source code in alc\path\path_object.py
from_tool_url
classmethod
¶
Decode a coordinate URL back into (tool, PathObject).
The inverse of :meth:tool_url; the pair round-trips every token in the
coordinate set. Unknown query params (?panel=, ?view=) are
ignored on purpose — tools carry their own view state in the same query
string and it is none of the coordinate's business.
Company grounds root from the registered company, so the URL — not
whatever the server last had selected — decides where the path resolves.
Source code in alc\path\path_object.py
copy ¶
An 'Identity-Preserving' copy. Ensures every token is passed to the new instance so the RouteEngine has a full map to work with.
Source code in alc\path\path_object.py
path_at ¶
path_at(
end_at: Optional[str] = None,
set_filename: bool = False,
ext: Optional[str] = None,
po: Optional[bool] = False,
sequence: bool = False,
) -> Union[Path, "PathObject"]
The core 'GPS' method. Resolves the path up to a specific token.
sequence=True (with set_filename=True) returns a frame-sequence
path — …/chr_genMan_mdl.####.png — so render outputs use the same
naming convention as every other file.
Source code in alc\path\path_object.py
get_preview_path ¶
The quicktime preview path: {res}/.preview/{subpath}/{base}{ext}.
Source code in alc\path\path_object.py
get_proxy_path ¶
get_proxy_path(
ext: str = ".png",
relative: bool = False,
as_posix=True,
frames: bool = True,
) -> str
The proxy path: {res}/.proxy/{subpath}/{base}.####{ext}.
frames=True (default) returns a 4-padded printf pattern for a proxy
SEQUENCE; frames=False returns a single {base}{ext} (a still
proxy). Proxies are PNG by convention (color-managed for EXR via oiio).
Source code in alc\path\path_object.py
get_script_media_path ¶
A flat home for a script/visual's attached media: {res}/{kind}/{name}.
kind is a plain folder — "images" for stills, "videos" for moving
formats — so a visual beat's dropped/generated file lands at
…/images/0010_A.png "for free", without the per-block/per-board nesting the
svz storyboard convention uses. The Script editor stays flat here; a publish
emits an OTIO editorial unit, so exact paths don't need to encode the cut. Rooted
in the render tree (an attached/created artifact, like a voice take).
Source code in alc\path\path_object.py
get_thumb_path ¶
Returns the path to the thumbnail for this task or version.
Args:
thumb_type: "version" (resolution-rooted .thumb convention),
"task" (task-level thumb), or "file" (beside an arbitrary file).
ext: thumbnail extension (default .png).
relative: return the path relative to root rather than absolute.
Source code in alc\path\path_object.py
get_msd_path ¶
Returns the path to the MSD file for this version. Args: pub: if True, returns the path to the publish MSD rather than the version MSD. relative: if True, returns the relative path from the root rather than the full path.
Source code in alc\path\path_object.py
list_dir ¶
Lists contents at a specific token level. Useful for finding available versions or resolutions.
Source code in alc\path\path_object.py
get_users ¶
Returns a list of users who have published versions for this task. Returns:
get_source_path ¶
Returns the path to the source version of this task. Args: dirname: if True, returns the directory containing the source version rather than the full path to the source version.
Returns:
Source code in alc\path\path_object.py
get_render_path ¶
Returns the path to the render version of this task. Args: dirname: if True, returns the directory containing the render version rather than the full path to the render version.
Returns:
Source code in alc\path\path_object.py
unreal_game_path ¶
In-engine /Game content path for this asset/shot — the Unreal scene
organization convention: /Game/{entity_type}/{category}/{asset}/{task}/
{variant} (assets) or …/{sequence}/{shot}/{task}/{variant} (shots).
Built from a minimal token set so the /Game route wins (PathObject's
company/project/sync_root/tree defaults would otherwise pull a disk route).
Returns the content-browser FOLDER; pair with unreal_asset_name.
Source code in alc\path\path_object.py
unreal_level_path ¶
/Game path of a LEVEL (map) belonging to this asset — colocated
with the model at {unreal_game_path}/{asset_name}_{kind}_Level so the
map is findable by name in the folder structure instead of a loose
/Game/… dump (which gets unwieldy fast in Unreal). kind tags the
purpose (TT = turntable). Pair with :meth:unreal_sequence_path.
Source code in alc\path\path_object.py
unreal_sequence_path ¶
/Game path of a LevelSequence belonging to this asset, colocated
with the model at {unreal_game_path}/{asset_name}_{kind}_Sequence.
Source code in alc\path\path_object.py
unreal_disk_path ¶
On-disk Perforce path for this asset/shot inside the Unreal project:
{perforce_root}/{company}/{project}/PERFORCE/{world}/Content/
{entity_type}/{category}/{asset}/{task}/{variant}.
The physical content location the /Game path maps to on disk.
Source code in alc\path\path_object.py
get_default_files ¶
Returns the default file path for the task of this pathObject Returns:
Source code in alc\path\path_object.py
get_filename ¶
The version's working-file name: :attr:version_basename + ext
(e.g. 010_SEQ_svz.blend).
Source code in alc\path\path_object.py
copy_default_files ¶
Seed this version from default_files/{task}/.
Never overwrites an existing working file. The default file is a
STARTING point, so it only applies where there is nothing to start from.
A version that already holds a file at the destination name is either a
version-up carrying the artist's work forward
(:meth:start_version -> version_up_minor(copy_source_files=True))
or a version somebody has already worked in — and the studio's start
recipe runs CopyDefaultFiles on whatever coordinate it is handed.
Without this guard, running start on a carried-forward version replaces
the artist's scene with the empty default and says nothing (measured
2026-08-11: a 668,800-byte working file came back as the 667,648-byte
default). Pass overwrite=True to deliberately reset a version to the
studio default.
Returns the list of files actually written.
Source code in alc\path\path_object.py
create_empty_version ¶
Creates the current path object as a version on disk.
comment is the ARTIST's statement about this version (#3796). It
rides through to the task register's VersionPointer — the authority
the version view reads — because a PathObject has no comment field
for update_user_versions to pick up, which is why every pointer in
the register carried an empty comment. Machine-minted versions pass
nothing and stay blank.
Raises:
| Type | Description |
|---|---|
ValueError
|
if this coordinate has no |
Returns:
Source code in alc\path\path_object.py
start ¶
Bring this task version into existence by running the start cookbook
recipe: create the source + render folders, copy the task's default files,
register the version in Alchemy (version + task MSDs), and — if a tracker
is configured — register the context and set it in-progress in ShotGrid.
Resolves the task-specific alchemy/start/{task} recipe, falling back to
the shared alchemy/start/default. This replaces the imperative
create_empty_version path so start is a first-class recipe in the
start → build → render → review → publish arc (every task inherits the
default unless it ships its own).
Source code in alc\path\path_object.py
ensure_started ¶
Make sure THIS coordinate's user has a version rail, starting the task if they don't. Idempotent, and the single home for the "materialize the task the first time someone opens it" decision.
Callers used to spell this inline as
if not po.task_msd.user_versions: po.start(), which tests whether the
register is empty rather than whether this person has anything — see
:meth:alc.models.task.TaskManifest.has_versions_for. A task holding only
a publish entry therefore skipped the start forever, leaving the
version view with a published chip and no versions to select.
Returns True when a start was actually run. The publish pseudo-user
never starts anything — it names the published snapshot, not a rail.
The start always happens at FIRST_VERSION, never at self.version.
The coordinate that triggers this routinely carries a publish-tree token
— ?v=latest, the address of the published snapshot — and starting on
it would mint VERSIONS/.../{user}/latest/, a user version named
"latest" that no version comparison can order and that every +version
would then build on.
Source code in alc\path\path_object.py
version_up_minor ¶
Mint the next minor version and log what actually happened (#3796).
The event used to say from_version=self.version — the version the
CALLER happened to be sitting on, which for the orchestrator is the
default 000.000 every single time, so fourteen version-ups all
claimed the same origin and none recorded the result. The real origin
is the task's latest version on disk (the rung this one is stacked on),
and the result is the version being created; both go on the event, so
the stream can be reconciled against the version MSD authority.
Raises:
| Type | Description |
|---|---|
ValueError
|
if this coordinate has no |
Source code in alc\path\path_object.py
1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 | |
start_version ¶
start_version(
copy_source_files=True,
status="ip",
comment=AUTO_VERSION_COMMENT,
require_default_file=True,
)
The start mechanism: the version a task should be opened at.
Every DCC's start phase asks the same question and it is not worth
answering twice: is there already work on disk?
- Versions exist -> version UP from the latest, carrying its source files forward, so the artist continues from what is there.
- No versions -> seed
000.000fromdefault_files/{task}/, so the task begins from the studio's configured scene rather than whatever happened to be open.
Three traps this exists to own, so no recipe step has to know them:
version_up_minorcopies its ownsource_files, not the latest version's. Called on any other coordinate it mints the right version number and fills it from the wrong version, silently. It must be called on :attr:latest_version_object, which is what happens here.- With no versions AND no default file, the honest outcome is a hard
error naming the missing file — not an empty version that looks like a
successful start and hands the artist a blank scene. Pass
require_default_file=Falsefor a task that legitimately has none. - A coordinate parsed at the USER level carries no
resolution, and every path below the version token then resolves one level too high. Checked here so the error names the coordinate the caller passed in.
Returns the :class:PathObject for the version to open.
Raises:
| Type | Description |
|---|---|
ValueError
|
if this coordinate has no |
FileNotFoundError
|
if there is nothing on disk and no default file. |
Source code in alc\path\path_object.py
run_cookbook ¶
run_cookbook(
recipe_types=None,
deadline=True,
dependency_ids=None,
sub_dependencies=False,
job_name=None,
batch_name=None,
software=None,
)
Run this task's cookbook (build -> publish -> ...).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
recipe_types
|
list[str] | None
|
recipe order to run (default
|
None
|
deadline
|
bool
|
submit to the farm instead of running locally. |
True
|
dependency_ids
|
list[str] | None
|
upstream Deadline job IDs to wait on — chain task cookbooks (dsn -> mdl -> rig) by passing the previous job id. |
None
|
sub_dependencies
|
bool
|
when False (default), the WHOLE cookbook is a single Deadline job (no per-step jobs / heartbeat waits). When True, each step is its own job chained by dependency (the original granular behaviour). |
False
|
job_name
|
str | None
|
Deadline Monitor job label (e.g.
|
None
|
batch_name
|
str | None
|
Deadline Monitor batch label; same
default behaviour as |
None
|
Returns:
| Type | Description |
|---|---|
|
str | None: On Deadline, the job id (for chaining). Locally, None. |
Source code in alc\path\path_object.py
2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 | |
copy_files ¶
copy_files(
dest_po: PathObject,
status: str = "wtg",
description: str = "Version Created with Alchemy",
new_msd: bool = False,
render: bool = True,
status_comment: str = "Status auto-updated with Alchemy",
)
Copies all the data from the current path object into the dest_po's folder structure. Args: dest_po: destination pathObject status: description: new_msd: render:
Returns:
Source code in alc\path\path_object.py
apply_recipe_step ¶
Retrieves the execution packet from the DCC driver and executes it headlessly via a monitored subprocess.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
software
|
str
|
The target DCC (e.g., 'blender', 'maya') |
required |
script
|
str
|
Path to the script to execute inside the DCC |
required |
Returns:
| Name | Type | Description |
|---|---|---|
int |
int
|
The exit code of the subprocess (0 for success) |