# -*- coding: utf-8 -*-
r"""제품 고유값(경로·도번 정규식·형강 규격·출력 위치) 한 곳 모음.

왜 필요한가: 파이프라인(pass1~pass10)은 500PRO 프레임 하나에 맞춰 값이 여기저기
박혀 있었다. 두 번째 제품(400Q)을 같은 파이프라인으로 돌리려면 그 값들만 갈아끼울 수
있어야 한다. 알고리즘·게이트·수치 판정은 이 파일과 무관하며 하나도 바뀌지 않았다.

제품 선택 방법 (셋 중 앞선 것이 이긴다):
  1) 커맨드라인  --product 400q      (또는 --product=400q)
  2) 환경변수    DWG2OBJ_PRODUCT=400q
  3) 아무것도 없으면 '500pro'  ← 기존 스크립트를 인자 없이 돌리면 예전과 100% 동일

--product 토큰은 import 시점에 sys.argv 에서 제거한다. pass6/pass9/pass10 이
'--' 로 시작하지 않는 인자를 도번으로 읽기 때문에 남겨 두면 '400q' 를 도번으로 오해한다.
인자를 안 주면 argv 는 손대지 않으므로 기존 동작이 그대로 보존된다.
"""
import os
import re
import sys

ROOT = r'E:\도진팩토리\3D스캔및티칭시스템'
FAB_ROOT = ROOT + r'\fab_models'
_SCR_BASE = (r'C:\Users\user\AppData\Local\Temp\claude'
             r'\E-----Claude--20260728-claude-code-2-1-219'
             r'\8defc145-8ae5-48d0-9058-355f3d9ae8ed\scratchpad')

