- 부품 상자 common_util_structure_parts.json(갈래마다 부품 · 읽는 칸 · 자리꼴 · 정본 · 42종 처음값) · 표 읽기 common_util_structure_catalog.ts / .py 짝(정의 반영 ⊕ 처음값 ⊕ 부품 끔) · 거울 시험 test_322_1_catalog_mirror - 목록: pickGroups 군 차례 = 구조물군 열쇠 · 페이지 거르기 listPick(B05 both · B06 both + b06) · HIDDEN_PICK_GROUPS · HIDDEN_PICK_TYPES · pickable 걷음(옛 대피 셋 asRoad 그대로) - 측점: structureStationMarks · pipeStationMarks · is_station_planting_type · resolve_extra_stations = 표 횡단도(anchor · span3 · none) - 카드 이름: cardTitle / card_title 한 공식(빈칸 = 그 종류 정의 기본값) · 표시 이름이 정의 관종도 앎 - 형상: 벽 = shape wall(형태 = 형상칸.form · 물빼기 = 형상칸.weep) · 포장 = shape pavement(「사용 중」 거르기 없앰) · 맹암거 · 사토 · 관 지점 시설 세트 = shape - 3D 몸체 = 그림 자료 주인 종류의 표 3D 부품 · 알약 띠 = 표 자리꼴 구간(관 지점 시설은 알약만) - ★1 노출형 · 개거 횡단도 none · ★2 쇄석 화면에서도 포장층 · ★3 기슭막이 표 자리꼴 interval(정의 파일 그대로) · ★4 코드 숨김 10종 list hidden · ★5 관 빈칸 D800 → 정의 기본 D1000 - 옛 시험: 도움 파일 로더 27개가 표 모듈 공용 한 벌(helper_322_catalog_module) 씀 · 기대값 고침(272_1 · b05_extra_stations · roundtrip · 314 · 52_7 · walls 거울 · wall_drain_pipe) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
175 lines
6.9 KiB
TypeScript
175 lines
6.9 KiB
TypeScript
/* =============================================================================
|
|
* common_util/common_util_structure_catalog.ts
|
|
* 구조물 표(catalog · PLAN 322-1) — 종류마다 「무엇을 할지」 한 줄 = 정의 `반영` ⊕ 부품 상자 처음값 ⊕ 부품 끔.
|
|
*
|
|
* 종류 키 · 군 글자 · 자리꼴 · managed_by 로 가리던 자리(목록 · 측점 · 카드 이름 · 형상 · 3D · 알약)가 이 한 줄을
|
|
* 읽음. 그리는 함수는 그대로(그림 자료 열쇠만 봄). 부품 상자 = `common_util_structure_parts.json`(파이썬과 같은 파일).
|
|
* 정의를 받기 전 = 처음값만으로 선 표(이름 · 군 · 정본 · 색 없음) — 받으면 `useCatalogDefinition` 이 갈아 끼움.
|
|
* 파이썬 짝 = `common_util_structure_catalog.py` · 거울 시험 = `resources/tester/test_322_1_catalog_mirror.py`.
|
|
* ========================================================================== */
|
|
|
|
import PARTS from "./common_util_structure_parts.json" with { type: "json" };
|
|
|
|
/** 고르기 목록 자리 — both = B05 · B06 · b06 = B06 만 · hidden = 안 보임(옛 자료는 그대로 그림). */
|
|
export type ListPlace = "both" | "b06" | "hidden";
|
|
export type PipeOwner = "pipe_points" | "structures";
|
|
|
|
export interface CatalogRow {
|
|
typeId: string;
|
|
name: string;
|
|
group: string;
|
|
groupName: string;
|
|
groupOrder: number;
|
|
typeOrder: number;
|
|
placement: string;
|
|
owner: PipeOwner;
|
|
list: ListPlace;
|
|
/** 횡단도 부품 — none · anchor · span3. */
|
|
station: string;
|
|
/** 카드 이름 부품 — name · pipe. */
|
|
title: string;
|
|
/** 형상 부품 — none · pipe_set · box · ford · ford_pavement · hidden_revet · wall · berm · underdrain · pavement · spoil. */
|
|
shape: string;
|
|
shapeOpts: Record<string, unknown>;
|
|
/** 3D 부품 — none · pipe · box · ford · chute · wall · surface. */
|
|
model3d: string;
|
|
color: string | null;
|
|
}
|
|
|
|
type Branch = "횡단도" | "카드이름" | "형상" | "3D";
|
|
type Reflect = Partial<Record<Branch | "목록", string>> & {
|
|
형상칸?: Record<string, unknown>;
|
|
자리꼴?: string;
|
|
};
|
|
|
|
/** 정의 한 벌 중 표가 읽는 칸 — `반영` · `부품` 은 M02 [저장] 때 생김(없으면 처음값). */
|
|
export interface CatalogDefinition {
|
|
구조물군?: Record<string, string>;
|
|
types?: ReadonlyArray<{
|
|
type_id: string;
|
|
name?: string;
|
|
group?: string;
|
|
placement?: string;
|
|
managed_by?: string | null;
|
|
enabled?: boolean;
|
|
style?: { color?: string };
|
|
options?: ReadonlyArray<{ key: string; choices?: readonly string[] }>;
|
|
}>;
|
|
반영?: Record<string, Reflect>;
|
|
부품?: Record<string, boolean>;
|
|
}
|
|
|
|
interface Parts {
|
|
parts: Record<Branch, Record<string, unknown>>;
|
|
처음값: Record<string, Reflect>;
|
|
}
|
|
|
|
/** 갈래 → 부품을 끈 값(`부품.{갈래}.{id} === false`). */
|
|
const OFF: Record<Branch, string> = {
|
|
횡단도: "none",
|
|
카드이름: "name",
|
|
형상: "none",
|
|
"3D": "none",
|
|
};
|
|
|
|
/** 정의 + 부품 상자 → 종류마다 한 줄(정의 types 차례 · 정의가 없으면 처음값 차례). */
|
|
export function resolveCatalog(
|
|
definition: CatalogDefinition | null,
|
|
parts: Parts = PARTS as unknown as Parts,
|
|
): Map<string, CatalogRow> {
|
|
const start = parts.처음값;
|
|
const groups = definition?.구조물군 ?? {};
|
|
const groupKeys = Object.keys(groups);
|
|
const off = new Set(
|
|
Object.entries(definition?.부품 ?? {})
|
|
.filter(([, on]) => on === false)
|
|
.map(([id]) => id),
|
|
);
|
|
const types = definition?.types?.length
|
|
? definition.types
|
|
: Object.keys(start).map((type_id) => ({ type_id }) as const);
|
|
const rows = new Map<string, CatalogRow>();
|
|
types.forEach((item, typeOrder) => {
|
|
const id = item.type_id;
|
|
const base = start[id] ?? {};
|
|
const mine = definition?.반영?.[id] ?? {};
|
|
const part = (branch: Branch): string => {
|
|
const value = mine[branch] ?? base[branch] ?? OFF[branch];
|
|
return off.has(`${branch}.${value}`) ? OFF[branch] : value;
|
|
};
|
|
const group = "group" in item ? (item.group ?? "") : "";
|
|
const index = groupKeys.indexOf(group);
|
|
rows.set(id, {
|
|
typeId: id,
|
|
name: ("name" in item && item.name) || id,
|
|
group,
|
|
groupName: groups[group] ?? group,
|
|
groupOrder: index < 0 ? groupKeys.length : index,
|
|
typeOrder,
|
|
placement: base.자리꼴 ?? ("placement" in item ? item.placement : undefined) ?? "point",
|
|
owner:
|
|
"managed_by" in item && item.managed_by === "pipe_points" ? "pipe_points" : "structures",
|
|
// 반영이 없으면 옛 짝 — 처음값 hidden 은 숨김 · 그 밖은 정의 enabled 가 가름(꺼짐 = B06 만)
|
|
list: (mine.목록 ??
|
|
(base.목록 === "hidden"
|
|
? "hidden"
|
|
: "enabled" in item && item.enabled === false
|
|
? "b06"
|
|
: "both")) as ListPlace,
|
|
station: part("횡단도"),
|
|
title: part("카드이름"),
|
|
shape: part("형상"),
|
|
shapeOpts: { ...(base.형상칸 ?? {}), ...(mine.형상칸 ?? {}) },
|
|
model3d: part("3D"),
|
|
color: ("style" in item && item.style?.color) || null,
|
|
});
|
|
});
|
|
return rows;
|
|
}
|
|
|
|
let current = resolveCatalog(null);
|
|
let pipeKinds: string[] = [];
|
|
|
|
/** 정의를 받으면(종류 레지스트리 · `fetchStructureTypes`) 표를 갈아 끼움 — 못 받았으면 처음값 표 그대로. */
|
|
export function useCatalogDefinition(definition: CatalogDefinition | null): void {
|
|
current = resolveCatalog(definition);
|
|
pipeKinds = [
|
|
...new Set(
|
|
(definition?.types ?? [])
|
|
.filter((type) => current.get(type.type_id)?.title === "pipe")
|
|
.flatMap(
|
|
(type) => type.options?.find((option) => option.key === "pipe_kind")?.choices ?? [],
|
|
),
|
|
),
|
|
];
|
|
}
|
|
|
|
/** 종류 한 줄 — 표에 없는 종류면 undefined. */
|
|
export const catalogRow = (typeId: string): CatalogRow | undefined => current.get(typeId);
|
|
|
|
/** 고르기 목록에 보일 종류인가 — B05 = both · B06 = both + b06(표에 없는 종류는 보임). */
|
|
export const listPick =
|
|
(page: "b05" | "b06") =>
|
|
(type: { type_id: string }): boolean => {
|
|
const list = catalogRow(type.type_id)?.list ?? "both";
|
|
return list === "both" || (page === "b06" && list === "b06");
|
|
};
|
|
|
|
/** 카드 · 측점 이름 — 카드이름 pipe = 「관종 D관경」(빈칸 = 그 종류 정의 기본값) · 그 밖 = 종류 이름. */
|
|
export function cardTitle(
|
|
typeId: string,
|
|
options: Record<string, unknown> | null | undefined,
|
|
defaults: Record<string, unknown> = {},
|
|
): string {
|
|
const row = catalogRow(typeId);
|
|
if (row?.title !== "pipe") return row?.name ?? typeId;
|
|
const value = (key: string): unknown => {
|
|
const own = options?.[key];
|
|
return own === undefined || own === null || own === "" ? defaults[key] : own;
|
|
};
|
|
return `${value("pipe_kind") ?? ""} D${Math.trunc(Number(value("pipe_diameter_mm"))) || 0}`;
|
|
}
|
|
|
|
/** 관 공식 이름의 관종들(정의 선택지) — 표시 이름이 「배수관」으로 줄일 이름을 알아봄. */
|
|
export const pipeTitleKinds = (): readonly string[] => pipeKinds;
|