Skip to content
The Technical ArtistArt. Code. Pipelines. Games.
Home/Python/First Pipeline Script
tutorial - starter
you build a tool
~45 minutes

Your First Pipeline Script

Startertutorial~4 min read

You have a folder called textures containing files named rock albedo 2.png, Rock_Albedo_FINAL.png, and rockalbedo-old(1).png. By the end of this page you'll have a real command-line tool that fixes it - safely - and you'll have learned loops, functions, and paths through the fixing, not before it.

You walk away with

A working CLI tool (download the finished script) with dry-run mode, collision detection, and an undo log - plus three useful habits for changing files safely. If you've read Intro to Python, you're ready.

On this page Why This Project 1 - See The Files 2 - The Convention Function 3 - Plan, Don't Act 4 - Safety Rails 5 - Make It A Tool The Challenge

Why A Renamer, Of All Things

A renamer brings together useful pipeline problems: inconsistent inputs, naming rules, and files that need to stay recoverable. You’ll practise walking folders, working with strings, and previewing changes. Use a scratch folder first so you can check the result without risking real assets.

Step 1 - See The Files Before Touching Them

Everything starts with pathlib. Make a scratch folder with some badly-named files (or download the finished script later and test on a copy), then:

Python
from pathlib import Path

folder = Path(r"D:/scratch/textures") # r"" so backslashes behave on Windows
for f in folder.iterdir():
 if f.is_file():
 print(f.stem, "|", f.suffix) # name without extension | extension

Run it. That loop - for each file, look at it - is the basic structure of a batch tool. f.stem and f.suffix doing the splitting for you is why we use pathlib and not string chopping.

Step 2 - The Convention Function

Decide the target: T_Rock_Albedo_02.png - a T_ prefix, TitleCase words joined by underscores, numbers padded to two digits. Now write the function that converts one name. One name, not the folder - small functions you can test in isolation are the habit that keeps tools debuggable:

Python
import re

def convention(name, prefix="T_"):
 # split on spaces, dashes, underscores AND camelCase seams
 words = re.split(r"[\s\-_]+", re.sub(r"(?<=[a-z0-9])(?=[A-Z])", " ", name))
 words = [w for w in words if w]

 if words and words[-1].isdigit(): # pad trailing numbers: 2 -> 02
 words[-1] = f"{int(words[-1]):02d}"

 new = "_".join(w if w.isdigit() else w.title() for w in words)
 return new if new.startswith(prefix) else prefix + new

# test it immediately, in the same file:
for test in ["rock albedo 2", "Rock_Albedo_FINAL", "rockAlbedo-old"]:
 print(test, "->", convention(test))

The regex looks scarier than it is: one split pattern for separators, one lookahead to cut camelCase at the seam. Try them on a few names and inspect the results. The important move is the last three lines - testing the function on hostile examples before it ever meets a real file.

Step 3 - Plan, Don't Act

Make the first run a preview: the script's default mode changes nothing. It builds a plan and shows you:

Python
def plan_renames(folder, prefix="T_"):
 pairs = []
 for f in sorted(folder.iterdir()):
 if not f.is_file():
 continue
 new_name = convention(f.stem, prefix) + f.suffix.lower()
 if new_name != f.name: # skip already-correct files
 pairs.append((f, f.with_name(new_name)))
 return pairs

for old, new in plan_renames(folder):
 print(f"{old.name:<40} -> {new.name}")

Run it, read the plan, find the surprise. Check for files you didn’t intend to include and names the function handles incorrectly. Fixing the plan is much easier than recovering from an incorrect rename.

Step 4 - Safety Rails

Add two checks before changing any files. Collision detection: if rock albedo 2.png and Rock_Albedo_02.png both map to the same new name, one would silently vanish. Refuse loudly instead:

Python
targets = [b.name for _, b in pairs]
dupes = {t for t in targets if targets.count(t) > 1}
if dupes:
 raise SystemExit(f"COLLISION: multiple files map to {sorted(dupes)}")

An undo log: when you do rename, write every old -> new pair to a text file first. It's four lines, and it converts "irreversible" into "annoying to reverse" - which gives you a practical recovery path:

Python
with open(folder / "_rename_log.txt", "a", encoding="utf-8") as fh:
 for old, new in pairs:
 old.rename(new)
 fh.write(f"{old.name} -> {new.name}\n")

Step 5 - Make It A Tool

A script becomes a tool when someone else can run it without reading it. argparse gives you a folder argument, a --prefix option, an --apply flag (dry-run stays the default - previewing changes should be the default), and free --help text. The finished script assembles all five steps - compare it against yours; then test any differences you find.

Shell
python ta_batch_rename.py D:/textures # shows the plan
python ta_batch_rename.py D:/textures --apply # commits it
python ta_batch_rename.py --help # someone else's first command

The Challenge

Extend it - pick one, or all three

1 (warm-up): add --recursive so it walks subfolders too (rglob is your friend - mind the log file). 2 (real): add suffix detection - files containing "normal"/"nrm" get _N appended, "roughness" gets _R, so rock normal.png -> T_Rock_N.png. 3 (job-interview-grade): write ta_batch_rename_undo.py, which reads a rename log and reverses it. When all three work, you have a portfolio piece - write the breakdown.

Where next: run this same project inside a DCC with the Rosetta Stone, look through the snippet library for another example, or level the tool up with a watch folder (snippet library -> CLI & Automation).