82 lines
3.2 KiB
Python
82 lines
3.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""ui_layout_image 스킬용 렌더 헬퍼 (dark HMI 스타일).
|
|
사용 예:
|
|
PYTHONUTF8=1 py .claude/skills/ui_layout_image/render_helpers.py # 데모 1장 생성
|
|
(셸 cwd 는 호출마다 초기화되므로 맨 파일명으로 실행하지 않는다)
|
|
실전에서는 import 하여 panel/kv/button/label 로 화면별 도식을 그린다.
|
|
"""
|
|
import os
|
|
import matplotlib
|
|
matplotlib.use("Agg")
|
|
import matplotlib.pyplot as plt
|
|
from matplotlib.patches import FancyBboxPatch, Rectangle
|
|
import matplotlib.font_manager as fm
|
|
|
|
# ---- 한글 폰트 ----
|
|
FONT = r"C:\Windows\Fonts\malgun.ttf"
|
|
try:
|
|
fp = fm.FontProperties(fname=FONT)
|
|
plt.rcParams["font.family"] = fp.get_name()
|
|
except Exception:
|
|
pass
|
|
plt.rcParams["axes.unicode_minus"] = False
|
|
|
|
# ---- dark HMI 팔레트 (프로젝트 UI와 일치) ----
|
|
BG = "#2D2D30"; PANEL = "#3E3E42"; BORDER = "#6E6E72"; HEADER = "#007ACC"
|
|
TXT = "#DDDDDD"; PURPLE = "#7E57C2"; BLUE = "#2D6CA2"; GREEN = "#2E7D32"; GRAY = "#555558"
|
|
INPUT = "#252528"; VALCOLOR = "#9CDCFE"
|
|
|
|
|
|
def new_canvas(w, h):
|
|
fig = plt.figure(figsize=(w / 100, h / 100), dpi=100)
|
|
ax = fig.add_axes([0, 0, 1, 1])
|
|
ax.set_xlim(0, w); ax.set_ylim(h, 0); ax.axis("off")
|
|
ax.add_patch(Rectangle((0, 0), w, h, facecolor=BG, edgecolor="none"))
|
|
return fig, ax
|
|
|
|
|
|
def panel(ax, x, y, w, h, header=None, fc=PANEL):
|
|
ax.add_patch(FancyBboxPatch((x + 2, y + 2), w - 4, h - 4, boxstyle="round,pad=2",
|
|
facecolor=fc, edgecolor=BORDER, linewidth=1.2))
|
|
if header:
|
|
ax.add_patch(Rectangle((x + 4, y + 4), w - 8, 22, facecolor=HEADER, edgecolor="none"))
|
|
ax.text(x + 12, y + 15, header, color="white", fontsize=9, fontweight="bold", va="center")
|
|
return x + 8, y + 30
|
|
|
|
|
|
def label(ax, x, y, text, size=8.5, color=TXT, bold=False, ha="left"):
|
|
ax.text(x, y, text, color=color, fontsize=size, va="center", ha=ha,
|
|
fontweight=("bold" if bold else "normal"))
|
|
|
|
|
|
def kv(ax, x, y, w, key, val=""):
|
|
ax.text(x + w * 0.46, y, key, color=TXT, fontsize=8, va="center", ha="right")
|
|
ax.add_patch(Rectangle((x + w * 0.5, y - 9), w * 0.48, 18, facecolor=INPUT, edgecolor=BORDER, lw=0.6))
|
|
if val:
|
|
ax.text(x + w * 0.52, y, val, color=VALCOLOR, fontsize=8, va="center", ha="left")
|
|
|
|
|
|
def button(ax, x, y, w, h, text, color=BLUE):
|
|
ax.add_patch(FancyBboxPatch((x, y), w, h, boxstyle="round,pad=1.5", facecolor=color, edgecolor="none"))
|
|
ax.text(x + w / 2, y + h / 2, text, color="white", fontsize=8.5, fontweight="bold", ha="center", va="center")
|
|
|
|
|
|
def save(fig, path):
|
|
d = os.path.dirname(path)
|
|
if d: # 파일명만 준 경우 dirname 이 '' 이라 makedirs 가 FileNotFoundError 를 낸다
|
|
os.makedirs(d, exist_ok=True)
|
|
fig.savefig(path, facecolor=BG)
|
|
plt.close(fig)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
fig, ax = new_canvas(600, 300)
|
|
panel(ax, 10, 10, 580, 280, "Demo Panel")
|
|
kv(ax, 30, 60, 300, "Key", "Value")
|
|
button(ax, 30, 90, 120, 28, "Run", PURPLE)
|
|
# 출력은 스킬 폴더가 아니라 SKILL.md 가 지정한 로컬 산출 폴더로 보낸다.
|
|
repo = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
|
|
out = os.path.join(repo, "UnTrackedLocalFiles", "hmi_mockups", "_demo.png")
|
|
save(fig, out)
|
|
print("demo saved:", out)
|