5 Commits

Author SHA1 Message Date
SOOBEEN HAN
981999dafe 충포장 설비 infoWindow 테스트
- 이전에는 제조 설비 infoWindow 테스트
2026-03-16 16:20:13 +09:00
SOOBEEN HAN
bb6c159adf 클래스명 변경 및 설비 클릭 상태일 때 다른 설비 클릭되지 않도록 변경 2026-03-16 16:19:38 +09:00
SOOBEEN HAN
e919db5bf7 설비 infoWindow 테스트
- 임시 큐브 객체 위에 정보표시 (월드)
- 큐브 클릭 시 양쪽으로 패널 뜨도록 함
- 데이터 클래스 정의하여 출력되도록 함 (mqtt연동 x)
2026-03-16 14:30:34 +09:00
SOOBEEN HAN
77a5fe219d 로딩씬->메인씬 넘어갈 때의 초기화 문제 해결 2026-03-16 10:14:37 +09:00
SOOBEEN HAN
372507ec84 제조지시현황 메뉴의 모달창 테스트 2026-03-16 10:13:58 +09:00
42 changed files with 4240 additions and 13 deletions

View File

@@ -0,0 +1,181 @@
/* ═══════════════════════════════════════════════════
Capping 상세 패널 스타일
═══════════════════════════════════════════════════ */
/* ── 루트 (전체 화면 오버레이) ─────────────────────── */
.ewlk-capping {
flex-direction: row;
justify-content: flex-end;
padding: 60px 20px 20px 80px;
}
/* ── 메인 컨테이너 ────────────────────────────────── */
.ewlk-capping__container {
width: 340px;
flex-shrink: 0;
}
/* ── 설비 정보 헤더 ───────────────────────────────── */
.ewlk-capping__info-header {
background-color: rgb(255, 255, 255);
border-color: rgb(220, 220, 225);
border-width: 1px;
border-radius: 6px;
padding: 12px;
margin-bottom: 10px;
flex-direction: row;
justify-content: space-between;
align-items: center;
}
.ewlk-capping__info-header-left {
flex-direction: column;
}
.ewlk-capping__info-title {
font-size: 11px;
color: rgb(100, 100, 110);
margin-bottom: 2px;
}
.ewlk-capping__equip-name {
font-size: 16px;
-unity-font-style: bold;
color: rgb(30, 30, 40);
}
/* ── 가동 토글 (읽기 전용) ──────────────────────── */
.ewlk-capping__toggle-wrap {
flex-direction: row;
align-items: center;
}
.ewlk-capping__toggle-label {
font-size: 12px;
color: rgb(80, 80, 100);
margin-right: 8px;
}
.ewlk-capping__toggle {
width: 44px;
height: 24px;
}
.ewlk-capping__toggle-track {
width: 44px;
height: 24px;
border-radius: 12px;
padding: 2px;
transition-property: background-color;
transition-duration: 0.2s;
}
.ewlk-capping__toggle-track--on {
background-color: rgb(60, 180, 80);
}
.ewlk-capping__toggle-track--off {
background-color: rgb(200, 200, 210);
}
.ewlk-capping__toggle-thumb {
width: 20px;
height: 20px;
border-radius: 10px;
background-color: rgb(255, 255, 255);
transition-property: translate;
transition-duration: 0.2s;
}
.ewlk-capping__toggle-thumb--on {
translate: 20px 0;
}
.ewlk-capping__toggle-thumb--off {
translate: 0 0;
}
/* ── 테이블 공통 ──────────────────────────────────── */
.ewlk-capping__table {
background-color: rgb(255, 255, 255);
border-color: rgb(220, 220, 225);
border-width: 1px;
border-radius: 6px;
margin-bottom: 10px;
overflow: hidden;
}
.ewlk-capping__table-header {
flex-direction: row;
background-color: rgb(245, 246, 250);
border-bottom-color: rgb(220, 220, 225);
border-bottom-width: 1px;
padding: 8px 0;
}
.ewlk-capping__table-header-cell {
flex-grow: 1;
flex-basis: 0;
font-size: 12px;
-unity-font-style: bold;
color: rgb(60, 60, 70);
-unity-text-align: middle-center;
}
.ewlk-capping__table-row {
flex-direction: row;
border-bottom-color: rgb(240, 240, 242);
border-bottom-width: 1px;
min-height: 32px;
align-items: center;
}
.ewlk-capping__table-cell {
flex-grow: 1;
flex-basis: 0;
font-size: 12px;
-unity-text-align: middle-center;
padding: 4px 8px;
}
.ewlk-capping__table-cell--category {
color: rgb(60, 60, 70);
}
.ewlk-capping__table-cell--measured {
color: rgb(30, 30, 40);
}
.ewlk-capping__table-cell--setting {
color: rgb(60, 80, 200);
}
/* ── 선택 해제 버튼 ───────────────────────────────── */
.ewlk-capping__deselect-btn {
position: absolute;
bottom: 40px;
align-self: center;
left: 50%;
translate: -50% 0;
padding: 8px 24px;
font-size: 13px;
color: rgb(255, 255, 255);
background-color: rgb(60, 130, 240);
border-radius: 6px;
border-width: 0;
-unity-font-style: bold;
}
.ewlk-capping__deselect-btn:hover {
background-color: rgb(40, 110, 220);
}
.ewlk-capping__deselect-btn:active {
background-color: rgb(30, 90, 190);
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: bc9c71db03fe52740a37fd8c641881c1
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
disableValidation: 0
unsupportedSelectorAction: 0

View File

@@ -0,0 +1,236 @@
/* ═══════════════════════════════════════════════════
설비 상세 패널 스타일
═══════════════════════════════════════════════════ */
/* ── 루트 (전체 화면 오버레이) ─────────────────────── */
.ewlk-detail {
flex-direction: row;
justify-content: space-between;
padding: 60px 20px 20px 80px;
}
/* ── 좌/우 컬럼 ───────────────────────────────────── */
.ewlk-detail__left {
width: 280px;
flex-shrink: 0;
}
.ewlk-detail__right {
width: 320px;
flex-shrink: 0;
}
/* ── 섹션 패널 ────────────────────────────────────── */
.ewlk-detail__section {
background-color: rgb(255, 255, 255);
border-color: rgb(220, 220, 225);
border-width: 1px;
border-radius: 6px;
margin-bottom: 10px;
overflow: hidden;
}
.ewlk-detail__section--stretch {
flex-grow: 1;
flex-shrink: 1;
min-height: 0;
}
.ewlk-detail__section-header {
background-color: rgb(245, 246, 250);
padding: 8px 12px;
font-size: 13px;
-unity-font-style: bold;
color: rgb(40, 40, 50);
border-bottom-color: rgb(220, 220, 225);
border-bottom-width: 1px;
}
.ewlk-detail__sub-header {
padding: 6px 12px;
font-size: 12px;
-unity-font-style: bold;
color: rgb(80, 80, 100);
background-color: rgb(250, 250, 252);
border-bottom-color: rgb(230, 230, 235);
border-bottom-width: 1px;
margin-top: 4px;
}
/* ── 스크롤 영역 ──────────────────────────────────── */
.ewlk-detail__scroll {
flex-grow: 1;
flex-shrink: 1;
}
/* ── 키-값 행 ─────────────────────────────────────── */
.ewlk-detail__row {
flex-direction: row;
align-items: center;
padding: 5px 12px;
border-bottom-color: rgb(240, 240, 242);
border-bottom-width: 1px;
min-height: 28px;
}
.ewlk-detail__row-key {
width: 90px;
flex-shrink: 0;
font-size: 12px;
color: rgb(100, 100, 110);
}
.ewlk-detail__row-value {
flex-grow: 1;
font-size: 12px;
color: rgb(30, 30, 40);
-unity-text-align: upper-right;
}
/* ── 키 라벨 중요도 표시 (*) ──────────────────────── */
.ewlk-detail__key-star {
color: rgb(220, 40, 40);
font-size: 13px;
-unity-font-style: bold;
margin-left: 1px;
}
/* ── 값 색상 구분 (측정/세팅/단위) ────────────────── */
.ewlk-detail__row-value-wrap {
flex-grow: 1;
flex-direction: row;
justify-content: flex-end;
align-items: center;
}
.ewlk-detail__val--measured {
font-size: 12px;
color: rgb(30, 30, 40);
}
.ewlk-detail__val--setting {
font-size: 12px;
color: rgb(60, 80, 200);
}
.ewlk-detail__val--separator {
font-size: 12px;
color: rgb(30, 30, 40);
margin-left: 2px;
margin-right: 2px;
}
.ewlk-detail__val--unit {
font-size: 11px;
color: rgb(30, 30, 40);
}
/* ── 데이터 유형 범례 ─────────────────────────────── */
.ewlk-detail__data-type-wrap {
flex-grow: 1;
flex-direction: column;
padding: 2px 0;
}
.ewlk-detail__data-type-item {
flex-direction: row;
align-items: center;
margin-bottom: 2px;
}
.ewlk-detail__dot {
width: 8px;
height: 8px;
border-radius: 4px;
margin-right: 6px;
}
.ewlk-detail__dot--measured {
background-color: rgb(30, 30, 30);
}
.ewlk-detail__dot--setting {
background-color: rgb(60, 80, 200);
}
.ewlk-detail__dot--predicted {
background-color: rgb(40, 160, 40);
}
.ewlk-detail__data-type-label {
font-size: 11px;
color: rgb(60, 60, 70);
}
/* ── 가동 상태 행 (쌍) ────────────────────────────── */
.ewlk-detail__status-pair {
flex-direction: row;
border-bottom-color: rgb(240, 240, 242);
border-bottom-width: 1px;
}
.ewlk-detail__status-cell {
flex-direction: row;
align-items: center;
flex-grow: 1;
flex-basis: 0;
padding: 5px 12px;
min-height: 28px;
}
.ewlk-detail__status-key {
font-size: 12px;
color: rgb(100, 100, 110);
margin-right: 8px;
flex-shrink: 0;
}
.ewlk-detail__status-value {
font-size: 11px;
padding: 2px 8px;
border-radius: 3px;
}
.ewlk-detail__status--active {
background-color: rgb(220, 245, 220);
color: rgb(20, 120, 20);
}
.ewlk-detail__status--inactive {
background-color: rgb(240, 240, 242);
color: rgb(140, 140, 150);
}
/* ── 선택 해제 버튼 ───────────────────────────────── */
.ewlk-detail__deselect-btn {
position: absolute;
bottom: 40px;
align-self: center;
left: 50%;
translate: -50% 0;
padding: 8px 24px;
font-size: 13px;
color: rgb(255, 255, 255);
background-color: rgb(60, 130, 240);
border-radius: 6px;
border-width: 0;
-unity-font-style: bold;
}
.ewlk-detail__deselect-btn:hover {
background-color: rgb(40, 110, 220);
}
.ewlk-detail__deselect-btn:active {
background-color: rgb(30, 90, 190);
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: bcff7fee6969c3046a315174982a7558
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
disableValidation: 0
unsupportedSelectorAction: 0

View File

@@ -0,0 +1,41 @@
/* ── 설비 정보 팝업 ─────────────────────────────── */
.ewlk-equip-popup {
flex-direction: row;
align-items: center;
padding: 4px 10px 4px 8px;
background-color: rgba(20, 24, 40, 0.85);
border-radius: 6px;
/* 팝업 중앙 정렬용 (translate은 Tracker에서 설정) */
}
/* ── 상태 표시 원 ──────────────────────────────── */
.ewlk-equip-popup__indicator {
width: 10px;
height: 10px;
border-radius: 5px;
margin-right: 6px;
/* 기본: 투명 (상태 클래스로 색상 결정) */
background-color: rgba(128, 128, 128, 0.5);
}
.ewlk-equip-popup__indicator.ewlk-equip-popup--running {
background-color: rgb(0, 200, 80);
}
.ewlk-equip-popup__indicator.ewlk-equip-popup--idle {
background-color: rgb(240, 200, 0);
}
.ewlk-equip-popup__indicator.ewlk-equip-popup--planned-stop {
background-color: rgb(140, 140, 140);
}
/* ── 설비명 라벨 ──────────────────────────────── */
.ewlk-equip-popup__label {
color: rgb(255, 255, 255);
font-size: 13px;
-unity-font-style: bold;
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 16e88e33fc1fa9a4697423125d9b9279
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
disableValidation: 0
unsupportedSelectorAction: 0

View File

@@ -0,0 +1,206 @@
/* ──────────────────────────────────────────────────────────
EWLKMfgOrderModalContent — 제조지시현황 모달
────────────────────────────────────────────────────────── */
/* ── 루트 컨테이너 ──────────────────────────────────────── */
.ewlk-mfgorder {
flex-direction: column;
flex-grow: 1;
background-color: rgb(18, 22, 36);
padding: 0;
min-height: 400px;
}
/* ── 탭 바 ──────────────────────────────────────────────── */
.ewlk-mfgorder__tab-bar {
flex-direction: row;
border-bottom-width: 2px;
border-bottom-color: rgb(50, 60, 90);
margin-bottom: 0;
}
.ewlk-mfgorder__tab {
flex-shrink: 0;
padding: 8px 20px;
background-color: rgb(34, 42, 66);
color: rgb(140, 150, 175);
border-width: 0;
border-bottom-width: 2px;
border-bottom-color: rgba(0, 0, 0, 0);
border-radius: 0;
font-size: 13px;
-unity-font-style: normal;
margin-bottom: -2px;
}
.ewlk-mfgorder__tab:hover {
background-color: rgb(40, 50, 78);
color: rgb(220, 225, 240);
}
.ewlk-mfgorder__tab--active {
color: rgb(220, 225, 240);
border-bottom-color: rgb(0, 140, 255);
-unity-font-style: bold;
}
/* ── 요약 바 ─────────────────────────────────────────────── */
.ewlk-mfgorder__summary-bar {
flex-direction: row;
background-color: rgb(26, 32, 50);
padding: 10px 16px;
border-bottom-width: 1px;
border-bottom-color: rgb(50, 60, 90);
}
.ewlk-mfgorder__summary-item {
flex-direction: row;
align-items: center;
flex-grow: 1;
margin-right: 16px;
}
.ewlk-mfgorder__summary-title {
color: rgb(140, 150, 175);
font-size: 11px;
margin-right: 8px;
flex-shrink: 0;
}
.ewlk-mfgorder__summary-value {
color: rgb(220, 225, 240);
font-size: 13px;
-unity-font-style: bold;
flex-shrink: 0;
}
/* ── 설비가동률 행 ───────────────────────────────────────── */
.ewlk-mfgorder__equip-row {
flex-direction: row;
align-items: center;
background-color: rgb(26, 32, 50);
padding: 6px 16px;
border-bottom-width: 1px;
border-bottom-color: rgb(50, 60, 90);
}
.ewlk-mfgorder__equip-title {
color: rgb(140, 150, 175);
font-size: 11px;
flex-shrink: 0;
margin-right: 10px;
width: 180px;
}
.ewlk-mfgorder__equip-rate {
color: rgb(220, 225, 240);
font-size: 12px;
flex-shrink: 0;
margin-left: 8px;
width: 44px;
}
.ewlk-mfgorder__count {
color: rgb(140, 150, 175);
font-size: 12px;
flex-shrink: 0;
margin-left: 12px;
}
/* ── 프로그레스 바 (공통) ────────────────────────────────── */
.ewlk-mfgorder__progress-wrap {
flex-grow: 1;
height: 10px;
background-color: rgb(50, 60, 90);
border-radius: 4px;
overflow: hidden;
max-width: 300px;
}
.ewlk-mfgorder__progress-fill {
height: 100%;
background-color: rgb(0, 200, 100);
border-radius: 4px;
width: 0%;
}
/* ── 테이블 헤더 ─────────────────────────────────────────── */
.ewlk-mfgorder__header {
flex-direction: row;
background-color: rgb(34, 42, 66);
border-bottom-width: 1px;
border-bottom-color: rgb(50, 60, 90);
padding: 0 4px;
}
.ewlk-mfgorder__header-cell {
color: rgb(140, 150, 175);
font-size: 11px;
-unity-font-style: bold;
-unity-text-align: middle-center;
padding: 6px 4px;
border-right-width: 1px;
border-right-color: rgb(50, 60, 90);
}
/* ── ListView ────────────────────────────────────────────── */
.ewlk-mfgorder__list {
flex-grow: 1;
background-color: rgb(18, 22, 36);
}
/* ── 데이터 행 ───────────────────────────────────────────── */
.ewlk-mfgorder__row {
flex-direction: row;
align-items: center;
height: 28px;
padding: 0 4px;
border-bottom-width: 1px;
border-bottom-color: rgb(50, 60, 90);
}
.ewlk-mfgorder__cell {
height: 100%;
justify-content: center;
padding: 0 4px;
border-right-width: 1px;
border-right-color: rgb(50, 60, 90);
overflow: hidden;
}
.ewlk-mfgorder__cell-label {
color: rgb(220, 225, 240);
font-size: 12px;
-unity-text-align: middle-left;
overflow: hidden;
}
/* ── 달성률 셀 내부 ─────────────────────────────────────── */
.ewlk-mfgorder__stopped {
color: rgb(160, 165, 185);
font-size: 11px;
-unity-text-align: middle-center;
background-color: rgb(60, 65, 85);
padding: 2px 6px;
border-radius: 3px;
display: none;
}
.ewlk-mfgorder__pct {
color: rgb(220, 225, 240);
font-size: 11px;
-unity-text-align: middle-right;
flex-shrink: 0;
margin-left: 4px;
width: 36px;
display: none;
}
/* ── 컬럼 너비 ───────────────────────────────────────────── */
.col-equip { width: 100px; flex-shrink: 0; }
.col-order { width: 120px; flex-shrink: 0; }
.col-item-code { width: 110px; flex-shrink: 0; }
.col-item-name { flex-grow: 1; }
.col-time { width: 80px; flex-shrink: 0; }
.col-target { width: 90px; flex-shrink: 0; }
.col-achievement { width: 150px; flex-shrink: 0; flex-direction: row; align-items: center; }

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 2dbc3cb9562ea774a8fd358ec9d8ec65
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
disableValidation: 0
unsupportedSelectorAction: 0

View File

@@ -0,0 +1,53 @@
/* ═══════════════════════════════════════════════════
충포장 설비 정보 팝업 스타일
═══════════════════════════════════════════════════ */
/* ── 루트 ────────────────────────────────────────── */
.ewlk-pack-popup {
padding: 8px 12px;
background-color: rgba(20, 24, 40, 0.88);
border-radius: 8px;
min-width: 120px;
}
/* ── 타이틀 ──────────────────────────────────────── */
.ewlk-pack-popup__title {
color: rgb(255, 255, 255);
font-size: 14px;
-unity-font-style: bold;
margin-bottom: 6px;
padding-bottom: 4px;
border-bottom-color: rgba(255, 255, 255, 0.2);
border-bottom-width: 1px;
}
/* ── 데이터 항목 ─────────────────────────────────── */
.ewlk-pack-popup__item {
flex-direction: row;
align-items: center;
margin-top: 3px;
}
.ewlk-pack-popup__dot {
width: 6px;
height: 6px;
border-radius: 3px;
background-color: rgb(180, 200, 255);
margin-right: 6px;
flex-shrink: 0;
}
.ewlk-pack-popup__label {
color: rgba(255, 255, 255, 0.75);
font-size: 12px;
margin-right: 4px;
}
.ewlk-pack-popup__value {
color: rgb(255, 255, 255);
font-size: 12px;
-unity-font-style: bold;
}

View File

@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 29ef463f5fc415a4bb3ff70c4c6f5ceb
ScriptedImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 2
userData:
assetBundleName:
assetBundleVariant:
script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0}
disableValidation: 0
unsupportedSelectorAction: 0

File diff suppressed because one or more lines are too long

View File

@@ -246,6 +246,151 @@ MonoBehaviour:
m_ShadowLayerMask: 1
m_RenderingLayers: 1
m_ShadowRenderingLayers: 1
--- !u!1 &164967187
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 164967191}
- component: {fileID: 164967190}
- component: {fileID: 164967189}
- component: {fileID: 164967188}
- component: {fileID: 164967192}
- component: {fileID: 164967193}
m_Layer: 0
m_Name: Cube_mfg
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!65 &164967188
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 164967187}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 1, y: 1, z: 1}
m_Center: {x: 0, y: 0, z: 0}
--- !u!23 &164967189
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 164967187}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!33 &164967190
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 164967187}
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
--- !u!4 &164967191
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 164967187}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: -1.5, y: 0.5, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &164967192
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 164967187}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 06942d388a19bcf458c4f333f1afc27a, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::UVC.EnglewoodLAB.EWLKEquipInfoTracker
uiDocument: {fileID: 1306527162}
equipmentName: M001
equipmentStatus: 0
worldYOffset: 1
--- !u!114 &164967193
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 164967187}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 520526319be9b2d4985315191c400d59, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::UVC.EnglewoodLAB.EWLKEquipSelectHandler
uiDocument: {fileID: 1306527162}
outlineScale: 1.05
outlineColor: {r: 0.2, g: 0.5, b: 1, a: 0.4}
--- !u!1 &557669013
GameObject:
m_ObjectHideFlags: 0
@@ -356,6 +501,7 @@ GameObject:
- component: {fileID: 1079771973}
- component: {fileID: 1079771972}
- component: {fileID: 1079771971}
- component: {fileID: 1079771974}
m_Layer: 0
m_Name: Main Camera
m_TagString: MainCamera
@@ -430,13 +576,430 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1079771970}
serializedVersion: 2
m_LocalRotation: {x: 0.2588191, y: 0, z: 0, w: 0.9659258}
m_LocalPosition: {x: 0, y: 3, z: -5}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 30, y: 0, z: 0}
--- !u!114 &1079771974
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1079771970}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.RenderPipelines.Universal.Runtime::UnityEngine.Rendering.Universal.UniversalAdditionalCameraData
m_RenderShadows: 1
m_RequiresDepthTextureOption: 2
m_RequiresOpaqueTextureOption: 2
m_CameraType: 0
m_Cameras: []
m_RendererIndex: -1
m_VolumeLayerMask:
serializedVersion: 2
m_Bits: 1
m_VolumeTrigger: {fileID: 0}
m_VolumeFrameworkUpdateModeOption: 2
m_RenderPostProcessing: 0
m_Antialiasing: 0
m_AntialiasingQuality: 2
m_StopNaN: 0
m_Dithering: 0
m_ClearDepth: 1
m_AllowXRRendering: 1
m_AllowHDROutput: 1
m_UseScreenCoordOverride: 0
m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0}
m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0}
m_RequiresDepthTexture: 0
m_RequiresColorTexture: 0
m_TaaSettings:
m_Quality: 3
m_FrameInfluence: 0.1
m_JitterScale: 1
m_MipBias: 0
m_VarianceClampScale: 0.9
m_ContrastAdaptiveSharpening: 0
m_Version: 2
--- !u!1 &1177134822
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1177134827}
- component: {fileID: 1177134826}
- component: {fileID: 1177134825}
- component: {fileID: 1177134824}
- component: {fileID: 1177134828}
m_Layer: 0
m_Name: Cube_robotCtn
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!65 &1177134824
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1177134822}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 1, y: 1, z: 1}
m_Center: {x: 0, y: 0, z: 0}
--- !u!23 &1177134825
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1177134822}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!33 &1177134826
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1177134822}
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
--- !u!4 &1177134827
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1177134822}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 1, z: -10}
m_LocalPosition: {x: 2.53, y: 0.5, z: 0.42}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &1177134828
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1177134822}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 95cc4aa0987ca5645bae8bdaf4dd0a28, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::UVC.EnglewoodLAB.EWLKPackEquipInfoTracker
uiDocument: {fileID: 1306527162}
equipType: 1
worldYOffset: 1
outlineScale: 1.05
outlineColor: {r: 0.2, g: 0.5, b: 1, a: 0.4}
--- !u!1 &1177232402
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1177232406}
- component: {fileID: 1177232405}
- component: {fileID: 1177232404}
- component: {fileID: 1177232403}
m_Layer: 0
m_Name: Stage
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!64 &1177232403
MeshCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1177232402}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 5
m_Convex: 0
m_CookingOptions: 30
m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0}
--- !u!23 &1177232404
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1177232402}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!33 &1177232405
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1177232402}
m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0}
--- !u!4 &1177232406
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1177232402}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 10, y: 1, z: 10}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1191611111
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1191611116}
- component: {fileID: 1191611115}
- component: {fileID: 1191611114}
- component: {fileID: 1191611113}
- component: {fileID: 1191611117}
m_Layer: 0
m_Name: Cube_loadingRobot
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!65 &1191611113
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1191611111}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 1, y: 1, z: 1}
m_Center: {x: 0, y: 0, z: 0}
--- !u!23 &1191611114
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1191611111}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!33 &1191611115
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1191611111}
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
--- !u!4 &1191611116
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1191611111}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 5.09, y: 0.5, z: 2.66}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &1191611117
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1191611111}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 95cc4aa0987ca5645bae8bdaf4dd0a28, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::UVC.EnglewoodLAB.EWLKPackEquipInfoTracker
uiDocument: {fileID: 1306527162}
equipType: 2
worldYOffset: 1
outlineScale: 1.05
outlineColor: {r: 0.2, g: 0.5, b: 1, a: 0.4}
--- !u!1 &1306527161
GameObject:
m_ObjectHideFlags: 0
@@ -597,6 +1160,136 @@ MonoBehaviour:
m_PivotReferenceSize: 0
m_Pivot: 0
m_WorldSpaceCollider: {fileID: 0}
--- !u!1 &2034494620
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 2034494625}
- component: {fileID: 2034494624}
- component: {fileID: 2034494623}
- component: {fileID: 2034494622}
- component: {fileID: 2034494626}
m_Layer: 0
m_Name: Cube_capping
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!65 &2034494622
BoxCollider:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2034494620}
m_Material: {fileID: 0}
m_IncludeLayers:
serializedVersion: 2
m_Bits: 0
m_ExcludeLayers:
serializedVersion: 2
m_Bits: 0
m_LayerOverridePriority: 0
m_IsTrigger: 0
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
m_Size: {x: 1, y: 1, z: 1}
m_Center: {x: 0, y: 0, z: 0}
--- !u!23 &2034494623
MeshRenderer:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2034494620}
m_Enabled: 1
m_CastShadows: 1
m_ReceiveShadows: 1
m_DynamicOccludee: 1
m_StaticShadowCaster: 0
m_MotionVectors: 1
m_LightProbeUsage: 1
m_ReflectionProbeUsage: 1
m_RayTracingMode: 2
m_RayTraceProcedural: 0
m_RayTracingAccelStructBuildFlagsOverride: 0
m_RayTracingAccelStructBuildFlags: 1
m_SmallMeshCulling: 1
m_ForceMeshLod: -1
m_MeshLodSelectionBias: 0
m_RenderingLayerMask: 1
m_RendererPriority: 0
m_Materials:
- {fileID: 2100000, guid: 31321ba15b8f8eb4c954353edc038b1d, type: 2}
m_StaticBatchInfo:
firstSubMesh: 0
subMeshCount: 0
m_StaticBatchRoot: {fileID: 0}
m_ProbeAnchor: {fileID: 0}
m_LightProbeVolumeOverride: {fileID: 0}
m_ScaleInLightmap: 1
m_ReceiveGI: 1
m_PreserveUVs: 0
m_IgnoreNormalsForChartDetection: 0
m_ImportantGI: 0
m_StitchLightmapSeams: 1
m_SelectedEditorRenderState: 3
m_MinimumChartSize: 4
m_AutoUVMaxDistance: 0.5
m_AutoUVMaxAngle: 89
m_LightmapParameters: {fileID: 0}
m_GlobalIlluminationMeshLod: 0
m_SortingLayerID: 0
m_SortingLayer: 0
m_SortingOrder: 0
m_MaskInteraction: 0
m_AdditionalVertexStreams: {fileID: 0}
--- !u!33 &2034494624
MeshFilter:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2034494620}
m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0}
--- !u!4 &2034494625
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2034494620}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0.91, y: 0.5, z: -1.35}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &2034494626
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2034494620}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 95cc4aa0987ca5645bae8bdaf4dd0a28, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::UVC.EnglewoodLAB.EWLKPackEquipInfoTracker
uiDocument: {fileID: 1306527162}
equipType: 0
worldYOffset: 1
outlineScale: 1.05
outlineColor: {r: 0.2, g: 0.5, b: 1, a: 0.4}
--- !u!1660057539 &9223372036854775807
SceneRoots:
m_ObjectHideFlags: 0
@@ -608,3 +1301,8 @@ SceneRoots:
- {fileID: 1840260739}
- {fileID: 883201337}
- {fileID: 1664424033}
- {fileID: 1177232406}
- {fileID: 164967191}
- {fileID: 2034494625}
- {fileID: 1177134827}
- {fileID: 1191611116}

