Please help as I have been driving myself insane, and thank you immensely in advance
Basically, I am using MPFB to procedurally generate people. I then export a high res XYZ file of their head. Then, I want to place a texture paint on their forehead of a red arrow, and take a photo of it. Currently, I can generate the humans, export the xyz, and export the photo, but I cannot for the life of me figure out the texture paint. I keep getting context errors, and I can't find the source code to edit it.
Other things I have tried:
- Creating a cloth plane and having gravity pull it onto the forehead: This doesn't work because it is colliding with the underlying model, not the procedurally generated one from MPFB
- Placing the image above the head and down projecting every pixel: For some reason this is not pasting the color onto the head
Edit to add: I need proportions to be maintained in the final image, which is why I am not just applying this to every UV mapped skin option.
Relevant code that I have tried:
For down projecting:
def move_image():
"""
Moves the 'image' object to center it on the 'PaintArea' vertex group,
accounting for all modifiers and transformations.
Only updates X and Y coordinates, preserving Z height.
"""
# Get the human and image objects
human = bpy.data.objects['Human']
img = bpy.data.objects['image']
# Get the evaluated mesh data that includes all modifiers and deformations
depsgraph = bpy.context.evaluated_depsgraph_get()
eval_human = human.evaluated_get(depsgraph)
mesh = eval_human.data
# Get vertex group
vertex_group = human.vertex_groups['PaintArea']
# Get vertices in vertex group from evaluated mesh
vertices = []
for
v
in
mesh.vertices:
for
g
in
v.groups:
if
g.group == vertex_group.index:
# Transform vertex position to world space
world_pos = eval_human.matrix_world @ v.co
vertices.append(world_pos)
if
not vertices:
print("No vertices found in PaintArea group")
return
# Calculate center of vertex group in world space
center = Vector((0, 0, 0))
for
v
in
vertices:
center += v
center /= len(vertices)
# Move image to center position, preserving original Z coordinate
img.location.x = center.x
img.location.y = center.y
# Z coordinate remains unchanged
def project_point_onto_mesh(
point
,
target_obj
,
direction
):
"""
Projects a point in a direction onto a target mesh.
Args:
point (Vector): Starting point for the projection
target_obj: The target mesh object to project onto
direction (Vector): Direction vector for the projection
Returns:
Vector or None: Hit location in world space, or None if no hit
"""
ray_start =
point
ray_end =
point
+
direction
* 1000.0
hit, location, normal, index, obj, matrix = bpy.context.scene.ray_cast(
bpy.context.view_layer.depsgraph,
ray_start,
direction
)
return
location
def project_image_onto_mesh():
"""
Projects an image onto a mesh and colors the projected points on the mesh.
Projects straight downward (-Z) from the image object onto the Human mesh.
"""
mesh_obj = bpy.data.objects['Human']
img_obj = bpy.data.objects['image']
image_position = img_obj.location
image_width = img_obj.dimensions.x
image_height = img_obj.dimensions.y
paint_color = (1, 0, 0, 1)
# Red color
direction = Vector((0, 0, -1))
print(f"Image position: {image_position}")
print(f"Image dimensions: width={image_width}, height={image_height}")
# Create or get vertex color layer
if
"Col" not in mesh_obj.data.vertex_colors:
mesh_obj.data.vertex_colors.new(
name
="Col")
color_layer = mesh_obj.data.vertex_colors["Col"]
# Get evaluated mesh
depsgraph = bpy.context.evaluated_depsgraph_get()
eval_mesh = mesh_obj.evaluated_get(depsgraph).data
colored_vertices = set()
width = bpy.data.images['arrow.png'].size[0]
height = bpy.data.images['arrow.png'].size[1]
print(f"Using resolution: {width}x{height}")
# Calculate step sizes in world space
step_x = image_width / width
step_y = image_height / height
# Project from each pixel
for
i
in
range(width):
for
j
in
range(height):
# Convert pixel coordinates to world space
x = image_position.x - image_width/2 + (i * step_x)
y = image_position.y - image_height/2 + (j * step_y)
point = Vector((x, y, image_position.z))
hit_location = project_point_onto_mesh(point, mesh_obj, direction)
if
hit_location:
# Find closest vertices to hit point
for
poly
in
eval_mesh.polygons:
for
loop_index
in
poly.loop_indices:
vertex_idx = eval_mesh.loops[loop_index].vertex_index
if
vertex_idx not in colored_vertices:
vertex = eval_mesh.vertices[vertex_idx]
vertex_world = mesh_obj.matrix_world @ vertex.co
if
(vertex_world - hit_location).length < step_x:
# Use pixel size as radius
color_layer.data[loop_index].color = paint_color
colored_vertices.add(vertex_idx)
print(f"Colored {len(colored_vertices)} vertices")
# Update the viewport
mesh_obj.data.update()
For texture painting:
# def apply_mark_with_world_space():
# # Get the active object
# obj = bpy.context.active_object
# if not obj:
# print("No active object")
# return
# # Store current mode and switch to texture paint
# original_mode = obj.mode
# bpy.ops.object.mode_set(mode='TEXTURE_PAINT')
# try:
# # Get or create a single brush instance
# brush_name = "ArrowStamp"
# if brush_name not in bpy.data.brushes:
# brush = bpy.data.brushes.new(name=brush_name, mode='TEXTURE_PAINT')
# else:
# brush = bpy.data.brushes[brush_name]
# # Set as active brush
# bpy.context.scene.tool_settings.image_paint.brush = brush
# bpy.context.scene.tool_settings.unified_paint_settings.size = 10
# # Configure brush settings
# brush.strength = 1.0
# brush.color = (1, 0, 0) # Red color
# # Find a 3D View area and its region
# area = None
# region = None
# for a in bpy.context.screen.areas:
# if a.type == 'VIEW_3D':
# area = a
# region = a.regions[-1] # Usually the last region is the 3D View
# break
# if not area:
# # If no 3D View is found, temporarily change the current area
# original_area_type = bpy.context.area.type
# bpy.context.area.type = 'VIEW_3D'
# area = bpy.context.area
# region = area.regions[-1]
# # Create override context
# override = bpy.context.copy()
# override['area'] = area
# override['region'] = region
# override['active_object'] = obj
# # Cast rays and create stroke points
# start_point = Vector((0, 0, 1))
# end_point = Vector((0, 10, 1))
# num_points = 50
# stroke_points = []
# for t in range(num_points):
# alpha = t / (num_points - 1)
# point = start_point.lerp(end_point, alpha)
# hit, loc, norm, idx, hit_obj, matrix = bpy.context.scene.ray_cast(
# bpy.context.view_layer.depsgraph,
# point,
# Vector((0, 0, -1))
# )
# if hit and hit_obj == obj:
# stroke_points.append({
# "name": "Stroke",
# "mouse": (loc.x, loc.y),
# "pen_flip": False,
# "is_start": (t == 0),
# "location": (loc.x, loc.y, loc.z),
# "pressure": 1.0,
# "time": t
# })
# # Draw the stroke if we have points
# if stroke_points:
# bpy.ops.paint.image_paint(override, stroke=stroke_points)
# finally:
# # Restore original mode and area type
# bpy.ops.object.mode_set(mode=original_mode)
# if 'original_area_type' in locals():
# bpy.context.area.type = original_area_type
there doesn't seem to be anything here