# -*- coding: utf-8 -*-
"""Pass 10: '도면 그림' 과 '만들어진 OBJ' 를 나란히 놓은 비교 PNG 를 만든다.
   눈으로 대조해야 진짜 맞는지 알 수 있다(제5조 — 화면으로 확인).
   사용: python pass10_compare.py 2110DN-0004-001:2656x250 ...
        (도번:도면상 부재 크기WxH — 그 크기의 덩어리를 찾아 그 영역을 렌더한다)
"""
import os, sys, re, json
import numpy as np
import matplotlib; matplotlib.use('Agg')
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
from matplotlib import font_manager, rcParams
for f in ('Malgun Gothic', 'Gulim', 'Batang'):
    if any(f == x.name for x in font_manager.fontManager.ttflist):
        rcParams['font.family'] = f; break
rcParams['axes.unicode_minus'] = False
import trimesh

import product          # 제품 고유값 모음 — 기본값 500pro
from shape_common import SCR, FAB, load_index, build_segments, components, dist_to_box

d1, d2 = load_index()
segs = build_segments(d2)
comps = components(segs)
PNRE = product.PN
# 도번은 '부품표(BOM) 열' 과 '단품도 옆' 두 곳에 적힌다. 먼저 만난 것을 쓰면 부품표 좌표가
# 잡혀 엉뚱한 위치의 같은 크기 덩어리를 그려버린다(0031-004 가 RB702 보스 그림으로 나왔다).
# 부품표 열은 같은 x 에 도번이 여러 개 쌓이므로 그것으로 걸러 단품도 쪽 좌표만 쓴다.
from shape_common import callout_positions
callouts = {p: ps[0] for p, ps in callout_positions(d1).items()}


def find_box(pn, W, H):
    px, py = callouts[pn]
    best = None
    for ci, c in enumerate(comps):
        if abs(c['w']-W)/W > 0.02 or abs(c['h']-H)/max(H, 1) > 0.02:
            continue
        dd = dist_to_box(px, py, c['bbox'])
        if best is None or dd < best[0]:
            best = (dd, c['bbox'])
    return best[1] if best else None


def draw_dwg(ax, bb, pad=0.10):
    x0, y0, x1, y1 = bb
    mx, my = (x1-x0)*pad + 20, (y1-y0)*pad + 20
    X0, X1, Y0, Y1 = x0-mx, x1+mx, y0-my, y1+my

    def inw(x, y):
        return X0 <= x <= X1 and Y0 <= y <= Y1
    for l in d2['lines']:
        if inw(l[0], l[1]) or inw(l[2], l[3]):
            ax.plot([l[0], l[2]], [l[1], l[3]], '-', color='#1a1a1a', lw=0.8)
    for a in d2['arcs']:
        if inw(a[0], a[1]):
            s, e = a[3], a[4]
            if e < s: e += 360
            t = np.radians(np.linspace(s, e, 40))
            ax.plot(a[0]+a[2]*np.cos(t), a[1]+a[2]*np.sin(t), '-', color='#1a1a1a', lw=0.8)
    for c in d1['circles']:
        if inw(c[2], c[3]):
            ax.add_patch(plt.Circle((c[2], c[3]), c[4], fill=False, color='#1a1a1a', lw=0.8))
    ax.set_xlim(X0, X1); ax.set_ylim(Y0, Y1); ax.set_aspect('equal')
    ax.set_xticks([]); ax.set_yticks([])


def draw_obj(ax, mesh):
    v, f = mesh.vertices, mesh.faces
    tri = v[f]
    pc = Poly3DCollection(tri, facecolor='#5b9bd5', edgecolor='#20415f', linewidths=0.12, alpha=1.0)
    ax.add_collection3d(pc)
    b = mesh.bounds; c = b.mean(0); r = float((b[1]-b[0]).max())/2 * 1.05
    ax.set_xlim(c[0]-r, c[0]+r); ax.set_ylim(c[1]-r, c[1]+r); ax.set_zlim(c[2]-r, c[2]+r)
    ax.view_init(elev=24, azim=-58)
    ax.set_xticks([]); ax.set_yticks([]); ax.set_zticks([])
    try: ax.set_box_aspect((1, 1, 1))
    except Exception: pass


def one(pn, W, H, out):
    bb = find_box(pn, W, H)
    if bb is None:
        print('  [!] %s: %gx%g 덩어리 못 찾음' % (pn, W, H)); return None
    fn = FAB + '\\' + product.OBJ_PREFIX + '%s.obj' % (product.obj_tail(pn))
    m = trimesh.load(fn)
    fig = plt.figure(figsize=(16, 6))
    a1 = fig.add_subplot(1, 2, 1)
    draw_dwg(a1, bb)
    a1.set_title('도면 그림  %s  (도면상 %.1f x %.1f mm)' % (pn, W, H), fontsize=12)
    a2 = fig.add_subplot(1, 2, 2, projection='3d')
    draw_obj(a2, m)
    e = m.extents
    a2.set_title('만든 OBJ  %.1f x %.1f x %.1f mm  watertight=%s  faces=%d'
                 % (e[0], e[1], e[2], m.is_watertight, len(m.faces)), fontsize=12)
    plt.tight_layout(); plt.savefig(out, dpi=110); plt.close(fig)
    print('  saved', out)
    return out


def out_path(pn):
    """비교 PNG 경로. 제품마다 파일명을 갈라 500PRO 산출물을 덮지 않게 한다."""
    if product.NAME == '500pro':
        return os.path.join(SCR, '비교_%s.png' % pn.replace('2110DN-', ''))
    return os.path.join(SCR, '비교%s_%s.png' % (product.NAME.upper(), pn))


if __name__ == '__main__':
    # --all : manifest 에 등록된 부재 전량을 자동으로 돈다.
    # 크기는 이미 확정된 OBJ 의 extents 를 쓴다(사람이 손으로 다시 재지 않는다).
    if '--all' in sys.argv:
        items = json.load(open(product.MANIFEST, encoding='utf-8'))['items']
        print('비교 PNG 대상 %d개' % len(items))
        nok = 0
        for it in items:
            pn = it['name'].split('/')[-1].strip()
            if pn not in callouts:
                print('  [!] %s: 도번 좌표 없음 — 건너뜀' % pn); continue
            e = it['size_mm']
            # 판재는 도면 외곽이 (가로x세로), 형강 옆면도는 (길이x춤) 이라 z 를 높이로 쓴다.
            cands = [(e[0], e[1]), (e[0], e[2])]
            done = False
            for W, H in cands:
                if one(pn, W, H, out_path(pn)):
                    nok += 1; done = True; break
            if not done:
                print('  [!] %s: 도면 덩어리 못 찾음 %s' % (pn, e))
        print('비교 PNG %d/%d 생성' % (nok, len(items)))
    else:
        for a in sys.argv[1:]:
            pn, dims = a.split(':')
            W, H = [float(x) for x in dims.lower().split('x')]
            one(pn, W, H, out_path(pn))
