改用相机网格生成笔画纹理
统一笔画配置与彩色 patch 的投影网格,替换旧脚本及产物。 已验证 Blender 导出、16 项相关测试、文档与 lint 门禁。
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
"""Convert camera-projected colour-patch cells to black capsule strokes.
|
||||
"""Convert camera-projected 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
|
||||
@@ -7,8 +7,8 @@ projection. It does not enforce a physical minimum stroke width.
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import bpy
|
||||
|
||||
@@ -19,22 +19,41 @@ from camera_grid_stroke_optimizer import optimize_angles
|
||||
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,
|
||||
_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,
|
||||
)
|
||||
|
||||
MODE = "camera_grid_capsules"
|
||||
DEFAULT_PARAMS = {
|
||||
"seed": 7,
|
||||
"width_ratio": 0.42,
|
||||
"length_ratio": 0.84,
|
||||
"orientation_mode": "random",
|
||||
"proposals": 100000,
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
parser.add_argument("--params", type=Path, default=None)
|
||||
parser.add_argument("--artifact-prefix", default="camera_grid_stroke")
|
||||
parser.add_argument("--seed", type=int, default=None)
|
||||
parser.add_argument("--width-ratio", type=float, default=None)
|
||||
parser.add_argument("--length-ratio", type=float, default=None)
|
||||
parser.add_argument("--orientation-mode", choices=("random", "optimized"),
|
||||
default="random", help="Use seeded random or experimental optimized angles")
|
||||
parser.add_argument("--proposals", type=int, default=100000,
|
||||
default=None, help="Use seeded random or experimental optimized angles")
|
||||
parser.add_argument("--proposals", type=int, default=None,
|
||||
help="Angle changes attempted in optimized mode")
|
||||
return parser.parse_args(argv)
|
||||
|
||||
@@ -45,7 +64,7 @@ def _pixel_polygon(uv_polygon, width, height):
|
||||
|
||||
|
||||
def _render(uv_name, geometry, settings, output_dir, width_ratio, length_ratio,
|
||||
angle_map=None):
|
||||
angle_map=None, artifact_prefix="camera_grid_stroke"):
|
||||
width, height = settings["width"], settings["height"]
|
||||
nx, ny = settings["nx"], settings["ny"]
|
||||
seed = settings.get("seed") or 0
|
||||
@@ -78,7 +97,9 @@ def _render(uv_name, geometry, settings, output_dir, width_ratio, length_ratio,
|
||||
|
||||
image.pixels[:] = pixels
|
||||
image.update()
|
||||
label = "camera_grid_stroke_optimized" if angle_map is not None else "camera_grid_stroke"
|
||||
label = artifact_prefix
|
||||
if angle_map is not None and artifact_prefix == "camera_grid_stroke":
|
||||
label += "_optimized"
|
||||
base = output_dir / f"{label}_{uv_name}"
|
||||
save_png_image(image, base.with_suffix(".png"))
|
||||
page_mesh = _clipped_polygons(mesh_polygons, width, height)
|
||||
@@ -96,10 +117,25 @@ def _render(uv_name, geometry, settings, output_dir, width_ratio, length_ratio,
|
||||
|
||||
def main():
|
||||
args = _args()
|
||||
if not 0 < args.width_ratio <= args.length_ratio < 1:
|
||||
params = dict(DEFAULT_PARAMS)
|
||||
if args.params is not None:
|
||||
loaded = json.loads(args.params.read_text(encoding="utf-8"))
|
||||
if not isinstance(loaded, dict) or set(loaded) - set(DEFAULT_PARAMS):
|
||||
raise ValueError("invalid stroke params")
|
||||
params.update(loaded)
|
||||
for key in DEFAULT_PARAMS:
|
||||
value = getattr(args, key)
|
||||
if value is not None:
|
||||
params[key] = value
|
||||
if not 0 < params["width_ratio"] <= params["length_ratio"] < 1:
|
||||
raise ValueError("require 0 < width-ratio <= length-ratio < 1")
|
||||
if params["orientation_mode"] not in ("random", "optimized") or params["proposals"] < 1:
|
||||
raise ValueError("invalid orientation mode or proposal count")
|
||||
job = json.loads(args.job.read_text(encoding="utf-8"))
|
||||
settings = job["settings"]
|
||||
if job.get("schema") != "orisys-sensor-design/texture-job-v3":
|
||||
raise ValueError("camera-grid strokes require a single-view texture-job-v3")
|
||||
settings = dict(job["settings"])
|
||||
settings["seed"] = params["seed"]
|
||||
obj = _prepare_mesh_object(job["objects"]["mesh"])
|
||||
cam = _require_object(job["objects"]["camera"], "CAMERA", "Camera")
|
||||
names = [entry["uv_map"] for entry in job["outputs"]]
|
||||
@@ -108,12 +144,12 @@ def main():
|
||||
args.output_dir.mkdir(parents=True, exist_ok=True)
|
||||
args.output_dir = args.output_dir.resolve()
|
||||
angle_map = None
|
||||
if args.orientation_mode == "optimized":
|
||||
if params["orientation_mode"] == "optimized":
|
||||
angle_map, report = optimize_angles(
|
||||
settings["nx"], settings["ny"], settings.get("seed") or 0,
|
||||
args.width_ratio, args.length_ratio, args.proposals,
|
||||
params["width_ratio"], params["length_ratio"], params["proposals"],
|
||||
)
|
||||
report_path = args.output_dir / "camera_grid_stroke_optimization.json"
|
||||
report_path = args.output_dir / f"{args.artifact_prefix}_optimization.json"
|
||||
report_path.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8")
|
||||
print(f"Camera-grid optimization: {report}")
|
||||
geometry = build_patch_geometry(
|
||||
@@ -123,7 +159,8 @@ def main():
|
||||
)
|
||||
for name in names:
|
||||
_render(name, geometry[name], settings, args.output_dir,
|
||||
args.width_ratio, args.length_ratio, angle_map)
|
||||
params["width_ratio"], params["length_ratio"], angle_map,
|
||||
args.artifact_prefix)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user