Files
orisys-sensor-design/blender/scripts/camera_grid_stroke.py
T
ykcheungab 2111a9f32f 新增基于摄像机网格投影的黑白笔画原型
沿用彩色块的网格与可见性投影,便于评估透视适配的笔画。已通过几何单元测试及 250/500 像素光流探针。
2026-10-05 21:49:26 +08:00

114 lines
4.5 KiB
Python

"""Convert camera-projected colour-patch cells to black capsule strokes.
Run with Blender's source geometry and the existing texture job. This first
camera-space prototype preserves the colour pattern's grid and visibility
projection. It does not enforce a physical minimum stroke width.
"""
import argparse
import json
from pathlib import Path
import sys
import bpy
sys.path.insert(0, str(Path(__file__).resolve().parent))
from camera_grid_stroke_geometry import capsule_in_cell, projected_stroke_uv
from patch_geometry import clip_rect
from patch_projection import build_patch_geometry
from random_patch_dd02c import (
_clipped_polygons, _clipped_shapes, _prepare_mesh_object, _require_object,
_require_uv_layer, fill_background, fill_polygon, save_png_image,
write_pdf_vector, write_svg_vector,
)
def _args():
argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
parser = argparse.ArgumentParser()
parser.add_argument("--job", type=Path, required=True)
parser.add_argument("--output-dir", type=Path, required=True)
parser.add_argument("--width-ratio", type=float, default=0.42)
parser.add_argument("--length-ratio", type=float, default=0.84)
return parser.parse_args(argv)
def _pixel_polygon(uv_polygon, width, height):
return [(u * (width - 1), v * (height - 1))
for u, v in clip_rect(uv_polygon, 0.0, 1.0, 0.0, 1.0)]
def _render(uv_name, geometry, settings, output_dir, width_ratio, length_ratio):
width, height = settings["width"], settings["height"]
nx, ny = settings["nx"], settings["ny"]
seed = settings.get("seed") or 0
image_name = f"CameraGridStroke_{uv_name}"
if image_name in bpy.data.images:
bpy.data.images.remove(bpy.data.images[image_name])
image = bpy.data.images.new(image_name, width=width, height=height, alpha=True)
pixels = list(image.pixels)
fill_background(pixels, width, height, (0, 0, 0, 1))
mesh_polygons = [_pixel_polygon(poly, width, height) for poly in geometry["mesh"]]
mesh_polygons = [poly for poly in mesh_polygons if len(poly) >= 3]
for poly in mesh_polygons:
fill_polygon(None, pixels, width, height, poly, (1, 1, 1, 1))
shapes = []
outline_cache = {}
for i, j, fragment, loop_uvs in geometry["camera_fragments"]:
key = (i, j)
if key not in outline_cache:
outline_cache[key] = capsule_in_cell(
i, j, nx, ny, seed, width_ratio, length_ratio)
uv_poly = projected_stroke_uv(fragment, loop_uvs, outline_cache[key])
pixel_poly = _pixel_polygon(uv_poly, width, height)
if len(pixel_poly) < 3:
continue
shapes.append((pixel_poly, (0, 0, 0, 1)))
fill_polygon(None, pixels, width, height, pixel_poly, (0, 0, 0, 1))
image.pixels[:] = pixels
image.update()
base = output_dir / f"camera_grid_stroke_{uv_name}"
save_png_image(image, base.with_suffix(".png"))
page_mesh = _clipped_polygons(mesh_polygons, width, height)
page_shapes = _clipped_shapes(shapes, width, height)
vector = settings["vector"]
vector_args = (width, height, vector["width_mm"], vector["height_mm"])
write_svg_vector(base.with_suffix(".svg"), *vector_args,
page_mesh, page_shapes, [], (0, 0, 0, 1), uv_name,
artwork_label="stroke")
write_pdf_vector(base.with_suffix(".pdf"), *vector_args,
page_mesh, page_shapes, [], (0, 0, 0, 1),
artwork_label="stroke")
print(f"{uv_name}: {len(outline_cache)} cells, {len(shapes)} projected fragments")
def main():
args = _args()
if not 0 < args.width_ratio <= args.length_ratio < 1:
raise ValueError("require 0 < width-ratio <= length-ratio < 1")
job = json.loads(args.job.read_text(encoding="utf-8"))
settings = job["settings"]
obj = _prepare_mesh_object(job["objects"]["mesh"])
cam = _require_object(job["objects"]["camera"], "CAMERA", "Camera")
names = [entry["uv_map"] for entry in job["outputs"]]
for name in names:
_require_uv_layer(obj, name)
args.output_dir.mkdir(parents=True, exist_ok=True)
args.output_dir = args.output_dir.resolve()
geometry = build_patch_geometry(
obj, cam, bpy.context.scene, bpy.context.evaluated_depsgraph_get(),
names, settings["nx"], settings["ny"], settings["edge_sample_grid"],
capture_camera_fragments=True,
)
for name in names:
_render(name, geometry[name], settings, args.output_dir,
args.width_ratio, args.length_ratio)
if __name__ == "__main__":
main()