生成无重叠的 0.2 mm Xynova 笔画图案

在打印页毫米坐标中筛选候选笔画,更新双 UV 产物。38 项测试通过;SVG 检查无交叠。
This commit is contained in:
ykcheungab
2026-10-05 14:56:34 +08:00
parent 0a3c3995f3
commit b2c39bd1b5
15 changed files with 34996 additions and 69989 deletions
+1 -1
View File
@@ -598,7 +598,7 @@ def cmd_stroke_texture(revision_dir: Path, args: argparse.Namespace) -> int:
"source_blend": {"path": str(paths.source_blend),
"sha256": sha256_file(paths.source_blend)},
# The v1 keys stay for compatibility; `resolved` carries what the block and the flag
# overrides actually produced, because with ten knobs the flags alone no longer
# overrides actually produced, because the flags alone no longer
# describe the run. The design.yaml and params hashes pin the tuning either way.
"settings": {
"mode": STROKE_MODE,
+8 -2
View File
@@ -3,7 +3,7 @@
The stroke generator is one mode, ``camera_compensated_width``: each source colour of the colour
artwork is one cell, and the mark inside it is a capsule whose width is scaled from that cell's
footprint on the print page — so a cell the projection stretches gets a proportionally larger
mark — then clamped. That gives ten live knobs, and this module owns their names, defaults and
mark — then clamped. This module owns the live knobs, their defaults and
ranges on the host side.
Why the keys are exactly these
@@ -45,12 +45,14 @@ STROKE_DEFAULTS: dict[str, Any] = {
"length_ratio": 2.4,
"coverage_cap": 0.45,
"neighbourhood_mm": 0.5,
"clearance_mm": 0.0,
"candidate_count": 1,
}
STROKE_KEYS = frozenset(STROKE_DEFAULTS)
#: Integer-valued keys, validated as ints so a fractional seed cannot reach the palette lookup.
STROKE_INT_KEYS = frozenset({"seed", "min_cell_pixels"})
STROKE_INT_KEYS = frozenset({"seed", "min_cell_pixels", "candidate_count"})
STROKE_PARAMS_FILENAME = "_stroke_pattern_params.json"
@@ -93,6 +95,10 @@ def validate_stroke_block(block: Any) -> list[str]:
if "min_cell_pixels" in block and block["min_cell_pixels"] < 1:
errors.append(f"{where}.min_cell_pixels: must be >= 1 (got {block['min_cell_pixels']!r})")
if "candidate_count" in block and block["candidate_count"] < 1:
errors.append(f"{where}.candidate_count: must be >= 1 (got {block['candidate_count']!r})")
if "clearance_mm" in block and block["clearance_mm"] < 0:
errors.append(f"{where}.clearance_mm: must be >= 0 (got {block['clearance_mm']!r})")
if "jitter_fraction" in block and not 0.0 <= block["jitter_fraction"] <= 1.0:
errors.append(
f"{where}.jitter_fraction: must be within [0, 1] (got {block['jitter_fraction']!r})"