View File

@@ -0,0 +1,52 @@
#nullable enable
using System.Collections.Generic;
namespace UVC.EnglewoodLAB.Data
{
/// <summary>
/// Capping 상세 패널 — 구분/측정/설정 행 데이터.
/// </summary>
public class CappingMeasureSettingRow
{
/// <summary>구분 (예: "Home")</summary>
public string Category { get; set; } = "-";
/// <summary>측정값</summary>
public string Measured { get; set; } = "-";
/// <summary>설정값</summary>
public string Setting { get; set; } = "-";
public CappingMeasureSettingRow() { }
public CappingMeasureSettingRow(string category, string measured = "-", string setting = "-")
{
Category = category;
Measured = measured;
Setting = setting;
}
}
/// <summary>
/// Capping 상세 패널 전체 데이터.
/// 설비 정보 + 가동 토글 + 구분/측정/설정 테이블.
/// </summary>
public class EWLKCappingDetailData
{
/// <summary>설비명</summary>
public string EquipmentName { get; set; } = "Capping";
/// <summary>가동 여부 (읽기 전용 토글 표시용)</summary>
public bool IsRunning { get; set; }
/// <summary>구분/측정/설정 테이블 (5행)</summary>
public List<CappingMeasureSettingRow> MeasureSettingRows { get; } = new()
{
new("Home"),
new("상승대기"),
new("하강"),
new("High Speed"),
new("Low Speed"),
};
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e2b078b543c790746b4a43ba7a9b0946

View File

@@ -0,0 +1,172 @@
#nullable enable
using System.Collections.Generic;
namespace UVC.EnglewoodLAB.Data
{
/// <summary>
/// 설비 상세 패널 전체 데이터.
/// </summary>
public class EWLKManuEquipDetailData
{
public EWLKManuEquipBasicInfoData BasicInfo { get; } = new();
public EWLKQualityInfoData QualityInfo { get; } = new();
public EWLKManuEquipOperationData OperationInfo { get; } = new();
public EWLKMainMixerData MainMixer { get; } = new();
}
// ── 설비 기본 정보 ──────────────────────────────────
/// <summary>
/// 설비 기본 정보 패널 데이터.
/// </summary>
public class EWLKManuEquipBasicInfoData
{
/// <summary>설비명</summary>
public string EquipmentName { get; set; } = "-";
/// <summary>설비 CAPA (예: "999.9 / 999.9")</summary>
public string Capacity { get; set; } = "-";
/// <summary>설비 위치 (예: "내관 1층(제조) / 01라인")</summary>
public string Location { get; set; } = "-";
}
// ── 품질 정보 ───────────────────────────────────────
/// <summary>
/// 품질 목표 항목 한 행.
/// </summary>
public class QualityTargetItem
{
/// <summary>항목명 (예: "목표 정보")</summary>
public string Label { get; set; } = "목표 정보";
/// <summary>범위 값 (예: "99 ~ 99")</summary>
public string Range { get; set; } = "-";
}
/// <summary>
/// 품질 정보 패널 데이터.
/// </summary>
public class EWLKQualityInfoData
{
public List<QualityTargetItem> Targets { get; } = new()
{
new QualityTargetItem(),
new QualityTargetItem(),
};
}
// ── 설비 운영 정보 ──────────────────────────────────
/// <summary>
/// 공정/상별 용해 항목.
/// </summary>
public class OperationInfoItem
{
/// <summary>품번</summary>
public string ProductNo { get; set; } = "-";
/// <summary>품명</summary>
public string ProductName { get; set; } = "-";
/// <summary>로트(LOT)</summary>
public string LotNo { get; set; } = "-";
/// <summary>제조량</summary>
public string ProductionQty { get; set; } = "-";
/// <summary>작업자</summary>
public string Worker { get; set; } = "-";
}
/// <summary>
/// 설비 운영 정보 패널 데이터.
/// </summary>
public class EWLKManuEquipOperationData
{
/// <summary>공정 정보</summary>
public OperationInfoItem ProcessInfo { get; } = new();
/// <summary>상별 용해</summary>
public OperationInfoItem MeltInfo { get; } = new();
}
// ── 메인 믹서 ───────────────────────────────────────
/// <summary>
/// 설비 가동 상태 항목 (가동/비가동 표시용).
/// </summary>
public class EquipRunStatusItem
{
public string Label { get; set; } = string.Empty;
public bool IsActive { get; set; }
public EquipRunStatusItem(string label, bool isActive = false)
{
Label = label;
IsActive = isActive;
}
}
/// <summary>
/// 메인 믹서 패널 데이터.
/// 측정값(검정) / 세팅값(파랑) / 단위(검정)로 색상 구분됩니다.
/// </summary>
public class EWLKMainMixerData
{
// ── 측정값만 (검정) ──────────────────────────────
/// <summary>자켓 온도 — 측정값</summary>
public string JacketTemp { get; set; } = "0.0";
/// <summary>상부 온도 — 측정값</summary>
public string UpperTemp { get; set; } = "0.0";
// ── 측정값 + 세팅값 (검정 / 파랑) ────────────────
/// <summary>온도 — 측정값</summary>
public string TempMeasured { get; set; } = "0.0";
/// <summary>온도 — 세팅값</summary>
public string TempSetting { get; set; } = "0.0";
/// <summary>HOMO — 측정값 (RPM)</summary>
public string HomoMeasured { get; set; } = "0";
/// <summary>HOMO — 세팅값 (RPM)</summary>
public string HomoSetting { get; set; } = "0";
/// <summary>PAD — 측정값 (RPM)</summary>
public string PadMeasured { get; set; } = "0";
/// <summary>PAD — 세팅값 (RPM)</summary>
public string PadSetting { get; set; } = "0";
/// <summary>SCR — 측정값 (RPM)</summary>
public string ScrMeasured { get; set; } = "0";
/// <summary>SCR — 세팅값 (RPM)</summary>
public string ScrSetting { get; set; } = "0";
/// <summary>설비내 압력 — 측정값 (mmHg)</summary>
public string PressureMeasured { get; set; } = "0.0";
/// <summary>설비내 압력 — 세팅값 (mmHg)</summary>
public string PressureSetting { get; set; } = "0.0";
// ── 세팅값만 (파랑) ──────────────────────────────
/// <summary>가열온도 설정 — 세팅값</summary>
public string HeatingTempSetting { get; set; } = "0.0";
/// <summary>냉각온도 설정 — 세팅값</summary>
public string CoolingTempSetting { get; set; } = "0.0";
// ── 단일 측정값 ──────────────────────────────────
/// <summary>가동 상태 항목 목록 (진공시작, 진공파기, 배출시작 등)</summary>
public List<EquipRunStatusItem> RunStatuses { get; } = new()
{
new("진공시작"),
new("진공파기"),
new("배출시작"),
new("탱크벨트"),
};
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 7b171558f2adbd54b8f22b6c2672ab68

View File

@@ -0,0 +1,29 @@
#nullable enable
namespace UVC.EnglewoodLAB.Data
{
/// <summary>
/// 설비 상태 열거형.
/// </summary>
public enum EquipmentStatus
{
/// <summary>가동 (초록)</summary>
Running,
/// <summary>비가동 (노랑)</summary>
Idle,
/// <summary>계획 정지 (회색)</summary>
PlannedStop,
}
/// <summary>
/// 설비 정보 팝업에 표시할 데이터.
/// </summary>
public class EWLKManuEquipInfoData
{
/// <summary>설비 상태</summary>
public EquipmentStatus Status { get; set; } = EquipmentStatus.Running;
/// <summary>설비명 (예: M001)</summary>
public string EquipmentName { get; set; } = string.Empty;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 426fb0aa797442849b6434b959f94739

View File

@@ -0,0 +1,84 @@
#nullable enable
using System.Collections.Generic;
namespace UVC.EnglewoodLAB.Data
{
/// <summary>제조지시현황 요약 데이터 (라인별)</summary>
public class EWLKMfgOrderSummaryData
{
/// <summary>공장구분 (탭 레이블로 사용)</summary>
public string FactoryName { get; set; } = string.Empty;
/// <summary>전체 제조 목표량</summary>
public string TotalTarget { get; set; } = string.Empty;
/// <summary>실제 실적량</summary>
public string TotalActual { get; set; } = string.Empty;
/// <summary>전체 제조 진척률 (0~100)</summary>
public float TotalProgress { get; set; }
/// <summary>일 설비가동률 가중대수% (0~100)</summary>
public float EquipRate { get; set; }
/// <summary>가동 중 설비 수</summary>
public int ActiveCount { get; set; }
/// <summary>전체 설비 수</summary>
public int TotalCount { get; set; }
}
/// <summary>설비별 제조지시 행 데이터</summary>
public class EWLKMfgOrderRowData
{
/// <summary>설비명</summary>
public string EquipName { get; set; } = string.Empty;
/// <summary>지시번호</summary>
public string OrderNo { get; set; } = string.Empty;
/// <summary>품목코드</summary>
public string ItemCode { get; set; } = string.Empty;
/// <summary>품목명</summary>
public string ItemName { get; set; } = string.Empty;
/// <summary>시작시간</summary>
public string StartTime { get; set; } = string.Empty;
/// <summary>목표량</summary>
public string Target { get; set; } = string.Empty;
/// <summary>달성률 (0~100). IsStopped가 true면 사용되지 않음</summary>
public float Achievement { get; set; }
/// <summary>계획 정지 여부 (true면 달성률 대신 "계획 정지" 표시)</summary>
public bool IsStopped { get; set; }
}
/// <summary>공장 라인별 제조지시 데이터 (요약 + 행 목록)</summary>
public class EWLKMfgOrderLineData
{
/// <summary>기본 라인 이름 (MQTT summary 수신 전 초기값)</summary>
public string DefaultName { get; }
/// <summary>요약 데이터</summary>
public EWLKMfgOrderSummaryData Summary { get; } = new();
/// <summary>설비별 행 목록 (ListView itemsSource)</summary>
public List<EWLKMfgOrderRowData> Rows { get; } = new();
/// <param name="defaultName">MQTT 수신 전 초기 탭 레이블</param>
public EWLKMfgOrderLineData(string defaultName) => DefaultName = defaultName;
}
/// <summary>제조지시현황 전체 데이터 (2개 라인)</summary>
public class EWLKMfgOrderData
{
/// <summary>라인 1</summary>
public EWLKMfgOrderLineData Line1 { get; } = new("라인 1");
/// <summary>라인 2</summary>
public EWLKMfgOrderLineData Line2 { get; } = new("라인 2");
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c3db028a33aa08d4c843705db64e7550

View File

@@ -0,0 +1,205 @@
#nullable enable
using System;
using UVC.Data;
using UVC.Data.Core;
using UVC.Data.Mqtt;
namespace UVC.EnglewoodLAB.Data
{
/// <summary>
/// 제조지시현황 데이터를 MQTT로 수신하는 서비스.
/// 2개 라인의 요약 및 설비 행 데이터를 구독합니다.
/// DataRepository.Instance.MqttReceiver(공유 수신기)를 사용합니다.
/// </summary>
/// <remarks>
/// MQTT 토픽 구조:
/// ewlk/mfgorder/line1/summary — 라인1 요약 (DataObject)
/// ewlk/mfgorder/line1/rows — 라인1 설비 행 배열 (DataArray)
/// ewlk/mfgorder/line2/summary — 라인2 요약 (DataObject)
/// ewlk/mfgorder/line2/rows — 라인2 설비 행 배열 (DataArray)
///
/// Summary JSON 스키마:
/// <code>
/// {
/// "factory_name": "2공장 제조1",
/// "total_target": "47,910.2 Kg",
/// "total_actual": "38,889.0 Kg",
/// "total_progress": 34.2,
/// "equip_rate": 0.0,
/// "active_count": 0,
/// "total_count": 47
/// }
/// </code>
///
/// Rows JSON 스키마 (배열):
/// <code>
/// [
/// {
/// "equip_name": "UHM50",
/// "order_no": "-",
/// "item_code": "-",
/// "item_name": "-",
/// "start_time": "-",
/// "target": "-",
/// "achievement": 0.0,
/// "status": "stopped"
/// },
/// ...
/// ]
/// </code>
/// </remarks>
public class EWLKMfgOrderMqttService : IDisposable
{
// ── MQTT 토픽 ──────────────────────────────────────────────
private const string TopicLine1Summary = "ewlk/mfgorder/line1/summary";
private const string TopicLine1Rows = "ewlk/mfgorder/line1/rows";
private const string TopicLine2Summary = "ewlk/mfgorder/line2/summary";
private const string TopicLine2Rows = "ewlk/mfgorder/line2/rows";
// ── DataMask ──────────────────────────────────────────────
private static readonly DataMask s_SummaryMask = CreateSummaryMask();
private static readonly DataMask s_RowMask = CreateRowMask();
private bool _subscribed;
private bool _disposed;
// ── 공개 API ──────────────────────────────────────────────
/// <summary>가장 최근에 수신한 제조지시 데이터</summary>
public EWLKMfgOrderData CurrentData { get; } = new();
/// <summary>라인 데이터(요약 또는 행)가 갱신될 때 발생 — 메인 스레드에서 호출됩니다.</summary>
public event Action<EWLKMfgOrderData>? OnDataUpdated;
// ── 구독 관리 ──────────────────────────────────────────────
/// <summary>
/// DataRepository 공유 수신기에 4개 토픽을 등록합니다.
/// MqttReceiver.Start()는 EWLKSceneMain에서 호출합니다.
/// </summary>
public void Subscribe()
{
if (_subscribed || _disposed) return;
_subscribed = true;
var receiver = DataRepository.Instance.MqttReceiver;
receiver.Add(BuildSummaryConfig(TopicLine1Summary,
data => UpdateSummary(CurrentData.Line1, data)));
receiver.Add(BuildRowsConfig(TopicLine1Rows,
data => UpdateRows(CurrentData.Line1, data)));
receiver.Add(BuildSummaryConfig(TopicLine2Summary,
data => UpdateSummary(CurrentData.Line2, data)));
receiver.Add(BuildRowsConfig(TopicLine2Rows,
data => UpdateRows(CurrentData.Line2, data)));
}
/// <summary>DataRepository 공유 수신기에서 4개 토픽 구독을 해제합니다.</summary>
public void Unsubscribe()
{
if (!_subscribed) return;
_subscribed = false;
var receiver = DataRepository.Instance.MqttReceiver;
receiver.Remove(TopicLine1Summary);
receiver.Remove(TopicLine1Rows);
receiver.Remove(TopicLine2Summary);
receiver.Remove(TopicLine2Rows);
}
// ── 내부 구현 ──────────────────────────────────────────────
private static DataMask CreateSummaryMask()
{
var mask = new DataMask();
mask["factory_name"] = "";
mask["total_target"] = "";
mask["total_actual"] = "";
mask["total_progress"] = 0.0f;
mask["equip_rate"] = 0.0f;
mask["active_count"] = 0;
mask["total_count"] = 0;
return mask;
}
private static DataMask CreateRowMask()
{
var mask = new DataMask();
mask["equip_name"] = "";
mask["order_no"] = "";
mask["item_code"] = "";
mask["item_name"] = "";
mask["start_time"] = "";
mask["target"] = "";
mask["achievement"] = 0.0f;
mask["status"] = ""; // "running" | "stopped"
return mask;
}
private static MqttSubscriptionConfig BuildSummaryConfig(
string topic, Action<IDataObject?> handler) =>
new MqttSubscriptionConfig(topic)
.SetDataMapper(new DataMapper(s_SummaryMask))
.SetHandler(handler);
private static MqttSubscriptionConfig BuildRowsConfig(
string topic, Action<IDataObject?> handler) =>
new MqttSubscriptionConfig(topic)
.SetDataMapper(new DataMapper(s_RowMask))
.SetHandler(handler);
/// <summary>요약 DataObject를 SummaryData에 반영합니다.</summary>
private void UpdateSummary(EWLKMfgOrderLineData line, IDataObject? data)
{
if (data is not DataObject obj) return;
var s = line.Summary;
s.FactoryName = obj.GetString("factory_name") ?? string.Empty;
s.TotalTarget = obj.GetString("total_target") ?? string.Empty;
s.TotalActual = obj.GetString("total_actual") ?? string.Empty;
s.TotalProgress = obj.GetFloat("total_progress") ?? 0f;
s.EquipRate = obj.GetFloat("equip_rate") ?? 0f;
s.ActiveCount = obj.GetInt("active_count") ?? 0;
s.TotalCount = obj.GetInt("total_count") ?? 0;
OnDataUpdated?.Invoke(CurrentData);
}
/// <summary>
/// 행 DataArray를 파싱하여 Rows를 교체합니다.
/// DataArray는 List&lt;DataObject&gt;를 상속합니다.
/// </summary>
private void UpdateRows(EWLKMfgOrderLineData line, IDataObject? data)
{
if (data is not DataArray arr) return;
line.Rows.Clear();
foreach (var obj in arr)
{
line.Rows.Add(new EWLKMfgOrderRowData
{
EquipName = obj.GetString("equip_name") ?? string.Empty,
OrderNo = obj.GetString("order_no") ?? string.Empty,
ItemCode = obj.GetString("item_code") ?? string.Empty,
ItemName = obj.GetString("item_name") ?? string.Empty,
StartTime = obj.GetString("start_time") ?? string.Empty,
Target = obj.GetString("target") ?? string.Empty,
Achievement = obj.GetFloat("achievement") ?? 0f,
IsStopped = (obj.GetString("status") ?? "") == "stopped",
});
}
OnDataUpdated?.Invoke(CurrentData);
}
// ── IDisposable ────────────────────────────────────────────
public void Dispose()
{
if (_disposed) return;
_disposed = true;
Unsubscribe();
OnDataUpdated = null;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: db642e1ce759b354aa8d0b4dd0f64084

View File

@@ -0,0 +1,38 @@
#nullable enable
using System.Collections.Generic;
namespace UVC.EnglewoodLAB.Data
{
/// <summary>
/// 충포장 설비 팝업 데이터 항목.
/// </summary>
public class PackEquipInfoItem
{
/// <summary>항목 라벨 (예: "토크 L")</summary>
public string Label { get; set; } = string.Empty;
/// <summary>값 (예: "0.0")</summary>
public string Value { get; set; } = "0.0";
public PackEquipInfoItem() { }
public PackEquipInfoItem(string label, string value = "0.0")
{
Label = label;
Value = value;
}
}
/// <summary>
/// 충포장 설비 정보 팝업 데이터.
/// 타이틀 + 데이터 항목 리스트로 구성됩니다.
/// </summary>
public class EWLKPackEquipInfoData
{
/// <summary>설비 타이틀 (예: "Capping", "로봇카토너")</summary>
public string Title { get; set; } = string.Empty;
/// <summary>표시할 데이터 항목들</summary>
public List<PackEquipInfoItem> Items { get; } = new();
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ae76d48b92ccf2a4e86d4e09be3e8933

View File

@@ -0,0 +1,156 @@
#nullable enable
using UnityEngine;
using UnityEngine.UIElements;
using UVC.EnglewoodLAB.Data;
using UVC.EnglewoodLAB.UIToolkit;
namespace UVC.EnglewoodLAB
{
/// <summary>
/// 3D 오브젝트 위에 설비 정보 팝업을 표시하는 트래커.
/// 이 컴포넌트를 설비 오브젝트(예: Cube)에 부착하고,
/// Inspector에서 UIDocument(DynamicUI)를 연결합니다.
/// </summary>
public class EWLKManuEquipInfoTracker : MonoBehaviour
{
[Header("UI")]
[Tooltip("팝업을 표시할 UIDocument (DynamicUI 권장)")]
[SerializeField] private UIDocument? uiDocument;
[Header("설비 데이터")]
[Tooltip("설비명")]
[SerializeField] private string equipmentName = "M001";
[Tooltip("설비 상태")]
[SerializeField] private EquipmentStatus equipmentStatus = EquipmentStatus.Running;
[Header("위치 설정")]
[Tooltip("오브젝트 상단으로부터 Y 오프셋 (월드 단위)")]
[SerializeField] private float worldYOffset = 1.0f;
// ── 내부 상태 ─────────────────────────────────
private EWLKManuEquipInfoPopup? _popup;
private EWLKManuEquipInfoData _data = new();
private Camera? _mainCamera;
private VisualElement? _root;
private bool _attached;
// ────────────────────────────────────────────
private void OnEnable()
{
_mainCamera = Camera.main;
if (uiDocument == null)
{
Debug.LogWarning($"[EWLKManuEquipInfoTracker] {name}: UIDocument가 할당되지 않았습니다.");
return;
}
// rootVisualElement가 아직 준비되지 않았을 수 있으므로 대기
if (uiDocument.rootVisualElement != null)
AttachPopup();
else
uiDocument.rootVisualElement?.RegisterCallback<AttachToPanelEvent>(_ => AttachPopup());
}
private void Start()
{
// OnEnable에서 root가 없었던 경우 Start에서 재시도
if (!_attached && uiDocument != null && uiDocument.rootVisualElement != null)
AttachPopup();
}
private void OnDisable()
{
DetachPopup();
}
private void LateUpdate()
{
if (_popup == null || _mainCamera == null || !_attached) return;
Vector3 worldPos = transform.position + Vector3.up * worldYOffset;
// 카메라 뒤에 있으면 숨김
Vector3 toTarget = (worldPos - _mainCamera.transform.position).normalized;
if (Vector3.Dot(_mainCamera.transform.forward, toTarget) <= 0)
{
_popup.style.display = DisplayStyle.None;
return;
}
_popup.style.display = DisplayStyle.Flex;
// 월드 → 스크린 → 패널 좌표 변환
Vector3 screenPos = _mainCamera.WorldToScreenPoint(worldPos);
// UI Toolkit: 좌상단 원점, Y 하향
Vector2 flipped = new Vector2(screenPos.x, Screen.height - screenPos.y);
if (_popup.panel != null)
{
Vector2 panelPos = RuntimePanelUtils.ScreenToPanel(_popup.panel, flipped);
// 팝업 중앙이 오브젝트 위에 오도록 보정
_popup.style.left = panelPos.x;
_popup.style.top = panelPos.y;
_popup.style.translate = new Translate(Length.Percent(-50), Length.Percent(-100));
}
}
// ── 팝업 부착/해제 ─────────────────────────────
private void AttachPopup()
{
if (_attached) return;
_root = uiDocument?.rootVisualElement;
if (_root == null) return;
_popup = new EWLKManuEquipInfoPopup();
ApplyData();
_root.Add(_popup);
_attached = true;
}
private void DetachPopup()
{
if (_popup != null && _root != null)
{
_root.Remove(_popup);
}
_popup = null;
_attached = false;
}
// ── 데이터 적용 ─────────────────────────────────
private void ApplyData()
{
_data.EquipmentName = equipmentName;
_data.Status = equipmentStatus;
_popup?.SetData(_data);
}
/// <summary>
/// 외부에서 설비 데이터를 업데이트합니다.
/// </summary>
public void UpdateData(EWLKManuEquipInfoData data)
{
_data = data;
_popup?.SetData(data);
}
#if UNITY_EDITOR
/// <summary>
/// Inspector 값 변경 시 실시간 반영 (에디터 전용).
/// </summary>
private void OnValidate()
{
if (_popup != null && Application.isPlaying)
ApplyData();
}
#endif
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 06942d388a19bcf458c4f333f1afc27a

View File

@@ -0,0 +1,174 @@
#nullable enable
using System;
using UnityEngine;
using UnityEngine.UIElements;
using UVC.EnglewoodLAB.Data;
using UVC.EnglewoodLAB.UIToolkit;
namespace UVC.EnglewoodLAB
{
/// <summary>
/// 설비 오브젝트(예: Cube)에 부착하여 클릭 시 상세 패널을 표시합니다.
/// 선택 시 파란 아웃라인을 표시하고, 선택 해제 시 모두 제거합니다.
/// </summary>
[RequireComponent(typeof(Collider))]
public class EWLKManuEquipSelectHandler : MonoBehaviour
{
[Header("UI")]
[Tooltip("패널을 표시할 UIDocument (DynamicUI 권장)")]
[SerializeField] private UIDocument? uiDocument;
[Header("아웃라인")]
[Tooltip("아웃라인 두께 (스케일 배율)")]
[SerializeField] private float outlineScale = 1.05f;
[Tooltip("아웃라인 색상")]
[SerializeField] private Color outlineColor = new(0.2f, 0.5f, 1f, 0.4f);
// ── 상세 패널 활성 상태 (다른 핸들러에서 클릭 차단용) ──
/// <summary>상세 패널이 열려 있는지 여부</summary>
public static bool IsPanelActive { get; private set; }
// ── 내부 상태 ─────────────────────────────────
private EWLKManuEquipDetailPanel? _panel;
private EWLKManuEquipDetailData _data = new();
private GameObject? _outlineObject;
private bool _selected;
private VisualElement? _root;
// ────────────────────────────────────────────
/// <summary>
/// 외부에서 표시할 데이터를 설정합니다.
/// </summary>
public void SetData(EWLKManuEquipDetailData data)
{
_data = data;
_panel?.SetData(data);
}
// ── Unity 생명주기 ──────────────────────────────
private void OnDestroy()
{
Deselect();
DestroyOutlineObject();
}
/// <summary>
/// 클릭 감지. Collider 필수.
/// </summary>
private void OnMouseDown()
{
if (_selected) return;
if (EWLKPackEquipInfoTracker.IsPanelActive) return;
Select();
}
// ── 선택/해제 ──────────────────────────────────
/// <summary>오브젝트를 선택합니다.</summary>
public void Select()
{
if (_selected) return;
_selected = true;
IsPanelActive = true;
ShowOutline();
ShowPanel();
}
/// <summary>오브젝트 선택을 해제합니다.</summary>
public void Deselect()
{
if (!_selected) return;
_selected = false;
IsPanelActive = false;
HideOutline();
HidePanel();
}
// ── 아웃라인 ───────────────────────────────────
private void ShowOutline()
{
if (_outlineObject == null)
CreateOutlineObject();
if (_outlineObject != null)
_outlineObject.SetActive(true);
}
private void HideOutline()
{
if (_outlineObject != null)
_outlineObject.SetActive(false);
}
private void CreateOutlineObject()
{
var meshFilter = GetComponent<MeshFilter>();
if (meshFilter == null || meshFilter.sharedMesh == null) return;
_outlineObject = new GameObject("_Outline");
_outlineObject.transform.SetParent(transform, false);
_outlineObject.transform.localScale = Vector3.one * outlineScale;
// 메시 복제
var outlineMF = _outlineObject.AddComponent<MeshFilter>();
outlineMF.sharedMesh = meshFilter.sharedMesh;
// 반투명 파란 머티리얼
var mat = new Material(Shader.Find("Sprites/Default"));
mat.color = outlineColor;
mat.renderQueue = 3000;
var renderer = _outlineObject.AddComponent<MeshRenderer>();
renderer.material = mat;
renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
renderer.receiveShadows = false;
_outlineObject.SetActive(false);
}
private void DestroyOutlineObject()
{
if (_outlineObject != null)
{
Destroy(_outlineObject);
_outlineObject = null;
}
}
// ── 패널 ───────────────────────────────────────
private void ShowPanel()
{
if (uiDocument == null)
{
Debug.LogWarning($"[EWLKManuEquipSelectHandler] {name}: UIDocument가 할당되지 않았습니다.");
return;
}
_root = uiDocument.rootVisualElement;
if (_root == null) return;
_panel = new EWLKManuEquipDetailPanel();
_panel.SetData(_data);
_panel.OnDeselectClicked += Deselect;
_root.Add(_panel);
}
private void HidePanel()
{
if (_panel != null)
{
_panel.OnDeselectClicked -= Deselect;
_root?.Remove(_panel);
_panel.Dispose();
_panel = null;
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 520526319be9b2d4985315191c400d59

View File

@@ -0,0 +1,319 @@
#nullable enable
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
using UVC.EnglewoodLAB.Data;
using UVC.EnglewoodLAB.UIToolkit;
namespace UVC.EnglewoodLAB
{
/// <summary>
/// 충포장 설비 유형 열거형.
/// </summary>
public enum PackEquipType
{
/// <summary>Capping 설비 (클릭 시 상세 패널 표시)</summary>
Capping,
/// <summary>로봇카토너</summary>
RobotCartoner,
/// <summary>적재로봇</summary>
LoadingRobot,
}
/// <summary>
/// 3D 오브젝트 위에 충포장 설비 정보 팝업을 표시하는 트래커.
/// Capping 설비는 클릭 시 상세 패널도 표시합니다.
/// </summary>
[RequireComponent(typeof(Collider))]
public class EWLKPackEquipInfoTracker : MonoBehaviour
{
[Header("UI")]
[Tooltip("팝업을 표시할 UIDocument (DynamicUI 권장)")]
[SerializeField] private UIDocument? uiDocument;
[Header("설비 설정")]
[Tooltip("충포장 설비 유형")]
[SerializeField] private PackEquipType equipType = PackEquipType.Capping;
[Header("위치 설정")]
[Tooltip("오브젝트 상단으로부터 Y 오프셋 (월드 단위)")]
[SerializeField] private float worldYOffset = 1.0f;
[Header("아웃라인")]
[Tooltip("아웃라인 두께 (스케일 배율)")]
[SerializeField] private float outlineScale = 1.05f;
[Tooltip("아웃라인 색상")]
[SerializeField] private Color outlineColor = new(0.2f, 0.5f, 1f, 0.4f);
// ── 내부 상태 ─────────────────────────────────
private EWLKPackEquipInfoPopup? _popup;
private EWLKPackEquipInfoData _popupData = new();
private Camera? _mainCamera;
private VisualElement? _root;
private bool _popupAttached;
// ── 상세 패널 활성 상태 (다른 핸들러에서 클릭 차단용) ──
/// <summary>Capping 상세 패널이 열려 있는지 여부</summary>
public static bool IsPanelActive { get; private set; }
// Capping 전용
private EWLKCappingDetailPanel? _detailPanel;
private EWLKCappingDetailData _cappingData = new();
private GameObject? _outlineObject;
private bool _selected;
// ────────────────────────────────────────────
private void OnEnable()
{
_mainCamera = Camera.main;
InitPopupData();
if (uiDocument == null)
{
Debug.LogWarning($"[EWLKPackEquipInfoTracker] {name}: UIDocument가 할당되지 않았습니다.");
return;
}
if (uiDocument.rootVisualElement != null)
AttachPopup();
else
uiDocument.rootVisualElement?.RegisterCallback<AttachToPanelEvent>(_ => AttachPopup());
}
private void Start()
{
if (!_popupAttached && uiDocument != null && uiDocument.rootVisualElement != null)
AttachPopup();
}
private void OnDisable()
{
Deselect();
DetachPopup();
}
private void OnDestroy()
{
DestroyOutlineObject();
}
private void LateUpdate()
{
if (_popup == null || _mainCamera == null || !_popupAttached) return;
Vector3 worldPos = transform.position + Vector3.up * worldYOffset;
// 카메라 뒤에 있으면 숨김
Vector3 toTarget = (worldPos - _mainCamera.transform.position).normalized;
if (Vector3.Dot(_mainCamera.transform.forward, toTarget) <= 0)
{
_popup.style.display = DisplayStyle.None;
return;
}
_popup.style.display = DisplayStyle.Flex;
// 월드 → 스크린 → 패널 좌표 변환
Vector3 screenPos = _mainCamera.WorldToScreenPoint(worldPos);
Vector2 flipped = new Vector2(screenPos.x, Screen.height - screenPos.y);
if (_popup.panel != null)
{
Vector2 panelPos = RuntimePanelUtils.ScreenToPanel(_popup.panel, flipped);
_popup.style.left = panelPos.x;
_popup.style.top = panelPos.y;
_popup.style.translate = new Translate(Length.Percent(-50), Length.Percent(-100));
}
}
// ── 클릭 (Capping만 상세 패널 표시) ──────────────
private void OnMouseDown()
{
if (equipType != PackEquipType.Capping) return;
if (_selected) return;
if (EWLKManuEquipSelectHandler.IsPanelActive) return;
Select();
}
// ── 선택/해제 ──────────────────────────────────
/// <summary>Capping 설비를 선택합니다.</summary>
public void Select()
{
if (_selected) return;
_selected = true;
IsPanelActive = true;
ShowOutline();
ShowDetailPanel();
}
/// <summary>선택을 해제합니다.</summary>
public void Deselect()
{
if (!_selected) return;
_selected = false;
IsPanelActive = false;
HideOutline();
HideDetailPanel();
}
// ── 팝업 데이터 초기화 ────────────────────────────
private void InitPopupData()
{
_popupData = new EWLKPackEquipInfoData();
switch (equipType)
{
case PackEquipType.Capping:
_popupData.Title = "Capping";
_popupData.Items.Add(new PackEquipInfoItem("토크 L:", "0.0"));
_popupData.Items.Add(new PackEquipInfoItem("토크 R:", "0.0"));
break;
case PackEquipType.RobotCartoner:
_popupData.Title = "로봇카토너";
_popupData.Items.Add(new PackEquipInfoItem("RPM", "0.0"));
break;
case PackEquipType.LoadingRobot:
_popupData.Title = "적재로봇";
_popupData.Items.Add(new PackEquipInfoItem("적재완료 수량", "0.0"));
break;
}
}
// ── 팝업 부착/해제 ─────────────────────────────
private void AttachPopup()
{
if (_popupAttached) return;
_root = uiDocument?.rootVisualElement;
if (_root == null) return;
_popup = new EWLKPackEquipInfoPopup();
_popup.SetData(_popupData);
_root.Add(_popup);
_popupAttached = true;
}
private void DetachPopup()
{
if (_popup != null && _root != null)
_root.Remove(_popup);
_popup = null;
_popupAttached = false;
}
// ── 아웃라인 ───────────────────────────────────
private void ShowOutline()
{
if (_outlineObject == null)
CreateOutlineObject();
if (_outlineObject != null)
_outlineObject.SetActive(true);
}
private void HideOutline()
{
if (_outlineObject != null)
_outlineObject.SetActive(false);
}
private void CreateOutlineObject()
{
var meshFilter = GetComponent<MeshFilter>();
if (meshFilter == null || meshFilter.sharedMesh == null) return;
_outlineObject = new GameObject("_Outline");
_outlineObject.transform.SetParent(transform, false);
_outlineObject.transform.localScale = Vector3.one * outlineScale;
var outlineMF = _outlineObject.AddComponent<MeshFilter>();
outlineMF.sharedMesh = meshFilter.sharedMesh;
var mat = new Material(Shader.Find("Sprites/Default"));
mat.color = outlineColor;
mat.renderQueue = 3000;
var renderer = _outlineObject.AddComponent<MeshRenderer>();
renderer.material = mat;
renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
renderer.receiveShadows = false;
_outlineObject.SetActive(false);
}
private void DestroyOutlineObject()
{
if (_outlineObject != null)
{
Destroy(_outlineObject);
_outlineObject = null;
}
}
// ── 상세 패널 (Capping 전용) ──────────────────────
private void ShowDetailPanel()
{
if (_root == null) return;
_detailPanel = new EWLKCappingDetailPanel();
_detailPanel.SetData(_cappingData);
_detailPanel.OnDeselectClicked += Deselect;
_root.Add(_detailPanel);
}
private void HideDetailPanel()
{
if (_detailPanel != null)
{
_detailPanel.OnDeselectClicked -= Deselect;
_root?.Remove(_detailPanel);
_detailPanel.Dispose();
_detailPanel = null;
}
}
// ── 외부 API ───────────────────────────────────
/// <summary>
/// 팝업 데이터를 업데이트합니다.
/// </summary>
public void UpdatePopupData(EWLKPackEquipInfoData data)
{
_popupData = data;
_popup?.SetData(data);
}
/// <summary>
/// Capping 상세 패널 데이터를 업데이트합니다.
/// </summary>
public void UpdateCappingData(EWLKCappingDetailData data)
{
_cappingData = data;
_detailPanel?.SetData(data);
}
#if UNITY_EDITOR
private void OnValidate()
{
if (_popup != null && Application.isPlaying)
{
InitPopupData();
_popup.SetData(_popupData);
}
}
#endif
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 95cc4aa0987ca5645bae8bdaf4dd0a28

View File

@@ -53,10 +53,14 @@ namespace UVC.EnglewoodLAB
Injector.RegisterInstance(new ModalUIDocument(modalUI), ServiceLifetime.Scene);
}
// OverView MQTT 서비스 등록 (토픽 구독 — 브로커 연결은 EWLKSceneMain.Initialize()에서)
// MQTT 서비스 등록 (토픽 구독 — 브로커 연결은 EWLKSceneMain.Initialize()에서)
var overViewMqtt = new EWLKOverViewMqttService();
overViewMqtt.Subscribe();
Injector.RegisterInstance<EWLKOverViewMqttService>(overViewMqtt, ServiceLifetime.Scene);
var mfgOrderMqtt = new EWLKMfgOrderMqttService();
mfgOrderMqtt.Subscribe();
Injector.RegisterInstance<EWLKMfgOrderMqttService>(mfgOrderMqtt, ServiceLifetime.Scene);
}
/// <summary>

View File

@@ -23,7 +23,8 @@ namespace UVC.EnglewoodLAB
[Inject] private DynamicUIDocument? _dynamicUIDoc;
[Inject] private ModalUIDocument? _modalUIDoc;
[Inject] private EWLKNavSideBar? _navSideBar;
[Inject] private EWLKOverViewMqttService? _overViewMqtt;
[Inject] private EWLKOverViewMqttService? _overViewMqtt;
[Inject] private EWLKMfgOrderMqttService? _mfgOrderMqtt;
// ── UIDocument 편의 접근자 ────────────────────────
private UIDocument? StaticUI => _staticUIDoc?.Value;
@@ -61,6 +62,11 @@ namespace UVC.EnglewoodLAB
private async UniTask InitializeAsync()
{
await InjectorAppContext.Instance.WaitForInitializationAsync();
// PerformSceneInjection() 완료까지 대기 (로딩씬 → 메인씬 전환 시 필요)
if (InjectorSceneContext.Instance != null)
await InjectorSceneContext.Instance.WaitForInitializationAsync();
Initialize();
}
@@ -110,6 +116,10 @@ namespace UVC.EnglewoodLAB
ShowOverViewModalAsync()
.Forget(ex => Debug.LogError(ex));
break;
case 2: // 제조지시현황
ShowMfgOrderModalAsync()
.Forget(ex => Debug.LogError(ex));
break;
default:
// TODO: 인덱스별 콘텐츠 뷰(DynamicUI) 전환 구현 예정
Debug.Log($"[EWLKSceneMain] 메뉴 {index} 선택됨");
@@ -147,5 +157,32 @@ namespace UVC.EnglewoodLAB
// 메뉴 선택 상태 해제 (모달이 닫히면 활성화 항목 없음)
_navSideBar?.SetActiveItem(-1);
}
// ── 제조지시현황 모달 ─────────────────────────────
/// <summary>
/// 제조지시현황 모달을 열고 MQTT 실시간 데이터를 바인딩합니다.
/// 모달이 닫히면 이벤트 바인딩을 해제합니다.
/// </summary>
private async UniTask ShowMfgOrderModalAsync()
{
var content = new EWLKMfgOrderModalContent();
if (_mfgOrderMqtt != null)
{
content.UpdateData(_mfgOrderMqtt.CurrentData);
_mfgOrderMqtt.OnDataUpdated += content.UpdateData;
}
var modal = UTKModal.Create("제조지시현황", UTKModal.ModalSize.Large);
modal.Add(content);
await modal.ShowAsync();
if (_mfgOrderMqtt != null)
_mfgOrderMqtt.OnDataUpdated -= content.UpdateData;
content.Dispose();
_navSideBar?.SetActiveItem(-1);
}
}
}

View File

@@ -0,0 +1,197 @@
#nullable enable
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
using UVC.EnglewoodLAB.Data;
namespace UVC.EnglewoodLAB.UIToolkit
{
/// <summary>
/// Capping 상세 패널.
/// 설비 정보 + 가동 토글(읽기전용) + 구분/측정/설정 테이블 + 선택해제 버튼.
/// </summary>
[UxmlElement]
public partial class EWLKCappingDetailPanel : VisualElement, IDisposable
{
private const string UssPath = "EWLK/UIToolkit/Main/EWLKCappingDetailPanelUss";
/// <summary>선택 해제 버튼 클릭 시 호출</summary>
public event Action? OnDeselectClicked;
// ── 캐시 ──────────────────────────────────────────
private readonly Label _equipNameVal;
private readonly Label _runningLabel;
private readonly VisualElement _toggleTrack;
private readonly VisualElement _toggleThumb;
// 구분/측정/설정 테이블 (5행)
private readonly List<(Label category, Label measured, Label setting)> _msRows = new();
// ────────────────────────────────────────────────
public EWLKCappingDetailPanel()
{
var uss = Resources.Load<StyleSheet>(UssPath);
if (uss != null) styleSheets.Add(uss);
AddToClassList("ewlk-capping");
style.position = Position.Absolute;
style.left = 0;
style.top = 0;
style.right = 0;
style.bottom = 0;
// ── 메인 컨테이너 (우측 정렬) ─────────────────
var container = new VisualElement();
container.AddToClassList("ewlk-capping__container");
// 1) 설비 정보 헤더 + 가동 토글
var infoHeader = new VisualElement();
infoHeader.AddToClassList("ewlk-capping__info-header");
var headerLeft = new VisualElement();
headerLeft.AddToClassList("ewlk-capping__info-header-left");
var headerTitle = new Label("설비 정보");
headerTitle.AddToClassList("ewlk-capping__info-title");
headerLeft.Add(headerTitle);
_equipNameVal = new Label("-");
_equipNameVal.AddToClassList("ewlk-capping__equip-name");
headerLeft.Add(_equipNameVal);
infoHeader.Add(headerLeft);
// 가동/비가동 토글 (읽기 전용)
var toggleWrap = new VisualElement();
toggleWrap.AddToClassList("ewlk-capping__toggle-wrap");
_runningLabel = new Label("비가동");
_runningLabel.AddToClassList("ewlk-capping__toggle-label");
toggleWrap.Add(_runningLabel);
var toggleContainer = new VisualElement();
toggleContainer.AddToClassList("ewlk-capping__toggle");
_toggleTrack = new VisualElement();
_toggleTrack.AddToClassList("ewlk-capping__toggle-track");
_toggleTrack.AddToClassList("ewlk-capping__toggle-track--off");
_toggleThumb = new VisualElement();
_toggleThumb.AddToClassList("ewlk-capping__toggle-thumb");
_toggleTrack.Add(_toggleThumb);
toggleContainer.Add(_toggleTrack);
toggleWrap.Add(toggleContainer);
infoHeader.Add(toggleWrap);
container.Add(infoHeader);
// 2) 구분/측정/설정 테이블
var table = new VisualElement();
table.AddToClassList("ewlk-capping__table");
// 테이블 헤더
table.Add(MakeTableHeader("구분", "측정", "설정"));
// 5행
for (int i = 0; i < 5; i++)
{
var row = MakeMeasureSettingRow(out var cat, out var meas, out var set);
table.Add(row);
_msRows.Add((cat, meas, set));
}
container.Add(table);
Add(container);
// 3) 선택 해제 버튼
var deselectBtn = new Button(() => OnDeselectClicked?.Invoke()) { text = "선택 해제" };
deselectBtn.AddToClassList("ewlk-capping__deselect-btn");
Add(deselectBtn);
// 기본 데이터 초기화
SetData(new EWLKCappingDetailData());
}
// ── 공개 API ────────────────────────────────────────
/// <summary>
/// 데이터를 바인딩합니다.
/// </summary>
public void SetData(EWLKCappingDetailData data)
{
// 설비명
_equipNameVal.text = data.EquipmentName;
// 가동 토글
UpdateToggle(data.IsRunning);
// 테이블
for (int i = 0; i < _msRows.Count && i < data.MeasureSettingRows.Count; i++)
{
_msRows[i].category.text = data.MeasureSettingRows[i].Category;
_msRows[i].measured.text = data.MeasureSettingRows[i].Measured;
_msRows[i].setting.text = data.MeasureSettingRows[i].Setting;
}
}
public void Dispose() { }
// ── 토글 ──────────────────────────────────────────
private void UpdateToggle(bool isRunning)
{
_runningLabel.text = isRunning ? "가동중" : "비가동";
_toggleTrack.EnableInClassList("ewlk-capping__toggle-track--on", isRunning);
_toggleTrack.EnableInClassList("ewlk-capping__toggle-track--off", !isRunning);
_toggleThumb.EnableInClassList("ewlk-capping__toggle-thumb--on", isRunning);
_toggleThumb.EnableInClassList("ewlk-capping__toggle-thumb--off", !isRunning);
}
// ── 빌더 헬퍼 ──────────────────────────────────────
/// <summary>테이블 헤더 생성</summary>
private static VisualElement MakeTableHeader(params string[] columns)
{
var row = new VisualElement();
row.AddToClassList("ewlk-capping__table-header");
foreach (var col in columns)
{
var lbl = new Label(col);
lbl.AddToClassList("ewlk-capping__table-header-cell");
row.Add(lbl);
}
return row;
}
/// <summary>구분/측정/설정 행 생성</summary>
private static VisualElement MakeMeasureSettingRow(
out Label category, out Label measured, out Label setting)
{
var row = new VisualElement();
row.AddToClassList("ewlk-capping__table-row");
category = new Label("-");
category.AddToClassList("ewlk-capping__table-cell");
category.AddToClassList("ewlk-capping__table-cell--category");
row.Add(category);
measured = new Label("-");
measured.AddToClassList("ewlk-capping__table-cell");
measured.AddToClassList("ewlk-capping__table-cell--measured");
row.Add(measured);
setting = new Label("-");
setting.AddToClassList("ewlk-capping__table-cell");
setting.AddToClassList("ewlk-capping__table-cell--setting");
row.Add(setting);
return row;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 872841a7d299e2741886b1578ad6dcaf

View File

@@ -0,0 +1,453 @@
#nullable enable
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
using UVC.EnglewoodLAB.Data;
namespace UVC.EnglewoodLAB.UIToolkit
{
/// <summary>
/// 설비 상세 패널. 좌측(기본정보·품질·운영) + 우측(메인 믹서) + 선택해제 버튼.
/// DynamicUI rootVisualElement에 추가하여 사용합니다.
/// </summary>
[UxmlElement]
public partial class EWLKManuEquipDetailPanel : VisualElement, IDisposable
{
private const string UssPath = "EWLK/UIToolkit/Main/EWLKManuEquipDetailPanelUss";
/// <summary>선택 해제 버튼 클릭 시 호출</summary>
public event Action? OnDeselectClicked;
// ── 값 라벨 캐시 (데이터 바인딩용) ──────────────
// 설비 기본 정보
private readonly Label _equipNameVal;
private readonly Label _capacityVal;
private readonly Label _locationVal;
// 품질 정보
private readonly List<Label> _qualityVals = new();
// 설비 운영 정보 — 공정
private readonly Label _processProductNoVal;
private readonly Label _processProductNameVal;
private readonly Label _processLotVal;
private readonly Label _processQtyVal;
private readonly Label _processWorkerVal;
// 설비 운영 정보 — 상별 용해
private readonly Label _meltProductNoVal;
private readonly Label _meltProductNameVal;
private readonly Label _meltLotVal;
private readonly Label _meltQtyVal;
private readonly Label _meltWorkerVal;
// 메인 믹서 — 측정값 라벨 (검정)
private readonly Label _jacketTempVal;
private readonly Label _upperTempVal;
private readonly Label _tempMeasuredVal;
private readonly Label _homoMeasuredVal;
private readonly Label _padMeasuredVal;
private readonly Label _scrMeasuredVal;
private readonly Label _pressureMeasuredVal;
// 메인 믹서 — 세팅값 라벨 (파랑)
private readonly Label _tempSettingVal;
private readonly Label _heatingVal;
private readonly Label _coolingVal;
private readonly Label _homoSettingVal;
private readonly Label _padSettingVal;
private readonly Label _scrSettingVal;
private readonly Label _pressureSettingVal;
private readonly List<Label> _runStatusLabels = new();
// ────────────────────────────────────────────────
public EWLKManuEquipDetailPanel()
{
var uss = Resources.Load<StyleSheet>(UssPath);
if (uss != null) styleSheets.Add(uss);
AddToClassList("ewlk-detail");
style.position = Position.Absolute;
style.left = 0;
style.top = 0;
style.right = 0;
style.bottom = 0;
// ── 좌측 컬럼 ──────────────────────────────────
var leftCol = new VisualElement();
leftCol.AddToClassList("ewlk-detail__left");
// 1) 설비 기본 정보
var basicPanel = MakeSection("설비 기본 정보");
basicPanel.Add(MakeRow("설비명", out _equipNameVal));
basicPanel.Add(MakeRow("설비 CAPA", out _capacityVal));
basicPanel.Add(MakeRow("설비 위치", out _locationVal));
basicPanel.Add(MakeDataTypeRow());
leftCol.Add(basicPanel);
// 2) 품질 정보
var qualityPanel = MakeSection("품질 정보");
for (int i = 0; i < 2; i++)
{
Label val;
qualityPanel.Add(MakeRow("목표 정보", out val));
_qualityVals.Add(val);
}
leftCol.Add(qualityPanel);
// 3) 설비 운영 정보 (ScrollView)
var operationPanel = MakeSection("설비 운영 정보");
operationPanel.AddToClassList("ewlk-detail__section--stretch");
var operationScroll = new ScrollView(ScrollViewMode.Vertical);
operationScroll.AddToClassList("ewlk-detail__scroll");
// 공정 정보
operationScroll.Add(MakeSubHeader("공정 정보"));
operationScroll.Add(MakeRow("품번", out _processProductNoVal));
operationScroll.Add(MakeRow("품명", out _processProductNameVal));
operationScroll.Add(MakeRow("로트(LOT)", out _processLotVal));
operationScroll.Add(MakeRow("제조량", out _processQtyVal));
operationScroll.Add(MakeRow("작업자", out _processWorkerVal));
// 상별 용해
operationScroll.Add(MakeSubHeader("상별 용해"));
operationScroll.Add(MakeRow("품번", out _meltProductNoVal));
operationScroll.Add(MakeRow("품명", out _meltProductNameVal));
operationScroll.Add(MakeRow("로트(LOT)", out _meltLotVal));
operationScroll.Add(MakeRow("제조량", out _meltQtyVal));
operationScroll.Add(MakeRow("작업자", out _meltWorkerVal));
operationPanel.Add(operationScroll);
leftCol.Add(operationPanel);
Add(leftCol);
// ── 우측 컬럼 ──────────────────────────────────
var rightCol = new VisualElement();
rightCol.AddToClassList("ewlk-detail__right");
var mixerPanel = MakeSection("메인 믹서");
var mixerScroll = new ScrollView(ScrollViewMode.Vertical);
mixerScroll.AddToClassList("ewlk-detail__scroll");
mixerScroll.Add(MakeMeasuredRow("자켓 온도", "°C", out _jacketTempVal));
mixerScroll.Add(MakeMeasuredRow("상부 온도", "°C", out _upperTempVal));
mixerScroll.Add(MakeDualRow("온도", "°C", out _tempMeasuredVal, out _tempSettingVal));
mixerScroll.Add(MakeSettingRow("가열온도 설정", "°C", out _heatingVal, starred: true));
mixerScroll.Add(MakeSettingRow("냉각온도 설정", "°C", out _coolingVal, starred: true));
mixerScroll.Add(MakeDualRow("HOMO", "RPM", out _homoMeasuredVal, out _homoSettingVal, starred: true));
mixerScroll.Add(MakeDualRow("PAD", "RPM", out _padMeasuredVal, out _padSettingVal, starred: true));
mixerScroll.Add(MakeDualRow("SCR", "RPM", out _scrMeasuredVal, out _scrSettingVal, starred: true));
mixerScroll.Add(MakeDualRow("설비내 압력", "mmHg", out _pressureMeasuredVal, out _pressureSettingVal));
// 가동 상태 쌍 행
var statusNames = new[]
{
("진공시작", "진공파기"),
("배출시작", "탱크벨트"),
};
foreach (var (left, right) in statusNames)
{
Label lVal, rVal;
mixerScroll.Add(MakeStatusPairRow(left, out lVal, right, out rVal));
_runStatusLabels.Add(lVal);
_runStatusLabels.Add(rVal);
}
mixerPanel.Add(mixerScroll);
rightCol.Add(mixerPanel);
Add(rightCol);
// ── 선택 해제 버튼 (중앙 하단) ──────────────────
var deselectBtn = new Button(() => OnDeselectClicked?.Invoke()) { text = "선택 해제" };
deselectBtn.AddToClassList("ewlk-detail__deselect-btn");
Add(deselectBtn);
// 기본 데이터로 초기화
SetData(new EWLKManuEquipDetailData());
}
// ── 공개 API ────────────────────────────────────────
/// <summary>
/// 데이터를 바인딩합니다.
/// </summary>
public void SetData(EWLKManuEquipDetailData data)
{
// 설비 기본 정보
_equipNameVal.text = data.BasicInfo.EquipmentName;
_capacityVal.text = data.BasicInfo.Capacity;
_locationVal.text = data.BasicInfo.Location;
// 품질 정보
for (int i = 0; i < _qualityVals.Count && i < data.QualityInfo.Targets.Count; i++)
_qualityVals[i].text = data.QualityInfo.Targets[i].Range;
// 설비 운영 정보 — 공정
var p = data.OperationInfo.ProcessInfo;
_processProductNoVal.text = p.ProductNo;
_processProductNameVal.text = p.ProductName;
_processLotVal.text = p.LotNo;
_processQtyVal.text = p.ProductionQty;
_processWorkerVal.text = p.Worker;
// 설비 운영 정보 — 상별 용해
var m = data.OperationInfo.MeltInfo;
_meltProductNoVal.text = m.ProductNo;
_meltProductNameVal.text = m.ProductName;
_meltLotVal.text = m.LotNo;
_meltQtyVal.text = m.ProductionQty;
_meltWorkerVal.text = m.Worker;
// 메인 믹서
var mx = data.MainMixer;
_jacketTempVal.text = mx.JacketTemp;
_upperTempVal.text = mx.UpperTemp;
_tempMeasuredVal.text = mx.TempMeasured;
_tempSettingVal.text = mx.TempSetting;
_heatingVal.text = mx.HeatingTempSetting;
_coolingVal.text = mx.CoolingTempSetting;
_homoMeasuredVal.text = mx.HomoMeasured;
_homoSettingVal.text = mx.HomoSetting;
_padMeasuredVal.text = mx.PadMeasured;
_padSettingVal.text = mx.PadSetting;
_scrMeasuredVal.text = mx.ScrMeasured;
_scrSettingVal.text = mx.ScrSetting;
_pressureMeasuredVal.text = mx.PressureMeasured;
_pressureSettingVal.text = mx.PressureSetting;
// 가동 상태
for (int i = 0; i < _runStatusLabels.Count && i < mx.RunStatuses.Count; i++)
{
var status = mx.RunStatuses[i];
_runStatusLabels[i].text = status.IsActive ? "가동" : "비가동";
_runStatusLabels[i].EnableInClassList("ewlk-detail__status--active", status.IsActive);
_runStatusLabels[i].EnableInClassList("ewlk-detail__status--inactive", !status.IsActive);
}
}
public void Dispose() { }
// ── 빌더 헬퍼 ──────────────────────────────────────
/// <summary>섹션 패널 생성 (헤더 + 바디 컨테이너)</summary>
private static VisualElement MakeSection(string title)
{
var section = new VisualElement();
section.AddToClassList("ewlk-detail__section");
var header = new Label(title);
header.AddToClassList("ewlk-detail__section-header");
section.Add(header);
return section;
}
/// <summary>서브 헤더 (공정 정보, 상별 용해 등)</summary>
private static VisualElement MakeSubHeader(string title)
{
var header = new Label(title);
header.AddToClassList("ewlk-detail__sub-header");
return header;
}
/// <summary>키-값 행 생성</summary>
private static VisualElement MakeRow(string key, out Label valueLabel)
{
var row = new VisualElement();
row.AddToClassList("ewlk-detail__row");
var keyLbl = new Label(key);
keyLbl.AddToClassList("ewlk-detail__row-key");
row.Add(keyLbl);
valueLabel = new Label("-");
valueLabel.AddToClassList("ewlk-detail__row-value");
row.Add(valueLabel);
return row;
}
/// <summary>키 영역 생성. starred=true이면 빨간 * 를 별도 Label로 추가</summary>
private static VisualElement MakeKeyElement(string key, bool starred = false)
{
var wrap = new VisualElement();
wrap.AddToClassList("ewlk-detail__row-key");
wrap.style.flexDirection = FlexDirection.Row;
var keyLbl = new Label(key);
wrap.Add(keyLbl);
if (starred)
{
var star = new Label("*");
star.AddToClassList("ewlk-detail__key-star");
wrap.Add(star);
}
return wrap;
}
/// <summary>측정값 행 — 값(검정) + 단위(검정)</summary>
private static VisualElement MakeMeasuredRow(string key, string unit, out Label valueLabel)
{
var row = new VisualElement();
row.AddToClassList("ewlk-detail__row");
row.Add(MakeKeyElement(key));
var valueWrap = new VisualElement();
valueWrap.AddToClassList("ewlk-detail__row-value-wrap");
valueLabel = new Label("-");
valueLabel.AddToClassList("ewlk-detail__val--measured");
valueWrap.Add(valueLabel);
var unitLbl = new Label($" ({unit})");
unitLbl.AddToClassList("ewlk-detail__val--unit");
valueWrap.Add(unitLbl);
row.Add(valueWrap);
return row;
}
/// <summary>듀얼 행 — 측정값(검정) / 세팅값(파랑) + 단위(검정)</summary>
private static VisualElement MakeDualRow(
string key, string unit,
out Label measuredLabel, out Label settingLabel,
bool starred = false)
{
var row = new VisualElement();
row.AddToClassList("ewlk-detail__row");
row.Add(MakeKeyElement(key, starred));
var valueWrap = new VisualElement();
valueWrap.AddToClassList("ewlk-detail__row-value-wrap");
measuredLabel = new Label("-");
measuredLabel.AddToClassList("ewlk-detail__val--measured");
valueWrap.Add(measuredLabel);
var slash = new Label("/");
slash.AddToClassList("ewlk-detail__val--separator");
valueWrap.Add(slash);
settingLabel = new Label("-");
settingLabel.AddToClassList("ewlk-detail__val--setting");
valueWrap.Add(settingLabel);
var unitLbl = new Label($" ({unit})");
unitLbl.AddToClassList("ewlk-detail__val--unit");
valueWrap.Add(unitLbl);
row.Add(valueWrap);
return row;
}
/// <summary>세팅 전용 행 — 값(파랑) + 단위(검정)</summary>
private static VisualElement MakeSettingRow(string key, string unit, out Label valueLabel, bool starred = false)
{
var row = new VisualElement();
row.AddToClassList("ewlk-detail__row");
row.Add(MakeKeyElement(key, starred));
var valueWrap = new VisualElement();
valueWrap.AddToClassList("ewlk-detail__row-value-wrap");
valueLabel = new Label("-");
valueLabel.AddToClassList("ewlk-detail__val--setting");
valueWrap.Add(valueLabel);
var unitLbl = new Label($" ({unit})");
unitLbl.AddToClassList("ewlk-detail__val--unit");
valueWrap.Add(unitLbl);
row.Add(valueWrap);
return row;
}
/// <summary>데이터 유형 행 (●측정값 ●세팅값 ●AI예측값)</summary>
private static VisualElement MakeDataTypeRow()
{
var row = new VisualElement();
row.AddToClassList("ewlk-detail__row");
var keyLbl = new Label("데이터 유형");
keyLbl.AddToClassList("ewlk-detail__row-key");
row.Add(keyLbl);
var valueWrap = new VisualElement();
valueWrap.AddToClassList("ewlk-detail__data-type-wrap");
valueWrap.Add(MakeDataTypeDot("측정 값", "ewlk-detail__dot--measured"));
valueWrap.Add(MakeDataTypeDot("세팅 값", "ewlk-detail__dot--setting"));
valueWrap.Add(MakeDataTypeDot("AI 예측 값", "ewlk-detail__dot--predicted"));
row.Add(valueWrap);
return row;
}
private static VisualElement MakeDataTypeDot(string text, string dotClass)
{
var item = new VisualElement();
item.AddToClassList("ewlk-detail__data-type-item");
var dot = new VisualElement();
dot.AddToClassList("ewlk-detail__dot");
dot.AddToClassList(dotClass);
item.Add(dot);
var lbl = new Label(text);
lbl.AddToClassList("ewlk-detail__data-type-label");
item.Add(lbl);
return item;
}
/// <summary>가동 상태 쌍 행 (예: 진공시작 | 진공파기)</summary>
private static VisualElement MakeStatusPairRow(
string leftKey, out Label leftVal,
string rightKey, out Label rightVal)
{
var row = new VisualElement();
row.AddToClassList("ewlk-detail__status-pair");
row.Add(MakeStatusCell(leftKey, out leftVal));
row.Add(MakeStatusCell(rightKey, out rightVal));
return row;
}
/// <summary>가동 상태 단독 행</summary>
private static VisualElement MakeStatusRow(string key, out Label valueLabel)
{
var row = new VisualElement();
row.AddToClassList("ewlk-detail__status-pair");
row.Add(MakeStatusCell(key, out valueLabel));
return row;
}
private static VisualElement MakeStatusCell(string key, out Label valueLabel)
{
var cell = new VisualElement();
cell.AddToClassList("ewlk-detail__status-cell");
var keyLbl = new Label(key);
keyLbl.AddToClassList("ewlk-detail__status-key");
cell.Add(keyLbl);
valueLabel = new Label("비가동");
valueLabel.AddToClassList("ewlk-detail__status-value");
valueLabel.AddToClassList("ewlk-detail__status--inactive");
cell.Add(valueLabel);
return cell;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 48fd515df0ec4be4ea516e678a7e075e

View File

@@ -0,0 +1,61 @@
#nullable enable
using UnityEngine;
using UnityEngine.UIElements;
using UVC.EnglewoodLAB.Data;
namespace UVC.EnglewoodLAB.UIToolkit
{
/// <summary>
/// 설비 정보 팝업 UI 요소.
/// 상태 원(초록/노랑/회색) + 설비명으로 구성됩니다.
/// </summary>
[UxmlElement]
public partial class EWLKManuEquipInfoPopup : VisualElement
{
private const string UssPath = "EWLK/UIToolkit/Main/EWLKManuEquipInfoPopupUss";
private static readonly string ClassRoot = "ewlk-equip-popup";
private static readonly string ClassIndicator = "ewlk-equip-popup__indicator";
private static readonly string ClassLabel = "ewlk-equip-popup__label";
private static readonly string ClassRunning = "ewlk-equip-popup--running";
private static readonly string ClassIdle = "ewlk-equip-popup--idle";
private static readonly string ClassPlannedStop = "ewlk-equip-popup--planned-stop";
private readonly VisualElement _indicator;
private readonly Label _nameLabel;
public EWLKManuEquipInfoPopup()
{
var uss = Resources.Load<StyleSheet>(UssPath);
if (uss != null) styleSheets.Add(uss);
AddToClassList(ClassRoot);
// 상태 표시 원
_indicator = new VisualElement();
_indicator.AddToClassList(ClassIndicator);
Add(_indicator);
// 설비명 라벨
_nameLabel = new Label();
_nameLabel.AddToClassList(ClassLabel);
Add(_nameLabel);
// 절대 위치 (Tracker가 좌표를 설정)
style.position = Position.Absolute;
}
/// <summary>
/// 데이터를 바인딩합니다.
/// </summary>
public void SetData(EWLKManuEquipInfoData data)
{
_nameLabel.text = data.EquipmentName;
// 상태 클래스 토글
_indicator.EnableInClassList(ClassRunning, data.Status == EquipmentStatus.Running);
_indicator.EnableInClassList(ClassIdle, data.Status == EquipmentStatus.Idle);
_indicator.EnableInClassList(ClassPlannedStop, data.Status == EquipmentStatus.PlannedStop);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 70bf7a477fbca2b40a4fd39ec76d3eaa

View File

@@ -0,0 +1,386 @@
#nullable enable
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
using UVC.EnglewoodLAB.Data;
namespace UVC.EnglewoodLAB.UIToolkit
{
/// <summary>
/// 제조지시현황 모달 콘텐츠.
/// 2개 라인 탭, 요약 바, 설비 행 ListView로 구성됩니다.
/// MQTT 데이터가 없는 경우 플레이스홀더 행을 표시합니다.
/// </summary>
[UxmlElement]
public partial class EWLKMfgOrderModalContent : VisualElement, IDisposable
{
private const string UssPath = "EWLK/UIToolkit/Main/EWLKMfgOrderModalContentUss";
// ── 탭 ──────────────────────────────────────────────
private readonly Button _tab1Btn;
private readonly Button _tab2Btn;
// ── 요약 바 ─────────────────────────────────────────
private readonly Label _totalTargetLabel;
private readonly Label _totalActualLabel;
private readonly Label _totalProgressLabel;
private readonly VisualElement _totalProgressFill;
private readonly Label _equipRateLabel;
private readonly VisualElement _equipRateFill;
private readonly Label _countLabel;
// ── 테이블 ───────────────────────────────────────────
private readonly ListView _listView;
// ── 상태 ─────────────────────────────────────────────
private EWLKMfgOrderData? _data;
private EWLKMfgOrderLineData? _currentLine;
private int _activeTabIndex;
private List<EWLKMfgOrderRowData> _displayRows = new();
// ── 플레이스홀더 (MQTT 연결 전 기본 표시용) ──────────
private static readonly List<EWLKMfgOrderRowData> s_PlaceholderRows = CreatePlaceholderRows();
// ────────────────────────────────────────────────────
public EWLKMfgOrderModalContent()
{
styleSheets.Add(Resources.Load<StyleSheet>(UssPath));
AddToClassList("ewlk-mfgorder");
// ── 탭 바 ────────────────────────────────────────
var tabBar = new VisualElement();
tabBar.AddToClassList("ewlk-mfgorder__tab-bar");
_tab1Btn = new Button(() => SelectTab(0)) { text = "라인 1" };
_tab1Btn.AddToClassList("ewlk-mfgorder__tab");
_tab2Btn = new Button(() => SelectTab(1)) { text = "라인 2" };
_tab2Btn.AddToClassList("ewlk-mfgorder__tab");
tabBar.Add(_tab1Btn);
tabBar.Add(_tab2Btn);
Add(tabBar);
// ── 요약 바 ──────────────────────────────────────
var summaryBar = new VisualElement();
summaryBar.AddToClassList("ewlk-mfgorder__summary-bar");
summaryBar.Add(MakeStaticItem("전체 제조 목표량", out _totalTargetLabel));
summaryBar.Add(MakeStaticItem("실제 실적량", out _totalActualLabel));
summaryBar.Add(MakeProgressItem("전체 제조 진척률",
out _totalProgressFill, out _totalProgressLabel));
Add(summaryBar);
// ── 설비가동률 행 ─────────────────────────────────
var equipRow = new VisualElement();
equipRow.AddToClassList("ewlk-mfgorder__equip-row");
var equipTitle = new Label("일 설비가동률(가중대수%)");
equipTitle.AddToClassList("ewlk-mfgorder__equip-title");
equipRow.Add(equipTitle);
var equipBarWrap = new VisualElement();
equipBarWrap.AddToClassList("ewlk-mfgorder__progress-wrap");
_equipRateFill = new VisualElement();
_equipRateFill.AddToClassList("ewlk-mfgorder__progress-fill");
equipBarWrap.Add(_equipRateFill);
equipRow.Add(equipBarWrap);
_equipRateLabel = new Label("-");
_equipRateLabel.AddToClassList("ewlk-mfgorder__equip-rate");
equipRow.Add(_equipRateLabel);
_countLabel = new Label("- / -");
_countLabel.AddToClassList("ewlk-mfgorder__count");
equipRow.Add(_countLabel);
Add(equipRow);
// ── 테이블 헤더 ───────────────────────────────────
Add(BuildTableHeader());
// ── ListView ─────────────────────────────────────
_listView = new ListView
{
makeItem = MakeRowElement,
bindItem = BindRowElement,
fixedItemHeight = 28,
selectionType = SelectionType.None,
};
_listView.AddToClassList("ewlk-mfgorder__list");
Add(_listView);
SetTabActive(0);
// 데이터가 없어도 플레이스홀더로 레이아웃 표시
RefreshView();
}
// ── 공개 API ──────────────────────────────────────────
/// <summary>
/// 데이터를 갱신합니다. MQTT 수신 시 호출됩니다 (메인 스레드 보장).
/// </summary>
public void UpdateData(EWLKMfgOrderData data)
{
_data = data;
// 공장구분이 수신되면 탭 레이블 갱신
if (!string.IsNullOrEmpty(data.Line1.Summary.FactoryName))
_tab1Btn.text = data.Line1.Summary.FactoryName;
if (!string.IsNullOrEmpty(data.Line2.Summary.FactoryName))
_tab2Btn.text = data.Line2.Summary.FactoryName;
_currentLine = _activeTabIndex == 0 ? data.Line1 : data.Line2;
RefreshView();
}
public void Dispose() { }
// ── 탭 선택 ───────────────────────────────────────────
private void SelectTab(int index)
{
_activeTabIndex = index;
SetTabActive(index);
if (_data != null)
_currentLine = index == 0 ? _data.Line1 : _data.Line2;
RefreshView();
}
private void SetTabActive(int index)
{
_tab1Btn.EnableInClassList("ewlk-mfgorder__tab--active", index == 0);
_tab2Btn.EnableInClassList("ewlk-mfgorder__tab--active", index == 1);
}
// ── 뷰 갱신 ───────────────────────────────────────────
private void RefreshView()
{
if (_currentLine == null)
{
// 데이터 없음 — 플레이스홀더 표시
_totalTargetLabel.text = "-";
_totalActualLabel.text = "-";
_totalProgressLabel.text = "-";
_totalProgressFill.style.width = Length.Percent(0f);
_equipRateLabel.text = "-";
_equipRateFill.style.width = Length.Percent(0f);
_countLabel.text = "- / -";
_displayRows = s_PlaceholderRows;
_listView.itemsSource = _displayRows;
_listView.Rebuild();
return;
}
var s = _currentLine.Summary;
_totalTargetLabel.text = Or(s.TotalTarget, "-");
_totalActualLabel.text = Or(s.TotalActual, "-");
_totalProgressLabel.text = $"{s.TotalProgress:F1}%";
_totalProgressFill.style.width =
Length.Percent(Mathf.Clamp(s.TotalProgress, 0f, 100f));
_equipRateLabel.text = $"{s.EquipRate:F1}%";
_equipRateFill.style.width =
Length.Percent(Mathf.Clamp(s.EquipRate, 0f, 100f));
_countLabel.text = $"{s.ActiveCount} / {s.TotalCount}";
_displayRows = _currentLine.Rows;
_listView.itemsSource = _displayRows;
_listView.Rebuild();
}
// ── ListView 바인딩 ───────────────────────────────────
/// <summary>
/// 행 요소를 생성합니다. 모든 셀과 서브 요소를 미리 생성합니다.
/// </summary>
private static VisualElement MakeRowElement()
{
var row = new VisualElement();
row.AddToClassList("ewlk-mfgorder__row");
// 문자열 셀 6개 (설비명~목표량)
var textCols = new[] {
"col-equip", "col-order", "col-item-code",
"col-item-name", "col-time", "col-target",
};
foreach (var cls in textCols)
{
var cell = new VisualElement();
cell.AddToClassList("ewlk-mfgorder__cell");
cell.AddToClassList(cls);
var lbl = new Label();
lbl.AddToClassList("ewlk-mfgorder__cell-label");
cell.Add(lbl);
row.Add(cell);
}
// 달성률 셀 (progress + stopped-label + pct-label)
var achieveCell = new VisualElement();
achieveCell.AddToClassList("ewlk-mfgorder__cell");
achieveCell.AddToClassList("col-achievement");
var barWrap = new VisualElement();
barWrap.name = "bar-wrap";
barWrap.AddToClassList("ewlk-mfgorder__progress-wrap");
var fill = new VisualElement();
fill.name = "achieve-fill";
fill.AddToClassList("ewlk-mfgorder__progress-fill");
barWrap.Add(fill);
var stoppedLbl = new Label("계획 정지");
stoppedLbl.name = "stopped-label";
stoppedLbl.AddToClassList("ewlk-mfgorder__stopped");
var pctLbl = new Label();
pctLbl.name = "pct-label";
pctLbl.AddToClassList("ewlk-mfgorder__pct");
achieveCell.Add(barWrap);
achieveCell.Add(stoppedLbl);
achieveCell.Add(pctLbl);
row.Add(achieveCell);
return row;
}
/// <summary>
/// ListView 바인딩. _displayRows(실데이터 또는 플레이스홀더)를 사용합니다.
/// </summary>
private void BindRowElement(VisualElement element, int index)
{
if (index >= _displayRows.Count) return;
var d = _displayRows[index];
SetLabel(element, "col-equip", d.EquipName);
SetLabel(element, "col-order", d.OrderNo);
SetLabel(element, "col-item-code", d.ItemCode);
SetLabel(element, "col-item-name", d.ItemName);
SetLabel(element, "col-time", d.StartTime);
SetLabel(element, "col-target", d.Target);
var barWrap = element.Q<VisualElement>("bar-wrap");
var fill = element.Q<VisualElement>("achieve-fill");
var stoppedLbl = element.Q<Label>("stopped-label");
var pctLbl = element.Q<Label>("pct-label");
if (d.IsStopped)
{
if (barWrap != null) barWrap.style.display = DisplayStyle.None;
if (stoppedLbl != null) stoppedLbl.style.display = DisplayStyle.Flex;
if (pctLbl != null) pctLbl.style.display = DisplayStyle.None;
}
else
{
float pct = Mathf.Clamp(d.Achievement, 0f, 100f);
if (barWrap != null)
{
barWrap.style.display = DisplayStyle.Flex;
if (fill != null) fill.style.width = Length.Percent(pct);
}
if (stoppedLbl != null) stoppedLbl.style.display = DisplayStyle.None;
if (pctLbl != null)
{
pctLbl.style.display = DisplayStyle.Flex;
pctLbl.text = $"{d.Achievement:F1}%";
}
}
}
// ── 헬퍼 ──────────────────────────────────────────────
private static void SetLabel(VisualElement row, string cellClass, string text)
{
var cell = row.Q(className: cellClass);
var lbl = cell?.Q<Label>();
if (lbl != null) lbl.text = Or(text, "-");
}
private static string Or(string value, string fallback) =>
string.IsNullOrEmpty(value) ? fallback : value;
/// <summary>
/// MQTT 연결 전에 레이아웃을 미리 확인하기 위한 플레이스홀더 행 생성.
/// Achievement = 10f 로 달성률 바 그래프를 기본 표시합니다.
/// </summary>
private static List<EWLKMfgOrderRowData> CreatePlaceholderRows()
{
var rows = new List<EWLKMfgOrderRowData>();
for (int i = 0; i < 5; i++)
{
rows.Add(new EWLKMfgOrderRowData
{
Achievement = 10f,
IsStopped = false,
});
}
return rows;
}
private static VisualElement MakeStaticItem(string title, out Label valueLabel)
{
var wrap = new VisualElement();
wrap.AddToClassList("ewlk-mfgorder__summary-item");
var t = new Label(title);
t.AddToClassList("ewlk-mfgorder__summary-title");
wrap.Add(t);
valueLabel = new Label("-");
valueLabel.AddToClassList("ewlk-mfgorder__summary-value");
wrap.Add(valueLabel);
return wrap;
}
private static VisualElement MakeProgressItem(
string title,
out VisualElement fill,
out Label valueLabel)
{
var wrap = new VisualElement();
wrap.AddToClassList("ewlk-mfgorder__summary-item");
var t = new Label(title);
t.AddToClassList("ewlk-mfgorder__summary-title");
wrap.Add(t);
var barWrap = new VisualElement();
barWrap.AddToClassList("ewlk-mfgorder__progress-wrap");
fill = new VisualElement();
fill.AddToClassList("ewlk-mfgorder__progress-fill");
barWrap.Add(fill);
wrap.Add(barWrap);
valueLabel = new Label("-");
valueLabel.AddToClassList("ewlk-mfgorder__summary-value");
wrap.Add(valueLabel);
return wrap;
}
private static VisualElement BuildTableHeader()
{
var header = new VisualElement();
header.AddToClassList("ewlk-mfgorder__header");
var cols = new[] {
("설비명", "col-equip"),
("지시번호", "col-order"),
("품목코드", "col-item-code"),
("품목명", "col-item-name"),
("시작시간", "col-time"),
("목표량", "col-target"),
("달성률", "col-achievement"),
};
foreach (var (text, cls) in cols)
{
var cell = new Label(text);
cell.AddToClassList("ewlk-mfgorder__header-cell");
cell.AddToClassList(cls);
header.Add(cell);
}
return header;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: aa9eed65d35268948b6b1a2cdd5f87f9

View File

@@ -0,0 +1,95 @@
#nullable enable
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UIElements;
using UVC.EnglewoodLAB.Data;
namespace UVC.EnglewoodLAB.UIToolkit
{
/// <summary>
/// 충포장 설비 정보 팝업 UI 요소.
/// 다크 배경 + 타이틀 + 데이터 항목 리스트로 구성됩니다.
/// </summary>
[UxmlElement]
public partial class EWLKPackEquipInfoPopup : VisualElement
{
private const string UssPath = "EWLK/UIToolkit/Main/EWLKPackEquipInfoPopupUss";
private static readonly string ClassRoot = "ewlk-pack-popup";
private static readonly string ClassTitle = "ewlk-pack-popup__title";
private static readonly string ClassItem = "ewlk-pack-popup__item";
private static readonly string ClassDot = "ewlk-pack-popup__dot";
private static readonly string ClassLabel = "ewlk-pack-popup__label";
private static readonly string ClassValue = "ewlk-pack-popup__value";
private readonly Label _titleLabel;
private readonly VisualElement _itemsContainer;
private readonly List<(Label label, Label value)> _itemCache = new();
public EWLKPackEquipInfoPopup()
{
var uss = Resources.Load<StyleSheet>(UssPath);
if (uss != null) styleSheets.Add(uss);
AddToClassList(ClassRoot);
// 타이틀
_titleLabel = new Label();
_titleLabel.AddToClassList(ClassTitle);
Add(_titleLabel);
// 항목 컨테이너
_itemsContainer = new VisualElement();
Add(_itemsContainer);
// 절대 위치 (Tracker가 좌표를 설정)
style.position = Position.Absolute;
}
/// <summary>
/// 데이터를 바인딩합니다.
/// </summary>
public void SetData(EWLKPackEquipInfoData data)
{
_titleLabel.text = data.Title;
// 기존 항목 재사용 또는 새로 생성
for (int i = 0; i < data.Items.Count; i++)
{
if (i < _itemCache.Count)
{
// 기존 항목 업데이트
_itemCache[i].label.text = data.Items[i].Label;
_itemCache[i].value.text = data.Items[i].Value;
}
else
{
// 새 항목 생성
var item = new VisualElement();
item.AddToClassList(ClassItem);
var dot = new VisualElement();
dot.AddToClassList(ClassDot);
item.Add(dot);
var lbl = new Label(data.Items[i].Label);
lbl.AddToClassList(ClassLabel);
item.Add(lbl);
var val = new Label(data.Items[i].Value);
val.AddToClassList(ClassValue);
item.Add(val);
_itemsContainer.Add(item);
_itemCache.Add((lbl, val));
}
}
// 초과 항목 숨김
for (int i = data.Items.Count; i < _itemCache.Count; i++)
{
_itemsContainer[i].style.display = DisplayStyle.None;
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 08527cc6d1b0cb946876fa1246a0c128