Nodes/Avatar Graph/Mesh Split Normals
ComfyUI Node

Mesh Split Normals

Fixing shading seams on Avatar Graph's auto-built mesh

By avatechai·Created 3 years ago·Updated 2 years ago· 264
Mesh Split Normals
  • BPY_OBJ
  • BPY_OBJ

If you've never touched Blender before, "split normals" sounds like jargon for its own sake. It isn't - it's the fix for a very specific, very visible problem: a mesh that shades wrong at an edge because neighboring faces are sharing a smoothed normal when they shouldn't be.

What it is, in the context of Avatar Graph

Avatar Graph doesn't invent its own mesh math. It embeds actual Blender - via the bpy pip package - right inside the ComfyUI process, and most of its Mesh_* nodes, this one included, are thin wrappers around Blender's own bpy.ops.mesh.* operators. Mesh_SplitNormals wraps bpy.ops.mesh.split_normals().

The pipeline this node lives in is: segment a 2D character image (SAM) → turn each segment into a plane mesh (Create Mesh Layer) → unwrap it (Plane Texture Unwrap) → clean up the geometry → add shape keys for blinking and lipsync → wire it all into the real-time "Shape Flow" runtime. Meshes generated automatically from a flat image plane, especially after a subdivide or a symmetrize pass, sometimes come out with shading artifacts along edges - a dark seam or a weird gradient where two faces meet. Split Normals is the reset button for that: it splits the custom normal at each vertex so every face corner gets its own copy instead of sharing a smoothed one with its neighbors. The visual result is a harder, more faceted edge exactly where you selected - useful when you want a crease to read as a crease instead of blending away.

Inputs and outputs

There's exactly one field, and it's the same one every Mesh_* node in this pack shares:

  • BPY_OBJ (optional input) - the live Blender object flowing through your graph from an upstream node (Create Mesh Layer, another Mesh_* node, etc.).

Output is a single BPY_OBJ, the same object with its normals now split. Chain it straight into the next mesh-editing node, or on to shape keys and eventually Avatar Main Output.

One thing worth knowing about how the underlying operator behaves: like all of Blender's mesh operators, it acts on whatever is currently selected on the mesh - not automatically the whole thing. If nothing upstream left a selection, or the wrong verts/faces are selected, running this node can look like it did nothing at all. That's the operator working as designed, not a bug in the node.

Installing Avatar Graph

The one thing to get right before anything else: bpy only ships wheels for Python 3.10.x, so your ComfyUI environment has to be pinned to that.

Easiest path is ComfyUI Manager - search avatar-graph-comfyui (or "Avatar Graph"), install, restart. By hand:

cd ComfyUI/custom_nodes
git clone https://github.com/avatechai/avatar-graph-comfyui.git
cd avatar-graph-comfyui
python -m pip install -r requirements.txt

Then restart with python main.py --enable-cors-header (add --force-fp16 on Mac). On Windows, the author ships a whole prebuilt Python 3.10 environment specifically so you don't have to fight pip over the version pin - see Method 1 in the README. On macOS/Linux, the recommendation is a dedicated conda create --name comfyui python=3.10 rather than repurposing an existing env.

Common issues

Wrong Python version. If your ComfyUI isn't running 3.10.x, bpy fails to import and the whole node pack can silently fail to load. Check python --version first, before anything else, if these nodes aren't showing up.

No live avatar preview. --enable-cors-header is what lets ComfyUI talk to the embedded editor.avatech.ai panel. Skip it and you can still run the graph and export a .GLB/.GLTF by right-clicking Avatar Main Output → Save File - you just lose the real-time preview panel.

Sparse or missing tooltips. The README says outright the project is still changing ("we are still making changes to the nodes and demo templates"), which lines up with what you'll see in the UI - most of these nodes ship with no tooltip text at all. If a field's behavior isn't obvious, the fastest ground truth is Blender's own docs for the matching operator name, or the pack's Discord.

Categoryblender

Inputs (1)

NameTypeDefaultDescription
BPY_OBJoptBPY_OBJ

Outputs (1)

NameTypeDescription
BPY_OBJBPY_OBJ