PRODUCTS = {
    # ---------------- 500PRO (검증 완료 — 값 변경 금지) ----------------
    '500pro': dict(
        label='에버다임 E500PRO',
        scr=_SCR_BASE,
        dxf=_SCR_BASE + r'\out\frame.dxf',
        out_subdir='',                     # fab_models 바로 밑 (기존 그대로)
        pn_regex=r'2110DN-\d{4}-\d{3,4}',
        frame_regex=r'2110DN-\d{4}|FRAME|SCALE|\bSIZE\b|^\d\s*/\s*\d$|현대에버다임',
        manifest='manifest.json',
        src_dwg=r'F:\설우\에버다임 프레임\e500 pro\2110DN-0003 500 PRO FRAME 25.11.12.dwg',
        skp_name='500PRO_전체부재',
        obj_prefix='e500pro_',
        # 도번 -> (규격원문, 단면종류, 춤 D, 폭 B, 두께 t)   ※ pass9_channel.py 에서 옮겨온 원문 그대로
        spec={
            '2110DN-0004-001': ('CHANNEL SS275 250*90*T9 1EA', 'C', 250.0, 90.0, 9.0),
            '2110DN-0004-002': ('CHANNEL SS275 250*90*T9 1EA (금창)', 'C', 250.0, 90.0, 9.0),
            '2110DN-0004-003': ('CHANNEL SS275 250*90*T9 1EA (금창)', 'C', 250.0, 90.0, 9.0),
            '2110DN-0004-004': ('CHANNEL SS275 250*90*T9 1EA', 'C', 250.0, 90.0, 9.0),
            '2110DN-0004-005': ('CHANNEL SS275 250*90*T9 1EA (금창)', 'C', 250.0, 90.0, 9.0),
            '2110DN-0004-020': ('CHANNEL SS275 250*90*T9 1EA (금창)', 'C', 250.0, 90.0, 9.0),
            # 2110DN-0010-001: 같은 도번 텍스트가 3곳(부품표 행 1 + 단품도 2).
            # 단면 250*90*T9 근거는 같은 도번의 형제 시트 부품표 행 원문 "CHANNEL / SS275 / 250x90xT9".
            '2110DN-0010-001': ('CHANNEL - SM490 9T 변경 1EA (절곡)', 'C', 250.0, 90.0, 9.0),
            # 2110DN-0009-001: 규격 원문에 단면치수가 없다. 춤 250 / 폭 90 은 이 도번이 소유한
            # 도면 형상에서 실측(옆면도 1323x250, 평면도 1323x90, 단면도 90x250) — 추정 아님.
            '2110DN-0009-001': ('CHANNEL - SM490 9T (변경) 1EA (절곡)', 'C', 250.0, 90.0, 9.0),
        },
        # 도번 -> 옆면도 콜아웃 좌표 강제 지정(같은 도번 텍스트가 여러 곳이라 첫 후보가 표제란일 때)
        spec_at={
            '2110DN-0010-001': (268975.6390266236, 6669.274319083852),
        },
        # BOM 표에서 규격칸이 도번칸 오른쪽 몇 mm 안에 있는가(표 열 배치는 제품마다 다르다).
        # 500PRO: 도번 바로 오른쪽이 규격칸이라 400mm 로 충분했다.
        bom_dx=400.0,
        # 형강 규격 표기를 어디서 얻었는지(manifest note 에 그대로 적힌다 — 출처를 지어내지 않는다).
        spec_src='VLM판독: %s — 단품도 시트를 그림으로 직접 읽어 확정한 표기.',
    ),

    # ---------------- 400Q ----------------
    '400q': dict(
        label='에버다임 400Q',
        scr=_SCR_BASE + r'\400q',
        dxf=_SCR_BASE + r'\out2\2110DB-0004_E400Q 25.10.29.dxf',
        out_subdir='400q',                 # fab_models\400q\ — 500PRO 산출물과 절대 섞이지 않는다
        # 400Q 는 500PRO 와 달리 도번 체계가 한 가지가 아니다. 실제 도면 텍스트를 훑어
        # 확인된 계열만 적는다(추측 계열 없음):
        #   2110DB/2114DB/1620DB-0000-000  현대에버다임 제품도번
        #   D-00000000[A-Z]               사내 표준품 도번 (뒤에 SW/A/B/C/Q 변형)
        #   RB/RC/RD/RJ 000-0000          기종별 부재 도번 (뒤에 SW/A/B/Q, -00 변형)
        # 조립도번(뒤 -000 없는 2110DB-0004 등)은 부재가 아니므로 일부러 제외한다.
        pn_regex=(r'(?:\d{4}DB-\d{4}-\d{3,4}'
                  r'|D-\d{8}(?:-?[A-Z]{1,2})?'
                  r'|R[BCDJ]\d{3}-\d{4}(?:-\d{1,2})?(?:SW|[A-Z])?)'),
        # 표제란/도면틀 글자 — 조립도번(뒤 -000 없음)이 여기 해당한다.
        frame_regex=(r'\d{4}DB-\d{4}(?!-\d)|FRAME|SCALE|\bSIZE\b'
                     r'|^\d\s*/\s*\d$|현대에버다임|에버다임'),
        manifest='manifest_400q.json',
        src_dwg=r'F:\설우\에버다임 프레임\400Q\2110DB-0004_E400Q 25.10.29.dwg',
        skp_name='400Q_전체부재',
        obj_prefix='e400q_',
        # 형강 규격표: 도면의 규격 텍스트(CHANNEL 250*90*9T 등)를 최근접 도번에 이어 확정한 것.
        # 단면종류 'C'(ㄷ형강)만 pass9_channel.py 가 만들 수 있다.
        # ANGLE·RECTANGLE PIPE 는 단면 생성 로직이 없어 여기 넣지 않는다(있으면 틀린 형상이 나온다).
        spec={
            '2110DB-0006-001': ('CHANNEL 250*90*9T', 'C', 250.0, 90.0, 9.0),
            '2110DB-0006-002': ('CHANNEL 250*90*9T', 'C', 250.0, 90.0, 9.0),
            '2110DB-0006-003': ('CHANNEL 250*90*9T', 'C', 250.0, 90.0, 9.0),
            '2110DB-0006-004': ('CHANNEL 250*90*9T', 'C', 250.0, 90.0, 9.0),
            '2110DB-0006-005': ('CHANNEL 250*90*9T', 'C', 250.0, 90.0, 9.0),
            '2110DB-0006-006': ('CHANNEL 250*90*9T', 'C', 250.0, 90.0, 9.0),
            'D-18010103': ('CHANNEL 250*90*9T', 'C', 250.0, 90.0, 9.0),
            'D-18010104': ('CHANNEL 250*90*9T', 'C', 250.0, 90.0, 9.0),
            'D-18010105': ('CHANNEL 250*90*9T', 'C', 250.0, 90.0, 9.0),
            'RC102-0025': ('CHANNEL 250*90*9T', 'C', 250.0, 90.0, 9.0),
        },
        spec_at={},
        # 400Q BOM 표는 도번칸과 규격칸 사이에 열이 더 있다. 실측(도번 기준 같은 행 오른쪽):
        #   dx +272 치수 / +360 품명(PLATE) / +645 재질(SS275) / +781 수량 / +861 규격(PL9)
        # 그래서 500PRO 의 400mm 로는 규격칸에 닿지 못해 186행이 전부 '규격칸 없음' 이 됐다.
        # 임계값을 감으로 올린 게 아니라 이 표의 실제 열 위치(861)를 담도록 잡은 값이다.
        bom_dx=1000.0,
        # 400Q 는 VLM 으로 읽은 게 아니라, 도면 텍스트에 적힌 규격 표기를 같은 시트(주영역)에서
        # 이 도번에 가장 가까운 것으로 이어 확정했다. 출처를 500PRO 문구로 쓰면 거짓이 된다.
        spec_src=('도면 규격 텍스트: %s — 주영역 같은 시트에서 이 도번에 가장 가까운 '
                  '규격 표기를 이어 확정(사본시트 제외).'),
        # 이 y 아래는 같은 시트의 사본/구버전 배치다(같은 도번·같은 규격 묶음이 통째로 반복).
        # 부재 소유권 판정에 섞이면 다른 시트의 덩어리를 붙잡으므로 주영역만 쓴다.
        y_main=-60000.0,
    ),

    # ---------------- D800 (DTH 메인프레임) ----------------
    'd800': dict(
        label='에버다임 D800 DTH 메인프레임',
        scr=_SCR_BASE + r'\d800',
        dxf=_SCR_BASE + r'\d800\out\RJ302-0001_P00_FRAME.dxf',
        out_subdir='d800',                 # fab_models\d800\ — 앞선 두 제품 산출물과 섞이지 않는다
        # D800 도번 체계: 실제 도면 텍스트 전수 집계(scratchpad\d800\tally_P00.txt, 고유 351개)에서
        # 확인된 접두사만 적는다(추측 계열 없음):
        #   RJ302(190) RJ102(119) RJ202(8) RJ304(6) RJ105(6) RJ104(2) RJ204(2)
        #   RJ122 RJ123 RJ329 RB204 RB319 RQ127 RX000 D628
        # RA3xx(RA300/RA303/RA305/RA307/RA311/RA312)는 볼트·너트 등 표준품이라 일부러 제외한다.
        # 400Q 처럼 '조립도번은 마디가 하나 짧다'는 구분이 D800 에는 없다(전부 2마디).
        # 그래서 조립도번은 정규식이 아니라 소유권 게이트(자기 단품도 폐곡선을 갖는가)로 걸러낸다.
        pn_regex=r'(?:R[BJQX]\d{3}-\d{4}|D\d{3}-\d{4})',
        # 표제란/도면틀 글자 — 색인 후 '가장 많이 반복되는 텍스트'를 실측해 확정했다.
        # 도면 1457장 전부에 같은 횟수로 나오는 표제란 항목명이라 부재 뷰 안에는 나올 수 없다:
        #   SCALE/TITLE/SUBJECT/SHEET/PAPER/MODEL 각 1457, SPEC. 1478, CH./AP. 각 1825,
        #   '에버다임의'(저작권 문구) 1432, FRAME 계열 534.
        frame_regex=(r'FRAME|SCALE|\bSIZE\b|^\d\s*/\s*\d$|현대에버다임|에버다임'
                     r'|\bTITLE\b|\bSUBJECT\b|\bSHEET\b|\bPAPER\b|\bMODEL\b'
                     r'|\bSPEC\.|\bCH\.|\bAP\.'),
        manifest='manifest_d800.json',
        src_dwg=_SCR_BASE + r'\d800\in\RJ302-0001_P00_FRAME.dwg',
        skp_name='D800_전체부재',
        obj_prefix='d800_',
        # 형강 규격은 색인 전에는 알 수 없다. pass5/5b/5c 로 확인한 뒤 실제 규격 원문으로 채운다.
        spec={},
        spec_at={},
        # D800 BOM 표의 실제 열 위치(도번칸 기준 같은 행 오른쪽, 색인에서 실측):
        #   dx +326 치수 / +451 품명(PLATE·BOSS) / +749 재질(SM490A·SS275)
        #      / +1093 수량 / +1227 EA / +1348 규격(PL 12·PL19)
        # 규격칸이 +1348 이므로 400Q 의 1000 으로는 닿지 못해 전 행이 '규격칸 없음'이 된다.
        # 감으로 올린 값이 아니라 이 표의 실제 규격칸 위치(1348)를 담도록 잡은 값이다.
        # +326/+1093/+1227 칸('000','1','EA')은 두께 표기로 읽히지 않아 행당 규격셀은 1개다.
        bom_dx=1400.0,
        spec_src=('도면 규격 텍스트: %s — 주영역 같은 시트에서 이 도번에 가장 가까운 '
                  '규격 표기를 이어 확정.'),
        # ---- 치수 게이트: D800 에서만 켠다(기본 False — 400Q·500PRO 는 코드 경로 불변) ----
        # 왜 필요한가: D800 도면은 부품표·표제란 칸이 '빈 사각형'으로 대량 그려져 있다.
        # pass6 후보 20,297개 중 글자 0개인 빈 사각형이 2,579개(180x200 이 773개, 130x30 이
        # 230개 반복). 글자가 없으니 도면틀 게이트를 그냥 통과해 부재로 둔갑한다 —
        # 표본 7개 중 5개가 실제로 표 칸이었다.
        # 판정 근거: 단품도의 부재 뷰 곁에는 그 부재의 치수 숫자가 적혀 있다(가로 585 인
        # 부재 곁에는 '585' 가 실제로 있다). 표 칸은 칸 크기를 적어두지 않는다.
        # 크기 임계값도 반복횟수 임계값도 아니라, 도면이 그렇게 그려져 있다는 사실이다.
        # 검증: D800 오인식 5건 전부 탈락, 정상 2건 전부 통과.
        # 왜 제품별인가: 400Q 는 치수가 DIMENSION 블록 안에 있어 색인 텍스트가 2,630개뿐이다
        # (D800 은 253,981개, 선 개수는 둘 다 100만 대로 비슷하다). 400Q 는 치수가 없는 게
        # 아니라 색인에서 보이지 않을 뿐이라, 이 게이트를 적용하면 검증완료 121개 중 120개가
        # 죽는다(실측). 그래서 치수가 관측 가능한 제품에서만 켠다.
        dim_gate=True,
        # 타일 렌더에서 도번의 라벨 자리를 전부 쓴다(+참조칸 제외).
        # 왜: D800 은 같은 도번이 여러 시트에 나오고(RJ102-0020 은 60자리) 첫 자리가
        # REF. NO. 참조칸이면 진짜 단품도가 한 번도 렌더되지 않는다(실측).
        # 기본 꺼짐 — 500PRO/400Q 는 영향 없다.
        tile_all_labels=True,
        # ---- 측면도 후순위: 같은 도번의 후보 중 '폭이 곧 두께'인 좁은 띠는 맨 뒤로 미룬다 ----
        # 왜 필요한가: 단품도에는 같은 부재가 정면도(넓은 판)와 측면도(두께만큼 얇은 띠)로
        # 두 번 그려진다. 측면도 띠가 콜아웃에 더 가까울 때가 있어(RJ302-0146: 띠 351mm vs
        # 정면도 478mm) 거리순으로만 고르면 두께 32T 부재가 32x230 짜리 막대로 나온다.
        # 실측 근거: 격리된 4건은 전부 size_mm 의 짧은 변이 두께와 정확히 같다.
        #   RJ102-0591 40x263x40 / RJ102-0595 32x263x32
        #   RJ102-0356 25x298x25 / RJ302-0146 32x230x32
        # 두께 t 인 판의 측면도 폭이 t 인 것은 임계값이 아니라 투상법의 정의다.
        # 버리지 않고 순서만 뒤로 민다 — 띠 말고 통과하는 후보가 없으면 예전과 똑같이 띠를 쓴다.
        # 기본 꺼짐 — 500PRO/400Q 는 이 코드를 아예 타지 않는다.
        side_view_demote=True,
        # ---- 검증2(외곽 안 글자) 를 셀 때 '치수 표기'는 글자로 세지 않는다 ----
        # 왜 필요한가: build_one 의 검증2 는 '외곽 안 글자가 3개 이상이면 도면틀·표제란'
        # 으로 판정한다. 그런데 진짜 단품도 정면도 안에는 그 부재의 치수가 당연히 적혀 있다.
        # 실측 탈락 사유: RJ102-0595 439x263 -> "R, 60, 2-" / RJ102-0356 584x298 -> "110, R, 30".
        # 전부 그 부재 자신의 치수·반지름·모따기 표기다. 즉 검증2 가 '부재라는 증거'를
        # '도면틀 증거'로 잘못 세고 있었다.
        # 무엇을 바꾸나: 게이트 자체(표제란 글자면 즉시 탈락, 그 외 글자 3개 이상이면 탈락)는
        # 그대로 두고 셈에서 치수 표기 패턴만 뺀다. 표제란 글자(TITLE/SPEC./도번 등)는 여전히
        # 그대로 잡히고, 글자 없는 빈 표 칸은 애초에 치수 게이트(dim_gate)가 막는다.
        # 임계값을 늦추는 게 아니라 '무엇을 글자로 셀 것인가'를 바로잡는 것이다.
        # 기본 꺼짐 — 500PRO/400Q 는 이 필터를 타지 않는다.
        dim_text_not_frame=True,
        # ---- 2단 탐색용 소형 부재 하한: D800 만 낮춘다 ----
        # 왜 필요한가: 확정 판재 6건이 후보에 아예 못 들어왔다. '도면에 단품도가 없어서'가
        # 아니라 '기본 하한(긴변 80 초과)보다 작은 소형 부재라서'였다. 진단에서 6건 전부
        # 라벨 곁에 실물 덩어리가 확인됐다(실측):
        #   RJ102-0027 36x16(라벨 366mm) / RJ102-0733 30x26(125mm) / RJ102-0150 70x40(0mm)
        #   RJ202-0011 32x70(190mm)     / RJ102-0026 28x14(84mm)  / RJ202-0012 101x93(143mm)
        # 가장 작은 실측 부재가 28x14 라 긴변 24 / 짧은변 12 로 잡는다(실측 최소값 바로 아래).
        #
        # 중요: 이 값은 '1단 탐색의 하한'이 아니다. 하한을 그냥 낮추는 방식은 이미 실패했다 —
        # 작은 덩어리가 소유권을 먼저 차지해 검증완료 94개 중 30개가 사라지고 2개가 27x28 로
        # 붕괴했다(실측). 그래서 이 하한에만 걸리는 덩어리는 comps_small 에 따로 담아 두고,
        # 1단에서 아무것도 못 찾은 도번만 2단에서 추가로 본다(pass6_multi.py 참조).
        # 기본값은 큰 하한과 같은 80/20 이라 500PRO·400Q 는 comps_small 이 항상 비어 있다.
        comp_long_min=24.0,
        comp_short_min=12.0,
        # y_main 은 일부러 두지 않는다(=주영역 구분 없음).
        # 400Q 는 y<-60000 에 같은 도번 묶음이 통째로 다시 그려진 사본시트가 있어 필요했다.
        # D800 의 도번 출현 y 분포는 -55682 ~ 354102 사이가 끊김 없이 연속이고
        # (20000 단위 버킷 전 구간에 고르게 분포) 통째 반복되는 사본 구역이 확인되지 않는다.
        # 근거 없이 경계를 지어내면 멀쩡한 부재를 잘라내므로 경계를 두지 않는다.
    ),

    # ---------------- T600 메인프레임 (RG102-0002) ----------------
    't600_frame': dict(
        label='에버다임 T600 메인프레임',
        scr=_SCR_BASE + r'\t600',
        dxf=_SCR_BASE + r'\t600\out_fB\FRAME_2409_share.dxf',
        out_subdir='t600_frame',           # fab_models\t600_frame\ — 앞선 제품과 섞이지 않는다
        # T600 프레임 도번 체계: 채택 DXF 도번 전수 집계(scratchpad\t600\tally_fB.txt,
        # 고유 377개)에서 확인된 접두사만 적는다(추측 계열 없음):
        #   RJ302(174) RJ102(121) RG102(42) RJ202(10) RJ105(6) RJ304(2) RJ104(2) RJ122(1)
        # RA3xx(RA300/RA303/RA305/RA307/RA311/RA312)는 볼트·너트 등 표준품이라 일부러 제외한다.
        # 근거는 출현수다 — RA305-8109 가 1234회, RA300-1114 가 519회, RA307-6128 이 421회로
        # 부재 도번(대개 한 자리수~십수 회)과 자릿수가 다르다. 도면 전체에 흩뿌려지는 체결류다.
        # 조립도번(RG102-0002 등)은 D800 과 마찬가지로 마디 길이로 구분되지 않으므로
        # 정규식이 아니라 소유권 게이트(자기 단품도 폐곡선을 갖는가)로 걸러낸다.
        pn_regex=r'R[JG]\d{3}-\d{4}',
        # 표제란/도면틀 글자 — D800 과 같은 에버다임 표제란 양식이라 같은 항목명을 옮겼다.
        # [미확인] T600 색인 후 최다 반복 텍스트를 실측해 재확인할 것.
        frame_regex=(r'FRAME|SCALE|\bSIZE\b|^\d\s*/\s*\d$|현대에버다임|에버다임'
                     r'|\bTITLE\b|\bSUBJECT\b|\bSHEET\b|\bPAPER\b|\bMODEL\b'
                     r'|\bSPEC\.|\bCH\.|\bAP\.'),
        manifest='manifest_t600_frame.json',
        # 원본 F:\ 무접촉 — 스크래치패드로 복사해 변환했다. 여기엔 '어느 원본에서 왔는가'를 남긴다.
        # [판본 판정] 지시받은 F:\설우\에버다임 프레임\신성용24.09...(46.4MB)는 완전판이 아니다.
        # 도번 전수 비교에서 이 파일(공유문서 85.6MB)이 진상위집합이었다 — RJ102-0711 이
        # 신성용본에만 없다(반대 방향 차이 없음). 엔티티 실수도 LINE 752,162 vs 750,669 /
        # TEXT 152,853 vs 152,246 으로 이쪽이 많다. '신성용'은 업체명 붙은 발췌본이다.
        src_dwg=(r'F:\설우\공유문서\2023~ 에버다임'
                 r'\24.08.21 T600 FRAME, MAST 제작 요청'
                 r'\24.09 RG102-0002 000 T600 MAIN FRAME 발주도 모음 @@@@@@@@@@.dwg'),
        skp_name='T600프레임_전체부재',
        obj_prefix='t600f_',
        # 형강 규격은 색인 전에는 알 수 없다. pass5/5b/5c 로 확인한 뒤 실제 규격 원문으로 채운다.
        spec={},
        spec_at={},
        # T600 프레임 BOM 표의 실제 열 위치(도번칸 기준 같은 행 오른쪽, 색인에서 실측):
        #   dx +326 치수 / +451 품명(PLATE·BOSS) / +749 재질(SM490A·SS275)
        #      / +1093 수량 / +1227 EA / +1348 규격(PL 6·PL 12·PL19)
        # 실측 원문 예: RJ102-0822 @x=802927 -> +1348:'PL 6'
        #               RJ302-0322 @x=802927 -> +1348:'PL 12'
        #               RJ302-0194 @x=802927 -> +1348:'PL19'
        # 값이 D800 과 같은 1400 이 된 것은 D800 값을 옮긴 게 아니라, T600 발주도가
        # D800 과 같은 에버다임 BOM 양식을 써서 규격칸 dx(+1348)가 실제로 일치하기 때문이다.
        # (위 3줄은 이 DXF 에서 직접 뽑은 실측 결과다 — scratchpad\t600\measure_bom.py)
        bom_dx=1400.0,
        spec_src=('도면 규격 텍스트: %s — 주영역 같은 시트에서 이 도번에 가장 가까운 '
                  '규격 표기를 이어 확정.'),
        # ---- 측면도 후순위: 실제로 관측돼서 켠다(감으로 옮긴 게 아니다) ----
        # 1차 전량 추출(플래그 전부 끔) 결과 성공 135개 중 22개가 '두께 == 평면 한 변'
        # 이었다. 두께 t 인 판의 측면도 폭이 t 인 것은 투상법의 정의이므로 이건 오인식이다.
        # 실측(1차 산출물 size_mm):
        #   RJ302-0147 25x1281x25 / RJ302-0148 25x695x25 / RJ302-0154 25x600x25
        #   RJ302-0215 45x400x45  / RJ302-0150 45x370x45 / RJ302-0214 45x290x45
        #   RJ302-0220 32x280x32  / RJ102-0078 25x751x25 / RJ302-0310 320x50x50 ...(총 22건)
        # D800 은 같은 증상이 4건이었는데 T600 프레임은 22건이라 영향이 더 크다.
        # 후보를 버리는 게 아니라 순서만 뒤로 미루므로, 띠 말고 통과할 후보가 없으면
        # 예전과 똑같이 띠를 쓴다(부재를 잃지 않는다).
        side_view_demote=True,
        # ---- 검증2 의 '외곽 안 글자' 셈에서 치수 표기를 뺀다(D800 과 같은 교정) ----
        # 왜: side_view_demote 만으로 남은 측면도 잔존 6건은 '정면도 후보가 검증2 에서
        # 탈락해서' 띠밖에 남지 않은 건들이다. 검증2 는 외곽 안 글자 3개 이상이면
        # 도면틀로 보는데, 진짜 정면도 안에는 그 부재의 치수가 당연히 적혀 있다.
        # D800 이 같은 도번들에서 이미 확인한 증상이고(RJ102-0356/0591/0595 등 동일 시트),
        # 교정 후 정면도 치수도 D800 기록과 일치한다(0591 439x263 / 0595 439x263 / 0356 584x298).
        # 게이트 자체는 그대로 두고 '무엇을 글자로 셀 것인가'만 바로잡는다.
        dim_text_not_frame=True,
        # ---- [알려진 한계] 표 칸(도면 비품) 오인식 18건은 게이트로 못 막아 산출물에서 격리 ----
        # 증상: 성공 135개 중 18개가 단 2가지 크기를 공유했다 —
        #   132.63 x 34.64 (12건) / 88.42 x 23.10 (6건). 두 크기의 비가 정확히 1.5 로
        #   같은 도형을 축척만 바꿔 그린 것이다. 이 도형은 도면 전체에 174개(139+35)가
        #   똑같이 반복되고 주변 60mm 안에 글자가 0개다 — 부재가 아니라 도면 비품이다.
        #   (육안 발각 경로: 비교T600_FRAME_RJ302-0398.png — 복잡도 118 이어야 할 부재가
        #    132.6x34.6 민판으로 나왔고 도면 쪽은 중첩 사각형이었다.)
        # 게이트 두 가지를 실측해 보고 둘 다 기각했다:
        #   (1) dim_gate: 켜면 135 -> 99. 표 칸 13건은 죽지만 정상부재 23건이 같이 죽고
        #       (RJ302-0037 1390x570 / RJ302-0052 2500x205 / RJ302-0043 239x570 등)
        #       표 칸 5건은 켜도 살아남는다. T600 은 DIMENSION 808·TEXT 152,853 으로
        #       D800(1305·253,981)보다 치수 관측률이 낮아 과잉살상이 난다.
        #   (2) 글자거리 게이트: 표 칸 34~51mm vs 정상 0~185mm 로 분포가 겹친다(실측).
        #       겹친 분포에 선을 그으면 그게 곧 감으로 맞추는 임계값이라 만들지 않았다.
        # 그래서 산출물 단계에서 18건을 격리했다(삭제 아님):
        #   scratchpad\t600\_격리_표칸18건\ — manifest 117건은 이 18건을 뺀 수다.
        # ※ 재실행하면 18건이 다시 만들어진다. 그래서 손으로 빼지 않고 스크립트로 만들었다 —
        #   pass6 직후 반드시 quarantine_t600_frame.py 를 돌려야 117건이 된다.
        #   실행 순서: pass6_multi.py -> quarantine_t600_frame.py -> add_manifest.py
        #   (빼먹으면 manifest 가 135건이 되고 SKP 에 표 칸 18개가 섞여 들어간다.)
        # tile_all_labels·dim_text_not_frame·comp_*_min 은 끈 채 둔다 — 필요하다는
        # 실측 근거가 아직 없다. 시트 테두리(√2 비율 393x281·196x140·786x561·387x285)는
        # 기존 검증2 가 이미 전부 탈락시킨다("외곽 안 글자 54개(표제란 증거 10개)").
        # y_main 은 두지 않는다(=주영역 구분 없음). 도번 출현 y 는 -55682 ~ 295856 이
        # 20000 단위 버킷 전 구간에 끊김 없이 분포하고(최소 17개 ~ 최대 339개) 통째로
        # 반복되는 사본 구역이 확인되지 않는다(실측). 400Q 처럼 경계를 둘 근거가 없다.
    ),

    # ---------------- T600 ARC 마스트 (RG111-0001) ----------------
    't600_mast': dict(
        label='에버다임 T600 ARC 마스트',
        scr=_SCR_BASE + r'\t600m',
        dxf=_SCR_BASE + r'\t600\out_mC\MAST_2411_mastdir.dxf',
        out_subdir='t600_mast',            # fab_models\t600_mast\
        # [판본 판정] 후보가 3개였다(지시서엔 2개). 도번 전수 비교:
        #   24.09 공유문서(187MB) 고유736/출현44,091 = 이 파일과 완전 동일
        #   24.11 공유문서(157MB) 고유735/출현44,024 = RX000-6939 빠진 진부분집합 → 탈락
        #   24.11 마스트폴더(109MB) 고유736/출현44,091 ← 채택
        # 24.09 와 도번이 완전 동일한데 판번호가 최신이고, 엔티티도 LINE +579·ARC +65 로
        # 이쪽이 많다(나머지 동일). 가장 작은 파일이 상위집합이었다 — 크기는 근거가 아니다.
        #
        # 도번 계열: 이 파일은 '발주도 모음'이라 마스트 고유 계열(RG1xx) 말고도 다른 기종
        # 계열이 대량으로 섞여 있다(RC830 304 / RB614 34 / RC317 31 / RA111 25 ...).
        # 마스트 본체 계열은 RG111(59) RG117(34) RG114(22) RG116(15) RG112(13) RG113(4)
        # 이고, 좌표상 RG111 은 x 양수 영역(44568~156631)에만 있는데 RC830 은 x 음수
        # 영역까지 퍼져 있다(-130706~164168).
        # [확정 — 육안 확인 완료] 타 계열도 이 파일에서 '진짜 단품도'를 갖는다. 전부 넣는다.
        # 처음에는 'LV4. RC830-0021 = 2EA' 꼴이 많아 RC830·RA111 을 BOM 참조행으로 보고
        # 제외하려 했다. 그 가설은 실측 두 번으로 기각됐다:
        #   (1) LV 행 비율이 계열을 못 가른다 — RG111 29% / RC830 27% (마스트 고유 계열도
        #       똑같이 LV 행에 나온다. 즉 LV 행은 계열 구분 근거가 아니다).
        #   (2) zone 렌더 육안(scratchpad\t600m\zone_RC830-0021.png, zone_RA111-0368.png):
        #       둘 다 도면틀(A~H/1~8 격자) + 부재 정면도 + ISOMETRIC + 표제란을 갖춘
        #       완전한 단품도였다. RA111-0368 은 PLATE / SS400 / 215 / 4-M12x1.75 THR,
        #       RC830-0021 은 PLATE / SM490 로 표제란에 품명·재질이 적혀 있다.
        #       D800 의 RA3xx(볼트·너트 표준품)와는 성격이 다르다 — 여기 RA 는 판재다.
        # 관측된 계열(고유수): RC830 304 / RG111 59 / RG117 34 / RB614 34 / RC317 31
        #   / RB530 27 / RA111 25 / RG114 22 / RC850 21 / RJ317 19 / RB250 18 / RA400 15
        #   / RG116 15 / RG112 13 / RD730 10 / RJ111 8 / RB211 8 / RB511 7 / RB212 7
        #   / RC812 7 / RJ112 6 / RB417 5 / RG113 4 ... → 접두 문자는 A/B/C/D/G/J 뿐이다.
        # 프레임처럼 출현수가 자릿수 다르게 튀는 체결류 계열은 마스트엔 없다(최다 RC830 1648,
        # 프레임의 RA305-8109 1234회 같은 '한 도번 집중'과 달리 304개 도번에 퍼져 있다).
        # 단품도가 없는 도번은 정규식이 아니라 소유권 게이트가 알아서 SKIP 한다.
        pn_regex=r'R[ABCDGJ]\d{3}-\d{4}',
        frame_regex=(r'FRAME|SCALE|\bSIZE\b|^\d\s*/\s*\d$|현대에버다임|에버다임'
                     r'|\bTITLE\b|\bSUBJECT\b|\bSHEET\b|\bPAPER\b|\bMODEL\b'
                     r'|\bSPEC\.|\bCH\.|\bAP\.'),
        manifest='manifest_t600_mast.json',
        src_dwg=(r'F:\설우\에버다임 마스트'
                 r'\24.11 RG111-0001 A00 T600 ARC MAST 발주도 모음 @@@@@@@@@@.dwg'),
        skp_name='T600마스트_전체부재',
        obj_prefix='t600m_',
        spec={},
        spec_at={},
        # 마스트 BOM 표는 '표 양식이 두 가지'다(실측). 프레임(+1348 한 가지)과 다르다:
        #   RC830 구역(좁은 표): +66 REV / +96 품명(BLOCK·RING·PLATE) / +244 재질(SS400)
        #                        / +313 수량 / +336 규격(PL20·PL4)
        #   RG111 구역(넓은 표): +222 REV / +320 품명(PLATE·BRACKET) / +815 재질(SUS304·SM355A)
        #                        / +994 수량 / +1070 규격(PL3·PL20·D9x280)
        # 넓은 쪽 규격칸이 +1070 이므로 그것까지 닿도록 1100 으로 잡는다.
        # 좁은 표 행에서도 범위 안 다른 칸('A00','BLOCK','SS400','1')은 두께 표기로 읽히지
        # 않아 행당 규격셀이 1개로 유지된다(pass5b 는 2개 이상이면 확정불가로 뺀다).
        bom_dx=1100.0,
        spec_src=('도면 규격 텍스트: %s — 주영역 같은 시트에서 이 도번에 가장 가까운 '
                  '규격 표기를 이어 확정.'),
        # ---- 측면도 후순위: 마스트도 1차 추출 실측으로 확인하고 켰다 ----
        # 플래그를 전부 끈 1차 추출 결과 성공 139개 중 20개가 '두께 == 평면 한 변'이었다.
        # 실측 예: RG111-0011 25x430x25 / RG111-0043 330x20x20 / RC830-0703 455.3x27x27
        #          RJ314-0011 320x30x30 / RG117-0018 25x110x25
        # 프레임에서 켰으니 마스트도 켠 게 아니라, 마스트 도면에서 같은 증상을 직접 세어 켰다.
        side_view_demote=True,
        # 나머지 플래그(dim_gate·tile_all_labels·dim_text_not_frame·comp_*_min)는 끈 채 둔다.
        # 프레임을 괴롭힌 '표 칸 반복'도 마스트에는 없다 — 같은 크기를 3건 넘게 공유하는
        # 산출물이 없다(최다 3건, 그것도 210.44x28.26·120x32 로 부재다운 크기다).
        # 프레임의 132.63x34.64 가 12건이었던 것과 대조된다.
    ),

    # ---------------- 45B 메인프레임 (RD202-0008_P00) ----------------
    # [2026-09-14 추가 — 순수 추가 항목. 위 다섯 제품의 값은 한 글자도 건드리지 않았다.]
    #
    # 입력이 앞선 제품들과 근본적으로 다르다(이것만 알면 나머지 설정이 다 설명된다):
    #   500PRO/400Q/D800/T600 = '발주도 모음' 1개 파일(수십~수백 MB)에 부재 수백 개가 들어있다.
    #   45B  = 도번 1개당 DXF 1개씩, 총 177개 파일로 쪼개져 있다
    #          (F:\_DWG_TO_DXF\... 에 이미 변환되어 있는 것을 읽기만 한다. 변환 파이프라인은 건드리지 않는다).
    #   게다가 부재 윤곽이 modelspace 가 아니라 INSERT 블록 안에 들어있다(실측:
    #   modelspace LINE 개수가 파일마다 138~140 으로 일정 = 그게 도면틀이다. 부재선은 블록 안).
    # 그래서 pass1/pass2(=DXF 를 index.json/index2.json 으로 바꾸는 입력 어댑터)만
    # 45B 전용 어댑터로 대체하고, 그 뒤 pass6_multi.py / add_manifest.py 는 손대지 않고 그대로 쓴다.
    # 어댑터는 177개 파일을 격자로 흩어 배치해 '발주도 모음 한 장'과 같은 모양의 색인을 만든다.
    '45b': dict(
        label='에버다임 45B 메인프레임 RD202-0008_P00',
        scr=(r'C:\Users\user\AppData\Local\Temp\claude'
             r'\E-----Claude--20260728-claude-code-2-1-219'
             r'\d7515fca-4f61-411d-a663-620845a60868\scratchpad\45b'),
        # 단일 DXF 가 존재하지 않는 제품이다. 어댑터가 폴더를 통째로 읽으므로 이 값은 쓰이지 않는다
        # (pass1/pass2 를 돌리면 안 된다 — 어댑터가 그 둘의 자리를 대신한다).
        dxf='',
        out_subdir='_45B_제작중',          # fab_models\_45B_제작중\ — 500PRO 산출물과 절대 섞이지 않는다
        # 도번 체계: EBOM(0_EBOM_RD202-0008_P00.xlsm)과 DXF 파일명 177개에서 확인된 계열만 적는다.
        #   RD202/RD102/RD702  RB202/RB102/RB319/RB502/RB602/RB702  RC102/RC302/RC802
        #   D-00000000         사내 표준품 도번
        # S0000000(볼트 등 표준품)은 제작 대상이 아니라 일부러 뺀다.
        pn_regex=r'(?:R[BCD]\d{3}-\d{4}|D-\d{8})',
        # 표제란 글자 — 45B 도면틀에서 실측한 항목명(파일 177개 전부 같은 틀을 쓴다).
        frame_regex=(r'FRAME|SCALE|\bSIZE\b|^\d\s*/\s*\d$|현대에버다임|에버다임'
                     r'|\bTITLE\b|\bDWG\.? ?NO\b|\bREV\b|\bMODEL\b|\bTOL\.|\bW\.T\.'
                     r'|\bD\.S\.|\bDR\.|\bCH\.|\bAP\.|\bMATERIAL\b|WEIGHT'),
        manifest='manifest_45b.json',
        src_dwg=(r'E:\06_고객사데이터\설우기계\공유문서\2019에버다임\5_ECD45B_RD202-0001'
                 r'\23.08 ECD45B T450G MAIN FRAME P개정 수정'
                 r'\20230814_RD202-0008_P00 MAINFRAME WELD ASSY'),
        skp_name='45B_전체부재',
        obj_prefix='e45b_',
        # 형강 단면은 EBOM 에 규격이 없다(속성칸이 PL6/PL12 처럼 판재 두께뿐).
        # 근거 없는 단면을 넣으면 틀린 형상이 나오므로 비워 둔다 — pass9 는 돌리지 않는다.
        spec={},
        spec_at={},
        # BOM 표 열 탐색은 45B 에서 쓰이지 않는다(두께 근거가 도면 표가 아니라 EBOM 파일이다).
        bom_dx=400.0,
        spec_src='%s',
    ),
}

DEFAULT = '500pro'

# ---------------- CEO 확정 두께(도면 취소선 정정) ----------------
# 왜 필요한가: 두께는 pass5(콜아웃)/pass5b(BOM)/pass5c(VLM)가 도면 글자에서 뽑는다.
# 그런데 도면에 정정(취소선)이 있는 부재는 글자만 봐서는 어느 쪽이 최종인지 알 수 없다.
# 그런 건은 CEO 가 원도면을 보고 확정하며, 그 값을 여기 남긴다. 여기 적지 않고 산출물만
# 고치면 다음 pass6 실행에서 조용히 옛 값으로 되돌아간다(회귀).
# PRODUCTS 안의 검증완료 값은 건드리지 않고 바깥에 따로 둔다.
#   도번 -> (두께mm, 근거 문구)
THK_CEO_ALL = {
    '500pro': {
        # BOM 규격칸 원문이 'PL25-PL30T' 라 pass5b 는 '두께 2개 — 확정 불가'로 건너뛰었고,
        # 콜아웃 글자는 취소선 쪽인 'SM490 25T' 를 그대로 읽었다.
        # CEO 가 도면을 재확인: 취소선 그어진 쪽이 25T, 최종은 30T.
        '2110DN-0019-002': (30.0, 'CEO 확정 30T, 취소선=25T (BOM 규격 원문 PL25-PL30T)'),
    },
    '400q': {},
    't600_frame': {
        # D800 과 같은 도번·같은 시트다. CEO 가 원도면 SPEC 칸을 재판독해 45T 로 확정했고,
        # 그 확정은 '부재 자체의 속성'이므로 제품키가 달라도 같은 값이 적용된다(CEO 지시).
        # T600 프레임 1차 추출값은 40T 였다(도면 띠 40x263 — D800 이 기록한 신호와 동일).
        'RJ102-0591': (45.0, 'CEO 확정 45T, D800과 동일 시트 — 이전 40T 근거는 표제란 잡글자'),
    },
    't600_mast': {
        # 콜아웃 15T vs BOM 16T 충돌 건. CEO 지시로 BOM 값(16T) 채택.
        # 경위를 정확히 남긴다: 콜아웃 15T 는 '잡글자'가 아니라 표제란 SPEC. 칸의 'PL15' 를
        # 읽은 값이다. 즉 같은 시트 안에 SPEC.=PL15 와 BOM=PL16 이 함께 있는 도면 내부
        # 불일치이며, 발주 기준인 BOM 을 따른다.
        'RG114-0021': (16.0, 'CEO 확정 16T — BOM PL16 채택. 표제란 SPEC.칸은 PL15 (도면 내부 불일치)'),
        # RC830-0828 / RC830-0829 는 일부러 넣지 않았다. 두 판독값이 모두 두께가 아니다:
        #   콜아웃 12T = 풍선번호 나열('9/10/6/11/12/5/3/8/4/7/2/1')을 두께로 읽은 것
        #   BOM 6T    = 재질칸 'A6013-T6'(알루미늄 6013, T6 템퍼)의 'T6' 를 6mm 로 읽은 것
        # 도면 글자에 실제 두께 근거가 없으므로 값을 지어내지 않고 산출물에서 격리했다.
        # CEO 가 원도면으로 두께를 확정해 주면 여기 등록한다. [CEO확인]
    },
    'd800': {
        # 같은 도번에 콜아웃이 여러 장(시트) 있고 SPEC 이 PL40 과 PL45 로 갈렸다.
        # (pass6 후보 목록에도 40x263 띠와 45x263 띠가 각각 실재한다 — 도면에 둘 다 그려져 있다.)
        # CEO 가 원도면 SPEC 칸을 재판독해 PL45 로 확정했다. 이전 40T 의 근거였던 글자는
        # 표제란 잡글자였음이 확인됐다.
        'RJ102-0591': (45.0, 'CEO 확정 45T — SPEC PL45 재판독. 이전 40T 근거는 표제란 잡글자'),
    },
}


