feat(구조물표): 322-1 부품 상자 · 표 읽기 — 목록 · 측점 · 카드 이름 · 형상 고르기 · 3D 몸체 · 알약 띠가 종류 키 · 군 글자 · 자리꼴 · managed_by 대신 구조물 표(catalog) 한 줄을 읽음 · 코드 숨김 목록 걷음 · ★ 넷 + 관 공식 한 벌
- 부품 상자 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
This commit is contained in:
@@ -11,8 +11,12 @@
|
||||
*
|
||||
* 값을 새로 만들지 않는다 — **정본(관 지점 옵션) + 레지스트리 기본값**만 조합한다.
|
||||
* 상수 사본을 두면 B05 폼과 갈라지므로 기본값은 레지스트리에서만 꺼낸다.
|
||||
* 시설마다 그릴 세트 = 구조물 표 형상(322-1 · `common_util_structure_catalog`).
|
||||
* ========================================================================== */
|
||||
|
||||
// `.js` = 짝 거울 시험이 tsc → Node ESM 으로 이 파일만 엮음(확장자 있어야 찾음 · vite 는 .ts 로 풂)
|
||||
import { catalogRow } from "./common_util_structure_catalog.js";
|
||||
|
||||
/** 관 지점 정본 1건 — `pipe_points.json` 의 한 줄(브라우저는 API 로 같은 것을 받는다). */
|
||||
export interface CulvertPipePoint {
|
||||
chainage_m: number;
|
||||
@@ -129,12 +133,8 @@ export function followPipeHeights(
|
||||
}
|
||||
}
|
||||
|
||||
/** 시설 종류 — 파이썬 `common_util_drainage_pipes` 의 상수와 같은 문자열. */
|
||||
/** 시설 종류 기본값 — 파이썬 `common_util_drainage_pipes.PIPE_FACILITY_PIPE` 와 같은 문자열. */
|
||||
const FACILITY_PIPE = "pipe";
|
||||
const FACILITY_BOX = "box_culvert";
|
||||
const FACILITY_FORD_PAVEMENT = "ford_pavement";
|
||||
const FACILITY_FORD_BRIDGE = "ford_bridge";
|
||||
const FACILITY_REVET = "revetment";
|
||||
|
||||
/** 숫자 옵션 하나를 정리한다. 문자열 저장분(관경 "1000")도 받는다 — 파이썬 `_number`. */
|
||||
function num(value: unknown, fallback: number | null): number | null {
|
||||
@@ -566,14 +566,15 @@ export function buildCulvertSets(
|
||||
const chainage = num(point.chainage_m, null);
|
||||
if (chainage === null) continue;
|
||||
const values = (point.options ?? {}) as Record<string, unknown>;
|
||||
const facility = point.facility || FACILITY_PIPE;
|
||||
// 그릴 세트 = 표 형상(322-1 · 모르는 시설 = 배관 세트)
|
||||
const shape = catalogRow(point.facility || FACILITY_PIPE)?.shape ?? "pipe_set";
|
||||
let spec: CulvertSetSpec;
|
||||
if (facility === FACILITY_FORD_BRIDGE) spec = fordSet(values, registry);
|
||||
else if (facility === FACILITY_BOX) spec = boxSet(values, registry);
|
||||
else if (facility === FACILITY_FORD_PAVEMENT) spec = fordPavementSet(values, registry);
|
||||
else if (facility === FACILITY_REVET) spec = revetSet(values);
|
||||
else if (text(values.pipe_diameter_mm) === null)
|
||||
continue; // 관경 안 고름 = 관 없음(54-39)
|
||||
if (shape === "ford") spec = fordSet(values, registry);
|
||||
else if (shape === "box") spec = boxSet(values, registry);
|
||||
else if (shape === "ford_pavement") spec = fordPavementSet(values, registry);
|
||||
else if (shape === "hidden_revet") spec = revetSet(values);
|
||||
else if (shape !== "pipe_set" || text(values.pipe_diameter_mm) === null)
|
||||
continue; // 형상 없음 · 관경 안 고름 = 관 없음(54-39)
|
||||
else spec = culvertSet(values, registry);
|
||||
sets.set(round(chainage, 2), spec);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
"""구조물 표(catalog · PLAN 322-1) — 종류마다 「무엇을 할지」 한 줄.
|
||||
|
||||
한 줄 = 정의 `반영` ⊕ 부품 상자 처음값 ⊕ 부품 끔. 종류 키 · 군 글자 · 자리꼴 · managed_by 로
|
||||
가리던 자리(측점 · 카드 이름 · 형상)가 이 한 줄을 읽음 · 그리는 함수는 그대로.
|
||||
부품 상자 = `common_util_structure_parts.json`(TS 와 같은 파일) · 줄 열쇠도 TS 와 같은 이름.
|
||||
TS 짝 = `common_util_structure_catalog.ts`
|
||||
거울 시험 = `resources/tester/test_322_1_catalog_mirror.py`.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import math
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
PARTS_PATH = Path(__file__).with_name("common_util_structure_parts.json")
|
||||
|
||||
# 갈래 → 부품을 끈 값(`부품.{갈래}.{id} == false`)
|
||||
_OFF = {"횡단도": "none", "카드이름": "name", "형상": "none", "3D": "none"}
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def load_parts() -> dict[str, Any]:
|
||||
with open(PARTS_PATH, encoding="utf-8") as handle:
|
||||
return json.load(handle)
|
||||
|
||||
|
||||
def resolve_catalog(
|
||||
definition: dict[str, Any] | None, parts: dict[str, Any] | None = None
|
||||
) -> dict[str, dict[str, Any]]:
|
||||
"""정의 + 부품 상자 → 종류마다 한 줄(정의 types 차례 · 정의가 없으면 처음값 차례)."""
|
||||
start = (parts or load_parts())["처음값"]
|
||||
definition = definition or {}
|
||||
groups = definition.get("구조물군") or {}
|
||||
group_keys = list(groups)
|
||||
off = {key for key, on in (definition.get("부품") or {}).items() if on is False}
|
||||
reflect = definition.get("반영") or {}
|
||||
types = definition.get("types") or [{"type_id": key} for key in start]
|
||||
rows: dict[str, dict[str, Any]] = {}
|
||||
for order, item in enumerate(types):
|
||||
type_id = item["type_id"]
|
||||
base = start.get(type_id) or {}
|
||||
mine = reflect.get(type_id) or {}
|
||||
|
||||
def part(branch: str, base=base, mine=mine) -> str:
|
||||
value = mine.get(branch, base.get(branch, _OFF[branch]))
|
||||
return _OFF[branch] if f"{branch}.{value}" in off else value
|
||||
|
||||
group = item.get("group") or ""
|
||||
# 반영이 없으면 옛 짝 — 처음값 hidden 은 숨김 · 그 밖은 정의 enabled 가 가름(꺼짐 = B06 만)
|
||||
if "목록" in mine:
|
||||
listed = mine["목록"]
|
||||
elif base.get("목록") == "hidden":
|
||||
listed = "hidden"
|
||||
else:
|
||||
listed = "b06" if item.get("enabled") is False else "both"
|
||||
rows[type_id] = {
|
||||
"typeId": type_id,
|
||||
"name": item.get("name") or type_id,
|
||||
"group": group,
|
||||
"groupName": groups.get(group, group),
|
||||
"groupOrder": group_keys.index(group) if group in group_keys else len(group_keys),
|
||||
"typeOrder": order,
|
||||
"placement": base.get("자리꼴") or item.get("placement") or "point",
|
||||
"owner": "pipe_points" if item.get("managed_by") == "pipe_points" else "structures",
|
||||
"list": listed,
|
||||
"station": part("횡단도"),
|
||||
"title": part("카드이름"),
|
||||
"shape": part("형상"),
|
||||
"shapeOpts": {**(base.get("형상칸") or {}), **(mine.get("형상칸") or {})},
|
||||
"model3d": part("3D"),
|
||||
"color": (item.get("style") or {}).get("color") or None,
|
||||
}
|
||||
return rows
|
||||
|
||||
|
||||
@lru_cache(maxsize=4)
|
||||
def _catalog_at(path: str, mtime: int, size: int) -> dict[str, dict[str, Any]]:
|
||||
from B05_Profile.B05_Profile_Structures_Schema import _read_definition
|
||||
|
||||
return resolve_catalog(_read_definition(path, mtime, size))
|
||||
|
||||
|
||||
def catalog() -> dict[str, dict[str, Any]]:
|
||||
"""지금 정의 판의 표 — 정의 파일이 바뀌면(M02 저장) 다음 부름부터 새 표(파일 시각 캐시)."""
|
||||
from B05_Profile.B05_Profile_Structures_Schema import _stamp
|
||||
|
||||
return _catalog_at(*_stamp())
|
||||
|
||||
|
||||
def catalog_row(type_id: str) -> dict[str, Any] | None:
|
||||
return catalog().get(type_id)
|
||||
|
||||
|
||||
def type_ids_with(**match: str) -> set[str]:
|
||||
"""표에서 칸 값이 맞는 종류들 — 예 `type_ids_with(shape="pavement")`."""
|
||||
return {key for key, row in catalog().items() if all(row[k] == v for k, v in match.items())}
|
||||
|
||||
|
||||
def card_title(type_id: str, options: dict[str, Any] | None) -> str | None:
|
||||
"""카드 · 측점 이름 — 카드이름 pipe = 「관종 D관경」(빈칸 = 그 종류 정의 기본값) · 그 밖 = 이름.
|
||||
|
||||
표에 없는 종류면 None(측점 안 붙임). TS `cardTitle` 짝(옛 빈칸 D800 ↔ 화면 D1000 어긋남 없앰).
|
||||
"""
|
||||
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map
|
||||
|
||||
row = catalog_row(type_id)
|
||||
if row is None:
|
||||
return None
|
||||
if row["title"] != "pipe":
|
||||
return row["name"]
|
||||
definition = structure_type_map().get(type_id)
|
||||
defaults = {o.key: o.default for o in definition.options} if definition else {}
|
||||
values = options or {}
|
||||
|
||||
def value(key: str) -> Any:
|
||||
own = values.get(key)
|
||||
return defaults.get(key) if own is None or own == "" else own
|
||||
|
||||
try:
|
||||
diameter = math.trunc(float(value("pipe_diameter_mm")))
|
||||
except (TypeError, ValueError):
|
||||
diameter = 0
|
||||
return f"{value('pipe_kind') or ''} D{diameter}"
|
||||
@@ -0,0 +1,174 @@
|
||||
/* =============================================================================
|
||||
* 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;
|
||||
@@ -0,0 +1,485 @@
|
||||
{
|
||||
"comment": "구조물 부품 상자(PLAN 322-1) — 코드가 그리는 방법 한 벌. TS(import) · 파이썬(json.load)이 같은 파일을 읽음. parts = 갈래(횡단도 · 카드이름 · 형상 · 3D)마다 부품 id → {이름 · 읽는 칸(옵션 키) · 자리꼴 · 정본}. 처음값 = 종류마다 정의 `반영` 이 없을 때 쓰는 값(지금 동작 · ★ = PLAN_구조물표 4장). 표 읽기 = common_util_structure_catalog.ts · .py(짝).",
|
||||
"parts": {
|
||||
"횡단도": {
|
||||
"anchor": {
|
||||
"이름": "기준 한 장",
|
||||
"읽는 칸": [],
|
||||
"자리꼴": ["point", "interval", "site"],
|
||||
"정본": ["pipe_points", "structures"]
|
||||
},
|
||||
"span3": {
|
||||
"이름": "시작 · 기준 · 끝",
|
||||
"읽는 칸": ["length_m", "before_m", "after_m"],
|
||||
"자리꼴": ["interval"],
|
||||
"정본": ["pipe_points", "structures"]
|
||||
}
|
||||
},
|
||||
"카드이름": {
|
||||
"pipe": {
|
||||
"이름": "관종 D관경",
|
||||
"읽는 칸": ["pipe_kind", "pipe_diameter_mm"],
|
||||
"자리꼴": ["point"],
|
||||
"정본": ["pipe_points"]
|
||||
}
|
||||
},
|
||||
"형상": {
|
||||
"pipe_set": {
|
||||
"이름": "배관 세트(관 · 유입 · 유출 · 흙막이 · 도수로)",
|
||||
"읽는 칸": [
|
||||
"pipe_kind",
|
||||
"pipe_diameter_mm",
|
||||
"revet_foundation",
|
||||
"inlet_type",
|
||||
"inlet_revet_form",
|
||||
"inlet_revet_length_m",
|
||||
"inlet_revet_before_m",
|
||||
"inlet_revet_after_m",
|
||||
"inlet_revet_height_m",
|
||||
"inlet_basin_length_m",
|
||||
"inlet_basin_before_m",
|
||||
"inlet_basin_after_m",
|
||||
"inlet_basin_height_m",
|
||||
"outlet_type",
|
||||
"outlet_revet_form",
|
||||
"outlet_revet_length_m",
|
||||
"outlet_revet_before_m",
|
||||
"outlet_revet_after_m",
|
||||
"outlet_revet_height_m"
|
||||
],
|
||||
"자리꼴": ["point"],
|
||||
"정본": ["pipe_points"]
|
||||
},
|
||||
"box": {
|
||||
"이름": "BOX 본체 · 날개벽",
|
||||
"읽는 칸": [
|
||||
"body_width_m",
|
||||
"body_height_m",
|
||||
"wall_thickness_m",
|
||||
"slab_thickness_m",
|
||||
"wing_in",
|
||||
"wing_in_height_m",
|
||||
"wing_in_length_m",
|
||||
"wing_in_angle_deg",
|
||||
"wing_out",
|
||||
"wing_out_height_m",
|
||||
"wing_out_length_m",
|
||||
"wing_out_angle_deg"
|
||||
],
|
||||
"자리꼴": ["point"],
|
||||
"정본": ["pipe_points"]
|
||||
},
|
||||
"ford": {
|
||||
"이름": "세월교(관 · 월류 · 날개벽)",
|
||||
"읽는 칸": [
|
||||
"pipe_kind",
|
||||
"pipe_diameter_mm",
|
||||
"pipe_count",
|
||||
"ford_width_m",
|
||||
"ford_height_m",
|
||||
"wing_in",
|
||||
"wing_in_height_m",
|
||||
"wing_in_length_m",
|
||||
"wing_in_angle_deg",
|
||||
"wing_out",
|
||||
"wing_out_height_m",
|
||||
"wing_out_length_m",
|
||||
"wing_out_angle_deg"
|
||||
],
|
||||
"자리꼴": ["point"],
|
||||
"정본": ["pipe_points"]
|
||||
},
|
||||
"ford_pavement": {
|
||||
"이름": "물넘이 포장(파인 노면 · 도수로)",
|
||||
"읽는 칸": ["ford_width_m", "ford_height_m", "ford_slope_deg", "ford_slope_pct"],
|
||||
"자리꼴": ["point"],
|
||||
"정본": ["pipe_points"]
|
||||
},
|
||||
"hidden_revet": {
|
||||
"이름": "독립 기슭막이(관 숨김 벽)",
|
||||
"읽는 칸": [
|
||||
"form",
|
||||
"height_m",
|
||||
"length_m",
|
||||
"before_m",
|
||||
"after_m",
|
||||
"tiers",
|
||||
"side",
|
||||
"foundation",
|
||||
"face_role",
|
||||
"face_slope_ratio"
|
||||
],
|
||||
"자리꼴": ["interval"],
|
||||
"정본": ["pipe_points"]
|
||||
},
|
||||
"wall": {
|
||||
"이름": "벽(옹벽 · 흙막이 · 돌쌓기)",
|
||||
"읽는 칸": [
|
||||
"form",
|
||||
"height_m",
|
||||
"side",
|
||||
"foundation",
|
||||
"tiers",
|
||||
"lift_m",
|
||||
"shift_m",
|
||||
"face_role",
|
||||
"face_slope_ratio"
|
||||
],
|
||||
"자리꼴": ["interval"],
|
||||
"정본": ["structures"]
|
||||
},
|
||||
"berm": {
|
||||
"이름": "소단(사면 계단)",
|
||||
"읽는 칸": ["width_m", "interval_m", "slope_deg", "depth_m"],
|
||||
"자리꼴": ["interval"],
|
||||
"정본": ["structures"]
|
||||
},
|
||||
"underdrain": {
|
||||
"이름": "맹암거",
|
||||
"읽는 칸": ["culvert_height_m", "culvert_slope_pct"],
|
||||
"자리꼴": ["point"],
|
||||
"정본": ["structures"]
|
||||
},
|
||||
"pavement": {
|
||||
"이름": "포장층",
|
||||
"읽는 칸": ["pave_spec", "height_m", "widening_off"],
|
||||
"자리꼴": ["interval"],
|
||||
"정본": ["structures"]
|
||||
},
|
||||
"spoil": {
|
||||
"이름": "사토(성토 채움)",
|
||||
"읽는 칸": ["capacity_m3"],
|
||||
"자리꼴": ["site"],
|
||||
"정본": ["structures"]
|
||||
}
|
||||
},
|
||||
"3D": {
|
||||
"pipe": {
|
||||
"이름": "관 · 유입 · 유출 벽",
|
||||
"읽는 칸": [],
|
||||
"자리꼴": ["point"],
|
||||
"정본": ["pipe_points"]
|
||||
},
|
||||
"box": {
|
||||
"이름": "BOX 본체 · 날개벽",
|
||||
"읽는 칸": [],
|
||||
"자리꼴": ["point"],
|
||||
"정본": ["pipe_points"]
|
||||
},
|
||||
"ford": {
|
||||
"이름": "세월교 몸체 · 관",
|
||||
"읽는 칸": [],
|
||||
"자리꼴": ["point"],
|
||||
"정본": ["pipe_points"]
|
||||
},
|
||||
"chute": {
|
||||
"이름": "물넘이 도수로 띠",
|
||||
"읽는 칸": [],
|
||||
"자리꼴": ["point"],
|
||||
"정본": ["pipe_points"]
|
||||
},
|
||||
"wall": {
|
||||
"이름": "벽 스윕",
|
||||
"읽는 칸": [],
|
||||
"자리꼴": ["interval"],
|
||||
"정본": ["pipe_points", "structures"]
|
||||
},
|
||||
"surface": {
|
||||
"이름": "설계선만(몸체 없음)",
|
||||
"읽는 칸": [],
|
||||
"자리꼴": ["point", "interval", "site"],
|
||||
"정본": ["pipe_points", "structures"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"처음값": {
|
||||
"pipe": {
|
||||
"목록": "both",
|
||||
"횡단도": "anchor",
|
||||
"카드이름": "pipe",
|
||||
"형상": "pipe_set",
|
||||
"3D": "pipe"
|
||||
},
|
||||
"box_culvert": {
|
||||
"목록": "both",
|
||||
"횡단도": "anchor",
|
||||
"카드이름": "name",
|
||||
"형상": "box",
|
||||
"3D": "box"
|
||||
},
|
||||
"ford_pavement": {
|
||||
"목록": "both",
|
||||
"횡단도": "anchor",
|
||||
"카드이름": "name",
|
||||
"형상": "ford_pavement",
|
||||
"3D": "chute"
|
||||
},
|
||||
"ford_bridge": {
|
||||
"목록": "both",
|
||||
"횡단도": "anchor",
|
||||
"카드이름": "name",
|
||||
"형상": "ford",
|
||||
"3D": "ford"
|
||||
},
|
||||
"cross_drain_exposed": {
|
||||
"목록": "both",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"open_ditch": {
|
||||
"목록": "both",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"cross_underdrain": {
|
||||
"목록": "both",
|
||||
"횡단도": "anchor",
|
||||
"카드이름": "name",
|
||||
"형상": "underdrain",
|
||||
"3D": "surface"
|
||||
},
|
||||
"ditch_side": {
|
||||
"목록": "hidden",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"ditch_ridge": {
|
||||
"목록": "hidden",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"ditch_berm": {
|
||||
"목록": "hidden",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"chute": {
|
||||
"목록": "hidden",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"slope_drain": {
|
||||
"목록": "hidden",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"underdrain": {
|
||||
"목록": "hidden",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"retaining_wall": {
|
||||
"목록": "both",
|
||||
"횡단도": "span3",
|
||||
"카드이름": "name",
|
||||
"형상": "wall",
|
||||
"형상칸": { "form": "콘크리트", "weep": true },
|
||||
"3D": "wall"
|
||||
},
|
||||
"masonry_wet": {
|
||||
"목록": "hidden",
|
||||
"횡단도": "span3",
|
||||
"카드이름": "name",
|
||||
"형상": "wall",
|
||||
"형상칸": { "form": "돌쌓기(찰)" },
|
||||
"3D": "wall"
|
||||
},
|
||||
"masonry_dry": {
|
||||
"목록": "hidden",
|
||||
"횡단도": "span3",
|
||||
"카드이름": "name",
|
||||
"형상": "wall",
|
||||
"형상칸": { "form": "돌쌓기(메)" },
|
||||
"3D": "wall"
|
||||
},
|
||||
"soil_guard": {
|
||||
"목록": "both",
|
||||
"횡단도": "span3",
|
||||
"카드이름": "name",
|
||||
"형상": "wall",
|
||||
"형상칸": { "form": "통나무·목재틀" },
|
||||
"3D": "wall"
|
||||
},
|
||||
"boulder_masonry": {
|
||||
"목록": "hidden",
|
||||
"횡단도": "span3",
|
||||
"카드이름": "name",
|
||||
"형상": "wall",
|
||||
"형상칸": { "form": "돌쌓기(메)" },
|
||||
"3D": "wall"
|
||||
},
|
||||
"berm": {
|
||||
"목록": "both",
|
||||
"횡단도": "span3",
|
||||
"카드이름": "name",
|
||||
"형상": "berm",
|
||||
"3D": "surface"
|
||||
},
|
||||
"erosion_check": {
|
||||
"목록": "hidden",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"bed_sill": {
|
||||
"목록": "hidden",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"check_dam_small": {
|
||||
"목록": "hidden",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"revetment": {
|
||||
"목록": "hidden",
|
||||
"횡단도": "span3",
|
||||
"카드이름": "name",
|
||||
"형상": "hidden_revet",
|
||||
"3D": "wall",
|
||||
"자리꼴": "interval"
|
||||
},
|
||||
"evac_road": {
|
||||
"목록": "both",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"refuge": {
|
||||
"목록": "hidden",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"work_yard": {
|
||||
"목록": "hidden",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"turnaround": {
|
||||
"목록": "hidden",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"mirror": {
|
||||
"목록": "both",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"curb": { "목록": "both", "횡단도": "none", "카드이름": "name", "형상": "none", "3D": "none" },
|
||||
"hazard_sign": {
|
||||
"목록": "both",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"guardrail": {
|
||||
"목록": "both",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"position_sign": {
|
||||
"목록": "both",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"water_intake": {
|
||||
"목록": "both",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"barrier_gate": {
|
||||
"목록": "both",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"completion_sign": {
|
||||
"목록": "both",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"spoil_bank": {
|
||||
"목록": "both",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "spoil",
|
||||
"3D": "surface"
|
||||
},
|
||||
"borrow_pit": {
|
||||
"목록": "both",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"wildlife_path": {
|
||||
"목록": "b06",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"revegetation": {
|
||||
"목록": "b06",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "none",
|
||||
"3D": "none"
|
||||
},
|
||||
"pavement_concrete": {
|
||||
"목록": "both",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "pavement",
|
||||
"3D": "surface"
|
||||
},
|
||||
"gravel_surfacing": {
|
||||
"목록": "b06",
|
||||
"횡단도": "none",
|
||||
"카드이름": "name",
|
||||
"형상": "pavement",
|
||||
"3D": "surface"
|
||||
},
|
||||
"etc": { "목록": "both", "횡단도": "none", "카드이름": "name", "형상": "none", "3D": "none" }
|
||||
}
|
||||
}
|
||||
@@ -105,10 +105,10 @@ def attach_pavement_specs(sections: list[dict[str, Any]], specs: list[dict[str,
|
||||
def attach_pavement_structures(project_root: Path, cross_sections: list[dict[str, Any]]) -> int:
|
||||
"""저장된 구조물 정본으로 `section.pavement` 를 얹음. 정본을 못 읽으면 0(비치명)."""
|
||||
from B05_Profile.B05_Profile_Structures_Repository import load_structures
|
||||
from B05_Profile.B05_Profile_Structures_Schema import structure_type_map
|
||||
from common_util.common_util_structure_catalog import type_ids_with
|
||||
|
||||
try:
|
||||
type_ids = {key for key, value in structure_type_map().items() if value.group == "G"}
|
||||
type_ids = type_ids_with(shape="pavement") # 표 형상(322-1 · 옛 G 군)
|
||||
structures = [item.model_dump() for item in load_structures(str(project_root))[1]]
|
||||
except Exception: # noqa: BLE001 — 정본을 못 읽어도 횡단 조회는 이어 감
|
||||
logger.exception("B06 노면공 정본을 읽지 못했습니다 (없는 것으로 본다)")
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
* 일반 측점 = 격자 · 시종점(구조물 측점 kind "irregular" 은 뺌) — 끝(노선 길이)을 넘지 않게 자르는 데만 ·
|
||||
* 상세를 읽을 때 한 번 둔다.
|
||||
*
|
||||
* 심는 측점 = 구조물 표(catalog) 횡단도 부품(322-1) — anchor = 기준 하나 · span3 = 시작 · 기준 · 끝 · none = 없음.
|
||||
*
|
||||
* 측점 한 벌(272) — 측점을 심는 관 · 구조물(`plantedChainages`) = 서버 `resolve_extra_stations`
|
||||
* (B05_Profile_Engine_Sections.py) 짝 · 같은 m 면 측점 하나 · 구조물이 위(`oneStationList`) = 서버
|
||||
* `_merge_irregular_into_longitudinal`(B05_Profile_Router_Confirm.py) 짝. 거울 시험 =
|
||||
@@ -16,6 +18,8 @@
|
||||
* 이 목록 하나를 씀.
|
||||
* ========================================================================== */
|
||||
|
||||
import { catalogRow } from "./common_util_structure_catalog";
|
||||
|
||||
const EPS = 1e-6;
|
||||
|
||||
/** 같은 측점으로 볼 거리(m) — 서버 측점 만들기의 격자 붙임 폭(`Engine_Sections_Core`)과 같음. */
|
||||
@@ -111,22 +115,22 @@ function marksOf(
|
||||
);
|
||||
}
|
||||
|
||||
/** 관 시설 하나가 심는 측점 — 독립 기슭막이 = 시작 · 기준 · 끝 · 그 밖(물넘이포장 포함 · 286-1) = 기준. */
|
||||
/** 관 시설 하나가 심는 측점 — 표 횡단도(독립 기슭막이 = span3 · 물넘이포장 포함 그 밖 = anchor · 286-1). */
|
||||
export function pipeStationMarks(pipe: PlantingPipe): StationMark[] {
|
||||
const facility = pipe.facility ?? "pipe";
|
||||
return marksOf(pipe.start_m, pipe.chainage_m, pipe.end_m, facility === "revetment");
|
||||
const station = catalogRow(pipe.facility ?? "pipe")?.station ?? "anchor";
|
||||
if (station === "none") return [];
|
||||
return marksOf(pipe.start_m, pipe.chainage_m, pipe.end_m, station === "span3");
|
||||
}
|
||||
|
||||
/** 구조물 하나가 심는 측점 — A군 = 기준 · C · D군 구간형 = 시작 · 기준 · 끝 · 다른 정본 소관 · 그 밖 = 없음. */
|
||||
/** 구조물 하나가 심는 측점 — 표 횡단도(anchor = 기준 · span3 = 시작 · 기준 · 끝) · 관 지점 정본 소관 · 모르는 종류 = 없음. */
|
||||
export function structureStationMarks(
|
||||
structure: PlantingStructure,
|
||||
type: PlantingType | undefined,
|
||||
): StationMark[] {
|
||||
if (!type || type.managed_by) return [];
|
||||
const interval = type.placement === "interval";
|
||||
if (type.group !== "A" && !(interval && (type.group === "C" || type.group === "D"))) return [];
|
||||
const row = type && catalogRow(type.type_id);
|
||||
if (!row || row.owner === "pipe_points" || row.station === "none") return [];
|
||||
const anchor = structure.chainage_m ?? structure.start_m;
|
||||
return marksOf(structure.start_m, anchor, structure.end_m, interval);
|
||||
return marksOf(structure.start_m, anchor, structure.end_m, row.station === "span3");
|
||||
}
|
||||
|
||||
/** 측점을 심는 자리 전부(오름차순 · 겹침 그대로) — 서버 `resolve_extra_stations` 의 누가거리. */
|
||||
|
||||
@@ -5,9 +5,11 @@
|
||||
* 기준 측점(0.02 m 안) 횡단 dict 에 `underdrain` = 높이 · 경사 — 횡단도가 돌 무늬 형상을 그리고,
|
||||
* 설계 계산은 그 측점을 측구 없이 지대 낮은 쪽 한 경사로 기울임(`tilted`).
|
||||
* 파이썬 짝 = `B06_Section/B06_Section_Engine_Tilt.py`(`load_underdrains` · `attach_underdrains`).
|
||||
* 맹암거 종류 = 구조물 표 형상 underdrain(322-1).
|
||||
* ========================================================================== */
|
||||
|
||||
export const UNDERDRAIN_TYPE_ID = "cross_underdrain";
|
||||
import { catalogRow } from "./common_util_structure_catalog";
|
||||
|
||||
const EDGE_TOLERANCE_M = 0.02;
|
||||
|
||||
/** 맹암거 제원 — 높이 · 경사가 비면 null(높이 없으면 형상 없음 · 경사 없으면 표준 횡단경사). */
|
||||
@@ -35,7 +37,7 @@ const positive = (value: unknown): number | null => {
|
||||
export function underdrainSpecsFrom(structures: readonly UnderdrainInput[]): UnderdrainSpec[] {
|
||||
return structures.flatMap((structure) => {
|
||||
const at = structure.chainage_m ?? structure.start_m;
|
||||
if (structure.type_id !== UNDERDRAIN_TYPE_ID || typeof at !== "number") return [];
|
||||
if (catalogRow(structure.type_id)?.shape !== "underdrain" || typeof at !== "number") return [];
|
||||
const options = structure.options ?? {};
|
||||
return [
|
||||
{
|
||||
|
||||
@@ -8,8 +8,12 @@
|
||||
* 산식이 같아야 하므로 두 파일 머리에 짝임을 적고 거울 테스트를 둔다.
|
||||
*
|
||||
* 얹는 것은 제원뿐이다 — 도형·면적은 `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;
|
||||
@@ -54,7 +58,7 @@ export interface WallTier {
|
||||
const EDGE_TOLERANCE_M = 0.02;
|
||||
|
||||
/**
|
||||
* 구조물 종류 → 횡단 기하가 아는 형태 이름 — 파이썬 `_FORM_BY_TYPE` 와 같은 표.
|
||||
* 구조물 종류 → 횡단 기하가 아는 형태 이름 = 표 형상칸.form(파이썬 `_wall_form` 짝).
|
||||
*
|
||||
* ⚠⚠ **「형상 동일」이 「수량 동일」이 아니다**(2026-09-09 사용자 확정 4차 원문:
|
||||
* 「흙막이는 횡단도에서 표현방식들과 옵션들은 동일하게 반영 · **형상은 동일** ·
|
||||
@@ -64,12 +68,9 @@ const EDGE_TOLERANCE_M = 0.02;
|
||||
* ⚠ 반대 방향의 사례가 골막이다 — 그림도 수량도 다르고, 두께식마저 다른 식이었다.
|
||||
* **구조물 계열이 다르면 상수를 나눠 쓸 것.**
|
||||
*/
|
||||
export const FORM_BY_TYPE: Record<string, string> = {
|
||||
masonry_wet: "돌쌓기(찰)",
|
||||
masonry_dry: "돌쌓기(메)",
|
||||
boulder_masonry: "돌쌓기(메)",
|
||||
retaining_wall: "콘크리트",
|
||||
soil_guard: "통나무·목재틀",
|
||||
const formOf = (typeId: string): string | null => {
|
||||
const form = catalogRow(typeId)?.shapeOpts.form;
|
||||
return typeof form === "string" ? form : null;
|
||||
};
|
||||
|
||||
const num = (value: unknown): number | null =>
|
||||
@@ -117,7 +118,7 @@ export function wallSpecsFrom(
|
||||
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) || FORM_BY_TYPE[structure.type_id] || null,
|
||||
form: (options.form as string) || formOf(structure.type_id),
|
||||
height_m: num(options.height_m),
|
||||
side: (options.side as string) ?? null,
|
||||
// 기초 축 — 저장 칸과 **같은 글자**. 초안 경로에서 빠지면 터파기가 안 그려진다.
|
||||
|
||||
Reference in New Issue
Block a user