fix algining xynova texture to uv printing fixture.
This commit is contained in:
@@ -579,8 +579,17 @@ def cmd_texture_views(revision_dir: Path, args: argparse.Namespace) -> int:
|
||||
by_id = {view.get("id"): view for view in print_views.get("views", [])}
|
||||
for pair, _artifacts in targets:
|
||||
view = by_id.get(pair.id) or {}
|
||||
page = view.get("page_mm") or []
|
||||
source = view.get("page_source")
|
||||
# The derived page is read back from what the run wrote, never recomputed here, so
|
||||
# the console cannot describe a page the run did not use.
|
||||
page_text = (
|
||||
f" page {page[0]:.3f}x{page[1]:.3f}mm ({source})"
|
||||
if len(page) == 2
|
||||
else ""
|
||||
)
|
||||
console.report(
|
||||
f" [{pair.id}] region={pair.camera} -> {pair.uv_map}"
|
||||
f" [{pair.id}] region={pair.camera} -> {pair.uv_map}{page_text}"
|
||||
f" owns {view.get('owned_triangles', 0):,} triangles,"
|
||||
f" emits {view.get('emitted_fragments', 0):,} fragments"
|
||||
)
|
||||
|
||||
@@ -64,8 +64,8 @@ MULTIVIEW_JOB_FILENAME = "_texture_views_job.json"
|
||||
VIEWS_BLOCK_KEY = "texture_patch_views"
|
||||
|
||||
_BLOCK_KEYS = frozenset({"enabled", "pattern_camera", "pairs", "merge", "seam", "registration",
|
||||
"print"})
|
||||
_PAIR_KEYS = frozenset({"id", "camera", "uv_map", "filename", "seed", "pattern_camera"})
|
||||
"print", "page"})
|
||||
_PAIR_KEYS = frozenset({"id", "camera", "uv_map", "filename", "seed", "pattern_camera", "page"})
|
||||
_MERGE_KEYS = frozenset({
|
||||
"mode", "incidence_limit_deg", "min_footprint_mm2", "min_triangle_coverage",
|
||||
"max_hole_fraction", "component_policy",
|
||||
@@ -94,6 +94,20 @@ GAP_TREATMENTS = ("no_print", "reduced_density")
|
||||
GAP_DEFAULT_TREATMENT = "no_print"
|
||||
GAP_DEFAULT_ALIGNMENT_TOLERANCE_MM = 0.1
|
||||
_PRINT_KEYS = frozenset({"base_fill"})
|
||||
_PAGE_KEYS = frozenset({"mode", "px_per_mm"})
|
||||
|
||||
#: How a pair's page is chosen.
|
||||
#:
|
||||
#: ``auto`` derive it from the geometry: a layout authored by *Project from View (Bounds)*
|
||||
#: from its region camera maps [0,1]^2 to that camera's image-plane extent, so the
|
||||
#: page is measurable. Only valid for such a layout, and the driver **proves** it is
|
||||
#: one before using the result.
|
||||
#: ``fixed`` keep ``texture_patch.vector``. Required for unwrapped layouts (`produce`,
|
||||
#: `compute`), which are not projections and for which a camera-derived page would
|
||||
#: be meaningless. Also the escape hatch when `auto` refuses.
|
||||
PAGE_MODES = ("auto", "fixed")
|
||||
PAGE_DEFAULT_PX_PER_MM = 200.0
|
||||
PAGE_DEFAULTS = {"mode": "auto", "px_per_mm": PAGE_DEFAULT_PX_PER_MM}
|
||||
|
||||
#: What is drawn under the colour patches, i.e. the white ground the marker sits on.
|
||||
#:
|
||||
@@ -151,6 +165,7 @@ class ViewPair:
|
||||
uv_map: str
|
||||
target: TextureOutputTarget
|
||||
seed: int | None
|
||||
page_mode: str = PAGE_DEFAULTS["mode"]
|
||||
|
||||
def to_job_entry(self, output_dir: Path) -> dict[str, Any]:
|
||||
return {
|
||||
@@ -159,6 +174,7 @@ class ViewPair:
|
||||
"pattern_camera": self.pattern_camera,
|
||||
"uv_map": self.uv_map,
|
||||
"seed": self.seed,
|
||||
"page": self.page_mode,
|
||||
"artifacts": {
|
||||
format_name: str(path)
|
||||
for format_name, path in self.target.artifact_paths(output_dir).items()
|
||||
@@ -235,6 +251,12 @@ def validate_views_block(
|
||||
value = pair.get("pattern_camera")
|
||||
if not isinstance(value, str) or not value.strip():
|
||||
errors.append(f"{where}.pattern_camera: must be a non-empty object name")
|
||||
if "page" in pair:
|
||||
value = pair.get("page")
|
||||
if value not in PAGE_MODES:
|
||||
errors.append(
|
||||
f"{where}.page: must be one of {list(PAGE_MODES)} (got {value!r})"
|
||||
)
|
||||
pair_id = pair.get("id")
|
||||
if isinstance(pair_id, str) and pair_id.strip():
|
||||
if pair_id in seen_ids:
|
||||
@@ -280,6 +302,7 @@ def validate_views_block(
|
||||
)
|
||||
seen_camera_layout[key] = str(pair_id)
|
||||
|
||||
errors.extend(validate_page_block(block.get("page")))
|
||||
errors.extend(validate_merge_block(block.get("merge")))
|
||||
errors.extend(validate_seam_block(block.get("seam")))
|
||||
errors.extend(validate_print_block(block.get("print")))
|
||||
@@ -303,6 +326,28 @@ def validate_print_block(print_block: Any) -> list[str]:
|
||||
return errors
|
||||
|
||||
|
||||
def validate_page_block(page: Any) -> list[str]:
|
||||
"""Validate the optional ``page`` sub-block (how each pair's page is sized)."""
|
||||
if page is None:
|
||||
return []
|
||||
where = f"{VIEWS_BLOCK_KEY}.page"
|
||||
if not isinstance(page, Mapping):
|
||||
return [f"{where}: must be a mapping"]
|
||||
errors: list[str] = []
|
||||
extra = sorted(set(page) - _PAGE_KEYS)
|
||||
if extra:
|
||||
errors.append(f"{where}: unknown key(s) {extra}; allowed: {sorted(_PAGE_KEYS)}")
|
||||
mode = page.get("mode", PAGE_DEFAULTS["mode"])
|
||||
if mode not in PAGE_MODES:
|
||||
errors.append(f"{where}.mode: must be one of {list(PAGE_MODES)} (got {mode!r})")
|
||||
density = page.get("px_per_mm", PAGE_DEFAULTS["px_per_mm"])
|
||||
if not isinstance(density, (int, float)) or isinstance(density, bool):
|
||||
errors.append(f"{where}.px_per_mm: must be a number")
|
||||
elif float(density) <= 0.0:
|
||||
errors.append(f"{where}.px_per_mm: must be > 0 (got {density!r})")
|
||||
return errors
|
||||
|
||||
|
||||
def validate_merge_block(merge: Any) -> list[str]:
|
||||
"""Validate the optional ``merge`` sub-block (ownership policy)."""
|
||||
if merge is None:
|
||||
@@ -453,6 +498,7 @@ def resolve_view_pairs(
|
||||
# View) and define each pair's REGION for ownership; neither is the pattern source, so
|
||||
# the pattern default is the sensing camera that also defines `compute`.
|
||||
pattern_camera = str(block.get("pattern_camera") or settings.camera)
|
||||
default_page_mode = str(resolve_page_settings(block)["mode"])
|
||||
base_seed = settings.seed
|
||||
pairs: list[ViewPair] = []
|
||||
for raw in block["pairs"]:
|
||||
@@ -473,6 +519,7 @@ def resolve_view_pairs(
|
||||
filename=str(raw.get("filename") or _default_filename(pair_id)),
|
||||
),
|
||||
seed=None if seed is None else int(seed),
|
||||
page_mode=str(raw.get("page") or default_page_mode),
|
||||
)
|
||||
)
|
||||
return tuple(pairs)
|
||||
@@ -554,6 +601,22 @@ def resolve_print_settings(block: Mapping[str, Any] | None) -> dict[str, Any]:
|
||||
return settings
|
||||
|
||||
|
||||
def resolve_page_settings(block: Mapping[str, Any] | None) -> dict[str, Any]:
|
||||
"""Resolve the optional ``page`` sub-block into the job's page settings.
|
||||
|
||||
The page itself is not computed here: deriving it needs the mesh and the camera, so the
|
||||
driver does it and the host only carries the mode and the raster density.
|
||||
"""
|
||||
settings = dict(PAGE_DEFAULTS)
|
||||
sub = (block or {}).get("page") or {}
|
||||
if isinstance(sub, Mapping):
|
||||
for key in _PAGE_KEYS:
|
||||
if key in sub:
|
||||
settings[key] = sub[key]
|
||||
settings["px_per_mm"] = float(settings["px_per_mm"])
|
||||
return settings
|
||||
|
||||
|
||||
def build_multiview_job(
|
||||
paths: RevisionPaths,
|
||||
settings: TexturePatchSettings,
|
||||
@@ -579,6 +642,7 @@ def build_multiview_job(
|
||||
"merge": resolve_merge_policy(block),
|
||||
"seam": resolve_seam_settings(block),
|
||||
"print": resolve_print_settings(block),
|
||||
"page": resolve_page_settings(block),
|
||||
"settings": settings.to_job_settings(),
|
||||
"pairs": [pair.to_job_entry(Path(output_dir)) for pair in pairs],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user