480 lines
31 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""
build_02설계_xlsx.py — PtoP Welder(가접용접기) 02.설계 산출물 재생산
────────────────────────────────────────────────────────────────────
LG PRI 표준 양식(ref 파일 불필요 · 완전 독립 실행).
스타일은 xlsxfill.py 에 하드코딩된 상수를 사용.
전제조건:
- Python 3.9+
- openpyxl >= 3.1 (pip install openpyxl)
실행:
cd <repo_root> # E:\\Work\\GIT\\LGES-OC5-PtoP-Welder
set 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])
wb.save(OUT); print("✓ PLC-PC:", OUT)
# ══════════════════════════════════════════════════════
# 2. 모션축·IO 맵 정의서
# 탭: 통신·제어 구성 | 모션축 정의(ACS) | I-O 맵(Ajin)
# ══════════════════════════════════════════════════════
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","Ajin(AJINEXTEK)","필드버스","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 축","Ajin Motion Board","보드","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 · Ajin) — 제안",
"각 축 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","포커스/높이","Ajin Motion Board","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는 Ajin Motion Board, Scanner θ/XY는 RTC 갈바노. 행정/속도/Home 미확정."
ws.cell(12, 3).font = Font(name="맑은 고딕", size=9, italic=True)
# ── I-O 맵 (Ajin) ───────────────────────────────
ws = wb.create_sheet("I-O 맵(Ajin)")
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 맵 (Ajin DI/DO) — 제안",
"근거: 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","Ajin DI","High","Core 레이저 Ready"),
(10,"CoreLaser_Error_State","DI","Ajin DI","High","Core 레이저 에러"),
(11,"CoreLaser_Laser_Output_State","DI","Ajin DI","High","Core 발진 출력 상태"),
(12,"CoreLaser_Control_Status","DI","Ajin DI","High","Core 제어 상태"),
(13,"RingLaser_Laser_Ready","DI","Ajin DI","High","Ring 레이저 Ready"),
(14,"RingLaser_Error_State","DI","Ajin DI","High","Ring 레이저 에러"),
(15,"RingLaser_Laser_Output_State","DI","Ajin DI","High","Ring 발진 출력 상태"),
(23,"RingLaser_Control_Status","DI","Ajin DI","High","Ring 제어 상태"),
(9,"CoreLaser_Control","DO","Ajin DO","High","Core 레이저 게이트(발진)"),
(10,"CoreLaser_ErrorClearing","DO","Ajin DO","High","Core 에러 클리어"),
(13,"RingLaser_Control","DO","Ajin DO","High","Ring 레이저 게이트(발진)"),
(14,"RingLaser_ErrorClearing","DO","Ajin DO","High","Ring 에러 클리어"),
(17,"EX_Red_Control","DO","Ajin 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})
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) + Ajin(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)
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])
wb.save(OUT); print("✓ TactTime:", OUT)
# ══════════════════════════════════════════════════════
if __name__ == "__main__":
build_plcpc()
build_motio()
build_funcspec()
build_tact()
print("\n✅ 02.설계 xlsx 4종 재생산 완료 (ref 파일 불필요).")