Files
orisys-sdk/tools/3d_patch_generation_blender/test_blender.py
T
ykcheungab ee21695795 clean up
2026-05-10 17:43:07 +08:00

278 lines
9.1 KiB
Python

import bpy
import bmesh
import numpy as np
from mathutils import Vector
import os
# ----------------------------------------------------------------------
# Scanline polygon fill for Blender Image pixels
# ----------------------------------------------------------------------
def fill_polygon(image, pixels, width, height, polygon, color):
poly = [(int(round(x)), int(round(y))) for x, y in polygon]
if len(poly) < 3:
return
min_x = max(0, min(p[0] for p in poly))
max_x = min(width-1, max(p[0] for p in poly))
min_y = max(0, min(p[1] for p in poly))
max_y = min(height-1, max(p[1] for p in poly))
for y in range(min_y, max_y + 1):
intersections = []
for i in range(len(poly)):
x1, y1 = poly[i]
x2, y2 = poly[(i+1) % len(poly)]
if y1 == y2:
continue
if (y1 <= y < y2) or (y2 <= y < y1):
t = (y - y1) / (y2 - y1)
x = x1 + t * (x2 - x1)
intersections.append(x)
intersections.sort()
for k in range(0, len(intersections), 2):
if k+1 >= len(intersections):
break
x_start = int(round(intersections[k]))
x_end = int(round(intersections[k+1]))
x_start = max(0, min(width-1, x_start))
x_end = max(0, min(width-1, x_end))
for x in range(x_start, x_end + 1):
idx = (y * width + x) * 4
pixels[idx:idx+4] = color
# ----------------------------------------------------------------------
# Get interpolated UV from face and hit location (fixed version)
# ----------------------------------------------------------------------
def get_uv_from_face(obj, face_index, location):
me = obj.data
local_loc = obj.matrix_world.inverted() @ location
poly = me.polygons[face_index]
vert_indices = poly.vertices
loop_indices = poly.loop_indices
if len(vert_indices) < 3:
return None
v0 = me.vertices[vert_indices[0]].co
v1 = me.vertices[vert_indices[1]].co
v2 = me.vertices[vert_indices[2]].co
uv_layer = me.uv_layers.active.data
uv0 = uv_layer[loop_indices[0]].uv
uv1 = uv_layer[loop_indices[1]].uv
uv2 = uv_layer[loop_indices[2]].uv
def tri_area(a, b, c):
return 0.5 * ((b - a).cross(c - a)).length
total_area = tri_area(v0, v1, v2)
if total_area == 0:
return None
w0 = tri_area(local_loc, v1, v2) / total_area
w1 = tri_area(local_loc, v2, v0) / total_area
w2 = 1.0 - w0 - w1
uv = w0 * uv0 + w1 * uv1 + w2 * uv2
return Vector((uv.x, uv.y))
# ----------------------------------------------------------------------
# Color generation functions (unchanged)
# ----------------------------------------------------------------------
def merge_intervals(intervals):
if not intervals:
return []
intervals.sort(key=lambda x: x[0])
merged = [intervals[0]]
for current in intervals[1:]:
last = merged[-1]
if current[0] <= last[1]:
merged[-1] = (last[0], max(last[1], current[1]))
else:
merged.append(current)
return merged
def get_allowed_intervals(neighbor_values, delta):
forbidden = []
for c in neighbor_values:
low = max(0, c - delta)
high = min(1, c + delta)
forbidden.append((low, high))
merged = merge_intervals(forbidden)
allowed = []
if merged:
if merged[0][0] > 0:
allowed.append((0, merged[0][0]))
for i in range(1, len(merged)):
allowed.append((merged[i-1][1], merged[i][0]))
if merged[-1][1] < 1:
allowed.append((merged[-1][1], 1))
else:
allowed.append((0, 1))
return allowed
def sample_from_allowed(allowed):
lengths = [end - start for start, end in allowed]
total_length = sum(lengths)
if total_length <= 0:
return None
point = np.random.rand() * total_length
cumulative = 0
for (start, end), length in zip(allowed, lengths):
cumulative += length
if point <= cumulative:
return np.random.uniform(start, end)
return allowed[-1][0]
def sample_channel(neighbor_values, delta):
allowed = get_allowed_intervals(neighbor_values, delta)
if not allowed:
return None
return sample_from_allowed(allowed)
def generate_valid_color(neighbors, r):
delta = np.sqrt(r)
color = np.zeros(3)
for channel in range(3):
neighbor_channel = [n[channel] for n in neighbors]
sampled = sample_channel(neighbor_channel, delta)
if sampled is None:
sampled = np.random.rand()
color[channel] = sampled
return color
# ----------------------------------------------------------------------
# Main function
# ----------------------------------------------------------------------
def generate_fingerprint_texture(
obj_name=None,
camera_name="Camera",
nx=40,
ny=30,
r=0.05,
texture_width=2048,
texture_height=2048,
output_path="//finger_texture.png"
):
scene = bpy.context.scene
depsgraph = bpy.context.evaluated_depsgraph_get()
if obj_name:
obj = bpy.data.objects[obj_name]
else:
obj = bpy.context.active_object
if obj is None or obj.type != 'MESH':
raise ValueError("No valid mesh object selected")
cam = bpy.data.objects[camera_name]
if cam is None or cam.type != 'CAMERA':
raise ValueError("Camera not found")
if not obj.data.uv_layers.active:
obj.data.uv_layers.new(name="UVMap")
render = scene.render
res_x = render.resolution_x
res_y = render.resolution_y
cam_data = cam.data
if cam_data.sensor_fit == 'HORIZONTAL':
sensor_width = cam_data.sensor_width
sensor_height = sensor_width / (res_x / res_y)
else:
sensor_height = cam_data.sensor_height
sensor_width = sensor_height * (res_x / res_y)
focal = cam_data.lens
frame_width = sensor_width / focal
frame_height = sensor_height / focal
patch_uv_polygons = []
patch_colors = np.zeros((ny, nx, 3), dtype=np.float32)
for j in range(ny):
v_start = j / ny
v_end = (j+1) / ny
for i in range(nx):
u_start = i / nx
u_end = (i+1) / nx
corners_ndc = [
(u_start, v_start),
(u_end, v_start),
(u_end, v_end),
(u_start, v_end)
]
uv_poly = []
for (u_ndc, v_ndc) in corners_ndc:
sx = (u_ndc - 0.5) * frame_width
sy = (v_ndc - 0.5) * frame_height
direction_cam = Vector((sx, sy, -1.0))
direction_world = cam.matrix_world.to_quaternion() @ direction_cam
direction_world.normalize()
origin = cam.matrix_world.translation
hit, location, normal, face_index, hit_obj, matrix = scene.ray_cast(depsgraph, origin, direction_world)
if hit and hit_obj == obj:
uv = get_uv_from_face(obj, face_index, location)
if uv is not None:
uv_poly.append(uv)
if len(uv_poly) < 3:
patch_uv_polygons.append(None)
continue
patch_uv_polygons.append(uv_poly)
neighbors = []
if j > 0:
neighbors.append(patch_colors[j-1, i])
if i > 0:
neighbors.append(patch_colors[j, i-1])
if len(neighbors) == 0:
color = np.random.rand(3)
else:
color = generate_valid_color(neighbors, r)
patch_colors[j, i] = color
img_name = "FingerprintTexture"
if img_name in bpy.data.images:
bpy.data.images.remove(bpy.data.images[img_name])
image = bpy.data.images.new(img_name, width=texture_width, height=texture_height, alpha=True)
pixels = list(image.pixels)
idx = 0
for j in range(ny):
for i in range(nx):
uv_poly = patch_uv_polygons[idx]
idx += 1
if uv_poly is None:
continue
pixel_poly = [(u * (texture_width - 1), v * (texture_height - 1)) for (u, v) in uv_poly]
color = patch_colors[j, i]
rgba = (color[0], color[1], color[2], 1.0)
fill_polygon(image, pixels, texture_width, texture_height, pixel_poly, rgba)
image.pixels[:] = pixels
image.update()
abs_path = bpy.path.abspath(output_path)
image.filepath_raw = abs_path
image.file_format = 'PNG'
image.save()
print(f"Texture saved to {abs_path}")
return image
# ----------------------------------------------------------------------
# Run
# ----------------------------------------------------------------------
if __name__ == "__main__":
generate_fingerprint_texture(
obj_name="FingerMesh", # Change to your mesh name
nx=50, ny=40,
r=0.05,
texture_width=4096,
texture_height=4096,
output_path="//finger_texture.png"
)