Files
orisys-sensor-design/blender/scripts/patch_projection.py
T
2026-09-19 21:02:34 +08:00

74 lines
3.9 KiB
Python

"""Blender adapter for evaluated, camera-local loop-triangle fragments."""
from patch_geometry import cell_fragments, interpolate_uv
def build_patch_geometry(obj, cam, scene, depsgraph, uv_names, nx, ny, edge_sample_grid):
from mathutils import Vector
if cam.data.type not in {'PERSP', 'ORTHO'}:
raise ValueError('Patch projection requires a perspective or orthographic camera')
camera = cam.evaluated_get(depsgraph)
evaluated = obj.evaluated_get(depsgraph)
mesh = evaluated.to_mesh(preserve_all_data_layers=True, depsgraph=depsgraph)
try:
mesh.calc_loop_triangles()
layers = {name: mesh.uv_layers.get(name) for name in uv_names}
if any(layer is None for layer in layers.values()):
raise ValueError('An evaluated mesh UV layer is missing')
render = scene.render
projection = camera.calc_matrix_camera(
depsgraph, x=render.resolution_x, y=render.resolution_y,
scale_x=render.pixel_aspect_x, scale_y=render.pixel_aspect_y)
view = camera.matrix_world.normalized().inverted()
clip_matrix = projection @ view
origin = camera.matrix_world.translation
forward = camera.matrix_world.to_quaternion() @ Vector((0, 0, -1))
output = {name: {'mesh': [], 'patches': []} for name in uv_names}
ray_count = 0
for triangle in mesh.loop_triangles:
world = [evaluated.matrix_world @ mesh.vertices[i].co for i in triangle.vertices]
clips = [tuple(clip_matrix @ p.to_4d()) for p in world]
uvs = {name: [tuple(layer.data[i].uv) for i in triangle.loops]
for name, layer in layers.items()}
for name in uv_names:
output[name]['mesh'].append(uvs[name])
cache = {}
def visible(weights):
nonlocal ray_count
key = tuple(round(w, 12) for w in weights)
if key in cache:
return cache[key]
point = sum((p * w for p, w in zip(world, weights)), Vector())
depth = -(view @ point).z
ray_origin = origin if camera.data.type == 'PERSP' else point - forward * depth
direction = point - ray_origin
distance = direction.length
# Relative hit-position tolerance in world units; never rely on
# original/evaluated polygon indices (modifiers can change them).
tolerance = max(1e-7, distance * 2e-5)
if distance <= tolerance:
cache[key] = False
return False
direction.normalize()
hit, location, normal, index, hit_obj, matrix = scene.ray_cast(
depsgraph, ray_origin, direction, distance=distance + tolerance)
ray_count += 1
same_object = hit_obj is not None and getattr(hit_obj, 'original', hit_obj) == obj.original
result = bool(hit and same_object and (location - point).length <= tolerance)
cache[key] = result
return result
for i, j, fragment in cell_fragments(clips, nx, ny, visible, edge_sample_grid):
for name in uv_names:
polygon = [interpolate_uv(p, uvs[name]) for p in fragment]
output[name]['patches'].append((i, j, polygon))
print(f'Evaluated {len(mesh.loop_triangles)} loop triangles; {ray_count} visibility rays; '
f'{len(output[uv_names[0]]["patches"])} local fragments per UV target')
print(f'Visibility sampling: seven probes per fragment; uniformly probed '
f'fragments are accepted or dropped whole, mixed boundary fragments '
f'refine to 1/4 cell or depth 6 and unresolved leaves are rejected. '
f'Sub-sample occluders may be missed; hit tolerance max(1e-7, distance*2e-5).')
return output
finally:
evaluated.to_mesh_clear()