Skip to content

hoyer-a/head-bisection

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Head Bisection Pipeline

Separates a head mesh from a torso mesh, selectively remeshes the cut boundary region, and smooths the seam to produce a clean, intersection-free result.


Pipeline Overview

Blender                         Python (PyMeshLab)              Blender
──────────────────              ──────────────────────          ──────────────────────
bisect_head_torso.py    →       RemeshVertices.py       →       smooth_remeshed.py
  Cut mesh at plane               Remesh boundary region          Smooth the seam
  Export PLY + indices            Output remeshed PLY             Export final PLY

Files produced at each step

Step Produces
Step 1 mesh_in_cut.ply — cut mesh (head removed)
Step 1 mesh_in_vertices.txt — boundary vertex indices
Step 1 selected_faces.txt — boundary face indices
Step 1 cut_plane.json — cut plane geometry for step 3
Step 2 mesh_in_remeshed.ply — remeshed mesh
Step 2 remesh_log_<timestamp>.txt — detailed remesh statistics
Step 3 mesh_smoothed.ply — final smoothed mesh

Requirements

Step 1 & 3 — Blender

  • Blender 3.6 or later (4.x recommended)
  • 3D Print Toolbox extension enabled (for self-intersection checks in step 3)
    • Blender 4.2+: Extensions → Get Extensions → search "3D Print Toolbox"
    • Older: Edit → Preferences → Add-ons → search "3D Print Toolbox"

Step 2 — Python

  • Python 3.10+
  • pymeshlab, numpy, scipy
pip install pymeshlab numpy scipy

Step 1 — Bisect in Blender (bisect_head_torso.py)

What it does: Cuts the mesh at a plane you define, removes the head geometry using a Boolean difference, selects the resulting boundary band of vertices, and exports everything needed for the next steps.

Setup in Blender:

  1. Import your mesh (e.g. mesh_in.obj or .ply)
  2. Create a plane object — name it <mesh_name>_plane (e.g. mesh_in_plane)
  3. Position and orient the plane at the desired cut location
    • The plane's local Z axis must point away from the head (toward the torso/body)
    • Use the object's rotation to orient it correctly
  4. Open the Scripting workspace in Blender

Configure the script — edit these lines at the top of bisect_head_torso.py:

# ⚠️ Set this to your working directory (where outputs will be saved)
OUTPUT_DIR: str = "/Users/antonhoyer/Documents/git-repositories/head_bisection"

# Width of the boundary band selected around the cut (fraction of mesh diagonal).
# Increase if the selected band looks too narrow in the viewport.
MARGIN_FACTOR: float = 0.05

# Cut method: False = Boolean (recommended for clean meshes), True = Bisect (faster)
USE_BISECT: bool = False

Run: Press Run Script in the Scripting workspace.

The script will:

  • Cut the mesh at the plane
  • Select a band of boundary vertices
  • Export <mesh_name>_cut.ply, <mesh_name>_vertices.txt, selected_faces.txt, cut_plane.json

After running, the mesh will be left in Edit Mode with the boundary selection visible so you can verify it looks correct before proceeding.


Step 2 — Remesh (RemeshVertices.py)

What it does: Loads the cut PLY, selects the boundary faces identified in step 1, dilates the selection by one ring of faces (with a spatial bounding-box clip to prevent it spreading across the whole mesh), and runs isotropic explicit remeshing on that region only. The rest of the mesh is untouched.

This step is slow. Expect several minutes for high-resolution meshes (~700k+ faces). Progress is printed to the console.

Configure the script — edit the bottom of RemeshVertices.py:

# ⚠️ Input: the cut PLY exported by step 1.
#    Must match the filename produced by bisect_head_torso.py (<mesh_name>_cut.ply)
"mesh_in_cut.ply",          # ← change "mesh_in" to your mesh name

# ⚠️ Output: remeshed PLY. This filename is used again in step 3.
"mesh_in_remeshed.ply",     # ← change to match your mesh name

The working directory is wherever you run the script from. The script loads mesh_in_vertices.txt and selected_faces.txt from the current directory, so either run it from OUTPUT_DIR or adjust the paths at the top:

vertices = np.loadtxt('mesh_in_vertices.txt', dtype=np.int64)
face_indices = np.loadtxt('selected_faces.txt', dtype=np.int64)

Run:

