- 부품 상자 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
165 lines
6.9 KiB
TypeScript
165 lines
6.9 KiB
TypeScript
/* =============================================================================
|
|
* common_util_structure_walls.ts
|
|
* 구조물 정본(C군 사면안정 벽)을 횡단 측점 제원으로 바꾸는 자리 —
|
|
* 파이썬 `B06_Section_Engine_Structures_Wall.py` 의 **짝**이다(거울 테스트로 대조).
|
|
*
|
|
* 왜 두 벌인가(CLAUDE.md 5장) — 서버는 상세를 내려보낼 때 얹어야 하고(저장분 기준),
|
|
* 브라우저는 사용자가 **아직 저장하지 않은** 목록으로 즉시 얹어야 한다. 값을 만드는
|
|
* 산식이 같아야 하므로 두 파일 머리에 짝임을 적고 거울 테스트를 둔다.
|
|
*
|
|
* 얹는 것은 제원뿐이다 — 도형·면적은 `B06_Section_UI_Cross_Revetment` 가 그린다.
|
|
* 벽 기본 형태(폼에서 형태를 비웠을 때) = 구조물 표 형상칸.form(322-1 · 옛 `FORM_BY_TYPE`).
|
|
* ========================================================================== */
|
|
|
|
// `.js` = 짝 거울 시험이 tsc → Node ESM 으로 이 파일만 엮음(확장자 있어야 찾음 · vite 는 .ts 로 풂)
|
|
import { catalogRow } from "./common_util_structure_catalog.js";
|
|
|
|
/** 구조물 정본 한 건(필요한 칸만). `structures.json` 과 같은 이름을 쓴다. */
|
|
export interface WallStructureInput {
|
|
structure_id?: string | null;
|
|
type_id: string;
|
|
placement?: string | null;
|
|
chainage_m: number;
|
|
start_m?: number | null;
|
|
end_m?: number | null;
|
|
options?: Record<string, unknown> | null;
|
|
}
|
|
|
|
/** 측점에 얹는 벽 제원 — `section.revetment` 와 같은 꼴. */
|
|
export interface WallSpec {
|
|
structure_id: string | null;
|
|
type_id: string;
|
|
name: string;
|
|
start_m: number;
|
|
end_m: number;
|
|
anchor_m: number;
|
|
form: string | null;
|
|
height_m: number | null;
|
|
side: string | null;
|
|
/** 기초 축 — "기초유" | "기초버림". 저장 칸과 같은 글자(터파기 그림이 이 값으로 갈린다). */
|
|
foundation: string | null;
|
|
tiers: number | null;
|
|
/** 추가 단(2단부터) 형태 · 높이 — 좌측 폼 「추가 단」 상자(178-3 · `tier_{n}_*`). 없으면 빈 목록. */
|
|
extras: WallTier[];
|
|
lift_m: number | null;
|
|
shift_m: number | null;
|
|
/** 전면 기울기 판정 칸 — 성토/절토 · 전면 기울기(저장 원본 · B08 과 같은 칸, 2026-09-14 B5). */
|
|
face_role: string | null;
|
|
face_slope_ratio: number | string | null;
|
|
}
|
|
|
|
/** 추가 단 한 벌 — 형태 빈 값 = 본 벽 형태 · 높이 빈 값 = 기본 단 높이(그리는 쪽). */
|
|
export interface WallTier {
|
|
form: string | null;
|
|
height_m: number | null;
|
|
}
|
|
|
|
/** 구간 경계 측점을 구간 안으로 볼 허용 오차(m) — 파이썬 `_EDGE_TOLERANCE_M`. */
|
|
const EDGE_TOLERANCE_M = 0.02;
|
|
|
|
/**
|
|
* 구조물 종류 → 횡단 기하가 아는 형태 이름 = 표 형상칸.form(파이썬 `_wall_form` 짝).
|
|
*
|
|
* ⚠⚠ **「형상 동일」이 「수량 동일」이 아니다**(2026-09-09 사용자 확정 4차 원문:
|
|
* 「흙막이는 횡단도에서 표현방식들과 옵션들은 동일하게 반영 · **형상은 동일** ·
|
|
* 물론 **데이터는 분리하여 계산**되어야 함」).
|
|
* ⇒ 흙막이는 기슭막이와 **같은 그리기 경로·같은 옵션 벌**을 쓰되, 원단위·공종코드·성분
|
|
* 줄은 **흙막이 자기 것**이다. 그림이 같다고 수량을 빌려 쓰면 안 된다.
|
|
* ⚠ 반대 방향의 사례가 골막이다 — 그림도 수량도 다르고, 두께식마저 다른 식이었다.
|
|
* **구조물 계열이 다르면 상수를 나눠 쓸 것.**
|
|
*/
|
|
const formOf = (typeId: string): string | null => {
|
|
const form = catalogRow(typeId)?.shapeOpts.form;
|
|
return typeof form === "string" ? form : null;
|
|
};
|
|
|
|
const num = (value: unknown): number | null =>
|
|
typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
|
|
const TIER_KEY = /^tier_(\d+)_/;
|
|
|
|
/** 「추가 단」 상자(`tier_{n}_*` · n 차례) → 추가 단 목록 — 옹벽 = 콘크리트(178-3) · 파이썬 `tier_extras` 짝. */
|
|
export function tierExtras(typeId: string, options: Record<string, unknown>): WallTier[] {
|
|
const ns = [
|
|
...new Set(
|
|
Object.keys(options)
|
|
.map((key) => TIER_KEY.exec(key)?.[1])
|
|
.filter((n): n is string => !!n)
|
|
.map(Number),
|
|
),
|
|
].sort((a, b) => a - b);
|
|
return ns.map((n) => ({
|
|
form: typeId === "retaining_wall" ? "콘크리트" : (options[`tier_${n}_form`] as string) || null,
|
|
height_m: num(options[`tier_${n}_height`]),
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* C군 벽 구조물을 제원 목록으로 바꾼다. 이름표(`names`)는 타입 레지스트리에서 온다.
|
|
* 구간(start·end)이 없는 항목은 건너뛴다 — 벽은 구간형이다.
|
|
*/
|
|
export function wallSpecsFrom(
|
|
structures: readonly WallStructureInput[],
|
|
names: ReadonlyMap<string, string>,
|
|
): WallSpec[] {
|
|
const specs: WallSpec[] = [];
|
|
for (const structure of structures) {
|
|
const name = names.get(structure.type_id);
|
|
if (!name) continue;
|
|
const start = num(structure.start_m);
|
|
const end = num(structure.end_m);
|
|
if (start === null || end === null) continue;
|
|
const options = (structure.options ?? {}) as Record<string, unknown>;
|
|
const extras = tierExtras(structure.type_id, options);
|
|
specs.push({
|
|
structure_id: structure.structure_id ?? null,
|
|
type_id: structure.type_id,
|
|
name,
|
|
start_m: Math.min(start, end),
|
|
end_m: Math.max(start, end),
|
|
anchor_m: num(structure.chainage_m) ?? Math.min(start, end),
|
|
form: (options.form as string) || formOf(structure.type_id),
|
|
height_m: num(options.height_m),
|
|
side: (options.side as string) ?? null,
|
|
// 기초 축 — 저장 칸과 **같은 글자**. 초안 경로에서 빠지면 터파기가 안 그려진다.
|
|
foundation: (options.foundation as string) ?? null,
|
|
// 단 수 = 1 + 추가 단 상자 수 · 상자 없으면 옛 `tiers`(178-3)
|
|
tiers: extras.length ? extras.length + 1 : num(options.tiers),
|
|
extras,
|
|
lift_m: num(options.lift_m),
|
|
shift_m: num(options.shift_m),
|
|
face_role: (options.face_role as string) ?? null,
|
|
face_slope_ratio: (options.face_slope_ratio as number | string) ?? null,
|
|
});
|
|
}
|
|
return specs;
|
|
}
|
|
|
|
/**
|
|
* 구간 안 측점에 제원을 얹는다(파이썬 `attach_wall_structures`). 얹은 개수를 돌려준다.
|
|
* 관 세트가 이미 붙은 측점은 건드리지 않는다 — 한 자리에 두 벽이 겹치면 읽히지 않는다.
|
|
*/
|
|
export function attachWallSpecs(
|
|
sections: Array<Record<string, unknown>>,
|
|
specs: readonly WallSpec[],
|
|
): number {
|
|
if (!specs.length) return 0;
|
|
let attached = 0;
|
|
for (const section of sections) {
|
|
const chainage = num(section.chainage_m);
|
|
if (chainage === null) continue;
|
|
if (section.culvert || section.revetment) continue;
|
|
for (const spec of specs) {
|
|
if (
|
|
spec.start_m - EDGE_TOLERANCE_M <= chainage &&
|
|
chainage <= spec.end_m + EDGE_TOLERANCE_M
|
|
) {
|
|
section.revetment = spec;
|
|
attached += 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return attached;
|
|
}
|