diff --git a/B05_Profile/B05_Profile_UI_Profile_Render.ts b/B05_Profile/B05_Profile_UI_Profile_Render.ts index bbb001486..13880cf22 100644 --- a/B05_Profile/B05_Profile_UI_Profile_Render.ts +++ b/B05_Profile/B05_Profile_UI_Profile_Render.ts @@ -294,6 +294,11 @@ export function renderProfile(ctx: ProfileRenderContext): void { ), selectedStationId, stationDisplay, + mcKeys: new Set( + (longitudinal.stations ?? []) + .filter((station) => station.kind === "curve") + .map((station) => station.chainage_m.toFixed(3)), + ), onCurveRadiusChange: (curve, radius) => ctx.applyEdits(setCurveRadius(store.edits(), curve, radius ?? 0)), // 값 열 계획고 직접 입력 → 규칙 측점과 동일한 station_offset 파이프라인. diff --git a/B05_Profile/B05_Profile_UI_Profile_Table.ts b/B05_Profile/B05_Profile_UI_Profile_Table.ts index 3dd442f14..7f137d4b3 100644 --- a/B05_Profile/B05_Profile_UI_Profile_Table.ts +++ b/B05_Profile/B05_Profile_UI_Profile_Table.ts @@ -43,6 +43,8 @@ export interface ProfileTableOptions { irregularStations?: IrregularStation[]; /** 현재 선택된 측점 id. 규칙/비정규 측점 모두 그 측점의 값 열을 테이블에 겹쳐 보여준다. */ selectedStationId?: string | null; + /** MC(곡선 가운데) 측점 누가거리(`toFixed(3)` 열쇠) — 측점 행에 「 (MC)」 · 크게 · 굵게 · 경고색(507-3 덧). */ + mcKeys?: ReadonlySet; /** 이어 공사 시작 기준 — 측점번호·누가거리 표시를 이만큼 더한다(내부 chainage는 0기준 유지). */ stationDisplay?: { station: number; cumulative: number }; onCurveRadiusChange: (curve: AlignmentCurve, radiusM: number | null) => void; @@ -55,6 +57,8 @@ interface StationRowSpec { unit: string; cell: (index: number) => string; modifier?: string; + /** 측점 이름 행 — MC 열에 (MC) 강조. */ + station?: boolean; } interface SegmentRowSpec { @@ -208,6 +212,7 @@ function buildStationRows( { label: "측점", unit: "측점번호+잔여거리", + station: true, cell: (index) => displayStationLabel(stations[index].chainage_m, interval, display.station), }, ]; @@ -458,6 +463,7 @@ function buildSelectedColumn( display: { station: number; cumulative: number }, onAdjustStation?: (chainageM: number, deltaM: number) => void, isIrregular = false, + mc = false, ): HTMLElement { const plan = interpolateSample(alignment.samples, chainage, "elevation_m"); const ground = interpolateSample(alignment.samples, chainage, "ground_elevation_m"); @@ -487,7 +493,12 @@ function buildSelectedColumn( { value: gradeCell((segment) => segment.grade_percent, 2), modifier: "grade" }, // 구배 S { value: { text: plan !== null ? plan.toFixed(2) : "" }, modifier: "plan", plan: true }, // 계획고 { value: { text: ground !== null ? ground.toFixed(2) : "" }, modifier: "" }, // 지반고 - { value: { text: displayStationLabel(chainage, interval, display.station) }, modifier: "" }, // 측점 + { + value: { + text: displayStationLabel(chainage, interval, display.station) + (mc ? " (MC)" : ""), + }, + modifier: mc ? "mc" : "", + }, // 측점 { value: { text: curve ? curve.l_m.toFixed(2) : "" }, modifier: "" }, // 곡선 L { value: { text: curve ? curve.r_m.toFixed(1) : "" }, modifier: "" }, // 곡선 R ]; @@ -617,10 +628,13 @@ export function createProfileTable(options: ProfileTableOptions): HTMLElement { alignment.stations.forEach((_, stationIndex) => { // 구조물 측점 열은 기본값을 비운다 — 격자(빈 셀)만 남기고 값은 선택 오버레이가 맡는다. const structural = isStructural[stationIndex]; + // MC 열 = 측점 행에 「 (MC)」 · 굵게 · 경고색(507-3 덧) — 칸 폭 판정(`longest`)은 옛 글 그대로 + const mc = + spec.station && options.mcKeys?.has(alignment.stations[stationIndex].chainage_m.toFixed(3)); const cell = element( "span", - `b05-profile-table__cell${structural ? " is-structure" : ""}`, - structural ? "" : spec.cell(stationIndex), + `b05-profile-table__cell${structural ? " is-structure" : ""}${mc ? " b06-station--mc" : ""}`, + structural ? "" : spec.cell(stationIndex) + (mc ? " (MC)" : ""), ); const rotated = stationThickness[stationIndex]; if (rotated !== null) { @@ -649,6 +663,7 @@ export function createProfileTable(options: ProfileTableOptions): HTMLElement { // 선택된 측점(규칙·비정규 공용)을 **값 열 오버레이**로 강조한다. // 규칙 측점은 종점 이동을 반영한 `centers[index]`, 비정규 측점은 `x(chainage)`를 중심으로 쓴다. const selectedIrregular = selectedIrregularEntry; + const mcAt = (chainageM: number): boolean => options.mcKeys?.has(chainageM.toFixed(3)) ?? false; const selectedRegularIndex = selectedStationRow ? alignment.stations.indexOf(selectedStationRow) : -1; @@ -665,6 +680,7 @@ export function createProfileTable(options: ProfileTableOptions): HTMLElement { display, options.onAdjustStation, true, + mcAt(selectedIrregular.chainage_m), ), ); } else if (selectedRegularIndex >= 0) { @@ -678,6 +694,7 @@ export function createProfileTable(options: ProfileTableOptions): HTMLElement { display, options.onAdjustStation, false, + mcAt(alignment.stations[selectedRegularIndex].chainage_m), ), ); } diff --git a/B05_Profile/B05_Profile_UI_Style_Table.css b/B05_Profile/B05_Profile_UI_Style_Table.css index 6860b155a..51be9fe46 100644 --- a/B05_Profile/B05_Profile_UI_Style_Table.css +++ b/B05_Profile/B05_Profile_UI_Style_Table.css @@ -288,6 +288,12 @@ color: rgb(37 99 235); } +/* MC 측점 이름(507-3 덧) — 굵게 · 경고색 */ +.b05-profile-table__irregular-col-cell.is-mc { + color: var(--color-warning); + font-weight: var(--font-weight-bold); +} + /* 계획고(is-plan)는 오버레이에서 별도 강조 없이 다른 값과 동일한 스타일을 쓴다(작업 C-2). */ /* 값 열의 계획고 직접 입력 셀 — 열은 pointer-events:none이라 입력만 되살린다. */ diff --git a/B07_DesignDetail/B07_DesignDetail_Router_Support.py b/B07_DesignDetail/B07_DesignDetail_Router_Support.py index 1f61b6729..17e9e38f8 100644 --- a/B07_DesignDetail/B07_DesignDetail_Router_Support.py +++ b/B07_DesignDetail/B07_DesignDetail_Router_Support.py @@ -221,6 +221,7 @@ def _drawing_list(project_root: Path, longitudinal_path: Path) -> list[DesignDra chainages = sheet["chainages"] first = station_by_chainage.get(chainages[0], {}) station = station_no_label(float(first.get("chainage_m", chainages[0])), interval) + station += mc_suffix(first) drawings.append( DesignDrawingItem( id=sheet["id"], diff --git a/B07_DesignDetail/B07_DesignDetail_Router_Support_Plan.py b/B07_DesignDetail/B07_DesignDetail_Router_Support_Plan.py index 44c539b14..1905ec8da 100644 --- a/B07_DesignDetail/B07_DesignDetail_Router_Support_Plan.py +++ b/B07_DesignDetail/B07_DesignDetail_Router_Support_Plan.py @@ -25,7 +25,11 @@ from B05_Profile.B05_Profile_Engine_Sections import ( ) from B05_Profile.B05_Profile_Structures_Repository import load_structures from B06_Section.B06_Section_Engine_Design import compute_cross_design, curve_widening_args -from B07_DesignDetail.B07_DesignDetail_Engine_Cad import infer_station_interval, station_no_label +from B07_DesignDetail.B07_DesignDetail_Engine_Cad import ( + infer_station_interval, + mc_suffix, + station_no_label, +) from B07_DesignDetail.B07_DesignDetail_Engine_Cad_IpTable import clip_lines, ip_table_piece from B07_DesignDetail.B07_DesignDetail_Engine_Cad_Plan import ( _structure_label, @@ -502,6 +506,11 @@ def build_pieces( pieces["ip"], pieces["radius"] = curve_pieces(_load_design_curves(project_root), route, box) if "station" in wanted: items = [] + curve_at = { + float(item["chainage_m"]) + for item in longitudinal.get("stations") or [] + if mc_suffix(item) and isinstance(item.get("chainage_m"), (int, float)) + } for chainage, x, y in stations: if not (start_m <= chainage <= end_m): continue @@ -509,6 +518,8 @@ def build_pieces( label = station_no_label(chainage, interval).replace("No.", "NO.") number_only = "+" not in label major = number_only and int(label[3:]) % 5 == 0 + if chainage in curve_at: # MC = 고칠 자리(507-3) — 5 배수가 아니어도 글 · 큰 글(major) + label, major = label + " (MC)", True items.append( { "chainage_m": chainage, diff --git a/B07_DesignDetail/B07_DesignDetail_UI_Panels.ts b/B07_DesignDetail/B07_DesignDetail_UI_Panels.ts index 7f805e943..5366a0934 100644 --- a/B07_DesignDetail/B07_DesignDetail_UI_Panels.ts +++ b/B07_DesignDetail/B07_DesignDetail_UI_Panels.ts @@ -178,7 +178,10 @@ export function buildDrawingSidePanel( } for (const drawing of items) { // 횡단면도는 장 단위(여러 측점)라 서버가 준 "N장 (구간)" 라벨을 그대로 쓴다. - section.append(drawingButton(drawing, drawing.label)); + const button = drawingButton(drawing, drawing.label); + // 이름 뒤 「 (MC)」(507-3 덧) — 한 칸에 안 들어가 두 열을 가로지름 + if (drawing.label.includes("(MC)")) button.dataset.mc = "true"; + section.append(button); } if (structure && items.length === 0) { const empty = document.createElement("p"); diff --git a/B07_DesignDetail/B07_DesignDetail_UI_Style.css b/B07_DesignDetail/B07_DesignDetail_UI_Style.css index 08a7af3f9..552deb3d5 100644 --- a/B07_DesignDetail/B07_DesignDetail_UI_Style.css +++ b/B07_DesignDetail/B07_DesignDetail_UI_Style.css @@ -70,6 +70,11 @@ grid-column: 1 / -1; } +/* MC 장 단추(507-3 덧) — 이름이 길어 한 줄 전체 */ +.b07-drawing-group[data-kind="cross"] .b07-drawing-button[data-mc="true"] { + grid-column: 1 / -1; +} + .b07-drawing-button { display: flex; align-items: center; diff --git a/resources/tester/test_507_3_mc_label.py b/resources/tester/test_507_3_mc_label.py index da93b8ca6..02b8d94ca 100644 --- a/resources/tester/test_507_3_mc_label.py +++ b/resources/tester/test_507_3_mc_label.py @@ -87,3 +87,39 @@ def test_b05_b06_screen_mc_emphasis() -> None: assert 'themeColor("--color-warning"' in ticks swap = _src("B06_Section/logic/B06_Section_Station_Swap.ts") assert 'formatStation(station.chainageM) + (kind === "curve" ? " (MC)" : "")' in swap + + +def test_b07_plan_station_mc_label() -> None: + """계획평면도 측점 글 = 5 배수만이었음 → MC 도 큰 글(major) + (MC).""" + plan = _src("B07_DesignDetail/B07_DesignDetail_Router_Support_Plan.py") + assert 'label, major = label + " (MC)", True' in plan + assert "if mc_suffix(item)" in plan # 종단 kind curve 측점만 + + +def test_b07_sheet_button_mc() -> None: + """B07 횡단 장 단추 「3장 (No.29+19.0 (MC))」 — 그 장 첫 측점이 MC 일 때.""" + support = _src("B07_DesignDetail/B07_DesignDetail_Router_Support.py") + assert "station += mc_suffix(first)" in support + assert "label=f\"{sheet['number']}장 ({station})\"" in support + + +def test_b05_profile_table_station_mc() -> None: + table = _src("B05_Profile/B05_Profile_UI_Profile_Table.ts") + # 칸 글 · 굵게 · 경고색 = B06 공용 규칙 · 칸 폭 판정(`longest`)은 (MC) 없는 글 그대로 + assert 'spec.cell(stationIndex) + (mc ? " (MC)" : "")' in table + assert '${mc ? " b06-station--mc" : ""}' in table + assert 'display.station) + (mc ? " (MC)" : "")' in table # 고른 측점 값 열 + render = _src("B05_Profile/B05_Profile_UI_Profile_Render.ts") + assert 'station.kind === "curve"' in render + css = _src("B05_Profile/B05_Profile_UI_Style_Table.css") + body = css[css.index("__irregular-col-cell.is-mc {") : css.index("}", css.index(".is-mc {"))] + assert "var(--color-warning)" in body + assert "var(--font-weight-bold)" in body + + +def test_b07_sheet_button_spans_row() -> None: + panels = _src("B07_DesignDetail/B07_DesignDetail_UI_Panels.ts") + assert 'button.dataset.mc = "true"' in panels + css = _src("B07_DesignDetail/B07_DesignDetail_UI_Style.css") + rule = css[css.index('.b07-drawing-button[data-mc="true"] {') :] + assert "grid-column: 1 / -1" in rule[: rule.index("}")]