cd /path/to/your/output_dir
python RemeshVertices.py

A timestamped log file (remesh_log_<timestamp>.txt) is written with full statistics. Check the Boundary Containment section — it should show a low "outside region" ratio.

Remesh parameters (in local_remesh_from_indices):

ms.meshing_isotropic_explicit_remeshing(
    iterations=20,            # More = smoother triangles, slower
    targetlen=ml.PercentageValue(0.100007),   # Target edge length as % of mesh bbox
    maxsurfdist=ml.PercentageValue(0.199988)  # Max deviation from original surface
)

Step 3 — Smooth the seam (smooth_remeshed.py)

What it does: Imports the remeshed PLY into a fresh Blender scene, re-selects the boundary band by world-space position (using the geometry saved in cut_plane.json), and applies Blender's built-in Smooth operator to the selected vertices. Repeats until no self-intersections remain, then attempts a geometric intersection repair if smoothing alone is insufficient. Exports the final cleaned mesh.

Configure the script — edit these lines at the top of smooth_remeshed.py:

# ⚠️ Path to your working directory
WORK_DIR: str = "/Users/antonhoyer/Documents/git-repositories/head_bisection"

# ⚠️ Remeshed PLY from step 2 (used as fallback if cut_plane.json has no entry)
DEFAULT_REMESHED_PLY: str = os.path.join(WORK_DIR, "mesh_in_remeshed.ply")

# ⚠️ Output path for the smoothed final mesh
DEFAULT_SMOOTHED_PLY: str = os.path.join(WORK_DIR, "mesh_smoothed.ply")

The script reads the remeshed PLY path from cut_plane.json if present (written automatically by step 1). If your mesh name is different from mesh_in, make sure cut_plane.json was generated by step 1 with the correct mesh name, or update DEFAULT_REMESHED_PLY manually.

Smoothing parameters:

SMOOTH_ITERATIONS: int = 10   # Passes per round (higher = smoother per round)
SMOOTH_FACTOR: float = 0.5    # 0.0–1.0, identical to Blender's Smooth factor slider
MAX_SMOOTH_ROUNDS: int = 5    # Safety cap on smooth+check rounds before repair

Run from Blender:

# Background mode (no UI):
blender --background --python smooth_remeshed.py

# With an open .blend file (useful for debugging — mesh will be visible):
blender your_file.blend --python smooth_remeshed.py

Or paste/run directly in Blender's Scripting workspace.

The script will print per-round intersection counts and whether it passed or required the geometric repair fallback.


File Reference

File Purpose Created by
bisect_head_torso.py Cut mesh + export indices — (run in Blender)
RemeshVertices.py Selective remesh of boundary — (run with Python)
smooth_remeshed.py Seam smoothing + intersection repair — (run in Blender)
save_indices.py Standalone helper to export selected face indices from Blender manually
mesh_in_cut.ply Cut mesh (head removed) Step 1
mesh_in_vertices.txt Boundary vertex indices Step 1
selected_faces.txt Boundary face indices Step 1
cut_plane.json Cut plane world-space geometry Step 1
mesh_in_remeshed.ply Selectively remeshed mesh Step 2
remesh_log_*.txt Remesh statistics log Step 2
mesh_smoothed.ply Final smoothed output mesh Step 3

Troubleshooting

Remesh spreads over the whole mesh (dilation issue) The BFS dilation is clipped to a spatial bounding box around the original selection. Check the Faces within bbox line in the remesh log. If the number is very high, your original selection may be too large or spread across the mesh.

No vertices selected in step 3 The margin_factor stored in cut_plane.json (from step 1) controls the selection band width. If the remesher moved vertices significantly, the band may miss them. Increase MARGIN_FACTOR in bisect_head_torso.py and re-run step 1 to regenerate cut_plane.json.

Self-intersections remain after smoothing The script automatically falls back to bpy.ops.mesh.intersect(use_self=True) which cuts the mesh at the actual intersection lines and resolves them geometrically. Increase MAX_SMOOTH_ROUNDS if you want more smoothing attempts before the repair.

Step 2 is very slow / appears stuck Normal for large meshes. Progress is printed every chunk. The selection re-injection (range-based) and the remesh itself (20 iterations) are the slow parts. Check the console for Starting remeshing on N faces... — once you see that, the remesher is running.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages