# -*- coding: utf-8 -*- """ build_02설계_xlsx.py — PtoP Welder(가접용접기) 02.설계 산출물 재생산 ──────────────────────────────────────────────────────────────────── LG PRI 표준 양식(ref 파일 불필요 · 완전 독립 실행). 스타일은 xlsxfill.py 에 하드코딩된 상수를 사용. 전제조건: - Python 3.9+ - openpyxl >= 3.1 (pip install openpyxl) 실행 (저장소 루트에서 한 줄. PYTHONUTF8은 반드시 같은 줄 접두 — 별도 줄이면 적용되지 않는다): PYTHONUTF8=1 py .claude/skills/ubisam-deliverables/references/build_02설계_xlsx.py 출력: docs/[Ubisam산출물]/02.설계/IO_DB설계/PLC_PC_인터페이스_신호정의서.xlsx docs/[Ubisam산출물]/02.설계/IO_DB설계/모션축_IO맵_정의서.xlsx docs/[Ubisam산출물]/02.설계/UIUX_기능설계/기능명세서.xlsx docs/[Ubisam산출물]/02.설계/시퀀스_인터록/TactTime_산정서.xlsx """ import sys, os, datetime sys.path.insert(0, os.path.dirname(__file__)) from xlsxfill import ( make_workbook, set_col_widths, set_row_height, write_banner, write_header_row, write_data_rows, group_merge, BANNER_FONT, BANNER_FILL, BANNER_BORDER, BANNER_ALIGN, SUBBANNER_FONT, SUBBANNER_BORDER, SUBBANNER_ALIGN, HEADER_FONT, HEADER_FILL, HEADER_BORDER_FIRST, HEADER_BORDER_MID, HEADER_BORDER_LAST, DATA_FONT, DATA_FILL_ODD, DATA_FILL_EVEN, DATA_BORDER_FIRST, DATA_BORDER_MID, DATA_BORDER_LAST, DATA_ALIGN_L, DATA_ALIGN_C, FUNC_BANNER_FONT, FUNC_HEADER_FONT, FUNC_HEADER_BORDER, FUNC_DATA_FONT, FUNC_DATA_BORDER, FUNC_DATA_ALIGN, TACT_FONT, TACT_BORDER, TACT_ALIGN, TACT_ALIGN_L, _apply, _border, ) from openpyxl.styles import Font, PatternFill, Alignment from openpyxl.utils import get_column_letter as GL OUT_DIR = "docs/[Ubisam산출물]/02.설계/" TODAY = datetime.date.today() # 실행일 자동 반영 # ══════════════════════════════════════════════════════ # 1. PLC-PC 인터페이스 신호 정의서 # 탭: 통신 사양 | PC→PLC (Write) | PLC→PC (Read) | 핸드셰이크 시퀀스 # ══════════════════════════════════════════════════════ def build_plcpc(): wb = make_workbook() OUT = OUT_DIR + "IO_DB설계/PLC_PC_인터페이스_신호정의서.xlsx" # ── 통신 사양 ────────────────────────────────── ws = wb.create_sheet("통신 사양") set_col_widths(ws, {"A":"3.7","B":24,"C":106}) write_banner(ws, 2, 2, 3, "PtoP Welder(가접용접기) ↔ PLC 인터페이스 통신 사양", "PtoP Welder(가접용접기) — MX Component / 단일 게이트(PAGE_REQ) 핸드셰이크 · 디바이스 주소=제안(M70000/M70500 베이스)") write_header_row(ws, 4, 2, ["항목", "내용"]) comm = [ ["PLC 기종", "Mitsubishi Melsec"], ["통신 방식", "MX Component (ePLCNetType.MXComponent)"], ["맵 파일", "CSV (PLCMMapFileType.CSV)"], ["데이터 갱신", "폴링(10ms) + 이벤트 비트(REQ/ACK)"], ["디바이스 영역","비트=M, 워드=D (PC Write M70000~/D70000~, PLC Read M70500~/D70500~)"], ["핸드셰이크", "REQ→처리→ACK/COMPLETE. 단일 트리거 PLC_PAGE_CHANGE_REQ"], ["좌표·실수", "워드 INT(short). Offset/변위 INT. 스케일 ×1(필요시 ×1000)"], ["Watchdog", "PLC_LIVE_STATUS(0x00) ↔ PC_LIVE_STATUS(0x00)"], ["인터락", "Door(0x06)/EMO(0x07)/Heavy(0x05)→정지, HeadTheta(D70510) 발진 인터락"], ["주의", "주소=제안. 2nd 블록(0x65~)은 양극(P) 예비, 현재 음극만 사용"], ] write_data_rows(ws, 5, 2, comm, center_cols={0}) # ── PC→PLC (Write) ───────────────────────────── ws = wb.create_sheet("PC→PLC (Write)") set_col_widths(ws, {"A":3.7,"B":8,"C":14,"D":28,"E":10,"F":14,"G":8,"H":36,"I":28,"J":22}) write_banner(ws, 2, 2, 10, "PC → PLC 신호 (PC Write)", "PC(Aurora)가 PLC로 보내는 요청·결과·보정값") HDR = ["No","Seq/구분","신호명","방향","디바이스(제안)","Type","의미 / 트리거","응답·완료","비고"] write_header_row(ws, 4, 2, HDR) write_rows = [ ["0x00","Status","PC_LIVE_STATUS","PC→PLC","M70000(제안)","BIT","PC Alive","","Watchdog"], ["0x01","Status","PC_AUTO_STATUS","PC→PLC","M70001","BIT","Auto 상태","","Enter시 true"], ["0x02","Status","PC_AUTO_COMPLETE","PC→PLC","M70002","BIT","Auto 완료","",""], ["0x03","Status","PC_MANUAL_COMPLETE","PC→PLC","M70003","BIT","Manual 완료","",""], ["0x04","Alarm","PC_LIGHT_ALARM_STATUS","PC→PLC","M70004","BIT","경알람","",""], ["0x05","Alarm","PC_HEAVY_ALARM_STATUS","PC→PLC","M70005","BIT","중알람","",""], ["0x06","Status","PC_DOOR_INTERLOCK","PC→PLC","M70006","BIT","Door 인터락","",""], ["0x07","Status","PC_EMO_STATUS","PC→PLC","M70007","BIT","EMO","",""], ["0x08","Process","PC_PAGE_CHANGE_COMPLETE","PC→PLC","M70008","BIT","페이지 확정 ACK","","PageNo 일치시 ON"], ["0x09","Process","PC_RECIPE_CHANGE_COMPLETE","PC→PLC","M70009","BIT","레시피 변경 완료(미감시)","","260618"], ["0x0F","Process","PC_WELD_READY_ACK","PC→PLC","M70015","BIT","Resume ACK/run","","bit15,260621"], ["0x10","Process","PC_WELD_TRIGGER_ACK","PC→PLC","M70016","BIT","용접 트리거 ACK","",""], ["0x11","Status","PC_WELD_BUSY","PC→PLC","M70017","BIT","Laser On 표시","","260626"], ["0x12","Process","PC_WELD_COMPLETE","PC→PLC","M70018","BIT","용접 완료","",""], ["0x14","Process","PC_WELD_NG_JUDGE","PC→PLC","M70020","BIT","용접 NG 판정","",""], ["0x25","Request","PC_RMS_PARAMETER_READ_REQUEST_ACK","PC→PLC","M70037","BIT","RMS read ACK","",""], ["D0x00","Info","PC_ERROR_CODE","PC→PLC","D70000(제안)","INT","에러코드 에코","",""], ["D0x01","Info","PC_PAGE_NO","PC→PLC","D70001","INT","페이지 에코","",""], ["D0x02","Info","PC_RECIPE_NO","PC→PLC","D70002","INT","레시피 에코","","260618"], ["D0x08","Process","PC_WELD_OFFSET_X","PC→PLC","D70008","INT","용접 보정 X","",""], ["D0x0A","Process","PC_WELD_OFFSET_Y","PC→PLC","D70010","INT","용접 보정 Y","",""], ["D0x0C","Process","PC_DISPLACEMENT1_MEASUREMENT","PC→PLC","D70012","INT","변위 측정1","",""], ["D0x0E","Process","PC_DISPLACEMENT2_MEASUREMENT","PC→PLC","D70014","INT","변위 측정2","",""], ["D0x14~","Info","PC_CELL_ID_01~10","PC→PLC","D70020~","INT(ASCII)","Cell ID 에코","",""], ["D0x1E~","Info","PC_LOT_ID_01~08","PC→PLC","D70030~","INT(ASCII)","Lot ID 에코","",""], ["D0x28~","Info","PC_FDC_PAGE1~3_POWER/VELOCITY","PC→PLC","D70040~","INT","FDC 파워/속도","",""], ["D0x3C~","Info","PC_RMS_ALLOC_START~END","PC→PLC","D70060~97","INT","RMS 할당","",""], ] write_data_rows(ws, 5, 2, write_rows, center_cols={0,3,5}) n = len(write_rows) group_merge(ws, 3, 5, n, [r[1] for r in write_rows]) ws.merge_cells(f"E5:E{4+n}") # ── PLC→PC (Read) ────────────────────────────── ws = wb.create_sheet("PLC→PC (Read)") set_col_widths(ws, {"A":3.7,"B":8,"C":14,"D":28,"E":10,"F":14,"G":8,"H":36,"I":28,"J":22}) write_banner(ws, 2, 2, 10, "PLC → PC 신호 (PC Read)", "PLC가 PC로 보내는 요청·상태·완료") write_header_row(ws, 4, 2, HDR) read_rows = [ ["0x00","Status","PLC_LIVE_STATUS","PLC→PC","M70500(제안)","BIT","PLC Alive","Watchdog",""], ["0x01","Status","PLC_AUTO_STATUS","PLC→PC","M70501","BIT","Auto 상태","",""], ["0x02","Request","PLC_AUTO_REQ","PLC→PC","M70502","BIT","Auto 요청","PC_AUTO_STATUS",""], ["0x03","Request","PLC_MANUAL_REQ","PLC→PC","M70503","BIT","Manual 요청","",""], ["0x04","Alarm","PLC_LIGHT_ALARM_STATUS","PLC→PC","M70504","BIT","경알람","",""], ["0x05","Alarm","PLC_HEAVY_ALARM_STATUS","PLC→PC","M70505","BIT","중알람","시퀀스 정지",""], ["0x06","Status","PLC_DOOR_INTERLOCK","PLC→PC","M70506","BIT","Door","인터락",""], ["0x07","Status","PLC_EMO_STATUS","PLC→PC","M70507","BIT","EMO","인터락",""], ["0x08","Request","PLC_PAGE_CHANGE_REQ","PLC→PC","M70508","BIT","단일 게이트 트리거","Page+Recipe 확정",""], ["0x09","Request","PLC_SCOUT_ENABLE","PLC→PC","M70509","BIT","Scout Enable","","260622"], ["0x0F","Request","PLC_WELD_READY_REQ","PLC→PC","M70515","BIT","Weld_01 Ready→Resume","","bit15,260621"], ["0x10","Request","PLC_WELD_TRIGGER_REQ","PLC→PC","M70516","BIT","용접 발진 요청","READY_ACK/BUSY",""], ["0x11","Status","PLC_WELD_WAIT","PLC→PC","M70517","BIT","용접 대기","",""], ["0x12","Process","PLC_WELD_COMPLETE_ACK","PLC→PC","M70518","BIT","용접 완료 ACK","",""], ["0x1E","Alarm","PLC_ALARM_RESET","PLC→PC","M70530","BIT","알람 리셋","",""], ["0x25","Request","PLC_RMS_PARAMETER_READ_REQUEST","PLC→PC","M70537","BIT","RMS read 요청","ACK",""], ["D0x00","Info","PLC_ERROR_CODE","PLC→PC","D70500(제안)","INT","에러코드","",""], ["D0x01","Info","PLC_PAGE_NO","PLC→PC","D70501","INT","페이지(1-base)","3회 검증",""], ["D0x02","Info","PLC_RECIPE_NO","PLC→PC","D70502","INT","레시피(11/21/22/31/32)","","260618"], ["D0x03","Info","PLC_CURRENT_SLOT_NO","PLC→PC","D70503","INT","현재 슬롯","EL/CLEAN",""], ["D0x0A","Process","HEAD_THETA_POSITION","PLC→PC","D70510","INT","Laser1 Head Theta","발진 인터락",""], ["D0x14~","Info","PLC_CELL_ID_01~10","PLC→PC","D70520~","INT(ASCII)","Cell ID","",""], ["D0x1E~","Info","PLC_LOT_ID_01~08","PLC→PC","D70530~","INT(ASCII)","Lot ID","",""], ] write_data_rows(ws, 5, 2, read_rows, center_cols={0,3,5}) n = len(read_rows) group_merge(ws, 3, 5, n, [r[1] for r in read_rows]) ws.merge_cells(f"E5:E{4+n}") # ── 핸드셰이크 시퀀스 ───────────────────────── ws = wb.create_sheet("핸드셰이크 시퀀스") set_col_widths(ws, {"A":3.7,"B":6,"C":16,"D":6,"E":8,"F":48,"G":36,"H":40}) write_banner(ws, 2, 2, 8, "PLC ↔ PC 핸드셰이크 시퀀스 (Seq별)", "요청 → 처리 → 완료/응답 흐름 │ PtoP Welder 단일 게이트(PLC_PAGE_CHANGE_REQ) 기준") write_header_row(ws, 4, 2, ["No","Seq","Step","주체","동작 / 신호","디바이스(제안)","비고"]) hs = [ [1,"PageGate",1,"PLC","Page Change 요청(단일 게이트)","PLC_PAGE_CHANGE_REQ(M70508) Set","Page+Recipe 확정 트리거"], [2,"PageGate",2,"PC","Recipe 확정/전환","PLC_RECIPE_NO(D70502)→ActivateRecipe→PC_RECIPE_NO(D70002) 에코","0/동일이면 전환 생략"], [3,"PageGate",3,"PC","Page 3회 검증·확정","PLC_PAGE_NO(D70501) 3회→PC_PAGE_NO 에코→PC_PAGE_CHANGE_COMPLETE(M70008) Set","200ms×3"], [4,"PageGate",4,"PC","용접 리스트 업로드","레이어 선택 + SeqUploadWeldingList","-"], [5,"Weld",1,"PLC","발진 요청","PLC_WELD_TRIGGER_REQ(M70516) Set","PreScout(M70509) 옵션"], [6,"Weld",2,"PC","발진 ACK · BUSY","PC_WELD_TRIGGER_ACK(M70016) Set → PC_WELD_BUSY(M70017) ON","HeadTheta(D70510) 인터락"], [7,"Weld",3,"PC","실제 발진(Gate ON/OFF)","Scanner.ExecuteList → Gate ON/OFF","laserEmissionGateDelay 15ms"], [8,"Weld",4,"PC","완료 통지","PC_WELD_BUSY OFF → PC_WELD_COMPLETE(M70018) Set","PostScout(M70509) 옵션"], [9,"Weld",5,"양측","완료 ACK · Clear","PLC_WELD_COMPLETE_ACK(M70518) → TRIGGER_ACK OFF","루프백(PLC_PAGE_CHANGE_REQ) 대기"], [10,"Resume",1,"PLC","Weld_01 Ready → Resume 요청","PLC_WELD_READY_REQ(M70515) Set","bit15, Auto→Manual+Resume(가변용접)"], [11,"Resume",2,"PC","Resume ACK / run","PC_WELD_READY_ACK(M70015) Set","Resume 시 READY_ACK OFF"], [12,"Interlock",1,"PLC","Door/EMO/Heavy 상태 공유","PLC_DOOR(M70506)/EMO(M70507)/HEAVY(M70505)","이상 시 시퀀스 정지"], [13,"Status",1,"양측","Alive Watchdog 토글","PC_LIVE(M70000) ↔ PLC_LIVE(M70500)","단절 시 안전정지"], [14,"Alarm",1,"PC","PC 알람 통지","PC_LIGHT_ALARM(M70004)/HEAVY_ALARM(M70005) Set","중알람 시 시퀀스 정지"], ] write_data_rows(ws, 5, 2, hs, center_cols={0,2,3}) group_merge(ws, 3, 5, len(hs), [r[1] for r in hs]) os.makedirs(os.path.dirname(OUT), exist_ok=True) wb.save(OUT); print("✓ PLC-PC:", OUT) # ══════════════════════════════════════════════════════ # 2. 모션축·IO 맵 정의서 # 탭: 통신·제어 구성 | 모션축 정의(ACS) | I-O 맵(DI/DO) # ══════════════════════════════════════════════════════ def build_motio(): wb = make_workbook() OUT = OUT_DIR + "IO_DB설계/모션축_IO맵_정의서.xlsx" # ── 통신·제어 구성 ───────────────────────────── ws = wb.create_sheet("통신·제어 구성") set_col_widths(ws, {"A":4.4,"B":16,"C":24.8,"D":26,"E":20,"F":22,"G":34}) write_banner(ws, 2, 2, 7, "PtoP Welder(가접용접기) 통신 · 제어 구성 (PC / Aurora)", "근거: EquipmentSetup.RegisterDevices() / PAIX_PTOP.cs / architecture.md §4 │ 주소·수량=제안") write_header_row(ws, 4, 2, ["구분","대상","컨트롤러 / 디바이스","통신 / 버스","수량·범위","비고"]) write_data_rows(ws, 5, 2, [ ["PLC통신","외부 신호 제어","Mitsubishi Melsec","MX Component","1","ePLCNetType.MXComponent"], ["I/O","디지털 IO","(제안) 보드타입 런타임 설정 — IOUnitConfig.BoardType 확인 필요","필드버스","DI/DO","PAIX_PTOP:IODevice"], ["Laser","Core/Ring 레이저","IO Gate(TruPulse nano)","DO 게이트","2","ExecuteWelding Gate"], ["Scanner","레이저 스캐너","RTC LaserScanner","RTC","1","Scanner.ExecuteList"], ["모션(Servo)","X/Y 축","ACS SPiiPlus","EtherCAT","2축 (제안)","AuroraACS"], ["모션(Servo)","Z 축","(제안) AuroraMotionBoard — 보드타입 확인 필요","보드","1축 (제안)","AuroraMotionBoard"], ["Vision","카메라/측정","MIL 기반","-","-","AuroraVision(현재 미사용)"], ["상/하위(MES)","호스트 연동","Stream(LBAD/RPC)","TCP","옵션","UseStream 시"], ], center_cols={0,4}) # ── 모션축 정의(ACS) ──────────────────────────── ws = wb.create_sheet("모션축 정의(ACS)") set_col_widths(ws, {"A":4.4,"B":7,"C":12,"D":18,"E":22,"F":8,"G":12,"H":10,"I":10,"J":10,"K":10,"L":20}) write_banner(ws, 2, 2, 12, "PtoP Welder(가접용접기) 모션축 구성표 (ACS · MotionBoard) — 제안", "각 축 Init / 단축·동기축 / Jog·Relative·Absolute │ 행정·속도·Home은 모듈 소스 미보유 → (제안)") write_header_row(ws, 4, 2, ["Axis","축 명(논리)","용도","컨트롤러/통신","구동","행정/범위","최대속도","Home 방식","Soft Limit","동기/단축","비고"]) write_data_rows(ws, 5, 2, [ ["0","X","셀 위치 X","ACS SPiiPlus/EtherCAT","Servo","(제안)","(제안)","(제안)","(제안)","단축",""], ["1","Y","셀 위치 Y","ACS SPiiPlus/EtherCAT","Servo","(제안)","(제안)","(제안)","(제안)","단축",""], ["2","Z","포커스/높이","(제안) AuroraMotionBoard","Servo","(제안)","(제안)","(제안)","(제안)","단축",""], ["-","Scanner θ/XY","빔 편향(갈바노)","RTC LaserScanner","Galvano","List Jump/Mark","-","-","보정테이블","-",""], ], center_cols={0,1,4,5,6,7,8,9}) ws.cell(12, 3).value = "※ ACS = PC가 EtherCAT으로 직접 제어하는 Servo 축(X/Y). Z는 AuroraMotionBoard(보드타입 미확정), Scanner θ/XY는 RTC 갈바노. 행정/속도/Home 미확정." ws.cell(12, 3).font = Font(name="맑은 고딕", size=9, italic=True) # ── I-O 맵 (DI/DO) ─────────────────────────────── ws = wb.create_sheet("I-O 맵(DI-DO)") set_col_widths(ws, {"A":4.4,"B":6,"C":30,"D":6,"E":10,"F":10,"G":8,"H":36,"I":20}) write_banner(ws, 2, 2, 9, "PtoP Welder(가접용접기) I/O 맵 (DI/DO) — 근거: PAIX_PTOP.cs ePrismaticDI/DOAddress · 보드 벤더 미확정", "근거: PAIX_PTOP.cs ePrismaticDIAddress / ePrismaticDOAddress · DI 9~23 / DO 9~17") write_header_row(ws, 4, 2, ["No","신호명","Type","권장 모듈","슬롯/CH","Active","설명/인터락","비고"]) raw_io = [ (9,"CoreLaser_Laser_Ready","DI","DI","High","Core 레이저 Ready"), (10,"CoreLaser_Error_State","DI","DI","High","Core 레이저 에러"), (11,"CoreLaser_Laser_Output_State","DI","DI","High","Core 발진 출력 상태"), (12,"CoreLaser_Control_Status","DI","DI","High","Core 제어 상태"), (13,"RingLaser_Laser_Ready","DI","DI","High","Ring 레이저 Ready"), (14,"RingLaser_Error_State","DI","DI","High","Ring 레이저 에러"), (15,"RingLaser_Laser_Output_State","DI","DI","High","Ring 발진 출력 상태"), (23,"RingLaser_Control_Status","DI","DI","High","Ring 제어 상태"), (9,"CoreLaser_Control","DO","DO","High","Core 레이저 게이트(발진)"), (10,"CoreLaser_ErrorClearing","DO","DO","High","Core 에러 클리어"), (13,"RingLaser_Control","DO","DO","High","Ring 레이저 게이트(발진)"), (14,"RingLaser_ErrorClearing","DO","DO","High","Ring 에러 클리어"), (17,"EX_Red_Control","DO","DO","High","EX Red 표시등 제어"), ] io_rows = [[i+1, name, typ, mod, f"{typ} Ch{ch}", act, desc, ""] for i,(ch,name,typ,mod,act,desc) in enumerate(raw_io)] write_data_rows(ws, 5, 2, io_rows, center_cols={0,2,3,4,5}) os.makedirs(os.path.dirname(OUT), exist_ok=True) wb.save(OUT); print("✓ 모션축·IO:", OUT) # ══════════════════════════════════════════════════════ # 3. 기능명세서 # 탭: OverView | MainView | RecipeView | ManualView | # DeviceView | SettingView | VisionView | Sequence 흐름 # ══════════════════════════════════════════════════════ def build_funcspec(): wb = make_workbook() OUT = OUT_DIR + "UIUX_기능설계/기능명세서.xlsx" # FUNC 계열은 배경 없이 thin border만 사용 tb = lambda: FUNC_DATA_BORDER # ── OverView ──────────────────────────────────── ws = wb.create_sheet("OverView") set_col_widths(ws, {"A":4.1,"B":7.8,"C":66,"D":18.2,"E":19.3}) set_row_height(ws, 2, 43.5) c = ws.cell(2, 2, "PtoP Welder(가접용접기) SW 기능명세서") _apply(c, FUNC_BANNER_FONT, border=_border("medium","medium","medium","medium"), alignment=Alignment(horizontal="center",vertical="center",wrap_text=True)) ws.merge_cells("B2:E2") # FUNC 헤더: 배경 없음(None), thin border write_header_row(ws, 4, 2, ["no.","Content"], font=FUNC_HEADER_FONT, fill=None) ov_items = [ "운영 SW는 LG PRI의 Aurora 플랫폼으로 개발", "운영 환경 : Windows 11, .NET Framework", "개발 환경 : Windows 11, Visual Studio", "설비 : PtoP Welder(가접용접기) — 셀 P2P 레이저 가접(Tack) 용접", "모션 : ACS SPiiPlus(X/Y, EtherCAT) + AuroraMotionBoard(Z, 보드타입 미확정), 빔 편향 = RTC 갈바노 스캐너", "레이저 : Core / Ring 2계통, IO Gate(TruPulse nano) 발진", "PLC : Mitsubishi MELSEC / MX Component, 단일 게이트(PAGE_REQ) 핸드셰이크", "비전 : MIL 기반(현재 AuroraVision 미사용)", ] for i, txt in enumerate(ov_items): r = 5 + i ws.cell(r, 2, i+1).font = FUNC_DATA_FONT ws.cell(r, 2).border = tb() ws.cell(r, 2).alignment = Alignment(horizontal="center", vertical="center") ws.cell(r, 3, txt).font = FUNC_DATA_FONT ws.cell(r, 3).border = tb() ws.cell(r, 3).alignment = Alignment(horizontal="left", vertical="center", wrap_text=True) ws.merge_cells(f"C{r}:E{r}") # ── View 시트 공통 헬퍼 ────────────────────────── # 헤더: Update|Funtion Area|Funtion Name|NO|Sub|Content VIEW_HDR = ["Update","Funtion Area","Funtion Name","NO","Sub","Content"] def make_view(name, banner_text, rows): ws = wb.create_sheet(name) set_col_widths(ws, {"A":4,"B":14,"C":16,"D":24,"E":6,"F":14,"G":48}) set_row_height(ws, 3, 28) ws.cell(3, 7, banner_text).font = FUNC_DATA_FONT ws.cell(3, 7).alignment = Alignment(horizontal="left", vertical="center") set_row_height(ws, 5, 20) for j, h in enumerate(VIEW_HDR): c = ws.cell(5, 2+j, h) c.font = FUNC_HEADER_FONT c.border = tb() c.alignment = Alignment(horizontal="center", vertical="center") for i, row in enumerate(rows): r = 6 + i vals = [TODAY] + row for j, v in enumerate(vals): c = ws.cell(r, 2+j, v) c.font = FUNC_DATA_FONT c.border = tb() c.alignment = Alignment( horizontal="center" if j in {0,3} else "left", vertical="center", wrap_text=True) make_view("MainView","다음은 Main 화면에서 보여야 하는 기능이다.",[ ["운전","StartSequence/Stop", 1,"SeqAutoRunPTOP", "자동운전 시작/정지"], ["상태","UpdatePageNum", 2,"EVENT_AUTO_RUN_UPDATE_PAGENUM","현재 페이지 표시"], ["상태","WorkingPosition", 3,"eWorkingPosition", "용접 진행 위치 표시"], ["운전","Auto→Manual+Resume", 4,"bit15(PLC_WELD_READY_REQ)", "Resume(가변용접) 기능"], ["표시","ScannerCanvasCtrlTeaching", 5,"레이어/오브젝트", "스캐너 캔버스"], ]) make_view("RecipeView","다음은 Recipe 화면에서 보여야 하는 기능이다.",[ ["레시피","ActivateRecipe", 1,"RecipeManager", "레시피 선택/활성"], ["레시피","RecipeChangeHistory", 2,"RchHandler", "변경 이력"], ["모델", "UCModel", 3,"EquipmentSpecificModel_PTOP","모델 편집"], ]) make_view("ManualView","다음은 Manual 화면에서 보여야 하는 기능이다.",[ ["Manual", "ScannerOperation", 1,"ScannerOperationView","스캐너 수동 조작(발진/Jump/Mark)"], ["Manual", "ModelFinder", 2,"ModelFinderView", "모델 파인더"], ["Calibration","ScannerCalibration",3,"(제안)", "스캐너 보정"], ["Calibration","PixelResolution", 4,"(제안)", "픽셀 해상도"], ]) make_view("DeviceView","다음은 Device 화면에서 보여야 하는 기능이다.",[ ["Device", "DeviceSettingView", 1,"LEVEL3", "디바이스 설정(활성/비활성)"], ["Monitoring","PLCBitMonitoring", 2,"ePLCDevice.M","PLC 비트 모니터"], ["Monitoring","PLCWordMonitoring", 3,"ePLCDevice.D","PLC 워드 모니터"], ["Monitoring","IOInterfaceView", 4,"IODevice", "IO 인터페이스"], ]) make_view("SettingView","다음은 Setting 화면에서 보여야 하는 기능이다.",[ ["Setting","Equipment", 1,"LEVEL1", "장비 설정"], ["Setting","Log", 2,"-", "로그 설정"], ["Setting","AutoBackup", 3,"-", "자동 백업"], ["Alarm", "History/Desc", 4,"-", "알람 이력·설명"], ["Stream", "ServiceSettingView",5,"UseStream","Stream(상/하위) 사용 시"], ]) make_view("VisionView","다음은 Vision 화면에서 보여야 하는 기능이다.",[ ["Vision","(해당없음)",1,"AuroraVision 미사용","MIL 기반 — 현재 미사용(제안)"], ]) # ── Sequence 흐름: Update|Funtion Area|Processing Name|NO|Content ws = wb.create_sheet("Sequence 흐름") set_col_widths(ws, {"A":4,"B":14,"C":16,"D":24,"E":6,"F":60}) SEQ_HDR = ["Update","Funtion Area","Processing Name","NO","Content"] set_row_height(ws, 5, 20) for j,h in enumerate(SEQ_HDR): c = ws.cell(5, 2+j, h) c.font = FUNC_HEADER_FONT; c.border = tb() c.alignment = Alignment(horizontal="center",vertical="center") seq_data = [ ["AutoRun","PAGE_REQ Gate", 1,"PLC_PAGE_CHANGE_REQ(M70508) 단일 게이트 트리거"], ["AutoRun","Recipe 확정", 2,"PLC_RECIPE_NO→ActivateRecipe→PC_RECIPE_NO 에코(0/동일 생략)"], ["AutoRun","Page 확정", 3,"PLC_PAGE_NO 3회 검증→PC_PAGE_CHANGE_COMPLETE"], ["AutoRun","List Upload", 4,"레이어 선택 + SeqUploadWeldingList"], ["AutoRun","발진 요청", 5,"PLC_WELD_TRIGGER_REQ→PC_WELD_TRIGGER_ACK/BUSY(HeadTheta 인터락)"], ["AutoRun","발진", 6,"Gate ON(15ms)→Scanner.ExecuteList→Gate OFF"], ["AutoRun","완료", 7,"PC_WELD_COMPLETE→PLC_WELD_COMPLETE_ACK"], ["AutoRun","루프백", 8,"CheckCycleStop→PAGE_REQ 대기(Resume 시 READY_ACK OFF)"], ] for i, row in enumerate(seq_data): r = 6 + i vals = [TODAY] + row for j,v in enumerate(vals): c = ws.cell(r, 2+j, v) c.font = FUNC_DATA_FONT; c.border = tb() c.alignment = Alignment(horizontal="center" if j in {0,3} else "left", vertical="center", wrap_text=True) os.makedirs(os.path.dirname(OUT), exist_ok=True) wb.save(OUT); print("✓ 기능명세서:", OUT) # ══════════════════════════════════════════════════════ # 4. TactTime 산정서 # 탭: PLC TactTime | PC TactTime # ══════════════════════════════════════════════════════ def build_tact(): wb = make_workbook() OUT = OUT_DIR + "시퀀스_인터록/TactTime_산정서.xlsx" def tact_cell(ws, r, c, v, align="center"): cell = ws.cell(r, c, v) cell.font = TACT_FONT; cell.border = TACT_BORDER cell.alignment = Alignment(horizontal=align, vertical="center", wrap_text=True) return cell # ── PLC TactTime (미입수) ────────────────────── ws = wb.create_sheet("PLC TactTime") set_col_widths(ws, {"B":18,"C":19.5,"D":49.6,"E":10,"F":10.8}) set_row_height(ws, 3, 53) tact_cell(ws, 3, 2, "PLC Main 동작 (PtoP Welder) — 미입수") ws.merge_cells("B3:E3") write_header_row(ws, 4, 2, ["공정","Unit","단위동작","T/T"], font=TACT_FONT, fill=None) set_row_height(ws, 4, 22) set_row_height(ws, 5, 40) tact_cell(ws, 5, 2, "(미입수)", "center") tact_cell(ws, 5, 3, "PLC 시퀀스 사양 입수 후 작성", "left") tact_cell(ws, 5, 4, "셀 로딩 / 언로딩 / 페이지 인덱싱 T/T — PLC 측 단위동작 미보유(제안)", "left") tact_cell(ws, 5, 5, "미입수", "center") # ── PC TactTime ──────────────────────────────── ws = wb.create_sheet("PC TactTime") set_col_widths(ws, {"A":11.7,"B":18,"C":38.1,"D":75.8,"E":8,"F":10.9,"G":12.2,"H":42,"I":12,"J":13}) set_row_height(ws, 3, 53) tact_cell(ws, 3, 2, "Main 동작 (PtoP Welder · 자동, 값=제안 / 출처: SeqAutoRunPTOP.cs)", "left") ws.merge_cells("B3:E3") tact_cell(ws, 3, 8, "구간 합계") ws.merge_cells("H3:J3") # 헤더 행 높이 set_row_height(ws, 4, 22) for col, h in zip([2,3,4,5], ["공정","Unit","단위동작","T/T"]): tact_cell(ws, 4, col, h) for col, h in zip([8,9,10], ["Working Process","상세합계","기준/참조합계"]): tact_cell(ws, 4, col, h) L = [ ("Page/Recipe 게이트","핸드셰이크","Page Change 요청 대기","가변"), ("Page/Recipe 게이트","핸드셰이크","Recipe 확정/전환","~0~수백"), ("Page/Recipe 게이트","핸드셰이크","Page 3회 검증 (200ms×3)","~600"), ("Page/Recipe 게이트","핸드셰이크","용접리스트 업로드","가변"), ("발진 준비","WeldingStart","발진 요청 대기","가변"), ("발진 준비","WeldingStart","준비 응답 WaitWeldingStart(200+5)","~205"), ("발진","ExecuteWelding","Gate ON delay(laserEmissionGateDelayMs)","15"), ("발진","ExecuteWelding","실제 발진 Scanner.ExecuteList","~285"), ("발진","ExecuteWelding","Gate OFF + BUSY OFF","~50"), ("발진","ExecuteWelding","Jump+Correction 복귀 Thread.Sleep(500)","~500"), ("완료","WeldComplete","완료비트 송신 WriteWeldComplete(50+5)","~55"), ("옵션","Scout","Pre/Post Scout (M70509 ON 시)","+100/회"), ] R = [ ("Page/Recipe 게이트","가변(폴링 10ms, timeout 60s)","-"), ("발진 준비","~205",""), ("발진(1발 기준)","~850 (15+285+50+500)",""), ("완료","~55",""), ("Scout(옵션)","+100/회",""), ("1 사이클(가변 제외)","≈1110+","제안"), ] for i, (grp, unit, action, tt) in enumerate(L): r = 5 + i set_row_height(ws, r, 30) tact_cell(ws, r, 2, grp, "left") tact_cell(ws, r, 3, unit, "left") tact_cell(ws, r, 4, action, "left") tact_cell(ws, r, 5, tt, "center") for i, (proc, detail, ref_) in enumerate(R): r = 5 + i tact_cell(ws, r, 8, proc, "left") tact_cell(ws, r, 9, detail, "center") tact_cell(ws, r, 10, ref_, "center") # 공정 그룹 병합 group_merge(ws, 2, 5, len(L), [r[0] for r in L]) group_merge(ws, 3, 5, len(L), [r[0]+"|"+r[1] for r in L]) os.makedirs(os.path.dirname(OUT), exist_ok=True) wb.save(OUT); print("✓ TactTime:", OUT) # ══════════════════════════════════════════════════════ if __name__ == "__main__": build_plcpc() build_motio() build_funcspec() build_tact() print("\n✅ 02.설계 xlsx 4종 재생산 완료 (ref 파일 불필요).")