build enviroment

This commit is contained in:
2026-02-03 11:40:26 +09:00
parent b8fe7b1d50
commit c78df1fc0e
183 changed files with 14945 additions and 2789 deletions

View File

@@ -10,7 +10,7 @@ IPointerEnterHandler, IPointerClickHandler
[SerializeField] private Image background;
[SerializeField] private TMP_Text label;
[SerializeField] private GameObject plusIcon; // 미리보기 선택
[SerializeField] private GameObject setIcon; // 확정 설정
[SerializeField] private Image setIcon; // 확정 설정
public int RackIndex { get; private set; } // 1..z
public int X { get; private set; } // 1..layout.x
@@ -29,7 +29,7 @@ IPointerEnterHandler, IPointerClickHandler
label.text = "1";
SetSelecting(false);
SetOccupied(false);
SetOccupiedIcon(null);
}
public void SetSelecting(bool on)
@@ -45,9 +45,20 @@ IPointerEnterHandler, IPointerClickHandler
}
}
public void SetOccupied(bool on)
public void SetOccupiedIcon(Sprite sprite)
{
if (setIcon != null) setIcon.SetActive(on);
if (setIcon == null) return;
if (sprite == null)
{
setIcon.sprite = null;
setIcon.enabled = false;
}
else
{
setIcon.sprite = sprite;
setIcon.enabled = true;
}
}
public void OnPointerEnter(PointerEventData eventData)

View File

@@ -49,12 +49,17 @@ public class GridView : MonoBehaviour
}
}
public void SetOccupiedCells(HashSet<(int rack, int x, int y)> occupied)
public void SetOccupiedCells(Dictionary<(int rack, int x, int y), Sprite> occupiedSprites)
{
foreach (var kv in cells)
{
var (x, y) = kv.Key;
kv.Value.SetOccupied(occupied.Contains((RackIndex, x, y)));
var cell = kv.Value;
if (occupiedSprites != null && occupiedSprites.TryGetValue((RackIndex, x, y), out var sprite))
cell.SetOccupiedIcon(sprite);
else
cell.SetOccupiedIcon(null);
}
}

View File

