Merge remote-tracking branch 'origin/main_laptop_1' into sub_desktop_1
This commit is contained in:
@@ -154,6 +154,7 @@ async def sync_uphill_overrides_into_designs(
|
||||
rock_boundary_offset_m=design.get("rock_boundary_offset_m"),
|
||||
two_stage_slope=bool(design.get("two_stage_slope", True)),
|
||||
ditch_enabled=design.get("ditch_enabled"),
|
||||
ditch_choice=design.get("ditch_choice"),
|
||||
surface_drop_m=ford_drop_at(float(chainage), ford_drops),
|
||||
**curve_widening_args(cross_record),
|
||||
)
|
||||
|
||||
@@ -426,6 +426,8 @@ export interface CrossDesign {
|
||||
| { type: "none" };
|
||||
/** 측구 생성 여부(엔진이 자동/override 반영해 실제 적용한 결과). */
|
||||
ditch_enabled?: boolean;
|
||||
/** 사용자가 정한 선택(`null` = 자동). 결과와 다른 값이다. */
|
||||
ditch_choice?: boolean | null;
|
||||
/** 포장 중첩 여부와 포장층 두께(포장 시). */
|
||||
paved: boolean;
|
||||
pavement_thickness_m?: number;
|
||||
@@ -517,8 +519,10 @@ export interface CrossDesignRequest {
|
||||
two_stage_slope?: boolean;
|
||||
/** 이 측점만 쓰는 암 절토 경사비(1:n 의 n). 없으면 표준값(2026-09-07). */
|
||||
cut_slope_ratio?: number | null;
|
||||
/** 측구 생성 여부. null/미지정=자동 판정, true/false=수동 override. */
|
||||
/** 옛 이름 — 결과값이 그대로 실려 오던 자리(호환). 새 요청은 `ditch_choice` 를 쓴다. */
|
||||
ditch_enabled?: boolean | null;
|
||||
/** 측구를 둘지 **사용자가 정한 선택**. `null`/미지정 = 자동 판정(2026-09-09 정리). */
|
||||
ditch_choice?: boolean | null;
|
||||
/** 설정 패널 편집값. 요청값 → config 기본값 순으로 우선한다. */
|
||||
standard_cross_section?: StandardCrossSection;
|
||||
}
|
||||
|
||||
@@ -287,6 +287,9 @@ function refreshLocally(input: CrossRefreshInput): number[] | null {
|
||||
twoStageSlope:
|
||||
choice?.two_stage_slope ??
|
||||
(design.two_stage_slope === undefined ? true : Boolean(design.two_stage_slope)),
|
||||
// ⚠ **선택과 결과를 갈라 넘긴다**(2026-09-09). 선택이 있으면 그것을 따르고,
|
||||
// 없으면 옛 저장분의 결과를 **자동값과 다를 때만** 선택으로 살린다.
|
||||
ditchChoice: typeof design.ditch_choice === "boolean" ? design.ditch_choice : null,
|
||||
ditchEnabled: typeof design.ditch_enabled === "boolean" ? design.ditch_enabled : null,
|
||||
// 세월교 월류 하강은 계획선 편집으로 바뀌지 않는다 — 저장분 값을 그대로 잇는다.
|
||||
surfaceDropM: typeof design.surface_drop_m === "number" ? design.surface_drop_m : 0,
|
||||
|
||||
@@ -171,6 +171,7 @@ def compute_cross_design(
|
||||
two_stage_slope: bool = True,
|
||||
cut_slope_ratio: float | None = None,
|
||||
ditch_enabled: bool | None = None,
|
||||
ditch_choice: bool | None = None,
|
||||
surface_drop_m: float = 0.0,
|
||||
plan_radius_m: float | None = None,
|
||||
curve_outer_side: str | None = None,
|
||||
@@ -272,7 +273,9 @@ def compute_cross_design(
|
||||
soil_cut_ratio=soil_cut_ratio,
|
||||
rock_boundary_offset_m=rock_boundary_offset_m,
|
||||
two_stage_slope=enable_two_stage,
|
||||
ditch_enabled=ditch_enabled,
|
||||
# 선택은 `ditch_choice`, 옛 저장분은 `ditch_enabled` 로 온다(자동값과 다를 때만 뜻).
|
||||
ditch_choice=ditch_choice,
|
||||
legacy_ditch_enabled=ditch_enabled,
|
||||
widening_left_m=widening_left,
|
||||
widening_right_m=widening_right,
|
||||
berm=berm,
|
||||
@@ -425,7 +428,10 @@ def compute_cross_design(
|
||||
"widening_right_m": round(geometry.half_road_right - geometry.half_road, 4),
|
||||
"cross_slope_pct": round(cross_slope_pct, 4),
|
||||
"ditch": ditch_spec,
|
||||
# 결과 — **실제로 섰나**. 읽는 쪽 여섯이 이 뜻으로 쓴다.
|
||||
"ditch_enabled": bool(geometry.has_ditch),
|
||||
# 선택 — **사용자가 정한 것**(`None` = 자동). 결과와 갈라 둔다(2026-09-09).
|
||||
"ditch_choice": geometry.ditch_choice,
|
||||
"paved": bool(paved),
|
||||
# 노면(노견 포함) 양 끝점 — 노면 렌더링 기준.
|
||||
"road_edges": {
|
||||
|
||||
@@ -54,7 +54,8 @@ class _SectionGeometry:
|
||||
soil_cut_ratio: float | None = None,
|
||||
rock_boundary_offset_m: float | None = None,
|
||||
two_stage_slope: bool = False,
|
||||
ditch_enabled: bool | None = None,
|
||||
ditch_choice: bool | None = None,
|
||||
legacy_ditch_enabled: bool | None = None,
|
||||
widening_left_m: float = 0.0,
|
||||
widening_right_m: float = 0.0,
|
||||
berm: BermSpec | None = None,
|
||||
@@ -108,18 +109,34 @@ class _SectionGeometry:
|
||||
else "fill"
|
||||
)
|
||||
|
||||
# 측구 생성 여부(D-1): 양성은 항상 미생성. ditch_enabled가 오면 그 값을 따르고(수동
|
||||
# override), None이면 자동 판정 — 측구측 노면 끝에서 지반이 설계면보다 높으면(절토
|
||||
# 상황) 생성, 낮으면(성토 상황, 자연 배수) 미생성. ground_at 없으면 보수적으로 생성.
|
||||
# 측구 생성 여부(D-1) — **자동 판정이 먼저, 사용자 선택이 그 위**(2026-09-09 정리).
|
||||
#
|
||||
# ⚠⚠ **한 칸에 두 뜻이 담겨 있던 자리다.** 결과(`ditch_enabled` = 실제 생성됨)를
|
||||
# 그대로 다시 입력으로 넣어 읽었으므로, **한 번 저장되면 자동 판정이 영영 다시
|
||||
# 안 돌았다.** 계획고를 내려 절토가 생겨도 측구가 안 서고 **아무 말도 안 나왔다.**
|
||||
# ⇒ 이제 **선택은 `ditch_choice`**(없음 = 자동)이고 **결과는 `ditch_enabled`** 다.
|
||||
# ⚠ 옛 저장분(`ditch_choice` 가 없던 것)은 `legacy_ditch_enabled` 로 온다. 그 값은
|
||||
# **자동값과 다를 때만 뜻이 있다** — 같으면 자동이 그렇게 냈던 것이고, 다르면
|
||||
# 사용자가 일부러 바꾼 것이다. 그래서 **다를 때만** 선택으로 살린다(설계 의도 보존).
|
||||
if section_mode == "both_fill":
|
||||
self.has_ditch = False
|
||||
elif ditch_enabled is not None:
|
||||
self.has_ditch = ditch_enabled
|
||||
auto_ditch = False
|
||||
elif ground_at is not None:
|
||||
ditch_edge = self.left_extent if ditch_side == "left" else -self.right_extent
|
||||
self.has_ditch = ground_at(ditch_edge) > self.road_z(ditch_edge) + 1e-3
|
||||
auto_ditch = ground_at(ditch_edge) > self.road_z(ditch_edge) + 1e-3
|
||||
else:
|
||||
self.has_ditch = True
|
||||
auto_ditch = True # ground_at 이 없으면 보수적으로 생성
|
||||
choice = ditch_choice
|
||||
if choice is None and legacy_ditch_enabled is not None:
|
||||
choice = bool(legacy_ditch_enabled)
|
||||
# ⚠ **자동과 같은 값을 고른 것은 「자동」으로 본다**(2026-09-09). 화면 토글이 2단이라
|
||||
# 「자동으로 되돌리기」 단추가 없다 — 되돌리려면 원래 값으로 다시 누르는 수밖에
|
||||
# 없는데, 그것을 선택으로 굳히면 **다시 같은 병**(지형이 바뀌어도 안 따라감)이 된다.
|
||||
if choice is not None and bool(choice) == auto_ditch:
|
||||
choice = None
|
||||
# 양성(both_fill)은 측구가 설 자리가 없다 — 선택보다 기하가 먼저다.
|
||||
self.has_ditch = auto_ditch if (choice is None or section_mode == "both_fill") else choice
|
||||
#: 그 측점에 **사용자가 정한 선택**(없으면 자동). 결과와 갈라 내보낸다.
|
||||
self.ditch_choice = choice
|
||||
|
||||
# 측구 꼭짓점(측구측 노면 끝 기준, 바깥 방향 부호 적용).
|
||||
self.ditch_points: list[tuple[float, float]] = []
|
||||
|
||||
@@ -65,6 +65,17 @@ def _spans(sections: list[dict[str, Any]], start_m: float, end_m: float) -> list
|
||||
return spans
|
||||
|
||||
|
||||
def _has_spoil_value(design: dict[str, Any]) -> bool:
|
||||
"""그 측점에 **사토장 값이 실제로 남아 있나**. 칸이 0 으로 있는 것은 값이 아니다."""
|
||||
for key in ("spoil_fill_area_m2", "spoil_fill_width_m", "spoil_fill_capacity_m3"):
|
||||
try:
|
||||
if float(design.get(key) or 0.0) > 0:
|
||||
return True
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
return bool(design.get("spoil_fill_structure_id"))
|
||||
|
||||
|
||||
def _side_of(design: dict[str, Any], option: Any) -> str | None:
|
||||
"""쌓는 쪽 — 「좌·우」면 그대로, 「자동」이면 그 측점의 **성토 쪽**."""
|
||||
word = str(option or "").strip()
|
||||
@@ -169,10 +180,8 @@ def enforce_spoil_fills(
|
||||
try:
|
||||
_revision, structures = load_structures(str(project_root))
|
||||
except Exception: # noqa: BLE001 — 정본이 없으면 사토장도 없다
|
||||
return 0
|
||||
structures = []
|
||||
sites = spoil_sites(structures)
|
||||
if not sites:
|
||||
return 0
|
||||
|
||||
def recompute(section, side, width_m, slope_ratio_n, longitudinal_data, standard_spec):
|
||||
design = section.get("design")
|
||||
@@ -193,6 +202,7 @@ def enforce_spoil_fills(
|
||||
two_stage_slope=bool(design.get("two_stage_slope", True)),
|
||||
cut_slope_ratio=stored_cut_slope(design),
|
||||
ditch_enabled=design.get("ditch_enabled"),
|
||||
ditch_choice=design.get("ditch_choice"),
|
||||
surface_drop_m=float(design.get("surface_drop_m") or 0.0),
|
||||
berm=stored_berm(design),
|
||||
spoil_fill={
|
||||
@@ -206,6 +216,45 @@ def enforce_spoil_fills(
|
||||
return None
|
||||
|
||||
changed = 0
|
||||
# ⚠⚠ **지운 사토장이 그림·수량에 남던 자리**(2026-09-09 화면 실측으로 잡음).
|
||||
# `enforce_spoil_fills` 는 **얹기만** 했으므로 구조물을 지워도 저장분의 `spoil_fill_*`
|
||||
# 가 그대로 남아 **횡단도에 계속 그려지고 면적표에도 섰다.**
|
||||
# ⇒ 사토장이 덮지 않는 측점에 값이 남아 있으면 **사토장 없이 다시 계산**해 지운다.
|
||||
# (노선 성토도 그때 원래 값으로 돌아온다 — 사토장 몫을 빼 두었기 때문이다.)
|
||||
covered: set[float] = set()
|
||||
for site in sites:
|
||||
for section in _sections_in(cross_sections, site["start_m"], site["end_m"]):
|
||||
covered.add(float(section["chainage_m"]))
|
||||
for section in cross_sections:
|
||||
design = section.get("design")
|
||||
if not isinstance(design, dict):
|
||||
continue
|
||||
chainage = section.get("chainage_m")
|
||||
if chainage is None or float(chainage) in covered:
|
||||
continue
|
||||
# ⚠ **칸이 있는 것과 값이 있는 것은 다르다** — 설계 결과는 사토장이 없어도
|
||||
# `spoil_fill_area_m2: 0.0` 을 늘 싣는다. 「값이 남아 있는」 측점만 되돌린다.
|
||||
if not _has_spoil_value(design):
|
||||
continue
|
||||
side = _side_of(design, None)
|
||||
plain = recompute(section, side, 0.0, None, longitudinal, standard) if side else None
|
||||
if plain is None:
|
||||
# 다시 계산할 수 없으면 **적어도 칸은 지운다** — 값이 남아 그려지는 것보다 낫다.
|
||||
for key in [k for k in design if str(k).startswith("spoil_fill_")]:
|
||||
design.pop(key, None)
|
||||
# 노선 성토는 사토장 몫을 뺀 값이라, 다시 계산 못 하면 그 몫을 되돌려 준다.
|
||||
moved = float(design.get("spoil_fill_replaced_fill_m2") or 0.0)
|
||||
if moved > 0:
|
||||
design["fill_area_m2"] = round(float(design.get("fill_area_m2") or 0.0) + moved, 4)
|
||||
changed += 1
|
||||
continue
|
||||
for key in ("status", "pavement_suggested", *USER_TOUCHED_KEYS):
|
||||
if design.get(key) is not None:
|
||||
plain[key] = design[key]
|
||||
section["design"] = plain
|
||||
changed += 1
|
||||
if not sites:
|
||||
return changed
|
||||
for site in sites:
|
||||
sections = _sections_in(cross_sections, site["start_m"], site["end_m"])
|
||||
if not sections:
|
||||
|
||||
@@ -641,6 +641,7 @@ async def compute_cross_section_design(
|
||||
# 측점별 암 절토 경사 — 요청값이 없으면 저장분에서 잇는다(2026-09-07).
|
||||
cut_slope_ratio=request.cut_slope_ratio or stored_cut_slope(stored_design or {}),
|
||||
ditch_enabled=request.ditch_enabled,
|
||||
ditch_choice=request.ditch_choice,
|
||||
surface_drop_m=ford_drop_at(request.chainage_m, ford_surface_drops(project_root)),
|
||||
berm=stored_berm(stored_design or {}),
|
||||
**curve_widening_args(cross_record),
|
||||
|
||||
@@ -198,6 +198,7 @@ def enforce_pavement_ranges(
|
||||
two_stage_slope=bool(design.get("two_stage_slope", True)),
|
||||
cut_slope_ratio=stored_cut_slope(design),
|
||||
ditch_enabled=design.get("ditch_enabled"),
|
||||
ditch_choice=design.get("ditch_choice"),
|
||||
surface_drop_m=ford_drop_at(chainage, ford_drops),
|
||||
berm=stored_berm(design),
|
||||
**curve_widening_args(section),
|
||||
@@ -251,6 +252,7 @@ def enforce_ford_surface_drops(
|
||||
two_stage_slope=bool(design.get("two_stage_slope", True)),
|
||||
cut_slope_ratio=stored_cut_slope(design),
|
||||
ditch_enabled=design.get("ditch_enabled"),
|
||||
ditch_choice=design.get("ditch_choice"),
|
||||
surface_drop_m=wanted,
|
||||
berm=stored_berm(design),
|
||||
**curve_widening_args(section),
|
||||
@@ -446,6 +448,7 @@ def recompute_designs_for_alignment(
|
||||
else stored_cut_slope(stored)
|
||||
),
|
||||
ditch_enabled=stored.get("ditch_enabled"),
|
||||
ditch_choice=stored.get("ditch_choice"),
|
||||
surface_drop_m=ford_drop_at(chainage, ford_drops),
|
||||
berm=session_berms.get(key) or stored_berm(stored),
|
||||
**curve_widening_args(section),
|
||||
|
||||
@@ -55,8 +55,10 @@ class CrossDesignRequest(BaseModel):
|
||||
# 이 측점만 쓰는 암 절토 경사비(1:n 의 n) — 카드에서 넣은 값(2026-09-07 사용자 지시).
|
||||
# None 이면 표준 횡단면 설정값을 쓴다.
|
||||
cut_slope_ratio: float | None = Field(default=None, gt=0)
|
||||
# 측구 생성 여부. None=자동 판정(측구측 절토면만 생성), True/False=수동 override.
|
||||
# 옛 이름 — **결과값**이 그대로 실려 오던 자리(호환). 자동값과 다를 때만 선택으로 산다.
|
||||
ditch_enabled: bool | None = None
|
||||
# 측구를 둘지 **사용자가 정한 선택**. None = 자동 판정(2026-09-09 갈라냄).
|
||||
ditch_choice: bool | None = None
|
||||
# B06 설정 패널 편집값(STANDARD_CROSS_SECTION 형태). 요청값 → config 기본값 순.
|
||||
standard_cross_section: dict[str, Any] | None = None
|
||||
|
||||
|
||||
@@ -113,8 +113,9 @@ export interface CrossDesignChange {
|
||||
paved: boolean;
|
||||
/** 암 지반 2단계 경사(암반 경계 아래=암, 위=토사) 적용 여부. 기본 true, 토글로 해제. */
|
||||
two_stage_slope: boolean;
|
||||
/** 측구 생성 여부. null=자동 판정, true/false=수동 override. */
|
||||
ditch_enabled: boolean | null;
|
||||
/** 측구를 둘지 **사용자가 정한 선택**. `null` = 자동 판정.
|
||||
* ⚠ 결과(`design.ditch_enabled` = 실제 섰나)와 **다른 값**이다(2026-09-09 정리). */
|
||||
ditch_choice: boolean | null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -337,7 +338,9 @@ export function buildDesignControls(
|
||||
paved: design?.paved ?? false,
|
||||
// 암 design일 때만 저장값을 신뢰(토사는 two_stage=false echo가 무의미) — 암 전환 시 기본 복합경사(4번).
|
||||
twoStage: design && isRock(design.ground_type) ? (design.two_stage_slope ?? true) : true,
|
||||
ditchEnabled: design?.ditch_enabled ?? null,
|
||||
// ⚠ **결과가 아니라 선택을 읽는다.** 결과를 읽으면 한 번 저장된 뒤로
|
||||
// 자동 판정이 영영 안 돈다(2026-09-09에 갈라낸 자리).
|
||||
ditchEnabled: design?.ditch_choice ?? null,
|
||||
};
|
||||
const bar = document.createElement("div");
|
||||
bar.className = "b06-design";
|
||||
@@ -358,7 +361,7 @@ export function buildDesignControls(
|
||||
ditch_type: state.ditchType,
|
||||
paved: state.paved,
|
||||
two_stage_slope: state.twoStage,
|
||||
ditch_enabled: state.ditchEnabled,
|
||||
ditch_choice: state.ditchEnabled,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -386,6 +389,7 @@ export function buildDesignControls(
|
||||
const rockCut = isRock(state.ground) && state.mode !== "both_fill";
|
||||
const hasDitch = state.mode !== "both_fill";
|
||||
// 측구 생성 여부(자동 판정 or 사용자 override) — 측구형식은 측구가 있을 때만 의미 있다.
|
||||
// 화면 표시는 **결과**를 보인다 — 선택이 없으면 자동으로 선 결과가 답이다.
|
||||
const ditchOn = state.ditchEnabled ?? design?.ditch_enabled ?? true;
|
||||
|
||||
// 경사 방향(좌/우): 양절·양성에서 활성. 항상 인라인 노출(오버플로 대상 아님). 라벨 삭제(E-7).
|
||||
|
||||
@@ -36,7 +36,11 @@ export function spoilFillTooltip(design: SpoilFillDrawing): string {
|
||||
const placed = Number(design.spoil_fill_placed_m3 ?? 0);
|
||||
lines.push(`구간 용량 ${capacity.toFixed(1)}㎥ 중 ${placed.toFixed(1)}㎥ 담김`);
|
||||
const unplaced = Number(design.spoil_fill_unplaced_m3 ?? 0);
|
||||
if (unplaced > 0) {
|
||||
// ⚠ **적어 보이는 0 을 경고로 띄우지 않는다**(2026-09-09 화면 실측). 폭을 이분법으로
|
||||
// 찾으므로 용량과 담긴 양이 소수점 아래에서 조금 남는다(400 − 399.9937 = 0.0063).
|
||||
// 그것을 「못 담음」으로 띄우면 **늘 경고가 뜬 채**가 되어 진짜 경고가 안 보인다.
|
||||
// 표시 자릿수(0.1㎥)에서 보이지 않는 몫은 없는 것으로 본다.
|
||||
if (unplaced >= 0.05) {
|
||||
lines.push(`⚠ ${unplaced.toFixed(1)}㎥ 는 못 담음 — 지반 자료가 있는 데까지만 넓힘`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@ export async function renderB06ProfileCross(root: HTMLElement): Promise<void> {
|
||||
ditch_type: design.ditch_type ?? "standard",
|
||||
paved: design.paved,
|
||||
two_stage_slope: design.two_stage_slope ?? true,
|
||||
ditch_enabled: design.ditch_enabled ?? null,
|
||||
ditch_choice: design.ditch_choice ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -93,6 +93,9 @@ export interface CrossDesignOptions {
|
||||
* 짝: 파이썬 `compute_cross_design(cut_slope_ratio=…)`. */
|
||||
cutSlopeRatio?: number | null;
|
||||
ditchEnabled?: boolean | null;
|
||||
/** 측구를 둘지 **사용자가 정한 선택**(`null` = 자동 판정).
|
||||
* 짝: 파이썬 `compute_cross_design(ditch_choice=…)`. */
|
||||
ditchChoice?: boolean | null;
|
||||
/** 세월교 월류 높이만큼 노면을 통째로 내린다(m). */
|
||||
surfaceDropM?: number;
|
||||
/** 이 측점의 평면 곡선반경(m) — 곡선부 확폭을 정하는 입력. 직선이면 null. */
|
||||
@@ -136,7 +139,10 @@ export interface CrossDesignResult {
|
||||
widening_right_m?: number;
|
||||
cross_slope_pct: number;
|
||||
ditch: Record<string, unknown>;
|
||||
/** 결과 — **실제로 섰나**. */
|
||||
ditch_enabled: boolean;
|
||||
/** 선택 — **사용자가 정한 것**(`null` = 자동). 결과와 갈라 둔다(2026-09-09). */
|
||||
ditch_choice: boolean | null;
|
||||
paved: boolean;
|
||||
road_edges: { left: CrossDesignEdge; right: CrossDesignEdge };
|
||||
carriageway_edges: { left: CrossDesignEdge; right: CrossDesignEdge };
|
||||
@@ -348,6 +354,7 @@ export function computeCrossDesign(
|
||||
rockBoundaryOffsetM,
|
||||
twoStageSlope: enableTwoStage,
|
||||
ditchEnabled: options.ditchEnabled ?? null,
|
||||
ditchChoice: options.ditchChoice ?? null,
|
||||
berm: options.berm ?? null,
|
||||
});
|
||||
|
||||
@@ -508,6 +515,7 @@ export function computeCrossDesign(
|
||||
cross_slope_pct: round4(crossSlopePct),
|
||||
ditch: ditchSpec,
|
||||
ditch_enabled: geometry.hasDitch,
|
||||
ditch_choice: geometry.ditchChoice,
|
||||
paved,
|
||||
road_edges: {
|
||||
left: {
|
||||
|
||||
@@ -106,6 +106,8 @@ export class SectionGeometry {
|
||||
ditchType: string;
|
||||
slopePerOffset: number;
|
||||
hasDitch: boolean;
|
||||
/** 사용자가 정한 선택(`null` = 자동). 결과(`hasDitch`)와 다른 값이다. */
|
||||
ditchChoice: boolean | null;
|
||||
ditchPoints: Array<[number, number]> = [];
|
||||
private groundAt: ((offsetM: number) => number) | null;
|
||||
private rockOffset: number;
|
||||
@@ -127,7 +129,10 @@ export class SectionGeometry {
|
||||
soilCutRatio: number | null;
|
||||
rockBoundaryOffsetM: number | null;
|
||||
twoStageSlope: boolean;
|
||||
/** 옛 저장분의 결과값 — **자동값과 다를 때만** 선택으로 살린다. */
|
||||
ditchEnabled: boolean | null;
|
||||
/** 사용자가 정한 선택(없으면 자동). */
|
||||
ditchChoice?: boolean | null;
|
||||
/** 곡선부 확폭(m) — 붙는 쪽만 값이 있고 반대쪽은 0이다. */
|
||||
wideningLeftM?: number;
|
||||
wideningRightM?: number;
|
||||
@@ -167,17 +172,37 @@ export class SectionGeometry {
|
||||
groundAt(-this.rightExtent) > this.roadZ(-this.rightExtent) + 1e-3 ? "cut" : "fill";
|
||||
}
|
||||
|
||||
// 측구 생성 여부(D-1).
|
||||
// 측구 생성 여부(D-1) — **자동 판정이 먼저, 사용자 선택이 그 위**(2026-09-09 정리).
|
||||
//
|
||||
// ⚠⚠ **한 칸에 두 뜻이 담겨 있던 자리다**(짝: 파이썬 `_SectionGeometry`). 결과
|
||||
// (`ditch_enabled` = 실제 생성됨)를 그대로 다시 입력으로 넣어 읽었으므로 **한 번
|
||||
// 저장되면 자동 판정이 영영 다시 안 돌았다.** 계획고를 내려 절토가 생겨도 측구가
|
||||
// 안 서고 아무 말도 안 나왔다. ⇒ **선택은 `ditchChoice`**(없음 = 자동), **결과는
|
||||
// `hasDitch`** 로 가른다.
|
||||
// ⚠ 옛 저장분은 `ditchEnabled` 로 온다. 그 값은 **자동값과 다를 때만 뜻이 있다** —
|
||||
// 같으면 자동이 그렇게 냈던 것이고, 다르면 사용자가 일부러 바꾼 것이다.
|
||||
let autoDitch: boolean;
|
||||
if (params.sectionMode === "both_fill") {
|
||||
this.hasDitch = false;
|
||||
} else if (params.ditchEnabled !== null && params.ditchEnabled !== undefined) {
|
||||
this.hasDitch = params.ditchEnabled;
|
||||
autoDitch = false;
|
||||
} else if (groundAt !== null) {
|
||||
const ditchEdge = params.ditchSide === "left" ? this.leftExtent : -this.rightExtent;
|
||||
this.hasDitch = groundAt(ditchEdge) > this.roadZ(ditchEdge) + 1e-3;
|
||||
autoDitch = groundAt(ditchEdge) > this.roadZ(ditchEdge) + 1e-3;
|
||||
} else {
|
||||
this.hasDitch = true;
|
||||
autoDitch = true; // groundAt 이 없으면 보수적으로 생성
|
||||
}
|
||||
let choice = params.ditchChoice ?? null;
|
||||
const legacy = params.ditchEnabled;
|
||||
if (choice === null && legacy !== null && legacy !== undefined) {
|
||||
choice = Boolean(legacy);
|
||||
}
|
||||
// ⚠ **자동과 같은 값을 고른 것은 「자동」으로 본다**(짝: 파이썬). 토글이 2단이라
|
||||
// 「자동으로 되돌리기」 단추가 없고, 원래 값으로 다시 누른 것을 선택으로 굳히면
|
||||
// **다시 같은 병**이 된다.
|
||||
if (choice !== null && Boolean(choice) === autoDitch) choice = null;
|
||||
// 양성(both_fill)은 측구가 설 자리가 없다 — 선택보다 기하가 먼저다.
|
||||
this.hasDitch = choice === null || params.sectionMode === "both_fill" ? autoDitch : choice;
|
||||
/** 그 측점에 **사용자가 정한 선택**(없으면 자동). 결과와 갈라 내보낸다. */
|
||||
this.ditchChoice = choice;
|
||||
|
||||
// 측구 꼭짓점(측구측 노면 끝 기준, 바깥 방향 부호 적용).
|
||||
const edgeOffset = params.ditchSide === "left" ? this.leftExtent : -this.rightExtent;
|
||||
|
||||
Reference in New Issue
Block a user