Action Reference¶
This page catalogues all built-in actions available in K-Pipe pipelines. Actions are referenced by class name in kpipe-publish.toml or kpipe-load.toml.
For how to declare and configure actions in a pipeline, see Pipeline Configuration. For the execute()/cleanup() action lifecycle, see Publishing Pipeline.
Besides the fields listed per action, every action accepts these shared fields:
| Field | Default | Description |
|---|---|---|
slot |
"main" |
Named slot used for input and/or output |
description |
per action | Label shown in the publish UI's action list; falls back to the class name |
enabled |
true |
false skips the action. Toggle it at publish time with a setting targeting "ClassName.enabled" |
Universal actions¶
These actions have no DCC dependency and can be used in any pipeline.
CollectImageFiles¶
Phase: collect
Scans a list of file paths, directories or sequence template paths for image files and stores them as a LayeredSequencePipelineData in the output slot. Directories are scanned non-recursively. Unrecognised file types are skipped with a warning.
Frame files — a frame number right before the extension, separated by ., - or _ (e.g. render.1001.exr, plate_0001.png) — that share a folder and name are grouped into one SequencePipelineData. Its path is the frame template (<folder>/render.####.exr), its frame range is taken from the sorted frames. Images without a frame number become one entry each. When more than one entry is collected (several folders or names), each gets a layer named after its file name; equal names in different folders are prefixed with the folder name (a_beauty, b_beauty).
Accepts every image format registered in kpipe.media (is_image_file()), including read-only formats such as HDR, PSD and Cineon — see image formats.
| Field | Default | Description |
|---|---|---|
slot |
"main" |
Output slot (writes LayeredSequencePipelineData) |
slot_in_paths |
"main" |
Input slot containing a list[str \| Path] of files or directories |
warn_missing_frames |
true |
Warn about gaps in a sequence; off for numbered files that may have gaps, such as UDIM tiles |
CollectPBRTextureSet¶
Phase: collect
Classifies images from a LayeredSequencePipelineData slot into a TextureSetPipelineData by inferring PBR map type and color from filenames (kpipe.media.classify_texture_map). Common naming conventions are recognised automatically (e.g. _BaseColor, _roughness, _nrm, _AmbientOcclusion). Typically chained after CollectImageFiles.
Color: a colorspace token after the map name sets the color: _Non-Color / _Raw → role:data, _sRGB → role:srgb_texture, _ACEScg / _ACEScct / _ACES2065-1 → that colorspace, _Linear → role:scene_linear. Otherwise color maps (base color, emissive, SSS) get role:srgb_texture and all other maps role:data (see Color).
One texture set: several files for the same map type fail the publish, with a message naming them. Files with unrecognised names are skipped with a warning. A normal map with DirectX, DX or normaldx in its name is collected with a warning, since its green channel is flipped compared to the OpenGL normals Blender expects.
UDIMs: numbered files are the UDIM tiles of a map (Wood_Normal.1001.exr, even a single one); pipelines should set warn_missing_frames = false on CollectImageFiles, since tiles can have gaps. Numbers outside 1001–9999 fail the publish. Maps with different tiles get a warning. Tiles are published as …-v001.normal.1001.exr and recorded per map in the product's tiles table; read them with Entity.get_texture_map_tile_paths(). Entity.get_texture_map_path() returns the <UDIM> form for such maps, which isn't a real file.
The publish layer name is read from ctx.layer — set by the layer selector in the publish UI — and falls back to the main layer when empty.
| Field | Default | Description |
|---|---|---|
slot |
"main" |
Output slot (writes TextureSetPipelineData) |
slot_in_images |
"main" |
Input slot (reads LayeredSequencePipelineData) |
CollectWorkfileLayer¶
Phase: collect
Publishes to the task layer of the open workfile, its main layer gives the main product layer, so each sequence file of an edit keeps its own product versions without an artist entering a layer. Has to run before CollectPublishVersion, for pipelines with layers = false: it replaces a layer chosen in the UI.
CollectPublishVersion¶
Phase: collect
Resolves the version the product will be published to before anything is extracted, so extract actions (e.g. ExtractBurnIn's {sequence_version}), note templates ({version}) and IntegrateMedia share the same number. Sets ctx.version_mode to the parsed version and ctx.version to the concrete version for the pipeline's layer (ctx.layer). Product layers have their own version counters: ctx.get_publish_version(layer) returns ctx.version for the pipeline's layer and resolves ctx.version_mode for any other.
Place it as the first collect step of every pipeline that publishes with IntegrateMedia.
| Field | Default | Description |
|---|---|---|
version |
"new" |
Version to publish to: a number or "new", "main", "replace_latest" (replace the latest version's files) or "extend_latest" (keep the latest version's files and add new ones; frame ranges/texture maps are merged in the metadata). Both *_latest modes write v001 if nothing is published yet |
The version can be chosen at publish time with an enum setting:
[[pipeline.render_blender_cycles.collect]]
action = "CollectPublishVersion"
[pipeline.render_blender_cycles.settings.version]
text = "Version"
type = "enum"
values = [["new", "New version"], ["replace_latest", "Replace latest"], ["extend_latest", "Extend latest"]]
default_value = "new"
target = "CollectPublishVersion.version"
CollectProductFile¶
Phase: collect
Collects an already published product file as the source of a re-publish, e.g. to promote the staged asset file to a new main version with IntegrateFile. Nothing is copied; the slot gets a FilePipelineData pointing at the published file, with layer set to the pipeline's layer (ctx.layer), not the layer it was read from. The source entry's extra table is carried over to ctx.extra (values already set win). Raises PublishError if the product doesn't exist.
| Field | Default | Description |
|---|---|---|
slot_out |
"main" |
Output slot (FilePipelineData) |
product_type |
"" |
Product type to read; empty = the pipeline's product type |
layer |
"" |
Layer to read from; empty = main layer |
version |
"latest" |
Version to read: a number or "latest", "main", … |
[[pipeline.blender_asset_active.collect]]
action = "CollectProductFile"
layer = "staged"
version = "main"
CollectNoteTemplate¶
Phase: collect
Fills a text template with pipeline context and adds it to the publish note (ctx.settings["note"]), so integrate actions record it in the product metadata (IntegrateMedia) and attach it as the revision comment (IntegrateKitsuMedia). The result is appended to the artist's note with separator in between, or replaces it if replace is set. Without an artist note, the template text becomes the note on its own.
Templates are filled by kpipe.template (get_template_values() / format_context_template()), shared with ExtractBurnIn. They use the same syntax as path templates: {var} placeholders, Python format specs ({version:03d}), optional <...> sections that are dropped when any variable inside them is empty (e.g. <, layer {layer}>), and {{ / }} for literal braces. Unknown variables become empty strings.
| Variable | Value |
|---|---|
{project_key} |
Project code |
{project_name} |
Project name |
{entity} |
Entity name (shot / asset / edit) |
{entity_full} |
Entity name including its parent, as in file names (e.g. 010_020) |
{entity_parent} |
Parent name (sequence / asset category) |
{entity_type} |
shot, asset or edit |
{task} |
Pipeline task name |
{layer} |
Publish layer (ctx.layer), empty for the main layer |
{product_type} |
Product type of the pipeline |
{pipeline_key} |
Name of the running pipeline |
{source} |
Source workfile id (ctx.source), empty if none |
{version} |
Publish version resolved by CollectPublishVersion (load pipelines: the loaded version), empty if unresolved |
{tool} |
Name of the running tool/DCC, as set at K-Pipe initialization |
{user} |
OS user name (or KPIPE_USER) |
{artist} |
Full name of the logged-in user (kpipe-manage login) from the production backend's team |
{artist_short} |
That user's first name |
{date} |
Today's date as YYYY-MM-DD |
{time} |
Current time as HH:MM |
{kpipe_version} |
Installed K-Pipe version |
{frame_start}, {frame_end} |
Frame range of the entity (int) — from entity.get_frame_range() where available, else 0 |
{handle_start}, {handle_end} |
Frame handles (int) — from the handles attribute config |
{fps} |
Frame rate — from entity.get_fps() where available, else the project's fps attribute config |
{root}, {pipeline_root}, {library_root}, {resources_root} |
Project roots, as in path templates |
{workfile_version} |
Version of the session's open workfile (int), empty without one |
{workfile_layer} |
Task layer of the session's workfile, empty for the main layer |
{workfile_name} |
File name of the session's open workfile without extension, empty without one |
{project_key}, {project_name}, the roots, {tool}, {user}, {artist}, {artist_short}, {date}, {time}, {kpipe_version}, the handles and the workfile_* variables (from the active session) don't depend on the pipeline context — parse_template() provides them in every template, including path templates.
Scalar publish settings are available by name too (e.g. {use_selection}), but never shadow the variables above. Entries in values override everything.
| Field | Default | Description |
|---|---|---|
template |
"" |
Template text. Empty = the note is left unchanged (with a warning) |
separator |
"\n\n" |
Text inserted between the artist's note and the template text |
values |
{} |
Extra template variables; override built-in ones of the same name |
replace |
false |
Discard the artist's note and use only the template text |
setting |
"note" |
ctx.settings key that holds the note |
If every part of the template is optional and empty, the note is left unchanged.
Example:
[pipeline.blender_render_review_video.settings]
note = { text = "Note", type = "text", default_value = "" }
[[pipeline.blender_render_review_video.collect]]
action = "CollectNoteTemplate"
template = "Published by {artist} from {tool}<, layer {layer}> on {date}<\nStatus: {status}>"
values = { status = "WIP" }
ExtractBurnIn¶
Phase: extract
Composites text burn-ins onto an image sequence using oiiotool. Reads a SequencePipelineData or LayeredSequencePipelineData from the slot, writes burned-in frames to a sibling temp directory (original directory name + _burnin), and updates the sequence path to the new frame template (e.g. frames_burnin/render.####.exr) so downstream integrate actions pick up the new frames automatically. The input path may be a frame template, a single frame or a directory of frames.
Each frame is rendered by a separate oiiotool process (one per CPU core in parallel), using the binary shipped with the oiio-python package in the K-Pipe venv (built with FreeType for text rendering). Running it as a subprocess avoids OpenImageIO libraries bundled by DCCs, such as Blender's, which is built without FreeType. A failing process raises a PublishError with oiiotool's error output; so does a missing oiio-python package.
Text fields are templates with the same syntax and variables as CollectNoteTemplate (including scalar publish settings), plus:
| Variable | Value |
|---|---|
{sequence_version} |
Version the sequence will be published to (int) — ctx.get_publish_version() for the sequence's layer, so it matches IntegrateMedia when CollectPublishVersion runs first |
{frame} |
Current frame number (int) |
Example: bottom_left = "v{sequence_version:03d} | {task}<, {layer}>".
| Field | Default | Description |
|---|---|---|
slot |
"main" |
Input/output slot — accepts SequencePipelineData or LayeredSequencePipelineData |
top_left |
"" |
Text rendered top-left. Empty = skip |
top_center |
"" |
Text rendered top-center |
top_right |
"" |
Text rendered top-right |
bottom_left |
"" |
Text rendered bottom-left |
bottom_center |
"" |
Text rendered bottom-center |
bottom_right |
"" |
Text rendered bottom-right |
font_size |
30 |
Font size in pixels |
font_path |
"" |
Path to a font file. Empty uses the OIIO default font |
color |
(0.8, 0.8, 0.8) |
Text colour as an RGB tuple |
shadow |
2 |
Drop-shadow size in pixels. 0 = no shadow |
padding_x |
30 |
Horizontal padding from the image edge in pixels |
padding_y |
10 |
Vertical padding from the image edge in pixels |
letterbox |
false |
Extend the canvas with a solid bar above and below the image; texts are placed on the bars. The output resolution grows by two bar heights |
letterbox_height |
0 |
Bar height in pixels. 0 fits the text: font_size + 2 * padding_y |
letterbox_color |
(0.0, 0.0, 0.0) |
Bar colour as an RGB tuple (alpha, if present, is set to 1) |
Example:
[[pipeline.render_blender_cycles.config]]
action = "ConfigBlenderRenderOutput"
[[pipeline.render_blender_cycles.extract]]
action = "ExtractBlenderRender"
slot = "main"
[[pipeline.render_blender_cycles.extract]]
action = "ExtractBurnIn"
slot = "main"
top_left = "{project_key} | {entity} | {task}"
top_right = "{date}"
bottom_left = "v{sequence_version:03d}"
bottom_right = "{frame:04d}"
[[pipeline.render_blender_cycles.integrate]]
action = "IntegrateMedia"
slot = "main"
ExtractFfmpegSequenceToVideo¶
Phase: extract
Muxes an image sequence into a video file using ffmpeg (provided by the kpipe-imgtools package, which bundles its own ffmpeg binary). Reads a SequencePipelineData from slot_in, encodes it with a media preset plus optional field overrides, and writes a VideoPipelineData (carrying the resolved video settings and the sequence's color) to slot_out. Raises PublishError for LayeredSequencePipelineData — select a single layer upstream first — and for invalid settings, e.g. a codec the container can't hold.
Input frame files are matched by file_format and their numeric naming convention (prefix, separator, zero-padding) is parsed directly rather than relying on ffmpeg's glob demuxer, so this works on builds without libglob (e.g. Windows).
Any container/codec combination from the format reference can be encoded: ProRes, DNxHR, H.264, H.265, AV1, VP9 and FFV1 in mov, mp4, mxf, mkv or webm. See ffmpeg limitations for what the encoders can't write.
| Field | Default | Description |
|---|---|---|
slot_in |
"main" |
Input slot (reads SequencePipelineData) |
slot_out |
"main" |
Output slot (writes VideoPipelineData) |
preset |
"h264_high" |
Video preset |
container |
"" |
Override: mov, mp4, mxf, mkv, webm. Switching to a container that can't hold the preset's codec also switches to the container's default codec |
codec |
"" |
Override: prores, dnxhr, h264, h265, av1, vp9, ffv1 |
profile |
"" |
Override: ProRes/DNxHR profile, e.g. hq, 4444, hqx |
quality |
"" |
Override: low, medium, high, lossless, or 0–100 |
bit_depth |
"" |
Override: 8, 10, 12 |
chroma |
"" |
Override: 420, 422, 444 |
bitrate |
"" |
Override: constant bitrate such as "20M" instead of constant quality (long-GOP codecs only) |
fps |
null |
Output frame rate. Falls back to the sequence's frame_range.fps/original_frame_range.fps, then the project's fps attribute config |
slot_in_audio |
"" |
Optional audio slot (reads AudioPipelineData, e.g. from ExtractBlenderAudio). Muxed in as the audio track, starting with the first frame and cut to the video's length. An empty or unset slot encodes without audio |
audio_codec |
"" |
Audio codec of the track: empty = the container's default (aac in mp4, pcm in mov/mxf/mkv, opus in webm) |
audio_bitrate |
"" |
Bitrate of lossy audio codecs, e.g. "192k"; empty = codec default |
extra_args |
{} |
Extra ffmpeg output kwargs merged over the generated arguments, e.g. { "tune" = "film" } |
Empty override fields keep the preset's value. The audio track keeps the source file's sample rate and channel count; its settings are written into the video's media.audio metadata.
Example:
[[pipeline.blender_render_review_video.extract]]
action = "ExtractBlenderSequencerFrames"
[[pipeline.blender_render_review_video.extract]]
action = "ExtractBurnIn"
bottom_left = "v{sequence_version:03d} | {task}"
[[pipeline.blender_render_review_video.extract]]
action = "ExtractFfmpegSequenceToVideo"
preset = "h264_review"
container = "mov"
[[pipeline.blender_render_review_video.integrate]]
action = "IntegrateMedia"
[[pipeline.blender_render_review_video.integrate]]
action = "CleanMedia"
ExtractFfmpegVideoToSequence¶
Phase: extract
Decodes a video file into an image sequence using ffmpeg. Reads a VideoPipelineData from slot_in, writes one frame file per frame using a media preset plus optional field overrides, and writes a SequencePipelineData (frame_range and original_frame_range both set to 1–frame count, image set to the resolved settings) to slot_out. If fps isn't given, it's probed from the source video, falling back to the project's fps attribute config.
| Field | Default | Description |
|---|---|---|
slot_in |
"main" |
Input slot (reads VideoPipelineData) |
slot_out |
"main" |
Output slot (writes SequencePipelineData) |
preset |
"png_8" |
Image preset |
file_format |
"" |
Override: png, exr, jpg, tif, dpx, tga, webp, avif, jxl |
bit_depth |
"" |
Override, e.g. 16, 16f, 32f |
compression |
"" |
Override, e.g. zip, zip:9, rle |
quality |
"" |
Override: low, medium, high, lossless, or 0–100 (lossy formats) |
channels |
"" |
Override: rgb, rgba (multilayer output isn't supported by ffmpeg) |
fps |
null |
Output frame rate. If unset, probed from the source video, then falls back to the project's fps attribute config |
extra_args |
{} |
Extra ffmpeg output kwargs merged over the generated arguments |
Warning
ffmpeg's EXR encoder can only write none, RLE, ZIPS and ZIP compression. DWAA/DWAB/PIZ fall back to lossless ZIP with a warning — see ffmpeg limitations.
Example:
[[pipeline.plate_conform.extract]]
action = "ExtractFfmpegVideoToSequence"
preset = "exr_zip"
channels = "rgb"
[[pipeline.plate_conform.integrate]]
action = "IntegrateMedia"
IntegrateMedia¶
Phase: integrate
Moves or copies extracted media from temp render directories into the versioned publish tree and writes product metadata. Handles image sequences, videos, layered sequences, and texture sets. For SequencePipelineData, VideoPipelineData, and TextureSetPipelineData (not LayeredSequencePipelineData, which has no single path), writes the same object back into slot_out — the default input slot for OpenFolder and IntegrateKitsuMedia — with path updated: the published file for VideoPipelineData (a single file), the published folder for SequencePipelineData/TextureSetPipelineData (inherently multi-file).
| Field | Default | Description |
|---|---|---|
slot_in |
"main" |
Input slot — accepts SequencePipelineData, VideoPipelineData, LayeredSequencePipelineData, or TextureSetPipelineData |
slot_out |
"publish" |
Output slot — a copy of the pipeline data with path set to the published file (VideoPipelineData), frame template (SequencePipelineData, also per layer in LayeredSequencePipelineData) or folder (TextureSetPipelineData). The input slot keeps the temp paths for CleanMedia |
remap_frames |
false |
Remap frame numbers from original_frame_range to frame_range (1-based) on copy |
keep_files |
true |
Copy files instead of moving them, leaving the source intact |
The product's source metadata (the workfile it was published from) is taken from ctx.source.
The publish version comes from CollectPublishVersion (ctx.get_publish_version() per product layer). Without that step, a new version is created.
IntegrateFile¶
Phase: integrate
Moves or copies a single file (FilePipelineData, e.g. from ExtractBlenderFile or CollectProductFile) into the publish tree of ctx.layer and writes product metadata (file-type = "file", source from ctx.source, note from the note setting). The version comes from CollectPublishVersion; without it a new version is created. Version "main" is overwritten on every publish, so a layer can hold exactly one file. ctx.extra is written as the entry's nested extra table.
| Field | Default | Description |
|---|---|---|
slot_in |
"main" |
Input slot (FilePipelineData) |
slot_out |
"publish" |
Output slot — a copy with path set to the published file |
keep_files |
true |
Copy the file instead of moving it |
CleanMedia¶
Phase: integrate
Deletes the temp render folder(s) left behind by extract actions. Typically placed as the last integrate step. Safe to run even if the folder is already gone.
The deleted folder is the one containing the path (a file or frame template), or the path itself if it is a directory. Slots can also hold a FilePipelineData. Pipeline data with protected = true is never deleted: IntegrateMedia sets it on the published copies it writes to its slot_out, and CollectImageFiles on the artist's source images.
| Field | Default | Description |
|---|---|---|
slot_in |
"main" |
Slot name, or a list of slot names, whose temp paths to delete. Empty slots are skipped |
OpenFolder¶
Phase: integrate
Opens the OS file browser at the folder containing a MediaPipelineData's path — the file's containing folder if path is a file, or path itself if it's already a folder. Typically chained right after IntegrateMedia, reading the slot it wrote.
| Field | Default | Description |
|---|---|---|
slot_in |
"publish" |
Slot containing a MediaPipelineData (e.g. written by IntegrateMedia's slot_out) |
ActionIncrementWorkfile¶
Phase: any (typically integrate)
Saves the current DCC workfile as a new incremented version, via Session.increment() — resolves the next workfile version, records it in kpipe-metadata.toml, and invokes the DCC's registered save handler. If save_current_version is set and the session already has a current workfile, its current state is first saved over that version, so the previous version keeps the state it was published from. Typically the last integrate step, so it only runs once everything else has published successfully. Requires a live K-Pipe session (Session.is_valid(), i.e. an entity and task already set by the running DCC) and a save handler registered by the DCC (Session.has_save_handler()); raises PublishError otherwise.
| Field | Default | Description |
|---|---|---|
file_extension |
"" |
Workfile extension to save as, e.g. "blend", "nk". Empty = infer from the currently open workfile's own suffix |
save_current_version |
true |
Save the current workfile version in place before incrementing |
ActionSaveWorkfile¶
Phase: any
Saves the current DCC workfile in place via Session.save_current(), overwriting the current workfile version. If the session has no current workfile yet (e.g. an unsaved scene with only entity and task set), saves a new workfile version via Session.increment() instead, which requires file_extension. Requires a live K-Pipe session (Session.is_valid()) and a save handler registered by the DCC (Session.has_save_handler()); raises PublishError otherwise, or when no current workfile exists and file_extension is empty.
| Field | Default | Description |
|---|---|---|
file_extension |
"" |
Workfile extension used only when no current workfile exists yet, e.g. "blend", "nk" |
Blender actions¶
CollectBlenderObjects¶
Phase: collect
Collects objects from one or more named Blender collections, or the current selection, into a slot as an ObjectsPipelineData (packages/blender/.../kpipe_blender/pipeline_data.py) — a flat, deduplicated list[bpy.types.Object] on its objects field.
If use_selection is set, uses the members of the collection currently active in the Outliner, unless that's just the scene's root collection (nothing specific selected), in which case it falls back to the currently selected objects instead. Otherwise, resolves each name in collection_names and each pattern in collection_names_regex (matched with re.search against every collection in the file) and flattens their members (all_objects, recursively including nested collections, deduplicated across multiple collections). A named collection that isn't found, or a regex pattern that matches no collection, raises a ValidationError, unless optional is set — then it is skipped with a warning. With optional, the slot can end up with an empty object list.
| Field | Default | Description |
|---|---|---|
slot_out |
"objects" |
Output slot (writes ObjectsPipelineData) |
use_selection |
false |
If true, collects the active Outliner collection's members, falling back to selected objects. Takes priority over collection_names / collection_names_regex |
collection_names |
[] |
Name(s) of the collection(s) to collect (a single string is also accepted) |
collection_names_regex |
[] |
Regex pattern(s) matched against all collection names; every match is collected (a single string is also accepted) |
optional |
false |
If true, missing collection_names entries and collection_names_regex patterns without a match are skipped with a warning instead of raising a ValidationError (e.g. an optional static/environment collection for ConfigBlenderTurntable) |
One of collection_names, collection_names_regex or use_selection must be set; none raises a ValidationError.
CollectBlenderSingleFrame¶
Phase: collect
Collects a single frame number from the active Blender scene as SequencePipelineData. The output frame_range is always 1–1; original_frame_range holds the actual scene frame number, which IntegrateMedia uses when remap_frames = true.
| Field | Default | Description |
|---|---|---|
slot |
"main" |
Output slot (writes SequencePipelineData) |
use_current_frame |
false |
false uses scene.frame_start; true uses scene.frame_current |
CollectBlenderCamera¶
Phase: collect
Collects a camera object from the active Blender scene into a slot for use by ExtractBlenderRender.
| Field | Default | Description |
|---|---|---|
slot |
"camera" |
Output slot (writes bpy.types.Object) |
camera_name |
"" |
Name of a specific camera object. If empty, uses the scene's active camera |
CollectBlenderStoryboardFrames¶
Phase: collect
Collects per-shot frame ranges from VSE storyboard strips into a LayeredSequencePipelineData. Each shot marker becomes one SequencePipelineData entry, with the layer set to {sequence}_{shot}.
Reads use_selection from ctx.settings — if true, only selected strips are processed.
| Field | Default | Description |
|---|---|---|
slot |
"main" |
Output slot (writes LayeredSequencePipelineData) |
ValidateBlenderShots¶
Phase: validate Stops the publish on errors of the Sequencer's shots, including drafts (see the Shots panel), warnings only end up in the log. A timeline without set shots passes, e.g. to review drafts. Also warns about positions the OTIO export gets wrong: a retimed scene strip, and strips before a nested scene's first frame.
CollectBlenderShotStrip¶
Phase: collect
Collects one shot's strip for the Publish Shots panel (which sets the setting shot_strip per shot) and sets where it is published to: ctx.entity and ctx.layer, so it has to be the first collect step, before CollectPublishVersion. original_frame_range is the strip's visible range in the timeline, frame_range the same length from the shot's local in (the project's on other entities). Drafts fail.
| Field | Default | Description |
|---|---|---|
slot_out |
"main" |
Output slot |
use_shot_entity |
false |
Publish to the shot's Shot entity (it has to be in the pipeline); else to layer {sequence}_{shot} on the current entity |
filter_pipeline_shots |
false |
Only shots in the pipeline: the panel leaves the others out, here they fail |
ConfigBlenderRenderOutput¶
Phase: config
Configures the output path and image output settings for a render. Reads sequence data from slot_in, resolves the render output path from the entity/task/product type, and applies a media preset plus optional field overrides to scene.render.image_settings through the Blender media adapter. The sequence data written to slot_out carries path, file_format, the resolved image settings and color — so a following ExtractBlenderRender step only has to render, and IntegrateMedia records the settings in the publish metadata.
The adapter sets media_type before file_format (Blender's valid formats depend on it), maps compression, bit depth and quality to Blender's enums (EXR DWA levels become Blender's quality), and handles color management: display/view specs override the view transform, float formats override the linear colorspace. cleanup() restores the filepath and every output setting it changed once the pipeline finishes. See Blender support for which formats Blender can write.
scene picks which scene gets the output settings. Since Blender 5, a workspace can have a separate sequencer scene (context.sequencer_scene), and depending on the workspace/editor Blender renders that one instead of context.scene. The default both sets the active scene and also the sequencer scene if there is one and it is a different scene.
| Field | Default | Description |
|---|---|---|
slot_in |
"main" |
Input slot (reads SequencePipelineData) |
slot_out |
"main" |
Output slot (writes the same SequencePipelineData with path/file_format/image/color filled in) |
scene |
"both" |
Target scene: active, sequencer or both (sequencer raises an error if there is no sequencer scene) |
preset |
"png_8" |
Image preset, e.g. exr_dwaa, exr_multilayer, jpg_review |
file_format |
"" |
Override: exr, png, jpg, tif, dpx, tga, webp, avif |
bit_depth |
"" |
Override, e.g. 16, 16f, 32f |
compression |
"" |
Override, e.g. dwaa:45, piz, zip |
quality |
"" |
Override: low, medium, high, lossless, or 0–100 |
channels |
"" |
Override: rgb, rgba, all (multilayer EXR with all passes) |
color |
"" |
Override: ColorSpec, e.g. display:sRGB/Standard |
Example:
[[pipeline.render_blender_cycles.config]]
action = "ConfigBlenderRenderOutput"
preset = "exr_multilayer"
ConfigBlenderRenderEngine¶
Phase: config
Sets the scene's render engine and its most common quality settings: sample count, render passes, and Workbench lighting/color. Settings come from a Blender render preset plus optional field overrides, the same way media presets work. Overrides that are set replace the preset's value; lists and tables are replaced, not merged. Empty or 0 fields keep the preset's value, or the scene's value when there is no preset. The action reads and writes no slots, so it can go anywhere in the config phase, typically before ConfigBlenderRenderOutput. cleanup() restores every setting it changed once the pipeline finishes.
- Samples go to the configured engine (or the scene's current one): Cycles
scene.cycles.samples, EEVEEscene.eevee.taa_render_samples. Workbench has no sample count (useworkbench_render_aa), so asamplesvalue logs a warning instead. - Passes are Blender's
use_pass_<name>suffixes (z,mist,normal,position,vector,diffuse_direct,glossy_color,emit,ambient_occlusion,shadow,cryptomatte_object, …; aliasesao,depth,emission). Each name is looked up on the view layer and on its Cycles/EEVEE settings (view_layer.cycles/view_layer.eevee, e.g.shadow_catcher,volume_direct,transparent). Unknown names raise aValidationError. passesapplies globally to every view layer that is enabled for rendering.view_layer_passesadds passes to view layers by name, even ones disabled for rendering. Unknown view layer names raise aValidationError.exclusive_passesturns off every other pass (except Combined) on the view layers that get passes, so the output doesn't depend on the workfile.- To write the passes, combine this action with a multilayer EXR output (
ConfigBlenderRenderOutputwithpreset = "exr_multilayer"orchannels = "all").
| Field | Default | Description |
|---|---|---|
preset |
"" |
Render preset, e.g. eevee_review, cycles_final, workbench_playblast |
engine |
"" |
Override: cycles, eevee or workbench |
samples |
0 |
Render samples for Cycles / EEVEE |
passes |
[] |
Passes to enable on all render-enabled view layers (list or single name) |
view_layer_passes |
{} |
Passes per view layer: { "<view layer>" = ["mist", …] }, added to passes |
exclusive_passes |
unset | Turn off all other passes (except Combined) on the configured view layers |
workbench_lighting |
"" |
studio, matcap or flat |
workbench_color_type |
"" |
material, object, random, single, texture or attribute |
workbench_render_aa |
"" |
Workbench anti-aliasing: off, fxaa, 5, 8, 11, 16, 32 |
Examples:
# EEVEE review render from a preset, with fewer samples
[[pipeline.blender_render_review_video.config]]
action = "ConfigBlenderRenderEngine"
preset = "eevee_review"
samples = 32
# Cycles render with passes in a multilayer EXR
[[pipeline.render_blender_cycles.config]]
action = "ConfigBlenderRenderEngine"
engine = "cycles"
samples = 512
passes = ["z", "normal", "cryptomatte_object"]
view_layer_passes = { FX = ["mist"] }
exclusive_passes = true
[[pipeline.render_blender_cycles.config]]
action = "ConfigBlenderRenderOutput"
preset = "exr_multilayer"
# Workbench playblast with object colors
[[pipeline.blender_playblast.config]]
action = "ConfigBlenderRenderEngine"
preset = "workbench_object_color"
To let artists choose the preset at publish time, add a render_preset setting that targets ConfigBlenderRenderEngine.preset (see Choosing a preset at publish time).
ConfigBlenderViewport¶
Phase: config
Sets the shading ExtractBlenderPlayblast draws with: the shading mode and common Solid-mode toggles.
- With a window (publish panel), it prepares a 3D view for the playblast: the one the publish was started from, else the window's largest. That view gets the shading mode and toggles, looks through the scene camera (no view-local camera) and has its overlays hidden. The view is written to
slot_out. The playblast renders through it and first copies the Workbench settings (lighting, color type, …, set byConfigBlenderRenderEngine) fromscene.display.shadingonto it, so the order of the two config actions doesn't matter. - Without a window (or
use_view = false), the playblast draws withscene.display.shading. These are the Workbench render settings, which only supportsolidandwireframe, somaterial/renderedraise aValidationErrorthere.
The toggles and a solid/wireframe mode are always set on scene.display.shading as well. Empty or unset fields keep the current value. cleanup() restores every setting it changed (on the view too) once the pipeline finishes.
| Field | Default | Description |
|---|---|---|
slot_out |
"viewport" |
Output slot (writes the prepared 3D view; not written without one) |
shading_type |
"" |
solid, material (Material Preview), rendered (scene engine and lights) or wireframe. Empty = the view gets the scene display's mode |
use_view |
true |
Prepare a 3D view for the playblast when there is a window |
show_cavity |
unset | Cavity shading (Solid) |
show_shadows |
unset | Shadows (Solid) |
show_outline |
unset | Object outlines (Solid) |
show_xray |
unset | X-Ray |
Example:
ConfigBlenderTurntable¶
Phase: config
Rigs a two-empty Z-axis turntable and keyframes its rotation over frames frames, then sets scene.frame_start/frame_end to that range and writes a SequencePipelineData covering it to slot_out. In the first half, both empties rotate 0°→360° together — the subject keeps the same relative pose to the camera while the (unparented) environment/lighting sweeps past a fixed framing. In the second half, the camera empty holds and the object empty keeps rotating 0°→360° alone — the classic all-sides object turntable. Rotation is linear (constant speed).
With turn_environment = false, the environment turn is skipped: the camera empty stays still and only the object empty rotates 360°→0°, in the same direction and at the same speed as in the second half of the full turntable. The frame range is then half of frames (frame_start to frame_start + frames // 2 - 1; the closing 0° key sits one frame past the end, so the clip loops seamlessly).
Reads the objects to turn from slot_in_asset (an ObjectsPipelineData, from CollectBlenderObjects), objects that stay fixed relative to the camera (e.g. lights, backdrop) from slot_in_static, and the camera from slot_in_camera (from CollectBlenderCamera). The asset and camera slots must be collected earlier in the pipeline; the static slot is optional — if it was never written or holds no objects, the turntable runs without static objects (combine with CollectBlenderObjects' optional = true for a static collection that may not exist). Asset objects are parented to the object empty, static objects and the camera to the camera empty. Only root objects of each collected set (those not parented to another object in the set) are reparented, so existing internal hierarchies (e.g. meshes parented to an armature) are preserved.
Each empty's rotation keyframes are set to LINEAR interpolation directly on their F-curves after insertion (so the turntable spins at constant speed), navigating Blender's layered-action structure (action.layers[].strips[].channelbag(slot).fcurves) rather than the pre-4.4 action.fcurves shortcut, which no longer exists.
cleanup() restores original parenting, deletes both empties, and restores the original frame range once the pipeline finishes.
| Field | Default | Description |
|---|---|---|
slot_in_asset |
"asset" |
Input slot (reads ObjectsPipelineData) with the objects to turn |
slot_in_static |
"static" |
Input slot (reads ObjectsPipelineData) with objects that move with the camera; optional (ignored if missing or empty) |
slot_in_camera |
"camera" |
Input slot (reads bpy.types.Object camera) |
slot_out |
"main" |
Output slot (writes a SequencePipelineData covering the turntable's frame range) |
frames |
72 |
Total turntable length in frames (minimum 8), split into two equal halves |
turn_environment |
true |
If false, skips the environment turn and renders only the object turn (half of frames) |
layer |
unset | Layer written to the output SequencePipelineData |
Example:
[[pipeline.render_turntable.collect]]
action = "CollectBlenderObjects"
slot_out = "asset"
collection_names = ["Render_Beauty"]
[[pipeline.render_turntable.collect]]
action = "CollectBlenderObjects"
slot_out = "static"
collection_names = ["Turntable_Static"]
optional = true
[[pipeline.render_turntable.collect]]
action = "CollectBlenderCamera"
[[pipeline.render_turntable.config]]
action = "ConfigBlenderTurntable"
frames = 120
# turn_environment = false # object turn only (60 frames)
[[pipeline.render_turntable.config]]
action = "ConfigBlenderRenderOutput"
[[pipeline.render_turntable.extract]]
action = "ExtractBlenderRender"
ExtractBlenderAudio¶
Phase: extract
Mixes down the scene's audio (unmuted sound strips and speakers) with bpy.ops.sound.mixdown into a WAV or FLAC file and writes an AudioPipelineData to slot_out. The range is the original_frame_range of the SequencePipelineData in slot_in. It is set as the scene range only for the mixdown and restored right after. The audio starts at the range's first frame, so it lines up with a sequence rendered over the same range. Feed it to ExtractFfmpegSequenceToVideo's slot_in_audio to get a video with sound while still burning in on the frames. It is an audio-only render of the range: whatever is audible there is mixed, however many clips. A range without sound gives a silent file, so the video gets a silent audio track.
| Field | Default | Description |
|---|---|---|
slot_in |
"main" |
Input slot (reads SequencePipelineData for its original_frame_range) |
slot_out |
"audio" |
Output slot (writes AudioPipelineData) |
preset |
"wav_24" |
Audio preset; Blender's mixdown writes wav (PCM 16/24/32f) or flac |
sample_rate |
0 |
Override, 0 = from preset |
channels |
0 |
Override (1, 2, 4, 6, 8), 0 = from preset |
product_type |
"" |
Render temp folder, empty = the pipeline's product type |
timeout |
60 |
Seconds to wait for the written file |
Blender writes the file only after the mixdown operator has returned (complete, in one go). So the action waits until the file exists, and fails after timeout seconds. From the publish panel it checks once per timer tick, so Blender stays responsive; without a window it waits blocking.
Example (toggled at publish time by a bool setting with target = "ExtractBlenderAudio.enabled"):
[[pipeline.blender_playblast.extract]]
action = "ExtractBlenderAudio"
slot_in = "render"
[[pipeline.blender_playblast.extract]]
action = "ExtractFfmpegSequenceToVideo"
slot_in = "burnin"
slot_in_audio = "audio"
slot_out = "video"
container = "mp4"
[[pipeline.blender_playblast.integrate]]
action = "CleanMedia"
slot_in = ["render", "burnin", "audio", "video"]
ExtractBlenderSequencerAudio¶
Phase: extract
Like ExtractBlenderAudio, with the same fields, output and settings, but mixes down the sequencer scene (context.sequencer_scene in Blender 5+, else the active scene). Use it when a shot's sound lives in the edit rather than in the shot scene.
By default (map_frame_range = false), the slot_in range is used as sequencer frames as is. That is also always the case when the sequencer scene is the active 3D scene (before Blender 5, or without a separate workspace sequencer scene).
With map_frame_range = true, the slot_in range, given in frames of the active 3D scene, is mapped onto the sequencer timeline through the scene strip showing that scene. That is the same strip math as CollectBlenderStoryboardFrames: the timeline frame is the strip's left_handle - left_handle_offset plus the scene frame minus the scene's frame_start, so trimmed strips work. The strip is the one named in strip, else the unmuted strip whose visible part contains the range's first frame, else the first one. A range reaching past the strip only logs a warning, because the audio there comes from whatever else is on the timeline. The mapping assumes the strip plays at normal speed, without retiming.
The written AudioPipelineData.frame_range stays the shot range, so the audio lines up with the playblast. Like ExtractBlenderAudio, it mixes whatever is audible in the (mapped) timeline range; a range without sound gives a silent track.
| Field | Default | Description |
|---|---|---|
map_frame_range |
false |
Map the 3D scene range onto the sequencer timeline through its scene strip. false = use the range as sequencer frames |
strip |
"" |
Name of the scene strip to map through; empty = found by the active scene |
The remaining fields (slot_in, slot_out, preset, sample_rate, channels, product_type, timeout) and the wait for the written file are as in ExtractBlenderAudio. To let artists choose the source at publish time, add both actions with one bool setting each (target = "ExtractBlenderAudio.enabled" / "ExtractBlenderSequencerAudio.enabled"). Both write to the audio slot, so only one should be enabled.
[[pipeline.blender_render_review.extract]]
action = "ExtractBlenderSequencerAudio"
slot_in = "render"
ExtractBlenderFbx¶
Phase: extract
Exports collected objects to {entity}.fbx in the entity's render folder, with exporter settings for Substance Painter (Face smoothing, no tangents, no animation, no embedded textures, FBX unit scale, Y-up). The objects are passed to the exporter through a context override, so the scene's selection stays untouched.
| Field | Default | Description |
|---|---|---|
slot_in |
"asset" |
Input slot (reads ObjectsPipelineData, from CollectBlenderObjects). Having no objects to export is an error |
slot_out |
"main" |
Output slot (writes FilePipelineData, for IntegrateFile) |
only_visible |
true |
Skip objects hidden in the viewport or from rendering (e.g. boolean cutters) |
object_types |
["MESH", "OTHER"] |
Exporter object types; OTHER converts curves, text etc. to meshes |
use_mesh_modifiers |
true |
Export meshes with their modifiers applied |
use_triangles |
false |
Triangulate on export; Painter otherwise triangulates on import |
Example (optionally opening the published file in Painter; LaunchDcc is toggled by a bool setting with target = "LaunchDcc.enabled", the --kpipe-* arguments are described in DCC integration):
[[pipeline.blender_asset_fbx.collect]]
action = "CollectBlenderObjects"
collection_names_regex = "(Prop-)|(Character-)|([Aa]sset).*"
slot_out = "asset"
[[pipeline.blender_asset_fbx.extract]]
action = "ExtractBlenderFbx"
[[pipeline.blender_asset_fbx.integrate]]
action = "IntegrateFile"
keep_files = false
[[pipeline.blender_asset_fbx.integrate]]
action = "LaunchDcc"
dcc = "substance"
slot_in = "publish"
args = ["--kpipe-entity", "{entity_type}:{entity_parent}:{entity}", "--kpipe-task", "shading", "--kpipe-mesh", "{path}"]
ExtractBlenderPlayblast¶
Phase: extract
Playblasts the sequence's original_frame_range through the scene's active camera with one viewport render over the whole range (bpy.ops.render.opengl(animation=True)). Slots, camera handling and cleanup() are the same as ExtractBlenderRender, so the two are interchangeable in a pipeline.
- From the Blender publish panel the viewport render runs as Blender's modal operator (
'INVOKE_DEFAULT'), which draws frame by frame in the event loop, so Blender stays responsive. The action waits while the operator is listed in any window's modal operators. Every window is checked, because the operator moves to the render display window when one opens ("Render In: New Window"). If a frame file is missing once it has ended (or older than the playblast), e.g. after Esc, the publish fails with "Playblast cancelled". - Without a window (
blender -b,RunPipeline,PublishPipeline.run()), the same playblast runs blocking in a single call.
A playblast is much faster than a render, even a Workbench render: it reuses the viewport's already evaluated scene and cached GPU data instead of building a separate render copy of the scene for every frame. It also uses the viewport settings — Subdivision viewport levels, modifiers visible in the viewport, viewport particle amounts — so the result can differ from a render.
The playblast always renders from scene.camera (the camera from slot_in_camera if set, else the active camera), whichever way it draws:
- Through the 3D view that
ConfigBlenderViewportprepared inslot_in_viewport, if there is one (view_context=True). That view is in camera view with overlays hidden, so its previous angle doesn't matter. Its shading mode can be anything, including Material Preview and Rendered. - Otherwise with the scene's display shading (
scene.display.shading,view_context=False), solid or wireframe only.
Set the shading with ConfigBlenderViewport (shading mode, cavity, shadows, …) and ConfigBlenderRenderEngine (Workbench lighting, color type, anti-aliasing). An error is raised if the scene has no camera.
| Field | Default | Description |
|---|---|---|
slot_in |
"main" |
Input slot (reads SequencePipelineData) |
slot_in_camera |
"camera" |
Input slot (reads bpy.types.Object camera). Set to "" to keep the scene's camera |
slot_in_viewport |
"viewport" |
Input slot (reads the 3D view prepared by ConfigBlenderViewport). Empty or unset = draw with the scene display shading |
slot_out |
"render" |
Output slot (writes the same SequencePipelineData, already carrying path/file_format from ConfigBlenderRenderOutput) |
| Example: |
[[pipeline.blender_playblast.collect]]
action = "CollectBlenderCamera"
[[pipeline.blender_playblast.collect]]
action = "CollectBlenderFrameRange"
[[pipeline.blender_playblast.config]]
action = "ConfigBlenderRenderEngine"
preset = "workbench_object_color"
[[pipeline.blender_playblast.config]]
action = "ConfigBlenderViewport"
shading_type = "solid"
show_cavity = true
[[pipeline.blender_playblast.config]]
action = "ConfigBlenderRenderOutput"
[[pipeline.blender_playblast.extract]]
action = "ExtractBlenderPlayblast"
ExtractBlenderRender¶
Phase: extract
Renders each frame of a collected SequencePipelineData's original_frame_range (from slot_in). Expects the output path and file format to already be configured — e.g. by a preceding ConfigBlenderRenderOutput step, which also fills in the sequence data's path/file_format. Sets the scene camera from slot_in_camera (e.g. from CollectBlenderCamera; skipped if the slot is empty), then renders the whole range as one animation render (bpy.ops.render.render(animation=True)). Blender keeps its render copy of the scene between frames, so only the first frame pays the full scene evaluation. The camera, the frame range and the settings below are restored as soon as the frames are done (by cleanup() if the render fails), so later actions such as ExtractBlenderSequencerAudio see the scene as it was. For fast previews use ExtractBlenderPlayblast instead.
- From the Blender publish panel the render runs as a background job (
'INVOKE_DEFAULT'), so Blender stays responsive. Esc cancels the render; the publish then fails with "Render cancelled" and cleans up once the job has stopped. - Without a window (
blender -b, commands running a publish pipeline viaRunPipeline,PublishPipeline.run()), the same render runs blocking in a single call.
| Field | Default | Description |
|---|---|---|
slot_in |
"main" |
Input slot (reads SequencePipelineData) |
slot_in_camera |
"camera" |
Input slot (reads bpy.types.Object camera). Set to "" to keep the scene's camera |
slot_out |
"render" |
Output slot (writes the same SequencePipelineData, already carrying path/file_format from ConfigBlenderRenderOutput) |
lock_interface |
true |
Lock the scene for editing while rendering (render.use_lock_interface) |
render_display |
"" |
Where the render result is shown: none, screen, area or window. This is a user preference; it is restored afterwards, empty = unchanged |
ExtractBlenderOtioTimeline¶
Phase: extract
Writes the sequencer scene's timeline as an OTIO file with Blender's own export (Blender 5.3+), then tags its set shots and stamps the workfile into it with kpipe_otio.editorial. Scene strips showing the sequencer of a linked file come along as nested stacks with their shots. Needs the kpipe-otio package. Writes a FilePipelineData for IntegrateFile.
| Field | Default | Description |
|---|---|---|
slot_out |
"otio" |
Output slot |
product_type |
"" |
Render temp folder (templates resolved; empty = the pipeline's product type) |
use_resolve_metadata |
false |
The export's option for DaVinci Resolve metadata |
CollectBlenderShotSelection¶
Phase: collect
Puts the set shots of the Sequencer's selected strips into ctx.settings["turnover_shots"], for ExtractTurnoverPlan.selection_only. A selected strip that shows a linked file's sequencer stands for every set shot inside it. Usually disabled in the config and switched on together with selection_only by one setting.
ExtractBlenderSequencerFrames¶
Phase: extract
Renders frame ranges from the Blender VSE (OpenGL render) to temp render paths. Handles both a single SequencePipelineData and a LayeredSequencePipelineData (renders each layer separately). Output settings come from a media preset plus optional field overrides, applied like in ConfigBlenderRenderOutput; each sequence gets path, file_format, image and color set. All changed render settings are restored after rendering. A publish renders one frame per step, so Blender stays responsive and Esc cancels between frames.
| Field | Default | Description |
|---|---|---|
slot_in |
"main" |
Input slot (reads SequencePipelineData or LayeredSequencePipelineData) |
slot_out |
"main" |
Output slot (writes the same data with path/format/settings filled in) |
preset |
"png_8" |
Image preset |
file_format, bit_depth, compression, quality, channels |
"" |
Overrides, same values as ConfigBlenderRenderOutput |
shading_type |
"MATERIAL" |
VSE viewport shading. One of WIREFRAME, SOLID, MATERIAL, RENDERED |
Blender load actions¶
Load actions are used in kpipe-load.toml and run as ordered [[pipeline.<key>.load]] steps.
The version and layer to load, and the reference id, are not step fields — they are provided on the pipeline context (ctx.version, ctx.layer, ctx.ref_id) by the session.
Every LoadAction owns the lifecycle of what it creates:
| Method | Called by | Purpose |
|---|---|---|
execute(ctx) |
Session.load_product() |
New load as ctx.ref_id |
update(ctx) -> list[str] |
Session.update_product() |
Switch to ctx.version / ctx.layer; ctx.reference holds the previous state. Returns ids of other references that changed along with it. Default: run execute() again |
remove(ctx) |
Session.remove_product() |
Delete what execute() created. Default: nothing. Steps run in reverse order |
can_update() |
load / update | False makes the reference non-updatable (the UI shows its version without a dropdown). Default: True |
References store the [[load]] steps they were created with. update_product() uses the current pipeline config when it still exists, else the stored steps; remove_product() always uses the stored steps, then runs the DCC's remove_ref_handler as a generic sweep of anything still tagged with kpipe_ref_id.
LoadBlenderLink¶
Loads a collection from a published .blend (e.g. an asset_blend) into a local container collection tagged with kpipe_ref_id, kpipe_load_mode, kpipe_library and kpipe_collection.
| Field | Default | Description |
|---|---|---|
mode |
"override" |
instance (linked, placed via a collection-instance Empty), override (linked, library override hierarchy for animating rigs) or append (local copy) |
collection |
"" |
Source collection name template. Empty: the collection recorded at publish time in the product's extra table (extra.collection, written by the asset pipeline, e.g. "Prop-Tree"), else "{entity}" |
relative_path |
true |
Store the library path relative to the workfile |
update() repoints and reloads the linked library, keeping overrides and local animation. Blender links each file only once, so all references sharing the library switch together; their ids are returned and the session updates their versions too. Appended references can't be updated. remove() deletes the container and drops the library once no other reference uses it.
LoadBlenderMedia¶
Loads a published image, image sequence, or video as a Blender image datablock. The datablock is tagged with a kpipe_ref_id custom property linking it back to the reference entry in the sidecar file; if a datablock tagged with the same reference already exists it is reloaded in place rather than duplicated. Datablock names are purely cosmetic ("{entity} {product_type} [{layer}]") — identity always lives in the custom property, so the 63-character name limit and Blender's .001 dedup suffixes are harmless.
| Field | Default | Description |
|---|---|---|
source |
"AUTO" |
AUTO detects from metadata; or set explicitly to SEQUENCE, MOVIE, or FILE |
AUTO detection rules: if frame-end > frame-start in the publish metadata → SEQUENCE; if the file is a video according to kpipe.media (get_media_type()) → MOVIE; otherwise → FILE.
If the publish metadata has a media.color ColorSpec (written by IntegrateMedia), it's resolved through OCIO and set as the datablock's colorspace. Display/view specs are left to Blender's default.
LoadBlenderTextureSet¶
Loads all maps in a published texture set (layer = material name, ctx.layer) as individual Blender image datablocks, applying the color stored per map in the publish metadata (colorspaces, as ColorSpec strings such as role:srgb_texture or role:data, resolved to Blender colorspace names through OCIO). Colorspaces can be overridden per map via colorspace_override. Maps with UDIM tiles load as one tiled image (source = "TILED"); in a render, a UV position on a tile the map doesn't have shows Cycles' magenta missing-image colour. Every created datablock is tagged with kpipe_ref_id (so the full set can be found and removed as a unit) and kpipe_map (the map name, e.g. base_color) custom properties; datablock names are cosmetic ("{entity} {product_type} [{layer}] {map}").
| Field | Default | Description |
|---|---|---|
colorspace_override |
{} |
Dict mapping map name to a Blender colorspace string, overriding stored metadata |
SetupBlenderPBRMaterial¶
Wires texture-set datablocks into a Principled BSDF material node tree. Runs as a second [[load]] step after LoadBlenderTextureSet: it finds all image datablocks tagged with the current ctx.ref_id, identifies each map via the kpipe_map custom property, and connects it to the matching BSDF socket. All textures share one Texture Coordinate and Mapping node, normal maps get a Normal Map node in between and height maps a Bump node (chained after the Normal Map if the set has both). AO, SSS and unknown maps get a texture node but stay unconnected.
The step uses the same node setup as the shader editor's PBR Shader from Texture Set operator (kpipe.create_pbr_shader), which builds it on demand from any loaded set in the current material. The load step completes the setup in place, so it can re-run on version updates: existing image nodes are reused, and only free inputs are linked, so manual edits stay.
The material is named after ctx.layer (the published texture-set layer name). If that is empty, the active material on the selected object is used; when neither yields a name the step is skipped (or raises if skip_no_mat = false).
| Field | Default | Description |
|---|---|---|
normal_strength |
1.0 |
Strength value on the Normal Map node |
skip_no_mat |
true |
Skip silently when no material name can be resolved instead of raising |
Substance Painter actions¶
Used by the pipeline substance_pbr_textures (kpipe-publish.substance.toml, dcc = "substance", run from the Publish tab of the K-Pipe dock). They run on Painter's main thread, so Painter doesn't respond during the export.
CollectSubstanceTextureSets¶
Phase: collect
Checks that a project is open and not busy, and writes {layer: texture set name} to slot_out (default texture_sets) for all texture sets of the project. A layer is the set's name cleaned to [A-Za-z0-9_]. Two sets cleaning to the same layer, and sets using material layering (several stacks), fail the publish.
ExtractSubstanceTextureSets¶
Phase: extract
Exports the texture sets of slot_in to the entity's render folder with a preset defined in the action ($textureSet_<Map>_$colorSpace(.$udim)) and writes a LayeredTextureSetPipelineData to slot_out, one TextureSetPipelineData per layer. Maps, colors and UDIM tiles are read from the file names like in CollectPBRTextureSet. IntegrateMedia publishes each set to its own layer with its own version counter.
| Field | Default | Description |
|---|---|---|
file_format |
"exr" |
File format |
bit_depth |
"16f" |
Bit depth, must be supported by the format |
size_log2 |
0 |
Texture size as a power of two (11 = 2048), 0 = the project's size |
padding |
"infinite" |
Painter's padding algorithm |
dithering |
false |
Use dithering |
height |
false |
Also export the height channel. Off by default, as Painter's OpenGL normal map is expected to contain it already and a shader using both would apply it twice |
TVPaint actions¶
CollectTVPaintJSON¶
Phase: collect
Collects animation layers from the JSON written by TVPaint's export-to-image-sequence command. Each layer with at least one linked frame becomes one SequencePipelineData in a LayeredSequencePipelineData, with the layer's frame range and the configured color. The JSON path must be stored in slot_in_path before the pipeline runs (set by the TVPaint extension).
| Field | Default | Description |
|---|---|---|
slot |
"main" |
Output slot (writes LayeredSequencePipelineData) |
slot_in_path |
"json_path" |
Input slot with the path to the TVPaint JSON |
skip_bg |
true |
Skip the layer named bg |
color |
"role:srgb_texture" |
ColorSpec assigned to every collected layer |
Kitsu actions¶
ValidateKitsuTask¶
Phase: validate
Fails if the entity has no such task, before anything is rendered: IntegrateKitsuMedia runs after the media is published, so failing there would leave a version without its preview.
| Field | Default | Description |
|---|---|---|
task |
"" |
The task's name, empty = the pipeline's task. Use the same as for IntegrateKitsuMedia |
IntegrateKitsuMedia¶
Phase: integrate
Uploads a rendered image or video as a preview revision on the current Kitsu task. For a SequencePipelineData slot the first frame (from the frame template, file or render directory) is uploaded as a still; for a VideoPipelineData slot the video file is uploaded. Reads note from ctx.settings and attaches it as the revision comment.
| Field | Default | Description |
|---|---|---|
slot_in |
"publish" |
Input slot (reads SequencePipelineData or VideoPipelineData); defaults to the slot IntegrateMedia writes the published media to |
task_status |
"" |
Short name of the Kitsu task status to set on upload (e.g. "wfa"). Empty = no change |
set_as_main |
true |
Whether to set the uploaded preview as the entity's main thumbnail |
normalize_movie |
true |
Videos only: let Kitsu re-encode the video for streaming. Set to false for pre-encoded files |
OpenTimelineIO actions¶
Actions of the kpipe-otio package, registered through the kpipe.actions entry point. They only read the K-Pipe shot tags of a timeline (metadata.kpipe.edit.kpipe_shot, see kpipe_otio.editorial), so they work for timelines from any editor whose export is tagged (Blender: ExtractBlenderOtioTimeline).
ExtractTurnoverPlan¶
Phase: extract
Plans a turnover from an OTIO file: which sequences, shots, ranges, tasks and conform flags the production backend gets (kpipe.turnover.plan_turnover). The rules, shot states and sequence locks are described in Turnover. Publish from the main edit file: a timeline from another workfile layer gets a warning.
The summary goes to the log and ctx.messages, which the publish UIs show. The step fails before anything is integrated on:
- duplicate or invalid shot names
- bad [turnover.rules]
- Only selected shots with nothing selected
- missing write access, unless dry_run (Kitsu needs a personal login and checks the rights itself)
It writes the plan to slot_out for IntegrateTurnover. The conform flags name the timeline: the file's own published version if it has one (CollectPublisherProducts), else the version this publish resolved. With neither, no flags are written.
| Field | Default | Description |
|---|---|---|
slot_in |
"otio" |
Input slot with the OTIO file (FilePipelineData) |
slot_out |
"turnover" |
Output slot with the TurnoverPlan |
update_timing |
true |
Write changed ranges of existing shots; off: every range change is a conform request |
create_tasks |
true |
Add the default tasks to new shots, and the missing ones to existing shots |
tasks |
[] |
Default task codes; empty = [turnover] tasks of kpipe.toml |
mark_omitted |
false |
Flag shots missing from the timeline as omitted (never deleted) |
selection_only |
false |
Only the shots in ctx.settings["turnover_shots"] (from CollectBlenderShotSelection) |
shots |
"" |
Filter of sequences or shots, e.g. "sq010 sq020_sh030"; empty = all |
dry_run |
false |
Only report; set it on IntegrateTurnover too |
IntegrateTurnover¶
Phase: integrate Writes the plan to the production backend: sequences, then shots with their tasks, then ranges, then refreshes the production cache. A failing item doesn't stop the others; the step fails at the end listing them. Running the turnover again is safe. Without a backend (local projects) only the production cache is written.
| Field | Default | Description |
|---|---|---|
slot_in |
"turnover" |
Input slot with the plan of ExtractTurnoverPlan |
dry_run |
false |
Write nothing |
Example (publisher_turnover in kpipe-publish.standalone.toml; Blender: blender_edit_turnover):
[pipeline.publisher_turnover]
dcc = "publisher"
product_type = "edit_timeline"
layers = false
[pipeline.publisher_turnover.settings]
dry_run = { text = "Dry run (only report)", type = "bool", default_value = false, target = ["ExtractTurnoverPlan.dry_run", "IntegrateTurnover.dry_run"] }
[[pipeline.publisher_turnover.collect]]
action = "CollectPublisherProducts"
file_type = "file"
slot_out = "otio"
[[pipeline.publisher_turnover.extract]]
action = "ExtractTurnoverPlan"
[[pipeline.publisher_turnover.integrate]]
action = "IntegrateTurnover"
K-Pipe GUI¶
Actions provided by the kpipe_gui package. They are registered automatically when kpipe_gui is imported.
CollectPublisherPaths¶
Phase: collect
Reads the file paths added in the K-Pipe Publisher tool and writes them to a slot as a list[PurePosixPath]. The publisher injects the dropped paths into ctx.settings["source_files"] before calling run_publish(). Raises PublishError if no files were provided.
Typically the first collect step in a publisher pipeline, feeding its output slot into a downstream action such as CollectImageFiles.
| Field | Default | Description |
|---|---|---|
slot |
"main" |
Output slot (writes list[PurePosixPath]) |
Example:
[[pipeline.my_publisher_pipeline.collect]]
action = "CollectPublisherPaths"
slot = "source_files"
[[pipeline.my_publisher_pipeline.collect]]
action = "CollectImageFiles"
slot_in_paths = "source_files"
slot = "images"
CollectPublisherProducts¶
Phase: collect
Collects a published product picked in the K-Pipe Publisher as the source of another publish. The publisher lists the entity's published layers of product_type instead of its file list and passes the chosen one as ctx.settings["source_layer"] (empty = main). Nothing is copied. An image sequence becomes a protected SequencePipelineData (frame range, fps, image settings and colour from the publish metadata), which targets the publish layer, not the source layer. A file becomes a FilePipelineData naming its source layer and version (don't pass that slot to CleanMedia).
| Field | Default | Description |
|---|---|---|
slot_out |
"main" |
Output slot |
product_type |
"" |
Product type to read; empty = the pipeline's (templates such as "{task}_render" are resolved) |
version |
"latest" |
A number or a version name |
file_type |
"sequence" |
Expected product: "sequence", "file" or "" for either |