@@ -5,8 +5,6 @@ using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
using static UnityEngine.Rendering.DebugUI.Table;
public class InitialInventoryWindow : MonoBehaviour
{
[Header("Top/Left")]
@@ -27,11 +25,33 @@ public class InitialInventoryWindow : MonoBehaviour
[SerializeField] private InitRow rowPrefab;
[Header("Bottom Buttons")]
[SerializeField] private Button btnApply; // Apply 버튼 (요구사항 추가)
[SerializeField] private Button btnCancel; // 선택: 닫기
[SerializeField] private Button btnReset; // 선택: 초기화
[SerializeField] private Button btnApply;
[SerializeField] private Button btnCancel;
[SerializeField] private Button btnReset;
private ASRSDataClass asrs;
// ===== 대상(ASRS/Rack) 공통 핸들 =====
private Func<List<InitializeEntry>> initGetter;
private Action<List<InitializeEntry>> initSetter;
// 필요 시 외부에서 "지금 어느 타입을 열었나"를 추적하고 싶다면 유지
private ASRSDataClass asrs; // 마지막으로 연 ASRS (선택)
private RackDataClass rack; // 마지막으로 연 Rack (선택) - 실제 클래스명에 맞게 변경
[Serializable]
private struct LayoutInfo
{
public int x, y, z;
public float x_length, y_length, z_length;
}
private enum DimLabelMode
{
Asrs, // 기존 ASRS 표기(스크린샷 기준 y/z 매핑)
Rack // 일반 표기(x,y,z 그대로)
}
private LayoutInfo currentLayout;
private DimLabelMode dimLabelMode = DimLabelMode.Asrs;
// rack views
private readonly List<GridView> rackViews = new();
@@ -59,24 +79,114 @@ public class InitialInventoryWindow : MonoBehaviour
// prefab dropdown cache
private List<PrefabCatalogFromManager.Item> prefabItems = new();
private int count = 1;
// 외부 버튼으로 모드 토글 가능
public bool IsSelectionMode { get; private set; } = true;
// ===== Public API =====
public void Open(ASRSDataClass asrs)
// =====================================================================
// Public API
// =====================================================================
// 기존 호출부 호환: Open(ASRSDataClass) 유지 (원하시면 제거 가능)
public void Open(ASRSDataClass asrs) => OpenAsrs(asrs);
void SaveChange(object source, object value, string name)
{
var path = PathIndexer.GetNodePath(source);
Patch updateData = new Patch();
updateData.value = value;
UpdateValueStack.AddPatch($"{path}.{name}", value);
}
public void OpenAsrs(ASRSDataClass asrs)
{
if (asrs == null) return;
this.asrs = asrs;
this.rack = null;
var l = asrs.asrs_layout;
var layout = new LayoutInfo
{
x = l.x,
y = l.y,
z = Mathf.Clamp(l.z, 1, 2),
x_length = l.x_length,
y_length = l.y_length,
z_length = l.z_length
};
OpenInternal(
layout,
getter: () => asrs.initialize,
setter: list => asrs.initialize = list,
mode: DimLabelMode.Asrs
);
}
public void OpenRack(RackDataClass rack,string path) // 실제 클래스명에 맞게 변경
{
if (rack == null) return;
this.rack = rack;
this.asrs = null;
var l = rack.rack_layout; // 실제 필드명에 맞게 변경
var layout = new LayoutInfo
{
x = l.x,
y = l.y,
z = Mathf.Clamp(l.z, 1, 2),
x_length = l.x_length,
y_length = l.y_length,
z_length = l.z_length
};
// initialize가 set 가능하면 아래 그대로
OpenInternal(
layout,
getter: () => rack.initialize,
setter: list => { rack.initialize = list; SaveChange(rack,list, "initialize"); },
mode: DimLabelMode.Rack
);
// 만약 rack.initialize가 set 불가(읽기전용 리스트)라면, 위 setter 대신 아래로 바꾸세요.
/*
OpenInternal(
layout,
getter: () => rack.initialize,
setter: list =>
{
rack.initialize.Clear();
rack.initialize.AddRange(list);
},
mode: DimLabelMode.Rack
);
*/
}
private void OpenInternal(
LayoutInfo layout,
Func<List<InitializeEntry>> getter,
Action<List<InitializeEntry>> setter,
DimLabelMode mode
)
{
gameObject.SetActive(true);
this.asrs = asrs;
BuildDimLabel();
currentLayout = layout;
initGetter = getter;
initSetter = setter;
dimLabelMode = mode;
// UI Build
BuildDimLabel(layout, mode);
BuildPrefabDropdown();
BuildRacks();
BuildRacks(layout);
// 기존 initialize 로딩
LoadInitialize(asrs.initialize);
LoadInitialize(getter?.Invoke());
HookButtons();
RefreshAll();
@@ -99,18 +209,35 @@ public class InitialInventoryWindow : MonoBehaviour
}
}
// ===== Build UI =====
private void BuildDimLabel()
// =====================================================================
// Build UI
// =====================================================================
private void BuildDimLabel(LayoutInfo l, DimLabelMode mode)
{
var l = asrs.asrs_layout;
float xM, yM, zM;
float xM = l.x * l.x_length;
float yM = l.z * l.z_length;
float zM = l.y * l.y_length;
if (mode == DimLabelMode.Asrs)
{
// 기존 ASRS 표기 유지:
// x => x*x_length
// y => z*z_length
// z => y*y_length
xM = l.x * l.x_length;
yM = l.z * l.z_length;
zM = l.y * l.y_length;
}
else
{
// Rack은 일반 표기(원하시면 Asrs처럼 바꿔도 됩니다)
xM = l.x * l.x_length;
yM = l.y * l.y_length;
zM = l.z * l.z_length;
}
xLabel.text = $"{xM:0.##} m (x)";
yLabel.text = $"{yM:0.##} m (y)";
zLabel.text = $"{zM:0.##} m (z)";
if (xLabel != null) xLabel.text = $"{xM:0.##} m (x)";
if (yLabel != null) yLabel.text = $"{yM:0.##} m (y)";
if (zLabel != null) zLabel.text = $"{zM:0.##} m (z)";
}
private void BuildPrefabDropdown()
@@ -128,19 +255,18 @@ public class InitialInventoryWindow : MonoBehaviour
SetCount(count);
}
private void BuildRacks()
private void BuildRacks(LayoutInfo l)
{
foreach (Transform child in rackContainer)
Destroy(child.gameObject);
rackViews.Clear();
var l = asrs.asrs_layout;
int rackCount = Mathf.Clamp(l.z, 1, 2);
for (int rack = 1; rack <= rackCount; rack++)
for (int rackIndex = 1; rackIndex <= rackCount; rackIndex++)
{
var view = Instantiate(rackSectionPrefab, rackContainer);
view.Build(this, rack, l.x, l.y);
view.Build(this, rackIndex, l.x, l.y);
rackViews.Add(view);
}
}
@@ -150,7 +276,7 @@ public class InitialInventoryWindow : MonoBehaviour
if (btnApply != null)
{
btnApply.onClick.RemoveAllListeners();
btnApply.onClick.AddListener(ApplyToAsrsInitialize);
btnApply.onClick.AddListener(ApplyToTargetInitialize);
}
if (btnCancel != null)
@@ -173,6 +299,7 @@ public class InitialInventoryWindow : MonoBehaviour
RefreshAll();
});
}
if (toggleSelectAll != null)
{
toggleSelectAll.onValueChanged.RemoveAllListeners();
@@ -203,6 +330,10 @@ public class InitialInventoryWindow : MonoBehaviour
private string ResolvePrefabDisplayName(string prefabId)
=> PrefabCatalogFromManager.ResolveDisplayName(prefabId);
// =====================================================================
// Table: select all / delete selected
// =====================================================================
private void OnToggleSelectAllChanged(bool on)
{
var rows = tableContent.GetComponentsInChildren<InitRow>(true);
@@ -218,7 +349,6 @@ public class InitialInventoryWindow : MonoBehaviour
var ids = rows.Where(r => r.IsSelected).Select(r => r.EntryId).ToHashSet();
if (ids.Count == 0) return;
// entries에서 id에 해당하는 것들을 삭제
for (int i = entries.Count - 1; i >= 0; i--)
{
if (!ids.Contains(entries[i].id)) continue;
@@ -228,9 +358,8 @@ public class InitialInventoryWindow : MonoBehaviour
entries.RemoveAt(i);
}
// 선택 상태/토글 상태 정리
selectedEntryId = null;
if (toggleSelectAll != null) toggleSelectAll.isOn = false;
if (toggleSelectAll != null) toggleSelectAll.SetIsOnWithoutNotify(false);
RefreshAll();
}
@@ -244,7 +373,6 @@ public class InitialInventoryWindow : MonoBehaviour
bool anySelected = rows.Any(r => r.IsSelected);
btnDeleteSelected.interactable = anySelected;
// ✅ 추가: 전체 선택 토글 상태 동기화
if (toggleSelectAll != null)
{
bool allOff = rows.Length > 0 && rows.All(r => !r.IsSelected);
@@ -252,12 +380,13 @@ public class InitialInventoryWindow : MonoBehaviour
if (allOff) toggleSelectAll.SetIsOnWithoutNotify(false);
else if (allOn) toggleSelectAll.SetIsOnWithoutNotify(true);
// 일부만 선택된 경우는 toggleSelectAll 상태를 유지 (원하시면 false로 내리도록 변경 가능)
}
}
// ===== initialize load (기존 데이터 반영) =====
// =====================================================================
// initialize load
// =====================================================================
private void LoadInitialize(List<InitializeEntry> initList)
{
entries.Clear();
@@ -296,9 +425,9 @@ public class InitialInventoryWindow : MonoBehaviour
}
}
// ============================================================
// =====================================================================
// GridView -> Window 콜백 (Click + Hover)
// ============================================================
// =====================================================================
public void OnCellHover(int rackIndex, int x, int y)
{
@@ -313,13 +442,11 @@ public class InitialInventoryWindow : MonoBehaviour
{
if (!IsSelectionMode) return;
// ✅ (추가) 앵커를 아직 안 잡은 상태에서, 이미 설정된 셀 클릭하면 "그 영역(Entry) 삭제"
// 사용 예: 1,3,1~4,4,1로 설정된 상태에서 3,4,1 클릭 -> 해당 Entry 전체 삭제
// 앵커를 아직 안 잡은 상태에서, 이미 설정된 셀 클릭 시 -> 그 Entry 전체 삭제
if (!anchors.ContainsKey(rackIndex) && occupancy.TryGetValue((rackIndex, x, y), out int entryId))
{
DeleteEntryById(entryId);
// 삭제 후에는 선택 관련 미리보기/앵커도 정리(안전)
anchors.Remove(rackIndex);
previewRects.Remove(rackIndex);
@@ -351,7 +478,6 @@ public class InitialInventoryWindow : MonoBehaviour
}
else
{
// 실패 시: 미리보기 유지(앵커 유지)
previewRects[rackIndex] = rect;
RefreshSelectionVisuals();
}
@@ -428,23 +554,20 @@ public class InitialInventoryWindow : MonoBehaviour
}
}
// ===== Apply: 현재 변경사항을 asrs.initialize에 반영 =====
// =====================================================================
// Apply: 현재 변경사항을 "현재 대상" initialize에 반영
// =====================================================================
private void ApplyToAsrsInitialize()
private void ApplyToTargetInitialize()
{
if (asrs == null) return;
if (initSetter == null) return;
// 현재 엔트리 -> InitializeEntry 리스트로 변환
asrs.initialize = BuildInitializeEntries();
// 필요하면 여기서 닫거나, “저장됨” 토스트 등을 띄우면 됩니다.
// Close();
initSetter.Invoke(BuildInitializeEntries());
Close();
}
private List<InitializeEntry> BuildInitializeEntries()
{
// 중요: InitializeEntry의 from/to는 Position 타입이므로 변환 필요
// 사용자님 기존 코드 방식 그대로 사용
return entries.Select(e => new InitializeEntry
{
count = e.count,
@@ -454,7 +577,9 @@ public class InitialInventoryWindow : MonoBehaviour
}).ToList();
}
// ===== UI Refresh =====
// =====================================================================
// UI Refresh
// =====================================================================
private void RefreshAll()
{
@@ -478,9 +603,30 @@ public class InitialInventoryWindow : MonoBehaviour
private void RefreshOccupiedVisuals()
{
var occupiedSet = occupancy.Keys.Select(k => (k.rack, k.x, k.y)).ToHashSet();
// (rack,x,y) -> sprite
var map = new Dictionary<(int rack, int x, int y), Sprite>();
foreach (var kv in occupancy)
{
var key = kv.Key; // (rack,x,y)
int entryId = kv.Value;
var entry = entries.FirstOrDefault(e => e.id == entryId);
if (entry == null) continue;
// prefabId -> shape
string shape = PrefabManager.Instance.GetPrefab(entry.prefabId).shape;
// shape -> sprite (SingletonScene 기반 Catalog 사용)
Sprite sprite = ShapeIconCatalog.Instance != null
? ShapeIconCatalog.Instance.GetIcon(shape)
: null;
map[key] = sprite;
}
foreach (var view in rackViews)
view.SetOccupiedCells(occupiedSet);
view.SetOccupiedCells(map);
}
private void RefreshTable()
@@ -494,7 +640,7 @@ public class InitialInventoryWindow : MonoBehaviour
{
var row = Instantiate(rowPrefab, tableContent);
// InitRow.Bind 시그니처는 사용자님 프로젝트 기준
// InitRow가 onSelectionChanged를 받는 버전이어야 합니다.
row.Bind(
e.id,
e.prefabDisplayName,
@@ -527,17 +673,12 @@ public class InitialInventoryWindow : MonoBehaviour
private void DeleteEntryById(int entryId)
{
// entries에서 엔트리 찾기
var entry = entries.FirstOrDefault(e => e.id == entryId);
if (entry == null) return;
// occupancy 해제 (범위 전체)
MarkOccupancy(entry.id, entry.from, entry.to, false);
// entries 제거
entries.Remove(entry);
// 테이블 선택 상태도 정리
if (selectedEntryId.HasValue && selectedEntryId.Value == entryId)
selectedEntryId = null;
}

View File

@@ -0,0 +1,36 @@
using System.Collections.Generic;
using UnityEngine;
using UVC.Core;
public class ShapeIconCatalog : SingletonScene<ShapeIconCatalog>
{
Dictionary<string,Sprite> iconMap = new Dictionary<string,Sprite>();
private readonly Dictionary<string, string> iconPaths = new Dictionary<string, string>()
{
{"box","Images/GridCellIcon/si_icon_box"},
{"flexible","Images/GridCellIcon/si_icon_flexible"},
{"sheet","Images/GridCellIcon/si_icon_flexible"},
{"barrel","Images/GridCellIcon/si_icon_barrel"},
{"bottle","Images/GridCellIcon/si_icon_bottle"},
{"coil","Images/GridCellIcon/si_icon_coil"},
{"powder","Images/GridCellIcon/is_icon_powder"},
{"hazard","Images/GridCellIcon/si_icon_hazard"}
};
protected override void Init()
{
foreach (var path in iconPaths)
{
iconMap.Add(path.Key,Resources.Load<Sprite>(path.Value));
}
}
public Sprite GetIcon(string key)
{
if (iconMap.ContainsKey(key))
{
return iconMap[key];
}
return null;
}
}

View File

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