Blueprint Studio Setup
Install Blueprint Studio, prepare local services, and confirm a working baseline.
Install Blueprint Studio, prepare local services, and confirm a working baseline.
Understand the publishing model, package records, components, metadata, and validation flow.
Blueprint publishing is built around a graph-driven workflow. A publish is not only a file copy. It is a controlled process that records context, checks, components, versions, metadata, user data, dependencies, file paths, and file hashes.
A ContainerGraph represents one publishable source file loaded through a publish blueprint YAML. It owns:
The graph is loaded with:
from quanta import ContainerGraph
graph = ContainerGraph.load( show_name="DemoProject", filepath="D:/show/seq010/shot020/work/scene_v001.ma", container_config="D:/show/config/publish/maya_publish.yaml", ) ```
PublishManager is a small runner for one or more graphs. The UI uses it to collect source files and publish them together.
from quanta import PublishManager
manager = PublishManager() manager.add_graph("DemoProject", scene_path, container_config_path) manager.run("Published from a custom tool") ```
The graph context stores production entities and publish selections. It supports common keys such as Project, Episode, Sequence, Shot, AssetCategory, Asset, TaskType, Task, Container, Version, and Component.
Context values are normally entity objects from the ledger or database records returned by the publish API. Dictionaries with id and name fields also work in many places.
A container groups related publishes for one context. Examples include master, anim, camera, render, and model. Containers are saved as DBContainerItem records.
A version belongs to a container. Blueprint creates the next version candidate by looking up existing versions for the selected container. Versions are saved as DBVersionItem records.
A component describes what kind of file is being published inside a version. Examples include maya_scene, abc_cache, usd_layer, thumbnail, and review_movie. Components are saved as DBComponentItem records.
A PublishItem represents one output that should be produced by a file publisher node. The build node creates publish items with session.add_publish_item(...).
A publish item carries its display name, publisher node name, component name, component type, user data, metadata, and its own publish chain.
When a file publisher calls pub_item.register_publish(self, file_path), Blueprint creates a DBPublishItem record. That record stores container, version, component, checkpoint results, metadata, user data, publisher name, file path, path template name, file hash, user id, and timestamps.
1. A source file is loaded into a ContainerGraph.
2. Blueprint parses the publish blueprint YAML.
3. The graph resolves project/context from path templates and environment variables.
4. Pre-build checks run.
5. The build node runs and calls session.add_publish_item(...) for each output.
6. A container is selected or created.
7. A version is selected or created.
8. Each checked publish item runs its publish chain.
9. Pre-publish checks set success, warning, failed, or error states.
10. File publisher nodes write output files.
11. File publisher nodes call pub_item.register_publish(self, file_path).
12. Blueprint creates or reuses container, version, and component records.
13. Blueprint creates the publish item record with checkpoints, metadata, user data, and file hash.
A valid publish graph must contain:
The container node must define project_name and template_name. If the project_name in the graph does not match the show_name passed to ContainerGraph.load, loading fails.
Blueprint can seed graph context from environment variables:
BLUEPRINT_PROJECT_NAME BLUEPRINT_EPISODE_NAME BLUEPRINT_SEQUENCE_NAME BLUEPRINT_SHOT_NAME BLUEPRINT_ASSETCATEGORY_NAME BLUEPRINT_ASSET_NAME BLUEPRINT_TASKTYPE_NAME BLUEPRINT_STEP_NAME BLUEPRINT_TASK_NAME
For Maya and other DCC tools, set these before opening the custom tool so loaders and publishers can resolve context automatically.
Use ContainerGraph and PublishManager when you want to run a Blueprint publish workflow.
Use Package only when you already have a container, version, and component and want to manually register a file.
Use create_publish_item, get_all_publish_items, and related record helpers when you are building loaders, browsers, dashboards, or automation tools.
Use BlueprintStudioClient when you need direct HTTP-level control.
Build a first publish flow and learn how Blueprint turns context into registered output.
This tutorial shows how to write a standalone Python publisher that runs outside the Blueprint UI but still uses the Blueprint publishing system.
Use this approach for command-line publishing, batch publishing, DCC shelf tools that should run without opening the full Blueprint window, and farm-side publish jobs that have the Blueprint runtime available.
Your Python process must be able to import quanta.
Your workstation or job environment must know the Blueprint API URL when running in studio mode:
$env:BLUEPRINT_API_URL = "http://127.0.0.1:8000/api/v1"
For a remote license/API server:
$env:BLUEPRINT_API_URL = "http://10.10.10.27:8000/api/v1"
You should also provide production context, either through path templates or environment variables:
$env:BLUEPRINT_PROJECT_NAME = "DemoProject" $env:BLUEPRINT_SEQUENCE_NAME = "sq010" $env:BLUEPRINT_SHOT_NAME = "sh020" $env:BLUEPRINT_STEP_NAME = "Animation" $env:BLUEPRINT_APP_NAME = "maya"
import os from quanta import AuthManager, PublishManager
os.environ["BLUEPRINT_API_URL"] = "http://127.0.0.1:8000/api/v1" os.environ["BLUEPRINT_PROJECT_NAME"] = "DemoProject" os.environ["BLUEPRINT_SEQUENCE_NAME"] = "sq010" os.environ["BLUEPRINT_SHOT_NAME"] = "sh020" os.environ["BLUEPRINT_STEP_NAME"] = "Animation" os.environ["BLUEPRINT_APP_NAME"] = "maya"
auth = AuthManager() if not auth.login("artist@example.com", "password", remember_me=True): raise RuntimeError(auth.last_error or "Login failed")
manager = PublishManager() manager.add_graph( show_name="DemoProject", filepath="D:/show/DemoProject/sq010/sh020/work/scene_v001.ma", container_conf_path="D:/show/DemoProject/config/publish/maya_publish.yaml", )
for graph in manager.graphs: containers, default_name = graph.get_containers() container = next((c for c in containers if c.name == default_name), containers[0]) graph.update_context("Container", container)
versions = graph.get_versions() graph.update_context("Version", versions[0])
manager.run("Published from standalone publisher") ```
manager.add_graph(...) loads the source file into a ContainerGraph. During load, Blueprint reads the publish blueprint YAML, validates the container and build nodes, resolves path-template context where possible, applies environment context, runs pre-build checks, and runs the build node.
The build node creates publish items. A publish item is one output that will later be processed by a file publisher.
Your standalone script must choose a container and version before calling manager.run(...). The UI normally does this with the context widget; in standalone mode you do it in code.
If your path template or environment variables do not fully resolve context, set it explicitly.
from quanta import get_ledger
ledger = get_ledger() project = ledger.get_project("DemoProject") sequence = project.get_child("Sequence", "sq010") shot = sequence.get_child("Shot", "sh020") task_type = ledger.get_task_type("Animation")
graph.update_context("Project", project) graph.update_context("Sequence", sequence) graph.update_context("Shot", shot) graph.update_context("TaskType", task_type) ```
For asset work:
project = ledger.get_project("DemoProject")
category = project.get_child("AssetCategory", "Character")
asset = project.get_child("Asset", "Hero")
task_type = ledger.get_task_type("Rigging")graph.update_context("Project", project) graph.update_context("AssetCategory", category) graph.update_context("Asset", asset) graph.update_context("TaskType", task_type) ```
graph.get_containers() returns existing containers matching the graph context. It also includes new container candidates from the container node settings.
containers, default_name = graph.get_containers()
container = next( (item for item in containers if item.name == "master"), containers[0], )
graph.update_context("Container", container) ```
If the selected container is new, Blueprint creates it during registration.
graph.get_versions() returns existing versions for the selected container, with the next version candidate inserted first. In most standalone publishers, use versions[0] to publish the next version.
versions = graph.get_versions()
next_version = versions[0]
graph.update_context("Version", next_version)
If the selected version is new, Blueprint creates it during registration.
from quanta import ContainerGraph
graph = ContainerGraph.load("DemoProject", source_path, publish_yaml)
containers, default_name = graph.get_containers() graph.update_context("Container", containers[0])
graph.update_context("Version", graph.get_versions()[0]) graph.run("Published by direct graph runner") ```
result = graph.get_result() print(result.status, result.message)
for detail in graph.get_details(): print(detail.name, detail.status, detail.message) ```
A status above warning blocks publishing. Check nodes should call one of:
item.set_success("message")
item.set_warning("message")
item.set_failed("message")
The publish YAML must contain a node of type container.
The publish YAML must contain a node of type build.
session.add_publish_item(..., publisher_name="...") must reference a file publisher node name that exists in the graph.
A file publisher must call:
pub_item.register_publish(self, file_path)
Blueprint warns when a file publisher node does not contain .register_publish( in its code.
Set the missing values with graph.update_context(...), fix your path template, or provide environment variables before loading the graph.
register_publish uses AuthManager().current_user. Log in before publishing, or restore an existing remembered session.
Core extension concepts for pipeline teams building custom workflow behavior.
This documentation is for pipeline developers who want to connect custom tools, standalone publishers, or DCC integrations to Blueprint.
Blueprint has two developer-facing layers:
Use the Python API when your tool is running on an artist workstation and has access to the Blueprint runtime package. Use the HTTP API when you are writing a service, web tool, sync process, or a custom application that should talk directly to the Blueprint server.
1. [Publishing System Overview](./publishing-system-overview.md) 2. [DCC Application Integration Guide](./dcc-application-integration-guide.md) 3. [Standalone Publisher Tutorial](./standalone-publisher-tutorial.md) 4. [Publish Graph Node Authoring](./publish-graph-node-authoring.md) 5. [Custom Tool API Guide](./custom-tool-api-guide.md) 6. [Publishing API Reference](./publishing-api-reference.md)
Start with the standalone publisher tutorial. It shows how to authenticate, load a Blueprint publish graph, choose a container and version, and run the publish.
Read the graph node authoring guide. It explains build nodes, pre-publish checks, file publishers, status reporting, and register_publish.
Read the custom tool API guide. It covers AuthManager, BlueprintStudioClient, the record helpers, and direct HTTP calls.
Read the DCC application integration guide. It explains how to create an application setup node, startup tool, handler, and hooks using MayaHandler as the reference.
Use the publishing API reference.
Blueprint can run in demo mode or studio/server mode. Most public Python helpers automatically choose the right backend:
For custom tools, prefer the public quanta imports where possible. They hide the storage backend and keep your tool portable.
Create reusable graph nodes for validation, publishing, metadata, and studio logic.
Blueprint publish graphs are written as node chains. Each node stores Python code. At runtime, Blueprint executes the node code and passes the correct object into run(...).
This guide is for pipeline TDs writing build nodes, checks, and file publishers.
The container node declares the graph's project and path template.
Required properties:
project_name: DemoProject template_name: maya_publish
Useful properties:
default_container_name: master container_list: - master - anim - camera
The build node creates publish items. It receives the current ContainerGraph as session.
def run(self, session):
session.add_publish_item(
name="Maya Scene",
publisher_name="Publish Maya Scene",
component_name="scene",
component_type="maya_scene",
user_data={"source": session.name},
metadata={"department": "animation"},
)
The publisher_name must match the name of a file publisher node in one of the publish branches.
A pre-publish check receives the current PublishItem as item. It must set a status.
def run(self, item):
source_path = item.get_source_path()
if not source_path:
item.set_failed("No source file was provided.")
returnitem.set_success("Source file found.") ```
Available status helpers:
item.set_success("message")
item.set_warning("message")
item.set_failed("message")
If the check raises an exception, Blueprint records an error state.
A file publisher receives the current PublishItem as pub_item. It must write or locate the output file, then register it.
def run(self, pub_item):
output_path = pub_item.get_publish_path({
"VERSION": pub_item.graph.context.get_version().name,
"COMPONENT": pub_item.component_name,
"EXT": "ma",
})pub_item.add_metadata("format", "mayaAscii") pub_item.add_user_data("note", "Published by Blueprint") pub_item.register_publish(self, output_path) ```
register_publish creates the container/version/component records if needed and then saves the publish file record.
Use session.add_publish_item(...) to declare outputs:
session.add_publish_item(
name="Display name in UI",
publisher_name="Publish Maya Scene",
component_name="scene",
component_type="maya_scene",
user_data={"key": "value"},
metadata={"department": "animation"},
)
Arguments:
Inside checks and publishers, the PublishItem provides:
pub_item.get_source_path() pub_item.get_publish_path(keys) pub_item.register_publish(publisher_node, file_path) pub_item.add_metadata(key, value) pub_item.get_metadata(key, default=None) pub_item.add_user_data(key, value) pub_item.get_user_data(key, default=None) pub_item.set_temp_data(key, value) pub_item.get_temp_data(key, default=None) pub_item.delete_temp_data(key)
Use temporary data when one check or action needs to pass runtime-only information to a later action. Temporary data is not saved to the publish record.
pub_item.get_publish_path(keys) merges graph context values with the keys you provide and resolves the graph's path template.
Typical keys supplied by a publisher:
{
"VERSION": pub_item.graph.context.get_version().name,
"COMPONENT": pub_item.component_name,
"EXT": "abc",
}
If a required token is missing, get_publish_path raises ValueError.
A successful file publisher must end with:
pub_item.register_publish(self, output_path)
During registration, Blueprint collects checkpoint results up to the current publisher, reads selected container and version from graph context, finds or creates the component, computes a short SHA256 file hash, and saves a DBPublishItem record.
Good checks should be small and explicit. Prefer messages that tell the user what happened and what to fix.
def run(self, item):
frame_range = item.get_user_data("frame_range")
if not frame_range:
item.set_warning("Frame range was not provided; using scene range.")
returnitem.set_success("Frame range is available.") ```
Use set_failed for blockers. Use set_warning for publishable but suspicious states.
A build node can add several publish items that target different publisher nodes.
def run(self, session):
session.add_publish_item(
name="Maya Scene",
publisher_name="Publish Maya Scene",
component_name="scene",
component_type="maya_scene",
)
session.add_publish_item(
name="Alembic Cache",
publisher_name="Publish Alembic",
component_name="main_cache",
component_type="abc_cache",
)
Each publish item receives a copy of the publish chain that contains its referenced publisher.
def run(self, pub_item):
import shutil
from pathlib import Pathsource = Path(pub_item.get_source_path()) version = pub_item.graph.context.get_version()
output = Path(pub_item.get_publish_path({ "VERSION": version.name, "COMPONENT": pub_item.component_name, "EXT": source.suffix.lstrip("."), })) output.parent.mkdir(parents=True, exist_ok=True) shutil.copy2(source, output)
pub_item.add_metadata("source_file", source.as_posix()) pub_item.add_metadata("file_ext", source.suffix.lstrip(".")) pub_item.register_publish(self, output.as_posix()) ```
Connect custom tools to Blueprint services and package data.
This guide is for developers building custom loaders, dashboards, sync tools, web services, DCC panels, or automation around Blueprint.
Use this layer for workstation tools, DCC tools, and Python applications running with the Blueprint runtime.
from quanta import (
AuthManager,
get_all_publish_items,
get_all_containers,
get_all_versions,
get_all_components,
)
These helpers work in demo mode and studio mode.
Use this layer when you want explicit endpoint control.
from quanta._internal.runtime_service.studio_client import BlueprintStudioClient
client = BlueprintStudioClient( base_url="http://127.0.0.1:8000/api/v1", token=access_token, ) ```
Any language can talk to the Blueprint server with HTTP and bearer tokens.
from quanta import AuthManager
auth = AuthManager() if not auth.login("artist@example.com", "password", remember_me=True): raise RuntimeError(auth.last_error or "Login failed")
print(auth.get_current_user_info()) ```
In studio mode, login calls POST /auth/login, POST /license/validate, and GET /auth/me. Blueprint validates the core quanta license during login.
curl.exe -X POST "http://127.0.0.1:8000/api/v1/auth/login" `
-H "Content-Type: application/json" `
-d "{\"email\":\"artist@example.com\",\"password\":\"password\"}"
The response includes access_token. Pass it as a bearer token:
curl.exe "http://127.0.0.1:8000/api/v1/auth/me" ` -H "Authorization: Bearer ACCESS_TOKEN"
Python:
allowed, reason, payload = auth.validate_license(["quanta", "my_custom_plugin"])
if not allowed:
raise RuntimeError(reason or "License denied")
HTTP:
curl.exe -X POST "http://127.0.0.1:8000/api/v1/license/validate" `
-H "Authorization: Bearer ACCESS_TOKEN" `
-H "Content-Type: application/json" `
-d "{\"plugin_keys\":[\"quanta\",\"my_custom_plugin\"]}"
Release plugin leases when your tool closes:
auth.release_license(["my_custom_plugin"])
from quanta import get_all_publish_items
items = get_all_publish_items(project_id="PROJECT_ID") for item in items: print(item.component.name, item.version.name, item.filepath) ```
Common filters:
get_all_publish_items(project_id="...") get_all_publish_items(shot_id="...", tasktype_id="...") get_all_publish_items(asset_id="...", tasktype_id="...") get_all_publish_items(container_id="...") get_all_publish_items(version_id="...")
The exact accepted filters depend on the server implementation, but the built-in tools commonly use project, episode, sequence, shot, asset, asset category, task type, task, container, and version ids.
from quanta import get_publish_items_by_ids
items = get_publish_items_by_ids( asset_ids=["ASSET_ID_A", "ASSET_ID_B"], tasktype_id="RIGGING_TASKTYPE_ID", ) ```
This is used by builder workflows to collect asset inventory for a shot.
from quanta import get_publish_item_by_column
item = get_publish_item_by_column("filepath", "D:/show/publish/file.abc") ```
Supported lookup columns are server-defined. The demo store supports common publish columns such as id, filepath, publisher name, and file hash.
from quanta import (
get_all_containers,
get_all_versions,
get_all_components,
)containers = get_all_containers(project_id="PROJECT_ID", shot_id="SHOT_ID") versions = get_all_versions(containers[0].id) components = get_all_components(containers[0].id, versions[0].id) ```
For most publishing tools, prefer the graph workflow or Package. If you need low-level control, use the dataclasses and create helpers.
from quanta import (
DBContainerItem,
DBVersionItem,
DBComponentItem,
DBPublishItem,
create_container,
create_version,
create_component,
create_publish_item,
)container = create_container(DBContainerItem( name="master", project_id="PROJECT_ID", shot_id="SHOT_ID", tasktype_id="TASKTYPE_ID", ))
version = create_version(DBVersionItem( name="v001", version_number=1, container_id=container.id, ))
component = create_component(DBComponentItem( name="cache", type="abc_cache", version_id=version.id, ))
publish = create_publish_item(DBPublishItem( container=container, version=version, component=component, publisher_name="Custom Alembic Exporter", filepath="D:/show/publish/cache_v001.abc", path_template_name="maya_publish", metadata={"frame_start": 1001, "frame_end": 1100}, user_data={"tool": "custom_cache_exporter"}, )) ```
from quanta._internal.runtime_service.studio_client import BlueprintStudioClient
client = BlueprintStudioClient( base_url="http://127.0.0.1:8000/api/v1", token="ACCESS_TOKEN", )
publishes = client.get("publish/items", params={"project_id": "PROJECT_ID"}) ```
The client disables proxy environment inheritance with trust_env=False. This avoids Windows or studio proxy settings accidentally hijacking local Blueprint API calls.
A loader usually needs to resolve the current DCC context, query publish items for that context, group records into components, let the user choose a version, and import, reference, load, or update files in the DCC.
For DCC tools, subclass or mirror BaseDccHandler:
from quanta import BaseDccHandler, DccHandlerMode
class MyDccHandler(BaseDccHandler): def __init__(self): super().__init__(mode=DccHandlerMode.LOADER)
def paths_in_scene(self): return []
def on_update(self, items): pass ```
BaseDccHandler.get_items() already knows how to query publishes for loader, updater, browser, and builder modes.
Common endpoints used by the Python runtime:
POST /auth/login GET /auth/me POST /auth/register POST /auth/password-reset/request POST /auth/password-reset/confirm GET /auth/departments GET /auth/user-names
POST /license/validate POST /license/release
GET /publish/items POST /publish/items POST /publish/items/by-assets GET /publish/items/lookup GET /publish/containers POST /publish/containers GET /publish/containers/{container_id} GET /publish/containers/{container_id}/versions POST /publish/versions GET /publish/versions/{version_id} GET /publish/versions/{version_id}/components POST /publish/components GET /publish/components/{component_id} GET /publish/components/lookup/by-name ```
The HTTP client raises httpx.HTTPStatusError for non-2xx responses. Wrap custom API calls:
import httpx
try: payload = client.get("publish/items") except httpx.HTTPStatusError as exc: print(exc.response.status_code, exc.response.text) except httpx.RequestError as exc: print("Connection failed", exc) ```
For workstation tools, show user-friendly messages for missing API URL, expired session, license denial, missing context, missing publish records, and file path not found.
Reference for publishing endpoints, payloads, and integration contracts.
This page is a compact reference for the Blueprint publishing Python API and the matching server endpoints used by the runtime.
Most custom tools should import from quanta:
from quanta import (
AuthManager,
Context,
PublishManager,
ContainerGraph,
PublishItem,
PublishError,
DBContainerItem,
DBVersionItem,
DBComponentItem,
DBPublishItem,
get_all_publish_items,
get_publish_items_by_ids,
get_publish_item_by_column,
create_publish_item,
get_all_containers,
create_container,
get_all_versions,
create_version,
get_all_components,
get_component,
create_component,
)
Logs in and restores user permissions. In studio mode, also validates the core quanta license. Returns True or False. On failure, read auth.last_error.
Validates the current session against one or more plugin keys.
Returns:
(allowed: bool, reason: str | None, payload: dict)
Releases plugin license leases for the current session.
Returns:
{
"user_id": "...",
"email": "...",
"name": "...",
}
Loads one source file into a ContainerGraph unless it is already loaded.
Runs all checked graphs.
Loads and validates a publish blueprint YAML, resolves context, runs pre-build nodes, and runs the build node.
Sets a context value and re-evaluates graph context. Accepted keys include Project, Episode, Sequence, Shot, AssetCategory, Asset, TaskType, Task, Container, and Version.
Returns:
(containers: list[DBContainerItem], default_name: str | None)
Includes existing matching containers and new candidates from the container node.
Returns versions for the selected container. If versions already exist, the next version candidate is inserted first.
Runs all checked publish items. Publishing is skipped if graph status is worse than warning.
session.add_publish_item(
name="Maya Scene",
publisher_name="Publish Maya Scene",
component_name="scene",
component_type="maya_scene",
user_data={},
metadata={},
)
Raises ContainerError if component_name or component_type is missing, or if publisher_name does not match a file publisher node.
Returns the source file path loaded into the graph.
Resolves the graph path template using graph context plus explicit keys. Raises ValueError when a required token is missing.
Registers an output file in Blueprint. This creates or reuses container, version, and component records, computes the file hash, and creates a publish record.
Adds metadata saved with the publish record.
Adds custom data saved with the publish record.
PublishItem.set_success(message) PublishItem.set_warning(message) PublishItem.set_failed(message)
These set the status for the currently running action.
id, db_type, project_id, episode_id, sequence_id, shot_id, asset_id, assetcategory_id, tasktype_id, task_id, name, metadata, container_path, created_at, updated_at, is_new
id, name, container_id, version_number, files, note, created_at, updated_at, is_new
id, name, type, version_id, created_at, updated_at
id, container, version, component, is_approved, checkpoints, metadata, publisher_name, filepath, path_template_name, user_data, dependencies, is_new, filehash, published_at, updated_at, user_id, user_name
get_all_publish_items(**filters) -> list[DBPublishItem] get_publish_items_by_ids(asset_ids: list[str], tasktype_id: str) -> list[DBPublishItem] get_publish_item_by_column(column: str, value: str) -> DBPublishItem | None create_publish_item(publish_item: DBPublishItem) -> DBPublishItem | None
get_all_containers(**filters) -> list[DBContainerItem] get_container_by_id(container_id: str) -> DBContainerItem | None create_container(container: DBContainerItem) -> DBContainerItem | None
get_all_versions(container_id: str) -> list[DBVersionItem] get_version_by_id(version_id: str) -> DBVersionItem | None create_version(version: DBVersionItem) -> DBVersionItem | None
get_all_components(container_id: str, version_id: str) -> list[DBComponentItem] get_component(container_id: str, version_id: str, component_name: str) -> DBComponentItem | None create_component(component: DBComponentItem) -> DBComponentItem | None
Base URL defaults to:
http://127.0.0.1:8000/api/v1
Override with BLUEPRINT_API_URL.
POST /auth/login GET /auth/me POST /auth/register POST /auth/password-reset/request POST /auth/password-reset/confirm GET /auth/departments GET /auth/user-names
POST /license/validate POST /license/release
GET /publish/items POST /publish/items POST /publish/items/by-assets GET /publish/items/lookup
Create publish item payload:
{
"id": null,
"container_id": "CONTAINER_ID",
"version_id": "VERSION_ID",
"component_id": "COMPONENT_ID",
"user_id": "USER_ID",
"is_approved": false,
"checkpoints": [],
"metadata": {},
"publisher_name": "Publish Maya Scene",
"filepath": "D:/show/publish/scene_v001.ma",
"path_template_name": "maya_publish",
"user_data": {},
"dependencies": {},
"filehash": "abc123"
}
GET /publish/containers
POST /publish/containers
GET /publish/containers/{container_id}
GET /publish/containers/{container_id}/versions
Create container payload:
{
"id": null,
"db_type": "",
"project_id": "PROJECT_ID",
"episode_id": null,
"sequence_id": "SEQUENCE_ID",
"shot_id": "SHOT_ID",
"asset_id": null,
"assetcategory_id": null,
"tasktype_id": "TASKTYPE_ID",
"task_id": null,
"name": "master",
"metadata": {},
"container_path": ""
}
POST /publish/versions
GET /publish/versions/{version_id}
GET /publish/versions/{version_id}/components
Create version payload:
{
"id": null,
"name": "v001",
"container_id": "CONTAINER_ID",
"version_number": 1,
"files": [],
"note": ""
}
POST /publish/components
GET /publish/components/{component_id}
GET /publish/components/lookup/by-name
Create component payload:
{
"id": null,
"name": "scene",
"type": "maya_scene",
"version_id": "VERSION_ID"
}
Blueprint check results use these practical states:
Pending Success Warning Failed Error Skipped
A graph or publish item should not continue when a prior result is worse than warning.
Package.register_file(...) computes a SHA256 hash from the output path and stores the first 16 characters in DBPublishItem.filehash. If the file cannot be read, the hash is saved as an empty string.
Integrate DCC tools such as Maya, Houdini, Nuke, Blender, Unreal, and studio plugins.
This guide explains how to add support for a new DCC or creative application in Blueprint, using the Maya integration as the reference implementation.
The Maya integration is made from three cooperating parts:
Use these files as the main examples:
src/plugins/node_editor/nodes/show_setup/maya/ src/startup_tools/maya/startup.py src/startup_tools/maya/modules/blueprint.mod src/startup_tools/maya/modules/scripts/maya_handler.py src/startup_tools/maya/modules/scripts/maya_hooks.py src/startup_tools/maya/modules/scripts/blueprint_menu.py src/quanta/_internal/dcc_loader_service.py src/quanta/ignition/
A new application should normally follow this shape:
src/plugins/node_editor/nodes/show_setup/<app_name>/
__init__.py
<app_name>.py
data.yamlsrc/startup_tools/<app_name>/ startup.py modules/ or scripts/ <app_name>_handler.py <app_name>_hooks.py application menu/bootstrap files ```
Keep the application name lowercase where it is used as an internal key. Maya uses maya; a Houdini integration would use houdini; Nuke would use nuke.
The node under plugins/node_editor/nodes/show_setup/<app_name> lets users configure the application in Blueprint's show setup graph.
Mirror the existing Maya, Houdini, or Nuke node class:
from qtpy.QtWidgets import QWidget
from plugins.node_editor.graph import Node, NodeMetadata, PortConfig from plugins.node_editor.widgets import PropertyWidget
class Houdini(Node): def __init__(self, metadata: NodeMetadata): super().__init__(metadata)
self.add_input( PortConfig( name="input", types=["project", "project_config", "dcc"], description="Input for Houdini node", default_value={}, ) )
self.add_output( PortConfig( is_input=False, name="output", description="Output for Houdini node", default_value={}, ) )
def get_data_editor(self, parent) -> QWidget: self._editor = PropertyWidget( node=self, config_tab=True, parameters_tab=True, show_export_button=True, export_node=True, parent=parent, ) return self._editor ```
In data.yaml, define:
Use export_tag: Extend for path variables that should be appended to an existing environment variable instead of replacing it.
startup_tools/<app_name>/startup.py is called when Blueprint prepares the application's environment. It should return a dictionary of environment variables and path lists.
Maya's startup tool injects a module path and optional Python runtime paths:
import os
from quanta import get_nucleus
def _split_paths(value: str) -> list[str]: return [path for path in str(value or "").split(os.pathsep) if path]
def app_start() -> dict: bp_runtime = get_nucleus() bp_path = bp_runtime.get_blueprint_paths()
startup_path = os.getenv("STARTUP_TOOL_PATH") or bp_path.startup_tool_path module_path = startup_path / "my_app" / "modules" python_paths = _split_paths(os.getenv("BLUEPRINT_MY_APP_PYTHONPATH", ""))
return { "MY_APP_MODULE_PATH": [str(module_path)], "PYTHONPATH": python_paths, } ```
The exact variables depend on the host application. The important rule is that the launched application must be able to import:
The handler is the main adapter between Blueprint and the host application. It should inherit from BaseDccHandler and implement the host-specific operations.
Import public symbols from quanta:
from typing import Optional
from quanta import ( BaseDccHandler, BucketItem, BucketMethod, ComponentItem, DccAction, DccEvent, DccHandlerMode, run_hooks, ) ```
Start with this skeleton:
class MyAppHandler(BaseDccHandler):
def __init__(self, mode: Optional[DccHandlerMode] = None):
super().__init__(mode=mode)
self.is_item_checkable = Truedef on_import(self, item: ComponentItem): self._add_to_bucket(item, BucketMethod.IMPORT)
def on_reference(self, item: ComponentItem): self._add_to_bucket(item, BucketMethod.REFERENCE)
def on_load(self, items: list[BucketItem]): for bucket_item in items or []: item = bucket_item.item publish_file = self._get_publish_file(item) if not publish_file: continue
action = DccAction( handler=self, mode=self.mode, item=item, bucket_item=bucket_item, publish_file=publish_file, method=bucket_item.method, ) run_hooks(DccEvent.BEFORE_LOAD, action) if action.cancelled: continue
action.loaded_items = self._load_into_host( action.publish_file, action.method, ) if action.loaded_items: run_hooks(DccEvent.AFTER_LOAD, action)
def on_update(self, items: list[ComponentItem]): for item in items or []: if not getattr(item, "checked", False): continue # Find existing host references/nodes and replace them with item.version.
def on_build(self, items: list[ComponentItem]): action = DccAction(handler=self, mode=self.mode, data={"items": items}) run_hooks(DccEvent.BEFORE_BUILD, action) if action.cancelled: return
self.on_load(self.bucket) run_hooks(DccEvent.AFTER_BUILD, action)
def paths_in_scene(self) -> list[str]: return []
def get_scene_item_ids(self) -> list[str]: return [] ```
Maya adds helper methods such as _get_publish_file, namespace generation, reference discovery, and shader assignment. For another application, replace those with the native concepts of that host:
BaseDccHandler already provides shared Blueprint behavior:
The supported modes are:
DccHandlerMode.LOADER # all publish items for the project DccHandlerMode.BROWSER # normal browser mode DccHandlerMode.BUILDER # shot inventory from browser/builder_config.yaml DccHandlerMode.UPDATER # current scene references plus latest available versions
Hooks are registered through quanta.ignition and are available from the public quanta package.
Blueprint currently defines these DCC events:
DccEvent.BEFORE_LOAD DccEvent.AFTER_LOAD DccEvent.BEFORE_UPDATE DccEvent.AFTER_UPDATE DccEvent.BEFORE_BUILD DccEvent.AFTER_BUILD
A hook receives a DccAction object. It can inspect or modify the action, append messages, or cancel the operation.
from quanta import DccEvent, register_hook
def validate_file_before_load(action): publish_file = action.publish_file if not publish_file: action.cancel("No publish file was supplied.") return
if not str(publish_file.filepath).lower().endswith((".abc", ".usd")): action.cancel(f"Unsupported file type: {publish_file.filepath}")
def report_after_load(action): print(f"Loaded {action.publish_file.filepath}: {action.loaded_items}")
def register_default_hooks() -> None: register_hook(DccEvent.BEFORE_LOAD, validate_file_before_load) register_hook(DccEvent.AFTER_LOAD, report_after_load)
register_default_hooks() ```
Returning False from a hook also cancels the action:
def block_camera_loads(action):
if action.component_type == "camera":
action.messages.append("Camera loads are disabled in this context.")
return False
Import your hooks module inside the handler or application bootstrap so registration happens once:
try:
import my_app_hooks # noqa: F401
except ImportError:
from . import my_app_hooks # noqa: F401
register_hook prevents duplicate registration for the same module and function name, so re-importing the same hooks module is safe in normal use.
DccAction is the payload passed to hooks. Common fields are:
Prefer action.cancel("message") instead of setting cancelled manually when you want the reason to travel with the action.
The Maya menu is built in blueprint_menu.py. A new application should provide an equivalent host bootstrap that opens Blueprint widgets or actions from the application's UI.
For loader, builder, updater, and publisher behavior, Maya calls widget helpers from blueprint_menu_widgets:
get_loader_widget(parent).show() get_builder_widget(parent).show() get_updater_widget(parent).show() get_publisher_widget(menu_label, config_path, version, parent, scene_path).show()
If the host cannot embed Qt widgets directly, create a thin command layer that launches Blueprint tools as a separate window or process, while still using the same handler and environment variables.
Before considering a new application supported, verify these points:
This example shows the smallest useful shape. Replace the pseudo host API calls with the application's real Python API.
from pathlib import Path from typing import Optional
from quanta import ( BaseDccHandler, BucketItem, BucketMethod, ComponentItem, DccAction, DccEvent, DccHandlerMode, run_hooks, )
try: import my_app_hooks # noqa: F401 except ImportError: from . import my_app_hooks # noqa: F401
class MyAppHandler(BaseDccHandler): def __init__(self, mode: Optional[DccHandlerMode] = None): super().__init__(mode=mode) self.is_item_checkable = True
def _get_publish_file(self, item: ComponentItem): version = item.version or (item.versions[0] if item.versions else "") files = item.files.get(version, []) return files[0] if files else None
def _load_into_host(self, publish_file, method: BucketMethod) -> list[str]: path = Path(publish_file.filepath) if not path.exists(): return []
if method == BucketMethod.REFERENCE: ref_id = host_api.reference_file(path.as_posix()) return [ref_id]
node_ids = host_api.import_file(path.as_posix()) return list(node_ids or [])
def on_import(self, item: ComponentItem): self._add_to_bucket(item, BucketMethod.IMPORT)
def on_reference(self, item: ComponentItem): self._add_to_bucket(item, BucketMethod.REFERENCE)
def on_load(self, items: list[BucketItem]): for bucket_item in items or []: publish_file = self._get_publish_file(bucket_item.item) if not publish_file: continue
action = DccAction( handler=self, mode=self.mode, item=bucket_item.item, bucket_item=bucket_item, publish_file=publish_file, method=bucket_item.method, ) run_hooks(DccEvent.BEFORE_LOAD, action) if action.cancelled: continue
action.loaded_items = self._load_into_host( action.publish_file, action.method, ) if action.loaded_items: run_hooks(DccEvent.AFTER_LOAD, action)
def paths_in_scene(self) -> list[str]: return [ref.filepath for ref in host_api.list_references()] ```