def _pick_name():
    """--product / 환경변수 / 기본값 순으로 제품 이름을 정하고, argv 에서 토큰을 제거한다."""
    name = None
    argv = sys.argv
    i = 1
    while i < len(argv):
        a = argv[i]
        if a == '--product' and i + 1 < len(argv):
            name = argv[i + 1]
            del argv[i:i + 2]
            continue
        if a.startswith('--product='):
            name = a.split('=', 1)[1]
            del argv[i:i + 1]
            continue
        i += 1
    if not name:
        name = os.environ.get('DWG2OBJ_PRODUCT') or DEFAULT
    name = name.strip().lower()
    if name not in PRODUCTS:
        raise SystemExit('알 수 없는 제품: %r (가능: %s)' % (name, ', '.join(PRODUCTS)))
    return name


NAME = _pick_name()


def get(name=None):
    """현재(또는 지정한) 제품의 값 묶음을 돌려준다."""
    return PRODUCTS[(name or NAME).strip().lower()]


def obj_tail(pn, name=None):
    """도번 -> OBJ 파일명 꼬리표.

    500PRO 는 도번이 '2110DN-0004-001' 한 가지 꼴이라 조립번호를 떼고 '0004_001' 로 썼다.
    400Q 는 'D-18010103' · 'RB102-0096' 처럼 마디가 2개인 도번이 섞여 있어 같은 규칙을 쓰면
    split('-')[2] 에서 IndexError 로 죽는다. 그래서 제품별로 규칙을 나눈다.
    (500PRO 규칙·결과 파일명은 한 글자도 바뀌지 않는다 — 기존 산출물과 계속 맞물린다.)
    """
    p = (name or NAME).strip().lower()
    parts = pn.split('-')
    if p == '500pro':
        return parts[1] + '_' + parts[2]
    return pn.replace('-', '_')


