Files
Aislo/common_util/common_util_structure_catalog.ts
T
eomsangdonandClaude Opus 5.5 81f7d98e6f feat(구조물표): 323-7 새 종류 · 정리 — M02 종류 더하기(본보기 복사 · 일곱 갈래 본보기 그대로) · 손코딩 동작 · 명세 = 본보기 키 · 특수 동작 = 동작 이름 · 정본 목록 = 표 · drawing_views 걷음 · 옛 이름 되짚기 걷음(자리로 가름) · 허용 거리 파이썬 한 곳 · 벽 툴팁 = 종류 이름
- M02 종류별 탭 [종류 더하기] = 본보기(구조물 정본 원본) 복사 — 키 자동 · 이름 「본보기 2」 · 옵션 · 표시 · 그룹 · 배치 · 규칙 · 종류 덧칸 · 반영 그대로 · 종류.{새}.본보기 · 관 지점 종류 · 복사본은 본보기 밖
- 구조물 표 = 본보기 처음값 이어받음(resolveCatalog · resolve_catalog 짝 · 줄 칸 base) · baseType/base_type · specFor(명세) · 덧단 · 길이 전후 풀기 · 옹벽 추가 단 콘크리트
- 검사(화면 = 서버 짝) — 본보기 = 원본 · 같은 군 · 자리꼴 · 정본 · 만든 뒤 고정 · 복사본은 본보기가 숨긴 칸 이어받음 · 서버 덧칸 본보기 받음
- 특수 동작 = 동작 이름 — 바로 안 놓기 · 포장 나누기 = 포장_물넘이_겹침 · 월류 높이 기다림 = 월류단면
- 정본 목록 = 표 — 파이썬 관 지점 종류 = catalog owner · B05 시설 서브폼 = 표 형상 · 안 쓰는 손 목록 걷음
- drawing_views — 스키마에서 빼고 읽을 때 버림 · M02 [저장] 때 빠짐(코드는 마스터 안 고침)
- 이관 = 지난 판 이름 대신 측점 계획 자리(planted)로 가름 · all_type_names 걷음
- 허용 거리 = common_util_structure_station.py 한 곳(SAME_STATION_M · EDGE_TOLERANCE_M · PIPE_MATCH_M) · TS 표는 station.ts 머리
- 시험: test_323_7_new_type(템플릿 · 일곱 갈래 · 그림 · 검사 짝 · 서버 읽기) · 322-0 유령 시험 = 자리 · 옛 도움 파일 동작 · 표 길 · 기대 글 고침

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
2026-10-14 18:59:13 +09:00

207 lines
9.0 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;
/** 본보기 종류 키(323-7 · M02 종류 더하기) — 처음값 · 손코딩 동작을 이 종류에서 이어받음 · 원본이면 자기. */
base: string;
}
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>;
/** 종류 덧칸 — 본보기(복사한 종류의 원본 키 · 323-7). */
종류?: Record<string, { 본보기?: string }>;
}
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 root = definition?.종류?.[id]?.본보기 ?? id; // 복사한 종류 = 본보기 처음값(323-7)
const base = start[root] ?? {};
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,
base: root,
});
});
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);
/** 손코딩 동작 · 명세를 고를 종류 키 = 본보기(복사한 종류 · 323-7) · 원본이면 자기 · 파이썬 `base_type` 짝. */
export const baseType = (typeId: string): string => current.get(typeId)?.base ?? 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}`;
}
/** 어느 종류 정의에도 없는 읽는 칸의 화면 글(부품 상자 `칸 이름` · 빈칸 경고 323-4 · 파이썬 `field_name` 짝). */
export const fieldName = (key: string): string | undefined =>
(PARTS as unknown as { "칸 이름"?: Record<string, string> })["칸 이름"]?.[key];
/**
* 종류 바꿈 = 제자리(종류 키만 갈아 끼움)인가(323-6 · 표 규칙) — 정본이 같고, 구조물은 자리꼴도 같아야.
* 아니면(관 지점 ↔ 구조물 · E · F 점형 ↔ 구간형) 옛 것을 지우고 같은 기준 측점에 새로(구간 = 기본 길이).
* 관 지점 시설은 구간도 옵션(길이 · 전 · 후)이라 자리꼴로 안 가름.
*/
export function swapsInPlace(fromTypeId: string, toTypeId: string): boolean {
const from = catalogRow(fromTypeId);
const to = catalogRow(toTypeId);
if (!from || !to || from.owner !== to.owner) return false;
return from.owner === "pipe_points" || from.placement === to.placement;
}
/** 종류가 지금 쓰는 형상 부품의 필수 칸(비면 그 그림이 안 섬 · 빈칸 경고 · 322-3) — 없으면 빈 목록. */
export function shapeNeeds(typeId: string): readonly string[] {
const shape = catalogRow(typeId)?.shape ?? "none";
const box = (PARTS as unknown as Parts).parts.형상 as Record<string, { "필수 칸"?: string[] }>;
return Object.hasOwn(box, shape) ? (box[shape]["필수 칸"] ?? []) : [];
}
/** 관 공식 이름의 관종들(정의 선택지) — 표시 이름이 「배수관」으로 줄일 이름을 알아봄. */
export const pipeTitleKinds = (): readonly string[] => pipeKinds;