mattrguerra/meridian-skillsmainPrivate on GitHub, published here in full

measure_template.py

Language Python4.1 KB104 lines

skills/artwork-template-rebuilder/scripts/measure_template.py

  1. #!/usr/bin/env python3
  2. """
  3. measure_template.py - Measure a vendor template's real geometry so the rebuild
  4. reproduces the SAME safe-line inset the vendor drew, template by template.
  5. This is the source of truth for dimensions. It does NOT read stated text labels
  6. ("Visual Area: X") and it does NOT guess a default. It finds the dashed safe-line
  7. rectangle the vendor actually drew and reports the bleed size + visual size in
  8. inches, plus the exact flags to hand to build_template.py.
  9. Usage:
  10. python measure_template.py /path/to/vendor.pdf
  11. Strategy:
  12. - Page MediaBox (in inches) = the bleed / outer boundary.
  13. - Collect every DASHED rectangle on the page (the safe line is always dashed).
  14. When colors are recoverable, a green or red dashed rect is the safe line; the
  15. innermost dashed rect wins when there are several (e.g. a red cut + green safe).
  16. - visual_w/visual_h = that rect's width/height in inches.
  17. - Insets are reported per side. If left!=right or top!=bottom beyond tolerance,
  18. it warns: build_template.py centers the visual area, so a non-centered safe line
  19. can't be reproduced exactly without the per-side path.
  20. If NO dashed rect is found, it says so plainly and emits no dimensions - the agent
  21. must then fall back (stated text, or a documented default) and TELL the user it did.
  22. """
  23. import sys
  24. import pdfplumber
  25. TOL_IN = 0.05 # inches; inset asymmetry below this is treated as centered/rounding
  26. def _in(pts):
  27. return pts / 72.0
  28. def measure(path):
  29. pdf = pdfplumber.open(path)
  30. page = pdf.pages[0]
  31. page_w_in, page_h_in = _in(float(page.width)), _in(float(page.height))
  32. # gather dashed rectangles (pdfplumber stores dash as ([pattern], phase) or None)
  33. dashed = []
  34. for r in page.rects:
  35. dash = r.get("dash")
  36. is_dashed = bool(dash and dash[0])
  37. if not is_dashed:
  38. continue
  39. x0, x1 = float(r["x0"]), float(r["x1"])
  40. top, bottom = float(r["top"]), float(r["bottom"])
  41. w_in = _in(abs(x1 - x0))
  42. h_in = _in(abs(bottom - top))
  43. # insets from page edge, in inches (top/bottom from pdfplumber are top-origin)
  44. ins_left = _in(x0)
  45. ins_right = page_w_in - _in(x1)
  46. ins_top = _in(top)
  47. ins_bottom = page_h_in - _in(bottom)
  48. dashed.append({
  49. "w_in": w_in, "h_in": h_in,
  50. "left": ins_left, "right": ins_right,
  51. "top": ins_top, "bottom": ins_bottom,
  52. "color": r.get("stroke_color"),
  53. "area": w_in * h_in,
  54. })
  55. print(f"FILE: {path}")
  56. print(f"BLEED (page MediaBox): {page_w_in:.2f} W x {page_h_in:.2f} H in")
  57. print(f"dashed rects found: {len(dashed)}")
  58. if not dashed:
  59. print("\nNO DASHED SAFE LINE FOUND.")
  60. print("=> Do NOT guess silently. Fall back to a stated visual size if the file")
  61. print(" gives one, else a documented default, and TELL THE USER what you used.")
  62. return
  63. # innermost dashed rect = safe line (smallest area). Color, when present, is a tiebreak.
  64. dashed.sort(key=lambda d: d["area"])
  65. safe = dashed[0]
  66. asym_x = abs(safe["left"] - safe["right"]) > TOL_IN
  67. asym_y = abs(safe["top"] - safe["bottom"]) > TOL_IN
  68. print("\nSAFE LINE (innermost dashed rect):")
  69. print(f" visual size : {safe['w_in']:.2f} W x {safe['h_in']:.2f} H in")
  70. print(f" insets (in) : left {safe['left']:.2f} right {safe['right']:.2f} "
  71. f"top {safe['top']:.2f} bottom {safe['bottom']:.2f}")
  72. if safe["color"] is not None:
  73. print(f" stroke color: {safe['color']}")
  74. if asym_x or asym_y:
  75. print(" WARNING: safe line is not centered on the page "
  76. f"({'L!=R ' if asym_x else ''}{'T!=B' if asym_y else ''}). "
  77. "build_template.py centers the visual area, so this won't reproduce exactly. "
  78. "Flag it to the user.")
  79. print("\nPASS TO build_template.py:")
  80. print(f" --bleed-w {page_w_in:.2f} --bleed-h {page_h_in:.2f} "
  81. f"--visual-w {safe['w_in']:.2f} --visual-h {safe['h_in']:.2f}")
  82. if __name__ == "__main__":
  83. if len(sys.argv) != 2:
  84. print("usage: python measure_template.py /path/to/vendor.pdf")
  85. sys.exit(1)
  86. measure(sys.argv[1])