Flexbile Python use in Blender

This post is about using Python in Blender. It’s not a tutorial. It’s more of an encouragement to learn and experiment with Python.

Imagine you work on a piece. The first stage is very experimental. You look for what works and what doesn’t. At some point the piece and the process take shape. Some things become repetitive. It’s very useful to notice those patterns early and optimize them.

Lets say I’m working in ZBrush and I want to render in Blender. The geometry moves between those two pretty often. I don’t want to spend time clicking around to export and import meshes. Here is where Python comes in.

I want to quickly reimport meshes from ZBrush. I don’t want to manually reapply materials and I don’t want to manually delete previous versions. I don’t want to create a full plugin or a system for doing that. I want to create a beautiful piece. Python is there to help me but it isn’t the goal itself. I want something I can use very quickly. No fancy stuff. Simplicity and full control.

Here you can see the process I need:

This has been achieved with a simple Python script:

# Put this script in Blender file structure, lib/michal folder. Remember to create
# __init__.py file in that folder.
#
# To reload this script in Blender session:
#
# from importlib import reload
# reload(michal.utils)
#

import bpy
from pathlib import Path

PATH_ROOT = Path(r"C:\Users\mc\Desktop\devil_lady\parts")

RING = {"file": PATH_ROOT / Path("ring.obj"), "mat": "ring", "object_name": "ring"}
HEAD = {"file": PATH_ROOT / Path("head.obj"), "mat": "head", "object_name": "head"}
HAIR_MAIN = {"file": PATH_ROOT / Path("hair_main.obj"), "mat": "hair", "object_name": "hair_main"}
BODY = {"file": PATH_ROOT / Path("body.obj"), "mat": "body", "object_name": "body"}
MOUTH_CHAIN = {"file": PATH_ROOT / Path("mouth_chain.obj"), "mat": "gold", "object_name": "mouth_chain"}


def material_exists(mat: str):
  if mat in bpy.data.materials.keys():
    return bpy.data.materials[mat]
  else:
    return None

def delete_by_name(name: str):
  # TODO(michalc): restore original selection?
  bpy.ops.object.select_all(action='DESELECT')
  try:
    bpy.data.objects[name].select_set(True)
    bpy.ops.object.delete()
  except KeyError:
    print(f"Failed to delete {name}. Not present.")

def import_obj(obj_info):
  delete_by_name(obj_info["object_name"])
  status = bpy.ops.import_scene.obj(filepath=str(obj_info["file"]))
  obj_object = bpy.context.selected_objects[0]
  obj_object.name = obj_info["object_name"]
  # TODO(michalc): rename the obj_object.data ... mesh object inside.
  mat_for_object = material_exists(obj_info["mat"])
  print(f"Imported {obj_object.name}, matching material: {mat_for_object}")
  print(obj_object.data)
  print(bpy.context.view_layer.objects.active)

  if mat_for_object:
    obj_object.data.materials[0] = mat_for_object

What’s so special about it? Nothing. Absolutely nothing. That’s the point. Something simple with paths hardcoded. I can reimport body mesh with:

michal.utils.import_obj(michal.utils.BODY)

I can quickly add more objects by just adding constants in the script file. You might think that this is the opposite of flexible. Yes, the script is pretty rigid. YOU should be flexible about using Python.

This script is part of the project. When I’m done I’ll archive it with the project. It’s not a system or a plugin. It’s some simple functionality that helps me solve specific issues in the workflow.

It does require some Python knowledge. You can’t be working on you piece and suddenly start learning Python. I mean, you can, but do you want to? You turned your PC to sculpt and you’re suddenly coding? I’m lucky enough because I work with Python. That allows me to write scripts like that quickly.

I’d like to convince you to step outside of your comfort zone. Learning Python can make you a better artist.

Yes, I know about GoB/GoZ. It’s a very limited plugin that doesn’t give a lot of control. It was always underwhelming.