_P = get()

# ---- 편의를 위한 모듈 레벨 노출 (각 pass 가 이 이름들을 그대로 쓴다) ----
LABEL = _P['label']
SCR = _P['scr']
DXF = _P['dxf']
FAB = FAB_ROOT if not _P['out_subdir'] else (FAB_ROOT + '\\' + _P['out_subdir'])
# manifest 안에 적히는 상대 경로 (예: 'fab_models/e500pro_0004_001.obj')
FAB_REL = 'fab_models' if not _P['out_subdir'] else ('fab_models/' + _P['out_subdir'])
# manifest 는 제품별 파일명으로 갈리고, 하위폴더를 쓰는 제품은 그 폴더 안에 둔다
# (400Q 요구: fab_models\400q\manifest_400q.json — 500PRO manifest 와 폴더까지 분리)
MANIFEST = FAB + '\\' + _P['manifest']
Y_MAIN = _P.get('y_main')                        # 주영역 경계(없으면 전체가 주영역)
BOM_DX = _P.get('bom_dx', 400.0)                 # BOM 규격칸 탐색 폭(표 열 배치가 제품마다 다름)
# 치수 게이트(부재 곁에 자기 치수 숫자가 적혀 있는가). 기본 꺼짐 — 켠 제품만 동작이 바뀐다.
DIM_GATE = bool(_P.get('dim_gate', False))
TILE_ALL_LABELS = bool(_P.get('tile_all_labels', False))
# 측면도(폭==두께인 좁은 띠) 후순위. 기본 꺼짐 — 켠 제품만 후보 순서가 바뀐다(후보를 버리지는 않는다).
SIDE_VIEW_DEMOTE = bool(_P.get('side_view_demote', False))
# 검증2 의 '외곽 안 글자' 셈에서 치수 표기를 빼는가. 기본 꺼짐 — 켠 제품만 셈이 달라진다.
DIM_TEXT_NOT_FRAME = bool(_P.get('dim_text_not_frame', False))
# 2단 탐색용 소형 덩어리 하한. 기본값은 1단 하한(80/20)과 같아서, 켜지 않은 제품은
# 소형 덩어리 목록이 항상 비고 2단 탐색 자체가 일어나지 않는다(코드 경로 불변).
COMP_LONG_MIN = float(_P.get('comp_long_min', 80.0))
COMP_SHORT_MIN = float(_P.get('comp_short_min', 20.0))
SPEC_SRC = _P.get('spec_src', '%s')              # 형강 규격 표기의 출처 문구(manifest note 용)
SRC_DWG = _P['src_dwg']
SKP_NAME = _P['skp_name']
OBJ_PREFIX = _P['obj_prefix']
PN_REGEX = _P['pn_regex']
FRAME_REGEX = _P['frame_regex']
PN = re.compile(PN_REGEX)
FRAME_PAT = re.compile(FRAME_REGEX)
SPEC = _P['spec']
SPEC_AT = _P['spec_at']
THK_CEO = THK_CEO_ALL.get(NAME, {})          # 도번 -> (CEO 확정 두께, 근거 문구)


if __name__ == '__main__':
    for k in ('NAME', 'LABEL', 'SCR', 'DXF', 'FAB', 'FAB_REL', 'MANIFEST',
              'SRC_DWG', 'SKP_NAME', 'OBJ_PREFIX', 'PN_REGEX', 'FRAME_REGEX'):
        print('%-11s = %r' % (k, globals()[k]))
    print('%-11s = %d개' % ('SPEC', len(SPEC)))
    print('%-11s = %d개' % ('SPEC_AT', len(SPEC_AT)))
